Repository navigation
Conversation
`w:autoHyphenation` (§17.15.1.10), `w:hyphenationZone` (§17.15.1.53), `w:consecutiveHyphenLimit` (§17.15.1.22) and `w:doNotHyphenateCaps` (§17.15.1.27) land in a new `HyphenationSettings` on `DocumentSettings`, whose `Default` holds the spec's omitted values: off, 360 twips, no limit, capitals hyphenated. The zone reuses `DimensionVal`, so the §22.9.2.14 universal-measure spellings parse exactly as they do for `w:defaultTabStop`, and `0` is read as a value (test-files/document_outline.docx declares it). Tests were written from the clauses first and watched fail — five of six against the unchanged parser (the sixth, the universal-measure case, passed vacuously against the 360 default and was changed to use 720-twip spellings so it cannot). Nothing consumes the new field yet.
…pro#158) `ResolvedDocument.hyphenation` is `DocumentSettings.hyphenation` moved across the resolve seam, beside `default_tab_stop` and `even_and_odd_headers`, so `paragraph_style_from_props` can read it next to the cascaded §17.3.1.34 `w:suppressAutoHyphens` it must be combined with. The six test literals of `ResolvedDocument` gain the field as `Default::default()`. Test written first: `resolve_carries_the_hyphenation_settings` sets every field to a non-default value and was watched fail (E0609, no such field) before the field existed. Nothing consumes it yet.
…e, ru (nerdy-pro#158) Adds `hyphenation = "=0.8.4"` with `default-features = false` — no `embed_all` (all 82 dictionaries, ~2.8 MB, the nerdy-pro#124 wheel-size risk) — and vendors only four `.standard.bincode` files into src/i18n/data/hyph/ through scripts/make_hyphenation_data.sh, whose LANGUAGES list is the single source of truth, in the shape of scripts/make_icu_data.sh. The files are tied to the pinned crate version (bincode 1.x schema of `Standard`), which the script reads back from Cargo.toml rather than restating. Raw 385,596 bytes, 214,624 deflated; the wheel delta is deliberately left unmeasured here (no release build in this environment) and the Cargo.toml comment says so. src/i18n/hyphenate.rs: `HyphenLanguage` (own ADT, four variants) maps a §17.3.2.20 tag by primary subtag — every `de-*` to de-1996, since the crate's only Swiss set is the 1901 orthography; `en-GB` to the British patterns, every other `en` to American; unknown or absent → None, never hyphenate — and `opportunities(word, lang)` returns the byte offsets the patterns allow, from one `OnceLock<Standard>` per language (`Standard` is Send + Sync, so no thread_local). ATTRIBUTION.md carries the per-language pattern licences (MIT for de-1996 and en-gb, Kuiken's notice for en-us, LPPL 1.2+ for ru). Tests first: seven of nine were watched fail against stubs returning None / empty (the crate's documented examples, the German compound seams, the Russian syllables, the tag map, the minima, the boundary contract, en-gb's exception list); the vacuous case (capitalised == lowercase on two empty vectors) was tightened to require a non-empty answer. The dictionary contract test was mutation-checked by loading `ru` under `Language::Ukrainian`, which fails it.
…nary (nerdy-pro#158) `FontProps.hyphenation: Option<HyphenLanguage>`, set in `font_props_from_run` from the *run's* cascaded §17.3.2.20 `w:lang/@w:val` — not from the paragraph mark `resolve_lang_tag` walks, because a German quotation inside an English paragraph is a run-level fact — with `Locale::first_tag`'s rule that an empty `@w:val` sets nothing. Only the Latin-script `@w:val` is read; `@eastAsia` and `@bidi` name scripts the fitter will exclude anyway (CJK never hyphenates, Arabic is shaped). On `FontProps` for the reason `rtl` is: per run, shared by `Rc` across every text fragment of the run. Every other `FontProps` literal (34, mostly test helpers) gets `hyphenation: None`. Nothing reads the field yet. Test first: `the_runs_cascaded_lang_picks_its_hyphenation_dictionary` was watched fail (E0609, no such field) before the field existed; it pins the four tags, an unknown language, an empty value, an absent `w:lang`.
) `fit_lines_in` is the fitter over a `Cow<[Fragment]>` with a `FitWindow` (start index + optional line limit) and an optional `HyphenationCtx`; the old `fit_lines`/`fit_lines_with_first` keep their signatures as wrappers passing `Cow::Borrowed`, `FitWindow::ALL` and no context, so every existing caller and test is unchanged and a document without `w:autoHyphenation` never materialises a copy. At the overflow site, before falling back to the last UAX nerdy-pro#14 opportunity, `hyphenate_at` tries to cut the word that did not fit: only a whole, unshaped, non-footnote-mark word of a run whose cascaded `w:lang` has a vendored dictionary, only when the gap the visible line would leave exceeds the §17.15.1.53 zone (strictly, so zone 0 is "any gap"), only while the §17.15.1.22 consecutive count is under the limit, and never an all-caps word under §17.15.1.27. Among the pattern points the longest head whose `head + "-"` re-measures to fit wins; the head is spliced in place with `BreakAfter::Hyphenated` (new variant, the engine's own) and its text already ending in U+002D, the tail inserted after it with the word's own `break_after` and hanging trailing space, so emission needs no change. Tokens with digits or interrupted letter runs are not words to the patterns (`hyphenation_core`). The margin-span ptab case is skipped and says so. The zone reading is the clause's normative text (gap > zone → attempt), and the comment at `hyphenate_at` records that Word in native layout (compatibilityMode 15, unparsed here) may hyphenate more — a Word reference render is still owed. `FitWindow::max_lines` exists for the floats path, which fits one line per call and must not leave hyphenated words behind in a vector it is about to re-fit. Tests written from the clauses first; twelve were watched fail against the fitter without the branch while all 23 pre-existing fitter tests stayed green through the Cow/window refactor. Mutation checks, each restored: dropping the zone comparison fails the two zone tests; ignoring the shaped/footnote guards fails those two; never resetting the count fails the reset test; dropping the glued-to-previous guard fails the run-boundary test; taking the shortest head fails nine.
nerdy-pro#158) `ParagraphStyle.hyphenation: Option<AutoHyphenation>` (zone in points, consecutive limit, caps flag) is `Some` only when the document turns §17.15.1.10 on *and* the paragraph's cascaded §17.3.1.34 `w:suppressAutoHyphens` is not `Some(true)`; `paragraph_style_from_props` takes `&HyphenationSettings` beside `default_tab_stop`, and its three callers pass `ctx.resolved.hyphenation`. Carried on the style so every `place_paragraph` caller — body, table cells through the stacker, split continuations — inherits it with no per-caller plumbing. `place_paragraph` builds the fitter's `HyphenationCtx` from the style, the measurer and `i18n::hyphenate::opportunities`, only when a measurer exists (registry-less layout hyphenates nothing, as `split_oversized_fragments` degrades), and `compute_line_placements` now takes the effective vector as `&mut Cow` and a `FitWindow`: the floats path fits from `frag_idx` in the whole vector with `max_lines: Some(1)` instead of slicing and remapping, which is identical for every paragraph without the setting and the only shape that can hold a spliced word. Tests first: the §17.3.1.34 test in convert.rs was watched fail (arity and missing field), the paragraph-level end-to-end test (real en-US patterns, 6 pt measurer, `hyphen-` / `ation` painted) was watched fail with the context still `None`, and its two controls (no measurer, no setting) pass on either side. One test helper's final word had to be given `BreakAfter::Opportunity` explicitly — `fixture_break_after` calls a bare word glued, where the production segmenter gives a paragraph's last piece an opportunity.
…y-pro#158) The §4 continuation re-fit (`section/layout.rs` → `fragments_from_line` → `place_paragraph` under `continuation_style`) re-places the rest of a split paragraph at the next page's or column's width, around that page's floats. A word hyphenated for the first width may fit whole there, and re-fitting the already-cut fragments puts `hyphen-` beside `ation` on one line — a hyphen painted mid-word. `unbreakable_prefix_lines` only guards lines narrowed on the first page, so the hole was real. `line::rejoin_hyphenated` folds every `BreakAfter::Hyphenated` head back into the tail after it — hyphen dropped, text concatenated, width re-measured through the paragraph's measurer, the tail's own `break_after` restored — and `fragments_from_line` runs it over the remainder. A tail whose head ended the emitted segment is left alone: its head is painted. The `Hyphenated` variant is what makes the undo possible, which is the argument for a variant on the fragment over a flag on `FittedLine`. Tests first, all watched fail: `fragments_from_line_rejoins_a_hyphenated_head_with_its_tail` (remainder had `hyphen-`/`ation`), and the end-to-end `a_word_cut_in_a_narrow_column_is_whole_again_in_the_wide_one` in section/mod.rs, which lays a hyphenating paragraph through a 66 pt column into a 200 pt one and reproduced the predicted `hyphen-` `ation` pair on the wide column's first line before the fix. Its control twin pins that a tail whose head was emitted travels untouched, and `rejoin_hyphenated`'s own test checks text, break status, width and hanging space round-trip.
…ro#158) scripts/make_auto_hyphenation_fixtures.py builds one justified de-DE document in a 2 in column under six w:settings variants — the spec defaults, a 7200-twip zone, zone 0 with consecutiveHyphenLimit 1, zone 0 with doNotHyphenateCaps, autoHyphenation off, and a compatibilityMode-15 twin kept as a probe — plus an unequal-column document (§17.6.4) whose one long paragraph flows from a 2 in column into a 4 in one, the float-free way to reach the continuation re-fit. Four paragraphs: German prose of compounds whose seams the de-1996 patterns and Word's dictionary agree on, the same prose exempted by w:suppressAutoHyphens, KRAFTFAHRZEUG/Kraftfahrzeug behind varying prefixes, and German prose carrying an en-US run. No authored hyphen or digit anywhere, so every `-` at a rendered line end is an inserted one. Every word is narrower than the column, so none takes the per-cluster over-wide path (the first draft's Donaudampfschifffahrtsgesellschaft did, and came out as gesel|lschaft — that path's pre-existing behaviour). tests/auto_hyphenation.rs asserts properties of the DrawCommand::Text stream grouped by baseline, never a transcript: every cut rejoins to a word of the paragraph and lies at a pattern point of the *run's* language (en-US inside the German paragraph), an inserted hyphen is only ever the last piece of its line, the exempted twin and the wide zone and the off document insert nothing, no two consecutive lines end in one under limit 1 (with the limitless twin as the control that they otherwise do), capitals stay whole only under doNotHyphenateCaps while Kraftfahr-zeug beside them is cut, the narrow→wide column paragraph never shows a hyphen mid-line, and test-files/document_outline.docx — autoHyphenation on, zone 0, docDefaults suppressAutoHyphens — paints no `-` its text does not contain (the §17.7.5.2 control the adversarial review asked for). Mutation-checked, each restored: a style that never carries the setting fails five tests; dropping the rejoin before the re-fit fails the columns and last-piece tests; ignoring doNotHyphenateCaps, the limit, and suppressAutoHyphens each fails exactly its own test. All seven fixtures pass scripts/verify_docx.py and are in parse_test_files.rs's ALL_FILES and the AGENTS.md corpus table; compat15 is marked a probe there.
…on (nerdy-pro#158) "Automatic hyphenation" flips to ✅ with its caveats stated in the cell: the normative zone reading (Word's native layout may hyphenate more), TeX patterns rather than Word's dictionary (cut points can differ), the four vendored languages with every other tag and every untagged run left whole, w:softHyphen still dropped, and no hyphenation of a word already cut per cluster for being wider than its line. The summary line is recomputed from the table — 83 implemented, 12 partial, 5 not yet supported — which tests/readme_feature_matrix.rs pins; "automatic hyphenation" leaves the FAQ's list of notable gaps; the dependency table gains the `hyphenation` row and names the attribution file. src/i18n/segment.rs's module doc gains the one paragraph on what it deliberately does not report: hyphenation inserts a glyph and depends on the line's remaining width, so it belongs to the fitter, not to UAX nerdy-pro#14.
…erdy-pro#158) `compute_line_placements` fits one line per call when `page_floats` is set, each at its own width, and `FitWindow::max_lines: Some(1)` is what keeps a word cut for one line from being left spliced for a later call to re-fit at another width. Nothing drove that path with a hyphenating paragraph: `a_window_fits_from_its_start_and_stops_at_its_line_limit` builds the window itself, so it stays green when the call site passes `max_lines: None`. `the_floats_path_hyphenates_each_line_at_its_own_width` lays six pairs of `the hyphenation ` through a float that narrows the first three 14 pt lines of a 126 pt paragraph to 66 pt, and asserts that the narrow lines hyphenate, that lines at both widths do, and that no inserted hyphen is followed by anything on its line. Watched fail under the mutation `max_lines: None` at the floats call site, which fits every remaining line at 66 pt on the first call and then paints `the hyphen- ation the ` as the fourth line; passes with `Some(1)`. The paragraph tests' `tagged` closure becomes `hyphenating_word` so this test can share it. Adversarial review: the §4 continuation rejoin was re-checked the same way — with `rejoin_hyphenated` made a no-op, its three unit tests and both columns integration tests fail, and this test does not, since the floats path never re-fits.
ikashapov
added a commit
to ikashapov/dxpdf
that referenced
this pull request
Oct 14, 2026
One squashed commit carrying the tree of integration/2026-10-14, which was previously a chain of ten merges ending at ac5787e (kept locally as archive/integration-2026-10-14-merges). Base: upstream main 3a5c7ca. Integrated, each at the head of its open upstream pull request: - nerdy-pro#252 fix-layout-defects-228-232 (a40d5ce) — issues nerdy-pro#228–nerdy-pro#232: bookmark-only paragraphs, nested-table width, list instances over one abstract numbering, table width resolution (pct and tcW autofit), column balancing and separators - nerdy-pro#183 issue-150-wmf-svg (6e5aa99) — WMF/EMF and SVG images - nerdy-pro#184 issue-151-pgborders-docgrid (27204b3) — page borders, document grid - nerdy-pro#186 issue-153-shaped-clusters (65739f6) — shaped text clusters - nerdy-pro#187 issue-154-tracked-changes (6982b34) — tracked changes and comments - nerdy-pro#185 issue-155-smartart-charts (3683223) — SmartArt and charts - nerdy-pro#256 issue-253-del-instr-text (0656a52) — w:delInstrText tolerated - nerdy-pro#257 issue-250-image-hyperlinks (cb5ce2b) — a:hlinkClick on pictures - nerdy-pro#258 issue-188-font-absent-cost (9a63bf0) — no deep catalogue tier before the substitution table, no theme script-font preload - nerdy-pro#259 issue-158-auto-hyphenation (9bfbae2) — w:autoHyphenation Beyond the union of those branches, the integration itself changed: - README keeps one row per feature after the merges; the matrix tally is recomputed from the rows. - nerdy-pro#253's tests assert that a deleted field code is never drawn and never run text, because nerdy-pro#154 keeps w:del runs instead of dropping them at parse. - nerdy-pro#250's push_link_over sits after nerdy-pro#151's resolve_line_metrics; nerdy-pro#158's HyphenationCtx.measure and rejoin_hyphenated take &dyn SplitMeasure (nerdy-pro#153). Verified on this exact tree: cargo fmt, clippy and doc clean; cargo test 2977 passed / 0 failed (skipping the capi library freshness guards); --no-default-features and --features capi build; the twelve-document harness passes 12/12.
This branch has not been deployed
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.
Implement automatic hyphenation (§17.15.1.10)
Closes #158.
What Word does
A document that declares
<w:autoHyphenation/>insettings.xmlis hyphenated by the consumer: when the next word does not fit on the line, Word cuts it at a point its proofing dictionary allows for the run's language, paints the head with a hyphen and carries the tail to the next line. Three companion settings control it —w:hyphenationZone(§17.15.1.53, default 360 twips; "the amount of whitespace which can be left at the end of a line … before hyphenation should be attempted"),w:consecutiveHyphenLimit(§17.15.1.22, how many lines in a row may end in a hyphen; 0 or omitted is no limit) andw:doNotHyphenateCaps(§17.15.1.27) — and a paragraph exempts itself withw:suppressAutoHyphens(§17.3.1.34), a §17.7.2 cascade property thatdocDefaultsand styles can set too. The language is the run'sw:lang(§17.3.2.20), so a German quotation inside an English paragraph is cut as German.dxpdf parsed none of the four settings and dropped
w:langbefore it reached a fragment, so a hyphenating document rendered with ragged or over-stretched lines and a different line count from Word.What this does
Parse → model → resolve.
w:autoHyphenation,w:hyphenationZone,w:consecutiveHyphenLimitandw:doNotHyphenateCapsland inHyphenationSettingsonDocumentSettings, whoseDefaultcarries the spec's omitted values (off, 360 twips, no limit, capitals hyphenated), and are copied ontoResolvedDocument. The zone sharesw:defaultTabStop's deserializer, so the universal-measure spellings parse, and0is a value — hyphenate on any gap — not an absence (test-files/document_outline.docxdeclares it).Language to the fragment.
FontProps.hyphenation: Option<HyphenLanguage>is resolved infont_props_from_runfrom the run's cascadedw:lang/@w:val, besidertland for the same reason: it is per run and every text fragment of a run shares it byRc. An untagged run, or one in a language with no vendored patterns, is never hyphenated — falling back to English was considered and rejected, since it would cut German text at English syllables.Patterns.
hyphenation = "=0.8.4"withdefault-features = false: Knuth–Liang over the TeX hyph-utf8 pattern sets, pinned exactly because the vendored dictionaries are serialized in that version'sbincodeschema and the crate's last release is from 2021. Noembed_all(82 dictionaries, ~2.8 MB — the #124 wheel-size risk).scripts/make_hyphenation_data.sh, in the shape ofmake_icu_data.sh, copies four.standard.bincodefiles out of the pinned crate source intosrc/i18n/data/hyph/—en-us,en-gb,de-1996,ru; 385,596 bytes raw, 214,624 deflated — withATTRIBUTION.mdcarrying each pattern set's licence (MIT for de-1996 and en-gb, Kuiken's permissive notice for en-us, LPPL 1.2+ for ru).src/i18n/hyphenate.rsmaps a tag by primary subtag (everyde-*tode-1996, because the crate's only Swiss set is the 1901 orthography;en-GBto the British patterns, every otherento American) and answersopportunities(word, lang)from oneOnceLock<Standard>per language.The fitter. Hyphenation happens in one place,
layout::line::fit_lines_in, at the one moment a word is known not to fit. The fitter now works over aCow<[Fragment]>with aFitWindow(start index and optional line limit) and an optionalHyphenationCtx;fit_lines/fit_lines_with_firstkeep their signatures as wrappers passing a borrowed slice and no context, so a document without the setting takes exactly the old code path and never materialises a copy — pinned by a bit-for-bit test. At overflow,hyphenate_atcuts the word that did not fit only when it is a whole, unshaped, non-footnote-mark word of a run with a dictionary; only when the gap the visible line would leave exceeds the zone (strictly, so zone 0 is "any gap"); only while the consecutive count is under the limit; never an all-caps word underdoNotHyphenateCaps; never a token with digits or an interrupted run of letters (don't,hard-nosed). Among the pattern points the longest head whosehead + "-"re-measures to fit wins. The head is spliced in place with the newBreakAfter::Hyphenatedand its text already ending in U+002D; the tail follows with the word's own break status and hanging trailing space. Emission needs no change: it paints the fragment's own text, and justification already measures from the fragments.Threading.
ParagraphStyle.hyphenation: Option<AutoHyphenation>isSomeonly when the document setting is on and the paragraph's cascadedw:suppressAutoHyphensis notSome(true); carried on the style so body paragraphs, table cells and split continuations all inherit it.place_paragraphbuilds the context when a measurer exists; the floats path now fits from an index into the whole vector withmax_lines: Some(1)instead of slicing and remapping — identical for every paragraph without the setting, and the only shape that can hold a spliced word.The continuation re-fit. The remainder of a paragraph split across a page or column is re-placed at the next page's width, and a word cut for the first width may fit whole there; re-fitting the cut fragments would paint
hyphen-besideationon one line.fragments_from_linenow runsline::rejoin_hyphenatedover the remainder — everyHyphenatedhead folded back into its tail, re-measured, the tail's break status restored — which is also the argument for a variant on the fragment rather than a flag on the line. The hole was reproduced before the fix by a paragraph flowing from a 66 pt column into a 200 pt one.What is deliberately left out
Each is stated in a comment at the site that makes the choice.
compatibilityMode15) ignores the zone altogether. This implements the normative reading with the 360-twip default; dxpdf does not parsew:compat, so Word may hyphenate more than this does, never less.test-files/auto-hyphenation-compat15.docxis kept as a probe for the Word render that settles it.ei-neis a legal cut here that Word would not make). The fixtures use compounds both agree on; the README cell says so.w:softHyphenis still dropped at parse; a word already cut per cluster bysplit_oversized_fragmentsfor being wider than its line is not hyphenated (Word would cut it at a syllable); a word glued across a<w:r>boundary, or to a list label's suffix, is left whole; shaped runs (Arabic, Hebrew, RTL) and every script without patterns are never hyphenated;@eastAsia/@biditags are not consulted; lines governed by a margin-relative position tab are skipped; the consecutive count restarts on a page continuation; the wheel delta was not measured here (no release build in this environment — the Cargo.toml comment says what to record).LANGUAGESin the regen script when a fixture and a caller justify one, the rulemake_icu_data.shalready applies.How it was verified
Every test was written from the clause first and watched fail, and each commit message records what failed; every new test was mutation-checked against the implementation it guards (the commit messages name the mutations and which tests caught them).
docx::parse::settings,model::types::settings); the resolve passthrough; the tag map, the crate's documented examples, a German compound's seams, Russian syllables, the minima and the boundary contract, and that every vendored dictionary loads (i18n::hyphenate); the run's cascadedw:langreachingFontProps; nineteen fitter tests over a fixed-advance measurer and a table of cuts — the zone both ways and its strictness, the visible-width gap, longest head, no fitting head, the consecutive limit and its reset, caps, glued words, untagged/shaped/footnote fragments, punctuation and trailing-space handling, the window, the rejoin, and bit-for-bit identity with no context; the §17.3.1.34 cascade inconvert.rs; the paragraph layer end to end with the real patterns, with its no-measurer and no-setting controls; and a section-level narrow→wide column paragraph that reproduced the continuation hole before the fix; and the floats path at the paragraph layer — one line per call, each at its own width, a float narrowing the first three lines — which hyphenates at both widths and never leaves an inserted hyphen mid-line, mutation-checked againstmax_lines: Noneat its call site (the fitter's own window test builds the window itself and stays green under that mutation; this one does not).tests/auto_hyphenation.rs) over seven fixtures built byscripts/make_auto_hyphenation_fixtures.py(all passscripts/verify_docx.py): every cut rejoins to a word of the paragraph and lies at a pattern point of the run's language (theen-USrun inside the German paragraph is cut atunderstand-ing); an inserted hyphen is only ever the last piece of its line; the exempted twin, the 7200-twip zone and the off document insert nothing; no two consecutive lines end in one under limit 1, with the limitless twin as the control that they otherwise do; capitals stay whole only underdoNotHyphenateCapswhileKraftfahr-zeugbeside them is still cut; the unequal-column paragraph never shows a hyphen mid-line; anddocument_outline.docx—autoHyphenationon, zone 0,docDefaultssuppressAutoHyphens— paints no hyphen its text does not contain (the §17.7.5.2 control).cargo fmt --check,cargo clippy --all-targets -- -D warnings,RUSTDOCFLAGS="-D warnings" cargo doc --no-deps,cargo test --all --no-fail-fast(skipping the two committed-Go-library freshness tests, which need a release build),cargo check --no-default-features,cargo check --features capi— all green; see the branch report for counts.test-files/*.docxrendered with the upstream-main debug binary and with this branch's, comparingpdfinfopage counts andpdftotext -layoutoutput. 56 documents (49 pre-existing + the 7 new fixtures): no page count changed anywhere, and the text of all 49 pre-existing documents is byte-identical — among themdocument_outline.docx(autoHyphenationon with adocDefaultsexemption),sample-emoji.docx(autoHyphenation w:val="0") andfont_scaling.docx(ahyphenationZonewith noautoHyphenation). Text differs in exactly five documents, all new and all carrying<w:autoHyphenation/>with a zone that permits cuts:auto-hyphenation-{base,compat15,limit-1,nocaps,columns}. The two new fixtures that must not hyphenate —zone-wide(a 7200-twip zone, wider than the column) andoff— render identically to the reference binary, which hyphenates nothing.