Add option to remove non-scaling-stroke from svg output

This commit is contained in:
Luke Campagnola 2018-04-25 12:15:14 -07:00
parent 56aae02821
commit ee2d00c42a

View File

@ -23,7 +23,8 @@ class SVGExporter(Exporter):
#{'name': 'height', 'type': 'float', 'value': tr.height(), 'limits': (0, None)}, #{'name': 'height', 'type': 'float', 'value': tr.height(), 'limits': (0, None)},
#{'name': 'viewbox clipping', 'type': 'bool', 'value': True}, #{'name': 'viewbox clipping', 'type': 'bool', 'value': True},
#{'name': 'normalize coordinates', 'type': 'bool', 'value': True}, #{'name': 'normalize coordinates', 'type': 'bool', 'value': True},
#{'name': 'normalize line width', 'type': 'bool', 'value': True}, {'name': 'scaling stroke', 'type': 'bool', 'value': False, 'tip': "If False, strokes are non-scaling, "
"which means that they appear the same width on screen regardless of how they are scaled or how the view is zoomed."},
]) ])
#self.params.param('width').sigValueChanged.connect(self.widthChanged) #self.params.param('width').sigValueChanged.connect(self.widthChanged)
#self.params.param('height').sigValueChanged.connect(self.heightChanged) #self.params.param('height').sigValueChanged.connect(self.heightChanged)
@ -49,7 +50,8 @@ class SVGExporter(Exporter):
## Qt's SVG generator is not complete. (notably, it lacks clipping) ## Qt's SVG generator is not complete. (notably, it lacks clipping)
## Instead, we will use Qt to generate SVG for each item independently, ## Instead, we will use Qt to generate SVG for each item independently,
## then manually reconstruct the entire document. ## then manually reconstruct the entire document.
xml = generateSvg(self.item) options = {ch.name():ch.value() for ch in self.params.children()}
xml = generateSvg(self.item, options)
if toBytes: if toBytes:
return xml.encode('UTF-8') return xml.encode('UTF-8')
@ -69,10 +71,10 @@ xmlHeader = """\
<desc>Generated with Qt and pyqtgraph</desc> <desc>Generated with Qt and pyqtgraph</desc>
""" """
def generateSvg(item): def generateSvg(item, options={}):
global xmlHeader global xmlHeader
try: try:
node, defs = _generateItemSvg(item) node, defs = _generateItemSvg(item, options=options)
finally: finally:
## reset export mode for all items in the tree ## reset export mode for all items in the tree
if isinstance(item, QtGui.QGraphicsScene): if isinstance(item, QtGui.QGraphicsScene):
@ -94,7 +96,7 @@ def generateSvg(item):
return xmlHeader + defsXml + node.toprettyxml(indent=' ') + "\n</svg>\n" return xmlHeader + defsXml + node.toprettyxml(indent=' ') + "\n</svg>\n"
def _generateItemSvg(item, nodes=None, root=None): def _generateItemSvg(item, nodes=None, root=None, options={}):
## This function is intended to work around some issues with Qt's SVG generator ## This function is intended to work around some issues with Qt's SVG generator
## and SVG in general. ## and SVG in general.
## 1) Qt SVG does not implement clipping paths. This is absurd. ## 1) Qt SVG does not implement clipping paths. This is absurd.
@ -209,18 +211,8 @@ def _generateItemSvg(item, nodes=None, root=None):
## Get rid of group transformation matrices by applying ## Get rid of group transformation matrices by applying
## transformation to inner coordinates ## transformation to inner coordinates
correctCoordinates(g1, defs, item) correctCoordinates(g1, defs, item, options)
profiler('correct') profiler('correct')
## make sure g1 has the transformation matrix
#m = (tr.m11(), tr.m12(), tr.m21(), tr.m22(), tr.m31(), tr.m32())
#g1.setAttribute('transform', "matrix(%f,%f,%f,%f,%f,%f)" % m)
#print "=================",item,"====================="
#print g1.toprettyxml(indent=" ", newl='')
## Inkscape does not support non-scaling-stroke (this is SVG 1.2, inkscape supports 1.1)
## So we need to correct anything attempting to use this.
#correctStroke(g1, item, root)
## decide on a name for this item ## decide on a name for this item
baseName = item.__class__.__name__ baseName = item.__class__.__name__
@ -239,15 +231,10 @@ def _generateItemSvg(item, nodes=None, root=None):
## See if this item clips its children ## See if this item clips its children
if int(item.flags() & item.ItemClipsChildrenToShape) > 0: if int(item.flags() & item.ItemClipsChildrenToShape) > 0:
## Generate svg for just the path ## Generate svg for just the path
#if isinstance(root, QtGui.QGraphicsScene):
#path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape()))
#else:
#path = QtGui.QGraphicsPathItem(root.mapToParent(item.mapToItem(root, item.shape())))
path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape())) path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape()))
item.scene().addItem(path) item.scene().addItem(path)
try: try:
#pathNode = _generateItemSvg(path, root=root).getElementsByTagName('path')[0] pathNode = _generateItemSvg(path, root=root, options=options)[0].getElementsByTagName('path')[0]
pathNode = _generateItemSvg(path, root=root)[0].getElementsByTagName('path')[0]
# assume <defs> for this path is empty.. possibly problematic. # assume <defs> for this path is empty.. possibly problematic.
finally: finally:
item.scene().removeItem(path) item.scene().removeItem(path)
@ -267,7 +254,7 @@ def _generateItemSvg(item, nodes=None, root=None):
## Add all child items as sub-elements. ## Add all child items as sub-elements.
childs.sort(key=lambda c: c.zValue()) childs.sort(key=lambda c: c.zValue())
for ch in childs: for ch in childs:
csvg = _generateItemSvg(ch, nodes, root) csvg = _generateItemSvg(ch, nodes, root, options=options)
if csvg is None: if csvg is None:
continue continue
cg, cdefs = csvg cg, cdefs = csvg
@ -277,7 +264,8 @@ def _generateItemSvg(item, nodes=None, root=None):
profiler('children') profiler('children')
return g1, defs return g1, defs
def correctCoordinates(node, defs, item):
def correctCoordinates(node, defs, item, options):
# TODO: correct gradient coordinates inside defs # TODO: correct gradient coordinates inside defs
## Remove transformation matrices from <g> tags by applying matrix to coordinates inside. ## Remove transformation matrices from <g> tags by applying matrix to coordinates inside.
@ -382,6 +370,10 @@ def correctCoordinates(node, defs, item):
w = ((s[0]-s[1])**2).sum()**0.5 w = ((s[0]-s[1])**2).sum()**0.5
ch.setAttribute('stroke-width', str(w)) ch.setAttribute('stroke-width', str(w))
# Remove non-scaling-stroke if requested
if options.get('scaling stroke') is True and ch.getAttribute('vector-effect') == 'non-scaling-stroke':
ch.removeAttribute('vector-effect')
if removeTransform: if removeTransform:
grp.removeAttribute('transform') grp.removeAttribute('transform')