tiny.pdf.object
Defined in tiny.pdf.
API (19)
Actions
Public operations.
Parser.atKeywordParser.expectKeywordParser.initParser.parseUnsignedParser.parseValueParser.skipWhitespaceParser.streamStartdecodeHexAppenddecodeLiteralAppenddelimiterOrWhitespacewhitespace
Types and contracts
Public types and contracts.
Source
Source: lib/pdf/src/object.zig
zig
const std = @import("std");pub const ParseError = error{ UnexpectedEnd, UnexpectedToken, BadNumber, BadName, BadString, BadDictionary, OutOfMemory,};pub const Reference = struct { number: u32, generation: u16,};pub const Entry = struct { key: []const u8, value: Value,};pub const Dict = struct { entries: []Entry = &.{}, pub fn get(self: Dict, key: []const u8) ?Value { for (self.entries) |entry| { if (std.mem.eql(u8, entry.key, key)) return entry.value; } return null; }};pub const StringKind = enum { literal, hex,};pub const String = struct { kind: StringKind, raw: []const u8,};pub const Value = union(enum) { null, boolean: bool, integer: i64, real: f64, name: []const u8, string: String, array: []Value, dict: Dict, reference: Reference,};const NumberToken = union(enum) { integer: i64, real: f64,};pub const Parser = struct { bytes: []const u8, pos: usize, pub fn init(bytes: []const u8, pos: usize) Parser { return .{ .bytes = bytes, .pos = pos }; } pub fn skipWhitespace(self: *Parser) void { if (self.pos >= self.bytes.len) return; for (self.bytes[self.pos..]) |_| { if (self.pos >= self.bytes.len) return; const byte = self.bytes[self.pos]; if (whitespace(byte)) { self.pos += 1; continue; } if (byte == '%') { while (self.pos < self.bytes.len and self.bytes[self.pos] != '\n' and self.bytes[self.pos] != '\r') self.pos += 1; continue; } return; } } pub fn parseValue(self: *Parser, arena: std.mem.Allocator) ParseError!Value { self.skipWhitespace(); if (self.pos >= self.bytes.len) return error.UnexpectedEnd; const byte = self.bytes[self.pos]; switch (byte) { '/' => return .{ .name = try self.parseName() }, '(' => return .{ .string = .{ .kind = .literal, .raw = try self.parseLiteralString() } }, '[' => return .{ .array = try self.parseArray(arena) }, '<' => { if (self.pos + 1 < self.bytes.len and self.bytes[self.pos + 1] == '<') { return .{ .dict = try self.parseDict(arena) }; } return .{ .string = .{ .kind = .hex, .raw = try self.parseHexString() } }; }, 't', 'f', 'n' => return try self.parseKeywordValue(), else => return try self.parseNumberOrReference(), } } pub fn expectKeyword(self: *Parser, keyword: []const u8) ParseError!void { self.skipWhitespace(); if (self.pos + keyword.len > self.bytes.len) return error.UnexpectedEnd; if (!std.mem.eql(u8, self.bytes[self.pos .. self.pos + keyword.len], keyword)) return error.UnexpectedToken; self.pos += keyword.len; } pub fn atKeyword(self: *Parser, keyword: []const u8) bool { self.skipWhitespace(); if (self.pos + keyword.len > self.bytes.len) return false; if (!std.mem.eql(u8, self.bytes[self.pos .. self.pos + keyword.len], keyword)) return false; const end = self.pos + keyword.len; if (end < self.bytes.len and !delimiterOrWhitespace(self.bytes[end])) return false; return true; } pub fn streamStart(self: *Parser) ParseError!usize { if (!self.atKeyword("stream")) return error.UnexpectedToken; self.pos += "stream".len; if (self.pos < self.bytes.len and self.bytes[self.pos] == '\r') self.pos += 1; if (self.pos < self.bytes.len and self.bytes[self.pos] == '\n') self.pos += 1; return self.pos; } pub fn parseUnsigned(self: *Parser, comptime T: type) ParseError!T { self.skipWhitespace(); const start = self.pos; while (self.pos < self.bytes.len and std.ascii.isDigit(self.bytes[self.pos])) self.pos += 1; if (self.pos == start) return error.BadNumber; return std.fmt.parseUnsigned(T, self.bytes[start..self.pos], 10) catch error.BadNumber; } fn parseName(self: *Parser) ParseError![]const u8 { self.pos += 1; const start = self.pos; while (self.pos < self.bytes.len and !delimiterOrWhitespace(self.bytes[self.pos])) self.pos += 1; if (self.pos == start) return error.BadName; return self.bytes[start..self.pos]; } fn parseLiteralString(self: *Parser) ParseError![]const u8 { self.pos += 1; const start = self.pos; var depth: usize = 1; for (self.bytes[self.pos..]) |_| { if (self.pos >= self.bytes.len) break; const byte = self.bytes[self.pos]; if (byte == '\\') { self.pos += @min(2, self.bytes.len - self.pos); continue; } if (byte == '(') depth += 1; if (byte == ')') { depth -= 1; if (depth == 0) { const raw = self.bytes[start..self.pos]; self.pos += 1; return raw; } } self.pos += 1; } return error.BadString; } fn parseHexString(self: *Parser) ParseError![]const u8 { self.pos += 1; const start = self.pos; while (self.pos < self.bytes.len and self.bytes[self.pos] != '>') self.pos += 1; if (self.pos >= self.bytes.len) return error.BadString; const raw = self.bytes[start..self.pos]; self.pos += 1; return raw; } fn parseArray(self: *Parser, arena: std.mem.Allocator) ParseError![]Value { self.pos += 1; var items: std.ArrayList(Value) = .empty; for (self.bytes[self.pos..]) |_| { self.skipWhitespace(); if (self.pos >= self.bytes.len) return error.UnexpectedEnd; if (self.bytes[self.pos] == ']') { self.pos += 1; return try items.toOwnedSlice(arena); } try items.append(arena, try self.parseValue(arena)); } return error.UnexpectedEnd; } fn parseDict(self: *Parser, arena: std.mem.Allocator) ParseError!Dict { self.pos += 2; var entries: std.ArrayList(Entry) = .empty; for (self.bytes[self.pos..]) |_| { self.skipWhitespace(); if (self.pos + 1 < self.bytes.len and self.bytes[self.pos] == '>' and self.bytes[self.pos + 1] == '>') { self.pos += 2; return .{ .entries = try entries.toOwnedSlice(arena) }; } if (self.pos >= self.bytes.len) return error.UnexpectedEnd; if (self.bytes[self.pos] != '/') return error.BadDictionary; const key = try self.parseName(); const value = try self.parseValue(arena); try entries.append(arena, .{ .key = key, .value = value }); } return error.UnexpectedEnd; } fn parseKeywordValue(self: *Parser) ParseError!Value { if (self.atKeyword("true")) { self.pos += 4; return .{ .boolean = true }; } if (self.atKeyword("false")) { self.pos += 5; return .{ .boolean = false }; } if (self.atKeyword("null")) { self.pos += 4; return .null; } return error.UnexpectedToken; } fn parseNumberOrReference(self: *Parser) ParseError!Value { const first = try self.parseNumberToken(); switch (first) { .real => |value| return .{ .real = value }, .integer => |value| { const saved = self.pos; if (self.tryReferenceSuffix()) |generation| { if (value >= 0 and value <= std.math.maxInt(u32)) { return .{ .reference = .{ .number = @intCast(value), .generation = generation } }; } } self.pos = saved; return .{ .integer = value }; }, } } fn parseNumberToken(self: *Parser) ParseError!NumberToken { self.skipWhitespace(); const start = self.pos; var saw_dot = false; if (self.pos < self.bytes.len and (self.bytes[self.pos] == '+' or self.bytes[self.pos] == '-')) self.pos += 1; for (self.bytes[self.pos..]) |byte| { if (std.ascii.isDigit(byte)) { self.pos += 1; continue; } if (byte == '.' and !saw_dot) { saw_dot = true; self.pos += 1; continue; } break; } const text = self.bytes[start..self.pos]; if (text.len == 0 or std.mem.eql(u8, text, "+") or std.mem.eql(u8, text, "-")) return error.BadNumber; if (saw_dot) { return .{ .real = std.fmt.parseFloat(f64, text) catch return error.BadNumber }; } return .{ .integer = std.fmt.parseInt(i64, text, 10) catch return error.BadNumber }; } fn tryReferenceSuffix(self: *Parser) ?u16 { const saved = self.pos; self.skipWhitespace(); const generation_start = self.pos; while (self.pos < self.bytes.len and std.ascii.isDigit(self.bytes[self.pos])) self.pos += 1; if (self.pos == generation_start) { self.pos = saved; return null; } const generation = std.fmt.parseUnsigned(u16, self.bytes[generation_start..self.pos], 10) catch { self.pos = saved; return null; }; self.skipWhitespace(); if (self.pos < self.bytes.len and self.bytes[self.pos] == 'R') { const after = self.pos + 1; if (after >= self.bytes.len or delimiterOrWhitespace(self.bytes[after])) { self.pos = after; return generation; } } self.pos = saved; return null; }};pub fn whitespace(byte: u8) bool { return byte == ' ' or byte == '\t' or byte == '\r' or byte == '\n' or byte == '\x0C' or byte == 0;}pub fn delimiterOrWhitespace(byte: u8) bool { return whitespace(byte) or switch (byte) { '(', ')', '<', '>', '[', ']', '{', '}', '/', '%' => true, else => false, };}pub fn decodeLiteralAppend(allocator: std.mem.Allocator, out: *std.ArrayList(u8), raw: []const u8) !void { var index: usize = 0; for (raw) |_| { if (index >= raw.len) break; const byte = raw[index]; if (byte != '\\') { try out.append(allocator, byte); index += 1; continue; } index += 1; if (index >= raw.len) break; const escaped = raw[index]; switch (escaped) { 'n' => try out.append(allocator, '\n'), 'r' => try out.append(allocator, '\r'), 't' => try out.append(allocator, '\t'), 'b' => try out.append(allocator, 0x08), 'f' => try out.append(allocator, 0x0C), '(', ')', '\\' => try out.append(allocator, escaped), '\n' => {}, '\r' => { if (index + 1 < raw.len and raw[index + 1] == '\n') index += 1; }, '0'...'7' => { var code: u16 = 0; var digits: usize = 0; while (digits < 3 and index < raw.len and raw[index] >= '0' and raw[index] <= '7') { code = code * 8 + (raw[index] - '0'); index += 1; digits += 1; } try out.append(allocator, @truncate(code)); continue; }, else => try out.append(allocator, escaped), } index += 1; }}pub fn decodeHexAppend(allocator: std.mem.Allocator, out: *std.ArrayList(u8), raw: []const u8) !void { var high: ?u8 = null; for (raw) |byte| { const nibble: u8 = switch (byte) { '0'...'9' => byte - '0', 'a'...'f' => byte - 'a' + 10, 'A'...'F' => byte - 'A' + 10, else => continue, }; if (high) |value| { try out.append(allocator, (value << 4) | nibble); high = null; } else { high = nibble; } } if (high) |value| try out.append(allocator, value << 4);}test "parser reads scalar values names strings and references" { var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena_state.deinit(); const arena = arena_state.allocator(); const source = "% lead comment\n" ++ "<< /Type /Page /Count 3 /Rot -90 /Scale 1.5 /Parent 2 0 R " ++ "/Label (hi \\(there\\)) /Id <48690A> /Flags [true false null] >>"; var parser = Parser.init(source, 0); const value = try parser.parseValue(arena); const dict = value.dict; try std.testing.expectEqualStrings("Page", dict.get("Type").?.name); try std.testing.expectEqual(@as(i64, 3), dict.get("Count").?.integer); try std.testing.expectEqual(@as(i64, -90), dict.get("Rot").?.integer); try std.testing.expectEqual(@as(f64, 1.5), dict.get("Scale").?.real); try std.testing.expectEqual(@as(u32, 2), dict.get("Parent").?.reference.number); try std.testing.expectEqualStrings("hi \\(there\\)", dict.get("Label").?.string.raw); try std.testing.expectEqual(StringKind.hex, dict.get("Id").?.string.kind); try std.testing.expectEqual(@as(usize, 3), dict.get("Flags").?.array.len); var literal = Parser.init("(", 0); try std.testing.expectError(error.BadString, literal.parseValue(arena)); var array = Parser.init("[1", 0); try std.testing.expectError(error.UnexpectedEnd, array.parseValue(arena)); var truncated_dict = Parser.init("<< /A 1", 0); try std.testing.expectError(error.UnexpectedEnd, truncated_dict.parseValue(arena)); var comment = Parser.init("% trailing comment", 0); comment.skipWhitespace(); try std.testing.expectEqual(comment.bytes.len, comment.pos);}test "literal and hex decoding handle escapes and odd digits" { var out = std.ArrayList(u8).empty; defer out.deinit(std.testing.allocator); try decodeLiteralAppend(std.testing.allocator, &out, "A\\nB\\051C\\\\D"); try std.testing.expectEqualStrings("A\nB)C\\D", out.items); out.clearRetainingCapacity(); try decodeLiteralAppend(std.testing.allocator, &out, "A\\"); try std.testing.expectEqualStrings("A", out.items); out.clearRetainingCapacity(); try decodeHexAppend(std.testing.allocator, &out, "48 65 6C 6C 6F 2"); try std.testing.expectEqualStrings("Hello ", out.items);}Source: lib/pdf/src/root.zig:15
zig
pub const object = @import("object.zig");Complete caller list for object.Parser.init
9 direct callers.
tiny.pdf.cmap.parseAlloc[function] atlib/pdf/src/cmap.zig:121tiny.pdf.content.extractTextAppend[function] atlib/pdf/src/content.zig:12lib.pdf.src.document.ObjectAccess.loadContainer[method] — private source atlib/pdf/src/document.zig:349in nearest public ownerlib.pdf.src.documentlib.pdf.src.document.ObjectAccess.objectStream[method] — private source atlib/pdf/src/document.zig:258in nearest public ownerlib.pdf.src.documentlib.pdf.src.document.ObjectAccess.parseObject[method] — private source atlib/pdf/src/document.zig:315in nearest public ownerlib.pdf.src.documentlib.pdf.src.object.test_parser_reads_scalar_values_names_strings_and_references[function] — test source atlib/pdf/src/object.zig:368in nearest public ownertiny.pdf.objectlib.pdf.src.xref.parseSection[function] — private source atlib/pdf/src/xref.zig:71in nearest public ownertiny.pdf.xreflib.pdf.src.xref.parseStreamSection[function] — private source atlib/pdf/src/xref.zig:170in nearest public ownertiny.pdf.xreflib.pdf.src.xref.startxref[function] — private source atlib/pdf/src/xref.zig:63in nearest public ownertiny.pdf.xref
Complete caller list for object.Parser.parseValue
7 direct callers.
tiny.pdf.cmap.parseAlloc[function] atlib/pdf/src/cmap.zig:121tiny.pdf.content.extractTextAppend[function] atlib/pdf/src/content.zig:12lib.pdf.src.document.ObjectAccess.parseObject[method] — private source atlib/pdf/src/document.zig:315in nearest public ownerlib.pdf.src.documentlib.pdf.src.object.Parser.parseArray[method] — private source atlib/pdf/src/object.zig:180in nearest public ownertiny.pdf.objectlib.pdf.src.object.Parser.parseDict[method] — private source atlib/pdf/src/object.zig:195in nearest public ownertiny.pdf.objectlib.pdf.src.object.test_parser_reads_scalar_values_names_strings_and_references[function] — test source atlib/pdf/src/object.zig:368in nearest public ownertiny.pdf.objectlib.pdf.src.xref.parseStreamSection[function] — private source atlib/pdf/src/xref.zig:170in nearest public ownertiny.pdf.xref
Complete call list for object.Parser.parseValue
8 direct calls.
lib.pdf.src.object.Parser.parseArray[method] — private source atlib/pdf/src/object.zig:180in nearest public ownertiny.pdf.objectlib.pdf.src.object.Parser.parseDict[method] — private source atlib/pdf/src/object.zig:195in nearest public ownertiny.pdf.objectlib.pdf.src.object.Parser.parseHexString[method] — private source atlib/pdf/src/object.zig:170in nearest public ownertiny.pdf.objectlib.pdf.src.object.Parser.parseKeywordValue[method] — private source atlib/pdf/src/object.zig:213in nearest public ownertiny.pdf.objectlib.pdf.src.object.Parser.parseLiteralString[method] — private source atlib/pdf/src/object.zig:145in nearest public ownertiny.pdf.objectlib.pdf.src.object.Parser.parseName[method] — private source atlib/pdf/src/object.zig:137in nearest public ownertiny.pdf.objectlib.pdf.src.object.Parser.parseNumberOrReference[method] — private source atlib/pdf/src/object.zig:229in nearest public ownertiny.pdf.objecttiny.pdf.object.Parser.skipWhitespace[method] atlib/pdf/src/object.zig:69
Complete caller list for object.Parser.skipWhitespace
11 direct callers.
tiny.pdf.cmap.parseAlloc[function] atlib/pdf/src/cmap.zig:121tiny.pdf.content.extractTextAppend[function] atlib/pdf/src/content.zig:12tiny.pdf.object.Parser.atKeyword[method] atlib/pdf/src/object.zig:112tiny.pdf.object.Parser.expectKeyword[method] atlib/pdf/src/object.zig:105lib.pdf.src.object.Parser.parseArray[method] — private source atlib/pdf/src/object.zig:180in nearest public ownertiny.pdf.objectlib.pdf.src.object.Parser.parseDict[method] — private source atlib/pdf/src/object.zig:195in nearest public ownertiny.pdf.objectlib.pdf.src.object.Parser.parseNumberToken[method] — private source atlib/pdf/src/object.zig:246in nearest public ownertiny.pdf.objecttiny.pdf.object.Parser.parseUnsigned[method] atlib/pdf/src/object.zig:129tiny.pdf.object.Parser.parseValue[method] atlib/pdf/src/object.zig:86lib.pdf.src.object.Parser.tryReferenceSuffix[method] — private source atlib/pdf/src/object.zig:271in nearest public ownertiny.pdf.objectlib.pdf.src.object.test_parser_reads_scalar_values_names_strings_and_references[function] — test source atlib/pdf/src/object.zig:368in nearest public ownertiny.pdf.object
Audit
| Definitions | 17 |
|---|---|
| Public names | 17 |
| Members | 15 |
| Version | 26.7.0 |
| Revision | daab053ee433 |