def draw_player(cr: cairo.Context, player: Player) -> None: with save_context(cr): cr.translate(player.x, player.y) with save_context(cr): cr.rotate(player.rotation) # draw player cr.set_source_rgb(0, 0, 0) cr.arc(0, 0, 10, 0, math.tau) cr.fill() # draw arms cr.move_to(2, -10) cr.rel_line_to(12, 0) cr.move_to(2, 10) cr.rel_line_to(12, 0) cr.stroke() # draw health cr.set_source_rgb(1, 1, 1) cr.rectangle(-20, -35, 40, 8) cr.fill() # cr.set_source_rgb(.1, .9, .2) cr.set_source_rgb(2 * (1 - player.health), 2 * player.health, 0) cr.rectangle(-20, -35, 40 * player.health, 8) cr.fill() cr.set_line_width(1) cr.set_source_rgb(0, 0, 0) cr.rectangle(-20, -35, 40, 8) cr.stroke()
def rotation(ctx: cairo.Context, radians: float): ctx.rotate(radians) try: yield finally: ctx.rotate(-radians)
def draw_trains(self, layout: Layout, cr: Context): for train in layout.trains.values(): car_start = train.position annotation = train.meta.get("annotation") for i, car in enumerate(train.cars): front_bogey_offset, rear_bogey_offset = car.bogey_offsets bogey_spacing = rear_bogey_offset - front_bogey_offset front_bogey_position = car_start - front_bogey_offset front_bogey_xy = self.transform_track_point(front_bogey_position) rear_bogey_position, rear_bogey_xy = self.point_back( front_bogey_position, bogey_spacing ) cr.save() cr.translate(front_bogey_xy[0], front_bogey_xy[1]) cr.rotate( math.pi + math.atan2( front_bogey_xy[1] - rear_bogey_xy[1], front_bogey_xy[0] - rear_bogey_xy[0], ) ) cr.set_source_rgb(*hex_to_rgb(train.meta.get("color", "#a0a0ff"))) if i == 0 and annotation: cr.move_to(0, -10) cr.set_font_size(5) cr.show_text(annotation) cr.set_line_width(4) cr.move_to(-front_bogey_offset, 0) cr.line_to(car.length - front_bogey_offset, 0) cr.stroke() cr.set_line_width(6) cr.move_to(1 - front_bogey_offset, 0) cr.line_to(car.length - front_bogey_offset - 1, 0) cr.stroke() if i == 0 and train.lights_on: cr.set_source_rgba(1, 1, 0.2, 0.5) for y in (-2.5, 2.5): cr.move_to(-front_bogey_offset - 1, y) cr.arc( -front_bogey_offset - 1, y, 10, 6 / 7 * math.pi, math.pi * 8 / 7, ) cr.close_path() cr.fill() cr.restore() car_start = rear_bogey_position - (car.length - rear_bogey_offset + 1)
def illustrate(self, surface): if self.art != None: illustration = Context(surface) illustration.scale(0.6, 0.6) illustration.translate(self.w / 6, self.h / 6) self.art.render_cairo(illustration) illustration.translate(self.w * 4 / 3, self.h * 4 / 3) illustration.rotate(pi) self.art.render_cairo(illustration)
def draw_piece(self, piece: Piece, cr: Context, drawing_options: DrawingOptions): if not piece.position: return cr.save() cr.translate(piece.position.x, piece.position.y) cr.rotate(piece.position.angle) piece.draw(cr, drawing_options) relative_positions = piece.relative_positions() for anchor_name, anchor in piece.anchors.items(): # if anchor.position != piece.position + relative_positions[anchor_name] + Position(0, 0, math.pi): # cr.move_to(0, 0) # cr.line_to(anchor.position.x, anchor.position.y) # cr.stroke() cr.save() cr.translate( relative_positions[anchor_name].x, relative_positions[anchor_name].y ) cr.rotate(relative_positions[anchor_name].angle) if len(anchor) == 2: cr.set_source_rgb(1, 0.5, 0.5) else: cr.set_source_rgb(0.5, 1, 0.5) next_piece, next_anchor_name = anchor.next(piece) cr.arc( 0, 0, 3 if (piece.placement and anchor_name == piece.anchor_names[0]) or ( next_piece and next_piece.placement and next_anchor_name == next_piece.anchor_names[0] ) else 1, 0, math.tau, ) cr.fill() cr.restore() cr.restore()
def illustrate(self, surface): if self.suit != None: suits = Context(surface) suits.scale(0.5, 0.5) suits.translate(self.w / 8, self.h / 12) self.suit.render_cairo(suits) suits.translate(self.w * 7 / 4, self.h * 11 / 6) suits.rotate(pi) self.suit.render_cairo(suits) if self.art != None: illustration = Context(surface) sf = self.w / self.art.get_dimension_data()[2] illustration.scale(sf, sf) self.art.render_cairo(illustration)
def draw_sensor( self, sensor: Sensor, layout: Layout, cr: Context, drawing_options: DrawingOptions = None, ): if not sensor.position: return cr.save() cr.translate(sensor.position.x, sensor.position.y) cr.rotate(sensor.position.angle) sensor.draw(cr, drawing_options or self.drawing_options) cr.restore()
def arrowhead(context: cairo.Context, size: float, position: Point, direction: Point = None, front_angle: float = math.pi / 4, back_angle: float = math.pi / 2): '''Add an arrow head to the current path, starting at the current point.''' if direction is None: direction = Point(0, 1) halfwidth = math.tan(front_angle / 2) * size back_height = halfwidth / math.tan(back_angle / 2) context.move_to(*position) context.rotate(-math.atan2(direction.x, direction.y)) context.rel_line_to(halfwidth, -back_height) context.rel_line_to(-halfwidth, size) context.rel_line_to(-halfwidth, -size) context.fill()
def screen(self, base_w, base_h, ctx: cairo.Context, display_id: int): if display_id == 0: self.decode_screen() ctx.translate(base_w * self._scale / 2, base_h * self._scale / 2) ctx.rotate(-radians(self._screen_rotation_degrees)) if self._screen_rotation_degrees == 90 or self._screen_rotation_degrees == 270: ctx.translate(-base_h * self._scale / 2, -base_w * self._scale / 2) else: ctx.translate(-base_w * self._scale / 2, -base_h * self._scale / 2) ctx.scale(self._scale, self._scale) if display_id == 0: ctx.set_source_surface(self._upper_image) else: ctx.set_source_surface(self._lower_image) ctx.get_source().set_filter(cairo.Filter.NEAREST) ctx.paint() if self._after_render_hook: self._after_render_hook(ctx, display_id)
def draw_speed_limit(ctx: Context, surface_marking: SurfaceMarking, limit: int, start_zone: bool): """Draw a speed limit on the road. Add a cross if it is the end of a speed limit zone. Args: start_zone: Is this speed limit the start or end? """ ctx.translate(surface_marking.center.x, surface_marking.center.y ) # Translate to the center point of the surface marking ctx.rotate(surface_marking.orientation) ctx.translate(0, surface_marking.width / 2) # Translate to the middle # Position text and scale it ctx.rotate(-math.pi / 2) # rotate by 90deg ctx.scale(1, -1) # mirror y-axis to display text correctly ctx.scale(0.7245, 1.8506) # scale text to match rule book # Set font options ctx.select_font_face("OpenDinSchriftenEngshrift", cairo.FONT_SLANT_NORMAL) ctx.set_font_size(0.4) ctx.text_path(str(limit)) ctx.set_line_width(0.01) # Draw text _, _, text_width, text_height, _, _ = ctx.text_extents(str(limit)) ctx.fill_preserve() ctx.stroke() if not start_zone: # Draw cross padding = 0.025 ctx.set_line_width(0.03) ctx.move_to(-padding, padding) ctx.line_to(text_width + padding, -text_height - padding) ctx.stroke() ctx.move_to(-padding, -text_height - padding) ctx.line_to(text_width + padding, padding) ctx.stroke()
def _to_png(self, font, font_position=None, dst=None, limit=800, size=1500, tab_width=1500, padding_characters=""): """Use HB, FreeType and Cairo to produce a png for a table. Parameters ---------- font: DFont font_position: str Label indicating which font has been used. dst: str Path to output image. If no path is given, return in-memory """ # TODO (M Foley) better packaging for pycairo, freetype-py # and uharfbuzz. # Users should be able to pip install these bindings without needing # to install the correct libs. # A special mention to the individuals who maintain these packages. Using # these dependencies has sped up the process of creating diff images # significantly. It's an incredible age we live in. y_tab = int(1500 / 25) x_tab = int(tab_width / 64) width, height = 1024, 200 cells_per_row = int((width - x_tab) / x_tab) # Compute height of image x, y, baseline = x_tab, 0, 0 for idx, row in enumerate(self._data[:limit]): x += x_tab if idx % cells_per_row == 0: y += y_tab x = x_tab height += y height += 100 # draw image Z = ImageSurface(FORMAT_ARGB32, width, height) ctx = Context(Z) ctx.rectangle(0, 0, width, height) ctx.set_source_rgb(1, 1, 1) ctx.fill() # label image ctx.set_font_size(30) ctx.set_source_rgb(0.5, 0.5, 0.5) ctx.move_to(x_tab, 50) ctx.show_text("{}: {}".format(self.table_name, len(self._data))) ctx.move_to(x_tab, 100) if font_position: ctx.show_text("Font Set: {}".format(font_position)) if len(self._data) > limit: ctx.set_font_size(20) ctx.move_to(x_tab, 150) ctx.show_text( "Warning: {} different items. Only showing most serious {}". format(len(self._data), limit)) hb.ot_font_set_funcs(font.hbfont) # Draw glyphs x, y, baseline = x_tab, 200, 0 x_pos = x_tab y_pos = 200 for idx, row in enumerate(self._data[:limit]): string = "{}{}{}".format(padding_characters, row['string'], padding_characters) buf = self._shape_string(font, string, row['features']) char_info = buf.glyph_infos char_pos = buf.glyph_positions for info, pos in zip(char_info, char_pos): gid = info.codepoint font.ftfont.load_glyph(gid, flags=6) bitmap = font.ftslot.bitmap if bitmap.width > 0: ctx.set_source_rgb(0, 0, 0) glyph_surface = _make_image_surface( font.ftfont.glyph.bitmap, copy=False) ctx.set_source_surface( glyph_surface, x_pos + font.ftslot.bitmap_left + (pos.x_offset / 64.), y_pos - font.ftslot.bitmap_top - (pos.y_offset / 64.)) glyph_surface.flush() ctx.paint() x_pos += (pos.x_advance) / 64. y_pos += (pos.y_advance) / 64. x_pos += x_tab - (x_pos % x_tab) if idx % cells_per_row == 0: # add label if font_position: ctx.set_source_rgb(0.5, 0.5, 0.5) ctx.set_font_size(10) ctx.move_to(width - 20, y_pos) ctx.rotate(1.5708) ctx.show_text(font_position) ctx.set_source_rgb(0, 0, 0) ctx.rotate(-1.5708) # Start a new row y_pos += y_tab x_pos = x_tab Z.flush() if dst: Z.write_to_png(dst) else: img = StringIO() Z.write_to_png(img) return Image.open(img)
class Canvas: def __init__(self, width: int, height: Optional[int] = None, *, normalise: bool = True): if height is None: height = width self._width = width self._height = height self._surface = ImageSurface(FORMAT_ARGB32, width, height) self._context = Context(self._surface) self._normalise = normalise if self._normalise: self.scale(self._width, self._height) @property def width(self) -> int: return self._width if not self._normalise else 1 @property def height(self) -> int: return self._height if not self._normalise else 1 # TODO depreciate @property def context(self) -> Context: # pragma: no cover return self._context def save(self) -> None: self._context.save() def restore(self) -> None: self._context.restore() # Transformation def rotate(self, angle: float) -> None: self._context.rotate(angle) def translate(self, x: float, y: float) -> None: self._context.translate(x, y) def scale(self, width: int, height: int) -> None: self._context.scale(width, height) def set_line_width(self, width: float) -> None: self._context.set_line_width(width) # Colour functionality def set_colour(self, colour: Colour) -> None: self._context.set_source_rgba(colour.red, colour.blue, colour.green, colour.alpha) def set_grey(self, colour_value: float, alpha: float = 1) -> None: colour = Colour(*(colour_value, ) * 3, alpha) self.set_colour(colour) def set_black(self) -> None: self.set_colour(BLACK) def set_white(self) -> None: self.set_colour(WHITE) def set_background(self, colour: Colour) -> None: self._context.rectangle(0, 0, self.width, self.height) self.set_colour(colour) self._context.fill() def set_line_cap(self, line_cap: LineCap) -> None: self._context.set_line_cap(line_cap.value) def set_line_join(self, line_join: LineJoin) -> None: self._context.set_line_join(line_join.value) def set_fill_rule(self, file_rule: FillRule) -> None: self._context.set_fill_rule(file_rule.value) # Render methods def fill(self, preserve: bool = False) -> None: if not preserve: self._context.fill() else: self._context.fill_preserve() def stroke(self, preserve: bool = False) -> None: if not preserve: self._context.stroke() else: self._context.stroke_preserve() def clip(self) -> None: self._context.clip() def _draw_path(self, path: Iterable[Point], close_path: bool) -> None: self._context.new_sub_path() for p in path: self._context.line_to(*p) if close_path: self._context.close_path() def draw_path(self, path: Iterable[PointType], close_path: bool = False) -> None: points = (p if isinstance(p, Point) else Point(*p) for p in path) self._draw_path(points, close_path) def draw_polygon(self, polygon: Polygon) -> None: self._draw_path(polygon.points, close_path=True) def draw_circle(self, circle: Circle, fill: bool = False, stroke: bool = True) -> None: self._context.new_sub_path() self._context.arc(*circle.centre, circle.radius, 0, 2 * pi) def write_to_png(self, file_name: str) -> None: self._surface.write_to_png(file_name)
def draw(self, ctx: cairo.Context, **kwargs): layer = kwargs.get('layer', 0) opacity = kwargs.get('opacity', 1.0) debug = kwargs.get('debug', False) ctx.save() pos, angle, width, length = self.pos, self.angle, self.width, self.length # move origin to position of branch ctx.translate(pos.x, pos.y) # draw rectangle ctx.save() ctx.rotate(angle) ctx.rectangle(0, -width / 2, length, width) r, g, b = _get_branch_color(self) ctx.set_source_rgba(r, g, b, opacity) ctx.fill() ctx.restore() if debug: # draw label label = '{:d}'.format(self.index) ctx.save() ctx.set_source_rgba(1, 0, 0, opacity) ctx.set_font_size(width * 0.1) extents = ctx.text_extents(label) ctx.rotate(angle) ctx.translate(length / 2 - extents.width / 2, 0) ctx.rotate(-angle) ctx.show_text(label) ctx.restore() msg = '{}'.format(' '.join(self.text.upper())) filler = '— ' if msg: # draw message ctx.save() ft_size = width * 0.6 ctx.set_font_size(ft_size) ctx.set_line_width(0.0002) ctx.select_font_face('Impact', cairo.FontSlant.NORMAL, cairo.FontWeight.BOLD) max_width = 0.6 * length msg_extents = ctx.text_extents(msg) if msg_extents.width > max_width: num_fillers = 0 while msg_extents.width > max_width: if ft_size < 0.0001: break ft_size -= 0.00001 ctx.set_font_size(ft_size) msg_extents = ctx.text_extents(msg) else: filler_extents = ctx.text_extents(filler) num_fillers = max( math.floor((length - msg_extents.width) / filler_extents.x_advance / 2) - 2, 0) full_msg = '{} {} {}'.format(filler * num_fillers, msg, filler * num_fillers).strip() extents = ctx.text_extents(full_msg) if math.pi / 2 <= angle <= 3 * math.pi / 2: ctx.rotate(angle + math.pi) ctx.translate(-length, 0) ctx.scale(1, 1) ctx.translate(length / 2 - extents.width / 2, extents.height / 2) else: ctx.rotate(angle) ctx.translate(length / 2 - extents.width / 2, extents.height / 2) ctx.set_source_rgba(1, 1, 1, opacity) ctx.text_path(full_msg) ctx.fill() ctx.stroke() # give border ctx.set_source_rgba(0, 0, 0, opacity) ctx.text_path(full_msg) ctx.stroke() ctx.restore() # draw point # ctx.rotate(-angle) # ctx.arc(0, 0, 0.004, 0, 2 * math.pi) # ctx.set_source_rgb(0, 1, 0) # ctx.fill() # restore out of branch coordinates ctx.restore()
def main(marker, paper, format, bbox, emergency, place, recipient, sender, text, sender_is_recipient, map_href): """ """ mark = Location(*marker) handle, filename = mkstemp(prefix='safetymap-', suffix='.pdf') close(handle) if paper == 'a4': surf = PDFSurface(filename, 210*ptpmm, 297*ptpmm) elif paper == 'letter': surf = PDFSurface(filename, 8.5*ptpin, 11*ptpin) ctx = Context(surf) ctx.scale(ptpmm, ptpmm) set_font_face_from_file(ctx, 'assets/HelveticaNeue.ttc') if paper == 'a4': draw_a4_master(ctx, format, map_href) ctx.translate(19, 24) elif paper == 'letter': draw_letter_master(ctx, format, map_href) ctx.translate(21, 18) ctx.set_line_width(.25 * mmppt) ctx.set_source_rgb(*md_gray) ctx.set_dash([3 * mmppt]) reps = {'4up': 4, '2up-fridge': 2, 'poster': 0} if reps[format]: card_img, mark_point = get_map_image(bbox, 84, 39, mark) for i in range(reps[format]): # dashed outlines ctx.move_to(0, 61) ctx.line_to(0, 0) ctx.line_to(173, 0) ctx.line_to(173, 61) #ctx.move_to(86, 0) #ctx.line_to(86, 61) ctx.stroke() # two card sides and contents draw_card_left(ctx, recipient, sender, text, sender_is_recipient) ctx.translate(86.5, 0) draw_card_right(ctx, card_img, mark_point, emergency, place) ctx.translate(-86.5, 61) if format == '4up': # bottom dashed outline ctx.move_to(0, 0) ctx.line_to(172, 0) ctx.stroke() elif format == '2up-fridge': # prepare to draw sideways ctx.translate(0, 122.5) ctx.rotate(-pi/2) ctx.rectangle(0, 0, 122.5, 173) ctx.stroke() poster_img, mark_point = get_map_image(bbox, 109, 77, mark) draw_small_poster(ctx, poster_img, mark_point, emergency, place, recipient, sender, text, sender_is_recipient) elif format == 'poster': ctx.rectangle(0, 0, 173, 245) ctx.stroke() poster_img, mark_point = get_map_image(bbox, 153, 108, mark) draw_large_poster(ctx, poster_img, mark_point, emergency, place, recipient, sender, text, sender_is_recipient) surf.finish() chmod(filename, 0644) return filename