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) }