Limits
A small PDF can ask for a lot of work: a stream that inflates to gigabytes, a page tree thousands of levels deep, forms drawn inside forms. lamina keeps such files within Limits, which you pass when you open one. Every field has a safe default, and you set only the ones you want to change.
- Hard limits stop the call with
limit-exceeded. Itsdata.limitnames the limit, so you can tell the user which one. - Soft limits drop the excess with a warning and carry on.
| Limit (TypeScript) | Kind | Default | Bounds |
|---|---|---|---|
maxNestingDepth |
hard | 256 | arrays and dictionaries inside each other; saved graphics states (q) |
maxStreamDecoded |
hard | 64 MiB | bytes one stream decodes to, per filter stage |
maxDecodedTotal |
hard | 1 GiB | bytes one call (open, one page’s content, one read) decodes, across all streams |
maxFilterChain |
hard | 8 | filters on one stream |
maxResolveDepth |
hard | 32 | indirect objects in flight while resolving a chain of references |
maxPageTreeDepth |
hard | 256 | /Kids levels below the page-tree root (a deeper subtree is skipped with a warning) |
maxTreeDepth |
hard | 64 | name and number tree levels, outline depth |
maxStringLength |
hard | 16 MiB | bytes in one string or name |
maxXrefSections |
soft | 4,096 | /Prev hops in the cross-reference chain (past it, the table is rebuilt by scanning) |
maxObjectNumber |
soft | 8,388,607 | the highest object number kept |
maxWarnings |
soft | 1,000 | warnings kept per document (then one warnings-truncated) |
maxContentOperators |
hard | 10,000,000 | operators run in one content call, forms each time they are drawn |
maxXobjectDepth |
hard | 32 | forms drawn inside forms |
maxImagePixels |
hard (reserved) | 2²⁸ | pixels of one image |
maxReadWindow |
hard | 256 MiB | one read from a range source (Document.openRange) |
strict: truerefuses any file that needs repair, instead of repairing it with warnings.
In TypeScript the names are camelCase. In Rust they’re Limits::new().with_max_stream_decoded(…) and so on. This example opens uploads with tighter decode limits than the defaults:
// Open bytes from someone else: a password, tighter limits than the defaults, and the outcome as// a value rather than an exception (the docs site's encrypted files, limits and errors guides).// It has no Rust golden: it writes nothing; test/examples.test.ts checks what it reports.import { Document, isLaminaError } from "@lamina/pdf";
/** What opening a file gave: its page count and warning codes, or why it didn't open. */export type Opened = | { readonly ok: true; readonly pages: number; readonly warnings: readonly string[] } | { readonly ok: false; readonly code: string; readonly hint: string | undefined };
/** * Opens `bytes` with `password` (empty: none) and limits for an upload endpoint: decoded streams * of at most 16 MiB each and 64 MiB in all. Needs `init()`. */export function openUntrusted(bytes: Uint8Array, password = ""): Opened { try { const doc = Document.open(bytes, { ...(password === "" ? {} : { password }), limits: { maxStreamDecoded: 16 << 20, maxDecodedTotal: 64 << 20 }, }); try { // Repairs and dropped values are warnings: the file opened, but not exactly as written. return { ok: true, pages: doc.pageCount, warnings: doc.warnings.map((w) => w.code) }; } finally { doc.free(); } } catch (e) { // `password-required`, `wrong-password`, `not-pdf`, `limit-exceeded`, …: a coded LaminaError // whose message and hint never contain text from the file (ADR 0026 §4). if (isLaminaError(e)) return { ok: false, code: e.code, hint: e.hint }; throw e; }}