Skip to content

feat(svm): draw the text run a metafile names, with the font it names - #785

Merged
andiwand merged 4 commits into
mainfrom
feat/svm-text
Aug 30, 2026
Merged

feat(svm): draw the text run a metafile names, with the font it names#785
andiwand merged 4 commits into
mainfrom
feat/svm-text

Conversation

@andiwand

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Stage 3 of #772 and the whole of #95, stacked on #784#779. Review those
first
; this branch's base is fix/svm-graphics-state.

Two thirds of every action in the corpus is text, and we drew almost none of
what the file says about it.

A text action names a run

MetaTextAction(point, string, index, length) — and the string it carries is
the whole paragraph. We drew the string, at every run's position.

odt/style+charset+svm-various-1.odt is the proof: its one metafile makes 29
text actions out of one Hebrew paragraph, so the reference rendering is that
paragraph printed 29 times on top of itself — an unreadable smear. With the run
applied it reads as the sentence it is, and the page's visible text loses 505
characters, every one of them a duplicate.

The run is taken in utf-16 units, before the conversion to utf-8, because that
is what the offsets count.

The font (#95)

italic, bold, underline, strikeout, and the orientation, which rotates the run
about its own start (tenths of a degree counter-clockwise, where svg turns
clockwise). Of the 7962 fonts in the corpus, 3068 are italic — every one of
them a formula variable we were drawing upright.

TEXTALIGN

It says whether the draw point is the top, the baseline or the bottom of the
run. #772 reads it as centre/right alignment for chart labels; it is not —
vcl has no horizontal text alignment, a run always starts at its point. It
still has to be written out, because its default is ALIGN_TOP and svg's is
the alphabetic baseline. svgwriter.cxx shifts the point by the font's ascent,
having the metrics; dominant-baseline leaves that to the browser, which has
them too.

The measurements the file already took

A TEXTARRAY carries a dx array — where each character ended, as the author's
font measured it — which becomes an x list, one position per character. A
STRETCHTEXT carries the advance its run has to fill, which becomes
textLength + lengthAdjust. Both are what keep a formula together when the
viewer's font is not the author's, and there are 20335 stretch texts in the
corpus.

An x list is only written when it has exactly one entry per character;
anything else is logged and dropped rather than guessed at.

Verification

8 new tests (23 in the svm suite), each built from inline bytes.

Rendered: the four svm fixtures and every changed page in headless Chrome. The
formulas of odt/Vektoranalysis Zusammenfassung.odt (1032 metafiles, 20k
stretch texts) come out italic and correctly spaced; odt/ruski.odt,
odt/arbeitsplanKW21.odt and the charts are unchanged in content and tighter
in spacing. Visible text is identical to the reference everywhere except the
Hebrew document above, where it loses the duplicates.

Needs the same reference-output regeneration as #784 — 23 files, all of
them documents carrying an svm.

andiwand and others added 3 commits August 30, 2026 11:30
Two thirds of every action in the corpus is text, and we drew almost none of
what the file says about it.

**A text action names a run of its string** - `(index, length)` - and the
string it carries is the whole paragraph. We drew the string. A document whose
text comes in runs, which is every bidirectional one and every formula, had the
full sentence stamped at each run's position, overprinting itself into a smear.
The run is taken in utf-16 units, before the conversion to utf-8, because that
is what the offsets count.

**The font attributes of #95**: italic (3068 of the 7962 fonts in the corpus,
all of them formula variables we drew upright), bold, underline, strikeout, and
the orientation, which rotates the run about its own start.

**`TEXTALIGN`**, which says whether the draw point is the top, the baseline or
the bottom of the run - vcl has no horizontal text alignment. Its default is
`ALIGN_TOP` and svg's is the alphabetic baseline, so it has to be written out.
`svgwriter.cxx` shifts the point by the font's ascent, having the metrics;
`dominant-baseline` leaves that to the browser, which has them too.

**The measurements the file took**: a `TEXTARRAY`'s dx array places every
character where it was measured, and a `STRETCHTEXT`'s width becomes
`textLength`, so a formula still fits its box when the viewer's font is not the
author's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CmCr22NW6wPQKiQidk96bq
The 21 private and 6 public files this branch changes, regenerated.
The eight test doc comments each restated their own test name, and the facts
they carried were already stated where the code is - `MetaTextAlign`, the
`text_align` member, `get_x_list_string`, the `textLength` and `rotate` sites.
The one that was not, that half the text in the corpus is italic and all of it
a formula variable, moves to `PLAN.md`, where it justifies the stage order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQpLmJpJ87qbKoG8B7kbLY
A version-1 text action's `offset` and `length` count utf-16 code units, but
`read_string_with_encoding` has already decoded a `UCS2` string to utf-8 by
the time they are read. Slicing that by byte drew the wrong run and could cut
a character in half, and invalid utf-8 costs the whole image exactly as an
unescaped `&` did. `select_run_with_encoding` slices where the offsets point.

Also: an empty dx array no longer reaches `size() - 1` in
`get_x_list_string`, and `read_utf16_string` is the new `read_u16string`
converted, rather than a second copy of it.

The changelog entries under `Unreleased` are cut to a claim and its
consequence; several ran to four lines of it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQpLmJpJ87qbKoG8B7kbLY
@andiwand
andiwand merged commit 12819e3 into main Aug 30, 2026
24 of 25 checks passed
@andiwand
andiwand deleted the feat/svm-text branch August 30, 2026 09:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant