Skip to content

M6: check boxes, radio buttons and push buttons without an appearance are drawn, and comb fields show their cells (ISO 32000-2 Table 192, 12.7.5) - #4368

Merged
aiongg merged 2 commits into
storytold:mainfrom
mtlcl410:fix/form-buttons-without-appearance
Oct 24, 2026
Merged

aiongg merged 2 commits into
storytold:mainfrom
mtlcl410:fix/form-buttons-without-appearance

Conversation

@mtlcl410

@mtlcl410 mtlcl410 commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

What this changes

Fixes #4367.

The need. A form can leave its appearances to the viewer, with /NeedAppearances or with widgets that have no /AP. #3475 draws text and choice fields in that case, for display only. Buttons were left out:

  • a check box or radio button that is on but has no appearance showed nothing, not even its border;
  • a push button with none showed nothing;
  • a comb field showed its characters but not its cells.

The basis. ISO 32000-2:

  • Table 192: BC and BG are the widget's border and background colours. CA is "the widget annotation's normal caption", and it "may be used with any type of button field, including check boxes … and radio buttons".
  • §12.7.5.2.3: a check box shows the appearance of its /AS state, and the example's on state draws (4) Tj in /ZaDb, the ZapfDingbats check mark (Annex D.6).
  • Table 168: /S /B is a beveled border, "a simulated embossed rectangle".
  • Table 228, Comb: "the field shall be automatically divided into as many equally spaced positions, or combs, as the value of MaxLen".

The change (pdfcraft_forms::with_field_appearances, appearance.rs)

  • Check boxes and radio buttons with no normal appearance for their state (/AS; else a check box's /V; else Off), or none at all, are drawn into the display copy (button_state_shown):
    • the /MK /BG background and the /MK /BC border, through the existing frame (/BS width and style);
    • when the state isn't Off, the first character of /MK /CA in the standard-14 ZapfDingbats font, so no font program is needed and it renders on wasm;
    • without a usable caption, 4 (check mark) for a check box and l (dot) for a radio button;
    • in the /DA colour, at the /DA size, or at 80% of the room inside the border for 0 Tf.
    • When the widget has a state dictionary, an Off state without its own appearance stays blank: the spec allows Off to be missing.
  • Push buttons with no normal appearance (push_button_shown): the same frame, so /BS /W 2 /S /B is a 2 pt beveled border, and the caption centred through the existing button_caption. A button with an icon (/MK /I) is drawn by PdfCraft's own button_appearance.
  • Comb fields with a border colour draw the MaxLen - 1 lines between their positions (comb_dividers), in the border's colour and /BS width, inside the border. Above 500 cells nothing is drawn; that many lines would be a solid block.
    • This is in the shared text-field drawing code, so it applies both to display-only appearances and to the appearances PdfCraft saves when it fills a comb field.
  • Saving writes the file as it was, as in fix(forms): draw fields the file leaves to the viewer, for display only (#3470) #3475. None of this enters /AP in the file, except the comb lines in appearances PdfCraft generates itself.
  • Combo boxes are left to M6: a drop-down shows its arrow, and its list looks and works like the form's own #3567, which draws the drop-down arrow for every combo box on screen. This PR doesn't change how they are drawn.

How you verified it

  • cargo fmt --all -- --check
  • clippy with -D warnings on the crates I changed: -p pdfcraft-forms -p pdfcraft-engine -j 2 --all-targets -- -D warnings clean
  • tests pass on the crates I changed: pdfcraft-forms 153 passed, 0 failed; pdfcraft-engine 351 passed, 0 failed, 4 ignored
  • cargo check --workspace --all-targets, cargo xtask layers, cargo xtask assets, cargo xtask parity: all clean (37 crates, no layering violations; assets ok)

Tests (synthetic fixtures)

  • check_boxes_without_an_appearance_show_their_caption_symbol: /CA 4, l, 8 and H in black, blue, red and black, each with its border; a /DA size is kept; the font is the standard-14 ZapfDingbats.
  • button_states_without_an_appearance:
    • an Off box shows only its border;
    • a radio button without a caption shows l;
    • a box whose file has only an Off appearance gets an on one;
    • a box with its own appearances keeps them;
    • nonsense /CA (an integer), /DA (nan Tf, 1 x rg), /BC, /BG and a negative /BS /W give the default symbol and no border, without a panic;
    • the source document is unchanged.
  • push_buttons_without_an_appearance_show_their_caption_and_frame: grey background, 2 pt border, the light and dark bevel, the caption at its /DA size.
  • comb_fields_show_their_cells:
    • six cells give five lines at x = 20 … 100 in the blue border colour;
    • /MaxLen 2^40 gives no lines and no trouble;
    • without /MK /BC, no lines either.
  • buttons_and_combs_left_to_the_viewer_are_displayed (engine, pixels):
    • on screen, the red cross, the push button's grey face, the comb's three blue lines, and none inside a cell;
    • Print as Image (print_raster::rasterize) shows the cross and the button too;
    • the saved file has no /AP on any of the widgets.
  • The fixture of the issue, rendered with pdfcraft-cli render, now shows I–L, O and D as described there.

Mutants. Each of these makes a test fail:

  • check boxes and radio buttons without /AP not drawn (forms tests, and the engine pixel test);
  • push buttons not drawn;
  • comb lines dropped (forms test and engine pixel test), or drawn in black instead of the border colour;
  • /MK /CA ignored;
  • a radio button defaulting to 4;
  • the symbol drawn in the Off state too;
  • the /DA colour ignored.

Never-crash

  • No new unwrap/expect/panic!/unreachable!/todo! outside tests
  • No input-derived indexing. The caption character becomes a code only through u8::try_from, and only if ZapfDingbats has a glyph there.
  • Sizes and widths: /DA sizes are capped at 300 by parse_da, the automatic size is clamped to 1–300, and a NaN or negative /BS /W is 0. Comb lines need a finite, positive border width and are capped at 500 cells, so a huge /MaxLen allocates nothing.
  • No unsafe
  • No crash fixed here

Provenance

  • No assets
  • No font files. ZapfDingbats is named as a standard-14 font, and the renderer supplies it.
  • No GPL/AGPL code
  • Behaviour comes from ISO 32000-2 Table 168, Table 192, Table 224, Table 228 and §12.7.5.2.3, and from black-box observation of how conforming viewers display a synthetic fixture.
  • No new asset, so no ATTRIBUTION.toml entry

Security

  • No new file, path or network access. Display-only drawing works on an in-memory copy.

Parity and reach

  • view.render-annotation-appearances and form.field-appearance-display notes say which buttons are drawn display-only. form.fill-text notes the comb cells. Existing text is kept.
  • Reaches the page view, page_render, the CLI render command, image exports and Print as Image. Vector printing still uses the file's own appearances, as before.

Anything a reviewer should look at first

  • How each choice was decided. The standard decides where it speaks. Where it is silent, the majority of five other conforming viewers decides, as observed on the synthetic fixture of the issue. Where they split, the first of them (viewer A) decides.

    Choice Decided by Basis
    Draw the /MK /CA symbol of an on check box the standard Table 192 makes CA the normal caption of any button field, and §12.7.5.2.3's example draws (4) in ZapfDingbats. Only 3 of 5 viewers draw it (viewer A shows the border only), and the standard wins.
    Draw the /MK /BC border and /MK /BG background of buttons without /AP the standard Table 192; all 5 viewers draw them as well
    Push button: grey face, beveled 2 pt border, centred caption the standard Table 192 (BG, BC, CA) and Table 168 (/S /B); 5 of 5 viewers agree
    Symbol size for 0 Tf (80% of the room inside the border) the viewer majority §12.7.4.3 leaves an automatic size "implementation dependent". The 3 viewers that draw the symbol size it to the box with a margin.
    Symbol in the /DA colour the standard §12.7.4.3: /DA sets the colour of the field's variable text; the 3 viewers that draw the symbol agree
    Default symbol without /CA: 4 for check boxes, l for radio buttons the standard for 4 (§12.7.5.2.3's example). For l the standard is silent and the fixture has no case without /CA, so it is PdfCraft's existing default for radio buttons (the dot). —
    Comb lines between the cells, in the border colour the viewer majority Table 228 divides the field into MaxLen positions but doesn't say to draw them; 4 of 5 viewers draw the lines, viewer A included
  • Saved comb appearances change. The comb lines are in the shared text-field drawing code, so a comb field with a border colour that PdfCraft fills or authors now saves the lines in its /AP too. Comb fields without /MK /BC are unchanged. No existing test asserted the old look; the new test covers the saved appearance.

  • Overlap with open PRs.

  • Check box state. A widget is redrawn when it has no /AP /N, or when /N is a state dictionary without its on state. /NeedAppearances alone doesn't redraw check boxes, radio buttons or push buttons that have appearances, so a file's own symbols are kept.

🤖 Generated with Claude Code

https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC

Summary by CodeRabbit

  • New Features
    • Check boxes, radio buttons, and push buttons without saved appearances now render with their configured symbols, colors, borders, and captions.
    • Comb-field cells now display colored dividers when the field has a visible border and a supported cell count.
  • Bug Fixes
    • Form widgets without saved appearances are displayed more consistently, while existing appearances remain unchanged.
    • Displaying these widgets does not add saved appearances to the source document.

mtlcl410 and others added 2 commits October 24, 2026 11:51
… are drawn, and comb fields show their cells (ISO 32000-2 Table 192, 12.7.5)

A form that leaves its appearances to the viewer had its text and choice
fields drawn for display (storytold#3475), but not its buttons. Now, also for
display only, without changing the file:

- Check boxes and radio buttons with no normal appearance for their
  state show their /MK background and border (Table 192), and when on,
  the /MK /CA caption as a standard-14 ZapfDingbats character
  (12.7.5.2.3), 4 or l by default, in the /DA colour, auto-sized for
  0 Tf.
- Push buttons with none show their /MK background, their /BS border
  (beveled per Table 168) and their centred caption.
- Comb fields with a border colour draw the MaxLen - 1 lines between
  their positions (Table 228), up to 500 cells. This also applies to the
  appearances PdfCraft saves for comb fields.
- A combo box drawn for display shows a drop-down button on screen, with
  its text kept clear of it. Saved appearances and Print as Image leave
  it out (with_printed_field_appearances, printed_display_bytes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
The on-screen drop-down button for combo boxes drawn display-only, the
narrowed text area beside it, and the Print as Image copy without it
(with_printed_field_appearances, printed_display_bytes) are removed.
Open PR storytold#3567 draws the arrow for every combo box in the page view.
Check boxes, radio buttons, push buttons and comb lines are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
@coderabbitai

coderabbitai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between a047c6b and dd1a894.

Files selected for processing: 7
  • crates/engine/src/tests.rs
  • crates/forms/README.md
  • crates/forms/src/appearance.rs
  • crates/forms/src/author.rs
  • crates/forms/src/lib.rs
  • crates/forms/src/tests.rs
  • parity/acrobat-features.toml

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.

Walkthrough

The change adds border-colored dividers to comb fields and fallback appearances for check boxes, radio buttons, and push buttons. The appearance cache adds missing button appearances to display copies. Tests cover generated appearances, rendered output, and preservation of the source document.

Changes: Widget appearances

Layer / File(s) Summary
Generate comb and button appearances
crates/forms/src/appearance.rs, crates/forms/src/author.rs, crates/forms/src/tests.rs, crates/forms/README.md, parity/acrobat-features.toml
Comb appearances draw border-colored dividers for 2–500 cells when the border dimensions are valid. Button fallback appearances draw frames and, where applicable, symbols or captions. Tests cover appearance settings, existing appearances, malformed settings, and comb limits.
Integrate fallback appearances into display copies
crates/forms/src/lib.rs, crates/engine/src/tests.rs, parity/acrobat-features.toml
The cache generates missing button appearances for display copies, stores them under named normal-appearance states, and updates /AS. Rendering tests check button and comb output and verify that saving leaves widgets without generated appearances.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: echelon

Merge Risk: Minimal · up to dd1a8

The reviewed display-only appearance changes are mergeable after normal checks.

Security architecture risk: Low · up to dd1a8

Generated appearances remain separate from the saved document, and the inspected rendering paths introduce no additional file, network, or action-execution privileges. Remaining uncertainty concerns resource limits for unusually large inputs and confirmation against the full prior version.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The inspected exposure is document-controlled widget data reaching appearance generation and downstream rendering. Any resource impact would affect the rendering consumer; no additional tenant, credential, service, or environment authority was identified in this call chain.

Security Findings and Attack Paths

  • inferred — A document can newly cause a missing push-button appearance to render its caption or document-owned icon. The inspected path emits rendering operators and resources, not document actions or external I/O. No authority-escalation attack path was established from this added reachability.

Trust Boundaries and Controls

  • observed — Generated appearances belong to a derived document overlay. Existing source objects remain separate from AP and AS updates, and persistent saving uses a different document root from display serialization.

Resilience and Maintainability Implications

  • observed — The appearance cache stores derived streams against tracked input-object identities and invalidates reuse when those identities change. Its mutable-reference API requires exclusive access rather than exposing unsynchronized shared mutation.
Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title clearly identifies the main changes: drawing buttons without appearances and showing comb-field cells. It is specific and directly related to the pull request.
Description check Passed The description is complete and follows the repository template. It explains the behavior change, verification commands and results, tests, safety checks, provenance, security, parity updates, scope l…
Linked Issues check Passed The changes satisfy the coding requirements in #4367. with_field_appearances_cached adds display-copy handling for missing checkbox and radio-button state appearances and missing push-button appeara…
Out of Scope Changes check Passed The changes stay within #4367. The added tests, README and parity updates document and verify the requested display-only button behavior and comb dividers. The crate-visible button_appearance change…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 21 functions across 5 files. (2 skipped: 2…
Full details: Description check

Explanation

The description is complete and follows the repository template. It explains the behavior change, verification commands and results, tests, safety checks, provenance, security, parity updates, scope limitations, and reviewer focus areas.

Full details: Linked Issues check

Explanation

The changes satisfy the coding requirements in #4367. with_field_appearances_cached adds display-copy handling for missing checkbox and radio-button state appearances and missing push-button appearances. button_state_shown draws /MK /BG, /MK /BC, and the on-state ZapfDingbats caption with /DA color and sizing. push_button_shown draws the frame and centered /MK /CA caption. comb_dividers draws the bounded MaxLen - 1 dividers in the border color and width. The tests cover these cases, malformed input, saved-file preservation, and generated comb appearances.

Full details: Out of Scope Changes check

Explanation

The changes stay within #4367. The added tests, README and parity updates document and verify the requested display-only button behavior and comb dividers. The crate-visible button_appearance change supports the existing icon path. The PR leaves combo-box arrow work to #3567 and does not add unrelated product behavior.

Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 21 functions across 5 files. (2 skipped: 2 unsupported.)

Finishing Touches: 💡 1
🧪 Generate unit tests (beta)
  • Create a new PR
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@aiongg
aiongg merged commit 8a0319a into storytold:main Oct 24, 2026
3 of 4 checks passed
echelon added a commit that referenced this pull request Oct 25, 2026
…K caption (ISO 32000-2 Table 224) (#4454)

* M6: with NeedAppearances, check boxes and radio buttons draw their /MK caption (ISO 32000-2 Table 224)

A form that sets /NeedAppearances asks the viewer to construct the
appearances of its widgets (ISO 32000-2 Table 224; deprecated in PDF 2.0).
The display copy already redraws text and choice fields for it, but check
boxes and radio buttons kept the file's own appearance streams, so a box
whose /MK /CA is ZapfDingbats 4 (a check mark) showed the filled square
its /AP carried.

With /NeedAppearances, every check box and radio button that has an /MK
is now drawn in its current state through button_state_shown (merged in
#4368): /MK /BG and /BC, and when on the /MK /CA ZapfDingbats character
(Table 192) in the /DA colour, sized to the box. A widget without /MK
keeps its own appearance, and without /NeedAppearances nothing changes.
A radio button with no /AS now takes its state from /V when /V names its
own on state. Display and print only: the file is saved as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC

* chore(forms): wrap button_state_shown's doc comment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
wugaxp pushed a commit to wugaxp/pdf-forge that referenced this pull request Oct 27, 2026
…K caption (ISO 32000-2 Table 224) (storytold#4454)

* M6: with NeedAppearances, check boxes and radio buttons draw their /MK caption (ISO 32000-2 Table 224)

A form that sets /NeedAppearances asks the viewer to construct the
appearances of its widgets (ISO 32000-2 Table 224; deprecated in PDF 2.0).
The display copy already redraws text and choice fields for it, but check
boxes and radio buttons kept the file's own appearance streams, so a box
whose /MK /CA is ZapfDingbats 4 (a check mark) showed the filled square
its /AP carried.

With /NeedAppearances, every check box and radio button that has an /MK
is now drawn in its current state through button_state_shown (merged in
storytold#4368): /MK /BG and /BC, and when on the /MK /CA ZapfDingbats character
(Table 192) in the /DA colour, sized to the box. A widget without /MK
keeps its own appearance, and without /NeedAppearances nothing changes.
A radio button with no /AS now takes its state from /V when /V names its
own on state. Display and print only: the file is saved as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC

* chore(forms): wrap button_state_shown's doc comment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
(cherry picked from commit 8708f35)
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.

Check boxes, radio buttons and push buttons without an appearance are not drawn, and comb fields show no cells

2 participants