Skip to content
Back to skills

Pdf Operations

ASecurity

Use when working on PDF manipulation in this repo — merging multiple PDFs into one, splitting a PDF into pages, extracting/reordering pages, rotating pages, or drawing overlays. Points to the existing pdf-lib pipeline instead of reimplementing it.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
ai-agentsgoaws

Works with

  • cli

Security analysis

A100/100

Scanned November 7, 2026

npx -y skills add th3r-dev/th3-skills --skill pdf-operations --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Pdf Operations?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Pdf Operations
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/th3r-dev-pdf-operations/badge)](https://www.skillsdirectory.com/skills/th3r-dev-pdf-operations)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: pdf-operations
description: Use when working on PDF manipulation in this repo — merging multiple PDFs into one, splitting a PDF into pages, extracting/reordering pages, rotating pages, or drawing overlays. Points to the existing pdf-lib pipeline instead of reimplementing it.
---

# PDF operations in pdf-kit

All PDF manipulation here runs client-side with `pdf-lib` + `pdfjs-dist`. There is no
server-side merge/split — the worker (`worker/index.ts`) only proxies `compress` and `ocr`
to an external Stirling-PDF instance. Don't add a server endpoint for merge/split/rotate;
`pdf-lib` in the browser already does it, keeping the "processed locally" guarantee shown
in the UI.

## Merge multiple PDFs into one — already implemented, reuse it

This is not a missing feature. The existing workspace flow already merges any number of
PDFs (and images) into a single output:

1. `src/features/workspace/import-files.ts` (`importPdf` → `isolatePdfPages`) splits every
   dropped PDF into one single-page `PDFDocument` per page, so each page becomes an
   independent `WorkspacePage` with its own `source` blob and `pageIndex: 0`.
2. The pages array holds pages from every dropped file, in the order the user arranged
   them (drag-and-drop reordering, rotate, delete — see `src/features/workspace/model.ts`).
3. `src/features/image-to-pdf/generate.ts` (`generatePdf`) walks that array and, for each
   `kind: 'pdf'` page, `copyPages`s it into one output `PDFDocument`, then `output.save()`
   returns the merged bytes.

So "merge PDF A + B into one file" == drop both files on the workspace and click
**Create PDF**. Covered by the test `merges imported PDF pages in workspace order` in
`tests/pdf-generation.test.ts`.

If you need a *headless* merge (no workspace/UI involved — e.g. a script or a new
non-visual entry point), skip the isolate/thumbnail machinery and go straight to pdf-lib:

```ts
import { PDFDocument } from 'pdf-lib'

async function mergePdfs(files: Uint8Array[]) {
  const output = await PDFDocument.create()
  for (const bytes of files) {
    const source = await PDFDocument.load(bytes)
    const pages = await output.copyPages(source, source.getPageIndices())
    pages.forEach((page) => output.addPage(page))
  }
  return output.save() // Uint8Array
}
```

That's the whole operation — don't reach for a server round-trip or a new dependency.

## Split a PDF into its pages

Use `isolatePdfPages` in `src/features/workspace/import-files.ts` — it already does
"one `PDFDocument` per page" via `copyPages`. Don't rewrite it.

## Rotate / reorder / delete / duplicate pages

Handled by `src/features/workspace/model.ts` (`rotateSelected`, `reorderPages`,
`moveSelected`, `deleteSelected`, `duplicateSelected`) operating on the `WorkspacePage[]`
array before `generatePdf` runs. Rotation is composed at export time in `generate.ts`:
`page.setRotation(degrees((sourceRotation + item.rotation) % 360))` — source PDF rotation
and the user's applied rotation are added together, not overwritten.

## Overlays (text/rectangle/circle annotations)

Defined per-page in `PdfPage.overlays` (`src/types/pdf.ts`), coordinates stored as
0–1 fractions of page size. `overlayPdfRect` in `src/features/pdf-editor/model.ts`
converts a fractional rect to absolute PDF points, accounting for the *source* page's
rotation (not the export rotation) since overlays are flattened before the rotation is
applied. `generate.ts` flattens overlays with `page.drawText` / `drawRectangle` /
`drawEllipse` using `StandardFonts.Helvetica`.

## Gotchas seen in this codebase

- Cache a loaded `PDFDocument` per source `Blob` (see the `Map` in `generatePdf`) —
  don't call `PDFDocument.load` once per page when multiple pages share one source file.
- `output.save()` returns a `Uint8Array`; wrap as `new Blob([bytes as BlobPart], { type: 'application/pdf' })`
  before creating a download URL (TS needs the `as BlobPart` cast here).
- WebP images aren't embeddable by pdf-lib directly — `generate.ts` re-encodes them to
  PNG via `OffscreenCanvas` first (`imageBytes` helper). Reuse that helper for new image
  formats rather than adding a new embed path.
- Text overlay only draws if `isPdfTextSupported` (WinAnsi-encodable) — Helvetica can't
  render arbitrary Unicode.

## Where tests live

`tests/pdf-generation.test.ts` covers `generatePdf` (merge order, overlays, rotation).
`tests/workspace.test.ts` covers page array operations. `e2e/pdf-kit.spec.ts` covers the
full UI flow with Playwright. Add cases there rather than a new test file for a new
operation on the existing pipeline.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.