svgLib: Add support for arcs in SVG path parser
Implement the arc to curve points calculation using the original svg.path library and use the curve points for the pen. The Arc class and methods to parameterize and get curve points are copied. This makes the svg to glif conversion possible for any SVGs.
This commit is contained in:
parent
5e23b0545b
commit
82e119e0e3
@ -10,6 +10,7 @@
|
||||
from __future__ import (
|
||||
print_function, division, absolute_import, unicode_literals)
|
||||
from fontTools.misc.py23 import *
|
||||
from math import sqrt, cos, sin, acos, degrees, radians
|
||||
import re
|
||||
|
||||
COMMANDS = set('MmZzLlHhVvCcSsQqTtAa')
|
||||
@ -18,6 +19,98 @@ UPPERCASE = set('MZLHVCSQTA')
|
||||
COMMAND_RE = re.compile("([MmZzLlHhVvCcSsQqTtAa])")
|
||||
FLOAT_RE = re.compile("[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?")
|
||||
|
||||
class Arc(object):
|
||||
|
||||
def __init__(self, start, radius, rotation, arc, sweep, end):
|
||||
"""radius is complex, rotation is in degrees,
|
||||
large and sweep are 1 or 0 (True/False also work)"""
|
||||
|
||||
self.start = start
|
||||
self.radius = radius
|
||||
self.rotation = rotation
|
||||
self.arc = bool(arc)
|
||||
self.sweep = bool(sweep)
|
||||
self.end = end
|
||||
|
||||
self._parameterize()
|
||||
|
||||
def _parameterize(self):
|
||||
# Conversion from endpoint to center parameterization
|
||||
# http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
|
||||
|
||||
cosr = cos(radians(self.rotation))
|
||||
sinr = sin(radians(self.rotation))
|
||||
dx = (self.start.real - self.end.real) / 2
|
||||
dy = (self.start.imag - self.end.imag) / 2
|
||||
x1prim = cosr * dx + sinr * dy
|
||||
x1prim_sq = x1prim * x1prim
|
||||
y1prim = -sinr * dx + cosr * dy
|
||||
y1prim_sq = y1prim * y1prim
|
||||
|
||||
rx = self.radius.real
|
||||
rx_sq = rx * rx
|
||||
ry = self.radius.imag
|
||||
ry_sq = ry * ry
|
||||
|
||||
# Correct out of range radii
|
||||
radius_check = (x1prim_sq / rx_sq) + (y1prim_sq / ry_sq)
|
||||
if radius_check > 1:
|
||||
rx *= sqrt(radius_check)
|
||||
ry *= sqrt(radius_check)
|
||||
rx_sq = rx * rx
|
||||
ry_sq = ry * ry
|
||||
|
||||
t1 = rx_sq * y1prim_sq
|
||||
t2 = ry_sq * x1prim_sq
|
||||
c = sqrt(abs((rx_sq * ry_sq - t1 - t2) / (t1 + t2)))
|
||||
|
||||
if self.arc == self.sweep:
|
||||
c = -c
|
||||
cxprim = c * rx * y1prim / ry
|
||||
cyprim = -c * ry * x1prim / rx
|
||||
|
||||
self.center = complex((cosr * cxprim - sinr * cyprim) +
|
||||
((self.start.real + self.end.real) / 2),
|
||||
(sinr * cxprim + cosr * cyprim) +
|
||||
((self.start.imag + self.end.imag) / 2))
|
||||
|
||||
ux = (x1prim - cxprim) / rx
|
||||
uy = (y1prim - cyprim) / ry
|
||||
vx = (-x1prim - cxprim) / rx
|
||||
vy = (-y1prim - cyprim) / ry
|
||||
n = sqrt(ux * ux + uy * uy)
|
||||
p = ux
|
||||
theta = degrees(acos(p / n))
|
||||
if uy < 0:
|
||||
theta = -theta
|
||||
self.theta = theta % 360
|
||||
|
||||
n = sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy))
|
||||
p = ux * vx + uy * vy
|
||||
d = p/n
|
||||
# In certain cases the above calculation can through inaccuracies
|
||||
# become just slightly out of range, f ex -1.0000000000000002.
|
||||
if d > 1.0:
|
||||
d = 1.0
|
||||
elif d < -1.0:
|
||||
d = -1.0
|
||||
delta = degrees(acos(d))
|
||||
if (ux * vy - uy * vx) < 0:
|
||||
delta = -delta
|
||||
self.delta = delta % 360
|
||||
if not self.sweep:
|
||||
self.delta -= 360
|
||||
|
||||
def point(self, pos):
|
||||
angle = radians(self.theta + (self.delta * pos))
|
||||
cosr = cos(radians(self.rotation))
|
||||
sinr = sin(radians(self.rotation))
|
||||
|
||||
x = (cosr * cos(angle) * self.radius.real - sinr * sin(angle) *
|
||||
self.radius.imag + self.center.real)
|
||||
y = (sinr * cos(angle) * self.radius.real + cosr * sin(angle) *
|
||||
self.radius.imag + self.center.imag)
|
||||
return complex(x, y)
|
||||
|
||||
def _tokenize_path(pathdef):
|
||||
for x in COMMAND_RE.split(pathdef):
|
||||
@ -209,7 +302,33 @@ def parse_path(pathdef, pen, current_pos=(0, 0)):
|
||||
last_control = control
|
||||
|
||||
elif command == 'A':
|
||||
raise NotImplementedError('arcs are not supported')
|
||||
# Arc
|
||||
radius = float(elements.pop()) + float(elements.pop()) * 1j
|
||||
rotation = float(elements.pop())
|
||||
arc = float(elements.pop())
|
||||
sweep = float(elements.pop())
|
||||
end = float(elements.pop()) + float(elements.pop()) * 1j
|
||||
|
||||
if not absolute:
|
||||
if end == 0:
|
||||
# Guard against a situation where arc start and end being same.
|
||||
# That results division by zero issues in Arc parameterization.
|
||||
end = 0.00009
|
||||
end += current_pos
|
||||
|
||||
svg_arc = Arc(current_pos, radius, rotation, arc, sweep, end)
|
||||
arc_points = []
|
||||
for x in range(1, 5):
|
||||
# There are infinite points in an arc, but for our context,
|
||||
# define the arc using 5 points(0.2, 0.4, 0.6...)
|
||||
arc_point = svg_arc.point(x*0.2)
|
||||
arc_points.append((arc_point.real, arc_point.imag))
|
||||
pen.qCurveTo(
|
||||
(current_pos.real, current_pos.imag),
|
||||
*arc_points,
|
||||
(end.real, end.imag)
|
||||
)
|
||||
current_pos = end
|
||||
|
||||
# no final Z command, it's an open path
|
||||
if start_pos is not None:
|
||||
|
Loading…
x
Reference in New Issue
Block a user