Skip to documentation
SLOP

tiny.pdf.content

Reference tiny.pdf content

Defined in tiny.pdf.

API (1)

Actions

Public operations.

No direct callersNo direct callstiny.pdfcontent
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallstest sourcelib.pdf.src.contenttest: content extraction maps codes t...test sourcelib.pdf.src.contenttest: content extraction reads show o...test sourcelib.pdf.src.contenttest: content extraction survives unk...tiny.pdfextractTextAllocprivate sourcelib.pdf.src.contentapplyOperatorprivate sourcelib.pdf.src.contentcollectStringsobject.Parserinitobject.ParserparseValueobject.ParserskipWhitespaceobjectdelimiterOrWhitespacecontentextractTextAppend
Static calls · unresolved targets: 2 · external targets: 3.

Source: lib/pdf/src/content.zig

zig
const std = @import("std");const alloc_arena = @import("alloc_arena");const font = @import("font.zig");const object = @import("object.zig");const Piece = union(enum) {    text: object.String,    space,};pub fn extractTextAppend(allocator: std.mem.Allocator, out: *std.ArrayList(u8), content: []const u8, fonts: font.Set) !void {    var parser = object.Parser.init(content, 0);    var pending_strings: std.ArrayList(Piece) = .empty;    defer pending_strings.deinit(allocator);    var arena_state = alloc_arena.Arena.init(allocator);    defer arena_state.deinit();    const arena = arena_state.allocator();    var last_name: ?[]const u8 = null;    var current_font: ?*const font.Font = null;    for (content) |_| {        parser.skipWhitespace();        if (parser.pos >= content.len) break;        const byte = content[parser.pos];        if (byte == '/' or byte == '(' or byte == '<' or byte == '[' or std.ascii.isDigit(byte) or byte == '+' or byte == '-' or byte == '.') {            const value = parser.parseValue(arena) catch {                parser.pos += 1;                continue;            };            switch (value) {                .name => |name| last_name = name,                else => {},            }            try collectStrings(allocator, &pending_strings, value);            continue;        }        const start = parser.pos;        while (parser.pos < content.len and !object.delimiterOrWhitespace(content[parser.pos])) parser.pos += 1;        if (parser.pos == start) {            parser.pos += 1;            continue;        }        const operator = content[start..parser.pos];        if (std.mem.eql(u8, operator, "Tf")) {            current_font = if (last_name) |name| fonts.get(name) else null;        }        try applyOperator(allocator, out, &pending_strings, operator, current_font);    }}fn collectStrings(allocator: std.mem.Allocator, pending: *std.ArrayList(Piece), value: object.Value) !void {    switch (value) {        .string => |string| try pending.append(allocator, .{ .text = string }),        .array => |items| {            for (items) |item| {                switch (item) {                    .string => |string| try pending.append(allocator, .{ .text = string }),                    .integer => |kern| if (kern <= -space_kerning_threshold) try pending.append(allocator, .space),                    .real => |kern| if (kern <= -@as(f64, space_kerning_threshold)) try pending.append(allocator, .space),                    else => {},                }            }        },        else => {},    }}const space_kerning_threshold = 180;fn applyOperator(allocator: std.mem.Allocator, out: *std.ArrayList(u8), pending: *std.ArrayList(Piece), operator: []const u8, current_font: ?*const font.Font) !void {    if (std.mem.eql(u8, operator, "Tj") or std.mem.eql(u8, operator, "'") or std.mem.eql(u8, operator, "\"") or std.mem.eql(u8, operator, "TJ")) {        if (std.mem.eql(u8, operator, "'") or std.mem.eql(u8, operator, "\"")) {            try breakLine(allocator, out);        }        for (pending.items) |piece| {            switch (piece) {                .text => |string| try appendString(allocator, out, string, current_font),                .space => {                    if (out.items.len > 0 and out.items[out.items.len - 1] != ' ' and out.items[out.items.len - 1] != '\n') {                        try out.append(allocator, ' ');                    }                },            }        }        pending.clearRetainingCapacity();        return;    }    if (std.mem.eql(u8, operator, "Td") or std.mem.eql(u8, operator, "TD") or std.mem.eql(u8, operator, "T*") or std.mem.eql(u8, operator, "ET")) {        try breakLine(allocator, out);        pending.clearRetainingCapacity();        return;    }    pending.clearRetainingCapacity();}fn appendString(allocator: std.mem.Allocator, out: *std.ArrayList(u8), string: object.String, current_font: ?*const font.Font) !void {    var raw = std.ArrayList(u8).empty;    defer raw.deinit(allocator);    switch (string.kind) {        .literal => try object.decodeLiteralAppend(allocator, &raw, string.raw),        .hex => try object.decodeHexAppend(allocator, &raw, string.raw),    }    try font.appendText(current_font, allocator, out, raw.items);}fn breakLine(allocator: std.mem.Allocator, out: *std.ArrayList(u8)) !void {    if (out.items.len == 0) return;    if (out.items[out.items.len - 1] == '\n') return;    try out.append(allocator, '\n');}test "content extraction reads show operators lines and tj arrays" {    var out = std.ArrayList(u8).empty;    defer out.deinit(std.testing.allocator);    const content =        "BT /F1 12 Tf 72 700 Td (Hello, ) Tj (world) Tj T* (second line) Tj " ++        "0 -14 Td [(kerned) -250 (array) 12 (Close)] TJ ET";    try extractTextAppend(std.testing.allocator, &out, content, .{});    try std.testing.expectEqualStrings("Hello, world\nsecond line\nkerned arrayClose\n", out.items);}test "content extraction survives unknown operators and hex strings" {    var out = std.ArrayList(u8).empty;    defer out.deinit(std.testing.allocator);    try extractTextAppend(std.testing.allocator, &out, "q 1 0 0 1 10 10 cm BT <48690A> Tj ET Q", .{});    try std.testing.expectEqualStrings("Hi\n", out.items);}test "content extraction maps codes through the selected font" {    const cmap = @import("cmap.zig");    var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);    defer arena_state.deinit();    const map = try cmap.parseAlloc(        arena_state.allocator(),        "1 begincodespacerange <00> <FF> endcodespacerange " ++            "1 beginbfrange <20> <7E> <0020> endbfrange " ++            "1 beginbfchar <02> <00660069> endbfchar",    );    const mapped = font.Font{ .map = map };    const plain = font.Font{};    const set = font.Set{ .entries = &.{        .{ .name = "F1", .font = &mapped },        .{ .name = "F2", .font = &plain },    } };    var out = std.ArrayList(u8).empty;    defer out.deinit(std.testing.allocator);    try extractTextAppend(        std.testing.allocator,        &out,        "BT /F1 9 Tf (Arti\\002cial) Tj /F2 9 Tf ( raw\\002) Tj ET",        set,    );    try std.testing.expectEqualStrings("Artificial raw\x02\n", out.items);}

Source: lib/pdf/src/root.zig:12

zig
pub const content = @import("content.zig");

Audit

Definitions2
Public names2
Members0
Version26.7.0
Revisiondaab053ee433