Merge pull request #671 from campagnola/svg-export-updates

Svg export updates
This commit is contained in:
Luke Campagnola 2018-04-25 13:05:48 -07:00 committed by GitHub
commit 1d1d7bfe1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -23,7 +23,8 @@ class SVGExporter(Exporter):
#{'name': 'height', 'type': 'float', 'value': tr.height(), 'limits': (0, None)},
#{'name': 'viewbox clipping', '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('height').sigValueChanged.connect(self.heightChanged)
@ -49,7 +50,8 @@ class SVGExporter(Exporter):
## Qt's SVG generator is not complete. (notably, it lacks clipping)
## Instead, we will use Qt to generate SVG for each item independently,
## 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:
return xml.encode('UTF-8')
@ -69,10 +71,10 @@ xmlHeader = """\
<desc>Generated with Qt and pyqtgraph</desc>
"""
def generateSvg(item):
def generateSvg(item, options={}):
global xmlHeader
try:
node, defs = _generateItemSvg(item)
node, defs = _generateItemSvg(item, options=options)
finally:
## reset export mode for all items in the tree
if isinstance(item, QtGui.QGraphicsScene):
@ -94,7 +96,7 @@ def generateSvg(item):
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
## and SVG in general.
## 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
## transformation to inner coordinates
correctCoordinates(g1, defs, item)
correctCoordinates(g1, defs, item, options)
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
baseName = item.__class__.__name__
@ -239,15 +231,10 @@ def _generateItemSvg(item, nodes=None, root=None):
## See if this item clips its children
if int(item.flags() & item.ItemClipsChildrenToShape) > 0:
## 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()))
item.scene().addItem(path)
try:
#pathNode = _generateItemSvg(path, root=root).getElementsByTagName('path')[0]
pathNode = _generateItemSvg(path, root=root)[0].getElementsByTagName('path')[0]
pathNode = _generateItemSvg(path, root=root, options=options)[0].getElementsByTagName('path')[0]
# assume <defs> for this path is empty.. possibly problematic.
finally:
item.scene().removeItem(path)
@ -267,17 +254,18 @@ def _generateItemSvg(item, nodes=None, root=None):
## Add all child items as sub-elements.
childs.sort(key=lambda c: c.zValue())
for ch in childs:
csvg = _generateItemSvg(ch, nodes, root)
csvg = _generateItemSvg(ch, nodes, root, options=options)
if csvg is None:
continue
cg, cdefs = csvg
childGroup.appendChild(cg) ### this isn't quite right--some items draw below their parent (good enough for now)
defs.extend(cdefs)
profiler('children')
return g1, defs
def correctCoordinates(node, defs, item):
def correctCoordinates(node, defs, item, options):
# TODO: correct gradient coordinates inside defs
## Remove transformation matrices from <g> tags by applying matrix to coordinates inside.
@ -344,6 +332,10 @@ def correctCoordinates(node, defs, item):
t = ''
nc = fn.transformCoordinates(tr, np.array([[float(x),float(y)]]), transpose=True)
newCoords += t+str(nc[0,0])+','+str(nc[0,1])+' '
# If coords start with L instead of M, then the entire path will not be rendered.
# (This can happen if the first point had nan values in it--Qt will skip it on export)
if newCoords[0] != 'M':
newCoords = 'M' + newCoords[1:]
ch.setAttribute('d', newCoords)
elif ch.tagName == 'text':
removeTransform = False
@ -378,6 +370,10 @@ def correctCoordinates(node, defs, item):
w = ((s[0]-s[1])**2).sum()**0.5
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:
grp.removeAttribute('transform')