Scope & limitations
The conversion core is broad and spec-driven, but ECMA-376 is vast and Ream does not implement all of it — this page is an honest map of what works and what doesn’t yet.
Implemented
Section titled “Implemented”Input — Ream parses Word (.docx and legacy .doc), Excel (.xlsx
and legacy .xls), PowerPoint (.pptx and legacy .ppt) and PDF, sniffed
from the bytes. The legacy binary .doc / .xls / .ppt (the OLE2/CFB formats) are
read through a shared container reader — see WordprocessingML / SpreadsheetML /
PresentationML below. A PowerPoint deck becomes
one page per slide at the deck size, its shapes read as positioned content: text
boxes (run formatting, alignment, vertical anchor, bullets, indents),
layout/master placeholders, pictures, shapes (geometry/fill/stroke/gradient),
DrawingML tables, embedded charts, theme colours, slide/master backgrounds,
grouped shapes and run hyperlinks; not read (each a graceful loss): text autofit
shrink, picture backgrounds, picture placeholders, alpha/roman list numbering.
PDF input handles classic and modern compressed files
(cross-reference streams, object streams) and encrypted files (RC4 / AES; the
user password is passed to Ream.parse, defaulting to the empty permissions-only
case). A tagged PDF (including the ones Ream writes) is rebuilt from
its structure tree — headings, paragraphs, tables, list items, reading order; an
untagged PDF is reconstructed heuristically from glyph positions (lines by
baseline, paragraphs by spacing, headings by relative font size, and a clean
two-column page split at its central gutter), which is approximate. Text comes back via each font’s /ToUnicode map; raster images,
hyperlinks and vector shapes are lifted back out too (JPEG verbatim, other
images re-encoded as PNG with soft-mask alpha, /Link URIs re-attached to the
text, filled paths, stroked lines and shading-pattern gradients turned into
shapes). Clipping paths and clip-bounded (sh) shadings are not read.
Output — convert('pdf'), convert('svg') (a page-stack preview),
convert('html') (flowed, needs no fonts), convert('docx') (write
WordprocessingML back out) and convert('xlsx') (write SpreadsheetML back out —
spreadsheet input only). The writers are for normalization, sanitization,
in-browser editing, and round-tripping. The docx round-trip is semantic, not
byte-exact, but complete — text, tables, images, lists, links, headers/footers,
multi-section geometry, footnotes/endnotes, charts and OfficeMath all write
back. The xlsx round-trip preserves the whole grid surface — cells, styles,
merges, the print model, conditional formatting, sparklines, tables and embedded
charts — and is byte-stable across a read↔write loop.
WordprocessingML (§17)
- Text, runs and the full style cascade (
docDefaults→ styles → direct formatting). - Tables — auto / fixed layout, §17.4 border-conflict resolution, cell shading,
vertical merge and grid span, nested tables, table styles (
w:tblStylewith conditional formats: banding, first/last row/column). - Lists and numbering (
abstractNum, level overrides), multi-level. - Sections — per-section page size and orientation, headers and footers,
multi-column layout (
w:cols). - Hyperlinks — external (clickable PDF annotations + HTML
<a>, scheme-allowlisted) and internal: bookmarks become named destinations /#-anchors. - Fields —
PAGE/NUMPAGESrender real page numbers in headers and footers. - Footnotes and endnotes — notes at the bottom of the referencing page behind Word’s separator rule; endnotes after the body.
- Review comments (
w:commentReference) — a bracketed superscript marker in the text and a “Comments” section after the body with each comment’s author, date and content (PDF and HTML). Reply threads and resolved state come fromcommentsExtended.xml(replies nest under their parent, resolved threads are flagged); the commented range (w:commentRangeStart/End) is highlighted; author identities resolve frompeople.xml. As an opt-in, comments can also be emitted as native PDF sticky-note annotations (commentAnnotations, interactive output only). - SmartArt — rendered from the diagram’s pre-rendered DrawingML drawing
(
diagrams/drawing#.xml) as positioned shapes; a file with no drawing fallback degrades to a graceful loss rather than an empty space. - Inline and floating images (PNG / JPEG / JPEG2000), including legacy VML
pictures (
<w:pict>/<w:object>— ActiveX and OLE-object previews, images from older Word); floating drawings (wp:anchor) render outside the text flow — wrap-none (incl.behindDoc) for watermarks/stamps/text boxes, and side wrapping (square/tight/through) where the body text flows around the exclusion area. - Tracked changes (
w:ins/w:del). - Reads both Transitional and Strict (ISO 29500) packages; block-level
content controls (
w:sdt) flow through. - Legacy
.doc(Word 97–2003) — the binaryWordDocumentstream inside the OLE2/CFB container is read for its text and formatting: the FIB locates the piece table (the CLX), whose pieces — 16-bit Unicode or 8-bit Windows-1252 (“compressed”) — are stitched back into the document text and split into paragraphs, while the CHPX and PAPX runs (located through thePlcfBteChpx/PlcfBtePapxbin tables and decoded from their sprms) carry bold / italic / underline / font size onto each run and alignment / indentation / spacing onto each paragraph. Tables are reconstructed too — the in-table paragraphs (marked by thefInTable/fTtpPAPX flags, cells delimited by the0x07cell mark) become a row-and-cell grid, with per-column widths, per-cell borders and vertical merges (the table definition’sTC80array) and per-cell background shading (sprmTDefTableShd’scvBackfill) — and inline images are extracted (the picture character’s CHPX points at a PICF in theDatastream; the raster blip is pulled out and sized from the PICF). Fields resolve to their cached result — the field code (PAGE,NUMPAGES,REF, …) is dropped and the stored result text kept. The section’s headers and footers are lifted from thePlcfHddstories (best-effort: the binary story ordering can’t be ground-truthed here, so only well-formed stories are surfaced). List items (sprmPIlfo/sprmPIlvl) render with their resolved number format — a real “1.” / “a)” / “iii.” or the bullet glyph, from theLST/LVL/LFOtables. So an old.docrenders to PDF/SVG/HTML and re-writes to.docx. Legacy drawing shapes / text boxes and comments are not read (re-save as.docxfor full fidelity); an encrypted file yields no text. The shared CFB reader (src/core/ole) is the same keystone.xlsuses.
SpreadsheetML (§18)
- Grids, shared strings, number formats and dates (incl. the 1904 date system).
- Legacy
.xls(BIFF8, Excel 97–2003) — the binaryWorkbookstream inside the OLE2/CFB container is read into the same grid model, so an old.xlsrenders to PDF/SVG/HTML and even re-writes to.xlsx. Cell values, structure (sheets, shared strings, merges, column widths, custom row heights, frozen panes, the 1904 flag), styling — fonts, fills, borders, number formats and alignment from the FONT/FORMAT/XF records, with colours resolved through the BIFF colour palette — embedded pictures (from the Office-Drawing/Escher BLIP store), embedded charts (the BIFF chart substream, plotted from the worksheet cells its AI records reference) and drawing shapes (autoshapes + text boxes, from the Escher shape records and their TXO text) are read, plus cell hyperlinks (the HLINK record’s URL moniker), the page-setup print model (orientation, scale, fit-to-page, margins, gridlines, centering, header/footer and manual page breaks), defined names (named ranges plus the print area and repeated titles, from the NAME records), cell comments (the Note record’s author + the text-box text), data validation (the rule type, ranges and alistrule’s in-cell dropdown) and conditional formatting — both the classiccellIs/expressionrules (with their differential fill / font colour) and the 2007 colour-scale / data-bar / icon-set extensions (the CF12 records, present only in a.xlsre-saved by Excel 2007+); only a graphical rule whose colour is theme-relative rather than a literal value degrades gracefully. - The print model — gridline suppression, print area, fit-to-page scaling, repeated print titles, manual page breaks, horizontal/vertical centering, and column-band pagination: a sheet wider than the page (and not fit-to-width) splits across pages, all rows of the left columns first, then the next band (“down, then over”), honouring manual column breaks — instead of being squeezed onto one page width.
- Frozen panes round-trip through the writer and become sticky header rows / columns in HTML output. They do not affect PDF — in Excel freezing is a view setting that does not print (the printed repeat is the print titles above).
- Conditional formatting — the highlight rules:
cellIs(compare-to-constant),top10(top/bottom N or N %),aboveAverage(mean, optionally shifted by N standard deviations),duplicateValues/uniqueValues(value frequency across the range, numbers by value and text case-insensitively) and the text tests (containsText/notContainsText/beginsWith/endsWith); plus the visual encodingscolorScale(2/3-stop gradients),dataBar(in-cell bars, with a zero axis so negative values run the other way) andiconSet— traffic lights, arrows, signs, symbols (check / exclamation / cross), flags, ratings (a bar meter) and quarters (a clock pie). The cross-cell rules resolve against the range’s value extent. Alsoexpression— an arbitrary formula evaluated per cell by a built-in formula engine against the workbook’s cached values (no recalculation): ~140 functions (logic / info incl.IFS/SWITCH/XORand theIS*family; the math, trig and exponential set; theSUM/COUNT/MEDIAN/SUMPRODUCT/STDEV/VAR/PERCENTILEaggregates and theCOUNTIF(S)/SUMIF(S)/AVERAGEIF(S)predicates; text; date / time; theMATCH/INDEX/VLOOKUP/HLOOKUPlookups andROW/COLUMN), sheet-qualified references (Sheet2!A1), defined names, inline array constants (OR(A1={1,3,5})) and the per-cell relative-reference shift. A construct genuinely beyond a deterministic per-cell predicate — a 3-D reference, a dynamic-array /LAMBDAidiom, or a volatile / dynamic-reference function (RAND/INDIRECT/OFFSET) — evaluates to an error, so the rule simply does not paint rather than misrender. AndtimePeriod(today / this-week / last-month … windows). Both stay deterministic:timePeriodandTODAY()/NOW()read an explicit reference date you pass asnow(never the system clock), so without one those clock-relative rules simply don’t paint. The highest-priority matching rule claims the cell’s fill / font; a data bar or icon applies on top. - Sparklines — per-cell line / column / win-loss mini charts, including cross-sheet data ranges and blank-cell gaps.
- Excel tables (
xl/tables) — banded rows and a styled header row, the colours resolved from the named table style against the workbook theme. - Pivot tables (
xl/pivotTables) — Excel caches the pivot’s output cells in the sheet, so the grid renders as data; on top of that Ream applies the named pivot style (pivotTableStyleInfo) — banded rows and a styled header — and emphasises grand-total / subtotal rows. The pivot is not recomputed from its cache. - Data validation (
<dataValidations>) — alistvalidation paints an in-cell dropdown affordance (a small button + ▾ at the cell’s right edge) on every cell of its range, in PDF and HTML; the constraint, its formulas and the input/error prompts round-trip throughconvert('xlsx'). - Slicers (
xl/slicers+xl/slicerCaches) — a slicer panel renders as a captioned button box after the grid (the way chart frames do). A native-table slicer fills its buttons from the referenced table column’s distinct values and highlights the items the column’s autofilter keeps; an OLAP/pivot slicer whose items live in a pivot cache degrades to a caption-only box. - Charts, pictures and shapes anchored to the sheet (the worksheet drawing part) render after the grid — a picture keeps its bytes; a shape its preset/custom geometry, fill, outline and text body (reusing the DrawingML shape readers).
- Cell hyperlinks (
<hyperlinks>) — an externalr:idresolves to a URL and the covered cell becomes a clickable link (PDF/Linkannotation, HTML<a>). - Header/footer text (
<headerFooter>) — Excel’s&-code mini-language (&L/&C/&Rregions,&P/&Npage-number fields resolved per page,&Asheet name,&B/&Ibold/italic) renders in the page margins. - Cell formatting details — in-cell rich text (a shared string built from
several
<r>runs renders one document-model run per run, each with its own bold / italic / underline / colour / size / super- or sub-script); wrapped text (wrapTextcells keep their full text and wrap to the cell, growing the row); left indent (indent); non-solid pattern fills (gray / hatch patterns blend foreground over background to a representative solid) and gradient fills (summarised to the mean of their stops); diagonal cell borders (up / down strokes across the cell); text rotation (textRotation— rotated / vertical cells render their text stacked top-to-bottom); and shrink-to-fit (shrinkToFitscales the cell’s font down to its column width). - Cell comments / notes — legacy notes (
xl/comments) and modern threaded comments (xl/threadedComments, authors resolved throughxl/persons) are read and listed in a “Comments” section after the grid — a heading then one line per comment,<cell> — <author>: <text>— mirroring Excel’s “print comments at end of sheet”. The legacy VML note box is ignored; only the text + author are surfaced. - Form controls — checkboxes, option buttons, spinners, scroll bars, list /
drop-downs and buttons (the worksheet’s
<controls>, each resolved to itsctrlProppart for type + state) are listed in a “Form controls” section after the grid, each with a type-appropriate affordance and its state ([x]/[ ]for a checked box,(o)for an option button, the value for a spinner). The control’s anchored VML shape isn’t drawn in place. - ActiveX controls — the embedded OLE controls (
<oleObjects>→xl/activeX) are listed in an “ActiveX controls” section the same way: theprogIdgives the control type and the<ax:ocxPr>property bag its visible state (caption, checked/value, group). A control persisted only to its binary.bin(MS-OFORMS) renders as its type without the caption — reading that property bag from the OLE/CFB stream is the remaining piece.
PresentationML (§19)
- Each slide is a page at the deck size (
p:sldSz); shapes are floating content positioned from theira:xfrm. - Text boxes (
p:sp) — runs (size, bold/italic/underline, colour, latin font), paragraph alignment, the body vertical anchor, bullets (a:buCharand auto-numbereda:buAutoNum) and per-level indents. - Placeholders — title/body/number shapes inherit geometry and per-level text
styles from the slide layout → master (
p:txStyles). - Pictures (
p:pic), shapes with geometry/fill/stroke/gradient, DrawingML tables (a:tbl) and embedded charts (c:chart). - SmartArt — rendered from the diagram’s pre-rendered DrawingML drawing
(
dsp:spTree) as positioned shapes; no drawing fallback ⇒ a graceful loss. - Theme colours (
a:clrScheme), slide/master backgrounds (p:bg) painted behind the content, and groups (p:grpSp) mapped through their child transform. - Run hyperlinks (
a:hlinkClick) → clickable PDF annotations / HTML<a>. - Legacy
.ppt(PowerPoint 97–2003) — the binaryPowerPoint Documentstream inside the OLE2/CFB container, reached through the Current User → UserEditAtom → PersistDirectoryAtom indirection. Each slide becomes one page at the deck size (the DocumentAtom slide size, in master units); the text is read from the TextChars / TextBytes atoms with run formatting (bold / italic / underline / size / colour from the StyleTextPropAtom) and paragraph alignment / indent level, and embedded images are pulled from the Pictures stream (OfficeArtBlip referenced by a shape’spib). A shape that carries a slide anchor (OfficeArtClientAnchor) is positioned at its rectangle — text boxes and pictures become floating content, like the.pptxreader; an un-anchored shape (e.g. a placeholder that inherits master geometry) flows in reading order. Decorative autoshapes are read as vector shapes — the preset type from the OfficeArtFSP (or, for a freeform, its exact custom geometry walked from thepVertices/pSegmentInfoarrays) plus their fill / line colour, whether a literal sRGB value, a system colour (the default Windows scheme) or one resolved through the slide’s colour scheme (the master’s when the slide follows it); only a palette-relative colour degrades gracefully.
Graphics & math
- DrawingML shapes (preset and custom geometry, gradients, group shapes, theme colors).
- Charts — bar/column, line, pie/doughnut, area, scatter, stacked.
- OfficeMath — fractions, scripts, radicals, n-ary operators, functions, limits, delimiters, matrices, accents; inline and display.
Typography
- Type0 + CIDFontType2 embedding with subsetting.
- Knuth–Plass line breaking, Liang hyphenation (en / ru).
- OpenType ligatures and kerning (GSUB/GPOS), mark positioning.
- BiDi (UAX #9), Arabic cursive joining.
- Renderer-compatibility
layoutProfile('word'/'libreoffice') — matches a target renderer’s line-height model, line breaking and default kerning; with the metric-compatible open substitutes (Carlito / Caladea / Arimo / Tinos / Cousine) this tracks the target closely without its private font metrics.
PDF / compliance
- PDF/A-1, -2, -3 at levels a / b / u — all formally veraPDF-validated.
- PDF/UA-1 (ISO 14289-1) — veraPDF-validated, alone or combined with PDF/A-2a in a single file.
- Tagged PDF — logical structure tree, headings, tables, lists (with
Lblmarkers), figures with alt text, links with alternate descriptions, footnoteNoteelements,/Lang, pagination artifacts. - Encryption — AES-256 (ISO 32000-2 R6) via WebCrypto, with permission flags.
- Digital signatures — PKCS#7 detached, ECDSA; object streams; JPEG2000 images.
Not yet
Section titled “Not yet”- Byte-for-byte visual reproduction of another renderer.
layoutProfileplus the metric-compatible substitutes get a target tool’s page geometry close — without its private font metrics — but pixel-identical output is a non-goal: that would need the exact same font file and the renderer’s internal glyph rounding. - A few format edge cases degrade gracefully — re-save the original in its modern
format for byte-exact fidelity, but each is a missing detail, never a wrong one:
a legacy
.pptshape’s palette-relative colour (literal, scheme- and system-relative colours resolve) or a rare arc / ellipse freeform segment (it falls back to the path’s preset bounds); in a legacy.xls, a 2007 Excel table’s banded style / autofilter (its shared-feature record is one even Apache POI leaves unparsed, so the cell values still render — only the table’s banding is absent) and a colour-scale / data-bar / icon-set rule (a 2007CF12record) whose colour is theme-relative rather than a literal value (the rules themselves, with literal or palette colours, are read); and an ActiveX CommandButton / Label whose caption is persisted only to a binary.bin(the MorphData control family — check box / option / toggle / text / combo / list — and every property-bag control are read, with their caption and value).
Validation
Section titled “Validation”Development is corpus-driven: documents are converted, compared against a LibreOffice “golden” render (structural text diff + rasterized visual diff), and PDF/A output is gated through veraPDF. Untrusted corpus files run inside a locked-down Docker sandbox.