ISO 32000 conformance
pdfboss keeps a ledger of ISO 32000 clauses in iso32000/Iso32000/Catalogue/, one row per
clause with the status pdfboss claims: implemented, incomplete, not implemented or out of
scope. Clause numbers follow ISO 32000-1:2008; where ISO 32000-2:2020 numbers a clause
differently the row carries both.
The ledger is checked by Lean 4 on every push (make iso32000-gate). Six theorems in
iso32000/Iso32000/Gate.lean establish that every row is unique, that every clause of chapters
7 to 14 and the normative annexes has a row speaking to it, that every implemented row is cited
by both implementation code and a test in the source tree, that every incomplete row is cited by
implementation code and explains what is missing, that every other row explains itself, and that
no first-edition citation names a clause the standard does not have. They are decided by Lean's
compiled evaluator (native_decide), so they trust the Lean compiler as well as its kernel; the
decode-after-encode theorems of the reference filter decoders in iso32000/Iso32000/Reference/,
whose vectors the Rust tests replay, are checked by the kernel alone. Citation counts below are
the number of source comments naming the clause or a sub-clause of it, in implementation code
and in tests.
This page is generated by make iso32000-report. Edit the ledger, not the page.
| Status | Rows |
|---|---|
| implemented | 277 |
| incomplete | 81 |
| not implemented | 42 |
| out of scope | 74 |
7 Syntax
| Clause | Feature | Status | Code citations | Test citations | Note |
|---|---|---|---|---|---|
| 7.2.2 | Character Set | implemented | 7 | 2 | Table 1 white-space bytes, Table 2 delimiters and regular bytes classify every token; the writer escapes delimiter bytes in names (crates/pdfboss-write/src/ser.rs write_name). |
| 7.2.3 | Comments | implemented | 1 | 2 | A percent sign skips to CR or LF everywhere tokens are read; the header and startxref comments are read by their own scanners (document.rs, xref.rs). |
| 7.3.2 | Boolean Objects | implemented | 2 | 2 | The true and false keywords parse to Object::Bool; the writer serializes them (crates/pdfboss-write/src/ser.rs, test ser.rs scalars_serialize_exactly). |
| 7.3.3 | Numeric Objects | implemented | 2 | 4 | Integers read as i64 and reals as f64 with sign and leading-dot forms, malformed runs cleaned leniently; the writer emits plain decimals without exponents (crates/pdfboss-write/src/ser.rs, test ser.rs real_pins_exact_bytes). |
| 7.3.4 | String Objects | implemented | 5 | 9 | Both string forms decode to raw bytes in Object::String; the writer picks the literal or hex form from the bytes (crates/pdfboss-write/src/ser.rs write_string, tests ser.rs strings_use_literal_form_with_escapes and ser.rs strings_fall_back_to_hex_form). |
| 7.3.4.2 | Literal Strings | implemented | 1 | 4 | Balanced parentheses, all Table 3 escapes, one to three digit octal codes, backslash line continuation and EOL normalization to LF are covered, and an unknown escape drops the backslash as the clause requires. |
| 7.3.4.3 | Hexadecimal Strings | implemented | 2 | 2 | White space inside the brackets is skipped and an odd final digit is read as the high nibble; non-hex bytes are skipped leniently (the implementation comment at lexer.rs cites the clause, the tests do not). |
| 7.3.5 | Name Objects | implemented | 1 | 1 | Number-sign escapes decode, the empty name is accepted and a bad escape is kept literally; the writer escapes bytes outside 0x21-0x7E and rejects NUL (crates/pdfboss-write/src/ser.rs and ser.rs, tests ser.rs names_escape_irregular_bytes and ser.rs names_with_nul_bytes_are_rejected). |
| 7.3.6 | Array Objects | implemented | 1 | 3 | Heterogeneous nested arrays parse with a nesting cap and an unterminated array ends leniently at end of input; the writer separates items with single spaces (crates/pdfboss-write/src/ser.rs arrays_separate_items_with_single_spaces). |
| 7.3.7 | Dictionary Objects | implemented | 2 | 3 | Name keys with any value parse, a non-name key is dropped and a value-less key maps to null; a null-valued entry is stored rather than treated as absent and a repeated key keeps the last value, while the writer sorts keys bytewise (crates/pdfboss-write/src/ser.rs dicts_sort_keys_bytewise). |
| 7.3.8 | Stream Objects | incomplete | 11 | 11 | The dictionary plus stream/endstream body, /Length, /Filter and /DecodeParms are handled, but the external-file entries (/F, /FFilter, /FDecodeParms) and /DL are never read; the writer refuses a stream nested inside another object (crates/pdfboss-write/src/error.rs, test ser.rs nested_streams_are_errors). |
| 7.3.8.1 | General | incomplete | 5 | 4 | Of the Table 5 entries only /Length, /Filter and /DecodeParms are consumed, so stream data kept in an external file (/F with /FFilter and /FDecodeParms) is never fetched and /DL is ignored. |
| 7.3.8.2 | Stream Extent | implemented | 1 | 5 | The EOL after the stream keyword (CRLF or LF, a lone CR tolerated) is skipped, a direct or indirect /Length is verified against the following endstream, and a wrong or missing length falls back to a scan for endstream that trims one trailing EOL. |
| 7.3.9 | Null Object | implemented | 2 | 3 | The null keyword parses to Object::Null and a reference to a missing object resolves to null; a null /Filter reads as no filter. |
| 7.3.10 | Indirect Objects | implemented | 4 | 8 | Object headers, endobj (lenient when missing), the N G R lookahead and the rule that a reference to a missing object is null are all covered, with generation mismatches tolerated; the writer emits N G R and numbers objects densely (crates/pdfboss-write/src/ser.rs refs_serialize_as_num_gen_r). |
| 7.4.2 | ASCIIHexDecode Filter | implemented | 3 | 7 | Hex pairs with white space skipped, the > EOD marker and an odd final digit padded with zero decode, and the AHx abbreviation is accepted. |
| 7.4.3 | ASCII85Decode Filter | implemented | 3 | 7 | Five-character groups, the z shorthand, the |
| 7.4.4 | LZWDecode and FlateDecode Filters | implemented | 13 | 29 | Both filters decode with the Table 8 parameters and the PNG and TIFF predictors at every bit depth (7.4.4.1 to 7.4.4.4); the writer compresses streams with Flate (crates/pdfboss-write/src/writer.rs compress_into, test writer.rs compressed_stream_round_trips). |
| 7.4.4.1 | General | implemented | 2 | 5 | FlateDecode accepts zlib (RFC 1950) and raw deflate data, returns the decoded prefix of a truncated stream and caps output size; LZWDecode is assessed under 7.4.4.2. |
| 7.4.4.2 | Details of LZW Encoding | implemented | 2 | 5 | Codes grow from 9 to 12 bits, clear-table (256) and EOD (257) codes, the KwKwK case and both EarlyChange width schedules decode; corrupt or truncated data keeps the decoded prefix. |
| 7.4.4.3 | LZWDecode and FlateDecode Parameters | implemented | 2 | 7 | Table 8 parameters (/Predictor, /Colors, /BitsPerComponent, /Columns, /EarlyChange) are read with their defaults, /DecodeParms arrays stay aligned with the /Filter array and indirect values resolve. |
| 7.4.4.4 | LZW and Flate Predictor Functions | implemented | 3 | 9 | PNG predictors 10 to 15 (per-row filter byte, all five filter types, sub-byte samples) and TIFF predictor 2 at 1, 2, 4, 8 and 16 bits per component decode (crates/pdfboss-core/src/filters/predictor.rs tiff_horizontal: bytes in place, big-endian 16-bit components, packed sub-byte components per colour with a row's padding bits left as stored). Tests: predictor.rs tiff_horizontal_diff_round_trips, tiff_16bit_samples_add_big_endian, tiff_4bit_samples_add_within_packed_bytes, tiff_2bit_samples_add_per_colour, tiff_1bit_samples_add_modulo_two, png_every_row_type_round_trips and png_row_length_rounds_up_for_sub_byte_samples. |
| 7.4.5 | RunLengthDecode Filter | implemented | 2 | 6 | Literal runs (0-127), repeat runs (129-255) and the 128 EOD byte decode, a missing EOD is tolerated and output size is capped. |
| 7.4.6 | CCITTFaxDecode Filter | implemented | 10 | 17 | Group 4 (K<0), pure 1-D Group 3 (K=0) and mixed Group 3 (K>0) decode to packed 1-bit samples, reading /K, /Columns, /Rows, /EncodedByteAlign, /BlackIs1 and /EndOfLine with Table 11 defaults; /EndOfBlock and /DamagedRowsBeforeError are described in comments but never read, the row count being inferred from the data instead. |
| 7.4.7 | JBIG2Decode Filter | implemented | 2 | 8 | The PDF embedded-stream organization with /JBIG2Globals decodes through a cleanroom codec (generic, refinement, symbol/text, pattern/halftone regions and MMR) and 1 bits are inverted to DeviceGray ink as the clause requires; the filter must be last in the chain. |
| 7.4.8 | DCTDecode Filter | implemented | 6 | 11 | The filter chain passes JPEG bytes through (crates/pdfboss-core/src/filters/mod.rs decode_stream) and the image layer decodes them with the external jpeg-decoder 0.3 crate (crates/pdfboss-render/src/image.rs decode_jpeg: gray, RGB and CMYK layouts, baseline and progressive). The colour transform follows Table 13: the Adobe APP14 flag when present (adobe_transform), else /ColorTransform from the trailing filter's decode parameters (dct_color_transform), else the crate's default of YCbCr for three components and none for four (jpeg_color_transform). Four-component samples are ink values as stored, the crate's 255 - ink output undone, and /Decode applies per component. Two-component JPEGs, which the clause allows, are refused by the crate; and the image's /ColorSpace is not consulted for JPEG data, which takes the device conversion for its component count (a limitation of the colour space rows under 8.6, not of the filter). Tests: image.rs dct_color_transform_zero_keeps_three_components_as_stored, dct_adobe_marker_overrides_color_transform, dct_color_transform_one_reads_four_components_as_ycck, dct_cmyk_samples_are_direct_ink_values. |
| 7.4.9 | JPXDecode Filter | implemented | 16 | 15 | A cleanroom JPEG 2000 decoder handles the codestream, the colour space comes from the codestream when the dictionary has none, /Decode is ignored except for image masks, /SMaskInData 1 and 2 (image.rs) and Indexed over JPX are honoured, and the filter must be last in the chain. |
| 7.4.10 | Crypt Filter | implemented | 6 | 6 | The decryptor reads a stream's own /Crypt filter when it loads the object (crates/pdfboss-core/src/crypt.rs stream_crypt_filter): /Name Identity, or no /Name, leaves the data as stored, a name from /CF decrypts with that filter's method and the file key as is, as the clause asks, and a name matching no /CF entry leaves the data as stored; decode_stream then treats Crypt as a no-op (crates/pdfboss-core/src/filters/mod.rs). Tests: crypt.rs a_stream_crypt_filter_naming_identity_is_left_as_stored, a_stream_crypt_filter_without_a_name_means_identity and a_stream_crypt_filter_naming_a_cf_entry_uses_the_file_key_as_is; filters/mod.rs crypt_filter_leaves_data_as_stored and crypt_filter_first_in_a_chain_is_a_no_op. |
| 7.5.2 | File Header | implemented | 2 | 2 | The %PDF-M.m header is searched in the first 1 KiB and defaults to 1.4 when absent, without shifting xref offsets for a header not at byte 0 (the recovery scan covers that case); the writer emits the header and a binary comment line (crates/pdfboss-write/src/writer.rs write_header, test writer.rs table_mode_minimal_document_loads). |
| 7.5.3 | File Body | implemented | 2 | 5 | Indirect objects are read at their cross-reference offsets, and when the table is unusable a whole-file scan for N G obj headers rebuilds it with the last occurrence winning. |
| 7.5.4 | Cross-Reference Table | implemented | 5 | 4 | Subsection headers and n/f entries are read token-wise so 19 and 21 byte lines load as well as 20 byte ones, though the free-entry linked list is not tracked; the writer emits exact 20-byte entries (crates/pdfboss-write/src/writer.rs emit_table and writer.rs table_offset, test writer.rs table_offsets_past_ten_digits_are_rejected). |
| 7.5.5 | File Trailer | implemented | 3 | 5 | The startxref offset is found in the last 1 KiB widening to 64 KiB and the trailer's /Size, /Root, /Info, /ID and /Encrypt are consumed, with %%EOF itself not required; the writer emits the trailer, startxref and %%EOF (crates/pdfboss-write/src/writer.rs trailer_dict). |
| 7.5.6 | Incremental Updates | implemented | 4 | 3 | The newest section wins and /Prev is followed with a visited-offset loop guard; the writer appends update sections chained by /Prev (crates/pdfboss-write/src/update.rs cites the clause; tests crates/pdfboss-write/tests/update_append.rs two_appends_chain and tests/test_update.py test_update_appends_and_preserves_bytes). |
| 7.5.7 | Object Streams | implemented | 6 | 3 | The /N and /First header pairs are parsed once and members are addressed by index and never decrypted separately, while /Extends is not needed for reading and not read; the writer packs objects into streams (crates/pdfboss-write/src/writer.rs build_objstm, test writer.rs object_streams_pack_and_resolve). |
| 7.5.8 | Cross-Reference Streams | implemented | 9 | 11 | Cross-reference streams are decoded through the ordinary filter chain and their dictionary serves as the trailer; the writer emits them by default (crates/pdfboss-write/src/writer.rs emit_stream, test writer.rs stream_mode_minimal_document_loads). |
| 7.5.8.2 | Cross-Reference Stream Dictionary | implemented | 1 | 2 | /W field widths of 0 to 8 bytes, /Size, /Index (defaulting to [0 Size]) and /Prev are read; /Type /XRef is not checked. |
| 7.5.8.3 | Cross-Reference Stream Data | implemented | 2 | 3 | Type 0, 1 and 2 entries are decoded as big-endian fields per /W, a zero-width type field defaults to type 1, unknown types read as free and truncated data ends the table leniently. |
| 7.5.8.4 | Compatibility with Applications That Do Not Support Compressed Reference Streams | implemented | 4 | 3 | A classic trailer's /XRefStm section merges ahead of its own table and before /Prev is followed (the fixture crates/pdfboss-testkit/src/lib.rs hybrid_doc documents the clause); the writer appends a classic table to a hybrid base (crates/pdfboss-write/tests/update_append.rs hybrid_base_appends_a_classic_table). |
| 7.6.2 | General Encryption Algorithm | implemented | 4 | 6 | Algorithm 1 per-object keys (with the sAlT suffix for AES-128) and the direct AES-256 file key decrypt strings and stream bodies, object-stream members are left alone and /Encrypt and /ID are never decrypted; the writer encrypts the same way with AES-256 (crates/pdfboss-write/src/writer.rs new_encrypted; tests crates/pdfboss-write/tests/encrypt.rs encrypt_dict_strings_are_never_encrypted and encrypt.rs xref_stream_is_never_encrypted). |
| 7.6.3 | Standard Security Handler | incomplete | 15 | 25 | V1/V2 RC4, V4 with the V2 or AESV2 crypt filter and V5 R5/R6 AES-256 files open under user or owner passwords, but a V4 file whose stream crypt filter is /Identity is refused (see 7.6.3.2 and 7.6.5); the writer produces AES-256 R6 files (crates/pdfboss-core/src/crypt.rs aes256_encrypt_dict). |
| 7.6.3.2 | Standard Encryption Dictionary | implemented | 11 | 10 | /Filter /Standard, /V, /R, /Length, /P, /O, /U, /OE, /UE, /Perms, /EncryptMetadata, /CF, /StmF, /StrF and /EFF are read (crates/pdfboss-core/src/crypt.rs from_standard_with_password and CryptFilters::parse; tests crypt.rs string_filter_is_chosen_independently_of_the_stream_filter and embedded_file_filter_applies_to_embedded_file_streams); /SubFilter names the formats of public-key handlers (adbe.pkcs7.*), so a reader with only the Standard handler has nothing to read there. |
| 7.6.3.3 | Encryption Key Algorithm | implemented | 11 | 5 | Algorithm 2 with the 32-byte pad, the 50-round MD5 for R3 and up and the R4 EncryptMetadata-false suffix derives the RC4/AES-128 key, and Algorithms 2.A and 2.B recover the AES-256 key for R5 and R6; every RC4 fixture is /V 2 /R 3 or /V 4 /R 4, so the R2 40-bit branch (crypt.rs) has no test. |
| 7.6.3.4 | Password Algorithms | implemented | 9 | 6 | The user password is verified through /U (Algorithms 4 and 5), the owner password recovers the padded user password from /O (Algorithm 7) and the R5/R6 checks use /O, /U, /OE, /UE and /Perms, while Algorithm 3 (computing /O) lives only in fixtures (crates/pdfboss-testkit/src/crypt.rs) and the R2 branch (crypt.rs) is untested; the writer computes /O, /U, /OE, /UE and /Perms for R6 (crates/pdfboss-core/src/crypt.rs, test crates/pdfboss-write/tests/encrypt.rs owner_password_also_opens). |
| 7.6.4 | Public-Key Security Handlers | out of scope | 12 | 0 | Only /Filter /Standard is accepted (crypt.rs) and every other handler fails with Error::Encrypted, because certificate and private-key handling is deliberately left out of pdfboss. |
| 7.6.5 | Crypt Filters | implemented | 7 | 13 | The /CF filters are read by name with /CFM V2, AESV2 or AESV3, /StmF, /StrF and /EFF pick among them with the standard Identity filter as the default and as a pass-through (crates/pdfboss-core/src/crypt.rs CryptFilters::parse), the crypt filter /Length sets the V4 key size (v4_key_length), /CFM None or an unknown method refuses the file as Error::Encrypted, and a stream's own /Crypt filter overrides /StmF (see 7.4.10); /EFF is recognised through /Type /EmbeddedFile, which Table 45 makes optional, so an untyped embedded file stream takes /StmF. /AuthEvent is not consulted: the password arrives when the document opens, Table 25 says readers ignore the entry for the /StmF and /StrF filters, and the Standard handler has the one file key for /EFF too. Tests: crypt.rs identity_stream_filter_leaves_streams_as_stored, string_filter_is_chosen_independently_of_the_stream_filter, embedded_file_filter_applies_to_embedded_file_streams, crypt_filter_length_sets_the_key_size and aesv3_identity_string_filter_leaves_strings_as_stored. |
| 7.7.2 | Document Catalog | incomplete | 6 | 3 | /Pages, /OCProperties, /StructTreeRoot, /Names (crates/pdfboss-core/src/names.rs), /Dests (destination.rs), /Outlines (outline.rs) and /PageLabels (page_label.rs) are read from the catalog; /Version never overrides the header and /Metadata, /MarkInfo, /Lang, /AcroForm, /PageMode and /PageLayout are not consumed, while the writer emits /Pages, /Outlines, /Metadata, /Names, /PageLabels and viewer entries (crates/pdfboss-write/src/pdf.rs and pdf.rs). |
| 7.7.3 | Page Tree | incomplete | 19 | 18 | The tree walk, inheritance and page count are handled, with page-level gaps listed under 7.7.3.3; the writer builds page trees (crates/pdfboss-write/src/pdf.rs, crates/pdfboss-write/src/assemble.rs cites the clause). |
| 7.7.3.2 | Page Tree Nodes | implemented | 2 | 6 | /Kids are walked depth-first in document order, /Count is used when plausible, /Type /Page ends descent and cycles and depth are capped; /Parent is not needed for reading. |
| 7.7.3.3 | Page Objects | incomplete | 9 | 6 | /Contents (single stream or array joined by newlines with nulls skipped), the five boxes with Table 30 defaults, /Rotate, /Resources, /Annots and /StructParents are consumed; /UserUnit and the page-level /Group transparency dictionary are never read, and the viewer entries (/Dur, /Trans, /AA, /Tabs) are ignored. |
| 7.7.3.4 | Inheritance of Page Attributes | implemented | 5 | 4 | /Resources, /MediaBox, /CropBox and /Rotate flow down the /Pages nodes with the nearest ancestor winning and the non-inheritable boxes staying on the leaf (the fixture at parity.rs cites the clause, the test does not). |
| 7.7.4 | Name Dictionary | implemented | 7 | 3 | crates/pdfboss-core/src/names.rs: NameTree names the ten trees of Table 31 by their dictionary key, name_tree_root_with reads the catalog /Names entry and the tree's root (direct or indirect, anything that is not a dictionary reads as absent), named_with resolves the object one name designates through tree.rs lookup, names_with lists a tree's pairs; Document::named and Document::names on the sync side, AsyncDocument::named and AsyncDocument::names on the async side (tests names.rs named_objects_come_from_the_catalog_name_dictionary, documents_without_a_name_dictionary_have_no_names, every_table_31_tree_has_its_dictionary_key; crates/pdfboss-aio/tests/parity.rs names fixture). The writer emits /Names /EmbeddedFiles (crates/pdfboss-write/src/pdf.rs, test crates/pdfboss-write/tests/roundtrip.rs attachments_sort_by_name_and_round_trip_bytes). Consumers of the individual trees (destinations 12.3.2.3, embedded files 7.11.4) are their own rows. |
| 7.8.2 | Content Streams | implemented | 2 | 5 | Operands followed by an operator are tokenized with unknown operators skipped, inline images with their abbreviations are parsed, and a /Contents array is concatenated with a newline between parts; the writer produces content streams (crates/pdfboss-write/src/canvas.rs and crates/pdfboss-write/src/content.rs). |
| 7.8.3 | Resource Dictionaries | implemented | 3 | 4 | Category/name lookups walk the resource chain innermost first (form, then caller, then page, with a page missing /Resources inheriting from /Pages), and text extraction and rendering apply the same rule. |
| 7.9.2 | String Object Types | implemented | 9 | 12 | Text strings decode from UTF-16BE, UTF-8 or PDFDocEncoding by their byte-order mark (crates/pdfboss-core/src/object.rs decode_text_string) and byte strings stay raw; the writer encodes text strings as ASCII or UTF-16BE (crates/pdfboss-write/src/pdf.rs text_string cites 7.9.2.2). |
| 7.9.2.2 | Text String Type | implemented | 3 | 3 | UTF-16BE with a BOM and the PDF 2.0 UTF-8 BOM form decode with lone surrogates and odd trailing bytes replaced by U+FFFD, while language escape sequences inside UTF-16 text are kept unchanged; the writer writes ASCII plainly and everything else as UTF-16BE with a BOM (crates/pdfboss-write/src/pdf.rs). |
| 7.9.2.3 | PDFDocEncoded String Type | implemented | 2 | 5 | A string without a BOM decodes through the PDFDocEncoding table (crates/pdfboss-core/src/object.rs pdf_doc_char): the accents at 0x18-0x1F, the punctuation and ligatures at 0x80-0x9E and the Euro at 0xA0 map to their characters, the undefined codes become U+FFFD, and every other code agrees with Latin-1 (tests object.rs decode_pdf_doc_encoding_accents_at_0x18, decode_pdf_doc_encoding_punctuation_at_0x80, decode_pdf_doc_encoding_undefined_codes_are_replaced). |
| 7.9.2.4 | Byte String Type | implemented | 2 | 2 | Strings are kept as raw bytes in Object::String and exposed through as_str_bytes with no character interpretation, which is what /ID, /O and /U consumers rely on. |
| 7.9.3 | Text Streams | not implemented | 0 | 0 | No consumer reads a text stream (used by JavaScript and similar viewer features); decode_text_string is only applied to string objects. |
| 7.9.4 | Dates | implemented | 11 | 8 | One Date type in the core crate (crates/pdfboss-core/src/date.rs, re-exported by pdfboss-write) parses the D:YYYYMMDDHHmmSSOHH'mm form with the clause's defaults and field ranges, taking the deviations producers write (a trailing apostrophe, Z followed by offset digits, an offset without apostrophes, no D: prefix) and refusing out-of-range fields; it formats back to the D: form and to ISO 8601. Metadata keeps /CreationDate and /ModDate as written and reads them through creation_date_parsed and mod_date_parsed (document.rs); the CLI info command prints a parsed date as ISO 8601. The Python metadata dict still carries the strings as written. Tests: date.rs parse_pdf_the_clause_example, parse_pdf_partial_fields_take_their_defaults, parse_pdf_accepts_the_common_deviations, parse_pdf_refuses_out_of_range_fields; document.rs metadata_dates_parse_per_the_date_clause. |
| 7.9.5 | Rectangles | implemented | 2 | 3 | Four-number arrays with indirect elements resolved are normalized so that any two opposite corners define the rectangle. |
| 7.9.6 | Name Trees | implemented | 5 | 4 | crates/pdfboss-core/src/tree.rs lookup and entries walk a tree of any depth: TreeKey for byte-string keys (Vec |
| 7.9.7 | Number Trees | implemented | 5 | 3 | crates/pdfboss-core/src/tree.rs lookup and entries with integer keys (TreeKey for i64): /Nums leaves, /Kids subtrees and /Limits under a visited set and a node budget, used by the structure /ParentTree lookup (structure.rs parent_array; tests tree.rs number_lookup_reads_nums_leaves, structure.rs parent_tree_kids_and_limits_are_descended); the writer emits a /PageLabels /Nums tree (crates/pdfboss-write/src/pdf.rs, test crates/pdfboss-write/tests/roundtrip.rs page_labels_resolve_roman_front_matter_and_offset_decimal). |
| 7.10.2 | Type 0 (Sampled) Functions | implemented | 2 | 7 | /Size, /BitsPerSample of 1 to 32, /Encode and /Decode with their defaults and multilinear interpolation between the 2^m nearest samples are covered, and /Order 3 is not honoured (the clause permits linear interpolation). |
| 7.10.3 | Type 2 (Exponential Interpolation) Functions | implemented | 2 | 3 | C0 + x^N (C1 - C0) with the C0, C1 and N defaults evaluates for shadings and tint transforms, the first input only as the clause defines. |
| 7.10.4 | Type 3 (Stitching) Functions | implemented | 2 | 2 | /Functions, /Bounds (k-1 of them, checked) and /Encode with its default remap the input into the selected subfunction, with subfunctions loaded under a MAX_FUNCTIONS budget. |
| 7.10.5 | Type 4 (PostScript Calculator) Functions | implemented | 4 | 11 | The full Table 42 operator set (arithmetic, relational, boolean, bitwise, stack and if/ifelse compiled to jumps) is parsed from the brace-delimited program with comments skipped, and the result feeds shadings and Separation/DeviceN tint transforms. |
| 7.10.5.2 | Errors in Type 4 Functions | implemented | 2 | 4 | Syntax errors reject the function at load time, and runtime failures (stack underflow or overflow, type mismatch, division by zero and other domain errors, runaway programs) clamp every output to the bottom of /Range rather than aborting the paint. |
| 7.11.2 | File Specification Strings | implemented | 3 | 2 | crates/pdfboss-core/src/embedded_file.rs spec_components splits a file specification string on every solidus not preceded by a reverse solidus and removes the reverse solidi that escape a solidus or each other, an absolute specification starting with an empty component (test embedded_file.rs file_specification_strings_split_on_unescaped_solidus); FileSpec keeps /F as stored and /UF decoded, and names the file from /UF then /F. The components are handed on as bytes without conversion to a platform path, as 7.11.2.1 asks; the writer stores the attachment name as both /F and /UF (crates/pdfboss-write/src/pdf.rs embedded_files_dict). |
| 7.11.3 | File Specification Dictionaries | implemented | 4 | 2 | crates/pdfboss-core/src/embedded_file.rs file_spec_with reads a /Type /Filespec dictionary (/F, /UF, /Desc, /FS, /V, and the /EF stream chosen from /UF, /F or the obsolescent /DOS, /Mac and /Unix) or a bare string as the /F of a specification; /ID, /RF and /CI are not read (tests embedded_file.rs a_file_specification_names_its_file_from_uf_then_f_then_the_legacy_keys, embedded_files_come_from_the_name_tree). The writer emits /Type /Filespec with /F, /UF, /Desc and /EF (crates/pdfboss-write/src/pdf.rs, test crates/pdfboss-write/tests/roundtrip.rs attachments_sort_by_name_and_round_trip_bytes). |
| 7.11.4 | Embedded File Streams | implemented | 8 | 3 | crates/pdfboss-core/src/embedded_file.rs embedded_files_with lists the catalog's /EmbeddedFiles name tree in tree order through names.rs, each EmbeddedFile carrying the decoded name, its FileSpec and the stream's /Subtype (Table 45) and /Params /Size, /CreationDate, /ModDate and /CheckSum (Table 46); embedded_file_data_with decodes the stream through the source's filter chain, MissingKey(EF) when the specification embeds nothing; Document::embedded_files and embedded_file_data, AsyncDocument twins (tests embedded_file.rs embedded_files_come_from_the_name_tree, documents_without_embedded_files_have_none; crates/pdfboss-aio/tests/parity.rs names fixture). Related files arrays (7.11.4.2) and the Mac subdictionary (Table 47) are not read; file attachment annotations belong to 12.5.6.15. The writer emits /Type /EmbeddedFile streams with /Subtype and /Params /Size and /ModDate but no related-files array (crates/pdfboss-write/src/pdf.rs embedded_files_dict; tests crates/pdfboss-write/tests/roundtrip.rs attachment_params_size_and_conditional_moddate and tests/test_write.py test_multiple_attachments_and_page_labels_compose). The CLI and Python bindings do not list attachments yet. |
| 7.11.5 | URL Specifications | implemented | 2 | 1 | FileSpec::url returns the /F of a specification whose /FS is URL, read as the 7-bit ASCII the clause requires (crates/pdfboss-core/src/embedded_file.rs; test url_specifications_name_a_url), so a URL file attachment or action target reaches Python as FileSpec.url; the writer does not emit URL specifications (crates/pdfboss-write/src/pdf.rs writes a /URI action, which belongs to chapter 12). |
| 7.11.6 | Collection Items | not implemented | 0 | 0 | No /CI collection item dictionaries are read or written. |
| 7.11.7 | Maintenance of File Specifications | out of scope | 0 | 0 | This clause describes how applications that move or rename files keep specifications valid, a file-management duty pdfboss has no role in. |
| 7.12.2 | Developer Extensions Dictionary | implemented | 1 | 2 | crates/pdfboss-core/src/extension.rs extensions_with reads the catalog's /Extensions dictionary into one DeveloperExtension per developer prefix (Table 50: the prefix is the key, /BaseVersion a name, /ExtensionLevel an integer; the dictionary's own /Type entry and an entry missing either value are skipped, indirect objects followed although the clause asks for direct ones, the result sorted by prefix since a dictionary keeps no order); Document::extensions and AsyncDocument::extensions expose it and the CLI's info command prints each extension after the version (tests extension.rs the_catalog_extensions_are_read_sorted_by_prefix with the clause's EXAMPLE 3, crates/pdfboss-aio/tests/parity.rs, crates/pdfboss-cli/src/main.rs info_text_lists_developer_extensions). Not exposed to Python; pdfboss-write emits no extensions dictionary. |
| 7.12.3 | BaseVersion | implemented | 2 | 2 | DeveloperExtension::base_version keeps the /BaseVersion name as written and DeveloperExtension::base_version_numbers reads it as the two integers around a period the clause prescribes, not as a real number, None for anything else (test extension.rs base_versions_are_two_integers). The clause's requirement that the base version not exceed the header or catalog version is not checked. |
| 7.12.4 | ExtensionLevel | implemented | 1 | 1 | DeveloperExtension::extension_level carries the /ExtensionLevel integer of each developer extensions dictionary, read with 7.12.2 (test extension.rs the_catalog_extensions_are_read_sorted_by_prefix: the levels 3 and 1002 of the clause's EXAMPLE 3). Ordering the levels over time is the developer's business and nothing pdfboss checks. |
8 Graphics
| Clause | Feature | Status | Code citations | Test citations | Note |
|---|---|---|---|---|---|
| 8.2 | Graphics Objects | implemented | 3 | 5 | Path, text, XObject, inline image and shading objects all execute, BX/EX parse and unknown operators are skipped; the object nesting rules bind writers and are not validated on read. |
| 8.3.2.2 | Device Space | implemented | 2 | 3 | The crop box, /Rotate and the scale factor build the base matrix with the y flip that puts the first device row at the top. |
| 8.3.2.3 | User Space | implemented | 2 | 2 | The page's default user space is the base matrix, cm concatenates onto the CTM and q/Q restores it. |
| 8.3.2.4 | Other Coordinate Spaces | implemented | 2 | 4 | Form, image (unit square), pattern, text and Type3 glyph spaces each map through their own matrix onto the CTM. |
| 8.3.2.5 | Relationships among Coordinate Spaces | implemented | 2 | 2 | Transformations compose by matrix concatenation from the innermost space out to device space. |
| 8.3.3 | Common Transformations | implemented | 2 | 1 | Translation, scaling and rotation constructors follow the clause's matrix forms; skew is expressed through the general six-element matrix. |
| 8.3.4 | Transformation Matrices | implemented | 2 | 4 | The six-element matrix, its concatenation order and inversion follow the clause; a non-finite cm operand is skipped. |
| 8.4.1 | General | incomplete | 2 | 2 | Table 52's CTM, clip, colour spaces and colours, line width/cap/join/miter/dash, alpha, blend mode, soft mask and text state are tracked with the clause's initial values; rendering intent, stroke adjustment and alpha source are not, and none of Table 53 (overprint, black generation, undercolor removal, transfer, halftone, flatness, smoothness) is tracked. |
| 8.4.2 | Graphics State Stack | implemented | 2 | 5 | q pushes and Q pops the whole state including clip and soft mask, a form's unbalanced Q cannot pop its caller's state (case form_with_unbalanced_restore), and nesting is capped at 64 levels. |
| 8.4.3.2 | Line Width | implemented | 3 | 3 | The pen is a user-space circle carried through the CTM (anisotropic scaling widens it correctly) and a zero or sub-pixel width paints the thinnest visible hairline; the writer emits w through canvas.rs set_line_width. |
| 8.4.3.3 | Line Cap Style | implemented | 7 | 5 | J and /LC set stroke.rs LineCap, which stroke_path reads at every run end: butt caps stop at the endpoint, round caps add the pen disc, projecting square caps carry the band on by half the line width in user space (stroke.rs butt_caps_stop_at_the_endpoints, square_caps_extend_the_band_by_half_the_width, executor.rs line_caps_follow_j_and_lc); zero-length dashes take the cap style along the path's direction; a J operand outside 0 to 2 sets butt; the writer emits J through canvas.rs set_line_cap. |
| 8.4.3.4 | Line Join Style | implemented | 7 | 7 | j and /LJ set stroke.rs LineJoin, which stroke_path reads at every interior vertex and at the start of a closed subpath: a miter carries the two bands' outer edges to their meeting point, a bevel cuts across their ends, a round join adds the pen disc; collinear segments get no join and a segment turning straight back gets only the round disc (stroke.rs miter_joins_fill_the_outer_corner, bevel_joins_cut_the_corner, round_joins_keep_the_pen_disc, joins_follow_a_flipped_matrix, executor.rs line_joins_follow_j_lj_m_and_ml); a j operand outside 0 to 2 sets miter; the writer emits j through canvas.rs set_line_join. |
| 8.4.3.5 | Miter Limit | implemented | 6 | 3 | M and /ML set miter_limit (values under 1 are ignored, default 10); stroke.rs miter_fits measures the two segment directions in user space and cuts a miter to a bevel when 1/sin(angle/2) is over the limit (stroke.rs the_miter_limit_cuts_sharp_joins_to_bevels, executor.rs line_joins_follow_j_lj_m_and_ml); the writer emits M through canvas.rs set_miter_limit. |
| 8.4.3.6 | Line Dash Pattern | implemented | 3 | 6 | The dash array and phase are measured in user space through the inverse CTM, an empty array restores solid strokes and /D in an ExtGState is honored; the writer emits d through canvas.rs set_dash. |
| 8.4.4 | Graphics State Operators | implemented | 2 | 3 | All eleven operators parse and set their state, and ri and i are accepted with no effect as the clause allows. |
| 8.4.5 | Graphics State Parameter Dictionaries | incomplete | 4 | 7 | /LW /LC /LJ /ML /D /CA /ca /BM /SMask are applied from the named resource; /RI /Font /OP /op /OPM /TR /TR2 /HT /FL /SM /SA /BG /BG2 /UCR /UCR2 /TK /AIS are ignored; the writer emits /ca /CA /BM only. |
| 8.5.2.1 | General | implemented | 3 | 4 | All seven construction operators build subpaths with the current point semantics of Table 59, re starts a closed rectangle subpath, and a segment before any m starts at the origin leniently instead of erroring. |
| 8.5.2.2 | Cubic Bezier Curves | implemented | 2 | 4 | c, v and y build the four control points per Table 59 and the curve is flattened adaptively within tolerance; the writer emits c through canvas.rs curve_to. |
| 8.5.3.1 | General | implemented | 2 | 3 | Every painting operator ends the path, the closing variants close the subpath first, F is accepted as f, and n discards the path while still applying a pending clip. |
| 8.5.3.2 | Stroking | implemented | 4 | 9 | Strokes are offset quads under the full CTM with dashes, stroke patterns and the cap style applied; path.rs keeps a degenerate subpath (a closed single point or coincident points), which stroke_path paints as a filled disc under round caps and not at all under butt or square caps, while a single-point open subpath paints nothing and zero-length dashes keep their caps (stroke.rs degenerate_subpaths_paint_a_dot_only_under_round_caps, zero_length_dashes_take_the_cap_style, executor.rs tests of the same names, path.rs degenerate_subpaths_are_kept_for_the_stroker); joins follow 8.4.3.4 and 8.4.3.5. |
| 8.5.3.3.1 | General | implemented | 2 | 2 | Open subpaths are implicitly closed before filling and coverage is anti-aliased per pixel. |
| 8.5.3.3.2 | Nonzero Winding Number Rule | implemented | 3 | 2 | f, B and b accumulate signed winding per scanline span and paint where it is nonzero. |
| 8.5.3.3.3 | Even-Odd Rule | implemented | 2 | 3 | f*, B* and b* count crossings and paint where the count is odd; the writer emits f* through canvas.rs fill_even_odd. |
| 8.5.4 | Clipping Path Operators | implemented | 3 | 4 | W and W* mark a pending clip that intersects the current clip after the following painting operator (n included), nested clips and a clip straddling a form (cases nested_clips, clip_straddling_a_form) intersect correctly, and the writer emits W n and W* n through canvas.rs clip. |
| 8.6.2 | Colour Values | implemented | 2 | 3 | Each family declares its component count and decode range, operands are clamped to the space's range, and every colour ends as RGB for compositing. |
| 8.6.3 | Colour Space Families | implemented | 2 | 2 | The device, CIE-based (CalGray, CalRGB, Lab, ICCBased) and special (Indexed, Separation, DeviceN, Pattern) families all parse from name or array form, with unknown names falling back to DeviceGray. |
| 8.6.4.2 | DeviceGray Colour Space | implemented | 1 | 2 | g/G and DeviceGray image samples convert gray to equal RGB channels (case device_colorspaces). |
| 8.6.4.3 | DeviceRGB Colour Space | implemented | 1 | 2 | rg/RG and DeviceRGB image samples pass through unchanged. |
| 8.6.4.4 | DeviceCMYK Colour Space | incomplete | 1 | 3 | k/K and CMYK images convert, but by a deliberate multiplicative formula (1-c)(1-k) rather than the clause's 1 - min(1, c + k), chosen so deep colours keep their hue. |
| 8.6.5.2 | CalGray Colour Spaces | implemented | 2 | 2 | /Gamma and /WhitePoint are read and gray decodes through XYZ to sRGB; /BlackPoint is ignored and a bare /CalGray name reads as DeviceGray. |
| 8.6.5.3 | CalRGB Colour Spaces | implemented | 1 | 2 | Per-channel /Gamma and the column-major /Matrix map to XYZ with identity defaults; /BlackPoint is ignored and a bare /CalRGB name or a dictionary without /WhitePoint falls back to DeviceRGB. |
| 8.6.5.4 | Lab Colour Spaces | implemented | 3 | 4 | /Range defaults to plus or minus 100, the default image decode is the Lab range, and the inverse transfer follows the clause's two-branch form (the source comment at math.rs cites the clause). |
| 8.6.5.5 | ICCBased Colour Spaces | implemented | 1 | 6 | Matrix/TRC, lut8/lut16 and lutAtoB profiles transform through the PCS, sRGB-equivalent profiles take the device path, /N mismatches and unparseable profiles fall back to /N or /Alternate, and /Range and /Metadata are ignored. |
| 8.6.5.6 | Default Colour Spaces | implemented | 3 | 2 | executor.rs device_or_default replaces a device space selected by g/G, rg/RG, k/K or a cs/CS device name (a pattern space's base included) with the /DefaultGray, /DefaultRGB or /DefaultCMYK entry of the /ColorSpace resources when that entry has the same component count and is neither Indexed nor Lab, passing the component values through; image_colorspace does the same for image XObjects and inline images (executor.rs default_colour_spaces_remap_device_colours, an_unsuitable_default_colour_space_is_ignored); the defaults are resolved once per content stream and inherited through the resource chain; not remapped: the initial colour of a page or form, shading dictionaries, the base of an Indexed space and the alternate of a Separation or DeviceN. |
| 8.6.5.7 | Implicit Conversion of CIE-Based Colour Spaces | implemented | 2 | 3 | Every CIE-based colour converts to the sRGB output space through XYZ with Bradford adaptation of the declared white point; the output space is fixed to sRGB. |
| 8.6.5.8 | Rendering Intents | incomplete | 2 | 1 | ri parses and is accepted, but neither it nor /RI changes anything; the ICC pipeline uses one fixed transform regardless of the requested intent. |
| 8.6.6.2 | Pattern Colour Spaces | incomplete | 2 | 5 | /Pattern and [/Pattern base] spaces select tiling and shading patterns for path fills and strokes, but text glyphs (executor.rs) and image stencils (executor.rs) painted with a pattern colour get a mid-gray stand-in and a report entry. |
| 8.6.6.3 | Indexed Colour Spaces | implemented | 3 | 5 | String and stream lookup tables over any base (Lab and nested Indexed included) resolve, out-of-range indices clamp, and the image decode default is [0 2^bpc-1] (case indexed_image_and_stencil). |
| 8.6.6.4 | Separation Colour Spaces | implemented | 10 | 10 | The tint transform (function types 0, 2, 3, 4) runs into the alternate space with initial tint 1.0 (crates/pdfboss-render/src/color.rs parse_with and to_rgb, executor.rs initial_components). The special colorant All ignores the alternate space and the transform and puts the tint on every colorant, gray 1 - tint on the composite output (ColorSpace::SeparationAll). None never marks the page: fills, strokes and text carry a zero-alpha colour (executor.rs fill_rgba8, stroke_rgba8), a stencil mask returns before painting (blit_image), an image in the space returns from image.rs draw, and a shading from shading.rs paint and paint_background. Not caught: a Type3 glyph that sets its own colour inside a None fill, and an Indexed space over a None base; a DeviceN whose colorants are all None belongs to 8.6.6.5. Tests: executor.rs separation_none_never_marks_the_page and separation_all_paints_the_tint_on_every_colorant, color.rs separation_all_and_none_ignore_the_alternate_and_transform. |
| 8.6.6.5 | DeviceN Colour Spaces | incomplete | 1 | 4 | n-colourant tint transforms evaluate into the alternate space, but spaces with more than 8 colourants (the clause allows 32) fall back to an ink approximation and the attributes dictionary is ignored. |
| 8.6.6.6 | Multitone Examples | implemented | 1 | 1 | Informative examples of 8.6.6.5; a two-colourant DeviceN with a sampled tint transform (the duotone shape) evaluates through the general DeviceN path. |
| 8.6.7 | Overprint Control | not implemented | 0 | 0 | Nothing reads /OP, /op or /OPM; every paint composites over the backdrop in RGB with no overprint simulation. |
| 8.6.8 | Colour Operators | implemented | 3 | 5 | All twelve operators set the stroking or nonstroking space and colour, black DeviceGray is the initial colour and cs/CS applies the space's initial colour; the writer emits rg/RG/g/G/k/K through canvas.rs set_fill and 219 set_stroke. |
| 8.7.2 | General Properties of Patterns | implemented | 2 | 3 | The pattern matrix maps pattern space to the default space of the page or of the form whose resource it is, not to the CTM at painting time, and loaded patterns are cached per document. |
| 8.7.3.1 | General | implemented | 2 | 6 | Table 75's /BBox, /XStep, /YStep, /Resources and /Matrix are read (a zero or missing step falls back to the bbox size), /TilingType is ignored as a spacing hint, and the tile count is capped at 65536 with a report. |
| 8.7.3.2 | Coloured Tiling Patterns | implemented | 2 | 3 | /PaintType 1 cells run with their own colour operators and inherit the paint site's blend mode and soft mask. |
| 8.7.3.3 | Uncoloured Tiling Patterns | implemented | 2 | 2 | /PaintType 2 cells paint in the scn colour given in the [/Pattern base] underlying space with the cell's own colour operators locked out. |
| 8.7.4.1 | General | incomplete | 1 | 3 | /Shading and /Matrix of a Table 76 pattern dictionary are read and the fill paints through the path with /Background, but the pattern's own /ExtGState entry is not applied. |
| 8.7.4.2 | Shading Operator | implemented | 2 | 4 | sh paints the named shading over the current clip intersected with its /BBox, ignores /Background as the clause requires, and reports a singular CTM instead of painting; the writer serializes sh at content.rs. |
| 8.7.4.3 | Shading Dictionaries | implemented | 1 | 4 | /ShadingType, /ColorSpace, /Background and /BBox are honored (/Background only behind pattern fills, per Table 78); /AntiAlias is ignored as a hint. |
| 8.7.4.4 | Colour Space: Special Considerations | incomplete | 2 | 2 | A /Function turns colour data into one parametric value and mesh vertex colours decode through the shading's space, but an /Indexed space combined with /Function and a /Pattern shading space are not rejected as the clause requires. |
| 8.7.4.5.2 | Type 1 (Function-Based) Shadings | implemented | 1 | 2 | The 2-in function evaluates per pixel over /Domain under /Matrix and points outside the domain stay unpainted. |
| 8.7.4.5.3 | Type 2 (Axial) Shadings | implemented | 2 | 3 | /Coords, /Domain, /Extend and a single or array-valued /Function are honored. |
| 8.7.4.5.4 | Type 3 (Radial) Shadings | implemented | 1 | 1 | The two circles, /Domain and /Extend are honored and a negative radius is rejected; the test covers concentric circles without extension. |
| 8.7.4.5.5 | Type 4 Shadings (Free-Form Gouraud-Shaded Triangle Meshes) | implemented | 2 | 4 | Flags 0, 1 and 2 with /BitsPerCoordinate, /BitsPerComponent, /BitsPerFlag and /Decode are read from the stream and Gouraud interpolation paints each triangle. |
| 8.7.4.5.6 | Type 5 Shadings (Lattice-Form Gouraud-Shaded Triangle Meshes) | implemented | 2 | 1 | /VerticesPerRow rows are split into triangle pairs and painted like type 4. |
| 8.7.4.5.7 | Type 6 Shadings (Coons Patch Meshes) | implemented | 2 | 3 | The 12 control points per patch in Table 85 order, edge sharing for flags 1 to 3, and interior points derived by the Coons formula are all handled. |
| 8.7.4.5.8 | Type 7 Shadings (Tensor-Product Patch Meshes) | implemented | 2 | 1 | The four extra interior points are read in Table 86 order and the patch is evaluated as a bicubic tensor patch. |
| 8.8.1 | General | implemented | 2 | 3 | Do dispatches on /Subtype (indirect values resolved) to image or form drawing and reports a missing or untyped XObject; the writer emits Do for images and form groups (canvas.rs draw_image, 387 draw_group). |
| 8.8.2 | PostScript XObjects | out of scope | 0 | 0 | pdfboss does not interpret PostScript, so a /PS XObject is skipped with an Unsupported report entry, which is what the clause tells a non-PostScript reader to do. |
| 8.9.2 | Image Parameters | implemented | 2 | 4 | /Width, /Height, /BitsPerComponent and /ColorSpace drive decoding; bad dimensions are rejected and short sample data reports as truncated. |
| 8.9.3 | Sample Representation | implemented | 2 | 3 | 1, 2, 4, 8 and 16 bit samples are read big-endian with each row padded to a whole byte, 16-bit samples scaling by 65535. |
| 8.9.4 | Image Coordinate System | implemented | 3 | 4 | The image fills the unit square under the CTM with row 0 at the top edge (v = 1) by inverse mapping of each device pixel. |
| 8.9.5.1 | General | incomplete | 3 | 4 | Of Table 89 the entries Width, Height, BitsPerComponent, ColorSpace, Decode, ImageMask, Mask, SMask, SMaskInData and OC are honored, while Intent, Interpolate, Alternates, Name, StructParent, ID, OPI and Metadata are ignored. |
| 8.9.5.2 | Decode Arrays | implemented | 6 | 7 | /Decode maps native samples, stencils and the Lab and Indexed defaults per Table 90 (crates/pdfboss-render/src/image.rs decode_samples, decode_stencil), maps DCTDecode samples per component through apply_decode (so a Photoshop-style CMYK JPEG with /Decode [1 0 1 0 1 0 1 0] comes out right), and is ignored for JPXDecode as 7.4.9 asks. Tests: image.rs decode_array_inverts_gray, dct_decode_array_applies_to_jpeg_samples and dct_decode_array_inverts_photoshop_cmyk. |
| 8.9.5.3 | Image Interpolation | implemented | 3 | 2 | image.rs ImageMeta reads /Interpolate (the inline /I is expanded at content.rs) and draw_rgba samples an interpolating image bilinearly (Rgba::bilinear) wherever it is drawn larger than itself, alpha included so stencil edges soften too; without the flag magnification stays nearest-neighbour and minification keeps its box average either way (image.rs interpolated_magnification_blends_between_samples, executor.rs interpolate_blends_a_magnified_image); the clause leaves the algorithm to the reader. |
| 8.9.5.4 | Alternate Images | not implemented | 1 | 0 | No code reads /Alternates; the base image is always drawn, which the clause permits a reader to do. |
| 8.9.6.1 | General | implemented | 1 | 3 | Stencil, explicit and colour key masking are dispatched from one place, with /SMask taking precedence over /Mask when both are present. |
| 8.9.6.2 | Stencil Masking | incomplete | 2 | 5 | /ImageMask true paints the nonstroking colour where the decoded sample is 0 (with /Decode [1 0] inverting and JPX stencils repacked to 1 bit), but a pattern nonstroking colour paints a mid-gray stand-in with a report entry. |
| 8.9.6.3 | Explicit Masking | implemented | 2 | 1 | A stream-valued /Mask must be an /ImageMask stencil, its 1 samples hide the base image and a mask of different dimensions is resampled onto the base's unit square; a non-stencil /Mask stream is reported; the test comment cites §8.9.6.4, which is not the -1 number for explicit masking. |
| 8.9.6.4 | Colour Key Masking | implemented | 2 | 1 | An array /Mask of 2n integer ranges hides samples whose raw values all fall inside, on native sample data only (DCT and JPX images decline with a report, since the clause warns the result is unpredictable there). |
| 8.9.7 | Inline Images | implemented | 5 | 8 | BI/ID/EI with the Table 93 key abbreviations, the /G /RGB /CMYK /I colour space abbreviations, the Table 94 filter abbreviations and /L are honored and a named colour space resolves through the /ColorSpace resource; the writer serializes inline images with /L. |
| 8.10.2 | Form Dictionaries | implemented | 2 | 6 | /Matrix, /BBox (as a clip) and /Resources (prepended to the lookup chain with page fallback; output lib.rs cites the clause) are honored to depth 16 (cases nested_forms, form_resource_shadowing), /Group /Ref /Metadata /PieceInfo /StructParent are ignored, and the writer emits form XObjects with /BBox and /Resources at pdf.rs. |
| 8.10.3 | Group XObjects | implemented | 3 | 4 | /Group is read on form XObjects and annotation appearance streams: /S /Transparency makes the form a transparency group (11.6.6) and any other subtype, or no /Group, paints as a plain form (executor.rs transparency_group; tests group_with_an_unknown_subtype_is_a_plain_form and non_isolated_group_blends_with_the_page_and_isolated_does_not in executor.rs); the soft-mask /G form also runs as a group (11.6.5.2). |
| 8.10.4.1 | General | incomplete | 1 | 0 | /Ref is never read, so a reference XObject paints its own proxy content as an ordinary form, which the clause permits when the referenced file is not consulted. |
| 8.10.4.2 | Printing Reference XObjects | out of scope | 0 | 0 | Printer-side substitution of the referenced page has no meaning for a rasterizer that never opens the referenced file. |
| 8.10.4.3 | Special Considerations | out of scope | 0 | 0 | The guidance on /Page and /ID matching against the referenced file does not apply while /Ref is not consulted. |
| 8.11.2.1 | General | implemented | 7 | 5 | A group is identified by its indirect reference and is off when the configuration's state says so; /Name, /Intent and /Usage are read as data by optional_content_groups_with (crates/pdfboss-core/src/oc.rs, test oc.rs groups_read_as_data), with Python and async twins. |
| 8.11.2.2 | Optional Content Membership Dictionaries | implemented | 2 | 6 | /OCGs (single or array, null entries ignored) under /P AnyOn, AllOn, AnyOff, AllOff and /VE And/Or/Not expressions with nesting all evaluate, /VE taking precedence. |
| 8.11.2.3 | Intent | implemented | 2 | 5 | A group's /Intent (a name or an array, default View) must share a name with the configuration's /Intent (default View, All matching every group) for its state to affect visibility, so an off Design group under a View configuration still paints, and an empty configuration array hides nothing (oc.rs retain_matching_intents; tests oc.rs a_group_of_another_intent_never_hides_content, the_configuration_intent_selects_the_groups_it_controls and executor.rs usage_application_and_intent_decide_what_paints). |
| 8.11.3.2 | Optional Content in Content Streams | implemented | 2 | 8 | BDC /OC with a named /Properties resource or an inline dictionary opens a span whose painting is suppressed while graphics state, text position and clipping still take effect, spans nest and EMC balances; text extraction applies the same gating. |
| 8.11.3.3 | Optional Content in XObjects and Annotations | implemented | 2 | 3 | An /OC entry on an image or form XObject or on an annotation dictionary hides it (counted, not reported as a drop); the code cites this as §8.11.3.5, which is not the -1 number. |
| 8.11.4.2 | Optional Content Properties Dictionary | implemented | 4 | 3 | /OCGs and the default /D configuration are read from the catalog; /Configs alternates are ignored because only /D applies when a document opens. |
| 8.11.4.3 | Optional Content Configuration Dictionaries | implemented | 3 | 4 | /BaseState, /ON and /OFF apply in specification order, /Intent selects the groups the configuration controls and /AS applies the usage application dictionaries of the requested event (oc.rs configured_off, apply_usage); /Name, /Creator, /Order, /ListMode, /RBGroups and /Locked are viewer UI entries with no rendering effect, and /Configs alternates are not read because only /D applies when a document opens (8.11.4.2). |
| 8.11.4.4 | Usage and Usage Application Dictionaries | implemented | 6 | 7 | The configuration's /AS dictionaries whose /Event is the requested event set each group in their /OCGs from the View, Print or Export entry of its /Usage that the /Category array names, ON turning the group on and OFF off, in array order with later dictionaries winning (oc.rs apply_usage; tests oc.rs view_usage_application_hides_a_view_state_off_group, each_event_applies_its_own_dictionaries, usage_without_application_or_category_changes_nothing). The Zoom, Language and User categories depend on a viewer's magnification, locale and user, which pdfboss has none of, and leave the state unchanged. Every Table 102 entry is read as data (oc.rs read_usage, test oc.rs groups_read_as_data). |
| 8.11.4.5 | Determining the State of Optional Content Groups | implemented | 4 | 10 | The state comes from /BaseState then /ON then /OFF of the default configuration, then the /AS dictionaries of one event, then the intent rule; a document without /OCProperties shows everything. Document::oc_state, which rendering and text extraction use, is the View event's state a viewer shows on screen (pdfium and pdf.js render the same), while oc_state_for(None) is the default configuration alone that the clause prescribes for printing and aggregating applications, and oc_state_for(Some(Print)) the printed state (tests executor.rs usage_application_and_intent_decide_what_paints, extract.rs usage_application_decides_which_layers_are_extracted, parity.rs optional_content_states_and_groups_agree). |
9 Text
| Clause | Feature | Status | Code citations | Test citations | Note |
|---|---|---|---|---|---|
| 9.2.2 | Basics of Showing Text | implemented | 2 | 4 | BT, Tf, Td, Tj and ET are parsed, executed for extraction and painted by the renderer; the writer emits the same five-operator sequence from Canvas::text (crates/pdfboss-write/src/canvas.rs). |
| 9.2.3 | Achieving Special Graphical Effects | implemented | 1 | 3 | The renderer paints every Tr mode of Table 106 for outline fonts: fills in the fill colour, strokes in the stroke colour through stroke_path with the graphics state's line parameters, and the clipping modes intersect the clip with the glyph outlines at ET so later content shows through the text (executor.rs clip_render_modes_intersect_the_clip_when_the_text_object_ends); SkippedKind::TextClip is no longer produced. |
| 9.2.4 | Glyph Positioning and Metrics | implemented | 3 | 3 | Widths are read in 1/1000 text-space units (Type3 through /FontMatrix), the horizontal displacement w0 advances the text matrix and vertical fonts use w1 with the position vector; the writer's Standard14::text_width sums AFM widths the same way (crates/pdfboss-write/src/font.rs). |
| 9.3.1 | General | implemented | 2 | 5 | The Table 104 text state parameters live in the graphics state on both the extraction and the render side, so q/Q restore them and BT/ET leave them alone; Tf takes the font resource name and size. |
| 9.3.2 | Character Spacing | implemented | 2 | 3 | Tc is added to every glyph displacement, scaled by Th horizontally and unscaled in vertical writing; only the render tests measure the Tc advance, extraction has no dedicated one. |
| 9.3.3 | Word Spacing | implemented | 3 | 4 | Tw applies to the single-byte code 32 only, including composite fonts whose CMap has a one-byte codespace covering it; the writer's justified paragraphs set Tw per line and reset it (crates/pdfboss-write/src/element.rs). |
| 9.3.4 | Horizontal Scaling | implemented | 2 | 2 | Tz/100 scales horizontal advances, TJ offsets and the glyph rendering matrix and is not applied to vertical displacements; only the extraction side has an advance test. |
| 9.3.5 | Leading | implemented | 1 | 2 | TL sets the leading, TD sets it to -ty, and T*, ' and " move the line matrix down by it. |
| 9.3.6 | Text Rendering Mode | implemented | 8 | 9 | executor.rs show_text and paint_glyph follow Table 106: modes 0, 2, 4, 6 fill, modes 1, 2, 5, 6 stroke each glyph after its fill in the stroke colour with the user-space line width, caps, joins and dash, modes 4 to 7 accumulate the outlines from BT (or from the Tr inside the text object) and ET intersects the clip with their nonzero union, no outlines meaning no clip, and Q restores it; mode 3 paints nothing; an operand outside the table is read by its bits (fill and stroke in the low two, clip in the third) as pdf.js reads it; a font that paints at no tier contributes no outlines, so a clipping mode over its text leaves later content unclipped; a Type3 font is affected by mode 3 only, so its glyphs paint in every other mode and never clip (executor.rs stroke_render_modes_paint_the_outline_in_the_stroke_colour, clip_render_modes_intersect_the_clip_when_the_text_object_ends, type3_text_ignores_every_mode_but_invisible, text_render_mode_is_restored_by_q); extraction marks modes 3 and 7 invisible. |
| 9.3.7 | Text Rise | implemented | 2 | 2 | Ts shifts the glyph origin in unscaled text space and is recorded on each TextSpan; the renderer applies it in the text rendering matrix without a dedicated test. |
| 9.3.8 | Text Knockout | not implemented | 0 | 0 | The ExtGState /TK flag is never read; glyphs are composited one at a time as they paint, which matches only the TK false behavior. |
| 9.4.1 | General | implemented | 3 | 2 | BT resets Tm and Tlm to identity and ET ends the object while the text state parameters survive across text objects; nested BT is not diagnosed. |
| 9.4.2 | Text-Positioning Operators | implemented | 2 | 4 | Td, TD, Tm and T* update Tlm and Tm per Table 108 in both consumers; the writer positions text with Td (crates/pdfboss-write/src/canvas.rs). |
| 9.4.3 | Text-Showing Operators | implemented | 2 | 4 | Tj, TJ (number offsets as -n/1000 times Tfs and Th, or ty in vertical mode), ' and " are all executed; TJ offsets and the two quote operators have parser tests only, no extraction or render test; the writer emits Tj. |
| 9.4.4 | Text Space Details | implemented | 6 | 4 | The rendering matrix [Tfs*Th 0 0 Tfs 0 Ts] x Tm x CTM and the tx/ty displacement formulas are applied per glyph, with the vertical position vector v shifting the glyph origin. |
| 9.5 | Introduction to Font Data Structures | implemented | 2 | 4 | Font dictionaries dispatch on /Subtype to the Type0, Type1/MMType1, TrueType and Type3 loaders on both consuming sides; the writer emits /Type /Font dictionaries for the standard 14 (crates/pdfboss-write/src/font.rs). |
| 9.6.2 | Type 1 Fonts | incomplete | 10 | 17 | Parent row: see 9.6.2.1 (implemented), 9.6.2.2 (incomplete) and 9.6.2.3 (incomplete). |
| 9.6.2.1 | General | implemented | 2 | 5 | Table 111 entries BaseFont, FirstChar, Widths, FontDescriptor, Encoding and ToUnicode are read (LastChar is implied by the Widths length); the writer emits Type, Subtype, BaseFont and Encoding for the standard 14 (crates/pdfboss-write/src/font.rs). |
| 9.6.2.2 | Standard Type 1 Fonts (Standard 14 Fonts) | incomplete | 6 | 12 | The twelve text faces (plus Arial, TimesNewRoman and CourierNew aliases) get AFM widths without /Widths (as rasterized advances and as extracted span widths) and Croscore substitute faces at GlyphPainting::Full (bundled only with the substitute-fonts feature, on by default in the CLI), and the writer emits them WinAnsi-encoded; Symbol and ZapfDingbats have their built-in encodings (Annex D.5 and D.6, crates/pdfboss-encoding/src/symbol.rs) but no metrics and no substitute face, so they stay unpainted and the writer encodes text for them without widths (crates/pdfboss-write/src/font.rs symbol_faces_encode_through_their_built_in_encodings). |
| 9.6.2.3 | Multiple Master Fonts | incomplete | 2 | 0 | /MMType1 is dispatched like /Type1 by the renderer and falls into the simple-font loader in extraction, but no test names an /MMType1 font. |
| 9.6.3 | TrueType Fonts | implemented | 2 | 4 | Simple /TrueType fonts with a FontFile2 program are parsed (head, maxp, loca, glyf, cmap, hmtx, post) and painted at every tier with /Widths taking precedence over hmtx; extraction reads the cmap platform ids to decide the WinAnsi gap fill. |
| 9.6.4 | Font Subsets | implemented | 5 | 2 | The six-uppercase-plus tag is stripped before standard-14 metric lookup, substitution and picture-font detection while extracted spans keep /BaseFont as written; the writer embeds no programs, so it never subsets. |
| 9.6.5 | Type 3 Fonts | implemented | 11 | 12 | FontMatrix, CharProcs, Encoding, Widths and the font's own Resources are read, CharProcs run as nested frames, d1 glyphs ignore their own color operators and the advance comes from /Widths through FontMatrix; FontBBox is not used, and several comments cite "9.6.5.2", a subclause number the 32000-1 outline does not have. |
| 9.6.6 | Character Encoding | incomplete | 21 | 30 | Parent row: see 9.6.6.1 (incomplete), 9.6.6.2 (incomplete), 9.6.6.3 (implemented) and 9.6.6.4 (incomplete). |
| 9.6.6.1 | General | incomplete | 4 | 6 | Named encodings and encoding dictionaries with /BaseEncoding and /Differences are applied for WinAnsiEncoding, MacRomanEncoding and StandardEncoding; MacExpertEncoding is not implemented and silently falls back to StandardEncoding. |
| 9.6.6.2 | Encodings for Type 1 Fonts | incomplete | 6 | 10 | An embedded program's built-in encoding (a Type 1 program's /Encoding dup/put entries or StandardEncoding token; a CFF program's Encoding table, standard or custom format 0/1 with supplements, expert not bundled) is the base when the PDF names none, /Differences apply over it, a named encoding outranks it, and a nonsymbolic or standard-14 font without /Encoding defaults to StandardEncoding; the built-in encodings of the non-embedded Symbol and ZapfDingbats faces are not available. |
| 9.6.6.3 | Encodings for Type 3 Fonts | implemented | 2 | 3 | The /Encoding dictionary's /Differences names select CharProcs (StandardEncoding names fill unmapped codes as a leniency); extraction decodes Type3 codes through the same simple-font encoding path. |
| 9.6.6.4 | Encodings for TrueType Fonts | implemented | 7 | 11 | crates/pdfboss-render/src/truetype.rs Cmap keeps the best Unicode subtable ((3,10), (3,1) or platform 0), the (3,0) symbol table and the (1,0) Macintosh table, each reachable on its own (gid_for_unicode, gid_for_symbol, gid_for_mac); glyph.rs load_simple builds the code table as the clause says: a glyph name from /Differences, else from the base encoding when the font names MacRoman or WinAnsi, is flagged Nonsymbolic, or carries any /Encoding without the Symbolic flag, StandardEncoding filling what the base leaves undefined; the name goes through the glyph list into the Unicode subtable, or without one back to a Mac OS Roman code (pdfboss-encoding mac_os_roman_code, Table 115 included) into the (1,0) subtable, and failing both through post; a code still unmapped takes the symbolic route, the (3,0) subtable at the byte and in the 0xF000, 0xF100 and 0xF200 ranges, else the (1,0) subtable at the byte, then this reader's own mapping, a Unicode subtable at the byte or 0xF000 plus the byte, and in a program without a cmap the byte as the glyph index (tests glyph.rs differences_name_paints_via_post, base_encoding_letter_still_paints, symbolic_font_codes_go_through_the_3_0_subtable_ranges, mac_roman_subtable_takes_the_name_mapped_to_a_mac_os_code, a_font_without_a_cmap_uses_the_code_as_the_glyph_index; pdfboss-encoding lib.rs mac_os_roman_codes_follow_table_115). A symbolic font with a (3,0) table is addressed by code first, whatever its /Encoding says, and its /Differences names reach glyphs only through post or by their glyph-list value in the (3,0) table, never through Mac OS Roman, since its (1,0) table is keyed by the font's own codes (pdf.js canvas.pdf; test a_symbolic_fonts_differences_names_do_not_go_through_mac_os_roman), the leniency the clause's NOTE 1 allows. |
| 9.7.2 | CID-Keyed Fonts Overview | implemented | 2 | 3 | The code to CID (CMap) to glyph (CIDToGIDMap or CFF charset) pipeline is in place for CIDFontType0 and CIDFontType2 descendants. |
| 9.7.3 | CIDSystemInfo Dictionaries | incomplete | 3 | 1 | Only /Ordering is read, to pick the Japan1, GB1, CNS1, Korea1 or KR CID-to-Unicode table; /Registry and /Supplement are ignored and the CMap-versus-CIDFont collection compatibility rule is not checked. |
| 9.7.4 | CIDFonts | implemented | 16 | 18 | Parent row: see 9.7.4.1, 9.7.4.2 and 9.7.4.3, all implemented (crates/pdfboss-render/src/glyph.rs load_type0 and load_cff_cid; tests there cover each child). |
| 9.7.4.1 | General | implemented | 2 | 3 | Table 117 entries Subtype, CIDSystemInfo, FontDescriptor, DW, W, DW2, W2 and CIDToGIDMap are read from the first descendant; BaseFont is used only as style evidence. |
| 9.7.4.2 | Glyph Selection in CIDFonts | implemented | 6 | 9 | CIDFontType2 honours /CIDToGIDMap as /Identity or a stream (crates/pdfboss-render/src/glyph.rs). CIDFontType0 maps CIDs through the CFF charset when the Top DICT carries CIDFont operators and uses the CID directly as the glyph index when it does not (glyph.rs load_cff_cid over cff.rs cid_to_gid, None for the second case). A FontFile3 of /Subtype /OpenType is read from the container's CFF table (cff.rs parse, cff_table), and so is a CIDFontType2 FontFile2 holding a CFF-flavoured OpenType program (OTTO), as macOS Quartz writes it (glyph.rs load_type0_truetype into cff_cid, at the CFF tier; the descendant's /CIDToGIDMap applies when the CFF has no CIDFont operators). Tests: glyph.rs cff_cid_font_paints_at_all_embedded_not_embedded_truetype_only, cff_cid_font_over_a_non_cid_program_uses_cids_as_gids, cff_cid_font_in_an_opentype_wrapper_paints, cff_opentype_in_a_cid_fontfile2_paints_at_the_cff_tier; cff.rs an_opentype_wrapped_cff_parses_from_its_cff_table. |
| 9.7.4.3 | Glyph Metrics in CIDFonts | implemented | 6 | 6 | Both forms of /W and /W2 are parsed, /DW defaults to 1000 also when neither /W nor /DW is present, never falling back to the program's hmtx (glyph.rs cid_widths; test a_cid_font_without_w_or_dw_advances_by_the_default_width), /DW2 to [880 -1000] and the default position vector is (w0/2, vy); ranges are capped at 65536 entries as a hostile-input guard. |
| 9.7.5 | CMaps | implemented | 14 | 11 | Parent row: see 9.7.5.1 to 9.7.5.4, all implemented. |
| 9.7.5.1 | General | implemented | 2 | 3 | CMaps split one to four byte codes by codespace range, map them to CIDs and carry the writing mode; the CID value domain is kept apart from the ToUnicode parser. |
| 9.7.5.2 | Predefined CMaps | implemented | 7 | 1 | All Table 118 names (20 Japan1, 14 GB1, 14 CNS1, 11 Korea1, Identity-H/V) plus UniAKR-UTF16-H load from packed Adobe data behind the predefined-cmaps feature, which is off in pdfboss-core by default but on in the CLI defaults and in pdfboss-py; without it every name but Identity falls back to the Identity assumption. |
| 9.7.5.3 | Embedded CMap Files | implemented | 2 | 3 | A CMap stream as /Encoding is parsed with its /WMode and /UseCMap chain (names or streams, depth bounded at 4) and the in-content usecmap operator; the stream dictionary's CMapName and CIDSystemInfo are not read. |
| 9.7.5.4 | CMap Example and Operator Summary | implemented | 1 | 4 | begincodespacerange, begincidchar, begincidrange, beginnotdefchar, beginnotdefrange and usecmap are executed and the remaining Table 119 operators (begincmap, endcmap, CMapName, def) are skipped as inert; the bf* operators belong to ToUnicode (9.10.3). |
| 9.7.6 | Type 0 Font Dictionaries | incomplete | 7 | 9 | Parent row: see 9.7.6.1 (implemented), 9.7.6.2 (implemented) and 9.7.6.3 (incomplete). |
| 9.7.6.1 | General | implemented | 2 | 3 | Table 121 entries Subtype, Encoding, DescendantFonts (first element) and ToUnicode are read; BaseFont feeds style detection only. |
| 9.7.6.2 | CMap Mapping | implemented | 3 | 3 | Codes are matched byte-wise against the codespace ranges (shortest match wins) and looked up by (length, value), so equal values of different lengths stay distinct; with no codespaces at all two-byte codes are assumed. |
| 9.7.6.3 | Handling Undefined Characters | incomplete | 2 | 3 | Notdef ranges are consulted after every real mapping, an invalid code consumes the shortest codespace whose first byte fits and maps to CID 0, and CID 0 still advances by /W or /DW; the renderer never draws the notdef glyph (gid 0 is skipped and reported as NoGlyph) and with no partial match one byte is consumed instead of the shortest codespace length. |
| 9.8.1 | General | implemented | 14 | 17 | Table 122 entries FontName, Flags, ItalicAngle, Ascent, Descent, CapHeight, StemV, FontWeight, MissingWidth and the three FontFile keys are consumed for metrics, style and substitution, and FontBBox stands in for an absent or zero Ascent or Descent in extracted span boxes (not for Type 3 fonts, whose descriptor is in FontMatrix space); XHeight, StemH, Leading, AvgWidth, MaxWidth and CharSet are not read. |
| 9.8.2 | Font Descriptor Flags | implemented | 12 | 8 | FixedPitch, Serif, Nonsymbolic, Italic and ForceBold (Table 123 bits 1, 2, 6, 7 and 19) drive span style, substitute face choice and the StandardEncoding default; Symbolic, Script, AllCap and SmallCap are not consulted. |
| 9.8.3 | Font Descriptors for CIDFonts | not implemented | 2 | 2 | Parent row: see 9.8.3.1 (incomplete), 9.8.3.2 (notImplemented) and 9.8.3.3 (notImplemented). |
| 9.8.3.1 | General | incomplete | 2 | 2 | The descendant's descriptor is read through the same Table 122 path as simple fonts (Flags, metrics, FontFile2 and FontFile3), but none of the Table 124 CIDFont-only entries (Style, Lang, FD, CIDSet) is consulted. |
| 9.8.3.2 | Style | not implemented | 0 | 0 | The /Style dictionary and its /Panose string are never read. |
| 9.8.3.3 | FD | not implemented | 0 | 0 | The /FD dictionary of per-class descriptors is never read; only the top-level descriptor metrics apply. |
| 9.9 | Embedded Font Programs | incomplete | 4 | 6 | FontFile (Type 1 as PFA or PFB, eexec decrypted), FontFile2 (TrueType) and FontFile3 as bare CFF (Type1C and CIDFontType0C) are parsed and painted; a CFF-flavoured OpenType program (OTTO) is read from its CFF table, in FontFile3 and in FontFile2 alike (cff.rs cff_table; glyph.rs load_simple and load_type0_truetype); the FontFile3 /Subtype is never read, Length1, Length2 and Length3 are not used, and the writer embeds no font programs. |
| 9.10.2 | Mapping Character Codes to Unicode Values | incomplete | 3 | 4 | ToUnicode is consulted first, simple fonts then map through the encoding's glyph names (Adobe Glyph List, uniXXXX and uXXXX[XX] forms, underscore ligatures, period variants) and composite fonts through the collection's Uni*-UTF16-H inverse keyed by /Ordering, and a marked-content sequence's /ActualText replaces the mapped text of everything shown inside it (14.9.4, 14.8.2.4.2); gXX or cidXX style glyph names are not resolved. |
| 9.10.3 | ToUnicode CMaps | implemented | 2 | 6 | begincodespacerange, beginbfchar and both beginbfrange forms are parsed with UTF-16BE destinations (multi-character and surrogate pairs) for simple and Type0 fonts alike; a lone U+FFFD destination is treated as no mapping and usecmap inside a ToUnicode CMap is not followed. |
10 Rendering
| Clause | Feature | Status | Code citations | Test citations | Note |
|---|---|---|---|---|---|
| 10.2 | CIE-Based Colour to Device Colour | implemented | 1 | 4 | CalRGB, CalGray, Lab and ICCBased colours convert through XYZ with Bradford adaptation into sRGB, the only output device; /BlackPoint is never read and the ri operator and /RI are parsed then discarded. |
| 10.3.2 | Conversion between DeviceGray and DeviceRGB | implemented | 3 | 2 | Gray to RGB replicates the value; RGB to gray uses the clause's 0.3/0.59/0.11 weights inside blend luminosity and Rec. 601 integer weights (77/150/29 over 256) for luminosity soft masks, and no gray output device exists. |
| 10.3.3 | Conversion between DeviceGray and DeviceCMYK | out of scope | 0 | 0 | The rasterizer targets only an RGB device, so gray never converts to or from CMYK. |
| 10.3.4 | Conversion from DeviceRGB to DeviceCMYK | out of scope | 0 | 0 | There is no CMYK output device, so RGB is never converted to CMYK. |
| 10.3.5 | Conversion from DeviceCMYK to DeviceRGB | implemented | 1 | 3 | Every CMYK fill, stroke and image sample converts to RGB, but on purpose with the multiplicative (1-c)(1-k) form rather than the clause's 1-min(1, c+k) so deep colours keep their hue. |
| 10.4 | Transfer Functions | out of scope | 0 | 0 | ExtGState /TR and /TR2 are output-device calibration hints a screen rasterizer ignores and they are never read; the soft-mask dictionary /TR is applied and assessed under 11.6.5.2. |
| 10.5.2 | Halftone Screens | out of scope | 0 | 0 | Halftoning is a printer feature; /HT is never read and the rasterizer paints continuous-tone anti-aliased RGB. |
| 10.5.3 | Spot Functions | out of scope | 0 | 0 | No halftone screens exist, so spot functions have nothing to drive. |
| 10.5.4 | Threshold Arrays | out of scope | 0 | 0 | No halftone screens exist, so threshold arrays are never read. |
| 10.5.5 | Halftone Dictionaries | out of scope | 0 | 0 | Halftone dictionaries of every type (1, 5, 6, 10, 16) are never parsed; the code does not distinguish 10.5.5.2 to 10.5.5.6. |
| 10.6.2 | Flatness Tolerance | incomplete | 2 | 2 | Curves flatten to a fixed 0.1 device-pixel error bound, which satisfies any permitted flatness, but the i operator is parsed then discarded and ExtGState /FL is never read. |
| 10.6.3 | Smoothness Tolerance | incomplete | 2 | 0 | Shadings are evaluated per device pixel, so any smoothness tolerance is met, but ExtGState /SM is never read. |
| 10.6.4 | Scan Conversion Rules | implemented | 1 | 10 | Anti-aliased analytic coverage (vertical subsamples, exact horizontal spans) under the nonzero and even-odd rules, clips applied as coverage masks, and open subpaths closed for filling. |
| 10.6.5 | Automatic Stroke Adjustment | not implemented | 0 | 0 | ExtGState /SA is never read; the 0.75 device-pixel minimum pen width at stroke.rs is a hairline floor, not stroke adjustment. |
11 Transparency
| Clause | Feature | Status | Code citations | Test citations | Note |
|---|---|---|---|---|---|
| 11.2 | Overview of Transparency | incomplete | 2 | 2 | Constant alpha, blend modes, soft masks and transparency groups (isolated or not, composited once with the alpha, blend mode and soft mask in force at their Do) are painted, but knockout groups, group colour spaces and separate shape and opacity values are absent. |
| 11.3.2 | Basic Notation for Compositing Computations | incomplete | 1 | 0 | Definitions only; the formula they name is assessed under 11.3.3. |
| 11.3.3 | Basic Compositing Formula | implemented | 2 | 3 | Union alpha, alpha-weighted colour and the backdrop-alpha weighting of the blend result ((1-ab)Cs + abB(Cb,Cs), raster.rs blended) all follow the formula; a non-Normal mode over a transparent backdrop passes the source colour through (raster.rs blend_over_a_transparent_backdrop_passes_the_source_through). |
| 11.3.4 | Blending Colour Space | incomplete | 1 | 0 | Blending always happens in 8-bit device sRGB after colour conversion; a group /CS is never read, so CMYK or Lab content blends in RGB rather than its own space. |
| 11.3.5 | Blend Mode | implemented | 9 | 13 | All sixteen Table 136 and 137 modes are parsed (name or array form, unknown names read as Normal) and painted on fills, strokes, glyphs, images, shadings and pattern tiles; the twelve separable formulas (32000-2 §11.3.5.2) are coded with Multiply and Screen checked against their formula, and the four non-separable modes (32000-2 §11.3.5.3) are checked against hand-computed vectors. |
| 11.3.6 | Interpretation of Alpha | incomplete | 2 | 1 | One alpha per paint (coverage times constant alpha times soft mask times image sample alpha) stands in for shape and opacity, which are never carried as separate values; the difference only shows in knockout groups (11.4.6). |
| 11.3.7 | Shape and Opacity Computations | incomplete | 5 | 5 | Source shape and opacity combine as the clause asks (11.3.7.2); result shape and opacity are not tracked (11.3.7.3). |
| 11.3.7.2 | Source Shape and Opacity | implemented | 2 | 4 | Source alpha is the product of anti-aliased object coverage, the graphics-state soft mask intersected with the clip, the constant alpha and any image sample alpha, matching fjfmqk with the constant shape fixed at 1. |
| 11.3.7.3 | Result Shape and Opacity | incomplete | 1 | 0 | composite_over unions alpha (as + ab*(1-as)) on the page and offscreen mask buffers, but result shape and opacity are never kept apart, so group results (11.4) cannot be formed. |
| 11.3.8 | Summary of Basic Compositing Computations | incomplete | 2 | 1 | Summary clause; the formulas it collects are assessed under 11.3.2 to 11.3.7, and raster.rs blended cites it for the backdrop-alpha weighting. |
| 11.4.2 | Notation for Group Compositing Computations | incomplete | 1 | 0 | Definitions only; the group compositing formulas they serve are assessed under 11.4.4 (raster.rs mix_group cites the clause). |
| 11.4.3 | Group Structure and Nomenclature | incomplete | 3 | 1 | Nested groups form a tree of frames, each painting into its own offscreen buffer whose initial backdrop is a copy of the group backdrop or, for an isolated group, transparent (executor.rs run, raster.rs copy_region), and the finished group composites onto the group backdrop once; the knockout alteration of the group backdrop (11.4.6) is absent. |
| 11.4.4 | Group Compositing Computations | incomplete | 3 | 2 | The non-isolated, non-knockout case runs as the clause's formulas reduce for one alpha per element: the group paints over a copy of its backdrop and raster.rs mix_group removes that backdrop's contribution so it counts once (Note 3), while an isolated group composites through raster.rs composite_group with the alpha, soft mask and blend mode at its Do; a non-isolated group invoked under a non-Normal blend mode is rendered isolated instead, shape and opacity are one value (/AIS is never read) and knockout is absent (11.4.6). |
| 11.4.5 | Isolated Groups | implemented | 3 | 2 | /Group /I true paints the group onto a transparent initial backdrop, so blend modes inside it see only the group's own objects (executor.rs group_kind and run; test non_isolated_group_blends_with_the_page_and_isolated_does_not in executor.rs); /CS is never read, so the flag alone decides and a group naming a blending colour space without /I true stays non-isolated. |
| 11.4.6 | Knockout Groups | not implemented | 1 | 1 | /Group /K is read and a true value is reported as an unsupported Knockout skip (executor.rs group_kind), but nothing knocks out: ExtGState /TK is never read and elements always composite over each other, glyphs one at a time through fill_spans. |
| 11.4.7 | Page Group | incomplete | 4 | 2 | The page renders onto an opaque white pixmap, matching a page group composited against white, and nested groups composite by the group formula (raster.rs mix_group, composite_group), but the page dictionary's /Group (/CS, /I, /K) is never read. |
| 11.4.8 | Summary of Group Compositing Computations | incomplete | 1 | 0 | Summary clause; the general formula is assessed under 11.4.4, the isolated initialization under 11.4.5 and the knockout backdrop choice under 11.4.6 (raster.rs composite_group cites it). |
| 11.5.2 | Deriving a Soft Mask from Group Alpha | implemented | 2 | 1 | The /G form renders on a transparent offscreen page as an isolated group and its alpha channel becomes the mask. |
| 11.5.3 | Deriving a Soft Mask from Group Luminosity | implemented | 2 | 2 | The group renders over an opaque black or /BC backdrop and Rec. 601 luma times alpha becomes the mask with /TR applied; /BC is read as one gray component whatever the group colour space and has no test, and luminosity is taken in device RGB rather than the group /CS. |
| 11.6.2 | Specifying Source and Backdrop Colours | implemented | 1 | 2 | The source colour is the current fill or stroke colour, image sample or shading value converted to RGB, and the backdrop is the pixel already on the page or offscreen mask buffer; no group backdrop exists (11.4). |
| 11.6.3 | Specifying Blending Colour Space and Blend Mode | incomplete | 2 | 2 | /BM as a name or array is honoured, but the blending colour space is never read from a group /CS and is always device RGB. |
| 11.6.4 | Specifying Shape and Opacity | implemented | 10 | 16 | Object coverage, the graphics-state soft mask and the constant alpha all feed one source alpha; see 11.6.4.2 to 11.6.4.4. |
| 11.6.4.2 | Object Shape and Opacity | implemented | 2 | 3 | Fills, strokes, glyph outlines and stencil masks contribute anti-aliased coverage as shape, and image sample alpha (/SMask, /Mask, JPX alpha) contributes opacity. |
| 11.6.4.3 | Mask Shape and Opacity | implemented | 2 | 4 | The ExtGState soft mask multiplies into the alpha of every paint (fills, strokes, glyphs, images, shadings, tiles) and follows q/Q; it is folded into one alpha rather than kept as mask opacity distinct from shape. |
| 11.6.4.4 | Constant Shape and Opacity | implemented | 4 | 7 | /ca and /CA are clamped to 0..=1 and applied to fills, glyphs, images, shadings and tiles (ca) and to strokes (CA); stroke alpha /CA has no test anywhere in the tree. |
| 11.6.5 | Specifying Soft Masks | incomplete | 9 | 16 | Soft-mask dictionaries are complete (11.6.5.2); soft-mask images lack /Matte handling (11.6.5.3). |
| 11.6.5.2 | Soft-Mask Dictionaries | implemented | 2 | 8 | /S (Alpha or Luminosity), /G rendered at the CTM in force at gs from a fresh graphics state, /BC, /TR (function, /Identity, /Default) and /SMask /None are handled with failures reported; /BC is approximated as gray and untested. |
| 11.6.5.3 | Soft-Mask Images | implemented | 5 | 6 | An image /SMask (with /Decode, any dimensions) and JPX /SMaskInData 1 and 2 give per-sample alpha; a /Matte is converted through the base image's colour space (image.rs matte_rgb) and image.rs Rgba::unmatted gives each sample its original colour back before compositing, exact for device gray and RGB and approximate through a non-linear conversion, while a mask whose dimensions differ from the base's is used as stored (image.rs matte_is_unblended_before_compositing, executor.rs matte_samples_are_unblended_before_painting). |
| 11.6.6 | Transparency Group XObjects | incomplete | 6 | 6 | A /Group /S /Transparency form (page content or annotation appearance) runs from alphas 1, blend Normal and no soft mask, renders offscreen and composites once with the alpha, blend mode and soft mask in force at its Do (executor.rs FrameKind::TransparencyGroup, tests transparency_group_composites_with_the_outer_alpha, nested_transparency_groups_multiply_their_alphas, non_isolated_group_blends_with_the_page_and_isolated_does_not). An isolated group renders onto a transparent backdrop, a non-isolated one onto a copy of its backdrop and mixes back by the constant alpha; a non-isolated group under a non-Normal blend mode at its Do renders isolated instead. /K true is reported as SkippedKind::Knockout and not honoured, and /CS is never read (blending stays device RGB). |
| 11.6.7 | Patterns and Transparency | incomplete | 2 | 6 | Shading patterns and tiling cells take the invoking state's constant alpha, blend mode and soft mask, but a tiling cell is not composited as one transparency group, so translucent or overlapping tiles composite tile by tile. |
| 11.7.2 | Colour Spaces for Transparency Groups | not implemented | 0 | 0 | Group /CS is never read and no group colour space exists; everything blends in device RGB. |
| 11.7.3 | Spot Colours and Transparency | out of scope | 0 | 0 | Separation and DeviceN colours pass through their tint transforms into RGB before compositing, so this screen device has no spot colourants to composite separately. |
| 11.7.4 | Overprinting and Transparency | out of scope | 0 | 0 | /OP, /op and /OPM are never read; overprint simulation is a print-preview feature the screen rasterizer does not attempt, which covers 11.7.4.2 to 11.7.4.5 alike. |
| 11.7.5 | Rendering Parameters and Transparency | not implemented | 0 | 0 | Neither halftone and transfer parameters (11.7.5.2) nor rendering intents (11.7.5.3) take part in compositing. |
| 11.7.5.2 | Halftone and Transfer Function | out of scope | 0 | 0 | No halftones or graphics-state transfer functions exist (10.4, 10.5), so there is nothing to apply after group compositing. |
| 11.7.5.3 | Rendering Intent and Colour Conversions | not implemented | 0 | 0 | ri and /RI are parsed then discarded (executor.rs), the ICC crate uses only a profile's default transform (icc lib.rs), and all colour converts to sRGB at paint time rather than at group compositing. |
12 Interactive Features
| Clause | Feature | Status | Code citations | Test citations | Note |
|---|---|---|---|---|---|
| 12.2 | Viewer Preferences | implemented | 7 | 2 | crates/pdfboss-core/src/viewer_preferences.rs viewer_preferences_with reads the catalog's /ViewerPreferences into ViewerPreferences with every entry of Table 150: the six flags (false by default), NonFullScreenPageMode, Direction, the four page-boundary names (ViewArea, ViewClip, PrintArea, PrintClip, CropBox by default), PrintScaling (AppDefault by default and for a name the standard does not list, as the table says), Duplex, PickTrayByPDFSize and NumCopies (None where the table leaves the default to the reader) and PrintPageRange as (first, last) pairs of positive integers, indirect values followed and a name the standard does not list left at the default; Document::viewer_preferences and AsyncDocument::viewer_preferences expose it, None for a catalog without the dictionary (tests viewer_preferences.rs every_viewer_preference_is_read, missing_and_unknown_viewer_preferences_take_the_defaults; crates/pdfboss-aio/tests/parity.rs). Nothing acts on the preferences yet: Direction does not turn the reading order and DisplayDocTitle does not change what info prints; the writer's Viewer struct at crates/pdfboss-write/src/pdf.rs emits the catalog's /PageLayout, /PageMode and /OpenAction, which belong to 7.7.2, and no /ViewerPreferences. |
| 12.3.2 | Destinations | incomplete | 16 | 16 | Explicit /XYZ destinations are written for links, outline items and /OpenAction; crates/pdfboss-core/src/destination.rs parses explicit destination arrays (12.3.2.2) and looks named destinations up in the /Names /Dests tree and the catalog /Dests dictionary (12.3.2.3), and Document::destination follows a /Dest or /D value of either kind; no writer API accepts a destination by name, no destination is followed to a page index, and the importer does not rewrite destinations. |
| 12.3.2.2 | Explicit Destinations | implemented | 7 | 10 | crates/pdfboss-core/src/destination.rs Destination::parse reads every Table 151 form: the page as a reference (DestinationPage::Object) or, for a remote go-to action, a 0-based number (DestinationPage::Number); Fit::Xyz, FitH, FitV, FitBH and FitBV keep a null or left-out coordinate as None (the viewer's current value) and read a zoom of 0 as null, FitR needs all four numbers, an unknown fit name or a page that is neither rejects the array; destination_with resolves an indirect array and indirect numbers while leaving the page reference a reference (tests destination.rs every_table_151_form_parses, null_zero_zoom_and_missing_parameters_keep_the_current_value, remote_destinations_carry_a_page_number, malformed_destinations_are_rejected, indirect_arrays_and_numbers_resolve). The writer emits [page /XYZ null null null] only, which the table allows; the importer copies a source /GoTo /D unchanged instead of rewriting it onto the imported page, a limitation of page assembly documented in docs/src/guide/assembling.md and pinned by crates/pdfboss-write/tests/importer.rs page_import_of_a_linked_source_carries_the_linked_page_along. |
| 12.3.2.3 | Named Destinations | implemented | 5 | 3 | crates/pdfboss-core/src/destination.rs named_destination_with looks a name up in the catalog's /Names /Dests tree (PDF 1.2, string keys, through tree.rs lookup) and then in the catalog's /Dests dictionary (PDF 1.1, name keys), taking a destination array or a dictionary whose /D holds one; named_destinations_with lists both sources sorted by name with the tree winning a duplicate; destination_value_with follows a /Dest or /D value that is an array, a name, a string or a dictionary with /D. Document::named_destination, named_destinations and destination on the sync side, AsyncDocument twins on the async side (tests destination.rs named_destinations_come_from_the_name_tree_and_the_catalog_dictionary, destination_values_follow_arrays_names_strings_and_d_dictionaries; crates/pdfboss-aio/tests/parity.rs names fixture). The writer still has no API that accepts a destination by name. |
| 12.3.3 | Document Outline | implemented | 7 | 7 | crates/pdfboss-core/src/outline.rs outline_with walks the catalog /Outlines through /First and /Next in display order and returns OutlineItem trees: /Title decoded as a text string, the destination from /Dest or from a /GoTo action's /D through destination.rs, open from a positive /Count (Table 153's recursive count is the writer's concern), /C as the title colour, the italic and bold bits of /F (Table 154) and /SE as a reference; a node seen twice or one that is not a dictionary ends its chain, 65536 items and 64 levels bound the walk. Document::outline and AsyncDocument::outline (tests outline.rs outline_items_nest_by_first_and_next, cycles_and_missing_outlines_end_quietly; crates/pdfboss-aio/tests/parity.rs outline fixture). The writer emits a complete outline tree (/Outlines with /First, /Last and /Count; items with /Title, /Parent, /Prev, /Next, /First, /Last, /Count and /Dest) with every item open and never writes /A, /SE, /C or /F, all optional (crates/pdfboss-write/src/pdf.rs Outline and Bookmark). |
| 12.3.4 | Thumbnail Images | implemented | 9 | 6 | crates/pdfboss-core/src/thumbnail.rs thumbnail_with reads a page's /Thumb image XObject into a Thumbnail with the five significant entries (Width, Height, ColorSpace as written, BitsPerComponent, Decode) and the stream itself (Document::thumbnail, AsyncDocument::thumbnail); crates/pdfboss-render/src/extract.rs page_thumbnail decodes it through the image decoder at its own size; pdfboss info counts the pages that carry one and pdfboss images --thumbnails writes them as page-N-thumb.png (tests thumbnail.rs reads_the_pages_thumbnail_and_its_significant_entries, extract.rs page_thumbnail_decodes_the_thumb_image_at_its_own_size, crates/pdfboss-aio/tests/parity.rs, crates/pdfboss-cli/src/main.rs cmd_images_writes_thumbnails_with_the_flag). The writer emits no thumbnails, which the clause leaves optional. |
| 12.3.5 | Collections | not implemented | 0 | 0 | The catalog /Collection dictionary and collection schemas are never read or written (the only Collection identifiers in the code are CJK CMap character collections). |
| 12.4.2 | Page Labels | implemented | 9 | 9 | crates/pdfboss-core/src/page_label.rs holds LabelStyle (the five /S styles of Table 159, with numeral giving decimal, subtractive Roman and repeated-letter forms) and PageLabel (first page, style, /P prefix, /St start); page_labels_with reads the catalog /PageLabels number tree through tree.rs entries, skipping non-dictionary values, reading an unknown /S as no style and a /St below 1 as 1; page_label picks the last range at or before a page and falls back to the 1-based page number for a page before the first range, which the clause forbids but files do. Document::page_labels and page_label, AsyncDocument twins (tests page_label.rs styles_number_pages_as_table_159_says, the_clauses_example_labels_its_pages, ranges_without_a_style_or_a_page_zero_still_label; crates/pdfboss-aio/tests/parity.rs outline fixture). The writer re-exports the same types and emits the /PageLabels tree with /S, /P and /St per range, validating the page-0 range, duplicate first pages and start_at (crates/pdfboss-write/src/pdf.rs page_labels_dict). The CLI, TUI and Python bindings do not show labels yet. |
| 12.4.3 | Articles | implemented | 12 | 4 | crates/pdfboss-core/src/article.rs articles_with reads the catalog's /Threads array into ArticleThread records (Table 160: the /I information dictionary through the shared metadata reader, the beads from /F along /N in chain order until the chain returns to a seen bead or breaks, each Bead with its Table 161 /P page and normalized /R rectangle), and page_beads_with reads a page's /B array in drawing order; Document::articles and page_beads and their AsyncDocument twins expose them, and pdfboss info counts threads and beads (tests article.rs walks_each_threads_beads_in_order_and_reads_its_information and a_broken_chain_stops_and_a_document_without_threads_has_none, crates/pdfboss-aio/tests/parity.rs, crates/pdfboss-cli/src/main.rs info_text_counts_article_threads). The writer emits no threads, which the clause leaves optional. |
| 12.4.4 | Presentations | implemented | 8 | 4 | crates/pdfboss-core/src/presentation.rs presentation_with reads a page's /Dur display duration and its /Trans transition dictionary into a Presentation whose Transition carries every Table 162 entry with the table's defaults filled in (TransitionStyle from /S with R as the default, /D, Dimension from /Dm, Motion from /M, TransitionDirection from /Di as an angle or the name None, /SS, /B); Document::presentation and AsyncDocument::presentation expose it and pdfboss info counts the pages that are slides (tests presentation.rs reads_the_duration_and_the_transition_with_the_tables_defaults and pages_without_the_entries_have_no_presentation_and_odd_values_fall_back, crates/pdfboss-aio/tests/parity.rs, crates/pdfboss-cli/src/main.rs info_text_counts_slide_pages). Playing a presentation is viewer behaviour, /PresSteps belongs to 12.4.4.2, and the writer emits no /Dur or /Trans, which the clause leaves optional. |
| 12.4.4.2 | Sub-page Navigation | out of scope | 0 | 0 | Navigation nodes (/PresSteps) drive keyboard-stepped presentation playback, which needs a viewer; pdfboss neither reads nor writes them. |
| 12.5.2 | Annotation Dictionaries | implemented | 15 | 9 | crates/pdfboss-core/src/annotation.rs annotations_with reads every Table 164 entry of a page's /Annots in order (Subtype, Rect normalized, Contents, P, NM, M as written and parsed, F, the presence of /AP, AS, Border with its dash array, C, StructParent, OC) into Annotation, exposed as Document::annotations, AsyncDocument::annotations and Page.annotations in Python, and pdfboss info counts annotations by subtype; the renderer reads /Subtype, /Rect, /F, /AP, /AS, /OC and /C for painting, the element listings expose only /Subtype, and the writer emits /Type, /Subtype, /Rect, /Border and /A for links. Tests: annotation.rs reads_the_entries_common_to_every_annotation and direct_malformed_and_missing_annotations. |
| 12.5.3 | Annotation Flags | implemented | 10 | 6 | Hidden (bit 2) and NoView (bit 6) suppress painting, Invisible (bit 1) suppresses it for annotations outside Table 169's standard types, NoRotate (bit 5) turns the appearance back by the page /Rotate about the upper-left corner of /Rect so it stays upright where the rotation put that corner (paint_annotations in crates/pdfboss-render/src/executor.rs), and /OC visibility is honoured. Print, NoZoom and the interactive flags (ReadOnly, Locked, ToggleNoView, LockedContents) have no effect on a raster of the on-screen view at a chosen resolution. Tests: invisible_flag_hides_only_unknown_annotation_types and no_rotate_annotations_stay_upright_on_a_rotated_page in crates/pdfboss-render/src/executor.rs. |
| 12.5.4 | Border Styles | implemented | 3 | 2 | Border::read in crates/pdfboss-render/src/annot.rs takes /BS (/W, /S, and /D under /S /D) or else /Border ([h v w [dash]]): Line, Square, Circle, Polygon, PolyLine and Ink appearances use the width and dash pattern, and a Link without /AP gets its border drawn inside /Rect in /C by Paint::link_border with the /Border corner radii, the U style as a line along the bottom edge, and a width past half the rectangle falling back to 1 as pdf.js does. The B and I styles are drawn solid, and a Link declaring neither /Border nor /BS draws no border, as Acrobat and pdf.js do rather than the clause's 1-point default. The writer puts /Border [0 0 0] on every link so no border shows. Test: link_borders_without_appearance_follow_the_border_style in crates/pdfboss-render/src/executor.rs. |
| 12.5.5 | Appearance Streams | implemented | 3 | 6 | The /AP /N normal appearance is selected (through /AS for a state dictionary, a single-state dictionary needing no /AS), its /BBox transformed by /Matrix is fitted onto the normalized /Rect per the clause's algorithm and run as a form XObject, and a declared appearance that cannot be read or placed is reported; /R and /D are pointer states a rasterizer does not show. |
| 12.5.6 | Annotation Types | incomplete | 51 | 41 | Assessed as a group: every type paints through its /AP /N appearance, /Popup annotations are skipped, /AS state selection serves Widget check boxes and radio buttons, and pdfboss-write emits /Link annotations; without /AP only Line, Square, Circle, Polygon, PolyLine, Ink and the four text markup types get an appearance synthesized from their own entries and a Link gets the border its /Border or /BS declares (crates/pdfboss-render/src/annot.rs), every other type paints nothing; text extraction reads the /QuadPoints of the four text markup subtypes (12.5.6.10) into span flags. |
| 12.5.6.2 | Markup Annotations | implemented | 4 | 2 | Every Table 170 entry (/T, /Popup, /CA with its default 1, /RC as a string or a text stream, /CreationDate, /IRT, /Subj, /RT with its default R, /IT) is read into Markup for the seventeen markup subtypes of Table 169, and a Link, Popup or Widget carries none (crates/pdfboss-core/src/annotation.rs read_markup; test reads_the_markup_entries); the renderer folds /CA into the appearances it builds for annotations without /AP and leaves an existing appearance stream as written (Paint::read in crates/pdfboss-render/src/annot.rs; test constant_opacity_applies_to_synthesized_appearances_only in executor.rs). /ExData, which only 3D markup uses, is not read. |
| 12.5.6.3 | Annotation States | implemented | 1 | 1 | A text annotation's /StateModel and /State are read into AnnotationState, the state defaulting to Unmarked under Marked and None under Review when only the model is given, with the /IRT reference to the annotation whose state changes carried in its Markup (crates/pdfboss-core/src/annotation.rs read_state; test reads_the_state_a_reply_sets); no per-user state is aggregated across a reply thread, which is a viewer's comments pane. |
| 12.5.6.4 | Text Annotations | incomplete | 2 | 1 | /Open, /Name (the icon), /State and /StateModel are read into Annotation (crates/pdfboss-core/src/annotation.rs read_annotation; test reads_the_state_a_reply_sets); painting goes only through the group /AP path (tested with /Stamp and Widget fixtures, not /Text), no icon is synthesized without /AP, and NoRotate keeps the icon upright on a rotated page (12.5.3) but the fixed-size NoZoom behaviour the clause expects is not applied. |
| 12.5.6.5 | Link Annotations | implemented | 6 | 11 | Link annotations with /Rect, /Border [0 0 0] and a /URI or /GoTo action are written from Rust, Python, Markdown and the TOML manifest; on read the annotation reader resolves /Dest, explicit or named, into a Destination and /A into an Action chain (crates/pdfboss-core/src/annotation.rs read_annotation; test reads_link_destinations_and_popups), and a Link without /AP paints only the border its /Border or /BS entry declares (12.5.4); /H, /PA and /QuadPoints are not read, and text extraction does not report link targets. |
| 12.5.6.6 | Free Text Annotations | incomplete | 1 | 0 | Painted only through the group /AP path; /DA, /Q, /RC, /DS, /CL, /IT, /BE and /RD are ignored and no appearance is generated from them. |
| 12.5.6.7 | Line Annotations | implemented | 4 | 1 | A Line without /AP is painted from its own entries by Paint::line in crates/pdfboss-render/src/annot.rs: the /L segment stroked in /C with the /BS or /Border width and dash pattern, moved sideways by /LL with the leader lines, their /LLE extensions and /LLO gaps, and the /LE endings of Table 176 drawn at the line width with the closed ones filled in /IC; the appearance is placed in default user space, so an empty or wrong /Rect neither moves nor clips it. /Cap captions with /CO and /CP, /IT and /Measure are not drawn. Test: line_annotations_without_appearance_are_drawn in crates/pdfboss-render/src/executor.rs. |
| 12.5.6.8 | Square and Circle Annotations | implemented | 3 | 1 | A Square or Circle without /AP is painted from its own entries by synthesized in crates/pdfboss-render/src/annot.rs: the rectangle or the inscribed ellipse inside /Rect shrunk by /RD, stroked in /C with the /BS or /Border width and dash pattern and filled with /IC, at the /CA opacity; an /AP still takes precedence and a /BE cloudy border is drawn plain. Test: square_and_circle_annotations_without_appearance_are_drawn in crates/pdfboss-render/src/executor.rs. |
| 12.5.6.9 | Polygon and Polyline Annotations | implemented | 3 | 1 | A Polygon or PolyLine without /AP is painted from its own entries by Paint::polyline in crates/pdfboss-render/src/annot.rs: the /Vertices joined by straight lines in /C at the /BS or /Border width and dash pattern, a polygon closed back to its first vertex and filled with /IC, a polyline left open with the /LE endings of Table 176 at its first and last vertex; the appearance is placed in default user space by its own box. /BE cloudy borders are drawn plain and /IT and /Measure are not read. Test: polygon_and_polyline_annotations_without_appearance_are_drawn in crates/pdfboss-render/src/executor.rs. |
| 12.5.6.10 | Text Markup Annotations | implemented | 5 | 10 | A Highlight, Underline, StrikeOut or Squiggly without /AP is painted from its own entries by Paint::markup in crates/pdfboss-render/src/annot.rs: each /QuadPoints quadrilateral is taken by its extent (writers disagree on the corner order), a Highlight filling it in /C with the Multiply blend mode so the text under it stays legible, an Underline stroking a 1-unit line along its bottom edge, a StrikeOut one through its middle and a Squiggly a zigzag a quarter of its height along the bottom, all at the /CA opacity and placed in default user space by their own box. Text extraction reads the same /QuadPoints (each quadrilateral by its extent, /Rect without them) into the span flags underline, strikethrough and highlight, with the /C color on highlights and Hidden, NoView and /OC-hidden annotations skipped (pdfboss-text decorations.rs markup_annotations, tested by the annotation tests in pdfboss-text lib.rs). Test: text_markup_annotations_without_appearance_are_drawn in crates/pdfboss-render/src/executor.rs. |
| 12.5.6.11 | Caret Annotations | incomplete | 1 | 0 | Painted only through the group /AP path; /RD and /Sy are ignored. |
| 12.5.6.12 | /Stamp annotations | implemented | 2 | 5 | /Stamp fixtures are the group's main test subject: the /AP appearance is fitted onto /Rect, hidden and OC-gated ones stay silent, and unreadable ones are reported; the /Name icon is not synthesized when /AP is absent. |
| 12.5.6.13 | Ink Annotations | implemented | 2 | 1 | An Ink without /AP is painted from its own entries by Paint::ink in crates/pdfboss-render/src/annot.rs: every /InkList path stroked through its points with straight segments, round caps and joins in /C at the /BS or /Border width and dash pattern, a single point becoming a dot of the line width, at the /CA opacity; the appearance is placed in default user space by its own box, so an unnormalized /Rect neither moves nor clips it. Curve smoothing between the points (permitted by ISO 32000-2) is not done. Test: ink_annotations_without_appearance_are_drawn in crates/pdfboss-render/src/executor.rs. |
| 12.5.6.14 | Pop-up Annotations | implemented | 2 | 2 | Pop-up annotations are skipped by the renderer even when they carry an /AP, which matches a static page render where the pop-up window is viewer UI; the annotation reader exposes /Parent and /Open (crates/pdfboss-core/src/annotation.rs read_annotation; test reads_link_destinations_and_popups). |
| 12.5.6.15 | File Attachment Annotations | incomplete | 2 | 1 | The annotation reader carries /FS as a FileSpec and /Name as the icon name, and Document::file_spec_data decodes the embedded stream (crates/pdfboss-core/src/annotation.rs read_annotation; test reads_a_file_attachment_and_its_data); painting goes only through the group /AP path and no icon is synthesized without /AP. |
| 12.5.6.16 | Sound Annotations | incomplete | 1 | 0 | Painted only through the group /AP path; /Sound and /Name are ignored and playback is viewer behavior. |
| 12.5.6.17 | Movie Annotations | incomplete | 1 | 0 | Painted only through the group /AP path; /Movie, /A and /T are ignored and playback is viewer behavior. |
| 12.5.6.18 | Screen Annotations | incomplete | 1 | 0 | Painted only through the group /AP path; /MK, /A and /AA are ignored and media playback is viewer behavior. |
| 12.5.6.19 | Widget Annotations | implemented | 4 | 1 | A widget's appearance comes from /AP /N with /AS selecting among the state dictionary's streams, and an /AS naming no state is reported as a dropped annotation; /H, /BS and the /MK colours and rotation are not read, so no appearance is generated for a widget lacking /AP. The /MK captions and icons reach the form fields (12.7.4.2.2). |
| 12.5.6.20 | Printer’s Mark Annotations | incomplete | 1 | 0 | Painted through the group /AP path like any other annotation; /MN is ignored and there is no print-versus-display distinction, so marks always show. |
| 12.5.6.21 | Trap Network Annotations | incomplete | 1 | 0 | Painted through the group /AP path; /LastModified, /Version, /AnnotStates and /FontFauxing are ignored and the page /LastModified consistency rule is not checked. |
| 12.5.6.22 | Watermark Annotations | incomplete | 1 | 0 | Painted through the group /AP path with the page; the /FixedPrint dictionary (/Matrix, /H, /V) is ignored, and the overlay command in crates/pdfboss-write/src/update.rs adds page content rather than a /Watermark annotation. |
| 12.5.6.23 | Redaction Annotations | incomplete | 1 | 0 | Painted through the group /AP path; /QuadPoints, /IC, /RO, /OverlayText, /Repeat, /DA and /Q are ignored, and applying a redaction (removing the content beneath) is not offered. |
| 12.6.2 | Action Dictionaries | implemented | 7 | 5 | crates/pdfboss-core/src/annotation.rs action_with reads an action dictionary's /S and its /Next entry, a single dictionary or an array, into an Action tree in order, a visited set over references stopping a chain that points back at an earlier action and a budget of 256 dictionaries stopping a runaway tree (tests follows_next_chains_once and a_runaway_next_chain_stops_at_the_budget); the writer emits /URI and /GoTo dictionaries without /Next, and no action is ever executed. |
| 12.6.3 | Trigger Events | implemented | 10 | 5 | The additional-actions dictionaries of annotations (Table 194, with the field triggers of Table 196 a merged widget and field dictionary carries), pages (Table 195) and the catalog (Table 197) are read into TriggeredAction lists in table order (crates/pdfboss-core/src/annotation.rs triggered_actions, page_additional_actions_with and document_additional_actions_with; test reads_trigger_events_on_annotations_pages_and_the_catalog); the writer emits the catalog /OpenAction as an explicit destination to the open_to page, and firing a trigger is viewer behaviour. |
| 12.6.4 | Action Types | implemented | 31 | 18 | Every Table 198 type is read as data and none is executed: GoTo, GoToR, GoToE, Launch, URI, Named and JavaScript into their own ActionKind entries, the other ten kinds with their dictionary kept as written (crates/pdfboss-core/src/annotation.rs read_action_kind; test reads_every_action_type); the writer emits only /URI and /GoTo. |
| 12.6.4.2 | Go-To Actions | implemented | 4 | 3 | /GoTo actions with /S /GoTo and an explicit /D destination are written for page-target links with the target page range-checked; on read the destination, explicit or named, resolves through the document's destinations into ActionKind::GoTo, the name kept beside it (crates/pdfboss-core/src/annotation.rs destination_entry; test reads_link_destinations_and_popups), a /GoTo is never followed (viewer behavior) and the importer copies it unchanged. |
| 12.6.4.3 | Remote Go-To Actions | implemented | 2 | 2 | /GoToR actions read their /F file specification, an explicit /D whose first element is a 0-based page number of the other document or a named /D kept as the name, and /NewWindow (crates/pdfboss-core/src/annotation.rs read_action_kind and destination_entry; test reads_remote_go_to_actions); nothing writes or follows one. |
| 12.6.4.4 | Embedded Go-To Actions | implemented | 8 | 1 | /GoToE actions read /F, /D, /NewWindow and the /T target chain, each Table 202 step with /R, /N, /P (a page number or a named destination) and /A (an annotation index or name), nested through /T to a depth of 32 (crates/pdfboss-core/src/annotation.rs read_target; test reads_embedded_go_to_targets); nothing writes or follows one. |
| 12.6.4.5 | Launch Actions | implemented | 6 | 1 | /Launch actions read /F, /NewWindow and the Windows parameters of /Win (Table 204: /F, /D, /O with its default open, /P) as data (crates/pdfboss-core/src/annotation.rs read_windows_launch; test reads_launch_actions); /Mac and /Unix are undefined by the standard, nothing writes a launch action, and launching an application is viewer behaviour. |
| 12.6.4.6 | Thread Actions | out of scope | 0 | 0 | Thread actions step through article beads inside a viewer; pdfboss executes no actions and does not model article threads. |
| 12.6.4.7 | URI Actions | implemented | 4 | 6 | /URI actions with /S /URI and the URI as a text string are written for external links from every writer front end; on read the annotation reader carries /URI and /IsMap in ActionKind::Uri (crates/pdfboss-core/src/annotation.rs read_action_kind; test reads_every_action_type); the catalog-level /URI base dictionary is not handled, and text extraction does not report URIs. |
| 12.6.4.8 | Sound Actions | out of scope | 0 | 0 | Sound playback needs a viewer with audio output; pdfboss executes no actions. |
| 12.6.4.9 | Movie Actions | out of scope | 0 | 0 | Movie playback needs a viewer with media output; pdfboss executes no actions. |
| 12.6.4.10 | Hide Actions | out of scope | 0 | 0 | Hiding or showing annotations at run time is viewer state; the renderer honors only the static Hidden flag. |
| 12.6.4.11 | Named Actions | out of scope | 1 | 1 | NextPage, PrevPage, FirstPage and LastPage are viewer navigation; pdfboss executes no actions. |
| 12.6.4.12 | Set-OCG-State Actions | out of scope | 0 | 0 | Toggling optional content groups at run time is viewer state; the renderer applies only the document's default /OCProperties /D configuration. |
| 12.6.4.13 | Rendition Actions | out of scope | 0 | 0 | Multimedia rendition playback needs a viewer; pdfboss executes no actions. |
| 12.6.4.14 | Transition Actions | out of scope | 0 | 0 | Page transitions are presentation playback inside a viewer; pdfboss executes no actions. |
| 12.6.4.15 | Go-To-3D-View Actions | out of scope | 0 | 0 | 3D artwork views need a 3D-capable viewer; pdfboss neither renders 3D streams nor executes actions. |
| 12.6.4.16 | JavaScript Actions | out of scope | 1 | 1 | pdfboss has no JavaScript engine and never executes document scripts. |
| 12.7.2 | Interactive Form Dictionary | implemented | 8 | 2 | Document::interactive_form reads the catalog /AcroForm dictionary: /Fields and /CO as reference lists, /NeedAppearances, the /SigFlags bits SignaturesExist and AppendOnly, /DR, /DA as written, /Q as a quadding and whether /XFA is present; absent or ill-typed entries take the Table 218 defaults. Nothing writes the dictionary. |
| 12.7.3 | Field Dictionaries | implemented | 15 | 11 | Document::form_fields (crates/pdfboss-core/src/form.rs form_fields_with) walks the /Fields tree depth first into one FormField per field dictionary: /FT, /Ff, /V and /DV are taken from the nearest ancestor when the field lacks them, /T, /TU and /TM are decoded as text strings, /AA is kept as written, a /Kids entry that is a /Subtype /Widget dictionary without a /T is recorded as one of the field's Widget records (reference, /AS, on state) and a merged field is its own widget; a reference seen before, a non-dictionary kid and nesting past 64 levels are skipped. Only the render path uses fields so far, and nothing writes them. |
| 12.7.3.2 | Field Names | implemented | 1 | 2 | FormField::name is the fully qualified name (crates/pdfboss-core/src/form.rs qualified_name): the partial /T names from the root field down joined by a PERIOD, a field without /T sharing its parent's name, an unnamed root the empty name. /T itself is kept as partial_name. Nothing checks that a partial name is free of periods. |
| 12.7.3.3 | Variable Text | incomplete | 3 | 4 | FormField::default_appearance and quadding (crates/pdfboss-core/src/form.rs) carry a field's /DA and /Q from its own dictionary, its nearest ancestor or the interactive form's document-wide defaults, left-justified when none sets /Q; DefaultAppearance::parse reads the Tf font resource name and size and the g, rg or k fill colour out of the string; default_style and rich_text hold /DS and /RV as text, a /RV text stream read into a string. No appearance stream is generated for text-bearing fields, so /NeedAppearances documents still show only pre-existing /AP streams. |
| 12.7.3.4 | Rich Text Strings | not implemented | 0 | 0 | /RV reaches FormField::rich_text as text and /DS FormField::default_style (12.7.3.3); the rich text markup and the style string are not parsed. |
| 12.7.4 | Field Types | implemented | 18 | 21 | FieldType (crates/pdfboss-core/src/form.rs) names the four field types of Table 220 and each has its row: buttons by kind and state (12.7.4.2), text with its text and MaxLen (12.7.4.3), choice with options and selection (12.7.4.4), signature as a signature dictionary (12.7.4.5); the widget-level /AS state selection that check boxes and radio buttons rely on is handled by the render path. No appearance stream is generated for any type (12.7.3.3). |
| 12.7.4.2 | Button Fields | implemented | 10 | 12 | FormField::button_kind (crates/pdfboss-core/src/form.rs) tells pushbuttons, check boxes and radio button fields apart by the Table 226 Pushbutton and Radio bits, FieldFlags exposes no_toggle_to_off, radio, pushbutton and radios_in_unison, and FormField::state returns the /V appearance-state name of a check box or radio button field (none for a pushbutton, which keeps no value); the render path selects each widget's appearance through /AS. Matching /V to the individual widgets' states is the radio button row's. |
| 12.7.4.2.2 | Pushbuttons | implemented | 1 | 2 | Each Widget carries its /MK appearance characteristics (crates/pdfboss-core/src/form.rs AppearanceCharacteristics, Table 189): the normal, rollover and alternate captions decoded as text strings, the normal, rollover and alternate icon references and the /TP caption position; FormField::button_kind names a pushbutton by its flag and FormField::state gives it no value. No appearance is generated from these entries: a pushbutton still paints only through a pre-existing /AP. |
| 12.7.4.2.3 | Check Boxes | implemented | 4 | 3 | FormField::checked (crates/pdfboss-core/src/form.rs) reports a check box as checked when its /V state is a name other than Off, an absent value being the off state; each Widget carries its /AS and the on state read as the one non-Off key of its /AP /N dictionary, FormField::on_widgets lists the widgets whose on state is the field's state, and the Table 227 /Opt export values are the field's options, indexed like the widgets. The render path selects the on or off appearance by /AS. |
| 12.7.4.2.4 | Radio Buttons | implemented | 2 | 4 | FormField::on_widgets (crates/pdfboss-core/src/form.rs) picks the kid widgets whose on state, the non-Off key of their /AP /N, is the parent field's /V, several at once for a RadiosInUnison group; FormField::state is Off for a radio button field without /V, the clause's default; the Table 227 /Opt export values are the field's options, indexed like the widgets; NoToggleToOff and RadiosInUnison are FieldFlags bits. Toggling is viewer interaction, which pdfboss has none of; the render path selects each widget's appearance by /AS. |
| 12.7.4.3 | Text Fields | implemented | 1 | 3 | FormField::text (crates/pdfboss-core/src/form.rs) decodes a text field's /V as a text string, a PDF 1.5 stream value being read into a string during the walk; FormField::max_len reads the inheritable /MaxLen of Table 229; FieldFlags exposes the Table 228 bits multiline, password, file_select, do_not_spell_check, do_not_scroll, comb and rich_text. No appearance stream is generated (12.7.3.3) and /RV is not parsed (12.7.3.4), so a text field still shows only a pre-existing /AP. |
| 12.7.4.4 | Choice Fields | implemented | 4 | 3 | FormField::options (crates/pdfboss-core/src/form.rs) reads /Opt into ChoiceOption export_value/name pairs, a lone text string naming both; top_index reads /TI, selected_indices reads /I as written, and FormField::selected returns the /V name or names of a choice field; FieldFlags exposes the Table 230 bits combo, edit, sort, multi_select and commit_on_sel_change (do_not_spell_check is shared with text fields). /I is not reconciled with /V and no appearance stream is generated. |
| 12.7.4.5 | Signature Fields | implemented | 1 | 2 | FormField::signature (crates/pdfboss-core/src/form.rs) reads a signature field's /V as a Signature record with the Table 252 entries Filter, SubFilter, ByteRange pairs, Contents bytes, Name, M as written, Location, Reason and ContactInfo, and FormField::lock and seed_value hold the Table 232 /Lock and /SV references. The lock and seed value dictionaries' entries are not read and nothing is signed or verified (12.8). |
| 12.7.5 | Form Actions | out of scope | 0 | 0 | Submit, reset and import-data actions need a viewer, a form-filling state and a network; pdfboss executes no actions. |
| 12.7.5.2 | Submit-Form Action | out of scope | 0 | 0 | Submitting field data over HTTP is viewer behavior; pdfboss executes no actions. |
| 12.7.5.3 | Reset-Form Action | out of scope | 0 | 0 | Resetting fields to their defaults needs a form-filling state; pdfboss executes no actions. |
| 12.7.5.4 | Import-Data Action | out of scope | 0 | 0 | Importing FDF data into a form needs a form-filling state; pdfboss executes no actions. |
| 12.7.6 | Named Pages | not implemented | 0 | 0 | The /Names /Pages and /Names /Templates name trees are never read or written. |
| 12.7.7 | Forms Data Format | not implemented | 0 | 0 | No FDF file is parsed or written anywhere in the code. |
| 12.7.7.2 | FDF File Structure | not implemented | 0 | 0 | The document loader recognizes only %PDF headers and never reads an FDF header, body or trailer. |
| 12.7.7.2.2 | FDF Header | not implemented | 0 | 0 | The %FDF header is not recognized. |
| 12.7.7.2.3 | FDF Body | not implemented | 0 | 0 | FDF body objects are never parsed. |
| 12.7.7.2.4 | FDF Trailer | not implemented | 0 | 0 | An FDF trailer (/Root only, no xref) is never parsed. |
| 12.7.7.3 | FDF Catalog | not implemented | 0 | 0 | The FDF catalog and its /FDF dictionary are never read or written. |
| 12.7.7.3.2 | FDF Fields | not implemented | 0 | 0 | FDF field dictionaries are never read or written. |
| 12.7.7.3.3 | FDF Pages | not implemented | 0 | 0 | FDF page and template dictionaries are never read or written. |
| 12.7.7.3.4 | FDF Annotation Dictionaries | not implemented | 0 | 0 | FDF annotation dictionaries with /Page are never read or written. |
| 12.7.8 | XFA Forms | not implemented | 0 | 0 | The /AcroForm /XFA entry and XFA packets are never read, so an XFA document renders only its PDF page content. |
| 12.8.1 | General | incomplete | 5 | 0 | The Signature record (crates/pdfboss-core/src/form.rs) reads Table 252's Filter, SubFilter, ByteRange, Contents, Name, M, Location, Reason and ContactInfo as data through FormField::signature; Cert, Reference, Changes and the Prop_ entries are not read, nothing is written, and there is no signing or verification. |
| 12.8.2 | Transform Methods | not implemented | 0 | 0 | Signature reference dictionaries and their /TransformMethod are never read. |
| 12.8.2.2 | DocMDP | not implemented | 0 | 0 | DocMDP transform parameters (/P, /V) are never read. |
| 12.8.2.2.2 | Validating Signatures That Use the DocMDP Transform Method | not implemented | 0 | 0 | No DocMDP validation exists, and the incremental-update writer in crates/pdfboss-write/src/update.rs appends changes without checking for a certifying signature. |
| 12.8.2.3 | UR | not implemented | 0 | 0 | Usage-rights transform parameters are never read. |
| 12.8.2.4 | FieldMDP | not implemented | 0 | 0 | FieldMDP transform parameters (/Action, /Fields) are never read. |
| 12.8.3 | Signature Interoperability | not implemented | 0 | 0 | No /SubFilter value is recognized and no signature bytes are decoded. |
| 12.8.3.2 | PKCS#1 Signatures | not implemented | 0 | 0 | adbe.x509.rsa_sha1 signatures and /Cert chains are never decoded or verified. |
| 12.8.3.3 | PKCS#7 Signatures as used in ISO 32000 | not implemented | 0 | 0 | PKCS#7 signature containers are never decoded or verified. |
| 12.8.3.3.2 | Revocation Information | not implemented | 0 | 0 | Embedded CRL and OCSP data are never read. |
| 12.8.4 | Permissions | incomplete | 2 | 4 | crates/pdfboss-core/src/permission.rs permission_handlers_with reads the catalog's /Perms dictionary (Table 258) into PermissionHandlers: the /DocMDP and /UR3 signature dictionaries as Signature records through the shared Signature::from_dict (Table 252 as data); Document::permission_handlers and AsyncDocument::permission_handlers expose them and pdfboss info names the handlers present (tests permission.rs reads_the_doc_mdp_and_usage_rights_handlers and missing_or_malformed_permissions_read_as_absent, crates/pdfboss-aio/tests/parity.rs, crates/pdfboss-cli/src/main.rs info_text_lists_permission_handlers). The signatures are not verified and the permissions their DocMDP and UR transform parameters carry (12.8.2.2, 12.8.2.3) are neither read nor enforced; the /Perms handling in crates/pdfboss-core/src/crypt.rs is the AES-256 encryption dictionary entry of §7.6.3.3. |
| 12.8.5 | Legal Content Attestations | implemented | 6 | 4 | crates/pdfboss-core/src/legal.rs legal_attestation_with reads the catalog's /Legal dictionary (Table 259) into LegalAttestation: every count entry as an integer, 0 when absent or not a non-negative integer, and the /Attestation text; Document::legal_attestation and AsyncDocument::legal_attestation expose it and pdfboss info prints the non-zero counts by key and whether an attestation is present (tests legal.rs reads_the_legal_attestation_counts and missing_or_malformed_legal_reads_as_absent, crates/pdfboss-aio/tests/parity.rs, crates/pdfboss-cli/src/main.rs info_text_lists_the_legal_attestation). The counts are taken as written and not recomputed from the document, and pdfboss-write emits no /Legal dictionary (Legal elsewhere in the code is a page size). |
| 12.9 | Measurement Properties | implemented | 12 | 5 | crates/pdfboss-core/src/measure.rs viewports_with reads a page's /VP array (Table 260) into Viewport records with the normalized /BBox, the /Name and the /Measure dictionary, which measure_from reads (Tables 261 and 262) as a Measure with the /Subtype (RL default, others kept by name), the /R scale ratio, the /X, /Y, /D, /A, /T and /S number-format chains (Table 263, every entry with the table's default), the /O origin and /CYX; a viewport without a four-number /BBox and a number format without /U or /C are skipped, and array order is kept so the last viewport containing a point wins as the clause says. Document::viewports and AsyncDocument::viewports expose them and pdfboss info counts the viewports and the pages holding them (tests measure.rs reads_viewports_with_their_measures_and_the_tables_defaults, reads_the_optional_measure_entries and missing_or_malformed_viewports_read_as_none, crates/pdfboss-aio/tests/parity.rs, crates/pdfboss-cli/src/main.rs info_text_counts_viewports). The /Measure entry a line annotation may carry (Table 175) is not read there, and pdfboss-write emits no viewport. |
| 12.10.1 | General | implemented | 2 | 4 | crates/pdfboss-core/src/requirement.rs requirements_with reads the catalog's /Requirements array (Table 266) into Requirement records carrying the /S type and the /RH handlers (12.10.2), in array order, skipping an entry that is no dictionary or has no /S name; Document::requirements and AsyncDocument::requirements expose them and pdfboss info prints the types (tests requirement.rs reads_the_requirements_in_order and missing_or_malformed_requirements_read_as_none, crates/pdfboss-aio/tests/parity.rs, crates/pdfboss-cli/src/main.rs info_text_lists_requirements). Whether the reader meets a requirement is the consumer's call; pdfboss-write emits no /Requirements, which the clause does not ask of a writer. |
| 12.10.2 | Requirement Handlers | implemented | 2 | 3 | crates/pdfboss-core/src/requirement.rs handlers_with reads a requirement's /RH entry, one handler dictionary or an array of them (Table 267), into RequirementHandler records with the /S kind (JS or NoOp, other names kept as written) and the /Script name of the document-level JavaScript, skipping a handler that is no dictionary or has no /S (tests requirement.rs reads_the_requirement_handlers and missing_or_malformed_handlers_read_as_none, crates/pdfboss-aio/tests/parity.rs). The handler is data: pdfboss has no JavaScript engine, so running the named script is viewer behavior. |
13 Multimedia Features
| Clause | Feature | Status | Code citations | Test citations | Note |
|---|---|---|---|---|---|
| 13.1 | General | out of scope | 0 | 0 | pdfboss parses, extracts, renders and creates PDFs and is not a viewer, so multimedia playback has no place in it. |
| 13.2 | Multimedia | out of scope | 1 | 0 | No crate reads a rendition, media clip or media player dictionary; a grep for those key names finds nothing in the Rust sources. |
| 13.2.1 | General | out of scope | 0 | 0 | A Screen annotation's /AP /N appearance still paints generically through crates/pdfboss-render/src/executor.rs, but its /A rendition action and /MK are never read because playback is a viewer concern. |
| 13.2.2 | Viability | out of scope | 0 | 0 | Viability (the /MH must-honor and /BE best-effort dictionaries) only matters when choosing a player to run, which pdfboss never does. |
| 13.2.3 | Renditions | out of scope | 1 | 0 | Rendition dictionaries are never read because pdfboss does not play media. |
| 13.2.3.1 | General | out of scope | 1 | 0 | Same as 13.2.3: no rendition dictionary is read. |
| 13.2.3.2 | Media Renditions | out of scope | 0 | 0 | Media rendition dictionaries (/S /MR) are never read because pdfboss does not play media. |
| 13.2.3.3 | Selector Renditions | out of scope | 0 | 0 | Selector renditions (/S /SR) are never read because pdfboss does not play media. |
| 13.2.4 | Media Clip Objects | out of scope | 0 | 0 | Media clip dictionaries are never read because pdfboss does not play media. |
| 13.2.4.1 | General | out of scope | 0 | 0 | Same as 13.2.4: no media clip dictionary is read. |
| 13.2.4.2 | Media Clip Data | out of scope | 0 | 0 | Media clip data (/S /MCD) and its file specification are never read; pdfboss neither extracts nor plays embedded media. |
| 13.2.4.3 | Media Clip Section | out of scope | 0 | 0 | Media clip sections (/S /MCS) are never read because pdfboss does not play media. |
| 13.2.5 | Media Play Parameters | out of scope | 0 | 0 | Play parameters (volume, repeat, duration) only apply during playback, which pdfboss never does. |
| 13.2.6 | Media Screen Parameters | out of scope | 0 | 0 | Screen parameters (window, floating window, background) describe a viewer's playback window and are never read. |
| 13.2.6.1 | General | out of scope | 0 | 0 | Same as 13.2.6: no screen parameter dictionary is read. |
| 13.2.6.2 | Media Offset Dictionary | out of scope | 0 | 0 | Media offsets (time, frame, marker) are only meaningful during playback and are never read. |
| 13.2.6.3 | Timespan Dictionary | out of scope | 0 | 0 | Timespan dictionaries are only meaningful during playback and are never read. |
| 13.2.7 | Other Multimedia Objects | out of scope | 0 | 0 | Media players, player info, software identifiers and monitor specifiers describe a viewer's environment and are never read. |
| 13.2.7.1 | General | out of scope | 0 | 0 | Same as 13.2.7: none of these dictionaries is read. |
| 13.2.7.2 | Media Players Dictionary | out of scope | 0 | 0 | Player selection lists (/MU, /A, /NU) are never read because pdfboss does not select or run players. |
| 13.2.7.3 | Media Player Info Dictionary | out of scope | 0 | 0 | Player info dictionaries are never read because pdfboss does not select or run players. |
| 13.2.7.4 | Software Identifier Dictionary | out of scope | 0 | 0 | Software identifiers are never read because pdfboss does not select or run players. |
| 13.2.7.4.1 | General | out of scope | 0 | 0 | Same as 13.2.7.4: no software identifier is read. |
| 13.2.7.4.2 | Software URIs | out of scope | 0 | 0 | Software URIs are never read because pdfboss does not select or run players. |
| 13.2.7.4.3 | Version arrays | out of scope | 0 | 0 | Player version arrays are never read because pdfboss does not select or run players. |
| 13.2.7.5 | Monitor Specifier | out of scope | 0 | 0 | Monitor specifiers describe the viewer's display and are never read. |
| 13.3 | Sounds | out of scope | 0 | 0 | Sound objects and a Sound annotation's /Sound stream are never read; the annotation's /AP /N appearance paints generically through crates/pdfboss-render/src/executor.rs and crates/pdfboss-core/src/elements.rs lists it by subtype like any other annotation. |
| 13.4 | Movies | out of scope | 0 | 0 | Movie dictionaries and movie activation dictionaries are never read; a Movie annotation's /AP /N appearance paints generically through crates/pdfboss-render/src/executor.rs. |
| 13.5 | Alternate Presentations | out of scope | 0 | 0 | The /AlternatePresentations name tree and SlideShow dictionaries are never read because pdfboss does not present documents. |
| 13.6.1 | General | out of scope | 0 | 0 | 3D artwork (3D annotations, 3D streams, U3D data) is never read because pdfboss neither renders nor extracts 3D content. |
| 13.6.3.1 | General | out of scope | 0 | 0 | 3D stream dictionaries (/Type /3D) are never read; pdfboss does no 3D work. |
| 13.6.4.1 | General | out of scope | 0 | 0 | 3D view dictionaries are never read; pdfboss does no 3D work. |
| 13.6.4.2 | Projection Dictionaries | out of scope | 0 | 0 | 3D projection dictionaries are never read; pdfboss does no 3D work. |
| 13.6.5 | Coordinate Systems for 3D | out of scope | 0 | 0 | 3D coordinate systems are never used; pdfboss does no 3D work. |
14 Document Interchange
| Clause | Feature | Status | Code citations | Test citations | Note |
|---|---|---|---|---|---|
| 14.2 | Procedure Sets | not implemented | 0 | 0 | Nothing reads or emits /ProcSet: the reader ignores the resource (which the clause allows) and pdfboss-write never adds a /ProcSet array to the resource dictionaries it writes. |
| 14.3.2 | Metadata Streams | incomplete | 2 | 4 | The writer emits an unfiltered /Type /Metadata /Subtype /XML packet on the catalog and rewrites it on update, and the reader honors EncryptMetadata false for that stream, but no reader path parses XMP (Document::metadata reads only /Info) and metadata streams on pages, XObjects or other components are neither read nor written. |
| 14.3.3 | Document Information Dictionary | incomplete | 4 | 5 | Title, Author, Subject, Keywords, Creator, Producer, CreationDate and ModDate are read as decoded text strings and written back (with the XMP packet kept in step on update), but the /Trapped entry of Table 317 is neither read nor written. |
| 14.4 | File Identifiers | implemented | 2 | 4 | The writer emits a two-element /ID of 16-byte SHA-256-derived strings, an incremental update keeps the first half and replaces the second, the strings stay unencrypted, and the reader consumes the trailer /ID for the encryption key. |
| 14.5 | Page-Piece Dictionaries | implemented | 10 | 4 | crates/pdfboss-core/src/piece_info.rs piece_info_with reads a dictionary's /PieceInfo (Table 318) into one PagePiece per product with the Table 319 entries, /LastModified as written and parsed on request through PagePiece::last_modified_parsed and /Private kept as the object written; it serves the catalog (document_piece_info_with, Document::piece_info, AsyncDocument::piece_info), a page (Document::page_piece_info, AsyncDocument::page_piece_info) and, applied to a form dictionary, a form XObject. pdfboss info lists the product names (tests piece_info.rs reads_each_products_data_dictionary_of_a_page_piece_dictionary and a_pages_piece_info_reads_the_same_way_and_a_missing_entry_is_empty, crates/pdfboss-aio/tests/parity.rs, crates/pdfboss-cli/src/main.rs info_text_lists_page_piece_products). The writer emits no /PieceInfo, the clause making it optional. |
| 14.6.1 | General | implemented | 3 | 2 | MP, DP, BMC, BDC and EMC (Table 320) parse into distinct operators, and the text and render executors keep a nesting stack per content stream so EMC pops the matching open. |
| 14.6.2 | Property Lists | implemented | 2 | 3 | BDC and DP operands are accepted as an inline dictionary or as a name looked up in the resource chain's /Properties subdictionary, and the named form keeps the indirect-reference identity the optional-content code keys on. |
| 14.6.3 | Marked Content and Clipping | incomplete | 2 | 0 | Marked-content nesting is tracked on its own stack and EMC never touches the graphics state, so a clip set inside a sequence persists as the clause requires, but no test exercises a clipping path or q/Q pair that straddles a marked-content sequence. |
| 14.7.2 | Structure Hierarchy | incomplete | 2 | 4 | The catalog's /StructTreeRoot is loaded and elements are walked through /K (single or array) and /P up to the root, but /IDTree, /ParentTreeNextKey and the element entries /T, /ID and /ActualText are never read (/S and /RoleMap are, under 14.7.3, /A, /C, /R and /ClassMap under 14.7.5, /Alt under 14.9.3, /Lang under 14.9.2 and /E under 14.9.5), and pdfboss-write neither emits a structure tree nor carries a base document's tree through assembly. |
| 14.7.3 | Structure Types | implemented | 6 | 3 | crates/pdfboss-core/src/structure.rs reads each element's /S while placing a page's marked-content sequences (Walk::key_of) and the root's /RoleMap once per document (role_map_of, an indirect map resolved); StructureTree::mapped_type follows the map from a type name until a name it has no entry for, a chain in case a mapped name is itself mapped, a cycle leaving the name as written; StructureTree::place_with returns both names per sequence as Placement { rank, structure_type, mapped_type }, and ranks_with stays the rank-only view (tests structure.rs structure_types_are_read_and_mapped_through_the_role_map, an_element_without_a_type_places_with_none). Nothing yet classifies the mapped name as one of the standard types of 14.8.4. |
| 14.7.4 | Structure Content | implemented | 25 | 25 | Both kinds of content item are located: marked-content sequences through the parent tree's arrays (14.7.4.2, 14.7.4.4) and whole objects through the parent tree's element under their /StructParent and the object reference among the element's kids (14.7.4.3). crates/pdfboss-core/src/structure.rs ContentItem names the two kinds, StructureTree::content_items_with enumerates a page's sequences and annotations in one shared rank space (test the_page_enumeration_skips_null_entries_and_needs_a_parent_tree), place_items_with places any set of items, and Document::content_items, the async twin and Python Page.content_items expose them; the pdfboss info tagged line counts what the parent tree files for each page (content_item_counts_with) without reading the elements. Element dictionaries, paths and ancestries are cached on the tree across pages, and the parent tree's entries are read once at load; the depth-4 rows give the detail. |
| 14.7.4.2 | Marked-Content Sequences as Content Items | implemented | 3 | 4 | Bare integer MCIDs (matched against the element's /Pg) and marked-content reference dictionaries are matched, and MCIDs are collected from BDC in page and form content. A reference's own /Pg overrides the element's, as Table 324 says, and its /Stm is read: the named stream's /StructParents must be the key the sequence was filed under, so a form's sequence numbered like the page's no longer stands in for it (crates/pdfboss-core/src/structure.rs Walk::is_reference_for, test a_reference_naming_a_stream_matches_only_that_streams_sequence). /StmOwn only says which object owns the stream and is not needed to match. |
| 14.7.4.3 | PDF Objects as Content Items | implemented | 5 | 8 | An object reference dictionary (Table 325) is matched by its /Obj against the object's reference, direct kids first and then indirect ones, with its own /Pg overriding the element's and the element's used when it has none (crates/pdfboss-core/src/structure.rs Walk::object_key_of and Walk::object_index; tests an_annotation_takes_its_place_between_the_sequences, an_indirect_object_reference_is_read_after_the_direct_kids, the_object_reference_page_overrides_the_elements, a_link_element_orders_its_sequence_and_annotation_by_kid_index). The element itself comes from the parent tree under the object's /StructParent (14.7.4.4). StructureTree::content_items_with enumerates a page's annotations from /Annots; an XObject referenced this way is placed when handed to place_items_with (test a_form_xobject_is_placed_when_handed_in), since nothing enumerates the XObjects a page draws without running its content. A direct annotation dictionary in /Annots has no reference an OBJR could name and is skipped. |
| 14.7.4.4 | Finding Structure Elements from Content Items | implemented | 9 | 10 | The /ParentTree number tree (/Nums, /Kids, /Limits) is searched under the /StructParents key of the page or of a form XObject that declares its own, its array indexed by MCID (crates/pdfboss-core/src/structure.rs Walk::parent_array, test a_form_key_ranks_alongside_the_page), and under the singular /StructParent key of a whole object, whose entry is a reference to the element itself (Walk::parent_element; test an_annotation_the_tree_does_not_hold_is_no_content_item covers a key the tree lacks, an object without the key and an element without a reference to the object). /ParentTreeNextKey is a writer's concern and is not read. |
| 14.7.5 | Structure Attributes | implemented | 14 | 4 | crates/pdfboss-core/src/structure.rs Walk::attribute_objects reads every element's attribute objects while placing a page's marked-content sequences: the objects its /C classes name first, then its direct /A objects (Walk::direct_attributes: one dictionary or an array of dictionaries, indirect ones resolved), each as AttributeObject { element, owner (/O), revision, entries }; Placement::attributes lists them for the element and its ancestors, the root's child first, and Placement::attribute(element, owner, key) answers with the last object that has the key, so a direct object overrides a class's (test structure.rs placements_carry_the_elements_attribute_objects). What the standard owners' entries mean is the 14.8.5 rows' business. |
| 14.7.5.2 | Attribute Classes | implemented | 4 | 1 | StructureTree loads the root's /ClassMap once (class_map_of) and Walk::class_attributes looks up each name an element's /C gives, one name or an array, taking the class's attribute objects as if they were the element's own and putting them before the direct /A objects, so those override; a name the map lacks brings nothing (test placements_carry_the_elements_attribute_objects: /C [/Wide 4] with /A overriding the class's ColSpan). |
| 14.7.5.3 | Attribute Revision Numbers | implemented | 2 | 1 | An element's /R lands on StructureElement::revision (0 when absent); in an /A array an integer after an attribute object is that object's revision, in a /C array an integer after a class name is the revision of every object the class brought, both on AttributeObject::revision (Walk::direct_attributes, Walk::class_attributes; test placements_carry_the_elements_attribute_objects checks /R 1, a revision 2 in /A and a revision 4 in /C). No consumer compares revisions yet. |
| 14.7.5.4 | User Properties | incomplete | 1 | 0 | A /O /UserProperties attribute object is read like any other (structure.rs attribute_object) and reaches Placement::attributes with its owner, but its /P array of property dictionaries (/N, /V, /F, /H) is not interpreted. |
| 14.7.6 | Example of Logical Structure | out of scope | 0 | 0 | A worked example of a structure tree with no requirement of its own; the structure it illustrates is assessed under 14.7.2 to 14.7.5. |
| 14.8.2 | Tagged PDF and Page Content | incomplete | 30 | 30 | Structure-tree reading order is offered on every extraction entry point and ranks the page's marked-content sequences by the tree (14.8.2.3); artifacts are marked on their spans (14.8.2.2), reversed show strings put back in logical order (14.8.2.3.3) and ActualText substituted for the shown glyphs (14.9.4), and annotations take their place among the sequences through Page::content_items (14.8.2.3.2). The font characteristics come from the descriptor: style from its flags, weight, stem and the name, and the family, stretch and weight as written (14.8.2.4.3); the depth-3 and depth-4 rows give the detail. |
| 14.8.2.2 | Real Content and Artifacts | implemented | 6 | 2 | crates/pdfboss-text/src/extract.rs marks every span shown inside an /Artifact marked-content sequence: a bare BMC gives ArtifactKind::Unspecified, a BDC's property list (inline or named through /Properties) gives the /Type of Table 330 (Pagination, Layout, Page, Background) and the /Subtype (Header, Footer, Watermark), on TextSpan::artifact (Mark::artifact, marked_artifact; test lib.rs artifact_marks_reach_the_spans); the Python Span exposes artifact and artifact_subtype. crates/pdfboss-output/src/structure.rs lays pagination artifacts out apart from the real content and gives them the PageHeader or PageFooter role by their position against the page's text midline, so Markdown drops a producer-marked running head or folio on a single page while text keeps it (attach_pagination_artifacts; test lib.rs tagged_pagination_artifacts_are_page_headers_and_footers_on_one_page); the repetition heuristic still covers untagged files. /BBox and /Attached are not read, and layout, page and background artifacts are marked but not treated differently from real content. |
| 14.8.2.3 | Page Content Order | implemented | 9 | 10 | Under ReadingOrder::StructureTree each marked-content sequence is ranked by its depth-first position in the tree and untagged content keeps its place (crates/pdfboss-text/src/extract.rs structure_order), a /ReversedChars sequence's show strings are put back in logical order under every order (14.8.2.3.3), and the annotations the tree holds are ranked in the same order as the sequences by StructureTree::content_items_with (14.8.2.3.2), which is where an annotation's slot among the text is read from, the span list carrying no annotation text. |
| 14.8.2.3.2 | Sequencing of Annotations | implemented | 1 | 2 | An annotation's place in the page content order is read off the structure tree: crates/pdfboss-core/src/structure.rs StructureTree::content_items_with ranks the page's marked-content sequences and the annotations held through object references in one depth-first order, so an annotation whose element sits between two sequences' elements comes out between them and a Link, Form or Annot element's own sequence and annotation come out in kid order (tests an_annotation_takes_its_place_between_the_sequences, a_link_element_orders_its_sequence_and_annotation_by_kid_index; Python tests/test_content_items.py). Text extraction itself returns no annotation text, so the slot is read from the items rather than from the span list; docs/src/guide/text.md gives the recipe. |
| 14.8.2.3.3 | Reverse-Order Show Strings | implemented | 4 | 3 | crates/pdfboss-text/src/extract.rs marks a /ReversedChars sequence (BMC or BDC, Mark::reversed) and puts every show string inside it back in logical order as it is emitted (reversed_chars): the characters reversed, a space at the beginning or end of the string kept in place, and TextSpan::glyph_x reversed with the characters; a sequence's /ActualText still wins as replacement text (test crates/pdfboss-text/src/lib.rs reversed_chars_sequences_put_their_strings_back_in_order). The span's geometry stays the visual one, and the span is marked TextSpan::logical so crates/pdfboss-output/src/bidi.rs, which puts right-to-left lines in reading order, does not reverse it a second time (test crates/pdfboss-output/src/lib.rs reversed_chars_and_actual_text_are_not_reversed_twice). |
| 14.8.2.4 | Extraction of Character Properties | implemented | 8 | 12 | Unicode values come from ToUnicode, the encoding's glyph names, the CID collection's CMap and a marked-content sequence's ActualText (14.8.2.4.2), font style from the FontDescriptor and the base font name, and the family, stretch and weight from the descriptor as written (14.8.2.4.3); the depth-4 rows give the detail. |
| 14.8.2.4.2 | Unicode Mapping in Tagged PDF | implemented | 3 | 4 | The clause's methods in its order: crates/pdfboss-text/src/font.rs decode_into maps a code through the font's /ToUnicode first (load_to_unicode, an empty CMap treated as absent), a simple font then through its encoding's glyph names (the Adobe Glyph List with uniXXXX and uXXXX forms, underscore ligatures and period variants; crates/pdfboss-encoding), a composite font through its /CIDSystemInfo ordering's Uni*-UTF16-H CMap inverted (cid_to_unicode), the same relation as the Registry-Ordering-UCS2 CMaps the clause names; and crates/pdfboss-text/src/extract.rs emit puts a marked-content sequence's /ActualText in place of everything shown inside it (14.9.4), so the property overrides the mapping as the clause requires (tests font.rs tounicode_beats_encoding, differences_agl_ligatures_and_variants_decode, type0_predefined_rksj_maps_codes_to_cids, crates/pdfboss-text/src/lib.rs actual_text_replaces_the_shown_glyphs). A code none of the methods maps comes out as U+FFFD, the reader's choice the clause leaves open; gXX and cidXX style glyph names are not resolved (9.10.2). |
| 14.8.2.4.3 | Font Characteristics | implemented | 3 | 6 | crates/pdfboss-text/src/font.rs Font::style reads the descriptor's /FontFamily (a byte string, decoded as a text string), /FontStretch (the nine names of Table 122, FontStretch::from_name) and /FontWeight as written onto TextSpan::font_family, font_stretch and font_weight, next to the bold, italic, serif and fixed-pitch readings of /Flags, /FontWeight, /StemV, /ItalicAngle and the /BaseFont name (tests crates/pdfboss-text/src/lib.rs descriptor_family_stretch_and_weight_reach_the_span, descriptor_without_font_characteristics_gives_none, font_stretch_names_round_trip_in_table_order; Python tests/test_spans.py test_style_attributes). A name outside the table gives no stretch. |
| 14.8.2.5 | Identifying Word Breaks | implemented | 3 | 4 | Words are found the way the clause has it, in the Unicode character stream: a space glyph decodes into the text wherever it is shown, the split into show strings carrying no meaning (crates/pdfboss-text/src/extract.rs show; test lib.rs a_space_glyph_before_a_word_gap_is_one_space), and a sequence's /ActualText replaces the shown glyphs with its spaces included (emit, 14.9.4; test lib.rs actual_text_spaces_are_word_breaks). For files whose producer left the spaces out, which the clause forbids in Tagged PDF but untagged files do freely, a horizontal gap between spans becomes a space (crates/pdfboss-output/src/structure.rs push_span; test structure.rs shrunk_justified_word_gaps_still_become_spaces), the positional guess NOTE 2 says a reader need not make. Splitting the text into words is left to the consumer, as NOTE 4 allows; no UAX #29 boundary analysis is done. |
| 14.8.3 | Basic Layout Model | incomplete | 2 | 1 | Under ReadingOrder::StructureTree the page is laid out on the clause's model: crates/pdfboss-core/src/structure.rs StandardKind sorts the standard types into grouping, block-level, inline-level and illustration elements (14.8.4), crates/pdfboss-output/src/structure.rs run_of gives every span the outermost block-level element above it, and push_tagged_blocks stacks those elements as blocks in the tree's order, each block's text laid out in lines by position with its inline-level elements flowing inside (Span, Code as inline code under 14.8.4.4); a list's items and a table's rows and cells nest inside their block (tests crates/pdfboss-output/src/lib.rs tagged_block_elements_decide_the_markdown_under_structure_tree_order). The Placement and WritingMode attributes that re-class an element or turn the progression directions (14.8.5.4) are not read, so the classification comes from the standard type alone and every page is laid out left to right, top to bottom; a block-level element inside a table cell or a list body, a nested list apart, is laid out within that cell's line or item's lines rather than as a block of its own; content the tree does not reach goes through the layout heuristics. |
| 14.8.4 | Standard Structure Types | implemented | 19 | 14 | crates/pdfboss-core/src/structure.rs StandardType names the 49 standard types with the standard's own spelling, StandardType::from_name recognizes a role-mapped name (case-sensitive, a document's own types staying None), StandardType::kind sorts each into the clause that defines it (StandardKind::Grouping, BlockLevel, InlineLevel, Illustration), and Placement::standard_type and Placement::path carry the result, with the standard-typed ancestry, for every placed marked-content sequence (tests structure.rs standard_types_are_recognized_by_name_and_grouped_by_clause, placements_carry_the_standard_typed_ancestry). What each set means for extraction is the depth-4 rows' business: the block-level elements are the blocks of structure-tree order (14.8.4.3), the grouping elements quote, caption and nest them (14.8.4.2), Code is the inline-level element with an effect of its own (14.8.4.4) and the illustration elements are laid out apart from the text around them (14.8.4.5). |
| 14.8.4.2 | Grouping Elements | implemented | 4 | 3 | Under ReadingOrder::StructureTree the paragraphs inside a BlockQuote take Role::Quote (crates/pdfboss-output/src/structure.rs in_block_quote) and the Markdown adapter renders them as a block quote, every line opened with > (markdown.rs quote); a Caption or TOCI holding text with no block-level element inside is a block of its own (block_element), so a table of contents comes out one entry per block and a figure's caption never merges with its neighbour; Sect, Part and Art nesting sets the level of an H heading (14.8.4.3); Document, Div, TOC, Index, NonStruct and Private hold blocks rather than being one and leave their content as shown, Private included since its text is on the page (tests crates/pdfboss-output/src/lib.rs block_quote_paragraphs_render_as_markdown_quotes, toc_entries_are_their_own_blocks). Headings and lists inside a BlockQuote are not quoted. |
| 14.8.4.3 | Block-Level Structure Elements | implemented | 4 | 7 | Under ReadingOrder::StructureTree every span the tree reaches carries TextSpan::structure (crates/pdfboss-text/src/lib.rs Structure: the holding element's standard type and the standard-typed ancestry from Placement::path, read by crates/pdfboss-core/src/structure.rs Walk::ancestry) and crates/pdfboss-output/src/structure.rs push_tagged_blocks builds the page's blocks from it: consecutive spans of one outermost block-level element are one block, H1 to H6 a heading at that level, H a heading as deep as its Part, Art and Sect ancestors nest, L a list with an item per LI and the Lbl text as the marker (digits closed by . or ) are the number, anything else a bullet; the label stays in the item's first line and the Markdown adapter strips it by length; an item's label is its own Lbl, not a nested list's; an item without a Lbl reads its marker off its first line as an untagged page does, and a first line that is nothing but the marker stays in the layout for plain text while the Markdown adapter opens the item on the line after it), Table a table with a row per TR (THead, TBody, TFoot transparent) and a cell per TH or TD (a cell's lines joined with a space), every other block-level element a paragraph, so two P elements a line apart stay two paragraphs; spans with no block-level element go through the layout heuristics, and ruled grids are not consulted on a tagged segment (tests crates/pdfboss-output/src/lib.rs tagged_block_elements_decide_the_markdown_under_structure_tree_order, a_tagged_list_without_labels_reads_its_markers_off_the_lines, a_nested_list_keeps_its_labels_out_of_the_outer_marker, the_same_page_in_content_order_reads_by_the_layout_heuristics; crates/pdfboss-text/src/lib.rs structure_tree_order_attaches_each_span_s_standard_typed_ancestry). ListNumbering is read under 14.8.5.5 and ColSpan and RowSpan under 14.8.5.7; a nested list's content stays inside the item holding it; a label that does not open its item's first line stays in the text as a bullet item; content order, the default, still infers headings, lists and tables from layout. |
| 14.8.4.4 | Inline-Level Structure Elements | incomplete | 4 | 2 | Under ReadingOrder::StructureTree text shown inside a Code element becomes its own inline run (crates/pdfboss-output/src/structure.rs in_code, Inline::code) and the Markdown adapter sets it as a CommonMark code span, backticks one longer than any run inside and no emphasis (markdown.rs push_code; test crates/pdfboss-output/src/lib.rs code_elements_render_as_inline_code); Span, Quote, Reference, BibEntry and Annot flow into their block as shown, which is all text and Markdown have for them. Not acted on: Link keeps its text without a target (object references are unread, 14.7.4.3), Note stays in place with no footnote syntax, and Ruby and Warichu keep every part (RB, RT, RP, WT, WP) in reading order. |
| 14.8.4.5 | Illustration Elements | incomplete | 2 | 1 | Under ReadingOrder::StructureTree the text inside a Figure, Formula or Form with no block-level element inside forms an illustration run (crates/pdfboss-output/src/structure.rs Run::Illustration, run_of, push_tagged_blocks): the layout heuristics lay it out on its own, adjacent illustrations together so a table a producer drew as a row of figures is still read as a table, and none of it joins the untagged text or the tagged blocks around it; a Figure's /Alt reaches its spans (14.9.3; test crates/pdfboss-output/src/lib.rs illustration_elements_are_their_own_blocks). A figure that shows no text has no span and so no block, and no adapter renders a description in its place; image extraction works from XObjects, not from the tree. |
| 14.8.5 | Standard Structure Attributes | incomplete | 10 | 6 | Attribute objects reach Placement::attributes and TextSpan::structure with their owner and entries (14.7.5) and the standard owners are told apart (14.8.5.2); of the standard attributes themselves the Table owner's ColSpan and RowSpan (14.8.5.7) and the List owner's ListNumbering (14.8.5.5, inherited down the tree as 14.8.5.3 says) are interpreted, the layout and PrintField attributes are not (14.8.5.4, 14.8.5.6). |
| 14.8.5.2 | Standard Attribute Owners | implemented | 3 | 1 | crates/pdfboss-core/src/structure.rs StandardOwner names the eleven owners of Table 331 (Layout, List, PrintField, Table and the XML-1.00, HTML-3.2, HTML-4.01, OEB-1.0, RTF-1.05, CSS-1.00 and CSS-2.00 formats) with the standard's spelling; StandardOwner::from_name recognizes an /O name case-sensitively, a producer's own owner and UserProperties (14.7.5.4) staying None, and AttributeObject::standard_owner applies it, which is how the tagged table code asks for the Table owner's spans (test structure.rs standard_attribute_owners_are_recognized_by_name). |
| 14.8.5.3 | Attribute Values and Inheritance | incomplete | 2 | 1 | The clause's priority list is followed for the attributes that are read: crates/pdfboss-core/src/structure.rs Placement::attribute resolves a key for one element with a direct /A object beating the classes its /C entry names and a later class an earlier one (steps b and c; 14.7.5.2), and crates/pdfboss-output/src/structure.rs list_numbering resolves ListNumbering, the one inheritable attribute read (Table 347), as step d says: the L element's own value, else the nearest ancestor's, an explicit None stopping the inheritance (test crates/pdfboss-output/src/lib.rs list_numbering_is_inherited_from_an_ancestor_unless_the_list_says_none); ColSpan and RowSpan are not inheritable and take the default 1 of step e (14.8.5.7). The export-format owners of step a are told apart (14.8.5.2) but never consulted, text and Markdown being none of their formats, and an ancestor without a standard type is skipped on the way up. |
| 14.8.5.4 | Layout Attributes | not implemented | 0 | 0 | Placement, WritingMode, BBox, spacing, color and column attributes are never read. |
| 14.8.5.5 | List Attribute | implemented | 3 | 3 | crates/pdfboss-output/src/structure.rs list_numbering reads a tagged list's ListNumbering from the L element's attribute objects of owner List (the last object with the key winning). When it names a numbering system (Decimal, UpperRoman, LowerRoman, UpperAlpha, LowerAlpha) push_tagged_list numbers the items: a label is read in that system (numbered_label: digits; a letter of the system's case, repeated past Z; Roman numerals of the system's case; closed by . or ) or by nothing), an unlabelled item takes the number after the previous item's, and a label outside the system (a lettered sub-item inside a Decimal list, as the 049 corpus tags them) keeps the marker its text gives (numbered_marker; tests crates/pdfboss-output/src/lib.rs list_numbering_numbers_the_items_of_a_numbered_list, structure.rs labels_are_read_in_the_list_s_numbering_system). None, Disc, Circle, Square and a missing attribute leave the markers to the labels and lines, which already read as bullets; Markdown writes every ordered item with a decimal number, and the Lang-dependent alphabet of the alphabetic systems is not consulted. |
| 14.8.5.6 | PrintField Attributes | not implemented | 0 | 0 | Role, checked and Desc attributes of form-field elements are never read. |
| 14.8.5.7 | Table Attributes | incomplete | 1 | 1 | crates/pdfboss-output/src/structure.rs table_span reads a tagged cell's ColSpan and RowSpan from the TH or TD element's attribute objects of owner Table (TextSpan::structure carries them, the last object with the key winning, values clamped to 1..255) onto Cell::colspan and Cell::rowspan, and the Markdown adapter renders a table with either span as HTML with colspan and rowspan attributes (test crates/pdfboss-output/src/lib.rs tagged_cells_carry_their_column_and_row_spans). Headers, Scope and Summary are not read, and an untagged table's structure is still inferred from column gaps and rulings. |
| 14.9.2 | Natural Language Specification | implemented | 12 | 6 | /Lang is read at all three levels: crates/pdfboss-core/src/language.rs language_with reads the catalog's as the document language (Document::language, AsyncDocument::language, Python Document.language and AsyncDocument.language), crates/pdfboss-core/src/structure.rs Walk::ancestry reads each structure element's and Placement::lang carries the nearest, and crates/pdfboss-text/src/extract.rs reads a BDC's property-list /Lang (inline or named) onto every span shown inside as TextSpan::lang, a span with none taking the nearest element's under ReadingOrder::StructureTree (Python Span.lang); every value is a text string decoded, the tag itself kept as written (tests language.rs the_catalog_lang_is_the_document_language, structure.rs placements_carry_the_nearest_language, crates/pdfboss-text/src/lib.rs natural_languages_reach_the_spans, crates/pdfboss-aio/tests/parity.rs). No output adapter uses the language yet. |
| 14.9.2.2 | Language Identifiers | implemented | 3 | 1 | crates/pdfboss-core/src/language.rs LanguageTag::parse reads a /Lang value in the RFC 3066 form the clause prescribes: a primary subtag (an ISO 639 code, or i for a registered and x for a private-use tag) and hyphen-separated subtags of one to eight ASCII letters (digits allowed after the first), the second subtag a country code when it has two letters; case carries no meaning, so the language comes out lower-case and the country upper-case, the remaining subtags as written, and an empty or overlong subtag, a character outside ASCII or a separator other than the hyphen makes the tag malformed (None). Document::language_tag applies it to the catalog's /Lang (test language.rs language_tags_split_into_language_and_country). The codes themselves are not checked against the ISO 639 and ISO 3166 lists. |
| 14.9.2.3 | Language Specification Hierarchy | incomplete | 1 | 2 | The two inner levels chain per span: a marked-content sequence's /Lang wins over the enclosing structure elements', the nearest of which wins over its ancestors' (TextSpan::lang, Placement::lang, test natural_languages_reach_the_spans); the outermost level, the catalog's /Lang, is Document::language and is not folded into a span whose sequence and elements declare none, so a caller takes it as the default itself. |
| 14.9.2.4 | Multi-language Text Arrays | not implemented | 0 | 0 | Multi-language text arrays are not parsed. |
| 14.9.3 | Alternate Descriptions | implemented | 4 | 3 | crates/pdfboss-text/src/extract.rs reads /Alt from a BDC's property list (inline or named through /Properties, decoded as a text string like /ActualText, Executor::marked_text_string) onto every span shown inside the sequence as TextSpan::alt, the shown text untouched, and under ReadingOrder::StructureTree a span with no description of its own takes the nearest structure element's /Alt, which crates/pdfboss-core/src/structure.rs Walk::ancestry reads on the way to the root and Placement::alt carries; the Python Span exposes alt (tests structure.rs placements_carry_the_nearest_alternate_description, crates/pdfboss-text/src/lib.rs alternate_descriptions_from_property_lists_reach_the_spans, alternate_descriptions_from_structure_elements_reach_the_spans). No output adapter renders a description yet, and a Figure that shows no text has no span to carry its /Alt. |
| 14.9.4 | Replacement Text | implemented | 5 | 5 | crates/pdfboss-text/src/extract.rs reads /ActualText from a BDC's property list (inline or named through /Properties, any tag, decoded as a text string) and treats it as a character substitution for the sequence: the first string shown inside becomes one span carrying the replacement, every later string only widens that span's extent, and an empty replacement leaves the sequence without text (Mark::actual, ActualText, emit, marked_actual_text; tests lib.rs actual_text_replaces_the_shown_glyphs with the clause's Drucker example, actual_text_covers_the_whole_sequence_and_may_be_empty). /ActualText on a structure element dictionary is not read; the language escape sequences of 7.9.2.2 are not interpreted. |
| 14.9.5 | Expansion of Abbreviations and Acronyms | implemented | 1 | 3 | crates/pdfboss-text/src/extract.rs reads /E from a BDC's property list (inline or named through /Properties, decoded as a text string like /Alt, Executor::marked_text_string) onto every span shown inside the sequence as TextSpan::expansion, the shown abbreviation untouched, and under ReadingOrder::StructureTree a span with no expansion of its own takes the nearest structure element's /E, which crates/pdfboss-core/src/structure.rs Walk::ancestry reads on the way to the root and Placement::expansion carries; the Python Span exposes expansion (tests structure.rs placements_carry_the_nearest_expansion, crates/pdfboss-text/src/lib.rs expansions_from_property_lists_reach_the_spans, expansions_from_structure_elements_reach_the_spans). No output adapter renders an expansion. |
| 14.10.2 | Web Capture Information Dictionary | out of scope | 0 | 0 | pdfboss reads, renders and writes PDFs and is not a web-capture tool, so /SpiderInfo is ignored. |
| 14.10.3 | Content Database | out of scope | 0 | 0 | Web capture is outside pdfboss's scope; the /IDS and /URLS name trees are ignored. |
| 14.10.4 | Content Sets | out of scope | 0 | 0 | Web capture is outside pdfboss's scope; /SPS and /SIS content sets are ignored. |
| 14.10.5 | Source Information | out of scope | 0 | 0 | Web capture is outside pdfboss's scope; source information dictionaries are ignored. |
| 14.10.6 | Object Attributes Related to Web Capture | out of scope | 0 | 0 | Web capture is outside pdfboss's scope; the /ID entry on page and XObject dictionaries is ignored (the trailer /ID is a different key and is handled under 14.4). |
| 14.11.2 | Page boxes | implemented | 6 | 5 | MediaBox, CropBox, BleedBox, TrimBox and ArtBox are read with the clause's defaults (crop falls back to media, the other three to crop), clipped to the media box and treated as non-inheritable, in the sync, async and Python APIs. |
| 14.11.2.2 | Display of page boxes | incomplete | 1 | 0 | Rendering maps the CropBox (turned by /Rotate) onto the output image as the clause's default visible area, but /BoxColorInfo is never read, no option renders the media, bleed, trim or art box instead, and no test renders a page whose CropBox is narrower than its MediaBox. |
| 14.11.3 | Printer’s Marks | not implemented | 0 | 0 | Nothing recognizes the PrinterMark annotation subtype or its /MN entry; such an annotation paints only incidentally through the generic appearance-stream path at crates/pdfboss-render/src/executor.rs, which skips only Popup, Hidden and NoView annotations. |
| 14.11.4 | Separation Dictionaries | implemented | 7 | 4 | crates/pdfboss-core/src/separation_info.rs separation_info_with reads a page's /SeparationInfo dictionary (Table 364) into SeparationInfo: the /Pages references of the separation set, the /DeviceColorant as a name or text string and the /ColorSpace array as written; a dictionary naming no colorant reads as absent. Document::separation_info and AsyncDocument::separation_info expose it and pdfboss info counts the pre-separated pages with their colorants (tests separation_info.rs reads_the_separation_dictionary and missing_or_malformed_separation_info_reads_as_absent, crates/pdfboss-aio/tests/parity.rs, crates/pdfboss-cli/src/main.rs info_text_counts_separations). Nothing recombines separations into a composite page, and pdfboss-write emits no separation dictionary. |
| 14.11.5 | Output Intents | incomplete | 8 | 4 | crates/pdfboss-core/src/output_intent.rs output_intents_with reads the catalog's /OutputIntents array into OutputIntent records with the Table 365 entries (S, OutputCondition, OutputConditionIdentifier, RegistryName, Info and the DestOutputProfile reference), skipping entries without the required /S; Document::output_intents and AsyncDocument::output_intents expose them and pdfboss info prints one line per intent (tests output_intent.rs reads_every_entry_of_the_output_intent_dictionaries, crates/pdfboss-aio/tests/parity.rs, crates/pdfboss-cli/src/main.rs info_text_lists_output_intents). Rendering never applies an intent's destination profile even though pdfboss-icc could; colours are converted from their source spaces only. |
| 14.11.6 | Trapping Support | out of scope | 0 | 0 | Trapping is a prepress step pdfboss does not perform; /TrapNet annotations get no special reading or painting (an appearance stream would paint like any other annotation). |
| 14.11.7 | Open Prepress Interface (OPI) | out of scope | 0 | 0 | OPI proxy substitution is a prepress workflow pdfboss does not take part in; /OPI on image and form XObjects is ignored and the XObject itself is used. |
Annexes
| Clause | Feature | Status | Code citations | Test citations | Note |
|---|---|---|---|---|---|
| A.2 | PDF Content Stream Operators | implemented | 2 | 19 | Every operator in Table A.1 parses into an Op (BI, ID and EI become one InlineImage op; f and F share Op::Fill) and none is unparsed; the rasterizer treats ri and i as an explicit no-op (executor.rs) and lets MP, DP, BX, EX, ET and d0 fall to the empty arm at executor.rs, while d1 is only checked for presence at executor.rs to choose the uncolored Type3 glyph path. |
| B.2 | Arithmetic Operators | implemented | 2 | 4 | All 21 arithmetic operators (abs add atan ceiling cos cvi cvr div exp floor idiv ln log mod mul neg round sin sqrt sub truncate) are recognized at shading.rs and evaluated by run_calculator; the tests feed add, mul, exp, log, sqrt, div, ln, atan, idiv, mod, round, truncate, floor, ceiling and cvi, leaving abs, neg, sub, sin, cos and cvr without a direct test. |
| B.3 | Relational, Boolean and Bitwise Operators | implemented | 2 | 3 | and bitshift eq false ge gt le lt ne not or true xor are all recognized; and, or, xor and not split on boolean versus integer operands, bitshift shifts in both directions, and eq/ne handle mixed-type operands. |
| B.4 | Conditional Operators | implemented | 1 | 1 | if and ifelse compile procedure blocks into forward jumps, nested conditionals branch correctly, and a block not followed by if or ifelse is a load error (shading.rs). |
| B.5 | Stack Operators | implemented | 2 | 2 | copy dup exch index pop roll are recognized, roll rotates in both directions, and the operand stack is capped at 100 entries (CALC_STACK, shading.rs) as §7.10.5.1 requires, with a 10,000-step runaway limit. |
| C.2 | Architectural Limits | implemented | 2 | 8 | The parser meets or exceeds every Table C.1 minimum (integers are i64 and reals f64 with integer overflow degrading to a real, no cap on string, name, array or dictionary size, CID range 0 to 0xFFFF) and sets its own caps above the spec's minima: object nesting 128 (spec 28), q/Q depth 64 (spec 28), indirect-reference chain 32 (MAX_RESOLVE_DEPTH), page-tree depth 256, form nesting 16; it never rejects a file for exceeding Table C.1. |
| C.3 | Memory Limits | implemented | 2 | 2 | Memory is bounded per stream rather than per document: each decoded stream is capped at 256 MiB (MAX_DECODED_LEN) so a decompression bomb is an error, CCITT checks its packed size against that cap and JBIG2 budgets its regions up front, and the aio tail scan stops at 64 KiB; there is no document-wide object-count or total-memory cap. |
| D.2 | Latin Character Set and Encodings | incomplete | 9 | 11 | The StandardEncoding, MacRomanEncoding and WinAnsiEncoding columns exist as code-to-Unicode tables (glyph-name tables only for Standard and WinAnsi), the PDFDocEncoding column decodes text strings (crates/pdfboss-core/src/object.rs pdf_doc_char) and pdfboss-write encodes text as WinAnsi; missing are a MacRoman glyph-name table, the WinAnsi footnote mapping unused codes above 0x27 to bullet (0x81, 0x8D, 0x8F, 0x90 and 0x9D return None), and /StandardEncoding named explicitly is only honored because it falls through the default arm at font.rs. |
| D.3 | PDFDocEncoding Character Set | implemented | 2 | 5 | The full PDFDocEncoding table decodes text strings without a BOM (crates/pdfboss-core/src/object.rs pdf_doc_char: accents at 0x18-0x1F, punctuation and ligatures at 0x80-0x9E, Euro at 0xA0, undefined codes to U+FFFD); the writer emits text strings as ASCII or UTF-16BE, which the clause allows, so no encoder is needed. |
| D.4 | Expert Set and MacExpertEncoding | not implemented | 0 | 0 | No MacExpertEncoding table exists anywhere; a font naming /MacExpertEncoding falls through the default arm at crates/pdfboss-text/src/font.rs and is decoded as StandardEncoding. |
| D.5 | Symbol Set and Encoding | implemented | 7 | 2 | crates/pdfboss-encoding/src/symbol.rs SYMBOL holds the 189 code-to-glyph-name pairs of the annex (no apple at 0o360), written by a script from the standard's text and cross-checked against unicode.org's Adobe Symbol mapping; lib.rs symbol_glyph_name and symbol expose them, the character coming through the Adobe Glyph List, so Omega, Delta and mu read as the ohm, increment and micro signs as pdf.js reads them (test lib.rs symbol_encoding_follows_annex_d5). Text extraction takes the table as the base encoding of a font whose /BaseFont family is Symbol when the file names no encoding and embeds no Type 1 program, /Differences on top (crates/pdfboss-text/src/font.rs standard_symbol_encoding, test symbol_and_zapf_dingbats_decode_through_their_built_in_encodings); the writer encodes Symbol text through the same table (crates/pdfboss-write/src/font.rs Standard14::decoder, test symbol_faces_encode_through_their_built_in_encodings). Rendering still leaves non-embedded Symbol unpainted, there being no license-clean substitute face (crates/pdfboss-render/src/substitute.rs), and no metrics are bundled for it (9.6.2.2). |
| D.6 | ZapfDingbats Set and Encoding | implemented | 6 | 2 | crates/pdfboss-encoding/src/symbol.rs ZAPF_DINGBATS holds the 188 codes of the annex (space and 187 dingbats; the annex leaves 0o200 to 0o215 out) with their glyph names and, since the Adobe Glyph List does not carry the a1 to a191 names, the Unicode characters of unicode.org's Adobe ZapfDingbats mapping; lib.rs zapf_dingbats_glyph_name and zapf_dingbats expose them (test lib.rs zapf_dingbats_encoding_follows_annex_d6). Text extraction and the writer use the table the same way as Symbol's (crates/pdfboss-text/src/font.rs standard_symbol_encoding, crates/pdfboss-write/src/font.rs Standard14::decoder). Rendering still leaves non-embedded ZapfDingbats unpainted and no metrics are bundled for it (9.6.2.2). |
| E.2 | Name Registry | out of scope | 0 | 0 | pdfboss neither registers names nor checks second-class or third-class prefixes; unknown dictionary keys pass through as plain names, and registration is a publishing process rather than a parser feature. |
| F.2 | Background and Assumptions | out of scope | 0 | 0 | Design background for linearization with no requirement of its own; the file structure it motivates is assessed under F.3 and F.4. |
| F.3 | Linearized PDF Document Structure | incomplete | 10 | 6 | crates/pdfboss-core/src/linearization.rs linearization_dictionary reads the linearization parameter dictionary of Table F.1 from the first object within the first 1024 bytes (Linearized, L, the H offset and length pairs, O, E, N, T, and P with its default of 0); Document::linearization and AsyncDocument::linearization return it as written, the async document from the header window it fetches at open, and is_linearized applies the /L rule of Table F.1: a length that no longer matches the file, as after an appended update, makes the file ordinary PDF (tests linearization.rs a_document_is_linearized_only_while_the_declared_length_matches, crates/pdfboss-aio/tests/parity.rs linearization_dictionaries_agree). The CLI's info prints the result (crates/pdfboss-cli/src/main.rs info_text). Nothing acts on the dictionary: pdfboss-aio still locates objects by scanning the tail for startxref and following the xref chain (crates/pdfboss-aio/src/document.rs find_tail) instead of reading the first-page cross-reference table and first-page section it points to, and pdfboss-write emits no /Linearized dictionary or first-page section (crates/pdfboss-write/src/writer.rs writes a plain header). |
| F.4 | Hint Tables | not implemented | 0 | 0 | No hint stream is parsed or produced; the /H offsets and the page offset and shared object hint tables are never read. |
| I | PDF Versions and Compatibility | incomplete | 2 | 5 | The %PDF-M.m header is parsed within the first 1 KiB with one to three digit components, a missing or malformed header silently defaults to 1.4, any parsed version including future ones is accepted without a warning, unknown operators and keys are skipped as Annex I asks, and pdfboss-write emits a configurable header; missing is the catalog /Version override, which no crate reads. |