Skip to content

Conversation

@PetoMPP
Copy link

@PetoMPP PetoMPP commented Jun 17, 2025

This change updates PdfSharp’s outline and annotation parsing to preserve the /A (Action) dictionary even when a /Dest is also present. Previously, PdfSharp would discard the /A entry when replacing it with a /Dest, which resulted in the loss of important behaviors such as JavaScript execution or chained /Next actions.

In many PDFs — especially those created with Adobe Acrobat or CAD tools — outlines and annotations include compound actions like:

/A <<
  /S /GoTo
  /D [...]
  /Next << /S /JavaScript /JS (...) >>
>>

Discarding the action breaks interactive features such as:

  • Highlighting fields (e.g. flashing red rectangles)

  • Dynamic form behaviors

  • Any action-driven JavaScript logic

Benefits:

  • Preserves full PDF behavior and interactivity

  • Aligns with the PDF spec, which allows both /A and /Dest

  • Enables advanced use cases like viewer development, script analysis, or validation

This change is backward-compatible and improves PdfSharp’s ability to work with professionally authored interactive PDFs.

PetoMPP added 2 commits June 17, 2025 08:58
This change updates PdfSharp’s outline and annotation parsing to preserve the /A (Action) dictionary even when a /Dest is also present. Previously, PdfSharp would discard the /A entry when replacing it with a /Dest, which resulted in the loss of important behaviors such as JavaScript execution or chained /Next actions.

In many PDFs — especially those created with Adobe Acrobat or CAD tools — outlines and annotations include compound actions like:

```pdf
Kopiuj
Edytuj
/A <<
  /S /GoTo
  /D [...]
  /Next << /S /JavaScript /JS (...) >>
>>
```
Discarding the action breaks interactive features such as:

- Highlighting fields (e.g. flashing red rectangles)

- Dynamic form behaviors

- Any action-driven JavaScript logic

Benefits:
- Preserves full PDF behavior and interactivity

- Aligns with the PDF spec, which allows both /A and /Dest

- Enables advanced use cases like viewer development, script analysis, or validation

This change is backward-compatible and improves PdfSharp’s ability to work with professionally authored interactive PDFs.
@ThomasHoevel
Copy link
Member

Hi!

Thanks for the information.

Can you provide PDF files for testing, please?

The PDF spec 1.7 writes about the "/A" parameter: "Optional; PDF 1.1; not permitted if a Dest entry is present".
That explains why PDFsharp so far removes the "/A" parameter.

But PDF files in the wild do not always care about the specs.

@PetoMPP
Copy link
Author

PetoMPP commented Jun 26, 2025

Hello, I can't really provide this PDF as it contains copyrighted data, but I can show the exact thing from it that I mention in this PR:

6966 0 obj
<</Parent 6965 0 R/A 6967 0 R/Title(REDACTED)/Next 6987 0 R/First 6969 0 R/Last 6969 0 R/Count -1>>
endobj
6967 0 obj
<</Type/Action/S/GoTo/Next 6968 0 R/D[222 0 R/Fit]>>
endobj
6968 0 obj
<</Type/Action/S/JavaScript/JS(highlight\(10, [207.708, 484.866, 344.479, 778.263]\);\n)>>
endobj

As you can see there is both /Next and /D in this /Action, and current implementation converts this /Action into /Dest element, and it is not possible to retrieve the /Next value after the conversion. In this case I need to read the /JS value of where is this Outline pointing to, as the parent is only pointing to /Fit.

@PetoMPP
Copy link
Author

PetoMPP commented Jun 26, 2025

I can also provide the code snippet of how I access the data, so it is clearer what is the aim of the changes:

if (!outline.Elements.TryGetValue("/A", out PdfItem actionRef) ||
  pdf.PdfActions.FirstOrDefault(a => a.Reference == actionRef) is not { } action ||
  (action. JavaScript ?? action.Next?.JavaScript) is not { } js)
{
  preciseBounds = false;
  return null;
}

The pdf.PdfActions is just a list of all PdfAction in the PdfDocument created as follows:

document.Internals
  .GetAllObjects()
  .OfType<PdfDictionary>()
  .Where(d => d.Elements.TryGetString("/Type", out string type) && type == "/Action")
  .Select(d => new PdfAction(d))
  .ToArray()

And PdfAction itself is just a small wrapper around PdfDictionary.

public class PdfAction : PdfDictionary
{
  public string Type
  {
    get { return Elements.TryGetString("/S", out string value) ? value : null; }
  }

  public PdfPage DestinationPage
  {
    get
    {
      return Elements.TryGetValue("/D", out PdfItem item) &&
        item is PdfArray destArr &&
        destArr.OfType<PdfReference>().Select(r => r.Value).FirstOrDefault() is PdfPage page
          ? page
          : null;
    }
  }

  public string JavaScript
  {
    get { return Elements.TryGetString("/JS", out string value) ? value : null; }
  }

  public PdfAction Next
  {
    get
    {
      return Elements.TryGetValue("/Next", out PdfItem next) && next is PdfReference nextRef
        ? nextRef.Value as PdfAction
        : null;
    }
  }

  public PdfAction(PdfDictionary dict) : base(dict)
  {
  }
}

@StLange
Copy link
Member

StLange commented Jul 23, 2025

Hi!
The upcoming version 6.2.1 fixes this, no elements are removed anymore. As Thomas already wrote, I originally removed the action because of the PDF 1.7 specs. That was wrong and the PDF 2.0 specs was updated.

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.

3 participants