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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user