← Back
Home › Systems › Productivity and collaboration

word-processor

Lets a person edit a document of styled paragraphs, tables, images and footnotes and lays it out on pages, keeping a structured document model separate from the derived layout.

Open the word-processor pageword-processor Template enginescompilers: Compile templates with loops, partials and auto-escaping into render functionsBinary delta diffcompression: Bsdiff, VCDIFF and xdelta-style binary patches between file versionsCRDTsdistributed: Data types whose replicas accept writes independently and always converge by merging, with no coordination: counters, sets, maps, registers, text.›Operational transformationdistributed: Transform concurrent edits against each other for collaborative text editingBlock inline layoutgraphics: CSS box model: block formatting, inline formatting contexts, line boxes, floats and marginsFont fallback itemizationgraphics: Split text into runs by script, direction and font coverage; pick fallback fonts per runFont rasterizationgraphics: TrueType/CFF outline parsing and anti-aliased glyph rasterization with hinting/cachingHyphenation patternsgraphics: Liang/TeX pattern hyphenation with language dictionaries and exceptionsKnuth plass line breakinggraphics: Optimal paragraph line breaking minimizing total badness with boxes, glue and penaltiesOpentype font parsinggraphics: Sfnt tables (cmap, glyf, CFF, hmtx, GSUB, GPOS), variable font axes, safe parsingPage layout paginationgraphics: Break flowed content into pages with floats, footnotes, keep-with-next and widow/orphan controlRich text document modelgraphics: Schema-constrained document trees with marks, positions and transactional steps (ProseMirror-style)Ropegraphics: A balanced tree of small string chunks with cached byte and newline counts, so seek, insert, delete and snapshot cost O(log n) instead of O(n).›Text shapinggraphics: Map Unicode to positioned glyphs via OpenType GSUB/GPOS lookups, clusters and complex scriptsUndo redographics: Command or snapshot history with grouping and coalescingUnicode bidi algorithmgraphics: UAX 9: resolve embedding levels and reorder mixed left-to-right and right-to-left textUnicode line breakinggraphics: UAX 14 line break opportunities plus hyphenation hooksImage codecsmedia: JPEG (DCT, Huffman), PNG, HEIF/AVIF decoding and encoding fundamentalsLevenshtein automatasearch-sort: Levenshtein automata / edit-distance search over dictionaries for typo toleranceSpell checkingsearch-sort: Hunspell-style affix dictionaries with suggestions by edit distance and phoneticsDocx ooxml formatstorage: Word OOXML packages: ZIP parts, document.xml runs, styles, tracked changes and commentsName resolutioncompilers: Bind every use of a name to its declaration with a scope stack, handling shadowing, hoisting and closure capture in one AST walk.›Recursive-descent parsingcompilers: Hand-written top-down parser with one function per grammar rule; simple, fast, with full control over error messages and recovery.›Html sanitizationsecurity: Allowlist-based HTML sanitizing of untrusted markupDelta encodingcompression: Store differences between consecutive values instead of the values, turning sorted IDs, timestamps and counters into small numbers that pack into few bits.›LZ77compression: Replace repeated byte strings with (distance, length) back-references into a sliding window; the parsing stage of gzip, LZ4, zstd, Brotli and xz.›Rolling hashdata-structures: Rabin-Karp polynomial rolling hashes and winnowing for substring fingerprints and document matchingSuffix arraysearch-sort: Sorted array of all suffix start positions of a text; finds every occurrence of any substring by binary search, at 4-5 bytes per text byte.›Fractional indexingdistributed: Give every list item a sortable string key and generate a new key strictly between its neighbours, so a move or insert writes one row.›Lamport clocksdistributed: One integer per process, max-plus-one on receive; gives a total order consistent with causality using 8 bytes per timestamp.›Vector clocksdistributed: One counter per node; comparing two vectors tells whether events are ordered or concurrent, which is exactly what conflict detection needs.›Message sequence numbersdistributed: A per-stream counter on every message lets the receiver detect loss, duplicates and reordering, ack cheaply and resume after reconnect with "send from N".›Css cascade inheritancegraphics: Resolve computed styles: origin and specificity ordering, inheritance, custom properties+ 3 moreBit packing and PFORcompression: Store blocks of integers with exactly b bits each and patch rare outliers separately (PFOR); the fastest integer codec family, billions of values per second.›Varint (LEB128)compression: Variable-length integers in 7-bit groups with a continuation bit; small numbers take 1 byte, the wire format of Protobuf, WebAssembly and DWARF.›Fast hash functionsdata-structures: Non-cryptographic hashes built from 64×64→128-bit multiplies and xor-shifts; pick by key size, trust level and whether the hash is persisted.›Radix sortsearch-sort: Non-comparison sort that distributes keys by 8-bit digits; several times faster than comparison sorts on large arrays of integers, floats or fixed-size keys.›String interningdata-structures: Store each distinct string once and refer to it by idCss selector matchinggraphics: Right-to-left selector matching with ancestor Bloom filters, rule hashing and style sharing+ 2 moreSIMD programminghardware-os: Process 4-16 lanes per instruction with AVX2, AVX-512 or NEON via Rust std::arch, with runtime dispatch and a portable fallback.›Bloom filterdata-structures: Bit array answering "definitely not present" or "probably present" in about 10 bits per key for 1 % false positives; inserts are cheap, deletes impossible.›Swiss tabledata-structures: Open-addressing hash table that filters 8-16 slots at once with 1-byte hash tags and SIMD, giving fast lookups at 87.5 % load.›

How it is built

Architecture overview

A word processor edits a document: paragraphs of styled text mixed with tables, images, footnotes and fields, laid out on pages of a chosen size. It must do four things at once. It keeps a structured document model that every edit changes in small, reversible steps. It lays out that model into lines and pages exactly as it will print, in every script the user types, and redraws as they type. It reads and writes file formats that other programs also produce, above all Word's DOCX. And, in the cloud editors, it lets several people edit the same document at the same time and converges to one result.

The central idea is a pipeline with two representations. The model is a tree of blocks (paragraphs, tables, lists) containing text with marks (bold, link, font) and positions that refer into it. The layout is derived: runs of text are split by script, direction and font, shaped into glyphs, broken into lines, and stacked into pages. An edit changes the model; layout recomputes only the affected paragraph and the pages after it. Anything that displays (screen, PDF, print) reads the layout, not the model.

Parts:

  • Document model and editing — the tree, positions, transactions, undo — rich-text-document-model (no page yet), undo-redo (no page yet), Rope, Text position encoding
  • File formats and templates — import and export, images, merge fields — docx-ooxml-format (no page yet), image-codecs (no page yet), template-engines (no page yet)
  • Fonts and shaping — maps characters to positioned glyphs and draws them — opentype-font-parsing (no page yet), font-fallback-itemization (no page yet), unicode-bidi-algorithm (no page yet), text-shaping (no page yet), font-rasterization (no page yet)
  • Paragraph layout — breaks text into lines — unicode-line-breaking (no page yet), knuth-plass-line-breaking (no page yet), hyphenation-patterns (no page yet), block-inline-layout (no page yet)
  • Pagination — flows blocks onto pages — page-layout-pagination (no page yet)
  • Proofing — spelling and suggestions — spell-checking (no page yet), levenshtein-automata (no page yet), Unicode text tokenization for search
  • Collaboration and history — concurrent edits, saved versions — operational-transformation (no page yet), CRDTs, binary-delta-diff (no page yet)
 keystroke / paste / import
        │
        ▼
 edit command ──► transaction (steps) ──► document model (blocks, marks)  ◄── undo / redo history
        │                                        │                 │
        │                                        │                 └──► sync: OT or CRDT ◄──► other editors
        │                                        ▼
        │                       itemize: script, direction (bidi), font fallback
        │                                        ▼
        │                       shape (glyphs + positions) ─► line breaking (+ hyphenation) ─► paragraph boxes
        │                                                                                         │
        │                                                                           pagination (floats, footnotes, keeps)
        ▼                                                                                         ▼
 proofing (spelling, suggestions)                                         screen raster / PDF / print
                                         save: DOCX package ◄── model;  open: DOCX ──► model

Two clocks run: the typing clock (a keystroke must show within one display frame, so only the touched paragraph is re-laid out) and the document clock (page numbers, tables of contents and the page count settle after, in the background).

How the parts fit together

Document model and editing

A document is a schema-constrained tree. rich-text-document-model (no page yet) defines which nodes may contain which (a list contains list items, a paragraph contains inline text and images, never a table), and represents formatting as marks on text ranges rather than as nested tags, so "bold from here to there" survives edits that split and merge runs. Positions are integers counted over the flattened document; changes are steps (replace a range with a slice) grouped into transactions, as in ProseMirror. A step has an inverse and can be mapped through other steps, which is what both undo and collaboration need.

Plain text inside a paragraph lives in a Rope: a balanced tree of small string chunks with cached lengths and newline counts, so insert, delete and seek by offset are O(log n) even in a book-length document, without moving the megabytes after the cursor.

Text positions have several coordinate systems that must agree: a byte offset in UTF-8 storage, a UTF-16 code unit index in a browser or platform text API, a character index, and a user-perceived character (grapheme cluster: a letter plus combining marks, or a flag made of two code points). Text position encoding stores one canonical offset and converts on demand, so the cursor moves over a whole grapheme and a selection never splits a surrogate pair.

undo-redo (no page yet) keeps a history of transactions. Typing a word is many steps but one undo; the history groups steps by time and by kind and coalesces adjacent insertions. Each entry stores the inverse steps, which are mapped over later changes if they must still apply (essential for collaboration, where undo must revert only your own edits and not a collaborator's text that landed in between).

File formats and templates

DOCX is the interchange format. docx-ooxml-format (no page yet): a ZIP package (Open Packaging Conventions) whose parts are XML: document.xml with paragraphs, runs and text elements, styles.xml with named styles and their inheritance, numbering, headers and footers, relationship files that link parts, and media files. It is specified as ECMA-376. A good importer maps this to the model without losing what it does not understand: unknown elements are kept in an opaque node so a round trip leaves them unchanged. Style resolution (document defaults, style chain, direct formatting) is where most fidelity bugs live; export writes the minimum direct formatting so the file stays editable by Word.

Images in the package are PNG, JPEG, and others; image-codecs (no page yet) decode them for display and re-encode on export. The editor keeps the original bytes and re-embeds them unchanged, because re-encoding a JPEG loses quality.

template-engines (no page yet) turn a document with merge fields into many documents: placeholders for names and addresses, loops over rows, conditional paragraphs. The template is compiled once into a render function, values are inserted as text nodes (never parsed as markup, which closes the injection hole), and the result goes through the same layout path as any other document.

Fonts and shaping

Before a line can be measured, text must become glyphs. opentype-font-parsing (no page yet) reads the font's tables (cmap for character to glyph, hmtx and the outline tables for advance widths and shapes, GSUB and GPOS for substitutions and positioning, variation axes for variable fonts). Fonts can arrive inside documents, so every offset is treated as untrusted.

The string is first itemized into runs that can be shaped as a unit: a single script, a single direction, a single font and size. font-fallback-itemization (no page yet) picks, per character, the first font in the style's list that covers it, falling back for symbols, emoji or CJK, and splits at script changes. unicode-bidi-algorithm (no page yet) (UAX 9) resolves the direction of every run in mixed text: Arabic or Hebrew words inside English text and the reverse, with numbers and punctuation handled by neutral rules. It produces embedding levels, and runs are reordered to visual order per line only after the line is broken; layout and cursor movement must work in logical order with a separate mapping to visual order.

text-shaping (no page yet) turns each run into positioned glyphs by applying GSUB lookups (ligatures such as fi, contextual forms in Arabic, reordering in Indic scripts) and GPOS lookups (kerning, mark attachment). The output is glyph ids, advances and offsets, plus a cluster map back to characters so selection, hit-testing and cursor placement work. HarfBuzz is the open implementation almost every editor uses. Shaped results are cached by (font, text, features), because re-shaping an unchanged paragraph on every key is the easiest way to make typing slow.

font-rasterization (no page yet) turns outlines into pixels: scan conversion with anti-aliasing, optional hinting, sub-pixel positioning so letters do not snap to whole pixels and uneven spacing does not appear at small sizes. Glyph bitmaps are cached in an atlas keyed by (font, size, glyph, sub-pixel offset).

Paragraph layout

unicode-line-breaking (no page yet) (UAX 14) says where a line may break: after spaces and hyphens, between CJK characters, never before a closing punctuation mark, with non-breaking space respected. It gives candidate break points, not breaks. hyphenation-patterns (no page yet) adds more candidates inside words: Liang's pattern method (the one in TeX) holds a few thousand patterns per language and finds the legal hyphen points of a word by looking at the pattern weights of its letter sequences, with an exception list for words the patterns get wrong.

Choosing among the candidates is the real problem. The simple method is greedy: fill each line as far as it goes. It makes bad spaces when a long word does not fit. knuth-plass-line-breaking (no page yet) treats the whole paragraph as a shortest-path problem: text is boxes (words), glue (stretchable spaces) and penalties (breaks, hyphens), each candidate line gets a badness from how far its glue is stretched or shrunk, and dynamic programming over feasible break points finds the set of breaks with the least total demerits (Knuth and Plass 1981). That is how TeX produces even spacing; word processors use it for justified text, usually with a greedy first pass for speed during typing and the optimal pass on idle.

block-inline-layout (no page yet) places the results: block elements stack vertically, text flows in line boxes whose height comes from the tallest inline item, with indents, spacing before and after, borders, tab stops, floating images that text wraps around, and table cells that run the same algorithm in a narrower width. Edits re-lay only the changed paragraph; a table cell can change the row height and everything below.

Pagination

page-layout-pagination (no page yet) takes the sequence of laid-out block boxes and cuts it into pages. A box that fits goes onto the page; one that does not is split at an allowed point. The rules interact: widow and orphan control keeps at least two lines of a paragraph at the top or bottom of a page, keep with next ties a heading to the paragraph after it, keep lines together forbids splitting, a footnote must appear on the page of its reference, a floating figure may move to the next page, and table rows can break across pages with repeated header rows. Satisfying one rule can violate another, so implementations resolve it greedily with limited backtracking.

Page breaks are global: an edit on page 3 may change every later page. The editor therefore lays out the visible pages first, then the rest in the background, and shows the final page count when it is done. Fields such as page numbers and tables of contents depend on the result and trigger a second pass; they are repeated until they stop changing (the table of contents itself can change the page count).

Proofing

spell-checking (no page yet) marks words that are not in the dictionary as the user types. Unicode text tokenization for search segments the text into words (UAX 29 word boundaries, which handle apostrophes, hyphens and numbers, and need a dictionary for languages without spaces). Each word is normalised and looked up in a dictionary of stems and affix rules (Hunspell's format expands "walk" into "walked", "walking" by rules, so a language is a few hundred thousand entries rather than millions of forms). Checks run on the changed paragraph when idle, with results cached by word.

Suggestions need a similarity search: all dictionary words within edit distance one or two of the misspelling. levenshtein-automata (no page yet) builds a small automaton that accepts exactly the strings within distance k of the given word; intersecting it with the dictionary stored as a trie or finite-state transducer walks only the branches that can still match, which is much faster than computing the edit distance against every entry. Candidates are ranked by distance and frequency.

Collaboration and history

Two writers insert at the same offset at the same time. operational-transformation (no page yet) (OT) keeps one server order of operations and transforms each incoming operation against those it did not see, so that "insert at 5" after somebody else's "insert at 2" becomes "insert at 6". Etherpad uses OT. It needs a central server to order operations, and its transform functions are subtle for rich text. CRDTs assign every character a stable identity, so inserts commute and replicas converge by merging in any order, without a central order; Yjs and Automerge implement this. Order of list items is kept with identifiers that sort between neighbours (Fractional indexing), and concurrency is tracked with Vector clocks or Lamport clocks. The price is metadata per character, which is trimmed with garbage collection. Formatting is the hard part: if one person bolds a word while another types at its end, should the new text be bold? Peritext (Ink & Switch) defines a rich-text CRDT with explicit rules for span boundaries.

Each saved version of a large file would repeat almost all its bytes. binary-delta-diff (no page yet) stores a new version as a patch against the previous one: the differences are found by matching long common substrings (a Suffix array or rolling-hash index over the old file), and the patch is a list of "copy from old" and "insert new" instructions, then compressed. Version history, autosave and upload of large files use it. It works best on the XML inside the DOCX when the package is stored uncompressed, because a ZIP's compression makes a small edit change most bytes.

Data and control flow

One keystroke:

  1. The key becomes an edit command; a transaction replaces the selection with the character.
  2. The model updates; the rope and positions adjust; the history groups the step.
  3. The affected paragraph is re-itemized; cached shaped runs are reused where text is unchanged.
  4. Lines are re-broken with the fast breaker; the new height shifts the blocks below.
  5. Visible pages are repainted; glyphs come from the atlas.
  6. The spell checker runs on the changed word; in a shared document, the step is sent to peers or the server.

Open and save:

  1. Opening unzips the package, parses XML parts, resolves styles and builds the model; layout starts at the first page and continues in the background.
  2. Saving serialises the model to XML parts, re-uses unchanged parts and media, zips them, and writes atomically (temporary file, then rename) so a crash cannot truncate the user's document.

Scaling

  1. Document length: a thousand-page document with many tables. Levers: rope for text, layout cache per paragraph, lazy layout of pages outside the view, virtualised rendering.
  2. Typing latency: one frame is about 16 ms at 60 Hz. Levers: re-lay only the changed paragraph, cache shaping, move spell checking and the optimal line breaking off the typing path.
  3. Script and font diversity: CJK fonts are large, complex scripts shape slowly. Levers: font subsetting, lazily loaded fonts.
  4. Concurrent editors: every operation is broadcast. Levers: batch operations per tick, compress presence updates, snapshot plus tail replay for late joiners, garbage collection of CRDT metadata.
  5. History size: many versions of a big file. Levers: deltas against a recent base, periodic full snapshots to bound replay.

Failure modes

FailureSymptomCauseFix
Wrong cursor in bidi textcursor jumps in mixed Arabic and Englishlogical and visual order confusedkeep logical positions, map to visual per line
Split characterselection or deletion corrupts an emoji or accentpositions counted in code units, not graphemesgrapheme-aware position encoding
Different layout between machinespage breaks differ between Word and the editordifferent fonts, metrics or shapingembed or substitute fonts with matching metrics, compatibility modes
Lost formatting on importstyles differ after openstyle inheritance resolved differentlyfollow the spec's resolution order, keep unknown elements
Diverging replicastwo users see different textnon-commuting operations, lost operationOT with server order or a CRDT, convergence tests with random concurrent edits
Undo reverts someone else's editcollaborator text disappearsglobal undo stackper-user undo mapped over remote steps
Slow typing in long fileslag after paragraph 500full relayout, uncached shapinglocal relayout, caches, background pagination
Truncated file after crashdocument unreadablein-place overwritewrite to a temporary file, flush, atomic rename

Real implementations

namewhat it isrepolicencenotes
LibreOffice Writerfull open-source word processor, DOCX import and exportLibreOffice/coresee repocomplete reference
ProseMirrorrich-text editor toolkit: schema, transactions, collaborationProseMirror/prosemirrorMITmodel and steps
Etherpadreal-time collaborative editor using OTether/etherpadApache-2.0OT in production
YjsCRDT framework for shared editingyjs/yjssee repoCRDT
AutomergeCRDT library with local-first syncautomerge/automergeMITCRDT
HarfBuzztext shaping engineharfbuzz/harfbuzzsee reposhaping
FreeTypefont rendering libraryfreetype/freetypesee reporasterization
ICUUnicode support: bidi, line breaking, segmentationunicode-org/icusee repoUAX implementations
Hunspellspell checker with affix-based dictionarieshunspell/hunspellsee repoproofing
Typstmarkup-based typesetting systemtypst/typstApache-2.0modern layout engine

Sources

  • D. Knuth and M. Plass, Breaking Paragraphs into Lines, Software: Practice and Experience 11(11), 1981.
  • F. Liang, Word Hy-phen-a-tion by Com-put-er, Stanford PhD thesis, 1983. thesis
  • Unicode Standard Annexes: UAX 9 bidirectional algorithm, UAX 14 line breaking, UAX 29 text segmentation
  • ECMA-376, Office Open XML file formats. standard
  • M. Shapiro et al., A comprehensive study of Convergent and Commutative Replicated Data Types, INRIA, 2011. report
  • Ink & Switch, Peritext: a CRDT for rich-text collaboration. essay

Similar systems

Share the most building blocks with this one.