@taloswork/ose-shared 0.1.11 → 0.1.12

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 (119) hide show
  1. package/package.json +3 -3
  2. package/skills/document/SKILL.md +143 -215
  3. package/skills/document/art-directions/base.css +27 -2
  4. package/skills/document/art-directions/cinematic-depth.css +3 -1
  5. package/skills/document/art-directions/collage-paper.css +5 -1
  6. package/skills/document/art-directions/editorial-cut.css +5 -1
  7. package/skills/document/art-directions/geometric-pop.css +6 -0
  8. package/skills/document/art-directions/gradient-mesh.css +5 -1
  9. package/skills/document/art-directions/illustrated-doodle.css +5 -0
  10. package/skills/document/art-directions/luxury-ornament.css +4 -1
  11. package/skills/document/art-directions/maximalist-blocks.css +6 -0
  12. package/skills/document/art-directions/technical-grid.css +4 -0
  13. package/skills/document/assets/fonts/fonts.json +2 -2
  14. package/skills/document/assets/pdf-fonts/NotoSansSC-PDF-Bold.ttf +0 -0
  15. package/skills/document/assets/pdf-fonts/NotoSansSC-PDF-Regular.ttf +0 -0
  16. package/skills/document/assets/pdf-fonts/OFL.txt +93 -0
  17. package/skills/document/assets/pdf-fonts/README.md +35 -0
  18. package/skills/document/assets/tessdata/LICENSE-Apache-2.0.txt +202 -0
  19. package/skills/document/assets/tessdata/README.md +21 -0
  20. package/skills/document/assets/tessdata/chi_sim.traineddata.gz +0 -0
  21. package/skills/document/assets/tessdata/eng.traineddata.gz +0 -0
  22. package/skills/document/bin/deck-build.cjs +2311 -41
  23. package/skills/document/bin/deck-chart.cjs +762 -0
  24. package/skills/document/bin/deck-plan.cjs +802 -0
  25. package/skills/document/bin/deck-qa.cjs +984 -249
  26. package/skills/document/bin/deck-scenario.cjs +1346 -0
  27. package/skills/document/bin/doc-convert.cjs +130 -39
  28. package/skills/document/bin/doc-fill.cjs +73121 -170
  29. package/skills/document/bin/doc-gen.cjs +625 -68
  30. package/skills/document/bin/doc-run.cjs +162 -24
  31. package/skills/document/bin/doc-scan.cjs +13 -0
  32. package/skills/document/bin/doc-text.cjs +13 -0
  33. package/skills/document/bin/doc-validate.cjs +718 -23
  34. package/skills/document/bin/doc-xml.cjs +9 -5
  35. package/skills/document/bin/docx-annotate.cjs +95 -19
  36. package/skills/document/bin/fontkit.cjs +1 -0
  37. package/skills/document/bin/html2pptx.cjs +1372 -121
  38. package/skills/document/bin/ocr-worker.cjs +1 -0
  39. package/skills/document/bin/ocr.cjs +131 -40
  40. package/skills/document/bin/pdf-form.cjs +674 -177
  41. package/skills/document/bin/pdf-images.cjs +13 -0
  42. package/skills/document/bin/pdf-ops.cjs +634 -199
  43. package/skills/document/bin/pdf-render.cjs +19 -3
  44. package/skills/document/bin/pdf-tables.cjs +13 -0
  45. package/skills/document/bin/pptx-clone.cjs +3 -3
  46. package/skills/document/bin/pptx-slide.cjs +3 -3
  47. package/skills/document/bin/xlsx-query.cjs +192 -61
  48. package/skills/document/bin-reference.md +56 -33
  49. package/skills/document/content-pipeline.md +129 -242
  50. package/skills/document/deck-design.md +355 -638
  51. package/skills/document/layouts/README.md +46 -33
  52. package/skills/document/layouts/capacity.json +1 -0
  53. package/skills/document/layouts/slots.json +1 -1
  54. package/skills/document/layouts/toc-panel.tpl +1 -1
  55. package/skills/document/motion/README.md +3 -2
  56. package/skills/document/office.md +3 -3
  57. package/skills/document/packs/board-brief/backdrop.html +2 -0
  58. package/skills/document/packs/board-brief/pack.css +2 -1
  59. package/skills/document/packs/mckinsey-photo/backdrop.html +2 -0
  60. package/skills/document/packs/mckinsey-photo/pack.css +2 -1
  61. package/skills/document/packs/press-room/backdrop.html +2 -0
  62. package/skills/document/packs/press-room/pack.css +2 -1
  63. package/skills/document/packs/prismatic-3d/backdrop.html +2 -0
  64. package/skills/document/packs/prismatic-3d/pack.css +4 -1
  65. package/skills/document/pdf.md +53 -49
  66. package/skills/document/rewrite.md +241 -308
  67. package/skills/document/scenarios.json +1290 -0
  68. package/skills/document/scenarios.schema.json +103 -0
  69. package/skills/document/styles/README.md +15 -25
  70. package/skills/document/styles/_contract-defaults.css +25 -1
  71. package/skills/document/styles/anthropic/skin.css +6 -3
  72. package/skills/document/styles/apple/skin.css +4 -3
  73. package/skills/document/styles/bistro-warm/backdrop.html +2 -0
  74. package/skills/document/styles/bistro-warm/skin.css +4 -1
  75. package/skills/document/styles/black-gold/skin.css +6 -5
  76. package/skills/document/styles/bold-clash/backdrop.html +2 -0
  77. package/skills/document/styles/bold-clash/skin.css +2 -1
  78. package/skills/document/styles/branding/skin.css +4 -4
  79. package/skills/document/styles/campus-teal/backdrop.html +2 -0
  80. package/skills/document/styles/campus-teal/skin.css +2 -1
  81. package/skills/document/styles/cinereel/skin.css +7 -3
  82. package/skills/document/styles/clinic-care/backdrop.html +2 -0
  83. package/skills/document/styles/clinic-care/skin.css +2 -1
  84. package/skills/document/styles/cool/skin.css +6 -5
  85. package/skills/document/styles/dark-glass/backdrop.html +2 -0
  86. package/skills/document/styles/dark-glass/skin.css +2 -1
  87. package/skills/document/styles/gov-brief/backdrop.html +2 -0
  88. package/skills/document/styles/gov-brief/skin.css +2 -1
  89. package/skills/document/styles/grid-tech/backdrop.html +2 -0
  90. package/skills/document/styles/grid-tech/skin.css +2 -1
  91. package/skills/document/styles/hand-drawn/skin.css +3 -3
  92. package/skills/document/styles/ivory-gold/skin.css +7 -6
  93. package/skills/document/styles/legal-slate/backdrop.html +2 -0
  94. package/skills/document/styles/legal-slate/skin.css +2 -1
  95. package/skills/document/styles/magazine-ink/skin.css +4 -3
  96. package/skills/document/styles/mckinsey/skin.css +2 -1
  97. package/skills/document/styles/memphis/backdrop.html +2 -0
  98. package/skills/document/styles/memphis/skin.css +2 -1
  99. package/skills/document/styles/mono/skin.css +6 -2
  100. package/skills/document/styles/neon-cyber/backdrop.html +2 -0
  101. package/skills/document/styles/neon-cyber/skin.css +2 -1
  102. package/skills/document/styles/olive-gold/skin.css +7 -6
  103. package/skills/document/styles/organic-earth/backdrop.html +2 -0
  104. package/skills/document/styles/organic-earth/skin.css +2 -1
  105. package/skills/document/styles/pastel-saas/backdrop.html +2 -0
  106. package/skills/document/styles/pastel-saas/skin.css +2 -1
  107. package/skills/document/styles/portfolio-noir/backdrop.html +2 -0
  108. package/skills/document/styles/portfolio-noir/skin.css +2 -1
  109. package/skills/document/styles/retro-press/backdrop.html +2 -0
  110. package/skills/document/styles/retro-press/skin.css +2 -1
  111. package/skills/document/styles/sport-arena/backdrop.html +2 -0
  112. package/skills/document/styles/sport-arena/skin.css +4 -1
  113. package/skills/document/styles/swiss-grid/skin.css +10 -5
  114. package/skills/document/styles/wedding-blush/backdrop.html +2 -0
  115. package/skills/document/styles/wedding-blush/skin.css +4 -1
  116. package/skills/document/theme-system.md +85 -77
  117. package/skills/document/themes/README.md +7 -5
  118. package/skills/document/vision.md +8 -5
  119. package/skills/document/xlsx.md +15 -25
@@ -12788,14 +12788,14 @@ var require_decode = __commonJS({
12788
12788
  var LZWStream_1 = tslib_1.__importDefault(require_LZWStream());
12789
12789
  var RunLengthStream_1 = tslib_1.__importDefault(require_RunLengthStream());
12790
12790
  var Stream_1 = tslib_1.__importDefault(require_Stream());
12791
- var decodeStream = function(stream, encoding, params) {
12791
+ var decodeStream = function(stream, encoding, params2) {
12792
12792
  if (encoding === PDFName_1.default.of("FlateDecode")) {
12793
12793
  return new FlateStream_1.default(stream);
12794
12794
  }
12795
12795
  if (encoding === PDFName_1.default.of("LZWDecode")) {
12796
12796
  var earlyChange = 1;
12797
- if (params instanceof PDFDict_1.default) {
12798
- var EarlyChange = params.lookup(PDFName_1.default.of("EarlyChange"));
12797
+ if (params2 instanceof PDFDict_1.default) {
12798
+ var EarlyChange = params2.lookup(PDFName_1.default.of("EarlyChange"));
12799
12799
  if (EarlyChange instanceof PDFNumber_1.default) {
12800
12800
  earlyChange = EarlyChange.asNumber();
12801
12801
  }
@@ -16677,11 +16677,11 @@ var require_svgPath = __commonJS({
16677
16677
  var args = [];
16678
16678
  var curArg = "";
16679
16679
  var foundDecimal = false;
16680
- var params = 0;
16680
+ var params2 = 0;
16681
16681
  for (var _i = 0, path_1 = path; _i < path_1.length; _i++) {
16682
16682
  var c = path_1[_i];
16683
16683
  if (parameters.has(c)) {
16684
- params = parameters.get(c);
16684
+ params2 = parameters.get(c);
16685
16685
  if (cmd) {
16686
16686
  if (curArg.length > 0) {
16687
16687
  args[args.length] = +curArg;
@@ -16696,7 +16696,7 @@ var require_svgPath = __commonJS({
16696
16696
  if (curArg.length === 0) {
16697
16697
  continue;
16698
16698
  }
16699
- if (args.length === params) {
16699
+ if (args.length === params2) {
16700
16700
  ret[ret.length] = { cmd, args };
16701
16701
  args = [+curArg];
16702
16702
  if (cmd === "M") {
@@ -16718,7 +16718,7 @@ var require_svgPath = __commonJS({
16718
16718
  }
16719
16719
  }
16720
16720
  if (curArg.length > 0) {
16721
- if (args.length === params) {
16721
+ if (args.length === params2) {
16722
16722
  ret[ret.length] = { cmd, args };
16723
16723
  args = [+curArg];
16724
16724
  if (cmd === "M") {
@@ -22296,7 +22296,7 @@ var require_BufferList = __commonJS({
22296
22296
  this.head = this.tail = null;
22297
22297
  this.length = 0;
22298
22298
  };
22299
- BufferList.prototype.join = function join5(s) {
22299
+ BufferList.prototype.join = function join7(s) {
22300
22300
  if (this.length === 0) return "";
22301
22301
  var p = this.head;
22302
22302
  var ret = "" + p.data;
@@ -29778,20 +29778,20 @@ var require_pptxgen_cjs = __commonJS({
29778
29778
  var Slide = (
29779
29779
  /** @class */
29780
29780
  (function() {
29781
- function Slide2(params) {
29781
+ function Slide2(params2) {
29782
29782
  var _a;
29783
- this.addSlide = params.addSlide;
29784
- this.getSlide = params.getSlide;
29785
- this._name = "Slide ".concat(params.slideNumber);
29786
- this._presLayout = params.presLayout;
29787
- this._rId = params.slideRId;
29783
+ this.addSlide = params2.addSlide;
29784
+ this.getSlide = params2.getSlide;
29785
+ this._name = "Slide ".concat(params2.slideNumber);
29786
+ this._presLayout = params2.presLayout;
29787
+ this._rId = params2.slideRId;
29788
29788
  this._rels = [];
29789
29789
  this._relsChart = [];
29790
29790
  this._relsMedia = [];
29791
- this._setSlideNum = params.setSlideNum;
29792
- this._slideId = params.slideId;
29793
- this._slideLayout = params.slideLayout || null;
29794
- this._slideNum = params.slideNumber;
29791
+ this._setSlideNum = params2.setSlideNum;
29792
+ this._slideId = params2.slideId;
29793
+ this._slideLayout = params2.slideLayout || null;
29794
+ this._slideNum = params2.slideNumber;
29795
29795
  this._slideObjects = [];
29796
29796
  this._slideNumberProps = ((_a = this._slideLayout) === null || _a === void 0 ? void 0 : _a._slideNumberProps) ? this._slideLayout._slideNumberProps : null;
29797
29797
  }
@@ -33202,11 +33202,399 @@ var require_pptxgen_cjs = __commonJS({
33202
33202
  }
33203
33203
  });
33204
33204
 
33205
+ // packages/core/dist/document/pdf-encrypted.js
33206
+ var ENCRYPTED_PDF_MESSAGE, PdfEncryptedError;
33207
+ var init_pdf_encrypted = __esm({
33208
+ "packages/core/dist/document/pdf-encrypted.js"() {
33209
+ "use strict";
33210
+ ENCRYPTED_PDF_MESSAGE = "PDF is password-protected (encrypted); decrypt first: pdf-ops decrypt <in> <password> <out>";
33211
+ PdfEncryptedError = class extends Error {
33212
+ constructor() {
33213
+ super(ENCRYPTED_PDF_MESSAGE);
33214
+ this.name = "PdfEncryptedError";
33215
+ }
33216
+ };
33217
+ }
33218
+ });
33219
+
33220
+ // packages/core/dist/document/pdf-extract.js
33221
+ var pdf_extract_exports = {};
33222
+ __export(pdf_extract_exports, {
33223
+ analyzePdfDocument: () => analyzePdfDocument,
33224
+ countVectorOperators: () => countVectorOperators,
33225
+ decryptPdf: () => decryptPdf,
33226
+ encryptPdf: () => encryptPdf,
33227
+ extractPdfImages: () => extractPdfImages,
33228
+ extractPdfTables: () => extractPdfTables,
33229
+ extractPdfText: () => extractPdfText,
33230
+ extractPdfTextLines: () => extractPdfTextLines,
33231
+ pdfNeedsPassword: () => pdfNeedsPassword,
33232
+ renderPdfPages: () => renderPdfPages,
33233
+ scanPdfSignals: () => scanPdfSignals,
33234
+ textMathHint: () => textMathHint,
33235
+ textTableHint: () => textTableHint
33236
+ });
33237
+ function loadMupdf() {
33238
+ if (!_mupdf) {
33239
+ const deps = process.env.TALOS_SKILL_DEPS;
33240
+ const spec = deps ? (0, import_node_url.pathToFileURL)((0, import_node_path2.join)(deps, "mupdf", "dist", "mupdf.js")).href : "mupdf";
33241
+ _mupdf = import(spec);
33242
+ }
33243
+ return _mupdf;
33244
+ }
33245
+ function textMathHint(text) {
33246
+ if (!text)
33247
+ return false;
33248
+ const hits = (text.match(MATH_CHARS) || []).length;
33249
+ return hits >= 3 || hits / Math.max(text.length, 1) > 0.02;
33250
+ }
33251
+ function textTableHint(text) {
33252
+ if (!text)
33253
+ return false;
33254
+ const gaps = (text.match(/ {2,}|\t/g) || []).length;
33255
+ return gaps >= 6;
33256
+ }
33257
+ async function openDoc(bytes) {
33258
+ const mupdf = await loadMupdf();
33259
+ const doc = mupdf.Document.openDocument(bytes, "application/pdf");
33260
+ if (doc.needsPassword())
33261
+ throw new PdfEncryptedError();
33262
+ if (doc.countPages() === 0) {
33263
+ throw new Error("PDF has no readable pages (corrupt, encrypted, or empty)");
33264
+ }
33265
+ return { mupdf, doc };
33266
+ }
33267
+ async function analyzePdfDocument(bytes, options = {}) {
33268
+ const wantText = options.text !== false;
33269
+ const wantSignals = options.signals === true;
33270
+ const { doc } = await openDoc(bytes);
33271
+ const pageCount = doc.countPages();
33272
+ const signals = [];
33273
+ let out = "";
33274
+ for (let i = 0; i < pageCount; i++) {
33275
+ const page = doc.loadPage(i);
33276
+ if (wantText) {
33277
+ out += `
33278
+ [page ${i + 1}]
33279
+ ${page.toStructuredText().asText()}`;
33280
+ }
33281
+ if (wantSignals) {
33282
+ signals.push(pageSignalsFrom(page, i + 1));
33283
+ }
33284
+ }
33285
+ return { text: wantText ? out.trim() : "", pageCount, signals };
33286
+ }
33287
+ async function extractPdfText(bytes) {
33288
+ const { text, pageCount } = await analyzePdfDocument(bytes, { text: true });
33289
+ return { text, pageCount };
33290
+ }
33291
+ function countVectorOperators(content) {
33292
+ let ops = 0;
33293
+ let curves = 0;
33294
+ let fills = 0;
33295
+ let i = 0;
33296
+ const n = content.length;
33297
+ while (i < n) {
33298
+ const ch = content[i];
33299
+ if (ch === "(") {
33300
+ let depth = 1;
33301
+ i++;
33302
+ while (i < n && depth > 0) {
33303
+ if (content[i] === "\\")
33304
+ i++;
33305
+ else if (content[i] === "(")
33306
+ depth++;
33307
+ else if (content[i] === ")")
33308
+ depth--;
33309
+ i++;
33310
+ }
33311
+ continue;
33312
+ }
33313
+ if (ch === "<" && content[i + 1] !== "<") {
33314
+ while (i < n && content[i] !== ">")
33315
+ i++;
33316
+ i++;
33317
+ continue;
33318
+ }
33319
+ if (ch === "I" && content[i + 1] === "D" && isDelimiter(content[i + 2])) {
33320
+ const end = content.indexOf("EI", i + 2);
33321
+ i = end === -1 ? n : end + 2;
33322
+ continue;
33323
+ }
33324
+ if (!isDelimiter(ch)) {
33325
+ const start = i;
33326
+ while (i < n && !isDelimiter(content[i]))
33327
+ i++;
33328
+ const token = content.slice(start, i);
33329
+ switch (token) {
33330
+ case "m":
33331
+ case "l":
33332
+ case "re":
33333
+ ops++;
33334
+ break;
33335
+ case "c":
33336
+ case "v":
33337
+ case "y":
33338
+ ops++;
33339
+ curves++;
33340
+ break;
33341
+ case "f":
33342
+ case "F":
33343
+ case "f*":
33344
+ fills++;
33345
+ break;
33346
+ case "B":
33347
+ case "B*":
33348
+ case "b":
33349
+ case "b*":
33350
+ fills++;
33351
+ break;
33352
+ default:
33353
+ break;
33354
+ }
33355
+ continue;
33356
+ }
33357
+ i++;
33358
+ }
33359
+ return { ops, curves, fills };
33360
+ }
33361
+ function isDelimiter(ch) {
33362
+ return ch === void 0 || ch === " " || ch === "\n" || ch === "\r" || ch === " " || ch === "\f" || ch === "\0" || ch === "[" || ch === "]" || ch === "/" || ch === "{" || ch === "}";
33363
+ }
33364
+ function pageContentStream(page) {
33365
+ try {
33366
+ const obj = page.getObject?.();
33367
+ const contents = obj?.get?.("Contents");
33368
+ if (!contents)
33369
+ return "";
33370
+ const streams = contents.isArray?.() && contents.get ? Array.from({ length: contents.length ?? 0 }, (_, i) => contents.get(i)) : [contents];
33371
+ let out = "";
33372
+ for (const stream of streams) {
33373
+ const buf = stream?.readStream?.();
33374
+ const bytes = buf?.asUint8Array?.();
33375
+ if (bytes)
33376
+ out += Buffer.from(bytes).toString("latin1");
33377
+ }
33378
+ return out;
33379
+ } catch {
33380
+ return "";
33381
+ }
33382
+ }
33383
+ function pageSignalsFrom(page, pageNumber) {
33384
+ const json = JSON.parse(page.toStructuredText("preserve-images").asJSON());
33385
+ const blocks = json.blocks ?? [];
33386
+ const text = blocks.flatMap((b) => (b.lines ?? []).map((l) => l.text ?? "")).join(" ");
33387
+ const [x0, y0, x1, y1] = page.getBounds();
33388
+ const pageArea = Math.max((x1 - x0) * (y1 - y0), 1);
33389
+ const imgArea = blocks.filter((b) => b.type === "image").reduce((a, b) => a + Math.max((b.bbox?.w ?? 0) * (b.bbox?.h ?? 0), 0), 0);
33390
+ const vector = countVectorOperators(pageContentStream(page));
33391
+ return {
33392
+ page: pageNumber,
33393
+ textChars: text.trim().length,
33394
+ imageCoverage: Math.min(imgArea / pageArea, 1),
33395
+ vectorOps: vector.ops,
33396
+ vectorCurves: vector.curves,
33397
+ vectorFills: vector.fills,
33398
+ mathHint: textMathHint(text),
33399
+ tableHint: textTableHint(text)
33400
+ };
33401
+ }
33402
+ async function scanPdfSignals(bytes) {
33403
+ const { signals } = await analyzePdfDocument(bytes, { text: false, signals: true });
33404
+ return signals;
33405
+ }
33406
+ function median(nums) {
33407
+ if (!nums.length)
33408
+ return 0;
33409
+ const s = [...nums].sort((a, b) => a - b);
33410
+ return s[Math.floor(s.length / 2)];
33411
+ }
33412
+ async function extractPdfTables(bytes, pageNum, minColumns = 2) {
33413
+ const { doc } = await openDoc(bytes);
33414
+ const total = doc.countPages();
33415
+ const from = pageNum ? pageNum : 1;
33416
+ const to = pageNum ? pageNum : total;
33417
+ const out = [];
33418
+ for (let p = from; p <= to; p++) {
33419
+ if (p < 1 || p > total)
33420
+ continue;
33421
+ const json = JSON.parse(doc.loadPage(p - 1).toStructuredText().asJSON());
33422
+ const cells = [];
33423
+ for (const b of json.blocks ?? []) {
33424
+ if (b.type === "image")
33425
+ continue;
33426
+ for (const l of b.lines ?? []) {
33427
+ const text = (l.text ?? "").trim();
33428
+ if (!text)
33429
+ continue;
33430
+ cells.push({ text, x: l.bbox?.x ?? 0, y: l.bbox?.y ?? 0, h: l.bbox?.h ?? 12 });
33431
+ }
33432
+ }
33433
+ if (cells.length < 2)
33434
+ continue;
33435
+ const medH = median(cells.map((c) => c.h)) || 12;
33436
+ const rowTol = Math.max(4, medH * 0.6);
33437
+ const colTol = Math.max(6, medH * 0.9);
33438
+ const byY = [...cells].sort((a, b) => a.y - b.y);
33439
+ const rowGroups = [];
33440
+ let rowRef = Number.NEGATIVE_INFINITY;
33441
+ for (const c of byY) {
33442
+ if (c.y - rowRef > rowTol) {
33443
+ rowGroups.push([]);
33444
+ rowRef = c.y;
33445
+ }
33446
+ rowGroups[rowGroups.length - 1].push(c);
33447
+ }
33448
+ const colRefs = [];
33449
+ for (const x of cells.map((c) => c.x).sort((a, b) => a - b)) {
33450
+ if (!colRefs.length || x - colRefs[colRefs.length - 1] > colTol)
33451
+ colRefs.push(x);
33452
+ }
33453
+ const colIndex = (x) => {
33454
+ let best = 0, bd = Infinity;
33455
+ colRefs.forEach((r, i) => {
33456
+ const d = Math.abs(x - r);
33457
+ if (d < bd) {
33458
+ bd = d;
33459
+ best = i;
33460
+ }
33461
+ });
33462
+ return best;
33463
+ };
33464
+ const rows = [];
33465
+ for (const group of rowGroups) {
33466
+ const arr = Array(colRefs.length).fill("");
33467
+ for (const c of group.sort((a, b) => a.x - b.x)) {
33468
+ const i = colIndex(c.x);
33469
+ arr[i] = arr[i] ? `${arr[i]} ${c.text}` : c.text;
33470
+ }
33471
+ if (arr.filter((v) => v).length >= minColumns)
33472
+ rows.push(arr);
33473
+ }
33474
+ if (rows.length)
33475
+ out.push({ page: p, rows });
33476
+ }
33477
+ return out;
33478
+ }
33479
+ async function extractPdfTextLines(bytes) {
33480
+ const { doc } = await openDoc(bytes);
33481
+ const out = [];
33482
+ for (let p = 1; p <= doc.countPages(); p++) {
33483
+ const page = doc.loadPage(p - 1);
33484
+ const [x0, y0, x1, y1] = page.getBounds();
33485
+ const json = JSON.parse(page.toStructuredText().asJSON());
33486
+ const lines = [];
33487
+ for (const b of json.blocks ?? []) {
33488
+ if (b.type === "image")
33489
+ continue;
33490
+ for (const l of b.lines ?? []) {
33491
+ const text = l.text ?? "";
33492
+ if (!text.trim() || !l.bbox)
33493
+ continue;
33494
+ lines.push({
33495
+ x: l.bbox.x ?? 0,
33496
+ y: l.bbox.y ?? 0,
33497
+ w: l.bbox.w ?? 0,
33498
+ h: l.bbox.h ?? 0,
33499
+ size: l.font?.size ?? 12,
33500
+ text
33501
+ });
33502
+ }
33503
+ }
33504
+ out.push({ page: p, width: x1 - x0, height: y1 - y0, lines });
33505
+ }
33506
+ return out;
33507
+ }
33508
+ async function renderPdfPages(bytes, pages, scale = 2, maxEdge) {
33509
+ const { mupdf, doc } = await openDoc(bytes);
33510
+ const total = doc.countPages();
33511
+ const wanted = pages === "all" ? Array.from({ length: total }, (_, i) => i + 1) : pages;
33512
+ const out = [];
33513
+ for (const p of wanted) {
33514
+ if (p < 1 || p > total)
33515
+ continue;
33516
+ const page = doc.loadPage(p - 1);
33517
+ const [x0, y0, x1, y1] = page.getBounds();
33518
+ const longest = Math.max(x1 - x0, y1 - y0);
33519
+ const s = maxEdge && longest > 0 ? Math.min(scale, maxEdge / longest) : scale;
33520
+ const pix = page.toPixmap(mupdf.Matrix.scale(s, s), mupdf.ColorSpace.DeviceRGB, false, true);
33521
+ out.push({ page: p, png: pix.asPNG() });
33522
+ }
33523
+ return out;
33524
+ }
33525
+ async function extractPdfImages(bytes, pages) {
33526
+ const { doc } = await openDoc(bytes);
33527
+ const total = doc.countPages();
33528
+ const wanted = pages && pages.length ? pages : Array.from({ length: total }, (_, i) => i + 1);
33529
+ const images = [];
33530
+ let skipped = 0;
33531
+ for (const p of wanted) {
33532
+ if (p < 1 || p > total)
33533
+ continue;
33534
+ const stext = doc.loadPage(p - 1).toStructuredText("preserve-images");
33535
+ let index = 0;
33536
+ stext.walk({
33537
+ onImageBlock(bbox, _matrix, image) {
33538
+ const slot = index++;
33539
+ try {
33540
+ const png = image.toPixmap().asPNG();
33541
+ images.push({
33542
+ page: p,
33543
+ index: slot,
33544
+ bbox: [bbox[0], bbox[1], bbox[2], bbox[3]],
33545
+ width: image.getWidth(),
33546
+ height: image.getHeight(),
33547
+ png
33548
+ });
33549
+ } catch {
33550
+ skipped++;
33551
+ }
33552
+ }
33553
+ });
33554
+ }
33555
+ return { images, skipped };
33556
+ }
33557
+ async function encryptPdf(bytes, userPassword, ownerPassword) {
33558
+ if (!userPassword)
33559
+ throw new Error("a non-empty user password is required");
33560
+ const { doc } = await openDoc(bytes);
33561
+ const opts = `encrypt=aes-256,user-password=${userPassword},owner-password=${ownerPassword || userPassword}`;
33562
+ return doc.saveToBuffer(opts).asUint8Array();
33563
+ }
33564
+ async function pdfNeedsPassword(bytes) {
33565
+ const mupdf = await loadMupdf();
33566
+ const doc = mupdf.Document.openDocument(bytes, "application/pdf");
33567
+ return doc.needsPassword();
33568
+ }
33569
+ async function decryptPdf(bytes, password) {
33570
+ const mupdf = await loadMupdf();
33571
+ const doc = mupdf.Document.openDocument(bytes, "application/pdf");
33572
+ if (doc.needsPassword()) {
33573
+ if (doc.authenticatePassword(password) === 0)
33574
+ throw new Error("wrong password");
33575
+ }
33576
+ if (doc.countPages() === 0)
33577
+ throw new Error("PDF has no readable pages (corrupt or empty)");
33578
+ return doc.saveToBuffer("encrypt=none").asUint8Array();
33579
+ }
33580
+ var import_node_path2, import_node_url, _mupdf, MATH_CHARS;
33581
+ var init_pdf_extract = __esm({
33582
+ "packages/core/dist/document/pdf-extract.js"() {
33583
+ "use strict";
33584
+ import_node_path2 = require("node:path");
33585
+ import_node_url = require("node:url");
33586
+ init_pdf_encrypted();
33587
+ _mupdf = null;
33588
+ MATH_CHARS = /[=∑∫√≤≥≠±∞πθλμσΩ×÷∂∇→←↔∈∉⊂⊃∀∃]/g;
33589
+ }
33590
+ });
33591
+
33205
33592
  // apps/admin/data/skills.lib/document/bin-src/html2pptx.mjs
33206
33593
  var import_promises3 = require("node:fs/promises");
33594
+ var import_node_crypto2 = require("node:crypto");
33207
33595
  var import_pdf_lib2 = __toESM(require_cjs(), 1);
33208
- var import_node_fs2 = require("node:fs");
33209
- var import_node_path4 = require("node:path");
33596
+ var import_node_fs3 = require("node:fs");
33597
+ var import_node_path6 = require("node:path");
33210
33598
 
33211
33599
  // packages/core/dist/document/html-pptx.js
33212
33600
  var import_jszip4 = __toESM(require_lib4(), 1);
@@ -33373,7 +33761,31 @@ async function injectTransitions(buf, perSlide) {
33373
33761
  // packages/core/dist/document/pptx-finalize.js
33374
33762
  var import_jszip3 = __toESM(require_lib4(), 1);
33375
33763
  async function finalizePptx(bytes) {
33376
- return stripDanglingOverrides(await normalizeChartParts(bytes));
33764
+ return stripDanglingOverrides(await dedupeParagraphProps(await normalizeChartParts(bytes)));
33765
+ }
33766
+ function dedupeParagraphPropsXml(xml) {
33767
+ return xml.replace(/<a:p>([\s\S]*?)<\/a:p>/g, (para, inner) => {
33768
+ const first = inner.search(/<a:(?:r|br|fld)\b/);
33769
+ if (first < 0)
33770
+ return para;
33771
+ const tail = inner.slice(first).replace(/<a:pPr\b[^>]*\/>|<a:pPr\b[^>]*>[\s\S]*?<\/a:pPr>/g, "");
33772
+ return `<a:p>${inner.slice(0, first)}${tail}</a:p>`;
33773
+ });
33774
+ }
33775
+ async function dedupeParagraphProps(bytes) {
33776
+ const zip = await import_jszip3.default.loadAsync(Buffer.from(bytes));
33777
+ let changed = false;
33778
+ for (const name of Object.keys(zip.files).filter((n) => /^ppt\/slides\/slide\d+\.xml$/.test(n))) {
33779
+ const xml = await zip.file(name).async("string");
33780
+ const next = dedupeParagraphPropsXml(xml);
33781
+ if (next !== xml) {
33782
+ zip.file(name, next);
33783
+ changed = true;
33784
+ }
33785
+ }
33786
+ if (!changed)
33787
+ return bytes;
33788
+ return new Uint8Array(await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" }));
33377
33789
  }
33378
33790
  async function normalizeChartParts(bytes) {
33379
33791
  const zip = await import_jszip3.default.loadAsync(Buffer.from(bytes));
@@ -33441,6 +33853,50 @@ async function stripDanglingOverrides(bytes) {
33441
33853
  return new Uint8Array(await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" }));
33442
33854
  }
33443
33855
 
33856
+ // packages/core/dist/document/text-slack.js
33857
+ var SLACK_CJK_RE = /[\u3000-\u303F\u3400-\u9FFF\uF900-\uFAFF\uFF00-\uFFEF]/;
33858
+ var PCT = { single: { cjk: 0.22, latin: 0.16 }, multi: { cjk: 0.1, latin: 0.06 } };
33859
+ var FLOOR_EM = { cjk: 1, latin: 0.45 };
33860
+ function params(single, cjk) {
33861
+ const script = cjk ? "cjk" : "latin";
33862
+ return { pct: PCT[single ? "single" : "multi"][script], floor: FLOOR_EM[script] * (single ? 1 : 0.5) };
33863
+ }
33864
+ function textSlack(width, fontSize, single, cjk) {
33865
+ const { pct, floor } = params(single, cjk);
33866
+ return Math.max(width * pct, floor * fontSize);
33867
+ }
33868
+ function unpaddedWidth(padded, fontSize, single, cjk) {
33869
+ const { pct, floor } = params(single, cjk);
33870
+ const floorAbs = floor * fontSize;
33871
+ const viaPct = padded / (1 + pct);
33872
+ return viaPct * pct >= floorAbs ? viaPct : Math.max(0, padded - floorAbs);
33873
+ }
33874
+ var CHART_TEXT_NAME = "talos-chart-text";
33875
+
33876
+ // packages/core/dist/document/deck-chart-svg.js
33877
+ var RAMP = [
33878
+ "var(--series-1, var(--c1, var(--accent)))",
33879
+ "var(--series-2, var(--c2, var(--muted)))",
33880
+ "var(--series-3, var(--c3, var(--title)))",
33881
+ "var(--series-4, var(--c4, var(--body)))"
33882
+ ];
33883
+ var MAX_SERIES = RAMP.length;
33884
+ function chartDataMissing(spec) {
33885
+ if (!spec || typeof spec !== "object" || Array.isArray(spec))
33886
+ return "the chart is not a {type, labels, series} object";
33887
+ const c = spec;
33888
+ const series = Array.isArray(c.series) ? c.series.filter((s) => s && typeof s === "object") : [];
33889
+ const labels = Array.isArray(c.labels) ? c.labels : [];
33890
+ const withValues = series.some((s) => Array.isArray(s.values) && s.values.length > 0);
33891
+ if (c.type === "scatter" && series.some((s) => Array.isArray(s.points) && s.points.length > 0))
33892
+ return null;
33893
+ if (!labels.length)
33894
+ return "the chart has no labels";
33895
+ if (!withValues)
33896
+ return "the chart has no series with values";
33897
+ return null;
33898
+ }
33899
+
33444
33900
  // packages/core/dist/document/html-pptx.js
33445
33901
  var DEFAULT_CHART_RAMP = ["2563EB", "10B981", "F59E0B", "EF4444", "8B5CF6", "06B6D4"];
33446
33902
  var SLIDE_W_IN = 13.333;
@@ -33476,6 +33932,10 @@ function cssAlpha(value) {
33476
33932
  const a = parseFloat(m[1]);
33477
33933
  return Number.isFinite(a) ? Math.max(0, Math.min(1, a)) : 1;
33478
33934
  }
33935
+ function lineAlpha(value) {
33936
+ const a = cssAlpha(value);
33937
+ return a < 1 ? { transparency: Math.round((1 - a) * 100) } : {};
33938
+ }
33479
33939
  function lintHardcodedColors(html) {
33480
33940
  const noSvg = html.replace(/<svg[\s\S]*?<\/svg>/gi, "");
33481
33941
  const seen = /* @__PURE__ */ new Set();
@@ -33497,6 +33957,28 @@ function lintHardcodedColors(html) {
33497
33957
  `${list.length} hardcoded brand color(s) bypass theme vars (e.g. ${list.slice(0, 4).join(", ")}) \u2014 deck won't re-theme with --theme. Route brand colors through var(--accent/--secondary/--title-color/--slide-bg/\u2026, fallback); SVG semantic colors are exempt.`
33498
33958
  ];
33499
33959
  }
33960
+ function withoutPrintNoise(html) {
33961
+ return html.replace(/<svg\b[^>]*>(?:(?!<\/svg>)[\s\S])*?feTurbulence[\s\S]*?<\/svg>/g, "");
33962
+ }
33963
+ var CHART_PLACEHOLDER_LABEL = "\u56FE\u8868\u5360\u4F4D";
33964
+ var decodeAttr = (v) => v.replace(/&#39;|&apos;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
33965
+ function markEmptyCharts(html) {
33966
+ const empty = [];
33967
+ const out = html.replace(/<div\b([^>]*?)\sdata-chart=(['"])([\s\S]*?)\2([^>]*)>\s*<\/div>/g, (full, before, _q, raw, after) => {
33968
+ let spec;
33969
+ try {
33970
+ spec = JSON.parse(decodeAttr(raw));
33971
+ } catch {
33972
+ spec = void 0;
33973
+ }
33974
+ const why = spec === void 0 ? "invalid data-chart JSON" : chartDataMissing(spec);
33975
+ if (!why)
33976
+ return full;
33977
+ empty.push(why);
33978
+ return `<div${before} data-chart-empty="1"${after}><span class="surface-label">${CHART_PLACEHOLDER_LABEL}</span></div>`;
33979
+ });
33980
+ return { html: out, empty };
33981
+ }
33500
33982
  var STRUCTURAL_LAYOUTS = /* @__PURE__ */ new Set(["cover", "toc", "section", "transition", "ending", "title"]);
33501
33983
  var isStructuralLayout = (layout) => STRUCTURAL_LAYOUTS.has(layout) || STRUCTURAL_LAYOUTS.has(layout.split("-")[0]);
33502
33984
  function lintRepeatedLayouts(html) {
@@ -33586,6 +34068,75 @@ function gradFillXml(g) {
33586
34068
  const deg = (((g.angle ?? 180) - 90) % 360 + 360) % 360;
33587
34069
  return `<a:gradFill rotWithShape="1"><a:gsLst>${gs}</a:gsLst><a:lin ang="${Math.round(deg * 6e4)}" scaled="1"/></a:gradFill>`;
33588
34070
  }
34071
+ var KANA_RE = /[぀-ヿㇰ-ㇿヲ-゚]/;
34072
+ var HANGUL_RE = /[ᄀ-ᇿ㄰-㆏가-힯]/;
34073
+ var HAN_RE = /[㐀-䶿一-鿿豈-﫿]/;
34074
+ var SIMPLIFIED_ONLY = "\u4EEC\u8FD9\u4E2A\u6765\u65F6\u4F1A\u8BF4\u5BF9\u8FC7\u8FD8\u5F00\u53D1\u7ECF\u5B66\u70B9\u56FD\u5B9E\u73B0\u957F\u95EE\u9898\u7535\u8BDD\u673A\u4E0E\u4E3A\u4ECE\u5F53\u5E94\u5173\u4E48\u4E1A\u4F53\u4E1C\u8F66\u95E8\u89C1\u65E0\u9875\u8D44\u8BAF\u5C06\u4E13\u533A\u52A8\u4EA7\u8FDB\u6837\u79CD\u8BA9\u8BA4\u8BC6\u8BBE\u8BA1\u52A1\u5458\u62A5\u573A\u7EA7\u603B\u7ED3\u6784\u8BBA\u8D28\u5386\u4E07\u4EBF\u534E\u4E24\u7F51\u7EDC\u6570\u636E\u7EDF\u4F18\u52BF\u4EF7\u8D2D\u4E70\u5356\u9500\u8D39\u8D38\u8FBE\u8FD0\u8425\u5C42\u5E7F\u6807\u89C4\u5212\u9879\u9009\u62E9\u672F\u5907\u73AF\u8282\u6218\u98CE\u9669\u56E2\u961F\u9886\u5BFC\u7EC4\u7EC7\u6237\u5E2E\u5904\u6761\u7EBF\u53D8\u7EA6\u89C2\u4F17\u89C6\u9891\u56FE\u4E66\u8BFB\u5199\u8BED\u7B80\u5355\u6742\u96BE\u70ED\u620F\u5267\u4E50\u6B22\u7231\u4EB2\u5988\u513F\u5C81\u8BB0\u5FC6\u68A6\u98DE\u9E1F\u9C7C\u9A6C\u9F99\u533B\u836F\u7597\u536B\u62A4\u8BC1\u94F6\u94B1\u8D27\u5E01\u6C47\u5151\u6DA8\u76D8\u503A\u6743\u6DA6\u51C0\u989D\u7A0E\u8D26\u7B7E\u7EED\u7EE7\u7EAA\u5F55\u6D4B\u8BD5\u9A8C\u8BC4\u5BA1\u95FB\u8BBF\u8C08\u8BB2\u8BFE\u8BAD\u7EC3\u4E60\u9645\u8054\u8FDE\u8F7D\u56F4\u6C14\u6E29\u6E7F\u706F\u8111\u8F6F\u6863\u7801\u8BCD\u6C49\u5C14\u5E86\u5B81\u82CF\u6E7E\u5385\u697C\u56ED\u9986\u94C1\u949F\u9547\u53BF\u4E61\u519C\u517B\u996D\u996E\u6E10\u72B6\u6001\u786E\u987B\u5934\u8138\u58F0\u54CD\u8BE5\u5417\u8C01\u7ED9\u8FDC\u8FB9\u521A\u53F7\u6362\u663E\u9690\u95ED\u95F4\u804C\u8D23\u8D1F\u62C5\u83B7\u5956\u52B1\u8D5B\u80DC\u8D25\u8F93\u8D62\u9012\u4F20\u8F6C\u94FE\u8BA8\u8BAE\u51B3\u5219\u534F\u8C03\u521B\u6EE1\u5F20";
34075
+ var TRADITIONAL_ONLY = "\u5011\u9019\u500B\u4F86\u6642\u6703\u8AAA\u5C0D\u904E\u9084\u958B\u767C\u7D93\u5B78\u9EDE\u570B\u5BE6\u73FE\u9577\u554F\u984C\u96FB\u8A71\u6A5F\u8207\u70BA\u5F9E\u7576\u61C9\u95DC\u9EBC\u696D\u9AD4\u6771\u8ECA\u9580\u898B\u7121\u9801\u8CC7\u8A0A\u5C07\u5C08\u5340\u52D5\u7522\u9032\u6A23\u7A2E\u8B93\u8A8D\u8B58\u8A2D\u8A08\u52D9\u54E1\u5831\u5834\u7D1A\u7E3D\u7D50\u69CB\u8AD6\u8CEA\u6B77\u842C\u5104\u83EF\u5169\u7DB2\u7D61\u6578\u64DA\u7D71\u512A\u52E2\u50F9\u8CFC\u8CB7\u8CE3\u92B7\u8CBB\u8CBF\u9054\u904B\u71DF\u5C64\u5EE3\u6A19\u898F\u5283\u9805\u9078\u64C7\u8853\u5099\u74B0\u7BC0\u6230\u98A8\u96AA\u5718\u968A\u9818\u5C0E\u7D44\u7E54\u6236\u5E6B\u8655\u689D\u7DDA\u8B8A\u7D04\u89C0\u773E\u8996\u983B\u5716\u66F8\u8B80\u5BEB\u8A9E\u7C21\u55AE\u96DC\u96E3\u71B1\u6232\u5287\u6A02\u6B61\u611B\u89AA\u5ABD\u5152\u6B72\u8A18\u61B6\u5922\u98DB\u9CE5\u9B5A\u99AC\u9F8D\u91AB\u85E5\u7642\u885B\u8B77\u8B49\u9280\u9322\u8CA8\u5E63\u532F\u514C\u6F32\u76E4\u50B5\u6B0A\u6F64\u6DE8\u984D\u7A05\u8CEC\u7C3D\u7E8C\u7E7C\u7D00\u9304\u6E2C\u8A66\u9A57\u8A55\u5BE9\u805E\u8A2A\u8AC7\u8B1B\u8AB2\u8A13\u7DF4\u7FD2\u969B\u806F\u9023\u8F09\u570D\u6C23\u6EAB\u6FD5\u71C8\u8166\u8EDF\u6A94\u78BC\u8A5E\u6F22\u723E\u6176\u5BE7\u8607\u7063\u5EF3\u6A13\u5712\u9928\u9435\u9418\u93AE\u7E23\u9109\u8FB2\u990A\u98EF\u98F2\u6F38\u72C0\u614B\u78BA\u9808\u982D\u81C9\u8072\u97FF\u8A72\u55CE\u8AB0\u7D66\u9060\u908A\u525B\u865F\u63DB\u986F\u96B1\u9589\u9593\u8077\u8CAC\u8CA0\u64D4\u7372\u734E\u52F5\u8CFD\u52DD\u6557\u8F38\u8D0F\u905E\u50B3\u8F49\u93C8\u8A0E\u8B70\u6C7A\u5247\u5354\u8ABF\u5275\u6EFF\u5F35";
34076
+ var SIMPLIFIED_SET = new Set(SIMPLIFIED_ONLY);
34077
+ var TRADITIONAL_SET = new Set(TRADITIONAL_ONLY);
34078
+ function chineseScript(text) {
34079
+ let simp = 0;
34080
+ let trad = 0;
34081
+ for (const ch of text) {
34082
+ if (SIMPLIFIED_SET.has(ch))
34083
+ simp++;
34084
+ else if (TRADITIONAL_SET.has(ch))
34085
+ trad++;
34086
+ }
34087
+ return trad > simp ? "zh-TW" : simp > trad ? "zh-CN" : void 0;
34088
+ }
34089
+ function chineseLangHint(lang) {
34090
+ const l = (lang ?? "").trim().toLowerCase();
34091
+ if (/^zh-(?:hant|tw|hk|mo)\b/.test(l))
34092
+ return "zh-TW";
34093
+ if (/^zh(?:-(?:hans|cn|sg|my)\b|$)/.test(l))
34094
+ return "zh-CN";
34095
+ return void 0;
34096
+ }
34097
+ function cjkLang(text, deckZh = "zh-CN") {
34098
+ if (KANA_RE.test(text))
34099
+ return "ja-JP";
34100
+ if (HANGUL_RE.test(text))
34101
+ return "ko-KR";
34102
+ if (HAN_RE.test(text))
34103
+ return chineseScript(text) ?? deckZh;
34104
+ return void 0;
34105
+ }
34106
+ function deckChineseLang(texts, hint) {
34107
+ const fromHint = chineseLangHint(hint);
34108
+ if (fromHint)
34109
+ return fromHint;
34110
+ let tw = 0;
34111
+ let cn = 0;
34112
+ for (const t of texts) {
34113
+ const v = chineseScript(t);
34114
+ if (v === "zh-TW")
34115
+ tw++;
34116
+ else if (v === "zh-CN")
34117
+ cn++;
34118
+ }
34119
+ return tw > cn ? "zh-TW" : "zh-CN";
34120
+ }
34121
+ var CJK_THEME_EA_FONT = "Microsoft YaHei";
34122
+ var CJK_THEME_EA_FONT_TC = "Microsoft JhengHei";
34123
+ var CJK_THEME_EA_XML = {
34124
+ "zh-CN": `<a:ea typeface="${CJK_THEME_EA_FONT}" panose="020B0503020204020204" pitchFamily="34" charset="-122"/>`,
34125
+ "zh-TW": `<a:ea typeface="${CJK_THEME_EA_FONT_TC}" panose="020B0604030504040204" pitchFamily="34" charset="-120"/>`
34126
+ };
34127
+ async function withCjkThemeFont(buf, langs, deckZh = langs.has("zh-CN") ? "zh-CN" : "zh-TW") {
34128
+ if (!(langs.has("zh-CN") || langs.has("zh-TW")) || langs.has("ja-JP") || langs.has("ko-KR"))
34129
+ return buf;
34130
+ const zip = await import_jszip4.default.loadAsync(buf);
34131
+ const themes = Object.keys(zip.files).filter((p) => /^ppt\/theme\/theme\d+\.xml$/.test(p));
34132
+ for (const path of themes) {
34133
+ const xml = await zip.file(path).async("string");
34134
+ const next = xml.replace(/(<a:(?:major|minor)Font>\s*<a:latin\b[^>]*\/>\s*)<a:ea typeface=""\s*\/>/g, `$1${CJK_THEME_EA_XML[deckZh]}`);
34135
+ if (next !== xml)
34136
+ zip.file(path, next);
34137
+ }
34138
+ return await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
34139
+ }
33589
34140
  async function injectGradients(buf, grads) {
33590
34141
  const zip = await import_jszip4.default.loadAsync(buf);
33591
34142
  for (const path of Object.keys(zip.files).filter((p) => /^ppt\/slides\/slide\d+\.xml$/.test(p))) {
@@ -33603,18 +34154,15 @@ async function injectGradients(buf, grads) {
33603
34154
  }
33604
34155
  return await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
33605
34156
  }
33606
- var CJK_RE = /[ -〿㐀-鿿豈-﫿＀-￯]/;
33607
34157
  function isSingleLineBox(el) {
33608
34158
  const linePx = isFiniteNum(el.lineHeightPx) && el.lineHeightPx > 0 ? el.lineHeightPx : el.fontSizePx * 1.3;
33609
34159
  return el.frame.h <= linePx * 1.5;
33610
34160
  }
33611
34161
  function compensateTextWidth(el, frame) {
33612
34162
  const single = isSingleLineBox(el);
33613
- const cjk = CJK_RE.test(el.text);
34163
+ const cjk = SLACK_CJK_RE.test(el.text);
33614
34164
  const pxToIn = el.frame.w > 0 ? frame.w / el.frame.w : 0;
33615
- const floor = (cjk ? el.fontSizePx : el.fontSizePx * 0.45) * pxToIn * (single ? 1 : 0.5);
33616
- const pct = single ? cjk ? 0.22 : 0.16 : cjk ? 0.1 : 0.06;
33617
- const pad = Math.max(frame.w * pct, floor);
34165
+ const pad = textSlack(frame.w, el.fontSizePx * pxToIn, single, cjk);
33618
34166
  const align = el.align ?? "left";
33619
34167
  let x = frame.x;
33620
34168
  if (align === "right")
@@ -33656,6 +34204,8 @@ async function convertLayoutToPptx(snapshot) {
33656
34204
  const gradients = [];
33657
34205
  const perSlideAnims = [];
33658
34206
  const perSlideTransitions = [];
34207
+ const deckLangs = /* @__PURE__ */ new Set();
34208
+ const deckZh = deckChineseLang(snapshot.slides.flatMap((sl) => sl.elements.flatMap((e) => e.kind === "text" ? [String(e.text ?? "")] : [])), snapshot.lang);
33659
34209
  for (const [slideNo, slideSpec] of snapshot.slides.entries()) {
33660
34210
  const slide = pptx.addSlide();
33661
34211
  const bg = cssColorToHex(slideSpec.background);
@@ -33701,7 +34251,20 @@ async function convertLayoutToPptx(snapshot) {
33701
34251
  break;
33702
34252
  }
33703
34253
  const tf = compensateTextWidth(el, frame);
33704
- slide.addText(text, {
34254
+ const lang = cjkLang(text, deckZh);
34255
+ if (lang)
34256
+ deckLangs.add(lang);
34257
+ const runs = Array.isArray(el.runs) ? el.runs.filter((r) => r && typeof r.text === "string" && r.text) : [];
34258
+ const pt = (px) => Math.round(px * PT_PER_PX * 10) / 10;
34259
+ const body = runs.length > 1 ? runs.map((r) => ({
34260
+ text: r.text,
34261
+ options: {
34262
+ ...cssColorToHex(r.color) ? { color: cssColorToHex(r.color) } : {},
34263
+ ...typeof r.bold === "boolean" ? { bold: r.bold } : {},
34264
+ ...isFiniteNum(r.fontSizePx) && r.fontSizePx > 0 ? { fontSize: pt(r.fontSizePx) } : {}
34265
+ }
34266
+ })) : text;
34267
+ slide.addText(body, {
33705
34268
  x: tf.x,
33706
34269
  y: tf.y,
33707
34270
  w: tf.w,
@@ -33719,16 +34282,20 @@ async function convertLayoutToPptx(snapshot) {
33719
34282
  valign: el.valign ?? (isSingleLineBox(el) ? "middle" : "top"),
33720
34283
  // CJK text: omit fontFace entirely — any specific family (even a CJK
33721
34284
  // one the exporter lacks) makes LibreOffice / Google Slides substitute
33722
- // and drop glyphs. The platform default covers CJK in every renderer.
33723
- // (Tried an allowlisted-CJK-serif exception — STSong renders in
33724
- // --editable but VANISHES in hybrid, the default deck mode; so no CJK
33725
- // font may be pinned. Serif CJK titles are not achievable here.)
34285
+ // and drop glyphs. (Tried an allowlisted-CJK-serif exception — STSong
34286
+ // renders in --editable but VANISHES in hybrid, the default deck mode;
34287
+ // so no CJK font may be pinned. Serif CJK titles are not achievable
34288
+ // here.) The CJK face comes from the theme's East Asian slot instead
34289
+ // (withCjkThemeFont) and the run's `lang` (cjkLang).
33726
34290
  // Latin text: first real family in the stack.
33727
- .../* @__PURE__ */ ((f2) => f2 ? { fontFace: f2 } : {})(CJK_RE.test(text) ? void 0 : pickFontFace(el.fontFamily)),
34291
+ .../* @__PURE__ */ ((f2) => f2 ? { fontFace: f2 } : {})(SLACK_CJK_RE.test(text) ? void 0 : pickFontFace(el.fontFamily)),
34292
+ ...lang ? { lang } : {},
33728
34293
  ...isFiniteNum(el.lineHeightPx) ? { lineSpacing: Math.round(el.lineHeightPx * PT_PER_PX * 10) / 10 } : {},
33729
34294
  // Boxes are browser-measured: kill pptx's own internal padding so
33730
34295
  // the text sits where Chromium put it.
33731
- margin: 0
34296
+ margin: 0,
34297
+ ...isFiniteNum(el.rotateDeg) && el.rotateDeg % 360 !== 0 ? { rotate: el.rotateDeg } : {},
34298
+ ...el.chartText ? { objectName: `${CHART_TEXT_NAME} ${elNo + 1}` } : {}
33732
34299
  });
33733
34300
  recordShape(el);
33734
34301
  break;
@@ -33767,7 +34334,9 @@ async function convertLayoutToPptx(snapshot) {
33767
34334
  h: frame.h,
33768
34335
  ...radius > 0 ? { rectRadius: radius } : {},
33769
34336
  fill: fill ? { color: fill, ...transparency > 0 ? { transparency } : {} } : { type: "none" },
33770
- line: line ? { color: line, width: Math.max(0.25, (el.borderWidthPx ?? 1) * PT_PER_PX) } : { type: "none" },
34337
+ // An rgba hairline (glass cards' rgba(255,255,255,.08)) keeps its alpha —
34338
+ // dropped, it turned into a solid white outline in --editable decks.
34339
+ line: line ? { color: line, width: Math.max(0.25, (el.borderWidthPx ?? 1) * PT_PER_PX), ...lineAlpha(el.borderColor), ...el.borderDash ? { dashType: "dash" } : {} } : { type: "none" },
33771
34340
  ...shadow ? { shadow } : {}
33772
34341
  });
33773
34342
  recordShape(el);
@@ -33796,7 +34365,7 @@ async function convertLayoutToPptx(snapshot) {
33796
34365
  y: frame.y,
33797
34366
  w: frame.w,
33798
34367
  h: frame.h,
33799
- line: { color: cssColorToHex(el.color) ?? "1A1A1A", width: Math.max(0.25, (el.widthPx ?? 1) * PT_PER_PX) }
34368
+ line: { color: cssColorToHex(el.color) ?? "1A1A1A", width: Math.max(0.25, (el.widthPx ?? 1) * PT_PER_PX), ...lineAlpha(el.color) }
33800
34369
  });
33801
34370
  recordShape(el);
33802
34371
  break;
@@ -33848,6 +34417,10 @@ async function convertLayoutToPptx(snapshot) {
33848
34417
  valAxisLineColor: "C9D0DA",
33849
34418
  valGridLine: { color: "E2E8F0", style: "dash", size: 0.5 },
33850
34419
  ...horizontal ? { barDir: "bar" } : {},
34420
+ // `zeroBaseline: true` pins the value axis at 0 (Office would zoom a far-from-zero line).
34421
+ ...spec.zeroBaseline === true && type !== "pie" && type !== "doughnut" ? { valAxisMinVal: 0 } : {},
34422
+ // radar `max` = the outer ring (deck-build passes it through from the spec).
34423
+ ...type === "radar" && Number(spec.max) > 0 ? { valAxisMinVal: 0, valAxisMaxVal: Number(spec.max) } : {},
33851
34424
  // `showValues: true` in the data-chart prints each point's number on
33852
34425
  // the mark (opt-in: on a dense series the labels collide).
33853
34426
  ...spec.showValues === true ? { showValue: true } : {},
@@ -33868,6 +34441,7 @@ async function convertLayoutToPptx(snapshot) {
33868
34441
  let buf = await pptx.write("nodebuffer");
33869
34442
  if (gradients.length)
33870
34443
  buf = await injectGradients(buf, gradients);
34444
+ buf = await withCjkThemeFont(buf, deckLangs, deckZh);
33871
34445
  buf = await injectTransitions(buf, perSlideTransitions);
33872
34446
  buf = await injectAnimations(buf, perSlideAnims);
33873
34447
  return { bytes: await finalizePptx(new Uint8Array(buf)), warnings };
@@ -33890,14 +34464,109 @@ async function imagesToPptx(imagesBase64Png) {
33890
34464
  return finalizePptx(new Uint8Array(buf));
33891
34465
  }
33892
34466
 
34467
+ // packages/core/dist/document/html-pptx-headless.js
34468
+ var import_node_crypto = require("node:crypto");
34469
+
33893
34470
  // packages/core/dist/document/html-pptx-walker.js
33894
34471
  var DOCUMENT_CAPTURE_DENSITY = 2;
34472
+ var CHART_SVG_SELECTOR = "svg.talos-chart-svg";
34473
+ var CHART_TEXT_HIDE_CSS = `${CHART_SVG_SELECTOR} text,${CHART_SVG_SELECTOR} text *{fill:transparent !important}`;
34474
+ var BACKDROP_HIDE_CSS = "*{color:transparent !important;-webkit-text-fill-color:transparent !important;text-shadow:none !important}" + CHART_TEXT_HIDE_CSS;
34475
+ var CAPTURE_HIDE_TEXT_JS = `(() => {
34476
+ if (document.getElementById("__talos_cap_hide")) return true;
34477
+ document.querySelectorAll("svg").forEach((el) => {
34478
+ if (el.parentElement && el.parentElement.closest("svg")) return;
34479
+ el.setAttribute("data-talos-cap-color", el.style.getPropertyValue("color") + "|" + el.style.getPropertyPriority("color"));
34480
+ el.style.setProperty("color", getComputedStyle(el).color, "important");
34481
+ });
34482
+ document.querySelectorAll("section.slide *:not(svg):not(svg *)").forEach((el) => {
34483
+ const cs = getComputedStyle(el);
34484
+ if (cs.webkitBackgroundClip === "text" || cs.backgroundClip === "text") el.setAttribute("data-talos-cap-clip", "");
34485
+ });
34486
+ const s = document.createElement("style");
34487
+ s.id = "__talos_cap_hide";
34488
+ s.textContent = "section.slide :not(svg):not(svg *){color:transparent !important;-webkit-text-fill-color:transparent !important;text-shadow:none !important}" +
34489
+ "[data-talos-cap-clip]{visibility:hidden !important}" + ${JSON.stringify(CHART_TEXT_HIDE_CSS)};
34490
+ document.head.appendChild(s);
34491
+ return true;
34492
+ })()`;
34493
+ var CAPTURE_SHOW_TEXT_JS = `(() => {
34494
+ document.getElementById("__talos_cap_hide")?.remove();
34495
+ document.querySelectorAll("[data-talos-cap-clip]").forEach((el) => el.removeAttribute("data-talos-cap-clip"));
34496
+ document.querySelectorAll("[data-talos-cap-color]").forEach((el) => {
34497
+ const [v, p] = el.getAttribute("data-talos-cap-color").split("|");
34498
+ if (v) el.style.setProperty("color", v, p); else el.style.removeProperty("color");
34499
+ el.removeAttribute("data-talos-cap-color");
34500
+ });
34501
+ return true;
34502
+ })()`;
34503
+ var captureIsolateJs = (capId) => `(() => {
34504
+ const t = document.querySelector('[data-talos-cap-id="${capId.replace(/[^0-9]/g, "")}"]');
34505
+ if (!t) return false;
34506
+ const slide = t.closest("section.slide") || document.body;
34507
+ slide.querySelectorAll("*").forEach((el) => {
34508
+ if (el === t || t.contains(el) || el.contains(t)) return;
34509
+ if (t.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_FOLLOWING) el.setAttribute("data-talos-cap-after", "");
34510
+ });
34511
+ if (!document.getElementById("__talos_cap_after")) {
34512
+ const s = document.createElement("style");
34513
+ s.id = "__talos_cap_after";
34514
+ s.textContent = "[data-talos-cap-after]{visibility:hidden !important}";
34515
+ document.head.appendChild(s);
34516
+ }
34517
+ return true;
34518
+ })()`;
34519
+ var CAPTURE_RELEASE_JS = `(() => {
34520
+ document.querySelectorAll("[data-talos-cap-after]").forEach((el) => el.removeAttribute("data-talos-cap-after"));
34521
+ return true;
34522
+ })()`;
34523
+ var DECOR_LAYER_SELECTOR = ".slide-backdrop";
34524
+ var DECOR_RASTER_MARKERS = ".theme-art-layer, .sig-layer";
34525
+ var decorIsolateJs = (si) => `(() => {
34526
+ const slide = document.querySelectorAll("section.slide")[${Math.max(0, Math.floor(si))}];
34527
+ if (!slide) return false;
34528
+ const layers = [...slide.querySelectorAll("[data-talos-decor-id]")];
34529
+ if (!layers.length) return false;
34530
+ slide.querySelectorAll("*").forEach((el) => {
34531
+ if (layers.some((l) => l === el || l.contains(el))) return;
34532
+ el.setAttribute(layers.some((l) => el.contains(l)) ? "data-talos-decor-wrap" : "data-talos-decor-hide", "");
34533
+ });
34534
+ if (!document.getElementById("__talos_decor_hide")) {
34535
+ const s = document.createElement("style");
34536
+ s.id = "__talos_decor_hide";
34537
+ s.textContent = "[data-talos-decor-hide]{visibility:hidden !important}" +
34538
+ "[data-talos-decor-wrap]{background:none !important;border-color:transparent !important;box-shadow:none !important;outline:none !important}";
34539
+ document.head.appendChild(s);
34540
+ }
34541
+ return true;
34542
+ })()`;
34543
+ var DECOR_RELEASE_JS = `(() => {
34544
+ document.getElementById("__talos_decor_hide")?.remove();
34545
+ document.querySelectorAll("[data-talos-decor-hide]").forEach((el) => el.removeAttribute("data-talos-decor-hide"));
34546
+ document.querySelectorAll("[data-talos-decor-wrap]").forEach((el) => el.removeAttribute("data-talos-decor-wrap"));
34547
+ return true;
34548
+ })()`;
34549
+ function placeDecorPicture(elements, dataUri) {
34550
+ const rest = elements.filter((e) => !e || e.kind !== "decor");
34551
+ return dataUri ? [{ kind: "image", frame: { x: 0, y: 0, w: 1280, h: 720 }, data: dataUri }, ...rest] : rest;
34552
+ }
33895
34553
  var LAYOUT_WALKER_JS = `
33896
34554
  (() => {
33897
34555
  const sections = [...document.querySelectorAll("section.slide")];
33898
34556
  if (!sections.length) return { error: "no <section class=\\"slide\\"> found" };
33899
34557
  const notes = [];
34558
+ // Hybrid/backdrop mode (see header). typeof-guarded: in-app it is a script-level
34559
+ // const (not a window property), headless it is window.__TALOS_BD.
34560
+ const hybrid = typeof __TALOS_BD !== "undefined" && !!__TALOS_BD;
33900
34561
  const color = (c) => (c && c !== "rgba(0, 0, 0, 0)" ? c : undefined);
34562
+ // Alpha of a computed CSS color ("rgb(..)" \u2192 1, "rgba(.., 0)" / "transparent" \u2192 0).
34563
+ const fillAlpha = (c) => {
34564
+ if (!c || c === "transparent") return 0;
34565
+ const m = c.match(/rgba?\\(([^)]*)\\)/);
34566
+ if (!m) return 1;
34567
+ const parts = m[1].split(/[\\s,\\/]+/).filter(Boolean);
34568
+ return parts.length >= 4 ? parseFloat(parts[3]) : 1;
34569
+ };
33901
34570
  const alignOf = (v) => (v === "center" ? "center" : v === "right" || v === "end" ? "right" : "left");
33902
34571
  // Entrance-animation intent: an element (or, for text, its nearest ancestor)
33903
34572
  // carrying data-anim="<effect>" [+ -seq/-start/-delay/-dur]. Emitted as {anim}
@@ -33922,6 +34591,60 @@ var LAYOUT_WALKER_JS = `
33922
34591
  const du = num("data-anim-dur"); if (du !== undefined) o.durMs = du;
33923
34592
  return o;
33924
34593
  };
34594
+ // deck-chart-svg text \u2192 native text elements. One element per <text>; a <tspan>
34595
+ // inside it (legend value, radar score) is a run of that element, so Office lays
34596
+ // the line out as one piece and the anchor holds for all of it. Frame = the
34597
+ // text's rendered box; font size scaled by the svg's viewBox\u2192screen factor;
34598
+ // paint = the computed fill; text-anchor \u2192 alignment (the side that stays put
34599
+ // when the box is widened for Office's metrics). A rotated <text> keeps its
34600
+ // unrotated box (getBBox through the CTM) plus the angle.
34601
+ const ANCHOR_ALIGN = { start: "left", middle: "center", end: "right" };
34602
+ const chartText = (svg, org, out, ea) => {
34603
+ for (const t of svg.querySelectorAll("text")) {
34604
+ const tcs = getComputedStyle(t);
34605
+ if (tcs.display === "none" || tcs.visibility === "hidden" || parseFloat(tcs.opacity) === 0) continue;
34606
+ const ctm = t.getScreenCTM();
34607
+ if (!ctm) continue;
34608
+ const scale = Math.hypot(ctm.a, ctm.b) || 1;
34609
+ const runs = [];
34610
+ for (const n of t.childNodes) {
34611
+ const host = n.nodeType === Node.TEXT_NODE ? t : n.nodeType === Node.ELEMENT_NODE ? n : null;
34612
+ if (!host) continue;
34613
+ const raw = (n.textContent || "").replace(/\\s+/g, " ");
34614
+ if (!raw.trim()) continue;
34615
+ const cs = host === t ? tcs : getComputedStyle(host);
34616
+ if (fillAlpha(cs.fill) === 0) continue;
34617
+ // A tspan pushed off by dx (the gap the generator leaves before a value) reads as a space.
34618
+ const gap = runs.length && host !== t && parseFloat(host.getAttribute("dx")) > 0 ? " " : "";
34619
+ runs.push({ text: gap + raw, color: cs.fill, bold: (parseInt(cs.fontWeight, 10) || 400) >= 600, fontSizePx: parseFloat(cs.fontSize) * scale });
34620
+ }
34621
+ const text = runs.map((r) => r.text).join("").replace(/\\s+/g, " ").trim();
34622
+ if (!text) continue;
34623
+ runs[0].text = runs[0].text.replace(/^\\s+/, "");
34624
+ runs[runs.length - 1].text = runs[runs.length - 1].text.replace(/\\s+$/, "");
34625
+ const deg = (Math.atan2(ctm.b, ctm.a) * 180) / Math.PI;
34626
+ let frame;
34627
+ if (Math.abs(deg) > 0.5) {
34628
+ const b = t.getBBox();
34629
+ const c = new DOMPoint(b.x + b.width / 2, b.y + b.height / 2).matrixTransform(ctm);
34630
+ const w = b.width * scale, h = b.height * scale;
34631
+ frame = { x: c.x - org.left - w / 2, y: c.y - org.top - h / 2, w, h };
34632
+ } else {
34633
+ const r = t.getBoundingClientRect();
34634
+ if (!r.width || !r.height) continue;
34635
+ frame = { x: r.left - org.left, y: r.top - org.top, w: r.width, h: r.height };
34636
+ }
34637
+ out.push({
34638
+ kind: "text", frame, text,
34639
+ fontSizePx: Math.max(...runs.map((r) => r.fontSizePx)), color: runs[0].color, fontFamily: tcs.fontFamily,
34640
+ bold: runs.every((r) => r.bold), italic: tcs.fontStyle.includes("italic"),
34641
+ align: ANCHOR_ALIGN[tcs.textAnchor] || "left", chartText: true,
34642
+ ...(runs.length > 1 ? { runs } : {}),
34643
+ ...(Math.abs(deg) > 0.5 ? { rotateDeg: Math.round(((deg % 360) + 360) % 360 * 10) / 10 } : {}),
34644
+ ...(ea ? { anim: ea } : {}),
34645
+ });
34646
+ }
34647
+ };
33925
34648
  const slides = sections.map((sec, si) => {
33926
34649
  const org = sec.getBoundingClientRect();
33927
34650
  const rel = (r) => ({ x: r.left - org.left, y: r.top - org.top, w: r.width, h: r.height });
@@ -33949,6 +34672,12 @@ var LAYOUT_WALKER_JS = `
33949
34672
  (cs.webkitBackgroundClip === "text" || cs.backgroundClip === "text" ||
33950
34673
  cs.getPropertyValue("--talos-bake").trim() === "1" ||
33951
34674
  (child.parentElement.closest && child.parentElement.closest("[data-bake]")))) continue;
34675
+ // Text with no visible fill is not text a reader sees as text: outline
34676
+ // display type (color/-webkit-text-fill-color: transparent plus a
34677
+ // -webkit-text-stroke) \u2014 section's ghost chapter numeral. In hybrid the
34678
+ // stroke is already in the backdrop; emitted as native text the
34679
+ // transparency was lost and PowerPoint drew it SOLID across the slide.
34680
+ if (fillAlpha(cs.webkitTextFillColor || cs.color) === 0) continue;
33952
34681
  const lh = parseFloat(cs.lineHeight);
33953
34682
  const ta = animOf(child.parentElement);
33954
34683
  elements.push({
@@ -33970,6 +34699,15 @@ var LAYOUT_WALKER_JS = `
33970
34699
  // SVG elements keep their original case ("svg"), unlike HTML ("DIV") \u2014
33971
34700
  // normalize so the tag checks below (esp. SVG) match either way.
33972
34701
  const tag = child.tagName.toUpperCase();
34702
+ // Native mode: a decoration layer carrying art-direction / signature markup
34703
+ // is one full-slide picture, not native shapes (DECOR_LAYER_SELECTOR above).
34704
+ // Hybrid shoots it into the backdrop with everything else, so it walks on.
34705
+ if (!hybrid && child.matches && child.matches(${JSON.stringify(DECOR_LAYER_SELECTOR)}) && child.querySelector(${JSON.stringify(DECOR_RASTER_MARKERS)})) {
34706
+ const decorId = String(window.__TALOS_DECOR_N = (window.__TALOS_DECOR_N || 0) + 1);
34707
+ child.setAttribute("data-talos-decor-id", decorId);
34708
+ elements.push({ kind: "decor", frame: rel(r), decorId });
34709
+ continue;
34710
+ }
33973
34711
  // data-chart placeholder \u2192 a native pptx chart (Stage B), not a raster.
33974
34712
  const dc = child.getAttribute ? child.getAttribute("data-chart") : null;
33975
34713
  if (dc) { if (r.width && r.height) elements.push({ kind: "chart", frame: rel(r), chart: dc, ...(ea ? { anim: ea } : {}) }); continue; }
@@ -33986,7 +34724,20 @@ var LAYOUT_WALKER_JS = `
33986
34724
  if (tag === "SVG" || tag === "CANVAS") {
33987
34725
  // Marked for element-level rasterization on the Node side (below):
33988
34726
  // only this box becomes a PNG; the rest of the slide stays native text.
33989
- if (r.width && r.height) elements.push({ kind: "capture", frame: rel(r), tag: tag.toLowerCase() });
34727
+ // Native mode tags it so the capture can hide what is emitted after it
34728
+ // (captureIsolateJs). Hybrid never captures per element \u2014 and its backdrop
34729
+ // cache key reads svg outerHTML, so it must not see the attribute.
34730
+ if (r.width && r.height) {
34731
+ let capId;
34732
+ if (!window.__TALOS_BD) {
34733
+ capId = String(window.__TALOS_CAP_N = (window.__TALOS_CAP_N || 0) + 1);
34734
+ child.setAttribute("data-talos-cap-id", capId);
34735
+ }
34736
+ elements.push({ kind: "capture", frame: rel(r), tag: tag.toLowerCase(), ...(capId ? { capId } : {}) });
34737
+ // A deck-chart-svg chart's text goes native ON TOP of its picture (both
34738
+ // modes hide it while shooting \u2014 CHART_TEXT_HIDE_CSS).
34739
+ if (tag === "SVG" && child.matches(${JSON.stringify(CHART_SVG_SELECTOR)})) chartText(child, org, elements, ea);
34740
+ }
33990
34741
  continue;
33991
34742
  }
33992
34743
  if (tag === "VIDEO") {
@@ -33996,13 +34747,19 @@ var LAYOUT_WALKER_JS = `
33996
34747
  const fill = color(cs.backgroundColor);
33997
34748
  const bw = parseFloat(cs.borderTopWidth);
33998
34749
  const borderColor = bw > 0 && cs.borderTopStyle !== "none" ? color(cs.borderTopColor) : undefined;
33999
- // box-shadow \u2192 native pptx outer shadow (first, non-inset shadow only;
34000
- // computed serialization is "color offX offY blur spread").
34750
+ // box-shadow \u2192 native pptx outer shadow: the FIRST NON-INSET layer (computed
34751
+ // serialization is "color offX offY blur spread[ inset]", layers comma-joined).
34752
+ // An inset layer beside it (cool/apple cards' 1px top highlight) is dropped \u2014
34753
+ // it used to void the whole list, so those cards lost their drop shadow.
34754
+ // A list of only inset layers has no native mapping (pptxgenjs writes an
34755
+ // <a:innerShdw> closed by </a:outerShdw>, malformed XML) \u2192 dropped with a note.
34001
34756
  let shadow;
34002
34757
  const bs = cs.boxShadow;
34003
- if (bs && bs !== "none" && !bs.includes("inset")) {
34004
- const m = bs.split(/,(?![^(]*\\))/)[0].trim().match(/(rgba?\\([^)]*\\)|#[0-9a-fA-F]+)\\s+(-?[\\d.]+)px\\s+(-?[\\d.]+)px\\s+(-?[\\d.]+)px/);
34758
+ if (bs && bs !== "none") {
34759
+ const outer = bs.split(/,(?![^(]*\\))/).map((l) => l.trim()).find((l) => !/\\binset\\b/.test(l));
34760
+ const m = outer && outer.match(/(rgba?\\([^)]*\\)|#[0-9a-fA-F]+)\\s+(-?[\\d.]+)px\\s+(-?[\\d.]+)px\\s+(-?[\\d.]+)px/);
34005
34761
  if (m) shadow = { color: m[1], offX: parseFloat(m[2]), offY: parseFloat(m[3]), blur: parseFloat(m[4]) };
34762
+ else if (!outer && !hybrid) notes.push("slide " + (si + 1) + ": inset box-shadow with no outer layer has no native pptx equivalent \u2014 dropped");
34006
34763
  }
34007
34764
  // CSS linear/radial-gradient \u2192 native pptx gradient fill (Stage B injects
34008
34765
  // <a:gradFill>). computed backgroundImage: "linear-gradient(135deg, rgb(..) 0%, rgb(..) 100%)".
@@ -34020,10 +34777,14 @@ var LAYOUT_WALKER_JS = `
34020
34777
  if (stops.length >= 2) gradient = { type: isRadial ? "radial" : "linear", angle: angle, stops: stops };
34021
34778
  }
34022
34779
  if (fill || borderColor || shadow || gradient) {
34023
- if (cs.backgroundImage && cs.backgroundImage !== "none" && !gradient) notes.push("slide " + (si + 1) + ": background-image flattened to backgroundColor");
34780
+ // Native (--editable) only: hybrid replaces every rect with the full-slide
34781
+ // backdrop raster, which already carries the background-image \u2014 there is
34782
+ // nothing flattened, and the note read as a failure to the model (B4).
34783
+ if (!hybrid && cs.backgroundImage && cs.backgroundImage !== "none" && !gradient) notes.push("slide " + (si + 1) + ": background-image flattened to backgroundColor");
34024
34784
  elements.push({
34025
34785
  kind: "rect", frame: rel(r), fill, borderColor,
34026
34786
  ...(borderColor ? { borderWidthPx: bw } : {}),
34787
+ ...(borderColor && (cs.borderTopStyle === "dashed" || cs.borderTopStyle === "dotted") ? { borderDash: true } : {}),
34027
34788
  ...(shadow ? { shadow } : {}),
34028
34789
  ...(gradient ? { gradient } : {}),
34029
34790
  radiusPx: parseFloat(cs.borderTopLeftRadius) || 0,
@@ -34050,7 +34811,9 @@ var LAYOUT_WALKER_JS = `
34050
34811
  }
34051
34812
  return { background: color(getComputedStyle(sec).backgroundColor) ?? "#ffffff", elements, ...(speakerNotes ? { speakerNotes } : {}), ...(transition ? { transition } : {}) };
34052
34813
  });
34053
- return { width: 1280, height: 720, slides, notes };
34814
+ const langEl = document.documentElement.lang ? document.documentElement : document.body && document.body.querySelector("[lang]");
34815
+ const lang = langEl ? langEl.getAttribute("lang") : "";
34816
+ return { width: 1280, height: 720, slides, notes, ...(lang ? { lang } : {}) };
34054
34817
  })()
34055
34818
  `;
34056
34819
 
@@ -34077,6 +34840,62 @@ var CAPTURE_SETTLE_MS = Number.isFinite(configuredSettleMs) ? Math.max(0, Math.m
34077
34840
  var pngDataUri = (bytes) => "data:image/png;base64," + Buffer.from(bytes).toString("base64");
34078
34841
  var isolateSlide = (si) => `new Promise((done) => { const secs = document.querySelectorAll("section.slide"); secs.forEach((s, j) => { s.style.display = j === ${si} ? "" : "none"; }); window.scrollTo(0, 0); requestAnimationFrame(() => requestAnimationFrame(() => done(true))); })`;
34079
34842
  var showAllSlides = `document.querySelectorAll("section.slide").forEach((s) => { s.style.display = ""; }); document.getElementById("__talos_hide")?.remove(); true`;
34843
+ var backdropKeyScript = (si, scope = "slide") => `(() => {
34844
+ const sec = document.querySelectorAll("section.slide")[${si}];
34845
+ if (!sec) return "";
34846
+ const decorOnly = ${JSON.stringify(scope === "decor")};
34847
+ const org = sec.getBoundingClientRect();
34848
+ const PAINT = ["backgroundColor","backgroundImage","backgroundSize","backgroundPosition","backgroundRepeat","borderTop","borderRight","borderBottom","borderLeft","borderRadius","boxShadow","outline","opacity","transform","filter","backdropFilter","mixBlendMode","clipPath","maskImage","visibility","zIndex"];
34849
+ const px = (v) => parseFloat(v) || 0;
34850
+ const paints = (cs) => (cs.backgroundColor !== "rgba(0, 0, 0, 0)" && cs.backgroundColor !== "transparent") || cs.backgroundImage !== "none"
34851
+ || px(cs.borderTopWidth) + px(cs.borderRightWidth) + px(cs.borderBottomWidth) + px(cs.borderLeftWidth) > 0
34852
+ || cs.boxShadow !== "none" || (cs.outlineStyle !== "none" && px(cs.outlineWidth) > 0) || cs.filter !== "none" || cs.backdropFilter !== "none";
34853
+ const visibleText = (el, cs) => px(cs.webkitTextStrokeWidth) > 0 || cs.webkitBackgroundClip === "text" || cs.backgroundClip === "text"
34854
+ || cs.getPropertyValue("--talos-bake").trim() === "1" || (el.closest && el.closest("[data-bake]"));
34855
+ const rect = (el) => { const r = el.getBoundingClientRect(); return [r.left - org.left, r.top - org.top, r.width, r.height].map(Math.round).join(","); };
34856
+ const out = [];
34857
+ const visit = (el, depth) => {
34858
+ const cs = getComputedStyle(el);
34859
+ if (cs.display === "none") { out.push(depth + "<" + el.tagName + " hidden"); return; }
34860
+ const tag = el.tagName.toUpperCase();
34861
+ let line = depth + "<" + tag;
34862
+ const pseudo = [];
34863
+ for (const ps of ["::before", "::after"]) {
34864
+ const pcs = getComputedStyle(el, ps);
34865
+ if (pcs.content && pcs.content !== "none" && pcs.content !== "normal") pseudo.push(ps + "{" + pcs.content + "|" + pcs.width + "x" + pcs.height + "|" + pcs.top + "," + pcs.left + "|" + PAINT.map((p) => pcs[p]).join("|") + "}");
34866
+ }
34867
+ const media = tag === "IMG" || tag === "SVG" || tag === "CANVAS" || tag === "VIDEO";
34868
+ const text = visibleText(el, cs);
34869
+ if (paints(cs) || media || pseudo.length || text) line += " @" + rect(el) + " " + PAINT.map((p) => cs[p]).join("|") + pseudo.join("");
34870
+ if (text) line += " text=" + el.textContent + "|" + cs.font + "|" + cs.webkitTextStroke + "|" + cs.color;
34871
+ if (tag === "IMG") line += " src=" + (el.currentSrc || el.src);
34872
+ if (tag === "CANVAS") { try { line += " px=" + el.toDataURL(); } catch (e) { line += " px=?"; } }
34873
+ if (tag === "SVG") { out.push(line + " svg=" + el.outerHTML); return; }
34874
+ out.push(line);
34875
+ for (const child of el.children) visit(child, depth + 1);
34876
+ };
34877
+ if (decorOnly) {
34878
+ // --editable decoration picture (decorIsolateJs): only the section's own paint
34879
+ // and its decoration layer(s) are in it \u2014 content is native \u2014 so a content
34880
+ // edit must not miss. The section line is its paint alone, no children.
34881
+ const cs = getComputedStyle(sec);
34882
+ const pseudo = ["::before", "::after"].map((ps) => { const p = getComputedStyle(sec, ps); return p.content + "|" + p.width + "x" + p.height + "|" + PAINT.map((k) => p[k]).join("|"); }).join("");
34883
+ out.push("<SECTION @" + rect(sec) + " " + PAINT.map((p) => cs[p]).join("|") + pseudo);
34884
+ for (const layer of sec.querySelectorAll("[data-talos-decor-id]")) { out.push("@" + rect(layer)); visit(layer, 1); }
34885
+ } else {
34886
+ visit(sec, 0);
34887
+ }
34888
+ // Long data: URIs stand in as length + FNV-1a, so the string stays small.
34889
+ return out.join("\\n").replace(/data:[^"')\\s]{64,}/g, (u) => {
34890
+ let h = 2166136261;
34891
+ for (let i = 0; i < u.length; i++) { h ^= u.charCodeAt(i); h = Math.imul(h, 16777619); }
34892
+ return "data#" + u.length + "#" + (h >>> 0).toString(16);
34893
+ });
34894
+ })()`;
34895
+ var BACKDROP_KEY_VERSION = "talos-backdrop-v2";
34896
+ var DECOR_KEY_VERSION = "talos-decor-v1";
34897
+ var cacheKey = (version, salt, descriptor) => (0, import_node_crypto.createHash)("sha256").update(version).update("\0").update(salt).update("\0").update(descriptor).digest("hex");
34898
+ var sameBytes = (a, b) => a.length === b.length && Buffer.from(a).equals(Buffer.from(b));
34080
34899
  async function snapshotHeadless(page, opts = {}) {
34081
34900
  const backdrop = opts.backdrop === true;
34082
34901
  await page.evaluate(`window.__TALOS_BD = ${JSON.stringify(backdrop)}; true`);
@@ -34085,11 +34904,30 @@ async function snapshotHeadless(page, opts = {}) {
34085
34904
  throw new Error(String(snapshot.error));
34086
34905
  }
34087
34906
  if (backdrop && Array.isArray(snapshot.slides)) {
34088
- await page.evaluate(`(() => { document.querySelectorAll("*").forEach((el) => { const bake = getComputedStyle(el).getPropertyValue("--talos-bake").trim() === "1" || (el.closest && el.closest("[data-bake]")); if (bake) { const c = getComputedStyle(el).color; el.style.setProperty("color", c, "important"); el.style.setProperty("-webkit-text-fill-color", c, "important"); } }); const s = document.createElement("style"); s.id = "__talos_hide"; s.textContent = "*{color:transparent !important;-webkit-text-fill-color:transparent !important;text-shadow:none !important}"; document.head.appendChild(s); return true; })()`);
34907
+ await page.evaluate(`(() => { document.querySelectorAll("*").forEach((el) => { const bake = getComputedStyle(el).getPropertyValue("--talos-bake").trim() === "1" || (el.closest && el.closest("[data-bake]")); if (bake) { const c = getComputedStyle(el).color; el.style.setProperty("color", c, "important"); el.style.setProperty("-webkit-text-fill-color", c, "important"); } }); const s = document.createElement("style"); s.id = "__talos_hide"; s.textContent = ${JSON.stringify(BACKDROP_HIDE_CSS)}; document.head.appendChild(s); return true; })()`);
34908
+ const cache = opts.cache;
34909
+ const keys = [];
34910
+ if (cache) {
34911
+ for (let si = 0; si < snapshot.slides.length; si++) {
34912
+ const descriptor = String(await page.evaluate(backdropKeyScript(si)));
34913
+ keys.push(cacheKey(BACKDROP_KEY_VERSION, cache.salt, descriptor));
34914
+ }
34915
+ }
34089
34916
  for (let si = 0; si < snapshot.slides.length; si++) {
34090
- await page.evaluate(isolateSlide(si));
34091
- await sleep(CAPTURE_SETTLE_MS);
34092
- const png = await page.screenshot({ x: 0, y: 0, width: 1280, height: 720 });
34917
+ const cached = cache ? await cache.get(keys[si]) : void 0;
34918
+ let png;
34919
+ if (cached && !cache.verify) {
34920
+ png = cached;
34921
+ } else {
34922
+ await page.evaluate(isolateSlide(si));
34923
+ await sleep(CAPTURE_SETTLE_MS);
34924
+ png = await page.screenshot({ x: 0, y: 0, width: 1280, height: 720 });
34925
+ if (cached && !sameBytes(cached, png)) {
34926
+ throw new Error(`backdrop cache key ${keys[si].slice(0, 12)} served different pixels on slide ${si + 1} \u2014 something that paints is missing from backdropKeyScript`);
34927
+ }
34928
+ if (cache && !cached)
34929
+ await cache.set(keys[si], png);
34930
+ }
34093
34931
  const backdropEl = { kind: "image", frame: { x: 0, y: 0, w: 1280, h: 720 }, data: pngDataUri(png) };
34094
34932
  const keepEls = snapshot.slides[si].elements.filter((e) => e && (e.kind === "text" || e.kind === "chart"));
34095
34933
  snapshot.slides[si].elements = [backdropEl, ...keepEls];
@@ -34097,7 +34935,39 @@ async function snapshotHeadless(page, opts = {}) {
34097
34935
  await page.evaluate(showAllSlides);
34098
34936
  return snapshot;
34099
34937
  }
34100
- if (Array.isArray(snapshot.slides)) {
34938
+ if (Array.isArray(snapshot.slides) && snapshot.slides.some((s) => Array.isArray(s.elements) && s.elements.some((e) => e && e.kind === "decor"))) {
34939
+ const cache = opts.cache;
34940
+ for (let si = 0; si < snapshot.slides.length; si++) {
34941
+ const els = snapshot.slides[si].elements;
34942
+ if (!Array.isArray(els) || !els.some((e) => e && e.kind === "decor"))
34943
+ continue;
34944
+ const key = cache ? cacheKey(DECOR_KEY_VERSION, cache.salt, String(await page.evaluate(backdropKeyScript(si, "decor")))) : void 0;
34945
+ const cached = cache ? await cache.get(key) : void 0;
34946
+ let png = cached && !cache.verify ? cached : null;
34947
+ if (!png) {
34948
+ try {
34949
+ await page.evaluate(decorIsolateJs(si));
34950
+ await page.evaluate(isolateSlide(si));
34951
+ await sleep(CAPTURE_SETTLE_MS);
34952
+ png = await page.screenshot({ x: 0, y: 0, width: 1280, height: 720 });
34953
+ } catch (e) {
34954
+ (snapshot.notes ??= []).push("slide " + (si + 1) + ": the decoration layer could not be rasterized (" + e.message + ") \u2014 it is left out");
34955
+ } finally {
34956
+ await page.evaluate(DECOR_RELEASE_JS).catch(() => {
34957
+ });
34958
+ }
34959
+ if (png && cached && !sameBytes(cached, png)) {
34960
+ throw new Error(`decoration cache key ${key.slice(0, 12)} served different pixels on slide ${si + 1} \u2014 something that paints is missing from backdropKeyScript`);
34961
+ }
34962
+ if (png && cache && !cached)
34963
+ await cache.set(key, png);
34964
+ }
34965
+ snapshot.slides[si].elements = placeDecorPicture(els, png ? pngDataUri(png) : null);
34966
+ }
34967
+ await page.evaluate(showAllSlides);
34968
+ }
34969
+ if (Array.isArray(snapshot.slides) && snapshot.slides.some((s) => Array.isArray(s.elements) && s.elements.some((e) => e && e.kind === "capture"))) {
34970
+ await page.evaluate(CAPTURE_HIDE_TEXT_JS);
34101
34971
  for (let si = 0; si < snapshot.slides.length; si++) {
34102
34972
  const els = snapshot.slides[si].elements;
34103
34973
  if (!Array.isArray(els) || !els.some((e) => e && e.kind === "capture"))
@@ -34117,17 +34987,25 @@ async function snapshotHeadless(page, opts = {}) {
34117
34987
  continue;
34118
34988
  }
34119
34989
  try {
34990
+ if (el.capId && await page.evaluate(captureIsolateJs(String(el.capId))))
34991
+ await sleep(CAPTURE_SETTLE_MS);
34120
34992
  const png = await page.screenshot({ x, y, width: w, height: h });
34993
+ if (el.capId)
34994
+ await page.evaluate(CAPTURE_RELEASE_JS);
34121
34995
  el.kind = "image";
34122
34996
  el.data = pngDataUri(png);
34123
34997
  delete el.tag;
34998
+ delete el.capId;
34124
34999
  } catch (e) {
34125
35000
  el.kind = "__drop";
35001
+ await page.evaluate(CAPTURE_RELEASE_JS).catch(() => {
35002
+ });
34126
35003
  (snapshot.notes ??= []).push("slide " + (si + 1) + ": <" + (el.tag ?? "svg") + "> could not be rasterized (" + e.message + ")");
34127
35004
  }
34128
35005
  }
34129
35006
  snapshot.slides[si].elements = els.filter((e) => e.kind !== "__drop");
34130
35007
  }
35008
+ await page.evaluate(CAPTURE_SHOW_TEXT_JS);
34131
35009
  await page.evaluate(showAllSlides);
34132
35010
  }
34133
35011
  return snapshot;
@@ -34629,7 +35507,7 @@ async function launchHeadlessChromium() {
34629
35507
  return null;
34630
35508
  }
34631
35509
  }
34632
- var CJK_RE2 = /[\u2E80-\u2FFF\u3000-\u303F\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\uFF00-\uFFEF]/;
35510
+ var CJK_RE = /[\u2E80-\u2FFF\u3000-\u303F\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF\uFF00-\uFFEF]/;
34633
35511
  var _fontManifest;
34634
35512
  async function fontManifest() {
34635
35513
  if (_fontManifest !== void 0) return _fontManifest;
@@ -34642,6 +35520,11 @@ async function fontManifest() {
34642
35520
  }
34643
35521
  return _fontManifest;
34644
35522
  }
35523
+ var _faceB64 = /* @__PURE__ */ new Map();
35524
+ async function faceBase64(path) {
35525
+ if (!_faceB64.has(path)) _faceB64.set(path, (await (0, import_promises.readFile)(path).catch(() => null))?.toString("base64") ?? null);
35526
+ return _faceB64.get(path);
35527
+ }
34645
35528
  async function faceCss(entries, range, doc) {
34646
35529
  const { dir } = await fontManifest();
34647
35530
  const rules = [];
@@ -34650,7 +35533,7 @@ async function faceCss(entries, range, doc) {
34650
35533
  if (!families.length) continue;
34651
35534
  const path = (0, import_node_path.join)(dir, entry.file);
34652
35535
  if (!(0, import_node_fs.existsSync)(path)) continue;
34653
- const b64 = (await (0, import_promises.readFile)(path).catch(() => null))?.toString("base64");
35536
+ const b64 = await faceBase64(path);
34654
35537
  if (!b64) continue;
34655
35538
  for (const family of families) {
34656
35539
  rules.push(
@@ -34660,26 +35543,25 @@ async function faceCss(entries, range, doc) {
34660
35543
  }
34661
35544
  return rules.join("");
34662
35545
  }
34663
- var _cjkFontCss;
35546
+ var _faceCssBySet = /* @__PURE__ */ new Map();
35547
+ async function cachedFaceCss(group, entries, range, doc) {
35548
+ const named = (entries ?? []).flatMap((e) => (e.families ?? []).filter((f) => doc.includes(f)));
35549
+ const key = `${group}|${named.join("|")}`;
35550
+ if (!_faceCssBySet.has(key)) _faceCssBySet.set(key, await faceCss(entries, range, doc));
35551
+ return _faceCssBySet.get(key);
35552
+ }
34664
35553
  async function cjkFontFaceCss(doc) {
34665
- if (_cjkFontCss === void 0) {
34666
- const m = await fontManifest();
34667
- _cjkFontCss = await faceCss(m.cjk, m.cjkUnicodeRange, doc);
34668
- }
34669
- return _cjkFontCss;
35554
+ if (process.env.TALOS_CJK_FONTS === "system") return "";
35555
+ const m = await fontManifest();
35556
+ return cachedFaceCss("cjk", m.cjk, m.cjkUnicodeRange, doc);
34670
35557
  }
34671
- var _latinFontCss;
34672
35558
  async function latinFontFaceCss(doc) {
34673
- if (_latinFontCss === void 0) {
34674
- const m = await fontManifest();
34675
- const wanted = (m.latin ?? []).filter((e) => (e.families ?? []).some((f) => doc.includes(f)));
34676
- _latinFontCss = await faceCss(wanted, void 0);
34677
- }
34678
- return _latinFontCss;
35559
+ const m = await fontManifest();
35560
+ return cachedFaceCss("latin", m.latin, void 0, doc);
34679
35561
  }
34680
35562
 
34681
35563
  // apps/admin/data/skills.lib/document/bin-src/lib/check-pptx.mjs
34682
- var import_node_path3 = require("node:path");
35564
+ var import_node_path4 = require("node:path");
34683
35565
 
34684
35566
  // packages/core/dist/document/doc-validate.js
34685
35567
  var import_jszip5 = __toESM(require_lib4(), 1);
@@ -34695,6 +35577,17 @@ async function validatePdf(bytes) {
34695
35577
  try {
34696
35578
  const doc = await import_pdf_lib.PDFDocument.load(bytes, { ignoreEncryption: true, throwOnInvalidObject: true, updateMetadata: false });
34697
35579
  pages = doc.getPageCount();
35580
+ if (doc.isEncrypted) {
35581
+ const { pdfNeedsPassword: pdfNeedsPassword2 } = await Promise.resolve().then(() => (init_pdf_extract(), pdf_extract_exports));
35582
+ if (await pdfNeedsPassword2(bytes).catch(() => true)) {
35583
+ errors.push({
35584
+ code: "pdf-encrypted",
35585
+ message: "the PDF needs a password to open, so its content cannot be checked or rendered \u2014 validate (and render) the unencrypted file before `pdf-ops encrypt`; to inspect this one, `pdf-ops decrypt <in> <password> <out>` first"
35586
+ });
35587
+ return { ok: false, kind: "pdf", errors, warnings, pages };
35588
+ }
35589
+ warnings.push({ code: "pdf-encrypted", message: 'the PDF is encrypted with an owner password only: it opens without a password, but pdf-form / pdf-ops cannot edit it until `pdf-ops decrypt <in> "" <out>`' });
35590
+ }
34698
35591
  if (pages === 0)
34699
35592
  errors.push({ code: "pdf-no-pages", message: "the PDF parses but has no pages" });
34700
35593
  else
@@ -34924,15 +35817,15 @@ async function validateOoxml(bytes) {
34924
35817
  }
34925
35818
 
34926
35819
  // packages/core/dist/document/doc-xsd.js
34927
- var import_node_path2 = require("node:path");
34928
- var import_node_url = require("node:url");
35820
+ var import_node_path3 = require("node:path");
35821
+ var import_node_url2 = require("node:url");
34929
35822
  var import_promises2 = require("node:fs/promises");
34930
35823
  var import_jszip6 = __toESM(require_lib4(), 1);
34931
35824
  var _xmllint = null;
34932
35825
  function loadXmllint() {
34933
35826
  if (!_xmllint) {
34934
35827
  const deps = process.env.TALOS_SKILL_DEPS;
34935
- const spec = deps ? (0, import_node_url.pathToFileURL)((0, import_node_path2.join)(deps, "xmllint-wasm", "index-node.js")).href : "xmllint-wasm";
35828
+ const spec = deps ? (0, import_node_url2.pathToFileURL)((0, import_node_path3.join)(deps, "xmllint-wasm", "index-node.js")).href : "xmllint-wasm";
34936
35829
  _xmllint = import(spec);
34937
35830
  }
34938
35831
  return _xmllint;
@@ -34975,7 +35868,7 @@ async function loadSchemaSet(schemaDir) {
34975
35868
  if (!files.length)
34976
35869
  throw new Error(`no .xsd schemas found in ${schemaDir}`);
34977
35870
  const set = /* @__PURE__ */ new Map();
34978
- await Promise.all(files.map(async (f) => set.set(f, await (0, import_promises2.readFile)((0, import_node_path2.join)(schemaDir, f), "utf8"))));
35871
+ await Promise.all(files.map(async (f) => set.set(f, await (0, import_promises2.readFile)((0, import_node_path3.join)(schemaDir, f), "utf8"))));
34979
35872
  return set;
34980
35873
  }
34981
35874
  function errText(e) {
@@ -35079,6 +35972,102 @@ async function slidePartsInOrder(zip) {
35079
35972
  return toParts(complete ? ordered : byNumber);
35080
35973
  }
35081
35974
 
35975
+ // packages/core/dist/document/png-decode.js
35976
+ var import_node_zlib = require("node:zlib");
35977
+ var SIG = [137, 80, 78, 71, 13, 10, 26, 10];
35978
+ var CHANNELS = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 };
35979
+ function decodePng(bytes) {
35980
+ try {
35981
+ if (bytes.length < 33 || SIG.some((b, i) => bytes[i] !== b))
35982
+ return void 0;
35983
+ const buf = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
35984
+ let width = 0, height = 0, depth = 0, type = -1, interlace = 0;
35985
+ let palette;
35986
+ const idat = [];
35987
+ for (let p = 8; p + 8 <= buf.length; ) {
35988
+ const len = buf.readUInt32BE(p);
35989
+ const kind = buf.toString("latin1", p + 4, p + 8);
35990
+ const data = buf.subarray(p + 8, p + 8 + len);
35991
+ if (kind === "IHDR") {
35992
+ width = data.readUInt32BE(0);
35993
+ height = data.readUInt32BE(4);
35994
+ depth = data[8];
35995
+ type = data[9];
35996
+ interlace = data[12];
35997
+ } else if (kind === "PLTE")
35998
+ palette = data;
35999
+ else if (kind === "IDAT")
36000
+ idat.push(data);
36001
+ else if (kind === "IEND")
36002
+ break;
36003
+ p += 12 + len;
36004
+ }
36005
+ const ch = CHANNELS[type];
36006
+ if (!width || !height || depth !== 8 || interlace !== 0 || ch === void 0 || type === 3 && !palette)
36007
+ return void 0;
36008
+ const raw = (0, import_node_zlib.inflateSync)(Buffer.concat(idat));
36009
+ const stride = width * ch;
36010
+ if (raw.length < height * (stride + 1))
36011
+ return void 0;
36012
+ const cur = new Uint8Array(stride);
36013
+ const prev = new Uint8Array(stride);
36014
+ const rgb = new Uint8Array(width * height * 3);
36015
+ for (let y = 0; y < height; y++) {
36016
+ const off = y * (stride + 1);
36017
+ const filter = raw[off];
36018
+ for (let i = 0; i < stride; i++) {
36019
+ const x = raw[off + 1 + i];
36020
+ const a = i >= ch ? cur[i - ch] : 0;
36021
+ const b = prev[i];
36022
+ const c = i >= ch ? prev[i - ch] : 0;
36023
+ let v;
36024
+ switch (filter) {
36025
+ case 0:
36026
+ v = x;
36027
+ break;
36028
+ case 1:
36029
+ v = x + a;
36030
+ break;
36031
+ case 2:
36032
+ v = x + b;
36033
+ break;
36034
+ case 3:
36035
+ v = x + (a + b >> 1);
36036
+ break;
36037
+ case 4: {
36038
+ const pa = Math.abs(b - c), pb = Math.abs(a - c), pc = Math.abs(a + b - 2 * c);
36039
+ v = x + (pa <= pb && pa <= pc ? a : pb <= pc ? b : c);
36040
+ break;
36041
+ }
36042
+ default:
36043
+ return void 0;
36044
+ }
36045
+ cur[i] = v & 255;
36046
+ }
36047
+ for (let px = 0; px < width; px++) {
36048
+ const o = (y * width + px) * 3;
36049
+ const s = px * ch;
36050
+ if (type === 3) {
36051
+ const k = cur[s] * 3;
36052
+ rgb[o] = palette[k];
36053
+ rgb[o + 1] = palette[k + 1];
36054
+ rgb[o + 2] = palette[k + 2];
36055
+ } else if (ch <= 2) {
36056
+ rgb[o] = rgb[o + 1] = rgb[o + 2] = cur[s];
36057
+ } else {
36058
+ rgb[o] = cur[s];
36059
+ rgb[o + 1] = cur[s + 1];
36060
+ rgb[o + 2] = cur[s + 2];
36061
+ }
36062
+ }
36063
+ prev.set(cur);
36064
+ }
36065
+ return { width, height, rgb };
36066
+ } catch {
36067
+ return void 0;
36068
+ }
36069
+ }
36070
+
35082
36071
  // packages/core/dist/document/doc-design-lint.js
35083
36072
  var PLACEHOLDER_RE = /替换为|在这里写|占位|示例文本|Lorem ipsum/i;
35084
36073
  var CJK_TEXT_RE = /[぀-ヿ㐀-䶿一-鿿가-힯豈-﫿]/;
@@ -35219,7 +36208,7 @@ function lintSlideXml(slideXml, slideNumber, masterBg, expectedLang = detectDeck
35219
36208
  }
35220
36209
  }
35221
36210
  }
35222
- if (worst && !worst.onShapeFill && anyVisualShape)
36211
+ if (worst && !worst.onShapeFill && (anyVisualShape || hasPic))
35223
36212
  worst = void 0;
35224
36213
  if (placeholder) {
35225
36214
  const snippet = placeholder.length > 24 ? `${placeholder.slice(0, 24)}\u2026` : placeholder;
@@ -35259,7 +36248,10 @@ var EMU_PER_INCH = 914400;
35259
36248
  var DEFAULT_CANVAS = { w: 12192e3, h: 6858e3 };
35260
36249
  var OFFCANVAS_TOL = EMU_PER_INCH / 10;
35261
36250
  var MIN_FONT_HUNDREDTHS = 1e3;
35262
- var OVERLAP_RATIO = 0.5;
36251
+ var LABEL_MAX_CHARS = 24;
36252
+ var MIN_LABEL_FONT_HUNDREDTHS = 800;
36253
+ var CHART_TEXT_RE = new RegExp(`<p:cNvPr\\b[^>]*\\bname="${CHART_TEXT_NAME}\\b`);
36254
+ var OVERLAP_RATIO = 0.3;
35263
36255
  function firstBox(block) {
35264
36256
  const xf = /<(?:a|p):xfrm\b[^>]*>[\s\S]*?<\/(?:a|p):xfrm>/.exec(block);
35265
36257
  const scope = xf ? xf[0] : block;
@@ -35277,6 +36269,51 @@ function overlapArea(a, b) {
35277
36269
  const iy = Math.max(0, Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y));
35278
36270
  return ix * iy;
35279
36271
  }
36272
+ var CJK_WIDE_RE = /[\u2E80-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF\uFF00-\uFFEF]/;
36273
+ var EMU_PER_PT = 12700;
36274
+ function inkBox(block, box) {
36275
+ let maxSz = 0;
36276
+ let inkW = 0;
36277
+ let lines = 0;
36278
+ for (const para of block.match(/<a:p>[\s\S]*?<\/a:p>|<a:p\b[^>]*>[\s\S]*?<\/a:p>/g) ?? []) {
36279
+ let paraW = 0;
36280
+ let paraSz = 0;
36281
+ for (const run of para.match(/<a:r>[\s\S]*?<\/a:r>/g) ?? []) {
36282
+ const raw = /<a:t\b[^>]*>([\s\S]*?)<\/a:t>/.exec(run)?.[1];
36283
+ if (raw === void 0)
36284
+ continue;
36285
+ const sz = +(/<a:rPr\b[^>]*\bsz="(\d+)"/.exec(run)?.[1] ?? 1800) / 100;
36286
+ paraSz = Math.max(paraSz, sz);
36287
+ for (const ch of unescXml(raw))
36288
+ paraW += (CJK_WIDE_RE.test(ch) ? 1 : ch === " " ? 0.3 : 0.55) * sz * EMU_PER_PT;
36289
+ }
36290
+ if (!paraW)
36291
+ continue;
36292
+ maxSz = Math.max(maxSz, paraSz);
36293
+ const paraLines = box.w > 0 ? Math.max(1, Math.ceil(paraW / box.w)) : 1;
36294
+ lines += paraLines;
36295
+ inkW = Math.max(inkW, Math.min(paraW, box.w));
36296
+ }
36297
+ if (!lines)
36298
+ return void 0;
36299
+ const paras = (block.match(/<a:p>[\s\S]*?<\/a:p>|<a:p\b[^>]*>[\s\S]*?<\/a:p>/g) ?? []).filter((p) => /<a:t\b[^>]*>[^<]*\S/.test(p));
36300
+ if (paras.length === 1 && maxSz > 0) {
36301
+ const lnSpc = /<a:lnSpc>\s*<a:spcPts\b[^>]*\bval="(\d+)"/.exec(block)?.[1];
36302
+ const lineH = (lnSpc ? +lnSpc / 100 : maxSz * 1.3) * EMU_PER_PT;
36303
+ const clamped = box.x + box.w >= DEFAULT_CANVAS.w - EMU_PER_PT;
36304
+ if (box.h <= lineH * 1.5 && !clamped) {
36305
+ const text = unescXml((block.match(/<a:t\b[^>]*>([\s\S]*?)<\/a:t>/g) ?? []).join(""));
36306
+ lines = 1;
36307
+ inkW = Math.min(inkW, unpaddedWidth(box.w, maxSz * EMU_PER_PT, true, SLACK_CJK_RE.test(text)));
36308
+ }
36309
+ }
36310
+ const inkH = Math.min(box.h, lines * maxSz * 1.2 * EMU_PER_PT);
36311
+ const algn = /<a:pPr\b[^>]*\balgn="(\w+)"/.exec(block)?.[1];
36312
+ const anchor = /<a:bodyPr\b[^>]*\banchor="(\w+)"/.exec(block)?.[1];
36313
+ const x = algn === "ctr" ? box.x + (box.w - inkW) / 2 : algn === "r" ? box.x + box.w - inkW : box.x;
36314
+ const y = anchor === "ctr" ? box.y + (box.h - inkH) / 2 : anchor === "b" ? box.y + box.h - inkH : box.y;
36315
+ return { x, y, w: inkW, h: inkH };
36316
+ }
35280
36317
  function lintSlideGeometry(slideXml, slideNumber, canvas = DEFAULT_CANVAS) {
35281
36318
  const findings = [];
35282
36319
  const blocks = slideXml.match(/<p:(?:sp|pic|graphicFrame|cxnSp)\b[^>]*>[\s\S]*?<\/p:(?:sp|pic|graphicFrame|cxnSp)>/g) ?? [];
@@ -35300,16 +36337,23 @@ function lintSlideGeometry(slideXml, slideNumber, canvas = DEFAULT_CANVAS) {
35300
36337
  worstOff = { by, edge };
35301
36338
  }
35302
36339
  }
35303
- if (hasText && box.w > 0 && box.h > 0)
35304
- textBoxes.push(box);
36340
+ if (hasText && box.w > 0 && box.h > 0) {
36341
+ const ink = inkBox(b, box);
36342
+ if (ink)
36343
+ textBoxes.push(ink);
36344
+ }
35305
36345
  }
36346
+ if (CHART_TEXT_RE.test(b))
36347
+ continue;
35306
36348
  for (const run of b.match(/<a:r>[\s\S]*?<\/a:r>/g) ?? []) {
35307
36349
  const raw = /<a:t\b[^>]*>([\s\S]*?)<\/a:t>/.exec(run)?.[1];
35308
36350
  if (raw === void 0 || !unescXml(raw).trim())
35309
36351
  continue;
36352
+ const text = unescXml(raw).trim();
35310
36353
  const sz = /<a:rPr\b[^>]*\bsz="(\d+)"/.exec(run)?.[1];
35311
- if (sz && +sz < MIN_FONT_HUNDREDTHS && (!smallest || +sz < smallest.sz)) {
35312
- smallest = { sz: +sz, text: unescXml(raw).trim() };
36354
+ const floor = [...text].length <= LABEL_MAX_CHARS ? MIN_LABEL_FONT_HUNDREDTHS : MIN_FONT_HUNDREDTHS;
36355
+ if (sz && +sz < floor && (!smallest || +sz < smallest.sz)) {
36356
+ smallest = { sz: +sz, text, floor };
35313
36357
  }
35314
36358
  }
35315
36359
  }
@@ -35325,7 +36369,7 @@ function lintSlideGeometry(slideXml, slideNumber, canvas = DEFAULT_CANVAS) {
35325
36369
  findings.push({
35326
36370
  slide: slideNumber,
35327
36371
  code: "tiny-font",
35328
- message: `text "${snippet}" is ${(smallest.sz / 100).toFixed(0)}pt \u2014 below the ~10pt readable floor for a projected slide; enlarge it or cut the text`
36372
+ message: `text "${snippet}" is ${+(smallest.sz / 100).toFixed(1)}pt \u2014 below the ~${smallest.floor / 100}pt readable floor for ${smallest.floor < MIN_FONT_HUNDREDTHS ? "a short label" : "text"} on a projected slide; enlarge it or cut the text`
35329
36373
  });
35330
36374
  }
35331
36375
  let worstPair;
@@ -35350,6 +36394,110 @@ function lintSlideGeometry(slideXml, slideNumber, canvas = DEFAULT_CANVAS) {
35350
36394
  }
35351
36395
  return findings;
35352
36396
  }
36397
+ var BACKDROP_COVER = 0.98;
36398
+ var BACKDROP_FLOOR = 2;
36399
+ var BACKDROP_SAMPLES = 2500;
36400
+ var LIN = Array.from({ length: 256 }, (_, i) => {
36401
+ const c = i / 255;
36402
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
36403
+ });
36404
+ var lumRgb = (r, g, b) => 0.2126 * LIN[r] + 0.7152 * LIN[g] + 0.0722 * LIN[b];
36405
+ var ratioOf = (a, b) => (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
36406
+ var hex2 = (n) => n.toString(16).padStart(2, "0").toUpperCase();
36407
+ var BLOCK_RE = /<p:(?:sp|pic|graphicFrame|cxnSp)\b[^>]*>[\s\S]*?<\/p:(?:sp|pic|graphicFrame|cxnSp)>/g;
36408
+ function backdropPicEmbed(slideXml, canvas = DEFAULT_CANVAS) {
36409
+ const first = (slideXml.match(BLOCK_RE) ?? [])[0];
36410
+ if (!first || !first.startsWith("<p:pic"))
36411
+ return void 0;
36412
+ const box = firstBox(first);
36413
+ if (!box || box.x > canvas.w * (1 - BACKDROP_COVER) || box.y > canvas.h * (1 - BACKDROP_COVER))
36414
+ return void 0;
36415
+ if (box.w < canvas.w * BACKDROP_COVER || box.h < canvas.h * BACKDROP_COVER)
36416
+ return void 0;
36417
+ return /<a:blip\b[^>]*\br:embed="([^"]+)"/.exec(first)?.[1];
36418
+ }
36419
+ function lintBackdropContrast(slideXml, slideNumber, backdrop, canvas = DEFAULT_CANVAS) {
36420
+ const blocks = slideXml.match(BLOCK_RE) ?? [];
36421
+ const sx = backdrop.width / canvas.w;
36422
+ const sy = backdrop.height / canvas.h;
36423
+ const occluders = [];
36424
+ let worst;
36425
+ for (let bi = 1; bi < blocks.length; bi++) {
36426
+ const b = blocks[bi];
36427
+ const box = firstBox(b);
36428
+ if (!box)
36429
+ continue;
36430
+ const txIdx = b.indexOf("<p:txBody>");
36431
+ const spPr = txIdx >= 0 ? b.slice(0, txIdx) : b;
36432
+ const prst = /<a:prstGeom\b[^>]*\bprst="([^"]+)"/.exec(spPr)?.[1];
36433
+ const visual = !b.startsWith("<p:sp") || FILL_RE.test(spPr) || prst !== void 0 && prst !== "rect";
36434
+ const hasText = txIdx >= 0 && /<a:t\b[^>]*>\s*\S/.test(b);
36435
+ if (visual) {
36436
+ occluders.push(box);
36437
+ continue;
36438
+ }
36439
+ if (!hasText)
36440
+ continue;
36441
+ const ink = inkBox(b, box);
36442
+ if (!ink || ink.w <= 0 || ink.h <= 0)
36443
+ continue;
36444
+ if (occluders.some((o2) => overlapArea(o2, ink) > 0))
36445
+ continue;
36446
+ let fg;
36447
+ let alpha = 1;
36448
+ let text = "";
36449
+ for (const run of b.match(/<a:r>[\s\S]*?<\/a:r>/g) ?? []) {
36450
+ const raw = /<a:t\b[^>]*>([\s\S]*?)<\/a:t>/.exec(run)?.[1];
36451
+ if (raw === void 0 || !unescXml(raw).trim())
36452
+ continue;
36453
+ text += unescXml(raw);
36454
+ if (fg)
36455
+ continue;
36456
+ const rPr = run.slice(0, Math.max(0, run.indexOf("</a:rPr>")));
36457
+ fg = firstSrgb(rPr);
36458
+ const a = /<a:srgbClr\b[^>]*>\s*<a:alpha\b[^>]*\bval="(\d+)"/.exec(rPr)?.[1];
36459
+ if (fg && a)
36460
+ alpha = Math.min(1, +a / 1e5);
36461
+ }
36462
+ if (!fg)
36463
+ continue;
36464
+ const x0 = Math.max(0, Math.floor(ink.x * sx));
36465
+ const y0 = Math.max(0, Math.floor(ink.y * sy));
36466
+ const x1 = Math.min(backdrop.width, Math.ceil((ink.x + ink.w) * sx));
36467
+ const y1 = Math.min(backdrop.height, Math.ceil((ink.y + ink.h) * sy));
36468
+ if (x1 <= x0 || y1 <= y0)
36469
+ continue;
36470
+ const step = Math.max(1, Math.floor(Math.sqrt((x1 - x0) * (y1 - y0) / BACKDROP_SAMPLES)));
36471
+ const fr = parseInt(fg.slice(0, 2), 16), fgG = parseInt(fg.slice(2, 4), 16), fb = parseInt(fg.slice(4, 6), 16);
36472
+ const fgLum = lumRgb(fr, fgG, fb);
36473
+ const samples = [];
36474
+ for (let y = y0; y < y1; y += step) {
36475
+ for (let x = x0; x < x1; x += step) {
36476
+ const o2 = (y * backdrop.width + x) * 3;
36477
+ const r = backdrop.rgb[o2], g = backdrop.rgb[o2 + 1], bl = backdrop.rgb[o2 + 2];
36478
+ const l = lumRgb(r, g, bl);
36479
+ const tl = alpha >= 1 ? fgLum : lumRgb(Math.round(alpha * fr + (1 - alpha) * r), Math.round(alpha * fgG + (1 - alpha) * g), Math.round(alpha * fb + (1 - alpha) * bl));
36480
+ samples.push([ratioOf(tl, l), o2]);
36481
+ }
36482
+ }
36483
+ if (!samples.length)
36484
+ continue;
36485
+ samples.sort((p, q) => p[0] - q[0]);
36486
+ const [ratio, o] = samples[samples.length >> 1];
36487
+ if (ratio < BACKDROP_FLOOR && (!worst || ratio < worst.ratio)) {
36488
+ const bg = hex2(backdrop.rgb[o]) + hex2(backdrop.rgb[o + 1]) + hex2(backdrop.rgb[o + 2]);
36489
+ worst = { ratio, text: text.trim(), fg, bg };
36490
+ }
36491
+ }
36492
+ if (!worst)
36493
+ return [];
36494
+ const snippet = worst.text.length > 24 ? `${worst.text.slice(0, 24)}\u2026` : worst.text;
36495
+ return [{
36496
+ slide: slideNumber,
36497
+ code: "low-contrast-text",
36498
+ message: `text "${snippet}" is #${worst.fg} over the slide's rendered background (\u2248#${worst.bg} under it) \u2014 median contrast ${worst.ratio.toFixed(1)}:1, below the ${BACKDROP_FLOOR.toFixed(1)} floor \u2014 it can barely be seen; text over a photo or panel takes that surface's text colour (light on a dark photo/scrim), not the skin's page text colour`
36499
+ }];
36500
+ }
35353
36501
  function canvasFromPresentation(xml) {
35354
36502
  if (!xml)
35355
36503
  return DEFAULT_CANVAS;
@@ -35357,6 +36505,26 @@ function canvasFromPresentation(xml) {
35357
36505
  const cy = /<p:sldSz\b[^>]*\bcy="(\d+)"/.exec(xml)?.[1];
35358
36506
  return cx && cy ? { w: +cx, h: +cy } : DEFAULT_CANVAS;
35359
36507
  }
36508
+ async function backdropRaster(zip, slideXml, relsPath, canvas) {
36509
+ const rId = backdropPicEmbed(slideXml, canvas);
36510
+ const rels = rId ? zip.file(relsPath) : null;
36511
+ if (!rId || !rels)
36512
+ return void 0;
36513
+ const relsXml = await rels.async("string");
36514
+ const rel = [...relsXml.matchAll(/<Relationship\b[^>]*>/g)].map((m) => m[0]).find((r) => new RegExp(`\\bId="${rId}"`).test(r));
36515
+ const target = rel && /\bTarget="([^"]+)"/.exec(rel)?.[1];
36516
+ if (!target || !/\.png$/i.test(target))
36517
+ return void 0;
36518
+ const parts = ["ppt", "slides"];
36519
+ for (const seg of target.split("/")) {
36520
+ if (seg === "..")
36521
+ parts.pop();
36522
+ else if (seg && seg !== ".")
36523
+ parts.push(seg);
36524
+ }
36525
+ const media = zip.file(target.startsWith("/") ? target.slice(1) : parts.join("/"));
36526
+ return media ? decodePng(await media.async("uint8array")) : void 0;
36527
+ }
35360
36528
  async function designLintPptx(bytes) {
35361
36529
  const zip = await import_jszip7.default.loadAsync(Buffer.from(bytes));
35362
36530
  const findings = [];
@@ -35367,9 +36535,15 @@ async function designLintPptx(bytes) {
35367
36535
  const presFile = zip.file("ppt/presentation.xml");
35368
36536
  const canvas = canvasFromPresentation(presFile ? await presFile.async("string") : void 0);
35369
36537
  const lang = detectDeckLang();
35370
- for (const { position, path } of await slidePartsInOrder(zip)) {
36538
+ for (const { position, path, relsPath } of await slidePartsInOrder(zip)) {
35371
36539
  const xml = await zip.file(path).async("string");
35372
- findings.push(...lintSlideXml(xml, position, masterBg, lang));
36540
+ const own = lintSlideXml(xml, position, masterBg, lang);
36541
+ findings.push(...own);
36542
+ if (!own.some((f) => f.code === "low-contrast-text")) {
36543
+ const backdrop = await backdropRaster(zip, xml, relsPath, canvas);
36544
+ if (backdrop)
36545
+ findings.push(...lintBackdropContrast(xml, position, backdrop, canvas));
36546
+ }
35373
36547
  findings.push(...lintSlideGeometry(xml, position, canvas));
35374
36548
  }
35375
36549
  return { ok: findings.length === 0, findings };
@@ -35381,7 +36555,7 @@ async function checkPptx(bytes, options = {}) {
35381
36555
  const report = { valid: validation.ok };
35382
36556
  if (!validation.ok) report.errors = validation.errors;
35383
36557
  if (options.strict && validation.ok) {
35384
- const schemaDir = process.env.TALOS_XSD_DIR || (0, import_node_path3.join)((0, import_node_path3.dirname)(process.argv[1]), "..", "schemas");
36558
+ const schemaDir = process.env.TALOS_XSD_DIR || (0, import_node_path4.join)((0, import_node_path4.dirname)(process.argv[1]), "..", "schemas");
35385
36559
  const strict = await validateOoxmlStrict(bytes, schemaDir);
35386
36560
  report.strict = strict.ok ? { ok: true } : { ok: false, errors: strict.errors };
35387
36561
  }
@@ -35389,6 +36563,24 @@ async function checkPptx(bytes, options = {}) {
35389
36563
  return report;
35390
36564
  }
35391
36565
 
36566
+ // apps/admin/data/skills.lib/document/bin-src/lib/user-styles.mjs
36567
+ var import_node_fs2 = require("node:fs");
36568
+ var import_node_path5 = require("node:path");
36569
+ var SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
36570
+ function userStylesDir() {
36571
+ return process.env.TALOS_DOC_STYLES_DIR?.trim() || null;
36572
+ }
36573
+ function userSkinDir(name) {
36574
+ const root = userStylesDir();
36575
+ if (!root || !SAFE_NAME.test(name)) return null;
36576
+ const dir = (0, import_node_path5.join)(root, "styles", name);
36577
+ return (0, import_node_fs2.existsSync)((0, import_node_path5.join)(dir, "skin.css")) ? dir : null;
36578
+ }
36579
+ function userSkinLookup(name) {
36580
+ const root = userStylesDir();
36581
+ return root ? (0, import_node_path5.join)(root, "styles", name, "skin.css") : null;
36582
+ }
36583
+
35392
36584
  // apps/admin/data/skills.lib/document/bin-src/html2pptx.mjs
35393
36585
  function imageResolver(baseDir) {
35394
36586
  return async (src) => {
@@ -35397,7 +36589,7 @@ function imageResolver(baseDir) {
35397
36589
  if (!r || !r.ok) return null;
35398
36590
  return { bytes: new Uint8Array(await r.arrayBuffer()), mime: (r.headers.get("content-type") || "").split(";")[0] || guessImageMime(src) };
35399
36591
  }
35400
- const p = (0, import_node_path4.isAbsolute)(src) ? src : (0, import_node_path4.join)(baseDir, src);
36592
+ const p = (0, import_node_path6.isAbsolute)(src) ? src : (0, import_node_path6.join)(baseDir, src);
35401
36593
  const buf = await (0, import_promises3.readFile)(p).catch(() => null);
35402
36594
  return buf ? { bytes: new Uint8Array(buf), mime: guessImageMime(p) } : null;
35403
36595
  };
@@ -35423,8 +36615,8 @@ function parsePreviewPages(spec) {
35423
36615
  var _tailwindCss;
35424
36616
  async function tailwindCss() {
35425
36617
  if (_tailwindCss !== void 0) return _tailwindCss;
35426
- const file = process.env.TALOS_TAILWIND_CSS || (0, import_node_path4.join)((0, import_node_path4.dirname)(process.argv[1]), "..", "assets", "tailwind.min.css");
35427
- _tailwindCss = (0, import_node_fs2.existsSync)(file) ? await (0, import_promises3.readFile)(file, "utf8").catch(() => "") : "";
36618
+ const file = process.env.TALOS_TAILWIND_CSS || (0, import_node_path6.join)((0, import_node_path6.dirname)(process.argv[1]), "..", "assets", "tailwind.min.css");
36619
+ _tailwindCss = (0, import_node_fs3.existsSync)(file) ? await (0, import_promises3.readFile)(file, "utf8").catch(() => "") : "";
35428
36620
  return _tailwindCss;
35429
36621
  }
35430
36622
  async function withHeadlessPage(html, fn, density = DOCUMENT_CAPTURE_DENSITY) {
@@ -35439,6 +36631,8 @@ async function withHeadlessPage(html, fn, density = DOCUMENT_CAPTURE_DENSITY) {
35439
36631
  await page.evaluate("document.fonts && document.fonts.ready").catch(() => {
35440
36632
  });
35441
36633
  const adapter = {
36634
+ // Part of the backdrop-cache salt: another Chromium may paint other pixels.
36635
+ browserVersion: browser.version(),
35442
36636
  evaluate: (script) => page.evaluate(script),
35443
36637
  screenshot: async (clip) => {
35444
36638
  const png = await page.screenshot({ clip });
@@ -35495,13 +36689,25 @@ body{width:1280px;margin:0}
35495
36689
  section.slide{position:relative;width:1280px;height:720px;overflow:hidden;
35496
36690
  background:var(--slide-bg,#ffffff);color:var(--body-color,#1f2328);
35497
36691
  font-family:var(--font-body,-apple-system,'Helvetica Neue','PingFang SC','Microsoft YaHei',sans-serif)}
35498
- section.slide h1,section.slide h2,section.slide h3{color:var(--title-color,#1a1a1a);
36692
+ section.slide h1,section.slide h2,section.slide h3{
35499
36693
  font-family:var(--font-heading,var(--font-body,-apple-system,'PingFang SC',sans-serif))}
36694
+ /* Title colour from the theme vars \u2014 except text laid over a full-bleed photo
36695
+ (.overlay-title): that follows the on-image rule (the skin's .overlay-title, else
36696
+ _contract-defaults.css), never the slide's title colour. Without the exemption
36697
+ this rule out-specifies the bare class and a light skin's photo title came out
36698
+ dark-on-dark. :where() keeps the specificity at (0,1,2) so every other skin
36699
+ rule wins or loses exactly as before. Mirrored in scripts/lib/deck-compose.mjs. */
36700
+ section.slide :is(h1,h2,h3):where(:not(.overlay-title)){color:var(--title-color,#1a1a1a)}
35500
36701
  /* Structural type scale (D2, mirrors Manus's [size=N]): a constrained 7-step
35501
36702
  ladder so titles/body/captions land on-scale instead of ad-hoc font sizes.
35502
36703
  Author with size="1".."7"; the walker measures the computed px \u2192 native pt. */
35503
36704
  [size="1"]{font-size:12px}[size="2"]{font-size:14px}[size="3"]{font-size:16px}
35504
36705
  [size="4"]{font-size:24px}[size="5"]{font-size:32px}[size="6"]{font-size:48px}[size="7"]{font-size:72px}
36706
+ /* A chart box with nothing to plot (markEmptyCharts): a light dashed frame + label \u2014
36707
+ and a contrast .panel around it (stats-chart) steps back, so the slide never shows
36708
+ a solid block of colour where the chart should be. */
36709
+ [data-chart-empty]{display:flex;align-items:center;justify-content:center;border:1px dashed var(--muted,#9aa0a6);border-radius:var(--card-radius,6px);color:var(--muted,#9aa0a6);font-size:16px;letter-spacing:.08em}
36710
+ .panel:has([data-chart-empty]){background:transparent!important;box-shadow:none!important;border-color:transparent!important}
35505
36711
  `;
35506
36712
  function injectHead(html, extra) {
35507
36713
  if (html.includes("</head>")) return html.replace("</head>", `${extra}</head>`);
@@ -35549,6 +36755,9 @@ async function main() {
35549
36755
  const mediaStyleArg = mediaIdx >= 0 ? args[mediaIdx + 1] : void 0;
35550
36756
  const bdIdx = args.indexOf("--backdrop");
35551
36757
  const bdVariant = bdIdx >= 0 ? args[bdIdx + 1] : void 0;
36758
+ const bdCacheIdx = args.indexOf("--backdrop-cache");
36759
+ const bdCacheDir = bdCacheIdx >= 0 ? args[bdCacheIdx + 1] : void 0;
36760
+ const bdCacheVerify = args.includes("--backdrop-cache-verify");
35552
36761
  const lineIdx = args.indexOf("--line");
35553
36762
  const lineArg = lineIdx >= 0 ? args[lineIdx + 1] : void 0;
35554
36763
  const notesIdx = args.indexOf("--notes-doc");
@@ -35561,8 +36770,8 @@ async function main() {
35561
36770
  const previewDensityIdx = args.indexOf("--preview-density");
35562
36771
  const previewDensityArg = previewDensityIdx >= 0 ? args[previewDensityIdx + 1] : void 0;
35563
36772
  const previewDensity = previewDensityArg === void 0 ? DOCUMENT_CAPTURE_DENSITY : Number(previewDensityArg);
35564
- const consumed = /* @__PURE__ */ new Set([themeIdx, themeIdx >= 0 ? themeIdx + 1 : -1, styleIdx, styleIdx >= 0 ? styleIdx + 1 : -1, mediaIdx, mediaIdx >= 0 ? mediaIdx + 1 : -1, bdIdx, bdIdx >= 0 ? bdIdx + 1 : -1, lineIdx, lineIdx >= 0 ? lineIdx + 1 : -1, modeIdx, modeIdx >= 0 ? modeIdx + 1 : -1, notesIdx, notesIdx >= 0 ? notesIdx + 1 : -1, previewIdx, previewIdx >= 0 ? previewIdx + 1 : -1, previewPagesIdx, previewPagesIdx >= 0 ? previewPagesIdx + 1 : -1, previewDensityIdx, previewDensityIdx >= 0 ? previewDensityIdx + 1 : -1]);
35565
- const positional = args.filter((a, i) => a !== "--rasterize" && a !== "--editable" && a !== "--theme-v2" && !consumed.has(i));
36773
+ const consumed = /* @__PURE__ */ new Set([themeIdx, themeIdx >= 0 ? themeIdx + 1 : -1, styleIdx, styleIdx >= 0 ? styleIdx + 1 : -1, mediaIdx, mediaIdx >= 0 ? mediaIdx + 1 : -1, bdIdx, bdIdx >= 0 ? bdIdx + 1 : -1, lineIdx, lineIdx >= 0 ? lineIdx + 1 : -1, modeIdx, modeIdx >= 0 ? modeIdx + 1 : -1, notesIdx, notesIdx >= 0 ? notesIdx + 1 : -1, previewIdx, previewIdx >= 0 ? previewIdx + 1 : -1, previewPagesIdx, previewPagesIdx >= 0 ? previewPagesIdx + 1 : -1, previewDensityIdx, previewDensityIdx >= 0 ? previewDensityIdx + 1 : -1, bdCacheIdx, bdCacheIdx >= 0 ? bdCacheIdx + 1 : -1]);
36774
+ const positional = args.filter((a, i) => a !== "--rasterize" && a !== "--editable" && a !== "--theme-v2" && a !== "--backdrop-cache-verify" && !consumed.has(i));
35566
36775
  const [input, out] = positional;
35567
36776
  if (!input || !out && !previewDir) die("usage: html2pptx <in.html|in.layout.json> [out.pptx|out.pdf] [--theme <theme.json>] [--style <name|dir>] [--theme-v2] [--media-style <id>] [--backdrop <variant>] [--line <name|none>] [--editable] [--rasterize] [--notes-doc <out.md>] [--preview-dir <dir>] [--preview-pages <all|N,N-M>] [--preview-density <1..4>]", 64);
35568
36777
  if (previewPagesArg && !previewDir) die("--preview-pages requires --preview-dir", 64);
@@ -35586,6 +36795,8 @@ async function main() {
35586
36795
  die(`not valid JSON: ${e.message}`, 65);
35587
36796
  }
35588
36797
  const { bytes: bytes2, warnings: warnings2 } = await convertLayoutToPptx(snapshot2).catch((e) => die(e.message, 65));
36798
+ const inert = [themePath && "--theme", styleArg && "--style", themeV2 && "--theme-v2", mediaStyleArg && "--media-style"].filter(Boolean);
36799
+ if (inert.length) warnings2.unshift(`${inert.join(" / ")} ignored: a .layout.json snapshot is already styled \u2014 pass them when rendering the HTML instead`);
35589
36800
  for (const w of [...snapshot2.notes ?? [], ...warnings2]) process.stderr.write(`html2pptx: ${w}
35590
36801
  `);
35591
36802
  await (0, import_promises3.writeFile)(out, Buffer.from(bytes2));
@@ -35616,40 +36827,42 @@ async function main() {
35616
36827
  themeCss = themeCssVars(theme);
35617
36828
  }
35618
36829
  let skinCss = "", backdropHtml = "", skinAccent = "", skinAxis = "", skinChart1 = "";
36830
+ let backdropVariant;
35619
36831
  if (styleArg) {
35620
- const skillDir = (0, import_node_path4.dirname)((0, import_node_path4.dirname)(process.argv[1] || ""));
36832
+ const skillDir = (0, import_node_path6.dirname)((0, import_node_path6.dirname)(process.argv[1] || ""));
35621
36833
  let styleDir, cssName = "skin.css", entry = null, catalog = null;
35622
- if ((0, import_node_fs2.existsSync)((0, import_node_path4.join)(styleArg, "skin.css"))) {
36834
+ if ((0, import_node_fs3.existsSync)((0, import_node_path6.join)(styleArg, "skin.css"))) {
35623
36835
  styleDir = styleArg;
35624
- } else if ((0, import_node_fs2.existsSync)((0, import_node_path4.join)(styleArg, "pack.css"))) {
36836
+ } else if ((0, import_node_fs3.existsSync)((0, import_node_path6.join)(styleArg, "pack.css"))) {
35625
36837
  styleDir = styleArg;
35626
36838
  cssName = "pack.css";
35627
36839
  } else {
35628
- const catalogPath = (0, import_node_path4.join)(skillDir, "catalog.json");
35629
- if ((0, import_node_fs2.existsSync)(catalogPath)) {
36840
+ const catalogPath = (0, import_node_path6.join)(skillDir, "catalog.json");
36841
+ if ((0, import_node_fs3.existsSync)(catalogPath)) {
35630
36842
  try {
35631
- ({ catalog } = parseCatalog(JSON.parse((0, import_node_fs2.readFileSync)(catalogPath, "utf8"))));
36843
+ ({ catalog } = parseCatalog(JSON.parse((0, import_node_fs3.readFileSync)(catalogPath, "utf8"))));
35632
36844
  if (catalog) entry = resolveEntry(catalog, styleArg);
35633
36845
  } catch {
35634
36846
  }
35635
36847
  }
35636
- styleDir = entry ? (0, import_node_path4.join)(skillDir, assetDirOf(entry)) : (0, import_node_path4.join)(skillDir, "styles", styleArg);
36848
+ styleDir = entry ? (0, import_node_path6.join)(skillDir, assetDirOf(entry)) : userSkinDir(styleArg) ?? (0, import_node_path6.join)(skillDir, "styles", styleArg);
35637
36849
  if (entry) cssName = cssFileOf(entry);
35638
36850
  }
35639
- const skinPath = (0, import_node_path4.join)(styleDir, cssName);
36851
+ const skinPath = (0, import_node_path6.join)(styleDir, cssName);
35640
36852
  try {
35641
36853
  skinCss = await (0, import_promises3.readFile)(skinPath, "utf8");
35642
36854
  } catch (e) {
35643
- die(`cannot read style ${skinPath}: ${e.message}`, 66);
36855
+ const user = userSkinLookup(styleArg);
36856
+ die(`cannot read style ${skinPath}: ${e.message}${user && !(0, import_node_path6.isAbsolute)(styleArg) ? ` (not a catalog id, and no user style at ${user})` : ""}`, 66);
35644
36857
  }
35645
- const defaultsPath = (0, import_node_path4.join)(skillDir, "styles", "_contract-defaults.css");
36858
+ const defaultsPath = (0, import_node_path6.join)(skillDir, "styles", "_contract-defaults.css");
35646
36859
  let v2Css = "";
35647
36860
  if (themeV2) {
35648
36861
  if (!entry) die("--theme-v2 requires a catalog style id (explicit style directories are V1 only)", 64);
35649
36862
  if (catalog && renderModeOf(catalog, entry.id) === "classic-v1") {
35650
36863
  die(`style "${entry.id}" is frozen in collection "original-13" as classic-v1; omit --theme-v2 and --media-style`, 64);
35651
36864
  }
35652
- const registryPath = (0, import_node_path4.join)(skillDir, "theme-system.json");
36865
+ const registryPath = (0, import_node_path6.join)(skillDir, "theme-system.json");
35653
36866
  let parsed;
35654
36867
  try {
35655
36868
  parsed = parseThemeSystem(JSON.parse(await (0, import_promises3.readFile)(registryPath, "utf8")));
@@ -35659,45 +36872,51 @@ async function main() {
35659
36872
  if (!parsed.system) die(`invalid Theme V2 registry: ${parsed.errors.join("; ")}`, 66);
35660
36873
  const layers = resolveThemeLayers(parsed.system, entry.id, mediaStyleArg);
35661
36874
  if (!layers) die(`Theme V2 cannot resolve style "${entry.id}"${mediaStyleArg ? ` with media style "${mediaStyleArg}"` : ""}`, 66);
35662
- for (const rel of layers.paths) v2Css += await (0, import_promises3.readFile)((0, import_node_path4.join)(skillDir, rel), "utf8") + "\n";
36875
+ for (const rel of layers.paths) v2Css += await (0, import_promises3.readFile)((0, import_node_path6.join)(skillDir, rel), "utf8") + "\n";
35663
36876
  process.stderr.write(`html2pptx: Theme V2 ${entry.id} = components:${layers.componentProfile} + media:${layers.mediaStyle} + art:${layers.artDirection}
35664
36877
  `);
35665
36878
  }
35666
- skinCss = ((0, import_node_fs2.existsSync)(defaultsPath) ? await (0, import_promises3.readFile)(defaultsPath, "utf8") + "\n" : "") + v2Css + skinCss;
35667
- const sharedBackdropsDir = (0, import_node_path4.join)(skillDir, "styles", "_backdrops");
36879
+ skinCss = ((0, import_node_fs3.existsSync)(defaultsPath) ? await (0, import_promises3.readFile)(defaultsPath, "utf8") + "\n" : "") + v2Css + skinCss;
36880
+ const sharedBackdropsDir = (0, import_node_path6.join)(skillDir, "styles", "_backdrops");
35668
36881
  const NO_AUTO_BACKDROP_VARIETY = /* @__PURE__ */ new Set(["mckinsey", "mono", "cinereel"]);
35669
36882
  let bdPath;
35670
- if (bdVariant) {
35671
- const ownVariantPath = (0, import_node_path4.join)(styleDir, `backdrop-${bdVariant}.html`);
35672
- const sharedVariantPath = (0, import_node_path4.join)(sharedBackdropsDir, `${bdVariant}.html`);
35673
- if ((0, import_node_fs2.existsSync)(ownVariantPath)) bdPath = ownVariantPath;
35674
- else if ((0, import_node_fs2.existsSync)(sharedVariantPath)) bdPath = sharedVariantPath;
36883
+ if (bdVariant === "default") {
36884
+ bdPath = (0, import_node_path6.join)(styleDir, "backdrop.html");
36885
+ } else if (bdVariant) {
36886
+ const ownVariantPath = (0, import_node_path6.join)(styleDir, `backdrop-${bdVariant}.html`);
36887
+ const sharedVariantPath = (0, import_node_path6.join)(sharedBackdropsDir, `${bdVariant}.html`);
36888
+ if ((0, import_node_fs3.existsSync)(ownVariantPath)) bdPath = ownVariantPath;
36889
+ else if ((0, import_node_fs3.existsSync)(sharedVariantPath)) bdPath = sharedVariantPath;
35675
36890
  else {
35676
36891
  process.stderr.write(`html2pptx: backdrop variant "${bdVariant}" not found for ${styleArg}, using default backdrop.html
35677
36892
  `);
35678
- bdPath = (0, import_node_path4.join)(styleDir, "backdrop.html");
36893
+ bdPath = (0, import_node_path6.join)(styleDir, "backdrop.html");
35679
36894
  }
35680
- } else if (themeV2 || NO_AUTO_BACKDROP_VARIETY.has((0, import_node_path4.basename)(styleDir))) {
35681
- bdPath = (0, import_node_path4.join)(styleDir, "backdrop.html");
36895
+ } else if (themeV2 || NO_AUTO_BACKDROP_VARIETY.has((0, import_node_path6.basename)(styleDir))) {
36896
+ bdPath = (0, import_node_path6.join)(styleDir, "backdrop.html");
35682
36897
  } else {
35683
- const ownVariants = (0, import_node_fs2.existsSync)(styleDir) ? (0, import_node_fs2.readdirSync)(styleDir).filter((f) => /^backdrop.*\.html$/.test(f)).map((f) => (0, import_node_path4.join)(styleDir, f)) : [];
35684
- const sharedVariants = (0, import_node_fs2.existsSync)(sharedBackdropsDir) ? (0, import_node_fs2.readdirSync)(sharedBackdropsDir).filter((f) => f.endsWith(".html")).map((f) => (0, import_node_path4.join)(sharedBackdropsDir, f)) : [];
36898
+ const ownVariants = (0, import_node_fs3.existsSync)(styleDir) ? (0, import_node_fs3.readdirSync)(styleDir).filter((f) => /^backdrop.*\.html$/.test(f)).map((f) => (0, import_node_path6.join)(styleDir, f)) : [];
36899
+ const sharedVariants = (0, import_node_fs3.existsSync)(sharedBackdropsDir) ? (0, import_node_fs3.readdirSync)(sharedBackdropsDir).filter((f) => f.endsWith(".html")).map((f) => (0, import_node_path6.join)(sharedBackdropsDir, f)) : [];
35685
36900
  const pool = [...ownVariants, ...sharedVariants].sort();
35686
36901
  if (pool.length > 0) {
35687
36902
  let h = 0;
35688
36903
  for (let i = 0; i < raw.length; i++) h = h * 31 + raw.charCodeAt(i) >>> 0;
35689
36904
  bdPath = pool[h % pool.length];
35690
36905
  } else {
35691
- bdPath = (0, import_node_path4.join)(styleDir, "backdrop.html");
36906
+ bdPath = (0, import_node_path6.join)(styleDir, "backdrop.html");
35692
36907
  }
35693
36908
  }
35694
- if ((0, import_node_fs2.existsSync)(bdPath)) backdropHtml = await (0, import_promises3.readFile)(bdPath, "utf8");
36909
+ if ((0, import_node_fs3.existsSync)(bdPath)) {
36910
+ backdropHtml = await (0, import_promises3.readFile)(bdPath, "utf8");
36911
+ const name = (0, import_node_path6.basename)(bdPath, ".html");
36912
+ backdropVariant = name === "backdrop" ? "default" : name.replace(/^backdrop-/, "");
36913
+ }
35695
36914
  const stripLine = (h) => h.replace(/<div class="bd-arc"[\s\S]*?<\/svg><\/div>/g, "");
35696
36915
  if (lineArg === "none") {
35697
36916
  backdropHtml = stripLine(backdropHtml);
35698
36917
  } else if (lineArg) {
35699
- const linePath = (0, import_node_path4.join)(skillDir, "styles", "_lines", `${lineArg}.html`);
35700
- if ((0, import_node_fs2.existsSync)(linePath)) backdropHtml = stripLine(backdropHtml) + await (0, import_promises3.readFile)(linePath, "utf8");
36918
+ const linePath = (0, import_node_path6.join)(skillDir, "styles", "_lines", `${lineArg}.html`);
36919
+ if ((0, import_node_fs3.existsSync)(linePath)) backdropHtml = stripLine(backdropHtml) + await (0, import_promises3.readFile)(linePath, "utf8");
35701
36920
  else process.stderr.write(`html2pptx: line motif "${lineArg}" not found in styles/_lines, keeping skin default line
35702
36921
  `);
35703
36922
  }
@@ -35705,22 +36924,37 @@ async function main() {
35705
36924
  skinAxis = (skinCss.match(/--muted\s*:\s*([^;}]+)/) || [])[1]?.trim() || "";
35706
36925
  skinChart1 = (skinCss.match(/--chart-1\s*:\s*([^;}]+)/) || [])[1]?.trim() || skinAccent;
35707
36926
  }
35708
- let { html: rawInlined, inlined, skipped } = await inlineHtmlImages(raw, imageResolver((0, import_node_path4.dirname)(input)));
36927
+ if (themePath && styleArg) {
36928
+ const w = `--theme ${(0, import_node_path6.basename)(themePath)} has no effect on colours/fonts: --style ${styleArg}${themeV2 ? " (Theme V2)" : ""} redefines the same tokens and overrides it. To recolour a --style deck, save the theme's palette as a user skin ($TALOS_DOC_STYLES_DIR/styles/<name>/skin.css \u2014 deck-design.md "Custom skins") and pass --style <name> without --theme`;
36929
+ process.stderr.write(`html2pptx: ${w}
36930
+ `);
36931
+ colorWarnings.push(w);
36932
+ }
36933
+ let { html: rawInlined, inlined, skipped } = await inlineHtmlImages(raw, imageResolver((0, import_node_path6.dirname)(input)));
35709
36934
  if (inlined) process.stderr.write(`html2pptx: inlined ${inlined} image(s) as data: URIs
35710
36935
  `);
35711
36936
  for (const s of skipped) process.stderr.write(`html2pptx: <img src="${s}"> not found/unfetchable \u2014 the render will drop it
35712
36937
  `);
35713
36938
  if (styleArg) {
35714
- const artLayer = themeV2 ? '<div class="theme-art-layer" aria-hidden="true"><i class="art-shape art-a"></i><i class="art-shape art-b"></i><i class="art-shape art-c"></i><i class="art-pattern"></i></div>' : "";
36939
+ const artLayer = themeV2 ? '<div class="theme-art-layer" aria-hidden="true"><i class="art-slot art-slot-a"><i class="art-shape art-a"></i></i><i class="art-slot art-slot-b"><i class="art-shape art-b"></i></i><i class="art-slot art-slot-c"><i class="art-shape art-c"></i></i><i class="art-slot art-slot-pattern"><i class="art-pattern"></i></i></div>' : "";
35715
36940
  if (backdropHtml || artLayer) rawInlined = rawInlined.replace(/<div class="slide-backdrop">\s*<\/div>/g, `<div class="slide-backdrop">${backdropHtml}${artLayer}</div>`);
35716
36941
  if (skinAccent) rawInlined = rawInlined.replaceAll("__ACCENT__", skinAccent);
35717
36942
  if (skinAxis) rawInlined = rawInlined.replaceAll("__AXIS__", skinAxis);
35718
36943
  if (skinChart1) rawInlined = rawInlined.replaceAll("__CHART1__", skinChart1);
35719
36944
  }
36945
+ const charts = markEmptyCharts(rawInlined);
36946
+ rawInlined = charts.html;
36947
+ charts.empty.forEach((why, i) => {
36948
+ const w = `empty chart ${i + 1} of ${charts.empty.length}: ${why} \u2014 drawn as a dashed "\u56FE\u8868\u5360\u4F4D" placeholder; give it {type, labels, series:[{values}]}`;
36949
+ process.stderr.write(`html2pptx: ${w}
36950
+ `);
36951
+ colorWarnings.push(w);
36952
+ });
35720
36953
  const tw = await tailwindCss();
35721
- const cjk = !hasService && CJK_RE2.test(rawInlined) ? await cjkFontFaceCss(rawInlined + skinCss + themeCss + SKELETON_CSS) : "";
36954
+ const cjk = !hasService && CJK_RE.test(rawInlined) ? await cjkFontFaceCss(rawInlined + skinCss + themeCss + SKELETON_CSS) : "";
35722
36955
  const latin = await latinFontFaceCss(rawInlined + skinCss);
35723
36956
  const fontCss = cjk + latin;
36957
+ const backdropSalt = (0, import_node_crypto2.createHash)("sha256").update(String(DOCUMENT_CAPTURE_DENSITY)).update("\0").update(fontCss).update("\0").update(themeCss).update("\0").update(SKELETON_CSS).update("\0").update(skinCss).digest("hex");
35724
36958
  const html = injectHead(rawInlined, `${fontCss ? `<style>${fontCss}</style>` : ""}${tw ? `<style>${tw}</style>` : ""}<style>${themeCss}${SKELETON_CSS}${skinCss}</style>`);
35725
36959
  if (previewDir) {
35726
36960
  let images;
@@ -35736,15 +36970,15 @@ async function main() {
35736
36970
  const pageNumbers = previewIndexes ? previewIndexes.map((i) => i + 1) : images.map((_, i) => i + 1);
35737
36971
  const files = [];
35738
36972
  for (let i = 0; i < images.length; i++) {
35739
- const path = (0, import_node_path4.join)(previewDir, `slide-${String(pageNumbers[i]).padStart(3, "0")}.png`);
36973
+ const path = (0, import_node_path6.join)(previewDir, `slide-${String(pageNumbers[i]).padStart(3, "0")}.png`);
35740
36974
  await (0, import_promises3.writeFile)(path, Buffer.from(images[i], "base64"));
35741
36975
  files.push(path);
35742
36976
  }
35743
- process.stdout.write(JSON.stringify({ ok: true, engine: hasService ? "preview" : "preview (headless)", slides: images.length, pages: pageNumbers, density: previewDensity, files }, null, 1) + "\n");
36977
+ process.stdout.write(JSON.stringify({ ok: true, engine: hasService ? "preview" : "preview (headless)", slides: images.length, pages: pageNumbers, density: previewDensity, files, warnings: colorWarnings }, null, 1) + "\n");
35744
36978
  return;
35745
36979
  }
35746
36980
  if (pdfOut) {
35747
- const printHtml = injectHead(html, `<style>${PRINT_CSS}</style>`);
36981
+ const printHtml = injectHead(withoutPrintNoise(html), `<style>${PRINT_CSS}</style>`);
35748
36982
  let pdf;
35749
36983
  if (hasService) pdf = await printService(printHtml).catch((e) => die(e.message, 1));
35750
36984
  else {
@@ -35771,13 +37005,17 @@ async function main() {
35771
37005
  const bytes2 = await imagesToPptx(images).catch((e) => die(e.message, 1));
35772
37006
  await (0, import_promises3.writeFile)(out, Buffer.from(bytes2));
35773
37007
  process.stderr.write("html2pptx: --rasterize output is image slides \u2014 pixel-perfect but NOT editable/searchable text\n");
35774
- await reportDeck({ ok: true, out, engine: hasService ? "rasterize" : "rasterize (headless)", slides: images.length }, bytes2);
37008
+ await reportDeck({ ok: true, out, engine: hasService ? "rasterize" : "rasterize (headless)", slides: images.length, warnings: colorWarnings }, bytes2);
35775
37009
  return;
35776
37010
  }
35777
37011
  let snapshot = null;
35778
37012
  let engine = "layout";
35779
37013
  const suffix = hasService ? "" : " (headless)";
35780
- const getLayout = (backdrop) => hasService ? callService("/layout", html, backdrop ? { backdrop: true } : {}) : withHeadlessPage(html, (p) => snapshotHeadless(p, { backdrop }));
37014
+ const getLayout = (backdrop) => hasService ? callService("/layout", html, backdrop ? { backdrop: true } : {}) : withHeadlessPage(html, (p) => snapshotHeadless(p, {
37015
+ backdrop,
37016
+ // Hybrid caches its backdrops; --editable its decoration-layer pictures (own key space).
37017
+ ...bdCacheDir ? { cache: fsBackdropCache(bdCacheDir, `${backdropSalt}\0${p.browserVersion}`, bdCacheVerify) } : {}
37018
+ }));
35781
37019
  if (hybrid) {
35782
37020
  try {
35783
37021
  snapshot = await getLayout(true);
@@ -35793,13 +37031,26 @@ async function main() {
35793
37031
  if (engine === "layout") engine = "layout" + suffix;
35794
37032
  const { bytes, warnings } = await convertLayoutToPptx(snapshot).catch((e) => die(e.message, 65));
35795
37033
  const allWarnings = [...colorWarnings, ...snapshot.notes ?? [], ...warnings];
35796
- for (const w of allWarnings) process.stderr.write(`html2pptx: ${w}
37034
+ for (const w of allWarnings.slice(colorWarnings.length)) process.stderr.write(`html2pptx: ${w}
35797
37035
  `);
35798
37036
  await (0, import_promises3.writeFile)(out, Buffer.from(bytes));
35799
37037
  if (notesDocPath) await writeNotesDoc(notesDocPath, snapshot.slides).catch((e) => process.stderr.write(`html2pptx: notes-doc: ${e.message}
35800
37038
  `));
35801
37039
  if (engine.startsWith("hybrid")) process.stderr.write("html2pptx: \u9ED8\u8BA4 hybrid \u2014 \u6BCF\u9875\u89C6\u89C9\u4E3A\u9AD8\u4FDD\u771F\u6805\u683C\u5E95\u56FE,\u6587\u5B57\u4FDD\u6301\u539F\u751F\u53EF\u7F16\u8F91;\u9700\u7EAF\u53EF\u7F16\u8F91\u7528 --editable\n");
35802
- await reportDeck({ ok: true, out, engine, slides: snapshot.slides?.length ?? 0, ...notesDocPath ? { notesDoc: notesDocPath } : {}, warnings: allWarnings }, bytes);
37040
+ await reportDeck({ ok: true, out, engine, slides: snapshot.slides?.length ?? 0, ...backdropVariant ? { backdrop: backdropVariant } : {}, ...notesDocPath ? { notesDoc: notesDocPath } : {}, warnings: allWarnings }, bytes);
37041
+ }
37042
+ function fsBackdropCache(dir, salt, verify) {
37043
+ return {
37044
+ salt,
37045
+ verify,
37046
+ get: (key) => (0, import_promises3.readFile)((0, import_node_path6.join)(dir, `${key}.png`)).then((b) => new Uint8Array(b)).catch(() => void 0),
37047
+ set: async (key, png) => {
37048
+ await (0, import_promises3.mkdir)(dir, { recursive: true });
37049
+ const tmp = (0, import_node_path6.join)(dir, `${key}.${process.pid}.tmp`);
37050
+ await (0, import_promises3.writeFile)(tmp, png);
37051
+ await (0, import_promises3.rename)(tmp, (0, import_node_path6.join)(dir, `${key}.png`));
37052
+ }
37053
+ };
35803
37054
  }
35804
37055
  async function reportDeck(result, bytes) {
35805
37056
  const check = await checkPptx(bytes);