pptxtojson 2.0.4 → 2.0.6
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 +353 -337
- package/README_zh.md +353 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +20 -11
- 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 +56 -23
- 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'
|
|
@@ -267,7 +267,9 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
|
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
let currentThemeContent = themeContent
|
|
270
271
|
if (themeFilename) {
|
|
272
|
+
currentThemeContent = currentThemeContent || await readXmlFileCached(zip, themeFilename, xmlCache)
|
|
271
273
|
const themeName = themeFilename.split('/').pop()
|
|
272
274
|
const themeResFileName = themeFilename.replace(themeName, '_rels/' + themeName) + '.rels'
|
|
273
275
|
const themeResContent = await readXmlFile(zip, themeResFileName)
|
|
@@ -305,7 +307,7 @@ async function processSingleSlide(zip, sldFileName, themeContent, defaultTextSty
|
|
|
305
307
|
slideMasterTextStyles,
|
|
306
308
|
layoutResObj,
|
|
307
309
|
masterResObj,
|
|
308
|
-
themeContent,
|
|
310
|
+
themeContent: currentThemeContent,
|
|
309
311
|
themeResObj,
|
|
310
312
|
diagramFileCache: {},
|
|
311
313
|
defaultTextStyle,
|
|
@@ -618,8 +620,8 @@ async function processGroupSpNode(node, warpObj, source, parentGroupHierarchy =
|
|
|
618
620
|
if (rotate) rotate = angleToDegrees(rotate)
|
|
619
621
|
|
|
620
622
|
// 计算缩放因子
|
|
621
|
-
const ws = cx / chcx
|
|
622
|
-
const hs = cy / chcy
|
|
623
|
+
const ws = chcx === 0 ? 0 : cx / chcx
|
|
624
|
+
const hs = chcy === 0 ? 0 : cy / chcy
|
|
623
625
|
|
|
624
626
|
// 构建当前组合层级(将当前组合添加到父级层级中)
|
|
625
627
|
const currentGroupHierarchy = [...parentGroupHierarchy, node]
|
|
@@ -638,30 +640,44 @@ async function processGroupSpNode(node, warpObj, source, parentGroupHierarchy =
|
|
|
638
640
|
}
|
|
639
641
|
}
|
|
640
642
|
|
|
643
|
+
const transformGroupedElement = (element, offsetX = 0, offsetY = 0) => {
|
|
644
|
+
const elementRotate = element.rotate || 0
|
|
645
|
+
const normalizedRotate = ((elementRotate % 360) + 360) % 360
|
|
646
|
+
const isUniformScale = Math.abs(ws - hs) < 0.000001
|
|
647
|
+
const shouldSwapDimensions = normalizedRotate === 90 || normalizedRotate === 270
|
|
648
|
+
const centerX = element.left + element.width / 2
|
|
649
|
+
const centerY = element.top + element.height / 2
|
|
650
|
+
const nextCenterX = (centerX - offsetX) * ws
|
|
651
|
+
const nextCenterY = (centerY - offsetY) * hs
|
|
652
|
+
const widthScale = shouldSwapDimensions && !isUniformScale ? hs : ws
|
|
653
|
+
const heightScale = shouldSwapDimensions && !isUniformScale ? ws : hs
|
|
654
|
+
const width = element.width * widthScale
|
|
655
|
+
const height = element.height * heightScale
|
|
656
|
+
|
|
657
|
+
const transformed = {
|
|
658
|
+
...element,
|
|
659
|
+
left: numberToFixed(nextCenterX - width / 2),
|
|
660
|
+
top: numberToFixed(nextCenterY - height / 2),
|
|
661
|
+
width: numberToFixed(width),
|
|
662
|
+
height: numberToFixed(height),
|
|
663
|
+
}
|
|
664
|
+
return transformed
|
|
665
|
+
}
|
|
666
|
+
|
|
641
667
|
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),
|
|
668
|
+
...transformGroupedElement(element, chx, chy),
|
|
647
669
|
...(element.type === 'group' && element.elements ? {
|
|
648
|
-
elements: processNestedGroupElements(element.elements
|
|
670
|
+
elements: processNestedGroupElements(element.elements)
|
|
649
671
|
} : {})
|
|
650
672
|
}))
|
|
651
673
|
|
|
652
|
-
function processNestedGroupElements(elements,
|
|
674
|
+
function processNestedGroupElements(elements, depth = 0) {
|
|
653
675
|
if (depth > 10) return elements
|
|
654
676
|
|
|
655
677
|
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
|
-
}
|
|
678
|
+
const processed = transformGroupedElement(element)
|
|
663
679
|
if (element.type === 'group' && element.elements) {
|
|
664
|
-
processed.elements = processNestedGroupElements(element.elements,
|
|
680
|
+
processed.elements = processNestedGroupElements(element.elements, depth + 1)
|
|
665
681
|
}
|
|
666
682
|
return processed
|
|
667
683
|
})
|
|
@@ -761,6 +777,7 @@ async function genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type,
|
|
|
761
777
|
|
|
762
778
|
const { top, left } = getPosition(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode)
|
|
763
779
|
const { width, height } = getSize(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode)
|
|
780
|
+
const pathViewBox = { x: 0, y: 0, width, height }
|
|
764
781
|
|
|
765
782
|
const isFlipV = getTextByPathList(slideXfrmNode, ['attrs', 'flipV']) === '1'
|
|
766
783
|
const isFlipH = getTextByPathList(slideXfrmNode, ['attrs', 'flipH']) === '1'
|
|
@@ -779,7 +796,11 @@ async function genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type,
|
|
|
779
796
|
if (node['p:txBody']) content = genTextBody(node['p:txBody'], node, slideLayoutSpNode, slideMasterSpNode, type, warpObj)
|
|
780
797
|
|
|
781
798
|
const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(node, type, warpObj)
|
|
782
|
-
const fill = await getShapeFill(node, warpObj, source,
|
|
799
|
+
const fill = await getShapeFill(node, warpObj, source, {
|
|
800
|
+
groupHierarchy,
|
|
801
|
+
slideLayoutSpNode,
|
|
802
|
+
slideMasterSpNode,
|
|
803
|
+
})
|
|
783
804
|
|
|
784
805
|
let shadow
|
|
785
806
|
const outerShdwNode = getTextByPathList(node, ['p:spPr', 'a:effectLst', 'a:outerShdw'])
|
|
@@ -823,36 +844,48 @@ async function genShape(node, slideLayoutSpNode, slideMasterSpNode, name, type,
|
|
|
823
844
|
const d = getCustomShapePath(custShapType, w, h)
|
|
824
845
|
if (!isHasValidText) data.content = ''
|
|
825
846
|
|
|
826
|
-
|
|
847
|
+
const customShapeData = {
|
|
827
848
|
...data,
|
|
828
849
|
type: 'shape',
|
|
829
850
|
shapType: 'custom',
|
|
830
851
|
path: d,
|
|
852
|
+
pathViewBox: { x: 0, y: 0, width: w, height: h },
|
|
831
853
|
}
|
|
854
|
+
if (isStrokeOnlyCustomGeometry(custShapType)) customShapeData.strokeOnly = true
|
|
855
|
+
|
|
856
|
+
return customShapeData
|
|
832
857
|
}
|
|
833
858
|
|
|
834
859
|
let shapePath = ''
|
|
835
860
|
if (shapType) shapePath = getShapePath(shapType, width, height, node)
|
|
861
|
+
const STROKE_ONLY_PRESET_SHAPE_TYPES = ['arc', 'leftBrace', 'rightBrace', 'bracePair', 'leftBracket', 'rightBracket', 'bracketPair']
|
|
862
|
+
const isStrokeOnlyPresetShape = STROKE_ONLY_PRESET_SHAPE_TYPES.includes(shapType)
|
|
836
863
|
|
|
837
864
|
if (shapType && (type === 'obj' || !type || shapType !== 'rect')) {
|
|
838
865
|
if (!isHasValidText) data.content = ''
|
|
839
|
-
|
|
866
|
+
const shapeData = {
|
|
840
867
|
...data,
|
|
841
868
|
type: 'shape',
|
|
842
869
|
shapType,
|
|
843
870
|
path: shapePath,
|
|
871
|
+
pathViewBox,
|
|
844
872
|
keypoints,
|
|
845
873
|
}
|
|
874
|
+
if (isStrokeOnlyPresetShape) shapeData.strokeOnly = true
|
|
875
|
+
return shapeData
|
|
846
876
|
}
|
|
847
877
|
if (shapType && !isHasValidText && (fill || borderWidth)) {
|
|
848
|
-
|
|
878
|
+
const shapeData = {
|
|
849
879
|
...data,
|
|
850
880
|
type: 'shape',
|
|
851
881
|
content: '',
|
|
852
882
|
shapType,
|
|
853
883
|
path: shapePath,
|
|
884
|
+
pathViewBox,
|
|
854
885
|
keypoints,
|
|
855
886
|
}
|
|
887
|
+
if (isStrokeOnlyPresetShape) shapeData.strokeOnly = true
|
|
888
|
+
return shapeData
|
|
856
889
|
}
|
|
857
890
|
return {
|
|
858
891
|
...data,
|
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'
|