Fix blank label between copies: send all copies in single GDI print job (StartDoc/N pages/EndDoc) instead of N separate jobs - thermal printers eject blank label between rapid separate jobs

This commit is contained in:
2026-02-25 16:20:45 +02:00
parent be1b494527
commit 71ccdb7b96
3 changed files with 33 additions and 34 deletions

BIN
dist/LabelPrinter.exe vendored

Binary file not shown.

View File

@@ -982,23 +982,13 @@ printer = PDF
success = False success = False
all_success = True all_success = True
try: try:
# Print multiple copies if count > 1 # Send all copies in ONE print job to prevent blank labels
for i in range(count): # being ejected between separate jobs on thermal printers.
if count > 1: success = print_label_standalone(
Clock.schedule_once(lambda dt, idx=i: popup.content.children[0].text.replace( label_text, printer, preview=0,
f'Processing {count} label(s)...', svg_template=template_path, copies=count
f'Printing {idx+1} of {count}...' )
), 0) all_success = success
success = print_label_standalone(label_text, printer, preview=0, svg_template=template_path)
if not success:
all_success = False
break
# Small delay between prints for multiple copies
if i < count - 1:
time.sleep(0.5)
# Get the PDF filename that was created # Get the PDF filename that was created
# Files are saved to pdf_backup/ with timestamp # Files are saved to pdf_backup/ with timestamp

View File

@@ -158,15 +158,18 @@ 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 _print_pdf_windows(file_path, printer_name): def _print_pdf_windows(file_path, printer_name, copies=1):
""" """
Print a PDF on Windows using pymupdf (fitz) to render and win32print/GDI to send. Print a PDF on Windows using pymupdf (fitz) to render and win32print/GDI to send.
All copies are sent as multiple pages within ONE print job so the printer
never sees a gap (which causes blank labels on thermal printers).
No external application is launched. Falls back to SumatraPDF if pymupdf No external application is launched. Falls back to SumatraPDF if pymupdf
is unavailable. is unavailable.
Returns True on success, False on failure. Returns True on success, False on failure.
""" """
_write_print_log(f"_print_pdf_windows called: printer={printer_name!r} file={file_path!r}") copies = max(1, int(copies))
_write_print_log(f"_print_pdf_windows called: printer={printer_name!r} copies={copies} file={file_path!r}")
# ── Method 1: pure-Python GDI (pymupdf + win32print) ───────────────── # ── Method 1: pure-Python GDI (pymupdf + win32print) ─────────────────
try: try:
@@ -207,16 +210,18 @@ def _print_pdf_windows(file_path, printer_name):
f"rendered={pix.width}x{pix.height}px dest={dest_w}x{dest_h}px" f"rendered={pix.width}x{pix.height}px dest={dest_w}x{dest_h}px"
) )
# Draw at exact physical size NOT stretched to the driver's paper area # Draw at exact physical size NOT stretched to the driver's paper area.
# All copies go into ONE print job to prevent blank labels between jobs.
hdc.StartDoc(os.path.basename(file_path)) hdc.StartDoc(os.path.basename(file_path))
hdc.StartPage()
dib = ImageWin.Dib(img) dib = ImageWin.Dib(img)
dib.draw(hdc.GetHandleOutput(), (0, 0, dest_w, dest_h)) for copy_idx in range(copies):
hdc.EndPage() hdc.StartPage()
dib.draw(hdc.GetHandleOutput(), (0, 0, dest_w, dest_h))
hdc.EndPage()
hdc.EndDoc() hdc.EndDoc()
hdc.DeleteDC() hdc.DeleteDC()
_write_print_log(f"GDI print SUCCESS → {printer_name}") _write_print_log(f"GDI print SUCCESS → {printer_name} ({copies} cop{'y' if copies==1 else 'ies'} in 1 job)")
return True return True
except ImportError as ie: except ImportError as ie:
@@ -291,15 +296,16 @@ def _print_pdf_windows(file_path, printer_name):
return False return False
def print_to_printer(printer_name, file_path): def print_to_printer(printer_name, file_path, copies=1):
""" """
Print file to printer (cross-platform). Print file to printer (cross-platform).
Uses SumatraPDF for silent printing on Windows. Uses pymupdf+GDI for silent printing on Windows with all copies in one job.
Args: Args:
printer_name (str): Name of printer or "PDF" for PDF output printer_name (str): Name of printer or "PDF" for PDF output
file_path (str): Path to file to print file_path (str): Path to file to print
copies (int): Number of copies to print in a single print job
Returns: Returns:
bool: True if successful bool: True if successful
""" """
@@ -320,7 +326,7 @@ def print_to_printer(printer_name, file_path):
# Windows: Print PDF using Python GDI (pymupdf + win32print). # Windows: Print PDF using Python GDI (pymupdf + win32print).
# No external viewer is launched at any point. # No external viewer is launched at any point.
if file_path.endswith('.pdf'): if file_path.endswith('.pdf'):
return _print_pdf_windows(file_path, printer_name) return _print_pdf_windows(file_path, printer_name, copies=copies)
else: else:
subprocess.run(['notepad', '/p', file_path], subprocess.run(['notepad', '/p', file_path],
check=False, check=False,
@@ -344,20 +350,23 @@ def print_to_printer(printer_name, file_path):
return True return True
def print_label_standalone(value, printer, preview=0, svg_template=None): def print_label_standalone(value, printer, preview=0, svg_template=None, copies=1):
""" """
Generate a PDF label, save it to pdf_backup/, and print it on the printer. Generate a PDF label, save it to pdf_backup/, and print it on the printer.
The printer's own paper size and orientation settings are used as-is. All copies are sent in a single print job to avoid blank labels on thermal
printers that eject a label between separate jobs.
Args: Args:
value (str): Label data in format "article;nr_art;serial;status" value (str): Label data in format "article;nr_art;serial;status"
printer (str): Printer name (or "PDF" to skip physical printing) printer (str): Printer name (or "PDF" to skip physical printing)
preview (int): 0 = print immediately; 1-3 = 3 s delay; >3 = 5 s delay preview (int): 0 = print immediately; 1-3 = 3 s delay; >3 = 5 s delay
svg_template (str): Path to SVG template (optional; auto-selected if None) svg_template (str): Path to SVG template (optional; auto-selected if None)
copies (int): Number of copies to print in one job (default 1)
Returns: Returns:
bool: True if sending to printer succeeded, False otherwise bool: True if sending to printer succeeded, False otherwise
""" """
copies = max(1, int(copies))
pdf_file = None pdf_file = None
try: try:
# Step 1 Generate and save PDF to pdf_backup/ # Step 1 Generate and save PDF to pdf_backup/
@@ -388,9 +397,9 @@ def print_label_standalone(value, printer, preview=0, svg_template=None):
print("\nCancelled by user") print("\nCancelled by user")
return False return False
# Step 3 Send to printer # Step 3 Send to printer (all copies in one job)
print("Sending to printer...") print(f"Sending to printer ({copies} cop{'y' if copies==1 else 'ies'})...")
return print_to_printer(printer, pdf_file) return print_to_printer(printer, pdf_file, copies=copies)
except Exception as e: except Exception as e:
print(f"Error printing label: {str(e)}") print(f"Error printing label: {str(e)}")