Skip to content

feat(docx): honour w:wordWrap for Hangul line breaking - #790

Merged
developer0hye merged 1 commit into
mainfrom
feat/docx-word-wrap
Aug 5, 2026
Merged

feat(docx): honour w:wordWrap for Hangul line breaking#790
developer0hye merged 1 commit into
mainfrom
feat/docx-word-wrap

Conversation

@developer0hye

Copy link
Copy Markdown
Owner

Summary

w:wordWrap is Word's switch between word-level and character-level Hangul line
breaking: w:val="1" keeps an eojeol whole, w:val="0" allows a break at any
syllable. It was not parsed, so a paragraph explicitly asking for
character-level breaking still got the word-level rule #626 applies to every
non-justified paragraph.

The dependency came first

docx-rs had no word_wrap anywhere in docx-core, which is what #730 was
blocked on. Added upstream in developer0hye/docx-rs#3 and merged into the
fix/parse-tolerance branch this repo pins, so no change is needed here beyond
reading the field.

That PR records both values rather than only the true case, unlike the
neighbouring flags — w:val="0" is the setting that carries the instruction,
and collapsing it to None is indistinguishable from the property being absent.

Measured

The same Korean sentence in a narrow column, three times, differing only in the
property:

paragraph before after
w:val="1" …신뢰를 / 바탕으로… …신뢰를 / 바탕으로…
w:val="0" …신뢰를 / 바탕으로… …신뢰를 바탕 / 으로…
absent …신뢰를 / 바탕으로… …신뢰를 / 바탕으로…

Only the paragraph that asks for character-level breaking changes, and it now
breaks inside 바탕으로 instead of before it.

The expected behaviour comes from the native Microsoft Word probes recorded in
the issue, not from a local reference render: LibreOffice does not implement
this property.
All three paragraphs above break identically in a LibreOffice
24.2.7.2 render of the same file, so it cannot serve as ground truth here and
none is attached.

Change

ParagraphStyle gains word_wrap, merge_paragraph_style resolves it
explicit-over-style, and paragraph_eojeol_wrap consults it before anything the
paragraph inherits — Word's probes show a ListParagraph with w:val="0"
breaks mid-eojeol although the style alone would not, so the property has to win
over the style chain.

ParagraphStyle::merge_from gained a clause too; without it the property would
have been dropped silently on that path.

Adding the field pushed TaggedElement::ListParagraph past clippy's
large_enum_variant threshold — 232 bytes against Plain's 24 — so its
Paragraph is boxed.

Tests

Five:

  • a paragraph with w:val="0" emits no eojeol frames, so Typst may break inside
    an eojeol
  • triangulation: w:val="1" keeps its frames, so the fix is not "the property
    is present, back off"
  • the value reaches the IR from docx-rs with 0, 1 and absent all distinct
  • an explicit value beats the one the style carries, and the style still supplies
    it when the paragraph says nothing

Visual impact

  • No rendered PDF change

  • Rendered PDF change or visual evidence added

  • Reason: no document in the corpus carries w:wordWrap — 0 of 59 DOCX under
    tests/fixtures/docx and tests/golden_mocks/*/sources/docx contain the
    element — so the change is inert on everything that exists today: of the 58
    that convert, all 58 render byte-identically. It only takes effect on a
    document that sets the property, and the sole reference renderer available
    here ignores it, so there is no honest GT/Before/After triple to attach. The
    probe's extracted lines are quoted in Measured above instead.

Related: #730

w:wordWrap is Word's switch between word-level and character-level
Hangul breaking: w:val="1" keeps an eojeol whole, w:val="0" allows a
break at any syllable. It was not parsed, so a paragraph explicitly
asking for character-level breaking still got the word-level rule #626
applies to every non-justified paragraph.

The value now reaches ParagraphStyle and paragraph_eojeol_wrap consults
it before anything the paragraph inherits, because Word's own probes
show the property overrides the style chain -- a ListParagraph with
w:val="0" breaks mid-eojeol although the style alone would not.

This needed a dependency field first: docx-rs had no word_wrap anywhere,
added upstream in developer0hye/docx-rs#3 and merged into the pinned
branch.

Adding the field pushed TaggedElement::ListParagraph past clippy's
large_enum_variant threshold (232 bytes against Plain's 24), so its
Paragraph is boxed.

Related: #730
Signed-off-by: developer0hye <developer.0hye@gmail.com>
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