from __future__ import annotations from typing import TYPE_CHECKING, Iterator, cast import pypdf from PIL import Image from reportlab.pdfgen.canvas import Canvas from xhtml2pdf.files import getFile, pisaFileObject if TYPE_CHECKING: from io import BytesIO from xhtml2pdf.context import pisaContext class WaterMarks: @staticmethod def get_size_location( img, context: dict, pagesize: tuple[int, int], *, is_portrait: bool ) -> tuple[int, int, int, int]: object_position: tuple[int, int] | None = context.get("object_position") cssheight: int | None = cast(int, context.get("height")) csswidth: int = cast(int, context.get("width")) iw, ih = img.getSize() pw, ph = pagesize width: int = pw # min(iw, pw) # max wfactor: float = float(width) / iw height: int = ph # min(ih, ph) # max hfactor: float = float(height) / ih factor_min: float = min(wfactor, hfactor) factor_max: float = max(wfactor, hfactor) if is_portrait: height = ih * factor_min width = iw * factor_min else: height = ih * factor_max width = iw * factor_min if object_position: # x, y, width=None, height=None x, y = object_position elif is_portrait: x, y = 0, ph - height else: x, y = 0, 0 if csswidth: width = csswidth if cssheight: height = cssheight return x, y, width, height @staticmethod def get_img_with_opacity(pisafile: pisaFileObject, context: dict) -> BytesIO: opacity: float = context.get("opacity", None) if opacity: name: str | None = pisafile.getNamedFile() img: Image.Image = Image.open(name) img = img.convert("RGBA") img.putalpha(int(255 * opacity)) img.save(name, "PNG") return getFile(name).getBytesIO() return pisafile.getBytesIO() @staticmethod def generate_pdf_background( pisafile: pisaFileObject, pagesize: tuple[int, int], *, is_portrait: bool, context: dict | None = None, ) -> pisaFileObject: """ Pypdf requires pdf as background so convert image to pdf in temporary file with same page dimensions :param pisafile: Image File :param pagesize: Page size for the new pdf """ # don't move up, we are preventing circular import from xhtml2pdf.xhtml2pdf_reportlab import PmlImageReader if context is None: context = {} output: pisaFileObject = pisaFileObject( None, "application/pdf" ) # build temporary file img: PmlImageReader = PmlImageReader( WaterMarks.get_img_with_opacity(pisafile, context) ) x, y, width, height = WaterMarks.get_size_location( img, context, pagesize, is_portrait=is_portrait ) canvas = Canvas(output.getNamedFile(), pagesize=pagesize) canvas.drawImage(img, x, y, width, height, mask="auto") canvas.save() return output @staticmethod def get_watermark(context: pisaContext, max_numpage: int) -> Iterator: if context.pisaBackgroundList: pages = [x[0] for x in context.pisaBackgroundList] + [max_numpage + 1] pages.pop(0) for counter, (page, bgfile, pgcontext) in enumerate( context.pisaBackgroundList ): if not bgfile.notFound(): yield range(page, pages[counter]), bgfile, int(pgcontext["step"]) @staticmethod def process_doc( context: pisaContext, istream: bytes, output: bytes ) -> tuple[bytes, bool]: pdfoutput: pypdf.PdfWriter = pypdf.PdfWriter() input1: pypdf.PdfReader = pypdf.PdfReader(istream) has_bg: bool = False for pages, bgouter, step in WaterMarks.get_watermark( context, len(input1.pages) ): for index, ctr in enumerate(pages): bginput: pypdf.PdfReader = pypdf.PdfReader(bgouter.getBytesIO()) pagebg: pypdf.PageObject = bginput.pages[0] page: pypdf.PageObject = input1.pages[ctr - 1] if index % step == 0: pagebg.merge_page(page) page = pagebg pdfoutput.add_page(page) has_bg = True if has_bg: pdfoutput.write(output) return output, has_bg