51 KiB
dart-pdf vs makepad-pdf — Comprehensive Gap Analysis
Comparison of the 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<f64>) 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)
- Text extraction / PdfTextRun — makepad has no equivalent of dart-pdf's rich
PdfTextRunwith glyph outlines and MCID tagging - Color space system — dart-pdf has a complete
PdfColorSpaceparser for all 10+ families; makepad has none - Signature validation — dart-pdf has full CMS/X.509/PAdES/chain verification; makepad only has the signature struct
- Accessibility/Tagged PDF — dart-pdf has
PdfStructTree,PdfStructElement, PDF/UA, PDF/A validation; makepad has none - Form widget rendering — dart-pdf renders widget annotations; makepad lacks this
- ICC profiles — dart-pdf has
IccProfile.parse()with sRGB conversion; makepad has none - Transparency compositing — dart-pdf's
beginGroup/beginSoftMaskedwith offscreen capture; makepad has basic blend modes only - Mesh shadings — dart-pdf handles types 4–7 mesh shadings; makepad has none
- Annotation rendering — dart-pdf renders all annotation types with fallbacks; makepad only annotates via content ops
- 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 cratedocument→ depends oncosonlygraphics→ depends oncos+documentonlymakepadintegration → 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
- Move
render_page_via_devicefrom the wiring file into the realimpl PdfPageViewblock inpdf_view.rs - Delete from
pdf_view.rs:- Private
GfxStatestruct render_vectors/render_textmethodsdraw_one_text/pick_draw_texthelpersmul_mat/text_advance/cmyk_to_rgb/color_from_valsfunctionscollect_text_segments(keep for text selection, re-implement as post-device pass)
- Private
- The widget's
draw_walkcallsrender_page_via_devicewhich instantiatesMakepadPdfDevice, feeds it the cachedVec<RenderCommand>, 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/RestoreStateSetTransform([f64; 6])SetStrokeColor(PdfColor)/SetFillColor(PdfColor)SetLineWidth(f64)/SetLineCap(i32)/SetLineJoin(i32)MoveTo(f64, f64)/LineTo(f64, f64)/CurveTo(...)/ClosePathStroke/Fill/FillEvenOdd/StrokeCloseClip(Path)BeginText/SetFont(String, f64)/ShowText(Vec<u8>)/EndTextPaintImage { texture_key, ctm: [f64; 6] }PaintFormXObject { name, ctm: [f64; 6] }
Cache Vec<RenderCommand> 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
/Parentchains 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<PageAnnotation>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
PdfFormFillingand replace withDocumentFormEditorthat:- 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
- Takes typed edit commands (
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
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.75andfs * 0.8magic 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/2tables
Step 5.2: Text selection
- Replace byte-proportional width with accumulated glyph advances
- Cache
GlyphAdvanceCacheper page (keyed by font name + size) - Rebuild only when page data changes, not every frame
- Use
char_width()fromfont.rswith 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<TextSegment>with position dataPageText::find(query: &str) -> Vec<Match>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
#[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
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<RenderCommand>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_threadinto 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<RenderCommand>per page inCachedPage - 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
PdfByteSourceconcept: 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)
- One user-visible capability per PR. No multi-feature dumps.
- No empty integration methods or
Ok(())no-ops in exposed APIs. - No
TODOthat substitutes a fake default (page-0 fallback, empty forms, unchanged redaction). - Parser has no
makepad_drawdependency. Widgets own event routing and drawing. - Viewer never launches commands/URLs. Host receives typed actions.
- Unsupported operations return typed errors, not silent degradation.
- Every feature PR includes: at least one real-PDF regression fixture + one behavior test.
- 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.