Пример #1
0
    def draw(self, scale=0.01, cm=False):
        self.dat.scale(scale, center=False)
        self.page = self.page.scale(scale)
        b = self.dat.bounds()
        limits = Rect(0, 0, 11, 8.5)
        if cm:
            limits = limits.scale(2.54)
        if b.x >= 0 and b.y >= 0 and b.w <= limits.w and b.h <= limits.h:
            print("Drawing!")
        else:
            print("Too big!", b)
            return False

        ad = axidraw.AxiDraw()
        ad.interactive()
        ad.options.units = 1 if cm else 0
        ad.options.speed_pendown = 50
        ad.options.speed_penup = 50

        ad.connect()
        ad.penup()
        self.ad = ad
        tp = TransformPen(self, (1, 0, 0, -1, 0, self.page.h))
        self.dat.replay(tp)
        time.sleep(MOVE_DELAY)
        ad.penup()
        ad.moveto(0, 0)
        ad.disconnect()
Пример #2
0
 def __init__(self, glyphName, width=500, **kwargs):
     r = Rect(kwargs.get("rect", Rect(1000, 1000)))
     kwargs.pop("rect", None)
     self.width = width
     self.body = r.take(750, "mdy").take(self.width, "mdx")
     self.glyphName = glyphName
     super().__init__(rect=r, **kwargs)
Пример #3
0
    def clip_timeline(self, fi, far, sw=30, sh=50, font_name=None):
        seq = DATPens()

        seq += (DATPen()
            .rect(Rect(0, 0, (self.duration * sw), sh)).f(bw(0.5, 0.35)))

        for tidx, t in enumerate(self.tracks):
            for cgidx, cg in enumerate(t.clip_groups):
                for cidx, c in enumerate(cg.clips):
                    r = Rect(c.start*sw, sh*tidx, (c.duration*sw)-2, sh)
                    current = c.start-25 <= fi <= c.end+25
                    if current:
                        seq.append(DATPens([
                            (DATPen()
                                .rect(r)
                                .f(hsl(cgidx*0.4+tidx*0.2, 0.75).lighter(0.2))),
                            (DATText(c.input_text,
                                Style(font_name, 24, load_font=0, fill=bw(0, 1 if tidx == self.workarea_track else 0.5)),
                                r.inset(3, 8)))]))

        on_cut = fi in self.jumps()

        seq.translate(far.w/2 - fi*sw, 10)

        seq.append(DATPen()
            .rect(far.take(sh*len(self.tracks)+20, "mny").take(6 if on_cut else 2, "mdx"))
            .f(hsl(0.9, 1) if on_cut else hsl(0.8, 0.5)))
        
        return seq
Пример #4
0
    def draw(self, scale=0.01, dry=True, cm=False):
        if dry:
            with viewer() as v:
                dp = DATPen().record(self.dat).attr(fill=None, stroke=0)
                v.send(SVGPen.Composite([dp], self.page), self.page)
        else:
            self.dat.scale(scale, center=False)
            self.page = self.page.scale(scale)
            b = self.dat.bounds()
            limits = Rect(0, 0, 11, 8.5)
            if cm:
                limits = limits.scale(2.54)
            if b.x >= 0 and b.y >= 0 and b.w <= limits.w and b.h <= limits.h:
                print("Drawing!")
            else:
                print("Too big!", b)
                return False
            ad = axidraw.AxiDraw()
            ad.interactive()
            ad.options.units = 1 if cm else 0
            ad.options.speed_pendown = 50
            ad.options.speed_penup = 50

            ad.connect()
            ad.penup()
            self.ad = ad
            tp = TransformPen(self, (1, 0, 0, -1, 0, self.page.h))
            self.dat.replay(tp)
            time.sleep(MOVE_DELAY)
            ad.penup()
            ad.moveto(0, 0)
            ad.disconnect()
Пример #5
0
    def __init__(self,
                 rect=(1080, 1080),
                 duration=10,
                 storyboard=[0],
                 timeline: Timeline = None,
                 audio=None,
                 **kwargs):
        super().__init__(**kwargs)
        self.rect = Rect(rect)
        self.r = self.rect
        self.audio = audio
        if self.audio and sf:
            self.wavfile = Wavfile(audio)
        else:
            self.wavfile = None
        self.start = 0
        self.end = duration

        self.storyboard = storyboard
        if timeline:
            self.timeline = timeline
            self.t = timeline
            self.start = timeline.start
            self.end = timeline.end

            if self.storyboard != [0] and timeline.storyboard == [0]:
                pass
            else:
                self.storyboard = timeline.storyboard.copy()
        else:
            self.timeline = Timeline(duration)
Пример #6
0
 def bounds(self):
     """Calculate the bounds of this shape; mostly for internal use."""
     try:
         cbp = BoundsPen(None)
         self.replay(cbp)
         mnx, mny, mxx, mxy = cbp.bounds
         return Rect((mnx, mny, mxx - mnx, mxy - mny))
     except:
         return Rect(0, 0, 0, 0)
Пример #7
0
 def __init__(self,
              rect=(1080, 1080),
              duration=10,
              storyboard=[0],
              timeline: Timeline = None,
              **kwargs):
     super().__init__(**kwargs)
     self.rect = Rect(rect)
     self.r = self.rect
     self.start = 0
     self.end = duration
     self.duration = duration
     self.storyboard = storyboard
     if timeline:
         self.timeline = timeline
         self.t = timeline
         self.start = timeline.start
         self.end = timeline.end
         self.duration = timeline.duration
         if self.storyboard != [0] and timeline.storyboard == [0]:
             pass
         else:
             self.storyboard = timeline.storyboard
     else:
         self.timeline = None
Пример #8
0
 def __init__(self,
              rect=(1080, 1080),
              bg="whitesmoke",
              hide=False,
              fmt="png",
              rasterizer=None,
              prefix=None,
              dst=None,
              custom_folder=None,
              postfn=None,
              watch=[],
              layers=[],
              ui_callback=None):
     self.hide = hide
     self.rect = Rect(rect)
     self.bg = normalize_color(bg)
     self.fmt = fmt
     self.prefix = prefix
     self.dst = Path(dst).expanduser().resolve() if dst else None
     self.custom_folder = custom_folder
     self.postfn = postfn
     self.ui_callback = ui_callback
     self.watch = [Path(w).expanduser().resolve() for w in watch]
     self.rasterizer = rasterizer
     self.layers = layers
     if not rasterizer:
         if self.fmt == "svg":
             self.rasterizer = "svg"
         else:
             system = platform.system()
             if system == "Darwin":
                 self.rasterizer = "drawbot"
             else:
                 self.rasterizer = "cairo"
Пример #9
0
 def __init__(self, rect=(1080, 1080), scale=1, **kwargs):
     if not db:
         raise Exception("DrawBot not installed!")
     super().__init__(rect=Rect(rect).scale(scale),
                      rasterizer="drawbot",
                      **kwargs)
     self.self_rasterizing = True
Пример #10
0
 def getFrame(self, th=False, tv=False):
     """For internal use; creates a frame based on calculated bounds."""
     if self.frame:
         if (th or tv) and len(self.value) > 0:
             f = self.frame
             b = self.bounds()
             if th and tv:
                 return b
             elif th:
                 return Rect(b.x, f.y, b.w, f.h)
             else:
                 return Rect(f.x, b.y, f.w, b.h)
         else:
             return self.frame
     else:
         return self.bounds()
Пример #11
0
 def roundedRect(self, rect, hr, vr):
     """Rounded rectangle primitive"""
     l, b, w, h = Rect(rect)
     r, t = l + w, b + h
     K = 4 * (math.sqrt(2) - 1) / 3
     circle = hr == 0.5 and vr == 0.5
     if hr <= 0.5:
         hr = w * hr
     if vr <= 0.5:
         vr = h * vr
     self.moveTo((l + hr, b))
     if not circle:
         self.lineTo((r - hr, b))
     self.curveTo((r + hr * (K - 1), b), (r, b + vr * (1 - K)), (r, b + vr))
     if not circle:
         self.lineTo((r, t - vr))
     self.curveTo((r, t - vr * (1 - K)), (r - hr * (1 - K), t), (r - hr, t))
     if not circle:
         self.lineTo((l + hr, t))
     self.curveTo((l + hr * (1 - K), t), (l, t - vr * (1 - K)), (l, t - vr))
     if not circle:
         self.lineTo((l, b + vr))
     self.curveTo((l, b + vr * (1 - K)), (l + hr * (1 - K), b), (l + hr, b))
     self.closePath()
     return self
Пример #12
0
 def svg(self, file, gid, rect=Rect(0, 0, 0, 100)):
     """WIP; attempt to read an svg file into the pen"""
     from bs4 import BeautifulSoup
     with open(file, "r") as f:
         soup = BeautifulSoup(f.read(), features="lxml")
         tp = TransformPen(self, (1, 0, 0, -1, 0, rect.h))
         for path in soup.find(id=gid).find_all("path"):
             parse_path(path.get("d"), tp)
     return self
Пример #13
0
 def dots(self, radius=4):
     """(Necessary?) Create circles at moveTo commands"""
     dp = DATPen()
     for t, pts in self.value:
         if t == "moveTo":
             x, y = pts[0]
             dp.oval(Rect((x - radius, y - radius, radius, radius)))
     self.value = dp.value
     return self
Пример #14
0
 def rect(self, rect, *args):
     """Rectangle primitive — `moveTo/lineTo/lineTo/lineTo/closePath`"""
     if isinstance(rect, Rect):
         self.moveTo(rect.point("SW").xy())
         self.lineTo(rect.point("SE").xy())
         self.lineTo(rect.point("NE").xy())
         self.lineTo(rect.point("NW").xy())
         self.closePath()
     elif isinstance(rect, Number):
         if len(args) == 1:
             return self.rect(Rect(rect, args[0]))
         else:
             return self.rect(Rect(rect, args[0], args[1], args[2]))
     elif isinstance(rect[0], Rect):
         for r in rect:
             self.rect(r)
     else:
         self.rect(Rect(rect))
     return self
Пример #15
0
 def getFrame(self, th=False, tv=False):
     if self.frame and (th == False and tv == False):
         return self.frame
     else:
         try:
             union = self.pens[0].getFrame(th=th, tv=tv)
             for p in self.pens[1:]:
                 union = union.union(p.getFrame(th=th, tv=tv))
             return union
         except Exception as e:
             return Rect(0, 0, 0, 0)
Пример #16
0
 def img(self,
         src=None,
         rect=Rect(0, 0, 500, 500),
         pattern=True,
         opacity=1.0):
     """Get/set an image fill"""
     if src:
         return self.attr(image=dict(
             src=src, rect=rect, pattern=pattern, opacity=opacity))
     else:
         return self.attr(field="image")
Пример #17
0
 def pens(self, align=False):
     rects = self.lineRects()
     pens = DATPens()
     for idx, l in enumerate(self.lines):
         r = rects[idx]
         dps = l.pens().translate(r.x, r.y)  # r.x
         dps.addFrame(Rect(r.x, r.y, r.w, r.h))
         #if align:
         #    dps.container = Rect(0, r.y, r.w, r.h)
         #    dps.align(dps.container, x=self.style.x, y=None)
         pens.append(dps)
     return pens
Пример #18
0
 def image(self, src=None, opacity=1, rect=None, rotate=0, repeating=False, scale=True):
     bounds = self.dat.bounds()
     src = str(src)
     if not rect:
         rect = bounds
     try:
         img_w, img_h = db.imageSize(src)
     except ValueError:
         print("DrawBotPen: No image")
         return
     x = bounds.x
     y = bounds.y
     if repeating:
         x_count = bounds.w / rect.w
         y_count = bounds.h / rect.h
     else:
         x_count = 1
         y_count = 1
     _x = 0
     _y = 0
     while x <= (bounds.w+bounds.x) and _x < x_count:
         _x += 1
         while y <= (bounds.h+bounds.y) and _y < y_count:
             _y += 1
             with db.savedState():
                 r = Rect(x, y, rect.w, rect.h)
                 #db.fill(1, 0, 0.5, 0.05)
                 #db.oval(*r)
                 if scale == True:
                     db.scale(rect.w/img_w, center=r.point("SW"))
                 elif scale:
                     try:
                         db.scale(scale[0], scale[1], center=r.point("SW"))
                     except TypeError:
                         db.scale(scale, center=r.point("SW"))
                 db.rotate(rotate)
                 db.image(src, (r.x, r.y), alpha=opacity)
             y += rect.h
         y = 0
         x += rect.w
Пример #19
0
    def skeleton(self, scale=1, returnSet=False):
        """Vector-editing visualization"""
        dp = DATPen()
        moveTo = DATPen(fill=("random", 0.5))
        lineTo = DATPen(fill=("random", 0.5))
        curveTo_on = DATPen(fill=("random", 0.5))
        curveTo_off = DATPen(fill=("random", 0.25))
        curveTo_bars = DATPen(fill=None,
                              stroke=dict(color=("random", 0.5),
                                          weight=1 * scale))
        for idx, (t, pts) in enumerate(self.value):
            if t == "moveTo":
                r = 12 * scale
                x, y = pts[0]
                moveTo.rect(Rect((x - r / 2, y - r / 2, r, r)))
            elif t == "curveTo":
                r = 6 * scale
                x, y = pts[-1]
                curveTo_on.oval(Rect((x - r / 2, y - r / 2, r, r)))
                r = 4 * scale
                x, y = pts[1]
                curveTo_off.oval(Rect((x - r / 2, y - r / 2, r, r)))
                x, y = pts[0]
                curveTo_off.oval(Rect((x - r / 2, y - r / 2, r, r)))
                p0 = self.value[idx - 1][-1][-1]
                curveTo_bars.line((p0, pts[0]))
                curveTo_bars.line((pts[1], pts[2]))
            elif t == "lineTo":
                r = 6 * scale
                x, y = pts[0]
                lineTo.rect(Rect((x - r / 2, y - r / 2, r, r)))

        all_pens = [moveTo, lineTo, curveTo_on, curveTo_off, curveTo_bars]
        if returnSet:
            return all_pens
        else:
            for _dp in all_pens:
                dp.record(_dp)
            self.value = dp.value
            return self
Пример #20
0
 def resetGlyphRun(self):
     self.glyphs = self.style.font.font.getGlyphRunFromTextInfo(
         self.text_info,
         addDrawings=False,
         features=self.features,
         varLocation=self.variations)
     #self.glyphs = self.style.font.font.getGlyphRun(self.text, features=self.features, varLocation=self.variations)
     x = 0
     for glyph in self.glyphs:
         glyph.frame = Rect(x + glyph.dx, glyph.dy, glyph.ax,
                            self.style.capHeight)
         x += glyph.ax
     self.getGlyphFrames()
Пример #21
0
        def contactsheet(r: Rect):
            _pngs = list(sorted(self.output_folder.glob("*.png")))
            if sliced:
                pngs = _pngs[sl]
            else:
                pngs = [p for i, p in enumerate(_pngs) if i in sl]

            dps = DATPens()
            dps += DATPen().rect(r).f(self.bg)
            for idx, g in enumerate(r.grid(columns=gx, rows=gy)):
                if idx < len(pngs):
                    dps += DATPen().rect(g).f(None).img(pngs[idx],
                                                        g,
                                                        pattern=False)
            return dps
Пример #22
0
 def pixellate(self, rect, increment=50, inset=0):
     """WIP"""
     x = -200
     y = -200
     dp = DATPen()
     while x < 1000:
         while y < 1000:
             #print(x, y)
             pen = PointInsidePen(None, (x, y))
             self.replay(pen)
             isInside = pen.getResult()
             if isInside:
                 dp.rect(Rect(x, y, increment, increment).inset(inset))
             y += increment
         x += increment
         y = -200
     self.value = dp.value
     return self
Пример #23
0
def get_image_rect(src):
    w, h = db.imageSize(str(src))
    return Rect(0, 0, w, h)
Пример #24
0
    def __init__(self,
                 rect=(1080, 1080),
                 bg="whitesmoke",
                 fmt="png",
                 name=None,
                 rasterizer=None,
                 prefix=None,
                 dst=None,
                 custom_folder=None,
                 postfn=None,
                 watch=[],
                 watch_restarts=[],
                 watch_soft=[],
                 solo=False,
                 rstate=False,
                 preview_only=False,
                 direct_draw=False,
                 clip=False,
                 composites=False,
                 bg_render=False,
                 style="default",
                 viewBox=True,
                 layer=False):
        """Base configuration for a renderable function"""

        self.rect = Rect(rect)
        self.bg = normalize_color(bg)
        self.fmt = fmt
        self.prefix = prefix
        self.dst = Path(dst).expanduser().resolve() if dst else None
        self.custom_folder = custom_folder
        self.postfn = postfn
        self.last_passes = []
        self.last_result = None
        self.style = style
        self.composites = composites

        self.watch = []
        for w in watch:
            self.add_watchee(w)

        self.watch_restarts = []
        for w in watch_restarts:
            self.watch_restarts.append(self.add_watchee(w, "restart"))

        self.watch_soft = []
        for w in watch_soft:
            self.watch_soft.append(self.add_watchee(w, "soft"))

        self.name = name
        self.codepath = None
        self.rasterizer = rasterizer
        self.self_rasterizing = False
        self.hidden = solo == -1
        self.solo = solo
        self.preview_only = preview_only
        self.rstate = rstate
        self.clip = clip
        self.viewBox = viewBox
        self.direct_draw = direct_draw
        self.bg_render = bg_render
        self.layer = layer
        if self.layer:
            self.bg = normalize_color(None)

        if not rasterizer:
            if self.fmt == "svg":
                self.rasterizer = "svg"
            elif self.fmt == "pickle":
                self.rasterizer = "pickle"
            else:
                self.rasterizer = "skia"
Пример #25
0
class animation(renderable, Timeable):
    """
    Base class for any frame-wise animation animatable by Coldtype
    """
    def __init__(self,
                 rect=(1080, 1080),
                 duration=10,
                 storyboard=[0],
                 timeline: Timeline = None,
                 audio=None,
                 **kwargs):
        super().__init__(**kwargs)
        self.rect = Rect(rect)
        self.r = self.rect
        self.audio = audio
        if self.audio and sf:
            self.wavfile = Wavfile(audio)
        else:
            self.wavfile = None
        self.start = 0
        self.end = duration

        self.storyboard = storyboard
        if timeline:
            self.timeline = timeline
            self.t = timeline
            self.start = timeline.start
            self.end = timeline.end

            if self.storyboard != [0] and timeline.storyboard == [0]:
                pass
            else:
                self.storyboard = timeline.storyboard.copy()
        else:
            self.timeline = Timeline(duration)

    def __call__(self, func):
        res = super().__call__(func)
        self.prefix = self.name + "_"
        return res

    def folder(self, filepath):
        return filepath.stem + "/" + self.name  # TODO necessary?

    def all_frames(self):
        return list(range(0, self.duration))

    def _active_frames(self, renderer_state):
        frames = []
        for f in renderer_state.get_frame_offsets(self.name):
            frames.append(f % self.duration)
        return frames

    def active_frames(self, action, renderer_state, indices):
        frames = self._active_frames(renderer_state)

        if action == Action.RenderAll:
            frames = self.all_frames()
        elif action in [Action.PreviewIndices, Action.RenderIndices]:
            frames = indices
        elif action in [Action.RenderWorkarea]:
            if self.timeline:
                try:
                    frames = self.workarea()
                except:
                    frames = self.all_frames()
                #if hasattr(self.timeline, "find_workarea"):
                #    frames = self.timeline.find_workarea()
        return frames

    def workarea(self):
        return list(self.timeline.workareas[0])

    def jump(self, current, direction):
        c = current % self.duration
        js = self.timeline.jumps()
        if direction < 0:
            for j in reversed(js):
                if c > j:
                    return j
        else:
            for j in js:
                if c < j:
                    return j
        return current

    def pass_suffix(self, index):
        return "{:04d}".format(index)

    def passes(self, action, renderer_state, indices=[]):
        frames = self.active_frames(action, renderer_state, indices)
        return [
            RenderPass(self, self.pass_suffix(i), [Frame(i, self)])
            for i in frames
        ]

    def runpost(self, result, render_pass, renderer_state):
        res = super().runpost(result, render_pass, renderer_state)
        if Overlay.Info in renderer_state.overlays:
            t = self.rect.take(50, "mxy")
            frame: Frame = render_pass.args[0]
            wave = DATPen()
            if self.audio and sf:
                wave = (self.wavfile.frame_waveform(
                    frame.i, self.rect.inset(0, 300),
                    20).translate(0, self.rect.h / 2).s(1).sw(5))
            return DATPens([
                wave, res,
                DATPen().rect(t).f(bw(0, 0.75)),
                DATText(f"{frame.i} / {self.duration}",
                        Style("Times", 42, load_font=0, fill=bw(1)),
                        t.inset(10))
            ])
        return res

    def package(self, filepath, output_folder):
        pass

    def contactsheet(self, gx, sl=slice(0, None, None)):
        try:
            sliced = True
            start, stop, step = sl.indices(self.duration)
            duration = (stop - start) // step
        except AttributeError:  # indices storyboard
            duration = len(sl)
            sliced = False

        ar = self.rect
        gy = math.ceil(duration / gx)

        @renderable(rect=(ar.w * gx, ar.h * gy),
                    bg=self.bg,
                    name=self.name + "_contactsheet")
        def contactsheet(r: Rect):
            _pngs = list(sorted(self.output_folder.glob("*.png")))
            if sliced:
                pngs = _pngs[sl]
            else:
                pngs = [p for i, p in enumerate(_pngs) if i in sl]

            dps = DATPens()
            dps += DATPen().rect(r).f(self.bg)
            for idx, g in enumerate(r.grid(columns=gx, rows=gy)):
                if idx < len(pngs):
                    dps += DATPen().rect(g).f(None).img(pngs[idx],
                                                        g,
                                                        pattern=False)
            return dps

        return contactsheet
Пример #26
0
class renderable():
    """
    Base class for any content renderable by Coldtype
    """
    def __init__(self,
                 rect=(1080, 1080),
                 bg="whitesmoke",
                 fmt="png",
                 name=None,
                 rasterizer=None,
                 prefix=None,
                 dst=None,
                 custom_folder=None,
                 postfn=None,
                 watch=[],
                 watch_restarts=[],
                 watch_soft=[],
                 solo=False,
                 rstate=False,
                 preview_only=False,
                 direct_draw=False,
                 clip=False,
                 composites=False,
                 bg_render=False,
                 style="default",
                 viewBox=True,
                 layer=False):
        """Base configuration for a renderable function"""

        self.rect = Rect(rect)
        self.bg = normalize_color(bg)
        self.fmt = fmt
        self.prefix = prefix
        self.dst = Path(dst).expanduser().resolve() if dst else None
        self.custom_folder = custom_folder
        self.postfn = postfn
        self.last_passes = []
        self.last_result = None
        self.style = style
        self.composites = composites

        self.watch = []
        for w in watch:
            self.add_watchee(w)

        self.watch_restarts = []
        for w in watch_restarts:
            self.watch_restarts.append(self.add_watchee(w, "restart"))

        self.watch_soft = []
        for w in watch_soft:
            self.watch_soft.append(self.add_watchee(w, "soft"))

        self.name = name
        self.codepath = None
        self.rasterizer = rasterizer
        self.self_rasterizing = False
        self.hidden = solo == -1
        self.solo = solo
        self.preview_only = preview_only
        self.rstate = rstate
        self.clip = clip
        self.viewBox = viewBox
        self.direct_draw = direct_draw
        self.bg_render = bg_render
        self.layer = layer
        if self.layer:
            self.bg = normalize_color(None)

        if not rasterizer:
            if self.fmt == "svg":
                self.rasterizer = "svg"
            elif self.fmt == "pickle":
                self.rasterizer = "pickle"
            else:
                self.rasterizer = "skia"

    def add_watchee(self, w, flag=None):
        try:
            pw = Path(w).expanduser().resolve()
            if not pw.exists():
                print(w, "<<< does not exist (cannot be watched)")
            else:
                self.watch.append([pw, flag])
                return pw
        except TypeError:
            if isinstance(w, Font):
                self.watch.append([w, flag])
            else:
                raise Exception(
                    "Can only watch path strings, Paths, and Fonts")

    def __call__(self, func):
        self.func = func
        if not self.name:
            self.name = self.func.__name__
        return self

    def folder(self, filepath):
        return ""

    def pass_suffix(self):
        return self.name

    def passes(self, action, renderer_state, indices=[]):
        return [RenderPass(self, self.pass_suffix(), [self.rect])]

    def package(self, filepath, output_folder):
        pass

    def run(self, render_pass, renderer_state):
        if self.rstate:
            return render_pass.fn(*render_pass.args, renderer_state)
        else:
            return render_pass.fn(*render_pass.args)

    def runpost(self, result, render_pass, renderer_state):
        if self.postfn:
            return self.postfn(self, result)
        else:
            return result

    def draw_preview(self, scale, canvas: skia.Canvas, rect, result,
                     render_pass):
        sr = self.rect.scale(scale, "mnx", "mxx")
        SkiaPen.CompositeToCanvas(result, sr, canvas, scale, style=self.style)

    def hide(self):
        self.hidden = True
        return self

    def show(self):
        self.hidden = False
        return self

    def normalize_result(self, pens):
        if not pens:
            return DATPens()
        elif hasattr(pens, "pens"):
            return pens
        elif isinstance(pens, DATPen):
            return DATPens([pens])
        elif isinstance(pens, DATText):
            return DATPens([pens])
        elif not isinstance(pens, DATPens):
            return DATPens(pens)
        else:
            return pens