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

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,30 @@
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
6
  var Page = makeBuiltin("page");
7
+ var Static = makeBuiltin("static");
8
+ var Header = (props) => createElementNode("static", {
9
+ ...props,
10
+ placement: "top"
11
+ });
12
+ var Footer = (props) => createElementNode("static", {
13
+ ...props,
14
+ placement: "bottom"
15
+ });
16
+ var Absolute = makeBuiltin("absolute");
7
17
  var Stack = makeBuiltin("stack");
8
18
  var Row = makeBuiltin("row");
9
19
  var Box = makeBuiltin("box");
10
20
  var Spacer = makeBuiltin("spacer");
11
21
  var Text = makeBuiltin("text");
22
+ var MultiVariableText = makeBuiltin("multiVariableText");
23
+ var Image = makeBuiltin("image");
24
+ var Svg = makeBuiltin("svg");
25
+ var Rectangle = makeBuiltin("rectangle");
26
+ var Ellipse = makeBuiltin("ellipse");
27
+ var Line = makeBuiltin("line");
12
28
  var List = makeBuiltin("list");
13
29
  var Table = makeBuiltin("table");
14
30
  var PageBreak = (props) => createElementNode("pagebreak", props);
@@ -18,6 +34,10 @@ var DEFAULT_FONT_SIZE = 10;
18
34
  var DEFAULT_LINE_HEIGHT = 1;
19
35
  var DEFAULT_CHARACTER_SPACING = 0;
20
36
  var DEFAULT_FONT_COLOR = "#000000";
37
+ var DEFAULT_VISUAL_HEIGHT = 40;
38
+ var DEFAULT_LINE_THICKNESS = .5;
39
+ var DEFAULT_LINE_COLOR = "#000000";
40
+ var DEFAULT_SHAPE_BORDER_COLOR = "#000000";
21
41
  var DEFAULT_DYNAMIC_FONT_SIZE = {
22
42
  min: 4,
23
43
  max: 72,
@@ -25,19 +45,43 @@ var DEFAULT_DYNAMIC_FONT_SIZE = {
25
45
  };
26
46
  var renderToTemplate = async (node, options = {}) => {
27
47
  validatePageBreakPlacement(node);
28
- const pages = flattenChildren(expandPageBreaks(node)).filter((child) => isPdfJsxElement(child) && child.kind === "page");
48
+ const expanded = expandPageBreaks(node);
49
+ validateAbsolutePlacement(expanded);
50
+ validateNoTopLevelStatic(expanded);
51
+ const pages = flattenChildren(expanded).filter((child) => isPdfJsxElement(child) && child.kind === "page");
29
52
  if (pages.length === 0) throw new Error("@pdfme/jsx: renderToTemplate root must contain at least one <Page>.");
30
53
  const firstPageProps = pages[0]?.props;
31
54
  const firstMargin = resolveBoxSides(firstPageProps.margin);
32
55
  const pageSize = resolvePageSize(firstPageProps.size, firstPageProps.orientation);
33
56
  validateConsistentPageProps(pages, pageSize, firstMargin);
57
+ validateStaticPlacement(pages);
58
+ const { pages: bodyPages, blocks: staticBlocks } = extractStaticChildren(pages);
34
59
  const inputs = {};
35
60
  const usedNames = /* @__PURE__ */ new Set();
36
61
  const nameCounters = {};
37
62
  const font = options.font ?? getDefaultFont();
38
63
  const _cache = /* @__PURE__ */ new Map();
39
64
  const pageSchemas = [];
40
- for (const page of pages) {
65
+ const staticSchemas = [];
66
+ const hasStaticChildren = hasStaticBlocks(staticBlocks);
67
+ if (hasStaticChildren && options.basePdf != null && !isBlankPdf(options.basePdf)) throw new Error("@pdfme/jsx: <Static> is supported only with a blank basePdf.");
68
+ if (hasStaticChildren) await layoutStaticBlocks({
69
+ blocks: staticBlocks,
70
+ frame: {
71
+ x: 0,
72
+ y: 0,
73
+ width: pageSize.width,
74
+ height: pageSize.height
75
+ },
76
+ staticSchemas,
77
+ inputs,
78
+ usedNames,
79
+ nameCounters,
80
+ defaultFont: firstPageProps.font,
81
+ font,
82
+ _cache
83
+ });
84
+ for (const page of bodyPages) {
41
85
  const props = page.props;
42
86
  const margin = resolveBoxSides(props.margin);
43
87
  const frame = {
@@ -55,21 +99,29 @@ var renderToTemplate = async (node, options = {}) => {
55
99
  font,
56
100
  _cache
57
101
  };
58
- await layoutChildren(page.children, frame, "stack", { gap: 0 }, ctx);
102
+ await layoutChildren(page.children, frame, "stack", {
103
+ gap: 0,
104
+ alignItems: "stretch",
105
+ justifyContent: "start"
106
+ }, ctx);
59
107
  pageSchemas.push(ctx.schemas);
60
108
  }
109
+ const basePdf = options.basePdf ?? {
110
+ width: pageSize.width,
111
+ height: pageSize.height,
112
+ padding: [
113
+ firstMargin.top,
114
+ firstMargin.right,
115
+ firstMargin.bottom,
116
+ firstMargin.left
117
+ ]
118
+ };
61
119
  return {
62
120
  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
- },
121
+ basePdf: staticSchemas.length > 0 && isBlankPdf(basePdf) ? {
122
+ ...basePdf,
123
+ staticSchema: [...basePdf.staticSchema ?? [], ...staticSchemas]
124
+ } : basePdf,
73
125
  schemas: pageSchemas
74
126
  },
75
127
  inputs: [inputs]
@@ -115,14 +167,66 @@ var flattenForSplitting = (children) => {
115
167
  return [children];
116
168
  };
117
169
  var expandPageBreaks = (node) => splitChildrenByPageBreak(node).flat();
170
+ var extractStaticChildren = (pages) => {
171
+ const blocks = {
172
+ top: [],
173
+ bottom: []
174
+ };
175
+ return {
176
+ pages: pages.map((page, pageIndex) => {
177
+ const bodyChildren = [];
178
+ for (const child of flattenForSplitting(page.children)) {
179
+ if (isPdfJsxElement(child) && child.kind === "static") {
180
+ if (pageIndex === 0) blocks[getStaticPlacement(child)].push(child.children);
181
+ continue;
182
+ }
183
+ bodyChildren.push(child);
184
+ }
185
+ return cloneElementWithChildren(page, bodyChildren);
186
+ }),
187
+ blocks
188
+ };
189
+ };
190
+ var hasStaticBlocks = (blocks) => blocks.top.length > 0 || blocks.bottom.length > 0;
191
+ var layoutStaticBlocks = async (arg) => {
192
+ for (const placement of ["top", "bottom"]) {
193
+ const children = arg.blocks[placement];
194
+ if (children.length === 0) continue;
195
+ const schemas = [];
196
+ const ctx = {
197
+ schemas,
198
+ inputs: arg.inputs,
199
+ usedNames: arg.usedNames,
200
+ nameCounters: arg.nameCounters,
201
+ defaultFont: arg.defaultFont,
202
+ font: arg.font,
203
+ _cache: arg._cache
204
+ };
205
+ const size = await layoutChildren(children, arg.frame, "stack", {
206
+ gap: 0,
207
+ alignItems: "stretch",
208
+ justifyContent: "start"
209
+ }, ctx);
210
+ if (placement === "bottom") shiftSchemas(schemas, 0, schemas.length, 0, Math.max(0, arg.frame.height - size.height));
211
+ arg.staticSchemas.push(...schemas);
212
+ }
213
+ };
118
214
  var layoutChildren = async (children, frame, mode, opts, ctx) => {
119
215
  const items = flattenChildren(children);
120
- const widths = mode === "row" ? resolveRowWidths(items, frame.width, opts.gap) : void 0;
216
+ const flowItems = items.filter((item) => !isAbsoluteElement(item));
217
+ const widths = mode === "row" ? resolveRowWidths(flowItems, frame.width, opts.gap) : void 0;
121
218
  let cursor = 0;
122
219
  let crossMax = 0;
220
+ let flowIndex = 0;
221
+ const layoutItems = [];
123
222
  for (let i = 0; i < items.length; i += 1) {
124
223
  const child = items[i];
125
- const width = mode === "row" ? widths?.[i] ?? 0 : frame.width;
224
+ if (isAbsoluteElement(child)) {
225
+ await renderAbsolute(child.props, child.children, frame, ctx);
226
+ continue;
227
+ }
228
+ const margin = getChildMargin(child);
229
+ const width = mode === "row" ? widths?.[flowIndex] ?? 0 : resolveStackChildWidth(child, frame.width, margin);
126
230
  const childFrame = mode === "stack" ? {
127
231
  x: frame.x,
128
232
  y: frame.y + cursor,
@@ -134,68 +238,197 @@ var layoutChildren = async (children, frame, mode, opts, ctx) => {
134
238
  width,
135
239
  height: frame.height
136
240
  };
241
+ childFrame.x += margin.left;
242
+ childFrame.y += margin.top;
243
+ childFrame.height = Math.max(0, childFrame.height - margin.top - margin.bottom);
244
+ const schemaStart = ctx.schemas.length;
137
245
  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);
246
+ const schemaEnd = ctx.schemas.length;
247
+ const mainSize = mode === "stack" ? margin.top + size.height + margin.bottom : margin.left + width + margin.right;
248
+ const outerHeight = margin.top + size.height + margin.bottom;
249
+ layoutItems.push({
250
+ schemaStart,
251
+ schemaEnd,
252
+ outerHeight
253
+ });
254
+ if (mode === "stack") {
255
+ const outerWidth = margin.left + size.width + margin.right;
256
+ const dx = resolveAlignOffset(frame.width, outerWidth, opts.alignItems);
257
+ if (dx !== 0) shiftSchemas(ctx.schemas, schemaStart, schemaEnd, dx, 0);
258
+ }
259
+ cursor += mainSize + (flowIndex < flowItems.length - 1 ? opts.gap : 0);
260
+ flowIndex += 1;
261
+ crossMax = Math.max(crossMax, mode === "stack" ? margin.left + size.width + margin.right : margin.top + size.height + margin.bottom);
262
+ }
263
+ const contentMainSize = cursor;
264
+ const containerMainSize = opts.mainSize ?? contentMainSize;
265
+ applyJustifyContent(ctx.schemas, layoutItems, mode, contentMainSize, containerMainSize, opts);
266
+ if (mode === "row") {
267
+ const rowHeight = opts.crossSize ?? crossMax;
268
+ for (const item of layoutItems) {
269
+ const dy = resolveAlignOffset(rowHeight, item.outerHeight, opts.alignItems);
270
+ if (dy !== 0) shiftSchemas(ctx.schemas, item.schemaStart, item.schemaEnd, 0, dy);
271
+ }
272
+ return {
273
+ width: containerMainSize,
274
+ height: rowHeight
275
+ };
141
276
  }
142
- return mode === "stack" ? {
277
+ return {
143
278
  width: crossMax,
144
- height: cursor
145
- } : {
146
- width: cursor,
147
- height: crossMax
279
+ height: containerMainSize
148
280
  };
149
281
  };
150
282
  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;
283
+ let usedWidth = 0;
284
+ let totalGrow = 0;
285
+ const parts = items.map((item) => {
286
+ const margin = getChildMargin(item);
287
+ const width = getChildWidth(item);
288
+ const grow = getChildFlexGrow(item) ?? (width == null ? 1 : 0);
289
+ const basis = width ?? 0;
290
+ usedWidth += basis + margin.left + margin.right;
291
+ totalGrow += grow;
292
+ return {
293
+ basis,
294
+ grow
295
+ };
164
296
  });
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);
297
+ const remaining = Math.max(0, frameWidth - usedWidth - Math.max(0, items.length - 1) * gap);
298
+ const flexUnit = totalGrow > 0 ? remaining / totalGrow : 0;
299
+ return parts.map(({ basis, grow }) => basis + grow * flexUnit);
300
+ };
301
+ var getChildMargin = (child) => {
302
+ if (typeof child === "string" || typeof child === "number") return resolveBoxSides();
303
+ return resolveBoxSides(child.props.margin);
304
+ };
305
+ var getChildWidth = (child) => {
306
+ if (typeof child === "string" || typeof child === "number") return void 0;
307
+ const width = child.props.width;
308
+ return typeof width === "number" ? width : void 0;
309
+ };
310
+ var getChildFlexGrow = (child) => {
311
+ if (typeof child === "string" || typeof child === "number") return void 0;
312
+ const props = child.props;
313
+ const flexGrow = props.flexGrow ?? props.flex;
314
+ return typeof flexGrow === "number" ? Math.max(0, flexGrow) : void 0;
315
+ };
316
+ var isAbsoluteElement = (child) => isPdfJsxElement(child) && child.kind === "absolute";
317
+ var getStaticPlacement = (element) => {
318
+ const placement = element.props.placement ?? "top";
319
+ if (placement !== "top" && placement !== "bottom") throw new Error("@pdfme/jsx: <Static> placement must be \"top\" or \"bottom\".");
320
+ return placement;
321
+ };
322
+ var resolveStackChildWidth = (child, frameWidth, margin) => {
323
+ const width = getChildWidth(child);
324
+ if (width != null) return width;
325
+ return Math.max(0, frameWidth - margin.left - margin.right);
326
+ };
327
+ var applyJustifyContent = (schemas, items, mode, contentMainSize, containerMainSize, opts) => {
328
+ const extraSpace = Math.max(0, containerMainSize - contentMainSize);
329
+ if (extraSpace === 0 || opts.justifyContent === "start") return;
330
+ const extraGap = opts.justifyContent === "space-between" && items.length > 1 ? extraSpace / (items.length - 1) : 0;
331
+ const startOffset = opts.justifyContent === "center" ? extraSpace / 2 : opts.justifyContent === "end" ? extraSpace : 0;
332
+ let runningExtraGap = 0;
333
+ for (const item of items) {
334
+ const offset = startOffset + runningExtraGap;
335
+ if (offset !== 0) shiftSchemas(schemas, item.schemaStart, item.schemaEnd, mode === "row" ? offset : 0, mode === "stack" ? offset : 0);
336
+ runningExtraGap += extraGap;
337
+ }
338
+ };
339
+ var resolveAlignOffset = (containerSize, itemSize, alignItems) => {
340
+ if (alignItems === "center") return Math.max(0, (containerSize - itemSize) / 2);
341
+ if (alignItems === "end") return Math.max(0, containerSize - itemSize);
342
+ return 0;
343
+ };
344
+ var shiftSchemas = (schemas, start, end, dx, dy) => {
345
+ for (let i = start; i < end; i += 1) {
346
+ const schema = schemas[i];
347
+ if (!schema) continue;
348
+ schema.position = {
349
+ x: schema.position.x + dx,
350
+ y: schema.position.y + dy
351
+ };
352
+ }
168
353
  };
169
354
  var renderElement = async (element, frame, parentMode, ctx) => {
355
+ const props = parentMode === "row" && getChildFlexGrow(element) != null ? {
356
+ ...element.props,
357
+ width: frame.width
358
+ } : element.props;
170
359
  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));
360
+ case "stack": return renderStack(props, element.children, frame, ctx);
361
+ case "row": return renderRow(props, element.children, frame, ctx);
362
+ case "box": return renderBox(props, element.children, frame, parentMode, ctx);
363
+ case "spacer": return Promise.resolve(renderSpacer(props));
175
364
  case "text": return renderText({
176
- ...element.props,
365
+ ...props,
366
+ children: element.children
367
+ }, frame, ctx);
368
+ case "multiVariableText": return renderMultiVariableText({
369
+ ...props,
370
+ children: element.children
371
+ }, frame, ctx);
372
+ case "image": return renderImage(props, frame, ctx);
373
+ case "svg": return renderSvg({
374
+ ...props,
177
375
  children: element.children
178
376
  }, frame, ctx);
377
+ case "rectangle": return renderShape("rectangle", props, frame, ctx);
378
+ case "ellipse": return renderShape("ellipse", props, frame, ctx);
379
+ case "line": return renderLine(props, frame, ctx);
179
380
  case "list": return renderList({
180
- ...element.props,
381
+ ...props,
181
382
  children: element.children
182
383
  }, frame, ctx);
183
- case "table": return renderTable(element.props, frame, ctx);
384
+ case "table": return renderTable(props, frame, ctx);
385
+ case "static": throw new Error("@pdfme/jsx: <Static> can only be used as a direct child of the first <Page>.");
386
+ case "absolute": return renderAbsolute(props, element.children, frame, ctx);
184
387
  default: return {
185
388
  width: 0,
186
389
  height: 0
187
390
  };
188
391
  }
189
392
  };
393
+ var renderAbsolute = async (props, children, frame, ctx) => {
394
+ const x = props.x ?? 0;
395
+ const y = props.y ?? 0;
396
+ await layoutChildren(children, {
397
+ x: frame.x + x,
398
+ y: frame.y + y,
399
+ width: props.width ?? Math.max(0, frame.width - x),
400
+ height: props.height ?? Math.max(0, frame.height - y)
401
+ }, "stack", {
402
+ gap: 0,
403
+ alignItems: "stretch",
404
+ justifyContent: "start"
405
+ }, ctx);
406
+ return {
407
+ width: 0,
408
+ height: 0
409
+ };
410
+ };
190
411
  var renderStack = (props, children, frame, ctx) => layoutChildren(children, {
191
412
  ...frame,
192
- width: props.width ?? frame.width
193
- }, "stack", { gap: props.gap ?? 0 }, ctx);
413
+ width: props.width ?? frame.width,
414
+ height: props.height ?? frame.height
415
+ }, "stack", {
416
+ gap: props.gap ?? 0,
417
+ alignItems: props.alignItems ?? "stretch",
418
+ justifyContent: props.justifyContent ?? "start",
419
+ mainSize: props.height
420
+ }, ctx);
194
421
  var renderRow = (props, children, frame, ctx) => layoutChildren(children, {
195
422
  ...frame,
196
423
  width: props.width ?? frame.width,
197
424
  height: props.height ?? frame.height
198
- }, "row", { gap: props.gap ?? 0 }, ctx);
425
+ }, "row", {
426
+ gap: props.gap ?? 0,
427
+ alignItems: props.alignItems ?? "start",
428
+ justifyContent: props.justifyContent ?? "start",
429
+ mainSize: props.width,
430
+ crossSize: props.height
431
+ }, ctx);
199
432
  var renderBox = async (props, children, frame, _parentMode, ctx) => {
200
433
  const width = props.width ?? frame.width;
201
434
  const padding = resolveBoxSides(props.padding);
@@ -223,7 +456,11 @@ var renderBox = async (props, children, frame, _parentMode, ctx) => {
223
456
  y: frame.y + padding.top,
224
457
  width: width - padding.left - padding.right,
225
458
  height: (props.height ?? frame.height) - padding.top - padding.bottom
226
- }, "stack", { gap: 0 }, ctx);
459
+ }, "stack", {
460
+ gap: 0,
461
+ alignItems: "stretch",
462
+ justifyContent: "start"
463
+ }, ctx);
227
464
  const height = props.height ?? childSize.height + padding.top + padding.bottom;
228
465
  if (needsRect) ctx.schemas[beforeCount] = {
229
466
  ...ctx.schemas[beforeCount],
@@ -243,8 +480,10 @@ var renderText = async (props, frame, ctx) => {
243
480
  const lineHeight = props.lineHeight ?? DEFAULT_LINE_HEIGHT;
244
481
  const width = props.width ?? frame.width;
245
482
  const value = childrenToString(props.children);
246
- const name = resolveName(ctx, "text", props.name);
247
483
  const readOnly = props.readOnly ?? props.name == null;
484
+ const textFormat = props.textFormat ?? "plain";
485
+ if (!readOnly && textFormat === "inline-markdown") throw new Error("@pdfme/jsx: editable <Text> does not support textFormat=\"inline-markdown\". Use read-only <Text> or <MultiVariableText>.");
486
+ const name = resolveName(ctx, "text", props.name);
248
487
  const schema = {
249
488
  name,
250
489
  type: "text",
@@ -267,13 +506,14 @@ var renderText = async (props, frame, ctx) => {
267
506
  characterSpacing: props.spacing ?? DEFAULT_CHARACTER_SPACING,
268
507
  fontColor: props.color ?? DEFAULT_FONT_COLOR,
269
508
  backgroundColor: props.background ?? "",
270
- textFormat: props.textFormat ?? "plain",
509
+ textFormat,
271
510
  overflow: props.overflow,
272
511
  strikethrough: props.strikethrough ?? false,
273
512
  underline: props.underline ?? false
274
513
  };
275
514
  if (props.borderColor) schema.borderColor = props.borderColor;
276
- if (props.borderWidth != null) schema.borderWidth = props.borderWidth;
515
+ if (props.borderWidth != null) schema.borderWidth = resolveBoxSides(props.borderWidth);
516
+ if (props.padding != null) schema.padding = resolveBoxSides(props.padding);
277
517
  if (props.dynamicFontSize) schema.dynamicFontSize = {
278
518
  min: props.dynamicFontSize.min ?? DEFAULT_DYNAMIC_FONT_SIZE.min,
279
519
  max: props.dynamicFontSize.max ?? DEFAULT_DYNAMIC_FONT_SIZE.max,
@@ -292,6 +532,174 @@ var renderText = async (props, frame, ctx) => {
292
532
  height: schema.height
293
533
  };
294
534
  };
535
+ var renderMultiVariableText = async (props, frame, ctx) => {
536
+ const fontSize = props.size ?? DEFAULT_FONT_SIZE;
537
+ const lineHeight = props.lineHeight ?? DEFAULT_LINE_HEIGHT;
538
+ const width = props.width ?? frame.width;
539
+ const templateText = props.text ?? childrenToString(props.children);
540
+ const values = normalizeMultiVariableTextValues(props.values);
541
+ const variables = resolveMultiVariableTextVariables(templateText, props.variables, values);
542
+ const name = resolveName(ctx, "multiVariableText", props.name);
543
+ const readOnly = props.readOnly ?? props.name == null;
544
+ const textFormat = props.textFormat ?? "plain";
545
+ const content = readOnly ? substituteMultiVariableText(templateText, values, textFormat === "inline-markdown") : JSON.stringify(values);
546
+ const schema = {
547
+ name,
548
+ type: "multiVariableText",
549
+ content,
550
+ position: {
551
+ x: frame.x,
552
+ y: frame.y
553
+ },
554
+ width,
555
+ height: props.height ?? 0,
556
+ rotate: props.rotate ?? 0,
557
+ opacity: props.opacity ?? 1,
558
+ readOnly,
559
+ required: props.required,
560
+ alignment: props.align ?? "left",
561
+ verticalAlignment: props.valign ?? "top",
562
+ fontSize,
563
+ fontName: props.font ?? ctx.defaultFont,
564
+ lineHeight,
565
+ characterSpacing: props.spacing ?? DEFAULT_CHARACTER_SPACING,
566
+ fontColor: props.color ?? DEFAULT_FONT_COLOR,
567
+ backgroundColor: props.background ?? "",
568
+ textFormat,
569
+ overflow: props.overflow,
570
+ strikethrough: props.strikethrough ?? false,
571
+ underline: props.underline ?? false,
572
+ text: templateText,
573
+ variables
574
+ };
575
+ if (props.borderColor) schema.borderColor = props.borderColor;
576
+ if (props.borderWidth != null) schema.borderWidth = resolveBoxSides(props.borderWidth);
577
+ if (props.padding != null) schema.padding = resolveBoxSides(props.padding);
578
+ if (props.dynamicFontSize) schema.dynamicFontSize = {
579
+ min: props.dynamicFontSize.min ?? DEFAULT_DYNAMIC_FONT_SIZE.min,
580
+ max: props.dynamicFontSize.max ?? DEFAULT_DYNAMIC_FONT_SIZE.max,
581
+ fit: props.dynamicFontSize.fit ?? DEFAULT_DYNAMIC_FONT_SIZE.fit
582
+ };
583
+ if (props.height == null) schema.height = await measureTextHeight({
584
+ value: readOnly ? content : substituteMultiVariableText(templateText, values, textFormat === "inline-markdown"),
585
+ schema,
586
+ font: ctx.font,
587
+ _cache: ctx._cache
588
+ });
589
+ if (!readOnly) ctx.inputs[name] = content;
590
+ ctx.schemas.push(schema);
591
+ return {
592
+ width,
593
+ height: schema.height
594
+ };
595
+ };
596
+ var renderImage = (props, frame, ctx) => {
597
+ const width = props.width ?? frame.width;
598
+ const height = props.height ?? DEFAULT_VISUAL_HEIGHT;
599
+ const name = resolveName(ctx, "image", props.name);
600
+ const readOnly = props.readOnly ?? props.name == null;
601
+ const content = props.src ?? "";
602
+ const schema = {
603
+ name,
604
+ type: "image",
605
+ content,
606
+ position: {
607
+ x: frame.x,
608
+ y: frame.y
609
+ },
610
+ width,
611
+ height,
612
+ rotate: props.rotate ?? 0,
613
+ opacity: props.opacity ?? 1,
614
+ readOnly,
615
+ required: props.required
616
+ };
617
+ if (!readOnly) ctx.inputs[name] = content;
618
+ ctx.schemas.push(schema);
619
+ return {
620
+ width,
621
+ height
622
+ };
623
+ };
624
+ var renderSvg = (props, frame, ctx) => {
625
+ const width = props.width ?? frame.width;
626
+ const height = props.height ?? DEFAULT_VISUAL_HEIGHT;
627
+ const name = resolveName(ctx, "svg", props.name);
628
+ const readOnly = props.readOnly ?? props.name == null;
629
+ const content = props.svg ?? childrenToString(props.children);
630
+ const schema = {
631
+ name,
632
+ type: "svg",
633
+ content,
634
+ position: {
635
+ x: frame.x,
636
+ y: frame.y
637
+ },
638
+ width,
639
+ height,
640
+ rotate: props.rotate ?? 0,
641
+ opacity: props.opacity ?? 1,
642
+ readOnly,
643
+ required: props.required
644
+ };
645
+ if (!readOnly) ctx.inputs[name] = content;
646
+ ctx.schemas.push(schema);
647
+ return {
648
+ width,
649
+ height
650
+ };
651
+ };
652
+ var renderShape = (type, props, frame, ctx) => {
653
+ const width = props.width ?? frame.width;
654
+ const height = props.height ?? DEFAULT_VISUAL_HEIGHT;
655
+ const fill = props.fill ?? "";
656
+ const borderWidth = props.borderWidth ?? (props.borderColor || !fill ? 1 : 0);
657
+ const schema = {
658
+ name: resolveName(ctx, type, props.name),
659
+ type,
660
+ position: {
661
+ x: frame.x,
662
+ y: frame.y
663
+ },
664
+ width,
665
+ height,
666
+ rotate: props.rotate ?? 0,
667
+ opacity: props.opacity ?? 1,
668
+ readOnly: true,
669
+ borderWidth,
670
+ borderColor: props.borderColor ?? (borderWidth > 0 ? DEFAULT_SHAPE_BORDER_COLOR : ""),
671
+ color: fill,
672
+ radius: type === "rectangle" ? props.radius ?? 0 : 0
673
+ };
674
+ ctx.schemas.push(schema);
675
+ return {
676
+ width,
677
+ height
678
+ };
679
+ };
680
+ var renderLine = (props, frame, ctx) => {
681
+ const width = props.width ?? frame.width;
682
+ const height = props.height ?? DEFAULT_LINE_THICKNESS;
683
+ const schema = {
684
+ name: resolveName(ctx, "line", props.name),
685
+ type: "line",
686
+ position: {
687
+ x: frame.x,
688
+ y: frame.y
689
+ },
690
+ width,
691
+ height,
692
+ rotate: props.rotate ?? 0,
693
+ opacity: props.opacity ?? 1,
694
+ readOnly: true,
695
+ color: props.color ?? DEFAULT_LINE_COLOR
696
+ };
697
+ ctx.schemas.push(schema);
698
+ return {
699
+ width,
700
+ height
701
+ };
702
+ };
295
703
  var renderList = (props, frame, ctx) => {
296
704
  const fontSize = props.size ?? DEFAULT_FONT_SIZE;
297
705
  const lineHeight = props.lineHeight ?? DEFAULT_LINE_HEIGHT;
@@ -363,7 +771,7 @@ var renderTable = (props, frame, ctx) => {
363
771
  showHead,
364
772
  repeatHead: props.repeatHead ?? false,
365
773
  head: props.head,
366
- headWidthPercentages: normalizeColumnWidths(props.widths, props.head.length),
774
+ headWidthPercentages: normalizeColumnWeights(props.columnWeights, props.head.length),
367
775
  tableStyles: {
368
776
  borderColor: props.tableStyles?.borderColor ?? "#000000",
369
777
  borderWidth: props.tableStyles?.borderWidth ?? .3
@@ -402,8 +810,47 @@ var normalizeListItems = (props) => {
402
810
  }));
403
811
  };
404
812
  var serializeListItem = (item) => `${" ".repeat(Math.max(0, item.level))}${item.text}`;
405
- var normalizeColumnWidths = (widths, columnCount) => {
406
- if (widths && widths.length > 0) return widths;
813
+ var normalizeMultiVariableTextValues = (values) => {
814
+ const normalized = {};
815
+ Object.entries(values ?? {}).forEach(([key, value]) => {
816
+ normalized[key] = value == null ? "" : String(value);
817
+ });
818
+ return normalized;
819
+ };
820
+ var resolveMultiVariableTextVariables = (templateText, variables, values) => {
821
+ const result = [];
822
+ const seen = /* @__PURE__ */ new Set();
823
+ const add = (name) => {
824
+ if (!seen.has(name)) {
825
+ seen.add(name);
826
+ result.push(name);
827
+ }
828
+ };
829
+ variables?.forEach(add);
830
+ getVariableNames(templateText).forEach(add);
831
+ Object.keys(values).forEach(add);
832
+ return result;
833
+ };
834
+ var substituteMultiVariableText = (templateText, values, escapeMarkdown) => {
835
+ let result = "";
836
+ let lastIndex = 0;
837
+ visitVariables(templateText, ({ name, startIndex, endIndex }) => {
838
+ result += templateText.slice(lastIndex, startIndex);
839
+ const value = values[name];
840
+ if (value != null) result += escapeMarkdown ? escapeInlineMarkdown(value) : value;
841
+ lastIndex = endIndex + 1;
842
+ });
843
+ return result + templateText.slice(lastIndex);
844
+ };
845
+ var normalizeColumnWeights = (columnWeights, columnCount) => {
846
+ if (columnWeights && columnWeights.length > 0) {
847
+ const normalizedWidths = Array.from({ length: columnCount }, (_, index) => {
848
+ const width = columnWeights[index];
849
+ return typeof width === "number" && Number.isFinite(width) && width > 0 ? width : 1;
850
+ });
851
+ const totalWidth = normalizedWidths.reduce((sum, width) => sum + width, 0);
852
+ if (totalWidth > 0) return normalizedWidths.map((width) => width / totalWidth * 100);
853
+ }
407
854
  if (columnCount <= 0) return [];
408
855
  return Array.from({ length: columnCount }, () => 100 / columnCount);
409
856
  };
@@ -491,6 +938,26 @@ var PAGE_BREAK_PARENT_KINDS = new Set([
491
938
  "stack",
492
939
  "box"
493
940
  ]);
941
+ var STATIC_CONTAINER_KINDS = new Set([
942
+ "absolute",
943
+ "stack",
944
+ "row",
945
+ "box"
946
+ ]);
947
+ var STATIC_LEAF_KINDS = new Set([
948
+ "spacer",
949
+ "text",
950
+ "image",
951
+ "svg",
952
+ "rectangle",
953
+ "ellipse",
954
+ "line"
955
+ ]);
956
+ var ABSOLUTE_PARENT_KINDS = new Set([
957
+ "page",
958
+ "static",
959
+ "box"
960
+ ]);
494
961
  var validatePageBreakPlacement = (node, parentKind = void 0, canBreak = false) => {
495
962
  for (const child of flattenForSplitting(node)) {
496
963
  if (!isPdfJsxElement(child)) continue;
@@ -502,7 +969,61 @@ var validatePageBreakPlacement = (node, parentKind = void 0, canBreak = false) =
502
969
  validatePageBreakPlacement(child.children, child.kind, childCanBreak);
503
970
  }
504
971
  };
972
+ var validateAbsolutePlacement = (node, parentKind = void 0) => {
973
+ for (const child of flattenForSplitting(node)) {
974
+ if (!isPdfJsxElement(child)) continue;
975
+ if (child.kind === "absolute" && (!parentKind || !ABSOLUTE_PARENT_KINDS.has(parentKind))) throw new Error("@pdfme/jsx: <Absolute> can only be used inside <Page>, <Static>, or <Box>.");
976
+ validateAbsolutePlacement(child.children, child.kind);
977
+ }
978
+ };
979
+ var validateStaticPlacement = (pages) => {
980
+ for (let pageIndex = 0; pageIndex < pages.length; pageIndex += 1) {
981
+ const page = pages[pageIndex];
982
+ for (const child of flattenForSplitting(page?.children ?? [])) {
983
+ if (isPdfJsxElement(child) && child.kind === "static") {
984
+ if (pageIndex !== 0) throw new Error("@pdfme/jsx: <Static> must appear before any <PageBreak> and can only be used inside the first <Page>.");
985
+ if (getStaticPlacement(child) === "bottom" && hasElementKind(child.children, "absolute")) throw new Error("@pdfme/jsx: <Absolute> is not supported inside bottom <Static>. Use top <Static> or <Page> for fixed page coordinates.");
986
+ validateStaticChildren(child.children);
987
+ continue;
988
+ }
989
+ validateNoNestedStatic(child);
990
+ }
991
+ }
992
+ };
993
+ var validateNoNestedStatic = (node) => {
994
+ if (!isPdfJsxElement(node)) return;
995
+ for (const child of flattenForSplitting(node.children)) {
996
+ if (isPdfJsxElement(child) && child.kind === "static") throw new Error("@pdfme/jsx: <Static> can only be used as a direct child of the first <Page>.");
997
+ validateNoNestedStatic(child);
998
+ }
999
+ };
1000
+ var hasElementKind = (node, kind) => {
1001
+ for (const child of flattenForSplitting(node)) {
1002
+ if (!isPdfJsxElement(child)) continue;
1003
+ if (child.kind === kind || hasElementKind(child.children, kind)) return true;
1004
+ }
1005
+ return false;
1006
+ };
1007
+ var validateNoTopLevelStatic = (node) => {
1008
+ for (const child of flattenChildren(node)) if (isPdfJsxElement(child) && child.kind === "static") throw new Error("@pdfme/jsx: <Static> can only be used as a direct child of the first <Page>.");
1009
+ };
1010
+ var validateStaticChildren = (children) => {
1011
+ for (const child of flattenForSplitting(children)) {
1012
+ if (!isPdfJsxElement(child)) continue;
1013
+ if (STATIC_CONTAINER_KINDS.has(child.kind)) {
1014
+ validateStaticChildren(child.children);
1015
+ continue;
1016
+ }
1017
+ 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.`);
1018
+ validateStaticLeafProps(child);
1019
+ }
1020
+ };
1021
+ var validateStaticLeafProps = (element) => {
1022
+ if (element.kind !== "text" && element.kind !== "image" && element.kind !== "svg") return;
1023
+ const props = element.props;
1024
+ if (props.readOnly === false || props.name != null && props.readOnly !== true) throw new Error("@pdfme/jsx: <Static> children must be read-only.");
1025
+ };
505
1026
  //#endregion
506
- export { Box, List, Page, PageBreak, Row, Spacer, Stack, Table, Text, renderToTemplate };
1027
+ export { Absolute, Box, Ellipse, Footer, Header, Image, Line, List, MultiVariableText, Page, PageBreak, Rectangle, Row, Spacer, Stack, Static, Svg, Table, Text, renderToTemplate };
507
1028
 
508
1029
  //# sourceMappingURL=index.js.map