添加FreeText注释到PDF

Adding FreeText annotation to PDF

本文关键字:PDF 注释 FreeText 添加      更新时间:2023-10-16

我使用podofo做PDF操作,如添加注释,签名等根据我的要求在我的iOS应用程序。我首先尝试了podofo库可用的唯一示例,它工作得很好。但是这个示例的问题是添加的注释没有在任何预览中显示,比如Google, Adobe Reader等。这是个问题。

根据Adobe的一些指导方针,我发现它需要Appearance Key才能出现FreeText annotation。我尝试在文本编辑器中分析原始pdf文件,以查看具有正确注释的pdf与podofo创建的pdf注释之间的差异。我发现有AP N键,stream对象是编码形式的注释,这是从podofo样本中缺失的。

然后搜索后,我发现了podofo自己的样本,并试图使用代码,这似乎是正确的,但也没有工作,我知道我错过了一些东西,但不确定是什么,在哪里,请看看下面的代码

+(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;
}

任何一个在SO宇宙可以请告诉我上面的代码有什么问题,以及如何添加一个正确的FreeText注释,使其正确地出现在任何地方。

多谢。

注释如下:

19 0 obj
<<
  /Type/Annot
  /Contents(þÿ M Y   A N N O T A T I O N)
  /DA(BTn/Ft18 12 Tf n 1 0 0 rg n<002D003900000021002E002E002F0034002100340029002F002E>Tj ETn)
  /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

三个观察:

  1. 它有默认外观但没有外观流
  2. Default Appearance内容无效。
  3. Default Resources在错误的对象中。

Item 1可能会导致外观在许多简单的查看器中不呈现,这些查看器只显示最终的内容(页面内容,注释外观等),但不会从Default appearance 创建外观。因此,您还应该提供一个外观流。

项目2和3可能会导致外观在更复杂的查看器中不呈现,这些查看器确实尝试从默认外观默认资源创建外观,但期望DA正确且DR正确定位。因此,您应该更正DA并移动DR

详细…

1 - 默认外观但不外观流

虽然根据ISO 32000-1规范,DA是自由文本注释所必需的,而AP则不是,但简单的PDF查看器可能没有内置代码来从默认外观创建外观流。

这并不完全令人惊讶:虽然在PDF的情况下没有太多要做的事情,但对某些内容应用默认值可能意味着计算适合某些区域的文本的最佳大小和类似的任务。因此,简单的、不完整的查看器往往不会实现这一点。

2 - Default Appearance内容无效

您的DA字符串包含BTET操作符。但是,如果您查看ISO 32000-1的12.7.3.3 可变文本部分,您将看到在创建外观时DA的内容被嵌入到BT中。等信封:

外观流包括以下标记内容的部分,它表示绘制文本的流的一部分:

/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 

默认的外观字符串(DA)包含建立图形状态参数(如文本大小和颜色)所需的任何图形状态或文本状态操作符,用于显示字段的可变文本。只有在文本对象中允许的操作符才能出现在这个字符串

但是BTET不允许在另一个BT内。ET文本对象!

此外,在DA中添加文本内容。正如上面所看到的,文本绘图操作被添加在 DA内容之后。因此,您最终将面临有重复文本的危险。

3 - Default Resources错位

在注释字典中有Default Resources。但是上面提到的ISO 32000-1的12.7.3.3 可变文本部分表明:

指定的font值应匹配默认资源字典 font条目中的资源名称(从交互式表单字典的DR条目引用)。

因此,您的DR将被忽略,并期望在其他地方使用。所以你选择的字体最好可以忽略

我也在做类似的事情。我尝试手动生成外观流,但发现很难。实际上,你上面发布的podofo示例代码是有效的,但它在添加外观流的方式上是错误的。你不能使用setappearance ream,这也是错误的。

podofo的PdfPainter可以绘制文本。它生成文本流。它看起来只适用于PdfPage,但实际上它也适用于XObject。这真的是一个隐藏的功能!

我的代码示例:
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);