108 lines
6.8 KiB
Markdown
108 lines
6.8 KiB
Markdown
# 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.
|