pptxtojson 2.0.3 → 2.0.5

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
@@ -7,7 +7,7 @@ import { getVerticalAlign, getTextAutoFit } from './paragraph'
7
7
  import { getTextInsets } from './textInsets'
8
8
  import { getPosition, getSize } from './position'
9
9
  import { genTextBody, getTextNodeValue } from './text'
10
- import { getCustomShapePath, identifyShape } from './shape'
10
+ import { getCustomShapePath, identifyShape, isStrokeOnlyCustomGeometry } from './shape'
11
11
  import { extractFileExtension, getTextByPathList, angleToDegrees, isVideoLink, escapeHtml, hasValidText, numberToFixed } from './utils'
12
12
  import { getShadow } from './shadow'
13
13
  import { getTableBorders, getTableCellParams, getTableRowParams } from './table'
@@ -22,6 +22,7 @@ export async function parse(file, options = {}) {
22
22
  const loadedImages = {}
23
23
  const loadedVideos = {}
24
24
  const loadedAudios = {}
25
+ const xmlCache = {}
25
26
  const parseOptions = {
26
27
  ...options,
27
28
  imageMode: options.imageMode || 'base64',
@@ -37,7 +38,7 @@ export async function parse(file, options = {}) {
37
38
  const usedFonts = await getUsedFonts(zip)
38
39
 
39
40
  for (const filename of filesInfo.slides) {
40
- const singleSlide = await processSingleSlide(zip, filename, themeContent, defaultTextStyle, loadedImages, loadedVideos, loadedAudios, parseOptions)
41
+ const singleSlide = await processSingleSlide(zip, filename, themeContent, defaultTextStyle, loadedImages, loadedVideos, loadedAudios, parseOptions, xmlCache)
41
42
  slides.push(singleSlide)
42
43
  }
43
44
 
@@ -143,7 +144,16 @@ async function getTheme(zip) {
143
144
  return { themeContent, themeColors }
144
145
  }
145
146
 
146
- async function processSingleSlide(zip, sldFileName, themeContent, defaultTextStyle, loadedImages, loadedVideos, loadedAudios, options) {
147
+ async function readXmlFileCached(zip, filename, xmlCache) {
148
+ if (!filename) return null
149
+ if (Object.prototype.hasOwnProperty.call(xmlCache, filename)) return xmlCache[filename]
150
+
151
+ const content = await readXmlFile(zip, filename)
152
+ xmlCache[filename] = content
153
+ return content
154
+ }
155
+
156
+ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextStyle, loadedImages, loadedVideos, loadedAudios, options, xmlCache) {
147
157
  const resName = sldFileName.replace('slides/slide', 'slides/_rels/slide') + '.rels'
148
158
  const resContent = await readXmlFile(zip, resName)
149
159
  let relationshipArray = resContent['Relationships']['Relationship']
@@ -206,10 +216,10 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
206
216
  const slideNotesContent = await readXmlFile(zip, noteFilename)
207
217
  const note = getNote(slideNotesContent)
208
218
 
209
- const slideLayoutContent = await readXmlFile(zip, layoutFilename)
210
- const slideLayoutTables = await indexNodes(slideLayoutContent)
219
+ const slideLayoutContent = await readXmlFileCached(zip, layoutFilename, xmlCache)
220
+ const slideLayoutTables = indexNodes(slideLayoutContent)
211
221
  const slideLayoutResFilename = layoutFilename.replace('slideLayouts/slideLayout', 'slideLayouts/_rels/slideLayout') + '.rels'
212
- const slideLayoutResContent = await readXmlFile(zip, slideLayoutResFilename)
222
+ const slideLayoutResContent = await readXmlFileCached(zip, slideLayoutResFilename, xmlCache)
213
223
  relationshipArray = slideLayoutResContent['Relationships']['Relationship']
214
224
  if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray]
215
225
 
@@ -231,11 +241,11 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
231
241
  }
232
242
  }
233
243
 
234
- const slideMasterContent = await readXmlFile(zip, masterFilename)
244
+ const slideMasterContent = await readXmlFileCached(zip, masterFilename, xmlCache)
235
245
  const slideMasterTextStyles = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:txStyles'])
236
246
  const slideMasterTables = indexNodes(slideMasterContent)
237
247
  const slideMasterResFilename = masterFilename.replace('slideMasters/slideMaster', 'slideMasters/_rels/slideMaster') + '.rels'
238
- const slideMasterResContent = await readXmlFile(zip, slideMasterResFilename)
248
+ const slideMasterResContent = await readXmlFileCached(zip, slideMasterResFilename, xmlCache)
239
249
  relationshipArray = slideMasterResContent['Relationships']['Relationship']
240
250
  if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray]
241
251
 
@@ -275,7 +285,7 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
275
285
  }
276
286
  }
277
287
 
278
- const tableStyles = await readXmlFile(zip, 'ppt/tableStyles.xml')
288
+ const tableStyles = await readXmlFileCached(zip, 'ppt/tableStyles.xml', xmlCache)
279
289
 
280
290
  const slideContent = await readXmlFile(zip, sldFileName)
281
291
  const nodes = slideContent['p:sld']['p:cSld']['p:spTree']
@@ -398,7 +408,7 @@ function getNote(noteContent) {
398
408
  text += `<${listType}>`
399
409
  listTypes[listLevel] = listType
400
410
  }
401
- text += `<li style="text-align:${align};">`
411
+ text += `<li><p style="text-align:${align};">`
402
412
  }
403
413
  else {
404
414
  while (listTypes.length > 0) {
@@ -416,7 +426,7 @@ function getNote(noteContent) {
416
426
  }
417
427
  }
418
428
 
419
- if (listType) text += '</li>'
429
+ if (listType) text += '</p></li>'
420
430
  else text += '</p>'
421
431
  }
422
432
  while (listTypes.length > 0) {
@@ -559,6 +569,8 @@ async function processMathNode(node, warpObj, source) {
559
569
  const { width, height } = getSize(xfrmNode, undefined, undefined)
560
570
 
561
571
  const oMath = findOMath(choice)[0]
572
+ if (!oMath) return null
573
+
562
574
  const latex = latexFormart(parseOMath(oMath))
563
575
 
564
576
  const blipFill = getTextByPathList(fallback, ['p:sp', 'p:spPr', 'a:blipFill'])
@@ -606,8 +618,8 @@ async function processGroupSpNode(node, warpObj, source, parentGroupHierarchy =
606
618
  if (rotate) rotate = angleToDegrees(rotate)
607
619
 
608
620
  // 计算缩放因子
609
- const ws = cx / chcx
610
- const hs = cy / chcy
621
+ const ws = chcx === 0 ? 0 : cx / chcx
622
+ const hs = chcy === 0 ? 0 : cy / chcy
611
623
 
612
624
  // 构建当前组合层级(将当前组合添加到父级层级中)
613
625
  const currentGroupHierarchy = [...parentGroupHierarchy, node]
@@ -626,30 +638,44 @@ async function processGroupSpNode(node, warpObj, source, parentGroupHierarchy =
626
638
  }
627
639
  }
628
640
 
641
+ const transformGroupedElement = (element, offsetX = 0, offsetY = 0) => {
642
+ const elementRotate = element.rotate || 0
643
+ const normalizedRotate = ((elementRotate % 360) + 360) % 360
644
+ const isUniformScale = Math.abs(ws - hs) < 0.000001
645
+ const shouldSwapDimensions = normalizedRotate === 90 || normalizedRotate === 270
646
+ const centerX = element.left + element.width / 2
647
+ const centerY = element.top + element.height / 2
648
+ const nextCenterX = (centerX - offsetX) * ws
649
+ const nextCenterY = (centerY - offsetY) * hs
650
+ const widthScale = shouldSwapDimensions && !isUniformScale ? hs : ws
651
+ const heightScale = shouldSwapDimensions && !isUniformScale ? ws : hs
652
+ const width = element.width * widthScale
653
+ const height = element.height * heightScale
654
+
655
+ const transformed = {
656
+ ...element,
657
+ left: numberToFixed(nextCenterX - width / 2),
658
+ top: numberToFixed(nextCenterY - height / 2),
659
+ width: numberToFixed(width),
660
+ height: numberToFixed(height),
661
+ }
662
+ return transformed
663
+ }
664
+
629
665
  const processedElements = elements.map(element => ({
630
- ...element,
631
- left: numberToFixed((element.left - chx) * ws),
632
- top: numberToFixed((element.top - chy) * hs),
633
- width: numberToFixed(element.width * ws),
634
- height: numberToFixed(element.height * hs),
666
+ ...transformGroupedElement(element, chx, chy),
635
667
  ...(element.type === 'group' && element.elements ? {
636
- elements: processNestedGroupElements(element.elements, ws, hs)
668
+ elements: processNestedGroupElements(element.elements)
637
669
  } : {})
638
670
  }))
639
671
 
640
- function processNestedGroupElements(elements, ws, hs, depth = 0) {
672
+ function processNestedGroupElements(elements, depth = 0) {
641
673
  if (depth > 10) return elements
642
674
 
643
675
  return elements.map(element => {
644
- const processed = {
645
- ...element,
646
- left: numberToFixed(element.left * ws),
647
- top: numberToFixed(element.top * hs),
648
- width: numberToFixed(element.width * ws),
649
- height: numberToFixed(element.height * hs),
650
- }
676
+ const processed = transformGroupedElement(element)
651
677
  if (element.type === 'group' && element.elements) {
652
- processed.elements = processNestedGroupElements(element.elements, ws, hs, depth + 1)
678
+ processed.elements = processNestedGroupElements(element.elements, depth + 1)
653
679
  }
654
680
  return processed
655
681
  })
@@ -767,7 +793,11 @@ async function genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type,
767
793
  if (node['p:txBody']) content = genTextBody(node['p:txBody'], node, slideLayoutSpNode, slideMasterSpNode, type, warpObj)
768
794
 
769
795
  const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(node, type, warpObj)
770
- const fill = await getShapeFill(node, warpObj, source, groupHierarchy)
796
+ const fill = await getShapeFill(node, warpObj, source, {
797
+ groupHierarchy,
798
+ slideLayoutSpNode,
799
+ slideMasterSpNode,
800
+ })
771
801
 
772
802
  let shadow
773
803
  const outerShdwNode = getTextByPathList(node, ['p:spPr', 'a:effectLst', 'a:outerShdw'])
@@ -811,12 +841,15 @@ async function genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type,
811
841
  const d = getCustomShapePath(custShapType, w, h)
812
842
  if (!isHasValidText) data.content = ''
813
843
 
814
- return {
844
+ const customShapeData = {
815
845
  ...data,
816
846
  type: 'shape',
817
847
  shapType: 'custom',
818
848
  path: d,
819
849
  }
850
+ if (isStrokeOnlyCustomGeometry(custShapType)) customShapeData.strokeOnly = true
851
+
852
+ return customShapeData
820
853
  }
821
854
 
822
855
  let shapePath = ''
package/src/shape.js CHANGED
@@ -223,6 +223,15 @@ export function getCustomShapePath(custShapType, w, h) {
223
223
  return d
224
224
  }
225
225
 
226
+ export function isStrokeOnlyCustomGeometry(custShapType) {
227
+ const pathNodes = getTextByPathList(custShapType, ['a:pathLst', 'a:path'])
228
+ if (!pathNodes) return false
229
+
230
+ const pathNodeList = Array.isArray(pathNodes) ? pathNodes : [pathNodes]
231
+
232
+ return pathNodeList.length === 1 && getTextByPathList(pathNodeList[0], ['attrs', 'fill']) === 'none'
233
+ }
234
+
226
235
  export function identifyShape(shapeData) {
227
236
  const pathLst = shapeData['a:pathLst']
228
237
  if (!pathLst || !pathLst['a:path']) return 'custom'
package/src/text.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getHorizontalAlign, getParagraphSpacing } from './paragraph'
1
+ import { getHorizontalAlign, getParagraphSpacing, getParagraphIndent } from './paragraph'
2
2
  import { getTextByPathList } from './utils'
3
3
 
4
4
  import {
@@ -25,9 +25,9 @@ export function genTextBody(textBodyNode, spNode, slideLayoutSpNode, slideMaster
25
25
 
26
26
  let text = ''
27
27
 
28
- const pFontStyle = getTextByPathList(spNode, ['p:style', 'a:fontRef'])
29
- const slideMasterTextStyles = spNode && spNode['a:tcPr'] ? undefined : warpObj['slideMasterTextStyles']
30
- const defaultTextStyle = spNode && spNode['a:tcPr'] ? warpObj['defaultTextStyle'] : undefined
28
+ const pFontStyle = getTextByPathList(spNode, ['p:style', 'a:fontRef'])
29
+ const slideMasterTextStyles = spNode && spNode['a:tcPr'] ? undefined : warpObj['slideMasterTextStyles']
30
+ const defaultTextStyle = spNode && spNode['a:tcPr'] ? warpObj['defaultTextStyle'] : undefined
31
31
 
32
32
  const pNode = textBodyNode['a:p']
33
33
  const pNodes = pNode.constructor === Array ? pNode : [pNode]
@@ -59,7 +59,10 @@ export function genTextBody(textBodyNode, spNode, slideLayoutSpNode, slideMaster
59
59
  }
60
60
 
61
61
  const align = getHorizontalAlign(pNode, spNode, type, slideLayoutSpNode, slideMasterSpNode, warpObj)
62
- const spacing = getParagraphSpacing(pNode, textBodyNode, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, warpObj)
62
+ const spacing = getParagraphSpacing(pNode, textBodyNode, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, warpObj)
63
+ const indent = getParagraphIndent(pNode, textBodyNode, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, warpObj)
64
+ const listType = getListType(pNode)
65
+ const listLevel = getListLevel(pNode)
63
66
 
64
67
  let styleText = `text-align: ${align};`
65
68
  if (spacing) {
@@ -67,9 +70,10 @@ export function genTextBody(textBodyNode, spNode, slideLayoutSpNode, slideMaster
67
70
  if (spacing.spaceBefore) styleText += `margin-top: ${spacing.spaceBefore};`
68
71
  if (spacing.spaceAfter) styleText += `margin-bottom: ${spacing.spaceAfter};`
69
72
  }
70
-
71
- const listType = getListType(pNode)
72
- const listLevel = getListLevel(pNode)
73
+ if (indent) {
74
+ if (!listType && indent.marginLeft) styleText += `margin-left: ${indent.marginLeft};`
75
+ if (!listType && indent.textIndent) styleText += `text-indent: ${indent.textIndent};`
76
+ }
73
77
 
74
78
  if (listType) {
75
79
  while (listTypes.length > listLevel + 1) {
@@ -86,7 +90,7 @@ export function genTextBody(textBodyNode, spNode, slideLayoutSpNode, slideMaster
86
90
  text += `<${listType}>`
87
91
  listTypes[listLevel] = listType
88
92
  }
89
- text += `<li style="${styleText}">`
93
+ text += `<li><p style="${styleText}">`
90
94
  }
91
95
  else {
92
96
  while (listTypes.length > 0) {
@@ -97,14 +101,14 @@ export function genTextBody(textBodyNode, spNode, slideLayoutSpNode, slideMaster
97
101
  }
98
102
 
99
103
  if (!rNode) {
100
- text += genSpanElement(pNode, spNode, textBodyNode, pFontStyle, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, defaultTextStyle, warpObj)
104
+ text += genSpanElement(pNode, spNode, textBodyNode, pFontStyle, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, defaultTextStyle, warpObj)
101
105
  }
102
106
  else {
103
107
  let prevStyleInfo = null
104
108
  let accumulatedText = ''
105
109
 
106
110
  for (const rNodeItem of rNode) {
107
- const styleInfo = getSpanStyleInfo(rNodeItem, pNode, textBodyNode, pFontStyle, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, defaultTextStyle, warpObj)
111
+ const styleInfo = getSpanStyleInfo(rNodeItem, pNode, textBodyNode, pFontStyle, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, defaultTextStyle, warpObj)
108
112
 
109
113
  if (!prevStyleInfo || prevStyleInfo.styleText !== styleInfo.styleText || prevStyleInfo.hasLink !== styleInfo.hasLink || styleInfo.hasLink) {
110
114
  if (accumulatedText) {
@@ -132,7 +136,7 @@ export function genTextBody(textBodyNode, spNode, slideLayoutSpNode, slideMaster
132
136
  }
133
137
  }
134
138
 
135
- if (listType) text += '</li>'
139
+ if (listType) text += '</p></li>'
136
140
  else text += '</p>'
137
141
  }
138
142
  while (listTypes.length > 0) {
@@ -161,8 +165,8 @@ export function getListLevel(node) {
161
165
  return 0
162
166
  }
163
167
 
164
- export function genSpanElement(node, pNode, textBodyNode, pFontStyle, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, defaultTextStyle, warpObj) {
165
- const { styleText, text, hasLink, linkURL } = getSpanStyleInfo(node, pNode, textBodyNode, pFontStyle, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, defaultTextStyle, warpObj)
168
+ export function genSpanElement(node, pNode, textBodyNode, pFontStyle, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, defaultTextStyle, warpObj) {
169
+ const { styleText, text, hasLink, linkURL } = getSpanStyleInfo(node, pNode, textBodyNode, pFontStyle, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, defaultTextStyle, warpObj)
166
170
  const processedText = text.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;').replace(/\s/g, '&nbsp;')
167
171
 
168
172
  if (hasLink) {
@@ -171,7 +175,7 @@ export function genSpanElement(node, pNode, textBodyNode, pFontStyle, slideLayou
171
175
  return `<span style="${styleText}">${processedText}</span>`
172
176
  }
173
177
 
174
- export function getSpanStyleInfo(node, pNode, textBodyNode, pFontStyle, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, defaultTextStyle, warpObj) {
178
+ export function getSpanStyleInfo(node, pNode, textBodyNode, pFontStyle, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, defaultTextStyle, warpObj) {
175
179
  let lvl = 1
176
180
  const pPrNode = pNode['a:pPr']
177
181
  const lvlNode = getTextByPathList(pPrNode, ['attrs', 'lvl'])
@@ -183,7 +187,7 @@ export function getSpanStyleInfo(node, pNode, textBodyNode, pFontStyle, slideLay
183
187
 
184
188
  let styleText = ''
185
189
  const fontColor = getFontColor(node, pNode, textBodyNode, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, lvl, pFontStyle, warpObj)
186
- const fontSize = getFontSize(node, pNode, textBodyNode, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, lvl, defaultTextStyle)
190
+ const fontSize = getFontSize(node, pNode, textBodyNode, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, lvl, defaultTextStyle)
187
191
  const fontType = getFontType(node, pNode, textBodyNode, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, lvl, warpObj)
188
192
  const fontBold = getFontBold(node, pNode, textBodyNode, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, lvl)
189
193
  const fontItalic = getFontItalic(node, pNode, textBodyNode, slideLayoutSpNode, slideMasterSpNode, type, slideMasterTextStyles, lvl)
@@ -221,4 +225,4 @@ export function getSpanStyleInfo(node, pNode, textBodyNode, pFontStyle, slideLay
221
225
  hasLink,
222
226
  linkURL: hasLink ? warpObj['slideResObj'][linkID]['target'] : null
223
227
  }
224
- }
228
+ }