def draw_circular_roman( ctx: cairo.Context, rng: Generator, pos_x: float, pos_y: float, radius_outer: float, radius_inner: float, ): chunks = rng.integers(6, 16) _calendar_base(ctx, pos_x, pos_y, radius_outer, radius_inner, chunks) ctx.select_font_face("Noto Sans Symbols") font_size = 0.8 * (radius_outer - radius_inner) ctx.set_font_size(font_size) angle_offset = pi / chunks for i, (x, y) in enumerate( points_along_arc( pos_x, pos_y, (radius_inner + radius_outer) / 2.0, angle_offset, angle_offset + tau, chunks, ), 1, ): with translation(ctx, x, y), rotation(ctx, (i * tau / chunks) + (pi / 2) - angle_offset): roman = int_to_roman(i) extents = ctx.text_extents(roman) ctx.move_to(-1 * extents.width / 2.0, extents.height / 2.0) ctx.show_text(roman) ctx.new_path()
def fill( self, ctx: cairo.Context, rng: Generator, x1: float, y1: float, x2: float, y2: float, ): with source(ctx, self.stops[-1].to_pattern()): ctx.fill_preserve() # Orient the canvas so that our gradient goes straight in direction of +Y. gradient_angle = angle((x1, y1), (x2, y2)) - (pi / 2) with translation(ctx, x1, y1), rotation(ctx, gradient_angle), source( ctx, self.stops[0].to_pattern()): # We translated and rotated the canvas, so our gradient control # vector is now from (0, 0) straight up: grad_control_y = distance((x1, y1), (x2, y2)) # Get a bounding box of what needs to be filled: start_x, start_y, end_x, end_y = ctx.fill_extents() ctx.new_path() for cx, cy, cr in self.pattern.func( rng, (start_x, start_y), (end_x, end_y), (0, 0), (0, grad_control_y), self.dot_radius, ): ctx.arc(cx, cy, cr, 0, tau) ctx.fill()
def render(self, text: str): lines = text.splitlines() for i, line in enumerate(lines): y_offset = self.blockheight * i with cairoctx.translation(self.ctx, self.scale * 3, self.scale * 3 + y_offset): # self.debug_square() self.render_line(line)
def letter(self, letter: str): color_top, color_bot = ALPHABET_PATTERN[letter.upper()] self.ctx.move_to(0, 0) self.rectangle(color_bot) with cairoctx.translation(self.ctx, 0, -(self.rect_height + self.margin)): self.ctx.move_to(0, 0) self.rectangle(color_top)
def letter(self, letter: str): color_top, color_bot = ALPHABET_PATTERN[letter.upper()] self.ctx.move_to(0, 0) self.triangle(color_bot) # Move up 2x diag_offset and flip the canvas upside-down with cairoctx.translation(self.ctx, 0, -2 * self.diag_offset): self.ctx.move_to(0, 0) with cairoctx.rotation(self.ctx, pi): self.triangle(color_top)
def render_line(self, line: str): for i, char in enumerate(line): if char == " ": continue orient_idx = i % 4 block_offset = i // 4 orient = self.orientations[orient_idx] x_offset = block_offset * self.blockwidth + orient[0][0] y_offset = orient[0][1] with cairoctx.translation(self.ctx, x_offset, y_offset): with cairoctx.rotation(self.ctx, orient[1]): self.letter(char)
def draw(self, ctx: cairo.Context): with translation(ctx, self.pos[0], self.pos[1]): with rotation(ctx, self.rotation): with source(ctx, self.color.to_pattern()): ctx.arc(0, 0, self.size, 0, math.tau) ctx.fill() if self.iris: self.iris.draw(ctx, relative_to=self.pos) self.pupil.draw(ctx, relative_to=self.pos) if self.eyelids: self.eyelids.draw(ctx, self.size, relative_to=self.pos)
def _calendar_mapped( ctx: cairo.Context, rng: Generator, pos_x: float, pos_y: float, radius_outer: float, radius_inner: float, mapping: Sequence[str], font_family: str = "Noto Sans Symbols", ): chunks = rng.integers(6, min(len(mapping), 24)) _calendar_base(ctx, pos_x, pos_y, radius_outer, radius_inner, chunks) ctx.select_font_face(font_family) font_size = 0.8 * (radius_outer - radius_inner) ctx.set_font_size(font_size) symbols = rng.choice(mapping, size=chunks, replace=False) angle_offset = pi / chunks for i, (x, y) in enumerate( points_along_arc( pos_x, pos_y, (radius_inner + radius_outer) / 2.0, angle_offset, angle_offset + tau, chunks, ), 1, ): with translation(ctx, x, y), rotation(ctx, (i * tau / chunks) + (pi / 2) - angle_offset): symbol = symbols[i - 1] extents = ctx.text_extents(symbol) ctx.move_to(-1 * extents.width / 2.0, extents.height / 2.0) ctx.show_text(symbol) ctx.new_path()