@8btc/wujie-ppt 0.0.2 → 0.0.4
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 +13 -5
- package/dist/PPTCanvas.d.ts +9 -14
- package/dist/PPTCanvas.test.d.ts +1 -0
- package/dist/mocks/import.ts +848 -0
- package/dist/mocks/slides2.json +316 -0
- package/dist/utils/import-parser.d.ts +92 -0
- package/dist/wujie-ppt.es.js +10011 -9471
- package/dist/wujie-ppt.umd.js +19 -48
- package/package.json +3 -2
|
@@ -0,0 +1,848 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
import { storeToRefs } from "pinia";
|
|
3
|
+
import {
|
|
4
|
+
parse,
|
|
5
|
+
type Shape,
|
|
6
|
+
type Element,
|
|
7
|
+
type ChartItem,
|
|
8
|
+
type BaseElement,
|
|
9
|
+
} from "pptxtojson";
|
|
10
|
+
import { nanoid } from "nanoid";
|
|
11
|
+
import { useSlidesStore } from "@/store";
|
|
12
|
+
import { decrypt } from "@/utils/crypto";
|
|
13
|
+
import {
|
|
14
|
+
type ShapePoolItem,
|
|
15
|
+
SHAPE_LIST,
|
|
16
|
+
SHAPE_PATH_FORMULAS,
|
|
17
|
+
} from "@/configs/shapes";
|
|
18
|
+
import useAddSlidesOrElements from "@/hooks/useAddSlidesOrElements";
|
|
19
|
+
import useSlideHandler from "@/hooks/useSlideHandler";
|
|
20
|
+
import useHistorySnapshot from "./useHistorySnapshot";
|
|
21
|
+
import message from "@/utils/message";
|
|
22
|
+
import { getSvgPathRange } from "@/utils/svgPathParser";
|
|
23
|
+
import type {
|
|
24
|
+
Slide,
|
|
25
|
+
TableCellStyle,
|
|
26
|
+
TableCell,
|
|
27
|
+
ChartType,
|
|
28
|
+
SlideBackground,
|
|
29
|
+
PPTShapeElement,
|
|
30
|
+
PPTLineElement,
|
|
31
|
+
PPTImageElement,
|
|
32
|
+
ShapeTextAlign,
|
|
33
|
+
PPTTextElement,
|
|
34
|
+
ChartOptions,
|
|
35
|
+
Gradient,
|
|
36
|
+
} from "@/types/slides";
|
|
37
|
+
|
|
38
|
+
const convertFontSizePtToPx = (html: string, ratio: number) => {
|
|
39
|
+
return html.replace(/font-size:\s*([\d.]+)pt/g, (match, p1) => {
|
|
40
|
+
return `font-size: ${(parseFloat(p1) * ratio).toFixed(1)}px`;
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default () => {
|
|
45
|
+
const slidesStore = useSlidesStore();
|
|
46
|
+
const { theme } = storeToRefs(useSlidesStore());
|
|
47
|
+
|
|
48
|
+
const { addHistorySnapshot } = useHistorySnapshot();
|
|
49
|
+
const { addSlidesFromData } = useAddSlidesOrElements();
|
|
50
|
+
const { isEmptySlide } = useSlideHandler();
|
|
51
|
+
|
|
52
|
+
const exporting = ref(false);
|
|
53
|
+
|
|
54
|
+
// 导入JSON文件
|
|
55
|
+
const importJSON = (files: FileList | File[], cover = false) => {
|
|
56
|
+
const file = files[0];
|
|
57
|
+
|
|
58
|
+
const reader = new FileReader();
|
|
59
|
+
reader.addEventListener("load", () => {
|
|
60
|
+
try {
|
|
61
|
+
const { slides } = JSON.parse(reader.result as string);
|
|
62
|
+
if (cover) {
|
|
63
|
+
slidesStore.updateSlideIndex(0);
|
|
64
|
+
slidesStore.setSlides(slides);
|
|
65
|
+
addHistorySnapshot();
|
|
66
|
+
} else if (isEmptySlide.value) {
|
|
67
|
+
slidesStore.setSlides(slides);
|
|
68
|
+
addHistorySnapshot();
|
|
69
|
+
} else addSlidesFromData(slides);
|
|
70
|
+
} catch {
|
|
71
|
+
message.error("无法正确读取 / 解析该文件");
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
reader.readAsText(file);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// 导入pptist文件
|
|
78
|
+
const importSpecificFile = (files: FileList | File[], cover = false) => {
|
|
79
|
+
const file = files[0];
|
|
80
|
+
|
|
81
|
+
const reader = new FileReader();
|
|
82
|
+
reader.addEventListener("load", () => {
|
|
83
|
+
try {
|
|
84
|
+
const { slides } = JSON.parse(decrypt(reader.result as string));
|
|
85
|
+
if (cover) {
|
|
86
|
+
slidesStore.updateSlideIndex(0);
|
|
87
|
+
slidesStore.setSlides(slides);
|
|
88
|
+
addHistorySnapshot();
|
|
89
|
+
} else if (isEmptySlide.value) {
|
|
90
|
+
slidesStore.setSlides(slides);
|
|
91
|
+
addHistorySnapshot();
|
|
92
|
+
} else addSlidesFromData(slides);
|
|
93
|
+
} catch {
|
|
94
|
+
message.error("无法正确读取 / 解析该文件");
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
reader.readAsText(file);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const rotateLine = (line: PPTLineElement, angleDeg: number) => {
|
|
101
|
+
const { start, end } = line;
|
|
102
|
+
|
|
103
|
+
const angleRad = (angleDeg * Math.PI) / 180;
|
|
104
|
+
|
|
105
|
+
const midX = (start[0] + end[0]) / 2;
|
|
106
|
+
const midY = (start[1] + end[1]) / 2;
|
|
107
|
+
|
|
108
|
+
const startTransX = start[0] - midX;
|
|
109
|
+
const startTransY = start[1] - midY;
|
|
110
|
+
const endTransX = end[0] - midX;
|
|
111
|
+
const endTransY = end[1] - midY;
|
|
112
|
+
|
|
113
|
+
const cosA = Math.cos(angleRad);
|
|
114
|
+
const sinA = Math.sin(angleRad);
|
|
115
|
+
|
|
116
|
+
const startRotX = startTransX * cosA - startTransY * sinA;
|
|
117
|
+
const startRotY = startTransX * sinA + startTransY * cosA;
|
|
118
|
+
|
|
119
|
+
const endRotX = endTransX * cosA - endTransY * sinA;
|
|
120
|
+
const endRotY = endTransX * sinA + endTransY * cosA;
|
|
121
|
+
|
|
122
|
+
const startNewX = startRotX + midX;
|
|
123
|
+
const startNewY = startRotY + midY;
|
|
124
|
+
const endNewX = endRotX + midX;
|
|
125
|
+
const endNewY = endRotY + midY;
|
|
126
|
+
|
|
127
|
+
const beforeMinX = Math.min(start[0], end[0]);
|
|
128
|
+
const beforeMinY = Math.min(start[1], end[1]);
|
|
129
|
+
|
|
130
|
+
const afterMinX = Math.min(startNewX, endNewX);
|
|
131
|
+
const afterMinY = Math.min(startNewY, endNewY);
|
|
132
|
+
|
|
133
|
+
const startAdjustedX = startNewX - afterMinX;
|
|
134
|
+
const startAdjustedY = startNewY - afterMinY;
|
|
135
|
+
const endAdjustedX = endNewX - afterMinX;
|
|
136
|
+
const endAdjustedY = endNewY - afterMinY;
|
|
137
|
+
|
|
138
|
+
const startAdjusted: [number, number] = [startAdjustedX, startAdjustedY];
|
|
139
|
+
const endAdjusted: [number, number] = [endAdjustedX, endAdjustedY];
|
|
140
|
+
const offset = [afterMinX - beforeMinX, afterMinY - beforeMinY];
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
start: startAdjusted,
|
|
144
|
+
end: endAdjusted,
|
|
145
|
+
offset,
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const parseLineElement = (el: Shape, ratio: number) => {
|
|
150
|
+
let start: [number, number] = [0, 0];
|
|
151
|
+
let end: [number, number] = [0, 0];
|
|
152
|
+
|
|
153
|
+
if (!el.isFlipV && !el.isFlipH) {
|
|
154
|
+
// 右下
|
|
155
|
+
start = [0, 0];
|
|
156
|
+
end = [el.width, el.height];
|
|
157
|
+
} else if (el.isFlipV && el.isFlipH) {
|
|
158
|
+
// 左上
|
|
159
|
+
start = [el.width, el.height];
|
|
160
|
+
end = [0, 0];
|
|
161
|
+
} else if (el.isFlipV && !el.isFlipH) {
|
|
162
|
+
// 右上
|
|
163
|
+
start = [0, el.height];
|
|
164
|
+
end = [el.width, 0];
|
|
165
|
+
} else {
|
|
166
|
+
// 左下
|
|
167
|
+
start = [el.width, 0];
|
|
168
|
+
end = [0, el.height];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const data: PPTLineElement = {
|
|
172
|
+
type: "line",
|
|
173
|
+
id: nanoid(10),
|
|
174
|
+
width: +((el.borderWidth || 1) * ratio).toFixed(2),
|
|
175
|
+
left: el.left,
|
|
176
|
+
top: el.top,
|
|
177
|
+
start,
|
|
178
|
+
end,
|
|
179
|
+
style: el.borderType,
|
|
180
|
+
color: el.borderColor,
|
|
181
|
+
points: ["", /straightConnector/.test(el.shapType) ? "arrow" : ""],
|
|
182
|
+
};
|
|
183
|
+
if (el.rotate) {
|
|
184
|
+
const { start, end, offset } = rotateLine(data, el.rotate);
|
|
185
|
+
|
|
186
|
+
data.start = start;
|
|
187
|
+
data.end = end;
|
|
188
|
+
data.left = data.left + offset[0];
|
|
189
|
+
data.top = data.top + offset[1];
|
|
190
|
+
}
|
|
191
|
+
if (/bentConnector/.test(el.shapType)) {
|
|
192
|
+
data.broken2 = [
|
|
193
|
+
Math.abs(data.start[0] - data.end[0]) / 2,
|
|
194
|
+
Math.abs(data.start[1] - data.end[1]) / 2,
|
|
195
|
+
];
|
|
196
|
+
}
|
|
197
|
+
if (/curvedConnector/.test(el.shapType)) {
|
|
198
|
+
const cubic: [number, number] = [
|
|
199
|
+
Math.abs(data.start[0] - data.end[0]) / 2,
|
|
200
|
+
Math.abs(data.start[1] - data.end[1]) / 2,
|
|
201
|
+
];
|
|
202
|
+
data.cubic = [cubic, cubic];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return data;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const flipGroupElements = (elements: BaseElement[], axis: "x" | "y") => {
|
|
209
|
+
const minX = Math.min(...elements.map((el) => el.left));
|
|
210
|
+
const maxX = Math.max(...elements.map((el) => el.left + el.width));
|
|
211
|
+
const minY = Math.min(...elements.map((el) => el.top));
|
|
212
|
+
const maxY = Math.max(...elements.map((el) => el.top + el.height));
|
|
213
|
+
|
|
214
|
+
const centerX = (minX + maxX) / 2;
|
|
215
|
+
const centerY = (minY + maxY) / 2;
|
|
216
|
+
|
|
217
|
+
return elements.map((element) => {
|
|
218
|
+
const newElement = { ...element };
|
|
219
|
+
|
|
220
|
+
if (axis === "y")
|
|
221
|
+
newElement.left = 2 * centerX - element.left - element.width;
|
|
222
|
+
if (axis === "x")
|
|
223
|
+
newElement.top = 2 * centerY - element.top - element.height;
|
|
224
|
+
|
|
225
|
+
return newElement;
|
|
226
|
+
});
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const calculateRotatedPosition = (
|
|
230
|
+
x: number,
|
|
231
|
+
y: number,
|
|
232
|
+
w: number,
|
|
233
|
+
h: number,
|
|
234
|
+
ox: number,
|
|
235
|
+
oy: number,
|
|
236
|
+
k: number
|
|
237
|
+
) => {
|
|
238
|
+
const radians = k * (Math.PI / 180);
|
|
239
|
+
|
|
240
|
+
const containerCenterX = x + w / 2;
|
|
241
|
+
const containerCenterY = y + h / 2;
|
|
242
|
+
|
|
243
|
+
const relativeX = ox - w / 2;
|
|
244
|
+
const relativeY = oy - h / 2;
|
|
245
|
+
|
|
246
|
+
const rotatedX =
|
|
247
|
+
relativeX * Math.cos(radians) + relativeY * Math.sin(radians);
|
|
248
|
+
const rotatedY =
|
|
249
|
+
-relativeX * Math.sin(radians) + relativeY * Math.cos(radians);
|
|
250
|
+
|
|
251
|
+
const graphicX = containerCenterX + rotatedX;
|
|
252
|
+
const graphicY = containerCenterY + rotatedY;
|
|
253
|
+
|
|
254
|
+
return { x: graphicX, y: graphicY };
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// 导入PPTX文件
|
|
258
|
+
const importPPTXFile = (
|
|
259
|
+
files: FileList | File[],
|
|
260
|
+
options?: { cover?: boolean; fixedViewport?: boolean }
|
|
261
|
+
) => {
|
|
262
|
+
const defaultOptions = {
|
|
263
|
+
cover: false,
|
|
264
|
+
fixedViewport: false,
|
|
265
|
+
};
|
|
266
|
+
const { cover, fixedViewport } = { ...defaultOptions, ...options };
|
|
267
|
+
|
|
268
|
+
const file = files[0];
|
|
269
|
+
if (!file) return;
|
|
270
|
+
|
|
271
|
+
exporting.value = true;
|
|
272
|
+
|
|
273
|
+
const shapeList: ShapePoolItem[] = [];
|
|
274
|
+
for (const item of SHAPE_LIST) {
|
|
275
|
+
shapeList.push(...item.children);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const reader = new FileReader();
|
|
279
|
+
reader.onload = async (e) => {
|
|
280
|
+
let json = null;
|
|
281
|
+
try {
|
|
282
|
+
json = await parse(e.target!.result as ArrayBuffer);
|
|
283
|
+
} catch {
|
|
284
|
+
exporting.value = false;
|
|
285
|
+
message.error("无法正确读取 / 解析该文件");
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
let ratio = 96 / 72;
|
|
290
|
+
const width = json.size.width;
|
|
291
|
+
|
|
292
|
+
if (fixedViewport) ratio = 1000 / width;
|
|
293
|
+
else slidesStore.setViewportSize(width * ratio);
|
|
294
|
+
|
|
295
|
+
slidesStore.setTheme({ themeColors: json.themeColors });
|
|
296
|
+
|
|
297
|
+
const slides: Slide[] = [];
|
|
298
|
+
for (const item of json.slides) {
|
|
299
|
+
const { type, value } = item.fill;
|
|
300
|
+
let background: SlideBackground;
|
|
301
|
+
if (type === "image") {
|
|
302
|
+
background = {
|
|
303
|
+
type: "image",
|
|
304
|
+
image: {
|
|
305
|
+
src: value.picBase64,
|
|
306
|
+
size: "cover",
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
} else if (type === "gradient") {
|
|
310
|
+
background = {
|
|
311
|
+
type: "gradient",
|
|
312
|
+
gradient: {
|
|
313
|
+
type: value.path === "line" ? "linear" : "radial",
|
|
314
|
+
colors: value.colors.map((item) => ({
|
|
315
|
+
...item,
|
|
316
|
+
pos: parseInt(item.pos),
|
|
317
|
+
})),
|
|
318
|
+
rotate: value.rot + 90,
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
} else if (type === "pattern") {
|
|
322
|
+
background = {
|
|
323
|
+
type: "solid",
|
|
324
|
+
color: "#fff",
|
|
325
|
+
};
|
|
326
|
+
} else {
|
|
327
|
+
background = {
|
|
328
|
+
type: "solid",
|
|
329
|
+
color: value || "#fff",
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const slide: Slide = {
|
|
334
|
+
id: nanoid(10),
|
|
335
|
+
elements: [],
|
|
336
|
+
background,
|
|
337
|
+
remark: item.note || "",
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const parseElements = (elements: Element[]) => {
|
|
341
|
+
const sortedElements = elements.sort((a, b) => a.order - b.order);
|
|
342
|
+
|
|
343
|
+
for (const el of sortedElements) {
|
|
344
|
+
const originWidth = el.width || 1;
|
|
345
|
+
const originHeight = el.height || 1;
|
|
346
|
+
const originLeft = el.left;
|
|
347
|
+
const originTop = el.top;
|
|
348
|
+
|
|
349
|
+
el.width = el.width * ratio;
|
|
350
|
+
el.height = el.height * ratio;
|
|
351
|
+
el.left = el.left * ratio;
|
|
352
|
+
el.top = el.top * ratio;
|
|
353
|
+
|
|
354
|
+
if (el.type === "text") {
|
|
355
|
+
const textEl: PPTTextElement = {
|
|
356
|
+
type: "text",
|
|
357
|
+
id: nanoid(10),
|
|
358
|
+
width: el.width,
|
|
359
|
+
height: el.height,
|
|
360
|
+
left: el.left,
|
|
361
|
+
top: el.top,
|
|
362
|
+
rotate: el.rotate,
|
|
363
|
+
defaultFontName: theme.value.fontName,
|
|
364
|
+
defaultColor: theme.value.fontColor,
|
|
365
|
+
content: convertFontSizePtToPx(el.content, ratio),
|
|
366
|
+
lineHeight: 1,
|
|
367
|
+
outline: {
|
|
368
|
+
color: el.borderColor,
|
|
369
|
+
width: +(el.borderWidth * ratio).toFixed(2),
|
|
370
|
+
style: el.borderType,
|
|
371
|
+
},
|
|
372
|
+
fill: el.fill.type === "color" ? el.fill.value : "",
|
|
373
|
+
vertical: el.isVertical,
|
|
374
|
+
};
|
|
375
|
+
if (el.shadow) {
|
|
376
|
+
textEl.shadow = {
|
|
377
|
+
h: el.shadow.h * ratio,
|
|
378
|
+
v: el.shadow.v * ratio,
|
|
379
|
+
blur: el.shadow.blur * ratio,
|
|
380
|
+
color: el.shadow.color,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
slide.elements.push(textEl);
|
|
384
|
+
} else if (el.type === "image") {
|
|
385
|
+
const element: PPTImageElement = {
|
|
386
|
+
type: "image",
|
|
387
|
+
id: nanoid(10),
|
|
388
|
+
src: el.src,
|
|
389
|
+
width: el.width,
|
|
390
|
+
height: el.height,
|
|
391
|
+
left: el.left,
|
|
392
|
+
top: el.top,
|
|
393
|
+
fixedRatio: true,
|
|
394
|
+
rotate: el.rotate,
|
|
395
|
+
flipH: el.isFlipH,
|
|
396
|
+
flipV: el.isFlipV,
|
|
397
|
+
};
|
|
398
|
+
if (el.borderWidth) {
|
|
399
|
+
element.outline = {
|
|
400
|
+
color: el.borderColor,
|
|
401
|
+
width: +(el.borderWidth * ratio).toFixed(2),
|
|
402
|
+
style: el.borderType,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
const clipShapeTypes = [
|
|
406
|
+
"roundRect",
|
|
407
|
+
"ellipse",
|
|
408
|
+
"triangle",
|
|
409
|
+
"rhombus",
|
|
410
|
+
"pentagon",
|
|
411
|
+
"hexagon",
|
|
412
|
+
"heptagon",
|
|
413
|
+
"octagon",
|
|
414
|
+
"parallelogram",
|
|
415
|
+
"trapezoid",
|
|
416
|
+
];
|
|
417
|
+
if (el.rect) {
|
|
418
|
+
element.clip = {
|
|
419
|
+
shape:
|
|
420
|
+
el.geom && clipShapeTypes.includes(el.geom)
|
|
421
|
+
? el.geom
|
|
422
|
+
: "rect",
|
|
423
|
+
range: [
|
|
424
|
+
[el.rect.l || 0, el.rect.t || 0],
|
|
425
|
+
[100 - (el.rect.r || 0), 100 - (el.rect.b || 0)],
|
|
426
|
+
],
|
|
427
|
+
};
|
|
428
|
+
} else if (el.geom && clipShapeTypes.includes(el.geom)) {
|
|
429
|
+
element.clip = {
|
|
430
|
+
shape: el.geom,
|
|
431
|
+
range: [
|
|
432
|
+
[0, 0],
|
|
433
|
+
[100, 100],
|
|
434
|
+
],
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
slide.elements.push(element);
|
|
438
|
+
} else if (el.type === "math") {
|
|
439
|
+
slide.elements.push({
|
|
440
|
+
type: "image",
|
|
441
|
+
id: nanoid(10),
|
|
442
|
+
src: el.picBase64,
|
|
443
|
+
width: el.width,
|
|
444
|
+
height: el.height,
|
|
445
|
+
left: el.left,
|
|
446
|
+
top: el.top,
|
|
447
|
+
fixedRatio: true,
|
|
448
|
+
rotate: 0,
|
|
449
|
+
});
|
|
450
|
+
} else if (el.type === "audio") {
|
|
451
|
+
slide.elements.push({
|
|
452
|
+
type: "audio",
|
|
453
|
+
id: nanoid(10),
|
|
454
|
+
src: el.blob,
|
|
455
|
+
width: el.width,
|
|
456
|
+
height: el.height,
|
|
457
|
+
left: el.left,
|
|
458
|
+
top: el.top,
|
|
459
|
+
rotate: 0,
|
|
460
|
+
fixedRatio: false,
|
|
461
|
+
color: theme.value.themeColors[0],
|
|
462
|
+
loop: false,
|
|
463
|
+
autoplay: false,
|
|
464
|
+
});
|
|
465
|
+
} else if (el.type === "video") {
|
|
466
|
+
slide.elements.push({
|
|
467
|
+
type: "video",
|
|
468
|
+
id: nanoid(10),
|
|
469
|
+
src: (el.blob || el.src)!,
|
|
470
|
+
width: el.width,
|
|
471
|
+
height: el.height,
|
|
472
|
+
left: el.left,
|
|
473
|
+
top: el.top,
|
|
474
|
+
rotate: 0,
|
|
475
|
+
autoplay: false,
|
|
476
|
+
});
|
|
477
|
+
} else if (el.type === "shape") {
|
|
478
|
+
if (el.shapType === "line" || /Connector/.test(el.shapType)) {
|
|
479
|
+
const lineElement = parseLineElement(el, ratio);
|
|
480
|
+
slide.elements.push(lineElement);
|
|
481
|
+
} else {
|
|
482
|
+
const shape = shapeList.find(
|
|
483
|
+
(item) => item.pptxShapeType === el.shapType
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
const vAlignMap: { [key: string]: ShapeTextAlign } = {
|
|
487
|
+
mid: "middle",
|
|
488
|
+
down: "bottom",
|
|
489
|
+
up: "top",
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const gradient: Gradient | undefined =
|
|
493
|
+
el.fill?.type === "gradient"
|
|
494
|
+
? {
|
|
495
|
+
type:
|
|
496
|
+
el.fill.value.path === "line" ? "linear" : "radial",
|
|
497
|
+
colors: el.fill.value.colors.map((item) => ({
|
|
498
|
+
...item,
|
|
499
|
+
pos: parseInt(item.pos),
|
|
500
|
+
})),
|
|
501
|
+
rotate: el.fill.value.rot,
|
|
502
|
+
}
|
|
503
|
+
: undefined;
|
|
504
|
+
|
|
505
|
+
const pattern: string | undefined =
|
|
506
|
+
el.fill?.type === "image"
|
|
507
|
+
? el.fill.value.picBase64
|
|
508
|
+
: undefined;
|
|
509
|
+
|
|
510
|
+
const fill = el.fill?.type === "color" ? el.fill.value : "";
|
|
511
|
+
|
|
512
|
+
const element: PPTShapeElement = {
|
|
513
|
+
type: "shape",
|
|
514
|
+
id: nanoid(10),
|
|
515
|
+
width: el.width,
|
|
516
|
+
height: el.height,
|
|
517
|
+
left: el.left,
|
|
518
|
+
top: el.top,
|
|
519
|
+
viewBox: [200, 200],
|
|
520
|
+
path: "M 0 0 L 200 0 L 200 200 L 0 200 Z",
|
|
521
|
+
fill,
|
|
522
|
+
gradient,
|
|
523
|
+
pattern,
|
|
524
|
+
fixedRatio: false,
|
|
525
|
+
rotate: el.rotate,
|
|
526
|
+
outline: {
|
|
527
|
+
color: el.borderColor,
|
|
528
|
+
width: +(el.borderWidth * ratio).toFixed(2),
|
|
529
|
+
style: el.borderType,
|
|
530
|
+
},
|
|
531
|
+
text: {
|
|
532
|
+
content: convertFontSizePtToPx(el.content, ratio),
|
|
533
|
+
defaultFontName: theme.value.fontName,
|
|
534
|
+
defaultColor: theme.value.fontColor,
|
|
535
|
+
align: vAlignMap[el.vAlign] || "middle",
|
|
536
|
+
},
|
|
537
|
+
flipH: el.isFlipH,
|
|
538
|
+
flipV: el.isFlipV,
|
|
539
|
+
};
|
|
540
|
+
if (el.shadow) {
|
|
541
|
+
element.shadow = {
|
|
542
|
+
h: el.shadow.h * ratio,
|
|
543
|
+
v: el.shadow.v * ratio,
|
|
544
|
+
blur: el.shadow.blur * ratio,
|
|
545
|
+
color: el.shadow.color,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (shape) {
|
|
550
|
+
element.path = shape.path;
|
|
551
|
+
element.viewBox = shape.viewBox;
|
|
552
|
+
|
|
553
|
+
if (shape.pathFormula) {
|
|
554
|
+
element.pathFormula = shape.pathFormula;
|
|
555
|
+
element.viewBox = [el.width, el.height];
|
|
556
|
+
|
|
557
|
+
const pathFormula = SHAPE_PATH_FORMULAS[shape.pathFormula];
|
|
558
|
+
if ("editable" in pathFormula && pathFormula.editable) {
|
|
559
|
+
element.path = pathFormula.formula(
|
|
560
|
+
el.width,
|
|
561
|
+
el.height,
|
|
562
|
+
pathFormula.defaultValue
|
|
563
|
+
);
|
|
564
|
+
element.keypoints = pathFormula.defaultValue;
|
|
565
|
+
} else
|
|
566
|
+
element.path = pathFormula.formula(el.width, el.height);
|
|
567
|
+
}
|
|
568
|
+
} else if (el.path && el.path.indexOf("NaN") === -1) {
|
|
569
|
+
const { maxX, maxY } = getSvgPathRange(el.path);
|
|
570
|
+
element.path = el.path;
|
|
571
|
+
if (maxX / maxY > originWidth / originHeight) {
|
|
572
|
+
element.viewBox = [
|
|
573
|
+
maxX,
|
|
574
|
+
(maxX * originHeight) / originWidth,
|
|
575
|
+
];
|
|
576
|
+
} else {
|
|
577
|
+
element.viewBox = [
|
|
578
|
+
(maxY * originWidth) / originHeight,
|
|
579
|
+
maxY,
|
|
580
|
+
];
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (el.shapType === "custom") {
|
|
584
|
+
if (el.path!.indexOf("NaN") !== -1) {
|
|
585
|
+
if (element.width === 0) element.width = 0.1;
|
|
586
|
+
if (element.height === 0) element.height = 0.1;
|
|
587
|
+
element.path = el.path!.replace(/NaN/g, "0");
|
|
588
|
+
} else {
|
|
589
|
+
element.special = true;
|
|
590
|
+
element.path = el.path!;
|
|
591
|
+
}
|
|
592
|
+
const { maxX, maxY } = getSvgPathRange(element.path);
|
|
593
|
+
if (maxX / maxY > originWidth / originHeight) {
|
|
594
|
+
element.viewBox = [
|
|
595
|
+
maxX,
|
|
596
|
+
(maxX * originHeight) / originWidth,
|
|
597
|
+
];
|
|
598
|
+
} else {
|
|
599
|
+
element.viewBox = [
|
|
600
|
+
(maxY * originWidth) / originHeight,
|
|
601
|
+
maxY,
|
|
602
|
+
];
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
if (element.path) slide.elements.push(element);
|
|
607
|
+
}
|
|
608
|
+
} else if (el.type === "table") {
|
|
609
|
+
const row = el.data.length;
|
|
610
|
+
const col = el.data[0].length;
|
|
611
|
+
|
|
612
|
+
const style: TableCellStyle = {
|
|
613
|
+
fontname: theme.value.fontName,
|
|
614
|
+
color: theme.value.fontColor,
|
|
615
|
+
};
|
|
616
|
+
const data: TableCell[][] = [];
|
|
617
|
+
for (let i = 0; i < row; i++) {
|
|
618
|
+
const rowCells: TableCell[] = [];
|
|
619
|
+
for (let j = 0; j < col; j++) {
|
|
620
|
+
const cellData = el.data[i][j];
|
|
621
|
+
|
|
622
|
+
let textDiv: HTMLDivElement | null =
|
|
623
|
+
document.createElement("div");
|
|
624
|
+
textDiv.innerHTML = cellData.text;
|
|
625
|
+
const p = textDiv.querySelector("p");
|
|
626
|
+
const align = p?.style.textAlign || "left";
|
|
627
|
+
|
|
628
|
+
const span = textDiv.querySelector("span");
|
|
629
|
+
const fontsize = span?.style.fontSize
|
|
630
|
+
? (parseInt(span?.style.fontSize) * ratio).toFixed(1) + "px"
|
|
631
|
+
: "";
|
|
632
|
+
const fontname = span?.style.fontFamily || "";
|
|
633
|
+
const color = span?.style.color || cellData.fontColor;
|
|
634
|
+
|
|
635
|
+
rowCells.push({
|
|
636
|
+
id: nanoid(10),
|
|
637
|
+
colspan: cellData.colSpan || 1,
|
|
638
|
+
rowspan: cellData.rowSpan || 1,
|
|
639
|
+
text: textDiv.innerText,
|
|
640
|
+
style: {
|
|
641
|
+
...style,
|
|
642
|
+
align: ["left", "right", "center"].includes(align)
|
|
643
|
+
? (align as "left" | "right" | "center")
|
|
644
|
+
: "left",
|
|
645
|
+
fontsize,
|
|
646
|
+
fontname,
|
|
647
|
+
color,
|
|
648
|
+
bold: cellData.fontBold,
|
|
649
|
+
backcolor: cellData.fillColor,
|
|
650
|
+
},
|
|
651
|
+
});
|
|
652
|
+
textDiv = null;
|
|
653
|
+
}
|
|
654
|
+
data.push(rowCells);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const allWidth = el.colWidths.reduce((a, b) => a + b, 0);
|
|
658
|
+
const colWidths: number[] = el.colWidths.map(
|
|
659
|
+
(item) => item / allWidth
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
const firstCell = el.data[0][0];
|
|
663
|
+
const border =
|
|
664
|
+
firstCell.borders.top ||
|
|
665
|
+
firstCell.borders.bottom ||
|
|
666
|
+
el.borders.top ||
|
|
667
|
+
el.borders.bottom ||
|
|
668
|
+
firstCell.borders.left ||
|
|
669
|
+
firstCell.borders.right ||
|
|
670
|
+
el.borders.left ||
|
|
671
|
+
el.borders.right;
|
|
672
|
+
const borderWidth = border?.borderWidth || 0;
|
|
673
|
+
const borderStyle = border?.borderType || "solid";
|
|
674
|
+
const borderColor = border?.borderColor || "#eeece1";
|
|
675
|
+
|
|
676
|
+
slide.elements.push({
|
|
677
|
+
type: "table",
|
|
678
|
+
id: nanoid(10),
|
|
679
|
+
width: el.width,
|
|
680
|
+
height: el.height,
|
|
681
|
+
left: el.left,
|
|
682
|
+
top: el.top,
|
|
683
|
+
colWidths,
|
|
684
|
+
rotate: 0,
|
|
685
|
+
data,
|
|
686
|
+
outline: {
|
|
687
|
+
width: +(borderWidth * ratio || 2).toFixed(2),
|
|
688
|
+
style: borderStyle,
|
|
689
|
+
color: borderColor,
|
|
690
|
+
},
|
|
691
|
+
cellMinHeight: el.rowHeights[0] ? el.rowHeights[0] * ratio : 36,
|
|
692
|
+
});
|
|
693
|
+
} else if (el.type === "chart") {
|
|
694
|
+
let labels: string[];
|
|
695
|
+
let legends: string[];
|
|
696
|
+
let series: number[][];
|
|
697
|
+
|
|
698
|
+
if (
|
|
699
|
+
el.chartType === "scatterChart" ||
|
|
700
|
+
el.chartType === "bubbleChart"
|
|
701
|
+
) {
|
|
702
|
+
labels = el.data[0].map((item, index) => `坐标${index + 1}`);
|
|
703
|
+
legends = ["X", "Y"];
|
|
704
|
+
series = el.data;
|
|
705
|
+
} else {
|
|
706
|
+
const data = el.data as ChartItem[];
|
|
707
|
+
labels = Object.values(data[0].xlabels);
|
|
708
|
+
legends = data.map((item) => item.key);
|
|
709
|
+
series = data.map((item) => item.values.map((v) => v.y));
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const options: ChartOptions = {};
|
|
713
|
+
|
|
714
|
+
let chartType: ChartType = "bar";
|
|
715
|
+
|
|
716
|
+
switch (el.chartType) {
|
|
717
|
+
case "barChart":
|
|
718
|
+
case "bar3DChart":
|
|
719
|
+
chartType = "bar";
|
|
720
|
+
if (el.barDir === "bar") chartType = "column";
|
|
721
|
+
if (
|
|
722
|
+
el.grouping === "stacked" ||
|
|
723
|
+
el.grouping === "percentStacked"
|
|
724
|
+
)
|
|
725
|
+
options.stack = true;
|
|
726
|
+
break;
|
|
727
|
+
case "lineChart":
|
|
728
|
+
case "line3DChart":
|
|
729
|
+
if (
|
|
730
|
+
el.grouping === "stacked" ||
|
|
731
|
+
el.grouping === "percentStacked"
|
|
732
|
+
)
|
|
733
|
+
options.stack = true;
|
|
734
|
+
chartType = "line";
|
|
735
|
+
break;
|
|
736
|
+
case "areaChart":
|
|
737
|
+
case "area3DChart":
|
|
738
|
+
if (
|
|
739
|
+
el.grouping === "stacked" ||
|
|
740
|
+
el.grouping === "percentStacked"
|
|
741
|
+
)
|
|
742
|
+
options.stack = true;
|
|
743
|
+
chartType = "area";
|
|
744
|
+
break;
|
|
745
|
+
case "scatterChart":
|
|
746
|
+
case "bubbleChart":
|
|
747
|
+
chartType = "scatter";
|
|
748
|
+
break;
|
|
749
|
+
case "pieChart":
|
|
750
|
+
case "pie3DChart":
|
|
751
|
+
chartType = "pie";
|
|
752
|
+
break;
|
|
753
|
+
case "radarChart":
|
|
754
|
+
chartType = "radar";
|
|
755
|
+
break;
|
|
756
|
+
case "doughnutChart":
|
|
757
|
+
chartType = "ring";
|
|
758
|
+
break;
|
|
759
|
+
default:
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
slide.elements.push({
|
|
763
|
+
type: "chart",
|
|
764
|
+
id: nanoid(10),
|
|
765
|
+
chartType: chartType,
|
|
766
|
+
width: el.width,
|
|
767
|
+
height: el.height,
|
|
768
|
+
left: el.left,
|
|
769
|
+
top: el.top,
|
|
770
|
+
rotate: 0,
|
|
771
|
+
themeColors: el.colors.length
|
|
772
|
+
? el.colors
|
|
773
|
+
: theme.value.themeColors,
|
|
774
|
+
textColor: theme.value.fontColor,
|
|
775
|
+
data: {
|
|
776
|
+
labels,
|
|
777
|
+
legends,
|
|
778
|
+
series,
|
|
779
|
+
},
|
|
780
|
+
options,
|
|
781
|
+
});
|
|
782
|
+
} else if (el.type === "group") {
|
|
783
|
+
let elements: BaseElement[] = el.elements.map((_el) => {
|
|
784
|
+
let left = _el.left + originLeft;
|
|
785
|
+
let top = _el.top + originTop;
|
|
786
|
+
|
|
787
|
+
if (el.rotate) {
|
|
788
|
+
const { x, y } = calculateRotatedPosition(
|
|
789
|
+
originLeft,
|
|
790
|
+
originTop,
|
|
791
|
+
originWidth,
|
|
792
|
+
originHeight,
|
|
793
|
+
_el.left,
|
|
794
|
+
_el.top,
|
|
795
|
+
el.rotate
|
|
796
|
+
);
|
|
797
|
+
left = x;
|
|
798
|
+
top = y;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const element = {
|
|
802
|
+
..._el,
|
|
803
|
+
left,
|
|
804
|
+
top,
|
|
805
|
+
};
|
|
806
|
+
if (el.isFlipH && "isFlipH" in element) element.isFlipH = true;
|
|
807
|
+
if (el.isFlipV && "isFlipV" in element) element.isFlipV = true;
|
|
808
|
+
|
|
809
|
+
return element;
|
|
810
|
+
});
|
|
811
|
+
if (el.isFlipH) elements = flipGroupElements(elements, "y");
|
|
812
|
+
if (el.isFlipV) elements = flipGroupElements(elements, "x");
|
|
813
|
+
parseElements(elements);
|
|
814
|
+
} else if (el.type === "diagram") {
|
|
815
|
+
const elements = el.elements.map((_el) => ({
|
|
816
|
+
..._el,
|
|
817
|
+
left: _el.left + originLeft,
|
|
818
|
+
top: _el.top + originTop,
|
|
819
|
+
}));
|
|
820
|
+
parseElements(elements);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
parseElements([...item.elements, ...item.layoutElements]);
|
|
825
|
+
slides.push(slide);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (cover) {
|
|
829
|
+
slidesStore.updateSlideIndex(0);
|
|
830
|
+
slidesStore.setSlides(slides);
|
|
831
|
+
addHistorySnapshot();
|
|
832
|
+
} else if (isEmptySlide.value) {
|
|
833
|
+
slidesStore.setSlides(slides);
|
|
834
|
+
addHistorySnapshot();
|
|
835
|
+
} else addSlidesFromData(slides);
|
|
836
|
+
|
|
837
|
+
exporting.value = false;
|
|
838
|
+
};
|
|
839
|
+
reader.readAsArrayBuffer(file);
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
return {
|
|
843
|
+
importSpecificFile,
|
|
844
|
+
importJSON,
|
|
845
|
+
importPPTXFile,
|
|
846
|
+
exporting,
|
|
847
|
+
};
|
|
848
|
+
};
|