@file-viewer/pptx 3.2.2 → 3.3.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.en.md +3 -0
- package/README.md +13 -10
- package/README.zh-CN.md +37 -0
- package/dist/chart-config.d.ts +16 -1
- package/dist/chart-config.js +18 -8
- package/dist/chart-legend.d.ts +11 -0
- package/dist/chart-legend.js +103 -0
- package/dist/chart-text.d.ts +19 -0
- package/dist/chart-text.js +164 -0
- package/dist/chart-types.d.ts +17 -8
- package/dist/chart.d.ts +3 -1
- package/dist/chart.js +48 -2
- package/dist/index.d.ts +9 -9
- package/dist/index.js +5 -5
- package/dist/options.d.ts +1 -1
- package/dist/presentation.d.ts +20 -1
- package/dist/presentation.js +39 -0
- package/dist/styles.d.ts +5 -0
- package/dist/styles.js +5 -0
- package/dist/types.d.ts +2 -2
- package/dist/viewer.d.ts +13 -2
- package/dist/viewer.js +27 -6
- package/dist/worker/pptx.worker.js +9 -9
- package/dist/worker-location.d.ts +1 -0
- package/dist/worker-location.js +3 -0
- package/dist/worker.d.ts +2 -1
- package/dist/worker.js +13 -1
- package/package.json +8 -3
package/README.en.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @file-viewer/pptx
|
|
2
2
|
|
|
3
|
+
[English](https://github.com/flyfish-dev/file-viewer/blob/main/packages/renderers/pptx/README.md) · [简体中文](https://github.com/flyfish-dev/file-viewer/blob/main/packages/renderers/pptx/README.zh-CN.md)
|
|
4
|
+
|
|
3
5
|
Native PPTX rendering engine extracted from Flyfish File Viewer. It is maintained as a standalone package so presentation rendering can evolve independently from framework adapters.
|
|
4
6
|
|
|
5
7
|
## Highlights
|
|
@@ -8,6 +10,7 @@ Native PPTX rendering engine extracted from Flyfish File Viewer. It is maintaine
|
|
|
8
10
|
- Web Worker based PPTX parsing with progressive slide output.
|
|
9
11
|
- Preserves the latest native Flyfish rendering path for themes, images, shapes, tables, text, and basic charts.
|
|
10
12
|
- Supports `fitMode`, `zoomPercent`, custom Worker URLs, and Worker factories for private deployment and strict CSP environments.
|
|
13
|
+
- Charts retain authored sizes and text styles through slideshow zoom. CJK labels use independent logical measurements; legends support bottom, right, top, top-right and left positions. Fonts must be installed or supplied by the host; unavailable fonts use browser fallbacks.
|
|
11
14
|
|
|
12
15
|
`resolvePptxPackageWorkerUrl()` lets integrations identify an emitted or directly served package Worker URL. It returns `undefined` when the location is not recognized, not as proof that a file is missing; integrations can then use their asset-copy strategy or explicit Worker configuration.
|
|
13
16
|
|
package/README.md
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
# @file-viewer/pptx
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](https://github.com/flyfish-dev/file-viewer/blob/main/packages/renderers/pptx/README.md) · [简体中文](https://github.com/flyfish-dev/file-viewer/blob/main/packages/renderers/pptx/README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Native PPTX rendering engine extracted from Flyfish File Viewer. It is maintained as a standalone package so presentation rendering can evolve independently from framework adapters.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- 使用 Web Worker 解析 PPTX,按页渐进输出,避免阻塞主线程。
|
|
9
|
-
- 保留历史原生解析链路中的主题、图片、形状、表格、文本和基础图表处理。
|
|
10
|
-
- 支持 `fitMode`、`zoomPercent`、自定义 Worker URL / Worker factory,方便内网、离线和 CSP 场景。
|
|
7
|
+
## Highlights
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
- Pure TypeScript/DOM API with no Vue, React, or iframe dependency.
|
|
10
|
+
- Web Worker based PPTX parsing with progressive slide output.
|
|
11
|
+
- Preserves the latest native Flyfish rendering path for themes, images, shapes, tables, text, and basic charts.
|
|
12
|
+
- Supports `fitMode`, `zoomPercent`, custom Worker URLs, and Worker factories for private deployment and strict CSP environments.
|
|
13
|
+
- Charts retain authored sizes and text styles through slideshow zoom. CJK labels use independent logical measurements; legends support bottom, right, top, top-right and left positions. Fonts must be installed or supplied by the host; unavailable fonts use browser fallbacks.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
`resolvePptxPackageWorkerUrl()` lets integrations identify an emitted or directly served package Worker URL. It returns `undefined` when the location is not recognized, not as proof that a file is missing; integrations can then use their asset-copy strategy or explicit Worker configuration.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
When `PptxViewer` is used directly, Vite or Webpack can serve a recognized package Worker; an unrecognized build never guesses an application-relative path. Angular, IIFE, offline static directories, and strict CSP deployments should use `file-viewer-copy-assets` or an explicit `workerUrl` so the final static origin serves the Worker reliably.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
17
20
|
|
|
18
21
|
```ts
|
|
19
22
|
import { PptxViewer } from '@file-viewer/pptx'
|
|
@@ -31,4 +34,4 @@ await viewer.setZoom(125)
|
|
|
31
34
|
viewer.destroy()
|
|
32
35
|
```
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
Most users should consume PPTX through `@file-viewer/core` or a standard framework package. The core package lazy-loads this renderer only when a presentation is opened.
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @file-viewer/pptx
|
|
2
|
+
|
|
3
|
+
[English](https://github.com/flyfish-dev/file-viewer/blob/main/packages/renderers/pptx/README.md) · [简体中文](https://github.com/flyfish-dev/file-viewer/blob/main/packages/renderers/pptx/README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
Flyfish File Viewer 的原生 PPTX 渲染引擎包,从历史稳定实现中独立拆分,便于持续维护和单独发布。
|
|
6
|
+
|
|
7
|
+
## 特性
|
|
8
|
+
|
|
9
|
+
- 纯 TypeScript/DOM 入口,不依赖 Vue、React 或 iframe。
|
|
10
|
+
- 使用 Web Worker 解析 PPTX,按页渐进输出,避免阻塞主线程。
|
|
11
|
+
- 保留历史原生解析链路中的主题、图片、形状、表格、文本和基础图表处理。
|
|
12
|
+
- 支持 `fitMode`、`zoomPercent`、自定义 Worker URL / Worker factory,方便内网、离线和 CSP 场景。
|
|
13
|
+
- 图表在放映缩放时保留原始尺寸和文字样式,中文标签按独立的逻辑尺寸测量,支持底部、右侧、顶部、右上和左侧图例。字体由宿主安装或提供;缺少字体时使用浏览器回退字体。
|
|
14
|
+
|
|
15
|
+
`resolvePptxPackageWorkerUrl()` 可供集成层识别打包器已输出或包目录直接提供的 Worker URL。未识别时返回 `undefined`,不代表文件不存在;集成层仍可使用自己的资产复制策略或显式 Worker 配置。
|
|
16
|
+
|
|
17
|
+
直接使用 `PptxViewer` 时,Vite 或 Webpack 可直接提供已识别的包内 Worker;无法识别的构建不会猜测应用相对路径。Angular、IIFE、离线静态目录或严格 CSP 部署应使用 `file-viewer-copy-assets` 或显式 `workerUrl`,让 Worker 由最终静态域名稳定提供。
|
|
18
|
+
|
|
19
|
+
## 使用
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { PptxViewer } from '@file-viewer/pptx'
|
|
23
|
+
|
|
24
|
+
const buffer = await file.arrayBuffer()
|
|
25
|
+
const viewer = await PptxViewer.open(buffer, document.querySelector('#app')!, {
|
|
26
|
+
fitMode: 'contain',
|
|
27
|
+
zoomPercent: 100,
|
|
28
|
+
onSlideRendered(index) {
|
|
29
|
+
console.log('slide rendered', index)
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
await viewer.setZoom(125)
|
|
34
|
+
viewer.destroy()
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
在 Flyfish File Viewer 中通常无需直接使用本包,`@file-viewer/core` 会按 PPTX 类型自动按需加载。
|
package/dist/chart-config.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import type { PptxChartModel } from
|
|
1
|
+
import type { PptxChartAxis, PptxChartModel } from './chart-types.js';
|
|
2
|
+
/** Common authored numeric formats; unsupported Excel formats remain in the model. */
|
|
2
3
|
export declare const formatPptxChartNumber: (value: number, format?: string) => string;
|
|
4
|
+
export declare const resolvePptxChartAxes: (model: PptxChartModel) => {
|
|
5
|
+
category: PptxChartAxis | undefined;
|
|
6
|
+
primary: PptxChartAxis | undefined;
|
|
7
|
+
secondary: PptxChartAxis | undefined;
|
|
8
|
+
};
|
|
9
|
+
/** One category coordinate system, with every authored bar/line/area group. */
|
|
3
10
|
export declare const createPptxCartesianChart: (model: PptxChartModel, library: Record<string, any>) => {
|
|
4
11
|
interaction: {
|
|
5
12
|
enabled: boolean;
|
|
@@ -44,6 +51,14 @@ export declare const createPptxCartesianChart: (model: PptxChartModel, library:
|
|
|
44
51
|
categories: string[];
|
|
45
52
|
tick: {
|
|
46
53
|
multiline: boolean;
|
|
54
|
+
culling?: undefined;
|
|
55
|
+
rotate?: undefined;
|
|
56
|
+
autorotate?: undefined;
|
|
57
|
+
} | {
|
|
58
|
+
multiline: boolean;
|
|
59
|
+
culling: boolean;
|
|
60
|
+
rotate: number;
|
|
61
|
+
autorotate: boolean;
|
|
47
62
|
};
|
|
48
63
|
};
|
|
49
64
|
y: {};
|
package/dist/chart-config.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** Common authored numeric formats; unsupported Excel formats remain in the model. */
|
|
1
2
|
export const formatPptxChartNumber = (value, format = 'General') => {
|
|
2
3
|
if (!Number.isFinite(value))
|
|
3
4
|
return '';
|
|
@@ -28,8 +29,17 @@ const axisOptions = (axis) => {
|
|
|
28
29
|
};
|
|
29
30
|
};
|
|
30
31
|
const defined = (labels) => Object.fromEntries(Object.entries(labels || {}).filter(([, value]) => value !== undefined));
|
|
32
|
+
export const resolvePptxChartAxes = (model) => {
|
|
33
|
+
var _a, _b;
|
|
34
|
+
const axesFor = (group) => model.axes.filter(axis => group === null || group === void 0 ? void 0 : group.axisIds.includes(axis.id));
|
|
35
|
+
const primary = (_a = axesFor(model.groups[0]).find(axis => axis.kind === 'value')) !== null && _a !== void 0 ? _a : model.axes.find(axis => axis.kind === 'value');
|
|
36
|
+
const secondary = model.groups.filter(group => group.series.length > 0).flatMap(axesFor).find(axis => axis.kind === 'value' && axis.id !== (primary === null || primary === void 0 ? void 0 : primary.id));
|
|
37
|
+
const category = (_b = axesFor(model.groups[0]).find(axis => axis.kind === 'category' || axis.kind === 'date')) !== null && _b !== void 0 ? _b : model.axes.find(axis => axis.kind === 'category' || axis.kind === 'date');
|
|
38
|
+
return { category, primary, secondary };
|
|
39
|
+
};
|
|
40
|
+
/** One category coordinate system, with every authored bar/line/area group. */
|
|
31
41
|
export const createPptxCartesianChart = (model, library) => {
|
|
32
|
-
var _a, _b, _c, _d
|
|
42
|
+
var _a, _b, _c, _d;
|
|
33
43
|
if (!model.groups.length || model.groups.some(group => !['lineChart', 'barChart', 'areaChart'].includes(group.chartType)))
|
|
34
44
|
return null;
|
|
35
45
|
const rows = model.groups.flatMap((group, groupIndex) => group.series.map((series, index) => ({
|
|
@@ -50,10 +60,7 @@ export const createPptxCartesianChart = (model, library) => {
|
|
|
50
60
|
const points = new Map(series.values.map(point => [String(point.x), point.y]));
|
|
51
61
|
return indices.map(index => { var _a; return (_a = points.get(index)) !== null && _a !== void 0 ? _a : null; });
|
|
52
62
|
};
|
|
53
|
-
const
|
|
54
|
-
const primary = (_b = axesFor(model.groups[0]).find(axis => axis.kind === 'value')) !== null && _b !== void 0 ? _b : model.axes.find(axis => axis.kind === 'value');
|
|
55
|
-
const secondary = rows.flatMap(row => axesFor(row.group)).find(axis => axis.kind === 'value' && axis.id !== (primary === null || primary === void 0 ? void 0 : primary.id));
|
|
56
|
-
const category = (_c = axesFor(model.groups[0]).find(axis => axis.kind === 'category' || axis.kind === 'date')) !== null && _c !== void 0 ? _c : model.axes.find(axis => axis.kind === 'category' || axis.kind === 'date');
|
|
63
|
+
const { primary, secondary, category } = resolvePptxChartAxes(model);
|
|
57
64
|
const factory = { barChart: library.bar, lineChart: library.line, areaChart: library.area };
|
|
58
65
|
if (rows.some(row => typeof factory[row.group.chartType] !== 'function'))
|
|
59
66
|
return null;
|
|
@@ -66,6 +73,7 @@ export const createPptxCartesianChart = (model, library) => {
|
|
|
66
73
|
const groups = model.groups.flatMap((group, groupIndex) => group.options.grouping === 'stacked' || group.options.grouping === 'percentStacked'
|
|
67
74
|
? [rows.filter(row => row.groupIndex === groupIndex).map(row => row.id)] : []);
|
|
68
75
|
const normalizeAll = model.groups.length === 1 && model.groups[0].options.grouping === 'percentStacked';
|
|
76
|
+
// Normalize only this stacked group, preserving an unrelated line/secondary axis.
|
|
69
77
|
const totals = new Map();
|
|
70
78
|
rows.forEach((row, rowIndex) => {
|
|
71
79
|
if (normalizeAll || row.group.options.grouping !== 'percentStacked')
|
|
@@ -91,7 +99,7 @@ export const createPptxCartesianChart = (model, library) => {
|
|
|
91
99
|
const color = (_b = (_a = row.group.options.series) === null || _a === void 0 ? void 0 : _a[row.index]) === null || _b === void 0 ? void 0 : _b.color;
|
|
92
100
|
return color && /^(?:#[\da-f]{6}(?:[\da-f]{2})?|transparent)$/i.test(color) ? [[row.id, color]] : [];
|
|
93
101
|
}));
|
|
94
|
-
const gap = (
|
|
102
|
+
const gap = (_b = model.groups.find(group => group.chartType === 'barChart')) === null || _b === void 0 ? void 0 : _b.options.gapWidth;
|
|
95
103
|
const hasLabels = rows.some(row => { var _a, _b; return row.group.options.labels || ((_b = (_a = row.group.options.series) === null || _a === void 0 ? void 0 : _a[row.index]) === null || _b === void 0 ? void 0 : _b.labels); });
|
|
96
104
|
const byId = new Map(rows.map(row => [row.id, row]));
|
|
97
105
|
const labelFormat = (value, id, position) => {
|
|
@@ -116,7 +124,7 @@ export const createPptxCartesianChart = (model, library) => {
|
|
|
116
124
|
};
|
|
117
125
|
return {
|
|
118
126
|
data: {
|
|
119
|
-
columns, types, type: types[(
|
|
127
|
+
columns, types, type: types[(_c = rows[0]) === null || _c === void 0 ? void 0 : _c.id], axes: dataAxes,
|
|
120
128
|
names: Object.fromEntries(rows.map(row => [row.id, String(row.series.key)])),
|
|
121
129
|
...(Object.keys(colors).length ? { colors } : {}),
|
|
122
130
|
...(groups.length ? { groups, order: null } : {}),
|
|
@@ -125,7 +133,9 @@ export const createPptxCartesianChart = (model, library) => {
|
|
|
125
133
|
},
|
|
126
134
|
axis: {
|
|
127
135
|
rotated: model.groups.some(group => group.barDirection === 'bar'),
|
|
128
|
-
x: { ...axisOptions(category), type: 'category', categories, tick:
|
|
136
|
+
x: { ...axisOptions(category), type: 'category', categories, tick: model.groups.some(group => group.barDirection === 'bar')
|
|
137
|
+
? { multiline: true }
|
|
138
|
+
: { multiline: false, culling: false, rotate: (_d = category === null || category === void 0 ? void 0 : category.labelRotation) !== null && _d !== void 0 ? _d : 60, autorotate: (category === null || category === void 0 ? void 0 : category.labelRotation) === undefined } },
|
|
129
139
|
y: axisOptions(primary),
|
|
130
140
|
...(secondary ? { y2: axisOptions(secondary) } : {}),
|
|
131
141
|
},
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PptxChartFont, PptxChartOptions } from './chart-types.js';
|
|
2
|
+
/** Billboard only provides outside bottom/right legends. Preserve the other
|
|
3
|
+
* DrawingML placements without changing the authored outer chart frame. */
|
|
4
|
+
export declare const createPptxChartLegend: (surface: HTMLElement, chart: any, legend?: PptxChartOptions["legend"], font?: PptxChartFont) => {
|
|
5
|
+
size(): {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
};
|
|
9
|
+
bind(api: any): void;
|
|
10
|
+
destroy(): void;
|
|
11
|
+
} | undefined;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { applyPptxChartFont } from './chart-text.js';
|
|
2
|
+
/** Billboard only provides outside bottom/right legends. Preserve the other
|
|
3
|
+
* DrawingML placements without changing the authored outer chart frame. */
|
|
4
|
+
export const createPptxChartLegend = (surface, chart, legend, font) => {
|
|
5
|
+
var _a, _b;
|
|
6
|
+
const document = surface.ownerDocument;
|
|
7
|
+
const view = document === null || document === void 0 ? void 0 : document.defaultView;
|
|
8
|
+
if (!view || (legend === null || legend === void 0 ? void 0 : legend.show) === false || !((legend === null || legend === void 0 ? void 0 : legend.overlay) || ['t', 'tr', 'l'].includes((legend === null || legend === void 0 ? void 0 : legend.position) || '')))
|
|
9
|
+
return;
|
|
10
|
+
const left = (legend === null || legend === void 0 ? void 0 : legend.position) === 'l' || (legend === null || legend === void 0 ? void 0 : legend.position) === 'r';
|
|
11
|
+
const layout = document.createElement('div');
|
|
12
|
+
layout.className = 'pptx-chart-layout';
|
|
13
|
+
layout.style.cssText = `display:flex;flex-direction:${left ? 'row' : 'column'};width:100%;height:100%;min-width:0;min-height:0;position:relative`;
|
|
14
|
+
surface.replaceWith(layout);
|
|
15
|
+
const list = document.createElement('div');
|
|
16
|
+
list.className = 'pptx-chart-legend';
|
|
17
|
+
list.setAttribute('role', 'group');
|
|
18
|
+
list.setAttribute('aria-label', 'Chart legend');
|
|
19
|
+
list.style.cssText = `display:flex;flex:${left ? '0 1 auto' : '0 0 auto'};flex-direction:${left ? 'column' : 'row'};flex-wrap:${left ? 'nowrap' : 'wrap'};gap:4px 12px;align-items:${left ? 'stretch' : 'center'};justify-content:${(legend === null || legend === void 0 ? void 0 : legend.position) === 'tr' ? 'flex-end' : 'center'};box-sizing:border-box;padding:4px;overflow:auto;${left ? 'max-width:50%' : 'max-height:50%'};font:12px sans-serif`;
|
|
20
|
+
applyPptxChartFont(list.style, font);
|
|
21
|
+
if (legend === null || legend === void 0 ? void 0 : legend.overlay) {
|
|
22
|
+
list.style.position = 'absolute';
|
|
23
|
+
list.style.zIndex = '1';
|
|
24
|
+
list.style[legend.position === 'b' ? 'bottom' : 'top'] = '0';
|
|
25
|
+
list.style[legend.position === 'tr' || legend.position === 'r' ? 'right' : 'left'] = '0';
|
|
26
|
+
if (left)
|
|
27
|
+
list.style.height = '100%';
|
|
28
|
+
else
|
|
29
|
+
list.style.width = '100%';
|
|
30
|
+
}
|
|
31
|
+
// Prevent the surface's 100% basis from pushing a non-overlay legend outside
|
|
32
|
+
// the frame. Flex assigns only the remaining logical area to the plot.
|
|
33
|
+
surface.style.width = left ? '0' : '100%';
|
|
34
|
+
surface.style.height = left ? '100%' : '0';
|
|
35
|
+
surface.style.alignSelf = 'stretch';
|
|
36
|
+
if (legend === null || legend === void 0 ? void 0 : legend.overlay)
|
|
37
|
+
surface.style.width = surface.style.height = '100%';
|
|
38
|
+
layout.append(list, surface);
|
|
39
|
+
const xs = new Set(Object.values(((_a = chart.data) === null || _a === void 0 ? void 0 : _a.xs) || {}).map(String));
|
|
40
|
+
const entries = (((_b = chart.data) === null || _b === void 0 ? void 0 : _b.columns) || []).filter((column) => !xs.has(String(column[0])))
|
|
41
|
+
.map((column) => {
|
|
42
|
+
var _a, _b, _c;
|
|
43
|
+
const id = String(column[0]);
|
|
44
|
+
const button = document.createElement('button');
|
|
45
|
+
button.type = 'button';
|
|
46
|
+
button.setAttribute('aria-pressed', 'true');
|
|
47
|
+
button.style.cssText = 'display:inline-flex;flex:0 0 auto;align-items:center;gap:4px;min-width:0;max-width:100%;border:0;padding:2px;background:none;color:inherit;font:inherit;text-align:start;cursor:pointer';
|
|
48
|
+
const swatch = document.createElement('span');
|
|
49
|
+
swatch.setAttribute('aria-hidden', 'true');
|
|
50
|
+
swatch.style.cssText = 'display:inline-block;flex:0 0 10px;width:10px;height:10px';
|
|
51
|
+
const text = document.createElement('span');
|
|
52
|
+
text.className = 'pptx-chart-legend-label';
|
|
53
|
+
text.style.cssText = 'min-width:0;overflow-wrap:anywhere';
|
|
54
|
+
text.textContent = String((_c = (_b = (_a = chart.data) === null || _a === void 0 ? void 0 : _a.names) === null || _b === void 0 ? void 0 : _b[id]) !== null && _c !== void 0 ? _c : id) || '\u200b';
|
|
55
|
+
button.append(swatch, text);
|
|
56
|
+
list.append(button);
|
|
57
|
+
return { id, button, swatch };
|
|
58
|
+
});
|
|
59
|
+
chart.legend = { ...chart.legend, show: false };
|
|
60
|
+
let observer;
|
|
61
|
+
let instance;
|
|
62
|
+
let width = 0, height = 0;
|
|
63
|
+
return {
|
|
64
|
+
size() {
|
|
65
|
+
return { width: surface.clientWidth, height: surface.clientHeight };
|
|
66
|
+
},
|
|
67
|
+
bind(api) {
|
|
68
|
+
var _a;
|
|
69
|
+
instance = api;
|
|
70
|
+
for (const { id, button, swatch } of entries) {
|
|
71
|
+
swatch.style.backgroundColor = ((_a = api.color) === null || _a === void 0 ? void 0 : _a.call(api, id)) || 'currentColor';
|
|
72
|
+
button.onclick = () => {
|
|
73
|
+
var _a;
|
|
74
|
+
(_a = api.toggle) === null || _a === void 0 ? void 0 : _a.call(api, id);
|
|
75
|
+
const visible = button.getAttribute('aria-pressed') !== 'true';
|
|
76
|
+
button.setAttribute('aria-pressed', String(visible));
|
|
77
|
+
button.style.opacity = visible ? '1' : '.45';
|
|
78
|
+
};
|
|
79
|
+
button.onmouseenter = () => { var _a; return (_a = api.focus) === null || _a === void 0 ? void 0 : _a.call(api, id); };
|
|
80
|
+
button.onmouseleave = () => { var _a; return (_a = api.revert) === null || _a === void 0 ? void 0 : _a.call(api); };
|
|
81
|
+
}
|
|
82
|
+
if (view.ResizeObserver) {
|
|
83
|
+
observer = new view.ResizeObserver(([entry]) => {
|
|
84
|
+
var _a;
|
|
85
|
+
const { width: nextWidth, height: nextHeight } = entry.contentRect;
|
|
86
|
+
if (nextWidth > 0 && nextHeight > 0 && (Math.abs(nextWidth - width) > .5 || Math.abs(nextHeight - height) > .5)) {
|
|
87
|
+
width = nextWidth;
|
|
88
|
+
height = nextHeight;
|
|
89
|
+
(_a = instance === null || instance === void 0 ? void 0 : instance.resize) === null || _a === void 0 ? void 0 : _a.call(instance, { width, height });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
observer.observe(surface);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
destroy() {
|
|
96
|
+
observer === null || observer === void 0 ? void 0 : observer.disconnect();
|
|
97
|
+
instance = undefined;
|
|
98
|
+
for (const { button } of entries)
|
|
99
|
+
button.onclick = button.onmouseenter = button.onmouseleave = null;
|
|
100
|
+
layout.remove();
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PptxChartFont, PptxChartModel } from './chart-types.js';
|
|
2
|
+
export declare const mergePptxChartFonts: (...fonts: Array<PptxChartFont | undefined>) => PptxChartFont;
|
|
3
|
+
/** CSSOM property assignment keeps document font names out of stylesheet source. */
|
|
4
|
+
export declare const applyPptxChartFont: (style: CSSStyleDeclaration, font?: PptxChartFont) => void;
|
|
5
|
+
/** Apply authored fonts before Billboard measures labels, including Shadow DOM. */
|
|
6
|
+
export declare const stylePptxChartText: (surface: HTMLElement, model?: PptxChartModel, chart?: any, legendFont?: PptxChartFont) => HTMLStyleElement | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Billboard's plugin hooks are per-chart. Its screen-space text rectangles and
|
|
9
|
+
* punctuation-normalized cache keys cannot represent DrawingML coordinates or
|
|
10
|
+
* distinct CJK labels. Measure fresh SVG glyph geometry in local coordinates.
|
|
11
|
+
*/
|
|
12
|
+
export declare const createPptxChartTextPlugin: (surface: HTMLElement) => {
|
|
13
|
+
$$: any;
|
|
14
|
+
$beforeInit(): void;
|
|
15
|
+
$init(): void;
|
|
16
|
+
$afterInit(): void;
|
|
17
|
+
$redraw(): void;
|
|
18
|
+
$willDestroy(): void;
|
|
19
|
+
} | undefined;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { resolvePptxChartAxes } from './chart-config.js';
|
|
2
|
+
export const mergePptxChartFonts = (...fonts) => Object.assign({}, ...fonts.map(font => Object.fromEntries(Object.entries(font || {}).filter(([, value]) => value !== undefined))));
|
|
3
|
+
/** CSSOM property assignment keeps document font names out of stylesheet source. */
|
|
4
|
+
export const applyPptxChartFont = (style, font) => {
|
|
5
|
+
if (!font)
|
|
6
|
+
return;
|
|
7
|
+
const families = [...new Set([font.family, font.eastAsianFamily, font.complexScriptFamily]
|
|
8
|
+
.filter((value) => typeof value === 'string' && !!value.trim())
|
|
9
|
+
.filter(value => !/^\+(?:mj|mn)-(?:lt|ea|cs)$/.test(value))
|
|
10
|
+
.map(value => /^(?:serif|sans-serif|monospace|cursive|fantasy|system-ui|ui-serif|ui-sans-serif|ui-monospace|ui-rounded|emoji|math|fangsong)$/i.test(value.trim())
|
|
11
|
+
? value.trim().toLowerCase() : JSON.stringify(value.slice(0, 512))))];
|
|
12
|
+
if (families.length)
|
|
13
|
+
style.fontFamily = [...families, 'sans-serif'].join(',');
|
|
14
|
+
if (typeof font.sizePt === 'number' && Number.isFinite(font.sizePt) && font.sizePt > 0 && font.sizePt <= 4000) {
|
|
15
|
+
style.fontSize = `${font.sizePt * 96 / 72}px`;
|
|
16
|
+
}
|
|
17
|
+
if (font.bold !== undefined)
|
|
18
|
+
style.fontWeight = font.bold ? 'bold' : 'normal';
|
|
19
|
+
if (font.italic !== undefined)
|
|
20
|
+
style.fontStyle = font.italic ? 'italic' : 'normal';
|
|
21
|
+
if (typeof font.color === 'string' && /^(?:#[\da-f]{6}(?:[\da-f]{2})?|transparent)$/i.test(font.color)) {
|
|
22
|
+
style.color = font.color;
|
|
23
|
+
style.fill = font.color;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
/** Apply authored fonts before Billboard measures labels, including Shadow DOM. */
|
|
27
|
+
export const stylePptxChartText = (surface, model, chart, legendFont) => {
|
|
28
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
29
|
+
const view = (_a = surface.ownerDocument) === null || _a === void 0 ? void 0 : _a.defaultView;
|
|
30
|
+
if (!view || typeof surface.appendChild !== 'function')
|
|
31
|
+
return;
|
|
32
|
+
const scope = `chart-${Math.random().toString(36).slice(2)}`;
|
|
33
|
+
surface.dataset.pptxChartText = scope;
|
|
34
|
+
surface.classList.add('pptx-chart-typed');
|
|
35
|
+
const selector = `.pptx-chart-surface.pptx-chart-typed[data-pptx-chart-text="${scope}"]`;
|
|
36
|
+
const element = surface.ownerDocument.createElement('style');
|
|
37
|
+
element.dataset.pptxChartFonts = '';
|
|
38
|
+
// Billboard replaces the bind target's children during initialization.
|
|
39
|
+
// Keep scoped rules beside that target so they also apply during measurement.
|
|
40
|
+
(_b = surface.parentNode) === null || _b === void 0 ? void 0 : _b.appendChild(element);
|
|
41
|
+
const sheet = element.sheet;
|
|
42
|
+
if (!sheet)
|
|
43
|
+
return element;
|
|
44
|
+
const rule = (suffix, font) => {
|
|
45
|
+
if (!font || !Object.values(font).some(value => value !== undefined))
|
|
46
|
+
return;
|
|
47
|
+
const index = sheet.insertRule(suffix.split(',').map(value => `${selector} ${value.trim()}`).join(',') + '{}', sheet.cssRules.length);
|
|
48
|
+
applyPptxChartFont(sheet.cssRules[index].style, font);
|
|
49
|
+
};
|
|
50
|
+
rule('svg', model === null || model === void 0 ? void 0 : model.font);
|
|
51
|
+
rule('.bb-legend-item,.bb-legend-item text', mergePptxChartFonts(model === null || model === void 0 ? void 0 : model.font, legendFont));
|
|
52
|
+
if (!model)
|
|
53
|
+
return element;
|
|
54
|
+
const { category, primary, secondary } = resolvePptxChartAxes(model);
|
|
55
|
+
for (const [axis, font] of [['x', category === null || category === void 0 ? void 0 : category.font], ['y', primary === null || primary === void 0 ? void 0 : primary.font], ['y2', secondary === null || secondary === void 0 ? void 0 : secondary.font]]) {
|
|
56
|
+
rule(`.bb-axis-${axis},.bb-axis-${axis} text`, mergePptxChartFonts(model.font, font));
|
|
57
|
+
}
|
|
58
|
+
let column = 0;
|
|
59
|
+
const escape = (_c = view.CSS) === null || _c === void 0 ? void 0 : _c.escape;
|
|
60
|
+
if (!escape)
|
|
61
|
+
return element;
|
|
62
|
+
for (const group of model.groups)
|
|
63
|
+
for (let index = 0; index < group.series.length; index++, column++) {
|
|
64
|
+
const id = String((_g = (_f = (_e = (_d = chart === null || chart === void 0 ? void 0 : chart.data) === null || _d === void 0 ? void 0 : _d.columns) === null || _e === void 0 ? void 0 : _e[column]) === null || _f === void 0 ? void 0 : _f[0]) !== null && _g !== void 0 ? _g : '');
|
|
65
|
+
if (!id)
|
|
66
|
+
continue;
|
|
67
|
+
const labels = (_j = (_h = group.options.series) === null || _h === void 0 ? void 0 : _h[index]) === null || _j === void 0 ? void 0 : _j.labels;
|
|
68
|
+
const font = mergePptxChartFonts(model.font, (_k = group.options.labels) === null || _k === void 0 ? void 0 : _k.font, labels === null || labels === void 0 ? void 0 : labels.font);
|
|
69
|
+
const texts = `.bb-texts-${escape(id)}`;
|
|
70
|
+
rule(`${texts},${texts} text`, font);
|
|
71
|
+
const points = { ...(_l = group.options.labels) === null || _l === void 0 ? void 0 : _l.points, ...labels === null || labels === void 0 ? void 0 : labels.points };
|
|
72
|
+
for (const [position, point] of Object.entries(points)) {
|
|
73
|
+
if (!/^\d+$/.test(position) || !point.font)
|
|
74
|
+
continue;
|
|
75
|
+
rule(`${texts} .bb-text-${escape(position)}`, mergePptxChartFonts(font, point.font));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return element;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Billboard's plugin hooks are per-chart. Its screen-space text rectangles and
|
|
82
|
+
* punctuation-normalized cache keys cannot represent DrawingML coordinates or
|
|
83
|
+
* distinct CJK labels. Measure fresh SVG glyph geometry in local coordinates.
|
|
84
|
+
*/
|
|
85
|
+
export const createPptxChartTextPlugin = (surface) => {
|
|
86
|
+
const document = surface.ownerDocument;
|
|
87
|
+
const view = document === null || document === void 0 ? void 0 : document.defaultView;
|
|
88
|
+
if (!view || typeof surface.appendChild !== 'function')
|
|
89
|
+
return undefined;
|
|
90
|
+
let original;
|
|
91
|
+
let wrapped;
|
|
92
|
+
let stopped = false;
|
|
93
|
+
let context;
|
|
94
|
+
const plugin = {
|
|
95
|
+
$$: undefined,
|
|
96
|
+
$beforeInit() {
|
|
97
|
+
const internal = this.$$;
|
|
98
|
+
if (typeof (internal === null || internal === void 0 ? void 0 : internal.getTextRect) !== 'function')
|
|
99
|
+
return;
|
|
100
|
+
original = internal.getTextRect;
|
|
101
|
+
wrapped = (source, className) => {
|
|
102
|
+
var _a, _b, _c, _d, _e;
|
|
103
|
+
const svg = ((_c = (_b = (_a = internal.$el) === null || _a === void 0 ? void 0 : _a.svg) === null || _b === void 0 ? void 0 : _b.node) === null || _c === void 0 ? void 0 : _c.call(_b)) || surface.querySelector('svg');
|
|
104
|
+
if (!svg)
|
|
105
|
+
return original.call(internal, source, className);
|
|
106
|
+
let base = ((_d = source === null || source === void 0 ? void 0 : source.node) === null || _d === void 0 ? void 0 : _d.call(source)) || source;
|
|
107
|
+
if (!Array.isArray(source) && !/text/i.test((base === null || base === void 0 ? void 0 : base.tagName) || ''))
|
|
108
|
+
base = (_e = base === null || base === void 0 ? void 0 : base.querySelector) === null || _e === void 0 ? void 0 : _e.call(base, 'text');
|
|
109
|
+
if (!Array.isArray(source) && !base)
|
|
110
|
+
return original.call(internal, source, className);
|
|
111
|
+
const values = Array.isArray(source) ? source : [base.textContent || ''];
|
|
112
|
+
const rectangles = values.map((value) => {
|
|
113
|
+
var _a;
|
|
114
|
+
const text = String(value !== null && value !== void 0 ? value : '');
|
|
115
|
+
const node = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
116
|
+
if (className)
|
|
117
|
+
node.setAttribute('class', className);
|
|
118
|
+
node.style.visibility = 'hidden';
|
|
119
|
+
if (base && !Array.isArray(source))
|
|
120
|
+
node.style.font = view.getComputedStyle(base).font;
|
|
121
|
+
node.textContent = text;
|
|
122
|
+
svg.appendChild(node);
|
|
123
|
+
try {
|
|
124
|
+
const box = node.getBBox();
|
|
125
|
+
if (box.height > 0 && (box.width > 0 || !text)) {
|
|
126
|
+
return { x: box.x, y: box.y, left: box.x, top: box.y, right: box.x + box.width,
|
|
127
|
+
bottom: box.y + box.height, width: box.width, height: box.height };
|
|
128
|
+
}
|
|
129
|
+
// Hidden slides still need logical font metrics before they become visible.
|
|
130
|
+
const style = view.getComputedStyle(node);
|
|
131
|
+
context !== null && context !== void 0 ? context : (context = document.createElement('canvas').getContext('2d'));
|
|
132
|
+
const size = Number.parseFloat(style.fontSize) || 10;
|
|
133
|
+
if (context)
|
|
134
|
+
context.font = `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
|
|
135
|
+
const metrics = context === null || context === void 0 ? void 0 : context.measureText(text);
|
|
136
|
+
const width = (_a = metrics === null || metrics === void 0 ? void 0 : metrics.width) !== null && _a !== void 0 ? _a : 0;
|
|
137
|
+
const height = ((metrics === null || metrics === void 0 ? void 0 : metrics.fontBoundingBoxAscent) || 0) + ((metrics === null || metrics === void 0 ? void 0 : metrics.fontBoundingBoxDescent) || 0) || size * 1.2;
|
|
138
|
+
return { x: 0, y: 0, left: 0, top: 0, right: width, bottom: height, width, height };
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
node.remove();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
return rectangles.length > 1 ? rectangles : rectangles[0];
|
|
145
|
+
};
|
|
146
|
+
internal.getTextRect = wrapped;
|
|
147
|
+
},
|
|
148
|
+
$init() { },
|
|
149
|
+
$afterInit() { var _a; (_a = document.fonts) === null || _a === void 0 ? void 0 : _a.addEventListener('loadingdone', refresh); },
|
|
150
|
+
$redraw() { },
|
|
151
|
+
$willDestroy() {
|
|
152
|
+
var _a, _b;
|
|
153
|
+
stopped = true;
|
|
154
|
+
(_a = document.fonts) === null || _a === void 0 ? void 0 : _a.removeEventListener('loadingdone', refresh);
|
|
155
|
+
if (((_b = this.$$) === null || _b === void 0 ? void 0 : _b.getTextRect) === wrapped)
|
|
156
|
+
this.$$.getTextRect = original;
|
|
157
|
+
original = wrapped = context = undefined;
|
|
158
|
+
this.$$ = undefined;
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
const refresh = () => { var _a, _b, _c; if (!stopped)
|
|
162
|
+
(_c = (_b = (_a = plugin.$$) === null || _a === void 0 ? void 0 : _a.api) === null || _b === void 0 ? void 0 : _b.flush) === null || _c === void 0 ? void 0 : _c.call(_b); };
|
|
163
|
+
return plugin;
|
|
164
|
+
};
|
package/dist/chart-types.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
+
/** Serializable DrawingML metadata carried by the actual PPTX Worker. */
|
|
1
2
|
export type PptxChartType = 'lineChart' | 'barChart' | 'areaChart' | 'scatterChart' | 'pieChart' | 'pie3DChart' | 'doughnutChart';
|
|
3
|
+
export interface PptxChartFont {
|
|
4
|
+
family?: string;
|
|
5
|
+
eastAsianFamily?: string;
|
|
6
|
+
complexScriptFamily?: string;
|
|
7
|
+
sizePt?: number;
|
|
8
|
+
bold?: boolean;
|
|
9
|
+
italic?: boolean;
|
|
10
|
+
color?: string;
|
|
11
|
+
}
|
|
2
12
|
export interface PptxChartLabels {
|
|
13
|
+
font?: PptxChartFont;
|
|
3
14
|
showValue?: boolean;
|
|
4
15
|
showCategory?: boolean;
|
|
5
16
|
showSeries?: boolean;
|
|
@@ -19,6 +30,8 @@ export interface PptxChartOptions {
|
|
|
19
30
|
legend?: {
|
|
20
31
|
show?: boolean;
|
|
21
32
|
position?: string;
|
|
33
|
+
overlay?: boolean;
|
|
34
|
+
font?: PptxChartFont;
|
|
22
35
|
};
|
|
23
36
|
labels?: PptxChartLabels;
|
|
24
37
|
series?: Array<{
|
|
@@ -34,6 +47,7 @@ export interface PptxChartSeries {
|
|
|
34
47
|
y: number | null;
|
|
35
48
|
}>;
|
|
36
49
|
xlabels: Record<string, string | number>;
|
|
50
|
+
/** Original indexed hierarchy, from innermost to outermost category level. */
|
|
37
51
|
categoryLevels?: Array<Record<string, string>>;
|
|
38
52
|
}
|
|
39
53
|
export interface PptxChartAxis {
|
|
@@ -54,10 +68,8 @@ export interface PptxChartAxis {
|
|
|
54
68
|
};
|
|
55
69
|
title?: string;
|
|
56
70
|
majorGridlines: boolean;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
sizePt?: number;
|
|
60
|
-
};
|
|
71
|
+
labelRotation?: number;
|
|
72
|
+
font?: PptxChartFont;
|
|
61
73
|
}
|
|
62
74
|
export interface PptxChartGroup {
|
|
63
75
|
chartType: PptxChartType;
|
|
@@ -71,10 +83,7 @@ export interface PptxChartModel {
|
|
|
71
83
|
groups: PptxChartGroup[];
|
|
72
84
|
axes: PptxChartAxis[];
|
|
73
85
|
title?: string;
|
|
74
|
-
font?:
|
|
75
|
-
family?: string;
|
|
76
|
-
sizePt?: number;
|
|
77
|
-
};
|
|
86
|
+
font?: PptxChartFont;
|
|
78
87
|
}
|
|
79
88
|
export interface PptxChartMessage {
|
|
80
89
|
type?: string;
|
package/dist/chart.d.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import type { PptxChartRenderer } from
|
|
1
|
+
import type { PptxChartRenderer } from './chart-types.js';
|
|
2
2
|
export type PptxChartLibraries = {
|
|
3
3
|
billboard: any;
|
|
4
4
|
d3Format: typeof import('d3-format');
|
|
5
5
|
};
|
|
6
6
|
export type PptxChartLibraryLoader = () => Promise<PptxChartLibraries>;
|
|
7
|
+
/** Register a renderer receiving the preserved Worker model, without reopening the ZIP. */
|
|
7
8
|
export declare const registerPptxChartRenderer: (renderer: PptxChartRenderer | null) => void;
|
|
8
9
|
export declare const registerPptxChartLibraryLoader: (loader: PptxChartLibraryLoader | null) => void;
|
|
9
10
|
export type PptxPostProcessingHandle = {
|
|
10
11
|
destroy: () => void;
|
|
11
12
|
};
|
|
12
13
|
export declare const findPptxChartTarget: (root: ParentNode, chartID: string) => HTMLElement | null;
|
|
14
|
+
/** DrawingML numbering, including the authored suffix punctuation. */
|
|
13
15
|
export declare const getNumericBulletText: (type: string, index: number) => string;
|
|
14
16
|
export declare const renderPptxPostProcessing: (charts: unknown, root: ParentNode) => Promise<PptxPostProcessingHandle>;
|