@lpdfio/lpdf 0.29.0 → 0.31.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/dist/engine.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PdfEngine = exports.LpdfRenderError = void 0;
4
+ const node_crypto_1 = require("node:crypto");
4
5
  const node_fs_1 = require("node:fs");
6
+ const _shared_1 = require("./_shared");
5
7
  // require() path is relative to the compiled output at dist/engine.js.
6
8
  // In the published package, wasm-pack artifacts live in dist/wasm/.
7
9
  // In the monorepo, `npm run build` copies them there from <root>/dist/node/.
@@ -28,13 +30,18 @@ class LpdfRenderError extends Error {
28
30
  }
29
31
  }
30
32
  exports.LpdfRenderError = LpdfRenderError;
33
+ /** Node's secure generator, for the entropy of AES-256. */
34
+ function secureRandomBytes(count) {
35
+ return new Uint8Array((0, node_crypto_1.randomBytes)(count));
36
+ }
31
37
  class PdfEngine {
32
38
  constructor() {
33
39
  this._licenseKey = '';
34
40
  this._fonts = new Map();
35
41
  this._images = new Map();
42
+ this._files = new Map();
43
+ this._markdownStyles = new Map();
36
44
  this._disposed = false;
37
- this._encrypt = null;
38
45
  }
39
46
  /**
40
47
  * Set the license key. Returns `this` for chaining.
@@ -78,21 +85,21 @@ class PdfEngine {
78
85
  return this;
79
86
  }
80
87
  /**
81
- * Configure RC4-128 encryption for all subsequent `render` calls.
82
- * Returns `this` for chaining.
88
+ * Register the bytes of a file the document carries, for the `<file>` of its `<attachments>` whose `ref`, or with
89
+ * no ref whose name, is `name`. The PDF carries them as they are. Call before `render`. Returns `this` for chaining.
83
90
  */
84
- setEncryption(options) {
91
+ loadFile(name, bytes) {
85
92
  this._throwIfDisposed();
86
- this._encrypt = options;
93
+ this._files.set(name, bytes);
87
94
  return this;
88
95
  }
89
96
  /**
90
- * Remove any previously configured encryption.
91
- * Returns `this` for chaining.
97
+ * Register the text of a file of Markdown rules, the root of which is a `<markdown-styles>`, for the `src` that
98
+ * names it, as `<markdown-styles src="…">` does. Call before `render`. Returns `this` for chaining.
92
99
  */
93
- clearEncryption() {
100
+ loadMarkdownStyles(src, text) {
94
101
  this._throwIfDisposed();
95
- this._encrypt = null;
102
+ this._markdownStyles.set(src, text);
96
103
  return this;
97
104
  }
98
105
  /**
@@ -103,6 +110,44 @@ class PdfEngine {
103
110
  this._disposed = true;
104
111
  }
105
112
  [Symbol.dispose]() { this.dispose(); }
113
+ /**
114
+ * Hand the engine the files of Markdown rules that a document names: the ones loaded here, and the ones read from
115
+ * their `src`. A file that can't be read is left for the engine to report, naming the `src`.
116
+ */
117
+ _loadMarkdownStyles(engine, srcs) {
118
+ const files = new Map(this._markdownStyles);
119
+ for (const src of srcs) {
120
+ if (!files.has(src)) {
121
+ try {
122
+ files.set(src, (0, node_fs_1.readFileSync)(src, 'utf8'));
123
+ }
124
+ catch { /* not found; the engine says so */ }
125
+ }
126
+ }
127
+ for (const [src, text] of files) {
128
+ engine.load_markdown_styles(src, text);
129
+ }
130
+ }
131
+ /**
132
+ * Hand the engine the files the document carries: the ones loaded here, and the ones read from the `src` of a file
133
+ * that wasn't. A `src` that can't be read is an error that names the file and the path.
134
+ */
135
+ _loadFiles(engine, srcs) {
136
+ const files = new Map(this._files);
137
+ for (const [key, src] of srcs) {
138
+ if (files.has(key))
139
+ continue;
140
+ try {
141
+ files.set(key, (0, node_fs_1.readFileSync)(src));
142
+ }
143
+ catch (e) {
144
+ throw new LpdfRenderError(`file '${key}' of <attachments>: cannot read its src '${src}': ${e instanceof Error ? e.message : String(e)}`);
145
+ }
146
+ }
147
+ for (const [key, bytes] of files) {
148
+ engine.load_file(key, bytes);
149
+ }
150
+ }
106
151
  _throwIfDisposed() {
107
152
  if (this._disposed)
108
153
  throw new Error('PdfEngine has been disposed.');
@@ -136,6 +181,8 @@ class PdfEngine {
136
181
  for (const [name, bytes] of this._images) {
137
182
  engine.load_image(name, bytes);
138
183
  }
184
+ this._loadMarkdownStyles(engine, markdownStylesSrcs(xml));
185
+ this._loadFiles(engine, extractAssetSrcs(xml, 'file'));
139
186
  // Auto-load images declared via <image src="…"> that weren't pre-loaded.
140
187
  for (const [key, src] of extractAssetSrcs(xml, 'image')) {
141
188
  if (!this._images.has(key)) {
@@ -145,12 +192,8 @@ class PdfEngine {
145
192
  catch { /* skip unresolvable image */ }
146
193
  }
147
194
  }
148
- if (this._encrypt) {
149
- const permsJson = JSON.stringify(this._encrypt.permissions ?? {});
150
- engine.set_encryption(this._encrypt.userPassword, this._encrypt.ownerPassword, permsJson);
151
- }
152
195
  const dataJson = callOptions.data != null ? JSON.stringify(callOptions.data) : null;
153
- pdf = engine.render_pdf(xml, dataJson);
196
+ pdf = engine.render_pdf(xml, dataJson, ...(0, _shared_1.encryptionArguments)(callOptions, secureRandomBytes));
154
197
  }
155
198
  else {
156
199
  // JSON (Kit tree) path — pass JSON directly to render_tree_pdf. Fonts and images the
@@ -180,15 +223,15 @@ class PdfEngine {
180
223
  for (const [name, bytes] of allImages) {
181
224
  engine.load_image(name, bytes);
182
225
  }
183
- if (this._encrypt) {
184
- const permsJson = JSON.stringify(this._encrypt.permissions ?? {});
185
- engine.set_encryption(this._encrypt.userPassword, this._encrypt.ownerPassword, permsJson);
186
- }
187
- pdf = engine.render_tree_pdf(json);
226
+ this._loadMarkdownStyles(engine, markdownStylesSrcsOfDocument(input));
227
+ this._loadFiles(engine, fileSrcsOfDocument(input));
228
+ pdf = engine.render_tree_pdf(json, ...(0, _shared_1.encryptionArguments)(callOptions, secureRandomBytes));
188
229
  }
189
230
  }
190
231
  catch (e) {
191
232
  engine.free();
233
+ if (e instanceof LpdfRenderError)
234
+ throw e;
192
235
  const msg = e instanceof Error ? e.message : String(e);
193
236
  throw new LpdfRenderError(msg);
194
237
  }
@@ -198,21 +241,80 @@ class PdfEngine {
198
241
  }
199
242
  exports.PdfEngine = PdfEngine;
200
243
  // ── Helpers ───────────────────────────────────────────────────────────────────
201
- /** Extract `ref??name → src` pairs from `<font>` or `<image>` tags in XML. */
244
+ // Comments and CDATA sections, whose text is not markup: a <file> shown in a Markdown block is text, not a file.
245
+ const XML_NOT_MARKUP_RE = /<!--[\s\S]*?-->|<!\[CDATA\[[\s\S]*?\]\]>/g;
246
+ const XML_ATTRIBUTE_RE = /([\w:.-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
247
+ const XML_ENTITY_RE = /&(?:(amp|lt|gt|quot|apos)|#(\d+)|#x([0-9a-fA-F]+));/g;
248
+ const XML_ENTITIES = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'" };
249
+ /** An attribute value as the XML means it, with its entities and character references decoded. */
250
+ function decodeXml(value) {
251
+ return value.replace(XML_ENTITY_RE, (_, name, decimal, hex) =>
252
+ // XML_ENTITY_RE matches exactly one of its three groups, so without a name or a decimal there is a hex.
253
+ name ? XML_ENTITIES[name] : String.fromCodePoint(parseInt(decimal ?? hex, decimal ? 10 : 16)));
254
+ }
255
+ /**
256
+ * The attributes of each `<tag>` element in XML, read as the engine reads them: comments and CDATA sections are left
257
+ * out, a quoted value may hold a `>`, either quote may be used, and the values are decoded.
258
+ */
259
+ function xmlElements(xml, tag) {
260
+ const elements = [];
261
+ // Attribute values may contain '>', so quoted runs are matched whole.
262
+ const re = new RegExp(`<${tag}\\b((?:[^>"']|"[^"]*"|'[^']*')*)>`, 'g');
263
+ for (const match of xml.replace(XML_NOT_MARKUP_RE, '').matchAll(re)) {
264
+ const attributes = new Map();
265
+ for (const attribute of match[1].matchAll(XML_ATTRIBUTE_RE)) {
266
+ attributes.set(attribute[1], decodeXml(attribute[2] ?? attribute[3]));
267
+ }
268
+ elements.push(attributes);
269
+ }
270
+ return elements;
271
+ }
272
+ /** The `ref ?? name → src` pairs of the `<font>`, `<image>` or `<file>` elements in XML. */
202
273
  function extractAssetSrcs(xml, tag) {
203
274
  const result = new Map();
204
- const re = tag === 'font' ? /<font\s[^>]*>/g : /<image\s[^>]*>/g;
205
- for (const match of xml.matchAll(re)) {
206
- const t = match[0];
207
- const name = /\bname="([^"]*)"/.exec(t)?.[1];
208
- const ref = /\bref="([^"]*)"/.exec(t)?.[1];
209
- const src = /\bsrc="([^"]*)"/.exec(t)?.[1];
210
- const key = ref ?? name;
275
+ for (const attributes of xmlElements(xml, tag)) {
276
+ const key = attributes.get('ref') ?? attributes.get('name');
277
+ const src = attributes.get('src');
211
278
  if (key && src)
212
279
  result.set(key, src);
213
280
  }
214
281
  return result;
215
282
  }
283
+ /** The `src` of each `<markdown-styles>` in XML. */
284
+ function markdownStylesSrcs(xml) {
285
+ const srcs = [];
286
+ for (const attributes of xmlElements(xml, 'markdown-styles')) {
287
+ const src = attributes.get('src');
288
+ if (src)
289
+ srcs.push(src);
290
+ }
291
+ return srcs;
292
+ }
293
+ /** The `src` of each `markdown-styles` a document holds, in the markdown of any of its sections. */
294
+ function markdownStylesSrcsOfDocument(document) {
295
+ const srcs = [];
296
+ for (const section of document.nodes) {
297
+ for (const child of section.nodes) {
298
+ if (child.type !== 'markdown')
299
+ continue;
300
+ for (const part of child.nodes) {
301
+ const src = part.type === 'markdown-styles' ? part.attrs['src'] : undefined;
302
+ if (typeof src === 'string' && src)
303
+ srcs.push(src);
304
+ }
305
+ }
306
+ }
307
+ return srcs;
308
+ }
309
+ /** The `ref ?? name → src` pairs of the files a document carries in `attrs.attachments`. */
310
+ function fileSrcsOfDocument(document) {
311
+ const result = new Map();
312
+ for (const { name, ref, src } of document.attrs['attachments'] ?? []) {
313
+ if (src)
314
+ result.set(ref ?? name, src);
315
+ }
316
+ return result;
317
+ }
216
318
  /** The `ref ?? name → src` pairs of the fonts or images a document declares in `attrs.assets`. */
217
319
  function assetSrcsOfDocument(document, kind) {
218
320
  const result = new Map();
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  export { PdfEngine, LpdfRenderError } from './engine';
2
- export type { EncryptPermissions, EncryptOptions } from './engine';
3
2
  export type { RenderOptions, EngineOptions } from './_shared';
4
- export { FieldType, Pin, Orientation, SectionType, FootnoteCounter, BuiltinFont, PageScope } from './constants';
5
- export type { PdfDocument, LpdfSectionNode, LpdfSectionChild, LpdfLayoutBlock, DocumentTokens, GradientToken, GradientStop, DocumentAssets, DocumentMeta, SectionAttr, DocumentAttr, } from './kit';
6
- export type { ColumnAttr, RowAttr, GridAttr, FrameAttr, LinkAttr, TableAttr, TheadAttr, TrAttr, TdAttr, TextAttr, SpanAttr, DividerAttr, ImgAttr, ShapeAttr, BarcodeAttr, RegionAttr, FieldAttr, BookmarkAttr, LayerAttr, RectAttr, CircleAttr, EllipseAttr, LineAttr, PathAttr, TextAtAttr, ImgAtAttr, BarcodeAtAttr, FontAttr, ImageAttr, LayoutAttr, } from './attrs';
7
- export type { LpdfNode, LpdfContainerNode, LpdfTextNode, LpdfSpanNode, LpdfFnNode, LpdfDividerNode, LpdfImgNode, LpdfShapeNode, LpdfBarcodeNode, LpdfTableNode, LpdfTheadNode, LpdfTrNode, LpdfTdNode, LpdfRegionNode, LpdfFieldNode, LpdfBookmarkNode, } from './layout';
3
+ export { FieldType, Pin, Orientation, SectionType, FootnoteCounter, Cipher, Permission, FileRelationship, BuiltinFont, PageScope, } from './constants';
4
+ export type { PdfDocument, LpdfSectionNode, LpdfSectionChild, LpdfLayoutBlock, DocumentTokens, TokenSlot, TokenRow, DocumentAssets, DocumentMeta, DocumentOutput, SectionAttr, DocumentAttr, } from './kit';
5
+ export type { ColumnAttr, RowAttr, GridAttr, ListAttr, LiAttr, FrameAttr, LinkAttr, TableAttr, TheadAttr, TrAttr, TdAttr, TextAttr, CodeAttr, SpanAttr, CodeSpanAttr, DividerAttr, ImgAttr, ShapeAttr, BarcodeAttr, RegionAttr, FieldAttr, BookmarkAttr, LayerAttr, RectAttr, CircleAttr, EllipseAttr, LineAttr, PathAttr, TextAtAttr, ImgAtAttr, BarcodeAtAttr, FontAttr, ImageAttr, FileAttr, LayoutAttr, GradientToken, GradientStop, MarkdownAttr, MarkdownStylesAttr, MarkdownH1Attr, MarkdownH2Attr, MarkdownH3Attr, MarkdownH4Attr, MarkdownH5Attr, MarkdownH6Attr, MarkdownPAttr, MarkdownCodeAttr, MarkdownQuoteAttr, MarkdownLiAttr, MarkdownAAttr, MarkdownStrongAttr, MarkdownEmAttr, MarkdownDelAttr, MarkdownTableAttr, MarkdownThAttr, MarkdownTdAttr, } from './attrs';
6
+ export type { LpdfNode, LpdfContainerNode, LpdfListNode, LpdfLiNode, LpdfTextNode, LpdfCodeNode, LpdfSpanNode, LpdfCodeSpanNode, LpdfFnNode, LpdfDividerNode, LpdfImgNode, LpdfShapeNode, LpdfBarcodeNode, LpdfTableNode, LpdfTheadNode, LpdfTrNode, LpdfTdNode, LpdfRegionNode, LpdfFieldNode, LpdfBookmarkNode, } from './layout';
7
+ export { LMarkdown } from './markdown';
8
+ export type { LpdfMarkdownNode, LpdfMarkdownStylesNode, LpdfMarkdownContentNode, LpdfMarkdownRuleNode, } from './markdown';
8
9
  export { CanvasTransform } from './canvas';
9
10
  export type { LpdfCanvasLayerNode, LpdfCanvasPrimitiveNode, LpdfCanvasRectNode, LpdfCanvasLineNode, LpdfCanvasEllipseNode, LpdfCanvasCircleNode, LpdfCanvasPathNode, LpdfCanvasTextAtNode, LpdfCanvasImgAtNode, LpdfCanvasBarcodeAtNode, } from './canvas';
10
11
  export { L, NoAttr } from './L';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.NoAttr = exports.L = exports.CanvasTransform = exports.PageScope = exports.BuiltinFont = exports.FootnoteCounter = exports.SectionType = exports.Orientation = exports.Pin = exports.FieldType = exports.LpdfRenderError = exports.PdfEngine = void 0;
3
+ exports.NoAttr = exports.L = exports.CanvasTransform = exports.LMarkdown = exports.PageScope = exports.BuiltinFont = exports.FileRelationship = exports.Permission = exports.Cipher = exports.FootnoteCounter = exports.SectionType = exports.Orientation = exports.Pin = exports.FieldType = exports.LpdfRenderError = exports.PdfEngine = void 0;
4
4
  // ── Engine ────────────────────────────────────────────────────────────────────
5
5
  var engine_1 = require("./engine");
6
6
  Object.defineProperty(exports, "PdfEngine", { enumerable: true, get: function () { return engine_1.PdfEngine; } });
@@ -12,8 +12,14 @@ Object.defineProperty(exports, "Pin", { enumerable: true, get: function () { ret
12
12
  Object.defineProperty(exports, "Orientation", { enumerable: true, get: function () { return constants_1.Orientation; } });
13
13
  Object.defineProperty(exports, "SectionType", { enumerable: true, get: function () { return constants_1.SectionType; } });
14
14
  Object.defineProperty(exports, "FootnoteCounter", { enumerable: true, get: function () { return constants_1.FootnoteCounter; } });
15
+ Object.defineProperty(exports, "Cipher", { enumerable: true, get: function () { return constants_1.Cipher; } });
16
+ Object.defineProperty(exports, "Permission", { enumerable: true, get: function () { return constants_1.Permission; } });
17
+ Object.defineProperty(exports, "FileRelationship", { enumerable: true, get: function () { return constants_1.FileRelationship; } });
15
18
  Object.defineProperty(exports, "BuiltinFont", { enumerable: true, get: function () { return constants_1.BuiltinFont; } });
16
19
  Object.defineProperty(exports, "PageScope", { enumerable: true, get: function () { return constants_1.PageScope; } });
20
+ // ── Markdown ──────────────────────────────────────────────────────────────────
21
+ var markdown_1 = require("./markdown");
22
+ Object.defineProperty(exports, "LMarkdown", { enumerable: true, get: function () { return markdown_1.LMarkdown; } });
17
23
  // ── Layer types ───────────────────────────────────────────────────────────────
18
24
  var canvas_1 = require("./canvas");
19
25
  Object.defineProperty(exports, "CanvasTransform", { enumerable: true, get: function () { return canvas_1.CanvasTransform; } });
package/dist/kit.d.ts CHANGED
@@ -21,24 +21,13 @@
21
21
  */
22
22
  import type { LpdfBookmarkNode, LpdfNode } from './layout';
23
23
  import type { LpdfCanvasLayerNode } from './canvas';
24
- import type { FontAttr, ImageAttr, LayoutAttr } from './attrs';
25
- /** One color of a gradient: `color` is hex or a color token, at an `offset` from `'0%'` to `'100%'`. */
26
- export interface GradientStop {
27
- offset: string;
28
- color: string;
29
- }
30
- /**
31
- * A gradient of the tokens, which a background or a fill uses as `gradient:name`. Give `from` and `to` for two
32
- * colors, or `stops` for more. `angle` is the direction of a linear gradient in degrees: 0 runs top to bottom
33
- * and 90 left to right. A radial gradient runs from the center of the box out to its farthest corner.
34
- */
35
- export interface GradientToken {
36
- type: 'linear' | 'radial';
37
- from?: string;
38
- to?: string;
39
- angle?: number | string;
40
- stops?: GradientStop[];
41
- }
24
+ import type { LpdfMarkdownNode } from './markdown';
25
+ import type { FileAttr, FontAttr, GradientToken, ImageAttr, LayoutAttr } from './attrs';
26
+ import { type AttrValue } from './_shared';
27
+ /** A slot of a token row, from the smallest to the largest. */
28
+ export type TokenSlot = 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl';
29
+ /** A token row: a value for each slot it sets. */
30
+ export type TokenRow<T> = Partial<Record<TokenSlot, T>>;
42
31
  export interface DocumentTokens {
43
32
  colors?: Record<string, string>;
44
33
  gradients?: Record<string, GradientToken>;
@@ -48,15 +37,16 @@ export interface DocumentTokens {
48
37
  * starting with a letter, and is neither a slot nor `fill` or `full`.
49
38
  */
50
39
  aliases?: Record<string, string>;
51
- space?: Record<string, string>;
52
- grid?: Record<string, string>;
53
- border?: Record<string, string>;
54
- radius?: Record<string, string>;
55
- width?: Record<string, string>;
40
+ space?: TokenRow<string>;
41
+ grid?: TokenRow<string>;
42
+ border?: TokenRow<string>;
43
+ radius?: TokenRow<string>;
44
+ width?: TokenRow<string>;
56
45
  /** The heights `height` names: `{ xs: '16pt', s: '24pt', m: '32pt', l: '48pt', xl: '64pt', xxl: '96pt' }` by default. */
57
- height?: Record<string, string>;
58
- textSize?: Record<string, string>;
59
- lineHeight?: Record<string, string>;
46
+ height?: TokenRow<string>;
47
+ textSize?: TokenRow<string>;
48
+ /** The factors `lineHeight` names, each a number above 0: `{ xs: 1, s: 1.1, m: 1.2, l: 1.4, xl: 1.6, xxl: 2 }` by default. */
49
+ lineHeight?: TokenRow<number>;
60
50
  }
61
51
  /**
62
52
  * The fonts and images the document declares, as the `assets` element of the XML does. A font or image
@@ -73,12 +63,32 @@ export interface DocumentMeta {
73
63
  keywords?: string;
74
64
  creator?: string;
75
65
  }
66
+ /**
67
+ * How the PDF file is written, as the `output` element of the XML is: whether it is encrypted, and what a reader
68
+ * may do with it, and the standard it meets. The passwords are not here: they come with the render call.
69
+ */
70
+ export interface DocumentOutput {
71
+ /** The cipher the PDF is encrypted with, `aes-256` or `rc4-128` (`Cipher`). Without it the PDF is not encrypted. */
72
+ encrypt?: string;
73
+ /**
74
+ * What a reader may do without the owner password, separated by spaces (`Permission`): `all`, or any of
75
+ * `print`, `modify`, `copy`, `annotate`, `fill-forms`, `accessibility`, `assemble`. A permission comes only from
76
+ * naming it: without `allow`, or with `''`, nothing is allowed, and `all` allows everything. It needs `encrypt`.
77
+ */
78
+ allow?: string;
79
+ /**
80
+ * Writes the PDF as PDF/A, the standard for files kept for the long term: `2b`, or `2u` when its text must also
81
+ * be searchable. The render needs `createdOn`, and every text a font of `assets`. What PDF/A cannot hold is an
82
+ * error that says what to change: a built-in font, a CMYK color, a form field, a barcode's digits, `encrypt`.
83
+ */
84
+ pdfa?: string;
85
+ }
76
86
  export interface SectionAttr {
77
87
  size?: string;
78
88
  orientation?: string;
79
89
  background?: string;
80
90
  title?: string;
81
- debug?: string;
91
+ debug?: boolean;
82
92
  type?: string;
83
93
  tocIndentStep?: string;
84
94
  footnoteCounter?: string;
@@ -91,25 +101,31 @@ export interface DocumentAttr {
91
101
  assets?: DocumentAssets;
92
102
  tokens?: DocumentTokens;
93
103
  meta?: DocumentMeta;
94
- debug?: string;
104
+ output?: DocumentOutput;
105
+ /**
106
+ * Files the PDF carries inside it, as the `attachments` element of the XML does: a reader lists them in its
107
+ * attachments panel. Each file's bytes are read from its `src`, or loaded with `loadFile` under its `ref` or name.
108
+ */
109
+ attachments?: FileAttr[];
110
+ debug?: boolean;
95
111
  footnoteCounter?: string;
96
112
  }
97
113
  /** A layout block — wraps an ordered list of layout nodes. */
98
114
  export interface LpdfLayoutBlock {
99
115
  type: 'layout';
100
- attrs: Record<string, string>;
116
+ attrs: Record<string, AttrValue>;
101
117
  nodes: LpdfNode[];
102
118
  }
103
119
  export interface LpdfSectionNode {
104
120
  type: 'section';
105
- attrs: Record<string, string>;
121
+ attrs: Record<string, AttrValue>;
106
122
  nodes: LpdfSectionChild[];
107
123
  }
108
124
  /**
109
- * What a section holds: a bookmark first, then layers, then at most one layout, then layers. Layers before
110
- * the layout paint under it, and layers after it paint over it.
125
+ * What a section holds: a bookmark first, then layers, then at most one layout or one markdown, then layers.
126
+ * Layers before the layout paint under it, and layers after it paint over it.
111
127
  */
112
- export type LpdfSectionChild = LpdfBookmarkNode | LpdfCanvasLayerNode | LpdfLayoutBlock;
128
+ export type LpdfSectionChild = LpdfBookmarkNode | LpdfCanvasLayerNode | LpdfLayoutBlock | LpdfMarkdownNode;
113
129
  export interface PdfDocument {
114
130
  version: 1;
115
131
  type: 'document';
package/dist/kit.js CHANGED
@@ -35,13 +35,15 @@ function section(attrs, nodes = []) {
35
35
  };
36
36
  }
37
37
  function document(attrs, nodes = []) {
38
- const { assets, tokens, meta, ...restOpts } = attrs ?? {};
38
+ const { assets, tokens, meta, output, attachments, ...restOpts } = attrs ?? {};
39
39
  const attrsObj = {
40
40
  ...(0, _shared_1.buildAttrs)(restOpts),
41
41
  };
42
42
  if (assets !== undefined)
43
43
  attrsObj['assets'] = assets;
44
44
  if (tokens !== undefined) {
45
+ // A factor of the line-height row and a gradient's angle are numbers, which JSON can't hold as NaN or infinity.
46
+ (0, _shared_1.assertFiniteIn)('tokens', tokens);
45
47
  const { textSize, lineHeight, ...rest } = tokens;
46
48
  attrsObj['tokens'] = {
47
49
  ...rest,
@@ -51,6 +53,10 @@ function document(attrs, nodes = []) {
51
53
  }
52
54
  if (meta !== undefined)
53
55
  attrsObj['meta'] = meta;
56
+ if (output !== undefined)
57
+ attrsObj['output'] = output;
58
+ if (attachments !== undefined)
59
+ attrsObj['attachments'] = attachments;
54
60
  return {
55
61
  version: 1,
56
62
  type: 'document',
package/dist/layout.d.ts CHANGED
@@ -1,87 +1,126 @@
1
- import type { ColumnAttr, RowAttr, GridAttr, FrameAttr, LinkAttr, TableAttr, TheadAttr, TrAttr, TdAttr, TextAttr, SpanAttr, DividerAttr, ImgAttr, ShapeAttr, BarcodeAttr, FieldAttr, RegionAttr, BookmarkAttr } from './attrs';
1
+ import type { ColumnAttr, RowAttr, GridAttr, ListAttr, LiAttr, FrameAttr, LinkAttr, TableAttr, TheadAttr, TrAttr, TdAttr, TextAttr, CodeAttr, SpanAttr, CodeSpanAttr, DividerAttr, ImgAttr, ShapeAttr, BarcodeAttr, FieldAttr, RegionAttr, BookmarkAttr } from './attrs';
2
+ import { type AttrValue } from './_shared';
2
3
  export interface LpdfSpanNode {
3
4
  type: 'span';
4
- attrs: Record<string, string>;
5
+ attrs: Record<string, AttrValue>;
6
+ nodes: string[];
7
+ }
8
+ /** A run of code inside a text: its words in the code font on a tint. */
9
+ export interface LpdfCodeSpanNode {
10
+ type: 'code-span';
11
+ attrs: Record<string, AttrValue>;
5
12
  nodes: string[];
6
13
  }
7
14
  /** A footnote: its words go at the bottom of the page where its number lands. */
8
15
  export interface LpdfFnNode {
9
16
  type: 'fn';
10
- attrs: Record<string, string>;
17
+ attrs: Record<string, AttrValue>;
11
18
  nodes: (string | LpdfSpanNode)[];
12
19
  }
13
20
  export interface LpdfContainerNode {
14
21
  type: 'column' | 'row' | 'grid' | 'frame' | 'link';
15
- attrs: Record<string, string>;
22
+ attrs: Record<string, AttrValue>;
23
+ nodes: LpdfNode[];
24
+ }
25
+ /** A list of items, each marked with a bullet or a number the engine gives. */
26
+ export interface LpdfListNode {
27
+ type: 'list';
28
+ attrs: Record<string, AttrValue>;
29
+ nodes: LpdfLiNode[];
30
+ }
31
+ /** One item of a list: what a layout holds, beside the item's marker. */
32
+ export interface LpdfLiNode {
33
+ type: 'li';
34
+ attrs: Record<string, AttrValue>;
16
35
  nodes: LpdfNode[];
17
36
  }
18
37
  export interface LpdfTextNode {
19
38
  type: 'text';
20
- attrs: Record<string, string>;
21
- nodes: (string | LpdfSpanNode | LpdfFnNode)[];
39
+ attrs: Record<string, AttrValue>;
40
+ nodes: (string | LpdfSpanNode | LpdfCodeSpanNode | LpdfFnNode)[];
41
+ }
42
+ /** Code in a box. Its strings keep their spaces and lines; its spans color parts of it. */
43
+ export interface LpdfCodeNode {
44
+ type: 'code';
45
+ attrs: Record<string, AttrValue>;
46
+ nodes: (string | LpdfSpanNode)[];
22
47
  }
23
48
  export interface LpdfDividerNode {
24
49
  type: 'divider';
25
- attrs: Record<string, string>;
50
+ attrs: Record<string, AttrValue>;
26
51
  }
27
52
  export interface LpdfImgNode {
28
53
  type: 'img';
29
- attrs: Record<string, string>;
54
+ attrs: Record<string, AttrValue>;
30
55
  }
31
56
  export interface LpdfShapeNode {
32
57
  type: 'shape';
33
- attrs: Record<string, string>;
58
+ attrs: Record<string, AttrValue>;
34
59
  }
35
60
  export interface LpdfBarcodeNode {
36
61
  type: 'barcode';
37
- attrs: Record<string, string>;
62
+ attrs: Record<string, AttrValue>;
38
63
  }
39
64
  export interface LpdfTheadNode {
40
65
  type: 'thead';
41
- attrs: Record<string, string>;
66
+ attrs: Record<string, AttrValue>;
42
67
  nodes: LpdfTdNode[];
43
68
  }
44
69
  export interface LpdfTrNode {
45
70
  type: 'tr';
46
- attrs: Record<string, string>;
71
+ attrs: Record<string, AttrValue>;
47
72
  nodes: LpdfTdNode[];
48
73
  }
49
74
  export interface LpdfTdNode {
50
75
  type: 'td';
51
- attrs: Record<string, string>;
76
+ attrs: Record<string, AttrValue>;
52
77
  nodes: LpdfNode[];
53
78
  }
54
79
  export interface LpdfTableNode {
55
80
  type: 'table';
56
- attrs: Record<string, string>;
81
+ attrs: Record<string, AttrValue>;
57
82
  nodes: (LpdfTheadNode | LpdfTrNode)[];
58
83
  }
59
84
  export interface LpdfRegionNode {
60
85
  type: 'region';
61
- attrs: Record<string, string>;
86
+ attrs: Record<string, AttrValue>;
62
87
  nodes: LpdfNode[];
63
88
  }
64
89
  export interface LpdfFieldNode {
65
90
  type: 'field';
66
- attrs: Record<string, string>;
91
+ attrs: Record<string, AttrValue>;
67
92
  }
68
93
  /** An outline entry and link target that marks the node after it; it draws nothing. */
69
94
  export interface LpdfBookmarkNode {
70
95
  type: 'bookmark';
71
- attrs: Record<string, string>;
96
+ attrs: Record<string, AttrValue>;
72
97
  }
73
- export type LpdfNode = LpdfContainerNode | LpdfTextNode | LpdfDividerNode | LpdfTableNode | LpdfImgNode | LpdfShapeNode | LpdfBarcodeNode | LpdfRegionNode | LpdfFieldNode | LpdfBookmarkNode;
98
+ export type LpdfNode = LpdfContainerNode | LpdfListNode | LpdfTextNode | LpdfCodeNode | LpdfDividerNode | LpdfTableNode | LpdfImgNode | LpdfShapeNode | LpdfBarcodeNode | LpdfRegionNode | LpdfFieldNode | LpdfBookmarkNode;
74
99
  declare function column(attrs: ColumnAttr | null, nodes?: LpdfNode[]): LpdfContainerNode;
75
100
  declare function row(attrs: RowAttr | null, nodes?: LpdfNode[]): LpdfContainerNode;
76
101
  declare function grid(attrs: GridAttr | null, nodes?: LpdfNode[]): LpdfContainerNode;
77
102
  declare function frame(attrs: FrameAttr | null, nodes?: LpdfNode[]): LpdfContainerNode;
78
103
  declare function link(attrs: LinkAttr, nodes?: LpdfNode[]): LpdfContainerNode;
104
+ /**
105
+ * A list of items, which are `li` nodes. A bullet list marks each item with a shape; with `type: 'number'` the
106
+ * engine numbers them, in the `format` given, and the numbers follow the items that `data-source` makes.
107
+ */
108
+ declare function list(attrs: ListAttr | null, nodes?: LpdfLiNode[]): LpdfListNode;
109
+ /** An item of a list. Its words go in a `text`; a `list` inside it is a list under the item. */
110
+ declare function li(attrs: LiAttr | null, nodes?: LpdfNode[]): LpdfLiNode;
79
111
  declare function table(attrs: TableAttr, nodes?: (LpdfTheadNode | LpdfTrNode)[]): LpdfTableNode;
80
112
  declare function thead(attrs: TheadAttr | null, nodes?: LpdfTdNode[]): LpdfTheadNode;
81
113
  declare function tr(attrs: TrAttr | null, nodes?: LpdfTdNode[]): LpdfTrNode;
82
114
  declare function td(attrs: TdAttr | null, nodes?: LpdfNode[]): LpdfTdNode;
83
- declare function text(attrs: TextAttr | null, nodes?: (string | LpdfSpanNode | LpdfFnNode)[]): LpdfTextNode;
115
+ declare function text(attrs: TextAttr | null, nodes?: (string | LpdfSpanNode | LpdfCodeSpanNode | LpdfFnNode)[]): LpdfTextNode;
116
+ /**
117
+ * Code in a box that fills the width and grows with its lines. The strings are kept as they are written: every
118
+ * space, and a new line wherever a `\n` is. A line longer than the box wraps. The spans color parts of it.
119
+ */
120
+ declare function code(attrs: CodeAttr | null, nodes?: (string | LpdfSpanNode)[]): LpdfCodeNode;
84
121
  declare function span(attrs: SpanAttr | null, nodes?: string[]): LpdfSpanNode;
122
+ /** A run of code in the nodes of a text: its words in the code font on a tint. */
123
+ declare function codeSpan(attrs: CodeSpanAttr | null, nodes?: string[]): LpdfCodeSpanNode;
85
124
  /**
86
125
  * A footnote, in the nodes of a text. The engine numbers it and puts a small raised number at its place in the
87
126
  * text; its words go at the bottom of the page where that number lands. It has no attributes: pass `NoAttr`.
@@ -104,10 +143,14 @@ export declare const LpdfLayout: Readonly<{
104
143
  column: typeof column;
105
144
  row: typeof row;
106
145
  grid: typeof grid;
146
+ list: typeof list;
147
+ li: typeof li;
107
148
  frame: typeof frame;
108
149
  link: typeof link;
109
150
  text: typeof text;
151
+ code: typeof code;
110
152
  span: typeof span;
153
+ codeSpan: typeof codeSpan;
111
154
  fn: typeof fn;
112
155
  divider: typeof divider;
113
156
  img: typeof img;
package/dist/layout.js CHANGED
@@ -22,6 +22,17 @@ function frame(attrs, nodes = []) {
22
22
  function link(attrs, nodes = []) {
23
23
  return makeContainer('link', attrs, nodes);
24
24
  }
25
+ /**
26
+ * A list of items, which are `li` nodes. A bullet list marks each item with a shape; with `type: 'number'` the
27
+ * engine numbers them, in the `format` given, and the numbers follow the items that `data-source` makes.
28
+ */
29
+ function list(attrs, nodes = []) {
30
+ return { type: 'list', attrs: (0, _shared_1.buildAttrs)(attrs), nodes };
31
+ }
32
+ /** An item of a list. Its words go in a `text`; a `list` inside it is a list under the item. */
33
+ function li(attrs, nodes = []) {
34
+ return { type: 'li', attrs: (0, _shared_1.buildAttrs)(attrs), nodes };
35
+ }
25
36
  function table(attrs, nodes = []) {
26
37
  return { type: 'table', attrs: (0, _shared_1.buildAttrs)(attrs), nodes };
27
38
  }
@@ -37,9 +48,20 @@ function td(attrs, nodes = []) {
37
48
  function text(attrs, nodes = []) {
38
49
  return { type: 'text', attrs: (0, _shared_1.buildAttrs)(attrs), nodes };
39
50
  }
51
+ /**
52
+ * Code in a box that fills the width and grows with its lines. The strings are kept as they are written: every
53
+ * space, and a new line wherever a `\n` is. A line longer than the box wraps. The spans color parts of it.
54
+ */
55
+ function code(attrs, nodes = []) {
56
+ return { type: 'code', attrs: (0, _shared_1.buildAttrs)(attrs), nodes };
57
+ }
40
58
  function span(attrs, nodes = []) {
41
59
  return { type: 'span', attrs: (0, _shared_1.buildAttrs)(attrs), nodes };
42
60
  }
61
+ /** A run of code in the nodes of a text: its words in the code font on a tint. */
62
+ function codeSpan(attrs, nodes = []) {
63
+ return { type: 'code-span', attrs: (0, _shared_1.buildAttrs)(attrs), nodes };
64
+ }
43
65
  /**
44
66
  * A footnote, in the nodes of a text. The engine numbers it and puts a small raised number at its place in the
45
67
  * text; its words go at the bottom of the page where that number lands. It has no attributes: pass `NoAttr`.
@@ -78,10 +100,14 @@ exports.LpdfLayout = Object.freeze({
78
100
  column,
79
101
  row,
80
102
  grid,
103
+ list,
104
+ li,
81
105
  frame,
82
106
  link,
83
107
  text,
108
+ code,
84
109
  span,
110
+ codeSpan,
85
111
  fn,
86
112
  divider,
87
113
  img,