@hirokisakabe/pom 8.9.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +38 -1
  2. package/dist/autoFit/strategyResult.js.map +1 -1
  3. package/dist/buildPptx.d.ts +1 -1
  4. package/dist/calcYogaLayout/fontLoader.js +1 -1
  5. package/dist/calcYogaLayout/fontLoader.js.map +1 -1
  6. package/dist/extractSlideMastersAsPptx.d.ts +13 -0
  7. package/dist/extractSlideMastersAsPptx.d.ts.map +1 -0
  8. package/dist/extractSlideMastersAsPptx.js +140 -0
  9. package/dist/extractSlideMastersAsPptx.js.map +1 -0
  10. package/dist/extractThemeTokensFromPptx.d.ts +13 -0
  11. package/dist/extractThemeTokensFromPptx.d.ts.map +1 -0
  12. package/dist/extractThemeTokensFromPptx.js +139 -0
  13. package/dist/extractThemeTokensFromPptx.js.map +1 -0
  14. package/dist/icons/index.js +2 -2
  15. package/dist/index.d.ts +4 -2
  16. package/dist/index.js +4 -1
  17. package/dist/parseXml/coercionRules.js +1 -1
  18. package/dist/parseXml/parseXml.js +1 -1
  19. package/dist/parseXml/parseXml.js.map +1 -1
  20. package/dist/parseXml/serializeXml.js +1 -1
  21. package/dist/parseXml/serializeXml.js.map +1 -1
  22. package/dist/pptxLayoutEnumeration.js +152 -0
  23. package/dist/pptxLayoutEnumeration.js.map +1 -0
  24. package/dist/registry/definitions/list.js +2 -1
  25. package/dist/registry/definitions/list.js.map +1 -1
  26. package/dist/registry/definitions/shape.js +3 -2
  27. package/dist/registry/definitions/shape.js.map +1 -1
  28. package/dist/registry/definitions/text.js +3 -2
  29. package/dist/registry/definitions/text.js.map +1 -1
  30. package/dist/registry/index.js +2 -2
  31. package/dist/registry/xmlChildRules.js.map +1 -1
  32. package/dist/renderPptx/glowEffects.js +8 -3
  33. package/dist/renderPptx/glowEffects.js.map +1 -1
  34. package/dist/renderPptx/gradientFills.js +1 -1
  35. package/dist/renderPptx/nodes/timeline.js +2 -1
  36. package/dist/renderPptx/nodes/timeline.js.map +1 -1
  37. package/dist/renderPptx/textOptions.js +1 -1
  38. package/dist/renderPptx/units.js +1 -1
  39. package/dist/renderPptx/utils/scaleToFit.js +1 -1
  40. package/dist/renderPptx/utils/straightLine.js +1 -1
  41. package/dist/renderPptx/utils/visualStyle.js +1 -1
  42. package/dist/shared/gradient.js +3 -3
  43. package/dist/shared/gradient.js.map +1 -1
  44. package/dist/types.d.ts +12 -1
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/types.js +11 -1
  47. package/dist/types.js.map +1 -1
  48. package/package.json +9 -8
package/README.md CHANGED
@@ -52,7 +52,7 @@
52
52
 
53
53
  ## Installation
54
54
 
55
- > Requires Node.js 18+
55
+ > Requires Node.js 22+
56
56
 
57
57
  ```bash
58
58
  npm install @hirokisakabe/pom
@@ -87,6 +87,43 @@ Each slide must be wrapped in a `<Slide>` element. To produce multiple slides, l
87
87
  </Slide>
88
88
  ```
89
89
 
90
+ To reuse colors from an existing `.pptx` template, extract PowerPoint theme colors as pom design tokens:
91
+
92
+ ```typescript
93
+ import { readFile } from "node:fs/promises";
94
+ import { extractThemeTokensFromPptx } from "@hirokisakabe/pom";
95
+
96
+ const pptxBytes = await readFile("template.pptx");
97
+ const themeTokens = await extractThemeTokensFromPptx(pptxBytes);
98
+
99
+ // One ThemeTokens object is returned for each visible slide layout under each slide master.
100
+ console.log(themeTokens[0]);
101
+ // {
102
+ // text: "#000000",
103
+ // background: "#FFFFFF",
104
+ // primary: "#4472C4",
105
+ // secondary: "#ED7D31",
106
+ // accent3: "#A5A5A5",
107
+ // accent4: "#FFC000",
108
+ // accent5: "#5B9BD5",
109
+ // accent6: "#70AD47"
110
+ // }
111
+ ```
112
+
113
+ To build a preview deck of every layout in an existing `.pptx` template (e.g. for a "pick a layout" UI), convert it into a PPTX containing one blank slide per visible slide layout:
114
+
115
+ ```typescript
116
+ import { readFile } from "node:fs/promises";
117
+ import { extractSlideMastersAsPptx } from "@hirokisakabe/pom";
118
+
119
+ const pptxBytes = await readFile("template.pptx");
120
+ const layoutDeck = await extractSlideMastersAsPptx(pptxBytes);
121
+ // layoutDeck: ArrayBuffer — one blank slide per visible slide layout, grouped by
122
+ // slide master in presentation order. Hidden layouts (`show="0"` / `"false"`) are
123
+ // excluded. The slide order matches extractThemeTokensFromPptx's output order, so
124
+ // the two can be zipped together to pair each slide with its theme.
125
+ ```
126
+
90
127
  ## Available Nodes
91
128
 
92
129
  | Node | Description |
@@ -1 +1 @@
1
- {"version":3,"file":"strategyResult.js","names":[],"sources":["../../src/autoFit/strategyResult.ts"],"sourcesContent":["/** AutoFit strategy が変更を適用できなかった理由 */\nexport type AutoFitSkipReason =\n /** 調整対象となる属性を持つノードが存在しない */\n | \"no-target\"\n /** 調整対象はあるが、下限値への到達や丸めにより値が変化しなかった */\n | \"no-effective-change\";\n\n/** AutoFit strategy の適用結果 */\nexport type AutoFitStrategyResult =\n | { changed: true }\n | { changed: false; reason: AutoFitSkipReason };\n\n/**\n * strategy 内部で集計した「変更有無」「調整対象の有無」から共通 result を組み立てる。\n */\nexport function toStrategyResult(params: {\n changed: boolean;\n sawTarget: boolean;\n}): AutoFitStrategyResult {\n if (params.changed) {\n return { changed: true };\n }\n return {\n changed: false,\n reason: params.sawTarget ? \"no-effective-change\" : \"no-target\",\n };\n}\n"],"mappings":";;;;AAeA,SAAgB,iBAAiB,QAGP;CACxB,IAAI,OAAO,SACT,OAAO,EAAE,SAAS,KAAK;CAEzB,OAAO;EACL,SAAS;EACT,QAAQ,OAAO,YAAY,wBAAwB;CACrD;AACF"}
1
+ {"version":3,"file":"strategyResult.js","names":[],"sources":["../../src/autoFit/strategyResult.ts"],"sourcesContent":["/** AutoFit strategy が変更を適用できなかった理由 */\nexport type AutoFitSkipReason =\n /** 調整対象となる属性を持つノードが存在しない */\n | \"no-target\"\n /** 調整対象はあるが、下限値への到達や丸めにより値が変化しなかった */\n | \"no-effective-change\";\n\n/** AutoFit strategy の適用結果 */\nexport type AutoFitStrategyResult =\n { changed: true } | { changed: false; reason: AutoFitSkipReason };\n\n/**\n * strategy 内部で集計した「変更有無」「調整対象の有無」から共通 result を組み立てる。\n */\nexport function toStrategyResult(params: {\n changed: boolean;\n sawTarget: boolean;\n}): AutoFitStrategyResult {\n if (params.changed) {\n return { changed: true };\n }\n return {\n changed: false,\n reason: params.sawTarget ? \"no-effective-change\" : \"no-target\",\n };\n}\n"],"mappings":";;;;AAcA,SAAgB,iBAAiB,QAGP;CACxB,IAAI,OAAO,SACT,OAAO,EAAE,SAAS,KAAK;CAEzB,OAAO;EACL,SAAS;EACT,QAAQ,OAAO,YAAY,wBAAwB;CACrD;AACF"}
@@ -18,5 +18,5 @@ declare function buildPptx(xml: string, slideSize: {
18
18
  strict?: boolean;
19
19
  }): Promise<BuildPptxResult>;
20
20
  //#endregion
21
- export { BuildPptxResult, buildPptx };
21
+ export { BuildPptxResult, type TextMeasurementMode, buildPptx };
22
22
  //# sourceMappingURL=buildPptx.d.ts.map
@@ -33,7 +33,7 @@ function getFont(weight) {
33
33
  return font;
34
34
  }
35
35
  /** バンドル済みフォント名の一覧 */
36
- const BUNDLED_FONT_NAMES = new Set(["Noto Sans JP"]);
36
+ const BUNDLED_FONT_NAMES = /* @__PURE__ */ new Set(["Noto Sans JP"]);
37
37
  /**
38
38
  * 指定されたフォントがバンドル済みかどうかを判定する
39
39
  */
@@ -1 +1 @@
1
- {"version":3,"file":"fontLoader.js","names":[],"sources":["../../src/calcYogaLayout/fontLoader.ts"],"sourcesContent":["/**\n * opentype.js を使用したフォント読み込みモジュール\n * Node.js とブラウザ両方で動作する\n */\n\nimport type { Font } from \"opentype.js\";\nimport * as opentypeModule from \"opentype.js\";\nimport { NOTO_SANS_JP_REGULAR_BASE64 } from \"./fonts/notoSansJPRegular.ts\";\nimport { NOTO_SANS_JP_BOLD_BASE64 } from \"./fonts/notoSansJPBold.ts\";\n\n// opentype.js 2.0 は ESM ビルドで named export のみを提供する一方、\n// CJS UMD ビルドでは module.exports = factory() の動的構造のため\n// Node ESM から取り込むと named exports が静的解析できない。\n// どちらの形でも動くよう default プロパティを優先して unwrap する。\nconst opentype =\n (opentypeModule as unknown as { default?: typeof opentypeModule }).default ??\n opentypeModule;\n\n// フォントキャッシュ\nconst fontCache = new Map<string, Font>();\n\n/**\n * Base64 文字列を ArrayBuffer に変換する\n * Node.js とブラウザ両方で動作する\n */\nfunction base64ToArrayBuffer(base64: string): ArrayBuffer {\n // Node.js 環境\n if (typeof Buffer !== \"undefined\") {\n const buffer = Buffer.from(base64, \"base64\");\n return buffer.buffer.slice(\n buffer.byteOffset,\n buffer.byteOffset + buffer.byteLength,\n );\n }\n // ブラウザ環境\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * フォントを取得する(キャッシュ付き)\n * @param weight フォントウェイト (\"normal\" or \"bold\")\n * @returns opentype.js の Font オブジェクト\n */\nfunction getFont(weight: \"normal\" | \"bold\"): Font {\n const cacheKey = weight;\n\n // キャッシュがあればそれを返す\n const cached = fontCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n // Base64 データを選択\n const base64 =\n weight === \"bold\" ? NOTO_SANS_JP_BOLD_BASE64 : NOTO_SANS_JP_REGULAR_BASE64;\n\n // ArrayBuffer に変換してパース\n const buffer = base64ToArrayBuffer(base64);\n const font = opentype.parse(buffer);\n\n // キャッシュに保存\n fontCache.set(cacheKey, font);\n\n return font;\n}\n\n/** バンドル済みフォント名の一覧 */\nconst BUNDLED_FONT_NAMES = new Set([\"Noto Sans JP\"]);\n\n/**\n * 指定されたフォントがバンドル済みかどうかを判定する\n */\nexport function isBundledFont(fontFamily: string): boolean {\n return BUNDLED_FONT_NAMES.has(fontFamily);\n}\n\n/**\n * 指定したテキストの幅を計測する\n * @param text 計測するテキスト\n * @param fontSizePx フォントサイズ(ピクセル)\n * @param weight フォントウェイト\n * @returns テキスト幅(ピクセル)\n */\nexport function measureTextWidth(\n text: string,\n fontSizePx: number,\n weight: \"normal\" | \"bold\",\n): number {\n const font = getFont(weight);\n return font.getAdvanceWidth(text, fontSizePx, { kerning: true });\n}\n\n/**\n * フォントの縦方向メトリクスを fontSizePx に対する比率で取得する\n *\n * - typoAscender / typoDescender: グリフ ink のおおよその上端・下端\n * (descender は正の値に符号反転して返す)\n * - winDescent: レンダラが固定行送り (spcPts) のときに行下端から\n * baseline までの距離として確保する descent\n *\n * バンドル外フォント使用時もバンドルフォント (Noto Sans JP) の値を\n * 近似値として使う想定 (テキスト幅計測と同じ方針)。\n *\n * @param weight フォントウェイト\n * @returns 各メトリクスの fontSizePx に対する比率\n */\nexport function measureFontVerticalMetricsRatio(weight: \"normal\" | \"bold\"): {\n typoAscender: number;\n typoDescender: number;\n winDescent: number;\n} {\n const font = getFont(weight);\n const upm = font.unitsPerEm;\n const os2 = font.tables?.os2;\n\n if (!os2) {\n // メトリクスが取れない場合は Noto Sans JP 相当の値で近似する\n return { typoAscender: 0.88, typoDescender: 0.12, winDescent: 0.288 };\n }\n\n return {\n typoAscender: os2.sTypoAscender / upm,\n typoDescender: -os2.sTypoDescender / upm,\n winDescent: os2.usWinDescent / upm,\n };\n}\n\n/**\n * フォントの自然な行高さ比率を取得する\n *\n * PowerPoint の lineHeight はフォントサイズではなく、\n * フォントメトリクス(ascent + descent)に対する倍率として適用される。\n * この関数は fontSizePx に対する自然な行高さの比率を返す。\n *\n * - USE_TYPO_METRICS (fsSelection bit 7) が設定されている場合:\n * sTypoAscender, sTypoDescender, sTypoLineGap を使用\n * - 設定されていない場合:\n * usWinAscent, usWinDescent を使用\n *\n * @param weight フォントウェイト\n * @returns fontSizePx に対する行高さの比率(例: 1.448)\n */\nexport function measureFontLineHeightRatio(weight: \"normal\" | \"bold\"): number {\n const font = getFont(weight);\n const upm = font.unitsPerEm;\n const os2 = font.tables?.os2;\n\n if (!os2) {\n return 1.0;\n }\n\n const useTypoMetrics = Boolean(os2.fsSelection & (1 << 7));\n\n if (useTypoMetrics) {\n return (os2.sTypoAscender - os2.sTypoDescender + os2.sTypoLineGap) / upm;\n }\n\n return (os2.usWinAscent + os2.usWinDescent) / upm;\n}\n"],"mappings":";;;;AAcA,MAAM,WACH,eAAkE,WACnE;AAGF,MAAM,4BAAY,IAAI,IAAkB;;;;;AAMxC,SAAS,oBAAoB,QAA6B;CAExD,IAAI,OAAO,WAAW,aAAa;EACjC,MAAM,SAAS,OAAO,KAAK,QAAQ,QAAQ;EAC3C,OAAO,OAAO,OAAO,MACnB,OAAO,YACP,OAAO,aAAa,OAAO,UAC7B;CACF;CAEA,MAAM,eAAe,KAAK,MAAM;CAChC,MAAM,QAAQ,IAAI,WAAW,aAAa,MAAM;CAChD,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KACvC,MAAM,KAAK,aAAa,WAAW,CAAC;CAEtC,OAAO,MAAM;AACf;;;;;;AAOA,SAAS,QAAQ,QAAiC;CAChD,MAAM,WAAW;CAGjB,MAAM,SAAS,UAAU,IAAI,QAAQ;CACrC,IAAI,QACF,OAAO;CAQT,MAAM,SAAS,oBAHb,WAAW,SAAS,2BAA2B,2BAGR;CACzC,MAAM,OAAO,SAAS,MAAM,MAAM;CAGlC,UAAU,IAAI,UAAU,IAAI;CAE5B,OAAO;AACT;;AAGA,MAAM,qBAAqB,IAAI,IAAI,CAAC,cAAc,CAAC;;;;AAKnD,SAAgB,cAAc,YAA6B;CACzD,OAAO,mBAAmB,IAAI,UAAU;AAC1C;;;;;;;;AASA,SAAgB,iBACd,MACA,YACA,QACQ;CAER,OADa,QAAQ,MACX,CAAC,CAAC,gBAAgB,MAAM,YAAY,EAAE,SAAS,KAAK,CAAC;AACjE;;;;;;;;;;;;;;;AAgBA,SAAgB,gCAAgC,QAI9C;CACA,MAAM,OAAO,QAAQ,MAAM;CAC3B,MAAM,MAAM,KAAK;CACjB,MAAM,MAAM,KAAK,QAAQ;CAEzB,IAAI,CAAC,KAEH,OAAO;EAAE,cAAc;EAAM,eAAe;EAAM,YAAY;CAAM;CAGtE,OAAO;EACL,cAAc,IAAI,gBAAgB;EAClC,eAAe,CAAC,IAAI,iBAAiB;EACrC,YAAY,IAAI,eAAe;CACjC;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,2BAA2B,QAAmC;CAC5E,MAAM,OAAO,QAAQ,MAAM;CAC3B,MAAM,MAAM,KAAK;CACjB,MAAM,MAAM,KAAK,QAAQ;CAEzB,IAAI,CAAC,KACH,OAAO;CAKT,IAFuB,QAAQ,IAAI,cAAe,GAEjC,GACf,QAAQ,IAAI,gBAAgB,IAAI,iBAAiB,IAAI,gBAAgB;CAGvE,QAAQ,IAAI,cAAc,IAAI,gBAAgB;AAChD"}
1
+ {"version":3,"file":"fontLoader.js","names":[],"sources":["../../src/calcYogaLayout/fontLoader.ts"],"sourcesContent":["/**\n * opentype.js を使用したフォント読み込みモジュール\n * Node.js とブラウザ両方で動作する\n */\n\nimport type { Font } from \"opentype.js\";\nimport * as opentypeModule from \"opentype.js\";\nimport { NOTO_SANS_JP_REGULAR_BASE64 } from \"./fonts/notoSansJPRegular.ts\";\nimport { NOTO_SANS_JP_BOLD_BASE64 } from \"./fonts/notoSansJPBold.ts\";\n\n// opentype.js 2.0 は ESM ビルドで named export のみを提供する一方、\n// CJS UMD ビルドでは module.exports = factory() の動的構造のため\n// Node ESM から取り込むと named exports が静的解析できない。\n// どちらの形でも動くよう default プロパティを優先して unwrap する。\nconst opentype =\n (opentypeModule as unknown as { default?: typeof opentypeModule }).default ??\n opentypeModule;\n\n// フォントキャッシュ\nconst fontCache = new Map<string, Font>();\n\n/**\n * Base64 文字列を ArrayBuffer に変換する\n * Node.js とブラウザ両方で動作する\n */\nfunction base64ToArrayBuffer(base64: string): ArrayBuffer {\n // Node.js 環境\n if (typeof Buffer !== \"undefined\") {\n const buffer = Buffer.from(base64, \"base64\");\n return buffer.buffer.slice(\n buffer.byteOffset,\n buffer.byteOffset + buffer.byteLength,\n );\n }\n // ブラウザ環境\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * フォントを取得する(キャッシュ付き)\n * @param weight フォントウェイト (\"normal\" or \"bold\")\n * @returns opentype.js の Font オブジェクト\n */\nfunction getFont(weight: \"normal\" | \"bold\"): Font {\n const cacheKey = weight;\n\n // キャッシュがあればそれを返す\n const cached = fontCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n // Base64 データを選択\n const base64 =\n weight === \"bold\" ? NOTO_SANS_JP_BOLD_BASE64 : NOTO_SANS_JP_REGULAR_BASE64;\n\n // ArrayBuffer に変換してパース\n const buffer = base64ToArrayBuffer(base64);\n const font = opentype.parse(buffer);\n\n // キャッシュに保存\n fontCache.set(cacheKey, font);\n\n return font;\n}\n\n/** バンドル済みフォント名の一覧 */\nconst BUNDLED_FONT_NAMES = new Set([\"Noto Sans JP\"]);\n\n/**\n * 指定されたフォントがバンドル済みかどうかを判定する\n */\nexport function isBundledFont(fontFamily: string): boolean {\n return BUNDLED_FONT_NAMES.has(fontFamily);\n}\n\n/**\n * 指定したテキストの幅を計測する\n * @param text 計測するテキスト\n * @param fontSizePx フォントサイズ(ピクセル)\n * @param weight フォントウェイト\n * @returns テキスト幅(ピクセル)\n */\nexport function measureTextWidth(\n text: string,\n fontSizePx: number,\n weight: \"normal\" | \"bold\",\n): number {\n const font = getFont(weight);\n return font.getAdvanceWidth(text, fontSizePx, { kerning: true });\n}\n\n/**\n * フォントの縦方向メトリクスを fontSizePx に対する比率で取得する\n *\n * - typoAscender / typoDescender: グリフ ink のおおよその上端・下端\n * (descender は正の値に符号反転して返す)\n * - winDescent: レンダラが固定行送り (spcPts) のときに行下端から\n * baseline までの距離として確保する descent\n *\n * バンドル外フォント使用時もバンドルフォント (Noto Sans JP) の値を\n * 近似値として使う想定 (テキスト幅計測と同じ方針)。\n *\n * @param weight フォントウェイト\n * @returns 各メトリクスの fontSizePx に対する比率\n */\nexport function measureFontVerticalMetricsRatio(weight: \"normal\" | \"bold\"): {\n typoAscender: number;\n typoDescender: number;\n winDescent: number;\n} {\n const font = getFont(weight);\n const upm = font.unitsPerEm;\n const os2 = font.tables?.os2;\n\n if (!os2) {\n // メトリクスが取れない場合は Noto Sans JP 相当の値で近似する\n return { typoAscender: 0.88, typoDescender: 0.12, winDescent: 0.288 };\n }\n\n return {\n typoAscender: os2.sTypoAscender / upm,\n typoDescender: -os2.sTypoDescender / upm,\n winDescent: os2.usWinDescent / upm,\n };\n}\n\n/**\n * フォントの自然な行高さ比率を取得する\n *\n * PowerPoint の lineHeight はフォントサイズではなく、\n * フォントメトリクス(ascent + descent)に対する倍率として適用される。\n * この関数は fontSizePx に対する自然な行高さの比率を返す。\n *\n * - USE_TYPO_METRICS (fsSelection bit 7) が設定されている場合:\n * sTypoAscender, sTypoDescender, sTypoLineGap を使用\n * - 設定されていない場合:\n * usWinAscent, usWinDescent を使用\n *\n * @param weight フォントウェイト\n * @returns fontSizePx に対する行高さの比率(例: 1.448)\n */\nexport function measureFontLineHeightRatio(weight: \"normal\" | \"bold\"): number {\n const font = getFont(weight);\n const upm = font.unitsPerEm;\n const os2 = font.tables?.os2;\n\n if (!os2) {\n return 1.0;\n }\n\n const useTypoMetrics = Boolean(os2.fsSelection & (1 << 7));\n\n if (useTypoMetrics) {\n return (os2.sTypoAscender - os2.sTypoDescender + os2.sTypoLineGap) / upm;\n }\n\n return (os2.usWinAscent + os2.usWinDescent) / upm;\n}\n"],"mappings":";;;;AAcA,MAAM,WACH,eAAkE,WACnE;AAGF,MAAM,4BAAY,IAAI,IAAkB;;;;;AAMxC,SAAS,oBAAoB,QAA6B;CAExD,IAAI,OAAO,WAAW,aAAa;EACjC,MAAM,SAAS,OAAO,KAAK,QAAQ,QAAQ;EAC3C,OAAO,OAAO,OAAO,MACnB,OAAO,YACP,OAAO,aAAa,OAAO,UAC7B;CACF;CAEA,MAAM,eAAe,KAAK,MAAM;CAChC,MAAM,QAAQ,IAAI,WAAW,aAAa,MAAM;CAChD,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KACvC,MAAM,KAAK,aAAa,WAAW,CAAC;CAEtC,OAAO,MAAM;AACf;;;;;;AAOA,SAAS,QAAQ,QAAiC;CAChD,MAAM,WAAW;CAGjB,MAAM,SAAS,UAAU,IAAI,QAAQ;CACrC,IAAI,QACF,OAAO;CAQT,MAAM,SAAS,oBAHb,WAAW,SAAS,2BAA2B,2BAGR;CACzC,MAAM,OAAO,SAAS,MAAM,MAAM;CAGlC,UAAU,IAAI,UAAU,IAAI;CAE5B,OAAO;AACT;;AAGA,MAAM,qCAAqB,IAAI,IAAI,CAAC,cAAc,CAAC;;;;AAKnD,SAAgB,cAAc,YAA6B;CACzD,OAAO,mBAAmB,IAAI,UAAU;AAC1C;;;;;;;;AASA,SAAgB,iBACd,MACA,YACA,QACQ;CAER,OADa,QAAQ,MACX,CAAC,CAAC,gBAAgB,MAAM,YAAY,EAAE,SAAS,KAAK,CAAC;AACjE;;;;;;;;;;;;;;;AAgBA,SAAgB,gCAAgC,QAI9C;CACA,MAAM,OAAO,QAAQ,MAAM;CAC3B,MAAM,MAAM,KAAK;CACjB,MAAM,MAAM,KAAK,QAAQ;CAEzB,IAAI,CAAC,KAEH,OAAO;EAAE,cAAc;EAAM,eAAe;EAAM,YAAY;CAAM;CAGtE,OAAO;EACL,cAAc,IAAI,gBAAgB;EAClC,eAAe,CAAC,IAAI,iBAAiB;EACrC,YAAY,IAAI,eAAe;CACjC;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,2BAA2B,QAAmC;CAC5E,MAAM,OAAO,QAAQ,MAAM;CAC3B,MAAM,MAAM,KAAK;CACjB,MAAM,MAAM,KAAK,QAAQ;CAEzB,IAAI,CAAC,KACH,OAAO;CAKT,IAFuB,QAAQ,IAAI,cAAe,GAEjC,GACf,QAAQ,IAAI,gBAAgB,IAAI,iBAAiB,IAAI,gBAAgB;CAGvE,QAAQ,IAAI,cAAc,IAAI,gBAAgB;AAChD"}
@@ -0,0 +1,13 @@
1
+ //#region src/extractSlideMastersAsPptx.d.ts
2
+ /**
3
+ * Convert a PPTX buffer into a new PPTX that contains exactly one blank slide
4
+ * per visible slide layout, grouped by slide master in presentation order.
5
+ *
6
+ * Layouts hidden via `p:sldLayout[show="0"]` (or `"false"`) are excluded. The
7
+ * slide/layout order matches {@link extractThemeTokensFromPptx}, so the two
8
+ * outputs can be zipped together to pair each generated slide with its theme.
9
+ */
10
+ declare function extractSlideMastersAsPptx(pptxBuffer: ArrayBuffer | Uint8Array): Promise<ArrayBuffer>;
11
+ //#endregion
12
+ export { extractSlideMastersAsPptx };
13
+ //# sourceMappingURL=extractSlideMastersAsPptx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractSlideMastersAsPptx.d.ts","names":[],"sources":["../src/extractSlideMastersAsPptx.ts"],"mappings":";;AAyMA;;;;;;;iBAAsB,yBAAA,CACpB,UAAA,EAAY,WAAA,GAAc,UAAA,GACzB,OAAA,CAAQ,WAAA"}
@@ -0,0 +1,140 @@
1
+ import { getMasterLayoutPartPaths, getPresentationMasterPartPaths, isVisibleLayout, toUint8Array } from "./pptxLayoutEnumeration.js";
2
+ import { XMLBuilder, XMLParser } from "fast-xml-parser";
3
+ import { readPptx } from "@pptx-glimpse/document";
4
+ //#region src/extractSlideMastersAsPptx.ts
5
+ const SLIDE_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
6
+ const SLIDE_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
7
+ const PRESENTATION_PART_PATH = "ppt/presentation.xml";
8
+ const PRESENTATION_RELS_PART_PATH = "ppt/_rels/presentation.xml.rels";
9
+ const CONTENT_TYPES_PART_PATH = "[Content_Types].xml";
10
+ const PRESENTATION_CHILD_ORDER = [
11
+ "p:sldMasterIdLst",
12
+ "p:notesMasterIdLst",
13
+ "p:handoutMasterIdLst",
14
+ "p:sldIdLst",
15
+ "p:sldSz",
16
+ "p:notesSz",
17
+ "p:embeddedFontLst",
18
+ "p:custShowLst",
19
+ "p:photoAlbum",
20
+ "p:custDataLst",
21
+ "p:kinsoku",
22
+ "p:defaultTextStyle",
23
+ "p:modifyVerifier",
24
+ "p:extLst"
25
+ ];
26
+ async function loadJSZip() {
27
+ const mod = await import("jszip");
28
+ return mod.default ?? mod;
29
+ }
30
+ const xmlParser = new XMLParser({
31
+ ignoreAttributes: false,
32
+ attributeNamePrefix: "@_",
33
+ isArray: (_name, jpath) => {
34
+ return jpath === "Relationships.Relationship" || jpath === "Types.Override" || jpath === "Types.Default" || jpath === "p:presentation.p:sldIdLst.p:sldId" || jpath === "p:presentation.p:sldMasterIdLst.p:sldMasterId";
35
+ }
36
+ });
37
+ const xmlBuilder = new XMLBuilder({
38
+ ignoreAttributes: false,
39
+ attributeNamePrefix: "@_",
40
+ format: true,
41
+ suppressEmptyNode: false
42
+ });
43
+ function blankSlideXml() {
44
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
45
+ <p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
46
+ <p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/></p:spTree></p:cSld>
47
+ </p:sld>`;
48
+ }
49
+ function blankSlideRelsXml(layoutPartPath) {
50
+ return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
51
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
52
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="/${layoutPartPath}"/>
53
+ </Relationships>`;
54
+ }
55
+ async function readZipText(zip, partPath) {
56
+ const file = zip.file(partPath);
57
+ if (file === null) throw new Error(`${partPath} not found in the PPTX`);
58
+ return file.async("text");
59
+ }
60
+ function reorderPresentationChildren(presentation) {
61
+ const attributeEntries = Object.entries(presentation).filter(([key]) => key.startsWith("@_"));
62
+ const knownKeys = new Set(PRESENTATION_CHILD_ORDER);
63
+ const orderedChildEntries = PRESENTATION_CHILD_ORDER.filter((key) => key in presentation).map((key) => [key, presentation[key]]);
64
+ const otherChildEntries = Object.entries(presentation).filter(([key]) => !key.startsWith("@_") && !knownKeys.has(key));
65
+ return Object.fromEntries([
66
+ ...attributeEntries,
67
+ ...orderedChildEntries,
68
+ ...otherChildEntries
69
+ ]);
70
+ }
71
+ async function updatePresentationXml(zip, slideCount) {
72
+ const xml = await readZipText(zip, PRESENTATION_PART_PATH);
73
+ const parsed = xmlParser.parse(xml);
74
+ const presentation = parsed["p:presentation"];
75
+ const maxSldId = (presentation["p:sldIdLst"]?.["p:sldId"] ?? []).reduce((max, sldId) => {
76
+ const id = parseInt(sldId["@_id"] ?? "0", 10);
77
+ return Number.isFinite(id) ? Math.max(max, id) : max;
78
+ }, 255);
79
+ presentation["p:sldIdLst"] = { "p:sldId": Array.from({ length: slideCount }, (_, index) => ({
80
+ "@_id": String(maxSldId + 1 + index),
81
+ "@_r:id": `rIdSlide${index + 1}`
82
+ })) };
83
+ parsed["p:presentation"] = reorderPresentationChildren(presentation);
84
+ zip.file(PRESENTATION_PART_PATH, xmlBuilder.build(parsed));
85
+ }
86
+ async function updatePresentationRelsXml(zip, slideCount) {
87
+ const xml = await readZipText(zip, PRESENTATION_RELS_PART_PATH);
88
+ const parsed = xmlParser.parse(xml);
89
+ const relationshipsRoot = parsed.Relationships;
90
+ const nonSlideRelationships = (relationshipsRoot.Relationship ?? []).filter((relationship) => relationship["@_Type"] !== SLIDE_REL_TYPE);
91
+ const newSlideRelationships = Array.from({ length: slideCount }, (_, index) => ({
92
+ "@_Id": `rIdSlide${index + 1}`,
93
+ "@_Type": SLIDE_REL_TYPE,
94
+ "@_Target": `slides/slide${index + 1}.xml`
95
+ }));
96
+ relationshipsRoot.Relationship = [...nonSlideRelationships, ...newSlideRelationships];
97
+ zip.file(PRESENTATION_RELS_PART_PATH, xmlBuilder.build(parsed));
98
+ }
99
+ async function updateContentTypesXml(zip, slideCount) {
100
+ const xml = await readZipText(zip, CONTENT_TYPES_PART_PATH);
101
+ const parsed = xmlParser.parse(xml);
102
+ const typesRoot = parsed.Types;
103
+ const nonSlideOverrides = (typesRoot.Override ?? []).filter((override) => !/^\/ppt\/slides\/slide\d+\.xml$/.test(override["@_PartName"] ?? ""));
104
+ const newSlideOverrides = Array.from({ length: slideCount }, (_, index) => ({
105
+ "@_PartName": `/ppt/slides/slide${index + 1}.xml`,
106
+ "@_ContentType": SLIDE_CONTENT_TYPE
107
+ }));
108
+ typesRoot.Override = [...nonSlideOverrides, ...newSlideOverrides];
109
+ zip.file(CONTENT_TYPES_PART_PATH, xmlBuilder.build(parsed));
110
+ }
111
+ /**
112
+ * Convert a PPTX buffer into a new PPTX that contains exactly one blank slide
113
+ * per visible slide layout, grouped by slide master in presentation order.
114
+ *
115
+ * Layouts hidden via `p:sldLayout[show="0"]` (or `"false"`) are excluded. The
116
+ * slide/layout order matches {@link extractThemeTokensFromPptx}, so the two
117
+ * outputs can be zipped together to pair each generated slide with its theme.
118
+ */
119
+ async function extractSlideMastersAsPptx(pptxBuffer) {
120
+ const buffer = toUint8Array(pptxBuffer);
121
+ const source = readPptx(buffer);
122
+ const layoutPartPaths = [];
123
+ for (const masterPartPath of getPresentationMasterPartPaths(source)) for (const layoutPartPath of getMasterLayoutPartPaths(source, masterPartPath)) if (isVisibleLayout(source, layoutPartPath)) layoutPartPaths.push(layoutPartPath);
124
+ if (layoutPartPaths.length === 0) throw new Error("No visible slide layouts found for slide masters");
125
+ const zip = await (await loadJSZip()).loadAsync(buffer);
126
+ for (const path of Object.keys(zip.files)) if (path.startsWith("ppt/slides/")) zip.remove(path);
127
+ layoutPartPaths.forEach((layoutPartPath, index) => {
128
+ const slideNumber = index + 1;
129
+ zip.file(`ppt/slides/slide${slideNumber}.xml`, blankSlideXml());
130
+ zip.file(`ppt/slides/_rels/slide${slideNumber}.xml.rels`, blankSlideRelsXml(layoutPartPath));
131
+ });
132
+ await updatePresentationXml(zip, layoutPartPaths.length);
133
+ await updatePresentationRelsXml(zip, layoutPartPaths.length);
134
+ await updateContentTypesXml(zip, layoutPartPaths.length);
135
+ return zip.generateAsync({ type: "arraybuffer" });
136
+ }
137
+ //#endregion
138
+ export { extractSlideMastersAsPptx };
139
+
140
+ //# sourceMappingURL=extractSlideMastersAsPptx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractSlideMastersAsPptx.js","names":[],"sources":["../src/extractSlideMastersAsPptx.ts"],"sourcesContent":["import { readPptx } from \"@pptx-glimpse/document\";\nimport { XMLBuilder, XMLParser } from \"fast-xml-parser\";\nimport {\n getMasterLayoutPartPaths,\n getPresentationMasterPartPaths,\n isVisibleLayout,\n toUint8Array,\n} from \"./pptxLayoutEnumeration.ts\";\n\nconst SLIDE_REL_TYPE =\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide\";\nconst SLIDE_CONTENT_TYPE =\n \"application/vnd.openxmlformats-officedocument.presentationml.slide+xml\";\nconst PRESENTATION_PART_PATH = \"ppt/presentation.xml\";\nconst PRESENTATION_RELS_PART_PATH = \"ppt/_rels/presentation.xml.rels\";\nconst CONTENT_TYPES_PART_PATH = \"[Content_Types].xml\";\n\n// CT_Presentation (ECMA-376) の子要素の宣言順。p:sldIdLst が元々存在しない\n// PPTX を扱う際、末尾追加ではなくこの順序で組み直して schema 違反を防ぐ。\nconst PRESENTATION_CHILD_ORDER = [\n \"p:sldMasterIdLst\",\n \"p:notesMasterIdLst\",\n \"p:handoutMasterIdLst\",\n \"p:sldIdLst\",\n \"p:sldSz\",\n \"p:notesSz\",\n \"p:embeddedFontLst\",\n \"p:custShowLst\",\n \"p:photoAlbum\",\n \"p:custDataLst\",\n \"p:kinsoku\",\n \"p:defaultTextStyle\",\n \"p:modifyVerifier\",\n \"p:extLst\",\n];\n\n// JSZip は CJS パッケージのため動的 import で読み込む\nasync function loadJSZip(): Promise<typeof import(\"jszip\")> {\n const mod = await import(\"jszip\");\n /* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n return (mod as any).default ?? mod;\n /* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n}\n\nconst xmlParser = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: \"@_\",\n isArray: (_name, jpath) => {\n return (\n jpath === \"Relationships.Relationship\" ||\n jpath === \"Types.Override\" ||\n jpath === \"Types.Default\" ||\n jpath === \"p:presentation.p:sldIdLst.p:sldId\" ||\n jpath === \"p:presentation.p:sldMasterIdLst.p:sldMasterId\"\n );\n },\n});\n\nconst xmlBuilder = new XMLBuilder({\n ignoreAttributes: false,\n attributeNamePrefix: \"@_\",\n format: true,\n suppressEmptyNode: false,\n});\n\n/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument */\n\nfunction blankSlideXml(): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<p:sld xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\">\n <p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id=\"1\" name=\"\"/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr/></p:spTree></p:cSld>\n</p:sld>`;\n}\n\nfunction blankSlideRelsXml(layoutPartPath: string): string {\n return `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n <Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout\" Target=\"/${layoutPartPath}\"/>\n</Relationships>`;\n}\n\nasync function readZipText(\n zip: import(\"jszip\"),\n partPath: string,\n): Promise<string> {\n const file = zip.file(partPath);\n if (file === null) throw new Error(`${partPath} not found in the PPTX`);\n return file.async(\"text\");\n}\n\nfunction reorderPresentationChildren(\n presentation: Record<string, unknown>,\n): Record<string, unknown> {\n const attributeEntries = Object.entries(presentation).filter(([key]) =>\n key.startsWith(\"@_\"),\n );\n const knownKeys = new Set(PRESENTATION_CHILD_ORDER);\n const orderedChildEntries = PRESENTATION_CHILD_ORDER.filter(\n (key) => key in presentation,\n ).map((key) => [key, presentation[key]] as const);\n const otherChildEntries = Object.entries(presentation).filter(\n ([key]) => !key.startsWith(\"@_\") && !knownKeys.has(key),\n );\n\n return Object.fromEntries([\n ...attributeEntries,\n ...orderedChildEntries,\n ...otherChildEntries,\n ]);\n}\n\nasync function updatePresentationXml(\n zip: import(\"jszip\"),\n slideCount: number,\n): Promise<void> {\n const xml = await readZipText(zip, PRESENTATION_PART_PATH);\n const parsed = xmlParser.parse(xml);\n const presentation = parsed[\"p:presentation\"];\n\n const existingSldIds: Array<Record<string, string>> =\n presentation[\"p:sldIdLst\"]?.[\"p:sldId\"] ?? [];\n const maxSldId = existingSldIds.reduce((max, sldId) => {\n const id = parseInt(sldId[\"@_id\"] ?? \"0\", 10);\n return Number.isFinite(id) ? Math.max(max, id) : max;\n }, 255);\n\n presentation[\"p:sldIdLst\"] = {\n \"p:sldId\": Array.from({ length: slideCount }, (_, index) => ({\n \"@_id\": String(maxSldId + 1 + index),\n \"@_r:id\": `rIdSlide${index + 1}`,\n })),\n };\n\n parsed[\"p:presentation\"] = reorderPresentationChildren(presentation);\n zip.file(PRESENTATION_PART_PATH, xmlBuilder.build(parsed));\n}\n\nasync function updatePresentationRelsXml(\n zip: import(\"jszip\"),\n slideCount: number,\n): Promise<void> {\n const xml = await readZipText(zip, PRESENTATION_RELS_PART_PATH);\n const parsed = xmlParser.parse(xml);\n const relationshipsRoot = parsed.Relationships;\n\n const existingRelationships: Array<Record<string, string>> =\n relationshipsRoot.Relationship ?? [];\n const nonSlideRelationships = existingRelationships.filter(\n (relationship) => relationship[\"@_Type\"] !== SLIDE_REL_TYPE,\n );\n const newSlideRelationships = Array.from(\n { length: slideCount },\n (_, index) => ({\n \"@_Id\": `rIdSlide${index + 1}`,\n \"@_Type\": SLIDE_REL_TYPE,\n \"@_Target\": `slides/slide${index + 1}.xml`,\n }),\n );\n\n relationshipsRoot.Relationship = [\n ...nonSlideRelationships,\n ...newSlideRelationships,\n ];\n\n zip.file(PRESENTATION_RELS_PART_PATH, xmlBuilder.build(parsed));\n}\n\nasync function updateContentTypesXml(\n zip: import(\"jszip\"),\n slideCount: number,\n): Promise<void> {\n const xml = await readZipText(zip, CONTENT_TYPES_PART_PATH);\n const parsed = xmlParser.parse(xml);\n const typesRoot = parsed.Types;\n\n const existingOverrides: Array<Record<string, string>> =\n typesRoot.Override ?? [];\n const nonSlideOverrides = existingOverrides.filter(\n (override) =>\n !/^\\/ppt\\/slides\\/slide\\d+\\.xml$/.test(override[\"@_PartName\"] ?? \"\"),\n );\n const newSlideOverrides = Array.from({ length: slideCount }, (_, index) => ({\n \"@_PartName\": `/ppt/slides/slide${index + 1}.xml`,\n \"@_ContentType\": SLIDE_CONTENT_TYPE,\n }));\n\n typesRoot.Override = [...nonSlideOverrides, ...newSlideOverrides];\n\n zip.file(CONTENT_TYPES_PART_PATH, xmlBuilder.build(parsed));\n}\n\n/* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-argument */\n\n/**\n * Convert a PPTX buffer into a new PPTX that contains exactly one blank slide\n * per visible slide layout, grouped by slide master in presentation order.\n *\n * Layouts hidden via `p:sldLayout[show=\"0\"]` (or `\"false\"`) are excluded. The\n * slide/layout order matches {@link extractThemeTokensFromPptx}, so the two\n * outputs can be zipped together to pair each generated slide with its theme.\n */\nexport async function extractSlideMastersAsPptx(\n pptxBuffer: ArrayBuffer | Uint8Array,\n): Promise<ArrayBuffer> {\n const buffer = toUint8Array(pptxBuffer);\n const source = readPptx(buffer);\n\n const layoutPartPaths: string[] = [];\n for (const masterPartPath of getPresentationMasterPartPaths(source)) {\n for (const layoutPartPath of getMasterLayoutPartPaths(\n source,\n masterPartPath,\n )) {\n if (isVisibleLayout(source, layoutPartPath)) {\n layoutPartPaths.push(layoutPartPath);\n }\n }\n }\n\n if (layoutPartPaths.length === 0) {\n throw new Error(\"No visible slide layouts found for slide masters\");\n }\n\n const JSZip = await loadJSZip();\n const zip = await JSZip.loadAsync(buffer);\n\n for (const path of Object.keys(zip.files)) {\n if (path.startsWith(\"ppt/slides/\")) zip.remove(path);\n }\n\n layoutPartPaths.forEach((layoutPartPath, index) => {\n const slideNumber = index + 1;\n zip.file(`ppt/slides/slide${slideNumber}.xml`, blankSlideXml());\n zip.file(\n `ppt/slides/_rels/slide${slideNumber}.xml.rels`,\n blankSlideRelsXml(layoutPartPath),\n );\n });\n\n await updatePresentationXml(zip, layoutPartPaths.length);\n await updatePresentationRelsXml(zip, layoutPartPaths.length);\n await updateContentTypesXml(zip, layoutPartPaths.length);\n\n return zip.generateAsync({ type: \"arraybuffer\" });\n}\n"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { ThemeTokens } from "./types.js";
2
+
3
+ //#region src/extractThemeTokensFromPptx.d.ts
4
+ /**
5
+ * Extract PowerPoint theme colors as pom ThemeTokens.
6
+ *
7
+ * The result repeats each slide master's tokens once for every visible layout
8
+ * under that master, preserving the source master/layout relationship order.
9
+ */
10
+ declare function extractThemeTokensFromPptx(pptxBuffer: ArrayBuffer | Uint8Array): Promise<ThemeTokens[]>;
11
+ //#endregion
12
+ export { extractThemeTokensFromPptx };
13
+ //# sourceMappingURL=extractThemeTokensFromPptx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractThemeTokensFromPptx.d.ts","names":[],"sources":["../src/extractThemeTokensFromPptx.ts"],"mappings":";;;;;AA0MA;;;;iBAAgB,0BAAA,CACd,UAAA,EAAY,WAAA,GAAc,UAAA,GACzB,OAAA,CAAQ,WAAA"}
@@ -0,0 +1,139 @@
1
+ import { FALLBACK_THEME_TOKENS } from "./types.js";
2
+ import { getAttributeByLocalName, getAttributes, getChildByLocalName, getMasterLayoutPartPaths, getPresentationMasterPartPaths, isVisibleLayout, parseXmlRoot, primitiveString, readRawPartXml, resolveRelationshipsByType, toUint8Array } from "./pptxLayoutEnumeration.js";
3
+ import { readPptx } from "@pptx-glimpse/document";
4
+ //#region src/extractThemeTokensFromPptx.ts
5
+ const THEME_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme";
6
+ const DEFAULT_COLOR_MAP = {
7
+ bg1: "lt1",
8
+ tx1: "dk1",
9
+ bg2: "lt2",
10
+ tx2: "dk2",
11
+ accent1: "accent1",
12
+ accent2: "accent2",
13
+ accent3: "accent3",
14
+ accent4: "accent4",
15
+ accent5: "accent5",
16
+ accent6: "accent6",
17
+ hlink: "hlink",
18
+ folHlink: "folHlink"
19
+ };
20
+ const THEME_TOKEN_SLOT_MAP = {
21
+ text: "tx1",
22
+ background: "bg1",
23
+ primary: "accent1",
24
+ secondary: "accent2",
25
+ accent3: "accent3",
26
+ accent4: "accent4",
27
+ accent5: "accent5",
28
+ accent6: "accent6"
29
+ };
30
+ const SYSTEM_COLOR_FALLBACKS = {
31
+ windowText: "#000000",
32
+ window: "#FFFFFF"
33
+ };
34
+ function normalizeHex(value) {
35
+ if (value === void 0) return void 0;
36
+ const raw = value.startsWith("#") ? value.slice(1) : value;
37
+ if (!/^[0-9a-fA-F]{6}$/.test(raw)) return void 0;
38
+ return `#${raw.toUpperCase()}`;
39
+ }
40
+ function resolveSourceColor(color) {
41
+ if (color === void 0) return void 0;
42
+ if (typeof color === "string") return normalizeHex(color);
43
+ if (color.kind === "srgb") return normalizeHex(color.hex);
44
+ if (color.kind === "system") return normalizeHex(color.lastColor) ?? SYSTEM_COLOR_FALLBACKS[color.value] ?? void 0;
45
+ }
46
+ function tokensFromColorScheme(colors, colorMap) {
47
+ const resolve = (token) => {
48
+ const logicalSlot = THEME_TOKEN_SLOT_MAP[token];
49
+ const schemeSlot = colorMap[logicalSlot] ?? logicalSlot;
50
+ return resolveSourceColor(colors?.[schemeSlot]) ?? resolveSourceColor(colors?.[logicalSlot]) ?? FALLBACK_THEME_TOKENS[token];
51
+ };
52
+ return {
53
+ text: resolve("text"),
54
+ background: resolve("background"),
55
+ primary: resolve("primary"),
56
+ secondary: resolve("secondary"),
57
+ accent3: resolve("accent3"),
58
+ accent4: resolve("accent4"),
59
+ accent5: resolve("accent5"),
60
+ accent6: resolve("accent6")
61
+ };
62
+ }
63
+ function fallbackTokensFromColorScheme(colors) {
64
+ const resolve = (token) => {
65
+ const schemeSlot = DEFAULT_COLOR_MAP[THEME_TOKEN_SLOT_MAP[token]];
66
+ return resolveSourceColor(colors?.[schemeSlot]) ?? FALLBACK_THEME_TOKENS[token];
67
+ };
68
+ return {
69
+ text: resolve("text"),
70
+ background: resolve("background"),
71
+ primary: resolve("primary"),
72
+ secondary: resolve("secondary"),
73
+ accent3: resolve("accent3"),
74
+ accent4: resolve("accent4"),
75
+ accent5: resolve("accent5"),
76
+ accent6: resolve("accent6")
77
+ };
78
+ }
79
+ function parseRawColorElement(parent) {
80
+ const srgbValue = primitiveString(getAttributeByLocalName(getChildByLocalName(parent, "srgbClr"), "val"));
81
+ if (srgbValue !== void 0) return normalizeHex(srgbValue);
82
+ const system = getChildByLocalName(parent, "sysClr");
83
+ const lastColor = primitiveString(getAttributeByLocalName(system, "lastClr"));
84
+ if (lastColor !== void 0) return normalizeHex(lastColor);
85
+ const systemValue = primitiveString(getAttributeByLocalName(system, "val"));
86
+ if (systemValue !== void 0) return SYSTEM_COLOR_FALLBACKS[systemValue];
87
+ }
88
+ function getThemeColorScheme(source, themePartPath) {
89
+ const typedTheme = source.themes.find((theme) => theme.partPath === themePartPath);
90
+ if (typedTheme?.colorScheme?.colors !== void 0) return typedTheme.colorScheme.colors;
91
+ const themeXml = readRawPartXml(source, themePartPath);
92
+ if (themeXml === void 0) return void 0;
93
+ const colorScheme = getChildByLocalName(getChildByLocalName(parseXmlRoot(themeXml, "theme"), "themeElements"), "clrScheme");
94
+ const colors = {};
95
+ for (const slot of Object.values(DEFAULT_COLOR_MAP)) {
96
+ const color = parseRawColorElement(getChildByLocalName(colorScheme, slot));
97
+ if (color !== void 0) colors[slot] = color;
98
+ }
99
+ return colors;
100
+ }
101
+ function getMasterThemePartPath(source, masterPartPath) {
102
+ return source.slideMasters.find((master) => master.partPath === masterPartPath)?.themePartPath ?? resolveRelationshipsByType(source, masterPartPath, THEME_REL_TYPE)[0];
103
+ }
104
+ function getMasterColorMap(source, masterPartPath) {
105
+ const typedMaster = source.slideMasters.find((master) => master.partPath === masterPartPath);
106
+ if (typedMaster?.colorMap?.mapping !== void 0) return {
107
+ ...DEFAULT_COLOR_MAP,
108
+ ...typedMaster.colorMap.mapping
109
+ };
110
+ const masterXml = readRawPartXml(source, masterPartPath);
111
+ if (masterXml === void 0) return DEFAULT_COLOR_MAP;
112
+ const colorMap = getAttributes(getChildByLocalName(parseXmlRoot(masterXml, "sldMaster"), "clrMap"));
113
+ return {
114
+ ...DEFAULT_COLOR_MAP,
115
+ ...colorMap
116
+ };
117
+ }
118
+ /**
119
+ * Extract PowerPoint theme colors as pom ThemeTokens.
120
+ *
121
+ * The result repeats each slide master's tokens once for every visible layout
122
+ * under that master, preserving the source master/layout relationship order.
123
+ */
124
+ function extractThemeTokensFromPptx(pptxBuffer) {
125
+ const source = readPptx(toUint8Array(pptxBuffer));
126
+ const tokens = [];
127
+ for (const masterPartPath of getPresentationMasterPartPaths(source)) {
128
+ const themePartPath = getMasterThemePartPath(source, masterPartPath);
129
+ const colorScheme = getThemeColorScheme(source, themePartPath);
130
+ const colorMap = getMasterColorMap(source, masterPartPath);
131
+ const masterTokens = themePartPath === void 0 ? fallbackTokensFromColorScheme(colorScheme) : tokensFromColorScheme(colorScheme, colorMap);
132
+ for (const layoutPartPath of getMasterLayoutPartPaths(source, masterPartPath)) if (isVisibleLayout(source, layoutPartPath)) tokens.push(masterTokens);
133
+ }
134
+ return Promise.resolve(tokens);
135
+ }
136
+ //#endregion
137
+ export { extractThemeTokensFromPptx };
138
+
139
+ //# sourceMappingURL=extractThemeTokensFromPptx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractThemeTokensFromPptx.js","names":[],"sources":["../src/extractThemeTokensFromPptx.ts"],"sourcesContent":["import { readPptx } from \"@pptx-glimpse/document\";\nimport type { PptxSourceModel, SourceColor } from \"@pptx-glimpse/document\";\nimport {\n getAttributeByLocalName,\n getAttributes,\n getChildByLocalName,\n getMasterLayoutPartPaths,\n getPresentationMasterPartPaths,\n isVisibleLayout,\n parseXmlRoot,\n primitiveString,\n readRawPartXml,\n resolveRelationshipsByType,\n toUint8Array,\n} from \"./pptxLayoutEnumeration.ts\";\nimport { FALLBACK_THEME_TOKENS } from \"./types.ts\";\nimport type { ThemeTokens } from \"./types.ts\";\n\nconst THEME_REL_TYPE =\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme\";\n\nconst DEFAULT_COLOR_MAP: Record<string, string> = {\n bg1: \"lt1\",\n tx1: \"dk1\",\n bg2: \"lt2\",\n tx2: \"dk2\",\n accent1: \"accent1\",\n accent2: \"accent2\",\n accent3: \"accent3\",\n accent4: \"accent4\",\n accent5: \"accent5\",\n accent6: \"accent6\",\n hlink: \"hlink\",\n folHlink: \"folHlink\",\n};\n\nconst THEME_TOKEN_SLOT_MAP = {\n text: \"tx1\",\n background: \"bg1\",\n primary: \"accent1\",\n secondary: \"accent2\",\n accent3: \"accent3\",\n accent4: \"accent4\",\n accent5: \"accent5\",\n accent6: \"accent6\",\n} as const satisfies Record<keyof ThemeTokens, string>;\n\nconst SYSTEM_COLOR_FALLBACKS: Record<string, string> = {\n windowText: \"#000000\",\n window: \"#FFFFFF\",\n};\n\nfunction normalizeHex(value: string | undefined): string | undefined {\n if (value === undefined) return undefined;\n const raw = value.startsWith(\"#\") ? value.slice(1) : value;\n if (!/^[0-9a-fA-F]{6}$/.test(raw)) return undefined;\n return `#${raw.toUpperCase()}`;\n}\n\ntype ThemeColorValue = SourceColor | string;\n\nfunction resolveSourceColor(\n color: ThemeColorValue | undefined,\n): string | undefined {\n if (color === undefined) return undefined;\n if (typeof color === \"string\") return normalizeHex(color);\n if (color.kind === \"srgb\") return normalizeHex(color.hex);\n if (color.kind === \"system\") {\n return (\n normalizeHex(color.lastColor) ??\n SYSTEM_COLOR_FALLBACKS[color.value] ??\n undefined\n );\n }\n return undefined;\n}\n\nfunction tokensFromColorScheme(\n colors: Readonly<Record<string, ThemeColorValue>> | undefined,\n colorMap: Readonly<Record<string, string>>,\n): ThemeTokens {\n const resolve = (token: keyof ThemeTokens): string => {\n const logicalSlot = THEME_TOKEN_SLOT_MAP[token];\n const schemeSlot = colorMap[logicalSlot] ?? logicalSlot;\n return (\n resolveSourceColor(colors?.[schemeSlot]) ??\n resolveSourceColor(colors?.[logicalSlot]) ??\n FALLBACK_THEME_TOKENS[token]\n );\n };\n\n return {\n text: resolve(\"text\"),\n background: resolve(\"background\"),\n primary: resolve(\"primary\"),\n secondary: resolve(\"secondary\"),\n accent3: resolve(\"accent3\"),\n accent4: resolve(\"accent4\"),\n accent5: resolve(\"accent5\"),\n accent6: resolve(\"accent6\"),\n };\n}\n\nfunction fallbackTokensFromColorScheme(\n colors: Readonly<Record<string, ThemeColorValue>> | undefined,\n): ThemeTokens {\n const resolve = (token: keyof ThemeTokens): string => {\n const schemeSlot = DEFAULT_COLOR_MAP[THEME_TOKEN_SLOT_MAP[token]];\n return (\n resolveSourceColor(colors?.[schemeSlot]) ?? FALLBACK_THEME_TOKENS[token]\n );\n };\n\n return {\n text: resolve(\"text\"),\n background: resolve(\"background\"),\n primary: resolve(\"primary\"),\n secondary: resolve(\"secondary\"),\n accent3: resolve(\"accent3\"),\n accent4: resolve(\"accent4\"),\n accent5: resolve(\"accent5\"),\n accent6: resolve(\"accent6\"),\n };\n}\n\nfunction parseRawColorElement(parent: unknown): string | undefined {\n const srgb = getChildByLocalName(parent, \"srgbClr\");\n const srgbValue = primitiveString(getAttributeByLocalName(srgb, \"val\"));\n if (srgbValue !== undefined) return normalizeHex(srgbValue);\n\n const system = getChildByLocalName(parent, \"sysClr\");\n const lastColor = primitiveString(getAttributeByLocalName(system, \"lastClr\"));\n if (lastColor !== undefined) return normalizeHex(lastColor);\n const systemValue = primitiveString(getAttributeByLocalName(system, \"val\"));\n if (systemValue !== undefined) return SYSTEM_COLOR_FALLBACKS[systemValue];\n\n return undefined;\n}\n\nfunction getThemeColorScheme(\n source: PptxSourceModel,\n themePartPath: string | undefined,\n): Readonly<Record<string, ThemeColorValue>> | undefined {\n const typedTheme = source.themes.find(\n (theme) => theme.partPath === themePartPath,\n );\n if (typedTheme?.colorScheme?.colors !== undefined) {\n return typedTheme.colorScheme.colors;\n }\n\n const themeXml = readRawPartXml(source, themePartPath);\n if (themeXml === undefined) return undefined;\n\n const root = parseXmlRoot(themeXml, \"theme\");\n const themeElements = getChildByLocalName(root, \"themeElements\");\n const colorScheme = getChildByLocalName(themeElements, \"clrScheme\");\n const colors: Record<string, string> = {};\n for (const slot of Object.values(DEFAULT_COLOR_MAP)) {\n const color = parseRawColorElement(getChildByLocalName(colorScheme, slot));\n if (color !== undefined) colors[slot] = color;\n }\n return colors;\n}\n\nfunction getMasterThemePartPath(\n source: PptxSourceModel,\n masterPartPath: string,\n): string | undefined {\n const typedMaster = source.slideMasters.find(\n (master) => master.partPath === masterPartPath,\n );\n return (\n typedMaster?.themePartPath ??\n resolveRelationshipsByType(source, masterPartPath, THEME_REL_TYPE)[0]\n );\n}\n\nfunction getMasterColorMap(\n source: PptxSourceModel,\n masterPartPath: string,\n): Record<string, string> {\n const typedMaster = source.slideMasters.find(\n (master) => master.partPath === masterPartPath,\n );\n if (typedMaster?.colorMap?.mapping !== undefined) {\n return { ...DEFAULT_COLOR_MAP, ...typedMaster.colorMap.mapping };\n }\n\n const masterXml = readRawPartXml(source, masterPartPath);\n if (masterXml === undefined) return DEFAULT_COLOR_MAP;\n\n const root = parseXmlRoot(masterXml, \"sldMaster\");\n const colorMap = getAttributes(getChildByLocalName(root, \"clrMap\"));\n return { ...DEFAULT_COLOR_MAP, ...colorMap };\n}\n\n/**\n * Extract PowerPoint theme colors as pom ThemeTokens.\n *\n * The result repeats each slide master's tokens once for every visible layout\n * under that master, preserving the source master/layout relationship order.\n */\nexport function extractThemeTokensFromPptx(\n pptxBuffer: ArrayBuffer | Uint8Array,\n): Promise<ThemeTokens[]> {\n const source = readPptx(toUint8Array(pptxBuffer));\n const tokens: ThemeTokens[] = [];\n\n for (const masterPartPath of getPresentationMasterPartPaths(source)) {\n const themePartPath = getMasterThemePartPath(source, masterPartPath);\n const colorScheme = getThemeColorScheme(source, themePartPath);\n const colorMap = getMasterColorMap(source, masterPartPath);\n const masterTokens =\n themePartPath === undefined\n ? fallbackTokensFromColorScheme(colorScheme)\n : tokensFromColorScheme(colorScheme, colorMap);\n\n for (const layoutPartPath of getMasterLayoutPartPaths(\n source,\n masterPartPath,\n )) {\n if (isVisibleLayout(source, layoutPartPath)) {\n tokens.push(masterTokens);\n }\n }\n }\n\n return Promise.resolve(tokens);\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import "./renderIcon.js";
2
- export {};
1
+ import { rasterizeIcon, rasterizeSvgContent } from "./renderIcon.js";
2
+ export { rasterizeIcon, rasterizeSvgContent };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
- import { MasterImageObject, MasterLineObject, MasterObject, MasterRectObject, MasterTextObject, POMNode, SlideMasterBackground, SlideMasterMargin, SlideMasterOptions, SlideNumberOptions } from "./types.js";
1
+ import { FALLBACK_THEME_TOKENS, MasterImageObject, MasterLineObject, MasterObject, MasterRectObject, MasterTextObject, POMNode, SlideMasterBackground, SlideMasterMargin, SlideMasterOptions, SlideNumberOptions, ThemeTokens } from "./types.js";
2
2
  import { ParseXmlError, parseXml } from "./parseXml/parseXml.js";
3
3
  import { serializeXml } from "./parseXml/serializeXml.js";
4
4
  import { TextMeasurementMode } from "./calcYogaLayout/measureText.js";
5
5
  import { Diagnostic, DiagnosticCode, DiagnosticsError } from "./diagnostics.js";
6
6
  import { BuildPptxResult, buildPptx } from "./buildPptx.js";
7
- export { type BuildPptxResult, type Diagnostic, type DiagnosticCode, DiagnosticsError, type MasterImageObject, type MasterLineObject, type MasterObject, type MasterRectObject, type MasterTextObject, type POMNode, ParseXmlError, type SlideMasterBackground, type SlideMasterMargin, type SlideMasterOptions, type SlideNumberOptions, type TextMeasurementMode, buildPptx, parseXml, serializeXml };
7
+ import { extractThemeTokensFromPptx } from "./extractThemeTokensFromPptx.js";
8
+ import { extractSlideMastersAsPptx } from "./extractSlideMastersAsPptx.js";
9
+ export { type BuildPptxResult, type Diagnostic, type DiagnosticCode, DiagnosticsError, FALLBACK_THEME_TOKENS, type MasterImageObject, type MasterLineObject, type MasterObject, type MasterRectObject, type MasterTextObject, type POMNode, ParseXmlError, type SlideMasterBackground, type SlideMasterMargin, type SlideMasterOptions, type SlideNumberOptions, type TextMeasurementMode, type ThemeTokens, buildPptx, extractSlideMastersAsPptx, extractThemeTokensFromPptx, parseXml, serializeXml };
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
+ import { FALLBACK_THEME_TOKENS } from "./types.js";
1
2
  import { DiagnosticsError } from "./diagnostics.js";
2
3
  import { ParseXmlError, parseXml } from "./parseXml/parseXml.js";
3
4
  import { buildPptx } from "./buildPptx.js";
5
+ import { extractThemeTokensFromPptx } from "./extractThemeTokensFromPptx.js";
6
+ import { extractSlideMastersAsPptx } from "./extractSlideMastersAsPptx.js";
4
7
  import { serializeXml } from "./parseXml/serializeXml.js";
5
- export { DiagnosticsError, ParseXmlError, buildPptx, parseXml, serializeXml };
8
+ export { DiagnosticsError, FALLBACK_THEME_TOKENS, ParseXmlError, buildPptx, extractSlideMastersAsPptx, extractThemeTokensFromPptx, parseXml, serializeXml };
@@ -572,6 +572,6 @@ const CHILD_ELEMENT_COERCION_MAP = {
572
572
  }
573
573
  };
574
574
  //#endregion
575
- export { CHILD_ELEMENT_COERCION_MAP, NODE_COERCION_MAP, coerceFallback, coerceWithRule, getObjectShapeFromRule, resolveMixedNotationShorthand };
575
+ export { CHILD_ELEMENT_COERCION_MAP, NODE_COERCION_MAP, coerceFallback, coerceUnionWithRules, coerceWithRule, getObjectShapeFromRule, isBooleanObjectUnionRule, resolveMixedNotationShorthand };
576
576
 
577
577
  //# sourceMappingURL=coercionRules.js.map
@@ -13,7 +13,7 @@ var ParseXmlError = class extends Error {
13
13
  }
14
14
  };
15
15
  Object.fromEntries(NODE_METADATA.map((def) => [def.tagName, def.type]));
16
- const UNIVERSAL_ATTRS = new Set(["x", "y"]);
16
+ const UNIVERSAL_ATTRS = /* @__PURE__ */ new Set(["x", "y"]);
17
17
  function getKnownAttributes(nodeType) {
18
18
  const rules = NODE_COERCION_MAP[nodeType];
19
19
  if (!rules) return [];