Source code elsie/render/backends/cairo/draw.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import contextlib
import math
from typing import Tuple

import cairocffi as cairo

from .utils import get_rgb_color


def rounded_rectangle(ctx: cairo.Context, x, y, w, h, rx, ry):
    # https://www.cairographics.org/cookbook/roundedrectangles/
    arc_to_bezier = 0.55228475
    if rx > w - rx:
        rx = w / 2
    if ry > h - ry:
        ry = h / 2

    c1 = arc_to_bezier * rx
    c2 = arc_to_bezier * ry

    ctx.new_path()
    ctx.move_to(x + rx, y)
    ctx.rel_line_to(w - 2 * rx, 0.0)
    ctx.rel_curve_to(c1, 0.0, rx, c2, rx, ry)
    ctx.rel_line_to(0, h - 2 * ry)
    ctx.rel_curve_to(0.0, c2, c1 - rx, ry, -rx, ry)
    ctx.rel_line_to(-w + 2 * rx, 0)
    ctx.rel_curve_to(-c1, 0, -rx, -c2, -rx, -ry)
    ctx.rel_line_to(0, -h + 2 * ry)
    ctx.rel_curve_to(0.0, -c2, rx - c1, -ry, rx, -ry)
    ctx.close_path()


def fill_shape(ctx, callback, bg_color):
    ctx.set_source_rgb(*get_rgb_color(bg_color))
    callback()
    ctx.fill()


def stroke_shape(ctx, callback, color, stroke_width=None, stroke_dasharray=None):
    ctx.set_source_rgb(*get_rgb_color(color))
    stroke_width = stroke_width or 1
    ctx.set_line_width(stroke_width)
    if stroke_dasharray:
        ctx.set_dash([float(v) for v in stroke_dasharray.split()])
    callback()
    ctx.stroke()


def fill_stroke_shape(
    ctx, callback, color=None, bg_color=None, stroke_width=None, stroke_dasharray=None
):
    if bg_color:
        fill_shape(ctx, callback, bg_color=bg_color)
    if color:
        stroke_shape(
            ctx,
            callback,
            color=color,
            stroke_width=stroke_width,
            stroke_dasharray=stroke_dasharray,
        )


@contextlib.contextmanager
def ctx_scope(ctx: cairo.Context):
    try:
        ctx.save()
        yield ctx
    finally:
        ctx.restore()


def transform(
    ctx: cairo.Context,
    point: Tuple[float, float],
    rotation: float = None,
    scale_x=1.0,
    scale_y=1.0,
):
    ctx.translate(point[0], point[1])
    if rotation is not None:
        ctx.rotate(math.radians(rotation))
    if scale_x != 1.0 or scale_y != 1.0:
        ctx.scale(sx=scale_x, sy=scale_y)
    ctx.translate(-point[0], -point[1])


def apply_viewbox(ctx: cairo.Context, width: float, height: float, viewbox) -> float:
    viewbox_width = viewbox[2]
    viewbox_height = viewbox[3]
    scale = min(width / viewbox_width, height / viewbox_height)
    ctx.translate(-viewbox[0] * scale, -viewbox[1] * scale)
    ctx.scale(scale, scale)
    translate_x = (width / scale - viewbox_width) / 2
    translate_y = (height / scale - viewbox_height) / 2
    ctx.translate(translate_x, translate_y)
    return scale