Wordwrap: prevent infinite loop + cache result

Previously a line that was too long but didn't contain spaces would cause
an infinite loop and hang Blender. Now we just hard-break each line when
there are no spaces to be found.

The result of word_wrap() is now also cached, so that redraws are more
efficient.
This commit is contained in:
2018-08-14 15:37:15 +02:00
parent 7b88d7704c
commit 2da2105763

View File

@@ -1,3 +1,4 @@
import functools
import os.path
import blf
@@ -249,15 +250,18 @@ def _after_submission_text() -> str:
return text
def word_wrap(string: str, width_in_px: int) -> str:
@functools.lru_cache()
def word_wrap(string: str, width_in_px: int, *, depth=0) -> str:
"""Word-wrapping with variable character width.
Newlines in the input string are kept in the output.
"""
assert depth < 3
if '\n' in string:
# If the string already consists of multiple lines, wrap each line individually.
return '\n'.join(word_wrap(line, width_in_px)
return '\n'.join(word_wrap(line, width_in_px, depth=depth + 1)
for line in string.splitlines(keepends=False))
# Do an estimate of the maximum number of characters to fit on a line.
@@ -271,10 +275,19 @@ def word_wrap(string: str, width_in_px: int) -> str:
line_width = blf.dimensions(font_id, candidate_line)[0]
# Keep removing the last word until the line fits the width.
while line_width >= width_in_px:
marker = len(candidate_line) - 1
while not candidate_line[marker].isspace():
marker -= 1
break_at_space = True
while candidate_line and line_width >= width_in_px:
if break_at_space:
marker = len(candidate_line) - 1
while marker > 0 and not candidate_line[marker].isspace():
marker -= 1
if marker <= 0:
# This line was unbreakable by whitespace. Let's just hard-break it.
break_at_space = False
continue
else:
marker = len(candidate_line) - 1
candidate_line = candidate_line[:marker]
line_width = blf.dimensions(font_id, candidate_line)[0]