返回 aippt
ppt2canvas.js
根目录 / static / ppt2canvas.js
1 /* eslint-disable no-unused-vars */
2 /* eslint-disable no-self-assign */
3 // import { geometryPaths } from './geometry.js'
4 // import { drawChart } from './chart.js'
5
6 function Ppt2Canvas(_canvas, imageCrossOrigin) {
7 var canvas = (typeof _canvas == 'string') ? document.getElementById(_canvas) : _canvas
8 var ctx = canvas.getContext('2d')
9 var pptx = null
10 var page = null
11 var idMap = {}
12 var imageCache = {}
13 var pageIndex = 0
14 var templateHandle = null
15
16 this.drawPptx = async (pptxObj, pageIdx) => {
17 idMap = {}
18 imageCache = {}
19 pptx = pptxObj
20 pageIndex = pageIdx
21 page = pptxObj.pages[pageIdx]
22 canvas.width = canvas.width
23 canvas.height = canvas.height
24 ctx.scaleX = canvas.width / pptxObj.width
25 ctx.scaleY = canvas.height / pptxObj.height
26 ctx.scale(ctx.scaleX, ctx.scaleY)
27 ctx.interior = ctx._interior = null
28 let placeholder = {}
29 let slideMasterIdx = page.extInfo.slideMasterIdx
30 if (slideMasterIdx != null && pptxObj.slideMasters) {
31 let slideMaster = pptxObj.slideMasters[slideMasterIdx]
32 let slideLayoutIdx = page.extInfo.slideLayoutIdx
33 let slideLayout = slideLayoutIdx != null ? slideMaster.slideLayouts[slideLayoutIdx] : null
34 await drawBackground(page.extInfo.background || (slideLayout || {}).background || slideMaster.background)
35 await drawSlideMaster(slideMaster, slideLayout, placeholder)
36 if (slideLayout) {
37 await drawSlideLayout(slideLayout, placeholder)
38 }
39 } else {
40 await drawBackground(page.extInfo.background)
41 }
42 if (!page.children) {
43 return
44 }
45 recursion(page.children, obj => {
46 idMap[obj.id] = obj
47 if (obj.extInfo.property && obj.extInfo.property.placeholder) {
48 // 继承母版占位符
49 let element_property = obj.extInfo.property
50 let placeholder_property = placeholder[element_property.placeholder.type] || {}
51 for (let k in placeholder_property) {
52 if (element_property[k] == null) {
53 element_property[k] = placeholder_property[k]
54 }
55 }
56 }
57 })
58 for (let i = 0; i < page.children.length; i++) {
59 await drawElement(page.children[i])
60 }
61 recursion(page.children, obj => {
62 idMap[obj.id] = obj
63 })
64 }
65
66 this.getCanvas = () => {
67 return canvas
68 }
69
70 this.resetSize = (width, height) => {
71 canvas.width = width * 2
72 canvas.height = height * 2
73 canvas.style.width = width + 'px'
74 canvas.style.height = height + 'px'
75 if (pptx) {
76 this.drawPptx(pptx, pageIndex)
77 }
78 }
79
80 async function drawElement(obj) {
81 ctx.fillStyle = 'transparent'
82 ctx.strokeStyle = 'transparent'
83 if (obj.noDraw || !obj.extInfo.property.anchor || obj.extInfo.property.hidden) {
84 // console.log('ignore element:', obj)
85 return
86 }
87 if (obj.type == 'text' || obj.type == 'freeform') {
88 await drawText(obj)
89 } else if (obj.type == 'image') {
90 await drawImage(obj)
91 } else if (obj.type == 'diagram') {
92 await drawDiagram(obj)
93 } else if (obj.type == 'container') {
94 await drawContainer(obj)
95 } else if (obj.type == 'table') {
96 await drawTable(obj)
97 } else if (obj.type == 'connector') {
98 await drawConnector(obj)
99 } else if (obj.type == 'graphicFrame') {
100 await drawGraphicFrame(obj)
101 }
102 }
103
104 async function drawText(obj) {
105 let property = obj.extInfo.property
106 let geometryName = (property.geometry || {}).name || 'rect'
107 ctx.save()
108 shapeHandle(property)
109 let marginTop = property.strokeStyle ? (property.strokeStyle.lineWidth || 1) : 0
110 if (geometryName == 'tableColumn') {
111 await drawTableColumn(property)
112 } else {
113 await drawGeometry(property)
114 }
115 let anchor = property.anchor
116 let cx = anchor[0] + anchor[2] / 2
117 let cy = anchor[1] + anchor[3] / 2
118 let x = ctx.groupFlipX || 1
119 let y = ctx.groupFlipY || 1
120 if (property.flipHorizontal) {
121 x *= -1
122 }
123 if (property.flipVertical) {
124 y *= -1
125 }
126 if (y == -1) {
127 if (x == 1) {
128 ctx.translate(cx, cy)
129 ctx.scale(-1, 1)
130 ctx.translate(-cx, -cy)
131 }
132 } else if (x == -1) {
133 ctx.translate(cx, cy)
134 ctx.scale(-1, 1)
135 ctx.translate(-cx, -cy)
136 }
137 if (templateHandle) {
138 await templateHandle('text', obj, ctx)
139 }
140 if (obj.children) {
141 let textInsets = property.textInsets || [0, 0, 0, 0]
142 let isVertical = property.textDirection && property.textDirection.indexOf('VERTICAL') > -1
143 let verticalAlignment = property.textVerticalAlignment
144 if (!isVertical && (verticalAlignment == 'MIDDLE' || verticalAlignment == 'BOTTOM')) {
145 let totalTextHeight = calcTextHeight(obj)
146 if (totalTextHeight < property.anchor[3]) {
147 if (verticalAlignment == 'MIDDLE') {
148 marginTop += (property.anchor[3] - totalTextHeight - textInsets[0] - textInsets[2]) / 2 + textInsets[0]
149 } else if (verticalAlignment == 'BOTTOM') {
150 marginTop += (property.anchor[3] - totalTextHeight - textInsets[2])
151 }
152 }
153 } else {
154 marginTop += textInsets[0]
155 }
156 for (let i = 0; i < obj.children.length; i++) {
157 let p = obj.children[i]
158 if (isVertical) {
159 await drawTextP(obj, p, isVertical, 0, i)
160 } else {
161 marginTop += await drawTextP(obj, p, isVertical, marginTop, i)
162 }
163 }
164 }
165 ctx.restore()
166 }
167
168 function calcTextHeight(obj) {
169 let totalTextHeight = 0
170 let wordWrap = obj.extInfo.property.textWordWrap ?? true
171 let textInsets = obj.extInfo.property.textInsets || [0, 0, 0, 0]
172 let wInset = textInsets[1] + textInsets[3]
173 for (let i = 0; i < obj.children.length; i++) {
174 let p = obj.children[i]
175 let lineSpacing = p.extInfo.property.lineSpacing > 0 ? (p.extInfo.property.lineSpacing / 100) : (1 + Math.abs(p.extInfo.property.lineSpacing || 0) / 100)
176 let maxFontSize = 0
177 let underlined = false
178 let totalTextWidth = 0
179 for (let j = 0; j < p.children.length; j++) {
180 let r_property = p.children[j].extInfo.property
181 let fontSize = r_property.fontSize || 16
182 let text = p.children[j].text
183 if (text == '\n') {
184 totalTextHeight += lineSpacing * fontSize
185 continue
186 }
187 if (fontSize > maxFontSize) {
188 maxFontSize = fontSize
189 }
190 if (r_property.underlined) {
191 underlined = true
192 }
193 let fonts = []
194 if (r_property.bold) {
195 fonts.push('bold')
196 }
197 if (r_property.italic) {
198 fonts.push('italic')
199 }
200 fonts.push(fontSize + 'px')
201 fonts.push('"' + (r_property.fontFamily || '等线') + '"')
202 ctx.font = fonts.join(' ')
203 let lines = text.split('\n')
204 let maxTextWidth = 0
205 for (let s = 0; s < lines.length; s++) {
206 let width = ctx.measureText(lines[s]).width
207 if (width > maxTextWidth) {
208 maxTextWidth = width
209 }
210 }
211 totalTextWidth += maxTextWidth
212 if (lines.length > 1) {
213 totalTextHeight += (lines.length - 1) * lineSpacing * fontSize
214 }
215 }
216 if (wordWrap) {
217 let lineNum = parseInt((totalTextWidth - 1) / (obj.extInfo.property.anchor[2] - wInset)) + 1
218 totalTextHeight += lineNum * lineSpacing * maxFontSize
219 } else {
220 totalTextHeight += lineSpacing * maxFontSize
221 }
222 if (underlined) {
223 totalTextHeight += 1
224 }
225 }
226 return totalTextHeight
227 }
228
229 async function drawTextP(textObj, p, isVertical, marginTop, pIdx) {
230 if (!p.children) {
231 return
232 }
233 let fontList = []
234 let underlined = false
235 let totalTextWidth = 0
236 let anchor = textObj.extInfo.property.anchor
237 let wordWrap = textObj.extInfo.property.textWordWrap ?? true
238 for (let i = 0; i < p.children.length; i++) {
239 let r = p.children[i]
240 let property = r.extInfo.property
241 let fontSize = property.fontSize || 16
242 let fonts = []
243 if (property.bold) {
244 fonts.push('bold')
245 }
246 if (property.italic) {
247 fonts.push('italic')
248 }
249 if (property.slideNum) {
250 r.text = (pageIndex + 1) + ''
251 }
252 fonts.push(fontSize + 'px')
253 fonts.push('"' + (property.fontFamily || '等线') + '"')
254 let font = fonts.join(' ')
255 fontList.push(font)
256 ctx.font = font
257 if (isVertical) {
258 if (property.lang == 'en-US' && /^[0-9a-zA-Z]+$/.test(r.text)) {
259 if (fontSize > totalTextWidth) {
260 totalTextWidth = fontSize
261 }
262 } else if (r.text.length > 0) {
263 let width = ctx.measureText(r.text[0]).width
264 if (width > totalTextWidth) {
265 totalTextWidth = width
266 }
267 }
268 } else {
269 let lines = r.text.split('\n')
270 let maxTextWidth = 0
271 for (let s = 0; s < lines.length; s++) {
272 let width = ctx.measureText(lines[s]).width
273 if (width > maxTextWidth) {
274 maxTextWidth = width
275 }
276 }
277 totalTextWidth += maxTextWidth
278 }
279 if (property.underlined) {
280 underlined = true
281 }
282 }
283 if (wordWrap) {
284 totalTextWidth = Math.min(totalTextWidth, anchor[2])
285 }
286 let textAlign = p.extInfo.property.textAlign
287 let verticalAlignment = textObj.extInfo.property.textVerticalAlignment
288 let textInsets = textObj.extInfo.property.textInsets || [0, 0, 0, 0]
289 let lineSpacing = p.extInfo.property.lineSpacing > 0 ? (p.extInfo.property.lineSpacing / 100) : (1 + Math.abs(p.extInfo.property.lineSpacing || 0) / 100)
290 let x = anchor[0]
291 let y = anchor[1] + (marginTop || 0)
292 let endX = anchor[0] + anchor[2] - textInsets[3]
293 ctx.textAlign = 'left'
294 ctx.textBaseline = 'top'
295 if (textAlign == 'CENTER') {
296 x = x + (anchor[2] - totalTextWidth - textInsets[1] - textInsets[3]) / 2 + textInsets[1]
297 } else if (textAlign == 'RIGHT') {
298 x = x + anchor[2] - textInsets[3] - totalTextWidth
299 } else {
300 x = x + textInsets[1]
301 }
302 const _x = x
303 const _y = y
304 let lastWord = null
305 let maxFontSize = 0
306 for (let i = 0; i < p.children.length; i++) {
307 let r = p.children[i]
308 let property = r.extInfo.property
309 let fontSize = property.fontSize || 16
310 let drawString = (s, x, y) => {
311 doShadow(ctx, property.shadow)
312 ctx.translate(x, y)
313 ctx.fillText(s, 0, 0)
314 ctx.translate(-x, -y)
315 }
316 let fillStyle = await toPaint(property.fontColor, anchor)
317 ctx.fillStyle = fillStyle
318 if (property.line && (property.fontColor == null || property.fontColor.type == 'noFill')) {
319 drawString = (s, x, y) => {
320 doShadow(ctx, property.shadow)
321 ctx.translate(x, y)
322 ctx.strokeText(s, 0, 0)
323 ctx.translate(-x, -y)
324 }
325 ctx.strokeStyle = await toPaint(property.line.paint, anchor)
326 }
327 ctx.font = fontList[i]
328 if (fontSize > maxFontSize) {
329 maxFontSize = fontSize
330 }
331 if (r.text == '\n') {
332 y += lineSpacing * fontSize
333 x = _x
334 continue
335 }
336 if (isVertical) {
337 // 竖版
338 x = x + fontSize * pIdx
339 if (property.lang == 'en-US' && /^[0-9a-zA-Z]+$/.test(r.text)) {
340 // 数字英文竖着的时候需要旋转90°方式呈现
341 let textWidth = ctx.measureText(r.text).width
342 let fontSize = property.fontSize || 16
343 ctx.save()
344 ctx.translate(x, y + fontSize)
345 ctx.rotate(90 * Math.PI / 180)
346 ctx.translate(-x, -y - fontSize)
347 drawString(r.text, x - fontSize, y)
348 ctx.restore()
349 y += textWidth
350 } else {
351 for (let j = 0; j < r.text.length; j++) {
352 let text = r.text[j]
353 drawString(text, x, y)
354 y += fontSize
355 }
356 }
357 if (property.underlined) {
358 let lineX = x
359 let lineY = _y
360 ctx.lineWidth = 1
361 ctx.strokeStyle = fillStyle
362 ctx.beginPath()
363 ctx.moveTo(lineX, lineY)
364 ctx.lineTo(lineX, lineY + (y - _y))
365 ctx.closePath()
366 ctx.stroke()
367 }
368 } else if (wordWrap) {
369 // 横版-自动换行
370 let texts = splitWords(r.text)
371 let drawLine = (textWidth) => {
372 if (property.underlined) {
373 let lineX = _x
374 let lineY = y
375 if (verticalAlignment == 'MIDDLE') {
376 lineY = lineY + fontSize / 2 + 1
377 } else if (verticalAlignment == 'BOTTOM') {
378 lineY = lineY + 1
379 } else {
380 lineY = lineY + fontSize + 1
381 }
382 ctx.lineWidth = 1
383 ctx.strokeStyle = fillStyle
384 ctx.beginPath()
385 ctx.moveTo(lineX, lineY)
386 ctx.lineTo(lineX + textWidth, lineY)
387 ctx.closePath()
388 ctx.stroke()
389 }
390 }
391 for (let j = 0; j < texts.length; j++) {
392 let item = texts[j]
393 if (item.text == '\n') {
394 x = _x
395 y += lineSpacing * fontSize
396 lastWord = null
397 continue
398 }
399 if (item.word && lastWord != null && !lastWord) {
400 let textWidth = ctx.measureText(item.text).width
401 if (x + textWidth > endX + fontSize / 4) {
402 drawLine(x - _x)
403 x = _x
404 y += lineSpacing * fontSize
405 }
406 }
407 for (let s = 0; s < item.text.length; s++) {
408 let text = item.text[s]
409 let textWidth = ctx.measureText(text).width
410 if (x + textWidth > endX + fontSize / 4 && !isSymbol(text)) {
411 drawLine(x - _x)
412 x = _x
413 y += lineSpacing * fontSize
414 }
415 let ty = y
416 if (verticalAlignment == 'MIDDLE') {
417 ctx.textBaseline = 'middle'
418 ty = ty + (lineSpacing * fontSize) / 2
419 } else if (verticalAlignment == 'BOTTOM') {
420 ctx.textBaseline = 'bottom'
421 ty = ty + (lineSpacing * fontSize)
422 } else {
423 ctx.textBaseline = 'top'
424 }
425 drawString(text, x, ty)
426 x += textWidth
427 }
428 lastWord = item.word
429 }
430 if (i == p.children.length - 1) {
431 drawLine(x - _x)
432 }
433 } else {
434 // 横版-不自动换行
435 let lines = r.text.split('\n')
436 for (let s = 0; s < lines.length; s++) {
437 if (s != 0) {
438 x = _x
439 y += lineSpacing * fontSize
440 }
441 let ty = y
442 if (verticalAlignment == 'MIDDLE') {
443 ctx.textBaseline = 'middle'
444 ty = ty + (lineSpacing * fontSize) / 2
445 } else if (verticalAlignment == 'BOTTOM') {
446 ctx.textBaseline = 'bottom'
447 ty = ty + (lineSpacing * fontSize)
448 } else {
449 ctx.textBaseline = 'top'
450 }
451 drawString(lines[s], x, ty)
452 let textWidth = ctx.measureText(lines[s]).width
453 if (property.underlined) {
454 let lineX = x
455 let lineY = y
456 if (verticalAlignment == 'MIDDLE') {
457 lineY = lineY + fontSize / 2 + 1
458 } else if (verticalAlignment == 'BOTTOM') {
459 lineY = lineY + 1
460 } else {
461 lineY = lineY + fontSize + 1
462 }
463 ctx.lineWidth = 1
464 ctx.strokeStyle = fillStyle
465 ctx.beginPath()
466 ctx.moveTo(lineX, lineY)
467 ctx.lineTo(lineX + textWidth, lineY)
468 ctx.closePath()
469 ctx.stroke()
470 }
471 x += textWidth
472 }
473 }
474 }
475 return (y - _y) + (underlined && !isVertical ? 1 : 0) + lineSpacing * maxFontSize
476 }
477
478 async function drawImage(obj) {
479 let property = obj.extInfo.property
480 ctx.save()
481 shapeHandle(property)
482 if (property.fillStyle && property.fillStyle.texture) {
483 property.fillStyle.texture.imageData = property.image
484 // 图片自带拉伸
485 property.fillStyle.texture.stretch = property.fillStyle.texture.stretch || [0, 0, 0, 0]
486 } else {
487 property.fillStyle = { 'type': 'texture', texture: { 'imageData': property.image, insets: property.clipping, stretch: [0, 0, 0, 0] } }
488 }
489 if (!property.geometry) {
490 property.geometry = { name: 'rect' }
491 }
492 await drawGeometry(property)
493 if (templateHandle) {
494 await templateHandle('image', obj, ctx)
495 }
496 ctx.restore()
497 }
498
499 async function drawTableColumn(property) {
500 let anchor = property.anchor
501 let x = anchor[0] - 1
502 let endX = anchor[0] + anchor[2] + 1
503 let y = anchor[1] - 1
504 let endY = anchor[1] + anchor[3] + 1
505 let borders = property.borders // top/left/bottom/right
506 ctx.beginPath()
507 ctx.moveTo(x, y)
508 let top = borders[0]
509 ctx.strokeStyle = toColor({ color: top.color }, 'white')
510 ctx.lineWidth = top.lineWidth
511 ctx.lineTo(endX, y)
512 let right = borders[3]
513 ctx.strokeStyle = toColor({ color: right.color }, 'white')
514 ctx.lineWidth = right.lineWidth
515 ctx.lineTo(endX, endY)
516 let bottom = borders[2]
517 ctx.strokeStyle = toColor({ color: bottom.color }, 'white')
518 ctx.lineWidth = bottom.lineWidth
519 ctx.lineTo(x, endY)
520 let left = borders[1]
521 ctx.strokeStyle = toColor({ color: left.color }, 'white')
522 ctx.lineWidth = left.lineWidth
523 ctx.lineTo(x, anchor[1])
524 ctx.closePath()
525 ctx.fillStyle = await toPaint(property.fillStyle, property.anchor)
526 ctx.fill()
527 ctx.stroke()
528 }
529
530 async function drawDiagram(obj) {
531 let property = obj.extInfo.property
532 ctx.save()
533 ctx.translate(property.anchor[0], property.anchor[1])
534 if (templateHandle) {
535 await templateHandle('diagram', obj, ctx)
536 }
537 for (let i = 0; i < obj.children.length; i++) {
538 await drawElement(obj.children[i])
539 }
540 ctx.translate(-property.anchor[0], -property.anchor[1])
541 ctx.restore()
542 }
543
544 async function drawContainer(obj) {
545 let property = obj.extInfo.property
546 if (property.realType == 'Group') {
547 ctx.save()
548 let _groupFlipX = ctx.groupFlipX
549 let _groupFlipY = ctx.groupFlipY
550 let _groupRotation = ctx.groupRotation
551 if (templateHandle) {
552 await templateHandle('container', obj, ctx)
553 }
554 shapeHandle(property)
555 let parentGroupFillStyle = ctx.groupFillStyle
556 let groupFillStyle = property.groupFillStyle
557 if (groupFillStyle) {
558 groupFillStyle = JSON.parse(JSON.stringify(groupFillStyle))
559 groupFillStyle.groupAnchor = property.anchor
560 if (parentGroupFillStyle) {
561 groupFillStyle.parentGroupFillStyle = parentGroupFillStyle
562 }
563 }
564 ctx.groupFillStyle = groupFillStyle
565 let copyChildren = JSON.parse(JSON.stringify(obj.children))
566 recursionGroupChildren(copyChildren, c => {
567 if (c.extInfo && c.extInfo.property && c.extInfo.property.anchor) {
568 let anchor = c.extInfo.property.anchor
569 anchor[0] *= ctx.interior.scaleX
570 anchor[1] *= ctx.interior.scaleY
571 anchor[2] *= ctx.interior.scaleX
572 anchor[3] *= ctx.interior.scaleY
573 }
574 })
575 for (let i = 0; i < copyChildren.length; i++) {
576 await drawElement(copyChildren[i])
577 }
578 ctx.interior = ctx._interior
579 ctx.groupFlipX = _groupFlipX
580 ctx.groupFlipY = _groupFlipY
581 ctx.groupRotation = _groupRotation
582 ctx.restore()
583 } else {
584 for (let i = 0; i < obj.children.length; i++) {
585 await drawElement(obj.children[i])
586 }
587 }
588 }
589
590 function recursionGroupChildren(children, fn) {
591 if (!children) {
592 return
593 }
594 for (let i = 0; i < children.length; i++) {
595 let c = children[i]
596 fn(c)
597 if (c.extInfo && c.extInfo.property.realType == 'Group') {
598 continue
599 }
600 if (c.children && c.children.length > 0) {
601 recursionGroupChildren(c.children, fn)
602 }
603 }
604 }
605
606 async function drawConnector(obj) {
607 let property = obj.extInfo.property
608 ctx.save()
609 shapeHandle(property)
610 await drawGeometry(property)
611 if (templateHandle) {
612 await templateHandle('connector', obj, ctx)
613 }
614 ctx.restore()
615 }
616
617 async function drawGraphicFrame(obj) {
618 let property = obj.extInfo.property
619 ctx.save()
620 shapeHandle(property)
621 if (templateHandle) {
622 await templateHandle('graphicFrame', obj, ctx)
623 }
624 if (property.chart && property.chart.chartData && property.chart.chartData.length > 0) {
625 await drawChart(property.chart, property.anchor, canvas, ctx)
626 }
627 ctx.restore()
628 }
629
630 async function drawTable(obj) {
631 let property = obj.extInfo.property
632 for (let i = 0; i < obj.children.length; i++) {
633 let row = obj.children[i]
634 await drawTableRow(obj, row)
635 }
636 }
637
638 async function drawTableRow(table, row) {
639 let property = row.extInfo.property
640 for (let i = 0; i < row.children.length; i++) {
641 let column = row.children[i]
642 await drawText(column)
643 }
644 }
645
646 async function drawGeometry(property) {
647 let geometry = property.geometry
648 let anchor = property.anchor
649 if (!geometry || !anchor || (anchor[2] == 0 && anchor[3] == 0)) {
650 return
651 }
652 ctx.save()
653 ctx.translate(anchor[0], anchor[1])
654 let paths = geometryPaths(property)
655 let _anchor = [0, 0, anchor[2], anchor[3]]
656 if (geometry.name == 'custom') {
657 await drawPaths(paths, property, _anchor, anchor)
658 } else if (geometry.name == 'line' || geometry.name == 'straightConnector1') {
659 await drawPaths(paths, property, _anchor, anchor)
660 } else {
661 await drawPaths(paths, property, _anchor, anchor)
662 }
663 ctx.translate(-anchor[0], -anchor[1])
664 ctx.restore()
665 }
666
667 async function drawPaths(paths, property, anchor, real_anchor) {
668 for (let i = 0; i < paths.length; i++) {
669 let path = paths[i]
670 // console.log('path: ', path)
671 ctx.save()
672 let scaleX = path.scaleX
673 let scaleY = path.scaleY
674 ctx.scale(scaleX, scaleY)
675 let _anchor = [anchor[0], anchor[1], anchor[2], anchor[3]]
676 if (scaleX != 1 || scaleY != 1) {
677 _anchor[0] = _anchor[0] / scaleX
678 _anchor[1] = _anchor[1] / scaleY
679 _anchor[2] = _anchor[2] / scaleX
680 _anchor[3] = _anchor[3] / scaleY
681 }
682 ctx.beginPath()
683 let split = path.path.replace(/\s+/g, ' ').trim().split(' ')
684 for (let j = 0; j < split.length; j++) {
685 switch (split[j]) {
686 case 'M':
687 ctx.moveTo(split[++j], split[++j])
688 break
689 case 'L':
690 ctx.lineTo(split[++j], split[++j])
691 break
692 case 'Q':
693 ctx.quadraticCurveTo(split[++j], split[++j], split[++j], split[++j])
694 break
695 case 'C':
696 ctx.bezierCurveTo(split[++j], split[++j], split[++j], split[++j], split[++j], split[++j])
697 break
698 case 'Z':
699 ctx.closePath()
700 break
701 }
702 }
703 let strokeStyle = null
704 if (property.strokeStyle) {
705 ctx.lineWidth = (property.strokeStyle.lineWidth || 1) / scaleY
706 let lineCap = property.strokeStyle.lineCap
707 if (!lineCap || lineCap == 'FLAT') {
708 lineCap = 'butt'
709 }
710 ctx.lineCap = lineCap.toLowerCase()
711 strokeStyle = await toPaint(property.strokeStyle.paint, _anchor)
712 }
713 doShadow(ctx, property.shadow)
714 if (path.stroked && strokeStyle) {
715 ctx.strokeStyle = strokeStyle
716 } else {
717 ctx.strokeStyle = 'transparent'
718 }
719 ctx.stroke()
720 ctx.scale(1 / scaleX, 1 / scaleY)
721 let fillStyle = await toPaint(property.fillStyle, anchor)
722 if (path.filled) {
723 ctx.fillStyle = fillStyle
724 } else {
725 ctx.fillStyle = 'transparent'
726 }
727 if (property.fillStyle && property.fillStyle.type == 'bgFill') {
728 let rotation = (property.rotation || 0) + (ctx.groupRotation || 0)
729 if (rotation) {
730 ctx.translate(real_anchor[2] / 2, real_anchor[3] / 2)
731 ctx.rotate(-rotation * Math.PI / 180)
732 ctx.translate(-real_anchor[2] / 2, -real_anchor[3] / 2)
733 }
734 let tx = 0, ty = 0
735 if (ctx.interior) {
736 tx = -ctx.interior.tx - real_anchor[0]
737 ty = -ctx.interior.ty - real_anchor[1]
738 } else {
739 tx = -real_anchor[0]
740 ty = -real_anchor[1]
741 }
742 ctx.translate(tx, ty)
743 ctx.fill()
744 ctx.translate(-tx, -ty)
745 } else {
746 ctx.fill()
747 }
748 ctx.restore()
749 }
750 }
751
752 function shapeHandle(property) {
753 let anchor = property.anchor
754 if (!anchor) {
755 return
756 }
757 let cx = anchor[0] + anchor[2] / 2
758 let cy = anchor[1] + anchor[3] / 2
759 if (property.rotation) {
760 // 旋转
761 ctx.translate(cx, cy)
762 ctx.rotate(property.rotation * Math.PI / 180)
763 ctx.translate(-cx, -cy)
764 if (property.realType == 'Group') {
765 ctx.groupRotation = (ctx.groupRotation || 0) + property.rotation
766 }
767 }
768 if (property.flipVertical) {
769 ctx.translate(cx, cy)
770 ctx.scale(1, -1)
771 ctx.translate(-cx, -cy)
772 if (property.realType == 'Group') {
773 ctx.groupFlipY = -(ctx.groupFlipY || 1)
774 }
775 }
776 if (property.flipHorizontal) {
777 ctx.translate(cx, cy)
778 ctx.scale(-1, 1)
779 ctx.translate(-cx, -cy)
780 if (property.realType == 'Group') {
781 ctx.groupFlipX = -(ctx.groupFlipX || 1)
782 }
783 }
784 // 嵌套容器 Group
785 let interior = property.interiorAnchor
786 if (interior && interior.length > 0) {
787 /*
788 // 缩放
789 let scaleX = interior[2] == 0 || anchor[2] == interior[2] ? 1 : anchor[2] / interior[2]
790 let scaleY = interior[3] == 0 || anchor[3] == interior[3] ? 1 : anchor[3] / interior[3]
791 ctx.scale(scaleX, scaleY)
792 ctx.translate(anchor[0] / scaleX - interior[0], anchor[1] / scaleY - interior[1])
793 */
794 let scaleX = interior[2] == 0 || anchor[2] == interior[2] ? 1 : anchor[2] / interior[2]
795 let scaleY = interior[3] == 0 || anchor[3] == interior[3] ? 1 : anchor[3] / interior[3]
796 let tx = anchor[0] - interior[0] * scaleX
797 let ty = anchor[1] - interior[1] * scaleY
798 ctx.translate(tx, ty)
799 ctx._interior = ctx.interior
800 ctx.interior = { scaleX, scaleY, tx, ty }
801 } else if (property.realType == 'Group') {
802 ctx._interior = ctx.interior
803 ctx.interior = { scaleX: 1, scaleY: 1, tx: 0, ty: 0 }
804 }
805 }
806
807 async function drawBackground(background) {
808 if (!background) {
809 ctx.bgFillStyle = null
810 return
811 }
812 ctx.fillStyle = await toPaint(background.fillStyle, background.anchor, true)
813 ctx.fillRect(background.anchor[0], background.anchor[1], background.anchor[2], background.anchor[3])
814 ctx.bgFillStyle = ctx.fillStyle
815 }
816
817 async function drawSlideMaster(slideMaster, slideLayout, placeholder) {
818 recursion(slideMaster.children, obj => {
819 if (obj.extInfo.property && obj.extInfo.property.placeholder) {
820 obj.noDraw = true
821 placeholder[obj.extInfo.property.placeholder.type] = obj.extInfo.property
822 }
823 })
824 if (slideLayout && slideLayout.noMaster) {
825 return
826 }
827 for (let i = 0; slideMaster.children && i < slideMaster.children.length; i++) {
828 await drawElement(slideMaster.children[i])
829 }
830 }
831
832 async function drawSlideLayout(slideLayout, placeholder) {
833 recursion(slideLayout.children, obj => {
834 if (obj.extInfo.property && obj.extInfo.property.placeholder) {
835 obj.noDraw = true
836 placeholder[obj.extInfo.property.placeholder.type] = obj.extInfo.property
837 }
838 })
839 for (let i = 0; slideLayout.children && i < slideLayout.children.length; i++) {
840 await drawElement(slideLayout.children[i])
841 }
842 }
843
844 function recursion(children, fn) {
845 if (!children) {
846 return
847 }
848 for (let i = 0; i < children.length; i++) {
849 let c = children[i]
850 fn(c)
851 if (c.children && c.children.length > 0) {
852 recursion(c.children, fn)
853 }
854 }
855 }
856
857 function doShadow(ctx, shadow) {
858 if (!shadow) {
859 return
860 }
861 ctx.shadowBlur = shadow.blur || 0
862 ctx.shadowColor = toColor(shadow.fillStyle.color)
863 if (shadow.distance) {
864 let radians = (shadow.angle || 0) * Math.PI / 180
865 let x = 0, y = 0, r = shadow.distance * 2
866 ctx.shadowOffsetX = x + r * Math.cos(radians)
867 ctx.shadowOffsetY = y + r * Math.sin(radians)
868 }
869 }
870
871 function toPaint(paint, anchor, isBackground, defaultColor) {
872 return new Promise((resolve, reject) => {
873 if (!paint) {
874 resolve(defaultColor || 'transparent')
875 } else if (paint.type == 'noFill') {
876 // 无填充
877 resolve('transparent')
878 } else if (paint.type == 'color') {
879 // 颜色
880 resolve(toColor(paint.color, defaultColor))
881 } else if (paint.type == 'bgFill') {
882 // 背景填充
883 resolve(ctx.bgFillStyle || defaultColor || 'transparent')
884 } else if (paint.type == 'groupFill') {
885 // 组合背景
886 let groupFillStyle = paint.parentGroupFillStyle || ctx.groupFillStyle
887 if (groupFillStyle) {
888 toPaint(groupFillStyle, anchor || groupFillStyle.groupAnchor, false, defaultColor).then(res => {
889 resolve(res)
890 })
891 } else {
892 resolve(defaultColor || 'transparent')
893 }
894 } else if (paint.type == 'gradient') {
895 // 渐变
896 let gradient = paint.gradient
897 let x = anchor[0], y = anchor[1], width = anchor[2], height = anchor[3]
898 let centerX = x + width / 2
899 let centerY = y + height / 2
900 let gradientObj
901 // linear,circular,rectangular,shape
902 if (gradient.gradientType == 'circular') {
903 // 射线
904 let radius = Math.sqrt(width * width + height * height) * (gradient.insets[1] == 0.5 ? 0.5 : 1)
905 let cx = centerX + width * (gradient.insets[1] - gradient.insets[3]) / 2
906 let cy = centerY + height * (gradient.insets[0] - gradient.insets[2]) / 2
907 gradientObj = ctx.createRadialGradient(cx, cy, 0, cx, cy, radius)
908 } else {
909 // 线性
910 let startX = x
911 let startY = centerY
912 let endX = x + width
913 let endY = centerY
914 if (gradient.angle) {
915 let radians = gradient.angle * Math.PI / 180
916 let midX = (startX + endX) / 2
917 let midY = (startY + endY) / 2
918 let newStartX = midX + (startX - midX) * Math.cos(radians) - (startY - midY) * Math.sin(radians)
919 let newStartY = midY + (startX - midX) * Math.sin(radians) + (startY - midY) * Math.cos(radians)
920 let newEndX = midX + (endX - midX) * Math.cos(radians) - (endY - midY) * Math.sin(radians)
921 let newEndY = midY + (endX - midX) * Math.sin(radians) + (endY - midY) * Math.cos(radians)
922 startX = newStartX
923 startY = newStartY
924 endX = newEndX
925 endY = newEndY
926 }
927 gradientObj = ctx.createLinearGradient(startX, startY, endX, endY)
928 }
929 for (let i = 0; i < gradient.colors.length; i++) {
930 let color = gradient.colors[i]
931 gradientObj.addColorStop(gradient.fractions[i], toColor(color))
932 }
933 resolve(gradientObj)
934 } else if (anchor && anchor[2] == 0 && anchor[3] == 0) {
935 resolve('transparent')
936 } else if (paint.type == 'texture') {
937 // 图片或纹理
938 let texture = paint.texture
939 let anonymous = texture.duoTone && texture.duoTone.length > 0 && texture.duoTonePrst
940 loadImage(texture.imageData, anonymous).then(img => {
941 if (img) {
942 let pat = createTexturePattern(img, texture, anchor, isBackground)
943 resolve(pat)
944 } else {
945 resolve('transparent')
946 }
947 })
948 } else if (paint.type == 'pattern') {
949 // 图案
950 let pattern = paint.pattern
951 // let prst = pattern.prst
952 let fgColor = pattern.fgColor.realColor
953 let bgColor = pattern.bgColor.realColor
954 let width = anchor[2], height = anchor[3]
955 let imgCanvas = document.createElement('canvas')
956 imgCanvas.width = width
957 imgCanvas.height = height
958 let imgCtx = imgCanvas.getContext('2d')
959 imgCtx.imageSmoothingEnabled = true
960 imgCtx.imageSmoothingQuality = 'high'
961 let imgData = imgCtx.createImageData(width, height)
962 let line = 0
963 for (let i = 0; i < imgData.data.length; i += 4) {
964 if (++line % 16 == 0) {
965 // 前景
966 imgData.data[i + 0] = (fgColor >> 16) & 255
967 imgData.data[i + 1] = (fgColor >> 8) & 255
968 imgData.data[i + 2] = (fgColor >> 0) & 255
969 imgData.data[i + 3] = (fgColor >> 24) & 255
970 } else {
971 // 背景
972 imgData.data[i + 0] = (bgColor >> 16) & 255
973 imgData.data[i + 1] = (bgColor >> 8) & 255
974 imgData.data[i + 2] = (bgColor >> 0) & 255
975 imgData.data[i + 3] = (bgColor >> 24) & 255
976 }
977 if (i % 400 == 0) {
978 line += 2
979 }
980 }
981 imgCtx.putImageData(imgData, 0, 0)
982 let imgSrc = imgCanvas.toDataURL()
983 let image = new Image()
984 image.src = imgSrc
985 image.onload = async function() {
986 resolve(ctx.createPattern(image, 'no-repeat'))
987 }
988 }
989 })
990 }
991
992 function createTexturePattern(img, texture, anchor, isBackground) {
993 let width = anchor[2]
994 let height = anchor[3]
995 let mode = texture.alignment || !texture.stretch ? 'repeat' : 'no-repeat'
996 if (width < 1 && height < 1 || isNaN(width) || isNaN(height)) {
997 return ctx.createPattern(img, mode)
998 }
999 if (texture.alignment || !texture.stretch) {
1000 width = img.width
1001 height = img.height
1002 }
1003 let patternCanvas = document.createElement('canvas')
1004 patternCanvas.width = Math.max(1, width)
1005 patternCanvas.height = Math.max(1, height)
1006 let patternCtx = patternCanvas.getContext('2d')
1007 patternCtx.imageSmoothingEnabled = true
1008 patternCtx.imageSmoothingQuality = 'high'
1009 if (texture.alpha >= 0 && texture.alpha < 100000) {
1010 patternCtx.globalAlpha = texture.alpha / 100000
1011 }
1012 let imgInsets = null
1013 if (texture.insets) {
1014 let top = texture.insets[0] / 100000
1015 let left = texture.insets[1] / 100000
1016 let bottom = texture.insets[2] / 100000
1017 let right = texture.insets[3] / 100000
1018 let x = img.width * left
1019 let y = img.height * top
1020 let w = img.width * (1 - left - right)
1021 let h = img.height * (1 - top - bottom)
1022 imgInsets = [x, y, w, h]
1023 }
1024 if (texture.stretch) {
1025 let top = texture.stretch[0] / 100000
1026 let left = texture.stretch[1] / 100000
1027 let bottom = texture.stretch[2] / 100000
1028 let right = texture.stretch[3] / 100000
1029 let x = width * left
1030 let y = height * top
1031 let w = width * (1 - left - right)
1032 let h = height * (1 - top - bottom)
1033 if (imgInsets) {
1034 patternCtx.drawImage(img, imgInsets[0], imgInsets[1], imgInsets[2], imgInsets[3], x, y, w, h)
1035 } else {
1036 patternCtx.drawImage(img, x, y, w, h)
1037 }
1038 } else if (texture.alignment) {
1039 let x = 0, y = 0
1040 if (texture.alignment == 'CENTER') {
1041 if (width > anchor[2]) {
1042 x = (width - anchor[2]) / 2
1043 }
1044 if (height > anchor[3]) {
1045 y = (height - anchor[3]) / 2
1046 }
1047 }
1048 patternCtx.drawImage(img, x, y, width, height, 0, 0, width, height)
1049 } else {
1050 if (imgInsets) {
1051 patternCtx.drawImage(img, imgInsets[0], imgInsets[1], imgInsets[2], imgInsets[3], 0, 0, width, height)
1052 } else {
1053 patternCtx.drawImage(img, 0, 0, width, height)
1054 }
1055 }
1056 if (texture.duoTone && texture.duoTone.length > 0 && texture.duoTonePrst) {
1057 try {
1058 // 重新着色
1059 let color = texture.duoTone[0].realColor
1060 let r = (color >> 16) & 255
1061 let g = (color >> 8) & 255
1062 let b = (color >> 0) & 255
1063 let imageData = patternCtx.getImageData(0, 0, patternCanvas.width, patternCanvas.height)
1064 let data = imageData.data
1065 for(var i = 0; i < data.length; i += 4) {
1066 let gray = (data[i] * 0.3 + data[i + 1] * 0.59 + data[i + 2] * 0.11) / 255
1067 // black / white
1068 let prst = texture.duoTonePrst == 'white' ? 255 : 0
1069 data[i] = gray * r + (1 - gray) * prst
1070 data[i + 1] = gray * g + (1 - gray) * prst
1071 data[i + 2] = gray * b + (1 - gray) * prst
1072 }
1073 patternCtx.putImageData(imageData, 0, 0)
1074 } catch(e) { /* empty */ }
1075 }
1076 return ctx.createPattern(patternCanvas, mode)
1077 }
1078
1079 function toColor(colorObj, defaultColor) {
1080 if (colorObj == null || (colorObj.color == null && colorObj.realColor == null)) {
1081 return defaultColor || 'transparent'
1082 }
1083 let color = colorObj.realColor != null ? colorObj.realColor : colorObj.color
1084 let r = (color >> 16) & 255
1085 let g = (color >> 8) & 255
1086 let b = (color >> 0) & 255
1087 let a = ((color >> 24) & 255) / 255
1088 if (colorObj.realColor == null) {
1089 if (colorObj.alpha != null && colorObj.alpha != -1) {
1090 if (colorObj.alpha > 1000) {
1091 a = colorObj.alpha / 100000
1092 } else {
1093 a = (colorObj.alpha > 0 && colorObj.alpha < 1) ? colorObj.alpha : colorObj.alpha / 255
1094 }
1095 a = Math.min(1, Math.max(0, a))
1096 }
1097 if (colorObj.lumMod && colorObj.lumMod > 0) {
1098 let value = colorObj.lumMod / 100000
1099 r = r * value
1100 g = g * value
1101 b = b * value
1102 }
1103 if (colorObj.lumOff && colorObj.lumOff > 0) {
1104 let value = colorObj.lumOff / 100000
1105 r += 255 * value
1106 g += 255 * value
1107 b += 255 * value
1108 }
1109 }
1110 return `rgba(${r}, ${g}, ${b}, ${a})`
1111 }
1112
1113 function toColorValue(r, g, b, a) {
1114 a = (a == null ? 255 : a)
1115 return (a & 255) << 24 | (r & 255) << 16 | (g & 255) << 8 | (b & 255) << 0
1116 }
1117
1118 function loadImage(src, anonymous) {
1119 return new Promise(resolve => {
1120 if (!src) {
1121 resolve()
1122 return
1123 }
1124 let cacheKey
1125 if (src.length < 15) {
1126 cacheKey = src
1127 } else {
1128 cacheKey = src.length + '_' + src.substring(src.length - 15)
1129 }
1130 let img = imageCache[cacheKey]
1131 if (img == null) {
1132 img = new Image()
1133 if (imageCrossOrigin || anonymous) {
1134 let eqOrigin = src.startsWith('data:') || src.startsWith(document.location.origin) || (src.startsWith('//') && (document.location.protocol + src).startsWith(document.location.origin))
1135 if (!eqOrigin) {
1136 // anonymous / use-credentials
1137 img.crossOrigin = imageCrossOrigin || 'anonymous'
1138 }
1139 }
1140 img.src = src
1141 img.onload = function() {
1142 imageCache[cacheKey] = img
1143 resolve(img)
1144 }
1145 img.onerror = function (e) {
1146 resolve()
1147 console.log('图片加载失败: ', src, e)
1148 }
1149 } else {
1150 resolve(img)
1151 }
1152 })
1153 }
1154
1155 function value2px(v, isEmu) {
1156 if (isEmu) {
1157 return +v / 12700
1158 } else {
1159 return +v
1160 }
1161 }
1162
1163 function value2emu(v) {
1164 return +v * 12700
1165 }
1166
1167 function getTfx(x, div) {
1168 if (div) {
1169 return x / (ctx.getTransform().a / ctx.scaleX)
1170 } else {
1171 return x * (ctx.getTransform().a / ctx.scaleX)
1172 }
1173 }
1174
1175 function getTfy(y, div) {
1176 if (div) {
1177 return y / (ctx.getTransform().d / ctx.scaleY)
1178 } else {
1179 return y * (ctx.getTransform().d / ctx.scaleY)
1180 }
1181 }
1182
1183 function splitWords(str) {
1184 if (!str) {
1185 return []
1186 }
1187 let pattern = /^[0-9a-zA-Z]+$/
1188 let array = []
1189 let item = ''
1190 let math = null
1191 for (let i = 0; i < str.length; i++) {
1192 if (str[i] == '\n') {
1193 if (item) {
1194 array.push({ text: item, word: math })
1195 }
1196 array.push({ text: '\n', word: false })
1197 item = ''
1198 math = null
1199 } else {
1200 let _math = pattern.test(str[i])
1201 if (math == null) {
1202 math = _math
1203 item += str[i]
1204 } else if (_math == math) {
1205 item += str[i]
1206 } else {
1207 array.push({ text: item, word: math })
1208 item = str[i]
1209 math = _math
1210 }
1211 }
1212 }
1213 if (item) {
1214 array.push({ text: item, word: math })
1215 }
1216 return array
1217 }
1218
1219 function isSymbol(s) {
1220 switch (s) {
1221 case ',':
1222 case '.':
1223 case '!':
1224 case '?':
1225 case ')':
1226 case ';':
1227 case ',':
1228 case '。':
1229 case '!':
1230 case '?':
1231 case ')':
1232 case ';':
1233 case '”':
1234 case ' ':
1235 return true
1236 }
1237 return false
1238 }
1239
1240 function rgb2hsv(r, g, b) {
1241 r /= 255, g /= 255, b /= 255
1242 let max = Math.max(r, g, b), min = Math.min(r, g, b)
1243 let h, s, v = max
1244 let d = max - min
1245 s = max == 0 ? 0 : d / max
1246 if (max == min) {
1247 h = 0
1248 } else {
1249 switch (max) {
1250 case r: h = (g - b) / d + (g < b ? 6 : 0); break
1251 case g: h = (b - r) / d + 2; break
1252 case b: h = (r - g) / d + 4; break
1253 }
1254 h /= 6
1255 }
1256 return [h, s, v]
1257 }
1258
1259 function hsv2rgb(h, s, v) {
1260 let r, g, b
1261 let i = Math.floor(h * 6)
1262 let f = h * 6 - i
1263 let p = v * (1 - s)
1264 let q = v * (1 - f * s)
1265 let t = v * (1 - (1 - f) * s)
1266 switch (i % 6) {
1267 case 0: r = v, g = t, b = p; break
1268 case 1: r = q, g = v, b = p; break
1269 case 2: r = p, g = v, b = t; break
1270 case 3: r = p, g = q, b = v; break
1271 case 4: r = t, g = p, b = v; break
1272 case 5: r = v, g = p, b = q; break
1273 }
1274 return [r * 255, g * 255, b * 255]
1275 }
1276
1277 function text(obj) {
1278 if (obj == null) {
1279 return null
1280 }
1281 let text = obj.text
1282 if (text != null) {
1283 return text
1284 }
1285 if (obj.children != null && obj.children.length > 0) {
1286 let textAll = ''
1287 for (let i = 0; i < obj.children.length; i++) {
1288 let p = obj.children[i]
1289 if (p.children == null) {
1290 continue
1291 }
1292 let pText = ''
1293 for (let j = 0; j < p.children.length; j++) {
1294 let r = p.children[j]
1295 if (r.text == null) {
1296 continue
1297 }
1298 pText += r.text
1299 }
1300 if (pText) {
1301 textAll += (pText + '\n')
1302 }
1303 }
1304 if (textAll.length > 0) {
1305 textAll = textAll.substring(0, textAll.length - 1)
1306 }
1307 return textAll
1308 }
1309 return ''
1310 }
1311
1312 this.setTemplateHandle = (_templateHandle) => {
1313 templateHandle = _templateHandle
1314 }
1315
1316 this.idMap = idMap
1317 this.recursion = recursion
1318 this.drawGeometry = drawGeometry
1319 this.drawElement = drawElement
1320 this.toPaint = toPaint
1321 this.toColor = toColor
1322 this.toColorValue = toColorValue
1323 this.loadImage = loadImage
1324 this.text = text
1325
1326 }
1327
1328 // export { Ppt2Canvas }
1328 lines JAVASCRIPT