Example #1
0
    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)
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
    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('&amp;', '&')
                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)
Example #6
0
    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("&amp;", "&")
                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)
Example #7
0
    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)
Example #8
0
    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('&amp;', '&')
                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