使用PoDoFo lib从PDF运算符中的数组TJ中提取文本

我正在尝试使用PoDoFo库从PDF文件中提取文本,它正在为Tj运算符工作,并且无法为(数组) TJ运算符执行此操作。 我在这里找到了这段代码(我的小修改):

const char* pszToken = NULL; PdfVariant var; EPdfContentsType eType; PdfContentsTokenizer tokenizer( pPage ); double dCurPosX = 0.0; double dCurPosY = 0.0; double dCurFontSize = 0.0; bool bTextBlock = false; PdfFont* pCurFont = NULL; std::stack stack; while( tokenizer.ReadNext( eType, pszToken, var ) ) { if( eType == ePdfContentsType_Keyword ) { // support 'l' and 'm' tokens _RPT1(_CRT_WARN, " %s\n", pszToken); if( strcmp( pszToken, "l" ) == 0 || strcmp( pszToken, "m" ) == 0 ) { dCurPosX = stack.top().GetReal(); stack.pop(); dCurPosY = stack.top().GetReal(); stack.pop(); } else if (strcmp(pszToken, "Td") == 0) { dCurPosY = stack.top().GetReal(); stack.pop(); dCurPosX = stack.top().GetReal(); stack.pop(); } else if (strcmp(pszToken, "Tm") == 0) { dCurPosY = stack.top().GetReal(); stack.pop(); dCurPosX = stack.top().GetReal(); stack.pop(); } else if( strcmp( pszToken, "BT" ) == 0 ) { bTextBlock = true; // BT does not reset font // dCurFontSize = 0.0; // pCurFont = NULL; } else if( strcmp( pszToken, "ET" ) == 0 ) { if( !bTextBlock ) fprintf( stderr, "WARNING: Found ET without BT!\n" ); } if( bTextBlock ) { if( strcmp( pszToken, "Tf" ) == 0 ) { dCurFontSize = stack.top().GetReal(); stack.pop(); PdfName fontName = stack.top().GetName(); PdfObject* pFont = pPage->GetFromResources( PdfName("Font"), fontName ); if( !pFont ) { PODOFO_RAISE_ERROR_INFO( ePdfError_InvalidHandle, "Cannot create font!" ); } pCurFont = pDocument->GetFont( pFont ); if( !pCurFont ) { fprintf( stderr, "WARNING: Unable to create font for object %i %i R\n", pFont->Reference().ObjectNumber(), pFont->Reference().GenerationNumber() ); } } else if( strcmp( pszToken, "Tj" ) == 0 || strcmp( pszToken, "'" ) == 0 ) { AddTextElement( dCurPosX, dCurPosY, pCurFont, stack.top().GetString() ); stack.pop(); } else if( strcmp( pszToken, "\"" ) == 0 ) { AddTextElement( dCurPosX, dCurPosY, pCurFont, stack.top().GetString() ); stack.pop(); stack.pop(); // remove char spacing from stack stack.pop(); // remove word spacing from stack } else if( strcmp( pszToken, "TJ" ) == 0 ) { PdfArray array = stack.top().GetArray(); stack.pop(); for( int i=0; i<static_cast(array.GetSize()); i++ ) { _RPT1(_CRT_WARN, " variant: %s", array[i].GetDataTypeString()); if(array[i].IsHexString()) { if(!pCurFont) { _RPT1(_CRT_WARN, " : Could not Get font!!%d\n", i); } else { if(!pCurFont->GetEncoding()) { _RPT1(_CRT_WARN, ": could not get encoding\n",0); } else { PdfString s = array[i].GetString(); _RPT1(_CRT_WARN, " : valid :%s ", s.IsValid()?"yes":"not"); _RPT1(_CRT_WARN, " ;hex :%s ", s.IsHex()?"yes":"not"); _RPT1(_CRT_WARN, " ;unicode: %s ", s.IsUnicode()?"yes":"not"); PdfString unicode = pCurFont->GetEncoding()->ConvertToUnicode(s,pCurFont); const char* szText = unicode.GetStringUtf8().c_str(); _RPT1(_CRT_WARN, " : %s\n", strlen(szText)> 0? szText: "nothing"); } } } else if(array[i].IsNumber()) { _RPT1(_CRT_WARN, " : %d\n", array[i].GetNumber()); } if( array[i].IsString() )//|| array[i].IsHexString()) AddTextElement( dCurPosX, dCurPosY, pCurFont, array[i].GetString() ); } } } } else if ( eType == ePdfContentsType_Variant ) { stack.push( var ); _RPT1(_CRT_WARN, " variant: %s\n", var.GetDataTypeString()); } else { // Impossible; type must be keyword or variant PODOFO_RAISE_ERROR( ePdfError_InternalLogic ); } } 

并为代码我得到这个输出:

  BT variant: Name variant: Real Tf variant: Number variant: Number variant: Number rg variant: Real variant: Number variant: Number variant: Number variant: Real variant: Real Tm variant: Array TJ variant: HexString : valid :yes ;hex :yes ;unicode: not : nothing variant: Number : -7 variant: HexString : valid :yes ;hex :yes ;unicode: not : nothing variant: Number : -15 variant: HexString : valid :yes ;hex :yes ;unicode: not : nothing variant: Number : -15 variant: HexString : valid :yes ;hex :yes ;unicode: not : nothing variant: Number : -11 variant: HexString : valid :yes ;hex :yes ;unicode: not : nothing variant: Number : -11 variant: HexString : valid :yes ;hex :yes ;unicode: not : nothing variant: Number : -19 variant: HexString : valid :yes ;hex :yes ;unicode: not : nothing variant: Number : -11 variant: HexString : valid :yes ;hex :yes ;unicode: not : nothing variant: Number : -15 variant: HexString : valid :yes ;hex :yes ;unicode: not : nothing variant: Number : -11 variant: HexString : valid :yes ;hex :yes ;unicode: not : nothing ET 

PDF流对象会喜欢这个(对不起但我不允许给你pdf文件):

  q Q q Q q q q 1 0 0 1 37.68 785.28 cm 91.92 0 0 31.44 0 0 cm /Img1 Do Q Q q q 1 0 0 1 431.28 780.24 cm 42.72 0 0 7.2 0 0 cm /Img2 Do Q Q q BT /F1 8.88 Tf 0 0 0 rg 0.9998 0 0 1 377.28 704.4 Tm [-7-15-15-11-11-19-11-15-11] TJ ET Q q 1 0 0 1 0 0 cm 0.4799 w 0 0 0 RG 377.28 703.44 m 415.2 703.44 l S Q q BT /F1 8.16 Tf 0 0 0 rg 0.9998 0 0 1 377.28 687.36 Tm [9-13-161-1313-13-9-6-13-13-13-61-6-13-9-13-91-9-9-9-9-13-61-9-13] TJ ET 

PDF文件应该在这里或这里找到

1.原始问题的答案,中央代码部分是这样的:

 else if( strcmp( pszToken, "TJ" ) == 0 ) { PdfArray array = stack.top().GetArray(); stack.pop(); for( int i=0; i(array.GetSize()); i++ ) { if( array[i].IsString() ) AddTextElement( dCurPosX, dCurPosY, pCurFont, array[i].GetString() ); } } } 

问题是:

我注意到array[i].IsString()永远不会成为现实。 这是从TJ运算符获取文本的正确方法吗?

简短的回答:

PoDoFo PdfVariants中的hex字符串由IsHexString()而不是IsString()识别。 因此,您必须测试两种字符串风格:

 if( array[i].IsString() || array[i].IsHexString() ) 

答案很长:

PDF中有两种基本的字符串:

字符串对象应使用以下两种方式之一编写:

  • 作为括在括号中的文字字符序列()(使用LEFT PARENTHESIS(28h)和RIGHT PARENThESIS(29h)); 见7.3.4.2,“文字字符串”。

  • 作为尖括号<>中包含的十六进制数据(使用LESS-THAN SIGN(3Ch)和GREATER-THAN SIGN(3Eh)); 见7.3.4.3,“十六进制字符串”。

( ISO 32000-1中的 7.3.4节)

PoDoFo模型都使用PdfString类,它在解析的上下文中经常被包装在PdfVariant ,甚至更具体地包含在PdfObject

但是,在确定其中包含的对象的类型时, PdfVariant区分文字字符串和hex字符串:

 /** \returns true if this variant is a string (ie GetDataType() == ePdfDataType_String) */ inline bool IsString() const { return GetDataType() == ePdfDataType_String; } /** \returns true if this variant is a hex-string (ie GetDataType() == ePdfDataType_HexString) */ inline bool IsHexString() const { return GetDataType() == ePdfDataType_HexString; } 

( PdfVariant.h )

PdfString的类型在包装时确定:

 PdfVariant::PdfVariant( const PdfString & rsString ) { Init(); Clear(); m_eDataType = rsString.IsHex() ? ePdfDataType_HexString : ePdfDataType_String; m_Data.pData = new PdfString( rsString ); } 

( PdfVariant.cpp )

对于TJ参数数组组件,有问题的字符串将被读取为hex字符串。

因此,在您的代码中,您必须同时考虑IsHexString()IsString()

 if( array[i].IsString() || array[i].IsHexString() ) 

2.此后,在修改代码以使用IsHexString(),进行检查后IsHexString(),问题集中在

 PdfString s = array[i].GetString(); _RPT1(_CRT_WARN, " : valid :%s ", s.IsValid()?"yes":"not"); _RPT1(_CRT_WARN, " ;hex :%s ", s.IsHex()?"yes":"not"); _RPT1(_CRT_WARN, " ;unicode: %s ", s.IsUnicode()?"yes":"not"); PdfString unicode = pCurFont->GetEncoding()->ConvertToUnicode(s,pCurFont); const char* szText = unicode.GetStringUtf8().c_str(); _RPT1(_CRT_WARN, " : %s\n", strlen(szText)> 0? szText: "nothing"); 

和问题(如评论中所述)

s.GetLength()返回2并且unicode.GetLength()返回0,转换不起作用?

对示例文档Document2.pdf的分析表明,所讨论的文档确实包含文本提取所需的信息。 该文档中唯一使用hex编码的字体是/ F1 ,其字体字典确实包含适当的/ ToUnicode映射,用于可靠的文本提取。

不幸的是,PoDoFo似乎还没有正确地使用该映射进行解析。 我没有看到它在任何地方检索/ ToUnicode映射以使包含的信息可用于文本解析。 看起来PoDoFo不能用于使用Type0 aka复合字体正确解析文档文本。