Auto-tag Apply tags headings by size and weight (#7062) (#7069)

* feat(a11y): auto-tag Apply tags headings by size and weight

Apply now chooses H1-H6 or P per text object instead of P for all.
The body style is the half-point size showing the most characters (bold
when most of those are bold). A short object (at most 3 lines, 200
characters, with a letter) is a heading when it is 1.15x the body, or
bold at 0.9x the body over a regular body in at most 2 lines. Heading
styles rank across the document, larger and bolder first, capped at H6,
and a level never skips past the one before it so heading-nesting
passes.

Size is Tf x Tm x CTM with q/Q tracked; bold is Metrics.bold. Text in
render mode 3 (an OCR layer) is not counted. Font metrics are measured
once per font object per document, at most 256 fonts.
AutoTagResult gains a headings count. Parity, gaps, roadmap and the
apply tool's note say headings are tagged.

Fixes #7062.

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

* fix(a11y): rank heading styles by map and skip clip-only text

Look up each heading style's rank in a map built once after sorting,
so ranking is linear in the number of text objects. Text in render
mode 7 (clip only) paints nothing, so it is excluded from heading
evidence like mode 3.

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

---------

Co-authored-by: KenM76 <KenM76>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-11-12 17:58:31 +01:00
1 parent 490287fc9d
commit 120c62fb82
9 files changed
+472 -26

No files matched your search

+2 -2
View File
@@ -9158,8 +9158,8 @@ milestone = "M12"
status = "partial"
commands = ["a11y.autotag"]
tools = ["accessibility_autotag_preview", "accessibility_autotag_apply"]
evidence = ["crates/automation/tests/it/automation.rs::autotag_preview_proposes_text_without_changing_the_pdf", "crates/automation/tests/it/automation.rs::autotag_apply_writes_reviewable_tags_and_survives_save_reopen", "crates/ui-egui/tests/it/accessibility.rs::automatic_text_tagging_opens_reviewable_tags"]
notes = "Auto-tag writes real marked content, a Document structure element, P tags and a parent tree for text-only PDFs, with undo and Tags-panel review. It refuses mixed content, forms, annotations and existing tags. The preview separately proposes headings. Images, tables, lists, links, fields and headings are not classified or tagged by Apply."
evidence = ["crates/automation/tests/it/automation.rs::autotag_preview_proposes_text_without_changing_the_pdf", "crates/automation/tests/it/automation.rs::autotag_apply_writes_reviewable_tags_and_survives_save_reopen", "crates/automation/tests/it/automation.rs::autotag_apply_tags_headings_by_size_across_pages", "crates/a11y/src/autotag_roles.rs::levels_rank_styles_largest_first_and_never_skip_a_level", "crates/ui-egui/tests/it/accessibility.rs::automatic_text_tagging_opens_reviewable_tags"]
notes = "Auto-tag writes real marked content, a Document structure element, H1-H6 and P tags and a parent tree for text-only PDFs, with undo and Tags-panel review. A text object is a heading when it is short and larger or bolder than the body text; levels rank those styles across the document and never skip a level. It refuses mixed content, forms, annotations and existing tags. The preview still proposes headings by its own single-line rule, so it can differ from Apply. Running headers and footers can still become headings (#7065); invisible text (render mode 3) is ignored. Images, tables, lists, links and fields are not classified or tagged by Apply."
[[feature]]
id = "a11y.autotag-fields"