summaryrefslogtreecommitdiff
path: root/lib/svg/units.py
blob: d97ea418fb4e36af16b9fbf38f55674b60c16b03 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import simpletransform

from ..i18n import _
from ..utils import cache


# modern versions of Inkscape use 96 pixels per inch as per the CSS standard
PIXELS_PER_MM = 96 / 25.4

# cribbed from inkscape-silhouette


def parse_length_with_units(str):
    '''
    Parse an SVG value which may or may not have units attached
    This version is greatly simplified in that it only allows: no units,
    units of px, mm, and %.  Everything else, it returns None for.
    There is a more general routine to consider in scour.py if more
    generality is ever needed.
    '''

    u = 'px'
    s = str.strip()
    if s[-2:] == 'px':
        s = s[:-2]
    elif s[-2:] == 'mm':
        u = 'mm'
        s = s[:-2]
    elif s[-2:] == 'pt':
        u = 'pt'
        s = s[:-2]
    elif s[-2:] == 'pc':
        u = 'pc'
        s = s[:-2]
    elif s[-2:] == 'cm':
        u = 'cm'
        s = s[:-2]
    elif s[-2:] == 'in':
        u = 'in'
        s = s[:-2]
    elif s[-1:] == '%':
        u = '%'
        s = s[:-1]
    try:
        v = float(s)
    except BaseException:
        raise ValueError(_("parseLengthWithUnits: unknown unit %s") % s)

    return v, u


def convert_length(length):
    value, units = parse_length_with_units(length)

    if not units or units == "px":
        return value

    if units == 'pt':
        value /= 72
        units = 'in'

    if units == 'pc':
        value /= 6
        units = 'in'

    if units == 'cm':
        value *= 10
        units = 'mm'

    if units == 'mm':
        value = value / 25.4
        units = 'in'

    if units == 'in':
        # modern versions of Inkscape use CSS's 96 pixels per inch.  When you
        # open an old document, inkscape will add a viewbox for you.
        return value * 96

    raise ValueError(_("Unknown unit: %s") % units)


@cache
def get_viewbox(svg):
    viewbox = svg.get('viewBox')
    if viewbox is None:
        viewbox = "0 0 0 0"
    return viewbox.strip().replace(',', ' ').split()


@cache
def get_doc_size(svg):
    width = svg.get('width')
    height = svg.get('height')

    if width == "100%" and height == "100%":
        # Some SVG editors set width and height to "100%".  I can't find any
        # solid documentation on how one is supposed to interpret that, so
        # just ignore it and use the viewBox.  That seems to have the intended
        # result anyway.

        width = None
        height = None

    if width is None or height is None:
        # fall back to the dimensions from the viewBox
        viewbox = get_viewbox(svg)
        width = viewbox[2]
        height = viewbox[3]

    doc_width = convert_length(width)
    doc_height = convert_length(height)

    return doc_width, doc_height


@cache
def get_viewbox_transform(node):
    # somewhat cribbed from inkscape-silhouette
    doc_width, doc_height = get_doc_size(node)

    viewbox = get_viewbox(node)

    dx = -float(viewbox[0])
    dy = -float(viewbox[1])
    transform = simpletransform.parseTransform("translate(%f, %f)" % (dx, dy))

    try:
        sx = doc_width / float(viewbox[2])
        sy = doc_height / float(viewbox[3])

        # preserve aspect ratio
        aspect_ratio = node.get('preserveAspectRatio', 'xMidYMid meet')
        if aspect_ratio != 'none':
            unit = parse_length_with_units(node.get('width'))[1]
            if float(viewbox[2]) > parse_length_with_units(node.get('width'))[0]:
                sx = convert_length(viewbox[2] + unit) / float(viewbox[2]) if 'slice' in aspect_ratio else 1.0
            if float(viewbox[3]) > parse_length_with_units(node.get('height'))[0]:
                sy = convert_length(viewbox[3] + unit) / float(viewbox[3]) if 'slice' in aspect_ratio else 1.0
            sx = sy = max(sx, sy) if 'slice' in aspect_ratio else min(sx, sy)

        scale_transform = simpletransform.parseTransform("scale(%f, %f)" % (sx, sy))
        transform = simpletransform.composeTransform(transform, scale_transform)
    except ZeroDivisionError:
        pass

    return transform