summaryrefslogtreecommitdiff
path: root/lib/svg
diff options
context:
space:
mode:
authorLex Neva <github.com@lexneva.name>2018-05-01 21:21:07 -0400
committerLex Neva <github.com@lexneva.name>2018-05-01 21:21:07 -0400
commit05daffb7e01db55879eb24f3c00532324a5d41af (patch)
tree43ff5d954e035e0e8b5a507b9c1bf9d6b4d3338d /lib/svg
parent1b31806423c8fec4040fed6d1009db016860b763 (diff)
refactor everything out of lib/__init__.py
Diffstat (limited to 'lib/svg')
-rw-r--r--lib/svg/__init__.py2
-rw-r--r--lib/svg/svg.py81
-rw-r--r--lib/svg/tags.py12
-rw-r--r--lib/svg/units.py105
4 files changed, 200 insertions, 0 deletions
diff --git a/lib/svg/__init__.py b/lib/svg/__init__.py
new file mode 100644
index 00000000..1895bba4
--- /dev/null
+++ b/lib/svg/__init__.py
@@ -0,0 +1,2 @@
+from .svg import color_block_to_point_lists, render_stitch_plan
+from .units import *
diff --git a/lib/svg/svg.py b/lib/svg/svg.py
new file mode 100644
index 00000000..3bc546e7
--- /dev/null
+++ b/lib/svg/svg.py
@@ -0,0 +1,81 @@
+import simpletransform, simplestyle, inkex
+
+from .units import get_viewbox_transform
+from .tags import SVG_GROUP_TAG, INKSCAPE_LABEL, INKSCAPE_GROUPMODE, SVG_PATH_TAG
+from ..i18n import _
+from ..utils import cache
+
+
+def color_block_to_point_lists(color_block):
+ point_lists = [[]]
+
+ for stitch in color_block:
+ if stitch.trim:
+ if point_lists[-1]:
+ point_lists.append([])
+ continue
+
+ if not stitch.jump and not stitch.stop:
+ point_lists[-1].append(stitch.as_tuple())
+
+ return point_lists
+
+
+@cache
+def get_correction_transform(svg):
+ transform = get_viewbox_transform(svg)
+
+ # we need to correct for the viewbox
+ transform = simpletransform.invertTransform(transform)
+ transform = simpletransform.formatTransform(transform)
+
+ return transform
+
+
+def color_block_to_paths(color_block, svg):
+ paths = []
+ # We could emit just a single path with one subpath per point list, but
+ # emitting multiple paths makes it easier for the user to manipulate them.
+ for point_list in color_block_to_point_lists(color_block):
+ color = color_block.color.visible_on_white.to_hex_str()
+ paths.append(inkex.etree.Element(
+ SVG_PATH_TAG,
+ {'style': simplestyle.formatStyle(
+ {'stroke': color,
+ 'stroke-width': "0.4",
+ 'fill': 'none'}),
+ 'd': "M" + " ".join(" ".join(str(coord) for coord in point) for point in point_list),
+ 'transform': get_correction_transform(svg),
+ 'embroider_manual_stitch': 'true',
+ 'embroider_trim_after': 'true',
+ }))
+
+ # no need to trim at the end of a thread color
+ if paths:
+ paths[-1].attrib.pop('embroider_trim_after')
+
+ return paths
+
+
+def render_stitch_plan(svg, stitch_plan):
+ layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
+ if layer is None:
+ layer = inkex.etree.Element(SVG_GROUP_TAG,
+ {'id': '__inkstitch_stitch_plan__',
+ INKSCAPE_LABEL: _('Stitch Plan'),
+ INKSCAPE_GROUPMODE: 'layer'})
+ else:
+ # delete old stitch plan
+ del layer[:]
+
+ # make sure the layer is visible
+ layer.set('style', 'display:inline')
+
+ for i, color_block in enumerate(stitch_plan):
+ group = inkex.etree.SubElement(layer,
+ SVG_GROUP_TAG,
+ {'id': '__color_block_%d__' % i,
+ INKSCAPE_LABEL: "color block %d" % (i + 1)})
+ group.extend(color_block_to_paths(color_block, svg))
+
+ svg.append(layer)
diff --git a/lib/svg/tags.py b/lib/svg/tags.py
new file mode 100644
index 00000000..fee59957
--- /dev/null
+++ b/lib/svg/tags.py
@@ -0,0 +1,12 @@
+import inkex
+
+
+SVG_PATH_TAG = inkex.addNS('path', 'svg')
+SVG_POLYLINE_TAG = inkex.addNS('polyline', 'svg')
+SVG_DEFS_TAG = inkex.addNS('defs', 'svg')
+SVG_GROUP_TAG = inkex.addNS('g', 'svg')
+
+INKSCAPE_LABEL = inkex.addNS('label', 'inkscape')
+INKSCAPE_GROUPMODE = inkex.addNS('groupmode', 'inkscape')
+
+EMBROIDERABLE_TAGS = (SVG_PATH_TAG, SVG_POLYLINE_TAG)
diff --git a/lib/svg/units.py b/lib/svg/units.py
new file mode 100644
index 00000000..015da60e
--- /dev/null
+++ b/lib/svg/units.py
@@ -0,0 +1,105 @@
+import simpletransform
+
+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:
+ 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_doc_size(svg):
+ doc_width = convert_length(svg.get('width'))
+ doc_height = convert_length(svg.get('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 = node.get('viewBox').strip().replace(',', ' ').split()
+
+ 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])
+ scale_transform = simpletransform.parseTransform("scale(%f, %f)" % (sx, sy))
+ transform = simpletransform.composeTransform(transform, scale_transform)
+ except ZeroDivisionError:
+ pass
+
+ return transform