diff --git a/build_windows.bat b/build_windows.bat index 8efcc32..881799f 100644 --- a/build_windows.bat +++ b/build_windows.bat @@ -23,12 +23,12 @@ if errorlevel 1 ( exit /b 1 ) -echo [1/5] Checking Python installation... +echo [1/6] Checking Python installation... python --version echo. 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 if errorlevel 1 ( echo ERROR: Failed to upgrade pip diff --git a/check_pdf_size.py b/check_pdf_size.py index 3966087..dc615e7 100644 --- a/check_pdf_size.py +++ b/check_pdf_size.py @@ -1,20 +1,110 @@ -from reportlab.lib.pagesizes import landscape -from reportlab.lib.utils import ImageReader -from reportlab.pdfgen import canvas +""" +check_pdf_size.py – verify that a PDF's page dimensions match 35 mm × 25 mm. + +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 sys -# Check the test PDF file -if os.path.exists('test_label.pdf'): - file_size = os.path.getsize('test_label.pdf') - print(f'test_label.pdf exists ({file_size} bytes)') - print(f'Expected: 35mm x 25mm landscape (99.2 x 70.9 points)') - print(f'') - 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') - print(f'') - print(f'In Adobe Reader: File > Properties > Description') - print(f' Page size should show: 3.5 x 2.5 cm or 1.38 x 0.98 in') -else: - print('test_label.pdf not found') +# ── Target dimensions ──────────────────────────────────────────────────────── +TARGET_W_MM = 35.0 # width (landscape, wider side) +TARGET_H_MM = 25.0 # height +TOLERANCE_MM = 0.5 # ± 0.5 mm is acceptable rounding from PDF viewers + +PT_PER_MM = 72.0 / 25.4 # 1 mm in points + + +def read_page_size_pt(pdf_path): + """ + Return (width_pt, height_pt) of the first page of *pdf_path*. + Tries pypdf first, then pymupdf (fitz) as a fallback. + Raises RuntimeError if neither library is available. + """ + # ── 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() diff --git a/label_printer_gui.py b/label_printer_gui.py index 39e9086..a85a316 100644 --- a/label_printer_gui.py +++ b/label_printer_gui.py @@ -990,7 +990,7 @@ printer = PDF f'Printing {idx+1} of {count}...' ), 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: all_success = False diff --git a/print_label.py b/print_label.py index ae20aa1..1813e4a 100644 --- a/print_label.py +++ b/print_label.py @@ -1,6 +1,3 @@ -from PIL import Image, ImageDraw, ImageFont -import barcode -from barcode.writer import ImageWriter import time import os import sys @@ -89,119 +86,6 @@ def get_available_printers(): 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): """ 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) -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(): """ Find GhostScript executable. Search order: @@ -403,10 +199,8 @@ def print_pdf_with_ghostscript(pdf_path, printer_name): # -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 + # The printer paper size and orientation are already configured + # in the driver – do not override them here. cmd = [ gs_path, "-dNOPAUSE", @@ -416,9 +210,6 @@ def print_pdf_with_ghostscript(pdf_path, printer_name): "-r600", "-dTextAlphaBits=4", "-dGraphicsAlphaBits=4", - "-dDEVICEWIDTHPOINTS=99.21", # 35 mm - "-dDEVICEHEIGHTPOINTS=70.87", # 25 mm - "-dFIXEDMEDIA", f"-sOutputFile=%printer%{printer_name}", pdf_path, ] @@ -472,19 +263,10 @@ def print_to_printer(printer_name, file_path): try: 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 @@ -536,14 +318,9 @@ def print_to_printer(printer_name, file_path): try: print(f"Using SumatraPDF: {sumatra_path}") print(f"Sending to printer: {printer_name}") - # 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. + # "noscale" = print the PDF at its exact page size. + # Do NOT add "landscape" – the printer driver + # already knows the orientation from its own settings. result = subprocess.run([ sumatra_path, "-print-to", @@ -601,73 +378,41 @@ def print_to_printer(printer_name, file_path): 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. - Always generates a PDF backup in pdf_backup and prints that PDF. - + 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. + Args: - value (str): The text to print on the label - printer (str): The name of the printer to use - preview (int): 0 = no preview, 1-3 = 3s preview, >3 = 5s preview - use_pdf (bool): False to also generate a PNG if PDF generation fails - svg_template (str): Path to specific SVG template to use (optional) - + value (str): Label data in format "article;nr_art;serial;status" + printer (str): Printer name (or "PDF" to skip physical printing) + 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) + 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 - try: - # Debug output - 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 + # Step 1 – Generate and save PDF to pdf_backup/ try: pdf_file = create_label_pdf(value, svg_template) if pdf_file and os.path.exists(pdf_file): print(f"PDF label created: {pdf_file}") - print(f"PDF backup saved to: {pdf_file}") else: - print("PDF generation returned no file path") + print("PDF generation failed – no output file") + return False except Exception as pdf_err: - print(f"PDF generation failed: {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") + print(f"PDF generation error: {pdf_err}") return False - - # Convert preview to int if it's a string + + # Step 2 – Optional countdown before printing if isinstance(preview, str): preview = int(preview) - - if preview > 0: # Any value above 0 shows a preview message - # Calculate preview duration in seconds - if 1 <= preview <= 3: - 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 + + if preview > 0: + preview_sec = 3 if 1 <= preview <= 3 else 5 + print(f"Printing in {preview_sec} seconds… (Ctrl+C to cancel)") try: for i in range(preview_sec, 0, -1): 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: print("\nCancelled by user") return False - - # Print after preview - print("Sending to printer...") - return print_to_printer(printer, temp_file) - else: - print("Direct printing without preview...") - # Direct printing without preview (preview = 0) - return print_to_printer(printer, temp_file) - + + # Step 3 – Send to printer + print("Sending to printer...") + return print_to_printer(printer, pdf_file) + except Exception as e: print(f"Error printing label: {str(e)}") return False - + finally: - # This block always executes, ensuring cleanup 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: - 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 diff --git a/print_label_pdf.py b/print_label_pdf.py index d93caf2..fad3f50 100644 --- a/print_label_pdf.py +++ b/print_label_pdf.py @@ -49,8 +49,8 @@ class PDFLabelGenerator: """ self.label_width = label_width * cm self.label_height = label_height * cm - # Force landscape: ensure width > height - self.page_size = landscape((self.label_height, self.label_width)) if self.label_width > self.label_height else (self.label_width, self.label_height) + # label_width (3.5 cm) > label_height (2.5 cm) → page is already landscape + self.page_size = (self.label_width, self.label_height) self.dpi = dpi self.margin = 1 * mm # Minimal margin