From ad254e15e339121404f8fcccd03437e029786288 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Wed, 28 Jan 2026 12:14:36 +1100 Subject: [PATCH] chore: add benchmark suite with pdf-lib comparisons --- .agents/plans/043-benchmarks.md | 250 ++++++++++++++++++++++++++++++++ .gitignore | 5 +- benchmarks/comparison.bench.ts | 121 ++++++++++++++++ benchmarks/drawing.bench.ts | 100 +++++++++++++ benchmarks/fixtures.ts | 71 +++++++++ benchmarks/forms.bench.ts | 58 ++++++++ benchmarks/loading.bench.ts | 32 ++++ benchmarks/saving.bench.ts | 45 ++++++ bun.lock | 29 +++- package.json | 2 + vitest.config.ts | 4 + 11 files changed, 715 insertions(+), 2 deletions(-) create mode 100644 .agents/plans/043-benchmarks.md create mode 100644 benchmarks/comparison.bench.ts create mode 100644 benchmarks/drawing.bench.ts create mode 100644 benchmarks/fixtures.ts create mode 100644 benchmarks/forms.bench.ts create mode 100644 benchmarks/loading.bench.ts create mode 100644 benchmarks/saving.bench.ts diff --git a/.agents/plans/043-benchmarks.md b/.agents/plans/043-benchmarks.md new file mode 100644 index 0000000..085faee --- /dev/null +++ b/.agents/plans/043-benchmarks.md @@ -0,0 +1,250 @@ +# Plan: Basic Benchmarks + +## Problem Statement + +Users evaluating @libpdf/core need confidence that the library performs reasonably well. Currently there are no benchmarks, making it impossible to: + +1. Demonstrate performance characteristics to potential users +2. Compare against alternatives like pdf-lib +3. Detect performance regressions during development + +## Goals + +- Provide basic benchmarks for common operations +- Compare performance against pdf-lib where APIs overlap +- Give users a rough sense of expected performance +- Keep the benchmark suite minimal and maintainable + +## Non-Goals + +- Comprehensive micro-benchmarks for every operation +- Benchmarking against pdf.js (different focus: rendering) +- Achieving "fastest" status (correctness > speed) +- CI integration (can add later if needed) + +## Scope + +### In Scope + +- Loading PDFs (small, medium, large) +- Saving PDFs (full write, incremental) +- Drawing operations (shapes, text) +- Form filling +- Comparison with pdf-lib for overlapping operations + +### Out of Scope + +- Encryption/decryption benchmarks (security-sensitive) +- Digital signature benchmarks (involves crypto) +- Text extraction benchmarks (can add later) +- Memory usage profiling + +## Technical Approach + +### Framework: Vitest Bench + +Vitest 4.x (already installed) has built-in benchmarking support via `vitest bench`. This provides: + +- Same configuration as existing tests +- Warmup iterations, iteration counts, time limits +- JSON output for potential CI integration +- Familiar API for contributors + +### Directory Structure + +``` +benchmarks/ + loading.bench.ts # PDF.load() performance + saving.bench.ts # PDF.save() performance + drawing.bench.ts # Shape/text drawing + forms.bench.ts # Form field operations + comparison.bench.ts # libpdf vs pdf-lib head-to-head +``` + +### Benchmark Categories + +#### 1. Loading Performance + +| Benchmark | Fixture | Description | +| --------------- | ------------------------------------------ | ------------------------ | +| Load small PDF | `basic/rot0.pdf` (888B) | Minimal parsing overhead | +| Load medium PDF | `basic/sample.pdf` (19KB) | Typical document | +| Load large PDF | `text/variety/us_constitution.pdf` (380KB) | Multi-page document | +| Load with forms | `forms/sample_form.pdf` (116KB) | Form parsing | + +#### 2. Saving Performance + +| Benchmark | Description | +| ----------------------- | ------------------------- | +| Save unmodified | Serialize without changes | +| Save with modifications | After adding content | +| Incremental save | Append-only save | + +#### 3. Drawing Performance + +| Benchmark | Description | +| ---------------------- | ----------------------- | +| Draw rectangles (100x) | Many simple shapes | +| Draw circles (100x) | Curved shapes | +| Draw lines (100x) | Path operations | +| Draw text (100 lines) | Text with standard font | + +#### 4. Form Operations + +| Benchmark | Description | +| ---------------- | ------------------------- | +| Fill text fields | Set values on text fields | +| Get field values | Read form data | +| Flatten form | Convert to static content | + +#### 5. Library Comparison + +Compare pdf-lib and libpdf on operations both support: + +| Operation | Description | +| ---------------- | ------------------------ | +| Load PDF | Parse the same document | +| Create blank PDF | New document creation | +| Add pages | Insert blank pages | +| Draw shapes | Rectangle/circle drawing | +| Save PDF | Serialize to bytes | + +### Fixture Selection + +Use existing fixtures for small/medium, download a large public domain PDF: + +- **Small**: `fixtures/basic/rot0.pdf` (888 bytes, minimal) +- **Medium**: `fixtures/basic/sample.pdf` (19KB, typical) +- **Large**: Download from Internet Archive or similar (~5-10MB, real-world document) +- **Forms**: `fixtures/forms/sample_form.pdf` (116KB, interactive) + +#### Large PDF Strategy + +For "large" benchmarks, we need a multi-MB PDF to test real-world performance. Options: + +1. **Internet Archive** — Public domain books/documents (e.g., government reports, old technical manuals) +2. **NASA Technical Reports** — All public domain, many are 5-20MB +3. **Project Gutenberg** — Public domain books with images + +The benchmark will download the large PDF on first run and cache it in `fixtures/benchmarks/`. This keeps the repo size small while allowing real-world performance testing. + +```typescript +// benchmarks/fixtures.ts +const LARGE_PDF_URL = "https://archive.org/download/..."; // TBD: specific URL +const LARGE_PDF_PATH = "fixtures/benchmarks/large-document.pdf"; + +export async function getLargePdf(): Promise { + if (await Bun.file(LARGE_PDF_PATH).exists()) { + return Bun.file(LARGE_PDF_PATH).bytes(); + } + // Download and cache + const response = await fetch(LARGE_PDF_URL); + const bytes = new Uint8Array(await response.arrayBuffer()); + await Bun.write(LARGE_PDF_PATH, bytes); + return bytes; +} +``` + +The `fixtures/benchmarks/` directory will be gitignored. + +### Example Usage + +```typescript +// benchmarks/loading.bench.ts +import { bench, describe } from "vitest"; +import { PDF } from "../src"; + +const smallPdf = await Bun.file("fixtures/basic/rot0.pdf").bytes(); +const largePdf = await Bun.file("fixtures/text/variety/us_constitution.pdf").bytes(); + +describe("PDF Loading", () => { + bench("load small PDF (888B)", async () => { + await PDF.load(smallPdf); + }); + + bench("load large PDF (380KB)", async () => { + await PDF.load(largePdf); + }); +}); +``` + +```typescript +// benchmarks/comparison.bench.ts +import { bench, describe } from "vitest"; +import { PDF } from "../src"; +import { PDFDocument } from "pdf-lib"; + +const pdfBytes = await Bun.file("fixtures/basic/sample.pdf").bytes(); + +describe("Load PDF", () => { + bench("libpdf", async () => { + await PDF.load(pdfBytes); + }); + + bench("pdf-lib", async () => { + await PDFDocument.load(pdfBytes); + }); +}); +``` + +### Configuration + +Add benchmark configuration to `vitest.config.ts`: + +```typescript +export default defineConfig({ + test: { + // existing config... + }, + bench: { + include: ["benchmarks/**/*.bench.ts"], + reporters: ["default"], + }, +}); +``` + +Add npm script to `package.json`: + +```json +{ + "scripts": { + "bench": "vitest bench" + } +} +``` + +### Dependencies + +pdf-lib will be added as a dev dependency for comparison benchmarks: + +```bash +bun add -d pdf-lib +``` + +## Test Plan + +- Run `bun run bench` successfully +- All benchmarks complete without errors +- Results display in readable format +- Comparison benchmarks show both libraries + +## Open Questions + +1. **Should we include pdf-lib comparisons?** + - Pro: Useful for users evaluating alternatives + - Con: Adds maintenance burden, results vary by machine + - **Decision**: Yes, include them — they're useful for users and we can note results are machine-dependent + +2. **Should we set up CI benchmarking?** + - Can be added later with CodSpeed or similar + - For now, local benchmarks are sufficient + +3. **How many iterations/warmup?** + - Default Vitest settings should be fine + - Can tune if results are noisy + +## Risks + +- **Performance may not be competitive**: That's okay — correctness and features matter more. Benchmarks help identify obvious issues. +- **Results vary by machine**: Document that benchmarks are relative, not absolute. +- **pdf-lib API differences**: Some operations may not be directly comparable; note differences in comments. diff --git a/.gitignore b/.gitignore index 35f378a..84df8ca 100644 --- a/.gitignore +++ b/.gitignore @@ -46,4 +46,7 @@ examples/output/ .agents/review # Debug files -debug/ \ No newline at end of file +debug/ + +# Benchmark fixtures (downloaded at runtime) +fixtures/benchmarks/ \ No newline at end of file diff --git a/benchmarks/comparison.bench.ts b/benchmarks/comparison.bench.ts new file mode 100644 index 0000000..255a57d --- /dev/null +++ b/benchmarks/comparison.bench.ts @@ -0,0 +1,121 @@ +/** + * Library comparison benchmarks. + * + * Compares @libpdf/core against pdf-lib for overlapping operations. + * Results are machine-dependent and should be used for relative comparison only. + */ + +import { PDFDocument } from "pdf-lib"; +import { bench, describe } from "vitest"; + +import { PDF } from "../src"; +import { loadFixture, getHeavyPdf } from "./fixtures"; + +// Pre-load fixture +const pdfBytes = await getHeavyPdf(); + +describe("Load PDF", () => { + bench("libpdf", async () => { + await PDF.load(pdfBytes); + }); + + bench("pdf-lib", async () => { + await PDFDocument.load(pdfBytes); + }); +}); + +describe("Create blank PDF", () => { + bench("libpdf", async () => { + const pdf = PDF.create(); + await pdf.save(); + }); + + bench("pdf-lib", async () => { + const pdf = await PDFDocument.create(); + await pdf.save(); + }); +}); + +describe("Add 10 pages", () => { + bench("libpdf", async () => { + const pdf = PDF.create(); + + for (let i = 0; i < 10; i++) { + pdf.addPage(); + } + + await pdf.save(); + }); + + bench("pdf-lib", async () => { + const pdf = await PDFDocument.create(); + + for (let i = 0; i < 10; i++) { + pdf.addPage(); + } + + await pdf.save(); + }); +}); + +describe("Draw 50 rectangles", () => { + bench("libpdf", async () => { + const pdf = PDF.create(); + const page = pdf.addPage(); + + for (let i = 0; i < 50; i++) { + page.drawRectangle({ + x: 50 + (i % 5) * 100, + y: 50 + Math.floor(i / 5) * 70, + width: 80, + height: 50, + }); + } + + await pdf.save(); + }); + + bench("pdf-lib", async () => { + const pdf = await PDFDocument.create(); + const page = pdf.addPage(); + + for (let i = 0; i < 50; i++) { + page.drawRectangle({ + x: 50 + (i % 5) * 100, + y: 50 + Math.floor(i / 5) * 70, + width: 80, + height: 50, + }); + } + + await pdf.save(); + }); +}); + +describe("Load and save PDF", () => { + bench("libpdf", async () => { + const pdf = await PDF.load(pdfBytes); + await pdf.save(); + }); + + bench("pdf-lib", async () => { + const pdf = await PDFDocument.load(pdfBytes); + await pdf.save(); + }); +}); + +describe("Load, modify, and save PDF", () => { + bench("libpdf", async () => { + const pdf = await PDF.load(pdfBytes); + const page = pdf.getPage(0)!; + page.drawRectangle({ x: 50, y: 50, width: 100, height: 100 }); + await pdf.save(); + }); + + bench("pdf-lib", async () => { + const pdf = await PDFDocument.load(pdfBytes); + const page = pdf.getPage(0); + page.drawRectangle({ x: 50, y: 50, width: 100, height: 100 }); + await pdf.save(); + }); +}); diff --git a/benchmarks/drawing.bench.ts b/benchmarks/drawing.bench.ts new file mode 100644 index 0000000..131e6b1 --- /dev/null +++ b/benchmarks/drawing.bench.ts @@ -0,0 +1,100 @@ +/** + * PDF drawing benchmarks. + * + * Tests performance of drawing operations on a page. + */ + +import { bench } from "vitest"; + +import { PDF } from "../src"; + +bench("draw 100 rectangles", async () => { + const pdf = PDF.create(); + const page = pdf.addPage(); + + for (let i = 0; i < 100; i++) { + page.drawRectangle({ + x: 50 + (i % 10) * 50, + y: 50 + Math.floor(i / 10) * 70, + width: 40, + height: 60, + }); + } + + await pdf.save(); +}); + +bench("draw 100 circles", async () => { + const pdf = PDF.create(); + const page = pdf.addPage(); + + for (let i = 0; i < 100; i++) { + page.drawCircle({ + x: 70 + (i % 10) * 50, + y: 80 + Math.floor(i / 10) * 70, + radius: 20, + }); + } + + await pdf.save(); +}); + +bench("draw 100 lines", async () => { + const pdf = PDF.create(); + const page = pdf.addPage(); + + for (let i = 0; i < 100; i++) { + page.drawLine({ + start: { x: 50, y: 50 + i * 7 }, + end: { x: 550, y: 50 + i * 7 }, + }); + } + + await pdf.save(); +}); + +bench("draw 100 text lines (standard font)", async () => { + const pdf = PDF.create(); + const page = pdf.addPage(); + + for (let i = 0; i < 100; i++) { + page.drawText(`Line ${i + 1}: The quick brown fox jumps over the lazy dog.`, { + x: 50, + y: 750 - i * 7, + font: "Helvetica", + size: 6, + }); + } + + await pdf.save(); +}); + +bench("create 10 pages with mixed content", async () => { + const pdf = PDF.create(); + + for (let p = 0; p < 10; p++) { + const page = pdf.addPage(); + + // Add some rectangles + for (let i = 0; i < 5; i++) { + page.drawRectangle({ + x: 50 + i * 100, + y: 700, + width: 80, + height: 50, + }); + } + + // Add some text + for (let i = 0; i < 10; i++) { + page.drawText(`Page ${p + 1}, Line ${i + 1}`, { + x: 50, + y: 600 - i * 20, + font: "Helvetica", + size: 12, + }); + } + } + + await pdf.save(); +}); diff --git a/benchmarks/fixtures.ts b/benchmarks/fixtures.ts new file mode 100644 index 0000000..b18e21c --- /dev/null +++ b/benchmarks/fixtures.ts @@ -0,0 +1,71 @@ +/** + * Benchmark fixture helpers. + * + * Provides utilities for loading PDF fixtures for benchmarks. + */ + +import { existsSync, mkdirSync, writeFileSync } from "node:fs"; +import { readFile } from "node:fs/promises"; + +// Heavy PDF - downloaded on first run (~10MB) +const HEAVY_PDF_PATH = "fixtures/benchmarks/cc-journalists-guide.pdf"; +const HEAVY_PDF_URL = + "https://creativecommons.org/wp-content/uploads/2023/05/A-Journalists-Guide-to-Creative-Commons-2.0.pdf"; + +// Fallback large PDF - use existing fixture from pdfbox malformed tests (2MB) +const LARGE_PDF_FALLBACK = "fixtures/malformed/pdfbox/PDFBOX-3947.pdf"; + +/** + * Load a fixture file as bytes. + */ +export async function loadFixture(path: string): Promise { + const buffer = await readFile(path); + + return new Uint8Array(buffer); +} + +/** + * Get the heavy PDF fixture (~10MB). + * Downloads on first run, cached locally. + */ +export async function getHeavyPdf(): Promise { + // Return cached file if it exists + if (existsSync(HEAVY_PDF_PATH)) { + return loadFixture(HEAVY_PDF_PATH); + } + + // Download and cache + console.log(`Downloading heavy PDF fixture from ${HEAVY_PDF_URL}...`); + + const response = await fetch(HEAVY_PDF_URL); + + if (!response.ok) { + console.warn(`Failed to download heavy PDF: ${response.status}, using fallback`); + + return loadFixture(LARGE_PDF_FALLBACK); + } + + const bytes = new Uint8Array(await response.arrayBuffer()); + + // Ensure directory exists + mkdirSync("fixtures/benchmarks", { recursive: true }); + writeFileSync(HEAVY_PDF_PATH, bytes); + + console.log( + `Cached heavy PDF to ${HEAVY_PDF_PATH} (${(bytes.length / 1024 / 1024).toFixed(1)}MB)`, + ); + + return bytes; +} + +/** + * Get the large PDF fixture (2MB fallback). + */ +export async function getLargePdf(): Promise { + return loadFixture(LARGE_PDF_FALLBACK); +} + +// Pre-load common fixtures +export const smallPdfPath = "fixtures/basic/rot0.pdf"; +export const mediumPdfPath = "fixtures/basic/sample.pdf"; +export const formPdfPath = "fixtures/forms/sample_form.pdf"; diff --git a/benchmarks/forms.bench.ts b/benchmarks/forms.bench.ts new file mode 100644 index 0000000..7a565c9 --- /dev/null +++ b/benchmarks/forms.bench.ts @@ -0,0 +1,58 @@ +/** + * PDF form benchmarks. + * + * Tests performance of form operations. + */ + +import { bench } from "vitest"; + +import { PDF } from "../src"; +import { formPdfPath, loadFixture } from "./fixtures"; + +// Pre-load fixture +const formPdf = await loadFixture(formPdfPath); + +bench("get form fields", async () => { + const pdf = await PDF.load(formPdf); + const form = pdf.getForm(); + form?.getFields(); +}); + +bench("fill text fields", async () => { + const pdf = await PDF.load(formPdf); + const form = pdf.getForm(); + + if (form) { + const fields = form.getFields(); + + for (const field of fields) { + if (field.type === "text") { + form.getTextField(field.name)?.setValue("Test Value"); + } + } + } + + await pdf.save(); +}); + +bench("read field values", async () => { + const pdf = await PDF.load(formPdf); + const form = pdf.getForm(); + + if (form) { + const fields = form.getFields(); + + for (const field of fields) { + if (field.type === "text") { + form.getTextField(field.name)?.getValue(); + } + } + } +}); + +bench("flatten form", async () => { + const pdf = await PDF.load(formPdf); + const form = pdf.getForm(); + form?.flatten(); + await pdf.save(); +}); diff --git a/benchmarks/loading.bench.ts b/benchmarks/loading.bench.ts new file mode 100644 index 0000000..35d7343 --- /dev/null +++ b/benchmarks/loading.bench.ts @@ -0,0 +1,32 @@ +/** + * PDF loading benchmarks. + * + * Tests PDF.load() performance with various file sizes. + */ + +import { bench } from "vitest"; + +import { PDF } from "../src"; +import { formPdfPath, getHeavyPdf, loadFixture, mediumPdfPath, smallPdfPath } from "./fixtures"; + +// Pre-load fixtures outside benchmark to avoid I/O in measurements +const smallPdf = await loadFixture(smallPdfPath); +const mediumPdf = await loadFixture(mediumPdfPath); +const formPdf = await loadFixture(formPdfPath); +const heavyPdf = await getHeavyPdf(); + +bench("load small PDF (888B)", async () => { + await PDF.load(smallPdf); +}); + +bench("load medium PDF (19KB)", async () => { + await PDF.load(mediumPdf); +}); + +bench("load form PDF (116KB)", async () => { + await PDF.load(formPdf); +}); + +bench(`load heavy PDF (${(heavyPdf.length / 1024 / 1024).toFixed(1)}MB)`, async () => { + await PDF.load(heavyPdf); +}); diff --git a/benchmarks/saving.bench.ts b/benchmarks/saving.bench.ts new file mode 100644 index 0000000..da9ba02 --- /dev/null +++ b/benchmarks/saving.bench.ts @@ -0,0 +1,45 @@ +/** + * PDF saving benchmarks. + * + * Tests PDF.save() performance with different scenarios. + */ + +import { bench } from "vitest"; + +import { PDF } from "../src"; +import { getHeavyPdf, loadFixture, mediumPdfPath } from "./fixtures"; + +// Pre-load fixtures +const mediumPdf = await loadFixture(mediumPdfPath); +const heavyPdf = await getHeavyPdf(); + +bench("save unmodified (19KB)", async () => { + const pdf = await PDF.load(mediumPdf); + await pdf.save(); +}); + +bench("save with modifications (19KB)", async () => { + const pdf = await PDF.load(mediumPdf); + const page = pdf.getPage(0)!; + page.drawRectangle({ x: 50, y: 50, width: 100, height: 100 }); + await pdf.save(); +}); + +bench("incremental save (19KB)", async () => { + const pdf = await PDF.load(mediumPdf); + const page = pdf.getPage(0)!; + page.drawRectangle({ x: 50, y: 50, width: 100, height: 100 }); + await pdf.save({ incremental: true }); +}); + +bench(`save heavy PDF (${(heavyPdf.length / 1024 / 1024).toFixed(1)}MB)`, async () => { + const pdf = await PDF.load(heavyPdf); + await pdf.save(); +}); + +bench(`incremental save heavy PDF (${(heavyPdf.length / 1024 / 1024).toFixed(1)}MB)`, async () => { + const pdf = await PDF.load(heavyPdf); + const page = pdf.getPage(0)!; + page.drawRectangle({ x: 50, y: 50, width: 100, height: 100 }); + await pdf.save({ incremental: true }); +}); diff --git a/bun.lock b/bun.lock index 06743bf..2d34aa2 100644 --- a/bun.lock +++ b/bun.lock @@ -22,6 +22,7 @@ "oxfmt": "^0.24.0", "oxlint": "^1.39.0", "oxlint-tsgolint": "^0.11.1", + "pdf-lib": "^1.17.1", "tsdown": "^0.18.4", "typescript": "^5", "vitest": "^4.0.16", @@ -179,6 +180,10 @@ "@oxlint/win32-x64": ["@oxlint/win32-x64@1.39.0", "", { "os": "win32", "cpu": "x64" }, "sha512-sbi25lfj74hH+6qQtb7s1wEvd1j8OQbTaH8v3xTcDjrwm579Cyh0HBv1YSZ2+gsnVwfVDiCTL1D0JsNqYXszVA=="], + "@pdf-lib/standard-fonts": ["@pdf-lib/standard-fonts@1.0.0", "", { "dependencies": { "pako": "^1.0.6" } }, "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA=="], + + "@pdf-lib/upng": ["@pdf-lib/upng@1.0.1", "", { "dependencies": { "pako": "^1.0.10" } }, "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], @@ -537,6 +542,8 @@ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "pdf-lib": ["pdf-lib@1.17.1", "", { "dependencies": { "@pdf-lib/standard-fonts": "^1.0.0", "@pdf-lib/upng": "^1.0.1", "pako": "^1.0.11", "tslib": "^1.11.1" } }, "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], @@ -635,7 +642,7 @@ "tsdown": ["tsdown@0.18.4", "", { "dependencies": { "ansis": "^4.2.0", "cac": "^6.7.14", "defu": "^6.1.4", "empathic": "^2.0.0", "hookable": "^6.0.1", "import-without-cache": "^0.2.5", "obug": "^2.1.1", "picomatch": "^4.0.3", "rolldown": "1.0.0-beta.57", "rolldown-plugin-dts": "^0.20.0", "semver": "^7.7.3", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tree-kill": "^1.2.2", "unconfig-core": "^7.4.2", "unrun": "^0.2.21" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "@vitejs/devtools": "*", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "@vitejs/devtools", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-J/tRS6hsZTkvqmt4+xdELUCkQYDuUCXgBv0fw3ImV09WPGbEKfsPD65E+WUjSu3E7Z6tji9XZ1iWs8rbGqB/ZA=="], - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -671,10 +678,24 @@ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "@emnapi/core/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + "@pdf-lib/standard-fonts/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + + "@pdf-lib/upng/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + + "@tybys/wasm-util/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "asn1js/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -685,8 +706,14 @@ "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "pdf-lib/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], + "pkijs/@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="], + "pkijs/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "pvtsutils/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "string-width-cjs/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], diff --git a/package.json b/package.json index c3e766d..a5f1e85 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "./package.json": "./package.json" }, "scripts": { + "bench": "vitest bench", "build": "tsdown", "docs:build": "bun run --cwd apps/docs build", "docs:dev": "bun run --cwd apps/docs dev", @@ -79,6 +80,7 @@ "oxfmt": "^0.24.0", "oxlint": "^1.39.0", "oxlint-tsgolint": "^0.11.1", + "pdf-lib": "^1.17.1", "tsdown": "^0.18.4", "typescript": "^5", "vitest": "^4.0.16" diff --git a/vitest.config.ts b/vitest.config.ts index e4e3a4d..d3839e0 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -11,5 +11,9 @@ export default defineConfig({ exclude: ["src/**/*.test.ts", "src/test-utils.ts"], reporter: ["text", "html"], }, + benchmark: { + include: ["benchmarks/**/*.bench.ts"], + exclude: ["**/node_modules/**", "**/checkouts/**"], + }, }, });