From 51e88bd436b4af7f6ac7f424a8c5ef580b3aac3e Mon Sep 17 00:00:00 2001
From: Luke Campagnola <luke.campagnola@gmail.com>
Date: Wed, 26 Dec 2012 13:48:12 -0500
Subject: [PATCH] SVG export fixes

---
 examples/__main__.py               |   6 +-
 pyqtgraph/exporters/SVGExporter.py | 104 +++++++++++++++++++++--------
 tests/svg.py                       |  58 +++++++++++-----
 3 files changed, 121 insertions(+), 47 deletions(-)

diff --git a/examples/__main__.py b/examples/__main__.py
index 1dbe7b9a..bbba88f8 100644
--- a/examples/__main__.py
+++ b/examples/__main__.py
@@ -166,7 +166,7 @@ def buildFileList(examples, files=None):
             buildFileList(val, files)
     return files
             
-def testFile(name, f, exe, lib, graphicsSystem):
+def testFile(name, f, exe, lib, graphicsSystem=None):
     global path
     fn =  os.path.join(path,f)
     #print "starting process: ", fn
@@ -194,8 +194,8 @@ except:
     print("test failed")
     raise
 
-"""  % (import1, import2, graphicsSystem)
-    #print code
+"""  % (import1, graphicsSystem, import2)
+
     process = subprocess.Popen(['exec %s -i' % (exe)], shell=True, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
     process.stdin.write(code.encode('UTF-8'))
     #process.stdin.close()
diff --git a/pyqtgraph/exporters/SVGExporter.py b/pyqtgraph/exporters/SVGExporter.py
index 9787de59..ce05b82d 100644
--- a/pyqtgraph/exporters/SVGExporter.py
+++ b/pyqtgraph/exporters/SVGExporter.py
@@ -153,7 +153,7 @@ def _generateItemSvg(item, nodes=None, root=None):
         
     if root is None:
         root = item
-        
+                
     ## Skip hidden items
     if hasattr(item, 'isVisible') and not item.isVisible():
         return None
@@ -171,10 +171,10 @@ def _generateItemSvg(item, nodes=None, root=None):
         doc = xml.parseString(xmlStr)
     else:
         childs = item.childItems()
-        if isinstance(root, QtGui.QGraphicsScene):
-            tr = item.sceneTransform()
-        else:
-            tr = item.itemTransform(root)
+        tr = itemTransform(item, root)
+        
+        #print item, pg.SRTTransform(tr)
+
         #tr.translate(item.pos().x(), item.pos().y())
         #tr = tr * item.transform()
         arr = QtCore.QByteArray()
@@ -248,7 +248,7 @@ def _generateItemSvg(item, nodes=None, root=None):
             if isinstance(root, QtGui.QGraphicsScene):
                 path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape()))
             else:
-                path = QtGui.QGraphicsPathItem(item.mapToItem(root, item.shape()))
+                path = QtGui.QGraphicsPathItem(root.mapToParent(item.mapToItem(root, item.shape())))
             pathNode = _generateItemSvg(path, root=root).getElementsByTagName('path')[0]
             ## and for the clipPath element
             clip = name + '_clip'
@@ -306,6 +306,7 @@ def correctCoordinates(node, item):
                 ch.setAttribute('d', newCoords)
             elif ch.tagName == 'text':
                 removeTransform = False
+                ## leave text alone for now. Might need this later to correctly render text with outline.
                 #c = np.array([
                     #[float(ch.getAttribute('x')), float(ch.getAttribute('y'))], 
                     #[float(ch.getAttribute('font-size')), 0], 
@@ -318,32 +319,82 @@ def correctCoordinates(node, item):
                 #ch.setAttribute('font-size', str(fs))
             else:
                 print('warning: export not implemented for SVG tag %s (from item %s)' % (ch.tagName, item))
+                
+            ## correct line widths if needed
+            if removeTransform and ch.getAttribute('vector-effect') != 'non-scaling-stroke':
+                w = float(grp.getAttribute('stroke-width'))
+                s = pg.transformCoordinates(tr, np.array([[w,0], [0,0]]), transpose=True)
+                w = ((s[0]-s[1])**2).sum()**0.5
+                ch.setAttribute('stroke-width', str(w))
             
         if removeTransform:
             grp.removeAttribute('transform')
         
 
-def correctStroke(node, item, root, width=1):
-    #print "==============", item, node
-    if node.hasAttribute('stroke-width'):
-        width = float(node.getAttribute('stroke-width'))
-    if node.getAttribute('vector-effect') == 'non-scaling-stroke':
-        node.removeAttribute('vector-effect')
-        if isinstance(root, QtGui.QGraphicsScene):
-            w = item.mapFromScene(pg.Point(width,0))
-            o = item.mapFromScene(pg.Point(0,0))
+def itemTransform(item, root):
+    ## Return the transformation mapping item to root
+    ## (actually to parent coordinate system of root)
+    
+    if item is root:
+        tr = QtGui.QTransform()
+        tr.translate(*item.pos())
+        tr = tr * item.transform()
+        return tr
+        
+    
+    if int(item.flags() & item.ItemIgnoresTransformations) > 0:
+        pos = item.pos()
+        parent = item.parentItem()
+        if parent is not None:
+            pos = itemTransform(parent, root).map(pos)
+        tr = QtGui.QTransform()
+        tr.translate(pos.x(), pos.y())
+        tr = item.transform() * tr
+    else:
+        ## find next parent that is either the root item or 
+        ## an item that ignores its transformation
+        nextRoot = item
+        while True:
+            nextRoot = nextRoot.parentItem()
+            if nextRoot is None:
+                nextRoot = root
+                break
+            if nextRoot is root or int(nextRoot.flags() & nextRoot.ItemIgnoresTransformations) > 0:
+                break
+        
+        if isinstance(nextRoot, QtGui.QGraphicsScene):
+            tr = item.sceneTransform()
         else:
-            w = item.mapFromItem(root, pg.Point(width,0))
-            o = item.mapFromItem(root, pg.Point(0,0))
-        w = w-o
-        #print "   ", w, o, w-o
-        w = (w.x()**2 + w.y()**2) ** 0.5
-        #print "   ", w
-        node.setAttribute('stroke-width', str(w))
+            tr = itemTransform(nextRoot, root) * item.itemTransform(nextRoot)[0]
+            #pos = QtGui.QTransform()
+            #pos.translate(root.pos().x(), root.pos().y())
+            #tr = pos * root.transform() * item.itemTransform(root)[0]
+        
     
-    for ch in node.childNodes:
-        if isinstance(ch, xml.Element):
-            correctStroke(ch, item, root, width)
+    return tr
+
+
+#def correctStroke(node, item, root, width=1):
+    ##print "==============", item, node
+    #if node.hasAttribute('stroke-width'):
+        #width = float(node.getAttribute('stroke-width'))
+    #if node.getAttribute('vector-effect') == 'non-scaling-stroke':
+        #node.removeAttribute('vector-effect')
+        #if isinstance(root, QtGui.QGraphicsScene):
+            #w = item.mapFromScene(pg.Point(width,0))
+            #o = item.mapFromScene(pg.Point(0,0))
+        #else:
+            #w = item.mapFromItem(root, pg.Point(width,0))
+            #o = item.mapFromItem(root, pg.Point(0,0))
+        #w = w-o
+        ##print "   ", w, o, w-o
+        #w = (w.x()**2 + w.y()**2) ** 0.5
+        ##print "   ", w
+        #node.setAttribute('stroke-width', str(w))
+    
+    #for ch in node.childNodes:
+        #if isinstance(ch, xml.Element):
+            #correctStroke(ch, item, root, width)
             
 def cleanXml(node):
     ## remove extraneous text; let the xml library do the formatting.
@@ -359,4 +410,5 @@ def cleanXml(node):
     if hasElement:
         for ch in nonElement:
             node.removeChild(ch)
-
+    elif node.tagName == 'g':  ## remove childless groups
+        node.parentNode.removeChild(node)
diff --git a/tests/svg.py b/tests/svg.py
index db54eb83..7c26833e 100644
--- a/tests/svg.py
+++ b/tests/svg.py
@@ -6,26 +6,40 @@ import pyqtgraph as pg
 app = pg.mkQApp()
 
 class SVGTest(test.TestCase):
-    def test_plotscene(self):
-        pg.setConfigOption('foreground', (0,0,0))
-        w = pg.GraphicsWindow()
-        w.show()        
-        p1 = w.addPlot()
-        p2 = w.addPlot()
-        p1.plot([1,3,2,3,1,6,9,8,4,2,3,5,3], pen={'color':'k'})
-        p1.setXRange(0,5)
-        p2.plot([1,5,2,3,4,6,1,2,4,2,3,5,3], pen={'color':'k', 'cosmetic':False, 'width': 0.3})
-        app.processEvents()
-        app.processEvents()
-        
-        ex = pg.exporters.SVGExporter.SVGExporter(w.scene())
-        ex.export(fileName='test.svg')
+    #def test_plotscene(self):
+        #pg.setConfigOption('foreground', (0,0,0))
+        #w = pg.GraphicsWindow()
+        #w.show()        
+        #p1 = w.addPlot()
+        #p2 = w.addPlot()
+        #p1.plot([1,3,2,3,1,6,9,8,4,2,3,5,3], pen={'color':'k'})
+        #p1.setXRange(0,5)
+        #p2.plot([1,5,2,3,4,6,1,2,4,2,3,5,3], pen={'color':'k', 'cosmetic':False, 'width': 0.3})
+        #app.processEvents()
+        #app.processEvents()
+        
+        #ex = pg.exporters.SVGExporter.SVGExporter(w.scene())
+        #ex.export(fileName='test.svg')
 
-    #def test_simple(self):
+
+    def test_simple(self):
+        scene = pg.QtGui.QGraphicsScene()
         #rect = pg.QtGui.QGraphicsRectItem(0, 0, 100, 100)
+        #scene.addItem(rect)
+        #rect.setPos(20,20)
         #rect.translate(50, 50)
         #rect.rotate(30)
-        #grp = pg.ItemGroup()
+        #rect.scale(0.5, 0.5)
+        
+        #rect1 = pg.QtGui.QGraphicsRectItem(0, 0, 100, 100)
+        #rect1.setParentItem(rect)
+        #rect1.setFlag(rect1.ItemIgnoresTransformations)
+        #rect1.setPos(20, 20)
+        #rect1.scale(2,2)
+        
+        #el1 = pg.QtGui.QGraphicsEllipseItem(0, 0, 100, 100)
+        #el1.setParentItem(rect1)
+        ##grp = pg.ItemGroup()
         #grp.setParentItem(rect)
         #grp.translate(200,0)
         ##grp.rotate(30)
@@ -40,8 +54,16 @@ class SVGTest(test.TestCase):
         #el.scale(0.5,2)
         #el.setParentItem(rect2)
         
-        #ex = pg.exporters.SVGExporter.SVGExporter(rect)
-        #ex.export(fileName='test.svg')
+        grp2 = pg.ItemGroup()
+        scene.addItem(grp2)
+        grp2.scale(100,100)
+        
+        rect3 = pg.QtGui.QGraphicsRectItem(0,0,2,2)
+        rect3.setPen(pg.mkPen(width=1, cosmetic=False))
+        grp2.addItem(rect3)
+        
+        ex = pg.exporters.SVGExporter.SVGExporter(scene)
+        ex.export(fileName='test.svg')
         
 
 if __name__ == '__main__':
-- 
GitLab