@pdfme/jsx 6.1.1-dev.8 → 6.1.2-dev.13

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/index.js CHANGED
@@ -1,14 +1,25 @@
1
1
  import { a as isPdfJsxElement, n as cloneElementWithChildren, o as isPdfJsxFragment, r as createElementNode } from "./node-BeNL0xMl.js";
2
- import { getDefaultFont, pt2mm, resolvePageSize } from "@pdfme/common";
3
- import { measureTextHeight } from "@pdfme/schemas/utils";
2
+ import { getDefaultFont, isBlankPdf, pt2mm, resolvePageSize } from "@pdfme/common";
3
+ import { escapeInlineMarkdown, getVariableNames, measureTextHeight, visitVariables } from "@pdfme/schemas/utils";
4
4
  //#region src/components.ts
5
5
  var makeBuiltin = (kind) => (props) => createElementNode(kind, props);
6
+ var Document = makeBuiltin("document");
6
7
  var Page = makeBuiltin("page");
8
+ var Static = makeBuiltin("static");
9
+ var Header = makeBuiltin("header");
10
+ var Footer = makeBuiltin("footer");
11
+ var Absolute = makeBuiltin("absolute");
7
12
  var Stack = makeBuiltin("stack");
8
13
  var Row = makeBuiltin("row");
9
14
  var Box = makeBuiltin("box");
10
15
  var Spacer = makeBuiltin("spacer");
11
16
  var Text = makeBuiltin("text");
17
+ var MultiVariableText = makeBuiltin("multiVariableText");
18
+ var Image = makeBuiltin("image");
19
+ var Svg = makeBuiltin("svg");
20
+ var Rectangle = makeBuiltin("rectangle");
21
+ var Ellipse = makeBuiltin("ellipse");
22
+ var Line = makeBuiltin("line");
12
23
  var List = makeBuiltin("list");
13
24
  var Table = makeBuiltin("table");
14
25
  var PageBreak = (props) => createElementNode("pagebreak", props);
@@ -18,6 +29,10 @@ var DEFAULT_FONT_SIZE = 10;
18
29
  var DEFAULT_LINE_HEIGHT = 1;
19
30
  var DEFAULT_CHARACTER_SPACING = 0;
20
31
  var DEFAULT_FONT_COLOR = "#000000";
32
+ var DEFAULT_VISUAL_HEIGHT = 40;
33
+ var DEFAULT_LINE_THICKNESS = .5;
34
+ var DEFAULT_LINE_COLOR = "#000000";
35
+ var DEFAULT_SHAPE_BORDER_COLOR = "#000000";
21
36
  var DEFAULT_DYNAMIC_FONT_SIZE = {
22
37
  min: 4,
23
38
  max: 72,
@@ -25,7 +40,11 @@ var DEFAULT_DYNAMIC_FONT_SIZE = {
25
40
  };
26
41
  var renderToTemplate = async (node, options = {}) => {
27
42
  validatePageBreakPlacement(node);
28
- const pages = flattenChildren(expandPageBreaks(node)).filter((child) => isPdfJsxElement(child) && child.kind === "page");
43
+ const root = resolveRenderRoot(node);
44
+ validateAbsolutePlacement(root.children);
45
+ validateStaticPlacement(root.children, root.hasDocument);
46
+ const { pages: rawPages, blocks: staticBlocks } = extractDocumentChildren(root);
47
+ const pages = rawPages.map((page) => applyDocumentPageDefaults(page, root.documentProps));
29
48
  if (pages.length === 0) throw new Error("@pdfme/jsx: renderToTemplate root must contain at least one <Page>.");
30
49
  const firstPageProps = pages[0]?.props;
31
50
  const firstMargin = resolveBoxSides(firstPageProps.margin);
@@ -37,6 +56,21 @@ var renderToTemplate = async (node, options = {}) => {
37
56
  const font = options.font ?? getDefaultFont();
38
57
  const _cache = /* @__PURE__ */ new Map();
39
58
  const pageSchemas = [];
59
+ const staticSchemas = [];
60
+ const hasStaticChildren = hasStaticBlocks(staticBlocks);
61
+ if (hasStaticChildren && options.basePdf != null && !isBlankPdf(options.basePdf)) throw new Error("@pdfme/jsx: <Header>, <Footer>, and <Static> are supported only with a blank basePdf.");
62
+ if (hasStaticChildren) await layoutStaticBlocks({
63
+ blocks: staticBlocks,
64
+ margin: firstMargin,
65
+ pageSize,
66
+ staticSchemas,
67
+ inputs,
68
+ usedNames,
69
+ nameCounters,
70
+ defaultFont: firstPageProps.font,
71
+ font,
72
+ _cache
73
+ });
40
74
  for (const page of pages) {
41
75
  const props = page.props;
42
76
  const margin = resolveBoxSides(props.margin);
@@ -55,21 +89,29 @@ var renderToTemplate = async (node, options = {}) => {
55
89
  font,
56
90
  _cache
57
91
  };
58
- await layoutChildren(page.children, frame, "stack", { gap: 0 }, ctx);
92
+ await layoutChildren(page.children, frame, "stack", {
93
+ gap: 0,
94
+ alignItems: "stretch",
95
+ justifyContent: "start"
96
+ }, ctx);
59
97
  pageSchemas.push(ctx.schemas);
60
98
  }
99
+ const basePdf = options.basePdf ?? {
100
+ width: pageSize.width,
101
+ height: pageSize.height,
102
+ padding: [
103
+ firstMargin.top,
104
+ firstMargin.right,
105
+ firstMargin.bottom,
106
+ firstMargin.left
107
+ ]
108
+ };
61
109
  return {
62
110
  template: {
63
- basePdf: options.basePdf ?? {
64
- width: pageSize.width,
65
- height: pageSize.height,
66
- padding: [
67
- firstMargin.top,
68
- firstMargin.right,
69
- firstMargin.bottom,
70
- firstMargin.left
71
- ]
72
- },
111
+ basePdf: staticSchemas.length > 0 && isBlankPdf(basePdf) ? {
112
+ ...basePdf,
113
+ staticSchema: [...basePdf.staticSchema ?? [], ...staticSchemas]
114
+ } : basePdf,
73
115
  schemas: pageSchemas
74
116
  },
75
117
  inputs: [inputs]
@@ -115,14 +157,141 @@ var flattenForSplitting = (children) => {
115
157
  return [children];
116
158
  };
117
159
  var expandPageBreaks = (node) => splitChildrenByPageBreak(node).flat();
160
+ var STATIC_DIRECT_KINDS = new Set([
161
+ "header",
162
+ "footer",
163
+ "static"
164
+ ]);
165
+ var createEmptyStaticBlocks = () => ({
166
+ footer: [],
167
+ header: [],
168
+ staticBottom: [],
169
+ staticTop: []
170
+ });
171
+ var resolveRenderRoot = (node) => {
172
+ const topLevelChildren = flattenChildren(node);
173
+ const documents = topLevelChildren.filter((child) => isPdfJsxElement(child) && child.kind === "document");
174
+ if (documents.length === 0) return {
175
+ children: expandPageBreaks(node),
176
+ hasDocument: false
177
+ };
178
+ if (documents.length > 1) throw new Error("@pdfme/jsx: only one <Document> root is supported.");
179
+ if (topLevelChildren.some((child) => child !== documents[0])) throw new Error("@pdfme/jsx: <Document> must be the only root element.");
180
+ const document = documents[0];
181
+ return {
182
+ children: expandPageBreaks(document.children),
183
+ documentProps: document.props,
184
+ hasDocument: true
185
+ };
186
+ };
187
+ var extractDocumentChildren = (root) => {
188
+ const blocks = createEmptyStaticBlocks();
189
+ const pages = [];
190
+ for (const child of flattenChildren(root.children)) {
191
+ if (!isPdfJsxElement(child)) continue;
192
+ if (child.kind === "page") {
193
+ pages.push(child);
194
+ continue;
195
+ }
196
+ if (!root.hasDocument || !STATIC_DIRECT_KINDS.has(child.kind)) continue;
197
+ if (child.kind === "header") blocks.header.push(...child.children);
198
+ else if (child.kind === "footer") blocks.footer.push(...child.children);
199
+ else if (child.kind === "static") blocks[getStaticPlacement(child) === "top" ? "staticTop" : "staticBottom"].push(...child.children);
200
+ }
201
+ return {
202
+ pages,
203
+ blocks
204
+ };
205
+ };
206
+ var applyDocumentPageDefaults = (page, documentProps) => {
207
+ if (!documentProps) return page;
208
+ const { children: _children, ...props } = documentProps;
209
+ return {
210
+ ...page,
211
+ props: {
212
+ ...props,
213
+ ...page.props
214
+ }
215
+ };
216
+ };
217
+ var hasStaticBlocks = (blocks) => blocks.header.length > 0 || blocks.footer.length > 0 || blocks.staticTop.length > 0 || blocks.staticBottom.length > 0;
218
+ var layoutStaticBlocks = async (arg) => {
219
+ const contentWidth = Math.max(0, arg.pageSize.width - arg.margin.left - arg.margin.right);
220
+ const blockFrames = [
221
+ {
222
+ children: arg.blocks.staticTop,
223
+ frame: {
224
+ x: 0,
225
+ y: 0,
226
+ width: arg.pageSize.width,
227
+ height: arg.pageSize.height
228
+ }
229
+ },
230
+ {
231
+ children: arg.blocks.header,
232
+ frame: {
233
+ x: arg.margin.left,
234
+ y: 0,
235
+ width: contentWidth,
236
+ height: arg.margin.top
237
+ }
238
+ },
239
+ {
240
+ children: arg.blocks.footer,
241
+ frame: {
242
+ x: arg.margin.left,
243
+ y: arg.pageSize.height - arg.margin.bottom,
244
+ width: contentWidth,
245
+ height: arg.margin.bottom
246
+ }
247
+ },
248
+ {
249
+ children: arg.blocks.staticBottom,
250
+ frame: {
251
+ x: 0,
252
+ y: 0,
253
+ width: arg.pageSize.width,
254
+ height: arg.pageSize.height
255
+ },
256
+ placement: "bottom"
257
+ }
258
+ ];
259
+ for (const { children, frame, placement } of blockFrames) {
260
+ if (children.length === 0) continue;
261
+ const schemas = [];
262
+ const size = await layoutChildren(children, frame, "stack", {
263
+ gap: 0,
264
+ alignItems: "stretch",
265
+ justifyContent: "start"
266
+ }, {
267
+ schemas,
268
+ inputs: arg.inputs,
269
+ usedNames: arg.usedNames,
270
+ nameCounters: arg.nameCounters,
271
+ defaultFont: arg.defaultFont,
272
+ font: arg.font,
273
+ _cache: arg._cache
274
+ });
275
+ if (placement === "bottom") shiftSchemas(schemas, 0, schemas.length, 0, Math.max(0, frame.height - size.height));
276
+ arg.staticSchemas.push(...schemas);
277
+ }
278
+ };
118
279
  var layoutChildren = async (children, frame, mode, opts, ctx) => {
119
280
  const items = flattenChildren(children);
120
- const widths = mode === "row" ? resolveRowWidths(items, frame.width, opts.gap) : void 0;
281
+ const flowItems = items.filter((item) => !isAbsoluteElement(item));
282
+ const widths = mode === "row" ? resolveRowWidths(flowItems, frame.width, opts.gap) : void 0;
121
283
  let cursor = 0;
122
284
  let crossMax = 0;
285
+ let flowIndex = 0;
286
+ const layoutItems = [];
123
287
  for (let i = 0; i < items.length; i += 1) {
124
288
  const child = items[i];
125
- const width = mode === "row" ? widths?.[i] ?? 0 : frame.width;
289
+ if (isAbsoluteElement(child)) {
290
+ await renderAbsolute(child.props, child.children, frame, ctx);
291
+ continue;
292
+ }
293
+ const margin = getChildMargin(child);
294
+ const width = mode === "row" ? widths?.[flowIndex] ?? 0 : resolveStackChildWidth(child, frame.width, margin);
126
295
  const childFrame = mode === "stack" ? {
127
296
  x: frame.x,
128
297
  y: frame.y + cursor,
@@ -134,68 +303,200 @@ var layoutChildren = async (children, frame, mode, opts, ctx) => {
134
303
  width,
135
304
  height: frame.height
136
305
  };
306
+ childFrame.x += margin.left;
307
+ childFrame.y += margin.top;
308
+ childFrame.height = Math.max(0, childFrame.height - margin.top - margin.bottom);
309
+ const schemaStart = ctx.schemas.length;
137
310
  const size = typeof child === "string" || typeof child === "number" ? await renderText({ children: String(child) }, childFrame, ctx) : await renderElement(child, childFrame, mode, ctx);
138
- const advance = mode === "stack" ? size.height : width;
139
- cursor += advance + (i < items.length - 1 ? opts.gap : 0);
140
- crossMax = Math.max(crossMax, mode === "stack" ? size.width : size.height);
311
+ const schemaEnd = ctx.schemas.length;
312
+ const mainSize = mode === "stack" ? margin.top + size.height + margin.bottom : margin.left + width + margin.right;
313
+ const outerHeight = margin.top + size.height + margin.bottom;
314
+ layoutItems.push({
315
+ schemaStart,
316
+ schemaEnd,
317
+ outerHeight
318
+ });
319
+ if (mode === "stack") {
320
+ const outerWidth = margin.left + size.width + margin.right;
321
+ const dx = resolveAlignOffset(frame.width, outerWidth, opts.alignItems);
322
+ if (dx !== 0) shiftSchemas(ctx.schemas, schemaStart, schemaEnd, dx, 0);
323
+ }
324
+ cursor += mainSize + (flowIndex < flowItems.length - 1 ? opts.gap : 0);
325
+ flowIndex += 1;
326
+ crossMax = Math.max(crossMax, mode === "stack" ? margin.left + size.width + margin.right : margin.top + size.height + margin.bottom);
327
+ }
328
+ const contentMainSize = cursor;
329
+ const containerMainSize = opts.mainSize ?? contentMainSize;
330
+ applyJustifyContent(ctx.schemas, layoutItems, mode, contentMainSize, containerMainSize, opts);
331
+ if (mode === "row") {
332
+ const rowHeight = opts.crossSize ?? crossMax;
333
+ for (const item of layoutItems) {
334
+ const dy = resolveAlignOffset(rowHeight, item.outerHeight, opts.alignItems);
335
+ if (dy !== 0) shiftSchemas(ctx.schemas, item.schemaStart, item.schemaEnd, 0, dy);
336
+ }
337
+ return {
338
+ width: containerMainSize,
339
+ height: rowHeight
340
+ };
141
341
  }
142
- return mode === "stack" ? {
342
+ return {
143
343
  width: crossMax,
144
- height: cursor
145
- } : {
146
- width: cursor,
147
- height: crossMax
344
+ height: containerMainSize
148
345
  };
149
346
  };
150
347
  var resolveRowWidths = (items, frameWidth, gap) => {
151
- let fixedWidth = 0;
152
- let flexCount = 0;
153
- const widths = items.map((item) => {
154
- if (typeof item === "string" || typeof item === "number") {
155
- flexCount += 1;
156
- return;
157
- }
158
- const width = item.props.width;
159
- if (typeof width === "number") {
160
- fixedWidth += width;
161
- return width;
162
- }
163
- flexCount += 1;
348
+ let usedWidth = 0;
349
+ let totalGrow = 0;
350
+ const parts = items.map((item) => {
351
+ const margin = getChildMargin(item);
352
+ const width = getChildWidth(item);
353
+ const grow = getChildFlexGrow(item) ?? (width == null ? 1 : 0);
354
+ const basis = width ?? 0;
355
+ usedWidth += basis + margin.left + margin.right;
356
+ totalGrow += grow;
357
+ return {
358
+ basis,
359
+ grow
360
+ };
164
361
  });
165
- const remaining = Math.max(0, frameWidth - fixedWidth - Math.max(0, items.length - 1) * gap);
166
- const flexWidth = flexCount > 0 ? remaining / flexCount : 0;
167
- return widths.map((width) => width ?? flexWidth);
362
+ const remaining = Math.max(0, frameWidth - usedWidth - Math.max(0, items.length - 1) * gap);
363
+ const flexUnit = totalGrow > 0 ? remaining / totalGrow : 0;
364
+ return parts.map(({ basis, grow }) => basis + grow * flexUnit);
365
+ };
366
+ var getChildMargin = (child) => {
367
+ if (typeof child === "string" || typeof child === "number") return resolveBoxSides();
368
+ return resolveBoxSides(child.props.margin);
369
+ };
370
+ var getChildWidth = (child) => {
371
+ if (typeof child === "string" || typeof child === "number") return void 0;
372
+ const width = child.props.width;
373
+ return typeof width === "number" ? width : void 0;
374
+ };
375
+ var getChildFlexGrow = (child) => {
376
+ if (typeof child === "string" || typeof child === "number") return void 0;
377
+ const props = child.props;
378
+ const flexGrow = props.flexGrow ?? props.flex;
379
+ return typeof flexGrow === "number" ? Math.max(0, flexGrow) : void 0;
380
+ };
381
+ var isAbsoluteElement = (child) => isPdfJsxElement(child) && child.kind === "absolute";
382
+ var getStaticPlacement = (element) => {
383
+ const placement = element.props.placement ?? "top";
384
+ if (placement !== "top" && placement !== "bottom") throw new Error("@pdfme/jsx: <Static> placement must be \"top\" or \"bottom\".");
385
+ return placement;
386
+ };
387
+ var resolveStackChildWidth = (child, frameWidth, margin) => {
388
+ const width = getChildWidth(child);
389
+ if (width != null) return width;
390
+ return Math.max(0, frameWidth - margin.left - margin.right);
391
+ };
392
+ var applyJustifyContent = (schemas, items, mode, contentMainSize, containerMainSize, opts) => {
393
+ const extraSpace = Math.max(0, containerMainSize - contentMainSize);
394
+ if (extraSpace === 0 || opts.justifyContent === "start") return;
395
+ const extraGap = opts.justifyContent === "space-between" && items.length > 1 ? extraSpace / (items.length - 1) : 0;
396
+ const startOffset = opts.justifyContent === "center" ? extraSpace / 2 : opts.justifyContent === "end" ? extraSpace : 0;
397
+ let runningExtraGap = 0;
398
+ for (const item of items) {
399
+ const offset = startOffset + runningExtraGap;
400
+ if (offset !== 0) shiftSchemas(schemas, item.schemaStart, item.schemaEnd, mode === "row" ? offset : 0, mode === "stack" ? offset : 0);
401
+ runningExtraGap += extraGap;
402
+ }
403
+ };
404
+ var resolveAlignOffset = (containerSize, itemSize, alignItems) => {
405
+ if (alignItems === "center") return Math.max(0, (containerSize - itemSize) / 2);
406
+ if (alignItems === "end") return Math.max(0, containerSize - itemSize);
407
+ return 0;
408
+ };
409
+ var shiftSchemas = (schemas, start, end, dx, dy) => {
410
+ for (let i = start; i < end; i += 1) {
411
+ const schema = schemas[i];
412
+ if (!schema) continue;
413
+ schema.position = {
414
+ x: schema.position.x + dx,
415
+ y: schema.position.y + dy
416
+ };
417
+ }
168
418
  };
169
419
  var renderElement = async (element, frame, parentMode, ctx) => {
420
+ const props = parentMode === "row" && getChildFlexGrow(element) != null ? {
421
+ ...element.props,
422
+ width: frame.width
423
+ } : element.props;
170
424
  switch (element.kind) {
171
- case "stack": return renderStack(element.props, element.children, frame, ctx);
172
- case "row": return renderRow(element.props, element.children, frame, ctx);
173
- case "box": return renderBox(element.props, element.children, frame, parentMode, ctx);
174
- case "spacer": return Promise.resolve(renderSpacer(element.props));
425
+ case "stack": return renderStack(props, element.children, frame, ctx);
426
+ case "row": return renderRow(props, element.children, frame, ctx);
427
+ case "box": return renderBox(props, element.children, frame, parentMode, ctx);
428
+ case "spacer": return Promise.resolve(renderSpacer(props));
175
429
  case "text": return renderText({
176
- ...element.props,
430
+ ...props,
431
+ children: element.children
432
+ }, frame, ctx);
433
+ case "multiVariableText": return renderMultiVariableText({
434
+ ...props,
435
+ children: element.children
436
+ }, frame, ctx);
437
+ case "image": return renderImage(props, frame, ctx);
438
+ case "svg": return renderSvg({
439
+ ...props,
177
440
  children: element.children
178
441
  }, frame, ctx);
442
+ case "rectangle": return renderShape("rectangle", props, frame, ctx);
443
+ case "ellipse": return renderShape("ellipse", props, frame, ctx);
444
+ case "line": return renderLine(props, frame, ctx);
179
445
  case "list": return renderList({
180
- ...element.props,
446
+ ...props,
181
447
  children: element.children
182
448
  }, frame, ctx);
183
- case "table": return renderTable(element.props, frame, ctx);
449
+ case "table": return renderTable(props, frame, ctx);
450
+ case "document": throw new Error("@pdfme/jsx: <Document> must be the root element.");
451
+ case "header":
452
+ case "footer":
453
+ case "static": throw new Error("@pdfme/jsx: <Header>, <Footer>, and <Static> can only be used as direct children of <Document>.");
454
+ case "absolute": return renderAbsolute(props, element.children, frame, ctx);
184
455
  default: return {
185
456
  width: 0,
186
457
  height: 0
187
458
  };
188
459
  }
189
460
  };
461
+ var renderAbsolute = async (props, children, frame, ctx) => {
462
+ const x = props.x ?? 0;
463
+ const y = props.y ?? 0;
464
+ await layoutChildren(children, {
465
+ x: frame.x + x,
466
+ y: frame.y + y,
467
+ width: props.width ?? Math.max(0, frame.width - x),
468
+ height: props.height ?? Math.max(0, frame.height - y)
469
+ }, "stack", {
470
+ gap: 0,
471
+ alignItems: "stretch",
472
+ justifyContent: "start"
473
+ }, ctx);
474
+ return {
475
+ width: 0,
476
+ height: 0
477
+ };
478
+ };
190
479
  var renderStack = (props, children, frame, ctx) => layoutChildren(children, {
191
480
  ...frame,
192
- width: props.width ?? frame.width
193
- }, "stack", { gap: props.gap ?? 0 }, ctx);
481
+ width: props.width ?? frame.width,
482
+ height: props.height ?? frame.height
483
+ }, "stack", {
484
+ gap: props.gap ?? 0,
485
+ alignItems: props.alignItems ?? "stretch",
486
+ justifyContent: props.justifyContent ?? "start",
487
+ mainSize: props.height
488
+ }, ctx);
194
489
  var renderRow = (props, children, frame, ctx) => layoutChildren(children, {
195
490
  ...frame,
196
491
  width: props.width ?? frame.width,
197
492
  height: props.height ?? frame.height
198
- }, "row", { gap: props.gap ?? 0 }, ctx);
493
+ }, "row", {
494
+ gap: props.gap ?? 0,
495
+ alignItems: props.alignItems ?? "start",
496
+ justifyContent: props.justifyContent ?? "start",
497
+ mainSize: props.width,
498
+ crossSize: props.height
499
+ }, ctx);
199
500
  var renderBox = async (props, children, frame, _parentMode, ctx) => {
200
501
  const width = props.width ?? frame.width;
201
502
  const padding = resolveBoxSides(props.padding);
@@ -223,7 +524,11 @@ var renderBox = async (props, children, frame, _parentMode, ctx) => {
223
524
  y: frame.y + padding.top,
224
525
  width: width - padding.left - padding.right,
225
526
  height: (props.height ?? frame.height) - padding.top - padding.bottom
226
- }, "stack", { gap: 0 }, ctx);
527
+ }, "stack", {
528
+ gap: 0,
529
+ alignItems: "stretch",
530
+ justifyContent: "start"
531
+ }, ctx);
227
532
  const height = props.height ?? childSize.height + padding.top + padding.bottom;
228
533
  if (needsRect) ctx.schemas[beforeCount] = {
229
534
  ...ctx.schemas[beforeCount],
@@ -243,8 +548,10 @@ var renderText = async (props, frame, ctx) => {
243
548
  const lineHeight = props.lineHeight ?? DEFAULT_LINE_HEIGHT;
244
549
  const width = props.width ?? frame.width;
245
550
  const value = childrenToString(props.children);
246
- const name = resolveName(ctx, "text", props.name);
247
551
  const readOnly = props.readOnly ?? props.name == null;
552
+ const textFormat = props.textFormat ?? "plain";
553
+ if (!readOnly && textFormat === "inline-markdown") throw new Error("@pdfme/jsx: editable <Text> does not support textFormat=\"inline-markdown\". Use read-only <Text> or <MultiVariableText>.");
554
+ const name = resolveName(ctx, "text", props.name);
248
555
  const schema = {
249
556
  name,
250
557
  type: "text",
@@ -267,13 +574,14 @@ var renderText = async (props, frame, ctx) => {
267
574
  characterSpacing: props.spacing ?? DEFAULT_CHARACTER_SPACING,
268
575
  fontColor: props.color ?? DEFAULT_FONT_COLOR,
269
576
  backgroundColor: props.background ?? "",
270
- textFormat: props.textFormat ?? "plain",
577
+ textFormat,
271
578
  overflow: props.overflow,
272
579
  strikethrough: props.strikethrough ?? false,
273
580
  underline: props.underline ?? false
274
581
  };
275
582
  if (props.borderColor) schema.borderColor = props.borderColor;
276
- if (props.borderWidth != null) schema.borderWidth = props.borderWidth;
583
+ if (props.borderWidth != null) schema.borderWidth = resolveBoxSides(props.borderWidth);
584
+ if (props.padding != null) schema.padding = resolveBoxSides(props.padding);
277
585
  if (props.dynamicFontSize) schema.dynamicFontSize = {
278
586
  min: props.dynamicFontSize.min ?? DEFAULT_DYNAMIC_FONT_SIZE.min,
279
587
  max: props.dynamicFontSize.max ?? DEFAULT_DYNAMIC_FONT_SIZE.max,
@@ -292,6 +600,174 @@ var renderText = async (props, frame, ctx) => {
292
600
  height: schema.height
293
601
  };
294
602
  };
603
+ var renderMultiVariableText = async (props, frame, ctx) => {
604
+ const fontSize = props.size ?? DEFAULT_FONT_SIZE;
605
+ const lineHeight = props.lineHeight ?? DEFAULT_LINE_HEIGHT;
606
+ const width = props.width ?? frame.width;
607
+ const templateText = props.text ?? childrenToString(props.children);
608
+ const values = normalizeMultiVariableTextValues(props.values);
609
+ const variables = resolveMultiVariableTextVariables(templateText, props.variables, values);
610
+ const name = resolveName(ctx, "multiVariableText", props.name);
611
+ const readOnly = props.readOnly ?? props.name == null;
612
+ const textFormat = props.textFormat ?? "plain";
613
+ const content = readOnly ? substituteMultiVariableText(templateText, values, textFormat === "inline-markdown") : JSON.stringify(values);
614
+ const schema = {
615
+ name,
616
+ type: "multiVariableText",
617
+ content,
618
+ position: {
619
+ x: frame.x,
620
+ y: frame.y
621
+ },
622
+ width,
623
+ height: props.height ?? 0,
624
+ rotate: props.rotate ?? 0,
625
+ opacity: props.opacity ?? 1,
626
+ readOnly,
627
+ required: props.required,
628
+ alignment: props.align ?? "left",
629
+ verticalAlignment: props.valign ?? "top",
630
+ fontSize,
631
+ fontName: props.font ?? ctx.defaultFont,
632
+ lineHeight,
633
+ characterSpacing: props.spacing ?? DEFAULT_CHARACTER_SPACING,
634
+ fontColor: props.color ?? DEFAULT_FONT_COLOR,
635
+ backgroundColor: props.background ?? "",
636
+ textFormat,
637
+ overflow: props.overflow,
638
+ strikethrough: props.strikethrough ?? false,
639
+ underline: props.underline ?? false,
640
+ text: templateText,
641
+ variables
642
+ };
643
+ if (props.borderColor) schema.borderColor = props.borderColor;
644
+ if (props.borderWidth != null) schema.borderWidth = resolveBoxSides(props.borderWidth);
645
+ if (props.padding != null) schema.padding = resolveBoxSides(props.padding);
646
+ if (props.dynamicFontSize) schema.dynamicFontSize = {
647
+ min: props.dynamicFontSize.min ?? DEFAULT_DYNAMIC_FONT_SIZE.min,
648
+ max: props.dynamicFontSize.max ?? DEFAULT_DYNAMIC_FONT_SIZE.max,
649
+ fit: props.dynamicFontSize.fit ?? DEFAULT_DYNAMIC_FONT_SIZE.fit
650
+ };
651
+ if (props.height == null) schema.height = await measureTextHeight({
652
+ value: readOnly ? content : substituteMultiVariableText(templateText, values, textFormat === "inline-markdown"),
653
+ schema,
654
+ font: ctx.font,
655
+ _cache: ctx._cache
656
+ });
657
+ if (!readOnly) ctx.inputs[name] = content;
658
+ ctx.schemas.push(schema);
659
+ return {
660
+ width,
661
+ height: schema.height
662
+ };
663
+ };
664
+ var renderImage = (props, frame, ctx) => {
665
+ const width = props.width ?? frame.width;
666
+ const height = props.height ?? DEFAULT_VISUAL_HEIGHT;
667
+ const name = resolveName(ctx, "image", props.name);
668
+ const readOnly = props.readOnly ?? props.name == null;
669
+ const content = props.src ?? "";
670
+ const schema = {
671
+ name,
672
+ type: "image",
673
+ content,
674
+ position: {
675
+ x: frame.x,
676
+ y: frame.y
677
+ },
678
+ width,
679
+ height,
680
+ rotate: props.rotate ?? 0,
681
+ opacity: props.opacity ?? 1,
682
+ readOnly,
683
+ required: props.required
684
+ };
685
+ if (!readOnly) ctx.inputs[name] = content;
686
+ ctx.schemas.push(schema);
687
+ return {
688
+ width,
689
+ height
690
+ };
691
+ };
692
+ var renderSvg = (props, frame, ctx) => {
693
+ const width = props.width ?? frame.width;
694
+ const height = props.height ?? DEFAULT_VISUAL_HEIGHT;
695
+ const name = resolveName(ctx, "svg", props.name);
696
+ const readOnly = props.readOnly ?? props.name == null;
697
+ const content = props.svg ?? childrenToString(props.children);
698
+ const schema = {
699
+ name,
700
+ type: "svg",
701
+ content,
702
+ position: {
703
+ x: frame.x,
704
+ y: frame.y
705
+ },
706
+ width,
707
+ height,
708
+ rotate: props.rotate ?? 0,
709
+ opacity: props.opacity ?? 1,
710
+ readOnly,
711
+ required: props.required
712
+ };
713
+ if (!readOnly) ctx.inputs[name] = content;
714
+ ctx.schemas.push(schema);
715
+ return {
716
+ width,
717
+ height
718
+ };
719
+ };
720
+ var renderShape = (type, props, frame, ctx) => {
721
+ const width = props.width ?? frame.width;
722
+ const height = props.height ?? DEFAULT_VISUAL_HEIGHT;
723
+ const fill = props.fill ?? "";
724
+ const borderWidth = props.borderWidth ?? (props.borderColor || !fill ? 1 : 0);
725
+ const schema = {
726
+ name: resolveName(ctx, type, props.name),
727
+ type,
728
+ position: {
729
+ x: frame.x,
730
+ y: frame.y
731
+ },
732
+ width,
733
+ height,
734
+ rotate: props.rotate ?? 0,
735
+ opacity: props.opacity ?? 1,
736
+ readOnly: true,
737
+ borderWidth,
738
+ borderColor: props.borderColor ?? (borderWidth > 0 ? DEFAULT_SHAPE_BORDER_COLOR : ""),
739
+ color: fill,
740
+ radius: type === "rectangle" ? props.radius ?? 0 : 0
741
+ };
742
+ ctx.schemas.push(schema);
743
+ return {
744
+ width,
745
+ height
746
+ };
747
+ };
748
+ var renderLine = (props, frame, ctx) => {
749
+ const width = props.width ?? frame.width;
750
+ const height = props.height ?? DEFAULT_LINE_THICKNESS;
751
+ const schema = {
752
+ name: resolveName(ctx, "line", props.name),
753
+ type: "line",
754
+ position: {
755
+ x: frame.x,
756
+ y: frame.y
757
+ },
758
+ width,
759
+ height,
760
+ rotate: props.rotate ?? 0,
761
+ opacity: props.opacity ?? 1,
762
+ readOnly: true,
763
+ color: props.color ?? DEFAULT_LINE_COLOR
764
+ };
765
+ ctx.schemas.push(schema);
766
+ return {
767
+ width,
768
+ height
769
+ };
770
+ };
295
771
  var renderList = (props, frame, ctx) => {
296
772
  const fontSize = props.size ?? DEFAULT_FONT_SIZE;
297
773
  const lineHeight = props.lineHeight ?? DEFAULT_LINE_HEIGHT;
@@ -363,7 +839,7 @@ var renderTable = (props, frame, ctx) => {
363
839
  showHead,
364
840
  repeatHead: props.repeatHead ?? false,
365
841
  head: props.head,
366
- headWidthPercentages: normalizeColumnWidths(props.widths, props.head.length),
842
+ headWidthPercentages: normalizeColumnWeights(props.columnWeights, props.head.length),
367
843
  tableStyles: {
368
844
  borderColor: props.tableStyles?.borderColor ?? "#000000",
369
845
  borderWidth: props.tableStyles?.borderWidth ?? .3
@@ -402,8 +878,47 @@ var normalizeListItems = (props) => {
402
878
  }));
403
879
  };
404
880
  var serializeListItem = (item) => `${" ".repeat(Math.max(0, item.level))}${item.text}`;
405
- var normalizeColumnWidths = (widths, columnCount) => {
406
- if (widths && widths.length > 0) return widths;
881
+ var normalizeMultiVariableTextValues = (values) => {
882
+ const normalized = {};
883
+ Object.entries(values ?? {}).forEach(([key, value]) => {
884
+ normalized[key] = value == null ? "" : String(value);
885
+ });
886
+ return normalized;
887
+ };
888
+ var resolveMultiVariableTextVariables = (templateText, variables, values) => {
889
+ const result = [];
890
+ const seen = /* @__PURE__ */ new Set();
891
+ const add = (name) => {
892
+ if (!seen.has(name)) {
893
+ seen.add(name);
894
+ result.push(name);
895
+ }
896
+ };
897
+ variables?.forEach(add);
898
+ getVariableNames(templateText).forEach(add);
899
+ Object.keys(values).forEach(add);
900
+ return result;
901
+ };
902
+ var substituteMultiVariableText = (templateText, values, escapeMarkdown) => {
903
+ let result = "";
904
+ let lastIndex = 0;
905
+ visitVariables(templateText, ({ name, startIndex, endIndex }) => {
906
+ result += templateText.slice(lastIndex, startIndex);
907
+ const value = values[name];
908
+ if (value != null) result += escapeMarkdown ? escapeInlineMarkdown(value) : value;
909
+ lastIndex = endIndex + 1;
910
+ });
911
+ return result + templateText.slice(lastIndex);
912
+ };
913
+ var normalizeColumnWeights = (columnWeights, columnCount) => {
914
+ if (columnWeights && columnWeights.length > 0) {
915
+ const normalizedWidths = Array.from({ length: columnCount }, (_, index) => {
916
+ const width = columnWeights[index];
917
+ return typeof width === "number" && Number.isFinite(width) && width > 0 ? width : 1;
918
+ });
919
+ const totalWidth = normalizedWidths.reduce((sum, width) => sum + width, 0);
920
+ if (totalWidth > 0) return normalizedWidths.map((width) => width / totalWidth * 100);
921
+ }
407
922
  if (columnCount <= 0) return [];
408
923
  return Array.from({ length: columnCount }, () => 100 / columnCount);
409
924
  };
@@ -491,6 +1006,33 @@ var PAGE_BREAK_PARENT_KINDS = new Set([
491
1006
  "stack",
492
1007
  "box"
493
1008
  ]);
1009
+ var STATIC_CONTAINER_KINDS = new Set([
1010
+ "absolute",
1011
+ "stack",
1012
+ "row",
1013
+ "box"
1014
+ ]);
1015
+ var STATIC_LEAF_KINDS = new Set([
1016
+ "spacer",
1017
+ "text",
1018
+ "image",
1019
+ "svg",
1020
+ "rectangle",
1021
+ "ellipse",
1022
+ "line"
1023
+ ]);
1024
+ var ABSOLUTE_PARENT_KINDS = new Set([
1025
+ "page",
1026
+ "header",
1027
+ "footer",
1028
+ "static",
1029
+ "box"
1030
+ ]);
1031
+ var STATIC_BLOCK_KINDS = new Set([
1032
+ "header",
1033
+ "footer",
1034
+ "static"
1035
+ ]);
494
1036
  var validatePageBreakPlacement = (node, parentKind = void 0, canBreak = false) => {
495
1037
  for (const child of flattenForSplitting(node)) {
496
1038
  if (!isPdfJsxElement(child)) continue;
@@ -502,7 +1044,62 @@ var validatePageBreakPlacement = (node, parentKind = void 0, canBreak = false) =
502
1044
  validatePageBreakPlacement(child.children, child.kind, childCanBreak);
503
1045
  }
504
1046
  };
1047
+ var validateAbsolutePlacement = (node, parentKind = void 0) => {
1048
+ for (const child of flattenForSplitting(node)) {
1049
+ if (!isPdfJsxElement(child)) continue;
1050
+ if (child.kind === "absolute" && (!parentKind || !ABSOLUTE_PARENT_KINDS.has(parentKind))) throw new Error("@pdfme/jsx: <Absolute> can only be used inside <Page>, <Header>, <Footer>, <Static>, or <Box>.");
1051
+ validateAbsolutePlacement(child.children, child.kind);
1052
+ }
1053
+ };
1054
+ var validateStaticPlacement = (children, hasDocument) => {
1055
+ for (const child of flattenChildren(children)) {
1056
+ if (!isPdfJsxElement(child)) {
1057
+ if (hasDocument && String(child).trim() !== "") throw new Error("@pdfme/jsx: <Document> children must be <Header>, <Footer>, <Static>, or <Page>.");
1058
+ continue;
1059
+ }
1060
+ if (STATIC_BLOCK_KINDS.has(child.kind)) {
1061
+ if (!hasDocument) throw new Error("@pdfme/jsx: <Header>, <Footer>, and <Static> can only be used as direct children of <Document>.");
1062
+ if (child.kind === "static") {
1063
+ if (getStaticPlacement(child) === "bottom" && hasElementKind(child.children, "absolute")) throw new Error("@pdfme/jsx: <Absolute> is not supported inside bottom <Static>. Use top <Static>, <Header>, <Footer>, or <Page> for fixed page coordinates.");
1064
+ }
1065
+ validateStaticChildren(child.children);
1066
+ continue;
1067
+ }
1068
+ if (hasDocument && child.kind !== "page") throw new Error("@pdfme/jsx: <Document> children must be <Header>, <Footer>, <Static>, or <Page>.");
1069
+ validateNoNestedStaticBlock(child);
1070
+ }
1071
+ };
1072
+ var validateNoNestedStaticBlock = (node) => {
1073
+ if (!isPdfJsxElement(node)) return;
1074
+ for (const child of flattenForSplitting(node.children)) {
1075
+ if (isPdfJsxElement(child) && STATIC_BLOCK_KINDS.has(child.kind)) throw new Error("@pdfme/jsx: <Header>, <Footer>, and <Static> can only be used as direct children of <Document>.");
1076
+ validateNoNestedStaticBlock(child);
1077
+ }
1078
+ };
1079
+ var hasElementKind = (node, kind) => {
1080
+ for (const child of flattenForSplitting(node)) {
1081
+ if (!isPdfJsxElement(child)) continue;
1082
+ if (child.kind === kind || hasElementKind(child.children, kind)) return true;
1083
+ }
1084
+ return false;
1085
+ };
1086
+ var validateStaticChildren = (children) => {
1087
+ for (const child of flattenForSplitting(children)) {
1088
+ if (!isPdfJsxElement(child)) continue;
1089
+ if (STATIC_CONTAINER_KINDS.has(child.kind)) {
1090
+ validateStaticChildren(child.children);
1091
+ continue;
1092
+ }
1093
+ if (!STATIC_LEAF_KINDS.has(child.kind)) throw new Error(`@pdfme/jsx: <Static> does not support <${child.kind}> children. Supported: read-only Stack, Row, Box, Spacer, Text, Image, Svg, Rectangle, Ellipse, and Line.`);
1094
+ validateStaticLeafProps(child);
1095
+ }
1096
+ };
1097
+ var validateStaticLeafProps = (element) => {
1098
+ if (element.kind !== "text" && element.kind !== "image" && element.kind !== "svg") return;
1099
+ const props = element.props;
1100
+ if (props.readOnly === false || props.name != null && props.readOnly !== true) throw new Error("@pdfme/jsx: <Static> children must be read-only.");
1101
+ };
505
1102
  //#endregion
506
- export { Box, List, Page, PageBreak, Row, Spacer, Stack, Table, Text, renderToTemplate };
1103
+ export { Absolute, Box, Document, Ellipse, Footer, Header, Image, Line, List, MultiVariableText, Page, PageBreak, Rectangle, Row, Spacer, Stack, Static, Svg, Table, Text, renderToTemplate };
507
1104
 
508
1105
  //# sourceMappingURL=index.js.map