@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
@@ -1 +1 @@
1
- {"version":3,"file":"renderPptx.js","names":[],"sources":["../../src/renderPptx/renderPptx.ts"],"sourcesContent":["// pptxgenjs の型定義\ntype PptxGenJSInstance = import(\"pptxgenjs\").default;\n\n// pptxgenjs は CJS パッケージのため動的 import で読み込む\nasync function loadPptxGenJS(): Promise<new () => PptxGenJSInstance> {\n const pptxModule = await import(\"pptxgenjs\");\n // CJS default export の解決: module.default.default (ESM wrapper) または module.default\n /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n const mod = pptxModule as any;\n return mod.default?.default ?? mod.default ?? mod;\n /* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n}\ntype SlideMasterProps = Parameters<PptxGenJSInstance[\"defineSlideMaster\"]>[0];\ntype ImageProps = {\n x: number;\n y: number;\n w: number;\n h: number;\n path?: string;\n data?: string;\n};\nimport type {\n PositionedNode,\n SlideMasterOptions,\n MasterObject,\n} from \"../types.ts\";\nimport type { BuildContext } from \"../buildContext.ts\";\nimport type { RenderContext, NodeBounds } from \"./types.ts\";\nimport { pxToIn, pxToPt } from \"./units.ts\";\nimport { convertUnderline, convertStrike } from \"./textOptions.ts\";\nimport { getImageData } from \"../shared/measureImage.ts\";\nimport { resolveBoxSpacing } from \"../shared/boxSpacing.ts\";\nimport {\n renderBackgroundAndBorder,\n renderBorderOnly,\n} from \"./utils/backgroundBorder.ts\";\nimport { registerBackgroundGradient } from \"./gradientFills.ts\";\nimport { getNodeDef } from \"../registry/index.ts\";\n\ntype SlidePx = { w: number; h: number };\n\nconst DEFAULT_MASTER_NAME = \"POM_MASTER\";\n\nfunction buildIdPositionMap(\n node: PositionedNode,\n diagnostics: import(\"../buildContext.ts\").BuildContext[\"diagnostics\"],\n): Map<string, NodeBounds> {\n const map = new Map<string, NodeBounds>();\n\n function traverse(n: PositionedNode) {\n if (n.id) {\n if (map.has(n.id)) {\n diagnostics.add(\n \"DUPLICATE_NODE_ID\",\n `Duplicate node id \"${n.id}\" — only the first occurrence will be used for Arrow references`,\n );\n } else {\n map.set(n.id, { x: n.x, y: n.y, w: n.w, h: n.h });\n }\n }\n if (n.type === \"vstack\" || n.type === \"hstack\" || n.type === \"layer\") {\n for (const child of n.children) {\n traverse(child);\n }\n }\n }\n\n traverse(node);\n return map;\n}\n\n/**\n * zIndex でソートして描画順を制御する(安定ソート)\n * zIndex が小さいノードが先に描画される(PowerPoint は追加順に重ねるため)\n */\nfunction sortByZIndex<T extends { zIndex?: number }>(children: T[]): T[] {\n // すべての子要素に zIndex が未設定の場合はそのまま返す\n if (children.every((c) => c.zIndex === undefined)) return children;\n return [...children].sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));\n}\n\n/**\n * MasterObject を pptxgenjs の objects 形式に変換する\n */\nfunction convertMasterObject(\n obj: MasterObject,\n): SlideMasterProps[\"objects\"] extends (infer T)[] | undefined ? T : never {\n switch (obj.type) {\n case \"text\":\n return {\n text: {\n text: obj.text,\n options: {\n x: pxToIn(obj.x),\n y: pxToIn(obj.y),\n w: pxToIn(obj.w),\n h: pxToIn(obj.h),\n fontSize: obj.fontSize ? pxToPt(obj.fontSize) : undefined,\n fontFace: obj.fontFamily,\n color: obj.color,\n bold: obj.bold,\n italic: obj.italic,\n underline: convertUnderline(obj.underline),\n strike: convertStrike(obj.strike),\n highlight: obj.highlight,\n align: obj.textAlign,\n },\n },\n };\n case \"image\": {\n const imageProps: ImageProps = {\n x: pxToIn(obj.x),\n y: pxToIn(obj.y),\n w: pxToIn(obj.w),\n h: pxToIn(obj.h),\n };\n // src が data URI かパスかを判定\n if (obj.src.startsWith(\"data:\")) {\n imageProps.data = obj.src;\n } else {\n imageProps.path = obj.src;\n }\n return { image: imageProps };\n }\n case \"rect\":\n return {\n rect: {\n x: pxToIn(obj.x),\n y: pxToIn(obj.y),\n w: pxToIn(obj.w),\n h: pxToIn(obj.h),\n fill: obj.fill\n ? { color: obj.fill.color, transparency: obj.fill.transparency }\n : undefined,\n line: obj.border\n ? {\n color: obj.border.color,\n width: obj.border.width,\n dashType: obj.border.dashType,\n }\n : undefined,\n },\n };\n case \"line\":\n return {\n line: {\n x: pxToIn(obj.x),\n y: pxToIn(obj.y),\n w: pxToIn(obj.w),\n h: pxToIn(obj.h),\n line: obj.line\n ? {\n color: obj.line.color,\n width: obj.line.width,\n dashType: obj.line.dashType,\n }\n : { color: \"000000\", width: 1 },\n },\n };\n }\n}\n\n/**\n * SlideMasterOptions から pptxgenjs の defineSlideMaster を呼び出す\n */\nfunction defineSlideMasterFromOptions(\n pptx: PptxGenJSInstance,\n master: SlideMasterOptions,\n): string {\n const masterName = master.title || DEFAULT_MASTER_NAME;\n\n const masterProps: SlideMasterProps = {\n title: masterName,\n };\n\n // background の変換\n if (master.background) {\n if (\"color\" in master.background) {\n masterProps.background = { color: master.background.color };\n } else if (\"path\" in master.background) {\n masterProps.background = { path: master.background.path };\n } else if (\"data\" in master.background) {\n masterProps.background = { data: master.background.data };\n } else if (\"image\" in master.background) {\n masterProps.background = { path: master.background.image };\n }\n }\n\n // margin の変換 (px -> inches)\n if (master.margin !== undefined) {\n const margin = resolveBoxSpacing(master.margin);\n masterProps.margin = [\n pxToIn(margin.top),\n pxToIn(margin.right),\n pxToIn(margin.bottom),\n pxToIn(margin.left),\n ];\n }\n\n // objects の変換\n if (master.objects && master.objects.length > 0) {\n masterProps.objects = master.objects.map((obj) => convertMasterObject(obj));\n }\n\n // slideNumber の変換\n if (master.slideNumber) {\n masterProps.slideNumber = {\n x: pxToIn(master.slideNumber.x),\n y: pxToIn(master.slideNumber.y),\n w: master.slideNumber.w ? pxToIn(master.slideNumber.w) : undefined,\n h: master.slideNumber.h ? pxToIn(master.slideNumber.h) : undefined,\n fontSize: master.slideNumber.fontSize\n ? pxToPt(master.slideNumber.fontSize)\n : undefined,\n fontFace: master.slideNumber.fontFamily,\n color: master.slideNumber.color,\n };\n }\n\n pptx.defineSlideMaster(masterProps);\n return masterName;\n}\n\n/**\n * PositionedNode ツリーを PptxGenJS スライドに変換する\n * @param pages PositionedNode ツリーの配列(各要素が1ページ)\n * @param slidePx スライド全体のサイズ(px)\n * @param master スライドマスターオプション(省略可能)\n * @returns PptxGenJS インスタンス\n */\nexport async function renderPptx(\n pages: PositionedNode[],\n slidePx: SlidePx,\n buildContext: BuildContext,\n master?: SlideMasterOptions,\n) {\n const slideIn = { w: pxToIn(slidePx.w), h: pxToIn(slidePx.h) }; // layout(=px) → PptxGenJS(=inch) への最終変換\n\n const PptxGenJS = await loadPptxGenJS();\n const pptx = new PptxGenJS();\n\n pptx.defineLayout({ name: \"custom\", width: slideIn.w, height: slideIn.h });\n pptx.layout = \"custom\";\n\n // マスターが指定されている場合、defineSlideMaster を呼び出す\n const masterName = master\n ? defineSlideMasterFromOptions(pptx, master)\n : undefined;\n\n for (const data of pages) {\n // マスターが指定されている場合は masterName を使用\n const slide = masterName ? pptx.addSlide({ masterName }) : pptx.addSlide();\n const idPositionMap = buildIdPositionMap(data, buildContext.diagnostics);\n const ctx: RenderContext = { slide, pptx, buildContext, idPositionMap };\n\n // ルートノードの backgroundColor はスライドの background プロパティとして適用\n // これにより、マスタースライドのオブジェクトを覆い隠さない\n // line/arrow ノードは backgroundColor を持たないためスキップ\n // ただし opacity が指定されている場合は slide.background では透過を表現できないため、\n // renderBackgroundAndBorder で描画する\n const isLinelike = data.type === \"line\" || data.type === \"arrow\";\n const rootBackgroundColor = !isLinelike ? data.backgroundColor : undefined;\n const rootBackgroundGradient = !isLinelike\n ? data.backgroundGradient\n : undefined;\n const rootHasOpacity =\n !isLinelike && \"opacity\" in data && data.opacity !== undefined;\n // backgroundGradient はマーカー色で slide.background に適用し、\n // 出力時の後処理で gradFill に置換される (gradientFills.ts 参照)\n const rootGradientMarker =\n rootBackgroundGradient && !rootHasOpacity\n ? registerBackgroundGradient(\n rootBackgroundGradient,\n undefined,\n buildContext.gradientFills,\n )\n : undefined;\n if (rootGradientMarker) {\n slide.background = { color: rootGradientMarker };\n } else if (rootBackgroundColor && !rootHasOpacity) {\n slide.background = { color: rootBackgroundColor };\n }\n\n // ルートノードの backgroundImage はスライドの background プロパティとして適用\n // backgroundColor と backgroundImage の両方がある場合、backgroundImage が優先\n const rootBackgroundImage = !isLinelike ? data.backgroundImage : undefined;\n if (rootBackgroundImage) {\n const cachedData = getImageData(\n rootBackgroundImage.src,\n buildContext.imageDataCache,\n );\n if (cachedData) {\n slide.background = { data: cachedData };\n } else {\n slide.background = { path: rootBackgroundImage.src };\n }\n }\n\n /**\n * node をスライドにレンダリングする\n * @param isRoot ルートノードかどうか(ルートノードの background は slide.background で処理済み)\n */\n function renderNode(node: PositionedNode, isRoot = false) {\n // line/arrow ノードは backgroundColor/border を持たないため、background/border の描画をスキップ\n if (node.type !== \"line\" && node.type !== \"arrow\") {\n // ルートノードの backgroundColor/backgroundImage は既に slide.background に適用済みなのでスキップ\n // ただし opacity がある場合は slide.background では透過を表現できないため通常描画\n if (\n isRoot &&\n (rootBackgroundImage ||\n ((rootBackgroundColor || rootBackgroundGradient) &&\n !rootHasOpacity))\n ) {\n // border のみ描画(backgroundColor/backgroundImage はスキップ)\n renderBorderOnly(node, ctx);\n } else {\n renderBackgroundAndBorder(node, ctx);\n }\n }\n\n const def = getNodeDef(node.type);\n\n switch (def.category) {\n case \"leaf\":\n if (!def.render) {\n throw new Error(\n `No render function registered for leaf node: ${node.type}`,\n );\n }\n def.render(node, ctx);\n break;\n\n case \"multi-child\":\n case \"absolute-child\": {\n const containerNode = node as Extract<\n PositionedNode,\n { type: \"vstack\" | \"hstack\" | \"layer\" }\n >;\n // zIndex でソートして描画順を制御(値が小さいものが先に描画される)\n for (const child of sortByZIndex(containerNode.children)) {\n renderNode(child);\n }\n break;\n }\n }\n }\n\n renderNode(data, true); // ルートノードとして処理\n }\n\n return pptx;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"renderPptx.js","names":[],"sources":["../../src/renderPptx/renderPptx.ts"],"sourcesContent":["// pptxgenjs の型定義\ntype PptxGenJSInstance = import(\"pptxgenjs\").default;\n\n// pptxgenjs は CJS パッケージのため動的 import で読み込む\nasync function loadPptxGenJS(): Promise<new () => PptxGenJSInstance> {\n const pptxModule = await import(\"pptxgenjs\");\n // CJS default export の解決: module.default.default (ESM wrapper) または module.default\n /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n const mod = pptxModule as any;\n return mod.default?.default ?? mod.default ?? mod;\n /* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */\n}\ntype SlideMasterProps = Parameters<PptxGenJSInstance[\"defineSlideMaster\"]>[0];\ntype ImageProps = {\n x: number;\n y: number;\n w: number;\n h: number;\n path?: string;\n data?: string;\n};\nimport type {\n PositionedNode,\n SlideMasterOptions,\n MasterObject,\n} from \"../types.ts\";\nimport type { BuildContext } from \"../buildContext.ts\";\nimport type { RenderContext, NodeBounds } from \"./types.ts\";\nimport { pxToIn, pxToPt } from \"./units.ts\";\nimport { convertUnderline, convertStrike } from \"./textOptions.ts\";\nimport { getImageData } from \"../shared/measureImage.ts\";\nimport { resolveBoxSpacing } from \"../shared/boxSpacing.ts\";\nimport {\n renderBackgroundAndBorder,\n renderBorderOnly,\n} from \"./utils/backgroundBorder.ts\";\nimport { getNodeDef } from \"../registry/index.ts\";\n\ntype SlidePx = { w: number; h: number };\n\nconst DEFAULT_MASTER_NAME = \"POM_MASTER\";\n\nfunction buildIdPositionMap(\n node: PositionedNode,\n diagnostics: import(\"../buildContext.ts\").BuildContext[\"diagnostics\"],\n): Map<string, NodeBounds> {\n const map = new Map<string, NodeBounds>();\n\n function traverse(n: PositionedNode) {\n if (n.id) {\n if (map.has(n.id)) {\n diagnostics.add(\n \"DUPLICATE_NODE_ID\",\n `Duplicate node id \"${n.id}\" — only the first occurrence will be used for Arrow references`,\n );\n } else {\n map.set(n.id, { x: n.x, y: n.y, w: n.w, h: n.h });\n }\n }\n if (n.type === \"vstack\" || n.type === \"hstack\" || n.type === \"layer\") {\n for (const child of n.children) {\n traverse(child);\n }\n }\n }\n\n traverse(node);\n return map;\n}\n\n/**\n * zIndex でソートして描画順を制御する(安定ソート)\n * zIndex が小さいノードが先に描画される(PowerPoint は追加順に重ねるため)\n */\nfunction sortByZIndex<T extends { zIndex?: number }>(children: T[]): T[] {\n // すべての子要素に zIndex が未設定の場合はそのまま返す\n if (children.every((c) => c.zIndex === undefined)) return children;\n return [...children].sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));\n}\n\n/**\n * MasterObject を pptxgenjs の objects 形式に変換する\n */\nfunction convertMasterObject(\n obj: MasterObject,\n): SlideMasterProps[\"objects\"] extends (infer T)[] | undefined ? T : never {\n switch (obj.type) {\n case \"text\":\n return {\n text: {\n text: obj.text,\n options: {\n x: pxToIn(obj.x),\n y: pxToIn(obj.y),\n w: pxToIn(obj.w),\n h: pxToIn(obj.h),\n fontSize: obj.fontSize ? pxToPt(obj.fontSize) : undefined,\n fontFace: obj.fontFamily,\n color: obj.color,\n bold: obj.bold,\n italic: obj.italic,\n underline: convertUnderline(obj.underline),\n strike: convertStrike(obj.strike),\n highlight: obj.highlight,\n align: obj.textAlign,\n },\n },\n };\n case \"image\": {\n const imageProps: ImageProps = {\n x: pxToIn(obj.x),\n y: pxToIn(obj.y),\n w: pxToIn(obj.w),\n h: pxToIn(obj.h),\n };\n // src が data URI かパスかを判定\n if (obj.src.startsWith(\"data:\")) {\n imageProps.data = obj.src;\n } else {\n imageProps.path = obj.src;\n }\n return { image: imageProps };\n }\n case \"rect\":\n return {\n rect: {\n x: pxToIn(obj.x),\n y: pxToIn(obj.y),\n w: pxToIn(obj.w),\n h: pxToIn(obj.h),\n fill: obj.fill\n ? { color: obj.fill.color, transparency: obj.fill.transparency }\n : undefined,\n line: obj.border\n ? {\n color: obj.border.color,\n width: obj.border.width,\n dashType: obj.border.dashType,\n }\n : undefined,\n },\n };\n case \"line\":\n return {\n line: {\n x: pxToIn(obj.x),\n y: pxToIn(obj.y),\n w: pxToIn(obj.w),\n h: pxToIn(obj.h),\n line: obj.line\n ? {\n color: obj.line.color,\n width: obj.line.width,\n dashType: obj.line.dashType,\n }\n : { color: \"000000\", width: 1 },\n },\n };\n }\n}\n\n/**\n * SlideMasterOptions から pptxgenjs の defineSlideMaster を呼び出す\n */\nfunction defineSlideMasterFromOptions(\n pptx: PptxGenJSInstance,\n master: SlideMasterOptions,\n): string {\n const masterName = master.title || DEFAULT_MASTER_NAME;\n\n const masterProps: SlideMasterProps = {\n title: masterName,\n };\n\n // background の変換\n if (master.background) {\n if (\"color\" in master.background) {\n masterProps.background = { color: master.background.color };\n } else if (\"path\" in master.background) {\n masterProps.background = { path: master.background.path };\n } else if (\"data\" in master.background) {\n masterProps.background = { data: master.background.data };\n } else if (\"image\" in master.background) {\n masterProps.background = { path: master.background.image };\n }\n }\n\n // margin の変換 (px -> inches)\n if (master.margin !== undefined) {\n const margin = resolveBoxSpacing(master.margin);\n masterProps.margin = [\n pxToIn(margin.top),\n pxToIn(margin.right),\n pxToIn(margin.bottom),\n pxToIn(margin.left),\n ];\n }\n\n // objects の変換\n if (master.objects && master.objects.length > 0) {\n masterProps.objects = master.objects.map((obj) => convertMasterObject(obj));\n }\n\n // slideNumber の変換\n if (master.slideNumber) {\n masterProps.slideNumber = {\n x: pxToIn(master.slideNumber.x),\n y: pxToIn(master.slideNumber.y),\n w: master.slideNumber.w ? pxToIn(master.slideNumber.w) : undefined,\n h: master.slideNumber.h ? pxToIn(master.slideNumber.h) : undefined,\n fontSize: master.slideNumber.fontSize\n ? pxToPt(master.slideNumber.fontSize)\n : undefined,\n fontFace: master.slideNumber.fontFamily,\n color: master.slideNumber.color,\n };\n }\n\n pptx.defineSlideMaster(masterProps);\n return masterName;\n}\n\n/**\n * PositionedNode ツリーを PptxGenJS スライドに変換する\n * @param pages PositionedNode ツリーの配列(各要素が1ページ)\n * @param slidePx スライド全体のサイズ(px)\n * @param master スライドマスターオプション(省略可能)\n * @returns PptxGenJS インスタンス\n */\nexport async function renderPptx(\n pages: PositionedNode[],\n slidePx: SlidePx,\n buildContext: BuildContext,\n master?: SlideMasterOptions,\n) {\n const slideIn = { w: pxToIn(slidePx.w), h: pxToIn(slidePx.h) }; // layout(=px) → PptxGenJS(=inch) への最終変換\n\n const PptxGenJS = await loadPptxGenJS();\n const pptx = new PptxGenJS();\n\n pptx.defineLayout({ name: \"custom\", width: slideIn.w, height: slideIn.h });\n pptx.layout = \"custom\";\n\n // マスターが指定されている場合、defineSlideMaster を呼び出す\n const masterName = master\n ? defineSlideMasterFromOptions(pptx, master)\n : undefined;\n\n for (const [pageIndex, data] of pages.entries()) {\n // マスターが指定されている場合は masterName を使用\n const slide = masterName ? pptx.addSlide({ masterName }) : pptx.addSlide();\n const idPositionMap = buildIdPositionMap(data, buildContext.diagnostics);\n const ctx: RenderContext = { slide, pptx, buildContext, idPositionMap };\n\n // ルートノードの backgroundColor はスライドの background プロパティとして適用\n // これにより、マスタースライドのオブジェクトを覆い隠さない\n // line/arrow ノードは backgroundColor を持たないためスキップ\n // ただし opacity が指定されている場合は slide.background では透過を表現できないため、\n // renderBackgroundAndBorder で描画する\n const isLinelike = data.type === \"line\" || data.type === \"arrow\";\n const rootBackgroundColor = !isLinelike ? data.backgroundColor : undefined;\n const rootBackgroundGradient = !isLinelike\n ? data.backgroundGradient\n : undefined;\n const rootHasOpacity =\n !isLinelike && \"opacity\" in data && data.opacity !== undefined;\n const rootGradientMarker =\n rootBackgroundGradient && !rootHasOpacity\n ? buildContext.glimpseTextBoxes.registerSlideBackgroundGradient(\n rootBackgroundGradient,\n pageIndex + 1,\n )\n : undefined;\n if (rootGradientMarker) {\n slide.background = { color: rootGradientMarker };\n } else if (\n rootBackgroundColor &&\n !rootBackgroundGradient &&\n !rootHasOpacity\n ) {\n slide.background = { color: rootBackgroundColor };\n }\n\n // ルートノードの backgroundImage はスライドの background プロパティとして適用\n // backgroundColor と backgroundImage の両方がある場合、backgroundImage が優先\n const rootBackgroundImage = !isLinelike ? data.backgroundImage : undefined;\n if (rootBackgroundImage) {\n const cachedData = getImageData(\n rootBackgroundImage.src,\n buildContext.imageDataCache,\n );\n if (cachedData) {\n slide.background = { data: cachedData };\n } else {\n slide.background = { path: rootBackgroundImage.src };\n }\n }\n\n /**\n * node をスライドにレンダリングする\n * @param isRoot ルートノードかどうか(ルートノードの background は slide.background で処理済み)\n */\n function renderNode(node: PositionedNode, isRoot = false) {\n // line/arrow ノードは backgroundColor/border を持たないため、background/border の描画をスキップ\n if (node.type !== \"line\" && node.type !== \"arrow\") {\n // ルートノードの backgroundColor/backgroundImage は既に slide.background に適用済みなのでスキップ\n // ただし opacity がある場合は slide.background では透過を表現できないため通常描画\n if (\n isRoot &&\n (rootBackgroundImage ||\n ((rootBackgroundColor || rootGradientMarker) && !rootHasOpacity))\n ) {\n // border のみ描画(backgroundColor/backgroundImage はスキップ)\n renderBorderOnly(node, ctx);\n } else {\n renderBackgroundAndBorder(node, ctx);\n }\n }\n\n const def = getNodeDef(node.type);\n\n switch (def.category) {\n case \"leaf\":\n if (!def.render) {\n throw new Error(\n `No render function registered for leaf node: ${node.type}`,\n );\n }\n def.render(node, ctx);\n break;\n\n case \"multi-child\":\n case \"absolute-child\": {\n const containerNode = node as Extract<\n PositionedNode,\n { type: \"vstack\" | \"hstack\" | \"layer\" }\n >;\n // zIndex でソートして描画順を制御(値が小さいものが先に描画される)\n for (const child of sortByZIndex(containerNode.children)) {\n renderNode(child);\n }\n break;\n }\n }\n }\n\n renderNode(data, true); // ルートノードとして処理\n }\n\n return pptx;\n}\n"],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import { getImageData } from "../../shared/measureImage.js";
2
- import { pxToIn, rectPxToIn } from "../units.js";
3
- import { registerBackgroundGradient } from "../gradientFills.js";
4
- import { BORDER_SIDES, convertBorderLine, convertShadow, hasVisibleBorder, resolveBackgroundFill, resolvePerSideBorders, resolveRectRadius } from "./visualStyle.js";
2
+ import { rectPxToIn } from "../units.js";
3
+ import { addGlimpsePicture, imageBytesFromSource } from "./glimpsePicture.js";
4
+ import { addGlimpseShape, backgroundShapeFill, createShapeBoundsInput, noShapeOutline, noneShapeFill, shapeOutline } from "./glimpseShape.js";
5
+ import { BORDER_SIDES, convertBorderLine, convertShadow, hasVisibleBorder, resolvePerSideBorders, resolveRectRadius } from "./visualStyle.js";
5
6
  //#region src/renderPptx/utils/backgroundBorder.ts
6
7
  /**
7
8
  * ノードの背景色・背景画像・ボーダー・影を描画する
@@ -11,63 +12,94 @@ import { BORDER_SIDES, convertBorderLine, convertShadow, hasVisibleBorder, resol
11
12
  */
12
13
  function renderBackgroundAndBorder(node, ctx) {
13
14
  const { backgroundColor, backgroundGradient, backgroundImage, border, borderRadius, shadow } = node;
14
- const gradientMarker = backgroundGradient ? registerBackgroundGradient(backgroundGradient, node.opacity, ctx.buildContext.gradientFills) : void 0;
15
15
  const perSideBorders = resolveEffectivePerSideBorders(node, ctx);
16
- const hasBackground = Boolean(backgroundColor) || Boolean(gradientMarker);
16
+ const hasBackground = Boolean(backgroundColor) || Boolean(backgroundGradient);
17
17
  const hasBackgroundImage = Boolean(backgroundImage);
18
18
  const hasUniformBorder = !perSideBorders && hasVisibleBorder(border);
19
19
  const hasShadow = Boolean(shadow);
20
20
  if (!hasBackground && !hasBackgroundImage && !hasUniformBorder && !perSideBorders && !hasShadow) return;
21
- const shapeType = borderRadius ? ctx.pptx.ShapeType.roundRect : ctx.pptx.ShapeType.rect;
21
+ const shapeType = borderRadius ? "roundRect" : "rect";
22
+ const legacyShapeType = borderRadius ? ctx.pptx.ShapeType.roundRect : ctx.pptx.ShapeType.rect;
22
23
  const rectRadius = resolveRectRadius(borderRadius, node.w, node.h);
23
24
  if (!hasBackgroundImage) {
24
25
  if (hasBackground || hasUniformBorder || hasShadow) {
25
- const fill = hasBackground ? resolveBackgroundFill(backgroundColor, node.opacity, gradientMarker) : { type: "none" };
26
- const line = hasUniformBorder ? convertBorderLine(border, "000000") : { type: "none" };
27
- ctx.slide.addShape(shapeType, {
28
- ...rectPxToIn(node),
26
+ if (hasShadow && !backgroundGradient) {
27
+ ctx.slide.addShape(legacyShapeType, {
28
+ ...rectPxToIn(node),
29
+ fill: hasBackground ? {
30
+ color: backgroundColor,
31
+ transparency: node.opacity !== void 0 ? (1 - node.opacity) * 100 : void 0
32
+ } : { type: "none" },
33
+ line: hasUniformBorder ? convertBorderLine(border, "000000") : { type: "none" },
34
+ rectRadius,
35
+ shadow: convertShadow(shadow)
36
+ });
37
+ renderPerSideBorderLines(node, perSideBorders, ctx);
38
+ return;
39
+ }
40
+ const fill = hasBackground ? backgroundShapeFill(backgroundColor, backgroundGradient) : noneShapeFill();
41
+ const line = hasUniformBorder ? shapeOutline(border, "000000") : noShapeOutline();
42
+ addGlimpseShape(ctx, {
43
+ preset: shapeType,
44
+ ...createShapeBoundsInput(node),
29
45
  fill,
30
- line,
46
+ outline: line
47
+ }, node, {
48
+ fillColor: backgroundColor,
49
+ fillOpacity: node.opacity,
50
+ backgroundGradient,
51
+ shadow,
31
52
  rectRadius,
32
- shadow: convertShadow(shadow)
53
+ dashType: border?.dashType
33
54
  });
34
55
  }
35
56
  renderPerSideBorderLines(node, perSideBorders, ctx);
36
57
  return;
37
58
  }
38
- if (hasBackground) ctx.slide.addShape(shapeType, {
39
- ...rectPxToIn(node),
40
- fill: resolveBackgroundFill(backgroundColor, node.opacity, gradientMarker),
41
- line: { type: "none" },
42
- rectRadius
59
+ if (hasBackground) addGlimpseShape(ctx, {
60
+ preset: shapeType,
61
+ ...createShapeBoundsInput(node),
62
+ fill: backgroundShapeFill(backgroundColor, backgroundGradient),
63
+ outline: noShapeOutline()
64
+ }, node, {
65
+ fillColor: backgroundColor,
66
+ fillOpacity: node.opacity,
67
+ backgroundGradient,
68
+ rectRadius,
69
+ dashType: border?.dashType
43
70
  });
44
71
  if (backgroundImage) {
45
72
  const sizing = backgroundImage.sizing ?? "cover";
46
- const imageOptions = {
47
- ...rectPxToIn(node),
48
- sizing: {
49
- type: sizing,
50
- w: pxToIn(node.w),
51
- h: pxToIn(node.h)
52
- }
53
- };
54
73
  const cachedData = getImageData(backgroundImage.src, ctx.buildContext.imageDataCache);
55
- if (cachedData) ctx.slide.addImage({
56
- ...imageOptions,
57
- data: cachedData
58
- });
59
- else ctx.slide.addImage({
60
- ...imageOptions,
61
- path: backgroundImage.src
74
+ addGlimpsePicture(ctx, node, imageBytesFromSource(backgroundImage.src, cachedData), { sizing: {
75
+ type: sizing,
76
+ w: node.w,
77
+ h: node.h
78
+ } });
79
+ }
80
+ if (hasUniformBorder || hasShadow) {
81
+ if (hasShadow) {
82
+ ctx.slide.addShape(legacyShapeType, {
83
+ ...rectPxToIn(node),
84
+ fill: { type: "none" },
85
+ line: hasUniformBorder ? convertBorderLine(border, "000000") : { type: "none" },
86
+ rectRadius,
87
+ shadow: convertShadow(shadow)
88
+ });
89
+ renderPerSideBorderLines(node, perSideBorders, ctx);
90
+ return;
91
+ }
92
+ addGlimpseShape(ctx, {
93
+ preset: shapeType,
94
+ ...createShapeBoundsInput(node),
95
+ fill: noneShapeFill(),
96
+ outline: hasUniformBorder ? shapeOutline(border, "000000") : noShapeOutline()
97
+ }, node, {
98
+ shadow,
99
+ rectRadius,
100
+ dashType: border?.dashType
62
101
  });
63
102
  }
64
- if (hasUniformBorder || hasShadow) ctx.slide.addShape(shapeType, {
65
- ...rectPxToIn(node),
66
- fill: { type: "none" },
67
- line: hasUniformBorder ? convertBorderLine(border, "000000") : { type: "none" },
68
- rectRadius,
69
- shadow: convertShadow(shadow)
70
- });
71
103
  renderPerSideBorderLines(node, perSideBorders, ctx);
72
104
  }
73
105
  /**
@@ -96,12 +128,14 @@ function renderBorderOnly(node, ctx) {
96
128
  return;
97
129
  }
98
130
  if (!hasVisibleBorder(border)) return;
99
- const shapeType = borderRadius ? ctx.pptx.ShapeType.roundRect : ctx.pptx.ShapeType.rect;
100
- ctx.slide.addShape(shapeType, {
101
- ...rectPxToIn(node),
102
- fill: { type: "none" },
103
- line: convertBorderLine(border, "000000"),
104
- rectRadius: resolveRectRadius(borderRadius, node.w, node.h)
131
+ addGlimpseShape(ctx, {
132
+ preset: borderRadius ? "roundRect" : "rect",
133
+ ...createShapeBoundsInput(node),
134
+ fill: noneShapeFill(),
135
+ outline: shapeOutline(border, "000000")
136
+ }, node, {
137
+ rectRadius: resolveRectRadius(borderRadius, node.w, node.h),
138
+ dashType: border.dashType
105
139
  });
106
140
  }
107
141
  /**
@@ -140,10 +174,12 @@ function renderPerSideBorderLines(node, perSideBorders, ctx) {
140
174
  for (const side of BORDER_SIDES) {
141
175
  const style = perSideBorders[side];
142
176
  if (!style) continue;
143
- ctx.slide.addShape(ctx.pptx.ShapeType.line, {
144
- ...rectPxToIn(edges[side]),
145
- line: convertBorderLine(style, "000000")
146
- });
177
+ addGlimpseShape(ctx, {
178
+ preset: "line",
179
+ ...createShapeBoundsInput(edges[side]),
180
+ fill: noneShapeFill(),
181
+ outline: shapeOutline(style, "000000")
182
+ }, edges[side], { dashType: style.dashType });
147
183
  }
148
184
  }
149
185
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"backgroundBorder.js","names":[],"sources":["../../../src/renderPptx/utils/backgroundBorder.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport { getImageData } from \"../../shared/measureImage.ts\";\nimport { registerBackgroundGradient } from \"../gradientFills.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn, rectPxToIn } from \"../units.ts\";\nimport {\n BORDER_SIDES,\n convertShadow,\n convertBorderLine,\n hasVisibleBorder,\n resolveBackgroundFill,\n resolvePerSideBorders,\n resolveRectRadius,\n type PerSideBorders,\n} from \"./visualStyle.ts\";\n\n/**\n * ノードの背景色・背景画像・ボーダー・影を描画する\n * 全ノードタイプで最初に呼び出される共通処理\n *\n * 描画順序: 背景色 → 背景画像 → ボーダー\n */\nexport function renderBackgroundAndBorder(\n node: PositionedNode,\n ctx: RenderContext,\n): void {\n const {\n backgroundColor,\n backgroundGradient,\n backgroundImage,\n border,\n borderRadius,\n shadow,\n } = node;\n\n // backgroundGradient はマーカー色の solidFill として描画し、\n // 出力時の後処理で gradFill に置換される (gradientFills.ts 参照)。\n // opacity はマーカー側ではなく gradFill のカラーストップの alpha で表現する\n const gradientMarker = backgroundGradient\n ? registerBackgroundGradient(\n backgroundGradient,\n node.opacity,\n ctx.buildContext.gradientFills,\n )\n : undefined;\n\n const perSideBorders = resolveEffectivePerSideBorders(node, ctx);\n\n const hasBackground = Boolean(backgroundColor) || Boolean(gradientMarker);\n const hasBackgroundImage = Boolean(backgroundImage);\n // 辺ごとの指定がある場合、一律 border は各辺へのマージで反映済みのため\n // shape の line としては描画しない\n const hasUniformBorder = !perSideBorders && hasVisibleBorder(border);\n const hasShadow = Boolean(shadow);\n\n if (\n !hasBackground &&\n !hasBackgroundImage &&\n !hasUniformBorder &&\n !perSideBorders &&\n !hasShadow\n ) {\n return;\n }\n\n // borderRadius がある場合は roundRect を使用し、rectRadius を計算\n const shapeType = borderRadius\n ? ctx.pptx.ShapeType.roundRect\n : ctx.pptx.ShapeType.rect;\n\n const rectRadius = resolveRectRadius(borderRadius, node.w, node.h);\n\n // backgroundImage がない場合は従来通り1回の addShape で処理\n if (!hasBackgroundImage) {\n if (hasBackground || hasUniformBorder || hasShadow) {\n const fill = hasBackground\n ? resolveBackgroundFill(backgroundColor, node.opacity, gradientMarker)\n : { type: \"none\" as const };\n\n const line = hasUniformBorder\n ? convertBorderLine(border, \"000000\")\n : { type: \"none\" as const };\n\n ctx.slide.addShape(shapeType, {\n ...rectPxToIn(node),\n fill,\n line,\n rectRadius,\n shadow: convertShadow(shadow),\n });\n }\n\n renderPerSideBorderLines(node, perSideBorders, ctx);\n return;\n }\n\n // backgroundImage がある場合は分割描画: 背景色 → 背景画像 → ボーダー\n\n // 1. 背景色\n if (hasBackground) {\n ctx.slide.addShape(shapeType, {\n ...rectPxToIn(node),\n fill: resolveBackgroundFill(\n backgroundColor,\n node.opacity,\n gradientMarker,\n ),\n line: { type: \"none\" as const },\n rectRadius,\n });\n }\n\n // 2. 背景画像\n if (backgroundImage) {\n const sizing = backgroundImage.sizing ?? \"cover\";\n const imageOptions: Record<string, unknown> = {\n ...rectPxToIn(node),\n sizing: {\n type: sizing,\n w: pxToIn(node.w),\n h: pxToIn(node.h),\n },\n };\n\n const cachedData = getImageData(\n backgroundImage.src,\n ctx.buildContext.imageDataCache,\n );\n if (cachedData) {\n ctx.slide.addImage({ ...imageOptions, data: cachedData });\n } else {\n ctx.slide.addImage({ ...imageOptions, path: backgroundImage.src });\n }\n }\n\n // 3. ボーダー\n if (hasUniformBorder || hasShadow) {\n ctx.slide.addShape(shapeType, {\n ...rectPxToIn(node),\n fill: { type: \"none\" as const },\n line: hasUniformBorder\n ? convertBorderLine(border, \"000000\")\n : { type: \"none\" as const },\n rectRadius,\n shadow: convertShadow(shadow),\n });\n }\n\n renderPerSideBorderLines(node, perSideBorders, ctx);\n}\n\n/**\n * 辺ごとの border 指定を解決する。borderRadius との併用は角の接続処理が\n * 複雑になるためサポートせず、警告を発して 4 辺一律の border に\n * フォールバックする\n */\nfunction resolveEffectivePerSideBorders(\n node: PositionedNode,\n ctx: RenderContext,\n): PerSideBorders | undefined {\n const perSideBorders = resolvePerSideBorders(node);\n if (perSideBorders && node.borderRadius !== undefined) {\n ctx.buildContext.diagnostics.add(\n \"PER_SIDE_BORDER_WITH_RADIUS\",\n 'borderTop / borderRight / borderBottom / borderLeft cannot be combined with borderRadius — falling back to the uniform \"border\" style',\n );\n return undefined;\n }\n return perSideBorders;\n}\n\n/**\n * ノードの border のみを描画する (背景・影は描画しない)。\n * ルートノードの backgroundColor / backgroundImage を slide.background に\n * 適用した後、border だけを個別に描画するパス用\n */\nexport function renderBorderOnly(\n node: PositionedNode,\n ctx: RenderContext,\n): void {\n const { border, borderRadius } = node;\n\n const perSideBorders = resolveEffectivePerSideBorders(node, ctx);\n if (perSideBorders) {\n renderPerSideBorderLines(node, perSideBorders, ctx);\n return;\n }\n\n if (!hasVisibleBorder(border)) return;\n\n const shapeType = borderRadius\n ? ctx.pptx.ShapeType.roundRect\n : ctx.pptx.ShapeType.rect;\n\n ctx.slide.addShape(shapeType, {\n ...rectPxToIn(node),\n fill: { type: \"none\" as const },\n line: convertBorderLine(border, \"000000\"),\n rectRadius: resolveRectRadius(borderRadius, node.w, node.h),\n });\n}\n\n/**\n * 辺ごとの border をノードの各辺に沿った line shape として描画する。\n * shape の line オプション (4 辺一律) では表現できないため、辺ごとに\n * 独立した line shape を追加する\n */\nfunction renderPerSideBorderLines(\n node: PositionedNode,\n perSideBorders: PerSideBorders | undefined,\n ctx: RenderContext,\n): void {\n if (!perSideBorders) return;\n\n const edges = {\n top: { x: node.x, y: node.y, w: node.w, h: 0 },\n right: { x: node.x + node.w, y: node.y, w: 0, h: node.h },\n bottom: { x: node.x, y: node.y + node.h, w: node.w, h: 0 },\n left: { x: node.x, y: node.y, w: 0, h: node.h },\n } as const;\n\n for (const side of BORDER_SIDES) {\n const style = perSideBorders[side];\n if (!style) continue;\n\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n ...rectPxToIn(edges[side]),\n line: convertBorderLine(style, \"000000\"),\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"backgroundBorder.js","names":[],"sources":["../../../src/renderPptx/utils/backgroundBorder.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport { getImageData } from \"../../shared/measureImage.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { rectPxToIn } from \"../units.ts\";\nimport {\n BORDER_SIDES,\n convertBorderLine,\n convertShadow,\n hasVisibleBorder,\n resolvePerSideBorders,\n resolveRectRadius,\n type PerSideBorders,\n} from \"./visualStyle.ts\";\nimport { addGlimpsePicture, imageBytesFromSource } from \"./glimpsePicture.ts\";\nimport {\n addGlimpseShape,\n backgroundShapeFill,\n createShapeBoundsInput,\n noShapeOutline,\n noneShapeFill,\n shapeOutline,\n} from \"./glimpseShape.ts\";\n\n/**\n * ノードの背景色・背景画像・ボーダー・影を描画する\n * 全ノードタイプで最初に呼び出される共通処理\n *\n * 描画順序: 背景色 → 背景画像 → ボーダー\n */\nexport function renderBackgroundAndBorder(\n node: PositionedNode,\n ctx: RenderContext,\n): void {\n const {\n backgroundColor,\n backgroundGradient,\n backgroundImage,\n border,\n borderRadius,\n shadow,\n } = node;\n\n const perSideBorders = resolveEffectivePerSideBorders(node, ctx);\n\n const hasBackground = Boolean(backgroundColor) || Boolean(backgroundGradient);\n const hasBackgroundImage = Boolean(backgroundImage);\n // 辺ごとの指定がある場合、一律 border は各辺へのマージで反映済みのため\n // shape の line としては描画しない\n const hasUniformBorder = !perSideBorders && hasVisibleBorder(border);\n const hasShadow = Boolean(shadow);\n\n if (\n !hasBackground &&\n !hasBackgroundImage &&\n !hasUniformBorder &&\n !perSideBorders &&\n !hasShadow\n ) {\n return;\n }\n\n // borderRadius がある場合は roundRect を使用し、rectRadius を計算\n const shapeType = borderRadius ? \"roundRect\" : \"rect\";\n const legacyShapeType = borderRadius\n ? ctx.pptx.ShapeType.roundRect\n : ctx.pptx.ShapeType.rect;\n const rectRadius = resolveRectRadius(borderRadius, node.w, node.h);\n\n // backgroundImage がない場合は従来通り1回の addShape で処理\n if (!hasBackgroundImage) {\n if (hasBackground || hasUniformBorder || hasShadow) {\n if (hasShadow && !backgroundGradient) {\n ctx.slide.addShape(legacyShapeType, {\n ...rectPxToIn(node),\n fill: hasBackground\n ? {\n color: backgroundColor,\n transparency:\n node.opacity !== undefined\n ? (1 - node.opacity) * 100\n : undefined,\n }\n : { type: \"none\" },\n line: hasUniformBorder\n ? convertBorderLine(border, \"000000\")\n : { type: \"none\" },\n rectRadius,\n shadow: convertShadow(shadow),\n });\n\n renderPerSideBorderLines(node, perSideBorders, ctx);\n return;\n }\n\n const fill = hasBackground\n ? backgroundShapeFill(backgroundColor, backgroundGradient)\n : noneShapeFill();\n\n const line = hasUniformBorder\n ? shapeOutline(border, \"000000\")\n : noShapeOutline();\n\n addGlimpseShape(\n ctx,\n {\n preset: shapeType,\n ...createShapeBoundsInput(node),\n fill,\n outline: line,\n },\n node,\n {\n fillColor: backgroundColor,\n fillOpacity: node.opacity,\n backgroundGradient,\n shadow,\n rectRadius,\n dashType: border?.dashType,\n },\n );\n }\n\n renderPerSideBorderLines(node, perSideBorders, ctx);\n return;\n }\n\n // backgroundImage がある場合は分割描画: 背景色 → 背景画像 → ボーダー\n\n // 1. 背景色\n if (hasBackground) {\n addGlimpseShape(\n ctx,\n {\n preset: shapeType,\n ...createShapeBoundsInput(node),\n fill: backgroundShapeFill(backgroundColor, backgroundGradient),\n outline: noShapeOutline(),\n },\n node,\n {\n fillColor: backgroundColor,\n fillOpacity: node.opacity,\n backgroundGradient,\n rectRadius,\n dashType: border?.dashType,\n },\n );\n }\n\n // 2. 背景画像\n if (backgroundImage) {\n const sizing = backgroundImage.sizing ?? \"cover\";\n const cachedData = getImageData(\n backgroundImage.src,\n ctx.buildContext.imageDataCache,\n );\n addGlimpsePicture(\n ctx,\n node,\n imageBytesFromSource(backgroundImage.src, cachedData),\n {\n sizing: { type: sizing, w: node.w, h: node.h },\n },\n );\n }\n\n // 3. ボーダー\n if (hasUniformBorder || hasShadow) {\n if (hasShadow) {\n ctx.slide.addShape(legacyShapeType, {\n ...rectPxToIn(node),\n fill: { type: \"none\" },\n line: hasUniformBorder\n ? convertBorderLine(border, \"000000\")\n : { type: \"none\" },\n rectRadius,\n shadow: convertShadow(shadow),\n });\n\n renderPerSideBorderLines(node, perSideBorders, ctx);\n return;\n }\n\n addGlimpseShape(\n ctx,\n {\n preset: shapeType,\n ...createShapeBoundsInput(node),\n fill: noneShapeFill(),\n outline: hasUniformBorder\n ? shapeOutline(border, \"000000\")\n : noShapeOutline(),\n },\n node,\n {\n shadow,\n rectRadius,\n dashType: border?.dashType,\n },\n );\n }\n\n renderPerSideBorderLines(node, perSideBorders, ctx);\n}\n\n/**\n * 辺ごとの border 指定を解決する。borderRadius との併用は角の接続処理が\n * 複雑になるためサポートせず、警告を発して 4 辺一律の border に\n * フォールバックする\n */\nfunction resolveEffectivePerSideBorders(\n node: PositionedNode,\n ctx: RenderContext,\n): PerSideBorders | undefined {\n const perSideBorders = resolvePerSideBorders(node);\n if (perSideBorders && node.borderRadius !== undefined) {\n ctx.buildContext.diagnostics.add(\n \"PER_SIDE_BORDER_WITH_RADIUS\",\n 'borderTop / borderRight / borderBottom / borderLeft cannot be combined with borderRadius — falling back to the uniform \"border\" style',\n );\n return undefined;\n }\n return perSideBorders;\n}\n\n/**\n * ノードの border のみを描画する (背景・影は描画しない)。\n * ルートノードの backgroundColor / backgroundImage を slide.background に\n * 適用した後、border だけを個別に描画するパス用\n */\nexport function renderBorderOnly(\n node: PositionedNode,\n ctx: RenderContext,\n): void {\n const { border, borderRadius } = node;\n\n const perSideBorders = resolveEffectivePerSideBorders(node, ctx);\n if (perSideBorders) {\n renderPerSideBorderLines(node, perSideBorders, ctx);\n return;\n }\n\n if (!hasVisibleBorder(border)) return;\n\n const shapeType = borderRadius ? \"roundRect\" : \"rect\";\n\n addGlimpseShape(\n ctx,\n {\n preset: shapeType,\n ...createShapeBoundsInput(node),\n fill: noneShapeFill(),\n outline: shapeOutline(border, \"000000\"),\n },\n node,\n {\n rectRadius: resolveRectRadius(borderRadius, node.w, node.h),\n dashType: border.dashType,\n },\n );\n}\n\n/**\n * 辺ごとの border をノードの各辺に沿った line shape として描画する。\n * shape の line オプション (4 辺一律) では表現できないため、辺ごとに\n * 独立した line shape を追加する\n */\nfunction renderPerSideBorderLines(\n node: PositionedNode,\n perSideBorders: PerSideBorders | undefined,\n ctx: RenderContext,\n): void {\n if (!perSideBorders) return;\n\n const edges = {\n top: { x: node.x, y: node.y, w: node.w, h: 0 },\n right: { x: node.x + node.w, y: node.y, w: 0, h: node.h },\n bottom: { x: node.x, y: node.y + node.h, w: node.w, h: 0 },\n left: { x: node.x, y: node.y, w: 0, h: node.h },\n } as const;\n\n for (const side of BORDER_SIDES) {\n const style = perSideBorders[side];\n if (!style) continue;\n\n addGlimpseShape(\n ctx,\n {\n preset: \"line\",\n ...createShapeBoundsInput(edges[side]),\n fill: noneShapeFill(),\n outline: shapeOutline(style, \"000000\"),\n },\n edges[side],\n {\n dashType: style.dashType,\n },\n );\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,130 @@
1
+ import { pxToEmu, rectPxToIn } from "../units.js";
2
+ import * as fs from "fs";
3
+ import { asEmu, asOoxmlAngle, asOoxmlPercent } from "@pptx-glimpse/document";
4
+ //#region src/renderPptx/utils/glimpsePicture.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
+ const FALLBACK_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAFgwJ/lU9qJwAAAABJRU5ErkJggg==";
16
+ function dataToBytes(data) {
17
+ const base64 = data.includes(",") ? data.split(",").at(-1) : data;
18
+ return new Uint8Array(Buffer.from(base64 ?? "", "base64"));
19
+ }
20
+ function isPngOrJpeg(bytes) {
21
+ const isPng = bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71;
22
+ const isJpeg = bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
23
+ return isPng || isJpeg;
24
+ }
25
+ function supportedOrFallback(bytes) {
26
+ return isPngOrJpeg(bytes) ? bytes : dataToBytes(FALLBACK_PNG);
27
+ }
28
+ function imageBytesFromSource(src, data) {
29
+ if (data) return supportedOrFallback(dataToBytes(data));
30
+ if (src.startsWith("data:")) return supportedOrFallback(dataToBytes(src));
31
+ if (src.startsWith("http://") || src.startsWith("https://")) return dataToBytes(FALLBACK_PNG);
32
+ try {
33
+ return supportedOrFallback(new Uint8Array(fs.readFileSync(src)));
34
+ } catch {
35
+ return dataToBytes(FALLBACK_PNG);
36
+ }
37
+ }
38
+ function positiveEmu(valuePx) {
39
+ return asEmu(Math.max(1, Math.round(pxToEmu(valuePx))));
40
+ }
41
+ function createPictureRotationInput(rotate) {
42
+ return rotate !== void 0 ? asOoxmlAngle(Math.round(rotate * 6e4)) : void 0;
43
+ }
44
+ function pct(value) {
45
+ return asOoxmlPercent(Math.round(value));
46
+ }
47
+ function createSizingCrop(sizing, content) {
48
+ const box = {
49
+ x: sizing.x ?? 0,
50
+ y: sizing.y ?? 0,
51
+ w: sizing.w ?? content.w,
52
+ h: sizing.h ?? content.h
53
+ };
54
+ const img = {
55
+ w: content.w,
56
+ h: content.h
57
+ };
58
+ if (sizing.type === "crop") {
59
+ const cropBase = {
60
+ w: content.w,
61
+ h: content.h
62
+ };
63
+ return {
64
+ bounds: {
65
+ ...content,
66
+ w: box.w,
67
+ h: box.h
68
+ },
69
+ crop: {
70
+ left: pct(1e5 * (box.x / cropBase.w)),
71
+ right: pct(1e5 * ((cropBase.w - (box.x + box.w)) / cropBase.w)),
72
+ top: pct(1e5 * (box.y / cropBase.h)),
73
+ bottom: pct(1e5 * ((cropBase.h - (box.y + box.h)) / cropBase.h))
74
+ }
75
+ };
76
+ }
77
+ const imgRatio = img.h / img.w;
78
+ const boxRatio = box.h / box.w;
79
+ const isCoverBoxBased = boxRatio > imgRatio;
80
+ const isContainWidthBased = boxRatio > imgRatio;
81
+ const width = sizing.type === "cover" ? isCoverBoxBased ? box.h / imgRatio : box.w : isContainWidthBased ? box.w : box.h / imgRatio;
82
+ const height = sizing.type === "cover" ? isCoverBoxBased ? box.h : box.w * imgRatio : isContainWidthBased ? box.w * imgRatio : box.h;
83
+ const horizontal = 1e5 * .5 * (1 - box.w / width);
84
+ const vertical = 1e5 * .5 * (1 - box.h / height);
85
+ return {
86
+ bounds: {
87
+ ...content,
88
+ w: box.w,
89
+ h: box.h
90
+ },
91
+ crop: {
92
+ left: pct(horizontal),
93
+ right: pct(horizontal),
94
+ top: pct(vertical),
95
+ bottom: pct(vertical)
96
+ }
97
+ };
98
+ }
99
+ function addGlimpsePicture(ctx, content, bytes, options) {
100
+ const sizingResult = options?.sizing ? createSizingCrop(options.sizing, content) : {
101
+ bounds: content,
102
+ crop: void 0
103
+ };
104
+ const input = {
105
+ bytes,
106
+ offsetX: asEmu(Math.round(pxToEmu(content.x))),
107
+ offsetY: asEmu(Math.round(pxToEmu(content.y))),
108
+ width: positiveEmu(sizingResult.bounds.w),
109
+ height: positiveEmu(sizingResult.bounds.h),
110
+ rotation: createPictureRotationInput(options?.rotate),
111
+ crop: sizingResult.crop
112
+ };
113
+ const marker = ctx.buildContext.glimpseTextBoxes.registerPicture(input, {
114
+ name: options?.name,
115
+ shadow: options?.shadow
116
+ });
117
+ ctx.slide.addShape(ctx.pptx.ShapeType.rect, {
118
+ ...rectPxToIn({
119
+ ...sizingResult.bounds,
120
+ w: Math.max(sizingResult.bounds.w, 1),
121
+ h: Math.max(sizingResult.bounds.h, 1)
122
+ }),
123
+ ...TRANSPARENT_MARKER_STYLE,
124
+ objectName: marker
125
+ });
126
+ }
127
+ //#endregion
128
+ export { addGlimpsePicture, imageBytesFromSource };
129
+
130
+ //# sourceMappingURL=glimpsePicture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glimpsePicture.js","names":[],"sources":["../../../src/renderPptx/utils/glimpsePicture.ts"],"sourcesContent":["import * as fs from \"fs\";\nimport {\n asEmu,\n asOoxmlAngle,\n asOoxmlPercent,\n type AddPictureCropInput,\n type AddPictureInput,\n} from \"@pptx-glimpse/document\";\nimport type { ShadowStyle } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToEmu, rectPxToIn } from \"../units.ts\";\n\ntype PictureBoundsPx = { x: number; y: number; w: number; h: number };\ntype ImageSizing = {\n type: \"contain\" | \"cover\" | \"crop\";\n w?: number;\n h?: number;\n x?: number;\n y?: number;\n};\n\nconst TRANSPARENT_MARKER_STYLE = {\n fill: { color: \"FFFFFF\", transparency: 100 },\n line: { color: \"FFFFFF\", transparency: 100 },\n} as const;\nconst FALLBACK_PNG =\n \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAFgwJ/lU9qJwAAAABJRU5ErkJggg==\";\n\nfunction dataToBytes(data: string): Uint8Array {\n const base64 = data.includes(\",\") ? data.split(\",\").at(-1) : data;\n return new Uint8Array(Buffer.from(base64 ?? \"\", \"base64\"));\n}\n\nfunction isPngOrJpeg(bytes: Uint8Array): boolean {\n const isPng =\n bytes[0] === 0x89 &&\n bytes[1] === 0x50 &&\n bytes[2] === 0x4e &&\n bytes[3] === 0x47;\n const isJpeg = bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff;\n return isPng || isJpeg;\n}\n\nfunction supportedOrFallback(bytes: Uint8Array): Uint8Array {\n return isPngOrJpeg(bytes) ? bytes : dataToBytes(FALLBACK_PNG);\n}\n\nexport function imageBytesFromSource(src: string, data?: string): Uint8Array {\n if (data) return supportedOrFallback(dataToBytes(data));\n if (src.startsWith(\"data:\")) return supportedOrFallback(dataToBytes(src));\n if (src.startsWith(\"http://\") || src.startsWith(\"https://\")) {\n return dataToBytes(FALLBACK_PNG);\n }\n try {\n return supportedOrFallback(new Uint8Array(fs.readFileSync(src)));\n } catch {\n return dataToBytes(FALLBACK_PNG);\n }\n}\n\nfunction positiveEmu(valuePx: number) {\n return asEmu(Math.max(1, Math.round(pxToEmu(valuePx))));\n}\n\nfunction createPictureRotationInput(rotate: number | undefined) {\n return rotate !== undefined\n ? asOoxmlAngle(Math.round(rotate * 60000))\n : undefined;\n}\n\nfunction pct(value: number) {\n return asOoxmlPercent(Math.round(value));\n}\n\nfunction createSizingCrop(\n sizing: ImageSizing,\n content: PictureBoundsPx,\n): { bounds: PictureBoundsPx; crop?: AddPictureCropInput } {\n const box = {\n x: sizing.x ?? 0,\n y: sizing.y ?? 0,\n w: sizing.w ?? content.w,\n h: sizing.h ?? content.h,\n };\n const img = { w: content.w, h: content.h };\n\n if (sizing.type === \"crop\") {\n const cropBase = { w: content.w, h: content.h };\n return {\n bounds: { ...content, w: box.w, h: box.h },\n crop: {\n left: pct(100000 * (box.x / cropBase.w)),\n right: pct(100000 * ((cropBase.w - (box.x + box.w)) / cropBase.w)),\n top: pct(100000 * (box.y / cropBase.h)),\n bottom: pct(100000 * ((cropBase.h - (box.y + box.h)) / cropBase.h)),\n },\n };\n }\n\n const imgRatio = img.h / img.w;\n const boxRatio = box.h / box.w;\n const isCoverBoxBased = boxRatio > imgRatio;\n const isContainWidthBased = boxRatio > imgRatio;\n const width =\n sizing.type === \"cover\"\n ? isCoverBoxBased\n ? box.h / imgRatio\n : box.w\n : isContainWidthBased\n ? box.w\n : box.h / imgRatio;\n const height =\n sizing.type === \"cover\"\n ? isCoverBoxBased\n ? box.h\n : box.w * imgRatio\n : isContainWidthBased\n ? box.w * imgRatio\n : box.h;\n const horizontal = 100000 * 0.5 * (1 - box.w / width);\n const vertical = 100000 * 0.5 * (1 - box.h / height);\n\n return {\n bounds: { ...content, w: box.w, h: box.h },\n crop: {\n left: pct(horizontal),\n right: pct(horizontal),\n top: pct(vertical),\n bottom: pct(vertical),\n },\n };\n}\n\nexport function addGlimpsePicture(\n ctx: RenderContext,\n content: PictureBoundsPx,\n bytes: Uint8Array,\n options?: {\n rotate?: number;\n sizing?: ImageSizing;\n name?: string;\n shadow?: ShadowStyle;\n },\n): void {\n const sizingResult = options?.sizing\n ? createSizingCrop(options.sizing, content)\n : { bounds: content, crop: undefined };\n const input: AddPictureInput = {\n bytes,\n offsetX: asEmu(Math.round(pxToEmu(content.x))),\n offsetY: asEmu(Math.round(pxToEmu(content.y))),\n width: positiveEmu(sizingResult.bounds.w),\n height: positiveEmu(sizingResult.bounds.h),\n rotation: createPictureRotationInput(options?.rotate),\n crop: sizingResult.crop,\n };\n const marker = ctx.buildContext.glimpseTextBoxes.registerPicture(input, {\n name: options?.name,\n shadow: options?.shadow,\n });\n ctx.slide.addShape(ctx.pptx.ShapeType.rect, {\n ...rectPxToIn({\n ...sizingResult.bounds,\n w: Math.max(sizingResult.bounds.w, 1),\n h: Math.max(sizingResult.bounds.h, 1),\n }),\n ...TRANSPARENT_MARKER_STYLE,\n objectName: marker,\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ import { pxToEmu, rectPxToIn } from "../units.js";
2
+ import { parseGradient } from "../../shared/gradient.js";
3
+ import { toColorInput } from "../glimpseTextBoxes.js";
4
+ import { asEmu, asOoxmlAngle } from "@pptx-glimpse/document";
5
+ //#region src/renderPptx/utils/glimpseShape.ts
6
+ const TRANSPARENT_MARKER_STYLE = {
7
+ fill: {
8
+ color: "FFFFFF",
9
+ transparency: 100
10
+ },
11
+ line: {
12
+ color: "FFFFFF",
13
+ transparency: 100
14
+ }
15
+ };
16
+ function positiveEmu(valuePx) {
17
+ return asEmu(Math.max(1, Math.round(pxToEmu(valuePx))));
18
+ }
19
+ function createShapeBoundsInput(bounds) {
20
+ return {
21
+ offsetX: asEmu(Math.round(pxToEmu(bounds.x))),
22
+ offsetY: asEmu(Math.round(pxToEmu(bounds.y))),
23
+ width: positiveEmu(bounds.w),
24
+ height: positiveEmu(bounds.h)
25
+ };
26
+ }
27
+ function createShapeRotationInput(rotate) {
28
+ return rotate !== void 0 ? asOoxmlAngle(Math.round(rotate * 6e4)) : void 0;
29
+ }
30
+ function solidShapeFill(color) {
31
+ const clean = toColorInput(color);
32
+ return clean ? {
33
+ kind: "solid",
34
+ color: clean
35
+ } : void 0;
36
+ }
37
+ function noneShapeFill() {
38
+ return { kind: "none" };
39
+ }
40
+ function gradientFallbackColor(value) {
41
+ return parseGradient(value)?.value.stops[0]?.color;
42
+ }
43
+ function backgroundShapeFill(backgroundColor, backgroundGradient) {
44
+ if (backgroundGradient) return solidShapeFill(gradientFallbackColor(backgroundGradient));
45
+ return solidShapeFill(backgroundColor);
46
+ }
47
+ function dashStyle(dashType) {
48
+ return dashType;
49
+ }
50
+ function shapeOutline(border, fallbackColor) {
51
+ if (!border) return void 0;
52
+ const fill = solidShapeFill(border.color ?? fallbackColor);
53
+ if (border.width === void 0 && fill === void 0 && border.dashType === void 0) return;
54
+ return {
55
+ width: border.width !== void 0 ? asEmu(Math.round(pxToEmu(border.width))) : void 0,
56
+ fill,
57
+ dash: dashStyle(border.dashType)
58
+ };
59
+ }
60
+ function noShapeOutline() {
61
+ return { fill: noneShapeFill() };
62
+ }
63
+ function arrowEndpoint(type) {
64
+ if (type === void 0 || type === "none") return void 0;
65
+ return {
66
+ type,
67
+ width: "med",
68
+ length: "med"
69
+ };
70
+ }
71
+ function addGlimpseShape(ctx, input, markerBounds, options) {
72
+ const marker = ctx.buildContext.glimpseTextBoxes.registerShape(input, {
73
+ ...options,
74
+ zeroWidth: markerBounds.w === 0,
75
+ zeroHeight: markerBounds.h === 0
76
+ });
77
+ ctx.slide.addShape(ctx.pptx.ShapeType.rect, {
78
+ ...rectPxToIn({
79
+ ...markerBounds,
80
+ w: Math.max(markerBounds.w, 1),
81
+ h: Math.max(markerBounds.h, 1)
82
+ }),
83
+ ...TRANSPARENT_MARKER_STYLE,
84
+ objectName: marker
85
+ });
86
+ }
87
+ //#endregion
88
+ export { addGlimpseShape, arrowEndpoint, backgroundShapeFill, createShapeBoundsInput, createShapeRotationInput, noShapeOutline, noneShapeFill, shapeOutline, solidShapeFill };
89
+
90
+ //# sourceMappingURL=glimpseShape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glimpseShape.js","names":[],"sources":["../../../src/renderPptx/utils/glimpseShape.ts"],"sourcesContent":["import {\n asEmu,\n asOoxmlAngle,\n type AddShapeFillInput,\n type AddShapeInput,\n type AddShapeOutlineInput,\n type SourceArrowEndpoint,\n type SourceDashStyle,\n} from \"@pptx-glimpse/document\";\nimport type { BorderStyle, ShadowStyle, TextGlow } from \"../../types.ts\";\nimport { parseGradient } from \"../../shared/gradient.ts\";\nimport {\n toColorInput,\n type CustomGeometryXmlInput,\n} from \"../glimpseTextBoxes.ts\";\nimport { pxToEmu, rectPxToIn } from \"../units.ts\";\nimport type { RenderContext } from \"../types.ts\";\n\ntype ShapeBoundsPx = { x: number; y: number; w: number; h: number };\n\nexport type GlimpseShapeStyleOptions = {\n fillColor?: string;\n fillOpacity?: number;\n backgroundGradient?: string;\n outlineGradient?: string;\n outlineOpacity?: number;\n glow?: TextGlow;\n shadow?: ShadowStyle;\n rectRadius?: number;\n dashType?: BorderStyle[\"dashType\"];\n flipH?: boolean;\n flipV?: boolean;\n zeroWidth?: boolean;\n zeroHeight?: boolean;\n customGeometry?: CustomGeometryXmlInput;\n};\n\nconst TRANSPARENT_MARKER_STYLE = {\n fill: { color: \"FFFFFF\", transparency: 100 },\n line: { color: \"FFFFFF\", transparency: 100 },\n} as const;\n\nfunction positiveEmu(valuePx: number) {\n return asEmu(Math.max(1, Math.round(pxToEmu(valuePx))));\n}\n\nexport function createShapeBoundsInput(bounds: ShapeBoundsPx) {\n return {\n offsetX: asEmu(Math.round(pxToEmu(bounds.x))),\n offsetY: asEmu(Math.round(pxToEmu(bounds.y))),\n width: positiveEmu(bounds.w),\n height: positiveEmu(bounds.h),\n };\n}\n\nexport function createShapeRotationInput(rotate: number | undefined) {\n return rotate !== undefined\n ? asOoxmlAngle(Math.round(rotate * 60000))\n : undefined;\n}\n\nexport function solidShapeFill(\n color: string | undefined,\n): AddShapeFillInput | undefined {\n const clean = toColorInput(color);\n return clean ? { kind: \"solid\", color: clean } : undefined;\n}\n\nexport function noneShapeFill(): AddShapeFillInput {\n return { kind: \"none\" };\n}\n\nfunction gradientFallbackColor(value: string): string | undefined {\n const gradient = parseGradient(value);\n return gradient?.value.stops[0]?.color;\n}\n\nexport function backgroundShapeFill(\n backgroundColor: string | undefined,\n backgroundGradient: string | undefined,\n): AddShapeFillInput | undefined {\n if (backgroundGradient) {\n const color = gradientFallbackColor(backgroundGradient);\n return solidShapeFill(color);\n }\n return solidShapeFill(backgroundColor);\n}\n\nfunction dashStyle(\n dashType: BorderStyle[\"dashType\"],\n): SourceDashStyle | undefined {\n return dashType as SourceDashStyle | undefined;\n}\n\nexport function shapeOutline(\n border: BorderStyle | undefined,\n fallbackColor?: string,\n): AddShapeOutlineInput | undefined {\n if (!border) return undefined;\n const fill = solidShapeFill(border.color ?? fallbackColor);\n if (\n border.width === undefined &&\n fill === undefined &&\n border.dashType === undefined\n ) {\n return undefined;\n }\n return {\n width:\n border.width !== undefined\n ? asEmu(Math.round(pxToEmu(border.width)))\n : undefined,\n fill,\n dash: dashStyle(border.dashType),\n };\n}\n\nexport function noShapeOutline(): AddShapeOutlineInput {\n return { fill: noneShapeFill() };\n}\n\nexport function arrowEndpoint(\n type:\n \"none\" | \"arrow\" | \"diamond\" | \"oval\" | \"stealth\" | \"triangle\" | undefined,\n): SourceArrowEndpoint | undefined {\n if (type === undefined || type === \"none\") return undefined;\n return { type, width: \"med\", length: \"med\" };\n}\n\nexport function addGlimpseShape(\n ctx: RenderContext,\n input: AddShapeInput,\n markerBounds: ShapeBoundsPx,\n options?: GlimpseShapeStyleOptions & { name?: string },\n): void {\n const marker = ctx.buildContext.glimpseTextBoxes.registerShape(input, {\n ...options,\n zeroWidth: markerBounds.w === 0,\n zeroHeight: markerBounds.h === 0,\n });\n ctx.slide.addShape(ctx.pptx.ShapeType.rect, {\n ...rectPxToIn({\n ...markerBounds,\n w: Math.max(markerBounds.w, 1),\n h: Math.max(markerBounds.h, 1),\n }),\n ...TRANSPARENT_MARKER_STYLE,\n objectName: marker,\n });\n}\n"],"mappings":";;;;;AAqCA,MAAM,2BAA2B;CAC/B,MAAM;EAAE,OAAO;EAAU,cAAc;CAAI;CAC3C,MAAM;EAAE,OAAO;EAAU,cAAc;CAAI;AAC7C;AAEA,SAAS,YAAY,SAAiB;CACpC,OAAO,MAAM,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC;AACxD;AAEA,SAAgB,uBAAuB,QAAuB;CAC5D,OAAO;EACL,SAAS,MAAM,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC;EAC5C,SAAS,MAAM,KAAK,MAAM,QAAQ,OAAO,CAAC,CAAC,CAAC;EAC5C,OAAO,YAAY,OAAO,CAAC;EAC3B,QAAQ,YAAY,OAAO,CAAC;CAC9B;AACF;AAEA,SAAgB,yBAAyB,QAA4B;CACnE,OAAO,WAAW,KAAA,IACd,aAAa,KAAK,MAAM,SAAS,GAAK,CAAC,IACvC,KAAA;AACN;AAEA,SAAgB,eACd,OAC+B;CAC/B,MAAM,QAAQ,aAAa,KAAK;CAChC,OAAO,QAAQ;EAAE,MAAM;EAAS,OAAO;CAAM,IAAI,KAAA;AACnD;AAEA,SAAgB,gBAAmC;CACjD,OAAO,EAAE,MAAM,OAAO;AACxB;AAEA,SAAS,sBAAsB,OAAmC;CAEhE,OADiB,cAAc,KACjB,CAAC,EAAE,MAAM,MAAM,EAAE,EAAE;AACnC;AAEA,SAAgB,oBACd,iBACA,oBAC+B;CAC/B,IAAI,oBAEF,OAAO,eADO,sBAAsB,kBACV,CAAC;CAE7B,OAAO,eAAe,eAAe;AACvC;AAEA,SAAS,UACP,UAC6B;CAC7B,OAAO;AACT;AAEA,SAAgB,aACd,QACA,eACkC;CAClC,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,MAAM,OAAO,eAAe,OAAO,SAAS,aAAa;CACzD,IACE,OAAO,UAAU,KAAA,KACjB,SAAS,KAAA,KACT,OAAO,aAAa,KAAA,GAEpB;CAEF,OAAO;EACL,OACE,OAAO,UAAU,KAAA,IACb,MAAM,KAAK,MAAM,QAAQ,OAAO,KAAK,CAAC,CAAC,IACvC,KAAA;EACN;EACA,MAAM,UAAU,OAAO,QAAQ;CACjC;AACF;AAEA,SAAgB,iBAAuC;CACrD,OAAO,EAAE,MAAM,cAAc,EAAE;AACjC;AAEA,SAAgB,cACd,MAEiC;CACjC,IAAI,SAAS,KAAA,KAAa,SAAS,QAAQ,OAAO,KAAA;CAClD,OAAO;EAAE;EAAM,OAAO;EAAO,QAAQ;CAAM;AAC7C;AAEA,SAAgB,gBACd,KACA,OACA,cACA,SACM;CACN,MAAM,SAAS,IAAI,aAAa,iBAAiB,cAAc,OAAO;EACpE,GAAG;EACH,WAAW,aAAa,MAAM;EAC9B,YAAY,aAAa,MAAM;CACjC,CAAC;CACD,IAAI,MAAM,SAAS,IAAI,KAAK,UAAU,MAAM;EAC1C,GAAG,WAAW;GACZ,GAAG;GACH,GAAG,KAAK,IAAI,aAAa,GAAG,CAAC;GAC7B,GAAG,KAAK,IAAI,aAAa,GAAG,CAAC;EAC/B,CAAC;EACD,GAAG;EACH,YAAY;CACd,CAAC;AACH"}