pptxtojson 2.0.4 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pptxtojson",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
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
@@ -702,25 +702,47 @@ export async function getSlideBackgroundFill(warpObj) {
702
702
  }
703
703
  }
704
704
 
705
- export async function getShapeFill(node, warpObj, source, groupHierarchy = []) {
706
- const fillType = getFillType(getTextByPathList(node, ['p:spPr']))
705
+ function getShapeFillCandidates(node, source, slideLayoutSpNode, slideMasterSpNode) {
706
+ const candidates = [{ node, source }]
707
+
708
+ if (slideLayoutSpNode) {
709
+ candidates.push({
710
+ node: slideLayoutSpNode,
711
+ source: 'slideLayoutBg',
712
+ })
713
+ }
714
+ if (slideMasterSpNode) {
715
+ candidates.push({
716
+ node: slideMasterSpNode,
717
+ source: 'slideMasterBg',
718
+ })
719
+ }
720
+
721
+ return candidates
722
+ }
723
+
724
+ async function resolveShapeFillFromNode(node, warpObj, source, groupHierarchy) {
725
+ if (!node) return { state: 'missing' }
726
+
727
+ const spPr = getTextByPathList(node, ['p:spPr'])
728
+ const fillType = spPr ? getFillType(spPr) : ''
707
729
  let type = 'color'
708
730
  let fillValue = ''
709
731
  if (fillType === 'NO_FILL') {
710
- return null
732
+ return { state: 'none' }
711
733
  }
712
734
  else if (fillType === 'SOLID_FILL') {
713
- const shpFill = node['p:spPr']['a:solidFill']
735
+ const shpFill = spPr['a:solidFill']
714
736
  fillValue = getSolidFill(shpFill, undefined, undefined, warpObj)
715
737
  type = 'color'
716
738
  }
717
739
  else if (fillType === 'GRADIENT_FILL') {
718
- const shpFill = node['p:spPr']['a:gradFill']
740
+ const shpFill = spPr['a:gradFill']
719
741
  fillValue = getGradientFill(shpFill, warpObj)
720
742
  type = 'gradient'
721
743
  }
722
744
  else if (fillType === 'PIC_FILL') {
723
- const shpFill = node['p:spPr']['a:blipFill']
745
+ const shpFill = spPr['a:blipFill']
724
746
  const picFill = await getPicFill(source, shpFill, warpObj)
725
747
  const opacity = getPicFillOpacity(shpFill)
726
748
  fillValue = {
@@ -732,12 +754,13 @@ export async function getShapeFill(node, warpObj, source, groupHierarchy = []) {
732
754
  type = 'image'
733
755
  }
734
756
  else if (fillType === 'PATTERN_FILL') {
735
- const shpFill = node['p:spPr']['a:pattFill']
757
+ const shpFill = spPr['a:pattFill']
736
758
  fillValue = getPatternFill({ 'a:pattFill': shpFill }, warpObj)
737
759
  type = 'pattern'
738
760
  }
739
761
  else if (fillType === 'GROUP_FILL') {
740
- return findFillInGroupHierarchy(groupHierarchy, warpObj, source)
762
+ const groupFill = await findFillInGroupHierarchy(groupHierarchy, warpObj, source)
763
+ return groupFill ? { state: 'found', fill: groupFill } : { state: 'none' }
741
764
  }
742
765
  if (!fillValue) {
743
766
  const clrName = getTextByPathList(node, ['p:style', 'a:fillRef'])
@@ -745,15 +768,36 @@ export async function getShapeFill(node, warpObj, source, groupHierarchy = []) {
745
768
  type = 'color'
746
769
  }
747
770
  if (!fillValue) {
748
- return null
771
+ return { state: 'missing' }
749
772
  }
750
773
 
751
774
  return {
752
- type,
753
- value: fillValue,
775
+ state: 'found',
776
+ fill: {
777
+ type,
778
+ value: fillValue,
779
+ }
754
780
  }
755
781
  }
756
782
 
783
+ export async function getShapeFill(node, warpObj, source, options = {}) {
784
+ const {
785
+ groupHierarchy = [],
786
+ slideLayoutSpNode,
787
+ slideMasterSpNode,
788
+ } = options
789
+
790
+ const candidates = getShapeFillCandidates(node, source, slideLayoutSpNode, slideMasterSpNode)
791
+ for (const candidate of candidates) {
792
+ const result = await resolveShapeFillFromNode(candidate.node, warpObj, candidate.source, groupHierarchy)
793
+
794
+ if (result.state === 'none') return null
795
+ if (result.state === 'found') return result.fill
796
+ }
797
+
798
+ return null
799
+ }
800
+
757
801
  async function findFillInGroupHierarchy(groupHierarchy, warpObj, source) {
758
802
  for (const groupNode of groupHierarchy) {
759
803
  if (!groupNode || !groupNode['p:grpSpPr']) continue
@@ -892,4 +936,4 @@ export function getSolidFill(solidFill, clrMap, phClr, warpObj) {
892
936
  if (color && color.indexOf('#') === -1) color = '#' + color
893
937
 
894
938
  return color
895
- }
939
+ }
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'
@@ -618,8 +618,8 @@ async function processGroupSpNode(node, warpObj, source, parentGroupHierarchy =
618
618
  if (rotate) rotate = angleToDegrees(rotate)
619
619
 
620
620
  // 计算缩放因子
621
- const ws = cx / chcx
622
- const hs = cy / chcy
621
+ const ws = chcx === 0 ? 0 : cx / chcx
622
+ const hs = chcy === 0 ? 0 : cy / chcy
623
623
 
624
624
  // 构建当前组合层级(将当前组合添加到父级层级中)
625
625
  const currentGroupHierarchy = [...parentGroupHierarchy, node]
@@ -638,30 +638,44 @@ async function processGroupSpNode(node, warpObj, source, parentGroupHierarchy =
638
638
  }
639
639
  }
640
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
+
641
665
  const processedElements = elements.map(element => ({
642
- ...element,
643
- left: numberToFixed((element.left - chx) * ws),
644
- top: numberToFixed((element.top - chy) * hs),
645
- width: numberToFixed(element.width * ws),
646
- height: numberToFixed(element.height * hs),
666
+ ...transformGroupedElement(element, chx, chy),
647
667
  ...(element.type === 'group' && element.elements ? {
648
- elements: processNestedGroupElements(element.elements, ws, hs)
668
+ elements: processNestedGroupElements(element.elements)
649
669
  } : {})
650
670
  }))
651
671
 
652
- function processNestedGroupElements(elements, ws, hs, depth = 0) {
672
+ function processNestedGroupElements(elements, depth = 0) {
653
673
  if (depth > 10) return elements
654
674
 
655
675
  return elements.map(element => {
656
- const processed = {
657
- ...element,
658
- left: numberToFixed(element.left * ws),
659
- top: numberToFixed(element.top * hs),
660
- width: numberToFixed(element.width * ws),
661
- height: numberToFixed(element.height * hs),
662
- }
676
+ const processed = transformGroupedElement(element)
663
677
  if (element.type === 'group' && element.elements) {
664
- processed.elements = processNestedGroupElements(element.elements, ws, hs, depth + 1)
678
+ processed.elements = processNestedGroupElements(element.elements, depth + 1)
665
679
  }
666
680
  return processed
667
681
  })
@@ -779,7 +793,11 @@ async function genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type,
779
793
  if (node['p:txBody']) content = genTextBody(node['p:txBody'], node, slideLayoutSpNode, slideMasterSpNode, type, warpObj)
780
794
 
781
795
  const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(node, type, warpObj)
782
- const fill = await getShapeFill(node, warpObj, source, groupHierarchy)
796
+ const fill = await getShapeFill(node, warpObj, source, {
797
+ groupHierarchy,
798
+ slideLayoutSpNode,
799
+ slideMasterSpNode,
800
+ })
783
801
 
784
802
  let shadow
785
803
  const outerShdwNode = getTextByPathList(node, ['p:spPr', 'a:effectLst', 'a:outerShdw'])
@@ -823,12 +841,15 @@ async function genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type,
823
841
  const d = getCustomShapePath(custShapType, w, h)
824
842
  if (!isHasValidText) data.content = ''
825
843
 
826
- return {
844
+ const customShapeData = {
827
845
  ...data,
828
846
  type: 'shape',
829
847
  shapType: 'custom',
830
848
  path: d,
831
849
  }
850
+ if (isStrokeOnlyCustomGeometry(custShapType)) customShapeData.strokeOnly = true
851
+
852
+ return customShapeData
832
853
  }
833
854
 
834
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'