From 8493d62d0839864f3f5cfaceae2f5c5463b62fad Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Thu, 12 Feb 2026 15:29:28 +0700 Subject: [PATCH] feat: Add `sign_document` model to enable rendering of sequence type sign items on signed PDFs. --- models/__init__.py | 1 + models/sign_document.py | 109 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 models/sign_document.py diff --git a/models/__init__.py b/models/__init__.py index 06a9a02..7594c42 100755 --- a/models/__init__.py +++ b/models/__init__.py @@ -2,3 +2,4 @@ from . import sign_item_type from . import sign_item from . import sign_request +from . import sign_document diff --git a/models/sign_document.py b/models/sign_document.py new file mode 100644 index 0000000..70eea81 --- /dev/null +++ b/models/sign_document.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +import io +from odoo import models +from odoo.exceptions import ValidationError +from odoo.tools.pdf import PdfFileReader, PdfFileWriter, PdfReadError, reshape_text +from reportlab.pdfgen import canvas +from reportlab.pdfbase import pdfmetrics +try: + from reportlab.pdfbase.pdfmetrics import stringWidth +except ImportError: + stringWidth = None + +class SignDocument(models.Model): + _inherit = 'sign.document' + + def render_document_with_items(self, signed_values=None, values_dict=None, final_log_hash=None): + base_output = super(SignDocument, self).render_document_with_items(signed_values, values_dict, final_log_hash) + + if not base_output: + return base_output + + items_by_page = self._get_sign_items_by_page() + has_sequence_items = any( + item.type_id.item_type == 'sequence' + for page_items in items_by_page.values() + for item in page_items + ) + + if not has_sequence_items: + return base_output + + try: + base_pdf = PdfFileReader(base_output, strict=False) + except (ValueError, PdfReadError): + return base_output + + if not signed_values: + signed_values, values_dict = self._get_preview_values() + + font = self._get_font() + normalFontSize = self._get_normal_font_size() + + packet = io.BytesIO() + # Create canvas using page size similar to image logic + 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 = 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) + + items = items_by_page.get(p + 1, []) + for item in items: + if item.type_id.item_type != 'sequence': + continue + + value_dict = signed_values.get(item.id) + if not value_dict: + continue + + value = value_dict.get('value') + # Sequence value is just text string + if not value: + continue + + # Render logic similar to text + value = reshape_text(str(value)) # Ensure string + can.setFont(font, height * item.height * 0.8) + + # Alignment logic + if item.alignment == "left": + can.drawString(width * item.posX, height * (1 - item.posY - item.height * 0.9), value) + elif item.alignment == "right": + can.drawRightString(width * (item.posX + item.width), height * (1 - item.posY - item.height * 0.9), value) + else: + can.drawCentredString(width * (item.posX + item.width / 2), height * (1 - item.posY - item.height * 0.9), value) + + can.showPage() + + can.save() + + item_pdf = PdfFileReader(packet) + new_pdf = PdfFileWriter() + + for p in range(0, base_pdf.getNumPages()): + page = base_pdf.getPage(p) + 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