From 792d2592b6fedd67c1d3e8c66ed71f7d92d6b767 Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Tue, 15 Aug 2017 15:16:14 +0200 Subject: [PATCH 01/27] When `pptx.userProperLayoutMaster` called, XML of a master file and layout files can be passed to the presentation. Otherwise, works as originally. --- dist/pptxgen.js | 115 +++++++++++++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 41 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index da61f2797..d397084e1 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -133,6 +133,10 @@ var PptxGenJS = function(){ gObjPptx.revision = '1'; gObjPptx.subject = 'PptxGenJS Presentation'; gObjPptx.title = 'PptxGenJS Presentation'; + gObjPptx.masterSlideXml = ''; + gObjPptx.layoutSlideXmls = {}; + gObjPptx.slide2layoutMapping = []; + gObjPptx.properLayoutMasterInUse = false; gObjPptx.fileName = 'Presentation'; gObjPptx.fileExtn = '.pptx'; gObjPptx.pptLayout = LAYOUTS['LAYOUT_16x9']; @@ -195,13 +199,23 @@ var PptxGenJS = function(){ zip.file("ppt/tableStyles.xml", makeXmlTableStyles()); zip.file("ppt/viewProps.xml", makeXmlViewProps()); + if ( gObjPptx.properLayoutMasterInUse ) { + var layouts = Object.keys(gObjPptx.layoutSlideXmls); + for ( var idx = 0, cnt = layouts.length; idx < cnt; idx++ ) { + zip.file("ppt/slideLayouts/slideLayout"+ ( idx + 1 ) +".xml", gObjPptx.layoutSlideXmls[layouts[idx]]); + zip.file("ppt/slideLayouts/_rels/slideLayout"+ ( idx + 1 ) +".xml.rels", makeXmlSlideLayoutRel( idx + 1 )); + } + } + // Create a Layout/Master/Rel/Slide file for each SLIDE for ( var idx=0; idx 359 ) { + console.warn('Warning: shadow.angle can only be 0-359'); + shadowOpts.angle = 270; + } + + // B: ROBUST: Cast any type of valid arg to int: '12', 12.3, etc. -> 12 + shadowOpts.angle = Math.round(Number(shadowOpts.angle)); + } + + // OPT: `opacity` + if ( shadowOpts.opacity ) { + // A: REALITY-CHECK + if ( isNaN(Number(shadowOpts.opacity)) || shadowOpts.opacity < 0 || shadowOpts.opacity > 1 ) { + console.warn('Warning: shadow.opacity can only be 0-1'); + shadowOpts.opacity = 0.75; + } + + // B: ROBUST: Cast any type of valid arg to int: '12', 12.3, etc. -> 12 + shadowOpts.opacity = Number(shadowOpts.opacity) + } + } + /* ======================================================================================================= | # # # # # ##### @@ -2573,9 +2625,12 @@ var PptxGenJS = function(){ } function makeXmlSlideRel(inSlideNum) { + var layouts = Object.keys(gObjPptx.layoutSlideXmls), + layoutIdx = layouts.indexOf(gObjPptx.slide2layoutMapping[inSlideNum - 1]) + 1; + var strXml = ''+CRLF + '' - + ' '; + + ' '; // Add any rels for this Slide (image/audio/video/youtube/chart) gObjPptx.slides[inSlideNum-1].rels.forEach(function(rel,idx){ @@ -2617,6 +2672,9 @@ var PptxGenJS = function(){ } function makeXmlSlideMaster() { + if ( gObjPptx.properLayoutMasterInUse ) { + return gObjPptx.masterSlideXml; + } var intSlideLayoutId = 2147483649; var strXml = ''+CRLF + '' @@ -2835,6 +2893,18 @@ var PptxGenJS = function(){ } } + this.setMasterSlide = function(masterSlideXml) { + gObjPptx.masterSlideXml = masterSlideXml; + }; + + this.addLayoutSlide = function(name, layoutSlideXml) { + gObjPptx.layoutSlideXmls[name] = layoutSlideXml; + }; + + this.useProperLayoutMaster = function() { + gObjPptx.properLayoutMasterInUse = true; + } + /** * Sets the Presentation's Title */ @@ -2925,44 +2995,7 @@ var PptxGenJS = function(){ var slideObjNum = 0; var pageNum = (slideNum + 1); - /** - * Checks shadow options passed by user and performs corrections if needed. - * @param {Object} shadowOpts - */ - function correctShadowOptions(shadowOpts) { - if ( !shadowOpts || shadowOpts === 'none' ) return; - - // OPT: `type` - if ( shadowOpts.type != 'outer' && shadowOpts.type != 'inner' ) { - console.warn('Warning: shadow.type options are `outer` or `inner`.'); - shadowOpts.type = 'outer'; - } - - // OPT: `angle` - if ( shadowOpts.angle ) { - // A: REALITY-CHECK - if ( isNaN(Number(shadowOpts.angle)) || shadowOpts.angle < 0 || shadowOpts.angle > 359 ) { - console.warn('Warning: shadow.angle can only be 0-359'); - shadowOpts.angle = 270; - } - - // B: ROBUST: Cast any type of valid arg to int: '12', 12.3, etc. -> 12 - shadowOpts.angle = Math.round(Number(shadowOpts.angle)); - } - - // OPT: `opacity` - if ( shadowOpts.opacity ) { - // A: REALITY-CHECK - if ( isNaN(Number(shadowOpts.opacity)) || shadowOpts.opacity < 0 || shadowOpts.opacity > 1 ) { - console.warn('Warning: shadow.opacity can only be 0-1'); - shadowOpts.opacity = 0.75; - } - - // B: ROBUST: Cast any type of valid arg to int: '12', 12.3, etc. -> 12 - shadowOpts.opacity = Number(shadowOpts.opacity) - } - } - + gObjPptx.slide2layoutMapping.push(inMaster); // A: Add this SLIDE to PRESENTATION, Add default values as well gObjPptx.slides[slideNum] = {}; gObjPptx.slides[slideNum].slide = slideObj; From dca1a8816a8fbb9ac065acfcf4d30ddbebf1ee3d Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Thu, 17 Aug 2017 11:59:35 +0200 Subject: [PATCH 02/27] Layout example added. --- examples/pptxgenjs-demo.layouts.html | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100755 examples/pptxgenjs-demo.layouts.html diff --git a/examples/pptxgenjs-demo.layouts.html b/examples/pptxgenjs-demo.layouts.html new file mode 100755 index 000000000..787ce3d64 --- /dev/null +++ b/examples/pptxgenjs-demo.layouts.html @@ -0,0 +1,46 @@ + + + + + PptxGenJS Examples/Demo Page + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 82851d904b387af756d799ab481edde1e07a61e4 Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Thu, 17 Aug 2017 13:23:17 +0200 Subject: [PATCH 03/27] Working draft: Layout (that includes text only) can be crated by the same definition as original "masters". --- dist/pptxgen.js | 663 +++++++++++++++++++++++++++ examples/pptxgenjs-demo.layouts.html | 21 +- 2 files changed, 681 insertions(+), 3 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index d397084e1..2b51a9f1f 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -135,6 +135,7 @@ var PptxGenJS = function(){ gObjPptx.title = 'PptxGenJS Presentation'; gObjPptx.masterSlideXml = ''; gObjPptx.layoutSlideXmls = {}; + gObjPptx.layoutDefinitions = []; gObjPptx.slide2layoutMapping = []; gObjPptx.properLayoutMasterInUse = false; gObjPptx.fileName = 'Presentation'; @@ -205,6 +206,10 @@ var PptxGenJS = function(){ zip.file("ppt/slideLayouts/slideLayout"+ ( idx + 1 ) +".xml", gObjPptx.layoutSlideXmls[layouts[idx]]); zip.file("ppt/slideLayouts/_rels/slideLayout"+ ( idx + 1 ) +".xml.rels", makeXmlSlideLayoutRel( idx + 1 )); } + + for (let i = 0, len = gObjPptx.layoutDefinitions.length; i < len; i++) { + console.log(makeXmlLayout(gObjPptx.layoutDefinitions[i])); + } } // Create a Layout/Master/Rel/Slide file for each SLIDE @@ -2613,6 +2618,487 @@ var PptxGenJS = function(){ return strSlideXml; } + /** + * Generates the XML layout resource from a layout object + * @param {Object} inSlide - The slide object to transform into XML + * @return {string} strSlideXml - Slide OOXML + */ + function makeXmlLayout(inLayout) { + var intTableNum = 1; + + // STEP 1: Start slide XML + var strSlideXml = ''+CRLF; + strSlideXml += ''; + strSlideXml += ''; + + // STEP 2: Add background color or background image (if any) + // A: Background color + if ( inLayout.layout.back ) strSlideXml += genXmlColorSelection(false, inLayout.layout.back); + // B: Add background image (using Strech) (if any) + if ( inLayout.layout.bkgdImgRid ) { + // FIXME: We should be doing this in the slideLayout... + strSlideXml += '' + + '' + + '' + + '' + + '' + + ''; + } + + // STEP 3: Continue slide by starting spTree node + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += ''; + + // STEP 4: Loop over all Slide.data objects and add them to this slide =============================== + $.each(inLayout.data, function(idx, layoutObj){ + var x = 0, y = 0, cx = getSmartParseNumber('75%','X'), cy = 0; + var locationAttr = "", shapeType = null; + + // A: Set option vars + layoutObj.options = layoutObj.options || {}; + + if ( layoutObj.options.w || layoutObj.options.w == 0 ) layoutObj.options.cx = layoutObj.options.w; + if ( layoutObj.options.h || layoutObj.options.h == 0 ) layoutObj.options.cy = layoutObj.options.h; + // + if ( layoutObj.options.x || layoutObj.options.x == 0 ) x = getSmartParseNumber( layoutObj.options.x , 'X' ); + if ( layoutObj.options.y || layoutObj.options.y == 0 ) y = getSmartParseNumber( layoutObj.options.y , 'Y' ); + if ( layoutObj.options.cx || layoutObj.options.cx == 0 ) cx = getSmartParseNumber( layoutObj.options.cx, 'X' ); + if ( layoutObj.options.cy || layoutObj.options.cy == 0 ) cy = getSmartParseNumber( layoutObj.options.cy, 'Y' ); + // + if ( layoutObj.options.shape ) shapeType = getShapeInfo( layoutObj.options.shape ); + // + if ( layoutObj.options.flipH ) locationAttr += ' flipH="1"'; + if ( layoutObj.options.flipV ) locationAttr += ' flipV="1"'; + if ( layoutObj.options.rotate ) locationAttr += ' rot="' + convertRotationDegrees(layoutObj.options.rotate)+ '"'; + + // B: Add OBJECT to current Slide ---------------------------- + switch ( layoutObj.type ) { + case 'table': + // FIRST: Ensure we have rows - otherwise, bail! + if ( !layoutObj.arrTabRows || (Array.isArray(layoutObj.arrTabRows) && layoutObj.arrTabRows.length == 0) ) break; + + // Set table vars + var objTableGrid = {}; + var arrTabRows = layoutObj.arrTabRows; + var objTabOpts = layoutObj.options; + var intColCnt = 0, intColW = 0; + + // Calc number of columns + // NOTE: Cells may have a colspan, so merely taking the length of the [0] (or any other) row is not + // ....: sufficient to determine column count. Therefore, check each cell for a colspan and total cols as reqd + arrTabRows[0].forEach(function(cell,idx){ + var cellOpts = cell.options || cell.opts || null; // DEPRECATED (`opts`) + intColCnt += ( cellOpts && cellOpts.colspan ? cellOpts.colspan : 1 ); + }); + + // STEP 1: Start Table XML ============================= + // NOTE: Non-numeric cNvPr id values will trigger "presentation needs repair" type warning in MS-PPT-2013 + var strXml = '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' '; + // + ' '; + + // FIXME: Support banded rows, first/last row, etc. + // NOTE: Banding, etc. only shows when using a table style! (or set alt row color if banding) + // + + // STEP 2: Set column widths + // Evenly distribute cols/rows across size provided when applicable (calc them if only overall dimensions were provided) + // A: Col widths provided? + if ( Array.isArray(objTabOpts.colW) ) { + strXml += ''; + for ( var col=0; col'; + } + strXml += ''; + } + // B: Table Width provided without colW? Then distribute cols + else { + intColW = ( objTabOpts.colW ? objTabOpts.colW : EMU ); + if ( layoutObj.options.cx && !objTabOpts.colW ) intColW = Math.round( layoutObj.options.cx / intColCnt ); // FIX: Issue#12 + strXml += ''; + for ( var col=0; col'; } + strXml += ''; + } + + // STEP 3: Build our row arrays into an actual grid to match the XML we will be building next (ISSUE #36) + // Note row arrays can arrive "lopsided" as in row1:[1,2,3] row2:[3] when first two cols rowspan!, + // so a simple loop below in XML building wont suffice to build table right. + // We have to build an actual grid now + /* + EX: (A0:rowspan=3, B1:rowspan=2, C1:colspan=2) + + /------|------|------|------\ + | A0 | B0 | C0 | D0 | + | | B1 | C1 | | + | | | C2 | D2 | + \------|------|------|------/ + */ + $.each(arrTabRows, function(rIdx,row){ + // A: Create row if needed (recall one may be created in loop below for rowspans, so dont assume we need to create one each iteration) + if ( !objTableGrid[rIdx] ) objTableGrid[rIdx] = {}; + + // B: Loop over all cells + $(row).each(function(cIdx,cell){ + // DESIGN: NOTE: Row cell arrays can be "uneven" (diff cell count in each) due to rowspan/colspan + // Therefore, for each cell we run 0->colCount to determien the correct slot for it to reside + // as the uneven/mixed nature of the data means we cannot use the cIdx value alone. + // E.g.: the 2nd element in the row array may actually go into the 5th table grid row cell b/c of colspans! + for (var idx=0; (cIdx+idx)'; + + // C: Loop over each CELL + $.each(rowObj, function(cIdx,cell){ + // FIRST: Create cell if needed (handle [null] and other manner of junk values) + // IMPORTANT: MS-PPTX PROBLEM: using '' will cause PPT to use its own default font/size! (Arial/18 in US) + // SOLN: Pass a space instead to cement formatting options (Issue #20) + if ( typeof cell === 'undefined' || cell == null ) cell = { text:' ', options:{} }; + + // 1: "hmerge" cells are just place-holders in the table grid - skip those and go to next cell + if ( cell.hmerge ) return; + + // 2: OPTIONS: Build/set cell options (blocked for code folding) =========================== + { + var cellOpts = cell.options || cell.opts || {}; + if ( typeof cell === 'number' || typeof cell === 'string' ) cell = { text:cell.toString() }; + cellOpts.isTableCell = true; // Used to create textBody XML + cell.options = cellOpts; + + // B: Apply default values (tabOpts being used when cellOpts dont exist): + // SEE: http://officeopenxml.com/drwTableCellProperties-alignment.php + ['align','bold','border','color','fill','font_face','font_size','margin','marginPt','underline','valign'] + .forEach(function(name,idx){ + if ( objTabOpts[name] && !cellOpts[name] && cellOpts[name] != 0 ) cellOpts[name] = objTabOpts[name]; + }); + + var cellValign = (cellOpts.valign) ? ' anchor="'+ cellOpts.valign.replace(/^c$/i,'ctr').replace(/^m$/i,'ctr').replace('center','ctr').replace('middle','ctr').replace('top','t').replace('btm','b').replace('bottom','b') +'"' : ''; + var cellColspan = (cellOpts.colspan) ? ' gridSpan="'+ cellOpts.colspan +'"' : ''; + var cellRowspan = (cellOpts.rowspan) ? ' rowSpan="'+ cellOpts.rowspan +'"' : ''; + var cellFill = ((cell.optImp && cell.optImp.fill) || cellOpts.fill ) ? ' ' : ''; + var cellMargin = ( cellOpts.margin == 0 || cellOpts.margin ? cellOpts.margin : (cellOpts.marginPt || DEF_CELL_MARGIN_PT) ); + if ( !Array.isArray(cellMargin) && typeof cellMargin === 'number' ) cellMargin = [cellMargin,cellMargin,cellMargin,cellMargin]; + cellMargin = ' marL="'+ cellMargin[3]*ONEPT +'" marR="'+ cellMargin[1]*ONEPT +'" marT="'+ cellMargin[0]*ONEPT +'" marB="'+ cellMargin[2]*ONEPT +'"'; + } + + // FIXME: Cell NOWRAP property (text wrap: add to a:tcPr (horzOverflow="overflow" or whatev opts exist) + + // 3: ROWSPAN: Add dummy cells for any active rowspan + if ( cell.vmerge ) { + strXml += ''; + return; + } + + // 4: Set CELL content and properties ================================== + strXml += '' + genXmlTextBody(cell) + ''; + + // 5: Borders: Add any borders + if ( cellOpts.border && typeof cellOpts.border === 'string' ) { + strXml += ' '; + strXml += ' '; + strXml += ' '; + strXml += ' '; + } + else if ( cellOpts.border && Array.isArray(cellOpts.border) ) { + $.each([ {idx:3,name:'lnL'}, {idx:1,name:'lnR'}, {idx:0,name:'lnT'}, {idx:2,name:'lnB'} ], function(i,obj){ + if ( cellOpts.border[obj.idx] ) { + var strC = ''; + var intW = (cellOpts.border[obj.idx] && (cellOpts.border[obj.idx].pt || cellOpts.border[obj.idx].pt == 0)) ? (ONEPT * Number(cellOpts.border[obj.idx].pt)) : ONEPT; + strXml += ''+ strC +''; + } + else strXml += ''; + }); + } + else if ( cellOpts.border && typeof cellOpts.border === 'object' ) { + var intW = (cellOpts.border && (cellOpts.border.pt || cellOpts.border.pt == 0) ) ? (ONEPT * Number(cellOpts.border.pt)) : ONEPT; + var strClr = ''; + var strAttr = ''+ strClr + strAttr +''; + strXml += ''+ strClr + strAttr +''; + strXml += ''+ strClr + strAttr +''; + strXml += ''+ strClr + strAttr +''; + // *** IMPORTANT! *** LRTB order matters! + } + + // 6: Close cell Properties & Cell + strXml += cellFill; + strXml += ' '; + strXml += ' '; + + // LAST: COLSPAN: Add a 'merged' col for each column being merged (SEE: http://officeopenxml.com/drwTableGrid.php) + if ( cellOpts.colspan ) { + for (var tmp=1; tmp'; + strSlideXml += '' : '/>'); + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += '' + + (layoutObj.options.rectRadius ? '' : '') + + ''; + + // Option: FILL + strSlideXml += ( layoutObj.options.fill ? genXmlColorSelection(layoutObj.options.fill) : '' ); + + // Shape Type: LINE: line color + if ( layoutObj.options.line ) { + strSlideXml += ''; + strSlideXml += genXmlColorSelection( layoutObj.options.line ); + if ( layoutObj.options.line_dash ) strSlideXml += ''; + if ( layoutObj.options.line_head ) strSlideXml += ''; + if ( layoutObj.options.line_tail ) strSlideXml += ''; + strSlideXml += ''; + } + + // EFFECTS > SHADOW: REF: @see http://officeopenxml.com/drwSp-effects.php + if ( layoutObj.options.shadow ) { + layoutObj.options.shadow.type = ( layoutObj.options.shadow.type || 'outer' ); + layoutObj.options.shadow.blur = ( layoutObj.options.shadow.blur || 8 ) * ONEPT; + layoutObj.options.shadow.offset = ( layoutObj.options.shadow.offset || 4 ) * ONEPT; + layoutObj.options.shadow.angle = ( layoutObj.options.shadow.angle || 270 ) * 60000; + layoutObj.options.shadow.color = ( layoutObj.options.shadow.color || '000000' ); + layoutObj.options.shadow.opacity = ( layoutObj.options.shadow.opacity || 0.75 ) * 100000; + + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += '' + strSlideXml += ''; + strSlideXml += ''; + } + + /* FIXME: FUTURE: Text wrapping (copied from MS-PPTX export) + // Commented out b/c i'm not even sure this works - current code produces text that wraps in shapes and textboxes, so... + if ( layoutObj.options.textWrap ) { + strSlideXml += '' + + '' + + '' + + '' + + ''; + } + */ + + // B: Close Shape Properties + strSlideXml += ''; + + // Add formatted text + strSlideXml += genXmlTextBody(layoutObj); + + // LAST: Close SHAPE ======================================================= + strSlideXml += ''; + break; + + case 'image': + strSlideXml += ''; + strSlideXml += ' ' + strSlideXml += ' '; + if ( layoutObj.hyperlink ) strSlideXml += ''; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ''; + strSlideXml += '' + strSlideXml += ' ' + strSlideXml += ' ' + strSlideXml += ' ' + strSlideXml += ' ' + strSlideXml += ' ' + strSlideXml += ''; + strSlideXml += ''; + break; + + case 'media': + if ( layoutObj.mtype == 'online' ) { + strSlideXml += ''; + strSlideXml += ' '; + // IMPORTANT: '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; // NOTE: Preview image is required! + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ''; + } + else { + strSlideXml += ''; + strSlideXml += ' '; + // IMPORTANT: '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; // NOTE: Preview image is required! + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ''; + } + break; + + case 'chart': + strSlideXml += ''; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' ' + strSlideXml += ' ' + strSlideXml += ' ' + strSlideXml += ' ' + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ''; + break; + } + }); + + // STEP 6: Close spTree and finalize slide XML + strSlideXml += ''; + /* FIXME: Remove this in 2.0.0 (commented for 1.6.0) + strSlideXml += ''; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ''; + */ + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += ''; + + // LAST: Return + return strSlideXml; + } + function makeXmlSlideLayoutRel(inSlideNum) { var strXml = ''+CRLF; strXml += ''; @@ -2901,6 +3387,14 @@ var PptxGenJS = function(){ gObjPptx.layoutSlideXmls[name] = layoutSlideXml; }; + this.addLayoutSlideDef = function(defObj) { + if (!defObj.title) throw Error('Missing layout title.'); + //gObjPptx.layoutDefinitions[defObj.title] = defObj; + //console.log(gObjPptx.layoutDefinitions); + this.addNewLayout(defObj); + console.log(gObjPptx.layoutDefinitions); + }; + this.useProperLayoutMaster = function() { gObjPptx.properLayoutMasterInUse = true; } @@ -3666,6 +4160,175 @@ var PptxGenJS = function(){ return slideObj; }; + + /** + * Add a new layout to the Presentation + * @returns {Object[]} layoutObj + */ + this.addNewLayout = function addNewLayout(layoutDef) { + var layoutObj = {}; + var layoutNum = gObjPptx.layoutDefinitions.length; + var layoutObjNum = 0; + + // A: Add this SLIDE to PRESENTATION, Add default values as well + gObjPptx.layoutDefinitions[layoutNum] = {}; + gObjPptx.layoutDefinitions[layoutNum].layout = layoutObj; + gObjPptx.layoutDefinitions[layoutNum].name = layoutDef.title + gObjPptx.layoutDefinitions[layoutNum].data = []; + gObjPptx.layoutDefinitions[layoutNum].rels = []; + + layoutObj.addText = function( inText, options ) { + var opt = ( options && typeof options === 'object' ? options : {} ); + var text = ( inText || '' ); + if ( Array.isArray(text) && text.length == 0 ) text = ''; + + // STEP 1: Grab Slide object count + layoutObjNum = gObjPptx.layoutDefinitions[layoutNum].data.length; + + // STEP 2: Set some options + // Set color (options > inherit from Slide > default to black) + opt.color = ( opt.color || this.color || '000000' ); + + // ROBUST: Convert attr values that will likely be passed by users to valid OOXML values + if ( opt.valign ) opt.valign = opt.valign.toLowerCase().replace(/^c.*/i,'ctr').replace(/^m.*/i,'ctr').replace(/^t.*/i,'t').replace(/^b.*/i,'b'); + if ( opt.align ) opt.align = opt.align.toLowerCase().replace(/^c.*/i,'center').replace(/^m.*/i,'center').replace(/^l.*/i,'left').replace(/^r.*/i,'right'); + + // ROBUST: Set rational values for some shadow props if needed + if ( opt.shadow ) { + // OPT: `type` + if ( opt.shadow.type != 'outer' && opt.shadow.type != 'inner' ) { + console.warn('Warning: shadow.type options are `outer` or `inner`.'); + opt.shadow.type = 'outer'; + } + + // OPT: `angle` + if ( opt.shadow.angle ) { + // A: REALITY-CHECK + if ( isNaN(Number(opt.shadow.angle)) || opt.shadow.angle < 0 || opt.shadow.angle > 359 ) { + console.warn('Warning: shadow.angle can only be 0-359'); + opt.shadow.angle = 270; + } + + // B: ROBUST: Cast any type of valid arg to int: '12', 12.3, etc. -> 12 + opt.shadow.angle = Math.round(Number(opt.shadow.angle)); + } + + // OPT: `opacity` + if ( opt.shadow.opacity ) { + // A: REALITY-CHECK + if ( isNaN(Number(opt.shadow.opacity)) || opt.shadow.opacity < 0 || opt.shadow.opacity > 1 ) { + console.warn('Warning: shadow.opacity can only be 0-1'); + opt.shadow.opacity = 0.75; + } + + // B: ROBUST: Cast any type of valid arg to int: '12', 12.3, etc. -> 12 + opt.shadow.opacity = Number(opt.shadow.opacity) + } + } + + // STEP 3: Set props + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum] = {}; + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].type = 'text'; + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].text = text; + + // STEP 4: Set options + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options = opt; + if ( opt.shape && opt.shape.name == 'line' ) { + opt.line = (opt.line || '333333' ); + opt.line_size = (opt.line_size || 1 ); + } + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.bodyProp = {}; + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.bodyProp.autoFit = (opt.autoFit || false); // If true, shape will collapse to text size (Fit To Shape) + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.bodyProp.anchor = (opt.valign || 'ctr'); // VALS: [t,ctr,b] + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.lineSpacing = (opt.lineSpacing && !isNaN(opt.lineSpacing) ? opt.lineSpacing : null); + + if ( (opt.inset && !isNaN(Number(opt.inset))) || opt.inset == 0 ) { + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.bodyProp.lIns = inch2Emu(opt.inset); + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.bodyProp.rIns = inch2Emu(opt.inset); + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.bodyProp.tIns = inch2Emu(opt.inset); + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.bodyProp.bIns = inch2Emu(opt.inset); + } + + // STEP 5: Create hyperlink rels + createHyperlinkRels(text, gObjPptx.layoutDefinitions[layoutNum].rels); + + // LAST: Return + return this; + }; + + + $.each(layoutDef, function(key, val) { + + // DEPRECATED `src` is replaced by `path` in v1.5.0 + if ( key == 'bkgd' && typeof val === 'object' && (val.src || val.path || val.data) ) { + // Allow the use of only the data key (no src reqd) + val.src = val.src || val.path || null; + if (!val.src) val.src = 'preencoded.png'; + var layoutObjRels = gObjPptx.layoutDefinitions[layoutNum].rels; + var strImgExtn = val.src.substring( val.src.indexOf('.')+1 ).toLowerCase(); + if ( strImgExtn == 'jpg' ) strImgExtn = 'jpeg'; + if ( strImgExtn == 'gif' ) strImgExtn = 'png'; // MS-PPT: canvas.toDataURL for gif comes out image/png, and PPT will show "needs repair" unless we do this + // FIXME: The next few lines are copies from .addImage above. A bad idea thats already bit me once! So of course it's makred as future :) + var intRels = 1; + for ( var idx=0; idx 0 ) { + // $.each(val, function(i,image){ + // layoutObj.addImage({ + // data: (image.data || ''), + // path: (image.path || image.src || ''), + // x: inch2Emu(image.x), + // y: inch2Emu(image.y), + // w: inch2Emu(image.w || image.cx), + // h: inch2Emu(image.h || image.cy) + // }); + // }); + // } + + // Shapes **DEPRECATED** (v1.5.0) + // if ( key == "shapes" && Array.isArray(val) && val.length > 0 ) { + // $.each(val, function(i,shape){ + // // 1: Grab all options (x, y, color, etc.) + // var objOpts = {}; + // $.each(Object.keys(shape), function(i,key){ if ( shape[key] != 'type' ) objOpts[key] = shape[key]; }); + // // 2: Create object using 'type' + // if ( shape.type == 'text' ) layoutObj.addText(shape.text, objOpts); + // else if ( shape.type == 'line' ) layoutObj.addShape(gObjPptxShapes.LINE, objOpts); + // else if ( shape.type == 'rectangle' ) layoutObj.addShape(gObjPptxShapes.RECTANGLE, objOpts); + // }); + // } + + // Add all Slide Master objects in the order they were given (Issue#53) + if ( key == "objects" && Array.isArray(val) && val.length > 0 ) { + val.forEach(function(object,idx){ + var key = Object.keys(object)[0]; + if ( MASTER_OBJECTS[key] && key == 'chart' ) layoutObj.addChart( CHART_TYPES[(object.chart.type||'').toUpperCase()], object.chart.data, object.chart.opts ); + else if ( MASTER_OBJECTS[key] && key == 'image' ) layoutObj.addImage(object[key]); + else if ( MASTER_OBJECTS[key] && key == 'line' ) layoutObj.addShape(gObjPptxShapes.LINE, object[key]); + else if ( MASTER_OBJECTS[key] && key == 'rect' ) layoutObj.addShape(gObjPptxShapes.RECTANGLE, object[key]); + else if ( MASTER_OBJECTS[key] && key == 'text' ) layoutObj.addText(object[key].text, object[key].options); + }); + } + }); + + // LAST: Return this Slide + return layoutObj; + }; + /** * Reproduces an HTML table as a PowerPoint table - including column widths, style, etc. - creates 1 or more slides as needed * "Auto-Paging is the future!" --Elon Musk diff --git a/examples/pptxgenjs-demo.layouts.html b/examples/pptxgenjs-demo.layouts.html index 787ce3d64..c699ce34a 100755 --- a/examples/pptxgenjs-demo.layouts.html +++ b/examples/pptxgenjs-demo.layouts.html @@ -22,15 +22,30 @@ From 1747a429c5c904cb10d2279d304c013c8f649e67 Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Thu, 17 Aug 2017 15:09:49 +0200 Subject: [PATCH 05/27] Image support added. Only single layout works properly. Plus, a lo tof duplicated code /slide vs layout slide) --- dist/pptxgen.js | 198 ++++++++++++++++++++++----- examples/pptxgenjs-demo.layouts.html | 41 ++++-- 2 files changed, 196 insertions(+), 43 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index 9e71d278f..d9edee86d 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -220,6 +220,23 @@ var PptxGenJS = function(){ zip.file("ppt/slideMasters/_rels/slideMaster1.xml.rels", makeXmlSlideMasterRel()); // Create all Rels (images, media, chart data) + gObjPptx.layoutDefinitions.forEach(function(layout, idx){ + layout.rels.forEach(function(rel, idy) { + if ( rel.type != 'online' && rel.type != 'hyperlink' ) { + // A: Loop vars + var data = rel.data; + + // B: Users will undoubtedly pass various string formats, so modify as needed + if ( data.indexOf(',') == -1 && data.indexOf(';') == -1 ) data = 'image/png;base64,' + data; + else if ( data.indexOf(',') == -1 ) data = 'image/png;base64,' + data; + else if ( data.indexOf(';') == -1 ) data = 'image/png;' + data; + + // C: Add media + zip.file( rel.Target.replace('..','ppt'), data.split(',').pop(), {base64:true} ); + } + }); + }); + gObjPptx.slides.forEach(function(slide,idx){ slide.rels.forEach(function(rel,idy){ if ( rel.type == 'chart' ) { @@ -1977,6 +1994,12 @@ var PptxGenJS = function(){ strXml += ' '; }); }); + gObjPptx.layoutDefinitions.forEach(function(layout,idx){ + layout.rels.forEach(function(rel,idy){ + if ( rel.type != 'image' && rel.type != 'online' && rel.type != 'chart' && rel.extn != 'm4v' && strXml.indexOf(rel.type) == -1 ) + strXml += ' '; + }); + }); strXml += ' '; strXml += ' '; @@ -1989,9 +2012,16 @@ var PptxGenJS = function(){ strXml += ' '; gObjPptx.slides.forEach(function(slide,idx){ strXml += ''; - strXml += ''; strXml += ''; + if (!gObjPptx.properLayoutMasterInUse) { + strXml += ''; + } }); + if (gObjPptx.properLayoutMasterInUse) { + gObjPptx.layoutDefinitions.forEach(function(layout, idx) { + strXml += ''; + }) + } // Add charts (if any) gObjPptx.slides.forEach(function(slide,idx){ @@ -2001,6 +2031,13 @@ var PptxGenJS = function(){ } }); }); + gObjPptx.layoutDefinitions.forEach(function(layout,idx){ + layout.rels.forEach(function(rel,idy){ + if ( rel.type == 'chart' ) { + strXml += ' '; + } + }); + }); strXml += ''; @@ -3093,12 +3130,46 @@ var PptxGenJS = function(){ return strSlideXml; } - function makeXmlSlideLayoutRel(inSlideNum) { + function makeXmlSlideLayoutRel(inLayoutNum) { var strXml = ''+CRLF; strXml += ''; - //?strXml += ' '; - //strXml += ' '; + //?strXml += ' '; + //strXml += ' '; strXml += ' '; + // Add any rels for this Slide (image/audio/video/youtube/chart) + gObjPptx.layoutDefinitions[inLayoutNum - 1].rels.forEach(function(rel, idx){ + if ( rel.type.toLowerCase().indexOf('image') > -1 ) { + strXml += ''; + } + else if ( rel.type.toLowerCase().indexOf('chart') > -1 ) { + strXml += ''; + } + else if ( rel.type.toLowerCase().indexOf('audio') > -1 ) { + // As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style + if ( strXml.indexOf(' Target="'+ rel.Target +'"') > -1 ) + strXml += ''; + else + strXml += ''; + } + else if ( rel.type.toLowerCase().indexOf('video') > -1 ) { + // As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style + if ( strXml.indexOf(' Target="'+ rel.Target +'"') > -1 ) + strXml += ''; + else + strXml += ''; + } + else if ( rel.type.toLowerCase().indexOf('online') > -1 ) { + // As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style + if ( strXml.indexOf(' Target="'+ rel.Target +'"') > -1 ) + strXml += ''; + else + strXml += ''; + } + else if ( rel.type.toLowerCase().indexOf('hyperlink') > -1 ) { + strXml += ''; + } + }); + strXml += ''; // return strXml; @@ -4173,6 +4244,96 @@ var PptxGenJS = function(){ gObjPptx.layoutDefinitions[layoutNum].data = []; gObjPptx.layoutDefinitions[layoutNum].rels = []; + // WARN: DEPRECATED: Will soon take a single {object} as argument (per current docs 20161120) + // FUTURE: layoutObj.addImage = function(opt){ + // NOTE: Remote images (eg: "http://whatev.com/blah"/from web and/or remote server arent supported yet - we'd need to create an , load it, then send to canvas: https://stackoverflow.com/questions/164181/how-to-fetch-a-remote-image-to-display-in-a-canvas) + layoutObj.addImage = function( strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData ) { + var intRels = 1; + + // FIRST: Set vars for this image (object param replaces positional args in 1.1.0) + // FIXME: FUTURE: DEPRECATED: Only allow object param in 1.5 or 2.0 + if ( typeof strImagePath === 'object' ) { + intPosX = (strImagePath.x || 0); + intPosY = (strImagePath.y || 0); + intSizeX = (strImagePath.cx || strImagePath.w || 0); + intSizeY = (strImagePath.cy || strImagePath.h || 0); + objHyperlink = (strImagePath.hyperlink || ''); + strImageData = (strImagePath.data || ''); + strImagePath = (strImagePath.path || ''); // IMPORTANT: This line must be last as were about to ovewrite ourself! + } + + // REALITY-CHECK: + if ( !strImagePath && !strImageData ) { + console.error("ERROR: `addImage()` requires either 'data' or 'path' parameter!"); + return null; + } + else if ( strImageData && strImageData.toLowerCase().indexOf('base64,') == -1 ) { + console.error("ERROR: Image `data` value lacks a base64 header! Ex: 'image/png;base64,NMP[...]')"); + return null; + } + + // STEP 2: Set vars for this Slide + var layoutObjNum = gObjPptx.layoutDefinitions[layoutNum].data.length; + var layoutObjRels = gObjPptx.layoutDefinitions[layoutNum].rels; + // Every image encoded via canvas>base64 is png (as of early 2017 no browser will produce other mime types) + var strImgExtn = 'png'; + // However, pre-encoded images can be whatever mime-type they want (and good for them!) + if ( strImageData && /image\/(\w+)\;/.exec(strImageData) && /image\/(\w+)\;/.exec(strImageData).length > 0 ) { + strImgExtn = /image\/(\w+)\;/.exec(strImageData)[1]; + } + // Node.js can read/base64-encode any image, so take at face value + if ( NODEJS && strImagePath.indexOf('.') > -1 ) strImgExtn = strImagePath.split('.').pop(); + + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum] = {}; + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].type = 'image'; + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].image = (strImagePath || 'preencoded.png'); + + // STEP 3: Set image properties & options + // FIXME: Measure actual image when no intSizeX/intSizeY params passed + // ....: This is an async process: we need to make getSizeFromImage use callback, then set H/W... + // if ( !intSizeX || !intSizeY ) { var imgObj = getSizeFromImage(strImagePath); + var imgObj = { width:1, height:1 }; + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options = {}; + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.x = (intPosX || 0); + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.y = (intPosY || 0); + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.cx = (intSizeX || imgObj.width ); + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.cy = (intSizeY || imgObj.height); + + // STEP 4: Add this image to this Slide Rels (rId/rels count spans all slides! Count all images to get next rId) + // NOTE: rId starts at 2 (hence the intRels+1 below) as slideLayout.xml is rId=1! + $.each(gObjPptx.layoutDefinitions, function(i, layout){ intRels += layout.rels.length; }); + layoutObjRels.push({ + path: (strImagePath || 'preencoded'+strImgExtn), + type: 'image/'+strImgExtn, + extn: strImgExtn, + data: (strImageData || ''), + rId: (intRels+1), + Target: '../media/image'+ intRels +'.'+ strImgExtn + }); + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].imageRid = layoutObjRels[layoutObjRels.length-1].rId; + + // STEP 5: (Issue#77) Hyperlink support + if ( typeof objHyperlink === 'object' ) { + if ( !objHyperlink.url || typeof objHyperlink.url !== 'string' ) console.log("ERROR: 'hyperlink.url is required and/or should be a string'"); + else { + var intRelId = intRels+2; + + layoutObjRels.push({ + type: 'hyperlink', + data: 'dummy', + rId: intRelId, + Target: objHyperlink.url + }); + + objHyperlink.rId = intRelId; + gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].hyperlink = objHyperlink; + } + } + + // LAST: Return this layout + return this; + }; + layoutObj.addShape = function( shape, opt ) { var options = ( typeof opt === 'object' ? opt : {} ); @@ -4304,7 +4465,7 @@ var PptxGenJS = function(){ extn: strImgExtn, data: (val.data || ''), rId: (intRels+1), - Target: '../media/layout-image' + intRels + '.' + strImgExtn + Target: '../media/image' + intRels + '.' + strImgExtn }); layoutObj.bkgdImgRid = layoutObjRels[layoutObjRels.length-1].rId; } @@ -4312,33 +4473,6 @@ var PptxGenJS = function(){ layoutObj.back = val; } - // Images **DEPRECATED** (v1.5.0) - // if ( key == "images" && Array.isArray(val) && val.length > 0 ) { - // $.each(val, function(i,image){ - // layoutObj.addImage({ - // data: (image.data || ''), - // path: (image.path || image.src || ''), - // x: inch2Emu(image.x), - // y: inch2Emu(image.y), - // w: inch2Emu(image.w || image.cx), - // h: inch2Emu(image.h || image.cy) - // }); - // }); - // } - - // Shapes **DEPRECATED** (v1.5.0) - // if ( key == "shapes" && Array.isArray(val) && val.length > 0 ) { - // $.each(val, function(i,shape){ - // // 1: Grab all options (x, y, color, etc.) - // var objOpts = {}; - // $.each(Object.keys(shape), function(i,key){ if ( shape[key] != 'type' ) objOpts[key] = shape[key]; }); - // // 2: Create object using 'type' - // if ( shape.type == 'text' ) layoutObj.addText(shape.text, objOpts); - // else if ( shape.type == 'line' ) layoutObj.addShape(gObjPptxShapes.LINE, objOpts); - // else if ( shape.type == 'rectangle' ) layoutObj.addShape(gObjPptxShapes.RECTANGLE, objOpts); - // }); - // } - // Add all Slide Master objects in the order they were given (Issue#53) if ( key == "objects" && Array.isArray(val) && val.length > 0 ) { val.forEach(function(object,idx){ diff --git a/examples/pptxgenjs-demo.layouts.html b/examples/pptxgenjs-demo.layouts.html index 1e1f84b48..f90ef3e04 100755 --- a/examples/pptxgenjs-demo.layouts.html +++ b/examples/pptxgenjs-demo.layouts.html @@ -20,24 +20,43 @@ From 17889587bc8e2df2a8be75976dceba99db2c83c2 Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Fri, 18 Aug 2017 11:22:09 +0200 Subject: [PATCH 08/27] Generating text definition object moved to a seperate function. --- dist/pptxgen.js | 172 +++++++++++++++--------------------------------- 1 file changed, 52 insertions(+), 120 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index e27e33aa1..664574df7 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -152,6 +152,52 @@ var PptxGenJS = function(){ // D: Fall back to base shapes if shapes file was not linked gObjPptxShapes = ( gObjPptxShapes || this.shapes ); + // GENERATORS + + var gObjPptxGenerators = { + textDefinitionObject: function textDefinitionObject(text, options) { + var opt = ( options && typeof options === 'object' ? options : {} ); + var resultObject = {}; + var text = ( text || '' ); + if ( Array.isArray(text) && text.length == 0 ) text = ''; + + // STEP 2: Set some options + // Set color (options > inherit from Slide > default to black) + opt.color = ( opt.color || this.color || '000000' ); + + // ROBUST: Convert attr values that will likely be passed by users to valid OOXML values + if ( opt.valign ) opt.valign = opt.valign.toLowerCase().replace(/^c.*/i,'ctr').replace(/^m.*/i,'ctr').replace(/^t.*/i,'t').replace(/^b.*/i,'b'); + if ( opt.align ) opt.align = opt.align.toLowerCase().replace(/^c.*/i,'center').replace(/^m.*/i,'center').replace(/^l.*/i,'left').replace(/^r.*/i,'right'); + + // ROBUST: Set rational values for some shadow props if needed + correctShadowOptions(opt.shadow); + + // STEP 3: Set props + resultObject.type = 'text'; + resultObject.text = text; + + // STEP 4: Set options + resultObject.options = opt; + if ( opt.shape && opt.shape.name == 'line' ) { + opt.line = (opt.line || '333333' ); + opt.line_size = (opt.line_size || 1 ); + } + resultObject.options.bodyProp = {}; + resultObject.options.bodyProp.autoFit = (opt.autoFit || false); // If true, shape will collapse to text size (Fit To Shape) + resultObject.options.bodyProp.anchor = (opt.valign || 'ctr'); // VALS: [t,ctr,b] + resultObject.options.lineSpacing = (opt.lineSpacing && !isNaN(opt.lineSpacing) ? opt.lineSpacing : null); + + if ( (opt.inset && !isNaN(Number(opt.inset))) || opt.inset == 0 ) { + resultObject.options.bodyProp.lIns = inch2Emu(opt.inset); + resultObject.options.bodyProp.rIns = inch2Emu(opt.inset); + resultObject.options.bodyProp.tIns = inch2Emu(opt.inset); + resultObject.options.bodyProp.bIns = inch2Emu(opt.inset); + } + + return resultObject; + } + } + /* =============================================================================================== | # # @@ -4332,51 +4378,9 @@ var PptxGenJS = function(){ }; slideObj.addText = function( inText, options ) { - var opt = ( options && typeof options === 'object' ? options : {} ); - var text = ( inText || '' ); - if ( Array.isArray(text) && text.length == 0 ) text = ''; - - // STEP 1: Grab Slide object count - slideObjNum = gObjPptx.slides[slideNum].data.length; - - // STEP 2: Set some options - // Set color (options > inherit from Slide > default to black) - opt.color = ( opt.color || this.color || '000000' ); - - // ROBUST: Convert attr values that will likely be passed by users to valid OOXML values - if ( opt.valign ) opt.valign = opt.valign.toLowerCase().replace(/^c.*/i,'ctr').replace(/^m.*/i,'ctr').replace(/^t.*/i,'t').replace(/^b.*/i,'b'); - if ( opt.align ) opt.align = opt.align.toLowerCase().replace(/^c.*/i,'center').replace(/^m.*/i,'center').replace(/^l.*/i,'left').replace(/^r.*/i,'right'); - - // ROBUST: Set rational values for some shadow props if needed - correctShadowOptions(opt.shadow); - - // STEP 3: Set props - gObjPptx.slides[slideNum].data[slideObjNum] = {}; - gObjPptx.slides[slideNum].data[slideObjNum].type = 'text'; - gObjPptx.slides[slideNum].data[slideObjNum].text = text; - - // STEP 4: Set options - gObjPptx.slides[slideNum].data[slideObjNum].options = opt; - if ( opt.shape && opt.shape.name == 'line' ) { - opt.line = (opt.line || '333333' ); - opt.line_size = (opt.line_size || 1 ); - } - gObjPptx.slides[slideNum].data[slideObjNum].options.bodyProp = {}; - gObjPptx.slides[slideNum].data[slideObjNum].options.bodyProp.autoFit = (opt.autoFit || false); // If true, shape will collapse to text size (Fit To Shape) - gObjPptx.slides[slideNum].data[slideObjNum].options.bodyProp.anchor = (opt.valign || 'ctr'); // VALS: [t,ctr,b] - gObjPptx.slides[slideNum].data[slideObjNum].options.lineSpacing = (opt.lineSpacing && !isNaN(opt.lineSpacing) ? opt.lineSpacing : null); - - if ( (opt.inset && !isNaN(Number(opt.inset))) || opt.inset == 0 ) { - gObjPptx.slides[slideNum].data[slideObjNum].options.bodyProp.lIns = inch2Emu(opt.inset); - gObjPptx.slides[slideNum].data[slideObjNum].options.bodyProp.rIns = inch2Emu(opt.inset); - gObjPptx.slides[slideNum].data[slideObjNum].options.bodyProp.tIns = inch2Emu(opt.inset); - gObjPptx.slides[slideNum].data[slideObjNum].options.bodyProp.bIns = inch2Emu(opt.inset); - } - - // STEP 5: Create hyperlink rels - createHyperlinkRels(text, gObjPptx.slides[slideNum].rels); - - // LAST: Return + var textObj = gObjPptxGenerators.textDefinitionObject(inText, options); + gObjPptx.slides[slideNum].data.push(textObj); + createHyperlinkRels(inText || '', gObjPptx.slides[slideNum].rels); return this; }; @@ -4740,81 +4744,9 @@ var PptxGenJS = function(){ }; layoutObj.addText = function( inText, options ) { - var opt = ( options && typeof options === 'object' ? options : {} ); - var text = ( inText || '' ); - if ( Array.isArray(text) && text.length == 0 ) text = ''; - - // STEP 1: Grab Slide object count - layoutObjNum = gObjPptx.layoutDefinitions[layoutNum].data.length; - - // STEP 2: Set some options - // Set color (options > inherit from Slide > default to black) - opt.color = ( opt.color || this.color || '000000' ); - - // ROBUST: Convert attr values that will likely be passed by users to valid OOXML values - if ( opt.valign ) opt.valign = opt.valign.toLowerCase().replace(/^c.*/i,'ctr').replace(/^m.*/i,'ctr').replace(/^t.*/i,'t').replace(/^b.*/i,'b'); - if ( opt.align ) opt.align = opt.align.toLowerCase().replace(/^c.*/i,'center').replace(/^m.*/i,'center').replace(/^l.*/i,'left').replace(/^r.*/i,'right'); - - // ROBUST: Set rational values for some shadow props if needed - if ( opt.shadow ) { - // OPT: `type` - if ( opt.shadow.type != 'outer' && opt.shadow.type != 'inner' ) { - console.warn('Warning: shadow.type options are `outer` or `inner`.'); - opt.shadow.type = 'outer'; - } - - // OPT: `angle` - if ( opt.shadow.angle ) { - // A: REALITY-CHECK - if ( isNaN(Number(opt.shadow.angle)) || opt.shadow.angle < 0 || opt.shadow.angle > 359 ) { - console.warn('Warning: shadow.angle can only be 0-359'); - opt.shadow.angle = 270; - } - - // B: ROBUST: Cast any type of valid arg to int: '12', 12.3, etc. -> 12 - opt.shadow.angle = Math.round(Number(opt.shadow.angle)); - } - - // OPT: `opacity` - if ( opt.shadow.opacity ) { - // A: REALITY-CHECK - if ( isNaN(Number(opt.shadow.opacity)) || opt.shadow.opacity < 0 || opt.shadow.opacity > 1 ) { - console.warn('Warning: shadow.opacity can only be 0-1'); - opt.shadow.opacity = 0.75; - } - - // B: ROBUST: Cast any type of valid arg to int: '12', 12.3, etc. -> 12 - opt.shadow.opacity = Number(opt.shadow.opacity) - } - } - - // STEP 3: Set props - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum] = {}; - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].type = 'text'; - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].text = text; - - // STEP 4: Set options - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options = opt; - if ( opt.shape && opt.shape.name == 'line' ) { - opt.line = (opt.line || '333333' ); - opt.line_size = (opt.line_size || 1 ); - } - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.bodyProp = {}; - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.bodyProp.autoFit = (opt.autoFit || false); // If true, shape will collapse to text size (Fit To Shape) - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.bodyProp.anchor = (opt.valign || 'ctr'); // VALS: [t,ctr,b] - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.lineSpacing = (opt.lineSpacing && !isNaN(opt.lineSpacing) ? opt.lineSpacing : null); - - if ( (opt.inset && !isNaN(Number(opt.inset))) || opt.inset == 0 ) { - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.bodyProp.lIns = inch2Emu(opt.inset); - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.bodyProp.rIns = inch2Emu(opt.inset); - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.bodyProp.tIns = inch2Emu(opt.inset); - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.bodyProp.bIns = inch2Emu(opt.inset); - } - - // STEP 5: Create hyperlink rels - createHyperlinkRels(text, gObjPptx.layoutDefinitions[layoutNum].rels); - - // LAST: Return + var textObj = gObjPptxGenerators.textDefinitionObject(inText, options); + gObjPptx.layoutDefinitions[layoutNum].data.push(textObj); + createHyperlinkRels(inText || '', gObjPptx.layoutDefinitions[layoutNum].rels); return this; }; From f18785a5c96e958f145200ace13e6cf6da5b73eb Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Fri, 18 Aug 2017 13:07:03 +0200 Subject: [PATCH 09/27] Generating shape definition moved to a separated function. --- dist/pptxgen.js | 86 ++++++++++++++++++------------------------------- 1 file changed, 31 insertions(+), 55 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index 664574df7..f59977940 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -155,7 +155,7 @@ var PptxGenJS = function(){ // GENERATORS var gObjPptxGenerators = { - textDefinitionObject: function textDefinitionObject(text, options) { + textDefObject: function textDefObject(text, options) { var opt = ( options && typeof options === 'object' ? options : {} ); var resultObject = {}; var text = ( text || '' ); @@ -194,6 +194,30 @@ var PptxGenJS = function(){ resultObject.options.bodyProp.bIns = inch2Emu(opt.inset); } + return resultObject; + }, + + shapeDefObject: function shapeDefObject(shape, opt) { + var resultObject = {}; + var options = ( typeof opt === 'object' ? opt : {} ); + + if ( !shape || typeof shape !== 'object' ) { + console.log("ERROR: Missing/Invalid shape parameter! Example: `addShape(pptx.shapes.LINE, {x:1, y:1, w:1, h:1});` "); + return; + } + + + resultObject.type = 'text'; + resultObject.options = options; + options.shape = shape; + options.x = ( options.x || (options.x == 0 ? 0 : 1) ); + options.y = ( options.y || (options.y == 0 ? 0 : 1) ); + options.w = ( options.w || 1.0 ); + options.h = ( options.h || (shape.name == 'line' ? 0 : 1.0) ); + options.line = ( options.line || (shape.name == 'line' ? '333333' : null) ); + options.line_size = ( options.line_size || (shape.name == 'line' ? 1 : null) ); + if ( ['dash','dashDot','lgDash','lgDashDot','lgDashDotDot','solid','sysDash','sysDot'].indexOf(options.line_dash || '') < 0 ) options.line_dash = 'solid'; + return resultObject; } } @@ -4223,32 +4247,8 @@ var PptxGenJS = function(){ } slideObj.addShape = function( shape, opt ) { - var options = ( typeof opt === 'object' ? opt : {} ); - - if ( !shape || typeof shape !== 'object' ) { - console.log("ERROR: Missing/Invalid shape parameter! Example: `addShape(pptx.shapes.LINE, {x:1, y:1, w:1, h:1});` "); - return; - } - - // STEP 1: Grab Slide object count - slideObjNum = gObjPptx.slides[slideNum].data.length; - - // STEP 2: Set props - gObjPptx.slides[slideNum].data[slideObjNum] = {}; - gObjPptx.slides[slideNum].data[slideObjNum].type = 'text'; - gObjPptx.slides[slideNum].data[slideObjNum].options = options; - - // STEP 3: Set option defaults - options.shape = shape; - options.x = ( options.x || (options.x == 0 ? 0 : 1) ); - options.y = ( options.y || (options.y == 0 ? 0 : 1) ); - options.w = ( options.w || 1.0 ); - options.h = ( options.h || (shape.name == 'line' ? 0 : 1.0) ); - options.line = ( options.line || (shape.name == 'line' ? '333333' : null) ); - options.line_size = ( options.line_size || (shape.name == 'line' ? 1 : null) ); - if ( ['dash','dashDot','lgDash','lgDashDot','lgDashDotDot','solid','sysDash','sysDot'].indexOf(options.line_dash || '') < 0 ) options.line_dash = 'solid'; - - // LAST: Return + var shapeObject = gObjPptxGenerators.shapeDefObject(shape, opt); + gObjPptx.slides[slideNum].data.push(shapeObject); return this; }; @@ -4378,7 +4378,7 @@ var PptxGenJS = function(){ }; slideObj.addText = function( inText, options ) { - var textObj = gObjPptxGenerators.textDefinitionObject(inText, options); + var textObj = gObjPptxGenerators.textDefObject(inText, options); gObjPptx.slides[slideNum].data.push(textObj); createHyperlinkRels(inText || '', gObjPptx.slides[slideNum].rels); return this; @@ -4714,37 +4714,13 @@ var PptxGenJS = function(){ layoutObj.addShape = function( shape, opt ) { - var options = ( typeof opt === 'object' ? opt : {} ); - - if ( !shape || typeof shape !== 'object' ) { - console.log("ERROR: Missing/Invalid shape parameter! Example: `addShape(pptx.shapes.LINE, {x:1, y:1, w:1, h:1});` "); - return; - } - - // STEP 1: Grab Slide object count - layoutObjNum = gObjPptx.layoutDefinitions[layoutNum].data.length; - - // STEP 2: Set props - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum] = {}; - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].type = 'text'; - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options = options; - - // STEP 3: Set option defaults - options.shape = shape; - options.x = ( options.x || (options.x == 0 ? 0 : 1) ); - options.y = ( options.y || (options.y == 0 ? 0 : 1) ); - options.w = ( options.w || 1.0 ); - options.h = ( options.h || (shape.name == 'line' ? 0 : 1.0) ); - options.line = ( options.line || (shape.name == 'line' ? '333333' : null) ); - options.line_size = ( options.line_size || (shape.name == 'line' ? 1 : null) ); - if ( ['dash','dashDot','lgDash','lgDashDot','lgDashDotDot','solid','sysDash','sysDot'].indexOf(options.line_dash || '') < 0 ) options.line_dash = 'solid'; - - // LAST: Return + var shapeObject = gObjPptxGenerators.shapeDefObject(shape, opt); + gObjPptx.layoutDefinitions[layoutNum].data.push(shapeObject); return this; }; layoutObj.addText = function( inText, options ) { - var textObj = gObjPptxGenerators.textDefinitionObject(inText, options); + var textObj = gObjPptxGenerators.textDefObject(inText, options); gObjPptx.layoutDefinitions[layoutNum].data.push(textObj); createHyperlinkRels(inText || '', gObjPptx.layoutDefinitions[layoutNum].rels); return this; From 7d42cb48c6e221c00c6512420f9e104112b35991 Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Fri, 18 Aug 2017 13:17:36 +0200 Subject: [PATCH 10/27] More unification for shapes and texts. --- dist/pptxgen.js | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index f59977940..a38d1ed8c 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -155,7 +155,7 @@ var PptxGenJS = function(){ // GENERATORS var gObjPptxGenerators = { - textDefObject: function textDefObject(text, options) { + addTextDefinition: function addTextDefinition(text, options, target) { var opt = ( options && typeof options === 'object' ? options : {} ); var resultObject = {}; var text = ( text || '' ); @@ -194,10 +194,13 @@ var PptxGenJS = function(){ resultObject.options.bodyProp.bIns = inch2Emu(opt.inset); } + target.data.push(resultObject); + createHyperlinkRels(text || '', target.rels); + return resultObject; }, - shapeDefObject: function shapeDefObject(shape, opt) { + addShapeDefinition: function shapeDefObject(shape, opt, target) { var resultObject = {}; var options = ( typeof opt === 'object' ? opt : {} ); @@ -206,7 +209,6 @@ var PptxGenJS = function(){ return; } - resultObject.type = 'text'; resultObject.options = options; options.shape = shape; @@ -218,6 +220,7 @@ var PptxGenJS = function(){ options.line_size = ( options.line_size || (shape.name == 'line' ? 1 : null) ); if ( ['dash','dashDot','lgDash','lgDashDot','lgDashDotDot','solid','sysDash','sysDot'].indexOf(options.line_dash || '') < 0 ) options.line_dash = 'solid'; + target.data.push(resultObject); return resultObject; } } @@ -4247,8 +4250,7 @@ var PptxGenJS = function(){ } slideObj.addShape = function( shape, opt ) { - var shapeObject = gObjPptxGenerators.shapeDefObject(shape, opt); - gObjPptx.slides[slideNum].data.push(shapeObject); + gObjPptxGenerators.addShapeDefinition(shape, opt, gObjPptx.slides[slideNum]); return this; }; @@ -4377,10 +4379,8 @@ var PptxGenJS = function(){ return this; }; - slideObj.addText = function( inText, options ) { - var textObj = gObjPptxGenerators.textDefObject(inText, options); - gObjPptx.slides[slideNum].data.push(textObj); - createHyperlinkRels(inText || '', gObjPptx.slides[slideNum].rels); + slideObj.addText = function( text, options ) { + gObjPptxGenerators.addTextDefinition(text, options, gObjPptx.slides[slideNum]); return this; }; @@ -4714,15 +4714,12 @@ var PptxGenJS = function(){ layoutObj.addShape = function( shape, opt ) { - var shapeObject = gObjPptxGenerators.shapeDefObject(shape, opt); - gObjPptx.layoutDefinitions[layoutNum].data.push(shapeObject); + gObjPptxGenerators.addShapeDefinition(shape, opt, gObjPptx.layoutDefinitions[layoutNum]) return this; }; - layoutObj.addText = function( inText, options ) { - var textObj = gObjPptxGenerators.textDefObject(inText, options); - gObjPptx.layoutDefinitions[layoutNum].data.push(textObj); - createHyperlinkRels(inText || '', gObjPptx.layoutDefinitions[layoutNum].rels); + layoutObj.addText = function( text, opt ) { + gObjPptxGenerators.addTextDefinition(text, opt, gObjPptx.layoutDefinitions[layoutNum]); return this; }; From d939e33cda01446a8f4a118669f22c46677da4f1 Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Fri, 18 Aug 2017 13:44:14 +0200 Subject: [PATCH 11/27] Generating images moved to a separate function. --- dist/pptxgen.js | 255 +++++++++++++++++------------------------------- 1 file changed, 88 insertions(+), 167 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index a38d1ed8c..0ece335ce 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -142,6 +142,7 @@ var PptxGenJS = function(){ gObjPptx.pptLayout = LAYOUTS['LAYOUT_16x9']; gObjPptx.rtlMode = false; gObjPptx.slides = []; + gObjPptx.imageCounter = 0; // C: Expose shape library to clients this.charts = CHART_TYPES; @@ -200,7 +201,7 @@ var PptxGenJS = function(){ return resultObject; }, - addShapeDefinition: function shapeDefObject(shape, opt, target) { + addShapeDefinition: function addImageDefinition(shape, opt, target) { var resultObject = {}; var options = ( typeof opt === 'object' ? opt : {} ); @@ -220,6 +221,90 @@ var PptxGenJS = function(){ options.line_size = ( options.line_size || (shape.name == 'line' ? 1 : null) ); if ( ['dash','dashDot','lgDash','lgDashDot','lgDashDotDot','solid','sysDash','sysDot'].indexOf(options.line_dash || '') < 0 ) options.line_dash = 'solid'; + target.data.push(resultObject); + return resultObject; + }, + + addImageDefinition: function addImageDefinition(strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData, target) { + var intRels = ++gObjPptx.relationCounter; + var resultObject = {}; + + // FIRST: Set vars for this image (object param replaces positional args in 1.1.0) + // FIXME: FUTURE: DEPRECATED: Only allow object param in 1.5 or 2.0 + if ( typeof strImagePath === 'object' ) { + intPosX = (strImagePath.x || 0); + intPosY = (strImagePath.y || 0); + intSizeX = (strImagePath.cx || strImagePath.w || 0); + intSizeY = (strImagePath.cy || strImagePath.h || 0); + objHyperlink = (strImagePath.hyperlink || ''); + strImageData = (strImagePath.data || ''); + strImagePath = (strImagePath.path || ''); // IMPORTANT: This line must be last as were about to ovewrite ourself! + } + + // REALITY-CHECK: + if ( !strImagePath && !strImageData ) { + console.error("ERROR: `addImage()` requires either 'data' or 'path' parameter!"); + return null; + } + else if ( strImageData && strImageData.toLowerCase().indexOf('base64,') == -1 ) { + console.error("ERROR: Image `data` value lacks a base64 header! Ex: 'image/png;base64,NMP[...]')"); + return null; + } + + // STEP 2: Set vars for this Slide + var slideObjRels = target.rels; + // Every image encoded via canvas>base64 is png (as of early 2017 no browser will produce other mime types) + var strImgExtn = 'png'; + // However, pre-encoded images can be whatever mime-type they want (and good for them!) + if ( strImageData && /image\/(\w+)\;/.exec(strImageData) && /image\/(\w+)\;/.exec(strImageData).length > 0 ) { + strImgExtn = /image\/(\w+)\;/.exec(strImageData)[1]; + } + // Node.js can read/base64-encode any image, so take at face value + if ( NODEJS && strImagePath.indexOf('.') > -1 ) strImgExtn = strImagePath.split('.').pop(); + + resultObject.type = 'image'; + resultObject.image = (strImagePath || 'preencoded.png'); + + // STEP 3: Set image properties & options + // FIXME: Measure actual image when no intSizeX/intSizeY params passed + // ....: This is an async process: we need to make getSizeFromImage use callback, then set H/W... + // if ( !intSizeX || !intSizeY ) { var imgObj = getSizeFromImage(strImagePath); + var imgObj = { width:1, height:1 }; + resultObject.options = {}; + resultObject.options.x = (intPosX || 0); + resultObject.options.y = (intPosY || 0); + resultObject.options.cx = (intSizeX || imgObj.width ); + resultObject.options.cy = (intSizeY || imgObj.height); + + // STEP 4: Add this image to this Slide Rels (rId/rels count spans all slides! Count all images to get next rId) + slideObjRels.push({ + path: (strImagePath || 'preencoded'+strImgExtn), + type: 'image/'+strImgExtn, + extn: strImgExtn, + data: (strImageData || ''), + rId: slideObjRels.length + 2, + Target: '../media/image'+ (++gObjPptx.imageCounter) +'.'+ strImgExtn + }); + resultObject.imageRid = slideObjRels[slideObjRels.length-1].rId; + + // STEP 5: (Issue#77) Hyperlink support + if ( typeof objHyperlink === 'object' ) { + if ( !objHyperlink.url || typeof objHyperlink.url !== 'string' ) console.log("ERROR: 'hyperlink.url is required and/or should be a string'"); + else { + var intRelId = slideObjRels.length + 3; + + slideObjRels.push({ + type: 'hyperlink', + data: 'dummy', + rId: intRelId, + Target: objHyperlink.url + }); + + objHyperlink.rId = intRelId; + resultObject.hyperlink = objHyperlink; + } + } + target.data.push(resultObject); return resultObject; } @@ -4052,89 +4137,7 @@ var PptxGenJS = function(){ // FUTURE: slideObj.addImage = function(opt){ // NOTE: Remote images (eg: "http://whatev.com/blah"/from web and/or remote server arent supported yet - we'd need to create an , load it, then send to canvas: https://stackoverflow.com/questions/164181/how-to-fetch-a-remote-image-to-display-in-a-canvas) slideObj.addImage = function( strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData ) { - var intRels = 1; - - // FIRST: Set vars for this image (object param replaces positional args in 1.1.0) - // FIXME: FUTURE: DEPRECATED: Only allow object param in 1.5 or 2.0 - if ( typeof strImagePath === 'object' ) { - intPosX = (strImagePath.x || 0); - intPosY = (strImagePath.y || 0); - intSizeX = (strImagePath.cx || strImagePath.w || 0); - intSizeY = (strImagePath.cy || strImagePath.h || 0); - objHyperlink = (strImagePath.hyperlink || ''); - strImageData = (strImagePath.data || ''); - strImagePath = (strImagePath.path || ''); // IMPORTANT: This line must be last as were about to ovewrite ourself! - } - - // REALITY-CHECK: - if ( !strImagePath && !strImageData ) { - console.error("ERROR: `addImage()` requires either 'data' or 'path' parameter!"); - return null; - } - else if ( strImageData && strImageData.toLowerCase().indexOf('base64,') == -1 ) { - console.error("ERROR: Image `data` value lacks a base64 header! Ex: 'image/png;base64,NMP[...]')"); - return null; - } - - // STEP 2: Set vars for this Slide - var slideObjNum = gObjPptx.slides[slideNum].data.length; - var slideObjRels = gObjPptx.slides[slideNum].rels; - // Every image encoded via canvas>base64 is png (as of early 2017 no browser will produce other mime types) - var strImgExtn = 'png'; - // However, pre-encoded images can be whatever mime-type they want (and good for them!) - if ( strImageData && /image\/(\w+)\;/.exec(strImageData) && /image\/(\w+)\;/.exec(strImageData).length > 0 ) { - strImgExtn = /image\/(\w+)\;/.exec(strImageData)[1]; - } - // Node.js can read/base64-encode any image, so take at face value - if ( NODEJS && strImagePath.indexOf('.') > -1 ) strImgExtn = strImagePath.split('.').pop(); - - gObjPptx.slides[slideNum].data[slideObjNum] = {}; - gObjPptx.slides[slideNum].data[slideObjNum].type = 'image'; - gObjPptx.slides[slideNum].data[slideObjNum].image = (strImagePath || 'preencoded.png'); - - // STEP 3: Set image properties & options - // FIXME: Measure actual image when no intSizeX/intSizeY params passed - // ....: This is an async process: we need to make getSizeFromImage use callback, then set H/W... - // if ( !intSizeX || !intSizeY ) { var imgObj = getSizeFromImage(strImagePath); - var imgObj = { width:1, height:1 }; - gObjPptx.slides[slideNum].data[slideObjNum].options = {}; - gObjPptx.slides[slideNum].data[slideObjNum].options.x = (intPosX || 0); - gObjPptx.slides[slideNum].data[slideObjNum].options.y = (intPosY || 0); - gObjPptx.slides[slideNum].data[slideObjNum].options.cx = (intSizeX || imgObj.width ); - gObjPptx.slides[slideNum].data[slideObjNum].options.cy = (intSizeY || imgObj.height); - - // STEP 4: Add this image to this Slide Rels (rId/rels count spans all slides! Count all images to get next rId) - // NOTE: rId starts at 2 (hence the intRels+1 below) as slideLayout.xml is rId=1! - $.each(gObjPptx.slides, function(i,slide){ intRels += slide.rels.length; }); - slideObjRels.push({ - path: (strImagePath || 'preencoded'+strImgExtn), - type: 'image/'+strImgExtn, - extn: strImgExtn, - data: (strImageData || ''), - rId: (intRels+1), - Target: '../media/image'+ intRels +'.'+ strImgExtn - }); - gObjPptx.slides[slideNum].data[slideObjNum].imageRid = slideObjRels[slideObjRels.length-1].rId; - - // STEP 5: (Issue#77) Hyperlink support - if ( typeof objHyperlink === 'object' ) { - if ( !objHyperlink.url || typeof objHyperlink.url !== 'string' ) console.log("ERROR: 'hyperlink.url is required and/or should be a string'"); - else { - var intRelId = intRels+2; - - slideObjRels.push({ - type: 'hyperlink', - data: 'dummy', - rId: intRelId, - Target: objHyperlink.url - }); - - objHyperlink.rId = intRelId; - gObjPptx.slides[slideNum].data[slideObjNum].hyperlink = objHyperlink; - } - } - - // LAST: Return this Slide + gObjPptxGenerators.addImageDefinition(strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData, gObjPptx.slides[slideNum]); return this; }; @@ -4626,89 +4629,7 @@ var PptxGenJS = function(){ // FUTURE: layoutObj.addImage = function(opt){ // NOTE: Remote images (eg: "http://whatev.com/blah"/from web and/or remote server arent supported yet - we'd need to create an , load it, then send to canvas: https://stackoverflow.com/questions/164181/how-to-fetch-a-remote-image-to-display-in-a-canvas) layoutObj.addImage = function( strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData ) { - var intRels = 1; - - // FIRST: Set vars for this image (object param replaces positional args in 1.1.0) - // FIXME: FUTURE: DEPRECATED: Only allow object param in 1.5 or 2.0 - if ( typeof strImagePath === 'object' ) { - intPosX = (strImagePath.x || 0); - intPosY = (strImagePath.y || 0); - intSizeX = (strImagePath.cx || strImagePath.w || 0); - intSizeY = (strImagePath.cy || strImagePath.h || 0); - objHyperlink = (strImagePath.hyperlink || ''); - strImageData = (strImagePath.data || ''); - strImagePath = (strImagePath.path || ''); // IMPORTANT: This line must be last as were about to ovewrite ourself! - } - - // REALITY-CHECK: - if ( !strImagePath && !strImageData ) { - console.error("ERROR: `addImage()` requires either 'data' or 'path' parameter!"); - return null; - } - else if ( strImageData && strImageData.toLowerCase().indexOf('base64,') == -1 ) { - console.error("ERROR: Image `data` value lacks a base64 header! Ex: 'image/png;base64,NMP[...]')"); - return null; - } - - // STEP 2: Set vars for this Slide - var layoutObjNum = gObjPptx.layoutDefinitions[layoutNum].data.length; - var layoutObjRels = gObjPptx.layoutDefinitions[layoutNum].rels; - // Every image encoded via canvas>base64 is png (as of early 2017 no browser will produce other mime types) - var strImgExtn = 'png'; - // However, pre-encoded images can be whatever mime-type they want (and good for them!) - if ( strImageData && /image\/(\w+)\;/.exec(strImageData) && /image\/(\w+)\;/.exec(strImageData).length > 0 ) { - strImgExtn = /image\/(\w+)\;/.exec(strImageData)[1]; - } - // Node.js can read/base64-encode any image, so take at face value - if ( NODEJS && strImagePath.indexOf('.') > -1 ) strImgExtn = strImagePath.split('.').pop(); - - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum] = {}; - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].type = 'image'; - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].image = (strImagePath || 'preencoded.png'); - - // STEP 3: Set image properties & options - // FIXME: Measure actual image when no intSizeX/intSizeY params passed - // ....: This is an async process: we need to make getSizeFromImage use callback, then set H/W... - // if ( !intSizeX || !intSizeY ) { var imgObj = getSizeFromImage(strImagePath); - var imgObj = { width:1, height:1 }; - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options = {}; - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.x = (intPosX || 0); - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.y = (intPosY || 0); - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.cx = (intSizeX || imgObj.width ); - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options.cy = (intSizeY || imgObj.height); - - // STEP 4: Add this image to this Slide Rels (rId/rels count spans all slides! Count all images to get next rId) - // NOTE: rId starts at 2 (hence the intRels+1 below) as slideLayout.xml is rId=1! - $.each(gObjPptx.layoutDefinitions, function(i, layout){ intRels += layout.rels.length; }); - layoutObjRels.push({ - path: (strImagePath || 'preencoded'+strImgExtn), - type: 'image/'+strImgExtn, - extn: strImgExtn, - data: (strImageData || ''), - rId: (intRels+1), - Target: '../media/image'+ intRels +'.'+ strImgExtn - }); - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].imageRid = layoutObjRels[layoutObjRels.length-1].rId; - - // STEP 5: (Issue#77) Hyperlink support - if ( typeof objHyperlink === 'object' ) { - if ( !objHyperlink.url || typeof objHyperlink.url !== 'string' ) console.log("ERROR: 'hyperlink.url is required and/or should be a string'"); - else { - var intRelId = intRels+2; - - layoutObjRels.push({ - type: 'hyperlink', - data: 'dummy', - rId: intRelId, - Target: objHyperlink.url - }); - - objHyperlink.rId = intRelId; - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].hyperlink = objHyperlink; - } - } - - // LAST: Return this layout + gObjPptxGenerators.addImageDefinition(strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData, gObjPptx.layoutDefinitions[layoutNum]); return this; }; From f6092c8fe5525bd892e62e5350eb562bffe3deeb Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Fri, 18 Aug 2017 15:11:30 +0200 Subject: [PATCH 12/27] Generating charts moved to a separate function + generation relations fixed. --- dist/pptxgen.js | 397 ++++++++++++++++++------------------------------ 1 file changed, 147 insertions(+), 250 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index 0ece335ce..cc499941c 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -143,6 +143,7 @@ var PptxGenJS = function(){ gObjPptx.rtlMode = false; gObjPptx.slides = []; gObjPptx.imageCounter = 0; + gObjPptx.chartCounter = 0; // C: Expose shape library to clients this.charts = CHART_TYPES; @@ -226,8 +227,8 @@ var PptxGenJS = function(){ }, addImageDefinition: function addImageDefinition(strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData, target) { - var intRels = ++gObjPptx.relationCounter; var resultObject = {}; + var imageRelId = target.rels.length + 2; // FIRST: Set vars for this image (object param replaces positional args in 1.1.0) // FIXME: FUTURE: DEPRECATED: Only allow object param in 1.5 or 2.0 @@ -251,8 +252,6 @@ var PptxGenJS = function(){ return null; } - // STEP 2: Set vars for this Slide - var slideObjRels = target.rels; // Every image encoded via canvas>base64 is png (as of early 2017 no browser will produce other mime types) var strImgExtn = 'png'; // However, pre-encoded images can be whatever mime-type they want (and good for them!) @@ -277,23 +276,23 @@ var PptxGenJS = function(){ resultObject.options.cy = (intSizeY || imgObj.height); // STEP 4: Add this image to this Slide Rels (rId/rels count spans all slides! Count all images to get next rId) - slideObjRels.push({ + target.rels.push({ path: (strImagePath || 'preencoded'+strImgExtn), type: 'image/'+strImgExtn, extn: strImgExtn, data: (strImageData || ''), - rId: slideObjRels.length + 2, + rId: imageRelId, Target: '../media/image'+ (++gObjPptx.imageCounter) +'.'+ strImgExtn }); - resultObject.imageRid = slideObjRels[slideObjRels.length-1].rId; + resultObject.imageRid = imageRelId; // STEP 5: (Issue#77) Hyperlink support if ( typeof objHyperlink === 'object' ) { if ( !objHyperlink.url || typeof objHyperlink.url !== 'string' ) console.log("ERROR: 'hyperlink.url is required and/or should be a string'"); else { - var intRelId = slideObjRels.length + 3; + var intRelId = imageRelId + 1; - slideObjRels.push({ + target.rels.push({ type: 'hyperlink', data: 'dummy', rId: intRelId, @@ -307,6 +306,130 @@ var PptxGenJS = function(){ target.data.push(resultObject); return resultObject; + }, + + /** + * Generate the chart based on input data. + * OOXML Chart Spec: ISO/IEC 29500-1:2016(E) + * + * @param {object} type should belong to: 'column', 'pie' + * @param {object} data a JSON object with follow the following format + * @param {object} opt + * @param {object} target a slide or layout that the object should be added to + * { + * title: 'eSurvey chart', + * data: [ + * { + * name: 'Income', + * labels: ['2005', '2006', '2007', '2008', '2009'], + * values: [23.5, 26.2, 30.1, 29.5, 24.6] + * }, + * { + * name: 'Expense', + * labels: ['2005', '2006', '2007', '2008', '2009'], + * values: [18.1, 22.8, 23.9, 25.1, 25] + * } + * ] + * } + */ + addChartDefinition: function addChartDefinition(type, data, opt, target) { + var options = ( opt && typeof opt === 'object' ? opt : {} ); + var targetRels = target.rels; + var chartId = (++gObjPptx.chartCounter); + var chartRelId = target.rels.length + 2; + var resultObject = {}; + + // STEP 1: TODO: check for reqd fields, correct type, etc + // inType in CHART_TYPES + // Array.isArray(data) + /* + if ( Array.isArray(rel.data) && rel.data.length > 0 && typeof rel.data[0] === 'object' + && rel.data[0].labels && Array.isArray(rel.data[0].labels) + && rel.data[0].values && Array.isArray(rel.data[0].values) ) { + obj = rel.data[0]; + } + else { + console.warn("USAGE: addChart( 'pie', [ {name:'Sales', labels:['Jan','Feb'], values:[10,20]} ], {x:1, y:1} )"); + return; + } + */ + + // STEP 3: Set default options/decode user options + // A: Core + options.type = type.name; + options.x = (typeof options.x !== 'undefined' && options.x != null && !isNaN(options.x) ? options.x : 1); + options.y = (typeof options.y !== 'undefined' && options.y != null && !isNaN(options.y) ? options.y : 1); + options.w = (options.w || '50%'); + options.h = (options.h || '50%'); + + // B: Options: misc + if ( ['bar','col'].indexOf(options.barDir || '') < 0 ) options.barDir = 'col'; + // IMPORTANT: 'bestFit' will cause issues with PPT-Online in some cases, so defualt to 'ctr'! + if ( ['bestFit','b','ctr','inBase','inEnd','l','outEnd','r','t'].indexOf(options.dataLabelPosition || '') < 0 ) options.dataLabelPosition = (options.type == 'pie' || options.type == 'doughnut' ? 'bestFit' : 'ctr'); + if ( ['b','l','r','t','tr'].indexOf(options.legendPos || '') < 0 ) options.legendPos = 'r'; + // barGrouping: "21.2.3.17 ST_Grouping (Grouping)" + if ( ['clustered','standard','stacked','percentStacked'].indexOf(options.barGrouping || '') < 0 ) options.barGrouping = 'standard'; + if ( options.barGrouping.indexOf('tacked') > -1 ) { + options.dataLabelPosition = 'ctr'; // IMPORTANT: PPT-Online will not open Presentation when 'outEnd' etc is used on stacked! + if (!options.barGapWidthPct) options.barGapWidthPct = 50; + } + // lineDataSymbol: http://www.datypic.com/sc/ooxml/a-val-32.html + // Spec has [plus,star,x] however neither PPT2013 nor PPT-Online support them + if ( ['circle','dash','diamond','dot','none','square','triangle'].indexOf(options.lineDataSymbol || '') < 0 ) options.lineDataSymbol = 'circle'; + options.lineDataSymbolSize = ( options.lineDataSymbolSize && !isNaN(options.lineDataSymbolSize ) ? options.lineDataSymbolSize : 6 ); + + // use default lines only for y-axis if nothing specified + options.valGridLine = options.valGridLine || {}; + options.catGridLine = options.catGridLine || 'none'; + correctGridLineOptions(options.catGridLine); + correctGridLineOptions(options.valGridLine); + + if ( options.type === 'line' ) { + correctShadowOptions(options.lineShadow); + } + + // C: Options: plotArea + options.showLabel = (options.showLabel == true || options.showLabel == false ? options.showLabel : false); + options.showLegend = (options.showLegend == true || options.showLegend == false ? options.showLegend : false); + options.showPercent = (options.showPercent == true || options.showPercent == false ? options.showPercent : true ); + options.showTitle = (options.showTitle == true || options.showTitle == false ? options.showTitle : false); + options.showValue = (options.showValue == true || options.showValue == false ? options.showValue : false); + + // D: Options: chart + options.barGapWidthPct = (!isNaN(options.barGapWidthPct) && options.barGapWidthPct >= 0 && options.barGapWidthPct <= 1000 ? options.barGapWidthPct : 150); + options.chartColors = ( Array.isArray(options.chartColors) ? options.chartColors : (options.type == 'pie' || options.type == 'doughnut' ? PIECHART_COLORS : BARCHART_COLORS) ); + options.chartColorsOpacity = ( options.chartColorsOpacity && !isNaN(options.chartColorsOpacity) ? options.chartColorsOpacity : null ); + // + options.border = ( options.border && typeof options.border === 'object' ? options.border : null ); + if ( options.border && (!options.border.pt || isNaN(options.border.pt)) ) options.border.pt = 1; + if ( options.border && (!options.border.color || typeof options.border.color !== 'string' || options.border.color.length != 6) ) options.border.color = '363636'; + // + options.dataBorder = ( options.dataBorder && typeof options.dataBorder === 'object' ? options.dataBorder : null ); + if ( options.dataBorder && (!options.dataBorder.pt || isNaN(options.dataBorder.pt)) ) options.dataBorder.pt = 0.75; + if ( options.dataBorder && (!options.dataBorder.color || typeof options.dataBorder.color !== 'string' || options.dataBorder.color.length != 6) ) options.dataBorder.color = 'F9F9F9'; + // + options.dataLabelFormatCode = ( options.dataLabelFormatCode && typeof options.dataLabelFormatCode === 'string' ? options.dataLabelFormatCode : (options.type == 'pie' || options.type == 'doughnut' ? '0%' : '#,##0') ); + // + options.lineSize = ( typeof options.lineSize === 'number' ? options.lineSize : 2 ); + options.valAxisMajorUnit = ( typeof options.valAxisMajorUnit === 'number' ? options.valAxisMajorUnit : null ); + + // STEP 4: Set props + resultObject.type = 'chart'; + resultObject.options = options; + + // STEP 5: Add this chart to this Slide Rels (rId/rels count spans all slides! Count all images to get next rId) + targetRels.push({ + rId: chartRelId, + data: data, + opts: options, + type: 'chart', + fileName:'chart'+ chartId +'.xml', + Target: '/ppt/charts/chart'+ chartId +'.xml' + }); + resultObject.chartRid = chartRelId; + + target.data.push(resultObject); + return this; } } @@ -1383,6 +1506,18 @@ var PptxGenJS = function(){ } } + function correctGridLineOptions(glOpts) { + if ( !glOpts || glOpts === 'none' ) return; + if ( glOpts.size !== undefined && (isNaN(Number(glOpts.size)) || glOpts.size <= 0) ) { + console.warn('Warning: chart.gridLine.size must be greater than 0.'); + delete glOpts.size; // delete prop to used defaults + } + if ( glOpts.style && ['solid', 'dash', 'dot'].indexOf(glOpts.style) < 0 ) { + console.warn('Warning: chart.gridLine.style options: `solid`, `dash`, `dot`.'); + delete glOpts.style; + } + } + /* ======================================================================================================= | # # # # # ##### @@ -4007,129 +4142,9 @@ var PptxGenJS = function(){ * ] * } */ - slideObj.addChart = function ( inType, inData, inOpt ) { - var intRels = 1; - var options = ( inOpt && typeof inOpt === 'object' ? inOpt : {} ); - - function correctGridLineOptions(glOpts) { - if ( !glOpts || glOpts === 'none' ) return; - if ( glOpts.size !== undefined && (isNaN(Number(glOpts.size)) || glOpts.size <= 0) ) { - console.warn('Warning: chart.gridLine.size must be greater than 0.'); - delete glOpts.size; // delete prop to used defaults - } - if ( glOpts.style && ['solid', 'dash', 'dot'].indexOf(glOpts.style) < 0 ) { - console.warn('Warning: chart.gridLine.style options: `solid`, `dash`, `dot`.'); - delete glOpts.style; - } - } - - // STEP 1: TODO: check for reqd fields, correct type, etc - // inType in CHART_TYPES - // Array.isArray(inData) - /* - if ( Array.isArray(rel.data) && rel.data.length > 0 && typeof rel.data[0] === 'object' - && rel.data[0].labels && Array.isArray(rel.data[0].labels) - && rel.data[0].values && Array.isArray(rel.data[0].values) ) { - obj = rel.data[0]; - } - else { - console.warn("USAGE: addChart( 'pie', [ {name:'Sales', labels:['Jan','Feb'], values:[10,20]} ], {x:1, y:1} )"); - return; - } - */ - - // STEP 2: Set vars for this Slide - var slideObjNum = gObjPptx.slides[slideNum].data.length; - var slideObjRels = gObjPptx.slides[slideNum].rels; - - // STEP 3: Set default options/decode user options - // A: Core - options.type = inType.name; - options.x = (typeof options.x !== 'undefined' && options.x != null && !isNaN(options.x) ? options.x : 1); - options.y = (typeof options.y !== 'undefined' && options.y != null && !isNaN(options.y) ? options.y : 1); - options.w = (options.w || '50%'); - options.h = (options.h || '50%'); - // B: Options: misc - if ( ['bar','col'].indexOf(options.barDir || '') < 0 ) options.barDir = 'col'; - // IMPORTANT: 'bestFit' will cause issues with PPT-Online in some cases, so defualt to 'ctr'! - if ( ['bestFit','b','ctr','inBase','inEnd','l','outEnd','r','t'].indexOf(options.dataLabelPosition || '') < 0 ) options.dataLabelPosition = (options.type == 'pie' || options.type == 'doughnut' ? 'bestFit' : 'ctr'); - if ( ['b','l','r','t','tr'].indexOf(options.legendPos || '') < 0 ) options.legendPos = 'r'; - // barGrouping: "21.2.3.17 ST_Grouping (Grouping)" - if ( ['clustered','standard','stacked','percentStacked'].indexOf(options.barGrouping || '') < 0 ) options.barGrouping = 'standard'; - if ( options.barGrouping.indexOf('tacked') > -1 ) { - options.dataLabelPosition = 'ctr'; // IMPORTANT: PPT-Online will not open Presentation when 'outEnd' etc is used on stacked! - if (!options.barGapWidthPct) options.barGapWidthPct = 50; - } - // lineDataSymbol: http://www.datypic.com/sc/ooxml/a-val-32.html - // Spec has [plus,star,x] however neither PPT2013 nor PPT-Online support them - if ( ['circle','dash','diamond','dot','none','square','triangle'].indexOf(options.lineDataSymbol || '') < 0 ) options.lineDataSymbol = 'circle'; - options.lineDataSymbolSize = ( options.lineDataSymbolSize && !isNaN(options.lineDataSymbolSize ) ? options.lineDataSymbolSize : 6 ); - // `layout` allows the override of PPT defaults to maximize space - if ( options.layout ) { - ['x', 'y', 'w', 'h'].forEach(function(key) { - var val = options.layout[key]; - if (isNaN(Number(val)) || val < 0 || val > 1) { - console.warn('Warning: chart.layout.' + key + ' can only be 0-1'); - delete options.layout[key]; // remove invalid value so that default will be used - } - }); - } - - // use default lines only for y-axis if nothing specified - options.valGridLine = options.valGridLine || {}; - options.catGridLine = options.catGridLine || 'none'; - correctGridLineOptions(options.catGridLine); - correctGridLineOptions(options.valGridLine); - - if ( options.type === 'line' ) { - correctShadowOptions(options.lineShadow); - } - - // C: Options: plotArea - options.showLabel = (options.showLabel == true || options.showLabel == false ? options.showLabel : false); - options.showLegend = (options.showLegend == true || options.showLegend == false ? options.showLegend : false); - options.showPercent = (options.showPercent == true || options.showPercent == false ? options.showPercent : true ); - options.showTitle = (options.showTitle == true || options.showTitle == false ? options.showTitle : false); - options.showValue = (options.showValue == true || options.showValue == false ? options.showValue : false); - - // D: Options: chart - options.barGapWidthPct = (!isNaN(options.barGapWidthPct) && options.barGapWidthPct >= 0 && options.barGapWidthPct <= 1000 ? options.barGapWidthPct : 150); - options.chartColors = ( Array.isArray(options.chartColors) ? options.chartColors : (options.type == 'pie' || options.type == 'doughnut' ? PIECHART_COLORS : BARCHART_COLORS) ); - options.chartColorsOpacity = ( options.chartColorsOpacity && !isNaN(options.chartColorsOpacity) ? options.chartColorsOpacity : null ); - // - options.border = ( options.border && typeof options.border === 'object' ? options.border : null ); - if ( options.border && (!options.border.pt || isNaN(options.border.pt)) ) options.border.pt = 1; - if ( options.border && (!options.border.color || typeof options.border.color !== 'string' || options.border.color.length != 6) ) options.border.color = '363636'; - // - options.dataBorder = ( options.dataBorder && typeof options.dataBorder === 'object' ? options.dataBorder : null ); - if ( options.dataBorder && (!options.dataBorder.pt || isNaN(options.dataBorder.pt)) ) options.dataBorder.pt = 0.75; - if ( options.dataBorder && (!options.dataBorder.color || typeof options.dataBorder.color !== 'string' || options.dataBorder.color.length != 6) ) options.dataBorder.color = 'F9F9F9'; - // - options.dataLabelFormatCode = ( options.dataLabelFormatCode && typeof options.dataLabelFormatCode === 'string' ? options.dataLabelFormatCode : (options.type == 'pie' || options.type == 'doughnut' ? '0%' : '#,##0') ); - // - options.lineSize = ( typeof options.lineSize === 'number' ? options.lineSize : 2 ); - options.valAxisMajorUnit = ( typeof options.valAxisMajorUnit === 'number' ? options.valAxisMajorUnit : null ); - - // STEP 4: Set props - gObjPptx.slides[slideNum].data[slideObjNum] = {}; - gObjPptx.slides[slideNum].data[slideObjNum].type = 'chart'; - gObjPptx.slides[slideNum].data[slideObjNum].options = options; - - // STEP 5: Add this chart to this Slide Rels (rId/rels count spans all slides! Count all images to get next rId) - // NOTE: rId starts at 2 (hence the intRels+1 below) as slideLayout.xml is rId=1! - gObjPptx.slides.forEach(function(slide,idx){ intRels += slide.rels.length; }); - slideObjRels.push({ - rId: (intRels+1), - data: inData, - opts: options, - type: 'chart', - fileName:'chart'+ intRels +'.xml', - Target: '/ppt/charts/chart'+ intRels +'.xml' - }); - gObjPptx.slides[slideNum].data[slideObjNum].chartRid = slideObjRels[slideObjRels.length-1].rId; - - // LAST: Return + slideObj.addChart = function ( type, data, opt ) { + gObjPptxGenerators.addChartDefinition(type, data, opt, gObjPptx.slides[slideNum]); return this; } @@ -4502,126 +4517,8 @@ var PptxGenJS = function(){ else return gObjPptx.layoutDefinitions[layoutNum].slideNumberObj; }; - /** - * Generate the chart based on input data. - * - * OOXML Chart Spec: ISO/IEC 29500-1:2016(E) - * - * @param {object} renderType should belong to: 'column', 'pie' - * @param {object} data a JSON object with follow the following format - * { - * title: 'eSurvey chart', - * data: [ - * { - * name: 'Income', - * labels: ['2005', '2006', '2007', '2008', '2009'], - * values: [23.5, 26.2, 30.1, 29.5, 24.6] - * }, - * { - * name: 'Expense', - * labels: ['2005', '2006', '2007', '2008', '2009'], - * values: [18.1, 22.8, 23.9, 25.1, 25] - * } - * ] - * } - */ - layoutObj.addChart = function ( inType, inData, inOpt ) { - var intRels = 1; - var options = ( inOpt && typeof inOpt === 'object' ? inOpt : {} ); - - // STEP 1: TODO: check for reqd fields, correct type, etc - // inType in CHART_TYPES - // Array.isArray(inData) - /* - if ( Array.isArray(rel.data) && rel.data.length > 0 && typeof rel.data[0] === 'object' - && rel.data[0].labels && Array.isArray(rel.data[0].labels) - && rel.data[0].values && Array.isArray(rel.data[0].values) ) { - obj = rel.data[0]; - } - else { - console.warn("USAGE: addChart( 'pie', [ {name:'Sales', labels:['Jan','Feb'], values:[10,20]} ], {x:1, y:1} )"); - return; - } - */ - - // STEP 2: Set vars for this Slide - var layoutObjNum = gObjPptx.layoutDefinitions[layoutNum].data.length; - var layoutObjRels = gObjPptx.layoutDefinitions[layoutNum].rels; - - // STEP 3: Set default options/decode user options - // A: Core - options.type = inType.name; - options.x = (typeof options.x !== 'undefined' && options.x != null && !isNaN(options.x) ? options.x : 1); - options.y = (typeof options.y !== 'undefined' && options.y != null && !isNaN(options.y) ? options.y : 1); - options.w = (options.w || '50%'); - options.h = (options.h || '50%'); - - // B: Options: misc - if ( ['bar','col'].indexOf(options.barDir || '') < 0 ) options.barDir = 'col'; - // IMPORTANT: 'bestFit' will cause issues with PPT-Online in some cases, so defualt to 'ctr'! - if ( ['bestFit','b','ctr','inBase','inEnd','l','outEnd','r','t'].indexOf(options.dataLabelPosition || '') < 0 ) options.dataLabelPosition = (options.type == 'pie' || options.type == 'doughnut' ? 'bestFit' : 'ctr'); - if ( ['b','l','r','t','tr'].indexOf(options.legendPos || '') < 0 ) options.legendPos = 'r'; - // barGrouping: "21.2.3.17 ST_Grouping (Grouping)" - if ( ['clustered','standard','stacked','percentStacked'].indexOf(options.barGrouping || '') < 0 ) options.barGrouping = 'standard'; - if ( options.barGrouping.indexOf('tacked') > -1 ) { - options.dataLabelPosition = 'ctr'; // IMPORTANT: PPT-Online will not open Presentation when 'outEnd' etc is used on stacked! - if (!options.barGapWidthPct) options.barGapWidthPct = 50; - } - // lineDataSymbol: http://www.datypic.com/sc/ooxml/a-val-32.html - // Spec has [plus,star,x] however neither PPT2013 nor PPT-Online support them - if ( ['circle','dash','diamond','dot','none','square','triangle'].indexOf(options.lineDataSymbol || '') < 0 ) options.lineDataSymbol = 'circle'; - options.lineDataSymbolSize = ( options.lineDataSymbolSize && !isNaN(options.lineDataSymbolSize ) ? options.lineDataSymbolSize : 6 ); - - // use default lines only for y-axis if nothing specified - options.valGridLine = options.valGridLine || {}; - options.catGridLine = options.catGridLine || 'none'; - correctGridLineOptions(options.catGridLine); - correctGridLineOptions(options.valGridLine); - - // C: Options: plotArea - options.showLabel = (options.showLabel == true || options.showLabel == false ? options.showLabel : false); - options.showLegend = (options.showLegend == true || options.showLegend == false ? options.showLegend : false); - options.showPercent = (options.showPercent == true || options.showPercent == false ? options.showPercent : true ); - options.showTitle = (options.showTitle == true || options.showTitle == false ? options.showTitle : false); - options.showValue = (options.showValue == true || options.showValue == false ? options.showValue : false); - - // D: Options: chart - options.barGapWidthPct = (!isNaN(options.barGapWidthPct) && options.barGapWidthPct >= 0 && options.barGapWidthPct <= 1000 ? options.barGapWidthPct : 150); - options.chartColors = ( Array.isArray(options.chartColors) ? options.chartColors : (options.type == 'pie' || options.type == 'doughnut' ? PIECHART_COLORS : BARCHART_COLORS) ); - options.chartColorsOpacity = ( options.chartColorsOpacity && !isNaN(options.chartColorsOpacity) ? options.chartColorsOpacity : null ); - // - options.border = ( options.border && typeof options.border === 'object' ? options.border : null ); - if ( options.border && (!options.border.pt || isNaN(options.border.pt)) ) options.border.pt = 1; - if ( options.border && (!options.border.color || typeof options.border.color !== 'string' || options.border.color.length != 6) ) options.border.color = '363636'; - // - options.dataBorder = ( options.dataBorder && typeof options.dataBorder === 'object' ? options.dataBorder : null ); - if ( options.dataBorder && (!options.dataBorder.pt || isNaN(options.dataBorder.pt)) ) options.dataBorder.pt = 0.75; - if ( options.dataBorder && (!options.dataBorder.color || typeof options.dataBorder.color !== 'string' || options.dataBorder.color.length != 6) ) options.dataBorder.color = 'F9F9F9'; - // - options.dataLabelFormatCode = ( options.dataLabelFormatCode && typeof options.dataLabelFormatCode === 'string' ? options.dataLabelFormatCode : (options.type == 'pie' || options.type == 'doughnut' ? '0%' : '#,##0') ); - // - options.lineSize = ( typeof options.lineSize === 'number' ? options.lineSize : 2 ); - options.valAxisMajorUnit = ( typeof options.valAxisMajorUnit === 'number' ? options.valAxisMajorUnit : null ); - - // STEP 4: Set props - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum] = {}; - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].type = 'chart'; - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].options = options; - - // STEP 5: Add this chart to this Slide Rels (rId/rels count spans all slides! Count all images to get next rId) - // NOTE: rId starts at 2 (hence the intRels+1 below) as slideLayout.xml is rId=1! - gObjPptx.layoutDefinitions.forEach(function(layout,idx){ intRels += layout.rels.length; }); - layoutObjRels.push({ - rId: (intRels+1), - data: inData, - opts: options, - type: 'chart', - fileName:'chart'+ intRels +'.xml', - Target: '/ppt/charts/chart'+ intRels +'.xml' - }); - gObjPptx.layoutDefinitions[layoutNum].data[layoutObjNum].chartRid = layoutObjRels[layoutObjRels.length-1].rId; - - // LAST: Return + layoutObj.addChart = function ( type, data, opt ) { + gObjPptxGenerators.addChartDefinition(type, data, opt, gObjPptx.layoutDefinitions[layoutNum]); return this; } From 816f3a3d9a85e065dc474915f0ba96f9e1d0389b Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Fri, 18 Aug 2017 15:23:28 +0200 Subject: [PATCH 13/27] Generating background definition added to a separate function. --- dist/pptxgen.js | 79 ++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 47 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index cc499941c..11b5d7d1c 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -157,6 +157,34 @@ var PptxGenJS = function(){ // GENERATORS var gObjPptxGenerators = { + // DEPRECATED `src` is replaced by `path` in v1.5.0 + addBackgroundDefinition: function addBackgroundDefinition(bkg, target) { + if (typeof bkg === 'object' && (bkg.src || bkg.path || bkg.data) ) { + // Allow the use of only the data key (no src reqd) + bkg.src = bkg.src || bkg.path || null; + if (!bkg.src) bkg.src = 'preencoded.png'; + var targetRels = target.rels; + var strImgExtn = bkg.src.substring( bkg.src.indexOf('.')+1 ).toLowerCase(); + if ( strImgExtn == 'jpg' ) strImgExtn = 'jpeg'; + if ( strImgExtn == 'gif' ) strImgExtn = 'png'; // MS-PPT: canvas.toDataURL for gif comes out image/png, and PPT will show "needs repair" unless we do this + // FIXME: The next few lines are copies from .addImage above. A bad idea thats already bit me once! So of course it's makred as future :) + var intRels = targetRels.length + 2; + targetRels.push({ + path: bkg.src, + type: 'image/'+strImgExtn, + extn: strImgExtn, + data: (bkg.data || ''), + rId: intRels, + Target: '../media/image' + (++gObjPptx.imageCounter) + '.' + strImgExtn + }); + target.layout.bkgdImgRid = intRels; + } + else if (bkg && typeof bkg === 'string' ) { + target.layout.back = bkg; + } + + }, + addTextDefinition: function addTextDefinition(text, options, target) { var opt = ( options && typeof options === 'object' ? options : {} ); var resultObject = {}; @@ -4415,29 +4443,8 @@ var PptxGenJS = function(){ // Background color/image // DEPRECATED `src` is replaced by `path` in v1.5.0 - if ( key == 'bkgd' && typeof val === 'object' && (val.src || val.path || val.data) ) { - // Allow the use of only the data key (no src reqd) - val.src = val.src || val.path || null; - if (!val.src) val.src = 'preencoded.png'; - var slideObjRels = gObjPptx.slides[slideNum].rels; - var strImgExtn = val.src.substring( val.src.indexOf('.')+1 ).toLowerCase(); - if ( strImgExtn == 'jpg' ) strImgExtn = 'jpeg'; - if ( strImgExtn == 'gif' ) strImgExtn = 'png'; // MS-PPT: canvas.toDataURL for gif comes out image/png, and PPT will show "needs repair" unless we do this - // FIXME: The next few lines are copies from .addImage above. A bad idea thats already bit me once! So of course it's makred as future :) - var intRels = 1; - for ( var idx=0; idx Date: Mon, 21 Aug 2017 08:30:50 +0200 Subject: [PATCH 14/27] Creating a layout generalized and move to a separate function. --- dist/pptxgen.js | 79 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 16 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index 11b5d7d1c..a555c846e 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -134,6 +134,10 @@ var PptxGenJS = function(){ gObjPptx.subject = 'PptxGenJS Presentation'; gObjPptx.title = 'PptxGenJS Presentation'; gObjPptx.masterSlideXml = ''; + gObjPptx.master = { + slide: {}, + colorMap: {} + }; gObjPptx.layoutDefinitions = []; gObjPptx.slide2layoutMapping = []; gObjPptx.properLayoutMasterInUse = false; @@ -458,6 +462,29 @@ var PptxGenJS = function(){ target.data.push(resultObject); return this; + }, + + createLayoutObject: function createLayoutObject(layoutDef, target) { + if ( layoutDef.bkgd ) { + gObjPptxGenerators.addBackgroundDefinition(layoutDef.bkgd, target); + } + + // Add all Slide Master objects in the order they were given (Issue#53) + if ( layoutDef.objects && Array.isArray(layoutDef.objects) && layoutDef.objects.length > 0 ) { + layoutDef.objects.forEach(function(object, idx){ + var key = Object.keys(object)[0]; + if ( MASTER_OBJECTS[key] && key == 'chart' ) gObjPptxGenerators.addChartDefinition(CHART_TYPES[(object.chart.type||'').toUpperCase()], object.chart.data, object.chart.opts, target); + else if ( MASTER_OBJECTS[key] && key == 'image' ) gObjPptxGenerators.addImageDefinition(object[key], null, null, null, null, null, target); + else if ( MASTER_OBJECTS[key] && key == 'line' ) gObjPptxGenerators.addShapeDefinition(gObjPptxShapes.LINE, object[key], target); + else if ( MASTER_OBJECTS[key] && key == 'rect' ) gObjPptxGenerators.addShapeDefinition(gObjPptxShapes.RECTANGLE, object[key], target); + else if ( MASTER_OBJECTS[key] && key == 'text' ) gObjPptxGenerators.addTextDefinition(object[key].text, object[key].options, target); + }); + } + + // Add Slide Numbers + if ( layoutDef.slideNumber && typeof layoutDef.slideNum ) { + target.slideNumberObj = layoutDef.slideNumber; + }; } } @@ -4120,14 +4147,15 @@ var PptxGenJS = function(){ gObjPptx.slide2layoutMapping.push(inMaster); // A: Add this SLIDE to PRESENTATION, Add default values as well - gObjPptx.slides[slideNum] = {}; - gObjPptx.slides[slideNum].slide = slideObj; - gObjPptx.slides[slideNum].name = 'Slide ' + pageNum; - gObjPptx.slides[slideNum].numb = pageNum; - gObjPptx.slides[slideNum].data = []; - gObjPptx.slides[slideNum].rels = []; - gObjPptx.slides[slideNum].slideNumberObj = null; - gObjPptx.slides[slideNum].hasSlideNumber = false; // DEPRECATED + gObjPptx.slides[slideNum] = { + slide: slideObj, + name: 'Slide ' + pageNum, + numb: pageNum, + data: [], + rels: [], + slideNumberObj: null, + hasSlideNumber: false + }; // ========================================================================== // PUBLIC METHODS: @@ -4499,8 +4527,26 @@ var PptxGenJS = function(){ /** - * Add a new layout to the Presentation - * @returns {Object[]} layoutObj + * Sets a master slide and color mappings. + * @param {Object} masterDef + */ + this.setMaster = function setMaster(masterDef) { + var slideObj = { + data: [], + rels: [] + }; + + gObjPptxGenerators.createLayoutObject(masterDef.slide || {}, slideObj); + + this.master = { + slide: slideObj, + colorMap: masterDef.colorMap + }; + }; + + /** + * Add a new layout to the presentation. + * @returns {Object} layoutObj */ this.addLayoutSlide = function addNewLayoutSlide(layoutDef) { var layoutObj = {}; @@ -4512,12 +4558,13 @@ var PptxGenJS = function(){ } // A: Add this SLIDE to PRESENTATION, Add default values as well - gObjPptx.layoutDefinitions[layoutNum] = {}; - gObjPptx.layoutDefinitions[layoutNum].layout = layoutObj; - gObjPptx.layoutDefinitions[layoutNum].name = layoutDef.title - gObjPptx.layoutDefinitions[layoutNum].data = []; - gObjPptx.layoutDefinitions[layoutNum].rels = []; - gObjPptx.layoutDefinitions[layoutNum].slideNumberObj = null; + gObjPptx.layoutDefinitions[layoutNum] = { + layout: layoutObj, + name: layoutDef.title, + data: [], + rels: [], + slideNumberObj: null + }; layoutObj.slideNumber = function( inObj ) { if ( inObj && typeof inObj === 'object' ) gObjPptx.layoutDefinitions[layoutNum].slideNumberObj = inObj; From c71a74c30701f2fc396057639ce5e7245610d463 Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Mon, 21 Aug 2017 12:21:28 +0200 Subject: [PATCH 15/27] * Dynamic relations come first (images/charts), then standard non-referrenced relations are added (layouts, masters, themes) + Master slide is generated by JSON --- dist/pptxgen.js | 1406 ++++++++++++++++++++++++----------------------- 1 file changed, 719 insertions(+), 687 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index a555c846e..2e930ee51 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -124,6 +124,8 @@ var PptxGenJS = function(){ var DEF_LINE_SHADOW = { type: 'outer', blur: 3, offset: (23000 / 12700), angle: 90, color: '000000', opacity: 0.35, rotateWithShape: true }; var DEF_TEXT_SHADOW = { type: 'outer', blur: 8, offset: 4, angle: 270, color: '000000', opacity: 0.75 }; + var LAYOUT_IDX_SERIES_BASE = 2147483649; + // A: Create internal pptx object var gObjPptx = {}; @@ -133,11 +135,7 @@ var PptxGenJS = function(){ gObjPptx.revision = '1'; gObjPptx.subject = 'PptxGenJS Presentation'; gObjPptx.title = 'PptxGenJS Presentation'; - gObjPptx.masterSlideXml = ''; - gObjPptx.master = { - slide: {}, - colorMap: {} - }; + gObjPptx.masterSlide = null; gObjPptx.layoutDefinitions = []; gObjPptx.slide2layoutMapping = []; gObjPptx.properLayoutMasterInUse = false; @@ -172,7 +170,7 @@ var PptxGenJS = function(){ if ( strImgExtn == 'jpg' ) strImgExtn = 'jpeg'; if ( strImgExtn == 'gif' ) strImgExtn = 'png'; // MS-PPT: canvas.toDataURL for gif comes out image/png, and PPT will show "needs repair" unless we do this // FIXME: The next few lines are copies from .addImage above. A bad idea thats already bit me once! So of course it's makred as future :) - var intRels = targetRels.length + 2; + var intRels = targetRels.length + 1; targetRels.push({ path: bkg.src, type: 'image/'+strImgExtn, @@ -181,10 +179,10 @@ var PptxGenJS = function(){ rId: intRels, Target: '../media/image' + (++gObjPptx.imageCounter) + '.' + strImgExtn }); - target.layout.bkgdImgRid = intRels; + target.slide.bkgdImgRid = intRels; } else if (bkg && typeof bkg === 'string' ) { - target.layout.back = bkg; + target.slide.back = bkg; } }, @@ -260,7 +258,7 @@ var PptxGenJS = function(){ addImageDefinition: function addImageDefinition(strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData, target) { var resultObject = {}; - var imageRelId = target.rels.length + 2; + var imageRelId = target.rels.length + 1; // FIRST: Set vars for this image (object param replaces positional args in 1.1.0) // FIXME: FUTURE: DEPRECATED: Only allow object param in 1.5 or 2.0 @@ -368,7 +366,7 @@ var PptxGenJS = function(){ var options = ( opt && typeof opt === 'object' ? opt : {} ); var targetRels = target.rels; var chartId = (++gObjPptx.chartCounter); - var chartRelId = target.rels.length + 2; + var chartRelId = target.rels.length + 1; var resultObject = {}; // STEP 1: TODO: check for reqd fields, correct type, etc @@ -448,6 +446,7 @@ var PptxGenJS = function(){ // STEP 4: Set props resultObject.type = 'chart'; resultObject.options = options; + debugger // STEP 5: Add this chart to this Slide Rels (rId/rels count spans all slides! Count all images to get next rId) targetRels.push({ @@ -485,8 +484,554 @@ var PptxGenJS = function(){ if ( layoutDef.slideNumber && typeof layoutDef.slideNum ) { target.slideNumberObj = layoutDef.slideNumber; }; + }, + + xmlSlideLayout: function(inLayout) { + var strSlideXml = inLayout.name ? '' : ''; + + // Background color + if ( inLayout.slide.back ) { + strSlideXml += genXmlColorSelection(false, inLayout.slide.back); + } + // B: Add background image (using Strech) (if any) + if ( inLayout.slide.bkgdImgRid ) { + // FIXME: We should be doing this in the slideLayout... + strSlideXml += '' + + '' + + '' + + '' + + '' + + ''; + } + + // STEP 3: Continue slide by starting spTree node + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += ''; + + // STEP 4: Loop over all Slide.data objects and add them to this slide =============================== + $.each(inLayout.data, function(idx, layoutObj){ + var x = 0, y = 0, cx = getSmartParseNumber('75%','X'), cy = 0; + var locationAttr = "", shapeType = null; + + // A: Set option vars + layoutObj.options = layoutObj.options || {}; + + if ( layoutObj.options.w || layoutObj.options.w == 0 ) layoutObj.options.cx = layoutObj.options.w; + if ( layoutObj.options.h || layoutObj.options.h == 0 ) layoutObj.options.cy = layoutObj.options.h; + // + if ( layoutObj.options.x || layoutObj.options.x == 0 ) x = getSmartParseNumber( layoutObj.options.x , 'X' ); + if ( layoutObj.options.y || layoutObj.options.y == 0 ) y = getSmartParseNumber( layoutObj.options.y , 'Y' ); + if ( layoutObj.options.cx || layoutObj.options.cx == 0 ) cx = getSmartParseNumber( layoutObj.options.cx, 'X' ); + if ( layoutObj.options.cy || layoutObj.options.cy == 0 ) cy = getSmartParseNumber( layoutObj.options.cy, 'Y' ); + // + if ( layoutObj.options.shape ) shapeType = getShapeInfo( layoutObj.options.shape ); + // + if ( layoutObj.options.flipH ) locationAttr += ' flipH="1"'; + if ( layoutObj.options.flipV ) locationAttr += ' flipV="1"'; + if ( layoutObj.options.rotate ) locationAttr += ' rot="' + convertRotationDegrees(layoutObj.options.rotate)+ '"'; + + // B: Add OBJECT to current Slide ---------------------------- + switch ( layoutObj.type ) { + case 'table': + // FIRST: Ensure we have rows - otherwise, bail! + if ( !layoutObj.arrTabRows || (Array.isArray(layoutObj.arrTabRows) && layoutObj.arrTabRows.length == 0) ) break; + + // Set table vars + var objTableGrid = {}; + var arrTabRows = layoutObj.arrTabRows; + var objTabOpts = layoutObj.options; + var intColCnt = 0, intColW = 0; + + // Calc number of columns + // NOTE: Cells may have a colspan, so merely taking the length of the [0] (or any other) row is not + // ....: sufficient to determine column count. Therefore, check each cell for a colspan and total cols as reqd + arrTabRows[0].forEach(function(cell,idx){ + var cellOpts = cell.options || cell.opts || null; // DEPRECATED (`opts`) + intColCnt += ( cellOpts && cellOpts.colspan ? cellOpts.colspan : 1 ); + }); + + // STEP 1: Start Table XML ============================= + // NOTE: Non-numeric cNvPr id values will trigger "presentation needs repair" type warning in MS-PPT-2013 + var strXml = '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' '; + // + ' '; + + // FIXME: Support banded rows, first/last row, etc. + // NOTE: Banding, etc. only shows when using a table style! (or set alt row color if banding) + // + + // STEP 2: Set column widths + // Evenly distribute cols/rows across size provided when applicable (calc them if only overall dimensions were provided) + // A: Col widths provided? + if ( Array.isArray(objTabOpts.colW) ) { + strXml += ''; + for ( var col=0; col'; + } + strXml += ''; + } + // B: Table Width provided without colW? Then distribute cols + else { + intColW = ( objTabOpts.colW ? objTabOpts.colW : EMU ); + if ( layoutObj.options.cx && !objTabOpts.colW ) intColW = Math.round( layoutObj.options.cx / intColCnt ); // FIX: Issue#12 + strXml += ''; + for ( var col=0; col'; } + strXml += ''; + } + + // STEP 3: Build our row arrays into an actual grid to match the XML we will be building next (ISSUE #36) + // Note row arrays can arrive "lopsided" as in row1:[1,2,3] row2:[3] when first two cols rowspan!, + // so a simple loop below in XML building wont suffice to build table right. + // We have to build an actual grid now + /* + EX: (A0:rowspan=3, B1:rowspan=2, C1:colspan=2) + + /------|------|------|------\ + | A0 | B0 | C0 | D0 | + | | B1 | C1 | | + | | | C2 | D2 | + \------|------|------|------/ + */ + $.each(arrTabRows, function(rIdx,row){ + // A: Create row if needed (recall one may be created in loop below for rowspans, so dont assume we need to create one each iteration) + if ( !objTableGrid[rIdx] ) objTableGrid[rIdx] = {}; + + // B: Loop over all cells + $(row).each(function(cIdx,cell){ + // DESIGN: NOTE: Row cell arrays can be "uneven" (diff cell count in each) due to rowspan/colspan + // Therefore, for each cell we run 0->colCount to determien the correct slot for it to reside + // as the uneven/mixed nature of the data means we cannot use the cIdx value alone. + // E.g.: the 2nd element in the row array may actually go into the 5th table grid row cell b/c of colspans! + for (var idx=0; (cIdx+idx)'; + + // C: Loop over each CELL + $.each(rowObj, function(cIdx,cell){ + // FIRST: Create cell if needed (handle [null] and other manner of junk values) + // IMPORTANT: MS-PPTX PROBLEM: using '' will cause PPT to use its own default font/size! (Arial/18 in US) + // SOLN: Pass a space instead to cement formatting options (Issue #20) + if ( typeof cell === 'undefined' || cell == null ) cell = { text:' ', options:{} }; + + // 1: "hmerge" cells are just place-holders in the table grid - skip those and go to next cell + if ( cell.hmerge ) return; + + // 2: OPTIONS: Build/set cell options (blocked for code folding) =========================== + { + var cellOpts = cell.options || cell.opts || {}; + if ( typeof cell === 'number' || typeof cell === 'string' ) cell = { text:cell.toString() }; + cellOpts.isTableCell = true; // Used to create textBody XML + cell.options = cellOpts; + + // B: Apply default values (tabOpts being used when cellOpts dont exist): + // SEE: http://officeopenxml.com/drwTableCellProperties-alignment.php + ['align','bold','border','color','fill','font_face','font_size','margin','marginPt','underline','valign'] + .forEach(function(name,idx){ + if ( objTabOpts[name] && !cellOpts[name] && cellOpts[name] != 0 ) cellOpts[name] = objTabOpts[name]; + }); + + var cellValign = (cellOpts.valign) ? ' anchor="'+ cellOpts.valign.replace(/^c$/i,'ctr').replace(/^m$/i,'ctr').replace('center','ctr').replace('middle','ctr').replace('top','t').replace('btm','b').replace('bottom','b') +'"' : ''; + var cellColspan = (cellOpts.colspan) ? ' gridSpan="'+ cellOpts.colspan +'"' : ''; + var cellRowspan = (cellOpts.rowspan) ? ' rowSpan="'+ cellOpts.rowspan +'"' : ''; + var cellFill = ((cell.optImp && cell.optImp.fill) || cellOpts.fill ) ? ' ' : ''; + var cellMargin = ( cellOpts.margin == 0 || cellOpts.margin ? cellOpts.margin : (cellOpts.marginPt || DEF_CELL_MARGIN_PT) ); + if ( !Array.isArray(cellMargin) && typeof cellMargin === 'number' ) cellMargin = [cellMargin,cellMargin,cellMargin,cellMargin]; + cellMargin = ' marL="'+ cellMargin[3]*ONEPT +'" marR="'+ cellMargin[1]*ONEPT +'" marT="'+ cellMargin[0]*ONEPT +'" marB="'+ cellMargin[2]*ONEPT +'"'; + } + + // FIXME: Cell NOWRAP property (text wrap: add to a:tcPr (horzOverflow="overflow" or whatev opts exist) + + // 3: ROWSPAN: Add dummy cells for any active rowspan + if ( cell.vmerge ) { + strXml += ''; + return; + } + + // 4: Set CELL content and properties ================================== + strXml += '' + genXmlTextBody(cell) + ''; + + // 5: Borders: Add any borders + if ( cellOpts.border && typeof cellOpts.border === 'string' ) { + strXml += ' '; + strXml += ' '; + strXml += ' '; + strXml += ' '; + } + else if ( cellOpts.border && Array.isArray(cellOpts.border) ) { + $.each([ {idx:3,name:'lnL'}, {idx:1,name:'lnR'}, {idx:0,name:'lnT'}, {idx:2,name:'lnB'} ], function(i,obj){ + if ( cellOpts.border[obj.idx] ) { + var strC = ''; + var intW = (cellOpts.border[obj.idx] && (cellOpts.border[obj.idx].pt || cellOpts.border[obj.idx].pt == 0)) ? (ONEPT * Number(cellOpts.border[obj.idx].pt)) : ONEPT; + strXml += ''+ strC +''; + } + else strXml += ''; + }); + } + else if ( cellOpts.border && typeof cellOpts.border === 'object' ) { + var intW = (cellOpts.border && (cellOpts.border.pt || cellOpts.border.pt == 0) ) ? (ONEPT * Number(cellOpts.border.pt)) : ONEPT; + var strClr = ''; + var strAttr = ''+ strClr + strAttr +''; + strXml += ''+ strClr + strAttr +''; + strXml += ''+ strClr + strAttr +''; + strXml += ''+ strClr + strAttr +''; + // *** IMPORTANT! *** LRTB order matters! + } + + // 6: Close cell Properties & Cell + strXml += cellFill; + strXml += ' '; + strXml += ' '; + + // LAST: COLSPAN: Add a 'merged' col for each column being merged (SEE: http://officeopenxml.com/drwTableGrid.php) + if ( cellOpts.colspan ) { + for (var tmp=1; tmp'; + strSlideXml += '' : '/>'); + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += '' + + (layoutObj.options.rectRadius ? '' : '') + + ''; + + // Option: FILL + strSlideXml += ( layoutObj.options.fill ? genXmlColorSelection(layoutObj.options.fill) : '' ); + + // Shape Type: LINE: line color + if ( layoutObj.options.line ) { + strSlideXml += ''; + strSlideXml += genXmlColorSelection( layoutObj.options.line ); + if ( layoutObj.options.line_dash ) strSlideXml += ''; + if ( layoutObj.options.line_head ) strSlideXml += ''; + if ( layoutObj.options.line_tail ) strSlideXml += ''; + strSlideXml += ''; + } + + // EFFECTS > SHADOW: REF: @see http://officeopenxml.com/drwSp-effects.php + if ( layoutObj.options.shadow ) { + layoutObj.options.shadow.type = ( layoutObj.options.shadow.type || 'outer' ); + layoutObj.options.shadow.blur = ( layoutObj.options.shadow.blur || 8 ) * ONEPT; + layoutObj.options.shadow.offset = ( layoutObj.options.shadow.offset || 4 ) * ONEPT; + layoutObj.options.shadow.angle = ( layoutObj.options.shadow.angle || 270 ) * 60000; + layoutObj.options.shadow.color = ( layoutObj.options.shadow.color || '000000' ); + layoutObj.options.shadow.opacity = ( layoutObj.options.shadow.opacity || 0.75 ) * 100000; + + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += '' + strSlideXml += ''; + strSlideXml += ''; + } + + /* FIXME: FUTURE: Text wrapping (copied from MS-PPTX export) + // Commented out b/c i'm not even sure this works - current code produces text that wraps in shapes and textboxes, so... + if ( layoutObj.options.textWrap ) { + strSlideXml += '' + + '' + + '' + + '' + + ''; + } + */ + + // B: Close Shape Properties + strSlideXml += ''; + + // Add formatted text + strSlideXml += genXmlTextBody(layoutObj); + + // LAST: Close SHAPE ======================================================= + strSlideXml += ''; + break; + + case 'image': + strSlideXml += ''; + strSlideXml += ' ' + strSlideXml += ' '; + if ( layoutObj.hyperlink ) strSlideXml += ''; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ''; + strSlideXml += '' + strSlideXml += ' ' + strSlideXml += ' ' + strSlideXml += ' ' + strSlideXml += ' ' + strSlideXml += ' ' + strSlideXml += ''; + strSlideXml += ''; + break; + + case 'media': + if ( layoutObj.mtype == 'online' ) { + strSlideXml += ''; + strSlideXml += ' '; + // IMPORTANT: '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; // NOTE: Preview image is required! + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ''; + } + else { + strSlideXml += ''; + strSlideXml += ' '; + // IMPORTANT: '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; // NOTE: Preview image is required! + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ''; + } + break; + + case 'chart': + strSlideXml += ''; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' ' + strSlideXml += ' ' + strSlideXml += ' ' + strSlideXml += ' ' + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ''; + break; + } + }); + + // STEP 5: Add slide numbers last (if any) + if ( inLayout.slideNumberObj ) { + + // TODO: FIXME: page numbers over 99 wrap in PPT-2013 (Desktop) + strSlideXml += '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' '; + // ISSUE #68: "Page number styling" + strSlideXml += ''; + strSlideXml += ' '; + strSlideXml += ' '; + if ( inLayout.slideNumberObj.fontFace || inLayout.slideNumberObj.fontSize || inLayout.slideNumberObj.color ) { + strSlideXml += ''; + if ( inLayout.slideNumberObj.color ) strSlideXml += genXmlColorSelection(inLayout.slideNumberObj.color); + if ( inLayout.slideNumberObj.fontFace ) strSlideXml += ''; + strSlideXml += ''; + } + strSlideXml += ''; + strSlideXml += '' + strSlideXml += ''; + } + + // STEP 6: Close spTree and finalize slide XML + strSlideXml += ''; + /* FIXME: Remove this in 2.0.0 (commented for 1.6.0) + strSlideXml += ''; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ' '; + strSlideXml += ''; + */ + strSlideXml += ''; + + // LAST: Return + return strSlideXml; + }, + + xmlSlideLayoutRelations: function(inLayout, defaults) { + var lastRid = 0; + var strXml = '' + CRLF; + strXml += ''; + // Add any rels for this Slide (image/audio/video/youtube/chart) + inLayout.rels.forEach(function(rel, idx){ + lastRid = Math.max(lastRid, rel.rId); + if ( rel.type.toLowerCase().indexOf('image') > -1 ) { + strXml += ''; + } + else if ( rel.type.toLowerCase().indexOf('chart') > -1 ) { + strXml += ''; + } + else if ( rel.type.toLowerCase().indexOf('audio') > -1 ) { + // As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style + if ( strXml.indexOf(' Target="'+ rel.Target +'"') > -1 ) + strXml += ''; + else + strXml += ''; + } + else if ( rel.type.toLowerCase().indexOf('video') > -1 ) { + // As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style + if ( strXml.indexOf(' Target="'+ rel.Target +'"') > -1 ) + strXml += ''; + else + strXml += ''; + } + else if ( rel.type.toLowerCase().indexOf('online') > -1 ) { + // As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style + if ( strXml.indexOf(' Target="'+ rel.Target +'"') > -1 ) + strXml += ''; + else + strXml += ''; + } + else if ( rel.type.toLowerCase().indexOf('hyperlink') > -1 ) { + strXml += ''; + } + }); + + defaults.forEach(function(rel, idx) { + strXml += ''; + }); + + strXml += ''; + return strXml; + } } - } /* =============================================================================================== | @@ -552,9 +1097,15 @@ var PptxGenJS = function(){ zip.file("ppt/slideLayouts/_rels/slideLayout"+ intSlideNum +".xml.rels", makeXmlSlideLayoutRel( intSlideNum )); } } - zip.file("ppt/slideMasters/slideMaster1.xml", makeXmlSlideMaster()); - zip.file("ppt/slideMasters/_rels/slideMaster1.xml.rels", makeXmlSlideMasterRel()); + if (gObjPptx.properLayoutMasterInUse && gObjPptx.masterSlide) { + zip.file("ppt/slideMasters/slideMaster1.xml", makeXmlMaster(gObjPptx.masterSlide)); + zip.file("ppt/slideMasters/_rels/slideMaster1.xml.rels", makeXmlMasterRel(gObjPptx.masterSlide)); + } + else { + zip.file("ppt/slideMasters/slideMaster1.xml", makeXmlSlideMaster()); + zip.file("ppt/slideMasters/_rels/slideMaster1.xml.rels", makeXmlSlideMasterRel()); + } // Create all Rels (images, media, chart data) gObjPptx.layoutDefinitions.forEach(function(layout, idx){ layout.rels.forEach(function(rel, idy) { @@ -3193,681 +3744,168 @@ var PptxGenJS = function(){ /** * Generates the XML layout resource from a layout object - * @param {Object} inSlide - The slide object to transform into XML + * @param {olbject} inLayout - The slide object to transform into XML * @return {string} strSlideXml - Slide OOXML */ function makeXmlLayout(inLayout) { - var intTableNum = 1; + var strSlideXml = gObjPptxGenerators.xmlSlideLayout(inLayout); + var strXml = '' + CRLF; + strXml += ''; + strXml += strSlideXml; + strXml += ''; + strXml += ''; + return strXml; + } - // STEP 1: Start slide XML - var strSlideXml = ''+CRLF; - strSlideXml += ''; - strSlideXml += ''; + /** + * Generates the XML master resource. + * @param {object} inMaster - The master slide object to transform into XML + * @return {string} Slide OOXML + */ + function makeXmlMaster(inMasterSlide) { + var strSlideXml = gObjPptxGenerators.xmlSlideLayout(inMasterSlide); + var layoutDefs = gObjPptx.layoutDefinitions.map(function(layoutDef, idx) { + return ''; + }); + var strXml = '' + CRLF; + strXml += ''; + strXml += strSlideXml; + strXml += '' + strXml += '' + layoutDefs.join('') + ''; + strXml += '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + '' + strXml += ''; + return strXml; + } - // STEP 2: Add background color or background image (if any) - // A: Background color - if ( inLayout.layout.back ) strSlideXml += genXmlColorSelection(false, inLayout.layout.back); - // B: Add background image (using Strech) (if any) - if ( inLayout.layout.bkgdImgRid ) { - // FIXME: We should be doing this in the slideLayout... - strSlideXml += '' - + '' - + '' - + '' - + '' - + ''; - } + function makeXmlSlideLayoutRel(inLayoutNum) { + return gObjPptxGenerators.xmlSlideLayoutRelations( + gObjPptx.properLayoutMasterInUse ? gObjPptx.layoutDefinitions[inLayoutNum - 1] : gObjPptx.slides[inLayoutNum - 1], + [{ target: "../slideMasters/slideMaster1.xml", type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"}] + ); + } - // STEP 3: Continue slide by starting spTree node - strSlideXml += ''; - strSlideXml += ''; - strSlideXml += ''; - strSlideXml += ''; + function makeXmlSlideRel(inSlideNum) { + return gObjPptxGenerators.xmlSlideLayoutRelations( + gObjPptx.slides[inSlideNum - 1], + [{ target: '../slideLayouts/slideLayout'+ ( gObjPptx.properLayoutMasterInUse ? getLayoutIdxForSlide(inSlideNum) : inSlideNum ) +'.xml', type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout"}] + ); + } - // STEP 4: Loop over all Slide.data objects and add them to this slide =============================== - $.each(inLayout.data, function(idx, layoutObj){ - var x = 0, y = 0, cx = getSmartParseNumber('75%','X'), cy = 0; - var locationAttr = "", shapeType = null; + function makeXmlMasterRel(inMasterSlide) { + var relCount = inMasterSlide.rels.length + var defaultRels = gObjPptx.layoutDefinitions.map(function(layoutDef, idx) { + return { target: '../slideLayouts/slideLayout'+ (idx + 1) +'.xml', type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout' }; + }); + defaultRels.push({ target: '../theme/theme1.xml', type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme' }); - // A: Set option vars - layoutObj.options = layoutObj.options || {}; + return gObjPptxGenerators.xmlSlideLayoutRelations( + inMasterSlide, + defaultRels + ); + } - if ( layoutObj.options.w || layoutObj.options.w == 0 ) layoutObj.options.cx = layoutObj.options.w; - if ( layoutObj.options.h || layoutObj.options.h == 0 ) layoutObj.options.cy = layoutObj.options.h; - // - if ( layoutObj.options.x || layoutObj.options.x == 0 ) x = getSmartParseNumber( layoutObj.options.x , 'X' ); - if ( layoutObj.options.y || layoutObj.options.y == 0 ) y = getSmartParseNumber( layoutObj.options.y , 'Y' ); - if ( layoutObj.options.cx || layoutObj.options.cx == 0 ) cx = getSmartParseNumber( layoutObj.options.cx, 'X' ); - if ( layoutObj.options.cy || layoutObj.options.cy == 0 ) cy = getSmartParseNumber( layoutObj.options.cy, 'Y' ); - // - if ( layoutObj.options.shape ) shapeType = getShapeInfo( layoutObj.options.shape ); - // - if ( layoutObj.options.flipH ) locationAttr += ' flipH="1"'; - if ( layoutObj.options.flipV ) locationAttr += ' flipV="1"'; - if ( layoutObj.options.rotate ) locationAttr += ' rot="' + convertRotationDegrees(layoutObj.options.rotate)+ '"'; + function getLayoutIdxForSlide(inSlideNum) { + var layoutName = gObjPptx.slide2layoutMapping[inSlideNum - 1], + layoutIdx; + for (var i = 0, len = gObjPptx.layoutDefinitions.length; i < len; i++) { + if (gObjPptx.layoutDefinitions[i].name === layoutName) { + return i + 1; + } + } + throw Error('Layout "' + layoutName + '" is not specified in this presentation.'); + } - // B: Add OBJECT to current Slide ---------------------------- - switch ( layoutObj.type ) { - case 'table': - // FIRST: Ensure we have rows - otherwise, bail! - if ( !layoutObj.arrTabRows || (Array.isArray(layoutObj.arrTabRows) && layoutObj.arrTabRows.length == 0) ) break; + function makeXmlSlideMaster() { + var intSlideLayoutId = 2147483649; + var strXml = ''+CRLF + + '' + + ' ' + + 'Click to edit Master title style' + + 'Click to edit Master text stylesSecond levelThird levelFourth levelFifth level' + + '12/25/2015' + + '' + + '' + + ''; + // Create a sldLayout for each SLIDE + for ( var idx=1; idx<=gObjPptx.slides.length; idx++ ) { + strXml += ' '; + intSlideLayoutId++; + } + strXml += '' + + '' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + '' + + ''; + // + return strXml; + } - // Set table vars - var objTableGrid = {}; - var arrTabRows = layoutObj.arrTabRows; - var objTabOpts = layoutObj.options; - var intColCnt = 0, intColW = 0; + function makeXmlSlideMasterRel() { + // FIXME: create a slideLayout for each SLDIE + var strXml = ''+CRLF + + ''; + for ( var idx=1; idx<=gObjPptx.slides.length; idx++ ) { + strXml += ' '; + } + strXml += ' '; + strXml += ''; + // + return strXml; + } - // Calc number of columns - // NOTE: Cells may have a colspan, so merely taking the length of the [0] (or any other) row is not - // ....: sufficient to determine column count. Therefore, check each cell for a colspan and total cols as reqd - arrTabRows[0].forEach(function(cell,idx){ - var cellOpts = cell.options || cell.opts || null; // DEPRECATED (`opts`) - intColCnt += ( cellOpts && cellOpts.colspan ? cellOpts.colspan : 1 ); - }); - - // STEP 1: Start Table XML ============================= - // NOTE: Non-numeric cNvPr id values will trigger "presentation needs repair" type warning in MS-PPT-2013 - var strXml = '' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' '; - // + ' '; - - // FIXME: Support banded rows, first/last row, etc. - // NOTE: Banding, etc. only shows when using a table style! (or set alt row color if banding) - // - - // STEP 2: Set column widths - // Evenly distribute cols/rows across size provided when applicable (calc them if only overall dimensions were provided) - // A: Col widths provided? - if ( Array.isArray(objTabOpts.colW) ) { - strXml += ''; - for ( var col=0; col'; - } - strXml += ''; - } - // B: Table Width provided without colW? Then distribute cols - else { - intColW = ( objTabOpts.colW ? objTabOpts.colW : EMU ); - if ( layoutObj.options.cx && !objTabOpts.colW ) intColW = Math.round( layoutObj.options.cx / intColCnt ); // FIX: Issue#12 - strXml += ''; - for ( var col=0; col'; } - strXml += ''; - } - - // STEP 3: Build our row arrays into an actual grid to match the XML we will be building next (ISSUE #36) - // Note row arrays can arrive "lopsided" as in row1:[1,2,3] row2:[3] when first two cols rowspan!, - // so a simple loop below in XML building wont suffice to build table right. - // We have to build an actual grid now - /* - EX: (A0:rowspan=3, B1:rowspan=2, C1:colspan=2) - - /------|------|------|------\ - | A0 | B0 | C0 | D0 | - | | B1 | C1 | | - | | | C2 | D2 | - \------|------|------|------/ - */ - $.each(arrTabRows, function(rIdx,row){ - // A: Create row if needed (recall one may be created in loop below for rowspans, so dont assume we need to create one each iteration) - if ( !objTableGrid[rIdx] ) objTableGrid[rIdx] = {}; - - // B: Loop over all cells - $(row).each(function(cIdx,cell){ - // DESIGN: NOTE: Row cell arrays can be "uneven" (diff cell count in each) due to rowspan/colspan - // Therefore, for each cell we run 0->colCount to determien the correct slot for it to reside - // as the uneven/mixed nature of the data means we cannot use the cIdx value alone. - // E.g.: the 2nd element in the row array may actually go into the 5th table grid row cell b/c of colspans! - for (var idx=0; (cIdx+idx)'; - - // C: Loop over each CELL - $.each(rowObj, function(cIdx,cell){ - // FIRST: Create cell if needed (handle [null] and other manner of junk values) - // IMPORTANT: MS-PPTX PROBLEM: using '' will cause PPT to use its own default font/size! (Arial/18 in US) - // SOLN: Pass a space instead to cement formatting options (Issue #20) - if ( typeof cell === 'undefined' || cell == null ) cell = { text:' ', options:{} }; - - // 1: "hmerge" cells are just place-holders in the table grid - skip those and go to next cell - if ( cell.hmerge ) return; - - // 2: OPTIONS: Build/set cell options (blocked for code folding) =========================== - { - var cellOpts = cell.options || cell.opts || {}; - if ( typeof cell === 'number' || typeof cell === 'string' ) cell = { text:cell.toString() }; - cellOpts.isTableCell = true; // Used to create textBody XML - cell.options = cellOpts; - - // B: Apply default values (tabOpts being used when cellOpts dont exist): - // SEE: http://officeopenxml.com/drwTableCellProperties-alignment.php - ['align','bold','border','color','fill','font_face','font_size','margin','marginPt','underline','valign'] - .forEach(function(name,idx){ - if ( objTabOpts[name] && !cellOpts[name] && cellOpts[name] != 0 ) cellOpts[name] = objTabOpts[name]; - }); - - var cellValign = (cellOpts.valign) ? ' anchor="'+ cellOpts.valign.replace(/^c$/i,'ctr').replace(/^m$/i,'ctr').replace('center','ctr').replace('middle','ctr').replace('top','t').replace('btm','b').replace('bottom','b') +'"' : ''; - var cellColspan = (cellOpts.colspan) ? ' gridSpan="'+ cellOpts.colspan +'"' : ''; - var cellRowspan = (cellOpts.rowspan) ? ' rowSpan="'+ cellOpts.rowspan +'"' : ''; - var cellFill = ((cell.optImp && cell.optImp.fill) || cellOpts.fill ) ? ' ' : ''; - var cellMargin = ( cellOpts.margin == 0 || cellOpts.margin ? cellOpts.margin : (cellOpts.marginPt || DEF_CELL_MARGIN_PT) ); - if ( !Array.isArray(cellMargin) && typeof cellMargin === 'number' ) cellMargin = [cellMargin,cellMargin,cellMargin,cellMargin]; - cellMargin = ' marL="'+ cellMargin[3]*ONEPT +'" marR="'+ cellMargin[1]*ONEPT +'" marT="'+ cellMargin[0]*ONEPT +'" marB="'+ cellMargin[2]*ONEPT +'"'; - } - - // FIXME: Cell NOWRAP property (text wrap: add to a:tcPr (horzOverflow="overflow" or whatev opts exist) - - // 3: ROWSPAN: Add dummy cells for any active rowspan - if ( cell.vmerge ) { - strXml += ''; - return; - } - - // 4: Set CELL content and properties ================================== - strXml += '' + genXmlTextBody(cell) + ''; - - // 5: Borders: Add any borders - if ( cellOpts.border && typeof cellOpts.border === 'string' ) { - strXml += ' '; - strXml += ' '; - strXml += ' '; - strXml += ' '; - } - else if ( cellOpts.border && Array.isArray(cellOpts.border) ) { - $.each([ {idx:3,name:'lnL'}, {idx:1,name:'lnR'}, {idx:0,name:'lnT'}, {idx:2,name:'lnB'} ], function(i,obj){ - if ( cellOpts.border[obj.idx] ) { - var strC = ''; - var intW = (cellOpts.border[obj.idx] && (cellOpts.border[obj.idx].pt || cellOpts.border[obj.idx].pt == 0)) ? (ONEPT * Number(cellOpts.border[obj.idx].pt)) : ONEPT; - strXml += ''+ strC +''; - } - else strXml += ''; - }); - } - else if ( cellOpts.border && typeof cellOpts.border === 'object' ) { - var intW = (cellOpts.border && (cellOpts.border.pt || cellOpts.border.pt == 0) ) ? (ONEPT * Number(cellOpts.border.pt)) : ONEPT; - var strClr = ''; - var strAttr = ''+ strClr + strAttr +''; - strXml += ''+ strClr + strAttr +''; - strXml += ''+ strClr + strAttr +''; - strXml += ''+ strClr + strAttr +''; - // *** IMPORTANT! *** LRTB order matters! - } - - // 6: Close cell Properties & Cell - strXml += cellFill; - strXml += ' '; - strXml += ' '; - - // LAST: COLSPAN: Add a 'merged' col for each column being merged (SEE: http://officeopenxml.com/drwTableGrid.php) - if ( cellOpts.colspan ) { - for (var tmp=1; tmp'; - strSlideXml += '' : '/>'); - strSlideXml += ''; - strSlideXml += ''; - strSlideXml += ''; - strSlideXml += ''; - strSlideXml += '' - + (layoutObj.options.rectRadius ? '' : '') - + ''; - - // Option: FILL - strSlideXml += ( layoutObj.options.fill ? genXmlColorSelection(layoutObj.options.fill) : '' ); - - // Shape Type: LINE: line color - if ( layoutObj.options.line ) { - strSlideXml += ''; - strSlideXml += genXmlColorSelection( layoutObj.options.line ); - if ( layoutObj.options.line_dash ) strSlideXml += ''; - if ( layoutObj.options.line_head ) strSlideXml += ''; - if ( layoutObj.options.line_tail ) strSlideXml += ''; - strSlideXml += ''; - } - - // EFFECTS > SHADOW: REF: @see http://officeopenxml.com/drwSp-effects.php - if ( layoutObj.options.shadow ) { - layoutObj.options.shadow.type = ( layoutObj.options.shadow.type || 'outer' ); - layoutObj.options.shadow.blur = ( layoutObj.options.shadow.blur || 8 ) * ONEPT; - layoutObj.options.shadow.offset = ( layoutObj.options.shadow.offset || 4 ) * ONEPT; - layoutObj.options.shadow.angle = ( layoutObj.options.shadow.angle || 270 ) * 60000; - layoutObj.options.shadow.color = ( layoutObj.options.shadow.color || '000000' ); - layoutObj.options.shadow.opacity = ( layoutObj.options.shadow.opacity || 0.75 ) * 100000; - - strSlideXml += ''; - strSlideXml += ''; - strSlideXml += ''; - strSlideXml += '' - strSlideXml += ''; - strSlideXml += ''; - } - - /* FIXME: FUTURE: Text wrapping (copied from MS-PPTX export) - // Commented out b/c i'm not even sure this works - current code produces text that wraps in shapes and textboxes, so... - if ( layoutObj.options.textWrap ) { - strSlideXml += '' - + '' - + '' - + '' - + ''; - } - */ - - // B: Close Shape Properties - strSlideXml += ''; - - // Add formatted text - strSlideXml += genXmlTextBody(layoutObj); - - // LAST: Close SHAPE ======================================================= - strSlideXml += ''; - break; - - case 'image': - strSlideXml += ''; - strSlideXml += ' ' - strSlideXml += ' '; - if ( layoutObj.hyperlink ) strSlideXml += ''; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ''; - strSlideXml += '' - strSlideXml += ' ' - strSlideXml += ' ' - strSlideXml += ' ' - strSlideXml += ' ' - strSlideXml += ' ' - strSlideXml += ''; - strSlideXml += ''; - break; - - case 'media': - if ( layoutObj.mtype == 'online' ) { - strSlideXml += ''; - strSlideXml += ' '; - // IMPORTANT: '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; // NOTE: Preview image is required! - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ''; - } - else { - strSlideXml += ''; - strSlideXml += ' '; - // IMPORTANT: '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; // NOTE: Preview image is required! - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ''; - } - break; - - case 'chart': - strSlideXml += ''; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' ' - strSlideXml += ' ' - strSlideXml += ' ' - strSlideXml += ' ' - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ''; - break; - } - }); - - // STEP 5: Add slide numbers last (if any) - if ( inLayout.slideNumberObj ) { - - // TODO: FIXME: page numbers over 99 wrap in PPT-2013 (Desktop) - strSlideXml += '' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' '; - // ISSUE #68: "Page number styling" - strSlideXml += ''; - strSlideXml += ' '; - strSlideXml += ' '; - if ( inLayout.slideNumberObj.fontFace || inLayout.slideNumberObj.fontSize || inLayout.slideNumberObj.color ) { - strSlideXml += ''; - if ( inLayout.slideNumberObj.color ) strSlideXml += genXmlColorSelection(inLayout.slideNumberObj.color); - if ( inLayout.slideNumberObj.fontFace ) strSlideXml += ''; - strSlideXml += ''; - } - strSlideXml += ''; - strSlideXml += '' - strSlideXml += ''; - } - - // STEP 6: Close spTree and finalize slide XML - strSlideXml += ''; - /* FIXME: Remove this in 2.0.0 (commented for 1.6.0) - strSlideXml += ''; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ' '; - strSlideXml += ''; - */ - strSlideXml += ''; - strSlideXml += ''; - strSlideXml += ''; - - // LAST: Return - return strSlideXml; - } - - function makeXmlSlideLayoutRel(inLayoutNum) { - var strXml = ''+CRLF; - strXml += ''; - strXml += ' '; - // Add any rels for this Slide (image/audio/video/youtube/chart) - gObjPptx.layoutDefinitions[inLayoutNum - 1].rels.forEach(function(rel, idx){ - if ( rel.type.toLowerCase().indexOf('image') > -1 ) { - strXml += ''; - } - else if ( rel.type.toLowerCase().indexOf('chart') > -1 ) { - strXml += ''; - } - else if ( rel.type.toLowerCase().indexOf('audio') > -1 ) { - // As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style - if ( strXml.indexOf(' Target="'+ rel.Target +'"') > -1 ) - strXml += ''; - else - strXml += ''; - } - else if ( rel.type.toLowerCase().indexOf('video') > -1 ) { - // As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style - if ( strXml.indexOf(' Target="'+ rel.Target +'"') > -1 ) - strXml += ''; - else - strXml += ''; - } - else if ( rel.type.toLowerCase().indexOf('online') > -1 ) { - // As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style - if ( strXml.indexOf(' Target="'+ rel.Target +'"') > -1 ) - strXml += ''; - else - strXml += ''; - } - else if ( rel.type.toLowerCase().indexOf('hyperlink') > -1 ) { - strXml += ''; - } - }); - - strXml += ''; - // - return strXml; - } - - function getLayoutIdxForSlide(inSlideNum) { - var layoutName = gObjPptx.slide2layoutMapping[inSlideNum - 1], - layoutIdx; - for (var i = 0, len = gObjPptx.layoutDefinitions.length; i < len; i++) { - if (gObjPptx.layoutDefinitions[i].name === layoutName) { - return i + 1; - } - } - throw Error('Layout "' + layoutName + '" is not specified in this presentation.'); - } - - function makeXmlSlideRel(inSlideNum) { - - - var strXml = ''+CRLF - + '' - + ' '; - - // Add any rels for this Slide (image/audio/video/youtube/chart) - gObjPptx.slides[inSlideNum-1].rels.forEach(function(rel,idx){ - if ( rel.type.toLowerCase().indexOf('image') > -1 ) { - strXml += ''; - } - else if ( rel.type.toLowerCase().indexOf('chart') > -1 ) { - strXml += ''; - } - else if ( rel.type.toLowerCase().indexOf('audio') > -1 ) { - // As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style - if ( strXml.indexOf(' Target="'+ rel.Target +'"') > -1 ) - strXml += ''; - else - strXml += ''; - } - else if ( rel.type.toLowerCase().indexOf('video') > -1 ) { - // As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style - if ( strXml.indexOf(' Target="'+ rel.Target +'"') > -1 ) - strXml += ''; - else - strXml += ''; - } - else if ( rel.type.toLowerCase().indexOf('online') > -1 ) { - // As media has *TWO* rel entries per item, check for first one, if found add second rel with alt style - if ( strXml.indexOf(' Target="'+ rel.Target +'"') > -1 ) - strXml += ''; - else - strXml += ''; - } - else if ( rel.type.toLowerCase().indexOf('hyperlink') > -1 ) { - strXml += ''; - } - }); - - strXml += ''; - // - return strXml; - } - - function makeXmlSlideMaster() { - if ( gObjPptx.properLayoutMasterInUse ) { - return gObjPptx.masterSlideXml; - } - var intSlideLayoutId = 2147483649; - var strXml = ''+CRLF - + '' - + ' ' - + 'Click to edit Master title style' - + 'Click to edit Master text stylesSecond levelThird levelFourth levelFifth level' - + '12/25/2015' - + '' - + '' - + ''; - // Create a sldLayout for each SLIDE - for ( var idx=1; idx<=gObjPptx.slides.length; idx++ ) { - strXml += ' '; - intSlideLayoutId++; - } - strXml += '' - + '' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + '' - + ''; - // - return strXml; - } - - function makeXmlSlideMasterRel() { - // FIXME: create a slideLayout for each SLDIE - var strXml = ''+CRLF - + ''; - for ( var idx=1; idx<=gObjPptx.slides.length; idx++ ) { - strXml += ' '; - } - strXml += ' '; - strXml += ''; - // - return strXml; - } - - // XML-GEN: Last 5 functions create root /ppt files + // XML-GEN: Last 5 functions create root /ppt files function makeXmlTheme() { var strXml = ''+CRLF; @@ -4023,10 +4061,6 @@ var PptxGenJS = function(){ } } - this.setMasterSlide = function(masterSlideXml) { - gObjPptx.masterSlideXml = masterSlideXml; - }; - this.useProperLayoutMaster = function() { gObjPptx.properLayoutMasterInUse = true; } @@ -4530,18 +4564,16 @@ var PptxGenJS = function(){ * Sets a master slide and color mappings. * @param {Object} masterDef */ - this.setMaster = function setMaster(masterDef) { + this.setMasterSlide = function setMasterSlide(masterDef) { var slideObj = { + slide: masterDef, data: [], rels: [] }; - gObjPptxGenerators.createLayoutObject(masterDef.slide || {}, slideObj); + gObjPptxGenerators.createLayoutObject(masterDef || {}, slideObj); - this.master = { - slide: slideObj, - colorMap: masterDef.colorMap - }; + gObjPptx.masterSlide = slideObj; }; /** @@ -4559,7 +4591,7 @@ var PptxGenJS = function(){ // A: Add this SLIDE to PRESENTATION, Add default values as well gObjPptx.layoutDefinitions[layoutNum] = { - layout: layoutObj, + slide: layoutObj, name: layoutDef.title, data: [], rels: [], From 56223c048a03303c534b02f2432cb8eaf9de2366 Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Mon, 21 Aug 2017 13:04:24 +0200 Subject: [PATCH 16/27] Charts suppoerted in master slide. --- dist/pptxgen.js | 652 ++++++++++++++++++------------------------------ 1 file changed, 249 insertions(+), 403 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index 2e930ee51..5343f13e3 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -446,7 +446,6 @@ var PptxGenJS = function(){ // STEP 4: Set props resultObject.type = 'chart'; resultObject.options = options; - debugger // STEP 5: Add this chart to this Slide Rels (rId/rels count spans all slides! Count all images to get next rId) targetRels.push({ @@ -454,6 +453,7 @@ var PptxGenJS = function(){ data: data, opts: options, type: 'chart', + globalId: chartId, fileName:'chart'+ chartId +'.xml', Target: '/ppt/charts/chart'+ chartId +'.xml' }); @@ -463,7 +463,7 @@ var PptxGenJS = function(){ return this; }, - createLayoutObject: function createLayoutObject(layoutDef, target) { + createSlideDefinition: function createSlideDefinition(layoutDef, target) { if ( layoutDef.bkgd ) { gObjPptxGenerators.addBackgroundDefinition(layoutDef.bkgd, target); } @@ -486,7 +486,7 @@ var PptxGenJS = function(){ }; }, - xmlSlideLayout: function(inLayout) { + xmlSlideLayout: function xmlSlideLayout(inLayout) { var strSlideXml = inLayout.name ? '' : ''; // Background color @@ -985,7 +985,7 @@ var PptxGenJS = function(){ return strSlideXml; }, - xmlSlideLayoutRelations: function(inLayout, defaults) { + xmlSlideLayoutRelations: function xmlSlideLayoutRelations(inLayout, defaults) { var lastRid = 0; var strXml = '' + CRLF; strXml += ''; @@ -1030,8 +1030,216 @@ var PptxGenJS = function(){ strXml += ''; return strXml; - } + }, + + /** + * Based on passed data, creates Excel Worksheet that is used as a data source for a chart. + * @param {Object} rel relation object + * @param {ZipObject} zip zip file that the resulting XLSX should be added to + * @return {Promise} promise of generating the XLSX file + */ + createExcelWorksheet: function createExcelWorksheet(rel, zip) { + var data = rel.data; + return new Promise(function(resolve, reject) { + var zipExcel = new JSZip(); + + zipExcel.folder("_rels"); + zipExcel.folder("docProps"); + zipExcel.folder("xl/_rels"); + zipExcel.folder("xl/tables"); + zipExcel.folder("xl/theme"); + zipExcel.folder("xl/worksheets"); + zipExcel.folder("xl/worksheets/_rels"); + + { + zipExcel.file("[Content_Types].xml", + '' + + ' ' + + ' ' + //+ ' ' + //+ ' ' + //+ ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + '\n' + ); + zipExcel.file("_rels/.rels", '' + + '' + + '' + + '' + + '\n'); + zipExcel.file("docProps/app.xml", + '' + + 'Microsoft Excel' + + '0' + + 'Worksheets1Sheet1' + + '\n' + ); + zipExcel.file("docProps/core.xml", + '' + + 'PptxGenJS' + + 'Ely, Brent' + + ''+ new Date().toISOString() +'' + + ''+ new Date().toISOString() +'' + + '\n'); + zipExcel.file("xl/_rels/workbook.xml.rels", + '' + + '' + + '' + + '' + + '' + + '' + + '\n' + ); + zipExcel.file("xl/styles.xml", + '' + + '' + + '\n' + ); + zipExcel.file("xl/theme/theme1.xml", + '\n' + ); + zipExcel.file("xl/workbook.xml", + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '\n' + ); + zipExcel.file("xl/worksheets/_rels/sheet1.xml.rels", + '' + + '' + + ' ' + + '\n' + ); + } + + // `sharedStrings.xml` + { + var strSharedStrings = ''; + strSharedStrings += '' + + // A: Add Labels + data[0].labels.forEach(function(label,idx){ strSharedStrings += ''+ label +''; }); + + // B: Add Series + data.forEach(function(objData,idx){ strSharedStrings += ''+ (objData.name || ' ') +''; }); + + // C: Add 'blank' for A1 + strSharedStrings += ''; + + strSharedStrings += '\n'; + zipExcel.file("xl/sharedStrings.xml", strSharedStrings); + } + + // tables/table1.xml + { + var strTableXml = ''; + strTableXml += ''; + strTableXml += ''; + strTableXml += ''; + data[0].labels.forEach(function(label,idx){ strTableXml += '' }); + strTableXml += ''; + strTableXml += ''; + strTableXml += '
'; + zipExcel.file("xl/tables/table1.xml", strTableXml); + } + + // worksheets/sheet1.xml + var strSheetXml = ''; + strSheetXml += '' + strSheetXml += ''; + strSheetXml += ''; + strSheetXml += ''; + strSheetXml += ''; + strSheetXml += ''; + data[0].labels.forEach(function(){ strSheetXml += '' }); + strSheetXml += ''; + strSheetXml += ''; + + /* EX: INPUT: `data` + [ + { name:'Red', labels:['Jan..May-17'], values:[11,13,14,15,16] }, + { name:'Amb', labels:['Jan..May-17'], values:[22, 6, 7, 8, 9] }, + { name:'Grn', labels:['Jan..May-17'], values:[33,32,42,53,63] } + ]; + */ + /* EX: OUTPUT: lineChart Worksheet: + -|---A---|--B--|--C--|--D--| + 1| | Red | Amb | Grn | + 2|Jan-17 | 11| 22| 33| + 3|Feb-17 | 55| 43| 70| + 4|Mar-17 | 56| 143| 99| + 5|Apr-17 | 65| 3| 120| + 6|May-17 | 75| 93| 170| + -|-------|-----|-----|-----| + */ + + // A: Create header row first (NOTE: Start at index=1 as headers cols start with 'B') + strSheetXml += ''; + strSheetXml += ''+ (data.length + data[0].labels.length) +''; + for (var idx=1; idx<=data[0].labels.length; idx++) { + // FIXME: Max cols is 52 + strSheetXml += ''; // NOTE: use `t="s"` for label cols! + strSheetXml += ''+ (idx-1) +''; + strSheetXml += ''; + } + strSheetXml += ''; + + // B: Add data row(s) + data.forEach(function(row,idx){ + // Leading col is reserved for the label, so hard-code it, then loop over col values + strSheetXml += ''; + strSheetXml += ''; + strSheetXml += ''+ (data[0].values.length + idx + 1) +''; + strSheetXml += ''; + row.values.forEach(function(val,idy){ + strSheetXml += ''; + strSheetXml += ''+ val +''; + strSheetXml += ''; + }); + strSheetXml += ''; + }); + + strSheetXml += ''; + strSheetXml += ''; + //strSheetXml += ''; // Causes unreadable error in O365 + strSheetXml += '\n'; + zipExcel.file("xl/worksheets/sheet1.xml", strSheetXml); + + // C: Add XLSX to PPTX export + zipExcel.generateAsync({type:'base64'}) + .then(function(content){ + // 1: Create the embedded Excel worksheet with labels and data + zip.file( "ppt/embeddings/Microsoft_Excel_Worksheet"+ rel.globalId +".xlsx", content, {base64:true} ); + + // 2: Create the chart.xml and rels files + zip.file("ppt/charts/_rels/"+ rel.fileName +".rels", + '' + + '' + + '' + + '' + ); + zip.file("ppt/charts/"+rel.fileName, makeXmlCharts(rel)); + + // 3: Done + resolve(); + }) + .catch(function(strErr){ + reject(strErr); + }); + }); } + }; /* =============================================================================================== | @@ -1110,204 +1318,7 @@ var PptxGenJS = function(){ gObjPptx.layoutDefinitions.forEach(function(layout, idx){ layout.rels.forEach(function(rel, idy) { if ( rel.type == 'chart' ) { - arrChartPromises.push(new Promise(function(resolve,reject){ - var zipExcel = new JSZip(); - - zipExcel.folder("_rels"); - zipExcel.folder("docProps"); - zipExcel.folder("xl/_rels"); - zipExcel.folder("xl/tables"); - zipExcel.folder("xl/theme"); - zipExcel.folder("xl/worksheets"); - zipExcel.folder("xl/worksheets/_rels"); - - { - zipExcel.file("[Content_Types].xml", - '' - + ' ' - + ' ' - //+ ' ' - //+ ' ' - //+ ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + '\n' - ); - zipExcel.file("_rels/.rels", '' - + '' - + '' - + '' - + '\n'); - zipExcel.file("docProps/app.xml", - '' - + 'Microsoft Excel' - + '0' - + 'Worksheets1Sheet1' - + '\n' - ); - zipExcel.file("docProps/core.xml", - '' - + 'PptxGenJS' - + 'Ely, Brent' - + ''+ new Date().toISOString() +'' - + ''+ new Date().toISOString() +'' - + '\n'); - zipExcel.file("xl/_rels/workbook.xml.rels", - '' - + '' - + '' - + '' - + '' - + '' - + '\n' - ); - zipExcel.file("xl/styles.xml", - '' - + '' - + '\n' - ); - zipExcel.file("xl/theme/theme1.xml", - '\n' - ); - zipExcel.file("xl/workbook.xml", - '' - + '' - + '' - + '' - + '' - + '' - + '' - + '\n' - ); - zipExcel.file("xl/worksheets/_rels/sheet1.xml.rels", - '' - + '' - + ' ' - + '\n' - ); - } - - // `sharedStrings.xml` - { - var strSharedStrings = ''; - strSharedStrings += '' - - // A: Add Labels - rel.data[0].labels.forEach(function(label,idx){ strSharedStrings += ''+ label +''; }); - - // B: Add Series - rel.data.forEach(function(objData,idx){ strSharedStrings += ''+ (objData.name || ' ') +''; }); - - // C: Add 'blank' for A1 - strSharedStrings += ''; - - strSharedStrings += '\n'; - zipExcel.file("xl/sharedStrings.xml", strSharedStrings); - } - - // tables/table1.xml - { - var strTableXml = ''; - strTableXml += ''; - strTableXml += ''; - strTableXml += ''; - rel.data[0].labels.forEach(function(label,idx){ strTableXml += '' }); - strTableXml += ''; - strTableXml += ''; - strTableXml += '
'; - zipExcel.file("xl/tables/table1.xml", strTableXml); - } - - // worksheets/sheet1.xml - var strSheetXml = ''; - strSheetXml += '' - strSheetXml += ''; - strSheetXml += ''; - strSheetXml += ''; - strSheetXml += ''; - strSheetXml += ''; - rel.data[0].labels.forEach(function(){ strSheetXml += '' }); - strSheetXml += ''; - strSheetXml += ''; - - /* EX: INPUT: `rel.data` - [ - { name:'Red', labels:['Jan..May-17'], values:[11,13,14,15,16] }, - { name:'Amb', labels:['Jan..May-17'], values:[22, 6, 7, 8, 9] }, - { name:'Grn', labels:['Jan..May-17'], values:[33,32,42,53,63] } - ]; - */ - /* EX: OUTPUT: lineChart Worksheet: - -|---A---|--B--|--C--|--D--| - 1| | Red | Amb | Grn | - 2|Jan-17 | 11| 22| 33| - 3|Feb-17 | 55| 43| 70| - 4|Mar-17 | 56| 143| 99| - 5|Apr-17 | 65| 3| 120| - 6|May-17 | 75| 93| 170| - -|-------|-----|-----|-----| - */ - - // A: Create header row first (NOTE: Start at index=1 as headers cols start with 'B') - strSheetXml += ''; - strSheetXml += ''+ (rel.data.length + rel.data[0].labels.length) +''; - for (var idx=1; idx<=rel.data[0].labels.length; idx++) { - // FIXME: Max cols is 52 - strSheetXml += ''; // NOTE: use `t="s"` for label cols! - strSheetXml += ''+ (idx-1) +''; - strSheetXml += ''; - } - strSheetXml += ''; - - // B: Add data row(s) - rel.data.forEach(function(row,idx){ - // Leading col is reserved for the label, so hard-code it, then loop over col values - strSheetXml += ''; - strSheetXml += ''; - strSheetXml += ''+ (rel.data[0].values.length + idx + 1) +''; - strSheetXml += ''; - row.values.forEach(function(val,idy){ - strSheetXml += ''; - strSheetXml += ''+ val +''; - strSheetXml += ''; - }); - strSheetXml += ''; - }); - - strSheetXml += ''; - strSheetXml += ''; - //strSheetXml += ''; // Causes unreadable error in O365 - strSheetXml += '\n'; - zipExcel.file("xl/worksheets/sheet1.xml", strSheetXml); - - // C: Add XLSX to PPTX export - zipExcel.generateAsync({type:'base64'}) - .then(function(content){ - // 1: Create the embedded Excel worksheet with labels and data - zip.file( "ppt/embeddings/Microsoft_Excel_Worksheet"+ (rel.rId-1) +".xlsx", content, {base64:true} ); - - // 2: Create the chart.xml and rels files - zip.file("ppt/charts/_rels/"+ rel.fileName +".rels", - '' - + '' - + '' - + '' - ); - zip.file("ppt/charts/"+rel.fileName, makeXmlCharts(rel)); - - // 3: Done - resolve(); - }) - .catch(function(strErr){ - reject(strErr); - }); - }) ); + arrChartPromises.push(gObjPptxGenerators.createExcelWorksheet(rel, zip)); } else if ( rel.type != 'online' && rel.type != 'hyperlink' ) { // A: Loop vars @@ -1327,204 +1338,27 @@ var PptxGenJS = function(){ gObjPptx.slides.forEach(function(slide,idx){ slide.rels.forEach(function(rel,idy){ if ( rel.type == 'chart' ) { - arrChartPromises.push(new Promise(function(resolve,reject){ - var zipExcel = new JSZip(); - - zipExcel.folder("_rels"); - zipExcel.folder("docProps"); - zipExcel.folder("xl/_rels"); - zipExcel.folder("xl/tables"); - zipExcel.folder("xl/theme"); - zipExcel.folder("xl/worksheets"); - zipExcel.folder("xl/worksheets/_rels"); - - { - zipExcel.file("[Content_Types].xml", - '' - + ' ' - + ' ' - //+ ' ' - //+ ' ' - //+ ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + ' ' - + '\n' - ); - zipExcel.file("_rels/.rels", '' - + '' - + '' - + '' - + '\n'); - zipExcel.file("docProps/app.xml", - '' - + 'Microsoft Excel' - + '0' - + 'Worksheets1Sheet1' - + '\n' - ); - zipExcel.file("docProps/core.xml", - '' - + 'PptxGenJS' - + 'Ely, Brent' - + ''+ new Date().toISOString() +'' - + ''+ new Date().toISOString() +'' - + '\n'); - zipExcel.file("xl/_rels/workbook.xml.rels", - '' - + '' - + '' - + '' - + '' - + '' - + '\n' - ); - zipExcel.file("xl/styles.xml", - '' - + '' - + '\n' - ); - zipExcel.file("xl/theme/theme1.xml", - '\n' - ); - zipExcel.file("xl/workbook.xml", - '' - + '' - + '' - + '' - + '' - + '' - + '' - + '\n' - ); - zipExcel.file("xl/worksheets/_rels/sheet1.xml.rels", - '' - + '' - + ' ' - + '\n' - ); - } - - // `sharedStrings.xml` - { - var strSharedStrings = ''; - strSharedStrings += '' - - // A: Add Labels - rel.data[0].labels.forEach(function(label,idx){ strSharedStrings += ''+ label +''; }); - - // B: Add Series - rel.data.forEach(function(objData,idx){ strSharedStrings += ''+ (objData.name || ' ') +''; }); - - // C: Add 'blank' for A1 - strSharedStrings += ''; - - strSharedStrings += '\n'; - zipExcel.file("xl/sharedStrings.xml", strSharedStrings); - } - - // tables/table1.xml - { - var strTableXml = ''; - strTableXml += ''; - strTableXml += ''; - strTableXml += ''; - rel.data[0].labels.forEach(function(label,idx){ strTableXml += '' }); - strTableXml += ''; - strTableXml += ''; - strTableXml += '
'; - zipExcel.file("xl/tables/table1.xml", strTableXml); - } + arrChartPromises.push(gObjPptxGenerators.createExcelWorksheet(rel, zip)); + } + else if ( rel.type != 'online' && rel.type != 'hyperlink' ) { + // A: Loop vars + var data = rel.data; - // worksheets/sheet1.xml - var strSheetXml = ''; - strSheetXml += '' - strSheetXml += ''; - strSheetXml += ''; - strSheetXml += ''; - strSheetXml += ''; - strSheetXml += ''; - rel.data[0].labels.forEach(function(){ strSheetXml += '' }); - strSheetXml += ''; - strSheetXml += ''; - - /* EX: INPUT: `rel.data` - [ - { name:'Red', labels:['Jan..May-17'], values:[11,13,14,15,16] }, - { name:'Amb', labels:['Jan..May-17'], values:[22, 6, 7, 8, 9] }, - { name:'Grn', labels:['Jan..May-17'], values:[33,32,42,53,63] } - ]; - */ - /* EX: OUTPUT: lineChart Worksheet: - -|---A---|--B--|--C--|--D--| - 1| | Red | Amb | Grn | - 2|Jan-17 | 11| 22| 33| - 3|Feb-17 | 55| 43| 70| - 4|Mar-17 | 56| 143| 99| - 5|Apr-17 | 65| 3| 120| - 6|May-17 | 75| 93| 170| - -|-------|-----|-----|-----| - */ + // B: Users will undoubtedly pass various string formats, so modify as needed + if ( data.indexOf(',') == -1 && data.indexOf(';') == -1 ) data = 'image/png;base64,' + data; + else if ( data.indexOf(',') == -1 ) data = 'image/png;base64,' + data; + else if ( data.indexOf(';') == -1 ) data = 'image/png;' + data; - // A: Create header row first (NOTE: Start at index=1 as headers cols start with 'B') - strSheetXml += ''; - strSheetXml += ''+ (rel.data.length + rel.data[0].labels.length) +''; - for (var idx=1; idx<=rel.data[0].labels.length; idx++) { - // FIXME: Max cols is 52 - strSheetXml += ''; // NOTE: use `t="s"` for label cols! - strSheetXml += ''+ (idx-1) +''; - strSheetXml += ''; - } - strSheetXml += ''; - - // B: Add data row(s) - rel.data.forEach(function(row,idx){ - // Leading col is reserved for the label, so hard-code it, then loop over col values - strSheetXml += ''; - strSheetXml += ''; - strSheetXml += ''+ (rel.data[0].values.length + idx + 1) +''; - strSheetXml += ''; - row.values.forEach(function(val,idy){ - strSheetXml += ''; - strSheetXml += ''+ val +''; - strSheetXml += ''; - }); - strSheetXml += ''; - }); + // C: Add media + zip.file( rel.Target.replace('..','ppt'), data.split(',').pop(), {base64:true} ); + } + }); + }); - strSheetXml += ''; - strSheetXml += ''; - //strSheetXml += ''; // Causes unreadable error in O365 - strSheetXml += '\n'; - zipExcel.file("xl/worksheets/sheet1.xml", strSheetXml); - - // C: Add XLSX to PPTX export - zipExcel.generateAsync({type:'base64'}) - .then(function(content){ - // 1: Create the embedded Excel worksheet with labels and data - zip.file( "ppt/embeddings/Microsoft_Excel_Worksheet"+ (rel.rId-1) +".xlsx", content, {base64:true} ); - - // 2: Create the chart.xml and rels files - zip.file("ppt/charts/_rels/"+ rel.fileName +".rels", - '' - + '' - + '' - + '' - ); - zip.file("ppt/charts/"+rel.fileName, makeXmlCharts(rel)); - - // 3: Done - resolve(); - }) - .catch(function(strErr){ - reject(strErr); - }); - }) ); + if (gObjPptx.masterSlide) { + gObjPptx.masterSlide.rels.forEach(function(rel,idy){ + if ( rel.type == 'chart' ) { + arrChartPromises.push(gObjPptxGenerators.createExcelWorksheet(rel, zip)); } else if ( rel.type != 'online' && rel.type != 'hyperlink' ) { // A: Loop vars @@ -1539,7 +1373,7 @@ var PptxGenJS = function(){ zip.file( rel.Target.replace('..','ppt'), data.split(',').pop(), {base64:true} ); } }); - }); + } // STEP 3: Wait for Promises (if any) then push the PPTX file to client-browser Promise.all( arrChartPromises ) @@ -2670,7 +2504,7 @@ var PptxGenJS = function(){ strXml += ''; // D: DATA (Add relID) - strXml += ''; + strXml += ''; // LAST: chartSpace end strXml += ''; @@ -3093,6 +2927,12 @@ var PptxGenJS = function(){ strXml += ' '; }); }); + if (gObjPptx.masterSlide) { + gObjPptx.masterSlide.rels.forEach(function(rel, idy){ + if ( rel.type != 'image' && rel.type != 'online' && rel.type != 'chart' && rel.extn != 'm4v' && strXml.indexOf(rel.type) == -1 ) + strXml += ' '; + }); + } strXml += ' '; strXml += ' '; @@ -3131,6 +2971,13 @@ var PptxGenJS = function(){ } }); }); + if (gObjPptx.masterSlide) { + gObjPptx.masterSlide.rels.forEach(function(rel,idy){ + if ( rel.type == 'chart' ) { + strXml += ' '; + } + }); + } strXml += ''; @@ -3765,7 +3612,7 @@ var PptxGenJS = function(){ function makeXmlMaster(inMasterSlide) { var strSlideXml = gObjPptxGenerators.xmlSlideLayout(inMasterSlide); var layoutDefs = gObjPptx.layoutDefinitions.map(function(layoutDef, idx) { - return ''; + return ''; }); var strXml = '' + CRLF; strXml += ''; @@ -4571,8 +4418,7 @@ var PptxGenJS = function(){ rels: [] }; - gObjPptxGenerators.createLayoutObject(masterDef || {}, slideObj); - + gObjPptxGenerators.createSlideDefinition(masterDef || {}, slideObj); gObjPptx.masterSlide = slideObj; }; From c0ab39800e1d855e8b7fa30fcdbdcf2dfe27df4f Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Mon, 21 Aug 2017 13:40:46 +0200 Subject: [PATCH 17/27] Duplicate code removed. --- dist/pptxgen.js | 173 +++++++++++++++++++++--------------------------- 1 file changed, 74 insertions(+), 99 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index 5343f13e3..0d2ac6576 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -1316,63 +1316,13 @@ var PptxGenJS = function(){ } // Create all Rels (images, media, chart data) gObjPptx.layoutDefinitions.forEach(function(layout, idx){ - layout.rels.forEach(function(rel, idy) { - if ( rel.type == 'chart' ) { - arrChartPromises.push(gObjPptxGenerators.createExcelWorksheet(rel, zip)); - } - else if ( rel.type != 'online' && rel.type != 'hyperlink' ) { - // A: Loop vars - var data = rel.data; - - // B: Users will undoubtedly pass various string formats, so modify as needed - if ( data.indexOf(',') == -1 && data.indexOf(';') == -1 ) data = 'image/png;base64,' + data; - else if ( data.indexOf(',') == -1 ) data = 'image/png;base64,' + data; - else if ( data.indexOf(';') == -1 ) data = 'image/png;' + data; - - // C: Add media - zip.file( rel.Target.replace('..','ppt'), data.split(',').pop(), {base64:true} ); - } - }); + createMediaFiles(layout, zip, arrChartPromises); }); - gObjPptx.slides.forEach(function(slide,idx){ - slide.rels.forEach(function(rel,idy){ - if ( rel.type == 'chart' ) { - arrChartPromises.push(gObjPptxGenerators.createExcelWorksheet(rel, zip)); - } - else if ( rel.type != 'online' && rel.type != 'hyperlink' ) { - // A: Loop vars - var data = rel.data; - - // B: Users will undoubtedly pass various string formats, so modify as needed - if ( data.indexOf(',') == -1 && data.indexOf(';') == -1 ) data = 'image/png;base64,' + data; - else if ( data.indexOf(',') == -1 ) data = 'image/png;base64,' + data; - else if ( data.indexOf(';') == -1 ) data = 'image/png;' + data; - - // C: Add media - zip.file( rel.Target.replace('..','ppt'), data.split(',').pop(), {base64:true} ); - } - }); + createMediaFiles(slide, zip, arrChartPromises); }); - - if (gObjPptx.masterSlide) { - gObjPptx.masterSlide.rels.forEach(function(rel,idy){ - if ( rel.type == 'chart' ) { - arrChartPromises.push(gObjPptxGenerators.createExcelWorksheet(rel, zip)); - } - else if ( rel.type != 'online' && rel.type != 'hyperlink' ) { - // A: Loop vars - var data = rel.data; - - // B: Users will undoubtedly pass various string formats, so modify as needed - if ( data.indexOf(',') == -1 && data.indexOf(';') == -1 ) data = 'image/png;base64,' + data; - else if ( data.indexOf(',') == -1 ) data = 'image/png;base64,' + data; - else if ( data.indexOf(';') == -1 ) data = 'image/png;' + data; - - // C: Add media - zip.file( rel.Target.replace('..','ppt'), data.split(',').pop(), {base64:true} ); - } - }); + if (gObjPptx.properLayoutMasterInUse && gObjPptx.masterSlide) { + createMediaFiles(gObjPptx.masterSlide, zip, arrChartPromises); } // STEP 3: Wait for Promises (if any) then push the PPTX file to client-browser @@ -1958,6 +1908,71 @@ var PptxGenJS = function(){ } } + /** + * @param {Object} glOpts {size, color, style} + * @param {Object} defaults {size, color, style} + * @param {String} type "major"(default) | "minor" + */ + function createGridLineElement(glOpts, defaults, type) { + type = type || 'major'; + var tagName = 'c:'+ type + 'Gridlines'; + strXml = '<'+ tagName + '>'; + strXml += ' '; + strXml += ' '; + strXml += ' '; // should accept scheme colors as implemented in PR 135 + strXml += ' '; + strXml += ' '; + strXml += ' '; + strXml += ''; + return strXml; + } + + function encodeImageRelations(layout, arrRelsDone) { + var intRels = 0; + layout.rels.forEach(function(rel, idy){ + // Read and Encode each image into base64 for use in export + if ( rel.type != 'online' && rel.type != 'chart' && !rel.data && $.inArray(rel.path, arrRelsDone) == -1 ) { + // Node encoding is syncronous, so we can load all images here, then call export with a callback (if any) + if ( NODEJS ) { + try { + var bitmap = fs.readFileSync(rel.path); + rel.data = new Buffer(bitmap).toString('base64'); + } + catch(ex) { + console.error('ERROR: Unable to read media: '+rel.path); + rel.data = IMG_BROKEN; + } + } + else { + intRels++; + convertImgToDataURLviaCanvas(rel); + arrRelsDone.push(rel.path); + } + } + }); + return intRels; + } + + function createMediaFiles(layout, zip, chartPromises) { + layout.rels.forEach(function(rel, idy) { + if ( rel.type == 'chart' ) { + chartPromises.push(gObjPptxGenerators.createExcelWorksheet(rel, zip)); + } + else if ( rel.type != 'online' && rel.type != 'hyperlink' ) { + // A: Loop vars + var data = rel.data; + + // B: Users will undoubtedly pass various string formats, so modify as needed + if ( data.indexOf(',') == -1 && data.indexOf(';') == -1 ) data = 'image/png;base64,' + data; + else if ( data.indexOf(',') == -1 ) data = 'image/png;base64,' + data; + else if ( data.indexOf(';') == -1 ) data = 'image/png;' + data; + + // C: Add media + zip.file( rel.Target.replace('..','ppt'), data.split(',').pop(), {base64:true} ); + } + }); + } + /* ======================================================================================================= | # # # # # ##### @@ -3964,52 +3979,14 @@ var PptxGenJS = function(){ // B: Total all physical rels across the Presentation // PERF: Only send unique paths for encoding (encoding func will find and fill *ALL* matching paths across the Presentation) gObjPptx.slides.forEach(function(slide,idx){ - slide.rels.forEach(function(rel,idy){ - // Read and Encode each image into base64 for use in export - if ( rel.type != 'online' && rel.type != 'chart' && !rel.data && $.inArray(rel.path, arrRelsDone) == -1 ) { - // Node encoding is syncronous, so we can load all images here, then call export with a callback (if any) - if ( NODEJS ) { - try { - var bitmap = fs.readFileSync(rel.path); - rel.data = new Buffer(bitmap).toString('base64'); - } - catch(ex) { - console.error('ERROR: Unable to read media: '+rel.path); - rel.data = IMG_BROKEN; - } - } - else { - intRels++; - convertImgToDataURLviaCanvas(rel); - arrRelsDone.push(rel.path); - } - } - }); + intRels += encodeImageRelations(slide, arrRelsDone); }); - gObjPptx.layoutDefinitions.forEach(function(layout, idx){ - layout.rels.forEach(function(rel,idy){ - // Read and Encode each image into base64 for use in export - if ( rel.type != 'online' && rel.type != 'chart' && !rel.data && $.inArray(rel.path, arrRelsDone) == -1 ) { - // Node encoding is syncronous, so we can load all images here, then call export with a callback (if any) - if ( NODEJS ) { - try { - var bitmap = fs.readFileSync(rel.path); - rel.data = new Buffer(bitmap).toString('base64'); - } - catch(ex) { - console.error('ERROR: Unable to read media: '+rel.path); - rel.data = IMG_BROKEN; - } - } - else { - intRels++; - convertImgToDataURLviaCanvas(rel); - arrRelsDone.push(rel.path); - } - } - }); + intRels += encodeImageRelations(layout, arrRelsDone); }); + if (gObjPptx.masterSlide) { + intRels += encodeImageRelations(gObjPptx.masterSlide, arrRelsDone); + } // STEP 3: Export now if there's no images to encode (otherwise, last async imgConvert call above will call exportFile) if ( intRels == 0 ) doExportPresentation(callback, outputType); @@ -4462,7 +4439,6 @@ var PptxGenJS = function(){ return this; }; - layoutObj.addShape = function( shape, opt ) { gObjPptxGenerators.addShapeDefinition(shape, opt, gObjPptx.layoutDefinitions[layoutNum]) return this; @@ -4473,7 +4449,6 @@ var PptxGenJS = function(){ return this; }; - $.each(layoutDef, function(key, val) { if ( key == 'bkgd' ) { From 17647cf9194bdf387072f56e75c916797f96a1e4 Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Mon, 21 Aug 2017 13:42:49 +0200 Subject: [PATCH 18/27] Example updated. --- examples/pptxgenjs-demo.layouts.html | 70 +++++++++++++++++++--------- 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/examples/pptxgenjs-demo.layouts.html b/examples/pptxgenjs-demo.layouts.html index b8714d761..709dffc59 100755 --- a/examples/pptxgenjs-demo.layouts.html +++ b/examples/pptxgenjs-demo.layouts.html @@ -23,33 +23,57 @@ var starlabsLogoSml = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAA2CAQAAACmP5VFAAAEC2lDQ1BpY2MAAHjajZVdbBRVGIaf3TkzawLOVQUtSZmgAiGlWcAoDQHd7S7bwlo22xZpY6Lb6dndsdPZ8cxs+QlXxETjDah3hsT4d0diYqIBfyJ4ITcYTAgK2JhouID4ExISbhTqxWx3B2jFc/XNe77vfb/vPWdmIHWp4vtu0oIZL1TlQtbaNz5hpS6T5DGW0c2yih34mVKpCFDxfZf71q0fSQBc2Lj4/n+uZVMysCHxENCYCuwZSBwA/bjtqxBSXcDW/aEfQqoIdKl94xOQehnoqkVxCHRNRvEbQJcaLQ9A6jhg2vXKFKROAL2TMbwWi6MeAOgqSE8qx7bKhaxVUo2q48pYuw/Y/p9rxm0u6K0GlgfTI7uB9ZB4baqS2w30QeKEXcmPAE9A4sqss3e4Fd/xw2wZWAvJNc3psQywAZKDVbVzLOJJqnpzcCF+91B99AVgBSS/9SaH97RqL9nBwASwBpJ36nKoCPSAZjnh0GhUq+1QjfKeSFerTslcHugF7c3pxu5yxKl9HsyO5Bc4D9UHhlv4uVcqu0pAN2i/SbdQjrS0f/yw1OpB9HjucDHSEjkZ5EcW8LA+OhjpCjdUo61acazq7Bxq5X9aV4PlVnzFd0vFqDc9qZrlsShf76uofCHi1EvSG2vx67PsTVSQNJhEYuNxG4syBbJY+CgaVHFwKSDxkCgkbjtnI5NIAqZROMwicQmQlJCoVmWHr4bE4xoKB5uBno9pYlHnDzzqsbwB6jTxqC3BE/VyvcXTECtFWmwRabFNFMV2sVX0Y4lnxXNih8iJtOgX29q1pdhEFjWut3lepYnEosxespzBJaSCy694NAgWd+VYd3N9Z+eIesmxzx+9EfPKIWA65lbc0T0P8ly/ql/TL+pX9cv6XCdD/1mf0+f0y3fN0rjPZbngzj0zL56VwcWlhmQGiYOHjM28Mc5x9vBXj3Z4LoqTL15YfvZw1TvW3UHt80dvyNeHbw1zpLeDpn9K/5m+mH4//VH6d+0d7TPta+2U9oV2Dks7rZ3RvtG+0z7Rvoyd1dJ3qH32ZGJ9S7xFvZa4ZtZcZT5u5szV5pNmscNnrjQ3mYPmOjNnrmqfW1wv7p7DOG7bn8W1orzYDUg8zDTOEm/VGB4O+5EoAiq4eBy8J6dVKXrEJjF0z+3eKraJ9jRG3sgZGSxjg9FvbDJ2GZmOqrHOyBn9xjojf9fttJeYVIbyQAgw0PAPKqdWD63N6fQzVsb3XWkNeXZfr1VxXUs5tXoYWEoGUs3KqT72jU9Y0Sf9ZpkEkFhxvoOFz8P2v0D7oYNNNOFEACuf6mDru+GR9+Dk03ZTzbb+EYnE9xBUt2yOnpZnQf9lfv7mWki9Dbffmp//+4P5+dsfgjYHp91/AaCffFWohAFiAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAACYktHRAD/h4/MvwAAAAlwSFlzAAAPYQAAD1UBExVUngAAAAd0SU1FB+EEHhMSJXkaXVYAAA7rSURBVGjezZp5nFTVlce/57xXa0PTzdogsgsoy7SAMYpblLiMiRJNlDBkXCZq3KLyGRF0JBKMLEr8OH4wOqO4RHE+ElHGoENcwBhxBBx2I5sCgiwNNDT0Vss780e9qq7urqLLBk1O/VHvnrv+zj333HPPveLgBUzIQ2IkzCVnvuDGHKCuEVchwiBG0IsgFaxjhe42SwLggBIlb2+gsWS953874FgvTmMgpcTZySpWykEP41hIQK9nNMmcuQ5L+IC7CeSoF+dJVtHP+zgbrDlyNr/gXDriAkY1G5jLC+zzUsIo4zG65h2zI3OTTwxgA4pgfbmR0fQghAAeh1jJ0yygxsuq4uKdxmC8FBYADEiwny2yjVgzYIqO1lq1PL+79N6c/KT+Tjvqb3WaZrXkhHS8VjQrm9A3dYCiKNpLt+bty9ScWTA8VfICXZ2jRI0+oR20EWCdpaZek19S63S7vqyjnIA2g9xZl+UZQIWep2/n4Hv6nJbqzVqvM50MXBe9RavztPS2dlUU7dkC4Ed8wCN0Y54yns7WiGYD/u1RWqzUqdrWyUbrFEmYP+ZRsdUY/5CDP5+7GckUgtnK6Q3ibqKZZD1VxDOpUdzokaV2R1liq5AoEzkpw0tymNpMT8K1/MAopSAqYSKTCGYJyCIM5y1upXOO4osZSqdm3EXcQXceaZZzIb38rxivMZc99OJ6RpHq7XKdzT4O8hilGODwE/qnJMWrrEcB5S9gcDLnZVpdwVOsJ8o/cj0lAEQZrfMP5bI6ceIYhkOQ9LS63Gof8cYW+voC66hTnU46P4c6HNLv5+D/Wftqd13sp2ak23XQGZkyr2mxEkLRMn3P522XPg3TK0hQ3/Bz4npl9jpT9CKt8/O2abkSQHFUf5Vpf7FGG/A0qLTzjHOOnu2cpRfoOJ2btbxectx0D656dpIV8yo/xG0ir0+p4bQmvE+4hb3MzpqBRuro03KqPOoJU79b/sT3WlQ8yZveLpuFOKAeK4gRTOXnWRlfJP+cFprMs18zwef3sgiH0zlGGafwHpuaVV/CALo1EcEtfMbd/DRndw3rOZH6q0OQ2qxZbRF5M/yGZZrN2o20hbY8rF7ezbgIWcXVgyjDvV0salKnlqWcR7aufc6tLOOfuauZLuSH3go3oREUy2nnrCDrl8jVecpUDAsoC6hqlLORfZyRld7JL1nCKKbSpvDBFzCyQtpohbCsUcUG7VLA4xSvq6xgRaO6f6EnPTOpCsbLQgYxq4mSt9RvKwBna/GxiMbLWUYVPLpZPzvCgqwaMd7nnIxLeZCJ3itWxsMMbU3Xx7s4WKs9agWMCOXAn/gyw9/KDs7yv6t5QJ7XKA9wcaHNZk1rHVUcpIrDeUTeKmo94LT5GSHKJlvMNX56KZ3o5w94Ok8kTW/nutaYWeb7SyXGruMHuPXk+sIabO3Zx2tcTRhIsoSziABx/p1ZEterucffAwuBeSalWukBhu1hz98CWD4NSG87PekNLGUdADvZyDlAkv/kQWptJNMKcF4bPOdLuZ+Itlih9dTynp6vRHpUJQwBr8I/RnxMW04BXuLfOEw/ZtG7AInOZ4OfcLiZ23COB+TcAy8IcGP/pQlgYUQShYVUAO/xHdoynwlU0p7pnF7Q2D5hfGadhrmXn3o4BVU8XlSIiVHMt55DnWJgHR9RwVrO513uYo+FuJcfFdKZh+C+yX0Z96WEaTrKCrNzx+ycpCk9jw6GnZDL6qh3mCfZAfSV7kAdC/iIIFXcwnYRuYFbKFAzkyTg90yj3md051HKpdDqx4PcYMAJadCJ0JnLuTujYFUST8vUFWUh23iEYTaIT4F32YsxmY1gP2AykcL789AEj1HG7T7KwTzKtWz7tvDauMQZJPEsRBm9KcpkLPZqM8dDaWszeJ0xPMRQm+fAdnaaYzFgOA/nOP63BLmWKXTOnKfO42Fu1v2t9jm6eTdRr6mmB7eoLH3Tp/xGtIQXJBOldIGhXMkUGW991bVE0kiQUDiBRxjw9UfooZXcQxfO9xlXsotJWtNKyP2Y1VpZ+RTnWb5qSKZk1p6Zdh9/Nf8c6yAuNzOcGupJHNUlzGFuPPiSO1md6eEm7sD9FldyYwpwHV0bki4xFrCYneySAJfbUD0kL7Ef4b9YSJAoYSJEiRLN+g8TJUqYKHvzdLOWO3nOP22FmMgefRY7js50btrC53gYYTrROxNQPJefMdNJK7XjH4kcHNWX1HS3nny02RgMuKKuE3FKnDKnJJCzlCLo1bo/E1XapZeRtSs3iWn9OG9M64iu0zX+7wv10jGtQDTdb6Mw7RQJaEhDGtUu+iNdl+EvcsIZo5X0kQtqiThg2aahD5/jiDkECROmiDafFms7rx0drJRSOvA/3u9zAfZQbJ6UMc238mVM0WW2+2vP2VoZQzUCJLjInm/Bn48TT2mR1vAabZnjy7gdQalLC8gnA+M/eIfD7ACFIi6k+9YSLbVS2lFKMW0oIkKIIA23TTvz9e2hHk/SjX/1BdgxK2ZdOMVkH9WJlM4cKryahwM7LOaL2xocoAzgJElYytJUSiHOmYxvcSPIDqOENSSAl7RqMQ8Pp56nbSzdm5ZsHbUiStAwtsyX60RsLMVUUCkr7au0WfHQGDMYwA8LbV3h53aNeSif6W0c8rups9ixAj0G8eSQkUuYn/NdYItd6X0VIH3K89B9TKIXQwrrQLAejPDbdNNi4zjMbF44LZXIeQusft0Es+1THWiuhzpOVEsVgfVMzLvxHGUU6cOYIMfxYNC4q5Yg5+tWfTEs5GkZySAvYTCKwdxAHwN4i4cyh4FCSRo+vhG0x0SpuPROplLEzWwG2vJL609HHucEUpZ7ztds0/4egWYDrmeWrGYCEdkAjOR8BrGCC3mULkAtU3n76wH+ZtZtNrVepIrwOnPsUq7jHasjwDjClPM5+/gJj9AB2MU9meBNIRQ0N4XYa+SGH085HANgqeIp2vMAMRYDp3IhMIAYG4CxzKQUWMkk9hcwihSdyFBQwgAn+He6fzcLWi1BggmUs0w2tYMxdALKKOMTQLmGB2kLLGAmR91RjUxwp5gHuUg6xTtwLr+hvc+tllo5SuVvi1zgCn4GLLK6QydxOQARhrCMJA4ON1Ink62a2fTnX47a1gcc9OfzdObZNjx6ZOYXPpDCN7hvkJQirqGIXbwPXEEfnz+MDezzRXKb3UuEah5gyVHb+ogXM3PVlsEMzYK7mdmWSPK3J5cAxcBy2WBduCrDH8wRNtEFgCDjqWMmO5jAXP8Cphl5aIyplDImR2z2C+5kIzc5XVjA6hywTTo411qAuWwv6Mws4F2sZ7BG5he4HIxRerasY776xmSRxbgk626wB+2zrk/DTOQOArKc+ziYr00P9nI797KpkXGu4lW52ltIG263KXZqTo/eKLNJ3J95FCM5yjQZv13GZK4yzV8my0wKZpcw2cbiuIYoe1lCEWOzbvbbMZBleJnTUpT7qWM2r9KfyeQ+9ePhVMrD3h84j9PpToCDrGeJLLcahRhL2ZV1nPRYTRuSQJK9HGEJQSr9vP0sIQAoq7LeCO5nsc9dSZJPeZ81GLCR90kCytZskXCAdwkBynqSbGAJq/CQUl2hbziuXqJHGr3WeUyH6J5GnIN6k6PaVl/I4s3IdbcguKJBDTtuKP0KBREJSliclNAVRYMaSf0cR0VCEhYVQBFN52go4EtXEUcjGtGwRjQURAMSlkAABwlkSruN3gmphP1fIIC6EpaA4grEWYTHuKw4LkA5B9jc6PVWOx6yOu95nUxfzmwM0QW8gCVSl/dG0ojjkojj4eIQD0nYqol56cGHCVFFLIlgaFgC1JD0/LkRIWZJQ8BNRDERqTPPklpLxAJ2BIvhxCVh4WSYaol7ccWQgCRTWpwM4YqndVaXRCEo0WQ1CUuA4WgR32MOPZjcBLDLy5zY5FYpwkjZbh/KBi7wLfCH8o6lllERd8kWOWxpSMPlV3xMtSF4EX7FKEayWQ4ZCr2ZyGhCsl4QpBPTOYcRrJNaw6EDtXdyKssdDDlN7pJx0l8+kyMCA5nAZYJsEAROZDpnUs4aqVe0jY2XzRwxrK08KN+nH2uIC/TgHi7kADv8kZmwXLYxtlnIvRMn8b/NQrSdeFRGex8yOf3uKUMByiWaUqkgwOmcwcn+egrRjzkEuQxcknADXzCbcXYiAEV0ZjY905HsAyWcw7lSBCAr5XUCMkcqQJVfsJZnuZ7OYFgxJTzOEM4EwwKUW9QAQvTiWXmG1IOpK4kxORM0RiUmb1o3RjdbiEFOZW0Oh7Irj+nFzOXxHE+O/SWUgCB9eItyKMLDIMAwOrMaDLct/VnKGg7Tx3/CYoTwUvbfsH58RRU9DMNidoAab18yCVZCT5byf8Qyj208wsR9D89XNEXApaeV+WW20o8uUpMBzGFnLVfkvP8dxm425+D3YDZn8TCvk4cEutCNjQwhUJteIAMpQf25CEnMEtT5YT2jI5OJpONplHOQ2lSkxTBJ7y8WQqkhSZyQD7crU6hr/PrIUm9WzmaE7w/8kbeYad+Fdr5KW6ILV+cc9ykSZGXOnD78joFMYg1Oozdolul0IB3pTS/KfJlXy1O8zBhHBGqptmIJ08b35YQKZhCgN7gEhKF0pj3lICgKYqmmq4nTnjBh3xdQvmIapZyY2XINPEyo5HHmJuNJPCTOCyzknzpwxK8El+aJW3WznjlWcYoG8gwlTODLTDDHCNlQG6whBTiNeXofmzglNWyCdioj2Bw3oJaP+TFXUcfG9E29beVLysFIdKITv2YGPWgjKaBpB6KKVVzFGCrli8z2s4UDDG5y9DQijGCYhhXBTmYkfflyn9+I6zhegBdzwDIMjw95MnXOo+kzN+U7PMdyh0SKUcd/05ti2U69SnIVn3kmL1tq7dTyJiexnLeUJArPMoYTmM4hQ+Agr0gNr+L6L/dekd0cYAEOGOzmDxJPhfZ5kjG0Z7pVF1MFFcyTWnvRP8XVMY9KUKixBfSmvXxudQLtOZu/8rr4D0AlTEA7WtSPrSeoIYoLKEl20NfCAgniJEn4T60cimhHhW3Qls70Kafj68Rp025Msf9f2WKNlFsSb7Fcmv4fcZnRFnqq3SkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMDQtMzBUMTk6MTg6MzcrMDI6MDCMsLKlAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTA0LTMwVDE5OjE4OjM3KzAyOjAw/e0KGQAAAABJRU5ErkJggg=='; var checkGreen = 'image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAAjcAAAI3AGf6F88AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAANVQTFRF////JLaSIJ+AIKqKKa2FKLCIJq+IJa6HJa6JJa6IJa6IJa2IJa6IJa6IJa6IJa6IJa6IJa6IJq6IKK+JKK+KKrCLLrGNL7KOMrOPNrSRN7WSPLeVQrmYRLmZSrycTr2eUb6gUb+gWsKlY8Wqbsmwb8mwdcy0d8y1e863g9G7hdK8htK9i9TAjNTAjtXBktfEntvKoNzLquDRruHTtePWt+TYv+fcx+rhyOvh0e7m1e/o2fHq4PTu5PXx5vbx7Pj18fr49fv59/z7+Pz7+f38/P79/f7+dNHCUgAAABF0Uk5TAAcIGBktSYSXmMHI2uPy8/XVqDFbAAABB0lEQVQ4y42T13qDMAyFZUKMbebp3mmbrnTvlY60TXn/R+oFGAyYzz1Xx/wylmWJqBLjUkVpGinJGXXliwSVEuG3sBdkaCgLPJMPQnQUDmo+jGFRPKz2WzkQl//wQvQoLPII0KuAiMjP+gMyn4iEFU1eAQCCiCU2fpCfFBVjxG18f35VOk7Swndmt9pKUl2++fG4qL2iqMPXpi8r1SKitDDne/rT8vPbRh2d6oC7n6PCLNx/bsEM0Edc5DdLAHD9tWueF9VJjmdP68DZ77iRkDKuuT19Hx3mx82MpVmo1Yfv+WXrSrxZ6slpiyes77FKif88t7Nh3C3nbFp327sHxz167uHtH/8/eds7gGsUQbkAAAAASUVORK5CYII='; - var demo1Layout = { - title: 'demo1', - objects: [ - { 'line': { x: 3.5, y:1.00, w:6.00, line:'0088CC', line_size:5 } }, - { 'chart': { type:'PIE', data:[{labels:['R','G','B'], values:[10,10,5]}], opts:{x:0.25, y:0.25, w:3, h:3} } }, - { 'rect': { x: 0.0, y:5.30, w:'100%', h:0.75, fill:'F1F1F1' } }, - { 'text': - { text:'Global IT & Services :: Status Report', - options:{ x:3.0, y:5.30, w:5.5, h:0.75, font_face:'Arial', color:'363636', font_size:20, valign:'m', margin:0 } } - }, - { 'image': { x:11.3, y:6.40, w:1.67, h:0.75, data:starlabsLogoSml } } - ] - }, - demo2Layout = { - title: "demo2", - bkgd: 'ff0000' - } - var pptx = new PptxGenJS(); + var titleLayout = { + title: 'title', + bkgd: { path: 'images/starlabs_bkgd.jpg' }, + objects: [ + { 'line': { x: 3.5, y:1.00, w:6.00, line:'0088CC', line_size:5 } }, + { 'chart': { type:'PIE', data:[{labels:['R','G','B'], values:[10,10,5]}], opts:{x:0.25, y:0.25, w:3, h:3} } }, + { 'rect': { x: 0.0, y:5.10, w:'100%', h:0.75, fill:'F1F1F1' } }, + { 'text': + { text:'Global IT & Services :: Status Report', + options:{ x:3.0, y:5.10, w:5.5, h:0.75, font_face:'Arial', color:'363636', font_size:20, valign:'m', margin:0 } } + }, + ] + }; + var baseLayout = { + bkgd: 'FFFFFF', + margin: [ 0.5, 0.25, 1.0, 0.25 ], + objects: [ + { 'rect': { x: 0.00, y:6.90, w:'100%', h:0.6, fill:'003b75' } }, + { 'image': { x:12.30, y:0.30, w:0.70, h:0.70, data:checkGreen } }, + { 'image': { x:11.45, y:5.95, w:1.67, h:0.75, data:starlabsLogoSml } }, + { 'text': + { + options: {x:0, y:6.9, w:'100%', h:0.6, align:'c', valign:'m', color:'FFFFFF', font_size:12}, + text: 'S.T.A.R. Laboratories - Confidential' + } + } ], + slideNumber: { x:0.6, y:7.0, color:'FFFFFF', fontFace:'Arial', fontSize:10 } + }; + var thanksLayout = { + title: 'thanks', + bkgd: '36ABFF', + objects: [ + { 'rect': { x:0.0, y:3.4, w:'100%', h:2.0, fill:'ffffff' } }, + { 'text': { text:'Thank You!', options:{ x:0.0, y:0.9, w:'100%', h:1, font_face:'Arial', color:'FFFFFF', font_size:60, align:'c' } } }, + { 'image': { x:4.6, y:3.5, w:4, h:1.8, path:'images/starlabs_logo.png' } } + ] + }; + function doTest() { + var pptx = new PptxGenJS(); pptx.setLayout('LAYOUT_WIDE'); pptx.useProperLayoutMaster(); - pptx.setMasterSlide('SocialBakers'); - pptx.addLayoutSlide(demo1Layout); - pptx.addLayoutSlide(demo2Layout); - pptx.addNewSlide('demo1'); - pptx.addNewSlide('demo2'); + pptx.setMasterSlide(baseLayout); + pptx.addLayoutSlide(titleLayout); + pptx.addLayoutSlide(thanksLayout); + pptx.addNewSlide('title').addImage({ + data: starlabsLogoSml, + hyperlink: {url: 'http://google.com'} + }); + pptx.addNewSlide('thanks'); pptx.save(); } From 49db20ecc70400962767ea2fd176841edc03a9ce Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Mon, 21 Aug 2017 13:56:44 +0200 Subject: [PATCH 19/27] Multiple calling doExportPresentation fixed. --- dist/pptxgen.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index 0d2ac6576..4cb3b4ae2 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -1503,13 +1503,26 @@ var PptxGenJS = function(){ var intEmpty = 0; // STEP 1: Set data for this rel, count outstanding - $.each(gObjPptx.slides, function(i,slide){ - $.each(slide.rels, function(i,rel){ + gObjPptx.slides.forEach(function(slide, i) { + slide.rels.forEach(function(rel, i){ if ( rel.path == slideRel.path ) rel.data = inStr; if ( !rel.data ) intEmpty++; }); }); - + if (gObjPptx.properLayoutMasterInUse) { + gObjPptx.layoutDefinitions.forEach(function(layout, i) { + layout.rels.forEach(function(rel, i){ + if ( rel.path == slideRel.path ) rel.data = inStr; + if ( !rel.data ) intEmpty++; + }); + }); + if (gObjPptx.masterSlide) { + gObjPptx.masterSlide.rels.forEach(function(rel, i) { + if ( rel.path == slideRel.path ) rel.data = inStr; + if ( !rel.data ) intEmpty++; + }); + } + } // STEP 2: Continue export process if all rels have base64 `data` now if ( intEmpty == 0 ) doExportPresentation(); } From fe75783a1e43413ad142bde00697bfe18fb8b10d Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Tue, 22 Aug 2017 12:45:30 +0200 Subject: [PATCH 20/27] Documentation added. Chatty object creating changes to a single assignment. $.each -> array.forEach Refactoring nomenclature Conditions on gObjPptx.masterSlide removed (always defined, but empty by default) --- dist/pptxgen.js | 494 +++++++++++++++++++++++++++++------------------- 1 file changed, 301 insertions(+), 193 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index 4cb3b4ae2..067e972bd 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -135,16 +135,27 @@ var PptxGenJS = function(){ gObjPptx.revision = '1'; gObjPptx.subject = 'PptxGenJS Presentation'; gObjPptx.title = 'PptxGenJS Presentation'; - gObjPptx.masterSlide = null; - gObjPptx.layoutDefinitions = []; - gObjPptx.slide2layoutMapping = []; - gObjPptx.properLayoutMasterInUse = false; gObjPptx.fileName = 'Presentation'; gObjPptx.fileExtn = '.pptx'; gObjPptx.pptLayout = LAYOUTS['LAYOUT_16x9']; gObjPptx.rtlMode = false; gObjPptx.slides = []; + + /** @type {object} master slide layout object */ + gObjPptx.masterSlide = { + slide: {}, + data: [], + rels: [] + }; + /** @type {object[]} slide layout definition objects, used for generating slide layout files */ + gObjPptx.layoutDefinitions = []; + /** @type {object} determines which layout is used for a specific slide */ + gObjPptx.slide2layoutMapping = []; + /** @type {boolean} if true proper master and layouts are used, otherwise the old way of templating expected */ + gObjPptx.properLayoutMasterInUse = false; + /** @type {Number} global counter for included images (used for index in their filenames) */ gObjPptx.imageCounter = 0; + /** @type {Number} global counter for included charts (used for index in their filenames) */ gObjPptx.chartCounter = 0; // C: Expose shape library to clients @@ -158,8 +169,17 @@ var PptxGenJS = function(){ // GENERATORS + /** + * @type {Object} + * Gathers methods for generating objects by API-specified definition. + */ var gObjPptxGenerators = { - // DEPRECATED `src` is replaced by `path` in v1.5.0 + /** + * Adds a background image or color to a slide definition. + * @param {String|Object} bkg color string or an object with image definition + * @param {Object} target slide object that the background is set to + * DEPRECATED `src` is replaced by `path` in v1.5.0 + */ addBackgroundDefinition: function addBackgroundDefinition(bkg, target) { if (typeof bkg === 'object' && (bkg.src || bkg.path || bkg.data) ) { // Allow the use of only the data key (no src reqd) @@ -184,11 +204,16 @@ var PptxGenJS = function(){ else if (bkg && typeof bkg === 'string' ) { target.slide.back = bkg; } - }, - addTextDefinition: function addTextDefinition(text, options, target) { - var opt = ( options && typeof options === 'object' ? options : {} ); + /** + * Adds a text object to a slide definition. + * @param {String} text + * @param {Object} opt + * @param {Object} target slide object that the text should be added to + */ + addTextDefinition: function addTextDefinition(text, opt, target) { + var opt = ( opt && typeof opt === 'object' ? opt : {} ); var resultObject = {}; var text = ( text || '' ); if ( Array.isArray(text) && text.length == 0 ) text = ''; @@ -232,7 +257,14 @@ var PptxGenJS = function(){ return resultObject; }, - addShapeDefinition: function addImageDefinition(shape, opt, target) { + /** + * Adds a shape object to a slide definition. + * @param {Object} shape shape const object (pptx.shapes) + * @param {Object} opt + * @param {Object} target slide object that the shape should be added to + * @return {Object} shape object + */ + addShapeDefinition: function addShapeDefinition(shape, opt, target) { var resultObject = {}; var options = ( typeof opt === 'object' ? opt : {} ); @@ -256,22 +288,37 @@ var PptxGenJS = function(){ return resultObject; }, - addImageDefinition: function addImageDefinition(strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData, target) { + /** + * Adds an image object to a slide definition. + * This method can be called with only two args (opt, target) - this is supposed to be the only way in future. + * @param {String|Object} strImagePath image path or object options + * @param {Number|Object} intPosX x-position or slide object that the image should be addded to + * @param {Number} intPosY + * @param {Number} intSizeX + * @param {Number} intWidth + * @param {Number} intHeight + * @param {String} strImageData base64 encoded representation of the image + * @param {Object} target slide that the image should be added to (if not specified as the 2nd arg) + * @return {Object} image object + */ + addImageDefinition: function addImageDefinition(strImagePath, intPosX, intPosY, intWidth, intHeight, strImageData, target) { var resultObject = {}; - var imageRelId = target.rels.length + 1; // FIRST: Set vars for this image (object param replaces positional args in 1.1.0) // FIXME: FUTURE: DEPRECATED: Only allow object param in 1.5 or 2.0 if ( typeof strImagePath === 'object' ) { + target = intPosX; // if the opts are an object, the second arg is supposed to be a target intPosX = (strImagePath.x || 0); intPosY = (strImagePath.y || 0); - intSizeX = (strImagePath.cx || strImagePath.w || 0); - intSizeY = (strImagePath.cy || strImagePath.h || 0); + intWidth = (strImagePath.cx || strImagePath.w || 0); + intHeight = (strImagePath.cy || strImagePath.h || 0); objHyperlink = (strImagePath.hyperlink || ''); strImageData = (strImagePath.data || ''); strImagePath = (strImagePath.path || ''); // IMPORTANT: This line must be last as were about to ovewrite ourself! } + var imageRelId = target.rels.length + 1; + // REALITY-CHECK: if ( !strImagePath && !strImageData ) { console.error("ERROR: `addImage()` requires either 'data' or 'path' parameter!"); @@ -295,15 +342,16 @@ var PptxGenJS = function(){ resultObject.image = (strImagePath || 'preencoded.png'); // STEP 3: Set image properties & options - // FIXME: Measure actual image when no intSizeX/intSizeY params passed + // FIXME: Measure actual image when no intWidth/intHeight params passed // ....: This is an async process: we need to make getSizeFromImage use callback, then set H/W... - // if ( !intSizeX || !intSizeY ) { var imgObj = getSizeFromImage(strImagePath); + // if ( !intWidth || !intHeight ) { var imgObj = getSizeFromImage(strImagePath); var imgObj = { width:1, height:1 }; - resultObject.options = {}; - resultObject.options.x = (intPosX || 0); - resultObject.options.y = (intPosY || 0); - resultObject.options.cx = (intSizeX || imgObj.width ); - resultObject.options.cy = (intSizeY || imgObj.height); + resultObject.options = { + x: (intPosX || 0), + y: (intPosY || 0), + cx: (intWidth || imgObj.width), + cy: (intHeight || imgObj.height) + }; // STEP 4: Add this image to this Slide Rels (rId/rels count spans all slides! Count all images to get next rId) target.rels.push({ @@ -345,7 +393,8 @@ var PptxGenJS = function(){ * @param {object} type should belong to: 'column', 'pie' * @param {object} data a JSON object with follow the following format * @param {object} opt - * @param {object} target a slide or layout that the object should be added to + * @param {object} target slide object that the chart should be added to + * @return {Object} chart object * { * title: 'eSurvey chart', * data: [ @@ -460,20 +509,37 @@ var PptxGenJS = function(){ resultObject.chartRid = chartRelId; target.data.push(resultObject); - return this; + return resultObject; }, - createSlideDefinition: function createSlideDefinition(layoutDef, target) { - if ( layoutDef.bkgd ) { - gObjPptxGenerators.addBackgroundDefinition(layoutDef.bkgd, target); + /** + * Transforms a slide definition to a slide object that is then passed to the XML transformation process. + * The following object is expected as a slide definition: + * { + * bkgd: 'FF00FF', + * objects: [{ + * text: { + * text: 'Hello World', + * x: 1, + * y: 1 + * } + * }] + * } + * @param {Object} slideDef slide definition + * @param {Object} target empty slide object that should be updated by the passed definition + * + */ + createSlideObject: function createSlideObject(slideDef, target) { + if ( slideDef.bkgd ) { + gObjPptxGenerators.addBackgroundDefinition(slideDef.bkgd, target); } // Add all Slide Master objects in the order they were given (Issue#53) - if ( layoutDef.objects && Array.isArray(layoutDef.objects) && layoutDef.objects.length > 0 ) { - layoutDef.objects.forEach(function(object, idx){ + if ( slideDef.objects && Array.isArray(slideDef.objects) && slideDef.objects.length > 0 ) { + slideDef.objects.forEach(function(object, idx){ var key = Object.keys(object)[0]; if ( MASTER_OBJECTS[key] && key == 'chart' ) gObjPptxGenerators.addChartDefinition(CHART_TYPES[(object.chart.type||'').toUpperCase()], object.chart.data, object.chart.opts, target); - else if ( MASTER_OBJECTS[key] && key == 'image' ) gObjPptxGenerators.addImageDefinition(object[key], null, null, null, null, null, target); + else if ( MASTER_OBJECTS[key] && key == 'image' ) gObjPptxGenerators.addImageDefinition(object[key], target); else if ( MASTER_OBJECTS[key] && key == 'line' ) gObjPptxGenerators.addShapeDefinition(gObjPptxShapes.LINE, object[key], target); else if ( MASTER_OBJECTS[key] && key == 'rect' ) gObjPptxGenerators.addShapeDefinition(gObjPptxShapes.RECTANGLE, object[key], target); else if ( MASTER_OBJECTS[key] && key == 'text' ) gObjPptxGenerators.addTextDefinition(object[key].text, object[key].options, target); @@ -481,24 +547,29 @@ var PptxGenJS = function(){ } // Add Slide Numbers - if ( layoutDef.slideNumber && typeof layoutDef.slideNum ) { - target.slideNumberObj = layoutDef.slideNumber; + if ( slideDef.slideNumber && typeof slideDef.slideNum ) { + target.slideNumberObj = slideDef.slideNumber; }; }, - xmlSlideLayout: function xmlSlideLayout(inLayout) { - var strSlideXml = inLayout.name ? '' : ''; + /** + * Transforms a slide object to resulting XML string. + * @param {Object} slideObject slide object created within gObjPptxGenerators.createSlideObject + * @return {String} XML string with as the root + */ + slideObjectToXml: function slideObjectToXml(slideObject) { + var strSlideXml = slideObject.name ? '' : ''; // Background color - if ( inLayout.slide.back ) { - strSlideXml += genXmlColorSelection(false, inLayout.slide.back); + if ( slideObject.slide.back ) { + strSlideXml += genXmlColorSelection(false, slideObject.slide.back); } // B: Add background image (using Strech) (if any) - if ( inLayout.slide.bkgdImgRid ) { + if ( slideObject.slide.bkgdImgRid ) { // FIXME: We should be doing this in the slideLayout... strSlideXml += '' + '' - + '' + + '' + '' + '' + ''; @@ -511,37 +582,37 @@ var PptxGenJS = function(){ strSlideXml += ''; // STEP 4: Loop over all Slide.data objects and add them to this slide =============================== - $.each(inLayout.data, function(idx, layoutObj){ + slideObject.data.forEach(function(slideItemObj, idx) { var x = 0, y = 0, cx = getSmartParseNumber('75%','X'), cy = 0; var locationAttr = "", shapeType = null; // A: Set option vars - layoutObj.options = layoutObj.options || {}; + slideItemObj.options = slideItemObj.options || {}; - if ( layoutObj.options.w || layoutObj.options.w == 0 ) layoutObj.options.cx = layoutObj.options.w; - if ( layoutObj.options.h || layoutObj.options.h == 0 ) layoutObj.options.cy = layoutObj.options.h; + if ( slideItemObj.options.w || slideItemObj.options.w == 0 ) slideItemObj.options.cx = slideItemObj.options.w; + if ( slideItemObj.options.h || slideItemObj.options.h == 0 ) slideItemObj.options.cy = slideItemObj.options.h; // - if ( layoutObj.options.x || layoutObj.options.x == 0 ) x = getSmartParseNumber( layoutObj.options.x , 'X' ); - if ( layoutObj.options.y || layoutObj.options.y == 0 ) y = getSmartParseNumber( layoutObj.options.y , 'Y' ); - if ( layoutObj.options.cx || layoutObj.options.cx == 0 ) cx = getSmartParseNumber( layoutObj.options.cx, 'X' ); - if ( layoutObj.options.cy || layoutObj.options.cy == 0 ) cy = getSmartParseNumber( layoutObj.options.cy, 'Y' ); + if ( slideItemObj.options.x || slideItemObj.options.x == 0 ) x = getSmartParseNumber( slideItemObj.options.x , 'X' ); + if ( slideItemObj.options.y || slideItemObj.options.y == 0 ) y = getSmartParseNumber( slideItemObj.options.y , 'Y' ); + if ( slideItemObj.options.cx || slideItemObj.options.cx == 0 ) cx = getSmartParseNumber( slideItemObj.options.cx, 'X' ); + if ( slideItemObj.options.cy || slideItemObj.options.cy == 0 ) cy = getSmartParseNumber( slideItemObj.options.cy, 'Y' ); // - if ( layoutObj.options.shape ) shapeType = getShapeInfo( layoutObj.options.shape ); + if ( slideItemObj.options.shape ) shapeType = getShapeInfo( slideItemObj.options.shape ); // - if ( layoutObj.options.flipH ) locationAttr += ' flipH="1"'; - if ( layoutObj.options.flipV ) locationAttr += ' flipV="1"'; - if ( layoutObj.options.rotate ) locationAttr += ' rot="' + convertRotationDegrees(layoutObj.options.rotate)+ '"'; + if ( slideItemObj.options.flipH ) locationAttr += ' flipH="1"'; + if ( slideItemObj.options.flipV ) locationAttr += ' flipV="1"'; + if ( slideItemObj.options.rotate ) locationAttr += ' rot="' + convertRotationDegrees(slideItemObj.options.rotate)+ '"'; // B: Add OBJECT to current Slide ---------------------------- - switch ( layoutObj.type ) { + switch ( slideItemObj.type ) { case 'table': // FIRST: Ensure we have rows - otherwise, bail! - if ( !layoutObj.arrTabRows || (Array.isArray(layoutObj.arrTabRows) && layoutObj.arrTabRows.length == 0) ) break; + if ( !slideItemObj.arrTabRows || (Array.isArray(slideItemObj.arrTabRows) && slideItemObj.arrTabRows.length == 0) ) break; // Set table vars var objTableGrid = {}; - var arrTabRows = layoutObj.arrTabRows; - var objTabOpts = layoutObj.options; + var arrTabRows = slideItemObj.arrTabRows; + var objTabOpts = slideItemObj.options; var intColCnt = 0, intColW = 0; // Calc number of columns @@ -556,7 +627,7 @@ var PptxGenJS = function(){ // NOTE: Non-numeric cNvPr id values will trigger "presentation needs repair" type warning in MS-PPT-2013 var strXml = '' + ' ' - + ' ' + + ' ' + ' ' + ' ' + ' ' @@ -580,14 +651,14 @@ var PptxGenJS = function(){ if ( Array.isArray(objTabOpts.colW) ) { strXml += ''; for ( var col=0; col'; + strXml += ' '; } strXml += ''; } // B: Table Width provided without colW? Then distribute cols else { intColW = ( objTabOpts.colW ? objTabOpts.colW : EMU ); - if ( layoutObj.options.cx && !objTabOpts.colW ) intColW = Math.round( layoutObj.options.cx / intColCnt ); // FIX: Issue#12 + if ( slideItemObj.options.cx && !objTabOpts.colW ) intColW = Math.round( slideItemObj.options.cx / intColCnt ); // FIX: Issue#12 strXml += ''; for ( var col=0; col'; } strXml += ''; @@ -657,7 +728,7 @@ var PptxGenJS = function(){ var intRowH = 0; // IMPORTANT: Default must be zero for auto-sizing to work if ( Array.isArray(objTabOpts.rowH) && objTabOpts.rowH[rIdx] ) intRowH = inch2Emu(Number(objTabOpts.rowH[rIdx])); else if ( objTabOpts.rowH && !isNaN(Number(objTabOpts.rowH)) ) intRowH = inch2Emu(Number(objTabOpts.rowH)); - else if ( layoutObj.options.cy || layoutObj.options.h ) intRowH = ( layoutObj.options.h ? inch2Emu(layoutObj.options.h) : layoutObj.options.cy) / arrTabRows.length; + else if ( slideItemObj.options.cy || slideItemObj.options.h ) intRowH = ( slideItemObj.options.h ? inch2Emu(slideItemObj.options.h) : slideItemObj.options.cy) / arrTabRows.length; // B: Start row strXml += ''; @@ -767,20 +838,20 @@ var PptxGenJS = function(){ case 'text': // Lines can have zero cy, but text should not - if ( !layoutObj.options.line && cy == 0 ) cy = (EMU * 0.3); + if ( !slideItemObj.options.line && cy == 0 ) cy = (EMU * 0.3); // Margin/Padding/Inset for textboxes - if ( layoutObj.options.margin && Array.isArray(layoutObj.options.margin) ) { - layoutObj.options.bodyProp.lIns = (layoutObj.options.margin[0] * ONEPT || 0); - layoutObj.options.bodyProp.rIns = (layoutObj.options.margin[1] * ONEPT || 0); - layoutObj.options.bodyProp.bIns = (layoutObj.options.margin[2] * ONEPT || 0); - layoutObj.options.bodyProp.tIns = (layoutObj.options.margin[3] * ONEPT || 0); + if ( slideItemObj.options.margin && Array.isArray(slideItemObj.options.margin) ) { + slideItemObj.options.bodyProp.lIns = (slideItemObj.options.margin[0] * ONEPT || 0); + slideItemObj.options.bodyProp.rIns = (slideItemObj.options.margin[1] * ONEPT || 0); + slideItemObj.options.bodyProp.bIns = (slideItemObj.options.margin[2] * ONEPT || 0); + slideItemObj.options.bodyProp.tIns = (slideItemObj.options.margin[3] * ONEPT || 0); } - else if ( (layoutObj.options.margin || layoutObj.options.margin == 0) && Number.isInteger(layoutObj.options.margin) ) { - layoutObj.options.bodyProp.lIns = (layoutObj.options.margin * ONEPT); - layoutObj.options.bodyProp.rIns = (layoutObj.options.margin * ONEPT); - layoutObj.options.bodyProp.bIns = (layoutObj.options.margin * ONEPT); - layoutObj.options.bodyProp.tIns = (layoutObj.options.margin * ONEPT); + else if ( (slideItemObj.options.margin || slideItemObj.options.margin == 0) && Number.isInteger(slideItemObj.options.margin) ) { + slideItemObj.options.bodyProp.lIns = (slideItemObj.options.margin * ONEPT); + slideItemObj.options.bodyProp.rIns = (slideItemObj.options.margin * ONEPT); + slideItemObj.options.bodyProp.bIns = (slideItemObj.options.margin * ONEPT); + slideItemObj.options.bodyProp.tIns = (slideItemObj.options.margin * ONEPT); } var effectsList = ''; @@ -791,50 +862,50 @@ var PptxGenJS = function(){ // B: The addition of the "txBox" attribute is the sole determiner of if an object is a Shape or Textbox strSlideXml += ''; - strSlideXml += '' : '/>'); + strSlideXml += '' : '/>'); strSlideXml += ''; strSlideXml += ''; strSlideXml += ''; strSlideXml += ''; strSlideXml += '' - + (layoutObj.options.rectRadius ? '' : '') + + (slideItemObj.options.rectRadius ? '' : '') + ''; // Option: FILL - strSlideXml += ( layoutObj.options.fill ? genXmlColorSelection(layoutObj.options.fill) : '' ); + strSlideXml += ( slideItemObj.options.fill ? genXmlColorSelection(slideItemObj.options.fill) : '' ); // Shape Type: LINE: line color - if ( layoutObj.options.line ) { - strSlideXml += ''; - strSlideXml += genXmlColorSelection( layoutObj.options.line ); - if ( layoutObj.options.line_dash ) strSlideXml += ''; - if ( layoutObj.options.line_head ) strSlideXml += ''; - if ( layoutObj.options.line_tail ) strSlideXml += ''; + if ( slideItemObj.options.line ) { + strSlideXml += ''; + strSlideXml += genXmlColorSelection( slideItemObj.options.line ); + if ( slideItemObj.options.line_dash ) strSlideXml += ''; + if ( slideItemObj.options.line_head ) strSlideXml += ''; + if ( slideItemObj.options.line_tail ) strSlideXml += ''; strSlideXml += ''; } // EFFECTS > SHADOW: REF: @see http://officeopenxml.com/drwSp-effects.php - if ( layoutObj.options.shadow ) { - layoutObj.options.shadow.type = ( layoutObj.options.shadow.type || 'outer' ); - layoutObj.options.shadow.blur = ( layoutObj.options.shadow.blur || 8 ) * ONEPT; - layoutObj.options.shadow.offset = ( layoutObj.options.shadow.offset || 4 ) * ONEPT; - layoutObj.options.shadow.angle = ( layoutObj.options.shadow.angle || 270 ) * 60000; - layoutObj.options.shadow.color = ( layoutObj.options.shadow.color || '000000' ); - layoutObj.options.shadow.opacity = ( layoutObj.options.shadow.opacity || 0.75 ) * 100000; + if ( slideItemObj.options.shadow ) { + slideItemObj.options.shadow.type = ( slideItemObj.options.shadow.type || 'outer' ); + slideItemObj.options.shadow.blur = ( slideItemObj.options.shadow.blur || 8 ) * ONEPT; + slideItemObj.options.shadow.offset = ( slideItemObj.options.shadow.offset || 4 ) * ONEPT; + slideItemObj.options.shadow.angle = ( slideItemObj.options.shadow.angle || 270 ) * 60000; + slideItemObj.options.shadow.color = ( slideItemObj.options.shadow.color || '000000' ); + slideItemObj.options.shadow.opacity = ( slideItemObj.options.shadow.opacity || 0.75 ) * 100000; strSlideXml += ''; - strSlideXml += ''; - strSlideXml += ''; - strSlideXml += '' + strSlideXml += ''; + strSlideXml += ''; + strSlideXml += '' strSlideXml += ''; strSlideXml += ''; } /* FIXME: FUTURE: Text wrapping (copied from MS-PPTX export) // Commented out b/c i'm not even sure this works - current code produces text that wraps in shapes and textboxes, so... - if ( layoutObj.options.textWrap ) { + if ( slideItemObj.options.textWrap ) { strSlideXml += '' + '' + '' @@ -847,7 +918,7 @@ var PptxGenJS = function(){ strSlideXml += ''; // Add formatted text - strSlideXml += genXmlTextBody(layoutObj); + strSlideXml += genXmlTextBody(slideItemObj); // LAST: Close SHAPE ======================================================= strSlideXml += ''; @@ -856,12 +927,12 @@ var PptxGenJS = function(){ case 'image': strSlideXml += ''; strSlideXml += ' ' - strSlideXml += ' '; - if ( layoutObj.hyperlink ) strSlideXml += ''; + strSlideXml += ' '; + if ( slideItemObj.hyperlink ) strSlideXml += ''; strSlideXml += ' '; strSlideXml += ' '; strSlideXml += ' '; - strSlideXml += ''; + strSlideXml += ''; strSlideXml += '' strSlideXml += ' ' strSlideXml += ' ' @@ -873,17 +944,17 @@ var PptxGenJS = function(){ break; case 'media': - if ( layoutObj.mtype == 'online' ) { + if ( slideItemObj.mtype == 'online' ) { strSlideXml += ''; strSlideXml += ' '; // IMPORTANT: '; + strSlideXml += ' '; strSlideXml += ' '; strSlideXml += ' '; - strSlideXml += ' '; + strSlideXml += ' '; strSlideXml += ' '; strSlideXml += ' '; - strSlideXml += ' '; // NOTE: Preview image is required! + strSlideXml += ' '; // NOTE: Preview image is required! strSlideXml += ' '; strSlideXml += ' '; strSlideXml += ' '; @@ -897,18 +968,18 @@ var PptxGenJS = function(){ strSlideXml += ''; strSlideXml += ' '; // IMPORTANT: '; + strSlideXml += ' '; strSlideXml += ' '; strSlideXml += ' '; - strSlideXml += ' '; + strSlideXml += ' '; strSlideXml += ' '; strSlideXml += ' '; - strSlideXml += ' '; + strSlideXml += ' '; strSlideXml += ' '; strSlideXml += ' '; strSlideXml += ' '; strSlideXml += ' '; - strSlideXml += ' '; // NOTE: Preview image is required! + strSlideXml += ' '; // NOTE: Preview image is required! strSlideXml += ' '; strSlideXml += ' '; strSlideXml += ' '; @@ -933,7 +1004,7 @@ var PptxGenJS = function(){ strSlideXml += ' ' strSlideXml += ' '; strSlideXml += ' '; - strSlideXml += ' '; + strSlideXml += ' '; strSlideXml += ' '; strSlideXml += ' '; strSlideXml += ''; @@ -942,14 +1013,14 @@ var PptxGenJS = function(){ }); // STEP 5: Add slide numbers last (if any) - if ( inLayout.slideNumberObj ) { + if ( slideObject.slideNumberObj ) { // TODO: FIXME: page numbers over 99 wrap in PPT-2013 (Desktop) strSlideXml += '' + ' ' + ' ' + ' ' - + ' ' + + ' ' + ' ' + ' ' + ' ' @@ -959,10 +1030,10 @@ var PptxGenJS = function(){ strSlideXml += ''; strSlideXml += ' '; strSlideXml += ' '; - if ( inLayout.slideNumberObj.fontFace || inLayout.slideNumberObj.fontSize || inLayout.slideNumberObj.color ) { - strSlideXml += ''; - if ( inLayout.slideNumberObj.color ) strSlideXml += genXmlColorSelection(inLayout.slideNumberObj.color); - if ( inLayout.slideNumberObj.fontFace ) strSlideXml += ''; + if ( slideObject.slideNumberObj.fontFace || slideObject.slideNumberObj.fontSize || slideObject.slideNumberObj.color ) { + strSlideXml += ''; + if ( slideObject.slideNumberObj.color ) strSlideXml += genXmlColorSelection(slideObject.slideNumberObj.color); + if ( slideObject.slideNumberObj.fontFace ) strSlideXml += ''; strSlideXml += ''; } strSlideXml += ''; @@ -985,12 +1056,20 @@ var PptxGenJS = function(){ return strSlideXml; }, - xmlSlideLayoutRelations: function xmlSlideLayoutRelations(inLayout, defaults) { - var lastRid = 0; + /** + * Transforms slide relations to XML string. + * Extra relations that are not dynamic can be passed using the 2nd arg (e.g. theme relation in master file). + * These relations use rId series that starts with 1-increased maximum of rIds used for dynamic relations. + * @param {Object} slideObject slide object whose relations are being transformed + * @param {Object[]} defaultRels array of default relations (such objects expected: { target: , type: }) + * @return {String} complete XML string ready to be saved as a file + */ + slideObjectRelationsToXml: function slideObjectRelationsToXml(slideObject, defaultRels) { + var lastRid = 0; // stores maximum rId used for dynamic relations var strXml = '' + CRLF; strXml += ''; // Add any rels for this Slide (image/audio/video/youtube/chart) - inLayout.rels.forEach(function(rel, idx){ + slideObject.rels.forEach(function(rel, idx){ lastRid = Math.max(lastRid, rel.rId); if ( rel.type.toLowerCase().indexOf('image') > -1 ) { strXml += ''; @@ -1024,7 +1103,7 @@ var PptxGenJS = function(){ } }); - defaults.forEach(function(rel, idx) { + defaultRels.forEach(function(rel, idx) { strXml += ''; }); @@ -1034,12 +1113,12 @@ var PptxGenJS = function(){ /** * Based on passed data, creates Excel Worksheet that is used as a data source for a chart. - * @param {Object} rel relation object + * @param {Object} chartObject chart object * @param {ZipObject} zip zip file that the resulting XLSX should be added to * @return {Promise} promise of generating the XLSX file */ - createExcelWorksheet: function createExcelWorksheet(rel, zip) { - var data = rel.data; + createExcelWorksheet: function createExcelWorksheet(chartObject, zip) { + var data = chartObject.data; return new Promise(function(resolve, reject) { var zipExcel = new JSZip(); @@ -1220,16 +1299,16 @@ var PptxGenJS = function(){ zipExcel.generateAsync({type:'base64'}) .then(function(content){ // 1: Create the embedded Excel worksheet with labels and data - zip.file( "ppt/embeddings/Microsoft_Excel_Worksheet"+ rel.globalId +".xlsx", content, {base64:true} ); + zip.file( "ppt/embeddings/Microsoft_Excel_Worksheet"+ chartObject.globalId +".xlsx", content, {base64:true} ); // 2: Create the chart.xml and rels files - zip.file("ppt/charts/_rels/"+ rel.fileName +".rels", + zip.file("ppt/charts/_rels/"+ chartObject.fileName +".rels", '' + '' - + '' + + '' + '' ); - zip.file("ppt/charts/"+rel.fileName, makeXmlCharts(rel)); + zip.file("ppt/charts/"+chartObject.fileName, makeXmlCharts(chartObject)); // 3: Done resolve(); @@ -1306,7 +1385,7 @@ var PptxGenJS = function(){ } } - if (gObjPptx.properLayoutMasterInUse && gObjPptx.masterSlide) { + if (gObjPptx.properLayoutMasterInUse) { zip.file("ppt/slideMasters/slideMaster1.xml", makeXmlMaster(gObjPptx.masterSlide)); zip.file("ppt/slideMasters/_rels/slideMaster1.xml.rels", makeXmlMasterRel(gObjPptx.masterSlide)); } @@ -1321,7 +1400,7 @@ var PptxGenJS = function(){ gObjPptx.slides.forEach(function(slide,idx){ createMediaFiles(slide, zip, arrChartPromises); }); - if (gObjPptx.properLayoutMasterInUse && gObjPptx.masterSlide) { + if (gObjPptx.properLayoutMasterInUse) { createMediaFiles(gObjPptx.masterSlide, zip, arrChartPromises); } @@ -1501,27 +1580,20 @@ var PptxGenJS = function(){ function callbackImgToDataURLDone(inStr, slideRel){ var intEmpty = 0; + var clbk = function(rel, i){ + if ( rel.path == slideRel.path ) rel.data = inStr; + if ( !rel.data ) intEmpty++; + } // STEP 1: Set data for this rel, count outstanding gObjPptx.slides.forEach(function(slide, i) { - slide.rels.forEach(function(rel, i){ - if ( rel.path == slideRel.path ) rel.data = inStr; - if ( !rel.data ) intEmpty++; - }); + slide.rels.forEach(clbk); }); if (gObjPptx.properLayoutMasterInUse) { gObjPptx.layoutDefinitions.forEach(function(layout, i) { - layout.rels.forEach(function(rel, i){ - if ( rel.path == slideRel.path ) rel.data = inStr; - if ( !rel.data ) intEmpty++; - }); + layout.rels.forEach(clbk); }); - if (gObjPptx.masterSlide) { - gObjPptx.masterSlide.rels.forEach(function(rel, i) { - if ( rel.path == slideRel.path ) rel.data = inStr; - if ( !rel.data ) intEmpty++; - }); - } + gObjPptx.masterSlide.rels.forEach(clbk); } // STEP 2: Continue export process if all rels have base64 `data` now if ( intEmpty == 0 ) doExportPresentation(); @@ -2955,12 +3027,6 @@ var PptxGenJS = function(){ strXml += ' '; }); }); - if (gObjPptx.masterSlide) { - gObjPptx.masterSlide.rels.forEach(function(rel, idy){ - if ( rel.type != 'image' && rel.type != 'online' && rel.type != 'chart' && rel.extn != 'm4v' && strXml.indexOf(rel.type) == -1 ) - strXml += ' '; - }); - } strXml += ' '; strXml += ' '; @@ -2971,39 +3037,36 @@ var PptxGenJS = function(){ strXml += ' '; strXml += ' '; strXml += ' '; - gObjPptx.slides.forEach(function(slide,idx){ + + gObjPptx.slides.forEach(function(slide, idx){ strXml += ''; strXml += ''; if (!gObjPptx.properLayoutMasterInUse) { strXml += ''; } - }); - if (gObjPptx.properLayoutMasterInUse) { - gObjPptx.layoutDefinitions.forEach(function(layout, idx) { - strXml += ''; - }) - } - - // Add charts (if any) - gObjPptx.slides.forEach(function(slide,idx){ - slide.rels.forEach(function(rel,idy){ + // add charts if any + slide.rels.forEach(function(rel){ if ( rel.type == 'chart' ) { strXml += ' '; } }); }); - gObjPptx.layoutDefinitions.forEach(function(layout,idx){ - layout.rels.forEach(function(rel,idy){ - if ( rel.type == 'chart' ) { - strXml += ' '; - } + + if (gObjPptx.properLayoutMasterInUse) { + gObjPptx.layoutDefinitions.forEach(function(layout, idx) { + strXml += ''; + layout.rels.forEach(function(rel){ + if ( rel.type == 'chart' ) { + strXml += ' '; + } + }); }); - }); - if (gObjPptx.masterSlide) { - gObjPptx.masterSlide.rels.forEach(function(rel,idy){ + gObjPptx.masterSlide.rels.forEach(function(rel) { if ( rel.type == 'chart' ) { strXml += ' '; } + if ( rel.type != 'image' && rel.type != 'online' && rel.type != 'chart' && rel.extn != 'm4v' && strXml.indexOf(rel.type) == -1 ) + strXml += ' '; }); } @@ -3093,6 +3156,9 @@ var PptxGenJS = function(){ return strXml; } + /** + * (The old way of creating layouts.) + */ function makeXmlSlideLayout() { var strXml = ''+CRLF; strXml += ''+CRLF @@ -3153,7 +3219,7 @@ var PptxGenJS = function(){ strSlideXml += ''; // STEP 4: Loop over all Slide.data objects and add them to this slide =============================== - $.each(inSlide.data, function(idx,slideObj){ + inSlide.data.forEach(function(slideObj, idx) { var x = 0, y = 0, cx = getSmartParseNumber('75%','X'), cy = 0; var locationAttr = "", shapeType = null; @@ -3619,11 +3685,11 @@ var PptxGenJS = function(){ /** * Generates the XML layout resource from a layout object - * @param {olbject} inLayout - The slide object to transform into XML - * @return {string} strSlideXml - Slide OOXML + * @param {Object} slideLayoutObject slide object that represents layout + * @return {String} complete XML string ready to be saved as a file */ - function makeXmlLayout(inLayout) { - var strSlideXml = gObjPptxGenerators.xmlSlideLayout(inLayout); + function makeXmlLayout(slideLayoutObject) { + var strSlideXml = gObjPptxGenerators.slideObjectToXml(slideLayoutObject); var strXml = '' + CRLF; strXml += ''; strXml += strSlideXml; @@ -3633,15 +3699,18 @@ var PptxGenJS = function(){ } /** - * Generates the XML master resource. - * @param {object} inMaster - The master slide object to transform into XML - * @return {string} Slide OOXML + * Generates XML for the master file.. + * @param {Object} slideObject slide object that represents master slide layout + * @return {String} complete XML string ready to be saved as a file */ - function makeXmlMaster(inMasterSlide) { - var strSlideXml = gObjPptxGenerators.xmlSlideLayout(inMasterSlide); + function makeXmlMaster(slideObject) { + var strSlideXml = gObjPptxGenerators.slideObjectToXml(slideObject); + + // pass layouts as static rels because they are not referrenced any time var layoutDefs = gObjPptx.layoutDefinitions.map(function(layoutDef, idx) { - return ''; + return ''; }); + var strXml = '' + CRLF; strXml += ''; strXml += strSlideXml; @@ -3679,35 +3748,56 @@ var PptxGenJS = function(){ return strXml; } - function makeXmlSlideLayoutRel(inLayoutNum) { - return gObjPptxGenerators.xmlSlideLayoutRelations( - gObjPptx.properLayoutMasterInUse ? gObjPptx.layoutDefinitions[inLayoutNum - 1] : gObjPptx.slides[inLayoutNum - 1], + /** + * Generates XML string for a slide layout relation file. + * @param {Number} layoutNumber 1-indexed number of a layout that relations are generated for + * @return {String} complete XML string ready to be saved as a file + */ + function makeXmlSlideLayoutRel(layoutNumber) { + return gObjPptxGenerators.slideObjectRelationsToXml( + gObjPptx.properLayoutMasterInUse ? gObjPptx.layoutDefinitions[layoutNumber - 1] : gObjPptx.slides[layoutNumber - 1], [{ target: "../slideMasters/slideMaster1.xml", type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"}] ); } - function makeXmlSlideRel(inSlideNum) { - return gObjPptxGenerators.xmlSlideLayoutRelations( - gObjPptx.slides[inSlideNum - 1], - [{ target: '../slideLayouts/slideLayout'+ ( gObjPptx.properLayoutMasterInUse ? getLayoutIdxForSlide(inSlideNum) : inSlideNum ) +'.xml', type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout"}] + /** + * Generates XML string for a slide relation file. + * @param {Number} slideNumber 1-indexed number of a layout that relations are generated for + * @return {String} complete XML string ready to be saved as a file + */ + function makeXmlSlideRel(slideNumber) { + return gObjPptxGenerators.slideObjectRelationsToXml( + gObjPptx.slides[slideNumber - 1], + [{ target: '../slideLayouts/slideLayout'+ ( gObjPptx.properLayoutMasterInUse ? getLayoutIdxForSlide(slideNumber) : slideNumber ) +'.xml', type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout"}] ); } - function makeXmlMasterRel(inMasterSlide) { - var relCount = inMasterSlide.rels.length + + /** + * Generates XML string for the master file. + * @param {Object} masterSlideObject slide object + * @return {String} complete XML string ready to be saved as a file + */ + function makeXmlMasterRel(masterSlideObject) { + var relCount = masterSlideObject.rels.length var defaultRels = gObjPptx.layoutDefinitions.map(function(layoutDef, idx) { return { target: '../slideLayouts/slideLayout'+ (idx + 1) +'.xml', type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout' }; }); defaultRels.push({ target: '../theme/theme1.xml', type: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme' }); - return gObjPptxGenerators.xmlSlideLayoutRelations( - inMasterSlide, + return gObjPptxGenerators.slideObjectRelationsToXml( + masterSlideObject, defaultRels ); } - function getLayoutIdxForSlide(inSlideNum) { - var layoutName = gObjPptx.slide2layoutMapping[inSlideNum - 1], + /** + * For the passed slide number, resolves name of a layout that is used for. + * @param {Number} slideNumber + * @return {String} layout name + */ + function getLayoutIdxForSlide(slideNumber) { + var layoutName = gObjPptx.slide2layoutMapping[slideNumber - 1], layoutIdx; for (var i = 0, len = gObjPptx.layoutDefinitions.length; i < len; i++) { if (gObjPptx.layoutDefinitions[i].name === layoutName) { @@ -3717,6 +3807,9 @@ var PptxGenJS = function(){ throw Error('Layout "' + layoutName + '" is not specified in this presentation.'); } + /** + * (the old way of generating master) + */ function makeXmlSlideMaster() { var intSlideLayoutId = 2147483649; var strXml = ''+CRLF @@ -3767,6 +3860,9 @@ var PptxGenJS = function(){ return strXml; } + /** + * (the old way of generating master relations) + */ function makeXmlSlideMasterRel() { // FIXME: create a slideLayout for each SLDIE var strXml = ''+CRLF @@ -3936,6 +4032,9 @@ var PptxGenJS = function(){ } } + /** + * Turns on the new way of processing master slide and slide layouts. + */ this.useProperLayoutMaster = function() { gObjPptx.properLayoutMasterInUse = true; } @@ -3994,10 +4093,10 @@ var PptxGenJS = function(){ gObjPptx.slides.forEach(function(slide,idx){ intRels += encodeImageRelations(slide, arrRelsDone); }); - gObjPptx.layoutDefinitions.forEach(function(layout, idx){ - intRels += encodeImageRelations(layout, arrRelsDone); - }); - if (gObjPptx.masterSlide) { + if (this.properLayoutMasterInUse) { + gObjPptx.layoutDefinitions.forEach(function(layout, idx){ + intRels += encodeImageRelations(layout, arrRelsDone); + }); intRels += encodeImageRelations(gObjPptx.masterSlide, arrRelsDone); } @@ -4079,7 +4178,10 @@ var PptxGenJS = function(){ // FUTURE: slideObj.addImage = function(opt){ // NOTE: Remote images (eg: "http://whatev.com/blah"/from web and/or remote server arent supported yet - we'd need to create an , load it, then send to canvas: https://stackoverflow.com/questions/164181/how-to-fetch-a-remote-image-to-display-in-a-canvas) slideObj.addImage = function( strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData ) { - gObjPptxGenerators.addImageDefinition(strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData, gObjPptx.slides[slideNum]); + if (intPosX === undefined && typeof(strImagePath) === "object") { + intPosX = gObjPptx.slides[slideNum]; + } + gObjPptxGenerators.addImageDefinition(strImagePath, intPosX || gObjPptx.slides[slideNum], intPosY, intSizeX, intSizeY, strImageData, gObjPptx.slides[slideNum]); return this; }; @@ -4400,6 +4502,7 @@ var PptxGenJS = function(){ /** * Sets a master slide and color mappings. * @param {Object} masterDef + * @return {Object} this */ this.setMasterSlide = function setMasterSlide(masterDef) { var slideObj = { @@ -4408,13 +4511,15 @@ var PptxGenJS = function(){ rels: [] }; - gObjPptxGenerators.createSlideDefinition(masterDef || {}, slideObj); + gObjPptxGenerators.createSlideObject(masterDef || {}, slideObj); gObjPptx.masterSlide = slideObj; + return this; }; /** - * Add a new layout to the presentation. - * @returns {Object} layoutObj + * Add a new slide layout to the presentation. + * @param {Object} layoutDef layout definition + * @return {Object} this */ this.addLayoutSlide = function addNewLayoutSlide(layoutDef) { var layoutObj = {}; @@ -4448,6 +4553,9 @@ var PptxGenJS = function(){ // FUTURE: layoutObj.addImage = function(opt){ // NOTE: Remote images (eg: "http://whatev.com/blah"/from web and/or remote server arent supported yet - we'd need to create an , load it, then send to canvas: https://stackoverflow.com/questions/164181/how-to-fetch-a-remote-image-to-display-in-a-canvas) layoutObj.addImage = function( strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData ) { + if (intPosX === undefined && typeof(strImagePath) === "object") { + intPosX = gObjPptx.layoutDefinitions[layoutNum]; + } gObjPptxGenerators.addImageDefinition(strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData, gObjPptx.layoutDefinitions[layoutNum]); return this; }; @@ -4486,7 +4594,7 @@ var PptxGenJS = function(){ if ( layoutDef.slideNumber && typeof layoutDef.slideNumber === 'object' ) layoutObj.slideNumber(layoutDef.slideNumber); // LAST: Return this Slide - return layoutObj; + return this; }; /** From ee3cec99480ff7d22a337f796d0299530dd33274 Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Tue, 22 Aug 2017 12:55:10 +0200 Subject: [PATCH 21/27] Useless code removed from addLayoutSlide, `createSlideObject` used instead --- dist/pptxgen.js | 65 ++++--------------------------------------------- 1 file changed, 5 insertions(+), 60 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index 067e972bd..0d8b9c4e9 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -4517,83 +4517,28 @@ var PptxGenJS = function(){ }; /** - * Add a new slide layout to the presentation. + * Adds a new slide layout to the presentation. * @param {Object} layoutDef layout definition * @return {Object} this */ this.addLayoutSlide = function addNewLayoutSlide(layoutDef) { var layoutObj = {}; - var layoutNum = gObjPptx.layoutDefinitions.length; - var layoutObjNum = 0; if (!layoutDef.title) { throw Error("Layout requires to be named. Specify `title` in its definition object."); } // A: Add this SLIDE to PRESENTATION, Add default values as well - gObjPptx.layoutDefinitions[layoutNum] = { - slide: layoutObj, + layoutObj = { + slide: {}, name: layoutDef.title, data: [], rels: [], slideNumberObj: null }; - layoutObj.slideNumber = function( inObj ) { - if ( inObj && typeof inObj === 'object' ) gObjPptx.layoutDefinitions[layoutNum].slideNumberObj = inObj; - else return gObjPptx.layoutDefinitions[layoutNum].slideNumberObj; - }; - - layoutObj.addChart = function ( type, data, opt ) { - gObjPptxGenerators.addChartDefinition(type, data, opt, gObjPptx.layoutDefinitions[layoutNum]); - return this; - } - - // WARN: DEPRECATED: Will soon take a single {object} as argument (per current docs 20161120) - // FUTURE: layoutObj.addImage = function(opt){ - // NOTE: Remote images (eg: "http://whatev.com/blah"/from web and/or remote server arent supported yet - we'd need to create an , load it, then send to canvas: https://stackoverflow.com/questions/164181/how-to-fetch-a-remote-image-to-display-in-a-canvas) - layoutObj.addImage = function( strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData ) { - if (intPosX === undefined && typeof(strImagePath) === "object") { - intPosX = gObjPptx.layoutDefinitions[layoutNum]; - } - gObjPptxGenerators.addImageDefinition(strImagePath, intPosX, intPosY, intSizeX, intSizeY, strImageData, gObjPptx.layoutDefinitions[layoutNum]); - return this; - }; - - layoutObj.addShape = function( shape, opt ) { - gObjPptxGenerators.addShapeDefinition(shape, opt, gObjPptx.layoutDefinitions[layoutNum]) - return this; - }; - - layoutObj.addText = function( text, opt ) { - gObjPptxGenerators.addTextDefinition(text, opt, gObjPptx.layoutDefinitions[layoutNum]); - return this; - }; - - $.each(layoutDef, function(key, val) { - - if ( key == 'bkgd' ) { - gObjPptxGenerators.addBackgroundDefinition(val, gObjPptx.layoutDefinitions[layoutNum]); - } - - // Add all Slide Master objects in the order they were given (Issue#53) - if ( key == "objects" && Array.isArray(val) && val.length > 0 ) { - val.forEach(function(object,idx){ - var key = Object.keys(object)[0]; - if ( MASTER_OBJECTS[key] && key == 'chart' ) layoutObj.addChart( CHART_TYPES[(object.chart.type||'').toUpperCase()], object.chart.data, object.chart.opts ); - else if ( MASTER_OBJECTS[key] && key == 'image' ) layoutObj.addImage(object[key]); - else if ( MASTER_OBJECTS[key] && key == 'line' ) layoutObj.addShape(gObjPptxShapes.LINE, object[key]); - else if ( MASTER_OBJECTS[key] && key == 'rect' ) layoutObj.addShape(gObjPptxShapes.RECTANGLE, object[key]); - else if ( MASTER_OBJECTS[key] && key == 'text' ) layoutObj.addText(object[key].text, object[key].options); - }); - } - }); - - - // Add Slide Numbers - if ( layoutDef.slideNumber && typeof layoutDef.slideNumber === 'object' ) layoutObj.slideNumber(layoutDef.slideNumber); - - // LAST: Return this Slide + gObjPptxGenerators.createSlideObject(layoutDef, layoutObj); + gObjPptx.layoutDefinitions.push(layoutObj) return this; }; From 390de269c25da480a1c115cf946f368510ef354b Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Tue, 22 Aug 2017 13:08:33 +0200 Subject: [PATCH 22/27] Layout mapping removed, layout name is a part of slide object. When no layout specified, empty used. --- dist/pptxgen.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index 0d8b9c4e9..5e173fbf6 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -123,7 +123,13 @@ var PptxGenJS = function(){ var DEF_CHART_GRIDLINE = { color: "888888", style: "solid", size: 1 }; var DEF_LINE_SHADOW = { type: 'outer', blur: 3, offset: (23000 / 12700), angle: 90, color: '000000', opacity: 0.35, rotateWithShape: true }; var DEF_TEXT_SHADOW = { type: 'outer', blur: 8, offset: 4, angle: 270, color: '000000', opacity: 0.75 }; - + var DEF_EMPTY_LAYOUT = { + slide: {}, + name: '[ default ]', + data: [], + rels: [], + slideNumberObj: null + }; var LAYOUT_IDX_SERIES_BASE = 2147483649; // A: Create internal pptx object @@ -148,9 +154,7 @@ var PptxGenJS = function(){ rels: [] }; /** @type {object[]} slide layout definition objects, used for generating slide layout files */ - gObjPptx.layoutDefinitions = []; - /** @type {object} determines which layout is used for a specific slide */ - gObjPptx.slide2layoutMapping = []; + gObjPptx.layoutDefinitions = [DEF_EMPTY_LAYOUT]; /** @type {boolean} if true proper master and layouts are used, otherwise the old way of templating expected */ gObjPptx.properLayoutMasterInUse = false; /** @type {Number} global counter for included images (used for index in their filenames) */ @@ -3797,7 +3801,7 @@ var PptxGenJS = function(){ * @return {String} layout name */ function getLayoutIdxForSlide(slideNumber) { - var layoutName = gObjPptx.slide2layoutMapping[slideNumber - 1], + var layoutName = gObjPptx.slides[slideNumber - 1].layout, layoutIdx; for (var i = 0, len = gObjPptx.layoutDefinitions.length; i < len; i++) { if (gObjPptx.layoutDefinitions[i].name === layoutName) { @@ -4115,7 +4119,6 @@ var PptxGenJS = function(){ var slideObjNum = 0; var pageNum = (slideNum + 1); - gObjPptx.slide2layoutMapping.push(inMaster); // A: Add this SLIDE to PRESENTATION, Add default values as well gObjPptx.slides[slideNum] = { slide: slideObj, @@ -4124,7 +4127,8 @@ var PptxGenJS = function(){ data: [], rels: [], slideNumberObj: null, - hasSlideNumber: false + hasSlideNumber: false, + layout: inMaster || '[ default ]' }; // ========================================================================== @@ -4522,14 +4526,11 @@ var PptxGenJS = function(){ * @return {Object} this */ this.addLayoutSlide = function addNewLayoutSlide(layoutDef) { - var layoutObj = {}; - if (!layoutDef.title) { throw Error("Layout requires to be named. Specify `title` in its definition object."); } - // A: Add this SLIDE to PRESENTATION, Add default values as well - layoutObj = { + var layoutObj = { slide: {}, name: layoutDef.title, data: [], From 315a7a9f7b5bb309395806143790e769586ad58b Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Tue, 22 Aug 2017 13:44:39 +0200 Subject: [PATCH 23/27] Readme update. --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6fc7fac01..5aae37223 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Quickly and easily create PowerPoint presentations with a few simple JavaScript - [Slide Master Examples](#slide-master-examples) - [Slide Master Object Options](#slide-master-object-options) - [Sample Slide Master File](#sample-slide-master-file) + - [Layouting – New Approach](#layouting-–-new-approach) - [Table-to-Slides Feature](#table-to-slides-feature) - [Table-to-Slides Options](#table-to-slides-options) - [Table-to-Slides HTML Options](#table-to-slides-html-options) @@ -102,7 +103,7 @@ Quickly and easily create PowerPoint presentations with a few simple JavaScript ************************************************************************************************** # Live Demo -Use JavaScript to Create a PowerPoint presentation with your web browser right now: +Use JavaScript to Create a PowerPoint presentation with your web browser right now: [https://gitbrent.github.io/PptxGenJS](https://gitbrent.github.io/PptxGenJS) # Installation @@ -827,12 +828,12 @@ Animated GIFs can be included in Presentations in one of two ways: | `hyperlink` | string | | | add hyperlink | object with `url` and optionally `tooltip`. Ex: `{ hyperlink:{url:'https://github.com'} }` | | `path` | string | | | image path | Same as used in an (img src="") tag. (either `data` or `path` is required) | -**NOTES** +**NOTES** * SVG images are not currently supported in PowerPoint or PowerPoint Online (even when encoded into base64). PptxGenJS does properly encode and include SVG images, so they will begin showing once Microsoft adds support for this image type. * Using `path` to add remote images (images from a different server) is not currently supported. -**Deprecation Warning** +**Deprecation Warning** Old positional parameters (e.g.: `slide.addImage('images/chart.png', 1, 1, 6, 3)`) are now deprecated as of 1.1.0 ### Image Examples @@ -914,7 +915,7 @@ PptxGenJS allows you to define Master Slides via objects that can then be used t functionality. Slide Masters are defined using the same object style used in Slides. Add these objects as a variable to a file that -is included in the script src tags on your page, then reference them by name in your code. +is included in the script src tags on your page, then reference them by name in your code. E.g.: `` ## Slide Master Examples @@ -941,9 +942,9 @@ var gObjPptxMasters = { ] } }; -``` +``` Every object added to the global master slide variable `gObjPptxMasters` can then be referenced -by their key names that you created (e.g.: "TITLE_SLIDE"). +by their key names that you created (e.g.: "TITLE_SLIDE"). **TIP:** Pre-encode your images (base64) and add the string as the optional data key/val @@ -975,9 +976,46 @@ pptx.save(); | `title` | string | | | Slide title | some title | ## Sample Slide Master File -A sample masters file is included in the distribution folder and contain a couple of different slides to get you started. +A sample masters file is included in the distribution folder and contain a couple of different slides to get you started. Location: `PptxGenJS/dist/pptxgen.masters.js` +## Layouting – New Approach +The new approach follows the principles used in PowerPoint. That means, each slide is attached to a slide layout that is attached to the master slide. Slide layouts inherit layout specified in the master file. Thus, if you apply any layout to a slide, objects specified in master slide and the slide layout will all appear in the slide. Advantage of this principle resides in the layouts variability. PPTX generated this way includes layout definitions easily changeable by user so that they can manipulate repetitive elements globally, for all the slides at once. + +To enable the new approach, call the following method any time before saving presentation: +```javascript +pptx.useProperLayoutMaster(); +``` + +### Master Slide +Defining the master slide is optional. It remains empty if not specified. If you want to specify layout that will apply globally to all slides, set up the master slide as following: + +```javascript +pptx.setMasterSlide({ + bkgd: 'ff0000', + objects: [ { text: { text: 'Hello World', x: 1, y: 1 } } ] +}); +``` +The configuration object is still the same as in [Slide Master Object Options](#slide-master-object-options) except for the `title` property that has no effect in this case. + +### Slide Layout +Slide layout enables you to create more specific slide designs based on the master slide. The same configuration object as in the master slide is used to describe slide layout design; but this time, the `title` property is required and needs to be unique. + +```javascript +pptx.addLayoutSlide({ + title: 'welcome' + bkgd: 'ff0000', + objects: [ { text: { text: 'Hello World', x: 1, y: 1 } } ] +}); +``` + +Then, the defined layouts can be applied to a slide passing their name as the first argument: +```javascript +pptx.addNewSlide('welcome', { bkgd: '0000ff' }); +``` + +You are not required to specify layout name for each slide. if you pass falsy (or no) value, an empty layout already defined inside the library will be used. + ************************************************************************************************** # Table-to-Slides Feature Syntax: @@ -1051,7 +1089,7 @@ Add a button to a webpage that will create a Presentation using whatever table d onclick="{ var pptx = new PptxGenJS(); pptx.addSlidesForTable('tableId',{ master:pptx.masters.MASTER_SLIDE }); pptx.save(); }"> ``` -**SharePoint Integration** +**SharePoint Integration** Placing a button like this into a WebPart is a great way to add "Export to PowerPoint" functionality to SharePoint. (You'd also need to add the 4 ` - - - - - - - - - - - - - - - - - - \ No newline at end of file From 65a0cca5896944fcf7e89e384d923fad679c717f Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Mon, 28 Aug 2017 09:54:56 +0200 Subject: [PATCH 25/27] Scheme colors support added to parts where was missing. --- dist/pptxgen.js | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index 5e173fbf6..539a0e35e 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -764,7 +764,7 @@ var PptxGenJS = function(){ var cellValign = (cellOpts.valign) ? ' anchor="'+ cellOpts.valign.replace(/^c$/i,'ctr').replace(/^m$/i,'ctr').replace('center','ctr').replace('middle','ctr').replace('top','t').replace('btm','b').replace('bottom','b') +'"' : ''; var cellColspan = (cellOpts.colspan) ? ' gridSpan="'+ cellOpts.colspan +'"' : ''; var cellRowspan = (cellOpts.rowspan) ? ' rowSpan="'+ cellOpts.rowspan +'"' : ''; - var cellFill = ((cell.optImp && cell.optImp.fill) || cellOpts.fill ) ? ' ' : ''; + var cellFill = ((cell.optImp && cell.optImp.fill) || cellOpts.fill ) ? ' ' + createColorElement((cell.optImp && cell.optImp.fill) || cellOpts.fill.replace('#','')) +'' : ''; var cellMargin = ( cellOpts.margin == 0 || cellOpts.margin ? cellOpts.margin : (cellOpts.marginPt || DEF_CELL_MARGIN_PT) ); if ( !Array.isArray(cellMargin) && typeof cellMargin === 'number' ) cellMargin = [cellMargin,cellMargin,cellMargin,cellMargin]; cellMargin = ' marL="'+ cellMargin[3]*ONEPT +'" marR="'+ cellMargin[1]*ONEPT +'" marT="'+ cellMargin[0]*ONEPT +'" marB="'+ cellMargin[2]*ONEPT +'"'; @@ -783,15 +783,15 @@ var PptxGenJS = function(){ // 5: Borders: Add any borders if ( cellOpts.border && typeof cellOpts.border === 'string' ) { - strXml += ' '; - strXml += ' '; - strXml += ' '; - strXml += ' '; + strXml += ' ' + createColorElement(cellOpts.border) + ''; + strXml += ' ' + createColorElement(cellOpts.border) + ''; + strXml += ' ' + createColorElement(cellOpts.border) + ''; + strXml += ' ' + createColorElement(cellOpts.border) + ''; } else if ( cellOpts.border && Array.isArray(cellOpts.border) ) { $.each([ {idx:3,name:'lnL'}, {idx:1,name:'lnR'}, {idx:0,name:'lnT'}, {idx:2,name:'lnB'} ], function(i,obj){ if ( cellOpts.border[obj.idx] ) { - var strC = ''; + var strC = '' + createColorElement((cellOpts.border[obj.idx].color) ? cellOpts.border[obj.idx].color : '666666') + ''; var intW = (cellOpts.border[obj.idx] && (cellOpts.border[obj.idx].pt || cellOpts.border[obj.idx].pt == 0)) ? (ONEPT * Number(cellOpts.border[obj.idx].pt)) : ONEPT; strXml += ''+ strC +''; } @@ -800,7 +800,7 @@ var PptxGenJS = function(){ } else if ( cellOpts.border && typeof cellOpts.border === 'object' ) { var intW = (cellOpts.border && (cellOpts.border.pt || cellOpts.border.pt == 0) ) ? (ONEPT * Number(cellOpts.border.pt)) : ONEPT; - var strClr = ''; + var strClr = '' + createColorElement((cellOpts.border.color) ? cellOpts.border.color.replace('#','') : '666666') + ''; var strAttr = ''; - strSlideXml += ''; - strSlideXml += '' + strSlideXml += createColorElement(slideItemObj.options.shadow.color, ''); strSlideXml += ''; strSlideXml += ''; } @@ -1940,8 +1939,7 @@ var PptxGenJS = function(){ strXml += ''; - strXml += ''; // TODO: should accept scheme colors implemented in Issue #135 - strXml += '' + strXml += createColorElement(color, ''); strXml += ''; return strXml; @@ -2008,7 +2006,7 @@ var PptxGenJS = function(){ strXml = '<'+ tagName + '>'; strXml += ' '; strXml += ' '; - strXml += ' '; // should accept scheme colors as implemented in PR 135 + strXml += ' ' + createColorElement(glOpts.color || defaults.color); // should accept scheme colors as implemented in PR 135 strXml += ' '; strXml += ' '; strXml += ' '; @@ -2241,8 +2239,8 @@ var PptxGenJS = function(){ strXml += ' '; strXml += ' '; strXml += ' '; - strXml += ' '; - strXml += ' '; + strXml += createColorElement(colors[index % colors.length]); + strXml += ' '; strXml += ' '; strXml += ' '; strXml += ' '; @@ -2345,7 +2343,7 @@ var PptxGenJS = function(){ strXml += ' '; if ( !!rel.opts.catAxisLineShow || typeof rel.opts.catAxisLineShow === 'undefined' ) { strXml += ''; - strXml += ' '; + strXml += createColorElement(rel.opts.axisLineColor ? rel.opts.axisLineColor : DEF_CHART_GRIDLINE.color); strXml += ''; } else { @@ -2404,7 +2402,7 @@ var PptxGenJS = function(){ strXml += ' '; if ( !!rel.opts.valAxisLineShow || typeof rel.opts.valAxisLineShow === 'undefined' ) { strXml += ''; - strXml += ' '; + strXml += createColorElement(rel.opts.axisLineColor ? rel.opts.axisLineColor : DEF_CHART_GRIDLINE.color); strXml += ''; } else { @@ -2647,13 +2645,13 @@ var PptxGenJS = function(){ sizeAttr = ' sz="' + opts.fontSize + '00"'; } strXml += ' '; - strXml += ' '; + strXml += ' ' + createColorElement(opts.color || '000000') + ''; strXml += ' '; strXml += ' '; strXml += ' '; strXml += ' '; strXml += ' '; - strXml += ' '; + strXml += ' ' + createColorElement(opts.color || '000000') + ''; strXml += ' '; strXml += ' '; strXml += ' '+ (decodeXmlEntities(opts.title) || '') +''; From c4ae8da9ae5c3a90d4eb01e5fdb7013126526edc Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Wed, 30 Aug 2017 15:56:04 +0200 Subject: [PATCH 26/27] Factory method exported. Using proper layout and master turned on by constructor arg. --- dist/pptxgen.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dist/pptxgen.js b/dist/pptxgen.js index 539a0e35e..e278b3963 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -60,7 +60,7 @@ if ( NODEJS ) { var gObjPptxColors = require('../dist/pptxgen.colors.js'); } -var PptxGenJS = function(){ +var PptxGenJS = function(useProperLayoutMaster){ // CONSTANTS var APP_VER = "1.8.0-beta"; var APP_REL = "20170822"; @@ -156,7 +156,7 @@ var PptxGenJS = function(){ /** @type {object[]} slide layout definition objects, used for generating slide layout files */ gObjPptx.layoutDefinitions = [DEF_EMPTY_LAYOUT]; /** @type {boolean} if true proper master and layouts are used, otherwise the old way of templating expected */ - gObjPptx.properLayoutMasterInUse = false; + gObjPptx.properLayoutMasterInUse = useProperLayoutMaster; /** @type {Number} global counter for included images (used for index in their filenames) */ gObjPptx.imageCounter = 0; /** @type {Number} global counter for included charts (used for index in their filenames) */ @@ -4702,4 +4702,5 @@ if ( NODEJS ) { // B: Export module module.exports = new PptxGenJS(); + module.exports.factory = PptxGenJS; } From a5214c897ca36da96c6b1bdd62ad92a54fd3a7dc Mon Sep 17 00:00:00 2001 From: Michal Kacerovsky Date: Wed, 30 Aug 2017 15:59:21 +0200 Subject: [PATCH 27/27] Readme update, original method removed. --- README.md | 4 ++-- dist/pptxgen.js | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5aae37223..ede2535a3 100644 --- a/README.md +++ b/README.md @@ -982,9 +982,9 @@ Location: `PptxGenJS/dist/pptxgen.masters.js` ## Layouting – New Approach The new approach follows the principles used in PowerPoint. That means, each slide is attached to a slide layout that is attached to the master slide. Slide layouts inherit layout specified in the master file. Thus, if you apply any layout to a slide, objects specified in master slide and the slide layout will all appear in the slide. Advantage of this principle resides in the layouts variability. PPTX generated this way includes layout definitions easily changeable by user so that they can manipulate repetitive elements globally, for all the slides at once. -To enable the new approach, call the following method any time before saving presentation: +To enable the new approach, pass `true` as the first constructor argument: ```javascript -pptx.useProperLayoutMaster(); +var pptx = new PptxGenJS(true); ``` ### Master Slide diff --git a/dist/pptxgen.js b/dist/pptxgen.js index e278b3963..02be6191a 100644 --- a/dist/pptxgen.js +++ b/dist/pptxgen.js @@ -4034,13 +4034,6 @@ var PptxGenJS = function(useProperLayoutMaster){ } } - /** - * Turns on the new way of processing master slide and slide layouts. - */ - this.useProperLayoutMaster = function() { - gObjPptx.properLayoutMasterInUse = true; - } - /** * Sets the Presentation's Title */