@hirokisakabe/pom 9.0.0 → 9.1.1

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 (59) hide show
  1. package/README.md +37 -0
  2. package/dist/autoFit/strategyResult.js.map +1 -1
  3. package/dist/buildContext.js +3 -1
  4. package/dist/buildContext.js.map +1 -1
  5. package/dist/buildPptx.d.ts +1 -1
  6. package/dist/buildPptx.d.ts.map +1 -1
  7. package/dist/buildPptx.js +2 -0
  8. package/dist/buildPptx.js.map +1 -1
  9. package/dist/calcYogaLayout/fontLoader.js +1 -1
  10. package/dist/calcYogaLayout/fontLoader.js.map +1 -1
  11. package/dist/extractSlideMastersAsPptx.d.ts +13 -0
  12. package/dist/extractSlideMastersAsPptx.d.ts.map +1 -0
  13. package/dist/extractSlideMastersAsPptx.js +140 -0
  14. package/dist/extractSlideMastersAsPptx.js.map +1 -0
  15. package/dist/extractThemeTokensFromPptx.d.ts +13 -0
  16. package/dist/extractThemeTokensFromPptx.d.ts.map +1 -0
  17. package/dist/extractThemeTokensFromPptx.js +139 -0
  18. package/dist/extractThemeTokensFromPptx.js.map +1 -0
  19. package/dist/icons/index.js +2 -2
  20. package/dist/index.d.ts +4 -2
  21. package/dist/index.js +4 -1
  22. package/dist/parseXml/coercionRules.js +1 -1
  23. package/dist/parseXml/parseXml.js +1 -1
  24. package/dist/parseXml/parseXml.js.map +1 -1
  25. package/dist/parseXml/serializeXml.js +1 -1
  26. package/dist/parseXml/serializeXml.js.map +1 -1
  27. package/dist/pptxLayoutEnumeration.js +152 -0
  28. package/dist/pptxLayoutEnumeration.js.map +1 -0
  29. package/dist/registry/definitions/list.js +2 -1
  30. package/dist/registry/definitions/list.js.map +1 -1
  31. package/dist/registry/definitions/shape.js +3 -2
  32. package/dist/registry/definitions/shape.js.map +1 -1
  33. package/dist/registry/definitions/text.js +3 -2
  34. package/dist/registry/definitions/text.js.map +1 -1
  35. package/dist/registry/index.js +2 -2
  36. package/dist/registry/xmlChildRules.js.map +1 -1
  37. package/dist/renderPptx/glimpseTextBoxes.js +347 -0
  38. package/dist/renderPptx/glimpseTextBoxes.js.map +1 -0
  39. package/dist/renderPptx/glowEffects.js +13 -3
  40. package/dist/renderPptx/glowEffects.js.map +1 -1
  41. package/dist/renderPptx/gradientFills.js +7 -20
  42. package/dist/renderPptx/gradientFills.js.map +1 -1
  43. package/dist/renderPptx/nodes/text.js +16 -45
  44. package/dist/renderPptx/nodes/text.js.map +1 -1
  45. package/dist/renderPptx/nodes/timeline.js +3 -2
  46. package/dist/renderPptx/nodes/timeline.js.map +1 -1
  47. package/dist/renderPptx/textOptions.js +1 -1
  48. package/dist/renderPptx/units.js +1 -1
  49. package/dist/renderPptx/utils/backgroundBorder.js +1 -1
  50. package/dist/renderPptx/utils/scaleToFit.js +1 -1
  51. package/dist/renderPptx/utils/straightLine.js +1 -1
  52. package/dist/renderPptx/utils/visualStyle.js +1 -1
  53. package/dist/shared/gradient.js +3 -3
  54. package/dist/shared/gradient.js.map +1 -1
  55. package/dist/types.d.ts +12 -1
  56. package/dist/types.d.ts.map +1 -1
  57. package/dist/types.js +12 -2
  58. package/dist/types.js.map +1 -1
  59. package/package.json +9 -8
@@ -1 +1 @@
1
- {"version":3,"file":"shape.js","names":[],"sources":["../../../src/registry/definitions/shape.ts"],"sourcesContent":["import type { POMNode } from \"../../types.ts\";\nimport type { NodeDefinition, Yoga } from \"../types.ts\";\nimport type { Node as YogaNode } from \"yoga-layout\";\nimport { measureText } from \"../../calcYogaLayout/measureText.ts\";\nimport type { BuildContext } from \"../../buildContext.ts\";\nimport { renderShapeNode } from \"../../renderPptx/nodes/shape.ts\";\nimport { getNodeMetadata } from \"../nodeMetadata.ts\";\n\nexport const shapeNodeDef: NodeDefinition = {\n ...getNodeMetadata(\"shape\"),\n applyYogaStyle(node: POMNode, yn: YogaNode, yoga: Yoga, ctx: BuildContext) {\n const n = node as Extract<POMNode, { type: \"shape\" }>;\n if (n.text) {\n const text = n.text;\n const fontSizePx = n.fontSize ?? 24;\n const fontFamily = n.fontFamily ?? \"Noto Sans JP\";\n const fontWeight = n.bold ? \"bold\" : \"normal\";\n const lineHeight = n.lineHeight ?? 1.3;\n\n yn.setMeasureFunc((width, widthMode) => {\n const maxWidthPx = (() => {\n switch (widthMode) {\n case yoga.MEASURE_MODE_UNDEFINED:\n return Number.POSITIVE_INFINITY;\n case yoga.MEASURE_MODE_EXACTLY:\n case yoga.MEASURE_MODE_AT_MOST:\n return width;\n default:\n return Number.POSITIVE_INFINITY;\n }\n })();\n\n const { widthPx, heightPx } = measureText(\n text,\n maxWidthPx,\n {\n fontFamily,\n fontSizePx,\n lineHeight,\n fontWeight,\n },\n ctx.textMeasurementMode,\n );\n\n return { width: widthPx, height: heightPx };\n });\n }\n },\n render(node, ctx) {\n renderShapeNode(node as Extract<typeof node, { type: \"shape\" }>, ctx);\n },\n};\n"],"mappings":";;;;AAQA,MAAa,eAA+B;CAC1C,GAAG,gBAAgB,OAAO;CAC1B,eAAe,MAAe,IAAc,MAAY,KAAmB;EACzE,MAAM,IAAI;EACV,IAAI,EAAE,MAAM;GACV,MAAM,OAAO,EAAE;GACf,MAAM,aAAa,EAAE,YAAY;GACjC,MAAM,aAAa,EAAE,cAAc;GACnC,MAAM,aAAa,EAAE,OAAO,SAAS;GACrC,MAAM,aAAa,EAAE,cAAc;GAEnC,GAAG,gBAAgB,OAAO,cAAc;IAatC,MAAM,EAAE,SAAS,aAAa,YAC5B,aAbwB;KACxB,QAAQ,WAAR;MACE,KAAK,KAAK,wBACR,OAAO,OAAO;MAChB,KAAK,KAAK;MACV,KAAK,KAAK,sBACR,OAAO;MACT,SACE,OAAO,OAAO;KAClB;IACF,EAAA,CAIW,GACT;KACE;KACA;KACA;KACA;IACF,GACA,IAAI,mBACN;IAEA,OAAO;KAAE,OAAO;KAAS,QAAQ;IAAS;GAC5C,CAAC;EACH;CACF;CACA,OAAO,MAAM,KAAK;EAChB,gBAAgB,MAAiD,GAAG;CACtE;AACF"}
1
+ {"version":3,"file":"shape.js","names":[],"sources":["../../../src/registry/definitions/shape.ts"],"sourcesContent":["import type { POMNode } from \"../../types.ts\";\nimport type { NodeDefinition, Yoga } from \"../types.ts\";\nimport type { Node as YogaNode } from \"yoga-layout\";\nimport { measureText } from \"../../calcYogaLayout/measureText.ts\";\nimport type { BuildContext } from \"../../buildContext.ts\";\nimport { renderShapeNode } from \"../../renderPptx/nodes/shape.ts\";\nimport { getNodeMetadata } from \"../nodeMetadata.ts\";\n\nexport const shapeNodeDef: NodeDefinition = {\n ...getNodeMetadata(\"shape\"),\n applyYogaStyle(node: POMNode, yn: YogaNode, yoga: Yoga, ctx: BuildContext) {\n const n = node as Extract<POMNode, { type: \"shape\" }>;\n if (n.text) {\n const text = n.text;\n const fontSizePx = n.fontSize ?? 24;\n const fontFamily = n.fontFamily ?? \"Noto Sans JP\";\n const fontWeight = n.bold ? \"bold\" : \"normal\";\n const lineHeight = n.lineHeight ?? 1.3;\n\n yn.setMeasureFunc((width, widthMode) => {\n const maxWidthPx = (() => {\n switch (widthMode) {\n case yoga.MEASURE_MODE_UNDEFINED:\n return Number.POSITIVE_INFINITY;\n case yoga.MEASURE_MODE_EXACTLY:\n case yoga.MEASURE_MODE_AT_MOST:\n return width;\n default:\n return Number.POSITIVE_INFINITY;\n }\n })();\n\n const { widthPx, heightPx } = measureText(\n text,\n maxWidthPx,\n {\n fontFamily,\n fontSizePx,\n lineHeight,\n fontWeight,\n },\n ctx.textMeasurementMode,\n );\n\n return { width: widthPx, height: heightPx };\n });\n }\n },\n render(node, ctx) {\n renderShapeNode(node as Extract<typeof node, { type: \"shape\" }>, ctx);\n },\n};\n"],"mappings":";;;;AAQA,MAAa,eAA+B;CAC1C,GAAG,gBAAgB,OAAO;CAC1B,eAAe,MAAe,IAAc,MAAY,KAAmB;EACzE,MAAM,IAAI;EACV,IAAI,EAAE,MAAM;GACV,MAAM,OAAO,EAAE;GACf,MAAM,aAAa,EAAE,YAAY;GACjC,MAAM,aAAa,EAAE,cAAc;GACnC,MAAM,aAAa,EAAE,OAAO,SAAS;GACrC,MAAM,aAAa,EAAE,cAAc;GAEnC,GAAG,gBAAgB,OAAO,cAAc;IACtC,MAAM,oBAAoB;KACxB,QAAQ,WAAR;MACE,KAAK,KAAK,wBACR,OAAO,OAAO;MAChB,KAAK,KAAK;MACV,KAAK,KAAK,sBACR,OAAO;MACT,SACE,OAAO,OAAO;KAClB;IACF,EAAA,CAAG;IAEH,MAAM,EAAE,SAAS,aAAa,YAC5B,MACA,YACA;KACE;KACA;KACA;KACA;IACF,GACA,IAAI,mBACN;IAEA,OAAO;KAAE,OAAO;KAAS,QAAQ;IAAS;GAC5C,CAAC;EACH;CACF;CACA,OAAO,MAAM,KAAK;EAChB,gBAAgB,MAAiD,GAAG;CACtE;AACF"}
@@ -14,14 +14,15 @@ const textNodeDef = {
14
14
  const lineHeight = n.lineHeight ?? 1.3;
15
15
  const letterSpacingPx = n.letterSpacing;
16
16
  yn.setMeasureFunc((width, widthMode) => {
17
- const { widthPx, heightPx } = measureText(text, (() => {
17
+ const maxWidthPx = (() => {
18
18
  switch (widthMode) {
19
19
  case yoga.MEASURE_MODE_UNDEFINED: return Number.POSITIVE_INFINITY;
20
20
  case yoga.MEASURE_MODE_EXACTLY:
21
21
  case yoga.MEASURE_MODE_AT_MOST: return width;
22
22
  default: return Number.POSITIVE_INFINITY;
23
23
  }
24
- })(), {
24
+ })();
25
+ const { widthPx, heightPx } = measureText(text, maxWidthPx, {
25
26
  fontFamily,
26
27
  fontSizePx,
27
28
  lineHeight,
@@ -1 +1 @@
1
- {"version":3,"file":"text.js","names":[],"sources":["../../../src/registry/definitions/text.ts"],"sourcesContent":["import type { POMNode } from \"../../types.ts\";\nimport type { NodeDefinition, Yoga } from \"../types.ts\";\nimport type { Node as YogaNode } from \"yoga-layout\";\nimport { measureText } from \"../../calcYogaLayout/measureText.ts\";\nimport type { BuildContext } from \"../../buildContext.ts\";\nimport { renderTextNode } from \"../../renderPptx/nodes/text.ts\";\nimport { getNodeMetadata } from \"../nodeMetadata.ts\";\n\nexport const textNodeDef: NodeDefinition = {\n ...getNodeMetadata(\"text\"),\n applyYogaStyle(node: POMNode, yn: YogaNode, yoga: Yoga, ctx: BuildContext) {\n const n = node as Extract<POMNode, { type: \"text\" }>;\n const text = n.text;\n const baseFontSizePx = n.fontSize ?? 24;\n // runs に fontSize override がある場合は最大値で計測する(描画は run 単位で\n // 各自の fontSize が適用されるが、計測関数は単一 fontSize しか扱えないので\n // 最大値を採用して縦方向の clipping を防ぐ。\n // 横方向は最大値だと過剰評価になるが、典型ケース (大きな数字 + 小さい単位)\n // では小さい unit 部分が短く影響は軽微で、安全側に倒す)\n const fontSizePx = Math.max(\n baseFontSizePx,\n ...(n.runs?.map((r) => r.fontSize ?? baseFontSizePx) ?? []),\n );\n const fontFamily = n.fontFamily ?? \"Noto Sans JP\";\n const fontWeight = n.bold ? \"bold\" : \"normal\";\n const lineHeight = n.lineHeight ?? 1.3;\n const letterSpacingPx = n.letterSpacing;\n\n yn.setMeasureFunc((width, widthMode) => {\n const maxWidthPx = (() => {\n switch (widthMode) {\n case yoga.MEASURE_MODE_UNDEFINED:\n return Number.POSITIVE_INFINITY;\n case yoga.MEASURE_MODE_EXACTLY:\n case yoga.MEASURE_MODE_AT_MOST:\n return width;\n default:\n return Number.POSITIVE_INFINITY;\n }\n })();\n\n const { widthPx, heightPx } = measureText(\n text,\n maxWidthPx,\n {\n fontFamily,\n fontSizePx,\n lineHeight,\n fontWeight,\n letterSpacingPx,\n },\n ctx.textMeasurementMode,\n );\n\n return { width: widthPx, height: heightPx };\n });\n },\n render(node, ctx) {\n renderTextNode(node as Extract<typeof node, { type: \"text\" }>, ctx);\n },\n};\n"],"mappings":";;;;AAQA,MAAa,cAA8B;CACzC,GAAG,gBAAgB,MAAM;CACzB,eAAe,MAAe,IAAc,MAAY,KAAmB;EACzE,MAAM,IAAI;EACV,MAAM,OAAO,EAAE;EACf,MAAM,iBAAiB,EAAE,YAAY;EAMrC,MAAM,aAAa,KAAK,IACtB,gBACA,GAAI,EAAE,MAAM,KAAK,MAAM,EAAE,YAAY,cAAc,KAAK,CAAC,CAC3D;EACA,MAAM,aAAa,EAAE,cAAc;EACnC,MAAM,aAAa,EAAE,OAAO,SAAS;EACrC,MAAM,aAAa,EAAE,cAAc;EACnC,MAAM,kBAAkB,EAAE;EAE1B,GAAG,gBAAgB,OAAO,cAAc;GAatC,MAAM,EAAE,SAAS,aAAa,YAC5B,aAbwB;IACxB,QAAQ,WAAR;KACE,KAAK,KAAK,wBACR,OAAO,OAAO;KAChB,KAAK,KAAK;KACV,KAAK,KAAK,sBACR,OAAO;KACT,SACE,OAAO,OAAO;IAClB;GACF,EAAA,CAIW,GACT;IACE;IACA;IACA;IACA;IACA;GACF,GACA,IAAI,mBACN;GAEA,OAAO;IAAE,OAAO;IAAS,QAAQ;GAAS;EAC5C,CAAC;CACH;CACA,OAAO,MAAM,KAAK;EAChB,eAAe,MAAgD,GAAG;CACpE;AACF"}
1
+ {"version":3,"file":"text.js","names":[],"sources":["../../../src/registry/definitions/text.ts"],"sourcesContent":["import type { POMNode } from \"../../types.ts\";\nimport type { NodeDefinition, Yoga } from \"../types.ts\";\nimport type { Node as YogaNode } from \"yoga-layout\";\nimport { measureText } from \"../../calcYogaLayout/measureText.ts\";\nimport type { BuildContext } from \"../../buildContext.ts\";\nimport { renderTextNode } from \"../../renderPptx/nodes/text.ts\";\nimport { getNodeMetadata } from \"../nodeMetadata.ts\";\n\nexport const textNodeDef: NodeDefinition = {\n ...getNodeMetadata(\"text\"),\n applyYogaStyle(node: POMNode, yn: YogaNode, yoga: Yoga, ctx: BuildContext) {\n const n = node as Extract<POMNode, { type: \"text\" }>;\n const text = n.text;\n const baseFontSizePx = n.fontSize ?? 24;\n // runs に fontSize override がある場合は最大値で計測する(描画は run 単位で\n // 各自の fontSize が適用されるが、計測関数は単一 fontSize しか扱えないので\n // 最大値を採用して縦方向の clipping を防ぐ。\n // 横方向は最大値だと過剰評価になるが、典型ケース (大きな数字 + 小さい単位)\n // では小さい unit 部分が短く影響は軽微で、安全側に倒す)\n const fontSizePx = Math.max(\n baseFontSizePx,\n ...(n.runs?.map((r) => r.fontSize ?? baseFontSizePx) ?? []),\n );\n const fontFamily = n.fontFamily ?? \"Noto Sans JP\";\n const fontWeight = n.bold ? \"bold\" : \"normal\";\n const lineHeight = n.lineHeight ?? 1.3;\n const letterSpacingPx = n.letterSpacing;\n\n yn.setMeasureFunc((width, widthMode) => {\n const maxWidthPx = (() => {\n switch (widthMode) {\n case yoga.MEASURE_MODE_UNDEFINED:\n return Number.POSITIVE_INFINITY;\n case yoga.MEASURE_MODE_EXACTLY:\n case yoga.MEASURE_MODE_AT_MOST:\n return width;\n default:\n return Number.POSITIVE_INFINITY;\n }\n })();\n\n const { widthPx, heightPx } = measureText(\n text,\n maxWidthPx,\n {\n fontFamily,\n fontSizePx,\n lineHeight,\n fontWeight,\n letterSpacingPx,\n },\n ctx.textMeasurementMode,\n );\n\n return { width: widthPx, height: heightPx };\n });\n },\n render(node, ctx) {\n renderTextNode(node as Extract<typeof node, { type: \"text\" }>, ctx);\n },\n};\n"],"mappings":";;;;AAQA,MAAa,cAA8B;CACzC,GAAG,gBAAgB,MAAM;CACzB,eAAe,MAAe,IAAc,MAAY,KAAmB;EACzE,MAAM,IAAI;EACV,MAAM,OAAO,EAAE;EACf,MAAM,iBAAiB,EAAE,YAAY;EAMrC,MAAM,aAAa,KAAK,IACtB,gBACA,GAAI,EAAE,MAAM,KAAK,MAAM,EAAE,YAAY,cAAc,KAAK,CAAC,CAC3D;EACA,MAAM,aAAa,EAAE,cAAc;EACnC,MAAM,aAAa,EAAE,OAAO,SAAS;EACrC,MAAM,aAAa,EAAE,cAAc;EACnC,MAAM,kBAAkB,EAAE;EAE1B,GAAG,gBAAgB,OAAO,cAAc;GACtC,MAAM,oBAAoB;IACxB,QAAQ,WAAR;KACE,KAAK,KAAK,wBACR,OAAO,OAAO;KAChB,KAAK,KAAK;KACV,KAAK,KAAK,sBACR,OAAO;KACT,SACE,OAAO,OAAO;IAClB;GACF,EAAA,CAAG;GAEH,MAAM,EAAE,SAAS,aAAa,YAC5B,MACA,YACA;IACE;IACA;IACA;IACA;IACA;GACF,GACA,IAAI,mBACN;GAEA,OAAO;IAAE,OAAO;IAAS,QAAQ;GAAS;EAC5C,CAAC;CACH;CACA,OAAO,MAAM,KAAK;EAChB,eAAe,MAAgD,GAAG;CACpE;AACF"}
@@ -1,4 +1,4 @@
1
- import { registerNode } from "./nodeRegistry.js";
1
+ import { getNodeDef, registerNode } from "./nodeRegistry.js";
2
2
  import { textNodeDef } from "./definitions/text.js";
3
3
  import { olNodeDef, ulNodeDef } from "./definitions/list.js";
4
4
  import { imageNodeDef } from "./definitions/image.js";
@@ -34,6 +34,6 @@ registerNode(hstackNodeDef);
34
34
  registerNode(layerNodeDef);
35
35
  registerNode(svgNodeDef);
36
36
  //#endregion
37
- export {};
37
+ export { getNodeDef };
38
38
 
39
39
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"xmlChildRules.js","names":[],"sources":["../../src/registry/xmlChildRules.ts"],"sourcesContent":["/**\n * parseXml / serializeXml が共有する XML child handling のルール定義。\n *\n * - インライン装飾タグ (B/I/A/U/S/Sub/Sup/Mark/Span) と TextRun property の対応\n * - ノードごとの XML child element 受け入れルール (XmlChildRule)\n *\n * parse 側 (parseXml.ts) と serialize 側 (serializeXml.ts) の双方がこの\n * モジュールを参照することで、タグ名・対応 property・許容 child のズレを防ぐ。\n * 依存方向は parseXml / serializeXml → registry の一方向に保つ(このモジュール\n * からは parseXml / serializeXml を import しない)。\n */\n\n// ===== インライン装飾 (B/I/A/U/S/Sub/Sup/Mark/Span) =====\n\n/** Text / Shape / Li / Td 内のインライン装飾を表すテキスト run */\nexport interface TextRun {\n text: string;\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n strike?: boolean;\n subscript?: boolean;\n superscript?: boolean;\n highlight?: string;\n color?: string;\n href?: string;\n fontFamily?: string;\n fontSize?: number;\n letterSpacing?: number;\n}\n\n/**\n * boolean 装飾タグ ↔ TextRun property の対応。\n * 配列の順序は serialize 時のネスト順(先頭が最も外側)。\n */\nexport const INLINE_BOOLEAN_FORMATS: readonly {\n readonly tag: string;\n readonly property:\n | \"bold\"\n | \"italic\"\n | \"underline\"\n | \"strike\"\n | \"subscript\"\n | \"superscript\";\n}[] = [\n { tag: \"B\", property: \"bold\" },\n { tag: \"I\", property: \"italic\" },\n { tag: \"U\", property: \"underline\" },\n { tag: \"S\", property: \"strike\" },\n { tag: \"Sub\", property: \"subscript\" },\n { tag: \"Sup\", property: \"superscript\" },\n];\n\n/** ハイパーリンクタグ。href 属性 → TextRun.href */\nexport const INLINE_LINK_TAG = \"A\";\n\n/** ハイライトタグ。color 属性 → TextRun.highlight(省略時は既定色) */\nexport const INLINE_MARK_TAG = \"Mark\";\n\n/** Mark の color 属性省略時に適用される既定のハイライト色 */\nexport const MARK_DEFAULT_HIGHLIGHT_COLOR = \"FFFF00\";\n\n/** 文字スタイルタグ。color / fontFamily / fontSize / letterSpacing 属性 → TextRun の同名 property */\nexport const INLINE_SPAN_TAG = \"Span\";\n\n/** インライン装飾として許容されるタグの一覧(エラーメッセージの列挙順) */\nexport const INLINE_FORMAT_TAG_LIST: readonly string[] = [\n \"B\",\n \"I\",\n \"A\",\n \"U\",\n \"S\",\n \"Sub\",\n \"Sup\",\n \"Mark\",\n \"Span\",\n];\n\n/** インライン装飾タグの集合(child element 判定用) */\nexport const INLINE_FORMAT_TAGS: ReadonlySet<string> = new Set(\n INLINE_FORMAT_TAG_LIST,\n);\n\n// ===== XML child element 受け入れルール =====\n\n/**\n * インライン装飾タグ (B/I/A/U/S/Mark/Span) のみを child として受け付け、\n * runs / text property へ変換するルール(Text 用)\n */\nexport interface InlineRunsChildRule {\n kind: \"inline-runs\";\n}\n\n/** 単一種類の child tag の繰り返しを受け取り、配列 property へ変換するルール */\nexport interface RepeatedChildRule {\n kind: \"repeated\";\n /** 受け付ける child tag 名 */\n childTag: string;\n /** 変換結果を格納する property 名 */\n property: string;\n /** 各 item が text content / インライン装飾タグを受け付けるか(Li 用) */\n allowsItemText?: boolean;\n}\n\n/**\n * parseXml 側の専用 converter で処理する複雑な child 構造\n * (ネスト構造や複数 property への振り分けを伴うもの)\n */\nexport interface NodeSpecificChildRule {\n kind: \"node-specific\";\n /** 直下で受け付ける child tag 名の一覧(unknown child エラーメッセージに使用) */\n expectedTags: readonly string[];\n}\n\nexport type XmlChildRule =\n | InlineRunsChildRule\n | RepeatedChildRule\n | NodeSpecificChildRule;\n\n/**\n * unknown child エラーメッセージ用に期待タグ一覧を整形する。\n * 例: [\"A\"] → \"<A>\" / [\"A\",\"B\"] → \"<A> or <B>\" / [\"A\",\"B\",\"C\"] → \"<A>, <B>, or <C>\"\n */\nexport function formatExpectedTags(tags: readonly string[]): string {\n const wrapped = tags.map((tag) => `<${tag}>`);\n if (wrapped.length === 1) return wrapped[0];\n if (wrapped.length === 2) return `${wrapped[0]} or ${wrapped[1]}`;\n return `${wrapped.slice(0, -1).join(\", \")}, or ${wrapped[wrapped.length - 1]}`;\n}\n"],"mappings":";;;;;AAmCA,MAAa,yBASP;CACJ;EAAE,KAAK;EAAK,UAAU;CAAO;CAC7B;EAAE,KAAK;EAAK,UAAU;CAAS;CAC/B;EAAE,KAAK;EAAK,UAAU;CAAY;CAClC;EAAE,KAAK;EAAK,UAAU;CAAS;CAC/B;EAAE,KAAK;EAAO,UAAU;CAAY;CACpC;EAAE,KAAK;EAAO,UAAU;CAAc;AACxC;;AAMA,MAAa,kBAAkB;;AAG/B,MAAa,+BAA+B;;AAG5C,MAAa,kBAAkB;;AAG/B,MAAa,yBAA4C;CACvD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAa,qBAA0C,IAAI,IACzD,sBACF;;;;;AA0CA,SAAgB,mBAAmB,MAAiC;CAClE,MAAM,UAAU,KAAK,KAAK,QAAQ,IAAI,IAAI,EAAE;CAC5C,IAAI,QAAQ,WAAW,GAAG,OAAO,QAAQ;CACzC,IAAI,QAAQ,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,QAAQ;CAC7D,OAAO,GAAG,QAAQ,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,OAAO,QAAQ,QAAQ,SAAS;AAC5E"}
1
+ {"version":3,"file":"xmlChildRules.js","names":[],"sources":["../../src/registry/xmlChildRules.ts"],"sourcesContent":["/**\n * parseXml / serializeXml が共有する XML child handling のルール定義。\n *\n * - インライン装飾タグ (B/I/A/U/S/Sub/Sup/Mark/Span) と TextRun property の対応\n * - ノードごとの XML child element 受け入れルール (XmlChildRule)\n *\n * parse 側 (parseXml.ts) と serialize 側 (serializeXml.ts) の双方がこの\n * モジュールを参照することで、タグ名・対応 property・許容 child のズレを防ぐ。\n * 依存方向は parseXml / serializeXml → registry の一方向に保つ(このモジュール\n * からは parseXml / serializeXml を import しない)。\n */\n\n// ===== インライン装飾 (B/I/A/U/S/Sub/Sup/Mark/Span) =====\n\n/** Text / Shape / Li / Td 内のインライン装飾を表すテキスト run */\nexport interface TextRun {\n text: string;\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n strike?: boolean;\n subscript?: boolean;\n superscript?: boolean;\n highlight?: string;\n color?: string;\n href?: string;\n fontFamily?: string;\n fontSize?: number;\n letterSpacing?: number;\n}\n\n/**\n * boolean 装飾タグ ↔ TextRun property の対応。\n * 配列の順序は serialize 時のネスト順(先頭が最も外側)。\n */\nexport const INLINE_BOOLEAN_FORMATS: readonly {\n readonly tag: string;\n readonly property:\n \"bold\" | \"italic\" | \"underline\" | \"strike\" | \"subscript\" | \"superscript\";\n}[] = [\n { tag: \"B\", property: \"bold\" },\n { tag: \"I\", property: \"italic\" },\n { tag: \"U\", property: \"underline\" },\n { tag: \"S\", property: \"strike\" },\n { tag: \"Sub\", property: \"subscript\" },\n { tag: \"Sup\", property: \"superscript\" },\n];\n\n/** ハイパーリンクタグ。href 属性 → TextRun.href */\nexport const INLINE_LINK_TAG = \"A\";\n\n/** ハイライトタグ。color 属性 → TextRun.highlight(省略時は既定色) */\nexport const INLINE_MARK_TAG = \"Mark\";\n\n/** Mark の color 属性省略時に適用される既定のハイライト色 */\nexport const MARK_DEFAULT_HIGHLIGHT_COLOR = \"FFFF00\";\n\n/** 文字スタイルタグ。color / fontFamily / fontSize / letterSpacing 属性 → TextRun の同名 property */\nexport const INLINE_SPAN_TAG = \"Span\";\n\n/** インライン装飾として許容されるタグの一覧(エラーメッセージの列挙順) */\nexport const INLINE_FORMAT_TAG_LIST: readonly string[] = [\n \"B\",\n \"I\",\n \"A\",\n \"U\",\n \"S\",\n \"Sub\",\n \"Sup\",\n \"Mark\",\n \"Span\",\n];\n\n/** インライン装飾タグの集合(child element 判定用) */\nexport const INLINE_FORMAT_TAGS: ReadonlySet<string> = new Set(\n INLINE_FORMAT_TAG_LIST,\n);\n\n// ===== XML child element 受け入れルール =====\n\n/**\n * インライン装飾タグ (B/I/A/U/S/Mark/Span) のみを child として受け付け、\n * runs / text property へ変換するルール(Text 用)\n */\nexport interface InlineRunsChildRule {\n kind: \"inline-runs\";\n}\n\n/** 単一種類の child tag の繰り返しを受け取り、配列 property へ変換するルール */\nexport interface RepeatedChildRule {\n kind: \"repeated\";\n /** 受け付ける child tag 名 */\n childTag: string;\n /** 変換結果を格納する property 名 */\n property: string;\n /** 各 item が text content / インライン装飾タグを受け付けるか(Li 用) */\n allowsItemText?: boolean;\n}\n\n/**\n * parseXml 側の専用 converter で処理する複雑な child 構造\n * (ネスト構造や複数 property への振り分けを伴うもの)\n */\nexport interface NodeSpecificChildRule {\n kind: \"node-specific\";\n /** 直下で受け付ける child tag 名の一覧(unknown child エラーメッセージに使用) */\n expectedTags: readonly string[];\n}\n\nexport type XmlChildRule =\n InlineRunsChildRule | RepeatedChildRule | NodeSpecificChildRule;\n\n/**\n * unknown child エラーメッセージ用に期待タグ一覧を整形する。\n * 例: [\"A\"] → \"<A>\" / [\"A\",\"B\"] → \"<A> or <B>\" / [\"A\",\"B\",\"C\"] → \"<A>, <B>, or <C>\"\n */\nexport function formatExpectedTags(tags: readonly string[]): string {\n const wrapped = tags.map((tag) => `<${tag}>`);\n if (wrapped.length === 1) return wrapped[0];\n if (wrapped.length === 2) return `${wrapped[0]} or ${wrapped[1]}`;\n return `${wrapped.slice(0, -1).join(\", \")}, or ${wrapped[wrapped.length - 1]}`;\n}\n"],"mappings":";;;;;AAmCA,MAAa,yBAIP;CACJ;EAAE,KAAK;EAAK,UAAU;CAAO;CAC7B;EAAE,KAAK;EAAK,UAAU;CAAS;CAC/B;EAAE,KAAK;EAAK,UAAU;CAAY;CAClC;EAAE,KAAK;EAAK,UAAU;CAAS;CAC/B;EAAE,KAAK;EAAO,UAAU;CAAY;CACpC;EAAE,KAAK;EAAO,UAAU;CAAc;AACxC;;AAMA,MAAa,kBAAkB;;AAG/B,MAAa,+BAA+B;;AAG5C,MAAa,kBAAkB;;AAG/B,MAAa,yBAA4C;CACvD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAa,qBAA0C,IAAI,IACzD,sBACF;;;;;AAwCA,SAAgB,mBAAmB,MAAiC;CAClE,MAAM,UAAU,KAAK,KAAK,QAAQ,IAAI,IAAI,EAAE;CAC5C,IAAI,QAAQ,WAAW,GAAG,OAAO,QAAQ;CACzC,IAAI,QAAQ,WAAW,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,QAAQ;CAC7D,OAAO,GAAG,QAAQ,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK,IAAI,EAAE,OAAO,QAAQ,QAAQ,SAAS;AAC5E"}
@@ -0,0 +1,347 @@
1
+ import { EMU_PER_IN, pxToEmu, pxToPt } from "./units.js";
2
+ import { createTextOptions, resolveSubSup } from "./textOptions.js";
3
+ import { parseLinearGradient } from "../shared/gradient.js";
4
+ import { addTextBox, asEmu, asHundredthPt, asOoxmlAngle, asOoxmlPercent, asPt, createPptx } from "@pptx-glimpse/document";
5
+ //#region src/renderPptx/glimpseTextBoxes.ts
6
+ /**
7
+ * Text primitive の @pptx-glimpse/document writer への段階的 swap。
8
+ *
9
+ * 混在期間の合成方式は「pptxgenjs 出力 zip をベースに、swap 済み Text
10
+ * primitive の shape XML だけを glimpse writer で生成して該当 marker shape と
11
+ * 差し替える」方式を採用する。pptxgenjs 側にはまだ Shape / Image / Table /
12
+ * Chart など未 swap primitive と slide master 生成が残っているため、既存 zip を
13
+ * ベースにすると [Content_Types].xml / rels / media parts の管理を現行実装へ
14
+ * 寄せられる。代替案として glimpse の package をベースに未 swap primitive を
15
+ * pptxgenjs から取り込む方式も検討したが、初回スライス時点では pptxgenjs 側の
16
+ * 非 text primitive と master 出力を XML part 単位で切り出す責務が増え、以降の
17
+ * primitive swap より先に package 合成の複雑さが大きくなるため採用しない。
18
+ *
19
+ * marker shape は描画順を保持するためだけに pptxgenjs へ追加し、write 時に
20
+ * glimpse の `<p:sp>` で丸ごと置換する。Text content 自体は pptxgenjs `addText`
21
+ * を経由しない。
22
+ */
23
+ const MARKER_PREFIX = "pom-text:";
24
+ const HYPERLINK_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
25
+ async function loadJSZip() {
26
+ const mod = await import("jszip");
27
+ return mod.default ?? mod;
28
+ }
29
+ function cleanHex(color) {
30
+ return color?.replace(/^#/, "").toUpperCase();
31
+ }
32
+ function toColorInput(color) {
33
+ const hex = cleanHex(color);
34
+ return hex ? {
35
+ kind: "srgb",
36
+ hex
37
+ } : void 0;
38
+ }
39
+ function toUnderlineInput(underline) {
40
+ if (underline === void 0 || underline === false) return void 0;
41
+ if (underline === true) return true;
42
+ return {
43
+ style: underline.style,
44
+ color: toColorInput(underline.color)
45
+ };
46
+ }
47
+ function toBaselineInput(subscript, superscript) {
48
+ if (subscript) return "subscript";
49
+ if (superscript) return "superscript";
50
+ }
51
+ function toGlowInput(glow) {
52
+ if (!glow) return void 0;
53
+ return {
54
+ radius: asEmu(Math.round(pxToEmu(glow.size ?? 8))),
55
+ color: toColorInput(glow.color ?? "FFFFFF")
56
+ };
57
+ }
58
+ function toOutlineInput(outline) {
59
+ if (!outline) return void 0;
60
+ return {
61
+ width: asEmu(Math.round(pxToEmu(outline.size ?? 1))),
62
+ color: toColorInput(outline.color ?? "FFFFFF")
63
+ };
64
+ }
65
+ function toCharSpacing(letterSpacingPx) {
66
+ if (letterSpacingPx === void 0) return void 0;
67
+ return Math.round(pxToPt(letterSpacingPx) * 100);
68
+ }
69
+ function toTextGradientInput(value) {
70
+ if (!value) return void 0;
71
+ const linear = parseLinearGradient(value);
72
+ if (!linear) return void 0;
73
+ const dmlAngle = ((linear.angle - 90) % 360 + 360) % 360;
74
+ return {
75
+ angle: asOoxmlAngle(Math.round(dmlAngle * 6e4)),
76
+ stops: linear.stops.map((stop) => ({
77
+ position: asOoxmlPercent(Math.round(stop.position * 1e3)),
78
+ color: toColorInput(stop.color)
79
+ }))
80
+ };
81
+ }
82
+ function stripUndefined(value) {
83
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
84
+ }
85
+ function buildRunProperties(node, run, gradientFill) {
86
+ const fontSizePx = run?.fontSize ?? node.fontSize ?? 24;
87
+ const letterSpacingPx = run?.letterSpacing ?? node.letterSpacing;
88
+ const subSup = run ? resolveSubSup(run, node) : node;
89
+ const color = gradientFill ? void 0 : toColorInput(run?.color ?? node.color);
90
+ return stripUndefined({
91
+ fontFace: run?.fontFamily ?? node.fontFamily ?? "Noto Sans JP",
92
+ fontSize: asPt(pxToPt(fontSizePx)),
93
+ color,
94
+ gradientFill,
95
+ bold: run?.bold ?? node.bold,
96
+ italic: run?.italic ?? node.italic,
97
+ underline: toUnderlineInput(resolveUnderline(node, run)),
98
+ strike: run?.strike ?? node.strike,
99
+ baseline: toBaselineInput(subSup.subscript, subSup.superscript),
100
+ highlight: toColorInput(run?.highlight ?? node.highlight),
101
+ glow: toGlowInput(node.glow),
102
+ outline: toOutlineInput(node.outline),
103
+ charSpacing: toCharSpacing(letterSpacingPx)
104
+ });
105
+ }
106
+ function resolveUnderline(node, run) {
107
+ if (run?.underline !== void 0) return run.underline;
108
+ if (node.underline !== void 0) return node.underline;
109
+ return run?.href ? true : void 0;
110
+ }
111
+ function createParagraphProperties(node) {
112
+ const lineHeight = node.lineHeight ?? 1.3;
113
+ const fontSizePx = node.fontSize ?? 24;
114
+ return stripUndefined({
115
+ align: node.textAlign,
116
+ lineSpacing: asHundredthPt(Math.round(pxToPt(fontSizePx * lineHeight) * 100))
117
+ });
118
+ }
119
+ function buildParagraphs(node) {
120
+ const gradientFill = toTextGradientInput(node.textGradient);
121
+ const sourceRuns = node.runs && node.runs.length > 0 ? node.runs.map((run) => ({
122
+ text: run.text,
123
+ properties: buildRunProperties(node, run, gradientFill),
124
+ href: run.href
125
+ })) : [{
126
+ text: node.text ?? "",
127
+ properties: buildRunProperties(node, void 0, gradientFill)
128
+ }];
129
+ const paragraphRuns = [[]];
130
+ for (const run of sourceRuns) run.text.replace(/\r*\n/g, "\n").split("\n").forEach((line, index) => {
131
+ if (index > 0) paragraphRuns.push([]);
132
+ paragraphRuns[paragraphRuns.length - 1]?.push({
133
+ ...run,
134
+ text: line
135
+ });
136
+ });
137
+ return {
138
+ paragraphs: paragraphRuns.map((runs) => ({
139
+ properties: createParagraphProperties(node),
140
+ runs
141
+ })),
142
+ hyperlinks: paragraphRuns.flatMap((runs) => runs.map((run) => run.text ? run.href : void 0))
143
+ };
144
+ }
145
+ function withGlowAlpha(xml, node) {
146
+ const glow = node.glow;
147
+ if (!glow) return xml;
148
+ const alpha = Math.round((glow.opacity ?? .75) * 1e5);
149
+ const color = cleanHex(glow.color ?? "FFFFFF");
150
+ const target = `<a:glow rad="${Math.round(pxToEmu(glow.size ?? 8))}"><a:srgbClr val="${color}"/></a:glow>`;
151
+ const replacement = `<a:glow rad="${Math.round(pxToEmu(glow.size ?? 8))}"><a:srgbClr val="${color}"><a:alpha val="${alpha}"/></a:srgbClr></a:glow>`;
152
+ return xml.replaceAll(target, replacement);
153
+ }
154
+ function withPptxGenParagraphDefaults(xml) {
155
+ let result = xml.replaceAll("baseline=\"-25000\"", "baseline=\"-40000\"");
156
+ result = result.replace(/<a:bodyPr\b([^>]*)\/>/g, (_match, attrs) => {
157
+ const attrText = attrs;
158
+ const withRtl = /(?:^|\s)rtlCol=/.test(attrText) ? attrText : `${attrText} rtlCol="0"`;
159
+ return `<a:bodyPr${/(?:^|\s)anchor=/.test(withRtl) ? withRtl : `${withRtl} anchor="t"`}/>`;
160
+ });
161
+ result = result.replace(/<a:pPr([^>]*)>([\s\S]*?)<\/a:pPr>/g, (match, attrs, body) => {
162
+ const attrText = attrs;
163
+ const nextAttrs = /(?:^|\s)indent=/.test(attrText) ? attrText : `${attrText} indent="0" marL="0"`;
164
+ const bodyText = body;
165
+ return `<a:pPr${nextAttrs}>${bodyText.includes("<a:buNone/>") ? bodyText : `${bodyText}<a:buNone/>`}</a:pPr>`;
166
+ });
167
+ result = result.replace(/<a:p>(?!<a:pPr)/g, "<a:p><a:pPr indent=\"0\" marL=\"0\"><a:buNone/></a:pPr>");
168
+ return result;
169
+ }
170
+ function createTextBoxXml(node, name) {
171
+ const source = createPptx();
172
+ const slideHandle = source.slides[0]?.handle;
173
+ if (!slideHandle) throw new Error("createPptx did not create an editable slide");
174
+ const textOptions = createTextOptions(node);
175
+ const { paragraphs, hyperlinks } = buildParagraphs(node);
176
+ const edit = addTextBox(source, slideHandle, {
177
+ offsetX: asEmu(Math.round(textOptions.x * EMU_PER_IN)),
178
+ offsetY: asEmu(Math.round(textOptions.y * EMU_PER_IN)),
179
+ width: asEmu(Math.round(textOptions.w * EMU_PER_IN)),
180
+ height: asEmu(Math.round(textOptions.h * EMU_PER_IN)),
181
+ rotation: node.rotate !== void 0 ? asOoxmlAngle(Math.round(node.rotate * 6e4)) : void 0,
182
+ name,
183
+ body: {
184
+ marginLeft: asEmu(0),
185
+ marginRight: asEmu(0),
186
+ marginTop: asEmu(0),
187
+ marginBottom: asEmu(0)
188
+ },
189
+ paragraphs
190
+ }).edits?.at(-1);
191
+ if (edit?.kind !== "addTextBox") throw new Error("addTextBox did not produce an addTextBox edit");
192
+ return {
193
+ xml: withPptxGenParagraphDefaults(withGlowAlpha(edit.xml, node)),
194
+ hyperlinks
195
+ };
196
+ }
197
+ var GlimpseTextBoxRegistry = class {
198
+ registered = [];
199
+ register(node) {
200
+ const index = this.registered.length;
201
+ const marker = `${MARKER_PREFIX}${index}`;
202
+ const name = `Text ${index + 1}`;
203
+ const { xml, hyperlinks } = createTextBoxXml(node, name);
204
+ this.registered.push({
205
+ marker,
206
+ name,
207
+ xml,
208
+ hyperlinks
209
+ });
210
+ return marker;
211
+ }
212
+ get isEmpty() {
213
+ return this.registered.length === 0;
214
+ }
215
+ get entries() {
216
+ return this.registered;
217
+ }
218
+ };
219
+ function escapeRegExp(value) {
220
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
221
+ }
222
+ function replaceShapeId(xml, id, name) {
223
+ return xml.replace(/<p:cNvPr id="[^"]+" name="[^"]*"/, `<p:cNvPr id="${id}" name="${name}"`);
224
+ }
225
+ function xmlAttr(value) {
226
+ return value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
227
+ }
228
+ function slideRelsPath(slidePath) {
229
+ return `ppt/slides/_rels/${slidePath.split("/").at(-1)}.rels`;
230
+ }
231
+ var SlideRelationshipEditor = class {
232
+ xml;
233
+ nextId;
234
+ changed = false;
235
+ constructor(xml) {
236
+ this.xml = xml;
237
+ const ids = Array.from(xml.matchAll(/\bId="rId(\d+)"/g), (match) => Number(match[1]));
238
+ this.nextId = Math.max(0, ...ids) + 1;
239
+ }
240
+ addHyperlink(href) {
241
+ const id = `rId${this.nextId++}`;
242
+ const rel = `<Relationship Id="${id}" Type="${HYPERLINK_REL_TYPE}" Target="${xmlAttr(href)}" TargetMode="External"/>`;
243
+ this.xml = this.xml.replace("</Relationships>", `${rel}</Relationships>`);
244
+ this.changed = true;
245
+ return id;
246
+ }
247
+ get result() {
248
+ return {
249
+ xml: this.xml,
250
+ changed: this.changed
251
+ };
252
+ }
253
+ };
254
+ function createRelationshipEditor(xml) {
255
+ return new SlideRelationshipEditor(xml ?? "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"></Relationships>");
256
+ }
257
+ function withHyperlinkRelationships(xml, hyperlinks, addRelationship) {
258
+ if (!hyperlinks.some(Boolean)) return xml;
259
+ let index = 0;
260
+ return xml.replace(/<a:rPr\b([^>]*)>([\s\S]*?)<\/a:rPr>/g, (match, attrs, body) => {
261
+ const href = hyperlinks[index++];
262
+ if (!href) return match;
263
+ return `<a:rPr${attrs}>${body}<a:hlinkClick r:id="${addRelationship(href)}"/></a:rPr>`;
264
+ });
265
+ }
266
+ function applyGlimpseTextBoxesToXml(xml, registry, addRelationship) {
267
+ let result = xml;
268
+ for (const entry of registry.entries) {
269
+ const re = new RegExp(`<p:sp><p:nvSpPr><p:cNvPr id="([^"]+)" name="${escapeRegExp(entry.marker)}"[\\s\\S]*?</p:sp>`, "g");
270
+ result = result.replace(re, (_match, id) => {
271
+ return withHyperlinkRelationships(replaceShapeId(entry.xml, id, entry.name), entry.hyperlinks, addRelationship);
272
+ });
273
+ }
274
+ return result;
275
+ }
276
+ async function applyGlimpseTextBoxes(data, registry) {
277
+ const zip = await (await loadJSZip()).loadAsync(data);
278
+ const slidePaths = Object.keys(zip.files).filter((path) => /^ppt\/slides\/slide\d+\.xml$/.test(path));
279
+ for (const path of slidePaths) {
280
+ const file = zip.file(path);
281
+ if (!file) continue;
282
+ const original = await file.async("text");
283
+ const relsPath = slideRelsPath(path);
284
+ const relsFile = zip.file(relsPath);
285
+ const relationshipEditor = createRelationshipEditor(relsFile ? await relsFile.async("text") : void 0);
286
+ const replaced = applyGlimpseTextBoxesToXml(original, registry, (href) => relationshipEditor.addHyperlink(href));
287
+ if (replaced !== original) zip.file(path, replaced);
288
+ const relationships = relationshipEditor.result;
289
+ if (relationships.changed) zip.file(relsPath, relationships.xml);
290
+ }
291
+ return zip;
292
+ }
293
+ function patchPptxWriteForGlimpseTextBoxes(pptx, registry) {
294
+ if (registry.isEmpty) return;
295
+ const originalWrite = pptx.write.bind(pptx);
296
+ const patchedWrite = async (rawProps) => {
297
+ const props = typeof rawProps === "string" ? { outputType: rawProps } : rawProps;
298
+ const zip = await applyGlimpseTextBoxes(await originalWrite({ outputType: "uint8array" }), registry);
299
+ const outputType = props?.outputType;
300
+ if (outputType === "STREAM") return zip.generateAsync({
301
+ type: "nodebuffer",
302
+ compression: props?.compression ? "DEFLATE" : "STORE"
303
+ });
304
+ if (outputType) return zip.generateAsync({
305
+ type: outputType,
306
+ compression: props?.compression ? "DEFLATE" : "STORE"
307
+ });
308
+ return zip.generateAsync({
309
+ type: "blob",
310
+ compression: props?.compression ? "DEFLATE" : "STORE"
311
+ });
312
+ };
313
+ pptx.write = patchedWrite;
314
+ const patchedStream = async (props) => pptx.write({
315
+ outputType: "STREAM",
316
+ compression: props?.compression
317
+ });
318
+ pptx.stream = patchedStream;
319
+ const patchedWriteFile = async (rawProps) => {
320
+ const props = typeof rawProps === "string" ? { fileName: rawProps } : rawProps;
321
+ if (!(typeof process !== "undefined" && Boolean(process.versions?.node))) {
322
+ const browserWriter = pptx;
323
+ if (typeof browserWriter.writeFileToBrowser !== "function") throw new Error("pptx.writeFile browser download helper is unavailable; use pptx.write({ outputType: 'blob' }) instead");
324
+ const rawName = props?.fileName ?? "Presentation.pptx";
325
+ const fileName = rawName.toLowerCase().endsWith(".pptx") ? rawName : `${rawName}.pptx`;
326
+ const blob = await patchedWrite({
327
+ outputType: "blob",
328
+ compression: props?.compression
329
+ });
330
+ await browserWriter.writeFileToBrowser(fileName, blob);
331
+ return fileName;
332
+ }
333
+ const rawName = props?.fileName ?? "Presentation.pptx";
334
+ const fileName = rawName.toLowerCase().endsWith(".pptx") ? rawName : `${rawName}.pptx`;
335
+ const buffer = await patchedWrite({
336
+ outputType: "nodebuffer",
337
+ compression: props?.compression
338
+ });
339
+ await (await import("fs")).promises.writeFile(fileName, buffer);
340
+ return fileName;
341
+ };
342
+ pptx.writeFile = patchedWriteFile;
343
+ }
344
+ //#endregion
345
+ export { GlimpseTextBoxRegistry, patchPptxWriteForGlimpseTextBoxes };
346
+
347
+ //# sourceMappingURL=glimpseTextBoxes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glimpseTextBoxes.js","names":[],"sources":["../../src/renderPptx/glimpseTextBoxes.ts"],"sourcesContent":["/**\n * Text primitive の @pptx-glimpse/document writer への段階的 swap。\n *\n * 混在期間の合成方式は「pptxgenjs 出力 zip をベースに、swap 済み Text\n * primitive の shape XML だけを glimpse writer で生成して該当 marker shape と\n * 差し替える」方式を採用する。pptxgenjs 側にはまだ Shape / Image / Table /\n * Chart など未 swap primitive と slide master 生成が残っているため、既存 zip を\n * ベースにすると [Content_Types].xml / rels / media parts の管理を現行実装へ\n * 寄せられる。代替案として glimpse の package をベースに未 swap primitive を\n * pptxgenjs から取り込む方式も検討したが、初回スライス時点では pptxgenjs 側の\n * 非 text primitive と master 出力を XML part 単位で切り出す責務が増え、以降の\n * primitive swap より先に package 合成の複雑さが大きくなるため採用しない。\n *\n * marker shape は描画順を保持するためだけに pptxgenjs へ追加し、write 時に\n * glimpse の `<p:sp>` で丸ごと置換する。Text content 自体は pptxgenjs `addText`\n * を経由しない。\n */\nimport {\n addTextBox,\n asEmu,\n asHundredthPt,\n asOoxmlAngle,\n asOoxmlPercent,\n asPt,\n createPptx,\n type AddTextBoxGradientFillInput,\n type AddTextBoxInput,\n type AddTextBoxParagraphInput,\n type AddTextBoxRunPropertiesInput,\n type PptxSourceModelAddTextBoxEdit,\n} from \"@pptx-glimpse/document\";\nimport type {\n PositionedNode,\n TextGlow,\n TextOutline,\n Underline,\n} from \"../types.ts\";\nimport { parseLinearGradient } from \"../shared/gradient.ts\";\nimport { EMU_PER_IN, pxToEmu, pxToPt } from \"./units.ts\";\nimport { createTextOptions, resolveSubSup } from \"./textOptions.ts\";\n\ntype PptxGenJSInstance = import(\"pptxgenjs\").default;\ntype StreamProps = NonNullable<Parameters<PptxGenJSInstance[\"stream\"]>[0]>;\ntype WriteProps = NonNullable<Parameters<PptxGenJSInstance[\"write\"]>[0]>;\ntype WriteFileProps = NonNullable<\n Parameters<PptxGenJSInstance[\"writeFile\"]>[0]\n>;\ntype BrowserWritablePptx = PptxGenJSInstance & {\n writeFileToBrowser?: (fileName: string, blobContent: Blob) => Promise<string>;\n};\ntype TextPositionedNode = Extract<PositionedNode, { type: \"text\" }>;\n\nconst MARKER_PREFIX = \"pom-text:\";\nconst HYPERLINK_REL_TYPE =\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink\";\n\ninterface GlimpseTextRun {\n text: string;\n properties: AddTextBoxRunPropertiesInput;\n href?: string;\n}\n\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\nfunction cleanHex(color: string | undefined): string | undefined {\n return color?.replace(/^#/, \"\").toUpperCase();\n}\n\nfunction toColorInput(color: string | undefined) {\n const hex = cleanHex(color);\n return hex ? { kind: \"srgb\" as const, hex } : undefined;\n}\n\nfunction toUnderlineInput(underline: Underline | undefined) {\n if (underline === undefined || underline === false) return undefined;\n if (underline === true) return true;\n return {\n style: underline.style,\n color: toColorInput(underline.color),\n };\n}\n\nfunction toBaselineInput(\n subscript: boolean | undefined,\n superscript: boolean | undefined,\n) {\n if (subscript) return \"subscript\" as const;\n if (superscript) return \"superscript\" as const;\n return undefined;\n}\n\nfunction toGlowInput(glow: TextGlow | undefined) {\n if (!glow) return undefined;\n return {\n radius: asEmu(Math.round(pxToEmu(glow.size ?? 8))),\n color: toColorInput(glow.color ?? \"FFFFFF\")!,\n };\n}\n\nfunction toOutlineInput(outline: TextOutline | undefined) {\n if (!outline) return undefined;\n return {\n width: asEmu(Math.round(pxToEmu(outline.size ?? 1))),\n color: toColorInput(outline.color ?? \"FFFFFF\"),\n };\n}\n\nfunction toCharSpacing(letterSpacingPx: number | undefined) {\n if (letterSpacingPx === undefined) return undefined;\n return Math.round(pxToPt(letterSpacingPx) * 100);\n}\n\nfunction toTextGradientInput(\n value: string | undefined,\n): AddTextBoxGradientFillInput | undefined {\n if (!value) return undefined;\n const linear = parseLinearGradient(value);\n if (!linear) return undefined;\n const dmlAngle = (((linear.angle - 90) % 360) + 360) % 360;\n return {\n angle: asOoxmlAngle(Math.round(dmlAngle * 60000)),\n stops: linear.stops.map((stop) => ({\n position: asOoxmlPercent(Math.round(stop.position * 1000)),\n color: toColorInput(stop.color)!,\n })),\n };\n}\n\nfunction stripUndefined<T extends Record<string, unknown>>(value: T): T {\n return Object.fromEntries(\n Object.entries(value).filter(([, entry]) => entry !== undefined),\n ) as T;\n}\n\nfunction buildRunProperties(\n node: TextPositionedNode,\n run: NonNullable<TextPositionedNode[\"runs\"]>[number] | undefined,\n gradientFill: AddTextBoxGradientFillInput | undefined,\n): AddTextBoxRunPropertiesInput {\n const fontSizePx = run?.fontSize ?? node.fontSize ?? 24;\n const letterSpacingPx = run?.letterSpacing ?? node.letterSpacing;\n const subSup = run ? resolveSubSup(run, node) : node;\n const color = gradientFill\n ? undefined\n : toColorInput(run?.color ?? node.color);\n\n return stripUndefined({\n fontFace: run?.fontFamily ?? node.fontFamily ?? \"Noto Sans JP\",\n fontSize: asPt(pxToPt(fontSizePx)),\n color,\n gradientFill,\n bold: run?.bold ?? node.bold,\n italic: run?.italic ?? node.italic,\n underline: toUnderlineInput(resolveUnderline(node, run)),\n strike: run?.strike ?? node.strike,\n baseline: toBaselineInput(subSup.subscript, subSup.superscript),\n highlight: toColorInput(run?.highlight ?? node.highlight),\n glow: toGlowInput(node.glow),\n outline: toOutlineInput(node.outline),\n charSpacing: toCharSpacing(letterSpacingPx),\n });\n}\n\nfunction resolveUnderline(\n node: TextPositionedNode,\n run: NonNullable<TextPositionedNode[\"runs\"]>[number] | undefined,\n): Underline | undefined {\n if (run?.underline !== undefined) return run.underline;\n if (node.underline !== undefined) return node.underline;\n return run?.href ? true : undefined;\n}\n\nfunction createParagraphProperties(\n node: TextPositionedNode,\n): AddTextBoxParagraphInput[\"properties\"] {\n const lineHeight = node.lineHeight ?? 1.3;\n const fontSizePx = node.fontSize ?? 24;\n return stripUndefined({\n align: node.textAlign,\n lineSpacing: asHundredthPt(\n Math.round(pxToPt(fontSizePx * lineHeight) * 100),\n ),\n });\n}\n\nfunction buildParagraphs(node: TextPositionedNode): {\n paragraphs: readonly AddTextBoxParagraphInput[];\n hyperlinks: readonly (string | undefined)[];\n} {\n const gradientFill = toTextGradientInput(node.textGradient);\n const sourceRuns: GlimpseTextRun[] =\n node.runs && node.runs.length > 0\n ? node.runs.map((run) => ({\n text: run.text,\n properties: buildRunProperties(node, run, gradientFill),\n href: run.href,\n }))\n : [\n {\n text: node.text ?? \"\",\n properties: buildRunProperties(node, undefined, gradientFill),\n },\n ];\n const paragraphRuns: GlimpseTextRun[][] = [[]];\n for (const run of sourceRuns) {\n const lines = run.text.replace(/\\r*\\n/g, \"\\n\").split(\"\\n\");\n lines.forEach((line, index) => {\n if (index > 0) {\n paragraphRuns.push([]);\n }\n paragraphRuns[paragraphRuns.length - 1]?.push({\n ...run,\n text: line,\n });\n });\n }\n\n return {\n paragraphs: paragraphRuns.map((runs) => ({\n properties: createParagraphProperties(node),\n runs,\n })),\n hyperlinks: paragraphRuns.flatMap((runs) =>\n runs.map((run) => (run.text ? run.href : undefined)),\n ),\n };\n}\n\nfunction withGlowAlpha(xml: string, node: TextPositionedNode): string {\n const glow = node.glow;\n if (!glow) return xml;\n const alpha = Math.round((glow.opacity ?? 0.75) * 100000);\n const color = cleanHex(glow.color ?? \"FFFFFF\");\n const target = `<a:glow rad=\"${Math.round(pxToEmu(glow.size ?? 8))}\"><a:srgbClr val=\"${color}\"/></a:glow>`;\n const replacement = `<a:glow rad=\"${Math.round(pxToEmu(glow.size ?? 8))}\"><a:srgbClr val=\"${color}\"><a:alpha val=\"${alpha}\"/></a:srgbClr></a:glow>`;\n return xml.replaceAll(target, replacement);\n}\n\nfunction withPptxGenParagraphDefaults(xml: string): string {\n let result = xml.replaceAll('baseline=\"-25000\"', 'baseline=\"-40000\"');\n result = result.replace(/<a:bodyPr\\b([^>]*)\\/>/g, (_match, attrs) => {\n const attrText = attrs as string;\n const withRtl = /(?:^|\\s)rtlCol=/.test(attrText)\n ? attrText\n : `${attrText} rtlCol=\"0\"`;\n const withAnchor = /(?:^|\\s)anchor=/.test(withRtl)\n ? withRtl\n : `${withRtl} anchor=\"t\"`;\n return `<a:bodyPr${withAnchor}/>`;\n });\n result = result.replace(\n /<a:pPr([^>]*)>([\\s\\S]*?)<\\/a:pPr>/g,\n (match, attrs, body) => {\n const attrText = attrs as string;\n const nextAttrs = /(?:^|\\s)indent=/.test(attrText)\n ? attrText\n : `${attrText} indent=\"0\" marL=\"0\"`;\n const bodyText = body as string;\n const nextBody = bodyText.includes(\"<a:buNone/>\")\n ? bodyText\n : `${bodyText}<a:buNone/>`;\n return `<a:pPr${nextAttrs}>${nextBody}</a:pPr>`;\n },\n );\n result = result.replace(\n /<a:p>(?!<a:pPr)/g,\n '<a:p><a:pPr indent=\"0\" marL=\"0\"><a:buNone/></a:pPr>',\n );\n return result;\n}\n\nfunction createTextBoxXml(\n node: TextPositionedNode,\n name: string,\n): { xml: string; hyperlinks: readonly (string | undefined)[] } {\n const source = createPptx();\n const slideHandle = source.slides[0]?.handle;\n if (!slideHandle) {\n throw new Error(\"createPptx did not create an editable slide\");\n }\n\n const textOptions = createTextOptions(node);\n const { paragraphs, hyperlinks } = buildParagraphs(node);\n const input: AddTextBoxInput = {\n offsetX: asEmu(Math.round(textOptions.x * EMU_PER_IN)),\n offsetY: asEmu(Math.round(textOptions.y * EMU_PER_IN)),\n width: asEmu(Math.round(textOptions.w * EMU_PER_IN)),\n height: asEmu(Math.round(textOptions.h * EMU_PER_IN)),\n rotation:\n node.rotate !== undefined\n ? asOoxmlAngle(Math.round(node.rotate * 60000))\n : undefined,\n name,\n body: {\n marginLeft: asEmu(0),\n marginRight: asEmu(0),\n marginTop: asEmu(0),\n marginBottom: asEmu(0),\n },\n paragraphs,\n };\n const edited = addTextBox(source, slideHandle, input);\n const edit = edited.edits?.at(-1) as\n PptxSourceModelAddTextBoxEdit | undefined;\n if (edit?.kind !== \"addTextBox\") {\n throw new Error(\"addTextBox did not produce an addTextBox edit\");\n }\n return {\n xml: withPptxGenParagraphDefaults(withGlowAlpha(edit.xml, node)),\n hyperlinks,\n };\n}\n\ninterface RegisteredTextBox {\n marker: string;\n name: string;\n xml: string;\n hyperlinks: readonly (string | undefined)[];\n}\n\nexport class GlimpseTextBoxRegistry {\n private readonly registered: RegisteredTextBox[] = [];\n\n register(node: TextPositionedNode): string {\n const index = this.registered.length;\n const marker = `${MARKER_PREFIX}${index}`;\n const name = `Text ${index + 1}`;\n const { xml, hyperlinks } = createTextBoxXml(node, name);\n this.registered.push({ marker, name, xml, hyperlinks });\n return marker;\n }\n\n get isEmpty(): boolean {\n return this.registered.length === 0;\n }\n\n get entries(): readonly RegisteredTextBox[] {\n return this.registered;\n }\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction replaceShapeId(xml: string, id: string, name: string): string {\n return xml.replace(\n /<p:cNvPr id=\"[^\"]+\" name=\"[^\"]*\"/,\n `<p:cNvPr id=\"${id}\" name=\"${name}\"`,\n );\n}\n\nfunction xmlAttr(value: string): string {\n return value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\");\n}\n\nfunction slideRelsPath(slidePath: string): string {\n const fileName = slidePath.split(\"/\").at(-1);\n return `ppt/slides/_rels/${fileName}.rels`;\n}\n\nclass SlideRelationshipEditor {\n private nextId: number;\n\n private changed = false;\n\n constructor(private xml: string) {\n const ids = Array.from(xml.matchAll(/\\bId=\"rId(\\d+)\"/g), (match) =>\n Number(match[1]),\n );\n this.nextId = Math.max(0, ...ids) + 1;\n }\n\n addHyperlink(href: string): string {\n const id = `rId${this.nextId++}`;\n const rel =\n `<Relationship Id=\"${id}\" Type=\"${HYPERLINK_REL_TYPE}\" ` +\n `Target=\"${xmlAttr(href)}\" TargetMode=\"External\"/>`;\n this.xml = this.xml.replace(\"</Relationships>\", `${rel}</Relationships>`);\n this.changed = true;\n return id;\n }\n\n get result(): { xml: string; changed: boolean } {\n return { xml: this.xml, changed: this.changed };\n }\n}\n\nfunction createRelationshipEditor(xml: string | undefined) {\n return new SlideRelationshipEditor(\n xml ??\n '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>' +\n '<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\"></Relationships>',\n );\n}\n\nfunction withHyperlinkRelationships(\n xml: string,\n hyperlinks: readonly (string | undefined)[],\n addRelationship: (href: string) => string,\n): string {\n if (!hyperlinks.some(Boolean)) return xml;\n let index = 0;\n return xml.replace(\n /<a:rPr\\b([^>]*)>([\\s\\S]*?)<\\/a:rPr>/g,\n (match, attrs, body) => {\n const href = hyperlinks[index++];\n if (!href) return match;\n const id = addRelationship(href);\n return `<a:rPr${attrs as string}>${body as string}<a:hlinkClick r:id=\"${id}\"/></a:rPr>`;\n },\n );\n}\n\nfunction applyGlimpseTextBoxesToXml(\n xml: string,\n registry: GlimpseTextBoxRegistry,\n addRelationship: (href: string) => string,\n): string {\n let result = xml;\n for (const entry of registry.entries) {\n const re = new RegExp(\n `<p:sp><p:nvSpPr><p:cNvPr id=\"([^\"]+)\" name=\"${escapeRegExp(\n entry.marker,\n )}\"[\\\\s\\\\S]*?</p:sp>`,\n \"g\",\n );\n result = result.replace(re, (_match, id: string) => {\n const xmlWithIds = replaceShapeId(entry.xml, id, entry.name);\n return withHyperlinkRelationships(\n xmlWithIds,\n entry.hyperlinks,\n addRelationship,\n );\n });\n }\n return result;\n}\n\nasync function applyGlimpseTextBoxes(\n data: Uint8Array | ArrayBuffer,\n registry: GlimpseTextBoxRegistry,\n): Promise<import(\"jszip\")> {\n const JSZip = await loadJSZip();\n const zip = await JSZip.loadAsync(data);\n\n const slidePaths = Object.keys(zip.files).filter((path) =>\n /^ppt\\/slides\\/slide\\d+\\.xml$/.test(path),\n );\n for (const path of slidePaths) {\n const file = zip.file(path);\n if (!file) continue;\n const original = await file.async(\"text\");\n const relsPath = slideRelsPath(path);\n const relsFile = zip.file(relsPath);\n const relationshipEditor = createRelationshipEditor(\n relsFile ? await relsFile.async(\"text\") : undefined,\n );\n const replaced = applyGlimpseTextBoxesToXml(original, registry, (href) =>\n relationshipEditor.addHyperlink(href),\n );\n if (replaced !== original) {\n zip.file(path, replaced);\n }\n const relationships = relationshipEditor.result;\n if (relationships.changed) {\n zip.file(relsPath, relationships.xml);\n }\n }\n return zip;\n}\n\nexport function patchPptxWriteForGlimpseTextBoxes(\n pptx: PptxGenJSInstance,\n registry: GlimpseTextBoxRegistry,\n): void {\n if (registry.isEmpty) return;\n\n const originalWrite = pptx.write.bind(pptx);\n\n const patchedWrite = async (rawProps?: WriteProps | string) => {\n const props: WriteProps | undefined =\n typeof rawProps === \"string\"\n ? ({ outputType: rawProps } as WriteProps)\n : rawProps;\n const data = (await originalWrite({\n outputType: \"uint8array\",\n })) as Uint8Array;\n const zip = await applyGlimpseTextBoxes(data, registry);\n\n const outputType = props?.outputType;\n if (outputType === \"STREAM\") {\n return zip.generateAsync({\n type: \"nodebuffer\",\n compression: props?.compression ? \"DEFLATE\" : \"STORE\",\n });\n }\n if (outputType) {\n return zip.generateAsync({\n type: outputType,\n compression: props?.compression ? \"DEFLATE\" : \"STORE\",\n });\n }\n return zip.generateAsync({\n type: \"blob\",\n compression: props?.compression ? \"DEFLATE\" : \"STORE\",\n });\n };\n pptx.write = patchedWrite;\n\n const patchedStream = async (props?: StreamProps) =>\n pptx.write({\n outputType: \"STREAM\",\n compression: props?.compression,\n });\n pptx.stream = patchedStream;\n\n const patchedWriteFile = async (rawProps?: WriteFileProps | string) => {\n const props: WriteFileProps | undefined =\n typeof rawProps === \"string\" ? { fileName: rawProps } : rawProps;\n const isNode =\n typeof process !== \"undefined\" && Boolean(process.versions?.node);\n if (!isNode) {\n const browserWriter = pptx as BrowserWritablePptx;\n if (typeof browserWriter.writeFileToBrowser !== \"function\") {\n throw new Error(\n \"pptx.writeFile browser download helper is unavailable; use pptx.write({ outputType: 'blob' }) instead\",\n );\n }\n const rawName = props?.fileName ?? \"Presentation.pptx\";\n const fileName = rawName.toLowerCase().endsWith(\".pptx\")\n ? rawName\n : `${rawName}.pptx`;\n const blob = (await patchedWrite({\n outputType: \"blob\",\n compression: props?.compression,\n })) as Blob;\n await browserWriter.writeFileToBrowser(fileName, blob);\n return fileName;\n }\n const rawName = props?.fileName ?? \"Presentation.pptx\";\n const fileName = rawName.toLowerCase().endsWith(\".pptx\")\n ? rawName\n : `${rawName}.pptx`;\n const buffer = (await patchedWrite({\n outputType: \"nodebuffer\",\n compression: props?.compression,\n })) as Buffer;\n const fs = await import(\"fs\");\n await fs.promises.writeFile(fileName, buffer);\n return fileName;\n };\n pptx.writeFile = patchedWriteFile;\n}\n"],"mappings":"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"}
@@ -27,7 +27,8 @@ var GlowEffectRegistry = class {
27
27
  });
28
28
  const existing = this.markerBySpec.get(specKey);
29
29
  if (existing) return existing;
30
- const marker = `${MARKER_PREFIX}${this.registered.length}`;
30
+ const index = this.registered.length;
31
+ const marker = `${MARKER_PREFIX}${index}`;
31
32
  this.markerBySpec.set(specKey, marker);
32
33
  this.registered.push({
33
34
  marker,
@@ -81,7 +82,11 @@ function applyGlowToXml(xml, registry) {
81
82
  result = result.replace(re, (_match, prefix, suffix) => {
82
83
  const block = prefix;
83
84
  const closingIdx = block.lastIndexOf("</a:effectLst>");
84
- if (closingIdx >= 0) return `${block.substring(0, closingIdx)}${glowXml}${block.substring(closingIdx)}${suffix}`;
85
+ if (closingIdx >= 0) {
86
+ const before = block.substring(0, closingIdx);
87
+ const after = block.substring(closingIdx);
88
+ return `${before}${glowXml}${after}${suffix}`;
89
+ }
85
90
  return `${block}<a:effectLst>${glowXml}</a:effectLst>${suffix}`;
86
91
  });
87
92
  }
@@ -127,6 +132,11 @@ function patchPptxWriteForGlowEffects(pptx, registry) {
127
132
  });
128
133
  };
129
134
  pptx.write = patchedWrite;
135
+ const patchedStream = async (props) => pptx.write({
136
+ outputType: "STREAM",
137
+ compression: props?.compression
138
+ });
139
+ pptx.stream = patchedStream;
130
140
  const patchedWriteFile = async (rawProps) => {
131
141
  const props = typeof rawProps === "string" ? { fileName: rawProps } : rawProps;
132
142
  if (!(typeof process !== "undefined" && Boolean(process.versions?.node))) return originalWriteFile(props);
@@ -142,6 +152,6 @@ function patchPptxWriteForGlowEffects(pptx, registry) {
142
152
  pptx.writeFile = patchedWriteFile;
143
153
  }
144
154
  //#endregion
145
- export { GlowEffectRegistry, patchPptxWriteForGlowEffects };
155
+ export { GlowEffectRegistry, applyGlowEffects, patchPptxWriteForGlowEffects };
146
156
 
147
157
  //# sourceMappingURL=glowEffects.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"glowEffects.js","names":[],"sources":["../../src/renderPptx/glowEffects.ts"],"sourcesContent":["/**\n * Shape / Icon の glow (光彩) 効果の実現\n *\n * pptxgenjs の Shape options には glow 相当のネイティブサポートがないため\n * (TextPropsOptions の glow は文字グリフ用)、以下の方式で実現する:\n *\n * 1. レンダリング時: glow 指定のある shape に一意なマーカー名を `objectName`\n * として埋め込み、レジストリに登録する。マーカー名は `<p:cNvPr name=\"...\"/>`\n * として PPTX 出力 XML に書き込まれる。\n * 2. 出力時: pptx.write() / writeFile() をラップし、出力 zip 内のスライド XML\n * の該当 `<p:sp>` の `<p:spPr>` 末尾に DrawingML ネイティブの\n * `<a:effectLst><a:glow>...</a:glow></a:effectLst>` を挿入する。\n *\n * 文字列置換採用理由 / 注意点は gradientFills.ts と同様。\n */\nimport type { TextGlow } from \"../types.ts\";\nimport { pxToEmu } from \"./units.ts\";\n\ntype PptxGenJSInstance = import(\"pptxgenjs\").default;\ntype WriteProps = NonNullable<Parameters<PptxGenJSInstance[\"write\"]>[0]>;\ntype WriteFileProps = NonNullable<\n Parameters<PptxGenJSInstance[\"writeFile\"]>[0]\n>;\n\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\ninterface RegisteredGlow {\n /** objectName に埋め込むマーカー文字列 */\n marker: string;\n /** size (px) — XML 出力時に EMU に変換する */\n sizePx: number;\n /** opacity 0-1 */\n opacity: number;\n /** 6桁 HEX (# なし、大文字) */\n color: string;\n}\n\nconst MARKER_PREFIX = \"pom-glow:\";\n\n/**\n * glow 仕様を一意なマーカー名にマップし、後処理で\n * `<a:effectLst><a:glow>` 要素として挿入できるようにするレジストリ\n */\nexport class GlowEffectRegistry {\n private readonly markerBySpec = new Map<string, string>();\n private readonly registered: RegisteredGlow[] = [];\n\n /**\n * glow を登録し、`objectName` に渡すマーカー文字列を返す。\n * 同じ仕様の glow は同じマーカーを返す。\n */\n register(glow: TextGlow): string {\n const sizePx = glow.size ?? 8;\n const opacity = glow.opacity ?? 0.75;\n const color = (glow.color ?? \"FFFFFF\").replace(/^#/, \"\").toUpperCase();\n const specKey = JSON.stringify({ sizePx, opacity, color });\n const existing = this.markerBySpec.get(specKey);\n if (existing) return existing;\n\n const index = this.registered.length;\n const marker = `${MARKER_PREFIX}${index}`;\n this.markerBySpec.set(specKey, marker);\n this.registered.push({ marker, sizePx, opacity, color });\n return marker;\n }\n\n get isEmpty(): boolean {\n return this.registered.length === 0;\n }\n\n get entries(): readonly RegisteredGlow[] {\n return this.registered;\n }\n}\n\n/**\n * DrawingML の `<a:glow>` 要素を構築する (effectLst で囲まない、内側だけ)\n *\n * - rad (光彩半径): px → EMU\n * - alpha (透明度): 0-1 → 1/1000 % (0-100000)\n */\nfunction buildGlowXml(entry: RegisteredGlow): string {\n const rad = Math.round(pxToEmu(entry.sizePx));\n const alpha = Math.round(entry.opacity * 100000);\n return `<a:glow rad=\"${rad}\"><a:srgbClr val=\"${entry.color}\"><a:alpha val=\"${alpha}\"/></a:srgbClr></a:glow>`;\n}\n\n/**\n * 1 つの slide XML を受け取り、登録された glow を該当 shape に適用した XML を返す\n *\n * `<p:cNvPr ... name=\"pom-glow:N\">` を含む `<p:sp>` ブロックを正規表現で検出し、\n * 以下のいずれかで `<a:glow>` を挿入する:\n *\n * 1. 既に `<a:effectLst>...</a:effectLst>` が存在する場合\n * (= pptxgenjs が shape の `shadow` 等で同要素を生成済み):\n * その `effectLst` 内側の末尾 (`</a:effectLst>` の直前) に `<a:glow>` を追加する。\n * OOXML スキーマ上 `<p:spPr>` 内の `<a:effectLst>` は最大 1 個までで、二重に\n * 並べると不正になるため。\n * 2. 既存 `effectLst` が無い場合:\n * `</p:spPr>` の直前に新規 `<a:effectLst><a:glow/></a:effectLst>` を挿入する。\n *\n * pptxgenjs は cNvPr を `<p:cNvPr.../>` (self-closing) と\n * `<p:cNvPr...></p:cNvPr>` (open+close) のどちらの形式でも出力し得るため、\n * cNvPr の閉じ形式に依存せず name 属性のあとから lazy に進める。\n */\nfunction applyGlowToXml(xml: string, registry: GlowEffectRegistry): string {\n let result = xml;\n for (const entry of registry.entries) {\n const glowXml = buildGlowXml(entry);\n const re = new RegExp(\n `(<p:cNvPr[^>]*name=\"${entry.marker}\"[\\\\s\\\\S]*?)(</p:spPr>)`,\n \"g\",\n );\n result = result.replace(re, (_match, prefix, suffix) => {\n const block = prefix as string;\n // shape ブロック内に既存の effectLst があるかを `</a:effectLst>` の有無で判定する。\n // 別 shape の effectLst を誤って書き換えないよう、検索範囲は prefix\n // (= 該当 cNvPr 〜 直後の </p:spPr> の手前まで) に限定している。\n const closingIdx = block.lastIndexOf(\"</a:effectLst>\");\n if (closingIdx >= 0) {\n const before = block.substring(0, closingIdx);\n const after = block.substring(closingIdx);\n return `${before}${glowXml}${after}${suffix as string}`;\n }\n return `${block}<a:effectLst>${glowXml}</a:effectLst>${suffix as string}`;\n });\n }\n return result;\n}\n\n/**\n * 出力 zip 内のスライド XML に glow 効果を挿入する\n */\nexport async function applyGlowEffects(\n data: Uint8Array | ArrayBuffer,\n registry: GlowEffectRegistry,\n): Promise<import(\"jszip\")> {\n const JSZip = await loadJSZip();\n const zip = await JSZip.loadAsync(data);\n\n const slidePaths = Object.keys(zip.files).filter((path) =>\n /^ppt\\/slides\\/slide\\d+\\.xml$/.test(path),\n );\n for (const path of slidePaths) {\n const file = zip.file(path);\n if (!file) continue;\n const original = await file.async(\"text\");\n const replaced = applyGlowToXml(original, registry);\n if (replaced !== original) {\n zip.file(path, replaced);\n }\n }\n return zip;\n}\n\n/**\n * pptx インスタンスの write / writeFile をラップし、出力時に glow 後処理を適用する。\n *\n * 既に他の後処理 (gradientFills 等) によりラップされている場合に備え、\n * 元の関数を保存してチェーン可能にしている。\n */\nexport function patchPptxWriteForGlowEffects(\n pptx: PptxGenJSInstance,\n registry: GlowEffectRegistry,\n): void {\n if (registry.isEmpty) return;\n\n const originalWrite = pptx.write.bind(pptx);\n const originalWriteFile = pptx.writeFile.bind(pptx);\n\n const patchedWrite = async (rawProps?: WriteProps | string) => {\n const props: WriteProps | undefined =\n typeof rawProps === \"string\"\n ? ({ outputType: rawProps } as WriteProps)\n : rawProps;\n const data = (await originalWrite({\n outputType: \"uint8array\",\n })) as Uint8Array;\n const zip = await applyGlowEffects(data, registry);\n\n const outputType = props?.outputType;\n if (outputType === \"STREAM\") {\n return zip.generateAsync({\n type: \"nodebuffer\",\n compression: props?.compression ? \"DEFLATE\" : \"STORE\",\n });\n }\n if (outputType) {\n return zip.generateAsync({ type: outputType });\n }\n return zip.generateAsync({\n type: \"blob\",\n compression: props?.compression ? \"DEFLATE\" : \"STORE\",\n });\n };\n pptx.write = patchedWrite;\n\n const patchedWriteFile = async (rawProps?: WriteFileProps | string) => {\n const props: WriteFileProps | undefined =\n typeof rawProps === \"string\" ? { fileName: rawProps } : rawProps;\n const isNode =\n typeof process !== \"undefined\" && Boolean(process.versions?.node);\n if (!isNode) {\n return originalWriteFile(props);\n }\n const rawName = props?.fileName ?? \"Presentation.pptx\";\n const fileName = rawName.toLowerCase().endsWith(\".pptx\")\n ? rawName\n : `${rawName}.pptx`;\n const buffer = (await patchedWrite({\n outputType: \"nodebuffer\",\n compression: props?.compression,\n })) as Buffer;\n const fs = await import(\"fs\");\n await fs.promises.writeFile(fileName, buffer);\n return fileName;\n };\n pptx.writeFile = patchedWriteFile;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"glowEffects.js","names":[],"sources":["../../src/renderPptx/glowEffects.ts"],"sourcesContent":["/**\n * Shape / Icon の glow (光彩) 効果の実現\n *\n * pptxgenjs の Shape options には glow 相当のネイティブサポートがないため\n * (TextPropsOptions の glow は文字グリフ用)、以下の方式で実現する:\n *\n * 1. レンダリング時: glow 指定のある shape に一意なマーカー名を `objectName`\n * として埋め込み、レジストリに登録する。マーカー名は `<p:cNvPr name=\"...\"/>`\n * として PPTX 出力 XML に書き込まれる。\n * 2. 出力時: pptx.write() / writeFile() をラップし、出力 zip 内のスライド XML\n * の該当 `<p:sp>` の `<p:spPr>` 末尾に DrawingML ネイティブの\n * `<a:effectLst><a:glow>...</a:glow></a:effectLst>` を挿入する。\n *\n * 文字列置換採用理由 / 注意点は gradientFills.ts と同様。\n */\nimport type { TextGlow } from \"../types.ts\";\nimport { pxToEmu } from \"./units.ts\";\n\ntype PptxGenJSInstance = import(\"pptxgenjs\").default;\ntype StreamProps = NonNullable<Parameters<PptxGenJSInstance[\"stream\"]>[0]>;\ntype WriteProps = NonNullable<Parameters<PptxGenJSInstance[\"write\"]>[0]>;\ntype WriteFileProps = NonNullable<\n Parameters<PptxGenJSInstance[\"writeFile\"]>[0]\n>;\n\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\ninterface RegisteredGlow {\n /** objectName に埋め込むマーカー文字列 */\n marker: string;\n /** size (px) — XML 出力時に EMU に変換する */\n sizePx: number;\n /** opacity 0-1 */\n opacity: number;\n /** 6桁 HEX (# なし、大文字) */\n color: string;\n}\n\nconst MARKER_PREFIX = \"pom-glow:\";\n\n/**\n * glow 仕様を一意なマーカー名にマップし、後処理で\n * `<a:effectLst><a:glow>` 要素として挿入できるようにするレジストリ\n */\nexport class GlowEffectRegistry {\n private readonly markerBySpec = new Map<string, string>();\n private readonly registered: RegisteredGlow[] = [];\n\n /**\n * glow を登録し、`objectName` に渡すマーカー文字列を返す。\n * 同じ仕様の glow は同じマーカーを返す。\n */\n register(glow: TextGlow): string {\n const sizePx = glow.size ?? 8;\n const opacity = glow.opacity ?? 0.75;\n const color = (glow.color ?? \"FFFFFF\").replace(/^#/, \"\").toUpperCase();\n const specKey = JSON.stringify({ sizePx, opacity, color });\n const existing = this.markerBySpec.get(specKey);\n if (existing) return existing;\n\n const index = this.registered.length;\n const marker = `${MARKER_PREFIX}${index}`;\n this.markerBySpec.set(specKey, marker);\n this.registered.push({ marker, sizePx, opacity, color });\n return marker;\n }\n\n get isEmpty(): boolean {\n return this.registered.length === 0;\n }\n\n get entries(): readonly RegisteredGlow[] {\n return this.registered;\n }\n}\n\n/**\n * DrawingML の `<a:glow>` 要素を構築する (effectLst で囲まない、内側だけ)\n *\n * - rad (光彩半径): px → EMU\n * - alpha (透明度): 0-1 → 1/1000 % (0-100000)\n */\nfunction buildGlowXml(entry: RegisteredGlow): string {\n const rad = Math.round(pxToEmu(entry.sizePx));\n const alpha = Math.round(entry.opacity * 100000);\n return `<a:glow rad=\"${rad}\"><a:srgbClr val=\"${entry.color}\"><a:alpha val=\"${alpha}\"/></a:srgbClr></a:glow>`;\n}\n\n/**\n * 1 つの slide XML を受け取り、登録された glow を該当 shape に適用した XML を返す\n *\n * `<p:cNvPr ... name=\"pom-glow:N\">` を含む `<p:sp>` ブロックを正規表現で検出し、\n * 以下のいずれかで `<a:glow>` を挿入する:\n *\n * 1. 既に `<a:effectLst>...</a:effectLst>` が存在する場合\n * (= pptxgenjs が shape の `shadow` 等で同要素を生成済み):\n * その `effectLst` 内側の末尾 (`</a:effectLst>` の直前) に `<a:glow>` を追加する。\n * OOXML スキーマ上 `<p:spPr>` 内の `<a:effectLst>` は最大 1 個までで、二重に\n * 並べると不正になるため。\n * 2. 既存 `effectLst` が無い場合:\n * `</p:spPr>` の直前に新規 `<a:effectLst><a:glow/></a:effectLst>` を挿入する。\n *\n * pptxgenjs は cNvPr を `<p:cNvPr.../>` (self-closing) と\n * `<p:cNvPr...></p:cNvPr>` (open+close) のどちらの形式でも出力し得るため、\n * cNvPr の閉じ形式に依存せず name 属性のあとから lazy に進める。\n */\nfunction applyGlowToXml(xml: string, registry: GlowEffectRegistry): string {\n let result = xml;\n for (const entry of registry.entries) {\n const glowXml = buildGlowXml(entry);\n const re = new RegExp(\n `(<p:cNvPr[^>]*name=\"${entry.marker}\"[\\\\s\\\\S]*?)(</p:spPr>)`,\n \"g\",\n );\n result = result.replace(re, (_match, prefix, suffix) => {\n const block = prefix as string;\n // shape ブロック内に既存の effectLst があるかを `</a:effectLst>` の有無で判定する。\n // 別 shape の effectLst を誤って書き換えないよう、検索範囲は prefix\n // (= 該当 cNvPr 〜 直後の </p:spPr> の手前まで) に限定している。\n const closingIdx = block.lastIndexOf(\"</a:effectLst>\");\n if (closingIdx >= 0) {\n const before = block.substring(0, closingIdx);\n const after = block.substring(closingIdx);\n return `${before}${glowXml}${after}${suffix as string}`;\n }\n return `${block}<a:effectLst>${glowXml}</a:effectLst>${suffix as string}`;\n });\n }\n return result;\n}\n\n/**\n * 出力 zip 内のスライド XML に glow 効果を挿入する\n */\nexport async function applyGlowEffects(\n data: Uint8Array | ArrayBuffer,\n registry: GlowEffectRegistry,\n): Promise<import(\"jszip\")> {\n const JSZip = await loadJSZip();\n const zip = await JSZip.loadAsync(data);\n\n const slidePaths = Object.keys(zip.files).filter((path) =>\n /^ppt\\/slides\\/slide\\d+\\.xml$/.test(path),\n );\n for (const path of slidePaths) {\n const file = zip.file(path);\n if (!file) continue;\n const original = await file.async(\"text\");\n const replaced = applyGlowToXml(original, registry);\n if (replaced !== original) {\n zip.file(path, replaced);\n }\n }\n return zip;\n}\n\n/**\n * pptx インスタンスの write / writeFile をラップし、出力時に glow 後処理を適用する。\n *\n * 既に他の後処理 (gradientFills 等) によりラップされている場合に備え、\n * 元の関数を保存してチェーン可能にしている。\n */\nexport function patchPptxWriteForGlowEffects(\n pptx: PptxGenJSInstance,\n registry: GlowEffectRegistry,\n): void {\n if (registry.isEmpty) return;\n\n const originalWrite = pptx.write.bind(pptx);\n const originalWriteFile = pptx.writeFile.bind(pptx);\n\n const patchedWrite = async (rawProps?: WriteProps | string) => {\n const props: WriteProps | undefined =\n typeof rawProps === \"string\"\n ? ({ outputType: rawProps } as WriteProps)\n : rawProps;\n const data = (await originalWrite({\n outputType: \"uint8array\",\n })) as Uint8Array;\n const zip = await applyGlowEffects(data, registry);\n\n const outputType = props?.outputType;\n if (outputType === \"STREAM\") {\n return zip.generateAsync({\n type: \"nodebuffer\",\n compression: props?.compression ? \"DEFLATE\" : \"STORE\",\n });\n }\n if (outputType) {\n return zip.generateAsync({ type: outputType });\n }\n return zip.generateAsync({\n type: \"blob\",\n compression: props?.compression ? \"DEFLATE\" : \"STORE\",\n });\n };\n pptx.write = patchedWrite;\n\n const patchedStream = async (props?: StreamProps) =>\n pptx.write({\n outputType: \"STREAM\",\n compression: props?.compression,\n });\n pptx.stream = patchedStream;\n\n const patchedWriteFile = async (rawProps?: WriteFileProps | string) => {\n const props: WriteFileProps | undefined =\n typeof rawProps === \"string\" ? { fileName: rawProps } : rawProps;\n const isNode =\n typeof process !== \"undefined\" && Boolean(process.versions?.node);\n if (!isNode) {\n return originalWriteFile(props);\n }\n const rawName = props?.fileName ?? \"Presentation.pptx\";\n const fileName = rawName.toLowerCase().endsWith(\".pptx\")\n ? rawName\n : `${rawName}.pptx`;\n const buffer = (await patchedWrite({\n outputType: \"nodebuffer\",\n compression: props?.compression,\n })) as Buffer;\n const fs = await import(\"fs\");\n await fs.promises.writeFile(fileName, buffer);\n return fileName;\n };\n pptx.writeFile = patchedWriteFile;\n}\n"],"mappings":"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"}