Revert "Merge pull request #1136 from santhoshtr/svg-arc-support"

This reverts commit 5c392bc86542fbbc0c63335cfedae0d1406b0794, reversing
changes made to 4b69d77ae57776480901e2af82c7d9c1c29de8d6.
This commit is contained in:
Cosimo Lupo 2018-01-05 13:07:57 +00:00
parent 5c392bc865
commit 40e50b60c3
2 changed files with 13 additions and 178 deletions

View File

@ -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:

View File

@ -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")