701 lines
27 KiB
Python
701 lines
27 KiB
Python
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 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:
|
||
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).
|
||
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
|
||
|
||
# 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: 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
|
||
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}")
|
||
# 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",
|
||
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
|