Skip to content

feat(page-numbering): support hebrew1 and hebrew2 page number formats - #3935

Merged
caio-pizzol merged 3 commits into
superdoc:mainfrom
Nathaniel-260:feat/hebrew-page-numbering
Aug 27, 2026
Merged

feat(page-numbering): support hebrew1 and hebrew2 page number formats#3935
caio-pizzol merged 3 commits into
superdoc:mainfrom
Nathaniel-260:feat/hebrew-page-numbering

Conversation

@Nathaniel-260

@Nathaniel-260 Nathaniel-260 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

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 formats
  • feat(document-api): allow hebrew page numbering formats

What changed

PageNumberFieldFormat['format'] had no Hebrew member, so a section carrying
<w:pgNumType w:fmt="hebrew1"/> fell through formatPageNumber's default:
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. The tests/consumer-typecheck fixture 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 א, a PAGE
field substitutes a localized error string. Measured directly, with
<w:pgNumType w:fmt="hebrew1" w:start="388"/> and ten pages:

page value Word
390 Χ©Χ¦
391 שצא
392 Χ©Χ¦Χ‘
393 שגיאה! ΧΧ™ΧŸ א׀שרוΧͺ ΧœΧ™Χ™Χ¦Χ’ אΧͺ Χ”ΧžΧ‘Χ€Χ¨ Χ‘ΧͺΧ‘Χ Χ™Χͺ Χ©Χ¦Χ•Χ™Χ Χ”.
394+ same error

hebrew2 was measured the same way in a second section of the same document and
has the identical bound β€” ‏ΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧͺΧ¦ at 392, the same error string at
393 β€” 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 of formatPageNumber, 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

isDigitBucketCompatiblePageNumberFormat in layoutHeaderFooter already
returns false for anything but decimal/numberInDash, so both Hebrew
formats correctly take the per-page layout path as-is.

formatPageNumber already clamps its input to >= 1, so unlike the list path
these formatters need no zero/negative guard.

Pre-existing gap left alone

PageNumberFieldFormat carries 'ordinal', which SectionPageNumberingFormat
still 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 β€” pass
  • pnpm run check:types (tsc -b) β€” pass
  • pnpm run docapi:sync regenerates cleanly; contract parity and contract
    output checks pass when run directly
  • vp lint, vp fmt --check β€” clean

End-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 Χ‘ for hebrew1 and the matching U+200F values for hebrew2. After an unrelated edit, export, and reopen, both files keep w:pgNumType/@w:fmt and still render correctly. Removing the engine change makes both fixtures fail back to Footer 1 / Footer 2. The visible fix therefore needs both changes in the same DOCX Engine release.

Review in cubic

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-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more β†’

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account β†’

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us β†’

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 7 files

Re-trigger cubic

@qodo-code-review

Copy link
Copy Markdown

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 review
Tip of the day
πŸ’‘ Did you know, you can ask Qodo to dismiss a finding you disagree with, with your reason on record

More tips β†— | Customize Qodo β†— | Qodo docs β†—


Powered by Qodo

@caio-pizzol caio-pizzol left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@caio-pizzol
caio-pizzol merged commit 5b6d135 into superdoc:main Aug 27, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants