// // officegen: layouts support for pptx documents. // // Please refer to README.md for this module's documentations. // // Copyright (c) 2013 Ziv Barber; // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // /** * This function creating the pptx layouts plugin object. * @summary Create a new pptx plugin. * @param {object} pluginsman Access to the plugins manager for pptx documents. * @constructor * @name MakeLayoutPlugin */ function MakeLayoutPlugin(pluginsman) { var funcThis = this this.ogPluginsApi = pluginsman.ogPluginsApi // Generic officegen API for plugins. this.msPluginsApi = pluginsman.genPrivate.plugs.type.msoffice // msoffice plugins API. this.pluginsman = pluginsman // Document type specific plugins API. this.pptxData = pluginsman.getDataStorage() // Here you can store any temporary data needed for generating the document and depending on the data filled by the user. this.mainPath = pluginsman.genPrivate.features.type.msoffice.main_path // The "folder" name inside the document zip that all the specific resources of this document type are stored. this.mainPathFile = pluginsman.genPrivate.features.type.msoffice.main_path_file // The name of the main real xml resource of this document. this.relsMain = pluginsman.genPrivate.type.msoffice.rels_main // Main rels file. this.relsApp = pluginsman.genPrivate.type.msoffice.rels_app // Main rels file inside the specific document type "folder". this.filesList = pluginsman.genPrivate.type.msoffice.files_list // Resources list xml. this.srcFilesList = pluginsman.genPrivate.type.msoffice.src_files_list // For storing extra files inside the document zip. // // Catch events inside the document so we can extent it: // // We want to extend the main API of the pptx document object: pluginsman.registerCallback('makeDocApi', function(docObj) { funcThis.extendPptxApi(docObj) }) // We want to extend the slide object API: pluginsman.registerCallback('newPage', function(docData) { funcThis.extendPptxSlideApi(docData) }) // This event tell us that the generator is about to start working: pluginsman.registerCallback('beforeGen', function(docObj) { funcThis.beforeGen(docObj) }) return this } // // Events implementations: // /** * This function extending the main document object with new API methods. * @param {object} docObj Document object. */ MakeLayoutPlugin.prototype.extendPptxApi = function(docObj) { var funcThis = this /** * Create a new title layout based slide. * @param {*} title Optional title string. * @param {*} subTitle Optional sub-title string. * @return The new slide object. */ docObj.makeTitleSlide = function(title, subTitle) { return docObj.makeNewSlide({ useLayout: 'title', title: title, subTitle: subTitle }) } /** * Create a new obj layout based slide. * @param {*} title Optional title string. * @param {*} objData Optional obj data. * @return The new slide object. */ docObj.makeObjSlide = function(title, objData) { return docObj.makeNewSlide({ useLayout: 'obj', title: title, objData: objData }) } /** * Create a new secHead layout based slide. * @param {*} title Optional title string. * @param {*} subTitle Optional sub-title string. * @return The new slide object. */ docObj.makeSecHeadSlide = function(title, subTitle) { return docObj.makeNewSlide({ useLayout: 'secHead', title: title, subTitle: subTitle }) } /** * Create a new custom layout. * @param {string} layoutName New layout name. * @param {object} options Additional settings. * @return The new layout object. */ docObj.makeNewLayout = function(layoutName, options) { if (!funcThis.pptxData.slideLayouts) { funcThis.pptxData.slideLayouts = [] } // Endif. if (!layoutName || typeof layoutName !== 'string') { throw new Error('Invalid layout name.') } // Endif. if (!options || typeof options !== 'object') { options = {} } // Endif. var slideLayouts = funcThis.pptxData.slideLayouts var newLayout = { slide: { getPageNumber: function() { return 1 }, layoutName: layoutName, back: options.back, name: options.display || layoutName }, data: options.data || [] } if (options.xmlCode && typeof options.xmlCode === 'string') { newLayout.data.push({ type: 'xml', data: options.xmlCode, options: {} }) } // Endif. // Register a new layout object: slideLayouts.push({ // We are using the slide resource generator to make the new layout since that the format is almost the same: execCb: function(data) { return funcThis.pluginsman.genobj.cbMakePptxSlide(data) }, cbData: newLayout, name: layoutName, display: layoutName, mkResCb: options.mkResCb || null }) return newLayout } } /** * This function extending a new created slide object with new API methods. * @param {object} docData Object with information about the new slide and more. */ MakeLayoutPlugin.prototype.extendPptxSlideApi = function(docData) { var funcThis = this var newSlide = docData.page // The new slide's API. // var slideData = docData.pageData // Place here data related to the slide. var slideOptions = docData.slideOptions // Slide creation options. // We'll add additional layout specific API only if the user requested to connect this slide to a layout: if ( slideOptions && typeof slideOptions === 'object' && slideOptions.useLayout ) { // Support for the standard office layouts: var layoutInfo = funcThis.findOfficeLayout(slideOptions.useLayout) if (layoutInfo) { var resCb = this.pluginsman.genobj.cbMakePptxLayout1 var layoutFileNum = layoutInfo.index if (layoutFileNum > 1) { resCb = function(data) { return funcThis['cbMakePptxLayout' + layoutFileNum](data) } } // Endif. // Data for the useLayout callback: newSlide.useLayout = { officeType: slideOptions.useLayout, name: layoutFileNum.display, mkResCb: resCb, slide: newSlide, // This is only for making the layout using cbMakePptxSlide() so data.slide will be the same as newSlide. ph1: [], ph2: [], ft: [], useDate: new Date(), isRealSlide: true, isFooter: false, isSlideNum: true, isDate: true } // // Additional slide API: // newSlide.setTitle = function(titleStr) { if (typeof titleStr === 'string') { newSlide.useLayout.ph1 = [{ text: titleStr }] } else if (typeof titleStr === 'object') { newSlide.useLayout.ph1 = titleStr } // Endif. } if (slideOptions.useLayout === 'obj') { newSlide.setObjData = function(objData) { if (typeof objData === 'string') { newSlide.useLayout.ph2 = [{ text: objData }] } else if (typeof objData === 'object') { newSlide.useLayout.ph2 = objData } // Endif. } newSlide.appendObjData = function(objData) { if (typeof objData === 'string') { newSlide.useLayout.ph2.push({ text: objData }) } else if (typeof objData === 'object' && objData.length) { newSlide.useLayout.ph2 = newSlide.useLayout.ph2.concat(objData) } // Endif. } } else { newSlide.setSubTitle = function(titleStr) { if (typeof titleStr === 'string') { newSlide.useLayout.ph2 = [{ text: titleStr }] } else if (typeof titleStr === 'object') { newSlide.useLayout.ph2 = titleStr } // Endif. } } // Endif. newSlide.setFooter = function(footerStr) { newSlide.useLayout.isFooter = true if (typeof footerStr === 'string') { newSlide.useLayout.ft = [{ text: footerStr }] } else if (typeof footerStr === 'object') { newSlide.useLayout.ft = footerStr } // Endif. } if (slideOptions.title) { newSlide.setTitle(slideOptions.title) } // Endif. if (slideOptions.useLayout === 'obj') { if (slideOptions.objData) { newSlide.setObjData(slideOptions.objData) } // Endif. } else { if (slideOptions.subTitle) { newSlide.setSubTitle(slideOptions.subTitle) } // Endif. } // Endif. if (slideOptions.footer) { newSlide.setFooter(slideOptions.footer) } // Endif. } else { // Custom layout: layoutInfo = funcThis.findLayoutByName(slideOptions.useLayout) if (!layoutInfo) { throw new Error( 'Invalid layout (you must register it first using makeNewLayout).' ) } // Endif. // Custom layout based slide: newSlide.useLayout = { customLayout: slideOptions.useLayout, slide: newSlide, // This is only for making the layout using cbMakePptxSlide() so data.slide will be the same as newSlide. layoutFileNum: layoutFileNum, layout: layoutInfo && layoutInfo.layout ? layoutInfo.layout : null } if (newSlide.useLayout.layout) { // Check if the custom layout wants to full redraw: if (newSlide.useLayout.layout.mkResCb) { newSlide.useLayout.mkResCb = newSlide.useLayout.layout.mkResCb } // Endif. } // Endif. } // Endif. } // Endif. } /** * This function been called just before starting to generate the output document zip. * @param {object} docObj Document object. */ MakeLayoutPlugin.prototype.beforeGen = function(docObj) { var funcThis = this if (!this.pptxData.slideLayouts) { this.pptxData.slideLayouts = [] } // Endif. var slideLayouts = this.pptxData.slideLayouts // [].concat(this.pptxData.slideLayouts || []) // Add a single layout from the built-in Microsoft Office layouts: function addLayout(curId, info) { // You can replace any built-in layout except for the first one (for now): if (!funcThis.findLayoutByName(info.name)) { slideLayouts.push({ execCb: function(data) { return funcThis['cbMakePptxLayout' + curId](data) }, cbData: { layoutRealOfficeId: curId }, // Do we need extendPptxSlideApi? we are not using it any more. name: info.name, display: info.display }) } // Endif. } // Information about all the Microsoft Office built-in layout types: var layoutsInfo = funcThis.getOfficeLayouts() // Add all the office standard layouts (only if they are not already exists): for (var i = 0; i < 10; i++) { addLayout(i + 2, layoutsInfo[i + 1]) } // End of for loop. // Work on all the registered layout so we'll add them into the output document: var curId = 0 for (var item in slideLayouts) { // Make sure that we'll know the right rel ID of this new slide resource: slideLayouts[item].relIdMaster = funcThis.pluginsman.genobj.slideMasterRels.length + 1 // Add it to the rels of the slides master: funcThis.pluginsman.genobj.slideMasterRels.push({ type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout', target: '../slideLayouts/slideLayout' + (curId + 2) + '.xml', clear: 'generate' }) // Fix any slide that using this layout: if (slideLayouts[item].name) { this.pluginsman.genPrivate.pages.forEach(function(value) { if ( value.slide.useLayout && typeof value.slide.useLayout === 'object' ) { if ( value.slide.useLayout.officeType === slideLayouts[item].name || value.slide.useLayout.customLayout === slideLayouts[item].name ) { // This slide is using this layout so we must fix it: // console.log('fix! ' + slideLayouts[item].name + ' ' + (curId + 2)) value.slide.useLayout.layoutFileNum = curId + 2 // Set the real layout for this slide: value.slide.getRelFile()[0].target = '../slideLayouts/slideLayout' + (curId + 2) + '.xml' } // Endif. } // Endif. }) } // Endif. // Add this slide layout resource + the rels resource of it: slideLayouts[item].cbData.layoutNum = curId + 2 funcThis.ogPluginsApi.intAddAnyResourceToParse( funcThis.mainPath + '\\slideLayouts\\slideLayout' + (curId + 2) + '.xml', 'buffer', slideLayouts[item].cbData, slideLayouts[item].execCb, false ) funcThis.ogPluginsApi.intAddAnyResourceToParse( funcThis.mainPath + '\\slideLayouts\\_rels\\slideLayout' + (curId + 2) + '.xml.rels', 'buffer', [ { type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster', target: '../slideMasters/slideMaster1.xml' } ], funcThis.pluginsman.genPrivate.plugs.type.msoffice.cbMakeRels, false ) // Add it also to the main list of resources in the document: funcThis.filesList.push({ name: '/ppt/slideLayouts/slideLayout' + (curId + 2) + '.xml', type: 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml', clear: 'generate' }) curId++ } // End of for loop. } // // Resource generating callbacks: // /** * Create the 'slideLayout2.xml' resource. * * @param {object} data Ignored by this callback function. * @return Text string. */ MakeLayoutPlugin.prototype.cbMakePptxLayout2 = function(data) { if (!data || typeof data !== 'object') { data = {} } // Endif. // You can place here the title: var ph1 = 'Click to edit Master title style' if (data.ph1 && data.slide && data.ph1.length) { ph1 = this.pluginsman.genobj.cMakePptxOutTextP('', data.ph1, {}, data.slide) } // Endif. // More data: var ph2 = 'Click to edit Master text stylesSecond levelThird levelFourth levelFifth level' if (data.ph2 && data.slide && data.ph2.length) { ph2 = this.pluginsman.genobj.cMakePptxOutTextP('', data.ph2, {}, data.slide) } // Endif. var ph3 = this.pluginsman.genobj.createFieldText('DATE_TIME', 1, data.useDate) var footFull = '' var ft = '' var curElNum = 4 if (data.isDate || !data.isRealSlide) { footFull += '' + ph3 + '' curElNum++ } // Endif. if (data.isFooter && data.ft && data.slide && data.ft.length) { ft = this.pluginsman.genobj.cMakePptxOutTextP('', data.ft, {}, data.slide) footFull += '' + ft + '' curElNum++ } else if (!data.isRealSlide) { footFull += '' curElNum++ } // Endif. if (data.isSlideNum || !data.isRealSlide) { footFull += '‹#›' curElNum++ } // Endif. return ( this.msPluginsApi.cbMakeMsOfficeBasicXml(data) + '' + ph1 + '' + ph2 + '' + footFull + '' ) } /** * Create the 'slideLayout3.xml' resource. * * @param {object} data Ignored by this callback function. * @return Text string. */ MakeLayoutPlugin.prototype.cbMakePptxLayout3 = function(data) { if (!data || typeof data !== 'object') { data = {} } // Endif. // You can place here the title: var ph1 = 'Click to edit Master title style' if (data.ph1 && data.slide && data.ph1.length) { ph1 = this.pluginsman.genobj.cMakePptxOutTextP('', data.ph1, {}, data.slide) } // Endif. // More data: var ph2 = 'Click to edit Master text styles' if (data.ph2 && data.slide && data.ph2.length) { ph2 = this.pluginsman.genobj.cMakePptxOutTextP('', data.ph2, {}, data.slide) } // Endif. var ph3 = this.pluginsman.genobj.createFieldText('DATE_TIME', 1, data.useDate) var footFull = '' var ft = '' var curElNum = 4 if (data.isDate || !data.isRealSlide) { footFull += '' + ph3 + '' curElNum++ } // Endif. if (data.isFooter && data.ft && data.slide && data.ft.length) { ft = this.pluginsman.genobj.cMakePptxOutTextP('', data.ft, {}, data.slide) footFull += '' + ft + '' curElNum++ } else if (!data.isRealSlide) { footFull += '' curElNum++ } // Endif. if (data.isSlideNum || !data.isRealSlide) { footFull += '‹#›' curElNum++ } // Endif. return ( this.msPluginsApi.cbMakeMsOfficeBasicXml(data) + '' + ph1 + '' + ph2 + '' + footFull + '' ) } /** * Create the 'slideLayout4.xml' resource. * * @param {object} data Ignored by this callback function. * @return Text string. */ MakeLayoutPlugin.prototype.cbMakePptxLayout4 = function(data) { if (!data || typeof data !== 'object') { data = {} } // Endif. return ( this.msPluginsApi.cbMakeMsOfficeBasicXml(data) + 'Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text stylesSecond levelThird levelFourth levelFifth level1/30/2017‹#›' ) } /** * Create the 'slideLayout5.xml' resource. * * @param {object} data Ignored by this callback function. * @return Text string. */ MakeLayoutPlugin.prototype.cbMakePptxLayout5 = function(data) { if (!data || typeof data !== 'object') { data = {} } // Endif. return ( this.msPluginsApi.cbMakeMsOfficeBasicXml(data) + 'Click to edit Master title styleClick to edit Master text stylesClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text stylesClick to edit Master text stylesSecond levelThird levelFourth levelFifth level1/30/2017‹#›' ) } /** * Create the 'slideLayout6.xml' resource. * * @param {object} data Ignored by this callback function. * @return Text string. */ MakeLayoutPlugin.prototype.cbMakePptxLayout6 = function(data) { if (!data || typeof data !== 'object') { data = {} } // Endif. return ( this.msPluginsApi.cbMakeMsOfficeBasicXml(data) + 'Click to edit Master title style1/30/2017‹#›' ) } /** * Create the 'slideLayout7.xml' resource. * * @param {object} data Ignored by this callback function. * @return Text string. */ MakeLayoutPlugin.prototype.cbMakePptxLayout7 = function(data) { if (!data || typeof data !== 'object') { data = {} } // Endif. return ( this.msPluginsApi.cbMakeMsOfficeBasicXml(data) + '1/30/2017‹#›' ) } /** * Create the 'slideLayout8.xml' resource. * * @param {object} data Ignored by this callback function. * @return Text string. */ MakeLayoutPlugin.prototype.cbMakePptxLayout8 = function(data) { if (!data || typeof data !== 'object') { data = {} } // Endif. return ( this.msPluginsApi.cbMakeMsOfficeBasicXml(data) + 'Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth levelClick to edit Master text styles1/30/2017‹#›' ) } /** * Create the 'slideLayout9.xml' resource. * * @param {object} data Ignored by this callback function. * @return Text string. */ MakeLayoutPlugin.prototype.cbMakePptxLayout9 = function(data) { if (!data || typeof data !== 'object') { data = {} } // Endif. return ( this.msPluginsApi.cbMakeMsOfficeBasicXml(data) + 'Click to edit Master title styleClick to edit Master text styles1/30/2017‹#›' ) } /** * Create the 'slideLayout10.xml' resource. * * @param {object} data Ignored by this callback function. * @return Text string. */ MakeLayoutPlugin.prototype.cbMakePptxLayout10 = function(data) { if (!data || typeof data !== 'object') { data = {} } // Endif. return ( this.msPluginsApi.cbMakeMsOfficeBasicXml(data) + 'Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level1/30/2017‹#›' ) } /** * Create the 'slideLayout11.xml' resource. * * @param {object} data Ignored by this callback function. * @return Text string. */ MakeLayoutPlugin.prototype.cbMakePptxLayout11 = function(data) { if (!data || typeof data !== 'object') { data = {} } // Endif. return ( this.msPluginsApi.cbMakeMsOfficeBasicXml(data) + 'Click to edit Master title styleClick to edit Master text stylesSecond levelThird levelFourth levelFifth level1/30/2017‹#›' ) } // // Additional helpers: // /** * Locate a layout in the current layout list. * * @param {string} name The name of the layout. * @return Either an object with the layout information or null if it not been found. */ MakeLayoutPlugin.prototype.findLayoutByName = function(name) { if (name === 'title' || name === 'Title Slide') { return { name: 'title', display: 'Title Slide', layoutNum: 1 } } // Endif. // Check all the registered layouts to find one with the given name: var retData = null this.pptxData.slideLayouts.every(function(value) { if (value.name === name || value.display === name) { retData = { name: value.name, display: value.display, layoutNum: value.layoutNum, layout: value } return false } // Endif. return true }) return retData } /** * Locate a layout in the built-in Office layouts list. * * @param {string} name The name of the layout. * @return Either an object with the layout information or null if it not been found. */ MakeLayoutPlugin.prototype.findOfficeLayout = function(name) { // Check all the registered layouts to find one with the given name: var retData = null this.getOfficeLayouts().every(function(value, index) { if (value.name === name || value.display === name) { retData = { name: value.name, display: value.display, index: index + 1 } return false } // Endif. return true }) return retData } /** * Get the built-in Microsoft Office layouts. * * @return Array with the Microsoft Office layouts information. */ MakeLayoutPlugin.prototype.getOfficeLayouts = function() { // Information about all the Microsoft Office built-in layout types: return [ { name: 'title', display: 'Title Slide' }, { name: 'obj', display: 'Title and Content' }, { name: 'secHead', display: 'Section Header' }, { name: 'twoObj', display: 'Two Content' }, { name: 'twoTxTwoObj', display: 'Comparison' }, { name: 'titleOnly', display: 'Title Only' }, { name: 'blank', display: 'Blank' }, { name: 'objTx', display: 'Content with Caption' }, { name: 'picTx', display: 'Picture with Caption' }, { name: 'vertTx', display: 'Title and Vertical Text' }, { name: 'vertTitleAndTx', display: 'Vertical Title and Text' } ] } module.exports = MakeLayoutPlugin