Skip to content

Commit 4ad587b

Browse files
committed
Enhance PDF rendering capabilities:
- Improved Excel to PDF conversion by ensuring images are rendered correctly even when they are outside the data area. - Added support for embedded JPEG and PNG images in PDF pages, including proper handling of image dimensions and rendering. - Updated PDF writer to accommodate image XObjects, ensuring images are placed correctly in the output PDF. - Enhanced text extraction and comparison in benchmarks to account for structural differences and improve accuracy in similarity scoring. - Introduced content-coverage penalties in pixel comparison to better reflect visual differences between rendered PDFs.
1 parent 7037163 commit 4ad587b

138 files changed

Lines changed: 6548 additions & 85 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ dotnet_ai_azure_book/
2626
tests/MiniPdf.Scripts/output/
2727
tests/MiniPdf.Scripts/pdf_output/
2828

29-
## Benchmark reports (ignore JSON/MD, keep HTML & images)
30-
tests/MiniPdf.Benchmark/reports/comparison_report.json
31-
tests/MiniPdf.Benchmark/reports/comparison_report.md
29+
## Benchmark
30+
tests/MiniPdf.Benchmark/reference_pdfs/
31+
tests/MiniPdf.Benchmark/__pycache__/

README.md

Lines changed: 192 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ A minimal, zero-dependency .NET library for converting Excel (.xlsx) files to PD
2020
## Features
2121

2222
- **Excel-to-PDF** — Convert `.xlsx` files to paginated PDF with automatic column layout
23+
- **Embedded images** — JPEG and PNG images embedded in Excel sheets are rendered in the PDF output
2324
- **Zero dependencies** — Uses only built-in .NET APIs (no external packages)
2425
- **Valid PDF 1.4** output
2526
- **Word-to-PDF** — In development
@@ -55,19 +56,19 @@ byte[] pdfBytes = MiniPdf.ConvertToPdf(stream);
5556

5657
## Benchmark
5758

58-
MiniPdf output is compared against LibreOffice as the reference renderer across **60 classic test cases**.
59+
MiniPdf output is compared against LibreOffice as the reference renderer across **90 classic test cases** (including 30 image-embedding cases).
5960

6061
| Category | Count | Threshold |
6162
|---|---|---|
62-
| 🟢 Excellent | 54 | ≥ 90% |
63-
| 🟡 Acceptable | 5 | 70% – 90% |
63+
| 🟢 Excellent | 82 | ≥ 90% |
64+
| 🟡 Acceptable | 7 | 70% – 90% |
6465
| 🔴 Needs Improvement | 1 | < 70% |
6566

66-
**Average overall score: 96.2%** (text similarity 40% + visual similarity 40% + page count 20%)
67+
**Average overall score: 96.4%** (text similarity 40% + visual similarity 40% + page count 20%)
6768

6869
### Visual Comparison
6970

70-
All 60 test cases comparing MiniPdf output (left) vs LibreOffice reference (right). Page 1 shown for multi-page results.
71+
All 90 test cases comparing MiniPdf output (left) vs LibreOffice reference (right). Page 1 shown for multi-page results.
7172

7273
<table>
7374
<tr><th>Test Case</th><th>MiniPdf</th><th>LibreOffice (Reference)</th><th>Score</th></tr>
@@ -123,7 +124,7 @@ All 60 test cases comparing MiniPdf output (left) vs LibreOffice reference (righ
123124
<td><b>classic09</b><br/>Long text<br/><i>MiniPdf: 1 page<br/>Reference: 12 pages</i></td>
124125
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic09_long_text_p1_minipdf.png" width="320"/></td>
125126
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic09_long_text_p1_reference.png" width="320"/></td>
126-
<td>🔴 20.3%</td>
127+
<td>🔴 21.9%</td>
127128
</tr>
128129
<tr>
129130
<td><b>classic10</b><br/>Special XML characters</td>
@@ -207,7 +208,7 @@ All 60 test cases comparing MiniPdf output (left) vs LibreOffice reference (righ
207208
<td><b>classic23</b><br/>Unicode text</td>
208209
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic23_unicode_text_p1_minipdf.png" width="320"/></td>
209210
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic23_unicode_text_p1_reference.png" width="320"/></td>
210-
<td>🟡 86.1%</td>
211+
<td>🟡 87.1%</td>
211212
</tr>
212213
<tr>
213214
<td><b>classic24</b><br/>Red text</td>
@@ -333,7 +334,7 @@ All 60 test cases comparing MiniPdf output (left) vs LibreOffice reference (righ
333334
<td><b>classic44</b><br/>Employee roster</td>
334335
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic44_employee_roster_p1_minipdf.png" width="320"/></td>
335336
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic44_employee_roster_p1_reference.png" width="320"/></td>
336-
<td>🟡 81.6%</td>
337+
<td>🟡 87.3%</td>
337338
</tr>
338339
<tr>
339340
<td><b>classic45</b><br/>Sales by region (4 sheets)</td>
@@ -375,7 +376,7 @@ All 60 test cases comparing MiniPdf output (left) vs LibreOffice reference (righ
375376
<td><b>classic51</b><br/>Product catalog</td>
376377
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic51_product_catalog_p1_minipdf.png" width="320"/></td>
377378
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic51_product_catalog_p1_reference.png" width="320"/></td>
378-
<td>🟡 77.2%</td>
379+
<td>🟡 81.9%</td>
379380
</tr>
380381
<tr>
381382
<td><b>classic52</b><br/>Pivot summary</td>
@@ -405,13 +406,13 @@ All 60 test cases comparing MiniPdf output (left) vs LibreOffice reference (righ
405406
<td><b>classic56</b><br/>Alternating row colors</td>
406407
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic56_alternating_row_colors_p1_minipdf.png" width="320"/></td>
407408
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic56_alternating_row_colors_p1_reference.png" width="320"/></td>
408-
<td>🟢 98.9%</td>
409+
<td>� 75.4%</td>
409410
</tr>
410411
<tr>
411412
<td><b>classic57</b><br/>CJK-only sheet</td>
412413
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic57_cjk_only_p1_minipdf.png" width="320"/></td>
413414
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic57_cjk_only_p1_reference.png" width="320"/></td>
414-
<td>🟡 84.0%</td>
415+
<td>🟡 85.2%</td>
415416
</tr>
416417
<tr>
417418
<td><b>classic58</b><br/>Mixed numeric formats</td>
@@ -431,6 +432,186 @@ All 60 test cases comparing MiniPdf output (left) vs LibreOffice reference (righ
431432
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic60_large_wide_table_p1_reference.png" width="320"/></td>
432433
<td>🟢 94.9%</td>
433434
</tr>
435+
<tr>
436+
<td><b>classic61</b><br/>Product card with image</td>
437+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic61_product_card_with_image_p1_minipdf.png" width="320"/></td>
438+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic61_product_card_with_image_p1_reference.png" width="320"/></td>
439+
<td>🟢 99.3%</td>
440+
</tr>
441+
<tr>
442+
<td><b>classic62</b><br/>Company logo header</td>
443+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic62_company_logo_header_p1_minipdf.png" width="320"/></td>
444+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic62_company_logo_header_p1_reference.png" width="320"/></td>
445+
<td>🟢 98.8%</td>
446+
</tr>
447+
<tr>
448+
<td><b>classic63</b><br/>Two products side by side</td>
449+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic63_two_products_side_by_side_p1_minipdf.png" width="320"/></td>
450+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic63_two_products_side_by_side_p1_reference.png" width="320"/></td>
451+
<td>🟢 99.2%</td>
452+
</tr>
453+
<tr>
454+
<td><b>classic64</b><br/>Employee directory with photo</td>
455+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic64_employee_directory_with_photo_p1_minipdf.png" width="320"/></td>
456+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic64_employee_directory_with_photo_p1_reference.png" width="320"/></td>
457+
<td>🟢 98.1%</td>
458+
</tr>
459+
<tr>
460+
<td><b>classic65</b><br/>Inventory with product photos</td>
461+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic65_inventory_with_product_photos_p1_minipdf.png" width="320"/></td>
462+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic65_inventory_with_product_photos_p1_reference.png" width="320"/></td>
463+
<td>🟢 98.2%</td>
464+
</tr>
465+
<tr>
466+
<td><b>classic66</b><br/>Invoice with logo</td>
467+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic66_invoice_with_logo_p1_minipdf.png" width="320"/></td>
468+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic66_invoice_with_logo_p1_reference.png" width="320"/></td>
469+
<td>🟢 96.2%</td>
470+
</tr>
471+
<tr>
472+
<td><b>classic67</b><br/>Real estate listing</td>
473+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic67_real_estate_listing_p1_minipdf.png" width="320"/></td>
474+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic67_real_estate_listing_p1_reference.png" width="320"/></td>
475+
<td>🟢 98.5%</td>
476+
</tr>
477+
<tr>
478+
<td><b>classic68</b><br/>Restaurant menu with photos</td>
479+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic68_restaurant_menu_p1_minipdf.png" width="320"/></td>
480+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic68_restaurant_menu_p1_reference.png" width="320"/></td>
481+
<td>🟢 92.3%</td>
482+
</tr>
483+
<tr>
484+
<td><b>classic69</b><br/>Image-only sheet</td>
485+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic69_image_only_sheet_p1_minipdf.png" width="320"/></td>
486+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic69_image_only_sheet_p1_reference.png" width="320"/></td>
487+
<td>🟢 99.1%</td>
488+
</tr>
489+
<tr>
490+
<td><b>classic70</b><br/>Product catalog with images</td>
491+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic70_product_catalog_with_images_p1_minipdf.png" width="320"/></td>
492+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic70_product_catalog_with_images_p1_reference.png" width="320"/></td>
493+
<td>🟢 96.3%</td>
494+
</tr>
495+
<tr>
496+
<td><b>classic71</b><br/>Multi-sheet with images (3 sheets)</td>
497+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic71_multi_sheet_with_images_p1_minipdf.png" width="320"/></td>
498+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic71_multi_sheet_with_images_p1_reference.png" width="320"/></td>
499+
<td>🟢 99.2%</td>
500+
</tr>
501+
<tr>
502+
<td><b>classic72</b><br/>Bar chart image with data</td>
503+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic72_bar_chart_image_with_data_p1_minipdf.png" width="320"/></td>
504+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic72_bar_chart_image_with_data_p1_reference.png" width="320"/></td>
505+
<td>🟢 97.7%</td>
506+
</tr>
507+
<tr>
508+
<td><b>classic73</b><br/>Event flyer with banner</td>
509+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic73_event_flyer_with_banner_p1_minipdf.png" width="320"/></td>
510+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic73_event_flyer_with_banner_p1_reference.png" width="320"/></td>
511+
<td>🟢 93.9%</td>
512+
</tr>
513+
<tr>
514+
<td><b>classic74</b><br/>Dashboard with KPI image</td>
515+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic74_dashboard_with_kpi_image_p1_minipdf.png" width="320"/></td>
516+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic74_dashboard_with_kpi_image_p1_reference.png" width="320"/></td>
517+
<td>🟢 97.2%</td>
518+
</tr>
519+
<tr>
520+
<td><b>classic75</b><br/>Certificate with seal</td>
521+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic75_certificate_with_seal_p1_minipdf.png" width="320"/></td>
522+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic75_certificate_with_seal_p1_reference.png" width="320"/></td>
523+
<td>🟢 99.2%</td>
524+
</tr>
525+
<tr>
526+
<td><b>classic76</b><br/>Product image grid (2×2)</td>
527+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic76_product_image_grid_p1_minipdf.png" width="320"/></td>
528+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic76_product_image_grid_p1_reference.png" width="320"/></td>
529+
<td>🟢 98.5%</td>
530+
</tr>
531+
<tr>
532+
<td><b>classic77</b><br/>News article with hero image</td>
533+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic77_news_article_with_hero_image_p1_minipdf.png" width="320"/></td>
534+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic77_news_article_with_hero_image_p1_reference.png" width="320"/></td>
535+
<td>🟢 97.3%</td>
536+
</tr>
537+
<tr>
538+
<td><b>classic78</b><br/>Small icon per row</td>
539+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic78_small_icon_per_row_p1_minipdf.png" width="320"/></td>
540+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic78_small_icon_per_row_p1_reference.png" width="320"/></td>
541+
<td>🟢 95.9%</td>
542+
</tr>
543+
<tr>
544+
<td><b>classic79</b><br/>Wide panoramic banner</td>
545+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic79_wide_panoramic_banner_p1_minipdf.png" width="320"/></td>
546+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic79_wide_panoramic_banner_p1_reference.png" width="320"/></td>
547+
<td>🟢 97.7%</td>
548+
</tr>
549+
<tr>
550+
<td><b>classic80</b><br/>Portrait tall image</td>
551+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic80_portrait_tall_image_p1_minipdf.png" width="320"/></td>
552+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic80_portrait_tall_image_p1_reference.png" width="320"/></td>
553+
<td>🟢 98.9%</td>
554+
</tr>
555+
<tr>
556+
<td><b>classic81</b><br/>Step-by-step with images</td>
557+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic81_step_by_step_with_images_p1_minipdf.png" width="320"/></td>
558+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic81_step_by_step_with_images_p1_reference.png" width="320"/></td>
559+
<td>🟢 98.7%</td>
560+
</tr>
561+
<tr>
562+
<td><b>classic82</b><br/>Before/after images</td>
563+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic82_before_after_images_p1_minipdf.png" width="320"/></td>
564+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic82_before_after_images_p1_reference.png" width="320"/></td>
565+
<td>🟢 97.4%</td>
566+
</tr>
567+
<tr>
568+
<td><b>classic83</b><br/>Color swatch palette</td>
569+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic83_color_swatch_palette_p1_minipdf.png" width="320"/></td>
570+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic83_color_swatch_palette_p1_reference.png" width="320"/></td>
571+
<td>🟢 97.4%</td>
572+
</tr>
573+
<tr>
574+
<td><b>classic84</b><br/>Travel destination cards</td>
575+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic84_travel_destination_cards_p1_minipdf.png" width="320"/></td>
576+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic84_travel_destination_cards_p1_reference.png" width="320"/></td>
577+
<td>🟢 97.8%</td>
578+
</tr>
579+
<tr>
580+
<td><b>classic85</b><br/>Lab results with image</td>
581+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic85_lab_results_with_image_p1_minipdf.png" width="320"/></td>
582+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic85_lab_results_with_image_p1_reference.png" width="320"/></td>
583+
<td>🟢 97.2%</td>
584+
</tr>
585+
<tr>
586+
<td><b>classic86</b><br/>Software screenshot features</td>
587+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic86_software_screenshot_features_p1_minipdf.png" width="320"/></td>
588+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic86_software_screenshot_features_p1_reference.png" width="320"/></td>
589+
<td>🟢 97.1%</td>
590+
</tr>
591+
<tr>
592+
<td><b>classic87</b><br/>Sports results with logos</td>
593+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic87_sports_results_with_logos_p1_minipdf.png" width="320"/></td>
594+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic87_sports_results_with_logos_p1_reference.png" width="320"/></td>
595+
<td>🟢 99.5%</td>
596+
</tr>
597+
<tr>
598+
<td><b>classic88</b><br/>Image after data</td>
599+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic88_image_after_data_p1_minipdf.png" width="320"/></td>
600+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic88_image_after_data_p1_reference.png" width="320"/></td>
601+
<td>🟢 98.9%</td>
602+
</tr>
603+
<tr>
604+
<td><b>classic89</b><br/>Nutrition label with image</td>
605+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic89_nutrition_label_with_image_p1_minipdf.png" width="320"/></td>
606+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic89_nutrition_label_with_image_p1_reference.png" width="320"/></td>
607+
<td>🟢 96.5%</td>
608+
</tr>
609+
<tr>
610+
<td><b>classic90</b><br/>Project status with milestones</td>
611+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic90_project_status_with_milestones_p1_minipdf.png" width="320"/></td>
612+
<td><img src="tests/MiniPdf.Benchmark/reports/images/classic90_project_status_with_milestones_p1_reference.png" width="320"/></td>
613+
<td>🟡 87.2%</td>
614+
</tr>
434615
</table>
435616

436617
## License

0 commit comments

Comments
 (0)