Ejemplo n.º 1
0
    def path(self):
        if not self._pang_ctx:
            self._pre_render()

        # here we create a new cairo.Context in order to hold the pathdata
        tempCairoContext = cairo.Context(cairo.RecordingSurface(cairo.CONTENT_ALPHA, None))
        tempCairoContext = driver.ensure_pycairo_context(tempCairoContext)
        tempCairoContext.move_to(self.x, self.y - self.baseline)
        # in here we create a pangoCairoContext in order to display layout on it

        # supposedly showlayout should work, but it fills the path instead,
        # therefore we use layout_path instead to render the layout to pangoCairoContext
        # tempCairoContext.show_layout(self.layout)
        PangoCairo.layout_path(tempCairoContext, self.layout)
        # here we extract the path from the temporal cairo.Context we used to draw on the previous step
        pathdata = tempCairoContext.copy_path()

        # creates a BezierPath instance for storing new shoebot path
        p = BezierPath(self._bot)

        # parsing of cairo path to build a shoebot path
        for item in pathdata:
            cmd = item[0]
            args = item[1]
            if cmd == PATH_MOVE_TO:
                p.moveto(*args)
            elif cmd == PATH_LINE_TO:
                p.lineto(*args)
            elif cmd == PATH_CURVE_TO:
                p.curveto(*args)
            elif cmd == PATH_CLOSE_PATH:
                p.closepath()
        # cairo function for freeing path memory
        return p
Ejemplo n.º 2
0
 def _pre_render(self):
     # we use a new CairoContext to pre render the text
     rs = cairo.RecordingSurface(cairo.CONTENT_ALPHA, None)
     cr = cairo.Context(rs)
     cr = driver.ensure_pycairo_context(cr)
     self._pang_ctx = pangocairo_create_context(cr)
     self.layout = PangoCairo.create_layout(cr)
     # layout line spacing
     # TODO: the behaviour is not the same as nodebox yet
     # self.layout.set_spacing(int(((self._lineheight-1)*self._fontsize)*Pango.SCALE)) #pango requires an int casting
     # we pass pango font description and the text to the pango layout
     self.layout.set_font_description(self._fontface)
     self.layout.set_text(self.text, -1)
     # check if max text width is set and pass it to pango layout
     # text will wrap, meanwhile it checks if and indent has to be applied
     # indent is subordinated to width because it makes no sense on a single-line text block
     if self.width:
         self.layout.set_width(int(self.width) * Pango.SCALE)
         if self._indent:
             self.layout.set_indent(self._indent * Pango.SCALE)
     # set text alignment
     if self._align == "right":
         self.layout.set_alignment(Pango.Alignment.RIGHT)
     elif self._align == "center":
         self.layout.set_alignment(Pango.Alignment.CENTER)
     elif self._align == "justify":
         self.layout.set_alignment(Pango.Alignment.LEFT)
         self.layout.set_justify(True)
     else:
         self.layout.set_alignment(Pango.Alignment.LEFT)
Ejemplo n.º 3
0
    def path(self):
        if not self._pangocairo_ctx:
            self._pre_render()

        # Render path data to a temporary cairo Context
        cairo_ctx = cairo.Context(
            cairo.RecordingSurface(cairo.CONTENT_ALPHA, None))
        cairo_ctx = driver.ensure_pycairo_context(cairo_ctx)
        cairo_ctx.move_to(self.x, self.y - self.baseline)
        # show_layout should work here, but fills the path instead,
        # instead, use layout_path to render the layout.
        PangoCairo.layout_path(cairo_ctx, self._pango_layout)

        # Parse cairo path into Shoebot BezierPath
        cairo_text_path = cairo_ctx.copy_path()
        p = BezierPath(self._bot)
        for item in cairo_text_path:
            cmd = item[0]
            args = item[1]
            if cmd == PATH_MOVE_TO:
                p.moveto(*args)
            elif cmd == PATH_LINE_TO:
                p.lineto(*args)
            elif cmd == PATH_CURVE_TO:
                p.curveto(*args)
            elif cmd == PATH_CLOSE_PATH:
                p.closepath()

        del cairo_ctx
        return p
Ejemplo n.º 4
0
    def _pre_render(self):
        # we use a new CairoContext to pre render the text
        rs = cairo.RecordingSurface(cairo.CONTENT_ALPHA, None)
        cr = cairo.Context(rs)
        cr = driver.ensure_pycairo_context(cr)

        # apply font options if set
        if self.hintstyle or self.hintmetrics or self.subpixelorder or self.antialias:
            opts = cairo.FontOptions()
            # map values to Cairo constants
            if self.antialias:
                opts.set_antialias(
                    getattr(cairo.Antialias, self.antialias.upper()))
            if self.hintstyle:
                opts.set_hint_style(
                    getattr(cairo.HintStyle, self.hintstyle.upper()))
            if self.hintmetrics:
                opts.set_hint_metrics(
                    getattr(cairo.HintMetrics, self.hintmetrics.upper()))
            if self.subpixelorder:
                opts.set_subpixel_order(
                    getattr(cairo.SubpixelOrder, self.subpixelorder.upper()))
            cr.set_font_options(opts)

        self._pangocairo_ctx = pangocairo_create_context(cr)
        self._pango_layout = PangoCairo.create_layout(cr)
        # layout line spacing
        # TODO: the behaviour is not the same as nodebox yet
        # self.layout.set_spacing(int(((self.lineheight-1)*self._fontsize)*Pango.SCALE)) #pango requires an int casting
        # we pass pango font description and the text to the pango layout
        self._pango_layout.set_font_description(self._pango_fontface)

        if not self.markup_vars:
            self._pango_layout.set_text(self.text, -1)
            return
        # some of the specified settings require a Pango.Markup hack
        # see https://stackoverflow.com/questions/55533312/how-to-create-a-letter-spacing-attribute-with-pycairo
        # and https://developer.gnome.org/pango/1.46/pango-Markup.html

        # we want to output something like
        # <span letter_spacing="2048">Hello World</span>
        markup_styles = " ".join([
            f'{setting}="{value}"'
            for setting, value in self.markup_vars.items()
        ])
        self._pango_layout.set_markup(
            f"<span {markup_styles}>{self.text}</span>")

        # check if max text width is set and pass it to pango layout
        # text will wrap, meanwhile it checks if and indent has to be applied
        # indent is subordinated to width because it makes no sense on a single-line text block
        if self.width:
            self._pango_layout.set_width(int(self.width) * Pango.SCALE)
            if self.indent:
                self._pango_layout.set_indent(self.indent * Pango.SCALE)
        # set text alignment
        self._pango_layout.set_alignment(_alignment_name_to_pango(self.align))
        if self.align == "justify":
            self._pango_layout.set_justify(True)
Ejemplo n.º 5
0
    def create_rcontext(self, size, frame):
        '''
        Creates a recording surface for the bot to draw on

        :param size: The width and height of bot
        '''
        self.frame = frame
        width, height = size
        meta_surface = cairo.RecordingSurface(cairo.CONTENT_COLOR_ALPHA, (0, 0, width, height))

        ctx = cairo.Context(meta_surface)
        return ctx
Ejemplo n.º 6
0
    def contains(self, x, y):
        """
        Return cached bounds of this Grob.
        If bounds are not cached, render to a meta surface, and
        keep the meta surface and bounds cached.
        """
        if self._bounds:
            return self._bounds

        record_surface = cairo.RecordingSurface(cairo.CONTENT_COLOR_ALPHA,
                                                (-1, -1, 1, 1))
        dummy_ctx = cairo.Context(record_surface)
        self._traverse(dummy_ctx)

        in_fill = dummy_ctx.in_fill(x, y)
        return in_fill
Ejemplo n.º 7
0
    def _get_bounds(self):
        """
        Return cached bounds of this Grob.
        If bounds are not cached, render to a meta surface, and
        keep the meta surface and bounds cached.
        """
        if self._bounds:
            return self._bounds

        record_surface = cairo.RecordingSurface(cairo.CONTENT_COLOR_ALPHA,
                                                (-1, -1, 1, 1))
        dummy_ctx = cairo.Context(record_surface)
        self._traverse(dummy_ctx)

        self._bounds = dummy_ctx.path_extents()
        return self._bounds
Ejemplo n.º 8
0
    def snapshot(self, target=None, defer=None, autonumber=False):
        """Save the contents of current surface into a file or cairo surface/context.

        :param filename: Can be a filename or a Cairo surface.
        :param defer: When to snapshot, if set to True waits until the frame has finished rendering.
        :param autonumber: If true then a number will be appended to the filename.
        """
        if autonumber:
            file_number = self._frame
        else:
            file_number = None

        if isinstance(target, cairo.Surface):
            # snapshot to Cairo surface
            if defer is None:
                self._canvas.snapshot(target, defer)
                defer = False
            ctx = cairo.Context(target)
            # this used to be self._canvas.snapshot, but I couldn't make it work.
            # self._canvas.snapshot(target, defer)
            # TODO: check if this breaks when taking more than 1 snapshot
            self._canvas._drawqueue.render(ctx)
            return
        elif target is None:
            # If nothing specified, use a default filename from the script name
            script_file = self._namespace.get("__file__")
            if script_file:
                target = os.path.splitext(script_file)[0] + ".svg"
                file_number = True

        if target:
            # snapshot to file, target is a filename
            if defer is None:
                defer = True
            self._canvas.snapshot(target, defer=defer, file_number=file_number)
        else:
            raise ShoebotError(
                "Image not saved: no target, filename or default to save to."
            )
Ejemplo n.º 9
0
    def snapshot(self, target=None, defer=None, autonumber=False):
        '''Save the contents of current surface into a file or cairo surface/context

        :param filename: Can be a filename or a Cairo surface.
        :param defer: If true, buffering/threading may be employed however output will not be immediate.
        :param autonumber: If true then a number will be appended to the filename.
        '''
        if autonumber:
            file_number = self._frame
        else:
            file_number = None

        if isinstance(target, cairo.Surface):
            # snapshot to Cairo surface
            if defer is None:
                self._canvas.snapshot(surface, defer)
                defer = False
            ctx = cairo.Context(target)
            # this used to be self._canvas.snapshot, but I couldn't make it work.
            # self._canvas.snapshot(target, defer)
            # TODO: check if this breaks when taking more than 1 snapshot
            self._canvas._drawqueue.render(ctx)
            return
        elif target is None:
            # If nothing specified, use a default filename from the script name
            script_file = self._namespace.get('__file__')
            if script_file:
                target = os.path.splitext(script_file)[0] + '.svg'
                file_number = True

        if target:
            # snapshot to file, target is a filename
            if defer is None:
                defer = True
            self._canvas.snapshot(target, defer=defer, file_number=file_number)
        else:
            raise ShoebotError('No image saved')
Ejemplo n.º 10
0
    def __init__(self,
                 bot,
                 path=None,
                 x=0,
                 y=0,
                 width=None,
                 height=None,
                 alpha=1.0,
                 data=None,
                 pathmode=CORNER,
                 **kwargs):
        Grob.__init__(self, bot)
        ColorMixin.__init__(self, **kwargs)

        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.alpha = alpha
        self.path = path
        self.data = data
        self._pathmode = pathmode
        sh = sw = None  # Surface Height and Width

        if isinstance(self.data, cairo.ImageSurface):
            sw = self.data.get_width()
            sh = self.data.get_height()
            self._surface = self.data
        else:
            # checks if image data is passed in command call, in this case it wraps
            # the data in a StringIO oject in order to use it as a file
            # the data itself must contain an entire image, not just pixel data
            # it can be useful for example to retrieve images from the web without
            # writing temp files (e.g. using nodebox's web library, see example 1 of the library)
            # if no data is passed the path is used to open a local file
            if self.data is None:
                surfaceref = self._surface_cache.get(path)
                if surfaceref:
                    surface = surfaceref.surface
                    if isinstance(surface, cairo.RecordingSurface):
                        extents = surface.get_extents()
                        # extents has x, y which we dont use right now
                        sw = extents.width
                        sh = extents.height
                    else:
                        sw = surface.get_width()
                        sh = surface.get_height()
                elif os.path.splitext(
                        path)[1].lower() == ".svg" and Rsvg is not None:
                    handle = Rsvg.Handle()
                    svg = handle.new_from_file(path)
                    dimensions = svg.get_dimensions()
                    sw = dimensions.width
                    sh = dimensions.height
                    surface = cairo.RecordingSurface(cairo.CONTENT_COLOR_ALPHA,
                                                     (0, 0, sw, sh))
                    ctx = cairo.Context(surface)
                    pycairo_ctx = driver.ensure_pycairo_context(ctx)
                    svg.render_cairo(pycairo_ctx)
                elif os.path.splitext(path)[1].lower() == ".png":
                    surface = cairo.ImageSurface.create_from_png(path)
                    sw = surface.get_width()
                    sh = surface.get_height()
                else:
                    img = PILImage.open(path)

                    if img.mode != "RGBA":
                        img = img.convert("RGBA")

                    sw, sh = img.size
                    # Would be nice to not have to do some of these conversions :-\
                    bgra_data = img.tobytes("raw", "BGRA", 0, 1)
                    bgra_array = array.array("B", bgra_data)
                    surface = cairo.ImageSurface.create_for_data(
                        bgra_array, cairo.FORMAT_ARGB32, sw, sh, sw * 4)

                self._surface_cache[path] = SurfaceRef(surface)
            else:
                img = PILImage.open(StringIO(self.data))

                if img.mode != "RGBA":
                    img = img.convert("RGBA")

                sw, sh = img.size
                # Would be nice to not have to do some of these conversions :-\
                bgra_data = img.tobytes("raw", "BGRA", 0, 1)
                bgra_array = array.array("B", bgra_data)
                surface = cairo.ImageSurface.create_for_data(
                    bgra_array, cairo.FORMAT_ARGB32, sw, sh, sw * 4)

            if width is not None or height is not None:
                if width:
                    wscale = float(width) / sw
                else:
                    wscale = 1.0
                if height:
                    hscale = float(height) / sh
                else:
                    if width:
                        hscale = wscale
                    else:
                        hscale = 1.0
                self._transform.scale(wscale, hscale)

            self.width = width or sw
            self.height = height or sh
            self._surface = surface

        self._deferred_render()
Ejemplo n.º 11
0
 def _get_context(self):
     self._ctx = self._ctx or cairo.Context(cairo.RecordingSurface(cairo.CONTENT_ALPHA, None))
     return self._ctx