@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
@@ -315,7 +315,7 @@ var require_BufferList = __commonJS({
315
315
  this.head = this.tail = null;
316
316
  this.length = 0;
317
317
  };
318
- BufferList.prototype.join = function join3(s) {
318
+ BufferList.prototype.join = function join4(s) {
319
319
  if (this.length === 0) return "";
320
320
  var p = this.head;
321
321
  var ret = "" + p.data;
@@ -18354,14 +18354,14 @@ var require_decode = __commonJS({
18354
18354
  var LZWStream_1 = tslib_1.__importDefault(require_LZWStream());
18355
18355
  var RunLengthStream_1 = tslib_1.__importDefault(require_RunLengthStream());
18356
18356
  var Stream_1 = tslib_1.__importDefault(require_Stream());
18357
- var decodeStream = function(stream, encoding, params) {
18357
+ var decodeStream = function(stream, encoding, params2) {
18358
18358
  if (encoding === PDFName_1.default.of("FlateDecode")) {
18359
18359
  return new FlateStream_1.default(stream);
18360
18360
  }
18361
18361
  if (encoding === PDFName_1.default.of("LZWDecode")) {
18362
18362
  var earlyChange = 1;
18363
- if (params instanceof PDFDict_1.default) {
18364
- var EarlyChange = params.lookup(PDFName_1.default.of("EarlyChange"));
18363
+ if (params2 instanceof PDFDict_1.default) {
18364
+ var EarlyChange = params2.lookup(PDFName_1.default.of("EarlyChange"));
18365
18365
  if (EarlyChange instanceof PDFNumber_1.default) {
18366
18366
  earlyChange = EarlyChange.asNumber();
18367
18367
  }
@@ -22243,11 +22243,11 @@ var require_svgPath = __commonJS({
22243
22243
  var args = [];
22244
22244
  var curArg = "";
22245
22245
  var foundDecimal = false;
22246
- var params = 0;
22246
+ var params2 = 0;
22247
22247
  for (var _i = 0, path_1 = path; _i < path_1.length; _i++) {
22248
22248
  var c = path_1[_i];
22249
22249
  if (parameters.has(c)) {
22250
- params = parameters.get(c);
22250
+ params2 = parameters.get(c);
22251
22251
  if (cmd) {
22252
22252
  if (curArg.length > 0) {
22253
22253
  args[args.length] = +curArg;
@@ -22262,7 +22262,7 @@ var require_svgPath = __commonJS({
22262
22262
  if (curArg.length === 0) {
22263
22263
  continue;
22264
22264
  }
22265
- if (args.length === params) {
22265
+ if (args.length === params2) {
22266
22266
  ret[ret.length] = { cmd, args };
22267
22267
  args = [+curArg];
22268
22268
  if (cmd === "M") {
@@ -22284,7 +22284,7 @@ var require_svgPath = __commonJS({
22284
22284
  }
22285
22285
  }
22286
22286
  if (curArg.length > 0) {
22287
- if (args.length === params) {
22287
+ if (args.length === params2) {
22288
22288
  ret[ret.length] = { cmd, args };
22289
22289
  args = [+curArg];
22290
22290
  if (cmd === "M") {
@@ -27581,9 +27581,396 @@ var require_cjs = __commonJS({
27581
27581
  }
27582
27582
  });
27583
27583
 
27584
+ // packages/core/dist/document/pdf-encrypted.js
27585
+ var ENCRYPTED_PDF_MESSAGE, PdfEncryptedError;
27586
+ var init_pdf_encrypted = __esm({
27587
+ "packages/core/dist/document/pdf-encrypted.js"() {
27588
+ "use strict";
27589
+ ENCRYPTED_PDF_MESSAGE = "PDF is password-protected (encrypted); decrypt first: pdf-ops decrypt <in> <password> <out>";
27590
+ PdfEncryptedError = class extends Error {
27591
+ constructor() {
27592
+ super(ENCRYPTED_PDF_MESSAGE);
27593
+ this.name = "PdfEncryptedError";
27594
+ }
27595
+ };
27596
+ }
27597
+ });
27598
+
27599
+ // packages/core/dist/document/pdf-extract.js
27600
+ var pdf_extract_exports = {};
27601
+ __export(pdf_extract_exports, {
27602
+ analyzePdfDocument: () => analyzePdfDocument,
27603
+ countVectorOperators: () => countVectorOperators,
27604
+ decryptPdf: () => decryptPdf,
27605
+ encryptPdf: () => encryptPdf,
27606
+ extractPdfImages: () => extractPdfImages,
27607
+ extractPdfTables: () => extractPdfTables,
27608
+ extractPdfText: () => extractPdfText,
27609
+ extractPdfTextLines: () => extractPdfTextLines,
27610
+ pdfNeedsPassword: () => pdfNeedsPassword,
27611
+ renderPdfPages: () => renderPdfPages,
27612
+ scanPdfSignals: () => scanPdfSignals,
27613
+ textMathHint: () => textMathHint,
27614
+ textTableHint: () => textTableHint
27615
+ });
27616
+ function loadMupdf() {
27617
+ if (!_mupdf) {
27618
+ const deps = process.env.TALOS_SKILL_DEPS;
27619
+ const spec = deps ? (0, import_node_url.pathToFileURL)((0, import_node_path.join)(deps, "mupdf", "dist", "mupdf.js")).href : "mupdf";
27620
+ _mupdf = import(spec);
27621
+ }
27622
+ return _mupdf;
27623
+ }
27624
+ function textMathHint(text) {
27625
+ if (!text)
27626
+ return false;
27627
+ const hits = (text.match(MATH_CHARS) || []).length;
27628
+ return hits >= 3 || hits / Math.max(text.length, 1) > 0.02;
27629
+ }
27630
+ function textTableHint(text) {
27631
+ if (!text)
27632
+ return false;
27633
+ const gaps = (text.match(/ {2,}|\t/g) || []).length;
27634
+ return gaps >= 6;
27635
+ }
27636
+ async function openDoc(bytes) {
27637
+ const mupdf = await loadMupdf();
27638
+ const doc = mupdf.Document.openDocument(bytes, "application/pdf");
27639
+ if (doc.needsPassword())
27640
+ throw new PdfEncryptedError();
27641
+ if (doc.countPages() === 0) {
27642
+ throw new Error("PDF has no readable pages (corrupt, encrypted, or empty)");
27643
+ }
27644
+ return { mupdf, doc };
27645
+ }
27646
+ async function analyzePdfDocument(bytes, options = {}) {
27647
+ const wantText = options.text !== false;
27648
+ const wantSignals = options.signals === true;
27649
+ const { doc } = await openDoc(bytes);
27650
+ const pageCount = doc.countPages();
27651
+ const signals = [];
27652
+ let out = "";
27653
+ for (let i = 0; i < pageCount; i++) {
27654
+ const page = doc.loadPage(i);
27655
+ if (wantText) {
27656
+ out += `
27657
+ [page ${i + 1}]
27658
+ ${page.toStructuredText().asText()}`;
27659
+ }
27660
+ if (wantSignals) {
27661
+ signals.push(pageSignalsFrom(page, i + 1));
27662
+ }
27663
+ }
27664
+ return { text: wantText ? out.trim() : "", pageCount, signals };
27665
+ }
27666
+ async function extractPdfText(bytes) {
27667
+ const { text, pageCount } = await analyzePdfDocument(bytes, { text: true });
27668
+ return { text, pageCount };
27669
+ }
27670
+ function countVectorOperators(content) {
27671
+ let ops = 0;
27672
+ let curves = 0;
27673
+ let fills = 0;
27674
+ let i = 0;
27675
+ const n = content.length;
27676
+ while (i < n) {
27677
+ const ch = content[i];
27678
+ if (ch === "(") {
27679
+ let depth = 1;
27680
+ i++;
27681
+ while (i < n && depth > 0) {
27682
+ if (content[i] === "\\")
27683
+ i++;
27684
+ else if (content[i] === "(")
27685
+ depth++;
27686
+ else if (content[i] === ")")
27687
+ depth--;
27688
+ i++;
27689
+ }
27690
+ continue;
27691
+ }
27692
+ if (ch === "<" && content[i + 1] !== "<") {
27693
+ while (i < n && content[i] !== ">")
27694
+ i++;
27695
+ i++;
27696
+ continue;
27697
+ }
27698
+ if (ch === "I" && content[i + 1] === "D" && isDelimiter(content[i + 2])) {
27699
+ const end = content.indexOf("EI", i + 2);
27700
+ i = end === -1 ? n : end + 2;
27701
+ continue;
27702
+ }
27703
+ if (!isDelimiter(ch)) {
27704
+ const start = i;
27705
+ while (i < n && !isDelimiter(content[i]))
27706
+ i++;
27707
+ const token = content.slice(start, i);
27708
+ switch (token) {
27709
+ case "m":
27710
+ case "l":
27711
+ case "re":
27712
+ ops++;
27713
+ break;
27714
+ case "c":
27715
+ case "v":
27716
+ case "y":
27717
+ ops++;
27718
+ curves++;
27719
+ break;
27720
+ case "f":
27721
+ case "F":
27722
+ case "f*":
27723
+ fills++;
27724
+ break;
27725
+ case "B":
27726
+ case "B*":
27727
+ case "b":
27728
+ case "b*":
27729
+ fills++;
27730
+ break;
27731
+ default:
27732
+ break;
27733
+ }
27734
+ continue;
27735
+ }
27736
+ i++;
27737
+ }
27738
+ return { ops, curves, fills };
27739
+ }
27740
+ function isDelimiter(ch) {
27741
+ return ch === void 0 || ch === " " || ch === "\n" || ch === "\r" || ch === " " || ch === "\f" || ch === "\0" || ch === "[" || ch === "]" || ch === "/" || ch === "{" || ch === "}";
27742
+ }
27743
+ function pageContentStream(page) {
27744
+ try {
27745
+ const obj = page.getObject?.();
27746
+ const contents = obj?.get?.("Contents");
27747
+ if (!contents)
27748
+ return "";
27749
+ const streams = contents.isArray?.() && contents.get ? Array.from({ length: contents.length ?? 0 }, (_, i) => contents.get(i)) : [contents];
27750
+ let out = "";
27751
+ for (const stream of streams) {
27752
+ const buf = stream?.readStream?.();
27753
+ const bytes = buf?.asUint8Array?.();
27754
+ if (bytes)
27755
+ out += Buffer.from(bytes).toString("latin1");
27756
+ }
27757
+ return out;
27758
+ } catch {
27759
+ return "";
27760
+ }
27761
+ }
27762
+ function pageSignalsFrom(page, pageNumber) {
27763
+ const json = JSON.parse(page.toStructuredText("preserve-images").asJSON());
27764
+ const blocks = json.blocks ?? [];
27765
+ const text = blocks.flatMap((b) => (b.lines ?? []).map((l) => l.text ?? "")).join(" ");
27766
+ const [x0, y0, x1, y1] = page.getBounds();
27767
+ const pageArea = Math.max((x1 - x0) * (y1 - y0), 1);
27768
+ const imgArea = blocks.filter((b) => b.type === "image").reduce((a, b) => a + Math.max((b.bbox?.w ?? 0) * (b.bbox?.h ?? 0), 0), 0);
27769
+ const vector = countVectorOperators(pageContentStream(page));
27770
+ return {
27771
+ page: pageNumber,
27772
+ textChars: text.trim().length,
27773
+ imageCoverage: Math.min(imgArea / pageArea, 1),
27774
+ vectorOps: vector.ops,
27775
+ vectorCurves: vector.curves,
27776
+ vectorFills: vector.fills,
27777
+ mathHint: textMathHint(text),
27778
+ tableHint: textTableHint(text)
27779
+ };
27780
+ }
27781
+ async function scanPdfSignals(bytes) {
27782
+ const { signals } = await analyzePdfDocument(bytes, { text: false, signals: true });
27783
+ return signals;
27784
+ }
27785
+ function median(nums) {
27786
+ if (!nums.length)
27787
+ return 0;
27788
+ const s = [...nums].sort((a, b) => a - b);
27789
+ return s[Math.floor(s.length / 2)];
27790
+ }
27791
+ async function extractPdfTables(bytes, pageNum, minColumns = 2) {
27792
+ const { doc } = await openDoc(bytes);
27793
+ const total = doc.countPages();
27794
+ const from = pageNum ? pageNum : 1;
27795
+ const to = pageNum ? pageNum : total;
27796
+ const out = [];
27797
+ for (let p = from; p <= to; p++) {
27798
+ if (p < 1 || p > total)
27799
+ continue;
27800
+ const json = JSON.parse(doc.loadPage(p - 1).toStructuredText().asJSON());
27801
+ const cells = [];
27802
+ for (const b of json.blocks ?? []) {
27803
+ if (b.type === "image")
27804
+ continue;
27805
+ for (const l of b.lines ?? []) {
27806
+ const text = (l.text ?? "").trim();
27807
+ if (!text)
27808
+ continue;
27809
+ cells.push({ text, x: l.bbox?.x ?? 0, y: l.bbox?.y ?? 0, h: l.bbox?.h ?? 12 });
27810
+ }
27811
+ }
27812
+ if (cells.length < 2)
27813
+ continue;
27814
+ const medH = median(cells.map((c) => c.h)) || 12;
27815
+ const rowTol = Math.max(4, medH * 0.6);
27816
+ const colTol = Math.max(6, medH * 0.9);
27817
+ const byY = [...cells].sort((a, b) => a.y - b.y);
27818
+ const rowGroups = [];
27819
+ let rowRef = Number.NEGATIVE_INFINITY;
27820
+ for (const c of byY) {
27821
+ if (c.y - rowRef > rowTol) {
27822
+ rowGroups.push([]);
27823
+ rowRef = c.y;
27824
+ }
27825
+ rowGroups[rowGroups.length - 1].push(c);
27826
+ }
27827
+ const colRefs = [];
27828
+ for (const x of cells.map((c) => c.x).sort((a, b) => a - b)) {
27829
+ if (!colRefs.length || x - colRefs[colRefs.length - 1] > colTol)
27830
+ colRefs.push(x);
27831
+ }
27832
+ const colIndex = (x) => {
27833
+ let best = 0, bd = Infinity;
27834
+ colRefs.forEach((r, i) => {
27835
+ const d = Math.abs(x - r);
27836
+ if (d < bd) {
27837
+ bd = d;
27838
+ best = i;
27839
+ }
27840
+ });
27841
+ return best;
27842
+ };
27843
+ const rows = [];
27844
+ for (const group of rowGroups) {
27845
+ const arr = Array(colRefs.length).fill("");
27846
+ for (const c of group.sort((a, b) => a.x - b.x)) {
27847
+ const i = colIndex(c.x);
27848
+ arr[i] = arr[i] ? `${arr[i]} ${c.text}` : c.text;
27849
+ }
27850
+ if (arr.filter((v) => v).length >= minColumns)
27851
+ rows.push(arr);
27852
+ }
27853
+ if (rows.length)
27854
+ out.push({ page: p, rows });
27855
+ }
27856
+ return out;
27857
+ }
27858
+ async function extractPdfTextLines(bytes) {
27859
+ const { doc } = await openDoc(bytes);
27860
+ const out = [];
27861
+ for (let p = 1; p <= doc.countPages(); p++) {
27862
+ const page = doc.loadPage(p - 1);
27863
+ const [x0, y0, x1, y1] = page.getBounds();
27864
+ const json = JSON.parse(page.toStructuredText().asJSON());
27865
+ const lines = [];
27866
+ for (const b of json.blocks ?? []) {
27867
+ if (b.type === "image")
27868
+ continue;
27869
+ for (const l of b.lines ?? []) {
27870
+ const text = l.text ?? "";
27871
+ if (!text.trim() || !l.bbox)
27872
+ continue;
27873
+ lines.push({
27874
+ x: l.bbox.x ?? 0,
27875
+ y: l.bbox.y ?? 0,
27876
+ w: l.bbox.w ?? 0,
27877
+ h: l.bbox.h ?? 0,
27878
+ size: l.font?.size ?? 12,
27879
+ text
27880
+ });
27881
+ }
27882
+ }
27883
+ out.push({ page: p, width: x1 - x0, height: y1 - y0, lines });
27884
+ }
27885
+ return out;
27886
+ }
27887
+ async function renderPdfPages(bytes, pages, scale = 2, maxEdge) {
27888
+ const { mupdf, doc } = await openDoc(bytes);
27889
+ const total = doc.countPages();
27890
+ const wanted = pages === "all" ? Array.from({ length: total }, (_, i) => i + 1) : pages;
27891
+ const out = [];
27892
+ for (const p of wanted) {
27893
+ if (p < 1 || p > total)
27894
+ continue;
27895
+ const page = doc.loadPage(p - 1);
27896
+ const [x0, y0, x1, y1] = page.getBounds();
27897
+ const longest = Math.max(x1 - x0, y1 - y0);
27898
+ const s = maxEdge && longest > 0 ? Math.min(scale, maxEdge / longest) : scale;
27899
+ const pix = page.toPixmap(mupdf.Matrix.scale(s, s), mupdf.ColorSpace.DeviceRGB, false, true);
27900
+ out.push({ page: p, png: pix.asPNG() });
27901
+ }
27902
+ return out;
27903
+ }
27904
+ async function extractPdfImages(bytes, pages) {
27905
+ const { doc } = await openDoc(bytes);
27906
+ const total = doc.countPages();
27907
+ const wanted = pages && pages.length ? pages : Array.from({ length: total }, (_, i) => i + 1);
27908
+ const images = [];
27909
+ let skipped = 0;
27910
+ for (const p of wanted) {
27911
+ if (p < 1 || p > total)
27912
+ continue;
27913
+ const stext = doc.loadPage(p - 1).toStructuredText("preserve-images");
27914
+ let index = 0;
27915
+ stext.walk({
27916
+ onImageBlock(bbox, _matrix, image) {
27917
+ const slot = index++;
27918
+ try {
27919
+ const png = image.toPixmap().asPNG();
27920
+ images.push({
27921
+ page: p,
27922
+ index: slot,
27923
+ bbox: [bbox[0], bbox[1], bbox[2], bbox[3]],
27924
+ width: image.getWidth(),
27925
+ height: image.getHeight(),
27926
+ png
27927
+ });
27928
+ } catch {
27929
+ skipped++;
27930
+ }
27931
+ }
27932
+ });
27933
+ }
27934
+ return { images, skipped };
27935
+ }
27936
+ async function encryptPdf(bytes, userPassword, ownerPassword) {
27937
+ if (!userPassword)
27938
+ throw new Error("a non-empty user password is required");
27939
+ const { doc } = await openDoc(bytes);
27940
+ const opts = `encrypt=aes-256,user-password=${userPassword},owner-password=${ownerPassword || userPassword}`;
27941
+ return doc.saveToBuffer(opts).asUint8Array();
27942
+ }
27943
+ async function pdfNeedsPassword(bytes) {
27944
+ const mupdf = await loadMupdf();
27945
+ const doc = mupdf.Document.openDocument(bytes, "application/pdf");
27946
+ return doc.needsPassword();
27947
+ }
27948
+ async function decryptPdf(bytes, password) {
27949
+ const mupdf = await loadMupdf();
27950
+ const doc = mupdf.Document.openDocument(bytes, "application/pdf");
27951
+ if (doc.needsPassword()) {
27952
+ if (doc.authenticatePassword(password) === 0)
27953
+ throw new Error("wrong password");
27954
+ }
27955
+ if (doc.countPages() === 0)
27956
+ throw new Error("PDF has no readable pages (corrupt or empty)");
27957
+ return doc.saveToBuffer("encrypt=none").asUint8Array();
27958
+ }
27959
+ var import_node_path, import_node_url, _mupdf, MATH_CHARS;
27960
+ var init_pdf_extract = __esm({
27961
+ "packages/core/dist/document/pdf-extract.js"() {
27962
+ "use strict";
27963
+ import_node_path = require("node:path");
27964
+ import_node_url = require("node:url");
27965
+ init_pdf_encrypted();
27966
+ _mupdf = null;
27967
+ MATH_CHARS = /[=∑∫√≤≥≠±∞πθλμσΩ×÷∂∇→←↔∈∉⊂⊃∀∃]/g;
27968
+ }
27969
+ });
27970
+
27584
27971
  // apps/admin/data/skills.lib/document/bin-src/doc-validate.mjs
27585
27972
  var import_promises2 = require("node:fs/promises");
27586
- var import_node_path2 = require("node:path");
27973
+ var import_node_path3 = require("node:path");
27587
27974
 
27588
27975
  // packages/core/dist/document/doc-validate.js
27589
27976
  var import_jszip = __toESM(require_lib3(), 1);
@@ -27599,6 +27986,17 @@ async function validatePdf(bytes) {
27599
27986
  try {
27600
27987
  const doc = await import_pdf_lib.PDFDocument.load(bytes, { ignoreEncryption: true, throwOnInvalidObject: true, updateMetadata: false });
27601
27988
  pages = doc.getPageCount();
27989
+ if (doc.isEncrypted) {
27990
+ const { pdfNeedsPassword: pdfNeedsPassword2 } = await Promise.resolve().then(() => (init_pdf_extract(), pdf_extract_exports));
27991
+ if (await pdfNeedsPassword2(bytes).catch(() => true)) {
27992
+ errors.push({
27993
+ code: "pdf-encrypted",
27994
+ 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"
27995
+ });
27996
+ return { ok: false, kind: "pdf", errors, warnings, pages };
27997
+ }
27998
+ 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>`' });
27999
+ }
27602
28000
  if (pages === 0)
27603
28001
  errors.push({ code: "pdf-no-pages", message: "the PDF parses but has no pages" });
27604
28002
  else
@@ -27828,15 +28226,15 @@ async function validateOoxml(bytes) {
27828
28226
  }
27829
28227
 
27830
28228
  // packages/core/dist/document/doc-xsd.js
27831
- var import_node_path = require("node:path");
27832
- var import_node_url = require("node:url");
28229
+ var import_node_path2 = require("node:path");
28230
+ var import_node_url2 = require("node:url");
27833
28231
  var import_promises = require("node:fs/promises");
27834
28232
  var import_jszip2 = __toESM(require_lib3(), 1);
27835
28233
  var _xmllint = null;
27836
28234
  function loadXmllint() {
27837
28235
  if (!_xmllint) {
27838
28236
  const deps = process.env.TALOS_SKILL_DEPS;
27839
- const spec = deps ? (0, import_node_url.pathToFileURL)((0, import_node_path.join)(deps, "xmllint-wasm", "index-node.js")).href : "xmllint-wasm";
28237
+ const spec = deps ? (0, import_node_url2.pathToFileURL)((0, import_node_path2.join)(deps, "xmllint-wasm", "index-node.js")).href : "xmllint-wasm";
27840
28238
  _xmllint = import(spec);
27841
28239
  }
27842
28240
  return _xmllint;
@@ -27879,7 +28277,7 @@ async function loadSchemaSet(schemaDir) {
27879
28277
  if (!files.length)
27880
28278
  throw new Error(`no .xsd schemas found in ${schemaDir}`);
27881
28279
  const set = /* @__PURE__ */ new Map();
27882
- await Promise.all(files.map(async (f) => set.set(f, await (0, import_promises.readFile)((0, import_node_path.join)(schemaDir, f), "utf8"))));
28280
+ await Promise.all(files.map(async (f) => set.set(f, await (0, import_promises.readFile)((0, import_node_path2.join)(schemaDir, f), "utf8"))));
27883
28281
  return set;
27884
28282
  }
27885
28283
  function errText(e) {
@@ -27983,6 +28381,118 @@ async function slidePartsInOrder(zip) {
27983
28381
  return toParts(complete ? ordered : byNumber);
27984
28382
  }
27985
28383
 
28384
+ // packages/core/dist/document/png-decode.js
28385
+ var import_node_zlib = require("node:zlib");
28386
+ var SIG = [137, 80, 78, 71, 13, 10, 26, 10];
28387
+ var CHANNELS = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 };
28388
+ function decodePng(bytes) {
28389
+ try {
28390
+ if (bytes.length < 33 || SIG.some((b, i) => bytes[i] !== b))
28391
+ return void 0;
28392
+ const buf = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
28393
+ let width = 0, height = 0, depth = 0, type = -1, interlace = 0;
28394
+ let palette;
28395
+ const idat = [];
28396
+ for (let p = 8; p + 8 <= buf.length; ) {
28397
+ const len = buf.readUInt32BE(p);
28398
+ const kind = buf.toString("latin1", p + 4, p + 8);
28399
+ const data = buf.subarray(p + 8, p + 8 + len);
28400
+ if (kind === "IHDR") {
28401
+ width = data.readUInt32BE(0);
28402
+ height = data.readUInt32BE(4);
28403
+ depth = data[8];
28404
+ type = data[9];
28405
+ interlace = data[12];
28406
+ } else if (kind === "PLTE")
28407
+ palette = data;
28408
+ else if (kind === "IDAT")
28409
+ idat.push(data);
28410
+ else if (kind === "IEND")
28411
+ break;
28412
+ p += 12 + len;
28413
+ }
28414
+ const ch = CHANNELS[type];
28415
+ if (!width || !height || depth !== 8 || interlace !== 0 || ch === void 0 || type === 3 && !palette)
28416
+ return void 0;
28417
+ const raw = (0, import_node_zlib.inflateSync)(Buffer.concat(idat));
28418
+ const stride = width * ch;
28419
+ if (raw.length < height * (stride + 1))
28420
+ return void 0;
28421
+ const cur = new Uint8Array(stride);
28422
+ const prev = new Uint8Array(stride);
28423
+ const rgb = new Uint8Array(width * height * 3);
28424
+ for (let y = 0; y < height; y++) {
28425
+ const off = y * (stride + 1);
28426
+ const filter = raw[off];
28427
+ for (let i = 0; i < stride; i++) {
28428
+ const x = raw[off + 1 + i];
28429
+ const a = i >= ch ? cur[i - ch] : 0;
28430
+ const b = prev[i];
28431
+ const c = i >= ch ? prev[i - ch] : 0;
28432
+ let v;
28433
+ switch (filter) {
28434
+ case 0:
28435
+ v = x;
28436
+ break;
28437
+ case 1:
28438
+ v = x + a;
28439
+ break;
28440
+ case 2:
28441
+ v = x + b;
28442
+ break;
28443
+ case 3:
28444
+ v = x + (a + b >> 1);
28445
+ break;
28446
+ case 4: {
28447
+ const pa = Math.abs(b - c), pb = Math.abs(a - c), pc = Math.abs(a + b - 2 * c);
28448
+ v = x + (pa <= pb && pa <= pc ? a : pb <= pc ? b : c);
28449
+ break;
28450
+ }
28451
+ default:
28452
+ return void 0;
28453
+ }
28454
+ cur[i] = v & 255;
28455
+ }
28456
+ for (let px = 0; px < width; px++) {
28457
+ const o = (y * width + px) * 3;
28458
+ const s = px * ch;
28459
+ if (type === 3) {
28460
+ const k = cur[s] * 3;
28461
+ rgb[o] = palette[k];
28462
+ rgb[o + 1] = palette[k + 1];
28463
+ rgb[o + 2] = palette[k + 2];
28464
+ } else if (ch <= 2) {
28465
+ rgb[o] = rgb[o + 1] = rgb[o + 2] = cur[s];
28466
+ } else {
28467
+ rgb[o] = cur[s];
28468
+ rgb[o + 1] = cur[s + 1];
28469
+ rgb[o + 2] = cur[s + 2];
28470
+ }
28471
+ }
28472
+ prev.set(cur);
28473
+ }
28474
+ return { width, height, rgb };
28475
+ } catch {
28476
+ return void 0;
28477
+ }
28478
+ }
28479
+
28480
+ // packages/core/dist/document/text-slack.js
28481
+ var SLACK_CJK_RE = /[\u3000-\u303F\u3400-\u9FFF\uF900-\uFAFF\uFF00-\uFFEF]/;
28482
+ var PCT = { single: { cjk: 0.22, latin: 0.16 }, multi: { cjk: 0.1, latin: 0.06 } };
28483
+ var FLOOR_EM = { cjk: 1, latin: 0.45 };
28484
+ function params(single, cjk) {
28485
+ const script = cjk ? "cjk" : "latin";
28486
+ return { pct: PCT[single ? "single" : "multi"][script], floor: FLOOR_EM[script] * (single ? 1 : 0.5) };
28487
+ }
28488
+ function unpaddedWidth(padded, fontSize, single, cjk) {
28489
+ const { pct, floor } = params(single, cjk);
28490
+ const floorAbs = floor * fontSize;
28491
+ const viaPct = padded / (1 + pct);
28492
+ return viaPct * pct >= floorAbs ? viaPct : Math.max(0, padded - floorAbs);
28493
+ }
28494
+ var CHART_TEXT_NAME = "talos-chart-text";
28495
+
27986
28496
  // packages/core/dist/document/doc-design-lint.js
27987
28497
  var PLACEHOLDER_RE = /替换为|在这里写|占位|示例文本|Lorem ipsum/i;
27988
28498
  var CJK_TEXT_RE = /[぀-ヿ㐀-䶿一-鿿가-힯豈-﫿]/;
@@ -28123,7 +28633,7 @@ function lintSlideXml(slideXml, slideNumber, masterBg, expectedLang = detectDeck
28123
28633
  }
28124
28634
  }
28125
28635
  }
28126
- if (worst && !worst.onShapeFill && anyVisualShape)
28636
+ if (worst && !worst.onShapeFill && (anyVisualShape || hasPic))
28127
28637
  worst = void 0;
28128
28638
  if (placeholder) {
28129
28639
  const snippet = placeholder.length > 24 ? `${placeholder.slice(0, 24)}\u2026` : placeholder;
@@ -28163,7 +28673,10 @@ var EMU_PER_INCH = 914400;
28163
28673
  var DEFAULT_CANVAS = { w: 12192e3, h: 6858e3 };
28164
28674
  var OFFCANVAS_TOL = EMU_PER_INCH / 10;
28165
28675
  var MIN_FONT_HUNDREDTHS = 1e3;
28166
- var OVERLAP_RATIO = 0.5;
28676
+ var LABEL_MAX_CHARS = 24;
28677
+ var MIN_LABEL_FONT_HUNDREDTHS = 800;
28678
+ var CHART_TEXT_RE = new RegExp(`<p:cNvPr\\b[^>]*\\bname="${CHART_TEXT_NAME}\\b`);
28679
+ var OVERLAP_RATIO = 0.3;
28167
28680
  function firstBox(block) {
28168
28681
  const xf = /<(?:a|p):xfrm\b[^>]*>[\s\S]*?<\/(?:a|p):xfrm>/.exec(block);
28169
28682
  const scope = xf ? xf[0] : block;
@@ -28181,6 +28694,51 @@ function overlapArea(a, b) {
28181
28694
  const iy = Math.max(0, Math.min(a.y + a.h, b.y + b.h) - Math.max(a.y, b.y));
28182
28695
  return ix * iy;
28183
28696
  }
28697
+ var CJK_WIDE_RE = /[\u2E80-\u9FFF\uAC00-\uD7AF\uF900-\uFAFF\uFF00-\uFFEF]/;
28698
+ var EMU_PER_PT = 12700;
28699
+ function inkBox(block, box) {
28700
+ let maxSz = 0;
28701
+ let inkW = 0;
28702
+ let lines = 0;
28703
+ for (const para of block.match(/<a:p>[\s\S]*?<\/a:p>|<a:p\b[^>]*>[\s\S]*?<\/a:p>/g) ?? []) {
28704
+ let paraW = 0;
28705
+ let paraSz = 0;
28706
+ for (const run of para.match(/<a:r>[\s\S]*?<\/a:r>/g) ?? []) {
28707
+ const raw = /<a:t\b[^>]*>([\s\S]*?)<\/a:t>/.exec(run)?.[1];
28708
+ if (raw === void 0)
28709
+ continue;
28710
+ const sz = +(/<a:rPr\b[^>]*\bsz="(\d+)"/.exec(run)?.[1] ?? 1800) / 100;
28711
+ paraSz = Math.max(paraSz, sz);
28712
+ for (const ch of unescXml(raw))
28713
+ paraW += (CJK_WIDE_RE.test(ch) ? 1 : ch === " " ? 0.3 : 0.55) * sz * EMU_PER_PT;
28714
+ }
28715
+ if (!paraW)
28716
+ continue;
28717
+ maxSz = Math.max(maxSz, paraSz);
28718
+ const paraLines = box.w > 0 ? Math.max(1, Math.ceil(paraW / box.w)) : 1;
28719
+ lines += paraLines;
28720
+ inkW = Math.max(inkW, Math.min(paraW, box.w));
28721
+ }
28722
+ if (!lines)
28723
+ return void 0;
28724
+ 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));
28725
+ if (paras.length === 1 && maxSz > 0) {
28726
+ const lnSpc = /<a:lnSpc>\s*<a:spcPts\b[^>]*\bval="(\d+)"/.exec(block)?.[1];
28727
+ const lineH = (lnSpc ? +lnSpc / 100 : maxSz * 1.3) * EMU_PER_PT;
28728
+ const clamped = box.x + box.w >= DEFAULT_CANVAS.w - EMU_PER_PT;
28729
+ if (box.h <= lineH * 1.5 && !clamped) {
28730
+ const text = unescXml((block.match(/<a:t\b[^>]*>([\s\S]*?)<\/a:t>/g) ?? []).join(""));
28731
+ lines = 1;
28732
+ inkW = Math.min(inkW, unpaddedWidth(box.w, maxSz * EMU_PER_PT, true, SLACK_CJK_RE.test(text)));
28733
+ }
28734
+ }
28735
+ const inkH = Math.min(box.h, lines * maxSz * 1.2 * EMU_PER_PT);
28736
+ const algn = /<a:pPr\b[^>]*\balgn="(\w+)"/.exec(block)?.[1];
28737
+ const anchor = /<a:bodyPr\b[^>]*\banchor="(\w+)"/.exec(block)?.[1];
28738
+ const x = algn === "ctr" ? box.x + (box.w - inkW) / 2 : algn === "r" ? box.x + box.w - inkW : box.x;
28739
+ const y = anchor === "ctr" ? box.y + (box.h - inkH) / 2 : anchor === "b" ? box.y + box.h - inkH : box.y;
28740
+ return { x, y, w: inkW, h: inkH };
28741
+ }
28184
28742
  function lintSlideGeometry(slideXml, slideNumber, canvas = DEFAULT_CANVAS) {
28185
28743
  const findings = [];
28186
28744
  const blocks = slideXml.match(/<p:(?:sp|pic|graphicFrame|cxnSp)\b[^>]*>[\s\S]*?<\/p:(?:sp|pic|graphicFrame|cxnSp)>/g) ?? [];
@@ -28204,16 +28762,23 @@ function lintSlideGeometry(slideXml, slideNumber, canvas = DEFAULT_CANVAS) {
28204
28762
  worstOff = { by, edge };
28205
28763
  }
28206
28764
  }
28207
- if (hasText && box.w > 0 && box.h > 0)
28208
- textBoxes.push(box);
28765
+ if (hasText && box.w > 0 && box.h > 0) {
28766
+ const ink = inkBox(b, box);
28767
+ if (ink)
28768
+ textBoxes.push(ink);
28769
+ }
28209
28770
  }
28771
+ if (CHART_TEXT_RE.test(b))
28772
+ continue;
28210
28773
  for (const run of b.match(/<a:r>[\s\S]*?<\/a:r>/g) ?? []) {
28211
28774
  const raw = /<a:t\b[^>]*>([\s\S]*?)<\/a:t>/.exec(run)?.[1];
28212
28775
  if (raw === void 0 || !unescXml(raw).trim())
28213
28776
  continue;
28777
+ const text = unescXml(raw).trim();
28214
28778
  const sz = /<a:rPr\b[^>]*\bsz="(\d+)"/.exec(run)?.[1];
28215
- if (sz && +sz < MIN_FONT_HUNDREDTHS && (!smallest || +sz < smallest.sz)) {
28216
- smallest = { sz: +sz, text: unescXml(raw).trim() };
28779
+ const floor = [...text].length <= LABEL_MAX_CHARS ? MIN_LABEL_FONT_HUNDREDTHS : MIN_FONT_HUNDREDTHS;
28780
+ if (sz && +sz < floor && (!smallest || +sz < smallest.sz)) {
28781
+ smallest = { sz: +sz, text, floor };
28217
28782
  }
28218
28783
  }
28219
28784
  }
@@ -28229,7 +28794,7 @@ function lintSlideGeometry(slideXml, slideNumber, canvas = DEFAULT_CANVAS) {
28229
28794
  findings.push({
28230
28795
  slide: slideNumber,
28231
28796
  code: "tiny-font",
28232
- 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`
28797
+ 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`
28233
28798
  });
28234
28799
  }
28235
28800
  let worstPair;
@@ -28254,6 +28819,110 @@ function lintSlideGeometry(slideXml, slideNumber, canvas = DEFAULT_CANVAS) {
28254
28819
  }
28255
28820
  return findings;
28256
28821
  }
28822
+ var BACKDROP_COVER = 0.98;
28823
+ var BACKDROP_FLOOR = 2;
28824
+ var BACKDROP_SAMPLES = 2500;
28825
+ var LIN = Array.from({ length: 256 }, (_, i) => {
28826
+ const c = i / 255;
28827
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
28828
+ });
28829
+ var lumRgb = (r, g, b) => 0.2126 * LIN[r] + 0.7152 * LIN[g] + 0.0722 * LIN[b];
28830
+ var ratioOf = (a, b) => (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
28831
+ var hex2 = (n) => n.toString(16).padStart(2, "0").toUpperCase();
28832
+ var BLOCK_RE = /<p:(?:sp|pic|graphicFrame|cxnSp)\b[^>]*>[\s\S]*?<\/p:(?:sp|pic|graphicFrame|cxnSp)>/g;
28833
+ function backdropPicEmbed(slideXml, canvas = DEFAULT_CANVAS) {
28834
+ const first = (slideXml.match(BLOCK_RE) ?? [])[0];
28835
+ if (!first || !first.startsWith("<p:pic"))
28836
+ return void 0;
28837
+ const box = firstBox(first);
28838
+ if (!box || box.x > canvas.w * (1 - BACKDROP_COVER) || box.y > canvas.h * (1 - BACKDROP_COVER))
28839
+ return void 0;
28840
+ if (box.w < canvas.w * BACKDROP_COVER || box.h < canvas.h * BACKDROP_COVER)
28841
+ return void 0;
28842
+ return /<a:blip\b[^>]*\br:embed="([^"]+)"/.exec(first)?.[1];
28843
+ }
28844
+ function lintBackdropContrast(slideXml, slideNumber, backdrop, canvas = DEFAULT_CANVAS) {
28845
+ const blocks = slideXml.match(BLOCK_RE) ?? [];
28846
+ const sx = backdrop.width / canvas.w;
28847
+ const sy = backdrop.height / canvas.h;
28848
+ const occluders = [];
28849
+ let worst;
28850
+ for (let bi = 1; bi < blocks.length; bi++) {
28851
+ const b = blocks[bi];
28852
+ const box = firstBox(b);
28853
+ if (!box)
28854
+ continue;
28855
+ const txIdx = b.indexOf("<p:txBody>");
28856
+ const spPr = txIdx >= 0 ? b.slice(0, txIdx) : b;
28857
+ const prst = /<a:prstGeom\b[^>]*\bprst="([^"]+)"/.exec(spPr)?.[1];
28858
+ const visual = !b.startsWith("<p:sp") || FILL_RE.test(spPr) || prst !== void 0 && prst !== "rect";
28859
+ const hasText = txIdx >= 0 && /<a:t\b[^>]*>\s*\S/.test(b);
28860
+ if (visual) {
28861
+ occluders.push(box);
28862
+ continue;
28863
+ }
28864
+ if (!hasText)
28865
+ continue;
28866
+ const ink = inkBox(b, box);
28867
+ if (!ink || ink.w <= 0 || ink.h <= 0)
28868
+ continue;
28869
+ if (occluders.some((o2) => overlapArea(o2, ink) > 0))
28870
+ continue;
28871
+ let fg;
28872
+ let alpha = 1;
28873
+ let text = "";
28874
+ for (const run of b.match(/<a:r>[\s\S]*?<\/a:r>/g) ?? []) {
28875
+ const raw = /<a:t\b[^>]*>([\s\S]*?)<\/a:t>/.exec(run)?.[1];
28876
+ if (raw === void 0 || !unescXml(raw).trim())
28877
+ continue;
28878
+ text += unescXml(raw);
28879
+ if (fg)
28880
+ continue;
28881
+ const rPr = run.slice(0, Math.max(0, run.indexOf("</a:rPr>")));
28882
+ fg = firstSrgb(rPr);
28883
+ const a = /<a:srgbClr\b[^>]*>\s*<a:alpha\b[^>]*\bval="(\d+)"/.exec(rPr)?.[1];
28884
+ if (fg && a)
28885
+ alpha = Math.min(1, +a / 1e5);
28886
+ }
28887
+ if (!fg)
28888
+ continue;
28889
+ const x0 = Math.max(0, Math.floor(ink.x * sx));
28890
+ const y0 = Math.max(0, Math.floor(ink.y * sy));
28891
+ const x1 = Math.min(backdrop.width, Math.ceil((ink.x + ink.w) * sx));
28892
+ const y1 = Math.min(backdrop.height, Math.ceil((ink.y + ink.h) * sy));
28893
+ if (x1 <= x0 || y1 <= y0)
28894
+ continue;
28895
+ const step = Math.max(1, Math.floor(Math.sqrt((x1 - x0) * (y1 - y0) / BACKDROP_SAMPLES)));
28896
+ const fr = parseInt(fg.slice(0, 2), 16), fgG = parseInt(fg.slice(2, 4), 16), fb = parseInt(fg.slice(4, 6), 16);
28897
+ const fgLum = lumRgb(fr, fgG, fb);
28898
+ const samples = [];
28899
+ for (let y = y0; y < y1; y += step) {
28900
+ for (let x = x0; x < x1; x += step) {
28901
+ const o2 = (y * backdrop.width + x) * 3;
28902
+ const r = backdrop.rgb[o2], g = backdrop.rgb[o2 + 1], bl = backdrop.rgb[o2 + 2];
28903
+ const l = lumRgb(r, g, bl);
28904
+ 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));
28905
+ samples.push([ratioOf(tl, l), o2]);
28906
+ }
28907
+ }
28908
+ if (!samples.length)
28909
+ continue;
28910
+ samples.sort((p, q) => p[0] - q[0]);
28911
+ const [ratio, o] = samples[samples.length >> 1];
28912
+ if (ratio < BACKDROP_FLOOR && (!worst || ratio < worst.ratio)) {
28913
+ const bg = hex2(backdrop.rgb[o]) + hex2(backdrop.rgb[o + 1]) + hex2(backdrop.rgb[o + 2]);
28914
+ worst = { ratio, text: text.trim(), fg, bg };
28915
+ }
28916
+ }
28917
+ if (!worst)
28918
+ return [];
28919
+ const snippet = worst.text.length > 24 ? `${worst.text.slice(0, 24)}\u2026` : worst.text;
28920
+ return [{
28921
+ slide: slideNumber,
28922
+ code: "low-contrast-text",
28923
+ 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`
28924
+ }];
28925
+ }
28257
28926
  function canvasFromPresentation(xml) {
28258
28927
  if (!xml)
28259
28928
  return DEFAULT_CANVAS;
@@ -28261,6 +28930,26 @@ function canvasFromPresentation(xml) {
28261
28930
  const cy = /<p:sldSz\b[^>]*\bcy="(\d+)"/.exec(xml)?.[1];
28262
28931
  return cx && cy ? { w: +cx, h: +cy } : DEFAULT_CANVAS;
28263
28932
  }
28933
+ async function backdropRaster(zip, slideXml, relsPath, canvas) {
28934
+ const rId = backdropPicEmbed(slideXml, canvas);
28935
+ const rels = rId ? zip.file(relsPath) : null;
28936
+ if (!rId || !rels)
28937
+ return void 0;
28938
+ const relsXml = await rels.async("string");
28939
+ const rel = [...relsXml.matchAll(/<Relationship\b[^>]*>/g)].map((m) => m[0]).find((r) => new RegExp(`\\bId="${rId}"`).test(r));
28940
+ const target = rel && /\bTarget="([^"]+)"/.exec(rel)?.[1];
28941
+ if (!target || !/\.png$/i.test(target))
28942
+ return void 0;
28943
+ const parts = ["ppt", "slides"];
28944
+ for (const seg of target.split("/")) {
28945
+ if (seg === "..")
28946
+ parts.pop();
28947
+ else if (seg && seg !== ".")
28948
+ parts.push(seg);
28949
+ }
28950
+ const media = zip.file(target.startsWith("/") ? target.slice(1) : parts.join("/"));
28951
+ return media ? decodePng(await media.async("uint8array")) : void 0;
28952
+ }
28264
28953
  async function designLintPptx(bytes) {
28265
28954
  const zip = await import_jszip3.default.loadAsync(Buffer.from(bytes));
28266
28955
  const findings = [];
@@ -28271,9 +28960,15 @@ async function designLintPptx(bytes) {
28271
28960
  const presFile = zip.file("ppt/presentation.xml");
28272
28961
  const canvas = canvasFromPresentation(presFile ? await presFile.async("string") : void 0);
28273
28962
  const lang = detectDeckLang();
28274
- for (const { position, path } of await slidePartsInOrder(zip)) {
28963
+ for (const { position, path, relsPath } of await slidePartsInOrder(zip)) {
28275
28964
  const xml = await zip.file(path).async("string");
28276
- findings.push(...lintSlideXml(xml, position, masterBg, lang));
28965
+ const own = lintSlideXml(xml, position, masterBg, lang);
28966
+ findings.push(...own);
28967
+ if (!own.some((f) => f.code === "low-contrast-text")) {
28968
+ const backdrop = await backdropRaster(zip, xml, relsPath, canvas);
28969
+ if (backdrop)
28970
+ findings.push(...lintBackdropContrast(xml, position, backdrop, canvas));
28971
+ }
28277
28972
  findings.push(...lintSlideGeometry(xml, position, canvas));
28278
28973
  }
28279
28974
  return { ok: findings.length === 0, findings };
@@ -28302,7 +28997,7 @@ async function main() {
28302
28997
  }];
28303
28998
  }
28304
28999
  if (strict && strictApplies) {
28305
- const schemaDir = process.env.TALOS_XSD_DIR || (0, import_node_path2.join)((0, import_node_path2.dirname)(process.argv[1]), "..", "schemas");
29000
+ const schemaDir = process.env.TALOS_XSD_DIR || (0, import_node_path3.join)((0, import_node_path3.dirname)(process.argv[1]), "..", "schemas");
28306
29001
  strictResult = await validateOoxmlStrict(bytes, schemaDir).catch((e) => {
28307
29002
  die(`--strict failed to run: ${e.message}`);
28308
29003
  });