1

我正在尝试从使用 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<PdfVariant> 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<int>(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
[<0026>-7<004F>-15<004C>-15<0048>-11<0051>-11<0057>-19<0058>-11<004F>-15<0058>-11<004C>] 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
[<0030>9<0027>-13<002C>-16<0003>1<0026>-13<0032>13<0031>-13<0036>-9<0037>-6<0035>-13<0038>-13<0026>-13<0037>-6<0003>1<0037>-6<0035>-13<0024>-9<0031>-13<0036>-9<0003>1<0028>-9<003B>-9<0033>-9<0028>-9<0035>-13<0037>-6<0003>1<0036>-9<0035>-13<002F>] TJ
ET

PDF 文件应在此处此处找到

4

1 回答 1

5

1.中心代码部分的原始问题的答案是:

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

问题是:

我注意到array[i].IsString()永远不会成为真的。这是从TJ操作员那里获取文本的正确方法吗?

简短的回答:

PoDoFoPdfVariants中的十六进制字符串由IsHexString()代替识别IsString()。因此,您必须测试两种字符串风格:

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

长答案:

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

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

  • 作为括在括号 ( ) 中的文字字符序列(使用左括号 (28h) 和右括号 (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解析上下文中通常包含在 aPdfVariant甚至更具体地包含在 a 中的类PdfObject

但是,在确定其中包含的对象的类型时,PdfVariant文字字符串和十六进制字符串之间的区别:

/** \returns true if this variant is a string (i.e. GetDataType() == ePdfDataType_String)
 */
inline bool IsString() const { return GetDataType() == ePdfDataType_String; }

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

( PDFVariant.h )

PdfString内部 a的类型PdfVariant在包装时确定:

PdfVariant::PdfVariant( const PdfString & rsString )
{
    Init();
    Clear();

    m_eDataType  = rsString.IsHex() ? ePdfDataType_HexString : ePdfDataType_String;
    m_Data.pData = new PdfString( rsString );
}

( PDFVariant.cpp )

对于您的TJ参数数组组件,有问题的字符串被读取为十六进制字符串。

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

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

2.此后,修改代码后使用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 并返回 0 ,unicode.GetLength()转换不起作用?

对示例文档Document2.pdf的分析表明,相关文档确实包含文本提取所需的信息。该文档中唯一与十六进制编码一起使用的字体是/F1,并且它的字体字典确实包含适当的/ToUnicode映射以进行可靠的文本提取。

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

于 2013-05-17T11:59:54.620 回答