19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:27 +01:00
parent d1963a3c3a
commit 2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions

View file

@ -108,6 +108,16 @@ def _unwrapping_get(self, key, default=None):
DictionaryObject.get = _unwrapping_get
if hasattr(NameObject, 'renumber_table'):
# Make sure all the correct delimiters are included
# We will make this change only if pypdf has the renumber_table attribute
# https://github.com/py-pdf/pypdf/commit/8c542f331828c5839fda48442d89b8ac5d3984ac
NameObject.renumber_table.update({
**{chr(i): f"#{i:02X}".encode() for i in b"#()<>[]{}/%"},
**{chr(i): f"#{i:02X}".encode() for i in range(33)},
})
if hasattr(PdfWriter, 'write_stream'):
# >= 2.x has a utility `write` which can open a path, so `write_stream` could be called directly
class BrandedFileWriter(PdfWriter):
@ -207,16 +217,21 @@ def rotate_pdf(pdf):
return _buffer.getvalue()
def to_pdf_stream(attachment) -> io.BytesIO:
def to_pdf_stream(attachment) -> io.BytesIO | None:
"""Get the byte stream of the attachment as a PDF."""
if not attachment.raw:
_logger.warning("%s has no raw data.", attachment)
return None
if attachment_raw := attachment._get_pdf_raw():
return io.BytesIO(attachment_raw)
stream = io.BytesIO(attachment.raw)
if attachment.mimetype == 'application/pdf':
return stream
elif attachment.mimetype.startswith('image'):
if attachment.mimetype.startswith('image'):
output_stream = io.BytesIO()
Image.open(stream).convert("RGB").save(output_stream, format="pdf")
return output_stream
_logger.warning("mimetype (%s) not recognized for %s", attachment.mimetype, attachment)
return None
def extract_page(attachment, num_page=0) -> io.BytesIO | None:
@ -383,8 +398,8 @@ class OdooPdfFileWriter(PdfFileWriter):
adapted_subtype = subtype
if REGEX_SUBTYPE_UNFORMATED.match(subtype):
# _pypdf2_2 does the formating when creating a NameObject
if SUBMOD == '._pypdf2_2':
# _pypdf2_2 and _pypdf does the formating when creating a NameObject
if SUBMOD in ('._pypdf2_2', '._pypdf'):
return '/' + subtype
adapted_subtype = '/' + subtype.replace('/', '#2F')
@ -488,16 +503,18 @@ class OdooPdfFileWriter(PdfFileWriter):
"""
# Set the PDF version to 1.7 (as PDF/A-3 is based on version 1.7) and make it PDF/A compliant.
# See https://github.com/veraPDF/veraPDF-validation-profiles/wiki/PDFA-Parts-2-and-3-rules#rule-612-1
self._header = b"%PDF-1.7"
# " The file header shall begin at byte zero and shall consist of "%PDF-1.n" followed by a single EOL marker,
# where 'n' is a single digit number between 0 (30h) and 7 (37h) "
# " The aforementioned EOL marker shall be immediately followed by a % (25h) character followed by at least four
# bytes, each of whose encoded byte values shall have a decimal value greater than 127 "
self._header = b"%PDF-1.7"
if SUBMOD != '._pypdf2_2':
self._header += b"\n"
# bytes, each of whose encoded byte values shall have a decimal value greater than 127 ".
# PyPDF2 2.X+ already adds these 4 characters by default (so ._pypdf2_2 and ._pypdf don't need it).
# The injected character `\xc3\xa9` is equivalent to the character `é`.
# Therefore, on `_pypdf2_1`, the header will look like: `%PDF-1.7\n%éééé`,
# while on `_pypdf2_2` and `_pypdf`, it will look like: `%PDF-1.7\n%âãÏÓ`.
if SUBMOD == '._pypdf2_1':
self._header += b"%\xDE\xAD\xBE\xEF"
self._header += b"\n%\xc3\xa9\xc3\xa9\xc3\xa9\xc3\xa9"
# Add a document ID to the trailer. This is only needed when using encryption with regular PDF, but is required
# when using PDF/A
@ -571,6 +588,14 @@ class OdooPdfFileWriter(PdfFileWriter):
outlines = self._root_object['/Outlines'].getObject()
outlines[NameObject('/Count')] = NumberObject(1)
# [6.7.2.2-1] include a MarkInfo dictionary containing "Marked" with true value
mark_info = DictionaryObject({NameObject("/Marked"): BooleanObject(True)})
self._root_object[NameObject("/MarkInfo")] = mark_info
# [6.7.3.3-1] include minimal document structure in the catalog
struct_tree_root = DictionaryObject({NameObject("/Type"): NameObject("/StructTreeRoot")})
self._root_object[NameObject("/StructTreeRoot")] = struct_tree_root
# Set odoo as producer
self.addMetadata({
'/Creator': "Odoo",
@ -618,7 +643,7 @@ class OdooPdfFileWriter(PdfFileWriter):
DictionaryObject({
NameObject('/CheckSum'): createStringObject(md5(attachment['content']).hexdigest()),
NameObject('/ModDate'): createStringObject(datetime.now().strftime(DEFAULT_PDF_DATETIME_FORMAT)),
NameObject('/Size'): NameObject(f"/{len(attachment['content'])}"),
NameObject('/Size'): NumberObject(len(attachment['content'])),
}),
})
if attachment.get('subtype'):