peasy-pdf 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peasy Tools
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,260 @@
1
+ # peasy-pdf
2
+
3
+ [![npm](https://img.shields.io/npm/v/peasy-pdf)](https://www.npmjs.com/package/peasy-pdf)
4
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue)](https://www.typescriptlang.org/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ PDF manipulation library for Node.js -- merge, split, rotate, reorder, extract pages, and manage metadata. TypeScript-first with full type safety, powered by [pdf-lib](https://pdf-lib.js.org/) for pure-JavaScript PDF processing with zero native dependencies.
8
+
9
+ Built from [Peasy PDF](https://peasypdf.com), the developer tools platform for PDF processing, conversion, and optimization.
10
+
11
+ > **Try the interactive tools at [peasypdf.com](https://peasypdf.com)** -- [Merge PDF](https://peasypdf.com/tools/merge-pdf/), [Split PDF](https://peasypdf.com/tools/split-pdf/), [Rotate PDF](https://peasypdf.com/tools/rotate-pdf/)
12
+
13
+ ## Table of Contents
14
+
15
+ - [Install](#install)
16
+ - [Quick Start](#quick-start)
17
+ - [What You Can Do](#what-you-can-do)
18
+ - [Merge and Split](#merge-and-split)
19
+ - [Rotate and Reorder](#rotate-and-reorder)
20
+ - [Page Management](#page-management)
21
+ - [Metadata](#metadata)
22
+ - [TypeScript Types](#typescript-types)
23
+ - [API Reference](#api-reference)
24
+ - [Also Available for Python](#also-available-for-python)
25
+ - [Peasy Developer Tools](#peasy-developer-tools)
26
+ - [License](#license)
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ npm install peasy-pdf
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ```typescript
37
+ import { merge, split, rotate, info, setMetadata } from "peasy-pdf";
38
+ import { readFileSync, writeFileSync } from "fs";
39
+
40
+ // Merge two PDFs into one
41
+ const pdf1 = readFileSync("report-q1.pdf");
42
+ const pdf2 = readFileSync("report-q2.pdf");
43
+ const merged = await merge(pdf1, pdf2);
44
+ writeFileSync("annual-report.pdf", merged);
45
+
46
+ // Split a PDF by page ranges
47
+ const parts = await split(readFileSync("document.pdf"), "1-3,4-6");
48
+ parts.forEach((part, i) => writeFileSync(`part-${i + 1}.pdf`, part));
49
+
50
+ // Rotate all pages 90 degrees clockwise
51
+ const rotated = await rotate(readFileSync("landscape.pdf"), 90);
52
+
53
+ // Get PDF information
54
+ const pdfInfo = await info(readFileSync("document.pdf"));
55
+ console.log(`Pages: ${pdfInfo.pages}, Title: ${pdfInfo.title}`);
56
+
57
+ // Set document metadata
58
+ const tagged = await setMetadata(readFileSync("draft.pdf"), {
59
+ title: "Annual Report 2026",
60
+ author: "Finance Team",
61
+ });
62
+ ```
63
+
64
+ ## What You Can Do
65
+
66
+ ### Merge and Split
67
+
68
+ Combine multiple PDF documents into a single file, or split a large PDF into smaller parts by page ranges. The merge operation preserves all page content, annotations, and formatting from the source documents.
69
+
70
+ | Operation | Function | Description |
71
+ |-----------|----------|-------------|
72
+ | Merge | `merge(...sources)` | Combine multiple PDFs into one document |
73
+ | Split | `split(source, ranges)` | Split a PDF by page ranges (e.g. "1-3,4-6") |
74
+
75
+ ```typescript
76
+ import { merge, split } from "peasy-pdf";
77
+
78
+ // Merge three reports into one combined document
79
+ const combined = await merge(q1Report, q2Report, q3Report);
80
+
81
+ // Split a 10-page document into three parts
82
+ const parts = await split(document, "1-3,4-7,8-10");
83
+ console.log(parts.length); // 3 separate PDF byte arrays
84
+ ```
85
+
86
+ Learn more: [Merge PDF Tool](https://peasypdf.com/tools/merge-pdf/) -- [Split PDF Tool](https://peasypdf.com/tools/split-pdf/)
87
+
88
+ ### Rotate and Reorder
89
+
90
+ Rotate pages by 90, 180, or 270 degrees, reverse page order, or extract odd/even pages. Rotation can target all pages or a specific subset using page specifications.
91
+
92
+ | Operation | Function | Description |
93
+ |-----------|----------|-------------|
94
+ | Rotate | `rotate(source, angle, pages?)` | Rotate pages by 90/180/270 degrees |
95
+ | Reverse | `reverse(source)` | Reverse the page order |
96
+ | Odd/Even | `oddEven(source, mode)` | Extract odd or even pages |
97
+
98
+ ```typescript
99
+ import { rotate, reverse, oddEven } from "peasy-pdf";
100
+
101
+ // Rotate all pages 90 degrees clockwise
102
+ const rotated = await rotate(pdf, 90);
103
+
104
+ // Rotate only pages 1 and 3
105
+ const partial = await rotate(pdf, 180, "1,3");
106
+
107
+ // Reverse page order (last page becomes first)
108
+ const reversed = await reverse(pdf);
109
+
110
+ // Extract only odd pages (1, 3, 5...) for duplex printing
111
+ const oddPages = await oddEven(pdf, "odd");
112
+ ```
113
+
114
+ Learn more: [Rotate PDF Tool](https://peasypdf.com/tools/rotate-pdf/)
115
+
116
+ ### Page Management
117
+
118
+ Delete, extract, insert, or duplicate individual pages. All page specifications use 1-based page numbers and support ranges (e.g. "1-3,5,7-9") or the keyword "all".
119
+
120
+ | Operation | Function | Description |
121
+ |-----------|----------|-------------|
122
+ | Delete | `deletePages(source, pages)` | Remove specific pages |
123
+ | Extract | `extractPages(source, pages)` | Extract pages into a new PDF |
124
+ | Insert blank | `insertBlank(source, after?, options?)` | Insert blank pages |
125
+ | Duplicate | `duplicatePages(source, pages?, copies?)` | Duplicate pages in place |
126
+
127
+ ```typescript
128
+ import { deletePages, extractPages, insertBlank, duplicatePages } from "peasy-pdf";
129
+
130
+ // Remove the cover page and table of contents
131
+ const trimmed = await deletePages(pdf, "1-2");
132
+
133
+ // Extract pages 5 through 10 into a new document
134
+ const excerpt = await extractPages(pdf, "5-10");
135
+
136
+ // Insert a blank separator page after page 3
137
+ const withSeparator = await insertBlank(pdf, "3");
138
+
139
+ // Insert 2 blank A4 pages at the end
140
+ const padded = await insertBlank(pdf, undefined, {
141
+ count: 2,
142
+ width: 595, // A4 width in points
143
+ height: 842, // A4 height in points
144
+ });
145
+
146
+ // Duplicate every page (useful for printing 2-up)
147
+ const doubled = await duplicatePages(pdf);
148
+ ```
149
+
150
+ Learn more: [PDF Page Tools](https://peasypdf.com/tools/)
151
+
152
+ ### Metadata
153
+
154
+ Read, write, or strip PDF document metadata including title, author, subject, keywords, creator, and producer fields. The `info()` function provides a quick overview including page count and file size.
155
+
156
+ | Operation | Function | Description |
157
+ |-----------|----------|-------------|
158
+ | Info | `info(source)` | Page count, metadata, file size |
159
+ | Get | `getMetadata(source)` | Read all metadata fields |
160
+ | Set | `setMetadata(source, metadata)` | Update metadata fields |
161
+ | Strip | `stripMetadata(source)` | Remove all metadata |
162
+
163
+ ```typescript
164
+ import { info, getMetadata, setMetadata, stripMetadata } from "peasy-pdf";
165
+
166
+ // Quick document overview
167
+ const pdfInfo = await info(pdf);
168
+ console.log(`${pdfInfo.pages} pages, ${pdfInfo.sizeBytes} bytes`);
169
+ console.log(`Title: ${pdfInfo.title}, Author: ${pdfInfo.author}`);
170
+
171
+ // Set metadata before publishing
172
+ const published = await setMetadata(pdf, {
173
+ title: "Q1 Financial Report",
174
+ author: "Finance Department",
175
+ subject: "Quarterly financials",
176
+ keywords: "finance, quarterly, 2026",
177
+ });
178
+
179
+ // Strip all metadata for privacy
180
+ const clean = await stripMetadata(pdf);
181
+ ```
182
+
183
+ Learn more: [PDF Metadata Tool](https://peasypdf.com/tools/pdf-metadata/)
184
+
185
+ ## TypeScript Types
186
+
187
+ ```typescript
188
+ import type { PdfInfo, PdfMetadata, PageSize, OddEvenMode } from "peasy-pdf";
189
+
190
+ // PdfInfo -- document overview from info()
191
+ const pdfInfo: PdfInfo = {
192
+ pages: 10,
193
+ title: "Annual Report",
194
+ author: "Finance Team",
195
+ subject: "2026 Financials",
196
+ creator: "peasy-pdf",
197
+ producer: "pdf-lib",
198
+ sizeBytes: 245_760,
199
+ };
200
+
201
+ // PdfMetadata -- fields for get/set metadata
202
+ const metadata: PdfMetadata = {
203
+ title: "My Document",
204
+ author: "John Doe",
205
+ subject: "Testing",
206
+ keywords: "test, pdf",
207
+ };
208
+
209
+ // OddEvenMode -- "odd" | "even" for page extraction
210
+ const mode: OddEvenMode = "odd";
211
+
212
+ // PageSize -- standard page sizes (planned for future use)
213
+ const size: PageSize = "a4";
214
+ ```
215
+
216
+ ## API Reference
217
+
218
+ | Function | Description |
219
+ |----------|-------------|
220
+ | `merge(...sources)` | Merge multiple PDFs into one |
221
+ | `split(source, ranges)` | Split PDF by comma-separated page ranges |
222
+ | `rotate(source, angle, pages?)` | Rotate pages by 90/180/270 degrees |
223
+ | `reverse(source)` | Reverse page order |
224
+ | `deletePages(source, pages)` | Remove specified pages |
225
+ | `extractPages(source, pages)` | Extract pages into new PDF |
226
+ | `oddEven(source, mode)` | Extract odd or even pages |
227
+ | `insertBlank(source, after?, options?)` | Insert blank pages |
228
+ | `duplicatePages(source, pages?, copies?)` | Duplicate pages in place |
229
+ | `info(source)` | Get page count, metadata, file size |
230
+ | `getMetadata(source)` | Read document metadata |
231
+ | `setMetadata(source, metadata)` | Set document metadata |
232
+ | `stripMetadata(source)` | Remove all metadata |
233
+ | `parsePages(spec, total)` | Parse page specification to 0-based indices |
234
+
235
+ All functions accept `Uint8Array` (PDF bytes) and return `Promise<Uint8Array>` or structured data. No filesystem access -- works in Node.js and browsers.
236
+
237
+ ## Also Available for Python
238
+
239
+ ```bash
240
+ pip install peasy-pdf
241
+ ```
242
+
243
+ The Python package provides PDF merge, split, rotate, compress, extract text, encrypt/decrypt, and 14 more operations with CLI, MCP server, and REST API client. See [peasy-pdf on PyPI](https://pypi.org/project/peasy-pdf/).
244
+
245
+ ## Peasy Developer Tools
246
+
247
+ | Package | PyPI | npm | Description |
248
+ |---------|------|-----|-------------|
249
+ | peasytext | [PyPI](https://pypi.org/project/peasytext/) | [npm](https://www.npmjs.com/package/peasytext) | Text analysis -- readability, sentiment, keywords |
250
+ | **peasy-pdf** | [PyPI](https://pypi.org/project/peasy-pdf/) | **[npm](https://www.npmjs.com/package/peasy-pdf)** | **PDF processing -- merge, split, rotate, metadata** |
251
+ | peasy-image | [PyPI](https://pypi.org/project/peasy-image/) | -- | Image ops -- resize, crop, filter, watermark |
252
+ | peasy-css | [PyPI](https://pypi.org/project/peasy-css/) | [npm](https://www.npmjs.com/package/peasy-css) | CSS generation -- gradients, shadows, flexbox, grid |
253
+ | peasy-compress | [PyPI](https://pypi.org/project/peasy-compress/) | [npm](https://www.npmjs.com/package/peasy-compress) | Archive & compression -- ZIP, gzip, brotli, deflate |
254
+ | peasy-document | [PyPI](https://pypi.org/project/peasy-document/) | [npm](https://www.npmjs.com/package/peasy-document) | Document conversion -- DOCX, HTML, Markdown |
255
+
256
+ Part of the [Peasy](https://peasytools.com) developer tools ecosystem.
257
+
258
+ ## License
259
+
260
+ MIT
@@ -0,0 +1,160 @@
1
+ /** Information about a PDF document. */
2
+ interface PdfInfo {
3
+ /** Total number of pages. */
4
+ pages: number;
5
+ /** Document title from metadata. */
6
+ title: string;
7
+ /** Document author from metadata. */
8
+ author: string;
9
+ /** Document subject from metadata. */
10
+ subject: string;
11
+ /** Creator application from metadata. */
12
+ creator: string;
13
+ /** PDF producer from metadata. */
14
+ producer: string;
15
+ /** File size in bytes. */
16
+ sizeBytes: number;
17
+ }
18
+ /** Metadata fields that can be set on a PDF document. */
19
+ interface PdfMetadata {
20
+ title?: string;
21
+ author?: string;
22
+ subject?: string;
23
+ keywords?: string;
24
+ creator?: string;
25
+ producer?: string;
26
+ }
27
+ /** Standard page sizes. */
28
+ type PageSize = "a3" | "a4" | "a5" | "letter" | "legal" | "tabloid";
29
+ /** Mode for odd/even page extraction. */
30
+ type OddEvenMode = "odd" | "even";
31
+
32
+ /**
33
+ * peasy-pdf — PDF manipulation engine powered by pdf-lib.
34
+ *
35
+ * All functions accept Uint8Array (PDF bytes) and return Uint8Array or
36
+ * structured data. No filesystem access — works in Node.js and browsers.
37
+ */
38
+
39
+ /**
40
+ * Parse a page specification string into zero-based page indices.
41
+ *
42
+ * Supported formats:
43
+ * - "all" — every page
44
+ * - "1,3,5" — specific pages (1-based)
45
+ * - "1-3,7-9" — ranges (inclusive, 1-based)
46
+ * - "2-4,6" — mixed ranges and singles
47
+ *
48
+ * Out-of-range values are clamped to [1, total].
49
+ */
50
+ declare function parsePages(spec: string, total: number): number[];
51
+ /**
52
+ * Merge multiple PDF documents into a single PDF.
53
+ *
54
+ * @param sources - Two or more PDF byte arrays to merge in order.
55
+ * @returns The merged PDF as a Uint8Array.
56
+ */
57
+ declare function merge(...sources: Uint8Array[]): Promise<Uint8Array>;
58
+ /**
59
+ * Split a PDF into multiple PDFs by page ranges.
60
+ *
61
+ * @param source - The source PDF bytes.
62
+ * @param ranges - Comma-separated page ranges, e.g. "1-3,4-6,7".
63
+ * Each range becomes a separate output PDF.
64
+ * @returns An array of PDF byte arrays, one per range group.
65
+ */
66
+ declare function split(source: Uint8Array, ranges: string): Promise<Uint8Array[]>;
67
+ /**
68
+ * Rotate pages by the given angle (90, 180, or 270 degrees clockwise).
69
+ *
70
+ * @param source - The source PDF bytes.
71
+ * @param angle - Rotation angle in degrees (90, 180, 270).
72
+ * @param pages - Optional page spec (default: "all").
73
+ * @returns The PDF with rotated pages.
74
+ */
75
+ declare function rotate(source: Uint8Array, angle: number, pages?: string): Promise<Uint8Array>;
76
+ /**
77
+ * Reverse the page order of a PDF.
78
+ *
79
+ * @param source - The source PDF bytes.
80
+ * @returns The PDF with pages in reverse order.
81
+ */
82
+ declare function reverse(source: Uint8Array): Promise<Uint8Array>;
83
+ /**
84
+ * Delete specified pages from a PDF.
85
+ *
86
+ * @param source - The source PDF bytes.
87
+ * @param pages - Page spec of pages to remove (e.g. "1,3,5-7").
88
+ * @returns The PDF with specified pages removed.
89
+ */
90
+ declare function deletePages(source: Uint8Array, pages: string): Promise<Uint8Array>;
91
+ /**
92
+ * Extract specified pages from a PDF into a new PDF.
93
+ *
94
+ * @param source - The source PDF bytes.
95
+ * @param pages - Page spec of pages to extract (e.g. "1-3,5").
96
+ * @returns A new PDF containing only the specified pages.
97
+ */
98
+ declare function extractPages(source: Uint8Array, pages: string): Promise<Uint8Array>;
99
+ /**
100
+ * Extract odd or even pages from a PDF.
101
+ *
102
+ * @param source - The source PDF bytes.
103
+ * @param mode - "odd" for pages 1, 3, 5... or "even" for pages 2, 4, 6...
104
+ * @returns A new PDF containing only the selected pages.
105
+ */
106
+ declare function oddEven(source: Uint8Array, mode: OddEvenMode): Promise<Uint8Array>;
107
+ /**
108
+ * Insert blank pages into a PDF.
109
+ *
110
+ * @param source - The source PDF bytes.
111
+ * @param after - Page spec for insertion point (e.g. "2" inserts after page 2).
112
+ * If omitted, blank pages are appended at the end.
113
+ * @param options - Optional: count (default 1), width/height in points.
114
+ * @returns The PDF with blank pages inserted.
115
+ */
116
+ declare function insertBlank(source: Uint8Array, after?: string, options?: {
117
+ count?: number;
118
+ width?: number;
119
+ height?: number;
120
+ }): Promise<Uint8Array>;
121
+ /**
122
+ * Duplicate pages within a PDF.
123
+ *
124
+ * @param source - The source PDF bytes.
125
+ * @param pages - Page spec of pages to duplicate (default: "all").
126
+ * @param copies - Number of copies of each page (default: 1, meaning 2 total).
127
+ * @returns The PDF with duplicated pages.
128
+ */
129
+ declare function duplicatePages(source: Uint8Array, pages?: string, copies?: number): Promise<Uint8Array>;
130
+ /**
131
+ * Get information about a PDF document.
132
+ *
133
+ * @param source - The PDF bytes.
134
+ * @returns PDF info including page count, metadata, and file size.
135
+ */
136
+ declare function info(source: Uint8Array): Promise<PdfInfo>;
137
+ /**
138
+ * Get document metadata from a PDF.
139
+ *
140
+ * @param source - The PDF bytes.
141
+ * @returns The document metadata fields.
142
+ */
143
+ declare function getMetadata(source: Uint8Array): Promise<PdfMetadata>;
144
+ /**
145
+ * Set document metadata on a PDF.
146
+ *
147
+ * @param source - The PDF bytes.
148
+ * @param metadata - Metadata fields to set. Only provided fields are updated.
149
+ * @returns The PDF with updated metadata.
150
+ */
151
+ declare function setMetadata(source: Uint8Array, metadata: PdfMetadata): Promise<Uint8Array>;
152
+ /**
153
+ * Remove all metadata from a PDF.
154
+ *
155
+ * @param source - The PDF bytes.
156
+ * @returns The PDF with all metadata fields cleared.
157
+ */
158
+ declare function stripMetadata(source: Uint8Array): Promise<Uint8Array>;
159
+
160
+ export { type OddEvenMode, type PageSize, type PdfInfo, type PdfMetadata, deletePages, duplicatePages, extractPages, getMetadata, info, insertBlank, merge, oddEven, parsePages, reverse, rotate, setMetadata, split, stripMetadata };
package/dist/index.js ADDED
@@ -0,0 +1,224 @@
1
+ // src/engine.ts
2
+ import { PDFDocument, degrees } from "pdf-lib";
3
+ function parsePages(spec, total) {
4
+ if (spec.trim().toLowerCase() === "all") {
5
+ return Array.from({ length: total }, (_, i) => i);
6
+ }
7
+ const indices = [];
8
+ for (const part of spec.split(",")) {
9
+ const trimmed = part.trim();
10
+ if (trimmed === "") continue;
11
+ if (trimmed.includes("-")) {
12
+ const [startStr, endStr] = trimmed.split("-", 2);
13
+ const start = Math.max(1, parseInt(startStr, 10));
14
+ const end = Math.min(total, parseInt(endStr, 10));
15
+ for (let i = start - 1; i < end; i++) {
16
+ indices.push(i);
17
+ }
18
+ } else {
19
+ const page = parseInt(trimmed, 10);
20
+ if (page >= 1 && page <= total) {
21
+ indices.push(page - 1);
22
+ }
23
+ }
24
+ }
25
+ return indices;
26
+ }
27
+ async function merge(...sources) {
28
+ if (sources.length === 0) {
29
+ throw new Error("merge requires at least one PDF");
30
+ }
31
+ const merged = await PDFDocument.create();
32
+ for (const src of sources) {
33
+ const doc = await PDFDocument.load(src);
34
+ const pages = await merged.copyPages(doc, doc.getPageIndices());
35
+ pages.forEach((p) => merged.addPage(p));
36
+ }
37
+ return merged.save();
38
+ }
39
+ async function split(source, ranges) {
40
+ const srcDoc = await PDFDocument.load(source);
41
+ const total = srcDoc.getPageCount();
42
+ const rangeGroups = ranges.split(",").reduce(
43
+ (groups, part) => {
44
+ const trimmed = part.trim();
45
+ if (trimmed === "") return groups;
46
+ groups.push([trimmed]);
47
+ return groups;
48
+ },
49
+ []
50
+ );
51
+ const results = [];
52
+ for (const group of rangeGroups) {
53
+ const spec = group.join(",");
54
+ const indices = parsePages(spec, total);
55
+ if (indices.length === 0) continue;
56
+ const newDoc = await PDFDocument.create();
57
+ const copiedPages = await newDoc.copyPages(srcDoc, indices);
58
+ copiedPages.forEach((p) => newDoc.addPage(p));
59
+ results.push(await newDoc.save());
60
+ }
61
+ return results;
62
+ }
63
+ async function rotate(source, angle, pages) {
64
+ const doc = await PDFDocument.load(source);
65
+ const indices = parsePages(pages || "all", doc.getPageCount());
66
+ for (const idx of indices) {
67
+ const page = doc.getPage(idx);
68
+ const current = page.getRotation().angle;
69
+ page.setRotation(degrees(current + angle));
70
+ }
71
+ return doc.save();
72
+ }
73
+ async function reverse(source) {
74
+ const srcDoc = await PDFDocument.load(source);
75
+ const total = srcDoc.getPageCount();
76
+ const reversed = await PDFDocument.create();
77
+ const indices = Array.from({ length: total }, (_, i) => total - 1 - i);
78
+ const pages = await reversed.copyPages(srcDoc, indices);
79
+ pages.forEach((p) => reversed.addPage(p));
80
+ return reversed.save();
81
+ }
82
+ async function deletePages(source, pages) {
83
+ const srcDoc = await PDFDocument.load(source);
84
+ const total = srcDoc.getPageCount();
85
+ const toDelete = new Set(parsePages(pages, total));
86
+ const keepIndices = Array.from({ length: total }, (_, i) => i).filter(
87
+ (i) => !toDelete.has(i)
88
+ );
89
+ if (keepIndices.length === 0) {
90
+ throw new Error("Cannot delete all pages from a PDF");
91
+ }
92
+ const newDoc = await PDFDocument.create();
93
+ const copiedPages = await newDoc.copyPages(srcDoc, keepIndices);
94
+ copiedPages.forEach((p) => newDoc.addPage(p));
95
+ return newDoc.save();
96
+ }
97
+ async function extractPages(source, pages) {
98
+ const srcDoc = await PDFDocument.load(source);
99
+ const indices = parsePages(pages, srcDoc.getPageCount());
100
+ if (indices.length === 0) {
101
+ throw new Error("No pages to extract");
102
+ }
103
+ const newDoc = await PDFDocument.create();
104
+ const copiedPages = await newDoc.copyPages(srcDoc, indices);
105
+ copiedPages.forEach((p) => newDoc.addPage(p));
106
+ return newDoc.save();
107
+ }
108
+ async function oddEven(source, mode) {
109
+ const srcDoc = await PDFDocument.load(source);
110
+ const total = srcDoc.getPageCount();
111
+ const indices = [];
112
+ for (let i = 0; i < total; i++) {
113
+ const pageNum = i + 1;
114
+ if (mode === "odd" && pageNum % 2 === 1) indices.push(i);
115
+ if (mode === "even" && pageNum % 2 === 0) indices.push(i);
116
+ }
117
+ if (indices.length === 0) {
118
+ throw new Error(`No ${mode} pages found`);
119
+ }
120
+ const newDoc = await PDFDocument.create();
121
+ const copiedPages = await newDoc.copyPages(srcDoc, indices);
122
+ copiedPages.forEach((p) => newDoc.addPage(p));
123
+ return newDoc.save();
124
+ }
125
+ async function insertBlank(source, after, options) {
126
+ const doc = await PDFDocument.load(source);
127
+ const total = doc.getPageCount();
128
+ const count = options?.count ?? 1;
129
+ const width = options?.width ?? 612;
130
+ const height = options?.height ?? 792;
131
+ if (after === void 0 || after === null) {
132
+ for (let i = 0; i < count; i++) {
133
+ doc.addPage([width, height]);
134
+ }
135
+ } else {
136
+ const indices = parsePages(after, total);
137
+ if (indices.length === 0) {
138
+ throw new Error("Invalid page specification for insertion point");
139
+ }
140
+ const insertAfter = Math.max(...indices);
141
+ for (let i = 0; i < count; i++) {
142
+ doc.insertPage(insertAfter + 1 + i, [width, height]);
143
+ }
144
+ }
145
+ return doc.save();
146
+ }
147
+ async function duplicatePages(source, pages, copies) {
148
+ const srcDoc = await PDFDocument.load(source);
149
+ const total = srcDoc.getPageCount();
150
+ const indices = parsePages(pages || "all", total);
151
+ const numCopies = copies ?? 1;
152
+ const newDoc = await PDFDocument.create();
153
+ for (let i = 0; i < total; i++) {
154
+ const [page] = await newDoc.copyPages(srcDoc, [i]);
155
+ newDoc.addPage(page);
156
+ if (indices.includes(i)) {
157
+ for (let c = 0; c < numCopies; c++) {
158
+ const [copy] = await newDoc.copyPages(srcDoc, [i]);
159
+ newDoc.addPage(copy);
160
+ }
161
+ }
162
+ }
163
+ return newDoc.save();
164
+ }
165
+ async function info(source) {
166
+ const doc = await PDFDocument.load(source);
167
+ return {
168
+ pages: doc.getPageCount(),
169
+ title: doc.getTitle() ?? "",
170
+ author: doc.getAuthor() ?? "",
171
+ subject: doc.getSubject() ?? "",
172
+ creator: doc.getCreator() ?? "",
173
+ producer: doc.getProducer() ?? "",
174
+ sizeBytes: source.byteLength
175
+ };
176
+ }
177
+ async function getMetadata(source) {
178
+ const doc = await PDFDocument.load(source);
179
+ return {
180
+ title: doc.getTitle() ?? void 0,
181
+ author: doc.getAuthor() ?? void 0,
182
+ subject: doc.getSubject() ?? void 0,
183
+ keywords: doc.getKeywords() ?? void 0,
184
+ creator: doc.getCreator() ?? void 0,
185
+ producer: doc.getProducer() ?? void 0
186
+ };
187
+ }
188
+ async function setMetadata(source, metadata) {
189
+ const doc = await PDFDocument.load(source);
190
+ if (metadata.title !== void 0) doc.setTitle(metadata.title);
191
+ if (metadata.author !== void 0) doc.setAuthor(metadata.author);
192
+ if (metadata.subject !== void 0) doc.setSubject(metadata.subject);
193
+ if (metadata.keywords !== void 0)
194
+ doc.setKeywords([metadata.keywords]);
195
+ if (metadata.creator !== void 0) doc.setCreator(metadata.creator);
196
+ if (metadata.producer !== void 0) doc.setProducer(metadata.producer);
197
+ return doc.save();
198
+ }
199
+ async function stripMetadata(source) {
200
+ const doc = await PDFDocument.load(source);
201
+ doc.setTitle("");
202
+ doc.setAuthor("");
203
+ doc.setSubject("");
204
+ doc.setKeywords([]);
205
+ doc.setCreator("");
206
+ doc.setProducer("");
207
+ return doc.save();
208
+ }
209
+ export {
210
+ deletePages,
211
+ duplicatePages,
212
+ extractPages,
213
+ getMetadata,
214
+ info,
215
+ insertBlank,
216
+ merge,
217
+ oddEven,
218
+ parsePages,
219
+ reverse,
220
+ rotate,
221
+ setMetadata,
222
+ split,
223
+ stripMetadata
224
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "peasy-pdf",
3
+ "version": "0.1.0",
4
+ "description": "PDF manipulation library for Node.js — merge, split, rotate, extract text, metadata. TypeScript-first.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup src/index.ts --format esm --dts",
19
+ "test": "vitest run",
20
+ "typecheck": "tsc --noEmit"
21
+ },
22
+ "keywords": [
23
+ "pdf",
24
+ "merge",
25
+ "split",
26
+ "rotate",
27
+ "extract",
28
+ "metadata",
29
+ "peasy"
30
+ ],
31
+ "author": "Peasy Tools",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "url": "https://github.com/peasytools/peasy-pdf-js.git"
35
+ },
36
+ "homepage": "https://peasypdf.com",
37
+ "dependencies": {
38
+ "pdf-lib": "^1.17"
39
+ },
40
+ "devDependencies": {
41
+ "tsup": "^8.0",
42
+ "typescript": "^5.7",
43
+ "vitest": "^3.0"
44
+ }
45
+ }