up follow livre
This commit is contained in:
parent
b4b4398bb0
commit
3a7a3849ae
12242 changed files with 2564461 additions and 6914 deletions
|
|
@ -0,0 +1,65 @@
|
|||
from fontTools.pens.transformPen import TransformPen
|
||||
from fontTools.misc import etree
|
||||
from fontTools.misc.textTools import tostr
|
||||
from .parser import parse_path
|
||||
from .shapes import PathBuilder
|
||||
|
||||
|
||||
__all__ = [tostr(s) for s in ("SVGPath", "parse_path")]
|
||||
|
||||
|
||||
class SVGPath(object):
|
||||
"""Parse SVG ``path`` elements from a file or string, and draw them
|
||||
onto a glyph object that supports the FontTools Pen protocol.
|
||||
|
||||
For example, reading from an SVG file and drawing to a Defcon Glyph:
|
||||
|
||||
.. code-block::
|
||||
|
||||
import defcon
|
||||
glyph = defcon.Glyph()
|
||||
pen = glyph.getPen()
|
||||
svg = SVGPath("path/to/a.svg")
|
||||
svg.draw(pen)
|
||||
|
||||
Or reading from a string containing SVG data, using the alternative
|
||||
'fromstring' (a class method):
|
||||
|
||||
.. code-block::
|
||||
|
||||
data = '<?xml version="1.0" ...'
|
||||
svg = SVGPath.fromstring(data)
|
||||
svg.draw(pen)
|
||||
|
||||
Both constructors can optionally take a 'transform' matrix (6-float
|
||||
tuple, or a FontTools Transform object) to modify the draw output.
|
||||
"""
|
||||
|
||||
def __init__(self, filename=None, transform=None):
|
||||
if filename is None:
|
||||
self.root = etree.ElementTree()
|
||||
else:
|
||||
tree = etree.parse(filename)
|
||||
self.root = tree.getroot()
|
||||
self.transform = transform
|
||||
|
||||
@classmethod
|
||||
def fromstring(cls, data, transform=None):
|
||||
self = cls(transform=transform)
|
||||
self.root = etree.fromstring(data)
|
||||
return self
|
||||
|
||||
def draw(self, pen):
|
||||
if self.transform:
|
||||
pen = TransformPen(pen, self.transform)
|
||||
pb = PathBuilder()
|
||||
# xpath | doesn't seem to reliable work so just walk it
|
||||
for el in self.root.iter():
|
||||
pb.add_path_from_element(el)
|
||||
original_pen = pen
|
||||
for path, transform in zip(pb.paths, pb.transforms):
|
||||
if transform:
|
||||
pen = TransformPen(original_pen, transform)
|
||||
else:
|
||||
pen = original_pen
|
||||
parse_path(path, pen)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
154
venv/lib/python3.13/site-packages/fontTools/svgLib/path/arc.py
Normal file
154
venv/lib/python3.13/site-packages/fontTools/svgLib/path/arc.py
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
"""Convert SVG Path's elliptical arcs to Bezier curves.
|
||||
|
||||
The code is mostly adapted from Blink's SVGPathNormalizer::DecomposeArcToCubic
|
||||
https://github.com/chromium/chromium/blob/93831f2/third_party/
|
||||
blink/renderer/core/svg/svg_path_parser.cc#L169-L278
|
||||
"""
|
||||
|
||||
from fontTools.misc.transform import Identity, Scale
|
||||
from math import atan2, ceil, cos, fabs, isfinite, pi, radians, sin, sqrt, tan
|
||||
|
||||
|
||||
TWO_PI = 2 * pi
|
||||
PI_OVER_TWO = 0.5 * pi
|
||||
|
||||
|
||||
def _map_point(matrix, pt):
|
||||
# apply Transform matrix to a point represented as a complex number
|
||||
r = matrix.transformPoint((pt.real, pt.imag))
|
||||
return r[0] + r[1] * 1j
|
||||
|
||||
|
||||
class EllipticalArc(object):
|
||||
def __init__(self, current_point, rx, ry, rotation, large, sweep, target_point):
|
||||
self.current_point = current_point
|
||||
self.rx = rx
|
||||
self.ry = ry
|
||||
self.rotation = rotation
|
||||
self.large = large
|
||||
self.sweep = sweep
|
||||
self.target_point = target_point
|
||||
|
||||
# SVG arc's rotation angle is expressed in degrees, whereas Transform.rotate
|
||||
# uses radians
|
||||
self.angle = radians(rotation)
|
||||
|
||||
# these derived attributes are computed by the _parametrize method
|
||||
self.center_point = self.theta1 = self.theta2 = self.theta_arc = None
|
||||
|
||||
def _parametrize(self):
|
||||
# convert from endopoint to center parametrization:
|
||||
# https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
|
||||
|
||||
# If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a
|
||||
# "lineto") joining the endpoints.
|
||||
# http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
|
||||
rx = fabs(self.rx)
|
||||
ry = fabs(self.ry)
|
||||
if not (rx and ry):
|
||||
return False
|
||||
|
||||
# If the current point and target point for the arc are identical, it should
|
||||
# be treated as a zero length path. This ensures continuity in animations.
|
||||
if self.target_point == self.current_point:
|
||||
return False
|
||||
|
||||
mid_point_distance = (self.current_point - self.target_point) * 0.5
|
||||
|
||||
point_transform = Identity.rotate(-self.angle)
|
||||
|
||||
transformed_mid_point = _map_point(point_transform, mid_point_distance)
|
||||
square_rx = rx * rx
|
||||
square_ry = ry * ry
|
||||
square_x = transformed_mid_point.real * transformed_mid_point.real
|
||||
square_y = transformed_mid_point.imag * transformed_mid_point.imag
|
||||
|
||||
# Check if the radii are big enough to draw the arc, scale radii if not.
|
||||
# http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
|
||||
radii_scale = square_x / square_rx + square_y / square_ry
|
||||
if radii_scale > 1:
|
||||
rx *= sqrt(radii_scale)
|
||||
ry *= sqrt(radii_scale)
|
||||
self.rx, self.ry = rx, ry
|
||||
|
||||
point_transform = Scale(1 / rx, 1 / ry).rotate(-self.angle)
|
||||
|
||||
point1 = _map_point(point_transform, self.current_point)
|
||||
point2 = _map_point(point_transform, self.target_point)
|
||||
delta = point2 - point1
|
||||
|
||||
d = delta.real * delta.real + delta.imag * delta.imag
|
||||
scale_factor_squared = max(1 / d - 0.25, 0.0)
|
||||
|
||||
scale_factor = sqrt(scale_factor_squared)
|
||||
if self.sweep == self.large:
|
||||
scale_factor = -scale_factor
|
||||
|
||||
delta *= scale_factor
|
||||
center_point = (point1 + point2) * 0.5
|
||||
center_point += complex(-delta.imag, delta.real)
|
||||
point1 -= center_point
|
||||
point2 -= center_point
|
||||
|
||||
theta1 = atan2(point1.imag, point1.real)
|
||||
theta2 = atan2(point2.imag, point2.real)
|
||||
|
||||
theta_arc = theta2 - theta1
|
||||
if theta_arc < 0 and self.sweep:
|
||||
theta_arc += TWO_PI
|
||||
elif theta_arc > 0 and not self.sweep:
|
||||
theta_arc -= TWO_PI
|
||||
|
||||
self.theta1 = theta1
|
||||
self.theta2 = theta1 + theta_arc
|
||||
self.theta_arc = theta_arc
|
||||
self.center_point = center_point
|
||||
|
||||
return True
|
||||
|
||||
def _decompose_to_cubic_curves(self):
|
||||
if self.center_point is None and not self._parametrize():
|
||||
return
|
||||
|
||||
point_transform = Identity.rotate(self.angle).scale(self.rx, self.ry)
|
||||
|
||||
# Some results of atan2 on some platform implementations are not exact
|
||||
# enough. So that we get more cubic curves than expected here. Adding 0.001f
|
||||
# reduces the count of sgements to the correct count.
|
||||
num_segments = int(ceil(fabs(self.theta_arc / (PI_OVER_TWO + 0.001))))
|
||||
for i in range(num_segments):
|
||||
start_theta = self.theta1 + i * self.theta_arc / num_segments
|
||||
end_theta = self.theta1 + (i + 1) * self.theta_arc / num_segments
|
||||
|
||||
t = (4 / 3) * tan(0.25 * (end_theta - start_theta))
|
||||
if not isfinite(t):
|
||||
return
|
||||
|
||||
sin_start_theta = sin(start_theta)
|
||||
cos_start_theta = cos(start_theta)
|
||||
sin_end_theta = sin(end_theta)
|
||||
cos_end_theta = cos(end_theta)
|
||||
|
||||
point1 = complex(
|
||||
cos_start_theta - t * sin_start_theta,
|
||||
sin_start_theta + t * cos_start_theta,
|
||||
)
|
||||
point1 += self.center_point
|
||||
target_point = complex(cos_end_theta, sin_end_theta)
|
||||
target_point += self.center_point
|
||||
point2 = target_point
|
||||
point2 += complex(t * sin_end_theta, -t * cos_end_theta)
|
||||
|
||||
point1 = _map_point(point_transform, point1)
|
||||
point2 = _map_point(point_transform, point2)
|
||||
target_point = _map_point(point_transform, target_point)
|
||||
|
||||
yield point1, point2, target_point
|
||||
|
||||
def draw(self, pen):
|
||||
for point1, point2, target_point in self._decompose_to_cubic_curves():
|
||||
pen.curveTo(
|
||||
(point1.real, point1.imag),
|
||||
(point2.real, point2.imag),
|
||||
(target_point.real, target_point.imag),
|
||||
)
|
||||
|
|
@ -0,0 +1,322 @@
|
|||
# SVG Path specification parser.
|
||||
# This is an adaptation from 'svg.path' by Lennart Regebro (@regebro),
|
||||
# modified so that the parser takes a FontTools Pen object instead of
|
||||
# returning a list of svg.path Path objects.
|
||||
# The original code can be found at:
|
||||
# https://github.com/regebro/svg.path/blob/4f9b6e3/src/svg/path/parser.py
|
||||
# Copyright (c) 2013-2014 Lennart Regebro
|
||||
# License: MIT
|
||||
|
||||
from .arc import EllipticalArc
|
||||
import re
|
||||
|
||||
|
||||
COMMANDS = set("MmZzLlHhVvCcSsQqTtAa")
|
||||
ARC_COMMANDS = set("Aa")
|
||||
UPPERCASE = set("MZLHVCSQTA")
|
||||
|
||||
COMMAND_RE = re.compile("([MmZzLlHhVvCcSsQqTtAa])")
|
||||
|
||||
# https://www.w3.org/TR/css-syntax-3/#number-token-diagram
|
||||
# but -6.e-5 will be tokenized as "-6" then "-5" and confuse parsing
|
||||
FLOAT_RE = re.compile(
|
||||
r"[-+]?" # optional sign
|
||||
r"(?:"
|
||||
r"(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][-+]?[0-9]+)?" # int/float
|
||||
r"|"
|
||||
r"(?:\.[0-9]+(?:[eE][-+]?[0-9]+)?)" # float with leading dot (e.g. '.42')
|
||||
r")"
|
||||
)
|
||||
BOOL_RE = re.compile("^[01]")
|
||||
SEPARATOR_RE = re.compile(f"[, \t]")
|
||||
|
||||
|
||||
def _tokenize_path(pathdef):
|
||||
arc_cmd = None
|
||||
for x in COMMAND_RE.split(pathdef):
|
||||
if x in COMMANDS:
|
||||
arc_cmd = x if x in ARC_COMMANDS else None
|
||||
yield x
|
||||
continue
|
||||
|
||||
if arc_cmd:
|
||||
try:
|
||||
yield from _tokenize_arc_arguments(x)
|
||||
except ValueError as e:
|
||||
raise ValueError(f"Invalid arc command: '{arc_cmd}{x}'") from e
|
||||
else:
|
||||
for token in FLOAT_RE.findall(x):
|
||||
yield token
|
||||
|
||||
|
||||
ARC_ARGUMENT_TYPES = (
|
||||
("rx", FLOAT_RE),
|
||||
("ry", FLOAT_RE),
|
||||
("x-axis-rotation", FLOAT_RE),
|
||||
("large-arc-flag", BOOL_RE),
|
||||
("sweep-flag", BOOL_RE),
|
||||
("x", FLOAT_RE),
|
||||
("y", FLOAT_RE),
|
||||
)
|
||||
|
||||
|
||||
def _tokenize_arc_arguments(arcdef):
|
||||
raw_args = [s for s in SEPARATOR_RE.split(arcdef) if s]
|
||||
if not raw_args:
|
||||
raise ValueError(f"Not enough arguments: '{arcdef}'")
|
||||
raw_args.reverse()
|
||||
|
||||
i = 0
|
||||
while raw_args:
|
||||
arg = raw_args.pop()
|
||||
|
||||
name, pattern = ARC_ARGUMENT_TYPES[i]
|
||||
match = pattern.search(arg)
|
||||
if not match:
|
||||
raise ValueError(f"Invalid argument for '{name}' parameter: {arg!r}")
|
||||
|
||||
j, k = match.span()
|
||||
yield arg[j:k]
|
||||
arg = arg[k:]
|
||||
|
||||
if arg:
|
||||
raw_args.append(arg)
|
||||
|
||||
# wrap around every 7 consecutive arguments
|
||||
if i == 6:
|
||||
i = 0
|
||||
else:
|
||||
i += 1
|
||||
|
||||
if i != 0:
|
||||
raise ValueError(f"Not enough arguments: '{arcdef}'")
|
||||
|
||||
|
||||
def parse_path(pathdef, pen, current_pos=(0, 0), arc_class=EllipticalArc):
|
||||
"""Parse SVG path definition (i.e. "d" attribute of <path> elements)
|
||||
and call a 'pen' object's moveTo, lineTo, curveTo, qCurveTo and closePath
|
||||
methods.
|
||||
|
||||
If 'current_pos' (2-float tuple) is provided, the initial moveTo will
|
||||
be relative to that instead being absolute.
|
||||
|
||||
If the pen has an "arcTo" method, it is called with the original values
|
||||
of the elliptical arc curve commands:
|
||||
|
||||
.. code-block::
|
||||
|
||||
pen.arcTo(rx, ry, rotation, arc_large, arc_sweep, (x, y))
|
||||
|
||||
Otherwise, the arcs are approximated by series of cubic Bezier segments
|
||||
("curveTo"), one every 90 degrees.
|
||||
"""
|
||||
# In the SVG specs, initial movetos are absolute, even if
|
||||
# specified as 'm'. This is the default behavior here as well.
|
||||
# But if you pass in a current_pos variable, the initial moveto
|
||||
# will be relative to that current_pos. This is useful.
|
||||
current_pos = complex(*current_pos)
|
||||
|
||||
elements = list(_tokenize_path(pathdef))
|
||||
# Reverse for easy use of .pop()
|
||||
elements.reverse()
|
||||
|
||||
start_pos = None
|
||||
command = None
|
||||
last_control = None
|
||||
|
||||
have_arcTo = hasattr(pen, "arcTo")
|
||||
|
||||
while elements:
|
||||
if elements[-1] in COMMANDS:
|
||||
# New command.
|
||||
last_command = command # Used by S and T
|
||||
command = elements.pop()
|
||||
absolute = command in UPPERCASE
|
||||
command = command.upper()
|
||||
else:
|
||||
# If this element starts with numbers, it is an implicit command
|
||||
# and we don't change the command. Check that it's allowed:
|
||||
if command is None:
|
||||
raise ValueError(
|
||||
"Unallowed implicit command in %s, position %s"
|
||||
% (pathdef, len(pathdef.split()) - len(elements))
|
||||
)
|
||||
last_command = command # Used by S and T
|
||||
|
||||
if command == "M":
|
||||
# Moveto command.
|
||||
x = elements.pop()
|
||||
y = elements.pop()
|
||||
pos = float(x) + float(y) * 1j
|
||||
if absolute:
|
||||
current_pos = pos
|
||||
else:
|
||||
current_pos += pos
|
||||
|
||||
# M is not preceded by Z; it's an open subpath
|
||||
if start_pos is not None:
|
||||
pen.endPath()
|
||||
|
||||
pen.moveTo((current_pos.real, current_pos.imag))
|
||||
|
||||
# when M is called, reset start_pos
|
||||
# This behavior of Z is defined in svg spec:
|
||||
# http://www.w3.org/TR/SVG/paths.html#PathDataClosePathCommand
|
||||
start_pos = current_pos
|
||||
|
||||
# Implicit moveto commands are treated as lineto commands.
|
||||
# So we set command to lineto here, in case there are
|
||||
# further implicit commands after this moveto.
|
||||
command = "L"
|
||||
|
||||
elif command == "Z":
|
||||
# Close path
|
||||
if current_pos != start_pos:
|
||||
pen.lineTo((start_pos.real, start_pos.imag))
|
||||
pen.closePath()
|
||||
current_pos = start_pos
|
||||
start_pos = None
|
||||
command = None # You can't have implicit commands after closing.
|
||||
|
||||
elif command == "L":
|
||||
x = elements.pop()
|
||||
y = elements.pop()
|
||||
pos = float(x) + float(y) * 1j
|
||||
if not absolute:
|
||||
pos += current_pos
|
||||
pen.lineTo((pos.real, pos.imag))
|
||||
current_pos = pos
|
||||
|
||||
elif command == "H":
|
||||
x = elements.pop()
|
||||
pos = float(x) + current_pos.imag * 1j
|
||||
if not absolute:
|
||||
pos += current_pos.real
|
||||
pen.lineTo((pos.real, pos.imag))
|
||||
current_pos = pos
|
||||
|
||||
elif command == "V":
|
||||
y = elements.pop()
|
||||
pos = current_pos.real + float(y) * 1j
|
||||
if not absolute:
|
||||
pos += current_pos.imag * 1j
|
||||
pen.lineTo((pos.real, pos.imag))
|
||||
current_pos = pos
|
||||
|
||||
elif command == "C":
|
||||
control1 = float(elements.pop()) + float(elements.pop()) * 1j
|
||||
control2 = float(elements.pop()) + float(elements.pop()) * 1j
|
||||
end = float(elements.pop()) + float(elements.pop()) * 1j
|
||||
|
||||
if not absolute:
|
||||
control1 += current_pos
|
||||
control2 += current_pos
|
||||
end += current_pos
|
||||
|
||||
pen.curveTo(
|
||||
(control1.real, control1.imag),
|
||||
(control2.real, control2.imag),
|
||||
(end.real, end.imag),
|
||||
)
|
||||
current_pos = end
|
||||
last_control = control2
|
||||
|
||||
elif command == "S":
|
||||
# Smooth curve. First control point is the "reflection" of
|
||||
# the second control point in the previous path.
|
||||
|
||||
if last_command not in "CS":
|
||||
# If there is no previous command or if the previous command
|
||||
# was not an C, c, S or s, assume the first control point is
|
||||
# coincident with the current point.
|
||||
control1 = current_pos
|
||||
else:
|
||||
# The first control point is assumed to be the reflection of
|
||||
# the second control point on the previous command relative
|
||||
# to the current point.
|
||||
control1 = current_pos + current_pos - last_control
|
||||
|
||||
control2 = float(elements.pop()) + float(elements.pop()) * 1j
|
||||
end = float(elements.pop()) + float(elements.pop()) * 1j
|
||||
|
||||
if not absolute:
|
||||
control2 += current_pos
|
||||
end += current_pos
|
||||
|
||||
pen.curveTo(
|
||||
(control1.real, control1.imag),
|
||||
(control2.real, control2.imag),
|
||||
(end.real, end.imag),
|
||||
)
|
||||
current_pos = end
|
||||
last_control = control2
|
||||
|
||||
elif command == "Q":
|
||||
control = float(elements.pop()) + float(elements.pop()) * 1j
|
||||
end = float(elements.pop()) + float(elements.pop()) * 1j
|
||||
|
||||
if not absolute:
|
||||
control += current_pos
|
||||
end += current_pos
|
||||
|
||||
pen.qCurveTo((control.real, control.imag), (end.real, end.imag))
|
||||
current_pos = end
|
||||
last_control = control
|
||||
|
||||
elif command == "T":
|
||||
# Smooth curve. Control point is the "reflection" of
|
||||
# the second control point in the previous path.
|
||||
|
||||
if last_command not in "QT":
|
||||
# If there is no previous command or if the previous command
|
||||
# was not an Q, q, T or t, assume the first control point is
|
||||
# coincident with the current point.
|
||||
control = current_pos
|
||||
else:
|
||||
# The control point is assumed to be the reflection of
|
||||
# the control point on the previous command relative
|
||||
# to the current point.
|
||||
control = current_pos + current_pos - last_control
|
||||
|
||||
end = float(elements.pop()) + float(elements.pop()) * 1j
|
||||
|
||||
if not absolute:
|
||||
end += current_pos
|
||||
|
||||
pen.qCurveTo((control.real, control.imag), (end.real, end.imag))
|
||||
current_pos = end
|
||||
last_control = control
|
||||
|
||||
elif command == "A":
|
||||
rx = abs(float(elements.pop()))
|
||||
ry = abs(float(elements.pop()))
|
||||
rotation = float(elements.pop())
|
||||
arc_large = bool(int(elements.pop()))
|
||||
arc_sweep = bool(int(elements.pop()))
|
||||
end = float(elements.pop()) + float(elements.pop()) * 1j
|
||||
|
||||
if not absolute:
|
||||
end += current_pos
|
||||
|
||||
# if the pen supports arcs, pass the values unchanged, otherwise
|
||||
# approximate the arc with a series of cubic bezier curves
|
||||
if have_arcTo:
|
||||
pen.arcTo(
|
||||
rx,
|
||||
ry,
|
||||
rotation,
|
||||
arc_large,
|
||||
arc_sweep,
|
||||
(end.real, end.imag),
|
||||
)
|
||||
else:
|
||||
arc = arc_class(
|
||||
current_pos, rx, ry, rotation, arc_large, arc_sweep, end
|
||||
)
|
||||
arc.draw(pen)
|
||||
|
||||
current_pos = end
|
||||
|
||||
# no final Z command, it's an open path
|
||||
if start_pos is not None:
|
||||
pen.endPath()
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
import re
|
||||
|
||||
|
||||
def _prefer_non_zero(*args):
|
||||
for arg in args:
|
||||
if arg != 0:
|
||||
return arg
|
||||
return 0.0
|
||||
|
||||
|
||||
def _ntos(n):
|
||||
# %f likes to add unnecessary 0's, %g isn't consistent about # decimals
|
||||
return ("%.3f" % n).rstrip("0").rstrip(".")
|
||||
|
||||
|
||||
def _strip_xml_ns(tag):
|
||||
# ElementTree API doesn't provide a way to ignore XML namespaces in tags
|
||||
# so we here strip them ourselves: cf. https://bugs.python.org/issue18304
|
||||
return tag.split("}", 1)[1] if "}" in tag else tag
|
||||
|
||||
|
||||
def _transform(raw_value):
|
||||
# TODO assumes a 'matrix' transform.
|
||||
# No other transform functions are supported at the moment.
|
||||
# https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform
|
||||
# start simple: if you aren't exactly matrix(...) then no love
|
||||
match = re.match(r"matrix\((.*)\)", raw_value)
|
||||
if not match:
|
||||
raise NotImplementedError
|
||||
matrix = tuple(float(p) for p in re.split(r"\s+|,", match.group(1)))
|
||||
if len(matrix) != 6:
|
||||
raise ValueError("wrong # of terms in %s" % raw_value)
|
||||
return matrix
|
||||
|
||||
|
||||
class PathBuilder(object):
|
||||
def __init__(self):
|
||||
self.paths = []
|
||||
self.transforms = []
|
||||
|
||||
def _start_path(self, initial_path=""):
|
||||
self.paths.append(initial_path)
|
||||
self.transforms.append(None)
|
||||
|
||||
def _end_path(self):
|
||||
self._add("z")
|
||||
|
||||
def _add(self, path_snippet):
|
||||
path = self.paths[-1]
|
||||
if path:
|
||||
path += " " + path_snippet
|
||||
else:
|
||||
path = path_snippet
|
||||
self.paths[-1] = path
|
||||
|
||||
def _move(self, c, x, y):
|
||||
self._add("%s%s,%s" % (c, _ntos(x), _ntos(y)))
|
||||
|
||||
def M(self, x, y):
|
||||
self._move("M", x, y)
|
||||
|
||||
def m(self, x, y):
|
||||
self._move("m", x, y)
|
||||
|
||||
def _arc(self, c, rx, ry, x, y, large_arc):
|
||||
self._add(
|
||||
"%s%s,%s 0 %d 1 %s,%s"
|
||||
% (c, _ntos(rx), _ntos(ry), large_arc, _ntos(x), _ntos(y))
|
||||
)
|
||||
|
||||
def A(self, rx, ry, x, y, large_arc=0):
|
||||
self._arc("A", rx, ry, x, y, large_arc)
|
||||
|
||||
def a(self, rx, ry, x, y, large_arc=0):
|
||||
self._arc("a", rx, ry, x, y, large_arc)
|
||||
|
||||
def _vhline(self, c, x):
|
||||
self._add("%s%s" % (c, _ntos(x)))
|
||||
|
||||
def H(self, x):
|
||||
self._vhline("H", x)
|
||||
|
||||
def h(self, x):
|
||||
self._vhline("h", x)
|
||||
|
||||
def V(self, y):
|
||||
self._vhline("V", y)
|
||||
|
||||
def v(self, y):
|
||||
self._vhline("v", y)
|
||||
|
||||
def _line(self, c, x, y):
|
||||
self._add("%s%s,%s" % (c, _ntos(x), _ntos(y)))
|
||||
|
||||
def L(self, x, y):
|
||||
self._line("L", x, y)
|
||||
|
||||
def l(self, x, y):
|
||||
self._line("l", x, y)
|
||||
|
||||
def _parse_line(self, line):
|
||||
x1 = float(line.attrib.get("x1", 0))
|
||||
y1 = float(line.attrib.get("y1", 0))
|
||||
x2 = float(line.attrib.get("x2", 0))
|
||||
y2 = float(line.attrib.get("y2", 0))
|
||||
|
||||
self._start_path()
|
||||
self.M(x1, y1)
|
||||
self.L(x2, y2)
|
||||
|
||||
def _parse_rect(self, rect):
|
||||
x = float(rect.attrib.get("x", 0))
|
||||
y = float(rect.attrib.get("y", 0))
|
||||
w = float(rect.attrib.get("width"))
|
||||
h = float(rect.attrib.get("height"))
|
||||
rx = float(rect.attrib.get("rx", 0))
|
||||
ry = float(rect.attrib.get("ry", 0))
|
||||
|
||||
rx = _prefer_non_zero(rx, ry)
|
||||
ry = _prefer_non_zero(ry, rx)
|
||||
# TODO there are more rules for adjusting rx, ry
|
||||
|
||||
self._start_path()
|
||||
self.M(x + rx, y)
|
||||
self.H(x + w - rx)
|
||||
if rx > 0:
|
||||
self.A(rx, ry, x + w, y + ry)
|
||||
self.V(y + h - ry)
|
||||
if rx > 0:
|
||||
self.A(rx, ry, x + w - rx, y + h)
|
||||
self.H(x + rx)
|
||||
if rx > 0:
|
||||
self.A(rx, ry, x, y + h - ry)
|
||||
self.V(y + ry)
|
||||
if rx > 0:
|
||||
self.A(rx, ry, x + rx, y)
|
||||
self._end_path()
|
||||
|
||||
def _parse_path(self, path):
|
||||
if "d" in path.attrib:
|
||||
self._start_path(initial_path=path.attrib["d"])
|
||||
|
||||
def _parse_polygon(self, poly):
|
||||
if "points" in poly.attrib:
|
||||
self._start_path("M" + poly.attrib["points"])
|
||||
self._end_path()
|
||||
|
||||
def _parse_polyline(self, poly):
|
||||
if "points" in poly.attrib:
|
||||
self._start_path("M" + poly.attrib["points"])
|
||||
|
||||
def _parse_circle(self, circle):
|
||||
cx = float(circle.attrib.get("cx", 0))
|
||||
cy = float(circle.attrib.get("cy", 0))
|
||||
r = float(circle.attrib.get("r"))
|
||||
|
||||
# arc doesn't seem to like being a complete shape, draw two halves
|
||||
self._start_path()
|
||||
self.M(cx - r, cy)
|
||||
self.A(r, r, cx + r, cy, large_arc=1)
|
||||
self.A(r, r, cx - r, cy, large_arc=1)
|
||||
|
||||
def _parse_ellipse(self, ellipse):
|
||||
cx = float(ellipse.attrib.get("cx", 0))
|
||||
cy = float(ellipse.attrib.get("cy", 0))
|
||||
rx = float(ellipse.attrib.get("rx"))
|
||||
ry = float(ellipse.attrib.get("ry"))
|
||||
|
||||
# arc doesn't seem to like being a complete shape, draw two halves
|
||||
self._start_path()
|
||||
self.M(cx - rx, cy)
|
||||
self.A(rx, ry, cx + rx, cy, large_arc=1)
|
||||
self.A(rx, ry, cx - rx, cy, large_arc=1)
|
||||
|
||||
def add_path_from_element(self, el):
|
||||
tag = _strip_xml_ns(el.tag)
|
||||
parse_fn = getattr(self, "_parse_%s" % tag.lower(), None)
|
||||
if not callable(parse_fn):
|
||||
return False
|
||||
parse_fn(el)
|
||||
if "transform" in el.attrib:
|
||||
self.transforms[-1] = _transform(el.attrib["transform"])
|
||||
return True
|
||||
Loading…
Add table
Add a link
Reference in a new issue