# 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](SYSTEM-PROFILE.md) and [GOVERNANCE.md](GOVERNANCE.md). For deploy overview see [README.md](../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](../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](USER-TRAINING.md) Track D · SOP: [WINDOWS-INSTALL-SOP.md](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](SYSTEM-PROFILE.md) | Owners, host fields |
| [GOVERNANCE.md](GOVERNANCE.md) | Process, residual risk |
| [DEVELOPMENT-STANDARDS.md](DEVELOPMENT-STANDARDS.md) | How to change code safely |
| [HOW-TO.md](HOW-TO.md) | Staff landing how-to (`docs/index.html`) |
| [USER-GUIDE.md](USER-GUIDE.md) | Operator how-to |
| [NOTICE](../NOTICE) | Third-party stack |
