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) { const pptx = new pptxgen(); const data = typeof pptxJson === 'string' ? JSON.parse(pptxJson) : pptxJson; const pres = data.presentation || {}; const slides = data.slides || []; if (pres.layout) pptx.layout = pres.layout; if (pres.author) pptx.author = pres.author; if (pres.title) pptx.title = pres.title; if (pres.subject) pptx.subject = pres.subject; if (pres.company) pptx.company = pres.company; if (pres.revision) pptx.revision = pres.revision; if (pres.rtlMode !== undefined) pptx.rtlMode = pres.rtlMode; if (pres.theme) pptx.theme = pres.theme; slides.forEach(slideData => renderSlide(pptx, slideData)); return pptx; } async function renderFile(inputPath, outputPath) { const json = fs.readFileSync(inputPath, 'utf8'); const pptx = render(json); // 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 = render; module.exports.renderFile = renderFile;