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
|
# -*- coding: utf-8 -*-
#
# Copyright (c) Aaron Spike <aaron@ekips.org>
# Aurélio A. Heckert <aurium(a)gmail.com>
# Bulia Byak <buliabyak@users.sf.net>
# Nicolas Dufour, nicoduf@yahoo.fr
# Peter J. R. Moulder <pjrm@users.sourceforge.net>
# Martin Owens <doctormo@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
"""
Convert to and from various units and find the closest matching unit.
"""
import re
# a dictionary of unit to user unit conversion factors
CONVERSIONS = {
'in': 96.0,
'pt': 1.3333333333333333,
'px': 1.0,
'mm': 3.779527559055118,
'cm': 37.79527559055118,
'm': 3779.527559055118,
'km': 3779527.559055118,
'Q': 0.94488188976378,
'pc': 16.0,
'yd': 3456.0,
'ft': 1152.0,
'': 1.0, # Default px
}
# allowed unit types, including percentages, relative units, and others
# that are not suitable for direct conversion to a length.
# Note that this is _not_ an exhaustive list of allowed unit types.
UNITS = ['in', 'pt', 'px', 'mm', 'cm', 'm', 'km', 'Q', 'pc', 'yd', 'ft', '',\
'%', 'em', 'ex', 'ch', 'rem', 'vw', 'vh', 'vmin', 'vmax',\
'deg', 'grad', 'rad', 'turn', 's', 'ms', 'Hz', 'kHz',\
'dpi', 'dpcm', 'dppx']
UNIT_MATCH = re.compile(r'({})'.format('|'.join(UNITS)))
NUMBER_MATCH = re.compile(r'(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)')
BOTH_MATCH = re.compile(r'^\s*{}\s*{}\s*$'.format(NUMBER_MATCH.pattern, UNIT_MATCH.pattern))
def parse_unit(value, default_unit='px', default_value=None):
"""
Takes a value such as 55.32px and returns (55.32, 'px')
Returns default (None) if no match can be found
"""
ret = BOTH_MATCH.match(str(value))
if ret:
return float(ret.groups()[0]), ret.groups()[-1] or default_unit
return (default_value, default_unit) if default_value is not None else None
def are_near_relative(point_a, point_b, eps=0.01):
"""Return true if the points are near to eps"""
return (point_a - point_b <= point_a * eps) and (point_a - point_b >= -point_a * eps)
def discover_unit(value, viewbox, default='px'):
"""Attempt to detect the unit being used based on the viewbox"""
# Default 100px when width can't be parsed
(value, unit) = parse_unit(value, default_value=100.0)
if unit not in CONVERSIONS:
return default
this_factor = CONVERSIONS[unit] * value / viewbox
# try to find the svgunitfactor in the list of units known. If we don't find something, ...
for unit, unit_factor in CONVERSIONS.items():
if unit != '':
# allow 1% error in factor
if are_near_relative(this_factor, unit_factor, eps=0.01):
return unit
return default
def convert_unit(value, to_unit):
"""Returns userunits given a string representation of units in another system"""
value, from_unit = parse_unit(value, default_value=0.0)
if from_unit in CONVERSIONS and to_unit in CONVERSIONS:
return value * CONVERSIONS[from_unit] / CONVERSIONS.get(to_unit, CONVERSIONS['px'])
return 0.0
def render_unit(value, unit):
"""Checks and then renders a number with its unit"""
try:
if isinstance(value, str):
(value, unit) = parse_unit(value, default_unit=unit)
return "{:.6g}{:s}".format(value, unit)
except TypeError:
return ''
|