Files
Odoo-Modules/fusion_authorizer_portal/utils/pdf_filler.py
gsinghpal e8e554de95 changes
2026-02-23 00:32:20 -05:00

147 lines
5.6 KiB
Python

# -*- coding: utf-8 -*-
# Fusion PDF Template Filler
# Generic utility for filling any PDF template with data overlays.
# Uses the same pattern as Odoo Enterprise Sign module (sign/utils/pdf_handling.py):
# - Read original PDF page dimensions from mediaBox
# - Create reportlab Canvas overlay at the same page size
# - Convert percentage positions (0.0-1.0) to absolute PDF coordinates
# - Merge overlay onto original page via mergePage()
import logging
from io import BytesIO
from reportlab.pdfgen import canvas
from reportlab.lib.utils import ImageReader
from odoo.tools.pdf import PdfFileReader, PdfFileWriter
_logger = logging.getLogger(__name__)
class PDFTemplateFiller:
"""Generic PDF template filler. Works with any template, any number of pages."""
@staticmethod
def fill_template(template_pdf_bytes, fields_by_page, context, signatures=None):
"""Fill a PDF template by overlaying text/checkmarks/signatures at configured positions.
Args:
template_pdf_bytes: bytes of the original PDF
fields_by_page: {page_num: [field_dicts]} where page_num is 1-based
Each field_dict has: field_key, pos_x, pos_y, width, height,
field_type, font_size, font_name
context: flat dict of {field_key: value} with all available data
signatures: dict of {field_key: binary_png} for signature image fields
Returns:
bytes of the filled PDF (all pages preserved)
"""
if signatures is None:
signatures = {}
try:
original = PdfFileReader(BytesIO(template_pdf_bytes))
except Exception as e:
_logger.error("Failed to read template PDF: %s", e)
raise
output = PdfFileWriter()
num_pages = original.getNumPages()
for page_idx in range(num_pages):
page = original.getPage(page_idx)
page_num = page_idx + 1 # 1-based page number
page_w = float(page.mediaBox.getWidth())
page_h = float(page.mediaBox.getHeight())
fields = fields_by_page.get(page_num, [])
if fields:
# Create a transparent overlay for this page
overlay_buf = BytesIO()
c = canvas.Canvas(overlay_buf, pagesize=(page_w, page_h))
for field in fields:
PDFTemplateFiller._draw_field(
c, field, context, signatures, page_w, page_h
)
c.save()
overlay_buf.seek(0)
# Merge overlay onto original page (same as sign module)
overlay_pdf = PdfFileReader(overlay_buf)
page.mergePage(overlay_pdf.getPage(0))
output.addPage(page)
result = BytesIO()
output.write(result)
return result.getvalue()
@staticmethod
def _draw_field(c, field, context, signatures, page_w, page_h):
"""Draw a single field onto the reportlab canvas.
Args:
c: reportlab Canvas
field: dict with field_key, pos_x, pos_y, width, height, field_type, etc.
context: data context dict
signatures: dict of {field_key: binary} for signature fields
page_w: page width in PDF points
page_h: page height in PDF points
"""
field_key = field.get('field_key') or field.get('field_name', '')
field_type = field.get('field_type', 'text')
value = context.get(field_key, field.get('default_value', ''))
if not value and field_type != 'signature':
return
# Convert percentage positions to absolute PDF coordinates
# pos_x/pos_y are 0.0-1.0 ratios from top-left
# PDF coordinate system: origin at bottom-left, Y goes up
abs_x = field['pos_x'] * page_w
abs_y = page_h - (field['pos_y'] * page_h) # flip Y axis
font_name = field.get('font_name', 'Helvetica')
font_size = field.get('font_size', 10.0)
if field_type in ('text', 'date'):
c.setFont(font_name, font_size)
text_val = str(value)
field_h = field.get('height', 0.018) * page_h
text_y = abs_y - field_h + (field_h - font_size) / 2
align = field.get('text_align', 'left')
if align == 'center':
center_x = abs_x + (field.get('width', 0.15) * page_w) / 2
c.drawCentredString(center_x, text_y, text_val)
elif align == 'right':
right_x = abs_x + field.get('width', 0.15) * page_w
c.drawRightString(right_x, text_y, text_val)
else:
c.drawString(abs_x, text_y, text_val)
elif field_type == 'checkbox':
if value:
c.setFont('ZapfDingbats', font_size)
cb_h = field.get('height', 0.018) * page_h
cb_y = abs_y - cb_h + (cb_h - font_size) / 2
c.drawString(abs_x, cb_y, '4')
elif field_type == 'signature':
sig_data = signatures.get(field_key)
if sig_data:
try:
img = ImageReader(BytesIO(sig_data))
sig_w = field.get('width', 0.15) * page_w
sig_h = field.get('height', 0.05) * page_h
# Draw signature image (position from top, so adjust Y)
c.drawImage(
img, abs_x, abs_y - sig_h,
width=sig_w, height=sig_h,
mask='auto',
)
except Exception as e:
_logger.warning("Failed to draw signature for %s: %s", field_key, e)