from .Exporter import Exporter from pyqtgraph.parametertree import Parameter from pyqtgraph.Qt import QtGui, QtCore, QtSvg import re import xml.dom.minidom as xml __all__ = ['SVGExporter'] class SVGExporter(Exporter): Name = "Scalable Vector Graphics (SVG)" def __init__(self, item): Exporter.__init__(self, item) #tr = self.getTargetRect() self.params = Parameter(name='params', type='group', children=[ #{'name': 'width', 'type': 'float', 'value': tr.width(), 'limits': (0, None)}, #{'name': 'height', 'type': 'float', 'value': tr.height(), 'limits': (0, None)}, ]) #self.params.param('width').sigValueChanged.connect(self.widthChanged) #self.params.param('height').sigValueChanged.connect(self.heightChanged) def widthChanged(self): sr = self.getSourceRect() ar = sr.height() / sr.width() self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) def heightChanged(self): sr = self.getSourceRect() ar = sr.width() / sr.height() self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) def parameters(self): return self.params def export(self, fileName=None, toBytes=False): if toBytes is False and fileName is None: self.fileSaveDialog(filter="Scalable Vector Graphics (*.svg)") return #self.svg = QtSvg.QSvgGenerator() #self.svg.setFileName(fileName) #dpi = QtGui.QDesktopWidget().physicalDpiX() ### not really sure why this works, but it seems to be important: #self.svg.setSize(QtCore.QSize(self.params['width']*dpi/90., self.params['height']*dpi/90.)) #self.svg.setResolution(dpi) ##self.svg.setViewBox() #targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height']) #sourceRect = self.getSourceRect() #painter = QtGui.QPainter(self.svg) #try: #self.setExportMode(True) #self.render(painter, QtCore.QRectF(targetRect), sourceRect) #finally: #self.setExportMode(False) #painter.end() ## Workaround to set pen widths correctly #data = open(fileName).readlines() #for i in range(len(data)): #line = data[i] #m = re.match(r'( pyqtgraph SVG export Generated with Qt and pyqtgraph """ + node.toprettyxml(indent=' ') + "\n\n" if toBytes: return bytes(xml) else: with open(fileName, 'w') as fh: fh.write(xml) def generateItemSvg(self, item): if isinstance(item, QtGui.QGraphicsScene): xmlStr = "" childs = [i for i in item.items() if i.parentItem() is None] else: tr = QtGui.QTransform() tr.translate(item.pos().x(), item.pos().y()) tr = tr * item.transform() if not item.isVisible() or int(item.flags() & item.ItemHasNoContents) > 0: m = (tr.m11(), tr.m12(), tr.m21(), tr.m22(), tr.m31(), tr.m32()) #print item, m xmlStr = '' % m else: arr = QtCore.QByteArray() buf = QtCore.QBuffer(arr) svg = QtSvg.QSvgGenerator() svg.setOutputDevice(buf) dpi = QtGui.QDesktopWidget().physicalDpiX() ### not really sure why this works, but it seems to be important: #self.svg.setSize(QtCore.QSize(self.params['width']*dpi/90., self.params['height']*dpi/90.)) svg.setResolution(dpi) p = QtGui.QPainter() p.begin(svg) if hasattr(item, 'setExportMode'): item.setExportMode(True, {'painter': p}) try: #tr = QtGui.QTransform() #tr.translate(item.pos().x(), item.pos().y()) #p.setTransform(tr * item.transform()) p.setTransform(tr) item.paint(p, QtGui.QStyleOptionGraphicsItem(), None) finally: p.end() if hasattr(item, 'setExportMode'): item.setExportMode(False) xmlStr = str(arr) childs = item.childItems() doc = xml.parseString(xmlStr) try: groups = doc.getElementsByTagName('g') if len(groups) == 1: g1 = g2 = groups[0] else: g1,g2 = groups[:2] except: print doc.toxml() raise g1.setAttribute('id', item.__class__.__name__) ## Check for item visibility visible = True if not isinstance(item, QtGui.QGraphicsScene): parent = item while visible and parent is not None: visible = parent.isVisible() parent = parent.parentItem() if not visible: style = g1.getAttribute('style').strip() if len(style)>0 and not style.endswith(';'): style += ';' style += 'display:none;' g1.setAttribute('style', style) childs.sort(key=lambda c: c.zValue()) for ch in childs: cg = self.generateItemSvg(ch) g2.appendChild(cg) return g1 ### To check: ### do all items really generate this double-group structure? ### are both groups necessary? ### How do we implement clipping? (can we clip to an object that is visible?)