@nemu-ai/pdf 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nemu
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,678 @@
1
+ # @nemu-ai/pdf
2
+
3
+ A modern PDF generation library with vector-based positioning and theming support.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @nemu-ai/pdf
9
+ # or
10
+ npm install @nemu-ai/pdf
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import { Document, Theme, create_theme, vector } from "@nemu-ai/pdf";
17
+
18
+ const pdf = new Document({
19
+ page_size: "A4",
20
+ margin: 50,
21
+ });
22
+
23
+ // Load custom fonts
24
+ await pdf.load_font("Satoshi", "./fonts/satoshi.ttf");
25
+
26
+ // Create theme
27
+ const theme = create_theme("custom", {
28
+ colors: {
29
+ primary: "#1a365d",
30
+ text: "#1f2937",
31
+ background: "#ffffff",
32
+ },
33
+ });
34
+
35
+ pdf.set_theme(theme);
36
+
37
+ // Create page
38
+ const page = pdf.create_page(theme);
39
+
40
+ // Add text with vector position
41
+ page.text({
42
+ content: "Hello World",
43
+ position: vector(50, 100),
44
+ style: {
45
+ font_family: "Satoshi",
46
+ font_size: 24,
47
+ color: theme.get_color("primary"),
48
+ },
49
+ });
50
+
51
+ // Draw shapes with vectors
52
+ page.rect(
53
+ vector(50, 150), // position
54
+ vector(200, 100), // size
55
+ { fill_color: "#f8fafc" }
56
+ );
57
+
58
+ // Save PDF
59
+ await pdf.build("output.pdf");
60
+ ```
61
+
62
+ ## Complete API Reference
63
+
64
+ ### Document
65
+
66
+ The main PDF document class.
67
+
68
+ ```typescript
69
+ const doc = new Document(options?: DocumentOptions)
70
+ ```
71
+
72
+ **DocumentOptions:**
73
+ ```typescript
74
+ {
75
+ page_size?: "A4" | "Letter" | "Legal" | "Custom"
76
+ custom_dimensions?: { width: number; height: number }
77
+ margin?: number | { top: number; right: number; bottom: number; left: number }
78
+ }
79
+ ```
80
+
81
+ **Methods:**
82
+
83
+ #### `load_font(name: string, path: string): Promise<void>`
84
+ Load a custom TrueType font.
85
+ ```typescript
86
+ await doc.load_font("Satoshi", "./fonts/satoshi.ttf");
87
+ await doc.load_font("Roboto", "./fonts/roboto.ttf");
88
+ ```
89
+
90
+ #### `load_font_sync(name: string, path: string): void`
91
+ Synchronous version of load_font.
92
+ ```typescript
93
+ doc.load_font_sync("Satoshi", "./fonts/satoshi.ttf");
94
+ ```
95
+
96
+ #### `load_image(name: string, path: string): Promise<void>`
97
+ Load an image for later use.
98
+ ```typescript
99
+ await doc.load_image("logo", "./images/logo.png");
100
+ await doc.load_image("photo", "./images/photo.jpg");
101
+ ```
102
+
103
+ #### `set_theme(theme: Theme): void`
104
+ Set the default theme for all pages.
105
+ ```typescript
106
+ const theme = create_theme("my-theme", { colors: { primary: "#000" } });
107
+ doc.set_theme(theme);
108
+ ```
109
+
110
+ #### `create_page(theme?: Theme): Page`
111
+ Create a new page. Uses default theme if none provided.
112
+ ```typescript
113
+ const page1 = doc.create_page(); // Uses default theme
114
+ const page2 = doc.create_page(customTheme); // Uses specific theme
115
+ ```
116
+
117
+ #### `build(path: string): Promise<void>`
118
+ Save the PDF to a file.
119
+ ```typescript
120
+ await doc.build("output.pdf");
121
+ await doc.build("./documents/report.pdf");
122
+ ```
123
+
124
+ ---
125
+
126
+ ### Page
127
+
128
+ Represents a single page in the PDF.
129
+
130
+ #### `text(options: TextOptions): void`
131
+ Add text to the page.
132
+
133
+ **TextOptions:**
134
+ ```typescript
135
+ {
136
+ content: string // Required: text content
137
+ position?: Vector // Optional: position vector
138
+ style?: StyleProperties // Optional: inline styles
139
+ classname?: string // Optional: theme classname
140
+ width?: number // Optional: max width for wrapping
141
+ }
142
+ ```
143
+
144
+ **Examples:**
145
+ ```typescript
146
+ // Simple text at cursor position
147
+ page.text({ content: "Hello World" });
148
+
149
+ // Text at specific position
150
+ page.text({
151
+ content: "Title",
152
+ position: vector(50, 100)
153
+ });
154
+
155
+ // Text with styling
156
+ page.text({
157
+ content: "Styled Text",
158
+ position: vector(50, 150),
159
+ style: {
160
+ font_family: "Satoshi",
161
+ font_size: 24,
162
+ color: "#1a365d",
163
+ font_weight: "bold"
164
+ }
165
+ });
166
+
167
+ // Text using classname from theme
168
+ page.text({
169
+ content: "Body Text",
170
+ position: vector(50, 200),
171
+ classname: "body-text",
172
+ width: 400 // Wraps text to 400px width
173
+ });
174
+ ```
175
+
176
+ #### `header(options: HeaderOptions): void`
177
+ Add a header (same as text but with larger default font).
178
+
179
+ **HeaderOptions:**
180
+ ```typescript
181
+ {
182
+ text: string // Required: header text
183
+ position?: Vector // Optional: position vector
184
+ style?: StyleProperties // Optional: inline styles
185
+ classname?: string // Optional: theme classname
186
+ width?: number // Optional: max width
187
+ }
188
+ ```
189
+
190
+ **Examples:**
191
+ ```typescript
192
+ page.header({ text: "Document Title" });
193
+
194
+ page.header({
195
+ text: "Chapter 1",
196
+ position: vector(50, 50),
197
+ style: { font_size: 32, color: theme.get_color("primary") }
198
+ });
199
+ ```
200
+
201
+ #### `rect(position: Vector, size: Vector, style?: ShapeStyle): void`
202
+ Draw a rectangle.
203
+
204
+ **Examples:**
205
+ ```typescript
206
+ // Simple rectangle
207
+ page.rect(vector(50, 100), vector(200, 100));
208
+
209
+ // Filled rectangle
210
+ page.rect(
211
+ vector(50, 100),
212
+ vector(200, 100),
213
+ { fill_color: "#f8fafc" }
214
+ );
215
+
216
+ // Bordered rectangle
217
+ page.rect(
218
+ vector(50, 100),
219
+ vector(200, 100),
220
+ {
221
+ stroke_color: "#e2e8f0",
222
+ stroke_width: 2
223
+ }
224
+ );
225
+
226
+ // Rounded rectangle
227
+ page.rect(
228
+ vector(50, 100),
229
+ vector(200, 100),
230
+ {
231
+ fill_color: "#f8fafc",
232
+ border_radius: 10
233
+ }
234
+ );
235
+
236
+ // Semi-transparent
237
+ page.rect(
238
+ vector(50, 100),
239
+ vector(200, 100),
240
+ {
241
+ fill_color: "#000000",
242
+ opacity: 0.1
243
+ }
244
+ );
245
+ ```
246
+
247
+ #### `image(options: ImageOptions): void`
248
+ Add an image.
249
+
250
+ **ImageOptions:**
251
+ ```typescript
252
+ {
253
+ name: string // Required: image name (from load_image)
254
+ position?: Vector // Optional: position vector
255
+ width?: number // Optional: width in pixels
256
+ height?: number // Optional: height in pixels
257
+ }
258
+ ```
259
+
260
+ **Examples:**
261
+ ```typescript
262
+ await doc.load_image("logo", "./logo.png");
263
+
264
+ // Image at position
265
+ page.image({
266
+ name: "logo",
267
+ position: vector(50, 100),
268
+ width: 200
269
+ });
270
+
271
+ // Image at cursor
272
+ page.image({ name: "logo", width: 150 });
273
+ ```
274
+
275
+ #### `container(position?: Vector): Container`
276
+ Create a container for grouping elements.
277
+
278
+ **Examples:**
279
+ ```typescript
280
+ // Container at position
281
+ const container = page.container(vector(50, 100));
282
+ container.rect(vector(0, 0), vector(200, 100));
283
+ container.text({
284
+ content: "Inside container",
285
+ position: vector(10, 10)
286
+ });
287
+
288
+ // Container at cursor
289
+ const container = page.container();
290
+ ```
291
+
292
+ #### `move_down(amount: number = 1): void`
293
+ Move cursor down by amount (1 unit = line height).
294
+
295
+ **Examples:**
296
+ ```typescript
297
+ page.move_down(); // Move 1 line
298
+ page.move_down(2); // Move 2 lines
299
+ page.move_down(0.5); // Move half line
300
+ ```
301
+
302
+ #### Cursor Methods
303
+
304
+ ```typescript
305
+ // Get cursor position
306
+ const cursor: Vector = page.get_cursor();
307
+ const x: number = page.get_cursor_x();
308
+ const y: number = page.get_cursor_y();
309
+
310
+ // Set cursor position
311
+ page.set_cursor(vector(100, 200));
312
+ page.set_cursor({ x: 100, y: 200 });
313
+ ```
314
+
315
+ #### Dimension Methods
316
+
317
+ ```typescript
318
+ const width: number = page.get_width(); // Page width
319
+ const height: number = page.get_height(); // Page height
320
+ const contentWidth: number = page.get_content_width(); // Width minus margins
321
+ const contentHeight: number = page.get_content_height(); // Height minus margins
322
+ const marginLeft: number = page.get_margin_left();
323
+ const marginRight: number = page.get_margin_right();
324
+ const marginTop: number = page.get_margin_top();
325
+ const marginBottom: number = page.get_margin_bottom();
326
+ ```
327
+
328
+ #### Text Measurement
329
+
330
+ ```typescript
331
+ const measurement = page.measure_text("Hello World", {
332
+ font_size: 14,
333
+ line_height: 1.5
334
+ });
335
+ // Returns: { width: number, height: number }
336
+ ```
337
+
338
+ ---
339
+
340
+ ### Container
341
+
342
+ Groups elements with relative positioning.
343
+
344
+ #### `rect(position: Vector, size: Vector, style?: ShapeStyle): void`
345
+ Draw rectangle relative to container position.
346
+
347
+ ```typescript
348
+ const container = page.container(vector(50, 50));
349
+ container.rect(vector(0, 0), vector(200, 100)); // At container origin
350
+ container.rect(vector(10, 10), vector(180, 80)); // Offset 10px
351
+ ```
352
+
353
+ #### `text(options: TextOptions): void`
354
+ Add text relative to container position.
355
+
356
+ ```typescript
357
+ const container = page.container(vector(50, 50));
358
+ container.text({
359
+ content: "Title",
360
+ position: vector(10, 10) // 10px from container edge
361
+ });
362
+ ```
363
+
364
+ #### `header(options: HeaderOptions): void`
365
+ Add header relative to container position.
366
+
367
+ ```typescript
368
+ container.header({
369
+ text: "Card Title",
370
+ position: vector(10, 10)
371
+ });
372
+ ```
373
+
374
+ #### `move_down(amount: number = 1): void`
375
+ Move cursor within container.
376
+
377
+ ---
378
+
379
+ ### Vector
380
+
381
+ 2D vector for positioning.
382
+
383
+ #### Creation
384
+
385
+ ```typescript
386
+ import { vector, Vector } from "@nemu-ai/pdf";
387
+
388
+ // From coordinates
389
+ const v1 = vector(100, 200);
390
+
391
+ // From object
392
+ const v2 = vector({ x: 100, y: 200 });
393
+ const v3 = new Vector(100, 200);
394
+ const v4 = new Vector({ x: 100, y: 200 });
395
+
396
+ // Copy
397
+ const v5 = v1.copy();
398
+
399
+ // Static factory methods
400
+ const zero = Vector.zero(); // (0, 0)
401
+ const one = Vector.one(); // (1, 1)
402
+ const up = Vector.up(); // (0, -1)
403
+ const down = Vector.down(); // (0, 1)
404
+ const left = Vector.left(); // (-1, 0)
405
+ const right = Vector.right(); // (1, 0)
406
+ ```
407
+
408
+ #### Operations
409
+
410
+ ```typescript
411
+ const a = vector(10, 20);
412
+ const b = vector(5, 5);
413
+
414
+ // Addition
415
+ const c = a.add(b); // (15, 25)
416
+
417
+ // Subtraction
418
+ const d = a.subtract(b); // (5, 15)
419
+
420
+ // Multiplication
421
+ const e = a.multiply(2); // (20, 40)
422
+
423
+ // Division
424
+ const f = a.divide(2); // (5, 10)
425
+
426
+ // Distance
427
+ const dist = a.dist_to(b); // Distance between vectors
428
+
429
+ // Magnitude
430
+ const mag = a.magnitude(); // Vector length
431
+
432
+ // Normalization
433
+ const norm = a.normalize(); // Unit vector
434
+
435
+ // Rotation
436
+ const rotated = a.rotate(Math.PI / 2); // Rotate 90 degrees
437
+
438
+ // Lerp (interpolation)
439
+ const between = a.lerp(b, 0.5); // Halfway between a and b
440
+ ```
441
+
442
+ #### Common Patterns
443
+
444
+ ```typescript
445
+ // Center a rectangle
446
+ const pageCenter = vector(
447
+ page.get_content_width() / 2,
448
+ page.get_content_height() / 2
449
+ );
450
+ const boxSize = vector(200, 100);
451
+ const boxPos = pageCenter.subtract(boxSize.divide(2));
452
+ page.rect(boxPos, boxSize);
453
+
454
+ // Grid layout
455
+ const start = vector(50, 50);
456
+ const gap = vector(10, 10);
457
+ const itemSize = vector(100, 50);
458
+
459
+ for (let row = 0; row < 3; row++) {
460
+ for (let col = 0; col < 3; col++) {
461
+ const pos = start.add(
462
+ vector(col, row).multiply(itemSize.add(gap))
463
+ );
464
+ page.rect(pos, itemSize);
465
+ }
466
+ }
467
+ ```
468
+
469
+ ---
470
+
471
+ ### Theme
472
+
473
+ Centralized styling system.
474
+
475
+ #### `create_theme(name: string, definition: ThemeDefinition): Theme`
476
+
477
+ ```typescript
478
+ const theme = create_theme("my-theme", {
479
+ colors: {
480
+ primary: "#1a365d",
481
+ secondary: "#2563eb",
482
+ accent: "#d97706",
483
+ background: "#ffffff",
484
+ text: "#1f2937",
485
+ text_secondary: "#4b5563",
486
+ border: "#e2e8f0",
487
+ // Any custom colors
488
+ brand_pink: "#ff69b4",
489
+ custom_color: "#123456"
490
+ },
491
+ fonts: {
492
+ heading: "Satoshi",
493
+ body: "Helvetica",
494
+ mono: "Courier"
495
+ },
496
+ spacing: {
497
+ xs: 5,
498
+ sm: 10,
499
+ md: 20,
500
+ lg: 30,
501
+ xl: 50
502
+ },
503
+ border_radius: {
504
+ sm: 5,
505
+ md: 10,
506
+ lg: 20
507
+ }
508
+ });
509
+ ```
510
+
511
+ #### `define_classname(name: string, style: StyleProperties): void`
512
+ #### `define_classname(definitions: Array<string | StyleProperties>): void`
513
+
514
+ ```typescript
515
+ // Single definition
516
+ theme.define_classname("title", {
517
+ font_family: "Satoshi",
518
+ font_size: 32,
519
+ color: theme.get_color("primary"),
520
+ font_weight: "bold"
521
+ });
522
+
523
+ // Multiple definitions
524
+ theme.define_classname([
525
+ "title", {
526
+ font_family: "Satoshi",
527
+ font_size: 32,
528
+ color: theme.get_color("primary"),
529
+ font_weight: "bold"
530
+ },
531
+ "subtitle", {
532
+ font_family: "Satoshi",
533
+ font_size: 24,
534
+ color: theme.get_color("text_secondary")
535
+ },
536
+ "body", {
537
+ font_family: "Helvetica",
538
+ font_size: 14,
539
+ color: theme.get_color("text"),
540
+ line_height: 1.6
541
+ },
542
+ "caption", {
543
+ font_family: "Helvetica",
544
+ font_size: 12,
545
+ color: theme.get_color("text_secondary"),
546
+ font_style: "italic"
547
+ }
548
+ ]);
549
+ ```
550
+
551
+ #### `get_color(key: string): string`
552
+
553
+ ```typescript
554
+ const primary = theme.get_color("primary");
555
+ const custom = theme.get_color("brand_pink");
556
+ ```
557
+
558
+ ---
559
+
560
+ ### StyleProperties
561
+
562
+ All style options available.
563
+
564
+ ```typescript
565
+ interface StyleProperties {
566
+ // Font
567
+ font_family?: string
568
+ font_size?: number
569
+ font_weight?: "normal" | "bold" | number
570
+ font_style?: "normal" | "italic" | "oblique"
571
+
572
+ // Text
573
+ color?: string
574
+ text_align?: "left" | "center" | "right" | "justify"
575
+ text_transform?: "none" | "capitalize" | "uppercase" | "lowercase"
576
+ text_decoration?: "none" | "underline" | "line-through" | "overline"
577
+ line_height?: number
578
+ letter_spacing?: number
579
+
580
+ // Spacing
581
+ margin?: number | string
582
+ margin_top?: number | string
583
+ margin_right?: number | string
584
+ margin_bottom?: number | string
585
+ margin_left?: number | string
586
+ padding?: number | string
587
+ padding_top?: number | string
588
+ padding_right?: number | string
589
+ padding_bottom?: number | string
590
+ padding_left?: number | string
591
+
592
+ // Layout
593
+ width?: number | string
594
+ height?: number | string
595
+ display?: "block" | "inline" | "none"
596
+ position?: "static" | "relative" | "absolute"
597
+ top?: number | string
598
+ right?: number | string
599
+ bottom?: number | string
600
+ left?: number | string
601
+
602
+ // Appearance
603
+ background_color?: string
604
+ border?: string
605
+ opacity?: number
606
+
607
+ // Advanced
608
+ overflow?: "visible" | "hidden" | "scroll" | "auto"
609
+ white_space?: "normal" | "nowrap" | "pre" | "pre-wrap" | "pre-line"
610
+ word_break?: "normal" | "break-all" | "keep-all"
611
+ text_overflow?: "clip" | "ellipsis"
612
+ vertical_align?: "baseline" | "top" | "middle" | "bottom" | string
613
+ }
614
+ ```
615
+
616
+ ---
617
+
618
+ ### ShapeStyle
619
+
620
+ Styling for shapes.
621
+
622
+ ```typescript
623
+ interface ShapeStyle {
624
+ fill_color?: string // Fill color (hex)
625
+ stroke_color?: string // Border color (hex)
626
+ stroke_width?: number // Border width
627
+ opacity?: number // Opacity 0-1
628
+ border_radius?: number // Corner radius
629
+ }
630
+ ```
631
+
632
+ ## Complete Example
633
+
634
+ ```typescript
635
+ import { Document, create_theme, vector } from "@nemu-ai/pdf";
636
+
637
+ const pdf = new Document({ page_size: "A4", margin: 50 });
638
+
639
+ const theme = create_theme("doc", {
640
+ colors: {
641
+ primary: "#1a365d",
642
+ background: "#f8fafc",
643
+ border: "#e2e8f0"
644
+ }
645
+ });
646
+
647
+ pdf.set_theme(theme);
648
+ const page = pdf.create_page(theme);
649
+
650
+ // Header
651
+ page.header({
652
+ text: "Document Title",
653
+ position: vector(50, 50),
654
+ style: { font_size: 24, color: theme.get_color("primary") }
655
+ });
656
+
657
+ // Card with content
658
+ const card_pos = vector(50, 100);
659
+ const card_size = vector(400, 200);
660
+
661
+ page.rect(card_pos, card_size, {
662
+ fill_color: theme.get_color("background"),
663
+ stroke_color: theme.get_color("border"),
664
+ stroke_width: 1
665
+ });
666
+
667
+ page.text({
668
+ content: "Card content here",
669
+ position: card_pos.add(vector(20, 20)),
670
+ style: { font_size: 14 }
671
+ });
672
+
673
+ await pdf.build("output.pdf");
674
+ ```
675
+
676
+ ## License
677
+
678
+ MIT
@@ -0,0 +1,11 @@
1
+ export declare const PAGE_DIMENSIONS: Record<string, {
2
+ width: number;
3
+ height: number;
4
+ }>;
5
+ export declare const DEFAULT_MARGIN = 72;
6
+ export declare const DEFAULT_FONT_SIZE = 12;
7
+ export declare const DEFAULT_LINE_HEIGHT = 1.2;
8
+ export declare const DEFAULT_FONT_FAMILY = "Helvetica";
9
+ export declare const PDF_VERSION = "1.4";
10
+ export declare const PDF_CREATOR = "PDF Generator";
11
+ export declare const STANDARD_FONTS: string[];