UNPKG

@ngoctay/nodejs-pptx

Version:

Generate PPTX files on the server-side with JavaScript.

291 lines (231 loc) 12.2 kB
/* eslint-disable no-prototype-builtins */ let { PptFactoryHelper } = require('../../helpers/ppt-factory-helper'); let { PptxContentHelper } = require('../../helpers/pptx-content-helper'); const { Xml } = require('../../xmlnode'); class SlideFactory { constructor(parentFactory, args) { this.parentFactory = parentFactory; this.content = parentFactory.content; this.args = args; } addSlide(slideName, layoutName) { const relsKey = `ppt/slides/_rels/${slideName}.xml.rels`; const slideKey = `ppt/slides/${slideName}.xml`; const layoutKey = `ppt/slideLayouts/${layoutName}.xml`; this.content[relsKey] = Xml.create('Relationships', [ Xml.create('Relationship', null, { Id: 'rId1', Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout', Target: `../slideLayouts/${layoutName}.xml`,}), ], { xmlns: 'http://schemas.openxmlformats.org/package/2006/relationships', }); // add the actual slide itself (use the layout template as the source; note: layout templates are NOT the same as master slide templates) const baseSlideContent = this.content[layoutKey].clone(); baseSlideContent.setName('p:sld'); baseSlideContent.removeAttr('preserve'); baseSlideContent.removeAttr('type'); return this.content[slideKey] = baseSlideContent; } duplicateSlide(existingSlideName, newSlideName) { const existingRelsKey = `ppt/slides/_rels/${existingSlideName}.xml.rels`; const existingSlideKey = `ppt/slides/${existingSlideName}.xml`; const newRelsKey = `ppt/slides/_rels/${newSlideName}.xml.rels`; const newSlideKey = `ppt/slides/${newSlideName}.xml`; this.content[newRelsKey] = this.content[existingRelsKey].clone(); this.content[newSlideKey] = this.content[existingSlideKey].clone(); return this.content[newSlideKey]; } removeSlide(slideName) { delete this.content[`ppt/slides/_rels/${slideName}.xml.rels`]; delete this.content[`ppt/slides/${slideName}.xml`]; } moveSlide(sourceSlideNum, destinationSlideNum) { if (destinationSlideNum > sourceSlideNum) { // move slides between start and destination backwards (e.g. slide 4 becomes 3, 3 becomes 2, etc.) for (let i = sourceSlideNum; i < destinationSlideNum; i++) { this.swapSlide(i, i + 1); } } else if (destinationSlideNum < sourceSlideNum) { // move slides between start and destination forward (e.g. slide 2 becomes 3, 3 becomes 4, etc.) for (let i = sourceSlideNum - 1; i >= destinationSlideNum; i--) { this.swapSlide(i, i + 1); } } } swapSlide(slideNum1, slideNum2) { let slideKey1 = `ppt/slides/slide${slideNum1}.xml`; let slideKey2 = `ppt/slides/slide${slideNum2}.xml`; let slideRelsKey1 = `ppt/slides/_rels/slide${slideNum1}.xml.rels`; // you need to swap rels in case slide layouts are used let slideRelsKey2 = `ppt/slides/_rels/slide${slideNum2}.xml.rels`; [this.content[slideKey1], this.content[slideKey2]] = [this.content[slideKey2], this.content[slideKey1]]; [this.content[slideRelsKey1], this.content[slideRelsKey2]] = [this.content[slideRelsKey2], this.content[slideRelsKey1]]; } addImageToSlideRelationship(slide, target) { let relsKey = `ppt/slides/_rels/${slide.name}.xml.rels`; let rId = `rId${this.content[relsKey].childCount() + 1}`; this.content[relsKey].push( Xml.create('Relationship', null, { Id: rId, Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', Target: target, }) ); return rId; } addHyperlinkToSlideRelationship(slide, target) { if (!target) return ''; let relsKey = `ppt/slides/_rels/${slide.name}.xml.rels`; let rId = `rId${this.content[relsKey].childCount() + 1}`; this.content[relsKey].push( Xml.create('Relationship', null, { Id: rId, Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', Target: target, TargetMode: 'External', }) ); return rId; } addSlideTargetRelationship(slide, target) { if (!target) return ''; let relsKey = `ppt/slides/_rels/${slide.name}.xml.rels`; let rId = `rId${this.content[relsKey].childCount() + 1}`; this.content[relsKey].push( Xml.create('Relationship', null, { Id: rId, Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide', Target: target, }) ); return rId; } addChartToSlideRelationship(slide, chartName) { let relsKey = `ppt/slides/_rels/${slide.name}.xml.rels`; let rId = `rId${this.content[relsKey].childCount() + 1}`; this.content[relsKey].push( Xml.create('Relationship', null, { Id: rId, Type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart', Target: `../charts/${chartName}.xml`, }) ); return rId; } addImage(slide, image, imageObjectName, rId) { let slideKey = `ppt/slides/${slide.name}.xml`; let picObjectId = slide.getNextObjectId(); let newImageBlock = Xml.create('p:pic', Xml.createTree({ 'p:nvPicPr': { 'p:cNvPr': { [Xml.ATTR_KEY]: { id: picObjectId, name: `${imageObjectName} ${picObjectId}`, descr: imageObjectName } }, 'p:cNvPicPr': { 'a:picLocks': { [Xml.ATTR_KEY]: { noChangeAspect: '1' } } }, 'p:nvPr': null, }, 'p:blipFill': { 'a:blip': { [Xml.ATTR_KEY]: { 'r:embed': rId, cstate: 'print' } }, 'a:stretch': { 'a:fillRect': null }, }, 'p:spPr': { 'a:xfrm': { 'a:off': { [Xml.ATTR_KEY]: { x: image.x(), y: image.y() } }, 'a:ext': { [Xml.ATTR_KEY]: { cx: image.cx(), cy: image.cy() } }, }, 'a:prstGeom': { [Xml.ATTR_KEY]: { prst: 'rect' }, 'a:avLst': null, }, }, })); if (typeof image.options.url === 'string' && image.options.url.length > 0) { newImageBlock.get('p:nvPicPr/p:cNvPr').push(Xml.create('a:hlinkClick', null, { 'r:id': image.options.rIdForHyperlink })); if (image.options.url[0] === '#') { newImageBlock.get('p:nvPicPr/p:cNvPr/a:hlinkClick').setAttr('action', 'ppaction://hlinksldjump'); } } this.content[slideKey].get('p:cSld/p:spTree').push(newImageBlock); return newImageBlock; } addText(slide, textBox) { let slideKey = `ppt/slides/${slide.name}.xml`; let objectId = slide.getNextObjectId(); let options = textBox.options; // construct the bare minimum structure of a shape block (text objects are a special case of shape) const newTextBlock = Xml.create('p:sp', PptFactoryHelper.createBaseShapeBlock(objectId, 'Text', textBox.x(), textBox.y(), textBox.cx(), textBox.cy()) ); // now add the nodes which turn a shape block into a text block newTextBlock.get('p:nvSpPr/p:cNvSpPr').setAttr('txBox', '1'); newTextBlock.get('p:txBody/a:bodyPr').setAttr('rtlCol', '0'); if (options.backgroundColor) { newTextBlock.get('p:spPr').push(Xml.create('a:solidFill', [PptFactoryHelper.createColorBlock(options.backgroundColor)])); } else { newTextBlock.get('p:spPr').push(Xml.create('a:noFill')); } PptFactoryHelper.addTextValuesToBlock(newTextBlock.get('p:txBody'), textBox, options); PptFactoryHelper.setTextBodyProperties(newTextBlock.get('p:txBody/a:bodyPr'), textBox, options); PptFactoryHelper.setShapeProperties(newTextBlock.get('p:spPr'), options); if (typeof options.url === 'string' && options.url.length > 0) { if (options.applyHrefOnShapeOnly) { newTextBlock.get('p:nvSpPr/p:cNvPr').push(Xml.create('a:hlinkClick', null, { 'r:id': options.rIdForHyperlink })); if (options.url[0] === '#') { newTextBlock.get('p:nvSpPr/p:cNvPr/a:hlinkClick').setAttr('action', 'ppaction://hlinksldjump'); } } else { newTextBlock.get('p:txBody/a:p/a:r/a:rPr').push(Xml.create('a:hlinkClick', null, { 'r:id': options.rIdForHyperlink })); if (options.url[0] === '#') { newTextBlock.get('p:txBody/a:p/a:r/a:rPr/a:hlinkClick').setAttr('action', 'ppaction://hlinksldjump'); } } } this.content[slideKey].get('p:cSld/p:spTree').push(newTextBlock); return newTextBlock; } addShape(slide, shape) { let slideKey = `ppt/slides/${slide.name}.xml`; let objectId = slide.getNextObjectId(); let options = shape.options; let type = shape.shapeType; let shapeColor = options.color || '00AA00'; if (options.textAlign === undefined) { options.textAlign = 'center'; // for shapes, we always want text defaulted to the center } const newShapeBlock = Xml.create('p:sp', PptFactoryHelper.createBaseShapeBlock(objectId, 'Shape', shape.x(), shape.y(), shape.cx(), shape.cy()) ); newShapeBlock.get('p:spPr/a:prstGeom').setAttr('prst', type.name); newShapeBlock.get('p:spPr').push(Xml.create('a:solidFill', [PptFactoryHelper.createColorBlock(shapeColor)])); newShapeBlock.get('p:txBody/a:bodyPr').setAttr('rtlCol', '0'); PptFactoryHelper.addTextValuesToBlock(newShapeBlock.get('p:txBody'), shape, options); PptFactoryHelper.setTextBodyProperties(newShapeBlock.get('p:txBody/a:bodyPr'), shape, options); PptFactoryHelper.setShapeProperties(newShapeBlock.get('p:spPr'), options, type.avLst); if (typeof options.url === 'string' && options.url.length > 0) { newShapeBlock.get('p:nvSpPr/p:cNvPr').push(Xml.create('a:hlinkClick', null, { 'r:id': options.rIdForHyperlink })); if (options.url[0] === '#') { newShapeBlock.get('p:nvSpPr/p:cNvPr/a:hlinkClick').setAttr('action', 'ppaction://hlinksldjump'); } } this.content[slideKey].get('p:cSld/p:spTree').push(newShapeBlock); return newShapeBlock; } addChart(slide, chart) { let slideKey = `ppt/slides/${slide.name}.xml`; let chartKey = `ppt/charts/${chart.name}.xml`; let newGraphicFrameBlock = PptFactoryHelper.createBaseChartFrameBlock(chart.x(), chart.y(), chart.cx(), chart.cy()); // goes onto the slide let newChartSpaceBlock = PptFactoryHelper.createBaseChartSpaceBlock(); // goes into the chart XML let seriesDataBlock = PptFactoryHelper.createSeriesDataBlock(chart.chartData); newChartSpaceBlock.get('c:chart/c:plotArea/c:barChart/c:ser').pushAll(seriesDataBlock); this.content[chartKey] = newChartSpaceBlock; this.content[slideKey].get('p:cSld/p:spTree').push(newGraphicFrameBlock); return newGraphicFrameBlock; } setBackgroundColor(slide, color) { let slideKey = `ppt/slides/${slide.name}.xml`; let slideContent = this.content[slideKey].get('p:cSld'); if (!slideContent.get('p:bg')) { slideContent.insert(0, Xml.create('p:bg')); } slideContent.get('p:bg').remove('p:bgPr'); slideContent.get('p:bg').push(Xml.create('p:bgPr', [ Xml.create('a:solidFill', [PptFactoryHelper.createColorBlock(color)]), Xml.create('a:effectLst'), ])); } } module.exports.SlideFactory = SlideFactory;