@gobing-ai/knowledge-kit 0.0.28 → 0.0.29

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 (40) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/package.json +1 -1
  3. package/plugins/generations/daily-article-gen/dist/index.js +27 -4
  4. package/plugins/generations/daily-article-gen/src/index.ts +34 -3
  5. package/plugins/generations/dailynews-gen/dist/index.js +27 -7
  6. package/plugins/generations/dailynews-gen/src/index.ts +29 -1
  7. package/plugins/generations/dailynews-gen/src/script-builder.ts +15 -12
  8. package/plugins/generations/image-gen/assets/fonts/NotoSansSC-Bold.otf +0 -0
  9. package/plugins/generations/image-gen/assets/fonts/NotoSansSC-Regular.otf +0 -0
  10. package/plugins/generations/image-gen/assets/fonts/OFL.txt +92 -0
  11. package/plugins/generations/image-gen/assets/fonts/SOURCE.md +15 -0
  12. package/plugins/generations/image-gen/dist/index.js +591 -15
  13. package/plugins/generations/image-gen/package.json +1 -0
  14. package/plugins/generations/image-gen/presets/formats/daily-news-cover.yaml +2 -2
  15. package/plugins/generations/image-gen/presets/formats/xhs-card.yaml +19 -11
  16. package/plugins/generations/image-gen/presets/styles/xhs-knowledge.yaml +2 -1
  17. package/plugins/generations/image-gen/scripts/compose-e2e-artifact.ts +135 -0
  18. package/plugins/generations/image-gen/src/bytes.ts +28 -0
  19. package/plugins/generations/image-gen/src/compose/card.ts +294 -0
  20. package/plugins/generations/image-gen/src/compose/chromium.ts +147 -0
  21. package/plugins/generations/image-gen/src/compose/cmap.ts +177 -0
  22. package/plugins/generations/image-gen/src/index.ts +167 -9
  23. package/plugins/generations/image-gen/src/job.ts +6 -0
  24. package/plugins/generations/image-gen/src/presets.ts +53 -13
  25. package/plugins/kk/plugin.json +1 -1
  26. package/plugins/kk/scripts/daily-voice-style.zh.md +25 -12
  27. package/plugins/kk/scripts/kk-workflow-stages.ts +268 -9
  28. package/plugins/kk/scripts/topic-boundary.json +105 -0
  29. package/plugins/kk/scripts/validate-voicescript.ts +15 -0
  30. package/plugins/kk/skills/content-safety/SKILL.md +163 -0
  31. package/plugins/kk/skills/content-safety/references/platforms/apple-podcasts.md +36 -0
  32. package/plugins/kk/skills/content-safety/references/platforms/netease-music.md +31 -0
  33. package/plugins/kk/skills/content-safety/references/platforms/wechatmp.md +32 -0
  34. package/plugins/kk/skills/content-safety/references/platforms/xiaoyuzhou.md +35 -0
  35. package/plugins/kk/skills/xhs-images/SKILL.md +11 -7
  36. package/plugins/kk/skills/xhs-images/references/card-layouts.md +4 -4
  37. package/plugins/kk/workflows/kk-daily-ai-voice.yaml +103 -13
  38. package/plugins/publishings/podcast-pub/dist/index.js +4 -0
  39. package/plugins/publishings/podcast-pub/src/index.ts +2 -0
  40. package/plugins/publishings/podcast-pub/src/show-notes.ts +6 -0
@@ -21809,8 +21809,332 @@ function findProjectRoot(startDir) {
21809
21809
  }
21810
21810
  var init_file_system_node = () => {};
21811
21811
 
21812
+ // ../../plugins/generations/image-gen/src/bytes.ts
21813
+ import { readFile, writeFile } from "fs/promises";
21814
+ async function writeBytes(path2, bytes) {
21815
+ await writeFile(path2, bytes);
21816
+ }
21817
+ async function readBytes(path2) {
21818
+ return new Uint8Array(await readFile(path2));
21819
+ }
21820
+ function imageDimensions(bytes) {
21821
+ const PNG_SIG = [137, 80, 78, 71, 13, 10, 26, 10];
21822
+ if (bytes.byteLength < 24) {
21823
+ throw new Error("imageDimensions: not a PNG \u2014 buffer too small");
21824
+ }
21825
+ for (let i = 0;i < 8; i++) {
21826
+ if (bytes[i] !== PNG_SIG[i]) {
21827
+ throw new Error("imageDimensions: not a PNG \u2014 invalid signature");
21828
+ }
21829
+ }
21830
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
21831
+ const ihdrLength = view.getUint32(8, false);
21832
+ if (ihdrLength !== 13 || bytes[12] !== 73 || bytes[13] !== 72 || bytes[14] !== 68 || bytes[15] !== 82) {
21833
+ throw new Error("imageDimensions: not a PNG \u2014 IHDR chunk not found at offset 8");
21834
+ }
21835
+ const width = view.getUint32(16, false);
21836
+ const height = view.getUint32(20, false);
21837
+ return { width, height };
21838
+ }
21839
+ var init_bytes = () => {};
21840
+
21841
+ // ../../plugins/generations/image-gen/src/compose/card.ts
21842
+ function assertCoverage(input, fonts) {
21843
+ const headlineCover = fonts.get(input.compose.fonts.headline);
21844
+ const bodyCover = fonts.get(input.compose.fonts.body);
21845
+ if (headlineCover === undefined) {
21846
+ throw new Error(`compose: headline font '${input.compose.fonts.headline}' not loaded`);
21847
+ }
21848
+ if (bodyCover === undefined) {
21849
+ throw new Error(`compose: body font '${input.compose.fonts.body}' not loaded`);
21850
+ }
21851
+ const checkField = (text, fieldName, covers) => {
21852
+ for (const ch of text) {
21853
+ const cp = ch.codePointAt(0);
21854
+ if (cp === undefined)
21855
+ continue;
21856
+ if (cp <= 32 || cp === 160 || cp >= 8192 && cp <= 8207 || cp === 12288) {
21857
+ continue;
21858
+ }
21859
+ if (!covers(cp)) {
21860
+ const hex3 = `U+${cp.toString(16).toUpperCase().padStart(4, "0")}`;
21861
+ const fontName = fieldName.startsWith("points") ? input.compose.fonts.body : input.compose.fonts.headline;
21862
+ throw new Error(`compose: ${fieldName} has ${hex3} '${ch}' not covered by ${fontName}`);
21863
+ }
21864
+ }
21865
+ };
21866
+ checkField(input.headline, "headline", headlineCover);
21867
+ for (const [i, point] of input.points.entries()) {
21868
+ checkField(point, `points[${i}]`, bodyCover);
21869
+ }
21870
+ }
21871
+ function assertTextFreePrompt(prompt, copy, sources) {
21872
+ const hit = (text) => {
21873
+ const headline = copy.headline.trim();
21874
+ if (headline.length >= 2 && text.includes(headline))
21875
+ return `the headline '${headline}'`;
21876
+ for (const point of copy.points) {
21877
+ const trimmed = point.trim();
21878
+ if (trimmed.length >= 2 && text.includes(trimmed))
21879
+ return `point '${trimmed}'`;
21880
+ }
21881
+ const han = /\p{Script=Han}/u.exec(text);
21882
+ if (han !== null) {
21883
+ const cp = (han[0]?.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, "0");
21884
+ return `Han character '${han[0]}' (U+${cp})`;
21885
+ }
21886
+ return null;
21887
+ };
21888
+ const fail = (source, what2) => {
21889
+ throw new Error(`compose: the illustration prompt must stay text-free \u2014 ${source} contributes ${what2} (card copy is typeset, never prompted)`);
21890
+ };
21891
+ for (const { source, text } of sources) {
21892
+ const what2 = hit(text);
21893
+ if (what2 !== null)
21894
+ fail(source, what2);
21895
+ }
21896
+ const what = hit(prompt);
21897
+ if (what !== null)
21898
+ fail("the composed prompt", what);
21899
+ }
21900
+ function cardHtml(input, fontUrls) {
21901
+ const { compose: cfg, width, height, illustration } = input;
21902
+ const { safeArea, headlineMinPx, bodyMinPx, illustrationHeight, palette } = cfg;
21903
+ const headlineFont = fontUrls.get(cfg.fonts.headline) ?? "";
21904
+ const bodyFont = fontUrls.get(cfg.fonts.body) ?? "";
21905
+ const headlineStartPx = headlineMinPx * 1.5;
21906
+ const textPanelHeight = height - illustrationHeight;
21907
+ const safeWidth = width - safeArea * 2;
21908
+ const illustrationSrc = illustration ? `data:${illustration.mime};base64,${Buffer.from(illustration.bytes).toString("base64")}` : "";
21909
+ const pointsHtml = input.points.map((pt, i) => `<div class="point" data-field="points[${i}]" style="font-size:${bodyMinPx}px;">${escHtml(pt)}</div>`).join(`
21910
+ `);
21911
+ return `<!DOCTYPE html>
21912
+ <html lang="zh-Hans">
21913
+ <head>
21914
+ <meta charset="utf-8">
21915
+ <style>
21916
+ @font-face {
21917
+ font-family: 'NotoSansSC-Bold';
21918
+ src: url('${headlineFont}') format('opentype');
21919
+ font-weight: bold;
21920
+ font-style: normal;
21921
+ }
21922
+ @font-face {
21923
+ font-family: 'NotoSansSC-Regular';
21924
+ src: url('${bodyFont}') format('opentype');
21925
+ font-weight: normal;
21926
+ font-style: normal;
21927
+ }
21928
+ * { box-sizing: border-box; margin: 0; padding: 0; }
21929
+ html, body {
21930
+ width: ${width}px;
21931
+ height: ${height}px;
21932
+ overflow: hidden;
21933
+ background: ${palette.background};
21934
+ }
21935
+ .card {
21936
+ width: ${width}px;
21937
+ height: ${height}px;
21938
+ display: flex;
21939
+ flex-direction: column;
21940
+ background: ${palette.background};
21941
+ }
21942
+ .illustration-band {
21943
+ width: ${width}px;
21944
+ height: ${illustrationHeight}px;
21945
+ flex-shrink: 0;
21946
+ overflow: hidden;
21947
+ background: ${palette.background};
21948
+ }
21949
+ .illustration-band img {
21950
+ width: 100%;
21951
+ height: 100%;
21952
+ object-fit: cover;
21953
+ display: block;
21954
+ }
21955
+ .text-panel {
21956
+ width: ${width}px;
21957
+ height: ${textPanelHeight}px;
21958
+ flex-shrink: 0;
21959
+ padding: ${safeArea}px;
21960
+ display: flex;
21961
+ flex-direction: column;
21962
+ justify-content: flex-start;
21963
+ overflow: hidden;
21964
+ background: ${palette.background};
21965
+ }
21966
+ .headline {
21967
+ font-family: 'NotoSansSC-Bold', sans-serif;
21968
+ font-weight: bold;
21969
+ color: ${palette.text};
21970
+ line-height: 1.3;
21971
+ margin-bottom: ${Math.round(safeArea / 2)}px;
21972
+ word-break: break-all;
21973
+ white-space: normal;
21974
+ /* font-size is set inline with JS fit-step */
21975
+ font-size: ${headlineStartPx}px;
21976
+ }
21977
+ .points-container {
21978
+ display: flex;
21979
+ flex-direction: column;
21980
+ gap: ${Math.round(bodyMinPx * 0.5)}px;
21981
+ }
21982
+ .point {
21983
+ font-family: 'NotoSansSC-Regular', sans-serif;
21984
+ font-weight: normal;
21985
+ color: ${palette.text};
21986
+ line-height: 1.5;
21987
+ word-break: break-all;
21988
+ white-space: normal;
21989
+ }
21990
+ .safe-area-sentinel {
21991
+ position: absolute;
21992
+ top: ${illustrationHeight + safeArea}px;
21993
+ left: ${safeArea}px;
21994
+ width: ${safeWidth}px;
21995
+ height: ${textPanelHeight - safeArea * 2}px;
21996
+ pointer-events: none;
21997
+ }
21998
+ </style>
21999
+ </head>
22000
+ <body>
22001
+ <div class="card">
22002
+ <div class="illustration-band">
22003
+ ${illustrationSrc ? `<img src="${illustrationSrc}" alt="">` : `<div style="width:100%;height:100%;background:${palette.background};"></div>`}
22004
+ </div>
22005
+ <div class="text-panel">
22006
+ <div class="headline" data-field="headline" id="headline">${escHtml(input.headline)}</div>
22007
+ <div class="points-container">
22008
+ ${pointsHtml}
22009
+ </div>
22010
+ </div>
22011
+ </div>
22012
+ <div class="safe-area-sentinel" id="safe-area-sentinel"></div>
22013
+ <script>
22014
+ (function () {
22015
+ // Step headline font down from start until it fits, never below floor.
22016
+ var headline = document.getElementById('headline');
22017
+ var headlineMinPx = ${headlineMinPx};
22018
+ var headlineStartPx = ${headlineStartPx};
22019
+ var safeWidth = ${safeWidth};
22020
+ var panelHeight = ${textPanelHeight - safeArea * 2};
22021
+ var size = headlineStartPx;
22022
+ while (size > headlineMinPx) {
22023
+ headline.style.fontSize = size + 'px';
22024
+ if (headline.scrollWidth <= safeWidth && headline.scrollHeight <= panelHeight) break;
22025
+ size -= 4;
22026
+ }
22027
+ headline.style.fontSize = Math.max(size, headlineMinPx) + 'px';
22028
+ })();
22029
+ </script>
22030
+ </body>
22031
+ </html>`;
22032
+ }
22033
+ function escHtml(s) {
22034
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
22035
+ }
22036
+
22037
+ // ../../plugins/generations/image-gen/src/compose/chromium.ts
22038
+ var exports_chromium = {};
22039
+ __export(exports_chromium, {
22040
+ makeChromiumComposer: () => makeChromiumComposer,
22041
+ ChromiumComposer: () => ChromiumComposer
22042
+ });
22043
+ import { join as join3 } from "path";
22044
+ function fontRouteUrls(fontNames) {
22045
+ const map2 = new Map;
22046
+ for (const name of fontNames) {
22047
+ map2.set(name, `${FONT_ROUTE_BASE}${encodeURIComponent(name)}`);
22048
+ }
22049
+ return map2;
22050
+ }
22051
+
22052
+ class ChromiumComposer {
22053
+ async compose(input) {
22054
+ let chromium;
22055
+ try {
22056
+ const pw = await import("playwright");
22057
+ chromium = pw.chromium;
22058
+ } catch (err) {
22059
+ throw new Error(`compose: Playwright is not installed. Run: bunx playwright install chromium
22060
+ (${err instanceof Error ? err.message : String(err)})`);
22061
+ }
22062
+ const fontNames = [input.compose.fonts.headline, input.compose.fonts.body];
22063
+ const fontUrls = fontRouteUrls(fontNames);
22064
+ const html = cardHtml(input, fontUrls);
22065
+ let browser;
22066
+ try {
22067
+ try {
22068
+ browser = await chromium.launch({ headless: true });
22069
+ } catch (err) {
22070
+ const msg = err instanceof Error ? err.message : String(err);
22071
+ throw new Error(`compose: Chromium browser not found. Run: bunx playwright install chromium
22072
+ (${msg})`);
22073
+ }
22074
+ const page = await browser.newPage({
22075
+ viewport: { width: input.width, height: input.height },
22076
+ deviceScaleFactor: 1
22077
+ });
22078
+ await page.route(`${FONT_ROUTE_BASE}**`, async (route) => {
22079
+ const url2 = new URL(route.request().url());
22080
+ const fontName = decodeURIComponent(url2.pathname.slice(1));
22081
+ const fontPath = join3(FONTS_DIR, fontName);
22082
+ try {
22083
+ const bytes = await readBytes(fontPath);
22084
+ await route.fulfill({
22085
+ status: 200,
22086
+ contentType: "font/otf",
22087
+ body: Buffer.from(bytes)
22088
+ });
22089
+ } catch {
22090
+ await route.abort();
22091
+ }
22092
+ });
22093
+ await page.setContent(html, { waitUntil: "domcontentloaded" });
22094
+ await page.evaluate("document.fonts.ready");
22095
+ const { safeArea, illustrationHeight } = input.compose;
22096
+ const safeLeft = safeArea;
22097
+ const safeTop = illustrationHeight + safeArea;
22098
+ const safeRight = input.width - safeArea;
22099
+ const safeBottom = input.height - safeArea;
22100
+ const overflowExpr = `
22101
+ (function(safeLeft, safeTop, safeRight, safeBottom) {
22102
+ var results = [];
22103
+ var els = Array.from(document.querySelectorAll('[data-field]'));
22104
+ for (var i = 0; i < els.length; i++) {
22105
+ var el = els[i];
22106
+ var field = el.dataset.field || '';
22107
+ var rect = el.getBoundingClientRect();
22108
+ if (rect.left < safeLeft || rect.top < safeTop || rect.right > safeRight || rect.bottom > safeBottom) {
22109
+ results.push({ field: field, rect: { top: rect.top, left: rect.left, bottom: rect.bottom, right: rect.right } });
22110
+ }
22111
+ }
22112
+ return results;
22113
+ })(${safeLeft}, ${safeTop}, ${safeRight}, ${safeBottom})
22114
+ `;
22115
+ const overflows = await page.evaluate(overflowExpr);
22116
+ if (overflows.length > 0) {
22117
+ const fields = overflows.map((o) => o.field).join(", ");
22118
+ throw new Error(`compose: text overflows the safe area for field(s): ${fields} (safe area: ${safeArea}px inset)`);
22119
+ }
22120
+ const screenshot = await page.screenshot({ type: "png", animations: "disabled" });
22121
+ return new Uint8Array(screenshot);
22122
+ } finally {
22123
+ await browser?.close();
22124
+ }
22125
+ }
22126
+ }
22127
+ function makeChromiumComposer() {
22128
+ return new ChromiumComposer;
22129
+ }
22130
+ var FONTS_DIR, FONT_ROUTE_BASE = "http://kk-fonts.local/";
22131
+ var init_chromium = __esm(() => {
22132
+ init_bytes();
22133
+ FONTS_DIR = join3(import.meta.dir, "..", "..", "assets", "fonts");
22134
+ });
22135
+
21812
22136
  // ../../plugins/generations/image-gen/src/index.ts
21813
- import { dirname as dirname3 } from "path";
22137
+ import { dirname as dirname3, join as join4 } from "path";
21814
22138
  import { parseArgs } from "util";
21815
22139
 
21816
22140
  // ../../packages/kk-core/src/kinds.ts
@@ -21908,14 +22232,141 @@ init_config();
21908
22232
  if (false) {}
21909
22233
  // ../../plugins/generations/image-gen/src/index.ts
21910
22234
  init_dist();
22235
+ init_bytes();
21911
22236
 
21912
- // ../../plugins/generations/image-gen/src/bytes.ts
21913
- import { readFile, writeFile } from "fs/promises";
21914
- async function writeBytes(path2, bytes) {
21915
- await writeFile(path2, bytes);
22237
+ // ../../plugins/generations/image-gen/src/compose/cmap.ts
22238
+ function u16(view, offset) {
22239
+ return view.getUint16(offset, false);
22240
+ }
22241
+ function u32(view, offset) {
22242
+ return view.getUint32(offset, false);
22243
+ }
22244
+ function tag(view, offset) {
22245
+ return String.fromCharCode(view.getUint8(offset), view.getUint8(offset + 1), view.getUint8(offset + 2), view.getUint8(offset + 3));
22246
+ }
22247
+ function assertBytes(view, offset, needed, context2) {
22248
+ if (offset + needed > view.byteLength) {
22249
+ throw new Error(`compose: truncated font \u2014 expected ${needed} bytes at offset ${offset} (${context2})`);
22250
+ }
22251
+ }
22252
+ function findCmapTable(view) {
22253
+ assertBytes(view, 0, 12, "sfnt header");
22254
+ const magic = u32(view, 0);
22255
+ if (magic !== 65536 && magic !== 1330926671 && magic !== 1953658213) {
22256
+ throw new Error(`compose: not a valid sfnt font (magic 0x${magic.toString(16).padStart(8, "0")})`);
22257
+ }
22258
+ const numTables = u16(view, 4);
22259
+ assertBytes(view, 12, numTables * 16, "sfnt table directory");
22260
+ for (let i = 0;i < numTables; i++) {
22261
+ const base = 12 + i * 16;
22262
+ if (tag(view, base) === "cmap") {
22263
+ const offset = u32(view, base + 8);
22264
+ const length = u32(view, base + 12);
22265
+ return { offset, length };
22266
+ }
22267
+ }
22268
+ throw new Error("compose: truncated font \u2014 cmap table not found in sfnt directory");
22269
+ }
22270
+ function coverageFormat4(view, subtableOffset) {
22271
+ assertBytes(view, subtableOffset, 14, "cmap format 4 header");
22272
+ const length = u16(view, subtableOffset + 2);
22273
+ assertBytes(view, subtableOffset, length, "cmap format 4 body");
22274
+ const segCount = u16(view, subtableOffset + 6) / 2;
22275
+ const endCodesBase = subtableOffset + 14;
22276
+ assertBytes(view, endCodesBase, segCount * 2 + 2 + segCount * 2 + segCount * 2 + segCount * 2, "cmap format 4 segments");
22277
+ const startCodesBase = endCodesBase + segCount * 2 + 2;
22278
+ const idDeltaBase = startCodesBase + segCount * 2;
22279
+ const idRangeOffsetBase = idDeltaBase + segCount * 2;
22280
+ return (cp) => {
22281
+ if (cp > 65535)
22282
+ return false;
22283
+ let lo = 0;
22284
+ let hi = segCount - 1;
22285
+ while (lo <= hi) {
22286
+ const mid = lo + hi >>> 1;
22287
+ const endCode = u16(view, endCodesBase + mid * 2);
22288
+ if (endCode < cp) {
22289
+ lo = mid + 1;
22290
+ } else {
22291
+ hi = mid - 1;
22292
+ }
22293
+ }
22294
+ if (lo >= segCount)
22295
+ return false;
22296
+ const startCode = u16(view, startCodesBase + lo * 2);
22297
+ if (cp < startCode)
22298
+ return false;
22299
+ const idRangeOffset = u16(view, idRangeOffsetBase + lo * 2);
22300
+ if (idRangeOffset === 0) {
22301
+ const idDelta = u16(view, idDeltaBase + lo * 2);
22302
+ return (cp + idDelta & 65535) !== 0;
22303
+ }
22304
+ const glyphIdAddr = idRangeOffsetBase + lo * 2 + idRangeOffset + (cp - startCode) * 2;
22305
+ if (glyphIdAddr + 2 > view.byteLength)
22306
+ return false;
22307
+ const glyphId = u16(view, glyphIdAddr);
22308
+ return glyphId !== 0;
22309
+ };
22310
+ }
22311
+ function coverageFormat12(view, subtableOffset) {
22312
+ assertBytes(view, subtableOffset, 16, "cmap format 12 header");
22313
+ const numGroups = u32(view, subtableOffset + 12);
22314
+ assertBytes(view, subtableOffset + 16, numGroups * 12, "cmap format 12 groups");
22315
+ const groupsBase = subtableOffset + 16;
22316
+ return (cp) => {
22317
+ let lo = 0;
22318
+ let hi = numGroups - 1;
22319
+ while (lo <= hi) {
22320
+ const mid = lo + hi >>> 1;
22321
+ const base = groupsBase + mid * 12;
22322
+ const startChar = u32(view, base);
22323
+ const endChar = u32(view, base + 4);
22324
+ if (endChar < cp) {
22325
+ lo = mid + 1;
22326
+ } else if (startChar > cp) {
22327
+ hi = mid - 1;
22328
+ } else {
22329
+ const startGlyph = u32(view, base + 8);
22330
+ return startGlyph + (cp - startChar) !== 0;
22331
+ }
22332
+ }
22333
+ return false;
22334
+ };
21916
22335
  }
21917
- async function readBytes(path2) {
21918
- return new Uint8Array(await readFile(path2));
22336
+ function glyphCoverage(fontBytes) {
22337
+ if (fontBytes.byteLength < 12) {
22338
+ throw new Error("compose: truncated font \u2014 too small for sfnt header");
22339
+ }
22340
+ const view = new DataView(fontBytes.buffer, fontBytes.byteOffset, fontBytes.byteLength);
22341
+ const { offset: cmapOffset } = findCmapTable(view);
22342
+ assertBytes(view, cmapOffset, 4, "cmap table header");
22343
+ const numSubtables = u16(view, cmapOffset + 2);
22344
+ assertBytes(view, cmapOffset + 4, numSubtables * 8, "cmap encoding records");
22345
+ let fmt4Offset;
22346
+ let fmt12Offset;
22347
+ for (let i = 0;i < numSubtables; i++) {
22348
+ const recBase = cmapOffset + 4 + i * 8;
22349
+ const platformId = u16(view, recBase);
22350
+ const encodingId = u16(view, recBase + 2);
22351
+ const subtableOffset = cmapOffset + u32(view, recBase + 4);
22352
+ if (subtableOffset + 2 > view.byteLength)
22353
+ continue;
22354
+ const fmt = u16(view, subtableOffset);
22355
+ if (fmt === 12 && (platformId === 3 || platformId === 0)) {
22356
+ fmt12Offset = subtableOffset;
22357
+ }
22358
+ if (fmt === 4 && (platformId === 3 || platformId === 0) && encodingId !== 10) {
22359
+ if (fmt4Offset === undefined)
22360
+ fmt4Offset = subtableOffset;
22361
+ }
22362
+ }
22363
+ if (fmt12Offset !== undefined) {
22364
+ return coverageFormat12(view, fmt12Offset);
22365
+ }
22366
+ if (fmt4Offset !== undefined) {
22367
+ return coverageFormat4(view, fmt4Offset);
22368
+ }
22369
+ throw new Error("compose: truncated font \u2014 no usable cmap subtable (format 4 or 12) found");
21919
22370
  }
21920
22371
 
21921
22372
  // ../../plugins/generations/image-gen/src/job.ts
@@ -21993,6 +22444,15 @@ var PresetSourceSchema = exports_external.object({
21993
22444
  extractedAt: exports_external.string().optional(),
21994
22445
  confidence: exports_external.enum(["high", "medium", "low"]).optional()
21995
22446
  });
22447
+ var ComposeConfigSchema = exports_external.object({
22448
+ engine: exports_external.literal("text-card"),
22449
+ safeArea: exports_external.number(),
22450
+ headlineMinPx: exports_external.number(),
22451
+ bodyMinPx: exports_external.number(),
22452
+ illustrationHeight: exports_external.number(),
22453
+ palette: exports_external.object({ background: exports_external.string(), text: exports_external.string(), accent: exports_external.string() }),
22454
+ fonts: exports_external.object({ headline: exports_external.string(), body: exports_external.string() })
22455
+ });
21996
22456
  var FormatPresetSchema = exports_external.object({
21997
22457
  name: exports_external.string(),
21998
22458
  description: exports_external.string(),
@@ -22004,7 +22464,10 @@ var FormatPresetSchema = exports_external.object({
22004
22464
  keywords: exports_external.array(exports_external.string()),
22005
22465
  outputFilename: exports_external.string(),
22006
22466
  variables: exports_external.record(exports_external.string(), exports_external.object({ description: exports_external.string(), default: exports_external.string() })),
22007
- template: exports_external.string()
22467
+ template: exports_external.string(),
22468
+ compose: ComposeConfigSchema.optional()
22469
+ }).refine((data) => data.compose === undefined || data.width !== undefined && data.height !== undefined, {
22470
+ message: "A format preset with a compose block requires both width and height to be declared"
22008
22471
  });
22009
22472
  var StylePresetSchema = exports_external.object({
22010
22473
  name: exports_external.string(),
@@ -22157,6 +22620,7 @@ function loadPresets() {
22157
22620
  import { Buffer as Buffer3 } from "buffer";
22158
22621
 
22159
22622
  // ../../plugins/generations/image-gen/src/providers/refs.ts
22623
+ init_bytes();
22160
22624
  import { extname as extname2 } from "path";
22161
22625
  function mimeForPath(path2) {
22162
22626
  const ext = extname2(path2).toLowerCase();
@@ -22178,6 +22642,7 @@ async function refToDataUri(path2) {
22178
22642
  }
22179
22643
 
22180
22644
  // ../../plugins/generations/image-gen/src/providers/seedream.ts
22645
+ init_bytes();
22181
22646
  import { Buffer as Buffer2 } from "buffer";
22182
22647
 
22183
22648
  // ../../plugins/generations/image-gen/src/providers/timeouts.ts
@@ -22376,9 +22841,11 @@ var createAgnes = (options) => {
22376
22841
  };
22377
22842
 
22378
22843
  // ../../plugins/generations/image-gen/src/providers/azure.ts
22844
+ init_bytes();
22379
22845
  import { extname as extname3 } from "path";
22380
22846
 
22381
22847
  // ../../plugins/generations/image-gen/src/providers/openai.ts
22848
+ init_bytes();
22382
22849
  import { Buffer as Buffer4 } from "buffer";
22383
22850
  import { basename as basename2 } from "path";
22384
22851
  var DEFAULT_MODEL3 = "gpt-image-2";
@@ -22788,6 +23255,7 @@ var createAzure = (options) => {
22788
23255
  };
22789
23256
 
22790
23257
  // ../../plugins/generations/image-gen/src/providers/codex-cli.ts
23258
+ init_bytes();
22791
23259
  import { spawn } from "child_process";
22792
23260
  import { randomBytes } from "crypto";
22793
23261
  import { access as access2, mkdir, rm, writeFile as writeFile2 } from "fs/promises";
@@ -23312,6 +23780,7 @@ import { execFileSync } from "child_process";
23312
23780
  import { randomUUID } from "crypto";
23313
23781
  import { tmpdir as tmpdir2 } from "os";
23314
23782
  import { join as join2 } from "path";
23783
+ init_bytes();
23315
23784
  var GOOGLE_MULTIMODAL_MODELS = [
23316
23785
  "gemini-3-pro-image",
23317
23786
  "gemini-3.1-flash-image",
@@ -23766,6 +24235,7 @@ var createJimeng = (options) => {
23766
24235
  };
23767
24236
 
23768
24237
  // ../../plugins/generations/image-gen/src/providers/minimax.ts
24238
+ init_bytes();
23769
24239
  import { Buffer as Buffer7 } from "buffer";
23770
24240
  var DEFAULT_MODEL9 = "image-01";
23771
24241
  var MAX_REFERENCE_IMAGE_BYTES = 10 * 1024 * 1024;
@@ -24896,6 +25366,65 @@ async function processGeneratorIO(inputPath, outputPath, options = {}) {
24896
25366
  }
24897
25367
  if (spec.job === "illustration")
24898
25368
  assertAnchorPlan(doc2?.body ?? "", positions, prompts.length);
25369
+ let _chromiumComposer;
25370
+ const resolveComposer = async () => {
25371
+ if (options.composer !== undefined)
25372
+ return options.composer;
25373
+ if (_chromiumComposer !== undefined)
25374
+ return _chromiumComposer;
25375
+ const { makeChromiumComposer: makeChromiumComposer2 } = await Promise.resolve().then(() => (init_chromium(), exports_chromium));
25376
+ _chromiumComposer = makeChromiumComposer2();
25377
+ return _chromiumComposer;
25378
+ };
25379
+ if (format.compose !== undefined) {
25380
+ const cfg = format.compose;
25381
+ if (spec.job !== "free") {
25382
+ throw new Error(`compose: format '${format.name}' has a compose block but the job is '${spec.job}' \u2014 compose formats require job: free`);
25383
+ }
25384
+ const vars = spec.vars ?? {};
25385
+ const headline = vars.headline ?? format.variables.headline?.default ?? "";
25386
+ if (!headline.trim()) {
25387
+ throw new Error(`compose: format '${format.name}' requires a non-empty vars.headline`);
25388
+ }
25389
+ const pointsRaw = vars.points ?? format.variables.points?.default ?? "";
25390
+ const points = pointsRaw.split(`
25391
+ `).filter((l) => l.trim() !== "");
25392
+ const promptVars = { subject: doc2?.body ?? "", ...spec.vars ?? {} };
25393
+ const textFreeSources = [
25394
+ { source: "the format template", text: format.template },
25395
+ {
25396
+ source: `the style preset '${style.name}'`,
25397
+ text: [style.modifiers, style.negative].filter((part) => part !== "").join(", ")
25398
+ },
25399
+ { source: "spec.extra", text: spec.extra ?? "" },
25400
+ ...Object.entries(promptVars).filter(([name]) => name !== "headline" && name !== "points" && name !== "index").map(([name, value]) => ({ source: `vars.${name}`, text: String(value) }))
25401
+ ];
25402
+ assertTextFreePrompt(prompts[0] ?? "", { headline, points }, textFreeSources);
25403
+ const fontsDir = join4(import.meta.dir, "..", "assets", "fonts");
25404
+ const fontNames = [cfg.fonts.headline, cfg.fonts.body];
25405
+ const fontBytesMap = new Map;
25406
+ for (const name of fontNames) {
25407
+ fontBytesMap.set(name, new Uint8Array(await readBytes(join4(fontsDir, name))));
25408
+ }
25409
+ const coverageMap = new Map;
25410
+ for (const [name, bytes] of fontBytesMap) {
25411
+ coverageMap.set(name, glyphCoverage(bytes));
25412
+ }
25413
+ if (format.width == null || format.height == null) {
25414
+ throw new Error(`image-gen: format '${format.name}' declares no canvas size; compose requires width and height`);
25415
+ }
25416
+ const cardInput = {
25417
+ headline,
25418
+ points,
25419
+ role: vars.role ?? format.variables.role?.default ?? "content",
25420
+ compose: cfg,
25421
+ width: format.width,
25422
+ height: format.height
25423
+ };
25424
+ assertCoverage(cardInput, coverageMap);
25425
+ const composer2 = await resolveComposer();
25426
+ await composer2.compose(cardInput);
25427
+ }
24899
25428
  const resolveProvider = options.providerResolver ?? defaultProviderResolver;
24900
25429
  const primary = resolveProvider(spec);
24901
25430
  const built = new Map;
@@ -25003,17 +25532,64 @@ async function processGeneratorIO(inputPath, outputPath, options = {}) {
25003
25532
  throw new Error(`Illustration anchor must occur exactly once in the source body, found ${parts.length - 1}: '${anchor.slice(0, 50)}'`);
25004
25533
  }
25005
25534
  }
25006
- const path2 = artifactPath(outputPath, stem, format.name, index + 1, result.extension);
25007
- await writeBytes(path2, result.bytes);
25008
- written.push(path2);
25535
+ let artifactPathValue;
25536
+ let artifactBytes;
25537
+ let artifactWidth;
25538
+ let artifactHeight;
25539
+ let illustrationMeta;
25540
+ if (format.compose !== undefined) {
25541
+ const illustrationArtifactPath = artifactPath(outputPath, stem, `${format.name}-illustration`, index + 1, result.extension);
25542
+ await writeBytes(illustrationArtifactPath, result.bytes);
25543
+ written.push(illustrationArtifactPath);
25544
+ const vars = spec.vars ?? {};
25545
+ const headline = vars.headline ?? format.variables.headline?.default ?? "";
25546
+ const pointsRaw = vars.points ?? format.variables.points?.default ?? "";
25547
+ const points = pointsRaw.split(`
25548
+ `).filter((l) => l.trim() !== "");
25549
+ if (format.width == null || format.height == null) {
25550
+ throw new Error(`image-gen: format '${format.name}' declares no canvas size; compose requires width and height`);
25551
+ }
25552
+ const cardInput = {
25553
+ headline,
25554
+ points,
25555
+ role: vars.role ?? format.variables.role?.default ?? "content",
25556
+ compose: format.compose,
25557
+ width: format.width,
25558
+ height: format.height,
25559
+ illustration: { bytes: result.bytes, mime: `image/${result.extension}` }
25560
+ };
25561
+ const composer2 = await resolveComposer();
25562
+ const cardBytes = await composer2.compose(cardInput);
25563
+ const cardPath = artifactPath(outputPath, stem, format.name, index + 1, "png");
25564
+ await writeBytes(cardPath, cardBytes);
25565
+ written.push(cardPath);
25566
+ const dims = imageDimensions(cardBytes);
25567
+ if (dims.width !== format.width || dims.height !== format.height) {
25568
+ throw new Error(`compose: card dimensions ${dims.width}x${dims.height} do not match format canvas ${format.width}x${format.height}`);
25569
+ }
25570
+ artifactPathValue = cardPath;
25571
+ artifactBytes = cardBytes.byteLength;
25572
+ artifactWidth = dims.width;
25573
+ artifactHeight = dims.height;
25574
+ illustrationMeta = { path: illustrationArtifactPath, bytes: result.bytes.byteLength };
25575
+ } else {
25576
+ const path2 = artifactPath(outputPath, stem, format.name, index + 1, result.extension);
25577
+ await writeBytes(path2, result.bytes);
25578
+ written.push(path2);
25579
+ artifactPathValue = path2;
25580
+ artifactBytes = result.bytes.byteLength;
25581
+ }
25009
25582
  const artifact = {
25010
- path: path2,
25583
+ path: artifactPathValue,
25011
25584
  format: format.name,
25012
25585
  style: style.name,
25013
25586
  provider: result.provider,
25014
25587
  model: result.model,
25015
25588
  prompt,
25016
- bytes: result.bytes.byteLength
25589
+ bytes: artifactBytes,
25590
+ ...artifactWidth !== undefined ? { width: artifactWidth } : {},
25591
+ ...artifactHeight !== undefined ? { height: artifactHeight } : {},
25592
+ ...illustrationMeta !== undefined ? { illustration: illustrationMeta } : {}
25017
25593
  };
25018
25594
  if (spec.seed !== undefined) {
25019
25595
  artifact.seed = spec.seed;
@@ -25024,10 +25600,10 @@ async function processGeneratorIO(inputPath, outputPath, options = {}) {
25024
25600
  const altText = `Illustration ${index + 1}`;
25025
25601
  const markdown = `${anchor}
25026
25602
 
25027
- ![${altText}](${path2})`;
25603
+ ![${altText}](${artifactPathValue})`;
25028
25604
  captions.push({
25029
25605
  index: index + 1,
25030
- path: path2,
25606
+ path: artifactPathValue,
25031
25607
  caption: anchor,
25032
25608
  altText,
25033
25609
  anchor