2016-07-11 23:01:57 +02:00
|
|
|
#!/usr/bin/env python3
|
2016-06-13 18:55:41 +02:00
|
|
|
# Copyright (C) 2016 Florian Festi
|
|
|
|
#
|
|
|
|
# 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
import xml.parsers.expat
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
class SVGFile(object):
|
2016-06-22 23:09:16 +02:00
|
|
|
pathre = re.compile(r"[MCL]? *((-?\d+(\.\d+)?) (-?\d+(\.\d+)?) *)+")
|
2016-06-13 18:55:41 +02:00
|
|
|
transformre = re.compile(r"matrix\(" + ",".join([r"(-?\d+(\.\d+)?)"] * 6) + "\)")
|
|
|
|
|
|
|
|
def __init__(self, filename):
|
|
|
|
self.filename = filename
|
|
|
|
self.minx = self.maxx = self.miny = self.maxy = None
|
|
|
|
|
|
|
|
def handleStartElement(self, name, attrs):
|
|
|
|
self.tags.append(name)
|
|
|
|
if name == "path" and "symbol" not in self.tags:
|
|
|
|
minx = maxx = miny = maxy = None
|
|
|
|
m = self.transformre.match(attrs.get("transform", ""))
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-06-13 18:55:41 +02:00
|
|
|
if m:
|
|
|
|
matrix = [float(m.group(i)) for i in range(1, 12, 2)]
|
|
|
|
else:
|
2016-06-22 23:10:26 +02:00
|
|
|
matrix = [1, 0,
|
|
|
|
0, 1,
|
|
|
|
0, 0]
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-06-13 18:55:41 +02:00
|
|
|
for m in self.pathre.findall(attrs.get("d", "")):
|
|
|
|
x = float(m[1])
|
2016-06-22 23:09:16 +02:00
|
|
|
y = float(m[3])
|
2016-08-17 15:07:41 +02:00
|
|
|
tx = matrix[0] * x + matrix[2] * y + matrix[4]
|
|
|
|
ty = matrix[1] * x + matrix[3] * y + matrix[5]
|
2016-06-13 18:55:41 +02:00
|
|
|
|
|
|
|
if self.minx is None or self.minx > tx:
|
|
|
|
self.minx = tx
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-06-13 18:55:41 +02:00
|
|
|
if self.maxx is None or self.maxx < tx:
|
|
|
|
self.maxx = tx
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-06-13 18:55:41 +02:00
|
|
|
if self.miny is None or self.miny > ty:
|
|
|
|
self.miny = ty
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-06-13 18:55:41 +02:00
|
|
|
if self.maxy is None or self.maxy < ty:
|
|
|
|
self.maxy = ty
|
|
|
|
|
|
|
|
def handleEndElement(self, name):
|
|
|
|
last = self.tags.pop()
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-06-13 18:55:41 +02:00
|
|
|
if last != name:
|
|
|
|
raise ValueError("Got </%s> expected </%s>" % (name, last))
|
|
|
|
|
|
|
|
def getEnvelope(self):
|
|
|
|
self.tags = []
|
|
|
|
p = xml.parsers.expat.ParserCreate()
|
|
|
|
p.StartElementHandler = self.handleStartElement
|
|
|
|
p.EndElementHandler = self.handleEndElement
|
|
|
|
p.ParseFile(open(self.filename, "rb"))
|
|
|
|
|
|
|
|
def rewriteViewPort(self):
|
|
|
|
f = open(self.filename, "r+")
|
|
|
|
s = f.read(1024)
|
|
|
|
|
2016-06-27 19:40:23 +02:00
|
|
|
m = re.search(r"""<svg[^>]*(width="(\d+pt)" height="(\d+pt)" viewBox="0 (0 (\d+) (\d+))") version="1.1">""", s)
|
2016-06-13 18:55:41 +02:00
|
|
|
|
2016-08-17 15:07:41 +02:00
|
|
|
# minx = 10*int(self.minx//10)-10
|
2016-06-22 10:47:47 +02:00
|
|
|
# as we don't rewrite the left border keep it as 0
|
2017-08-20 19:56:33 +02:00
|
|
|
|
|
|
|
self.minx = self.minx or 0
|
|
|
|
self.miny = self.miny or 0
|
|
|
|
self.maxx = self.maxx or (self.minx + 10)
|
|
|
|
self.maxy = self.maxy or (self.miny + 10)
|
|
|
|
|
2016-06-22 10:47:47 +02:00
|
|
|
if 0 <= self.minx <= 50:
|
|
|
|
minx = 0
|
|
|
|
else:
|
2016-08-17 15:07:41 +02:00
|
|
|
minx = 10 * int(self.minx // 10) - 10
|
|
|
|
# raise ValueError("Left end of drawing at wrong place: %imm (0-50mm expected)" % self.minx)
|
|
|
|
maxx = 10 * int(self.maxx // 10) + 10
|
|
|
|
miny = 10 * int(self.miny // 10) - 10
|
|
|
|
maxy = 10 * int(self.maxy // 10) + 10
|
2016-06-13 18:55:41 +02:00
|
|
|
|
|
|
|
if m:
|
|
|
|
f.seek(m.start(1))
|
2019-02-14 21:13:51 +01:00
|
|
|
s = ('width="%imm" height="%imm" viewBox="%i %i %i %i"' %
|
|
|
|
(maxx - minx, maxy - miny, minx, miny, maxx - minx, maxy - miny))
|
2016-08-17 15:07:41 +02:00
|
|
|
|
2016-06-27 19:40:23 +02:00
|
|
|
if len(s) > len(m.group(1)):
|
|
|
|
raise ValueError("Not enough space for size")
|
2016-08-17 15:07:41 +02:00
|
|
|
|
|
|
|
f.write(s + " " * (len(m.group(1)) - len(s)))
|
2016-06-13 18:55:41 +02:00
|
|
|
else:
|
2016-08-17 15:07:41 +02:00
|
|
|
raise ValueError("Could not understand SVG file")
|
|
|
|
|
2017-02-25 16:10:28 +01:00
|
|
|
unit2mm = {"mm" : 1.0,
|
|
|
|
"cm" : 10.0,
|
|
|
|
"in" : 25.4,
|
|
|
|
"px" : 90.0/25.4,
|
|
|
|
"pt" : 90.0/25.4/1.25,
|
|
|
|
"pc" : 90.0/25.4/15,
|
|
|
|
}
|
|
|
|
|
|
|
|
def getSizeInMM(tree):
|
|
|
|
root = tree.getroot()
|
2019-02-14 21:13:51 +01:00
|
|
|
m = re.match(r"(-?\d+\.?\d*)(\D+)", root.get("height"))
|
2017-02-25 16:10:28 +01:00
|
|
|
height, units = m.groups()
|
|
|
|
height = float(height) * unit2mm.get(units, 1.0)
|
|
|
|
|
2019-02-14 21:13:51 +01:00
|
|
|
m = re.match(r"(-?\d+\.?\d*)(\D+)", root.get("width"))
|
2017-02-25 16:10:28 +01:00
|
|
|
width, units = m.groups()
|
|
|
|
width = float(width) * unit2mm.get(units, 1.0)
|
|
|
|
|
|
|
|
return width, height
|
|
|
|
|
|
|
|
def getViewBox(tree):
|
|
|
|
root = tree.getroot()
|
2019-02-14 21:13:51 +01:00
|
|
|
m = re.match(r"\s*(-?\d+\.?\d*)\s+"
|
|
|
|
"(-?\d+\.?\d*)\s+"
|
|
|
|
"(-?\d+\.?\d*)\s+"
|
|
|
|
"(-?\d+\.?\d)\s*", root.get("viewBox"))
|
2017-02-25 16:10:28 +01:00
|
|
|
|
|
|
|
return [float(m) for m in m.groups()]
|
|
|
|
|
|
|
|
def ticksPerMM(tree):
|
|
|
|
width, height = getSizeInMM(tree)
|
|
|
|
x1, y1, x2, y2 = getViewBox(tree)
|
|
|
|
|
|
|
|
return x2/width, y2/height
|
2016-06-13 18:55:41 +02:00
|
|
|
|
2017-02-23 17:30:26 +01:00
|
|
|
def svgMerge(box, inkscape, output):
|
2017-03-10 22:55:33 +01:00
|
|
|
from lxml import etree as et
|
|
|
|
|
2017-02-23 17:30:26 +01:00
|
|
|
parser = et.XMLParser(remove_blank_text=True)
|
|
|
|
|
|
|
|
src_tree = et.parse(box, parser)
|
|
|
|
dest_tree = et.parse(inkscape, parser)
|
|
|
|
dest_root = dest_tree.getroot()
|
|
|
|
|
2017-02-25 16:10:28 +01:00
|
|
|
src_width, src_height = getSizeInMM(src_tree)
|
|
|
|
dest_width, dest_height = getSizeInMM(dest_tree)
|
|
|
|
|
|
|
|
src_scale_x, src_scale_y = ticksPerMM(src_tree)
|
|
|
|
dest_scale_x, dest_scale_y = ticksPerMM(dest_tree)
|
|
|
|
|
|
|
|
scale_x = dest_scale_x / src_scale_x
|
|
|
|
scale_y = dest_scale_y / src_scale_y
|
2017-02-23 17:30:26 +01:00
|
|
|
|
2017-02-25 16:10:28 +01:00
|
|
|
src_view = getViewBox(src_tree)
|
2017-02-23 17:30:26 +01:00
|
|
|
|
2017-02-25 16:10:28 +01:00
|
|
|
off_x = src_view[0] * -scale_x
|
|
|
|
off_y = (src_view[1]+src_view[3]) * -scale_y + dest_height * scale_y
|
2017-02-23 17:30:26 +01:00
|
|
|
|
|
|
|
for el in src_tree.getroot():
|
|
|
|
import sys
|
|
|
|
dest_root.append(el)
|
|
|
|
if el.tag.endswith("g"):
|
2017-02-25 16:10:28 +01:00
|
|
|
el.set("transform", "matrix(%f,0,0,%f, %f, %f)" % (
|
|
|
|
scale_x, scale_y, off_x, off_y))
|
2017-02-23 17:30:26 +01:00
|
|
|
|
|
|
|
# write the xml file
|
|
|
|
et.ElementTree(dest_root).write(output, pretty_print=True, encoding='utf-8', xml_declaration=True)
|
|
|
|
|
2016-06-13 18:55:41 +02:00
|
|
|
if __name__ == "__main__":
|
|
|
|
svg = SVGFile("examples/box.svg")
|
|
|
|
svg.getEnvelope()
|
|
|
|
svg.rewriteViewPort()
|