feat(style): render a right-to-left document right-to-left - #819
Merged
Conversation
andiwand
added a commit
to opendocument-app/OpenDocument.test
that referenced
this pull request
Sep 5, 2026
Two minimal packages for opendocument-app/OpenDocument.core#819, each paragraph stating one attribute under test. The odt is a right-to-left page layout (`style:writing-mode="rl-tb"`) with paragraphs for `fo:text-align` start / end / center and for a `lr-tb` paragraph inside it. The docx is a right-to-left section (`w:sectPr/w:bidi`) with `w:jc` start / end / left / center and a `w:bidi="0"` paragraph. They pin the contrast between the two formats: LibreOffice renders odf `start` left and `end` right whatever the direction, and `w:jc` start right and end left in a right-to-left paragraph. Both carry a mixed hebrew/arabic + latin + digit line, whose ordering the base direction decides. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TP23uvwVtgEzZZTJnWsJC2
andiwand
added a commit
to opendocument-app/OpenDocument.test.output
that referenced
this pull request
Sep 5, 2026
For opendocument-app/OpenDocument.core#819. Every view's root now carries the document's base direction, so `<html>` becomes `<html dir="ltr">` throughout; no visible text moves. Adds the output of the two new text-direction fixtures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TP23uvwVtgEzZZTJnWsJC2
`style:writing-mode`, `w:bidi` and `a:pPr@rtl` are read into a new `TextDirection` on `ParagraphStyle` and `PageLayout`. The view's root carries the document's as `<html dir>`; a paragraph states its own only where it differs, which keeps `direction:ltr` off every docx paragraph. `w:jc`'s `start`/`end` stop resolving to `left`/`right`: they follow the paragraph's direction, so a right-to-left `start` paragraph rendered flush left where Word flushes it right. `TextAlign` gains the two values and they reach the css as `text-align:start`/`end`. `fo:text-align`'s same-named values stay absolute. [OpenDocument] 20.386 calls them direction-relative, but no producer writes them that way - LibreOffice inverts the two against `w:jc` on a round-trip, so a `w:jc="start"` paragraph it flushes right comes back out as `fo:text-align="end"`. Towards opendocument-app/OpenDocument.droid#653. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TP23uvwVtgEzZZTJnWsJC2
A reader whose format means something else by the names maps into the absolute values, which is what `fo:text-align` does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TP23uvwVtgEzZZTJnWsJC2
Two hand-authored packages, one attribute per paragraph, pinning the contrast the formats disagree on: odf `start`/`end` are the absolute sides, `w:jc`'s follow the direction. Checked against LibreOffice edge by edge. All eight docx paragraphs match it. Seven of nine odt paragraphs do; the two that do not are the same case, an `rl-tb` paragraph stating no alignment, which LibreOffice flushes left and css flushes to the start edge. Reference output: 677 files gain the root `dir`, 146 docx paragraphs move from `text-align:left` to `start`, and no visible text moves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TP23uvwVtgEzZZTJnWsJC2
andiwand
force-pushed
the
feat/text-direction
branch
from
September 5, 2026 20:31
e737367 to
ac66210
Compare
andiwand
added a commit
that referenced
this pull request
Sep 6, 2026
…every field (#825) #819 added `PageLayout::direction` and left three designated initializers naming every other field, which `-Wmissing-field-initializers` refuses under gcc-14 and clang-18. gcc stops at the first, so only the iwork one was visible. The msvc job fails separately: a raw string inside an `EXPECT_EQ` argument, which the traditional preprocessor does not read, so the quotes inside it come out as a stray escape. Built outside the macro instead. main is red on all three today. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements OpenDocument.droid#653. All three parts, in one PR — they share one reference-output regen, and
style.hpp/document_style.cppcarry hunks from all three, so splitting would be artificial.The view has a base direction
HtmlWriter::write_begintook no options and wrote a bare<html>. It now takesHtmlElementOptions, andfront()puts the document's direction on it.WritingStatecarries that direction so a paragraph writesdirection:only where it differs — without that, every docx paragraph gets adirection:ltr(Word writes<w:bidi w:val="0"/>indocDefaults).style:writing-modeis parsedNew
TextDirectiononParagraphStyleandPageLayout, read from:style:writing-modestyle:page-layout-properties/@style:writing-modew:bidiw:sectPr/w:bidia:pPr/@rtlThe vertical modes (
tb-*) andpagename no horizontal direction, so they stay unset. In odf this rides the existing style cascade, so a document that states the direction on its default paragraph style — which is what LibreOffice writes — inherits it everywhere for free.start/endalignment — and a correction to the issueThe issue says both odf and ooxml resolve these to the wrong edge. That holds for ooxml and is fixed here. It does not hold for odf, and I left odf alone.
TextAlign::start/endcarry the css meaning; a reader whose format means something else by the names maps into the absolute values.w:jcpasses them through,fo:text-aligndoes not — LibreOffice inverts the two on a round-trip, so aw:jc="start"paragraph it flushes right comes back out asfo:text-align="end":So odf's
startis a plainleft— which is why writers emit it for ordinary body text — and mapping it to cssstartwould have flipped every right-to-left odf document to the wrong edge.odt/style+charset+svm-various-1.odtin the private data is exactly that document: 224 Hebrew paragraphs atfo:text-align="end"on anrl-tbpage, which LibreOffice flushes right. It keeps rendering right, and now also gets<html dir="rtl">, which is what fixes the ordering of its mixed Hebrew/Latin lines.TextAligngainsstartandendfor the ooxml side. Appended afterjustify, so existing values are stable in every binding; only an exhaustiveswitchneeds an arm.Fixtures
odt/text-direction.odtanddocx/text-direction.docxin the public input repo — hand-authored, one attribute per paragraph, short lines so each one's edge is readable. Rendered both with LibreOffice and measured the emitted html in Chrome, paragraph by paragraph:fo:text-align=startfo:text-align=endcenter/justifylr-tbrl-tbrl-tb+startlr-tb+startw:bidi, unalignedw:bidi+w:jc=startw:bidi+w:jc=endw:jc=left/right/centerw:bidi=0w:bidi=0+w:jc=startdocx matches LibreOffice 8 of 8. odt matches 7 of 9.
The known divergence
The two odt misses are one case: a paragraph that states a right-to-left direction but no alignment. LibreOffice flushes it left — its internal default adjust is
leftwhatever the direction — while<html dir="rtl">with notext-alignresolves to the start edge, so we flush it right.I left it, because there is no clean fix: matching LibreOffice means emitting an explicit
text-align:leftfor odf but not for docx, which is format knowledge the renderer deliberately does not have. It is also narrow — a real LibreOffice-authored right-to-left document always writes an explicit alignment (the Hebrew document above does), so only a synthetic file like this fixture hits it. The fixture pins it, so if we ever change our mind it shows up as a diff. Worth your call.lang— not doneLeft out deliberately. The reliable source is per-script (
fo:languagevsstyle:language-complex,w:lang/@w:valvs@w:bidi), so a singlelangneeds a script-selection rule that depends on the resolved direction — and direction and text style resolve independently in the cascade.dc:languageis not a substitute: 7 of 138 odf test inputs carry it. It wants its own design decision.Verified
-Werror, jni (1/1 junit), python (69 pytest), andODRStyle.mmall clean.<html>→<html dir="ltr">(676) /dir="rtl">(1), andtext-align:left→text-align:start(146, all docx) — plus the four files of the two new fixtures. A fresh run is byte-identical to the pushed references.🤖 Generated with Claude Code