@hirokisakabe/pom 8.1.0 → 8.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/buildPptx.d.ts +1 -1
- package/dist/clientApi.d.ts +4 -0
- package/dist/clientApi.js +3 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/parseXml/parseXml.d.ts +31 -1
- package/dist/parseXml/parseXml.d.ts.map +1 -1
- package/dist/parseXml/parseXml.js +1 -1
- package/dist/parseXml/serializeXml.d.ts +22 -0
- package/dist/parseXml/serializeXml.d.ts.map +1 -0
- package/dist/parseXml/serializeXml.js +99 -0
- package/dist/parseXml/serializeXml.js.map +1 -0
- package/dist/renderPptx/nodes/matrix.js +8 -5
- package/dist/renderPptx/nodes/matrix.js.map +1 -1
- package/dist/types.d.ts +2157 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -1
package/dist/buildPptx.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { SlideMasterOptions } from "./types.js";
|
|
1
2
|
import { TextMeasurementMode } from "./calcYogaLayout/measureText.js";
|
|
2
3
|
import { Diagnostic } from "./diagnostics.js";
|
|
3
|
-
import { SlideMasterOptions } from "./types.js";
|
|
4
4
|
|
|
5
5
|
//#region src/buildPptx.d.ts
|
|
6
6
|
interface BuildPptxResult {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { MasterImageObject, MasterLineObject, MasterObject, MasterRectObject, MasterTextObject, POMNode, SlideMasterBackground, SlideMasterMargin, SlideMasterOptions, SlideNumberOptions } from "./types.js";
|
|
2
|
+
import { ParseXmlError, parseXml } from "./parseXml/parseXml.js";
|
|
3
|
+
import { serializeXml } from "./parseXml/serializeXml.js";
|
|
1
4
|
import { TextMeasurementMode } from "./calcYogaLayout/measureText.js";
|
|
2
5
|
import { Diagnostic, DiagnosticCode, DiagnosticsError } from "./diagnostics.js";
|
|
3
|
-
import { MasterImageObject, MasterLineObject, MasterObject, MasterRectObject, MasterTextObject, SlideMasterBackground, SlideMasterMargin, SlideMasterOptions, SlideNumberOptions } from "./types.js";
|
|
4
6
|
import { BuildPptxResult, buildPptx } from "./buildPptx.js";
|
|
5
|
-
|
|
6
|
-
export { type BuildPptxResult, type Diagnostic, type DiagnosticCode, DiagnosticsError, type MasterImageObject, type MasterLineObject, type MasterObject, type MasterRectObject, type MasterTextObject, ParseXmlError, type SlideMasterBackground, type SlideMasterMargin, type SlideMasterOptions, type SlideNumberOptions, type TextMeasurementMode, buildPptx };
|
|
7
|
+
export { type BuildPptxResult, type Diagnostic, type DiagnosticCode, DiagnosticsError, type MasterImageObject, type MasterLineObject, type MasterObject, type MasterRectObject, type MasterTextObject, type POMNode, ParseXmlError, type SlideMasterBackground, type SlideMasterMargin, type SlideMasterOptions, type SlideNumberOptions, type TextMeasurementMode, buildPptx, parseXml, serializeXml };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DiagnosticsError } from "./diagnostics.js";
|
|
2
|
-
import { ParseXmlError } from "./parseXml/parseXml.js";
|
|
2
|
+
import { ParseXmlError, parseXml } from "./parseXml/parseXml.js";
|
|
3
3
|
import { buildPptx } from "./buildPptx.js";
|
|
4
|
-
|
|
4
|
+
import { serializeXml } from "./parseXml/serializeXml.js";
|
|
5
|
+
export { DiagnosticsError, ParseXmlError, buildPptx, parseXml, serializeXml };
|
|
@@ -1,8 +1,38 @@
|
|
|
1
|
+
import { POMNode } from "../types.js";
|
|
2
|
+
|
|
1
3
|
//#region src/parseXml/parseXml.d.ts
|
|
2
4
|
declare class ParseXmlError extends Error {
|
|
3
5
|
readonly errors: string[];
|
|
4
6
|
constructor(errors: string[]);
|
|
5
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* XML 文字列を POMNode 配列に変換する。
|
|
10
|
+
*
|
|
11
|
+
* 最上位は `<Slide>` 要素のみが許容される。各 `<Slide>` が 1 つのスライドに
|
|
12
|
+
* 対応し、その子要素がスライドのルート POMNode となる。子要素が複数ある場合は
|
|
13
|
+
* 暗黙的に VStack でラップされる。
|
|
14
|
+
*
|
|
15
|
+
* XML タグは POM ノードタイプにマッピングされ、属性値は Zod スキーマを参照して
|
|
16
|
+
* 適切な型(number, boolean, array, object)に変換される。
|
|
17
|
+
* 未知のタグ名が指定された場合はエラーがスローされる。
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* import { parseXml, buildPptx } from "@hirokisakabe/pom";
|
|
22
|
+
*
|
|
23
|
+
* const xml = `
|
|
24
|
+
* <Slide>
|
|
25
|
+
* <VStack gap="16" padding="32">
|
|
26
|
+
* <Text fontSize="32" bold="true">売上レポート</Text>
|
|
27
|
+
* </VStack>
|
|
28
|
+
* </Slide>
|
|
29
|
+
* `;
|
|
30
|
+
*
|
|
31
|
+
* const nodes = parseXml(xml);
|
|
32
|
+
* const pptx = await buildPptx(nodes, { w: 1280, h: 720 });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function parseXml(xmlString: string): POMNode[];
|
|
6
36
|
//#endregion
|
|
7
|
-
export { ParseXmlError };
|
|
37
|
+
export { ParseXmlError, parseXml };
|
|
8
38
|
//# sourceMappingURL=parseXml.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parseXml.d.ts","names":[],"sources":["../../src/parseXml/parseXml.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"parseXml.d.ts","names":[],"sources":["../../src/parseXml/parseXml.ts"],"mappings":";;;cAgCa,aAAA,SAAsB,KAAK;EAAA,SACtB,MAAA;cACJ,MAAA;AAAA;;;;;;;AAAgB;AAutC9B;;;;AAAoD;;;;;;;;;;;;;;;;iBAApC,QAAA,CAAS,SAAA,WAAoB,OAAO"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { POMNode } from "../types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/parseXml/serializeXml.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* POMNode 配列を XML 文字列に変換する。
|
|
6
|
+
*
|
|
7
|
+
* parseXml の逆操作として機能する。runs(インライン装飾)は B/I/A/U/S/Mark/Span
|
|
8
|
+
* タグとして child element に直列化されるため、テキストの装飾情報も保持される。
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { parseXml, serializeXml } from "@hirokisakabe/pom";
|
|
13
|
+
*
|
|
14
|
+
* const nodes = parseXml(xml);
|
|
15
|
+
* // ... ノードの並び替えなど ...
|
|
16
|
+
* const newXml = serializeXml(nodes);
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
declare function serializeXml(nodes: POMNode[]): string;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { serializeXml };
|
|
22
|
+
//# sourceMappingURL=serializeXml.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serializeXml.d.ts","names":[],"sources":["../../src/parseXml/serializeXml.ts"],"mappings":";;;;;AA8JA;;;;AAA6C;;;;;;;;;iBAA7B,YAAA,CAAa,KAAgB,EAAT,OAAO"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { TAG_TO_TYPE } from "./parseXml.js";
|
|
2
|
+
//#region src/parseXml/serializeXml.ts
|
|
3
|
+
const TYPE_TO_TAG = Object.fromEntries(Object.entries(TAG_TO_TYPE).map(([tag, type]) => [type, tag]));
|
|
4
|
+
const SKIP_KEYS = new Set([
|
|
5
|
+
"type",
|
|
6
|
+
"children",
|
|
7
|
+
"runs",
|
|
8
|
+
"svgContent"
|
|
9
|
+
]);
|
|
10
|
+
const INLINE_CONTENT_TYPES = new Set(["text", "shape"]);
|
|
11
|
+
const CONTAINER_TYPES = new Set([
|
|
12
|
+
"vstack",
|
|
13
|
+
"hstack",
|
|
14
|
+
"layer"
|
|
15
|
+
]);
|
|
16
|
+
function escapeAttrValue(value) {
|
|
17
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
18
|
+
}
|
|
19
|
+
function escapeXmlContent(value) {
|
|
20
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
21
|
+
}
|
|
22
|
+
function serializePrimitive(value) {
|
|
23
|
+
if (typeof value === "string") return escapeAttrValue(value);
|
|
24
|
+
if (typeof value === "number") return String(value);
|
|
25
|
+
return value ? "true" : "false";
|
|
26
|
+
}
|
|
27
|
+
function serializeAttrs(node) {
|
|
28
|
+
const parts = [];
|
|
29
|
+
for (const [key, value] of Object.entries(node)) {
|
|
30
|
+
if (SKIP_KEYS.has(key) || value === void 0) continue;
|
|
31
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
32
|
+
for (const [subKey, subValue] of Object.entries(value)) if (subValue !== void 0) parts.push(`${key}.${subKey}="${serializePrimitive(subValue)}"`);
|
|
33
|
+
} else if (Array.isArray(value)) parts.push(`${key}="${escapeAttrValue(JSON.stringify(value))}"`);
|
|
34
|
+
else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") parts.push(`${key}="${serializePrimitive(value)}"`);
|
|
35
|
+
}
|
|
36
|
+
return parts.length > 0 ? " " + parts.join(" ") : "";
|
|
37
|
+
}
|
|
38
|
+
function serializeRun(run) {
|
|
39
|
+
let content = escapeXmlContent(run.text);
|
|
40
|
+
if (run.href) content = `<A href="${escapeAttrValue(run.href)}">${content}</A>`;
|
|
41
|
+
if (run.highlight) content = `<Mark color="${escapeAttrValue(run.highlight)}">${content}</Mark>`;
|
|
42
|
+
const spanAttrs = [];
|
|
43
|
+
if (run.color) spanAttrs.push(`color="${escapeAttrValue(run.color)}"`);
|
|
44
|
+
if (run.fontFamily) spanAttrs.push(`fontFamily="${escapeAttrValue(run.fontFamily)}"`);
|
|
45
|
+
if (spanAttrs.length > 0) content = `<Span ${spanAttrs.join(" ")}>${content}</Span>`;
|
|
46
|
+
if (run.strike) content = `<S>${content}</S>`;
|
|
47
|
+
if (run.underline) content = `<U>${content}</U>`;
|
|
48
|
+
if (run.italic) content = `<I>${content}</I>`;
|
|
49
|
+
if (run.bold) content = `<B>${content}</B>`;
|
|
50
|
+
return content;
|
|
51
|
+
}
|
|
52
|
+
function serializeRuns(runs) {
|
|
53
|
+
return runs.map(serializeRun).join("");
|
|
54
|
+
}
|
|
55
|
+
function serializeNode(node, depth) {
|
|
56
|
+
const indent = " ".repeat(depth);
|
|
57
|
+
const tag = TYPE_TO_TAG[node.type];
|
|
58
|
+
const nodeRecord = node;
|
|
59
|
+
if (CONTAINER_TYPES.has(node.type)) {
|
|
60
|
+
const children = nodeRecord.children ?? [];
|
|
61
|
+
const attrStr = serializeAttrs(nodeRecord);
|
|
62
|
+
if (children.length === 0) return `${indent}<${tag}${attrStr} />`;
|
|
63
|
+
return `${indent}<${tag}${attrStr}>\n${children.map((c) => serializeNode(c, depth + 1)).join("\n")}\n${indent}</${tag}>`;
|
|
64
|
+
}
|
|
65
|
+
if (node.type === "svg") {
|
|
66
|
+
const svgContent = nodeRecord.svgContent ?? "";
|
|
67
|
+
return `${indent}<${tag}${serializeAttrs(nodeRecord)}>\n${svgContent}\n${indent}</${tag}>`;
|
|
68
|
+
}
|
|
69
|
+
if (INLINE_CONTENT_TYPES.has(node.type) && Array.isArray(nodeRecord.runs) && nodeRecord.runs.length > 0) {
|
|
70
|
+
const runs = nodeRecord.runs;
|
|
71
|
+
return `${indent}<${tag}${serializeAttrs({
|
|
72
|
+
...nodeRecord,
|
|
73
|
+
text: void 0
|
|
74
|
+
})}>${serializeRuns(runs)}</${tag}>`;
|
|
75
|
+
}
|
|
76
|
+
return `${indent}<${tag}${serializeAttrs(nodeRecord)} />`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* POMNode 配列を XML 文字列に変換する。
|
|
80
|
+
*
|
|
81
|
+
* parseXml の逆操作として機能する。runs(インライン装飾)は B/I/A/U/S/Mark/Span
|
|
82
|
+
* タグとして child element に直列化されるため、テキストの装飾情報も保持される。
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* import { parseXml, serializeXml } from "@hirokisakabe/pom";
|
|
87
|
+
*
|
|
88
|
+
* const nodes = parseXml(xml);
|
|
89
|
+
* // ... ノードの並び替えなど ...
|
|
90
|
+
* const newXml = serializeXml(nodes);
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
function serializeXml(nodes) {
|
|
94
|
+
return nodes.map((node) => `<Slide>\n${serializeNode(node, 1)}\n</Slide>`).join("\n");
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { serializeXml };
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=serializeXml.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serializeXml.js","names":[],"sources":["../../src/parseXml/serializeXml.ts"],"sourcesContent":["import type { POMNode } from \"../types.ts\";\nimport { TAG_TO_TYPE } from \"./parseXml.ts\";\n\nconst TYPE_TO_TAG: Record<string, string> = Object.fromEntries(\n Object.entries(TAG_TO_TYPE).map(([tag, type]) => [type, tag]),\n);\n\n// runs と svgContent は専用の直列化パスで処理する\nconst SKIP_KEYS = new Set([\"type\", \"children\", \"runs\", \"svgContent\"]);\n\n// runs によるインライン装飾を child element として直列化するノードタイプ\nconst INLINE_CONTENT_TYPES = new Set([\"text\", \"shape\"]);\n\nconst CONTAINER_TYPES = new Set([\"vstack\", \"hstack\", \"layer\"]);\n\ninterface TextRun {\n text: string;\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n strike?: boolean;\n highlight?: string;\n color?: string;\n href?: string;\n fontFamily?: string;\n}\n\nfunction escapeAttrValue(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n\nfunction escapeXmlContent(value: string): string {\n return value\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n\nfunction serializePrimitive(value: string | number | boolean): string {\n if (typeof value === \"string\") return escapeAttrValue(value);\n if (typeof value === \"number\") return String(value);\n return value ? \"true\" : \"false\";\n}\n\nfunction serializeAttrs(node: Record<string, unknown>): string {\n const parts: string[] = [];\n for (const [key, value] of Object.entries(node)) {\n if (SKIP_KEYS.has(key) || value === undefined) continue;\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n for (const [subKey, subValue] of Object.entries(\n value as Record<string, unknown>,\n )) {\n if (subValue !== undefined) {\n parts.push(\n `${key}.${subKey}=\"${serializePrimitive(subValue as string | number | boolean)}\"`,\n );\n }\n }\n } else if (Array.isArray(value)) {\n parts.push(`${key}=\"${escapeAttrValue(JSON.stringify(value))}\"`);\n } else if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n parts.push(`${key}=\"${serializePrimitive(value)}\"`);\n }\n }\n return parts.length > 0 ? \" \" + parts.join(\" \") : \"\";\n}\n\nfunction serializeRun(run: TextRun): string {\n let content = escapeXmlContent(run.text);\n\n if (run.href) {\n content = `<A href=\"${escapeAttrValue(run.href)}\">${content}</A>`;\n }\n if (run.highlight) {\n content = `<Mark color=\"${escapeAttrValue(run.highlight)}\">${content}</Mark>`;\n }\n const spanAttrs: string[] = [];\n if (run.color) spanAttrs.push(`color=\"${escapeAttrValue(run.color)}\"`);\n if (run.fontFamily)\n spanAttrs.push(`fontFamily=\"${escapeAttrValue(run.fontFamily)}\"`);\n if (spanAttrs.length > 0) {\n content = `<Span ${spanAttrs.join(\" \")}>${content}</Span>`;\n }\n if (run.strike) content = `<S>${content}</S>`;\n if (run.underline) content = `<U>${content}</U>`;\n if (run.italic) content = `<I>${content}</I>`;\n if (run.bold) content = `<B>${content}</B>`;\n return content;\n}\n\nfunction serializeRuns(runs: TextRun[]): string {\n return runs.map(serializeRun).join(\"\");\n}\n\nfunction serializeNode(node: POMNode, depth: number): string {\n const indent = \" \".repeat(depth);\n const tag = TYPE_TO_TAG[node.type];\n const nodeRecord = node as Record<string, unknown>;\n\n if (CONTAINER_TYPES.has(node.type)) {\n const children = (nodeRecord.children as POMNode[]) ?? [];\n const attrStr = serializeAttrs(nodeRecord);\n if (children.length === 0) {\n return `${indent}<${tag}${attrStr} />`;\n }\n const childrenStr = children\n .map((c) => serializeNode(c, depth + 1))\n .join(\"\\n\");\n return `${indent}<${tag}${attrStr}>\\n${childrenStr}\\n${indent}</${tag}>`;\n }\n\n if (node.type === \"svg\") {\n const svgContent = (nodeRecord.svgContent as string) ?? \"\";\n const attrStr = serializeAttrs(nodeRecord);\n return `${indent}<${tag}${attrStr}>\\n${svgContent}\\n${indent}</${tag}>`;\n }\n\n // Text / Shape: runs があればインライン child element として直列化し装飾を保持する\n if (\n INLINE_CONTENT_TYPES.has(node.type) &&\n Array.isArray(nodeRecord.runs) &&\n (nodeRecord.runs as unknown[]).length > 0\n ) {\n const runs = nodeRecord.runs as TextRun[];\n // runs がある場合、text は runs から復元できるため属性からも除外する\n const attrsWithoutText = { ...nodeRecord, text: undefined };\n const attrStr = serializeAttrs(attrsWithoutText);\n const inlineContent = serializeRuns(runs);\n return `${indent}<${tag}${attrStr}>${inlineContent}</${tag}>`;\n }\n\n const attrStr = serializeAttrs(nodeRecord);\n return `${indent}<${tag}${attrStr} />`;\n}\n\n/**\n * POMNode 配列を XML 文字列に変換する。\n *\n * parseXml の逆操作として機能する。runs(インライン装飾)は B/I/A/U/S/Mark/Span\n * タグとして child element に直列化されるため、テキストの装飾情報も保持される。\n *\n * @example\n * ```typescript\n * import { parseXml, serializeXml } from \"@hirokisakabe/pom\";\n *\n * const nodes = parseXml(xml);\n * // ... ノードの並び替えなど ...\n * const newXml = serializeXml(nodes);\n * ```\n */\nexport function serializeXml(nodes: POMNode[]): string {\n return nodes\n .map((node) => `<Slide>\\n${serializeNode(node, 1)}\\n</Slide>`)\n .join(\"\\n\");\n}\n"],"mappings":"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"}
|
|
@@ -56,16 +56,19 @@ function renderMatrixNode(node, ctx) {
|
|
|
56
56
|
align: "center",
|
|
57
57
|
valign: "top"
|
|
58
58
|
});
|
|
59
|
+
const yLabelW = 100 * scaleFactor;
|
|
60
|
+
const yLabelH = 20 * scaleFactor;
|
|
59
61
|
ctx.slide.addText(axes.y, {
|
|
60
|
-
x: pxToIn(content.x +
|
|
61
|
-
y: pxToIn(centerY -
|
|
62
|
-
w: pxToIn(
|
|
63
|
-
h: pxToIn(
|
|
62
|
+
x: pxToIn(content.x + axisMargin / 2 - yLabelW / 2),
|
|
63
|
+
y: pxToIn(centerY - yLabelH / 2),
|
|
64
|
+
w: pxToIn(yLabelW),
|
|
65
|
+
h: pxToIn(yLabelH),
|
|
64
66
|
fontSize: pxToPt(12 * scaleFactor),
|
|
65
67
|
fontFace: "Noto Sans JP",
|
|
66
68
|
color: "64748B",
|
|
67
69
|
align: "center",
|
|
68
|
-
valign: "middle"
|
|
70
|
+
valign: "middle",
|
|
71
|
+
rotate: 270
|
|
69
72
|
});
|
|
70
73
|
if (quadrants) renderQuadrantLabels(ctx, quadrants, areaX, areaY, areaW, areaH, centerX, centerY, scaleFactor);
|
|
71
74
|
const itemLabelW = 100 * scaleFactor;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"matrix.js","names":[],"sources":["../../../src/renderPptx/nodes/matrix.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn, pxToPt } from \"../units.ts\";\nimport { measureMatrix } from \"../../calcYogaLayout/measureCompositeNodes.ts\";\nimport { calcScaleFactor } from \"../utils/scaleToFit.ts\";\nimport { getContentArea } from \"../utils/contentArea.ts\";\n\ntype MatrixPositionedNode = Extract<PositionedNode, { type: \"matrix\" }>;\n\nexport function renderMatrixNode(\n node: MatrixPositionedNode,\n ctx: RenderContext,\n): void {\n const items = node.items;\n const axes = node.axes;\n const quadrants = node.quadrants;\n\n const defaultItemColor = \"1D4ED8\"; // blue\n const baseItemSize = 24; // px\n const baseLineWidth = 2; // px\n const axisColor = \"E2E8F0\";\n\n // スケール係数を計算(コンテンツ領域基準)\n const content = getContentArea(node);\n const intrinsic = measureMatrix(node);\n const scaleFactor = calcScaleFactor(\n content.w,\n content.h,\n intrinsic.width,\n intrinsic.height,\n \"matrix\",\n ctx.buildContext.diagnostics,\n );\n\n const itemSize = baseItemSize * scaleFactor;\n const lineWidth = baseLineWidth * scaleFactor;\n\n // マトリクスの描画領域(軸ラベル用の余白を考慮)\n const axisMargin = 60 * scaleFactor; // 軸ラベル用の余白\n const areaX = content.x + axisMargin;\n const areaY = content.y + axisMargin;\n const areaW = content.w - axisMargin * 2;\n const areaH = content.h - axisMargin * 2;\n\n // 中心座標\n const centerX = areaX + areaW / 2;\n const centerY = areaY + areaH / 2;\n\n // === 1. 十字線(軸線)を描画 ===\n // 横線(X軸)\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(areaX),\n y: pxToIn(centerY),\n w: pxToIn(areaW),\n h: 0,\n line: { color: axisColor, width: pxToPt(lineWidth) },\n });\n\n // 縦線(Y軸)\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(centerX),\n y: pxToIn(areaY),\n w: 0,\n h: pxToIn(areaH),\n line: { color: axisColor, width: pxToPt(lineWidth) },\n });\n\n // === 2. 軸ラベルを描画 ===\n const axisLabelW = 120 * scaleFactor;\n const axisLabelH = 24 * scaleFactor;\n\n // X軸ラベル(下部中央)\n ctx.slide.addText(axes.x, {\n x: pxToIn(centerX - axisLabelW / 2),\n y: pxToIn(areaY + areaH + 8 * scaleFactor),\n w: pxToIn(axisLabelW),\n h: pxToIn(axisLabelH),\n fontSize: pxToPt(12 * scaleFactor),\n fontFace: \"Noto Sans JP\",\n color: \"64748B\",\n align: \"center\",\n valign: \"top\",\n });\n\n // Y
|
|
1
|
+
{"version":3,"file":"matrix.js","names":[],"sources":["../../../src/renderPptx/nodes/matrix.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToIn, pxToPt } from \"../units.ts\";\nimport { measureMatrix } from \"../../calcYogaLayout/measureCompositeNodes.ts\";\nimport { calcScaleFactor } from \"../utils/scaleToFit.ts\";\nimport { getContentArea } from \"../utils/contentArea.ts\";\n\ntype MatrixPositionedNode = Extract<PositionedNode, { type: \"matrix\" }>;\n\nexport function renderMatrixNode(\n node: MatrixPositionedNode,\n ctx: RenderContext,\n): void {\n const items = node.items;\n const axes = node.axes;\n const quadrants = node.quadrants;\n\n const defaultItemColor = \"1D4ED8\"; // blue\n const baseItemSize = 24; // px\n const baseLineWidth = 2; // px\n const axisColor = \"E2E8F0\";\n\n // スケール係数を計算(コンテンツ領域基準)\n const content = getContentArea(node);\n const intrinsic = measureMatrix(node);\n const scaleFactor = calcScaleFactor(\n content.w,\n content.h,\n intrinsic.width,\n intrinsic.height,\n \"matrix\",\n ctx.buildContext.diagnostics,\n );\n\n const itemSize = baseItemSize * scaleFactor;\n const lineWidth = baseLineWidth * scaleFactor;\n\n // マトリクスの描画領域(軸ラベル用の余白を考慮)\n const axisMargin = 60 * scaleFactor; // 軸ラベル用の余白\n const areaX = content.x + axisMargin;\n const areaY = content.y + axisMargin;\n const areaW = content.w - axisMargin * 2;\n const areaH = content.h - axisMargin * 2;\n\n // 中心座標\n const centerX = areaX + areaW / 2;\n const centerY = areaY + areaH / 2;\n\n // === 1. 十字線(軸線)を描画 ===\n // 横線(X軸)\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(areaX),\n y: pxToIn(centerY),\n w: pxToIn(areaW),\n h: 0,\n line: { color: axisColor, width: pxToPt(lineWidth) },\n });\n\n // 縦線(Y軸)\n ctx.slide.addShape(ctx.pptx.ShapeType.line, {\n x: pxToIn(centerX),\n y: pxToIn(areaY),\n w: 0,\n h: pxToIn(areaH),\n line: { color: axisColor, width: pxToPt(lineWidth) },\n });\n\n // === 2. 軸ラベルを描画 ===\n const axisLabelW = 120 * scaleFactor;\n const axisLabelH = 24 * scaleFactor;\n\n // X軸ラベル(下部中央)\n ctx.slide.addText(axes.x, {\n x: pxToIn(centerX - axisLabelW / 2),\n y: pxToIn(areaY + areaH + 8 * scaleFactor),\n w: pxToIn(axisLabelW),\n h: pxToIn(axisLabelH),\n fontSize: pxToPt(12 * scaleFactor),\n fontFace: \"Noto Sans JP\",\n color: \"64748B\",\n align: \"center\",\n valign: \"top\",\n });\n\n // Y軸ラベル(左部中央)270° 回転で下から上読み。w が視覚的な高さになるため CJK 5 文字以上も収まる幅を確保\n const yLabelW = 100 * scaleFactor;\n const yLabelH = 20 * scaleFactor;\n ctx.slide.addText(axes.y, {\n x: pxToIn(content.x + axisMargin / 2 - yLabelW / 2),\n y: pxToIn(centerY - yLabelH / 2),\n w: pxToIn(yLabelW),\n h: pxToIn(yLabelH),\n fontSize: pxToPt(12 * scaleFactor),\n fontFace: \"Noto Sans JP\",\n color: \"64748B\",\n align: \"center\",\n valign: \"middle\",\n rotate: 270,\n });\n\n // === 3. 象限ラベルを描画 ===\n if (quadrants) {\n renderQuadrantLabels(\n ctx,\n quadrants,\n areaX,\n areaY,\n areaW,\n areaH,\n centerX,\n centerY,\n scaleFactor,\n );\n }\n\n // === 4. アイテムをプロット ===\n const itemLabelW = 100 * scaleFactor;\n const itemLabelH = 18 * scaleFactor;\n\n for (const item of items) {\n // 座標変換: (0,0)=左下, (1,1)=右上\n // x: 0 -> areaX, 1 -> areaX + areaW\n // y: 0 -> areaY + areaH, 1 -> areaY (反転)\n const itemX = areaX + item.x * areaW;\n const itemY = areaY + (1 - item.y) * areaH; // Y軸反転\n const itemColor = item.color ?? defaultItemColor;\n\n // 円を描画\n ctx.slide.addShape(ctx.pptx.ShapeType.ellipse, {\n x: pxToIn(itemX - itemSize / 2),\n y: pxToIn(itemY - itemSize / 2),\n w: pxToIn(itemSize),\n h: pxToIn(itemSize),\n fill: { color: itemColor },\n line: { type: \"none\" as const },\n });\n\n // ラベルを描画(円の上)\n ctx.slide.addText(item.label, {\n x: pxToIn(itemX - itemLabelW / 2),\n y: pxToIn(itemY - itemSize / 2 - 20 * scaleFactor),\n w: pxToIn(itemLabelW),\n h: pxToIn(itemLabelH),\n fontSize: pxToPt(11 * scaleFactor),\n fontFace: \"Noto Sans JP\",\n color: \"1E293B\",\n bold: true,\n align: \"center\",\n valign: \"bottom\",\n });\n }\n}\n\nfunction renderQuadrantLabels(\n ctx: RenderContext,\n quadrants: NonNullable<MatrixPositionedNode[\"quadrants\"]>,\n areaX: number,\n areaY: number,\n areaW: number,\n areaH: number,\n centerX: number,\n centerY: number,\n scaleFactor: number,\n): void {\n const quadrantFontSize = 11 * scaleFactor;\n const quadrantColor = \"94A3B8\"; // slate-400\n const quadrantInset = 10 * scaleFactor;\n const quadrantW = areaW / 2 - 20 * scaleFactor;\n const quadrantH = 48 * scaleFactor;\n\n // 左上\n ctx.slide.addText(quadrants.topLeft, {\n x: pxToIn(areaX + quadrantInset),\n y: pxToIn(areaY + quadrantInset),\n w: pxToIn(quadrantW),\n h: pxToIn(quadrantH),\n fontSize: pxToPt(quadrantFontSize),\n fontFace: \"Noto Sans JP\",\n color: quadrantColor,\n align: \"left\",\n valign: \"top\",\n });\n\n // 右上\n ctx.slide.addText(quadrants.topRight, {\n x: pxToIn(centerX + quadrantInset),\n y: pxToIn(areaY + quadrantInset),\n w: pxToIn(quadrantW),\n h: pxToIn(quadrantH),\n fontSize: pxToPt(quadrantFontSize),\n fontFace: \"Noto Sans JP\",\n color: quadrantColor,\n align: \"right\",\n valign: \"top\",\n });\n\n // 左下\n ctx.slide.addText(quadrants.bottomLeft, {\n x: pxToIn(areaX + quadrantInset),\n y: pxToIn(centerY + areaH / 2 - quadrantH - quadrantInset),\n w: pxToIn(quadrantW),\n h: pxToIn(quadrantH),\n fontSize: pxToPt(quadrantFontSize),\n fontFace: \"Noto Sans JP\",\n color: quadrantColor,\n align: \"left\",\n valign: \"bottom\",\n });\n\n // 右下\n ctx.slide.addText(quadrants.bottomRight, {\n x: pxToIn(centerX + quadrantInset),\n y: pxToIn(centerY + areaH / 2 - quadrantH - quadrantInset),\n w: pxToIn(quadrantW),\n h: pxToIn(quadrantH),\n fontSize: pxToPt(quadrantFontSize),\n fontFace: \"Noto Sans JP\",\n color: quadrantColor,\n align: \"right\",\n valign: \"bottom\",\n });\n}\n"],"mappings":"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"}
|