pptx-glimpse 0.1.0 → 0.1.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/README.md +110 -105
- package/dist/index.cjs +44 -24
- package/dist/index.js +44 -24
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
# pptx-glimpse
|
|
2
2
|
|
|
3
|
-
PPTX
|
|
3
|
+
A TypeScript library for converting PPTX slides to SVG / PNG.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- **Node.js >= 20** (does not work in browser environments)
|
|
8
|
+
- Requires a platform supported by [sharp](https://sharp.pixelplumbing.com/), which is used for PNG conversion
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
6
11
|
|
|
7
12
|
```bash
|
|
8
13
|
npm install pptx-glimpse
|
|
9
14
|
```
|
|
10
15
|
|
|
11
|
-
##
|
|
16
|
+
## Usage
|
|
12
17
|
|
|
13
18
|
```typescript
|
|
14
19
|
import { readFileSync } from "fs";
|
|
@@ -16,138 +21,138 @@ import { convertPptxToSvg, convertPptxToPng } from "pptx-glimpse";
|
|
|
16
21
|
|
|
17
22
|
const pptx = readFileSync("presentation.pptx");
|
|
18
23
|
|
|
19
|
-
// SVG
|
|
24
|
+
// Convert to SVG
|
|
20
25
|
const svgResults = await convertPptxToSvg(pptx);
|
|
21
26
|
// [{ slideNumber: 1, svg: "<svg>...</svg>" }, ...]
|
|
22
27
|
|
|
23
|
-
// PNG
|
|
28
|
+
// Convert to PNG
|
|
24
29
|
const pngResults = await convertPptxToPng(pptx);
|
|
25
30
|
// [{ slideNumber: 1, png: Buffer, width: 960, height: 540 }, ...]
|
|
26
31
|
```
|
|
27
32
|
|
|
28
|
-
##
|
|
29
|
-
|
|
30
|
-
PowerPoint の静的なビジュアルコンテンツの変換に対応しています。アニメーションやトランジションなど、動的な要素には対応していません。
|
|
31
|
-
|
|
32
|
-
### 対応している機能
|
|
33
|
-
|
|
34
|
-
#### 図形
|
|
35
|
-
|
|
36
|
-
| 機能 | 詳細 |
|
|
37
|
-
| -------------- | ------------------------------------------------------------------------- |
|
|
38
|
-
| プリセット図形 | 113 種類 (矩形、楕円、矢印、フローチャート、吹き出し、星形、数学記号など) |
|
|
39
|
-
| カスタム図形 | カスタムパス (moveTo, lnTo, cubicBezTo, close) による任意の図形描画 |
|
|
40
|
-
| コネクター | 線コネクターの描画、線スタイル・色・太さの設定 |
|
|
41
|
-
| グループ | 図形のグループ化、ネストされたグループ |
|
|
42
|
-
| 変形 | 位置、サイズ、回転、反転 (flipH/flipV)、調整値 |
|
|
43
|
-
|
|
44
|
-
#### テキスト
|
|
45
|
-
|
|
46
|
-
| 機能 | 詳細 |
|
|
47
|
-
| ---------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
48
|
-
| 文字書式 | フォントサイズ、フォントファミリー (東アジアフォント対応)、太字、斜体、下線、取り消し線、文字色、上付き・下付き |
|
|
49
|
-
| 段落書式 | 水平配置 (left/center/right/justify)、垂直アンカー (top/center/bottom)、行間、段落前後スペース、インデント |
|
|
50
|
-
| 箇条書き | 文字箇条書き (buChar)、自動番号 (buAutoNum、9 種類)、箇条書きフォント・色・サイズ |
|
|
51
|
-
| テキストボックス | 折り返し (square/none)、自動調整 (noAutofit/normAutofit/spAutofit)、フォントスケール、マージン |
|
|
52
|
-
| 単語折り返し | 英語・日本語・CJK テキストの折り返し、複数フォントサイズ混在時の折り返し |
|
|
53
|
-
|
|
54
|
-
#### 塗りつぶし
|
|
55
|
-
|
|
56
|
-
| 機能 | 詳細 |
|
|
57
|
-
| -------------- | -------------------------------------------------------- |
|
|
58
|
-
| ソリッドカラー | RGB 色指定、透明度 |
|
|
59
|
-
| グラデーション | 線形グラデーション、複数グラデーションストップ、角度指定 |
|
|
60
|
-
| 画像塗りつぶし | PNG/JPEG/GIF、引き伸ばし (stretch) モード |
|
|
61
|
-
| 塗りつぶしなし | noFill 指定 |
|
|
62
|
-
|
|
63
|
-
#### 線・枠線
|
|
64
|
-
|
|
65
|
-
| 機能 | 詳細 |
|
|
66
|
-
| ------------ | ------------------------------------------------------------- |
|
|
67
|
-
| 線スタイル | 線幅、ソリッドカラー、透明度 |
|
|
68
|
-
| 破線スタイル | solid, dash, dot, dashDot, lgDash, lgDashDot, sysDash, sysDot |
|
|
33
|
+
## Feature Support
|
|
69
34
|
|
|
70
|
-
|
|
35
|
+
Supports conversion of static visual content in PowerPoint. Dynamic elements such as animations and transitions are not supported.
|
|
71
36
|
|
|
72
|
-
|
|
73
|
-
| ------------ | ----------------------------------------------------------------------------- |
|
|
74
|
-
| 色指定方式 | RGB (srgbClr)、テーマ色 (schemeClr)、システム色 (sysClr) |
|
|
75
|
-
| テーマカラー | カラースキーム (dk1, lt1, dk2, lt2, accent1-6, hlink, folHlink)、カラーマップ |
|
|
76
|
-
| 色変換 | 輝度調整 (lumMod/lumOff)、ティント (tint)、シェード (shade)、透明度 (alpha) |
|
|
37
|
+
### Supported Features
|
|
77
38
|
|
|
78
|
-
####
|
|
39
|
+
#### Shapes
|
|
79
40
|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
|
|
|
41
|
+
| Feature | Details |
|
|
42
|
+
| -------------- | ------------------------------------------------------------------------------------------------ |
|
|
43
|
+
| Preset shapes | 113 types (rectangles, ellipses, arrows, flowcharts, callouts, stars, math symbols, etc.) |
|
|
44
|
+
| Custom shapes | Arbitrary shape drawing using custom paths (moveTo, lnTo, cubicBezTo, close) |
|
|
45
|
+
| Connectors | Line connector drawing, line style / color / width settings |
|
|
46
|
+
| Groups | Shape grouping, nested groups |
|
|
47
|
+
| Transforms | Position, size, rotation, flip (flipH/flipV), adjustment values |
|
|
86
48
|
|
|
87
|
-
####
|
|
49
|
+
#### Text
|
|
88
50
|
|
|
89
|
-
|
|
|
90
|
-
| -------------- |
|
|
91
|
-
|
|
|
92
|
-
|
|
|
51
|
+
| Feature | Details |
|
|
52
|
+
| -------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| Character formatting | Font size, font family (East Asian font support), bold, italic, underline, strikethrough, font color, superscript / subscript |
|
|
54
|
+
| Paragraph formatting | Horizontal alignment (left/center/right/justify), vertical anchor (top/center/bottom), line spacing, before/after paragraph spacing, indent |
|
|
55
|
+
| Bullet points | Character bullets (buChar), auto-numbering (buAutoNum, 9 types), bullet font / color / size |
|
|
56
|
+
| Text boxes | Word wrap (square/none), auto-fit (noAutofit/normAutofit/spAutofit), font scaling, margins |
|
|
57
|
+
| Word wrapping | Word wrapping for English, Japanese, and CJK text, wrapping with mixed font sizes |
|
|
93
58
|
|
|
94
|
-
####
|
|
59
|
+
#### Fill
|
|
95
60
|
|
|
96
|
-
|
|
|
97
|
-
|
|
|
98
|
-
|
|
|
99
|
-
|
|
|
61
|
+
| Feature | Details |
|
|
62
|
+
| -------------- | ---------------------------------------------------- |
|
|
63
|
+
| Solid color | RGB color specification, transparency |
|
|
64
|
+
| Gradient | Linear gradient, multiple gradient stops, angle |
|
|
65
|
+
| Image fill | PNG/JPEG/GIF, stretch mode |
|
|
66
|
+
| No fill | noFill specification |
|
|
100
67
|
|
|
101
|
-
####
|
|
68
|
+
#### Lines & Borders
|
|
102
69
|
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
|
|
|
70
|
+
| Feature | Details |
|
|
71
|
+
| -------------- | ------------------------------------------------------------- |
|
|
72
|
+
| Line style | Line width, solid color, transparency |
|
|
73
|
+
| Dash style | solid, dash, dot, dashDot, lgDash, lgDashDot, sysDash, sysDot |
|
|
107
74
|
|
|
108
|
-
####
|
|
75
|
+
#### Colors
|
|
109
76
|
|
|
110
|
-
|
|
|
77
|
+
| Feature | Details |
|
|
111
78
|
| -------------- | ------------------------------------------------------------------------------------ |
|
|
112
|
-
|
|
|
113
|
-
|
|
|
114
|
-
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
79
|
+
| Color types | RGB (srgbClr), theme color (schemeClr), system color (sysClr) |
|
|
80
|
+
| Theme colors | Color scheme (dk1, lt1, dk2, lt2, accent1-6, hlink, folHlink), color map |
|
|
81
|
+
| Color transforms | Luminance adjustment (lumMod/lumOff), tint, shade, transparency (alpha) |
|
|
82
|
+
|
|
83
|
+
#### Effects
|
|
84
|
+
|
|
85
|
+
| Feature | Details |
|
|
86
|
+
| -------------- | ------------------------------------------ |
|
|
87
|
+
| Outer shadow | Blur radius, distance, direction, color / transparency |
|
|
88
|
+
| Inner shadow | Blur radius, distance, direction, color / transparency |
|
|
89
|
+
| Glow | Radius, color / transparency |
|
|
90
|
+
| Soft edge | Radius |
|
|
91
|
+
|
|
92
|
+
#### Images
|
|
93
|
+
|
|
94
|
+
| Feature | Details |
|
|
95
|
+
| -------------- | ---------------------------------------------------------- |
|
|
96
|
+
| Image elements | PNG/JPEG/GIF, position / size / rotation / flip, effects |
|
|
97
|
+
| Image fill | Image fill for shapes and backgrounds |
|
|
98
|
+
|
|
99
|
+
#### Tables
|
|
100
|
+
|
|
101
|
+
| Feature | Details |
|
|
102
|
+
| ---------------- | --------------------------------------------------------------------------- |
|
|
103
|
+
| Table structure | Row and column grid, cell merging (gridSpan/rowSpan), row height / column width |
|
|
104
|
+
| Cell formatting | Text, fill (solid/gradient/image), borders (top/bottom/left/right, styles) |
|
|
105
|
+
|
|
106
|
+
#### Charts
|
|
107
|
+
|
|
108
|
+
| Feature | Details |
|
|
109
|
+
| ---------------- | ------------------------------------------------------------------------------------ |
|
|
110
|
+
| Supported charts | Bar chart (vertical/horizontal), line chart, pie chart, scatter plot |
|
|
111
|
+
| Chart elements | Title, legend (position), series (name/values/categories/color), category axis, value axis |
|
|
112
|
+
|
|
113
|
+
#### Background & Slide Settings
|
|
114
|
+
|
|
115
|
+
| Feature | Details |
|
|
116
|
+
| -------------- | --------------------------------------------------------------------------------------- |
|
|
117
|
+
| Background | Solid, gradient, image. Fallback order: slide → layout → master |
|
|
118
|
+
| Slide size | 16:9, 4:3, custom sizes |
|
|
119
|
+
| Theme | Theme color scheme, theme fonts (majorFont/minorFont) |
|
|
120
|
+
|
|
121
|
+
### Unsupported Features
|
|
122
|
+
|
|
123
|
+
| Category | Unsupported features |
|
|
124
|
+
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
125
|
+
| Fill | Pattern fill, radial gradient, path gradient |
|
|
126
|
+
| Effects | Reflection, 3D rotation / extrusion, artistic effects |
|
|
127
|
+
| Charts | Area, radar, doughnut, bubble, stock, combo, histogram, box plot, waterfall, treemap, sunburst |
|
|
128
|
+
| Chart details | Data labels, axis titles / tick marks / grid lines, error bars, trendlines |
|
|
129
|
+
| Text | Vertical text, individual text effects (shadow/glow), text outline, text columns |
|
|
130
|
+
| Tables | Table style template application, diagonal borders |
|
|
131
|
+
| Shapes | Shape operations (Union/Subtract/Intersect/Fragment) |
|
|
132
|
+
| SmartArt | All SmartArt features |
|
|
133
|
+
| Multimedia | Embedded video / audio |
|
|
134
|
+
| Animations | Object animations, slide transitions |
|
|
135
|
+
| Links | Hyperlinks |
|
|
136
|
+
| Slide elements | Slide notes, comments, headers / footers, slide numbers / dates |
|
|
137
|
+
| Image formats | EMF/WMF (parsed but not rendered) |
|
|
138
|
+
| Other | Macros / VBA, sections, zoom slides |
|
|
139
|
+
|
|
140
|
+
## Test Rendering
|
|
141
|
+
|
|
142
|
+
You can specify a PPTX file to preview SVG and PNG conversion results.
|
|
138
143
|
|
|
139
144
|
```bash
|
|
140
145
|
npm run render -- <pptx-file> [output-dir]
|
|
141
146
|
```
|
|
142
147
|
|
|
143
|
-
- `output-dir`
|
|
148
|
+
- If `output-dir` is omitted, output is saved to `./output`
|
|
144
149
|
|
|
145
150
|
```bash
|
|
146
|
-
#
|
|
151
|
+
# Examples
|
|
147
152
|
npm run render -- presentation.pptx
|
|
148
153
|
npm run render -- presentation.pptx ./my-output
|
|
149
154
|
```
|
|
150
155
|
|
|
151
|
-
##
|
|
156
|
+
## License
|
|
152
157
|
|
|
153
158
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -246,11 +246,12 @@ function parseRelationships(xml) {
|
|
|
246
246
|
const id = rel["@_Id"];
|
|
247
247
|
const type = rel["@_Type"];
|
|
248
248
|
const target = rel["@_Target"];
|
|
249
|
+
const targetMode = rel["@_TargetMode"];
|
|
249
250
|
if (!id || !type || !target) {
|
|
250
251
|
console.warn(`${WARN_PREFIX3} Relationship: entry missing required attribute, skipping`);
|
|
251
252
|
continue;
|
|
252
253
|
}
|
|
253
|
-
rels.set(id, { id, type, target });
|
|
254
|
+
rels.set(id, { id, type, target, ...targetMode && { targetMode } });
|
|
254
255
|
}
|
|
255
256
|
return rels;
|
|
256
257
|
}
|
|
@@ -630,7 +631,7 @@ function parseShapeTree(spTree, rels, slidePath, archive, colorResolver, context
|
|
|
630
631
|
const elements = [];
|
|
631
632
|
const shapes = spTree.sp ?? [];
|
|
632
633
|
for (const sp of shapes) {
|
|
633
|
-
const shape = parseShape(sp, colorResolver);
|
|
634
|
+
const shape = parseShape(sp, colorResolver, rels);
|
|
634
635
|
if (shape) {
|
|
635
636
|
elements.push(shape);
|
|
636
637
|
} else {
|
|
@@ -675,7 +676,7 @@ function parseShapeTree(spTree, rels, slidePath, archive, colorResolver, context
|
|
|
675
676
|
}
|
|
676
677
|
return elements;
|
|
677
678
|
}
|
|
678
|
-
function parseShape(sp, colorResolver) {
|
|
679
|
+
function parseShape(sp, colorResolver, rels) {
|
|
679
680
|
const spPr = sp.spPr;
|
|
680
681
|
if (!spPr) return null;
|
|
681
682
|
const transform = parseTransform(spPr.xfrm);
|
|
@@ -683,7 +684,7 @@ function parseShape(sp, colorResolver) {
|
|
|
683
684
|
const geometry = parseGeometry(spPr);
|
|
684
685
|
const fill = parseFillFromNode(spPr, colorResolver);
|
|
685
686
|
const outline = parseOutline(spPr.ln, colorResolver);
|
|
686
|
-
const textBody = parseTextBody(sp.txBody, colorResolver);
|
|
687
|
+
const textBody = parseTextBody(sp.txBody, colorResolver, rels);
|
|
687
688
|
const effects = parseEffectList(spPr.effectLst, colorResolver);
|
|
688
689
|
const ph = sp.nvSpPr?.nvPr?.ph;
|
|
689
690
|
const placeholderType = ph ? ph["@_type"] ?? "body" : void 0;
|
|
@@ -899,7 +900,7 @@ function parseCustomGeometryPaths(custGeom) {
|
|
|
899
900
|
}
|
|
900
901
|
return svgParts.length > 0 ? svgParts.join(" ") : null;
|
|
901
902
|
}
|
|
902
|
-
function parseTextBody(txBody, colorResolver) {
|
|
903
|
+
function parseTextBody(txBody, colorResolver, rels) {
|
|
903
904
|
if (!txBody) return null;
|
|
904
905
|
const bodyPr = txBody.bodyPr;
|
|
905
906
|
let autoFit = "noAutofit";
|
|
@@ -929,7 +930,7 @@ function parseTextBody(txBody, colorResolver) {
|
|
|
929
930
|
const paragraphs = [];
|
|
930
931
|
const pList = txBody.p ?? [];
|
|
931
932
|
for (const p of pList) {
|
|
932
|
-
paragraphs.push(parseParagraph(p, colorResolver));
|
|
933
|
+
paragraphs.push(parseParagraph(p, colorResolver, rels));
|
|
933
934
|
}
|
|
934
935
|
if (paragraphs.length === 0) return null;
|
|
935
936
|
return { paragraphs, bodyProperties };
|
|
@@ -968,7 +969,7 @@ function parseBullet(pPr, colorResolver) {
|
|
|
968
969
|
}
|
|
969
970
|
return { bullet, bulletFont, bulletColor, bulletSizePct };
|
|
970
971
|
}
|
|
971
|
-
function parseParagraph(p, colorResolver) {
|
|
972
|
+
function parseParagraph(p, colorResolver, rels) {
|
|
972
973
|
const pPr = p.pPr;
|
|
973
974
|
const { bullet, bulletFont, bulletColor, bulletSizePct } = parseBullet(pPr, colorResolver);
|
|
974
975
|
const properties = {
|
|
@@ -990,12 +991,12 @@ function parseParagraph(p, colorResolver) {
|
|
|
990
991
|
const text = r.t ?? "";
|
|
991
992
|
const textContent = typeof text === "object" ? text["#text"] ?? "" : String(text);
|
|
992
993
|
const rPr = r.rPr;
|
|
993
|
-
const runProps = parseRunProperties(rPr, colorResolver);
|
|
994
|
+
const runProps = parseRunProperties(rPr, colorResolver, rels);
|
|
994
995
|
runs.push({ text: textContent, properties: runProps });
|
|
995
996
|
}
|
|
996
997
|
return { runs, properties };
|
|
997
998
|
}
|
|
998
|
-
function parseRunProperties(rPr, colorResolver) {
|
|
999
|
+
function parseRunProperties(rPr, colorResolver, rels) {
|
|
999
1000
|
if (!rPr) {
|
|
1000
1001
|
return {
|
|
1001
1002
|
fontSize: null,
|
|
@@ -1006,7 +1007,8 @@ function parseRunProperties(rPr, colorResolver) {
|
|
|
1006
1007
|
underline: false,
|
|
1007
1008
|
strikethrough: false,
|
|
1008
1009
|
color: null,
|
|
1009
|
-
baseline: 0
|
|
1010
|
+
baseline: 0,
|
|
1011
|
+
hyperlink: null
|
|
1010
1012
|
};
|
|
1011
1013
|
}
|
|
1012
1014
|
const fontSize = rPr["@_sz"] ? hundredthPointToPoint(Number(rPr["@_sz"])) : null;
|
|
@@ -1021,6 +1023,7 @@ function parseRunProperties(rPr, colorResolver) {
|
|
|
1021
1023
|
if (!rPr.solidFill && !rPr.srgbClr && !rPr.schemeClr && !rPr.sysClr) {
|
|
1022
1024
|
color = null;
|
|
1023
1025
|
}
|
|
1026
|
+
const hyperlink = parseHyperlink(rPr.hlinkClick, rels);
|
|
1024
1027
|
return {
|
|
1025
1028
|
fontSize,
|
|
1026
1029
|
fontFamily,
|
|
@@ -1030,9 +1033,19 @@ function parseRunProperties(rPr, colorResolver) {
|
|
|
1030
1033
|
underline,
|
|
1031
1034
|
strikethrough,
|
|
1032
1035
|
color,
|
|
1033
|
-
baseline
|
|
1036
|
+
baseline,
|
|
1037
|
+
hyperlink
|
|
1034
1038
|
};
|
|
1035
1039
|
}
|
|
1040
|
+
function parseHyperlink(hlinkClick, rels) {
|
|
1041
|
+
if (!hlinkClick) return null;
|
|
1042
|
+
const rId = hlinkClick["@_r:id"] ?? hlinkClick["@_id"];
|
|
1043
|
+
if (!rId || !rels) return null;
|
|
1044
|
+
const rel = rels.get(rId);
|
|
1045
|
+
if (!rel) return null;
|
|
1046
|
+
const tooltip = hlinkClick["@_tooltip"];
|
|
1047
|
+
return { url: rel.target, ...tooltip && { tooltip } };
|
|
1048
|
+
}
|
|
1036
1049
|
|
|
1037
1050
|
// src/parser/slide-master-parser.ts
|
|
1038
1051
|
var WARN_PREFIX6 = "[pptx-glimpse]";
|
|
@@ -3623,23 +3636,30 @@ function buildStyleAttrs(props, fontScale = 1, fontFamilies) {
|
|
|
3623
3636
|
return styles.join(" ");
|
|
3624
3637
|
}
|
|
3625
3638
|
function renderSegment(text, props, fontScale, prefix) {
|
|
3639
|
+
let tspanContent;
|
|
3626
3640
|
if (!needsScriptSplit(props)) {
|
|
3627
3641
|
const styles = buildStyleAttrs(props, fontScale);
|
|
3628
|
-
|
|
3629
|
-
}
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3642
|
+
tspanContent = `<tspan ${prefix}${styles}>${escapeXml(text)}</tspan>`;
|
|
3643
|
+
} else {
|
|
3644
|
+
const parts = splitByScript(text);
|
|
3645
|
+
const result = [];
|
|
3646
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3647
|
+
const part = parts[i];
|
|
3648
|
+
const fonts = part.isEa ? [props.fontFamilyEa, props.fontFamily] : [props.fontFamily, props.fontFamilyEa];
|
|
3649
|
+
const styles = buildStyleAttrs(props, fontScale, fonts);
|
|
3650
|
+
if (i === 0) {
|
|
3651
|
+
result.push(`<tspan ${prefix}${styles}>${escapeXml(part.text)}</tspan>`);
|
|
3652
|
+
} else {
|
|
3653
|
+
result.push(`<tspan ${styles}>${escapeXml(part.text)}</tspan>`);
|
|
3654
|
+
}
|
|
3640
3655
|
}
|
|
3656
|
+
tspanContent = result.join("");
|
|
3657
|
+
}
|
|
3658
|
+
if (props.hyperlink) {
|
|
3659
|
+
const href = escapeXml(props.hyperlink.url);
|
|
3660
|
+
return `<a href="${href}">${tspanContent}</a>`;
|
|
3641
3661
|
}
|
|
3642
|
-
return
|
|
3662
|
+
return tspanContent;
|
|
3643
3663
|
}
|
|
3644
3664
|
function getDefaultFontSize(paragraphs) {
|
|
3645
3665
|
for (const p of paragraphs) {
|
package/dist/index.js
CHANGED
|
@@ -209,11 +209,12 @@ function parseRelationships(xml) {
|
|
|
209
209
|
const id = rel["@_Id"];
|
|
210
210
|
const type = rel["@_Type"];
|
|
211
211
|
const target = rel["@_Target"];
|
|
212
|
+
const targetMode = rel["@_TargetMode"];
|
|
212
213
|
if (!id || !type || !target) {
|
|
213
214
|
console.warn(`${WARN_PREFIX3} Relationship: entry missing required attribute, skipping`);
|
|
214
215
|
continue;
|
|
215
216
|
}
|
|
216
|
-
rels.set(id, { id, type, target });
|
|
217
|
+
rels.set(id, { id, type, target, ...targetMode && { targetMode } });
|
|
217
218
|
}
|
|
218
219
|
return rels;
|
|
219
220
|
}
|
|
@@ -593,7 +594,7 @@ function parseShapeTree(spTree, rels, slidePath, archive, colorResolver, context
|
|
|
593
594
|
const elements = [];
|
|
594
595
|
const shapes = spTree.sp ?? [];
|
|
595
596
|
for (const sp of shapes) {
|
|
596
|
-
const shape = parseShape(sp, colorResolver);
|
|
597
|
+
const shape = parseShape(sp, colorResolver, rels);
|
|
597
598
|
if (shape) {
|
|
598
599
|
elements.push(shape);
|
|
599
600
|
} else {
|
|
@@ -638,7 +639,7 @@ function parseShapeTree(spTree, rels, slidePath, archive, colorResolver, context
|
|
|
638
639
|
}
|
|
639
640
|
return elements;
|
|
640
641
|
}
|
|
641
|
-
function parseShape(sp, colorResolver) {
|
|
642
|
+
function parseShape(sp, colorResolver, rels) {
|
|
642
643
|
const spPr = sp.spPr;
|
|
643
644
|
if (!spPr) return null;
|
|
644
645
|
const transform = parseTransform(spPr.xfrm);
|
|
@@ -646,7 +647,7 @@ function parseShape(sp, colorResolver) {
|
|
|
646
647
|
const geometry = parseGeometry(spPr);
|
|
647
648
|
const fill = parseFillFromNode(spPr, colorResolver);
|
|
648
649
|
const outline = parseOutline(spPr.ln, colorResolver);
|
|
649
|
-
const textBody = parseTextBody(sp.txBody, colorResolver);
|
|
650
|
+
const textBody = parseTextBody(sp.txBody, colorResolver, rels);
|
|
650
651
|
const effects = parseEffectList(spPr.effectLst, colorResolver);
|
|
651
652
|
const ph = sp.nvSpPr?.nvPr?.ph;
|
|
652
653
|
const placeholderType = ph ? ph["@_type"] ?? "body" : void 0;
|
|
@@ -862,7 +863,7 @@ function parseCustomGeometryPaths(custGeom) {
|
|
|
862
863
|
}
|
|
863
864
|
return svgParts.length > 0 ? svgParts.join(" ") : null;
|
|
864
865
|
}
|
|
865
|
-
function parseTextBody(txBody, colorResolver) {
|
|
866
|
+
function parseTextBody(txBody, colorResolver, rels) {
|
|
866
867
|
if (!txBody) return null;
|
|
867
868
|
const bodyPr = txBody.bodyPr;
|
|
868
869
|
let autoFit = "noAutofit";
|
|
@@ -892,7 +893,7 @@ function parseTextBody(txBody, colorResolver) {
|
|
|
892
893
|
const paragraphs = [];
|
|
893
894
|
const pList = txBody.p ?? [];
|
|
894
895
|
for (const p of pList) {
|
|
895
|
-
paragraphs.push(parseParagraph(p, colorResolver));
|
|
896
|
+
paragraphs.push(parseParagraph(p, colorResolver, rels));
|
|
896
897
|
}
|
|
897
898
|
if (paragraphs.length === 0) return null;
|
|
898
899
|
return { paragraphs, bodyProperties };
|
|
@@ -931,7 +932,7 @@ function parseBullet(pPr, colorResolver) {
|
|
|
931
932
|
}
|
|
932
933
|
return { bullet, bulletFont, bulletColor, bulletSizePct };
|
|
933
934
|
}
|
|
934
|
-
function parseParagraph(p, colorResolver) {
|
|
935
|
+
function parseParagraph(p, colorResolver, rels) {
|
|
935
936
|
const pPr = p.pPr;
|
|
936
937
|
const { bullet, bulletFont, bulletColor, bulletSizePct } = parseBullet(pPr, colorResolver);
|
|
937
938
|
const properties = {
|
|
@@ -953,12 +954,12 @@ function parseParagraph(p, colorResolver) {
|
|
|
953
954
|
const text = r.t ?? "";
|
|
954
955
|
const textContent = typeof text === "object" ? text["#text"] ?? "" : String(text);
|
|
955
956
|
const rPr = r.rPr;
|
|
956
|
-
const runProps = parseRunProperties(rPr, colorResolver);
|
|
957
|
+
const runProps = parseRunProperties(rPr, colorResolver, rels);
|
|
957
958
|
runs.push({ text: textContent, properties: runProps });
|
|
958
959
|
}
|
|
959
960
|
return { runs, properties };
|
|
960
961
|
}
|
|
961
|
-
function parseRunProperties(rPr, colorResolver) {
|
|
962
|
+
function parseRunProperties(rPr, colorResolver, rels) {
|
|
962
963
|
if (!rPr) {
|
|
963
964
|
return {
|
|
964
965
|
fontSize: null,
|
|
@@ -969,7 +970,8 @@ function parseRunProperties(rPr, colorResolver) {
|
|
|
969
970
|
underline: false,
|
|
970
971
|
strikethrough: false,
|
|
971
972
|
color: null,
|
|
972
|
-
baseline: 0
|
|
973
|
+
baseline: 0,
|
|
974
|
+
hyperlink: null
|
|
973
975
|
};
|
|
974
976
|
}
|
|
975
977
|
const fontSize = rPr["@_sz"] ? hundredthPointToPoint(Number(rPr["@_sz"])) : null;
|
|
@@ -984,6 +986,7 @@ function parseRunProperties(rPr, colorResolver) {
|
|
|
984
986
|
if (!rPr.solidFill && !rPr.srgbClr && !rPr.schemeClr && !rPr.sysClr) {
|
|
985
987
|
color = null;
|
|
986
988
|
}
|
|
989
|
+
const hyperlink = parseHyperlink(rPr.hlinkClick, rels);
|
|
987
990
|
return {
|
|
988
991
|
fontSize,
|
|
989
992
|
fontFamily,
|
|
@@ -993,9 +996,19 @@ function parseRunProperties(rPr, colorResolver) {
|
|
|
993
996
|
underline,
|
|
994
997
|
strikethrough,
|
|
995
998
|
color,
|
|
996
|
-
baseline
|
|
999
|
+
baseline,
|
|
1000
|
+
hyperlink
|
|
997
1001
|
};
|
|
998
1002
|
}
|
|
1003
|
+
function parseHyperlink(hlinkClick, rels) {
|
|
1004
|
+
if (!hlinkClick) return null;
|
|
1005
|
+
const rId = hlinkClick["@_r:id"] ?? hlinkClick["@_id"];
|
|
1006
|
+
if (!rId || !rels) return null;
|
|
1007
|
+
const rel = rels.get(rId);
|
|
1008
|
+
if (!rel) return null;
|
|
1009
|
+
const tooltip = hlinkClick["@_tooltip"];
|
|
1010
|
+
return { url: rel.target, ...tooltip && { tooltip } };
|
|
1011
|
+
}
|
|
999
1012
|
|
|
1000
1013
|
// src/parser/slide-master-parser.ts
|
|
1001
1014
|
var WARN_PREFIX6 = "[pptx-glimpse]";
|
|
@@ -3586,23 +3599,30 @@ function buildStyleAttrs(props, fontScale = 1, fontFamilies) {
|
|
|
3586
3599
|
return styles.join(" ");
|
|
3587
3600
|
}
|
|
3588
3601
|
function renderSegment(text, props, fontScale, prefix) {
|
|
3602
|
+
let tspanContent;
|
|
3589
3603
|
if (!needsScriptSplit(props)) {
|
|
3590
3604
|
const styles = buildStyleAttrs(props, fontScale);
|
|
3591
|
-
|
|
3592
|
-
}
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3605
|
+
tspanContent = `<tspan ${prefix}${styles}>${escapeXml(text)}</tspan>`;
|
|
3606
|
+
} else {
|
|
3607
|
+
const parts = splitByScript(text);
|
|
3608
|
+
const result = [];
|
|
3609
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3610
|
+
const part = parts[i];
|
|
3611
|
+
const fonts = part.isEa ? [props.fontFamilyEa, props.fontFamily] : [props.fontFamily, props.fontFamilyEa];
|
|
3612
|
+
const styles = buildStyleAttrs(props, fontScale, fonts);
|
|
3613
|
+
if (i === 0) {
|
|
3614
|
+
result.push(`<tspan ${prefix}${styles}>${escapeXml(part.text)}</tspan>`);
|
|
3615
|
+
} else {
|
|
3616
|
+
result.push(`<tspan ${styles}>${escapeXml(part.text)}</tspan>`);
|
|
3617
|
+
}
|
|
3603
3618
|
}
|
|
3619
|
+
tspanContent = result.join("");
|
|
3620
|
+
}
|
|
3621
|
+
if (props.hyperlink) {
|
|
3622
|
+
const href = escapeXml(props.hyperlink.url);
|
|
3623
|
+
return `<a href="${href}">${tspanContent}</a>`;
|
|
3604
3624
|
}
|
|
3605
|
-
return
|
|
3625
|
+
return tspanContent;
|
|
3606
3626
|
}
|
|
3607
3627
|
function getDefaultFontSize(paragraphs) {
|
|
3608
3628
|
for (const p of paragraphs) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pptx-glimpse",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Convert PPTX slides to SVG and PNG",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"vrt:lo:docker-build": "docker build -t pptx-glimpse-vrt docker/libreoffice-vrt",
|
|
29
29
|
"vrt:lo:fixtures": "docker run --rm -v \"$(pwd)\":/workspace pptx-glimpse-vrt python3 /workspace/vrt/libreoffice/generate_fixtures.py",
|
|
30
30
|
"vrt:lo:reference": "docker run --rm -v \"$(pwd)\":/workspace pptx-glimpse-vrt bash /workspace/vrt/libreoffice/render_references.sh",
|
|
31
|
-
"vrt:lo:update": "npm run vrt:lo:docker-build && npm run vrt:lo:fixtures && npm run vrt:lo:reference"
|
|
31
|
+
"vrt:lo:update": "npm run vrt:lo:docker-build && npm run vrt:lo:fixtures && npm run vrt:lo:reference",
|
|
32
|
+
"test:package": "npm run build && bash scripts/test-package.sh"
|
|
32
33
|
},
|
|
33
34
|
"files": [
|
|
34
35
|
"dist"
|