initial commit
This commit is contained in:
+224
@@ -0,0 +1,224 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user