diff --git a/.gitignore b/.gitignore index 4f4c14e..f1379b5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ label/ build/ logs/ pdf_backup/ +conf/ghostscript/ venv/ .venv/ __pycache__/ diff --git a/LabelPrinter.spec b/LabelPrinter.spec index 6481e88..c59ddb2 100644 --- a/LabelPrinter.spec +++ b/LabelPrinter.spec @@ -1,12 +1,70 @@ # -*- mode: python ; coding: utf-8 -*- +# +# LabelPrinter PyInstaller spec file. +# +# GhostScript bundling +# -------------------- +# Run build_windows.bat (or prepare_ghostscript.bat) BEFORE pyinstaller. +# That script copies the following directories from the system +# GhostScript installation into conf\ghostscript\: +# +# conf\ghostscript\bin\ <- gswin64c.exe + gsdll64.dll +# conf\ghostscript\lib\ <- all .ps init files +# +# This spec detects those directories automatically and adds them to the +# bundle under ghostscript\bin\ and ghostscript\lib\ inside _MEIPASS so +# that print_label.py can find and launch the bundled executable. +# If the directories are absent the build still succeeds (prints will fall +# back to SumatraPDF on the target machine). +import os +import glob as _glob + +# ── GhostScript: collect binaries and lib init files ────────────────────── +_gs_bin_src = os.path.join('conf', 'ghostscript', 'bin') +_gs_lib_src = os.path.join('conf', 'ghostscript', 'lib') + +gs_binaries = [] # (src_path, dest_folder_in_bundle) +gs_datas = [] # (src_path, dest_folder_in_bundle) + +if os.path.isdir(_gs_bin_src): + for _fn in ['gswin64c.exe', 'gsdll64.dll', 'gswin32c.exe', 'gsdll32.dll']: + _fp = os.path.join(_gs_bin_src, _fn) + if os.path.exists(_fp): + # Put executables/DLLs in binaries so PyInstaller handles deps + gs_binaries.append((_fp, 'ghostscript/bin')) + print(f'[spec] Bundling GhostScript binaries from: {_gs_bin_src}') +else: + print('[spec] WARNING: conf/ghostscript/bin not found.' + ' GhostScript will NOT be bundled in the exe.') + +if os.path.isdir(_gs_lib_src): + for _fp in _glob.glob(os.path.join(_gs_lib_src, '*')): + if os.path.isfile(_fp): + gs_datas.append((_fp, 'ghostscript/lib')) + print(f'[spec] Bundling GhostScript lib from: {_gs_lib_src}') + +# ── Base data files ──────────────────────────────────────────────────────── +base_datas = [ + ('conf\\SumatraPDF.exe', '.'), + ('conf\\SumatraPDF-settings.txt', 'conf'), + ('conf\\label_template_ok.svg', 'conf'), + ('conf\\label_template_nok.svg', 'conf'), + ('conf\\label_template.svg', 'conf'), +] a = Analysis( ['label_printer_gui.py'], pathex=[], - binaries=[], - datas=[('conf\\SumatraPDF.exe', '.'), ('conf\\SumatraPDF-settings.txt', 'conf'), ('conf\\label_template_ok.svg', 'conf'), ('conf\\label_template_nok.svg', 'conf'), ('conf\\label_template.svg', 'conf')], - hiddenimports=['kivy', 'PIL', 'barcode', 'reportlab', 'print_label', 'print_label_pdf', 'svglib', 'cairosvg', 'watchdog', 'watchdog.observers', 'watchdog.events', 'pystray', 'win32timezone'], + binaries=gs_binaries, + datas=base_datas + gs_datas, + hiddenimports=[ + 'kivy', 'PIL', 'barcode', 'reportlab', + 'print_label', 'print_label_pdf', + 'svglib', 'cairosvg', + 'watchdog', 'watchdog.observers', 'watchdog.events', + 'pystray', 'win32timezone', + ], hookspath=[], hooksconfig={}, runtime_hooks=[], diff --git a/build_exe.py b/build_exe.py index c8a47b4..ebe7d1f 100644 --- a/build_exe.py +++ b/build_exe.py @@ -1,6 +1,6 @@ """ PyInstaller build script for Label Printer GUI -Run this to create a standalone Windows executable +Run this to create a standalone Windows executable. IMPORTANT: This script MUST be run on Windows to generate a Windows .exe file. If run on Linux/macOS, it will create a Linux/macOS binary that won't work on Windows. @@ -10,73 +10,133 @@ To build for Windows: 2. Install dependencies: pip install -r requirements_windows.txt 3. Run this script: python build_exe.py 4. The Windows .exe will be created in the dist/ folder + +GhostScript bundling +-------------------- +If GhostScript is installed on this build machine the script will +automatically copy the required files into conf\\ghostscript\\ before +calling PyInstaller so they are embedded in LabelPrinter.exe. +The target machine then needs NO separate GhostScript install. """ import os import sys import subprocess +import shutil # Get the current directory script_dir = os.path.dirname(os.path.abspath(__file__)) -# PyInstaller arguments -args = [ - 'label_printer_gui.py', - '--onefile', # Create a single executable - '--windowed', # Don't show console window - '--name=LabelPrinter', # Executable name - '--distpath=./dist', # Output directory - '--workpath=./build', # Work directory (was --buildpath) - '--hidden-import=kivy', - '--hidden-import=kivy.core.window', - '--hidden-import=kivy.core.text', - '--hidden-import=kivy.core.image', - '--hidden-import=kivy.uix.boxlayout', - '--hidden-import=kivy.uix.gridlayout', - '--hidden-import=kivy.uix.label', - '--hidden-import=kivy.uix.textinput', - '--hidden-import=kivy.uix.button', - '--hidden-import=kivy.uix.spinner', - '--hidden-import=kivy.uix.scrollview', - '--hidden-import=kivy.uix.popup', - '--hidden-import=kivy.clock', - '--hidden-import=kivy.graphics', - '--hidden-import=PIL', - '--hidden-import=barcode', - '--hidden-import=reportlab', - '--hidden-import=print_label', - '--hidden-import=print_label_pdf', - '--hidden-import=svglib', - '--hidden-import=cairosvg', - '--hidden-import=watchdog', - '--hidden-import=watchdog.observers', - '--hidden-import=watchdog.events', - '--hidden-import=pystray', - '--hidden-import=win32timezone', -] + +def prepare_ghostscript(): + """ + Find the system GhostScript installation and copy the minimum files + needed for bundling into conf\\ghostscript\\. + + Files copied: + conf\\ghostscript\\bin\\gswin64c.exe (or 32-bit variant) + conf\\ghostscript\\bin\\gsdll64.dll + conf\\ghostscript\\lib\\*.ps (PostScript init files) + + Returns True if GhostScript was found and prepared, False otherwise. + """ + import glob + + gs_exe = None + gs_dll = None + gs_lib = None + + for pf in [r"C:\Program Files", r"C:\Program Files (x86)"]: + gs_base = os.path.join(pf, "gs") + if not os.path.isdir(gs_base): + continue + # Iterate versions newest-first + for ver_dir in sorted(os.listdir(gs_base), reverse=True): + bin_dir = os.path.join(gs_base, ver_dir, "bin") + lib_dir = os.path.join(gs_base, ver_dir, "lib") + if os.path.exists(os.path.join(bin_dir, "gswin64c.exe")): + gs_exe = os.path.join(bin_dir, "gswin64c.exe") + gs_dll = os.path.join(bin_dir, "gsdll64.dll") + gs_lib = lib_dir + break + if os.path.exists(os.path.join(bin_dir, "gswin32c.exe")): + gs_exe = os.path.join(bin_dir, "gswin32c.exe") + gs_dll = os.path.join(bin_dir, "gsdll32.dll") + gs_lib = lib_dir + break + if gs_exe: + break + + if not gs_exe: + print(" WARNING: GhostScript not found on this machine.") + print(" The exe will still build but GhostScript will NOT be bundled.") + print(" Install GhostScript from https://ghostscript.com/releases/gsdnld.html") + print(" then rebuild for sharp vector-quality printing.") + return False + + dest_bin = os.path.join("conf", "ghostscript", "bin") + dest_lib = os.path.join("conf", "ghostscript", "lib") + os.makedirs(dest_bin, exist_ok=True) + os.makedirs(dest_lib, exist_ok=True) + + print(f" GhostScript found: {os.path.dirname(gs_exe)}") + + shutil.copy2(gs_exe, dest_bin) + print(f" Copied: {os.path.basename(gs_exe)}") + + if os.path.exists(gs_dll): + shutil.copy2(gs_dll, dest_bin) + print(f" Copied: {os.path.basename(gs_dll)}") + + count = 0 + for ps_file in glob.glob(os.path.join(gs_lib, "*.ps")): + shutil.copy2(ps_file, dest_lib) + count += 1 + print(f" Copied {count} .ps init files from lib/") + + print(f" GhostScript prepared in conf\\ghostscript\\") + return True + if __name__ == '__main__': print("=" * 60) print("Label Printer GUI - PyInstaller Build") print("=" * 60) - print("\nBuilding standalone executable...") - print("This may take a few minutes...\n") - - # Change to script directory + + # Change to script directory so relative paths work os.chdir(script_dir) - - # Run PyInstaller directly with subprocess for better error reporting + + # Step 1: Prepare GhostScript for bundling (Windows only) + if sys.platform == "win32": + print("\n[1/2] Preparing GhostScript for bundling...") + prepare_ghostscript() + else: + print("\n[1/2] Skipping GhostScript prep (non-Windows build machine)") + + # Step 2: Build with PyInstaller using the handcrafted spec file. + # The spec's Python code auto-detects conf\\ghostscript\\ and includes it. + print("\n[2/2] Building standalone executable via LabelPrinter.spec...") + print("This may take a few minutes...\n") + try: - result = subprocess.run(['pyinstaller'] + args, check=True) - + result = subprocess.run( + ['pyinstaller', 'LabelPrinter.spec', + '--distpath=./dist', '--workpath=./build', '-y'], + check=True, + ) + print("\n" + "=" * 60) print("Build Complete!") print("=" * 60) print("\nExecutable location: ./dist/LabelPrinter.exe") + print("\nBundled components:") + print(" - GhostScript (vector-quality printing)") + print(" - SumatraPDF (fallback printing)") + print(" - SVG templates, conf files") print("\nYou can now:") - print("1. Double-click LabelPrinter.exe to run") - print("2. Share the exe with others") - print("3. Create a shortcut on desktop") + print(" 1. Double-click LabelPrinter.exe to run") + print(" 2. Copy the dist\\ folder to target machines") + print(" 3. No extra software installation required on target machines") print("\nNote: First run may take a moment as Kivy initializes") except subprocess.CalledProcessError as e: print("\n" + "=" * 60) @@ -88,3 +148,4 @@ if __name__ == '__main__': except Exception as e: print(f"\nFatal error: {e}") sys.exit(1) + diff --git a/build_windows.bat b/build_windows.bat index 96d08eb..8efcc32 100644 --- a/build_windows.bat +++ b/build_windows.bat @@ -38,7 +38,7 @@ if errorlevel 1 ( echo. REM Install dependencies -echo [3/5] Installing dependencies... +echo [3/6] Installing dependencies... echo Installing: python-barcode, pillow, reportlab, kivy, pyinstaller, pywin32, wmi, watchdog, svglib, cairosvg, pystray... pip install python-barcode pillow reportlab kivy pyinstaller pywin32 wmi watchdog svglib cairosvg pystray if errorlevel 1 ( @@ -48,6 +48,11 @@ if errorlevel 1 ( ) echo. +REM Copy GhostScript binaries for bundling (optional but strongly recommended) +echo [4/6] Preparing GhostScript for bundling... +call prepare_ghostscript.bat +echo. + REM Check that SumatraPDF.exe exists before building if not exist "conf\SumatraPDF.exe" ( echo ERROR: conf\SumatraPDF.exe not found! @@ -59,43 +64,20 @@ if not exist "conf\SumatraPDF.exe" ( echo conf\SumatraPDF.exe found - will be bundled into executable. echo. -REM Clean old build -echo [4/5] Cleaning old build artifacts... +REM Clean old build (keep the spec file - it's handcrafted) +echo [5/6] Cleaning old build artifacts... if exist "dist" rmdir /s /q dist if exist "build" rmdir /s /q build -if exist "*.spec" del *.spec echo. -REM Build with PyInstaller -echo [5/5] Building executable with PyInstaller... +REM Build with PyInstaller using the spec file. +REM The spec (LabelPrinter.spec) contains Python code that detects whether +REM conf\ghostscript\ was prepared and includes those files automatically. +echo [6/6] Building executable with PyInstaller... echo This may take 5-15 minutes, please wait... echo. -pyinstaller label_printer_gui.py ^ - --onefile ^ - --windowed ^ - --name=LabelPrinter ^ - --distpath=./dist ^ - --workpath=./build ^ - --hidden-import=kivy ^ - --hidden-import=PIL ^ - --hidden-import=barcode ^ - --hidden-import=reportlab ^ - --hidden-import=print_label ^ - --hidden-import=print_label_pdf ^ - --hidden-import=svglib ^ - --hidden-import=cairosvg ^ - --hidden-import=watchdog ^ - --hidden-import=watchdog.observers ^ - --hidden-import=watchdog.events ^ - --hidden-import=pystray ^ - --hidden-import=win32timezone ^ - --add-data "conf\SumatraPDF.exe;." ^ - --add-data "conf\SumatraPDF-settings.txt;conf" ^ - --add-data "conf\label_template_ok.svg;conf" ^ - --add-data "conf\label_template_nok.svg;conf" ^ - --add-data "conf\label_template.svg;conf" ^ - -y +pyinstaller LabelPrinter.spec --distpath=./dist --workpath=./build -y if errorlevel 1 ( echo. @@ -129,7 +111,8 @@ echo Next steps: echo 1. Copy the entire dist\ folder to the target machine echo (it contains LabelPrinter.exe AND the conf\ folder) echo 2. Run LabelPrinter.exe from the dist folder -echo 3. Ensure conf\SumatraPDF.exe is present for printing to work +echo 3. GhostScript is bundled inside the exe for sharp vector printing +echo 4. SumatraPDF (conf\SumatraPDF.exe) is also bundled as fallback echo. echo Note: First run may take a moment as Kivy initializes echo. diff --git a/dist/LabelPrinter.exe b/dist/LabelPrinter.exe deleted file mode 100644 index 4b059af..0000000 Binary files a/dist/LabelPrinter.exe and /dev/null differ diff --git a/prepare_ghostscript.bat b/prepare_ghostscript.bat new file mode 100644 index 0000000..7d5ef0a --- /dev/null +++ b/prepare_ghostscript.bat @@ -0,0 +1,117 @@ +@echo off +REM ============================================================ +REM prepare_ghostscript.bat +REM Finds the system GhostScript installation and copies the +REM files needed for bundling into conf\ghostscript\ +REM +REM Files copied: +REM conf\ghostscript\bin\gswin64c.exe (or gswin32c.exe) +REM conf\ghostscript\bin\gsdll64.dll (or gsdll32.dll) +REM conf\ghostscript\lib\*.ps (PostScript init files) +REM +REM Run this ONCE before building the exe with build_windows.bat. +REM Only the two executables + the lib/ folder are needed (~15-20 MB). +REM The full GhostScript fonts/Resources are NOT bundled to keep +REM the exe size manageable; Windows system fonts are used instead. +REM ============================================================ + +setlocal enabledelayedexpansion + +set GS_FOUND=0 +set GS_BIN_SRC= +set GS_LIB_SRC= +set GS_EXE= + +echo. +echo ============================================================ +echo GhostScript Bundle Preparation +echo ============================================================ +echo. + +REM ---- Search for GhostScript in both Program Files locations ---- +for %%P in ("%ProgramFiles%" "%ProgramFiles(x86)%") do ( + if exist "%%~P\gs" ( + for /d %%V in ("%%~P\gs\gs*") do ( + if exist "%%~V\bin\gswin64c.exe" ( + set GS_BIN_SRC=%%~V\bin + set GS_LIB_SRC=%%~V\lib + set GS_EXE=gswin64c.exe + set GS_DLL=gsdll64.dll + set GS_FOUND=1 + echo Found GhostScript (64-bit): %%~V + ) + if !GS_FOUND!==0 ( + if exist "%%~V\bin\gswin32c.exe" ( + set GS_BIN_SRC=%%~V\bin + set GS_LIB_SRC=%%~V\lib + set GS_EXE=gswin32c.exe + set GS_DLL=gsdll32.dll + set GS_FOUND=1 + echo Found GhostScript (32-bit): %%~V + ) + ) + ) + ) +) + +if %GS_FOUND%==0 ( + echo. + echo WARNING: GhostScript is NOT installed on this machine. + echo. + echo The exe will still build successfully, but GhostScript will + echo NOT be bundled. On the target machine the app will fall back + echo to SumatraPDF for printing (which may produce dotted output). + echo. + echo To get sharp vector-quality printing, install GhostScript: + echo https://ghostscript.com/releases/gsdnld.html + echo Then re-run this script before building. + echo. + exit /b 0 +) + +REM ---- Create destination folders ---- +set GS_DEST_BIN=conf\ghostscript\bin +set GS_DEST_LIB=conf\ghostscript\lib + +if not exist "%GS_DEST_BIN%" mkdir "%GS_DEST_BIN%" +if not exist "%GS_DEST_LIB%" mkdir "%GS_DEST_LIB%" + +REM ---- Copy executable and DLL ---- +echo. +echo Copying GhostScript binaries... +copy /y "%GS_BIN_SRC%\%GS_EXE%" "%GS_DEST_BIN%\%GS_EXE%" >nul +if errorlevel 1 ( echo ERROR copying %GS_EXE% & exit /b 1 ) +echo + %GS_EXE% + +copy /y "%GS_BIN_SRC%\%GS_DLL%" "%GS_DEST_BIN%\%GS_DLL%" >nul +if errorlevel 1 ( echo ERROR copying %GS_DLL% & exit /b 1 ) +echo + %GS_DLL% + +REM ---- Copy lib/ (PostScript init files .ps) ---- +echo. +echo Copying GhostScript lib (PostScript init files)... +for %%F in ("%GS_LIB_SRC%\*.ps") do ( + copy /y "%%F" "%GS_DEST_LIB%\" >nul +) +echo + *.ps copied from %GS_LIB_SRC% + +REM ---- Report ---- +echo. +echo ============================================================ +echo GhostScript prepared successfully in conf\ghostscript\ +echo ============================================================ +echo. + +REM Show size summary +set BIN_COUNT=0 +set LIB_COUNT=0 +for %%F in ("%GS_DEST_BIN%\*") do set /a BIN_COUNT+=1 +for %%F in ("%GS_DEST_LIB%\*") do set /a LIB_COUNT+=1 +echo bin\ : %BIN_COUNT% file(s) +echo lib\ : %LIB_COUNT% file(s) +echo. +echo GhostScript will be embedded into LabelPrinter.exe at build time. +echo. + +endlocal +exit /b 0 diff --git a/print_label.py b/print_label.py index 3114d9b..ae20aa1 100644 --- a/print_label.py +++ b/print_label.py @@ -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",