Commit 6c38c129 authored by Martin Owens's avatar Martin Owens 🤖

Support for parsing units other than absolute length

* Add new list of UNITS accepted within SVG, a superset of those supported for length conversion.
* Add "Q" unit to dictionary of supported absolute length conversions
* convert_unit returns None when asked to convert to or from units that aren't supported for absolute length conversion.
* Add test for parsing non-absolute length units (%)
* Add test for converting to default user units from in
* Add test for converting from default user units to in
* Add tests for converting to and from valid but non-length units
* Add tests for converting to and from unsupported units
parent c61dcb41
......@@ -36,12 +36,22 @@ CONVERSIONS = {
'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
UNIT_MATCH = re.compile(r'({})'.format('|'.join(CONVERSIONS)))
# 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']
  • In which context is Inkscape ever going to need Hz and kHz? 😕 , @doctormo ?

  • These are legitimate units within SVG/CSS, so it is helpful for Inkscape to be aware of them and handle them gracefully, even if Inkscape does not directly generate or make use of these units.

    The context in which time units are useful is animation. (Animation is part of the SVG standard.)

    Even if Inkscape itself does not create animations at present, Inkscape may be used to create elements used in SVG animations, and (IMO) we should provide a welcoming environment for extensions and other development working with SVG animation.


    1. "SVG follows the description and definition of common values and units from the CSS Values and Units Module css-values for attributes, presentation attributes and CSS properties."

    2. CSS supports frequency units.

  • I don't want to see an animation that uses kHz as a unit... These are for sound, as far as I can tell.

  • While I doubt that this is going to be useful in any way, I can live with 'it's not going to hurt'.

  • I added them because they were allowed units, not because I expected to use them.

  • @Moini I see what you did there 😁

  • @oskay I see, thanks - that makes more sense than the animation explanation.

  • @doctormo Sorry?

  • 'it's not going to hurt' -> Hz thought it was a particular choice of words for a pun.

  • Oh, that :) - no, didn't notice. I was reading the unit in German, where it's more like hare 🐰 -tz .

Please register or sign in to reply
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))
......@@ -66,6 +76,8 @@ 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, ...
......@@ -79,8 +91,10 @@ def discover_unit(value, viewbox, default='px'):
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)
return value * CONVERSIONS[from_unit] / CONVERSIONS.get(to_unit, CONVERSIONS['px'])
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):
......@@ -13,6 +13,7 @@ class UnitsTest(TestCase):
self.assertEqual(parse_unit('50'), (50.0, 'px'))
self.assertEqual(parse_unit('50quaks'), None)
self.assertEqual(parse_unit('50quaks', default_value=10), (10.0, 'px'))
self.assertEqual(parse_unit('50%'), (50.0, '%'))
def test_near(self):
"""Test the closeness of numbers"""
......@@ -31,6 +32,12 @@ class UnitsTest(TestCase):
self.assertEqual(convert_unit("10mm", 'px'), 37.79527559055118)
self.assertEqual(convert_unit("1in", 'cm'), 2.54)
self.assertEqual(convert_unit("37.79527559055118px", 'mm'), 10.0)
self.assertEqual(convert_unit("1in", ''), 96.0)
self.assertEqual(convert_unit("96", 'in'), 1.0)
self.assertEqual(convert_unit("10%", 'mm'), 0.0)
self.assertEqual(convert_unit("1in", 'grad'), 0.0)
self.assertEqual(convert_unit("10quaks", 'mm'), 0.0)
self.assertEqual(convert_unit("10mm", 'quaks'), 0.0)
def test_render_unit(self):
"""Convert unit and value pair into rendered unit string"""
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment