Commit Graph
54 Commits
Author SHA1 Message Date
Steffen Becker 3154a9b7a2 feat: a list label takes the alignment the file gives it
Word's w:lvlJc / ODF's fo:text-align on the level: set right, the label grows
into the margin instead of over the text it labels. That is what the built-in
Roman numberings use, and a fixture's numerals collided without it.
2026-08-08 23:09:58 +00:00
Steffen Becker 2830e8a9d4 feat: a top-and-bottom frame is drawn where the file places it
Both anchor offsets now render: the x moves the rotor inside the full-width
float, the y the float's top. Chromium puts every line after such a float
below it, so a frame set below the paragraph top is sunk behind the text at
import and the node view measures what those lines already cover.
2026-08-08 22:51:39 +00:00
Steffen Becker ee56a348ee feat: formulas can be inserted and edited, and survive both formats
A formula node stores only its LaTeX source; the MathML on screen, the ODF
formula object and Word's OMML are derived from it, and both importers parse
foreign markup back into that source, so an imported formula stays editable.

Word documents were losing their formulas outright: m:oMath sits beside the w:r
runs in its own namespace, past convertInline's `namespaceURI !== W` guard.
2026-08-08 21:50:36 +00:00
Steffen Becker 0af3488e6e feat: every list level takes its indent from the file
Only the top level's own w:ind w:left (ODF: the label-alignment margin)
was read; every level below it was forced to the editor's 1.27cm step.

Both formats store a level's indent absolutely while the editor nests one
step per level, so import now subtracts the level above and the ODF
export adds the running sum back per level (DOCX already accumulated).

Measured: a 0.5cm/-0.6cm pair puts LibreOffice's two levels at 1.77cm and
2.44cm, and the editor draws them at 1.769 and 2.44. Parity corpus
unchanged.
2026-08-08 17:47:17 +00:00
Steffen Becker ca3767f3d4 feat: a cell keeps its own margins where they differ from the table's
w:tcMar and an ODF cell style's own fo:padding were both dropped: the
table's value (in ODF the first cell's) stood for every cell.

cellPadding is now an attr on tableCell/tableHeader too, measured against
the table's rather than the editor default, so only a real disagreement
carries one. --cell-pad inherits, so the cell's own declaration wins for
its own td and the CSS is unchanged.

Checked against LibreOffice: a 1.2cm cell in a 0.3cm table puts its text
at 332.2pt where the grid column starts at 297.6.
2026-08-08 17:47:06 +00:00
Steffen Becker 7d5bc5e0c9 feat: a run of tabs too wide for its line carries on the next
LibreOffice restarts the tab grid on the continuation line; Chromium
hangs the leftover tabs and starts the next line at the margin, 160mm
away in the repro. The run is walked from the pen before it — the one
thing the fix can't move, so the answer holds whether or not the break
is already in place — and a break widget goes where the line runs out.

Measuring this turned up why the two disagreed in the first place: a
file that declares no tab interval falls back to ODF's own 2cm, not to
LibreOffice's UI default of 1.25, so leaving ours implied on export
rendered every tab elsewhere. Both exports declare it now, and both
imports fall back to their format's value rather than to ours.

Only runs of two or more are walked: reading a block's geometry forces
a reflow, and doing it per tab across 48 pages slowed pagination enough
that the parity harness compared a layout still settling.
2026-08-08 15:58:40 +00:00
Steffen Becker ae2290e22c feat: a header or footer keeps its own tab stops
The left⇥centre⇥right layout every letterhead uses rides on them, and a
zone had none: Word keeps them on the Header style, ODF on the zone
paragraph's, and neither was read or written.

An inactive zone is static generateHTML output no plugin reaches, so it
is measured straight on the DOM. Two bugs surfaced doing it: a segment
its own advance had pushed onto the next line measured as wrapped, and
the alignment then had no way back — it takes the sum of the segment's
client rects now. And the scale came from offsetHeight, which rounds
half a percent away on a zone one line tall.
2026-08-08 15:13:58 +00:00
Steffen Becker ea39dde58b feat: a tab lands on the file's own grid, and can draw its leader
The default tab interval was fixed at 1.25cm, so a file's own
w:defaultTabStop / style:tab-stop-distance was ignored and the DOCX
export wrote 1.25 back over Word's 1.27. It is a document setting now.

A stop can also carry the character it repeats across its gap. odf-kit
has no leader option and mints one automatic style per option set, so
the leader rides style:type behind a sentinel — matching on position
and alignment alone gave every stop at 6cm the same leader, which
LibreOffice drew back plain as day.
2026-08-08 14:40:26 +00:00
Steffen Becker 6accc7268f feat: every section keeps its own header and footer
A document had one set for all of it, taken from whichever section
declared one last — so a fixture lost its letterhead entirely
and page 1 got the empty header its final section asked for.

A body block now carries `sectionBreak`, ordinal rather than indexed so
editing cannot desync it. Import resolves Word's "Link to Previous" per
reference type; export writes a set per sectPr, and on the ODF side each
section past the first gets its own master page, which the block switches
to. The layer picks a page's set from where pageBreaks reports the
sections starting, and each section's zone reaches ride
`--pb-section-reach` so a later section's letterhead makes room for
itself: page 10 of a fixture was 24.9mm out, now 1.1mm.

Editing still reaches section 1 only; the rest render read-only.
2026-08-08 14:02:36 +00:00
Steffen Becker db0fe71cdb feat: a table keeps the cell margins its file gave it
The cell padding was a constant in three places that disagreed with each
other, so a narrow column lost 0.38cm it did not have: a bibliography
marker column of 0.71cm wrapped "[2] " onto three lines and every entry
grew with it, costing a fixture a whole page.

The table now carries its own margins (w:tblCellMar, direct or from the
table style; fo:padding on the ODF cell style) and both exports write
them back. A side within a twentieth of a millimetre of the default is
the default, or Word's 108 twips would tag every table.
2026-08-08 12:18:16 +00:00
Steffen Becker 993e64e933 fix: keep the highlighter pen a docx run carries
Word's highlighter is w:highlight, a palette name; only w:shd's hex fill was
read, so 121 yellow runs of a fixture imported unmarked. 'none' maps to the
same 'auto' sentinel an empty w:shd gives, so it still cancels a style's pen.
2026-08-08 08:56:38 +00:00
Steffen Becker be96ee626c docs: apply rules for comments 2026-08-08 08:28:55 +00:00
Steffen Becker cd1da67586 fix: carry a character style through a list item on ODF export
odf-kit's list builder reads a run's marks itself and knows nothing about
charStyle, and applyRuns — which handles the style on every other path — never
sees a list paragraph, so a character-styled list item exported as plain text.

A pre-pass now does both of applyRuns' jobs: bake the resolved props into direct
marks (the run's own marks staying on top), which is what gets the run a span at
all, and prefix the CST sentinel so applyCharacterStyles re-points that span at
the named style. Formatting and name both round-trip.
2026-08-07 20:27:31 +00:00
Steffen Becker a8a4a4c32f feat: let a character style on the item's first portion reach the marker
The charStyle mark carries only a name, so resolving it needs the registry: the
plugin takes it as a `sheet` option (like ParagraphStyle), the exporters read
their own exportSheet. The style is the base, the portion's own marks stay on
top. The decoration pass now also re-runs on FORCE_PAGE_RECALC, so editing a
style repaints the markers.

The ODF marker style moves to styles.xml as a named style — LibreOffice ignores
a level's text:style-name when it resolves to an automatic style, which made the
whole ODF leg a no-op (probed both ways).
2026-08-07 20:27:07 +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 0b54f6cc44 feat: render a list marker bold when the item starts bold
A bold mark sits inside the item's paragraph and never reaches ::marker, so the
roman numerals of a numbered heading stayed light while their text was bold.
A plugin tags such items and editor.css weights the marker. The trigger is the
first text portion, which is the rule LibreOffice numbering itself uses.
2026-08-07 17:41:09 +00:00
Steffen Becker 67e2a00bf0 feat: add a right paragraph indent
The mirror of the left indent (fo:margin-right, w:ind w:right) with its own
marker on the ruler. ODF export rides the PBX sentinel: odf-kit has a paragraph
option for the left indent but none for the right one.

Clearing direct formatting now resets the first-line and right indents as well —
no named style governs either, so both are always direct formatting.
2026-08-07 16:34:42 +00:00
Steffen Becker 92d641f72f feat: honour a paragraph's own tab stops
CSS only has the fixed tab-size grid, so a tab that resolves to one of the
paragraph's stops is measured and given the exact advance as an inline margin.
A hanging indent implies a stop at the text position, as in Word.
2026-08-06 21:45:47 +00:00
Steffen Becker c014a73da2 feat: support first-line and hanging indents
Only the block's left margin was read, so w:firstLine, w:hanging and
fo:text-indent were dropped: every hanging-indent paragraph rendered with
its first line flush with the rest instead of outdented.

Stored as the signed indentFirst attr in cm (negative = hanging) and
rendered as CSS text-indent. Word splits it into two exclusive
attributes by sign, ODF keeps one signed value.
2026-08-06 19:58:33 +00:00
Steffen Becker 3a68a144cb fix: read OOXML toggles as ST_OnOff, not as element presence
w:evenAndOddHeaders, w:titlePg and w:pageBreakBefore were treated as on
whenever the element existed, but w:val="false"/"0"/"off" turns them off
and both Word and the docx lib write that explicit form.

A file with <w:evenAndOddHeaders w:val="false"/> therefore took the
even-page header variant on every second page, which is empty — the
header and footer vanished from page 2 onwards.
2026-08-06 19:53:47 +00:00
Steffen Becker f48e5508a8 feat: honour the document's widow-orphan setting
Widow-orphan control was hardcoded on. It is a per-paragraph property in
both formats, so a file that turns it off paginated differently from
Word and LibreOffice.

Stored as the widowControl attr next to breakBefore. DOCX resolves
w:widowControl direct-then-style-chain-then-docDefaults; ODF reads
fo:widows/fo:orphans, where only an explicit 0 means off (absent is the
XSL-FO default of 2). Export rides the existing PBX sentinel rather than
adding a pass.
2026-08-06 19:45:08 +00:00
Steffen Becker 275a77564e fix: use Word's asymmetric cell padding for tables
Cells padded 0.1cm on all sides; Word and LibreOffice use 0.19cm left and
right and none top or bottom. Text sat 0.7mm too far left and every row
was 2mm too tall, so the offset accumulated down a table.

odf-kit only writes the fo:padding shorthand, which ODF defines as a
single length — LibreOffice drops a two-value form — so the export
expands it per side.
2026-08-06 19:26:56 +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 82699e653b feat: add table header column 2026-08-03 22:47:44 +02:00
Steffen Becker 5b11cdf3f6 fix: styles manager 2026-08-03 09:42:07 +02:00
Steffen Becker 8cdd487172 feat: right-click context-menu 2026-08-02 23:06:18 +02:00
Steffen Becker 9483d598fc feat: keyboard shortcuts 2026-07-31 20:43:48 +02:00
Steffen Becker 7cc9a36d18 feat: touchpad and keyboard scale document instad of browser ui 2026-07-31 18:37:17 +02:00
Steffen Becker ca4fbd422b feat: table margin edge resize 2026-07-31 17:46:26 +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
Steffen Becker d461ce09a6 fix: align styles and headings to libreoffice 2026-07-28 22:45:24 +02:00
Steffen Becker f4973caade test: add unit test for display of font size 2026-07-27 20:38:13 +02:00
Steffen Becker f1ba14c948 feat: add paragraph frames and background 2026-07-19 16:34:23 +02:00
Steffen Becker dce22ab3f4 fix: header and footer editor 2026-07-18 11:49:49 +02:00
Steffen Becker 6535d4c58d fix: page breaks, empty lines, ... 2026-07-15 22:19:49 +02:00
Steffen Becker 40c139dd2c feat: add embedded font loading 2026-07-11 14:07:36 +02:00
Steffen Becker 1abdb428ba feat: ott support 2026-07-11 12:57:55 +02:00
Steffen Becker 8e6c4817d8 feat: add lazy tiff to png decoding 2026-07-10 13:02:27 +02:00
Steffen Becker edf51d9a68 fix: show linked images 2026-07-10 00:11:14 +02:00
Steffen Becker 7faa16c89d feat: add german spell check 2026-07-05 14:15:30 +02:00
Steffen Becker e44f09ef42 feat: proper ordered list cycling 2026-07-05 11:51:07 +02:00
Steffen Becker d018092cce feat: improve ordered list 2026-07-04 21:45:05 +02:00
Steffen Becker 3b7e7606bb feat: bullet symbols 2026-07-04 17:14:54 +02:00
Steffen Becker 15b9386eb7 feat: add table frame menue 2026-07-04 00:00:44 +02:00
Steffen Becker 0aa9399f93 feat: textbox and shapes 2026-07-03 22:53:38 +02:00
Steffen Becker 9700487353 feat: docx export 2026-06-30 02:21:10 +02:00
Steffen Becker 418f152d4b feat: header row toggle 2026-06-28 12:23:41 +02:00