FeaturesSecurityPricingCompareDevelopersBlogStart Free
BlogAugust 4, 2026

How faithfully does a data room render a spreadsheet?

Four Excel producer dialects, rendered through our viewer and through LibreOffice, then compared cell by cell in a real browser. Display strings, fonts, alignment, merges and Saudi number formats matched on every cell. Fills scored 99.7% on the run below, against a committed 99.3% floor that fails the build on regression, and the cells that miss do so on purpose.

Method

The subject is the spreadsheet viewer the room portal ships, imported from its own source file rather than reimplemented for the test. The reference is LibreOffice 26.2.1.2 running headless. Both read the same four fixture workbooks.

The fixtures are four producer dialects, because the same visual result is written to disk four different ways depending on which program authored the file: openpyxl, LibreOffice Calc, a legacy .xls converted to .xlsx, and a hand-written spec-minimal ECMA-376 file that omits every optional attribute a parser might be relying on.

  1. Fixture
    One .xlsx, four producer dialects
  2. Two renders
    Viewer in Chromium; LibreOffice headless to HTML and PDF
  3. Extract
    Computed style per cell from both sides
  4. Score
    Six dimensions, plus a coarse pixel check
  5. Gate
    Below the committed baseline, the run exits 1
One pass of tests/fidelity/run.mjs. The viewer and the reference read the same bytes.

LibreOffice is converted twice. The HTML export is its interpretation: display strings, fills, bold and italic, alignment and merged ranges, with conditional formatting already evaluated into concrete background colours. The PDF export, rasterised at 150 DPI, is its rendering, used only for a coarse pixel comparison. The viewer side is extracted from the live DOM in headless Chromium at a 1440 by 1000 viewport, reading getComputedStyle on every td[data-cell-r]. Cells with zero width are skipped on both sides, because both have already dropped hidden rows and columns.

Six dimensions are scored, each as a pass count over a comparable count. Fill colours allow a tolerance of 8 per channel. Alignment compares only center and right, because LibreOffice stamps align="left"onto every default text cell including cells on right-to-left sheets, so a left match would be counting filler. Merges accept LibreOffice's column span both as written and net of columns its own export dropped.

Saudi number formats are the exception to using LibreOffice as truth. For Hijri dates and Arabic-Indic digits it is measurably wrong, so those cells are scored against a value computed from the specification with Intl.DateTimeFormat and Intl.NumberFormat. That divergence has its own article.

Reproducing it

The runner is tests/fidelity/run.mjs. It needs Bun, LibreOffice on the path as soffice, pdftoppm, and Playwright's Chromium. From a checkout:

node tests/fidelity/run.mjs --rebuild
node tests/fidelity/run.mjs --fixtures ksa-golden-openpyxl

Each run writes a timestamped JSON result and a PNG of the rendered grid, then compares every score against tests/fidelity/baseline.json. Any dimension below its committed floor prints the regression and exits 1, so a fidelity loss fails the build rather than shipping.

Results

Two different numbers matter here and they are easy to conflate. The run score is what one execution measured on one day. The committed floor is the gate underneath it, the level below which the build exits non-zero. A run that beats its floor is not a licence to quote the run as the guarantee, so both are below.

Run of 4 August 2026, four fixtures, 2,596 display-string comparisons in total.

  • Display strings100%
  • Fills99.7%
  • Font style100%
  • Alignment100%
  • Merged cells100%
  • KSA formats100%
Pass rate by dimension on the 4 August 2026 run, ksa-golden-openpyxl.xlsx. Counts: 875/875 display strings, 877/880 fills, 877/877 font styles, 819/819 alignments, 2/2 merges, 5/5 KSA number formats.
Per-fixture scores from the 4 August 2026 run. No data means the dimension had no comparable cells on that fixture.
CriterionopenpyxlLO Calc.xls round tripspec-minimal
Display strings100%100%100%100%
Fills99.7%99.7%100%100%
Font style100%100%100%no data
Alignment100%100%100%100%
Merged cells100%100%100%no data
KSA number formats100%100%100%no data
Pixel similarityCoarse, informational only90.1%89.8%91%96%
Per-fixture scores from the 4 August 2026 run. No data means the dimension had no comparable cells on that fixture.
Committed floors from tests/fidelity/baseline.json. A run scoring below any of these exits non-zero.
CriterionopenpyxlLO Calc.xls round tripspec-minimal
Display strings100100100100
Fills99.399.399.8100
Font style100100100not gated
Alignment100100100100
Merged cells100100100not gated
KSA number formats100100100not gated
Committed floors from tests/fidelity/baseline.json. A run scoring below any of these exits non-zero.

Five of the six dimensions are gated at 100, so a single regressed cell fails the run. Fills are gated lower because three cells miss for a reason described below, and pinning the floor at 99.7 would make the gate fire on ordinary colour-scale rounding. The floor is 99.3% or better; the run scored 99.7%. Quote both, and never the floor alone as though it were the measurement.

On the spec-minimal fixture, three dimensions are not gated at all. LibreOffice renders none of the bold fonts or number formats its own styles.xml declares for that file, so there is nothing to compare against and the runner records the reason rather than scoring a fake 100%.

The Sifrsys spreadsheet viewer showing a right-to-left Arabic budget sheet with Hijri dates, Arabic-Indic digits, colour-scale fills and a merged title row.
The viewer output the scorer measured, captured by the run itself. Right-to-left column order, Hijri dates in column E, Arabic-Indic digits in rows 11 and 12.
99.7%
Fills on the 4 Aug 2026 run
877 of 880 cells
99.3%
Committed floor for fills, below which the build fails
tests/fidelity/baseline.json
100%
Display strings matched
2,596 cells across four dialects

The three cells that did not match

All three are colour-scale conditional formatting, and the divergence is on purpose. Excel computes the minimum and maximum of a colour scale over every cell in the range, including hidden ones. The render model never ships a hidden cell's value to the browser, because the minimum of a range containing a hidden walkaway price would leak that price to anyone who read the gradient. The gradient is therefore computed over visible cells only, and three cells land on a slightly different shade.

Fidelity matters beyond appearance. A viewer that keeps the grid keeps the cell addresses, which is what makes a citation like Revenue!B14:F34 possible in the first place. Convert the workbook to a PDF and the addresses are gone, so the best a citation can do is name a page.

Related: Arabic, Hijri and RTL in data rooms · What a citation has to point at · How we test our own data room · Pricing

Every paid tier includes the viewer and the AI that cites it, with no per-page fees.

FAQ

Questions about the fidelity test.

Fidelity is the share of cells whose rendered appearance in the viewer matches the appearance the file's author specified. It covers the display string, the fill colour, bold and italic, alignment, merged ranges, and number formats. A data room that stores a file perfectly but draws it wrongly has failed, because the reader acts on what is drawn.
LibreOffice converts headless from the command line, so the comparison runs unattended in CI and anyone can repeat it on any machine. It also records the number format it resolved for each cell, which makes its own failures visible rather than silent. Where LibreOffice is demonstrably wrong, such as Hijri dates and Arabic-Indic digits, those cells are scored against the OOXML specification instead.
Most convert the workbook to PDF or to images first, which flattens the grid and drops the cell addresses. Fidelity then depends on the converter rather than the viewer. Our test measures an interactive grid built from a parsed model of the file, which is why cell addresses survive and can be cited.
It does not measure charts or pivot tables, which the viewer does not render yet. It does not recompute formulas: an uncomputed formula renders an em rule by policy rather than a stale or invented value. It does not measure very large workbooks, password-protected files, or macros.
Start Free