Product: PDFTron.NET.x64
Product Version: 11.12.0
Please give a brief summary of your issue:
Our application allows multiple users to add comments to the same PDF.
-
The PDF is stored in a central location.
Each user’s comments are stored in a separate XFDF file. -
When a user saves their work:
- We extract the XFDF from the current PDF using FDFExtract(e_both) and SaveAsXFDF().
We merge the extracted XFDF with the user’s existing XFDF file.
During the merge, we keep user-created comments, which are identified by the title attribute (the author’s name).
We save the updated XFDF file.
- We extract the XFDF from the current PDF using FDFExtract(e_both) and SaveAsXFDF().
-
When a user downloads the PDF with all comments:
- We remove all comment annotations from the PDF.
We apply the merged XFDF to the PDF using FDFUpdate().
- We remove all comment annotations from the PDF.
User-created comments from WebViewer include a title attribute that identifies the author. PDF annotations such as e_Link (hyperlinks) do not have a title attribute.
Problem
After calling FDFUpdate(), all e_Link (hyperlink) annotations are missing from the resulting PDF.
Root cause — two compounding factors:
- Factor 1 — XFDF merge strips links: The XFDF merge step retains only annotations that have a non-empty title attribute (user comments) and discards everything else. e_Link annotations have no title, so they are excluded from the merged XFDF.
- Factor 2 — FDFUpdate() replaces the page /Annots array entirely: When FDFUpdate() is called, it does not merge the XFDF into the existing page annotations — it replaces each page’s /Annots array with only what is present in the XFDF. Any annotation not represented in the XFDF (including e_Link) is silently discarded.
The combination means that even though the original PDF had hyperlinks, and the deletion step in our workflow explicitly skips e_Link annotations, FDFUpdate() wipes them out regardless by replacing the array.
Confirmation Request
We would like to confirm:
- Is it expected/documented behaviour that FDFUpdate() replaces the page /Annots array entirely rather than performing an additive merge?
- Does FDFUpdate() leave the underlying SDF objects of pre-existing annotations intact in the document’s cross-reference table (i.e., it replaces the array pointer but does not free the objects themselves)?
Suggested Fix 1 — Snapshot and Restore
We snapshot all e_Link annotations per page before calling FDFUpdate(), then push them back using Page.AnnotPushBack() after the update. This relies on the assumption that FDFUpdate() does not free the underlying SDF objects, meaning the snapshotted Annot wrappers remain valid.
// 1. Snapshot e_Link annotations before FDFUpdate
var linksByPage = new Dictionary<int, List>();
using (var pageIter = pdfDoc.GetPageIterator())
{
while (pageIter.HasNext())
{
var page = pageIter.Current();
var links = new List();
for (int i = 0; i < page.GetNumAnnots(); i++)
{
var annot = page.GetAnnot(i);
if (annot.GetType() == Annot.Type.e_Link)
links.Add(annot);
}
if (links.Count > 0)
linksByPage[page.GetIndex()] = links;
pageIter.Next();
}
}
// 2. Apply XFDF
using (var fdfDoc = new FDFDoc(FDFDoc.CreateFromXFDF(xfdfString)))
pdfDoc.FDFUpdate(fdfDoc);
// 3. Restore snapshotted links
using (var pageIter = pdfDoc.GetPageIterator())
{
while (pageIter.HasNext())
{
var page = pageIter.Current();
if (linksByPage.TryGetValue(page.GetIndex(), out var links))
{
foreach (var link in links)
page.AnnotPushBack(link); // Re-adds the existing SDF object reference
}
pageIter.Next();
}
}
This works empirically — links are restored correctly in our testing. However, we are relying on undocumented SDK internals (SDF object lifetime after FDFUpdate()). We would like confirmation that this approach is safe and that the underlying SDF objects are guaranteed to remain valid after FDFUpdate() replaces the /Annots array.
Suggested Fix 2 — Preserve Links in the XFDF
An alternative is to retain e_Link annotations within the XFDF during the merge step, so they are naturally carried through FDFUpdate() without any post-update restoration.
Since FDFExtract(e_both) does include link annotations in the XFDF section, the XFDF at the point of extraction looks like:
<xfdf>
<annots>
<link name="link-id-1" page="0" rect="...">
<dest><named name="..."/></dest>
</link>
<text name="comment-id-1" title="UserA" page="0" rect="...">
Some comment
</text>
</annots>
</xfdf>
The XFDF merge step would be changed to retain elements that:
• Have a non-empty title attribute (user comments — existing behaviour), OR
• Have no title attribute AND no inreplyto or State attributes (structural annotations such as links)
With links preserved in the XFDF, FDFUpdate() would re-apply them naturally alongside user comments — no snapshot/restore needed.
Questions on this approach:
- Does FDFUpdate() correctly handle elements in the XFDF section, including their and sub-elements?
- Is there a risk of link annotations being duplicated if they already exist in the PDF when FDFUpdate() is called?
- Are there any known edge cases where link annotations round-trip through FDFExtract → SaveAsXFDF → FDFUpdate incorrectly (e.g., named destinations, URI actions, cross-document links)?
We would appreciate guidance on which approach is safer and better aligned with the intended SDK usage.
Regards,
Zaid