This commit is contained in:
2026-02-23 17:07:00 +02:00
parent cbf324eb76
commit 7a77199fcf
7 changed files with 433 additions and 92 deletions

View File

@@ -308,9 +308,13 @@ def configure_printer_quality(printer_name, width_mm=35, height_mm=25):
except:
pass
# Set orientation to landscape
# Set orientation to PORTRAIT (1 = no rotation).
# For a 35mm × 25mm thermal label, Portrait means "print across the
# 35mm print-head width without rotating". Landscape (2) would
# rotate the output 90° CCW, which is exactly the reported
# "rotated-left" symptom so we must NOT use Landscape here.
try:
devmode.Orientation = 2 # Landscape
devmode.Orientation = 1 # Portrait = no rotation
except:
pass
@@ -343,6 +347,101 @@ def configure_printer_quality(printer_name, width_mm=35, height_mm=25):
return False
def find_ghostscript():
"""
Find GhostScript executable. Search order:
1. Bundled inside the PyInstaller .exe (ghostscript/bin/ in _MEIPASS)
2. System-level installation (C:\\Program Files\\gs\\...)
Returns the full path to gswin64c.exe / gswin32c.exe, or None.
"""
# 1 ── Bundled location (PyInstaller one-file build)
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
for exe_name in ['gswin64c.exe', 'gswin32c.exe']:
bundled = os.path.join(sys._MEIPASS, 'ghostscript', 'bin', exe_name)
if os.path.exists(bundled):
print(f"Using bundled GhostScript: {bundled}")
return bundled
# 2 ── System installation (newest version first)
for program_files in [r"C:\Program Files", r"C:\Program Files (x86)"]:
gs_base = os.path.join(program_files, "gs")
if os.path.exists(gs_base):
for version_dir in sorted(os.listdir(gs_base), reverse=True):
for exe_name in ["gswin64c.exe", "gswin32c.exe"]:
gs_path = os.path.join(gs_base, version_dir, "bin", exe_name)
if os.path.exists(gs_path):
return gs_path
return None
def print_pdf_with_ghostscript(pdf_path, printer_name):
"""
Print PDF via GhostScript mswinpr2 device for true vector quality.
GhostScript sends native vector data to the printer driver, avoiding
the low-DPI rasterisation that causes dotted/pixelated output.
Returns True on success, False if GhostScript is unavailable or fails.
"""
gs_path = find_ghostscript()
if not gs_path:
print("GhostScript not found skipping to SumatraPDF fallback.")
return False
# Build environment: if running as a bundled exe, point GS_LIB at the
# extracted lib/ folder so GhostScript can find its .ps init files.
env = os.environ.copy()
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
bundled_lib = os.path.join(sys._MEIPASS, 'ghostscript', 'lib')
if os.path.isdir(bundled_lib):
env['GS_LIB'] = bundled_lib
print(f"GS_LIB → {bundled_lib}")
try:
# -sDEVICE=mswinpr2 : native Windows printer device (vector path)
# -dNOPAUSE / -dBATCH : batch mode, no user prompts
# -r600 : 600 DPI matches typical thermal-printer head
# -dTextAlphaBits=4 : anti-aliasing for text
# -dGraphicsAlphaBits=4
# -dNOSAFER : allow file access needed for fonts
# -dDEVICEWIDTHPOINTS \
# -dDEVICEHEIGHTPOINTS : explicit label size (35mm x 25mm in pts)
# -dFIXEDMEDIA : do not auto-scale/rotate to a different size
# 35mm = 35/25.4*72 ≈ 99.21 pt, 25mm = 25/25.4*72 ≈ 70.87 pt
cmd = [
gs_path,
"-dNOPAUSE",
"-dBATCH",
"-sDEVICE=mswinpr2",
"-dNOSAFER",
"-r600",
"-dTextAlphaBits=4",
"-dGraphicsAlphaBits=4",
"-dDEVICEWIDTHPOINTS=99.21", # 35 mm
"-dDEVICEHEIGHTPOINTS=70.87", # 25 mm
"-dFIXEDMEDIA",
f"-sOutputFile=%printer%{printer_name}",
pdf_path,
]
print(f"Printing with GhostScript (vector quality) to: {printer_name}")
result = subprocess.run(
cmd,
check=True,
capture_output=True,
text=True,
timeout=60,
env=env,
creationflags=subprocess.CREATE_NO_WINDOW if os.name == "nt" else 0,
)
print(f"✅ Label sent via GhostScript: {printer_name}")
return True
except subprocess.CalledProcessError as e:
print(f"GhostScript print failed (exit {e.returncode}): {e.stderr.strip() if e.stderr else ''}")
return False
except Exception as e:
print(f"GhostScript error: {e}")
return False
def print_to_printer(printer_name, file_path):
"""
Print file to printer (cross-platform).
@@ -374,15 +473,34 @@ def print_to_printer(printer_name, file_path):
if WIN32_AVAILABLE:
import win32print
import win32api
# Configure printer DEVMODE BEFORE sending any job.
# This is critical: it sets Portrait orientation (no rotation)
# and maximum print quality so the 35mm×25mm PDF maps
# directly to the physical label without being auto-rotated
# by the driver (which caused the 90° "rotated left" symptom).
configure_printer_quality(printer_name)
if file_path.endswith('.pdf'):
# Try silent printing methods (no viewer opens)
import os
import winreg
printed = False
# Method 1: SumatraPDF (bundled inside exe or external)
# Method 1: GhostScript (best quality true vector path).
# Tried first regardless of whether we are running as a
# script or as the packaged .exe, because GhostScript is
# a system-level installation and will be present on the
# machine independently of how this app is launched.
# If GhostScript is not installed it returns False
# immediately and we fall through to SumatraPDF.
printed = print_pdf_with_ghostscript(file_path, printer_name)
if printed:
return True
# Method 2: SumatraPDF (bundled inside exe or external)
sumatra_paths = []
# Get the directory where this script/exe is running
@@ -418,11 +536,14 @@ def print_to_printer(printer_name, file_path):
try:
print(f"Using SumatraPDF: {sumatra_path}")
print(f"Sending to printer: {printer_name}")
# IMPORTANT: The PDF is already landscape (35mm wide x 25mm tall).
# Do NOT add "landscape" to print-settings — it would rotate an
# already-landscape PDF 90° again, printing it portrait.
# "noscale" alone tells SumatraPDF to honour the PDF page size
# exactly and not fit/stretch it to the printer paper.
# The printer DEVMODE has already been configured
# above (Portrait, 35mm×25mm, high quality).
# "noscale" tells SumatraPDF to send the PDF
# at its exact size without any shrink/fit.
# Do NOT add "landscape" here: the DEVMODE
# Portrait setting already matches the label
# orientation; adding landscape would tell the
# driver to rotate 90° again and undo the fix.
result = subprocess.run([
sumatra_path,
"-print-to",