def draw_dodecahedron( ctx: cairo.Context, rng: Generator, pos_x: float, pos_y: float, radius: float, rotation: float = 0, ): # Outer boundary outer_points = list( points_along_arc(pos_x, pos_y, radius, rotation, rotation + tau, 10)) for prev_index, (bx, by) in enumerate(outer_points, -1): ax, ay = outer_points[prev_index] ctx.move_to(ax, ay) ctx.line_to(bx, by) ctx.stroke() # Frontside inner pentagon inner_fs_points = list( points_along_arc(pos_x, pos_y, 0.6 * radius, rotation, rotation + tau, 5)) for prev_index, (bx, by) in enumerate(inner_fs_points, -1): ax, ay = inner_fs_points[prev_index] ctx.move_to(ax, ay) ctx.line_to(bx, by) ctx.stroke() # Outer to frontside inner for (ax, ay), (bx, by) in zip(inner_fs_points, outer_points[::2]): ctx.move_to(ax, ay) ctx.line_to(bx, by) ctx.stroke() # backside inner pentagon offset = pi / 5 inner_bs_points = list( points_along_arc( pos_x, pos_y, 0.6 * radius, rotation + offset, rotation + offset + tau, 5, )) for prev_index, (bx, by) in enumerate(inner_bs_points, -1): ax, ay = inner_bs_points[prev_index] ctx.move_to(ax, ay) ctx.line_to(bx, by) ctx.stroke() # Outer to backside inner for (ax, ay), (bx, by) in zip(inner_bs_points, outer_points[1::2]): ctx.move_to(ax, ay) ctx.line_to(bx, by) ctx.stroke()
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 _calendar_base( ctx: cairo.Context, pos_x: float, pos_y: float, radius_outer: float, radius_inner: float, chunks: int, ): ctx.arc(pos_x, pos_y, radius_outer, 0, tau) ctx.stroke_preserve() ctx.arc(pos_x, pos_y, radius_inner, 0, tau) ctx.stroke() for (start_x, start_y), (end_x, end_y) in zip( points_along_arc(pos_x, pos_y, radius_inner, 0, tau, chunks), points_along_arc(pos_x, pos_y, radius_outer, 0, tau, chunks), ): ctx.move_to(start_x, start_y) ctx.line_to(end_x, end_y) ctx.stroke()
def draw_star(ctx: cairo.Context, rng: Generator, pos_x: float, pos_y: float, size: float): spikes = rng.integers(5, 8) outer_points = list( jitter_points(points_along_arc(pos_x, pos_y, size, 0, tau, spikes), rng, 0.1 * size), ) inner_offset = pi / spikes inner_points = list( jitter_points( points_along_arc(pos_x, pos_y, size / 2.0, inner_offset, inner_offset + tau, spikes), rng, 0.1 * size, )) ctx.move_to(*inner_points[-1]) for (ax, ay), (bx, by) in zip(outer_points, inner_points): ctx.line_to(ax, ay) ctx.line_to(bx, by) ctx.stroke_preserve() with source(ctx, Color(1.0, 1.0, 1.0).to_pattern()): ctx.fill()
def draw_moon_cycles( ctx: cairo.Context, rng: Generator, pos_x: float, pos_y: float, radius_outer: float, radius_inner: float, ): ctx.arc(pos_x, pos_y, radius_outer, 0, tau) ctx.arc(pos_x, pos_y, radius_inner, 0, tau) ctx.stroke() n_moons = rng.integers(6, 12) for i, (x, y) in enumerate( points_along_arc( pos_x, pos_y, (radius_outer + radius_inner) / 2.0, 0, tau, n_moons ) ): pct_done = i / n_moons eclipse_pct = (pct_done * 2.0) - 1.0 draw_moon(ctx, x, y, (radius_outer - radius_inner) / 2.3, eclipse_pct)
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()
def draw_star_band( ctx: cairo.Context, rng: Generator, pos_x: float, pos_y: float, radius_outer: float, radius_inner: float, ): band_center = (radius_outer + radius_inner) / 2 star_size = 0.2 * (radius_outer - radius_inner) n_stars = rng.integers(20, 50) ctx.arc(pos_x, pos_y, radius_outer, 0, tau) ctx.arc_negative(pos_x, pos_y, radius_inner, 0, -tau) ctx.stroke_preserve() ctx.clip_preserve() grad_offset_x = rng.uniform(radius_inner / 3.0, radius_inner) grad_offset_y = rng.uniform(radius_inner / 3.0, radius_inner) grad = PointLinearGradient( [Color(0, 0, 0), Color(1, 1, 1, 0.0)], Pattern.PACKED) grad.fill( ctx, rng, pos_x - grad_offset_x, pos_y - grad_offset_y, pos_x + grad_offset_x, pos_y + grad_offset_y, ) for x, y in jitter_points( points_along_arc(pos_x, pos_y, band_center, 0, tau, n_stars), rng, star_size): draw_star(ctx, rng, x, y, star_size) ctx.reset_clip()
def draw_tangents( ctx: cairo.Context, rng: Generator, pos_x: float, pos_y: float, radius_outer: float, radius_inner: float, ): origin_points = rng.choice([12, 24, 36, 48, 60]) for start_x, start_y in points_along_arc( pos_x, pos_y, radius_outer, 0, tau, origin_points ): angle_c_to_tangent = acos(radius_inner / radius_outer) angle_c_to_point = atan2(start_y - pos_y, start_x - pos_x) # 2 tangents: for op in (add, sub): angle_tangent = op(angle_c_to_point, angle_c_to_tangent) tangent_x = pos_x + radius_inner * cos(angle_tangent) tangent_y = pos_y + radius_inner * sin(angle_tangent) ctx.move_to(start_x, start_y) ctx.line_to(tangent_x, tangent_y) ctx.stroke()