Merging FDF annotations with PDF

Q:
I'm trying to import annotations from a FDF file to a PDF file, but
couldn't find a way (only the function "FDFmerge" for form fields).
Does the PDFNet SDK have the functionality to import annotations from
FDF to PDF? Thanks in advance for your answer
---
A:
I found some sample code that may be of help when merging FDF
annotations with PDF.

To find the relevant article search for "Merging annotations with PDF
on the server" in PDFNet knowledge base:
    http://groups.google.com/group/pdfnet-sdk

In the above example all annotations are placed on the first page,
however you can use 'Page' entry in the annotation dictionary to find
the destination page number. For example,

Obj annot_dict = annots.GetAt(i);
DictIterator itr = annot_dict.Find("Page"); if (itr !=
annot_dict.DictEnd()) {
  // Note: in FDF, pages are numbered starting from 0; PDFNet numbers
pages starting from 1.
  int page_num = (int) itr.Value().GetNumber() + 1;
  ...
}

For more information on how to use SDF/Cos API please take a look at
following links:
  http://www.pdftron.com/net/usermanual.html#sdf_obj
  http://www.pdftron.com/net/samplecode/SDFTest.cs

After the second import there must not exist double annotations in the PDF file.

Since you have full control over the merging process you could
determine if a given annotation is being modified or is it newly
created by checking if the annotation bounding boxes & types match or
by maintaining custom entries/properties.

Q:
Thank you very much for the sample code. While checking it out,
something catched my eye. In my test FDF file there is an annotation
with a reply. After importing the annotations, the reply isn't a reply
anymore, but an single annotation by itself. Is it a bug? Is it
possible to import replies correctly?
---
A:

Is it a bug? Is it possible to import replies correctly?

Depends on what are your requirements. If you need to import replies
properly, you need to check IRT entry in the FDF annotation dictionary
(btw. CosEdit utility is useful for this type of inspections). If the
annotation contain IRT entry you can use it to look up after another
annotation with the ID name given as the value of IRT entry. Then you
can replace the IRT entry in the imported annotation with a direct
link.

Q:

If you need to import replies correctly you need to check IRT entry in
the FDF annotation dictionary

I checked the IRT entry in the FDF file and in the PDF file before
I've sent you the last mail. Both had the correct entry.
---
A:
If you look at a.pdf created using Acrobat (e.g. using CosEdit -
www.pdftron.com/cosedit) you will notice that IRT entry is not a
'name' object but is indirect 'link' to the reply annotation.

So after importing annotations you need to update/overwrite IRT entry
with a direct link:
  annot1.GetSDFObj().Put("IRT"), annot2.GetSDFObj());

The only missing part is to 'find' the imported annotation (i.e.
annot2) with the value of "NM" entry matching the value of previous
IRT entry.

So the value of IRT entry (after annotation import) is not correct
because it needs to be a dictionary link instead of a name identifier
(which is a convention used in FDF to decouple annotations from each
other).

Q:

I think, I can solve the problem with the IRT entry, but I still have
another problem with multiple annotations. I tried a lot to get it to
work, but there are still double annots in my PDF file at the end.

First I did it like shown in the example (http://groups.google.com/
group/pdfnet-sdk/browse_thread/thread/
afc3ce3a1da7cfa5/0ff4691af0353538?lnk=gst&q=annotations
+server&rnum=1#0ff4691af0353538), but I got double annotations,
because all FDF annotations are imported at the beginning without a
check.

At last I tried to find new FDF annotations, that do not exist in the
PDF file, before I call the function pdftron::SDF::Doc::ImportObj. It
seems to work, but when I call pdftron::PDF::Page::AnnotPushBack after
the importObj function, I have multiple annots in my PDF again. The
class is along the following lines:

//-----
FDFDoc fDoc = new FDFDoc(fdfFile);
DictIterator fdfannotsIter = objFDF.Find("Annots");
if(fdfannotsIter == objFDF.DictEnd()) // if no /Annots entry in FDF
object
  return;

Obj objFdfannots = fdfannotsIter.Value(); // get all FDF annots
for(int i=0; i<objFdfannots.Size(); i++) {
  Obj objFdfannot = objFdfannots.GetAt(i); // a single FDF annotation
as Obj
  Annot fdfAnnot = new Annot(objFdfannot); // a single FDF annotation
as Annot
  if(!fdfAnnot.GetType().ToString().Equals("e_Popup")) { // popup
annots won't be imported (they are imported right after import of
other annots like text etc.)
   // get page number for the FDF annotation
   DictIterator fdfpageIter = objFdfannot.Find("Page");
   if(fdfpageIter != objFdfannot.DictEnd()) {
      int page_num = (int) fdfpageIter.Value().GetNumber() + 1;
      // get page object in PDF
      PageIterator pdfpageIter = pDoc.PageFind(page_num);
      if(pdfpageIter == pDoc.PageEnd()) { // if there is no page with
that page number
         return;
      }
      Page page = pdfpageIter.Current();

      //if there exists an annot with the same NM-entry on the page
already
      if(!existsInPage(page, fdfAnnot)) {
         Obj importedAnnot = pDoc.GetSDFDoc().ImportObj(objFdfannot,
true);
         Annot newAnnot = new Annot(importedAnnot);
         page.AnnotPushBack(newAnnot);

      // if annot has an IRT entry, change it to correct entry
      ...

      // import popup for the annotation
      ...
} }
---
A:

The problem lies in multiple object imports. In your sample FDF, the
annotation has indirect link to the associated 'Popup' annotation and
the 'popup' annotation has a link to the parent annotation (i.e.
'Parent' entry in popup annotation dictionary). When you use
ImportObj() it will also import all referenced object form a given
object (so importing an annotation will also import its popup
annotation; and importing a popup annotation will import associated
parent annotation).

As a workaround, you can import all annotations at once. For example:

Obj imported_annots = pdfdoc.GetSDFDoc().ImportObj(objFdfannots,
true);

Where 'objFdfannots' corresponds to 'Annots' array in FDF document.

After this you can iterate over imported_annots similar to your sample
code, but you would not call ImportObjs() anymore.

In case you would like to import only some annotations from
'objFdfannots' array, it is not a problem. You would still import all
annotations as described above, however since not all annotations will
not be associated with PDF page (using AnnotPushBack()) they will be
automatically removed during pdfdoc.Save() (if you specify 'remove
unused object' or 'linearization' flag).

Thanks again for your help. I did not know, that objects are
automatically imported, when they are referenced by other objects.
That was my problem.

I already had another solution by using the function
pdftron::SDF::Doc::ImportObj with "false" as the deep_copy flag. After
that I set the indirect links (popup, parent) with the correct values
again. It worked, but it needed more source code. But now I did it
like you said:
- importing all annotations at the beginning
- calling the AnnotsPushBack method only for new annots and for their
related popups It works fine and the source code is slimmer.

There is something I want to let you know. I tried a lot of PDF libs
in the last weeks for the project of my dissertation. At first, I
found the PDFNet SDK very difficult and unclear compared to other
libs. But now I really like it. It seems to be the most powerful lib
and almost the only one for annotation import).