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/README.md +352 -337
- package/README_zh.md +352 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/fill.js +56 -12
- package/src/pptxtojson.js +41 -20
- package/src/shape.js +9 -0
package/package.json
CHANGED
package/src/fill.js
CHANGED
|
@@ -702,25 +702,47 @@ export async function getSlideBackgroundFill(warpObj) {
|
|
|
702
702
|
}
|
|
703
703
|
}
|
|
704
704
|
|
|
705
|
-
|
|
706
|
-
const
|
|
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
|
|
732
|
+
return { state: 'none' }
|
|
711
733
|
}
|
|
712
734
|
else if (fillType === 'SOLID_FILL') {
|
|
713
|
-
const shpFill =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
|
771
|
+
return { state: 'missing' }
|
|
749
772
|
}
|
|
750
773
|
|
|
751
774
|
return {
|
|
752
|
-
|
|
753
|
-
|
|
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
|
|
668
|
+
elements: processNestedGroupElements(element.elements)
|
|
649
669
|
} : {})
|
|
650
670
|
}))
|
|
651
671
|
|
|
652
|
-
function processNestedGroupElements(elements,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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'
|