CCS PDF Editor · Docs

# CCS PDF Editor — Architecture

Field | Value
--- | ---
Version | 1.5.40
Owner | CCS Information Technology
Approved by | CCS Information Technology
Last updated | 2026-08-15
Review frequency | Annual (or after significant architectural change)
Next review | 2027-08-05

High-level design of the CCS PDF Editor as an offline-first, client-side application. For ownership and residual risk see SYSTEM-PROFILE.md and GOVERNANCE.md. For deploy overview see README.md.

## 1. Purpose of the system

CCS PDF Editor is a static web application (optional Windows WebView2 shell) that:

1. Renders PDF pages (PDF.js canvas + text layer + interactive AnnotationLayer for widgets).
2. Lets staff annotate with DOM overlays in Editor workspace.
3. Lets staff fill form fields in Editor with Pointer (native widgets and/or designer fill surfaces).
4. Lets staff design fillable AcroForm fields in Form workspace; supported native widgets are auto-imported as designer overlays (no prompt).
5. Optionally runs local OCR (Tesseract.js + English data under `lib/`; `file://` uses base64 embeds).
6. Exports either:

- Project (`.ccspdf`) — PDF bytes + appended compressed edit state (overlays + form layout), or

- Final PDF — source pages + rasterized annotation layer (html2canvas) + live form widgets (pdf-lib AcroForm), optional QPDF (WASM) password protect.

7. Opens many password-protected PDFs via PDF.js `onPassword` UI.
8. Prints via the browser/print pipeline (full-opacity annotations; form designer chrome omitted).

It is not multi-tenant SaaS, not a document management system, and not a certified electronic signature platform.

## 2. Context diagram

                        ┌─────────────────────────────────────┐
                        │  Optional: intranet reverse proxy    │
                        │  TLS · optional SSO / IP allowlist   │
                        └──────────────────┬──────────────────┘
                                           │ HTTPS (static only)
                                           ▼
                        ┌─────────────────────────────────────┐
                        │  Static web host                     │
                        │  index.html, js/, lib/, assets/      │
                        │  NO document database / upload API   │
                        └──────────────────┬──────────────────┘
                                           │ download assets once
                                           ▼
         Local PDF / .ccspdf ──────►┌──────────────────────┐──────► Saved PDF / .ccspdf
         (user disk / share)        │  Browser tab memory  │        (user disk / share)
                                    │  PDF.js · overlays   │
                                    │  pdf-lib · Tesseract │
                                    └──────────────────────┘
                                           │
                                           ▼
                                    Printer / PDF printer
                                    (OS / org print path)

    Optional alternate runtimes:
      · Windows WebView2 shell (deploy_windows / build-windows.ps1)
      · Browser offline zip pack (build-release.ps1) — file:// capable
      · Tauri-style shell if adopted later (same static UI)

## 3. Application layering

    ┌──────────────────────────────────────────────────────────┐
    │  Browser / WebView2 DOM                                   │
    │  index.html · style.css · fonts.css · modals              │
    └────────────────────────────┬─────────────────────────────┘
                                 │
    ┌────────────────────────────▼─────────────────────────────┐
    │  js/ui/*          Toolbars, modals, hotkeys, header density│
    │  js/interaction/* Mouse, keyboard, drag, file import       │
    │  js/overlays/*    Text, shapes, images, tables, snip,      │
    │                   form-fields (design + Editor fill UI)    │
    │  js/features/*    OCR, print, signature + form attestation │
    │  js/engine/*      PDF render, AnnotationLayer, native-forms│
    │  js/core/*        State, history, import/export (io.js)    │
    │                   host-bridge (WebView2 open-with)         │
    └────────────────────────────┬─────────────────────────────┘
                                 │
    ┌────────────────────────────▼─────────────────────────────┐
    │  lib/*  PDF.js · pdf-lib · qpdf.wasm · html2canvas ·      │
    │         Tesseract (+ offline embeds for file://) · fonts  │
    └──────────────────────────────────────────────────────────┘

Layer | Responsibility
--- | ---
UI | Workspace switch, tools, dialogs, responsive header
Interaction | Pointer tools, multi-select, zoom/scroll; workspace isolation
Overlays | Annotation objects + form designer + Editor fill surfaces
Engine | PDF.js canvases, text layer, interactive form widgets, auto-import
Core I/O | Load PDF/project; save project; flatten + AcroForm + optional QPDF encrypt
lib/ | Third-party engines (see NOTICE)

There is no required JS build pipeline for the static web UI. Scripts load with `defer` from `index.html`. Windows EXE is produced separately via `scripts/build-windows.ps1`.

## 4. Core runtime flows

### 4.1 Open PDF

1. User selects file(s), drops onto the viewer, or (Windows shell) uses Open with / argv path.
2. Bytes enter memory (`ArrayBuffer`).
3. If encrypted, PDF.js `onPassword` drives the password modal; user password unlocks the document.
4. PDF.js renders each page to canvas (annotationMode DISABLE so widgets are not double-painted).
5. Text layer + AnnotationLayer (`renderForms: true`) for interactive native widgets.
6. Source bytes retained in `window.sourcePdfBuffers[fingerprint]` for later export.
7. If file is `.ccspdf`, trailer schema reapplied as overlays.
8. Otherwise, supported native widgets are auto-imported as Form designer overlays (silent); matching native AnnotationLayer sections are hidden so Editor fill uses the designer fill surface without doubles.

### 4.2 Edit / fill

- Annotation overlays are DOM elements inside `.pageContainer`.
- Form filling in Editor + Pointer: native AnnotationLayer widgets and/or `.form-field-fill` controls on designer overlays.
- Undo/redo records DOM operations via `historyEngine`.
- No server round-trip.

### 4.3 Workspaces (Editor vs Form)

`APP.workspaceMode` is `"editor"` or `"form"`. Body classes `workspace-editor` / `workspace-form` (and `tool-select`) control hit-testing:

Mode | Annotations | Form widgets | Native AnnotationLayer
--- | --- | --- | ---
Editor + Pointer | Full, interactive | Fill surfaces active | Interactive (unless hidden after import)
Editor + other tool | Interactive | Fill PE off | PE off (don’t steal annotate clicks)
Form | Dimmed, PE none | Design chrome + handles | Dimmed, PE none

Switching into Form always selects Pointer. Save PDF and Print temporarily apply full-opacity annotation styling so Form-mode dimming is not included in output.

### 4.4 Save Project (`.ccspdf`)

1. Acquire save destination first (native picker / modal) while user gesture is valid.
2. For each page: copy original PDF page via pdf-lib; serialize overlay + form-field JSON.
3. Gzip + hash edit state; append after PDF bytes with markers.
4. Write blob to destination.

### 4.5 Save PDF (annotations flattened + fillable fields)

1. Acquire save destination first (critical for Chromium File System Access API).
2. If the UI is in Form mode, temporarily restore full-opacity annotations for capture.
3. For each page: copy the source page; if decorative overlays exist, hide form designer widgets and the base canvas, rasterize overlays, and embed the PNG.
4. After all pages exist: create AcroForm widgets (text, checkbox, radio group, dropdown; signature field where supported).
5. Optionally encrypt the finished bytes with a user password via QPDF (WASM) (content-preserving; AcroForm fields remain fillable).
6. Write the final PDF.

### 4.6 OCR

1. Tesseract worker initialized with local `workerPath`, `corePath`, `langPath` under `./lib/`.
2. On `file://`, worker/core/lang assets load via base64 embeds + IndexedDB language seed (CORS-safe).
3. Canvas pixels recognized in-browser; text layer spans injected for selection/stamp tools.
4. No remote OCR endpoint in this release.

### 4.7 Print

Temporarily uses full-opacity annotation styling, clones page canvases and annotation overlays (not form designer chrome), then opens the browser print dialog.

## 5. Data flow & trust boundaries

Boundary | Trust assumption
--- | ---
Static host → browser | Assets are authentic CCS release (HTTPS + host controls)
User disk → browser | User chose a file they are allowed to open
Browser → user disk | User chooses save location; OS permissions apply
Browser → network | Should not upload document content; only loads same-origin assets (SW is network-only pass-through)

### 5.1 In-memory data

While a document is open, memory may hold: full PDF bytes, high-resolution canvases, overlay HTML (including text that may be PHI), form-field designer state, OCR results, and signature/image bitmaps.

### 5.2 Persistence

Store | Content | Controlled by
--- | --- | ---
User filesystem | Saved PDF / `.ccspdf` | User + OS ACLs + org DLP
Browser download folder | Fallback saves | User profile
Cache Storage (if SW improved) | App assets only (intended) | Service worker design
Server disk | Static app files only | CCS IT

## 6. Offline-first model

Asset | Offline strategy
--- | ---
Application JS/CSS/HTML | Deployed on host; intended for local cache / PWA
PDF.js worker / cmaps | Vendored under `lib/`
OCR engine + `eng.traineddata` | Vendored under `lib/`; embeds for `file://`
QPDF WASM | Vendored `qpdf.js` + `qpdf.wasm` (+ embed for `file://` encrypt)
Fonts | Self-hosted `assets/*.woff2` via `fonts.css`
User PDFs | Always local to the user

Service worker (`sw.js`): network-only pass-through (PWA installability); no Cache API precache or fallback. Offline intent = no CDN hard-dependency + vendored `lib/` / assets on the origin; script/CSS freshness via `config.js` `?v=`.

Deploy modes:

Mode | Notes
--- | ---
HTTPS intranet (primary) | Preferred production; PWA manifest / SW work
Browser zip pack | `scripts/build-release.ps1` → `app/` + launchers; `file://` supported with embeds
Windows WebView2 | `scripts/build-windows.ps1` → `CcsPdfEditor.exe` + `app/` + `tools\`

## 7. Security design notes

Topic | Approach
--- | ---
AuthN/Z | Outside the app (network edge)
XSS | Prefer textContent / sanitizer for restored HTML (`sanitizeHTML`); keep third-party libs updated
Supply chain | Vendor libs in-repo; no runtime npm install on host
Save gesture | Destination acquired before long export work (File System Access activation)
Secrets | None in client application code; no third-party API keys required
CSP | Optional host header; validate wasm requirements for Tesseract/PDF.js before enabling a restrictive policy

## 8. Deployment topologies

### 8.1 Internal OWA (primary)

- Static files on intranet web server.
- Path example: `/pdf-edit/` matching `manifest.json` `start_url`.
- Access: VPN / campus network / reverse-proxy auth as CCS prefers.

### 8.2 Windows WebView2 shell (supported optional)

- Same static UI under `app/`, hosted in a small WinForms + WebView2 EXE.
- Open with / file associations via `tools\Register File Types.bat` (HKCU).
- Desktop icon via `tools\Create Desktop Shortcut.bat`.
- Single-instance named pipe for second-launch file handoff.
- Inventory separately on SYSTEM-PROFILE if production.

### 8.3 Browser offline pack (supported optional)

- `scripts/build-release.ps1` stages `app/` + launchers + docs.
- Edge/Chrome `--app=` launchers; works offline including OCR/encrypt embeds.

### 8.4 What not to do

- Do not put an unauthenticated public internet copy if staff will open PHI without compensating controls.
- Do not add a “upload PDF to server for processing” feature without a full architecture and governance revision (data store, access control, audit, retention).
- Do not load OCR models from public CDNs in production CCS builds.

## 9. File format: `.ccspdf` / embedded project state

CCS project files are valid PDFs that also carry an editable schema + optional audit.

### 9.0 Primary: Catalog dictionary `/CCSProject` (schema 4.0+)

Stored on the document catalog (private key ignored by most readers):

Key | Meaning
--- | ---
`/Type` | `/CCSProject`
`/Ver` | `(4.0)`
`/Fmt` | `/GZipHex` — JSON → gzip → hex in `/Data`
`/Hash` | Integrity string (same salt recipe as legacy trailer)
`/Data` | `<hex…>` of the gzip payload

Implemented in `js/core/project-state.js`. Load tries legacy trailer first (if present), then catalog.

Save Project (`.ccspdf`) and Export PDF (`.pdf`) both embed `/CCSProject` (no trailer by default).

- `.ccspdf`: re-editable package — open in CCS to restore overlays; other apps only show base PDF + AcroForm.
- Export PDF: stamps baked into pixels for any reader; form fields remain fillable; schema is audit/restore of forms.

Trailer dual-write is opt-in only (`dualWriteTrailer: true`).

Resilience note: Other editors may strip unknown catalog keys on “Save as”. Hex-in-catalog is the standard private-data pattern; full legal non-repudiation still needs signed metadata / external e-sign when required.

### 9.1 Legacy trailer (still written + read)

1. Valid PDF body (with or without `/CCSProject`).
2. Trailing ASCII marker: `\n---CCS-SAVE-STATE---`.
3. Integrity hash + delimiter + gzip-compressed JSON schema of overlays and optional rolling audit.

Field | Purpose
--- | ---
`version` | Schema version string (`3.1` trailer-only, `4.0` embedded)
`pdfName` | Suggested file name
`pages[]` | Per-page overlay / form-field layout
`audit[]` | Optional rolling change log (see §9.2)
`storage` | Optional: `catalog-CCSProject` / `catalog+trailer`

Ordinary PDF readers open the PDF portion; edit restore requires this application (or a compatible tool that understands trailer and/or `/CCSProject`).

### 9.2 Rolling project audit (insight log)

High-signal changes while a document is open are recorded in memory (`js/core/project-audit.js`) and written into the trailer on Save Project.

Cap | Value
--- | ---
Rolling entry count | 200 (oldest dropped)
Compressed audit size safety | ~128 KB gzip of `audit[]` (oldest dropped until under)
Schema field | `audit[]` (schema version 3.1+)

Entry shape (approx.): `{ id, t (ISO-8601), action, detail? }`

Typical `action` codes: `FIELD_CREATED`, `FIELD_FILLED`, `FIELD_SIGNED`, `FIELD_IMPORTED`, `TEXT_ADDED`, `IMAGE_ADDED`, `SHAPE_ADDED`, `OVERLAY_DELETED`, `OVERLAY_TRANSFORM`, `PAGE_CHANGE`, `SAVE_PROJECT`, plus coarse `EDIT` fallbacks from history.

Guarantees / non-goals

- Insight for support and contested-project review — not a keystroke log, not multi-user collaboration history.
- Not identity-bound to Windows/Entra user; the app has no login.
- Strings truncated; passwords and bulk PHI values are not retained for their own sake.
- Export PDF also embeds `/CCSProject` (schema includes overlays/forms for re-edit; decorative layers are still flattened into page pixels).
- File-level who/when remains OneDrive / network share versioning where staff save files.

### 9.3 Layered seal + form-signature attestation

Project schema may include a `.seal` object built so hashes do not loop on themselves (`js/core/project-state.js`, `js/core/project-crypto.js`):

Field | Meaning
--- | ---
`originHash` | SHA-256 of bytes when the document was first opened this session
`contentHash` | SHA-256 of PDF bytes before `/CCSProject` is attached
`schemaHash` | SHA-256 of schema JSON without `.seal`
Optional ECDSA | Device-local WebCrypto signature over seal material (not a TSA)

Form signature fields (Editor fill): fixed attestation wording, signed name, ISO time, basic browser/device meta, and digests are stored on the field and included in schema → `schemaHash`. UI shows a compact caption with mid-length hashes; a body-level hover popover exposes full hex with Copy (`js/features/signature.js`).

This is device-local attestation, not certified legal e-sign / identity proofing.

### 9.4 Incident extraction (IT)

To extract project state / audit without opening the editor (supports catalog `/CCSProject` and legacy trailer):

Context | How
--- | ---
Windows install package | Drag `.ccspdf` or Export PDF onto `tools\Extract Project Audit.bat`
Windows install package | `powershell -File tools\scripts\extract-ccspdf-audit.ps1 -Path "…\file.ccspdf" -FullSchema -ExtractPdf`
Source / offline pack | `powershell -File scripts\extract-ccspdf-audit.ps1 -Path "…\file.pdf" -FullSchema -ExtractPdf`
Source / offline pack | Drag onto `scripts\Extract-Ccspdf-Audit.bat`

Prefer trailer when both exist (same as app open path); use `-PreferCatalog` to force catalog.

Outputs (folder `*_extract` next to the source file):

File | Content
--- | ---
`*_summary.txt` | Storage source, integrity, seal hashes, form signatures, chronological audit
`_audit.json` / `_audit.csv` | Machine-readable log
`*_seal.json` | Layered seal when present
`*_signatures.json` | Form signature attestations when present
`*_schema.json` | Full schema JSON (`-FullSchema`)
`*_body.pdf` | Trailer stripped, or source copy for catalog-only (`-ExtractPdf`)

Verifies the integrity hash using the same algorithm as the app (`Uint8Array` comma-join + salt `CCS-SAVE-HASH-CHECK-S4lT!!`). This is decode/gunzip, not QPDF password removal. Exit code 0 = integrity OK; 2 = hash mismatch (artifacts still written for investigation).

Training lab: USER-TRAINING.md Track D · SOP: WINDOWS-INSTALL-SOP.md §8.

(§9.4 continues the extract workflow formerly numbered §9.3 in older docs.)

## 10. Related documentation

Document | Purpose
--- | ---
SYSTEM-PROFILE.md | Owners, host fields
GOVERNANCE.md | Process, residual risk
DEVELOPMENT-STANDARDS.md | How to change code safely
HOW-TO.md | Staff landing how-to (`docs/index.html`)
USER-GUIDE.md | Operator how-to
NOTICE | Third-party stack

Rendered for the browser from repository markdown. Edit the `.md` sources, then re-run `powershell -File docs/marketing/render-docs.ps1` from the repo root.