Commit Graph
40 Commits
Author SHA1 Message Date
Steffen Becker b80440de0e fix: a chapter number is set and placed the way the file describes it
The outline level's character style, its indent and the stop its label's tab
runs to were never read, so a 96pt grey chapter number rendered at the
heading's own size and everything under it sat ~30mm high. The label is an
inline block now — one set larger than its heading grows the line as both
products grow it — hung out of the paragraph's indent with the level's stop as
its minimum width. text-indent inherits into that block and would shrink it by
exactly what it hangs out, so the label zeroes it. Both formats carry all of it
back out (ODF's label alignment, Word's w:suff / w:ind / w:rPr).
2026-09-04 19:30:08 +02:00
Steffen Becker 43d9a864d4 docs: the band is measured, a manual break keeps its space, headings number
Corrects two probed claims that measurement contradicted: a hard page break
does *not* swallow the block's space above (only an automatic one does), and
the header band is max(declared height, content + gap) rather than a line
count. Adds the chapter-numbering model, the mirrored master pair, and the
harness ceiling a CSS-drawn label is.
2026-09-04 19:30:06 +02:00
Steffen Becker 80463883f0 feat: chapter numbering — headings carry the number the file gives them
A numbered thesis lost every "1.", "2.1.4" and "A." on import: the outline
definition was read as a list wrapping headings and dropped. It is now a
document-level definition on the style sheet (format, prefix, suffix,
display-levels, start per level), drawn by CSS counters that reset down the
levels, and carried by both formats — ODF's text:outline-style, Word's
multilevel numbering bound to the heading styles. The contents rows count the
same way, since no text walk can read a CSS counter.
2026-09-04 19:30:06 +02:00
Steffen Becker ce9dc9bb4d fix: the header/footer band is measured, not counted in lines
The body's margin has to clear the band the zone renders at, and the
estimate counted paragraphs — blind to a line that wraps, to a run smaller
than the 12pt floor, and to the zone's padding. HeaderFooterLayer now lays
each section's zones out off-screen at their text width and reports the
height; the ODF zone's own gap to the body and its padding ride the
collapsed paragraph's spacing, which is where the export already puts them.
Measured on one file: a three-line band read as two, 14.9mm a page.
2026-09-04 19:30:05 +02:00
Steffen Becker 525c95f3a8 feat: add Numbering 1.a.i, the default cycle as a preset
The gallery avoids presets the dropdown plus default nesting already
covers, but the default cycle itself earns its slot: once a list wears
another style, switching back needs a style to switch to. No sheet
version bump — an added built-in survives the merge on its own.
2026-08-26 14:03:55 +00:00
Steffen Becker 6f57a8e7ed feat: level-varied preset gallery for the list-style built-ins
The uniform LibreOffice definitions duplicated the dropdown formats plus
default nesting. The gallery now ships patterns the depth cycle cannot
produce: the outline chains Outline I.A.1 / A.I.1, the kind-mixing
Numbering with Bullets and Checklist, and the Diamond Bullets ladder,
all on the plain 1.27cm step. STYLE_SHEET_VERSION 12 swaps stored
built-ins while user styles survive.
2026-08-26 09:58:33 +00:00
Steffen Becker 697eea4d86 fix: a list style's level decides the depth's kind
effectiveListLevel resolved a level only for its own node species, so a
Tab-nested <ul> under a number level kept cycle bullets while the named
ODF definition (and LibreOffice) numbered it. The level's kind now wins:
the walk numbers a <ul>/bullets an <ol> (CSS keys generalized), both
exporters resolve clones and forks through eff.kind (new ODT pass
applyListLevelKinds switches L# level species), and setListStyle retypes
the subtree. Node marker attrs still override with their own species.
2026-08-26 09:21:45 +00:00
Steffen Becker 48f44f3dcd feat: named list styles — assignment UI and manager tab
setListStyle assigns on the outermost list, clearing the subtree's direct
marker attrs (applying a Listenformatvorlage replaces direct numbering).
Both chromes' bullet/numbering dropdowns grow a "List styles" section split
by the style's level-1 kind, plus a manage entry; the style manager gets a
fourth, flat tab with a per-level editor and a three-level preview. The
decoration now also sets data-bullet — editor.css keys the var()-reading
marker rule on it, so a style's bullet paints (verified live, headless).
2026-08-26 00:38:44 +00:00
Steffen Becker e2525237de feat: named list styles — model, editor resolution, ODF round-trip
Fourth style family (LibreOffice's Listenformatvorlagen): per-level model in
styles/listStyles.ts, five probed LO built-ins, listStyleName on the outermost
list, one effective-level resolver shared by the decoration walk and the export.
ODF writes the full named text:list-style into styles.xml and repoints
unoverridden lists at it (overrides keep the resolved automatic clone);
import maps named list styles back to the attr + registry, attr-free.
Probed: LO keeps the named reference and display-name across a re-save.
2026-08-25 20:38:15 +00:00
Steffen Becker a87e7a4ada fix: a heading's style name never lands on a plain paragraph
styleName was kept on split, so Enter at the end of a heading left a paragraph
carrying it: drawn like a heading, absent from the navigator's outline, exported
as text:p. It is no longer kept there. setParagraphStyle also missed the name
whenever it switched the node type — a following updateAttributes looks for the
new type in the pre-chain state — so it now rides along with setNode. Levels 2–6
never reached that command at all: the digit class built from MAX_HEADING_LEVEL
(10) read as [0-1], so Heading's own Mod-Alt-N took the key and set a level with
no style; that keymap is dropped, running ahead of every plugin binding.
2026-08-19 18:41:21 +02:00
Steffen Becker 9b14d1654f feat: the style gallery lists heading levels 1–5 until asked for the rest
Both reference products define ten levels but only offer the first five;
visibleStyles() filters styleOrder for the gallery in either chrome, with a
session-only 'show all styles' toggle in the menu. A deeper level a block
already uses stays listed, so the caret always has an active style.
2026-08-16 14:01:30 +02:00
Steffen Becker 147034730f feat: headings go to level 10, as both word processors do
HTML stops at h6, so 7-10 render as unknown elements editor.css makes
blocks; their built-in styles continue level 6 at 12pt, since LibreOffice
writes these styles with no properties and resolves them from its own
pool. Both importers read a two-digit level again (the style-name regexes
stopped at one digit). Verified through a LibreOffice re-save of both
formats and in the live editor.
2026-08-15 01:21:23 +02:00
Steffen Becker 53aacd9f77 feat: captions that number themselves
A caption is a paragraph in the Caption style holding a sequence field —
one counter per category, in document order, renumbered as the document is
edited and on load (a file's cached numbers are unreadable wherever the
format is not arabic). Round-trips as an ODF text:sequence with
LibreOffice's own ooow:Illustration+1 formula and a Word SEQ field.
Caption style values probed off LibreOffice: 10pt italic, 0.212cm around.
2026-08-14 06:21:30 +02:00
Steffen Becker 71c7b53281 feat: heading level 6, with LibreOffice's italic on 4 and 6
LibreOffice's style pool, probed: Heading 6 is 12pt bold italic and
Heading 4 italic too. MAX_HEADING_LEVEL drives the extension registry,
the shortcut plugin, the TOC depth and both importers' yardstick, so
only the constant table and the CSS selector lists had to grow.
Bookmarks/cross-references move to Implemented — they already are.
2026-08-14 00:38:49 +02:00
Steffen Becker 0da75ed456 refactor: the project is called edentext
Renames the product in the docs, the page title and the DOCX creator, and moves
the whole storage namespace from odf-editor- to edentext-. The prefix is a
per-origin namespace, so it stays the product name rather than a generic one.
2026-08-13 21:43:12 +02:00
Steffen Becker 6957f5682b fix: a style kerns where its own w:kern says so, not only the document's
Word kerns nothing unless w:kern names a size to start at, and the Office
theme declares it on Title alone — but TextProps.kerning stored only the
off state, and a style inherits its parent's, so a title under a document
that kerns nothing could never turn it back on. Its 26pt line then ran
0.36mm over the text width and wrapped a word onto a second line where
LibreOffice sets it in one (measured 151.64mm kerned against 151.98).
Both states are stored now, and both formats read and write them.
2026-08-13 19:15:28 +02:00
Steffen Becker 04d1fed5f0 fix: banded rows start on the first body row
The stripe began one row late: regionsAt counted the second body row as the
first band. Probed on a five-row fixture with a red band1Horz — LibreOffice
shades row 0 where the style paints no header region, and the row under the
header where it does.

The corpus cannot see this (no fixture uses the editor's own registry), so it
stayed at 739; checked by eye and by the unit test's inverted expectations. A
document with a table style assigned re-bands on its next open.
2026-08-13 07:06:13 +00:00
Steffen Becker c7e1f91d46 fix: a rule line a paragraph style declares is drawn
Only direct w:pPr/w:pBdr was read, so a Title style's line went missing and
everything under it sat 1.9mm high. The box now goes to the style registry,
where ParaProps' borders already render and round-trip.

Two thirds of that gap is the file's own w:pBdr w:space, carried as
ParaProps.borderPadding (ODF fo:padding) and applied to the ruled sides only —
padding the top as well pushes the paragraph's first line down.
Corpus 761 -> 739.
2026-08-13 00:39:10 +00:00
Steffen Becker 5969795d04 fix: a header or footer takes the document's font, not the editor's
The zone was pinned to Liberation Serif 12pt, so a run whose font the
file leaves to the default — every page-number field in a fixture
fixture — rendered serif inside an Arial footer. Both formats base the
Header/Footer style on the default paragraph style, so the zone now
takes its text half.
2026-08-11 20:58:29 +00:00
Steffen Becker baf0865256 fix: a section keeps its own side margins too
.tiptap's padding draws one pair of side margins for the whole document, so a
title page declaring 3.18cm left sat 3.85mm off. The difference now rides a
--sec-inset-left/-right node decoration on the section's top-level blocks, and
everything that writes a block margin adds it in — an indent, a named style's
and a table's alignment all compose with it instead of losing to it.
2026-08-11 19:03:58 +00:00
Steffen Becker 361ce14864 feat: pair kerning follows the file, not the browser
A browser always kerns; ODF kerns unless told not to and Word kerns only
above the size w:kern names — nothing, in most files. Measured 5% on a
kerning-heavy string, enough to break a line a word early throughout every
Word document in the corpus. Carried on the style, since that is where
w:docDefaults and the default paragraph style put it.
2026-08-11 07:23:20 +00:00
Steffen Becker 819f51587e fix: direct formatting on a run beats its named character style
A character style is a stylesheet rule, direct formatting an inline style on the
run's span. The charStyle mark ranked below TextStyle, so its span sat *inside*
and the rule won over the ancestor's inline style — a 9pt style silently undid a
run's own 10pt. Priority 102 lifts it above TextStyle's 101.

One fixture: 259 → 172 parity issues, lineEnd 49 → 7.
2026-08-10 18:08:13 +00:00
Steffen Becker d6bc656c83 feat: the space between two blocks follows the document's own model
LibreOffice adds the upper block's space-below to the lower one's
space-above, and drops the space-above where a page break put the block
at a page top — but for a Word document it takes only the larger of the
two (all probed against soffice). CSS margins can only collapse, so
space-before rides --space-before and editor.css renders it as padding
('add') or margin ('max'); the model is read from settings.xml, assumed
for DOCX, and written back on ODF export.
2026-08-09 16:53:13 +00:00
Steffen Becker 60980fa4d6 docs: load CLAUDE.md detail only where it is needed
The root file is read at every session start and had grown to 87 KB, so every
session paid for knowledge it did not use. Move the per-directory detail into
nested CLAUDE.md files, which load when a file in that directory is touched,
and the five deep-dive topics into docs/. The root keeps the commands, the hard
rules and an index of where the rest lives.

Content moved verbatim — the probed Word/LibreOffice behaviour, sentinel order
and shared constants are what the code cannot say. CI fails the root over 120
lines, since a prose budget alone is what let it grow.
2026-08-08 15:33:46 +02:00
Steffen Becker b61fadca48 feat: the character effects Word and LO carry beyond bold and italic
Letter case, the underline's shape and colour, a double strikethrough and a
freely raised run all had no home in the model, so every one of them was
dropped on import. They ride the marks now, render in CSS, and round-trip.

odf-kit's run formatting has a field for none of them, so a sentinel folds
them into the run's own automatic style — the route the character styles
already take, and it reaches every path odf-kit serializes.
2026-08-08 10:06:31 +00:00
Steffen Becker 5549d625f7 feat: the index reads like the one a word processor generates
Its leader is real dots now, not a border, so it scales with the font and
reaches the PDF as text. Title and entries follow the document's Heading 1
and default style, which is what the index styles of Word and LO are based on.
2026-08-08 09:33:25 +00:00
Steffen Becker c82a78cc6a feat: character spacing, and a line box that multiplies its two halves
Word's Title style tightens by -0.5pt, so a title came out ~3mm wide of
LibreOffice's. letterSpacingPt joins TextProps and rides both formats
(w:spacing, fo:letter-spacing).

Line spacing needed the same care: it is proportional to the font's natural
line height, so the factor and that height are now separate CSS variables and
editor.css multiplies them in one place. Before, whichever of the two was
declared nearer simply overwrote the other — a block carrying its own font
silently dropped the style's spacing.
2026-08-08 08:30:05 +00:00
Steffen Becker 869556e531 feat: render Calibri Light with the bundled Carlito
Word's default theme heading font, so every Word file with a Title or a
heading references it. Without a face it dropped to the browser's serif —
a serif heading at the wrong width.
2026-08-08 08:30:04 +00:00
Steffen Becker 3a71491b58 fix: scale a named style's line spacing by the font's natural line height
A proportional line spacing multiplies the font's own single line height in
Word and LibreOffice, CSS multiplies the font size. The node attr already
scaled by Liberation Serif's 1.15; a style's line-height went out raw, so an
imported 1.08 rendered ~20% tighter than the file asks for and the drift
accumulated down the page.
2026-08-08 08:30:04 +00:00
Steffen Becker be96ee626c docs: apply rules for comments 2026-08-08 08:28:55 +00:00
Steffen Becker 943a885754 feat: carry the item's formatting into its list marker
The marker took only the weight of the item's first text portion. It now takes
the family, slant, size and color too — as custom properties on every item,
since they inherit and a nested item must not take its parent's.

A named paragraph style reaches it a second way: the marker inherits the list
item's own font, and the document stylesheet only styled the item's paragraph,
so an imported document whose Standard style is Arial drew its numbers in the
editor's serif default. styleCss now emits the style's text half for the item
too (li:has(> …)).

Both file formats carry marker formatting per level, so a level definition is
written only for a list whose items agree: ODF mints a character style the level
points at, DOCX the level's w:rPr. A mixed list gets none on purpose, so
LibreOffice keeps applying its own first-portion rule.
2026-08-07 20:26:44 +00:00
Steffen Becker 52cf5ac702 fix: derive single line spacing from the font, not a fixed 1.15
Single spacing is the font's natural line height. The hardcoded 1.15 is
Liberation Serif's (and Liberation Sans' and Caladea's), but Carlito
needs 1.2208 and Liberation Mono 1.1333 — measured against LibreOffice
at 12pt. A Calibri body ran 6% too tight, drifting a line per page.

Emitted next to font-family, so an explicit line spacing still wins.
2026-08-06 19:19:06 +00:00
Steffen Becker d608771105 feat: add table options 2026-08-06 20:01:44 +02:00
Steffen Becker 1ed9f3d172 feat: table-styles 2026-08-05 19:01:25 +02:00
Steffen Becker 5b11cdf3f6 fix: styles manager 2026-08-03 09:42:07 +02:00
Steffen Becker ec0cfb76fd feat: add character style editor 2026-07-30 23:34:36 +02:00
Steffen Becker 3dcae6f640 feat: named character styles 2026-07-30 23:34:23 +02:00
Steffen Becker 603f4df905 feat: paragraph style manager ui 2026-07-30 19:58:47 +02:00
Steffen Becker d79287c10a feat: odf import/export of named paragraph styles 2026-07-29 10:53:54 +02:00
Steffen Becker ac9d6cdcff feat: named paragraph styles with inheritance 2026-07-28 23:53:00 +02:00