Skip to content

Architecture overview

Orifold follows a unidirectional flow: SwiftUI views send intents to one observable view model, local PDF engines behind protocol seams do the work, and a staged export pipeline writes validated artifacts. Nothing crosses a network.

Orifold architecture: SwiftUI views send intents to one observable view model, local PDF engines behind protocol seams do the work, and a staged export pipeline writes validated artifacts inside a local-only boundary, guarded by a release gate
Layer Responsibility
SwiftUI app Document window, sidebar, canvas, annotation toolbar, search, inspector, password prompts, export controls, empty-state Recently Viewed shelf, language switcher
Workspace state Imported documents, page order, text/object edit operations, canonical replay bases, undo snapshots, comments, tags, signatures, form summaries, decorations, recent-file history
PDF services PDFKit display/composition, versioned shared PDFium page inspection, deterministic text+object replay, PDFium structural object editing/image compression/validation/glyph geometry, qpdf repair/AES-256/sanitize/structural validation, Vision OCR, form flattening, decoration baking
Signing CMS/PAdES signature construction, PKCS#12 · Keychain · self-signed identities, certificate trust evaluation, RFC-3161 timestamping
Local storage Saved PDF data, workspace metadata, source payloads, comments, signatures, page edit state
Release tooling One-line installer, package builder, Desktop update launcher, uninstaller, GitHub Actions release asset, validation tests
Orifold/
App/ App entry point, command wiring, keyboard-shortcut registry
DesignSystem/ Shared visual tokens and styling
Document/ macOS document package read/write support
Engine/ PDF loading, repair, conversion, OCR, compression, encryption, sanitize, forms, export, Recently Viewed store
Models/ Workspace, page, annotation, comment, export, decoration, comfort, and recent-file models
Pet/ Gami & Ori, the in-app companions, plus hint bubble/placement logic
Resources/ App metadata, entitlements, assets, Localizable.xcstrings (6 languages)
Signing/ Digital-ID providers, CMS construction, timestamping, trust evaluation, appearance
ViewModels/ Workspace state, document operations, search, export, undo
Views/ SwiftUI interface components
Packages/ Vendored binary engines — PDFiumBinary, QPDFBinary (universal static libs)
Tests/ Test suites run in the release gate
The staged export pipeline runs in a fixed order, and validation gates the write

Export is a sequence, not a single call: flatten annotations and form answers → bake decorations (stamps, watermarks, Bates labels, page numbers) → compress (optional image downsampling + lossless qpdf repack) → sanitize (optional strip of auto-run actions, JavaScript, embedded files, metadata) → encrypt (optional AES-256) → structural validation. The final qpdf structural check gates the write: if the artifact isn’t sound, it never reaches disk. This ordering is deliberate — flattening before decoration burn-in means nothing can be silently edited after the fact.

Committed text and object edits replay through one member-atomic path

WorkspaceEditReplayEngine takes canonical member bytes plus the committed text and object operations. It applies structural object changes first, imports transparent text-edit overlays as Form XObjects, grafts live annotations/form state with qpdf, and finishes rotations plus combined bake stamps through PDFium SaveAsCopy. PDFKit may serialize the live view as an annotation/form source, but those bytes are never used as the replay destination’s page-content base. Load/export reconciliation uses the same path, so stale bytes self-heal without one editing lane reverting the other.

Page-object consumers share one versioned PDFium inspection snapshot

PDFPageObjectInspection walks an admitted page’s PDFium objects once and projects three views: text render-mode regions, PageGraphicsIndex rule lines, and the editable PageObjectMap. The workspace assigns canonical member bytes a revision token and retains each admitted PageRef for that revision, so text and object consumers reuse one snapshot even after visiting many pages. Per-revision and process-wide page/projection-cost ceilings fail closed rather than evicting and rescanning accepted snapshots; replacing canonical bytes invalidates the token. Permission and live page rotation are projected without rescanning.

Canvas event ordering is a state-machine contract

CanvasInteractionSession turns tool changes, Delete/Escape, geometry changes, and object mutations into ordered actions. The AppKit coordinator interprets those actions, keeping undo-manager alignment, document swaps, selection cleanup, and overlay refresh order in one testable place.

Import goes through a text-layer-preserving normalizer, not a plain re-serialize

PDFKit’s dataRepresentation re-serialization drops certain text layers (Type3 / Skia-drawn glyphs), which historically caused edits to “land on top” of invisible original text. Import instead runs through a qpdf-preserving normalizer so the real text layer survives into the workspace, and text-edit geometry is measured from PDFium’s actual glyph transforms rather than re-derived. See the Developer FAQ for the full story.

Engines sit behind protocol seams, so they're swappable and testable

Each engine is reached through a protocol (PDFProcessingEngine, signing contracts, and so on) rather than called directly. That seam is why the compression backend can be swapped, why signing can be driven by a test harness with a self-signed identity, and why the suite can exercise the pipeline without the real binaries in every path.

Swift 5.9+, 100% SwiftUI. 186 source files, roughly ~68,000 lines, 752 tests gating every release, 6 languages, 4 sandbox entitlements.