Skip to content

How to tag a hyperlink that is only part of a mixed inline/styled text run as a Link structure element (PDF/UA)? #604

Description

@gvgramazio

What I'm trying to do

Export a tagged PDF (PDF/UA) where a hyperlink covers only part of a sentence, e.g.:

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

where only "adipiscing elit" should be a clickable link, and that link should be a properly nested Link structure element (not just a clickable /Link annotation), otherwise PDF/UA checkers (e.g. PAC) report: "Link annotation is not nested inside a Link structure element."

What works: linking the whole element

When the entire text of an element is the link target, this tags correctly:

<element kind="textField" uuid="f44d8ccb-580c-495c-9ac5-bc3b6bade20a"
         x="0" y="0" width="200" height="15"
         linkType="Reference" linkTarget="Self">
    <expression><![CDATA["lorem-ipsum.example/dolor"]]></expression>
    <hyperlinkReferenceExpression><![CDATA["https://lorem-ipsum.example/dolor"]]></hyperlinkReferenceExpression>
</element>

Inspecting the exported PDF's structure tree confirms the /Link annotation carries a /StructParent, and the corresponding Link tag in the structure tree references it back via OBJR. This matches how JRPrintText#getLinkType() is checked at the whole-element level by the tag-helper logic (JRPdfExporterTagHelper in the 7.0.7 CE implementation, or any PdfTagger SPI implementation since 7.0.8).

What doesn't tag correctly: inline <a> inside styled/mixed text

JasperReports' styled markup also lets you embed a hyperlink as an inline anchor inside a text element's rich content, e.g.:

<element kind="textField" uuid="9416616e-df13-452c-930e-5c53cc3944f4"
         x="0" y="0" width="400" height="15" markup="styled">
    <expression><![CDATA[
        "Lorem ipsum dolor sit amet, consectetur "
        + "<a href=\"https://lorem-ipsum.example/adipiscing\">adipiscing elit</a>."
    ]]></expression>
</element>

This does paint a real, clickable /Link annotation for "adipiscing elit" at export time, but it is never wrapped in a matching Link structure tag. I traced this to the net.sf.jasperreports.pdf.common.PdfTagger SPI itself: startText(JRPrintText) / startText(JRPrintText, String) only ever receive the whole print element and check its getLinkType(); there's no callback for a hyperlink run embedded inside styled text. So this can't be fixed from a custom PdfTagger plugin alone since the gap is in how JRPdfExporter paints inline anchor runs, upstream of the tagger hook.

The only workaround I've found: an invisible overlay hotspot

Render the visible text as a plain styled run that only looks like a link (underline + link color, no real hyperlink), then overlay a second, invisible, transparent element, hand-positioned/sized to match the rendered substring, that carries the real linkType:

<element kind="frame" uuid="a460861e-4047-4453-a4f2-054537353f2d" x="0" y="0" width="400" height="15">
    <!-- Visible text: styled to look like a link, carries no real hyperlink -->
    <element kind="textField" uuid="924ed60a-c6e4-4a80-8e3f-3119389a83dd"
             x="0" y="0" width="400" height="15" markup="styled">
        <expression><![CDATA[
            "Lorem ipsum dolor sit amet, consectetur "
            + "<style isUnderline='true' forecolor='#0000EE'>adipiscing elit</style>."
        ]]></expression>
    </element>
    <!-- Invisible overlay carrying the real link, manually positioned under "adipiscing elit" -->
    <element kind="textField" uuid="851a7d63-a1e3-4dd4-92e0-4d083c603fe1"
             x="238" y="0" width="70" height="15"
             mode="Transparent" forecolor="rgba(255,255,255,0.0)"
             linkType="Reference" linkTarget="Self">
        <expression><![CDATA["adipiscing elit"]]></expression>
        <hyperlinkReferenceExpression><![CDATA["https://lorem-ipsum.example/adipiscing"]]></hyperlinkReferenceExpression>
    </element>
</element>

This does tag correctly (the invisible element is, itself, a whole-element hyperlink), but it has real downsides:

  • Brittle positioning. The overlay's x/width must be hand-measured in pixels against the rendered width of the visible substring. Any change in translated text length, font substitution, or font size breaks the alignment silently.
  • Duplicated text. The same words exist twice in the content stream: once as a plain, non-interactive visible run, once as an invisible interactive run. Depending on how assistive tech handles zero-alpha/transparent text, this risks duplicate or confusing announcements, and shows up oddly in text extraction/copy-paste.
  • Doesn't scale. Every "partial sentence is a link" occurrence needs its own manually tuned overlay; there's no way to parametrize this for dynamic, data-driven, or localized content.

The question

Is there a supported, first-class way to tag a hyperlink that covers only part of a mixed/styled text run as a proper Link structure element I'm missing? If not, would a feature request be in scope here: either extending the PdfTagger SPI with a per-run hyperlink hook, or having the built-in inline-<a>-to-annotation code path set /StructParent/OBJR the same way whole-element hyperlinks already do?

Environment: JasperReports 7.0.8, PDF export via jasperreports-pdf, tagged export enabled (net.sf.jasperreports.export.pdf.tagged=true).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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