Skip to content

Commit 9e340e1

Browse files
committed
Merge origin/master
2 parents 84c3df2 + 6ed182d commit 9e340e1

11 files changed

+84
-31
lines changed

.coveragerc

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
[run]
22
omit =
33
test/cairosvg_reference/*
4+
*/virtualenv/*
5+
.*

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ __pycache__
66
.coverage
77
.cache
88
/.idea
9+
.eggs
910
build

MANIFEST.in

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Deployment tests are already bundled in the cairosvg module
2-
include COPYING NEWS.rst README.rst
3-
exclude test/*.py cairosvg.py
2+
prune test
3+
exclude cairosvg.py

NEWS.rst

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Version 2.0, not released yet
1313
* Fix URL/id handling
1414
* Use bounding boxes for gradients
1515
* Split deployment and development tests
16+
* Add a scale option
17+
* Add a parent size option
1618
* Test with Travis
1719

1820

cairosvg/__init__.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
2020
"""
2121

22-
__version__ = '2.0.0rc2' # noqa (version is used by relative imports)
22+
__version__ = '2.0.0rc5' # noqa (version is used by relative imports)
2323

2424

2525
import os
@@ -70,6 +70,8 @@ def main():
7070
parser.add_argument(
7171
'-H', '--height', default=None, type=float,
7272
help='height of the parent container in pixels')
73+
parser.add_argument(
74+
'-s', '--scale', default=1, type=float, help='output scaling factor')
7375
parser.add_argument(
7476
'-u', '--unsafe', action='store_true',
7577
help='resolve XML entities and allow very large files '
@@ -79,7 +81,7 @@ def main():
7981
options = parser.parse_args()
8082
kwargs = {
8183
'parent_width': options.width, 'parent_height': options.height,
82-
'dpi': options.dpi, 'unsafe': options.unsafe}
84+
'dpi': options.dpi, 'scale': options.scale, 'unsafe': options.unsafe}
8385
kwargs['write_to'] = (
8486
sys.stdout.buffer if options.output == '-' else options.output)
8587
if options.input == '-':

cairosvg/path.py

+55-21
Original file line numberDiff line numberDiff line change
@@ -144,20 +144,35 @@ def path(surface, node):
144144
last_letter = None
145145
string = normalize(string)
146146

147+
# Keep the current point because Cairo's get_current_point is not accurate
148+
# enough. See https://github.com/Kozea/CairoSVG/issues/111.
149+
if surface.context.has_current_point():
150+
current_point = surface.context.get_current_point()
151+
else:
152+
surface.context.move_to(0, 0)
153+
current_point = 0, 0
154+
147155
while string:
148156
string = string.strip()
149157
if string.split(' ', 1)[0] in PATH_LETTERS:
150158
letter, string = (string + ' ').split(' ', 1)
151159
if last_letter in (None, 'z', 'Z') and letter not in 'mM':
152-
node.vertices.append(surface.context.get_current_point())
160+
node.vertices.append(current_point)
161+
first_path_point = current_point
153162
elif letter == 'M':
154163
letter = 'L'
155164
elif letter == 'm':
156165
letter = 'l'
157166

167+
if last_letter in (None, 'm', 'M', 'z', 'Z'):
168+
first_path_point = None
169+
if letter not in (None, 'm', 'M', 'z', 'Z') and first_path_point is None:
170+
first_path_point = current_point
171+
158172
if letter in 'aA':
159173
# Elliptic curve
160-
x1, y1 = surface.context.get_current_point()
174+
surface.context.set_tolerance(0.00001)
175+
x1, y1 = current_point
161176
rx, ry, string = point(surface, string)
162177
rotation, string = string.split(' ', 1)
163178
rotation = radians(float(rotation))
@@ -233,16 +248,18 @@ def path(surface, node):
233248
surface.context.scale(1, radii_ratio)
234249
arc(xc, yc, rx, angle1, angle2)
235250
surface.context.restore()
251+
current_point = current_point[0] + x3, current_point[1] + y3
236252

237253
elif letter == 'c':
238254
# Relative curve
239-
x, y = surface.context.get_current_point()
255+
x, y = current_point
240256
x1, y1, string = point(surface, string)
241257
x2, y2, string = point(surface, string)
242258
x3, y3, string = point(surface, string)
243259
node.vertices.append((
244260
point_angle(x2, y2, x1, y1), point_angle(x2, y2, x3, y3)))
245261
surface.context.rel_curve_to(x1, y1, x2, y2, x3, y3)
262+
current_point = current_point[0] + x3, current_point[1] + y3
246263

247264
# Save absolute values for x and y, useful if next letter is s or S
248265
x1 += x
@@ -260,50 +277,56 @@ def path(surface, node):
260277
node.vertices.append((
261278
point_angle(x2, y2, x1, y1), point_angle(x2, y2, x3, y3)))
262279
surface.context.curve_to(x1, y1, x2, y2, x3, y3)
280+
current_point = x3, y3
263281

264282
elif letter == 'h':
265283
# Relative horizontal line
266284
x, string = (string + ' ').split(' ', 1)
267-
old_x, old_y = surface.context.get_current_point()
285+
old_x, old_y = current_point
268286
angle = 0 if size(surface, x, 'x') > 0 else pi
269287
node.vertices.append((pi - angle, angle))
270-
surface.context.rel_line_to(size(surface, x, 'x'), 0)
288+
x = size(surface, x, 'x')
289+
surface.context.rel_line_to(x, 0)
290+
current_point = current_point[0] + x, current_point[1]
271291

272292
elif letter == 'H':
273293
# Horizontal line
274294
x, string = (string + ' ').split(' ', 1)
275-
old_x, old_y = surface.context.get_current_point()
295+
old_x, old_y = current_point
276296
angle = 0 if size(surface, x, 'x') > old_x else pi
277297
node.vertices.append((pi - angle, angle))
278-
surface.context.line_to(size(surface, x, 'x'), old_y)
298+
x = size(surface, x, 'x')
299+
surface.context.line_to(x, old_y)
300+
current_point = x, current_point[1]
279301

280302
elif letter == 'l':
281303
# Relative straight line
282304
x, y, string = point(surface, string)
283305
angle = point_angle(0, 0, x, y)
284306
node.vertices.append((pi - angle, angle))
285307
surface.context.rel_line_to(x, y)
308+
current_point = current_point[0] + x, current_point[1] + y
286309

287310
elif letter == 'L':
288311
# Straight line
289312
x, y, string = point(surface, string)
290-
old_x, old_y = surface.context.get_current_point()
313+
old_x, old_y = current_point
291314
angle = point_angle(old_x, old_y, x, y)
292315
node.vertices.append((pi - angle, angle))
293316
surface.context.line_to(x, y)
317+
current_point = x, y
294318

295319
elif letter == 'm':
296320
# Current point relative move
297321
x, y, string = point(surface, string)
298-
if surface.context.has_current_point():
299-
surface.context.rel_move_to(x, y)
300-
else:
301-
surface.context.move_to(x, y)
322+
surface.context.rel_move_to(x, y)
323+
current_point = current_point[0] + x, current_point[1] + y
302324

303325
elif letter == 'M':
304326
# Current point move
305327
x, y, string = point(surface, string)
306328
surface.context.move_to(x, y)
329+
current_point = x, y
307330

308331
elif letter == 'q':
309332
# Relative quadratic curve
@@ -314,27 +337,30 @@ def path(surface, node):
314337
x1, y1, x2, y2, x3, y3)
315338
surface.context.rel_curve_to(xq1, yq1, xq2, yq2, xq3, yq3)
316339
node.vertices.append((0, 0))
340+
current_point = current_point[0] + x3, current_point[1] + y3
317341

318342
elif letter == 'Q':
319343
# Quadratic curve
320-
x1, y1 = surface.context.get_current_point()
344+
x1, y1 = current_point
321345
x2, y2, string = point(surface, string)
322346
x3, y3, string = point(surface, string)
323347
xq1, yq1, xq2, yq2, xq3, yq3 = quadratic_points(
324348
x1, y1, x2, y2, x3, y3)
325349
surface.context.curve_to(xq1, yq1, xq2, yq2, xq3, yq3)
326350
node.vertices.append((0, 0))
351+
current_point = x3, y3
327352

328353
elif letter == 's':
329354
# Relative smooth curve
330-
x, y = surface.context.get_current_point()
355+
x, y = current_point
331356
x1 = x3 - x2 if last_letter in 'csCS' else 0
332357
y1 = y3 - y2 if last_letter in 'csCS' else 0
333358
x2, y2, string = point(surface, string)
334359
x3, y3, string = point(surface, string)
335360
node.vertices.append((
336361
point_angle(x2, y2, x1, y1), point_angle(x2, y2, x3, y3)))
337362
surface.context.rel_curve_to(x1, y1, x2, y2, x3, y3)
363+
current_point = current_point[0] + x3, current_point[1] + y3
338364

339365
# Save absolute values for x and y, useful if next letter is s or S
340366
x1 += x
@@ -346,14 +372,15 @@ def path(surface, node):
346372

347373
elif letter == 'S':
348374
# Smooth curve
349-
x, y = surface.context.get_current_point()
375+
x, y = current_point
350376
x1 = x3 + (x3 - x2) if last_letter in 'csCS' else x
351377
y1 = y3 + (y3 - y2) if last_letter in 'csCS' else y
352378
x2, y2, string = point(surface, string)
353379
x3, y3, string = point(surface, string)
354380
node.vertices.append((
355381
point_angle(x2, y2, x1, y1), point_angle(x2, y2, x3, y3)))
356382
surface.context.curve_to(x1, y1, x2, y2, x3, y3)
383+
current_point = x3, y3
357384

358385
elif letter == 't':
359386
# Relative quadratic curve end
@@ -372,10 +399,11 @@ def path(surface, node):
372399
x1, y1, x2, y2, x3, y3)
373400
node.vertices.append((0, 0))
374401
surface.context.rel_curve_to(xq1, yq1, xq2, yq2, xq3, yq3)
402+
current_point = current_point[0] + x3, current_point[1] + y3
375403

376404
elif letter == 'T':
377405
# Quadratic curve end
378-
abs_x, abs_y = surface.context.get_current_point()
406+
abs_x, abs_y = current_point
379407
if last_letter not in 'QqTt':
380408
x2, y2, x3, y3 = abs_x, abs_y, abs_x, abs_y
381409
elif last_letter in 'qt':
@@ -389,30 +417,36 @@ def path(surface, node):
389417
x1, y1, x2, y2, x3, y3)
390418
node.vertices.append((0, 0))
391419
surface.context.curve_to(xq1, yq1, xq2, yq2, xq3, yq3)
420+
current_point = x3, y3
392421

393422
elif letter == 'v':
394423
# Relative vertical line
395424
y, string = (string + ' ').split(' ', 1)
396-
old_x, old_y = surface.context.get_current_point()
425+
old_x, old_y = current_point
397426
angle = pi / 2 if size(surface, y, 'y') > 0 else -pi / 2
398427
node.vertices.append((-angle, angle))
399-
surface.context.rel_line_to(0, size(surface, y, 'y'))
428+
y = size(surface, y, 'y')
429+
surface.context.rel_line_to(0, y)
430+
current_point = current_point[0], current_point[1] + y
400431

401432
elif letter == 'V':
402433
# Vertical line
403434
y, string = (string + ' ').split(' ', 1)
404-
old_x, old_y = surface.context.get_current_point()
435+
old_x, old_y = current_point
405436
angle = pi / 2 if size(surface, y, 'y') > 0 else -pi / 2
406437
node.vertices.append((-angle, angle))
407-
surface.context.line_to(old_x, size(surface, y, 'y'))
438+
y = size(surface, y, 'y')
439+
surface.context.line_to(old_x, y)
440+
current_point = current_point[0], y
408441

409442
elif letter in 'zZ':
410443
# End of path
411444
node.vertices.append(None)
412445
surface.context.close_path()
446+
current_point = first_path_point or (0, 0)
413447

414448
if letter not in 'zZ':
415-
node.vertices.append(surface.context.get_current_point())
449+
node.vertices.append(current_point)
416450

417451
string = string.strip()
418452
last_letter = letter

cairosvg/surface.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -129,16 +129,17 @@ def convert(cls, bytestring=None, **kwargs):
129129
dpi = kwargs.pop('dpi', 96)
130130
parent_width = kwargs.pop('parent_width', None)
131131
parent_height = kwargs.pop('parent_height', None)
132+
scale = kwargs.pop('scale', 1)
132133
write_to = kwargs.pop('write_to', None)
133134
kwargs['bytestring'] = bytestring
134135
tree = Tree(**kwargs)
135136
output = write_to or io.BytesIO()
136-
cls(tree, output, dpi, None, parent_width, parent_height).finish()
137+
cls(tree, output, dpi, None, parent_width, parent_height, scale).finish()
137138
if write_to is None:
138139
return output.getvalue()
139140

140141
def __init__(self, tree, output, dpi, parent_surface=None,
141-
parent_width=None, parent_height=None):
142+
parent_width=None, parent_height=None, scale=1):
142143
"""Create the surface from a filename or a file-like object.
143144
144145
The rendered content is written to ``output`` which can be a filename,
@@ -175,6 +176,8 @@ def __init__(self, tree, output, dpi, parent_surface=None,
175176
self.font_size = size(self, '12pt')
176177
self.stroke_and_fill = True
177178
width, height, viewbox = node_format(self, tree)
179+
width *= scale
180+
height *= scale
178181
# Actual surface dimensions: may be rounded on raster surfaces types
179182
self.cairo, self.width, self.height = self._create_surface(
180183
width * self.device_units_per_user_units,

pytest.ini

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
[pytest]
2-
addopts = --flake8 --isort --cov --ignore=test/cairosvg_reference
2+
# Broken with Travis, see https://gitlab.com/pycqa/flake8/issues/164
3+
# addopts = --flake8 --isort --cov --ignore=test/cairosvg_reference
4+
addopts = --isort --cov --ignore=test/cairosvg_reference
35
norecursedirs = dist .cache .git build *.egg-info .eggs venv cairosvg_reference

setup.cfg

+3
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1+
[aliases]
2+
test = pytest
3+
14
[bdist_wheel]
25
python-tag = py3

setup.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"""
1616

1717
import re
18+
import sys
1819
from os import path
1920

2021
from setuptools import setup
@@ -23,6 +24,8 @@
2324
with open(init_path, 'r', encoding='utf-8') as fd:
2425
version = re.search("__version__ = '([^']+)'", fd.read().strip()).group(1)
2526

27+
needs_pytest = {'pytest', 'test', 'ptr'}.intersection(sys.argv)
28+
pytest_runner = ['pytest-runner'] if needs_pytest else []
2629

2730
# When the version is updated, ``cairosvg.__version__`` must be modified.
2831
# A new section in the ``NEWS`` file must be added too.
@@ -38,9 +41,10 @@
3841
platforms='Any',
3942
packages=['cairosvg'],
4043
provides=['cairosvg'],
41-
setup_requires=['pytest-runner'],
44+
setup_requires=pytest_runner,
4245
install_requires=['cairocffi', 'lxml', 'cssselect', 'pillow', 'tinycss'],
43-
tests_require=['pytest-cov', 'pytest-flake8', 'pytest-isort', 'pytest'],
46+
tests_require=[
47+
'pytest-cov', 'pytest-flake8', 'pytest-isort', 'pytest-runner'],
4448
extras_require={'test': (
4549
'pytest-runner', 'pytest-cov', 'pytest-flake8', 'pytest-isort')},
4650
keywords=['svg', 'convert', 'cairo', 'pdf', 'png', 'postscript'],

test/cairosvg_reference

Submodule cairosvg_reference updated from 9c24adf to 99373c5

0 commit comments

Comments
 (0)