pptx文件转fabric.js画布模板json的实现原理

1,289 阅读4分钟

最近在 tojson.js 开源项目中支持了pptx 转json`, 该 json 满足 fabricjs 画布渲染的数据结构, 今天来探讨下实现原理

pptx-json 参考 pptx2json, 不过该仓库的逻辑和 `pptx2html 一样, 只是把 html 格式换成了 json 数据

调研阶段, 认为只是把 pptx2json 返回的数据格式转化成 fabricjs 支持的数据格式就能满足需求, 不过其中对文本元素的解析不能满足需求

为什么?

  1. 文本属性未使用 json, 其中包括文本内容、字体大小、字体类型、字重 最近在 tojson.js 开源项目中支持了 pptx 转 json, 该 json 满足 fabricjs 画布渲染的数据结构, 今天来探讨下实现原理

pptx-json 参考 pptx2json, 不过该仓库的逻辑和 pptx2html 一样, 只是把 html 格式换成了 json 数据

调研阶段, 认为只是把 pptx2json 返回的数据格式转化成 fabricjs 支持的数据格式就能满足需求, 不过其中对文本元素的解析不能满足需求

为什么?

  1. 文本属性未使用 json, 其中包括文本内容、字体大小、字体类型、字重 wecom-temp-c79e5ea2e30a791fa7d349298b164cfa.png
  2. 该库使用的是 p 标签代表换行,如何在 fabricjs 中代表换行?

wecom-temp-955c1904cafb799be049259680e1737c.png wecom-temp-a51c0b6851f66257e0d757babfcee4b0.png 3. 在有序(无序)列表中一行文案被拆分成多个 span 标签表示 wecom-temp-a0c56c5689a1106cc51de43f0c54ff41.png wecom-temp-f75e50c22944966f420e7f07f1dc640b.png 4. 对齐元素返回字段错误 wecom-temp-1746648bc726d456354261abbeae276b.png

wecom-temp-ef5a2ea450fffea220667a2a71949733.png

所以只能魔改源码的形式来满足自身需求

原理

解析.pptx文件, 其实和sketch文件一样, 都是使用jsZip对文件进行解压后会返回json, 然后对json做处理

企业微信截图_204dbaae-95e5-477d-9e51-7360692ecd1d.png

魔改

文本 json 改造

原代码

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}; font-size: smaller;`;
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(/\s/i, "&nbsp;")}</a></span>`;
}
return `<span style="${styleText}">${text.replace(/\s/i, "&nbsp;")}</span>`;

改造后

const styleText = {};
if (fontColor) styleText.fontColor = fontColor;
if (fontSize) styleText.fontSize = fontSize;
if (fontType) styleText.fontType = fontType;
if (fontBold) styleText.fontBold = fontBold;
if (fontItalic) styleText.fontItalic = fontItalic;
if (fontDecoration) styleText.fontDecoration = fontDecoration;
if (fontDecorationLine) styleText.fontDecorationLine = fontDecorationLine;
if (fontSpace) styleText.fontSpace = fontSpace;
if (subscript) styleText.subscript = subscript;
if (shadow) styleText["text-shadow"] = shadow;
styleText.text = text;
return styleText;

通过创建对象的形式返还需要的 json

文本内容合并

dom 转换为 json 后发现, 本来一行元素但是使用了数组来表示, 这样转换为 fabricjs 支持的 json 格式也是转换为两个文本对象吗 Alt text转存失败,建议直接上传图片文件

实验了下, 不行, why?

如果要转换为两个文本元素, 至少要该元素的位置, 包括 left, top, 但是只有一个, 不能同时代表两个位置, 这样的话位置会重合, 不能做到无缝衔接

所以, 只能通过合并文本的形式转换为一个数组

阅读源代码, 发现只有 rNode 是数组才会有以上问题, 同时测试后发现, 除了文本内容不同外, 其他属性完全相同(注: 如果某个文案下颜色、字重不同, 可能会造成属性不同, 需要感觉 fabricjs 字段进行兼容)

if (!rNode) text += genSpanElement(pNode, slideLayoutSpNode, type, warpObj, fontsizeFactor, slideFactor);
else {
  for (const rNodeItem of rNode) {
    text += genSpanElement(rNodeItem, slideLayoutSpNode, type, warpObj, fontsizeFactor, slideFactor);
  }
}

改造后

if (!rNode) styles.push(genSpanElement(pNode, slideLayoutSpNode, type, warpObj, fontsizeFactor, slideFactor, textBodySpPr));
else {
  let newStyles = [];
  for (const rNodeItem of rNode) {
    newStyles.push(genSpanElement(rNodeItem, slideLayoutSpNode, type, warpObj, fontsizeFactor, slideFactor, textBodySpPr));
  }
  let resultText = "";
  newStyles.forEach((item) => {
    resultText += item.text;
  });
  newStyles[0].text = resultText;
  styles.push(newStyles[0]);
}

文本列表合并

上文提过列表或者手动换行是通过 ul(ol)或 p 标签进行表示, 如何在 fabricjs 中表示呢?

wecom-temp-06e45d863abbba73c58580b364db8d68.png 通过以上文本元素转 json 后, 列表会返回以上数据格式, 如何区分 文本内容合并

 for (const pNode of pNodes) {
    let rNode = pNode['a:r']
    let fldNode = pNode['a:fld']
    let brNode = pNode['a:br']
    if (!rNode) text += genSpanElement(pNode, slideLayoutSpNode, type, warpObj, fontsizeFactor, slideFactor)
    else {
      for (const rNodeItem of rNode) {
        text += genSpanElement(rNodeItem, slideLayoutSpNode, type, warpObj, fontsizeFactor, slideFactor)
      }
    }

相信大家已经看出来了, 像二维数组

pNodesrNode 两个字段就能区分, 不过 fabricjs 如何换行

上文提过, 可以获取到每个 pNodes 下的位置, 但是测试下来不行, 还有一个原因就是列表本来是一个整体, 通过拆分文本的形式就会生成多个文本元素

我们知道, \n 代表换行, 在 fabricjs 中\n 也代表换行, 所以只需要把文本合并后对每段加上\n 就行

if (styles.length > 1) {
  const newText = styles.reduce((now, next) => {
    now += next.text + "\n";
    return now;
  }, "");
  styles[0].text = newText;
  return styles[0];
}

对齐字端修复

通过在 fabricjs 中测试 json 是否满足渲染要求发现, 有个文本元素未居中展示, 通过调试源码发现, 对齐使用的是 left

后来在其他库里寻求解决方案发现, pptx2html 对齐字端使用的是 alngalign, 替换后测试发现返回 center, 满足要求

通过以上步骤下来, 把 pptx2json 参考更改成我想要的

具体的数据转化看我往期文章

使用

import pptxtojson from "pptx-json";
const options = {
  uploadUrl: options.uploadUrl, // 图片上传的接口地址
  uploadCallback: options.uploadCallback, // 上传完成后的数据处理回调方法
};
const instance = new pptxtojson(options);
const result = instance.init(files);

总结

github 地址: github.com/haixin-fang…

预览地址: haixin-fang.github.io/tojson.js/p…