pptxtojson 0.0.13 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/pptxtojson.js CHANGED
@@ -1,76 +1,49 @@
1
1
  import JSZip from 'jszip'
2
- import * as txml from 'txml/dist/txml.mjs'
3
- import tinycolor from 'tinycolor2'
2
+ import { readXmlFile } from './readXmlFile'
3
+ import { getBorder } from './border'
4
+ import { getSlideBackgroundFill, getShapeFill } from './fill'
5
+ import { getChartInfo } from './chart'
6
+ import { getVerticalAlign } from './align'
7
+ import { getPosition, getSize } from './position'
8
+ import { genTextBody } from './text'
9
+ import { getCustomShapePath } from './shape'
10
+ import { extractFileExtension, base64ArrayBuffer, getTextByPathList, angleToDegrees, getMimeType, isVideoLink, escapeHtml } from './utils'
11
+ import { getShadow } from './shadow'
4
12
 
5
- import {
6
- extractFileExtension,
7
- base64ArrayBuffer,
8
- eachElement,
9
- getTextByPathList,
10
- angleToDegrees,
11
- escapeHtml,
12
- getMimeType,
13
- } from './utils'
13
+ let SLIDE_FACTOR = 96 / 914400
14
+ let FONTSIZE_FACTOR = 100 / 75
14
15
 
15
- const FACTOR = 75 / 914400
16
+ const defaultOptions = {
17
+ slideFactor: SLIDE_FACTOR,
18
+ fontsizeFactor: FONTSIZE_FACTOR,
19
+ }
20
+
21
+ export async function parse(file, options = {}) {
22
+ options = { ...defaultOptions, ...options }
16
23
 
17
- let themeContent = null
18
- let defaultTextStyle = null
24
+ if (options.slideFactor) SLIDE_FACTOR = options.slideFactor
25
+ if (options.fontsizeFactor) FONTSIZE_FACTOR = options.fontsizeFactor
19
26
 
20
- export async function parse(file) {
21
27
  const slides = []
22
28
 
23
29
  const zip = await JSZip.loadAsync(file)
24
30
 
25
31
  const filesInfo = await getContentTypes(zip)
26
- const { width, height, defaultTextStyle: _defaultTextStyle } = await getSlideSize(zip)
27
- themeContent = await loadTheme(zip)
28
- defaultTextStyle = _defaultTextStyle
32
+ const { width, height, defaultTextStyle } = await getSlideInfo(zip)
33
+ const themeContent = await loadTheme(zip)
29
34
 
30
35
  for (const filename of filesInfo.slides) {
31
- const singleSlide = await processSingleSlide(zip, filename)
36
+ const singleSlide = await processSingleSlide(zip, filename, themeContent, defaultTextStyle)
32
37
  slides.push(singleSlide)
33
38
  }
34
39
 
35
40
  return {
36
41
  slides,
37
- size: { width, height },
38
- }
39
- }
40
-
41
- function simplifyLostLess(children, parentAttributes = {}) {
42
- const out = {}
43
- if (!children.length) return out
44
-
45
- if (children.length === 1 && typeof children[0] === 'string') {
46
- return Object.keys(parentAttributes).length ? {
47
- attrs: parentAttributes,
48
- value: children[0],
49
- } : children[0]
50
- }
51
- for (const child of children) {
52
- if (typeof child !== 'object') return
53
- if (child.tagName === '?xml') continue
54
-
55
- if (!out[child.tagName]) out[child.tagName] = []
56
-
57
- const kids = simplifyLostLess(child.children || [], child.attributes)
58
- out[child.tagName].push(kids)
59
-
60
- if (Object.keys(child.attributes).length) {
61
- kids.attrs = child.attributes
62
- }
63
- }
64
- for (const child in out) {
65
- if (out[child].length === 1) out[child] = out[child][0]
42
+ size: {
43
+ width,
44
+ height,
45
+ },
66
46
  }
67
-
68
- return out
69
- }
70
-
71
- async function readXmlFile(zip, filename) {
72
- const data = await zip.file(filename).async('string')
73
- return simplifyLostLess(txml.parse(data))
74
47
  }
75
48
 
76
49
  async function getContentTypes(zip) {
@@ -105,13 +78,13 @@ async function getContentTypes(zip) {
105
78
  }
106
79
  }
107
80
 
108
- async function getSlideSize(zip) {
81
+ async function getSlideInfo(zip) {
109
82
  const content = await readXmlFile(zip, 'ppt/presentation.xml')
110
83
  const sldSzAttrs = content['p:presentation']['p:sldSz']['attrs']
111
84
  const defaultTextStyle = content['p:presentation']['p:defaultTextStyle']
112
85
  return {
113
- width: parseInt(sldSzAttrs['cx']) * FACTOR,
114
- height: parseInt(sldSzAttrs['cy']) * FACTOR,
86
+ width: parseInt(sldSzAttrs['cx']) * SLIDE_FACTOR,
87
+ height: parseInt(sldSzAttrs['cy']) * SLIDE_FACTOR,
115
88
  defaultTextStyle,
116
89
  }
117
90
  }
@@ -132,17 +105,17 @@ async function loadTheme(zip) {
132
105
  else if (relationshipArray['attrs']['Type'] === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme') {
133
106
  themeURI = relationshipArray['attrs']['Target']
134
107
  }
135
-
136
108
  if (!themeURI) throw Error(`Can't open theme file.`)
137
109
 
138
110
  return await readXmlFile(zip, 'ppt/' + themeURI)
139
111
  }
140
112
 
141
- async function processSingleSlide(zip, sldFileName) {
113
+ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextStyle) {
142
114
  const resName = sldFileName.replace('slides/slide', 'slides/_rels/slide') + '.rels'
143
115
  const resContent = await readXmlFile(zip, resName)
144
116
  let relationshipArray = resContent['Relationships']['Relationship']
145
117
  let layoutFilename = ''
118
+ let diagramFilename = ''
146
119
  const slideResObj = {}
147
120
 
148
121
  if (relationshipArray.constructor === Array) {
@@ -152,6 +125,7 @@ async function processSingleSlide(zip, sldFileName) {
152
125
  layoutFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
153
126
  break
154
127
  case 'http://schemas.microsoft.com/office/2007/relationships/diagramDrawing':
128
+ diagramFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
155
129
  slideResObj[relationshipArrayItem['attrs']['Id']] = {
156
130
  type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
157
131
  target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
@@ -219,9 +193,7 @@ async function processSingleSlide(zip, sldFileName) {
219
193
  }
220
194
  }
221
195
  }
222
- else {
223
- themeFilename = relationshipArray['attrs']['Target'].replace('../', 'ppt/')
224
- }
196
+ else themeFilename = relationshipArray['attrs']['Target'].replace('../', 'ppt/')
225
197
 
226
198
  const themeResObj = {}
227
199
  if (themeFilename) {
@@ -249,6 +221,37 @@ async function processSingleSlide(zip, sldFileName) {
249
221
  }
250
222
  }
251
223
 
224
+ const diagramResObj = {}
225
+ let digramFileContent = {}
226
+ if (diagramFilename) {
227
+ const diagName = diagramFilename.split('/').pop()
228
+ const diagramResFileName = diagramFilename.replace(diagName, '_rels/' + diagName) + '.rels'
229
+ digramFileContent = await readXmlFile(zip, diagramFilename)
230
+ if (digramFileContent && digramFileContent && digramFileContent) {
231
+ let digramFileContentObjToStr = JSON.stringify(digramFileContent)
232
+ digramFileContentObjToStr = digramFileContentObjToStr.replace(/dsp:/g, 'p:')
233
+ digramFileContent = JSON.parse(digramFileContentObjToStr)
234
+ }
235
+ const digramResContent = await readXmlFile(zip, diagramResFileName)
236
+ if (digramResContent) {
237
+ relationshipArray = digramResContent['Relationships']['Relationship']
238
+ if (relationshipArray.constructor === Array) {
239
+ for (const relationshipArrayItem of relationshipArray) {
240
+ diagramResObj[relationshipArrayItem['attrs']['Id']] = {
241
+ 'type': relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
242
+ 'target': relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
243
+ }
244
+ }
245
+ }
246
+ else {
247
+ diagramResObj[relationshipArray['attrs']['Id']] = {
248
+ 'type': relationshipArray['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
249
+ 'target': relationshipArray['attrs']['Target'].replace('../', 'ppt/')
250
+ }
251
+ }
252
+ }
253
+ }
254
+
252
255
  const slideContent = await readXmlFile(zip, sldFileName)
253
256
  const nodes = slideContent['p:sld']['p:cSld']['p:spTree']
254
257
  const warpObj = {
@@ -264,6 +267,8 @@ async function processSingleSlide(zip, sldFileName) {
264
267
  masterResObj: masterResObj,
265
268
  themeContent: themeContent,
266
269
  themeResObj: themeResObj,
270
+ digramFileContent: digramFileContent,
271
+ diagramResObj: diagramResObj,
267
272
  defaultTextStyle: defaultTextStyle,
268
273
  }
269
274
  const bgColor = await getSlideBackgroundFill(warpObj)
@@ -289,10 +294,8 @@ async function processSingleSlide(zip, sldFileName) {
289
294
  }
290
295
 
291
296
  function indexNodes(content) {
292
-
293
297
  const keys = Object.keys(content)
294
298
  const spTreeNode = content[keys[0]]['p:cSld']['p:spTree']
295
-
296
299
  const idTable = {}
297
300
  const idxTable = {}
298
301
  const typeTable = {}
@@ -336,10 +339,10 @@ async function processNodesInSlide(nodeKey, nodeValue, warpObj) {
336
339
  case 'p:sp': // Shape, Text
337
340
  json = processSpNode(nodeValue, warpObj)
338
341
  break
339
- case 'p:cxnSp': // Shape, Text (with connection)
342
+ case 'p:cxnSp': // Shape, Text
340
343
  json = processCxnSpNode(nodeValue, warpObj)
341
344
  break
342
- case 'p:pic': // Picture
345
+ case 'p:pic': // Image, Video, Audio
343
346
  json = processPicNode(nodeValue, warpObj)
344
347
  break
345
348
  case 'p:graphicFrame': // Chart, Diagram, Table
@@ -358,15 +361,17 @@ async function processNodesInSlide(nodeKey, nodeValue, warpObj) {
358
361
  }
359
362
 
360
363
  async function processGroupSpNode(node, warpObj) {
361
- const xfrmNode = node['p:grpSpPr']['a:xfrm']
362
- const x = parseInt(xfrmNode['a:off']['attrs']['x']) * FACTOR
363
- const y = parseInt(xfrmNode['a:off']['attrs']['y']) * FACTOR
364
- const chx = parseInt(xfrmNode['a:chOff']['attrs']['x']) * FACTOR
365
- const chy = parseInt(xfrmNode['a:chOff']['attrs']['y']) * FACTOR
366
- const cx = parseInt(xfrmNode['a:ext']['attrs']['cx']) * FACTOR
367
- const cy = parseInt(xfrmNode['a:ext']['attrs']['cy']) * FACTOR
368
- const chcx = parseInt(xfrmNode['a:chExt']['attrs']['cx']) * FACTOR
369
- const chcy = parseInt(xfrmNode['a:chExt']['attrs']['cy']) * FACTOR
364
+ const xfrmNode = getTextByPathList(node, ['p:grpSpPr', 'a:xfrm'])
365
+ if (!xfrmNode) return null
366
+
367
+ const x = parseInt(xfrmNode['a:off']['attrs']['x']) * SLIDE_FACTOR
368
+ const y = parseInt(xfrmNode['a:off']['attrs']['y']) * SLIDE_FACTOR
369
+ const chx = parseInt(xfrmNode['a:chOff']['attrs']['x']) * SLIDE_FACTOR
370
+ const chy = parseInt(xfrmNode['a:chOff']['attrs']['y']) * SLIDE_FACTOR
371
+ const cx = parseInt(xfrmNode['a:ext']['attrs']['cx']) * SLIDE_FACTOR
372
+ const cy = parseInt(xfrmNode['a:ext']['attrs']['cy']) * SLIDE_FACTOR
373
+ const chcx = parseInt(xfrmNode['a:chExt']['attrs']['cx']) * SLIDE_FACTOR
374
+ const chcy = parseInt(xfrmNode['a:chExt']['attrs']['cy']) * SLIDE_FACTOR
370
375
 
371
376
  const elements = []
372
377
  for (const nodeKey in node) {
@@ -392,11 +397,11 @@ async function processGroupSpNode(node, warpObj) {
392
397
  }
393
398
  }
394
399
 
395
- function processSpNode(node, warpObj) {
396
- const id = node['p:nvSpPr']['p:cNvPr']['attrs']['id']
397
- const name = node['p:nvSpPr']['p:cNvPr']['attrs']['name']
398
- const idx = node['p:nvSpPr']['p:nvPr']['p:ph'] ? node['p:nvSpPr']['p:nvPr']['p:ph']['attrs']['idx'] : undefined
399
- let type = node['p:nvSpPr']['p:nvPr']['p:ph'] ? node['p:nvSpPr']['p:nvPr']['p:ph']['attrs']['type'] : undefined
400
+ function processSpNode(node, warpObj, source) {
401
+ const id = getTextByPathList(node, ['p:nvSpPr', 'p:cNvPr', 'attrs', 'id'])
402
+ const name = getTextByPathList(node, ['p:nvSpPr', 'p:cNvPr', 'attrs', 'name'])
403
+ const idx = getTextByPathList(node, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'idx'])
404
+ let type = getTextByPathList(node, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
400
405
 
401
406
  let slideLayoutSpNode, slideMasterSpNode
402
407
 
@@ -422,6 +427,11 @@ function processSpNode(node, warpObj) {
422
427
  if (!type) type = getTextByPathList(slideLayoutSpNode, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
423
428
  if (!type) type = getTextByPathList(slideMasterSpNode, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
424
429
 
430
+ if (!type) {
431
+ if (source === 'diagramBg') type = 'diagram'
432
+ else type = 'obj'
433
+ }
434
+
425
435
  return genShape(node, slideLayoutSpNode, slideMasterSpNode, id, name, idx, type, warpObj)
426
436
  }
427
437
 
@@ -434,51 +444,6 @@ function processCxnSpNode(node, warpObj) {
434
444
  return genShape(node, undefined, undefined, id, name, idx, type, warpObj)
435
445
  }
436
446
 
437
- function shapeArc(cX, cY, rX, rY, stAng, endAng, isClose) {
438
- let dData
439
- let angle = stAng
440
- if (endAng >= stAng) {
441
- while (angle <= endAng) {
442
- const radians = angle * (Math.PI / 180)
443
- const x = cX + Math.cos(radians) * rX
444
- const y = cY + Math.sin(radians) * rY
445
- if (angle === stAng) {
446
- dData = ' M' + x + ' ' + y
447
- }
448
- dData += ' L' + x + ' ' + y
449
- angle++
450
- }
451
- }
452
- else {
453
- while (angle > endAng) {
454
- const radians = angle * (Math.PI / 180)
455
- const x = cX + Math.cos(radians) * rX
456
- const y = cY + Math.sin(radians) * rY
457
- if (angle === stAng) {
458
- dData = ' M ' + x + ' ' + y
459
- }
460
- dData += ' L ' + x + ' ' + y
461
- angle--
462
- }
463
- }
464
- dData += (isClose ? ' z' : '')
465
- return dData
466
- }
467
-
468
- function getVerticalAlign(node, slideLayoutSpNode, slideMasterSpNode) {
469
- let anchor = getTextByPathList(node, ['p:txBody', 'a:bodyPr', 'attrs', 'anchor'])
470
- if (anchor) {
471
- anchor = getTextByPathList(slideLayoutSpNode, ['p:txBody', 'a:bodyPr', 'attrs', 'anchor'])
472
- if (anchor) {
473
- anchor = getTextByPathList(slideMasterSpNode, ['p:txBody', 'a:bodyPr', 'attrs', 'anchor'])
474
- if (anchor) {
475
- anchor = 't'
476
- }
477
- }
478
- }
479
- return (anchor === 'ctr') ? 'mid' : ((anchor === 'b') ? 'down' : 'up')
480
- }
481
-
482
447
  function genShape(node, slideLayoutSpNode, slideMasterSpNode, id, name, idx, type, warpObj) {
483
448
  const xfrmList = ['p:spPr', 'a:xfrm']
484
449
  const slideXfrmNode = getTextByPathList(node, xfrmList)
@@ -488,17 +453,11 @@ function genShape(node, slideLayoutSpNode, slideMasterSpNode, id, name, idx, typ
488
453
  const shapType = getTextByPathList(node, ['p:spPr', 'a:prstGeom', 'attrs', 'prst'])
489
454
  const custShapType = getTextByPathList(node, ['p:spPr', 'a:custGeom'])
490
455
 
491
- const { top, left } = getPosition(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode)
492
- const { width, height } = getSize(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode)
456
+ const { top, left } = getPosition(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode, SLIDE_FACTOR)
457
+ const { width, height } = getSize(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode, SLIDE_FACTOR)
493
458
 
494
- let isFlipV = false
495
- let isFlipH = false
496
- if (getTextByPathList(slideXfrmNode, ['attrs', 'flipV']) === '1') {
497
- isFlipV = true
498
- }
499
- if (getTextByPathList(slideXfrmNode, ['attrs', 'flipH']) === '1') {
500
- isFlipH = true
501
- }
459
+ const isFlipV = getTextByPathList(slideXfrmNode, ['attrs', 'flipV']) === '1'
460
+ const isFlipH = getTextByPathList(slideXfrmNode, ['attrs', 'flipH']) === '1'
502
461
 
503
462
  const rotate = angleToDegrees(getTextByPathList(slideXfrmNode, ['attrs', 'rot']))
504
463
 
@@ -511,273 +470,176 @@ function genShape(node, slideLayoutSpNode, slideMasterSpNode, id, name, idx, typ
511
470
  else txtRotate = rotate
512
471
 
513
472
  let content = ''
514
- if (node['p:txBody']) content = genTextBody(node['p:txBody'], slideLayoutSpNode, slideMasterSpNode, type, warpObj)
473
+ if (node['p:txBody']) content = genTextBody(node['p:txBody'], slideLayoutSpNode, slideMasterSpNode, type, warpObj, FONTSIZE_FACTOR, SLIDE_FACTOR)
515
474
 
516
- const { borderColor, borderWidth, borderType } = getBorder(node, type)
517
- const fillColor = getShapeFill(node) || ''
475
+ const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(node, type, warpObj)
476
+ const fillColor = getShapeFill(node, undefined, warpObj) || ''
518
477
 
519
- const vAlign = getVerticalAlign(node, slideLayoutSpNode, slideMasterSpNode, type)
478
+ let shadow
479
+ const outerShdwNode = getTextByPathList(node, ['p:spPr', 'a:effectLst', 'a:outerShdw'])
480
+ if (outerShdwNode) shadow = getShadow(outerShdwNode, warpObj, SLIDE_FACTOR)
520
481
 
521
- if (custShapType) {
522
- const pathLstNode = getTextByPathList(custShapType, ['a:pathLst'])
523
- const pathNodes = getTextByPathList(pathLstNode, ['a:path'])
524
- const maxX = parseInt(pathNodes['attrs']['w'])
525
- const maxY = parseInt(pathNodes['attrs']['h'])
526
- const ext = getTextByPathList(slideXfrmNode, ['a:ext', 'attrs'])
527
- const cx = parseInt(ext['cx']) * FACTOR
528
- const cy = parseInt(ext['cy']) * FACTOR
529
- const w = parseInt(ext['cx']) * FACTOR
530
- const h = parseInt(ext['cy']) * FACTOR
531
- const cX = (1 / maxX) * w
532
- const cY = (1 / maxY) * h
533
- let d = ''
534
-
535
- let moveToNode = getTextByPathList(pathNodes, ['a:moveTo'])
536
-
537
- const lnToNodes = pathNodes['a:lnTo']
538
- let cubicBezToNodes = pathNodes['a:cubicBezTo']
539
- const arcToNodes = pathNodes['a:arcTo']
540
- let closeNode = getTextByPathList(pathNodes, ['a:close'])
541
- if (!Array.isArray(moveToNode)) {
542
- moveToNode = [moveToNode]
543
- }
544
-
545
- const multiSapeAry = []
546
- if (moveToNode.length > 0) {
547
- // a:moveTo
548
- Object.keys(moveToNode).forEach(key => {
549
- const moveToPtNode = moveToNode[key]['a:pt']
550
- if (moveToPtNode) {
551
- Object.keys(moveToPtNode).forEach(key2 => {
552
- const moveToNoPt = moveToPtNode[key2]
553
- const spX = moveToNoPt['attrs', 'x']
554
- const spY = moveToNoPt['attrs', 'y']
555
-
556
- multiSapeAry.push({
557
- type: 'movto',
558
- x: spX,
559
- y: spY,
560
- })
561
- })
562
- }
563
- })
564
- // a:lnTo
565
- if (lnToNodes) {
566
- Object.keys(lnToNodes).forEach(key => {
567
- const lnToPtNode = lnToNodes[key]['a:pt']
568
- if (lnToPtNode) {
569
- Object.keys(lnToPtNode).forEach(key2 => {
570
- const lnToNoPt = lnToPtNode[key2]
571
- const ptX = lnToNoPt['attrs', 'x']
572
- const ptY = lnToNoPt['attrs', 'y']
573
- multiSapeAry.push({
574
- type: 'lnto',
575
- x: ptX,
576
- y: ptY,
577
- })
578
- })
579
- }
580
- })
581
- }
582
- // a:cubicBezTo
583
- if (cubicBezToNodes) {
584
-
585
- const cubicBezToPtNodesAry = []
586
- if (!Array.isArray(cubicBezToNodes)) {
587
- cubicBezToNodes = [cubicBezToNodes]
588
- }
589
- Object.keys(cubicBezToNodes).forEach(key => {
590
- cubicBezToPtNodesAry.push(cubicBezToNodes[key]['a:pt'])
591
- })
592
-
593
- cubicBezToPtNodesAry.forEach(key2 => {
594
- const pts_ary = []
595
- key2.forEach(pt => {
596
- const pt_obj = {
597
- x: pt['attrs']['x'],
598
- y: pt['attrs']['y'],
599
- }
600
- pts_ary.push(pt_obj)
601
- })
602
- multiSapeAry.push({
603
- type: 'cubicBezTo',
604
- cubBzPt: pts_ary
605
- })
606
- })
607
- }
608
- // a:arcTo
609
- if (arcToNodes) {
610
- const arcToNodesAttrs = arcToNodes['attrs']
611
- const hR = arcToNodesAttrs['hR']
612
- const wR = arcToNodesAttrs['wR']
613
- const stAng = arcToNodesAttrs['stAng']
614
- const swAng = arcToNodesAttrs['swAng']
615
- let shftX = 0
616
- let shftY = 0
617
- const arcToPtNode = getTextByPathList(arcToNodes, ['a:pt', 'attrs'])
618
- if (arcToPtNode) {
619
- shftX = arcToPtNode['x']
620
- shftY = arcToPtNode['y']
621
- }
622
-
623
- multiSapeAry.push({
624
- type: 'arcTo',
625
- hR: hR,
626
- wR: wR,
627
- stAng: stAng,
628
- swAng: swAng,
629
- shftX: shftX,
630
- shftY: shftY,
631
- })
482
+ const vAlign = getVerticalAlign(node, slideLayoutSpNode, slideMasterSpNode, type)
483
+ const isVertical = getTextByPathList(node, ['p:txBody', 'a:bodyPr', 'attrs', 'vert']) === 'eaVert'
632
484
 
633
- }
634
- // a:close
635
- if (closeNode) {
636
- if (!Array.isArray(closeNode)) closeNode = [closeNode]
637
- Object.keys(closeNode).forEach(() => {
638
- multiSapeAry.push({
639
- type: 'close',
640
- })
641
- })
642
- }
485
+ const data = {
486
+ left,
487
+ top,
488
+ width,
489
+ height,
490
+ borderColor,
491
+ borderWidth,
492
+ borderType,
493
+ borderStrokeDasharray: strokeDasharray,
494
+ fillColor,
495
+ content,
496
+ isFlipV,
497
+ isFlipH,
498
+ rotate,
499
+ vAlign,
500
+ id,
501
+ name,
502
+ idx,
503
+ }
643
504
 
644
- let k = 0
645
- while (k < multiSapeAry.length) {
505
+ if (shadow) data.shadow = shadow
646
506
 
647
- if (multiSapeAry[k].type === 'movto') {
648
- const spX = parseInt(multiSapeAry[k].x) * cX
649
- const spY = parseInt(multiSapeAry[k].y) * cY
650
- d += ' M' + spX + ',' + spY
651
- }
652
- else if (multiSapeAry[k].type === 'lnto') {
653
- const Lx = parseInt(multiSapeAry[k].x) * cX
654
- const Ly = parseInt(multiSapeAry[k].y) * cY
655
- d += ' L' + Lx + ',' + Ly
656
- }
657
- else if (multiSapeAry[k].type === 'cubicBezTo') {
658
- const Cx1 = parseInt(multiSapeAry[k].cubBzPt[0].x) * cX
659
- const Cy1 = parseInt(multiSapeAry[k].cubBzPt[0].y) * cY
660
- const Cx2 = parseInt(multiSapeAry[k].cubBzPt[1].x) * cX
661
- const Cy2 = parseInt(multiSapeAry[k].cubBzPt[1].y) * cY
662
- const Cx3 = parseInt(multiSapeAry[k].cubBzPt[2].x) * cX
663
- const Cy3 = parseInt(multiSapeAry[k].cubBzPt[2].y) * cY
664
- d += ' C' + Cx1 + ',' + Cy1 + ' ' + Cx2 + ',' + Cy2 + ' ' + Cx3 + ',' + Cy3
665
- }
666
- else if (multiSapeAry[k].type === 'arcTo') {
667
- const hR = parseInt(multiSapeAry[k].hR) * cX
668
- const wR = parseInt(multiSapeAry[k].wR) * cY
669
- const stAng = parseInt(multiSapeAry[k].stAng) / 60000
670
- const swAng = parseInt(multiSapeAry[k].swAng) / 60000
671
- const endAng = stAng + swAng
672
- d += shapeArc(wR, hR, wR, hR, stAng, endAng, false)
673
- }
674
- else if (multiSapeAry[k].type === 'close') {
675
- d += 'z'
676
- }
677
- k++
678
- }
679
- }
507
+ if (custShapType && type !== 'diagram') {
508
+ const ext = getTextByPathList(slideXfrmNode, ['a:ext', 'attrs'])
509
+ const cx = parseInt(ext['cx']) * SLIDE_FACTOR
510
+ const cy = parseInt(ext['cy']) * SLIDE_FACTOR
511
+ const w = parseInt(ext['cx']) * SLIDE_FACTOR
512
+ const h = parseInt(ext['cy']) * SLIDE_FACTOR
513
+ const d = getCustomShapePath(custShapType, w, h)
680
514
 
681
515
  return {
516
+ ...data,
682
517
  type: 'shape',
683
- left,
684
- top,
685
- width,
686
- height,
687
518
  cx,
688
519
  cy,
689
- borderColor,
690
- borderWidth,
691
- borderType,
692
- fillColor,
693
- content,
694
- isFlipV,
695
- isFlipH,
696
- rotate,
697
520
  shapType: 'custom',
698
- vAlign,
699
521
  path: d,
700
- id,
701
- name,
702
- idx,
703
522
  }
704
523
  }
705
-
706
- if (shapType) {
524
+ if (shapType && type !== 'text') {
707
525
  const ext = getTextByPathList(slideXfrmNode, ['a:ext', 'attrs'])
708
- const cx = parseInt(ext['cx']) * FACTOR
709
- const cy = parseInt(ext['cy']) * FACTOR
526
+ const cx = parseInt(ext['cx']) * SLIDE_FACTOR
527
+ const cy = parseInt(ext['cy']) * SLIDE_FACTOR
710
528
 
711
529
  return {
712
- type: type === 'text' ? 'text' : 'shape',
713
- left,
714
- top,
715
- width,
716
- height,
530
+ ...data,
531
+ type: 'shape',
717
532
  cx,
718
533
  cy,
719
- borderColor,
720
- borderWidth,
721
- borderType,
722
- fillColor,
723
- content,
724
- isFlipV,
725
- isFlipH,
726
- rotate,
727
534
  shapType,
728
- vAlign,
729
- id,
730
- name,
731
- idx,
732
535
  }
733
536
  }
734
-
735
537
  return {
538
+ ...data,
736
539
  type: 'text',
737
- left,
738
- top,
739
- width,
740
- height,
741
- borderColor,
742
- borderWidth,
743
- borderType,
744
- fillColor,
745
- isFlipV,
746
- isFlipH,
540
+ isVertical,
747
541
  rotate: txtRotate,
748
- content,
749
- vAlign,
750
- id,
751
- name,
752
- idx,
753
542
  }
754
543
  }
755
544
 
756
- async function processPicNode(node, warpObj) {
545
+ async function processPicNode(node, warpObj, source) {
546
+ let resObj
547
+ if (source === 'slideMasterBg') resObj = warpObj['masterResObj']
548
+ else if (source === 'slideLayoutBg') resObj = warpObj['layoutResObj']
549
+ else resObj = warpObj['slideResObj']
550
+
757
551
  const rid = node['p:blipFill']['a:blip']['attrs']['r:embed']
758
- const imgName = warpObj['slideResObj'][rid]['target']
552
+ const imgName = resObj[rid]['target']
759
553
  const imgFileExt = extractFileExtension(imgName).toLowerCase()
760
554
  const zip = warpObj['zip']
761
555
  const imgArrayBuffer = await zip.file(imgName).async('arraybuffer')
762
556
  const xfrmNode = node['p:spPr']['a:xfrm']
763
557
 
764
558
  const mimeType = getMimeType(imgFileExt)
765
- const { top, left } = getPosition(xfrmNode, undefined, undefined)
766
- const { width, height } = getSize(xfrmNode, undefined, undefined)
559
+ const { top, left } = getPosition(xfrmNode, undefined, undefined, SLIDE_FACTOR)
560
+ const { width, height } = getSize(xfrmNode, undefined, undefined, SLIDE_FACTOR)
767
561
  const src = `data:${mimeType};base64,${base64ArrayBuffer(imgArrayBuffer)}`
768
562
 
769
563
  let rotate = 0
770
564
  const rotateNode = getTextByPathList(node, ['p:spPr', 'a:xfrm', 'attrs', 'rot'])
771
565
  if (rotateNode) rotate = angleToDegrees(rotateNode)
772
566
 
567
+ const videoNode = getTextByPathList(node, ['p:nvPicPr', 'p:nvPr', 'a:videoFile'])
568
+ let videoRid, videoFile, videoFileExt, videoMimeType, uInt8ArrayVideo, videoBlob
569
+ let isVdeoLink = false
570
+
571
+ if (videoNode) {
572
+ videoRid = videoNode['attrs']['r:link']
573
+ videoFile = resObj[videoRid]['target']
574
+ if (isVideoLink(videoFile)) {
575
+ videoFile = escapeHtml(videoFile)
576
+ isVdeoLink = true
577
+ }
578
+ else {
579
+ videoFileExt = extractFileExtension(videoFile).toLowerCase()
580
+ if (videoFileExt === 'mp4' || videoFileExt === 'webm' || videoFileExt === 'ogg') {
581
+ uInt8ArrayVideo = await zip.file(videoFile).async('arraybuffer')
582
+ videoMimeType = getMimeType(videoFileExt)
583
+ videoBlob = URL.createObjectURL(new Blob([uInt8ArrayVideo], {
584
+ type: videoMimeType
585
+ }))
586
+ }
587
+ }
588
+ }
589
+
590
+ const audioNode = getTextByPathList(node, ['p:nvPicPr', 'p:nvPr', 'a:audioFile'])
591
+ let audioRid, audioFile, audioFileExt, uInt8ArrayAudio, audioBlob
592
+ if (audioNode) {
593
+ audioRid = audioNode['attrs']['r:link']
594
+ audioFile = resObj[audioRid]['target']
595
+ audioFileExt = extractFileExtension(audioFile).toLowerCase()
596
+ if (audioFileExt === 'mp3' || audioFileExt === 'wav' || audioFileExt === 'ogg') {
597
+ uInt8ArrayAudio = await zip.file(audioFile).async('arraybuffer')
598
+ audioBlob = URL.createObjectURL(new Blob([uInt8ArrayAudio]))
599
+ }
600
+ }
601
+
602
+ if (videoNode && !isVdeoLink) {
603
+ return {
604
+ type: 'video',
605
+ top,
606
+ left,
607
+ width,
608
+ height,
609
+ rotate,
610
+ blob: videoBlob,
611
+ }
612
+ }
613
+ if (videoNode && isVdeoLink) {
614
+ return {
615
+ type: 'video',
616
+ top,
617
+ left,
618
+ width,
619
+ height,
620
+ rotate,
621
+ src: videoFile,
622
+ }
623
+ }
624
+ if (audioNode) {
625
+ return {
626
+ type: 'audio',
627
+ top,
628
+ left,
629
+ width,
630
+ height,
631
+ rotate,
632
+ blob: audioBlob,
633
+ }
634
+ }
773
635
  return {
774
636
  type: 'image',
775
637
  top,
776
638
  left,
777
639
  width,
778
640
  height,
779
- src,
780
641
  rotate,
642
+ src,
781
643
  }
782
644
  }
783
645
 
@@ -805,121 +667,11 @@ async function processGraphicFrameNode(node, warpObj) {
805
667
  return result
806
668
  }
807
669
 
808
- function genTextBody(textBodyNode, slideLayoutSpNode, slideMasterSpNode, type, warpObj) {
809
- if (!textBodyNode) return ''
810
-
811
- let text = ''
812
- const slideMasterTextStyles = warpObj['slideMasterTextStyles']
813
-
814
- const pNode = textBodyNode['a:p']
815
- const pNodes = pNode.constructor === Array ? pNode : [pNode]
816
-
817
- let isList = ''
818
-
819
- for (const pNode of pNodes) {
820
- let rNode = pNode['a:r']
821
- let fldNode = pNode['a:fld']
822
- let brNode = pNode['a:br']
823
- if (rNode) {
824
- rNode = (rNode.constructor === Array) ? rNode : [rNode]
825
-
826
- if (fldNode) {
827
- fldNode = (fldNode.constructor === Array) ? fldNode : [fldNode]
828
- rNode = rNode.concat(fldNode)
829
- }
830
- if (brNode) {
831
- brNode = (brNode.constructor === Array) ? brNode : [brNode]
832
- brNode.forEach(item => item.type = 'br')
833
-
834
- if (brNode.length > 1) brNode.shift()
835
- rNode = rNode.concat(brNode)
836
- rNode.sort((a, b) => {
837
- if (!a.attrs || !b.attrs) return true
838
- return a.attrs.order - b.attrs.order
839
- })
840
- }
841
- }
842
-
843
- const align = getHorizontalAlign(pNode, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles)
844
-
845
- const listType = getListType(pNode)
846
- if (listType) {
847
- if (!isList) {
848
- text += `<${listType}>`
849
- isList = listType
850
- }
851
- else if (isList && isList !== listType) {
852
- text += `</${isList}>`
853
- text += `<${listType}>`
854
- isList = listType
855
- }
856
- text += `<li style="text-align: ${align};">`
857
- }
858
- else {
859
- if (isList) {
860
- text += `</${isList}>`
861
- isList = ''
862
- }
863
- text += `<p style="text-align: ${align};">`
864
- }
865
-
866
- if (!rNode) text += genSpanElement(pNode, slideLayoutSpNode, type, warpObj)
867
- else {
868
- for (const rNodeItem of rNode) {
869
- text += genSpanElement(rNodeItem, slideLayoutSpNode, type, warpObj)
870
- }
871
- }
872
-
873
- if (listType) text += '</li>'
874
- else text += '</p>'
875
- }
876
- return text
877
- }
878
-
879
- function getListType(node) {
880
- const pPrNode = node['a:pPr']
881
- if (!pPrNode) return ''
882
-
883
- if (pPrNode['a:buChar']) return 'ul'
884
- if (pPrNode['a:buAutoNum']) return 'ol'
885
-
886
- return ''
887
- }
888
-
889
- function genSpanElement(node, slideLayoutSpNode, type, warpObj) {
890
- const slideMasterTextStyles = warpObj['slideMasterTextStyles']
891
-
892
- let text = node['a:t']
893
- if (typeof text !== 'string') text = getTextByPathList(node, ['a:fld', 'a:t'])
894
- if (typeof text !== 'string') text = '&nbsp;'
895
-
896
- let styleText = ''
897
- const fontColor = getFontColor(node)
898
- const fontSize = getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles)
899
- const fontType = getFontType(node, type)
900
- const fontBold = getFontBold(node)
901
- const fontItalic = getFontItalic(node)
902
- const fontDecoration = getFontDecoration(node)
903
- if (fontColor) styleText += `color: ${fontColor};`
904
- if (fontSize) styleText += `font-size: ${fontSize};`
905
- if (fontType) styleText += `font-family: ${fontType};`
906
- if (fontBold) styleText += `font-weight: ${fontBold};`
907
- if (fontItalic) styleText += `font-style: ${fontItalic};`
908
- if (fontDecoration) styleText += `text-decoration: ${fontDecoration};`
909
-
910
- const linkID = getTextByPathList(node, ['a:rPr', 'a:hlinkClick', 'attrs', 'r:id'])
911
- if (linkID) {
912
- const linkURL = warpObj['slideResObj'][linkID]['target']
913
- return `<span style="${styleText}"><a href="${linkURL}" target="_blank">${text.replace(/\s/i, '&nbsp;')}</a></span>`
914
- }
915
- return `<span style="${styleText}">${text.replace(/\s/i, '&nbsp;')}</span>`
916
- }
917
-
918
670
  function genTable(node, warpObj) {
919
671
  const tableNode = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'a:tbl'])
920
672
  const xfrmNode = getTextByPathList(node, ['p:xfrm'])
921
- const { top, left } = getPosition(xfrmNode, undefined, undefined)
922
- const { width, height } = getSize(xfrmNode, undefined, undefined)
673
+ const { top, left } = getPosition(xfrmNode, undefined, undefined, SLIDE_FACTOR)
674
+ const { width, height } = getSize(xfrmNode, undefined, undefined, SLIDE_FACTOR)
923
675
 
924
676
  const trNodes = tableNode['a:tr']
925
677
 
@@ -931,7 +683,7 @@ function genTable(node, warpObj) {
931
683
 
932
684
  if (tcNodes.constructor === Array) {
933
685
  for (const tcNode of tcNodes) {
934
- const text = genTextBody(tcNode['a:txBody'], undefined, undefined, undefined, warpObj)
686
+ const text = genTextBody(tcNode['a:txBody'], undefined, undefined, undefined, warpObj, FONTSIZE_FACTOR, SLIDE_FACTOR)
935
687
  const rowSpan = getTextByPathList(tcNode, ['attrs', 'rowSpan'])
936
688
  const colSpan = getTextByPathList(tcNode, ['attrs', 'gridSpan'])
937
689
  const vMerge = getTextByPathList(tcNode, ['attrs', 'vMerge'])
@@ -941,10 +693,9 @@ function genTable(node, warpObj) {
941
693
  }
942
694
  }
943
695
  else {
944
- const text = genTextBody(tcNodes['a:txBody'], undefined, undefined, undefined, warpObj)
696
+ const text = genTextBody(tcNodes['a:txBody'], undefined, undefined, undefined, warpObj, FONTSIZE_FACTOR, SLIDE_FACTOR)
945
697
  tr.push({ text })
946
698
  }
947
-
948
699
  data.push(tr)
949
700
  }
950
701
  }
@@ -954,12 +705,12 @@ function genTable(node, warpObj) {
954
705
 
955
706
  if (tcNodes.constructor === Array) {
956
707
  for (const tcNode of tcNodes) {
957
- const text = genTextBody(tcNode['a:txBody'], undefined, undefined, undefined, warpObj)
708
+ const text = genTextBody(tcNode['a:txBody'], undefined, undefined, undefined, warpObj, FONTSIZE_FACTOR, SLIDE_FACTOR)
958
709
  tr.push({ text })
959
710
  }
960
711
  }
961
712
  else {
962
- const text = genTextBody(tcNodes['a:txBody'], undefined, undefined, undefined, warpObj)
713
+ const text = genTextBody(tcNodes['a:txBody'], undefined, undefined, undefined, warpObj, FONTSIZE_FACTOR, SLIDE_FACTOR)
963
714
  tr.push({ text })
964
715
  }
965
716
  data.push(tr)
@@ -977,63 +728,19 @@ function genTable(node, warpObj) {
977
728
 
978
729
  async function genChart(node, warpObj) {
979
730
  const xfrmNode = getTextByPathList(node, ['p:xfrm'])
980
- const { top, left } = getPosition(xfrmNode, undefined, undefined)
981
- const { width, height } = getSize(xfrmNode, undefined, undefined)
731
+ const { top, left } = getPosition(xfrmNode, undefined, undefined, SLIDE_FACTOR)
732
+ const { width, height } = getSize(xfrmNode, undefined, undefined, SLIDE_FACTOR)
982
733
 
983
734
  const rid = node['a:graphic']['a:graphicData']['c:chart']['attrs']['r:id']
984
735
  const refName = warpObj['slideResObj'][rid]['target']
985
736
  const content = await readXmlFile(warpObj['zip'], refName)
986
737
  const plotArea = getTextByPathList(content, ['c:chartSpace', 'c:chart', 'c:plotArea'])
987
738
 
988
- let chart = null
989
- for (const key in plotArea) {
990
- switch (key) {
991
- case 'c:lineChart':
992
- chart = {
993
- type: 'lineChart',
994
- data: extractChartData(plotArea[key]['c:ser']),
995
- }
996
- break
997
- case 'c:barChart':
998
- chart = {
999
- type: getTextByPathList(plotArea[key], ['c:grouping', 'attrs', 'val']) === 'stacked' ? 'stackedBarChart' : 'barChart',
1000
- data: extractChartData(plotArea[key]['c:ser']),
1001
- }
1002
- break
1003
- case 'c:pieChart':
1004
- chart = {
1005
- type: 'pieChart',
1006
- data: extractChartData(plotArea[key]['c:ser']),
1007
- }
1008
- break
1009
- case 'c:pie3DChart':
1010
- chart = {
1011
- type: 'pie3DChart',
1012
- data: extractChartData(plotArea[key]['c:ser']),
1013
- }
1014
- break
1015
- case 'c:areaChart':
1016
- chart = {
1017
- type: getTextByPathList(plotArea[key], ['c:grouping', 'attrs', 'val']) === 'percentStacked' ? 'stackedAreaChart' : 'areaChart',
1018
- data: extractChartData(plotArea[key]['c:ser']),
1019
- }
1020
- break
1021
- case 'c:scatterChart':
1022
- chart = {
1023
- type: 'scatterChart',
1024
- data: extractChartData(plotArea[key]['c:ser']),
1025
- }
1026
- break
1027
- case 'c:catAx':
1028
- break
1029
- case 'c:valAx':
1030
- break
1031
- default:
1032
- }
1033
- }
739
+ const chart = getChartInfo(plotArea)
1034
740
 
1035
741
  if (!chart) return {}
1036
- return {
742
+
743
+ const data = {
1037
744
  type: 'chart',
1038
745
  top,
1039
746
  left,
@@ -1042,12 +749,28 @@ async function genChart(node, warpObj) {
1042
749
  data: chart.data,
1043
750
  chartType: chart.type,
1044
751
  }
752
+ if (chart.marker !== undefined) data.marker = chart.marker
753
+ if (chart.barDir !== undefined) data.barDir = chart.barDir
754
+ if (chart.holeSize !== undefined) data.holeSize = chart.holeSize
755
+ if (chart.grouping !== undefined) data.grouping = chart.grouping
756
+ if (chart.style !== undefined) data.style = chart.style
757
+
758
+ return data
1045
759
  }
1046
760
 
1047
- function genDiagram(node) {
761
+ function genDiagram(node, warpObj) {
1048
762
  const xfrmNode = getTextByPathList(node, ['p:xfrm'])
1049
- const { left, top } = getPosition(xfrmNode, undefined, undefined)
1050
- const { width, height } = getSize(xfrmNode, undefined, undefined)
763
+ const { left, top } = getPosition(xfrmNode, undefined, undefined, SLIDE_FACTOR)
764
+ const { width, height } = getSize(xfrmNode, undefined, undefined, SLIDE_FACTOR)
765
+
766
+ const dgmDrwSpArray = getTextByPathList(warpObj['digramFileContent'], ['p:drawing', 'p:spTree', 'p:sp'])
767
+ const elements = []
768
+ if (dgmDrwSpArray) {
769
+ for (const item of dgmDrwSpArray) {
770
+ const el = processSpNode(item, warpObj, 'diagramBg')
771
+ if (el) elements.push(el)
772
+ }
773
+ }
1051
774
 
1052
775
  return {
1053
776
  type: 'diagram',
@@ -1055,551 +778,6 @@ function genDiagram(node) {
1055
778
  top,
1056
779
  width,
1057
780
  height,
781
+ elements,
1058
782
  }
1059
- }
1060
-
1061
- function getPosition(slideSpNode, slideLayoutSpNode, slideMasterSpNode) {
1062
- let off
1063
-
1064
- if (slideSpNode) off = slideSpNode['a:off']['attrs']
1065
- else if (slideLayoutSpNode) off = slideLayoutSpNode['a:off']['attrs']
1066
- else if (slideMasterSpNode) off = slideMasterSpNode['a:off']['attrs']
1067
-
1068
- if (!off) return { top: 0, left: 0 }
1069
-
1070
- return {
1071
- top: parseInt(off['y']) * FACTOR,
1072
- left: parseInt(off['x']) * FACTOR,
1073
- }
1074
- }
1075
-
1076
- function getSize(slideSpNode, slideLayoutSpNode, slideMasterSpNode) {
1077
- let ext
1078
-
1079
- if (slideSpNode) ext = slideSpNode['a:ext']['attrs']
1080
- else if (slideLayoutSpNode) ext = slideLayoutSpNode['a:ext']['attrs']
1081
- else if (slideMasterSpNode) ext = slideMasterSpNode['a:ext']['attrs']
1082
-
1083
- if (!ext) return { width: 0, height: 0 }
1084
-
1085
- return {
1086
- width: parseInt(ext['cx']) * FACTOR,
1087
- height: parseInt(ext['cy']) * FACTOR,
1088
- }
1089
- }
1090
-
1091
- function getHorizontalAlign(node, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles) {
1092
- let algn = getTextByPathList(node, ['a:pPr', 'attrs', 'algn'])
1093
-
1094
- if (!algn) algn = getTextByPathList(slideLayoutSpNode, ['p:txBody', 'a:p', 'a:pPr', 'attrs', 'algn'])
1095
- if (!algn) algn = getTextByPathList(slideMasterSpNode, ['p:txBody', 'a:p', 'a:pPr', 'attrs', 'algn'])
1096
- if (!algn) {
1097
- switch (type) {
1098
- case 'title':
1099
- case 'subTitle':
1100
- case 'ctrTitle':
1101
- algn = getTextByPathList(slideMasterTextStyles, ['p:titleStyle', 'a:lvl1pPr', 'attrs', 'algn'])
1102
- break
1103
- default:
1104
- algn = getTextByPathList(slideMasterTextStyles, ['p:otherStyle', 'a:lvl1pPr', 'attrs', 'algn'])
1105
- }
1106
- }
1107
- if (!algn) {
1108
- if (type === 'title' || type === 'subTitle' || type === 'ctrTitle') return 'center'
1109
- else if (type === 'sldNum') return 'right'
1110
- }
1111
- return algn === 'ctr' ? 'center' : algn === 'r' ? 'right' : 'left'
1112
- }
1113
-
1114
- function getFontType(node, type) {
1115
- let typeface = getTextByPathList(node, ['a:rPr', 'a:latin', 'attrs', 'typeface'])
1116
-
1117
- if (!typeface) {
1118
- const fontSchemeNode = getTextByPathList(themeContent, ['a:theme', 'a:themeElements', 'a:fontScheme'])
1119
-
1120
- if (type === 'title' || type === 'subTitle' || type === 'ctrTitle') {
1121
- typeface = getTextByPathList(fontSchemeNode, ['a:majorFont', 'a:latin', 'attrs', 'typeface'])
1122
- }
1123
- else if (type === 'body') {
1124
- typeface = getTextByPathList(fontSchemeNode, ['a:minorFont', 'a:latin', 'attrs', 'typeface'])
1125
- }
1126
- else {
1127
- typeface = getTextByPathList(fontSchemeNode, ['a:minorFont', 'a:latin', 'attrs', 'typeface'])
1128
- }
1129
- }
1130
-
1131
- return typeface || ''
1132
- }
1133
-
1134
- function getFontColor(node) {
1135
- const color = getTextByPathList(node, ['a:rPr', 'a:solidFill', 'a:srgbClr', 'attrs', 'val'])
1136
- return color ? `#${color}` : ''
1137
- }
1138
-
1139
- function getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles) {
1140
- let fontSize
1141
-
1142
- if (node['a:rPr']) fontSize = parseInt(node['a:rPr']['attrs']['sz']) / 100
1143
-
1144
- if ((isNaN(fontSize) || !fontSize)) {
1145
- const sz = getTextByPathList(slideLayoutSpNode, ['p:txBody', 'a:lstStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
1146
- fontSize = parseInt(sz) / 100
1147
- }
1148
-
1149
- if (isNaN(fontSize) || !fontSize) {
1150
- let sz
1151
- if (type === 'title' || type === 'subTitle' || type === 'ctrTitle') {
1152
- sz = getTextByPathList(slideMasterTextStyles, ['p:titleStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
1153
- }
1154
- else if (type === 'body') {
1155
- sz = getTextByPathList(slideMasterTextStyles, ['p:bodyStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
1156
- }
1157
- else if (type === 'dt' || type === 'sldNum') {
1158
- sz = '1200'
1159
- }
1160
- else if (!type) {
1161
- sz = getTextByPathList(slideMasterTextStyles, ['p:otherStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
1162
- }
1163
- if (sz) fontSize = parseInt(sz) / 100
1164
- }
1165
-
1166
- const baseline = getTextByPathList(node, ['a:rPr', 'attrs', 'baseline'])
1167
- if (baseline && !isNaN(fontSize)) fontSize -= 10
1168
-
1169
- return (isNaN(fontSize) || !fontSize) ? '18.75px' : (fontSize / 0.75 * (75 / 96) + 'px')
1170
- }
1171
-
1172
- function getFontBold(node) {
1173
- return (node['a:rPr'] && node['a:rPr']['attrs']['b'] === '1') ? 'bold' : ''
1174
- }
1175
-
1176
- function getFontItalic(node) {
1177
- return (node['a:rPr'] && node['a:rPr']['attrs']['i'] === '1') ? 'italic' : ''
1178
- }
1179
-
1180
- function getFontDecoration(node) {
1181
- return (node['a:rPr'] && node['a:rPr']['attrs']['u'] === 'sng') ? 'underline' : ''
1182
- }
1183
-
1184
- function getBorder(node, elType) {
1185
- const lineNode = node['p:spPr']['a:ln']
1186
-
1187
- let borderWidth = parseInt(getTextByPathList(lineNode, ['attrs', 'w'])) / 12700
1188
- if (isNaN(borderWidth)) {
1189
- if (lineNode) borderWidth = 0
1190
- else if (elType === 'text') borderWidth = 0
1191
- else borderWidth = 1
1192
- }
1193
-
1194
- let borderColor = getTextByPathList(lineNode, ['a:solidFill', 'a:srgbClr', 'attrs', 'val'])
1195
- if (!borderColor) {
1196
- const schemeClrNode = getTextByPathList(lineNode, ['a:solidFill', 'a:schemeClr'])
1197
- const schemeClr = 'a:' + getTextByPathList(schemeClrNode, ['attrs', 'val'])
1198
- borderColor = getSchemeColorFromTheme(schemeClr)
1199
- }
1200
-
1201
- if (!borderColor) {
1202
- const schemeClrNode = getTextByPathList(node, ['p:style', 'a:lnRef', 'a:schemeClr'])
1203
- const schemeClr = 'a:' + getTextByPathList(schemeClrNode, ['attrs', 'val'])
1204
- borderColor = getSchemeColorFromTheme(schemeClr)
1205
-
1206
- if (borderColor) {
1207
- let shade = getTextByPathList(schemeClrNode, ['a:shade', 'attrs', 'val'])
1208
-
1209
- if (shade) {
1210
- shade = parseInt(shade) / 100000
1211
-
1212
- const color = tinycolor('#' + borderColor).toHsl()
1213
- borderColor = tinycolor({ h: color.h, s: color.s, l: color.l * shade, a: color.a }).toHex()
1214
- }
1215
- }
1216
- }
1217
-
1218
- if (!borderColor) borderColor = '#000'
1219
- else borderColor = `#${borderColor}`
1220
-
1221
- const type = getTextByPathList(lineNode, ['a:prstDash', 'attrs', 'val'])
1222
- let borderType = 'solid'
1223
- let strokeDasharray = '0'
1224
- switch (type) {
1225
- case 'solid':
1226
- borderType = 'solid'
1227
- strokeDasharray = '0'
1228
- break
1229
- case 'dash':
1230
- borderType = 'dashed'
1231
- strokeDasharray = '5'
1232
- break
1233
- case 'dashDot':
1234
- borderType = 'dashed'
1235
- strokeDasharray = '5, 5, 1, 5'
1236
- break
1237
- case 'dot':
1238
- borderType = 'dotted'
1239
- strokeDasharray = '1, 5'
1240
- break
1241
- case 'lgDash':
1242
- borderType = 'dashed'
1243
- strokeDasharray = '10, 5'
1244
- break
1245
- case 'lgDashDotDot':
1246
- borderType = 'dashed'
1247
- strokeDasharray = '10, 5, 1, 5, 1, 5'
1248
- break
1249
- case 'sysDash':
1250
- borderType = 'dashed'
1251
- strokeDasharray = '5, 2'
1252
- break
1253
- case 'sysDashDot':
1254
- borderType = 'dashed'
1255
- strokeDasharray = '5, 2, 1, 5'
1256
- break
1257
- case 'sysDashDotDot':
1258
- borderType = 'dashed'
1259
- strokeDasharray = '5, 2, 1, 5, 1, 5'
1260
- break
1261
- case 'sysDot':
1262
- borderType = 'dotted'
1263
- strokeDasharray = '2, 5'
1264
- break
1265
- default:
1266
- }
1267
-
1268
- return {
1269
- borderColor,
1270
- borderWidth,
1271
- borderType,
1272
- strokeDasharray,
1273
- }
1274
- }
1275
-
1276
- function getFillType(node) {
1277
- let fillType = ''
1278
- if (node['a:noFill']) fillType = 'NO_FILL'
1279
- if (node['a:solidFill']) fillType = 'SOLID_FILL'
1280
- if (node['a:gradFill']) fillType = 'GRADIENT_FILL'
1281
- if (node['a:pattFill']) fillType = 'PATTERN_FILL'
1282
- if (node['a:blipFill']) fillType = 'PIC_FILL'
1283
- if (node['a:grpFill']) fillType = 'GROUP_FILL'
1284
-
1285
- return fillType
1286
- }
1287
-
1288
- async function getPicFill(type, node, warpObj) {
1289
- let img
1290
- const rId = node['a:blip']['attrs']['r:embed']
1291
- let imgPath
1292
- if (type === 'slideBg' || type === 'slide') {
1293
- imgPath = getTextByPathList(warpObj, ['slideResObj', rId, 'target'])
1294
- }
1295
- else if (type === 'slideLayoutBg') {
1296
- imgPath = getTextByPathList(warpObj, ['layoutResObj', rId, 'target'])
1297
- }
1298
- else if (type === 'slideMasterBg') {
1299
- imgPath = getTextByPathList(warpObj, ['masterResObj', rId, 'target'])
1300
- }
1301
- else if (type === 'themeBg') {
1302
- imgPath = getTextByPathList(warpObj, ['themeResObj', rId, 'target'])
1303
- }
1304
- if (!imgPath) return imgPath
1305
-
1306
- img = getTextByPathList(warpObj, ['loaded-images', imgPath])
1307
- if (!img) {
1308
- imgPath = escapeHtml(imgPath)
1309
-
1310
- const imgExt = imgPath.split('.').pop()
1311
- if (imgExt === 'xml') return undefined
1312
-
1313
- const imgArrayBuffer = await warpObj['zip'].file(imgPath).async('arraybuffer')
1314
- const imgMimeType = getMimeType(imgExt)
1315
- img = `data:${imgMimeType};base64,${base64ArrayBuffer(imgArrayBuffer)}`
1316
- }
1317
- return img
1318
- }
1319
-
1320
- async function getBgPicFill(bgPr, sorce, warpObj) {
1321
- const picBase64 = await getPicFill(sorce, bgPr['a:blipFill'], warpObj)
1322
- const aBlipNode = bgPr['a:blipFill']['a:blip']
1323
-
1324
- const aphaModFixNode = getTextByPathList(aBlipNode, ['a:alphaModFix', 'attrs'])
1325
- let opacity = 1
1326
- if (aphaModFixNode && aphaModFixNode['amt'] && aphaModFixNode['amt'] !== '') {
1327
- opacity = parseInt(aphaModFixNode['amt']) / 100000
1328
- }
1329
-
1330
- return {
1331
- picBase64,
1332
- opacity,
1333
- }
1334
- }
1335
-
1336
- function getBgGradientFill(bgPr, phClr, slideMasterContent, warpObj) {
1337
- if (bgPr) {
1338
- const grdFill = bgPr['a:gradFill']
1339
- const gsLst = grdFill['a:gsLst']['a:gs']
1340
- const color_ary = []
1341
- const pos_ary = []
1342
-
1343
- for (let i = 0; i < gsLst.length; i++) {
1344
- const lo_color = getSolidFill(gsLst[i], slideMasterContent['p:sldMaster']['p:clrMap']['attrs'], phClr, warpObj)
1345
- const pos = getTextByPathList(gsLst[i], ['attrs', 'pos'])
1346
-
1347
- if (pos) pos_ary[i] = pos / 1000 + '%'
1348
- else pos_ary[i] = ''
1349
- color_ary[i] = `#${lo_color}`
1350
- }
1351
- const lin = grdFill['a:lin']
1352
- let rot = 90
1353
- if (lin) {
1354
- rot = angleToDegrees(lin['attrs']['ang'])
1355
- rot = rot + 90
1356
- }
1357
- return {
1358
- rot,
1359
- colors: color_ary,
1360
- pos: pos_ary,
1361
- }
1362
- }
1363
- else if (phClr) return `#${phClr}`
1364
- return null
1365
- }
1366
-
1367
- async function getSlideBackgroundFill(warpObj) {
1368
- const slideContent = warpObj['slideContent']
1369
- const slideLayoutContent = warpObj['slideLayoutContent']
1370
- const slideMasterContent = warpObj['slideMasterContent']
1371
-
1372
- let bgPr = getTextByPathList(slideContent, ['p:sld', 'p:cSld', 'p:bg', 'p:bgPr'])
1373
-
1374
- let background = '#fff'
1375
- let backgroundType = 'color'
1376
-
1377
- if (bgPr) {
1378
- const bgFillTyp = getFillType(bgPr)
1379
-
1380
- if (bgFillTyp === 'SOLID_FILL') {
1381
- const sldFill = bgPr['a:solidFill']
1382
- let clrMapOvr
1383
- const sldClrMapOvr = getTextByPathList(slideContent, ['p:sld', 'p:clrMapOvr', 'a:overrideClrMapping', 'attrs'])
1384
- if (sldClrMapOvr) clrMapOvr = sldClrMapOvr
1385
- else {
1386
- const sldClrMapOvr = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'p:clrMapOvr', 'a:overrideClrMapping', 'attrs'])
1387
- if (sldClrMapOvr) clrMapOvr = sldClrMapOvr
1388
- else clrMapOvr = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:clrMap', 'attrs'])
1389
- }
1390
- const sldBgClr = getSolidFill(sldFill, clrMapOvr, undefined, warpObj)
1391
- background = `#${sldBgClr}`
1392
- }
1393
- else if (bgFillTyp === 'GRADIENT_FILL') {
1394
- const gradientFill = getBgGradientFill(bgPr, undefined, slideMasterContent, warpObj)
1395
- if (typeof gradientFill === 'string') {
1396
- background = gradientFill
1397
- }
1398
- else if (gradientFill) {
1399
- background = gradientFill
1400
- backgroundType = 'gradient'
1401
- }
1402
- }
1403
- else if (bgFillTyp === 'PIC_FILL') {
1404
- background = await getBgPicFill(bgPr, 'slideBg', warpObj)
1405
- backgroundType = 'image'
1406
- }
1407
- }
1408
- else {
1409
- bgPr = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'p:cSld', 'p:bg', 'p:bgPr'])
1410
-
1411
- let clrMapOvr
1412
- const sldClrMapOvr = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'p:clrMapOvr', 'a:overrideClrMapping', 'attrs'])
1413
- if (sldClrMapOvr) clrMapOvr = sldClrMapOvr
1414
- else clrMapOvr = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:clrMap', 'attrs'])
1415
-
1416
- if (bgPr) {
1417
- const bgFillTyp = getFillType(bgPr)
1418
- if (bgFillTyp === 'SOLID_FILL') {
1419
- const sldFill = bgPr['a:solidFill']
1420
- const sldBgClr = getSolidFill(sldFill, clrMapOvr, undefined, warpObj)
1421
- background = `#${sldBgClr}`
1422
- }
1423
- else if (bgFillTyp === 'GRADIENT_FILL') {
1424
- const gradientFill = getBgGradientFill(bgPr, undefined, slideMasterContent, warpObj)
1425
- if (typeof gradientFill === 'string') {
1426
- background = gradientFill
1427
- }
1428
- else if (gradientFill) {
1429
- background = gradientFill
1430
- backgroundType = 'gradient'
1431
- }
1432
- }
1433
- else if (bgFillTyp === 'PIC_FILL') {
1434
- background = await getBgPicFill(bgPr, 'slideLayoutBg', warpObj)
1435
- backgroundType = 'image'
1436
- }
1437
- }
1438
- else {
1439
- bgPr = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:cSld', 'p:bg', 'p:bgPr'])
1440
-
1441
- const clrMap = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:clrMap', 'attrs'])
1442
- if (bgPr) {
1443
- const bgFillTyp = getFillType(bgPr)
1444
- if (bgFillTyp === 'SOLID_FILL') {
1445
- const sldFill = bgPr['a:solidFill']
1446
- const sldBgClr = getSolidFill(sldFill, clrMap, undefined, warpObj)
1447
- background = `#${sldBgClr}`
1448
- }
1449
- else if (bgFillTyp === 'GRADIENT_FILL') {
1450
- const gradientFill = getBgGradientFill(bgPr, undefined, slideMasterContent, warpObj)
1451
- if (typeof gradientFill === 'string') {
1452
- background = gradientFill
1453
- }
1454
- else if (gradientFill) {
1455
- background = gradientFill
1456
- backgroundType = 'gradient'
1457
- }
1458
- }
1459
- else if (bgFillTyp === 'PIC_FILL') {
1460
- background = await getBgPicFill(bgPr, 'slideMasterBg', warpObj)
1461
- backgroundType = 'image'
1462
- }
1463
- }
1464
- }
1465
- }
1466
- return {
1467
- type: backgroundType,
1468
- value: background,
1469
- }
1470
- }
1471
-
1472
- function getShapeFill(node, isSvgMode) {
1473
- if (getTextByPathList(node, ['p:spPr', 'a:noFill'])) {
1474
- return isSvgMode ? 'none' : ''
1475
- }
1476
-
1477
- let fillColor
1478
- if (!fillColor) {
1479
- fillColor = getTextByPathList(node, ['p:spPr', 'a:solidFill', 'a:srgbClr', 'attrs', 'val'])
1480
- }
1481
-
1482
- if (!fillColor) {
1483
- const schemeClr = 'a:' + getTextByPathList(node, ['p:spPr', 'a:solidFill', 'a:schemeClr', 'attrs', 'val'])
1484
- fillColor = getSchemeColorFromTheme(schemeClr)
1485
- }
1486
-
1487
- if (!fillColor) {
1488
- const schemeClr = 'a:' + getTextByPathList(node, ['p:style', 'a:fillRef', 'a:schemeClr', 'attrs', 'val'])
1489
- fillColor = getSchemeColorFromTheme(schemeClr)
1490
- }
1491
-
1492
- if (fillColor) {
1493
- fillColor = `#${fillColor}`
1494
-
1495
- let lumMod = parseInt(getTextByPathList(node, ['p:spPr', 'a:solidFill', 'a:schemeClr', 'a:lumMod', 'attrs', 'val'])) / 100000
1496
- let lumOff = parseInt(getTextByPathList(node, ['p:spPr', 'a:solidFill', 'a:schemeClr', 'a:lumOff', 'attrs', 'val'])) / 100000
1497
- if (isNaN(lumMod)) lumMod = 1.0
1498
- if (isNaN(lumOff)) lumOff = 0
1499
-
1500
- const color = tinycolor(fillColor).toHsl()
1501
- const lum = color.l * (1 + lumOff)
1502
- return tinycolor({ h: color.h, s: color.s, l: lum, a: color.a }).toHexString()
1503
- }
1504
-
1505
- if (isSvgMode) return 'none'
1506
- return fillColor
1507
- }
1508
-
1509
- function getSolidFill(solidFill) {
1510
- if (!solidFill) return solidFill
1511
-
1512
- let color = 'fff'
1513
-
1514
- if (solidFill['a:srgbClr']) {
1515
- color = getTextByPathList(solidFill['a:srgbClr'], ['attrs', 'val'])
1516
- }
1517
- else if (solidFill['a:schemeClr']) {
1518
- const schemeClr = 'a:' + getTextByPathList(solidFill['a:schemeClr'], ['attrs', 'val'])
1519
- color = getSchemeColorFromTheme(schemeClr)
1520
- }
1521
-
1522
- return color
1523
- }
1524
-
1525
- function getSchemeColorFromTheme(schemeClr) {
1526
- switch (schemeClr) {
1527
- case 'a:tx1':
1528
- schemeClr = 'a:dk1'
1529
- break
1530
- case 'a:tx2':
1531
- schemeClr = 'a:dk2'
1532
- break
1533
- case 'a:bg1':
1534
- schemeClr = 'a:lt1'
1535
- break
1536
- case 'a:bg2':
1537
- schemeClr = 'a:lt2'
1538
- break
1539
- default:
1540
- break
1541
- }
1542
- const refNode = getTextByPathList(themeContent, ['a:theme', 'a:themeElements', 'a:clrScheme', schemeClr])
1543
- let color = getTextByPathList(refNode, ['a:srgbClr', 'attrs', 'val'])
1544
- if (!color) color = getTextByPathList(refNode, ['a:sysClr', 'attrs', 'lastClr'])
1545
- return color
1546
- }
1547
-
1548
- function extractChartData(serNode) {
1549
- const dataMat = []
1550
- if (!serNode) return dataMat
1551
-
1552
- if (serNode['c:xVal']) {
1553
- let dataRow = []
1554
- eachElement(serNode['c:xVal']['c:numRef']['c:numCache']['c:pt'], innerNode => {
1555
- dataRow.push(parseFloat(innerNode['c:v']))
1556
- return ''
1557
- })
1558
- dataMat.push(dataRow)
1559
- dataRow = []
1560
- eachElement(serNode['c:yVal']['c:numRef']['c:numCache']['c:pt'], innerNode => {
1561
- dataRow.push(parseFloat(innerNode['c:v']))
1562
- return ''
1563
- })
1564
- dataMat.push(dataRow)
1565
- }
1566
- else {
1567
- eachElement(serNode, (innerNode, index) => {
1568
- const dataRow = []
1569
- const colName = getTextByPathList(innerNode, ['c:tx', 'c:strRef', 'c:strCache', 'c:pt', 'c:v']) || index
1570
-
1571
- const rowNames = {}
1572
- if (getTextByPathList(innerNode, ['c:cat', 'c:strRef', 'c:strCache', 'c:pt'])) {
1573
- eachElement(innerNode['c:cat']['c:strRef']['c:strCache']['c:pt'], innerNode => {
1574
- rowNames[innerNode['attrs']['idx']] = innerNode['c:v']
1575
- return ''
1576
- })
1577
- }
1578
- else if (getTextByPathList(innerNode, ['c:cat', 'c:numRef', 'c:numCache', 'c:pt'])) {
1579
- eachElement(innerNode['c:cat']['c:numRef']['c:numCache']['c:pt'], innerNode => {
1580
- rowNames[innerNode['attrs']['idx']] = innerNode['c:v']
1581
- return ''
1582
- })
1583
- }
1584
-
1585
- if (getTextByPathList(innerNode, ['c:val', 'c:numRef', 'c:numCache', 'c:pt'])) {
1586
- eachElement(innerNode['c:val']['c:numRef']['c:numCache']['c:pt'], innerNode => {
1587
- dataRow.push({
1588
- x: innerNode['attrs']['idx'],
1589
- y: parseFloat(innerNode['c:v']),
1590
- })
1591
- return ''
1592
- })
1593
- }
1594
-
1595
- dataMat.push({
1596
- key: colName,
1597
- values: dataRow,
1598
- xlabels: rowNames,
1599
- })
1600
- return ''
1601
- })
1602
- }
1603
-
1604
- return dataMat
1605
- }
783
+ }