from PIL import Image, ImageDraw, ImageFont import barcode from barcode.writer import ImageWriter import time import os import sys import datetime import platform import subprocess from print_label_pdf import PDFLabelGenerator # Cross-platform printer support try: import cups CUPS_AVAILABLE = True except ImportError: CUPS_AVAILABLE = False try: import win32api import win32print WIN32_AVAILABLE = True except ImportError: WIN32_AVAILABLE = False SYSTEM = platform.system() # 'Linux', 'Windows', 'Darwin' def get_available_printers(): """ Get list of available printers (cross-platform). Includes both local and network printers on Windows. Returns: list: List of available printer names, with "PDF" as fallback """ try: if SYSTEM == "Linux" and CUPS_AVAILABLE: # Linux: Use CUPS conn = cups.Connection() printers = conn.getPrinters() return list(printers.keys()) if printers else ["PDF"] elif SYSTEM == "Windows": # Windows: Get local + connected printers (includes print server connections) try: printers = [] # PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS captures: # - Locally installed printers # - Printers connected from a print server (e.g. \\server\printer) try: flags = win32print.PRINTER_ENUM_LOCAL | win32print.PRINTER_ENUM_CONNECTIONS for printer_info in win32print.EnumPrinters(flags): printer_name = printer_info[2] if printer_name and printer_name not in printers: printers.append(printer_name) except Exception as e: print(f"Error enumerating printers: {e}") # Add PDF as fallback option if "PDF" not in printers: printers.append("PDF") return printers if printers else ["PDF"] except Exception as e: print(f"Error getting Windows printers: {e}") return ["PDF"] elif SYSTEM == "Darwin": # macOS: Use lpstat command try: result = subprocess.run(["lpstat", "-p", "-d"], capture_output=True, text=True) printers = [] for line in result.stdout.split('\n'): if line.startswith('printer'): printer_name = line.split()[1] printers.append(printer_name) return printers if printers else ["PDF"] except: return ["PDF"] else: return ["PDF"] except Exception as e: print(f"Error getting printers: {e}") 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. PDFs are saved to the pdf_backup folder. Args: text (str): Combined text in format "article;nr_art;serial;status" or single value status: 1 = OK label, 0 = NOK label svg_template (str): Path to specific SVG template to use (optional) Returns: str: Path to the generated PDF file """ # Parse the text input - using semicolon separator parts = text.split(';') if ';' in text else [text, '', ''] article = parts[0].strip() if len(parts) > 0 else '' nr_art = parts[1].strip() if len(parts) > 1 else '' serial = parts[2].strip() if len(parts) > 2 else '' status_flag = parts[3].strip() if len(parts) > 3 else '1' # Create PDF using high-quality generator generator = PDFLabelGenerator() # Ensure pdf_backup folder exists pdf_backup_dir = 'pdf_backup' os.makedirs(pdf_backup_dir, exist_ok=True) timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") pdf_filename = os.path.join(pdf_backup_dir, f"final_label_{timestamp}.pdf") # Select template/image based on status flag # 1 = OK label, 0 = NOK label selected_template = svg_template if svg_template and os.path.exists(svg_template) else None default_svg = os.path.join('conf', 'label_template.svg') ok_svg = os.path.join('conf', 'label_template_ok.svg') nok_svg = os.path.join('conf', 'label_template_nok.svg') if selected_template: image_path = os.path.join('conf', 'accepted.png') if status_flag != '0' else os.path.join('conf', 'refused.png') elif status_flag == '0': # NOK label: prefer dedicated NOK SVG template, otherwise use refused image in standard layout if os.path.exists(nok_svg): selected_template = nok_svg elif os.path.exists(default_svg): selected_template = default_svg image_path = os.path.join('conf', 'refused.png') else: # OK label (default): prefer dedicated OK SVG template, fallback to default SVG template if os.path.exists(ok_svg): selected_template = ok_svg elif os.path.exists(default_svg): selected_template = default_svg image_path = os.path.join('conf', 'accepted.png') 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 landscape try: devmode.Orientation = 2 # Landscape 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 print_to_printer(printer_name, file_path): """ Print file to printer (cross-platform). Uses SumatraPDF for silent printing on Windows. Args: printer_name (str): Name of printer or "PDF" for PDF output file_path (str): Path to file to print Returns: bool: True if successful """ try: if printer_name == "PDF": # PDF output - file is already saved print(f"PDF output: {file_path}") return True elif SYSTEM == "Linux" and CUPS_AVAILABLE: # Linux: Use CUPS conn = cups.Connection() conn.printFile(printer_name, file_path, "Label Print", {}) print(f"Label sent to printer: {printer_name}") return True elif SYSTEM == "Windows": # Windows: Print PDF silently without any viewer opening try: if WIN32_AVAILABLE: import win32print import win32api 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) sumatra_paths = [] # Get the directory where this script/exe is running if getattr(sys, 'frozen', False): # Running as compiled executable # PyInstaller extracts bundled files to sys._MEIPASS temp folder if hasattr(sys, '_MEIPASS'): # Check bundled version first (inside the exe) bundled_sumatra = os.path.join(sys._MEIPASS, 'SumatraPDF.exe') sumatra_paths.append(bundled_sumatra) # Check app directory and conf subfolder for external version app_dir = os.path.dirname(sys.executable) sumatra_paths.append(os.path.join(app_dir, "SumatraPDF", "SumatraPDF.exe")) sumatra_paths.append(os.path.join(app_dir, "SumatraPDF.exe")) sumatra_paths.append(os.path.join(app_dir, "conf", "SumatraPDF.exe")) # conf subfolder next to exe sumatra_paths.append(os.path.join(os.getcwd(), "conf", "SumatraPDF.exe")) # conf relative to cwd else: # Running as script - check local folders app_dir = os.path.dirname(os.path.abspath(__file__)) sumatra_paths.append(os.path.join(app_dir, "SumatraPDF", "SumatraPDF.exe")) sumatra_paths.append(os.path.join(app_dir, "SumatraPDF.exe")) sumatra_paths.append(os.path.join(app_dir, "conf", "SumatraPDF.exe")) # Then check system installations sumatra_paths.extend([ r"C:\Program Files\SumatraPDF\SumatraPDF.exe", r"C:\Program Files (x86)\SumatraPDF\SumatraPDF.exe", ]) for sumatra_path in sumatra_paths: if os.path.exists(sumatra_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. result = subprocess.run([ sumatra_path, "-print-to", printer_name, file_path, "-print-settings", "noscale", "-silent", "-exit-when-done" ], check=False, creationflags=subprocess.CREATE_NO_WINDOW, capture_output=True, text=True, timeout=30) if result.returncode != 0: print(f"SumatraPDF returned code {result.returncode}") if result.stderr: print(f"SumatraPDF stderr: {result.stderr.strip()}") else: print(f"Label sent to printer via SumatraPDF: {printer_name}") printed = True break except Exception as e: print(f"SumatraPDF error: {e}") # Do not launch default PDF viewers (Adobe/Edge/etc.) as fallback. if not printed: print("SumatraPDF not found or failed. PDF saved as backup only (no viewer launched).") return False return True else: # Non-PDF files subprocess.run(['notepad', '/p', file_path], check=False, creationflags=subprocess.CREATE_NO_WINDOW) print(f"Label sent to printer: {printer_name}") return True except Exception as e: print(f"Windows print error: {e}") print("PDF backup saved as fallback") return True elif SYSTEM == "Darwin": # macOS: Use lp command subprocess.run(["lp", "-d", printer_name, file_path], check=True) print(f"Label sent to printer: {printer_name}") return True else: print(f"Unsupported system: {SYSTEM}") return False except Exception as e: print(f"Printer error: {str(e)}") print("Label already saved to file as fallback...") print(f"Label file: {file_path}") return True def print_label_standalone(value, printer, preview=0, use_pdf=True, 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. 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) Returns: bool: True if printing was successful, 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 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") 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") return False # Convert preview to int if it's a string 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 try: for i in range(preview_sec, 0, -1): print(f" {i}...", end=" ", flush=True) time.sleep(1) print("\nPrinting now...") 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) 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") else: print("Cleanup complete - label file retained for reference") # Main code removed - import this module or run as part of the Kivy GUI application