mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2026-08-28 19:24:01 +08:00
perf: 升级pptxtojson,优化导入PPTX还原细节
This commit is contained in:
@@ -38,7 +38,7 @@
|
||||
|
||||
- **Low-code Platforms / H5 Editors / Image Editors / Whiteboards** (Recommendation: Not Recommended): We suggest choosing open-source projects that better match those scenarios.
|
||||
- **AI PPT Generation Tool** (Recommendation: ⭐⭐): This project supports a template-based AIPPT generation approach, and also provides a data structure definition for AI to directly generate page data. See `AI_PPT_SCHEMA.md`. In theory, it supports non-template PPT generation, where AI directly generates PPTist data. Note that the final result of AIPPT depends heavily on model capabilities, prompt design, asset generation/retrieval, and other factors. PPTist is better suited as a foundation for carrying, editing, and further processing structured generation results, rather than an out-of-the-box complete AIPPT commercial solution.
|
||||
- **PPT File Preview Tool** (Recommendation: ⭐⭐): This project already supports importing PPTX files and can cover many common page contents and styles (roughly 80%+ fidelity overall), making it suitable for online preview, lightweight review, and content browsing. However, because the PPTX ecosystem is complex, animations, special charts, deeply nested structures, non-standard elements, and some advanced styles may inevitably have fidelity differences. If your business requires pixel-perfect fidelity, evaluate with your actual sample files.
|
||||
- **PPT File Preview Tool** (Recommendation: ⭐⭐): This project supports importing PPTX files and can cover many common page contents and styles (roughly 85%+ fidelity overall), making it suitable for online preview, lightweight review, and content browsing. However, because the PPTX ecosystem is complex, animations, special charts, deeply nested structures, non-standard elements, and some advanced styles may inevitably have fidelity differences. If your business requires pixel-perfect fidelity, evaluate with your actual sample files.
|
||||
- **Office PPT Authoring Tool** (Recommendation: ⭐⭐): This project supports many common Office PPT features and can import/export local PPTX files, making it usable as a foundation for online authoring and editing. Note that full Office PPT compatibility involves many details, including complex layouts, animations, charts, themes, and masters. Some details will inevitably be lost during file import/export, so 100% fidelity cannot be guaranteed. If your core goal is to fully replace Microsoft PowerPoint, evaluate and make trade-offs with your actual business documents.
|
||||
- **Web Slide Editing/Presentation App** (Recommendation: ⭐⭐⭐⭐⭐): This is the most recommended scenario. The core strengths of PPTist are its editing capabilities and editing experience. You can build upon this project to customize or add unique nodes and features according to your needs, without using import/export as the ultimate goal. After all, those customized things do not exist in Office PPT. **Summary: I hope you use PPTist to build a presentation product different from Office PPT, rather than just an editing relay station for Office PPT.**
|
||||
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@
|
||||
|
||||
- **低代码平台/H5编辑器/图片编辑器/白板应用**(推荐度:完全不推荐):建议选择匹配度更高的开源项目。
|
||||
- **AIPPT生成工具**(推荐度:⭐⭐):本项目支持模板式 AIPPT 生成思路,也提供了面向 AI 直接生成页面数据的结构定义,参考 [AI_PPT_SCHEMA](/doc/AI_PPT_SCHEMA.md),理论上支持非模板式 PPT 生成(由 AI 直接生成 PPTist 数据)。需要注意的是,AIPPT 的最终效果很大程度取决于模型能力、提示词设计、素材生成/检索等。PPTist 更适合作为结构化生成结果的承载、编辑和二次加工底座,而不是开箱即用的完整 AIPPT 商业方案。
|
||||
- **PPT文件预览工具**(推荐度:⭐⭐):本项目已经支持导入PPTX文件,并能覆盖较多常见页面内容和样式(综合来看,大约有80%+的还原度),可以用于在线预览、轻量校对、内容浏览等场景。但由于PPTX生态本身较复杂,动画、特殊图表、多层嵌套、非标准元素、部分高级样式等仍不可避免的存在还原差异。如果你的业务对像素级还原要求极高,建议结合实际样例进行评估。
|
||||
- **PPT文件预览工具**(推荐度:⭐⭐):本项目支持导入PPTX文件,并能覆盖较多常见页面内容和样式(综合来看,可达到85%+的还原度),可以用于在线预览、轻量校对、内容浏览等场景。但由于PPTX生态本身较复杂,动画、特殊图表、多层嵌套、非标准元素、部分高级样式等仍不可避免的存在还原差异。如果你的业务对像素级还原要求极高,建议结合实际样例进行评估。
|
||||
- **Office PPT制作工具**(推荐度:⭐⭐):本项目支持大量Office PPT常用功能,也支持导入和导出本地PPTX文件,可作为在线制作、编辑的基础方案使用。需要注意的是,Office PPT的完整兼容涉及复杂排版、动画、图表、主题、母版等大量细节,文件导入导出时必然会丢失部分细节,无法做到100%还原。因此,如果你的核心目标是完全替代Office PowerPoint,建议结合实际业务文档进行测试和取舍。
|
||||
- **Web 幻灯片编辑/演示应用**(推荐度:⭐⭐⭐⭐⭐):这是最推荐的场景,编辑能力和编辑体验是PPTist最核心的优势,你可以在本项目的基础上,根据自己的需求,定制/新增一些个性化的节点/功能,不以导入导出为最终目的(毕竟这些定制化的东西在Office PPT中是不存在的)。**一句话总结:我更希望你基于PPTist打造一个不同于Office PPT的演示类产品,而不只是做一个Office PPT的编辑中转站。**
|
||||
|
||||
|
||||
Generated
+7
-7
@@ -24,7 +24,7 @@
|
||||
"number-precision": "^1.6.0",
|
||||
"pinia": "^3.0.2",
|
||||
"pptxgenjs": "^3.12.0",
|
||||
"pptxtojson": "^2.0.6",
|
||||
"pptxtojson": "^2.1.0",
|
||||
"prosemirror-commands": "^1.6.0",
|
||||
"prosemirror-dropcursor": "^1.8.1",
|
||||
"prosemirror-gapcursor": "^1.3.2",
|
||||
@@ -4568,9 +4568,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/pptxtojson": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/pptxtojson/-/pptxtojson-2.0.6.tgz",
|
||||
"integrity": "sha512-9NRaBJDqAO4yP5Fs9zNwmybuOeKA0TjIJ5ue9XlkI1B0UUWz3cssXlBySykSzaqFuDIb42GnPvu9rexVn2GObQ==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pptxtojson/-/pptxtojson-2.1.0.tgz",
|
||||
"integrity": "sha512-CGBe+QVAnxAwry1zQWUpcnBDk2VJTeHD3kogjsqCPIy3muV1J4YGrH/XnOLJvIvlT3jHym1HgXzyLZrWTBI8Eg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jszip": "^3.10.1",
|
||||
@@ -9173,9 +9173,9 @@
|
||||
}
|
||||
},
|
||||
"pptxtojson": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/pptxtojson/-/pptxtojson-2.0.6.tgz",
|
||||
"integrity": "sha512-9NRaBJDqAO4yP5Fs9zNwmybuOeKA0TjIJ5ue9XlkI1B0UUWz3cssXlBySykSzaqFuDIb42GnPvu9rexVn2GObQ==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pptxtojson/-/pptxtojson-2.1.0.tgz",
|
||||
"integrity": "sha512-CGBe+QVAnxAwry1zQWUpcnBDk2VJTeHD3kogjsqCPIy3muV1J4YGrH/XnOLJvIvlT3jHym1HgXzyLZrWTBI8Eg==",
|
||||
"requires": {
|
||||
"jszip": "^3.10.1",
|
||||
"tinycolor2": "1.6.0",
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@
|
||||
"number-precision": "^1.6.0",
|
||||
"pinia": "^3.0.2",
|
||||
"pptxgenjs": "^3.12.0",
|
||||
"pptxtojson": "^2.0.6",
|
||||
"pptxtojson": "^2.1.0",
|
||||
"prosemirror-commands": "^1.6.0",
|
||||
"prosemirror-dropcursor": "^1.8.1",
|
||||
"prosemirror-gapcursor": "^1.3.2",
|
||||
|
||||
+44
-14
@@ -21,6 +21,7 @@ import type {
|
||||
SlideBackground,
|
||||
PPTShapeElement,
|
||||
PPTLineElement,
|
||||
LinePoint,
|
||||
PPTImageElement,
|
||||
TextAlignVertical,
|
||||
PPTTextElement,
|
||||
@@ -442,11 +443,17 @@ export default () => {
|
||||
}
|
||||
}
|
||||
|
||||
const parseLineEnd = (lineEnd?: { type?: string }): LinePoint => {
|
||||
if (!lineEnd || !lineEnd.type || lineEnd.type === 'none') return ''
|
||||
if (['triangle', 'stealth', 'arrow'].includes(lineEnd.type)) return 'arrow'
|
||||
if (['diamond', 'oval'].includes(lineEnd.type)) return 'dot'
|
||||
return ''
|
||||
}
|
||||
|
||||
const parseLineElement = (el: Shape, ratio: number) => {
|
||||
let start: [number, number] = [0, 0]
|
||||
let end: [number, number] = [0, 0]
|
||||
let rotateOffset: [number, number] = [0, 0]
|
||||
|
||||
if (!el.isFlipV && !el.isFlipH) { // 右下
|
||||
start = [0, 0]
|
||||
end = [el.width, el.height]
|
||||
@@ -474,7 +481,7 @@ export default () => {
|
||||
end,
|
||||
style: el.borderType,
|
||||
color: el.borderColor,
|
||||
points: ['', /straightConnector/.test(el.shapType) ? 'arrow' : '']
|
||||
points: [parseLineEnd(el.headEnd), parseLineEnd(el.tailEnd)]
|
||||
}
|
||||
if (el.rotate) {
|
||||
const { start, end, offset } = rotateLine(data, el.rotate)
|
||||
@@ -500,8 +507,8 @@ export default () => {
|
||||
if (!('x' in point) || !('y' in point)) return null
|
||||
if (typeof point.x !== 'number' || typeof point.y !== 'number') return null
|
||||
|
||||
let x = point.x * ratio
|
||||
let y = point.y * ratio
|
||||
let x = el.pathViewBox?.width ? point.x / el.pathViewBox.width * el.width : point.x * ratio
|
||||
let y = el.pathViewBox?.height ? point.y / el.pathViewBox.height * el.height : point.y * ratio
|
||||
|
||||
if (el.isFlipH) x = el.width - x
|
||||
if (el.isFlipV) y = el.height - y
|
||||
@@ -1130,6 +1137,12 @@ export default () => {
|
||||
const fontsize = span?.style.fontSize ? (parseInt(span?.style.fontSize) * ratio).toFixed(1) + 'px' : ''
|
||||
const fontname = span?.style.fontFamily || ''
|
||||
const color = span?.style.color || cellData.fontColor
|
||||
const fontWeight = span?.style.fontWeight || ''
|
||||
const bold = fontWeight === 'bold' || +fontWeight >= 600 || cellData.fontBold
|
||||
const em = span?.style.fontStyle === 'italic'
|
||||
const textDecoration = span?.style.textDecoration || ''
|
||||
const underline = textDecoration.includes('underline')
|
||||
const strikethrough = textDecoration.includes('line-through')
|
||||
|
||||
rowCells.push({
|
||||
id: nanoid(10),
|
||||
@@ -1143,7 +1156,10 @@ export default () => {
|
||||
fontsize,
|
||||
fontname,
|
||||
color,
|
||||
bold: cellData.fontBold,
|
||||
bold,
|
||||
em,
|
||||
underline,
|
||||
strikethrough,
|
||||
backcolor: cellData.fillColor,
|
||||
},
|
||||
})
|
||||
@@ -1155,15 +1171,29 @@ export default () => {
|
||||
const allWidth = el.colWidths.reduce((a, b) => a + b, 0)
|
||||
const colWidths: number[] = el.colWidths.map(item => item / allWidth)
|
||||
|
||||
const firstCell = el.data[0][0]
|
||||
const border = firstCell.borders.top ||
|
||||
firstCell.borders.bottom ||
|
||||
el.borders.top ||
|
||||
el.borders.bottom ||
|
||||
firstCell.borders.left ||
|
||||
firstCell.borders.right ||
|
||||
el.borders.left ||
|
||||
el.borders.right
|
||||
const isVisibleBorder = (b?: { borderColor?: string; borderWidth?: number }) => {
|
||||
if (!b || !b.borderWidth) return false
|
||||
const c = b.borderColor || ''
|
||||
if (/^#[0-9a-fA-F]{8}$/.test(c) && c.slice(-2).toLowerCase() === '00') return false
|
||||
return true
|
||||
}
|
||||
const borderCounter = new Map<string, { border: any; count: number }>()
|
||||
const collectBorders = (borders?: Record<string, any>) => {
|
||||
if (!borders) return
|
||||
for (const side of ['top', 'bottom', 'left', 'right'] as const) {
|
||||
const b = borders[side]
|
||||
if (!isVisibleBorder(b)) continue
|
||||
const key = `${b.borderColor}|${b.borderWidth}|${b.borderType}`
|
||||
const hit = borderCounter.get(key)
|
||||
if (hit) hit.count++
|
||||
else borderCounter.set(key, { border: b, count: 1 })
|
||||
}
|
||||
}
|
||||
collectBorders(el.borders)
|
||||
for (const rowCells of el.data) {
|
||||
for (const cell of rowCells) collectBorders(cell.borders)
|
||||
}
|
||||
const border = [...borderCounter.values()].sort((a, b) => b.count - a.count)[0]?.border
|
||||
const borderWidth = border?.borderWidth || 0
|
||||
const borderStyle = border?.borderType || 'solid'
|
||||
const borderColor = border?.borderColor || '#eeece1'
|
||||
|
||||
Reference in New Issue
Block a user