gbpptx
v0.2.0
Published
Office-style PowerPoint (pptx) editor: real PresentationML read/write, ribbon UI, slide sorter, presenter view.
Maintainers
Readme
gbpptx
Office 式 PowerPoint 编辑器:真实的 .pptx(PresentationML)读写 + 原生 HTML 界面。打开外部演示文稿、像 PowerPoint 一样查看和编辑、再导出 .pptx,整条链路都走本包自己的 OOXML 内核。
没有 React/Vue,没有 ProseMirror,没有第三方 PPT 库;整个包只依赖 gbooxml 做 ZIP 与 XML。渲染走 DOM + SVG,不使用 <canvas> 元素。
界面一览
标题栏:快速保存 / 撤销 / 重做 / 放映 · 文档名 · 保存状态 · 自动保存 · 设置
Ribbon:文件 | 开始 | 插入 | 设计 | 切换 | 幻灯片放映 | 审阅 | 视图
+ 形状格式 / 图片格式 / 表格(上下文标签)
左窗格:幻灯片缩略图 / 大纲
画布: 多选、框选、吸附参考线、组缩放、旋转、就地文字与表格编辑
右窗格:设置形状格式(大小与位置 / 填充与线条 / 文本 / 图片 / 表格)
底部: 幻灯片浏览 / 阅读视图 / 普通视图 · 备注 · 缩放滑块- 文件后台:主页、新建(7 种版式模板)、打开、信息、另存为、导出(PPTX / PDF)、打印、关闭。
- 开始:剪切/复制/粘贴、新建幻灯片、隐藏、重设、字体/字号/加粗/斜体/下划线/删除线/颜色、项目符号/编号/缩进/行距/对齐、形状库、填充/轮廓/阴影、排列/对齐/分布、查找/替换/全选。
- 插入:新建幻灯片、表格(Office 式 10×8 尺寸选择器)、图片、18 种形状、文本框、线条、箭头、备注。
- 设计:6 套主题预览、页面背景、幻灯片大小(16:9 / 4:3)。
- 切换:无、淡出、推进、擦除、覆盖、分割、缩放;持续时间、应用到全部、预览。
- 幻灯片放映:从头开始(F5)、从当前页(Shift+F5)、演讲者视图、循环放映、隐藏幻灯片。
- 审阅:备注窗格、字数统计。
- 视图:普通 / 幻灯片浏览 / 阅读视图、网格线、参考线、备注、格式窗格、适应窗口、缩放。
- 就地编辑:双击文本或形状改文字;选中文字后可对部分 run 设字体/字号/颜色/加粗等;双击表格单元格编辑,Tab/Enter 在单元格间移动。
- 放映:全屏、单击/方向键翻页、B 黑屏、W 白屏、Esc 退出;演讲者视图带计时器、下一页预览和备注。
- 文件工作流:拖入
.pptx直接打开;拖入 PNG/JPEG/GIF 直接插图;有本地文件句柄时自动保存;关闭页面前提示未保存更改。
安装与运行
npm install gbpptx
npm start # node web/serve.mjs,默认 http://localhost:5274/web/
npm test # 内核往返 + 外部 pptx 导入 + XML 合法性测试web/serve.mjs 是零依赖静态服务器。web/index.html 用 importmap 把 gbooxml 映射到 /vendor/gbooxml/index.js,服务器把该前缀指向同级目录的 gbooxml/src。这个 demo 依赖 monorepo 目录布局;从 npm 单独安装后要把 importmap 指到 node_modules/gbooxml/src/index.js。
页面依次加载 tokens.css、slides.css、office.css,自己装配页面时三份都要引入。
三层结构
| 层 | 入口 | 职责 |
| --- | --- | --- |
| 模型 | gbpptx/model | 快照结构、画幅、主题、形状/线条/表格/图片/文本工厂、版式、几何 |
| 文件 | gbpptx(根入口) | parsePptx 读、buildPptx 写、buildPptxVerified 自检写 |
| 界面 | web/ | SlidesApp:Ribbon、窗格、画布、格式窗格、后台视图、放映 |
根入口把模型层与文件层的名字一起导出:
import { createDraft, parsePptx, buildPptxVerified } from 'gbpptx';
import { createSlideWithLayout } from 'gbpptx/model';
import { fontSizeToSz, isPptx } from 'gbpptx/pptx';通配规则是 ./* → ./src/*.js,深层路径不带 .js。
快速开始
造一份演示文稿并写出 .pptx
import {
createDraft, createShapeObject, createTableObject, createTextObject,
buildPptxVerified,
} from 'gbpptx';
const pres = createDraft(); // 960×540,一页空白
pres.title = '季度回顾';
pres.theme = 'midnight';
const slide = pres.slides[0];
const title = createTextObject(80, 60, 1);
title.props.html = '<div>季度回顾</div>';
title.props.fontSize = 40;
slide.objects.push(title);
const shape = createShapeObject('star', 120, 220, 2);
shape.props.text = '重点';
slide.objects.push(shape);
const table = createTableObject(520, 120, 3, 3, 3);
table.props.rows = [['指标', 'Q1', 'Q2'], ['收入', '120', '150'], ['成本', '80', '92']];
table.props.tableStyle = 'banded';
table.props.bandedRows = true;
slide.objects.push(table);
const bytes = await buildPptxVerified(pres); // Promise<Uint8Array>buildPptxVerified 写完后会读回来比对画幅、页序、背景、备注、隐藏状态、切换、对象内容与列表结构;不一致会抛错并拒绝写出。只想强行生成用 buildPptx。
读一个外部 .pptx
import { parsePptx, normalizeSnapshot, isPptx } from 'gbpptx';
if (isPptx(bytes)) {
const raw = await parsePptx(bytes);
console.log(raw.size, raw.slides.length, raw.warnings);
const pres = normalizeSnapshot(raw);
}parsePptx 会:
- 按
sldSz保留长宽比缩放到 960/540 基准(4:3 得到 720×540,自定义画幅等比缩放); - 从版式/母版继承占位符几何(外部文件标题/正文没有
xfrm也能落位); - 解析主题
clrScheme,把schemeClr、tx1/bg1/accentN、lumMod/lumOff/tint/shade落成具体颜色; - 把组合形状
grpSp按组变换摊平成页内元素; - 还原真正的项目符号/编号(
buChar/buAutoNum→<ul>/<ol>); - 读取表格样式、图片裁剪、双端箭头、虚线、切换、隐藏页、备注;
- 跳过图表/SmartArt/音视频/未知元素,并在
warnings里计数。
对象模型(v2)
{
version: 2,
title: '',
theme: 'clean',
size: { w: 960, h: 540 }, // 逻辑像素,保留源画幅
slides: [{
id: 's-1',
bg: '', // '' = 跟随主题
notes: '',
hidden: false,
transition: { type: 'fade', dir: 'l', speed: 'med' },
objects: [{
id: 'o-1',
type: 'text', // text | image | shape | line | table
x: 120, y: 100, w: 360, h: 110, rot: 0, z: 1,
props: { /* 按类型 */ },
}],
}],
}z 升序叠放,rot 角度制;线条的 w/h 是从 (x,y) 出发的向量,可为负。
| type | props |
| --- | --- |
| text | html、font、fontSize、color、align、bold、italic、underline、strike、anchor、lineSpacing、fill、stroke、strokeW |
| image | src(data URL)、alt、crop:{l,t,r,b}(百分比)、shadow |
| shape | kind、fill、stroke、strokeW、dash、shadow、text、textColor、font、fontSize、bold、italic、underline、strike、align、anchor、lineSpacing |
| line | stroke、strokeW、dash、arrowStart、arrowEnd |
| table | rows、headerRow、bandedRows、tableStyle、font、fontSize、color、colW、rowH |
- 18 种形状:矩形、圆角矩形、椭圆、等腰/直角三角形、菱形、平行四边形、梯形、五/六/八边形、五角星、上下左右箭头、V 形、十字形。
- 4 种表格样式:无样式、标题行、镶边行、深色;样式颜色跟随主题。
- 7 种切换:无、淡出、推进、擦除、覆盖、分割、缩放。
- 主题:clean / midnight / ocean / sunset / forest / paper。
- 文本 HTML 支持
div/p/br/b/strong/i/em/u/s/span/font/ul/ol/li;列表会写成真正的 PowerPoint 项目符号/编号,而不是「• 」字面量。
坐标系与单位
逻辑画布默认 960×540 px,写出为 sldSz 的 EMU(96dpi)。导入 4:3 或自定义画幅时按长宽比换算到 960/540 基准,导出时按快照 size 原样写回。
EMU_PER_PX = 9525,pxToEmu/emuToPx互转。- 旋转:模型角度制,OOXML 1/60000 度。
- 字号:模型 px,OOXML 百分之一磅(
× 0.75 × 100)。 - 界面缩放只改 CSS
transform: scale(),模型坐标不变。
导出兼容性
- 母版/版式是空白版式,所有元素写在页内
spTree,导出即所见即所得。 - 表格样式落成显式单元格填充/边框;表头文字按主题强调色自动选黑白。
- 图片按导入时的原始格式内嵌(PNG/JPEG/GIF/SVG/BMP/TIFF/EMF/WMF/WebP),不抓远程 URL。
- 切换写成
<p:transition>;隐藏页写show="0"。 - 写出后自检会重新打开字节并比对;测试还会用严格 XML 解析器检查每个 XML 部件,避免 PowerPoint 的「需要修复」提示。
已知限制
- 对象动画(进入/强调/退出)尚未支持;只支持页切换。
- 图表、SmartArt、音视频、批注、超链接等元素在导入时会被跳过(
warnings会给出计数),导出不会凭空生成。 - 组合形状导入时摊平,导出后不再保留组合语义。
- 合并单元格、逐单元格边框/填充、单元格内富文本暂不支持;表格样式按整表近似。
- 外部主题的字体方案只取第一个字族;渐变填充按首站近似为纯色,背景图片不导入。
- EMF/WMF 等浏览器不能直接解码的图片会在编辑画布里显示为空白,但字节仍会原样导出。
- 纯 Node 写出含文本的对象需要 DOM:请挂 jsdom(测试里已这么做);形状/线/表格在裸 Node 可写。
- 自检摘要对几何取整到 0.1px,并忽略文本颜色、列宽/行高这类派生值;它是内容级比对,不是逐字节 diff。
- 老二进制
.ppt会被isPptx判假(只认 ZIP 魔数并显式排除 OLE 容器)。
开发
node web/serve.mjs # 默认 5274
node web/serve.mjs 8080 # 或 GBPPTX_PORT=8080
npm test控制台暴露 window.gbpptx,可直接调 await window.gbpptx.selfTest() 做一次前端全链路自检。
目录
src/kernel/model.js 快照/画幅/主题/形状/版式
src/kernel/read.js .pptx → 快照(占位符/主题色/组/切换)
src/kernel/write.js 快照 → .pptx
src/kernel/roundtrip.js 写出并读回自检
src/kernel/text.js HTML ⇄ 段落/run/列表
src/kernel/print.js A4 横向打印 / PDF 通道
web/app/slides-app.js 编辑器控制器
web/app/ui/ Ribbon/画布/窗格/后台/放映/图标/浮层
web/styles/ tokens / 幻灯片渲染 / Office 外壳
test/ 往返与外部导入测试许可
MIT
