Adding FreeText annotation to PDF
Asked Answered
Y

2

2

I am using podofo for doing PDF operations, like adding annotations, signatures etc as per my requirement in my iOS application. I have first tried the only sample for the podofo library available which works great. But the problem with the sample is the annotations added doesn't show in any preview like Google, Adobe Reader etc. Thats a problem.

As per few guideline from Adobe I found that it requires to have Appearance Key for the FreeText annotation to appear. I have tried analyzing raw pdf file in a text editor to see the difference in the PDF which has correct Annotations, with podofo created PDF annotations. I found there are AP N keys with a stream object that is in encoded form for the annotation, which was missing from podofo sample.

Then after searching I found podofo's own sample and tried to use the code, which seems to be doing correctly, but didn't work either, I know I am missing something, but not sure what, and where, please have a look of the code below

+(void)createFreeTextAnnotationOnPage:(NSInteger)pageIndex doc:(PdfMemDocument*)aDoc rect:(CGRect)aRect borderWidth:(double)bWidth title:(NSString*)title content:(NSString*)content bOpen:(Boolean)bOpen color:(UIColor*)color {
    PoDoFo::PdfMemDocument *doc = (PoDoFo::PdfMemDocument *) aDoc;
    PoDoFo::PdfPage* pPage = doc->GetPage(pageIndex);
    if (! pPage) {
        // couldn't get that page
        return;
    }

    PoDoFo::PdfRect rect;
    rect.SetBottom(aRect.origin.y);
    rect.SetLeft(aRect.origin.x);
    rect.SetHeight(aRect.size.height);
    rect.SetWidth(aRect.size.width);


    PoDoFo::PdfString sTitle(reinterpret_cast<const PoDoFo::pdf_utf8*>([title UTF8String]));
    PoDoFo::PdfString sContent(reinterpret_cast<const PoDoFo::pdf_utf8*>([content UTF8String]));

    PoDoFo::PdfFont* pFont = doc->CreateFont( "Helvetica", new PoDoFo::PdfIdentityEncoding( 0, 0xffff, true ) );


    std::ostringstream  oss;
    oss << "BT" << std::endl << "/" <<   pFont->GetIdentifier().GetName()
    << " "  <<   pFont->GetFontSize()
    << " Tf " << std::endl;

    [APDFManager WriteStringToStream:sContent :oss :pFont];
    oss << "Tj ET" << std::endl;

    PoDoFo::PdfDictionary fonts;
    fonts.AddKey(pFont->GetIdentifier().GetName(), pFont->GetObject()->Reference());
    PoDoFo::PdfDictionary resources;
    resources.AddKey( PoDoFo::PdfName("Fonts"), fonts );

    PoDoFo::PdfAnnotation* pAnnotation =
    pPage->CreateAnnotation( PoDoFo::ePdfAnnotation_FreeText, rect );



    pAnnotation->SetTitle( sTitle );
    pAnnotation->SetContents( sContent );
    //pAnnotation->SetAppearanceStream( &xObj );
    pAnnotation->GetObject()->GetDictionary().AddKey( PoDoFo::PdfName("DA"), PoDoFo::PdfString(oss.str()) );
    pAnnotation->GetObject()->GetDictionary().AddKey( PoDoFo::PdfName("DR"), resources );
}

+(void) WriteStringToStream:(const PoDoFo::PdfString & )rsString :(std::ostringstream &)  oss :(PoDoFo::PdfFont*) pFont
{
    PoDoFo::PdfEncoding* pEncoding = new PoDoFo::PdfIdentityEncoding( 0, 0xffff, true );
    PoDoFo::PdfRefCountedBuffer buffer = pEncoding->ConvertToEncoding( rsString, pFont );
    PoDoFo::pdf_long  lLen    = 0;
    char* pBuffer = NULL;

    std::auto_ptr<PoDoFo::PdfFilter> pFilter = PoDoFo::PdfFilterFactory::Create( PoDoFo::ePdfFilter_ASCIIHexDecode );
    pFilter->Encode( buffer.GetBuffer(), buffer.GetSize(), &pBuffer, &lLen );

    oss << "<";
    oss << std::string( pBuffer, lLen );
    oss << ">";
    free( pBuffer );
    delete pEncoding;
}

Any one in SO universe can please tell me what's wrong with above code, and how to add a correct FreeText Annotation so that it appears correctly everywhere.

Many Thanks.

Yestreen answered 13/6, 2014 at 12:19 Comment(4)
Between << " Tf " << std::endl; and oss << "Tj ET" << std::endl; something is missing: there should at least be the operand of TJ, e.g. oss << "[(Hello world)]" (assuming the encoding of pfont is a standard encoding).Manifestative
Can you supply a sample PDF generated by your code? I'm not using PoDoFo myself, but I could look what is missing in the result and causing your problems.Manifestative
@Manifestative Yes please find the pdf here db.tt/6sqgvrul the annotation is visible only with Mac Preview and podofo. But it doesn't show up to Google, Adobe Reader etc. You can find the annotation by name My Annotation.Yestreen
But it doesn't show up to Google, Adobe Reader etc - I just downloaded it and will look at it. But for which version of Adobe Reader does the annotation not show? I can see it on MS Windows both using Adobe Reader XI and Adobe Acrobat 9.5.Manifestative
M
5

The annotation in question looks like this:

19 0 obj
<<
  /Type/Annot
  /Contents(þÿ M Y   A N N O T A T I O N)
  /DA(BT\n/Ft18 12 Tf \n 1 0 0 rg \n<002D003900000021002E002E002F0034002100340029002F002E>Tj ET\n)
  /DR<</Fonts<</Ft18 18 0 R>>>>
  /M(D:20140616141406+05'00')
  /P 4 0 R
  /Rect[ 188.814117 748.970520 467.849731 795.476456]
  /Subtype/FreeText
  /T(þÿ A n n o t a t e P D F)
>>
endobj

Three observations:

  1. It has a Default Appearance but not APpearance streams.
  2. The contents of the Default Appearance are invalid.
  3. The Default Resources are in the wrong object.

Item 1 may cause the appearance not to render in many simple viewers which only show finalized stuff (page content, annotation appearances, ...) but don't create appearances from the Default Appearance. You should, therefore, also supply an appearance stream.

Items 2 and 3 may cause the appearance not to render in more complex viewers which do try to create appearances from the Default Appearance and Default Resources but expect the DA to be correct and the DR correctly located. You should, therefore, correct the DA and move the DR.

In detail...

1 - Default Appearance but not APpearance streams

While according to the specification ISO 32000-1 the DA is required for free text annotation and AP is not, simpler PDF viewers may not have built-in code to create an appearance stream from the default appearance.

This is not completely surprising: While in case of your PDF there is not much to do, applying the default to some content can imply calculating the best size for text to fit into some area and similar tasks. Thus, simple, incomplete viewers tend to not implement this.

2 - Default Appearance contents are invalid

Your DA string contains BT and ET operators. If you look at section 12.7.3.3 Variable Text of ISO 32000-1, though, you'll see that during appearance creation the contents of DA are embedded into a BT .. ET envelope:

The appearance stream includes the following section of marked content, which represents the portion of the stream that draws the text:

/Tx BMC          % Begin marked content with tag Tx 
  q              % Save graphics state 
      … Any required graphics state changes, such as clipping … 
    BT           % Begin text object 
      … Default appearance string ( DA ) … 
      … Text-positioning and text-showing operators to show the variable text … 
    ET           % End text object 
  Q              % Restore graphics state 
EMC              % End marked content 

The default appearance string (DA) contains any graphics state or text state operators needed to establish the graphics state parameters, such as text size and colour, for displaying the field’s variable text. Only operators that are allowed within text objects shall occur in this string

But BT and ET are not allowed inside another BT .. ET text object!

Furthermore you add the text content inside your DA. As you see above, the text drawing operations are added right after your DA contents. Thus, you're in danger of having duplicate texts eventually.

3 - Default Resources dislocation

You have the Default Resources in the annotation dictionary. But the section 12.7.3.3 Variable Text of ISO 32000-1 mentioned above indicates:

The specified font value shall match a resource name in the Font entry of the default resource dictionary (referenced from the DR entry of the interactive form dictionary).

Thus, your DR will be ignored and are expected elsewhere. So your choice of font may at best be ignored

Manifestative answered 16/6, 2014 at 13:54 Comment(1)
Thanks for spending so much time, and explaining.. I am going to look into it asap and let you know.Yestreen
V
0

I am working on the similar things. I tried generating appearance stream manually, but found it's difficult. Actually the podofo sample code you post above works, but it's wrong in the way of adding appearance stream. You can't use SetAppearanceStream which is wrong either.

podofo's PdfPainter can draw text. It generates text stream. It looks like working for PdfPage only, but actually it works for XObject too. It's really a hidden feature!

My code sample:

PdfFont *pFont = ...;

// Add XObject
PdfXObject xObj(borderPdfRect, pPdfMemDocument);

PdfPainter painter;
painter.SetPage(&xObj);
painter.Save(); // Save graphics settings

// Draw text
painter.SetFont(pFont);
painter.GetFont()->SetFontSize(fontSize);
painter.SetColor(self.textColor.color.red, self.textColor.color.green, self.textColor.color.blue);
PdfString pdfStr(reinterpret_cast<const pdf_utf8*>([self.text UTF8String]));
painter.DrawMultiLineText(textPdfRect, pdfStr);
painter.Restore();
painter.FinishPage();

// Add xObj as appearance stream. Don't use SetAppearanceStream
PdfDictionary dict;
dict.AddKey("N", xObj.GetObject()->Reference());
pTextAnno->GetObject()->GetDictionary().AddKey("AP", dict);
Vitriolic answered 16/3, 2016 at 3:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.