feat(page-numbering): support hebrew1 and hebrew2 page number formats - #3935
Conversation
PageNumberFieldFormat had no Hebrew member, so a section carrying <w:pgNumType w:fmt="hebrew1"/> fell through formatPageNumber's default arm and rendered Latin digits. The format string reaches the layout bridge verbatim from the document (incrementalLayout reads section.numbering?.format), so this affects any Word document that numbers its pages in Hebrew, not just documents built through the API. Note this is a different symptom from the list-marker gap: an unmapped list format produces an empty marker, an unmapped page format produces the wrong number. Values in range are byte-identical to Word 16, read from a PAGE field in a document whose sectPr carries each format. These deliberately do not reuse the list-numbering formatters, because Word does not treat the two paths alike. Both stop representing Hebrew numerals above 392, but a list marker wraps back to Χ while a PAGE field substitutes an error string localized to the Word UI language. Measured on a document with <w:pgNumType w:fmt="hebrew1"/> in one section and hebrew2 in another: page 392 is the last one numbered in both, and 393 onward read "Χ©ΧΧΧΧ! ΧΧΧ ΧΧ€Χ©Χ¨ΧΧͺ ΧΧΧΧ¦Χ ΧΧͺ ΧΧΧ‘Χ€Χ¨ ΧΧͺΧΧ ΧΧͺ Χ©Χ¦ΧΧΧ Χ." on a Hebrew-UI Word 16. There is no locale-independent string to reproduce, so falling back to decimal keeps the page readable, and the divergence is documented at the call site. formatPageNumber already clamps its input to >= 1, so unlike the list path these formatters need no guard for zero or negative counters. isDigitBucketCompatiblePageNumberFormat in layoutHeaderFooter already excludes anything but decimal and numberInDash, so both formats correctly take the per-page layout path with no change there.
sections.setPageNumbering enforces PAGE_NUMBER_FORMATS at runtime through assertOneOf, so 'hebrew1' and 'hebrew2' were rejected with "must be one of: decimal, lowerLetter, ..." even though the layout engine can now render them. That left the API unable to express a numbering style Word writes and SuperDoc displays. The read side was already inconsistent with the write side: sections.list returns whatever w:pgNumType/@w:fmt the document carries, so a Word document numbered in Hebrew reported format: 'hebrew1' from a union that could not accept it. A form that round-tripped that value threw on save. Widen the runtime list, the SectionPageNumberingFormat union, the sd-sections projection, and the contract enum together so all four agree, and add a consumer-typecheck fixture asserting both formats are assignable from outside the package. Pre-existing and left alone: PageNumberFieldFormat in the layout contracts also carries 'ordinal', which SectionPageNumberingFormat still does not. That gap is older than this change and widening it here would be unrelated scope.
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more β On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
Code Review by Qodoπ Bugs (0) π Rule violations (0) π Requirement gaps (0)
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewTip of the dayπ‘ Did you know, you can ask Qodo to dismiss a finding you disagree with, with your reason on record Powered by Qodo |
caio-pizzol
left a comment
There was a problem hiding this comment.
Thanks for the detailed work here β approved! We verified this with two Microsoft Word-created fixtures, not just unit tests. With the companion DOCX Engine change, both formats render correctly in Chromium and survive an unrelated edit, export, and reopen without losing w:fmt. Removing that engine change reproduces the decimal fallback. We also agree that decimal is the safest fallback above 392. I updated Fixes to Relates so #3933 stays open until both numbering paths ship.
Relates to #3933. Sibling of #3934 β the two are independent and can be reviewed and merged in either order.
Two commits:
feat(page-numbering): support hebrew1 and hebrew2 page number formatsfeat(document-api): allow hebrew page numbering formatsWhat changed
PageNumberFieldFormat['format']had no Hebrew member, so a section carrying<w:pgNumType w:fmt="hebrew1"/>fell throughformatPageNumber'sdefault:arm and rendered Latin digits. Note this is a different symptom from the
list-marker gap in #3934 β there an unmapped format produced an empty marker;
here it produces the wrong number.
The format is preserved in OOXML, but the shipped v2 runtime currently filters it out before the public formatter and
sections.list. This PR adds the public formatter and API support. A companion DOCX Engine change is still required for rendered output and API reads; we implemented that internally and verified the pair against Word-authored fixtures. Thetests/consumer-typecheckfixture also checks that both values are assignable from outside the package.The one judgement call
Word does not wrap on this path β it errors. Both paths stop representing
Hebrew numerals above 392, but where a list marker restarts from
Χ, aPAGEfield substitutes a localized error string. Measured directly, with
<w:pgNumType w:fmt="hebrew1" w:start="388"/>and ten pages:Χ©Χ¦Χ©Χ¦ΧΧ©Χ¦ΧΧ©ΧΧΧΧ! ΧΧΧ ΧΧ€Χ©Χ¨ΧΧͺ ΧΧΧΧ¦Χ ΧΧͺ ΧΧΧ‘Χ€Χ¨ ΧΧͺΧΧ ΧΧͺ Χ©Χ¦ΧΧΧ Χ.hebrew2was measured the same way in a second section of the same document andhas the identical bound β
βΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧ¦at 392, the same error string at393 β so this is a property of Word's Hebrew converter, not of one format.
That string is written in whatever language the Word UI runs in, so reproducing it faithfully would mean guessing a locale. This PR falls back to decimal above 392 instead. We agree with that choice: it keeps the page readable, matches the existing
default:behavior offormatPageNumber, and avoids inventing a locale-specific error message.This is also why the page formatters deliberately do not reuse the
list-numbering formatters in #3934, despite identical glyph rules in range.
Also verified, no change needed
isDigitBucketCompatiblePageNumberFormatinlayoutHeaderFooteralreadyreturns
falsefor anything butdecimal/numberInDash, so both Hebrewformats correctly take the per-page layout path as-is.
formatPageNumberalready clamps its input to>= 1, so unlike the list paththese formatters need no zero/negative guard.
Pre-existing gap left alone
PageNumberFieldFormatcarries'ordinal', whichSectionPageNumberingFormatstill does not. That predates this change; widening it here would be unrelated
scope.
Checks
packages/layout-engine/contractsβ 15 tests pass (10 existing + 5 new)@superdoc/document-apiβ passpnpm run check:types(tsc -b) β passpnpm run docapi:syncregenerates cleanly; contract parity and contractoutput checks pass when run directly
vp lint,vp fmt --checkβ cleanEnd-to-end verification
We reproduced the decimal fallback and confirmed the missing filter lives in the DOCX Engine ahead of this public formatter. With this PR plus the companion engine change, two Microsoft Word-created fixtures render
Footer Χ/Footer Χforhebrew1and the matching U+200F values forhebrew2. After an unrelated edit, export, and reopen, both files keepw:pgNumType/@w:fmtand still render correctly. Removing the engine change makes both fixtures fail back toFooter 1/Footer 2. The visible fix therefore needs both changes in the same DOCX Engine release.