Skip to content

Port O365-aligned XLSX rendering parity findings from MiniPdf Rust #2

Open

Description

@shps951023

Context

A benchmark-driven XLSX-to-PDF parity session was completed in mini-software/MiniPdf using Microsoft 365 Excel as the authoritative visual reference and LibreOffice as an auxiliary implementation reference.

This issue records the reusable parsing and layout findings for MiniExcel-Rust. No MiniExcel-Rust code change is claimed yet.

Objective

Evaluate and port the applicable OOXML behaviors:

  • explicit print scale combined with print-title rows
  • page count and vertical pagination
  • containsText conditional formatting
  • custom Excel date and thousands number formats
  • leftward overflow for right-aligned text
  • odd-footer fields, page number, and total page count
  • font-family/style selection based on the workbook

Reference outcome

For the PO_anonymized.xlsx benchmark, the MiniPdf Rust implementation improved from:

Metric Initial Final
Pages 8/9 9/9
Text similarity 0.9751 1.0000
Visual similarity 0.8200 0.9002
Overall score 0.8180 0.9601

The remaining differences are primarily rasterization, anti-aliasing, border thickness, and minor drawing geometry.

Acceptance criteria

  • Identify which findings apply to MiniExcel-Rust's current XLSX model/parser
  • Add focused fixtures for print titles, explicit scale, conditional formatting, number formats, overflow, and footer fields
  • Compare output primarily against Microsoft 365 Excel
  • Use LibreOffice behavior/source only as supporting evidence
  • Preserve page counts and avoid visual regressions greater than 0.002
  • Document font-family and font-style selection separately from PDF subset aliases

A detailed session logic flow and implementation notes will be posted as a follow-up comment.

Activity

  1. shps951023 commented on Sep 3, 2026

    @shps951023
    MemberAuthor

    Session logic flow

    Run focused benchmark
      -> verify artifacts are fresh
      -> compare MiniPdf PNG/PDF with Microsoft 365 reference
      -> use Python (PyMuPDF, Pillow, NumPy, OOXML ZIP/XML parsing) to measure differences
      -> classify font, formatting, clipping, pagination, drawing, and footer causes
      -> locate the owning Rust parser/layout path
      -> make one narrowly scoped change
      -> rerun the focused benchmark
      -> accept only measurable improvement with unchanged page/text correctness
      -> run adjacent-case and full classic regressions
      -> refresh canonical artifacts
    

    Summary

    The original canonical MiniPdf image was stale: it lacked the footer even though the latest isolated renderer already supported it. After regenerating artifacts and completing the remaining layout calibration, the final benchmark reached 9/9 pages, text similarity 1.0000, visual similarity 0.9002, and overall score 0.9601 against Microsoft 365 Excel.

    Microsoft 365 was the scored reference. LibreOffice was used only to triangulate OOXML behavior and possible implementation strategies.

    Root causes and fixes

    1. Pagination and scale

      • Explicit pageSetup scale=95 combined with print-title rows was effectively shrunk twice vertically.
      • The renderer now keeps that vertical path at full calibrated scale.
      • A narrowly scoped horizontal calibration is applied only when print-title rows coexist with explicit scale below 100% and the sheet is not using fit-to-width.
    2. Conditional formatting

      • The workbook used a containsText rule for the green Accept result.
      • Parsing and application of that differential fill were added.
    3. Number/date formatting

      • Excel serial dates needed the workbook's day-short-month-year format.
      • Zero-decimal numbers needed thousands separators.
      • Generic format handling was added rather than hard-coding cell values.
    4. Text overflow

      • Right-aligned Excel text can overflow into empty cells on its left.
      • The clipping region was extended leftward until a blocking cell, fixing Re-inspection: being rendered as only tion:.
    5. Footer rendering

      • oddFooter was parsed into left/center/right sections.
      • &P and &N page fields, font declarations, size controls, and footer margin were expanded at render time after total page count was known.
    6. Artifact freshness

      • Canonical output must be regenerated after renderer changes. Comparing timestamps, report metrics, footer presence, and SHA256 hashes distinguished stale images from real renderer differences.

    Font conclusion

    This was not primarily a font problem.

    • MPFont* names in the PDF are subset resource aliases, not font-family names.
    • The Rust CLI registered Windows Calibri regular/bold/italic/bold-italic and Arial variants.
    • Measured spans matched Microsoft 365 closely:
      • title: Calibri Bold 15.2 pt vs 15.24 pt
      • body: Calibri 7.6 pt vs 7.56 pt
      • Accept: Calibri Bold Italic 9.5 pt vs 9.48 pt
      • page footer: Arial 6.0 pt vs 5.76 pt

    Validation

    • Focused PO benchmark: 9/9 pages, text 1.0000, visual 0.9002, overall 0.9601
    • Rust workspace: 86 tests passed
    • Classic XLSX regression: 191/191 cases matched; no page-count changes and no visual regression worse than 0.002
    • Adjacent explicit-scale/print-title issue cases retained their previous metrics
    • Final PDF, four comparison PNGs, and three-way composite matched the refreshed canonical artifacts by SHA256

    Recommended MiniExcel-Rust implementation order

    1. Build focused OOXML fixtures for each behavior above.
    2. Keep parsed print settings, footer tokens, and style/number-format metadata in structured models.
    3. Separate parse-time semantics from render-time page-dependent expansion.
    4. Use Microsoft 365 output as the acceptance baseline.
    5. Gate each change with focused and corpus-level regression reports.
  2. radelqui commented on Sep 3, 2026

    @radelqui

    Here's how I'd approach porting these findings into MiniExcel-Rust concretely.

    1. Print scale + print titles: parse pageSetup@scale from the sheet's XML and definedName name="_xlnm.Print_Titles" from the workbook. Apply the scale factor to your layout engine's row height and column width before pagination, then repeat the title rows on every page break.

    2. Vertical pagination: once scale is applied, walk rows top-down, accumulating scaled heights. When the sum exceeds the printable page height (paper size minus margins minus header/footer), insert a break. That should get you 9/9 pages instead of 8.

    3. containsText conditional formatting: in the worksheet XML look for cfRule type="containsText" with a text child. Do a simple substring match on the cell's display value, then apply the dxf style from dxfs in styles.xml.

    4. Number formats: parse numFmtId from cellXfs, look it up in numFmts. For dates, map format tokens (yyyy, mm, dd) to chrono format strings. For thousands separators, detect the #,##0 pattern and format accordingly. Always test against what Excel actually outputs, not the spec's examples.

    5. Right-aligned overflow: when a cell is right-aligned and the rendered text is wider than the column, let it spill leftward into adjacent empty cells. Clip only when the neighbor has content.

    6. Odd-footer fields: parse oddFooter from the sheet XML. Replace &P with the current page number and &N with total page count.

    7. Font selection: resolve the font from the fonts collection in styles.xml, matching family, bold, and italic. Fall back to a sensible default if the font isn't embedded.

    For fixtures, create minimal .xlsx files (one concern per file) and compare rendered output against Excel 365 screenshots or PDF exports.

    What if every question like this got answered instantly and the fix was handled for you on the spot? That's exactly what ME Office AI does. Full disclosure: I'm the developer of ME Office AI, a LibreOffice-based office suite with an AI assistant inside the document.
    https://apps.microsoft.com/detail/9PCV4RCJCFXV?cid=gh-meoffice

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions