Add template selection and multiple copy printing features

- Implemented template selection: type 0=OK (green), type 1=NOK (red)
- Added multiple copy printing (1-100 copies)
- Extended file format to 5 fields: ARTICLE;NR_ART;SERIAL;TYPE;COUNT
- Created OK/NOK SVG templates with visual distinction
- Fixed PDF landscape orientation issues
- Updated SumatraPDF to use noscale for exact dimensions
- Auto-generate conf folder with default templates on first run
- Made pystray optional for system tray functionality
- Updated build scripts for Python 3.13 compatibility (Kivy 2.3+, PyInstaller 6.18)
- Added comprehensive build documentation
- Improved printer configuration guidance
This commit is contained in:
NAME
2026-02-13 23:34:59 +02:00
parent 3b23f89cf0
commit 839828340d
13 changed files with 1131 additions and 117 deletions

View File

@@ -38,17 +38,19 @@ except (ImportError, OSError) as e:
class PDFLabelGenerator:
"""Generate high-quality PDF labels with image and text"""
def __init__(self, label_width=3.5, label_height=2.5, dpi=600):
def __init__(self, label_width=3.5, label_height=2.5, dpi=1200):
"""
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)
dpi (int): DPI for image rendering (default 1200 for high quality thermal printer)
"""
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)
self.dpi = dpi
self.margin = 1 * mm # Minimal margin
@@ -68,9 +70,11 @@ class PDFLabelGenerator:
try:
img = Image.open(image_path)
# Convert to RGB if needed
if img.mode not in ['RGB', 'L']:
# Convert to RGB for best quality (don't use grayscale)
if img.mode != 'RGB':
img = img.convert('RGB')
# Set DPI information for high-quality output
img.info['dpi'] = (self.dpi, self.dpi)
return img
except Exception as e:
print(f"Image loading error: {e}")
@@ -130,42 +134,25 @@ class PDFLabelGenerator:
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)
# Use cairosvg FIRST as it handles fonts and complex SVGs better
if CAIROSVG_AVAILABLE:
try:
# Render at high DPI for sharp output
cairosvg.svg2pdf(url=temp_svg_path, write_to=pdf_output, dpi=self.dpi)
print("Converting SVG to PDF using CairoSVG (high quality)...")
# CRITICAL: Let CairoSVG read dimensions from SVG file (width="35mm" height="25mm")
# DO NOT specify output_width/output_height as they control raster size, not PDF page size
# The SVG already has the correct dimensions, just render at high DPI
cairosvg.svg2pdf(
url=temp_svg_path,
write_to=pdf_output,
dpi=300 # High DPI for sharp output, page size comes from SVG
)
# Clean up temp SVG
try:
os.remove(temp_svg_path)
except:
pass
print(f"✅ PDF created from SVG template: {pdf_output}")
if filename:
return pdf_output
else:
@@ -174,9 +161,54 @@ class PDFLabelGenerator:
os.remove(pdf_output)
return pdf_bytes
except Exception as cairo_err:
print(f"CairoSVG conversion failed: {cairo_err}")
print(f"CairoSVG conversion failed: {cairo_err}, trying svglib...")
print("SVG conversion failed. svglib and cairosvg both unavailable or failed.")
# Fallback: Try svglib (generates many warnings but works)
if SVG_AVAILABLE:
try:
print("Converting SVG to PDF using svglib (fallback)...")
drawing = svg2rlg(temp_svg_path)
if drawing:
# CRITICAL: Force exact label dimensions (35mm x 25mm landscape)
# Convert to points: 1mm = 2.834645669 points
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas as pdf_canvas
target_width = 35 * mm
target_height = 25 * mm
# Scale drawing to exact size
if drawing.width > 0 and drawing.height > 0:
scale_x = target_width / drawing.width
scale_y = target_height / drawing.height
drawing.width = target_width
drawing.height = target_height
drawing.scale(scale_x, scale_y)
# Create PDF with explicit landscape page size
c = pdf_canvas.Canvas(pdf_output, pagesize=(target_width, target_height))
c.setPageCompression(0) # No compression for quality
renderPDF.draw(drawing, c, 0, 0)
c.save()
# Clean up temp SVG
try:
os.remove(temp_svg_path)
except:
pass
print(f"✅ PDF created from SVG template: {pdf_output}")
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}")
print("❌ SVG conversion failed. Both cairosvg and svglib unavailable or failed.")
return None
except Exception as e:
@@ -226,11 +258,15 @@ class PDFLabelGenerator:
else:
pdf_buffer = io.BytesIO()
# Create canvas with label dimensions
c = canvas.Canvas(pdf_buffer, pagesize=(self.label_width, self.label_height))
# Create canvas with label dimensions - explicitly landscape
c = canvas.Canvas(pdf_buffer, pagesize=self.page_size)
# Set higher resolution for better quality
c.setPageCompression(1) # Enable compression
# CRITICAL: Disable compression for maximum print quality
c.setPageCompression(0) # Disable compression for best quality
# Set high resolution for crisp output on thermal printers
# Page size already set to landscape orientation
c._pagesize = self.page_size
# Calculate dimensions
usable_width = self.label_width - 2 * self.margin
@@ -257,11 +293,11 @@ class PDFLabelGenerator:
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')
# Keep as RGB for better quality (thermal printers handle conversion)
# Save at high DPI for sharp output
img.save(temp_img_path, 'PNG', dpi=(self.dpi, self.dpi), optimize=False)
# Draw image maintaining aspect ratio
# Draw image maintaining aspect ratio with high quality
c.drawImage(
temp_img_path,
image_x,
@@ -269,7 +305,8 @@ class PDFLabelGenerator:
width=image_width,
height=image_height,
preserveAspectRatio=True,
anchor='c'
anchor='c',
mask='auto' # Better quality rendering
)
# Clean up
@@ -291,10 +328,15 @@ class PDFLabelGenerator:
else:
text = f"{label_name} -"
# Use appropriate font size to fit (6pt = ~2.1mm height)
font_size = 6
# IMPROVED: Larger font size for better readability (8pt = ~2.8mm height)
# This is critical for thermal printers - text must be crisp and readable
font_size = 8
c.setFont("Helvetica-Bold", font_size)
# Enable text rendering mode for crisp output
c.setStrokeColorRGB(0, 0, 0)
c.setFillColorRGB(0, 0, 0)
try:
c.drawString(text_area_x, y_position, text)
except Exception as e: