The Kalem Book GitHub

This Book describes Kalem's main branch, which may be ahead of the latest release.

Word documents

The docx plugin of getkalem/plugins opens Word documents as themselves: the file is a package of WordprocessingML parts, and the plugin reads, edits and writes those parts, never a conversion of them. Kalem lays the paragraphs out itself, through the plugin API's flow interface, and reviews the comments and tracked changes through its annotations interface (see Plugins, "Viewers of flowing text"). The plugin is installed from the plugin list; its README and its work list (plugins/docx/docx_todo.md) are kept with it.

1. The standard and the oracle

  • The standard is ECMA-376 part 1, WordprocessingML, in its transitional namespace (the strict one is read as it), with the w14, w15 and w16 additions Word writes read and kept.
  • The oracle is Word 2016 and later (Microsoft 365): "as Word writes it" is how each edit is written, element order following the schema's sequences, which Word checks. Word cannot be driven from a script past its file access prompt, so it is checked by hand.
  • LibreOffice Writer 7.3 is the second oracle, by hand too: soffice --convert-to txt and --convert-to docx open the edited files, show the edits and keep the tracked insertions and deletions with their authors.
  • The corpus has four files from three producers: python-docx over its default template, a package written part by part with every construct the plugin reads, and each of the two saved again by LibreOffice. The tests check that each saves back byte for byte, and what each edit changes.

2. Which files

.docx, .docm, .dotx and .dotm. A file with one of these extensions that starts as a ZIP archive is the plugin's at once; one that does not is tried as a document encrypted with a password to open, which Word writes as a compound file. A file of another extension is not the plugin's whatever it holds.

3. What is read

  • The package through its relationships, never by a part's name: the body, the headers and footers, the footnotes and endnotes, the comments, the styles, the numbering, the settings and the theme. Every element is read with its byte span; an element the plugin does not know is kept with its span and shown by name.
  • Styles as 17.7.2 puts them together: the document defaults, the table style and its conditional formats as w:tblLook turns them on, the numbering level, the paragraph style and its basedOn chain, the character style, then direct formatting; the toggle properties combined across style kinds as 17.7.3 says. Theme fonts and colors are resolved, a tint or shade computed as Word computes it.
  • Lists counted as Word counts them: instances of one abstract definition counting together, w:startOverride, w:lvlRestart, legal numbering, levels linked to styles; labels in every common number format, bullets in Symbol and Wingdings as the Unicode characters they draw.
  • Fields show their results, simple and complex, nested; HYPERLINK fields and w:hyperlink are links. Notes are numbered in order of reference as w:footnotePr and w:endnotePr say.
  • Comments with their ranges, and tracked changes: insertions, deletions, moves, formatting changes of runs and paragraphs, and paragraph marks inserted or deleted, each with its author and date.
  • A password to open ([MS-OFFCRYPTO]): agile encryption, Word 2010's and later's, is read and written, and Word 2007's standard encryption is read. Kalem asks for the password, hidden as typed.

4. What is shown

Word's web layout, without pages: a document of the editor whose lines are the paragraphs, in both editors.

  • Each paragraph is a line, drawn with its runs' look: bold, italic, underline, strike, caps and small caps, raised and lowered text, color, highlight and typeface; a size in proportion to the body text's, so a heading is larger than the text around it. A list item has its label before it; a heading is in the outline.
  • A tab, a line break, a page break and a column break are one character each in the paragraph's line; a picture, a note's mark and an object are one object character.
  • A picture is drawn where it is, as large beside the text as in the document, and no wider than the text: in a PNG, JPEG, GIF, BMP, TIFF or WebP file, or an SVG drawing through the PNG Word keeps of it. A floating picture is drawn in its paragraph's line. In the terminal a picture alone on its line is drawn over as many rows as it takes when the terminal draws pictures (kitty's, iTerm2's or sixel graphics) and the cursor is elsewhere; otherwise, and for a picture in EMF or WMF or one linked from outside the document, its alternative text shows, [Picture: …].
  • A table row whose cells hold one paragraph each is one line, its cells apart by tabs and the line marked ▏ at its start. A row with more in a cell shows a paragraph a line, the row's first marked ▏ and each further cell's first ▏┆. A nested table's paragraphs are shown in the cell that holds it.
  • The first section's header comes before the body, marked Header ▏, and its footer after it, marked Footer ▏. Footnotes and endnotes follow, each marked with its number. A text box follows the paragraph it is anchored in, marked ▕.
  • A separator drawn as shapes (a line, a thin bar across the page, Word's horizontal line) is a horizontal rule across the text; a line among text shows as ──. Charts, other shapes, SmartArt, equations and embedded objects are shown by name, as placeholders.
  • The document's colors were chosen for a white page: on a dark theme, a color that would not read on the background (black text, a dark gray, a dark blue) is shown with its lightness turned over and its hue kept, as a word processor's dark mode shows it, and text on a highlight is drawn to read on the highlight. The file keeps its colors.
  • Text a comment is on is highlighted; inserted text is underlined in green and deleted text struck through in red. The status bar says the comment at the cursor with its author, or who made the change there.

5. What is edited

Typing, Enter, Backspace and Delete edit the document as typing in Word does, in the body, a table cell, the header, the footer and the notes:

  • Typed text goes into the run before it and takes its look; over a selection it takes the look of the selection's first character. A deletion shortens, empties or removes the runs it covers.
  • Enter splits the paragraph, the elements open where it splits with it; Enter at the end of a paragraph whose style names a next style (a heading's, Normal) gives the new paragraph that style. Backspace at a paragraph's start joins it to the one before; a deletion across paragraphs is one edit.
  • Each of Kalem's edits is one step of the document's own history: Undo and Redo go through it, and undoing everything saves the file as it was opened, byte for byte.
  • In a document that tracks changes, typed text is written as an insertion, deleted text kept as a deletion, and Enter and Backspace mark the paragraph mark inserted or deleted, each with the author, the date and an ID of its own. The author is the setting user.name, else the system account's name.

The commands of the Format menu and the toolbar change the look of the selected text, or of the word at the cursor:

Command What it writes
Bold, Italic, Underline, Strike Through w:b and w:bCs, w:i and w:iCs, w:u, w:strike in each run's w:rPr; off again, taken away, and written off (w:val"0", an underline =w:val"none"=) where a style still turns it on
Superscript, Subscript w:vertAlign
Font, Font Size w:rFonts (w:ascii and w:hAnsi, the theme's fonts that would win taken away), w:sz and w:szCs in half points
Text Color w:color; Automatic takes it away
Highlight Color w:highlight for the sixteen colors Word names, else a shading (w:shd)
Clear Formatting The run's direct look taken away; its character style, language and the like kept
Paragraph Style w:pStyle first in the paragraph's w:pPr; none for the default style, as Word writes Normal
Align Left, Center, Align Right, Justify w:jc (left, center, right, both); taken away when the style aligns so already, written where it does not
Increase and Decrease Indent A list item's level (w:ilvl), else w:ind's w:left by half an inch
Line Spacing, Space Before, Space After w:spacing's w:line (auto, in 240ths of a line), w:before and w:after in twentieths of a point
Bullets, Numbering, Numbering Style w:numPr: the list of the paragraph before continued when it is of the kind, else a new one, of the document's definition of the kind or of Word's own (added to numbering.xml, the part made with its relationship and content type when there is none); the paragraphs given the List Paragraph style, as Word does; taken out, w:numId 0 where a style makes them a list
Clear Paragraph Formatting w:jc, w:ind, w:spacing and the like taken away; the style and the list kept

A run is cut where the range starts or ends inside it; every property goes where the schema's sequence puts it, since Word repairs a file whose properties are out of order. In a document that tracks changes, each run's old properties are kept in a w:rPrChange and each paragraph's in a w:pPrChange: Accept Change keeps them, Reject Change undoes a run's (a paragraph's is not rejected yet, and Reject All Changes leaves it).

The commands of the Review category act on the annotations:

Command What it does
Next Change, Previous Change Moves to the next or the previous tracked change
Accept Change Keeps the change at the cursor: an insertion's text stays, a deletion's goes, a deleted paragraph mark joins its paragraphs (in the body only), a formatting change keeps the new look
Reject Change Undoes it: an insertion's text goes, a deletion's text comes back, a run's formatting change restores the old look (a paragraph's is refused for now)
Accept All Changes, Reject All Changes The same for every change, in every part of the document
Track Changes Turns tracking on or off (w:trackRevisions in the settings)
Next Comment, Previous Comment Moves to the next or the previous comment
New Comment (Ctrl+Alt+M) Puts a comment on the selected text, in the body or a note: its range's start and end around the text, a run split where the range starts or ends inside it, a run with its reference after the end
Reply to Comment Answers the comment at the cursor: a comment anchored on the same text and named as its answer; an answer to an answer answers the first comment, as in Word
Edit Comment Changes the text of the comment at the cursor, a paragraph a line; the palette starts with its text. Its author, date and mark stay, and its last paragraph keeps its ID, so its answers and its done mark stay its own
Resolve Comment Marks the thread at the cursor done, or open again: the mark is on its first comment, and its answers show resolved with it
Delete Comment Takes the comment at the cursor away with its answers (an answer alone, when it is one): its range, its reference and the comment; the runs a range split stay split, as in Word

What is not edited says why when it is tried: deleted text of a tracked change, a field's result, a note's mark, a check box, text drawn two ways in the file (mc:AlternateContent), a paragraph of a text box, an edit across a table's cells, a join across a section break, and every edit of a document whose protection is enforced, with accepting and rejecting changes and Track Changes, as Word refuses them (a document protected for comments takes comments, and one protected for tracked changes keeps Track Changes on). A comment in a header or a footer is refused, as Word keeps none there.

6. What is written

Only the paragraphs an edit touched are written again; every other byte of the part, and every other entry of the ZIP archive with its local header, is copied as it was. A save without edits returns the file unchanged. Nothing is written that ECMA-376 does not define: no rsid invented, no property Word would not write. A comment added to a document without comments adds the comments part, with its relationship and content type; an answer adds the extended comments part (commentsExtended.xml) the same way when there is none, and its entry names the answered comment's last paragraph by its w14:paraId, which is given one when it has none. Resolving writes w15:done in that part, made the same way. Deleting a comment takes its entries out of the parts Word keeps beside the comments (commentsExtended.xml, commentsIds.xml, commentsExtensible.xml); the comments part stays when its last comment goes. A document opened with a password is saved encrypted again with it.

7. Export and conversion

None. Save As writes the document in its own kind only: a name of another kind (report.docm for a .docx, report.pdf) is refused with the plugin's reason, and the document keeps its file.

8. Known differences

  • No pages: the layout is Word's web layout, without pages, columns or the page's size.
  • Only the first section's header and footer are shown, once.
  • Pictures in EMF or WMF, Windows's own drawings, are not drawn; a picture's crop, rotation and effects are not applied.
  • Comments are highlighted in the text, not shown in a margin.
  • Ordinal and cardinal list labels are in English whatever the document's language.
  • LibreOffice drops a paragraph mark inserted as a tracked change when it saves the document again; Word keeps it.
  • LibreOffice 7.3 shows an answer as a comment of its own: it reads no threads. Word shows it under the comment it answers.

9. Not implemented

  • Section breaks are not marked in the text.
  • Character styles given; borders, shading, tabs and keeping lines together edited; pictures, links, tables and notes inserted.
  • Text boxes edited.
  • Word opening every edited file without a repair prompt, checked by hand.
  • Pages and printing.

10. Limits and performance

Measured on 2026-10-09 on an Apple M1 Max, with the plugin's library in a release build: a document of 20,000 paragraphs (4.4 MB of document.xml) is opened and laid out as blocks in 0.18 s in 82 MB; the save takes 7 ms (2026-10-10). The component is 2.0 MB as released with its picture decoders, its functions' names kept for backtraces. Kalem fetches a document's items from it 2,048 at a time when it opens one; the document of 20,000 paragraphs opens in Kalem in 0.34 s.

An edit does not walk the document again. The plugin keeps the body as it walked it, and every sixteen elements of the body (a table is one) where the walk was (the lists' counts, the fields and comments open); an edit walks again from the last such place before it until the walk is where it was before, usually a few paragraphs on, and the paragraphs after it are those of before. Kalem then fetches the items the plugin says changed (flow-3, API 0.2.9) and lays out their lines again. Measured on 2026-10-10 with the component in Kalem, a character typed in the middle of the document of 20,000 paragraphs takes 1.4 ms from the keystroke to the text shown (0.66 s before); Enter 4.6 ms, Backspace joining two paragraphs 2.3 ms, Undo 3.5 ms.

11. Code and tests

  • The plugin: plugins/docx of getkalem/plugins: document.rs (the document and its edits), story.rs, styles.rs, numbering.rs, props.rs, flow.rs (the view), edit.rs (runs rewritten), review.rs (changes accepted and rejected), comments.rs (comments added and answered), contract.rs (the flow and its annotations), viewer.rs (the plugin as Kalem's viewer); the Office layer it shares with the workbook plugin in crates/ooxml.
  • Its tests: tests/docx.rs, tests/conformance.rs (the corpus saved back byte for byte), tests/comments.rs, tests/viewer.rs, tests/contract.rs; the corpus in tests/corpus/, made by make.py.
  • Kalem's half: the interfaces in crates/kalem-plugin/wit/flow.wit and annotations.wit, the contract in crates/kalem-viewer/src/flow.rs, the document of the editor in crates/kalem-core/src/flow.rs, the Review commands in crates/kalem-core/src/builtin.rs; its tests in crates/kalem-core/tests/flow.rs, crates/kalem-script/tests/flow.rs and both editors' tests/flow.rs, and crates/kalem-cli/tests/flow_component.rs, which opens, edits and saves a real document through the plugin's component when given one.