Examples / text_extraction / c

Text Extraction

A complete, runnable c program — 201 lines, shipped in your download.

Demonstrates Text

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

examples/c/text_extraction/text_extraction.c 201 lines
/* text_extraction -- C port of examples\Vb6\text_extraction
   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 <stdio.h>
#include <math.h>
#include <wchar.h>
#include "lumaspdf.h"

/* TTextDir */
#define tfNotInitialized 5
#define 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;

/* CIntList (template handle list) */
static SI32 m_Templates[4096];
static SI32 m_TemplCount;

static SI32 PDF_CALL ErrProc(void* Data, SI32 ErrCode, const char* ErrMessage, SI32 ErrType) {
    (void)Data; (void)ErrCode; (void)ErrType;
    if (ErrMessage) printf("%s\n", ErrMessage);
    return 0;
}

/* ---------------- output helpers ---------------- */
static void WriteWStr(const wchar_t* s) {
    if (s && *s) fwrite(s, 2, wcslen(s), m_File);
}
static void WriteWCharsFromPtr(const LWCHAR* p, SI32 count) {
    if (p && count > 0) fwrite(p, 2, count, m_File);
}

/* ---------------- CIntList ---------------- */
static void ListClear(void) { m_TemplCount = 0; }
static void ListAdd(SI32 v) { if (m_TemplCount < 4096) m_Templates[m_TemplCount++] = v; }
static SI32 ListFind(SI32 v) {
    SI32 i;
    for (i = 0; i < m_TemplCount; i++) if (m_Templates[i] == v) return 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 int 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;
    if ((dx * dx + dy * dy) == 0.0) return 0;   /* div-by-zero guard */
    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(void) {
    SI32 i;
    double x1, x2, y1, y2, x3, y3, distance, spaceWidth;
    SI32 textDir; TCTM m; double spw;
    TTextRecordW* recs;

    x1 = 0.0; y1 = 0.0;
    x2 = 0.0; y2 = m_Stack.FontSize;
    m = MulMatrix(&m_Stack.ctm, &m_Stack.tm);
    Transform(&m, &x1, &y1);
    Transform(&m, &x2, &y2);
    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);
        spaceWidth = CalcDistance(x1, y1, x3, y3);
        distance = CalcDistance(m_LastTextEndX, m_LastTextEndY, x1, y1);
        if (distance > spaceWidth) WriteWStr(L" ");
    }

    spw = -m_Stack.SpaceWidth * 0.5;
    recs = (TTextRecordW*)m_Stack.Kerning;
    for (i = 0; i < (SI32)m_Stack.KerningCount; i++) {
        if (recs[i].Advance < spw) WriteWStr(L" ");
        WriteWCharsFromPtr(recs[i].Text, recs[i].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(void) {
    int 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(void) {
    SI32 i, j, tmpl, tmplCount, tmplCount2;
    tmplCount = pdfGetTemplCount(m_PDF);
    for (i = 0; i < tmplCount; i++) {
        if (pdfEditTemplate(m_PDF, i) == 0) return;
        tmpl = pdfGetTemplHandle(m_PDF);
        if (ListFind(tmpl) < 0) {
            ListAdd(tmpl);
            if (pdfInitStack(m_PDF, &m_Stack) == 0) return;
            ParseText();
            tmplCount2 = pdfGetTemplCount(m_PDF);
            for (j = 0; j < tmplCount2; j++) ParseTemplates();
            pdfEndTemplate(m_PDF);
        } else {
            pdfEndTemplate(m_PDF);
        }
    }
}

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

int main(void) {
    int i, cnt;
    unsigned char bom[2] = { 0xFF, 0xFE };
    wchar_t hdr[96];

    m_PDF = pdfNewPDF();
    pdfCreateNewPDFA(m_PDF, "");
    pdfSetOnErrorProc(m_PDF, 0, ErrProc);

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

    pdfSetImportFlags(m_PDF, ifImportAll | ifImportAsPage);
    if (pdfOpenImportFileA(m_PDF, "in.pdf", ptOpen, "") < 0) { pdfDeletePDF(m_PDF); return 1; }
    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 1; }
    fwrite(bom, 1, 2, m_File);

    cnt = pdfGetPageCount(m_PDF);
    for (i = 1; i <= cnt; i++) {
        pdfEditPage(m_PDF, i);
        swprintf(hdr, 96, L"%ls%%----------------------- Page %d -----------------------------\r\n",
                 (i > 1 ? L"\r\n" : L""), i);
        WriteWStr(hdr);
        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/c/text_extraction/text_extraction.c. The build fails if this page and that file ever differ.