Examples / text_extraction / cpp

Text Extraction

A complete, runnable cpp program — 190 lines, shipped in your download.

Demonstrates Text

Same example, other languages: activex_cpp c cpp_linux csharp delphi php python vbnet

examples/cpp/text_extraction/text_extraction.cpp 190 lines
// text_extraction -- C++ port of examples\Vb6\text_extraction\text_extraction.bas
// Imports a PDF and extracts its text with GetPageText()/TPDFStack, rebuilding
// text lines and word boundaries by transforming each text record to user
// space. Output is written to out.txt as UTF-16LE (with BOM).
#include "apputil.h"
#include <cmath>
#include <vector>

// TTextDir
static const SI32 tfNotInitialized = 5;
static const double MAX_LINE_ERROR = 4.0;   // square of the allowed error (2 * 2)

static PPDF      m_PDF;
static FILE*     m_File;
static TPDFStack m_Stack;
static SI32      m_LastTextDir;
static double    m_LastTextEndX, m_LastTextEndY, m_LastTextInfX, m_LastTextInfY;

// template handle list
static std::vector<SI32> m_Templates;

static SI32 PDF_CALL ErrProc(void* Data, SI32 ErrCode, const char* ErrMessage, SI32 ErrType){
    if(ErrMessage) printf("%s\n", ErrMessage);
    return 0;   // We try to continue if an error occurs
}

// ------------------------- output helpers -------------------------
static void WriteWStr(const wchar_t* s){ fwrite(s, sizeof(wchar_t), wcslen(s), m_File); }
static void WriteWCharsFromPtr(const LWCHAR* p, SI32 count){
    if(!p || count <= 0) return;
    fwrite(p, sizeof(wchar_t), (size_t)count, m_File);
}

// ------------------------- CIntList -------------------------
static void ListClear(){ m_Templates.clear(); }
static void ListAdd(SI32 v){ m_Templates.push_back(v); }
static SI32 ListFind(SI32 v){
    for(size_t i = 0; i < m_Templates.size(); i++) if(m_Templates[i] == v) return (SI32)i;
    return -1;
}

// ------------------------- matrix helpers -------------------------
static TCTM MulMatrix(const TCTM& M1, const TCTM& M2){
    TCTM r;
    r.a = M2.a*M1.a + M2.b*M1.c;
    r.b = M2.a*M1.b + M2.b*M1.d;
    r.c = M2.c*M1.a + M2.d*M1.c;
    r.d = M2.c*M1.b + M2.d*M1.d;
    r.x = M2.x*M1.a + M2.y*M1.c + M1.x;
    r.y = M2.x*M1.b + M2.y*M1.d + M1.y;
    return r;
}
static void Transform(const TCTM& M, double& x, double& y){
    double tx = x;
    x = tx*M.a + y*M.c + M.x;
    y = tx*M.b + y*M.d + M.y;
}
static double CalcDistance(double x1,double y1,double x2,double y2){
    double dx=x2-x1, dy=y2-y1; return sqrt(dx*dx+dy*dy);
}
static bool IsPointOnLine(double x,double y,double x0,double y0,double x1,double y1){
    double dx,dy,di;
    x -= x0; y -= y0;
    dx = x1-x0; dy = y1-y0;
    di = (x*dx + y*dy) / (dx*dx + dy*dy);
    if(di < 0.0) di = 0.0; else if(di > 1.0) di = 1.0;
    dx = x - di*dx;
    dy = y - di*dy;
    di = dx*dx + dy*dy;
    return di < MAX_LINE_ERROR;
}

// ------------------------- text reconstruction -------------------------
static void AddText(){
    double x1=0.0, y1=0.0, x2=0.0, y2=m_Stack.FontSize, x3, y3;
    TCTM m = MulMatrix(m_Stack.ctm, m_Stack.tm);
    Transform(m, x1, y1);          // Start point of the text record
    Transform(m, x2, y2);          // Second point -> text direction
    SI32 textDir;
    if(y1 == y2) textDir = ((x1 > x2 ? 1 : 0) + 1) * 2;
    else         textDir = (y1 > y2 ? 1 : 0);

    if((textDir != m_LastTextDir) ||
       (!IsPointOnLine(x1, y1, m_LastTextEndX, m_LastTextEndY, m_LastTextInfX, m_LastTextInfY))){
        m_LastTextInfX = 1000000.0;
        m_LastTextInfY = 0.0;
        Transform(m, m_LastTextInfX, m_LastTextInfY);
        if(m_LastTextDir != tfNotInitialized) WriteWStr(L"\r\n");
    } else {
        x3 = m_Stack.SpaceWidth; y3 = 0.0;
        Transform(m, x3, y3);
        double spaceWidth = CalcDistance(x1, y1, x3, y3);
        double distance   = CalcDistance(m_LastTextEndX, m_LastTextEndY, x1, y1);
        if(distance > spaceWidth) WriteWStr(L" ");
    }

    float spw = -m_Stack.SpaceWidth * 0.5f;
    for(UI32 i = 0; i < m_Stack.KerningCount; i++){
        TTextRecordW rec = m_Stack.Kerning[i];
        if(rec.Advance < spw) WriteWStr(L" ");
        WriteWCharsFromPtr(rec.Text, rec.Length);
    }

    m_LastTextEndX = m_Stack.TextWidth + spw;   // spw is negative
    m_LastTextEndY = 0.0;
    m_LastTextDir = textDir;
    Transform(m, m_LastTextEndX, m_LastTextEndY);
}

static void ParseText(){
    bool haveMore = (pdfGetPageText(m_PDF, &m_Stack) != 0);
    if((!haveMore) && (m_Stack.TextLen == 0)) return;
    AddText();
    if(haveMore){
        while(pdfGetPageText(m_PDF, &m_Stack) != 0) AddText();
    }
}

static void ParseTemplates(){
    SI32 tmplCount = pdfGetTemplCount(m_PDF);
    for(SI32 i = 0; i < tmplCount; i++){
        if(pdfEditTemplate(m_PDF, i) == 0) return;
        SI32 tmpl = pdfGetTemplHandle(m_PDF);
        if(ListFind(tmpl) < 0){
            ListAdd(tmpl);
            if(pdfInitStack(m_PDF, &m_Stack) == 0) return;
            ParseText();
            SI32 tmplCount2 = pdfGetTemplCount(m_PDF);
            for(SI32 j = 0; j < tmplCount2; j++) ParseTemplates();
            pdfEndTemplate(m_PDF);
        } else {
            pdfEndTemplate(m_PDF);
        }
    }
}

static void ParsePage(){
    ListClear();
    if(pdfInitStack(m_PDF, &m_Stack) == 0){
        printf("%s\n", pdfGetErrorMessage(m_PDF));
        return;
    }
    m_LastTextEndX = m_LastTextEndY = 0.0;
    m_LastTextDir = tfNotInitialized;
    m_LastTextInfX = m_LastTextInfY = 0.0;
    ParseText();
    ParseTemplates();
}

int main(){
    ChdirToExe();
    m_PDF = pdfNewPDF();
    pdfCreateNewPDFA(m_PDF, "");          // We do not produce a PDF file in this example
    pdfSetOnErrorProc(m_PDF, 0, ErrProc);

    pdfSetCMapDirA(m_PDF, "CMap", lcmRecursive | lcmDelayed);

    pdfSetImportFlags(m_PDF, ifImportAll | ifImportAsPage);
    const char* inFile = "in.pdf";
    if(pdfOpenImportFileA(m_PDF, inFile, ptOpen, "") < 0){
        pdfDeletePDF(m_PDF);
        return 0;
    }
    pdfImportPDFFile(m_PDF, 1, 1.0, 1.0);
    pdfCloseImportFile(m_PDF);

    pdfFlattenAnnots(m_PDF, affMarkupAnnots);
    pdfFlattenForm(m_PDF);

    m_File = fopen("out.txt", "wb");
    if(!m_File){ pdfDeletePDF(m_PDF); return 0; }
    unsigned char bom[2] = {0xFF, 0xFE};   // UTF-16LE BOM
    fwrite(bom, 1, 2, m_File);

    for(SI32 i = 1; i <= pdfGetPageCount(m_PDF); i++){
        pdfEditPage(m_PDF, i);
        wchar_t buf[128];
        int n = swprintf(buf, 128, L"%ls%%----------------------- Page %d -----------------------------\r\n",
                         (i > 1 ? L"\r\n" : L""), (int)i);
        if(n > 0) fwrite(buf, sizeof(wchar_t), (size_t)n, m_File);
        ParsePage();
        pdfEndPage(m_PDF);
    }
    fclose(m_File);

    printf("Text successfully extracted to out.txt\n");
    pdfDeletePDF(m_PDF);
    return 0;
}

This file is in the SDK at examples/cpp/text_extraction/text_extraction.cpp. The build fails if this page and that file ever differ.