# dart-pdf vs makepad-pdf — Comprehensive Gap Analysis > Comparison of the [dart-pdf](https://github.com/ben-milanko/dart-pdf) (Dart/Flutter) library's public API against the makepad-pdf (Rust) implementation in `makepad/libs/pdf_parse/src/`. --- ## 1. PDF File Parsing & Structure | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **CosDocument (PDF parsing)** | Full COS-level parser: xref tables, trailer, object loading, object streams, recovery of broken xref via header scanning | **EXISTS** — `PdfDocument::parse()` | Object stream support is partial; xref recovery not fully equivalent | | **Xref table parsing** | Full traditional xref + cross-reference streams; incremental update support (`applyIncrementalUpdate`) | **EXISTS** — `parse_xref()` + xref module | No xref-hybrid or xref-stream parsing; no incremental update merging | | **Linearized PDF** | Not explicitly surfaced but parseable | **MISSING** | — | | **Object streams** | Compressed objects inside ObjStm are decoded on demand | **PARTIAL** — parser can handle some stream objects | Not full ObjStm indexed access | | **Incremental update** | `CosIncrementalUpdater` appends revision sections; `PdfEditor.save()` produces incremental bytes | **EXISTS** — `PdfIncrementalUpdater` + `PdfWriter` | Limited to content stream replacement; no full dictionary-level incremental updates | | **PDF version handling** | `version` getter from `%PDF-` header; catalog `/Version` override | **PARTIAL** — reads header | — | | **Byte-range random access** | `PdfByteSource`, `PdfHttpByteSource` for progressive/remote loading | **MISSING** | No remote/progressive byte source support | | **Token stream** | Full lexer with token types, position tracking | **EXISTS** — `Lexer` | — | | **COS object model** | CosDictionary, CosArray, CosStream, CosString, CosName, CosInteger, CosReal, CosBoolean, CosNull, CosReference | **EXISTS** — `PdfObj` enum | Similar model; fewer variant types | | **Serializer** | `CosSerializer` — writes any COS object to PDF syntax | **EXISTS** — `PdfWriter` | — | | **Document open recovery** | Full xref recovery by scanning for `N G obj` headers, fallback catalog discovery | **PARTIAL** — basic parsing | No header-scan recovery | | **Decoded-stream cache** | LRU cache for decoded streams with budget caps | **MISSING** | No decoded-stream caching | --- ## 2. Content Stream Operations | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **Content stream parser** | `ContentStreamParser.parse()` + cursor-based incremental parsing | **EXISTS** — `parse_content_stream()` | No cursor/incremental parsing | | **PdfOp enum (all ops)** | All standard content ops: path construction, path painting, graphics state, color, text, XObjects, inline images, marked content | **EXISTS** — `PdfOp` enum | Equivalent coverage | | **Path construction** | `m`, `l`, `c`, `v`, `y`, `h`, `re` | **EXISTS** — all path ops | — | | **Path painting** | `S`, `s`, `f`/`F`, `f*`, `B`, `B*`, `b`, `b*`, `n` | **EXISTS** — all painting ops | — | | **Clipping** | `W`, `W*` | **EXISTS** — `Clip`, `ClipEvenOdd` | — | | **Graphics state** | `q`, `Q`, `cm`, `w`, `J`, `j`, `M`, `d`, `gs` | **EXISTS** — all graphics state ops | — | | **Inline images** | `BI`/`ID`/`EI` — decoded by device | **EXISTS** — `InlineImage` variant | — | | **XObjects** | `Do` operator | **EXISTS** — `PaintXObject` | — | | **Marked content** | `BMC`, `BDC`, `EMC` | **EXISTS** — all three operators | — | | **Compatibility** | `BX`, `EX` | **MISSING** — skipped silently | — | | **Type3 metrics** | `d0`, `d1` | **MISSING** | — | | **Shading** | `sh` operator | **MISSING** | — | | **Cancellation** | `PdfCancellationToken` for cooperative cancellation mid-walk | **MISSING** | — | | **Async interpretation** | `drawPageOperationsAsync()`, `drawPageContentAsync()` with yield intervals | **MISSING** | — | --- ## 3. Graphics State (Interpreter) | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **GraphicsState class** | Full state: CTM, fill/stroke color, stroke params, font, text state, color spaces, soft mask, blend mode, overprint, patterns | **PARTIAL** — `PdfGfxState` has CTM, colors, stroke params, font, text matrix, char/word spacing | No color spaces tracking, no soft mask, no blend mode, no overprint, no pattern state | | **Save/restore** | Stack-based `q`/`Q` with full state clone | **EXISTS** — push/pop on state stack | — | | **CTM (transform)** | 3×3 matrix via `cm` operator | **EXISTS** — `ConcatMatrix` | — | | **Clip path** | `clipPath()` device callback with fill rule | **EXISTS** — `Clip`/`ClipEvenOdd` | — | | **Overprint** | `setOverprint()` with fill/stroke/mode | **MISSING** | — | | **Blend mode** | `setBlendMode()` with full `PdfBlendMode` enum (16 modes) | **MISSING** in content ops | — | | **Soft mask** | `beginSoftMasked()`/`endSoftMasked()` with luminosity mask, backdrop, transfer function | **MISSING** | — | --- ## 4. Path Construction & Painting | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **PdfPath** | Sealed path segment hierarchy: `PdfMoveTo`, `PdfLineTo`, `PdfCubicTo`, `PdfClosePath` | **EXISTS** — `PdfOp::MoveTo/LineTo/CurveTo/ClosePath` | No sealed type; segments are enum variants directly | | **PdfStroke** | Width, cap (0/1/2), join (0/1/2), miter limit, dash array, dash phase | **EXISTS** — `SetLineWidth`, `SetLineCap`, `SetLineJoin`, `SetMiterLimit`, `SetDash` | — | | **PdfFillRule** | `nonzero`, `evenOdd` | **EXISTS** — separate ops `Fill`/`FillEvenOdd` | — | | **Ellipse approximation** | 4× Bézier quarter-arc with `_kappa` constant | **EXISTS** — in `content_writer.dart` helper | Not in makepad core | | **Rounded rect** | Full rounded-rect path via 4× Bézier + straight lines | **EXISTS** — `PdfOp::RoundedRect` | — | | **SVG path parsing** | Not in dart-pdf; separate | **EXISTS** — `svg_path.rs` → `parse_svg_path()` | Makepad has this, dart-pdf doesn't | | **Gradient path fill** | `fillPathGradient()` with axial/radial shading patterns | **MISSING** | — | | **Gouraud mesh** | `fillMesh()` for mesh shadings (types 4–7) | **MISSING** | — | --- ## 5. Text Operations | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **Text state** | `Tf`, `Td`, `TD`, `Tm`, `T*`, `TL`, `Tc`, `Tw`, `Tz`, `Ts`, `Tr` | **EXISTS** — all text state ops in `PdfOp` | — | | **Text showing** | `Tj`, `TJ`, `'`, `"` operators | **EXISTS** — `ShowText`, `ShowTextArray`, `ShowTextNextLine`, `ShowTextNextLineSpacing` | — | | **PdfTextRun** | Rich text run: transform, color, gradient, font info, glyph outlines, invisible flag, stroke mode, MCID | **MISSING** | No text run abstraction; text is just bytes in content ops | | **PdfGlyphPlacement** | Per-glyph outline + offset in em units | **MISSING** | — | | **Text extraction** | `TextExtraction` module, `text_diff.dart`, `struct_text.dart` | **MISSING** | — | | **Text clipping** | Render modes 4–7: glyph outlines accumulate for clipping at ET | **MISSING** | — | | **Vertical text** | Vertical writing mode support with `offsetY` | **MISSING** | — | | **Font metrics** | `measureHelvetica()`, base-14 width tables | **EXISTS** — `char_width()` in `font.rs` | — | | **ContentWriter text ops** | `beginText()`, `endText()`, `font()`, `textAt()`, `showText()`, `showGlyphHex()`, `nextLine()`, `charSpacing()`, `horizontalScale()` | **EXISTS** — `PdfOp` variants | No `ContentWriter` abstraction yet | --- ## 6. Color & Color Spaces | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **PdfColor (RGB)** | `PdfColor(r, g, b)` with named constructors: `gray()`, `cmyk()`, `black`, `white` | **EXISTS** — `PdfColor` (RGBA f32) | — | | **DeviceGray** | Single-channel gray | **EXISTS** — via `PdfColor::gray()` | — | | **DeviceRGB** | Standard RGB | **EXISTS** — `PdfColor` default | — | | **DeviceCMYK** | CMYK color space | **PARTIAL** — `SetStrokeCmyk`/`SetFillCmyk` ops exist | No CMYK→RGB conversion | | **CalGray** | CIE-based calibrated gray | **MISSING** | — | | **CalRGB** | CIE-based calibrated RGB | **MISSING** | — | | **Lab** | CIE L\*a\*b\* | **MISSING** | — | | **ICCBased** | ICC profile-based color management | **MISSING** | — | | **Indexed** | Palette-based: 1 component selects from lookup table | **MISSING** | — | | **Separation** | Single-colorant space with tint transform | **MISSING** | — | | **DeviceN** | Multi-colorant space with tint transform | **MISSING** | — | | **Pattern** | Pattern color space | **MISSING** | — | | **PdfColorSpace parser** | `PdfColorSpace.parse()` resolves any CS object: device, calibrated, ICC, indexed, separation, deviceN, pattern | **MISSING** | — | | **ICC profile** | `IccProfile.parse()` with channel count, sRGB conversion | **MISSING** | — | | **Calibrated color** | `PdfCalibratedColorSpace` for CalGray/CalRGB/Lab | **MISSING** | — | | **Function-based tint** | `PdfFunction` for Separation/DeviceN tint transforms | **MISSING** | — | | **Color component parsing** | `sc`/`scn`/`SC`/`SCN` ops resolve through active color space | **PARTIAL** — `SetFillColor(Vec)` raw | No space-aware resolution | | **Overprint** | `setOverprint()` for subtractive color compositing | **MISSING** | — | | **Color for annotations** | `_colorArray()` converts PDF color arrays (gray/RGB/CMYK) to 0xRRGGBB | **MISSING** | — | --- ## 7. Images | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **PdfImageRequest** | Image draw request: stream, transform, alpha, stencil mode, inline flag, pre-decoded pixels | **MISSING** | — | | **Image XObject parsing** | `PdfImage` with width, height, color space, bits per component, filters | **EXISTS** — `PdfImage` struct in `image.rs` | — | | **JPEG embedding** | `PdfEmbeddableImage.jpeg()` — passes DCT-encoded bytes through | **MISSING** | — | | **PNG embedding** | `PdfEmbeddableImage.png()` — decodes PNG, re-compresses as Flate, splits alpha to /SMask | **MISSING** | — | | **Image decode** | `decodeImageToRgba()` in `image.rs` | **EXISTS** — for JPEG/PNG | — | | **Image extract** | `extract_image()` reads XObject streams | **EXISTS** — in `image.rs` | — | | **Inline image decode** | `_drawInlineImage()` in interpreter | **MISSING** in interpreter | — | | **Stencil mask** | `/ImageMask` stencils with `stencilColor` | **MISSING** | — | | **Image scan pass** | `scanImagesOnly` mode: fast first pass to collect image sources | **MISSING** | — | | **Image cache** | `PdfDecodedPixels` with pixel cache keyed by stream content | **MISSING** | — | | **CMYK JPEG rejection** | CMYK JPEGs explicitly rejected for embedding | **MISSING** | — | | **JPEG info reader** | `readJpegInfo()` — SOF marker parsing for dimensions/components | **MISSING** | — | | **PNG decoder** | Full PNG decode with alpha extraction, tRNS palette support | **MISSING** | — | --- ## 8. Fonts | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **PdfFontInfo** | Loaded font: glyph outlines, widths, encoding, CMap, vertical mode | **EXISTS** — `font.rs` has char_width, glyph metrics | — | | **Type0 (composite)** | CJK fonts with CMap-based encoding | **MISSING** | — | | **Type1** | PostScript outline fonts | **MISSING** | — | | **Type3** | User-defined glyph procedures (d0/d1 metrics, CharProc content) | **MISSING** | — | | **TrueType** | TrueType embedded fonts with glyph tables | **MISSING** | — | | **CFF** | Compact Font Format outlines | **MISSING** | — | | **Base-14 metrics** | Helvetica, Times, Courier (all variants) AFM widths hardcoded | **EXISTS** — `char_width()` + metrics tables | — | | **Font embedder** | `PdfFontEmbedder` for embedding TTF/OTF fonts into PDF | **MISSING** | — | | **ToUnicode CMap** | Maps glyph codes back to Unicode for text extraction | **MISSING** | — | | **Shared font cache** | LRU cache (128 entries) across renders | **MISSING** | — | | **Glyph outlines** | `outlineFor()` returns `PdfPath` for embedded font glyphs | **MISSING** | — | | **Vertical writing** | `isVertical` flag from CIDFont WMode | **MISSING** | — | | **Standard fonts enum** | `PdfStandardFont` — 12 base-14 variants with family/bold/italic | **EXISTS** — `PdfStandardFont` enum in makepad | — | | **Font name resolver** | `PdfStandardFont.fromName()` maps any font name to base-14 | **MISSING** | — | --- ## 9. Annotations | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **PdfAnnotation base** | Full annotation: subtype, rect, flags, color, interior color, border, actions, appearance streams, opacity | **EXISTS** — `PdfAnnotation` in `annotation.rs` | — | | **PdfLinkAnnotation** | Clickable link with action (URI, GoTo, Named, JavaScript) | **EXISTS** — link handling in `annotation.rs` | — | | **PdfWidgetAnnotation** | Form field widget with field type, name, value, action | **MISSING** | — | | **Text markup** | Highlight, Underline, StrikeOut, Squiggly with QuadPoints | **EXISTS** — `Highlight` in editor.rs | Partial — only highlight; no underline/strikeout/squiggly | | **Ink** | Freehand ink strokes (InkList) | **MISSING** | — | | **FreeText** | Text annotations with /DA styling, callout lines, rich content (XHTML) | **MISSING** | — | | **Square/Circle** | Rectangle and ellipse annotations | **EXISTS** — `add_square_annotation()` | — | | **Line** | Line annotations with endpoints | **EXISTS** — `add_line_annotation()` | — | | **PolyLine/Polygon** | Multi-vertex annotations | **MISSING** | — | | **Stamp** | Stamp annotations (text stamps, image stamps, custom stamps, template stamps) | **PARTIAL** — `stamp_page()` for text stamping | No stamp annotation type; just content stream stamping | | **Measurement annotations** | Line/PolyLine/Polygon measurements with /Measure scale, takeoff metadata | **MISSING** | — | | **Redaction** | Redaction annotations | **MISSING** | — | | **Annotation behavior** | `PdfAnnotationBehavior` — editability, tap targets, style resolution | **MISSING** | — | | **Annotation editing** | `PdfAnnotationEditing` — move, resize, restyle, rotate annotations | **MISSING** | — | | **Appearance stream rendering** | `drawAnnotations()` with fallback rendering for every annotation type | **MISSING** | — | | **Annotation clipboard** | Copy/paste annotations across pages/documents | **MISSING** | — | | **Annotation sync** | Cross-device annotation synchronization | **MISSING** | — | | **Comments/threads** | `PdfCommentThread` — reply threads, state annotations | **MISSING** | — | --- ## 10. Form Fields | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **PdfAcroForm** | Full AcroForm: fields tree, default appearance, NeedAppearances | **EXISTS** — `PdfFormEditor` in `form.rs` | — | | **PdfFormField** | Terminal field: name, type, value, checked state, widgets, options, /DA, flags | **EXISTS** — `PdfFormField` in `form.rs` | — | | **PdfFieldType** | text, checkBox, radioGroup, pushButton, comboBox, listBox, signature | **EXISTS** — `PdfFieldType` in `form.rs` | — | | **Field value reading** | `value`, `isChecked`, `options` getter | **EXISTS** — `get_field_value()` | — | | **Field value writing** | `PdfFormEditor.setFieldValue()` with appearance regeneration | **EXISTS** — `set_field_value()` | — | | **Widget reconciliation** | `_reconcileOrphanWidgets()` — matches page widgets not in /Fields | **MISSING** | — | | **Form appearance regen** | `_regenerateFormAppearancesOnPage()` after edits | **MISSING** | — | | **Form admin** | `form_admin.dart` — batch field operations, field metadata | **MISSING** | — | | **Form styling** | `form_styling.dart` — font/color/border styling across fields | **MISSING** | — | | **Signature field** | `PdfFieldType.signature` | **MISSING** | — | | **Radio on-states** | `onStates`, `widgetOnState()` for radio button groups | **MISSING** | — | --- ## 11. Digital Signatures | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **PdfSignature** | Full signature: signer, reason, location, byte range, contents, subFilter, signing time | **EXISTS** — `PdfSignature` in `signature.rs` | — | | **Signature validation** | `validate()` with CMS parse, digest check, crypto verify, byte range check | **MISSING** | — | | **PAdES levels** | B-B, B-T, B-LT, B-LTA detection from CAdES markers + timestamp + DSS | **EXISTS** — `PdfPadesLevel` in `signature.rs` | — | | **CMS/PKCS#7 parse** | `CmsSignedData.parse()`, `CmsSignerInfo`, certificate extraction | **MISSING** | — | | **CMS verification** | `cmsVerify()` — digest match + RSA/ECDSA signature verify | **MISSING** | — | | **X.509 certificate** | `X509Certificate.parse()` — full TBS, extensions, OCSP/CRL URLs | **MISSING** | — | | **Certificate chain building** | `verifyCertificateChain()` — leaf to trust anchor path | **MISSING** | — | | **Trust store** | `PdfTrustStore` — add DER/PEM certificates | **MISSING** | — | | **RSA operations** | RSA sign, verify, PKCS#1 v1.5, PSS | **MISSING** | — | | **ECDSA operations** | ECDSA sign/verify with curve support | **MISSING** | — | | **Timestamp tokens** | `TimeStampToken.parse()`, RFC 3161 validation | **MISSING** | — | | **DSS (Document Security Store)** | `PdfDss.of()` — read /Certs, /OCSPs, /CRLs from catalog | **MISSING** | — | | **Revocation status** | `PdfRevocationStatus` — none/unknown/good/revoked from embedded OCSP/CRL | **MISSING** | — | | **Revocation client** | `PdfRevocationClient` typedef for fetching OCSP/CRL | **MISSING** | — | | **Timestamp client** | `PdfTimestampClient` typedef for TSA requests | **MISSING** | — | | **Signing identity** | `PdfSigningIdentity` — self-signed CA generation, certificate creation | **MISSING** | — | | **Fulcio** | `fulcio.dart` — Sigstore/Fulcio identity integration | **MISSING** | — | | **X509 builder** | `x509_builder.dart` — CA certificate generation, cert signing | **MISSING** | — | | **ASN.1** | `asn1.dart` — DER parse/serialize, sequence, set, context tags | **MISSING** | — | | **AES encryption** | AES-128, AES-256 CBC encrypt/decrypt for signatures | **EXISTS** — in `encryption.rs` | — | | **RC4 encryption** | RC4 stream cipher for classic encryption | **EXISTS** — in `encryption.rs` | — | | **CRL parsing** | `CertificateRevocationList.parse()` | **MISSING** | — | | **OCSP parsing** | `OcspResponse.parse()` | **MISSING** | — | | **ESS signing certificate** | `essSigningCertificateV2Attribute()` — PAdES baseline marker | **MISSING** | — | | **Doc timestamp validation** | `_validateDocTimeStamp()` — full RFC 3161 token check | **MISSING** | — | | **Byte-range signature** | Full ByteRange + Contents handling with gap validation | **MISSING** | — | --- ## 12. Encryption & Security | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **StandardSecurityHandler** | V1–V5, R2–R6: RC4 40/128-bit, AES-128, AES-256, password auth | **EXISTS** — `PdfEncrypt` in `encryption.rs` | — | | **RC4** | RC4 40-bit and 128-bit encryption/decryption | **EXISTS** — `rc4.rs` | — | | **AES-128** | AES CBC with per-object keys | **EXISTS** — `aes.rs` | — | | **AES-256** | AES-256 with SHA-256/384/512 hash-2B algorithm (R5/R6) | **EXISTS** — `PdfEncrypt` supports AES-256 | — | | **Password auth** | User password, owner password, Algorithm 2–7 | **EXISTS** — `authenticate()` | — | | **Permission flags** | /P permission word handling | **EXISTS** — `Permission` struct | — | | **Encrypt metadata** | /EncryptMetadata flag — exempt /Metadata streams | **MISSING** | — | | **Crypt filter** | /Crypt filter with Identity bypass | **MISSING** | — | | **Object-level key derivation** | Algorithm 1: file key + obj num + gen + salt | **EXISTS** — per-object key derivation | — | | **Encrypt on write** | `encryptObjectGraph()` — deep-copy with encryption | **MISSING** | — | | **Decrypt on read** | `decryptObjectGraph()` — in-place decryption of string graph | **MISSING** | — | | **Stream payload encryption** | `streamPayloadIsEncrypted()` with XRef/Metadata/Crypt exemptions | **MISSING** | — | --- ## 13. Page Management | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **PdfPage** | MediaBox, CropBox, rotation, resources, annotations, content bytes | **EXISTS** — `PdfPage` in `page.rs` | — | | **Page tree** | Full page tree walk with inherited attributes, /Count hints, leaf caching | **PARTIAL** — basic page tree | No inherited attribute merging; no /Count skip optimization | | **Page count** | `pageCount` from leaf walk (not /Count) | **EXISTS** — `page_count()` | — | | **MediaBox/CropBox** | Resolved through tree with US Letter fallback | **EXISTS** — `media_box` | CropBox not explicitly surfaced | | **Rotation** | Inherited rotation, normalized to 0/90/180/270 | **EXISTS** — `rotate` field | — | | **Content bytes** | `contentBytes()` — concatenates decoded /Contents streams | **EXISTS** — `content_bytes()` | — | | **Page cache** | `_pageCache` by index, `invalidatePageCache()` on structural edits | **MISSING** | — | | **Page labels** | `page_labels.dart` — numbered page labels from /PageLabels | **MISSING** | — | | **Page format** | `page_format.rs` — page creation with format presets | **EXISTS** | — | | **Page operations** | `page_ops.rs` — page manipulation operations | **EXISTS** | — | | **Multi-page** | `multi_page.rs` — multi-page support | **EXISTS** | — | --- ## 14. Document Metadata & Info | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **Document info** | `/Title`, `/Author`, `/Subject`, `/Keywords`, `/Creator`, `/Producer` | **EXISTS** — `PdfDocumentInfo` in `document_info.rs` | — | | **setInfo()** | `PdfEditor.setInfo()` — write/update document info dictionary | **MISSING** | — | | **XMP metadata** | `xmp.dart` — XMP packet reading/writing | **MISSING** | — | | **Document ID** | Trailer /ID array | **EXISTS** — in encryption | — | | **Trailer** | Full trailer dictionary access | **EXISTS** — `trailer` field | — | | **Version** | `%PDF-` header version + catalog /Version | **PARTIAL** — header only | — | --- ## 15. Bookmarks & Outlines | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **PdfOutline** | Read outline tree: items, destinations, open/closed, bold/italic/color | **EXISTS** — `PdfBookmarkTree` in `outline.rs` | — | | **PdfOutlineItem** | Title, destination, open state, children, bold, italic, color | **EXISTS** — `PdfBookmarkItem` in `outline.rs` | — | | **PdfDestination** | Page + fit type (Fit, FitH, FitV, FitR, XYZ) with params | **EXISTS** — `PdfOutlineDestination` | — | | **PdfExplicitDestination** | Write-side destination builder | **MISSING** | — | | **Outline editing** | `PdfOutlineEditing` — add, remove, reorder, restyle items | **MISSING** | — | | **Named destinations** | /Dests dictionary and /Names → /Dests name tree | **MISSING** | — | --- ## 16. Transparency & Compositing | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **Blend modes** | 16 PDF blend modes (normal, multiply, screen, overlay, darken, lighten, etc.) | **EXISTS** — `BlendMode` enum in `transparency.rs` | — | | **Blend pixel** | `blend_pixel()` — per-pixel compositing with blend modes | **EXISTS** — in `transparency.rs` | — | | **Transparency group** | `TransparencyGroup` — group compositing | **EXISTS** — in `transparency.rs` | — | | **Soft mask** | `SoftMask` struct | **EXISTS** — in `transparency.rs` | — | | **beginGroup/endGroup** | `PdfDevice.beginGroup(alpha, knockout)` / `endGroup()` | **MISSING** | — | | **beginSoftMasked/endSoftMasked** | Offscreen capture with luminosity mask, backdrop luminance, transfer function | **MISSING** | — | | **Alpha** | Per-element alpha in `fillPath()` and `strokePath()` | **EXISTS** — alpha in `PdfColor` (RGBA) | — | | **Overprint mode** | /OPM 0 or 1 | **MISSING** | — | --- ## 17. Shading & Gradients | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **Axial shading** | Linear gradient shading patterns | **EXISTS** — `Gradient::Linear` in `gradient.rs` | — | | **Radial shading** | Radial gradient shading patterns | **EXISTS** — `Gradient::Radial` in `gradient.rs` | — | | **Mesh shadings (types 4–7)** | Free-form Gouraud, Coons, tensor-product mesh shadings | **MISSING** | — | | **PdfGradient** | Axial/radial gradient with color stops, `averageColor` fallback | **MISSING** | — | | **PdfMesh** | Gouraud triangle mesh with `averageColor` | **MISSING** | — | | **Shading function** | Function-based shadings | **MISSING** | — | | **Tiling patterns** | PaintType 1 patterns with cell content | **MISSING** | — | | **build_gradient_ops** | `gradient.rs` — generate PDF ops for gradient fill | **EXISTS** | — | | **Tile modes** | `TileMode` enum (clamp, repeat, reflect) | **EXISTS** — in `gradient.rs` | — | | **Function parsing** | `PdfFunction` for type 2 (exponential) and type 3 (stitching) | **MISSING** | — | --- ## 18. Filters & Compression | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **FlateDecode** | zlib decompression | **EXISTS** — in `filter.rs` | — | | **LZWDecode** | LZW decompression | **EXISTS** — `filter_lzw.rs` | — | | **RunLengthDecode** | Run-length decompression | **EXISTS** — `filter_runlength.rs` | — | | **CCITTFaxDecode** | Group 3/4 fax decompression | **EXISTS** — `filter_ccitt.rs` | — | | **DCTDecode** | JPEG passthrough (no decode needed for embedding) | **EXISTS** — `PdfFilter::Dct` | — | | **ASCII85Decode** | ASCII85 decompression | **MISSING** | — | | **ASCIIHexDecode** | ASCII hex decompression | **MISSING** | — | | **Crypt filter** | /Crypt filter for decryption passthrough | **MISSING** | — | | **Filter chain** | Multiple filters in sequence | **EXISTS** — filter array parsing | — | | **DecodeParms** | Filter parameters handling | **PARTIAL** | — | | **Encode (write)** | FlateEncode for embedding content/images | **EXISTS** — `PdfFilter::Flate` encode | — | --- ## 19. Editing & Writing | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **PdfEditor** | High-level editing session: accumulate edits, save as incremental update | **EXISTS** — `PdfEditor` in `editor.rs` | — | | **CosIncrementalUpdater** | Append-only incremental revision builder | **EXISTS** — `PdfIncrementalUpdater` in `writer.rs` | — | | **replace_page_content** | Replace page content stream ops | **EXISTS** — `replace_page_content()` | — | | **stamp_page** | Stamp text on page | **EXISTS** — `stamp_page()` | — | | **add_highlight** | Add highlight annotation | **EXISTS** — `add_highlight()` | — | | **add_square_annotation** | Add square annotation | **EXISTS** — `add_square_annotation()` | — | | **add_line_annotation** | Add line annotation | **EXISTS** — `add_line_annotation()` | — | | **ContentWriter** | Operator-by-operator content stream builder | **MISSING** (no equivalent abstraction) | — | | **Form editing** | `PdfFormEditor.setFieldValue()` with appearance regen | **EXISTS** — `set_field_value()` | — | | **Page rotation** | `rotatePage()` with form appearance regen | **MISSING** | — | | **Annotation editing** | Move, resize, rotate, restyle, delete annotations | **MISSING** | — | | **Free text editing** | Create/edit free-text annotations with styling | **MISSING** | — | | **Ink editing** | Create/edit freehand ink strokes | **MISSING** | — | | **Image stamp** | `addImageStamp()` — embed image as stamp annotation | **MISSING** | — | | **Stamp templates** | `PdfStampTemplate` — reusable stamp templates | **MISSING** | — | | **Outline editing** | Add, remove, reorder bookmarks | **MISSING** | — | | **Page editor** | Add, remove, reorder, duplicate pages | **MISSING** | — | | **Content editor** | Modify base page content (reflow, rewrite operators) | **MISSING** | — | | **Redaction** | Apply redaction annotations (whiteout) | **MISSING** | — | | **OCR editor** | `ocr_editor.dart` — OCR integration for scanned pages | **MISSING** | — | | **Attachment editor** | Add/remove file attachments | **MISSING** | — | | **Header/footer** | `header_footer.dart` — add headers/footers across pages | **MISSING** | — | | **Save as bytes** | `save()` → full file bytes; `saveTail()` → incremental append only | **EXISTS** — `save()` → bytes | — | | **Undo support** | `save_state()` before edits for undo | **EXISTS** — `save_state()` | — | | **Impact tracking** | `PdfEditImpact` — tracks which pages changed visually/content/annotations | **MISSING** | — | | **Struct tree editing** | `writeStructTree()` — write tagged PDF structure | **MISSING** | — | | **Page labels editing** | Edit page label numbers and prefixes | **MISSING** | — | --- ## 20. Accessibility & Conformance | Feature | dart-pdf Description | Makepad Status | What's Missing | |---|---|---|---| | **StructTree** | `PdfStructTree` — full structure tree: role map, elements, reading order | **MISSING** | — | | **PdfStructElement** | Structure type, alt text, actual text, language, children, MCID refs | **MISSING** | — | | **PdfStructParentTree** | Number tree mapping MCIDs to structure elements | **MISSING** | — | | **PdfStructSpec** | Write-side structure spec for tagging content | **MISSING** | — | | **Tagged PDF** | `pdfIsMarkedTagged()` — check /MarkInfo /Marked | **MISSING** | — | | **PDF/UA** | `pdf_ua.dart` — PDF/UA-1 validation checker | **MISSING** | — | | **PDF/A** | `pdf_a.dart` — PDF/A-2b validation checker | **MISSING** | — | | **Conformance report** | `PdfConformanceReport` with error/warning/info findings by rule | **MISSING** | — | | **Alt text** | Structure element `/Alt` for figures and non-text content | **MISSING** | — | | **Actual text** | `/ActualText` for ligature/abbreviation expansion | **MISSING** | — | | **Reading order** | `elementsInReadingOrder()` — depth-first pre-order tree walk | **MISSING** | — | | **Object references** | `/OBJR` — structure element to whole-object reference | **MISSING** | — | | **MCID tagging** | Marked-content IDs linking content to structure | **MISSING** | — | | **Document AI** | `document_ai.dart` — AI-assisted document analysis | **MISSING** | — | --- ## Summary of Makepad Strengths (not in dart-pdf) | Feature | Makepad Status | dart-pdf Status | |---|---|---| | **SVG path parsing** | `svg_path.rs` — full SVG path to PDF ops | Not present | | **Barcode generation** | `barcode.rs` — Code128, Code39, EAN13, QR code | Not present | | **Table layout** | `table_layout.rs` — `TableStyle`, `render_table()` | Not present | | **Watermark** | `watermark.rs` — watermark builder API | Not present | | **Rounded rect** | `PdfOp::RoundedRect` — built-in op | ContentWriter helper only | | **Gradient generation** | `build_gradient_ops()` in `gradient.rs` | Not in public API | | **PDF version from bytes** | Reads `%PDF-` header directly | — | --- ## Priority Gaps (Highest Impact) 1. **Text extraction / PdfTextRun** — makepad has no equivalent of dart-pdf's rich `PdfTextRun` with glyph outlines and MCID tagging 2. **Color space system** — dart-pdf has a complete `PdfColorSpace` parser for all 10+ families; makepad has none 3. **Signature validation** — dart-pdf has full CMS/X.509/PAdES/chain verification; makepad only has the signature struct 4. **Accessibility/Tagged PDF** — dart-pdf has `PdfStructTree`, `PdfStructElement`, PDF/UA, PDF/A validation; makepad has none 5. **Form widget rendering** — dart-pdf renders widget annotations; makepad lacks this 6. **ICC profiles** — dart-pdf has `IccProfile.parse()` with sRGB conversion; makepad has none 7. **Transparency compositing** — dart-pdf's `beginGroup`/`beginSoftMasked` with offscreen capture; makepad has basic blend modes only 8. **Mesh shadings** — dart-pdf handles types 4–7 mesh shadings; makepad has none 9. **Annotation rendering** — dart-pdf renders all annotation types with fallbacks; makepad only annotates via content ops 10. **XMP metadata** — dart-pdf has XMP read/write; makepad has none # Unified Execution Plan: Port dart-pdf Architecture into Makepad ## Synthesis of Three Reviews All three reviews converge on the same diagnosis: **we shipped a feature dump, not an engine**. The code has ~39K lines of PDF modules that are largely disconnected from the 1,200-line viewer widget. The path forward is not "fix the renderer" — it's **adopt dart-pdf's architectural boundaries and build one vertical slice to proof**. --- ## Phase 0: The Purge (Days 1–3) **Goal:** One renderer. No fake APIs. No orphan modules. Clean baseline. | Action | Target | Rationale | |--------|--------|-----------| | Archive current branch | `git tag archive/pdf-experiment-$(date +%Y%m%d)` | Preserve work without blocking | | Start new branch from pinned `dev` | `b41e7404b6bb893d32f7d2924cd7b1c28983547f` | Clean baseline | | Delete `PdfFormFilling` no-op methods | `form.rs` | `Ok(())` that mutates nothing is a lie to callers | | Delete empty `render_annotations` / `render_form_fields` / `redraw_all` stubs | `pdf_view.rs` | Dead integration seams | | Delete or feature-gate hand-rolled crypto | `encryption.rs` (MD5/SHA/AES/RC4), `cms.rs` | CVE factory; replace with `ring`/`sha2`/`aes` when signatures are actually needed | | Delete orphan wiring files (or keep behind `pdf_wiring` feature) | `form_widget_ui.rs`, `pdf_view_wiring.rs`, `inline_image_render.rs`, `annotation_click_handler.rs` | Reference code that compiles but does nothing | | Delete all 40 new parser modules that the viewer doesn't call | jbig2, trust_store, cms, ocr, stamp_annotation, redaction_annotation, measurement_annotations, etc. | Feature accretion without integration | | Choose one `CachedPage` struct and delete the other | `pdf_view.rs` | DRY violation | | Remove `create_texture_from_pdf_image` duplicates (keep one in `makepad_pdf_device.rs`) | `inline_image_render.rs`, `pdf_view_wiring.rs` | Copy-pasted in 3 files | | Record toolchain + Makepad commit + build commands | `Makefile` or `AGENTS.md` | Reproducible builds | **Exit criterion:** `cargo check -p makepad-pdf-parse` and `cargo check -p makepad-widgets --features pdf` pass with zero behavior changes. No API surface claims features it doesn't deliver. --- ## Phase 1: Architecture — Split the Monolith (Days 4–10) **Goal:** Mirror dart-pdf's layered crate structure. Strict dependency boundaries. ### Target crate layout ``` makepad-pdf-cos ← lexer, parser, filters, xref, COS objects NO makepad_draw imports NO rendering types makepad-pdf-document ← page tree, annotations, AcroForm model, destinations, object references NO makepad_draw imports NO content interpreter makepad-pdf-graphics ← content interpreter, PdfDevice trait, GraphicsState, fonts, image decode, text extraction, RenderCommand recording NO makepad_draw imports Engine-neutral makepad-pdf-makepad ← MakepadPdfDevice (implements PdfDevice), GPU textures, PdfView widget, input routing, viewport, caching THIS is where makepad_draw lives optional: makepad-pdf-signing, makepad-pdf-ocr (separate repos/branches) ``` ### Dependency rules (non-negotiable) - `cos` → standalone Rust crate - `document` → depends on `cos` only - `graphics` → depends on `cos` + `document` only - `makepad` integration → depends on all above + `makepad_draw` - **UI never mutates COS dictionaries directly.** It submits typed document edits. - **PdfDevice cannot expose Makepad types.** Makepad implements it in the integration crate. ### What moves where | Current location | New crate | Notes | |-----------------|-----------|-------| | `object.rs`, `stream.rs`, `filter.rs`, `xref.rs`, `lexer.rs` | `pdf-cos` | Pure parsing, no UI | | `document.rs`, `page.rs`, `annotations.rs`, `form.rs` (model only), `destinations.rs` | `pdf-document` | Document semantics | | `device.rs`, `content.rs`, `font*.rs`, `image.rs`, `color_space.rs`, `graphics_state.rs`, `pattern.rs`, `transparency.rs`, `clipping.rs` | `pdf-graphics` | Engine-neutral | | `makepad_pdf_device.rs`, `pdf_view.rs` | `pdf-makepad` | Widget + GPU | | `encryption.rs`, `signature_crypto.rs`, `trust_store.rs`, `cms.rs` | **delete or quarantine** | Not real implementations | ### RecordingDevice for testing Before writing `MakepadPdfDevice`, create a `RecordingDevice` that captures all `PdfDevice` calls as structured `RenderCommand` values. This lets you: - Golden-test the interpreter output against a corpus - Verify operator sequencing without GPU context - Compare against dart-pdf's interpreter output **Exit criterion:** Four crates compile independently. `pdf-graphics` has zero Makepad imports. `RecordingDevice` captures ops from `interpret_ops` on a test PDF. --- ## Phase 2: Rendering Pipeline — MakePdfDevice the Only Renderer (Days 11–20) **Goal:** Delete the inline renderer. Wire the device as the sole rendering path. Fix the lies. ### Step 2.1: Wire MakepadPdfDevice into PdfPageView 1. Move `render_page_via_device` from the wiring file into the real `impl PdfPageView` block in `pdf_view.rs` 2. Delete from `pdf_view.rs`: - Private `GfxState` struct - `render_vectors` / `render_text` methods - `draw_one_text` / `pick_draw_text` helpers - `mul_mat` / `text_advance` / `cmyk_to_rgb` / `color_from_vals` functions - `collect_text_segments` (keep for text selection, re-implement as post-device pass) 3. The widget's `draw_walk` calls `render_page_via_device` which instantiates `MakepadPdfDevice`, feeds it the cached `Vec`, and drives rendering ### Step 2.2: Introduce RenderCommands Replace direct `PdfOp` replay with an intermediate layer: ``` PdfOps (PDF-native) ↓ interpret_ops + RecordingDevice RenderCommand (engine-neutral) ↓ MakepadPdfDevice Makepad GPU primitives ``` `RenderCommand` variants: - `SaveState` / `RestoreState` - `SetTransform([f64; 6])` - `SetStrokeColor(PdfColor)` / `SetFillColor(PdfColor)` - `SetLineWidth(f64)` / `SetLineCap(i32)` / `SetLineJoin(i32)` - `MoveTo(f64, f64)` / `LineTo(f64, f64)` / `CurveTo(...)` / `ClosePath` - `Stroke` / `Fill` / `FillEvenOdd` / `StrokeClose` - `Clip(Path)` - `BeginText` / `SetFont(String, f64)` / `ShowText(Vec)` / `EndText` - `PaintImage { texture_key, ctm: [f64; 6] }` - `PaintFormXObject { name, ctm: [f64; 6] }` Cache `Vec` per page in `CachedPage`. Replaying cached commands is faster than re-parsing content streams. ### Step 2.3: Fix the lies | Bug | Fix | |-----|-----| | Inline images silently dropped | `RecordingDevice` captures `InlineImage` ops; `MakepadPdfDevice` renders them | | `paint_x_object` ignores rotation/skew | Use full 6-component CTM bounding-box transform (all 4 corners) | | `clip()` stores path then clears it | Implement scissor rect for axis-aligned clips; stencil for complex paths | | XObject not decoded on cache miss | Decode on worker thread, store BGRA in `CachedPageExt::image_data`, create Texture on UI thread | | `set_dash` is TODO | Map PDF dash array to line stipple or shader uniform | | Color space is TODO | Route through `color_space.rs`; support DeviceGray/RGB/CMYK + ICCBased minimum | **Exit criterion:** One PDF with text, vectors, and images renders identically through both `RecordingDevice` (golden test) and `MakepadPdfDevice` (visual check). Old inline renderer code is deleted. --- ## Phase 3: Document Model — Real State, Not Stubs (Days 21–30) **Goal:** Document-owned mutable state. Typed edits. No `Ok(())` lies. ### Step 3.1: Object references and identity - Pages, annotations, and form fields must be keyed by **object reference** (generation + offset), not name strings - Resolve inherited field keys up `/Parent` chains in AcroForm - Preserve widget↔field object identity across parse ### Step 3.2: Annotation model - Parse all annotation types from page dictionaries - Resolve page references correctly (never default to page 0) - Store annotations in document-owned `Vec` per page - Background loader sends **real** annotation data, not empty vectors ### Step 3.3: Form field model - Parse AcroForm field tree with `/FT`, `/Ff`, `/V`, `/AS`, `/DV` - Resolve inherited values from parent fields - Distinguish text / checkbox / radio / choice / signature - Track dirty state per field - **Delete `PdfFormFilling`** and replace with `DocumentFormEditor` that: - Takes typed edit commands (`SetText { field_ref, value }`, `SetCheck { field_ref, on/off }`) - Mutates document-owned state - Returns `Result<(), FormError>` with real error types - Invalidates affected page appearance ### Step 3.4: Appearance generation (document-level) - Regenerate field appearances as a document operation (not widget code) - Generate minimal appearance streams for text fields and checkboxes - Test: edit → regenerate appearance → serialize → reparse → verify roundtrip **Exit criterion:** Parse a real AcroForm PDF, modify a text field value via `DocumentFormEditor`, verify the field's `/V` changed and the dirty flag is set. No save/write yet — just in-memory correctness. --- ## Phase 4: Interactivity — Wire the Orphan Files (Days 31–38) **Goal:** PdfPageView responds to user input. Forms work. Links navigate. ### Step 4.1: Enable interaction ```rust fn is_interactive(&self) -> bool { true } fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { // 1. Form field events (focus, keyboard, click) if handle_form_field_event(cx, event, &mut self.form_focus, ...) { self.redraw_all(); return; } // 2. Annotation hit testing (links, buttons) if let Some(action) = annotation_click_handler::hit_test(event, &self.annotations, ...) { self.action_tx.send(action); // emit to host return; } // 3. Text selection (drag to select, copy) // 4. Viewport (zoom, pan, scroll) } ``` ### Step 4.2: Annotation rendering - Draw link affordances (blue underline or rect) - Draw form field widget borders - Draw FreeText backgrounds - Cursor feedback: pointer over links, I-beam over text fields ### Step 4.3: Form rendering - Render field values inside text field rects - Show focused field highlight - Render combo dropdown overlay - On value change → `DocumentFormEditor` → invalidate appearance → redraw ### Step 4.4: Navigation actions - Emit typed actions to host: `PdfAction::OpenUri(String)`, `PdfAction::GoTo { page, dest }`, `PdfAction::Launch { file }` - **Viewer never opens URLs/browsers itself.** Host application receives actions. **Exit criterion:** Click a link in a real PDF → host receives `OpenUri`. Click a text field → type text → field value updates → appearance regenerates → visual change visible. --- ## Phase 5: Font Engine & Text — Replace Magic with Metrics (Days 39–48) **Goal:** Correct font metrics. Proper text selection. Real glyph advances. ### Step 5.1: Font metrics - Remove `fs * 0.75` and `fs * 0.8` magic numbers - Use actual font ascent/descent/cap-height from font dictionaries - For Type1: parse AFM or font dictionary metrics - For TrueType/CFF: read `hhea`, `OS/2` tables ### Step 5.2: Text selection - Replace byte-proportional width with accumulated glyph advances - Cache `GlyphAdvanceCache` per page (keyed by font name + size) - Rebuild only when page data changes, not every frame - Use `char_width()` from `font.rs` with proper font scaling ### Step 5.3: Composite fonts - CMap parsing for CID fonts (identity, GBK, UniCNS, etc.) - ToUnicode map for text extraction - Two-byte character handling in `text_advance` ### Step 5.4: Text extraction API - `PageText::text_segments() -> Vec` with position data - `PageText::find(query: &str) -> Vec` with page/rect coordinates - Feed selection/copy/search from this API **Exit criterion:** Select text across a line with mixed fonts. Verify selection rectangles align with actual glyph positions. Copy produces correct Unicode. Search finds a string and highlights the right page/position. --- ## Phase 6: Testing Infrastructure — Earn the Green Checkmarks (Days 49–56) **Goal:** Real PDFs. Real regressions. No test theater. ### Step 6.1: PDF corpus Check into repo: ``` tests/fixtures/ basic/ ← text-only, vector-only, simple images fonts/ ← Type1, TrueType, CID, composite, CJK forms/ ← text field, checkbox, radio, combo annotations/ ← link, FreeText, widget, popup images/ ← JPEG, PNG, inline, XObject edge/ ← rotation, crop boxes, nested CTMs malformed/ ← truncated, corrupt xref, bad streams encrypted/ ← RC4, AES (once real crypto is added) ``` Source: Ghent PDF Output Suite (subset), PDF.js edge-case corpus, hand-crafted minimal PDFs. ### Step 6.2: Integration tests ```rust #[test] fn renders_text_vectors_images() { let doc = PdfDocument::parse(include_bytes!("fixtures/basic/hello.pdf")); let page = doc.page(0); let ops = parse_content_stream(&page.content_data); let mut recorder = RecordingDevice::new(); let mut state = PdfGfxState::default(); interpret_ops(&mut recorder, &ops, &mut state); assert!(!recorder.commands.is_empty()); assert!(recorder.commands.iter().any(|c| matches!(c, RenderCommand::ShowText(_)))); } ``` ### Step 6.3: Fuzz testing ```bash cargo fuzz run parse_content_stream -- -max_total_time=300 cargo fuzz run parse_xref -- -max_total_time=300 cargo fuzz run parse_object -- -max_total_time=300 ``` Target: zero panics on arbitrary input. All errors are `Result::Err`, never `unwrap` on untrusted data. ### Step 6.4: Visual regression (optional, later) - Serialize `Vec` as golden files - Compare across runs for deterministic output - Later: pixel comparison against PDFium baseline **Exit criterion:** `cargo test` runs corpus tests. `cargo fuzz` runs for 5 minutes without panics. CI produces pass/fail report. --- ## Phase 7: Performance — After Correctness (Days 57–64) **Goal:** Make it fast enough to ship. Not before correctness is proven. ### Step 7.1: Off-thread parsing - Wire `load_pdf_data_off_thread` into the real widget lifecycle - Show loading placeholder during parse - Generation IDs on worker results (stale results cannot update new document) ### Step 7.2: Texture management - LRU cache for decoded image textures - Evict textures for pages that scroll out of view - GPU upload only on UI thread ### Step 7.3: Render command cache - Cache `Vec` per page in `CachedPage` - Replaying cached commands skips re-parsing content streams - Dirty-region rendering for interactive edits (form field changes) ### Step 7.4: Progressive loading (stretch) - Port dart-pdf's `PdfByteSource` concept: random-access byte reads - First-paint: parse xref + render page 1 while rest downloads - Bounded fetch with cancellation **Exit criterion:** Large PDF (50+ pages) loads and renders first page in <200ms. Rapid document switching doesn't leak memory or show stale pages. --- ## Phase 8: Advanced Features — Separate Projects **Do not start any of these until Phases 0–7 are complete and tested.** Each gets its own branch, design doc, and merge criteria: | Feature | Prerequisites | Estimated effort | |---------|--------------|-----------------| | **Encryption** (RC4/AES-128/256) | Real crypto crates (`ring`, `aes`), byte-range tests | 2–3 weeks | | **Signatures** | CMS/X.509 via `x509-cert` + `rsa`/`p256`, trust store, interop tests against OpenSSL | 4–6 weeks | | **Annotation editing** | Document model, appearance generation, incremental save | 3–4 weeks | | **AcroForm full support** | Document model, appearance regeneration, JavaScript actions | 4–6 weeks | | **Incremental save** | Writer/serializer, byte-preserving tests, signature preservation | 3–4 weeks | | **Advanced color** (ICC, DeviceN, Separation) | ICC profile parser, Lab/XYZ conversion, tint transforms | 3–4 weeks | | **Advanced transparency** (blend modes, soft masks, groups) | Render graph, compositing, GPU blending | 4–6 weeks | | **JBIG2 / JPX** | External decoder crates or FFI, test fixtures | 2–3 weeks each | | **OCR** | Host opt-in only, tesseract/ONNX integration, API key policy | 2–3 weeks | | **PDF/UA structure tree** | Tagged PDF model, accessibility tree | 3–4 weeks | --- ## Non-Negotiable Rules (All Phases) 1. **One user-visible capability per PR.** No multi-feature dumps. 2. **No empty integration methods or `Ok(())` no-ops** in exposed APIs. 3. **No `TODO` that substitutes a fake default** (page-0 fallback, empty forms, unchanged redaction). 4. **Parser has no `makepad_draw` dependency.** Widgets own event routing and drawing. 5. **Viewer never launches commands/URLs.** Host receives typed actions. 6. **Unsupported operations return typed errors**, not silent degradation. 7. **Every feature PR includes:** at least one real-PDF regression fixture + one behavior test. 8. **Apache-2.0 attribution.** If dart-pdf code is ported: `THIRD_PARTY_NOTICES.md`, port ledger per file, legal review before distribution. --- ## Phase Summary | Phase | Duration | Deliverable | Success Metric | |-------|----------|-------------|----------------| | **0: Purge** | Days 1–3 | Clean baseline, no lies | `cargo check` passes, no behavior changes | | **1: Architecture** | Days 4–10 | 4-crate split, RecordingDevice | `pdf-graphics` compiles without `makepad_draw` | | **2: Rendering** | Days 11–20 | Single renderer, RenderCommands | Real PDF renders through device only | | **3: Document Model** | Days 21–30 | Typed edits, real form state | Edit field → verify `/V` changed | | **4: Interactivity** | Days 31–38 | Forms, links, navigation work | Click link → host action; type in field → visible | | **5: Font Engine** | Days 39–48 | Correct metrics, text selection | Select/copy text across mixed fonts | | **6: Testing** | Days 49–56 | Corpus, fuzz, CI | `cargo fuzz` runs 5min without panic | | **7: Performance** | Days 57–64 | Off-thread, caching, texture LRU | First page <200ms, no memory leak | | **8: Advanced** | Separate branches | Each feature independently | Per-feature design doc + tests | **Total to a shippable v1 read-only viewer:** ~64 working days. **The dart-pdf lesson:** Port the **architecture, contracts, and test strategy** — not the files. A read-only viewer with correct rendering, working forms, and clickable links is a real product. 40 disconnected modules around a broken renderer is not.