xref: /aosp_15_r20/external/fonttools/Lib/fontTools/svgLib/path/shapes.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughesimport re
2*e1fe3e4aSElliott Hughes
3*e1fe3e4aSElliott Hughes
4*e1fe3e4aSElliott Hughesdef _prefer_non_zero(*args):
5*e1fe3e4aSElliott Hughes    for arg in args:
6*e1fe3e4aSElliott Hughes        if arg != 0:
7*e1fe3e4aSElliott Hughes            return arg
8*e1fe3e4aSElliott Hughes    return 0.0
9*e1fe3e4aSElliott Hughes
10*e1fe3e4aSElliott Hughes
11*e1fe3e4aSElliott Hughesdef _ntos(n):
12*e1fe3e4aSElliott Hughes    # %f likes to add unnecessary 0's, %g isn't consistent about # decimals
13*e1fe3e4aSElliott Hughes    return ("%.3f" % n).rstrip("0").rstrip(".")
14*e1fe3e4aSElliott Hughes
15*e1fe3e4aSElliott Hughes
16*e1fe3e4aSElliott Hughesdef _strip_xml_ns(tag):
17*e1fe3e4aSElliott Hughes    # ElementTree API doesn't provide a way to ignore XML namespaces in tags
18*e1fe3e4aSElliott Hughes    # so we here strip them ourselves: cf. https://bugs.python.org/issue18304
19*e1fe3e4aSElliott Hughes    return tag.split("}", 1)[1] if "}" in tag else tag
20*e1fe3e4aSElliott Hughes
21*e1fe3e4aSElliott Hughes
22*e1fe3e4aSElliott Hughesdef _transform(raw_value):
23*e1fe3e4aSElliott Hughes    # TODO assumes a 'matrix' transform.
24*e1fe3e4aSElliott Hughes    # No other transform functions are supported at the moment.
25*e1fe3e4aSElliott Hughes    # https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform
26*e1fe3e4aSElliott Hughes    # start simple: if you aren't exactly matrix(...) then no love
27*e1fe3e4aSElliott Hughes    match = re.match(r"matrix\((.*)\)", raw_value)
28*e1fe3e4aSElliott Hughes    if not match:
29*e1fe3e4aSElliott Hughes        raise NotImplementedError
30*e1fe3e4aSElliott Hughes    matrix = tuple(float(p) for p in re.split(r"\s+|,", match.group(1)))
31*e1fe3e4aSElliott Hughes    if len(matrix) != 6:
32*e1fe3e4aSElliott Hughes        raise ValueError("wrong # of terms in %s" % raw_value)
33*e1fe3e4aSElliott Hughes    return matrix
34*e1fe3e4aSElliott Hughes
35*e1fe3e4aSElliott Hughes
36*e1fe3e4aSElliott Hughesclass PathBuilder(object):
37*e1fe3e4aSElliott Hughes    def __init__(self):
38*e1fe3e4aSElliott Hughes        self.paths = []
39*e1fe3e4aSElliott Hughes        self.transforms = []
40*e1fe3e4aSElliott Hughes
41*e1fe3e4aSElliott Hughes    def _start_path(self, initial_path=""):
42*e1fe3e4aSElliott Hughes        self.paths.append(initial_path)
43*e1fe3e4aSElliott Hughes        self.transforms.append(None)
44*e1fe3e4aSElliott Hughes
45*e1fe3e4aSElliott Hughes    def _end_path(self):
46*e1fe3e4aSElliott Hughes        self._add("z")
47*e1fe3e4aSElliott Hughes
48*e1fe3e4aSElliott Hughes    def _add(self, path_snippet):
49*e1fe3e4aSElliott Hughes        path = self.paths[-1]
50*e1fe3e4aSElliott Hughes        if path:
51*e1fe3e4aSElliott Hughes            path += " " + path_snippet
52*e1fe3e4aSElliott Hughes        else:
53*e1fe3e4aSElliott Hughes            path = path_snippet
54*e1fe3e4aSElliott Hughes        self.paths[-1] = path
55*e1fe3e4aSElliott Hughes
56*e1fe3e4aSElliott Hughes    def _move(self, c, x, y):
57*e1fe3e4aSElliott Hughes        self._add("%s%s,%s" % (c, _ntos(x), _ntos(y)))
58*e1fe3e4aSElliott Hughes
59*e1fe3e4aSElliott Hughes    def M(self, x, y):
60*e1fe3e4aSElliott Hughes        self._move("M", x, y)
61*e1fe3e4aSElliott Hughes
62*e1fe3e4aSElliott Hughes    def m(self, x, y):
63*e1fe3e4aSElliott Hughes        self._move("m", x, y)
64*e1fe3e4aSElliott Hughes
65*e1fe3e4aSElliott Hughes    def _arc(self, c, rx, ry, x, y, large_arc):
66*e1fe3e4aSElliott Hughes        self._add(
67*e1fe3e4aSElliott Hughes            "%s%s,%s 0 %d 1 %s,%s"
68*e1fe3e4aSElliott Hughes            % (c, _ntos(rx), _ntos(ry), large_arc, _ntos(x), _ntos(y))
69*e1fe3e4aSElliott Hughes        )
70*e1fe3e4aSElliott Hughes
71*e1fe3e4aSElliott Hughes    def A(self, rx, ry, x, y, large_arc=0):
72*e1fe3e4aSElliott Hughes        self._arc("A", rx, ry, x, y, large_arc)
73*e1fe3e4aSElliott Hughes
74*e1fe3e4aSElliott Hughes    def a(self, rx, ry, x, y, large_arc=0):
75*e1fe3e4aSElliott Hughes        self._arc("a", rx, ry, x, y, large_arc)
76*e1fe3e4aSElliott Hughes
77*e1fe3e4aSElliott Hughes    def _vhline(self, c, x):
78*e1fe3e4aSElliott Hughes        self._add("%s%s" % (c, _ntos(x)))
79*e1fe3e4aSElliott Hughes
80*e1fe3e4aSElliott Hughes    def H(self, x):
81*e1fe3e4aSElliott Hughes        self._vhline("H", x)
82*e1fe3e4aSElliott Hughes
83*e1fe3e4aSElliott Hughes    def h(self, x):
84*e1fe3e4aSElliott Hughes        self._vhline("h", x)
85*e1fe3e4aSElliott Hughes
86*e1fe3e4aSElliott Hughes    def V(self, y):
87*e1fe3e4aSElliott Hughes        self._vhline("V", y)
88*e1fe3e4aSElliott Hughes
89*e1fe3e4aSElliott Hughes    def v(self, y):
90*e1fe3e4aSElliott Hughes        self._vhline("v", y)
91*e1fe3e4aSElliott Hughes
92*e1fe3e4aSElliott Hughes    def _line(self, c, x, y):
93*e1fe3e4aSElliott Hughes        self._add("%s%s,%s" % (c, _ntos(x), _ntos(y)))
94*e1fe3e4aSElliott Hughes
95*e1fe3e4aSElliott Hughes    def L(self, x, y):
96*e1fe3e4aSElliott Hughes        self._line("L", x, y)
97*e1fe3e4aSElliott Hughes
98*e1fe3e4aSElliott Hughes    def l(self, x, y):
99*e1fe3e4aSElliott Hughes        self._line("l", x, y)
100*e1fe3e4aSElliott Hughes
101*e1fe3e4aSElliott Hughes    def _parse_line(self, line):
102*e1fe3e4aSElliott Hughes        x1 = float(line.attrib.get("x1", 0))
103*e1fe3e4aSElliott Hughes        y1 = float(line.attrib.get("y1", 0))
104*e1fe3e4aSElliott Hughes        x2 = float(line.attrib.get("x2", 0))
105*e1fe3e4aSElliott Hughes        y2 = float(line.attrib.get("y2", 0))
106*e1fe3e4aSElliott Hughes
107*e1fe3e4aSElliott Hughes        self._start_path()
108*e1fe3e4aSElliott Hughes        self.M(x1, y1)
109*e1fe3e4aSElliott Hughes        self.L(x2, y2)
110*e1fe3e4aSElliott Hughes
111*e1fe3e4aSElliott Hughes    def _parse_rect(self, rect):
112*e1fe3e4aSElliott Hughes        x = float(rect.attrib.get("x", 0))
113*e1fe3e4aSElliott Hughes        y = float(rect.attrib.get("y", 0))
114*e1fe3e4aSElliott Hughes        w = float(rect.attrib.get("width"))
115*e1fe3e4aSElliott Hughes        h = float(rect.attrib.get("height"))
116*e1fe3e4aSElliott Hughes        rx = float(rect.attrib.get("rx", 0))
117*e1fe3e4aSElliott Hughes        ry = float(rect.attrib.get("ry", 0))
118*e1fe3e4aSElliott Hughes
119*e1fe3e4aSElliott Hughes        rx = _prefer_non_zero(rx, ry)
120*e1fe3e4aSElliott Hughes        ry = _prefer_non_zero(ry, rx)
121*e1fe3e4aSElliott Hughes        # TODO there are more rules for adjusting rx, ry
122*e1fe3e4aSElliott Hughes
123*e1fe3e4aSElliott Hughes        self._start_path()
124*e1fe3e4aSElliott Hughes        self.M(x + rx, y)
125*e1fe3e4aSElliott Hughes        self.H(x + w - rx)
126*e1fe3e4aSElliott Hughes        if rx > 0:
127*e1fe3e4aSElliott Hughes            self.A(rx, ry, x + w, y + ry)
128*e1fe3e4aSElliott Hughes        self.V(y + h - ry)
129*e1fe3e4aSElliott Hughes        if rx > 0:
130*e1fe3e4aSElliott Hughes            self.A(rx, ry, x + w - rx, y + h)
131*e1fe3e4aSElliott Hughes        self.H(x + rx)
132*e1fe3e4aSElliott Hughes        if rx > 0:
133*e1fe3e4aSElliott Hughes            self.A(rx, ry, x, y + h - ry)
134*e1fe3e4aSElliott Hughes        self.V(y + ry)
135*e1fe3e4aSElliott Hughes        if rx > 0:
136*e1fe3e4aSElliott Hughes            self.A(rx, ry, x + rx, y)
137*e1fe3e4aSElliott Hughes        self._end_path()
138*e1fe3e4aSElliott Hughes
139*e1fe3e4aSElliott Hughes    def _parse_path(self, path):
140*e1fe3e4aSElliott Hughes        if "d" in path.attrib:
141*e1fe3e4aSElliott Hughes            self._start_path(initial_path=path.attrib["d"])
142*e1fe3e4aSElliott Hughes
143*e1fe3e4aSElliott Hughes    def _parse_polygon(self, poly):
144*e1fe3e4aSElliott Hughes        if "points" in poly.attrib:
145*e1fe3e4aSElliott Hughes            self._start_path("M" + poly.attrib["points"])
146*e1fe3e4aSElliott Hughes            self._end_path()
147*e1fe3e4aSElliott Hughes
148*e1fe3e4aSElliott Hughes    def _parse_polyline(self, poly):
149*e1fe3e4aSElliott Hughes        if "points" in poly.attrib:
150*e1fe3e4aSElliott Hughes            self._start_path("M" + poly.attrib["points"])
151*e1fe3e4aSElliott Hughes
152*e1fe3e4aSElliott Hughes    def _parse_circle(self, circle):
153*e1fe3e4aSElliott Hughes        cx = float(circle.attrib.get("cx", 0))
154*e1fe3e4aSElliott Hughes        cy = float(circle.attrib.get("cy", 0))
155*e1fe3e4aSElliott Hughes        r = float(circle.attrib.get("r"))
156*e1fe3e4aSElliott Hughes
157*e1fe3e4aSElliott Hughes        # arc doesn't seem to like being a complete shape, draw two halves
158*e1fe3e4aSElliott Hughes        self._start_path()
159*e1fe3e4aSElliott Hughes        self.M(cx - r, cy)
160*e1fe3e4aSElliott Hughes        self.A(r, r, cx + r, cy, large_arc=1)
161*e1fe3e4aSElliott Hughes        self.A(r, r, cx - r, cy, large_arc=1)
162*e1fe3e4aSElliott Hughes
163*e1fe3e4aSElliott Hughes    def _parse_ellipse(self, ellipse):
164*e1fe3e4aSElliott Hughes        cx = float(ellipse.attrib.get("cx", 0))
165*e1fe3e4aSElliott Hughes        cy = float(ellipse.attrib.get("cy", 0))
166*e1fe3e4aSElliott Hughes        rx = float(ellipse.attrib.get("rx"))
167*e1fe3e4aSElliott Hughes        ry = float(ellipse.attrib.get("ry"))
168*e1fe3e4aSElliott Hughes
169*e1fe3e4aSElliott Hughes        # arc doesn't seem to like being a complete shape, draw two halves
170*e1fe3e4aSElliott Hughes        self._start_path()
171*e1fe3e4aSElliott Hughes        self.M(cx - rx, cy)
172*e1fe3e4aSElliott Hughes        self.A(rx, ry, cx + rx, cy, large_arc=1)
173*e1fe3e4aSElliott Hughes        self.A(rx, ry, cx - rx, cy, large_arc=1)
174*e1fe3e4aSElliott Hughes
175*e1fe3e4aSElliott Hughes    def add_path_from_element(self, el):
176*e1fe3e4aSElliott Hughes        tag = _strip_xml_ns(el.tag)
177*e1fe3e4aSElliott Hughes        parse_fn = getattr(self, "_parse_%s" % tag.lower(), None)
178*e1fe3e4aSElliott Hughes        if not callable(parse_fn):
179*e1fe3e4aSElliott Hughes            return False
180*e1fe3e4aSElliott Hughes        parse_fn(el)
181*e1fe3e4aSElliott Hughes        if "transform" in el.attrib:
182*e1fe3e4aSElliott Hughes            self.transforms[-1] = _transform(el.attrib["transform"])
183*e1fe3e4aSElliott Hughes        return True
184