Skip to content

Tagged PDF export: run-level HTML <a> links not nested inside Link structure elements — fails PDF/UA validation #598

Description

@ukufd

Summary

When exporting a tagged PDF (PDF/A-3A with setTagged(true)) containing run-level hyperlinks (HTML <a> tags in markup="html" text fields), the generated PDF fails PDF/UA validation. No Link structure element is created for inline links — the annotation is orphaned, violating ISO 14289-1 (PDF/UA-1) and WCAG 2.1 — 4.1.2 Name, Role, Value.

Element-level links (linkType="Reference") work correctly — Link structure element is created with proper MCID and OBJR. PAC passes for those.

Environment

Component Version
JasperReports 7.0.7
jasperreports-pdf 7.0.7
OpenPDF (transitive) 1.3.43.jaspersoft.2
Java 21

Reproduction project

ukufd/jasperreports-wcag-bugreport — contains templates, pre-generated PDFs, and PAC validation reports.

mvn clean compile exec:java

Then open target/accessibility-output/with_links.pdf in PAC 2024 and run the WCAG check.

Template snippet

<element kind="textField" markup="html" textAdjust="StretchHeight" hTextAlign="Justified"
         x="0" y="0" width="420" height="20">
    <expression><![CDATA["This section contains <a href='https://jasperreports.sourceforge.net'
        style='color:#267CB8'>a link with custom text</a> inside a paragraph."]]></expression>
</element>

Actual structure (JasperReports 7.0.7)

Text (→P)
  └─ MC (MCID=5) → "This section contains " + "a link with custom text" + " inside a paragraph."
                     ^^^ all three text runs in a SINGLE marked content sequence

/Link annotation on page:
  ├─ NO /StructParent
  ├─ NO /Contents
  └─ /A → URI(https://jasperreports.sourceforge.net)
  • ❌ No Link structure element — annotation is orphaned.
  • ❌ No /StructParent → invisible to the structure tree.
  • ❌ No /Contents → PAC warning.
  • ❌ PAC error: "Link" annotation is not nested inside a "Link" structure element.

Expected PDF/UA structure

P
  ├─ MC (MCID=N)   → "This section contains "
  ├─ Link
  │   ├─ MC (MCID=N+1) → "a link with custom text"
  │   └─ OBJR → /Link annotation
  └─ MC (MCID=N+2) → " inside a paragraph."

Root cause

The issue originates in JRPdfExporterTagHelper.startText() and JRPdfExporter.getPhrase():

  1. Single marked content sequence covers entire paragraphstartText() opens one beginMarkedContentSequence() for the whole paragraph. All chunk text rendered by ColumnText.go() lands inside this single MC — there is no mechanism to close the MC before a link, open a new MC under a Link structure element, and resume the paragraph MC after.

  2. No Link structure element for run-level hyperlinksstartText() only checks textElement.getLinkType() (element-level links). HTML <a> tags parsed as JRTextAttribute.HYPERLINK run attributes are not detected — no Link structure element is created for them.

  3. ColumnText dual-buffer architecture prevents inline MC splitting — OpenPDF's ColumnText.go() writes text to a duplicate PdfContentByte buffer and appends it to the main canvas only after all chunks are rendered. MC operations during chunk iteration go to the main canvas, not the text buffer — resulting in MC boundaries that do not wrap the actual text operators.

  4. Link annotation OBJR silently dropped when /K is null or PdfDictionaryClassicChunk.addAnnotationToTag() only handles PdfArray and PdfNumber for the /K entry. If /K is null or a PdfDictionary, the annotation's OBJR is not added to the structure tree.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions