Revert "Merge pull request #1136 from santhoshtr/svg-arc-support"
This reverts commit 5c392bc86542fbbc0c63335cfedae0d1406b0794, reversing changes made to 4b69d77ae57776480901e2af82c7d9c1c29de8d6.
This commit is contained in:
parent
5c392bc865
commit
40e50b60c3
@ -10,7 +10,6 @@
|
|||||||
from __future__ import (
|
from __future__ import (
|
||||||
print_function, division, absolute_import, unicode_literals)
|
print_function, division, absolute_import, unicode_literals)
|
||||||
from fontTools.misc.py23 import *
|
from fontTools.misc.py23 import *
|
||||||
from math import sqrt, cos, sin, acos, degrees, radians
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
COMMANDS = set('MmZzLlHhVvCcSsQqTtAa')
|
COMMANDS = set('MmZzLlHhVvCcSsQqTtAa')
|
||||||
@ -19,98 +18,6 @@ UPPERCASE = set('MZLHVCSQTA')
|
|||||||
COMMAND_RE = re.compile("([MmZzLlHhVvCcSsQqTtAa])")
|
COMMAND_RE = re.compile("([MmZzLlHhVvCcSsQqTtAa])")
|
||||||
FLOAT_RE = re.compile("[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?")
|
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,
|
|
||||||
arc 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):
|
def _tokenize_path(pathdef):
|
||||||
for x in COMMAND_RE.split(pathdef):
|
for x in COMMAND_RE.split(pathdef):
|
||||||
@ -127,6 +34,9 @@ def parse_path(pathdef, pen, current_pos=(0, 0)):
|
|||||||
|
|
||||||
If 'current_pos' (2-float tuple) is provided, the initial moveTo will
|
If 'current_pos' (2-float tuple) is provided, the initial moveTo will
|
||||||
be relative to that instead being absolute.
|
be relative to that instead being absolute.
|
||||||
|
|
||||||
|
Arc segments (commands "A" or "a") are not currently supported, and raise
|
||||||
|
NotImplementedError.
|
||||||
"""
|
"""
|
||||||
# In the SVG specs, initial movetos are absolute, even if
|
# In the SVG specs, initial movetos are absolute, even if
|
||||||
# specified as 'm'. This is the default behavior here as well.
|
# specified as 'm'. This is the default behavior here as well.
|
||||||
@ -299,29 +209,7 @@ def parse_path(pathdef, pen, current_pos=(0, 0)):
|
|||||||
last_control = control
|
last_control = control
|
||||||
|
|
||||||
elif command == 'A':
|
elif command == 'A':
|
||||||
# Arc
|
raise NotImplementedError('arcs are not supported')
|
||||||
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:
|
|
||||||
end += current_pos
|
|
||||||
|
|
||||||
if end == current_pos:
|
|
||||||
# Guard against a situation where arc start and end being same.
|
|
||||||
# That results division by zero issues in Arc parameterization.
|
|
||||||
end += 0.00009
|
|
||||||
svg_arc = Arc(current_pos, radius, rotation, arc, sweep, end)
|
|
||||||
arc_points = []
|
|
||||||
for point in [0.2, 0.4, 0.6, 0.8, 1]:
|
|
||||||
# There are infinite points in an arc, but for our context,
|
|
||||||
# define the arc using 5 points.
|
|
||||||
arc_point = svg_arc.point(point)
|
|
||||||
arc_points.append((arc_point.real, arc_point.imag))
|
|
||||||
pen.qCurveTo(*arc_points)
|
|
||||||
current_pos = end
|
|
||||||
|
|
||||||
# no final Z command, it's an open path
|
# no final Z command, it's an open path
|
||||||
if start_pos is not None:
|
if start_pos is not None:
|
||||||
|
@ -226,74 +226,14 @@ import pytest
|
|||||||
("closePath", ()),
|
("closePath", ()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
# absolute A command, arc 1
|
|
||||||
(
|
|
||||||
"M 100 100 A 150 150 0 1 0 150 -150 z",
|
|
||||||
[
|
|
||||||
('moveTo', ((100.0, 100.0),)),
|
|
||||||
('qCurveTo', ((217.17583, 139.78681),
|
|
||||||
(324.37829, 77.97418),
|
|
||||||
(348.64695, -43.36913),
|
|
||||||
(273.46493, -141.65865),
|
|
||||||
(150.0, -150.0))),
|
|
||||||
('lineTo', ((100.0, 100.0),)),
|
|
||||||
('closePath', ()),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
# relative A command
|
|
||||||
(
|
|
||||||
"M 100 100 a 150 150 0 1 0 150 -150",
|
|
||||||
[
|
|
||||||
('moveTo', ((100.0, 100.0),)),
|
|
||||||
('qCurveTo', ((161.832212, 221.352549),
|
|
||||||
(296.3525491, 242.6584774),
|
|
||||||
(392.6584774, 146.35254915),
|
|
||||||
(371.3525491, 11.83221215),
|
|
||||||
(250.0, -50.0))),
|
|
||||||
('endPath', ())
|
|
||||||
]
|
|
||||||
),
|
|
||||||
# absolute A command, arc 1, sweap 1, rotation 30
|
|
||||||
(
|
|
||||||
"M 100 100 A 150 150 30 1 1 150 -150 z",
|
|
||||||
[
|
|
||||||
('moveTo', ((100.0, 100.0),)),
|
|
||||||
('qCurveTo', ((-23.46493, 91.65865),
|
|
||||||
(-98.6469560, -6.63086811),
|
|
||||||
(-74.3782932, -127.97418174),
|
|
||||||
(32.8241612, -189.786813),
|
|
||||||
(150.0, -150.0))),
|
|
||||||
('lineTo', ((100.0, 100.0),)),
|
|
||||||
('closePath', ()),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
# absolute A command, arc 1, sweap 1, rotation 30, end == start
|
|
||||||
(
|
|
||||||
"M 100 100 A 150 150 30 1 1 100 100 z",
|
|
||||||
[
|
|
||||||
('moveTo', ((100.0, 100.0),)),
|
|
||||||
('qCurveTo', ((-42.6584408, -3.64747653),
|
|
||||||
(11.832264448, -171.3525544),
|
|
||||||
(188.16782558, -171.352554),
|
|
||||||
(242.65853078, -3.647476),
|
|
||||||
(100.0, 100.0))),
|
|
||||||
('lineTo', ((100.0, 100.0),)),
|
|
||||||
('closePath', ()),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_parse_path(pathdef, expected):
|
def test_parse_path(pathdef, expected):
|
||||||
pen = RecordingPen()
|
pen = RecordingPen()
|
||||||
parse_path(pathdef, pen)
|
parse_path(pathdef, pen)
|
||||||
|
|
||||||
assert len(pen.value) == len(expected)
|
assert pen.value == expected
|
||||||
for (instr, coords), (exp_instr, exp_coords) in zip(pen.value, expected):
|
|
||||||
assert instr == exp_instr
|
|
||||||
assert len(coords) == len(exp_coords)
|
|
||||||
for c, e in zip(coords, exp_coords):
|
|
||||||
assert c == pytest.approx(e)
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"pathdef1, pathdef2",
|
"pathdef1, pathdef2",
|
||||||
@ -348,3 +288,10 @@ def test_invalid_implicit_command():
|
|||||||
with pytest.raises(ValueError) as exc_info:
|
with pytest.raises(ValueError) as exc_info:
|
||||||
parse_path("M 100 100 L 200 200 Z 100 200", RecordingPen())
|
parse_path("M 100 100 L 200 200 Z 100 200", RecordingPen())
|
||||||
assert exc_info.match("Unallowed implicit command")
|
assert exc_info.match("Unallowed implicit command")
|
||||||
|
|
||||||
|
|
||||||
|
def test_arc_not_implemented():
|
||||||
|
pathdef = "M300,200 h-150 a150,150 0 1,0 150,-150 z"
|
||||||
|
with pytest.raises(NotImplementedError) as exc_info:
|
||||||
|
parse_path(pathdef, RecordingPen())
|
||||||
|
assert exc_info.match("arcs are not supported")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user