Files
adaptronic_label-printer/print_label_pdf.py
NAME 8954135f93 Complete label printer redesign: file monitoring, SVG templates, sharp print quality
- Redesigned GUI for automatic file monitoring workflow
- Changed label format to 35x25mm landscape
- Implemented SVG template support with variable substitution
- Added configuration auto-save/load (conf/app.conf)
- Added system tray minimize functionality
- Fixed print quality: landscape orientation, vector fonts, 600 DPI
- Auto-clear file after print to prevent duplicates
- All popups auto-dismiss after 2-3 seconds
- Semicolon separator for data format (article;nr_art;serial)
- SumatraPDF integration with noscale settings
- Printer configured for outline fonts (sharp output)
- Reorganized documentation into documentation/ folder
2026-02-12 22:25:51 +02:00

403 lines
15 KiB
Python

"""
PDF-based Label Printing Module
Generates high-quality PDF labels with image and text.
Uses reportlab for superior PDF generation.
Layout: 35mm x 25mm landscape - supports SVG templates with variable substitution.
"""
from reportlab.lib.pagesizes import landscape
from reportlab.lib.units import cm, mm
from reportlab.pdfgen import canvas
from reportlab.graphics import renderPDF
import io
from PIL import Image
import os
import tempfile
import datetime
import re
import traceback
# SVG support
try:
from svglib.svglib import svg2rlg
SVG_AVAILABLE = True
except ImportError:
SVG_AVAILABLE = False
print("Warning: svglib not available. Install with: pip install svglib")
try:
import cairosvg
CAIROSVG_AVAILABLE = True
except (ImportError, OSError) as e:
CAIROSVG_AVAILABLE = False
# Only show warning if it's an import error, not missing Cairo library
if isinstance(e, ImportError):
print("Warning: cairosvg not available. Install with: pip install cairosvg")
class PDFLabelGenerator:
"""Generate high-quality PDF labels with image and text"""
def __init__(self, label_width=3.5, label_height=2.5, dpi=600):
"""
Initialize PDF label generator.
Args:
label_width (float): Width in cm (default 3.5 cm = 35mm)
label_height (float): Height in cm (default 2.5 cm = 25mm)
dpi (int): DPI for image rendering (default 600 for high quality print)
"""
self.label_width = label_width * cm
self.label_height = label_height * cm
self.dpi = dpi
self.margin = 1 * mm # Minimal margin
def load_image(self, image_path):
"""
Load and prepare image for embedding in PDF.
Args:
image_path (str): Path to image file
Returns:
PIL.Image or None: Loaded image
"""
if not image_path or not os.path.exists(image_path):
print(f"Image not found: {image_path}")
return None
try:
img = Image.open(image_path)
# Convert to RGB if needed
if img.mode not in ['RGB', 'L']:
img = img.convert('RGB')
return img
except Exception as e:
print(f"Image loading error: {e}")
return None
def replace_svg_variables(self, svg_content, variables):
"""
Replace variables in SVG template with actual values.
Args:
svg_content (str): SVG file content as string
variables (dict): Dictionary of variable replacements
Returns:
str: SVG content with replaced variables
"""
# Replace placeholders like {Article}, {NrArt}, {Serial}
for key, value in variables.items():
placeholder = f"{{{key}}}"
svg_content = svg_content.replace(placeholder, str(value or ''))
return svg_content
def create_label_from_svg_template(self, template_path, variables, filename=None):
"""
Create PDF label from SVG template with variable substitution.
Args:
template_path (str): Path to SVG template file
variables (dict): Dictionary with keys: Article, NrArt, Serial
filename (str): Output filename (if None, returns bytes)
Returns:
bytes or str: PDF content as bytes or filename if saved
"""
if not os.path.exists(template_path):
print(f"SVG template not found: {template_path}")
return None
try:
# Read SVG template
with open(template_path, 'r', encoding='utf-8') as f:
svg_content = f.read()
# Replace variables
svg_content = self.replace_svg_variables(svg_content, variables)
# Save modified SVG to temp file
temp_svg = tempfile.NamedTemporaryFile(mode='w', suffix='.svg', delete=False, encoding='utf-8')
temp_svg.write(svg_content)
temp_svg.close()
temp_svg_path = temp_svg.name
# Convert SVG to PDF
if filename:
pdf_output = filename
else:
pdf_output = tempfile.NamedTemporaryFile(suffix='.pdf', delete=False).name
# Try svglib first (more portable, no external dependencies)
if SVG_AVAILABLE:
try:
drawing = svg2rlg(temp_svg_path)
if drawing:
# Render at original size - quality depends on PDF rendering
# The PDF will contain vector graphics for sharp output
renderPDF.drawToFile(drawing, pdf_output)
# Clean up temp SVG
try:
os.remove(temp_svg_path)
except:
pass
if filename:
return pdf_output
else:
with open(pdf_output, 'rb') as f:
pdf_bytes = f.read()
os.remove(pdf_output)
return pdf_bytes
except Exception as svg_err:
print(f"svglib conversion failed: {svg_err}, trying cairosvg...")
# Fallback: Try cairosvg (requires system Cairo library)
if CAIROSVG_AVAILABLE:
try:
# Render at high DPI for sharp output
cairosvg.svg2pdf(url=temp_svg_path, write_to=pdf_output, dpi=self.dpi)
# Clean up temp SVG
try:
os.remove(temp_svg_path)
except:
pass
if filename:
return pdf_output
else:
with open(pdf_output, 'rb') as f:
pdf_bytes = f.read()
os.remove(pdf_output)
return pdf_bytes
except Exception as cairo_err:
print(f"CairoSVG conversion failed: {cairo_err}")
print("SVG conversion failed. svglib and cairosvg both unavailable or failed.")
return None
except Exception as e:
print(f"SVG template error: {e}")
traceback.print_exc()
return None
def create_label_pdf(self, comanda, article, serial, filename=None, image_path=None, svg_template=None):
"""
Create a PDF label with image on left and text on right.
Label: 35mm x 25mm landscape
Layout: 1/3 left = image, 2/3 right = 3 rows of text
Or use SVG template if provided.
Args:
comanda (str): Nr. Comanda value
article (str): Nr. Art. value
serial (str): Serial No. value
filename (str): Output filename (if None, returns bytes)
image_path (str): Path to accepted.png image
svg_template (str): Path to SVG template file (optional)
Returns:
bytes or str: PDF content as bytes or filename if saved
"""
# If SVG template is provided, use it instead
if svg_template and os.path.exists(svg_template):
variables = {
'Article': comanda,
'NrArt': article,
'Serial': serial,
'Comanda': comanda, # Alternative name
}
return self.create_label_from_svg_template(svg_template, variables, filename)
# Fallback to standard layout
# Prepare data for rows
rows_data = [
("Nr. Comanda:", comanda),
("Nr. Art.:", article),
("Serial No.:", serial),
]
# Create PDF canvas in memory or to file
if filename:
pdf_buffer = filename
else:
pdf_buffer = io.BytesIO()
# Create canvas with label dimensions
c = canvas.Canvas(pdf_buffer, pagesize=(self.label_width, self.label_height))
# Set higher resolution for better quality
c.setPageCompression(1) # Enable compression
# Calculate dimensions
usable_width = self.label_width - 2 * self.margin
usable_height = self.label_height - 2 * self.margin
# Image area: 1/3 of width on the left
image_width = usable_width / 3
image_x = self.margin
image_y = self.margin
image_height = usable_height
# Text area: 2/3 of width on the right
text_area_x = self.margin + image_width + 1 * mm # Small gap
text_area_width = usable_width - image_width - 1 * mm
row_height = usable_height / 3
# Draw image on left if available
if image_path and os.path.exists(image_path):
try:
img = self.load_image(image_path)
if img:
# Save temp file for reportlab
temp_img_file = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
temp_img_path = temp_img_file.name
temp_img_file.close()
# Convert to grayscale for black and white
img_bw = img.convert('L')
img_bw.save(temp_img_path, 'PNG')
# Draw image maintaining aspect ratio
c.drawImage(
temp_img_path,
image_x,
image_y,
width=image_width,
height=image_height,
preserveAspectRatio=True,
anchor='c'
)
# Clean up
try:
os.remove(temp_img_path)
except:
pass
except Exception as e:
print(f"Error drawing image: {e}")
# Draw text rows on right
for idx, (label_name, value) in enumerate(rows_data):
# Calculate y position for this row (top to bottom)
y_position = self.label_height - self.margin - (idx * row_height) - row_height/2
# Draw text with better quality
if value and value.strip():
text = f"{label_name} {value.strip()}"
else:
text = f"{label_name} -"
# Use appropriate font size to fit (6pt = ~2.1mm height)
font_size = 6
c.setFont("Helvetica-Bold", font_size)
try:
c.drawString(text_area_x, y_position, text)
except Exception as e:
print(f"Error drawing text row {idx}: {e}")
# Save PDF
c.save()
# Return filename or bytes
if filename:
return filename
else:
pdf_buffer.seek(0)
return pdf_buffer.getvalue()
def create_label_pdf_file(self, comanda, article, serial, filename=None, image_path=None):
"""
Create PDF label file and return the filename.
Args:
comanda (str): Nr. Comanda value
article (str): Nr. Art. value
serial (str): Serial No. value
filename (str): Output filename (if None, auto-generates)
image_path (str): Path to accepted.png image
Returns:
str: Path to created PDF file
"""
if not filename:
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"label_{timestamp}.pdf"
return self.create_label_pdf(comanda, article, serial, filename, image_path)
def create_label_pdf_simple(text, image_path=None, svg_template=None):
"""
Simple wrapper to create PDF from combined text (article;nr_art;serial).
Args:
text (str): Combined text in format "article;nr_art;serial"
image_path (str): Path to accepted.png image
svg_template (str): Path to SVG template file (optional, checks conf/label_template.svg by default)
Returns:
bytes: PDF content
"""
# Parse text - 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 ''
# Use default image path if not provided
if not image_path:
image_path = os.path.join('conf', 'accepted.png')
# Check for default SVG template if not provided
if not svg_template:
default_svg = os.path.join('conf', 'label_template.svg')
if os.path.exists(default_svg):
svg_template = default_svg
generator = PDFLabelGenerator()
pdf_bytes = generator.create_label_pdf(article, nr_art, serial, None, image_path, svg_template)
return pdf_bytes
def create_label_pdf_file(text, filename=None, image_path=None, svg_template=None):
"""
Create PDF label file from combined text.
Args:
text (str): Combined text in format "article;nr_art;serial"
filename (str): Output filename (auto-generates if None)
image_path (str): Path to accepted.png image
svg_template (str): Path to SVG template file (optional, checks conf/label_template.svg by default)
Returns:
str: Path to created PDF file
"""
# Parse text - 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 ''
if not filename:
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"label_{timestamp}.pdf"
# Use default image path if not provided
if not image_path:
image_path = os.path.join('conf', 'accepted.png')
# Check for default SVG template if not provided
if not svg_template:
default_svg = os.path.join('conf', 'label_template.svg')
if os.path.exists(default_svg):
svg_template = default_svg
generator = PDFLabelGenerator()
return generator.create_label_pdf(article, nr_art, serial, filename, image_path, svg_template)