diff --git a/reference/chart.md b/reference/chart.md index 5632afb..d16fa38 100644 --- a/reference/chart.md +++ b/reference/chart.md @@ -202,6 +202,9 @@ - **catAxisLabelFontSize**:轴标签字号 - 类型:`number` + - 单位:pt + - 取值范围:无特殊约束范围 + - 无默认值 - **catAxisLabelFrequency**:标签频率 - 类型:`string` @@ -211,6 +214,9 @@ - **catAxisLabelRotate**:标签旋转角度 - 类型:`number` + - 单位:度 + - 取值范围:无特殊约束范围 + - 无默认值 - **catAxisLineColor**:轴线颜色 - 类型:`string` @@ -220,6 +226,9 @@ - **catAxisLineSize**:轴线粗细 - 类型:`number` + - 单位:pt + - 取值范围:无特殊约束范围 + - 无默认值 - **catAxisLineStyle**:轴线样式 - 类型:`'solid' | 'dash' | 'dot'` @@ -232,6 +241,9 @@ - **catAxisMajorUnit**:主要刻度单位 - 类型:`number` + - 单位:无单位(数值刻度) + - 取值范围:无特殊约束范围 + - 无默认值 - **catAxisMaxVal**:轴最大值 - 类型:`number` @@ -244,6 +256,9 @@ - **catAxisMinorUnit**:次要刻度单位 - 类型:`number` + - 单位:无单位(数值刻度) + - 取值范围:无特殊约束范围 + - 无默认值 - **catAxisMinVal**:轴最小值 - 类型:`number` @@ -269,6 +284,9 @@ - **catAxisTitleRotate**:轴标题旋转 - 类型:`number` + - 单位:度 + - 取值范围:无特殊约束范围 + - 无默认值 - **catGridLine**:分类网格线 - 类型:`OptsChartGridLine` @@ -307,6 +325,9 @@ - **serAxisLabelFontSize**:系列轴标签字号 - 类型:`number` + - 单位:pt + - 取值范围:无特殊约束范围 + - 无默认值 - **serAxisLabelFrequency**:系列轴标签频率 - 类型:`string` @@ -399,6 +420,9 @@ - **valAxisLabelFontSize**:数值轴标签字号 - 类型:`number` + - 单位:pt + - 取值范围:无特殊约束范围 + - 无默认值 - **valAxisLabelFormatCode**:数值轴标签格式代码 - 类型:`string` @@ -408,6 +432,9 @@ - **valAxisLabelRotate**:数值轴标签旋转 - 类型:`number` + - 单位:度 + - 取值范围:无特殊约束范围 + - 无默认值 - **valAxisLineColor**:数值轴线颜色 - 类型:`string` @@ -417,6 +444,9 @@ - **valAxisLineSize**:数值轴线粗细 - 类型:`number` + - 单位:pt + - 取值范围:无特殊约束范围 + - 无默认值 - **valAxisLineStyle**:数值轴线样式 - 类型:`'solid' | 'dash' | 'dot'` @@ -431,15 +461,24 @@ - **valAxisMajorUnit**:数值轴主要刻度单位 - 类型:`number` + - 单位:无单位(数值刻度) + - 取值范围:无特殊约束范围 + - 无默认值 - **valAxisMaxVal**:数值轴最大值 - 类型:`number` + - 单位:无单位(数值与数据一致) + - 取值范围:无特殊约束范围 + - 无默认值 - **valAxisMinorTickMark**:数值轴副刻度标记 - 类型:`ChartAxisTickMark` - **valAxisMinVal**:数值轴最小值 - 类型:`number` + - 单位:无单位(数值与数据一致) + - 取值范围:无特殊约束范围 + - 无默认值 - **valAxisOrientation**:数值轴方向 - 类型:`'minMax'` @@ -473,13 +512,19 @@ - **bar3DShape**:3D 柱形形状 - 类型:`string` + - 取值范围:`'box' | 'cylinder' | 'cone' | 'pyramid'`(PowerPoint 标准值),类型声明为 string,也可接受其他值 + - 默认值:`'box'` - **barDir**:柱形方向 - 类型:`string` - 取值范围:`'bar'`(水平)、`'col'`(垂直) + - 默认值:`'col'` - **barGapDepthPct**:3D 柱形深度间距百分比 - 类型:`number` + - 取值范围:`0`–`500` + - 默认值:`150` + - 单位:% - **barGapWidthPct**:间距宽度百分比 - 类型:`number` @@ -488,6 +533,8 @@ - **barGrouping**:分组方式 - 类型:`string` + - 取值范围:`'clustered' | 'stacked' | 'standard'`(PowerPoint 标准值),类型声明为 string,也可接受其他值 + - 默认值:`'clustered'` - **barOverlapPct**:系列重叠百分比 - 类型:`number` @@ -501,9 +548,12 @@ - **dataNoEffects**:禁用数据点效果 - 类型:`boolean` + - 默认值:`false` - **holeSize**:中心孔大小 - 类型:`number` + - 取值范围:`0`–`90`(百分比) + - 默认值:`50` --- @@ -548,6 +598,7 @@ - **dataNoEffects**:禁用数据点效果 - 类型:`boolean` + - 默认值:`false` - **firstSliceAng**:第一扇区起始角度 - 类型:`number` @@ -569,9 +620,12 @@ - **dataLabelBkgrdColors**:数据标签背景色 - 类型:`boolean` + - 默认值:`false` - **dataLabelColor**:数据标签颜色 - 类型:`string` + - 取值范围:无特殊约束范围 + - 无默认值 - **dataLabelFontBold**:数据标签粗体 - 类型:`boolean` @@ -584,6 +638,9 @@ - **dataLabelFontSize**:数据标签字号 - 类型:`number` + - 单位:pt + - 取值范围:无特殊约束范围 + - 无默认值 - **dataLabelFormatCode**:数据标签格式代码 - 类型:`string` @@ -601,6 +658,9 @@ - **dataTableFontSize**:数据表字号 - 类型:`number` + - 单位:pt + - 取值范围:无特殊约束范围 + - 无默认值 - **dataTableFormatCode**:数据表格式代码 - 类型:`string` @@ -650,6 +710,8 @@ - **titleAlign**:标题对齐 - 类型:`string` + - 取值范围:`'l' | 'ctr' | 'r'` + - 默认值:`'ctr'` - **titleBold**:标题粗体 - 类型:`boolean` @@ -682,6 +744,9 @@ - **size**:网格线粗细 - 类型:`number` + - 单位:pt + - 取值范围:无特殊约束范围 + - 无默认值 - **style**:网格线样式 - 类型:`'solid' | 'dash' | 'dot' | 'none'` @@ -705,3 +770,12 @@ - **altText**:替代文字(无障碍) - 类型:`string` - 说明:PowerPoint 中右击图表 > "编辑替代文字" + +--- + +## 已废弃参数 + +| 已废弃参数 | 替代参数 | 废弃版本 | +|---|---|---| +| `border` | `plotArea.border` | v3.11.0 | +| `fill` | `plotArea.fill` | v3.11.0 | diff --git a/reference/output.md b/reference/output.md index f338231..48b1f1b 100644 --- a/reference/output.md +++ b/reference/output.md @@ -154,3 +154,11 @@ type WRITE_OUTPUT_TYPE = JSZIP_OUTPUT_TYPE | 'STREAM' - **title**:演示文稿标题 - 类型:`string` + +- **masterSlide**:当前母版 + - 类型:`PresSlide` + - 说明:当前演示文稿的母版幻灯片对象 + +- **presLayout**:布局信息 + - 类型:`PresLayout` + - 说明:只读,包含 `name`(布局名称)、`width`(宽度,英寸)、`height`(高度,英寸) diff --git a/reference/slide.md b/reference/slide.md index b962f21..ea6d4fc 100644 --- a/reference/slide.md +++ b/reference/slide.md @@ -220,3 +220,4 @@ pptx.addSection(props: SectionProps) |---|---|---| | `fill` | `ShapeFillProps.color` | v3.6.0 | | `src` | `DataOrPathProps.path` | v3.6.0 | +| `bkgd` (Slide 类) | `background` | v3.3.0 | diff --git a/reference/text.md b/reference/text.md index d7573b4..c41259c 100644 --- a/reference/text.md +++ b/reference/text.md @@ -1,6 +1,6 @@ # 文字参数 (Text Props) -文字参数来自 `TextPropsOptions`(继承 `TextBaseProps`、`PositionProps`、`ObjectNameProps`)和 `TextGlowProps`,用于 `slide.addText()` 方法。 +文字参数来自 `TextPropsOptions`(继承 `TextBaseProps`、`PositionProps`、`DataOrPathProps`、`ObjectNameProps`)和 `TextGlowProps`,用于 `slide.addText()` 方法。 --- @@ -131,6 +131,16 @@ ## 文字专有属性 (TextPropsOptions 特有) +继承 `DataOrPathProps`,支持以下嵌入图片的属性: + +- **path**:图片路径 + - 类型:`string` + - 说明:URL 或本地路径,可在文本框中嵌入图片 + +- **data**:图片 Base64 数据 + - 类型:`string` + - 说明:Base64 编码的图片数据,替代 `path` + - **baseline**:基线偏移 - 类型:`number` @@ -231,7 +241,7 @@ - **lineSpacingMultiple**:行间距(倍数) - 类型:`number` - 取值范围:`0.0`–`9.99` - - 单位:百分比倍数 + - 单位:倍数(非百分比) - 说明:PowerPoint 段落 > 缩进和间距 > 行距 > "多倍行距" - 示例:`1.5`(1.5 倍行距) - 始于:v3.5.0 @@ -385,6 +395,10 @@ | `autoFit` | `fit` | v3.3.0 | | `shrinkText` | `fit` | v3.3.0 | | `inset` | `margin` | v3.10.0 | +| `bullet.code` | `bullet.characterCode` | v3.3.0 | +| `bullet.marginPt` | `bullet.indent` | v3.3.0 | +| `bullet.startAt` | `bullet.numberStartAt` | v3.3.0 | +| `bullet.style` | `bullet.numberType` | v3.3.0 | | `lineDash` | `line.dashType` | v3.3.0 | | `lineHead` | `line.beginArrowType` | v3.3.0 | | `lineSize` | `line.width` | v3.3.0 | diff --git a/renderer/chart-renderer.js b/renderer/chart-renderer.js index 5393306..6090be7 100644 --- a/renderer/chart-renderer.js +++ b/renderer/chart-renderer.js @@ -1,5 +1,46 @@ +var { normalizePosition, normalizeColor, normalizeColorInOptions, validateEnum } = require('./index'); + function renderChart(slide, chartData) { - slide.addChart(chartData.chartType, chartData.data, chartData.options); + var opts = {}; + if (chartData.options) { + for (var k in chartData.options) { + if (chartData.options.hasOwnProperty(k)) opts[k] = chartData.options[k]; + } + } + + normalizePosition(opts); + + var validChartTypes = ['area', 'bar', 'bar3D', 'bubble', 'doughnut', 'line', 'pie', 'radar', 'scatter']; + validateEnum(chartData.chartType, validChartTypes, 'chartType'); + + if (chartData.data) { + for (var i = 0; i < chartData.data.length; i++) { + var series = chartData.data[i]; + if (series.labels && series.values && series.labels.length !== series.values.length) { + console.warn('Chart series "' + (series.name || i) + '" has mismatched labels (' + series.labels.length + ') and values (' + series.values.length + ')'); + } + } + } + + validateEnum(opts.barDir, ['bar', 'col'], 'barDir'); + validateEnum(opts.barGrouping, ['clustered', 'stacked', 'standard'], 'barGrouping'); + validateEnum(opts.radarStyle, ['standard', 'marker', 'filled'], 'radarStyle'); + validateEnum(opts.legendPos, ['b', 'l', 'r', 't', 'tr'], 'legendPos'); + validateEnum(opts.lineCap, ['flat', 'round', 'square'], 'lineCap'); + validateEnum(opts.lineDataSymbol, ['circle', 'dash', 'diamond', 'dot', 'none', 'square', 'triangle'], 'lineDataSymbol'); + validateEnum(opts.displayBlanksAs, ['gap', 'span', 'zero'], 'displayBlanksAs'); + validateEnum(opts.bar3DShape, ['box', 'cylinder', 'cone', 'pyramid'], 'bar3DShape'); + validateEnum(opts.dataLabelPosition, ['b', 'bestFit', 'ctr', 'l', 'r', 't', 'inEnd', 'outEnd'], 'dataLabelPosition'); + validateEnum(opts.titleAlign, ['l', 'ctr', 'r'], 'titleAlign'); + + if (opts.chartColors) { + for (var i = 0; i < opts.chartColors.length; i++) { + var nc = normalizeColor(opts.chartColors[i]); + if (nc) opts.chartColors[i] = nc; + } + } + + slide.addChart(chartData.chartType, chartData.data, opts); } module.exports = { renderChart }; diff --git a/renderer/image-renderer.js b/renderer/image-renderer.js index 3c10404..ad610d6 100644 --- a/renderer/image-renderer.js +++ b/renderer/image-renderer.js @@ -1,5 +1,28 @@ +var { normalizePosition, normalizeColorInOptions, validateEnum } = require('./index'); + function renderImage(slide, imageData) { - slide.addImage(imageData.options); + var opts = {}; + if (imageData.options) { + for (var k in imageData.options) { + if (imageData.options.hasOwnProperty(k)) opts[k] = imageData.options[k]; + } + } + + normalizePosition(opts); + + if (opts.sizing && typeof opts.sizing === 'object') { + validateEnum(opts.sizing.type, ['contain', 'cover', 'crop'], 'sizing.type'); + } + if (opts.shadow && typeof opts.shadow === 'object') { + normalizeColorInOptions(opts.shadow, 'color'); + validateEnum(opts.shadow.type, ['outer', 'inner', 'none'], 'shadow.type'); + } + + try { + slide.addImage(opts); + } catch (e) { + console.warn('Failed to load image: ' + (opts.path || 'unknown')); + } } module.exports = { renderImage }; diff --git a/renderer/index.js b/renderer/index.js index cb6e995..a656c72 100644 --- a/renderer/index.js +++ b/renderer/index.js @@ -1,5 +1,68 @@ const pptxgen = require('pptxgenjs'); const fs = require('fs'); + +function normalizePosition(options) { + if (!options || typeof options !== 'object') return options; + ['x', 'y', 'w', 'h'].forEach(function(key) { + if (options[key] === undefined) return; + var val = options[key]; + if (typeof val === 'number' && !isNaN(val)) return; + if (typeof val === 'string' && /^\d+(\.\d+)?%$/.test(val)) return; + console.warn('Invalid position value for "' + key + '": got ' + JSON.stringify(val) + ', expected a number or percentage string (e.g. "50%")'); + delete options[key]; + }); + if ((options.x !== undefined || options.y !== undefined) && options.w === undefined) options.w = 4; + if ((options.x !== undefined || options.y !== undefined) && options.h === undefined) options.h = 3; + return options; +} + +function normalizeColor(color) { + if (color === undefined || color === null) return color; + if (typeof color !== 'string') { + console.warn('Invalid color value: expected a string, got ' + typeof color); + return undefined; + } + var themeColors = ['tx1', 'tx2', 'bg1', 'bg2', 'accent1', 'accent2', 'accent3', 'accent4', 'accent5', 'accent6']; + for (var i = 0; i < themeColors.length; i++) { + if (color === themeColors[i]) return color; + } + var clean = color.replace(/^#/, ''); + if (/^[0-9A-Fa-f]{6}$/.test(clean)) return clean; + console.warn('Invalid color value: "' + color + '" is not a valid color format (expected 6-digit hex or theme color)'); + return undefined; +} + +function normalizeColorInOptions(options, key) { + if (!options || options[key] === undefined) return; + var nc = normalizeColor(options[key]); + if (nc === undefined) delete options[key]; + else options[key] = nc; +} + +function validateEnum(value, validValues, name) { + if (value === undefined || value === null) return value; + if (validValues.indexOf(value) === -1) { + console.warn('Invalid value for ' + name + ': got "' + value + '", expected one of [' + validValues.join(', ') + ']'); + } + return value; +} + +function withDefaults(options, defaults) { + if (!options || typeof options !== 'object') return options; + for (var key in defaults) { + if (defaults.hasOwnProperty(key) && options[key] === undefined) { + options[key] = defaults[key]; + } + } + return options; +} + +module.exports.normalizePosition = normalizePosition; +module.exports.normalizeColor = normalizeColor; +module.exports.normalizeColorInOptions = normalizeColorInOptions; +module.exports.validateEnum = validateEnum; +module.exports.withDefaults = withDefaults; + const { renderSlide } = require('./slide-renderer'); function render(pptxJson) { @@ -25,7 +88,42 @@ function render(pptxJson) { async function renderFile(inputPath, outputPath) { const json = fs.readFileSync(inputPath, 'utf8'); const pptx = render(json); - return pptx.writeFile({ fileName: outputPath }); + + // pptxgenjs v4 uses https.get() to download URL-based images but doesn't + // handle connection errors on the request object (only on the response + // stream). A failed TLS handshake emits an unhandled 'error' event on the + // request that crashes Node.js, and the internal promise never resolves. + // Patch to pipe request errors into a fake response that ends cleanly so + // writeFile completes with an empty placeholder image. + var https = require('https'); + var _origGet = https.get; + var EventEmitter = require('events').EventEmitter; + https.get = function() { + var args = arguments; + var cb = typeof args[args.length - 1] === 'function' ? args[args.length - 1] : null; + var urlStr = typeof args[0] === 'string' ? args[0] : ''; + var req = _origGet.apply(this, args); + req.on('error', function(err) { + console.warn('Failed to load image: ' + (urlStr || err.hostname || err.host || 'unknown')); + if (cb) { + var fakeRes = new EventEmitter(); + fakeRes.setEncoding = function() {}; + cb(fakeRes); + process.nextTick(function() { + fakeRes.emit('data', ''); + fakeRes.emit('end'); + }); + } + }); + return req; + }; + + try { + return await pptx.writeFile({ fileName: outputPath }); + } finally { + https.get = _origGet; + } } -module.exports = { render, renderFile }; +module.exports.render = render; +module.exports.renderFile = renderFile; diff --git a/renderer/shape-renderer.js b/renderer/shape-renderer.js index 6ccd686..f77ed02 100644 --- a/renderer/shape-renderer.js +++ b/renderer/shape-renderer.js @@ -1,5 +1,35 @@ +var { normalizePosition, normalizeColorInOptions, validateEnum } = require('./index'); + function renderShape(slide, shapeData) { - slide.addShape(shapeData.shapeName, shapeData.options); + var opts = {}; + if (shapeData.options) { + for (var k in shapeData.options) { + if (shapeData.options.hasOwnProperty(k)) opts[k] = shapeData.options[k]; + } + } + + normalizePosition(opts); + + if (opts.fill && typeof opts.fill === 'object') { + normalizeColorInOptions(opts.fill, 'color'); + validateEnum(opts.fill.type, ['none', 'solid'], 'fill.type'); + } + + if (opts.line && typeof opts.line === 'object') { + normalizeColorInOptions(opts.line, 'color'); + validateEnum(opts.line.dashType, ['solid', 'dash', 'dashDot', 'lgDash', 'lgDashDot', 'lgDashDotDot', 'sysDash', 'sysDot'], 'line.dashType'); + validateEnum(opts.line.beginArrowType, ['none', 'arrow', 'diamond', 'oval', 'stealth', 'triangle'], 'line.beginArrowType'); + validateEnum(opts.line.endArrowType, ['none', 'arrow', 'diamond', 'oval', 'stealth', 'triangle'], 'line.endArrowType'); + } + + if (opts.shadow && typeof opts.shadow === 'object') { + normalizeColorInOptions(opts.shadow, 'color'); + validateEnum(opts.shadow.type, ['outer', 'inner', 'none'], 'shadow.type'); + } + + validateEnum(opts.align, ['left', 'center', 'right', 'justify'], 'align'); + + slide.addShape(shapeData.shapeName, opts); } module.exports = { renderShape }; diff --git a/renderer/slide-renderer.js b/renderer/slide-renderer.js index a11dc23..17f1303 100644 --- a/renderer/slide-renderer.js +++ b/renderer/slide-renderer.js @@ -1,3 +1,4 @@ +const { normalizeColor } = require('./index'); const { renderText } = require('./text-renderer'); const { renderShape } = require('./shape-renderer'); const { renderTable } = require('./table-renderer'); @@ -7,8 +8,26 @@ const { renderChart } = require('./chart-renderer'); function renderSlide(pptx, slideData) { const slide = pptx.addSlide(); - if (slideData.background) slide.background = slideData.background; - if (slideData.color) slide.color = slideData.color; + if (slideData.background) { + if (typeof slideData.background === 'object') { + slide.background = {}; + for (var k in slideData.background) { + if (slideData.background.hasOwnProperty(k)) { + slide.background[k] = slideData.background[k]; + } + } + if (slide.background.color) { + var nc = normalizeColor(slide.background.color); + if (nc) slide.background.color = nc; + } + } else { + slide.background = slideData.background; + } + } + if (slideData.color) { + var nc = normalizeColor(slideData.color); + if (nc) slide.color = nc; + } if (slideData.hidden) slide.hidden = slideData.hidden; const objects = slideData.objects || []; diff --git a/renderer/table-renderer.js b/renderer/table-renderer.js index d139a36..8ebf0f9 100644 --- a/renderer/table-renderer.js +++ b/renderer/table-renderer.js @@ -1,5 +1,85 @@ +var { normalizePosition, normalizeColorInOptions, validateEnum } = require('./index'); + function renderTable(slide, tableData) { - slide.addTable(tableData.rows, tableData.options); + var opts = {}; + if (tableData.options) { + for (var k in tableData.options) { + if (tableData.options.hasOwnProperty(k)) opts[k] = tableData.options[k]; + } + } + + normalizePosition(opts); + + if (opts.fill && typeof opts.fill === 'object') { + normalizeColorInOptions(opts.fill, 'color'); + validateEnum(opts.fill.type, ['none', 'solid'], 'fill.type'); + } + + if (opts.border) { + if (Array.isArray(opts.border)) { + if (opts.border.length !== 4) { + console.warn('Invalid table border array: expected 4 sides, got ' + opts.border.length); + } + } else if (typeof opts.border === 'object') { + normalizeColorInOptions(opts.border, 'color'); + if (opts.border.type !== undefined) { + validateEnum(opts.border.type, ['none', 'dash', 'solid'], 'border.type'); + } + } + } + + var rows = []; + if (tableData.rows) { + for (var r = 0; r < tableData.rows.length; r++) { + var row = tableData.rows[r]; + var newRow = []; + for (var c = 0; c < row.length; c++) { + var cell = row[c]; + var newCell = { text: cell.text }; + + if (cell.colspan !== undefined) { + if (typeof cell.colspan !== 'number' || !isFinite(cell.colspan) || cell.colspan < 1 || cell.colspan !== Math.floor(cell.colspan)) { + console.warn('Invalid colspan: got ' + JSON.stringify(cell.colspan) + ', expected integer >= 1'); + } + newCell.colspan = cell.colspan; + } + if (cell.rowspan !== undefined) { + if (typeof cell.rowspan !== 'number' || !isFinite(cell.rowspan) || cell.rowspan < 1 || cell.rowspan !== Math.floor(cell.rowspan)) { + console.warn('Invalid rowspan: got ' + JSON.stringify(cell.rowspan) + ', expected integer >= 1'); + } + newCell.rowspan = cell.rowspan; + } + + if (cell.options) { + newCell.options = {}; + for (var k in cell.options) { + if (cell.options.hasOwnProperty(k)) newCell.options[k] = cell.options[k]; + } + normalizeColorInOptions(newCell.options, 'color'); + normalizeColorInOptions(newCell.options, 'highlight'); + if (newCell.options.fill && typeof newCell.options.fill === 'object') { + normalizeColorInOptions(newCell.options.fill, 'color'); + } + if (newCell.options.border) { + if (Array.isArray(newCell.options.border)) { + if (newCell.options.border.length !== 4) { + console.warn('Invalid cell border array: expected 4 sides, got ' + newCell.options.border.length); + } + } else if (typeof newCell.options.border === 'object') { + normalizeColorInOptions(newCell.options.border, 'color'); + } + } + validateEnum(newCell.options.align, ['left', 'center', 'right', 'justify'], 'cell.align'); + validateEnum(newCell.options.valign, ['top', 'middle', 'bottom'], 'cell.valign'); + } + + newRow.push(newCell); + } + rows.push(newRow); + } + } + + slide.addTable(rows, opts); } module.exports = { renderTable }; diff --git a/renderer/text-renderer.js b/renderer/text-renderer.js index 172216b..ec2d35a 100644 --- a/renderer/text-renderer.js +++ b/renderer/text-renderer.js @@ -1,5 +1,47 @@ +var { normalizePosition, normalizeColor, normalizeColorInOptions, validateEnum } = require('./index'); + function renderText(slide, textData) { - slide.addText(textData.text, textData.options); + var opts = {}; + if (textData.options) { + for (var k in textData.options) { + if (textData.options.hasOwnProperty(k)) opts[k] = textData.options[k]; + } + } + + normalizePosition(opts); + normalizeColorInOptions(opts, 'color'); + normalizeColorInOptions(opts, 'highlight'); + + if (opts.underline && typeof opts.underline === 'object') { + normalizeColorInOptions(opts.underline, 'color'); + } + if (opts.bullet && typeof opts.bullet === 'object') { + normalizeColorInOptions(opts.bullet, 'color'); + } + if (opts.glow && typeof opts.glow === 'object') { + normalizeColorInOptions(opts.glow, 'color'); + } + if (opts.outline && typeof opts.outline === 'object') { + normalizeColorInOptions(opts.outline, 'color'); + } + + validateEnum(opts.align, ['left', 'center', 'right', 'justify'], 'align'); + validateEnum(opts.valign, ['top', 'middle', 'bottom'], 'valign'); + validateEnum(opts.textDirection, ['horz', 'vert', 'vert270', 'wordArtVert'], 'textDirection'); + validateEnum(opts.vert, ['eaVert', 'horz', 'mongolianVert', 'vert', 'vert270', 'wordArtVert', 'wordArtVertRtl'], 'vert'); + validateEnum(opts.fit, ['none', 'shrink', 'resize'], 'fit'); + + if (typeof opts.strike === 'string') { + validateEnum(opts.strike, ['dblStrike', 'sngStrike'], 'strike'); + } + if (opts.underline && opts.underline.style !== undefined) { + validateEnum(opts.underline.style, ['dash', 'dashHeavy', 'dashLong', 'dashLongHeavy', 'dbl', 'dotDash', 'dotDashHeave', 'dotDotDash', 'dotDotDashHeavy', 'dotted', 'dottedHeavy', 'heavy', 'none', 'sng', 'wavy', 'wavyDbl', 'wavyHeavy'], 'underline.style'); + } + if (opts.bullet && typeof opts.bullet === 'object' && opts.bullet.type !== undefined) { + validateEnum(opts.bullet.type, ['bullet', 'number'], 'bullet.type'); + } + + slide.addText(textData.text, opts); } module.exports = { renderText }; diff --git a/schema/presentation.schema.json b/schema/presentation.schema.json index ada94be..1a498cb 100644 --- a/schema/presentation.schema.json +++ b/schema/presentation.schema.json @@ -313,6 +313,8 @@ { "type": "object", "properties": { + "autoPageCharWeight": { "type": "number", "minimum": -1.0, "maximum": 1.0, "description": "Auto-page character weight adjustment" }, + "autoPageLineWeight": { "type": "number", "minimum": -1.0, "maximum": 1.0, "description": "Auto-page line weight adjustment" }, "border": { "oneOf": [ { "$ref": "#/definitions/borderProps" }, @@ -364,7 +366,8 @@ { "type": "number" }, { "type": "array", "items": { "type": "number" } } ] - } + }, + "verbose": { "type": "boolean", "description": "Verbose mode for auto-paging debug output" } } } ], @@ -453,6 +456,79 @@ }, "description": "Chart grid line options" }, + "catAxisItem": { + "type": "object", + "properties": { + "hidden": { "type": "boolean" }, + "labelColor": { "type": "string" }, + "labelFontBold": { "type": "boolean" }, + "labelFontFace": { "type": "string" }, + "labelFontItalic": { "type": "boolean" }, + "labelFontSize": { "type": "number" }, + "labelFrequency": { "type": "string" }, + "labelPos": { "type": "string", "enum": ["none", "low", "high", "nextTo"] }, + "labelRotate": { "type": "number" }, + "lineColor": { "type": "string" }, + "lineShow": { "type": "boolean" }, + "lineSize": { "type": "number" }, + "lineStyle": { "type": "string", "enum": ["solid", "dash", "dot"] }, + "majorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] }, + "majorTimeUnit": { "type": "string" }, + "majorUnit": { "type": "number" }, + "maxVal": { "type": "number" }, + "minorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] }, + "minorTimeUnit": { "type": "string" }, + "minorUnit": { "type": "number" }, + "minVal": { "type": "number" }, + "multiLevelLabels": { "type": "boolean" }, + "orientation": { "type": "string", "enum": ["minMax"] }, + "title": { "type": "string" }, + "titleColor": { "type": "string" }, + "titleFontFace": { "type": "string" }, + "titleFontSize": { "type": "number" }, + "titleRotate": { "type": "number" }, + "gridLine": { "$ref": "#/definitions/optsChartGridLine" }, + "labelFormatCode": { "type": "string" }, + "baseTimeUnit": { "type": "string" }, + "crossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] } + }, + "description": "Category axis item for catAxes array" + }, + "valAxisItem": { + "type": "object", + "properties": { + "crossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] }, + "displayUnit": { "type": "string", "enum": ["billions", "hundredMillions", "hundreds", "hundredThousands", "millions", "tenMillions", "tenThousands", "thousands", "trillions"] }, + "displayUnitLabel": { "type": "boolean" }, + "hidden": { "type": "boolean" }, + "labelColor": { "type": "string" }, + "labelFontBold": { "type": "boolean" }, + "labelFontFace": { "type": "string" }, + "labelFontItalic": { "type": "boolean" }, + "labelFontSize": { "type": "number" }, + "labelFormatCode": { "type": "string" }, + "labelPos": { "type": "string", "enum": ["none", "low", "high", "nextTo"] }, + "labelRotate": { "type": "number" }, + "lineColor": { "type": "string" }, + "lineShow": { "type": "boolean" }, + "lineSize": { "type": "number" }, + "lineStyle": { "type": "string", "enum": ["solid", "dash", "dot"] }, + "logScaleBase": { "type": "number", "minimum": 2, "maximum": 99 }, + "majorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] }, + "majorUnit": { "type": "number" }, + "maxVal": { "type": "number" }, + "minorTickMark": { "type": "string", "enum": ["none", "inside", "outside", "cross"] }, + "minVal": { "type": "number" }, + "orientation": { "type": "string", "enum": ["minMax"] }, + "title": { "type": "string" }, + "titleColor": { "type": "string" }, + "titleFontFace": { "type": "string" }, + "titleFontSize": { "type": "number" }, + "titleRotate": { "type": "number" }, + "gridLine": { "$ref": "#/definitions/optsChartGridLine" } + }, + "description": "Value axis item for valAxes array" + }, "chartOptions": { "allOf": [ { "$ref": "#/definitions/position" }, @@ -466,7 +542,7 @@ "chartColors": { "type": "array", "items": { "$ref": "#/definitions/hexColor" } }, "chartColorsOpacity": { "type": "number" }, "dataBorder": { "$ref": "#/definitions/borderProps" }, - "displayBlanksAs": { "type": "string" }, + "displayBlanksAs": { "type": "string", "enum": ["gap", "span", "zero"] }, "invertedColors": { "type": "array", "items": { "$ref": "#/definitions/hexColor" } }, "lang": { "type": "string" }, "layout": { "$ref": "#/definitions/position" }, @@ -500,7 +576,7 @@ } }, - "catAxes": { "type": "array", "items": { "type": "object" } }, + "catAxes": { "type": "array", "items": { "$ref": "#/definitions/catAxisItem" } }, "catAxisBaseTimeUnit": { "type": "string" }, "catAxisCrossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] }, "catAxisHidden": { "type": "boolean" }, @@ -551,7 +627,7 @@ "serAxisMajorUnit": { "type": "number" }, "serAxisMinorTimeUnit": { "type": "string" }, "serAxisMinorUnit": { "type": "number" }, - "serAxisOrientation": { "type": "string" }, + "serAxisOrientation": { "type": "string", "enum": ["minMax"] }, "serAxisTitle": { "type": "string" }, "serAxisTitleColor": { "type": "string" }, "serAxisTitleFontFace": { "type": "string" }, @@ -563,7 +639,7 @@ "secondaryValAxis": { "type": "boolean" }, "showValAxisTitle": { "type": "boolean" }, - "valAxes": { "type": "array", "items": { "type": "object" } }, + "valAxes": { "type": "array", "items": { "$ref": "#/definitions/valAxisItem" } }, "valAxisCrossesAt": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["autoZero"] }] }, "valAxisDisplayUnit": { "type": "string", "enum": ["billions", "hundredMillions", "hundreds", "hundredThousands", "millions", "tenMillions", "tenThousands", "thousands", "trillions"] }, "valAxisDisplayUnitLabel": { "type": "boolean" }, @@ -595,11 +671,11 @@ "valGridLine": { "$ref": "#/definitions/optsChartGridLine" }, "valLabelFormatCode": { "type": "string" }, - "bar3DShape": { "type": "string" }, - "barDir": { "type": "string" }, + "bar3DShape": { "type": "string", "enum": ["box", "cylinder", "cone", "pyramid"] }, + "barDir": { "type": "string", "enum": ["bar", "col"] }, "barGapDepthPct": { "type": "number" }, "barGapWidthPct": { "type": "number" }, - "barGrouping": { "type": "string" }, + "barGrouping": { "type": "string", "enum": ["clustered", "stacked", "standard"] }, "barOverlapPct": { "type": "number" }, "dataNoEffects": { "type": "boolean" }, @@ -625,7 +701,7 @@ "dataLabelFontItalic": { "type": "boolean" }, "dataLabelFontSize": { "type": "number" }, "dataLabelFormatCode": { "type": "string" }, - "dataLabelFormatScatter": { "type": "string" }, + "dataLabelFormatScatter": { "type": "string", "enum": ["custom", "customXY", "XY"] }, "dataLabelPosition": { "type": "string", "enum": ["b", "bestFit", "ctr", "l", "r", "t", "inEnd", "outEnd"] }, "dataTableFontSize": { "type": "number" }, @@ -642,7 +718,7 @@ "legendPos": { "type": "string", "enum": ["b", "l", "r", "t", "tr"] }, "title": { "type": "string" }, - "titleAlign": { "type": "string" }, + "titleAlign": { "type": "string", "enum": ["l", "ctr", "r"] }, "titleBold": { "type": "boolean" }, "titleColor": { "type": "string" }, "titleFontFace": { "type": "string" }, @@ -708,6 +784,7 @@ "background": { "$ref": "#/definitions/backgroundProps" }, "color": { "$ref": "#/definitions/hexColor", "description": "Default text color for this slide" }, "hidden": { "type": "boolean", "description": "Whether slide is hidden" }, + "notes": { "type": "string", "description": "Speaker notes for this slide" }, "slideNumber": { "$ref": "#/definitions/slideNumberProps" }, "objects": { "type": "array", @@ -736,6 +813,16 @@ "company": { "type": "string" }, "revision": { "type": "string" }, "rtlMode": { "type": "boolean" }, + "masterSlide": { "type": "object", "description": "Master slide reference object" }, + "presLayout": { + "type": "object", + "properties": { + "name": { "type": "string", "description": "Layout name (e.g. LAYOUT_16x9)" }, + "width": { "type": "number", "description": "Slide width in inches" }, + "height": { "type": "number", "description": "Slide height in inches" } + }, + "description": "Presentation layout dimensions" + }, "theme": { "type": "object", "properties": {