Skip to content

Commit

Permalink
Merge pull request Kozea#397 from eumiro/math
Browse files Browse the repository at this point in the history
Math optimizations
  • Loading branch information
liZe authored Aug 19, 2023
2 parents 8ecb080 + 486aebd commit 7468122
Show file tree
Hide file tree
Showing 7 changed files with 32 additions and 38 deletions.
24 changes: 10 additions & 14 deletions cairosvg/bounding_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"""

from math import acos, atan, cos, fmod, isinf, pi, radians, sin, sqrt, tan
from math import (
acos, atan, copysign, cos, fmod, hypot, isinf, pi, radians, sin, sqrt, tan,
tau)

from .features import match_features
from .helpers import PATH_LETTERS, normalize, point, size
Expand Down Expand Up @@ -220,9 +222,7 @@ def bounding_box_text(surface, node):

def angle(bx, by):
"""Get the angle between vector (1,0) and vector (bx,by)."""
return fmod(
2 * pi + (1 if by > 0 else -1) * acos(bx / sqrt(bx * bx + by * by)),
2 * pi)
return fmod(tau + copysign(acos(bx / hypot(bx, by)), by), tau)


def bounding_box_elliptical_arc(x1, y1, rx, ry, phi, large, sweep, x, y):
Expand Down Expand Up @@ -260,7 +260,7 @@ def bounding_box_elliptical_arc(x1, y1, rx, ry, phi, large, sweep, x, y):
cx = cxprime * cos(phi) - cyprime * sin(phi) + (x1 + x) / 2
cy = cxprime * sin(phi) + cyprime * cos(phi) + (y1 + y) / 2

if phi == 0 or phi == pi:
if phi in (0, pi):
minx = cx - rx
tminx = angle(-rx, 0)
maxx = cx + rx
Expand All @@ -269,7 +269,7 @@ def bounding_box_elliptical_arc(x1, y1, rx, ry, phi, large, sweep, x, y):
tminy = angle(0, -ry)
maxy = cy + ry
tmaxy = angle(0, ry)
elif phi == pi / 2 or phi == 3 * pi / 2:
elif phi in (pi / 2, 3 * pi / 2):
minx = cx - ry
tminx = angle(-ry, 0)
maxx = cx + ry
Expand Down Expand Up @@ -314,17 +314,13 @@ def bounding_box_elliptical_arc(x1, y1, rx, ry, phi, large, sweep, x, y):
angle1, angle2 = angle2, angle1
other_arc = True

if ((not other_arc and (angle1 > tminx or angle2 < tminx)) or
(other_arc and not (angle1 > tminx or angle2 < tminx))):
if other_arc == (angle1 <= tminx <= angle2):
minx = min(x, x1)
if ((not other_arc and (angle1 > tmaxx or angle2 < tmaxx)) or
(other_arc and not (angle1 > tmaxx or angle2 < tmaxx))):
if other_arc == (angle1 <= tmaxx <= angle2):
maxx = max(x, x1)
if ((not other_arc and (angle1 > tminy or angle2 < tminy)) or
(other_arc and not (angle1 > tminy or angle2 < tminy))):
if other_arc == (angle1 <= tminy <= angle2):
miny = min(y, y1)
if ((not other_arc and (angle1 > tmaxy or angle2 < tmaxy)) or
(other_arc and not (angle1 > tmaxy or angle2 < tmaxy))):
if other_arc == (angle1 <= tmaxy <= angle2):
maxy = max(y, y1)

return minx, miny, maxx - minx, maxy - miny
Expand Down
16 changes: 7 additions & 9 deletions cairosvg/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""

import re
from math import atan2, cos, radians, sin, tan
from math import atan2, cos, hypot, radians, sin, tan

from .surface import cairo
from .url import parse_url
Expand All @@ -13,8 +13,8 @@
'mm': 1 / 25.4,
'cm': 1 / 2.54,
'in': 1,
'pt': 1 / 72.,
'pc': 1 / 6.,
'pt': 1 / 72,
'pc': 1 / 6,
'px': None,
}

Expand All @@ -29,7 +29,7 @@ class PointError(Exception):

def distance(x1, y1, x2, y2):
"""Get the distance between two points."""
return ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
return hypot(x2 - x1, y2 - y1)


def paint(value):
Expand Down Expand Up @@ -345,8 +345,7 @@ def size(surface, string, reference='xy'):
If ``reference`` is a float, it is used as reference for percentages. If it
is ``'x'``, we use the viewport width as reference. If it is ``'y'``, we
use the viewport height as reference. If it is ``'xy'``, we use
``(viewport_width ** 2 + viewport_height ** 2) ** .5 / 2 ** .5`` as
reference.
``hypot(viewport_width, viewport_height) / 2 ** .5`` as reference.
"""
if not string:
Expand All @@ -370,9 +369,8 @@ def size(surface, string, reference='xy'):
reference = surface.context_height or 0
elif reference == 'xy':
reference = (
(surface.context_width ** 2 +
surface.context_height ** 2) ** .5 /
2 ** .5)
hypot(surface.context_width, surface.context_height) / 2 ** .5
)
return float(string[:-1]) * reference / 100
elif string.endswith('em'):
return surface.font_size * float(string[:-2])
Expand Down
8 changes: 5 additions & 3 deletions cairosvg/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@ def image(surface, node):
width = size(surface, node.get('width'), 'x')
height = size(surface, node.get('height'), 'y')

if image_bytes[:4] == b'\x89PNG' and not surface.map_image:
if image_bytes.startswith(b'\x89PNG') and not surface.map_image:
png_file = BytesIO(image_bytes)
elif (image_bytes[:5] in (b'<svg ', b'<?xml', b'<!DOC') or
image_bytes[:2] == b'\x1f\x8b') or b'<svg' in image_bytes:
elif (
image_bytes.startswith((b'<svg ', b'<?xml', b'<!DOC', b'\x1f\x8b'))
or b'<svg' in image_bytes
):
if 'x' in node:
del node['x']
if 'y' in node:
Expand Down
2 changes: 1 addition & 1 deletion cairosvg/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ def __init__(self, **kwargs):
if not bytestring:
bytestring = self.fetch_url(
parse_url(self.url), 'image/svg+xml')
if len(bytestring) >= 2 and bytestring[:2] == b'\x1f\x8b':
if bytestring.startswith(b'\x1f\x8b'):
bytestring = gzip.decompress(bytestring)
tree = ElementTree.fromstring(
bytestring, forbid_entities=not unsafe,
Expand Down
8 changes: 4 additions & 4 deletions cairosvg/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

from math import pi, radians
from math import copysign, hypot, pi, radians

from .bounding_box import calculate_bounding_box
from .helpers import (
Expand Down Expand Up @@ -206,7 +206,7 @@ def path(surface, node):
angle = point_angle(0, 0, xe, ye)

# Put the second point onto the x axis
xe = (xe ** 2 + ye ** 2) ** .5
xe = hypot(xe, ye)
ye = 0

# Update the x radius if it is too small
Expand Down Expand Up @@ -423,7 +423,7 @@ def path(surface, node):
# Relative vertical line
y, string = (string + ' ').split(' ', 1)
old_x, old_y = current_point
angle = pi / 2 if size(surface, y, 'y') > 0 else -pi / 2
angle = copysign(pi / 2, size(surface, y, 'y'))
node.vertices.append((-angle, angle))
y = size(surface, y, 'y')
surface.context.rel_line_to(0, y)
Expand All @@ -433,7 +433,7 @@ def path(surface, node):
# Vertical line
y, string = (string + ' ').split(' ', 1)
old_x, old_y = current_point
angle = pi / 2 if size(surface, y, 'y') > old_y else -pi / 2
angle = copysign(pi / 2, size(surface, y, 'y') - old_y)
node.vertices.append((-angle, angle))
y = size(surface, y, 'y')
surface.context.line_to(old_x, y)
Expand Down
6 changes: 2 additions & 4 deletions cairosvg/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,8 @@ def rect(surface, node):
if rx == 0 or ry == 0:
surface.context.rectangle(x, y, width, height)
else:
if rx > width / 2:
rx = width / 2
if ry > height / 2:
ry = height / 2
rx = min(rx, width / 2)
ry = min(ry, height / 2)

# Inspired by Cairo Cookbook
# http://cairographics.org/cookbook/roundedrectangles/
Expand Down
6 changes: 3 additions & 3 deletions cairosvg/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def text(surface, node, draw_as_text=False):

text_anchor = node.get('text-anchor')
if text_anchor == 'middle':
x_align = - (width / 2. + x_bearing)
x_align = - (width / 2 + x_bearing)
if letter_spacing and node.text:
x_align -= (len(node.text) - 1) * letter_spacing / 2
elif text_anchor == 'end':
Expand All @@ -121,7 +121,7 @@ def text(surface, node, draw_as_text=False):
alignment_baseline = (node.get('dominant-baseline') or
node.get('alignment-baseline'))
if display_anchor == 'middle':
y_align = -height / 2.0 - y_bearing
y_align = -height / 2 - y_bearing
elif display_anchor == 'top':
y_align = -y_bearing
elif display_anchor == 'bottom':
Expand All @@ -130,7 +130,7 @@ def text(surface, node, draw_as_text=False):
alignment_baseline == 'middle'):
# TODO: This is wrong, Cairo gives no reasonable access to x-height
# information, so we use font top-to-bottom
y_align = (ascent + descent) / 2.0 - descent
y_align = (ascent + descent) / 2 - descent
elif (alignment_baseline == 'text-before-edge' or
alignment_baseline == 'before_edge' or
alignment_baseline == 'top' or
Expand Down

0 comments on commit 7468122

Please sign in to comment.