""" 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)