@pdfme/schemas 6.0.6 → 6.1.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.
@@ -0,0 +1,1388 @@
1
+ import { A as FONT_SIZE_ADJUSTMENT, B as VERTICAL_ALIGN_MIDDLE, C as DEFAULT_ALIGNMENT, D as DEFAULT_TEXT_FORMAT, E as DEFAULT_FONT_VARIANT_FALLBACK, F as SYNTHETIC_BOLD_CSS_TEXT_SHADOW, I as SYNTHETIC_BOLD_OFFSET_RATIO, L as TEXT_FORMAT_INLINE_MARKDOWN, M as FONT_VARIANT_FALLBACK_PLAIN, N as FONT_VARIANT_FALLBACK_SYNTHETIC, O as DYNAMIC_FIT_HORIZONTAL, P as PLACEHOLDER_FONT_COLOR, R as TEXT_FORMAT_PLAIN, S as CODE_HORIZONTAL_PADDING, T as DEFAULT_FONT_COLOR, _ as widthOfTextAtSize, b as ALIGN_RIGHT, d as getBrowserVerticalFontAdjustments, f as getFontDescentInPt, g as splitTextToSize, h as isFirefox, j as FONT_VARIANT_FALLBACK_ERROR, k as DYNAMIC_FIT_VERTICAL, l as calculateDynamicFontSize, m as heightOfFontAtSize, p as getFontKitFont, s as HEX_COLOR_PATTERN, u as fetchRemoteFontData, v as ALIGN_CENTER, w as DEFAULT_DYNAMIC_FIT, x as CODE_BACKGROUND_COLOR, y as ALIGN_JUSTIFY, z as VERTICAL_ALIGN_BOTTOM } from "./dynamicTemplate-Dsrw11aL.js";
2
+ import { convertForPdfLayoutProps, createSvgStr, hex2PrintingColor, isEditable, rotatePoint } from "./utils.js";
3
+ import { DEFAULT_FONT_NAME, getDefaultFont, getFallbackFontName, mm2pt, pt2mm } from "@pdfme/common";
4
+ import { AlignCenter, AlignJustify, AlignLeft, AlignRight, ArrowDownToLine, ArrowUpToLine, Strikethrough, TextCursorInput, Underline } from "lucide";
5
+ //#region src/text/inlineMarkdown.ts
6
+ var MARKDOWN_ESCAPABLE_CHARS = new Set([
7
+ "\\",
8
+ "*",
9
+ "~",
10
+ "`"
11
+ ]);
12
+ var MARKDOWN_ESCAPE_PATTERN = /[\\*~`]/g;
13
+ var MARKDOWN_UNESCAPE_PATTERN = /\\([\\*~`])/g;
14
+ var sameStyle = (a, b) => Boolean(a.bold) === Boolean(b.bold) && Boolean(a.italic) === Boolean(b.italic) && Boolean(a.strikethrough) === Boolean(b.strikethrough) && Boolean(a.code) === Boolean(b.code);
15
+ var appendRun = (runs, text, style) => {
16
+ if (!text) return;
17
+ const lastRun = runs.at(-1);
18
+ if (lastRun && sameStyle(lastRun, style)) {
19
+ lastRun.text += text;
20
+ return;
21
+ }
22
+ runs.push({
23
+ text,
24
+ ...style.bold ? { bold: true } : {},
25
+ ...style.italic ? { italic: true } : {},
26
+ ...style.strikethrough ? { strikethrough: true } : {},
27
+ ...style.code ? { code: true } : {}
28
+ });
29
+ };
30
+ var findClosingDelimiter = (value, delimiter, from) => {
31
+ for (let i = from; i < value.length; i++) {
32
+ if (value[i] === "\\") {
33
+ i += 1;
34
+ continue;
35
+ }
36
+ if (delimiter !== "`" && value[i] === "`") {
37
+ const codeEnd = findClosingDelimiter(value, "`", i + 1);
38
+ if (codeEnd === -1) continue;
39
+ i = codeEnd;
40
+ continue;
41
+ }
42
+ if (value.startsWith(delimiter, i)) return i;
43
+ }
44
+ return -1;
45
+ };
46
+ var getDelimiter = (value, index) => {
47
+ if (value[index] === "`") return "`";
48
+ if (value.startsWith("***", index)) return "***";
49
+ if (value.startsWith("**", index)) return "**";
50
+ if (value.startsWith("~~", index)) return "~~";
51
+ if (value[index] === "*") return "*";
52
+ return "";
53
+ };
54
+ var mergeStyle = (style, delimiter) => {
55
+ if (delimiter === "***") return {
56
+ ...style,
57
+ bold: true,
58
+ italic: true
59
+ };
60
+ if (delimiter === "**") return {
61
+ ...style,
62
+ bold: true
63
+ };
64
+ if (delimiter === "*") return {
65
+ ...style,
66
+ italic: true
67
+ };
68
+ if (delimiter === "~~") return {
69
+ ...style,
70
+ strikethrough: true
71
+ };
72
+ return style;
73
+ };
74
+ var parseRange = (value, from, to, style) => {
75
+ const runs = [];
76
+ let buffer = "";
77
+ const flush = () => {
78
+ appendRun(runs, buffer, style);
79
+ buffer = "";
80
+ };
81
+ for (let index = from; index < to; index++) {
82
+ const char = value[index];
83
+ if (char === "\\" && index + 1 < to && MARKDOWN_ESCAPABLE_CHARS.has(value[index + 1])) {
84
+ buffer += value[index + 1];
85
+ index += 1;
86
+ continue;
87
+ }
88
+ const delimiter = getDelimiter(value, index);
89
+ if (!delimiter) {
90
+ buffer += char;
91
+ continue;
92
+ }
93
+ const closingIndex = findClosingDelimiter(value, delimiter, index + delimiter.length);
94
+ if (closingIndex === -1 || closingIndex + delimiter.length > to) {
95
+ buffer += char;
96
+ continue;
97
+ }
98
+ flush();
99
+ if (delimiter === "`") appendRun(runs, value.slice(index + 1, closingIndex).replace(MARKDOWN_UNESCAPE_PATTERN, "$1"), {
100
+ ...style,
101
+ code: true
102
+ });
103
+ else parseRange(value, index + delimiter.length, closingIndex, mergeStyle(style, delimiter)).forEach((run) => appendRun(runs, run.text, run));
104
+ index = closingIndex + delimiter.length - 1;
105
+ }
106
+ flush();
107
+ return runs;
108
+ };
109
+ var parseInlineMarkdown = (value) => {
110
+ if (!value) return [];
111
+ return parseRange(value, 0, value.length, {});
112
+ };
113
+ var escapeInlineMarkdown = (value) => value.replace(MARKDOWN_ESCAPE_PATTERN, (char) => `\\${char}`);
114
+ var stripInlineMarkdown = (value) => parseInlineMarkdown(value).map((run) => run.text).join("");
115
+ //#endregion
116
+ //#region src/text/richText.ts
117
+ var richTextWordSegmenter = new Intl.Segmenter(void 0, { granularity: "word" });
118
+ var richTextGraphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
119
+ var getBaseFontName = (schema, font) => schema.fontName && font[schema.fontName] ? schema.fontName : getFallbackFontName(font);
120
+ var getLoadedFontName = (font, fontName) => fontName && font[fontName] ? fontName : void 0;
121
+ var isInlineMarkdownTextSchema = (schema) => schema.textFormat === "inline-markdown" && !(schema.type === "text" && schema.readOnly !== true);
122
+ var resolveFontVariant = (run, schema, font) => {
123
+ const baseFontName = getBaseFontName(schema, font);
124
+ const variants = schema.fontVariants ?? {};
125
+ const fallback = schema.fontVariantFallback ?? "synthetic";
126
+ let fontName = baseFontName;
127
+ let needsBold = Boolean(run.bold);
128
+ let needsItalic = Boolean(run.italic);
129
+ if (run.code) fontName = getLoadedFontName(font, variants.code) ?? baseFontName;
130
+ else if (run.bold && run.italic) {
131
+ const boldItalic = getLoadedFontName(font, variants.boldItalic);
132
+ const italic = getLoadedFontName(font, variants.italic);
133
+ const bold = getLoadedFontName(font, variants.bold);
134
+ if (boldItalic) {
135
+ fontName = boldItalic;
136
+ needsBold = false;
137
+ needsItalic = false;
138
+ } else if (italic) {
139
+ fontName = italic;
140
+ needsItalic = false;
141
+ } else if (bold) {
142
+ fontName = bold;
143
+ needsBold = false;
144
+ }
145
+ } else if (run.bold) {
146
+ const bold = getLoadedFontName(font, variants.bold);
147
+ if (bold) {
148
+ fontName = bold;
149
+ needsBold = false;
150
+ }
151
+ } else if (run.italic) {
152
+ const italic = getLoadedFontName(font, variants.italic);
153
+ if (italic) {
154
+ fontName = italic;
155
+ needsItalic = false;
156
+ }
157
+ }
158
+ if ((needsBold || needsItalic || run.code && !getLoadedFontName(font, variants.code)) && fallback === "error") throw new Error(`[@pdfme/schemas] Missing font variant for markdown text in field "${schema.name}".`);
159
+ return {
160
+ fontName,
161
+ syntheticBold: fallback !== "plain" && needsBold,
162
+ syntheticItalic: fallback !== "plain" && needsItalic
163
+ };
164
+ };
165
+ var resolveRichTextRuns = async (arg) => {
166
+ const { runs, schema, font, _cache } = arg;
167
+ const fontKitCache = /* @__PURE__ */ new Map();
168
+ const getResolvedFontKitFont = async (fontName) => {
169
+ const cached = fontKitCache.get(fontName);
170
+ if (cached) return cached;
171
+ const fontKitFont = await getFontKitFont(fontName, font, _cache);
172
+ fontKitCache.set(fontName, fontKitFont);
173
+ return fontKitFont;
174
+ };
175
+ return Promise.all(runs.map(async (run) => {
176
+ const resolution = resolveFontVariant(run, schema, font);
177
+ return {
178
+ ...run,
179
+ ...resolution,
180
+ fontKitFont: await getResolvedFontKitFont(resolution.fontName)
181
+ };
182
+ }));
183
+ };
184
+ var measureRunText = (run, text, fontSize, characterSpacing) => {
185
+ const syntheticBoldWidth = run.syntheticBold ? fontSize * SYNTHETIC_BOLD_OFFSET_RATIO * 2 : 0;
186
+ const syntheticItalicWidth = run.syntheticItalic ? heightOfFontAtSize(run.fontKitFont, fontSize) * Math.tan(12 * Math.PI / 180) : 0;
187
+ return widthOfTextAtSize(text, run.fontKitFont, fontSize, characterSpacing) + syntheticBoldWidth + syntheticItalicWidth;
188
+ };
189
+ var createLine = () => ({
190
+ runs: [],
191
+ width: 0,
192
+ hardBreak: false
193
+ });
194
+ var pushRunToLine = (line, run, text, fontSize, characterSpacing) => {
195
+ if (!text) return;
196
+ const width = measureRunText(run, text, fontSize, characterSpacing);
197
+ if (line.runs.length > 0) line.width += characterSpacing;
198
+ line.runs.push({
199
+ ...run,
200
+ text,
201
+ width
202
+ });
203
+ line.width += width;
204
+ };
205
+ var measurePiecesWidth = (pieces, fontSize, characterSpacing) => {
206
+ let width = 0;
207
+ let hasText = false;
208
+ pieces.forEach((piece) => {
209
+ if (!piece.text) return;
210
+ if (hasText) width += characterSpacing;
211
+ width += measureRunText(piece.run, piece.text, fontSize, characterSpacing);
212
+ hasText = true;
213
+ });
214
+ return width;
215
+ };
216
+ var sliceRunPieces = (pieces, startIndex, endIndex) => {
217
+ const result = [];
218
+ let offset = 0;
219
+ pieces.forEach((piece) => {
220
+ const pieceStart = offset;
221
+ const pieceEnd = pieceStart + piece.text.length;
222
+ const sliceStart = Math.max(startIndex, pieceStart);
223
+ const sliceEnd = Math.min(endIndex, pieceEnd);
224
+ if (sliceStart < sliceEnd) result.push({
225
+ run: piece.run,
226
+ text: piece.text.slice(sliceStart - pieceStart, sliceEnd - pieceStart)
227
+ });
228
+ offset = pieceEnd;
229
+ });
230
+ return result;
231
+ };
232
+ var segmentRunPiecesByWord = (runs, onSegment, onHardBreak) => {
233
+ let paragraphPieces = [];
234
+ const flushParagraph = () => {
235
+ if (paragraphPieces.length === 0) return;
236
+ const paragraphText = paragraphPieces.map((piece) => piece.text).join("");
237
+ Array.from(richTextWordSegmenter.segment(paragraphText), ({ segment, index }) => {
238
+ const pieces = sliceRunPieces(paragraphPieces, index, index + segment.length);
239
+ if (pieces.length > 0) onSegment(pieces);
240
+ });
241
+ paragraphPieces = [];
242
+ };
243
+ runs.forEach((run) => {
244
+ run.text.split(/(\r\n|\r|\n)/).forEach((part) => {
245
+ if (part === "\r\n" || part === "\r" || part === "\n") {
246
+ flushParagraph();
247
+ onHardBreak();
248
+ return;
249
+ }
250
+ if (part) paragraphPieces.push({
251
+ run,
252
+ text: part
253
+ });
254
+ });
255
+ });
256
+ flushParagraph();
257
+ };
258
+ var splitIntoGraphemes = (value) => Array.from(richTextGraphemeSegmenter.segment(value), ({ segment }) => segment);
259
+ var countRichTextLineGraphemes = (line) => splitIntoGraphemes(line.runs.map((run) => run.text).join("")).length;
260
+ var layoutRichTextLines = (arg) => {
261
+ const { runs, fontSize, characterSpacing, boxWidthInPt } = arg;
262
+ const lines = [];
263
+ let currentLine = createLine();
264
+ const pushCurrentLine = (hardBreak) => {
265
+ currentLine.hardBreak = hardBreak;
266
+ lines.push(currentLine);
267
+ currentLine = createLine();
268
+ };
269
+ const pushPiecesToLine = (pieces) => {
270
+ pieces.forEach((piece) => {
271
+ pushRunToLine(currentLine, piece.run, piece.text, fontSize, characterSpacing);
272
+ });
273
+ };
274
+ const pushOversizedText = (run, text) => {
275
+ let remainingText = text;
276
+ while (remainingText.length > 0) {
277
+ const pendingSpacing = currentLine.runs.length > 0 ? characterSpacing : 0;
278
+ const remainingWidth = Math.max(boxWidthInPt - currentLine.width - pendingSpacing, 0);
279
+ const remainingTextWidth = measureRunText(run, remainingText, fontSize, characterSpacing);
280
+ if (remainingTextWidth <= remainingWidth || currentLine.runs.length === 0 && remainingTextWidth <= boxWidthInPt) {
281
+ pushRunToLine(currentLine, run, remainingText, fontSize, characterSpacing);
282
+ return;
283
+ }
284
+ if (currentLine.runs.length > 0 && remainingTextWidth <= boxWidthInPt) {
285
+ pushCurrentLine(false);
286
+ continue;
287
+ }
288
+ const graphemes = splitIntoGraphemes(remainingText);
289
+ let fittingText = "";
290
+ let fittingLength = 0;
291
+ for (const grapheme of graphemes) {
292
+ const candidate = fittingText + grapheme;
293
+ const candidateWidth = measureRunText(run, candidate, fontSize, characterSpacing);
294
+ const maxWidth = currentLine.runs.length === 0 ? boxWidthInPt : remainingWidth;
295
+ if (candidateWidth > maxWidth) {
296
+ if (fittingText) break;
297
+ if (currentLine.runs.length > 0) break;
298
+ }
299
+ fittingText = candidate;
300
+ fittingLength += grapheme.length;
301
+ if (candidateWidth > maxWidth) break;
302
+ }
303
+ if (!fittingText) {
304
+ pushCurrentLine(false);
305
+ continue;
306
+ }
307
+ pushRunToLine(currentLine, run, fittingText, fontSize, characterSpacing);
308
+ remainingText = remainingText.slice(fittingLength);
309
+ if (remainingText.length > 0) pushCurrentLine(false);
310
+ }
311
+ };
312
+ const pushSegment = (pieces) => {
313
+ const segmentWidth = measurePiecesWidth(pieces, fontSize, characterSpacing);
314
+ const pendingSpacing = currentLine.runs.length > 0 ? characterSpacing : 0;
315
+ if (segmentWidth <= Math.max(boxWidthInPt - currentLine.width - pendingSpacing, 0) || currentLine.runs.length === 0 && segmentWidth <= boxWidthInPt) {
316
+ pushPiecesToLine(pieces);
317
+ return;
318
+ }
319
+ if (currentLine.runs.length > 0) {
320
+ pushCurrentLine(false);
321
+ if (segmentWidth <= boxWidthInPt) {
322
+ pushPiecesToLine(pieces);
323
+ return;
324
+ }
325
+ }
326
+ pieces.forEach((piece) => pushOversizedText(piece.run, piece.text));
327
+ };
328
+ segmentRunPiecesByWord(runs, pushSegment, () => pushCurrentLine(true));
329
+ if (currentLine.runs.length > 0 || lines.length === 0) pushCurrentLine(false);
330
+ return lines;
331
+ };
332
+ var measureParagraphWidths = (runs, fontSize, characterSpacing) => {
333
+ const widths = [];
334
+ let paragraphPieces = [];
335
+ const pushWidth = () => {
336
+ widths.push(measurePiecesWidth(paragraphPieces, fontSize, characterSpacing));
337
+ paragraphPieces = [];
338
+ };
339
+ runs.forEach((run) => {
340
+ run.text.split(/(\r\n|\r|\n)/).forEach((part) => {
341
+ if (part === "\r\n" || part === "\r" || part === "\n") {
342
+ pushWidth();
343
+ return;
344
+ }
345
+ if (part) paragraphPieces.push({
346
+ run,
347
+ text: part
348
+ });
349
+ });
350
+ });
351
+ pushWidth();
352
+ return widths;
353
+ };
354
+ var getLineHeightAtSize = (line, fontSize) => {
355
+ if (line.runs.length === 0) return fontSize;
356
+ return Math.max(...line.runs.map((run) => heightOfFontAtSize(run.fontKitFont, fontSize)));
357
+ };
358
+ var calculateDynamicRichTextFontSize = async (arg) => {
359
+ const { value, schema, font, _cache, startingFontSize } = arg;
360
+ const { fontSize: schemaFontSize, dynamicFontSize: dynamicFontSizeSetting, characterSpacing: schemaCharacterSpacing, width: boxWidth, height: boxHeight, lineHeight = 1 } = schema;
361
+ const fontSize = startingFontSize || schemaFontSize || 13;
362
+ if (!dynamicFontSizeSetting) return fontSize;
363
+ if (dynamicFontSizeSetting.max < dynamicFontSizeSetting.min) return fontSize;
364
+ const resolvedRuns = await resolveRichTextRuns({
365
+ runs: parseInlineMarkdown(value),
366
+ schema,
367
+ font,
368
+ _cache
369
+ });
370
+ const characterSpacing = schemaCharacterSpacing ?? 0;
371
+ const dynamicFontFit = dynamicFontSizeSetting.fit ?? "vertical";
372
+ const boxWidthInPt = mm2pt(boxWidth);
373
+ let dynamicFontSize = fontSize;
374
+ if (dynamicFontSize < dynamicFontSizeSetting.min) dynamicFontSize = dynamicFontSizeSetting.min;
375
+ else if (dynamicFontSize > dynamicFontSizeSetting.max) dynamicFontSize = dynamicFontSizeSetting.max;
376
+ const calculateConstraints = (size) => {
377
+ let totalWidthInMm = 0;
378
+ let totalHeightInMm = 0;
379
+ layoutRichTextLines({
380
+ runs: resolvedRuns,
381
+ fontSize: size,
382
+ characterSpacing,
383
+ boxWidthInPt
384
+ }).forEach((line, lineIndex) => {
385
+ if (dynamicFontFit === "vertical") totalWidthInMm = Math.max(totalWidthInMm, pt2mm(line.width));
386
+ if (lineIndex === 0) totalHeightInMm += pt2mm(getLineHeightAtSize(line, size) * lineHeight);
387
+ else totalHeightInMm += pt2mm(size * lineHeight);
388
+ });
389
+ if (dynamicFontFit === "horizontal") measureParagraphWidths(resolvedRuns, size, characterSpacing).forEach((paragraphWidth) => {
390
+ totalWidthInMm = Math.max(totalWidthInMm, pt2mm(paragraphWidth));
391
+ });
392
+ return {
393
+ totalWidthInMm,
394
+ totalHeightInMm
395
+ };
396
+ };
397
+ const shouldFontGrowToFit = (totalWidthInMm, totalHeightInMm) => {
398
+ if (dynamicFontSize >= dynamicFontSizeSetting.max) return false;
399
+ if (dynamicFontFit === "horizontal") return totalWidthInMm < boxWidth;
400
+ return totalHeightInMm < boxHeight;
401
+ };
402
+ const shouldFontShrinkToFit = (totalWidthInMm, totalHeightInMm) => {
403
+ if (dynamicFontSize <= dynamicFontSizeSetting.min || dynamicFontSize <= 0) return false;
404
+ return totalWidthInMm > boxWidth || totalHeightInMm > boxHeight;
405
+ };
406
+ let { totalWidthInMm, totalHeightInMm } = calculateConstraints(dynamicFontSize);
407
+ while (shouldFontGrowToFit(totalWidthInMm, totalHeightInMm)) {
408
+ dynamicFontSize += FONT_SIZE_ADJUSTMENT;
409
+ const { totalWidthInMm: newWidth, totalHeightInMm: newHeight } = calculateConstraints(dynamicFontSize);
410
+ if (newHeight < boxHeight) {
411
+ totalWidthInMm = newWidth;
412
+ totalHeightInMm = newHeight;
413
+ } else {
414
+ dynamicFontSize -= FONT_SIZE_ADJUSTMENT;
415
+ break;
416
+ }
417
+ }
418
+ while (shouldFontShrinkToFit(totalWidthInMm, totalHeightInMm)) {
419
+ dynamicFontSize -= FONT_SIZE_ADJUSTMENT;
420
+ ({totalWidthInMm, totalHeightInMm} = calculateConstraints(dynamicFontSize));
421
+ }
422
+ return dynamicFontSize;
423
+ };
424
+ //#endregion
425
+ //#region src/text/richTextPdfRender.ts
426
+ var getSyntheticBoldWidth = (run, fontSize) => run.syntheticBold ? fontSize * SYNTHETIC_BOLD_OFFSET_RATIO * 2 : 0;
427
+ var getSyntheticItalicWidth = (run, fontSize) => run.syntheticItalic ? heightOfFontAtSize(run.fontKitFont, fontSize) * Math.tan(12 * Math.PI / 180) : 0;
428
+ var getRunWidth = (run, fontSize, characterSpacing) => widthOfTextAtSize(run.text, run.fontKitFont, fontSize, characterSpacing) + getSyntheticBoldWidth(run, fontSize) + getSyntheticItalicWidth(run, fontSize);
429
+ var getPdfFont = (run, pdfFontObj) => {
430
+ const pdfFont = pdfFontObj[run.fontName];
431
+ if (!pdfFont) throw new Error(`[@pdfme/schemas] Missing embedded font "${run.fontName}".`);
432
+ return pdfFont;
433
+ };
434
+ var drawDecorationLine = (arg) => {
435
+ const { page, x, y, width, rotate, pivotPoint, fontSize, color, opacity } = arg;
436
+ if (width <= 0) return;
437
+ page.drawLine({
438
+ start: rotatePoint({
439
+ x,
440
+ y
441
+ }, pivotPoint, rotate.angle),
442
+ end: rotatePoint({
443
+ x: x + width,
444
+ y
445
+ }, pivotPoint, rotate.angle),
446
+ thickness: 1 / 12 * fontSize,
447
+ color,
448
+ opacity
449
+ });
450
+ };
451
+ var drawRun = (arg) => {
452
+ const { page, pdfLib, run, pdfFont, x, y, rotate, pivotPoint, fontSize, lineHeight, color, opacity, colorType, characterSpacing, strikethrough } = arg;
453
+ const runWidth = getRunWidth(run, fontSize, characterSpacing);
454
+ const textHeight = heightOfFontAtSize(run.fontKitFont, fontSize);
455
+ if (run.code) {
456
+ const padding = CODE_HORIZONTAL_PADDING;
457
+ const bgX = x - padding;
458
+ const bgY = y - textHeight * .2;
459
+ const bgPoint = rotate.angle === 0 ? {
460
+ x: bgX,
461
+ y: bgY
462
+ } : rotatePoint({
463
+ x: bgX,
464
+ y: bgY
465
+ }, pivotPoint, rotate.angle);
466
+ page.drawRectangle({
467
+ x: bgPoint.x,
468
+ y: bgPoint.y,
469
+ width: runWidth + padding * 2,
470
+ height: textHeight * 1.2,
471
+ rotate,
472
+ color: hex2PrintingColor(CODE_BACKGROUND_COLOR, colorType),
473
+ opacity
474
+ });
475
+ }
476
+ if (strikethrough && runWidth > 0) drawDecorationLine({
477
+ page,
478
+ x,
479
+ y: y + textHeight / 3,
480
+ width: runWidth,
481
+ rotate,
482
+ pivotPoint,
483
+ fontSize,
484
+ color,
485
+ opacity
486
+ });
487
+ const drawAt = (drawX) => {
488
+ const point = rotate.angle === 0 ? {
489
+ x: drawX,
490
+ y
491
+ } : rotatePoint({
492
+ x: drawX,
493
+ y
494
+ }, pivotPoint, rotate.angle);
495
+ page.drawText(run.text, {
496
+ x: point.x,
497
+ y: point.y,
498
+ rotate,
499
+ size: fontSize,
500
+ color,
501
+ lineHeight: lineHeight * fontSize,
502
+ font: pdfFont,
503
+ opacity,
504
+ ...run.syntheticItalic ? { ySkew: pdfLib.degrees(12) } : {}
505
+ });
506
+ };
507
+ drawAt(x);
508
+ if (run.syntheticBold) {
509
+ const offset = fontSize * SYNTHETIC_BOLD_OFFSET_RATIO;
510
+ for (let i = 1; i <= 2; i++) drawAt(x + offset * i);
511
+ }
512
+ };
513
+ var renderInlineMarkdownText = async (arg) => {
514
+ const { value, schema, font, pdfFontObj, fontKitFont, page, pdfLib, _cache, colorType, fontSize, color, alignment, verticalAlignment, lineHeight, characterSpacing, x, width, height, pageHeight, pivotPoint, rotate, opacity } = arg;
515
+ const lines = layoutRichTextLines({
516
+ runs: await resolveRichTextRuns({
517
+ runs: parseInlineMarkdown(value),
518
+ schema,
519
+ font,
520
+ _cache
521
+ }),
522
+ fontSize,
523
+ characterSpacing,
524
+ boxWidthInPt: width
525
+ });
526
+ const firstLineTextHeight = heightOfFontAtSize(fontKitFont, fontSize);
527
+ const descent = getFontDescentInPt(fontKitFont, fontSize);
528
+ const halfLineHeightAdjustment = lineHeight === 0 ? 0 : (lineHeight - 1) * fontSize / 2;
529
+ let yOffset = 0;
530
+ if (verticalAlignment === "top") yOffset = firstLineTextHeight + halfLineHeightAdjustment;
531
+ else {
532
+ const otherLinesHeight = lineHeight * fontSize * (lines.length - 1);
533
+ if (verticalAlignment === "bottom") yOffset = height - otherLinesHeight + descent - halfLineHeightAdjustment;
534
+ else if (verticalAlignment === "middle") yOffset = (height - otherLinesHeight - firstLineTextHeight + descent) / 2 + firstLineTextHeight;
535
+ }
536
+ lines.forEach((line, rowIndex) => {
537
+ if (line.runs.length === 0) return;
538
+ let textWidth = line.width;
539
+ let spacing = characterSpacing;
540
+ if (alignment === "justify" && !line.hardBreak && rowIndex < lines.length - 1) {
541
+ const graphemeCount = countRichTextLineGraphemes(line);
542
+ if (graphemeCount > 0) {
543
+ spacing += (width - textWidth) / graphemeCount;
544
+ textWidth = width;
545
+ }
546
+ }
547
+ let xLine = x;
548
+ if (alignment === "center") xLine += (width - textWidth) / 2;
549
+ else if (alignment === "right") xLine += width - textWidth;
550
+ const yLine = pageHeight - mm2pt(schema.position.y) - yOffset - lineHeight * fontSize * rowIndex;
551
+ page.pushOperators(pdfLib.setCharacterSpacing(spacing));
552
+ if (schema.strikethrough || schema.underline) {
553
+ const textHeight = Math.max(...line.runs.map((run) => heightOfFontAtSize(run.fontKitFont, fontSize)));
554
+ if (schema.strikethrough) drawDecorationLine({
555
+ page,
556
+ x: xLine,
557
+ y: yLine + textHeight / 3,
558
+ width: textWidth,
559
+ rotate,
560
+ pivotPoint,
561
+ fontSize,
562
+ color,
563
+ opacity
564
+ });
565
+ if (schema.underline) drawDecorationLine({
566
+ page,
567
+ x: xLine,
568
+ y: yLine - textHeight / 12,
569
+ width: textWidth,
570
+ rotate,
571
+ pivotPoint,
572
+ fontSize,
573
+ color,
574
+ opacity
575
+ });
576
+ }
577
+ line.runs.reduce((currentX, run, runIndex) => {
578
+ const runWidth = getRunWidth(run, fontSize, spacing);
579
+ drawRun({
580
+ page,
581
+ pdfLib,
582
+ run,
583
+ pdfFont: getPdfFont(run, pdfFontObj),
584
+ x: currentX,
585
+ y: yLine,
586
+ rotate,
587
+ pivotPoint,
588
+ fontSize,
589
+ lineHeight,
590
+ color,
591
+ opacity,
592
+ colorType,
593
+ characterSpacing: spacing,
594
+ strikethrough: Boolean(run.strikethrough)
595
+ });
596
+ return currentX + runWidth + (runIndex === line.runs.length - 1 ? 0 : spacing);
597
+ }, xLine);
598
+ });
599
+ };
600
+ //#endregion
601
+ //#region src/text/pdfRender.ts
602
+ var embedAndGetFontObj = async (arg) => {
603
+ const { pdfDoc, font, _cache } = arg;
604
+ if (_cache.has(pdfDoc)) return _cache.get(pdfDoc);
605
+ const fontValues = await Promise.all(Object.values(font).map(async (v) => {
606
+ let fontData = v.data;
607
+ if (typeof fontData === "string" && fontData.startsWith("http")) fontData = await fetchRemoteFontData(fontData);
608
+ return pdfDoc.embedFont(fontData, { subset: typeof v.subset === "undefined" ? true : v.subset });
609
+ }));
610
+ const fontObj = Object.keys(font).reduce((acc, cur, i) => Object.assign(acc, { [cur]: fontValues[i] }), {});
611
+ _cache.set(pdfDoc, fontObj);
612
+ return fontObj;
613
+ };
614
+ var getFontProp = ({ value, fontKitFont, schema, colorType, fontSize: resolvedFontSize }) => {
615
+ const fontSize = resolvedFontSize ?? (schema.dynamicFontSize ? calculateDynamicFontSize({
616
+ textSchema: schema,
617
+ fontKitFont,
618
+ value
619
+ }) : schema.fontSize ?? 13);
620
+ const color = hex2PrintingColor(schema.fontColor || "#000000", colorType);
621
+ return {
622
+ alignment: schema.alignment ?? "left",
623
+ verticalAlignment: schema.verticalAlignment ?? "top",
624
+ lineHeight: schema.lineHeight ?? 1,
625
+ characterSpacing: schema.characterSpacing ?? 0,
626
+ fontSize,
627
+ color
628
+ };
629
+ };
630
+ var pdfRender = async (arg) => {
631
+ const { value, pdfDoc, pdfLib, page, options, schema, _cache } = arg;
632
+ if (!value) return;
633
+ const { font = getDefaultFont(), colorType } = options;
634
+ const [pdfFontObj, fontKitFont] = await Promise.all([embedAndGetFontObj({
635
+ pdfDoc,
636
+ font,
637
+ _cache
638
+ }), getFontKitFont(schema.fontName, font, _cache)]);
639
+ const enableInlineMarkdown = isInlineMarkdownTextSchema(schema);
640
+ const { fontSize, color, alignment, verticalAlignment, lineHeight, characterSpacing } = getFontProp({
641
+ value: enableInlineMarkdown ? stripInlineMarkdown(value) : value,
642
+ fontKitFont,
643
+ schema,
644
+ colorType,
645
+ fontSize: enableInlineMarkdown && schema.dynamicFontSize ? await calculateDynamicRichTextFontSize({
646
+ value,
647
+ schema,
648
+ font,
649
+ _cache
650
+ }) : void 0
651
+ });
652
+ const fontName = schema.fontName ? schema.fontName : getFallbackFontName(font);
653
+ const pdfFontValue = pdfFontObj && pdfFontObj[fontName];
654
+ const pageHeight = page.getHeight();
655
+ const { width, height, rotate, position: { x, y }, opacity } = convertForPdfLayoutProps({
656
+ schema,
657
+ pageHeight,
658
+ applyRotateTranslate: false
659
+ });
660
+ const pivotPoint = {
661
+ x: x + width / 2,
662
+ y: pageHeight - mm2pt(schema.position.y) - height / 2
663
+ };
664
+ if (schema.backgroundColor) {
665
+ const color = hex2PrintingColor(schema.backgroundColor, colorType);
666
+ if (rotate.angle !== 0) {
667
+ const rotatedPoint = rotatePoint({
668
+ x,
669
+ y
670
+ }, pivotPoint, rotate.angle);
671
+ page.drawRectangle({
672
+ x: rotatedPoint.x,
673
+ y: rotatedPoint.y,
674
+ width,
675
+ height,
676
+ rotate,
677
+ color
678
+ });
679
+ } else page.drawRectangle({
680
+ x,
681
+ y,
682
+ width,
683
+ height,
684
+ rotate,
685
+ color
686
+ });
687
+ }
688
+ if (enableInlineMarkdown) {
689
+ await renderInlineMarkdownText({
690
+ value,
691
+ schema,
692
+ font,
693
+ pdfFontObj,
694
+ fontKitFont,
695
+ page,
696
+ pdfLib,
697
+ _cache,
698
+ colorType,
699
+ fontSize,
700
+ color,
701
+ alignment,
702
+ verticalAlignment,
703
+ lineHeight,
704
+ characterSpacing,
705
+ x,
706
+ width,
707
+ height,
708
+ pageHeight,
709
+ pivotPoint,
710
+ rotate,
711
+ opacity
712
+ });
713
+ return;
714
+ }
715
+ const firstLineTextHeight = heightOfFontAtSize(fontKitFont, fontSize);
716
+ const descent = getFontDescentInPt(fontKitFont, fontSize);
717
+ const halfLineHeightAdjustment = lineHeight === 0 ? 0 : (lineHeight - 1) * fontSize / 2;
718
+ const lines = splitTextToSize({
719
+ value,
720
+ characterSpacing,
721
+ fontSize,
722
+ fontKitFont,
723
+ boxWidthInPt: width
724
+ });
725
+ let yOffset = 0;
726
+ if (verticalAlignment === "top") yOffset = firstLineTextHeight + halfLineHeightAdjustment;
727
+ else {
728
+ const otherLinesHeight = lineHeight * fontSize * (lines.length - 1);
729
+ if (verticalAlignment === "bottom") yOffset = height - otherLinesHeight + descent - halfLineHeightAdjustment;
730
+ else if (verticalAlignment === "middle") yOffset = (height - otherLinesHeight - firstLineTextHeight + descent) / 2 + firstLineTextHeight;
731
+ }
732
+ const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
733
+ lines.forEach((line, rowIndex) => {
734
+ const trimmed = line.replace("\n", "");
735
+ const textWidth = widthOfTextAtSize(trimmed, fontKitFont, fontSize, characterSpacing);
736
+ const textHeight = heightOfFontAtSize(fontKitFont, fontSize);
737
+ const rowYOffset = lineHeight * fontSize * rowIndex;
738
+ if (line === "") line = "\r\n";
739
+ let xLine = x;
740
+ if (alignment === "center") xLine += (width - textWidth) / 2;
741
+ else if (alignment === "right") xLine += width - textWidth;
742
+ let yLine = pageHeight - mm2pt(schema.position.y) - yOffset - rowYOffset;
743
+ if (schema.strikethrough && textWidth > 0) {
744
+ const _x = xLine + textWidth + 1;
745
+ const _y = yLine + textHeight / 3;
746
+ page.drawLine({
747
+ start: rotatePoint({
748
+ x: xLine,
749
+ y: _y
750
+ }, pivotPoint, rotate.angle),
751
+ end: rotatePoint({
752
+ x: _x,
753
+ y: _y
754
+ }, pivotPoint, rotate.angle),
755
+ thickness: 1 / 12 * fontSize,
756
+ color,
757
+ opacity
758
+ });
759
+ }
760
+ if (schema.underline && textWidth > 0) {
761
+ const _x = xLine + textWidth + 1;
762
+ const _y = yLine - textHeight / 12;
763
+ page.drawLine({
764
+ start: rotatePoint({
765
+ x: xLine,
766
+ y: _y
767
+ }, pivotPoint, rotate.angle),
768
+ end: rotatePoint({
769
+ x: _x,
770
+ y: _y
771
+ }, pivotPoint, rotate.angle),
772
+ thickness: 1 / 12 * fontSize,
773
+ color,
774
+ opacity
775
+ });
776
+ }
777
+ if (rotate.angle !== 0) {
778
+ const rotatedPoint = rotatePoint({
779
+ x: xLine,
780
+ y: yLine
781
+ }, pivotPoint, rotate.angle);
782
+ xLine = rotatedPoint.x;
783
+ yLine = rotatedPoint.y;
784
+ }
785
+ let spacing = characterSpacing;
786
+ if (alignment === "justify" && line.slice(-1) !== "\n") {
787
+ const iterator = segmenter.segment(trimmed)[Symbol.iterator]();
788
+ const len = Array.from(iterator).length;
789
+ spacing += (width - textWidth) / len;
790
+ }
791
+ page.pushOperators(pdfLib.setCharacterSpacing(spacing));
792
+ page.drawText(trimmed, {
793
+ x: xLine,
794
+ y: yLine,
795
+ rotate,
796
+ size: fontSize,
797
+ color,
798
+ lineHeight: lineHeight * fontSize,
799
+ font: pdfFontValue,
800
+ opacity
801
+ });
802
+ });
803
+ };
804
+ //#endregion
805
+ //#region src/text/icons/index.ts
806
+ var TextStrikethroughIcon = createSvgStr(Strikethrough);
807
+ var TextUnderlineIcon = createSvgStr(Underline);
808
+ var TextAlignLeftIcon = createSvgStr(AlignLeft);
809
+ var TextAlignCenterIcon = createSvgStr(AlignCenter);
810
+ var TextAlignRightIcon = createSvgStr(AlignRight);
811
+ var TextAlignJustifyIcon = createSvgStr(AlignJustify);
812
+ var TextVerticalAlignTopIcon = createSvgStr(ArrowUpToLine);
813
+ var TextVerticalAlignMiddleIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><path d="M8 19h3v4h2v-4h3l-4-4l-4 4zm8-14h-3V1h-2v4H8l4 4l4-4zM4 11v2h16v-2H4z" fill="currentColor"></path></svg>`;
814
+ var TextVerticalAlignBottomIcon = createSvgStr(ArrowDownToLine);
815
+ //#endregion
816
+ //#region src/text/extraFormatter.ts
817
+ var Formatter = /* @__PURE__ */ function(Formatter) {
818
+ Formatter["STRIKETHROUGH"] = "strikethrough";
819
+ Formatter["UNDERLINE"] = "underline";
820
+ Formatter["ALIGNMENT"] = "alignment";
821
+ Formatter["VERTICAL_ALIGNMENT"] = "verticalAlignment";
822
+ return Formatter;
823
+ }({});
824
+ function getExtraFormatterSchema(i18n) {
825
+ const buttons = [
826
+ {
827
+ key: Formatter.STRIKETHROUGH,
828
+ icon: TextStrikethroughIcon,
829
+ type: "boolean"
830
+ },
831
+ {
832
+ key: Formatter.UNDERLINE,
833
+ icon: TextUnderlineIcon,
834
+ type: "boolean"
835
+ },
836
+ {
837
+ key: Formatter.ALIGNMENT,
838
+ icon: TextAlignLeftIcon,
839
+ type: "select",
840
+ value: DEFAULT_ALIGNMENT
841
+ },
842
+ {
843
+ key: Formatter.ALIGNMENT,
844
+ icon: TextAlignCenterIcon,
845
+ type: "select",
846
+ value: ALIGN_CENTER
847
+ },
848
+ {
849
+ key: Formatter.ALIGNMENT,
850
+ icon: TextAlignRightIcon,
851
+ type: "select",
852
+ value: ALIGN_RIGHT
853
+ },
854
+ {
855
+ key: Formatter.ALIGNMENT,
856
+ icon: TextAlignJustifyIcon,
857
+ type: "select",
858
+ value: ALIGN_JUSTIFY
859
+ },
860
+ {
861
+ key: Formatter.VERTICAL_ALIGNMENT,
862
+ icon: TextVerticalAlignTopIcon,
863
+ type: "select",
864
+ value: "top"
865
+ },
866
+ {
867
+ key: Formatter.VERTICAL_ALIGNMENT,
868
+ icon: TextVerticalAlignMiddleIcon,
869
+ type: "select",
870
+ value: VERTICAL_ALIGN_MIDDLE
871
+ },
872
+ {
873
+ key: Formatter.VERTICAL_ALIGNMENT,
874
+ icon: TextVerticalAlignBottomIcon,
875
+ type: "select",
876
+ value: VERTICAL_ALIGN_BOTTOM
877
+ }
878
+ ];
879
+ return {
880
+ title: i18n("schemas.text.format"),
881
+ widget: "ButtonGroup",
882
+ buttons,
883
+ span: 24
884
+ };
885
+ }
886
+ //#endregion
887
+ //#region src/text/propPanel.ts
888
+ var UseDynamicFontSize = (props) => {
889
+ const { rootElement, changeSchemas, activeSchema, i18n } = props;
890
+ const checkbox = document.createElement("input");
891
+ checkbox.type = "checkbox";
892
+ checkbox.checked = Boolean(activeSchema?.dynamicFontSize);
893
+ checkbox.onchange = (e) => {
894
+ changeSchemas([{
895
+ key: "dynamicFontSize",
896
+ value: e.target.checked ? {
897
+ min: 4,
898
+ max: 72,
899
+ fit: DEFAULT_DYNAMIC_FIT
900
+ } : void 0,
901
+ schemaId: activeSchema.id
902
+ }]);
903
+ };
904
+ const label = document.createElement("label");
905
+ const span = document.createElement("span");
906
+ span.innerText = i18n("schemas.text.dynamicFontSize") || "";
907
+ span.style.cssText = "margin-left: 0.5rem";
908
+ label.style.cssText = "display: flex; width: 100%;";
909
+ label.appendChild(checkbox);
910
+ label.appendChild(span);
911
+ rootElement.appendChild(label);
912
+ };
913
+ var UseInlineMarkdown = (props) => {
914
+ const { rootElement, changeSchemas, activeSchema, i18n } = props;
915
+ const checkbox = document.createElement("input");
916
+ checkbox.type = "checkbox";
917
+ checkbox.checked = activeSchema?.textFormat === TEXT_FORMAT_INLINE_MARKDOWN;
918
+ checkbox.onchange = (e) => {
919
+ changeSchemas([{
920
+ key: "textFormat",
921
+ value: e.target.checked ? TEXT_FORMAT_INLINE_MARKDOWN : TEXT_FORMAT_PLAIN,
922
+ schemaId: activeSchema.id
923
+ }]);
924
+ };
925
+ const label = document.createElement("label");
926
+ const span = document.createElement("span");
927
+ span.innerText = i18n("schemas.text.inlineMarkdown") || "";
928
+ span.style.cssText = "margin-left: 0.5rem";
929
+ label.style.cssText = "display: flex; width: 100%;";
930
+ label.appendChild(checkbox);
931
+ label.appendChild(span);
932
+ rootElement.appendChild(label);
933
+ };
934
+ var propPanel = {
935
+ schema: ({ options, activeSchema, i18n }) => {
936
+ const font = options.font || { [DEFAULT_FONT_NAME]: {
937
+ data: "",
938
+ fallback: true
939
+ } };
940
+ const fontNames = Object.keys(font);
941
+ const fallbackFontName = getFallbackFontName(font);
942
+ const enableDynamicFont = Boolean(activeSchema?.dynamicFontSize);
943
+ const activeTextSchema = activeSchema;
944
+ const hideTextFormat = activeTextSchema.type === "text" && activeTextSchema.readOnly !== true;
945
+ const enableInlineMarkdown = activeTextSchema.textFormat === "inline-markdown" && !hideTextFormat;
946
+ const baseFontName = activeTextSchema.fontName && font[activeTextSchema.fontName] ? activeTextSchema.fontName : fallbackFontName;
947
+ const optionalFontNames = [{
948
+ label: baseFontName,
949
+ value: ""
950
+ }, ...fontNames.filter((name) => name !== baseFontName).map((name) => ({
951
+ label: name,
952
+ value: name
953
+ }))];
954
+ return {
955
+ fontName: {
956
+ title: i18n("schemas.text.fontName"),
957
+ type: "string",
958
+ widget: "select",
959
+ default: fallbackFontName,
960
+ placeholder: fallbackFontName,
961
+ props: { options: fontNames.map((name) => ({
962
+ label: name,
963
+ value: name
964
+ })) },
965
+ span: 12
966
+ },
967
+ fontSize: {
968
+ title: i18n("schemas.text.size"),
969
+ type: "number",
970
+ widget: "inputNumber",
971
+ span: 6,
972
+ disabled: enableDynamicFont,
973
+ props: { min: 0 }
974
+ },
975
+ characterSpacing: {
976
+ title: i18n("schemas.text.spacing"),
977
+ type: "number",
978
+ widget: "inputNumber",
979
+ span: 6,
980
+ props: { min: 0 }
981
+ },
982
+ formatter: getExtraFormatterSchema(i18n),
983
+ lineHeight: {
984
+ title: i18n("schemas.text.lineHeight"),
985
+ type: "number",
986
+ widget: "inputNumber",
987
+ props: {
988
+ step: .1,
989
+ min: 0
990
+ },
991
+ span: 8
992
+ },
993
+ useDynamicFontSize: {
994
+ type: "boolean",
995
+ widget: "UseDynamicFontSize",
996
+ bind: false,
997
+ span: 16
998
+ },
999
+ dynamicFontSize: {
1000
+ type: "object",
1001
+ widget: "card",
1002
+ column: 3,
1003
+ properties: {
1004
+ min: {
1005
+ title: i18n("schemas.text.min"),
1006
+ type: "number",
1007
+ widget: "inputNumber",
1008
+ hidden: !enableDynamicFont,
1009
+ props: { min: 0 }
1010
+ },
1011
+ max: {
1012
+ title: i18n("schemas.text.max"),
1013
+ type: "number",
1014
+ widget: "inputNumber",
1015
+ hidden: !enableDynamicFont,
1016
+ props: { min: 0 }
1017
+ },
1018
+ fit: {
1019
+ title: i18n("schemas.text.fit"),
1020
+ type: "string",
1021
+ widget: "select",
1022
+ hidden: !enableDynamicFont,
1023
+ props: { options: [{
1024
+ label: i18n("schemas.horizontal"),
1025
+ value: DYNAMIC_FIT_HORIZONTAL
1026
+ }, {
1027
+ label: i18n("schemas.vertical"),
1028
+ value: DYNAMIC_FIT_VERTICAL
1029
+ }] }
1030
+ }
1031
+ }
1032
+ },
1033
+ fontColor: {
1034
+ title: i18n("schemas.textColor"),
1035
+ type: "string",
1036
+ widget: "color",
1037
+ props: { disabledAlpha: true },
1038
+ rules: [{
1039
+ pattern: HEX_COLOR_PATTERN,
1040
+ message: i18n("validation.hexColor")
1041
+ }]
1042
+ },
1043
+ backgroundColor: {
1044
+ title: i18n("schemas.bgColor"),
1045
+ type: "string",
1046
+ widget: "color",
1047
+ props: { disabledAlpha: true },
1048
+ rules: [{
1049
+ pattern: HEX_COLOR_PATTERN,
1050
+ message: i18n("validation.hexColor")
1051
+ }]
1052
+ },
1053
+ useInlineMarkdown: {
1054
+ type: "boolean",
1055
+ widget: "UseInlineMarkdown",
1056
+ bind: false,
1057
+ hidden: hideTextFormat,
1058
+ span: enableInlineMarkdown ? 12 : 24
1059
+ },
1060
+ fontVariantFallback: {
1061
+ title: i18n("schemas.text.variantFallback"),
1062
+ type: "string",
1063
+ widget: "select",
1064
+ default: DEFAULT_FONT_VARIANT_FALLBACK,
1065
+ hidden: !enableInlineMarkdown,
1066
+ props: { options: [
1067
+ {
1068
+ label: i18n("schemas.text.synthetic"),
1069
+ value: FONT_VARIANT_FALLBACK_SYNTHETIC
1070
+ },
1071
+ {
1072
+ label: i18n("schemas.text.plain"),
1073
+ value: FONT_VARIANT_FALLBACK_PLAIN
1074
+ },
1075
+ {
1076
+ label: i18n("schemas.text.error"),
1077
+ value: FONT_VARIANT_FALLBACK_ERROR
1078
+ }
1079
+ ] },
1080
+ span: 12
1081
+ },
1082
+ fontVariants: {
1083
+ title: i18n("schemas.text.markdownFonts"),
1084
+ type: "object",
1085
+ widget: "card",
1086
+ column: 2,
1087
+ hidden: !enableInlineMarkdown,
1088
+ properties: {
1089
+ bold: {
1090
+ title: i18n("schemas.text.boldFont"),
1091
+ type: "string",
1092
+ widget: "select",
1093
+ props: { options: optionalFontNames }
1094
+ },
1095
+ italic: {
1096
+ title: i18n("schemas.text.italicFont"),
1097
+ type: "string",
1098
+ widget: "select",
1099
+ props: { options: optionalFontNames }
1100
+ },
1101
+ boldItalic: {
1102
+ title: i18n("schemas.text.boldItalicFont"),
1103
+ type: "string",
1104
+ widget: "select",
1105
+ props: { options: optionalFontNames }
1106
+ },
1107
+ code: {
1108
+ title: i18n("schemas.text.codeFont"),
1109
+ type: "string",
1110
+ widget: "select",
1111
+ props: { options: optionalFontNames }
1112
+ }
1113
+ }
1114
+ }
1115
+ };
1116
+ },
1117
+ widgets: {
1118
+ UseDynamicFontSize,
1119
+ UseInlineMarkdown
1120
+ },
1121
+ defaultSchema: {
1122
+ name: "",
1123
+ type: "text",
1124
+ content: "Type Something...",
1125
+ position: {
1126
+ x: 0,
1127
+ y: 0
1128
+ },
1129
+ width: 45,
1130
+ height: 10,
1131
+ rotate: 0,
1132
+ alignment: DEFAULT_ALIGNMENT,
1133
+ verticalAlignment: "top",
1134
+ fontSize: 13,
1135
+ textFormat: DEFAULT_TEXT_FORMAT,
1136
+ fontVariantFallback: DEFAULT_FONT_VARIANT_FALLBACK,
1137
+ lineHeight: 1,
1138
+ characterSpacing: 0,
1139
+ dynamicFontSize: void 0,
1140
+ fontColor: DEFAULT_FONT_COLOR,
1141
+ fontName: void 0,
1142
+ backgroundColor: "",
1143
+ opacity: 1,
1144
+ strikethrough: false,
1145
+ underline: false
1146
+ }
1147
+ };
1148
+ //#endregion
1149
+ //#region src/text/uiRender.ts
1150
+ var replaceUnsupportedChars = (text, fontKitFont) => {
1151
+ const charSupportCache = {};
1152
+ const isCharSupported = (char) => {
1153
+ if (char in charSupportCache) return charSupportCache[char];
1154
+ const isSupported = fontKitFont.hasGlyphForCodePoint(char.codePointAt(0) || 0);
1155
+ charSupportCache[char] = isSupported;
1156
+ return isSupported;
1157
+ };
1158
+ return text.split(/(\r\n|\n|\r)/).map((segment) => {
1159
+ if (/\r\n|\n|\r/.test(segment)) return segment;
1160
+ return Array.from(segment).map((char) => {
1161
+ if (/\s/.test(char) || char.charCodeAt(0) < 32) return char;
1162
+ return isCharSupported(char) ? char : "〿";
1163
+ }).join("");
1164
+ }).join("");
1165
+ };
1166
+ var uiRender = async (arg) => {
1167
+ const { value, schema, mode, onChange, stopEditing, tabIndex, placeholder, options, _cache } = arg;
1168
+ const usePlaceholder = isEditable(mode, schema) && placeholder && !value;
1169
+ const getText = (element) => {
1170
+ let text = element.innerText;
1171
+ if (text.endsWith("\n")) text = text.slice(0, -1);
1172
+ return text;
1173
+ };
1174
+ const font = options?.font || getDefaultFont();
1175
+ const fontKitFont = await getFontKitFont(schema.fontName, font, _cache);
1176
+ const enableInlineMarkdown = isInlineMarkdownTextSchema(schema);
1177
+ const displayValue = enableInlineMarkdown ? stripInlineMarkdown(value) : value;
1178
+ const dynamicRichTextFontSize = enableInlineMarkdown && schema.dynamicFontSize ? await calculateDynamicRichTextFontSize({
1179
+ value: usePlaceholder ? placeholder : value,
1180
+ schema,
1181
+ font,
1182
+ _cache
1183
+ }) : void 0;
1184
+ const textBlock = buildStyledTextContainer(arg, fontKitFont, usePlaceholder ? placeholder : displayValue, dynamicRichTextFontSize);
1185
+ const processedText = replaceUnsupportedChars(value, fontKitFont);
1186
+ if (!isEditable(mode, schema)) {
1187
+ if (enableInlineMarkdown) {
1188
+ await renderInlineMarkdownReadOnly({
1189
+ textBlock,
1190
+ value,
1191
+ schema,
1192
+ font,
1193
+ _cache
1194
+ });
1195
+ return;
1196
+ }
1197
+ textBlock.innerHTML = processedText.split("").map((l, i) => {
1198
+ const escaped = l.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1199
+ return `<span style="letter-spacing:${String(value).length === i + 1 ? 0 : "inherit"};">${escaped}</span>`;
1200
+ }).join("");
1201
+ return;
1202
+ }
1203
+ makeElementPlainTextContentEditable(textBlock);
1204
+ textBlock.tabIndex = tabIndex || 0;
1205
+ textBlock.innerText = mode === "designer" ? value : processedText;
1206
+ textBlock.addEventListener("blur", (e) => {
1207
+ if (onChange) onChange({
1208
+ key: "content",
1209
+ value: getText(e.target)
1210
+ });
1211
+ if (stopEditing) stopEditing();
1212
+ });
1213
+ if (schema.dynamicFontSize) {
1214
+ let dynamicFontSize = void 0;
1215
+ textBlock.addEventListener("keyup", () => {
1216
+ setTimeout(() => {
1217
+ (() => {
1218
+ if (!textBlock.textContent) return;
1219
+ dynamicFontSize = calculateDynamicFontSize({
1220
+ textSchema: schema,
1221
+ fontKitFont,
1222
+ value: isInlineMarkdownTextSchema(schema) ? stripInlineMarkdown(getText(textBlock)) : getText(textBlock),
1223
+ startingFontSize: dynamicFontSize
1224
+ });
1225
+ textBlock.style.fontSize = `${dynamicFontSize}pt`;
1226
+ const { topAdj: newTopAdj, bottomAdj: newBottomAdj } = getBrowserVerticalFontAdjustments(fontKitFont, dynamicFontSize ?? schema.fontSize ?? 13, schema.lineHeight ?? 1, schema.verticalAlignment ?? "top");
1227
+ textBlock.style.paddingTop = `${newTopAdj}px`;
1228
+ textBlock.style.marginBottom = `${newBottomAdj}px`;
1229
+ })();
1230
+ }, 0);
1231
+ });
1232
+ }
1233
+ if (usePlaceholder) {
1234
+ textBlock.style.color = PLACEHOLDER_FONT_COLOR;
1235
+ textBlock.addEventListener("focus", () => {
1236
+ if (textBlock.innerText === placeholder) {
1237
+ textBlock.innerText = "";
1238
+ textBlock.style.color = schema.fontColor ?? "#000000";
1239
+ }
1240
+ });
1241
+ }
1242
+ if (mode === "designer") setTimeout(() => {
1243
+ textBlock.focus();
1244
+ const selection = window.getSelection();
1245
+ const range = document.createRange();
1246
+ if (selection && range) {
1247
+ range.selectNodeContents(textBlock);
1248
+ range.collapse(false);
1249
+ selection?.removeAllRanges();
1250
+ selection?.addRange(range);
1251
+ }
1252
+ });
1253
+ };
1254
+ var renderInlineMarkdownReadOnly = async (arg) => {
1255
+ const { textBlock, value, schema, font, _cache } = arg;
1256
+ const runs = await resolveRichTextRuns({
1257
+ runs: parseInlineMarkdown(value),
1258
+ schema,
1259
+ font,
1260
+ _cache
1261
+ });
1262
+ textBlock.innerHTML = "";
1263
+ runs.forEach((run) => {
1264
+ const span = document.createElement("span");
1265
+ span.textContent = replaceUnsupportedChars(run.text, run.fontKitFont);
1266
+ if (run.fontName) span.style.fontFamily = `'${run.fontName}'`;
1267
+ if (run.syntheticBold) {
1268
+ span.style.fontWeight = "800";
1269
+ span.style.textShadow = SYNTHETIC_BOLD_CSS_TEXT_SHADOW;
1270
+ }
1271
+ if (run.syntheticItalic) span.style.fontStyle = "italic";
1272
+ if (run.strikethrough) span.style.textDecoration = "line-through";
1273
+ if (run.code) {
1274
+ span.style.backgroundColor = CODE_BACKGROUND_COLOR;
1275
+ span.style.borderRadius = "2px";
1276
+ span.style.padding = "0 0.15em";
1277
+ if (!schema.fontVariants?.code || !font[schema.fontVariants.code]) span.style.fontFamily = run.fontName ? `'${run.fontName}', monospace` : "monospace";
1278
+ }
1279
+ textBlock.appendChild(span);
1280
+ });
1281
+ };
1282
+ var buildStyledTextContainer = (arg, fontKitFont, value, resolvedDynamicFontSize) => {
1283
+ const { schema, rootElement, mode } = arg;
1284
+ let dynamicFontSize = resolvedDynamicFontSize;
1285
+ if (dynamicFontSize === void 0 && schema.dynamicFontSize && value) dynamicFontSize = calculateDynamicFontSize({
1286
+ textSchema: schema,
1287
+ fontKitFont,
1288
+ value,
1289
+ startingFontSize: dynamicFontSize
1290
+ });
1291
+ const { topAdj, bottomAdj } = getBrowserVerticalFontAdjustments(fontKitFont, dynamicFontSize ?? schema.fontSize ?? 13, schema.lineHeight ?? 1, schema.verticalAlignment ?? "top");
1292
+ const topAdjustment = topAdj.toString();
1293
+ const bottomAdjustment = bottomAdj.toString();
1294
+ const container = document.createElement("div");
1295
+ const containerStyle = {
1296
+ padding: 0,
1297
+ resize: "none",
1298
+ backgroundColor: getBackgroundColor(value, schema),
1299
+ border: "none",
1300
+ display: "flex",
1301
+ flexDirection: "column",
1302
+ justifyContent: mapVerticalAlignToFlex(schema.verticalAlignment),
1303
+ width: "100%",
1304
+ height: "100%",
1305
+ cursor: isEditable(mode, schema) ? "text" : "default"
1306
+ };
1307
+ Object.assign(container.style, containerStyle);
1308
+ rootElement.innerHTML = "";
1309
+ rootElement.appendChild(container);
1310
+ const textDecorations = [];
1311
+ if (schema.strikethrough) textDecorations.push("line-through");
1312
+ if (schema.underline) textDecorations.push("underline");
1313
+ const textBlockStyle = {
1314
+ fontFamily: schema.fontName ? `'${schema.fontName}'` : "inherit",
1315
+ color: schema.fontColor ? schema.fontColor : DEFAULT_FONT_COLOR,
1316
+ fontSize: `${dynamicFontSize ?? schema.fontSize ?? 13}pt`,
1317
+ letterSpacing: `${schema.characterSpacing ?? 0}pt`,
1318
+ lineHeight: `${schema.lineHeight ?? 1}em`,
1319
+ textAlign: schema.alignment ?? "left",
1320
+ whiteSpace: "pre-wrap",
1321
+ wordBreak: "break-word",
1322
+ resize: "none",
1323
+ border: "none",
1324
+ outline: "none",
1325
+ marginBottom: `${bottomAdjustment}px`,
1326
+ paddingTop: `${topAdjustment}px`,
1327
+ backgroundColor: "transparent",
1328
+ textDecoration: textDecorations.join(" ")
1329
+ };
1330
+ const textBlock = document.createElement("div");
1331
+ textBlock.id = "text-" + String(schema.id);
1332
+ Object.assign(textBlock.style, textBlockStyle);
1333
+ container.appendChild(textBlock);
1334
+ return textBlock;
1335
+ };
1336
+ /**
1337
+ * Firefox doesn't support 'plaintext-only' contentEditable mode, which we want to avoid mark-up.
1338
+ * This function adds a workaround for Firefox to make the contentEditable element behave like 'plaintext-only'.
1339
+ */
1340
+ var makeElementPlainTextContentEditable = (element) => {
1341
+ if (!isFirefox()) {
1342
+ element.contentEditable = "plaintext-only";
1343
+ return;
1344
+ }
1345
+ element.contentEditable = "true";
1346
+ element.addEventListener("keydown", (e) => {
1347
+ if (e.key === "Enter" && !e.shiftKey) {
1348
+ e.preventDefault();
1349
+ document.execCommand("insertLineBreak", false, void 0);
1350
+ }
1351
+ });
1352
+ element.addEventListener("paste", (e) => {
1353
+ e.preventDefault();
1354
+ const paste = e.clipboardData?.getData("text");
1355
+ const selection = window.getSelection();
1356
+ if (!selection?.rangeCount) return;
1357
+ selection.deleteFromDocument();
1358
+ selection.getRangeAt(0).insertNode(document.createTextNode(paste || ""));
1359
+ selection.collapseToEnd();
1360
+ });
1361
+ };
1362
+ var mapVerticalAlignToFlex = (verticalAlignmentValue) => {
1363
+ switch (verticalAlignmentValue) {
1364
+ case "top": return "flex-start";
1365
+ case VERTICAL_ALIGN_MIDDLE: return "center";
1366
+ case VERTICAL_ALIGN_BOTTOM: return "flex-end";
1367
+ }
1368
+ return "flex-start";
1369
+ };
1370
+ var getBackgroundColor = (value, schema) => {
1371
+ if (!value || !schema.backgroundColor) return "transparent";
1372
+ return schema.backgroundColor;
1373
+ };
1374
+ //#endregion
1375
+ //#region src/text/index.ts
1376
+ var textSchema = {
1377
+ pdf: pdfRender,
1378
+ ui: uiRender,
1379
+ propPanel,
1380
+ icon: createSvgStr(TextCursorInput)
1381
+ };
1382
+ //#endregion
1383
+ //#region src/builtins.ts
1384
+ var builtInPlugins = { Text: textSchema };
1385
+ //#endregion
1386
+ export { mapVerticalAlignToFlex as a, Formatter as c, isInlineMarkdownTextSchema as d, resolveFontVariant as f, makeElementPlainTextContentEditable as i, getExtraFormatterSchema as l, parseInlineMarkdown as m, textSchema as n, uiRender as o, escapeInlineMarkdown as p, buildStyledTextContainer as r, propPanel as s, builtInPlugins as t, pdfRender as u };
1387
+
1388
+ //# sourceMappingURL=builtins-C0BvXHWr.js.map