pptxtojson 1.6.0 → 1.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pptxtojson",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "A javascript tool for parsing .pptx file",
5
5
  "type": "module",
6
6
  "main": "./dist/index.umd.js",
package/src/fill.js CHANGED
@@ -85,6 +85,79 @@ export function getPicFillOpacity(node) {
85
85
  return opacity
86
86
  }
87
87
 
88
+ export function getPicFilters(node) {
89
+ if (!node) return null
90
+
91
+ const aBlipNode = node['a:blip']
92
+ if (!aBlipNode) return null
93
+
94
+ const filters = {}
95
+
96
+ // 从a:extLst中获取滤镜效果(Microsoft Office 2010+扩展)
97
+ const extLstNode = aBlipNode['a:extLst']
98
+ if (extLstNode && extLstNode['a:ext']) {
99
+ const extNodes = Array.isArray(extLstNode['a:ext']) ? extLstNode['a:ext'] : [extLstNode['a:ext']]
100
+
101
+ for (const extNode of extNodes) {
102
+ if (!extNode['a14:imgProps'] || !extNode['a14:imgProps']['a14:imgLayer']) continue
103
+
104
+ const imgLayerNode = extNode['a14:imgProps']['a14:imgLayer']
105
+ const imgEffects = imgLayerNode['a14:imgEffect']
106
+
107
+ if (!imgEffects) continue
108
+
109
+ const effectArray = Array.isArray(imgEffects) ? imgEffects : [imgEffects]
110
+
111
+ for (const effect of effectArray) {
112
+ // 饱和度
113
+ if (effect['a14:saturation']) {
114
+ const satAttr = getTextByPathList(effect, ['a14:saturation', 'attrs', 'sat'])
115
+ if (satAttr) {
116
+ filters.saturation = parseInt(satAttr) / 100000
117
+ }
118
+ }
119
+
120
+ // 亮度、对比度
121
+ if (effect['a14:brightnessContrast']) {
122
+ const brightAttr = getTextByPathList(effect, ['a14:brightnessContrast', 'attrs', 'bright'])
123
+ const contrastAttr = getTextByPathList(effect, ['a14:brightnessContrast', 'attrs', 'contrast'])
124
+
125
+ if (brightAttr) {
126
+ filters.brightness = parseInt(brightAttr) / 100000
127
+ }
128
+ if (contrastAttr) {
129
+ filters.contrast = parseInt(contrastAttr) / 100000
130
+ }
131
+ }
132
+
133
+ // 锐化/柔化
134
+ if (effect['a14:sharpenSoften']) {
135
+ const amountAttr = getTextByPathList(effect, ['a14:sharpenSoften', 'attrs', 'amount'])
136
+ if (amountAttr) {
137
+ const amount = parseInt(amountAttr) / 100000
138
+ if (amount > 0) {
139
+ filters.sharpen = amount
140
+ }
141
+ else {
142
+ filters.soften = Math.abs(amount)
143
+ }
144
+ }
145
+ }
146
+
147
+ // 色温
148
+ if (effect['a14:colorTemperature']) {
149
+ const tempAttr = getTextByPathList(effect, ['a14:colorTemperature', 'attrs', 'colorTemp'])
150
+ if (tempAttr) {
151
+ filters.colorTemperature = parseInt(tempAttr)
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ return Object.keys(filters).length > 0 ? filters : null
159
+ }
160
+
88
161
  export async function getBgPicFill(bgPr, sorce, warpObj) {
89
162
  const picBase64 = await getPicFill(sorce, bgPr['a:blipFill'], warpObj)
90
163
  const aBlipNode = bgPr['a:blipFill']['a:blip']
@@ -461,7 +534,7 @@ export async function getSlideBackgroundFill(warpObj) {
461
534
  }
462
535
  }
463
536
 
464
- export async function getShapeFill(node, pNode, isSvgMode, warpObj, source) {
537
+ export async function getShapeFill(node, pNode, isSvgMode, warpObj, source, groupHierarchy = []) {
465
538
  const fillType = getFillType(getTextByPathList(node, ['p:spPr']))
466
539
  let type = 'color'
467
540
  let fillValue = ''
@@ -488,23 +561,16 @@ export async function getShapeFill(node, pNode, isSvgMode, warpObj, source) {
488
561
  }
489
562
  type = 'image'
490
563
  }
564
+ else if (fillType === 'GROUP_FILL') {
565
+ return findFillInGroupHierarchy(groupHierarchy, warpObj, source)
566
+ }
491
567
  if (!fillValue) {
492
568
  const clrName = getTextByPathList(node, ['p:style', 'a:fillRef'])
493
569
  fillValue = getSolidFill(clrName, undefined, undefined, warpObj)
494
570
  type = 'color'
495
571
  }
496
- if (!fillValue && pNode) {
497
- const grpFill = getTextByPathList(node, ['p:spPr', 'a:grpFill'])
498
- if (grpFill) {
499
- const grpShpFill = pNode['p:grpSpPr']
500
- if (grpShpFill) {
501
- const spShpNode = { 'p:spPr': grpShpFill }
502
- return getShapeFill(spShpNode, node, isSvgMode, warpObj, source)
503
- }
504
- }
505
- else if (fillType === 'NO_FILL') {
506
- return isSvgMode ? 'none' : ''
507
- }
572
+ if (!fillValue && pNode && fillType === 'NO_FILL') {
573
+ return isSvgMode ? 'none' : ''
508
574
  }
509
575
 
510
576
  return {
@@ -513,6 +579,52 @@ export async function getShapeFill(node, pNode, isSvgMode, warpObj, source) {
513
579
  }
514
580
  }
515
581
 
582
+ async function findFillInGroupHierarchy(groupHierarchy, warpObj, source) {
583
+ for (const groupNode of groupHierarchy) {
584
+ if (!groupNode || !groupNode['p:grpSpPr']) continue
585
+
586
+ const grpSpPr = groupNode['p:grpSpPr']
587
+ const fillType = getFillType(grpSpPr)
588
+
589
+ if (fillType === 'SOLID_FILL') {
590
+ const shpFill = grpSpPr['a:solidFill']
591
+ const fillValue = getSolidFill(shpFill, undefined, undefined, warpObj)
592
+ if (fillValue) {
593
+ return {
594
+ type: 'color',
595
+ value: fillValue,
596
+ }
597
+ }
598
+ }
599
+ else if (fillType === 'GRADIENT_FILL') {
600
+ const shpFill = grpSpPr['a:gradFill']
601
+ const fillValue = getGradientFill(shpFill, warpObj)
602
+ if (fillValue) {
603
+ return {
604
+ type: 'gradient',
605
+ value: fillValue,
606
+ }
607
+ }
608
+ }
609
+ else if (fillType === 'PIC_FILL') {
610
+ const shpFill = grpSpPr['a:blipFill']
611
+ const picBase64 = await getPicFill(source, shpFill, warpObj)
612
+ const opacity = getPicFillOpacity(shpFill)
613
+ if (picBase64) {
614
+ return {
615
+ type: 'image',
616
+ value: {
617
+ picBase64,
618
+ opacity,
619
+ },
620
+ }
621
+ }
622
+ }
623
+ }
624
+
625
+ return null
626
+ }
627
+
516
628
  export function getSolidFill(solidFill, clrMap, phClr, warpObj) {
517
629
  if (!solidFill) return ''
518
630
 
package/src/fontStyle.js CHANGED
@@ -48,14 +48,49 @@ export function getFontColor(node, pNode, lstStyle, pFontStyle, lvl, warpObj) {
48
48
  return color || ''
49
49
  }
50
50
 
51
- export function getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles) {
51
+ export function getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles, textBodyNode, pNode) {
52
52
  let fontSize
53
53
 
54
54
  if (getTextByPathList(node, ['a:rPr', 'attrs', 'sz'])) fontSize = getTextByPathList(node, ['a:rPr', 'attrs', 'sz']) / 100
55
55
 
56
+ if ((isNaN(fontSize) || !fontSize) && pNode) {
57
+ if (getTextByPathList(pNode, ['a:endParaRPr', 'attrs', 'sz'])) {
58
+ fontSize = getTextByPathList(pNode, ['a:endParaRPr', 'attrs', 'sz']) / 100
59
+ }
60
+ }
61
+
62
+ if ((isNaN(fontSize) || !fontSize) && textBodyNode) {
63
+ const lstStyle = getTextByPathList(textBodyNode, ['a:lstStyle'])
64
+ if (lstStyle) {
65
+ let lvl = 1
66
+ if (pNode) {
67
+ const lvlNode = getTextByPathList(pNode, ['a:pPr', 'attrs', 'lvl'])
68
+ if (lvlNode !== undefined) lvl = parseInt(lvlNode) + 1
69
+ }
70
+
71
+ const sz = getTextByPathList(lstStyle, [`a:lvl${lvl}pPr`, 'a:defRPr', 'attrs', 'sz'])
72
+ if (sz) fontSize = parseInt(sz) / 100
73
+ }
74
+ }
75
+
56
76
  if ((isNaN(fontSize) || !fontSize)) {
57
77
  const sz = getTextByPathList(slideLayoutSpNode, ['p:txBody', 'a:lstStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
58
- fontSize = parseInt(sz) / 100
78
+ if (sz) fontSize = parseInt(sz) / 100
79
+ }
80
+
81
+ if ((isNaN(fontSize) || !fontSize) && slideLayoutSpNode) {
82
+ let lvl = 1
83
+ if (pNode) {
84
+ const lvlNode = getTextByPathList(pNode, ['a:pPr', 'attrs', 'lvl'])
85
+ if (lvlNode !== undefined) lvl = parseInt(lvlNode) + 1
86
+ }
87
+ const layoutSz = getTextByPathList(slideLayoutSpNode, ['p:txBody', 'a:lstStyle', `a:lvl${lvl}pPr`, 'a:defRPr', 'attrs', 'sz'])
88
+ if (layoutSz) fontSize = parseInt(layoutSz) / 100
89
+ }
90
+
91
+ if ((isNaN(fontSize) || !fontSize) && pNode) {
92
+ const paraSz = getTextByPathList(pNode, ['a:pPr', 'a:defRPr', 'attrs', 'sz'])
93
+ if (paraSz) fontSize = parseInt(paraSz) / 100
59
94
  }
60
95
 
61
96
  if (isNaN(fontSize) || !fontSize) {
package/src/pptxtojson.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import JSZip from 'jszip'
2
2
  import { readXmlFile } from './readXmlFile'
3
3
  import { getBorder } from './border'
4
- import { getSlideBackgroundFill, getShapeFill, getSolidFill, getPicFill } from './fill'
4
+ import { getSlideBackgroundFill, getShapeFill, getSolidFill, getPicFill, getPicFilters } from './fill'
5
5
  import { getChartInfo } from './chart'
6
6
  import { getVerticalAlign } from './align'
7
7
  import { getPosition, getSize } from './position'
@@ -135,9 +135,17 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
135
135
  switch (relationshipArrayItem['attrs']['Type']) {
136
136
  case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout':
137
137
  layoutFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
138
+ slideResObj[relationshipArrayItem['attrs']['Id']] = {
139
+ type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
140
+ target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
141
+ }
138
142
  break
139
143
  case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide':
140
144
  noteFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
145
+ slideResObj[relationshipArrayItem['attrs']['Id']] = {
146
+ type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
147
+ target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
148
+ }
141
149
  break
142
150
  case 'http://schemas.microsoft.com/office/2007/relationships/diagramDrawing':
143
151
  diagramFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
@@ -399,12 +407,12 @@ function indexNodes(content) {
399
407
  return { idTable, idxTable, typeTable }
400
408
  }
401
409
 
402
- async function processNodesInSlide(nodeKey, nodeValue, nodes, warpObj, source) {
410
+ async function processNodesInSlide(nodeKey, nodeValue, nodes, warpObj, source, groupHierarchy = []) {
403
411
  let json
404
412
 
405
413
  switch (nodeKey) {
406
414
  case 'p:sp': // Shape, Text
407
- json = await processSpNode(nodeValue, nodes, warpObj, source)
415
+ json = await processSpNode(nodeValue, nodes, warpObj, source, groupHierarchy)
408
416
  break
409
417
  case 'p:cxnSp': // Shape, Text
410
418
  json = await processCxnSpNode(nodeValue, nodes, warpObj, source)
@@ -416,11 +424,11 @@ async function processNodesInSlide(nodeKey, nodeValue, nodes, warpObj, source) {
416
424
  json = await processGraphicFrameNode(nodeValue, warpObj, source)
417
425
  break
418
426
  case 'p:grpSp':
419
- json = await processGroupSpNode(nodeValue, warpObj, source)
427
+ json = await processGroupSpNode(nodeValue, warpObj, source, groupHierarchy)
420
428
  break
421
429
  case 'mc:AlternateContent':
422
430
  if (getTextByPathList(nodeValue, ['mc:Fallback', 'p:grpSpPr', 'a:xfrm'])) {
423
- json = await processGroupSpNode(getTextByPathList(nodeValue, ['mc:Fallback']), warpObj, source)
431
+ json = await processGroupSpNode(getTextByPathList(nodeValue, ['mc:Fallback']), warpObj, source, groupHierarchy)
424
432
  }
425
433
  else if (getTextByPathList(nodeValue, ['mc:Choice'])) {
426
434
  json = await processMathNode(nodeValue, warpObj, source)
@@ -466,7 +474,7 @@ async function processMathNode(node, warpObj, source) {
466
474
  }
467
475
  }
468
476
 
469
- async function processGroupSpNode(node, warpObj, source) {
477
+ async function processGroupSpNode(node, warpObj, source, parentGroupHierarchy = []) {
470
478
  const order = node['attrs']['order']
471
479
  const xfrmNode = getTextByPathList(node, ['p:grpSpPr', 'a:xfrm'])
472
480
  if (!xfrmNode) return null
@@ -489,16 +497,19 @@ async function processGroupSpNode(node, warpObj, source) {
489
497
  const ws = cx / chcx
490
498
  const hs = cy / chcy
491
499
 
500
+ // 构建当前组合层级(将当前组合添加到父级层级中)
501
+ const currentGroupHierarchy = [...parentGroupHierarchy, node]
502
+
492
503
  const elements = []
493
504
  for (const nodeKey in node) {
494
505
  if (node[nodeKey].constructor === Array) {
495
506
  for (const item of node[nodeKey]) {
496
- const ret = await processNodesInSlide(nodeKey, item, node, warpObj, source)
507
+ const ret = await processNodesInSlide(nodeKey, item, node, warpObj, source, currentGroupHierarchy)
497
508
  if (ret) elements.push(ret)
498
509
  }
499
510
  }
500
511
  else {
501
- const ret = await processNodesInSlide(nodeKey, node[nodeKey], node, warpObj, source)
512
+ const ret = await processNodesInSlide(nodeKey, node[nodeKey], node, warpObj, source, currentGroupHierarchy)
502
513
  if (ret) elements.push(ret)
503
514
  }
504
515
  }
@@ -523,7 +534,7 @@ async function processGroupSpNode(node, warpObj, source) {
523
534
  }
524
535
  }
525
536
 
526
- async function processSpNode(node, pNode, warpObj, source) {
537
+ async function processSpNode(node, pNode, warpObj, source, groupHierarchy = []) {
527
538
  const name = getTextByPathList(node, ['p:nvSpPr', 'p:cNvPr', 'attrs', 'name'])
528
539
  const idx = getTextByPathList(node, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'idx'])
529
540
  let type = getTextByPathList(node, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
@@ -558,7 +569,7 @@ async function processSpNode(node, pNode, warpObj, source) {
558
569
  else type = 'obj'
559
570
  }
560
571
 
561
- return await genShape(node, pNode, slideLayoutSpNode, slideMasterSpNode, name, type, order, warpObj, source)
572
+ return await genShape(node, pNode, slideLayoutSpNode, slideMasterSpNode, name, type, order, warpObj, source, groupHierarchy)
562
573
  }
563
574
 
564
575
  async function processCxnSpNode(node, pNode, warpObj, source) {
@@ -569,7 +580,7 @@ async function processCxnSpNode(node, pNode, warpObj, source) {
569
580
  return await genShape(node, pNode, undefined, undefined, name, type, order, warpObj, source)
570
581
  }
571
582
 
572
- async function genShape(node, pNode, slideLayoutSpNode, slideMasterSpNode, name, type, order, warpObj, source) {
583
+ async function genShape(node, pNode, slideLayoutSpNode, slideMasterSpNode, name, type, order, warpObj, source, groupHierarchy = []) {
573
584
  const xfrmList = ['p:spPr', 'a:xfrm']
574
585
  const slideXfrmNode = getTextByPathList(node, xfrmList)
575
586
  const slideLayoutXfrmNode = getTextByPathList(slideLayoutSpNode, xfrmList)
@@ -598,7 +609,7 @@ async function genShape(node, pNode, slideLayoutSpNode, slideMasterSpNode, name,
598
609
  if (node['p:txBody']) content = genTextBody(node['p:txBody'], node, slideLayoutSpNode, type, warpObj)
599
610
 
600
611
  const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(node, type, warpObj)
601
- const fill = await getShapeFill(node, pNode, undefined, warpObj, source) || ''
612
+ const fill = await getShapeFill(node, pNode, undefined, warpObj, source, groupHierarchy) || ''
602
613
 
603
614
  let shadow
604
615
  const outerShdwNode = getTextByPathList(node, ['p:spPr', 'a:effectLst', 'a:outerShdw'])
@@ -791,11 +802,13 @@ async function processPicNode(node, warpObj, source) {
791
802
 
792
803
  const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(node, undefined, warpObj)
793
804
 
794
- return {
805
+ const filters = getPicFilters(node['p:blipFill'])
806
+
807
+ const imageData = {
795
808
  type: 'image',
796
809
  top,
797
810
  left,
798
- width,
811
+ width,
799
812
  height,
800
813
  rotate,
801
814
  src,
@@ -809,6 +822,10 @@ async function processPicNode(node, warpObj, source) {
809
822
  borderType,
810
823
  borderStrokeDasharray: strokeDasharray,
811
824
  }
825
+
826
+ if (filters) imageData.filters = filters
827
+
828
+ return imageData
812
829
  }
813
830
 
814
831
  async function processGraphicFrameNode(node, warpObj, source) {
package/src/text.js CHANGED
@@ -111,7 +111,7 @@ export function genSpanElement(node, pNode, textBodyNode, pFontStyle, slideLayou
111
111
 
112
112
  let styleText = ''
113
113
  const fontColor = getFontColor(node, pNode, lstStyle, pFontStyle, lvl, warpObj)
114
- const fontSize = getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles)
114
+ const fontSize = getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles, textBodyNode, pNode)
115
115
  const fontType = getFontType(node, type, warpObj)
116
116
  const fontBold = getFontBold(node)
117
117
  const fontItalic = getFontItalic(node)
@@ -133,7 +133,7 @@ export function genSpanElement(node, pNode, textBodyNode, pFontStyle, slideLayou
133
133
  if (shadow) styleText += `text-shadow: ${shadow};`
134
134
 
135
135
  const linkID = getTextByPathList(node, ['a:rPr', 'a:hlinkClick', 'attrs', 'r:id'])
136
- if (linkID) {
136
+ if (linkID && warpObj['slideResObj'][linkID]) {
137
137
  const linkURL = warpObj['slideResObj'][linkID]['target']
138
138
  return `<span style="${styleText}"><a href="${linkURL}" target="_blank">${text.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;').replace(/\s/g, '&nbsp;')}</a></span>`
139
139
  }