PDF-Make

 view release on metacpan or  search on metacpan

src/pdfmake_textract.c  view on Meta::CPAN


    /* /Pg filter: if this StructElem binds a specific page and it doesn't
     * match ours, we can still recurse — children may override /Pg. */
    k_key = pdfmake_arena_intern_name(r->arena, "K", 1);
    k = pdfmake_dict_get(node, k_key);
    if (!k) return;

    /* /K can be an integer (MCID), a dict (StructElem/MCR/OBJR), a ref, or
     * an array of any of the above. walk_struct_node handles all cases. */
    walk_struct_node(r, k, this_role, page_dict, depth + 1);
}

pdfmake_err_t pdfmake_textract_resolve_struct_tree(
    pdfmake_textract_result_t *result,
    pdfmake_obj_t             *struct_root,
    pdfmake_obj_t             *page_dict)
{
    pdfmake_reader_t *rd;
    uint32_t k_key;
    pdfmake_obj_t *k;
    if (!result) return PDFMAKE_EINVAL;
    if (!struct_root || !result->arena) return PDFMAKE_OK;  /* no-op */

    /* Follow an indirect reference to the StructTreeRoot dict */
    if (struct_root->kind == PDFMAKE_REF && result->reader) {
        rd = (pdfmake_reader_t *)result->reader;
        if (rd->parser) {
            struct_root = pdfmake_parser_resolve(rd->parser, struct_root->as.ref);
            if (!struct_root) return PDFMAKE_OK;
        }
    }
    if (struct_root->kind != PDFMAKE_DICT) return PDFMAKE_OK;

    /* Start at /K of the StructTreeRoot; role 0 means "none" (children will
     * overwrite as soon as they hit a StructElem with /S). */
    k_key = pdfmake_arena_intern_name(result->arena, "K", 1);
    k = pdfmake_dict_get(struct_root, k_key);
    if (k) walk_struct_node(result, k, 0, page_dict, 0);

    return PDFMAKE_OK;
}

uint32_t pdfmake_textract_role_for_mcid(
    const pdfmake_textract_result_t *result, int32_t mcid)
{
    size_t i;
    if (!result || mcid < 0) return 0;
    for (i = 0; i < result->struct_map_len; i++) {
        if (result->struct_map[i].mcid == mcid) {
            return result->struct_map[i].role_id;
        }
    }
    return 0;
}

/*============================================================================
 * Phase 13 — Annotation + form field text extraction
 *==========================================================================*/

/* Decode a PDF "text string" (§7.9.2) to UTF-8, arena-allocated.
 * Handles UTF-16BE (with BOM FE FF) and UTF-8-with-BOM (EF BB BF); everything
 * else is treated as PDFDocEncoding, which matches ISO-8859-1 for the ASCII
 * subset used by every real-world annotation we've seen. */
static const char *decode_pdf_text(pdfmake_arena_t *arena,
                                    const uint8_t *b, size_t n)
{
    size_t i;
    pdfmake_buf_t out;
    char *s;
    uint32_t cp;
    uint32_t lo;
    uint8_t c;
    if (!arena || !b) return NULL;

    /* UTF-16BE BOM */
    if (n >= 2 && b[0] == 0xFE && b[1] == 0xFF) {
        pdfmake_buf_init(&out);
        s = NULL;
        for (i = 2; i + 1 < n; i += 2) {
            cp = ((uint32_t)b[i] << 8) | b[i + 1];
            if (cp >= 0xD800 && cp <= 0xDBFF && i + 3 < n) {
                lo = ((uint32_t)b[i + 2] << 8) | b[i + 3];
                if (lo >= 0xDC00 && lo <= 0xDFFF) {
                    cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
                    i += 2;
                }
            }
            if (cp < 0x80) {
                pdfmake_buf_append_byte(&out, (uint8_t)cp);
            } else if (cp < 0x800) {
                pdfmake_buf_append_byte(&out, 0xC0 | (cp >> 6));
                pdfmake_buf_append_byte(&out, 0x80 | (cp & 0x3F));
            } else if (cp < 0x10000) {
                pdfmake_buf_append_byte(&out, 0xE0 | (cp >> 12));
                pdfmake_buf_append_byte(&out, 0x80 | ((cp >> 6) & 0x3F));
                pdfmake_buf_append_byte(&out, 0x80 | (cp & 0x3F));
            } else {
                pdfmake_buf_append_byte(&out, 0xF0 | (cp >> 18));
                pdfmake_buf_append_byte(&out, 0x80 | ((cp >> 12) & 0x3F));
                pdfmake_buf_append_byte(&out, 0x80 | ((cp >> 6) & 0x3F));
                pdfmake_buf_append_byte(&out, 0x80 | (cp & 0x3F));
            }
        }
        s = pdfmake_arena_alloc(arena, out.len + 1);
        if (s) { memcpy(s, out.data, out.len); s[out.len] = 0; }
        pdfmake_buf_free(&out);
        return s;
    }

    /* UTF-8 with BOM (PDF 2.0) */
    if (n >= 3 && b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF) {
        b += 3; n -= 3;
    }

    /* PDFDocEncoding → promote high-bit bytes to 2-byte UTF-8. */
    pdfmake_buf_init(&out);
    for (i = 0; i < n; i++) {
        c = b[i];
        if (c < 0x80) {
            pdfmake_buf_append_byte(&out, c);
        } else {
            pdfmake_buf_append_byte(&out, 0xC0 | (c >> 6));
            pdfmake_buf_append_byte(&out, 0x80 | (c & 0x3F));
        }
    }
    s = pdfmake_arena_alloc(arena, out.len + 1);
    if (s) { memcpy(s, out.data, out.len); s[out.len] = 0; }
    pdfmake_buf_free(&out);
    return s;
}

/* Pull a PDF text string out of a dict (resolving one level of indirection).
 * Returns NULL if the key is missing or not a string. */
static const char *dict_get_text(pdfmake_reader_t *rd,
                                  pdfmake_arena_t *arena,
                                  pdfmake_obj_t *dict,
                                  const char *name)
{
    uint32_t key;
    pdfmake_obj_t *v;
    if (!dict || dict->kind != PDFMAKE_DICT) return NULL;
    key = pdfmake_arena_intern_name(arena, name, strlen(name));
    v = pdfmake_dict_get(dict, key);
    if (v && v->kind == PDFMAKE_REF && rd && rd->parser) {
        v = pdfmake_parser_resolve(rd->parser, v->as.ref);
    }
    if (!v || v->kind != PDFMAKE_STR) return NULL;
    return decode_pdf_text(arena, v->as.str.bytes, v->as.str.len);
}

/* Resolve a dict entry that's allowed to be an indirect reference. */
static pdfmake_obj_t *dict_get_resolved(pdfmake_reader_t *rd,
                                         pdfmake_arena_t *arena,
                                         pdfmake_obj_t *dict,
                                         const char *name)
{
    uint32_t key;
    pdfmake_obj_t *v;
    if (!dict || dict->kind != PDFMAKE_DICT) return NULL;
    key = pdfmake_arena_intern_name(arena, name, strlen(name));
    v = pdfmake_dict_get(dict, key);
    if (v && v->kind == PDFMAKE_REF && rd && rd->parser) {
        v = pdfmake_parser_resolve(rd->parser, v->as.ref);
    }
    return v;
}

pdfmake_annot_text_list_t *pdfmake_annot_text_list_new(pdfmake_arena_t *arena) {
    pdfmake_annot_text_list_t *l = calloc(1, sizeof(*l));
    if (l) l->arena = arena;



( run in 0.648 second using v1.01-cache-2.11-cpan-9581c071862 )