diff --git a/api.php b/api.php
index 3dee428..b0c9353 100644
--- a/api.php
+++ b/api.php
@@ -1372,6 +1372,7 @@ function handleSaveAdjustedResult() {
$check_to_cp = [
'Color Contrast' => ['04'],
'Image Accessibility' => ['13'],
+ 'Heading Structure' => ['14'],
];
$cp_to_check = [];
foreach ($check_to_cp as $checkName => $cpIds) {
diff --git a/enterprise_pdf_checker.py b/enterprise_pdf_checker.py
index 555bcc9..3d1684a 100644
--- a/enterprise_pdf_checker.py
+++ b/enterprise_pdf_checker.py
@@ -1311,22 +1311,41 @@ Respond in JSON format:
return
struct_tree = catalog["/StructTreeRoot"]
- headings = []
+ if hasattr(struct_tree, 'get_object'):
+ struct_tree = struct_tree.get_object()
- def walk_tree(element):
+ # Load RoleMap so custom tag names (e.g. /Heading1) resolve to standard ones (/H1)
+ role_map = {}
+ if "/RoleMap" in struct_tree:
+ rm = struct_tree["/RoleMap"]
+ if hasattr(rm, 'get_object'):
+ rm = rm.get_object()
+ try:
+ for key, value in rm.items():
+ role_map[str(key)] = str(value)
+ except (AttributeError, TypeError):
+ pass
+
+ headings = []
+ HEADING_TAGS = {"/H1", "/H2", "/H3", "/H4", "/H5", "/H6"}
+
+ def walk_tree(element, depth=0):
+ if depth > 100:
+ return
try:
if hasattr(element, 'get_object'):
element = element.get_object()
if isinstance(element, dict):
tag = str(element.get("/S", ""))
- if tag in ["/H1", "/H2", "/H3", "/H4", "/H5", "/H6"]:
- headings.append(int(tag[2]))
+ mapped_tag = role_map.get(tag, tag)
+ if mapped_tag in HEADING_TAGS:
+ headings.append(int(mapped_tag[2]))
kids = element.get("/K", [])
if isinstance(kids, list):
for kid in kids:
- walk_tree(kid)
+ walk_tree(kid, depth + 1)
elif kids:
- walk_tree(kids)
+ walk_tree(kids, depth + 1)
except (AttributeError, TypeError, KeyError):
pass
diff --git a/js/results.js b/js/results.js
index 65ffce8..b165208 100644
--- a/js/results.js
+++ b/js/results.js
@@ -446,7 +446,7 @@ function displayScoreBreakdown(breakdown) {
}
// Maps H-type Matterhorn checkpoint IDs to the Score Breakdown check names that drive them
-const CP_TO_CHECK = { '04': 'Color Contrast', '13': 'Image Accessibility' };
+const CP_TO_CHECK = { '04': 'Color Contrast', '13': 'Image Accessibility', '14': 'Heading Structure' };
function displayMatterhorn(summary) {
const card = document.getElementById('matterhornCard');
@@ -459,9 +459,9 @@ function displayMatterhorn(summary) {
const cpMap = {};
summary.checkpoints.forEach(cp => { cpMap[cp.id] = cp; });
- // Compute effective status: H-type FAIL → MANUAL_PASS if linked check is overridden
+ // Compute effective status: FAIL → MANUAL_PASS if linked check is overridden
function effectiveStatus(cp) {
- if (cp.how === 'H' && cp.status === 'FAIL') {
+ if (cp.status === 'FAIL') {
const linked = CP_TO_CHECK[cp.id];
if (linked && overriddenChecks.has(linked)) return 'MANUAL_PASS';
}
@@ -503,7 +503,7 @@ function displayMatterhorn(summary) {
`;
} else if (effStatus === 'PASS') {
statusHtml = `✓ PASS`;
- } else if (effStatus === 'FAIL' && cp.how === 'H' && CP_TO_CHECK[cp.id]) {
+ } else if (effStatus === 'FAIL' && CP_TO_CHECK[cp.id]) {
const linked = CP_TO_CHECK[cp.id];
statusHtml = `✗ FAIL
`;