1
0
Fork 0
inkscape/share/extensions/print_win32_vector.py
Daniel Baumann 02d935e272
Adding upstream version 1.4.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 23:40:13 +02:00

334 lines
12 KiB
Python
Executable file

#!/usr/bin/env python3
# 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 struct
import inkex
from inkex import (
PathElement,
ShapeElement,
Rectangle,
Group,
Use,
Transform,
Line,
Circle,
Ellipse,
)
from inkex.paths import Path
if sys.platform.startswith("win"):
# The wintypes module raises a ValueError when imported on non-Windows systems
# for certain Python versions.
from ctypes import wintypes
myspool = ctypes.WinDLL("winspool.drv")
mygdi = ctypes.WinDLL("gdi32.dll")
class LOGBRUSH(ctypes.Structure):
_fields_ = [
("lbStyle", wintypes.UINT),
("lbColor", wintypes.COLORREF),
("lbHatch", wintypes.PULONG),
]
DM_IN_PROMPT = 4 # call printer property sheet
DM_OUT_BUFFER = 2 # write to DEVMODE structure
LOGPIXELSX = 88
LOGPIXELSY = 90
class DOCINFO(ctypes.Structure):
_fields_ = [
("cbSize", ctypes.c_int),
("lpszDocName", wintypes.LPCSTR),
("lpszOutput", wintypes.LPCSTR),
("lpszDatatype", wintypes.LPCSTR),
("fwType", wintypes.DWORD),
]
StartDocA = mygdi.StartDocA
StartDocA.argtypes = [wintypes.HDC, ctypes.POINTER(DOCINFO)]
StartDocA.restype = ctypes.c_int
EndDoc = mygdi.EndDoc
EndDoc.argtypes = [wintypes.HDC]
EndDoc.restype = ctypes.c_int
CreateDCA = mygdi.CreateDCA
CreateDCA.argtypes = [
wintypes.LPCSTR,
wintypes.LPCSTR,
wintypes.LPCSTR,
wintypes.LPCVOID,
]
CreateDCA.restype = wintypes.HDC
GetDefaultPrinterA = myspool.GetDefaultPrinterA
GetDefaultPrinterA.argtypes = [wintypes.LPSTR, wintypes.LPDWORD]
GetDefaultPrinterA.restype = wintypes.BOOL
OpenPrinterA = myspool.OpenPrinterA
OpenPrinterA.argtypes = [wintypes.LPSTR, wintypes.LPHANDLE, wintypes.LPCVOID]
OpenPrinterA.restype = wintypes.BOOL
ClosePrinter = myspool.ClosePrinter
ClosePrinter.argtypes = [wintypes.HANDLE]
ClosePrinter.restype = wintypes.BOOL
DocumentPropertiesA = myspool.DocumentPropertiesA
DocumentPropertiesA.argtypes = [
wintypes.HWND,
wintypes.HANDLE,
wintypes.LPSTR,
wintypes.LPVOID,
wintypes.LPVOID,
wintypes.DWORD,
]
DocumentPropertiesA.restype = wintypes.LONG
SelectObject = mygdi.SelectObject
SelectObject.argtypes = [wintypes.HDC, wintypes.HGDIOBJ]
SelectObject.restype = wintypes.HGDIOBJ
MoveToEx = mygdi.MoveToEx
MoveToEx.argtypes = [wintypes.HDC, ctypes.c_int, ctypes.c_int, wintypes.LPCVOID]
MoveToEx.restype = wintypes.BOOL
CreateBrushIndirect = mygdi.CreateBrushIndirect
CreateBrushIndirect.argtypes = [ctypes.POINTER(LOGBRUSH)]
CreateBrushIndirect.restype = wintypes.HBRUSH
CreatePen = mygdi.CreatePen
CreatePen.argtypes = [ctypes.c_int, ctypes.c_int, wintypes.COLORREF]
CreatePen.restype = wintypes.HPEN
BeginPath = mygdi.BeginPath
BeginPath.argtypes = [wintypes.HDC]
BeginPath.restype = wintypes.BOOL
EndPath = mygdi.EndPath
EndPath.argtypes = [wintypes.HDC]
EndPath.restype = wintypes.BOOL
FillPath = mygdi.FillPath
FillPath.argtypes = [wintypes.HDC]
FillPath.restype = wintypes.BOOL
PolyBezierTo = mygdi.PolyBezierTo
PolyBezierTo.argtypes = [wintypes.HDC, wintypes.LPCVOID, wintypes.DWORD]
GetDeviceCaps = mygdi.GetDeviceCaps
GetDeviceCaps.argtypes = [wintypes.HDC, ctypes.c_int]
GetDeviceCaps.restype = ctypes.c_int
class PrintWin32Vector(inkex.EffectExtension):
def __init__(self):
super(PrintWin32Vector, self).__init__()
def process_shape(self, node, mat):
"""Process shape"""
pen_color = 0 # Stroke color
fill_color = None # Fill color
pen_width = 1 # Pen width in printer pixels
# Win32 API expect colors in 0x00bbggrr format.
def to_bgr(color):
color = color.to_rgb()
return (color[2] << 16) + (color[1] << 8) + color[0]
if not isinstance(node, (PathElement, Rectangle, Line, Circle, Ellipse)):
return
# Very NB : If the pen width is greater than 1 then the output will Not be a vector output !
style = node.style
if style:
attr = style("stroke")
if attr is not None and isinstance(attr, inkex.Color):
pen_color = to_bgr(attr)
attr = style("fill")
if attr is not None and isinstance(attr, inkex.Color):
fill_color = to_bgr(attr)
if "stroke-width" in style:
pen_width = self.svg.to_dimensionless(style("stroke-width").strip())
pen_width = int(pen_width * self.scale_xy)
path = node.path.to_superpath().transform(mat @ node.transform)
hPen = CreatePen(0, pen_width, pen_color)
SelectObject(self.hDC, hPen)
self.emit_path(path)
if fill_color is not None:
brush = LOGBRUSH(0, fill_color, None)
hBrush = CreateBrushIndirect(ctypes.byref(brush))
SelectObject(self.hDC, hBrush)
BeginPath(self.hDC)
self.emit_path(path)
EndPath(self.hDC)
FillPath(self.hDC)
return
def emit_path(self, p):
for sub in p:
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])
PolyBezierTo(self.hDC, ctypes.addressof(points), 3 * (len(sub) - 1))
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):
trans = group.get("transform")
if trans:
self.groupmat.append(self.groupmat[-1] @ Transform(trans))
for node in group:
if not isinstance(node, ShapeElement) or not node.is_visible():
continue
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 = wintypes.DWORD()
GetDefaultPrinterA(None, ctypes.byref(pcchBuffer)) # get length of printer name
pname = ctypes.create_string_buffer(pcchBuffer.value)
GetDefaultPrinterA(pname, ctypes.byref(pcchBuffer)) # get printer name
hPrinter = wintypes.HANDLE()
if OpenPrinterA(pname.value, ctypes.byref(hPrinter), None) == 0:
return inkex.errormsg(_("Failed to open default printer"))
# get printer properties dialog
pcchBuffer = DocumentPropertiesA(0, hPrinter, pname, None, None, 0)
pDevMode = ctypes.create_string_buffer(
pcchBuffer + 100
) # allocate extra just in case
pcchBuffer = DocumentPropertiesA(
0,
hPrinter,
pname,
ctypes.byref(pDevMode),
None,
DM_IN_PROMPT + DM_OUT_BUFFER,
)
ClosePrinter(hPrinter)
if pcchBuffer != 1: # user clicked Cancel
exit()
# initialize print document
lpszDocName = self.doc_name()
docInfo = DOCINFO(
ctypes.sizeof(DOCINFO), ctypes.addressof(lpszDocName), 0, 0, 0
)
self.hDC = CreateDCA(None, pname, None, ctypes.byref(pDevMode))
if StartDocA(self.hDC, ctypes.byref(docInfo)) < 0:
exit() # user clicked Cancel
# Take the DPI value from the DC instead of extracting it from pDevMode, this
# method works when print drivers return device-independent quality values (e.g.
# DMRES_MEDIUM).
dpi_x = GetDeviceCaps(self.hDC, LOGPIXELSX)
dpi_y = GetDeviceCaps(self.hDC, LOGPIXELSY)
# Convert from dots-per-inch (DPI) to user units.
self.scale_x = dpi_x / self.svg.viewport_to_unit("1in")
self.scale_y = dpi_y / self.svg.viewport_to_unit("1in")
doc = self.document.getroot()
# Process viewBox height attribute to correct page scaling.
viewBox = self.svg.get_viewbox()
if viewBox and viewBox[2] and viewBox[3]:
doc_width = self.svg.viewbox_width
doc_height = self.svg.viewbox_height
self.scale_x *= doc_width / self.svg.viewport_to_unit(
self.svg.add_unit(viewBox[2])
)
self.scale_y *= doc_height / self.svg.viewport_to_unit(
self.svg.add_unit(viewBox[3])
)
self.scale_xy = (self.scale_x + self.scale_y) / 2
self.groupmat = [
Transform([[self.scale_x, 0.0, 0.0], [0.0, self.scale_y, 0.0]])
]
self.process_group(doc)
EndDoc(self.hDC)
if __name__ == "__main__":
PrintWin32Vector().run()