initial commit
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
# Font broker contract
|
||||
|
||||
This implementation supplements design 03 §3.2 and 05. The selected font is
|
||||
transferred as an immutable, bounded byte snapshot through the existing IPC
|
||||
connection. No filesystem path or OS font handle crosses the boundary. The
|
||||
main process selects fonts through Fontconfig on Linux and GDI on Windows;
|
||||
PDFium and SFNT/TTC table parsing remain inside the sandboxed PDF worker.
|
||||
See [the API investigation](font-broker-plan.md) for pinned upstream evidence.
|
||||
Windows implementation and native Windows execution are separate claims;
|
||||
consult [the validation record](VALIDATION.md) for actual execution coverage.
|
||||
|
||||
## Request envelope and identity
|
||||
|
||||
Only a `WorkerProcess` with an explicitly registered font handler accepts
|
||||
`font.map`, `font.read`, or `font.close` requests. Production registers the
|
||||
handler only for PDF workers. Requests use the current parent operation's
|
||||
`sessionId`, `generation`, and `requestId`. The payload contains a positive,
|
||||
monotonically increasing `fontRequestId`; the result or error repeats it.
|
||||
Only one font request may be outstanding. These are control frames, with no
|
||||
streaming `more` field and the existing 1 MiB encoded control-frame limit.
|
||||
Unknown fields, wrong types, stale identities and unissued handles are rejected.
|
||||
|
||||
The main process routes font requests before ordinary PDF responses. It never
|
||||
finishes or replaces the active PDF operation or extends its deadline as a
|
||||
side effect. The callback API removes `fontRequestId` from request/result maps;
|
||||
the transport layer owns that field. Broker completion returns either the
|
||||
inner result map or `{error: {code, message}}`. Cancellation discards pending
|
||||
completion and invalidates issued handles. A parent timeout error can end a
|
||||
pending font request without interpreting it as successful font acquisition.
|
||||
|
||||
| Operation | Additional payload fields | Additional result fields |
|
||||
| --- | --- | --- |
|
||||
| `font.map` | `faceLocal` bytes, `weight` integer, `italic` boolean, `charset` integer, `pitchFamily` integer | `found` boolean. If true: `fontId`, `actualFaceLocal`, `charset`, `size`, `sha256`, `substituted`, and exactly one of `faceIndex` / `faceOffset` |
|
||||
| `font.read` | `fontId`, `offset`, `length` | Same `fontId` / `offset`, and exactly `length` bytes in `data` |
|
||||
| `font.close` | `fontId` | `released: true` |
|
||||
|
||||
Names use the system local encoding as required by the pinned PDFium public
|
||||
API, contain no NUL, and are at most 256 bytes. Requested names may be empty;
|
||||
selected names must not be empty. Weight is 0–1000. Charset is one of
|
||||
0, 1, 2, 128, 129, 134, 136, 161, 163, 177, 178, 204, 222, 238.
|
||||
Pitch/family accepts family values 0x00–0x50 in steps of 0x10 with pitch 0–2.
|
||||
Names are typed family values, never fontconfig pattern expressions or paths.
|
||||
The font ID is 32 lowercase hexadecimal characters, scoped to one service.
|
||||
SHA-256 is 32 raw bytes. Face index is 0–65535; face offset is inside the snapshot.
|
||||
|
||||
## Limits and errors
|
||||
|
||||
| Resource | Bound |
|
||||
| --- | --- |
|
||||
| Snapshot | 64 MiB per font/collection |
|
||||
| Read | 1–65536 bytes; checked against the issued snapshot's actual size |
|
||||
| Open font handles | 16 per session |
|
||||
| Different selection requests | 256 per session, including misses |
|
||||
| Total transferred font bytes | 512 MiB per session |
|
||||
| Worker snapshot cache | 128 MiB; live callback references cannot be evicted |
|
||||
| Broker snapshots | 256 MiB across active, staging and revoked services; global LRU of unreferenced snapshots |
|
||||
| OS font threads | Two per application process; excess services fail explicitly |
|
||||
| Broker task queue | Eight entries per service (production wire permits only one pending request) |
|
||||
| One reply / complete font fetch | 5 seconds / 15 seconds, within the unchanged parent deadline |
|
||||
|
||||
`E_FONT_LIMIT` is a new registered error for these resource bounds. It is not
|
||||
reported as a missing font. `E_FONT_FAILED` is a new registered error for OS
|
||||
font acquisition, unsupported/invalid font data. A snapshot hash or wire mismatch uses the protocol-failure path.
|
||||
Existing `E_WORKER_TIMEOUT` and `E_WORKER_CRASH` represent transport/deadline
|
||||
failure. All four font error maps contain `fontRequestId`, `code`, and a
|
||||
nonempty message of at most 4096 characters. Malformed reverse RPC uses the
|
||||
existing worker protocol-failure path. A genuine absent match is the success
|
||||
result `found: false`; PDFium may use its built-in fallback and the adapter
|
||||
records a warning. A communication/resource error must fail the PDF operation.
|
||||
Fatal font errors remain sticky for the adapter lifetime, because PDFium may
|
||||
have cached an internal substitute during the failed call. Retry requires a
|
||||
new worker/document; clearing only the error would hide that failed selection.
|
||||
|
||||
## Execution and font data
|
||||
|
||||
Normal PDF operations run from a queued invocation after the transport's
|
||||
receive stack unwinds. Font responses are routed during the bounded nested
|
||||
wait without reentering PDFium or starting another PDF operation. Main-thread
|
||||
GUI processing never waits for Fontconfig/GDI. Revocation prevents new work
|
||||
and drops late callbacks, while an already running OS call retains its bounded
|
||||
thread and snapshot charges until cleanup. Snapshot eviction considers unused
|
||||
entries across services, so an old document cannot monopolize reclaimable
|
||||
cache space while a new document opens. In-process test/low-memory constructor
|
||||
arguments may lower the cache bound; they cannot raise the production cap.
|
||||
|
||||
The worker always installs `FPDF_SYSFONTINFO` version 2. A missing provider
|
||||
does not reactivate PDFium's default filesystem font mapper. The production
|
||||
Linux worker receives no font-directory grants. Downloaded chunks must match
|
||||
declared size and SHA-256 before the immutable snapshot reaches PDFium.
|
||||
|
||||
For a TTC, `ttcf` returns the complete collection and table zero returns the
|
||||
bytes from the selected face offset to the end. A normal table tag resolves
|
||||
through that face's checked directory. A short non-null caller buffer is
|
||||
zero-initialized and filled with the available prefix; the required size is
|
||||
returned. This also supports the pinned PDFium mapper's 1024-byte checksum
|
||||
read. Font parsing rejects out-of-range tables, invalid collection indices and
|
||||
duplicate table tags. Static TTF/OpenType/collections are the initial scope;
|
||||
named variable instances are not silently mapped to face zero. A collection
|
||||
may contain at most 256 faces and each face at most 4096 table entries. These
|
||||
parser bounds are separate from the wire field range for an OS face index.
|
||||
|
||||
Font substitution depends on installed candidates. Fixed aliases and family
|
||||
priorities are maintained in `src/broker/font_backend.cpp`; selected family,
|
||||
charset, face metadata and substitution status describe the actual result.
|
||||
The synthetic unembedded Japanese H/V corpus exercises native CID writing
|
||||
modes. Its independent-renderer comparison is additional evidence, not a
|
||||
human-approved golden image or a claim about every PDF CMap.
|
||||
Reference in New Issue
Block a user