roseluo commited on
Commit
22a39c6
·
verified ·
1 Parent(s): c921190

Upload pdf_utils.py

Browse files
Files changed (1) hide show
  1. pdf_utils.py +100 -0
pdf_utils.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ WaveSign — PDF Support via Rasterization
3
+ -----------------------------------------
4
+ Pipeline:
5
+ 1. Rasterize original PDF pages at 300 DPI
6
+ 2. Embed diffraction watermark into each page image
7
+ 3. Repackage watermarked pages into a signed image-PDF
8
+ 4. Rasterize the signed PDF again — sign those final images
9
+ (ensures verification always matches the exact output PDF pixels)
10
+
11
+ Output is an image-PDF (not text-searchable) — correct for tamper-evident signing.
12
+ """
13
+
14
+ import io
15
+ from pdf2image import convert_from_bytes
16
+ from reportlab.pdfgen import canvas as rl_canvas
17
+ from reportlab.lib.utils import ImageReader
18
+ from pypdf import PdfReader
19
+
20
+ from core import embed_watermark, sign_image, verify_image, detect_mode
21
+
22
+ DPI = 300
23
+
24
+
25
+ def pdf_to_images(pdf_bytes: bytes, dpi: int = DPI) -> list:
26
+ """Rasterize all PDF pages. Returns list of PIL Images."""
27
+ return convert_from_bytes(pdf_bytes, dpi=dpi)
28
+
29
+
30
+ def images_to_pdf(images: list) -> bytes:
31
+ """Pack PIL Images into a single PDF. Each image fills its page."""
32
+ buf = io.BytesIO()
33
+ first_w, first_h = images[0].size
34
+ c = rl_canvas.Canvas(buf, pagesize=(first_w * 72 / DPI, first_h * 72 / DPI))
35
+ for img in images:
36
+ w_px, h_px = img.size
37
+ pw, ph = w_px * 72 / DPI, h_px * 72 / DPI
38
+ c.setPageSize((pw, ph))
39
+ img_buf = io.BytesIO()
40
+ img.save(img_buf, format="PNG")
41
+ img_buf.seek(0)
42
+ c.drawImage(ImageReader(img_buf), 0, 0, width=pw, height=ph)
43
+ c.showPage()
44
+ c.save()
45
+ buf.seek(0)
46
+ return buf.read()
47
+
48
+
49
+ def sign_pdf(pdf_bytes: bytes, secret: str, dpi: int = DPI, strength: float = 0.03):
50
+ """
51
+ Sign all pages of a PDF.
52
+
53
+ Critical: signatures are computed from the FINAL rasterized output PDF,
54
+ not from intermediate watermarked images. This ensures verify_pdf always
55
+ produces matching results without round-trip pixel drift.
56
+
57
+ Returns: (signed_pdf_bytes, signatures_list, n_pages)
58
+ """
59
+ # Step 1: rasterize original pages
60
+ images = pdf_to_images(pdf_bytes, dpi=dpi)
61
+
62
+ # Step 2: embed watermark into each page
63
+ watermarked = []
64
+ for img in images:
65
+ mode = detect_mode(img)
66
+ wm = embed_watermark(img, secret, strength=strength, mode=mode)
67
+ watermarked.append(wm)
68
+
69
+ # Step 3: pack watermarked pages into signed PDF
70
+ signed_pdf_bytes = images_to_pdf(watermarked)
71
+
72
+ # Step 4: rasterize signed PDF — sign these exact pixels
73
+ final_images = pdf_to_images(signed_pdf_bytes, dpi=dpi)
74
+ n_pages = len(final_images)
75
+ sigs = []
76
+ for i, img in enumerate(final_images):
77
+ sig = sign_image(img, secret, mode='document')
78
+ sig["page_index"] = i
79
+ sigs.append(sig)
80
+
81
+ return signed_pdf_bytes, sigs, n_pages
82
+
83
+
84
+ def verify_pdf(pdf_bytes: bytes, secret: str, signatures: list) -> list:
85
+ """
86
+ Verify all pages of a signed PDF against per-page signatures.
87
+ Returns list of per-page result dicts with page_index key.
88
+ """
89
+ images = pdf_to_images(pdf_bytes, dpi=DPI)
90
+ results = []
91
+ for i, (img, sig) in enumerate(zip(images, signatures)):
92
+ result = verify_image(img, secret, sig)
93
+ result["page_index"] = i
94
+ results.append(result)
95
+ return results
96
+
97
+
98
+ def get_pdf_page_count(pdf_bytes: bytes) -> int:
99
+ """Quick page count without rasterization."""
100
+ return len(PdfReader(io.BytesIO(pdf_bytes)).pages)