@hirokisakabe/pom 8.5.0 → 8.5.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/calcYogaLayout/fontLoader.js +30 -1
- package/dist/calcYogaLayout/fontLoader.js.map +1 -1
- package/dist/registry/definitions/text.js +1 -1
- package/dist/registry/definitions/text.js.map +1 -1
- package/dist/renderPptx/nodes/shape.js +19 -16
- package/dist/renderPptx/nodes/shape.js.map +1 -1
- package/dist/renderPptx/nodes/text.js +1 -1
- package/dist/renderPptx/nodes/text.js.map +1 -1
- package/dist/renderPptx/textOptions.js +22 -3
- package/dist/renderPptx/textOptions.js.map +1 -1
- package/package.json +1 -1
|
@@ -51,6 +51,35 @@ function measureTextWidth(text, fontSizePx, weight) {
|
|
|
51
51
|
return getFont(weight).getAdvanceWidth(text, fontSizePx, { kerning: true });
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
|
+
* フォントの縦方向メトリクスを fontSizePx に対する比率で取得する
|
|
55
|
+
*
|
|
56
|
+
* - typoAscender / typoDescender: グリフ ink のおおよその上端・下端
|
|
57
|
+
* (descender は正の値に符号反転して返す)
|
|
58
|
+
* - winDescent: レンダラが固定行送り (spcPts) のときに行下端から
|
|
59
|
+
* baseline までの距離として確保する descent
|
|
60
|
+
*
|
|
61
|
+
* バンドル外フォント使用時もバンドルフォント (Noto Sans JP) の値を
|
|
62
|
+
* 近似値として使う想定 (テキスト幅計測と同じ方針)。
|
|
63
|
+
*
|
|
64
|
+
* @param weight フォントウェイト
|
|
65
|
+
* @returns 各メトリクスの fontSizePx に対する比率
|
|
66
|
+
*/
|
|
67
|
+
function measureFontVerticalMetricsRatio(weight) {
|
|
68
|
+
const font = getFont(weight);
|
|
69
|
+
const upm = font.unitsPerEm;
|
|
70
|
+
const os2 = font.tables?.os2;
|
|
71
|
+
if (!os2) return {
|
|
72
|
+
typoAscender: .88,
|
|
73
|
+
typoDescender: .12,
|
|
74
|
+
winDescent: .288
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
typoAscender: os2.sTypoAscender / upm,
|
|
78
|
+
typoDescender: -os2.sTypoDescender / upm,
|
|
79
|
+
winDescent: os2.usWinDescent / upm
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
54
83
|
* フォントの自然な行高さ比率を取得する
|
|
55
84
|
*
|
|
56
85
|
* PowerPoint の lineHeight はフォントサイズではなく、
|
|
@@ -74,6 +103,6 @@ function measureFontLineHeightRatio(weight) {
|
|
|
74
103
|
return (os2.usWinAscent + os2.usWinDescent) / upm;
|
|
75
104
|
}
|
|
76
105
|
//#endregion
|
|
77
|
-
export { isBundledFont, measureFontLineHeightRatio, measureTextWidth };
|
|
106
|
+
export { isBundledFont, measureFontLineHeightRatio, measureFontVerticalMetricsRatio, measureTextWidth };
|
|
78
107
|
|
|
79
108
|
//# sourceMappingURL=fontLoader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fontLoader.js","names":[],"sources":["../../src/calcYogaLayout/fontLoader.ts"],"sourcesContent":["/**\n * opentype.js を使用したフォント読み込みモジュール\n * Node.js とブラウザ両方で動作する\n */\n\nimport type { Font } from \"opentype.js\";\nimport * as opentypeModule from \"opentype.js\";\nimport { NOTO_SANS_JP_REGULAR_BASE64 } from \"./fonts/notoSansJPRegular.ts\";\nimport { NOTO_SANS_JP_BOLD_BASE64 } from \"./fonts/notoSansJPBold.ts\";\n\n// opentype.js 2.0 は ESM ビルドで named export のみを提供する一方、\n// CJS UMD ビルドでは module.exports = factory() の動的構造のため\n// Node ESM から取り込むと named exports が静的解析できない。\n// どちらの形でも動くよう default プロパティを優先して unwrap する。\nconst opentype =\n (opentypeModule as unknown as { default?: typeof opentypeModule }).default ??\n opentypeModule;\n\n// フォントキャッシュ\nconst fontCache = new Map<string, Font>();\n\n/**\n * Base64 文字列を ArrayBuffer に変換する\n * Node.js とブラウザ両方で動作する\n */\nfunction base64ToArrayBuffer(base64: string): ArrayBuffer {\n // Node.js 環境\n if (typeof Buffer !== \"undefined\") {\n const buffer = Buffer.from(base64, \"base64\");\n return buffer.buffer.slice(\n buffer.byteOffset,\n buffer.byteOffset + buffer.byteLength,\n );\n }\n // ブラウザ環境\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * フォントを取得する(キャッシュ付き)\n * @param weight フォントウェイト (\"normal\" or \"bold\")\n * @returns opentype.js の Font オブジェクト\n */\nfunction getFont(weight: \"normal\" | \"bold\"): Font {\n const cacheKey = weight;\n\n // キャッシュがあればそれを返す\n const cached = fontCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n // Base64 データを選択\n const base64 =\n weight === \"bold\" ? NOTO_SANS_JP_BOLD_BASE64 : NOTO_SANS_JP_REGULAR_BASE64;\n\n // ArrayBuffer に変換してパース\n const buffer = base64ToArrayBuffer(base64);\n const font = opentype.parse(buffer);\n\n // キャッシュに保存\n fontCache.set(cacheKey, font);\n\n return font;\n}\n\n/** バンドル済みフォント名の一覧 */\nconst BUNDLED_FONT_NAMES = new Set([\"Noto Sans JP\"]);\n\n/**\n * 指定されたフォントがバンドル済みかどうかを判定する\n */\nexport function isBundledFont(fontFamily: string): boolean {\n return BUNDLED_FONT_NAMES.has(fontFamily);\n}\n\n/**\n * 指定したテキストの幅を計測する\n * @param text 計測するテキスト\n * @param fontSizePx フォントサイズ(ピクセル)\n * @param weight フォントウェイト\n * @returns テキスト幅(ピクセル)\n */\nexport function measureTextWidth(\n text: string,\n fontSizePx: number,\n weight: \"normal\" | \"bold\",\n): number {\n const font = getFont(weight);\n return font.getAdvanceWidth(text, fontSizePx, { kerning: true });\n}\n\n/**\n * フォントの自然な行高さ比率を取得する\n *\n * PowerPoint の lineHeight はフォントサイズではなく、\n * フォントメトリクス(ascent + descent)に対する倍率として適用される。\n * この関数は fontSizePx に対する自然な行高さの比率を返す。\n *\n * - USE_TYPO_METRICS (fsSelection bit 7) が設定されている場合:\n * sTypoAscender, sTypoDescender, sTypoLineGap を使用\n * - 設定されていない場合:\n * usWinAscent, usWinDescent を使用\n *\n * @param weight フォントウェイト\n * @returns fontSizePx に対する行高さの比率(例: 1.448)\n */\nexport function measureFontLineHeightRatio(weight: \"normal\" | \"bold\"): number {\n const font = getFont(weight);\n const upm = font.unitsPerEm;\n const os2 = font.tables?.os2;\n\n if (!os2) {\n return 1.0;\n }\n\n const useTypoMetrics = Boolean(os2.fsSelection & (1 << 7));\n\n if (useTypoMetrics) {\n return (os2.sTypoAscender - os2.sTypoDescender + os2.sTypoLineGap) / upm;\n }\n\n return (os2.usWinAscent + os2.usWinDescent) / upm;\n}\n"],"mappings":";;;;AAcA,MAAM,WACH,eAAkE,WACnE;AAGF,MAAM,4BAAY,IAAI,IAAkB;;;;;AAMxC,SAAS,oBAAoB,QAA6B;CAExD,IAAI,OAAO,WAAW,aAAa;EACjC,MAAM,SAAS,OAAO,KAAK,QAAQ,QAAQ;EAC3C,OAAO,OAAO,OAAO,MACnB,OAAO,YACP,OAAO,aAAa,OAAO,UAC7B;CACF;CAEA,MAAM,eAAe,KAAK,MAAM;CAChC,MAAM,QAAQ,IAAI,WAAW,aAAa,MAAM;CAChD,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KACvC,MAAM,KAAK,aAAa,WAAW,CAAC;CAEtC,OAAO,MAAM;AACf;;;;;;AAOA,SAAS,QAAQ,QAAiC;CAChD,MAAM,WAAW;CAGjB,MAAM,SAAS,UAAU,IAAI,QAAQ;CACrC,IAAI,QACF,OAAO;CAQT,MAAM,SAAS,oBAHb,WAAW,SAAS,2BAA2B,2BAGR;CACzC,MAAM,OAAO,SAAS,MAAM,MAAM;CAGlC,UAAU,IAAI,UAAU,IAAI;CAE5B,OAAO;AACT;;AAGA,MAAM,qBAAqB,IAAI,IAAI,CAAC,cAAc,CAAC;;;;AAKnD,SAAgB,cAAc,YAA6B;CACzD,OAAO,mBAAmB,IAAI,UAAU;AAC1C;;;;;;;;AASA,SAAgB,iBACd,MACA,YACA,QACQ;CAER,OADa,QAAQ,MACX,CAAC,CAAC,gBAAgB,MAAM,YAAY,EAAE,SAAS,KAAK,CAAC;AACjE;;;;;;;;;;;;;;;;AAiBA,SAAgB,2BAA2B,QAAmC;CAC5E,MAAM,OAAO,QAAQ,MAAM;CAC3B,MAAM,MAAM,KAAK;CACjB,MAAM,MAAM,KAAK,QAAQ;CAEzB,IAAI,CAAC,KACH,OAAO;CAKT,IAFuB,QAAQ,IAAI,cAAe,GAEjC,GACf,QAAQ,IAAI,gBAAgB,IAAI,iBAAiB,IAAI,gBAAgB;CAGvE,QAAQ,IAAI,cAAc,IAAI,gBAAgB;AAChD"}
|
|
1
|
+
{"version":3,"file":"fontLoader.js","names":[],"sources":["../../src/calcYogaLayout/fontLoader.ts"],"sourcesContent":["/**\n * opentype.js を使用したフォント読み込みモジュール\n * Node.js とブラウザ両方で動作する\n */\n\nimport type { Font } from \"opentype.js\";\nimport * as opentypeModule from \"opentype.js\";\nimport { NOTO_SANS_JP_REGULAR_BASE64 } from \"./fonts/notoSansJPRegular.ts\";\nimport { NOTO_SANS_JP_BOLD_BASE64 } from \"./fonts/notoSansJPBold.ts\";\n\n// opentype.js 2.0 は ESM ビルドで named export のみを提供する一方、\n// CJS UMD ビルドでは module.exports = factory() の動的構造のため\n// Node ESM から取り込むと named exports が静的解析できない。\n// どちらの形でも動くよう default プロパティを優先して unwrap する。\nconst opentype =\n (opentypeModule as unknown as { default?: typeof opentypeModule }).default ??\n opentypeModule;\n\n// フォントキャッシュ\nconst fontCache = new Map<string, Font>();\n\n/**\n * Base64 文字列を ArrayBuffer に変換する\n * Node.js とブラウザ両方で動作する\n */\nfunction base64ToArrayBuffer(base64: string): ArrayBuffer {\n // Node.js 環境\n if (typeof Buffer !== \"undefined\") {\n const buffer = Buffer.from(base64, \"base64\");\n return buffer.buffer.slice(\n buffer.byteOffset,\n buffer.byteOffset + buffer.byteLength,\n );\n }\n // ブラウザ環境\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * フォントを取得する(キャッシュ付き)\n * @param weight フォントウェイト (\"normal\" or \"bold\")\n * @returns opentype.js の Font オブジェクト\n */\nfunction getFont(weight: \"normal\" | \"bold\"): Font {\n const cacheKey = weight;\n\n // キャッシュがあればそれを返す\n const cached = fontCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n\n // Base64 データを選択\n const base64 =\n weight === \"bold\" ? NOTO_SANS_JP_BOLD_BASE64 : NOTO_SANS_JP_REGULAR_BASE64;\n\n // ArrayBuffer に変換してパース\n const buffer = base64ToArrayBuffer(base64);\n const font = opentype.parse(buffer);\n\n // キャッシュに保存\n fontCache.set(cacheKey, font);\n\n return font;\n}\n\n/** バンドル済みフォント名の一覧 */\nconst BUNDLED_FONT_NAMES = new Set([\"Noto Sans JP\"]);\n\n/**\n * 指定されたフォントがバンドル済みかどうかを判定する\n */\nexport function isBundledFont(fontFamily: string): boolean {\n return BUNDLED_FONT_NAMES.has(fontFamily);\n}\n\n/**\n * 指定したテキストの幅を計測する\n * @param text 計測するテキスト\n * @param fontSizePx フォントサイズ(ピクセル)\n * @param weight フォントウェイト\n * @returns テキスト幅(ピクセル)\n */\nexport function measureTextWidth(\n text: string,\n fontSizePx: number,\n weight: \"normal\" | \"bold\",\n): number {\n const font = getFont(weight);\n return font.getAdvanceWidth(text, fontSizePx, { kerning: true });\n}\n\n/**\n * フォントの縦方向メトリクスを fontSizePx に対する比率で取得する\n *\n * - typoAscender / typoDescender: グリフ ink のおおよその上端・下端\n * (descender は正の値に符号反転して返す)\n * - winDescent: レンダラが固定行送り (spcPts) のときに行下端から\n * baseline までの距離として確保する descent\n *\n * バンドル外フォント使用時もバンドルフォント (Noto Sans JP) の値を\n * 近似値として使う想定 (テキスト幅計測と同じ方針)。\n *\n * @param weight フォントウェイト\n * @returns 各メトリクスの fontSizePx に対する比率\n */\nexport function measureFontVerticalMetricsRatio(weight: \"normal\" | \"bold\"): {\n typoAscender: number;\n typoDescender: number;\n winDescent: number;\n} {\n const font = getFont(weight);\n const upm = font.unitsPerEm;\n const os2 = font.tables?.os2;\n\n if (!os2) {\n // メトリクスが取れない場合は Noto Sans JP 相当の値で近似する\n return { typoAscender: 0.88, typoDescender: 0.12, winDescent: 0.288 };\n }\n\n return {\n typoAscender: os2.sTypoAscender / upm,\n typoDescender: -os2.sTypoDescender / upm,\n winDescent: os2.usWinDescent / upm,\n };\n}\n\n/**\n * フォントの自然な行高さ比率を取得する\n *\n * PowerPoint の lineHeight はフォントサイズではなく、\n * フォントメトリクス(ascent + descent)に対する倍率として適用される。\n * この関数は fontSizePx に対する自然な行高さの比率を返す。\n *\n * - USE_TYPO_METRICS (fsSelection bit 7) が設定されている場合:\n * sTypoAscender, sTypoDescender, sTypoLineGap を使用\n * - 設定されていない場合:\n * usWinAscent, usWinDescent を使用\n *\n * @param weight フォントウェイト\n * @returns fontSizePx に対する行高さの比率(例: 1.448)\n */\nexport function measureFontLineHeightRatio(weight: \"normal\" | \"bold\"): number {\n const font = getFont(weight);\n const upm = font.unitsPerEm;\n const os2 = font.tables?.os2;\n\n if (!os2) {\n return 1.0;\n }\n\n const useTypoMetrics = Boolean(os2.fsSelection & (1 << 7));\n\n if (useTypoMetrics) {\n return (os2.sTypoAscender - os2.sTypoDescender + os2.sTypoLineGap) / upm;\n }\n\n return (os2.usWinAscent + os2.usWinDescent) / upm;\n}\n"],"mappings":";;;;AAcA,MAAM,WACH,eAAkE,WACnE;AAGF,MAAM,4BAAY,IAAI,IAAkB;;;;;AAMxC,SAAS,oBAAoB,QAA6B;CAExD,IAAI,OAAO,WAAW,aAAa;EACjC,MAAM,SAAS,OAAO,KAAK,QAAQ,QAAQ;EAC3C,OAAO,OAAO,OAAO,MACnB,OAAO,YACP,OAAO,aAAa,OAAO,UAC7B;CACF;CAEA,MAAM,eAAe,KAAK,MAAM;CAChC,MAAM,QAAQ,IAAI,WAAW,aAAa,MAAM;CAChD,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KACvC,MAAM,KAAK,aAAa,WAAW,CAAC;CAEtC,OAAO,MAAM;AACf;;;;;;AAOA,SAAS,QAAQ,QAAiC;CAChD,MAAM,WAAW;CAGjB,MAAM,SAAS,UAAU,IAAI,QAAQ;CACrC,IAAI,QACF,OAAO;CAQT,MAAM,SAAS,oBAHb,WAAW,SAAS,2BAA2B,2BAGR;CACzC,MAAM,OAAO,SAAS,MAAM,MAAM;CAGlC,UAAU,IAAI,UAAU,IAAI;CAE5B,OAAO;AACT;;AAGA,MAAM,qBAAqB,IAAI,IAAI,CAAC,cAAc,CAAC;;;;AAKnD,SAAgB,cAAc,YAA6B;CACzD,OAAO,mBAAmB,IAAI,UAAU;AAC1C;;;;;;;;AASA,SAAgB,iBACd,MACA,YACA,QACQ;CAER,OADa,QAAQ,MACX,CAAC,CAAC,gBAAgB,MAAM,YAAY,EAAE,SAAS,KAAK,CAAC;AACjE;;;;;;;;;;;;;;;AAgBA,SAAgB,gCAAgC,QAI9C;CACA,MAAM,OAAO,QAAQ,MAAM;CAC3B,MAAM,MAAM,KAAK;CACjB,MAAM,MAAM,KAAK,QAAQ;CAEzB,IAAI,CAAC,KAEH,OAAO;EAAE,cAAc;EAAM,eAAe;EAAM,YAAY;CAAM;CAGtE,OAAO;EACL,cAAc,IAAI,gBAAgB;EAClC,eAAe,CAAC,IAAI,iBAAiB;EACrC,YAAY,IAAI,eAAe;CACjC;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,2BAA2B,QAAmC;CAC5E,MAAM,OAAO,QAAQ,MAAM;CAC3B,MAAM,MAAM,KAAK;CACjB,MAAM,MAAM,KAAK,QAAQ;CAEzB,IAAI,CAAC,KACH,OAAO;CAKT,IAFuB,QAAQ,IAAI,cAAe,GAEjC,GACf,QAAQ,IAAI,gBAAgB,IAAI,iBAAiB,IAAI,gBAAgB;CAGvE,QAAQ,IAAI,cAAc,IAAI,gBAAgB;AAChD"}
|
|
@@ -10,7 +10,7 @@ const textNodeDef = {
|
|
|
10
10
|
const fontSizePx = n.fontSize ?? 24;
|
|
11
11
|
const fontFamily = n.fontFamily ?? "Noto Sans JP";
|
|
12
12
|
const fontWeight = n.bold ? "bold" : "normal";
|
|
13
|
-
const lineHeight = 1.3;
|
|
13
|
+
const lineHeight = n.lineHeight ?? 1.3;
|
|
14
14
|
const letterSpacingPx = n.letterSpacing;
|
|
15
15
|
yn.setMeasureFunc((width, widthMode) => {
|
|
16
16
|
const { widthPx, heightPx } = measureText(text, (() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","names":[],"sources":["../../../src/registry/definitions/text.ts"],"sourcesContent":["import type { POMNode } from \"../../types.ts\";\nimport type { NodeDefinition, Yoga } from \"../types.ts\";\nimport type { Node as YogaNode } from \"yoga-layout\";\nimport { measureText } from \"../../calcYogaLayout/measureText.ts\";\nimport type { BuildContext } from \"../../buildContext.ts\";\nimport { renderTextNode } from \"../../renderPptx/nodes/text.ts\";\nimport { getNodeMetadata } from \"../nodeMetadata.ts\";\n\nexport const textNodeDef: NodeDefinition = {\n ...getNodeMetadata(\"text\"),\n applyYogaStyle(node: POMNode, yn: YogaNode, yoga: Yoga, ctx: BuildContext) {\n const n = node as Extract<POMNode, { type: \"text\" }>;\n const text = n.text;\n const fontSizePx = n.fontSize ?? 24;\n const fontFamily = n.fontFamily ?? \"Noto Sans JP\";\n const fontWeight = n.bold ? \"bold\" : \"normal\";\n const lineHeight = 1.3;\n const letterSpacingPx = n.letterSpacing;\n\n yn.setMeasureFunc((width, widthMode) => {\n const maxWidthPx = (() => {\n switch (widthMode) {\n case yoga.MEASURE_MODE_UNDEFINED:\n return Number.POSITIVE_INFINITY;\n case yoga.MEASURE_MODE_EXACTLY:\n case yoga.MEASURE_MODE_AT_MOST:\n return width;\n default:\n return Number.POSITIVE_INFINITY;\n }\n })();\n\n const { widthPx, heightPx } = measureText(\n text,\n maxWidthPx,\n {\n fontFamily,\n fontSizePx,\n lineHeight,\n fontWeight,\n letterSpacingPx,\n },\n ctx.textMeasurementMode,\n );\n\n return { width: widthPx, height: heightPx };\n });\n },\n render(node, ctx) {\n renderTextNode(node as Extract<typeof node, { type: \"text\" }>, ctx);\n },\n};\n"],"mappings":";;;;AAQA,MAAa,cAA8B;CACzC,GAAG,gBAAgB,MAAM;CACzB,eAAe,MAAe,IAAc,MAAY,KAAmB;EACzE,MAAM,IAAI;EACV,MAAM,OAAO,EAAE;EACf,MAAM,aAAa,EAAE,YAAY;EACjC,MAAM,aAAa,EAAE,cAAc;EACnC,MAAM,aAAa,EAAE,OAAO,SAAS;EACrC,MAAM,aAAa;
|
|
1
|
+
{"version":3,"file":"text.js","names":[],"sources":["../../../src/registry/definitions/text.ts"],"sourcesContent":["import type { POMNode } from \"../../types.ts\";\nimport type { NodeDefinition, Yoga } from \"../types.ts\";\nimport type { Node as YogaNode } from \"yoga-layout\";\nimport { measureText } from \"../../calcYogaLayout/measureText.ts\";\nimport type { BuildContext } from \"../../buildContext.ts\";\nimport { renderTextNode } from \"../../renderPptx/nodes/text.ts\";\nimport { getNodeMetadata } from \"../nodeMetadata.ts\";\n\nexport const textNodeDef: NodeDefinition = {\n ...getNodeMetadata(\"text\"),\n applyYogaStyle(node: POMNode, yn: YogaNode, yoga: Yoga, ctx: BuildContext) {\n const n = node as Extract<POMNode, { type: \"text\" }>;\n const text = n.text;\n const fontSizePx = n.fontSize ?? 24;\n const fontFamily = n.fontFamily ?? \"Noto Sans JP\";\n const fontWeight = n.bold ? \"bold\" : \"normal\";\n const lineHeight = n.lineHeight ?? 1.3;\n const letterSpacingPx = n.letterSpacing;\n\n yn.setMeasureFunc((width, widthMode) => {\n const maxWidthPx = (() => {\n switch (widthMode) {\n case yoga.MEASURE_MODE_UNDEFINED:\n return Number.POSITIVE_INFINITY;\n case yoga.MEASURE_MODE_EXACTLY:\n case yoga.MEASURE_MODE_AT_MOST:\n return width;\n default:\n return Number.POSITIVE_INFINITY;\n }\n })();\n\n const { widthPx, heightPx } = measureText(\n text,\n maxWidthPx,\n {\n fontFamily,\n fontSizePx,\n lineHeight,\n fontWeight,\n letterSpacingPx,\n },\n ctx.textMeasurementMode,\n );\n\n return { width: widthPx, height: heightPx };\n });\n },\n render(node, ctx) {\n renderTextNode(node as Extract<typeof node, { type: \"text\" }>, ctx);\n },\n};\n"],"mappings":";;;;AAQA,MAAa,cAA8B;CACzC,GAAG,gBAAgB,MAAM;CACzB,eAAe,MAAe,IAAc,MAAY,KAAmB;EACzE,MAAM,IAAI;EACV,MAAM,OAAO,EAAE;EACf,MAAM,aAAa,EAAE,YAAY;EACjC,MAAM,aAAa,EAAE,cAAc;EACnC,MAAM,aAAa,EAAE,OAAO,SAAS;EACrC,MAAM,aAAa,EAAE,cAAc;EACnC,MAAM,kBAAkB,EAAE;EAE1B,GAAG,gBAAgB,OAAO,cAAc;GAatC,MAAM,EAAE,SAAS,aAAa,YAC5B,aAbwB;IACxB,QAAQ,WAAR;KACE,KAAK,KAAK,wBACR,OAAO,OAAO;KAChB,KAAK,KAAK;KACV,KAAK,KAAK,sBACR,OAAO;KACT,SACE,OAAO,OAAO;IAClB;GACF,EAAA,CAIW,GACT;IACE;IACA;IACA;IACA;IACA;GACF,GACA,IAAI,mBACN;GAEA,OAAO;IAAE,OAAO;IAAS,QAAQ;GAAS;EAC5C,CAAC;CACH;CACA,OAAO,MAAM,KAAK;EAChB,eAAe,MAAgD,GAAG;CACpE;AACF"}
|
|
@@ -14,22 +14,25 @@ function renderShapeNode(node, ctx) {
|
|
|
14
14
|
shadow: convertShadow(node.shadow),
|
|
15
15
|
rotate: node.rotate
|
|
16
16
|
};
|
|
17
|
-
if (node.text)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
if (node.text) {
|
|
18
|
+
const fontSizePx = node.fontSize ?? 24;
|
|
19
|
+
const lineHeight = node.lineHeight ?? 1.3;
|
|
20
|
+
ctx.slide.addText(node.text, {
|
|
21
|
+
...shapeOptions,
|
|
22
|
+
shape: node.shapeType,
|
|
23
|
+
fontSize: pxToPt(fontSizePx),
|
|
24
|
+
fontFace: node.fontFamily ?? "Noto Sans JP",
|
|
25
|
+
color: node.color,
|
|
26
|
+
bold: node.bold,
|
|
27
|
+
italic: node.italic,
|
|
28
|
+
underline: convertUnderline(node.underline),
|
|
29
|
+
strike: convertStrike(node.strike),
|
|
30
|
+
highlight: node.highlight,
|
|
31
|
+
align: node.textAlign ?? "center",
|
|
32
|
+
valign: "middle",
|
|
33
|
+
lineSpacing: pxToPt(fontSizePx * lineHeight)
|
|
34
|
+
});
|
|
35
|
+
} else ctx.slide.addShape(node.shapeType, shapeOptions);
|
|
33
36
|
}
|
|
34
37
|
//#endregion
|
|
35
38
|
export { renderShapeNode };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shape.js","names":[],"sources":["../../../src/renderPptx/nodes/shape.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToPt } from \"../units.ts\";\nimport { convertUnderline, convertStrike } from \"../textOptions.ts\";\nimport { getContentAreaIn } from \"../utils/contentArea.ts\";\nimport { convertBorderLine, convertShadow } from \"../utils/visualStyle.ts\";\n\ntype ShapePositionedNode = Extract<PositionedNode, { type: \"shape\" }>;\n\nexport function renderShapeNode(\n node: ShapePositionedNode,\n ctx: RenderContext,\n): void {\n const shapeOptions = {\n ...getContentAreaIn(node),\n fill: node.fill\n ? {\n color: node.fill.color,\n transparency: node.fill.transparency,\n }\n : undefined,\n line: node.line ? convertBorderLine(node.line) : undefined,\n shadow: convertShadow(node.shadow),\n rotate: node.rotate,\n };\n\n if (node.text) {\n // テキストがある場合:addTextでshapeを指定\n ctx.slide.addText(node.text, {\n ...shapeOptions,\n shape: node.shapeType,\n fontSize: pxToPt(
|
|
1
|
+
{"version":3,"file":"shape.js","names":[],"sources":["../../../src/renderPptx/nodes/shape.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport { pxToPt } from \"../units.ts\";\nimport { convertUnderline, convertStrike } from \"../textOptions.ts\";\nimport { getContentAreaIn } from \"../utils/contentArea.ts\";\nimport { convertBorderLine, convertShadow } from \"../utils/visualStyle.ts\";\n\ntype ShapePositionedNode = Extract<PositionedNode, { type: \"shape\" }>;\n\nexport function renderShapeNode(\n node: ShapePositionedNode,\n ctx: RenderContext,\n): void {\n const shapeOptions = {\n ...getContentAreaIn(node),\n fill: node.fill\n ? {\n color: node.fill.color,\n transparency: node.fill.transparency,\n }\n : undefined,\n line: node.line ? convertBorderLine(node.line) : undefined,\n shadow: convertShadow(node.shadow),\n rotate: node.rotate,\n };\n\n if (node.text) {\n const fontSizePx = node.fontSize ?? 24;\n const lineHeight = node.lineHeight ?? 1.3;\n // テキストがある場合:addTextでshapeを指定\n ctx.slide.addText(node.text, {\n ...shapeOptions,\n shape: node.shapeType,\n fontSize: pxToPt(fontSizePx),\n fontFace: node.fontFamily ?? \"Noto Sans JP\",\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 align: node.textAlign ?? \"center\",\n valign: \"middle\" as const,\n // Text と同じく行送りを固定値 (spcPts) で指定し、計測高さ\n // (行数 × fontSize × lineHeight) と実描画の行高さを一致させる (#846)。\n // valign middle のためテキストブロックは枠内中央に配置され、\n // Text のような描画 y 補正は不要\n lineSpacing: pxToPt(fontSizePx * lineHeight),\n });\n } else {\n // テキストがない場合:addShapeを使用\n ctx.slide.addShape(node.shapeType, shapeOptions);\n }\n}\n"],"mappings":";;;;;AASA,SAAgB,gBACd,MACA,KACM;CACN,MAAM,eAAe;EACnB,GAAG,iBAAiB,IAAI;EACxB,MAAM,KAAK,OACP;GACE,OAAO,KAAK,KAAK;GACjB,cAAc,KAAK,KAAK;EAC1B,IACA,KAAA;EACJ,MAAM,KAAK,OAAO,kBAAkB,KAAK,IAAI,IAAI,KAAA;EACjD,QAAQ,cAAc,KAAK,MAAM;EACjC,QAAQ,KAAK;CACf;CAEA,IAAI,KAAK,MAAM;EACb,MAAM,aAAa,KAAK,YAAY;EACpC,MAAM,aAAa,KAAK,cAAc;EAEtC,IAAI,MAAM,QAAQ,KAAK,MAAM;GAC3B,GAAG;GACH,OAAO,KAAK;GACZ,UAAU,OAAO,UAAU;GAC3B,UAAU,KAAK,cAAc;GAC7B,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,QAAQ,KAAK;GACb,WAAW,iBAAiB,KAAK,SAAS;GAC1C,QAAQ,cAAc,KAAK,MAAM;GACjC,WAAW,KAAK;GAChB,OAAO,KAAK,aAAa;GACzB,QAAQ;GAKR,aAAa,OAAO,aAAa,UAAU;EAC7C,CAAC;CACH,OAEE,IAAI,MAAM,SAAS,KAAK,WAAW,YAAY;AAEnD"}
|
|
@@ -35,7 +35,7 @@ function renderTextNode(node, ctx) {
|
|
|
35
35
|
align: textOptions.align,
|
|
36
36
|
valign: textOptions.valign,
|
|
37
37
|
margin: textOptions.margin,
|
|
38
|
-
|
|
38
|
+
lineSpacing: textOptions.lineSpacing
|
|
39
39
|
});
|
|
40
40
|
} else ctx.slide.addText(node.text ?? "", textOptions);
|
|
41
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","names":[],"sources":["../../../src/renderPptx/nodes/text.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport {\n createTextOptions,\n convertUnderline,\n convertStrike,\n convertGlow,\n convertOutline,\n} from \"../textOptions.ts\";\nimport { pxToPt } from \"../units.ts\";\n\ntype TextPositionedNode = Extract<PositionedNode, { type: \"text\" }>;\n\nexport function renderTextNode(\n node: TextPositionedNode,\n ctx: RenderContext,\n): void {\n const textOptions = createTextOptions(node);\n\n if (node.runs && node.runs.length > 0) {\n const fontSizePx = node.fontSize ?? 24;\n const fontFamily = node.fontFamily ?? \"Noto Sans JP\";\n const textItems = node.runs.map((run) => {\n const letterSpacingPx = run.letterSpacing ?? node.letterSpacing;\n return {\n text: run.text,\n options: {\n fontSize: pxToPt(fontSizePx),\n fontFace: run.fontFamily ?? fontFamily,\n color: run.color ?? node.color,\n bold: run.bold ?? node.bold,\n italic: run.italic ?? node.italic,\n underline: convertUnderline(run.underline ?? node.underline),\n strike: convertStrike(run.strike ?? node.strike),\n highlight: run.highlight ?? node.highlight,\n // glow / outline はノード単位指定のみ (run 単位はスコープ外)\n glow: convertGlow(node.glow),\n outline: convertOutline(node.outline),\n charSpacing:\n letterSpacingPx !== undefined ? pxToPt(letterSpacingPx) : undefined,\n ...(run.href ? { hyperlink: { url: run.href } } : {}),\n },\n };\n });\n ctx.slide.addText(textItems, {\n x: textOptions.x,\n y: textOptions.y,\n w: textOptions.w,\n h: textOptions.h,\n rotate: textOptions.rotate,\n align: textOptions.align,\n valign: textOptions.valign,\n margin: textOptions.margin,\n
|
|
1
|
+
{"version":3,"file":"text.js","names":[],"sources":["../../../src/renderPptx/nodes/text.ts"],"sourcesContent":["import type { PositionedNode } from \"../../types.ts\";\nimport type { RenderContext } from \"../types.ts\";\nimport {\n createTextOptions,\n convertUnderline,\n convertStrike,\n convertGlow,\n convertOutline,\n} from \"../textOptions.ts\";\nimport { pxToPt } from \"../units.ts\";\n\ntype TextPositionedNode = Extract<PositionedNode, { type: \"text\" }>;\n\nexport function renderTextNode(\n node: TextPositionedNode,\n ctx: RenderContext,\n): void {\n const textOptions = createTextOptions(node);\n\n if (node.runs && node.runs.length > 0) {\n const fontSizePx = node.fontSize ?? 24;\n const fontFamily = node.fontFamily ?? \"Noto Sans JP\";\n const textItems = node.runs.map((run) => {\n const letterSpacingPx = run.letterSpacing ?? node.letterSpacing;\n return {\n text: run.text,\n options: {\n fontSize: pxToPt(fontSizePx),\n fontFace: run.fontFamily ?? fontFamily,\n color: run.color ?? node.color,\n bold: run.bold ?? node.bold,\n italic: run.italic ?? node.italic,\n underline: convertUnderline(run.underline ?? node.underline),\n strike: convertStrike(run.strike ?? node.strike),\n highlight: run.highlight ?? node.highlight,\n // glow / outline はノード単位指定のみ (run 単位はスコープ外)\n glow: convertGlow(node.glow),\n outline: convertOutline(node.outline),\n charSpacing:\n letterSpacingPx !== undefined ? pxToPt(letterSpacingPx) : undefined,\n ...(run.href ? { hyperlink: { url: run.href } } : {}),\n },\n };\n });\n ctx.slide.addText(textItems, {\n x: textOptions.x,\n y: textOptions.y,\n w: textOptions.w,\n h: textOptions.h,\n rotate: textOptions.rotate,\n align: textOptions.align,\n valign: textOptions.valign,\n margin: textOptions.margin,\n lineSpacing: textOptions.lineSpacing,\n });\n } else {\n ctx.slide.addText(node.text ?? \"\", textOptions);\n }\n}\n"],"mappings":";;;AAaA,SAAgB,eACd,MACA,KACM;CACN,MAAM,cAAc,kBAAkB,IAAI;CAE1C,IAAI,KAAK,QAAQ,KAAK,KAAK,SAAS,GAAG;EACrC,MAAM,aAAa,KAAK,YAAY;EACpC,MAAM,aAAa,KAAK,cAAc;EACtC,MAAM,YAAY,KAAK,KAAK,KAAK,QAAQ;GACvC,MAAM,kBAAkB,IAAI,iBAAiB,KAAK;GAClD,OAAO;IACL,MAAM,IAAI;IACV,SAAS;KACP,UAAU,OAAO,UAAU;KAC3B,UAAU,IAAI,cAAc;KAC5B,OAAO,IAAI,SAAS,KAAK;KACzB,MAAM,IAAI,QAAQ,KAAK;KACvB,QAAQ,IAAI,UAAU,KAAK;KAC3B,WAAW,iBAAiB,IAAI,aAAa,KAAK,SAAS;KAC3D,QAAQ,cAAc,IAAI,UAAU,KAAK,MAAM;KAC/C,WAAW,IAAI,aAAa,KAAK;KAEjC,MAAM,YAAY,KAAK,IAAI;KAC3B,SAAS,eAAe,KAAK,OAAO;KACpC,aACE,oBAAoB,KAAA,IAAY,OAAO,eAAe,IAAI,KAAA;KAC5D,GAAI,IAAI,OAAO,EAAE,WAAW,EAAE,KAAK,IAAI,KAAK,EAAE,IAAI,CAAC;IACrD;GACF;EACF,CAAC;EACD,IAAI,MAAM,QAAQ,WAAW;GAC3B,GAAG,YAAY;GACf,GAAG,YAAY;GACf,GAAG,YAAY;GACf,GAAG,YAAY;GACf,QAAQ,YAAY;GACpB,OAAO,YAAY;GACnB,QAAQ,YAAY;GACpB,QAAQ,YAAY;GACpB,aAAa,YAAY;EAC3B,CAAC;CACH,OACE,IAAI,MAAM,QAAQ,KAAK,QAAQ,IAAI,WAAW;AAElD"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { measureFontVerticalMetricsRatio } from "../calcYogaLayout/fontLoader.js";
|
|
2
|
+
import { pxToIn, pxToPt } from "./units.js";
|
|
2
3
|
import { getContentAreaIn } from "./utils/contentArea.js";
|
|
3
4
|
//#region src/renderPptx/textOptions.ts
|
|
4
5
|
/**
|
|
@@ -44,18 +45,36 @@ function convertOutline(outline) {
|
|
|
44
45
|
color: outline.color ?? "FFFFFF"
|
|
45
46
|
};
|
|
46
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* 行内でグリフ ink が上下中央に来るようにするための描画 y 補正量 (px)
|
|
50
|
+
*
|
|
51
|
+
* レンダラ (LibreOffice / PowerPoint) は固定行送り (spcPts) のとき
|
|
52
|
+
* baseline を「行下端 − winDescent × fontSize」に置くため、グリフ ink
|
|
53
|
+
* (typoAscender + typoDescender) は行内で下寄りになり、同じ gap でも
|
|
54
|
+
* テキスト上側の視覚余白が広く下側が狭く見える (#846)。
|
|
55
|
+
* baseline の実位置と「ink を行内中央に置いたときの baseline 位置」の
|
|
56
|
+
* 差分を返し、呼び出し側でテキストフレームをその分だけ上へずらす。
|
|
57
|
+
*/
|
|
58
|
+
function calcGlyphCenteringShiftPx(fontSizePx, lineHeight, fontWeight) {
|
|
59
|
+
const lineHeightPx = fontSizePx * lineHeight;
|
|
60
|
+
const metrics = measureFontVerticalMetricsRatio(fontWeight);
|
|
61
|
+
return lineHeightPx - metrics.winDescent * fontSizePx - ((lineHeightPx - (metrics.typoAscender + metrics.typoDescender) * fontSizePx) / 2 + metrics.typoAscender * fontSizePx);
|
|
62
|
+
}
|
|
47
63
|
function createTextOptions(node) {
|
|
48
64
|
const fontSizePx = node.fontSize ?? 24;
|
|
49
65
|
const fontFamily = node.fontFamily ?? "Noto Sans JP";
|
|
50
66
|
const lineHeight = node.lineHeight ?? 1.3;
|
|
67
|
+
const area = getContentAreaIn(node);
|
|
68
|
+
const glyphShiftPx = calcGlyphCenteringShiftPx(fontSizePx, lineHeight, node.bold ? "bold" : "normal");
|
|
51
69
|
return {
|
|
52
|
-
...
|
|
70
|
+
...area,
|
|
71
|
+
y: area.y - pxToIn(glyphShiftPx),
|
|
53
72
|
fontSize: pxToPt(fontSizePx),
|
|
54
73
|
fontFace: fontFamily,
|
|
55
74
|
align: node.textAlign ?? "left",
|
|
56
75
|
valign: "top",
|
|
57
76
|
margin: 0,
|
|
58
|
-
|
|
77
|
+
lineSpacing: pxToPt(fontSizePx * lineHeight),
|
|
59
78
|
rotate: node.rotate,
|
|
60
79
|
color: node.color,
|
|
61
80
|
bold: node.bold,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textOptions.js","names":[],"sources":["../../src/renderPptx/textOptions.ts"],"sourcesContent":["import type {\n PositionedNode,\n TextGlow,\n TextOutline,\n Underline,\n UnderlineStyle,\n} from \"../types.ts\";\nimport { pxToPt } from \"./units.ts\";\nimport { getContentAreaIn } 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\n/**\n * glow プロパティを pptxgenjs 形式に変換する\n * size はユーザー入力 px、pptxgenjs の glow.size は pt。\n * pptxgenjs は省略時デフォルトを Object.assign で合成するため undefined を\n * 渡すとデフォルトが消える。ここで pom 側のデフォルトを確定させる。\n */\nexport function convertGlow(\n glow: TextGlow | undefined,\n): { size: number; opacity: number; color: string } | undefined {\n if (glow === undefined) return undefined;\n return {\n size: pxToPt(glow.size ?? 8),\n opacity: glow.opacity ?? 0.75,\n color: glow.color ?? \"FFFFFF\",\n };\n}\n\n/**\n * outline プロパティを pptxgenjs 形式に変換する\n * size はユーザー入力 px、pptxgenjs の outline.size は pt\n */\nexport function convertOutline(\n outline: TextOutline | undefined,\n): { size: number; color: string } | undefined {\n if (outline === undefined) return undefined;\n return {\n size: pxToPt(outline.size ?? 1),\n color: outline.color ?? \"FFFFFF\",\n };\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\n return {\n ...
|
|
1
|
+
{"version":3,"file":"textOptions.js","names":[],"sources":["../../src/renderPptx/textOptions.ts"],"sourcesContent":["import type {\n PositionedNode,\n TextGlow,\n TextOutline,\n Underline,\n UnderlineStyle,\n} from \"../types.ts\";\nimport { pxToIn, pxToPt } from \"./units.ts\";\nimport { getContentAreaIn } from \"./utils/contentArea.ts\";\nimport { measureFontVerticalMetricsRatio } from \"../calcYogaLayout/fontLoader.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\n/**\n * glow プロパティを pptxgenjs 形式に変換する\n * size はユーザー入力 px、pptxgenjs の glow.size は pt。\n * pptxgenjs は省略時デフォルトを Object.assign で合成するため undefined を\n * 渡すとデフォルトが消える。ここで pom 側のデフォルトを確定させる。\n */\nexport function convertGlow(\n glow: TextGlow | undefined,\n): { size: number; opacity: number; color: string } | undefined {\n if (glow === undefined) return undefined;\n return {\n size: pxToPt(glow.size ?? 8),\n opacity: glow.opacity ?? 0.75,\n color: glow.color ?? \"FFFFFF\",\n };\n}\n\n/**\n * outline プロパティを pptxgenjs 形式に変換する\n * size はユーザー入力 px、pptxgenjs の outline.size は pt\n */\nexport function convertOutline(\n outline: TextOutline | undefined,\n): { size: number; color: string } | undefined {\n if (outline === undefined) return undefined;\n return {\n size: pxToPt(outline.size ?? 1),\n color: outline.color ?? \"FFFFFF\",\n };\n}\n\n/**\n * 行内でグリフ ink が上下中央に来るようにするための描画 y 補正量 (px)\n *\n * レンダラ (LibreOffice / PowerPoint) は固定行送り (spcPts) のとき\n * baseline を「行下端 − winDescent × fontSize」に置くため、グリフ ink\n * (typoAscender + typoDescender) は行内で下寄りになり、同じ gap でも\n * テキスト上側の視覚余白が広く下側が狭く見える (#846)。\n * baseline の実位置と「ink を行内中央に置いたときの baseline 位置」の\n * 差分を返し、呼び出し側でテキストフレームをその分だけ上へずらす。\n */\nexport function calcGlyphCenteringShiftPx(\n fontSizePx: number,\n lineHeight: number,\n fontWeight: \"normal\" | \"bold\",\n): number {\n const lineHeightPx = fontSizePx * lineHeight;\n const metrics = measureFontVerticalMetricsRatio(fontWeight);\n const baselineFromTopPx = lineHeightPx - metrics.winDescent * fontSizePx;\n const inkHeightPx =\n (metrics.typoAscender + metrics.typoDescender) * fontSizePx;\n const centeredBaselineFromTopPx =\n (lineHeightPx - inkHeightPx) / 2 + metrics.typoAscender * fontSizePx;\n return baselineFromTopPx - centeredBaselineFromTopPx;\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\n const area = getContentAreaIn(node);\n const glyphShiftPx = calcGlyphCenteringShiftPx(\n fontSizePx,\n lineHeight,\n node.bold ? \"bold\" : \"normal\",\n );\n\n return {\n ...area,\n y: area.y - pxToIn(glyphShiftPx),\n fontSize: pxToPt(fontSizePx),\n fontFace: fontFamily,\n align: node.textAlign ?? \"left\",\n valign: \"top\" as const,\n margin: 0,\n // 行送りを固定値 (spcPts) で指定する。倍率指定 (spcPct) はレンダラがフォント\n // メトリクスに対する倍率として解釈するため、計測高さ (行数 × fontSize ×\n // lineHeight) と実描画の行高さが一致せず、グリフがボックスからはみ出して\n // 上下余白が非対称になる (#846)\n lineSpacing: pxToPt(fontSizePx * lineHeight),\n rotate: node.rotate,\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 glow: convertGlow(node.glow),\n outline: convertOutline(node.outline),\n // letterSpacing はユーザー入力 px、pptxgenjs の charSpacing は pt\n charSpacing:\n node.letterSpacing !== undefined ? pxToPt(node.letterSpacing) : undefined,\n };\n}\n"],"mappings":";;;;;;;AAgBA,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;;;;;;;AAQA,SAAgB,YACd,MAC8D;CAC9D,IAAI,SAAS,KAAA,GAAW,OAAO,KAAA;CAC/B,OAAO;EACL,MAAM,OAAO,KAAK,QAAQ,CAAC;EAC3B,SAAS,KAAK,WAAW;EACzB,OAAO,KAAK,SAAS;CACvB;AACF;;;;;AAMA,SAAgB,eACd,SAC6C;CAC7C,IAAI,YAAY,KAAA,GAAW,OAAO,KAAA;CAClC,OAAO;EACL,MAAM,OAAO,QAAQ,QAAQ,CAAC;EAC9B,OAAO,QAAQ,SAAS;CAC1B;AACF;;;;;;;;;;;AAYA,SAAgB,0BACd,YACA,YACA,YACQ;CACR,MAAM,eAAe,aAAa;CAClC,MAAM,UAAU,gCAAgC,UAAU;CAM1D,OAL0B,eAAe,QAAQ,aAAa,eAI3D,gBAFA,QAAQ,eAAe,QAAQ,iBAAiB,cAElB,IAAI,QAAQ,eAAe;AAE9D;AAEA,SAAgB,kBAAkB,MAAgB;CAChD,MAAM,aAAa,KAAK,YAAY;CACpC,MAAM,aAAa,KAAK,cAAc;CACtC,MAAM,aAAa,KAAK,cAAc;CAEtC,MAAM,OAAO,iBAAiB,IAAI;CAClC,MAAM,eAAe,0BACnB,YACA,YACA,KAAK,OAAO,SAAS,QACvB;CAEA,OAAO;EACL,GAAG;EACH,GAAG,KAAK,IAAI,OAAO,YAAY;EAC/B,UAAU,OAAO,UAAU;EAC3B,UAAU;EACV,OAAO,KAAK,aAAa;EACzB,QAAQ;EACR,QAAQ;EAKR,aAAa,OAAO,aAAa,UAAU;EAC3C,QAAQ,KAAK;EACb,OAAO,KAAK;EACZ,MAAM,KAAK;EACX,QAAQ,KAAK;EACb,WAAW,iBAAiB,KAAK,SAAS;EAC1C,QAAQ,cAAc,KAAK,MAAM;EACjC,WAAW,KAAK;EAChB,MAAM,YAAY,KAAK,IAAI;EAC3B,SAAS,eAAe,KAAK,OAAO;EAEpC,aACE,KAAK,kBAAkB,KAAA,IAAY,OAAO,KAAK,aAAa,IAAI,KAAA;CACpE;AACF"}
|