diff --git a/README.md b/README.md index 1f22a04..40de3fb 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# tspdf +# pdfkernel Fast, dependency-light PDF forms and text for TypeScript. No WASM. -[![npm](https://img.shields.io/npm/v/@folsom/tspdf.svg)](https://www.npmjs.com/package/@folsom/tspdf) -[![bundle size](https://img.shields.io/bundlephobia/minzip/@folsom/tspdf)](https://bundlephobia.com/package/@folsom/tspdf) -[![license](https://img.shields.io/npm/l/@folsom/tspdf.svg)](./LICENSE) +[![npm](https://img.shields.io/npm/v/pdfkernel.svg)](https://www.npmjs.com/package/pdfkernel) +[![bundle size](https://img.shields.io/bundlephobia/minzip/pdfkernel)](https://bundlephobia.com/package/pdfkernel) +[![license](https://img.shields.io/npm/l/pdfkernel.svg)](./LICENSE) - Read and fill AcroForm fields, including checkboxes, radios and dropdowns - Generate field appearances (`/DA`, auto-size, alignment, comb, multiline) @@ -43,7 +43,7 @@ Runs on `@noble/hashes`, `@noble/ciphers` and `fflate`. Nothing else. ## Installation ```bash -bun add @folsom/tspdf # or npm / pnpm / yarn +bun add pdfkernel # or npm / pnpm / yarn ``` ## Usage @@ -51,7 +51,7 @@ bun add @folsom/tspdf # or npm / pnpm / yarn ### Reading a document ```ts -import { PdfDocument } from "@folsom/tspdf"; +import { PdfDocument } from "pdfkernel"; const doc = PdfDocument.open(bytes); @@ -139,7 +139,7 @@ Appearances are drawn into the page content and the form is removed. Widgets tha ### Assembling documents ```ts -import { createDocument, addPage, copyPages, mergeDocuments, splitDocument } from "@folsom/tspdf"; +import { createDocument, addPage, copyPages, mergeDocuments, splitDocument } from "pdfkernel"; const merged = mergeDocuments([a, b]); const [chapter1, chapter2] = splitDocument(doc, [[0, 4], [5, 9]]); // inclusive ranges @@ -175,7 +175,7 @@ const doc = await PdfDocument.load(bytes, { pages: [0] }); ### Permissions -Documents can restrict filling, modification and copying via `/P`. tspdf enforces those flags, so +Documents can restrict filling, modification and copying via `/P`. pdfkernel enforces those flags, so the restricted operation throws unless you opt out: ```ts @@ -204,10 +204,10 @@ so a bomb degrades instead of taking the process down. ### Escaping to the object model -The facade covers the common cases. For anything else, `@folsom/tspdf/lowlevel` exposes the raw layer: +The facade covers the common cases. For anything else, `pdfkernel/lowlevel` exposes the raw layer: ```ts -import { N, isDict, isStream } from "@folsom/tspdf/lowlevel"; +import { N, isDict, isStream } from "pdfkernel/lowlevel"; const cat = doc.dict(doc.trailer.get(N.Root)); const custom = doc.dict(cat?.get(N.AcroForm)); @@ -223,7 +223,7 @@ Every facade method delegates to a free function that is also exported. `doc.pag Reproduce with `bun run bench`. Measured on f990.pdf, 778 KB with 1075 fields. -| | tspdf | pdfium | mupdf | pdf-lib | +| | pdfkernel | pdfium | mupdf | pdf-lib | | --- | --- | --- | --- | --- | | open + read all fields | **7.3 ms** | 34.4 ms | 20.8 ms | 221.2 ms | | fill 20 fields and save | **7.0 ms** | n/a | 43.8 ms | 549.7 ms | @@ -232,13 +232,13 @@ Reproduce with `bun run bench`. Measured on f990.pdf, 778 KB with 1075 fields. | output size | **790 KB** | n/a | 1842 KB | 1492 KB | Two things are not like-for-like: the mupdf and pdf-lib read columns fetch field names only, and -pdfium builds a richer text page than tspdf does, sorting into reading order and deriving boxes from +pdfium builds a richer text page than pdfkernel does, sorting into reading order and deriving boxes from glyph outlines. Holding a document open is where the difference compounds. mupdf's `fill()` costs the same whether you set one value or two hundred, because each call reopens and re-serialises the file: -| values set | mupdf | tspdf | appended | +| values set | mupdf | pdfkernel | appended | | --- | --- | --- | --- | | 1 | 6.50 ms | **0.060 ms** | 0.5 KB | | 10 | 6.20 ms | **0.116 ms** | 4.3 KB | @@ -249,7 +249,7 @@ number that matters for a CLI that handles one file and exits. ## Limitations -tspdf does not render. There is no rasteriser and no plan for one; if you need pixels, use pdfium or +pdfkernel does not render. There is no rasteriser and no plan for one; if you need pixels, use pdfium or mupdf. It also does not: - Decode image codecs. DCT, JPX, JBIG2, CCITTFax and Brotli streams come back as raw bytes with an @@ -271,7 +271,7 @@ mupdf. It also does not: fixture files and no oracle. `test/fixtures.ts` builds them; add a case there rather than committing a binary. -The deeper gates check tspdf against pdfium and mupdf as oracles rather than against its own +The deeper gates check pdfkernel against pdfium and mupdf as oracles rather than against its own expectations, and need a corpus under `../data`. Field reads are compared record by record (9418 widgets across 46 government and corporate forms, no mismatches); written files are handed back to both engines to read; generated appearances are @@ -279,7 +279,7 @@ rasterised by mupdf and compared as ink coverage. Text extraction agrees with pd characters. Broader coverage comes from the pdf.js and pdfium regression corpora, 932 files that each exist -because they broke a parser. tspdf opens 908 of them; pdfium opens 896. Between them they exercise +because they broke a parser. pdfkernel opens 908 of them; pdfium opens 896. Between them they exercise every security handler revision from R2 to R6, classic and stream xrefs, object streams and the recovery path. diff --git a/bench/fill.ts b/bench/fill.ts index 3ccf931..3e7c582 100644 --- a/bench/fill.ts +++ b/bench/fill.ts @@ -1,6 +1,6 @@ /** Fill benchmark — the headline claim. * - * 200 synthesized widgets on the bill-of-lading template, filled two ways by tspdf: + * 200 synthesized widgets on the bill-of-lading template, filled two ways by pdfkernel: * * "stateless" — open + set + save per call, i.e. apples-to-apples with a one-shot filler * "live handle"— open once, then time only set + save, i.e. what the API shape unlocks diff --git a/bun.lock b/bun.lock index c9a701f..fdaae1c 100644 --- a/bun.lock +++ b/bun.lock @@ -3,7 +3,7 @@ "configVersion": 0, "workspaces": { "": { - "name": "protopdf", + "name": "pdfkernel", "dependencies": { "@noble/ciphers": "^2.2.0", "@noble/hashes": "^2.2.0", diff --git a/package.json b/package.json index 7b61c1d..671e372 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@folsom/tspdf", + "name": "pdfkernel", "version": "0.1.0", "description": "A pure-TypeScript PDF core: lazy parsing, append-only writing, AcroForm read/fill/flatten/synthesize, and text extraction. No WASM.", "type": "module", @@ -7,11 +7,11 @@ "author": "Folsom Intelligence", "repository": { "type": "git", - "url": "git+https://github.com/folsomintel/tspdf.git" + "url": "git+https://github.com/folsomintel/pdfkernel.git" }, - "homepage": "https://github.com/folsomintel/tspdf#readme", + "homepage": "https://github.com/folsomintel/pdfkernel#readme", "bugs": { - "url": "https://github.com/folsomintel/tspdf/issues" + "url": "https://github.com/folsomintel/pdfkernel/issues" }, "sideEffects": false, "publishConfig": { diff --git a/src/index.ts b/src/index.ts index ef628e6..6ebf808 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -/** tspdf — public API. +/** pdfkernel — public API. * * The shape is an object model: `PdfDocument` → `Page`, and `Form` → `Field`. Nearly everything * here is also available as a free function taking `(doc, …)`, because the facades are thin @@ -7,9 +7,9 @@ * One build for every platform. Nothing in this graph imports `node:` anything, so a bundler has * nothing to polyfill and there is no second target to keep in sync. * - * The raw object model (`PdfDict`, the type guards, `N`) lives in `@folsom/tspdf/lowlevel`; its + * The raw object model (`PdfDict`, the type guards, `N`) lives in `pdfkernel/lowlevel`; its * *types* are re-exported below so signatures stay nameable without that import. Anything reached - * by deep-importing `@folsom/tspdf/src/…` is internal and may change without notice. + * by deep-importing `pdfkernel/src/…` is internal and may change without notice. * * `PdfDocument.open(bytes)` — synchronous, every platform * `PdfDocument.load(bytes)` — asynchronous, pre-warms streams up front @@ -22,7 +22,7 @@ export { Form, type FieldEntry } from "./form/form.ts"; export { Field } from "./form/field.ts"; /** Value types, for naming what the facade hands back. The runtime constructors and type guards - * are in `@folsom/tspdf/lowlevel`. */ + * are in `pdfkernel/lowlevel`. */ export type { PdfValue, PdfDict, PdfStream, PdfString, Ref } from "./core/objects.ts"; export type { Name } from "./core/names.ts"; diff --git a/src/low-level.ts b/src/low-level.ts index 3968866..ad16538 100644 --- a/src/low-level.ts +++ b/src/low-level.ts @@ -1,4 +1,4 @@ -/** `@folsom/tspdf/lowlevel` — the raw object model and the helpers that speak it. +/** `pdfkernel/lowlevel` — the raw object model and the helpers that speak it. * * Nothing here is needed to read, fill, flatten, assemble or extract text; the facade on * `PdfDocument`, `Page`, `Form` and `Field` covers all of that without a `PdfDict` in sight. This diff --git a/test/api.ts b/test/api.ts index 1110f4e..272a04a 100644 --- a/test/api.ts +++ b/test/api.ts @@ -2,7 +2,7 @@ * * Everything that writes is verified by pdfium and mupdf reading the result back. Our own reader * agreeing with our own writer proves only that they share a bug — that lesson has been paid for - * twice already in this project, so nothing here is checked with tspdf alone. + * twice already in this project, so nothing here is checked with pdfkernel alone. */ import { readFileSync } from "node:fs"; import * as mupdf from "mupdf"; @@ -93,7 +93,7 @@ console.log("metadata"); const doc = PdfDocument.open(bytesOf(F1040)); setInfo(doc, { title: "Round Trip", - author: "tspdf", + author: "pdfkernel", subject: "テスト", keywords: "a,b", creationDate: now, @@ -113,7 +113,7 @@ console.log("metadata"); const mupdfAuthor = md.getMetaData("info:Author"); md.destroy(); check(mupdfTitle === "Round Trip", `mupdf reads the title (${mupdfTitle})`); - check(mupdfAuthor === "tspdf", `mupdf reads the author (${mupdfAuthor})`); + check(mupdfAuthor === "pdfkernel", `mupdf reads the author (${mupdfAuthor})`); } // -------------------------------------------------------------------------- 2. page geometry diff --git a/test/appearance.ts b/test/appearance.ts index 5c6a42f..a0b3258 100644 --- a/test/appearance.ts +++ b/test/appearance.ts @@ -2,11 +2,11 @@ * * Rendered with mupdf, not pdfium: FPDF_RenderPageBitmap does not draw widget annotations without * an FPDF_FFLDraw pass. mupdf draws appearance streams directly, so it is both the right renderer - * here and an independent check — it is rasterising content tspdf wrote, using its own parser. + * here and an independent check — it is rasterising content pdfkernel wrote, using its own parser. * * Two variants per file, both rendered identically and compared to the untouched original: - * AP tspdf fill with appearance generation - * noAP tspdf fill with /NeedAppearances only, leaving mupdf to generate the appearances + * AP pdfkernel fill with appearance generation + * noAP pdfkernel fill with /NeedAppearances only, leaving mupdf to generate the appearances * * noAP is the reference: identical /V values, appearances drawn by mupdf itself. Comparing the two * is a true like-for-like — our appearance stream against mupdf's, for the same text in the same diff --git a/test/pdfium.ts b/test/pdfium.ts index 065334e..7e70c7c 100644 --- a/test/pdfium.ts +++ b/test/pdfium.ts @@ -28,7 +28,7 @@ export async function pdfiumModule(): Promise { return _module; } -/** fpdf_formfill.h field types → the PDF `/FT` names tspdf reports. */ +/** fpdf_formfill.h field types → the PDF `/FT` names pdfkernel reports. */ const FT_MAP: Record = { 0: null, 1: "/Btn", diff --git a/test/spec.ts b/test/spec.ts index 2c8652b..26e0f08 100644 --- a/test/spec.ts +++ b/test/spec.ts @@ -1,6 +1,6 @@ /** Behaviour checks against hand-built PDFs. No corpus, no pdfium, no mupdf. * - * The differential gates prove tspdf *agrees* with the engines that own the format, which is the + * The differential gates prove pdfkernel *agrees* with the engines that own the format, which is the * strongest evidence available but needs ~1 GB of fixtures and two WASM baselines. Without those a * clone previously ran two tests. Everything here runs on `bun test/spec.ts` in a bare checkout. *