On Wed, 2 Sep 2009, Johan Asplund wrote:
Some journals want to have the figures embedded in a word-document.
Therefore I would like to be able to export to a vector-format that
can be inserted into word.
Okay, I have a test patch (quite basic) which implements much of what is
needed for an EMF export. It appears to work
What it doesn't do:
- Custom line styles - might be possible to fix by improving pyemf
- only standard line styles are improved
- Bitmap (e.g. images, embedded pixmaps) - might be possible to fix
by improving pyemf, but there is no support.
- Rotated text - for some reason these get converted to bitmaps by Qt -
might be possible to fix if I can work out why
- Transparency - appears not to be possible in EMF format
The text output doesn't look great in OpenOffice as OO seems to have
problems with the paths generated.
It requires however:
- pyemf http://pyemf.sourceforge.net/
- PyQt-x11-gpl-4.6-snapshot-20090906 (or greater)
- sip-4.9-snapshot-20090906 (or greater)
The new PyQt and sip are required because of a problem in the wrapper for
QPaintEngine.
Jeremy
--
Jeremy Sanders <[email protected]>
http://www.jeremysanders.net/ Cambridge, UK
Public Key Server PGP Key ID: E1AAE053
Index: windows/mainwindow.py
===================================================================
--- windows/mainwindow.py (revision 1055)
+++ windows/mainwindow.py (working copy)
@@ -932,6 +932,7 @@
(["bmp"], "Windows bitmap format"),
(["pdf"], "Portable Document Format"),
(["svg"], "Scalable Vector Graphics"),
+ (["emf"], "Windows Metafile Format"),
#(["pic"], "QT Pic format"),
]
Index: document/emf_export.py
===================================================================
--- document/emf_export.py (revision 0)
+++ document/emf_export.py (revision 0)
@@ -0,0 +1,332 @@
+# Copyright (C) 2009 Jeremy S. Sanders
+# Email: Jeremy Sanders <[email protected]>
+#
+# 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 2 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, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+##############################################################################
+
+# $Id$
+
+"""A paint engine to produce EMF exports.
+
+Requires: PyQt-x11-gpl-4.6-snapshot-20090906.tar.gz
+ sip-4.9-snapshot-20090906.tar.gz
+ pyemf
+"""
+
+import sys
+import pyemf
+import veusz.qtall as qt4
+
+inch_mm = 25.4
+scale = 100
+
+class EMFPaintEngine(qt4.QPaintEngine):
+ "Custom EMF paint engine."""
+
+ def __init__(self, width_in, height_in, dpi=75):
+ qt4.QPaintEngine.__init__(self)
+ self.width = width_in
+ self.height = height_in
+ self.dpi = dpi
+
+ def begin(self, paintdevice):
+ self.emf = pyemf.EMF(self.width, self.height, self.dpi*scale)
+ self.pen = self.emf.GetStockObject(pyemf.BLACK_PEN)
+ self.brush = self.emf.GetStockObject(pyemf.NULL_BRUSH)
+
+ self.paintdevice = paintdevice
+ return True
+
+ def drawLines(self, lines):
+ """Draw lines to emf output."""
+ print "lines"
+ self.emf.SelectObject(self.pen)
+ for line in lines:
+ self.emf.Polyline(
+ [(line.x1()*scale, line.y1()*scale),
+ (line.x2()*scale, line.y2()*scale)] )
+
+ def drawPolygon(self, points, mode):
+ """Draw polygon on output."""
+ print "Polygon"
+ pts = [(p.x()*scale, p.y()*scale) for p in points]
+ self.emf.SelectObject(self.pen)
+ if mode == qt4.QPaintEngine.PolylineMode:
+ self.emf.Polyline(pts)
+ else:
+ self.emf.SelectObject(self.brush)
+ self.emf.SetPolyFillMode( {qt4.QPaintEngine.WindingMode:
+ pyemf.WINDING,
+ qt4.QPaintEngine.OddEvenMode:
+ pyemf.ALTERNATE,
+ qt4.QPaintEngine.ConvexMode:
+ pyemf.WINDING} )
+ self.emf.Polygon(pts)
+
+ def drawEllipse(self, rect):
+ """Draw an ellipse."""
+ print "ellipse"
+ self.emf.SelectObject(self.pen)
+ self.emf.SelectObject(self.brush)
+ args = (rect.left()*scale, rect.top()*scale,
+ rect.right()*scale, rect.bottom()*scale,
+ rect.left()*scale, rect.top()*scale,
+ rect.left()*scale, rect.top()*scale)
+ self.emf.Pie(*args)
+ self.emf.Arc(*args)
+
+ def drawPoints(self, points):
+ """Draw points."""
+ print "points"
+ self.emf.SelectObject(self.brush)
+ for pt in points:
+ x, y = (pt.x()-0.5)*scale, (pt.y()-0.5)*scale
+ self.emf.Pie( x, y,
+ (pt.x()+0.5)*scale, (pt.y()+0.5)*scale,
+ x, y, x, y )
+
+ def drawPixmap(self, r, pm, sr):
+ print "pixmap", r, pm, sr, pm.width(), pm.height()
+
+ def _createPath(self, path):
+ """Convert qt path to emf path"""
+ self.emf.BeginPath()
+ count = path.elementCount()
+ i = 0
+ while i < count:
+ e = path.elementAt(i)
+ if e.type == qt4.QPainterPath.MoveToElement:
+ self.emf.MoveTo(e.x*scale, e.y*scale)
+ elif e.type == qt4.QPainterPath.LineToElement:
+ self.emf.LineTo(e.x*scale, e.y*scale)
+ elif e.type == qt4.QPainterPath.CurveToElement:
+ e1 = path.elementAt(i+1)
+ e2 = path.elementAt(i+2)
+ self.emf.PolyBezierTo((
+ (e.x*scale, e.y*scale),
+ (e1.x*scale, e1.y*scale),
+ (e2.x*scale, e2.y*scale)))
+ i += 2
+ else:
+ assert False
+
+ i += 1
+ self.emf.CloseFigure()
+ self.emf.EndPath()
+
+ def drawPath(self, path):
+ """Draw a path on the output."""
+ print "path"
+
+ self.emf.SelectObject(self.pen)
+ self.emf.SelectObject(self.brush)
+ self._createPath(path)
+ self.emf.StrokeAndFillPath()
+
+ def end(self):
+ return True
+
+ def saveFile(self, filename):
+ self.emf.save(filename)
+
+ def _updatePen(self, pen):
+ """Update the pen to the currently selected one."""
+ style = {qt4.Qt.NoPen: pyemf.PS_NULL,
+ qt4.Qt.SolidLine: pyemf.PS_SOLID,
+ qt4.Qt.DashLine: pyemf.PS_DASH,
+ qt4.Qt.DotLine: pyemf.PS_DOT,
+ qt4.Qt.DashDotLine: pyemf.PS_DASHDOT,
+ qt4.Qt.DashDotDotLine: pyemf.PS_DASHDOTDOT,
+ qt4.Qt.CustomDashLine: pyemf.PS_SOLID}[pen.style()]
+ width = pen.widthF()*scale
+ qc = pen.color()
+ color = (qc.red(), qc.green(), qc.blue())
+ print "pen", color
+ self.pen = self.emf.CreatePen(style, width, color)
+
+ def _updateBrush(self, brush):
+ """Update to selected brush."""
+ style = brush.style()
+ qc = brush.color()
+ color = (qc.red(), qc.green(), qc.blue())
+ print "brush", color
+ if style == qt4.Qt.SolidPattern:
+ self.brush = self.emf.CreateSolidBrush(color)
+ elif style == qt4.Qt.NoBrush:
+ self.brush = self.emf.GetStockObject(pyemf.NULL_BRUSH)
+ else:
+ try:
+ hatch = {qt4.Qt.HorPattern: pyemf.HS_HORIZONTAL,
+ qt4.Qt.VerPattern: pyemf.HS_VERTICAL,
+ qt4.Qt.CrossPattern: pyemf.HS_CROSS,
+ qt4.Qt.BDiagPattern: pyemf.HS_BDIAGONAL,
+ qt4.Qt.FDiagPattern: pyemf.HS_FDIAGONAL,
+ qt4.Qt.DiagCrossPattern:
+ pyemf.HS_DIAGCROSS}[brush.style()]
+ except KeyError:
+ self.brush = self.emf.CreateSolidBrush(color)
+ else:
+ self.brush = CreateHatchBrush(hatch, color)
+
+ def _updateClipPath(self, path, operation):
+ """Update clipping path."""
+ print "clip"
+ if operation != qt4.Qt.NoClip:
+ self._createPath(path)
+ clipmode = {
+ qt4.Qt.ReplaceClip: pyemf.RGN_COPY,
+ qt4.Qt.IntersectClip: pyemf.RGN_AND,
+ qt4.Qt.UniteClip: pyemf.RGN_OR}[operation]
+ else:
+ # is this the only wave to get rid of clipping?
+ self.emf.BeginPath()
+ self.emf.MoveTo(0,0)
+ w = self.width*self.dpi*scale
+ h = self.height*self.dpi*scale
+ self.emf.LineTo(w, 0)
+ self.emf.LineTo(w, h)
+ self.emf.LineTo(0, h)
+ self.emf.CloseFigure()
+ self.emf.EndPath()
+ clipmode = pyemf.RGN_COPY
+
+ self.emf.SelectClipPath(mode=clipmode)
+
+ def _updateTransform(self, m):
+ """Update transformation."""
+ print "transform", (m.m11(), m.m12(),
+ m.m21(), m.m22(),
+ m.dx(), m.dy())
+ self.emf.SetWorldTransform(m.m11(), m.m12(),
+ m.m21(), m.m22(),
+ m.dx()*scale, m.dy()*scale)
+
+ def updateState(self, state):
+ """Examine what has changed in state and call apropriate function."""
+ ss = state.state()
+ if ss & qt4.QPaintEngine.DirtyPen:
+ self._updatePen(state.pen())
+ if ss & qt4.QPaintEngine.DirtyBrush:
+ self._updateBrush(state.brush())
+ if ss & qt4.QPaintEngine.DirtyClipPath:
+ self._updateClipPath(state.clipPath(), state.clipOperation())
+ if ss & qt4.QPaintEngine.DirtyClipRegion:
+ path = qt4.QPainterPath()
+ path.addRegion(state.clipRegion())
+ self._updateClipPath(path, state.clipOperation())
+ if ss & qt4.QPaintEngine.DirtyTransform:
+ self._updateTransform(state.matrix())
+
+ def type(self):
+ return qt4.QPaintEngine.PostScript
+
+ def hasFeature(self, f):
+ return f in (
+ qt4.QPaintEngine.Antialiasing,
+ qt4.QPaintEngine.PainterPaths,
+ qt4.QPaintEngine.PrimitiveTransform,
+ qt4.QPaintEngine.PaintOutsidePaintEvent,
+ )
+
+class EMFPaintDevice(qt4.QPaintDevice):
+ """Paint device for EMF paint engine."""
+
+ def __init__(self, width_in, height_in, dpi=75):
+ qt4.QPaintDevice.__init__(self)
+ self.engine = EMFPaintEngine(width_in, height_in, dpi=dpi)
+
+ def paintEngine(self):
+ return self.engine
+
+ def width(self):
+ return self.engine.width*self.engine.dpi
+
+ def widthMM(self):
+ return int(self.width() * inch_mm)
+
+ def height(self):
+ return self.engine.height*self.engine.dpi
+
+ def heightMM(self):
+ return int(self.height() * inch_mm)
+
+ def logicalDpiX(self):
+ return self.engine.dpi
+
+ def logicalDpiY(self):
+ return self.engine.dpi
+
+ def physicalDpiX(self):
+ return self.engine.dpi
+
+ def physicalDpiY(self):
+ return self.engine.dpi
+
+ def depth(self):
+ return 24
+
+ def numColors(self):
+ return 2147483647
+
+ def metric(self, m):
+ if m & qt4.QPaintDevice.PdmWidth:
+ return self.width()
+ elif m & qt4.QPaintDevice.PdmHeight:
+ return self.height()
+ elif m & qt4.QPaintDevice.PdmWidthMM:
+ return self.widthMM()
+ elif m & qt4.QPaintDevice.PdmHeightMM:
+ return self.heightMM()
+ elif m & qt4.QPaintDevice.PdmNumColors:
+ return self.numColors()
+ elif m & qt4.QPaintDevice.PdmDepth:
+ return self.depth()
+ elif m & qt4.QPaintDevice.PdmDpiX:
+ return self.logicalDpiX()
+ elif m & qt4.QPaintDevice.PdmDpiY:
+ return self.logicalDpiY()
+ elif m & qt4.QPaintDevice.PdmPhysicalDpiX:
+ return self.physicalDpiX()
+ elif m & qt4.QPaintDevice.PdmPhysicalDpiY:
+ return self.physcialDpiY()
+
+def main():
+ app = qt4.QApplication(sys.argv)
+
+ device = EMFPaintDevice(5,5)
+ p = qt4.QPainter(device)
+ #p.drawLines( [qt4.QLineF(0, 0, 100, 100), qt4.QLineF(100,100,200,200)] )
+ #p.drawLine( qt4.QLineF(200,200,300,300))
+
+ p.setBrush(qt4.QBrush(qt4.Qt.red))
+ p.setPen(qt4.QPen(qt4.QBrush(qt4.Qt.green), 3.))
+ p.save()
+ p.setClipRect( qt4.QRectF( qt4.QPointF(0,0), qt4.QPointF(70,70)))
+ p.drawPolygon(qt4.QPolygonF([qt4.QPointF(0,0), qt4.QPointF(100,0),
+ qt4.QPointF(0,100), qt4.QPointF(100,100)]))
+ p.setBrush(qt4.QBrush(qt4.Qt.blue))
+ p.drawEllipse(qt4.QRectF(200,200,50,50))
+ p.restore()
+
+ p.setPen(qt4.QPen(qt4.Qt.blue))
+ p.setFont(qt4.QFont("Arial", 60))
+ p.drawText(100, 100, "Hi there")
+
+ p.end()
+
+ device.paintEngine().saveFile("out.emf")
+
+if __name__ == '__main__':
+ main()
Property changes on: document/emf_export.py
___________________________________________________________________
Added: svn:keywords
+ Author Date Id Revision
Index: document/doc.py
===================================================================
--- document/doc.py (revision 1055)
+++ document/doc.py (working copy)
@@ -38,6 +38,12 @@
import veusz.utils as utils
import veusz.setting as setting
+#try:
+import emf_export
+# hasemf = True
+#except ImportError:
+# hasemf = False
+
class Document( qt4.QObject ):
"""Document class for holding the graph data.
@@ -506,6 +512,22 @@
painter.end()
pic.save(filename)
+ def _exportEMF(self, filename, page):
+ """Export document as EMF."""
+ pixmap = qt4.QPixmap(1,1)
+ painter = Painter(pixmap, scaling=1., dpi=75.)
+ width, height = self.basewidget.getSize(painter)
+ painter.end()
+
+ print width, height
+
+ paintdev = emf_export.EMFPaintDevice(width/75., height/75.,
+ dpi=75)
+ painter = Painter(paintdev)
+ self.basewidget.draw( painter, page )
+ painter.end()
+ paintdev.paintEngine().saveFile(filename)
+
def export(self, filename, pagenumber, color=True, dpi=100,
antialias=True, quality=85):
"""Export the figure to the filename."""
@@ -525,6 +547,9 @@
elif ext == '.pic':
self._exportPIC(filename, pagenumber)
+ elif ext == '.emf':
+ self._exportEMF(filename, pagenumber)
+
else:
raise RuntimeError, "File type '%s' not supported" % ext
_______________________________________________
Veusz-discuss mailing list
[email protected]
https://mail.gna.org/listinfo/veusz-discuss