pptx-glimpse 0.2.2 → 0.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 +4 -2
- package/dist/index.cjs +172 -7
- package/dist/index.d.cts +98 -1
- package/dist/index.d.ts +98 -1
- package/dist/index.js +170 -7
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/pptx-glimpse)
|
|
4
4
|
|
|
5
|
-
A
|
|
5
|
+
A lightweight JavaScript library for rendering PowerPoint (.pptx) files as SVG or PNG in Node.js. No LibreOffice required.
|
|
6
6
|
|
|
7
|
-
[Demo](https://pptx-glimpse.vercel.app/) | [npm](https://www.npmjs.com/package/pptx-glimpse)
|
|
7
|
+
**[Try the Demo](https://pptx-glimpse.vercel.app/)** | [npm](https://www.npmjs.com/package/pptx-glimpse)
|
|
8
|
+
|
|
9
|
+

|
|
8
10
|
|
|
9
11
|
## Motivation
|
|
10
12
|
|
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,8 @@ __export(index_exports, {
|
|
|
35
35
|
convertPptxToPng: () => convertPptxToPng,
|
|
36
36
|
convertPptxToSvg: () => convertPptxToSvg,
|
|
37
37
|
createFontMapping: () => createFontMapping,
|
|
38
|
+
createOpentypeSetupFromBuffers: () => createOpentypeSetupFromBuffers,
|
|
39
|
+
createOpentypeTextMeasurerFromBuffers: () => createOpentypeTextMeasurerFromBuffers,
|
|
38
40
|
getMappedFont: () => getMappedFont,
|
|
39
41
|
getWarningEntries: () => getWarningEntries,
|
|
40
42
|
getWarningSummary: () => getWarningSummary
|
|
@@ -8110,6 +8112,108 @@ function collectFontFilePaths(additionalDirs) {
|
|
|
8110
8112
|
return result;
|
|
8111
8113
|
}
|
|
8112
8114
|
|
|
8115
|
+
// src/font/ttc-parser.ts
|
|
8116
|
+
var TTC_TAG = 1953784678;
|
|
8117
|
+
function isTtcBuffer(data) {
|
|
8118
|
+
const view = toDataView(data);
|
|
8119
|
+
if (view.byteLength < 4) return false;
|
|
8120
|
+
return view.getUint32(0) === TTC_TAG;
|
|
8121
|
+
}
|
|
8122
|
+
function extractTtcFonts(data) {
|
|
8123
|
+
const view = toDataView(data);
|
|
8124
|
+
const bytes = toUint8Array(data);
|
|
8125
|
+
if (view.byteLength < 12) return [];
|
|
8126
|
+
if (view.getUint32(0) !== TTC_TAG) return [];
|
|
8127
|
+
const numFonts = view.getUint32(8);
|
|
8128
|
+
if (numFonts === 0) return [];
|
|
8129
|
+
const headerEnd = 12 + numFonts * 4;
|
|
8130
|
+
if (view.byteLength < headerEnd) return [];
|
|
8131
|
+
const results = [];
|
|
8132
|
+
for (let i = 0; i < numFonts; i++) {
|
|
8133
|
+
try {
|
|
8134
|
+
const fontOffset = view.getUint32(12 + i * 4);
|
|
8135
|
+
const extracted = extractSingleFont(view, bytes, fontOffset);
|
|
8136
|
+
if (extracted) results.push(extracted);
|
|
8137
|
+
} catch {
|
|
8138
|
+
}
|
|
8139
|
+
}
|
|
8140
|
+
return results;
|
|
8141
|
+
}
|
|
8142
|
+
function extractSingleFont(view, bytes, fontOffset) {
|
|
8143
|
+
if (fontOffset + 12 > view.byteLength) return null;
|
|
8144
|
+
const sfVersion = view.getUint32(fontOffset);
|
|
8145
|
+
const numTables = view.getUint16(fontOffset + 4);
|
|
8146
|
+
if (numTables === 0) return null;
|
|
8147
|
+
const tableRecordsStart = fontOffset + 12;
|
|
8148
|
+
const tableRecordsEnd = tableRecordsStart + numTables * 16;
|
|
8149
|
+
if (tableRecordsEnd > view.byteLength) return null;
|
|
8150
|
+
const tables = [];
|
|
8151
|
+
for (let i = 0; i < numTables; i++) {
|
|
8152
|
+
const recOffset = tableRecordsStart + i * 16;
|
|
8153
|
+
const tableOffset = view.getUint32(recOffset + 8);
|
|
8154
|
+
const tableLength = view.getUint32(recOffset + 12);
|
|
8155
|
+
if (tableOffset > view.byteLength || tableLength > view.byteLength - tableOffset) {
|
|
8156
|
+
return null;
|
|
8157
|
+
}
|
|
8158
|
+
tables.push({
|
|
8159
|
+
tag: view.getUint32(recOffset),
|
|
8160
|
+
checkSum: view.getUint32(recOffset + 4),
|
|
8161
|
+
offset: tableOffset,
|
|
8162
|
+
length: tableLength
|
|
8163
|
+
});
|
|
8164
|
+
}
|
|
8165
|
+
const headerSize = 12 + numTables * 16;
|
|
8166
|
+
let dataSize = 0;
|
|
8167
|
+
for (const table of tables) {
|
|
8168
|
+
dataSize += alignTo4(table.length);
|
|
8169
|
+
}
|
|
8170
|
+
const totalSize = headerSize + dataSize;
|
|
8171
|
+
const output = new ArrayBuffer(totalSize);
|
|
8172
|
+
const outView = new DataView(output);
|
|
8173
|
+
const outBytes = new Uint8Array(output);
|
|
8174
|
+
outView.setUint32(0, sfVersion);
|
|
8175
|
+
outView.setUint16(4, numTables);
|
|
8176
|
+
const { searchRange, entrySelector, rangeShift } = calcOffsetTableFields(numTables);
|
|
8177
|
+
outView.setUint16(6, searchRange);
|
|
8178
|
+
outView.setUint16(8, entrySelector);
|
|
8179
|
+
outView.setUint16(10, rangeShift);
|
|
8180
|
+
let currentDataOffset = headerSize;
|
|
8181
|
+
for (let i = 0; i < numTables; i++) {
|
|
8182
|
+
const table = tables[i];
|
|
8183
|
+
const recOffset = 12 + i * 16;
|
|
8184
|
+
outView.setUint32(recOffset, table.tag);
|
|
8185
|
+
outView.setUint32(recOffset + 4, table.checkSum);
|
|
8186
|
+
outView.setUint32(recOffset + 8, currentDataOffset);
|
|
8187
|
+
outView.setUint32(recOffset + 12, table.length);
|
|
8188
|
+
outBytes.set(bytes.subarray(table.offset, table.offset + table.length), currentDataOffset);
|
|
8189
|
+
currentDataOffset += alignTo4(table.length);
|
|
8190
|
+
}
|
|
8191
|
+
return output;
|
|
8192
|
+
}
|
|
8193
|
+
function alignTo4(n) {
|
|
8194
|
+
return n + 3 & ~3;
|
|
8195
|
+
}
|
|
8196
|
+
function calcOffsetTableFields(numTables) {
|
|
8197
|
+
let searchRange = 16;
|
|
8198
|
+
let entrySelector = 0;
|
|
8199
|
+
while (searchRange * 2 <= numTables * 16) {
|
|
8200
|
+
searchRange *= 2;
|
|
8201
|
+
entrySelector++;
|
|
8202
|
+
}
|
|
8203
|
+
const rangeShift = numTables * 16 - searchRange;
|
|
8204
|
+
return { searchRange, entrySelector, rangeShift };
|
|
8205
|
+
}
|
|
8206
|
+
function toDataView(data) {
|
|
8207
|
+
if (data instanceof Uint8Array) {
|
|
8208
|
+
return new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
8209
|
+
}
|
|
8210
|
+
return new DataView(data);
|
|
8211
|
+
}
|
|
8212
|
+
function toUint8Array(data) {
|
|
8213
|
+
if (data instanceof Uint8Array) return data;
|
|
8214
|
+
return new Uint8Array(data);
|
|
8215
|
+
}
|
|
8216
|
+
|
|
8113
8217
|
// src/font/opentype-helpers.ts
|
|
8114
8218
|
async function tryLoadOpentype() {
|
|
8115
8219
|
try {
|
|
@@ -8136,6 +8240,63 @@ function toArrayBuffer(data) {
|
|
|
8136
8240
|
if (data instanceof ArrayBuffer) return data;
|
|
8137
8241
|
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
8138
8242
|
}
|
|
8243
|
+
function parseFontBuffer(arrayBuffer, opentype) {
|
|
8244
|
+
if (isTtcBuffer(arrayBuffer)) {
|
|
8245
|
+
const results = [];
|
|
8246
|
+
for (const buf of extractTtcFonts(arrayBuffer)) {
|
|
8247
|
+
try {
|
|
8248
|
+
results.push(opentype.parse(buf));
|
|
8249
|
+
} catch {
|
|
8250
|
+
}
|
|
8251
|
+
}
|
|
8252
|
+
return results;
|
|
8253
|
+
}
|
|
8254
|
+
return [opentype.parse(arrayBuffer)];
|
|
8255
|
+
}
|
|
8256
|
+
async function createOpentypeTextMeasurerFromBuffers(fontBuffers, fontMapping) {
|
|
8257
|
+
const setup = await createOpentypeSetupFromBuffers(fontBuffers, fontMapping);
|
|
8258
|
+
return setup?.measurer ?? null;
|
|
8259
|
+
}
|
|
8260
|
+
async function createOpentypeSetupFromBuffers(fontBuffers, fontMapping) {
|
|
8261
|
+
if (fontBuffers.length === 0) return null;
|
|
8262
|
+
const opentype = await tryLoadOpentype();
|
|
8263
|
+
if (!opentype) return null;
|
|
8264
|
+
const mapping = createFontMapping(fontMapping);
|
|
8265
|
+
const reverseMap = buildReverseMapping(mapping);
|
|
8266
|
+
const measurerFonts = /* @__PURE__ */ new Map();
|
|
8267
|
+
const resolverFonts = /* @__PURE__ */ new Map();
|
|
8268
|
+
let firstMeasurerFont = null;
|
|
8269
|
+
let firstResolverFont = null;
|
|
8270
|
+
for (const buffer of fontBuffers) {
|
|
8271
|
+
try {
|
|
8272
|
+
const arrayBuffer = toArrayBuffer(buffer.data);
|
|
8273
|
+
const isTtc = isTtcBuffer(arrayBuffer);
|
|
8274
|
+
const fonts = parseFontBuffer(arrayBuffer, opentype);
|
|
8275
|
+
for (const font of fonts) {
|
|
8276
|
+
if (!firstMeasurerFont) firstMeasurerFont = font;
|
|
8277
|
+
if (!firstResolverFont) firstResolverFont = font;
|
|
8278
|
+
if (isTtc) {
|
|
8279
|
+
const fontFamily = font.names.fontFamily;
|
|
8280
|
+
if (fontFamily) {
|
|
8281
|
+
for (const name of Object.values(fontFamily)) {
|
|
8282
|
+
registerFont(name, font, reverseMap, measurerFonts, resolverFonts);
|
|
8283
|
+
}
|
|
8284
|
+
}
|
|
8285
|
+
} else if (buffer.name) {
|
|
8286
|
+
registerFont(buffer.name, font, reverseMap, measurerFonts, resolverFonts);
|
|
8287
|
+
}
|
|
8288
|
+
}
|
|
8289
|
+
} catch {
|
|
8290
|
+
}
|
|
8291
|
+
}
|
|
8292
|
+
if (measurerFonts.size === 0 && !firstMeasurerFont) return null;
|
|
8293
|
+
const measurer = new OpentypeTextMeasurer(measurerFonts, firstMeasurerFont ?? void 0);
|
|
8294
|
+
const fontResolver = new DefaultTextPathFontResolver(
|
|
8295
|
+
resolverFonts,
|
|
8296
|
+
firstResolverFont ?? void 0
|
|
8297
|
+
);
|
|
8298
|
+
return { measurer, fontResolver };
|
|
8299
|
+
}
|
|
8139
8300
|
function registerFont(name, font, reverseMap, measurerFonts, resolverFonts) {
|
|
8140
8301
|
const fullFont = font;
|
|
8141
8302
|
if (!measurerFonts.has(name)) {
|
|
@@ -8167,13 +8328,15 @@ async function createOpentypeSetupFromSystem(additionalFontDirs, fontMapping) {
|
|
|
8167
8328
|
try {
|
|
8168
8329
|
const data = await (0, import_promises.readFile)(filePath);
|
|
8169
8330
|
const arrayBuffer = toArrayBuffer(data);
|
|
8170
|
-
const
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8331
|
+
const fonts = parseFontBuffer(arrayBuffer, opentype);
|
|
8332
|
+
for (const font of fonts) {
|
|
8333
|
+
if (!firstMeasurerFont) firstMeasurerFont = font;
|
|
8334
|
+
if (!firstResolverFont) firstResolverFont = font;
|
|
8335
|
+
const fontFamily = font.names.fontFamily;
|
|
8336
|
+
if (fontFamily) {
|
|
8337
|
+
for (const name of Object.values(fontFamily)) {
|
|
8338
|
+
registerFont(name, font, reverseMap, measurerFonts, resolverFonts);
|
|
8339
|
+
}
|
|
8177
8340
|
}
|
|
8178
8341
|
}
|
|
8179
8342
|
} catch {
|
|
@@ -8319,6 +8482,8 @@ function collectFontsFromTextBody(textBody, fonts) {
|
|
|
8319
8482
|
convertPptxToPng,
|
|
8320
8483
|
convertPptxToSvg,
|
|
8321
8484
|
createFontMapping,
|
|
8485
|
+
createOpentypeSetupFromBuffers,
|
|
8486
|
+
createOpentypeTextMeasurerFromBuffers,
|
|
8322
8487
|
getMappedFont,
|
|
8323
8488
|
getWarningEntries,
|
|
8324
8489
|
getWarningSummary
|
package/dist/index.d.cts
CHANGED
|
@@ -84,4 +84,101 @@ interface UsedFonts {
|
|
|
84
84
|
*/
|
|
85
85
|
declare function collectUsedFonts(input: Buffer | Uint8Array): UsedFonts;
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
/**
|
|
88
|
+
* テキストの幅と行高さを計測するインターフェース。
|
|
89
|
+
* デフォルトでは静的フォントメトリクスによる計測が使われる。
|
|
90
|
+
* ユーザーが独自の実装を ConvertOptions.textMeasurer に渡すことで、
|
|
91
|
+
* Canvas API や opentype.js など任意の計測バックエンドに差し替えられる。
|
|
92
|
+
*/
|
|
93
|
+
interface TextMeasurer {
|
|
94
|
+
/**
|
|
95
|
+
* テキストの推定幅をピクセル単位で返す。
|
|
96
|
+
* @param text - 計測対象のテキスト
|
|
97
|
+
* @param fontSizePt - フォントサイズ (ポイント)
|
|
98
|
+
* @param bold - 太字かどうか
|
|
99
|
+
* @param fontFamily - ラテン文字用フォントファミリー名
|
|
100
|
+
* @param fontFamilyEa - 東アジア文字用フォントファミリー名
|
|
101
|
+
*/
|
|
102
|
+
measureTextWidth(text: string, fontSizePt: number, bold: boolean, fontFamily?: string | null, fontFamilyEa?: string | null): number;
|
|
103
|
+
/**
|
|
104
|
+
* フォントの自然な行高さ比率 (行高さ / フォントサイズ) を返す。
|
|
105
|
+
* メトリクスが不明な場合は 1.2 をフォールバック値として返す。
|
|
106
|
+
* @param fontFamily - ラテン文字用フォントファミリー名
|
|
107
|
+
* @param fontFamilyEa - 東アジア文字用フォントファミリー名
|
|
108
|
+
*/
|
|
109
|
+
getLineHeightRatio(fontFamily?: string | null, fontFamilyEa?: string | null): number;
|
|
110
|
+
/**
|
|
111
|
+
* フォントの ascender 比率 (ascender / unitsPerEm) を返す。
|
|
112
|
+
* 1行目のベースラインオフセット計算に使用する。
|
|
113
|
+
* メトリクスが不明な場合は 1.0 をフォールバック値として返す。
|
|
114
|
+
* @param fontFamily - ラテン文字用フォントファミリー名
|
|
115
|
+
* @param fontFamilyEa - 東アジア文字用フォントファミリー名
|
|
116
|
+
*/
|
|
117
|
+
getAscenderRatio(fontFamily?: string | null, fontFamilyEa?: string | null): number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface OpentypeGlyph {
|
|
121
|
+
advanceWidth?: number;
|
|
122
|
+
}
|
|
123
|
+
interface OpentypeFont {
|
|
124
|
+
unitsPerEm: number;
|
|
125
|
+
ascender: number;
|
|
126
|
+
descender: number;
|
|
127
|
+
stringToGlyphs(text: string): OpentypeGlyph[];
|
|
128
|
+
}
|
|
129
|
+
declare class OpentypeTextMeasurer implements TextMeasurer {
|
|
130
|
+
private fonts;
|
|
131
|
+
private defaultFont;
|
|
132
|
+
/**
|
|
133
|
+
* @param fonts - フォント名 → opentype.js Font オブジェクトのマップ
|
|
134
|
+
* @param defaultFont - フォールバックフォント(省略可)
|
|
135
|
+
*/
|
|
136
|
+
constructor(fonts: Map<string, OpentypeFont>, defaultFont?: OpentypeFont);
|
|
137
|
+
measureTextWidth(text: string, fontSizePt: number, bold: boolean, fontFamily?: string | null, fontFamilyEa?: string | null): number;
|
|
138
|
+
getLineHeightRatio(fontFamily?: string | null, fontFamilyEa?: string | null): number;
|
|
139
|
+
getAscenderRatio(fontFamily?: string | null, fontFamilyEa?: string | null): number;
|
|
140
|
+
private resolveFont;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* テキスト→パス変換用のフォントリゾルバーコンテキスト。
|
|
145
|
+
* opentype.js の getPath() メソッドを持つ Font オブジェクトへのアクセスを提供する。
|
|
146
|
+
*/
|
|
147
|
+
interface OpentypePath {
|
|
148
|
+
toPathData(decimalPlaces?: number): string;
|
|
149
|
+
}
|
|
150
|
+
interface OpentypeFullFont {
|
|
151
|
+
unitsPerEm: number;
|
|
152
|
+
ascender: number;
|
|
153
|
+
descender: number;
|
|
154
|
+
getPath(text: string, x: number, y: number, fontSize: number): OpentypePath;
|
|
155
|
+
}
|
|
156
|
+
interface TextPathFontResolver {
|
|
157
|
+
resolveFont(fontFamily: string | null | undefined, fontFamilyEa: string | null | undefined): OpentypeFullFont | null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** フォントバッファの入力形式 */
|
|
161
|
+
interface FontBuffer {
|
|
162
|
+
name?: string;
|
|
163
|
+
data: ArrayBuffer | Uint8Array;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* フォントバッファ配列から OpentypeTextMeasurer を構築する。
|
|
167
|
+
*
|
|
168
|
+
* 内部で opentype.js を動的 import してフォントをパースする。
|
|
169
|
+
* opentype.js が利用不可な場合は null を返す。
|
|
170
|
+
*/
|
|
171
|
+
declare function createOpentypeTextMeasurerFromBuffers(fontBuffers: FontBuffer[], fontMapping?: FontMapping): Promise<OpentypeTextMeasurer | null>;
|
|
172
|
+
interface OpentypeSetup {
|
|
173
|
+
measurer: OpentypeTextMeasurer;
|
|
174
|
+
fontResolver: TextPathFontResolver;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* フォントバッファ配列から OpentypeTextMeasurer と TextPathFontResolver を同時に構築する。
|
|
178
|
+
*
|
|
179
|
+
* opentype.parse() が返すオブジェクトは OpentypeFont と OpentypeFullFont の両方を満たすため、
|
|
180
|
+
* 同じ Font オブジェクトを measurer と fontResolver の両方に渡す。
|
|
181
|
+
*/
|
|
182
|
+
declare function createOpentypeSetupFromBuffers(fontBuffers: FontBuffer[], fontMapping?: FontMapping): Promise<OpentypeSetup | null>;
|
|
183
|
+
|
|
184
|
+
export { type ConvertOptions, DEFAULT_FONT_MAPPING, type FontBuffer, type FontMapping, type LogLevel, type OpentypeSetup, type SlideImage, type SlideSvg, type UsedFonts, type WarningEntry, type WarningSummary, collectUsedFonts, convertPptxToPng, convertPptxToSvg, createFontMapping, createOpentypeSetupFromBuffers, createOpentypeTextMeasurerFromBuffers, getMappedFont, getWarningEntries, getWarningSummary };
|
package/dist/index.d.ts
CHANGED
|
@@ -84,4 +84,101 @@ interface UsedFonts {
|
|
|
84
84
|
*/
|
|
85
85
|
declare function collectUsedFonts(input: Buffer | Uint8Array): UsedFonts;
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
/**
|
|
88
|
+
* テキストの幅と行高さを計測するインターフェース。
|
|
89
|
+
* デフォルトでは静的フォントメトリクスによる計測が使われる。
|
|
90
|
+
* ユーザーが独自の実装を ConvertOptions.textMeasurer に渡すことで、
|
|
91
|
+
* Canvas API や opentype.js など任意の計測バックエンドに差し替えられる。
|
|
92
|
+
*/
|
|
93
|
+
interface TextMeasurer {
|
|
94
|
+
/**
|
|
95
|
+
* テキストの推定幅をピクセル単位で返す。
|
|
96
|
+
* @param text - 計測対象のテキスト
|
|
97
|
+
* @param fontSizePt - フォントサイズ (ポイント)
|
|
98
|
+
* @param bold - 太字かどうか
|
|
99
|
+
* @param fontFamily - ラテン文字用フォントファミリー名
|
|
100
|
+
* @param fontFamilyEa - 東アジア文字用フォントファミリー名
|
|
101
|
+
*/
|
|
102
|
+
measureTextWidth(text: string, fontSizePt: number, bold: boolean, fontFamily?: string | null, fontFamilyEa?: string | null): number;
|
|
103
|
+
/**
|
|
104
|
+
* フォントの自然な行高さ比率 (行高さ / フォントサイズ) を返す。
|
|
105
|
+
* メトリクスが不明な場合は 1.2 をフォールバック値として返す。
|
|
106
|
+
* @param fontFamily - ラテン文字用フォントファミリー名
|
|
107
|
+
* @param fontFamilyEa - 東アジア文字用フォントファミリー名
|
|
108
|
+
*/
|
|
109
|
+
getLineHeightRatio(fontFamily?: string | null, fontFamilyEa?: string | null): number;
|
|
110
|
+
/**
|
|
111
|
+
* フォントの ascender 比率 (ascender / unitsPerEm) を返す。
|
|
112
|
+
* 1行目のベースラインオフセット計算に使用する。
|
|
113
|
+
* メトリクスが不明な場合は 1.0 をフォールバック値として返す。
|
|
114
|
+
* @param fontFamily - ラテン文字用フォントファミリー名
|
|
115
|
+
* @param fontFamilyEa - 東アジア文字用フォントファミリー名
|
|
116
|
+
*/
|
|
117
|
+
getAscenderRatio(fontFamily?: string | null, fontFamilyEa?: string | null): number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface OpentypeGlyph {
|
|
121
|
+
advanceWidth?: number;
|
|
122
|
+
}
|
|
123
|
+
interface OpentypeFont {
|
|
124
|
+
unitsPerEm: number;
|
|
125
|
+
ascender: number;
|
|
126
|
+
descender: number;
|
|
127
|
+
stringToGlyphs(text: string): OpentypeGlyph[];
|
|
128
|
+
}
|
|
129
|
+
declare class OpentypeTextMeasurer implements TextMeasurer {
|
|
130
|
+
private fonts;
|
|
131
|
+
private defaultFont;
|
|
132
|
+
/**
|
|
133
|
+
* @param fonts - フォント名 → opentype.js Font オブジェクトのマップ
|
|
134
|
+
* @param defaultFont - フォールバックフォント(省略可)
|
|
135
|
+
*/
|
|
136
|
+
constructor(fonts: Map<string, OpentypeFont>, defaultFont?: OpentypeFont);
|
|
137
|
+
measureTextWidth(text: string, fontSizePt: number, bold: boolean, fontFamily?: string | null, fontFamilyEa?: string | null): number;
|
|
138
|
+
getLineHeightRatio(fontFamily?: string | null, fontFamilyEa?: string | null): number;
|
|
139
|
+
getAscenderRatio(fontFamily?: string | null, fontFamilyEa?: string | null): number;
|
|
140
|
+
private resolveFont;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* テキスト→パス変換用のフォントリゾルバーコンテキスト。
|
|
145
|
+
* opentype.js の getPath() メソッドを持つ Font オブジェクトへのアクセスを提供する。
|
|
146
|
+
*/
|
|
147
|
+
interface OpentypePath {
|
|
148
|
+
toPathData(decimalPlaces?: number): string;
|
|
149
|
+
}
|
|
150
|
+
interface OpentypeFullFont {
|
|
151
|
+
unitsPerEm: number;
|
|
152
|
+
ascender: number;
|
|
153
|
+
descender: number;
|
|
154
|
+
getPath(text: string, x: number, y: number, fontSize: number): OpentypePath;
|
|
155
|
+
}
|
|
156
|
+
interface TextPathFontResolver {
|
|
157
|
+
resolveFont(fontFamily: string | null | undefined, fontFamilyEa: string | null | undefined): OpentypeFullFont | null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** フォントバッファの入力形式 */
|
|
161
|
+
interface FontBuffer {
|
|
162
|
+
name?: string;
|
|
163
|
+
data: ArrayBuffer | Uint8Array;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* フォントバッファ配列から OpentypeTextMeasurer を構築する。
|
|
167
|
+
*
|
|
168
|
+
* 内部で opentype.js を動的 import してフォントをパースする。
|
|
169
|
+
* opentype.js が利用不可な場合は null を返す。
|
|
170
|
+
*/
|
|
171
|
+
declare function createOpentypeTextMeasurerFromBuffers(fontBuffers: FontBuffer[], fontMapping?: FontMapping): Promise<OpentypeTextMeasurer | null>;
|
|
172
|
+
interface OpentypeSetup {
|
|
173
|
+
measurer: OpentypeTextMeasurer;
|
|
174
|
+
fontResolver: TextPathFontResolver;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* フォントバッファ配列から OpentypeTextMeasurer と TextPathFontResolver を同時に構築する。
|
|
178
|
+
*
|
|
179
|
+
* opentype.parse() が返すオブジェクトは OpentypeFont と OpentypeFullFont の両方を満たすため、
|
|
180
|
+
* 同じ Font オブジェクトを measurer と fontResolver の両方に渡す。
|
|
181
|
+
*/
|
|
182
|
+
declare function createOpentypeSetupFromBuffers(fontBuffers: FontBuffer[], fontMapping?: FontMapping): Promise<OpentypeSetup | null>;
|
|
183
|
+
|
|
184
|
+
export { type ConvertOptions, DEFAULT_FONT_MAPPING, type FontBuffer, type FontMapping, type LogLevel, type OpentypeSetup, type SlideImage, type SlideSvg, type UsedFonts, type WarningEntry, type WarningSummary, collectUsedFonts, convertPptxToPng, convertPptxToSvg, createFontMapping, createOpentypeSetupFromBuffers, createOpentypeTextMeasurerFromBuffers, getMappedFont, getWarningEntries, getWarningSummary };
|
package/dist/index.js
CHANGED
|
@@ -8067,6 +8067,108 @@ function collectFontFilePaths(additionalDirs) {
|
|
|
8067
8067
|
return result;
|
|
8068
8068
|
}
|
|
8069
8069
|
|
|
8070
|
+
// src/font/ttc-parser.ts
|
|
8071
|
+
var TTC_TAG = 1953784678;
|
|
8072
|
+
function isTtcBuffer(data) {
|
|
8073
|
+
const view = toDataView(data);
|
|
8074
|
+
if (view.byteLength < 4) return false;
|
|
8075
|
+
return view.getUint32(0) === TTC_TAG;
|
|
8076
|
+
}
|
|
8077
|
+
function extractTtcFonts(data) {
|
|
8078
|
+
const view = toDataView(data);
|
|
8079
|
+
const bytes = toUint8Array(data);
|
|
8080
|
+
if (view.byteLength < 12) return [];
|
|
8081
|
+
if (view.getUint32(0) !== TTC_TAG) return [];
|
|
8082
|
+
const numFonts = view.getUint32(8);
|
|
8083
|
+
if (numFonts === 0) return [];
|
|
8084
|
+
const headerEnd = 12 + numFonts * 4;
|
|
8085
|
+
if (view.byteLength < headerEnd) return [];
|
|
8086
|
+
const results = [];
|
|
8087
|
+
for (let i = 0; i < numFonts; i++) {
|
|
8088
|
+
try {
|
|
8089
|
+
const fontOffset = view.getUint32(12 + i * 4);
|
|
8090
|
+
const extracted = extractSingleFont(view, bytes, fontOffset);
|
|
8091
|
+
if (extracted) results.push(extracted);
|
|
8092
|
+
} catch {
|
|
8093
|
+
}
|
|
8094
|
+
}
|
|
8095
|
+
return results;
|
|
8096
|
+
}
|
|
8097
|
+
function extractSingleFont(view, bytes, fontOffset) {
|
|
8098
|
+
if (fontOffset + 12 > view.byteLength) return null;
|
|
8099
|
+
const sfVersion = view.getUint32(fontOffset);
|
|
8100
|
+
const numTables = view.getUint16(fontOffset + 4);
|
|
8101
|
+
if (numTables === 0) return null;
|
|
8102
|
+
const tableRecordsStart = fontOffset + 12;
|
|
8103
|
+
const tableRecordsEnd = tableRecordsStart + numTables * 16;
|
|
8104
|
+
if (tableRecordsEnd > view.byteLength) return null;
|
|
8105
|
+
const tables = [];
|
|
8106
|
+
for (let i = 0; i < numTables; i++) {
|
|
8107
|
+
const recOffset = tableRecordsStart + i * 16;
|
|
8108
|
+
const tableOffset = view.getUint32(recOffset + 8);
|
|
8109
|
+
const tableLength = view.getUint32(recOffset + 12);
|
|
8110
|
+
if (tableOffset > view.byteLength || tableLength > view.byteLength - tableOffset) {
|
|
8111
|
+
return null;
|
|
8112
|
+
}
|
|
8113
|
+
tables.push({
|
|
8114
|
+
tag: view.getUint32(recOffset),
|
|
8115
|
+
checkSum: view.getUint32(recOffset + 4),
|
|
8116
|
+
offset: tableOffset,
|
|
8117
|
+
length: tableLength
|
|
8118
|
+
});
|
|
8119
|
+
}
|
|
8120
|
+
const headerSize = 12 + numTables * 16;
|
|
8121
|
+
let dataSize = 0;
|
|
8122
|
+
for (const table of tables) {
|
|
8123
|
+
dataSize += alignTo4(table.length);
|
|
8124
|
+
}
|
|
8125
|
+
const totalSize = headerSize + dataSize;
|
|
8126
|
+
const output = new ArrayBuffer(totalSize);
|
|
8127
|
+
const outView = new DataView(output);
|
|
8128
|
+
const outBytes = new Uint8Array(output);
|
|
8129
|
+
outView.setUint32(0, sfVersion);
|
|
8130
|
+
outView.setUint16(4, numTables);
|
|
8131
|
+
const { searchRange, entrySelector, rangeShift } = calcOffsetTableFields(numTables);
|
|
8132
|
+
outView.setUint16(6, searchRange);
|
|
8133
|
+
outView.setUint16(8, entrySelector);
|
|
8134
|
+
outView.setUint16(10, rangeShift);
|
|
8135
|
+
let currentDataOffset = headerSize;
|
|
8136
|
+
for (let i = 0; i < numTables; i++) {
|
|
8137
|
+
const table = tables[i];
|
|
8138
|
+
const recOffset = 12 + i * 16;
|
|
8139
|
+
outView.setUint32(recOffset, table.tag);
|
|
8140
|
+
outView.setUint32(recOffset + 4, table.checkSum);
|
|
8141
|
+
outView.setUint32(recOffset + 8, currentDataOffset);
|
|
8142
|
+
outView.setUint32(recOffset + 12, table.length);
|
|
8143
|
+
outBytes.set(bytes.subarray(table.offset, table.offset + table.length), currentDataOffset);
|
|
8144
|
+
currentDataOffset += alignTo4(table.length);
|
|
8145
|
+
}
|
|
8146
|
+
return output;
|
|
8147
|
+
}
|
|
8148
|
+
function alignTo4(n) {
|
|
8149
|
+
return n + 3 & ~3;
|
|
8150
|
+
}
|
|
8151
|
+
function calcOffsetTableFields(numTables) {
|
|
8152
|
+
let searchRange = 16;
|
|
8153
|
+
let entrySelector = 0;
|
|
8154
|
+
while (searchRange * 2 <= numTables * 16) {
|
|
8155
|
+
searchRange *= 2;
|
|
8156
|
+
entrySelector++;
|
|
8157
|
+
}
|
|
8158
|
+
const rangeShift = numTables * 16 - searchRange;
|
|
8159
|
+
return { searchRange, entrySelector, rangeShift };
|
|
8160
|
+
}
|
|
8161
|
+
function toDataView(data) {
|
|
8162
|
+
if (data instanceof Uint8Array) {
|
|
8163
|
+
return new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
8164
|
+
}
|
|
8165
|
+
return new DataView(data);
|
|
8166
|
+
}
|
|
8167
|
+
function toUint8Array(data) {
|
|
8168
|
+
if (data instanceof Uint8Array) return data;
|
|
8169
|
+
return new Uint8Array(data);
|
|
8170
|
+
}
|
|
8171
|
+
|
|
8070
8172
|
// src/font/opentype-helpers.ts
|
|
8071
8173
|
async function tryLoadOpentype() {
|
|
8072
8174
|
try {
|
|
@@ -8093,6 +8195,63 @@ function toArrayBuffer(data) {
|
|
|
8093
8195
|
if (data instanceof ArrayBuffer) return data;
|
|
8094
8196
|
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
8095
8197
|
}
|
|
8198
|
+
function parseFontBuffer(arrayBuffer, opentype) {
|
|
8199
|
+
if (isTtcBuffer(arrayBuffer)) {
|
|
8200
|
+
const results = [];
|
|
8201
|
+
for (const buf of extractTtcFonts(arrayBuffer)) {
|
|
8202
|
+
try {
|
|
8203
|
+
results.push(opentype.parse(buf));
|
|
8204
|
+
} catch {
|
|
8205
|
+
}
|
|
8206
|
+
}
|
|
8207
|
+
return results;
|
|
8208
|
+
}
|
|
8209
|
+
return [opentype.parse(arrayBuffer)];
|
|
8210
|
+
}
|
|
8211
|
+
async function createOpentypeTextMeasurerFromBuffers(fontBuffers, fontMapping) {
|
|
8212
|
+
const setup = await createOpentypeSetupFromBuffers(fontBuffers, fontMapping);
|
|
8213
|
+
return setup?.measurer ?? null;
|
|
8214
|
+
}
|
|
8215
|
+
async function createOpentypeSetupFromBuffers(fontBuffers, fontMapping) {
|
|
8216
|
+
if (fontBuffers.length === 0) return null;
|
|
8217
|
+
const opentype = await tryLoadOpentype();
|
|
8218
|
+
if (!opentype) return null;
|
|
8219
|
+
const mapping = createFontMapping(fontMapping);
|
|
8220
|
+
const reverseMap = buildReverseMapping(mapping);
|
|
8221
|
+
const measurerFonts = /* @__PURE__ */ new Map();
|
|
8222
|
+
const resolverFonts = /* @__PURE__ */ new Map();
|
|
8223
|
+
let firstMeasurerFont = null;
|
|
8224
|
+
let firstResolverFont = null;
|
|
8225
|
+
for (const buffer of fontBuffers) {
|
|
8226
|
+
try {
|
|
8227
|
+
const arrayBuffer = toArrayBuffer(buffer.data);
|
|
8228
|
+
const isTtc = isTtcBuffer(arrayBuffer);
|
|
8229
|
+
const fonts = parseFontBuffer(arrayBuffer, opentype);
|
|
8230
|
+
for (const font of fonts) {
|
|
8231
|
+
if (!firstMeasurerFont) firstMeasurerFont = font;
|
|
8232
|
+
if (!firstResolverFont) firstResolverFont = font;
|
|
8233
|
+
if (isTtc) {
|
|
8234
|
+
const fontFamily = font.names.fontFamily;
|
|
8235
|
+
if (fontFamily) {
|
|
8236
|
+
for (const name of Object.values(fontFamily)) {
|
|
8237
|
+
registerFont(name, font, reverseMap, measurerFonts, resolverFonts);
|
|
8238
|
+
}
|
|
8239
|
+
}
|
|
8240
|
+
} else if (buffer.name) {
|
|
8241
|
+
registerFont(buffer.name, font, reverseMap, measurerFonts, resolverFonts);
|
|
8242
|
+
}
|
|
8243
|
+
}
|
|
8244
|
+
} catch {
|
|
8245
|
+
}
|
|
8246
|
+
}
|
|
8247
|
+
if (measurerFonts.size === 0 && !firstMeasurerFont) return null;
|
|
8248
|
+
const measurer = new OpentypeTextMeasurer(measurerFonts, firstMeasurerFont ?? void 0);
|
|
8249
|
+
const fontResolver = new DefaultTextPathFontResolver(
|
|
8250
|
+
resolverFonts,
|
|
8251
|
+
firstResolverFont ?? void 0
|
|
8252
|
+
);
|
|
8253
|
+
return { measurer, fontResolver };
|
|
8254
|
+
}
|
|
8096
8255
|
function registerFont(name, font, reverseMap, measurerFonts, resolverFonts) {
|
|
8097
8256
|
const fullFont = font;
|
|
8098
8257
|
if (!measurerFonts.has(name)) {
|
|
@@ -8124,13 +8283,15 @@ async function createOpentypeSetupFromSystem(additionalFontDirs, fontMapping) {
|
|
|
8124
8283
|
try {
|
|
8125
8284
|
const data = await readFile(filePath);
|
|
8126
8285
|
const arrayBuffer = toArrayBuffer(data);
|
|
8127
|
-
const
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8286
|
+
const fonts = parseFontBuffer(arrayBuffer, opentype);
|
|
8287
|
+
for (const font of fonts) {
|
|
8288
|
+
if (!firstMeasurerFont) firstMeasurerFont = font;
|
|
8289
|
+
if (!firstResolverFont) firstResolverFont = font;
|
|
8290
|
+
const fontFamily = font.names.fontFamily;
|
|
8291
|
+
if (fontFamily) {
|
|
8292
|
+
for (const name of Object.values(fontFamily)) {
|
|
8293
|
+
registerFont(name, font, reverseMap, measurerFonts, resolverFonts);
|
|
8294
|
+
}
|
|
8134
8295
|
}
|
|
8135
8296
|
}
|
|
8136
8297
|
} catch {
|
|
@@ -8275,6 +8436,8 @@ export {
|
|
|
8275
8436
|
convertPptxToPng,
|
|
8276
8437
|
convertPptxToSvg,
|
|
8277
8438
|
createFontMapping,
|
|
8439
|
+
createOpentypeSetupFromBuffers,
|
|
8440
|
+
createOpentypeTextMeasurerFromBuffers,
|
|
8278
8441
|
getMappedFont,
|
|
8279
8442
|
getWarningEntries,
|
|
8280
8443
|
getWarningSummary
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pptx-glimpse",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "A lightweight JavaScript library for rendering PowerPoint (.pptx) files as SVG or PNG in Node.js. No LibreOffice required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"test:coverage": "vitest run --coverage",
|
|
25
25
|
"test:watch": "vitest",
|
|
26
26
|
"typecheck": "tsc --noEmit",
|
|
27
|
+
"knip": "knip",
|
|
27
28
|
"dev": "tsx scripts/dev-server.ts",
|
|
28
29
|
"render": "tsx scripts/test-render.ts",
|
|
29
30
|
"inspect": "tsx scripts/inspect-pptx.ts",
|
|
@@ -75,6 +76,7 @@
|
|
|
75
76
|
"eslint": "^9.0.0",
|
|
76
77
|
"eslint-config-prettier": "^10.0.0",
|
|
77
78
|
"jszip": "^3.10.1",
|
|
79
|
+
"knip": "^5.85.0",
|
|
78
80
|
"pixelmatch": "^7.1.0",
|
|
79
81
|
"prettier": "^3.0.0",
|
|
80
82
|
"tsup": "^8.0.0",
|