@hirokisakabe/pom 9.1.0 → 9.1.2

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 (52) hide show
  1. package/dist/buildContext.js +2 -4
  2. package/dist/buildContext.js.map +1 -1
  3. package/dist/buildPptx.d.ts.map +1 -1
  4. package/dist/buildPptx.js +2 -6
  5. package/dist/buildPptx.js.map +1 -1
  6. package/dist/extractSlideMastersAsPptx.js +1 -1
  7. package/dist/renderPptx/glimpseTextBoxes.js +646 -0
  8. package/dist/renderPptx/glimpseTextBoxes.js.map +1 -0
  9. package/dist/renderPptx/nodes/flow.js +95 -90
  10. package/dist/renderPptx/nodes/flow.js.map +1 -1
  11. package/dist/renderPptx/nodes/icon.js +33 -24
  12. package/dist/renderPptx/nodes/icon.js.map +1 -1
  13. package/dist/renderPptx/nodes/image.js +5 -22
  14. package/dist/renderPptx/nodes/image.js.map +1 -1
  15. package/dist/renderPptx/nodes/list.js +96 -111
  16. package/dist/renderPptx/nodes/list.js.map +1 -1
  17. package/dist/renderPptx/nodes/matrix.js +92 -70
  18. package/dist/renderPptx/nodes/matrix.js.map +1 -1
  19. package/dist/renderPptx/nodes/processArrow.js +68 -35
  20. package/dist/renderPptx/nodes/processArrow.js.map +1 -1
  21. package/dist/renderPptx/nodes/pyramid.js +34 -16
  22. package/dist/renderPptx/nodes/pyramid.js.map +1 -1
  23. package/dist/renderPptx/nodes/shape.js +63 -37
  24. package/dist/renderPptx/nodes/shape.js.map +1 -1
  25. package/dist/renderPptx/nodes/svg.js +2 -5
  26. package/dist/renderPptx/nodes/svg.js.map +1 -1
  27. package/dist/renderPptx/nodes/text.js +16 -45
  28. package/dist/renderPptx/nodes/text.js.map +1 -1
  29. package/dist/renderPptx/nodes/timeline.js +108 -74
  30. package/dist/renderPptx/nodes/timeline.js.map +1 -1
  31. package/dist/renderPptx/nodes/tree.js +78 -79
  32. package/dist/renderPptx/nodes/tree.js.map +1 -1
  33. package/dist/renderPptx/renderPptx.js +4 -5
  34. package/dist/renderPptx/renderPptx.js.map +1 -1
  35. package/dist/renderPptx/utils/backgroundBorder.js +85 -49
  36. package/dist/renderPptx/utils/backgroundBorder.js.map +1 -1
  37. package/dist/renderPptx/utils/glimpsePicture.js +130 -0
  38. package/dist/renderPptx/utils/glimpsePicture.js.map +1 -0
  39. package/dist/renderPptx/utils/glimpseShape.js +90 -0
  40. package/dist/renderPptx/utils/glimpseShape.js.map +1 -0
  41. package/dist/renderPptx/utils/glimpseTextBox.js +126 -0
  42. package/dist/renderPptx/utils/glimpseTextBox.js.map +1 -0
  43. package/dist/renderPptx/utils/straightLine.js +29 -14
  44. package/dist/renderPptx/utils/straightLine.js.map +1 -1
  45. package/dist/renderPptx/utils/visualStyle.js +1 -24
  46. package/dist/renderPptx/utils/visualStyle.js.map +1 -1
  47. package/dist/types.js +1 -1
  48. package/package.json +4 -4
  49. package/dist/renderPptx/glowEffects.js +0 -152
  50. package/dist/renderPptx/glowEffects.js.map +0 -1
  51. package/dist/renderPptx/gradientFills.js +0 -167
  52. package/dist/renderPptx/gradientFills.js.map +0 -1
@@ -0,0 +1,126 @@
1
+ import { pxToEmu, pxToPt, rectPxToIn } from "../units.js";
2
+ import { toColorInput } from "../glimpseTextBoxes.js";
3
+ import { asEmu, asHundredthPt, asOoxmlAngle, asPt } from "@pptx-glimpse/document";
4
+ //#region src/renderPptx/utils/glimpseTextBox.ts
5
+ const TRANSPARENT_MARKER_STYLE = {
6
+ fill: {
7
+ color: "FFFFFF",
8
+ transparency: 100
9
+ },
10
+ line: {
11
+ color: "FFFFFF",
12
+ transparency: 100
13
+ }
14
+ };
15
+ function toUnderlineInput(underline) {
16
+ if (underline === void 0 || underline === false) return void 0;
17
+ if (underline === true) return true;
18
+ return {
19
+ style: underline.style,
20
+ color: toColorInput(underline.color)
21
+ };
22
+ }
23
+ function createGlimpseRunProperties(style) {
24
+ return {
25
+ fontFace: style.fontFace ?? "Noto Sans JP",
26
+ fontSize: asPt(pxToPt(style.fontSize ?? 24)),
27
+ color: toColorInput(style.color),
28
+ bold: style.bold,
29
+ italic: style.italic,
30
+ underline: toUnderlineInput(style.underline),
31
+ strike: style.strike,
32
+ baseline: style.subscript ? "subscript" : style.superscript ? "superscript" : void 0,
33
+ highlight: toColorInput(style.highlight)
34
+ };
35
+ }
36
+ function createGlimpseParagraph(text, style, options = {}) {
37
+ const fontSizePx = style.fontSize ?? 24;
38
+ return {
39
+ properties: {
40
+ align: options.align,
41
+ lineSpacing: options.lineHeight !== void 0 ? asHundredthPt(Math.round(pxToPt(fontSizePx * options.lineHeight) * 100)) : void 0
42
+ },
43
+ runs: [{
44
+ text,
45
+ properties: createGlimpseRunProperties(style)
46
+ }]
47
+ };
48
+ }
49
+ function createGlimpseParagraphs(text, style, options = {}) {
50
+ return text.replace(/\r*\n/g, "\n").split("\n").map((line) => createGlimpseParagraph(line, style, options));
51
+ }
52
+ function textBoxInput(bounds, options, paragraphs) {
53
+ const margin = options.margin !== void 0 ? asEmu(Math.round(pxToEmu(options.margin))) : void 0;
54
+ return {
55
+ offsetX: asEmu(Math.round(pxToEmu(bounds.x))),
56
+ offsetY: asEmu(Math.round(pxToEmu(bounds.y))),
57
+ width: asEmu(Math.max(1, Math.round(pxToEmu(bounds.w)))),
58
+ height: asEmu(Math.max(1, Math.round(pxToEmu(bounds.h)))),
59
+ rotation: options.rotate !== void 0 ? asOoxmlAngle(Math.round(options.rotate * 6e4)) : void 0,
60
+ body: {
61
+ marginLeft: margin,
62
+ marginRight: margin,
63
+ marginTop: margin,
64
+ marginBottom: margin,
65
+ anchor: options.valign
66
+ },
67
+ paragraphs
68
+ };
69
+ }
70
+ function withShapeAutofit(xml) {
71
+ const selfClosing = xml.replace(/<a:bodyPr\b([^>]*)\/>/, (_match, attrs) => `<a:bodyPr${attrs}><a:spAutoFit/></a:bodyPr>`);
72
+ if (selfClosing !== xml) return selfClosing;
73
+ return xml.replace(/(<a:bodyPr\b[^>]*>)([\s\S]*?)(<\/a:bodyPr>)/, (_match, open, body, close) => {
74
+ if (body.includes("<a:spAutoFit/>")) return `${open}${body}${close}`;
75
+ return `${open}<a:spAutoFit/>${body}${close}`;
76
+ });
77
+ }
78
+ function composeXmlTransform(autoFit, transform) {
79
+ if (!autoFit) return transform;
80
+ return (xml) => {
81
+ const fitted = withShapeAutofit(xml);
82
+ return transform ? transform(fitted) : fitted;
83
+ };
84
+ }
85
+ function addGlimpseTextBox(ctx, bounds, options = {}) {
86
+ const paragraphs = options.paragraphs ?? createGlimpseParagraphs(options.text ?? "", options, {
87
+ align: options.align,
88
+ lineHeight: options.lineHeight
89
+ });
90
+ const marker = ctx.buildContext.glimpseTextBoxes.registerTextBox(textBoxInput(bounds, options, paragraphs), {
91
+ hyperlinks: options.hyperlinks,
92
+ xmlTransform: composeXmlTransform(options.autoFit, options.xmlTransform)
93
+ });
94
+ ctx.slide.addShape(ctx.pptx.ShapeType.rect, {
95
+ ...rectPxToIn({
96
+ ...bounds,
97
+ w: Math.max(bounds.w, 1),
98
+ h: Math.max(bounds.h, 1)
99
+ }),
100
+ ...TRANSPARENT_MARKER_STYLE,
101
+ objectName: marker
102
+ });
103
+ }
104
+ function bulletXml(options) {
105
+ if (options.kind === "bullet") return "<a:buSzPct val=\"100000\"/><a:buChar char=\"&#x2022;\"/>";
106
+ return `<a:buSzPct val="100000"/><a:buFont typeface="+mj-lt"/><a:buAutoNum type="${isSupportedAutoNumScheme(options.scheme) ? options.scheme : "arabicPeriod"}" startAt="${options.startAt ?? 1}"/>`;
107
+ }
108
+ function isSupportedAutoNumScheme(value) {
109
+ return value === "arabicPeriod" || value === "arabicParenR" || value === "romanUcPeriod" || value === "romanLcPeriod" || value === "alphaUcPeriod" || value === "alphaLcPeriod" || value === "alphaLcParenR" || value === "alphaUcParenR" || value === "arabicPlain";
110
+ }
111
+ function withoutParagraphIndentAttrs(attrs) {
112
+ return attrs.replace(/\s(?:indent|marL)="[^"]*"/g, "");
113
+ }
114
+ function listBulletXmlTransform(options) {
115
+ const paragraphBody = `${`<a:lnSpc><a:spcPct val="${Math.round((options.lineHeight ?? 1.3) * 1e5)}"/></a:lnSpc>`}${bulletXml(options)}`;
116
+ return (xml) => {
117
+ return xml.replace(/<a:pPr([^>]*)\/>/g, (_match, attrs) => `<a:pPr${withoutParagraphIndentAttrs(attrs)} marL="342900" indent="-342900">${paragraphBody}</a:pPr>`).replace(/<a:pPr([^>]*)>([\s\S]*?)<\/a:pPr>/g, (match, attrs, body) => {
118
+ if (!body.includes("<a:buNone/>")) return match;
119
+ return `<a:pPr${withoutParagraphIndentAttrs(attrs)} marL="342900" indent="-342900">${body.replace(/(?:<a:lnSpc>[\s\S]*?<\/a:lnSpc>)?<a:buNone\/>/, paragraphBody)}</a:pPr>`;
120
+ });
121
+ };
122
+ }
123
+ //#endregion
124
+ export { addGlimpseTextBox, createGlimpseParagraphs, createGlimpseRunProperties, listBulletXmlTransform };
125
+
126
+ //# sourceMappingURL=glimpseTextBox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glimpseTextBox.js","names":[],"sources":["../../../src/renderPptx/utils/glimpseTextBox.ts"],"sourcesContent":["import {\n asEmu,\n asHundredthPt,\n asOoxmlAngle,\n asPt,\n type AddTextBoxInput,\n type AddTextBoxParagraphInput,\n type AddTextBoxRunPropertiesInput,\n type SourceAutoNumScheme,\n} from \"@pptx-glimpse/document\";\nimport type { Underline } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToEmu, pxToPt, rectPxToIn } from \"../units.ts\";\nimport { toColorInput } from \"../glimpseTextBoxes.ts\";\n\ntype TextBoundsPx = { x: number; y: number; w: number; h: number };\n\ntype TextBoxVerticalAlign = \"top\" | \"middle\" | \"bottom\";\n\nexport type GlimpseTextRunStyle = {\n fontSize?: number;\n fontFace?: string;\n color?: string;\n bold?: boolean;\n italic?: boolean;\n underline?: Underline;\n strike?: boolean;\n subscript?: boolean;\n superscript?: boolean;\n highlight?: string;\n};\n\nexport type GlimpseTextBoxOptions = GlimpseTextRunStyle & {\n text?: string;\n align?: \"left\" | \"center\" | \"right\";\n valign?: TextBoxVerticalAlign;\n lineHeight?: number;\n rotate?: number;\n autoFit?: boolean;\n margin?: number;\n};\n\nexport type ListBulletOptions =\n | { kind: \"bullet\" }\n | {\n kind: \"number\";\n scheme?: string;\n startAt?: number;\n };\n\nconst TRANSPARENT_MARKER_STYLE = {\n fill: { color: \"FFFFFF\", transparency: 100 },\n line: { color: \"FFFFFF\", transparency: 100 },\n} as const;\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\nexport function createGlimpseRunProperties(\n style: GlimpseTextRunStyle,\n): AddTextBoxRunPropertiesInput {\n return {\n fontFace: style.fontFace ?? \"Noto Sans JP\",\n fontSize: asPt(pxToPt(style.fontSize ?? 24)),\n color: toColorInput(style.color),\n bold: style.bold,\n italic: style.italic,\n underline: toUnderlineInput(style.underline),\n strike: style.strike,\n baseline: style.subscript\n ? \"subscript\"\n : style.superscript\n ? \"superscript\"\n : undefined,\n highlight: toColorInput(style.highlight),\n };\n}\n\nfunction createGlimpseParagraph(\n text: string,\n style: GlimpseTextRunStyle,\n options: Pick<GlimpseTextBoxOptions, \"align\" | \"lineHeight\"> = {},\n): AddTextBoxParagraphInput {\n const fontSizePx = style.fontSize ?? 24;\n return {\n properties: {\n align: options.align,\n lineSpacing:\n options.lineHeight !== undefined\n ? asHundredthPt(\n Math.round(pxToPt(fontSizePx * options.lineHeight) * 100),\n )\n : undefined,\n },\n runs: [{ text, properties: createGlimpseRunProperties(style) }],\n };\n}\n\nexport function createGlimpseParagraphs(\n text: string,\n style: GlimpseTextRunStyle,\n options: Pick<GlimpseTextBoxOptions, \"align\" | \"lineHeight\"> = {},\n): AddTextBoxParagraphInput[] {\n return text\n .replace(/\\r*\\n/g, \"\\n\")\n .split(\"\\n\")\n .map((line) => createGlimpseParagraph(line, style, options));\n}\n\nfunction textBoxInput(\n bounds: TextBoundsPx,\n options: GlimpseTextBoxOptions,\n paragraphs: readonly AddTextBoxParagraphInput[],\n): AddTextBoxInput {\n const margin =\n options.margin !== undefined\n ? asEmu(Math.round(pxToEmu(options.margin)))\n : undefined;\n return {\n offsetX: asEmu(Math.round(pxToEmu(bounds.x))),\n offsetY: asEmu(Math.round(pxToEmu(bounds.y))),\n width: asEmu(Math.max(1, Math.round(pxToEmu(bounds.w)))),\n height: asEmu(Math.max(1, Math.round(pxToEmu(bounds.h)))),\n rotation:\n options.rotate !== undefined\n ? asOoxmlAngle(Math.round(options.rotate * 60000))\n : undefined,\n body: {\n marginLeft: margin,\n marginRight: margin,\n marginTop: margin,\n marginBottom: margin,\n anchor: options.valign,\n },\n paragraphs,\n };\n}\n\nfunction withShapeAutofit(xml: string): string {\n const selfClosing = xml.replace(\n /<a:bodyPr\\b([^>]*)\\/>/,\n (_match, attrs: string) => `<a:bodyPr${attrs}><a:spAutoFit/></a:bodyPr>`,\n );\n if (selfClosing !== xml) return selfClosing;\n return xml.replace(\n /(<a:bodyPr\\b[^>]*>)([\\s\\S]*?)(<\\/a:bodyPr>)/,\n (_match, open: string, body: string, close: string) => {\n if (body.includes(\"<a:spAutoFit/>\")) return `${open}${body}${close}`;\n return `${open}<a:spAutoFit/>${body}${close}`;\n },\n );\n}\n\nfunction composeXmlTransform(\n autoFit: boolean | undefined,\n transform: ((xml: string) => string) | undefined,\n) {\n if (!autoFit) return transform;\n return (xml: string) => {\n const fitted = withShapeAutofit(xml);\n return transform ? transform(fitted) : fitted;\n };\n}\n\nexport function addGlimpseTextBox(\n ctx: RenderContext,\n bounds: TextBoundsPx,\n options: GlimpseTextBoxOptions & {\n paragraphs?: readonly AddTextBoxParagraphInput[];\n hyperlinks?: readonly (string | undefined)[];\n xmlTransform?: (xml: string) => string;\n } = {},\n): void {\n const paragraphs =\n options.paragraphs ??\n createGlimpseParagraphs(options.text ?? \"\", options, {\n align: options.align,\n lineHeight: options.lineHeight,\n });\n const marker = ctx.buildContext.glimpseTextBoxes.registerTextBox(\n textBoxInput(bounds, options, paragraphs),\n {\n hyperlinks: options.hyperlinks,\n xmlTransform: composeXmlTransform(options.autoFit, options.xmlTransform),\n },\n );\n ctx.slide.addShape(ctx.pptx.ShapeType.rect, {\n ...rectPxToIn({\n ...bounds,\n w: Math.max(bounds.w, 1),\n h: Math.max(bounds.h, 1),\n }),\n ...TRANSPARENT_MARKER_STYLE,\n objectName: marker,\n });\n}\n\nfunction bulletXml(options: ListBulletOptions): string {\n if (options.kind === \"bullet\") {\n return '<a:buSzPct val=\"100000\"/><a:buChar char=\"&#x2022;\"/>';\n }\n const scheme = isSupportedAutoNumScheme(options.scheme)\n ? options.scheme\n : \"arabicPeriod\";\n return `<a:buSzPct val=\"100000\"/><a:buFont typeface=\"+mj-lt\"/><a:buAutoNum type=\"${scheme}\" startAt=\"${\n options.startAt ?? 1\n }\"/>`;\n}\n\nfunction isSupportedAutoNumScheme(\n value: string | undefined,\n): value is SourceAutoNumScheme {\n return (\n value === \"arabicPeriod\" ||\n value === \"arabicParenR\" ||\n value === \"romanUcPeriod\" ||\n value === \"romanLcPeriod\" ||\n value === \"alphaUcPeriod\" ||\n value === \"alphaLcPeriod\" ||\n value === \"alphaLcParenR\" ||\n value === \"alphaUcParenR\" ||\n value === \"arabicPlain\"\n );\n}\n\nfunction withoutParagraphIndentAttrs(attrs: string): string {\n return attrs.replace(/\\s(?:indent|marL)=\"[^\"]*\"/g, \"\");\n}\n\nexport function listBulletXmlTransform(\n options: ListBulletOptions & { lineHeight?: number },\n) {\n const lineSpacing = `<a:lnSpc><a:spcPct val=\"${Math.round(\n (options.lineHeight ?? 1.3) * 100000,\n )}\"/></a:lnSpc>`;\n const paragraphBody = `${lineSpacing}${bulletXml(options)}`;\n return (xml: string): string => {\n const expanded = xml.replace(\n /<a:pPr([^>]*)\\/>/g,\n (_match, attrs: string) =>\n `<a:pPr${withoutParagraphIndentAttrs(attrs)} marL=\"342900\" indent=\"-342900\">${paragraphBody}</a:pPr>`,\n );\n return expanded.replace(\n /<a:pPr([^>]*)>([\\s\\S]*?)<\\/a:pPr>/g,\n (match: string, attrs: string, body: string) => {\n if (!body.includes(\"<a:buNone/>\")) return match;\n return `<a:pPr${withoutParagraphIndentAttrs(attrs)} marL=\"342900\" indent=\"-342900\">${body.replace(\n /(?:<a:lnSpc>[\\s\\S]*?<\\/a:lnSpc>)?<a:buNone\\/>/,\n paragraphBody,\n )}</a:pPr>`;\n },\n );\n };\n}\n\nexport function listLineSpacingXmlTransform(options: { lineHeight?: number }) {\n const lineSpacing = `<a:lnSpc><a:spcPct val=\"${Math.round(\n (options.lineHeight ?? 1.3) * 100000,\n )}\"/></a:lnSpc>`;\n const paragraphBody = `${lineSpacing}<a:buNone/>`;\n return (xml: string): string => {\n const expanded = xml.replace(\n /<a:pPr([^>]*)\\/>/g,\n (_match, attrs: string) => `<a:pPr${attrs}>${paragraphBody}</a:pPr>`,\n );\n return expanded.replace(\n /<a:pPr([^>]*)>([\\s\\S]*?)<\\/a:pPr>/g,\n (_match: string, attrs: string, body: string) => {\n const nextBody = body\n .replace(/<a:lnSpc>[\\s\\S]*?<\\/a:lnSpc>/, \"\")\n .replace(/<a:buNone\\/>/, \"\");\n return `<a:pPr${attrs}>${paragraphBody}${nextBody}</a:pPr>`;\n },\n );\n };\n}\n"],"mappings":";;;;AAkDA,MAAM,2BAA2B;CAC/B,MAAM;EAAE,OAAO;EAAU,cAAc;CAAI;CAC3C,MAAM;EAAE,OAAO;EAAU,cAAc;CAAI;AAC7C;AAEA,SAAS,iBAAiB,WAAkC;CAC1D,IAAI,cAAc,KAAA,KAAa,cAAc,OAAO,OAAO,KAAA;CAC3D,IAAI,cAAc,MAAM,OAAO;CAC/B,OAAO;EACL,OAAO,UAAU;EACjB,OAAO,aAAa,UAAU,KAAK;CACrC;AACF;AAEA,SAAgB,2BACd,OAC8B;CAC9B,OAAO;EACL,UAAU,MAAM,YAAY;EAC5B,UAAU,KAAK,OAAO,MAAM,YAAY,EAAE,CAAC;EAC3C,OAAO,aAAa,MAAM,KAAK;EAC/B,MAAM,MAAM;EACZ,QAAQ,MAAM;EACd,WAAW,iBAAiB,MAAM,SAAS;EAC3C,QAAQ,MAAM;EACd,UAAU,MAAM,YACZ,cACA,MAAM,cACJ,gBACA,KAAA;EACN,WAAW,aAAa,MAAM,SAAS;CACzC;AACF;AAEA,SAAS,uBACP,MACA,OACA,UAA+D,CAAC,GACtC;CAC1B,MAAM,aAAa,MAAM,YAAY;CACrC,OAAO;EACL,YAAY;GACV,OAAO,QAAQ;GACf,aACE,QAAQ,eAAe,KAAA,IACnB,cACE,KAAK,MAAM,OAAO,aAAa,QAAQ,UAAU,IAAI,GAAG,CAC1D,IACA,KAAA;EACR;EACA,MAAM,CAAC;GAAE;GAAM,YAAY,2BAA2B,KAAK;EAAE,CAAC;CAChE;AACF;AAEA,SAAgB,wBACd,MACA,OACA,UAA+D,CAAC,GACpC;CAC5B,OAAO,KACJ,QAAQ,UAAU,IAAI,CAAC,CACvB,MAAM,IAAI,CAAC,CACX,KAAK,SAAS,uBAAuB,MAAM,OAAO,OAAO,CAAC;AAC/D;AAEA,SAAS,aACP,QACA,SACA,YACiB;CACjB,MAAM,SACJ,QAAQ,WAAW,KAAA,IACf,MAAM,KAAK,MAAM,QAAQ,QAAQ,MAAM,CAAC,CAAC,IACzC,KAAA;CACN,OAAO;EACL,SAAS,MAAM,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC;EAC5C,SAAS,MAAM,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC;EAC5C,OAAO,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC,CAAC;EACvD,QAAQ,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC,CAAC;EACxD,UACE,QAAQ,WAAW,KAAA,IACf,aAAa,KAAK,MAAM,QAAQ,SAAS,GAAK,CAAC,IAC/C,KAAA;EACN,MAAM;GACJ,YAAY;GACZ,aAAa;GACb,WAAW;GACX,cAAc;GACd,QAAQ,QAAQ;EAClB;EACA;CACF;AACF;AAEA,SAAS,iBAAiB,KAAqB;CAC7C,MAAM,cAAc,IAAI,QACtB,0BACC,QAAQ,UAAkB,YAAY,MAAM,2BAC/C;CACA,IAAI,gBAAgB,KAAK,OAAO;CAChC,OAAO,IAAI,QACT,gDACC,QAAQ,MAAc,MAAc,UAAkB;EACrD,IAAI,KAAK,SAAS,gBAAgB,GAAG,OAAO,GAAG,OAAO,OAAO;EAC7D,OAAO,GAAG,KAAK,gBAAgB,OAAO;CACxC,CACF;AACF;AAEA,SAAS,oBACP,SACA,WACA;CACA,IAAI,CAAC,SAAS,OAAO;CACrB,QAAQ,QAAgB;EACtB,MAAM,SAAS,iBAAiB,GAAG;EACnC,OAAO,YAAY,UAAU,MAAM,IAAI;CACzC;AACF;AAEA,SAAgB,kBACd,KACA,QACA,UAII,CAAC,GACC;CACN,MAAM,aACJ,QAAQ,cACR,wBAAwB,QAAQ,QAAQ,IAAI,SAAS;EACnD,OAAO,QAAQ;EACf,YAAY,QAAQ;CACtB,CAAC;CACH,MAAM,SAAS,IAAI,aAAa,iBAAiB,gBAC/C,aAAa,QAAQ,SAAS,UAAU,GACxC;EACE,YAAY,QAAQ;EACpB,cAAc,oBAAoB,QAAQ,SAAS,QAAQ,YAAY;CACzE,CACF;CACA,IAAI,MAAM,SAAS,IAAI,KAAK,UAAU,MAAM;EAC1C,GAAG,WAAW;GACZ,GAAG;GACH,GAAG,KAAK,IAAI,OAAO,GAAG,CAAC;GACvB,GAAG,KAAK,IAAI,OAAO,GAAG,CAAC;EACzB,CAAC;EACD,GAAG;EACH,YAAY;CACd,CAAC;AACH;AAEA,SAAS,UAAU,SAAoC;CACrD,IAAI,QAAQ,SAAS,UACnB,OAAO;CAKT,OAAO,4EAHQ,yBAAyB,QAAQ,MAAM,IAClD,QAAQ,SACR,eACsF,aACxF,QAAQ,WAAW,EACpB;AACH;AAEA,SAAS,yBACP,OAC8B;CAC9B,OACE,UAAU,kBACV,UAAU,kBACV,UAAU,mBACV,UAAU,mBACV,UAAU,mBACV,UAAU,mBACV,UAAU,mBACV,UAAU,mBACV,UAAU;AAEd;AAEA,SAAS,4BAA4B,OAAuB;CAC1D,OAAO,MAAM,QAAQ,8BAA8B,EAAE;AACvD;AAEA,SAAgB,uBACd,SACA;CAIA,MAAM,gBAAgB,GAAG,2BAHsB,KAAK,OACjD,QAAQ,cAAc,OAAO,GAChC,EAAE,iBACqC,UAAU,OAAO;CACxD,QAAQ,QAAwB;EAM9B,OALiB,IAAI,QACnB,sBACC,QAAQ,UACP,SAAS,4BAA4B,KAAK,EAAE,kCAAkC,cAAc,SAElF,CAAC,CAAC,QACd,uCACC,OAAe,OAAe,SAAiB;GAC9C,IAAI,CAAC,KAAK,SAAS,aAAa,GAAG,OAAO;GAC1C,OAAO,SAAS,4BAA4B,KAAK,EAAE,kCAAkC,KAAK,QACxF,iDACA,aACF,EAAE;EACJ,CACF;CACF;AACF"}
@@ -1,4 +1,6 @@
1
- import { pxToIn, pxToPt } from "../units.js";
1
+ import { pxToEmu } from "../units.js";
2
+ import { addGlimpseShape, arrowEndpoint, createShapeBoundsInput, noneShapeFill, solidShapeFill } from "./glimpseShape.js";
3
+ import { asEmu } from "@pptx-glimpse/document";
2
4
  //#region src/renderPptx/utils/straightLine.ts
3
5
  /**
4
6
  * boolean | LineArrowOptions から pptxgenjs の arrow type を取得
@@ -12,27 +14,40 @@ function resolveArrowType(arrow) {
12
14
  /**
13
15
  * 始点 (x1, y1) から終点 (x2, y2) への直線を描画する
14
16
  */
15
- function addStraightLine(ctx, { x1, y1, x2, y2 }, { color, lineWidth, dashType, beginArrow, endArrow }) {
17
+ function addStraightLine(ctx, { x1, y1, x2, y2 }, { color, lineWidth, dashType, beginArrow, endArrow, lineGradient, lineGradientOpacity }) {
16
18
  const minX = Math.min(x1, x2);
17
19
  const minY = Math.min(y1, y2);
18
20
  const lineW = Math.abs(x2 - x1);
19
21
  const lineH = Math.abs(y2 - y1);
20
22
  const flipH = x2 < x1;
21
23
  const flipV = y2 < y1;
22
- ctx.slide.addShape(ctx.pptx.ShapeType.line, {
23
- x: pxToIn(minX),
24
- y: pxToIn(minY),
25
- w: pxToIn(lineW),
26
- h: pxToIn(lineH),
24
+ addGlimpseShape(ctx, {
25
+ preset: "line",
26
+ ...createShapeBoundsInput({
27
+ x: minX,
28
+ y: minY,
29
+ w: lineW,
30
+ h: lineH
31
+ }),
32
+ fill: noneShapeFill(),
33
+ outline: {
34
+ fill: solidShapeFill(color ?? "000000"),
35
+ width: lineWidth !== void 0 ? asEmu(Math.round(pxToEmu(lineWidth))) : asEmu(12700),
36
+ dash: dashType === "lgDashDotDot" ? void 0 : dashType,
37
+ headEnd: arrowEndpoint(resolveArrowType(beginArrow)),
38
+ tailEnd: arrowEndpoint(resolveArrowType(endArrow))
39
+ }
40
+ }, {
41
+ x: minX,
42
+ y: minY,
43
+ w: lineW,
44
+ h: lineH
45
+ }, {
27
46
  flipH,
28
47
  flipV,
29
- line: {
30
- color: color ?? "000000",
31
- width: lineWidth !== void 0 ? pxToPt(lineWidth) : 1,
32
- dashType,
33
- beginArrowType: resolveArrowType(beginArrow),
34
- endArrowType: resolveArrowType(endArrow)
35
- }
48
+ dashType,
49
+ outlineGradient: lineGradient,
50
+ outlineOpacity: lineGradientOpacity
36
51
  });
37
52
  }
38
53
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"straightLine.js","names":[],"sources":["../../../src/renderPptx/utils/straightLine.ts"],"sourcesContent":["/**\n * 2 点間の直線を pptxgenjs の line shape に変換する共通描画処理。\n *\n * pptxgenjs の line shape は左上座標 (x, y) + サイズ (w, h) で表現され、\n * 線の向き (始点→終点) は flipH / flipV で表すため、端点座標からの\n * 変換ロジックを Line / Arrow ノードで共有する。\n */\nimport type { LineArrow, LineNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn, pxToPt } from \"../units.ts\";\n\n/**\n * boolean | LineArrowOptions から pptxgenjs の arrow type を取得\n */\nexport function resolveArrowType(\n arrow: LineArrow | undefined,\n): \"none\" | \"arrow\" | \"diamond\" | \"oval\" | \"stealth\" | \"triangle\" | undefined {\n if (arrow === undefined) {\n return undefined;\n }\n if (arrow === false) {\n return \"none\";\n }\n if (arrow === true) {\n return \"triangle\"; // デフォルト\n }\n return arrow.type ?? \"triangle\";\n}\n\ntype StraightLinePoints = { x1: number; y1: number; x2: number; y2: number };\n\ntype StraightLineStyle = Pick<\n LineNode,\n \"color\" | \"lineWidth\" | \"dashType\" | \"beginArrow\" | \"endArrow\"\n>;\n\n/**\n * 始点 (x1, y1) から終点 (x2, y2) への直線を描画する\n */\nexport function addStraightLine(\n ctx: RenderContext,\n { x1, y1, x2, y2 }: StraightLinePoints,\n { color, lineWidth, dashType, beginArrow, endArrow }: StraightLineStyle,\n): void {\n const minX = Math.min(x1, x2);\n const minY = Math.min(y1, y2);\n const lineW = Math.abs(x2 - x1);\n const lineH = Math.abs(y2 - y1);\n\n // 線の方向を判定して flip を決定\n // flipH: 右から左へ向かう線\n // flipV: 下から上へ向かう線\n const flipH = x2 < x1;\n const flipV = y2 < y1;\n\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(minX),\n y: pxToIn(minY),\n w: pxToIn(lineW),\n h: pxToIn(lineH),\n flipH,\n flipV,\n line: {\n color: color ?? \"000000\",\n width: lineWidth !== undefined ? pxToPt(lineWidth) : 1,\n dashType,\n beginArrowType: resolveArrowType(beginArrow),\n endArrowType: resolveArrowType(endArrow),\n },\n });\n}\n"],"mappings":";;;;;AAcA,SAAgB,iBACd,OAC4E;CAC5E,IAAI,UAAU,KAAA,GACZ;CAEF,IAAI,UAAU,OACZ,OAAO;CAET,IAAI,UAAU,MACZ,OAAO;CAET,OAAO,MAAM,QAAQ;AACvB;;;;AAYA,SAAgB,gBACd,KACA,EAAE,IAAI,IAAI,IAAI,MACd,EAAE,OAAO,WAAW,UAAU,YAAY,YACpC;CACN,MAAM,OAAO,KAAK,IAAI,IAAI,EAAE;CAC5B,MAAM,OAAO,KAAK,IAAI,IAAI,EAAE;CAC5B,MAAM,QAAQ,KAAK,IAAI,KAAK,EAAE;CAC9B,MAAM,QAAQ,KAAK,IAAI,KAAK,EAAE;CAK9B,MAAM,QAAQ,KAAK;CACnB,MAAM,QAAQ,KAAK;CAEnB,IAAI,MAAM,SAAS,IAAI,KAAK,UAAU,MAAM;EAC1C,GAAG,OAAO,IAAI;EACd,GAAG,OAAO,IAAI;EACd,GAAG,OAAO,KAAK;EACf,GAAG,OAAO,KAAK;EACf;EACA;EACA,MAAM;GACJ,OAAO,SAAS;GAChB,OAAO,cAAc,KAAA,IAAY,OAAO,SAAS,IAAI;GACrD;GACA,gBAAgB,iBAAiB,UAAU;GAC3C,cAAc,iBAAiB,QAAQ;EACzC;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"straightLine.js","names":[],"sources":["../../../src/renderPptx/utils/straightLine.ts"],"sourcesContent":["/**\n * 2 点間の直線を pptxgenjs の line shape に変換する共通描画処理。\n *\n * pptxgenjs の line shape は左上座標 (x, y) + サイズ (w, h) で表現され、\n * 線の向き (始点→終点) は flipH / flipV で表すため、端点座標からの\n * 変換ロジックを Line / Arrow ノードで共有する。\n */\nimport type { LineArrow, LineNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { asEmu } from \"@pptx-glimpse/document\";\nimport { pxToEmu } from \"../units.ts\";\nimport {\n addGlimpseShape,\n arrowEndpoint,\n createShapeBoundsInput,\n noneShapeFill,\n solidShapeFill,\n} from \"./glimpseShape.ts\";\n\n/**\n * boolean | LineArrowOptions から pptxgenjs の arrow type を取得\n */\nexport function resolveArrowType(\n arrow: LineArrow | undefined,\n): \"none\" | \"arrow\" | \"diamond\" | \"oval\" | \"stealth\" | \"triangle\" | undefined {\n if (arrow === undefined) {\n return undefined;\n }\n if (arrow === false) {\n return \"none\";\n }\n if (arrow === true) {\n return \"triangle\"; // デフォルト\n }\n return arrow.type ?? \"triangle\";\n}\n\ntype StraightLinePoints = { x1: number; y1: number; x2: number; y2: number };\n\ntype StraightLineStyle = Pick<\n LineNode,\n \"color\" | \"lineWidth\" | \"dashType\" | \"beginArrow\" | \"endArrow\"\n> & {\n lineGradient?: string;\n lineGradientOpacity?: number;\n};\n\n/**\n * 始点 (x1, y1) から終点 (x2, y2) への直線を描画する\n */\nexport function addStraightLine(\n ctx: RenderContext,\n { x1, y1, x2, y2 }: StraightLinePoints,\n {\n color,\n lineWidth,\n dashType,\n beginArrow,\n endArrow,\n lineGradient,\n lineGradientOpacity,\n }: StraightLineStyle,\n): void {\n const minX = Math.min(x1, x2);\n const minY = Math.min(y1, y2);\n const lineW = Math.abs(x2 - x1);\n const lineH = Math.abs(y2 - y1);\n\n // 線の方向を判定して flip を決定\n // flipH: 右から左へ向かう線\n // flipV: 下から上へ向かう線\n const flipH = x2 < x1;\n const flipV = y2 < y1;\n\n addGlimpseShape(\n ctx,\n {\n preset: \"line\",\n ...createShapeBoundsInput({ x: minX, y: minY, w: lineW, h: lineH }),\n fill: noneShapeFill(),\n outline: {\n fill: solidShapeFill(color ?? \"000000\"),\n width:\n lineWidth !== undefined\n ? asEmu(Math.round(pxToEmu(lineWidth)))\n : asEmu(12700),\n dash: dashType === \"lgDashDotDot\" ? undefined : dashType,\n headEnd: arrowEndpoint(resolveArrowType(beginArrow)),\n tailEnd: arrowEndpoint(resolveArrowType(endArrow)),\n },\n },\n { x: minX, y: minY, w: lineW, h: lineH },\n {\n flipH,\n flipV,\n dashType,\n outlineGradient: lineGradient,\n outlineOpacity: lineGradientOpacity,\n },\n );\n}\n"],"mappings":";;;;;;;AAsBA,SAAgB,iBACd,OAC4E;CAC5E,IAAI,UAAU,KAAA,GACZ;CAEF,IAAI,UAAU,OACZ,OAAO;CAET,IAAI,UAAU,MACZ,OAAO;CAET,OAAO,MAAM,QAAQ;AACvB;;;;AAeA,SAAgB,gBACd,KACA,EAAE,IAAI,IAAI,IAAI,MACd,EACE,OACA,WACA,UACA,YACA,UACA,cACA,uBAEI;CACN,MAAM,OAAO,KAAK,IAAI,IAAI,EAAE;CAC5B,MAAM,OAAO,KAAK,IAAI,IAAI,EAAE;CAC5B,MAAM,QAAQ,KAAK,IAAI,KAAK,EAAE;CAC9B,MAAM,QAAQ,KAAK,IAAI,KAAK,EAAE;CAK9B,MAAM,QAAQ,KAAK;CACnB,MAAM,QAAQ,KAAK;CAEnB,gBACE,KACA;EACE,QAAQ;EACR,GAAG,uBAAuB;GAAE,GAAG;GAAM,GAAG;GAAM,GAAG;GAAO,GAAG;EAAM,CAAC;EAClE,MAAM,cAAc;EACpB,SAAS;GACP,MAAM,eAAe,SAAS,QAAQ;GACtC,OACE,cAAc,KAAA,IACV,MAAM,KAAK,MAAM,QAAQ,SAAS,CAAC,CAAC,IACpC,MAAM,KAAK;GACjB,MAAM,aAAa,iBAAiB,KAAA,IAAY;GAChD,SAAS,cAAc,iBAAiB,UAAU,CAAC;GACnD,SAAS,cAAc,iBAAiB,QAAQ,CAAC;EACnD;CACF,GACA;EAAE,GAAG;EAAM,GAAG;EAAM,GAAG;EAAO,GAAG;CAAM,GACvC;EACE;EACA;EACA;EACA,iBAAiB;EACjB,gBAAgB;CAClB,CACF;AACF"}
@@ -78,29 +78,6 @@ function convertBorderLine(border, fallbackColor) {
78
78
  };
79
79
  }
80
80
  /**
81
- * ノードの opacity (0-1、1 = 不透明) を pptxgenjs の
82
- * transparency (0-100、100 = 透明) に変換する
83
- */
84
- function opacityToTransparency(opacity) {
85
- return opacity !== void 0 ? (1 - opacity) * 100 : void 0;
86
- }
87
- /**
88
- * backgroundColor / opacity / グラデーションマーカー色から
89
- * pptxgenjs の fill オプションを解決する
90
- *
91
- * gradientMarker がある場合はマーカー色の単色塗りとして描画し、
92
- * 出力時の後処理で gradFill に置換される (gradientFills.ts 参照)。
93
- * opacity はマーカー側ではなく gradFill のカラーストップの alpha で
94
- * 表現するため、マーカー fill には transparency を付与しない。
95
- */
96
- function resolveBackgroundFill(backgroundColor, opacity, gradientMarker) {
97
- if (gradientMarker) return { color: gradientMarker };
98
- return {
99
- color: backgroundColor,
100
- transparency: opacityToTransparency(opacity)
101
- };
102
- }
103
- /**
104
81
  * borderRadius (px) をノードサイズに対する 0-1 の正規化値
105
82
  * (pptxgenjs roundRect の rectRadius) に変換する
106
83
  */
@@ -108,6 +85,6 @@ function resolveRectRadius(borderRadius, w, h) {
108
85
  return borderRadius ? Math.min(borderRadius / Math.min(w, h) * 2, 1) : void 0;
109
86
  }
110
87
  //#endregion
111
- export { BORDER_SIDES, convertBorderLine, convertShadow, hasVisibleBorder, opacityToTransparency, resolveBackgroundFill, resolvePerSideBorders, resolveRectRadius, stripHash };
88
+ export { BORDER_SIDES, convertBorderLine, convertShadow, hasVisibleBorder, resolvePerSideBorders, resolveRectRadius, stripHash };
112
89
 
113
90
  //# sourceMappingURL=visualStyle.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"visualStyle.js","names":[],"sources":["../../../src/renderPptx/utils/visualStyle.ts"],"sourcesContent":["/**\n * 見た目属性 (fill / border / shadow / opacity / borderRadius) を\n * pptxgenjs のオプションへ変換する共通 resolver 群。\n *\n * ノードごとに変換ロジックが重複・乖離すると、XML 上は同じ指定でも\n * 描画結果が異なるリスクがあるため、属性値 → pptxgenjs オプションの\n * 純粋変換をここに集約する。\n *\n * 共通化対象の属性カテゴリ:\n * - fill (背景色 + opacity): resolveBackgroundFill / opacityToTransparency\n * - border / line (枠線): hasVisibleBorder / convertBorderLine\n * - shadow (影): convertShadow\n * - borderRadius (角丸): resolveRectRadius\n *\n * 既存 helper との責務境界:\n * - gradientFills.ts: backgroundGradient のマーカー登録と出力時の gradFill 置換。\n * グラデーションはマーカー色 1 色の単色塗りに帰着するため本モジュールでは\n * 解釈せず、マーカー色を resolveBackgroundFill で単色 fill として扱う。\n * - utils/backgroundBorder.ts: 背景色 → 背景画像 → ボーダーの描画順序の\n * オーケストレーション。個々の属性値の変換は本モジュールに委譲する。\n * - textOptions.ts: テキスト系属性 (font / underline / strike など) の変換。\n */\nimport type { BorderStyle, ShadowStyle } from \"../../types.ts\";\nimport { pxToPt } from \"../units.ts\";\n\n/**\n * 色文字列から \"#\" を 1 つ取り除き、pptxgenjs が受け付ける HEX 文字列にする。\n * undefined はそのまま返すため `stripHash(color) ?? fallback` の形で使える。\n */\nexport function stripHash(color: string | undefined): string | undefined {\n return color?.replace(\"#\", \"\");\n}\n\n/**\n * ShadowStyle を pptxgenjs の shadow オプションに変換する\n * type 未指定時は outer をデフォルトとする\n */\nexport function convertShadow(shadow: ShadowStyle | undefined):\n | {\n type: \"outer\" | \"inner\";\n opacity?: number;\n blur?: number;\n angle?: number;\n offset?: number;\n color?: string;\n }\n | undefined {\n if (!shadow) return undefined;\n return {\n type: shadow.type ?? \"outer\",\n opacity: shadow.opacity,\n blur: shadow.blur,\n angle: shadow.angle,\n offset: shadow.offset,\n color: shadow.color,\n };\n}\n\n/**\n * border が描画対象となる指定 (color / width / dashType のいずれか) を\n * 持つかを判定する\n */\nexport function hasVisibleBorder(\n border: BorderStyle | undefined,\n): border is BorderStyle {\n return Boolean(\n border &&\n (border.color !== undefined ||\n border.width !== undefined ||\n border.dashType !== undefined),\n );\n}\n\nexport const BORDER_SIDES = [\"top\", \"right\", \"bottom\", \"left\"] as const;\nexport type BorderSide = (typeof BORDER_SIDES)[number];\n\nexport type PerSideBorders = Partial<Record<BorderSide, BorderStyle>>;\n\n/**\n * 4 辺一律の border と辺ごとの borderTop / borderRight / borderBottom /\n * borderLeft をマージし、描画対象となる辺ごとの BorderStyle を返す。\n *\n * - 辺ごとの指定が 1 つも無い場合は undefined を返し、呼び出し側は\n * 従来の 4 辺一律描画 (shape の line オプション) にフォールバックする\n * - 辺ごとの指定がある場合、各辺は border をベースに辺ごとの指定で\n * フィールド単位に上書きした BorderStyle になる (辺ごとの指定が優先)\n * - マージ結果が描画対象とならない辺 (指定なし) は結果に含まれない\n */\nexport function resolvePerSideBorders(style: {\n border?: BorderStyle;\n borderTop?: BorderStyle;\n borderRight?: BorderStyle;\n borderBottom?: BorderStyle;\n borderLeft?: BorderStyle;\n}): PerSideBorders | undefined {\n const overrides: Partial<Record<BorderSide, BorderStyle | undefined>> = {\n top: style.borderTop,\n right: style.borderRight,\n bottom: style.borderBottom,\n left: style.borderLeft,\n };\n\n const hasPerSideOverride = Object.values(overrides).some(hasVisibleBorder);\n if (!hasPerSideOverride) return undefined;\n\n const result: PerSideBorders = {};\n for (const side of BORDER_SIDES) {\n const merged = { ...style.border, ...overrides[side] };\n if (hasVisibleBorder(merged)) {\n result[side] = merged;\n }\n }\n return result;\n}\n\n/**\n * BorderStyle を pptxgenjs の line オプションに変換する\n * width はユーザー入力 px、pptxgenjs の line.width は pt\n *\n * @param fallbackColor color 未指定時に使う色。省略時は color を\n * undefined のまま渡し pptxgenjs のデフォルトに任せる\n */\nexport function convertBorderLine(\n border: BorderStyle,\n fallbackColor?: string,\n): { color?: string; width?: number; dashType?: BorderStyle[\"dashType\"] } {\n return {\n color: border.color ?? fallbackColor,\n width: border.width !== undefined ? pxToPt(border.width) : undefined,\n dashType: border.dashType,\n };\n}\n\n/**\n * ノードの opacity (0-1、1 = 不透明) を pptxgenjs の\n * transparency (0-100、100 = 透明) に変換する\n */\nexport function opacityToTransparency(\n opacity: number | undefined,\n): number | undefined {\n return opacity !== undefined ? (1 - opacity) * 100 : undefined;\n}\n\n/**\n * backgroundColor / opacity / グラデーションマーカー色から\n * pptxgenjs の fill オプションを解決する\n *\n * gradientMarker がある場合はマーカー色の単色塗りとして描画し、\n * 出力時の後処理で gradFill に置換される (gradientFills.ts 参照)。\n * opacity はマーカー側ではなく gradFill のカラーストップの alpha で\n * 表現するため、マーカー fill には transparency を付与しない。\n */\nexport function resolveBackgroundFill(\n backgroundColor: string | undefined,\n opacity: number | undefined,\n gradientMarker: string | undefined,\n): { color?: string; transparency?: number } {\n if (gradientMarker) return { color: gradientMarker };\n return {\n color: backgroundColor,\n transparency: opacityToTransparency(opacity),\n };\n}\n\n/**\n * borderRadius (px) をノードサイズに対する 0-1 の正規化値\n * (pptxgenjs roundRect の rectRadius) に変換する\n */\nexport function resolveRectRadius(\n borderRadius: number | undefined,\n w: number,\n h: number,\n): number | undefined {\n return borderRadius\n ? Math.min((borderRadius / Math.min(w, h)) * 2, 1)\n : undefined;\n}\n"],"mappings":";;;;;;AA6BA,SAAgB,UAAU,OAA+C;CACvE,OAAO,OAAO,QAAQ,KAAK,EAAE;AAC/B;;;;;AAMA,SAAgB,cAAc,QAShB;CACZ,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,OAAO;EACL,MAAM,OAAO,QAAQ;EACrB,SAAS,OAAO;EAChB,MAAM,OAAO;EACb,OAAO,OAAO;EACd,QAAQ,OAAO;EACf,OAAO,OAAO;CAChB;AACF;;;;;AAMA,SAAgB,iBACd,QACuB;CACvB,OAAO,QACL,WACC,OAAO,UAAU,KAAA,KAChB,OAAO,UAAU,KAAA,KACjB,OAAO,aAAa,KAAA,EACxB;AACF;AAEA,MAAa,eAAe;CAAC;CAAO;CAAS;CAAU;AAAM;;;;;;;;;;;AAe7D,SAAgB,sBAAsB,OAMP;CAC7B,MAAM,YAAkE;EACtE,KAAK,MAAM;EACX,OAAO,MAAM;EACb,QAAQ,MAAM;EACd,MAAM,MAAM;CACd;CAGA,IAAI,CADuB,OAAO,OAAO,SAAS,CAAC,CAAC,KAAK,gBACnC,GAAG,OAAO,KAAA;CAEhC,MAAM,SAAyB,CAAC;CAChC,KAAK,MAAM,QAAQ,cAAc;EAC/B,MAAM,SAAS;GAAE,GAAG,MAAM;GAAQ,GAAG,UAAU;EAAM;EACrD,IAAI,iBAAiB,MAAM,GACzB,OAAO,QAAQ;CAEnB;CACA,OAAO;AACT;;;;;;;;AASA,SAAgB,kBACd,QACA,eACwE;CACxE,OAAO;EACL,OAAO,OAAO,SAAS;EACvB,OAAO,OAAO,UAAU,KAAA,IAAY,OAAO,OAAO,KAAK,IAAI,KAAA;EAC3D,UAAU,OAAO;CACnB;AACF;;;;;AAMA,SAAgB,sBACd,SACoB;CACpB,OAAO,YAAY,KAAA,KAAa,IAAI,WAAW,MAAM,KAAA;AACvD;;;;;;;;;;AAWA,SAAgB,sBACd,iBACA,SACA,gBAC2C;CAC3C,IAAI,gBAAgB,OAAO,EAAE,OAAO,eAAe;CACnD,OAAO;EACL,OAAO;EACP,cAAc,sBAAsB,OAAO;CAC7C;AACF;;;;;AAMA,SAAgB,kBACd,cACA,GACA,GACoB;CACpB,OAAO,eACH,KAAK,IAAK,eAAe,KAAK,IAAI,GAAG,CAAC,IAAK,GAAG,CAAC,IAC/C,KAAA;AACN"}
1
+ {"version":3,"file":"visualStyle.js","names":[],"sources":["../../../src/renderPptx/utils/visualStyle.ts"],"sourcesContent":["/**\n * 見た目属性 (fill / border / shadow / opacity / borderRadius) を\n * pptxgenjs のオプションへ変換する共通 resolver 群。\n *\n * ノードごとに変換ロジックが重複・乖離すると、XML 上は同じ指定でも\n * 描画結果が異なるリスクがあるため、属性値 → pptxgenjs オプションの\n * 純粋変換をここに集約する。\n *\n * 共通化対象の属性カテゴリ:\n * - fill (背景色 + opacity): resolveBackgroundFill / opacityToTransparency\n * - border / line (枠線): hasVisibleBorder / convertBorderLine\n * - shadow (影): convertShadow\n * - borderRadius (角丸): resolveRectRadius\n *\n * 既存 helper との責務境界:\n * - utils/glimpseShape.ts: backgroundGradient writer 生成 XML native\n * gradFill として出力する。\n * - utils/backgroundBorder.ts: 背景色 → 背景画像 → ボーダーの描画順序の\n * オーケストレーション。個々の属性値の変換は本モジュールに委譲する。\n * - textOptions.ts: テキスト系属性 (font / underline / strike など) の変換。\n */\nimport type { BorderStyle, ShadowStyle } from \"../../types.ts\";\nimport { pxToPt } from \"../units.ts\";\n\n/**\n * 色文字列から \"#\" を 1 つ取り除き、pptxgenjs が受け付ける HEX 文字列にする。\n * undefined はそのまま返すため `stripHash(color) ?? fallback` の形で使える。\n */\nexport function stripHash(color: string | undefined): string | undefined {\n return color?.replace(\"#\", \"\");\n}\n\n/**\n * ShadowStyle を pptxgenjs の shadow オプションに変換する\n * type 未指定時は outer をデフォルトとする\n */\nexport function convertShadow(shadow: ShadowStyle | undefined):\n | {\n type: \"outer\" | \"inner\";\n opacity?: number;\n blur?: number;\n angle?: number;\n offset?: number;\n color?: string;\n }\n | undefined {\n if (!shadow) return undefined;\n return {\n type: shadow.type ?? \"outer\",\n opacity: shadow.opacity,\n blur: shadow.blur,\n angle: shadow.angle,\n offset: shadow.offset,\n color: shadow.color,\n };\n}\n\n/**\n * border が描画対象となる指定 (color / width / dashType のいずれか) を\n * 持つかを判定する\n */\nexport function hasVisibleBorder(\n border: BorderStyle | undefined,\n): border is BorderStyle {\n return Boolean(\n border &&\n (border.color !== undefined ||\n border.width !== undefined ||\n border.dashType !== undefined),\n );\n}\n\nexport const BORDER_SIDES = [\"top\", \"right\", \"bottom\", \"left\"] as const;\nexport type BorderSide = (typeof BORDER_SIDES)[number];\n\nexport type PerSideBorders = Partial<Record<BorderSide, BorderStyle>>;\n\n/**\n * 4 辺一律の border と辺ごとの borderTop / borderRight / borderBottom /\n * borderLeft をマージし、描画対象となる辺ごとの BorderStyle を返す。\n *\n * - 辺ごとの指定が 1 つも無い場合は undefined を返し、呼び出し側は\n * 従来の 4 辺一律描画 (shape の line オプション) にフォールバックする\n * - 辺ごとの指定がある場合、各辺は border をベースに辺ごとの指定で\n * フィールド単位に上書きした BorderStyle になる (辺ごとの指定が優先)\n * - マージ結果が描画対象とならない辺 (指定なし) は結果に含まれない\n */\nexport function resolvePerSideBorders(style: {\n border?: BorderStyle;\n borderTop?: BorderStyle;\n borderRight?: BorderStyle;\n borderBottom?: BorderStyle;\n borderLeft?: BorderStyle;\n}): PerSideBorders | undefined {\n const overrides: Partial<Record<BorderSide, BorderStyle | undefined>> = {\n top: style.borderTop,\n right: style.borderRight,\n bottom: style.borderBottom,\n left: style.borderLeft,\n };\n\n const hasPerSideOverride = Object.values(overrides).some(hasVisibleBorder);\n if (!hasPerSideOverride) return undefined;\n\n const result: PerSideBorders = {};\n for (const side of BORDER_SIDES) {\n const merged = { ...style.border, ...overrides[side] };\n if (hasVisibleBorder(merged)) {\n result[side] = merged;\n }\n }\n return result;\n}\n\n/**\n * BorderStyle を pptxgenjs の line オプションに変換する\n * width はユーザー入力 px、pptxgenjs の line.width は pt\n *\n * @param fallbackColor color 未指定時に使う色。省略時は color を\n * undefined のまま渡し pptxgenjs のデフォルトに任せる\n */\nexport function convertBorderLine(\n border: BorderStyle,\n fallbackColor?: string,\n): { color?: string; width?: number; dashType?: BorderStyle[\"dashType\"] } {\n return {\n color: border.color ?? fallbackColor,\n width: border.width !== undefined ? pxToPt(border.width) : undefined,\n dashType: border.dashType,\n };\n}\n\n/**\n * ノードの opacity (0-1、1 = 不透明) を pptxgenjs の\n * transparency (0-100、100 = 透明) に変換する\n */\nexport function opacityToTransparency(\n opacity: number | undefined,\n): number | undefined {\n return opacity !== undefined ? (1 - opacity) * 100 : undefined;\n}\n\n/**\n * backgroundColor / opacity / 互換用のグラデーション fallback 色から\n * pptxgenjs の fill オプションを解決する\n *\n * gradientMarker は旧 pptxgenjs 経路との互換 helper 用に残している。\n * writer 経路の backgroundGradient utils/glimpseShape.ts 側で native\n * gradFill に変換する。\n */\nexport function resolveBackgroundFill(\n backgroundColor: string | undefined,\n opacity: number | undefined,\n gradientMarker: string | undefined,\n): { color?: string; transparency?: number } {\n if (gradientMarker) return { color: gradientMarker };\n return {\n color: backgroundColor,\n transparency: opacityToTransparency(opacity),\n };\n}\n\n/**\n * borderRadius (px) をノードサイズに対する 0-1 の正規化値\n * (pptxgenjs roundRect の rectRadius) に変換する\n */\nexport function resolveRectRadius(\n borderRadius: number | undefined,\n w: number,\n h: number,\n): number | undefined {\n return borderRadius\n ? Math.min((borderRadius / Math.min(w, h)) * 2, 1)\n : undefined;\n}\n"],"mappings":";;;;;;AA4BA,SAAgB,UAAU,OAA+C;CACvE,OAAO,OAAO,QAAQ,KAAK,EAAE;AAC/B;;;;;AAMA,SAAgB,cAAc,QAShB;CACZ,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,OAAO;EACL,MAAM,OAAO,QAAQ;EACrB,SAAS,OAAO;EAChB,MAAM,OAAO;EACb,OAAO,OAAO;EACd,QAAQ,OAAO;EACf,OAAO,OAAO;CAChB;AACF;;;;;AAMA,SAAgB,iBACd,QACuB;CACvB,OAAO,QACL,WACC,OAAO,UAAU,KAAA,KAChB,OAAO,UAAU,KAAA,KACjB,OAAO,aAAa,KAAA,EACxB;AACF;AAEA,MAAa,eAAe;CAAC;CAAO;CAAS;CAAU;AAAM;;;;;;;;;;;AAe7D,SAAgB,sBAAsB,OAMP;CAC7B,MAAM,YAAkE;EACtE,KAAK,MAAM;EACX,OAAO,MAAM;EACb,QAAQ,MAAM;EACd,MAAM,MAAM;CACd;CAGA,IAAI,CADuB,OAAO,OAAO,SAAS,CAAC,CAAC,KAAK,gBACnC,GAAG,OAAO,KAAA;CAEhC,MAAM,SAAyB,CAAC;CAChC,KAAK,MAAM,QAAQ,cAAc;EAC/B,MAAM,SAAS;GAAE,GAAG,MAAM;GAAQ,GAAG,UAAU;EAAM;EACrD,IAAI,iBAAiB,MAAM,GACzB,OAAO,QAAQ;CAEnB;CACA,OAAO;AACT;;;;;;;;AASA,SAAgB,kBACd,QACA,eACwE;CACxE,OAAO;EACL,OAAO,OAAO,SAAS;EACvB,OAAO,OAAO,UAAU,KAAA,IAAY,OAAO,OAAO,KAAK,IAAI,KAAA;EAC3D,UAAU,OAAO;CACnB;AACF;;;;;AAoCA,SAAgB,kBACd,cACA,GACA,GACoB;CACpB,OAAO,eACH,KAAK,IAAK,eAAe,KAAK,IAAI,GAAG,CAAC,IAAK,GAAG,CAAC,IAC/C,KAAA;AACN"}
package/dist/types.js CHANGED
@@ -1,5 +1,5 @@
1
- import { parseGradient, parseLinearGradient } from "./shared/gradient.js";
2
1
  import { ICON_DATA } from "./icons/iconData.js";
2
+ import { parseGradient, parseLinearGradient } from "./shared/gradient.js";
3
3
  import { z } from "zod";
4
4
  //#region src/types.ts
5
5
  const FALLBACK_THEME_TOKENS = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hirokisakabe/pom",
3
- "version": "9.1.0",
3
+ "version": "9.1.2",
4
4
  "description": "AI-friendly PowerPoint generation with a Flexbox layout engine.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -53,8 +53,8 @@
53
53
  "@types/node": "^26.1.0",
54
54
  "@types/opentype.js": "^1.3.10",
55
55
  "@types/pngjs": "6.0.5",
56
- "@vitest/coverage-v8": "^4.1.9",
57
- "@vitest/ui": "^4.1.9",
56
+ "@vitest/coverage-v8": "^4.1.10",
57
+ "@vitest/ui": "^4.1.10",
58
58
  "dependency-cruiser": "^18.0.0",
59
59
  "lucide-static": "^1.23.0",
60
60
  "pixelmatch": "7.2.0",
@@ -64,7 +64,7 @@
64
64
  "tsx": "4.23.0"
65
65
  },
66
66
  "dependencies": {
67
- "@pptx-glimpse/document": "0.1.0",
67
+ "@pptx-glimpse/document": "0.6.0",
68
68
  "@resvg/resvg-wasm": "^2.6.2",
69
69
  "fast-xml-parser": "^5.9.3",
70
70
  "image-size": "2.0.2",
@@ -1,152 +0,0 @@
1
- import { pxToEmu } from "./units.js";
2
- //#region src/renderPptx/glowEffects.ts
3
- async function loadJSZip() {
4
- const mod = await import("jszip");
5
- return mod.default ?? mod;
6
- }
7
- const MARKER_PREFIX = "pom-glow:";
8
- /**
9
- * glow 仕様を一意なマーカー名にマップし、後処理で
10
- * `<a:effectLst><a:glow>` 要素として挿入できるようにするレジストリ
11
- */
12
- var GlowEffectRegistry = class {
13
- markerBySpec = /* @__PURE__ */ new Map();
14
- registered = [];
15
- /**
16
- * glow を登録し、`objectName` に渡すマーカー文字列を返す。
17
- * 同じ仕様の glow は同じマーカーを返す。
18
- */
19
- register(glow) {
20
- const sizePx = glow.size ?? 8;
21
- const opacity = glow.opacity ?? .75;
22
- const color = (glow.color ?? "FFFFFF").replace(/^#/, "").toUpperCase();
23
- const specKey = JSON.stringify({
24
- sizePx,
25
- opacity,
26
- color
27
- });
28
- const existing = this.markerBySpec.get(specKey);
29
- if (existing) return existing;
30
- const index = this.registered.length;
31
- const marker = `${MARKER_PREFIX}${index}`;
32
- this.markerBySpec.set(specKey, marker);
33
- this.registered.push({
34
- marker,
35
- sizePx,
36
- opacity,
37
- color
38
- });
39
- return marker;
40
- }
41
- get isEmpty() {
42
- return this.registered.length === 0;
43
- }
44
- get entries() {
45
- return this.registered;
46
- }
47
- };
48
- /**
49
- * DrawingML の `<a:glow>` 要素を構築する (effectLst で囲まない、内側だけ)
50
- *
51
- * - rad (光彩半径): px → EMU
52
- * - alpha (透明度): 0-1 → 1/1000 % (0-100000)
53
- */
54
- function buildGlowXml(entry) {
55
- const rad = Math.round(pxToEmu(entry.sizePx));
56
- const alpha = Math.round(entry.opacity * 1e5);
57
- return `<a:glow rad="${rad}"><a:srgbClr val="${entry.color}"><a:alpha val="${alpha}"/></a:srgbClr></a:glow>`;
58
- }
59
- /**
60
- * 1 つの slide XML を受け取り、登録された glow を該当 shape に適用した XML を返す
61
- *
62
- * `<p:cNvPr ... name="pom-glow:N">` を含む `<p:sp>` ブロックを正規表現で検出し、
63
- * 以下のいずれかで `<a:glow>` を挿入する:
64
- *
65
- * 1. 既に `<a:effectLst>...</a:effectLst>` が存在する場合
66
- * (= pptxgenjs が shape の `shadow` 等で同要素を生成済み):
67
- * その `effectLst` 内側の末尾 (`</a:effectLst>` の直前) に `<a:glow>` を追加する。
68
- * OOXML スキーマ上 `<p:spPr>` 内の `<a:effectLst>` は最大 1 個までで、二重に
69
- * 並べると不正になるため。
70
- * 2. 既存 `effectLst` が無い場合:
71
- * `</p:spPr>` の直前に新規 `<a:effectLst><a:glow/></a:effectLst>` を挿入する。
72
- *
73
- * pptxgenjs は cNvPr を `<p:cNvPr.../>` (self-closing) と
74
- * `<p:cNvPr...></p:cNvPr>` (open+close) のどちらの形式でも出力し得るため、
75
- * cNvPr の閉じ形式に依存せず name 属性のあとから lazy に進める。
76
- */
77
- function applyGlowToXml(xml, registry) {
78
- let result = xml;
79
- for (const entry of registry.entries) {
80
- const glowXml = buildGlowXml(entry);
81
- const re = new RegExp(`(<p:cNvPr[^>]*name="${entry.marker}"[\\s\\S]*?)(</p:spPr>)`, "g");
82
- result = result.replace(re, (_match, prefix, suffix) => {
83
- const block = prefix;
84
- const closingIdx = block.lastIndexOf("</a:effectLst>");
85
- if (closingIdx >= 0) {
86
- const before = block.substring(0, closingIdx);
87
- const after = block.substring(closingIdx);
88
- return `${before}${glowXml}${after}${suffix}`;
89
- }
90
- return `${block}<a:effectLst>${glowXml}</a:effectLst>${suffix}`;
91
- });
92
- }
93
- return result;
94
- }
95
- /**
96
- * 出力 zip 内のスライド XML に glow 効果を挿入する
97
- */
98
- async function applyGlowEffects(data, registry) {
99
- const zip = await (await loadJSZip()).loadAsync(data);
100
- const slidePaths = Object.keys(zip.files).filter((path) => /^ppt\/slides\/slide\d+\.xml$/.test(path));
101
- for (const path of slidePaths) {
102
- const file = zip.file(path);
103
- if (!file) continue;
104
- const original = await file.async("text");
105
- const replaced = applyGlowToXml(original, registry);
106
- if (replaced !== original) zip.file(path, replaced);
107
- }
108
- return zip;
109
- }
110
- /**
111
- * pptx インスタンスの write / writeFile をラップし、出力時に glow 後処理を適用する。
112
- *
113
- * 既に他の後処理 (gradientFills 等) によりラップされている場合に備え、
114
- * 元の関数を保存してチェーン可能にしている。
115
- */
116
- function patchPptxWriteForGlowEffects(pptx, registry) {
117
- if (registry.isEmpty) return;
118
- const originalWrite = pptx.write.bind(pptx);
119
- const originalWriteFile = pptx.writeFile.bind(pptx);
120
- const patchedWrite = async (rawProps) => {
121
- const props = typeof rawProps === "string" ? { outputType: rawProps } : rawProps;
122
- const zip = await applyGlowEffects(await originalWrite({ outputType: "uint8array" }), registry);
123
- const outputType = props?.outputType;
124
- if (outputType === "STREAM") return zip.generateAsync({
125
- type: "nodebuffer",
126
- compression: props?.compression ? "DEFLATE" : "STORE"
127
- });
128
- if (outputType) return zip.generateAsync({ type: outputType });
129
- return zip.generateAsync({
130
- type: "blob",
131
- compression: props?.compression ? "DEFLATE" : "STORE"
132
- });
133
- };
134
- pptx.write = patchedWrite;
135
- const patchedWriteFile = async (rawProps) => {
136
- const props = typeof rawProps === "string" ? { fileName: rawProps } : rawProps;
137
- if (!(typeof process !== "undefined" && Boolean(process.versions?.node))) return originalWriteFile(props);
138
- const rawName = props?.fileName ?? "Presentation.pptx";
139
- const fileName = rawName.toLowerCase().endsWith(".pptx") ? rawName : `${rawName}.pptx`;
140
- const buffer = await patchedWrite({
141
- outputType: "nodebuffer",
142
- compression: props?.compression
143
- });
144
- await (await import("fs")).promises.writeFile(fileName, buffer);
145
- return fileName;
146
- };
147
- pptx.writeFile = patchedWriteFile;
148
- }
149
- //#endregion
150
- export { GlowEffectRegistry, applyGlowEffects, patchPptxWriteForGlowEffects };
151
-
152
- //# sourceMappingURL=glowEffects.js.map
@@ -1 +0,0 @@
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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"}