sign_image_field/models/sign_document.py

160 lines
6.1 KiB
Python

# -*- 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)