Files
pdf-odin-llm/error.odin
T

225 lines
9.8 KiB
Odin
Raw Normal View History

2026-09-25 23:32:29 +02:00
package pdf
import "core:fmt"
// Error is the single error type the library reports.
//
// Real PDFs are frequently malformed, and a reader that refuses a file every
// other viewer opens is not useful. So most of these are only returned when
// recovery has already been attempted and failed; a file that merely has a
// broken cross-reference table, a wrong /Length, or a truncated stream opens
// with no error at all. Consult `Document.repairs` to see what was worked
// around.
Error :: enum u32 {
None = 0,
// -- I/O and file structure -------------------------------------------
Cant_Open_File,
Read_Failed,
Not_A_PDF, // no %PDF- header and no recoverable objects
Truncated, // file ends inside a construct that must complete
No_Xref, // no xref and object reconstruction found nothing
Bad_Xref,
Bad_Trailer,
No_Catalog,
No_Pages,
Page_Out_Of_Range,
Object_Not_Found,
Cyclic_Reference,
Recursion_Limit, // nesting deeper than the library will follow
Object_Limit, // file claims more objects than we will allocate
// -- Lexing and object parsing ----------------------------------------
Syntax_Error,
Unexpected_Token,
Bad_Number,
Bad_Name,
Bad_String,
Bad_Dict,
Bad_Array,
Bad_Stream,
Type_Mismatch, // object was not the type the caller asked for
// -- Filters ----------------------------------------------------------
Unknown_Filter,
Filter_Failed,
Bad_Predictor,
Bad_Flate_Data,
Bad_Lzw_Data,
Bad_Ascii85_Data,
Bad_Ascii_Hex_Data,
Bad_Run_Length_Data,
Bad_Ccitt_Data,
Bad_Jpeg_Data,
Bad_Jbig2_Data,
Unsupported_Jpx, // JPEG 2000 is passed through, never decoded
// -- Encryption -------------------------------------------------------
Encrypted, // needs a password the caller has not supplied
Wrong_Password,
Unsupported_Encryption,
Bad_Encrypt_Dict,
// -- Content, colour and function -------------------------------------
Bad_Content_Stream,
Bad_Colorspace,
Bad_Function,
Bad_Shading,
Bad_Pattern,
Bad_Image,
Unsupported_Image,
// -- Fonts ------------------------------------------------------------
Bad_Font,
Bad_Cmap,
Bad_Cff,
Bad_TrueType,
Bad_Type1,
No_Glyph,
// -- Rendering --------------------------------------------------------
Bitmap_Too_Large,
Out_Of_Memory,
// -- Caller error -----------------------------------------------------
Invalid_Argument,
Not_Implemented,
}
// A Repair records a malformation that was worked around while reading, so a
// caller can tell "opened cleanly" from "opened, but this file is damaged".
// Repairs never fail an open; they are recorded on the Document and can be
// ignored entirely.
Repair :: struct {
kind: Repair_Kind,
// Byte offset in the file the problem was noticed at, or 0 if not tied to
// a position.
offset: i64,
// Object the repair applies to, or {0,0}.
ref: Ref,
}
Repair_Kind :: enum u32 {
None = 0,
Rebuilt_Xref, // xref was unusable; objects found by scanning
Bad_Startxref, // startxref pointed somewhere that isn't an xref
Xref_Entry_Mismatch, // xref said obj N, the file had obj M there
Missing_Object, // a reference resolved to nothing; read as null
Recursion_Limit_Hit, // object nesting too deep to follow; read as null
Bad_Stream_Length, // /Length wrong; endstream located by scanning
Missing_Endstream,
Unterminated_String,
Unterminated_Dict,
Unterminated_Array,
Bad_Filter_Data, // a stream failed to decode; treated as empty
Missing_Root, // catalog located by scanning for /Type /Catalog
Missing_Page_Node,
Cyclic_Page_Tree,
Bad_Page_Object,
Bad_Font_Object,
Bad_Resource,
Unbalanced_Q, // content stream had stray q/Q
Content_Syntax, // operator soup skipped inside a content stream
}
// error_string returns a short human-readable description. Suitable for a log
// line or a UI message; not stable enough to parse.
error_string :: proc(err: Error) -> string {
switch err {
case .None: return "no error"
case .Cant_Open_File: return "cannot open file"
case .Read_Failed: return "read failed"
case .Not_A_PDF: return "not a PDF file"
case .Truncated: return "file is truncated"
case .No_Xref: return "no usable cross-reference information"
case .Bad_Xref: return "malformed cross-reference table"
case .Bad_Trailer: return "malformed trailer"
case .No_Catalog: return "document has no catalog"
case .No_Pages: return "document has no pages"
case .Page_Out_Of_Range: return "page index out of range"
case .Object_Not_Found: return "object not found"
case .Cyclic_Reference: return "cyclic object reference"
case .Recursion_Limit: return "nesting too deep"
case .Object_Limit: return "too many objects"
case .Syntax_Error: return "syntax error"
case .Unexpected_Token: return "unexpected token"
case .Bad_Number: return "malformed number"
case .Bad_Name: return "malformed name"
case .Bad_String: return "malformed string"
case .Bad_Dict: return "malformed dictionary"
case .Bad_Array: return "malformed array"
case .Bad_Stream: return "malformed stream"
case .Type_Mismatch: return "object has the wrong type"
case .Unknown_Filter: return "unknown stream filter"
case .Filter_Failed: return "stream filter failed"
case .Bad_Predictor: return "malformed predictor parameters"
case .Bad_Flate_Data: return "malformed Flate data"
case .Bad_Lzw_Data: return "malformed LZW data"
case .Bad_Ascii85_Data: return "malformed ASCII85 data"
case .Bad_Ascii_Hex_Data: return "malformed ASCIIHex data"
case .Bad_Run_Length_Data: return "malformed RunLength data"
case .Bad_Ccitt_Data: return "malformed CCITT fax data"
case .Bad_Jpeg_Data: return "malformed JPEG data"
case .Bad_Jbig2_Data: return "malformed JBIG2 data"
case .Unsupported_Jpx: return "JPEG 2000 images are not decoded"
case .Encrypted: return "document is encrypted; a password is required"
case .Wrong_Password: return "wrong password"
case .Unsupported_Encryption: return "unsupported encryption scheme"
case .Bad_Encrypt_Dict: return "malformed encryption dictionary"
case .Bad_Content_Stream: return "malformed content stream"
case .Bad_Colorspace: return "malformed colour space"
case .Bad_Function: return "malformed function"
case .Bad_Shading: return "malformed shading"
case .Bad_Pattern: return "malformed pattern"
case .Bad_Image: return "malformed image"
case .Unsupported_Image: return "unsupported image format"
case .Bad_Font: return "malformed font"
case .Bad_Cmap: return "malformed CMap"
case .Bad_Cff: return "malformed CFF font program"
case .Bad_TrueType: return "malformed TrueType font program"
case .Bad_Type1: return "malformed Type1 font program"
case .No_Glyph: return "glyph not present in font"
case .Bitmap_Too_Large: return "requested bitmap is too large"
case .Out_Of_Memory: return "out of memory"
case .Invalid_Argument: return "invalid argument"
case .Not_Implemented: return "not implemented"
}
return "unknown error"
}
repair_string :: proc(kind: Repair_Kind) -> string {
switch kind {
case .None: return "none"
case .Rebuilt_Xref: return "cross-reference table rebuilt by scanning"
case .Bad_Startxref: return "startxref did not point at an xref"
case .Xref_Entry_Mismatch: return "cross-reference entry pointed at the wrong object"
case .Missing_Object: return "reference to a missing object read as null"
case .Recursion_Limit_Hit: return "object nesting too deep to follow"
case .Bad_Stream_Length: return "stream /Length was wrong"
case .Missing_Endstream: return "stream had no endstream keyword"
case .Unterminated_String: return "unterminated string"
case .Unterminated_Dict: return "unterminated dictionary"
case .Unterminated_Array: return "unterminated array"
case .Bad_Filter_Data: return "stream failed to decode"
case .Missing_Root: return "catalog located by scanning"
case .Missing_Page_Node: return "page tree node missing"
case .Cyclic_Page_Tree: return "cyclic page tree"
case .Bad_Page_Object: return "malformed page object"
case .Bad_Font_Object: return "malformed font object"
case .Bad_Resource: return "malformed resource"
case .Unbalanced_Q: return "unbalanced q/Q in content stream"
case .Content_Syntax: return "unparsable content stream fragment skipped"
}
return "unknown repair"
}
// repair_describe formats a repair with its position, for logs.
// The returned string is allocated with `allocator`.
repair_describe :: proc(r: Repair, allocator := context.allocator) -> string {
if r.ref.num != 0 {
return fmt.aprintf("%s (object %d %d R, offset %d)",
repair_string(r.kind), r.ref.num, r.ref.gen, r.offset, allocator = allocator)
}
return fmt.aprintf("%s (offset %d)", repair_string(r.kind), r.offset, allocator = allocator)
}