@formfeed/engine 0.3.9 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/index.cjs +2471 -354
  2. package/index.js +2471 -354
  3. package/package.json +1 -1
  4. package/types/frame/fit.d.cts +13 -0
  5. package/types/frame/fit.d.ts +13 -0
  6. package/types/frame/fit.entry.d.cts +1 -0
  7. package/types/frame/fit.entry.d.ts +1 -0
  8. package/types/frame/stage.d.cts +22 -0
  9. package/types/frame/stage.d.ts +22 -0
  10. package/types/frame/stage.entry.d.cts +1 -0
  11. package/types/frame/stage.entry.d.ts +1 -0
  12. package/types/frame/text-edit.d.cts +75 -0
  13. package/types/frame/text-edit.d.ts +75 -0
  14. package/types/frame/text-edit.entry.d.cts +1 -0
  15. package/types/frame/text-edit.entry.d.ts +1 -0
  16. package/types/index.d.cts +21 -14
  17. package/types/index.d.ts +21 -14
  18. package/types/lib/assemble.d.cts +5 -0
  19. package/types/lib/assemble.d.ts +5 -0
  20. package/types/lib/design/emit.d.cts +20 -0
  21. package/types/lib/design/emit.d.ts +20 -0
  22. package/types/lib/design/fonts.d.cts +27 -0
  23. package/types/lib/design/fonts.d.ts +27 -0
  24. package/types/lib/design/hash.d.cts +6 -0
  25. package/types/lib/design/hash.d.ts +6 -0
  26. package/types/lib/design/index.d.cts +12 -0
  27. package/types/lib/design/index.d.ts +12 -0
  28. package/types/lib/design/layers.d.cts +12 -0
  29. package/types/lib/design/layers.d.ts +12 -0
  30. package/types/lib/design/parse.d.cts +25 -0
  31. package/types/lib/design/parse.d.ts +25 -0
  32. package/types/lib/design/starters.d.cts +14 -0
  33. package/types/lib/design/starters.d.ts +14 -0
  34. package/types/lib/design/types.d.cts +171 -0
  35. package/types/lib/design/types.d.ts +171 -0
  36. package/types/lib/dialect.d.cts +32 -0
  37. package/types/lib/dialect.d.ts +32 -0
  38. package/types/lib/generated/frames.d.cts +6 -0
  39. package/types/lib/generated/frames.d.ts +6 -0
  40. package/types/lib/preview.d.cts +16 -2
  41. package/types/lib/preview.d.ts +16 -2
  42. package/types/lib/source-edit.d.cts +112 -0
  43. package/types/lib/source-edit.d.ts +112 -0
  44. package/types/lib/source-positions.d.cts +12 -3
  45. package/types/lib/source-positions.d.ts +12 -3
  46. package/types/lib/source-scan.d.cts +61 -0
  47. package/types/lib/source-scan.d.ts +61 -0
  48. package/types/lib/translation.d.cts +17 -0
  49. package/types/lib/translation.d.ts +17 -0
package/index.cjs CHANGED
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  DEFAULT_CHROME_PADDING: () => DEFAULT_CHROME_PADDING,
34
+ DESIGN_LIMITS: () => DESIGN_LIMITS,
35
+ DESIGN_VERSION: () => DESIGN_VERSION,
34
36
  DefaultHelperRegistry: () => DefaultHelperRegistry,
35
37
  EPC_MAX_BYTES: () => EPC_MAX_BYTES,
36
38
  EngineSyntaxError: () => EngineSyntaxError,
@@ -44,32 +46,52 @@ __export(index_exports, {
44
46
  analyzeOffice: () => analyzeOffice,
45
47
  annotateSourcePositions: () => annotateSourcePositions,
46
48
  apitemplateRegions: () => apitemplateRegions,
49
+ applyRegionEdit: () => applyRegionEdit,
47
50
  assembleDocument: () => assembleDocument,
48
51
  brandCss: () => brandCss,
49
52
  builtinHelpers: () => builtinHelpers,
53
+ canvasDocument: () => canvasDocument,
50
54
  chartInitScript: () => chartInitScript,
51
55
  chartMarkup: () => chartMarkup,
52
56
  chartPayload: () => chartPayload,
57
+ chipValues: () => chipValues,
53
58
  convertScss: () => convertScss,
54
59
  createHelperRegistry: () => createHelperRegistry,
60
+ dataPathPattern: () => dataPathPattern,
55
61
  defaultHelpers: () => defaultHelpers,
62
+ defaultLayer: () => defaultLayer,
56
63
  defaultLimits: () => defaultLimits,
57
64
  defaultOutput: () => defaultOutput,
58
65
  defaultSettings: () => defaultSettings,
59
66
  depthGuard: () => depthGuard,
67
+ designBytes: () => designBytes,
68
+ designOutputHash: () => designOutputHash,
69
+ designStarters: () => designStarters,
60
70
  diffSchemas: () => diffSchemas,
71
+ editAttribute: () => editAttribute,
72
+ editableRegion: () => editableRegion,
73
+ editableRegionAt: () => editableRegionAt,
74
+ emitDesign: () => emitDesign,
61
75
  emptyBrand: () => emptyBrand,
76
+ emptyDesign: () => emptyDesign,
77
+ encodeTemplateText: () => encodeTemplateText,
62
78
  enforceLimits: () => enforceLimits,
63
79
  engineIds: () => engineIds,
64
80
  engines: () => engines,
65
81
  epcPayload: () => epcPayload,
66
82
  escapeHtml: () => escapeHtml,
83
+ findRegionAgain: () => findRegionAgain,
67
84
  flowDocument: () => flowDocument,
68
85
  fontFaceCss: () => fontFaceCss,
86
+ genericFamily: () => genericFamily,
69
87
  getEngine: () => getEngine,
88
+ googleFont: () => googleFont,
89
+ googleFonts: () => googleFonts,
90
+ googleFontsUrl: () => googleFontsUrl,
70
91
  handlebarsEngine: () => handlebarsEngine,
71
92
  helperDocs: () => helperDocs,
72
93
  ibanChecksum: () => ibanChecksum,
94
+ imageIsEmpty: () => imageIsEmpty,
73
95
  imageMarkup: () => imageMarkup,
74
96
  imageOutput: () => imageOutput,
75
97
  importApitemplate: () => importApitemplate,
@@ -86,6 +108,7 @@ __export(index_exports, {
86
108
  jinja2Engine: () => jinja2Engine,
87
109
  jinjaTags: () => jinjaTags,
88
110
  jsreportTemplates: () => jsreportTemplates,
111
+ layerIdPattern: () => layerIdPattern,
89
112
  layeredPartials: () => layeredPartials,
90
113
  liquidBuiltinFilters: () => liquidBuiltinFilters,
91
114
  liquidEngine: () => liquidEngine,
@@ -94,6 +117,8 @@ __export(index_exports, {
94
117
  mergeSettings: () => mergeSettings,
95
118
  mergeSnippetCss: () => mergeSnippetCss,
96
119
  mergeSnippetI18n: () => mergeSnippetI18n,
120
+ nearestWeight: () => nearestWeight,
121
+ nextLayerId: () => nextLayerId,
97
122
  normaliseIban: () => normaliseIban,
98
123
  normalisePartialName: () => normalisePartialName,
99
124
  nunjucksBuiltinFilters: () => nunjucksBuiltinFilters,
@@ -106,14 +131,18 @@ __export(index_exports, {
106
131
  pageSize: () => pageSize,
107
132
  pagedDocument: () => pagedDocument,
108
133
  paperOf: () => paperOf,
134
+ parseDesign: () => parseDesign,
109
135
  parseSourceAttribute: () => parseSourceAttribute,
110
136
  partialInclude: () => partialInclude,
111
137
  printReset: () => printReset,
112
138
  qrSvg: () => qrSvg,
113
139
  readJsreportExport: () => readJsreportExport,
140
+ regionChips: () => regionChips,
141
+ regionItems: () => regionItems,
114
142
  renderOffice: () => renderOffice,
115
143
  renderVersion: () => renderVersion,
116
144
  resolvePath: () => resolvePath,
145
+ resolveTranslation: () => resolveTranslation,
117
146
  scanBlocks: () => scanBlocks,
118
147
  schemaPaths: () => schemaPaths,
119
148
  slugFromName: () => slugFromName,
@@ -128,6 +157,9 @@ __export(index_exports, {
128
157
  svgDataUri: () => svgDataUri,
129
158
  toDate: () => toDate,
130
159
  toNumber: () => toNumber,
160
+ translationItems: () => translationItems,
161
+ translationTarget: () => translationTarget,
162
+ translationText: () => translationText,
131
163
  validateEpc: () => validateEpc,
132
164
  zoomGestureScript: () => zoomGestureScript
133
165
  });
@@ -317,11 +349,11 @@ function validateEpc(input) {
317
349
  const purpose = String(input.purpose ?? "");
318
350
  if (purpose.length > 4) problems.push({ field: "purpose", code: "too_long", max: 4, actual: purpose.length });
319
351
  const reference = String(input.reference ?? "").trim();
320
- const text = String(input.text ?? "").trim();
321
- if (reference && text) problems.push({ field: "reference", code: "reference_or_text" });
352
+ const text2 = String(input.text ?? "").trim();
353
+ if (reference && text2) problems.push({ field: "reference", code: "reference_or_text" });
322
354
  if (reference.length > 35)
323
355
  problems.push({ field: "reference", code: "too_long", max: 35, actual: reference.length });
324
- if (text.length > 140) problems.push({ field: "text", code: "too_long", max: 140, actual: text.length });
356
+ if (text2.length > 140) problems.push({ field: "text", code: "too_long", max: 140, actual: text2.length });
325
357
  const bytes = byteLength(epcPayload(input));
326
358
  if (bytes > EPC_MAX_BYTES)
327
359
  problems.push({ field: "payload", code: "too_long", max: EPC_MAX_BYTES, actual: bytes });
@@ -357,6 +389,12 @@ var barcodeTypes = {
357
389
  datamatrix: "datamatrix",
358
390
  pdf417: "pdf417"
359
391
  };
392
+ var ownTextLayout = /* @__PURE__ */ new Set(["ean13", "ean8", "upca"]);
393
+ function codeText(helper, value) {
394
+ const text2 = value === void 0 || value === null ? "" : String(value);
395
+ if (text2 === "") throw new Error(`${helper}: nothing to encode, the value is empty or missing from the data`);
396
+ return text2;
397
+ }
360
398
  var codeHelpers = [
361
399
  {
362
400
  name: "qrcode",
@@ -373,7 +411,7 @@ var codeHelpers = [
373
411
  },
374
412
  fn: (ctx, value, options) => {
375
413
  const o = options ?? {};
376
- const svg = qrSvg(String(value ?? ""), o);
414
+ const svg = qrSvg(codeText("qrcode", value), o);
377
415
  if (ctx.drawing) return ctx.drawing({ kind: "svg", svg, width: o.size, height: o.size, alt: "QR code" });
378
416
  return svgDataUri(svg);
379
417
  }
@@ -393,16 +431,17 @@ var codeHelpers = [
393
431
  fn: (ctx, value, options) => {
394
432
  const o = options ?? {};
395
433
  const bcid = barcodeTypes[String(o.type ?? "code128").toLowerCase()] ?? "code128";
434
+ const text2 = codeText("barcode", value);
396
435
  const svg = (0, import_bwip_js.toSVG)({
397
436
  bcid,
398
- text: String(value ?? ""),
437
+ text: text2,
399
438
  height: o.height ?? 12,
400
439
  ...o.width === void 0 ? {} : { width: o.width },
401
440
  scale: o.scale ?? 2,
402
441
  includetext: o.text !== false,
403
- textxalign: "center"
442
+ ...ownTextLayout.has(bcid) ? {} : { textxalign: "center" }
404
443
  });
405
- if (ctx.drawing) return ctx.drawing({ kind: "svg", svg, alt: `Barcode ${String(value ?? "")}` });
444
+ if (ctx.drawing) return ctx.drawing({ kind: "svg", svg, alt: `Barcode ${text2}` });
406
445
  return svgDataUri(svg);
407
446
  }
408
447
  },
@@ -453,6 +492,22 @@ var import_fr = require("n2words/fr");
453
492
  var import_it = require("n2words/it");
454
493
  var import_nl = require("n2words/nl");
455
494
 
495
+ // packages/engine/src/lib/translation.ts
496
+ function resolveTranslation(dictionaries, locale, key) {
497
+ const lang = locale.split("-")[0] ?? locale;
498
+ for (const candidate of [locale, lang, "en"]) {
499
+ const text2 = dictionaries?.[candidate]?.[key];
500
+ if (typeof text2 === "string") return { text: text2, from: candidate };
501
+ }
502
+ return { text: key, from: null };
503
+ }
504
+ function translationTarget(dictionaries, locale, key) {
505
+ const lang = locale.split("-")[0] ?? locale;
506
+ const { from } = resolveTranslation(dictionaries, locale, key);
507
+ if (from === locale || from === lang) return from;
508
+ return dictionaries?.[locale] ? locale : lang;
509
+ }
510
+
456
511
  // packages/engine/src/lib/helpers/office.ts
457
512
  var officeUnsupportedHelpers = /* @__PURE__ */ new Set(["chart", "markdown"]);
458
513
  function officeUnsupported(name) {
@@ -497,7 +552,9 @@ var str = (v) => v === void 0 || v === null ? "" : String(v);
497
552
  var opt = (v, fallback) => v === void 0 || v === null || v === "" ? fallback : v;
498
553
  function requireFinite(helper, value) {
499
554
  if (typeof value === "number" && !Number.isFinite(value))
500
- throw new Error(`${helper}: got ${value}, the result of a calculation with a value that is missing or not a number`);
555
+ throw new Error(
556
+ `${helper}: got ${value}, the result of a calculation with a value that is missing or not a number`
557
+ );
501
558
  }
502
559
  var wordsByLang = {
503
560
  de: import_de.toCardinal,
@@ -675,13 +732,20 @@ var formatHelpers = [
675
732
  if (Number.isNaN(n)) return "";
676
733
  const named = args.find(isNamedArgs) ?? {};
677
734
  const [decimals, method] = args.filter((a) => !isNamedArgs(a));
678
- const places = Number(opt(decimals, opt(named["precision"], opt(named["decimals"], 0))));
735
+ const places = Number(
736
+ opt(decimals, opt(named["precision"], opt(named["decimals"], 0)))
737
+ );
679
738
  const how = String(opt(method, opt(named["method"], "common")));
680
739
  const f = 10 ** places;
681
740
  const scaled = Number((Math.abs(n) * f).toPrecision(15));
682
- if (how === "floor") return (n < 0 ? -Math.ceil(scaled) : Math.floor(scaled)) / f;
683
- if (how === "ceil") return (n < 0 ? -Math.floor(scaled) : Math.ceil(scaled)) / f;
684
- if (how !== "common") throw new Error(`round: method must be 'common', 'floor' or 'ceil', not '${how}'`);
741
+ if (how === "floor")
742
+ return (n < 0 ? -Math.ceil(scaled) : Math.floor(scaled)) / f;
743
+ if (how === "ceil")
744
+ return (n < 0 ? -Math.floor(scaled) : Math.ceil(scaled)) / f;
745
+ if (how !== "common")
746
+ throw new Error(
747
+ `round: method must be 'common', 'floor' or 'ceil', not '${how}'`
748
+ );
685
749
  return Math.round(scaled + Number.EPSILON) / f * Math.sign(n);
686
750
  }
687
751
  },
@@ -913,11 +977,9 @@ var formatHelpers = [
913
977
  category: "text"
914
978
  },
915
979
  fn: (ctx, key, params) => {
916
- const dictionaries = ctx.i18n ?? {};
917
- const lang = ctx.locale.split("-")[0] ?? ctx.locale;
918
- const text = dictionaries[ctx.locale]?.[str(key)] ?? dictionaries[lang]?.[str(key)] ?? dictionaries["en"]?.[str(key)] ?? str(key);
980
+ const { text: text2 } = resolveTranslation(ctx.i18n, ctx.locale, str(key));
919
981
  const p = params ?? {};
920
- return text.replace(/\{(\w+)\}/g, (_, name) => str(p[name]));
982
+ return text2.replace(/\{(\w+)\}/g, (_, name) => str(p[name]));
921
983
  }
922
984
  },
923
985
  {
@@ -1242,8 +1304,8 @@ function imageMarkup(url, options = {}) {
1242
1304
  options.fit ? `object-fit:${options.fit}` : ""
1243
1305
  ].filter(Boolean).join(";");
1244
1306
  const cls = options.class ? ` class="${escapeHtml(options.class)}"` : "";
1245
- const style = styles ? ` style="${styles}"` : "";
1246
- return `<img src="${escapeHtml(url)}" alt="${escapeHtml(options.alt ?? "")}"${cls}${style}>`;
1307
+ const style2 = styles ? ` style="${styles}"` : "";
1308
+ return `<img src="${escapeHtml(url)}" alt="${escapeHtml(options.alt ?? "")}"${cls}${style2}>`;
1247
1309
  }
1248
1310
  function resolveAsset(base, url) {
1249
1311
  if (!base || /^[a-z][a-z0-9+.-]*:/i.test(url)) return url;
@@ -1531,11 +1593,11 @@ function scanBlocks(source, spec) {
1531
1593
  while (m = pattern.exec(source)) {
1532
1594
  const keyword = m[1] ?? "";
1533
1595
  const rest = m[2] ?? "";
1534
- const range = rangeOf(source, m.index, m.index + m[0].length);
1596
+ const range2 = rangeOf(source, m.index, m.index + m[0].length);
1535
1597
  if (inRaw) {
1536
1598
  if (spec.closers[keyword] === inRaw) {
1537
- const open = stack.pop();
1538
- if (open) open.block.close = range;
1599
+ const open2 = stack.pop();
1600
+ if (open2) open2.block.close = range2;
1539
1601
  inRaw = null;
1540
1602
  }
1541
1603
  continue;
@@ -1543,7 +1605,7 @@ function scanBlocks(source, spec) {
1543
1605
  if (isBlockOpener(keyword, rest)) {
1544
1606
  const block = {
1545
1607
  type: spec.openers[keyword] ?? "other",
1546
- open: range
1608
+ open: range2
1547
1609
  };
1548
1610
  blocks.push(block);
1549
1611
  stack.push({ keyword, block });
@@ -1557,7 +1619,7 @@ function scanBlocks(source, spec) {
1557
1619
  code: "unexpected-end-tag",
1558
1620
  message: `Unexpected {% ${keyword} %} without an open block`,
1559
1621
  args: { keyword },
1560
- range
1622
+ range: range2
1561
1623
  });
1562
1624
  } else if (top.keyword !== expected) {
1563
1625
  diagnostics.push({
@@ -1565,16 +1627,16 @@ function scanBlocks(source, spec) {
1565
1627
  code: "mismatched-end-tag",
1566
1628
  message: `Expected {% end${top.keyword} %} to close the {% ${top.keyword} %} at line ${top.block.open.start.line}, found {% ${keyword} %}`,
1567
1629
  args: { open: top.keyword, line: top.block.open.start.line, found: keyword },
1568
- range
1630
+ range: range2
1569
1631
  });
1570
1632
  const idx = stack.map((s) => s.keyword).lastIndexOf(expected);
1571
1633
  if (idx >= 0) {
1572
1634
  const closed = stack.splice(idx, 1)[0];
1573
- if (closed) closed.block.close = range;
1635
+ if (closed) closed.block.close = range2;
1574
1636
  }
1575
1637
  } else {
1576
1638
  stack.pop();
1577
- top.block.close = range;
1639
+ top.block.close = range2;
1578
1640
  }
1579
1641
  } else if (spec.continuations.includes(keyword) && !stack.length) {
1580
1642
  diagnostics.push({
@@ -1582,17 +1644,17 @@ function scanBlocks(source, spec) {
1582
1644
  code: "unexpected-tag",
1583
1645
  message: `{% ${keyword} %} outside of a block`,
1584
1646
  args: { keyword },
1585
- range
1647
+ range: range2
1586
1648
  });
1587
1649
  }
1588
1650
  }
1589
- for (const open of stack) {
1651
+ for (const open2 of stack) {
1590
1652
  diagnostics.push({
1591
1653
  severity: "error",
1592
1654
  code: "unclosed-block",
1593
- message: `{% ${open.keyword} %} is never closed; add {% end${open.keyword} %}`,
1594
- args: { keyword: open.keyword },
1595
- range: open.block.open
1655
+ message: `{% ${open2.keyword} %} is never closed; add {% end${open2.keyword} %}`,
1656
+ args: { keyword: open2.keyword },
1657
+ range: open2.block.open
1596
1658
  });
1597
1659
  }
1598
1660
  return { blocks, diagnostics };
@@ -2024,8 +2086,8 @@ var SPEC = /^(?:([\s\S])?([<>=^]))?([+\- ])?(#)?(0)?(\d+)?([,_])?(?:\.(\d+))?([b
2024
2086
  function group(digits, separator) {
2025
2087
  return digits.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
2026
2088
  }
2027
- function pythonExponent(text) {
2028
- return text.replace(/e([+-])(\d)$/i, (m, sign, digit) => `${m[0]}${sign}0${digit}`);
2089
+ function pythonExponent(text2) {
2090
+ return text2.replace(/e([+-])(\d)$/i, (m, sign, digit) => `${m[0]}${sign}0${digit}`);
2029
2091
  }
2030
2092
  function formatNumber(value, type, precision, alternate) {
2031
2093
  if (!Number.isFinite(value)) return Number.isNaN(value) ? "nan" : "inf";
@@ -2038,20 +2100,20 @@ function formatNumber(value, type, precision, alternate) {
2038
2100
  return `${(abs * 100).toFixed(precision ?? 6)}%`;
2039
2101
  case "e":
2040
2102
  case "E": {
2041
- const text = pythonExponent(abs.toExponential(precision ?? 6));
2042
- return type === "E" ? text.toUpperCase() : text;
2103
+ const text2 = pythonExponent(abs.toExponential(precision ?? 6));
2104
+ return type === "E" ? text2.toUpperCase() : text2;
2043
2105
  }
2044
2106
  case "g":
2045
2107
  case "G": {
2046
2108
  const p = precision === 0 ? 1 : precision ?? 6;
2047
2109
  const exp = abs === 0 ? 0 : Math.floor(Math.log10(abs));
2048
- let text = exp < -4 || exp >= p ? pythonExponent(abs.toExponential(p - 1)) : abs.toFixed(Math.max(0, p - 1 - exp));
2110
+ let text2 = exp < -4 || exp >= p ? pythonExponent(abs.toExponential(p - 1)) : abs.toFixed(Math.max(0, p - 1 - exp));
2049
2111
  if (!alternate) {
2050
- const [mantissa = "", exponent] = text.split("e");
2112
+ const [mantissa = "", exponent] = text2.split("e");
2051
2113
  const trimmed = mantissa.includes(".") ? mantissa.replace(/0+$/, "").replace(/\.$/, "") : mantissa;
2052
- text = exponent === void 0 ? trimmed : `${trimmed}e${exponent}`;
2114
+ text2 = exponent === void 0 ? trimmed : `${trimmed}e${exponent}`;
2053
2115
  }
2054
- return type === "G" ? text.toUpperCase() : text;
2116
+ return type === "G" ? text2.toUpperCase() : text2;
2055
2117
  }
2056
2118
  case "d":
2057
2119
  case "n":
@@ -2120,35 +2182,35 @@ function lookupField(name, positional, named, next) {
2120
2182
  function pythonFormat(template, positional, named = {}) {
2121
2183
  let auto = 0;
2122
2184
  const next = () => auto++;
2123
- const render = (text) => {
2185
+ const render = (text2) => {
2124
2186
  let out = "";
2125
- for (let i = 0; i < text.length; i++) {
2126
- const ch = text[i];
2187
+ for (let i = 0; i < text2.length; i++) {
2188
+ const ch = text2[i];
2127
2189
  if (ch === "}") {
2128
2190
  out += "}";
2129
- if (text[i + 1] === "}") i++;
2191
+ if (text2[i + 1] === "}") i++;
2130
2192
  continue;
2131
2193
  }
2132
2194
  if (ch !== "{") {
2133
2195
  out += ch;
2134
2196
  continue;
2135
2197
  }
2136
- if (text[i + 1] === "{") {
2198
+ if (text2[i + 1] === "{") {
2137
2199
  out += "{";
2138
2200
  i++;
2139
2201
  continue;
2140
2202
  }
2141
2203
  let depth = 1;
2142
2204
  let j = i + 1;
2143
- for (; j < text.length && depth > 0; j++) {
2144
- if (text[j] === "{") depth++;
2145
- else if (text[j] === "}") depth--;
2205
+ for (; j < text2.length && depth > 0; j++) {
2206
+ if (text2[j] === "{") depth++;
2207
+ else if (text2[j] === "}") depth--;
2146
2208
  }
2147
2209
  if (depth > 0) {
2148
- out += text.slice(i);
2210
+ out += text2.slice(i);
2149
2211
  break;
2150
2212
  }
2151
- const field = text.slice(i + 1, j - 1);
2213
+ const field = text2.slice(i + 1, j - 1);
2152
2214
  const colon = field.indexOf(":");
2153
2215
  const head = colon < 0 ? field : field.slice(0, colon);
2154
2216
  const name = head.replace(/![rsa]$/, "");
@@ -3670,7 +3732,7 @@ function layeredPartials(own, shared2, onShared) {
3670
3732
  };
3671
3733
  }
3672
3734
 
3673
- // packages/engine/src/lib/snippets.ts
3735
+ // packages/engine/src/lib/dialect.ts
3674
3736
  var loop = (engine, item, list2, body) => engine === "handlebars" ? `{{#each ${list2}}}
3675
3737
  ${body.replace(new RegExp(`(?<![\\w.])${item}\\.`, "g"), "")}
3676
3738
  {{/each}}` : `{% for ${item} in ${list2} %}
@@ -3691,6 +3753,32 @@ var tr = (engine, key, params = {}) => {
3691
3753
  return `{{ '${key}' | t${entries.length ? ": " + entries.map(([k, v]) => `${k}: ${v}`).join(", ") : ""} }}`;
3692
3754
  return `{{ t('${key}'${entries.length ? `, { ${entries.map(([k, v]) => `${k}: ${v}`).join(", ")} }` : ""}) }}`;
3693
3755
  };
3756
+ var ifBlock = (engine, cond, body) => engine === "handlebars" ? `{{#if ${cond}}}${body}{{/if}}` : `{% if ${cond} %}${body}{% endif %}`;
3757
+ function stringLiteral(engine, value) {
3758
+ if (engine === "liquid") {
3759
+ if (!value.includes("'")) return `'${value}'`;
3760
+ if (!value.includes('"')) return `"${value}"`;
3761
+ return `'${value.replace(/'/g, "\u2019")}'`;
3762
+ }
3763
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
3764
+ }
3765
+ function filterCall(engine, input, name, args = []) {
3766
+ if (engine === "handlebars")
3767
+ return `{{${name} ${input}${args.map((a) => ` ${a}`).join("")}}}`;
3768
+ if (engine === "liquid")
3769
+ return `{{ ${input} | ${name}${args.length ? `: ${args.join(", ")}` : ""} }}`;
3770
+ return `{{ ${input} | ${name}${args.length ? `(${args.join(", ")})` : ""} }}`;
3771
+ }
3772
+ function helperCall(engine, name, input, options = {}) {
3773
+ const entries = Object.entries(options);
3774
+ if (engine === "handlebars")
3775
+ return `{{${name} ${input}${entries.map(([k, v]) => ` ${k}=${v}`).join("")}}}`;
3776
+ if (engine === "liquid")
3777
+ return `{{ ${input} | ${name}${entries.length ? `: ${entries.map(([k, v]) => `${k}: ${v}`).join(", ")}` : ""} }}`;
3778
+ return `{{ ${name}(${input}${entries.length ? `, { ${entries.map(([k, v]) => `${k}: ${v}`).join(", ")} }` : ""}) }}`;
3779
+ }
3780
+
3781
+ // packages/engine/src/lib/snippets.ts
3694
3782
  function forAll(build) {
3695
3783
  return {
3696
3784
  jinja2: build("jinja2"),
@@ -3731,8 +3819,18 @@ var snippets = [
3731
3819
  </div>$0`
3732
3820
  ),
3733
3821
  sampleData: {
3734
- company: { name: "Fennlor Studio GmbH", street: "Musterstra\xDFe 1", zip: "12345", city: "Musterstadt" },
3735
- customer: { name: "Olvarest GmbH", street: "Beispielweg 2", zip: "54321", city: "Beispielstadt" }
3822
+ company: {
3823
+ name: "Fennlor Studio GmbH",
3824
+ street: "Musterstra\xDFe 1",
3825
+ zip: "12345",
3826
+ city: "Musterstadt"
3827
+ },
3828
+ customer: {
3829
+ name: "Olvarest GmbH",
3830
+ street: "Beispielweg 2",
3831
+ zip: "54321",
3832
+ city: "Beispielstadt"
3833
+ }
3736
3834
  },
3737
3835
  // Form B puts the field 45 mm below the top edge and 20 mm from the left; the page margins decide where it lands
3738
3836
  css: `.ff-window { width: 85mm; height: 45mm; box-sizing: border-box; padding: 0 5mm; overflow: hidden; }
@@ -3759,8 +3857,16 @@ var snippets = [
3759
3857
  .ff-reference th { text-align: left; font-weight: normal; color: #555; padding: 1px 12px 1px 0; }
3760
3858
  .ff-reference td { text-align: right; padding: 1px 0; }`,
3761
3859
  i18n: {
3762
- en: { "reference.customer": "Customer no.", "reference.invoice": "Invoice no.", "reference.date": "Date" },
3763
- de: { "reference.customer": "Kundennummer", "reference.invoice": "Rechnungsnummer", "reference.date": "Datum" }
3860
+ en: {
3861
+ "reference.customer": "Customer no.",
3862
+ "reference.invoice": "Invoice no.",
3863
+ "reference.date": "Date"
3864
+ },
3865
+ de: {
3866
+ "reference.customer": "Kundennummer",
3867
+ "reference.invoice": "Rechnungsnummer",
3868
+ "reference.date": "Datum"
3869
+ }
3764
3870
  }
3765
3871
  },
3766
3872
  {
@@ -3775,14 +3881,24 @@ var snippets = [
3775
3881
  </div>$0`
3776
3882
  ),
3777
3883
  sampleData: {
3778
- invoice: { number: "2026-0042", period_start: "2026-09-01", period_end: "2026-09-30" }
3884
+ invoice: {
3885
+ number: "2026-0042",
3886
+ period_start: "2026-09-01",
3887
+ period_end: "2026-09-30"
3888
+ }
3779
3889
  },
3780
3890
  css: `.ff-heading { margin: 24px 0 16px; }
3781
3891
  .ff-heading h1 { font-family: var(--brand-font-heading, inherit); font-size: 18pt; color: var(--brand-color-primary, inherit); margin: 0 0 4px; }
3782
3892
  .ff-heading p { margin: 0; color: #555; }`,
3783
3893
  i18n: {
3784
- en: { "heading.title": "Invoice {number}", "heading.period": "Service period:" },
3785
- de: { "heading.title": "Rechnung {number}", "heading.period": "Leistungszeitraum:" }
3894
+ en: {
3895
+ "heading.title": "Invoice {number}",
3896
+ "heading.period": "Service period:"
3897
+ },
3898
+ de: {
3899
+ "heading.title": "Rechnung {number}",
3900
+ "heading.period": "Leistungszeitraum:"
3901
+ }
3786
3902
  }
3787
3903
  },
3788
3904
  {
@@ -3909,11 +4025,17 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
3909
4025
  group: "invoice",
3910
4026
  title: "Small business VAT note",
3911
4027
  description: "States that no VAT is charged under \xA7 19 UStG (Kleinunternehmerregelung).",
3912
- source: forAll((e) => `<p class="ff-note">${tr(e, "vat.small_business")}</p>$0`),
4028
+ source: forAll(
4029
+ (e) => `<p class="ff-note">${tr(e, "vat.small_business")}</p>$0`
4030
+ ),
3913
4031
  css: `.ff-note { margin-top: 12px; font-size: 9pt; color: #444; }`,
3914
4032
  i18n: {
3915
- en: { "vat.small_business": "No VAT is charged under the small business scheme of \xA7 19 UStG." },
3916
- de: { "vat.small_business": "Gem\xE4\xDF \xA7 19 UStG wird keine Umsatzsteuer berechnet." }
4033
+ en: {
4034
+ "vat.small_business": "No VAT is charged under the small business scheme of \xA7 19 UStG."
4035
+ },
4036
+ de: {
4037
+ "vat.small_business": "Gem\xE4\xDF \xA7 19 UStG wird keine Umsatzsteuer berechnet."
4038
+ }
3917
4039
  }
3918
4040
  },
3919
4041
  {
@@ -3974,7 +4096,9 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
3974
4096
  <span>${tr(e, "footer.page")} <span class="pageNumber"></span> ${tr(e, "footer.of")} <span class="totalPages"></span></span>
3975
4097
  </div>$0`
3976
4098
  ),
3977
- sampleData: { company: { name: "Fennlor Studio GmbH", email: "hello@fennlor.example" } },
4099
+ sampleData: {
4100
+ company: { name: "Fennlor Studio GmbH", email: "hello@fennlor.example" }
4101
+ },
3978
4102
  i18n: {
3979
4103
  en: { "footer.page": "Page", "footer.of": "of" },
3980
4104
  de: { "footer.page": "Seite", "footer.of": "von" }
@@ -4059,7 +4183,10 @@ ${loop(e, "line", "invoice.lines", ` <tr><td>${val(e, "line.description")}</t
4059
4183
  },
4060
4184
  i18n: {
4061
4185
  en: { "qr.alt": "Payment QR code", "qr.caption": "Scan to pay" },
4062
- de: { "qr.alt": "QR-Code zur Zahlung", "qr.caption": "Zum Bezahlen scannen" }
4186
+ de: {
4187
+ "qr.alt": "QR-Code zur Zahlung",
4188
+ "qr.caption": "Zum Bezahlen scannen"
4189
+ }
4063
4190
  },
4064
4191
  sampleData: {
4065
4192
  payment: {
@@ -4178,7 +4305,9 @@ var selectorKey = (prelude) => (prelude.split(";").pop() ?? "").trim().replace(/
4178
4305
  function mergeSnippetCss(css2, snippetCss) {
4179
4306
  if (!snippetCss?.trim()) return css2;
4180
4307
  const defined = new Set(
4181
- [...stripComments(css2).matchAll(/([^{}]+)\{/g)].map((m) => selectorKey(m[1] ?? ""))
4308
+ [...stripComments(css2).matchAll(/([^{}]+)\{/g)].map(
4309
+ (m) => selectorKey(m[1] ?? "")
4310
+ )
4182
4311
  );
4183
4312
  const missing = [...stripComments(snippetCss).matchAll(/([^{}]+)\{[^{}]*\}/g)].filter((m) => !defined.has(selectorKey(m[1] ?? ""))).map((m) => m[0].trim());
4184
4313
  if (!missing.length) return css2;
@@ -4226,9 +4355,9 @@ function pick(input, ...keys) {
4226
4355
  function toLength(value) {
4227
4356
  if (value === void 0 || value === null || value === "") return void 0;
4228
4357
  if (typeof value === "number") return `${value}mm`;
4229
- const text = String(value).trim();
4230
- if (/^-?\d+(\.\d+)?$/.test(text)) return `${text}mm`;
4231
- if (/^-?\d+(\.\d+)?(mm|cm|in|px|pt)$/i.test(text)) return text.toLowerCase();
4358
+ const text2 = String(value).trim();
4359
+ if (/^-?\d+(\.\d+)?$/.test(text2)) return `${text2}mm`;
4360
+ if (/^-?\d+(\.\d+)?(mm|cm|in|px|pt)$/i.test(text2)) return text2.toLowerCase();
4232
4361
  return void 0;
4233
4362
  }
4234
4363
  function toMarginLength(value) {
@@ -4248,7 +4377,7 @@ function convertHeaderFooter(html) {
4248
4377
  if (typeof html !== "string" || !html.trim()) return void 0;
4249
4378
  return chromePlaceholders.reduce((out, [pattern, span]) => out.replace(pattern, span), html);
4250
4379
  }
4251
- var escapeText = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4380
+ var escapeText = (text2) => text2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4252
4381
  var visibleMarkup = (html) => html.replace(/<style[\s\S]*?<\/style>/gi, "").trim();
4253
4382
  function apitemplateChrome(take, kind) {
4254
4383
  const custom = take(`custom_${kind}`);
@@ -4262,7 +4391,7 @@ function apitemplateChrome(take, kind) {
4262
4391
  if (!slots.some(Boolean)) return { fromSlots: false };
4263
4392
  const sizeText = size === void 0 ? "" : String(size).trim();
4264
4393
  const fontSize2 = /^\d+(\.\d+)?$/.test(sizeText) ? `${sizeText}px` : /^\d+(\.\d+)?(px|pt|mm)$/.test(sizeText) ? sizeText : "9px";
4265
- const cells = slots.map((text, i) => `<span style="flex:1;text-align:${["left", "center", "right"][i]}">${escapeText(text)}</span>`).join("");
4394
+ const cells = slots.map((text2, i) => `<span style="flex:1;text-align:${["left", "center", "right"][i]}">${escapeText(text2)}</span>`).join("");
4266
4395
  const styles = customHtml.match(/<style[\s\S]*?<\/style>/gi)?.join("") ?? "";
4267
4396
  return {
4268
4397
  html: convertHeaderFooter(`${styles}<div style="display:flex;gap:4mm;font-size:${fontSize2}">${cells}</div>`),
@@ -4271,8 +4400,8 @@ function apitemplateChrome(take, kind) {
4271
4400
  }
4272
4401
  function clearsChromePadding(html, kind) {
4273
4402
  if (!html) return false;
4274
- for (const style of html.matchAll(/<style[^>]*>([\s\S]*?)<\/style>/gi))
4275
- for (const rule2 of (style[1] ?? "").matchAll(/([^{}]+)\{([^}]*)\}/g))
4403
+ for (const style2 of html.matchAll(/<style[^>]*>([\s\S]*?)<\/style>/gi))
4404
+ for (const rule2 of (style2[1] ?? "").matchAll(/([^{}]+)\{([^}]*)\}/g))
4276
4405
  if (new RegExp(`#${kind}\\b`).test(rule2[1] ?? "") && /(^|;)\s*padding\s*:\s*0(px|mm)?\s*(!important\s*)?(;|$)/i.test((rule2[2] ?? "").trim()))
4277
4406
  return true;
4278
4407
  return false;
@@ -4423,9 +4552,9 @@ function checkTemplate(engineId, html, sampleData, name, docs) {
4423
4552
  function lengthMm(value) {
4424
4553
  if (value === void 0 || value === null || value === "") return void 0;
4425
4554
  if (typeof value === "number" && Number.isFinite(value)) return `${value}mm`;
4426
- const text = String(value).trim();
4427
- if (/^-?\d+(\.\d+)?$/.test(text)) return `${text}mm`;
4428
- if (/^-?\d+(\.\d+)?(mm|cm|in|px|pt)$/i.test(text)) return text.toLowerCase();
4555
+ const text2 = String(value).trim();
4556
+ if (/^-?\d+(\.\d+)?$/.test(text2)) return `${text2}mm`;
4557
+ if (/^-?\d+(\.\d+)?(mm|cm|in|px|pt)$/i.test(text2)) return text2.toLowerCase();
4429
4558
  return void 0;
4430
4559
  }
4431
4560
  function toBase64(value) {
@@ -4828,7 +4957,7 @@ var unsupportedFilters = {
4828
4957
  in_time_zone: 'pass the zone to `date` as its second argument: `date: "dd.MM.yyyy", "Europe/Berlin"`'
4829
4958
  };
4830
4959
  function mapLiquid(source, fn) {
4831
- return source.replace(/(\{\{-?|\{%-?)([\s\S]*?)(-?\}\}|-?%\})/g, (_m, open, inner, close) => `${open}${fn(inner)}${close}`);
4960
+ return source.replace(/(\{\{-?|\{%-?)([\s\S]*?)(-?\}\}|-?%\})/g, (_m, open2, inner, close) => `${open2}${fn(inner)}${close}`);
4832
4961
  }
4833
4962
  function convertDialect(source, changes) {
4834
4963
  const errors = [];
@@ -4942,11 +5071,11 @@ function inlineIncludes(source, blocks, errors, changes) {
4942
5071
  }
4943
5072
  var cssPaperFormats = { a3: "A3", a4: "A4", a5: "A5", letter: "Letter" };
4944
5073
  var isoSizes = { a0: [841, 1189], a1: [594, 841], a2: [420, 594], a6: [105, 148] };
4945
- var escapeHtml2 = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5074
+ var escapeHtml2 = (text2) => text2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4946
5075
  function simpleHeaderFooter(part, margin) {
4947
5076
  const cells = ["left", "center", "right"].map((side) => part[side] ?? "");
4948
5077
  if (!cells.some((c) => c.trim())) return void 0;
4949
- const cell3 = (text, align) => `<div style="flex: 1; text-align: ${align}">${escapeHtml2(text).replace(/\[page\]/g, '<span class="pageNumber"></span>').replace(/\[topage\]/g, '<span class="totalPages"></span>')}</div>`;
5078
+ const cell3 = (text2, align) => `<div style="flex: 1; text-align: ${align}">${escapeHtml2(text2).replace(/\[page\]/g, '<span class="pageNumber"></span>').replace(/\[topage\]/g, '<span class="totalPages"></span>')}</div>`;
4950
5079
  return `<div style="display: flex; width: 100%; font-size: 10px; padding: 0 ${margin.right} 0 ${margin.left}">${cell3(cells[0] ?? "", "left")}${cell3(cells[1] ?? "", "center")}${cell3(cells[2] ?? "", "right")}</div>`;
4951
5080
  }
4952
5081
  function mapSettings(input, kind, warnings, changes) {
@@ -5115,11 +5244,11 @@ var DOCS3 = "/migrate/jsreport";
5115
5244
  var COLLECTIONS = ["templates", "data", "assets", "components", "scripts", "folders"];
5116
5245
  function readJsreportExport(files) {
5117
5246
  const bundle = { templates: [], data: [], assets: [], components: [], scripts: [], folders: [] };
5118
- for (const [path, text] of Object.entries(files)) {
5247
+ for (const [path, text2] of Object.entries(files)) {
5119
5248
  const normalized = path.replace(/\\/g, "/").replace(/^\.?\//, "");
5120
5249
  if (normalized === "metadata.json") {
5121
5250
  try {
5122
- bundle.version = String(JSON.parse(text).reporterVersion ?? "");
5251
+ bundle.version = String(JSON.parse(text2).reporterVersion ?? "");
5123
5252
  } catch {
5124
5253
  }
5125
5254
  continue;
@@ -5127,7 +5256,7 @@ function readJsreportExport(files) {
5127
5256
  const collection = normalized.split("/")[0];
5128
5257
  if (!COLLECTIONS.includes(collection) || !normalized.endsWith(".json")) continue;
5129
5258
  try {
5130
- bundle[collection].push(JSON.parse(text));
5259
+ bundle[collection].push(JSON.parse(text2));
5131
5260
  } catch {
5132
5261
  }
5133
5262
  }
@@ -5169,7 +5298,7 @@ function assetBase64(content) {
5169
5298
  }
5170
5299
  return toBase64(content);
5171
5300
  }
5172
- var escapeMustaches = (text) => text.replace(/\{\{/g, "\\{{");
5301
+ var escapeMustaches = (text2) => text2.replace(/\{\{/g, "\\{{");
5173
5302
  function resolveAssets(source, bundle, options, warnings, changes) {
5174
5303
  const v3 = /\{\{\{?\s*asset\s+(["'])([^"']+)\1(?:\s+(["'])([^"']+)\3)?\s*\}?\}\}/g;
5175
5304
  const v2 = /\{#asset\s+([^\s}]+)(?:\s+@encoding=(\w+))?\s*\}/g;
@@ -5406,6 +5535,11 @@ function importJsreport(bundle, ref, options = {}) {
5406
5535
  };
5407
5536
  }
5408
5537
 
5538
+ // packages/engine/src/lib/generated/frames.ts
5539
+ var textEditRuntime = '"use strict";(()=>{var H=`\nhtml.ff-edit-mode [data-ff-edit] { cursor: text; }\nhtml.ff-edit-mode [data-ff-edit]:hover { outline: 1px dashed rgba(37,99,235,.85); outline-offset: 2px; }\nhtml.ff-edit-mode [data-ff-edit="t"]:hover { outline-color: rgba(147,51,234,.9); }\n.ff-editing { outline: 2px solid #2563eb !important; outline-offset: 2px; cursor: text; }\n.ff-editing .ff-chip { background: rgba(37,99,235,.14) !important; box-shadow: inset 0 0 0 1px rgba(37,99,235,.45) !important; border-radius: 3px !important; padding: 0 1px !important; cursor: default; }\n.ff-edit-badge { position: absolute; z-index: 2147483647; font: 500 11px/1.4 system-ui, sans-serif !important; color: #fff !important; background: #2563eb !important; padding: 1px 6px !important; border-radius: 4px !important; pointer-events: none; white-space: nowrap; }\n`;function R(s,l={}){let g=l.clicks!==!1,p=l.live===!0,o=s.document,i=null,a=null,c=e=>{try{s.parent.postMessage(e,"*")}catch{}},b=o.createElement("style");b.setAttribute("data-formfeed","edit"),b.setAttribute("data-pagedjs-ignore",""),b.textContent=H,(o.head??o.documentElement).appendChild(b),g&&o.documentElement.classList.add("ff-edit-mode");let D=e=>(e&&typeof e.closest=="function"?e:null)?.closest("[data-ff-edit]")??null;o.addEventListener("click",e=>{if(!g)return;if(i&&!i.done&&i.element.contains(e.target)){e.stopImmediatePropagation();return}let t=D(e.target);if(!t)return;e.preventDefault(),e.stopImmediatePropagation(),i&&!i.done&&y();let n=t.getAttribute("data-ff-src")??"",r=t.getBoundingClientRect();a={src:n,element:t,x:e.clientX,y:e.clientY},c({type:"formfeed:edit-request",src:n,edit:t.getAttribute("data-ff-edit"),text:t.textContent??"",count:o.querySelectorAll(`[data-ff-src="${n.replace(/"/g,"")}"]`).length,split:t.hasAttribute("data-split-from")||t.hasAttribute("data-split-to"),rect:{left:r.left,top:r.top,width:r.width,height:r.height}})},!0),o.addEventListener("keydown",e=>{if(!g||i&&!i.done||!(e.ctrlKey||e.metaKey)||e.altKey)return;let t=e.key.toLowerCase();(t==="z"||t==="y")&&(e.preventDefault(),c({type:t==="y"||e.shiftKey?"formfeed:redo":"formfeed:undo"}))}),s.addEventListener("message",e=>{if(e.source!==s.parent)return;let t=e.data;!t||typeof t.type!="string"||(t.type==="formfeed:edit-begin"?F(t):(t.type==="formfeed:edit-refuse"||t.type==="formfeed:edit-end")&&(a=null,i&&!i.done&&h(!1)))});function F(e){i&&!i.done&&h(!1);let t=e.layer?o.querySelector(`[data-ff-layer="${String(e.layer).replace(/"/g,"")}"] > .ff-text`):a&&a.src===e.src?a.element:o.querySelector(`[data-ff-src="${String(e.src).replace(/"/g,"")}"]`),n=e.at,r=e.layer?n&&Number.isFinite(n.x)&&Number.isFinite(n.y)?{x:Number(n.x),y:Number(n.y)}:null:a&&a.src===e.src?a:null;if(a=null,!t||!Array.isArray(e.items)){c({type:"formfeed:edit-cancel",token:String(e.token)});return}let m=Array.from(t.childNodes),f=o.createDocumentFragment();for(let u of e.items)if(u.kind==="text")f.appendChild(o.createTextNode(String(u.text)));else if(u.kind==="break")f.appendChild(o.createElement("br"));else if(u.kind==="chip"){let d=o.createElement("span");d.className="ff-chip",d.setAttribute("data-ff-chip",String(u.index)),d.setAttribute("contenteditable","false"),u.title&&(d.title=String(u.title)),d.textContent=String(u.text),f.appendChild(d)}for(let u of m)t.removeChild(u);t.appendChild(f),t.contentEditable="true",t.classList.add("ff-editing"),t.setAttribute("role","textbox"),e.label&&t.setAttribute("aria-label",String(e.label)),t.spellcheck=!0,i={token:String(e.token),element:t,saved:m,allowBreaks:e.allowBreaks===!0,badge:e.note?B(t,String(e.note)):null,done:!1},t.addEventListener("keydown",E),t.addEventListener("beforeinput",v),t.addEventListener("paste",C),t.addEventListener("drop",L),t.addEventListener("focusout",A),p&&t.addEventListener("input",x),t.focus(),e.select==="all"?w(t):P(t,r),c({type:"formfeed:edit-started",token:i.token}),typeof e.insert=="string"&&e.insert&&k(e.insert.replace(/\\r\\n?|\\n/g," "))}function k(e){if(typeof o.execCommand=="function"&&o.execCommand("insertText",!1,e))return;let t=s.getSelection(),n=t&&t.rangeCount?t.getRangeAt(0):null;n&&(n.deleteContents(),n.insertNode(o.createTextNode(e)),n.collapse(!1),p&&x())}function x(){i&&!i.done&&c({type:"formfeed:edit-input",token:i.token,items:S(i.element)})}function B(e,t){let n=o.createElement("div");n.className="ff-edit-badge",n.textContent=t;let r=e.getBoundingClientRect();return n.style.left=`${r.left+s.scrollX}px`,n.style.top=`${Math.max(0,r.top+s.scrollY-22)}px`,o.body.appendChild(n),n}function w(e){let t=s.getSelection();if(!t)return;let n=o.createRange();n.selectNodeContents(e),t.removeAllRanges(),t.addRange(n)}function P(e,t){let n=s.getSelection();if(!n)return;let r=null,m=o;if(t){if(m.caretRangeFromPoint)r=m.caretRangeFromPoint(t.x,t.y);else if(m.caretPositionFromPoint){let f=m.caretPositionFromPoint(t.x,t.y);f&&(r=o.createRange(),r.setStart(f.offsetNode,f.offset))}}if(!r||!e.contains(r.startContainer))r=o.createRange(),r.selectNodeContents(e),r.collapse(!1);else if(t){let f=r.startContainer,d=(f.nodeType===1?f:f.parentElement)?.closest("[data-ff-chip]")??null;if(d&&d!==e&&e.contains(d)){let N=d.getBoundingClientRect();r=o.createRange(),t.x<N.left+N.width/2?r.setStartBefore(d):r.setStartAfter(d),r.collapse(!0)}}n.removeAllRanges(),n.addRange(r)}function E(e){let t=e.key;t==="Escape"?(e.preventDefault(),h(!0)):t==="Enter"&&!e.shiftKey?(e.preventDefault(),y()):t==="Enter"?(e.preventDefault(),i?.allowBreaks&&M()):t==="Tab"?(e.preventDefault(),y()):(e.ctrlKey||e.metaKey)&&["b","i","u"].includes(t.toLowerCase())&&(e.preventDefault(),p&&i&&!e.altKey&&c({type:"formfeed:edit-style",token:i.token,key:t.toLowerCase()}))}function v(e){let t=e.inputType??"";(t.startsWith("format")||t==="insertFromDrop"||t==="insertParagraph"||t==="insertLineBreak")&&e.preventDefault()}function C(e){e.preventDefault(),k((e.clipboardData?.getData("text/plain")??"").replace(/\\r\\n?|\\n/g," "))}function L(e){e.preventDefault()}function A(){s.setTimeout(()=>{i&&!i.done&&(!o.hasFocus()||!i.element.contains(o.activeElement))&&y()},0)}function M(){let e=s.getSelection();if(!e||!e.rangeCount)return;let t=e.getRangeAt(0);t.deleteContents();let n=o.createElement("br");t.insertNode(n),n.nextSibling||n.parentNode?.appendChild(o.createElement("br"));let r=o.createRange();r.setStartAfter(n),r.collapse(!0),e.removeAllRanges(),e.addRange(r)}function T(e){e.done=!0;let t=e.element;t.removeEventListener("keydown",E),t.removeEventListener("beforeinput",v),t.removeEventListener("paste",C),t.removeEventListener("drop",L),t.removeEventListener("focusout",A),t.removeEventListener("input",x),t.removeAttribute("contenteditable"),t.removeAttribute("role"),t.removeAttribute("aria-label"),t.classList.remove("ff-editing"),e.badge?.remove(),i===e&&(i=null)}function y(){let e=i;if(!e||e.done)return;let t=S(e.element);T(e),c({type:"formfeed:edit-commit",token:e.token,items:t})}function h(e){let t=i;if(!t||t.done)return;let n=t.element;for(;n.firstChild;)n.removeChild(n.firstChild);for(let r of t.saved)n.appendChild(r);T(t),e&&c({type:"formfeed:edit-cancel",token:t.token})}}function S(s){let l=[],g=o=>{let i=l[l.length-1];i?.kind==="text"?i.text+=o:o&&l.push({kind:"text",text:o})},p=o=>{for(let i of Array.from(o.childNodes))if(i.nodeType===3)g(i.data);else if(i.nodeType===1){let a=i,c=a.getAttribute("data-ff-chip");c!==null?l.push({kind:"chip",index:Number(c)}):a.tagName==="BR"?l.push({kind:"break"}):((a.tagName==="DIV"||a.tagName==="P")&&l.length&&l.push({kind:"break"}),p(a))}};for(p(s);l[l.length-1]?.kind==="break";)l.pop();return l}R(window);})();';
5540
+ var fitRuntime = '"use strict";(()=>{function a(t,e,n){if(e===t)return t.scrollHeight<=t.clientHeight+.5&&t.scrollWidth<=t.clientWidth+.5;let o=n.getComputedStyle(t),i=t.clientHeight-(parseFloat(o.paddingTop)||0)-(parseFloat(o.paddingBottom)||0);return e.scrollHeight<=i+.5&&e.scrollWidth<=e.clientWidth+.5}function m(t,e){let n=t.firstElementChild??t;n.style.fontSize="";let o=parseFloat(e.getComputedStyle(n).fontSize)||16,i=Math.min(o,parseFloat(t.getAttribute("data-ff-fit-min")??"")||o/2);if(a(t,n,e))return o;let l=i,s=o;for(let d=0;d<14;d++){let r=(l+s)/2;n.style.fontSize=`${r}px`,a(t,n,e)?l=r:s=r}let f=Math.floor(l*100)/100;return n.style.fontSize=`${f}px`,f}function c(t){let e=()=>{for(let o of Array.from(t.document.querySelectorAll(\'[data-ff-fit="shrink"]\')))m(o,t);t.document.documentElement.setAttribute("data-ff-fit","done")};t.formfeedFit=e;let n=t.document.fonts;n?n.ready.then(e):t.addEventListener("load",e)}c(window);})();';
5541
+ var stageRuntime = '"use strict";(()=>{var I=`\nhtml.ff-edit-mode [data-ff-edit] { cursor: text; }\nhtml.ff-edit-mode [data-ff-edit]:hover { outline: 1px dashed rgba(37,99,235,.85); outline-offset: 2px; }\nhtml.ff-edit-mode [data-ff-edit="t"]:hover { outline-color: rgba(147,51,234,.9); }\n.ff-editing { outline: 2px solid #2563eb !important; outline-offset: 2px; cursor: text; }\n.ff-editing .ff-chip { background: rgba(37,99,235,.14) !important; box-shadow: inset 0 0 0 1px rgba(37,99,235,.45) !important; border-radius: 3px !important; padding: 0 1px !important; cursor: default; }\n.ff-edit-badge { position: absolute; z-index: 2147483647; font: 500 11px/1.4 system-ui, sans-serif !important; color: #fff !important; background: #2563eb !important; padding: 1px 6px !important; border-radius: 4px !important; pointer-events: none; white-space: nowrap; }\n`;function D(a,l={}){let f=l.clicks!==!1,c=l.live===!0,o=a.document,n=null,s=null,u=e=>{try{a.parent.postMessage(e,"*")}catch{}},p=o.createElement("style");p.setAttribute("data-formfeed","edit"),p.setAttribute("data-pagedjs-ignore",""),p.textContent=I,(o.head??o.documentElement).appendChild(p),f&&o.documentElement.classList.add("ff-edit-mode");let d=e=>(e&&typeof e.closest=="function"?e:null)?.closest("[data-ff-edit]")??null;o.addEventListener("click",e=>{if(!f)return;if(n&&!n.done&&n.element.contains(e.target)){e.stopImmediatePropagation();return}let t=d(e.target);if(!t)return;e.preventDefault(),e.stopImmediatePropagation(),n&&!n.done&&v();let r=t.getAttribute("data-ff-src")??"",i=t.getBoundingClientRect();s={src:r,element:t,x:e.clientX,y:e.clientY},u({type:"formfeed:edit-request",src:r,edit:t.getAttribute("data-ff-edit"),text:t.textContent??"",count:o.querySelectorAll(`[data-ff-src="${r.replace(/"/g,"")}"]`).length,split:t.hasAttribute("data-split-from")||t.hasAttribute("data-split-to"),rect:{left:i.left,top:i.top,width:i.width,height:i.height}})},!0),o.addEventListener("keydown",e=>{if(!f||n&&!n.done||!(e.ctrlKey||e.metaKey)||e.altKey)return;let t=e.key.toLowerCase();(t==="z"||t==="y")&&(e.preventDefault(),u({type:t==="y"||e.shiftKey?"formfeed:redo":"formfeed:undo"}))}),a.addEventListener("message",e=>{if(e.source!==a.parent)return;let t=e.data;!t||typeof t.type!="string"||(t.type==="formfeed:edit-begin"?b(t):(t.type==="formfeed:edit-refuse"||t.type==="formfeed:edit-end")&&(s=null,n&&!n.done&&L(!1)))});function b(e){n&&!n.done&&L(!1);let t=e.layer?o.querySelector(`[data-ff-layer="${String(e.layer).replace(/"/g,"")}"] > .ff-text`):s&&s.src===e.src?s.element:o.querySelector(`[data-ff-src="${String(e.src).replace(/"/g,"")}"]`),r=e.at,i=e.layer?r&&Number.isFinite(r.x)&&Number.isFinite(r.y)?{x:Number(r.x),y:Number(r.y)}:null:s&&s.src===e.src?s:null;if(s=null,!t||!Array.isArray(e.items)){u({type:"formfeed:edit-cancel",token:String(e.token)});return}let x=Array.from(t.childNodes),g=o.createDocumentFragment();for(let y of e.items)if(y.kind==="text")g.appendChild(o.createTextNode(String(y.text)));else if(y.kind==="break")g.appendChild(o.createElement("br"));else if(y.kind==="chip"){let m=o.createElement("span");m.className="ff-chip",m.setAttribute("data-ff-chip",String(y.index)),m.setAttribute("contenteditable","false"),y.title&&(m.title=String(y.title)),m.textContent=String(y.text),g.appendChild(m)}for(let y of x)t.removeChild(y);t.appendChild(g),t.contentEditable="true",t.classList.add("ff-editing"),t.setAttribute("role","textbox"),e.label&&t.setAttribute("aria-label",String(e.label)),t.spellcheck=!0,n={token:String(e.token),element:t,saved:x,allowBreaks:e.allowBreaks===!0,badge:e.note?E(t,String(e.note)):null,done:!1},t.addEventListener("keydown",C),t.addEventListener("beforeinput",S),t.addEventListener("paste",A),t.addEventListener("drop",T),t.addEventListener("focusout",N),c&&t.addEventListener("input",k),t.focus(),e.select==="all"?H(t):M(t,i),u({type:"formfeed:edit-started",token:n.token}),typeof e.insert=="string"&&e.insert&&h(e.insert.replace(/\\r\\n?|\\n/g," "))}function h(e){if(typeof o.execCommand=="function"&&o.execCommand("insertText",!1,e))return;let t=a.getSelection(),r=t&&t.rangeCount?t.getRangeAt(0):null;r&&(r.deleteContents(),r.insertNode(o.createTextNode(e)),r.collapse(!1),c&&k())}function k(){n&&!n.done&&u({type:"formfeed:edit-input",token:n.token,items:w(n.element)})}function E(e,t){let r=o.createElement("div");r.className="ff-edit-badge",r.textContent=t;let i=e.getBoundingClientRect();return r.style.left=`${i.left+a.scrollX}px`,r.style.top=`${Math.max(0,i.top+a.scrollY-22)}px`,o.body.appendChild(r),r}function H(e){let t=a.getSelection();if(!t)return;let r=o.createRange();r.selectNodeContents(e),t.removeAllRanges(),t.addRange(r)}function M(e,t){let r=a.getSelection();if(!r)return;let i=null,x=o;if(t){if(x.caretRangeFromPoint)i=x.caretRangeFromPoint(t.x,t.y);else if(x.caretPositionFromPoint){let g=x.caretPositionFromPoint(t.x,t.y);g&&(i=o.createRange(),i.setStart(g.offsetNode,g.offset))}}if(!i||!e.contains(i.startContainer))i=o.createRange(),i.selectNodeContents(e),i.collapse(!1);else if(t){let g=i.startContainer,m=(g.nodeType===1?g:g.parentElement)?.closest("[data-ff-chip]")??null;if(m&&m!==e&&e.contains(m)){let F=m.getBoundingClientRect();i=o.createRange(),t.x<F.left+F.width/2?i.setStartBefore(m):i.setStartAfter(m),i.collapse(!0)}}r.removeAllRanges(),r.addRange(i)}function C(e){let t=e.key;t==="Escape"?(e.preventDefault(),L(!0)):t==="Enter"&&!e.shiftKey?(e.preventDefault(),v()):t==="Enter"?(e.preventDefault(),n?.allowBreaks&&P()):t==="Tab"?(e.preventDefault(),v()):(e.ctrlKey||e.metaKey)&&["b","i","u"].includes(t.toLowerCase())&&(e.preventDefault(),c&&n&&!e.altKey&&u({type:"formfeed:edit-style",token:n.token,key:t.toLowerCase()}))}function S(e){let t=e.inputType??"";(t.startsWith("format")||t==="insertFromDrop"||t==="insertParagraph"||t==="insertLineBreak")&&e.preventDefault()}function A(e){e.preventDefault(),h((e.clipboardData?.getData("text/plain")??"").replace(/\\r\\n?|\\n/g," "))}function T(e){e.preventDefault()}function N(){a.setTimeout(()=>{n&&!n.done&&(!o.hasFocus()||!n.element.contains(o.activeElement))&&v()},0)}function P(){let e=a.getSelection();if(!e||!e.rangeCount)return;let t=e.getRangeAt(0);t.deleteContents();let r=o.createElement("br");t.insertNode(r),r.nextSibling||r.parentNode?.appendChild(o.createElement("br"));let i=o.createRange();i.setStartAfter(r),i.collapse(!0),e.removeAllRanges(),e.addRange(i)}function R(e){e.done=!0;let t=e.element;t.removeEventListener("keydown",C),t.removeEventListener("beforeinput",S),t.removeEventListener("paste",A),t.removeEventListener("drop",T),t.removeEventListener("focusout",N),t.removeEventListener("input",k),t.removeAttribute("contenteditable"),t.removeAttribute("role"),t.removeAttribute("aria-label"),t.classList.remove("ff-editing"),e.badge?.remove(),n===e&&(n=null)}function v(){let e=n;if(!e||e.done)return;let t=w(e.element);R(e),u({type:"formfeed:edit-commit",token:e.token,items:t})}function L(e){let t=n;if(!t||t.done)return;let r=t.element;for(;r.firstChild;)r.removeChild(r.firstChild);for(let i of t.saved)r.appendChild(i);R(t),e&&u({type:"formfeed:edit-cancel",token:t.token})}}function w(a){let l=[],f=o=>{let n=l[l.length-1];n?.kind==="text"?n.text+=o:o&&l.push({kind:"text",text:o})},c=o=>{for(let n of Array.from(o.childNodes))if(n.nodeType===3)f(n.data);else if(n.nodeType===1){let s=n,u=s.getAttribute("data-ff-chip");u!==null?l.push({kind:"chip",index:Number(u)}):s.tagName==="BR"?l.push({kind:"break"}):((s.tagName==="DIV"||s.tagName==="P")&&l.length&&l.push({kind:"break"}),c(s))}};for(c(a);l[l.length-1]?.kind==="break";)l.pop();return l}function K(a,l){return Array.from(a.querySelectorAll("[data-ff-layer]")).map(f=>{let c=f.querySelector(":scope > .ff-text"),o=!1;if(c&&f.style.height!=="auto"){let n=l.getComputedStyle(f),s=f.clientHeight-(parseFloat(n.paddingTop)||0)-(parseFloat(n.paddingBottom)||0);o=n.overflow==="hidden"&&(c.scrollHeight>s+1||c.scrollWidth>c.clientWidth+1)}return{id:f.getAttribute("data-ff-layer")??"",width:f.offsetWidth,height:f.offsetHeight,overflow:o}})}function B(a){let l=a.document,f=p=>{try{a.parent.postMessage(p,"*")}catch{}},c=p=>typeof p=="string"?l.querySelector(`[data-ff-layer="${p.replace(/"/g,"")}"]`):null,o=()=>{for(let p of Array.from(l.querySelectorAll("[data-ff-layer]")))for(let d of["left","top","width","height","transform"])p.style.removeProperty(d)},n=()=>{a.formfeedFit?.(),f({type:"formfeed:ready",layers:K(l,a)})};a.addEventListener("message",p=>{if(p.source!==a.parent)return;let d=p.data;if(!(!d||typeof d.type!="string")){if(d.type==="formfeed:layer-style"){let b=c(d.id);if(!b)return;let h=(k,E)=>{typeof E=="number"&&Number.isFinite(E)&&b.style.setProperty(k,`${E}px`)};h("left",d.left),h("top",d.top),h("width",d.width),h("height",d.height),typeof d.rotation=="number"&&Number.isFinite(d.rotation)&&b.style.setProperty("transform",d.rotation?`rotate(${d.rotation}deg)`:"none")}else if(d.type==="formfeed:layer-style-clear")o();else if(d.type==="formfeed:css"&&typeof d.css=="string"){let b=l.querySelector(\'style[data-formfeed="template"]\');b&&(b.textContent=d.css),o(),n()}}}),D(a,{clicks:!1,live:!0});let s=l.fonts,u=()=>s?void s.ready.then(n):n();l.readyState==="complete"?u():a.addEventListener("load",u)}B(window);})();';
5542
+
5409
5543
  // packages/engine/src/lib/assemble.ts
5410
5544
  var DEFAULT_CHROME_PADDING = "0 10mm";
5411
5545
  function defaultSettings() {
@@ -5505,9 +5639,12 @@ function chartScripts(html, vendor) {
5505
5639
  if (!vendor?.chartJs || !html.includes("data-ff-chart"))
5506
5640
  return { head: "", body: "" };
5507
5641
  const lib = "inline" in vendor.chartJs ? `<script>${vendor.chartJs.inline}</script>` : `<script src="${escapeHtml(vendor.chartJs.src)}"></script>`;
5508
- return { head: `${lib}
5509
- `, body: `${chartInitScript()}
5510
- ` };
5642
+ return {
5643
+ head: `${lib}
5644
+ `,
5645
+ body: `${chartInitScript()}
5646
+ `
5647
+ };
5511
5648
  }
5512
5649
  function assembleDocument(input) {
5513
5650
  const settings = mergeSettings(input.settings);
@@ -5534,6 +5671,8 @@ ${brandFontFamilies(input.brand)}`
5534
5671
  ` : "" : input.extraCss ? `<style data-formfeed="extra">${input.extraCss}</style>
5535
5672
  ` : "";
5536
5673
  const charts = chartScripts(input.html, input.vendor);
5674
+ const fit = input.html.includes("data-ff-fit") ? `<script>${fitRuntime.replace(/<\/script/gi, "<\\/script")}</script>
5675
+ ` : "";
5537
5676
  return `<!doctype html>
5538
5677
  <html lang="${escapeHtml(locale.split("_")[0] ?? locale)}">
5539
5678
  <head>
@@ -5545,7 +5684,7 @@ ${tailwind}<style data-formfeed="template">${input.css ?? ""}</style>
5545
5684
  ${charts.head}</head>
5546
5685
  <body class="formfeed-body formfeed-${kind}${input.mode === "preview" ? " formfeed-preview" : ""}">
5547
5686
  ${input.html}
5548
- ${charts.body}</body>
5687
+ ${charts.body}${fit}</body>
5549
5688
  </html>
5550
5689
  `;
5551
5690
  }
@@ -5620,7 +5759,10 @@ function paperOf(settings) {
5620
5759
  }
5621
5760
  var pageNumberSpans = (html, page, total) => {
5622
5761
  const fill = (source, name, value) => source.replace(
5623
- new RegExp(`<([a-z][a-z0-9]*)(\\s[^>]*?\\bclass=(["'])(?:[^"']*\\s)?${name}(?:\\s[^"']*)?\\3[^>]*)>[^<]*</\\1>`, "gi"),
5762
+ new RegExp(
5763
+ `<([a-z][a-z0-9]*)(\\s[^>]*?\\bclass=(["'])(?:[^"']*\\s)?${name}(?:\\s[^"']*)?\\3[^>]*)>[^<]*</\\1>`,
5764
+ "gi"
5765
+ ),
5624
5766
  (_match, tag, attrs) => `<${tag}${attrs}>${value}</${tag}>`
5625
5767
  );
5626
5768
  return fill(fill(html, "pageNumber", page), "totalPages", total);
@@ -5643,6 +5785,7 @@ document.addEventListener('click', function (event) {
5643
5785
  } catch (e) {}
5644
5786
  }, true);
5645
5787
  </script>`;
5788
+ var textEditScript = `<script>${textEditRuntime.replace(/<\/script/gi, "<\\/script")}</script>`;
5646
5789
  var pageNavScript = `<script>
5647
5790
  (function () {
5648
5791
  var pages = function () { return document.querySelectorAll('.pagedjs_page'); };
@@ -5744,8 +5887,11 @@ var zoomGestureScript = `<script>
5744
5887
  })();
5745
5888
  </script>`;
5746
5889
  function intoHead(document, markup) {
5747
- return document.replace(/<head[^>]*>(?:\s*<meta\b[^>]*>)*/i, (head) => `${head}
5748
- ${markup}`);
5890
+ return document.replace(
5891
+ /<head[^>]*>(?:\s*<meta\b[^>]*>)*/i,
5892
+ (head) => `${head}
5893
+ ${markup}`
5894
+ );
5749
5895
  }
5750
5896
  function chromeBoxStyle(settings, which) {
5751
5897
  const margin = settings.margin ?? {};
@@ -5760,7 +5906,7 @@ function flowChromeStyle(settings, which) {
5760
5906
  const height = settings[which]?.height;
5761
5907
  return `position:absolute;${which === "header" ? "top" : "bottom"}:0;left:0;width:100%;box-sizing:border-box;padding:${padding};${height ? `height:${height};` : ""}`;
5762
5908
  }
5763
- function flowDocument(draft) {
5909
+ function flowDocument(draft, options = {}) {
5764
5910
  const { settings, kind, headerHtml, footerHtml } = draft;
5765
5911
  const margin = settings.margin ?? {};
5766
5912
  const paper = paperOf(settings);
@@ -5772,7 +5918,15 @@ body.formfeed-preview { position: relative; box-sizing: border-box; width: ${pap
5772
5918
  </style>` : `<style data-formfeed="preview">html { background: #e5e7eb; } body.formfeed-preview { margin: 24px auto; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.2); }</style>`;
5773
5919
  const header = headerHtml ? `<div class="formfeed-chrome" style="${pdf ? flowChromeStyle(settings, "header") : `${chromeBoxStyle(settings, "header")}margin-bottom:8px`}">${headerHtml}</div>` : "";
5774
5920
  const footer = footerHtml ? `<div class="formfeed-chrome" style="${pdf ? flowChromeStyle(settings, "footer") : `${chromeBoxStyle(settings, "footer")}margin-top:8px`}">${pageNumberSpans(footerHtml, "1", "1")}</div>` : "";
5775
- return intoHead(draft.document, `${chrome}${inspectScript}${zoomGestureScript}${pdf ? overflowScript("flow") : ""}`).replace(/(<body[^>]*>)/, `$1${header}`).replace("</body>", `${footer}</body>`);
5921
+ return intoHead(
5922
+ draft.document,
5923
+ `${chrome}${options.editing ? textEditScript : ""}${inspectScript}${zoomGestureScript}${pdf ? overflowScript("flow") : ""}`
5924
+ ).replace(/(<body[^>]*>)/, `$1${header}`).replace("</body>", `${footer}</body>`);
5925
+ }
5926
+ function canvasDocument(draft) {
5927
+ const chrome = `<style data-formfeed="stage">html, body { background: transparent !important; margin: 0 !important; }</style>`;
5928
+ const runtime = `<script>${stageRuntime.replace(/<\/script/gi, "<\\/script")}</script>`;
5929
+ return intoHead(draft.document, `${chrome}${runtime}${zoomGestureScript}`);
5776
5930
  }
5777
5931
  function pagedDocument(draft, options) {
5778
5932
  const { headerHtml, footerHtml, settings } = draft;
@@ -5794,82 +5948,17 @@ html { background: #e5e7eb; }
5794
5948
  `;
5795
5949
  const selectorGuard = `<script>(function(){var q=DocumentFragment.prototype.querySelectorAll;DocumentFragment.prototype.querySelectorAll=function(s){try{return q.call(this,s);}catch(e){console.warn('formfeed paged preview: skipped a selector Paged.js could not use:',s);return q.call(this,':not(*)');}};})();</script>`;
5796
5950
  const boxSizingGuard = `<script>(function(){var rule='.pagedjs_pagebox *';var o=new MutationObserver(function(ms){ms.forEach(function(m){m.addedNodes.forEach(function(n){if(n.nodeName==='STYLE'&&n.textContent&&n.textContent.indexOf(rule)>=0){n.textContent=n.textContent.split(rule).join('.pagedjs_pagebox [class*="pagedjs_"]');o.disconnect();}});});});o.observe(document.documentElement,{childList:true,subtree:true});})();</script>`;
5797
- const config = `<script>window.PagedConfig = { auto: true, after: function (flow) { try { if (window.formfeedDrawCharts) window.formfeedDrawCharts(); } catch (e) {} try { parent.postMessage({ type: 'formfeed:pages', pages: flow.total }, '*'); } catch (e) {} if (window.formfeedCheckOverflow) window.formfeedCheckOverflow(); } };</script>`;
5951
+ const config = `<script>window.PagedConfig = { auto: true, after: function (flow) { try { if (window.formfeedDrawCharts) window.formfeedDrawCharts(); } catch (e) {} try { if (window.formfeedFit) window.formfeedFit(); } catch (e) {} try { parent.postMessage({ type: 'formfeed:pages', pages: flow.total }, '*'); } catch (e) {} if (window.formfeedCheckOverflow) window.formfeedCheckOverflow(); } };</script>`;
5798
5952
  const script = `<script src="${options.pagedScriptUrl}"></script>`;
5799
5953
  const header = headerHtml ? `<div class="ff-running-header">${pageNumberSpans(headerHtml, '<span class="ff-page-no"></span>', '<span class="ff-page-total"></span>')}</div>` : "";
5800
5954
  const footer = footerHtml ? `<div class="ff-running-footer">${pageNumberSpans(footerHtml, '<span class="ff-page-no"></span>', '<span class="ff-page-total"></span>')}</div>` : "";
5801
5955
  return intoHead(
5802
5956
  draft.document,
5803
- `<style data-formfeed="paged">${runningCss}</style>${selectorGuard}${boxSizingGuard}${overflowScript("paged")}${config}${script}${inspectScript}${pageNavScript}${zoomGestureScript}`
5957
+ `<style data-formfeed="paged">${runningCss}</style>${selectorGuard}${boxSizingGuard}${overflowScript("paged")}${config}${script}${options.editing ? textEditScript : ""}${inspectScript}${pageNavScript}${zoomGestureScript}`
5804
5958
  ).replace(/(<body[^>]*>)/, `$1${header}${footer}`);
5805
5959
  }
5806
5960
 
5807
- // packages/engine/src/lib/source-positions.ts
5808
- var sourceAttribute = "data-ff-src";
5809
- function parseSourceAttribute(value) {
5810
- const match = /^(body|header|footer):(\d+):(\d+)$/.exec(value ?? "");
5811
- if (!match) return null;
5812
- const line = Number(match[2]);
5813
- const column = Number(match[3]);
5814
- if (line < 1 || column < 1) return null;
5815
- return { file: match[1], line, column };
5816
- }
5817
- function annotateSourcePositions(source, engine, file) {
5818
- const scanner = new Scanner(source, engine);
5819
- const lines = lineStarts(source);
5820
- const out = [];
5821
- let copied = 0;
5822
- const n = source.length;
5823
- let i = 0;
5824
- while (i < n) {
5825
- const ch = source[i];
5826
- if (ch === "{") {
5827
- i = scanner.skipTemplate(i) ?? i + 1;
5828
- continue;
5829
- }
5830
- if (ch !== "<") {
5831
- i++;
5832
- continue;
5833
- }
5834
- if (source.startsWith("<!--", i)) {
5835
- i = scanner.skipTo(i + 4, "-->");
5836
- continue;
5837
- }
5838
- if (source.startsWith("<![CDATA[", i)) {
5839
- i = scanner.skipTo(i + 9, "]]>");
5840
- continue;
5841
- }
5842
- const next = source[i + 1];
5843
- if (next === "!" || next === "?") {
5844
- i = scanner.tagEnd(i + 2);
5845
- continue;
5846
- }
5847
- if (next === "/") {
5848
- i += 2;
5849
- continue;
5850
- }
5851
- tagName.lastIndex = i + 1;
5852
- const name = tagName.exec(source)?.[0];
5853
- if (!name) {
5854
- i++;
5855
- continue;
5856
- }
5857
- const nameEnd = i + 1 + name.length;
5858
- const end = scanner.tagEnd(nameEnd);
5859
- const after = source[nameEnd];
5860
- const plainName = after === void 0 || after === ">" || after === "/" || /\s/.test(after);
5861
- if (plainName && !source.slice(nameEnd, end).toLowerCase().includes(sourceAttribute)) {
5862
- const { line, column } = locate(lines, i);
5863
- out.push(source.slice(copied, nameEnd), ` ${sourceAttribute}="${file}:${line}:${column}"`);
5864
- copied = nameEnd;
5865
- }
5866
- i = end;
5867
- if (rawTextElements.has(name.toLowerCase()) && source[end - 1] === ">")
5868
- i = scanner.skipToClosingTag(end, name);
5869
- }
5870
- out.push(source.slice(copied));
5871
- return out.join("");
5872
- }
5961
+ // packages/engine/src/lib/source-scan.ts
5873
5962
  var tagName = /[A-Za-z][^\s/>{}<"'=]*/y;
5874
5963
  var rawTextElements = /* @__PURE__ */ new Set([
5875
5964
  "script",
@@ -5910,6 +5999,38 @@ var Scanner = class {
5910
5999
  return this.blockEnd(tag.end, tag.keyword);
5911
6000
  return tag.end;
5912
6001
  }
6002
+ /** The construct starting at `at` and what kind it is, or null when `{` starts none. */
6003
+ classify(at) {
6004
+ const s = this.source;
6005
+ const tag = this.templateTag(at);
6006
+ if (!tag) return null;
6007
+ if (this.engine === "handlebars") {
6008
+ if (s.startsWith("{{{{", at)) return { type: "statement", end: tag.end };
6009
+ if (s.startsWith("{{!", at)) return { type: "comment", end: tag.end };
6010
+ if (s.startsWith("{{{", at))
6011
+ return {
6012
+ type: "output",
6013
+ end: tag.end,
6014
+ inner: s.slice(at + 3, tag.end - 3)
6015
+ };
6016
+ const inner = s.slice(at + 2, tag.end - 2);
6017
+ const head = inner.replace(/^~?\s*/, "");
6018
+ if (/^[#/^>*]/.test(head) || /^else(?:\s|~?$)/.test(head))
6019
+ return { type: "statement", end: tag.end };
6020
+ return { type: "output", end: tag.end, inner };
6021
+ }
6022
+ if (s.startsWith("{#", at)) return { type: "comment", end: tag.end };
6023
+ if (s.startsWith("{{", at))
6024
+ return {
6025
+ type: "output",
6026
+ end: tag.end,
6027
+ inner: s.slice(at + 2, tag.end - 2)
6028
+ };
6029
+ if (/^\{%[-+~]?\s*#/.test(s.slice(at, at + 64)))
6030
+ return { type: "comment", end: tag.end };
6031
+ const end = tag.keyword && this.captures(tag.keyword, tag.rest ?? "") ? this.blockEnd(tag.end, tag.keyword) : tag.end;
6032
+ return { type: "statement", end, keyword: tag.keyword };
6033
+ }
5913
6034
  /** Index after `close`, stepping over template constructs; the end of the source when missing. */
5914
6035
  skipTo(from, close) {
5915
6036
  let j = from;
@@ -5974,7 +6095,8 @@ var Scanner = class {
5974
6095
  j++;
5975
6096
  continue;
5976
6097
  }
5977
- if (tag.keyword === keyword && this.captures(keyword, tag.rest ?? "")) depth++;
6098
+ if (tag.keyword === keyword && this.captures(keyword, tag.rest ?? ""))
6099
+ depth++;
5978
6100
  else if (tag.keyword === `end${keyword}` && --depth === 0) return tag.end;
5979
6101
  j = tag.end;
5980
6102
  }
@@ -5985,22 +6107,32 @@ var Scanner = class {
5985
6107
  if (s[at] !== "{") return null;
5986
6108
  if (this.engine === "handlebars") {
5987
6109
  if (s.startsWith("{{{{", at)) return { end: this.handlebarsRawBlock(at) };
5988
- if (s.startsWith("{{!--", at)) return { end: this.indexAfter(at + 5, "--}}") };
5989
- if (s.startsWith("{{!", at)) return { end: this.indexAfter(at + 3, "}}") };
5990
- if (s.startsWith("{{{", at)) return { end: this.expressionEnd(at + 3, "}}}") };
5991
- if (s.startsWith("{{", at)) return { end: this.expressionEnd(at + 2, "}}") };
6110
+ if (s.startsWith("{{!--", at))
6111
+ return { end: this.indexAfter(at + 5, "--}}") };
6112
+ if (s.startsWith("{{!", at))
6113
+ return { end: this.indexAfter(at + 3, "}}") };
6114
+ if (s.startsWith("{{{", at))
6115
+ return { end: this.expressionEnd(at + 3, "}}}") };
6116
+ if (s.startsWith("{{", at))
6117
+ return { end: this.expressionEnd(at + 2, "}}") };
5992
6118
  return null;
5993
6119
  }
5994
6120
  if (this.engine === "jinja2" && s.startsWith("{#", at))
5995
6121
  return { end: this.indexAfter(at + 2, "#}") };
5996
- if (s.startsWith("{{", at)) return { end: this.expressionEnd(at + 2, "}}") };
6122
+ if (s.startsWith("{{", at))
6123
+ return { end: this.expressionEnd(at + 2, "}}") };
5997
6124
  if (!s.startsWith("{%", at)) return null;
5998
6125
  const end = /^[-+~]?\s*#/.test(s.slice(at + 2, at + 64)) ? this.indexAfter(at + 2, "%}") : this.expressionEnd(at + 2, "%}");
5999
- const inner = /^[-+~]?\s*(\w+)([\s\S]*)$/.exec(s.slice(at + 2, Math.max(at + 2, end - 2)));
6126
+ const inner = /^[-+~]?\s*(\w+)([\s\S]*)$/.exec(
6127
+ s.slice(at + 2, Math.max(at + 2, end - 2))
6128
+ );
6000
6129
  const keyword = inner?.[1];
6001
6130
  if (!keyword) return { end };
6002
6131
  if (literalBlocks[this.engine].has(keyword)) {
6003
- const close = new RegExp(`\\{%[-+~]?\\s*end${keyword}\\s*[-+~]?%\\}`, "g");
6132
+ const close = new RegExp(
6133
+ `\\{%[-+~]?\\s*end${keyword}\\s*[-+~]?%\\}`,
6134
+ "g"
6135
+ );
6004
6136
  close.lastIndex = end;
6005
6137
  const match = close.exec(s);
6006
6138
  return { end: match ? match.index + match[0].length : this.n, keyword };
@@ -6055,125 +6187,2110 @@ function locate(starts, offset) {
6055
6187
  }
6056
6188
  return { line: low + 1, column: offset - (starts[low] ?? 0) + 1 };
6057
6189
  }
6190
+ function offsetOf(starts, sourceLength, line, column) {
6191
+ const start = starts[line - 1];
6192
+ if (start === void 0 || column < 1) return null;
6193
+ const offset = start + column - 1;
6194
+ const next = starts[line] ?? sourceLength + 1;
6195
+ return offset < next ? offset : null;
6196
+ }
6058
6197
 
6059
- // packages/engine/src/lib/office/errors.ts
6060
- var OfficeError = class extends Error {
6061
- constructor(code, message, details = {}) {
6062
- super(message);
6063
- this.code = code;
6064
- this.details = details;
6065
- this.name = "OfficeError";
6066
- }
6067
- };
6068
- var officeLimits = {
6069
- /** Entries in one container. */
6070
- maxEntries: 2e3,
6071
- /** Sum of the uncompressed sizes the central directory declares. */
6072
- maxDeclaredBytes: 200 * 1024 * 1024,
6073
- /** One part we inflate; enforced while inflating, because the declared size can lie. */
6074
- maxPartBytes: 20 * 1024 * 1024,
6075
- /** A conversion upload or a template file. */
6076
- maxFileBytes: 20 * 1024 * 1024
6077
- };
6078
-
6079
- // node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/browser.js
6080
- var u8 = Uint8Array;
6081
- var u16 = Uint16Array;
6082
- var i32 = Int32Array;
6083
- var fleb = new u8([
6084
- 0,
6085
- 0,
6086
- 0,
6087
- 0,
6088
- 0,
6089
- 0,
6090
- 0,
6091
- 0,
6092
- 1,
6093
- 1,
6094
- 1,
6095
- 1,
6096
- 2,
6097
- 2,
6098
- 2,
6099
- 2,
6100
- 3,
6101
- 3,
6102
- 3,
6103
- 3,
6104
- 4,
6105
- 4,
6106
- 4,
6107
- 4,
6108
- 5,
6109
- 5,
6110
- 5,
6111
- 5,
6112
- 0,
6113
- /* unused */
6114
- 0,
6115
- 0,
6116
- /* impossible */
6117
- 0
6198
+ // packages/engine/src/lib/source-edit.ts
6199
+ var MAX_REGION = 2e4;
6200
+ var voidElements = /* @__PURE__ */ new Set([
6201
+ "area",
6202
+ "base",
6203
+ "br",
6204
+ "col",
6205
+ "embed",
6206
+ "hr",
6207
+ "img",
6208
+ "input",
6209
+ "link",
6210
+ "meta",
6211
+ "param",
6212
+ "source",
6213
+ "track",
6214
+ "wbr"
6118
6215
  ]);
6119
- var fdeb = new u8([
6120
- 0,
6121
- 0,
6122
- 0,
6123
- 0,
6124
- 1,
6125
- 1,
6126
- 2,
6127
- 2,
6128
- 3,
6129
- 3,
6130
- 4,
6131
- 4,
6132
- 5,
6133
- 5,
6134
- 6,
6135
- 6,
6136
- 7,
6137
- 7,
6138
- 8,
6139
- 8,
6140
- 9,
6141
- 9,
6142
- 10,
6143
- 10,
6144
- 11,
6145
- 11,
6146
- 12,
6147
- 12,
6148
- 13,
6149
- 13,
6150
- /* unused */
6151
- 0,
6152
- 0
6216
+ var phrasingElements = /* @__PURE__ */ new Set([
6217
+ "a",
6218
+ "abbr",
6219
+ "b",
6220
+ "bdi",
6221
+ "bdo",
6222
+ "cite",
6223
+ "code",
6224
+ "data",
6225
+ "del",
6226
+ "dfn",
6227
+ "em",
6228
+ "font",
6229
+ "i",
6230
+ "img",
6231
+ "ins",
6232
+ "kbd",
6233
+ "label",
6234
+ "mark",
6235
+ "q",
6236
+ "s",
6237
+ "samp",
6238
+ "small",
6239
+ "span",
6240
+ "strong",
6241
+ "sub",
6242
+ "sup",
6243
+ "svg",
6244
+ "time",
6245
+ "u",
6246
+ "var",
6247
+ "wbr"
6153
6248
  ]);
6154
- var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
6155
- var freb = function(eb, start) {
6156
- var b = new u16(31);
6157
- for (var i = 0; i < 31; ++i) {
6158
- b[i] = start += 1 << eb[i - 1];
6249
+ var namedEntities = {
6250
+ amp: 38,
6251
+ lt: 60,
6252
+ gt: 62,
6253
+ quot: 34,
6254
+ apos: 39,
6255
+ nbsp: 160,
6256
+ shy: 173,
6257
+ ensp: 8194,
6258
+ emsp: 8195,
6259
+ thinsp: 8201,
6260
+ zwnj: 8204,
6261
+ zwj: 8205,
6262
+ ndash: 8211,
6263
+ mdash: 8212,
6264
+ lsquo: 8216,
6265
+ rsquo: 8217,
6266
+ sbquo: 8218,
6267
+ ldquo: 8220,
6268
+ rdquo: 8221,
6269
+ bdquo: 8222,
6270
+ laquo: 171,
6271
+ raquo: 187,
6272
+ lsaquo: 8249,
6273
+ rsaquo: 8250,
6274
+ hellip: 8230,
6275
+ bull: 8226,
6276
+ middot: 183,
6277
+ euro: 8364,
6278
+ pound: 163,
6279
+ yen: 165,
6280
+ cent: 162,
6281
+ copy: 169,
6282
+ reg: 174,
6283
+ trade: 8482,
6284
+ deg: 176,
6285
+ sect: 167,
6286
+ para: 182,
6287
+ times: 215,
6288
+ divide: 247,
6289
+ plusmn: 177,
6290
+ micro: 181,
6291
+ frac12: 189,
6292
+ frac14: 188,
6293
+ frac34: 190,
6294
+ sup2: 178,
6295
+ sup3: 179,
6296
+ dagger: 8224,
6297
+ Dagger: 8225,
6298
+ larr: 8592,
6299
+ rarr: 8594,
6300
+ uarr: 8593,
6301
+ darr: 8595,
6302
+ auml: 228,
6303
+ ouml: 246,
6304
+ uuml: 252,
6305
+ Auml: 196,
6306
+ Ouml: 214,
6307
+ Uuml: 220,
6308
+ szlig: 223,
6309
+ eacute: 233,
6310
+ egrave: 232,
6311
+ agrave: 224,
6312
+ aacute: 225,
6313
+ ccedil: 231,
6314
+ oacute: 243,
6315
+ iacute: 237,
6316
+ uacute: 250,
6317
+ ntilde: 241
6318
+ };
6319
+ var entityPattern = /&(#[0-9]{1,7}|#[xX][0-9a-fA-F]{1,6}|[A-Za-z][A-Za-z0-9]{0,31});/y;
6320
+ var NBSP = String.fromCharCode(160);
6321
+ var BREAK = String.fromCharCode(57344);
6322
+ var REMOVED = String.fromCharCode(57345);
6323
+ var CHIP_OPEN = String.fromCharCode(57346);
6324
+ var CHIP_CLOSE = String.fromCharCode(57347);
6325
+ function editableRegion(source, engine, location) {
6326
+ const offset = offsetOf(
6327
+ lineStarts(source),
6328
+ source.length,
6329
+ location.line,
6330
+ location.column
6331
+ );
6332
+ return offset === null ? { reason: "not-an-element" } : editableRegionAt(source, engine, offset);
6333
+ }
6334
+ function editableRegionAt(source, engine, at) {
6335
+ const scanned = scanRegion(source, engine, at);
6336
+ if ("reason" in scanned) return scanned;
6337
+ const region = { kind: "text", ...scanned };
6338
+ const visible = region.parts.filter(
6339
+ (p) => !(p.kind === "text" && !source.slice(p.start, p.end).trim())
6340
+ );
6341
+ const only = visible.length === 1 ? visible[0] : void 0;
6342
+ if (only?.kind === "chip" && only.chip === "expression") {
6343
+ const call = translationCall(engine, only.inner ?? "");
6344
+ if (call)
6345
+ return { ...region, kind: "t", key: call.key, params: call.params };
6346
+ }
6347
+ const hasText = region.parts.some(
6348
+ (p) => p.kind === "text" && decodeText2(source.slice(p.start, p.end)).text.trim() !== ""
6349
+ );
6350
+ return hasText ? region : { reason: "no-text" };
6351
+ }
6352
+ function findRegionAgain(source, engine, outer) {
6353
+ const first = source.indexOf(outer);
6354
+ if (first < 0 || source.indexOf(outer, first + 1) >= 0) return null;
6355
+ const found = editableRegionAt(source, engine, first);
6356
+ return "reason" in found ? null : found;
6357
+ }
6358
+ function regionChips(source, region) {
6359
+ const chips = [];
6360
+ for (const part of region.parts)
6361
+ if (part.kind === "chip")
6362
+ chips.push({
6363
+ index: chips.length,
6364
+ kind: part.chip,
6365
+ label: part.label,
6366
+ source: source.slice(part.start, part.end)
6367
+ });
6368
+ return chips;
6369
+ }
6370
+ function regionItems(source, region) {
6371
+ const items = [];
6372
+ let chip = 0;
6373
+ for (const part of region.parts) {
6374
+ if (part.kind === "text")
6375
+ items.push({
6376
+ kind: "text",
6377
+ text: decodeText2(source.slice(part.start, part.end)).text
6378
+ });
6379
+ else if (part.kind === "break") items.push({ kind: "break" });
6380
+ else items.push({ kind: "chip", index: chip++ });
6159
6381
  }
6160
- var r = new i32(b[30]);
6161
- for (var i = 1; i < 30; ++i) {
6162
- for (var j = b[i]; j < b[i + 1]; ++j) {
6163
- r[j] = j - b[i] << 5 | i;
6382
+ return items;
6383
+ }
6384
+ function applyRegionEdit(source, engine, region, items) {
6385
+ const chipParts = region.parts.filter(
6386
+ (p) => p.kind === "chip"
6387
+ );
6388
+ const kept = [];
6389
+ for (const item of items)
6390
+ if (item.kind === "chip") {
6391
+ if (!Number.isInteger(item.index) || item.index < 0 || item.index >= chipParts.length)
6392
+ return { error: "chips" };
6393
+ if (kept.length && item.index <= (kept[kept.length - 1] ?? -1))
6394
+ return { error: "chips" };
6395
+ kept.push(item.index);
6396
+ }
6397
+ const breakSpelling = source.slice(...spanOf(region.parts.find((p) => p.kind === "break"))) || "<br>";
6398
+ const newSegments = [""];
6399
+ for (const item of items) {
6400
+ if (item.kind === "chip") newSegments.push("");
6401
+ else
6402
+ newSegments[newSegments.length - 1] += item.kind === "break" ? BREAK : item.text.replace(/[\uE000-\uE003]/g, "");
6403
+ }
6404
+ const pieces = [];
6405
+ let segStart = region.start;
6406
+ let partIndex = 0;
6407
+ for (let k = 0; k <= kept.length; k++) {
6408
+ const boundary = k < kept.length ? chipParts[kept[k] ?? 0] : void 0;
6409
+ const segEnd = boundary ? boundary.start : region.end;
6410
+ const segParts = [];
6411
+ while (partIndex < region.parts.length && (region.parts[partIndex]?.start ?? Infinity) < segEnd)
6412
+ segParts.push(region.parts[partIndex++]);
6413
+ if (boundary) partIndex++;
6414
+ pieces.push(
6415
+ rewriteSegment(
6416
+ source,
6417
+ segStart,
6418
+ segEnd,
6419
+ segParts,
6420
+ newSegments[k] ?? "",
6421
+ breakSpelling
6422
+ )
6423
+ );
6424
+ if (boundary) {
6425
+ pieces.push(source.slice(boundary.start, boundary.end));
6426
+ segStart = boundary.end;
6164
6427
  }
6165
6428
  }
6166
- return { b, r };
6167
- };
6168
- var _a = freb(fleb, 2);
6169
- var fl = _a.b;
6170
- var revfl = _a.r;
6171
- fl[28] = 258, revfl[258] = 28;
6172
- var _b = freb(fdeb, 0);
6173
- var fd = _b.b;
6174
- var revfd = _b.r;
6175
- var rev = new u16(32768);
6176
- for (i = 0; i < 32768; ++i) {
6429
+ const expected = flatExpected(
6430
+ items,
6431
+ chipParts.map((c) => source.slice(c.start, c.end))
6432
+ );
6433
+ const attempt = source.slice(0, region.start) + pieces.join("") + source.slice(region.end);
6434
+ if (readsBack(attempt, engine, region.tagStart, expected))
6435
+ return { source: attempt };
6436
+ const whole = items.map(
6437
+ (item) => item.kind === "chip" ? source.slice(chipParts[item.index]?.start, chipParts[item.index]?.end) : item.kind === "break" ? breakSpelling : encodeText(item.text, true, true)
6438
+ ).join("");
6439
+ const rewritten = source.slice(0, region.start) + whole + source.slice(region.end);
6440
+ return readsBack(rewritten, engine, region.tagStart, expected) ? { source: rewritten } : { error: "invalid" };
6441
+ }
6442
+ function translationItems(text2) {
6443
+ const items = [];
6444
+ const names = [];
6445
+ let last = 0;
6446
+ for (const match of text2.matchAll(/\{(\w+)\}/g)) {
6447
+ if (match.index > last)
6448
+ items.push({ kind: "text", text: text2.slice(last, match.index) });
6449
+ items.push({ kind: "chip", index: names.length });
6450
+ names.push(match[1] ?? "");
6451
+ last = match.index + match[0].length;
6452
+ }
6453
+ if (last < text2.length) items.push({ kind: "text", text: text2.slice(last) });
6454
+ return { items, names };
6455
+ }
6456
+ function translationText(items, names) {
6457
+ return items.map(
6458
+ (item) => item.kind === "chip" ? `{${names[item.index] ?? ""}}` : item.kind === "break" ? " " : item.text.replace(new RegExp(NBSP, "g"), " ")
6459
+ ).join("");
6460
+ }
6461
+ function chipValues(rendered, items) {
6462
+ const slots = [""];
6463
+ for (const item of items) {
6464
+ if (item.kind === "chip") slots.push("");
6465
+ else if (item.kind === "text") slots[slots.length - 1] += item.text;
6466
+ }
6467
+ const chips = slots.length - 1;
6468
+ if (!rendered.startsWith(slots[0] ?? "")) return null;
6469
+ let found = null;
6470
+ let solutions = 0;
6471
+ let steps = 0;
6472
+ const values = [];
6473
+ const place = (k, pos) => {
6474
+ if (solutions > 1 || ++steps > 1e4) return;
6475
+ if (k === chips) {
6476
+ if (pos === rendered.length) {
6477
+ solutions++;
6478
+ found = [...values];
6479
+ }
6480
+ return;
6481
+ }
6482
+ const next = slots[k + 1] ?? "";
6483
+ const last = k + 1 === chips;
6484
+ for (let q = pos; q <= rendered.length; q++) {
6485
+ q = rendered.indexOf(next, q);
6486
+ if (q < 0) return;
6487
+ if (!last || q + next.length === rendered.length) {
6488
+ values[k] = rendered.slice(pos, q);
6489
+ place(k + 1, q + next.length);
6490
+ if (solutions > 1) return;
6491
+ }
6492
+ }
6493
+ };
6494
+ place(0, (slots[0] ?? "").length);
6495
+ return solutions === 1 && steps <= 1e4 ? found : null;
6496
+ }
6497
+ function scanRegion(source, engine, at) {
6498
+ if (source[at] !== "<") return { reason: "not-an-element" };
6499
+ tagName.lastIndex = at + 1;
6500
+ const name = tagName.exec(source)?.[0];
6501
+ if (!name) return { reason: "not-an-element" };
6502
+ const nameEnd = at + 1 + name.length;
6503
+ if (!plainNameEnd(source[nameEnd])) return { reason: "dynamic-tag" };
6504
+ const tag = name.toLowerCase();
6505
+ const scanner = new Scanner(source, engine);
6506
+ const start = scanner.tagEnd(nameEnd);
6507
+ if (voidElements.has(tag) || source[start - 2] === "/")
6508
+ return { reason: "void" };
6509
+ if (rawTextElements.has(tag)) return { reason: "raw-text" };
6510
+ const parts = [];
6511
+ let textStart = start;
6512
+ const flush = (to) => {
6513
+ if (to > textStart) parts.push({ kind: "text", start: textStart, end: to });
6514
+ };
6515
+ let i = start;
6516
+ while (i < source.length) {
6517
+ if (i - start > MAX_REGION) return { reason: "too-large" };
6518
+ const ch = source[i];
6519
+ if (ch === "{") {
6520
+ const construct = scanner.classify(i);
6521
+ if (!construct) {
6522
+ i++;
6523
+ continue;
6524
+ }
6525
+ if (construct.type === "statement") return { reason: "control-flow" };
6526
+ flush(i);
6527
+ parts.push(
6528
+ construct.type === "output" ? {
6529
+ kind: "chip",
6530
+ chip: "expression",
6531
+ label: expressionLabel(construct.inner),
6532
+ inner: construct.inner,
6533
+ start: i,
6534
+ end: construct.end
6535
+ } : {
6536
+ kind: "chip",
6537
+ chip: "comment",
6538
+ label: "comment",
6539
+ start: i,
6540
+ end: construct.end
6541
+ }
6542
+ );
6543
+ i = textStart = construct.end;
6544
+ continue;
6545
+ }
6546
+ if (ch === "&") {
6547
+ entityPattern.lastIndex = i;
6548
+ const match = entityPattern.exec(source);
6549
+ if (match && entityChar(match[1] ?? "") === null && !(match[1] ?? "").startsWith("#")) {
6550
+ flush(i);
6551
+ parts.push({
6552
+ kind: "chip",
6553
+ chip: "entity",
6554
+ label: match[0],
6555
+ start: i,
6556
+ end: i + match[0].length
6557
+ });
6558
+ i = textStart = i + match[0].length;
6559
+ continue;
6560
+ }
6561
+ i++;
6562
+ continue;
6563
+ }
6564
+ if (ch !== "<") {
6565
+ i++;
6566
+ continue;
6567
+ }
6568
+ if (source.startsWith("<!--", i)) {
6569
+ const end = scanner.skipTo(i + 4, "-->");
6570
+ flush(i);
6571
+ parts.push({
6572
+ kind: "chip",
6573
+ chip: "comment",
6574
+ label: "comment",
6575
+ start: i,
6576
+ end
6577
+ });
6578
+ i = textStart = end;
6579
+ continue;
6580
+ }
6581
+ const next = source[i + 1];
6582
+ if (next === "/") {
6583
+ tagName.lastIndex = i + 2;
6584
+ const closing = tagName.exec(source)?.[0];
6585
+ if (closing?.toLowerCase() !== tag) return { reason: "unclosed" };
6586
+ flush(i);
6587
+ return {
6588
+ tag,
6589
+ tagStart: at,
6590
+ start,
6591
+ end: i,
6592
+ outerEnd: scanner.tagEnd(i + 2 + closing.length),
6593
+ parts
6594
+ };
6595
+ }
6596
+ if (next === "!" || next === "?") return { reason: "block-child" };
6597
+ tagName.lastIndex = i + 1;
6598
+ const child2 = tagName.exec(source)?.[0];
6599
+ if (!child2) {
6600
+ i++;
6601
+ continue;
6602
+ }
6603
+ const childNameEnd = i + 1 + child2.length;
6604
+ if (!plainNameEnd(source[childNameEnd])) return { reason: "dynamic-tag" };
6605
+ const childTag = child2.toLowerCase();
6606
+ const childStartEnd = scanner.tagEnd(childNameEnd);
6607
+ if (childTag === "br") {
6608
+ flush(i);
6609
+ parts.push({ kind: "break", start: i, end: childStartEnd });
6610
+ i = textStart = childStartEnd;
6611
+ continue;
6612
+ }
6613
+ if (rawTextElements.has(childTag)) return { reason: "raw-text" };
6614
+ if (!phrasingElements.has(childTag)) return { reason: "block-child" };
6615
+ let childEnd = childStartEnd;
6616
+ if (!voidElements.has(childTag) && source[childStartEnd - 2] !== "/") {
6617
+ childEnd = matchingEnd(source, scanner, childStartEnd, childTag);
6618
+ if (childEnd === null) return { reason: "unclosed" };
6619
+ }
6620
+ flush(i);
6621
+ parts.push({
6622
+ kind: "chip",
6623
+ chip: "element",
6624
+ label: `<${childTag}>`,
6625
+ start: i,
6626
+ end: childEnd
6627
+ });
6628
+ i = textStart = childEnd;
6629
+ }
6630
+ return { reason: "unclosed" };
6631
+ }
6632
+ function plainNameEnd(after) {
6633
+ return after === void 0 || after === ">" || after === "/" || /\s/.test(after);
6634
+ }
6635
+ function matchingEnd(source, scanner, from, name) {
6636
+ let depth = 1;
6637
+ let j = from;
6638
+ while (j < source.length) {
6639
+ const ch = source[j];
6640
+ if (ch === "{") {
6641
+ j = scanner.skipTemplate(j) ?? j + 1;
6642
+ continue;
6643
+ }
6644
+ if (ch !== "<") {
6645
+ j++;
6646
+ continue;
6647
+ }
6648
+ if (source.startsWith("<!--", j)) {
6649
+ j = scanner.skipTo(j + 4, "-->");
6650
+ continue;
6651
+ }
6652
+ if (source[j + 1] === "/") {
6653
+ tagName.lastIndex = j + 2;
6654
+ const closing = tagName.exec(source)?.[0];
6655
+ const end2 = scanner.tagEnd(j + 2);
6656
+ if (closing?.toLowerCase() === name && --depth === 0) return end2;
6657
+ j = end2;
6658
+ continue;
6659
+ }
6660
+ tagName.lastIndex = j + 1;
6661
+ const opening = tagName.exec(source)?.[0];
6662
+ if (!opening) {
6663
+ j++;
6664
+ continue;
6665
+ }
6666
+ const end = scanner.tagEnd(j + 1 + opening.length);
6667
+ const lower = opening.toLowerCase();
6668
+ if (lower === name && source[end - 2] !== "/") depth++;
6669
+ j = rawTextElements.has(lower) ? scanner.skipToClosingTag(end, lower) : end;
6670
+ }
6671
+ return null;
6672
+ }
6673
+ function expressionLabel(inner) {
6674
+ const label2 = inner.replace(/^[-~+&]?\s*/, "").replace(/\s*[-~+]?$/, "").replace(/\s+/g, " ");
6675
+ return label2.length > 60 ? `${label2.slice(0, 59)}\u2026` : label2;
6676
+ }
6677
+ function translationCall(engine, inner) {
6678
+ const expression = inner.replace(/^[-~+]?\s*/, "").replace(/\s*[-~+]?$/, "");
6679
+ let match;
6680
+ if (engine === "jinja2") {
6681
+ match = /^(?:t|translate)\(\s*(['"])((?:(?!\1).)*)\1\s*(?:,\s*\{([\s\S]*)\}\s*)?\)$/.exec(
6682
+ expression
6683
+ );
6684
+ return match ? { key: match[2] ?? "", params: parseParams(match[3] ?? "", ":", ",") } : null;
6685
+ }
6686
+ if (engine === "liquid") {
6687
+ match = /^(['"])((?:(?!\1).)*)\1\s*\|\s*(?:t|translate)\b\s*(?::\s*([\s\S]*))?$/.exec(
6688
+ expression
6689
+ );
6690
+ return match ? { key: match[2] ?? "", params: parseParams(match[3] ?? "", ":", ",") } : null;
6691
+ }
6692
+ match = /^(?:t|translate)\s+(['"])((?:(?!\1).)*)\1([\s\S]*)$/.exec(
6693
+ expression
6694
+ );
6695
+ return match ? { key: match[2] ?? "", params: parseParams(match[3] ?? "", "=", " ") } : null;
6696
+ }
6697
+ function parseParams(text2, assign, separator) {
6698
+ const params = {};
6699
+ for (const piece of splitTopLevel(text2, separator)) {
6700
+ const at = piece.indexOf(assign);
6701
+ if (at <= 0) continue;
6702
+ const name = piece.slice(0, at).trim().replace(/^['"]|['"]$/g, "");
6703
+ if (/^[\w-]+$/.test(name)) params[name] = piece.slice(at + 1).trim();
6704
+ }
6705
+ return params;
6706
+ }
6707
+ function splitTopLevel(text2, separator) {
6708
+ const pieces = [];
6709
+ let depth = 0;
6710
+ let quote2 = null;
6711
+ let current = "";
6712
+ for (const ch of text2) {
6713
+ if (quote2) {
6714
+ if (ch === quote2) quote2 = null;
6715
+ } else if (ch === '"' || ch === "'") quote2 = ch;
6716
+ else if ("([{".includes(ch)) depth++;
6717
+ else if (")]}".includes(ch)) depth--;
6718
+ else if (depth === 0 && (separator === " " ? /\s/.test(ch) : ch === separator)) {
6719
+ if (current.trim()) pieces.push(current);
6720
+ current = "";
6721
+ continue;
6722
+ }
6723
+ current += ch;
6724
+ }
6725
+ if (current.trim()) pieces.push(current);
6726
+ return pieces;
6727
+ }
6728
+ function entityChar(reference) {
6729
+ if (reference.startsWith("#")) {
6730
+ const code2 = reference[1] === "x" || reference[1] === "X" ? parseInt(reference.slice(2), 16) : parseInt(reference.slice(1), 10);
6731
+ return Number.isFinite(code2) && code2 > 0 && code2 <= 1114111 && !(code2 >= 55296 && code2 <= 57343) ? String.fromCodePoint(code2) : null;
6732
+ }
6733
+ const code = namedEntities[reference];
6734
+ return code === void 0 ? null : String.fromCharCode(code);
6735
+ }
6736
+ function decodeText2(raw) {
6737
+ let text2 = "";
6738
+ const map = [];
6739
+ let i = 0;
6740
+ while (i < raw.length) {
6741
+ if (raw[i] === "&") {
6742
+ entityPattern.lastIndex = i;
6743
+ const match = entityPattern.exec(raw);
6744
+ const decoded = match ? entityChar(match[1] ?? "") : null;
6745
+ if (match && decoded !== null) {
6746
+ for (let u = 0; u < decoded.length; u++) map.push(i);
6747
+ text2 += decoded;
6748
+ i += match[0].length;
6749
+ continue;
6750
+ }
6751
+ }
6752
+ text2 += raw[i];
6753
+ map.push(i);
6754
+ i++;
6755
+ }
6756
+ map.push(raw.length);
6757
+ return { text: text2, map };
6758
+ }
6759
+ function encodeText(text2, keepNbsp, allBraces = false) {
6760
+ let out = "";
6761
+ for (let k = 0; k < text2.length; k++) {
6762
+ const ch = text2[k] ?? "";
6763
+ const next = text2[k + 1];
6764
+ if (ch === "&") out += "&amp;";
6765
+ else if (ch === "<") out += "&lt;";
6766
+ else if (ch === ">") out += "&gt;";
6767
+ else if (ch === NBSP) out += keepNbsp ? "&nbsp;" : " ";
6768
+ else if (ch === "{" && (allBraces || next === void 0 || next === "{" || next === "%" || next === "#"))
6769
+ out += "&#123;";
6770
+ else if (ch === "#" && next === "}") out += "&#35;";
6771
+ else out += ch;
6772
+ }
6773
+ return out;
6774
+ }
6775
+ function encodeTemplateText(text2) {
6776
+ return encodeText(text2, true);
6777
+ }
6778
+ function spanOf(part) {
6779
+ return part ? [part.start, part.end] : [0, 0];
6780
+ }
6781
+ function rewriteSegment(source, segStart, segEnd, parts, next, breakSpelling) {
6782
+ let old = "";
6783
+ const map = [];
6784
+ for (const part of parts) {
6785
+ if (part.kind === "text") {
6786
+ const decoded = decodeText2(source.slice(part.start, part.end));
6787
+ old += decoded.text;
6788
+ for (let k = 0; k < decoded.text.length; k++)
6789
+ map.push(part.start + (decoded.map[k] ?? 0));
6790
+ } else {
6791
+ old += part.kind === "break" ? BREAK : REMOVED;
6792
+ map.push(part.start);
6793
+ }
6794
+ }
6795
+ map.push(segEnd);
6796
+ if (old === next) return source.slice(segStart, segEnd);
6797
+ let prefix = 0;
6798
+ const limit = Math.min(old.length, next.length);
6799
+ while (prefix < limit && old[prefix] === next[prefix]) prefix++;
6800
+ let suffix = 0;
6801
+ while (suffix < limit - prefix && old[old.length - 1 - suffix] === next[next.length - 1 - suffix])
6802
+ suffix++;
6803
+ if (prefix > 0 && isHighSurrogate(old.charCodeAt(prefix - 1))) prefix--;
6804
+ if (suffix > 0 && isLowSurrogate(old.charCodeAt(old.length - suffix)))
6805
+ suffix--;
6806
+ const from = map[prefix] ?? segEnd;
6807
+ const to = map[old.length - suffix] ?? segEnd;
6808
+ const middle = next.slice(prefix, next.length - suffix);
6809
+ const keepNbsp = old.slice(prefix, old.length - suffix).includes(NBSP);
6810
+ const written = middle.split(BREAK).map((text2) => encodeText(text2, keepNbsp)).join(breakSpelling);
6811
+ return source.slice(segStart, from) + written + source.slice(to, segEnd);
6812
+ }
6813
+ var isHighSurrogate = (code) => code >= 55296 && code <= 56319;
6814
+ var isLowSurrogate = (code) => code >= 56320 && code <= 57343;
6815
+ function flatExpected(items, chipSources) {
6816
+ return items.map(
6817
+ (item) => item.kind === "chip" ? CHIP_OPEN + (chipSources[item.index] ?? "") + CHIP_CLOSE : item.kind === "break" ? BREAK : item.text.replace(/[\uE000-\uE003]/g, "")
6818
+ ).join("").replace(new RegExp(NBSP, "g"), " ");
6819
+ }
6820
+ function readsBack(source, engine, tagStart, expected) {
6821
+ const scanned = scanRegion(source, engine, tagStart);
6822
+ if ("reason" in scanned) return false;
6823
+ if (engine === "jinja2" && scanned.parts.some(
6824
+ (p) => p.kind === "text" && source.slice(p.start, p.end).includes("#}")
6825
+ ))
6826
+ return false;
6827
+ const flat = scanned.parts.map(
6828
+ (part) => part.kind === "text" ? decodeText2(source.slice(part.start, part.end)).text : part.kind === "break" ? BREAK : CHIP_OPEN + source.slice(part.start, part.end) + CHIP_CLOSE
6829
+ ).join("").replace(new RegExp(NBSP, "g"), " ");
6830
+ return flat === expected;
6831
+ }
6832
+
6833
+ // packages/engine/src/lib/source-positions.ts
6834
+ var sourceAttribute = "data-ff-src";
6835
+ var editAttribute = "data-ff-edit";
6836
+ function parseSourceAttribute(value) {
6837
+ const match = /^(body|header|footer):(\d+):(\d+)$/.exec(value ?? "");
6838
+ if (!match) return null;
6839
+ const line = Number(match[2]);
6840
+ const column = Number(match[3]);
6841
+ if (line < 1 || column < 1) return null;
6842
+ return { file: match[1], line, column };
6843
+ }
6844
+ function annotateSourcePositions(source, engine, file, options = {}) {
6845
+ const scanner = new Scanner(source, engine);
6846
+ const lines = lineStarts(source);
6847
+ const out = [];
6848
+ let copied = 0;
6849
+ const n = source.length;
6850
+ let i = 0;
6851
+ while (i < n) {
6852
+ const ch = source[i];
6853
+ if (ch === "{") {
6854
+ i = scanner.skipTemplate(i) ?? i + 1;
6855
+ continue;
6856
+ }
6857
+ if (ch !== "<") {
6858
+ i++;
6859
+ continue;
6860
+ }
6861
+ if (source.startsWith("<!--", i)) {
6862
+ i = scanner.skipTo(i + 4, "-->");
6863
+ continue;
6864
+ }
6865
+ if (source.startsWith("<![CDATA[", i)) {
6866
+ i = scanner.skipTo(i + 9, "]]>");
6867
+ continue;
6868
+ }
6869
+ const next = source[i + 1];
6870
+ if (next === "!" || next === "?") {
6871
+ i = scanner.tagEnd(i + 2);
6872
+ continue;
6873
+ }
6874
+ if (next === "/") {
6875
+ i += 2;
6876
+ continue;
6877
+ }
6878
+ tagName.lastIndex = i + 1;
6879
+ const name = tagName.exec(source)?.[0];
6880
+ if (!name) {
6881
+ i++;
6882
+ continue;
6883
+ }
6884
+ const nameEnd = i + 1 + name.length;
6885
+ const end = scanner.tagEnd(nameEnd);
6886
+ const after = source[nameEnd];
6887
+ const plainName = after === void 0 || after === ">" || after === "/" || /\s/.test(after);
6888
+ if (plainName && !source.slice(nameEnd, end).toLowerCase().includes(sourceAttribute)) {
6889
+ const { line, column } = locate(lines, i);
6890
+ const region = options.editable ? editableRegionAt(source, engine, i) : null;
6891
+ const edit = region && !("reason" in region) ? ` ${editAttribute}="${region.kind}"` : "";
6892
+ out.push(
6893
+ source.slice(copied, nameEnd),
6894
+ ` ${sourceAttribute}="${file}:${line}:${column}"${edit}`
6895
+ );
6896
+ copied = nameEnd;
6897
+ }
6898
+ i = end;
6899
+ if (rawTextElements.has(name.toLowerCase()) && source[end - 1] === ">")
6900
+ i = scanner.skipToClosingTag(end, name);
6901
+ }
6902
+ out.push(source.slice(copied));
6903
+ return out.join("");
6904
+ }
6905
+
6906
+ // packages/engine/src/lib/design/types.ts
6907
+ var DESIGN_VERSION = 1;
6908
+
6909
+ // packages/engine/src/lib/design/parse.ts
6910
+ var DESIGN_LIMITS = {
6911
+ layers: 200,
6912
+ textPerLayer: 5e3,
6913
+ bytes: 256 * 1024
6914
+ };
6915
+ var layerIdPattern = /^[a-z][a-z0-9-]{0,31}$/;
6916
+ var dataPathPattern = /^[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*$/;
6917
+ var colourPattern = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
6918
+ var tokenPattern = /^[a-z][a-z0-9-]{0,31}$/;
6919
+ var symbologies = [
6920
+ "code128",
6921
+ "ean13",
6922
+ "ean8",
6923
+ "upca",
6924
+ "itf14",
6925
+ "code39"
6926
+ ];
6927
+ var Reader = class {
6928
+ constructor(at) {
6929
+ this.at = at;
6930
+ }
6931
+ problems = [];
6932
+ problem(message) {
6933
+ this.problems.push(`${this.at}: ${message}`);
6934
+ }
6935
+ number(value, name, min, max2, fallback) {
6936
+ if (typeof value !== "number" || !Number.isFinite(value)) {
6937
+ if (fallback !== void 0 && value === void 0) return fallback;
6938
+ this.problem(`${name} must be a number`);
6939
+ return fallback ?? min;
6940
+ }
6941
+ return Math.min(max2, Math.max(min, value));
6942
+ }
6943
+ string(value, name, max2, pattern) {
6944
+ if (typeof value !== "string" || value.length > max2 || pattern && !pattern.test(value)) {
6945
+ this.problem(`${name} is not valid`);
6946
+ return "";
6947
+ }
6948
+ return value;
6949
+ }
6950
+ oneOf(value, name, options, fallback) {
6951
+ if (value === void 0 && fallback !== void 0) return fallback;
6952
+ if (typeof value === "string" && options.includes(value))
6953
+ return value;
6954
+ this.problem(`${name} must be one of ${options.join(", ")}`);
6955
+ return options[0];
6956
+ }
6957
+ bool(value, fallback) {
6958
+ return typeof value === "boolean" ? value : fallback;
6959
+ }
6960
+ paint(value, name) {
6961
+ const v = value;
6962
+ if (v && typeof v["color"] === "string" && colourPattern.test(v["color"]))
6963
+ return { color: v["color"] };
6964
+ if (v && typeof v["brand"] === "string" && tokenPattern.test(v["brand"]) && typeof v["fallback"] === "string" && colourPattern.test(v["fallback"]))
6965
+ return { brand: v["brand"], fallback: v["fallback"] };
6966
+ this.problem(`${name} is not a colour`);
6967
+ return { color: "#000000" };
6968
+ }
6969
+ paintOrNull(value, name) {
6970
+ return value === null || value === void 0 ? null : this.paint(value, name);
6971
+ }
6972
+ path(value, name) {
6973
+ return this.string(value, name, 200, dataPathPattern);
6974
+ }
6975
+ };
6976
+ function designBytes(value) {
6977
+ return new TextEncoder().encode(JSON.stringify(value) ?? "").length;
6978
+ }
6979
+ function parseDesign(value) {
6980
+ const root = new Reader("design");
6981
+ const v = value;
6982
+ if (!v || typeof v !== "object" || Array.isArray(v))
6983
+ return { design: null, problems: ["design: not an object"] };
6984
+ if (designBytes(v) > DESIGN_LIMITS.bytes)
6985
+ return {
6986
+ design: null,
6987
+ problems: [`design: larger than ${DESIGN_LIMITS.bytes / 1024} kB`]
6988
+ };
6989
+ if (v["version"] !== DESIGN_VERSION)
6990
+ root.problem(`version must be ${DESIGN_VERSION}`);
6991
+ const output = typeof v["output"] === "string" ? v["output"].slice(0, 64) : "";
6992
+ const background = root.paintOrNull(v["background"], "background");
6993
+ const rawLayers = Array.isArray(v["layers"]) ? v["layers"] : null;
6994
+ if (!rawLayers) root.problem("layers must be a list");
6995
+ if (rawLayers && rawLayers.length > DESIGN_LIMITS.layers)
6996
+ root.problem(`at most ${DESIGN_LIMITS.layers} layers`);
6997
+ const problems = [...root.problems];
6998
+ const layers = [];
6999
+ const ids = /* @__PURE__ */ new Set();
7000
+ for (const [index, raw] of (rawLayers ?? []).slice(0, DESIGN_LIMITS.layers).entries()) {
7001
+ const id = raw?.["id"];
7002
+ const reader = new Reader(
7003
+ `layer ${typeof id === "string" ? id : index + 1}`
7004
+ );
7005
+ const layer = readLayer(reader, raw);
7006
+ if (layer && ids.has(layer.id)) reader.problem("id is used twice");
7007
+ if (layer) ids.add(layer.id);
7008
+ problems.push(...reader.problems);
7009
+ if (layer) layers.push(layer);
7010
+ }
7011
+ let guides;
7012
+ const g = v["guides"];
7013
+ if (g && typeof g === "object") {
7014
+ const list2 = (x) => (Array.isArray(x) ? x : []).filter((n) => typeof n === "number" && Number.isFinite(n)).slice(0, 100);
7015
+ guides = { x: list2(g["x"]), y: list2(g["y"]) };
7016
+ }
7017
+ if (problems.length) return { design: null, problems };
7018
+ return {
7019
+ design: {
7020
+ version: DESIGN_VERSION,
7021
+ output,
7022
+ background,
7023
+ layers,
7024
+ ...guides ? { guides } : {}
7025
+ },
7026
+ problems: []
7027
+ };
7028
+ }
7029
+ function readLayer(r, raw) {
7030
+ const v = raw;
7031
+ if (!v || typeof v !== "object") {
7032
+ r.problem("not an object");
7033
+ return null;
7034
+ }
7035
+ const base = {
7036
+ id: r.string(v["id"], "id", 32, layerIdPattern),
7037
+ name: typeof v["name"] === "string" && v["name"].trim() ? v["name"].slice(0, 100) : String(v["id"] ?? ""),
7038
+ x: r.number(v["x"], "x", -1e4, 1e4),
7039
+ y: r.number(v["y"], "y", -1e4, 1e4),
7040
+ width: r.number(v["width"], "width", 1, 1e4),
7041
+ height: r.number(v["height"], "height", 1, 1e4),
7042
+ rotation: r.number(v["rotation"], "rotation", -360, 360, 0),
7043
+ opacity: r.number(v["opacity"], "opacity", 0, 1, 1),
7044
+ visible: r.bool(v["visible"], true),
7045
+ ...v["locked"] === true ? { locked: true } : {}
7046
+ };
7047
+ const showWhen = v["showWhen"];
7048
+ if (showWhen) base.showWhen = { path: r.path(showWhen["path"], "showWhen") };
7049
+ if (v["radius"] !== void 0)
7050
+ base.radius = r.number(v["radius"], "radius", 0, 5e3);
7051
+ if (v["shadow"]) base.shadow = readShadow(r, v["shadow"]);
7052
+ if (v["stroke"]) base.stroke = readStroke(r, v["stroke"]);
7053
+ switch (v["type"]) {
7054
+ case "text": {
7055
+ const style2 = readStyle(r, v["style"]);
7056
+ const layer = {
7057
+ ...base,
7058
+ type: "text",
7059
+ content: readContent(r, v["content"]),
7060
+ style: style2,
7061
+ sizing: r.oneOf(
7062
+ v["sizing"],
7063
+ "sizing",
7064
+ ["fixed", "shrink", "grow"],
7065
+ "fixed"
7066
+ ),
7067
+ valign: r.oneOf(
7068
+ v["valign"],
7069
+ "valign",
7070
+ ["top", "middle", "bottom"],
7071
+ "top"
7072
+ )
7073
+ };
7074
+ if (v["minFontSize"] !== void 0)
7075
+ layer.minFontSize = r.number(
7076
+ v["minFontSize"],
7077
+ "minFontSize",
7078
+ 1,
7079
+ style2.size
7080
+ );
7081
+ if (v["maxLines"] !== void 0)
7082
+ layer.maxLines = Math.round(
7083
+ r.number(v["maxLines"], "maxLines", 1, 100)
7084
+ );
7085
+ if (v["fill"] !== void 0)
7086
+ layer.fill = r.paintOrNull(v["fill"], "fill");
7087
+ if (v["padding"] !== void 0)
7088
+ layer.padding = r.number(v["padding"], "padding", 0, 1e3);
7089
+ return layer;
7090
+ }
7091
+ case "image": {
7092
+ const layer = {
7093
+ ...base,
7094
+ type: "image",
7095
+ source: readImageSource(r, v["source"]),
7096
+ fit: r.oneOf(
7097
+ v["fit"],
7098
+ "fit",
7099
+ ["cover", "contain", "fill"],
7100
+ "cover"
7101
+ )
7102
+ };
7103
+ const focus = v["focus"];
7104
+ if (focus)
7105
+ layer.focus = {
7106
+ x: r.number(focus["x"], "focus", 0, 100),
7107
+ y: r.number(focus["y"], "focus", 0, 100)
7108
+ };
7109
+ return layer;
7110
+ }
7111
+ case "shape":
7112
+ return {
7113
+ ...base,
7114
+ type: "shape",
7115
+ shape: r.oneOf(v["shape"], "shape", [
7116
+ "rectangle",
7117
+ "ellipse",
7118
+ "line"
7119
+ ]),
7120
+ fill: r.paintOrNull(v["fill"], "fill")
7121
+ };
7122
+ case "qr":
7123
+ return {
7124
+ ...base,
7125
+ type: "qr",
7126
+ value: readBinding(r, v["value"]),
7127
+ ecc: r.oneOf(v["ecc"], "ecc", ["L", "M", "Q", "H"], "M"),
7128
+ color: r.paint(v["color"] ?? { color: "#000000" }, "color"),
7129
+ background: r.paintOrNull(v["background"], "background")
7130
+ };
7131
+ case "barcode":
7132
+ return {
7133
+ ...base,
7134
+ type: "barcode",
7135
+ value: readBinding(r, v["value"]),
7136
+ symbology: r.oneOf(v["symbology"], "symbology", symbologies, "code128"),
7137
+ showText: r.bool(v["showText"], true)
7138
+ };
7139
+ default:
7140
+ r.problem("unknown layer type");
7141
+ return null;
7142
+ }
7143
+ }
7144
+ function readShadow(r, raw) {
7145
+ const v = raw ?? {};
7146
+ const color = r.string(v["color"], "shadow colour", 9, colourPattern) || "#00000040";
7147
+ return {
7148
+ x: r.number(v["x"], "shadow x", -500, 500, 0),
7149
+ y: r.number(v["y"], "shadow y", -500, 500, 0),
7150
+ blur: r.number(v["blur"], "shadow blur", 0, 500, 0),
7151
+ color
7152
+ };
7153
+ }
7154
+ function readStroke(r, raw) {
7155
+ const v = raw ?? {};
7156
+ return {
7157
+ width: r.number(v["width"], "stroke width", 0, 200),
7158
+ paint: r.paint(v["paint"], "stroke colour")
7159
+ };
7160
+ }
7161
+ function readStyle(r, raw) {
7162
+ const v = raw ?? {};
7163
+ const style2 = {
7164
+ font: readFont(r, v["font"]),
7165
+ weight: Math.round(r.number(v["weight"], "weight", 100, 900, 400) / 100) * 100,
7166
+ size: r.number(v["size"], "size", 1, 2e3),
7167
+ lineHeight: r.number(v["lineHeight"], "lineHeight", 0.5, 5, 1.2),
7168
+ letterSpacing: r.number(v["letterSpacing"], "letterSpacing", -50, 200, 0),
7169
+ color: r.paint(v["color"], "colour"),
7170
+ align: r.oneOf(
7171
+ v["align"],
7172
+ "align",
7173
+ ["left", "center", "right", "justify"],
7174
+ "left"
7175
+ )
7176
+ };
7177
+ if (v["italic"] === true) style2.italic = true;
7178
+ if (v["uppercase"] === true) style2.uppercase = true;
7179
+ if (v["underline"] === true) style2.underline = true;
7180
+ if (v["strike"] === true) style2.strike = true;
7181
+ return style2;
7182
+ }
7183
+ function readFont(r, raw) {
7184
+ const v = raw ?? {};
7185
+ if (v["brand"] === "heading" || v["brand"] === "body")
7186
+ return { brand: v["brand"] };
7187
+ const family = r.string(v["family"], "font", 100, /^[^"'\\<>\n\r;{}]+$/);
7188
+ return {
7189
+ family,
7190
+ source: r.oneOf(
7191
+ v["source"],
7192
+ "font source",
7193
+ ["google", "organisation", "system"],
7194
+ "system"
7195
+ )
7196
+ };
7197
+ }
7198
+ function readContent(r, raw) {
7199
+ if (!Array.isArray(raw)) {
7200
+ r.problem("content must be a list");
7201
+ return [];
7202
+ }
7203
+ let length = 0;
7204
+ const items = [];
7205
+ for (const item of raw) {
7206
+ if (item && typeof item["text"] === "string") {
7207
+ length += item["text"].length;
7208
+ items.push({ text: item["text"] });
7209
+ } else if (item && item["break"] === true) items.push({ break: true });
7210
+ else if (item && typeof item["path"] === "string") {
7211
+ const path = r.path(item["path"], "content path");
7212
+ const format = item["format"] === void 0 ? void 0 : readFormat(r, item["format"]);
7213
+ items.push(format ? { path, format } : { path });
7214
+ } else
7215
+ r.problem("content holds something that is not text, a path or a break");
7216
+ }
7217
+ if (length > 5e3) r.problem("at most 5000 characters of text");
7218
+ return items;
7219
+ }
7220
+ function readFormat(r, raw) {
7221
+ const v = raw ?? {};
7222
+ switch (v["helper"]) {
7223
+ case "upper":
7224
+ case "lower":
7225
+ case "title":
7226
+ case "money":
7227
+ return { helper: v["helper"] };
7228
+ case "number":
7229
+ return {
7230
+ helper: "number",
7231
+ decimals: Math.round(r.number(v["decimals"], "decimals", 0, 10, 2))
7232
+ };
7233
+ case "date":
7234
+ return {
7235
+ helper: "date",
7236
+ pattern: r.string(v["pattern"], "date pattern", 64, /^[^{}\n\r]+$/)
7237
+ };
7238
+ case "truncate":
7239
+ return {
7240
+ helper: "truncate",
7241
+ length: Math.round(r.number(v["length"], "length", 1, 5e3))
7242
+ };
7243
+ case "default":
7244
+ return {
7245
+ helper: "default",
7246
+ value: r.string(v["value"], "default value", 500, /^[^{}\n\r]*$/)
7247
+ };
7248
+ default:
7249
+ r.problem("unknown format");
7250
+ return void 0;
7251
+ }
7252
+ }
7253
+ function readImageSource(r, raw) {
7254
+ const v = raw ?? {};
7255
+ if (v["url"] === "") return { url: "" };
7256
+ if (v["asset"] === "") return { asset: "" };
7257
+ if (v["path"] === "") return { path: "" };
7258
+ if (typeof v["url"] === "string")
7259
+ return {
7260
+ url: r.string(v["url"], "image url", 2e3, /^https?:\/\/[^\s"'<>]+$/)
7261
+ };
7262
+ if (typeof v["asset"] === "string")
7263
+ return {
7264
+ asset: r.string(v["asset"], "file name", 200, /^[^"\\<>\n\r{}]+$/)
7265
+ };
7266
+ if (typeof v["path"] === "string")
7267
+ return { path: r.path(v["path"], "image path") };
7268
+ if (v["brandLogo"] !== void 0)
7269
+ return {
7270
+ brandLogo: r.oneOf(v["brandLogo"], "brand logo", [
7271
+ "primary",
7272
+ "inverse",
7273
+ "mark"
7274
+ ])
7275
+ };
7276
+ r.problem("the image has no source");
7277
+ return { url: "" };
7278
+ }
7279
+ function readBinding(r, raw) {
7280
+ const v = raw ?? {};
7281
+ if (typeof v["path"] === "string")
7282
+ return { path: r.path(v["path"], "value path") };
7283
+ if (typeof v["text"] === "string")
7284
+ return { text: r.string(v["text"], "value", 2e3, /^[^\n\r{}]*$/) };
7285
+ r.problem("the code has no value");
7286
+ return { text: "" };
7287
+ }
7288
+
7289
+ // packages/engine/src/lib/design/fonts.ts
7290
+ var range = (from, to) => Array.from({ length: (to - from) / 100 + 1 }, (_, i) => from + i * 100);
7291
+ var googleFonts = [
7292
+ {
7293
+ family: "Inter",
7294
+ category: "sans-serif",
7295
+ weights: range(100, 900),
7296
+ italic: true
7297
+ },
7298
+ {
7299
+ family: "Roboto",
7300
+ category: "sans-serif",
7301
+ weights: range(100, 900),
7302
+ italic: true
7303
+ },
7304
+ {
7305
+ family: "Open Sans",
7306
+ category: "sans-serif",
7307
+ weights: range(300, 800),
7308
+ italic: true
7309
+ },
7310
+ {
7311
+ family: "Lato",
7312
+ category: "sans-serif",
7313
+ weights: [100, 300, 400, 700, 900],
7314
+ italic: true
7315
+ },
7316
+ {
7317
+ family: "Montserrat",
7318
+ category: "sans-serif",
7319
+ weights: range(100, 900),
7320
+ italic: true
7321
+ },
7322
+ {
7323
+ family: "Poppins",
7324
+ category: "sans-serif",
7325
+ weights: range(100, 900),
7326
+ italic: true
7327
+ },
7328
+ {
7329
+ family: "Source Sans 3",
7330
+ category: "sans-serif",
7331
+ weights: range(200, 900),
7332
+ italic: true
7333
+ },
7334
+ {
7335
+ family: "Nunito",
7336
+ category: "sans-serif",
7337
+ weights: range(200, 900),
7338
+ italic: true
7339
+ },
7340
+ {
7341
+ family: "Raleway",
7342
+ category: "sans-serif",
7343
+ weights: range(100, 900),
7344
+ italic: true
7345
+ },
7346
+ {
7347
+ family: "Work Sans",
7348
+ category: "sans-serif",
7349
+ weights: range(100, 900),
7350
+ italic: true
7351
+ },
7352
+ {
7353
+ family: "DM Sans",
7354
+ category: "sans-serif",
7355
+ weights: range(100, 900),
7356
+ italic: true
7357
+ },
7358
+ {
7359
+ family: "Manrope",
7360
+ category: "sans-serif",
7361
+ weights: range(200, 800),
7362
+ italic: false
7363
+ },
7364
+ {
7365
+ family: "Rubik",
7366
+ category: "sans-serif",
7367
+ weights: range(300, 900),
7368
+ italic: true
7369
+ },
7370
+ {
7371
+ family: "Noto Sans",
7372
+ category: "sans-serif",
7373
+ weights: range(100, 900),
7374
+ italic: true
7375
+ },
7376
+ {
7377
+ family: "IBM Plex Sans",
7378
+ category: "sans-serif",
7379
+ weights: range(100, 700),
7380
+ italic: true
7381
+ },
7382
+ {
7383
+ family: "Space Grotesk",
7384
+ category: "sans-serif",
7385
+ weights: range(300, 700),
7386
+ italic: false
7387
+ },
7388
+ {
7389
+ family: "Outfit",
7390
+ category: "sans-serif",
7391
+ weights: range(100, 900),
7392
+ italic: false
7393
+ },
7394
+ {
7395
+ family: "Plus Jakarta Sans",
7396
+ category: "sans-serif",
7397
+ weights: range(200, 800),
7398
+ italic: true
7399
+ },
7400
+ {
7401
+ family: "Figtree",
7402
+ category: "sans-serif",
7403
+ weights: range(300, 900),
7404
+ italic: true
7405
+ },
7406
+ {
7407
+ family: "Karla",
7408
+ category: "sans-serif",
7409
+ weights: range(200, 800),
7410
+ italic: true
7411
+ },
7412
+ {
7413
+ family: "Barlow",
7414
+ category: "sans-serif",
7415
+ weights: range(100, 900),
7416
+ italic: true
7417
+ },
7418
+ {
7419
+ family: "Archivo",
7420
+ category: "sans-serif",
7421
+ weights: range(100, 900),
7422
+ italic: true
7423
+ },
7424
+ {
7425
+ family: "Oswald",
7426
+ category: "display",
7427
+ weights: range(200, 700),
7428
+ italic: false
7429
+ },
7430
+ { family: "Bebas Neue", category: "display", weights: [400], italic: false },
7431
+ { family: "Anton", category: "display", weights: [400], italic: false },
7432
+ {
7433
+ family: "Playfair Display",
7434
+ category: "serif",
7435
+ weights: range(400, 900),
7436
+ italic: true
7437
+ },
7438
+ {
7439
+ family: "Merriweather",
7440
+ category: "serif",
7441
+ weights: range(300, 900),
7442
+ italic: true
7443
+ },
7444
+ { family: "Lora", category: "serif", weights: range(400, 700), italic: true },
7445
+ {
7446
+ family: "Libre Baskerville",
7447
+ category: "serif",
7448
+ weights: [400, 700],
7449
+ italic: true
7450
+ },
7451
+ {
7452
+ family: "Source Serif 4",
7453
+ category: "serif",
7454
+ weights: range(200, 900),
7455
+ italic: true
7456
+ },
7457
+ {
7458
+ family: "EB Garamond",
7459
+ category: "serif",
7460
+ weights: range(400, 800),
7461
+ italic: true
7462
+ },
7463
+ {
7464
+ family: "Cormorant Garamond",
7465
+ category: "serif",
7466
+ weights: range(300, 700),
7467
+ italic: true
7468
+ },
7469
+ {
7470
+ family: "DM Serif Display",
7471
+ category: "serif",
7472
+ weights: [400],
7473
+ italic: true
7474
+ },
7475
+ {
7476
+ family: "Fraunces",
7477
+ category: "serif",
7478
+ weights: range(100, 900),
7479
+ italic: true
7480
+ },
7481
+ {
7482
+ family: "Roboto Slab",
7483
+ category: "serif",
7484
+ weights: range(100, 900),
7485
+ italic: false
7486
+ },
7487
+ {
7488
+ family: "Zilla Slab",
7489
+ category: "serif",
7490
+ weights: [300, 400, 500, 600, 700],
7491
+ italic: true
7492
+ },
7493
+ {
7494
+ family: "JetBrains Mono",
7495
+ category: "monospace",
7496
+ weights: range(100, 800),
7497
+ italic: true
7498
+ },
7499
+ {
7500
+ family: "IBM Plex Mono",
7501
+ category: "monospace",
7502
+ weights: range(100, 700),
7503
+ italic: true
7504
+ },
7505
+ {
7506
+ family: "Space Mono",
7507
+ category: "monospace",
7508
+ weights: [400, 700],
7509
+ italic: true
7510
+ },
7511
+ {
7512
+ family: "Caveat",
7513
+ category: "handwriting",
7514
+ weights: range(400, 700),
7515
+ italic: false
7516
+ },
7517
+ {
7518
+ family: "Pacifico",
7519
+ category: "handwriting",
7520
+ weights: [400],
7521
+ italic: false
7522
+ },
7523
+ {
7524
+ family: "Dancing Script",
7525
+ category: "handwriting",
7526
+ weights: range(400, 700),
7527
+ italic: false
7528
+ }
7529
+ ];
7530
+ var byFamily = new Map(googleFonts.map((font) => [font.family, font]));
7531
+ function googleFont(family) {
7532
+ return byFamily.get(family);
7533
+ }
7534
+ function genericFamily(font) {
7535
+ if (!font) return "sans-serif";
7536
+ return font.category === "serif" ? "serif" : font.category === "monospace" ? "monospace" : font.category === "handwriting" ? "cursive" : "sans-serif";
7537
+ }
7538
+ function nearestWeight(font, weight) {
7539
+ return font.weights.reduce(
7540
+ (best, w) => Math.abs(w - weight) < Math.abs(best - weight) || Math.abs(w - weight) === Math.abs(best - weight) && w > best ? w : best,
7541
+ font.weights[0] ?? 400
7542
+ );
7543
+ }
7544
+ function googleFontsUrl(uses) {
7545
+ const wanted = /* @__PURE__ */ new Map();
7546
+ for (const use of uses) {
7547
+ const font = byFamily.get(use.family);
7548
+ if (!font) continue;
7549
+ const italic = use.italic && font.italic ? 1 : 0;
7550
+ const set = wanted.get(font.family) ?? /* @__PURE__ */ new Set();
7551
+ set.add(`${italic},${nearestWeight(font, use.weight)}`);
7552
+ wanted.set(font.family, set);
7553
+ }
7554
+ if (!wanted.size) return null;
7555
+ const families = [...wanted.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([family, set]) => {
7556
+ const tuples = [...set].map((t) => t.split(",").map(Number)).sort((a, b) => a[0] - b[0] || a[1] - b[1]);
7557
+ const name = family.replace(/ /g, "+");
7558
+ return tuples.some(([italic]) => italic) ? `family=${name}:ital,wght@${tuples.map(([i, w]) => `${i},${w}`).join(";")}` : `family=${name}:wght@${tuples.map(([, w]) => w).join(";")}`;
7559
+ });
7560
+ return `https://fonts.googleapis.com/css2?${families.join("&")}&display=block`;
7561
+ }
7562
+
7563
+ // packages/engine/src/lib/design/hash.ts
7564
+ function designOutputHash(html, css2) {
7565
+ const text2 = `${html}\0${css2}`;
7566
+ let a = 2166136261;
7567
+ let b = 16777619 ^ 2654435769;
7568
+ for (let i = 0; i < text2.length; i++) {
7569
+ const code = text2.charCodeAt(i);
7570
+ if (code === 13) continue;
7571
+ a = Math.imul(a ^ code, 16777619) >>> 0;
7572
+ b = Math.imul(b ^ code, 16777619) >>> 0;
7573
+ b = b + (a >>> 13) >>> 0;
7574
+ }
7575
+ return a.toString(16).padStart(8, "0") + b.toString(16).padStart(8, "0");
7576
+ }
7577
+
7578
+ // packages/engine/src/lib/design/layers.ts
7579
+ function imageIsEmpty(source) {
7580
+ if ("url" in source) return source.url === "";
7581
+ if ("asset" in source) return source.asset === "";
7582
+ if ("path" in source) return source.path === "";
7583
+ return false;
7584
+ }
7585
+ function emptyDesign() {
7586
+ return {
7587
+ version: DESIGN_VERSION,
7588
+ output: "",
7589
+ background: { color: "#ffffff" },
7590
+ layers: []
7591
+ };
7592
+ }
7593
+ function nextLayerId(design2, type) {
7594
+ const taken = new Set(design2.layers.map((layer) => layer.id));
7595
+ for (let n = 1; ; n++) {
7596
+ const id = `${type}-${n}`;
7597
+ if (!taken.has(id)) return id;
7598
+ }
7599
+ }
7600
+ var baseText = {
7601
+ type: "text",
7602
+ rotation: 0,
7603
+ opacity: 1,
7604
+ visible: true,
7605
+ content: [{ text: "Text" }],
7606
+ style: {
7607
+ font: { family: "Inter", source: "google" },
7608
+ // regular: the editor's Bold is a toggle, and a new text that was already bold confused it
7609
+ weight: 400,
7610
+ size: 48,
7611
+ lineHeight: 1.2,
7612
+ letterSpacing: 0,
7613
+ color: { color: "#111827" },
7614
+ align: "left"
7615
+ },
7616
+ sizing: "fixed",
7617
+ valign: "top"
7618
+ };
7619
+ function defaultLayer(type, id, name, canvas) {
7620
+ const centred = (width, height) => ({
7621
+ x: Math.round((canvas.width - width) / 2),
7622
+ y: Math.round((canvas.height - height) / 2),
7623
+ width,
7624
+ height
7625
+ });
7626
+ const common = { id, name, rotation: 0, opacity: 1, visible: true };
7627
+ switch (type) {
7628
+ case "text":
7629
+ return {
7630
+ ...baseText,
7631
+ ...common,
7632
+ ...centred(Math.min(canvas.width - 80, 600), 80)
7633
+ };
7634
+ case "image":
7635
+ return {
7636
+ ...common,
7637
+ type: "image",
7638
+ ...centred(Math.min(canvas.width, 400), Math.min(canvas.height, 300)),
7639
+ // not chosen yet (`imageIsEmpty`), and the file library first: that is where an upload goes
7640
+ source: { asset: "" },
7641
+ fit: "cover"
7642
+ };
7643
+ case "shape":
7644
+ return {
7645
+ ...common,
7646
+ type: "shape",
7647
+ ...centred(240, 160),
7648
+ shape: "rectangle",
7649
+ fill: { color: "#e5e7eb" }
7650
+ };
7651
+ case "qr":
7652
+ return {
7653
+ ...common,
7654
+ type: "qr",
7655
+ ...centred(240, 240),
7656
+ value: { text: "https://example.test" },
7657
+ ecc: "M",
7658
+ color: { color: "#000000" },
7659
+ background: { color: "#ffffff" }
7660
+ };
7661
+ case "barcode":
7662
+ return {
7663
+ ...common,
7664
+ type: "barcode",
7665
+ ...centred(400, 140),
7666
+ value: { text: "4006381333931" },
7667
+ symbology: "ean13",
7668
+ showText: true
7669
+ };
7670
+ }
7671
+ }
7672
+
7673
+ // packages/engine/src/lib/design/emit.ts
7674
+ var colourPattern2 = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
7675
+ var tokenPattern2 = /^[a-z][a-z0-9-]{0,31}$/;
7676
+ function emitDesign(design2, engine, size) {
7677
+ const lines = ['<div class="ff-design">'];
7678
+ const map = {};
7679
+ const rules = [];
7680
+ for (const layer of design2.layers) {
7681
+ if (!layer.visible) continue;
7682
+ const markup = layerHtml(layer, engine);
7683
+ lines.push(
7684
+ ` ${layer.showWhen?.path ? ifBlock(engine, layer.showWhen.path, markup) : markup}`
7685
+ );
7686
+ map[layer.id] = { start: lines.length, end: lines.length };
7687
+ rules.push(...layerCss(layer));
7688
+ }
7689
+ lines.push("</div>");
7690
+ const html = `${lines.join("\n")}
7691
+ `;
7692
+ const fonts = googleFontsUrl(
7693
+ design2.layers.filter(
7694
+ (l) => l.visible && l.type === "text" && "family" in l.style.font && l.style.font.source === "google"
7695
+ ).map((l) => ({
7696
+ family: l.style.font.family,
7697
+ weight: l.style.weight,
7698
+ italic: l.style.italic === true
7699
+ }))
7700
+ );
7701
+ const head = [
7702
+ ...fonts ? [`@import url("${fonts}");`] : [],
7703
+ `.ff-design { ${decl({
7704
+ position: "relative",
7705
+ width: px(size.width),
7706
+ height: px(size.height),
7707
+ overflow: "hidden",
7708
+ background: design2.background ? paint(design2.background) : void 0
7709
+ })} }`,
7710
+ ".ff-layer { position: absolute; box-sizing: border-box; margin: 0; }"
7711
+ ];
7712
+ const css2 = `${[...head, ...rules].join("\n")}
7713
+ `;
7714
+ return { html, css: css2, map, output: designOutputHash(html, css2) };
7715
+ }
7716
+ function open(layer, tag, extra = "") {
7717
+ return `<${tag} class="ff-layer l-${layer.id}" data-ff-layer="${layer.id}"${extra}`;
7718
+ }
7719
+ function layerHtml(layer, engine) {
7720
+ switch (layer.type) {
7721
+ case "text": {
7722
+ const fit = layer.sizing === "shrink" ? ` data-ff-fit="shrink" data-ff-fit-min="${num(layer.minFontSize ?? Math.max(8, Math.round(layer.style.size / 3)))}"` : "";
7723
+ return `${open(layer, "div", fit)}><p class="ff-text">${textHtml(layer.content, engine)}</p></div>`;
7724
+ }
7725
+ case "image":
7726
+ if (imageIsEmpty(layer.source)) return `${open(layer, "div")}></div>`;
7727
+ return `${open(layer, "img", ` src="${imageSource(layer.source, engine)}" alt=""`)}>`;
7728
+ case "shape":
7729
+ return `${open(layer, "div")}></div>`;
7730
+ case "qr":
7731
+ return `${open(
7732
+ layer,
7733
+ "img",
7734
+ ` src="${helperCall(engine, "qrcode", binding(layer.value, engine), {
7735
+ ecc: stringLiteral(engine, layer.ecc),
7736
+ margin: "0",
7737
+ color: stringLiteral(engine, literalColour(layer.color)),
7738
+ background: stringLiteral(
7739
+ engine,
7740
+ layer.background ? literalColour(layer.background) : "#ffffff00"
7741
+ )
7742
+ })}" alt=""`
7743
+ )}>`;
7744
+ case "barcode":
7745
+ return `${open(
7746
+ layer,
7747
+ "img",
7748
+ ` src="${helperCall(engine, "barcode", binding(layer.value, engine), {
7749
+ type: stringLiteral(engine, layer.symbology),
7750
+ text: layer.showText ? "true" : "false"
7751
+ })}" alt=""`
7752
+ )}>`;
7753
+ }
7754
+ }
7755
+ function textHtml(items, engine) {
7756
+ return items.map((item) => {
7757
+ if ("break" in item) return "<br>";
7758
+ if ("text" in item)
7759
+ return encodeTemplateText(item.text.replace(/[\r\n]+/g, " "));
7760
+ return valueHtml(item.path, item.format, engine);
7761
+ }).join("");
7762
+ }
7763
+ function valueHtml(path, format, engine) {
7764
+ if (!format) return `{{ ${path} }}`;
7765
+ switch (format.helper) {
7766
+ case "upper":
7767
+ case "lower":
7768
+ case "title":
7769
+ case "money":
7770
+ return filterCall(engine, path, format.helper);
7771
+ case "number":
7772
+ return filterCall(engine, path, "number", [String(format.decimals)]);
7773
+ case "date":
7774
+ return filterCall(engine, path, "date", [
7775
+ stringLiteral(engine, format.pattern)
7776
+ ]);
7777
+ case "truncate":
7778
+ return filterCall(engine, path, "truncate", [String(format.length)]);
7779
+ case "default":
7780
+ return filterCall(engine, path, "default", [
7781
+ stringLiteral(engine, format.value)
7782
+ ]);
7783
+ }
7784
+ }
7785
+ function binding(value, engine) {
7786
+ return "path" in value ? value.path : stringLiteral(engine, value.text);
7787
+ }
7788
+ function imageSource(source, engine) {
7789
+ if ("url" in source) return escapeAttribute(source.url);
7790
+ if ("asset" in source)
7791
+ return helperCall(engine, "asset", stringLiteral(engine, source.asset));
7792
+ if ("path" in source) return `{{ ${source.path} }}`;
7793
+ return `{{ brand.logo.${source.brandLogo} }}`;
7794
+ }
7795
+ function layerCss(layer) {
7796
+ const text2 = layer.type === "text" ? layer : null;
7797
+ const box = {
7798
+ left: px(layer.x),
7799
+ top: px(layer.y),
7800
+ width: px(layer.width),
7801
+ height: text2?.sizing === "grow" ? void 0 : px(layer.height),
7802
+ opacity: layer.opacity < 1 ? num(layer.opacity) : void 0,
7803
+ transform: layer.rotation ? `rotate(${num(layer.rotation)}deg)` : void 0,
7804
+ "border-radius": layer.type === "shape" && layer.shape === "ellipse" ? "50%" : layer.radius ? px(layer.radius) : void 0,
7805
+ border: layer.stroke && layer.stroke.width > 0 ? `${px(layer.stroke.width)} solid ${paint(layer.stroke.paint)}` : void 0,
7806
+ "box-shadow": layer.shadow && !text2 ? shadow(layer.shadow) : void 0
7807
+ };
7808
+ if (text2) {
7809
+ Object.assign(box, {
7810
+ display: "flex",
7811
+ "flex-direction": "column",
7812
+ "justify-content": text2.valign === "middle" ? "center" : text2.valign === "bottom" ? "flex-end" : "flex-start",
7813
+ overflow: text2.sizing === "grow" ? void 0 : "hidden",
7814
+ padding: text2.padding ? px(text2.padding) : void 0,
7815
+ background: text2.fill ? paint(text2.fill) : void 0
7816
+ });
7817
+ const s = text2.style;
7818
+ const clamp = text2.maxLines ? {
7819
+ display: "-webkit-box",
7820
+ "-webkit-box-orient": "vertical",
7821
+ "-webkit-line-clamp": String(text2.maxLines),
7822
+ overflow: "hidden"
7823
+ } : {};
7824
+ const inner = decl({
7825
+ margin: "0",
7826
+ width: "100%",
7827
+ "font-family": fontFamily(s.font),
7828
+ "font-weight": String(servedWeight(s.font, s.weight)),
7829
+ "font-size": px(s.size),
7830
+ "line-height": num(s.lineHeight),
7831
+ "letter-spacing": s.letterSpacing ? px(s.letterSpacing) : void 0,
7832
+ color: paint(s.color),
7833
+ "text-align": s.align,
7834
+ "font-style": s.italic ? "italic" : void 0,
7835
+ "text-transform": s.uppercase ? "uppercase" : void 0,
7836
+ "text-decoration": [s.underline && "underline", s.strike && "line-through"].filter(Boolean).join(" ") || void 0,
7837
+ "text-shadow": text2.shadow ? shadow(text2.shadow) : void 0,
7838
+ "overflow-wrap": "break-word",
7839
+ ...clamp
7840
+ });
7841
+ return [
7842
+ `.l-${layer.id} { ${decl(box)} }`,
7843
+ `.l-${layer.id} .ff-text { ${inner} }`
7844
+ ];
7845
+ }
7846
+ if (layer.type === "image")
7847
+ Object.assign(box, {
7848
+ display: "block",
7849
+ "object-fit": layer.fit,
7850
+ "object-position": layer.focus ? `${num(layer.focus.x)}% ${num(layer.focus.y)}%` : void 0
7851
+ });
7852
+ if (layer.type === "qr" || layer.type === "barcode")
7853
+ Object.assign(box, { display: "block", "object-fit": "contain" });
7854
+ if (layer.type === "shape")
7855
+ box["background"] = layer.fill ? paint(layer.fill) : void 0;
7856
+ return [`.l-${layer.id} { ${decl(box)} }`];
7857
+ }
7858
+ function decl(properties) {
7859
+ return Object.entries(properties).filter(([, value]) => value !== void 0).map(([name, value]) => `${name}: ${value};`).join(" ");
7860
+ }
7861
+ function num(n) {
7862
+ return String(Math.round(n * 100) / 100);
7863
+ }
7864
+ function px(n) {
7865
+ const value = num(n);
7866
+ return value === "0" ? "0" : `${value}px`;
7867
+ }
7868
+ function safeColour(value) {
7869
+ return colourPattern2.test(value) ? value : "#000000";
7870
+ }
7871
+ function paint(p) {
7872
+ if ("brand" in p)
7873
+ return tokenPattern2.test(p.brand) ? `var(--brand-color-${p.brand}, ${safeColour(p.fallback)})` : safeColour(p.fallback);
7874
+ return safeColour(p.color);
7875
+ }
7876
+ function literalColour(p) {
7877
+ return safeColour("brand" in p ? p.fallback : p.color);
7878
+ }
7879
+ function shadow(s) {
7880
+ return `${px(s.x)} ${px(s.y)} ${px(s.blur)} ${safeColour(s.color)}`;
7881
+ }
7882
+ function servedWeight(font, weight) {
7883
+ const google = "family" in font && font.source === "google" ? googleFont(font.family) : void 0;
7884
+ return google ? nearestWeight(google, weight) : weight;
7885
+ }
7886
+ function fontFamily(font) {
7887
+ if ("brand" in font) return `var(--brand-font-${font.brand}, sans-serif)`;
7888
+ const family = font.family.replace(/["'\\<>\n\r;{}]/g, "");
7889
+ const generic = font.source === "google" ? genericFamily(googleFont(family)) : "sans-serif";
7890
+ return `"${family}", ${generic}`;
7891
+ }
7892
+ function escapeAttribute(value) {
7893
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\{/g, "%7B").replace(/\}/g, "%7D");
7894
+ }
7895
+
7896
+ // packages/engine/src/lib/design/starters.ts
7897
+ var style = (overrides) => ({
7898
+ font: { family: "Inter", source: "google" },
7899
+ weight: 600,
7900
+ size: 40,
7901
+ lineHeight: 1.2,
7902
+ letterSpacing: 0,
7903
+ color: { color: "#111827" },
7904
+ align: "left",
7905
+ ...overrides
7906
+ });
7907
+ var text = (layer) => ({
7908
+ type: "text",
7909
+ rotation: 0,
7910
+ opacity: 1,
7911
+ visible: true,
7912
+ sizing: "fixed",
7913
+ valign: "top",
7914
+ ...layer
7915
+ });
7916
+ var design = (background, layers) => ({
7917
+ version: DESIGN_VERSION,
7918
+ output: "",
7919
+ background,
7920
+ layers
7921
+ });
7922
+ var designStarters = [
7923
+ {
7924
+ id: "blank",
7925
+ title: "Blank",
7926
+ size: { width: 1200, height: 630 },
7927
+ design: design({ color: "#ffffff" }, []),
7928
+ sampleData: {}
7929
+ },
7930
+ {
7931
+ id: "article",
7932
+ title: "Article card",
7933
+ size: { width: 1200, height: 630 },
7934
+ design: design({ brand: "primary", fallback: "#0f172a" }, [
7935
+ text({
7936
+ id: "site",
7937
+ name: "Site",
7938
+ x: 80,
7939
+ y: 64,
7940
+ width: 1040,
7941
+ height: 40,
7942
+ content: [{ path: "site.name" }],
7943
+ style: style({
7944
+ size: 26,
7945
+ weight: 600,
7946
+ letterSpacing: 2,
7947
+ uppercase: true,
7948
+ color: { color: "#cbd5e1" }
7949
+ })
7950
+ }),
7951
+ text({
7952
+ id: "title",
7953
+ name: "Title",
7954
+ x: 80,
7955
+ y: 130,
7956
+ width: 1040,
7957
+ height: 320,
7958
+ content: [{ path: "post.title" }],
7959
+ sizing: "shrink",
7960
+ minFontSize: 40,
7961
+ valign: "middle",
7962
+ style: style({
7963
+ size: 76,
7964
+ weight: 800,
7965
+ lineHeight: 1.1,
7966
+ color: { color: "#ffffff" }
7967
+ })
7968
+ }),
7969
+ {
7970
+ id: "rule",
7971
+ name: "Rule",
7972
+ type: "shape",
7973
+ shape: "rectangle",
7974
+ x: 80,
7975
+ y: 478,
7976
+ width: 120,
7977
+ height: 6,
7978
+ rotation: 0,
7979
+ opacity: 1,
7980
+ visible: true,
7981
+ fill: { brand: "accent", fallback: "#38bdf8" }
7982
+ },
7983
+ text({
7984
+ id: "byline",
7985
+ name: "Byline",
7986
+ x: 80,
7987
+ y: 508,
7988
+ width: 1040,
7989
+ height: 50,
7990
+ content: [{ text: "By " }, { path: "post.author" }],
7991
+ showWhen: { path: "post.author" },
7992
+ style: style({ size: 30, weight: 500, color: { color: "#cbd5e1" } })
7993
+ })
7994
+ ]),
7995
+ sampleData: {
7996
+ site: { name: "Fennlor Studio" },
7997
+ post: {
7998
+ title: "How we cut our invoice run from an hour to four minutes",
7999
+ author: "Max Mustermann"
8000
+ }
8001
+ }
8002
+ },
8003
+ {
8004
+ id: "quote",
8005
+ title: "Quote card",
8006
+ size: { width: 1080, height: 1080 },
8007
+ design: design({ color: "#f5efe6" }, [
8008
+ text({
8009
+ id: "mark",
8010
+ name: "Quotation mark",
8011
+ x: 96,
8012
+ y: 60,
8013
+ width: 240,
8014
+ height: 220,
8015
+ content: [{ text: "\u201C" }],
8016
+ style: style({
8017
+ font: { family: "Playfair Display", source: "google" },
8018
+ size: 220,
8019
+ weight: 700,
8020
+ lineHeight: 1,
8021
+ color: { color: "#d97706" }
8022
+ })
8023
+ }),
8024
+ text({
8025
+ id: "quote",
8026
+ name: "Quote",
8027
+ x: 96,
8028
+ y: 260,
8029
+ width: 888,
8030
+ height: 520,
8031
+ content: [{ path: "review.quote" }],
8032
+ sizing: "shrink",
8033
+ minFontSize: 32,
8034
+ valign: "middle",
8035
+ style: style({
8036
+ font: { family: "Playfair Display", source: "google" },
8037
+ size: 60,
8038
+ weight: 600,
8039
+ lineHeight: 1.22,
8040
+ color: { color: "#1c1917" }
8041
+ })
8042
+ }),
8043
+ text({
8044
+ id: "author",
8045
+ name: "Author",
8046
+ x: 96,
8047
+ y: 836,
8048
+ width: 888,
8049
+ height: 50,
8050
+ content: [{ path: "review.author" }],
8051
+ style: style({ size: 36, weight: 600, color: { color: "#1c1917" } })
8052
+ }),
8053
+ text({
8054
+ id: "role",
8055
+ name: "Role",
8056
+ x: 96,
8057
+ y: 896,
8058
+ width: 888,
8059
+ height: 44,
8060
+ content: [{ path: "review.role" }],
8061
+ showWhen: { path: "review.role" },
8062
+ style: style({ size: 28, weight: 400, color: { color: "#78716c" } })
8063
+ })
8064
+ ]),
8065
+ sampleData: {
8066
+ review: {
8067
+ quote: "Delivery took two days, and the one shade that arrived dented was replaced before we had finished the call.",
8068
+ author: "Erika Mustermann",
8069
+ role: "Office manager, Olvarest GmbH"
8070
+ }
8071
+ }
8072
+ },
8073
+ {
8074
+ id: "event",
8075
+ title: "Event story",
8076
+ size: { width: 1080, height: 1920 },
8077
+ design: design({ color: "#111827" }, [
8078
+ text({
8079
+ id: "label",
8080
+ name: "Label",
8081
+ x: 96,
8082
+ y: 200,
8083
+ width: 888,
8084
+ height: 50,
8085
+ content: [{ text: "Event" }],
8086
+ style: style({
8087
+ size: 36,
8088
+ weight: 700,
8089
+ letterSpacing: 6,
8090
+ uppercase: true,
8091
+ color: { color: "#fbbf24" }
8092
+ })
8093
+ }),
8094
+ text({
8095
+ id: "title",
8096
+ name: "Title",
8097
+ x: 96,
8098
+ y: 290,
8099
+ width: 888,
8100
+ height: 620,
8101
+ content: [{ path: "event.title" }],
8102
+ sizing: "shrink",
8103
+ minFontSize: 56,
8104
+ valign: "bottom",
8105
+ style: style({
8106
+ size: 112,
8107
+ weight: 800,
8108
+ lineHeight: 1.05,
8109
+ color: { color: "#ffffff" }
8110
+ })
8111
+ }),
8112
+ text({
8113
+ id: "date",
8114
+ name: "Date",
8115
+ x: 96,
8116
+ y: 980,
8117
+ width: 888,
8118
+ height: 70,
8119
+ content: [
8120
+ {
8121
+ path: "event.date",
8122
+ format: { helper: "date", pattern: "d MMMM yyyy" }
8123
+ }
8124
+ ],
8125
+ style: style({ size: 52, weight: 600, color: { color: "#e5e7eb" } })
8126
+ }),
8127
+ text({
8128
+ id: "place",
8129
+ name: "Place",
8130
+ x: 96,
8131
+ y: 1060,
8132
+ width: 888,
8133
+ height: 60,
8134
+ content: [{ path: "event.place" }],
8135
+ style: style({ size: 38, weight: 400, color: { color: "#9ca3af" } })
8136
+ }),
8137
+ {
8138
+ id: "qr",
8139
+ name: "QR code",
8140
+ type: "qr",
8141
+ x: 96,
8142
+ y: 1440,
8143
+ width: 300,
8144
+ height: 300,
8145
+ rotation: 0,
8146
+ opacity: 1,
8147
+ visible: true,
8148
+ radius: 16,
8149
+ value: { path: "event.url" },
8150
+ ecc: "M",
8151
+ color: { color: "#111827" },
8152
+ background: { color: "#ffffff" }
8153
+ },
8154
+ text({
8155
+ id: "scan",
8156
+ name: "Scan note",
8157
+ x: 440,
8158
+ y: 1560,
8159
+ width: 544,
8160
+ height: 60,
8161
+ content: [{ text: "Scan for your ticket" }],
8162
+ style: style({ size: 40, weight: 600, color: { color: "#ffffff" } })
8163
+ })
8164
+ ]),
8165
+ sampleData: {
8166
+ event: {
8167
+ title: "Formfeed meetup: documents as code",
8168
+ date: "2026-11-12",
8169
+ place: "Musterstra\xDFe 1 \xB7 12345 Musterstadt",
8170
+ url: "https://fennlor.test/events/meetup"
8171
+ }
8172
+ }
8173
+ }
8174
+ ];
8175
+
8176
+ // packages/engine/src/lib/office/errors.ts
8177
+ var OfficeError = class extends Error {
8178
+ constructor(code, message, details = {}) {
8179
+ super(message);
8180
+ this.code = code;
8181
+ this.details = details;
8182
+ this.name = "OfficeError";
8183
+ }
8184
+ };
8185
+ var officeLimits = {
8186
+ /** Entries in one container. */
8187
+ maxEntries: 2e3,
8188
+ /** Sum of the uncompressed sizes the central directory declares. */
8189
+ maxDeclaredBytes: 200 * 1024 * 1024,
8190
+ /** One part we inflate; enforced while inflating, because the declared size can lie. */
8191
+ maxPartBytes: 20 * 1024 * 1024,
8192
+ /** A conversion upload or a template file. */
8193
+ maxFileBytes: 20 * 1024 * 1024
8194
+ };
8195
+
8196
+ // node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/browser.js
8197
+ var u8 = Uint8Array;
8198
+ var u16 = Uint16Array;
8199
+ var i32 = Int32Array;
8200
+ var fleb = new u8([
8201
+ 0,
8202
+ 0,
8203
+ 0,
8204
+ 0,
8205
+ 0,
8206
+ 0,
8207
+ 0,
8208
+ 0,
8209
+ 1,
8210
+ 1,
8211
+ 1,
8212
+ 1,
8213
+ 2,
8214
+ 2,
8215
+ 2,
8216
+ 2,
8217
+ 3,
8218
+ 3,
8219
+ 3,
8220
+ 3,
8221
+ 4,
8222
+ 4,
8223
+ 4,
8224
+ 4,
8225
+ 5,
8226
+ 5,
8227
+ 5,
8228
+ 5,
8229
+ 0,
8230
+ /* unused */
8231
+ 0,
8232
+ 0,
8233
+ /* impossible */
8234
+ 0
8235
+ ]);
8236
+ var fdeb = new u8([
8237
+ 0,
8238
+ 0,
8239
+ 0,
8240
+ 0,
8241
+ 1,
8242
+ 1,
8243
+ 2,
8244
+ 2,
8245
+ 3,
8246
+ 3,
8247
+ 4,
8248
+ 4,
8249
+ 5,
8250
+ 5,
8251
+ 6,
8252
+ 6,
8253
+ 7,
8254
+ 7,
8255
+ 8,
8256
+ 8,
8257
+ 9,
8258
+ 9,
8259
+ 10,
8260
+ 10,
8261
+ 11,
8262
+ 11,
8263
+ 12,
8264
+ 12,
8265
+ 13,
8266
+ 13,
8267
+ /* unused */
8268
+ 0,
8269
+ 0
8270
+ ]);
8271
+ var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
8272
+ var freb = function(eb, start) {
8273
+ var b = new u16(31);
8274
+ for (var i = 0; i < 31; ++i) {
8275
+ b[i] = start += 1 << eb[i - 1];
8276
+ }
8277
+ var r = new i32(b[30]);
8278
+ for (var i = 1; i < 30; ++i) {
8279
+ for (var j = b[i]; j < b[i + 1]; ++j) {
8280
+ r[j] = j - b[i] << 5 | i;
8281
+ }
8282
+ }
8283
+ return { b, r };
8284
+ };
8285
+ var _a = freb(fleb, 2);
8286
+ var fl = _a.b;
8287
+ var revfl = _a.r;
8288
+ fl[28] = 258, revfl[258] = 28;
8289
+ var _b = freb(fdeb, 0);
8290
+ var fd = _b.b;
8291
+ var revfd = _b.r;
8292
+ var rev = new u16(32768);
8293
+ for (i = 0; i < 32768; ++i) {
6177
8294
  x = (i & 43690) >> 1 | (i & 21845) << 1;
6178
8295
  x = (x & 52428) >> 2 | (x & 13107) << 2;
6179
8296
  x = (x & 61680) >> 4 | (x & 3855) << 4;
@@ -7358,8 +9475,8 @@ function assertWellFormed(tokens, part) {
7358
9475
  break;
7359
9476
  }
7360
9477
  case "close": {
7361
- const open = stack.pop();
7362
- if (open !== token2.name) throw invalid2(`</${token2.name}> closes <${open ?? "nothing"}>`, part);
9478
+ const open2 = stack.pop();
9479
+ if (open2 !== token2.name) throw invalid2(`</${token2.name}> closes <${open2 ?? "nothing"}>`, part);
7363
9480
  break;
7364
9481
  }
7365
9482
  case "text":
@@ -7375,12 +9492,12 @@ function assertWellFormed(tokens, part) {
7375
9492
  }
7376
9493
  var FORBIDDEN = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/;
7377
9494
  var ENTITY = /&(?:#x[0-9A-Fa-f]+|#\d+|amp|lt|gt|quot|apos);/y;
7378
- function checkText(text, part) {
7379
- if (FORBIDDEN.test(text)) throw invalid2("a character XML does not allow", part);
7380
- if (text.includes("<")) throw invalid2("unescaped < in text", part);
7381
- for (let i = text.indexOf("&"); i !== -1; i = text.indexOf("&", i + 1)) {
9495
+ function checkText(text2, part) {
9496
+ if (FORBIDDEN.test(text2)) throw invalid2("a character XML does not allow", part);
9497
+ if (text2.includes("<")) throw invalid2("unescaped < in text", part);
9498
+ for (let i = text2.indexOf("&"); i !== -1; i = text2.indexOf("&", i + 1)) {
7382
9499
  ENTITY.lastIndex = i;
7383
- if (!ENTITY.test(text)) throw invalid2(`invalid entity reference near "${text.slice(i, i + 12)}"`, part);
9500
+ if (!ENTITY.test(text2)) throw invalid2(`invalid entity reference near "${text2.slice(i, i + 12)}"`, part);
7384
9501
  }
7385
9502
  }
7386
9503
  function checkAttributes(raw, part) {
@@ -7395,7 +9512,7 @@ function checkAttributes(raw, part) {
7395
9512
  });
7396
9513
  if (rest.trim() !== "") throw invalid2(`malformed attributes in ${raw.slice(0, 40)}`, part);
7397
9514
  }
7398
- function decodeText2(raw) {
9515
+ function decodeText3(raw) {
7399
9516
  return raw.replace(/&(#x[0-9A-Fa-f]+|#\d+|amp|lt|gt|quot|apos);/g, (whole, name) => {
7400
9517
  switch (name) {
7401
9518
  case "amp":
@@ -7415,13 +9532,13 @@ function decodeText2(raw) {
7415
9532
  }
7416
9533
  });
7417
9534
  }
7418
- function escapeText2(text) {
7419
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
9535
+ function escapeText2(text2) {
9536
+ return text2.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
7420
9537
  }
7421
9538
  var FORBIDDEN_GLOBAL = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g;
7422
- function stripForbidden(text) {
9539
+ function stripForbidden(text2) {
7423
9540
  let removed = 0;
7424
- const out = text.replace(FORBIDDEN_GLOBAL, () => {
9541
+ const out = text2.replace(FORBIDDEN_GLOBAL, () => {
7425
9542
  removed++;
7426
9543
  return "";
7427
9544
  });
@@ -7672,13 +9789,13 @@ function placeDrawings(xml2, pattern, markupFor) {
7672
9789
  pattern.lastIndex = 0;
7673
9790
  if (pattern.test(t.raw)) {
7674
9791
  const segments = t.raw.split(pattern);
7675
- let text = segments[0];
9792
+ let text2 = segments[0];
7676
9793
  for (let s = 1; s < segments.length; s += 2) {
7677
9794
  const index = Number(segments[s]);
7678
- text += `</w:t></w:r><w:r>${runProps}${markupFor(index)}</w:r>${runOpen}${runProps}<w:t xml:space="preserve">` + segments[s + 1];
9795
+ text2 += `</w:t></w:r><w:r>${runProps}${markupFor(index)}</w:r>${runOpen}${runProps}<w:t xml:space="preserve">` + segments[s + 1];
7679
9796
  }
7680
9797
  out[textOpen] = withPreserve(out[textOpen]);
7681
- out.push(text);
9798
+ out.push(text2);
7682
9799
  continue;
7683
9800
  }
7684
9801
  }
@@ -7701,13 +9818,13 @@ function withRelationships(rels2, relationships) {
7701
9818
  return rels2.slice(0, end) + added + rels2.slice(end);
7702
9819
  }
7703
9820
  function relationshipIds(rels2) {
7704
- return new Set([...(rels2 ?? "").matchAll(/\sId="([^"]+)"/g)].map((m) => decodeText2(m[1])));
9821
+ return new Set([...(rels2 ?? "").matchAll(/\sId="([^"]+)"/g)].map((m) => decodeText3(m[1])));
7705
9822
  }
7706
9823
  function withImageTypes(contentTypes, extensions) {
7707
9824
  let out = contentTypes;
7708
9825
  for (const ext of extensions) {
7709
9826
  if (new RegExp(`<Default\\b[^>]*Extension="${ext}"`, "i").test(out)) continue;
7710
- out = out.replace(/<Types\b[^>]*>/, (open) => `${open}<Default Extension="${ext}" ContentType="image/${ext}"/>`);
9827
+ out = out.replace(/<Types\b[^>]*>/, (open2) => `${open2}<Default Extension="${ext}" ContentType="image/${ext}"/>`);
7711
9828
  }
7712
9829
  return out;
7713
9830
  }
@@ -7729,7 +9846,7 @@ var THEME_REFERENCES = /^\+(mj|mn)-/;
7729
9846
  function documentFonts(archive) {
7730
9847
  const fonts = /* @__PURE__ */ new Map();
7731
9848
  const add2 = (name, embedded = false) => {
7732
- const clean = decodeText2(name).trim();
9849
+ const clean = decodeText3(name).trim();
7733
9850
  if (!clean || THEME_REFERENCES.test(clean)) return;
7734
9851
  const key = clean.toLowerCase();
7735
9852
  const known = fonts.get(key);
@@ -7910,7 +10027,7 @@ function toTemplateSource(xml2, flavour, nonce, part) {
7910
10027
  for (const token2 of tokens) {
7911
10028
  if (token2.kind === "text" && inText) {
7912
10029
  flush();
7913
- source += decodeText2(token2.raw);
10030
+ source += decodeText3(token2.raw);
7914
10031
  continue;
7915
10032
  }
7916
10033
  pending.raw += token2.raw;
@@ -7933,11 +10050,11 @@ function fromTemplateOutput(output, template, part) {
7933
10050
  let last = 0;
7934
10051
  let inText = false;
7935
10052
  const emit = (segment) => {
7936
- const { text, removed } = stripForbidden(segment);
10053
+ const { text: text2, removed } = stripForbidden(segment);
7937
10054
  removedCharacters += removed;
7938
- if (!inText && text.trim() !== "")
7939
- throw new OfficeError("office_document_invalid", `${part ?? "part"}: text outside a text element: "${text.trim().slice(0, 40)}"`);
7940
- xml2 += escapeText2(text);
10055
+ if (!inText && text2.trim() !== "")
10056
+ throw new OfficeError("office_document_invalid", `${part ?? "part"}: text outside a text element: "${text2.trim().slice(0, 40)}"`);
10057
+ xml2 += escapeText2(text2);
7941
10058
  };
7942
10059
  for (const m of output.matchAll(placeholder)) {
7943
10060
  emit(output.slice(last, m.index));
@@ -7979,15 +10096,15 @@ function layoutText(xml2, flavour, part) {
7979
10096
  out.push(token2.raw);
7980
10097
  continue;
7981
10098
  }
7982
- const text = token2.raw.replace(/\r\n?/g, "\n");
10099
+ const text2 = token2.raw.replace(/\r\n?/g, "\n");
7983
10100
  if (flavour === "wordprocessing") {
7984
- const pieces = text.split(/(\n|\t)/);
7985
- if (pieces.length > 1 || /^\s|\s$/.test(text)) out[textOpenIndex] = preserveSpace(out[textOpenIndex]);
10101
+ const pieces = text2.split(/(\n|\t)/);
10102
+ if (pieces.length > 1 || /^\s|\s$/.test(text2)) out[textOpenIndex] = preserveSpace(out[textOpenIndex]);
7986
10103
  out.push(
7987
10104
  pieces.map((p) => p === "\n" ? '</w:t><w:br/><w:t xml:space="preserve">' : p === " " ? '</w:t><w:tab/><w:t xml:space="preserve">' : p).join("")
7988
10105
  );
7989
10106
  } else {
7990
- out.push(text.split("\n").join(`</a:t></a:r><a:br>${runProperties}</a:br><a:r>${runProperties}<a:t>`));
10107
+ out.push(text2.split("\n").join(`</a:t></a:r><a:br>${runProperties}</a:br><a:r>${runProperties}<a:t>`));
7991
10108
  }
7992
10109
  }
7993
10110
  return out.join("");
@@ -8020,17 +10137,17 @@ var AUTOCORRECTED = [
8020
10137
  [/\u200B|\u200C|\u200D|\uFEFF/g, ""]
8021
10138
  ];
8022
10139
  function normaliseTags(template, part) {
8023
- const { text, origin, placeholders } = stripPlaceholders(template);
10140
+ const { text: text2, origin, placeholders } = stripPlaceholders(template);
8024
10141
  const sourceRange = (from, to) => [
8025
10142
  origin[from] ?? template.source.length,
8026
10143
  (origin[to - 1] ?? template.source.length - 1) + 1
8027
10144
  ];
8028
10145
  const diagnostics = [];
8029
10146
  const edits = [];
8030
- for (const tag of findTags(text)) {
10147
+ for (const tag of findTags(text2)) {
8031
10148
  const inside = placeholders.filter((p) => p.at > tag.start && p.at < tag.end);
8032
10149
  const paragraph2 = paragraphOf(template, placeholders, tag.start);
8033
- const raw = text.slice(tag.start, tag.end);
10150
+ const raw = text2.slice(tag.start, tag.end);
8034
10151
  const unsafe = inside.filter((p) => {
8035
10152
  const piece = template.markup[p.index];
8036
10153
  return !piece || piece.paragraphBoundary || piece.containerBoundary;
@@ -8084,8 +10201,8 @@ function normaliseTags(template, part) {
8084
10201
  return { template: { ...template, source: applyEdits(template.source, edits) }, diagnostics };
8085
10202
  }
8086
10203
  var ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
8087
- function decodeEntities(text) {
8088
- return text.replace(/&(#x[0-9a-f]+|#\d+|amp|lt|gt|quot|apos);/gi, (whole, name) => {
10204
+ function decodeEntities(text2) {
10205
+ return text2.replace(/&(#x[0-9a-f]+|#\d+|amp|lt|gt|quot|apos);/gi, (whole, name) => {
8089
10206
  if (name[0] !== "#") return ENTITIES[name.toLowerCase()] ?? whole;
8090
10207
  const code = name[1] === "x" || name[1] === "X" ? parseInt(name.slice(2), 16) : parseInt(name.slice(1), 10);
8091
10208
  return Number.isFinite(code) ? String.fromCodePoint(code) : whole;
@@ -8093,28 +10210,28 @@ function decodeEntities(text) {
8093
10210
  }
8094
10211
  function stripPlaceholders(template) {
8095
10212
  const pattern = placeholderPattern(template.nonce);
8096
- let text = "";
10213
+ let text2 = "";
8097
10214
  const origin = [];
8098
10215
  const placeholders = [];
8099
10216
  let last = 0;
8100
10217
  for (const m of template.source.matchAll(pattern)) {
8101
10218
  for (let i = last; i < (m.index ?? 0); i++) {
8102
- text += template.source[i];
10219
+ text2 += template.source[i];
8103
10220
  origin.push(i);
8104
10221
  }
8105
- placeholders.push({ index: Number(m[1]), at: text.length, from: m.index ?? 0, to: (m.index ?? 0) + m[0].length });
10222
+ placeholders.push({ index: Number(m[1]), at: text2.length, from: m.index ?? 0, to: (m.index ?? 0) + m[0].length });
8106
10223
  last = (m.index ?? 0) + m[0].length;
8107
10224
  }
8108
10225
  for (let i = last; i < template.source.length; i++) {
8109
- text += template.source[i];
10226
+ text2 += template.source[i];
8110
10227
  origin.push(i);
8111
10228
  }
8112
- return { text, origin, placeholders };
10229
+ return { text: text2, origin, placeholders };
8113
10230
  }
8114
10231
  function listTags(template, part) {
8115
- const { text, placeholders } = stripPlaceholders(template);
8116
- return findTags(text).map((tag) => {
8117
- const raw = text.slice(tag.start, tag.end);
10232
+ const { text: text2, placeholders } = stripPlaceholders(template);
10233
+ return findTags(text2).map((tag) => {
10234
+ const raw = text2.slice(tag.start, tag.end);
8118
10235
  const kind = raw.startsWith("{#") ? "comment" : raw.startsWith("{%") || /^\{\{~?\s*[#/^]/.test(raw) || /^\{\{~?\s*else\b/.test(raw) ? "block" : "output";
8119
10236
  return { part, paragraph: paragraphOf(template, placeholders, tag.start), text: raw.replace(/\s+/g, " ").trim(), kind };
8120
10237
  });
@@ -8135,15 +10252,15 @@ function undoAutocorrect(tag) {
8135
10252
  for (const [pattern, replacement] of AUTOCORRECTED) out = out.replace(pattern, replacement);
8136
10253
  return out;
8137
10254
  }
8138
- function findTags(text) {
10255
+ function findTags(text2) {
8139
10256
  const tags = [];
8140
- for (let i = 0; i < text.length - 1; i++) {
8141
- const opener = OPENERS.find((o) => text.startsWith(o, i));
10257
+ for (let i = 0; i < text2.length - 1; i++) {
10258
+ const opener = OPENERS.find((o) => text2.startsWith(o, i));
8142
10259
  if (!opener) continue;
8143
10260
  const closer = CLOSERS[opener];
8144
- const end = text.indexOf(closer, i + opener.length);
10261
+ const end = text2.indexOf(closer, i + opener.length);
8145
10262
  if (end === -1) continue;
8146
- const stop = end + closer.length + (opener === "{{" && text[end + closer.length] === "}" ? 1 : 0);
10263
+ const stop = end + closer.length + (opener === "{{" && text2[end + closer.length] === "}" ? 1 : 0);
8147
10264
  tags.push({ start: i, end: stop });
8148
10265
  i = stop - 1;
8149
10266
  }
@@ -8173,8 +10290,8 @@ var CONTENT = /* @__PURE__ */ new Set(["w:drawing", "w:pict", "w:object", "w:fld
8173
10290
  var JINJA_OPEN = /^(for|if|unless|case|capture|raw|with|macro|call|filter|block|tablerow|comment|autoescape)\b/;
8174
10291
  var JINJA_CLOSE = /^end(for|if|unless|case|capture|raw|with|macro|call|filter|block|tablerow|comment|autoescape)\b/;
8175
10292
  var JINJA_MIDDLE = /^(else|elif|elsif|when)\b/;
8176
- function blockRole(text) {
8177
- const t = text.trim();
10293
+ function blockRole(text2) {
10294
+ const t = text2.trim();
8178
10295
  let m = /^\{%-?\s*([\s\S]*?)\s*-?%\}$/.exec(t);
8179
10296
  if (m && !t.slice(2, -2).includes("%}")) {
8180
10297
  const body = m[1] ?? "";
@@ -8206,14 +10323,14 @@ function dropTaggedFallbacks(xml2, part) {
8206
10323
  if (t.kind === "open" && t.name === "mc:Fallback" && !t.selfClosing) {
8207
10324
  let depth = 1;
8208
10325
  let j = i + 1;
8209
- let text = "";
10326
+ let text2 = "";
8210
10327
  for (; j < tokens.length && depth > 0; j++) {
8211
10328
  const u = tokens[j];
8212
10329
  if (u.kind === "open" && u.name === "mc:Fallback" && !u.selfClosing) depth++;
8213
10330
  else if (u.kind === "close" && u.name === "mc:Fallback") depth--;
8214
- else if (u.kind === "text") text += decodeText2(u.raw);
10331
+ else if (u.kind === "text") text2 += decodeText3(u.raw);
8215
10332
  }
8216
- if (/\{\{|\{%|\{#/.test(text)) {
10333
+ if (/\{\{|\{%|\{#/.test(text2)) {
8217
10334
  i = j - 1;
8218
10335
  continue;
8219
10336
  }
@@ -8254,25 +10371,25 @@ function applyStructure(xml2, part) {
8254
10371
  if (s.role === "open")
8255
10372
  stack.push({ keyword: s.keyword, parent, paragraph: s.info.number, tag: s.tag, after: unitOf(span).end });
8256
10373
  else if (s.role === "close" || s.role === "middle") {
8257
- const open = s.role === "close" ? stack.pop() : stack.at(-1);
8258
- if (open && s.role === "close" && LOOPS.has(open.keyword)) {
8259
- const body = tokens.slice(open.after, unitOf(span).start);
10374
+ const open2 = s.role === "close" ? stack.pop() : stack.at(-1);
10375
+ if (open2 && s.role === "close" && LOOPS.has(open2.keyword)) {
10376
+ const body = tokens.slice(open2.after, unitOf(span).start);
8260
10377
  const reference = body.find((t) => t.kind === "open" && REFERENCES.has(t.name));
8261
10378
  if (reference && reference.kind === "open")
8262
10379
  diagnostics.push({
8263
10380
  severity: "error",
8264
10381
  code: "office-reference-in-loop",
8265
- message: `The loop from paragraph ${open.paragraph} repeats a ${reference.name.includes("comment") ? "comment" : "footnote or endnote"}, which cannot be copied. Move it out of the loop.`,
10382
+ message: `The loop from paragraph ${open2.paragraph} repeats a ${reference.name.includes("comment") ? "comment" : "footnote or endnote"}, which cannot be copied. Move it out of the loop.`,
8266
10383
  part,
8267
- paragraph: open.paragraph,
8268
- text: short2(open.tag)
10384
+ paragraph: open2.paragraph,
10385
+ text: short2(open2.tag)
8269
10386
  });
8270
10387
  }
8271
- if (open && open.parent !== parent)
10388
+ if (open2 && open2.parent !== parent)
8272
10389
  diagnostics.push({
8273
10390
  severity: "error",
8274
10391
  code: "office-block-unbalanced",
8275
- message: `${short2(open.tag)} (paragraph ${open.paragraph}) and ${short2(s.tag)} are in different places: one in a table cell or text box, the other outside it. Put both in the same place.`,
10392
+ message: `${short2(open2.tag)} (paragraph ${open2.paragraph}) and ${short2(s.tag)} are in different places: one in a table cell or text box, the other outside it. Put both in the same place.`,
8276
10393
  part,
8277
10394
  paragraph: s.info.number,
8278
10395
  text: short2(s.tag)
@@ -8286,9 +10403,9 @@ function applyStructure(xml2, part) {
8286
10403
  for (const [span, s] of structural) {
8287
10404
  if (rowOf.has(span)) continue;
8288
10405
  const keep = s.info.sectionProperties;
8289
- let text = element(s.tag);
8290
- if (keep) text = s.role === "open" ? `<w:p>${keep}</w:p>${text}` : `${text}<w:p>${keep}</w:p>`;
8291
- replacements.push({ start: span.start, end: span.end, text });
10406
+ let text2 = element(s.tag);
10407
+ if (keep) text2 = s.role === "open" ? `<w:p>${keep}</w:p>${text2}` : `${text2}<w:p>${keep}</w:p>`;
10408
+ replacements.push({ start: span.start, end: span.end, text: text2 });
8292
10409
  const holder = span.parent && BODIES.has(span.parent.name) ? span.parent : null;
8293
10410
  if (holder && !keep) cellsEmptied.set(holder, (cellsEmptied.get(holder) ?? 0) + 1);
8294
10411
  }
@@ -8342,7 +10459,7 @@ function collectParagraphs(tokens, root) {
8342
10459
  const out = [];
8343
10460
  const walk3 = (span) => {
8344
10461
  if (PARAGRAPH.has(span.name)) {
8345
- let text = "";
10462
+ let text2 = "";
8346
10463
  let hasContent = false;
8347
10464
  let sectionProperties = null;
8348
10465
  let inText = false;
@@ -8353,10 +10470,10 @@ function collectParagraphs(tokens, root) {
8353
10470
  else if (CONTENT.has(t.name)) hasContent = true;
8354
10471
  else if (t.name === "w:sectPr") sectionProperties = "";
8355
10472
  } else if (t.kind === "close" && TEXT.has(t.name)) inText = false;
8356
- else if (t.kind === "text" && inText) text += decodeText2(t.raw);
10473
+ else if (t.kind === "text" && inText) text2 += decodeText3(t.raw);
8357
10474
  }
8358
10475
  if (sectionProperties !== null) sectionProperties = propertiesOf(tokens, span);
8359
- out.push({ span, number: out.length + 1, text, hasContent, sectionProperties });
10476
+ out.push({ span, number: out.length + 1, text: text2, hasContent, sectionProperties });
8360
10477
  }
8361
10478
  for (const child2 of span.children) walk3(child2);
8362
10479
  };
@@ -8464,7 +10581,7 @@ function placeSlidePictures(xml2, pattern, drawingFor, pictureMarkup, slide) {
8464
10581
  for (const container of containers) {
8465
10582
  const indexes = [];
8466
10583
  const edits = [];
8467
- let text = "";
10584
+ let text2 = "";
8468
10585
  let inText = false;
8469
10586
  for (let i = container.start + 1; i < container.end; i++) {
8470
10587
  const t = tokens[i];
@@ -8473,7 +10590,7 @@ function placeSlidePictures(xml2, pattern, drawingFor, pictureMarkup, slide) {
8473
10590
  else if (t.kind === "text" && inText) {
8474
10591
  pattern.lastIndex = 0;
8475
10592
  if (!pattern.test(t.raw)) {
8476
- text += decodeText2(t.raw);
10593
+ text2 += decodeText3(t.raw);
8477
10594
  continue;
8478
10595
  }
8479
10596
  const segments = t.raw.split(pattern);
@@ -8482,7 +10599,7 @@ function placeSlidePictures(xml2, pattern, drawingFor, pictureMarkup, slide) {
8482
10599
  if (s % 2 === 1) indexes.push(Number(segments[s]));
8483
10600
  else kept += segments[s];
8484
10601
  }
8485
- text += decodeText2(kept);
10602
+ text2 += decodeText3(kept);
8486
10603
  edits.push({ at: i, raw: kept });
8487
10604
  }
8488
10605
  }
@@ -8501,7 +10618,7 @@ function placeSlidePictures(xml2, pattern, drawingFor, pictureMarkup, slide) {
8501
10618
  }
8502
10619
  const autoFit = tokens.slice(container.start, container.end).some((t) => t.kind === "open" && t.name === "a:spAutoFit");
8503
10620
  const placed = layoutPictures(pictures, box, autoFit).map((b, i) => pictureMarkup(pictures[i], b)).join("");
8504
- const onlyPictures = container.name === "p:sp" && text.trim() === "";
10621
+ const onlyPictures = container.name === "p:sp" && text2.trim() === "";
8505
10622
  if (onlyPictures) replacements.push({ start: container.start, end: container.end, text: placed });
8506
10623
  else {
8507
10624
  for (const edit of edits) replacements.push({ start: edit.at, end: edit.at, text: edit.raw });
@@ -8774,8 +10891,8 @@ function starterTags(engine) {
8774
10891
  codeFit: engine === "liquid" ? "{{ offer.url | qrcode }}" : "{{ qrcode(offer.url) }}"
8775
10892
  };
8776
10893
  }
8777
- var paragraph = (text, style, bold = false) => `<w:p>${style ? `<w:pPr><w:pStyle w:val="${style}"/></w:pPr>` : ""}<w:r>${bold ? "<w:rPr><w:b/></w:rPr>" : ""}<w:t xml:space="preserve">${escapeText2(text)}</w:t></w:r></w:p>`;
8778
- var cell = (text, width, bold = false) => `<w:tc><w:tcPr><w:tcW w:w="${width}" w:type="dxa"/></w:tcPr>${paragraph(text, void 0, bold)}</w:tc>`;
10894
+ var paragraph = (text2, style2, bold = false) => `<w:p>${style2 ? `<w:pPr><w:pStyle w:val="${style2}"/></w:pPr>` : ""}<w:r>${bold ? "<w:rPr><w:b/></w:rPr>" : ""}<w:t xml:space="preserve">${escapeText2(text2)}</w:t></w:r></w:p>`;
10895
+ var cell = (text2, width, bold = false) => `<w:tc><w:tcPr><w:tcW w:w="${width}" w:type="dxa"/></w:tcPr>${paragraph(text2, void 0, bold)}</w:tc>`;
8779
10896
  var row = (cells) => `<w:tr>${cells}</w:tr>`;
8780
10897
  function starterDocument(engine) {
8781
10898
  const t = starterTags(engine);
@@ -8818,7 +10935,7 @@ function starterDocument(engine) {
8818
10935
  ["word/document.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:w="${W}"><w:body>${body}</w:body></w:document>`]
8819
10936
  ];
8820
10937
  return {
8821
- bytes: writeZip(files.map(([name, text]) => ({ name, data: encoder2.encode(text) }))),
10938
+ bytes: writeZip(files.map(([name, text2]) => ({ name, data: encoder2.encode(text2) }))),
8822
10939
  sampleData: starterSampleData()
8823
10940
  };
8824
10941
  }
@@ -8846,15 +10963,15 @@ var CM = 36e4;
8846
10963
  var FONT = "Carlito";
8847
10964
  var ACCENT = "0F766E";
8848
10965
  var emptyTree = '<p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/>';
8849
- var run = (text, size, options = {}) => `<a:r><a:rPr lang="en-GB" sz="${size * 100}"${options.bold ? ' b="1"' : ""} dirty="0">${options.color ? `<a:solidFill><a:srgbClr val="${options.color}"/></a:solidFill>` : ""}<a:latin typeface="${FONT}"/></a:rPr><a:t>${escapeText2(text)}</a:t></a:r>`;
8850
- var para = (text, size, options = {}) => `<a:p>${run(text, size, options)}</a:p>`;
10966
+ var run = (text2, size, options = {}) => `<a:r><a:rPr lang="en-GB" sz="${size * 100}"${options.bold ? ' b="1"' : ""} dirty="0">${options.color ? `<a:solidFill><a:srgbClr val="${options.color}"/></a:solidFill>` : ""}<a:latin typeface="${FONT}"/></a:rPr><a:t>${escapeText2(text2)}</a:t></a:r>`;
10967
+ var para = (text2, size, options = {}) => `<a:p>${run(text2, size, options)}</a:p>`;
8851
10968
  function textBox(id, name, x, y, cx, cy, paragraphs) {
8852
10969
  return `<p:sp><p:nvSpPr><p:cNvPr id="${id}" name="${name}"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="${x}" y="${y}"/><a:ext cx="${cx}" cy="${cy}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/></p:spPr><p:txBody><a:bodyPr wrap="square" rtlCol="0"><a:noAutofit/></a:bodyPr><a:lstStyle/>${paragraphs}</p:txBody></p:sp>`;
8853
10970
  }
8854
- function cell2(text, header) {
10971
+ function cell2(text2, header) {
8855
10972
  const fill = header ? `<a:solidFill><a:srgbClr val="${ACCENT}"/></a:solidFill>` : "<a:noFill/>";
8856
10973
  const line = `<a:lnB w="6350"><a:solidFill><a:srgbClr val="BFBFBF"/></a:solidFill></a:lnB>`;
8857
- return `<a:tc><a:txBody><a:bodyPr/><a:lstStyle/>${para(text, 14, header ? { bold: true, color: "FFFFFF" } : {})}</a:txBody><a:tcPr marL="91440" marR="91440" marT="45720" marB="45720">${line}${fill}</a:tcPr></a:tc>`;
10974
+ return `<a:tc><a:txBody><a:bodyPr/><a:lstStyle/>${para(text2, 14, header ? { bold: true, color: "FFFFFF" } : {})}</a:txBody><a:tcPr marL="91440" marR="91440" marT="45720" marB="45720">${line}${fill}</a:tcPr></a:tc>`;
8858
10975
  }
8859
10976
  var tableRow = (a, b, header = false) => `<a:tr h="${Math.round(0.9 * CM)}">${cell2(a, header)}${cell2(b, header)}</a:tr>`;
8860
10977
  function table2(id, x, y, rows) {
@@ -8944,7 +11061,7 @@ function starterPresentation(engine) {
8944
11061
  ];
8945
11062
  const encoder2 = new TextEncoder();
8946
11063
  return {
8947
- bytes: writeZip(files.map(([name, text]) => ({ name, data: encoder2.encode(text) }))),
11064
+ bytes: writeZip(files.map(([name, text2]) => ({ name, data: encoder2.encode(text2) }))),
8948
11065
  sampleData: starterSampleData()
8949
11066
  };
8950
11067
  }