#!/usr/bin/env python # coding=utf-8 # # Copyright (C) 2012 Alvin Penner, penner@vaxxine.com # # 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. # """ This extension will generate vector graphics printout, specifically for Windows GDI32. This is a modified version of the file dxf_outlines.py by Aaron Spike, aaron@ekips.org It will write only to the default printer. The printing preferences dialog will be called. In order to ensure a pure vector output, use a linewidth < 1 printer pixel - see http://www.lessanvaezi.com/changing-printer-settings-using-the-windows-api/ - get GdiPrintSample.zip at http://archive.msdn.microsoft.com/WindowsPrintSample """ import sys import ctypes import inkex from inkex import PathElement, Rectangle, Group, Use, Transform from inkex.paths import Path if sys.platform.startswith("win"): myspool = ctypes.WinDLL("winspool.drv") mygdi = ctypes.WinDLL("gdi32.dll") else: myspool = None mygdi = None LOGBRUSH = ctypes.c_long * 3 DM_IN_PROMPT = 4 # call printer property sheet DM_OUT_BUFFER = 2 # write to DEVMODE structure class PrintWin32Vector(inkex.EffectExtension): def __init__(self): super(PrintWin32Vector, self).__init__() self.visibleLayers = True # print only visible layers def process_shape(self, node, mat): """Process shape""" rgb = (0, 0, 0) # stroke color fillcolor = None # fill color stroke = 1 # pen width in printer pixels # Very NB : If the pen width is greater than 1 then the output will Not be a vector output ! style = node.style if style: if "stroke" in style: if ( style["stroke"] and style["stroke"] != "none" and style["stroke"][0:3] != "url" ): rgb = inkex.Color(style["stroke"]).to_rgb() if "stroke-width" in style: stroke = self.svg.unittouu(style["stroke-width"]) / self.svg.unittouu( "1px" ) stroke = int(stroke * self.scale) if "fill" in style: if ( style["fill"] and style["fill"] != "none" and style["fill"][0:3] != "url" ): fill = inkex.Color(style["fill"]).to_rgb() fillcolor = fill[0] + 256 * fill[1] + 256 * 256 * fill[2] color = rgb[0] + 256 * rgb[1] + 256 * 256 * rgb[2] if isinstance(node, PathElement): p = node.path.to_superpath() if not p: return elif isinstance(node, Rectangle): x = float(node.get("x")) y = float(node.get("y")) width = float(node.get("width")) height = float(node.get("height")) p = [[[x, y], [x, y], [x, y]]] p.append([[x + width, y], [x + width, y], [x + width, y]]) p.append( [ [x + width, y + height], [x + width, y + height], [x + width, y + height], ] ) p.append([[x, y + height], [x, y + height], [x, y + height]]) p.append([[x, y], [x, y], [x, y]]) p = [p] else: return mat += node.transform p = Path(p).transform(Transform(mat)).to_arrays() hPen = mygdi.CreatePen(0, stroke, color) mygdi.SelectObject(self.hDC, hPen) self.emit_path(p) if fillcolor is not None: brush = LOGBRUSH(0, fillcolor, 0) hBrush = mygdi.CreateBrushIndirect(ctypes.addressof(brush)) mygdi.SelectObject(self.hDC, hBrush) mygdi.BeginPath(self.hDC) self.emit_path(p) mygdi.EndPath(self.hDC) mygdi.FillPath(self.hDC) return def emit_path(self, p): for sub in p: mygdi.MoveToEx(self.hDC, int(sub[0][1][0]), int(sub[0][1][1]), None) POINTS = ctypes.c_long * (6 * (len(sub) - 1)) points = POINTS() for i in range(len(sub) - 1): points[6 * i] = int(sub[i][2][0]) points[6 * i + 1] = int(sub[i][2][1]) points[6 * i + 2] = int(sub[i + 1][0][0]) points[6 * i + 3] = int(sub[i + 1][0][1]) points[6 * i + 4] = int(sub[i + 1][1][0]) points[6 * i + 5] = int(sub[i + 1][1][1]) mygdi.PolyBezierTo(self.hDC, ctypes.addressof(points), 3 * (len(sub) - 1)) return def process_clone(self, node): trans = node.get("transform") x = node.get("x") y = node.get("y") mat = Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) if trans: mat *= Transform(trans) if x: mat *= Transform([[1.0, 0.0, float(x)], [0.0, 1.0, 0.0]]) if y: mat *= Transform([[1.0, 0.0, 0.0], [0.0, 1.0, float(y)]]) # push transform if trans or x or y: self.groupmat.append(Transform(self.groupmat[-1]) * mat) # get referenced node refnode = node.href if refnode is not None: if isinstance(refnode, inkex.Group): self.process_group(refnode) elif refnode.tag == "svg:use": self.process_clone(refnode) else: self.process_shape(refnode, self.groupmat[-1]) # pop transform if trans or x or y: self.groupmat.pop() def process_group(self, group): if isinstance(group, inkex.Layer): style = group.style if "display" in style: if style["display"] == "none" and self.visibleLayers: return trans = group.get("transform") if trans: self.groupmat.append(Transform(self.groupmat[-1]) * Transform(trans)) for node in group: if isinstance(node, Group): self.process_group(node) elif isinstance(node, Use): self.process_clone(node) else: self.process_shape(node, self.groupmat[-1]) if trans: self.groupmat.pop() def doc_name(self): docname = self.svg.xpath("@sodipodi:docname") if not docname: docname = ["New document 1"] return ctypes.create_string_buffer( ("Inkscape " + docname[0].split("\\")[-1]).encode("ascii", "replace") ) def effect(self): pcchBuffer = ctypes.c_long() myspool.GetDefaultPrinterA( None, ctypes.byref(pcchBuffer) ) # get length of printer name pname = ctypes.create_string_buffer(pcchBuffer.value) myspool.GetDefaultPrinterA(pname, ctypes.byref(pcchBuffer)) # get printer name hPrinter = ctypes.c_long() if myspool.OpenPrinterA(pname.value, ctypes.byref(hPrinter), None) == 0: return inkex.errormsg(_("Failed to open default printer")) # get printer properties dialog pcchBuffer = myspool.DocumentPropertiesA(0, hPrinter, pname, None, None, 0) pDevMode = ctypes.create_string_buffer( pcchBuffer + 100 ) # allocate extra just in case pcchBuffer = myspool.DocumentPropertiesA( 0, hPrinter, pname, ctypes.byref(pDevMode), None, DM_IN_PROMPT + DM_OUT_BUFFER, ) myspool.ClosePrinter(hPrinter) if pcchBuffer != 1: # user clicked Cancel exit() # initiallize print document lpszDocName = self.doc_name() DOCINFO = ctypes.c_long * 5 docInfo = DOCINFO(20, ctypes.addressof(lpszDocName), 0, 0, 0) self.hDC = mygdi.CreateDCA(None, pname, None, ctypes.byref(pDevMode)) if mygdi.StartDocA(self.hDC, ctypes.byref(docInfo)) < 0: exit() # user clicked Cancel self.scale = ( ord(pDevMode[58]) + 256.0 * ord(pDevMode[59]) ) / 96 # use PrintQuality from DEVMODE self.scale /= self.svg.unittouu("1px") h = self.svg.unittouu(self.svg.xpath("@height")[0]) doc = self.document.getroot() # process viewBox height attribute to correct page scaling viewBox = doc.get("viewBox") if viewBox: viewBox2 = viewBox.split(",") if len(viewBox2) < 4: viewBox2 = viewBox.split(" ") self.scale *= h / self.svg.unittouu(self.addDocumentUnit(viewBox2[3])) self.groupmat = [[[self.scale, 0.0, 0.0], [0.0, self.scale, 0.0]]] self.process_group(doc) mygdi.EndDoc(self.hDC) if __name__ == "__main__": PrintWin32Vector().run()