Example #1
0
    def render_shape(self, drawer, _, **kwargs):
        fill = kwargs.get('fill')

        # draw outer circle
        r = self.radius
        box = Box(self.center.x - r, self.center.y - r,
                  self.center.x + r, self.center.y + r)
        if kwargs.get('shadow'):
            box = self.shift_shadow(box)
            if kwargs.get('style') == 'blur':
                drawer.ellipse(box, fill=fill, outline=fill,
                               filter='transp-blur')
            else:
                drawer.ellipse(box, fill=fill, outline=fill)
        else:
            drawer.ellipse(box, fill='white', outline=self.node.linecolor,
                           style=self.node.style)

        # draw inner circle
        box = Box(self.center.x - r / 2, self.center.y - r / 2,
                  self.center.x + r / 2, self.center.y + r / 2)
        if not kwargs.get('shadow'):
            if self.node.color == self.node.basecolor:
                color = self.node.linecolor
            else:
                color = self.node.color

            drawer.ellipse(box, fill=color, outline=self.node.linecolor,
                           style=self.node.style)
Example #2
0
    def __init__(self, node, metrics):
        self.node = node
        self.metrics = metrics

        m = self.metrics.cell(self.node)
        self.textalign = 'center'
        self.connectors = [m.top, m.right, m.bottom, m.left]

        if node.icon is None:
            self.iconbox = None
            self.textbox = m.box
        else:
            image_size = images.get_image_size(node.icon)
            if image_size is None:
                iconsize = (0, 0)
            else:
                boundedbox = [metrics.node_width // 2, metrics.node_height]
                iconsize = images.calc_image_size(image_size, boundedbox)

            vmargin = (metrics.node_height - iconsize[1]) // 2
            self.iconbox = Box(m.topleft.x, m.topleft.y + vmargin,
                               m.topleft.x + iconsize[0],
                               m.topleft.y + vmargin + iconsize[1])

            self.textbox = Box(self.iconbox[2], m.top.y, m.bottomright.x,
                               m.bottomright.y)
Example #3
0
        def __init__(self, node, metrics=None):
            super(NationalFlagImage, self).__init__(node, metrics)

            self.textalign = 'left'
            self.image_path = image_path

            box = metrics.cell(node).box
            bounded = (box[2] - box[0], box[3] - box[1])
            size = images.get_image_size(image_path)
            size = images.calc_image_size(size, bounded)

            pt = metrics.cell(node).center
            self.image_box = Box(pt.x - size[0] / 2, pt.y - size[1] / 2,
                                 pt.x + size[0] / 2, pt.y + size[1] / 2)

            width = metrics.node_width / 2 - size[0] / 2 + metrics.cellsize
            self.textbox = Box(pt.x + size[0] / 2, pt.y - size[1] / 2,
                               pt.x + size[0] / 2 + width, pt.y + size[1] / 2)

            size = self.metrics.textsize(node.label,
                                         self.metrics.font_for(None),
                                         self.textbox.width)

            self.connectors[0] = XY(pt.x, self.image_box[1])
            self.connectors[1] = XY(
                self.image_box.x2 + size.width + self.metrics.node_padding,
                pt.y)
            self.connectors[2] = XY(pt.x, self.image_box[3])
            self.connectors[3] = XY(self.image_box[0], pt.y)
Example #4
0
    def frame(self, lanes):
        dummy = elements.DiagramNode(None)
        dummy.xy = XY(0, 0)
        dummy.colwidth = self.colwidth
        dummy.colheight = self.colheight
        cell = self.cell(dummy, use_padding=False)

        headerbox = Box(
            cell.topleft.x - self.span_width // 2,
            (cell.topleft.y - self.node_height - self.span_height - 2),
            cell.topright.x + self.span_width // 2,
            cell.topright.y - self.span_height // 2)

        outline = Box(headerbox[0], headerbox[1], headerbox[2],
                      cell.bottom.y + self.span_height // 2)

        separators = [(XY(headerbox[0],
                          headerbox[3]), XY(headerbox[2], headerbox[3]))]

        for lane in lanes[:-1]:
            x = lane.xy.x + lane.colwidth + 1

            m = self.cell(lane, use_padding=False)
            span_width = self.spreadsheet.span_width[x] // 2
            x1 = m.right.x + span_width

            xy = (XY(x1, outline[1]), XY(x1, outline[3]))
            separators.append(xy)

        Frame = namedtuple('Frame', 'headerbox outline separators')
        return Frame(headerbox, outline, separators)
Example #5
0
    def textarea(self, box, string, font, **kwargs):
        if 'rotate' in kwargs and kwargs['rotate'] != 0:
            angle = 360 - int(kwargs['rotate']) % 360
            del kwargs['rotate']

            if angle in (90, 270):
                _box = Box(0, 0, box.height, box.width)
            else:
                _box = box

            text = ImageDrawEx(None, parent=self, transparency=True)
            text.set_canvas_size(_box.size)
            textbox = Box(0, 0, _box.width, _box.height)
            text.textarea(textbox, string, font, **kwargs)

            filler = Image.new('RGB', box.size, kwargs.get('fill'))
            self.paste(filler, box.topleft, text._image.rotate(angle))
            return

        lines = self.textfolder(box, string, font, **kwargs)

        if kwargs.get('outline'):
            outline = kwargs.get('outline')
            self.rectangle(lines.outlinebox, fill='white', outline=outline)

        rendered = False
        for string, xy in lines.lines:
            self.text(xy, string, font, **kwargs)
            rendered = True

        if not rendered and font.size > 0:
            _font = font.duplicate()
            _font.size = int(font.size * 0.8)
            self.textarea(box, string, _font, **kwargs)
Example #6
0
    def grouplabelbox(self):
        box = super(GroupMetrics, self).grouplabelbox
        span = self.cellsize
        box = Box(box[0], box[1] + span, box[2], box[3] + span)

        if self.is_root_group:
            width = (self.node_width + self.span_width) // 2
            box = Box(box[0] + width, box[1], box[2] + width, box[3])

        return box
Example #7
0
    def textarea(self, box, string, font, **kwargs):
        if 'rotate' in kwargs and kwargs['rotate'] != 0:
            angle = 360 - int(kwargs['rotate']) % 360
            self.write("%d rotate", angle)

            if angle == 90:
                box = Box(-box.y2, box.x1, -box.y1, box.x1 + box.width)
                box = box.shift(x=self.size.y, y=self.size.y)
            elif angle == 180:
                box = Box(-box.x2, -box.y2, -box.x1, -box.y2 + box.height)
                box = box.shift(y=self.size.y * 2)
            elif angle == 270:
                box = Box(box.y1, -box.x2, box.y2, -box.x1)
                box = box.shift(x=-self.size.y, y=self.size.y)

        lines = self.textfolder(box, string, font, **kwargs)

        if kwargs.get('outline'):
            outline = kwargs.get('outline')
            self.rectangle(lines.outlinebox, fill='white', outline=outline)

        rendered = False
        for string, xy in lines.lines:
            self.text(xy, string, font, **kwargs)
            rendered = True

        if not rendered and font.size > 0:
            font.size = int(font.size * 0.8)
            self.textarea(box, string, font, **kwargs)
Example #8
0
    def render_shape_background(self, drawer, **kwargs):
        fill = kwargs.get('fill')

        m = self.metrics.cell(self.node)
        pt = m.topleft
        rx = (self.node.width or self.metrics.node_width) // 12
        ry = (self.node.height or self.metrics.node_height) // 5

        ellipses = [
            Box(pt.x + rx * 2, pt.y + ry, pt.x + rx * 5, pt.y + ry * 3),
            Box(pt.x + rx * 4, pt.y, pt.x + rx * 9, pt.y + ry * 2),
            Box(pt.x + rx * 8, pt.y + ry, pt.x + rx * 11, pt.y + ry * 3),
            Box(pt.x + rx * 9, pt.y + ry * 2, pt.x + rx * 13, pt.y + ry * 4),
            Box(pt.x + rx * 8, pt.y + ry * 2, pt.x + rx * 11, pt.y + ry * 5),
            Box(pt.x + rx * 5, pt.y + ry * 2, pt.x + rx * 8, pt.y + ry * 5),
            Box(pt.x + rx * 2, pt.y + ry * 2, pt.x + rx * 5, pt.y + ry * 5),
            Box(pt.x + rx * 0, pt.y + ry * 2, pt.x + rx * 4, pt.y + ry * 4)
        ]

        for e in ellipses:
            if kwargs.get('shadow'):
                e = self.shift_shadow(e)
                if kwargs.get('style') == 'blur':
                    drawer.ellipse(e,
                                   fill=fill,
                                   outline=fill,
                                   filter='transp-blur')
                else:
                    drawer.ellipse(e, fill=fill, outline=fill)
            else:
                drawer.ellipse(e,
                               fill=self.node.color,
                               outline=self.node.linecolor,
                               style=self.node.style)

        rects = [
            Box(pt.x + rx * 2, pt.y + ry * 2, pt.x + rx * 11, pt.y + ry * 4),
            Box(pt.x + rx * 4, pt.y + ry, pt.x + rx * 9, pt.y + ry * 2)
        ]
        for rect in rects:
            if kwargs.get('shadow'):
                rect = self.shift_shadow(rect)
                if kwargs.get('style') == 'blur':
                    drawer.rectangle(rect,
                                     fill=fill,
                                     outline=fill,
                                     filter='transp-blur')
                else:
                    drawer.rectangle(rect, fill=fill, outline=fill)
            else:
                drawer.rectangle(rect,
                                 fill=self.node.color,
                                 outline=self.node.color)
Example #9
0
    def render_shape_background(self, drawer, **kwargs):
        fill = kwargs.get('fill')

        m = self.metrics.cell(self.node)
        r = self.metrics.cellsize * 2

        box = m.box
        ellipses = [
            Box(box[0], box[1], box[0] + r * 2, box[3]),
            Box(box[2] - r * 2, box[1], box[2], box[3])
        ]

        for e in ellipses:
            if kwargs.get('shadow'):
                e = self.shift_shadow(e)
                if kwargs.get('style') == 'blur':
                    drawer.ellipse(e,
                                   fill=fill,
                                   outline=fill,
                                   filter='transp-blur')
                else:
                    drawer.ellipse(e, fill=fill, outline=fill)
            else:
                drawer.ellipse(e,
                               fill=self.node.color,
                               outline=self.node.linecolor,
                               style=self.node.style)

        rect = Box(box[0] + r, box[1], box[2] - r, box[3])
        if kwargs.get('shadow'):
            rect = self.shift_shadow(rect)
            if kwargs.get('style') == 'blur':
                drawer.rectangle(rect,
                                 fill=fill,
                                 outline=fill,
                                 filter='transp-blur')
            else:
                drawer.rectangle(rect, fill=fill, outline=fill)
        else:
            drawer.rectangle(rect,
                             fill=self.node.color,
                             outline=self.node.color)

        lines = [(XY(box[0] + r, box[1]), XY(box[2] - r, box[1])),
                 (XY(box[0] + r, box[3]), XY(box[2] - r, box[3]))]
        for line in lines:
            if not kwargs.get('shadow'):
                drawer.line(line,
                            fill=self.node.linecolor,
                            style=self.node.style)
Example #10
0
    def leftnotebox(self):
        if not self.edge.leftnote:
            return Box(0, 0, 0, 0)

        m = self.metrics
        cell = m.cell(self.edge.left_node)
        notesize = self.edge.leftnotesize

        x = cell.center.x - m.cellsize * 3 - notesize.width
        y = self.baseheight - notesize.height // 2

        if self.edge.failed and self.edge.direction == 'left':
            x += self.metrics.edge_length // 2 - m.cellsize

        return Box(x, y, x + notesize.width, y + notesize.height)
Example #11
0
    def _scale(cls, value, ratio):
        if ratio == 1:
            return value

        klass = value.__class__
        if klass == XY:
            ret = XY(value.x * ratio, value.y * ratio)
        elif klass == Size:
            ret = Size(value.width * ratio, value.height * ratio)
        elif klass == Box:
            ret = Box(value[0] * ratio, value[1] * ratio,
                      value[2] * ratio, value[3] * ratio)
        elif klass == tuple:
            ret = tuple([cls.scale(x, ratio) for x in value])
        elif klass == list:
            ret = [cls.scale(x, ratio) for x in value]
        elif klass == EdgeLines:
            ret = EdgeLines()
            ret.polylines = cls.scale(value.polylines, ratio)
        elif klass == FontInfo:
            ret = FontInfo(value.familyname, value.path, value.size * ratio)
        elif klass == int:
            ret = value * ratio
        elif klass == str:
            ret = value
        else:
            ret = cls(value, ratio)

        return ret
Example #12
0
    def lane_headerbox(self, lane):
        headerbox = self.frame([]).headerbox
        m = self.cell(lane)
        x1 = m.left.x - self.spreadsheet.span_width[lane.xy.x] // 2
        x2 = m.right.x + self.spreadsheet.span_width[lane.xy.x + 1] // 2

        return Box(x1, headerbox[1], x2, headerbox[3])
Example #13
0
    def lane_textbox(self, lane):
        headerbox = self.frame([]).headerbox
        m = self.cell(lane, use_padding=False)
        x1 = m.left.x
        x2 = m.right.x

        return Box(x1, headerbox[1], x2, headerbox[3])
Example #14
0
    def __init__(self, node, metrics=None):
        super(Actor, self).__init__(node, metrics)

        m = metrics.cell(node)
        if node.label:
            font = metrics.font_for(self.node)
            textsize = metrics.textsize(node.label, font)
            shortside = min(m.width, m.height - textsize.height)
        else:
            textsize = Size(0, 0)
            shortside = min(m.width, m.height)

        r = self.radius = shortside // 8  # radius of actor's head
        self.center = metrics.cell(node).center

        self.connectors[0] = XY(self.center.x, self.center.y - r * 9 // 2)
        self.connectors[1] = XY(self.center.x + r * 4, self.center.y)
        self.connectors[2] = XY(self.center.x,
                                self.center.y + r * 4 + textsize.height)
        self.connectors[3] = XY(self.center.x - r * 4, self.center.y)

        self.textbox = Box(m.left.x,
                           self.center.y + r * 4,
                           m.right.x,
                           self.connectors[2].y)
Example #15
0
    def rightnotebox(self):
        if not self.edge.rightnote:
            return Box(0, 0, 0, 0)

        m = self.metrics
        cell = m.cell(self.edge.right_node)
        if self.edge.direction == 'self':
            x = self.right + m.cellsize * 2
        elif self.edge.failed and self.edge.direction == 'right':
            x = self.right + m.cellsize * 4
        else:
            x = cell.center.x + m.cellsize * 2

        notesize = self.edge.rightnotesize
        y = self.baseheight - notesize.height // 2
        return Box(x, y, x + notesize.width, y + notesize.height)
Example #16
0
 def get_shape_box(*args):
     if fn.__name__ == 'polygon':
         xlist = [pt.x for pt in args[0]]
         ylist = [pt.y for pt in args[0]]
         return Box(min(xlist), min(ylist), max(xlist), max(ylist))
     else:
         return args[0]
Example #17
0
    def __init__(self, node, metrics=None):
        super(Mail, self).__init__(node, metrics)

        m = self.metrics.cell(self.node)
        r = self.metrics.cellsize * 2
        self.textbox = Box(m.topleft.x, m.topleft.y + r, m.bottomright.x,
                           m.bottomright.y)
    def textsize(self, string, font, maxwidth=None, **kwargs):
        if maxwidth is None:
            maxwidth = 65535

        box = Box(0, 0, maxwidth, 65535)
        textbox = textfolder.get(self, box, string, font=None, **kwargs)
        return textbox.outlinebox.size
Example #19
0
    def render_shape(self, drawer, _, **kwargs):
        if kwargs.get('shadow'):
            return

        m = self.metrics
        center = m.cell(self.node).center
        dots = [center]
        if self.node.group.orientation == 'landscape':
            pt = XY(center.x, center.y - m.node_height / 2)
            dots.append(pt)

            pt = XY(center.x, center.y + m.node_height / 2)
            dots.append(pt)
        else:
            pt = XY(center.x - m.node_width / 3, center.y)
            dots.append(pt)

            pt = XY(center.x + m.node_width / 3, center.y)
            dots.append(pt)

        r = m.cellsize / 2
        for dot in dots:
            box = Box(dot.x - r, dot.y - r, dot.x + r, dot.y + r)
            drawer.ellipse(box,
                           fill=self.node.linecolor,
                           outline=self.node.linecolor)
Example #20
0
    def __init__(self, node, metrics=None):
        super(Database, self).__init__(node, metrics)

        m = self.metrics.cell(self.node)
        r = self.metrics.cellsize
        self.textbox = Box(m.topleft.x, m.topleft.y + r * 3 // 2,
                           m.bottomright.x, m.bottomright.y - r // 2)
Example #21
0
    def activity_shadow(self, node, activity):
        box = self.activity_box(node, activity)

        return Box(box[0] + self.shadow_offset.x,
                   box[1] + self.shadow_offset.y,
                   box[2] + self.shadow_offset.x,
                   box[3] + self.shadow_offset.y)
Example #22
0
    def labelbox(self):
        span = XY(self.span_width, self.span_height)

        _dir = self.edge.direction
        node1 = self.cell(self.edge.node1, use_padding=False)
        node2 = self.cell(self.edge.node2, use_padding=False)

        if _dir == 'right':
            if self.edge.skipped:
                box = Box(node1.bottomright.x + span.x,
                          node1.bottomright.y,
                          node2.bottomleft.x - span.x,
                          node2.bottomleft.y + span.y // 2)
            else:
                box = Box(node1.topright.x, node1.topright.y - span.y // 8,
                          node2.left.x, node2.left.y - span.y // 8)

        elif _dir == 'right-up':
            box = Box(node2.left.x - span.x, node2.left.y,
                      node2.bottomleft.x, node2.bottomleft.y)

        elif _dir == 'right-down':
            box = Box(node2.topleft.x, node2.topleft.y - span.y // 2,
                      node2.top.x, node2.top.y)

        elif _dir in ('up', 'left-up', 'left', 'same'):
            if self.edge.node2.xy.y < self.edge.node1.xy.y:
                box = Box(node1.topright.x - span.x // 2 + span.x // 4,
                          node1.topright.y - span.y // 2,
                          node1.topright.x + span.x // 2 + span.x // 4,
                          node1.topright.y)
            else:
                box = Box(node1.top.x + span.x // 4,
                          node1.top.y - span.y,
                          node1.topright.x + span.x // 4,
                          node1.topright.y - span.y // 2)

        elif _dir == 'down':
            box = Box(node2.top.x + span.x // 4,
                      node2.top.y - span.y // 2,
                      node2.topright.x + span.x // 4,
                      node2.topright.y)

        elif _dir == 'left-down':
            box = Box(node1.bottomleft.x, node1.bottomleft.y,
                      node1.bottom.x, node1.bottom.y + span.y // 2)

        # shrink box
        box = Box(box[0] + span.x // 8, box[1],
                  box[2] - span.x // 8, box[3])

        return box
Example #23
0
    def textbox(self):
        x = self.left.x
        y = self.top.y

        width = self.node_width * 3 // 2
        height = self.node_height

        return Box(x - width, y - height // 2, x, y + height // 2)
Example #24
0
    def __init__(self, node, metrics=None):
        super(Cloud, self).__init__(node, metrics)

        pt = metrics.cell(node).topleft
        rx = (self.node.width or self.metrics.node_width) // 12
        ry = (self.node.height or self.metrics.node_height) // 5
        self.textbox = Box(pt.x + rx * 2, pt.y + ry, pt.x + rx * 11,
                           pt.y + ry * 4)
Example #25
0
 def remap(self, obj):
     if isinstance(obj, (XY, tuple)) and len(obj) == 2:
         return XY(obj[0], self.size.y - obj[1])
     elif isinstance(obj, (Box, tuple)) and len(obj) == 4:
         return Box(obj[0], self.size.y - obj[3], obj[2],
                    self.size.y - obj[1])
     else:
         return obj
Example #26
0
    def __init__(self, node, metrics=None):
        super(Input, self).__init__(node, metrics)

        m = self.metrics.cell(self.node)
        r = self.metrics.cellsize * 3

        self.textbox = Box(m.topleft.x + r, m.topleft.y,
                           m.bottomright.x - r, m.bottomright.y)
Example #27
0
    def __init__(self, node, metrics=None):
        super(LoopIn, self).__init__(node, metrics)

        m = self.metrics.cell(self.node)
        ydiff = self.metrics.node_height // 4

        self.textbox = Box(m.topleft.x, m.topleft.y + ydiff, m.bottomright.x,
                           m.bottomright.y)
Example #28
0
    def textarea(self, box, string, font, **kwargs):
        if 'rotate' in kwargs and kwargs['rotate'] != 0:
            angle = 360 - int(kwargs['rotate']) % 360
            del kwargs['rotate']

            if angle in (90, 270):
                _box = Box(0, 0, box.height, box.width)
            else:
                _box = box

            text = ImageDrawEx(None, parent=self, transparency=True)
            text.set_canvas_size(_box.size)
            textbox = Box(0, 0, _box.width, _box.height)
            text.textarea(textbox, string, font, **kwargs)

            filler = Image.new('RGB', box.size, kwargs.get('fill'))

            mask = text._image.rotate(angle, expand=True)
            if mask.size != filler.size:
                # Image.rotate(expand=True) of Pillow earlier than
                # 3.3.0 (including 2.x) returns image object with
                # unexpected size: for example, rotating 10x20 by 270
                # causes not 20x10 but 21x11.
                # Therefore, crop rotated image in order to make it
                # match against size of "filler".
                mask = mask.crop((0, 0, box.width, box.height))

            self.paste(filler, box.topleft, mask)
            return

        lines = self.textfolder(box, string, font, **kwargs)

        if kwargs.get('outline'):
            outline = kwargs.get('outline')
            self.rectangle(lines.outlinebox, fill='white', outline=outline)

        rendered = False
        for string, xy in lines.lines:
            self.text(xy, string, font, **kwargs)
            rendered = True

        if not rendered and font.size > 0:
            _font = font.duplicate()
            _font.size = int(font.size * 0.8)
            self.textarea(box, string, _font, **kwargs)
Example #29
0
    def labelbox(self):
        _dir = self.edge.direction
        if _dir == 'right':
            span = XY(self.span_width, self.span_height)
            cell1 = self.cell(self.edge.node1, use_padding=False)
            cell2 = self.cell(self.edge.node2, use_padding=False)

            if self.edge.skipped:
                box = Box(cell1.bottom.x, cell1.bottom.y,
                          cell1.bottomright.x,
                          cell1.bottomright.y + span.y // 2)
            else:
                box = Box(cell1.bottom.x, cell2.left.y - span.y // 2,
                          cell1.bottom.x, cell2.left.y)
        else:
            box = super(FlowchartLandscapeEdgeMetrics, self).labelbox

        return box
Example #30
0
    def __init__(self, node, metrics=None):
        super(Square, self).__init__(node, metrics)

        r = min(metrics.node_width, metrics.node_height) // 2 + \
            metrics.cellsize // 2
        pt = metrics.cell(node).center
        self.connectors = [XY(pt.x, pt.y - r),  # top
                           XY(pt.x + r, pt.y),  # right
                           XY(pt.x, pt.y + r),  # bottom
                           XY(pt.x - r, pt.y)]  # left
        self.textbox = Box(pt.x - r, pt.y - r, pt.x + r, pt.y + r)
Example #31
0
    def rotated_textarea(self, box, string, font, **kwargs):
        angle = int(kwargs["rotate"]) % 360
        del kwargs["rotate"]

        if angle in (90, 270):
            _box = Box(box[0], box[1], box[0] + box.height, box[1] + box.width)
            if angle == 90:
                _box = _box.shift(x=box.width)
            elif angle == 270:
                _box = _box.shift(y=box.height)
        elif angle == 180:
            _box = Box(box[2], box[3], box[2] + box.width, box[3] + box.height)
        else:
            _box = Box(box[0], box[1], box[0] + box.width, box[1] + box.height)

        rotate = "rotate(%d,%d,%d)" % (angle, _box[0], _box[1])
        group = g(transform="%s" % rotate)
        self.svg.addElement(group)

        elem = SVGImageDrawElement(group, self)
        elem.textarea(_box, string, font, **kwargs)
Example #32
0
    def textarea(self, box, string, font, **kwargs):
        self.canvas.saveState()

        if 'rotate' in kwargs and kwargs['rotate'] != 0:
            angle = 360 - int(kwargs['rotate']) % 360
            self.canvas.rotate(angle)

            if angle == 90:
                box = Box(-box.y2, box.x1, -box.y1, box.x1 + box.width)
                box = box.shift(x=self.size.y, y=self.size.y)
            elif angle == 180:
                box = Box(-box.x2, -box.y2, -box.x1, -box.y2 + box.height)
                box = box.shift(y=self.size.y * 2)
            elif angle == 270:
                box = Box(box.y1, -box.x2, box.y2, -box.x1)
                box = box.shift(x=-self.size.y, y=self.size.y)

        self.set_font(font)
        lines = self.textfolder(box, string, font, **kwargs)

        if kwargs.get('outline'):
            outline = kwargs.get('outline')
            self.rectangle(lines.outlinebox, fill='white', outline=outline)

        rendered = False
        for string, xy in lines.lines:
            self.text(xy, string, font, **kwargs)
            rendered = True
        self.canvas.restoreState()

        if not rendered and font.size > 0:
            font.size = int(font.size * 0.8)
            self.textarea(box, string, font, **kwargs)