@hirokisakabe/pom 8.2.0 → 8.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -24
- package/dist/autoFit/autoFit.js.map +1 -1
- package/dist/buildContext.js +3 -1
- package/dist/buildContext.js.map +1 -1
- package/dist/buildPptx.d.ts.map +1 -1
- package/dist/buildPptx.js +4 -0
- package/dist/buildPptx.js.map +1 -1
- package/dist/calcYogaLayout/calcYogaLayout.js +2 -1
- package/dist/calcYogaLayout/calcYogaLayout.js.map +1 -1
- package/dist/calcYogaLayout/fontLoader.js.map +1 -1
- package/dist/calcYogaLayout/measureText.d.ts.map +1 -1
- package/dist/calcYogaLayout/measureText.js +9 -2
- package/dist/calcYogaLayout/measureText.js.map +1 -1
- package/dist/diagnostics.js.map +1 -1
- package/dist/icons/renderIcon.js.map +1 -1
- package/dist/parseMasterPptx.js.map +1 -1
- package/dist/parseXml/coercionRules.js +6 -2
- package/dist/parseXml/coercionRules.js.map +1 -1
- package/dist/parseXml/parseXml.d.ts.map +1 -1
- package/dist/parseXml/parseXml.js +11 -8
- package/dist/parseXml/parseXml.js.map +1 -1
- package/dist/parseXml/serializeXml.d.ts.map +1 -1
- package/dist/parseXml/serializeXml.js +1 -0
- package/dist/parseXml/serializeXml.js.map +1 -1
- package/dist/registry/definitions/list.js.map +1 -1
- package/dist/registry/definitions/shape.js.map +1 -1
- package/dist/registry/definitions/text.js +3 -1
- package/dist/registry/definitions/text.js.map +1 -1
- package/dist/renderPptx/gradientFills.js +139 -0
- package/dist/renderPptx/gradientFills.js.map +1 -0
- package/dist/renderPptx/nodes/icon.js.map +1 -1
- package/dist/renderPptx/nodes/list.js.map +1 -1
- package/dist/renderPptx/nodes/matrix.js +8 -5
- package/dist/renderPptx/nodes/matrix.js.map +1 -1
- package/dist/renderPptx/nodes/table.js.map +1 -1
- package/dist/renderPptx/nodes/text.js +18 -14
- package/dist/renderPptx/nodes/text.js.map +1 -1
- package/dist/renderPptx/nodes/tree.js.map +1 -1
- package/dist/renderPptx/renderPptx.js +6 -2
- package/dist/renderPptx/renderPptx.js.map +1 -1
- package/dist/renderPptx/textOptions.js +2 -1
- package/dist/renderPptx/textOptions.js.map +1 -1
- package/dist/renderPptx/utils/backgroundBorder.js +6 -4
- package/dist/renderPptx/utils/backgroundBorder.js.map +1 -1
- package/dist/shared/gradient.js +103 -0
- package/dist/shared/gradient.js.map +1 -0
- package/dist/shared/measureImage.js.map +1 -1
- package/dist/shared/tableUtils.js.map +1 -1
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -2
- package/dist/types.js.map +1 -1
- package/package.json +9 -9
|
@@ -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 { renderBackgroundAndBorder } 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 if (typeof master.margin === \"number\") {\n masterProps.margin = pxToIn(master.margin);\n } else {\n masterProps.margin = [\n pxToIn(master.margin.top ?? 0),\n pxToIn(master.margin.right ?? 0),\n pxToIn(master.margin.bottom ?? 0),\n pxToIn(master.margin.left ?? 0),\n ];\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 rootHasOpacity =\n !isLinelike && \"opacity\" in data && data.opacity !== undefined;\n 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 || (rootBackgroundColor && !rootHasOpacity))\n ) {\n // border のみ描画(backgroundColor/backgroundImage はスキップ)\n const { border, borderRadius } = node;\n const hasBorder = Boolean(\n border &&\n (border.color !== undefined ||\n border.width !== undefined ||\n border.dashType !== undefined),\n );\n if (hasBorder) {\n const line = {\n color: border?.color ?? \"000000\",\n width:\n border?.width !== undefined ? pxToPt(border.width) : undefined,\n dashType: border?.dashType,\n };\n const shapeType = borderRadius\n ? ctx.pptx.ShapeType.roundRect\n : ctx.pptx.ShapeType.rect;\n const rectRadius = borderRadius\n ? Math.min((borderRadius / Math.min(node.w, node.h)) * 2, 1)\n : undefined;\n ctx.slide.addShape(shapeType, {\n x: pxToIn(node.x),\n y: pxToIn(node.y),\n w: pxToIn(node.w),\n h: pxToIn(node.h),\n fill: { type: \"none\" },\n line,\n rectRadius,\n });\n }\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 { renderBackgroundAndBorder } 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 if (typeof master.margin === \"number\") {\n masterProps.margin = pxToIn(master.margin);\n } else {\n masterProps.margin = [\n pxToIn(master.margin.top ?? 0),\n pxToIn(master.margin.right ?? 0),\n pxToIn(master.margin.bottom ?? 0),\n pxToIn(master.margin.left ?? 0),\n ];\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 const { border, borderRadius } = node;\n const hasBorder = Boolean(\n border &&\n (border.color !== undefined ||\n border.width !== undefined ||\n border.dashType !== undefined),\n );\n if (hasBorder) {\n const line = {\n color: border?.color ?? \"000000\",\n width:\n border?.width !== undefined ? pxToPt(border.width) : undefined,\n dashType: border?.dashType,\n };\n const shapeType = borderRadius\n ? ctx.pptx.ShapeType.roundRect\n : ctx.pptx.ShapeType.rect;\n const rectRadius = borderRadius\n ? Math.min((borderRadius / Math.min(node.w, node.h)) * 2, 1)\n : undefined;\n ctx.slide.addShape(shapeType, {\n x: pxToIn(node.x),\n y: pxToIn(node.y),\n w: pxToIn(node.w),\n h: pxToIn(node.h),\n fill: { type: \"none\" },\n line,\n rectRadius,\n });\n }\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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"}
|
|
@@ -40,7 +40,8 @@ function createTextOptions(node) {
|
|
|
40
40
|
italic: node.italic,
|
|
41
41
|
underline: convertUnderline(node.underline),
|
|
42
42
|
strike: convertStrike(node.strike),
|
|
43
|
-
highlight: node.highlight
|
|
43
|
+
highlight: node.highlight,
|
|
44
|
+
charSpacing: node.letterSpacing !== void 0 ? pxToPt(node.letterSpacing) : void 0
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
47
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textOptions.js","names":[],"sources":["../../src/renderPptx/textOptions.ts"],"sourcesContent":["import type { PositionedNode, Underline, UnderlineStyle } from \"../types.ts\";\nimport { pxToIn, pxToPt } from \"./units.ts\";\nimport { getContentArea } from \"./utils/contentArea.ts\";\n\ntype TextNode = Extract<PositionedNode, { type: \"text\" }>;\n\n/**\n * underline プロパティを pptxgenjs 形式に変換する\n */\nexport function convertUnderline(\n underline: Underline | undefined,\n): { style?: UnderlineStyle; color?: string } | undefined {\n if (underline === undefined) return undefined;\n if (underline === false) return undefined;\n if (underline === true) return { style: \"sng\" };\n return {\n style: underline.style,\n color: underline.color,\n };\n}\n\n/**\n * strike プロパティを pptxgenjs 形式に変換する\n */\nexport function convertStrike(\n strike: boolean | undefined,\n): \"sngStrike\" | undefined {\n if (strike) return \"sngStrike\";\n return undefined;\n}\n\nexport function createTextOptions(node: TextNode) {\n const fontSizePx = node.fontSize ?? 24;\n const fontFamily = node.fontFamily ?? \"Noto Sans JP\";\n const lineHeight = node.lineHeight ?? 1.3;\n const content = getContentArea(node);\n\n return {\n x: pxToIn(content.x),\n y: pxToIn(content.y),\n w: pxToIn(content.w),\n h: pxToIn(content.h),\n fontSize: pxToPt(fontSizePx),\n fontFace: fontFamily,\n align: node.textAlign ?? \"left\",\n valign: \"top\" as const,\n margin: 0,\n lineSpacingMultiple: lineHeight,\n color: node.color,\n bold: node.bold,\n italic: node.italic,\n underline: convertUnderline(node.underline),\n strike: convertStrike(node.strike),\n highlight: node.highlight,\n };\n}\n"],"mappings":";;;;;;AASA,SAAgB,iBACd,WACwD;CACxD,IAAI,cAAc,KAAA,GAAW,OAAO,KAAA;CACpC,IAAI,cAAc,OAAO,OAAO,KAAA;CAChC,IAAI,cAAc,MAAM,OAAO,EAAE,OAAO,MAAM;CAC9C,OAAO;EACL,OAAO,UAAU;EACjB,OAAO,UAAU;CACnB;AACF;;;;AAKA,SAAgB,cACd,QACyB;CACzB,IAAI,QAAQ,OAAO;AAErB;AAEA,SAAgB,kBAAkB,MAAgB;CAChD,MAAM,aAAa,KAAK,YAAY;CACpC,MAAM,aAAa,KAAK,cAAc;CACtC,MAAM,aAAa,KAAK,cAAc;CACtC,MAAM,UAAU,eAAe,IAAI;CAEnC,OAAO;EACL,GAAG,OAAO,QAAQ,CAAC;EACnB,GAAG,OAAO,QAAQ,CAAC;EACnB,GAAG,OAAO,QAAQ,CAAC;EACnB,GAAG,OAAO,QAAQ,CAAC;EACnB,UAAU,OAAO,UAAU;EAC3B,UAAU;EACV,OAAO,KAAK,aAAa;EACzB,QAAQ;EACR,QAAQ;EACR,qBAAqB;EACrB,OAAO,KAAK;EACZ,MAAM,KAAK;EACX,QAAQ,KAAK;EACb,WAAW,iBAAiB,KAAK,SAAS;EAC1C,QAAQ,cAAc,KAAK,MAAM;EACjC,WAAW,KAAK;
|
|
1
|
+
{"version":3,"file":"textOptions.js","names":[],"sources":["../../src/renderPptx/textOptions.ts"],"sourcesContent":["import type { PositionedNode, Underline, UnderlineStyle } from \"../types.ts\";\nimport { pxToIn, pxToPt } from \"./units.ts\";\nimport { getContentArea } from \"./utils/contentArea.ts\";\n\ntype TextNode = Extract<PositionedNode, { type: \"text\" }>;\n\n/**\n * underline プロパティを pptxgenjs 形式に変換する\n */\nexport function convertUnderline(\n underline: Underline | undefined,\n): { style?: UnderlineStyle; color?: string } | undefined {\n if (underline === undefined) return undefined;\n if (underline === false) return undefined;\n if (underline === true) return { style: \"sng\" };\n return {\n style: underline.style,\n color: underline.color,\n };\n}\n\n/**\n * strike プロパティを pptxgenjs 形式に変換する\n */\nexport function convertStrike(\n strike: boolean | undefined,\n): \"sngStrike\" | undefined {\n if (strike) return \"sngStrike\";\n return undefined;\n}\n\nexport function createTextOptions(node: TextNode) {\n const fontSizePx = node.fontSize ?? 24;\n const fontFamily = node.fontFamily ?? \"Noto Sans JP\";\n const lineHeight = node.lineHeight ?? 1.3;\n const content = getContentArea(node);\n\n return {\n x: pxToIn(content.x),\n y: pxToIn(content.y),\n w: pxToIn(content.w),\n h: pxToIn(content.h),\n fontSize: pxToPt(fontSizePx),\n fontFace: fontFamily,\n align: node.textAlign ?? \"left\",\n valign: \"top\" as const,\n margin: 0,\n lineSpacingMultiple: lineHeight,\n color: node.color,\n bold: node.bold,\n italic: node.italic,\n underline: convertUnderline(node.underline),\n strike: convertStrike(node.strike),\n highlight: node.highlight,\n // letterSpacing はユーザー入力 px、pptxgenjs の charSpacing は pt\n charSpacing:\n node.letterSpacing !== undefined ? pxToPt(node.letterSpacing) : undefined,\n };\n}\n"],"mappings":";;;;;;AASA,SAAgB,iBACd,WACwD;CACxD,IAAI,cAAc,KAAA,GAAW,OAAO,KAAA;CACpC,IAAI,cAAc,OAAO,OAAO,KAAA;CAChC,IAAI,cAAc,MAAM,OAAO,EAAE,OAAO,MAAM;CAC9C,OAAO;EACL,OAAO,UAAU;EACjB,OAAO,UAAU;CACnB;AACF;;;;AAKA,SAAgB,cACd,QACyB;CACzB,IAAI,QAAQ,OAAO;AAErB;AAEA,SAAgB,kBAAkB,MAAgB;CAChD,MAAM,aAAa,KAAK,YAAY;CACpC,MAAM,aAAa,KAAK,cAAc;CACtC,MAAM,aAAa,KAAK,cAAc;CACtC,MAAM,UAAU,eAAe,IAAI;CAEnC,OAAO;EACL,GAAG,OAAO,QAAQ,CAAC;EACnB,GAAG,OAAO,QAAQ,CAAC;EACnB,GAAG,OAAO,QAAQ,CAAC;EACnB,GAAG,OAAO,QAAQ,CAAC;EACnB,UAAU,OAAO,UAAU;EAC3B,UAAU;EACV,OAAO,KAAK,aAAa;EACzB,QAAQ;EACR,QAAQ;EACR,qBAAqB;EACrB,OAAO,KAAK;EACZ,MAAM,KAAK;EACX,QAAQ,KAAK;EACb,WAAW,iBAAiB,KAAK,SAAS;EAC1C,QAAQ,cAAc,KAAK,MAAM;EACjC,WAAW,KAAK;EAEhB,aACE,KAAK,kBAAkB,KAAA,IAAY,OAAO,KAAK,aAAa,IAAI,KAAA;CACpE;AACF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getImageData } from "../../shared/measureImage.js";
|
|
2
2
|
import { pxToIn, pxToPt } from "../units.js";
|
|
3
|
+
import { registerBackgroundGradient } from "../gradientFills.js";
|
|
3
4
|
//#region src/renderPptx/utils/backgroundBorder.ts
|
|
4
5
|
function convertShadow(shadow) {
|
|
5
6
|
return {
|
|
@@ -18,8 +19,9 @@ function convertShadow(shadow) {
|
|
|
18
19
|
* 描画順序: 背景色 → 背景画像 → ボーダー
|
|
19
20
|
*/
|
|
20
21
|
function renderBackgroundAndBorder(node, ctx) {
|
|
21
|
-
const { backgroundColor, backgroundImage, border, borderRadius, shadow } = node;
|
|
22
|
-
const
|
|
22
|
+
const { backgroundColor, backgroundGradient, backgroundImage, border, borderRadius, shadow } = node;
|
|
23
|
+
const gradientMarker = backgroundGradient ? registerBackgroundGradient(backgroundGradient, node.opacity, ctx.buildContext.gradientFills) : void 0;
|
|
24
|
+
const hasBackground = Boolean(backgroundColor) || Boolean(gradientMarker);
|
|
23
25
|
const hasBackgroundImage = Boolean(backgroundImage);
|
|
24
26
|
const hasBorder = Boolean(border && (border.color !== void 0 || border.width !== void 0 || border.dashType !== void 0));
|
|
25
27
|
const hasShadow = Boolean(shadow);
|
|
@@ -27,7 +29,7 @@ function renderBackgroundAndBorder(node, ctx) {
|
|
|
27
29
|
const shapeType = borderRadius ? ctx.pptx.ShapeType.roundRect : ctx.pptx.ShapeType.rect;
|
|
28
30
|
const rectRadius = borderRadius ? Math.min(borderRadius / Math.min(node.w, node.h) * 2, 1) : void 0;
|
|
29
31
|
if (!hasBackgroundImage) {
|
|
30
|
-
const fill = hasBackground ? {
|
|
32
|
+
const fill = hasBackground ? gradientMarker ? { color: gradientMarker } : {
|
|
31
33
|
color: backgroundColor,
|
|
32
34
|
transparency: node.opacity !== void 0 ? (1 - node.opacity) * 100 : void 0
|
|
33
35
|
} : { type: "none" };
|
|
@@ -53,7 +55,7 @@ function renderBackgroundAndBorder(node, ctx) {
|
|
|
53
55
|
y: pxToIn(node.y),
|
|
54
56
|
w: pxToIn(node.w),
|
|
55
57
|
h: pxToIn(node.h),
|
|
56
|
-
fill: {
|
|
58
|
+
fill: gradientMarker ? { color: gradientMarker } : {
|
|
57
59
|
color: backgroundColor,
|
|
58
60
|
transparency: node.opacity !== void 0 ? (1 - node.opacity) * 100 : void 0
|
|
59
61
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backgroundBorder.js","names":[],"sources":["../../../src/renderPptx/utils/backgroundBorder.ts"],"sourcesContent":["import type { PositionedNode, ShadowStyle } from \"../../types.ts\";\nimport { getImageData } from \"../../shared/measureImage.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn, pxToPt } from \"../units.ts\";\n\nfunction convertShadow(shadow: ShadowStyle) {\n return {\n type: shadow.type ?? (\"outer\" as const),\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 * ノードの背景色・背景画像・ボーダー・影を描画する\n * 全ノードタイプで最初に呼び出される共通処理\n *\n * 描画順序: 背景色 → 背景画像 → ボーダー\n */\nexport function renderBackgroundAndBorder(\n node: PositionedNode,\n ctx: RenderContext,\n): void {\n const {
|
|
1
|
+
{"version":3,"file":"backgroundBorder.js","names":[],"sources":["../../../src/renderPptx/utils/backgroundBorder.ts"],"sourcesContent":["import type { PositionedNode, ShadowStyle } from \"../../types.ts\";\nimport { getImageData } from \"../../shared/measureImage.ts\";\nimport { registerBackgroundGradient } from \"../gradientFills.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn, pxToPt } from \"../units.ts\";\n\nfunction convertShadow(shadow: ShadowStyle) {\n return {\n type: shadow.type ?? (\"outer\" as const),\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 * ノードの背景色・背景画像・ボーダー・影を描画する\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 hasBackground = Boolean(backgroundColor) || Boolean(gradientMarker);\n const hasBackgroundImage = Boolean(backgroundImage);\n const hasBorder = Boolean(\n border &&\n (border.color !== undefined ||\n border.width !== undefined ||\n border.dashType !== undefined),\n );\n const hasShadow = Boolean(shadow);\n\n if (!hasBackground && !hasBackgroundImage && !hasBorder && !hasShadow) {\n return;\n }\n\n // borderRadius がある場合は roundRect を使用し、rectRadius を計算\n const shapeType = borderRadius\n ? ctx.pptx.ShapeType.roundRect\n : ctx.pptx.ShapeType.rect;\n\n // px を 0-1 の正規化値に変換\n const rectRadius = borderRadius\n ? Math.min((borderRadius / Math.min(node.w, node.h)) * 2, 1)\n : undefined;\n\n // backgroundImage がない場合は従来通り1回の addShape で処理\n if (!hasBackgroundImage) {\n const fill = hasBackground\n ? gradientMarker\n ? { color: gradientMarker }\n : {\n color: backgroundColor,\n transparency:\n node.opacity !== undefined ? (1 - node.opacity) * 100 : undefined,\n }\n : { type: \"none\" as const };\n\n const line = hasBorder\n ? {\n color: border?.color ?? \"000000\",\n width: border?.width !== undefined ? pxToPt(border.width) : undefined,\n dashType: border?.dashType,\n }\n : { type: \"none\" as const };\n\n ctx.slide.addShape(shapeType, {\n x: pxToIn(node.x),\n y: pxToIn(node.y),\n w: pxToIn(node.w),\n h: pxToIn(node.h),\n fill,\n line,\n rectRadius,\n shadow: shadow ? convertShadow(shadow) : undefined,\n });\n return;\n }\n\n // backgroundImage がある場合は分割描画: 背景色 → 背景画像 → ボーダー\n\n // 1. 背景色\n if (hasBackground) {\n ctx.slide.addShape(shapeType, {\n x: pxToIn(node.x),\n y: pxToIn(node.y),\n w: pxToIn(node.w),\n h: pxToIn(node.h),\n fill: gradientMarker\n ? { color: gradientMarker }\n : {\n color: backgroundColor,\n transparency:\n node.opacity !== undefined ? (1 - node.opacity) * 100 : undefined,\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 x: pxToIn(node.x),\n y: pxToIn(node.y),\n w: pxToIn(node.w),\n h: pxToIn(node.h),\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 (hasBorder || hasShadow) {\n ctx.slide.addShape(shapeType, {\n x: pxToIn(node.x),\n y: pxToIn(node.y),\n w: pxToIn(node.w),\n h: pxToIn(node.h),\n fill: { type: \"none\" as const },\n line: hasBorder\n ? {\n color: border?.color ?? \"000000\",\n width:\n border?.width !== undefined ? pxToPt(border.width) : undefined,\n dashType: border?.dashType,\n }\n : { type: \"none\" as const },\n rectRadius,\n shadow: shadow ? convertShadow(shadow) : undefined,\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//#region src/shared/gradient.ts
|
|
2
|
+
/** `to <方向>` キーワード → CSS 角度 (deg)。コーナーは 45 度刻みの近似 */
|
|
3
|
+
const DIRECTION_KEYWORDS = {
|
|
4
|
+
"to top": 0,
|
|
5
|
+
"to right": 90,
|
|
6
|
+
"to bottom": 180,
|
|
7
|
+
"to left": 270,
|
|
8
|
+
"to top right": 45,
|
|
9
|
+
"to right top": 45,
|
|
10
|
+
"to bottom right": 135,
|
|
11
|
+
"to right bottom": 135,
|
|
12
|
+
"to bottom left": 225,
|
|
13
|
+
"to left bottom": 225,
|
|
14
|
+
"to top left": 315,
|
|
15
|
+
"to left top": 315
|
|
16
|
+
};
|
|
17
|
+
const COLOR_PATTERN = /^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/;
|
|
18
|
+
function normalizeColor(raw) {
|
|
19
|
+
const match = COLOR_PATTERN.exec(raw);
|
|
20
|
+
if (!match) return null;
|
|
21
|
+
const hex = match[1];
|
|
22
|
+
if (hex.length === 3) return hex.split("").map((c) => c + c).join("").toUpperCase();
|
|
23
|
+
return hex.toUpperCase();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 位置省略されたストップに CSS 互換のルールで位置を割り当てる。
|
|
27
|
+
* - 先頭が省略なら 0、末尾が省略なら 100
|
|
28
|
+
* - 中間の省略は前後の明示位置の間で均等配置
|
|
29
|
+
* - 位置が前のストップより小さい場合は前の値に切り上げ (非減少を保証)
|
|
30
|
+
*/
|
|
31
|
+
function resolveStopPositions(stops) {
|
|
32
|
+
const positions = stops.map((s) => s.position);
|
|
33
|
+
if (positions[0] === void 0) positions[0] = 0;
|
|
34
|
+
if (positions[positions.length - 1] === void 0) positions[positions.length - 1] = 100;
|
|
35
|
+
let prevIndex = 0;
|
|
36
|
+
for (let i = 1; i < positions.length; i++) {
|
|
37
|
+
if (positions[i] === void 0) continue;
|
|
38
|
+
const gap = i - prevIndex;
|
|
39
|
+
if (gap > 1) {
|
|
40
|
+
const start = positions[prevIndex];
|
|
41
|
+
const end = positions[i];
|
|
42
|
+
for (let j = 1; j < gap; j++) positions[prevIndex + j] = start + (end - start) * j / gap;
|
|
43
|
+
}
|
|
44
|
+
prevIndex = i;
|
|
45
|
+
}
|
|
46
|
+
let prev = 0;
|
|
47
|
+
return stops.map((stop, i) => {
|
|
48
|
+
const clamped = Math.min(Math.max(positions[i], 0), 100);
|
|
49
|
+
const position = Math.max(clamped, prev);
|
|
50
|
+
prev = position;
|
|
51
|
+
return {
|
|
52
|
+
color: stop.color,
|
|
53
|
+
position
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* linear-gradient() 構文をパースする。不正な構文の場合は null を返す。
|
|
59
|
+
*/
|
|
60
|
+
function parseLinearGradient(value) {
|
|
61
|
+
const match = /^\s*linear-gradient\s*\(\s*(.+?)\s*\)\s*$/.exec(value);
|
|
62
|
+
if (!match) return null;
|
|
63
|
+
const args = match[1].split(",").map((s) => s.trim());
|
|
64
|
+
if (args.length === 0) return null;
|
|
65
|
+
let angle = 180;
|
|
66
|
+
let stopArgs = args;
|
|
67
|
+
const first = args[0];
|
|
68
|
+
const angleMatch = /^(-?\d+(?:\.\d+)?)deg$/.exec(first);
|
|
69
|
+
const directionAngle = DIRECTION_KEYWORDS[first.toLowerCase().replace(/\s+/g, " ")];
|
|
70
|
+
if (angleMatch) {
|
|
71
|
+
angle = (Number(angleMatch[1]) % 360 + 360) % 360;
|
|
72
|
+
stopArgs = args.slice(1);
|
|
73
|
+
} else if (directionAngle !== void 0) {
|
|
74
|
+
angle = directionAngle;
|
|
75
|
+
stopArgs = args.slice(1);
|
|
76
|
+
}
|
|
77
|
+
if (stopArgs.length < 2) return null;
|
|
78
|
+
const stops = [];
|
|
79
|
+
for (const stopArg of stopArgs) {
|
|
80
|
+
const parts = stopArg.split(/\s+/);
|
|
81
|
+
if (parts.length === 0 || parts.length > 2) return null;
|
|
82
|
+
const color = normalizeColor(parts[0]);
|
|
83
|
+
if (color === null) return null;
|
|
84
|
+
let position;
|
|
85
|
+
if (parts.length === 2) {
|
|
86
|
+
const posMatch = /^(-?\d+(?:\.\d+)?)%$/.exec(parts[1]);
|
|
87
|
+
if (!posMatch) return null;
|
|
88
|
+
position = Number(posMatch[1]);
|
|
89
|
+
}
|
|
90
|
+
stops.push({
|
|
91
|
+
color,
|
|
92
|
+
position
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
angle,
|
|
97
|
+
stops: resolveStopPositions(stops)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { parseLinearGradient };
|
|
102
|
+
|
|
103
|
+
//# sourceMappingURL=gradient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gradient.js","names":[],"sources":["../../src/shared/gradient.ts"],"sourcesContent":["/**\n * リニアグラデーション文字列のパース\n *\n * CSS の linear-gradient() 風の構文を受け付ける:\n * linear-gradient(135deg, #FF0000 0%, #0000FF 100%)\n * linear-gradient(to right, #FF0000, #00FF00, #0000FF)\n * linear-gradient(#FF0000, #0000FF) ← 角度省略時は 180deg (上→下)\n *\n * - 角度: `<数値>deg` または `to <方向>` キーワード。省略時は 180deg。\n * - カラーストップ: 16進カラー (#RGB / #RRGGBB / RRGGBB) + 任意の位置 (%)。\n * 位置省略時は CSS と同様に補間する (先頭 0% / 末尾 100% / 中間は線形補間)。\n * - ストップは 2 つ以上必須。\n */\n\nexport interface GradientStop {\n /** 6桁大文字 HEX (# なし) */\n color: string;\n /** 0-100 (%) */\n position: number;\n}\n\nexport interface LinearGradient {\n /** CSS 基準の角度 (deg)。0 = 上向き、時計回り。0-360 に正規化済み */\n angle: number;\n stops: GradientStop[];\n}\n\n/** `to <方向>` キーワード → CSS 角度 (deg)。コーナーは 45 度刻みの近似 */\nconst DIRECTION_KEYWORDS: Record<string, number> = {\n \"to top\": 0,\n \"to right\": 90,\n \"to bottom\": 180,\n \"to left\": 270,\n \"to top right\": 45,\n \"to right top\": 45,\n \"to bottom right\": 135,\n \"to right bottom\": 135,\n \"to bottom left\": 225,\n \"to left bottom\": 225,\n \"to top left\": 315,\n \"to left top\": 315,\n};\n\nconst COLOR_PATTERN = /^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/;\n\nfunction normalizeColor(raw: string): string | null {\n const match = COLOR_PATTERN.exec(raw);\n if (!match) return null;\n const hex = match[1];\n if (hex.length === 3) {\n return hex\n .split(\"\")\n .map((c) => c + c)\n .join(\"\")\n .toUpperCase();\n }\n return hex.toUpperCase();\n}\n\n/**\n * 位置省略されたストップに CSS 互換のルールで位置を割り当てる。\n * - 先頭が省略なら 0、末尾が省略なら 100\n * - 中間の省略は前後の明示位置の間で均等配置\n * - 位置が前のストップより小さい場合は前の値に切り上げ (非減少を保証)\n */\nfunction resolveStopPositions(\n stops: { color: string; position: number | undefined }[],\n): GradientStop[] {\n const positions: (number | undefined)[] = stops.map((s) => s.position);\n if (positions[0] === undefined) positions[0] = 0;\n if (positions[positions.length - 1] === undefined) {\n positions[positions.length - 1] = 100;\n }\n\n let prevIndex = 0;\n for (let i = 1; i < positions.length; i++) {\n if (positions[i] === undefined) continue;\n const gap = i - prevIndex;\n if (gap > 1) {\n const start = positions[prevIndex]!;\n const end = positions[i]!;\n for (let j = 1; j < gap; j++) {\n positions[prevIndex + j] = start + ((end - start) * j) / gap;\n }\n }\n prevIndex = i;\n }\n\n let prev = 0;\n return stops.map((stop, i) => {\n const clamped = Math.min(Math.max(positions[i]!, 0), 100);\n const position = Math.max(clamped, prev);\n prev = position;\n return { color: stop.color, position };\n });\n}\n\n/**\n * linear-gradient() 構文をパースする。不正な構文の場合は null を返す。\n */\nexport function parseLinearGradient(value: string): LinearGradient | null {\n const match = /^\\s*linear-gradient\\s*\\(\\s*(.+?)\\s*\\)\\s*$/.exec(value);\n if (!match) return null;\n\n const args = match[1].split(\",\").map((s) => s.trim());\n if (args.length === 0) return null;\n\n let angle = 180;\n let stopArgs = args;\n\n const first = args[0];\n const angleMatch = /^(-?\\d+(?:\\.\\d+)?)deg$/.exec(first);\n const directionAngle =\n DIRECTION_KEYWORDS[first.toLowerCase().replace(/\\s+/g, \" \")];\n if (angleMatch) {\n angle = ((Number(angleMatch[1]) % 360) + 360) % 360;\n stopArgs = args.slice(1);\n } else if (directionAngle !== undefined) {\n angle = directionAngle;\n stopArgs = args.slice(1);\n }\n\n if (stopArgs.length < 2) return null;\n\n const stops: { color: string; position: number | undefined }[] = [];\n for (const stopArg of stopArgs) {\n const parts = stopArg.split(/\\s+/);\n if (parts.length === 0 || parts.length > 2) return null;\n const color = normalizeColor(parts[0]);\n if (color === null) return null;\n\n let position: number | undefined;\n if (parts.length === 2) {\n const posMatch = /^(-?\\d+(?:\\.\\d+)?)%$/.exec(parts[1]);\n if (!posMatch) return null;\n position = Number(posMatch[1]);\n }\n stops.push({ color, position });\n }\n\n return { angle, stops: resolveStopPositions(stops) };\n}\n"],"mappings":";;AA4BA,MAAM,qBAA6C;CACjD,UAAU;CACV,YAAY;CACZ,aAAa;CACb,WAAW;CACX,gBAAgB;CAChB,gBAAgB;CAChB,mBAAmB;CACnB,mBAAmB;CACnB,kBAAkB;CAClB,kBAAkB;CAClB,eAAe;CACf,eAAe;AACjB;AAEA,MAAM,gBAAgB;AAEtB,SAAS,eAAe,KAA4B;CAClD,MAAM,QAAQ,cAAc,KAAK,GAAG;CACpC,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,MAAM,MAAM;CAClB,IAAI,IAAI,WAAW,GACjB,OAAO,IACJ,MAAM,EAAE,CAAC,CACT,KAAK,MAAM,IAAI,CAAC,CAAC,CACjB,KAAK,EAAE,CAAC,CACR,YAAY;CAEjB,OAAO,IAAI,YAAY;AACzB;;;;;;;AAQA,SAAS,qBACP,OACgB;CAChB,MAAM,YAAoC,MAAM,KAAK,MAAM,EAAE,QAAQ;CACrE,IAAI,UAAU,OAAO,KAAA,GAAW,UAAU,KAAK;CAC/C,IAAI,UAAU,UAAU,SAAS,OAAO,KAAA,GACtC,UAAU,UAAU,SAAS,KAAK;CAGpC,IAAI,YAAY;CAChB,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;EACzC,IAAI,UAAU,OAAO,KAAA,GAAW;EAChC,MAAM,MAAM,IAAI;EAChB,IAAI,MAAM,GAAG;GACX,MAAM,QAAQ,UAAU;GACxB,MAAM,MAAM,UAAU;GACtB,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KACvB,UAAU,YAAY,KAAK,SAAU,MAAM,SAAS,IAAK;EAE7D;EACA,YAAY;CACd;CAEA,IAAI,OAAO;CACX,OAAO,MAAM,KAAK,MAAM,MAAM;EAC5B,MAAM,UAAU,KAAK,IAAI,KAAK,IAAI,UAAU,IAAK,CAAC,GAAG,GAAG;EACxD,MAAM,WAAW,KAAK,IAAI,SAAS,IAAI;EACvC,OAAO;EACP,OAAO;GAAE,OAAO,KAAK;GAAO;EAAS;CACvC,CAAC;AACH;;;;AAKA,SAAgB,oBAAoB,OAAsC;CACxE,MAAM,QAAQ,4CAA4C,KAAK,KAAK;CACpE,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,OAAO,MAAM,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,KAAK,CAAC;CACpD,IAAI,KAAK,WAAW,GAAG,OAAO;CAE9B,IAAI,QAAQ;CACZ,IAAI,WAAW;CAEf,MAAM,QAAQ,KAAK;CACnB,MAAM,aAAa,yBAAyB,KAAK,KAAK;CACtD,MAAM,iBACJ,mBAAmB,MAAM,YAAY,CAAC,CAAC,QAAQ,QAAQ,GAAG;CAC5D,IAAI,YAAY;EACd,SAAU,OAAO,WAAW,EAAE,IAAI,MAAO,OAAO;EAChD,WAAW,KAAK,MAAM,CAAC;CACzB,OAAO,IAAI,mBAAmB,KAAA,GAAW;EACvC,QAAQ;EACR,WAAW,KAAK,MAAM,CAAC;CACzB;CAEA,IAAI,SAAS,SAAS,GAAG,OAAO;CAEhC,MAAM,QAA2D,CAAC;CAClE,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,QAAQ,QAAQ,MAAM,KAAK;EACjC,IAAI,MAAM,WAAW,KAAK,MAAM,SAAS,GAAG,OAAO;EACnD,MAAM,QAAQ,eAAe,MAAM,EAAE;EACrC,IAAI,UAAU,MAAM,OAAO;EAE3B,IAAI;EACJ,IAAI,MAAM,WAAW,GAAG;GACtB,MAAM,WAAW,uBAAuB,KAAK,MAAM,EAAE;GACrD,IAAI,CAAC,UAAU,OAAO;GACtB,WAAW,OAAO,SAAS,EAAE;EAC/B;EACA,MAAM,KAAK;GAAE;GAAO;EAAS,CAAC;CAChC;CAEA,OAAO;EAAE;EAAO,OAAO,qBAAqB,KAAK;CAAE;AACrD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"measureImage.js","names":[],"sources":["../../src/shared/measureImage.ts"],"sourcesContent":["import * as fs from \"fs\";\nimport { imageSize } from \"image-size\";\nimport type { DiagnosticCollector } from \"../diagnostics.ts\";\n\ntype ImageSizeCache = Map<string, { widthPx: number; heightPx: number }>;\ntype ImageDataCache = Map<string, string>;\n\n/**\n * キャッシュされた画像データ(Base64)を取得する\n * @param src 画像のパス\n * @param cache 画像データキャッシュ\n * @returns Base64形式の画像データ、またはキャッシュがない場合はundefined\n */\nexport function getImageData(\n src: string,\n cache: ImageDataCache,\n): string | undefined {\n return cache.get(src);\n}\n\n/**\n * 画像サイズを事前取得してキャッシュする(非同期)\n * HTTPS URLの画像を処理する際に使用\n * @param src 画像のパス(ローカルパス、base64データ、またはHTTPS URL)\n * @param sizeCache 画像サイズキャッシュ\n * @param dataCache 画像データキャッシュ\n * @returns 画像の幅と高さ(px)\n */\nexport async function prefetchImageSize(\n src: string,\n sizeCache: ImageSizeCache,\n dataCache: ImageDataCache,\n diagnostics: DiagnosticCollector,\n): Promise<{\n widthPx: number;\n heightPx: number;\n}> {\n // キャッシュにあればそれを返す\n const cached = sizeCache.get(src);\n if (cached) {\n return cached;\n }\n\n try {\n let buffer: Uint8Array;\n\n // base64データの場合\n if (src.startsWith(\"data:\")) {\n const base64Data = src.split(\",\")[1];\n buffer = new Uint8Array(Buffer.from(base64Data, \"base64\"));\n }\n // HTTPS/HTTP URLの場合\n else if (src.startsWith(\"https://\") || src.startsWith(\"http://\")) {\n const response = await fetch(src);\n if (!response.ok) {\n throw new Error(`Failed to fetch image: ${response.status}`);\n }\n const arrayBuffer = await response.arrayBuffer();\n buffer = new Uint8Array(arrayBuffer);\n\n // 画像データをBase64形式でキャッシュ(pptxgenjs用)\n const contentType = response.headers.get(\"content-type\") || \"image/png\";\n const base64 = Buffer.from(arrayBuffer).toString(\"base64\");\n dataCache.set(src, `${contentType};base64,${base64}`);\n }\n // ローカルファイルパスの場合\n else {\n buffer = new Uint8Array(fs.readFileSync(src));\n }\n\n const dimensions = imageSize(buffer);\n\n const width = dimensions.width ?? 100; // デフォルト100px\n const height = dimensions.height ?? 100; // デフォルト100px\n\n const result = {\n widthPx: width,\n heightPx: height,\n };\n\n // キャッシュに保存\n sizeCache.set(src, result);\n\n return result;\n } catch (error) {\n // エラーが発生した場合はデフォルトサイズを返す\n diagnostics.add(\n \"IMAGE_MEASURE_FAILED\",\n `Failed to measure image size for ${src}: ${String(error)}`,\n );\n const result = {\n widthPx: 100,\n heightPx: 100,\n };\n sizeCache.set(src, result);\n return result;\n }\n}\n\n/**\n * 画像ファイルのサイズを取得する(同期)\n * 事前にprefetchImageSizeでキャッシュしておくこと\n * @param src 画像のパス(ローカルパス、base64データ、またはHTTPS URL)\n * @param sizeCache 画像サイズキャッシュ\n * @returns 画像の幅と高さ(px)\n */\nexport function measureImage(\n src: string,\n sizeCache: ImageSizeCache,\n diagnostics: DiagnosticCollector,\n): {\n widthPx: number;\n heightPx: number;\n} {\n // キャッシュにあればそれを返す\n const cached = sizeCache.get(src);\n if (cached) {\n return cached;\n }\n\n // キャッシュにない場合(ローカルファイルやbase64のみ同期処理可能)\n try {\n let buffer: Uint8Array;\n\n // base64データの場合\n if (src.startsWith(\"data:\")) {\n const base64Data = src.split(\",\")[1];\n buffer = new Uint8Array(Buffer.from(base64Data, \"base64\"));\n }\n // HTTPS/HTTP URLの場合はキャッシュがないとデフォルト値を返す\n else if (src.startsWith(\"https://\") || src.startsWith(\"http://\")) {\n diagnostics.add(\n \"IMAGE_NOT_PREFETCHED\",\n `Image size for URL ${src} was not prefetched. Using default size.`,\n );\n return {\n widthPx: 100,\n heightPx: 100,\n };\n }\n // ローカルファイルパスの場合\n else {\n buffer = new Uint8Array(fs.readFileSync(src));\n }\n\n const dimensions = imageSize(buffer);\n\n const width = dimensions.width ?? 100; // デフォルト100px\n const height = dimensions.height ?? 100; // デフォルト100px\n\n const result = {\n widthPx: width,\n heightPx: height,\n };\n\n // キャッシュに保存\n sizeCache.set(src, result);\n\n return result;\n } catch (error) {\n // エラーが発生した場合はデフォルトサイズを返す\n diagnostics.add(\n \"IMAGE_MEASURE_FAILED\",\n `Failed to measure image size for ${src}: ${String(error)}`,\n );\n return {\n widthPx: 100,\n heightPx: 100,\n };\n }\n}\n"],"mappings":";;;;;;;;;AAaA,SAAgB,aACd,KACA,OACoB;CACpB,OAAO,MAAM,IAAI,GAAG;AACtB;;;;;;;;;AAUA,eAAsB,kBACpB,KACA,WACA,WACA,aAIC;CAED,MAAM,SAAS,UAAU,IAAI,GAAG;CAChC,IAAI,QACF,OAAO;CAGT,IAAI;EACF,IAAI;EAGJ,IAAI,IAAI,WAAW,OAAO,GAAG;GAC3B,MAAM,aAAa,IAAI,MAAM,GAAG,
|
|
1
|
+
{"version":3,"file":"measureImage.js","names":[],"sources":["../../src/shared/measureImage.ts"],"sourcesContent":["import * as fs from \"fs\";\nimport { imageSize } from \"image-size\";\nimport type { DiagnosticCollector } from \"../diagnostics.ts\";\n\ntype ImageSizeCache = Map<string, { widthPx: number; heightPx: number }>;\ntype ImageDataCache = Map<string, string>;\n\n/**\n * キャッシュされた画像データ(Base64)を取得する\n * @param src 画像のパス\n * @param cache 画像データキャッシュ\n * @returns Base64形式の画像データ、またはキャッシュがない場合はundefined\n */\nexport function getImageData(\n src: string,\n cache: ImageDataCache,\n): string | undefined {\n return cache.get(src);\n}\n\n/**\n * 画像サイズを事前取得してキャッシュする(非同期)\n * HTTPS URLの画像を処理する際に使用\n * @param src 画像のパス(ローカルパス、base64データ、またはHTTPS URL)\n * @param sizeCache 画像サイズキャッシュ\n * @param dataCache 画像データキャッシュ\n * @returns 画像の幅と高さ(px)\n */\nexport async function prefetchImageSize(\n src: string,\n sizeCache: ImageSizeCache,\n dataCache: ImageDataCache,\n diagnostics: DiagnosticCollector,\n): Promise<{\n widthPx: number;\n heightPx: number;\n}> {\n // キャッシュにあればそれを返す\n const cached = sizeCache.get(src);\n if (cached) {\n return cached;\n }\n\n try {\n let buffer: Uint8Array;\n\n // base64データの場合\n if (src.startsWith(\"data:\")) {\n const base64Data = src.split(\",\")[1];\n buffer = new Uint8Array(Buffer.from(base64Data, \"base64\"));\n }\n // HTTPS/HTTP URLの場合\n else if (src.startsWith(\"https://\") || src.startsWith(\"http://\")) {\n const response = await fetch(src);\n if (!response.ok) {\n throw new Error(`Failed to fetch image: ${response.status}`);\n }\n const arrayBuffer = await response.arrayBuffer();\n buffer = new Uint8Array(arrayBuffer);\n\n // 画像データをBase64形式でキャッシュ(pptxgenjs用)\n const contentType = response.headers.get(\"content-type\") || \"image/png\";\n const base64 = Buffer.from(arrayBuffer).toString(\"base64\");\n dataCache.set(src, `${contentType};base64,${base64}`);\n }\n // ローカルファイルパスの場合\n else {\n buffer = new Uint8Array(fs.readFileSync(src));\n }\n\n const dimensions = imageSize(buffer);\n\n const width = dimensions.width ?? 100; // デフォルト100px\n const height = dimensions.height ?? 100; // デフォルト100px\n\n const result = {\n widthPx: width,\n heightPx: height,\n };\n\n // キャッシュに保存\n sizeCache.set(src, result);\n\n return result;\n } catch (error) {\n // エラーが発生した場合はデフォルトサイズを返す\n diagnostics.add(\n \"IMAGE_MEASURE_FAILED\",\n `Failed to measure image size for ${src}: ${String(error)}`,\n );\n const result = {\n widthPx: 100,\n heightPx: 100,\n };\n sizeCache.set(src, result);\n return result;\n }\n}\n\n/**\n * 画像ファイルのサイズを取得する(同期)\n * 事前にprefetchImageSizeでキャッシュしておくこと\n * @param src 画像のパス(ローカルパス、base64データ、またはHTTPS URL)\n * @param sizeCache 画像サイズキャッシュ\n * @returns 画像の幅と高さ(px)\n */\nexport function measureImage(\n src: string,\n sizeCache: ImageSizeCache,\n diagnostics: DiagnosticCollector,\n): {\n widthPx: number;\n heightPx: number;\n} {\n // キャッシュにあればそれを返す\n const cached = sizeCache.get(src);\n if (cached) {\n return cached;\n }\n\n // キャッシュにない場合(ローカルファイルやbase64のみ同期処理可能)\n try {\n let buffer: Uint8Array;\n\n // base64データの場合\n if (src.startsWith(\"data:\")) {\n const base64Data = src.split(\",\")[1];\n buffer = new Uint8Array(Buffer.from(base64Data, \"base64\"));\n }\n // HTTPS/HTTP URLの場合はキャッシュがないとデフォルト値を返す\n else if (src.startsWith(\"https://\") || src.startsWith(\"http://\")) {\n diagnostics.add(\n \"IMAGE_NOT_PREFETCHED\",\n `Image size for URL ${src} was not prefetched. Using default size.`,\n );\n return {\n widthPx: 100,\n heightPx: 100,\n };\n }\n // ローカルファイルパスの場合\n else {\n buffer = new Uint8Array(fs.readFileSync(src));\n }\n\n const dimensions = imageSize(buffer);\n\n const width = dimensions.width ?? 100; // デフォルト100px\n const height = dimensions.height ?? 100; // デフォルト100px\n\n const result = {\n widthPx: width,\n heightPx: height,\n };\n\n // キャッシュに保存\n sizeCache.set(src, result);\n\n return result;\n } catch (error) {\n // エラーが発生した場合はデフォルトサイズを返す\n diagnostics.add(\n \"IMAGE_MEASURE_FAILED\",\n `Failed to measure image size for ${src}: ${String(error)}`,\n );\n return {\n widthPx: 100,\n heightPx: 100,\n };\n }\n}\n"],"mappings":";;;;;;;;;AAaA,SAAgB,aACd,KACA,OACoB;CACpB,OAAO,MAAM,IAAI,GAAG;AACtB;;;;;;;;;AAUA,eAAsB,kBACpB,KACA,WACA,WACA,aAIC;CAED,MAAM,SAAS,UAAU,IAAI,GAAG;CAChC,IAAI,QACF,OAAO;CAGT,IAAI;EACF,IAAI;EAGJ,IAAI,IAAI,WAAW,OAAO,GAAG;GAC3B,MAAM,aAAa,IAAI,MAAM,GAAG,CAAC,CAAC;GAClC,SAAS,IAAI,WAAW,OAAO,KAAK,YAAY,QAAQ,CAAC;EAC3D,OAEK,IAAI,IAAI,WAAW,UAAU,KAAK,IAAI,WAAW,SAAS,GAAG;GAChE,MAAM,WAAW,MAAM,MAAM,GAAG;GAChC,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,0BAA0B,SAAS,QAAQ;GAE7D,MAAM,cAAc,MAAM,SAAS,YAAY;GAC/C,SAAS,IAAI,WAAW,WAAW;GAGnC,MAAM,cAAc,SAAS,QAAQ,IAAI,cAAc,KAAK;GAC5D,MAAM,SAAS,OAAO,KAAK,WAAW,CAAC,CAAC,SAAS,QAAQ;GACzD,UAAU,IAAI,KAAK,GAAG,YAAY,UAAU,QAAQ;EACtD,OAGE,SAAS,IAAI,WAAW,GAAG,aAAa,GAAG,CAAC;EAG9C,MAAM,aAAa,UAAU,MAAM;EAKnC,MAAM,SAAS;GACb,SAJY,WAAW,SAAS;GAKhC,UAJa,WAAW,UAAU;EAKpC;EAGA,UAAU,IAAI,KAAK,MAAM;EAEzB,OAAO;CACT,SAAS,OAAO;EAEd,YAAY,IACV,wBACA,oCAAoC,IAAI,IAAI,OAAO,KAAK,GAC1D;EACA,MAAM,SAAS;GACb,SAAS;GACT,UAAU;EACZ;EACA,UAAU,IAAI,KAAK,MAAM;EACzB,OAAO;CACT;AACF;;;;;;;;AASA,SAAgB,aACd,KACA,WACA,aAIA;CAEA,MAAM,SAAS,UAAU,IAAI,GAAG;CAChC,IAAI,QACF,OAAO;CAIT,IAAI;EACF,IAAI;EAGJ,IAAI,IAAI,WAAW,OAAO,GAAG;GAC3B,MAAM,aAAa,IAAI,MAAM,GAAG,CAAC,CAAC;GAClC,SAAS,IAAI,WAAW,OAAO,KAAK,YAAY,QAAQ,CAAC;EAC3D,OAEK,IAAI,IAAI,WAAW,UAAU,KAAK,IAAI,WAAW,SAAS,GAAG;GAChE,YAAY,IACV,wBACA,sBAAsB,IAAI,yCAC5B;GACA,OAAO;IACL,SAAS;IACT,UAAU;GACZ;EACF,OAGE,SAAS,IAAI,WAAW,GAAG,aAAa,GAAG,CAAC;EAG9C,MAAM,aAAa,UAAU,MAAM;EAKnC,MAAM,SAAS;GACb,SAJY,WAAW,SAAS;GAKhC,UAJa,WAAW,UAAU;EAKpC;EAGA,UAAU,IAAI,KAAK,MAAM;EAEzB,OAAO;CACT,SAAS,OAAO;EAEd,YAAY,IACV,wBACA,oCAAoC,IAAI,IAAI,OAAO,KAAK,GAC1D;EACA,OAAO;GACL,SAAS;GACT,UAAU;EACZ;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tableUtils.js","names":[],"sources":["../../src/shared/tableUtils.ts"],"sourcesContent":["import type { TableNode } from \"../types.ts\";\n\nconst DEFAULT_TABLE_ROW_HEIGHT = 32;\nconst DEFAULT_TABLE_COLUMN_WIDTH = 100;\n\nexport function calcTableIntrinsicSize(node: TableNode) {\n const width = node.columns.reduce(\n (sum, column) => sum + (column.width ?? DEFAULT_TABLE_COLUMN_WIDTH),\n 0,\n );\n const height = resolveRowHeights(node).reduce((sum, h) => sum + h, 0);\n\n return { width, height };\n}\n\nexport function resolveRowHeights(node: TableNode) {\n const fallbackRowHeight = node.defaultRowHeight ?? DEFAULT_TABLE_ROW_HEIGHT;\n return node.rows.map((row) => row.height ?? fallbackRowHeight);\n}\n\n/**\n * テーブルの各カラム幅を解決する\n * - 幅が指定されているカラムはその値を使用\n * - 幅が未指定のカラムは、残りの幅を均等分割\n *\n * @param node テーブルノード\n * @param tableWidth テーブル全体の幅(レイアウト計算後の確定値)\n */\nexport function resolveColumnWidths(\n node: TableNode,\n tableWidth: number,\n): number[] {\n const specifiedTotal = node.columns.reduce(\n (sum, col) => sum + (col.width ?? 0),\n 0,\n );\n const unspecifiedCount = node.columns.filter(\n (col) => col.width === undefined,\n ).length;\n\n // 未指定カラムがない場合、または未指定カラムに割り当てる幅を計算\n const remainingWidth = Math.max(0, tableWidth - specifiedTotal);\n const widthPerUnspecified =\n unspecifiedCount > 0 ? remainingWidth / unspecifiedCount : 0;\n\n return node.columns.map((col) => col.width ?? widthPerUnspecified);\n}\n"],"mappings":";AAEA,MAAM,2BAA2B;AACjC,MAAM,6BAA6B;AAEnC,SAAgB,uBAAuB,MAAiB;CAOtD,OAAO;EAAE,OANK,KAAK,QAAQ,QACxB,KAAK,WAAW,OAAO,OAAO,SAAS,6BACxC,CAIW;EAAG,QAFD,kBAAkB,IAAI,
|
|
1
|
+
{"version":3,"file":"tableUtils.js","names":[],"sources":["../../src/shared/tableUtils.ts"],"sourcesContent":["import type { TableNode } from \"../types.ts\";\n\nconst DEFAULT_TABLE_ROW_HEIGHT = 32;\nconst DEFAULT_TABLE_COLUMN_WIDTH = 100;\n\nexport function calcTableIntrinsicSize(node: TableNode) {\n const width = node.columns.reduce(\n (sum, column) => sum + (column.width ?? DEFAULT_TABLE_COLUMN_WIDTH),\n 0,\n );\n const height = resolveRowHeights(node).reduce((sum, h) => sum + h, 0);\n\n return { width, height };\n}\n\nexport function resolveRowHeights(node: TableNode) {\n const fallbackRowHeight = node.defaultRowHeight ?? DEFAULT_TABLE_ROW_HEIGHT;\n return node.rows.map((row) => row.height ?? fallbackRowHeight);\n}\n\n/**\n * テーブルの各カラム幅を解決する\n * - 幅が指定されているカラムはその値を使用\n * - 幅が未指定のカラムは、残りの幅を均等分割\n *\n * @param node テーブルノード\n * @param tableWidth テーブル全体の幅(レイアウト計算後の確定値)\n */\nexport function resolveColumnWidths(\n node: TableNode,\n tableWidth: number,\n): number[] {\n const specifiedTotal = node.columns.reduce(\n (sum, col) => sum + (col.width ?? 0),\n 0,\n );\n const unspecifiedCount = node.columns.filter(\n (col) => col.width === undefined,\n ).length;\n\n // 未指定カラムがない場合、または未指定カラムに割り当てる幅を計算\n const remainingWidth = Math.max(0, tableWidth - specifiedTotal);\n const widthPerUnspecified =\n unspecifiedCount > 0 ? remainingWidth / unspecifiedCount : 0;\n\n return node.columns.map((col) => col.width ?? widthPerUnspecified);\n}\n"],"mappings":";AAEA,MAAM,2BAA2B;AACjC,MAAM,6BAA6B;AAEnC,SAAgB,uBAAuB,MAAiB;CAOtD,OAAO;EAAE,OANK,KAAK,QAAQ,QACxB,KAAK,WAAW,OAAO,OAAO,SAAS,6BACxC,CAIW;EAAG,QAFD,kBAAkB,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM,MAAM,GAAG,CAE9C;CAAE;AACzB;AAEA,SAAgB,kBAAkB,MAAiB;CACjD,MAAM,oBAAoB,KAAK,oBAAoB;CACnD,OAAO,KAAK,KAAK,KAAK,QAAQ,IAAI,UAAU,iBAAiB;AAC/D;;;;;;;;;AAUA,SAAgB,oBACd,MACA,YACU;CACV,MAAM,iBAAiB,KAAK,QAAQ,QACjC,KAAK,QAAQ,OAAO,IAAI,SAAS,IAClC,CACF;CACA,MAAM,mBAAmB,KAAK,QAAQ,QACnC,QAAQ,IAAI,UAAU,KAAA,CACzB,CAAC,CAAC;CAGF,MAAM,iBAAiB,KAAK,IAAI,GAAG,aAAa,cAAc;CAC9D,MAAM,sBACJ,mBAAmB,IAAI,iBAAiB,mBAAmB;CAE7D,OAAO,KAAK,QAAQ,KAAK,QAAQ,IAAI,SAAS,mBAAmB;AACnE"}
|