22"""
33ZPDF Correctness Benchmark
44
5- Compares text extraction accuracy against MuPDF (reference), Tika (PDFBox), and pdftotext (Poppler).
5+ Compares text extraction accuracy and speed against:
6+ - MuPDF (mutool) - accuracy reference
7+ - pdfium (pypdfium2) - speed reference (Chrome's PDF engine)
8+ - Tika (PDFBox)
9+ - pdftotext (Poppler)
610
711Usage:
812 python benchmark/accuracy.py [pdf_files...]
1721sys .path .insert (0 , str (Path (__file__ ).parent .parent / "python" ))
1822import zpdf
1923
24+ # Try to import pypdfium2 for pdfium comparison
25+ try :
26+ import pypdfium2 as pdfium
27+ HAS_PDFIUM = True
28+ except ImportError :
29+ HAS_PDFIUM = False
30+
2031
2132def normalize (text : str ) -> str :
2233 import re
@@ -86,6 +97,22 @@ def extract_pdftotext(pdf_path: str) -> tuple:
8697 return result .stdout , elapsed
8798
8899
100+ def extract_pdfium (pdf_path : str ) -> tuple :
101+ """Extract text using pdfium (Chrome's PDF engine)."""
102+ if not HAS_PDFIUM :
103+ return "" , 0
104+
105+ start = time .perf_counter ()
106+ pdf = pdfium .PdfDocument (pdf_path )
107+ texts = []
108+ for page in pdf :
109+ textpage = page .get_textpage ()
110+ texts .append (textpage .get_text_bounded ())
111+ text = "\f " .join (texts )
112+ elapsed = (time .perf_counter () - start ) * 1000
113+ return text , elapsed , len (pdf )
114+
115+
89116def check_tool (name : str , cmd : list ) -> bool :
90117 try :
91118 subprocess .run (cmd , capture_output = True )
@@ -114,23 +141,31 @@ def main():
114141 print ("mutool (MuPDF) not found - required as reference" )
115142 sys .exit (1 )
116143
117- print ("ZPDF Accuracy Benchmark" )
144+ print ("ZPDF Accuracy & Speed Benchmark" )
118145 print ()
119- print ("Reference: MuPDF (mutool)" )
120- print (f"Tools: zpdf (reading order), zpdf (stream order), mutool{ ', tika' if has_tika else '' } { ', pdftotext' if has_pdftotext else '' } " )
146+ print ("Reference: MuPDF (mutool) for accuracy" )
147+ tools = ["zpdf" , "mutool" ]
148+ if HAS_PDFIUM :
149+ tools .append ("pdfium" )
150+ if has_tika :
151+ tools .append ("tika" )
152+ if has_pdftotext :
153+ tools .append ("pdftotext" )
154+ print (f"Tools: { ', ' .join (tools )} " )
121155 print ()
122156
123157 for pdf in pdf_files :
124158 print (f"--- { pdf .name } ---" )
125159
126160 try :
127161 # Extract with all tools
128- # zpdf with reading order (default)
129- zpdf_ro_text , zpdf_ro_ms , pages = extract_zpdf (str (pdf ), reading_order = True )
130- # zpdf with stream order (for comparison)
131- zpdf_so_text , zpdf_so_ms , _ = extract_zpdf (str (pdf ), reading_order = False )
162+ zpdf_text , zpdf_ms , pages = extract_zpdf (str (pdf ), reading_order = False )
132163 mutool_text , mutool_ms = extract_mutool (str (pdf ))
133164
165+ pdfium_text , pdfium_ms = ("" , 0 )
166+ if HAS_PDFIUM :
167+ pdfium_text , pdfium_ms , _ = extract_pdfium (str (pdf ))
168+
134169 tika_text , tika_ms = ("" , 0 )
135170 if has_tika :
136171 tika_text , tika_ms = extract_tika (str (pdf ))
@@ -140,51 +175,55 @@ def main():
140175 pdftotext_text , pdftotext_ms = extract_pdftotext (str (pdf ))
141176
142177 # Normalize
143- zpdf_ro_norm = normalize (zpdf_ro_text )
144- zpdf_so_norm = normalize (zpdf_so_text )
178+ zpdf_norm = normalize (zpdf_text )
145179 mutool_norm = normalize (mutool_text )
180+ pdfium_norm = normalize (pdfium_text ) if HAS_PDFIUM else ""
146181 tika_norm = normalize (tika_text ) if has_tika else ""
147182 pdftotext_norm = normalize (pdftotext_text ) if has_pdftotext else ""
148183
149184 # Compute metrics vs MuPDF reference
150185 print (f"Pages: { pages } " )
151186 print ()
152- print (f"{ 'Tool' :<20 } { 'Char Acc' :>10} { 'WER' :>8} { 'Time' :>10} { 'Speedup ' :>10 } " )
153- print ("-" * 60 )
187+ print (f"{ 'Tool' :<12 } { 'Char Acc' :>10} { 'WER' :>8} { 'Time' :>10} { 'pages/sec ' :>12 } " )
188+ print ("-" * 54 )
154189
155- # zpdf (reading order) - default
156- acc = char_accuracy (zpdf_ro_norm , mutool_norm )
157- wer = word_error_rate (zpdf_ro_norm , mutool_norm )
158- speedup = mutool_ms / zpdf_ro_ms if zpdf_ro_ms > 0 else 0
159- print (f"{ 'zpdf (reading)' :<20} { acc :>9.1%} { wer :>7.1%} { zpdf_ro_ms :>8.0f} ms { speedup :>9.1f} x" )
160-
161- # zpdf (stream order) - for comparison
162- acc = char_accuracy (zpdf_so_norm , mutool_norm )
163- wer = word_error_rate (zpdf_so_norm , mutool_norm )
164- speedup = mutool_ms / zpdf_so_ms if zpdf_so_ms > 0 else 0
165- print (f"{ 'zpdf (stream)' :<20} { acc :>9.1%} { wer :>7.1%} { zpdf_so_ms :>8.0f} ms { speedup :>9.1f} x" )
190+ # zpdf
191+ acc = char_accuracy (zpdf_norm , mutool_norm )
192+ wer = word_error_rate (zpdf_norm , mutool_norm )
193+ pps = pages / (zpdf_ms / 1000 ) if zpdf_ms > 0 else 0
194+ print (f"{ 'zpdf' :<12} { acc :>9.1%} { wer :>7.1%} { zpdf_ms :>8.0f} ms { pps :>10,.0f} " )
166195
167196 # MuPDF (reference = 100%)
168- print (f"{ 'mutool' :<20} { '100.0%' :>10} { '0.0%' :>8} { mutool_ms :>8.0f} ms { '1.0x' :>10} " )
197+ pps = pages / (mutool_ms / 1000 ) if mutool_ms > 0 else 0
198+ print (f"{ 'mutool' :<12} { '100.0%' :>10} { '0.0%' :>8} { mutool_ms :>8.0f} ms { pps :>10,.0f} " )
199+
200+ # pdfium
201+ if HAS_PDFIUM :
202+ acc = char_accuracy (pdfium_norm , mutool_norm )
203+ wer = word_error_rate (pdfium_norm , mutool_norm )
204+ pps = pages / (pdfium_ms / 1000 ) if pdfium_ms > 0 else 0
205+ print (f"{ 'pdfium' :<12} { acc :>9.1%} { wer :>7.1%} { pdfium_ms :>8.0f} ms { pps :>10,.0f} " )
169206
170207 # Tika
171208 if has_tika :
172209 acc = char_accuracy (tika_norm , mutool_norm )
173210 wer = word_error_rate (tika_norm , mutool_norm )
174- speedup = mutool_ms / tika_ms if tika_ms > 0 else 0
175- print (f"{ 'tika' :<20 } { acc :>9.1%} { wer :>7.1%} { tika_ms :>8.0f} ms { speedup :>9.1f } x " )
211+ pps = pages / ( tika_ms / 1000 ) if tika_ms > 0 else 0
212+ print (f"{ 'tika' :<12 } { acc :>9.1%} { wer :>7.1%} { tika_ms :>8.0f} ms { pps :>10,.0f } " )
176213
177214 # pdftotext
178215 if has_pdftotext :
179216 acc = char_accuracy (pdftotext_norm , mutool_norm )
180217 wer = word_error_rate (pdftotext_norm , mutool_norm )
181- speedup = mutool_ms / pdftotext_ms if pdftotext_ms > 0 else 0
182- print (f"{ 'pdftotext' :<20 } { acc :>9.1%} { wer :>7.1%} { pdftotext_ms :>8.0f} ms { speedup :>9.1f } x " )
218+ pps = pages / ( pdftotext_ms / 1000 ) if pdftotext_ms > 0 else 0
219+ print (f"{ 'pdftotext' :<12 } { acc :>9.1%} { wer :>7.1%} { pdftotext_ms :>8.0f} ms { pps :>10,.0f } " )
183220
184221 print ()
185222
186223 except Exception as e :
187224 print (f"Error: { e } " )
225+ import traceback
226+ traceback .print_exc ()
188227 print ()
189228
190229
0 commit comments