feat: 补全 styles 映射 — opacity/lineHeight/letterSpacing/textDecoration/display/visibility

TEXT 新增:
- textDecorationLine → underline/strike
- letterSpacing → charSpacing
- opacity → transparency
- lineHeight → lineSpacingMultiple
- display:none / visibility:hidden → 跳过

Shape 新增:
- opacity → transparency
- display:none / visibility:hidden → 跳过

PPTX 渲染端同步传递所有新属性
This commit is contained in:
李进
2026-07-24 16:44:19 +08:00
parent 0e60f7525b
commit dd916dcf9e
+41 -2
View File
@@ -524,6 +524,9 @@ function convert(input) {
// text 节点
if (n.type === 'TEXT' && n.src) {
// 跳过不可见节点
if (n.styles.display === 'none' || n.styles.visibility === 'hidden') { continue; }
const st = n.styles;
const fs = parseFloat(st.fontSize) || 14;
var pt = Math.round(fs);
@@ -533,6 +536,28 @@ function convert(input) {
if (parseInt(st.fontWeight) >= 700) opts.bold = true;
if (st.fontStyle === 'italic') opts.italic = true;
if (st.textAlign && st.textAlign !== 'start') opts.align = st.textAlign;
// 文字装饰
if (st.textDecorationLine) {
if (st.textDecorationLine.includes('underline')) opts.underline = true;
if (st.textDecorationLine.includes('line-through')) opts.strike = 'sngStrike';
}
// 字符间距
if (st.letterSpacing && st.letterSpacing !== 'normal') {
var ls = parseFloat(st.letterSpacing);
if (!isNaN(ls) && ls !== 0) opts.charSpacing = Math.round(ls * 100 / fs); // 转为百分比
}
// 透明度
if (st.opacity !== undefined && st.opacity !== '' && parseFloat(st.opacity) < 1) {
opts.transparency = Math.round((1 - parseFloat(st.opacity)) * 100);
}
// 行高
if (st.lineHeight && st.lineHeight !== 'normal') {
var lh = parseFloat(st.lineHeight);
if (!isNaN(lh) && lh > 0) {
var lhRatio = lh / fs;
if (lhRatio > 0.5 && lhRatio < 5) opts.lineSpacingMultiple = Math.round(lhRatio * 100) / 100;
}
}
// 宽度:顶层元素用 contentW,嵌套元素用 parentW,加最小宽度保障
var textW = n.parentW || r.w;
@@ -571,6 +596,9 @@ function convert(input) {
}
// ===== 提取填充色 =====
// 跳过不可见节点
if (n.styles.display === 'none' || n.styles.visibility === 'hidden') { continue; }
let fillColor = null;
const bg = n.styles.backgroundColor;
if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') {
@@ -585,6 +613,10 @@ function convert(input) {
}
if (fillColor) {
var shapeOpts = { x: opts.x, y: opts.y, w: opts.w, h: opts.h, fill: { color: fillColor } };
// 透明度
if (n.styles.opacity !== undefined && n.styles.opacity !== '' && parseFloat(n.styles.opacity) < 1) {
shapeOpts.transparency = Math.round((1 - parseFloat(n.styles.opacity)) * 100);
}
// 圆角
var brVal = n.styles.borderTopLeftRadius;
@@ -764,14 +796,20 @@ if (require.main === module) {
for (const obj of (slideData.objects || [])) {
const o = obj.options || {};
if (obj.type === 'text') {
slide.addText(obj.text || '', {
const textOpts = {
x: o.x, y: o.y, w: o.w, h: o.h,
fontSize: o.fontSize || 12,
fontFace: o.fontFace || 'Arial',
color: o.color || '000000',
bold: o.bold || false,
align: o.align || 'left'
});
};
if (o.underline) textOpts.underline = o.underline;
if (o.strike) textOpts.strike = o.strike;
if (o.charSpacing) textOpts.charSpacing = o.charSpacing;
if (o.transparency !== undefined) textOpts.transparency = o.transparency;
if (o.lineSpacingMultiple) textOpts.lineSpacingMultiple = o.lineSpacingMultiple;
slide.addText(obj.text || '', textOpts);
} else if (obj.type === 'shape') {
const st = { rect: 'rect', roundRect: 'roundRect', ellipse: 'ellipse' }[obj.shapeName] || 'rect';
const shapeOpts = {
@@ -782,6 +820,7 @@ if (require.main === module) {
else shapeOpts.line = { type: 'none' }; // 去掉 pptxgenjs 默认边框
if (o.shadow) shapeOpts.shadow = o.shadow;
if (o.rectRadius) shapeOpts.rectRadius = o.rectRadius;
if (o.transparency !== undefined) shapeOpts.transparency = o.transparency;
slide.addShape(pres.ShapeType[st] || pres.ShapeType.rect, shapeOpts);
} else if (obj.type === 'image') {
slide.addImage({