WaveSign-API / pdf_utils.py
roseluo's picture
Upload 5 files
88b2f48 verified
Raw History Blame Contribute Delete
3.41 kB
"""
WaveSign β€” PDF Support via Rasterization
-----------------------------------------
Pipeline:
1. Rasterize original PDF pages at 300 DPI
2. Embed diffraction watermark into each page image
3. Repackage watermarked pages into a signed image-PDF
4. Rasterize the signed PDF again β€” sign those final images
(ensures verification always matches the exact output PDF pixels)
Output is an image-PDF (not text-searchable) β€” correct for tamper-evident signing.
"""
import io
from pdf2image import convert_from_bytes
from reportlab.pdfgen import canvas as rl_canvas
from reportlab.lib.utils import ImageReader
from pypdf import PdfReader
from core import embed_watermark, sign_image, verify_image, detect_mode
DPI = 300
def pdf_to_images(pdf_bytes: bytes, dpi: int = DPI) -> list:
"""Rasterize all PDF pages. Returns list of PIL Images."""
return convert_from_bytes(pdf_bytes, dpi=dpi)
def images_to_pdf(images: list) -> bytes:
"""Pack PIL Images into a single PDF. Each image fills its page."""
buf = io.BytesIO()
first_w, first_h = images[0].size
c = rl_canvas.Canvas(buf, pagesize=(first_w * 72 / DPI, first_h * 72 / DPI))
for img in images:
w_px, h_px = img.size
pw, ph = w_px * 72 / DPI, h_px * 72 / DPI
c.setPageSize((pw, ph))
img_buf = io.BytesIO()
img.save(img_buf, format="PNG")
img_buf.seek(0)
c.drawImage(ImageReader(img_buf), 0, 0, width=pw, height=ph)
c.showPage()
c.save()
buf.seek(0)
return buf.read()
def sign_pdf(pdf_bytes: bytes, secret: str, dpi: int = DPI, strength: float = 0.03):
"""
Sign all pages of a PDF.
Critical: signatures are computed from the FINAL rasterized output PDF,
not from intermediate watermarked images. This ensures verify_pdf always
produces matching results without round-trip pixel drift.
Returns: (signed_pdf_bytes, signatures_list, n_pages)
"""
# Step 1: rasterize original pages
images = pdf_to_images(pdf_bytes, dpi=dpi)
# Step 2: embed watermark into each page
watermarked = []
for img in images:
mode = detect_mode(img)
wm = embed_watermark(img, secret, strength=strength, mode=mode)
watermarked.append(wm)
# Step 3: pack watermarked pages into signed PDF
signed_pdf_bytes = images_to_pdf(watermarked)
# Step 4: rasterize signed PDF β€” sign these exact pixels
final_images = pdf_to_images(signed_pdf_bytes, dpi=dpi)
n_pages = len(final_images)
sigs = []
for i, img in enumerate(final_images):
sig = sign_image(img, secret, mode='document')
sig["page_index"] = i
sigs.append(sig)
return signed_pdf_bytes, sigs, n_pages
def verify_pdf(pdf_bytes: bytes, secret: str, signatures: list) -> list:
"""
Verify all pages of a signed PDF against per-page signatures.
Returns list of per-page result dicts with page_index key.
"""
images = pdf_to_images(pdf_bytes, dpi=DPI)
results = []
for i, (img, sig) in enumerate(zip(images, signatures)):
result = verify_image(img, secret, sig)
result["page_index"] = i
results.append(result)
return results
def get_pdf_page_count(pdf_bytes: bytes) -> int:
"""Quick page count without rasterization."""
return len(PdfReader(io.BytesIO(pdf_bytes)).pages)