podpdf 1.0.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/README.md ADDED
@@ -0,0 +1,406 @@
1
+ # podpdf
2
+
3
+ **Ultra-fast, zero-dependency PDF generation for Node.js & Bun**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/podpdf.svg)](https://www.npmjs.com/package/podpdf)
6
+ [![bundle size](https://img.shields.io/badge/size-8KB-brightgreen)](https://bundlephobia.com/package/podpdf)
7
+ [![zero dependencies](https://img.shields.io/badge/dependencies-0-blue)](https://www.npmjs.com/package/podpdf)
8
+
9
+ ```
10
+ 8 KB minified • Zero dependencies • 5x faster than jsPDF • TypeScript native
11
+ ```
12
+
13
+ ## Why podpdf?
14
+
15
+ | Library | Size | Dependencies | Speed |
16
+ |---------|------|--------------|-------|
17
+ | **podpdf** | **8 KB** | **0** | **5.5x** |
18
+ | jsPDF | 290 KB | 2+ | 1x |
19
+ | pdfkit | 1 MB | 10+ | 0.8x |
20
+
21
+ ## Feature Comparison
22
+
23
+ | Feature | podpdf | jsPDF | pdfkit |
24
+ |---------|:------:|:-----:|:------:|
25
+ | **Text** | ✅ | ✅ | ✅ |
26
+ | **Text Styling (bold/italic)** | ✅ | ✅ | ✅ |
27
+ | **Text Wrap** | ✅ | ✅ | ✅ |
28
+ | **Text Alignment** | ✅ | ✅ | ✅ |
29
+ | **Rectangle** | ✅ | ✅ | ✅ |
30
+ | **Rounded Rectangle** | ✅ | ✅ | ✅ |
31
+ | **Circle** | ✅ | ✅ | ✅ |
32
+ | **Line (solid/dashed)** | ✅ | ✅ | ✅ |
33
+ | **Tables** | ✅ | ⚠️ Plugin | ⚠️ Manual |
34
+ | **Images (JPEG/PNG)** | ✅ | ✅ | ✅ |
35
+ | **Links/URLs** | ✅ | ✅ | ✅ |
36
+ | **Multi-page** | ✅ | ✅ | ✅ |
37
+ | **Custom Fonts** | ❌ | ✅ | ✅ |
38
+ | **Vector Graphics** | ⚠️ Basic | ✅ | ✅ Full |
39
+ | **Forms/Fields** | ❌ | ✅ | ✅ |
40
+ | **Encryption** | ❌ | ✅ | ✅ |
41
+ | **TypeScript Native** | ✅ | ❌ | ❌ |
42
+ | **Fluent API** | ✅ | ⚠️ Partial | ✅ |
43
+ | **Browser Support** | ✅ | ✅ | ❌ |
44
+ | **Node.js/Bun** | ✅ | ✅ | ✅ |
45
+
46
+ > **podpdf** - Best balance of size, speed, and features for common use-cases (invoices, reports, tables)
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ npm install podpdf
52
+ # or
53
+ yarn add podpdf
54
+ # or
55
+ pnpm add podpdf
56
+ # or
57
+ bun add podpdf
58
+ ```
59
+
60
+ ## Quick Start
61
+
62
+ ```typescript
63
+ import { pdf } from 'podpdf'
64
+
65
+ await pdf('A4')
66
+ .text('Hello World!', 50, 50, { size: 24, weight: 'bold' })
67
+ .rect(50, 80, 200, 100, { fill: '#3498db', radius: 10 })
68
+ .save('hello.pdf')
69
+ ```
70
+
71
+ ## Features
72
+
73
+ - **Text** - Multiple fonts, sizes, colors, alignment, text wrapping
74
+ - **Shapes** - Rectangle, rounded rectangle, circle, line (solid & dashed)
75
+ - **Tables** - Easy table creation with headers, styling, alignment
76
+ - **Images** - JPEG and PNG support
77
+ - **Links** - Clickable URLs with optional underline
78
+ - **Multi-page** - Multiple pages with different sizes
79
+ - **Fluent API** - Chainable methods for clean code
80
+
81
+ ## API Reference
82
+
83
+ ### Create Document
84
+
85
+ ```typescript
86
+ import { pdf, PDF, SIZES } from 'podpdf'
87
+
88
+ // Using helper function
89
+ const doc = pdf('A4')
90
+
91
+ // Available sizes: A3, A4, A5, LETTER
92
+ // Or custom: pdf({ width: 600, height: 800 })
93
+ ```
94
+
95
+ ### Text
96
+
97
+ ```typescript
98
+ .text(content, x, y, options?)
99
+ ```
100
+
101
+ | Option | Type | Default | Description |
102
+ |--------|------|---------|-------------|
103
+ | `size` | number | 12 | Font size |
104
+ | `color` | string | '#000' | Color (hex) |
105
+ | `weight` | string | 'normal' | 'normal', 'bold', 'italic', 'bolditalic' |
106
+ | `align` | string | 'left' | 'left', 'center', 'right' |
107
+ | `maxWidth` | number | - | Auto wrap text |
108
+
109
+ ```typescript
110
+ .text('Title', 50, 50, { size: 24, weight: 'bold', color: '#333' })
111
+ .text('Centered', 297, 100, { align: 'center' })
112
+ .text('Long text...', 50, 150, { maxWidth: 400 })
113
+ ```
114
+
115
+ ### Shapes
116
+
117
+ ```typescript
118
+ // Rectangle
119
+ .rect(x, y, width, height, { fill?, stroke?, lineWidth?, radius? })
120
+
121
+ // Circle
122
+ .circle(cx, cy, radius, { fill?, stroke?, lineWidth? })
123
+
124
+ // Line
125
+ .line(x1, y1, x2, y2, { color?, width?, dash? })
126
+ ```
127
+
128
+ ```typescript
129
+ .rect(50, 50, 200, 100, { fill: '#e74c3c' })
130
+ .rect(50, 50, 200, 100, { fill: '#3498db', radius: 15 })
131
+ .circle(150, 200, 50, { fill: '#9b59b6' })
132
+ .line(50, 300, 250, 300, { color: '#2ecc71', width: 2 })
133
+ .line(50, 320, 250, 320, { dash: [5, 3] })
134
+ ```
135
+
136
+ ### Tables
137
+
138
+ ```typescript
139
+ .table(data, x, y, options)
140
+ ```
141
+
142
+ | Option | Type | Default | Description |
143
+ |--------|------|---------|-------------|
144
+ | `columns` | array | required | Column definitions |
145
+ | `headerBg` | string | '#F0F0F0' | Header background |
146
+ | `headerColor` | string | '#000' | Header text color |
147
+ | `borderColor` | string | '#CCC' | Border color |
148
+ | `fontSize` | number | 10 | Font size |
149
+ | `padding` | number | 8 | Cell padding |
150
+
151
+ ```typescript
152
+ .table(
153
+ [
154
+ ['John', '25', 'Admin'],
155
+ ['Jane', '30', 'User'],
156
+ ],
157
+ 50, 100,
158
+ {
159
+ columns: [
160
+ { header: 'Name', width: 100 },
161
+ { header: 'Age', width: 60, align: 'center' },
162
+ { header: 'Role', width: 80 },
163
+ ],
164
+ headerBg: '#2c3e50',
165
+ headerColor: '#fff'
166
+ }
167
+ )
168
+ ```
169
+
170
+ ### Images
171
+
172
+ ```typescript
173
+ const imageData = await Bun.file('photo.jpg').bytes()
174
+ // or: Buffer.from(fs.readFileSync('photo.jpg'))
175
+
176
+ .image(imageData, x, y, { width?, height? })
177
+ ```
178
+
179
+ ### Links
180
+
181
+ ```typescript
182
+ .link('Click here', 'https://example.com', x, y, { underline?, color? })
183
+ ```
184
+
185
+ ### Pages
186
+
187
+ ```typescript
188
+ .page() // Add page with default size
189
+ .page('A5') // Different size
190
+ .page({ width: 500, height: 700 }) // Custom
191
+ ```
192
+
193
+ ### Output
194
+
195
+ ```typescript
196
+ // Save to file
197
+ await doc.save('output.pdf')
198
+
199
+ // Get as Uint8Array
200
+ const bytes = doc.build()
201
+ ```
202
+
203
+ ## Examples
204
+
205
+ ### Complete Invoice
206
+
207
+ ```typescript
208
+ import { pdf } from 'podpdf'
209
+
210
+ const invoice = {
211
+ number: 'INV-2024-001',
212
+ date: '2024-12-26',
213
+ dueDate: '2025-01-26',
214
+ company: {
215
+ name: 'Tech Solutions Inc.',
216
+ address: '123 Innovation Street',
217
+ city: 'San Francisco, CA 94102',
218
+ email: 'billing@techsolutions.com'
219
+ },
220
+ client: {
221
+ name: 'Acme Corporation',
222
+ address: '456 Business Avenue',
223
+ city: 'New York, NY 10001',
224
+ email: 'accounts@acme.com'
225
+ },
226
+ items: [
227
+ { desc: 'Website Development', qty: 1, rate: 5000, amount: 5000 },
228
+ { desc: 'Mobile App (iOS)', qty: 1, rate: 8000, amount: 8000 },
229
+ { desc: 'UI/UX Design', qty: 40, rate: 75, amount: 3000 },
230
+ { desc: 'API Integration', qty: 20, rate: 100, amount: 2000 },
231
+ { desc: 'Quality Assurance', qty: 15, rate: 60, amount: 900 },
232
+ ]
233
+ }
234
+
235
+ const subtotal = invoice.items.reduce((sum, i) => sum + i.amount, 0)
236
+ const tax = subtotal * 0.1
237
+ const total = subtotal + tax
238
+
239
+ const fmt = (n: number) => '$' + n.toLocaleString('en-US', { minimumFractionDigits: 2 })
240
+
241
+ await pdf('A4')
242
+ // Header
243
+ .rect(0, 0, 595, 100, { fill: '#1a1a2e' })
244
+ .text(invoice.company.name.toUpperCase(), 50, 40, { size: 20, color: '#fff', weight: 'bold' })
245
+ .text('INVOICE', 545, 35, { size: 28, color: '#4a9eff', weight: 'bold', align: 'right' })
246
+ .text(invoice.number, 545, 65, { size: 12, color: '#888', align: 'right' })
247
+
248
+ // From
249
+ .text('From:', 50, 130, { size: 10, color: '#888' })
250
+ .text(invoice.company.name, 50, 145, { size: 12, weight: 'bold' })
251
+ .text(invoice.company.address, 50, 160, { size: 10 })
252
+ .text(invoice.company.city, 50, 175, { size: 10 })
253
+ .text(invoice.company.email, 50, 190, { size: 10, color: '#4a9eff' })
254
+
255
+ // Bill To
256
+ .text('Bill To:', 320, 130, { size: 10, color: '#888' })
257
+ .text(invoice.client.name, 320, 145, { size: 12, weight: 'bold' })
258
+ .text(invoice.client.address, 320, 160, { size: 10 })
259
+ .text(invoice.client.city, 320, 175, { size: 10 })
260
+ .text(invoice.client.email, 320, 190, { size: 10, color: '#4a9eff' })
261
+
262
+ // Date boxes
263
+ .rect(50, 220, 160, 45, { fill: '#f8f9fa', radius: 5 })
264
+ .text('Invoice Date', 60, 235, { size: 9, color: '#888' })
265
+ .text(invoice.date, 60, 252, { size: 12, weight: 'bold' })
266
+
267
+ .rect(230, 220, 160, 45, { fill: '#f8f9fa', radius: 5 })
268
+ .text('Due Date', 240, 235, { size: 9, color: '#888' })
269
+ .text(invoice.dueDate, 240, 252, { size: 12, weight: 'bold' })
270
+
271
+ // Items Table
272
+ .table(
273
+ invoice.items.map(i => [i.desc, i.qty.toString(), fmt(i.rate), fmt(i.amount)]),
274
+ 50, 290,
275
+ {
276
+ columns: [
277
+ { header: 'Description', width: 220 },
278
+ { header: 'Qty', width: 60, align: 'center' },
279
+ { header: 'Rate', width: 90, align: 'right' },
280
+ { header: 'Amount', width: 95, align: 'right' },
281
+ ],
282
+ headerBg: '#1a1a2e',
283
+ headerColor: '#fff',
284
+ borderColor: '#e0e0e0',
285
+ fontSize: 10,
286
+ padding: 10
287
+ }
288
+ )
289
+
290
+ // Summary Box
291
+ .rect(330, 480, 185, 100, { stroke: '#e0e0e0', radius: 5 })
292
+ .text('Subtotal:', 345, 500, { size: 11 })
293
+ .text(fmt(subtotal), 500, 500, { size: 11, align: 'right' })
294
+ .text('Tax (10%):', 345, 520, { size: 11 })
295
+ .text(fmt(tax), 500, 520, { size: 11, align: 'right' })
296
+ .line(345, 540, 500, 540, { color: '#e0e0e0' })
297
+ .rect(330, 548, 185, 30, { fill: '#1a1a2e', radius: 5 })
298
+ .text('Total:', 345, 567, { size: 12, weight: 'bold', color: '#fff' })
299
+ .text(fmt(total), 500, 567, { size: 12, weight: 'bold', color: '#fff', align: 'right' })
300
+
301
+ // Payment Info
302
+ .rect(50, 610, 230, 90, { fill: '#f0f7ff', radius: 5 })
303
+ .text('Payment Information', 65, 630, { size: 11, weight: 'bold', color: '#1a1a2e' })
304
+ .text('Bank: First National Bank', 65, 650, { size: 9 })
305
+ .text('Account: 1234-5678-9012', 65, 665, { size: 9 })
306
+ .text('SWIFT: FNBKUS12', 65, 680, { size: 9 })
307
+
308
+ // Terms
309
+ .rect(300, 610, 215, 90, { fill: '#fff9e6', radius: 5 })
310
+ .text('Terms & Conditions', 315, 630, { size: 11, weight: 'bold', color: '#b8860b' })
311
+ .text('Payment due within 30 days', 315, 650, { size: 9 })
312
+ .text('Late fee: 1.5% per month', 315, 665, { size: 9 })
313
+ .text('Make checks payable to:', 315, 680, { size: 9 })
314
+ .text('Tech Solutions Inc.', 315, 695, { size: 9, weight: 'bold' })
315
+
316
+ // Footer
317
+ .line(50, 730, 545, 730, { color: '#eee' })
318
+ .text('Thank you for your business!', 297, 750, { size: 11, color: '#666', align: 'center' })
319
+ .text('Questions? Email billing@techsolutions.com', 297, 768, { size: 9, color: '#999', align: 'center' })
320
+
321
+ .save('invoice.pdf')
322
+ ```
323
+
324
+ ### Report with Chart
325
+
326
+ ```typescript
327
+ import { pdf } from 'podpdf'
328
+
329
+ const data = [120, 150, 180, 140, 200]
330
+ const max = Math.max(...data)
331
+
332
+ const doc = pdf('A4')
333
+ .text('Sales Report', 50, 50, { size: 24, weight: 'bold' })
334
+
335
+ // Simple bar chart
336
+ for (let i = 0; i < data.length; i++) {
337
+ const height = (data[i] / max) * 100
338
+ doc.rect(80 + i * 60, 200 - height, 40, height, { fill: '#3498db', radius: 3 })
339
+ }
340
+
341
+ await doc.save('report.pdf')
342
+ ```
343
+
344
+ ## Page Sizes
345
+
346
+ ```typescript
347
+ import { SIZES } from 'podpdf'
348
+
349
+ SIZES.A3 // { width: 842, height: 1191 }
350
+ SIZES.A4 // { width: 595, height: 842 }
351
+ SIZES.A5 // { width: 420, height: 595 }
352
+ SIZES.LETTER // { width: 612, height: 792 }
353
+ ```
354
+
355
+ ## TypeScript
356
+
357
+ Full TypeScript support with exported types:
358
+
359
+ ```typescript
360
+ import type {
361
+ Color, // string | [r, g, b]
362
+ Align, // 'left' | 'center' | 'right'
363
+ Weight, // 'normal' | 'bold' | 'italic' | 'bolditalic'
364
+ Size, // { width, height }
365
+ TextOpts,
366
+ RectOpts,
367
+ LineOpts,
368
+ CircleOpts,
369
+ ImageOpts,
370
+ LinkOpts,
371
+ TableCol,
372
+ TableOpts
373
+ } from 'podpdf'
374
+ ```
375
+
376
+ ## Benchmark
377
+
378
+ Tested with 1000 document generations:
379
+
380
+ | Test | podpdf | jsPDF |
381
+ |------|--------|-------|
382
+ | Simple text | 0.033ms | 0.271ms |
383
+ | Styled text | 0.044ms | 0.260ms |
384
+ | Shapes | 0.024ms | 0.254ms |
385
+ | Multi-page | 0.083ms | 0.251ms |
386
+ | Complex doc | 0.051ms | 0.260ms |
387
+
388
+ **Result: podpdf is 5.5x faster on average**
389
+
390
+ ## Browser Support
391
+
392
+ podpdf is designed for Node.js and Bun. For browser usage, the `build()` method returns a `Uint8Array` that can be converted to a Blob:
393
+
394
+ ```typescript
395
+ const bytes = doc.build()
396
+ const blob = new Blob([bytes], { type: 'application/pdf' })
397
+ const url = URL.createObjectURL(blob)
398
+ ```
399
+
400
+ ## License
401
+
402
+ MIT
403
+
404
+ ## Contributing
405
+
406
+ Contributions are welcome! Please open an issue or submit a PR.
@@ -0,0 +1,71 @@
1
+ export type Color = string | [number, number, number];
2
+ export type Align = 'left' | 'center' | 'right';
3
+ export type Weight = 'normal' | 'bold' | 'italic' | 'bolditalic';
4
+ export interface Size {
5
+ width: number;
6
+ height: number;
7
+ }
8
+ export interface TextOpts {
9
+ size?: number;
10
+ color?: Color;
11
+ align?: Align;
12
+ weight?: Weight;
13
+ maxWidth?: number;
14
+ }
15
+ export interface RectOpts {
16
+ fill?: Color;
17
+ stroke?: Color;
18
+ lineWidth?: number;
19
+ radius?: number;
20
+ }
21
+ export interface LineOpts {
22
+ color?: Color;
23
+ width?: number;
24
+ dash?: number[];
25
+ }
26
+ export interface CircleOpts {
27
+ fill?: Color;
28
+ stroke?: Color;
29
+ lineWidth?: number;
30
+ }
31
+ export interface ImageOpts {
32
+ width?: number;
33
+ height?: number;
34
+ }
35
+ export interface LinkOpts {
36
+ underline?: boolean;
37
+ color?: Color;
38
+ }
39
+ export interface TableCol {
40
+ header: string;
41
+ width?: number;
42
+ align?: Align;
43
+ }
44
+ export interface TableOpts {
45
+ columns: TableCol[];
46
+ headerBg?: Color;
47
+ headerColor?: Color;
48
+ borderColor?: Color;
49
+ fontSize?: number;
50
+ padding?: number;
51
+ }
52
+ export declare const SIZES: Record<string, Size>;
53
+ export declare class PDF {
54
+ private pages;
55
+ private cur;
56
+ private sz;
57
+ constructor(s?: Size | keyof typeof SIZES);
58
+ page(s?: Size | keyof typeof SIZES): this;
59
+ text(t: string, x: number, y: number, o?: TextOpts): this;
60
+ rect(x: number, y: number, w: number, h: number, o?: RectOpts): this;
61
+ line(x1: number, y1: number, x2: number, y2: number, o?: LineOpts): this;
62
+ circle(cx: number, cy: number, r: number, o?: CircleOpts): this;
63
+ image(d: Uint8Array, x: number, y: number, o?: ImageOpts): this;
64
+ link(t: string, url: string, x: number, y: number, o?: LinkOpts): this;
65
+ table(data: string[][], x: number, y: number, o: TableOpts): this;
66
+ private ensure;
67
+ build(): Uint8Array;
68
+ save(path: string): Promise<void>;
69
+ }
70
+ export declare const pdf: (s?: Size | keyof typeof SIZES) => PDF;
71
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import{createRequire as U}from"node:module";var B=Object.create;var{getPrototypeOf:v,defineProperty:z,getOwnPropertyNames:E}=Object;var W=Object.prototype.hasOwnProperty;var L=(e,n,t)=>{t=e!=null?B(v(e)):{};let i=n||!e||!e.__esModule?z(t,"default",{value:e,enumerable:!0}):t;for(let r of E(e))if(!W.call(i,r))z(i,r,{get:()=>e[r],enumerable:!0});return i};var P=U(import.meta.url);var O={A4:{width:595,height:842},A3:{width:842,height:1191},A5:{width:420,height:595},LETTER:{width:612,height:792}},q={normal:"Helvetica",bold:"Helvetica-Bold",italic:"Helvetica-Oblique",bolditalic:"Helvetica-BoldOblique"},T=(e)=>{if(Array.isArray(e))return e;let n=e.replace("#","");if(n.length===3)return[parseInt(n[0]+n[0],16)/255,parseInt(n[1]+n[1],16)/255,parseInt(n[2]+n[2],16)/255];return[parseInt(n.slice(0,2),16)/255,parseInt(n.slice(2,4),16)/255,parseInt(n.slice(4,6),16)/255]},I=(e)=>{let[n,t,i]=T(e);return`${n.toFixed(3)} ${t.toFixed(3)} ${i.toFixed(3)} rg`},F=(e)=>{let[n,t,i]=T(e);return`${n.toFixed(3)} ${t.toFixed(3)} ${i.toFixed(3)} RG`},D=(e)=>e.replace(/\\/g,"\\\\").replace(/\(/g,"\\(").replace(/\)/g,"\\)"),s=(e)=>Number.isInteger(e)?e.toString():e.toFixed(2),y=(e,n)=>e.length*n*0.5;class x{c=[];s=0;e=new TextEncoder;w(e){let n=typeof e==="string"?this.e.encode(e):e;return this.c.push(n),this.s+=n.length,this}l(e){return this.w(e+`
2
+ `)}size(){return this.s}out(){let e=new Uint8Array(this.s),n=0;for(let t of this.c)e.set(t,n),n+=t.length;return e}}class A{w;h;c=[];f=new Set;imgs=[];links=[];ic=0;constructor(e){this.w=e.width,this.h=e.height}text(e,n,t,i={}){let r=i.size??12,o=i.weight??"normal",l=i.color??"#000",u=i.align??"left",$=q[o];this.f.add($);let g=this.h-t,b=n,a=i.maxWidth?this.wrap(e,i.maxWidth,r):[e];for(let h of a)b=u==="center"?n-y(h,r)/2:u==="right"?n-y(h,r):n,this.c.push("q","BT",I(l),`/${$.replace("-","")} ${r} Tf`,`${s(b)} ${s(g)} Td`,`(${D(h)}) Tj`,"ET","Q"),g-=r*1.2;return this}rect(e,n,t,i,r={}){let o=this.h-n-i;if(this.c.push("q"),r.radius&&r.radius>0){let l=Math.min(r.radius,t/2,i/2);this.c.push(`${s(e+l)} ${s(o)} m`,`${s(e+t-l)} ${s(o)} l`,`${s(e+t)} ${s(o)} ${s(e+t)} ${s(o+l)} y`,`${s(e+t)} ${s(o+i-l)} l`,`${s(e+t)} ${s(o+i)} ${s(e+t-l)} ${s(o+i)} y`,`${s(e+l)} ${s(o+i)} l`,`${s(e)} ${s(o+i)} ${s(e)} ${s(o+i-l)} y`,`${s(e)} ${s(o+l)} l`,`${s(e)} ${s(o)} ${s(e+l)} ${s(o)} y`,"h")}else this.c.push(`${s(e)} ${s(o)} ${s(t)} ${s(i)} re`);if(r.fill)this.c.push(I(r.fill)),this.c.push(r.stroke?"B":"f");if(r.stroke){if(this.c.push(F(r.stroke)),r.lineWidth)this.c.push(`${r.lineWidth} w`);if(!r.fill)this.c.push("S")}return this.c.push("Q"),this}line(e,n,t,i,r={}){if(this.c.push("q"),r.color)this.c.push(F(r.color));if(r.width)this.c.push(`${r.width} w`);if(r.dash)this.c.push(`[${r.dash.join(" ")}] 0 d`);return this.c.push(`${s(e)} ${s(this.h-n)} m`,`${s(t)} ${s(this.h-i)} l`,"S","Q"),this}circle(e,n,t,i={}){let r=this.h-n;if(this.c.push("q"),this.c.push(`${s(e+t)} ${s(r)} m`,`${s(e+t)} ${s(r+t*0.5523)} ${s(e+t*0.5523)} ${s(r+t)} ${s(e)} ${s(r+t)} c`,`${s(e-t*0.5523)} ${s(r+t)} ${s(e-t)} ${s(r+t*0.5523)} ${s(e-t)} ${s(r)} c`,`${s(e-t*0.5523)} ${s(r-t)} ${s(e-t)} ${s(r-t*0.5523)} ${s(e)} ${s(r-t)} c`,`${s(e+t*0.5523)} ${s(r-t)} ${s(e+t)} ${s(r-t*0.5523)} ${s(e+t)} ${s(r)} c`),i.fill)this.c.push(I(i.fill)),this.c.push(i.stroke?"B":"f");if(i.stroke){if(this.c.push(F(i.stroke)),i.lineWidth)this.c.push(`${i.lineWidth} w`);if(!i.fill)this.c.push("S")}return this.c.push("Q"),this}image(e,n,t,i={}){let r=this.imgInfo(e);if(!r)return this;let o=i.width??r.w,l=i.height??r.h,u=`I${++this.ic}`;return this.imgs.push({d:e,w:r.w,h:r.h,x:n,y:this.h-t-l,id:u}),this.c.push("q",`${s(o)} 0 0 ${s(l)} ${s(n)} ${s(this.h-t-l)} cm`,`/${u} Do`,"Q"),this}link(e,n,t,i,r={}){let o=r.color??"#00E";this.text(e,t,i,{color:o,size:12});let u=y(e,12);if(r.underline!==!1)this.line(t,i+2,t+u,i+2,{color:o,width:0.5});return this.links.push({x:t,y:this.h-i-12,w:u,h:12,url:n}),this}table(e,n,t,i){let r=i.padding??8,o=i.fontSize??10,l=o+r*2,u=i.columns.map((a,h)=>a.width??Math.max(y(a.header,o),...e.map((d)=>y(d[h]??"",o)))+r*2),$=u.reduce((a,h)=>a+h,0),g=t;this.rect(n,g,$,l,{fill:i.headerBg??"#F0F0F0"});let b=n;for(let a=0;a<i.columns.length;a++){let h=i.columns[a];this.text(h.header,b+(h.align==="center"?u[a]/2:h.align==="right"?u[a]-r:r),g+r+o*0.8,{size:o,weight:"bold",color:i.headerColor??"#000",align:h.align}),b+=u[a]}g+=l;for(let a=0;a<e.length;a++){this.rect(n,g,$,l,{fill:a%2?"#F9F9F9":"#FFF"}),b=n;for(let h=0;h<i.columns.length;h++){let d=i.columns[h];this.text(e[a][h]??"",b+(d.align==="center"?u[h]/2:d.align==="right"?u[h]-r:r),g+r+o*0.8,{size:o,align:d.align}),b+=u[h]}g+=l}return this.rect(n,t,$,l*(e.length+1),{stroke:i.borderColor??"#CCC"}),this}wrap(e,n,t){let i=e.split(" "),r=[],o="";for(let l of i){let u=o?`${o} ${l}`:l;if(y(u,t)<=n)o=u;else{if(o)r.push(o);o=l}}if(o)r.push(o);return r}imgInfo(e){if(e[0]===255&&e[1]===216){let n=2;while(n<e.length){if(e[n]!==255)break;let t=e[n+1];if(t>=192&&t<=207&&t!==196&&t!==200&&t!==204)return{h:e[n+5]<<8|e[n+6],w:e[n+7]<<8|e[n+8]};n+=2+(e[n+2]<<8|e[n+3])}}if(e[0]===137&&e[1]===80)return{w:e[16]<<24|e[17]<<16|e[18]<<8|e[19],h:e[20]<<24|e[21]<<16|e[22]<<8|e[23]};return null}}class R{pages=[];cur=null;sz;constructor(e="A4"){this.sz=typeof e==="string"?O[e]:e}page(e){return this.cur=new A(e?typeof e==="string"?O[e]:e:this.sz),this.pages.push(this.cur),this}text(e,n,t,i){return this.ensure().text(e,n,t,i),this}rect(e,n,t,i,r){return this.ensure().rect(e,n,t,i,r),this}line(e,n,t,i,r){return this.ensure().line(e,n,t,i,r),this}circle(e,n,t,i){return this.ensure().circle(e,n,t,i),this}image(e,n,t,i){return this.ensure().image(e,n,t,i),this}link(e,n,t,i,r){return this.ensure().link(e,n,t,i,r),this}table(e,n,t,i){return this.ensure().table(e,n,t,i),this}ensure(){if(!this.cur)this.page();return this.cur}build(){let e=new x,n=[0],t=0;e.l("%PDF-1.4").l("%µµµµ");let i=new Set;this.pages.forEach((c)=>c.f.forEach((m)=>i.add(m)));let r=Array.from(i),o={};for(let c of r){let m=++t;o[c]=m,n[m]=e.size(),e.l(`${m} 0 obj`).l(`<</Type/Font/Subtype/Type1/BaseFont/${c}>>`).l("endobj")}let l=[],u=[],$=[];for(let c of this.pages){let m=[];for(let p of c.imgs){let f=++t;m.push(f),n[f]=e.size();let j=String.fromCharCode(...p.d);e.l(`${f} 0 obj`).l(`<</Type/XObject/Subtype/Image/Width ${p.w}/Height ${p.h}/ColorSpace/DeviceRGB/BitsPerComponent 8/Filter/DCTDecode/Length ${p.d.length}>>`).l("stream").w(j).l("").l("endstream").l("endobj")}$.push(m);let C=c.c.join(`
3
+ `),S=new TextEncoder().encode(C).length,w=++t;l.push(w),n[w]=e.size(),e.l(`${w} 0 obj`).l(`<</Length ${S}>>`).l("stream").w(C).l("").l("endstream").l("endobj");let k=[];for(let p of c.links){let f=++t;k.push(f),n[f]=e.size(),e.l(`${f} 0 obj`).l(`<</Type/Annot/Subtype/Link/Rect[${p.x} ${p.y} ${p.x+p.w} ${p.y+p.h}]/Border[0 0 0]/A<</Type/Action/S/URI/URI(${p.url})>>>>`).l("endobj")}u.push(k)}let g=++t,b=t+1,a=this.pages.map((c,m)=>b+m);n[g]=e.size(),e.l(`${g} 0 obj`).l(`<</Type/Pages/Kids[${a.map((c)=>`${c} 0 R`).join(" ")}]/Count ${a.length}>>`).l("endobj");for(let c=0;c<this.pages.length;c++){let m=this.pages[c],C=++t,S=Array.from(m.f).map((p)=>`/${p.replace("-","")} ${o[p]} 0 R`).join(""),w=m.imgs.map((p,f)=>`/${p.id} ${$[c][f]} 0 R`).join(""),k=u[c].length?`/Annots[${u[c].map((p)=>`${p} 0 R`).join(" ")}]`:"";n[C]=e.size(),e.l(`${C} 0 obj`).l(`<</Type/Page/Parent ${g} 0 R/MediaBox[0 0 ${m.w} ${m.h}]/Contents ${l[c]} 0 R/Resources<<${S?`/Font<<${S}>>`:""}${w?`/XObject<<${w}>>`:""}>>${k}>>`).l("endobj")}let h=++t;n[h]=e.size(),e.l(`${h} 0 obj`).l(`<</Type/Catalog/Pages ${g} 0 R>>`).l("endobj");let d=e.size();e.l("xref").l(`0 ${t+1}`).l("0000000000 65535 f ");for(let c=1;c<=t;c++)e.l(`${n[c].toString().padStart(10,"0")} 00000 n `);return e.l("trailer").l(`<</Size ${t+1}/Root ${h} 0 R>>`).l("startxref").l(d.toString()).l("%%EOF"),e.out()}async save(e){let n=this.build();if(typeof Bun<"u")await Bun.write(e,n);else{let{writeFile:t}=await import("fs/promises");await t(e,n)}}}var Z=(e)=>new R(e);export{Z as pdf,O as SIZES,R as PDF};
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "podpdf",
3
+ "version": "1.0.0",
4
+ "description": "Ultra-fast, zero-dependency PDF generation library. 8KB minified, 5x faster than jsPDF.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "bun build src/index.ts --outdir dist --target node --minify && bun run build:types",
20
+ "build:types": "tsc --emitDeclarationOnly",
21
+ "dev": "bun --watch src/index.ts",
22
+ "test": "bun test/test.ts",
23
+ "example": "bun example.ts",
24
+ "samples": "bun samples/tables.ts && bun samples/invoice.ts && bun samples/report.ts"
25
+ },
26
+ "keywords": [
27
+ "pdf",
28
+ "pdf-generation",
29
+ "pdf-generator",
30
+ "document",
31
+ "invoice",
32
+ "report",
33
+ "table",
34
+ "typescript",
35
+ "zero-dependency",
36
+ "fast",
37
+ "lightweight",
38
+ "minimal"
39
+ ],
40
+ "author": "",
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/user/podpdf"
45
+ },
46
+ "homepage": "https://github.com/user/podpdf#readme",
47
+ "bugs": {
48
+ "url": "https://github.com/user/podpdf/issues"
49
+ },
50
+ "engines": {
51
+ "node": ">=18"
52
+ },
53
+ "devDependencies": {
54
+ "@types/bun": "^1.3.5",
55
+ "typescript": "^5.7.2"
56
+ }
57
+ }