Estoy usando wkhtmltopdf para representar un documento HTML (con plantilla de Django) en un archivo PDF de una sola página. Me gustaría renderizarlo inmediatamente con la altura correcta (que no he podido hacer hasta ahora) o renderizarlo incorrectamente y recortarlo. Estoy usando Python.
wkhtmltopdf convierte en un PDF de una sola página muy, muy largo con mucho espacio adicional usando --page-heightpdfCropMargins para recortar: crop(["-p4", "100", "0", "100", "100", "-a4", "0", "-28", "0", "0", "input.pdf"]) El PDF se representa perfectamente con 28 unidades de margen en la parte inferior, pero tuve que usar el sistema de archivos para ejecutar el comando de crop . Parece que la herramienta espera un archivo de entrada y un archivo de salida, y también crea archivos temporales a la mitad. Así que no puedo usarlo.
wkhtmltopdf renderizar a PDF de varias páginas con parámetros predeterminadosPyPDF4 (o PyPDF2 ) para leer el archivo y combinar páginas en una sola página largaEl PDF se muestra muy bien en la mayoría de los casos, sin embargo, a veces se pueden ver muchos espacios en blanco adicionales en la parte inferior si por casualidad la última página del PDF tenía muy poco contenido.
El escenario ideal implicaría una función que tome HTML y lo convierta en un PDF de una sola página con la cantidad esperada de espacio en blanco en la parte inferior. Estaría feliz de renderizar el PDF usando wkhtmltopdf , ya que devuelve bytes y luego procesar estos bytes para eliminar cualquier espacio en blanco adicional. Pero no quiero involucrar al sistema de archivos en esto, sino que quiero realizar todas las operaciones en la memoria. ¿Quizás de alguna manera pueda inspeccionar el PDF directamente y eliminar el espacio en blanco manualmente, o hacer algo de magia HTML para determinar la altura de procesamiento de antemano?
Tenga en cuenta que pdfkit es un contenedor wkhtmltopdf
# This is not a valid HTML (includes Django-specific stuff) template: Template = get_template("some-django-template.html") # This is now valid HTML rendered = template.render({ "foo": "bar", }) # This first renders PDF from HTML normally (multiple pages) # Then counts how many pages were created and determines the required single-page height # Then renders a single-page PDF from HTML using the page height and width arguments return pdfkit.from_string(rendered, options={ "page-height": f"{297 * PdfFileReader(BytesIO(pdfkit.from_string(rendered))).getNumPages()}mm", "page-width": "210mm" }) Es equivalente a Attempt type 2 , excepto que no uso PyDPF4 aquí para unir las páginas, sino que renderizo de nuevo con wkhtmltopdf usando la altura de página precalculada.
Puede haber mejores maneras de hacer esto, pero 3 días después de una recompensa sin respuestas, esto al menos funciona. Supongo que puede recortar el PDF usted mismo, y todo lo que hago aquí es determinar qué tan abajo en la última página todavía tiene contenido. Si esa suposición es incorrecta, probablemente podría descubrir cómo recortar el PDF. O, de lo contrario, simplemente recorte la imagen (fácil en Pillow) y luego conviértala a PDF. Además, si tiene un PDF grande, es posible que deba calcular qué tan abajo termina el texto en todo el PDF. Solo estoy averiguando qué tan abajo en la última página termina el contenido. Pero convertir de uno a otro es como un simple problema aritmético.
Código probado:
import pdfkit from PyPDF2 import PdfFileReader from io import BytesIO # This library isn't named fitz on pypi, # obtain this library with `pip install PyMuPDF==1.19.4` import fitz # `pip install Pillow==8.3.1` from PIL import Image import numpy as np # However you arrive at valid HTML, it makes no difference to the solution. rendered = "<html><head></head><body><h3>Hello World</h3><p>hello</p></body></html>" # This first renders PDF from HTML normally (multiple pages) # Then counts how many pages were created and determines the required single-page height # Then renders a single-page PDF from HTML using the page height and width arguments pdf_bytes = pdfkit.from_string(rendered, options={ "page-height": f"{297 * PdfFileReader(BytesIO(pdfkit.from_string(rendered))).getNumPages()}mm", "page-width": "210mm" }) # convert the pdf into an image. pdf = fitz.open(stream=pdf_bytes, filetype="pdf") last_page = pdf[pdf.pageCount-1] matrix = fitz.Matrix(1, 1) image_pixels = last_page.get_pixmap(matrix=matrix, colorspace="GRAY") image = Image.frombytes("L", [image_pixels.width, image_pixels.height], image_pixels.samples) #Uncomment if you want to see. #image.show() # Now figure out where the end of the text is: # First binarize. This might not be the most efficient way to do this. # But it's how I do it. THRESHOLD = 100 # I wrote this code ages ago and don't remember the details but # basically, we treat every pixel > 100 as a white pixel, # We convert the result to a true/false matrix # And then invert that. # The upshot is that, at the end, a value of "True" # in the matrix will represent a black pixel in that location. binary_matrix = np.logical_not(image.point( lambda p: 255 if p > THRESHOLD else 0 ).convert("1")) # Now find last white row, starting at the bottom row_count, column_count = binary_matrix.shape last_row = 0 for i, row in enumerate(reversed(binary_matrix)): if any(row): last_row = i break else: continue percentage_from_top = (1 - last_row / row_count) * 100 print(percentage_from_top) # Now you know where the page ends. # Go back and crop the PDF accordingly.