pdf-oxide 0.3.37 → 0.3.39

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.
Files changed (65) hide show
  1. package/lib/builders/document-builder.d.ts +350 -0
  2. package/lib/builders/document-builder.js +724 -0
  3. package/lib/builders/index.d.ts +4 -2
  4. package/lib/builders/index.js +4 -2
  5. package/lib/builders/pdf-builder.d.ts +2 -0
  6. package/lib/builders/pdf-builder.js +12 -0
  7. package/lib/builders/streaming-table.d.ts +49 -0
  8. package/lib/builders/streaming-table.js +110 -0
  9. package/lib/document-editor.d.ts +122 -0
  10. package/lib/document-editor.js +313 -0
  11. package/lib/errors.js +3 -4
  12. package/lib/form-field-manager.js +3 -1
  13. package/lib/index.d.ts +41 -7
  14. package/lib/index.js +266 -90
  15. package/lib/managers/accessibility-manager.js +19 -8
  16. package/lib/managers/annotation-manager.js +9 -9
  17. package/lib/managers/barcode-manager.js +18 -7
  18. package/lib/managers/batch-manager.js +2 -5
  19. package/lib/managers/cache-manager.js +1 -3
  20. package/lib/managers/compliance-manager.js +58 -19
  21. package/lib/managers/document-utility-manager.js +6 -6
  22. package/lib/managers/dom-pdf-creator.js +9 -9
  23. package/lib/managers/enterprise-manager.js +4 -1
  24. package/lib/managers/extended-managers.js +8 -1
  25. package/lib/managers/extraction-manager.js +7 -2
  26. package/lib/managers/final-utilities.d.ts +3 -3
  27. package/lib/managers/final-utilities.js +9 -4
  28. package/lib/managers/hybrid-ml-advanced.js +22 -6
  29. package/lib/managers/index.d.ts +22 -22
  30. package/lib/managers/index.js +23 -23
  31. package/lib/managers/layer-manager.js +20 -21
  32. package/lib/managers/ocr-manager.d.ts +2 -2
  33. package/lib/managers/ocr-manager.js +7 -7
  34. package/lib/managers/optimization-manager.js +24 -4
  35. package/lib/managers/page-manager.js +5 -6
  36. package/lib/managers/pattern-detection.d.ts +1 -1
  37. package/lib/managers/pattern-detection.js +4 -6
  38. package/lib/managers/search-manager.js +3 -3
  39. package/lib/managers/signature-manager.d.ts +14 -0
  40. package/lib/managers/signature-manager.js +185 -40
  41. package/lib/managers/streams.js +8 -2
  42. package/lib/managers/xfa-manager.js +69 -19
  43. package/lib/native-loader.d.ts +7 -0
  44. package/lib/native-loader.js +62 -0
  45. package/lib/native.d.ts +16 -0
  46. package/lib/native.js +69 -0
  47. package/lib/pdf-creator-manager.js +4 -1
  48. package/lib/result-accessors-manager.js +3 -1
  49. package/lib/timestamp.d.ts +54 -0
  50. package/lib/timestamp.js +115 -0
  51. package/lib/tsa-client.d.ts +44 -0
  52. package/lib/tsa-client.js +67 -0
  53. package/lib/types/common.d.ts +80 -1
  54. package/lib/types/common.js +14 -1
  55. package/lib/types/index.d.ts +1 -1
  56. package/lib/types/index.js +1 -1
  57. package/lib/types/manager-types.js +4 -2
  58. package/lib/workers/index.d.ts +1 -1
  59. package/lib/workers/pool.js +2 -4
  60. package/package.json +17 -11
  61. package/prebuilds/darwin-arm64/pdf_oxide.node +0 -0
  62. package/prebuilds/darwin-x64/pdf_oxide.node +0 -0
  63. package/prebuilds/linux-arm64/pdf_oxide.node +0 -0
  64. package/prebuilds/linux-x64/pdf_oxide.node +0 -0
  65. package/prebuilds/win32-x64/pdf_oxide.node +0 -0
@@ -0,0 +1,724 @@
1
+ /**
2
+ * Fluent document builder — the programmatic multi-page construction API
3
+ * exposed through the C FFI.
4
+ *
5
+ * Mirrors the Python / WASM / C# / Go equivalents. The same handle-lifetime
6
+ * contract applies: terminal methods (`build`, `save`, `saveEncrypted`,
7
+ * `toBytesEncrypted`) CONSUME the builder, and only one `PageBuilder` may
8
+ * be open at a time.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { DocumentBuilder, EmbeddedFont } from 'pdf-oxide';
13
+ *
14
+ * const font = EmbeddedFont.fromFile('DejaVuSans.ttf');
15
+ * const builder = DocumentBuilder.create()
16
+ * .title('Hello')
17
+ * .registerEmbeddedFont('DejaVu', font); // consumes `font`
18
+ * builder.a4Page()
19
+ * .font('DejaVu', 12)
20
+ * .at(72, 720).text('Привет, мир!')
21
+ * .at(72, 700).text('Καλημέρα κόσμε')
22
+ * .done();
23
+ * const bytes = builder.build(); // consumes the builder
24
+ * ```
25
+ */
26
+ // Load the addon via the shared prebuild-aware loader — resolves
27
+ // against `prebuilds/<triple>/pdf_oxide.node` in the published
28
+ // package and the in-tree `build/Release/` output in dev mode.
29
+ import { loadNative } from '../native.js';
30
+ import { Align, } from '../types/common.js';
31
+ import { StreamingTable } from './streaming-table.js';
32
+ const native = loadNative();
33
+ /**
34
+ * TTF/OTF font handle registerable with {@link DocumentBuilder}. Single-use:
35
+ * after `registerEmbeddedFont` the native handle is moved into the builder
36
+ * and this object becomes disposed.
37
+ */
38
+ export class EmbeddedFont {
39
+ constructor(handle) {
40
+ this._consumed = false;
41
+ this._handle = handle;
42
+ }
43
+ /** Load a TTF / OTF font from disk. */
44
+ static fromFile(path) {
45
+ return new EmbeddedFont(native.embeddedFontFromFile(path));
46
+ }
47
+ /** Load a font from a byte buffer; pass `name` to override the PostScript name. */
48
+ static fromBytes(data, name) {
49
+ return new EmbeddedFont(native.embeddedFontFromBytes(data, name));
50
+ }
51
+ /** @internal — used by {@link DocumentBuilder.registerEmbeddedFont} */
52
+ get handle() {
53
+ if (this._consumed) {
54
+ throw new Error('EmbeddedFont already consumed');
55
+ }
56
+ return this._handle;
57
+ }
58
+ /** @internal — called by the builder after the FFI transfers ownership. */
59
+ markConsumed() {
60
+ this._consumed = true;
61
+ this._handle = null;
62
+ }
63
+ /** Release the native font handle if it hasn't been consumed. */
64
+ close() {
65
+ if (!this._consumed && this._handle != null) {
66
+ native.embeddedFontFree(this._handle);
67
+ this._consumed = true;
68
+ this._handle = null;
69
+ }
70
+ }
71
+ /** Symbol.dispose support for `using` declarations. */
72
+ [Symbol.dispose]() {
73
+ this.close();
74
+ }
75
+ }
76
+ /**
77
+ * Fluent top-level API for multi-page PDF construction.
78
+ * Use {@link DocumentBuilder.create} to start a new builder.
79
+ */
80
+ export class DocumentBuilder {
81
+ constructor(handle) {
82
+ this._consumed = false;
83
+ this._openPage = null;
84
+ this._handle = handle;
85
+ }
86
+ /** Create a fresh empty builder. */
87
+ static create() {
88
+ return new DocumentBuilder(native.documentBuilderCreate());
89
+ }
90
+ /** @internal — used by PageBuilder.done */
91
+ clearOpenPage() {
92
+ this._openPage = null;
93
+ }
94
+ checkUsable() {
95
+ if (this._consumed || this._handle == null) {
96
+ throw new Error('DocumentBuilder has been consumed');
97
+ }
98
+ if (this._openPage != null) {
99
+ throw new Error('A PageBuilder is already open; call done() first.');
100
+ }
101
+ return this._handle;
102
+ }
103
+ /** Set the document title. */
104
+ title(title) {
105
+ native.documentBuilderSetTitle(this.checkUsable(), title);
106
+ return this;
107
+ }
108
+ /** Set the document author. */
109
+ author(author) {
110
+ native.documentBuilderSetAuthor(this.checkUsable(), author);
111
+ return this;
112
+ }
113
+ /** Set the document subject. */
114
+ subject(subject) {
115
+ native.documentBuilderSetSubject(this.checkUsable(), subject);
116
+ return this;
117
+ }
118
+ /** Set the document keywords (comma-separated per PDF convention). */
119
+ keywords(keywords) {
120
+ native.documentBuilderSetKeywords(this.checkUsable(), keywords);
121
+ return this;
122
+ }
123
+ /** Set the creator application name. */
124
+ creator(creator) {
125
+ native.documentBuilderSetCreator(this.checkUsable(), creator);
126
+ return this;
127
+ }
128
+ /** Run a JavaScript script when the document is opened (/OpenAction). */
129
+ onOpen(script) {
130
+ native.documentBuilderOnOpen(this.checkUsable(), script);
131
+ return this;
132
+ }
133
+ /**
134
+ * Enable PDF/UA-1 tagged PDF mode.
135
+ *
136
+ * When enabled, `build()` emits `/MarkInfo`, `/StructTreeRoot`, `/Lang`, and
137
+ * `/ViewerPreferences` in the catalog. Opt-in — no effect unless called.
138
+ * Bundle F-1/F-2.
139
+ */
140
+ taggedPdfUa1() {
141
+ native.documentBuilderTaggedPdfUa1(this.checkUsable());
142
+ return this;
143
+ }
144
+ /**
145
+ * Set the document's natural language tag, e.g. `"en-US"`.
146
+ *
147
+ * Emitted as `/Lang` in the catalog when `taggedPdfUa1()` is set. Bundle F-2.
148
+ */
149
+ language(lang) {
150
+ native.documentBuilderLanguage(this.checkUsable(), lang);
151
+ return this;
152
+ }
153
+ /**
154
+ * Add a role-map entry: custom structure type → standard PDF structure type.
155
+ *
156
+ * Emitted in `/RoleMap` inside the StructTreeRoot when `taggedPdfUa1()` is
157
+ * set. Multiple calls accumulate entries. Bundle F-4.
158
+ */
159
+ roleMap(custom, standard) {
160
+ native.documentBuilderRoleMap(this.checkUsable(), custom, standard);
161
+ return this;
162
+ }
163
+ /**
164
+ * Register a TTF / OTF font under `name`. CONSUMES `font` on success —
165
+ * do not call `close()` on the font afterwards.
166
+ */
167
+ registerEmbeddedFont(name, font) {
168
+ const builderHandle = this.checkUsable();
169
+ // font.handle getter throws if already consumed, so validation is done.
170
+ native.documentBuilderRegisterEmbeddedFont(builderHandle, name, font.handle);
171
+ font.markConsumed();
172
+ return this;
173
+ }
174
+ /** Start a new A4 page. Only one page may be outstanding per builder. */
175
+ a4Page() {
176
+ const h = this.checkUsable();
177
+ const pageHandle = native.documentBuilderA4Page(h);
178
+ this._openPage = new PageBuilder(this, pageHandle);
179
+ return this._openPage;
180
+ }
181
+ /** Start a new US Letter page. */
182
+ letterPage() {
183
+ const h = this.checkUsable();
184
+ const pageHandle = native.documentBuilderLetterPage(h);
185
+ this._openPage = new PageBuilder(this, pageHandle);
186
+ return this._openPage;
187
+ }
188
+ /** Start a page with custom dimensions in PDF points (72 pt = 1 inch). */
189
+ page(width, height) {
190
+ const h = this.checkUsable();
191
+ const pageHandle = native.documentBuilderPage(h, width, height);
192
+ this._openPage = new PageBuilder(this, pageHandle);
193
+ return this._openPage;
194
+ }
195
+ consumeHandle() {
196
+ const h = this.checkUsable();
197
+ this._consumed = true;
198
+ this._handle = null;
199
+ return h;
200
+ }
201
+ /** Build the PDF and return the bytes. CONSUMES the builder. */
202
+ build() {
203
+ const h = this.consumeHandle();
204
+ try {
205
+ return native.documentBuilderBuild(h);
206
+ }
207
+ finally {
208
+ native.documentBuilderFree(h);
209
+ }
210
+ }
211
+ /** Save the PDF to a file. CONSUMES the builder. */
212
+ save(path) {
213
+ const h = this.consumeHandle();
214
+ try {
215
+ native.documentBuilderSave(h, path);
216
+ }
217
+ finally {
218
+ native.documentBuilderFree(h);
219
+ }
220
+ }
221
+ /** Save the PDF with AES-256 encryption. CONSUMES the builder. */
222
+ saveEncrypted(path, userPassword, ownerPassword) {
223
+ const h = this.consumeHandle();
224
+ try {
225
+ native.documentBuilderSaveEncrypted(h, path, userPassword, ownerPassword);
226
+ }
227
+ finally {
228
+ native.documentBuilderFree(h);
229
+ }
230
+ }
231
+ /** Return the PDF as encrypted bytes (AES-256). CONSUMES the builder. */
232
+ toBytesEncrypted(userPassword, ownerPassword) {
233
+ const h = this.consumeHandle();
234
+ try {
235
+ return native.documentBuilderToBytesEncrypted(h, userPassword, ownerPassword);
236
+ }
237
+ finally {
238
+ native.documentBuilderFree(h);
239
+ }
240
+ }
241
+ /** Release native resources if the builder wasn't consumed. */
242
+ close() {
243
+ if (!this._consumed && this._handle != null) {
244
+ native.documentBuilderFree(this._handle);
245
+ this._consumed = true;
246
+ this._handle = null;
247
+ }
248
+ }
249
+ /** Symbol.dispose support for `using` declarations. */
250
+ [Symbol.dispose]() {
251
+ this.close();
252
+ }
253
+ }
254
+ /**
255
+ * Fluent per-page builder returned by `DocumentBuilder.a4Page` etc.
256
+ * Single-use — `done()` commits the page and invalidates this builder.
257
+ */
258
+ export class PageBuilder {
259
+ /** @internal — constructed by DocumentBuilder */
260
+ constructor(parent, handle) {
261
+ this._done = false;
262
+ this._parent = parent;
263
+ this._handle = handle;
264
+ }
265
+ h() {
266
+ if (this._done || this._handle == null) {
267
+ throw new Error('PageBuilder already committed');
268
+ }
269
+ return this._handle;
270
+ }
271
+ // --- content --------------------------------------------------------
272
+ /** Set font + size for subsequent text. */
273
+ font(name, size) {
274
+ native.pageBuilderFont(this.h(), name, size);
275
+ this._lastFontSize = size;
276
+ return this;
277
+ }
278
+ /** Move the cursor to absolute coordinates. */
279
+ at(x, y) {
280
+ native.pageBuilderAt(this.h(), x, y);
281
+ return this;
282
+ }
283
+ /** Emit a line of text at the current cursor position. */
284
+ text(text) {
285
+ native.pageBuilderText(this.h(), text);
286
+ return this;
287
+ }
288
+ /** Emit a heading (level 1-6). */
289
+ heading(level, text) {
290
+ native.pageBuilderHeading(this.h(), level, text);
291
+ return this;
292
+ }
293
+ /** Emit a paragraph with automatic line wrapping. */
294
+ paragraph(text) {
295
+ native.pageBuilderParagraph(this.h(), text);
296
+ return this;
297
+ }
298
+ /** Advance the cursor by the given number of points. */
299
+ space(points) {
300
+ native.pageBuilderSpace(this.h(), points);
301
+ return this;
302
+ }
303
+ /** Draw a horizontal rule across the page. */
304
+ horizontalRule() {
305
+ native.pageBuilderHorizontalRule(this.h());
306
+ return this;
307
+ }
308
+ // --- annotations (Phase 3) -----------------------------------------
309
+ /** Attach a URL link to the previous text element. */
310
+ linkUrl(url) {
311
+ native.pageBuilderLinkUrl(this.h(), url);
312
+ return this;
313
+ }
314
+ /** Link the previous text to an internal page (0-based). */
315
+ linkPage(pageIndex) {
316
+ native.pageBuilderLinkPage(this.h(), pageIndex);
317
+ return this;
318
+ }
319
+ /** Link the previous text to a named destination. */
320
+ linkNamed(destination) {
321
+ native.pageBuilderLinkNamed(this.h(), destination);
322
+ return this;
323
+ }
324
+ /** Link the previous text to a JavaScript action. */
325
+ linkJavascript(script) {
326
+ native.pageBuilderLinkJavascript(this.h(), script);
327
+ return this;
328
+ }
329
+ /** Run JavaScript when this page is opened (/AA /O). */
330
+ onOpen(script) {
331
+ native.pageBuilderOnOpen(this.h(), script);
332
+ return this;
333
+ }
334
+ /** Run JavaScript when this page is closed (/AA /C). */
335
+ onClose(script) {
336
+ native.pageBuilderOnClose(this.h(), script);
337
+ return this;
338
+ }
339
+ /** Set a keystroke JS action (/AA /K) on the last form field. */
340
+ fieldKeystroke(script) {
341
+ native.pageBuilderFieldKeystroke(this.h(), script);
342
+ return this;
343
+ }
344
+ /** Set a format JS action (/AA /F) on the last form field. */
345
+ fieldFormat(script) {
346
+ native.pageBuilderFieldFormat(this.h(), script);
347
+ return this;
348
+ }
349
+ /** Set a validate JS action (/AA /V) on the last form field. */
350
+ fieldValidate(script) {
351
+ native.pageBuilderFieldValidate(this.h(), script);
352
+ return this;
353
+ }
354
+ /** Set a calculate JS action (/AA /C) on the last form field. */
355
+ fieldCalculate(script) {
356
+ native.pageBuilderFieldCalculate(this.h(), script);
357
+ return this;
358
+ }
359
+ /** Highlight the previous text with an RGB colour (channels 0-1). */
360
+ highlight(r, g, b) {
361
+ native.pageBuilderHighlight(this.h(), r, g, b);
362
+ return this;
363
+ }
364
+ /** Underline the previous text. */
365
+ underline(r, g, b) {
366
+ native.pageBuilderUnderline(this.h(), r, g, b);
367
+ return this;
368
+ }
369
+ /** Strike through the previous text. */
370
+ strikeout(r, g, b) {
371
+ native.pageBuilderStrikeout(this.h(), r, g, b);
372
+ return this;
373
+ }
374
+ /** Squiggly-underline the previous text. */
375
+ squiggly(r, g, b) {
376
+ native.pageBuilderSquiggly(this.h(), r, g, b);
377
+ return this;
378
+ }
379
+ /** Attach a sticky-note annotation to the previous text. */
380
+ stickyNote(text) {
381
+ native.pageBuilderStickyNote(this.h(), text);
382
+ return this;
383
+ }
384
+ /** Place a sticky-note at an absolute position. */
385
+ stickyNoteAt(x, y, text) {
386
+ native.pageBuilderStickyNoteAt(this.h(), x, y, text);
387
+ return this;
388
+ }
389
+ /** Apply a text watermark to the page. */
390
+ watermark(text) {
391
+ native.pageBuilderWatermark(this.h(), text);
392
+ return this;
393
+ }
394
+ /** Apply the standard "CONFIDENTIAL" diagonal watermark. */
395
+ watermarkConfidential() {
396
+ native.pageBuilderWatermarkConfidential(this.h());
397
+ return this;
398
+ }
399
+ /** Apply the standard "DRAFT" diagonal watermark. */
400
+ watermarkDraft() {
401
+ native.pageBuilderWatermarkDraft(this.h());
402
+ return this;
403
+ }
404
+ /**
405
+ * Attach a standard stamp annotation at the cursor (150×50 default).
406
+ * `typeName` matches the PDF spec's standard stamps (Approved,
407
+ * NotApproved, Draft, Confidential, Final, Experimental, Expired,
408
+ * ForPublicRelease, NotForPublicRelease, AsIs, Sold, Departmental,
409
+ * ForComment, TopSecret) — any other name becomes a custom stamp.
410
+ */
411
+ stamp(typeName) {
412
+ native.pageBuilderStamp(this.h(), typeName);
413
+ return this;
414
+ }
415
+ /** Place a free-flowing text annotation inside the rectangle (x, y, w, h). */
416
+ freeText(x, y, w, h, text) {
417
+ native.pageBuilderFreetext(this.h(), x, y, w, h, text);
418
+ return this;
419
+ }
420
+ // --- Form-field widgets --------------------------------------------
421
+ /**
422
+ * Add a single-line text form field at the rectangle (x, y, w, h).
423
+ * `name` is the unique field identifier used for form submission;
424
+ * `defaultValue` is the initial text (pass undefined for blank).
425
+ */
426
+ textField(name, x, y, w, h, defaultValue) {
427
+ native.pageBuilderTextField(this.h(), name, x, y, w, h, defaultValue);
428
+ return this;
429
+ }
430
+ /**
431
+ * Add a checkbox form field at the rectangle (x, y, w, h).
432
+ * `checked` sets the initial state.
433
+ */
434
+ checkbox(name, x, y, w, h, checked = false) {
435
+ native.pageBuilderCheckbox(this.h(), name, x, y, w, h, checked);
436
+ return this;
437
+ }
438
+ /**
439
+ * Add a dropdown combo-box form field. `options` are the user-
440
+ * visible choices; `selected` picks the initial value.
441
+ */
442
+ comboBox(name, x, y, w, h, options, selected) {
443
+ native.pageBuilderComboBox(this.h(), name, x, y, w, h, options, selected);
444
+ return this;
445
+ }
446
+ /**
447
+ * Add a radio-button group. `buttons` is an array of
448
+ * `[exportValue, x, y, w, h]` tuples — one per option. `selected`
449
+ * picks the initial value by export value.
450
+ */
451
+ radioGroup(name, buttons, selected) {
452
+ native.pageBuilderRadioGroup(this.h(), name, buttons, selected);
453
+ return this;
454
+ }
455
+ /** Add a clickable push button with a visible caption. */
456
+ pushButton(name, x, y, w, h, caption) {
457
+ native.pageBuilderPushButton(this.h(), name, x, y, w, h, caption);
458
+ return this;
459
+ }
460
+ /** Add an unsigned signature placeholder field (/FT /Sig) at the given bounds. */
461
+ signatureField(name, x, y, w, h) {
462
+ native.pageBuilderSignatureField(this.h(), name, x, y, w, h);
463
+ return this;
464
+ }
465
+ /**
466
+ * Add a footnote: inline `refMark` emitted at the cursor position, and
467
+ * `noteText` placed near the page bottom with a separator artifact line.
468
+ */
469
+ footnote(refMark, noteText) {
470
+ native.pageBuilderFootnote(this.h(), refMark, noteText);
471
+ return this;
472
+ }
473
+ /**
474
+ * Lay out `text` as balanced multi-column flow.
475
+ * `columnCount` columns separated by `gapPt` points.
476
+ * Paragraphs in `text` are delimited by `"\n\n"`.
477
+ */
478
+ columns(columnCount, gapPt, text) {
479
+ native.pageBuilderColumns(this.h(), columnCount, gapPt, text);
480
+ return this;
481
+ }
482
+ /**
483
+ * Emit `text` inline at the current cursor position without advancing
484
+ * to a new line. The cursor advances horizontally so the next `inline`
485
+ * call follows on the same line.
486
+ */
487
+ inline(text) {
488
+ native.pageBuilderInline(this.h(), text);
489
+ return this;
490
+ }
491
+ /** Emit `text` inline in bold weight. */
492
+ inlineBold(text) {
493
+ native.pageBuilderInlineBold(this.h(), text);
494
+ return this;
495
+ }
496
+ /** Emit `text` inline in italic style. */
497
+ inlineItalic(text) {
498
+ native.pageBuilderInlineItalic(this.h(), text);
499
+ return this;
500
+ }
501
+ /** Emit `text` inline in an RGB colour (channels 0–1). */
502
+ inlineColor(r, g, b, text) {
503
+ native.pageBuilderInlineColor(this.h(), r, g, b, text);
504
+ return this;
505
+ }
506
+ /** Advance the cursor to the start of the next line. */
507
+ newline() {
508
+ native.pageBuilderNewline(this.h());
509
+ return this;
510
+ }
511
+ // --- Barcode / QR-code placement ------------------------------------
512
+ /**
513
+ * Place a 1-D barcode image on the page at `(x, y, w, h)`.
514
+ * `barcodeType`: 0=Code128 1=Code39 2=EAN13 3=EAN8 4=UPCA 5=ITF
515
+ * 6=Code93 7=Codabar.
516
+ */
517
+ barcode1d(barcodeType, data, x, y, w, h) {
518
+ native.pageBuilderBarcode1d(this.h(), barcodeType, data, x, y, w, h);
519
+ return this;
520
+ }
521
+ /** Place a QR-code image on the page (square: `size × size` pt). */
522
+ barcodeQr(data, x, y, size) {
523
+ native.pageBuilderBarcodeQr(this.h(), data, x, y, size);
524
+ return this;
525
+ }
526
+ /**
527
+ * Embed an image with an accessibility alt text (PDF/UA-1 §Figure).
528
+ * `bytes` must contain raw JPEG/PNG/WebP image data.
529
+ */
530
+ imageWithAlt(bytes, x, y, w, h, altText) {
531
+ native.pageBuilderImageWithAlt(this.h(), bytes, x, y, w, h, altText);
532
+ return this;
533
+ }
534
+ /**
535
+ * Embed a decorative image as an /Artifact (no alt text, PDF/UA-1 §Artifact).
536
+ * `bytes` must contain raw JPEG/PNG/WebP image data.
537
+ */
538
+ imageArtifact(bytes, x, y, w, h) {
539
+ native.pageBuilderImageArtifact(this.h(), bytes, x, y, w, h);
540
+ return this;
541
+ }
542
+ // --- Low-level graphics primitives ---------------------------------
543
+ /** Draw a stroked rectangle outline (1pt black). */
544
+ rect(x, y, w, h) {
545
+ native.pageBuilderRect(this.h(), x, y, w, h);
546
+ return this;
547
+ }
548
+ /** Draw a filled rectangle in RGB colour (channels 0–1). */
549
+ filledRect(x, y, w, h, r, g, b) {
550
+ native.pageBuilderFilledRect(this.h(), x, y, w, h, r, g, b);
551
+ return this;
552
+ }
553
+ /** Draw a line from `(x1, y1)` to `(x2, y2)` with 1pt black stroke. */
554
+ line(x1, y1, x2, y2) {
555
+ native.pageBuilderLine(this.h(), x1, y1, x2, y2);
556
+ return this;
557
+ }
558
+ // --- v0.3.39 primitives (#393) -------------------------------------
559
+ /**
560
+ * Draw a stroked rectangle outline with caller-supplied width + RGB
561
+ * colour (channels 0–1). Underlies the Table surface.
562
+ */
563
+ strokeRect(x, y, w, h, style) {
564
+ const width = style?.width ?? 1;
565
+ const [r, g, b] = style?.color ?? [0, 0, 0];
566
+ native.pageBuilderStrokeRect(this.h(), x, y, w, h, width, r, g, b);
567
+ return this;
568
+ }
569
+ /**
570
+ * Draw a straight line with caller-supplied width + RGB colour.
571
+ */
572
+ strokeLine(x1, y1, x2, y2, style) {
573
+ const width = style?.width ?? 1;
574
+ const [r, g, b] = style?.color ?? [0, 0, 0];
575
+ native.pageBuilderStrokeLine(this.h(), x1, y1, x2, y2, width, r, g, b);
576
+ return this;
577
+ }
578
+ /**
579
+ * Place wrapped text inside the rectangle (x, y, w, h) with the
580
+ * given horizontal alignment. Uses the current font + size. Text
581
+ * that does not fit is clipped to the rectangle height.
582
+ */
583
+ textInRect(x, y, w, h, text, align = Align.Left) {
584
+ native.pageBuilderTextInRect(this.h(), x, y, w, h, text, align);
585
+ return this;
586
+ }
587
+ /**
588
+ * Start a new page with the *same* dimensions as the current one.
589
+ * Text config (font + size) carries over; the cursor resets to the
590
+ * top-left margin. Callers wanting header-repeat-on-break must
591
+ * re-emit the header explicitly.
592
+ */
593
+ newPageSameSize() {
594
+ native.pageBuilderNewPageSameSize(this.h());
595
+ return this;
596
+ }
597
+ /**
598
+ * Measure the width of `text` in the current font and size.
599
+ *
600
+ * Note: v0.3.39 ships a JS-side approximation (0.55em per glyph for
601
+ * ASCII / typical Latin-1 characters). A true per-glyph measurement
602
+ * FFI is pending. The result is in PDF points.
603
+ */
604
+ measure(text) {
605
+ // Retrieve current font size if the user tracked it via `font(...)`.
606
+ // We have no FFI query for the size; fall back to the 10pt default.
607
+ const size = this._lastFontSize ?? 10;
608
+ // ~0.55 em is a reasonable average for proportional fonts.
609
+ return text.length * size * 0.55;
610
+ }
611
+ /**
612
+ * Estimate the remaining vertical space on the page at the current
613
+ * cursor position. v0.3.39 returns `null` when the native cursor is
614
+ * unknown — callers should treat `null` as "unknown; assume fresh
615
+ * page". A real FFI hook is pending in a follow-up release.
616
+ */
617
+ remainingSpace() {
618
+ return null;
619
+ }
620
+ /**
621
+ * Emit a buffered table at the current cursor. All rows are
622
+ * marshalled into a single FFI call — memory scales with the row
623
+ * count. For million-row streams use {@link streamingTable}.
624
+ */
625
+ table(spec) {
626
+ const columns = spec.columns;
627
+ if (!columns || columns.length === 0) {
628
+ throw new Error('table spec must contain at least one column');
629
+ }
630
+ const widths = columns.map((c) => c.width);
631
+ const aligns = columns.map((c) => (c.align ?? Align.Left));
632
+ const hasHeader = spec.hasHeader !== false;
633
+ const rows = spec.rows ?? [];
634
+ const cells = [];
635
+ if (hasHeader) {
636
+ for (const c of columns)
637
+ cells.push(c.header);
638
+ }
639
+ const bodyRowCount = rows.length;
640
+ for (const row of rows) {
641
+ if (row.length !== columns.length) {
642
+ throw new Error(`row width ${row.length} does not match column count ${columns.length}`);
643
+ }
644
+ for (const cell of row)
645
+ cells.push(cell ?? null);
646
+ }
647
+ const totalRows = (hasHeader ? 1 : 0) + bodyRowCount;
648
+ native.pageBuilderTable(this.h(), widths, aligns, totalRows, cells, hasHeader);
649
+ return this;
650
+ }
651
+ /**
652
+ * Begin a streaming table. Uses the native row-at-a-time FFI
653
+ * (`pdf_page_builder_streaming_table_begin_v2`). Pass a `mode` in
654
+ * `config` to control column-sizing strategy (default: fixed widths).
655
+ */
656
+ streamingTable(config) {
657
+ return new StreamingTable(this, config);
658
+ }
659
+ /** @internal — open streaming table FFI handle on this page. */
660
+ _streamingTableBeginV2(headers, widths, aligns, repeatHeader, mode, maxRowspan) {
661
+ let modeInt = 0;
662
+ let sampleRows = 20;
663
+ let minW = 0;
664
+ let maxW = 9999;
665
+ if (mode?.kind === 'sample') {
666
+ modeInt = 1;
667
+ if (mode.sampleRows != null)
668
+ sampleRows = mode.sampleRows;
669
+ if (mode.minColWidthPt != null)
670
+ minW = mode.minColWidthPt;
671
+ if (mode.maxColWidthPt != null)
672
+ maxW = mode.maxColWidthPt;
673
+ }
674
+ native.pageBuilderStreamingTableBeginV2(this.h(), headers, widths, aligns, repeatHeader, modeInt, sampleRows, minW, maxW, maxRowspan);
675
+ }
676
+ /** @internal — push one row into the open streaming table (all rowspan=1). */
677
+ _streamingTablePushRow(cells) {
678
+ native.pageBuilderStreamingTablePushRow(this.h(), cells);
679
+ }
680
+ /** @internal — push one row with per-cell rowspan values. */
681
+ _streamingTablePushRowV2(cells) {
682
+ native.pageBuilderStreamingTablePushRowV2(this.h(), cells);
683
+ }
684
+ /** @internal — close the open streaming table. */
685
+ _streamingTableFinish() {
686
+ native.pageBuilderStreamingTableFinish(this.h());
687
+ }
688
+ /**
689
+ * Commit the page's buffered operations to the parent builder and
690
+ * return the parent for chaining. After `done()` this PageBuilder is
691
+ * invalid.
692
+ */
693
+ done() {
694
+ if (this._done) {
695
+ throw new Error('PageBuilder already committed');
696
+ }
697
+ native.pageBuilderDone(this._handle);
698
+ this._done = true;
699
+ this._handle = null;
700
+ this._parent.clearOpenPage();
701
+ return this._parent;
702
+ }
703
+ /**
704
+ * Drop an uncommitted page. Use only for error recovery — the parent's
705
+ * open-page slot is released so the next `a4Page()` etc. succeeds.
706
+ */
707
+ close() {
708
+ if (!this._done && this._handle != null) {
709
+ native.pageBuilderFree(this._handle);
710
+ this._parent.clearOpenPage();
711
+ this._done = true;
712
+ this._handle = null;
713
+ }
714
+ }
715
+ /** Symbol.dispose support for `using`. */
716
+ [Symbol.dispose]() {
717
+ this.close();
718
+ }
719
+ }
720
+ export { StreamingTable } from './streaming-table.js';
721
+ // Re-export the v0.3.39 table surface so users can `import { Align,
722
+ // StreamingTable } from 'pdf-oxide'` without reaching into ./types or
723
+ // ./builders/streaming-table.
724
+ export { Align, };