李志伟 1 mês atrás
pai
commit
293d79d56f

+ 2 - 0
package.json

@@ -32,6 +32,8 @@
         "pptxtojson": "^1.3.1",
         "handsontable": "^11.1.0",
         "tinycolor2": "^1.6.0",
+        "jszip": "^3.10.1",
+        "txml": "^5.1.1",
         "vue": "2.7.16",
         "vue-router": "^3.5.1",
         "vuex": "^3.0.1",

+ 6 - 0
pnpm-lock.yaml

@@ -38,6 +38,9 @@ importers:
       js-cookie:
         specifier: ^3.0.5
         version: 3.0.5
+      jszip:
+        specifier: ^3.10.1
+        version: 3.10.1
       lodash:
         specifier: ^4.17.21
         version: 4.17.21
@@ -53,6 +56,9 @@ importers:
       tinycolor2:
         specifier: ^1.6.0
         version: 1.6.0
+      txml:
+        specifier: ^5.1.1
+        version: 5.1.1
       vue:
         specifier: 2.7.16
         version: 2.7.16

+ 2 - 2
src/components/view_file/components/vendors/pptx/index.js

@@ -2,12 +2,12 @@
  * @Author: LiZhiWei
  * @Date: 2025-04-09 10:49:05
  * @LastEditors: LiZhiWei
- * @LastEditTime: 2025-04-17 11:49:20
+ * @LastEditTime: 2025-04-17 15:08:42
  * @Description:
  */
 import Vue from "vue"
 import PPT from "./PPT.vue"
-import { parse } from "@/utils/pptxToJson/index"
+import { parse } from "@/utils/pptxToJson/pptxtojson"
 /**
  * 渲染ppt
  */

+ 65 - 0
src/utils/pptxToJson/align.js

@@ -0,0 +1,65 @@
+import { getTextByPathList } from './utils'
+
+export function getHorizontalAlign(node, pNode, type, warpObj) {
+  let algn = getTextByPathList(node, ['a:pPr', 'attrs', 'algn'])
+  if (!algn) algn = getTextByPathList(pNode, ['a:pPr', 'attrs', 'algn'])
+
+  if (!algn) {
+    if (type === 'title' || type === 'ctrTitle' || type === 'subTitle') {
+      let lvlIdx = 1
+      const lvlNode = getTextByPathList(pNode, ['a:pPr', 'attrs', 'lvl'])
+      if (lvlNode) {
+        lvlIdx = parseInt(lvlNode) + 1
+      }
+      const lvlStr = 'a:lvl' + lvlIdx + 'pPr'
+      algn = getTextByPathList(warpObj, ['slideLayoutTables', 'typeTable', type, 'p:txBody', 'a:lstStyle', lvlStr, 'attrs', 'algn'])
+      if (!algn) algn = getTextByPathList(warpObj, ['slideMasterTables', 'typeTable', type, 'p:txBody', 'a:lstStyle', lvlStr, 'attrs', 'algn'])
+      if (!algn) algn = getTextByPathList(warpObj, ['slideMasterTextStyles', 'p:titleStyle', lvlStr, 'attrs', 'algn'])
+      if (!algn && type === 'subTitle') {
+        algn = getTextByPathList(warpObj, ['slideMasterTextStyles', 'p:bodyStyle', lvlStr, 'attrs', 'algn'])
+      }
+    } 
+    else if (type === 'body') {
+      algn = getTextByPathList(warpObj, ['slideMasterTextStyles', 'p:bodyStyle', 'a:lvl1pPr', 'attrs', 'algn'])
+    } 
+    else {
+      algn = getTextByPathList(warpObj, ['slideMasterTables', 'typeTable', type, 'p:txBody', 'a:lstStyle', 'a:lvl1pPr', 'attrs', 'algn'])
+    }
+  }
+
+  let align = 'left'
+  if (algn) {
+    switch (algn) {
+      case 'l':
+        align = 'left'
+        break
+      case 'r':
+        align = 'right'
+        break
+      case 'ctr':
+        align = 'center'
+        break
+      case 'just':
+        align = 'justify'
+        break
+      case 'dist':
+        align = 'justify'
+        break
+      default:
+        align = 'inherit'
+    }
+  }
+  return align
+}
+
+export function getVerticalAlign(node, slideLayoutSpNode, slideMasterSpNode) {
+  let anchor = getTextByPathList(node, ['p:txBody', 'a:bodyPr', 'attrs', 'anchor'])
+  if (!anchor) {
+    anchor = getTextByPathList(slideLayoutSpNode, ['p:txBody', 'a:bodyPr', 'attrs', 'anchor'])
+    if (!anchor) {
+      anchor = getTextByPathList(slideMasterSpNode, ['p:txBody', 'a:bodyPr', 'attrs', 'anchor'])
+      if (!anchor) anchor = 't'
+    }
+  }
+  return (anchor === 'ctr') ? 'mid' : ((anchor === 'b') ? 'down' : 'up')
+}

+ 105 - 0
src/utils/pptxToJson/border.js

@@ -0,0 +1,105 @@
+import tinycolor from 'tinycolor2'
+import { getSchemeColorFromTheme } from './schemeColor'
+import { getTextByPathList } from './utils'
+
+export function getBorder(node, elType, warpObj) {
+  let lineNode = getTextByPathList(node, ['p:spPr', 'a:ln'])
+  if (!lineNode) {
+    const lnRefNode = getTextByPathList(node, ['p:style', 'a:lnRef'])
+    if (lnRefNode) {
+      const lnIdx = getTextByPathList(lnRefNode, ['attrs', 'idx'])
+      lineNode = warpObj['themeContent']['a:theme']['a:themeElements']['a:fmtScheme']['a:lnStyleLst']['a:ln'][Number(lnIdx) - 1]
+    }
+  }
+  if (!lineNode) lineNode = node
+
+  const isNoFill = getTextByPathList(lineNode, ['a:noFill'])
+
+  let borderWidth = isNoFill ? 0 : (parseInt(getTextByPathList(lineNode, ['attrs', 'w'])) / 12700)
+  if (isNaN(borderWidth)) {
+    if (lineNode) borderWidth = 0
+    else if (elType !== 'obj') borderWidth = 0
+    else borderWidth = 1
+  }
+
+  let borderColor = getTextByPathList(lineNode, ['a:solidFill', 'a:srgbClr', 'attrs', 'val'])
+  if (!borderColor) {
+    const schemeClrNode = getTextByPathList(lineNode, ['a:solidFill', 'a:schemeClr'])
+    const schemeClr = 'a:' + getTextByPathList(schemeClrNode, ['attrs', 'val'])
+    borderColor = getSchemeColorFromTheme(schemeClr, warpObj)
+  }
+
+  if (!borderColor) {
+    const schemeClrNode = getTextByPathList(node, ['p:style', 'a:lnRef', 'a:schemeClr'])
+    const schemeClr = 'a:' + getTextByPathList(schemeClrNode, ['attrs', 'val'])
+    borderColor = getSchemeColorFromTheme(schemeClr, warpObj)
+
+    if (borderColor) {
+      let shade = getTextByPathList(schemeClrNode, ['a:shade', 'attrs', 'val'])
+
+      if (shade) {
+        shade = parseInt(shade) / 100000
+        
+        const color = tinycolor('#' + borderColor).toHsl()
+        borderColor = tinycolor({ h: color.h, s: color.s, l: color.l * shade, a: color.a }).toHex()
+      }
+    }
+  }
+
+  if (!borderColor) borderColor = '#000000'
+  else borderColor = `#${borderColor}`
+
+  const type = getTextByPathList(lineNode, ['a:prstDash', 'attrs', 'val'])
+  let borderType = 'solid'
+  let strokeDasharray = '0'
+  switch (type) {
+    case 'solid':
+      borderType = 'solid'
+      strokeDasharray = '0'
+      break
+    case 'dash':
+      borderType = 'dashed'
+      strokeDasharray = '5'
+      break
+    case 'dashDot':
+      borderType = 'dashed'
+      strokeDasharray = '5, 5, 1, 5'
+      break
+    case 'dot':
+      borderType = 'dotted'
+      strokeDasharray = '1, 5'
+      break
+    case 'lgDash':
+      borderType = 'dashed'
+      strokeDasharray = '10, 5'
+      break
+    case 'lgDashDotDot':
+      borderType = 'dotted'
+      strokeDasharray = '10, 5, 1, 5, 1, 5'
+      break
+    case 'sysDash':
+      borderType = 'dashed'
+      strokeDasharray = '5, 2'
+      break
+    case 'sysDashDot':
+      borderType = 'dotted'
+      strokeDasharray = '5, 2, 1, 5'
+      break
+    case 'sysDashDotDot':
+      borderType = 'dotted'
+      strokeDasharray = '5, 2, 1, 5, 1, 5'
+      break
+    case 'sysDot':
+      borderType = 'dotted'
+      strokeDasharray = '2, 5'
+      break
+    default:
+  }
+
+  return {
+    borderColor,
+    borderWidth,
+    borderType,
+    strokeDasharray,
+  }
+}

+ 213 - 0
src/utils/pptxToJson/chart.js

@@ -0,0 +1,213 @@
+import { eachElement, getTextByPathList } from './utils'
+import { applyTint } from './color'
+
+function extractChartColors(serNode, warpObj) {
+  if (serNode.constructor !== Array) serNode = [serNode]
+  const schemeClrs = []
+  for (const node of serNode) {
+    let schemeClr = getTextByPathList(node, ['c:spPr', 'a:solidFill', 'a:schemeClr'])
+    if (!schemeClr) schemeClr = getTextByPathList(node, ['c:spPr', 'a:ln', 'a:solidFill', 'a:schemeClr'])
+    if (!schemeClr) schemeClr = getTextByPathList(node, ['c:marker', 'c:spPr', 'a:ln', 'a:solidFill', 'a:schemeClr'])
+
+    let clr = getTextByPathList(schemeClr, ['attrs', 'val'])
+    if (clr) {
+      clr = getTextByPathList(warpObj['themeContent'], ['a:theme', 'a:themeElements', 'a:clrScheme', `a:${clr}`, 'a:srgbClr', 'attrs', 'val'])
+      const tint = getTextByPathList(schemeClr, ['a:tint', 'attrs', 'val']) / 100000
+      if (clr && !isNaN(tint)) {
+        clr = applyTint(clr, tint)
+      }
+    }
+    else clr = getTextByPathList(node, ['c:spPr', 'a:solidFill', 'a:srgbClr', 'attrs', 'val'])
+
+    if (clr) clr = '#' + clr
+    schemeClrs.push(clr)
+  }
+  return schemeClrs
+}
+
+function extractChartData(serNode) {
+  const dataMat = []
+  if (!serNode) return dataMat
+
+  if (serNode['c:xVal']) {
+    let dataRow = []
+    eachElement(serNode['c:xVal']['c:numRef']['c:numCache']['c:pt'], innerNode => {
+      dataRow.push(parseFloat(innerNode['c:v']))
+      return ''
+    })
+    dataMat.push(dataRow)
+    dataRow = []
+    eachElement(serNode['c:yVal']['c:numRef']['c:numCache']['c:pt'], innerNode => {
+      dataRow.push(parseFloat(innerNode['c:v']))
+      return ''
+    })
+    dataMat.push(dataRow)
+  } 
+  else {
+    eachElement(serNode, (innerNode, index) => {
+      const dataRow = []
+      const colName = getTextByPathList(innerNode, ['c:tx', 'c:strRef', 'c:strCache', 'c:pt', 'c:v']) || index
+
+      const rowNames = {}
+      if (getTextByPathList(innerNode, ['c:cat', 'c:strRef', 'c:strCache', 'c:pt'])) {
+        eachElement(innerNode['c:cat']['c:strRef']['c:strCache']['c:pt'], innerNode => {
+          rowNames[innerNode['attrs']['idx']] = innerNode['c:v']
+          return ''
+        })
+      } 
+      else if (getTextByPathList(innerNode, ['c:cat', 'c:numRef', 'c:numCache', 'c:pt'])) {
+        eachElement(innerNode['c:cat']['c:numRef']['c:numCache']['c:pt'], innerNode => {
+          rowNames[innerNode['attrs']['idx']] = innerNode['c:v']
+          return ''
+        })
+      }
+
+      if (getTextByPathList(innerNode, ['c:val', 'c:numRef', 'c:numCache', 'c:pt'])) {
+        eachElement(innerNode['c:val']['c:numRef']['c:numCache']['c:pt'], innerNode => {
+          dataRow.push({
+            x: innerNode['attrs']['idx'],
+            y: parseFloat(innerNode['c:v']),
+          })
+          return ''
+        })
+      }
+
+      dataMat.push({
+        key: colName,
+        values: dataRow,
+        xlabels: rowNames,
+      })
+      return ''
+    })
+  }
+
+  return dataMat
+}
+
+export function getChartInfo(plotArea, warpObj) {
+  let chart = null
+  for (const key in plotArea) {
+    switch (key) {
+      case 'c:lineChart':
+        chart = {
+          type: 'lineChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: extractChartColors(plotArea[key]['c:ser'], warpObj),
+          grouping: getTextByPathList(plotArea[key], ['c:grouping', 'attrs', 'val']),
+          marker: plotArea[key]['c:marker'] ? true : false,
+        }
+        break
+      case 'c:line3DChart':
+        chart = {
+          type: 'line3DChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: extractChartColors(plotArea[key]['c:ser'], warpObj),
+          grouping: getTextByPathList(plotArea[key], ['c:grouping', 'attrs', 'val']),
+        }
+        break
+      case 'c:barChart':
+        chart = {
+          type: 'barChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: extractChartColors(plotArea[key]['c:ser'], warpObj),
+          grouping: getTextByPathList(plotArea[key], ['c:grouping', 'attrs', 'val']),
+          barDir: getTextByPathList(plotArea[key], ['c:barDir', 'attrs', 'val']),
+        }
+        break
+      case 'c:bar3DChart':
+        chart = {
+          type: 'bar3DChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: extractChartColors(plotArea[key]['c:ser'], warpObj),
+          grouping: getTextByPathList(plotArea[key], ['c:grouping', 'attrs', 'val']),
+          barDir: getTextByPathList(plotArea[key], ['c:barDir', 'attrs', 'val']),
+        }
+        break
+      case 'c:pieChart':
+        chart = {
+          type: 'pieChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: extractChartColors(plotArea[key]['c:ser']['c:dPt'], warpObj),
+        }
+        break
+      case 'c:pie3DChart':
+        chart = {
+          type: 'pie3DChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: extractChartColors(plotArea[key]['c:ser']['c:dPt'], warpObj),
+        }
+        break
+      case 'c:doughnutChart':
+        chart = {
+          type: 'doughnutChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: extractChartColors(plotArea[key]['c:ser']['c:dPt'], warpObj),
+          holeSize: getTextByPathList(plotArea[key], ['c:holeSize', 'attrs', 'val']),
+        }
+        break
+      case 'c:areaChart':
+        chart = {
+          type: 'areaChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: extractChartColors(plotArea[key]['c:ser'], warpObj),
+          grouping: getTextByPathList(plotArea[key], ['c:grouping', 'attrs', 'val']),
+        }
+        break
+      case 'c:area3DChart':
+        chart = {
+          type: 'area3DChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: extractChartColors(plotArea[key]['c:ser'], warpObj),
+          grouping: getTextByPathList(plotArea[key], ['c:grouping', 'attrs', 'val']),
+        }
+        break
+      case 'c:scatterChart':
+        chart = {
+          type: 'scatterChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: extractChartColors(plotArea[key]['c:ser'], warpObj),
+          style: getTextByPathList(plotArea[key], ['c:scatterStyle', 'attrs', 'val']),
+        }
+        break
+      case 'c:bubbleChart':
+        chart = {
+          type: 'bubbleChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: extractChartColors(plotArea[key]['c:ser'], warpObj),
+        }
+        break
+      case 'c:radarChart':
+        chart = {
+          type: 'radarChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: extractChartColors(plotArea[key]['c:ser'], warpObj),
+          style: getTextByPathList(plotArea[key], ['c:radarStyle', 'attrs', 'val']),
+        }
+        break
+      case 'c:surfaceChart':
+        chart = {
+          type: 'surfaceChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: extractChartColors(plotArea[key]['c:ser'], warpObj),
+        }
+        break
+      case 'c:surface3DChart':
+        chart = {
+          type: 'surface3DChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: extractChartColors(plotArea[key]['c:ser'], warpObj),
+        }
+        break
+      case 'c:stockChart':
+        chart = {
+          type: 'stockChart',
+          data: extractChartData(plotArea[key]['c:ser']),
+          colors: [],
+        }
+        break
+      default:
+    }
+  }
+
+  return chart
+}

+ 177 - 0
src/utils/pptxToJson/color.js

@@ -0,0 +1,177 @@
+import tinycolor from 'tinycolor2'
+
+export function hueToRgb(t1, t2, hue) {
+  if (hue < 0) hue += 6
+  if (hue >= 6) hue -= 6
+  if (hue < 1) return (t2 - t1) * hue + t1
+  else if (hue < 3) return t2
+  else if (hue < 4) return (t2 - t1) * (4 - hue) + t1
+  return t1
+}
+
+export function hslToRgb(hue, sat, light) {
+  let t2
+  hue = hue / 60
+  if (light <= 0.5) {
+    t2 = light * (sat + 1)
+  } 
+  else {
+    t2 = light + sat - (light * sat)
+  }
+  const t1 = light * 2 - t2
+  const r = hueToRgb(t1, t2, hue + 2) * 255
+  const g = hueToRgb(t1, t2, hue) * 255
+  const b = hueToRgb(t1, t2, hue - 2) * 255
+  return { r, g, b }
+}
+
+export function applyShade(rgbStr, shadeValue, isAlpha) {
+  const color = tinycolor(rgbStr).toHsl()
+  if (shadeValue >= 1) shadeValue = 1
+  const cacl_l = Math.min(color.l * shadeValue, 1)
+  if (isAlpha) {
+    return tinycolor({
+      h: color.h,
+      s: color.s,
+      l: cacl_l,
+      a: color.a
+    }).toHex8()
+  }
+
+  return tinycolor({
+    h: color.h,
+    s: color.s,
+    l: cacl_l,
+    a: color.a,
+  }).toHex()
+}
+
+export function applyTint(rgbStr, tintValue, isAlpha) {
+  const color = tinycolor(rgbStr).toHsl()
+  if (tintValue >= 1) tintValue = 1
+  const cacl_l = color.l * tintValue + (1 - tintValue)
+  if (isAlpha) {
+    return tinycolor({
+      h: color.h,
+      s: color.s,
+      l: cacl_l,
+      a: color.a
+    }).toHex8()
+  }
+
+  return tinycolor({
+    h: color.h,
+    s: color.s,
+    l: cacl_l,
+    a: color.a
+  }).toHex()
+}
+
+export function applyLumOff(rgbStr, offset, isAlpha) {
+  const color = tinycolor(rgbStr).toHsl()
+  const lum = offset + color.l
+  if (lum >= 1) {
+    if (isAlpha) {
+      return tinycolor({
+        h: color.h,
+        s: color.s,
+        l: 1,
+        a: color.a
+      }).toHex8()
+    }
+      
+    return tinycolor({
+      h: color.h,
+      s: color.s,
+      l: 1,
+      a: color.a
+    }).toHex()
+  }
+  if (isAlpha) {
+    return tinycolor({
+      h: color.h,
+      s: color.s,
+      l: lum,
+      a: color.a
+    }).toHex8()
+  }
+
+  return tinycolor({
+    h: color.h,
+    s: color.s,
+    l: lum,
+    a: color.a
+  }).toHex()
+}
+
+export function applyLumMod(rgbStr, multiplier, isAlpha) {
+  const color = tinycolor(rgbStr).toHsl()
+  let cacl_l = color.l * multiplier
+  if (cacl_l >= 1) cacl_l = 1
+  if (isAlpha) {
+    return tinycolor({
+      h: color.h,
+      s: color.s,
+      l: cacl_l,
+      a: color.a
+    }).toHex8()
+  }
+
+  return tinycolor({
+    h: color.h,
+    s: color.s,
+    l: cacl_l,
+    a: color.a
+  }).toHex()
+}
+
+export function applyHueMod(rgbStr, multiplier, isAlpha) {
+  const color = tinycolor(rgbStr).toHsl()
+  let cacl_h = color.h * multiplier
+  if (cacl_h >= 360) cacl_h = cacl_h - 360
+  if (isAlpha) {
+    return tinycolor({
+      h: cacl_h,
+      s: color.s,
+      l: color.l,
+      a: color.a
+    }).toHex8()
+  }
+
+  return tinycolor({
+    h: cacl_h,
+    s: color.s,
+    l: color.l,
+    a: color.a
+  }).toHex()
+}
+
+export function applySatMod(rgbStr, multiplier, isAlpha) {
+  const color = tinycolor(rgbStr).toHsl()
+  let cacl_s = color.s * multiplier
+  if (cacl_s >= 1) cacl_s = 1
+  if (isAlpha) {
+    return tinycolor({
+      h: color.h,
+      s: cacl_s,
+      l: color.l,
+      a: color.a
+    }).toHex8()
+  }
+
+  return tinycolor({
+    h: color.h,
+    s: cacl_s,
+    l: color.l,
+    a: color.a
+  }).toHex()
+}
+
+export function getColorName2Hex(name) {
+  let hex
+  const colorName = ['white', 'AliceBlue', 'AntiqueWhite', 'Aqua', 'Aquamarine', 'Azure', 'Beige', 'Bisque', 'black', 'BlanchedAlmond', 'Blue', 'BlueViolet', 'Brown', 'BurlyWood', 'CadetBlue', 'Chartreuse', 'Chocolate', 'Coral', 'CornflowerBlue', 'Cornsilk', 'Crimson', 'Cyan', 'DarkBlue', 'DarkCyan', 'DarkGoldenRod', 'DarkGray', 'DarkGrey', 'DarkGreen', 'DarkKhaki', 'DarkMagenta', 'DarkOliveGreen', 'DarkOrange', 'DarkOrchid', 'DarkRed', 'DarkSalmon', 'DarkSeaGreen', 'DarkSlateBlue', 'DarkSlateGray', 'DarkSlateGrey', 'DarkTurquoise', 'DarkViolet', 'DeepPink', 'DeepSkyBlue', 'DimGray', 'DimGrey', 'DodgerBlue', 'FireBrick', 'FloralWhite', 'ForestGreen', 'Fuchsia', 'Gainsboro', 'GhostWhite', 'Gold', 'GoldenRod', 'Gray', 'Grey', 'Green', 'GreenYellow', 'HoneyDew', 'HotPink', 'IndianRed', 'Indigo', 'Ivory', 'Khaki', 'Lavender', 'LavenderBlush', 'LawnGreen', 'LemonChiffon', 'LightBlue', 'LightCoral', 'LightCyan', 'LightGoldenRodYellow', 'LightGray', 'LightGrey', 'LightGreen', 'LightPink', 'LightSalmon', 'LightSeaGreen', 'LightSkyBlue', 'LightSlateGray', 'LightSlateGrey', 'LightSteelBlue', 'LightYellow', 'Lime', 'LimeGreen', 'Linen', 'Magenta', 'Maroon', 'MediumAquaMarine', 'MediumBlue', 'MediumOrchid', 'MediumPurple', 'MediumSeaGreen', 'MediumSlateBlue', 'MediumSpringGreen', 'MediumTurquoise', 'MediumVioletRed', 'MidnightBlue', 'MintCream', 'MistyRose', 'Moccasin', 'NavajoWhite', 'Navy', 'OldLace', 'Olive', 'OliveDrab', 'Orange', 'OrangeRed', 'Orchid', 'PaleGoldenRod', 'PaleGreen', 'PaleTurquoise', 'PaleVioletRed', 'PapayaWhip', 'PeachPuff', 'Peru', 'Pink', 'Plum', 'PowderBlue', 'Purple', 'RebeccaPurple', 'Red', 'RosyBrown', 'RoyalBlue', 'SaddleBrown', 'Salmon', 'SandyBrown', 'SeaGreen', 'SeaShell', 'Sienna', 'Silver', 'SkyBlue', 'SlateBlue', 'SlateGray', 'SlateGrey', 'Snow', 'SpringGreen', 'SteelBlue', 'Tan', 'Teal', 'Thistle', 'Tomato', 'Turquoise', 'Violet', 'Wheat', 'White', 'WhiteSmoke', 'Yellow', 'YellowGreen']
+  const colorHex = ['ffffff', 'f0f8ff', 'faebd7', '00ffff', '7fffd4', 'f0ffff', 'f5f5dc', 'ffe4c4', '000000', 'ffebcd', '0000ff', '8a2be2', 'a52a2a', 'deb887', '5f9ea0', '7fff00', 'd2691e', 'ff7f50', '6495ed', 'fff8dc', 'dc143c', '00ffff', '00008b', '008b8b', 'b8860b', 'a9a9a9', 'a9a9a9', '006400', 'bdb76b', '8b008b', '556b2f', 'ff8c00', '9932cc', '8b0000', 'e9967a', '8fbc8f', '483d8b', '2f4f4f', '2f4f4f', '00ced1', '9400d3', 'ff1493', '00bfff', '696969', '696969', '1e90ff', 'b22222', 'fffaf0', '228b22', 'ff00ff', 'dcdcdc', 'f8f8ff', 'ffd700', 'daa520', '808080', '808080', '008000', 'adff2f', 'f0fff0', 'ff69b4', 'cd5c5c', '4b0082', 'fffff0', 'f0e68c', 'e6e6fa', 'fff0f5', '7cfc00', 'fffacd', 'add8e6', 'f08080', 'e0ffff', 'fafad2', 'd3d3d3', 'd3d3d3', '90ee90', 'ffb6c1', 'ffa07a', '20b2aa', '87cefa', '778899', '778899', 'b0c4de', 'ffffe0', '00ff00', '32cd32', 'faf0e6', 'ff00ff', '800000', '66cdaa', '0000cd', 'ba55d3', '9370db', '3cb371', '7b68ee', '00fa9a', '48d1cc', 'c71585', '191970', 'f5fffa', 'ffe4e1', 'ffe4b5', 'ffdead', '000080', 'fdf5e6', '808000', '6b8e23', 'ffa500', 'ff4500', 'da70d6', 'eee8aa', '98fb98', 'afeeee', 'db7093', 'ffefd5', 'ffdab9', 'cd853f', 'ffc0cb', 'dda0dd', 'b0e0e6', '800080', '663399', 'ff0000', 'bc8f8f', '4169e1', '8b4513', 'fa8072', 'f4a460', '2e8b57', 'fff5ee', 'a0522d', 'c0c0c0', '87ceeb', '6a5acd', '708090', '708090', 'fffafa', '00ff7f', '4682b4', 'd2b48c', '008080', 'd8bfd8', 'ff6347', '40e0d0', 'ee82ee', 'f5deb3', 'ffffff', 'f5f5f5', 'ffff00', '9acd32']
+  const findIndx = colorName.indexOf(name)
+  if (findIndx !== -1) hex = colorHex[findIndx]
+  return hex
+}

+ 3 - 0
src/utils/pptxToJson/constants.js

@@ -0,0 +1,3 @@
+export const RATIO_Inches_EMUs = 914400 // 1英寸 = 914400EMUs
+export const RATIO_Inches_Points = 72 // 1英寸 = 72pt
+export const RATIO_EMUs_Points = RATIO_Inches_Points / RATIO_Inches_EMUs // 1EMUs = (72 / 914400)pt

+ 594 - 0
src/utils/pptxToJson/fill.js

@@ -0,0 +1,594 @@
+import tinycolor from 'tinycolor2'
+import { getSchemeColorFromTheme } from './schemeColor'
+import {
+  applyShade,
+  applyTint,
+  applyLumOff,
+  applyLumMod,
+  applyHueMod,
+  applySatMod,
+  hslToRgb,
+  getColorName2Hex,
+} from './color'
+
+import {
+  base64ArrayBuffer,
+  getTextByPathList,
+  angleToDegrees,
+  escapeHtml,
+  getMimeType,
+  toHex,
+} from './utils'
+
+export function getFillType(node) {
+  let fillType = ''
+  if (node['a:noFill']) fillType = 'NO_FILL'
+  if (node['a:solidFill']) fillType = 'SOLID_FILL'
+  if (node['a:gradFill']) fillType = 'GRADIENT_FILL'
+  if (node['a:pattFill']) fillType = 'PATTERN_FILL'
+  if (node['a:blipFill']) fillType = 'PIC_FILL'
+  if (node['a:grpFill']) fillType = 'GROUP_FILL'
+
+  return fillType
+}
+
+export async function getPicFill(type, node, warpObj) {
+  let img
+  const rId = node['a:blip']['attrs']['r:embed']
+  let imgPath
+  if (type === 'slideBg' || type === 'slide') {
+    imgPath = getTextByPathList(warpObj, ['slideResObj', rId, 'target'])
+  }
+  else if (type === 'slideLayoutBg') {
+    imgPath = getTextByPathList(warpObj, ['layoutResObj', rId, 'target'])
+  }
+  else if (type === 'slideMasterBg') {
+    imgPath = getTextByPathList(warpObj, ['masterResObj', rId, 'target'])
+  }
+  else if (type === 'themeBg') {
+    imgPath = getTextByPathList(warpObj, ['themeResObj', rId, 'target'])
+  }
+  else if (type === 'diagramBg') {
+    imgPath = getTextByPathList(warpObj, ['diagramResObj', rId, 'target'])
+  }
+  if (!imgPath) return imgPath
+
+  img = getTextByPathList(warpObj, ['loaded-images', imgPath])
+  if (!img) {
+    imgPath = escapeHtml(imgPath)
+
+    const imgExt = imgPath.split('.').pop()
+    if (imgExt === 'xml') return undefined
+
+    const imgArrayBuffer = await warpObj['zip'].file(imgPath).async('arraybuffer')
+    const imgMimeType = getMimeType(imgExt)
+    img = `data:${imgMimeType};base64,${base64ArrayBuffer(imgArrayBuffer)}`
+
+    const loadedImages = warpObj['loaded-images'] || {}
+    loadedImages[imgPath] = img
+    warpObj['loaded-images'] = loadedImages
+  }
+  return img
+}
+
+export function getPicFillOpacity(node) {
+  const aBlipNode = node['a:blip']
+
+  const aphaModFixNode = getTextByPathList(aBlipNode, ['a:alphaModFix', 'attrs'])
+  let opacity = 1
+  if (aphaModFixNode && aphaModFixNode['amt'] && aphaModFixNode['amt'] !== '') {
+    opacity = parseInt(aphaModFixNode['amt']) / 100000
+  }
+
+  return opacity
+}
+
+export async function getBgPicFill(bgPr, sorce, warpObj) {
+  const picBase64 = await getPicFill(sorce, bgPr['a:blipFill'], warpObj)
+  const aBlipNode = bgPr['a:blipFill']['a:blip']
+
+  const aphaModFixNode = getTextByPathList(aBlipNode, ['a:alphaModFix', 'attrs'])
+  let opacity = 1
+  if (aphaModFixNode && aphaModFixNode['amt'] && aphaModFixNode['amt'] !== '') {
+    opacity = parseInt(aphaModFixNode['amt']) / 100000
+  }
+
+  return {
+    picBase64,
+    opacity,
+  }
+}
+
+export function getGradientFill(node, warpObj) {
+  const gsLst = node['a:gsLst']['a:gs']
+  const colors = []
+  for (let i = 0; i < gsLst.length; i++) {
+    const lo_color = getSolidFill(gsLst[i], undefined, undefined, warpObj)
+    const pos = getTextByPathList(gsLst[i], ['attrs', 'pos'])
+    
+    colors[i] = {
+      pos: pos ? (pos / 1000 + '%') : '',
+      color: lo_color,
+    }
+  }
+  const lin = node['a:lin']
+  let rot = 0
+  let pathType = 'line'
+  if (lin) rot = angleToDegrees(lin['attrs']['ang'])
+  else {
+    const path = node['a:path']
+    if (path && path['attrs'] && path['attrs']['path']) pathType = path['attrs']['path'] 
+  }
+  return {
+    rot,
+    path: pathType,
+    colors: colors.sort((a, b) => parseInt(a.pos) - parseInt(b.pos)),
+  }
+}
+
+export function getBgGradientFill(bgPr, phClr, slideMasterContent, warpObj) {
+  if (bgPr) {
+    const grdFill = bgPr['a:gradFill']
+    const gsLst = grdFill['a:gsLst']['a:gs']
+    const colors = []
+    
+    for (let i = 0; i < gsLst.length; i++) {
+      const lo_color = getSolidFill(gsLst[i], slideMasterContent['p:sldMaster']['p:clrMap']['attrs'], phClr, warpObj)
+      const pos = getTextByPathList(gsLst[i], ['attrs', 'pos'])
+
+      colors[i] = {
+        pos: pos ? (pos / 1000 + '%') : '',
+        color: lo_color,
+      }
+    }
+    const lin = grdFill['a:lin']
+    let rot = 0
+    let pathType = 'line'
+    if (lin) rot = angleToDegrees(lin['attrs']['ang']) + 0
+    else {
+      const path = grdFill['a:path']
+      if (path && path['attrs'] && path['attrs']['path']) pathType = path['attrs']['path'] 
+    }
+    return {
+      rot,
+      path: pathType,
+      colors: colors.sort((a, b) => parseInt(a.pos) - parseInt(b.pos)),
+    }
+  }
+  else if (phClr) {
+    return phClr.indexOf('#') === -1 ? `#${phClr}` : phClr
+  }
+  return null
+}
+
+export async function getSlideBackgroundFill(warpObj) {
+  const slideContent = warpObj['slideContent']
+  const slideLayoutContent = warpObj['slideLayoutContent']
+  const slideMasterContent = warpObj['slideMasterContent']
+  
+  let bgPr = getTextByPathList(slideContent, ['p:sld', 'p:cSld', 'p:bg', 'p:bgPr'])
+  let bgRef = getTextByPathList(slideContent, ['p:sld', 'p:cSld', 'p:bg', 'p:bgRef'])
+
+  let background = '#fff'
+  let backgroundType = 'color'
+
+  if (bgPr) {
+    const bgFillTyp = getFillType(bgPr)
+
+    if (bgFillTyp === 'SOLID_FILL') {
+      const sldFill = bgPr['a:solidFill']
+      let clrMapOvr
+      const sldClrMapOvr = getTextByPathList(slideContent, ['p:sld', 'p:clrMapOvr', 'a:overrideClrMapping', 'attrs'])
+      if (sldClrMapOvr) clrMapOvr = sldClrMapOvr
+      else {
+        const sldClrMapOvr = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'p:clrMapOvr', 'a:overrideClrMapping', 'attrs'])
+        if (sldClrMapOvr) clrMapOvr = sldClrMapOvr
+        else clrMapOvr = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:clrMap', 'attrs'])
+      }
+      const sldBgClr = getSolidFill(sldFill, clrMapOvr, undefined, warpObj)
+      background = sldBgClr
+    }
+    else if (bgFillTyp === 'GRADIENT_FILL') {
+      const gradientFill = getBgGradientFill(bgPr, undefined, slideMasterContent, warpObj)
+      if (typeof gradientFill === 'string') {
+        background = gradientFill
+      }
+      else if (gradientFill) {
+        background = gradientFill
+        backgroundType = 'gradient'
+      }
+    }
+    else if (bgFillTyp === 'PIC_FILL') {
+      background = await getBgPicFill(bgPr, 'slideBg', warpObj)
+      backgroundType = 'image'
+    }
+  }
+  else if (bgRef) {
+    let clrMapOvr
+    const sldClrMapOvr = getTextByPathList(slideContent, ['p:sld', 'p:clrMapOvr', 'a:overrideClrMapping', 'attrs'])
+    if (sldClrMapOvr) clrMapOvr = sldClrMapOvr
+    else {
+      const sldClrMapOvr = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'p:clrMapOvr', 'a:overrideClrMapping', 'attrs'])
+      if (sldClrMapOvr) clrMapOvr = sldClrMapOvr
+      else clrMapOvr = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:clrMap', 'attrs'])
+    }
+    const phClr = getSolidFill(bgRef, clrMapOvr, undefined, warpObj)
+    const idx = Number(bgRef['attrs']['idx'])
+
+    if (idx > 1000) {
+      const trueIdx = idx - 1000
+      const bgFillLst = warpObj['themeContent']['a:theme']['a:themeElements']['a:fmtScheme']['a:bgFillStyleLst']
+      const sortblAry = []
+      Object.keys(bgFillLst).forEach(key => {
+        const bgFillLstTyp = bgFillLst[key]
+        if (key !== 'attrs') {
+          if (bgFillLstTyp.constructor === Array) {
+            for (let i = 0; i < bgFillLstTyp.length; i++) {
+              const obj = {}
+              obj[key] = bgFillLstTyp[i]
+              if (bgFillLstTyp[i]['attrs']) {
+                obj['idex'] = bgFillLstTyp[i]['attrs']['order']
+                obj['attrs'] = {
+                  'order': bgFillLstTyp[i]['attrs']['order']
+                }
+              }
+              sortblAry.push(obj)
+            }
+          } 
+          else {
+            const obj = {}
+            obj[key] = bgFillLstTyp
+            if (bgFillLstTyp['attrs']) {
+              obj['idex'] = bgFillLstTyp['attrs']['order']
+              obj['attrs'] = {
+                'order': bgFillLstTyp['attrs']['order']
+              }
+            }
+            sortblAry.push(obj)
+          }
+        }
+      })
+      const sortByOrder = sortblAry.slice(0)
+      sortByOrder.sort((a, b) => a.idex - b.idex)
+      const bgFillLstIdx = sortByOrder[trueIdx - 1]
+      const bgFillTyp = getFillType(bgFillLstIdx)
+      if (bgFillTyp === 'SOLID_FILL') {
+        const sldFill = bgFillLstIdx['a:solidFill']
+        const sldBgClr = getSolidFill(sldFill, clrMapOvr, undefined, warpObj)
+        background = sldBgClr
+      } 
+      else if (bgFillTyp === 'GRADIENT_FILL') {
+        const gradientFill = getBgGradientFill(bgFillLstIdx, phClr, slideMasterContent, warpObj)
+        if (typeof gradientFill === 'string') {
+          background = gradientFill
+        }
+        else if (gradientFill) {
+          background = gradientFill
+          backgroundType = 'gradient'
+        }
+      }
+    }
+  }
+  else {
+    bgPr = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'p:cSld', 'p:bg', 'p:bgPr'])
+    bgRef = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'p:cSld', 'p:bg', 'p:bgRef'])
+
+    let clrMapOvr
+    const sldClrMapOvr = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'p:clrMapOvr', 'a:overrideClrMapping', 'attrs'])
+    if (sldClrMapOvr) clrMapOvr = sldClrMapOvr
+    else clrMapOvr = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:clrMap', 'attrs'])
+
+    if (bgPr) {
+      const bgFillTyp = getFillType(bgPr)
+      if (bgFillTyp === 'SOLID_FILL') {
+        const sldFill = bgPr['a:solidFill']
+        const sldBgClr = getSolidFill(sldFill, clrMapOvr, undefined, warpObj)
+        background = sldBgClr
+      }
+      else if (bgFillTyp === 'GRADIENT_FILL') {
+        const gradientFill = getBgGradientFill(bgPr, undefined, slideMasterContent, warpObj)
+        if (typeof gradientFill === 'string') {
+          background = gradientFill
+        }
+        else if (gradientFill) {
+          background = gradientFill
+          backgroundType = 'gradient'
+        }
+      }
+      else if (bgFillTyp === 'PIC_FILL') {
+        background = await getBgPicFill(bgPr, 'slideLayoutBg', warpObj)
+        backgroundType = 'image'
+      }
+    }
+    else if (bgRef) {
+      const phClr = getSolidFill(bgRef, clrMapOvr, undefined, warpObj)
+      const idx = Number(bgRef['attrs']['idx'])
+  
+      if (idx > 1000) {
+        const trueIdx = idx - 1000
+        const bgFillLst = warpObj['themeContent']['a:theme']['a:themeElements']['a:fmtScheme']['a:bgFillStyleLst']
+        const sortblAry = []
+        Object.keys(bgFillLst).forEach(key => {
+          const bgFillLstTyp = bgFillLst[key]
+          if (key !== 'attrs') {
+            if (bgFillLstTyp.constructor === Array) {
+              for (let i = 0; i < bgFillLstTyp.length; i++) {
+                const obj = {}
+                obj[key] = bgFillLstTyp[i]
+                if (bgFillLstTyp[i]['attrs']) {
+                  obj['idex'] = bgFillLstTyp[i]['attrs']['order']
+                  obj['attrs'] = {
+                    'order': bgFillLstTyp[i]['attrs']['order']
+                  }
+                }
+                sortblAry.push(obj)
+              }
+            } 
+            else {
+              const obj = {}
+              obj[key] = bgFillLstTyp
+              if (bgFillLstTyp['attrs']) {
+                obj['idex'] = bgFillLstTyp['attrs']['order']
+                obj['attrs'] = {
+                  'order': bgFillLstTyp['attrs']['order']
+                }
+              }
+              sortblAry.push(obj)
+            }
+          }
+        })
+        const sortByOrder = sortblAry.slice(0)
+        sortByOrder.sort((a, b) => a.idex - b.idex)
+        const bgFillLstIdx = sortByOrder[trueIdx - 1]
+        const bgFillTyp = getFillType(bgFillLstIdx)
+        if (bgFillTyp === 'SOLID_FILL') {
+          const sldFill = bgFillLstIdx['a:solidFill']
+          const sldBgClr = getSolidFill(sldFill, clrMapOvr, undefined, warpObj)
+          background = sldBgClr
+        } 
+        else if (bgFillTyp === 'GRADIENT_FILL') {
+          const gradientFill = getBgGradientFill(bgFillLstIdx, phClr, slideMasterContent, warpObj)
+          if (typeof gradientFill === 'string') {
+            background = gradientFill
+          }
+          else if (gradientFill) {
+            background = gradientFill
+            backgroundType = 'gradient'
+          }
+        }
+        else if (bgFillTyp === 'PIC_FILL') {
+          background = await getBgPicFill(bgFillLstIdx, 'themeBg', warpObj)
+          backgroundType = 'image'
+        }
+      }
+    }
+    else {
+      bgPr = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:cSld', 'p:bg', 'p:bgPr'])
+      bgRef = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:cSld', 'p:bg', 'p:bgRef'])
+
+      const clrMap = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:clrMap', 'attrs'])
+      if (bgPr) {
+        const bgFillTyp = getFillType(bgPr)
+        if (bgFillTyp === 'SOLID_FILL') {
+          const sldFill = bgPr['a:solidFill']
+          const sldBgClr = getSolidFill(sldFill, clrMap, undefined, warpObj)
+          background = sldBgClr
+        }
+        else if (bgFillTyp === 'GRADIENT_FILL') {
+          const gradientFill = getBgGradientFill(bgPr, undefined, slideMasterContent, warpObj)
+          if (typeof gradientFill === 'string') {
+            background = gradientFill
+          }
+          else if (gradientFill) {
+            background = gradientFill
+            backgroundType = 'gradient'
+          }
+        }
+        else if (bgFillTyp === 'PIC_FILL') {
+          background = await getBgPicFill(bgPr, 'slideMasterBg', warpObj)
+          backgroundType = 'image'
+        }
+      }
+      else if (bgRef) {
+        const phClr = getSolidFill(bgRef, clrMap, undefined, warpObj)
+        const idx = Number(bgRef['attrs']['idx'])
+    
+        if (idx > 1000) {
+          const trueIdx = idx - 1000
+          const bgFillLst = warpObj['themeContent']['a:theme']['a:themeElements']['a:fmtScheme']['a:bgFillStyleLst']
+          const sortblAry = []
+          Object.keys(bgFillLst).forEach(key => {
+            const bgFillLstTyp = bgFillLst[key]
+            if (key !== 'attrs') {
+              if (bgFillLstTyp.constructor === Array) {
+                for (let i = 0; i < bgFillLstTyp.length; i++) {
+                  const obj = {}
+                  obj[key] = bgFillLstTyp[i]
+                  if (bgFillLstTyp[i]['attrs']) {
+                    obj['idex'] = bgFillLstTyp[i]['attrs']['order']
+                    obj['attrs'] = {
+                      'order': bgFillLstTyp[i]['attrs']['order']
+                    }
+                  }
+                  sortblAry.push(obj)
+                }
+              } 
+              else {
+                const obj = {}
+                obj[key] = bgFillLstTyp
+                if (bgFillLstTyp['attrs']) {
+                  obj['idex'] = bgFillLstTyp['attrs']['order']
+                  obj['attrs'] = {
+                    'order': bgFillLstTyp['attrs']['order']
+                  }
+                }
+                sortblAry.push(obj)
+              }
+            }
+          })
+          const sortByOrder = sortblAry.slice(0)
+          sortByOrder.sort((a, b) => a.idex - b.idex)
+          const bgFillLstIdx = sortByOrder[trueIdx - 1]
+          const bgFillTyp = getFillType(bgFillLstIdx)
+          if (bgFillTyp === 'SOLID_FILL') {
+            const sldFill = bgFillLstIdx['a:solidFill']
+            const sldBgClr = getSolidFill(sldFill, clrMapOvr, undefined, warpObj)
+            background = sldBgClr
+          } 
+          else if (bgFillTyp === 'GRADIENT_FILL') {
+            const gradientFill = getBgGradientFill(bgFillLstIdx, phClr, slideMasterContent, warpObj)
+            if (typeof gradientFill === 'string') {
+              background = gradientFill
+            }
+            else if (gradientFill) {
+              background = gradientFill
+              backgroundType = 'gradient'
+            }
+          }
+          else if (bgFillTyp === 'PIC_FILL') {
+            background = await getBgPicFill(bgFillLstIdx, 'themeBg', warpObj)
+            backgroundType = 'image'
+          }
+        }
+      }
+    }
+  }
+  return {
+    type: backgroundType,
+    value: background,
+  }
+}
+
+export async function getShapeFill(node, pNode, isSvgMode, warpObj, source) {
+  const fillType = getFillType(getTextByPathList(node, ['p:spPr']))
+  let type = 'color'
+  let fillValue = ''
+  if (fillType === 'NO_FILL') {
+    return isSvgMode ? 'none' : ''
+  } 
+  else if (fillType === 'SOLID_FILL') {
+    const shpFill = node['p:spPr']['a:solidFill']
+    fillValue = getSolidFill(shpFill, undefined, undefined, warpObj)
+    type = 'color'
+  }
+  else if (fillType === 'GRADIENT_FILL') {
+    const shpFill = node['p:spPr']['a:gradFill']
+    fillValue = getGradientFill(shpFill, warpObj)
+    type = 'gradient'
+  }
+  else if (fillType === 'PIC_FILL') {
+    const shpFill = node['p:spPr']['a:blipFill']
+    const picBase64 = await getPicFill(source, shpFill, warpObj)
+    const opacity = getPicFillOpacity(shpFill)
+    fillValue = {
+      picBase64,
+      opacity,
+    }
+    type = 'image'
+  }
+  if (!fillValue) {
+    const clrName = getTextByPathList(node, ['p:style', 'a:fillRef'])
+    fillValue = getSolidFill(clrName, undefined, undefined, warpObj)
+    type = 'color'
+  }
+  if (!fillValue && pNode) {
+    const grpFill = getTextByPathList(node, ['p:spPr', 'a:grpFill'])
+    if (grpFill) {
+      const grpShpFill = pNode['p:grpSpPr']
+      if (grpShpFill) {
+        const spShpNode = { 'p:spPr': grpShpFill }
+        return getShapeFill(spShpNode, node, isSvgMode, warpObj, source)
+      }
+    } 
+    else if (fillType === 'NO_FILL') {
+      return isSvgMode ? 'none' : ''
+    }
+  }
+
+  return {
+    type,
+    value: fillValue,
+  }
+}
+
+export function getSolidFill(solidFill, clrMap, phClr, warpObj) {
+  if (!solidFill) return ''
+
+  let color = ''
+  let clrNode
+
+  if (solidFill['a:srgbClr']) {
+    clrNode = solidFill['a:srgbClr']
+    color = getTextByPathList(clrNode, ['attrs', 'val'])
+  } 
+  else if (solidFill['a:schemeClr']) {
+    clrNode = solidFill['a:schemeClr']
+    const schemeClr = 'a:' + getTextByPathList(clrNode, ['attrs', 'val'])
+    color = getSchemeColorFromTheme(schemeClr, warpObj, clrMap, phClr) || ''
+  }
+  else if (solidFill['a:scrgbClr']) {
+    clrNode = solidFill['a:scrgbClr']
+    const defBultColorVals = clrNode['attrs']
+    const red = (defBultColorVals['r'].indexOf('%') !== -1) ? defBultColorVals['r'].split('%').shift() : defBultColorVals['r']
+    const green = (defBultColorVals['g'].indexOf('%') !== -1) ? defBultColorVals['g'].split('%').shift() : defBultColorVals['g']
+    const blue = (defBultColorVals['b'].indexOf('%') !== -1) ? defBultColorVals['b'].split('%').shift() : defBultColorVals['b']
+    color = toHex(255 * (Number(red) / 100)) + toHex(255 * (Number(green) / 100)) + toHex(255 * (Number(blue) / 100))
+  } 
+  else if (solidFill['a:prstClr']) {
+    clrNode = solidFill['a:prstClr']
+    const prstClr = getTextByPathList(clrNode, ['attrs', 'val'])
+    color = getColorName2Hex(prstClr)
+  } 
+  else if (solidFill['a:hslClr']) {
+    clrNode = solidFill['a:hslClr']
+    const defBultColorVals = clrNode['attrs']
+    const hue = Number(defBultColorVals['hue']) / 100000
+    const sat = Number((defBultColorVals['sat'].indexOf('%') !== -1) ? defBultColorVals['sat'].split('%').shift() : defBultColorVals['sat']) / 100
+    const lum = Number((defBultColorVals['lum'].indexOf('%') !== -1) ? defBultColorVals['lum'].split('%').shift() : defBultColorVals['lum']) / 100
+    const hsl2rgb = hslToRgb(hue, sat, lum)
+    color = toHex(hsl2rgb.r) + toHex(hsl2rgb.g) + toHex(hsl2rgb.b)
+  } 
+  else if (solidFill['a:sysClr']) {
+    clrNode = solidFill['a:sysClr']
+    const sysClr = getTextByPathList(clrNode, ['attrs', 'lastClr'])
+    if (sysClr) color = sysClr
+  }
+
+  let isAlpha = false
+  const alpha = parseInt(getTextByPathList(clrNode, ['a:alpha', 'attrs', 'val'])) / 100000
+  if (!isNaN(alpha)) {
+    const al_color = tinycolor(color)
+    al_color.setAlpha(alpha)
+    color = al_color.toHex8()
+    isAlpha = true
+  }
+
+  const hueMod = parseInt(getTextByPathList(clrNode, ['a:hueMod', 'attrs', 'val'])) / 100000
+  if (!isNaN(hueMod)) {
+    color = applyHueMod(color, hueMod, isAlpha)
+  }
+  const lumMod = parseInt(getTextByPathList(clrNode, ['a:lumMod', 'attrs', 'val'])) / 100000
+  if (!isNaN(lumMod)) {
+    color = applyLumMod(color, lumMod, isAlpha)
+  }
+  const lumOff = parseInt(getTextByPathList(clrNode, ['a:lumOff', 'attrs', 'val'])) / 100000
+  if (!isNaN(lumOff)) {
+    color = applyLumOff(color, lumOff, isAlpha)
+  }
+  const satMod = parseInt(getTextByPathList(clrNode, ['a:satMod', 'attrs', 'val'])) / 100000
+  if (!isNaN(satMod)) {
+    color = applySatMod(color, satMod, isAlpha)
+  }
+  const shade = parseInt(getTextByPathList(clrNode, ['a:shade', 'attrs', 'val'])) / 100000
+  if (!isNaN(shade)) {
+    color = applyShade(color, shade, isAlpha)
+  }
+  const tint = parseInt(getTextByPathList(clrNode, ['a:tint', 'attrs', 'val'])) / 100000
+  if (!isNaN(tint)) {
+    color = applyTint(color, tint, isAlpha)
+  }
+
+  if (color && color.indexOf('#') === -1) color = '#' + color
+
+  return color
+}

+ 162 - 0
src/utils/pptxToJson/fontStyle.js

@@ -0,0 +1,162 @@
+import { getTextByPathList } from './utils'
+import { getShadow } from './shadow'
+import { getFillType, getSolidFill } from './fill'
+
+export function getFontType(node, type, warpObj) {
+  let typeface = getTextByPathList(node, ['a:rPr', 'a:latin', 'attrs', 'typeface'])
+
+  if (!typeface) {
+    const fontSchemeNode = getTextByPathList(warpObj['themeContent'], ['a:theme', 'a:themeElements', 'a:fontScheme'])
+
+    if (type === 'title' || type === 'subTitle' || type === 'ctrTitle') {
+      typeface = getTextByPathList(fontSchemeNode, ['a:majorFont', 'a:latin', 'attrs', 'typeface'])
+    } 
+    else if (type === 'body') {
+      typeface = getTextByPathList(fontSchemeNode, ['a:minorFont', 'a:latin', 'attrs', 'typeface'])
+    } 
+    else {
+      typeface = getTextByPathList(fontSchemeNode, ['a:minorFont', 'a:latin', 'attrs', 'typeface'])
+    }
+  }
+
+  return typeface || ''
+}
+
+export function getFontColor(node, pNode, lstStyle, pFontStyle, lvl, warpObj) {
+  const rPrNode = getTextByPathList(node, ['a:rPr'])
+  let filTyp, color
+  if (rPrNode) {
+    filTyp = getFillType(rPrNode)
+    if (filTyp === 'SOLID_FILL') {
+      const solidFillNode = rPrNode['a:solidFill']
+      color = getSolidFill(solidFillNode, undefined, undefined, warpObj)
+    }
+  }
+  if (!color && getTextByPathList(lstStyle, ['a:lvl' + lvl + 'pPr', 'a:defRPr'])) {
+    const lstStyledefRPr = getTextByPathList(lstStyle, ['a:lvl' + lvl + 'pPr', 'a:defRPr'])
+    filTyp = getFillType(lstStyledefRPr)
+    if (filTyp === 'SOLID_FILL') {
+      const solidFillNode = lstStyledefRPr['a:solidFill']
+      color = getSolidFill(solidFillNode, undefined, undefined, warpObj)
+    }
+  }
+  if (!color) {
+    const sPstyle = getTextByPathList(pNode, ['p:style', 'a:fontRef'])
+    if (sPstyle) color = getSolidFill(sPstyle, undefined, undefined, warpObj)
+    if (!color && pFontStyle) color = getSolidFill(pFontStyle, undefined, undefined, warpObj)
+  }
+  return color || ''
+}
+
+export function getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles) {
+  
+  let fontSize
+
+  // 检查列表样式中的字体大小
+  const lstStyleSz = getTextByPathList(node, ['a:lstStyle', 'a:lvl1pPr', 'a:defRPr', 'attrs', 'sz'])
+  if (lstStyleSz) {
+    fontSize = parseInt(lstStyleSz) / 100
+  }
+
+  // 检查直接在文本上设置的字体大小
+  if (!fontSize || isNaN(fontSize)) {
+    const directSz = getTextByPathList(node, ['a:rPr', 'attrs', 'sz'])
+    if (directSz) {
+      fontSize = parseInt(directSz) / 100
+    }
+  }
+
+  // 检查段落级别的字体大小
+  if (!fontSize || isNaN(fontSize)) {
+    const pPrNode = getTextByPathList(node, ['a:pPr'])
+    if (pPrNode) {
+      const lvl = parseInt(pPrNode.attrs?.lvl || '0')
+      
+      // 检查布局中的字体大小设置
+      const layoutSz = getTextByPathList(slideLayoutSpNode, ['p:txBody', 'a:lstStyle', `a:lvl${lvl + 1}pPr`, 'a:defRPr', 'attrs', 'sz'])
+      if (layoutSz) {
+        fontSize = parseInt(layoutSz) / 100
+      }
+      
+      // 如果没有找到布局大小,检查母版样式
+      if ((!fontSize || isNaN(fontSize)) && slideMasterTextStyles) {
+        let styleNode
+        if (type === 'title' || type === 'ctrTitle') {
+          styleNode = getTextByPathList(slideMasterTextStyles, ['p:titleStyle'])
+        }
+        else if (type === 'body') {
+          styleNode = getTextByPathList(slideMasterTextStyles, ['p:bodyStyle'])
+        }
+        else if (type === 'subTitle') {
+          styleNode = getTextByPathList(slideMasterTextStyles, ['p:otherStyle'])
+        }
+        
+        if (styleNode) {
+          const masterSz = getTextByPathList(styleNode, [`a:lvl${lvl + 1}pPr`, 'a:defRPr', 'attrs', 'sz'])
+          if (masterSz) {
+            fontSize = parseInt(masterSz) / 100
+          }
+        }
+      }
+    }
+  }
+
+  // 4. 如果还是没有找到,使用默认大小
+  if (!fontSize || isNaN(fontSize)) {
+    if (type === 'title' || type === 'ctrTitle') {
+      fontSize = 60 // 标题默认60磅
+    }
+    else if (type === 'subTitle') {
+      fontSize = 32 // 副标题默认32磅
+    }
+    else if (type === 'body') {
+      fontSize = 18 // 正文默认18磅
+    }
+    else {
+      fontSize = 18 // 其他默认18磅
+    }
+  }
+
+  return fontSize + 'pt'
+}
+
+export function getFontBold(node) {
+  return getTextByPathList(node, ['a:rPr', 'attrs', 'b']) === '1' ? 'bold' : ''
+}
+
+export function getFontItalic(node) {
+  return getTextByPathList(node, ['a:rPr', 'attrs', 'i']) === '1' ? 'italic' : ''
+}
+
+export function getFontDecoration(node) {
+  return getTextByPathList(node, ['a:rPr', 'attrs', 'u']) === 'sng' ? 'underline' : ''
+}
+
+export function getFontDecorationLine(node) {
+  return getTextByPathList(node, ['a:rPr', 'attrs', 'strike']) === 'sngStrike' ? 'line-through' : ''
+}
+
+export function getFontSpace(node) {
+  const spc = getTextByPathList(node, ['a:rPr', 'attrs', 'spc'])
+  return spc ? (parseInt(spc) / 100 + 'pt') : ''
+}
+
+export function getFontSubscript(node) {
+  const baseline = getTextByPathList(node, ['a:rPr', 'attrs', 'baseline'])
+  if (!baseline) return ''
+  return parseInt(baseline) > 0 ? 'super' : 'sub'
+}
+
+export function getFontShadow(node, warpObj) {
+  const txtShadow = getTextByPathList(node, ['a:rPr', 'a:effectLst', 'a:outerShdw'])
+  if (txtShadow) {
+    const shadow = getShadow(txtShadow, warpObj)
+    if (shadow) {
+      const { h, v, blur, color } = shadow
+      if (!isNaN(v) && !isNaN(h)) {
+        return h + 'pt ' + v + 'pt ' + (blur ? blur + 'pt' : '') + ' ' + color
+      }
+    }
+  }
+  return ''
+}

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
src/utils/pptxToJson/index.js


+ 184 - 0
src/utils/pptxToJson/math.js

@@ -0,0 +1,184 @@
+import { getTextByPathList } from './utils'
+
+export function findOMath(obj) {
+  let results = []
+  if (typeof obj !== 'object') return results
+  if (obj['m:oMath']) results = results.concat(obj['m:oMath'])
+  
+  Object.values(obj).forEach(value => {
+    if (Array.isArray(value) || typeof value === 'object') {
+      results = results.concat(findOMath(value))
+    }
+  })
+  return results
+}
+
+export function parseFraction(fraction) {
+  const numerator = parseOMath(fraction['m:num'])
+  const denominator = parseOMath(fraction['m:den'])
+  return `\\frac{${numerator}}{${denominator}}`
+}
+export function parseSuperscript(superscript) {
+  const base = parseOMath(superscript['m:e'])
+  const sup = parseOMath(superscript['m:sup'])
+  return `${base}^{${sup}}`
+}
+export function parseSubscript(subscript) {
+  const base = parseOMath(subscript['m:e'])
+  const sub = parseOMath(subscript['m:sub'])
+  return `${base}_{${sub}}`
+}
+export function parseRadical(radical) {
+  const degree = parseOMath(radical['m:deg'])
+  const expression = parseOMath(radical['m:e'])
+  return degree ? `\\sqrt[${degree}]{${expression}}` : `\\sqrt{${expression}}`
+}
+export function parseMatrix(matrix) {
+  const rows = matrix['m:mr']
+  const matrixRows = rows.map((row) => {
+    return row['m:e'].map((element) => parseOMath(element)).join(' & ')
+  })
+  return `\\begin{matrix} ${matrixRows.join(' \\\\ ')} \\end{matrix}`
+}
+export function parseNary(nary) {
+  const op = getTextByPathList(nary, ['m:naryPr', 'm:chr', 'attrs', 'm:val']) || '∫'
+  const sub = parseOMath(nary['m:sub'])
+  const sup = parseOMath(nary['m:sup'])
+  const e = parseOMath(nary['m:e'])
+  return `${op}_{${sub}}^{${sup}}{${e}}`
+}
+export function parseLimit(limit, type) {
+  const base = parseOMath(limit['m:e'])
+  const lim = parseOMath(limit['m:lim'])
+  return type === 'low' ? `${base}_{${lim}}` : `${base}^{${lim}}`
+}
+export function parseDelimiter(delimiter) {
+  let left = getTextByPathList(delimiter, ['m:dPr', 'm:begChr', 'attrs', 'm:val'])
+  let right = getTextByPathList(delimiter, ['m:dPr', 'm:endChr', 'attrs', 'm:val'])
+  if (!left && !right) {
+    left = '('
+    right = ')'
+  }
+  if (left && right) {
+    left = `\\left${left}`
+    right = `\\right${right}`
+  }
+  const e = parseOMath(delimiter['m:e'])
+  return `${left}${e}${right}`
+}
+export function parseFunction(func) {
+  const name = parseOMath(func['m:fName'])
+  const arg = parseOMath(func['m:e'])
+  return `\\${name}{${arg}}`
+}
+export function parseGroupChr(groupChr) {
+  const chr = getTextByPathList(groupChr, ['m:groupChrPr', 'm:chr', 'attrs', 'm:val'])
+  const e = parseOMath(groupChr['m:e'])
+  return `${chr}${e}${chr}`
+}
+export function parseEqArr(eqArr) {
+  const equations = eqArr['m:e'].map((eq) => parseOMath(eq)).join(' \\\\ ')
+  return `\\begin{cases} ${equations} \\end{cases}`
+}
+export function parseBar(bar) {
+  const e = parseOMath(bar['m:e'])
+  const pos = getTextByPathList(bar, ['m:barPr', 'm:pos', 'attrs', 'm:val'])
+  return pos === 'top' ? `\\overline{${e}}` : `\\underline{${e}}`
+}
+export function parseAccent(accent) {
+  const chr = getTextByPathList(accent, ['m:accPr', 'm:chr', 'attrs', 'm:val']) || '^'
+  const e = parseOMath(accent['m:e'])
+  switch (chr) {
+    case '\u0301':
+      return `\\acute{${e}}`
+    case '\u0300':
+      return `\\grave{${e}}`
+    case '\u0302':
+      return `\\hat{${e}}`
+    case '\u0303':
+      return `\\tilde{${e}}`
+    case '\u0304':
+      return `\\bar{${e}}`
+    case '\u0306':
+      return `\\breve{${e}}`
+    case '\u0307':
+      return `\\dot{${e}}`
+    case '\u0308':
+      return `\\ddot{${e}}`
+    case '\u030A':
+      return `\\mathring{${e}}`
+    case '\u030B':
+      return `\\H{${e}}`
+    case '\u030C':
+      return `\\check{${e}}`
+    case '\u0327':
+      return `\\c{${e}}`
+    default:
+      return `\\${chr}{${e}}`
+  }
+}
+export function parseBox(box) {
+  const e = parseOMath(box['m:e'])
+  return `\\boxed{${e}}`
+}
+
+
+export function parseOMath(oMath) {
+  if (!oMath) return ''
+
+  if (Array.isArray(oMath)) {
+    return oMath.map(item => parseOMath(item)).join('')
+  }
+
+  const oMathList = []
+  const keys = Object.keys(oMath)
+  for (const key of keys) {
+    if (Array.isArray(oMath[key])) {
+      oMathList.push(...oMath[key].map(item => ({ key, value: item })))
+    }
+    else oMathList.push({ key, value: oMath[key] })
+  }
+
+  oMathList.sort((a, b) => {
+    let oA = 0
+    if (a.key === 'm:r' && a.value && a.value['a:rPr']) oA = a.value['a:rPr']['attrs']['order']
+    else if (a.value[`${a.key}Pr`] && a.value[`${a.key}Pr`]['m:ctrlPr'] && a.value[`${a.key}Pr`]['m:ctrlPr']['a:rPr']) {
+      oA = a.value[`${a.key}Pr`] && a.value[`${a.key}Pr`]['m:ctrlPr'] && a.value[`${a.key}Pr`]['m:ctrlPr']['a:rPr'] && a.value[`${a.key}Pr`]['m:ctrlPr']['a:rPr']['attrs']['order']
+    }
+    let oB = 0
+    if (b.key === 'm:r' && b.value && b.value['a:rPr']) oB = b.value['a:rPr']['attrs']['order']
+    else if (b.value[`${b.key}Pr`] && b.value[`${b.key}Pr`]['m:ctrlPr'] && b.value[`${b.key}Pr`]['m:ctrlPr']['a:rPr']) {
+      oB = b.value[`${b.key}Pr`] && b.value[`${b.key}Pr`]['m:ctrlPr'] && b.value[`${b.key}Pr`]['m:ctrlPr']['a:rPr'] && b.value[`${b.key}Pr`]['m:ctrlPr']['a:rPr']['attrs']['order']
+    }
+    return oA - oB
+  })
+
+  return oMathList.map(({ key, value }) => {
+    if (key === 'm:f') return parseFraction(value)
+    if (key === 'm:sSup') return parseSuperscript(value)
+    if (key === 'm:sSub') return parseSubscript(value)
+    if (key === 'm:rad') return parseRadical(value)
+    if (key === 'm:nary') return parseNary(value)
+    if (key === 'm:limLow') return parseLimit(value, 'low')
+    if (key === 'm:limUpp') return parseLimit(value, 'upp')
+    if (key === 'm:d') return parseDelimiter(value)
+    if (key === 'm:func') return parseFunction(value)
+    if (key === 'm:groupChr') return parseGroupChr(value)
+    if (key === 'm:eqArr') return parseEqArr(value)
+    if (key === 'm:bar') return parseBar(value)
+    if (key === 'm:acc') return parseAccent(value)
+    if (key === 'm:borderBox') return parseBox(value)
+    if (key === 'm:m') return parseMatrix(value)
+    if (key === 'm:r') return parseOMath(value)
+    if (key === 'm:t') return value
+    return ''
+  }).join('')
+}
+
+export function latexFormart(latex) {
+  return latex.replaceAll(/&lt;/g, '<')
+    .replaceAll(/&gt;/g, '>')
+    .replaceAll(/&amp;/g, '&')
+    .replaceAll(/&apos;/g, "'")
+    .replaceAll(/&quot;/g, '"')
+}

+ 31 - 0
src/utils/pptxToJson/position.js

@@ -0,0 +1,31 @@
+import { RATIO_EMUs_Points } from './constants'
+
+export function getPosition(slideSpNode, slideLayoutSpNode, slideMasterSpNode) {
+  let off
+
+  if (slideSpNode) off = slideSpNode['a:off']['attrs']
+  else if (slideLayoutSpNode) off = slideLayoutSpNode['a:off']['attrs']
+  else if (slideMasterSpNode) off = slideMasterSpNode['a:off']['attrs']
+
+  if (!off) return { top: 0, left: 0 }
+
+  return {
+    top: parseInt(off['y']) * RATIO_EMUs_Points,
+    left: parseInt(off['x']) * RATIO_EMUs_Points,
+  }
+}
+
+export function getSize(slideSpNode, slideLayoutSpNode, slideMasterSpNode) {
+  let ext
+
+  if (slideSpNode) ext = slideSpNode['a:ext']['attrs']
+  else if (slideLayoutSpNode) ext = slideLayoutSpNode['a:ext']['attrs']
+  else if (slideMasterSpNode) ext = slideMasterSpNode['a:ext']['attrs']
+
+  if (!ext) return { width: 0, height: 0 }
+
+  return {
+    width: parseInt(ext['cx']) * RATIO_EMUs_Points,
+    height: parseInt(ext['cy']) * RATIO_EMUs_Points,
+  }
+}

+ 1082 - 0
src/utils/pptxToJson/pptxtojson.js

@@ -0,0 +1,1082 @@
+import JSZip from 'jszip'
+import { readXmlFile } from './readXmlFile'
+import { getBorder } from './border'
+import { getSlideBackgroundFill, getShapeFill, getSolidFill, getPicFill } from './fill'
+import { getChartInfo } from './chart'
+import { getVerticalAlign } from './align'
+import { getPosition, getSize } from './position'
+import { genTextBody } from './text'
+import { getCustomShapePath } from './shape'
+import { extractFileExtension, base64ArrayBuffer, getTextByPathList, angleToDegrees, getMimeType, isVideoLink, escapeHtml, hasValidText } from './utils'
+import { getShadow } from './shadow'
+import { getTableBorders, getTableCellParams, getTableRowParams } from './table'
+import { RATIO_EMUs_Points } from './constants'
+import { findOMath, latexFormart, parseOMath } from './math'
+
+export async function parse(file) {
+  const slides = []
+  
+  const zip = await JSZip.loadAsync(file)
+
+  const filesInfo = await getContentTypes(zip)
+  const { width, height, defaultTextStyle } = await getSlideInfo(zip)
+  const { themeContent, themeColors } = await getTheme(zip)
+
+  for (const filename of filesInfo.slides) {
+    const singleSlide = await processSingleSlide(zip, filename, themeContent, defaultTextStyle)
+    slides.push(singleSlide)
+  }
+
+  return {
+    slides,
+    themeColors,
+    size: {
+      width,
+      height,
+    },
+  }
+}
+
+async function getContentTypes(zip) {
+  const ContentTypesJson = await readXmlFile(zip, '[Content_Types].xml')
+  const subObj = ContentTypesJson['Types']['Override']
+  let slidesLocArray = []
+  let slideLayoutsLocArray = []
+
+  for (const item of subObj) {
+    switch (item['attrs']['ContentType']) {
+      case 'application/vnd.openxmlformats-officedocument.presentationml.slide+xml':
+        slidesLocArray.push(item['attrs']['PartName'].substr(1))
+        break
+      case 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml':
+        slideLayoutsLocArray.push(item['attrs']['PartName'].substr(1))
+        break
+      default:
+    }
+  }
+  
+  const sortSlideXml = (p1, p2) => {
+    const n1 = +/(\d+)\.xml/.exec(p1)[1]
+    const n2 = +/(\d+)\.xml/.exec(p2)[1]
+    return n1 - n2
+  }
+  slidesLocArray = slidesLocArray.sort(sortSlideXml)
+  slideLayoutsLocArray = slideLayoutsLocArray.sort(sortSlideXml)
+  
+  return {
+    slides: slidesLocArray,
+    slideLayouts: slideLayoutsLocArray,
+  }
+}
+
+async function getSlideInfo(zip) {
+  const content = await readXmlFile(zip, 'ppt/presentation.xml')
+  const sldSzAttrs = content['p:presentation']['p:sldSz']['attrs']
+  const defaultTextStyle = content['p:presentation']['p:defaultTextStyle']
+  return {
+    width: parseInt(sldSzAttrs['cx']) * RATIO_EMUs_Points,
+    height: parseInt(sldSzAttrs['cy']) * RATIO_EMUs_Points,
+    defaultTextStyle,
+  }
+}
+
+async function getTheme(zip) {
+  const preResContent = await readXmlFile(zip, 'ppt/_rels/presentation.xml.rels')
+  const relationshipArray = preResContent['Relationships']['Relationship']
+  let themeURI
+
+  if (relationshipArray.constructor === Array) {
+    for (const relationshipItem of relationshipArray) {
+      if (relationshipItem['attrs']['Type'] === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme') {
+        themeURI = relationshipItem['attrs']['Target']
+        break
+      }
+    }
+  } 
+  else if (relationshipArray['attrs']['Type'] === 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme') {
+    themeURI = relationshipArray['attrs']['Target']
+  }
+
+  const themeContent = await readXmlFile(zip, 'ppt/' + themeURI)
+
+  const themeColors = []
+  const clrScheme = getTextByPathList(themeContent, ['a:theme', 'a:themeElements', 'a:clrScheme'])
+  if (clrScheme) {
+    for (let i = 1; i <= 6; i++) {
+      if (clrScheme[`a:accent${i}`] === undefined) break
+      const color = getTextByPathList(clrScheme, [`a:accent${i}`, 'a:srgbClr', 'attrs', 'val'])
+      if (color) themeColors.push('#' + color)
+    }
+  }
+
+  return { themeContent, themeColors }
+}
+
+async function processSingleSlide(zip, sldFileName, themeContent, defaultTextStyle) {
+  const resName = sldFileName.replace('slides/slide', 'slides/_rels/slide') + '.rels'
+  const resContent = await readXmlFile(zip, resName)
+  let relationshipArray = resContent['Relationships']['Relationship']
+  if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray]
+  
+  let noteFilename = ''
+  let layoutFilename = ''
+  let masterFilename = ''
+  let themeFilename = ''
+  let diagramFilename = ''
+  const slideResObj = {}
+  const layoutResObj = {}
+  const masterResObj = {}
+  const themeResObj = {}
+  const diagramResObj = {}
+
+  for (const relationshipArrayItem of relationshipArray) {
+    switch (relationshipArrayItem['attrs']['Type']) {
+      case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout':
+        layoutFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
+        break
+      case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide':
+        noteFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
+        break
+      case 'http://schemas.microsoft.com/office/2007/relationships/diagramDrawing':
+        diagramFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
+        slideResObj[relationshipArrayItem['attrs']['Id']] = {
+          type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
+          target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
+        }
+        break
+      case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image':
+      case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart':
+      case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink':
+      default:
+        slideResObj[relationshipArrayItem['attrs']['Id']] = {
+          type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
+          target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/'),
+        }
+    }
+  }
+  
+  const slideNotesContent = await readXmlFile(zip, noteFilename)
+  const note = getNote(slideNotesContent)
+
+  const slideLayoutContent = await readXmlFile(zip, layoutFilename)
+  const slideLayoutTables = await indexNodes(slideLayoutContent)
+  const slideLayoutResFilename = layoutFilename.replace('slideLayouts/slideLayout', 'slideLayouts/_rels/slideLayout') + '.rels'
+  const slideLayoutResContent = await readXmlFile(zip, slideLayoutResFilename)
+  relationshipArray = slideLayoutResContent['Relationships']['Relationship']
+  if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray]
+
+  for (const relationshipArrayItem of relationshipArray) {
+    switch (relationshipArrayItem['attrs']['Type']) {
+      case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster':
+        masterFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
+        break
+      default:
+        layoutResObj[relationshipArrayItem['attrs']['Id']] = {
+          type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
+          target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/'),
+        }
+    }
+  }
+
+  const slideMasterContent = await readXmlFile(zip, masterFilename)
+  const slideMasterTextStyles = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:txStyles'])
+  const slideMasterTables = indexNodes(slideMasterContent)
+  const slideMasterResFilename = masterFilename.replace('slideMasters/slideMaster', 'slideMasters/_rels/slideMaster') + '.rels'
+  const slideMasterResContent = await readXmlFile(zip, slideMasterResFilename)
+  relationshipArray = slideMasterResContent['Relationships']['Relationship']
+  if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray]
+
+  for (const relationshipArrayItem of relationshipArray) {
+    switch (relationshipArrayItem['attrs']['Type']) {
+      case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme':
+        themeFilename = relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
+        break
+      default:
+        masterResObj[relationshipArrayItem['attrs']['Id']] = {
+          type: relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
+          target: relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/'),
+        }
+    }
+  }
+
+  if (themeFilename) {
+    const themeName = themeFilename.split('/').pop()
+    const themeResFileName = themeFilename.replace(themeName, '_rels/' + themeName) + '.rels'
+    const themeResContent = await readXmlFile(zip, themeResFileName)
+    if (themeResContent) {
+      relationshipArray = themeResContent['Relationships']['Relationship']
+      if (relationshipArray) {
+        if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray]
+        for (const relationshipArrayItem of relationshipArray) {
+          themeResObj[relationshipArrayItem['attrs']['Id']] = {
+            'type': relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
+            'target': relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
+          }
+        }
+      }
+    }
+  }
+
+  let digramFileContent = {}
+  if (diagramFilename) {
+    const diagName = diagramFilename.split('/').pop()
+    const diagramResFileName = diagramFilename.replace(diagName, '_rels/' + diagName) + '.rels'
+    digramFileContent = await readXmlFile(zip, diagramFilename)
+    if (digramFileContent) {
+      const digramFileContentObjToStr = JSON.stringify(digramFileContent).replace(/dsp:/g, 'p:')
+      digramFileContent = JSON.parse(digramFileContentObjToStr)
+    }
+    const digramResContent = await readXmlFile(zip, diagramResFileName)
+    if (digramResContent) {
+      relationshipArray = digramResContent['Relationships']['Relationship']
+      if (relationshipArray.constructor !== Array) relationshipArray = [relationshipArray]
+      for (const relationshipArrayItem of relationshipArray) {
+        diagramResObj[relationshipArrayItem['attrs']['Id']] = {
+          'type': relationshipArrayItem['attrs']['Type'].replace('http://schemas.openxmlformats.org/officeDocument/2006/relationships/', ''),
+          'target': relationshipArrayItem['attrs']['Target'].replace('../', 'ppt/')
+        }
+      }
+    }
+  }
+
+  const tableStyles = await readXmlFile(zip, 'ppt/tableStyles.xml')
+
+  const slideContent = await readXmlFile(zip, sldFileName)
+  const nodes = slideContent['p:sld']['p:cSld']['p:spTree']
+  const warpObj = {
+    zip,
+    slideLayoutContent,
+    slideLayoutTables,
+    slideMasterContent,
+    slideMasterTables,
+    slideContent,
+    tableStyles,
+    slideResObj,
+    slideMasterTextStyles,
+    layoutResObj,
+    masterResObj,
+    themeContent,
+    themeResObj,
+    digramFileContent,
+    diagramResObj,
+    defaultTextStyle,
+  }
+  const layoutElements = await getLayoutElements(warpObj)
+  const fill = await getSlideBackgroundFill(warpObj)
+
+  const elements = []
+  for (const nodeKey in nodes) {
+    if (nodes[nodeKey].constructor !== Array) nodes[nodeKey] = [nodes[nodeKey]]
+    for (const node of nodes[nodeKey]) {
+      const ret = await processNodesInSlide(nodeKey, node, nodes, warpObj, 'slide')
+      if (ret) elements.push(ret)
+    }
+  }
+
+  return {
+    fill,
+    elements,
+    layoutElements,
+    note,
+  }
+}
+
+function getNote(noteContent) {
+  let text = ''
+  let spNodes = getTextByPathList(noteContent, ['p:notes', 'p:cSld', 'p:spTree', 'p:sp'])
+  if (!spNodes) return ''
+
+  if (spNodes.constructor !== Array) spNodes = [spNodes]
+  for (const spNode of spNodes) {
+    let rNodes = getTextByPathList(spNode, ['p:txBody', 'a:p', 'a:r'])
+    if (!rNodes) continue
+
+    if (rNodes.constructor !== Array) rNodes = [rNodes]
+    for (const rNode of rNodes) {
+      const t = getTextByPathList(rNode, ['a:t'])
+      if (t && typeof t === 'string') text += t
+    }
+  }
+  return text
+}
+
+async function getLayoutElements(warpObj) {
+  const elements = []
+  const slideLayoutContent = warpObj['slideLayoutContent']
+  const slideMasterContent = warpObj['slideMasterContent']
+  const nodesSldLayout = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'p:cSld', 'p:spTree'])
+  const nodesSldMaster = getTextByPathList(slideMasterContent, ['p:sldMaster', 'p:cSld', 'p:spTree'])
+
+  const showMasterSp = getTextByPathList(slideLayoutContent, ['p:sldLayout', 'attrs', 'showMasterSp'])
+  if (nodesSldLayout) {
+    for (const nodeKey in nodesSldLayout) {
+      if (nodesSldLayout[nodeKey].constructor === Array) {
+        for (let i = 0; i < nodesSldLayout[nodeKey].length; i++) {
+          const ph = getTextByPathList(nodesSldLayout[nodeKey][i], ['p:nvSpPr', 'p:nvPr', 'p:ph'])
+          if (!ph) {
+            const ret = await processNodesInSlide(nodeKey, nodesSldLayout[nodeKey][i], nodesSldLayout, warpObj, 'slideLayoutBg')
+            if (ret) elements.push(ret)
+          }
+        }
+      } 
+      else {
+        const ph = getTextByPathList(nodesSldLayout[nodeKey], ['p:nvSpPr', 'p:nvPr', 'p:ph'])
+        if (!ph) {
+          const ret = await processNodesInSlide(nodeKey, nodesSldLayout[nodeKey], nodesSldLayout, warpObj, 'slideLayoutBg')
+          if (ret) elements.push(ret)
+        }
+      }
+    }
+  }
+  if (nodesSldMaster && showMasterSp !== '0') {
+    for (const nodeKey in nodesSldMaster) {
+      if (nodesSldMaster[nodeKey].constructor === Array) {
+        for (let i = 0; i < nodesSldMaster[nodeKey].length; i++) {
+          const ph = getTextByPathList(nodesSldMaster[nodeKey][i], ['p:nvSpPr', 'p:nvPr', 'p:ph'])
+          if (!ph) {
+            const ret = await processNodesInSlide(nodeKey, nodesSldMaster[nodeKey][i], nodesSldMaster, warpObj, 'slideMasterBg')
+            if (ret) elements.push(ret)
+          }
+        }
+      } 
+      else {
+        const ph = getTextByPathList(nodesSldMaster[nodeKey], ['p:nvSpPr', 'p:nvPr', 'p:ph'])
+        if (!ph) {
+          const ret = await processNodesInSlide(nodeKey, nodesSldMaster[nodeKey], nodesSldMaster, warpObj, 'slideMasterBg')
+          if (ret) elements.push(ret)
+        }
+      }
+    }
+  }
+  return elements
+}
+
+function indexNodes(content) {
+  const keys = Object.keys(content)
+  const spTreeNode = content[keys[0]]['p:cSld']['p:spTree']
+  const idTable = {}
+  const idxTable = {}
+  const typeTable = {}
+
+  for (const key in spTreeNode) {
+    if (key === 'p:nvGrpSpPr' || key === 'p:grpSpPr') continue
+
+    const targetNode = spTreeNode[key]
+
+    if (targetNode.constructor === Array) {
+      for (const targetNodeItem of targetNode) {
+        const nvSpPrNode = targetNodeItem['p:nvSpPr']
+        const id = getTextByPathList(nvSpPrNode, ['p:cNvPr', 'attrs', 'id'])
+        const idx = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'idx'])
+        const type = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'type'])
+
+        if (id) idTable[id] = targetNodeItem
+        if (idx) idxTable[idx] = targetNodeItem
+        if (type) typeTable[type] = targetNodeItem
+      }
+    } 
+    else {
+      const nvSpPrNode = targetNode['p:nvSpPr']
+      const id = getTextByPathList(nvSpPrNode, ['p:cNvPr', 'attrs', 'id'])
+      const idx = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'idx'])
+      const type = getTextByPathList(nvSpPrNode, ['p:nvPr', 'p:ph', 'attrs', 'type'])
+
+      if (id) idTable[id] = targetNode
+      if (idx) idxTable[idx] = targetNode
+      if (type) typeTable[type] = targetNode
+    }
+  }
+
+  return { idTable, idxTable, typeTable }
+}
+
+async function processNodesInSlide(nodeKey, nodeValue, nodes, warpObj, source) {
+  let json
+
+  switch (nodeKey) {
+    case 'p:sp': // Shape, Text
+      json = await processSpNode(nodeValue, nodes, warpObj, source)
+      break
+    case 'p:cxnSp': // Shape, Text
+      json = await processCxnSpNode(nodeValue, nodes, warpObj, source)
+      break
+    case 'p:pic': // Image, Video, Audio
+      json = await processPicNode(nodeValue, warpObj, source)
+      break
+    case 'p:graphicFrame': // Chart, Diagram, Table
+      json = await processGraphicFrameNode(nodeValue, warpObj, source)
+      break
+    case 'p:grpSp':
+      json = await processGroupSpNode(nodeValue, warpObj, source)
+      break
+    case 'mc:AlternateContent':
+      if (getTextByPathList(nodeValue, ['mc:Fallback', 'p:grpSpPr', 'a:xfrm'])) {
+        json = await processGroupSpNode(getTextByPathList(nodeValue, ['mc:Fallback']), warpObj, source)
+      }
+      else if (getTextByPathList(nodeValue, ['mc:Choice'])) {
+        json = await processMathNode(nodeValue, warpObj, source)
+      }
+      break
+    default:
+  }
+
+  return json
+}
+
+async function processMathNode(node, warpObj, source) {
+  const choice = getTextByPathList(node, ['mc:Choice'])
+  const fallback = getTextByPathList(node, ['mc:Fallback'])
+
+  const order = node['attrs']['order']
+  const xfrmNode = getTextByPathList(choice, ['p:sp', 'p:spPr', 'a:xfrm'])
+  const { top, left } = getPosition(xfrmNode, undefined, undefined)
+  const { width, height } = getSize(xfrmNode, undefined, undefined)
+
+  const oMath = findOMath(choice)[0]
+  const latex = latexFormart(parseOMath(oMath))
+
+  const blipFill = getTextByPathList(fallback, ['p:sp', 'p:spPr', 'a:blipFill'])
+  const picBase64 = await getPicFill(source, blipFill, warpObj)
+
+  return {
+    type: 'math',
+    top,
+    left,
+    width, 
+    height,
+    latex,
+    picBase64,
+    order,
+  }
+}
+
+async function processGroupSpNode(node, warpObj, source) {
+  const order = node['attrs']['order']
+  const xfrmNode = getTextByPathList(node, ['p:grpSpPr', 'a:xfrm'])
+  if (!xfrmNode) return null
+
+  const x = parseInt(xfrmNode['a:off']['attrs']['x']) * RATIO_EMUs_Points
+  const y = parseInt(xfrmNode['a:off']['attrs']['y']) * RATIO_EMUs_Points
+  const chx = parseInt(xfrmNode['a:chOff']['attrs']['x']) * RATIO_EMUs_Points
+  const chy = parseInt(xfrmNode['a:chOff']['attrs']['y']) * RATIO_EMUs_Points
+  const cx = parseInt(xfrmNode['a:ext']['attrs']['cx']) * RATIO_EMUs_Points
+  const cy = parseInt(xfrmNode['a:ext']['attrs']['cy']) * RATIO_EMUs_Points
+  const chcx = parseInt(xfrmNode['a:chExt']['attrs']['cx']) * RATIO_EMUs_Points
+  const chcy = parseInt(xfrmNode['a:chExt']['attrs']['cy']) * RATIO_EMUs_Points
+
+  const isFlipV = getTextByPathList(xfrmNode, ['attrs', 'flipV']) === '1'
+  const isFlipH = getTextByPathList(xfrmNode, ['attrs', 'flipH']) === '1'
+
+  let rotate = getTextByPathList(xfrmNode, ['attrs', 'rot']) || 0
+  if (rotate) rotate = angleToDegrees(rotate)
+
+  const ws = cx / chcx
+  const hs = cy / chcy
+
+
+  const elements = []
+  for (const nodeKey in node) {
+    if (node[nodeKey].constructor === Array) {
+      for (const item of node[nodeKey]) {
+        const ret = await processNodesInSlide(nodeKey, item, node, warpObj, source)
+        if (ret) {
+          // 如果是嵌套的组合,需要调整其子元素的位置和大小
+          if (ret.type === 'group') {
+            ret.elements = ret.elements.map(element => ({
+              ...element,
+              left: element.left * ws,
+              top: element.top * hs,
+              width: element.width * ws,
+              height: element.height * hs,
+            }))
+          }
+          elements.push(ret)
+        }
+      }
+    }
+    else {
+      const ret = await processNodesInSlide(nodeKey, node[nodeKey], node, warpObj, source)
+      if (ret) {
+        // 如果是嵌套的组合,需要调整其子元素的位置和大小
+        if (ret.type === 'group') {
+          ret.elements = ret.elements.map(element => ({
+            ...element,
+            left: element.left * ws,
+            top: element.top * hs,
+            width: element.width * ws,
+            height: element.height * hs,
+          }))
+        }
+        elements.push(ret)
+      }
+    }
+  }
+  
+  return {
+    type: 'group',
+    top: y,
+    left: x,
+    width: cx,
+    height: cy,
+    rotate,
+    order,
+    isFlipV,
+    isFlipH,
+    elements: elements.map(element => ({
+      ...element,
+      left: (element.left - chx) * ws,
+      top: (element.top - chy) * hs,
+      width: element.width * ws,
+      height: element.height * hs,
+    }))
+  }
+}
+
+async function processSpNode(node, pNode, warpObj, source) {
+  const name = getTextByPathList(node, ['p:nvSpPr', 'p:cNvPr', 'attrs', 'name'])
+  const idx = getTextByPathList(node, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'idx'])
+  let type = getTextByPathList(node, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
+  const order = getTextByPathList(node, ['attrs', 'order'])
+
+  let slideLayoutSpNode, slideMasterSpNode
+
+  if (type) {
+    if (idx) {
+      slideLayoutSpNode = warpObj['slideLayoutTables']['typeTable'][type]
+      slideMasterSpNode = warpObj['slideMasterTables']['typeTable'][type]
+    } 
+    else {
+      slideLayoutSpNode = warpObj['slideLayoutTables']['typeTable'][type]
+      slideMasterSpNode = warpObj['slideMasterTables']['typeTable'][type]
+    }
+  }
+  else if (idx) {
+    slideLayoutSpNode = warpObj['slideLayoutTables']['idxTable'][idx]
+    slideMasterSpNode = warpObj['slideMasterTables']['idxTable'][idx]
+  }
+
+  if (!type) {
+    const txBoxVal = getTextByPathList(node, ['p:nvSpPr', 'p:cNvSpPr', 'attrs', 'txBox'])
+    if (txBoxVal === '1') type = 'text'
+  }
+  if (!type) type = getTextByPathList(slideLayoutSpNode, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
+  if (!type) type = getTextByPathList(slideMasterSpNode, ['p:nvSpPr', 'p:nvPr', 'p:ph', 'attrs', 'type'])
+
+  if (!type) {
+    if (source === 'diagramBg') type = 'diagram'
+    else type = 'obj'
+  }
+
+  return await genShape(node, pNode, slideLayoutSpNode, slideMasterSpNode, name, type, order, warpObj, source)
+}
+
+async function processCxnSpNode(node, pNode, warpObj, source) {
+  const name = node['p:nvCxnSpPr']['p:cNvPr']['attrs']['name']
+  const type = (node['p:nvCxnSpPr']['p:nvPr']['p:ph'] === undefined) ? undefined : node['p:nvSpPr']['p:nvPr']['p:ph']['attrs']['type']
+  const order = node['attrs']['order']
+
+  return await genShape(node, pNode, undefined, undefined, name, type, order, warpObj, source)
+}
+
+async function genShape(node, pNode, slideLayoutSpNode, slideMasterSpNode, name, type, order, warpObj, source) {
+  const xfrmList = ['p:spPr', 'a:xfrm']
+  const slideXfrmNode = getTextByPathList(node, xfrmList)
+  const slideLayoutXfrmNode = getTextByPathList(slideLayoutSpNode, xfrmList)
+  const slideMasterXfrmNode = getTextByPathList(slideMasterSpNode, xfrmList)
+
+  const shapType = getTextByPathList(node, ['p:spPr', 'a:prstGeom', 'attrs', 'prst'])
+  const custShapType = getTextByPathList(node, ['p:spPr', 'a:custGeom'])
+
+  const { top, left } = getPosition(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode)
+  const { width, height } = getSize(slideXfrmNode, slideLayoutXfrmNode, slideMasterXfrmNode)
+
+  const isFlipV = getTextByPathList(slideXfrmNode, ['attrs', 'flipV']) === '1'
+  const isFlipH = getTextByPathList(slideXfrmNode, ['attrs', 'flipH']) === '1'
+
+  const rotate = angleToDegrees(getTextByPathList(slideXfrmNode, ['attrs', 'rot']))
+
+  const txtXframeNode = getTextByPathList(node, ['p:txXfrm'])
+  let txtRotate
+  if (txtXframeNode) {
+    const txtXframeRot = getTextByPathList(txtXframeNode, ['attrs', 'rot'])
+    if (txtXframeRot) txtRotate = angleToDegrees(txtXframeRot) + 90
+  } 
+  else txtRotate = rotate
+
+  let content = ''
+  if (node['p:txBody']) content = genTextBody(node['p:txBody'], node, slideLayoutSpNode, type, warpObj)
+
+  const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(node, type, warpObj)
+  const fill = await getShapeFill(node, pNode, undefined, warpObj, source) || ''
+
+  let shadow
+  const outerShdwNode = getTextByPathList(node, ['p:spPr', 'a:effectLst', 'a:outerShdw'])
+  if (outerShdwNode) shadow = getShadow(outerShdwNode, warpObj)
+
+  const vAlign = getVerticalAlign(node, slideLayoutSpNode, slideMasterSpNode, type)
+  const isVertical = getTextByPathList(node, ['p:txBody', 'a:bodyPr', 'attrs', 'vert']) === 'eaVert'
+
+  const data = {
+    left,
+    top,
+    width,
+    height,
+    borderColor,
+    borderWidth,
+    borderType,
+    borderStrokeDasharray: strokeDasharray,
+    fill,
+    content,
+    isFlipV,
+    isFlipH,
+    rotate,
+    vAlign,
+    name,
+    order,
+  }
+
+  if (shadow) data.shadow = shadow
+
+  if (custShapType && type !== 'diagram') {
+    const ext = getTextByPathList(slideXfrmNode, ['a:ext', 'attrs'])
+    const w = parseInt(ext['cx']) * RATIO_EMUs_Points
+    const h = parseInt(ext['cy']) * RATIO_EMUs_Points
+    const d = getCustomShapePath(custShapType, w, h)
+    if (data.content && !hasValidText(data.content)) data.content = ''
+
+    return {
+      ...data,
+      type: 'shape',
+      shapType: 'custom',
+      path: d,
+    }
+  }
+  if (shapType && (type === 'obj' || !type)) {
+    if (data.content && !hasValidText(data.content)) data.content = ''
+    return {
+      ...data,
+      type: 'shape',
+      shapType,
+    }
+  }
+  return {
+    ...data,
+    type: 'text',
+    isVertical,
+    rotate: txtRotate,
+  }
+}
+
+async function processPicNode(node, warpObj, source) {
+  let resObj
+  if (source === 'slideMasterBg') resObj = warpObj['masterResObj']
+  else if (source === 'slideLayoutBg') resObj = warpObj['layoutResObj']
+  else resObj = warpObj['slideResObj']
+
+  const order = node['attrs']['order']
+  
+  const rid = node['p:blipFill']['a:blip']['attrs']['r:embed']
+  const imgName = resObj[rid]['target']
+  const imgFileExt = extractFileExtension(imgName).toLowerCase()
+  const zip = warpObj['zip']
+  const imgArrayBuffer = await zip.file(imgName).async('arraybuffer')
+  const xfrmNode = node['p:spPr']['a:xfrm']
+
+  const mimeType = getMimeType(imgFileExt)
+  const { top, left } = getPosition(xfrmNode, undefined, undefined)
+  const { width, height } = getSize(xfrmNode, undefined, undefined)
+  const src = `data:${mimeType};base64,${base64ArrayBuffer(imgArrayBuffer)}`
+
+  const isFlipV = getTextByPathList(xfrmNode, ['attrs', 'flipV']) === '1'
+  const isFlipH = getTextByPathList(xfrmNode, ['attrs', 'flipH']) === '1'
+
+  let rotate = 0
+  const rotateNode = getTextByPathList(node, ['p:spPr', 'a:xfrm', 'attrs', 'rot'])
+  if (rotateNode) rotate = angleToDegrees(rotateNode)
+
+  const videoNode = getTextByPathList(node, ['p:nvPicPr', 'p:nvPr', 'a:videoFile'])
+  let videoRid, videoFile, videoFileExt, videoMimeType, uInt8ArrayVideo, videoBlob
+  let isVdeoLink = false
+
+  if (videoNode) {
+    videoRid = videoNode['attrs']['r:link']
+    videoFile = resObj[videoRid]['target']
+    if (isVideoLink(videoFile)) {
+      videoFile = escapeHtml(videoFile)
+      isVdeoLink = true
+    } 
+    else {
+      videoFileExt = extractFileExtension(videoFile).toLowerCase()
+      if (videoFileExt === 'mp4' || videoFileExt === 'webm' || videoFileExt === 'ogg') {
+        uInt8ArrayVideo = await zip.file(videoFile).async('arraybuffer')
+        videoMimeType = getMimeType(videoFileExt)
+        videoBlob = URL.createObjectURL(new Blob([uInt8ArrayVideo], {
+          type: videoMimeType
+        }))
+      }
+    }
+  }
+
+  const audioNode = getTextByPathList(node, ['p:nvPicPr', 'p:nvPr', 'a:audioFile'])
+  let audioRid, audioFile, audioFileExt, uInt8ArrayAudio, audioBlob
+  if (audioNode) {
+    audioRid = audioNode['attrs']['r:link']
+    audioFile = resObj[audioRid]['target']
+    audioFileExt = extractFileExtension(audioFile).toLowerCase()
+    if (audioFileExt === 'mp3' || audioFileExt === 'wav' || audioFileExt === 'ogg') {
+      uInt8ArrayAudio = await zip.file(audioFile).async('arraybuffer')
+      audioBlob = URL.createObjectURL(new Blob([uInt8ArrayAudio]))
+    }
+  }
+
+  if (videoNode && !isVdeoLink) {
+    return {
+      type: 'video',
+      top,
+      left,
+      width, 
+      height,
+      rotate,
+      blob: videoBlob,
+      order,
+    }
+  } 
+  if (videoNode && isVdeoLink) {
+    return {
+      type: 'video',
+      top,
+      left,
+      width, 
+      height,
+      rotate,
+      src: videoFile,
+      order,
+    }
+  }
+  if (audioNode) {
+    return {
+      type: 'audio',
+      top,
+      left,
+      width, 
+      height,
+      rotate,
+      blob: audioBlob,
+      order,
+    }
+  }
+
+  let rect
+  const srcRectAttrs = getTextByPathList(node, ['p:blipFill', 'a:srcRect', 'attrs'])
+  if (srcRectAttrs && (srcRectAttrs.t || srcRectAttrs.b || srcRectAttrs.l || srcRectAttrs.r)) {
+    rect = {}
+    if (srcRectAttrs.t) rect.t = srcRectAttrs.t / 1000
+    if (srcRectAttrs.b) rect.b = srcRectAttrs.b / 1000
+    if (srcRectAttrs.l) rect.l = srcRectAttrs.l / 1000
+    if (srcRectAttrs.r) rect.r = srcRectAttrs.r / 1000
+  }
+  const geom = getTextByPathList(node, ['p:spPr', 'a:prstGeom', 'attrs', 'prst']) || 'rect'
+
+  const { borderColor, borderWidth, borderType, strokeDasharray } = getBorder(node, undefined, warpObj)
+
+  return {
+    type: 'image',
+    top,
+    left,
+    width, 
+    height,
+    rotate,
+    src,
+    isFlipV,
+    isFlipH,
+    order,
+    rect,
+    geom,
+    borderColor,
+    borderWidth,
+    borderType,
+    borderStrokeDasharray: strokeDasharray,
+  }
+}
+
+async function processGraphicFrameNode(node, warpObj, source) {
+  const graphicTypeUri = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'attrs', 'uri'])
+  
+  let result
+  switch (graphicTypeUri) {
+    case 'http://schemas.openxmlformats.org/drawingml/2006/table':
+      result = await genTable(node, warpObj)
+      break
+    case 'http://schemas.openxmlformats.org/drawingml/2006/chart':
+      result = await genChart(node, warpObj)
+      break
+    case 'http://schemas.openxmlformats.org/drawingml/2006/diagram':
+      result = await genDiagram(node, warpObj)
+      break
+    case 'http://schemas.openxmlformats.org/presentationml/2006/ole':
+      let oleObjNode = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'mc:AlternateContent', 'mc:Fallback', 'p:oleObj'])
+      if (!oleObjNode) oleObjNode = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'p:oleObj'])
+      if (oleObjNode) result = await processGroupSpNode(oleObjNode, warpObj, source)
+      break
+    default:
+  }
+  return result
+}
+
+async function genTable(node, warpObj) {
+  const order = node['attrs']['order']
+  const tableNode = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'a:tbl'])
+  const xfrmNode = getTextByPathList(node, ['p:xfrm'])
+  const { top, left } = getPosition(xfrmNode, undefined, undefined)
+  const { width, height } = getSize(xfrmNode, undefined, undefined)
+
+  const getTblPr = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'a:tbl', 'a:tblPr'])
+  let getColsGrid = getTextByPathList(node, ['a:graphic', 'a:graphicData', 'a:tbl', 'a:tblGrid', 'a:gridCol'])
+  if (getColsGrid.constructor !== Array) getColsGrid = [getColsGrid]
+
+  const colWidths = []
+  if (getColsGrid) {
+    for (const item of getColsGrid) {
+      const colWidthParam = getTextByPathList(item, ['attrs', 'w']) || 0
+      const colWidth = parseInt(colWidthParam) * RATIO_EMUs_Points
+      colWidths.push(colWidth)
+    }
+  }
+
+  const firstRowAttr = getTblPr['attrs'] ? getTblPr['attrs']['firstRow'] : undefined
+  const firstColAttr = getTblPr['attrs'] ? getTblPr['attrs']['firstCol'] : undefined
+  const lastRowAttr = getTblPr['attrs'] ? getTblPr['attrs']['lastRow'] : undefined
+  const lastColAttr = getTblPr['attrs'] ? getTblPr['attrs']['lastCol'] : undefined
+  const bandRowAttr = getTblPr['attrs'] ? getTblPr['attrs']['bandRow'] : undefined
+  const bandColAttr = getTblPr['attrs'] ? getTblPr['attrs']['bandCol'] : undefined
+  const tblStylAttrObj = {
+    isFrstRowAttr: (firstRowAttr && firstRowAttr === '1') ? 1 : 0,
+    isFrstColAttr: (firstColAttr && firstColAttr === '1') ? 1 : 0,
+    isLstRowAttr: (lastRowAttr && lastRowAttr === '1') ? 1 : 0,
+    isLstColAttr: (lastColAttr && lastColAttr === '1') ? 1 : 0,
+    isBandRowAttr: (bandRowAttr && bandRowAttr === '1') ? 1 : 0,
+    isBandColAttr: (bandColAttr && bandColAttr === '1') ? 1 : 0,
+  }
+
+  let thisTblStyle
+  const tbleStyleId = getTblPr['a:tableStyleId']
+  if (tbleStyleId) {
+    const tbleStylList = warpObj['tableStyles']['a:tblStyleLst']['a:tblStyle']
+    if (tbleStylList) {
+      if (tbleStylList.constructor === Array) {
+        for (let k = 0; k < tbleStylList.length; k++) {
+          if (tbleStylList[k]['attrs']['styleId'] === tbleStyleId) {
+            thisTblStyle = tbleStylList[k]
+          }
+        }
+      } 
+      else {
+        if (tbleStylList['attrs']['styleId'] === tbleStyleId) {
+          thisTblStyle = tbleStylList
+        }
+      }
+    }
+  }
+  if (thisTblStyle) thisTblStyle['tblStylAttrObj'] = tblStylAttrObj
+
+  let borders = {}
+  const tblStyl = getTextByPathList(thisTblStyle, ['a:wholeTbl', 'a:tcStyle'])
+  const tblBorderStyl = getTextByPathList(tblStyl, ['a:tcBdr'])
+  if (tblBorderStyl) borders = getTableBorders(tblBorderStyl, warpObj)
+
+  let tbl_bgcolor = ''
+  let tbl_bgFillschemeClr = getTextByPathList(thisTblStyle, ['a:tblBg', 'a:fillRef'])
+  if (tbl_bgFillschemeClr) {
+    tbl_bgcolor = getSolidFill(tbl_bgFillschemeClr, undefined, undefined, warpObj)
+  }
+  if (tbl_bgFillschemeClr === undefined) {
+    tbl_bgFillschemeClr = getTextByPathList(thisTblStyle, ['a:wholeTbl', 'a:tcStyle', 'a:fill', 'a:solidFill'])
+    tbl_bgcolor = getSolidFill(tbl_bgFillschemeClr, undefined, undefined, warpObj)
+  }
+
+  let trNodes = tableNode['a:tr']
+  if (trNodes.constructor !== Array) trNodes = [trNodes]
+  
+  const data = []
+  const rowHeights = []
+  for (let i = 0; i < trNodes.length; i++) {
+    const trNode = trNodes[i]
+    
+    const rowHeightParam = getTextByPathList(trNodes[i], ['attrs', 'h']) || 0
+    const rowHeight = parseInt(rowHeightParam) * RATIO_EMUs_Points
+    rowHeights.push(rowHeight)
+
+    const {
+      fillColor,
+      fontColor,
+      fontBold,
+    } = getTableRowParams(trNodes, i, tblStylAttrObj, thisTblStyle, warpObj)
+
+    const tcNodes = trNode['a:tc']
+    const tr = []
+
+    if (tcNodes.constructor === Array) {
+      for (let j = 0; j < tcNodes.length; j++) {
+        const tcNode = tcNodes[j]
+        let a_sorce
+        if (j === 0 && tblStylAttrObj['isFrstColAttr'] === 1) {
+          a_sorce = 'a:firstCol'
+          if (tblStylAttrObj['isLstRowAttr'] === 1 && i === (trNodes.length - 1) && getTextByPathList(thisTblStyle, ['a:seCell'])) {
+            a_sorce = 'a:seCell'
+          } 
+          else if (tblStylAttrObj['isFrstRowAttr'] === 1 && i === 0 &&
+            getTextByPathList(thisTblStyle, ['a:neCell'])) {
+            a_sorce = 'a:neCell'
+          }
+        } 
+        else if (
+          (j > 0 && tblStylAttrObj['isBandColAttr'] === 1) &&
+          !(tblStylAttrObj['isFrstColAttr'] === 1 && i === 0) &&
+          !(tblStylAttrObj['isLstRowAttr'] === 1 && i === (trNodes.length - 1)) &&
+          j !== (tcNodes.length - 1)
+        ) {
+          if ((j % 2) !== 0) {
+            let aBandNode = getTextByPathList(thisTblStyle, ['a:band2V'])
+            if (aBandNode === undefined) {
+              aBandNode = getTextByPathList(thisTblStyle, ['a:band1V'])
+              if (aBandNode) a_sorce = 'a:band2V'
+            } 
+            else a_sorce = 'a:band2V'
+          }
+        }
+        if (j === (tcNodes.length - 1) && tblStylAttrObj['isLstColAttr'] === 1) {
+          a_sorce = 'a:lastCol'
+          if (tblStylAttrObj['isLstRowAttr'] === 1 && i === (trNodes.length - 1) && getTextByPathList(thisTblStyle, ['a:swCell'])) {
+            a_sorce = 'a:swCell'
+          } 
+          else if (tblStylAttrObj['isFrstRowAttr'] === 1 && i === 0 && getTextByPathList(thisTblStyle, ['a:nwCell'])) {
+            a_sorce = 'a:nwCell'
+          }
+        }
+        const text = genTextBody(tcNode['a:txBody'], tcNode, undefined, undefined, warpObj)
+        const cell = await getTableCellParams(tcNode, thisTblStyle, a_sorce, warpObj)
+        const td = { text }
+        if (cell.rowSpan) td.rowSpan = cell.rowSpan
+        if (cell.colSpan) td.colSpan = cell.colSpan
+        if (cell.vMerge) td.vMerge = cell.vMerge
+        if (cell.hMerge) td.hMerge = cell.hMerge
+        if (cell.fontBold || fontBold) td.fontBold = cell.fontBold || fontBold
+        if (cell.fontColor || fontColor) td.fontColor = cell.fontColor || fontColor
+        if (cell.fillColor || fillColor || tbl_bgcolor) td.fillColor = cell.fillColor || fillColor || tbl_bgcolor
+        if (cell.borders) td.borders = cell.borders
+
+        tr.push(td)
+      }
+    } 
+    else {
+      let a_sorce
+      if (tblStylAttrObj['isFrstColAttr'] === 1 && tblStylAttrObj['isLstRowAttr'] !== 1) {
+        a_sorce = 'a:firstCol'
+      } 
+      else if (tblStylAttrObj['isBandColAttr'] === 1 && tblStylAttrObj['isLstRowAttr'] !== 1) {
+        let aBandNode = getTextByPathList(thisTblStyle, ['a:band2V'])
+        if (!aBandNode) {
+          aBandNode = getTextByPathList(thisTblStyle, ['a:band1V'])
+          if (aBandNode) a_sorce = 'a:band2V'
+        } 
+        else a_sorce = 'a:band2V'
+      }
+      if (tblStylAttrObj['isLstColAttr'] === 1 && tblStylAttrObj['isLstRowAttr'] !== 1) {
+        a_sorce = 'a:lastCol'
+      }
+
+      const text = genTextBody(tcNodes['a:txBody'], tcNodes, undefined, undefined, warpObj)
+      const cell = await getTableCellParams(tcNodes, thisTblStyle, a_sorce, warpObj)
+      const td = { text }
+      if (cell.rowSpan) td.rowSpan = cell.rowSpan
+      if (cell.colSpan) td.colSpan = cell.colSpan
+      if (cell.vMerge) td.vMerge = cell.vMerge
+      if (cell.hMerge) td.hMerge = cell.hMerge
+      if (cell.fontBold || fontBold) td.fontBold = cell.fontBold || fontBold
+      if (cell.fontColor || fontColor) td.fontColor = cell.fontColor || fontColor
+      if (cell.fillColor || fillColor || tbl_bgcolor) td.fillColor = cell.fillColor || fillColor || tbl_bgcolor
+      if (cell.borders) td.borders = cell.borders
+
+      tr.push(td)
+    }
+    data.push(tr)
+  }
+
+  return {
+    type: 'table',
+    top,
+    left,
+    width,
+    height,
+    data,
+    order,
+    borders,
+    rowHeights,
+    colWidths,
+  }
+}
+
+async function genChart(node, warpObj) {
+  const order = node['attrs']['order']
+  const xfrmNode = getTextByPathList(node, ['p:xfrm'])
+  const { top, left } = getPosition(xfrmNode, undefined, undefined)
+  const { width, height } = getSize(xfrmNode, undefined, undefined)
+
+  const rid = node['a:graphic']['a:graphicData']['c:chart']['attrs']['r:id']
+  let refName = getTextByPathList(warpObj['slideResObj'], [rid, 'target'])
+  if (!refName) refName = getTextByPathList(warpObj['layoutResObj'], [rid, 'target'])
+  if (!refName) refName = getTextByPathList(warpObj['masterResObj'], [rid, 'target'])
+  if (!refName) return {}
+
+  const content = await readXmlFile(warpObj['zip'], refName)
+  const plotArea = getTextByPathList(content, ['c:chartSpace', 'c:chart', 'c:plotArea'])
+
+  const chart = getChartInfo(plotArea, warpObj)
+
+  if (!chart) return {}
+
+  const data = {
+    type: 'chart',
+    top,
+    left,
+    width,
+    height,
+    data: chart.data,
+    colors: chart.colors,
+    chartType: chart.type,
+    order,
+  }
+  if (chart.marker !== undefined) data.marker = chart.marker
+  if (chart.barDir !== undefined) data.barDir = chart.barDir
+  if (chart.holeSize !== undefined) data.holeSize = chart.holeSize
+  if (chart.grouping !== undefined) data.grouping = chart.grouping
+  if (chart.style !== undefined) data.style = chart.style
+
+  return data
+}
+
+async function genDiagram(node, warpObj) {
+  const order = node['attrs']['order']
+  const xfrmNode = getTextByPathList(node, ['p:xfrm'])
+  const { left, top } = getPosition(xfrmNode, undefined, undefined)
+  const { width, height } = getSize(xfrmNode, undefined, undefined)
+  
+  const dgmDrwSpArray = getTextByPathList(warpObj['digramFileContent'], ['p:drawing', 'p:spTree', 'p:sp'])
+  const elements = []
+  if (dgmDrwSpArray) {
+    for (const item of dgmDrwSpArray) {
+      const el = await processSpNode(item, node, warpObj, 'diagramBg')
+      if (el) elements.push(el)
+    }
+  }
+
+  return {
+    type: 'diagram',
+    left,
+    top,
+    width,
+    height,
+    elements,
+    order,
+  }
+}

+ 47 - 0
src/utils/pptxToJson/readXmlFile.js

@@ -0,0 +1,47 @@
+import * as txml from 'txml/dist/txml.mjs'
+
+let cust_attr_order = 0
+
+export function simplifyLostLess(children, parentAttributes = {}) {
+  const out = {}
+  if (!children.length) return out
+
+  if (children.length === 1 && typeof children[0] === 'string') {
+    return Object.keys(parentAttributes).length ? {
+      attrs: { order: cust_attr_order++, ...parentAttributes },
+      value: children[0],
+    } : children[0]
+  }
+  for (const child of children) {
+    if (typeof child !== 'object') return
+    if (child.tagName === '?xml') continue
+
+    if (!out[child.tagName]) out[child.tagName] = []
+
+    const kids = simplifyLostLess(child.children || [], child.attributes)
+    
+    if (typeof kids === 'object') {
+      if (!kids.attrs) kids.attrs = { order: cust_attr_order++ }
+      else kids.attrs.order = cust_attr_order++
+    }
+    if (Object.keys(child.attributes || {}).length) {
+      kids.attrs = { ...kids.attrs, ...child.attributes }
+    }
+    out[child.tagName].push(kids)
+  }
+  for (const child in out) {
+    if (out[child].length === 1) out[child] = out[child][0]
+  }
+
+  return out
+}
+
+export async function readXmlFile(zip, filename) {
+  try {
+    const data = await zip.file(filename).async('string')
+    return simplifyLostLess(txml.parse(data))
+  }
+  catch {
+    return null
+  }
+}

+ 56 - 0
src/utils/pptxToJson/schemeColor.js

@@ -0,0 +1,56 @@
+import { getTextByPathList } from './utils'
+
+export function getSchemeColorFromTheme(schemeClr, warpObj, clrMap, phClr) {
+  let color
+  let slideLayoutClrOvride
+  if (clrMap) slideLayoutClrOvride = clrMap
+  else {
+    let sldClrMapOvr = getTextByPathList(warpObj['slideContent'], ['p:sld', 'p:clrMapOvr', 'a:overrideClrMapping', 'attrs'])
+    if (sldClrMapOvr) slideLayoutClrOvride = sldClrMapOvr
+    else {
+      sldClrMapOvr = getTextByPathList(warpObj['slideLayoutContent'], ['p:sldLayout', 'p:clrMapOvr', 'a:overrideClrMapping', 'attrs'])
+      if (sldClrMapOvr) slideLayoutClrOvride = sldClrMapOvr
+      else {
+        slideLayoutClrOvride = getTextByPathList(warpObj['slideMasterContent'], ['p:sldMaster', 'p:clrMap', 'attrs'])
+      }
+    }
+  }
+  const schmClrName = schemeClr.substr(2)
+  if (schmClrName === 'phClr' && phClr) color = phClr
+  else {
+    if (slideLayoutClrOvride) {
+      switch (schmClrName) {
+        case 'tx1':
+        case 'tx2':
+        case 'bg1':
+        case 'bg2':
+          schemeClr = 'a:' + slideLayoutClrOvride[schmClrName]
+          break
+        default:
+          break
+      }
+    }
+    else {
+      switch (schemeClr) {
+        case 'tx1':
+          schemeClr = 'a:dk1'
+          break
+        case 'tx2':
+          schemeClr = 'a:dk2'
+          break
+        case 'bg1':
+          schemeClr = 'a:lt1'
+          break
+        case 'bg2':
+          schemeClr = 'a:lt2'
+          break
+        default:
+          break
+      }
+    }
+    const refNode = getTextByPathList(warpObj['themeContent'], ['a:theme', 'a:themeElements', 'a:clrScheme', schemeClr])
+    color = getTextByPathList(refNode, ['a:srgbClr', 'attrs', 'val'])
+    if (!color && refNode) color = getTextByPathList(refNode, ['a:sysClr', 'attrs', 'lastClr'])
+  }
+  return color
+}

+ 19 - 0
src/utils/pptxToJson/shadow.js

@@ -0,0 +1,19 @@
+import { getSolidFill } from './fill'
+import { RATIO_EMUs_Points } from './constants'
+
+export function getShadow(node, warpObj) {
+  const chdwClrNode = getSolidFill(node, undefined, undefined, warpObj)
+  const outerShdwAttrs = node['attrs']
+  const dir = outerShdwAttrs['dir'] ? (parseInt(outerShdwAttrs['dir']) / 60000) : 0
+  const dist = parseInt(outerShdwAttrs['dist']) * RATIO_EMUs_Points
+  const blurRad = outerShdwAttrs['blurRad'] ? parseInt(outerShdwAttrs['blurRad']) * RATIO_EMUs_Points : ''
+  const vx = dist * Math.sin(dir * Math.PI / 180)
+  const hx = dist * Math.cos(dir * Math.PI / 180)
+
+  return {
+    h: hx,
+    v: vx,
+    blur: blurRad,
+    color: chdwClrNode,
+  }
+}

+ 190 - 0
src/utils/pptxToJson/shape.js

@@ -0,0 +1,190 @@
+import { getTextByPathList } from './utils'
+
+export function shapeArc(cX, cY, rX, rY, stAng, endAng, isClose) {
+  let dData
+  let angle = stAng
+  if (endAng >= stAng) {
+    while (angle <= endAng) {
+      const radians = angle * (Math.PI / 180)
+      const x = cX + Math.cos(radians) * rX
+      const y = cY + Math.sin(radians) * rY
+      if (angle === stAng) {
+        dData = ' M' + x + ' ' + y
+      }
+      dData += ' L' + x + ' ' + y
+      angle++
+    }
+  } 
+  else {
+    while (angle > endAng) {
+      const radians = angle * (Math.PI / 180)
+      const x = cX + Math.cos(radians) * rX
+      const y = cY + Math.sin(radians) * rY
+      if (angle === stAng) {
+        dData = ' M ' + x + ' ' + y
+      }
+      dData += ' L ' + x + ' ' + y
+      angle--
+    }
+  }
+  dData += (isClose ? ' z' : '')
+  return dData
+}
+
+export function getCustomShapePath(custShapType, w, h) {
+  const pathLstNode = getTextByPathList(custShapType, ['a:pathLst'])
+  let pathNodes = getTextByPathList(pathLstNode, ['a:path'])
+
+  if (Array.isArray(pathNodes)) pathNodes = pathNodes.shift()
+
+  const maxX = parseInt(pathNodes['attrs']['w'])
+  const maxY = parseInt(pathNodes['attrs']['h'])
+  const cX = maxX === 0 ? 0 : (1 / maxX) * w
+  const cY = maxY === 0 ? 0 : (1 / maxY) * h
+  let d = ''
+
+  let moveToNode = getTextByPathList(pathNodes, ['a:moveTo'])
+
+  const lnToNodes = pathNodes['a:lnTo']
+  let cubicBezToNodes = pathNodes['a:cubicBezTo']
+  const arcToNodes = pathNodes['a:arcTo']
+  let closeNode = getTextByPathList(pathNodes, ['a:close'])
+  if (!Array.isArray(moveToNode)) moveToNode = [moveToNode]
+
+  const multiSapeAry = []
+  if (moveToNode.length > 0) {
+    Object.keys(moveToNode).forEach(key => {
+      const moveToPtNode = moveToNode[key]['a:pt']
+      if (moveToPtNode) {
+        Object.keys(moveToPtNode).forEach(key => {
+          const moveToNoPt = moveToPtNode[key]
+          const spX = moveToNoPt['attrs', 'x']
+          const spY = moveToNoPt['attrs', 'y']
+          const order = moveToNoPt['attrs', 'order']
+          multiSapeAry.push({
+            type: 'movto',
+            x: spX,
+            y: spY,
+            order,
+          })
+        })
+      }
+    })
+    if (lnToNodes) {
+      Object.keys(lnToNodes).forEach(key => {
+        const lnToPtNode = lnToNodes[key]['a:pt']
+        if (lnToPtNode) {
+          Object.keys(lnToPtNode).forEach(key => {
+            const lnToNoPt = lnToPtNode[key]
+            const ptX = lnToNoPt['attrs', 'x']
+            const ptY = lnToNoPt['attrs', 'y']
+            const order = lnToNoPt['attrs', 'order']
+            multiSapeAry.push({
+              type: 'lnto',
+              x: ptX,
+              y: ptY,
+              order,
+            })
+          })
+        }
+      })
+    }
+    if (cubicBezToNodes) {
+      const cubicBezToPtNodesAry = []
+      if (!Array.isArray(cubicBezToNodes)) {
+        cubicBezToNodes = [cubicBezToNodes]
+      }
+      Object.keys(cubicBezToNodes).forEach(key => {
+        cubicBezToPtNodesAry.push(cubicBezToNodes[key]['a:pt'])
+      })
+
+      cubicBezToPtNodesAry.forEach(key => {
+        const pts_ary = []
+        key.forEach(pt => {
+          const pt_obj = {
+            x: pt['attrs']['x'],
+            y: pt['attrs']['y'],
+          }
+          pts_ary.push(pt_obj)
+        })
+        const order = key[0]['attrs']['order']
+        multiSapeAry.push({
+          type: 'cubicBezTo',
+          cubBzPt: pts_ary,
+          order,
+        })
+      })
+    }
+    if (arcToNodes) {
+      const arcToNodesAttrs = arcToNodes['attrs']
+      const order = arcToNodesAttrs['order']
+      const hR = arcToNodesAttrs['hR']
+      const wR = arcToNodesAttrs['wR']
+      const stAng = arcToNodesAttrs['stAng']
+      const swAng = arcToNodesAttrs['swAng']
+      let shftX = 0
+      let shftY = 0
+      const arcToPtNode = getTextByPathList(arcToNodes, ['a:pt', 'attrs'])
+      if (arcToPtNode) {
+        shftX = arcToPtNode['x']
+        shftY = arcToPtNode['y']
+      }
+      multiSapeAry.push({
+        type: 'arcTo',
+        hR: hR,
+        wR: wR,
+        stAng: stAng,
+        swAng: swAng,
+        shftX: shftX,
+        shftY: shftY,
+        order,
+      })
+    }
+    if (closeNode) {
+      if (!Array.isArray(closeNode)) closeNode = [closeNode]
+      Object.keys(closeNode).forEach(() => {
+        multiSapeAry.push({
+          type: 'close',
+          order: Infinity,
+        })
+      })
+    }
+
+    multiSapeAry.sort((a, b) => a.order - b.order)
+
+    let k = 0
+    while (k < multiSapeAry.length) {
+      if (multiSapeAry[k].type === 'movto') {
+        const spX = parseInt(multiSapeAry[k].x) * cX
+        const spY = parseInt(multiSapeAry[k].y) * cY
+        d += ' M' + spX + ',' + spY
+      } 
+      else if (multiSapeAry[k].type === 'lnto') {
+        const Lx = parseInt(multiSapeAry[k].x) * cX
+        const Ly = parseInt(multiSapeAry[k].y) * cY
+        d += ' L' + Lx + ',' + Ly
+      } 
+      else if (multiSapeAry[k].type === 'cubicBezTo') {
+        const Cx1 = parseInt(multiSapeAry[k].cubBzPt[0].x) * cX
+        const Cy1 = parseInt(multiSapeAry[k].cubBzPt[0].y) * cY
+        const Cx2 = parseInt(multiSapeAry[k].cubBzPt[1].x) * cX
+        const Cy2 = parseInt(multiSapeAry[k].cubBzPt[1].y) * cY
+        const Cx3 = parseInt(multiSapeAry[k].cubBzPt[2].x) * cX
+        const Cy3 = parseInt(multiSapeAry[k].cubBzPt[2].y) * cY
+        d += ' C' + Cx1 + ',' + Cy1 + ' ' + Cx2 + ',' + Cy2 + ' ' + Cx3 + ',' + Cy3
+      } 
+      else if (multiSapeAry[k].type === 'arcTo') {
+        const hR = parseInt(multiSapeAry[k].hR) * cX
+        const wR = parseInt(multiSapeAry[k].wR) * cY
+        const stAng = parseInt(multiSapeAry[k].stAng) / 60000
+        const swAng = parseInt(multiSapeAry[k].swAng) / 60000
+        const endAng = stAng + swAng
+        d += shapeArc(wR, hR, wR, hR, stAng, endAng, false)
+      }
+      else if (multiSapeAry[k].type === 'close') d += 'z'
+      k++
+    }
+  }
+
+  return d
+}

+ 199 - 0
src/utils/pptxToJson/table.js

@@ -0,0 +1,199 @@
+import { getShapeFill, getSolidFill } from './fill'
+import { getTextByPathList } from './utils'
+import { getBorder } from './border'
+
+export function getTableBorders(node, warpObj) {
+  const borders = {}
+  if (node['a:bottom']) {
+    const obj = {
+      'p:spPr': {
+        'a:ln': node['a:bottom']['a:ln']
+      }
+    }
+    const border = getBorder(obj, undefined, warpObj)
+    borders.bottom = border
+  }
+  if (node['a:top']) {
+    const obj = {
+      'p:spPr': {
+        'a:ln': node['a:top']['a:ln']
+      }
+    }
+    const border = getBorder(obj, undefined, warpObj)
+    borders.top = border
+  }
+  if (node['a:right']) {
+    const obj = {
+      'p:spPr': {
+        'a:ln': node['a:right']['a:ln']
+      }
+    }
+    const border = getBorder(obj, undefined, warpObj)
+    borders.right = border
+  }
+  if (node['a:left']) {
+    const obj = {
+      'p:spPr': {
+        'a:ln': node['a:left']['a:ln']
+      }
+    }
+    const border = getBorder(obj, undefined, warpObj)
+    borders.left = border
+  }
+  return borders
+}
+
+export async function getTableCellParams(tcNode, thisTblStyle, cellSource, warpObj) {
+  const rowSpan = getTextByPathList(tcNode, ['attrs', 'rowSpan'])
+  const colSpan = getTextByPathList(tcNode, ['attrs', 'gridSpan'])
+  const vMerge = getTextByPathList(tcNode, ['attrs', 'vMerge'])
+  const hMerge = getTextByPathList(tcNode, ['attrs', 'hMerge'])
+  let fillColor
+  let fontColor
+  let fontBold
+
+  const getCelFill = getTextByPathList(tcNode, ['a:tcPr'])
+  if (getCelFill) {
+    const cellObj = { 'p:spPr': getCelFill }
+    const fill = await getShapeFill(cellObj, undefined, false, warpObj, 'slide')
+
+    if (fill && fill.type === 'color' && fill.value) {
+      fillColor = fill.value 
+    }
+  }
+  if (!fillColor) {
+    let bgFillschemeClr
+    if (cellSource) bgFillschemeClr = getTextByPathList(thisTblStyle, [cellSource, 'a:tcStyle', 'a:fill', 'a:solidFill'])
+    if (bgFillschemeClr) {
+      fillColor = getSolidFill(bgFillschemeClr, undefined, undefined, warpObj)
+    }
+  }
+
+  let rowTxtStyl
+  if (cellSource) rowTxtStyl = getTextByPathList(thisTblStyle, [cellSource, 'a:tcTxStyle'])
+  if (rowTxtStyl) {
+    fontColor = getSolidFill(rowTxtStyl, undefined, undefined, warpObj)
+    if (getTextByPathList(rowTxtStyl, ['attrs', 'b']) === 'on') fontBold = true
+  }
+
+  let lin_bottm = getTextByPathList(tcNode, ['a:tcPr', 'a:lnB'])
+  if (!lin_bottm) {
+    if (cellSource) lin_bottm = getTextByPathList(thisTblStyle[cellSource], ['a:tcStyle', 'a:tcBdr', 'a:bottom', 'a:ln'])
+    if (!lin_bottm) lin_bottm = getTextByPathList(thisTblStyle, ['a:wholeTbl', 'a:tcStyle', 'a:tcBdr', 'a:bottom', 'a:ln'])
+  }
+  let lin_top = getTextByPathList(tcNode, ['a:tcPr', 'a:lnT'])
+  if (!lin_top) {
+    if (cellSource) lin_top = getTextByPathList(thisTblStyle[cellSource], ['a:tcStyle', 'a:tcBdr', 'a:top', 'a:ln'])
+    if (!lin_top) lin_top = getTextByPathList(thisTblStyle, ['a:wholeTbl', 'a:tcStyle', 'a:tcBdr', 'a:top', 'a:ln'])
+  }
+  let lin_left = getTextByPathList(tcNode, ['a:tcPr', 'a:lnL'])
+  if (!lin_left) {
+    if (cellSource) lin_left = getTextByPathList(thisTblStyle[cellSource], ['a:tcStyle', 'a:tcBdr', 'a:left', 'a:ln'])
+    if (!lin_left) lin_left = getTextByPathList(thisTblStyle, ['a:wholeTbl', 'a:tcStyle', 'a:tcBdr', 'a:left', 'a:ln'])
+  }
+  let lin_right = getTextByPathList(tcNode, ['a:tcPr', 'a:lnR'])
+  if (!lin_right) {
+    if (cellSource) lin_right = getTextByPathList(thisTblStyle[cellSource], ['a:tcStyle', 'a:tcBdr', 'a:right', 'a:ln'])
+    if (!lin_right) lin_right = getTextByPathList(thisTblStyle, ['a:wholeTbl', 'a:tcStyle', 'a:tcBdr', 'a:right', 'a:ln'])
+  }
+
+  const borders = {}
+  if (lin_bottm) borders.bottom = getBorder(lin_bottm, undefined, warpObj)
+  if (lin_top) borders.top = getBorder(lin_top, undefined, warpObj)
+  if (lin_left) borders.left = getBorder(lin_left, undefined, warpObj)
+  if (lin_right) borders.right = getBorder(lin_right, undefined, warpObj)
+
+  return {
+    fillColor,
+    fontColor,
+    fontBold,
+    borders,
+    rowSpan: rowSpan ? +rowSpan : undefined,
+    colSpan: colSpan ? +colSpan : undefined,
+    vMerge: vMerge ? +vMerge : undefined,
+    hMerge: hMerge ? +hMerge : undefined,
+  }
+}
+
+export function getTableRowParams(trNodes, i, tblStylAttrObj, thisTblStyle, warpObj) {
+  let fillColor
+  let fontColor
+  let fontBold
+
+  if (thisTblStyle && thisTblStyle['a:wholeTbl']) {
+    const bgFillschemeClr = getTextByPathList(thisTblStyle, ['a:wholeTbl', 'a:tcStyle', 'a:fill', 'a:solidFill'])
+    if (bgFillschemeClr) {
+      const local_fillColor = getSolidFill(bgFillschemeClr, undefined, undefined, warpObj)
+      if (local_fillColor) fillColor = local_fillColor
+    }
+    const rowTxtStyl = getTextByPathList(thisTblStyle, ['a:wholeTbl', 'a:tcTxStyle'])
+    if (rowTxtStyl) {
+      const local_fontColor = getSolidFill(rowTxtStyl, undefined, undefined, warpObj)
+      if (local_fontColor) fontColor = local_fontColor
+      if (getTextByPathList(rowTxtStyl, ['attrs', 'b']) === 'on') fontBold = true
+    }
+  }
+  if (i === 0 && tblStylAttrObj['isFrstRowAttr'] === 1 && thisTblStyle) {
+    const bgFillschemeClr = getTextByPathList(thisTblStyle, ['a:firstRow', 'a:tcStyle', 'a:fill', 'a:solidFill'])
+    if (bgFillschemeClr) {
+      const local_fillColor = getSolidFill(bgFillschemeClr, undefined, undefined, warpObj)
+      if (local_fillColor) fillColor = local_fillColor
+    }
+    const rowTxtStyl = getTextByPathList(thisTblStyle, ['a:firstRow', 'a:tcTxStyle'])
+    if (rowTxtStyl) {
+      const local_fontColor = getSolidFill(rowTxtStyl, undefined, undefined, warpObj)
+      if (local_fontColor) fontColor = local_fontColor
+      if (getTextByPathList(rowTxtStyl, ['attrs', 'b']) === 'on') fontBold = true
+    }
+  }
+  else if (i > 0 && tblStylAttrObj['isBandRowAttr'] === 1 && thisTblStyle) {
+    fillColor = ''
+    if ((i % 2) === 0 && thisTblStyle['a:band2H']) {
+      const bgFillschemeClr = getTextByPathList(thisTblStyle, ['a:band2H', 'a:tcStyle', 'a:fill', 'a:solidFill'])
+      if (bgFillschemeClr) {
+        const local_fillColor = getSolidFill(bgFillschemeClr, undefined, undefined, warpObj)
+        if (local_fillColor) fillColor = local_fillColor
+      }
+      const rowTxtStyl = getTextByPathList(thisTblStyle, ['a:band2H', 'a:tcTxStyle'])
+      if (rowTxtStyl) {
+        const local_fontColor = getSolidFill(rowTxtStyl, undefined, undefined, warpObj)
+        if (local_fontColor) fontColor = local_fontColor
+      }
+      if (getTextByPathList(rowTxtStyl, ['attrs', 'b']) === 'on') fontBold = true
+    }
+    if ((i % 2) !== 0 && thisTblStyle['a:band1H']) {
+      const bgFillschemeClr = getTextByPathList(thisTblStyle, ['a:band1H', 'a:tcStyle', 'a:fill', 'a:solidFill'])
+      if (bgFillschemeClr) {
+        const local_fillColor = getSolidFill(bgFillschemeClr, undefined, undefined, warpObj)
+        if (local_fillColor) fillColor = local_fillColor
+      }
+      const rowTxtStyl = getTextByPathList(thisTblStyle, ['a:band1H', 'a:tcTxStyle'])
+      if (rowTxtStyl) {
+        const local_fontColor = getSolidFill(rowTxtStyl, undefined, undefined, warpObj)
+        if (local_fontColor) fontColor = local_fontColor
+        if (getTextByPathList(rowTxtStyl, ['attrs', 'b']) === 'on') fontBold = true
+      }
+    }
+  }
+  if (i === (trNodes.length - 1) && tblStylAttrObj['isLstRowAttr'] === 1 && thisTblStyle) {
+    const bgFillschemeClr = getTextByPathList(thisTblStyle, ['a:lastRow', 'a:tcStyle', 'a:fill', 'a:solidFill'])
+    if (bgFillschemeClr) {
+      const local_fillColor = getSolidFill(bgFillschemeClr, undefined, undefined, warpObj)
+      if (local_fillColor) {
+        fillColor = local_fillColor
+      }
+    }
+    const rowTxtStyl = getTextByPathList(thisTblStyle, ['a:lastRow', 'a:tcTxStyle'])
+    if (rowTxtStyl) {
+      const local_fontColor = getSolidFill(rowTxtStyl, undefined, undefined, warpObj)
+      if (local_fontColor) fontColor = local_fontColor
+      if (getTextByPathList(rowTxtStyl, ['attrs', 'b']) === 'on') fontBold = true
+    }
+  }
+
+  return {
+    fillColor,
+    fontColor,
+    fontBold,
+  }
+}

+ 286 - 0
src/utils/pptxToJson/text.js

@@ -0,0 +1,286 @@
+import { getHorizontalAlign } from './align'
+import { getTextByPathList } from './utils'
+
+import {
+  getFontType,
+  getFontColor,
+  getFontSize,
+  getFontBold,
+  getFontItalic,
+  getFontDecoration,
+  getFontDecorationLine,
+  getFontSpace,
+  getFontSubscript,
+  getFontShadow,
+} from './fontStyle'
+
+export function genTextBody(
+  textBodyNode,
+  spNode,
+  slideLayoutSpNode,
+  type,
+  warpObj
+) {
+
+  if (!textBodyNode) return ''
+
+  let text = ''
+  const listCounters = {} // 添加列表计数器对象
+
+  const pFontStyle = getTextByPathList(spNode, ['p:style', 'a:fontRef'])
+  const pNode = textBodyNode['a:p']
+  const pNodes = pNode.constructor === Array ? pNode : [pNode]
+  let isList = ''
+
+  for (const pNode of pNodes) {
+    let rNode = pNode['a:r']
+    let fldNode = pNode['a:fld']
+    let brNode = pNode['a:br']
+    if (rNode) {
+      rNode = rNode.constructor === Array ? rNode : [rNode]
+
+      if (fldNode) {
+        fldNode = fldNode.constructor === Array ? fldNode : [fldNode]
+        rNode = rNode.concat(fldNode)
+      }
+      if (brNode) {
+        brNode = brNode.constructor === Array ? brNode : [brNode]
+        brNode.forEach((item) => (item.type = 'br'))
+
+        if (brNode.length > 1) brNode.shift()
+        rNode = rNode.concat(brNode)
+        rNode.sort((a, b) => {
+          if (!a.attrs || !b.attrs) return true
+          return a.attrs.order - b.attrs.order
+        })
+      }
+    }
+
+    const align = getHorizontalAlign(pNode, spNode, type, warpObj)
+
+    const listType = getListType(pNode)
+    if (listType) {
+      const [tagName, className] = listType.split(' ')
+      const level = parseInt(pNode['a:pPr'].attrs?.lvl || '0') // 获取列表层级
+      const indentSize = (level + 1) * 20 // 根据层级计算缩进大小
+      
+      if (!isList) {
+        text += `<${tagName} style="list-style: none; padding-left: ${indentSize}px;" class="${className}">`
+        isList = listType
+        listCounters[className] = 0
+      }
+      else if (isList && isList !== listType) {
+        text += `</${isList.split(' ')[0]}>`
+        text += `<${tagName} style="list-style: none; padding-left: ${indentSize}px;" class="${className}">`
+        isList = listType
+        listCounters[className] = 0
+      }
+
+      listCounters[className]++
+      const currentNumber = listCounters[className]
+      
+      text += `<li style="text-align: ${align};">`
+      
+      if (className === 'custom-bullet') {
+        const bulletChar = pNode['a:pPr']['a:buChar'].attrs?.char || '•'
+        const symbolMap = {
+          'n': '■', // 实心方块
+          'l': '●', // 实心圆
+          'u': '◆', // 实心菱形
+          'p': '□', // 空心方块
+          'ü': '✔', // 对号
+          'Ø': '➢', // 箭头
+          '': '•', // 默认圆点
+        }
+        const displayChar = symbolMap[bulletChar] || bulletChar
+        text += `<span style="display: inline-block; width: 20px; margin-left: ${indentSize}px;">${displayChar}</span>`
+      }
+      else {
+        let displayNumber = ''
+        
+        switch (className) {
+          case 'circle-number':
+            const circleNums = ['①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩']
+            displayNumber = circleNums[currentNumber - 1] || `${currentNumber}`
+            break
+          case 'roman-upper':
+            const romanNums = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X']
+            displayNumber = `${romanNums[currentNumber - 1]}.`
+            break
+          case 'alpha-upper':
+            displayNumber = `${String.fromCharCode(64 + currentNumber)}.`
+            break
+          case 'alpha-lower-paren':
+            displayNumber = `${String.fromCharCode(96 + currentNumber)})`
+            break
+          case 'alpha-lower':
+            displayNumber = `${String.fromCharCode(96 + currentNumber)}.`
+            break
+          case 'chinese-upper': // 添加中文数字
+            const chineseUpperNums = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
+            displayNumber = `${chineseUpperNums[currentNumber - 1]}.`
+            break
+          default:
+            displayNumber = `${currentNumber}.`
+        }
+      
+        text += `<span style="display: inline-block; margin-left: ${indentSize}px;">${displayNumber}</span>`
+      }
+    }
+    else {
+      if (isList) {
+        text += `</${isList.split(' ')[0]}>`
+        isList = ''
+      }
+      text += `<p style="text-align: ${align};">`
+    }
+
+    if (!rNode) {
+      text += genSpanElement(
+        pNode,
+        spNode,
+        textBodyNode,
+        pFontStyle,
+        slideLayoutSpNode,
+        type,
+        warpObj
+      )
+    }
+    else {
+      for (const rNodeItem of rNode) {
+        text += genSpanElement(
+          rNodeItem,
+          pNode,
+          textBodyNode,
+          pFontStyle,
+          slideLayoutSpNode,
+          type,
+          warpObj
+        )
+      }
+    }
+
+    if (listType) text += '</li>'
+    else text += '</p>'
+  }
+  return text
+}
+
+export function getListType(node) {
+  const pPrNode = node['a:pPr']
+  if (!pPrNode) return ''
+    
+  if (pPrNode['a:buChar']) {
+    return 'ul custom-bullet'
+  }
+  if (pPrNode['a:buAutoNum']) {
+    const numType = pPrNode['a:buAutoNum'].attrs?.type || 'arabicPeriod'
+    // 根据不同的编号类型返回对应的样式
+    switch (numType) {
+      case 'circleNumDbPlain':
+        return 'ol circle-number' // ①②③
+      case 'romanUcPeriod':
+        return 'ol roman-upper' // I. II. III.
+      case 'alphaUcPeriod':
+        return 'ol alpha-upper' // A. B. C.
+      case 'alphaLcParen':
+        return 'ol alpha-lower-paren' // a) b) c)
+      case 'alphaLcPeriod':
+        return 'ol alpha-lower' // a. b. c.
+      case 'ea1JpnChsDbPeriod':
+        return 'ol chinese-upper' // 一. 二. 三.
+      default:
+        return 'ol decimal' // 1. 2. 3.
+    }
+  }
+  return ''
+}
+
+
+
+
+export function genSpanElement(
+  node,
+  pNode,
+  textBodyNode,
+  pFontStyle,
+  slideLayoutSpNode,
+  type,
+  warpObj
+) {
+  const lstStyle = textBodyNode['a:lstStyle']
+  const slideMasterTextStyles = warpObj['slideMasterTextStyles']
+
+  let lvl = 1
+  const pPrNode = pNode['a:pPr']
+  if (pPrNode) {
+    const lvlNode = getTextByPathList(pPrNode, ['attrs', 'lvl'])
+    if (lvlNode !== undefined) lvl = parseInt(lvlNode) + 1
+  }
+
+
+  let fontSize
+  const directSize = getTextByPathList(node, ['a:rPr', 'attrs', 'sz'])
+  
+  if (directSize) {
+    // 如果节点本身有字体大小设置,直接使用
+    fontSize = getFontSize(node, slideLayoutSpNode, type, slideMasterTextStyles)
+  }
+  else {
+    // 如果节点没有字体大小设置,使用 textBodyNode
+    fontSize = getFontSize(textBodyNode, slideLayoutSpNode, type, slideMasterTextStyles)
+  }
+
+
+
+  let text = node['a:t']
+  if (typeof text !== 'string') text = getTextByPathList(node, ['a:fld', 'a:t'])
+  if (typeof text !== 'string') text = '&nbsp;'
+
+  let styleText = ''
+  const fontColor = getFontColor(
+    node,
+    pNode,
+    lstStyle,
+    pFontStyle,
+    lvl,
+    warpObj
+  )
+  const fontType = getFontType(node, type, warpObj)
+  const fontBold = getFontBold(node)
+  const fontItalic = getFontItalic(node)
+  const fontDecoration = getFontDecoration(node)
+  const fontDecorationLine = getFontDecorationLine(node)
+  const fontSpace = getFontSpace(node)
+  const shadow = getFontShadow(node, warpObj)
+  const subscript = getFontSubscript(node)
+
+  if (fontColor) styleText += `color: ${fontColor};`
+  if (fontSize) styleText += `font-size: ${fontSize};`
+  if (fontType) styleText += `font-family: ${fontType};`
+  if (fontBold) styleText += `font-weight: ${fontBold};`
+  if (fontItalic) styleText += `font-style: ${fontItalic};`
+  if (fontDecoration) styleText += `text-decoration: ${fontDecoration};`
+  if (fontDecorationLine) {
+    styleText += `text-decoration-line: ${fontDecorationLine};`
+  }
+  if (fontSpace) styleText += `letter-spacing: ${fontSpace};`
+  if (subscript) styleText += `vertical-align: ${subscript};`
+  if (shadow) styleText += `text-shadow: ${shadow};`
+
+  const linkID = getTextByPathList(node, [
+    'a:rPr',
+    'a:hlinkClick',
+    'attrs',
+    'r:id',
+  ])
+  if (linkID) {
+    const linkURL = warpObj['slideResObj'][linkID]['target']
+    return `<span style="${styleText}"><a href="${linkURL}" target="_blank">${text
+      .replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
+      .replace(/\s/g, '&nbsp;')}</a></span>`
+  }
+  return `<span style="${styleText}">${text
+    .replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
+    .replace(/\s/g, '&nbsp;')}</span>`
+}

+ 160 - 0
src/utils/pptxToJson/utils.js

@@ -0,0 +1,160 @@
+export function base64ArrayBuffer(arrayBuffer) {
+  const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
+  const bytes = new Uint8Array(arrayBuffer)
+  const byteLength = bytes.byteLength
+  const byteRemainder = byteLength % 3
+  const mainLength = byteLength - byteRemainder
+  
+  let base64 = ''
+  let a, b, c, d
+  let chunk
+
+  for (let i = 0; i < mainLength; i = i + 3) {
+    chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]
+    a = (chunk & 16515072) >> 18
+    b = (chunk & 258048) >> 12
+    c = (chunk & 4032) >> 6
+    d = chunk & 63
+    base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]
+  }
+
+  if (byteRemainder === 1) {
+    chunk = bytes[mainLength]
+    a = (chunk & 252) >> 2
+    b = (chunk & 3) << 4
+    base64 += encodings[a] + encodings[b] + '=='
+  } 
+  else if (byteRemainder === 2) {
+    chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]
+    a = (chunk & 64512) >> 10
+    b = (chunk & 1008) >> 4
+    c = (chunk & 15) << 2
+    base64 += encodings[a] + encodings[b] + encodings[c] + '='
+  }
+
+  return base64
+}
+
+export function extractFileExtension(filename) {
+  return filename.substr((~-filename.lastIndexOf('.') >>> 0) + 2)
+}
+
+export function eachElement(node, func) {
+  if (!node) return node
+
+  let result = ''
+  if (node.constructor === Array) {
+    for (let i = 0; i < node.length; i++) {
+      result += func(node[i], i)
+    }
+  } 
+  else result += func(node, 0)
+
+  return result
+}
+
+export function getTextByPathList(node, path) {
+  if (!node) return node
+
+  for (const key of path) {
+    node = node[key]
+    if (!node) return node
+  }
+
+  return node
+}
+
+export function angleToDegrees(angle) {
+  if (!angle) return 0
+  return Math.round(angle / 60000)
+}
+
+export function escapeHtml(text) {
+  const map = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#039;',
+  }
+  return text.replace(/[&<>"']/g, m => map[m])
+}
+
+export function getMimeType(imgFileExt) {
+  let mimeType = ''
+  switch (imgFileExt.toLowerCase()) {
+    case 'jpg':
+    case 'jpeg':
+      mimeType = 'image/jpeg'
+      break
+    case 'png':
+      mimeType = 'image/png'
+      break
+    case 'gif':
+      mimeType = 'image/gif'
+      break
+    case 'emf':
+      mimeType = 'image/x-emf'
+      break
+    case 'wmf':
+      mimeType = 'image/x-wmf'
+      break
+    case 'svg':
+      mimeType = 'image/svg+xml'
+      break
+    case 'mp4':
+      mimeType = 'video/mp4'
+      break
+    case 'webm':
+      mimeType = 'video/webm'
+      break
+    case 'ogg':
+      mimeType = 'video/ogg'
+      break
+    case 'avi':
+      mimeType = 'video/avi'
+      break
+    case 'mpg':
+      mimeType = 'video/mpg'
+      break
+    case 'wmv':
+      mimeType = 'video/wmv'
+      break
+    case 'mp3':
+      mimeType = 'audio/mpeg'
+      break
+    case 'wav':
+      mimeType = 'audio/wav'
+      break
+    case 'tif':
+      mimeType = 'image/tiff'
+      break
+    case 'tiff':
+      mimeType = 'image/tiff'
+      break
+    default:
+  }
+  return mimeType
+}
+
+export function isVideoLink(vdoFile) {
+  const urlRegex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
+  return urlRegex.test(vdoFile)
+}
+
+export function toHex(n) {
+  let hex = n.toString(16)
+  while (hex.length < 2) {
+    hex = '0' + hex
+  }
+  return hex
+}
+
+export function hasValidText(htmlString) {
+  if (!DOMParser) return true
+
+  const parser = new DOMParser()
+  const doc = parser.parseFromString(htmlString, 'text/html')
+  const text = doc.body.textContent || doc.body.innerText
+  return text.trim() !== ''
+}

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff