# -*- coding: utf-8 -*- import base64 import io from odoo import models, api from odoo.exceptions import ValidationError from odoo.tools.pdf import PdfFileReader, PdfFileWriter, PdfReadError from reportlab.pdfgen import canvas from reportlab.lib.utils import ImageReader from PIL import UnidentifiedImageError class SignDocument(models.Model): _inherit = 'sign.document' def render_document_with_items(self, signed_values=None, values_dict=None, final_log_hash=None): # 1. Get the base output from super (standard + other modules) base_output = super(SignDocument, self).render_document_with_items(signed_values, values_dict, final_log_hash) # If super returns None (e.g. encrypted PDF), we respect it. if not base_output: return base_output # 2. Check if we have any 'image' items to render items_by_page = self._get_sign_items_by_page() has_image_items = any( item.type_id.item_type == 'image' for page_items in items_by_page.values() for item in page_items ) if not has_image_items: return base_output # 3. Load the base output into PdfFileReader # base_output is a BytesIO try: base_pdf = PdfFileReader(base_output, strict=False) except (ValueError, PdfReadError): # Should not happen if super succeeded, but safety first return base_output if not signed_values: signed_values, values_dict = self._get_preview_values() # 4. Prepare a new canvas overlay for OUR items packet = io.BytesIO() # We need page size from the base_pdf (which might have been rotated/modified?) # Actually super returns a PDF where pages are merged. # We should use the same page size logic as original method. # But wait, we iterate pages. # Let's see how original method creates canvas: # can = canvas.Canvas(packet, pagesize=self._get_page_size(old_pdf)) # We need to replicate this per-page iteration to draw on the correct page. # Problem: canvas is one continuous stream of pages. # We need to make a canvas that matches the page count and sizes of base_pdf. # However, we can just create a single canvas, add pages, and then merge. # But we need the correct page size for each page. # Let's iterate base_pdf pages to get sizes. # Odoo's original code uses self._get_page_size(old_pdf) which returns MAX width/height. # Then for each page it gets mediaBox. # We will do the same loop structure. can = canvas.Canvas(packet, pagesize=self._get_page_size(base_pdf)) for p in range(0, base_pdf.getNumPages()): page = base_pdf.getPage(p) width = float(abs(page.mediaBox.getWidth())) height = float(abs(page.mediaBox.getHeight())) # Rotation handling (same as original) rotation = page.get('/Rotate', 0) if rotation and isinstance(rotation, int): can.rotate(rotation) if rotation == 90: width, height = height, width can.translate(0, -height) elif rotation == 180: can.translate(-width, -height) elif rotation == 270: width, height = height, width can.translate(-width, 0) # Function to fix transparency (copied/imported from original or re-defined?) # Since it's not exported, we re-define it or import if possible. # _fix_image_transparency is not exported. We will redefine it helper. items = items_by_page.get(p + 1, []) for item in items: if item.type_id.item_type != 'image': continue value_dict = signed_values.get(item.id) if not value_dict: continue value = value_dict.get('value') # For image type, value is the base64 image if not value: continue try: image_data = base64.b64decode(value[value.find(',') + 1:]) image_reader = ImageReader(io.BytesIO(image_data)) except (UnidentifiedImageError, ValueError): # Skip invalid images continue # Setup transparency override if needed (simplified version) # We can try to access the protected method if we really want, but let's implement the logic. if hasattr(image_reader, '_image'): _fix_image_transparency(image_reader._image) can.drawImage( image_reader, width * item.posX, height * (1 - item.posY - item.height), width * item.width, height * item.height, 'auto', True ) can.showPage() can.save() # 5. Merge our new overlay with the base_pdf item_pdf = PdfFileReader(packet) new_pdf = PdfFileWriter() for p in range(0, base_pdf.getNumPages()): page = base_pdf.getPage(p) # Merge the overlay if p < item_pdf.getNumPages(): page.mergePage(item_pdf.getPage(p)) new_pdf.addPage(page) output = io.BytesIO() try: new_pdf.write(output) except PdfReadError: raise ValidationError(self.env._("There was an issue generating the document.")) return output def _fix_image_transparency(image): # Copied from sign/models/sign_document.py if image.mode != 'RGBA': return pixels = image.load() for x in range(image.size[0]): for y in range(image.size[1]): if pixels[x, y] == (0, 0, 0, 0): pixels[x, y] = (255, 255, 255, 0)