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), the text
style the deck states once, layout/master placeholders and the decoration they
carry, backgrounds (solid, gradient, picture or theme reference), pictures with
their recolouring, shapes (geometry/fill/stroke/gradient), DrawingML tables with
the style they name, embedded charts and objects, theme colours through the
deck’s colour map, grouped shapes and run hyperlinks; text set to shrink is
measured and shrunk to fit its box, and a SmartArt is laid out from its own
parts whether or not the file cached a drawing. Not read (a graceful loss):
alpha/roman list numbering.
PDF input handles classic and modern compressed files
(cross-reference streams, object streams) and encrypted files (RC4 / AES; the
user or owner password is passed to Ream.parse, defaulting to the empty
permissions-only case, and one that opens the file as neither is refused with
WrongPasswordError). A tagged PDF (including the ones Ream writes) is rebuilt from
its structure tree — headings, paragraphs, tables, list items, reading order,
the elements a document names for itself followed through its /RoleMap to the
standard ones they stand for, and an inline element (a span, a link, a formula
set in a line, MathML) read as a stretch of its parent’s line rather than a
block of its own (§14.8); an
untagged PDF is reconstructed heuristically from glyph positions (lines by
baseline, paragraphs by spacing and by the indent the page set them with,
headings by relative font size, a word the line broke in half put back together,
and the page read for its own gutters: as many columns of PROSE as it was set
in — a page of labels with figures against the right margin breaks a dozen lines
at the same x and is not two columns, so it is read across and each label keeps
its figure — blocks set SIDE BY SIDE, each at a leading of its own, set as the
borderless table a writer sets them in and each read down on its own (an
invoice’s stack of dates and numbers beside the address it bills; blocks whose
lines share their baselines stay one text, read across on its stops), a page
RULED into a grid rebuilt as a table with its measured columns and rows, a gap
written as a TAB with the stop the page set the piece after it at, where no
word space could make it or where it lands on a stop the lines around it share,
consecutive lines standing on the SAME stops rebuilt as a table of their own,
with a column whose figures end together set against its right edge, a line
drawn between two blocks given to the block it separates as that paragraph’s
border, a rule typed out of hyphens left on the line of its own it was written
on, a band repeated at the top or bottom of every sheet as a running head or
foot with its regions and its page number as a field, and a bracketed block of
rows and columns as an OfficeMath matrix), which is approximate. A page in
columns is read down them: a gutter is the white between two columns, not where
most of a ragged column’s lines end; a JUSTIFIED column, flush on both sides, is
prose as surely as a ragged one; and a column a few ems wide beside a gap the
page’s lines run across is a table’s. A page that turns to its next column short
of the foot breaks there, and a last page set in balanced columns ends their
section on that page. A line set wholly in a typewriter face is CODE, a listing
kept line for line with its comments on the stops the page set them at; a row of
four cells and more is a TABLE’s, read whole where it reaches across the gutter,
the line over it its heading, a rule across it the border of the row under it and
a rule drawn down it the division between two of its columns; a drawing and the
words set on it — boxes and arrows, a bar chart — are one FIGURE, set as an
inline group in its column; a line that opens with a bullet or a hanging label
opens a list item, and an INDEX’s entries hang, each a paragraph with the lines
that carry it on. A box filled behind a single line — a heading on a dark bar —
is that paragraph’s shading. A running band’s numeral that changes from page to
page is the page’s NUMBER, counted in the numerals the page prints: front matter
in roman starts a section of its own, a book that leaves its blank pages out is
counted all the same, and a book that signs its even and odd pages at opposite
edges gets a foot to each side (§17.10.1). A page whose words run down its sheet
comes back as a section set that way (§17.6.20 tbRl). The margins are
measured to the page’s INK — a blank is
not ink, and a picture is: a sheet whose one line of text sits over a picture
wider than that line keeps a measure the picture still fits. Text comes
back via each font’s /ToUnicode map (a range of it judged code by code,
§9.10.3), or — where a composite font ships none — from the reverse cmap of
the program it embeds (§9.10.2), or — where a SIMPLE
font ships none, which is every PDF from TeX — from the glyph NAMES its
/Encoding /Differences states (§9.6.6.1) over the base encoding the font
declares (Annex D.2 Standard, WinAnsi and MacRoman, or a symbolic font’s own
built-in one — ZapfDingbats is a font of PICTURES and is read as pictures,
Annex D.6), and a code the differences leave .notdef is no character at all.
The names TeX gives its Greek and its mathematics — alpha, epsilon1,
asteriskmath — are read like any other, and an accent TeX strikes over a letter
it has no glyph for is composed with that letter: “naïve”, not “na¨ıve”. A word
space is read where the page STEPS one as wide as most of its face’s space as
well as where it draws one, a dot stepped on after a word is the rest of an
ellipsis, and a dash closed up to its word takes no space where a line breaks.
A ligature comes back as the letters it joins — “first” is first to a search
and to an edit — and a composite font the file names but does not embed, with
Identity-H and no /ToUnicode, is read in the glyph order of the face it
names where that is one of the Microsoft core faces (§9.7.4 — Arial, Times New
Roman, Verdana and their like keep the Macintosh standard order for their first
258 glyphs). A face that cannot be addressed by character at all — a TrueType
program with no usable cmap (§9.6.6.4) — is not guessed at: its codes are
glyph indices, the text is marked unrecoverable and the glyphs are DRAWN from
their outlines, a word at a time, as are the glyphs of a font that names them
after nothing a reader knows (CFF charstrings run as the programs they are,
glyf outlines and their composites, post names over the Macintosh standard
order). Punctuation a font names no character for — a producer that maps every
mark to U+0000 leaves an invoice with no hyphen, colon or bracket in its text —
is read off the SHAPE the page draws, where that shape says one thing: a flat
bar at the middle of the lower case is a dash, two stacked dots a colon, one
small mark on the baseline a full stop or a comma. The
predefined CMaps a file names instead of embedding are known (§9.7.5.2),
including the vertical ones that advance the pen down the page. A run names its
face’s FAMILY — Inter for Inter-SemiBold, without the producer’s debris (a
,Bold, a -Identity-H, a serial number) — with the kind of face it is, so a
program that lacks it substitutes one of the same kind. The weight and slant
come from the /FontDescriptor, and where it states no weight from the header
of the program itself; a face’s own name outweighs the flags that contradict it
(a -Light face is not bold for a ForceBold hint), and a family the
substitution tables know decides whether it is a serif, a sans or a typewriter
face. The embedded font programs themselves are carried into the output, so a
rebuilt page is set in the type it was set in. Written to .docx, each face the
page drew with is rebuilt as TrueType — the outline of every character it drew,
at the advance the page set it at, with its kerning pairs (GPOS kern) and the
ligatures the page drew (GSUB liga) — and embedded where its licence (OS/2
fsType) lets it, and a word space TeX shrank to fit a line is written as narrow
as TeX set it (§17.3.2.43 w:w): a re-set line breaks where the page broke it,
on a machine that has none of the document’s fonts.
Raster images, hyperlinks and vector artwork are lifted back out too (JPEG
verbatim but for a baseline CMYK one, which every viewer reads in Adobe’s
inverted convention and which is decoded instead, Adobe’s YCCK included, and
given back in the colours its inks paint; other images re-encoded as PNG with
soft-mask
alpha — including JBIG2, the bilevel coding a scanner stores a page of text in
(ISO/IEC 14492:
generic, refinement, symbol-dictionary, text and halftone regions, arithmetic
and Huffman, and a dictionary whose symbols are REFINEMENTS of the ones it
already holds), and CCITT Group 3 two-dimensional, where every line begins with
the bit that says how it is coded — /Link URIs re-attached to the text,
filled paths, stroked lines — dashed in the page’s own pattern and capped as it
caps them (§8.4.3.6 d, §8.4.3.3 J) — and shading-pattern gradients turned
into shapes), colour set through a named space (§8.6.8 cs / sc, which is how
every PDF a browser prints states it) — including the CIE
spaces CalGray (§8.6.5.6), CalRGB (§8.6.5.7, gamma and matrix into XYZ,
adapted onto D65 by Bradford) and Lab (§8.6.5.8), an /ICCBased stream
decoded through the profile it carries (§8.6.5.5), and a Separation or
DeviceN run through its own tint transform (§8.6.6.4/§8.6.6.5) — for an
IMAGE as much as for a fill — which needs
all four kinds of PDF function (§7.10: sampled, exponential, stitching, and the
type-4 PostScript calculator) — along with clipping
paths (§8.5.4 W / W*, applied to paths and pictures alike, and carried into
a turned picture’s OWN axes), stencil image masks (§8.9.6.2, painted in the
page’s non-stroking colour), colour-key masks (§8.9.6.4 — the samples a /Mask
array names left unpainted, held against the samples before any /Decode),
images written into the content stream (§8.9.7 BI … EI), tiling patterns
(§8.7.3 — read as the pattern’s colour at the strength its tile covers where
they fill type, and where they fill a shape with a tile drawn in lines and dots,
a hatch or a grid, which comes back as the tone it gives the page rather than as
the hatch itself; a tile that holds a picture is lifted as that picture; an
uncoloured pattern, §8.7.3.3, takes the colour scn gives it), constant alpha
(§11.6.4.4 — a fill’s /ca and a stroke’s /CA through the gs operator),
Type 3 glyphs (§9.6.5 — content streams, drawn as the artwork they are in every
reading: such a font has no program and no face, so its codes are never re-set
in a substitute, which would draw the letter a second time),
annotation appearance streams (§12.5.5, so a form field draws itself — inside
its /BBox, which is its clip, and what it writes, a button’s caption or a
field’s value, stands where the annotation stands, over the box it is written
on, rather than being read into the page’s lines; a form that asks for its
appearances rebuilt, §12.7.2
/NeedAppearances, shows its fields’ values rather than the stale text of the
old appearance), the text
render modes (§9.3.6 Tr — stroked type keeps its outline, and the invisible
modes an OCR layer uses are marked rather than painted), the box a viewer shows
(§14.11.2 /CropBox — which sizes the page AND clips the marks outside it; a
media box with no area is the Letter sheet a page that states none gets) and
the page’s own /Rotate (§14.11.1). A file’s optional content is honoured
(§8.11 — the layers its default configuration turns off stay off, on /OC … BDC
marked content and on an XObject’s own /OC, through an /OCMD‘s policies).
An annotation the file gives no /AP is drawn from its own properties (§12.5.5
— Ink, Line, Square, Circle, Polygon, PolyLine, a text field’s /V set in its
/DA, a free-text note’s /Contents set in its /DA (§12.5.6.6), and the text
markups’ QuadPoints applied to the words they cover). The
built-in metrics of the 14 standard fonts (§9.6.2.2) stand in where a file
embeds neither the face nor a /Widths array. A gradient the page paints with
a bare sh is read as the shape it covers (§8.7.4.5, function-based type 1
included), in the colour space the shading dictionary states; an axial one runs
the way its matrix carries it onto the page, and a shape it fills shows the part
of its axis the shape covers (§8.7.4.5.3) rather than the whole axis squeezed
across it; text filled with
one comes back in its colours rather than in black, and the loss report says
where the gradient’s shape was flattened to a single colour.
Output — convert('pdf'), convert('svg') (a page-stack preview),
convert('html') (flowed, needs no fonts) — a workbook’s HTML is Excel’s window and
its SVG a picture of each sheet, whole (SpreadsheetML below) — convert('md') (GitHub-Flavored
Markdown), 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, and so do a paragraph’s tab stops (§17.3.1.38) and its own rules
(§17.3.1.24 w:pBdr), and the page-number fields a running foot is written with
(§17.16.19 w:fldSimple — PAGE and NUMPAGES, so the second sheet does not
say it is the first). So do a paragraph’s shading (§17.3.1.31 w:shd) and how
far its rules stand off it, its widow control and whether it keeps with the next
paragraph or keeps its lines together (§17.3.1.44, §17.3.1.14–15); a run’s width
and character spacing (§17.3.2.43 w:w, §17.3.2.35 w:spacing), its kerning and
its ligatures (w:kern, w14:ligatures); a line break, as w:br rather than a
newline inside the text; a line spaced in lines, in the 240ths w:lineRule="auto"
counts; a table’s indent (§17.4.65 w:tblInd); a drawing group (wpg:wgp); and a
section’s page-number numerals (§17.6.12 w:pgNumType) and the way its lines run
(§17.6.20 w:textDirection). The document’s settings write back too — headers of
its own for the even pages, a gutter at the head, doNotExpandShiftReturn and the
compatibility mode (§17.15.1) — and the styles part states the defaults the
writer leaves out, which Word would otherwise take from its own Normal template.
A picture keeps how it is drawn into its frame — its crop
(§20.1.8.55 a:srcRect), its turn and mirror (§20.1.7.6 a:xfrm) and its
opacity (a:alphaModFix) — so a cropped picture read from Word and saved again
no longer comes back whole and squeezed into the frame its crop was sized for; a
fill and a line keep the opacity they are painted at (§20.1.2.3.1 a:alpha),
and a line its own dash pattern (§20.1.8.21 a:custDash). A run names its
font’s family, and the font table says what kind of face that is (§17.8.3), so
a reader that lacks the family substitutes one of the same kind. A section
states where it must OPEN as well as where it begins: a
w:type of oddPage or evenPage (§17.6.22) survives the round trip and is
laid out as Word lays it out, printing the blank sheet it implies — charged to
the section that ends, not the one that starts. A drawing that states where on
the page it belongs is written back as
the wp:anchor that puts it there (§20.4.2.3) rather than flattened into the
text flow. No image part the format cannot show goes into the package: JPEG
2000 is not among the parts §15.2.14 admits, and is dropped with a loss that
names it rather than written as a hole. 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.
Stream filters the reader does not implement (§7.4) — a filter it cannot
undo leaves that stream unread, and when the unread one is the cross-reference
the whole document is missing. Rather than carry a decoder for every filter
anyone might write, Ream.parse(bytes, { filters }) takes one from the caller:
import brotliPromise from 'brotli-dec-wasm'; // ~200 KB wasm, browsers and workersconst brotli = await brotliPromise;Ream.parse(pdf, { filters: { BrotliDecode: (b) => brotli.decompress(b) } });On a server the runtime already carries one, so brotliDecompressSync from
node:zlib goes in the same slot. Brotli is RFC 7932’s context-modelled
Huffman scheme over a 122 KB static dictionary, and a filter almost nothing
produces does not belong in every browser bundle — which is why this is a hook
rather than a dependency. Absent, or throwing, the filter is reported
unreadable by name rather than producing an empty document silently. FlateDecode, LZW, RunLength, ASCII85,
ASCIIHex, CCITT, DCT, JPX and JBIG2 need nothing supplied.
The two PDF readings — a
conversion cannot have both readings at once: words that move cannot agree with
rules that do not, so anchored artwork over reflowed text lines up with none of
it. The FILE decides which it gets. A paper is mostly LINES with a rule or two
between them and is read as a re-flowable document — paragraphs and tables in
reading order, from the structure tree where the file has one, and where it has
none, in the columns of PROSE the page was set in, with its running head and
foot kept out of the body, its blocks set side by side read down each on its
own, its ruled grids, its rows-on-the-same-stops and its tables across the
columns rebuilt as tables, its listings kept line for line, its figures kept
with their labels, and the lines it drew between blocks given to the paragraphs
they separate; what a form
field or a button writes still stands where the page placed it, over the box it
is written on.
A form or a
drawing is mostly MARKS (one form sets 28 numbered rows in 355 ruled boxes) and
is read as a page: every line stands where its glyphs stand, beside the artwork,
with no reading order, no paragraphs and no tables. The marks are counted
against the baselines on the MEDIAN page, so one plan folded into a report does
not make the report a plan; the reader records which reading it took and why.
There is no option to override it. A caller holding a Uint8Array cannot know
whether it is a paper or a form, so asking it to choose only moved the guess
outward — and left the choice the library actually makes untested, because
everything that measured it pinned the other one. Markdown, which places
nothing, reads a page kept only for its table’s rules — or for letters the file
names no character for — as the document it is without them: a ruled estimate
comes back as its rows, its sums whole.
Markdown (convert('md')) — GitHub-Flavored Markdown, a flow medium like
HTML: no pagination, no layout, no fonts, zero I/O. It keeps what a document
says and drops how it looks. Kept: headings (from w:outlineLvl, falling
back to a Heading N / Title style id), bold, italic, strikethrough, ordered
and bullet lists with their real numbers and nesting, tables as GFM pipe tables
with per-column alignment, hyperlinks (through the same scheme allowlist the
HTML writer uses), pictures, footnotes and endnotes as GFM footnotes, review
comments as footnotes attributed to their author, bookmarks as inline anchors,
and the text inside shapes. A spreadsheet opens each sheet with a heading
carrying its tab name — markdown has no pages to tell one sheet from the next
by, so { sheetNames: false } is there for the bare tables — and every cell
whole and in its own column, however narrow the column a page cuts it to. Underline and super/subscript survive as <u> /
<sup> / <sub>, which GFM parses as inline html.
Dropped, each reported once in the loss report rather than silently: alignment,
indents, colour, font family and size, tab stops, page and column breaks,
headers and footers, charts, shape geometry, and math. A page break can be kept
instead — { pageBreaks: 'rule' } writes the --- thematic break a slide deck
wants, since the .pptx and .ppt readers mark each slide boundary with one
and it is the only structure a deck has. Merged cells flatten —
markdown’s table is a plain grid — and a nested table flattens into the cell
that holds it. Pictures are inlined as data: URIs by default; pass
{ images: 'link' } to reference them under ./media/ and write the bytes
yourself, or { images: 'drop' } to omit them.
Password-protected packages (ECMA-376 §2.3, MS-OFFCRYPTO)
- An encrypted
.docx/.xlsx/.pptxis not a zip at all: the whole OPC package is ciphertext inside an OLE container. Given the password (Ream.parse(bytes, { password })) it is decrypted and read like any other document. Both schemes in the wild are supported — the standard one (Office 2007 and LibreOffice: AES-ECB under a key spun from 50 000 SHA-1 rounds) and the agile one (Office 2010 and later: an XML descriptor naming its own hash and cipher, the package cut into 4096-byte segments with an IV each).isEncryptedPackage(bytes)says a file wants a password before the parse does, and each scheme carries a verifier, so a wrong password is refused (WrongPasswordError) rather than yielding rubbish. Decryption only — Ream never writes an encrypted package.
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 (a
pair for the even and the odd pages,
w:evenAndOddHeaders, and a first page of its own), multi-column layout (w:cols), and lines that run down the sheet (§17.6.20w:textDirectiontbRl/btLr— the text turned a quarter, the header and footer left across the sheet, as Word sets them). - 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, in the numerals the section names (§17.6.12w:pgNumType w:fmt— front matter in roman, a body that starts again at 1). - Laid out the way Word sets it — each rule measured in Word for Mac. A line
stands as tall as Word sets its faces: the face’s hhea line, or its typo line
where it asks for that, the tallest ascent and the deepest descent on the line
taken apart, a list marker adding its ascent, a picture standing on the
baseline. Two paragraphs stand the larger of their spacings apart, not the
sum, and a space before is dropped at the head of a page or column where Word
drops it; automatic spacing is 14pt in every compatibility mode. A paragraph’s
first and last lines never stand alone (§17.3.1.44 widow control), a heading
goes to the next page with what it heads (
w:keepNext), and a paragraph asked to stay whole does (w:keepLines). A table’s horizontal borders take the room they are wide, and a Word 2010 table (compatibility mode 14 and earlier) stands out by its first cell’s margin. Each page sets its body below the header and above the footer it shows, and a page break typed on a line of its own stays on the page it ends. - 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 — the diagram’s own parts (
data,layout,colors,quickStyle) are laid out by the same engine the deck reader uses, so a document that cached no drawing renders rather than degrading; where a pre-rendered drawing (diagrams/drawing#.xml) is present it is used directly. - Inline and floating images (PNG / JPEG / JPEG2000 / GIF / TIFF / BMP —
baseline TIFF 6.0 and Windows bitmaps are decoded to samples, since PDF has no
filter that carries either; a BMP is read at every depth from one bit to
thirty-two, run-length and bit-field forms included),
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, which Word reads beside no other wrap) for watermarks/stamps/text boxes, and side wrapping (square/tight/through) where the body text flows around the exclusion area, on both sides of it where the drawing leaves room, each line holding what its indents and the drawing’s stand-off leave. A drawing anchored to a paragraph that breaks to a new page goes with it. A drawing anchored inside a table cell is placed against the cell (or against the page, wherelayoutInCellis off), and a picture’s washout (@gain/@blacklevel) prints as the contrast and brightness it asks for. - Metafiles — EMF (MS-EMF) and WMF (MS-WMF), the format Word writes for anything it draws itself: an embedded workbook’s preview, a legacy clipart, an ActiveX control’s face, an equation. The records are played through their own device context — pen, brush, font, current point, window/viewport and world transform — and drawn as paths and text: lines, polygons, polylines, Béziers, rectangles, ellipses, path brackets, brush blits and both text records. In the flow, floating, in a header, inside a cell, or inline in a line of text.
- Text boxes chained with
wps:linkedTxbx: what overruns one box continues in the next of its chain. - 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.docyields no text — the binary formats lock themselves with the older RC4 scheme, which is not the OOXML one above. 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).
- Measured against Excel itself — its own PDF of a probe workbook built for each
question, and the absolute positions it writes beside every anchored drawing.
Columns are counted in the Normal font’s digit as Excel counts it: a Chinese,
Japanese or Korean face’s is a pixel more than its half-width figure (宋体 12 is
9px). A column no
<col>covers and nodefaultColWidthsets isbaseColWidthdigits and padding, rounded up to Excel’s step of eight pixels (Calibri’s 64px). A colour’stintis worked as Excel works it, in the Windows HLS model, on a theme,rgbor indexed colour alike; a workbook with no theme part takes the Office 2023 colours Excel starts from. A number is shown as Excel shows it: General fits the column’s room to at most 15 significant digits, rounded or in scientific notation, and fills the cell with#when no way of writing it fits; a format gets as many places as it asks, the digits past a double’s fifteen written as zeros. Text a column is too narrow for is cut by the cell’s box, not shortened: the page holds the whole of it under a clip, as Excel’s own PDF does, and shows its start, its middle or its end as the cell is aligned left, centred or right. - On a screen — HTML and SVG — a workbook is what Excel’s window shows, not its
printed pages: every visible tab, an empty one too; one grid a sheet, all of its
columns at full size; no print area, repeated titles, page breaks, printed headings
or centring; text running on across the row; the drawings over the cells where they
are anchored; the window’s gridlines where
showGridLinesasks for them, on every edge no border claims and no fill covers; every noted cell flagged with Excel’s red corner, purple for a threaded comment. HTML makes each tab a<section data-sheet>under its name and shows a note on hover; SVG draws each sheet whole on a page as large as what is on it, every glyph from its face’s own outline with the words laid over it unseen for search and selection. PDF is the printed pages, by the print model below. - Sheets that read from the right (
rightToLeft) — the grid, each cell’s left and right borders and the drawings turn round as Excel turns them (a right arrow still points right), and on paper the sheet meets the right margin. A cell’s text keeps the side its alignment names, and General aligns text by its first strong character on any sheet. - 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, metafiles included), 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 — each placed and sized on the sheet’s own columns and rows, since a.xlsdrawing anchors to cells, and a grouped shape through the group that anchors it — 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. Printed row and column headings shrink with a sheet the print scale fits; a chart sheet prints landscape unless it says otherwise, its chart filling the page.
- 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. Rules the 2009 extension carries in<extLst>(x14:cfRule— a child<xm:sqref>,<xm:f>formulas and an inline<x14:dxf>) are read beside the ones the base schema declares; acellIsoperand may be text as well as a number. - Sparklines — per-cell line / column / win-loss mini charts, including cross-sheet data ranges and blank-cell gaps.
- Excel tables (
xl/tables) — drawn in the style they name: the sixty styles Excel builds in (TableStyleLight1…21, Medium1…28, Dark1…11, from the standard’s ownpresetTableStyles.xml) and the ones a workbook defines in<tableStyles>. Each region is laid over the table in §18.8.41’s order — the whole table, column and row stripes, last and first columns, header and total rows, their corner cells — with its fill, weight, colour and rules; stripes run through the data rows only. The cell’s own formatting stays on top, and its text takes the table’s colour only where its font names its colour as the Normal style does. Structured references (Table[Column],[#All],[#Data],[#Headers],[#Totals],[@Column], column ranges) evaluate in conditional formatting — a rule’s formula, a colour scale’s, data bar’s or icon set’s stops — and so does a defined name that is itself a formula. - 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)
stand where they are anchored, over the cells — a picture keeps its bytes; a shape
its preset/custom geometry, fill, outline, text body and
a:xfrmrotation/flips (reusing the DrawingML shape readers). All three anchor kinds place a drawing:twoCellAnchor,oneCellAnchorandabsoluteAnchor, counted in the grid’s own column unit, a hidden row or column taking no room; a group (xdr:grpSp) maps its children through the group’s own coordinate space. On paper each page shows the part of every drawing over its own rows and columns, cut off where they end — a chart across a page break prints in two pieces, a drawing in the print titles repeats, nothing outside the print area prints, and the rows a drawing reaches print. A drawing markedfPrintsWithSheet="0"shows on a screen only. - SmartArt — the pre-rendered drawing (
diagrams/drawing#.xml, thedsp:namespace) where the file caches one, and otherwise the diagram laid out from its owndata/layout/colors/quickStyleparts; either way each label lands in the rectangle the diagram sets aside for it. - 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); indent (indent— three spaces of the Normal font a level, from the side the cell is aligned to); 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. A note the sheet shows (its VML shape visible) is drawn where Excel draws it: in itsx:Anchorbox, the comment’s runs in their own faces, VML’s hard shadow and a line to the cell’s corner. A window always shows them; paper follows the page setup’scellComments— nothing by default (reported as a loss), the shown notes in place underasDisplayed, and underatEnda “Comments” section after the grid, one line per comment:<cell> — <author>: <text>. - 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 drawn in place, where their anchor or their VML shape puts them — a shape inside a<v:group>through the group’s own coordinate space, a box given in points alone on the rows Excel counted it by. One the file gives no geometry is listed in a “Form controls” section after the grid, with a type-appropriate affordance and its state ([x]/[ ]for a checked box,(o)for an option button, the value for a spinner). - ActiveX controls — the embedded OLE controls (
<oleObjects>→xl/activeX) are drawn in place from the VML shape that shares their id, and otherwise 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, turned and mirrored by its@rot/@flip. -
Text boxes (
p:sp) — runs (size, bold/italic/underline stated either way, colour, latin font,cap), paragraph alignment, the body vertical anchor, bullets and per-level indents. -
The style a deck states once — the presentation’s
p:defaultTextStyle, the master’s three text families (p:txStyles) and each prototype’s owna:lstStyle, in that order, under the slide’s own runs. Placeholders inherit their geometry, fill and outline from the layout → master prototype as well. -
Bullets — a literal
a:buChar(read out of the symbol facea:buFontnames, so a Wingdingslis the circle it draws), an auto-numbereda:buAutoNum, or none; with the colour and size the level gives them (a:buClr,a:buSzPct/a:buSzPts) and a tab out to the hanging indent. -
The deck’s decoration — the shapes a master and its layouts carry are drawn under the slide’s own content, unless the slide or the layout turns them off (
@showMasterSp). -
Backgrounds (
p:bg) — a solid fill, a gradient, a picture, or a theme reference (p:bgRefintoa:bgFillStyleLst); a shape markeduseBgFillis painted with the piece of the background that lies under it. -
Pictures (
p:pic) with the recolouring the file asks for —a:duotone,a:clrChange,a:alphaModFix— shapes with geometry/fill/stroke/gradient, and embedded charts (c:chart), including a chart papered with a picture. -
WordArt (
a:prstTxWarp) — the text bent through the preset curve its body names and stretched onto the shape’s box: thirty envelope presets and the two rings, each glyph placed on its own along the curve. -
Tables (
a:tbl) — the style the table names intableStyles.xmlwith the parts it switches on (header row, banding, first/last column), composed under the cell’s owna:tcPrfill,a:noFilland four rules; row heights froma:tr@h. -
Embedded objects (
p:oleObj) — the snapshot the producer wrote beside the object, in either the modern (p:pic) or the legacy (VML drawing) spelling. -
SmartArt — the pre-rendered drawing (
dsp:spTree) where the deck caches one, and otherwise the diagram laid out from its own parts: eleven algorithms (lin,composite,cycle,hierChild/hierRoot,snake,tx,sp,connamong them), theforEach/chooseaxis walks the layouts are written in, the constraint solver that sizes and spaces the boxes, and the colour and style parts that paint them. -
Theme colours (
a:clrScheme) through the deck’s own colour map (p:clrMap, and a layout’s or slide’s override), 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).A slide’s title and body come from the document’s own slide list, not from the shape that draws them: the placeholder holds an
OutlineTextRefAtomnaming which of the slide’s texts is its, so each lands in the rectangle the file states for it. Their size, colour, typeface and bullet come from the master — theTextMasterStyleAtomfor that text type and indent level — because a run usually states none of them; a run that does states only where it differs. A colour may be a literal sRGB or a slot in the slide’s colour scheme, and the typeface is an index into the deck’s font collection, so a deck set in Times is measured in Times. A bullet is a character, translated out of the symbol face that states it, and it hangs on the margins the paragraph or its master states — the body indent, the first-line indent and the space set around each paragraph. The three furniture placeholders — date, footer and slide number — take their text from the deck’s own header/footer record rather than from the shape, which holds none of it.The slide is drawn on the background it states — a solid colour, a shade, or a picture — or on its master’s, when its flags say to follow it, and under the decoration the master carries: the rules, the logo, the footer band, with the master’s own placeholders left behind as the prototypes they are.
Shapes are placed by their slide anchor; a grouped shape by the ChildAnchor its group resolves, which is what makes a
.ppttable — a group of cell rectangles — read as one. Each carries its fill: a literal or scheme colour, a shade, or a picture, stretched or tiled, including a picture stored inline on the shape rather than in the deck’s picture stream. A freeform carries its exact custom geometry (thepVertices/pSegmentInfoarrays), and a piece of WordArt is drawn as the word it is rather than as the rectangle its shape type would give. A picture shows the part of itself the shape crops to, and drops the colour the shape names as transparent — how clip art older than the alpha channel says a rectangle of ground is not part of the drawing. A metafile picture (EMF / WMF, deflated inside its Escher blip) is replayed like any other, and a headerless DIB blip is read as the bitmap it is. 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. A value axis Excel
is left to scale takes Excel’s ends and step; the category axis lies where it
crosses (
crosses,crossesAt) with its labels wheretickLblPosputs them, a reversed one keeps its values at its start, and a crowded one is thinned by the labels it draws; no step cuts the value axis into more than 500 intervals, and its labels are General in nine characters, as Excel writes them; categories on several levels (c:multiLvlStrCache) are labelled in rows; a long title wraps; system colours (sysClr) are read and transformed. - 3-D charts in three dimensions. A 3-D bar or column chart stands as boxes in a
box of walls and a floor, seen as its view (
c:view3D) turns and tilts it: each bardepthPercentof its width deep, its top and side shaded as Excel shades them, the gridlines round the walls, the walls and floor filled and ruled as the chart says. A 3-D pie is a tilted disc, its slices standing out by theirc:explosion. Each rule was measured on Excel’s own PDF. - A chart as Excel sets it. A workbook’s chart text is in the theme’s face at
10pt in its text colour, its titles bold, under the chart’s and each element’s own
c:txPr; a pie’s data labels say what their switches ask, inside their slices or outside and tied back, as Excel places them; bars are as wide asgapWidthandc:overlapleave them; gridlines are drawn only where an axis has them, a deleted axis not at all, and a plot its author sized fills that box. - 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.
- A face for the writing system the document is in. The curated substitutes are Latin, so text in Han, Kana, Hangul, Arabic, Hebrew, Thai or the geometric symbol block reaches a Noto face for that script instead of a row of notdef boxes — chosen per CHARACTER, fetched only for the scripts a document actually holds, and in one weight rather than four. Unified Han is the document’s call: the same character is drawn differently in Japanese, Korean and Chinese, so its neighbours decide which face draws it.
- The font a document brings with it is used ahead of any substitute — a
.docxfont-table part (de-obfuscated against the GUID in its name) or a.pptxp:embeddedFontLstface. Aw:rFontsthat names a theme slot rather than a family (asciiTheme="minorHAnsi") resolves through the theme’s major / minor fonts, and a narrow family is measured at the narrow advance widths rather than the regular ones. - Renderer-compatibility
layoutProfile('word'/'libreoffice') — matches how a target renderer measures text, breaks it into lines and stacks the lines; with the metric-compatible open substitutes (Carlito / Caladea / Arimo / Tinos / Cousine) this tracks the target closely without its private font metrics. Under the default profile a document Word sets — a.docxor a.doc— stands its lines as Word does, from a table of the line Word gives each family it is known for;'word'stands every document’s lines that way, and'libreoffice'takes each line from the face in hand.
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), a rare arc / ellipse freeform segment (it falls back to the path’s preset bounds) or a texture drawn through WordArt’s glyphs (the word, its size, its face and the preset curve it is bent through are all drawn); 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). - Inside a metafile, a gradient fill and a flood fill are not drawn; everything else the file draws is — the arcs, pies and chords, the clipping region it limits its records to, and the rasters it blits. Each omission is reported as a named loss rather than guessed at.
- A
.pptxembedded font packed with MicroType Express. PowerPoint wraps an embedded face in an EOT container and, unlike Word’s, it is compressed with a codec of its own; the deck renders in a substitute and says so rather than silently dropping the family. - Where a section’s lines run down the sheet, a table and an inline picture turn with them, where Word sets a table flat in the corner of the sheet and keeps an inline picture upright in its line. A PDF page whose words run UP its sheet, or stand on their heads, has no section to be set in that way — Word and LibreOffice lay a section’s text across the sheet or down it — and is set across it, with a loss that says so.
- A book’s running foot names no chapter. Read from a PDF, each side’s foot says what most of that side’s pages say and the page’s number; the chapter a page is in, named differently in every chapter, is left out rather than set once and printed on every page. LibreOffice also seats a book’s left and right pages by the number its first page carries, and may sign a page with the other side’s foot where Word, which takes each page’s own number, does not.
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. A PDF read back as .docx is set beside its source page on a
bench of some four hundred files, and the documents a change is for are opened in
Word and in LibreOffice and counted page for page against the source; a rule of
Word’s layout is measured in Word before it is written. Untrusted corpus files run
inside a locked-down Docker sandbox. CodeQL’s security-extended queries scan every
change; the expressions that read a document’s own text were checked for
backtracking a crafted file could make quadratic, and replaced where they had it,
and a chart part’s own counts are held to as many points as a sheet has rows.