Skip to content

feat: align paragraphs in odf, docx and pptx, in the editor and the bindings - #942

Open
andiwand wants to merge 5 commits into
mainfrom
feat/paragraph-align
Open

andiwand wants to merge 5 commits into
mainfrom
feat/paragraph-align

Conversation

@andiwand

@andiwand andiwand commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Summary

A paragraph now takes a horizontal alignment in every format that already takes other formatting. Cells of ods and xlsx files already take align through setCellStyle, so this PR adds the same to text documents, presentations and drawings.

  • C++ API. Paragraph::set_style(ParagraphStyle) writes text_align. Any other field refuses with UnsupportedOperation. The adapter hook is ParagraphAdapter::paragraph_set_style.
  • Wire. {"op": "setParagraphStyle", "id": 9, "style": {"align": "center"}}, where align is left, center, right or justify.
  • Engines.
    • odf (odt, odp, odg): a fresh automatic style P<n> with fo:text-align. One base and one delta make one style. create_cell_style and create_paragraph_style share the code that copies or inherits the base style.
    • docx: w:jc at its rank in CT_PPr. Justified is both.
    • pptx: a:pPr/@algn. start and end refuse, because ST_TextAlignType has no value for either.
  • Editor. odr.editing.format({align}) aligns every paragraph that the selection reaches, and it can hold run keys too. onSelectionChange reports align. The formatJustify* input types are chords.
  • Bindings. Python Paragraph.set_style, Java Paragraph.setStyle, Objective-C -[ODRParagraph setStyle:error:], and npm Document.setParagraphStyle(id, style). In Java and Objective-C, a ParagraphStyle can now be built and written.
  • Fix. The docx reader did not read w:jc="both", which is how Word writes a justified paragraph. It now reads it.

Reference output

This PR changes the reference output in two ways:

  1. The embedded document.js changes.
  2. Twelve docx pages (eleven public, one private) now show text-align:justify. In nine of them this is the only change. In the three file-sample_*.docx pages, the paragraph states both over a style that states left, so text-align:left becomes text-align:justify.

The output repos carry these files at 6976b72 (public) and 4e6b97d (private), and test/data.cmake pins both. Nothing else in the full render changed.

Test plan

  • odr_test: 1608 unit tests pass, and 15 of them are new in DocumentEdit (odf, docx, pptx, refusals, save round trips).
  • test/browser/text/tests.html, headless Chrome: 217 checks, 0 failed.
  • Python: 88 passed.
  • Java (odr_jni_junit): 67 passed.
  • Swift (swift test on the macOS xcframework): the two new tests pass, 0 failures.
  • npm (build-wasm, node): 56 passed.
  • The touched C++ files compile with Homebrew clang -Wall -Wextra -Werror.

andiwand and others added 3 commits September 29, 2026 07:47
Paragraph::set_style and the setParagraphStyle op write text_align. Any
other field of ParagraphStyle refuses with UnsupportedOperation. The op
takes `align` as left, center, right or justify.

- odf points the paragraph at a fresh automatic style P<n> that carries
  fo:text-align. It is made as a cell style is, so one base and one delta
  make one style. create_cell_style and create_paragraph_style now share
  the code that copies or inherits the base style.
- docx writes w:jc at its rank in the CT_PPr sequence. Justified is `both`.
- pptx writes a:pPr/@algn, and refuses start and end, because
  ST_TextAlignType has no value for either.

The docx reader did not read w:jc="both", which is how Word writes a
justified paragraph. It now reads it, so twelve docx pages of the
reference output change. In each page, the only change is the alignment.

The design doc also describes how the editor aligns, which the next
commit adds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tigcf9he3w1aHfNPJoEGsd
odr.editing.format({align}) sends one setParagraphStyle for each paragraph
that the selection reaches, and a collapsed caret aligns its own
paragraph. A style can hold align and run keys together. The editor checks
the paragraphs first and aligns them last, so a refused mark leaves no
paragraph aligned. It writes text-align on the x-p, as the renderer does.

onSelectionChange reports `align` where the paragraphs agree. Two
alignments of one paragraph fold into one op. The formatJustify* input
types are chords, and under scope `paragraph` they refuse as every chord
does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tigcf9he3w1aHfNPJoEGsd
… npm

Python binds Paragraph.set_style as it is. Java and Objective-C get
Paragraph.setStyle and -[ODRParagraph setStyle:error:]. A ParagraphStyle
can now be built and its fields written, so a caller builds the delta.
Any field but textAlign refuses before the call reaches C++. The npm
package's setParagraphStyle(id, style) takes the object that the page's
odr.editing.format takes, and replays it through the envelope, so the
parser of the wire checks it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tigcf9he3w1aHfNPJoEGsd
andiwand added a commit to opendocument-app/OpenDocument.test.output that referenced this pull request Sep 29, 2026
…raphs

The engine now reads w:jc="both", so justified docx paragraphs render with
text-align:justify. resources/document.js and resources/editing.js carry
the paragraph alignment of the document editor.

From opendocument-app/OpenDocument.core#942.
andiwand and others added 2 commits September 29, 2026 07:53
Twelve docx pages now show text-align:justify where the file states
w:jc="both", and the embedded document.js and editing.js align paragraphs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tigcf9he3w1aHfNPJoEGsd
Paragraph::set_style returns early when text_align is not set, so the
engines no longer check it and no longer write an empty pPr. The tests
share one tree walk for nth_run and nth_paragraph. The changelog entry and
some doc comments are shorter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0168TSFHrPyguyvsxTXLXqMD

This branch has not been deployed

No deployments
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