def render(self, real=False): '''Return a tuple (width, height) to create the image with the user constraints. (width, height) includes the padding. ''' if real: return self._render_real() options = copy(self.options) options['space_width'] = self.get_extents(' ')[0] options['strip'] = strip = (options['strip'] or options['halign'] == 'justify') uw, uh = options['text_size'] = self._text_size text = self.text if strip: text = text.strip() self.is_shortened = False if uw is not None and options['shorten']: text = self.shorten(text) self._cached_lines = lines = [] if not text: return 0, 0 if uh is not None and (options['valign'] == 'middle' or options['valign'] == 'center'): center = -1 # pos of newline if len(text) > 1: middle = int(len(text) // 2) l, r = text.rfind('\n', 0, middle), text.find('\n', middle) if l != -1 and r != -1: center = l if center - l <= r - center else r elif l != -1: center = l elif r != -1: center = r # if a newline split text, render from center down and up til uh if center != -1: # layout from center down until half uh w, h, clipped = layout_text(text[center + 1:], lines, (0, 0), (uw, uh / 2), options, self.get_cached_extents(), True, True) # now layout from center upwards until uh is reached w, h, clipped = layout_text(text[:center + 1], lines, (w, h), (uw, uh), options, self.get_cached_extents(), False, True) else: # if there's no new line, layout everything w, h, clipped = layout_text(text, lines, (0, 0), (uw, None), options, self.get_cached_extents(), True, True) else: # top or bottom w, h, clipped = layout_text(text, lines, (0, 0), (uw, uh), options, self.get_cached_extents(), options['valign'] == 'top', True) self._internal_size = w, h if uw: w = uw if uh: h = uh if h > 1 and w < 2: w = 2 return int(w), int(h)
def render(self, real=False): """Return a tuple (width, height) to create the image with the user constraints. (width, height) includes the padding. """ if real: return self._render_real() options = copy(self.options) options["space_width"] = self.get_extents(" ")[0] options["strip"] = strip = options["strip"] or options["halign"][-1] == "y" uw, uh = options["text_size"] = self._text_size text = self.text if strip: text = text.strip() if uw is not None and options["shorten"]: text = self.shorten(text) self._cached_lines = lines = [] if not text: return 0, 0 if uh is not None and options["valign"][-1] == "e": # middle center = -1 # pos of newline if len(text) > 1: middle = int(len(text) // 2) l, r = text.rfind("\n", 0, middle), text.find("\n", middle) if l != -1 and r != -1: center = l if center - l <= r - center else r elif l != -1: center = l elif r != -1: center = r # if a newline split text, render from center down and up til uh if center != -1: # layout from center down until half uh w, h, clipped = layout_text( text[center + 1 :], lines, (0, 0), (uw, uh / 2), options, self.get_cached_extents(), True, True ) # now layout from center upwards until uh is reached w, h, clipped = layout_text( text[: center + 1], lines, (w, h), (uw, uh), options, self.get_cached_extents(), False, True ) else: # if there's no new line, layout everything w, h, clipped = layout_text( text, lines, (0, 0), (uw, None), options, self.get_cached_extents(), True, True ) else: # top or bottom w, h, clipped = layout_text( text, lines, (0, 0), (uw, uh), options, self.get_cached_extents(), options["valign"][-1] == "p", True ) self._internal_size = w, h if uw: w = uw if uh: h = uh if h > 1 and w < 2: w = 2 return int(w), int(h)
def render(self, real=False): '''Return a tuple (width, height) to create the image with the user constraints. (width, height) includes the padding. ''' if real: return self._render_real() options = copy(self.options) options['space_width'] = self.get_extents(' ')[0] options['strip'] = strip = (options['strip'] or options['halign'][-1] == 'y') uw, uh = options['text_size'] = self._text_size text = self.text if not strip: # all text will be stripped by default. unicode NO-BREAK SPACE # characters will be preserved, so we replace the leading and # trailing spaces with \u00a0 text = text.decode('utf8', errors=options['unicode_errors']) if isinstance( text, bytes) else text lspace = len(text) - len(text.lstrip()) rspace = len(text) - len(text.rstrip()) text = (u'\u00a0' * lspace) + text.strip() + (u'\u00a0' * rspace) if uw is not None and options['shorten']: text = self.shorten(text) self._cached_lines = lines = [] if not text: return 0, 0 ostrip = options['strip'] strip = options['strip'] = True if uh is not None and options['valign'][-1] == 'e': # middle center = -1 # pos of newline if len(text) > 1: middle = int(len(text) // 2) l, r = text.rfind('\n', 0, middle), text.find('\n', middle) if l != -1 and r != -1: center = l if center - l <= r - center else r elif l != -1: center = l elif r != -1: center = r # if a newline split text, render from center down and up til uh if center != -1: # layout from center down until half uh w, h, clipped = layout_text(text[center + 1:], lines, (0, 0), (uw, uh / 2), options, self.get_cached_extents(), True, True) # now layout from center upwards until uh is reached w, h, clipped = layout_text(text[:center + 1], lines, (w, h), (uw, uh), options, self.get_cached_extents(), False, True) else: # if there's no new line, layout everything w, h, clipped = layout_text(text, lines, (0, 0), (uw, None), options, self.get_cached_extents(), True, True) else: # top or bottom w, h, clipped = layout_text(text, lines, (0, 0), (uw, uh), options, self.get_cached_extents(), options['valign'][-1] == 'p', True) options['strip'] = ostrip self._internal_size = w, h if uw: w = uw if uh: h = uh if h > 1 and w < 2: w = 2 return int(w), int(h)
def _pre_render(self): # split markup, words, and lines # result: list of word with position and width/height # during the first pass, we don't care about h/valign self._cached_lines = lines = [] self._refs = {} self._anchors = {} clipped = False w = h = 0 uw, uh = self.text_size spush = self._push_style spop = self._pop_style options = self.options options['_ref'] = None options['_anchor'] = None options['script'] = 'normal' shorten = options['shorten'] # if shorten, then don't split lines to fit uw, because it will be # flattened later when shortening and broken up lines if broken # mid-word will have space mid-word when lines are joined uw_temp = None if shorten else uw xpad = options['padding_x'] uhh = (None if uh is not None and options['valign'] != 'top' or options['shorten'] else uh) options['strip'] = options['strip'] or options['halign'] == 'justify' find_base_dir = Label.find_base_direction base_dir = options['base_direction'] self._resolved_base_dir = None for item in self.markup: if item == '[b]': spush('bold') options['bold'] = True self.resolve_font_name() elif item == '[/b]': spop('bold') self.resolve_font_name() elif item == '[i]': spush('italic') options['italic'] = True self.resolve_font_name() elif item == '[/i]': spop('italic') self.resolve_font_name() elif item == '[u]': spush('underline') options['underline'] = True self.resolve_font_name() elif item == '[/u]': spop('underline') self.resolve_font_name() elif item == '[s]': spush('strikethrough') options['strikethrough'] = True self.resolve_font_name() elif item == '[/s]': spop('strikethrough') self.resolve_font_name() elif item[:6] == '[size=': item = item[6:-1] try: if item[-2:] in ('px', 'pt', 'in', 'cm', 'mm', 'dp', 'sp'): size = dpi2px(item[:-2], item[-2:]) else: size = int(item) except ValueError: raise size = options['font_size'] spush('font_size') options['font_size'] = size elif item == '[/size]': spop('font_size') elif item[:7] == '[color=': color = parse_color(item[7:-1]) spush('color') options['color'] = color elif item == '[/color]': spop('color') elif item[:6] == '[font=': fontname = item[6:-1] spush('font_name') options['font_name'] = fontname self.resolve_font_name() elif item == '[/font]': spop('font_name') self.resolve_font_name() elif item[:13] == '[font_family=': spush('font_family') options['font_family'] = item[13:-1] elif item == '[/font_family]': spop('font_family') elif item[:14] == '[font_context=': fctx = item[14:-1] if not fctx or fctx.lower() == 'none': fctx = None spush('font_context') options['font_context'] = fctx elif item == '[/font_context]': spop('font_context') elif item[:15] == '[font_features=': spush('font_features') options['font_features'] = item[15:-1] elif item == '[/font_features]': spop('font_features') elif item[:15] == '[text_language=': lang = item[15:-1] if not lang or lang.lower() == 'none': lang = None spush('text_language') options['text_language'] = lang elif item == '[/text_language]': spop('text_language') elif item[:5] == '[sub]': spush('font_size') spush('script') options['font_size'] = options['font_size'] * .5 options['script'] = 'subscript' elif item == '[/sub]': spop('font_size') spop('script') elif item[:5] == '[sup]': spush('font_size') spush('script') options['font_size'] = options['font_size'] * .5 options['script'] = 'superscript' elif item == '[/sup]': spop('font_size') spop('script') elif item[:5] == '[ref=': ref = item[5:-1] spush('_ref') options['_ref'] = ref elif item == '[/ref]': spop('_ref') elif not clipped and item[:8] == '[anchor=': options['_anchor'] = item[8:-1] elif not clipped: item = item.replace('&bl;', '[').replace('&br;', ']').replace('&', '&') if not base_dir: base_dir = self._resolved_base_dir = find_base_dir(item) opts = copy(options) extents = self.get_cached_extents() opts['space_width'] = extents(' ')[0] w, h, clipped = layout_text(item, lines, (w, h), (uw_temp, uhh), opts, extents, append_down=True, complete=False) if len(lines): # remove any trailing spaces from the last line old_opts = self.options self.options = copy(opts) w, h, clipped = layout_text('', lines, (w, h), (uw_temp, uhh), self.options, self.get_cached_extents(), append_down=True, complete=True) self.options = old_opts self.is_shortened = False if shorten: options['_ref'] = None # no refs for you! options['_anchor'] = None w, h, lines = self.shorten_post(lines, w, h) self._cached_lines = lines # when valign is not top, for markup we layout everything (text_size[1] # is temporarily set to None) and after layout cut to size if too tall elif uh != uhh and h > uh and len(lines) > 1: if options['valign'] == 'bottom': i = 0 while i < len(lines) - 1 and h > uh: h -= lines[i].h i += 1 del lines[:i] else: # middle i = 0 top = int(h / 2. + uh / 2.) # remove extra top portion while i < len(lines) - 1 and h > top: h -= lines[i].h i += 1 del lines[:i] i = len(lines) - 1 # remove remaining bottom portion while i and h > uh: h -= lines[i].h i -= 1 del lines[i + 1:] # now justify the text if options['halign'] == 'justify' and uw is not None: # XXX: update refs to justified pos # when justify, each line should've been stripped already split = partial(re.split, re.compile('( +)')) uww = uw - 2 * xpad chr = type(self.text) space = chr(' ') empty = chr('') for i in range(len(lines)): line = lines[i] words = line.words # if there's nothing to justify, we're done if (not line.w or int(uww - line.w) <= 0 or not len(words) or line.is_last_line): continue done = False parts = [ None, ] * len(words) # contains words split by space idxs = [ None, ] * len(words) # indices of the space in parts # break each word into spaces and add spaces until it's full # do first round of split in case we don't need to split all for w in range(len(words)): word = words[w] sw = word.options['space_width'] p = parts[w] = split(word.text) idxs[w] = [ v for v in range(len(p)) if p[v].startswith(' ') ] # now we have the indices of the spaces in split list for k in idxs[w]: # try to add single space at each space if line.w + sw > uww: done = True break line.w += sw word.lw += sw p[k] += space if done: break # there's not a single space in the line? if not any(idxs): continue # now keep adding spaces to already split words until done while not done: for w in range(len(words)): if not idxs[w]: continue word = words[w] sw = word.options['space_width'] p = parts[w] for k in idxs[w]: # try to add single space at each space if line.w + sw > uww: done = True break line.w += sw word.lw += sw p[k] += space if done: break # if not completely full, push last words to right edge diff = int(uww - line.w) if diff > 0: # find the last word that had a space for w in range(len(words) - 1, -1, -1): if not idxs[w]: continue break old_opts = self.options self.options = word.options word = words[w] # split that word into left/right and push right till uww l_text = empty.join(parts[w][:idxs[w][-1]]) r_text = empty.join(parts[w][idxs[w][-1]:]) left = LayoutWord(word.options, self.get_extents(l_text)[0], word.lh, l_text) right = LayoutWord(word.options, self.get_extents(r_text)[0], word.lh, r_text) left.lw = max(left.lw, word.lw + diff - right.lw) self.options = old_opts # now put words back together with right/left inserted for k in range(len(words)): if idxs[k]: words[k].text = empty.join(parts[k]) words[w] = right words.insert(w, left) else: for k in range(len(words)): if idxs[k]: words[k].text = empty.join(parts[k]) line.w = uww w = max(w, uww) self._internal_size = w, h if uw: w = uw if uh: h = uh if h > 1 and w < 2: w = 2 if w < 1: w = 1 if h < 1: h = 1 return int(w), int(h)
def _pre_render(self): # split markup, words, and lines # result: list of word with position and width/height # during the first pass, we don't care about h/valign self._cached_lines = lines = [] self._refs = {} self._anchors = {} clipped = False w = h = 0 uw, uh = self.text_size spush = self._push_style spop = self._pop_style opts = options = self.options options["_ref"] = None options["_anchor"] = None options["script"] = "normal" shorten = options["shorten"] # if shorten, then don't split lines to fit uw, because it will be # flattened later when shortening and broken up lines if broken # mid-word will have space mid-word when lines are joined uw_temp = None if shorten else uw xpad = options["padding_x"] uhh = None if uh is not None and options["valign"][-1] != "p" or options["shorten"] else uh options["strip"] = options["strip"] or options["halign"][-1] == "y" for item in self.markup: if item == "[b]": spush("bold") options["bold"] = True self.resolve_font_name() elif item == "[/b]": spop("bold") self.resolve_font_name() elif item == "[i]": spush("italic") options["italic"] = True self.resolve_font_name() elif item == "[/i]": spop("italic") self.resolve_font_name() elif item[:6] == "[size=": item = item[6:-1] try: if item[-2:] in ("px", "pt", "in", "cm", "mm", "dp", "sp"): size = dpi2px(item[:-2], item[-2:]) else: size = int(item) except ValueError: raise size = options["font_size"] spush("font_size") options["font_size"] = size elif item == "[/size]": spop("font_size") elif item[:7] == "[color=": color = parse_color(item[7:-1]) spush("color") options["color"] = color elif item == "[/color]": spop("color") elif item[:6] == "[font=": fontname = item[6:-1] spush("font_name") options["font_name"] = fontname self.resolve_font_name() elif item == "[/font]": spop("font_name") self.resolve_font_name() elif item[:5] == "[sub]": spush("font_size") spush("script") options["font_size"] = options["font_size"] * 0.5 options["script"] = "subscript" elif item == "[/sub]": spop("font_size") spop("script") elif item[:5] == "[sup]": spush("font_size") spush("script") options["font_size"] = options["font_size"] * 0.5 options["script"] = "superscript" elif item == "[/sup]": spop("font_size") spop("script") elif item[:5] == "[ref=": ref = item[5:-1] spush("_ref") options["_ref"] = ref elif item == "[/ref]": spop("_ref") elif not clipped and item[:8] == "[anchor=": options["_anchor"] = item[8:-1] elif not clipped: item = item.replace("&bl;", "[").replace("&br;", "]").replace("&", "&") opts = copy(options) extents = self.get_cached_extents() opts["space_width"] = extents(" ")[0] w, h, clipped = layout_text(item, lines, (w, h), (uw_temp, uhh), opts, extents, True, False) if len(lines): # remove any trailing spaces from the last line old_opts = self.options self.options = copy(opts) w, h, clipped = layout_text( "", lines, (w, h), (uw_temp, uhh), self.options, self.get_cached_extents(), True, True ) self.options = old_opts if shorten: options["_ref"] = None # no refs for you! options["_anchor"] = None w, h, lines = self.shorten_post(lines, w, h) self._cached_lines = lines # when valign is not top, for markup we layout everything (text_size[1] # is temporarily set to None) and after layout cut to size if too tall elif uh != uhh and h > uh and len(lines) > 1: if options["valign"][-1] == "m": # bottom i = 0 while i < len(lines) - 1 and h > uh: h -= lines[i].h i += 1 del lines[:i] else: # middle i = 0 top = int(h / 2.0 + uh / 2.0) # remove extra top portion while i < len(lines) - 1 and h > top: h -= lines[i].h i += 1 del lines[:i] i = len(lines) - 1 # remove remaining bottom portion while i and h > uh: h -= lines[i].h i -= 1 del lines[i + 1 :] # now justify the text if options["halign"][-1] == "y" and uw is not None: # XXX: update refs to justified pos # when justify, each line shouldv'e been stripped already split = partial(re.split, re.compile("( +)")) uww = uw - 2 * xpad chr = type(self.text) space = chr(" ") empty = chr("") for i in range(len(lines)): line = lines[i] words = line.words # if there's nothing to justify, we're done if not line.w or int(uww - line.w) <= 0 or not len(words) or line.is_last_line: continue done = False parts = [None] * len(words) # contains words split by space idxs = [None] * len(words) # indices of the space in parts # break each word into spaces and add spaces until it's full # do first round of split in case we don't need to split all for w in range(len(words)): word = words[w] sw = word.options["space_width"] p = parts[w] = split(word.text) idxs[w] = [v for v in range(len(p)) if p[v].startswith(" ")] # now we have the indices of the spaces in split list for k in idxs[w]: # try to add single space at each space if line.w + sw > uww: done = True break line.w += sw word.lw += sw p[k] += space if done: break # there's not a single space in the line? if not any(idxs): continue # now keep adding spaces to already split words until done while not done: for w in range(len(words)): if not idxs[w]: continue word = words[w] sw = word.options["space_width"] p = parts[w] for k in idxs[w]: # try to add single space at each space if line.w + sw > uww: done = True break line.w += sw word.lw += sw p[k] += space if done: break # if not completely full, push last words to right edge diff = int(uww - line.w) if diff > 0: # find the last word that had a space for w in range(len(words) - 1, -1, -1): if not idxs[w]: continue break old_opts = self.options self.options = word.options word = words[w] # split that word into left/right and push right till uww l_text = empty.join(parts[w][: idxs[w][-1]]) r_text = empty.join(parts[w][idxs[w][-1] :]) left = LayoutWord(word.options, self.get_extents(l_text)[0], word.lh, l_text) right = LayoutWord(word.options, self.get_extents(r_text)[0], word.lh, r_text) left.lw = max(left.lw, word.lw + diff - right.lw) self.options = old_opts # now put words back together with right/left inserted for k in range(len(words)): if idxs[k]: words[k].text = empty.join(parts[k]) words[w] = right words.insert(w, left) else: for k in range(len(words)): if idxs[k]: words[k].text = empty.join(parts[k]) line.w = uww w = max(w, uww) self._internal_size = w, h if uw: w = uw if uh: h = uh if h > 1 and w < 2: w = 2 if w < 1: w = 1 if h < 1: h = 1 return int(w), int(h)
def render(self, real=False): '''Return a tuple (width, height) to create the image with the user constraints. (width, height) includes the padding. ''' if real: return self._render_real() options = copy(self.options) options['space_width'] = self.get_extents(' ')[0] options['strip'] = strip = (options['strip'] or options['halign'][-1] == 'y') uw, uh = options['text_size'] = self._text_size text = self.text if not strip: # all text will be stripped by default. unicode NO-BREAK SPACE # characters will be preserved, so we replace the leading and # trailing spaces with \u00a0 text = text.decode('utf8') if isinstance(text, bytes) else text lspace = len(text) - len(text.lstrip()) rspace = len(text) - len(text.rstrip()) text = (u'\u00a0' * lspace) + text.strip() + (u'\u00a0' * rspace) if uw is not None and options['shorten']: text = self.shorten(text) self._cached_lines = lines = [] if not text: return 0, 0 ostrip = options['strip'] strip = options['strip'] = True if uh is not None and options['valign'][-1] == 'e': # middle center = -1 # pos of newline if len(text) > 1: middle = int(len(text) // 2) l, r = text.rfind('\n', 0, middle), text.find('\n', middle) if l != -1 and r != -1: center = l if center - l <= r - center else r elif l != -1: center = l elif r != -1: center = r # if a newline split text, render from center down and up til uh if center != -1: # layout from center down until half uh w, h, clipped = layout_text(text[center + 1:], lines, (0, 0), (uw, uh / 2), options, self.get_cached_extents(), True, True) # now layout from center upwards until uh is reached w, h, clipped = layout_text(text[:center + 1], lines, (w, h), (uw, uh), options, self.get_cached_extents(), False, True) else: # if there's no new line, layout everything w, h, clipped = layout_text(text, lines, (0, 0), (uw, None), options, self.get_cached_extents(), True, True) else: # top or bottom w, h, clipped = layout_text(text, lines, (0, 0), (uw, uh), options, self.get_cached_extents(), options['valign'][-1] == 'p', True) options['strip'] = ostrip self._internal_size = w, h if uw: w = uw if uh: h = uh if h > 1 and w < 2: w = 2 return int(w), int(h)
def _pre_render(self): # split markup, words, and lines # result: list of word with position and width/height # during the first pass, we don't care about h/valign self._cached_lines = lines = [] self._refs = {} self._anchors = {} clipped = False w = h = 0 uw, uh = self.text_size spush = self._push_style spop = self._pop_style opts = options = self.options options['_ref'] = None options['script'] = 'normal' shorten = options['shorten'] # if shorten, then don't split lines to fit uw, because it will be # flattened later when shortening and broken up lines if broken # mid-word will have space mid-word when lines are joined uw_temp = None if shorten else uw xpad = options['padding_x'] uhh = (None if uh is not None and options['valign'][-1] != 'p' or options['shorten'] else uh) options['strip'] = options['strip'] or options['halign'][-1] == 'y' for item in self.markup: if item == '[b]': spush('bold') options['bold'] = True self.resolve_font_name() elif item == '[/b]': spop('bold') self.resolve_font_name() elif item == '[i]': spush('italic') options['italic'] = True self.resolve_font_name() elif item == '[/i]': spop('italic') self.resolve_font_name() elif item[:6] == '[size=': item = item[6:-1] try: if item[-2:] in ('px', 'pt', 'in', 'cm', 'mm', 'dp', 'sp'): size = dpi2px(item[:-2], item[-2:]) else: size = int(item) except ValueError: raise size = options['font_size'] spush('font_size') options['font_size'] = size elif item == '[/size]': spop('font_size') elif item[:7] == '[color=': color = parse_color(item[7:-1]) spush('color') options['color'] = color elif item == '[/color]': spop('color') elif item[:6] == '[font=': fontname = item[6:-1] spush('font_name') options['font_name'] = fontname self.resolve_font_name() elif item == '[/font]': spop('font_name') self.resolve_font_name() elif item[:5] == '[sub]': spush('font_size') spush('script') options['font_size'] = options['font_size'] * .5 options['script'] = 'subscript' elif item == '[/sub]': spop('font_size') spop('script') elif item[:5] == '[sup]': spush('font_size') spush('script') options['font_size'] = options['font_size'] * .5 options['script'] = 'superscript' elif item == '[/sup]': spop('font_size') spop('script') elif item[:5] == '[ref=': ref = item[5:-1] spush('_ref') options['_ref'] = ref elif item == '[/ref]': spop('_ref') elif not clipped and item[:8] == '[anchor=': ref = item[8:-1] if len(lines): x, y = lines[-1].x, lines[-1].y else: x = y = 0 self._anchors[ref] = x, y elif not clipped: item = item.replace('&bl;', '[').replace( '&br;', ']').replace('&', '&') opts = copy(options) extents = self.get_cached_extents() opts['space_width'] = extents(' ')[0] w, h, clipped = layout_text(item, lines, (w, h), (uw_temp, uhh), opts, extents, True, False) if len(lines): # remove any trailing spaces from the last line old_opts = self.options self.options = copy(opts) w, h, clipped = layout_text('', lines, (w, h), (uw_temp, uhh), self.options, self.get_cached_extents(), True, True) self.options = old_opts if shorten: options['_ref'] = None # no refs for you! w, h, lines = self.shorten_post(lines, w, h) self._cached_lines = lines # when valign is not top, for markup we layout everything (text_size[1] # is temporarily set to None) and after layout cut to size if too tall elif uh != uhh and h > uh and len(lines) > 1: if options['valign'][-1] == 'm': # bottom i = 0 while i < len(lines) - 1 and h > uh: h -= lines[i].h i += 1 del lines[:i] else: # middle i = 0 top = int(h / 2. + uh / 2.) # remove extra top portion while i < len(lines) - 1 and h > top: h -= lines[i].h i += 1 del lines[:i] i = len(lines) - 1 # remove remaining bottom portion while i and h > uh: h -= lines[i].h i -= 1 del lines[i + 1:] # now justify the text if options['halign'][-1] == 'y' and uw is not None: # XXX: update refs to justified pos # when justify, each line shouldv'e been stripped already split = partial(re.split, re.compile('( +)')) uww = uw - 2 * xpad chr = type(self.text) space = chr(' ') empty = chr('') for i in range(len(lines)): line = lines[i] words = line.words # if there's nothing to justify, we're done if (not line.w or int(uww - line.w) <= 0 or not len(words) or line.is_last_line): continue done = False parts = [None, ] * len(words) # contains words split by space idxs = [None, ] * len(words) # indices of the space in parts # break each word into spaces and add spaces until it's full # do first round of split in case we don't need to split all for w in range(len(words)): word = words[w] sw = word.options['space_width'] p = parts[w] = split(word.text) idxs[w] = [v for v in range(len(p)) if p[v].startswith(' ')] # now we have the indices of the spaces in split list for k in idxs[w]: # try to add single space at each space if line.w + sw > uww: done = True break line.w += sw word.lw += sw p[k] += space if done: break # there's not a single space in the line? if not any(idxs): continue # now keep adding spaces to already split words until done while not done: for w in range(len(words)): if not idxs[w]: continue word = words[w] sw = word.options['space_width'] p = parts[w] for k in idxs[w]: # try to add single space at each space if line.w + sw > uww: done = True break line.w += sw word.lw += sw p[k] += space if done: break # if not completely full, push last words to right edge diff = int(uww - line.w) if diff > 0: # find the last word that had a space for w in range(len(words) - 1, -1, -1): if not idxs[w]: continue break old_opts = self.options self.options = word.options word = words[w] # split that word into left/right and push right till uww l_text = empty.join(parts[w][:idxs[w][-1]]) r_text = empty.join(parts[w][idxs[w][-1]:]) left = LayoutWord(word.options, self.get_extents(l_text)[0], word.lh, l_text) right = LayoutWord(word.options, self.get_extents(r_text)[0], word.lh, r_text) left.lw = max(left.lw, word.lw + diff - right.lw) self.options = old_opts # now put words back together with right/left inserted for k in range(len(words)): if idxs[k]: words[k].text = empty.join(parts[k]) words[w] = right words.insert(w, left) else: for k in range(len(words)): if idxs[k]: words[k].text = empty.join(parts[k]) line.w = uww w = max(w, uww) self._internal_size = w, h if uw: w = uw if uh: h = uh if h > 1 and w < 2: w = 2 if w < 1: w = 1 if h < 1: h = 1 return w, h