Compare commits
3 Commits
7a77199fcf
...
7197df9f5c
| Author | SHA1 | Date | |
|---|---|---|---|
| 7197df9f5c | |||
| f67e1fb0da | |||
| f7833ed4b9 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
label/
|
label/
|
||||||
build/
|
build/
|
||||||
|
dist/
|
||||||
logs/
|
logs/
|
||||||
pdf_backup/
|
pdf_backup/
|
||||||
conf/ghostscript/
|
conf/ghostscript/
|
||||||
@@ -9,4 +10,6 @@ __pycache__/
|
|||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
*.pyd
|
*.pyd
|
||||||
|
build_output.log
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ if errorlevel 1 (
|
|||||||
exit /b 1
|
exit /b 1
|
||||||
)
|
)
|
||||||
|
|
||||||
echo [1/5] Checking Python installation...
|
echo [1/6] Checking Python installation...
|
||||||
python --version
|
python --version
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
REM Upgrade pip
|
REM Upgrade pip
|
||||||
echo [2/5] Upgrading pip, setuptools, and wheel...
|
echo [2/6] Upgrading pip, setuptools, and wheel...
|
||||||
python -m pip install --upgrade pip setuptools wheel
|
python -m pip install --upgrade pip setuptools wheel
|
||||||
if errorlevel 1 (
|
if errorlevel 1 (
|
||||||
echo ERROR: Failed to upgrade pip
|
echo ERROR: Failed to upgrade pip
|
||||||
@@ -50,7 +50,7 @@ echo.
|
|||||||
|
|
||||||
REM Copy GhostScript binaries for bundling (optional but strongly recommended)
|
REM Copy GhostScript binaries for bundling (optional but strongly recommended)
|
||||||
echo [4/6] Preparing GhostScript for bundling...
|
echo [4/6] Preparing GhostScript for bundling...
|
||||||
call prepare_ghostscript.bat
|
python prepare_ghostscript.py
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
REM Check that SumatraPDF.exe exists before building
|
REM Check that SumatraPDF.exe exists before building
|
||||||
|
|||||||
@@ -1,20 +1,110 @@
|
|||||||
from reportlab.lib.pagesizes import landscape
|
"""
|
||||||
from reportlab.lib.utils import ImageReader
|
check_pdf_size.py – verify that a PDF's page dimensions match 35 mm × 25 mm.
|
||||||
from reportlab.pdfgen import canvas
|
|
||||||
|
Usage:
|
||||||
|
python check_pdf_size.py [path/to/label.pdf]
|
||||||
|
|
||||||
|
If no path is given the script operates on every PDF found in pdf_backup/.
|
||||||
|
Exit code 0 = all OK, 1 = mismatch or error.
|
||||||
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
# Check the test PDF file
|
# ── Target dimensions ────────────────────────────────────────────────────────
|
||||||
if os.path.exists('test_label.pdf'):
|
TARGET_W_MM = 35.0 # width (landscape, wider side)
|
||||||
file_size = os.path.getsize('test_label.pdf')
|
TARGET_H_MM = 25.0 # height
|
||||||
print(f'test_label.pdf exists ({file_size} bytes)')
|
TOLERANCE_MM = 0.5 # ± 0.5 mm is acceptable rounding from PDF viewers
|
||||||
print(f'Expected: 35mm x 25mm landscape (99.2 x 70.9 points)')
|
|
||||||
print(f'')
|
PT_PER_MM = 72.0 / 25.4 # 1 mm in points
|
||||||
print(f'Open test_label.pdf in a PDF viewer to verify:')
|
|
||||||
print(f' - Page size should be wider than tall')
|
|
||||||
print(f' - Content should be correctly oriented')
|
def read_page_size_pt(pdf_path):
|
||||||
print(f'')
|
"""
|
||||||
print(f'In Adobe Reader: File > Properties > Description')
|
Return (width_pt, height_pt) of the first page of *pdf_path*.
|
||||||
print(f' Page size should show: 3.5 x 2.5 cm or 1.38 x 0.98 in')
|
Tries pypdf first, then pymupdf (fitz) as a fallback.
|
||||||
else:
|
Raises RuntimeError if neither library is available.
|
||||||
print('test_label.pdf not found')
|
"""
|
||||||
|
# ── pypdf ────────────────────────────────────────────────────────────────
|
||||||
|
try:
|
||||||
|
from pypdf import PdfReader # type: ignore
|
||||||
|
reader = PdfReader(pdf_path)
|
||||||
|
page = reader.pages[0]
|
||||||
|
w = float(page.mediabox.width)
|
||||||
|
h = float(page.mediabox.height)
|
||||||
|
return w, h
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ── pymupdf (fitz) ───────────────────────────────────────────────────────
|
||||||
|
try:
|
||||||
|
import fitz # type: ignore
|
||||||
|
doc = fitz.open(pdf_path)
|
||||||
|
rect = doc[0].rect
|
||||||
|
return rect.width, rect.height
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise RuntimeError(
|
||||||
|
"Install pypdf or pymupdf:\n"
|
||||||
|
" pip install pypdf\n"
|
||||||
|
" pip install pymupdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_file(pdf_path):
|
||||||
|
"""Print a pass/fail line for one PDF. Returns True if dimensions match."""
|
||||||
|
if not os.path.exists(pdf_path):
|
||||||
|
print(f" MISS {pdf_path} (file not found)")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
w_pt, h_pt = read_page_size_pt(pdf_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ERR {pdf_path} ({e})")
|
||||||
|
return False
|
||||||
|
|
||||||
|
w_mm = w_pt / PT_PER_MM
|
||||||
|
h_mm = h_pt / PT_PER_MM
|
||||||
|
|
||||||
|
w_ok = abs(w_mm - TARGET_W_MM) <= TOLERANCE_MM
|
||||||
|
h_ok = abs(h_mm - TARGET_H_MM) <= TOLERANCE_MM
|
||||||
|
ok = w_ok and h_ok
|
||||||
|
|
||||||
|
status = "PASS" if ok else "FAIL"
|
||||||
|
print(
|
||||||
|
f" {status} {os.path.basename(pdf_path)}"
|
||||||
|
f" {w_mm:.2f}×{h_mm:.2f} mm"
|
||||||
|
f" (target {TARGET_W_MM}×{TARGET_H_MM} mm ±{TOLERANCE_MM} mm)"
|
||||||
|
)
|
||||||
|
return ok
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
targets = sys.argv[1:]
|
||||||
|
|
||||||
|
if not targets:
|
||||||
|
backup_dir = os.path.join(os.path.dirname(__file__), "pdf_backup")
|
||||||
|
if os.path.isdir(backup_dir):
|
||||||
|
targets = [
|
||||||
|
os.path.join(backup_dir, f)
|
||||||
|
for f in sorted(os.listdir(backup_dir))
|
||||||
|
if f.lower().endswith(".pdf")
|
||||||
|
]
|
||||||
|
if not targets:
|
||||||
|
# fall back to test_label.pdf in cwd
|
||||||
|
targets = ["test_label.pdf"]
|
||||||
|
|
||||||
|
print(f"Checking {len(targets)} PDF(s)…")
|
||||||
|
results = [check_file(p) for p in targets]
|
||||||
|
|
||||||
|
total = len(results)
|
||||||
|
passed = sum(results)
|
||||||
|
failed = total - passed
|
||||||
|
print(f"\n {passed}/{total} passed" + (f", {failed} FAILED" if failed else ""))
|
||||||
|
sys.exit(0 if failed == 0 else 1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
|
|||||||
@@ -990,7 +990,7 @@ printer = PDF
|
|||||||
f'Printing {idx+1} of {count}...'
|
f'Printing {idx+1} of {count}...'
|
||||||
), 0)
|
), 0)
|
||||||
|
|
||||||
success = print_label_standalone(label_text, printer, preview=0, use_pdf=True, svg_template=template_path)
|
success = print_label_standalone(label_text, printer, preview=0, svg_template=template_path)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
all_success = False
|
all_success = False
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ set GS_BIN_SRC=
|
|||||||
set GS_LIB_SRC=
|
set GS_LIB_SRC=
|
||||||
set GS_EXE=
|
set GS_EXE=
|
||||||
|
|
||||||
|
REM Pre-assign ProgramFiles(x86) to avoid batch parser choking on the parentheses
|
||||||
|
set "PF64=%ProgramFiles%"
|
||||||
|
set "PF32=%ProgramFiles(x86)%"
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ============================================================
|
echo ============================================================
|
||||||
echo GhostScript Bundle Preparation
|
echo GhostScript Bundle Preparation
|
||||||
@@ -29,7 +33,7 @@ echo ============================================================
|
|||||||
echo.
|
echo.
|
||||||
|
|
||||||
REM ---- Search for GhostScript in both Program Files locations ----
|
REM ---- Search for GhostScript in both Program Files locations ----
|
||||||
for %%P in ("%ProgramFiles%" "%ProgramFiles(x86)%") do (
|
for %%P in ("%PF64%" "%PF32%") do (
|
||||||
if exist "%%~P\gs" (
|
if exist "%%~P\gs" (
|
||||||
for /d %%V in ("%%~P\gs\gs*") do (
|
for /d %%V in ("%%~P\gs\gs*") do (
|
||||||
if exist "%%~V\bin\gswin64c.exe" (
|
if exist "%%~V\bin\gswin64c.exe" (
|
||||||
|
|||||||
115
prepare_ghostscript.py
Normal file
115
prepare_ghostscript.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
"""
|
||||||
|
prepare_ghostscript.py
|
||||||
|
Finds the system GhostScript installation and copies the files needed for
|
||||||
|
bundling into conf/ghostscript/
|
||||||
|
|
||||||
|
Files copied:
|
||||||
|
conf/ghostscript/bin/gswin64c.exe (or gswin32c.exe)
|
||||||
|
conf/ghostscript/bin/gsdll64.dll (or gsdll32.dll)
|
||||||
|
conf/ghostscript/lib/*.ps (PostScript init files)
|
||||||
|
|
||||||
|
Run this ONCE before building the exe with build_windows.bat.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import glob
|
||||||
|
import sys
|
||||||
|
|
||||||
|
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
DEST_BIN = os.path.join(SCRIPT_DIR, "conf", "ghostscript", "bin")
|
||||||
|
DEST_LIB = os.path.join(SCRIPT_DIR, "conf", "ghostscript", "lib")
|
||||||
|
|
||||||
|
SEARCH_ROOTS = [
|
||||||
|
os.environ.get("ProgramFiles", r"C:\Program Files"),
|
||||||
|
os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)"),
|
||||||
|
os.environ.get("ProgramW6432", r"C:\Program Files"),
|
||||||
|
]
|
||||||
|
|
||||||
|
FLAVOURS = [
|
||||||
|
("gswin64c.exe", "gsdll64.dll"),
|
||||||
|
("gswin32c.exe", "gsdll32.dll"),
|
||||||
|
]
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print(" GhostScript Bundle Preparation")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def find_ghostscript():
|
||||||
|
"""Return (bin_dir, lib_dir, exe_name, dll_name) or None."""
|
||||||
|
for root in dict.fromkeys(r for r in SEARCH_ROOTS if r): # unique, preserve order
|
||||||
|
gs_root = os.path.join(root, "gs")
|
||||||
|
if not os.path.isdir(gs_root):
|
||||||
|
continue
|
||||||
|
# Each versioned sub-folder, newest first
|
||||||
|
versions = sorted(
|
||||||
|
[d for d in glob.glob(os.path.join(gs_root, "gs*")) if os.path.isdir(d)],
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
for ver_dir in versions:
|
||||||
|
bin_dir = os.path.join(ver_dir, "bin")
|
||||||
|
lib_dir = os.path.join(ver_dir, "lib")
|
||||||
|
for exe, dll in FLAVOURS:
|
||||||
|
if os.path.isfile(os.path.join(bin_dir, exe)):
|
||||||
|
print(f"Found GhostScript: {ver_dir}")
|
||||||
|
return bin_dir, lib_dir, exe, dll
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
result = find_ghostscript()
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
print("WARNING: GhostScript is NOT installed on this machine.")
|
||||||
|
print()
|
||||||
|
print(" The exe will still build successfully, but GhostScript will")
|
||||||
|
print(" NOT be bundled. On the target machine the app will fall back")
|
||||||
|
print(" to SumatraPDF for printing (which may produce dotted output).")
|
||||||
|
print()
|
||||||
|
print(" To get sharp vector-quality printing, install GhostScript:")
|
||||||
|
print(" https://ghostscript.com/releases/gsdnld.html")
|
||||||
|
print(" Then re-run this script before building.")
|
||||||
|
print()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
src_bin, src_lib, exe_name, dll_name = result
|
||||||
|
|
||||||
|
# Create destination folders
|
||||||
|
os.makedirs(DEST_BIN, exist_ok=True)
|
||||||
|
os.makedirs(DEST_LIB, exist_ok=True)
|
||||||
|
|
||||||
|
# Copy executable and DLL
|
||||||
|
print("Copying GhostScript binaries...")
|
||||||
|
for fname in (exe_name, dll_name):
|
||||||
|
src = os.path.join(src_bin, fname)
|
||||||
|
dst = os.path.join(DEST_BIN, fname)
|
||||||
|
if os.path.isfile(src):
|
||||||
|
shutil.copy2(src, dst)
|
||||||
|
print(f" + {fname}")
|
||||||
|
else:
|
||||||
|
print(f" WARNING: {fname} not found in {src_bin}")
|
||||||
|
|
||||||
|
# Copy lib/ (PostScript init files .ps)
|
||||||
|
print()
|
||||||
|
print("Copying GhostScript lib (PostScript init files)...")
|
||||||
|
ps_files = glob.glob(os.path.join(src_lib, "*.ps"))
|
||||||
|
for ps in ps_files:
|
||||||
|
shutil.copy2(ps, DEST_LIB)
|
||||||
|
print(f" + {len(ps_files)} .ps files copied from {src_lib}")
|
||||||
|
|
||||||
|
# Report
|
||||||
|
bin_count = len(os.listdir(DEST_BIN))
|
||||||
|
lib_count = len(os.listdir(DEST_LIB))
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print(" GhostScript prepared successfully in conf\\ghostscript\\")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
print(f" bin\\ : {bin_count} file(s)")
|
||||||
|
print(f" lib\\ : {lib_count} file(s)")
|
||||||
|
print()
|
||||||
|
print("GhostScript will be embedded into LabelPrinter.exe at build time.")
|
||||||
|
print()
|
||||||
326
print_label.py
326
print_label.py
@@ -1,6 +1,3 @@
|
|||||||
from PIL import Image, ImageDraw, ImageFont
|
|
||||||
import barcode
|
|
||||||
from barcode.writer import ImageWriter
|
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -89,119 +86,6 @@ def get_available_printers():
|
|||||||
return ["PDF"]
|
return ["PDF"]
|
||||||
|
|
||||||
|
|
||||||
def create_label_image(text):
|
|
||||||
"""
|
|
||||||
Create a label image with 3 rows: label + barcode for each field.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text (str): Combined text in format "SAP|CANTITATE|LOT" or single value
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
PIL.Image: The generated label image
|
|
||||||
"""
|
|
||||||
# Parse the text input
|
|
||||||
parts = text.split('|') if '|' in text else [text, '', '']
|
|
||||||
sap_nr = parts[0].strip() if len(parts) > 0 else ''
|
|
||||||
cantitate = parts[1].strip() if len(parts) > 1 else ''
|
|
||||||
lot_number = parts[2].strip() if len(parts) > 2 else ''
|
|
||||||
|
|
||||||
# Label dimensions (narrower, 3 rows)
|
|
||||||
label_width = 800 # 8 cm
|
|
||||||
label_height = 600 # 6 cm
|
|
||||||
|
|
||||||
# Create canvas
|
|
||||||
label_img = Image.new('RGB', (label_width, label_height), 'white')
|
|
||||||
draw = ImageDraw.Draw(label_img)
|
|
||||||
|
|
||||||
# Row setup - 3 equal rows
|
|
||||||
row_height = label_height // 3
|
|
||||||
left_margin = 15
|
|
||||||
row_spacing = 3
|
|
||||||
|
|
||||||
# Fonts
|
|
||||||
font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
|
|
||||||
try:
|
|
||||||
label_font = ImageFont.truetype(font_path, 16)
|
|
||||||
value_font = ImageFont.truetype(font_path, 14)
|
|
||||||
except IOError:
|
|
||||||
label_font = ImageFont.load_default()
|
|
||||||
value_font = ImageFont.load_default()
|
|
||||||
|
|
||||||
# Data for 3 rows
|
|
||||||
rows_data = [
|
|
||||||
("SAP-Nr", sap_nr),
|
|
||||||
("Cantitate", cantitate),
|
|
||||||
("Lot Nr", lot_number),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Generate barcodes first
|
|
||||||
CODE128 = barcode.get_barcode_class('code128')
|
|
||||||
writer_options = {
|
|
||||||
"write_text": False,
|
|
||||||
"module_width": 0.4,
|
|
||||||
"module_height": 8,
|
|
||||||
"quiet_zone": 2,
|
|
||||||
"font_size": 0
|
|
||||||
}
|
|
||||||
|
|
||||||
barcode_images = []
|
|
||||||
for _, value in rows_data:
|
|
||||||
if value:
|
|
||||||
try:
|
|
||||||
code = CODE128(value[:25], writer=ImageWriter())
|
|
||||||
filename = code.save('temp_barcode', options=writer_options)
|
|
||||||
barcode_img = Image.open(filename)
|
|
||||||
barcode_images.append(barcode_img)
|
|
||||||
except:
|
|
||||||
barcode_images.append(None)
|
|
||||||
else:
|
|
||||||
barcode_images.append(None)
|
|
||||||
|
|
||||||
# Draw each row with label and barcode
|
|
||||||
for idx, ((label_name, value), barcode_img) in enumerate(zip(rows_data, barcode_images)):
|
|
||||||
row_y = idx * row_height
|
|
||||||
|
|
||||||
# Draw label name
|
|
||||||
draw.text(
|
|
||||||
(left_margin, row_y + 3),
|
|
||||||
label_name,
|
|
||||||
fill='black',
|
|
||||||
font=label_font
|
|
||||||
)
|
|
||||||
|
|
||||||
# Draw barcode if available
|
|
||||||
if barcode_img:
|
|
||||||
# Resize barcode to fit in row width
|
|
||||||
barcode_width = label_width - left_margin - 10
|
|
||||||
barcode_height = row_height - 25
|
|
||||||
# Use high-quality resampling for crisp barcodes
|
|
||||||
try:
|
|
||||||
# Try newer Pillow API first
|
|
||||||
from PIL.Image import Resampling
|
|
||||||
barcode_resized = barcode_img.resize((barcode_width, barcode_height), Resampling.LANCZOS)
|
|
||||||
except (ImportError, AttributeError):
|
|
||||||
# Fallback for older Pillow versions
|
|
||||||
barcode_resized = barcode_img.resize((barcode_width, barcode_height), Image.LANCZOS)
|
|
||||||
label_img.paste(barcode_resized, (left_margin, row_y + 20))
|
|
||||||
else:
|
|
||||||
# Fallback: show value as text
|
|
||||||
draw.text(
|
|
||||||
(left_margin, row_y + 25),
|
|
||||||
value if value else "(empty)",
|
|
||||||
fill='black',
|
|
||||||
font=value_font
|
|
||||||
)
|
|
||||||
|
|
||||||
# Clean up temporary barcode files
|
|
||||||
try:
|
|
||||||
if os.path.exists('temp_barcode.png'):
|
|
||||||
os.remove('temp_barcode.png')
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return label_img
|
|
||||||
|
|
||||||
|
|
||||||
def create_label_pdf(text, svg_template=None):
|
def create_label_pdf(text, svg_template=None):
|
||||||
"""
|
"""
|
||||||
Create a high-quality PDF label with 3 rows: label + barcode for each field.
|
Create a high-quality PDF label with 3 rows: label + barcode for each field.
|
||||||
@@ -259,94 +143,6 @@ def create_label_pdf(text, svg_template=None):
|
|||||||
return generator.create_label_pdf(article, nr_art, serial, pdf_filename, image_path, selected_template)
|
return generator.create_label_pdf(article, nr_art, serial, pdf_filename, image_path, selected_template)
|
||||||
|
|
||||||
|
|
||||||
def configure_printer_quality(printer_name, width_mm=35, height_mm=25):
|
|
||||||
"""
|
|
||||||
Configure printer for high quality label printing (Windows only).
|
|
||||||
Sets paper size, orientation, and QUALITY settings.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
printer_name (str): Name of the printer
|
|
||||||
width_mm (int): Label width in millimeters (default 35)
|
|
||||||
height_mm (int): Label height in millimeters (default 25)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if successful
|
|
||||||
"""
|
|
||||||
if SYSTEM != "Windows" or not WIN32_AVAILABLE:
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
import win32print
|
|
||||||
import pywintypes
|
|
||||||
|
|
||||||
hprinter = win32print.OpenPrinter(printer_name)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Get current printer properties
|
|
||||||
props = win32print.GetPrinter(hprinter, 2)
|
|
||||||
devmode = props.get("pDevMode")
|
|
||||||
|
|
||||||
if devmode is None:
|
|
||||||
print("Could not get printer DEVMODE")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# CRITICAL: Set print quality to HIGHEST
|
|
||||||
# This prevents dotted/pixelated text
|
|
||||||
try:
|
|
||||||
devmode.PrintQuality = 600 # 600 DPI (high quality)
|
|
||||||
except:
|
|
||||||
try:
|
|
||||||
devmode.PrintQuality = 4 # DMRES_HIGH
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Set custom paper size
|
|
||||||
try:
|
|
||||||
devmode.PaperSize = 256 # DMPAPER_USER (custom size)
|
|
||||||
devmode.PaperLength = height_mm * 10 # Height in 0.1mm units
|
|
||||||
devmode.PaperWidth = width_mm * 10 # Width in 0.1mm units
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 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 = 1 # Portrait = no rotation
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Set additional quality settings
|
|
||||||
try:
|
|
||||||
devmode.Color = 1 # Monochrome for labels
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
devmode.TTOption = 2 # DMTT_BITMAP - print TrueType as graphics (sharper)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Apply settings
|
|
||||||
try:
|
|
||||||
props["pDevMode"] = devmode
|
|
||||||
win32print.SetPrinter(hprinter, 2, props, 0)
|
|
||||||
print(f"Printer configured: {width_mm}x{height_mm}mm @ HIGH QUALITY")
|
|
||||||
return True
|
|
||||||
except Exception as set_err:
|
|
||||||
print(f"Could not apply printer settings: {set_err}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
finally:
|
|
||||||
win32print.ClosePrinter(hprinter)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Could not configure printer quality: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def find_ghostscript():
|
def find_ghostscript():
|
||||||
"""
|
"""
|
||||||
Find GhostScript executable. Search order:
|
Find GhostScript executable. Search order:
|
||||||
@@ -403,10 +199,8 @@ def print_pdf_with_ghostscript(pdf_path, printer_name):
|
|||||||
# -dTextAlphaBits=4 : anti-aliasing for text
|
# -dTextAlphaBits=4 : anti-aliasing for text
|
||||||
# -dGraphicsAlphaBits=4
|
# -dGraphicsAlphaBits=4
|
||||||
# -dNOSAFER : allow file access needed for fonts
|
# -dNOSAFER : allow file access needed for fonts
|
||||||
# -dDEVICEWIDTHPOINTS \
|
# The printer paper size and orientation are already configured
|
||||||
# -dDEVICEHEIGHTPOINTS : explicit label size (35mm x 25mm in pts)
|
# in the driver – do not override them here.
|
||||||
# -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 = [
|
cmd = [
|
||||||
gs_path,
|
gs_path,
|
||||||
"-dNOPAUSE",
|
"-dNOPAUSE",
|
||||||
@@ -416,9 +210,6 @@ def print_pdf_with_ghostscript(pdf_path, printer_name):
|
|||||||
"-r600",
|
"-r600",
|
||||||
"-dTextAlphaBits=4",
|
"-dTextAlphaBits=4",
|
||||||
"-dGraphicsAlphaBits=4",
|
"-dGraphicsAlphaBits=4",
|
||||||
"-dDEVICEWIDTHPOINTS=99.21", # 35 mm
|
|
||||||
"-dDEVICEHEIGHTPOINTS=70.87", # 25 mm
|
|
||||||
"-dFIXEDMEDIA",
|
|
||||||
f"-sOutputFile=%printer%{printer_name}",
|
f"-sOutputFile=%printer%{printer_name}",
|
||||||
pdf_path,
|
pdf_path,
|
||||||
]
|
]
|
||||||
@@ -472,19 +263,10 @@ def print_to_printer(printer_name, file_path):
|
|||||||
try:
|
try:
|
||||||
if WIN32_AVAILABLE:
|
if WIN32_AVAILABLE:
|
||||||
import win32print
|
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'):
|
if file_path.endswith('.pdf'):
|
||||||
# Try silent printing methods (no viewer opens)
|
# Try silent printing methods (no viewer opens)
|
||||||
import os
|
import os
|
||||||
import winreg
|
|
||||||
|
|
||||||
printed = False
|
printed = False
|
||||||
|
|
||||||
@@ -536,14 +318,9 @@ def print_to_printer(printer_name, file_path):
|
|||||||
try:
|
try:
|
||||||
print(f"Using SumatraPDF: {sumatra_path}")
|
print(f"Using SumatraPDF: {sumatra_path}")
|
||||||
print(f"Sending to printer: {printer_name}")
|
print(f"Sending to printer: {printer_name}")
|
||||||
# The printer DEVMODE has already been configured
|
# "noscale" = print the PDF at its exact page size.
|
||||||
# above (Portrait, 35mm×25mm, high quality).
|
# Do NOT add "landscape" – the printer driver
|
||||||
# "noscale" tells SumatraPDF to send the PDF
|
# already knows the orientation from its own settings.
|
||||||
# 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([
|
result = subprocess.run([
|
||||||
sumatra_path,
|
sumatra_path,
|
||||||
"-print-to",
|
"-print-to",
|
||||||
@@ -601,73 +378,41 @@ def print_to_printer(printer_name, file_path):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def print_label_standalone(value, printer, preview=0, use_pdf=True, svg_template=None):
|
def print_label_standalone(value, printer, preview=0, svg_template=None):
|
||||||
"""
|
"""
|
||||||
Print a label with the specified text on the specified printer.
|
Generate a PDF label, save it to pdf_backup/, and print it on the printer.
|
||||||
Always generates a PDF backup in pdf_backup and prints that PDF.
|
The printer's own paper size and orientation settings are used as-is.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
value (str): The text to print on the label
|
value (str): Label data in format "article;nr_art;serial;status"
|
||||||
printer (str): The name of the printer to use
|
printer (str): Printer name (or "PDF" to skip physical printing)
|
||||||
preview (int): 0 = no preview, 1-3 = 3s preview, >3 = 5s preview
|
preview (int): 0 = print immediately; 1-3 = 3 s delay; >3 = 5 s delay
|
||||||
use_pdf (bool): False to also generate a PNG if PDF generation fails
|
svg_template (str): Path to SVG template (optional; auto-selected if None)
|
||||||
svg_template (str): Path to specific SVG template to use (optional)
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if printing was successful, False otherwise
|
bool: True if sending to printer succeeded, False otherwise
|
||||||
"""
|
"""
|
||||||
# Track generated files
|
|
||||||
file_created = False
|
|
||||||
temp_file = None
|
|
||||||
pdf_file = None
|
pdf_file = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Debug output
|
# Step 1 – Generate and save PDF to pdf_backup/
|
||||||
print(f"Preview value: {preview}")
|
|
||||||
print(f"Preview type: {type(preview)}")
|
|
||||||
print(f"Using format: {'PDF' if use_pdf else 'PNG'}")
|
|
||||||
|
|
||||||
# Always generate a PDF backup and print that PDF for verification
|
|
||||||
try:
|
try:
|
||||||
pdf_file = create_label_pdf(value, svg_template)
|
pdf_file = create_label_pdf(value, svg_template)
|
||||||
if pdf_file and os.path.exists(pdf_file):
|
if pdf_file and os.path.exists(pdf_file):
|
||||||
print(f"PDF label created: {pdf_file}")
|
print(f"PDF label created: {pdf_file}")
|
||||||
print(f"PDF backup saved to: {pdf_file}")
|
|
||||||
else:
|
else:
|
||||||
print("PDF generation returned no file path")
|
print("PDF generation failed – no output file")
|
||||||
|
return False
|
||||||
except Exception as pdf_err:
|
except Exception as pdf_err:
|
||||||
print(f"PDF generation failed: {pdf_err}")
|
print(f"PDF generation error: {pdf_err}")
|
||||||
|
|
||||||
# Optionally also create the label image (PNG)
|
|
||||||
if not pdf_file or not os.path.exists(pdf_file):
|
|
||||||
if not use_pdf:
|
|
||||||
label_img = create_label_image(value)
|
|
||||||
temp_file = 'final_label.png'
|
|
||||||
label_img.save(temp_file)
|
|
||||||
print(f"PNG label created: {temp_file}")
|
|
||||||
else:
|
|
||||||
temp_file = pdf_file
|
|
||||||
|
|
||||||
file_created = True
|
|
||||||
|
|
||||||
if not temp_file or not os.path.exists(temp_file):
|
|
||||||
print("No label file created for printing")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Convert preview to int if it's a string
|
# Step 2 – Optional countdown before printing
|
||||||
if isinstance(preview, str):
|
if isinstance(preview, str):
|
||||||
preview = int(preview)
|
preview = int(preview)
|
||||||
|
|
||||||
if preview > 0: # Any value above 0 shows a preview message
|
if preview > 0:
|
||||||
# Calculate preview duration in seconds
|
preview_sec = 3 if 1 <= preview <= 3 else 5
|
||||||
if 1 <= preview <= 3:
|
print(f"Printing in {preview_sec} seconds… (Ctrl+C to cancel)")
|
||||||
preview_sec = 3 # 3 seconds
|
|
||||||
else: # preview > 3
|
|
||||||
preview_sec = 5 # 5 seconds
|
|
||||||
|
|
||||||
print(f"Printing in {preview_sec} seconds... (Press Ctrl+C to cancel)")
|
|
||||||
|
|
||||||
# Simple countdown timer using time.sleep
|
|
||||||
try:
|
try:
|
||||||
for i in range(preview_sec, 0, -1):
|
for i in range(preview_sec, 0, -1):
|
||||||
print(f" {i}...", end=" ", flush=True)
|
print(f" {i}...", end=" ", flush=True)
|
||||||
@@ -676,25 +421,20 @@ def print_label_standalone(value, printer, preview=0, use_pdf=True, svg_template
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nCancelled by user")
|
print("\nCancelled by user")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Print after preview
|
# Step 3 – Send to printer
|
||||||
print("Sending to printer...")
|
print("Sending to printer...")
|
||||||
return print_to_printer(printer, temp_file)
|
return print_to_printer(printer, pdf_file)
|
||||||
else:
|
|
||||||
print("Direct printing without preview...")
|
|
||||||
# Direct printing without preview (preview = 0)
|
|
||||||
return print_to_printer(printer, temp_file)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error printing label: {str(e)}")
|
print(f"Error printing label: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# This block always executes, ensuring cleanup
|
|
||||||
if pdf_file and os.path.exists(pdf_file):
|
if pdf_file and os.path.exists(pdf_file):
|
||||||
print("Cleanup complete - PDF backup saved to pdf_backup folder")
|
print("Cleanup complete – PDF backup saved to pdf_backup/")
|
||||||
else:
|
else:
|
||||||
print("Cleanup complete - label file retained for reference")
|
print("Cleanup complete")
|
||||||
|
|
||||||
|
|
||||||
# Main code removed - import this module or run as part of the Kivy GUI application
|
# Main code removed - import this module or run as part of the Kivy GUI application
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ class PDFLabelGenerator:
|
|||||||
"""
|
"""
|
||||||
self.label_width = label_width * cm
|
self.label_width = label_width * cm
|
||||||
self.label_height = label_height * cm
|
self.label_height = label_height * cm
|
||||||
# Force landscape: ensure width > height
|
# label_width (3.5 cm) > label_height (2.5 cm) → page is already landscape
|
||||||
self.page_size = landscape((self.label_height, self.label_width)) if self.label_width > self.label_height else (self.label_width, self.label_height)
|
self.page_size = (self.label_width, self.label_height)
|
||||||
self.dpi = dpi
|
self.dpi = dpi
|
||||||
self.margin = 1 * mm # Minimal margin
|
self.margin = 1 * mm # Minimal margin
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user