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
def do_drawing(self, size, frame, cairo_ctx): """ Update the backing store from a cairo context and schedule a REDRAW_EVENT (expose event) :param size: width, height in pixels of bot :param frame: frame # thar was drawn :param cairo_ctx: cairo context the bot was drawn on """ if self.get_window() and not self.bot_size: # Get initial size for window self.set_size_request(*size) self.bot_size = size self.backing_store = BackingStore.get_backingstore( self.width, self.height) cr = pycairo.Context(self.backing_store.surface) if self.scale_fit: self.scale_context_and_center(cr) cairo_ctx = driver.ensure_pycairo_context(cairo_ctx) cr.set_source_surface(cairo_ctx.get_target()) # Create the cairo context cr.set_operator(cairo.OPERATOR_SOURCE) cr.paint() self.queue_draw()
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
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)
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)
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
def do_drawing(self, size, frame, cairo_ctx): ''' Update the backing store from a cairo context and schedule a redraw (expose event) :param size: width, height in pixels of bot :param frame: frame # thar was drawn :param cairo_ctx: cairo context the bot was drawn on ''' if self.get_window() and not self.bot_size: # Get initial size for window self.set_size_request(*size) self.bot_size = size self.backing_store = BackingStore.get_backingstore(self.width, self.height) cr = pycairo.Context(self.backing_store.surface) if self.scale_fit: self.scale_context_and_center(cr) cairo_ctx = driver.ensure_pycairo_context(cairo_ctx) cr.set_source_surface(cairo_ctx.get_target()) # Create the cairo context cr.set_operator(cairo.OPERATOR_SOURCE) cr.paint() self.queue_draw() while Gtk.events_pending(): Gtk.main_iteration_do(False)
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)
def draw(self, widget, cr): """ Draw just the exposed part of the backing store, scaled to fit """ if self.bot_size is None: # No bot to draw yet. self.draw_default_image(cr) return cr = driver.ensure_pycairo_context(cr) surface = self.backing_store.surface cr.set_source_surface(surface) cr.paint()
def draw(self, widget, cr): ''' Draw just the exposed part of the backing store, scaled to fit ''' if self.bot_size is None: # No bot to draw yet. self.draw_default_image(cr) return cr = driver.ensure_pycairo_context(cr) surface = self.backing_store.surface cr.set_source_surface(surface) cr.paint()
def _render(self, ctx=None): if not self._doRender: return ctx = ctx or self._get_context() pycairo_ctx = driver.ensure_pycairo_context(ctx) # we build a PangoCairo context linked to cairo context # then we create a pango layout # we update the context as we already used a null one on the pre-rendering # supposedly there should not be a big performance penalty self._pang_ctx = pangocairo_create_context(pycairo_ctx) if self._fillcolor is not None: # Go to initial point (CORNER or CENTER): transform = self._call_transform_mode(self._transform) ctx.set_matrix(transform) ctx.translate(self.x, self.y - self.baseline) if self._outline is False: ctx.set_source_rgba(*self._fillcolor) PangoCairo.show_layout(pycairo_ctx, self.layout) PangoCairo.update_layout(pycairo_ctx, self.layout)
def _render(self, ctx=None): if not self._doRender: return ctx = ctx or self._get_context() pycairo_ctx = driver.ensure_pycairo_context(ctx) # we build a PangoCairo context linked to cairo context # then we create a pango layout # we update the context as we already used a null one on the pre-rendering # supposedly there should not be a big performance penalty self._pang_ctx = pangocairo_create_context(pycairo_ctx) if self._fillcolor is not None: # Go to initial point (CORNER or CENTER): transform = self._call_transform_mode(self._transform) ctx.set_matrix(transform) ctx.translate(self.x, self.y - self.baseline) if self._outline is False: ctx.set_source_rgba(*self._fillcolor) PangoCairo.show_layout(pycairo_ctx, self.layout) PangoCairo.update_layout(pycairo_ctx, self.layout)
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()
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._imagesurface = 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: imagesurface = surfaceref.surface sw = imagesurface.get_width() sh = imagesurface.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 imagesurface = cairo.RecordingSurface(cairo.CONTENT_COLOR_ALPHA, (0, 0, sw, sh)) ctx = cairo.Context(imagesurface) pycairo_ctx = driver.ensure_pycairo_context(ctx) svg.render_cairo(pycairo_ctx) elif os.path.splitext(path)[1].lower() == '.png': imagesurface = cairo.ImageSurface.create_from_png(path) sw = imagesurface.get_width() sh = imagesurface.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) imagesurface = cairo.ImageSurface.create_for_data(bgra_array, cairo.FORMAT_ARGB32, sw, sh, sw * 4) self._surface_cache[path] = SurfaceRef(imagesurface) 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) imagesurface = 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._imagesurface = imagesurface self._deferred_render()