pptxgenjs
Version:
JavaScript library that creates PowerPoint presentations
785 lines (708 loc) • 287 kB
JavaScript
/*\
|*| :: pptxgen.js ::
|*|
|*| JavaScript framework that creates PowerPoint (pptx) presentations
|*| https://github.com/gitbrent/PptxGenJS
|*|
|*| This framework is released under the MIT Public License (MIT)
|*|
|*| PptxGenJS (C) 2015-2018 Brent Ely -- https://github.com/gitbrent
|*|
|*| Some code derived from the OfficeGen project:
|*| github.com/Ziv-Barber/officegen/ (Copyright 2013 Ziv Barber)
|*|
|*| Permission is hereby granted, free of charge, to any person obtaining a copy
|*| of this software and associated documentation files (the "Software"), to deal
|*| in the Software without restriction, including without limitation the rights
|*| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|*| copies of the Software, and to permit persons to whom the Software is
|*| furnished to do so, subject to the following conditions:
|*|
|*| The above copyright notice and this permission notice shall be included in all
|*| copies or substantial portions of the Software.
|*|
|*| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|*| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|*| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|*| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|*| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|*| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|*| SOFTWARE.
\*/
/*
PPTX Units are "DXA" (except for font sizing)
....: There are 1440 DXA per inch. 1 inch is 72 points. 1 DXA is 1/20th's of a point (20 DXA is 1 point).
....: There is also something called EMU's (914400 EMUs is 1 inch, 12700 EMUs is 1pt).
SEE: https://startbigthinksmall.wordpress.com/2010/01/04/points-inches-and-emus-measuring-units-in-office-open-xml/
|
OBJECT LAYOUTS: 16x9 (10" x 5.625"), 16x10 (10" x 6.25"), 4x3 (10" x 7.5"), Wide (13.33" x 7.5") and Custom (any size)
|
REFS:
* "Structure of a PresentationML document (Open XML SDK)"
* @see: https://msdn.microsoft.com/en-us/library/office/gg278335.aspx
* TableStyleId enumeration
* @see: https://msdn.microsoft.com/en-us/library/office/hh273476(v=office.14).aspx
*/
// Polyfill for IE11 (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger)
Number.isInteger = Number.isInteger || function(value) {
return typeof value === "number" && isFinite(value) && Math.floor(value) === value;
};
// Detect Node.js (NODEJS is ultimately used to determine how to save: either `fs` or web-based, so using fs-detection is perfect)
var NODEJS = false;
{
// NOTE: `NODEJS` determines which network library to use, so using fs-detection is apropos.
if ( typeof module !== 'undefined' && module.exports && typeof require === 'function' ) {
try {
require.resolve('fs');
NODEJS = true;
}
catch (ex) {
NODEJS = false;
}
}
}
// [Node.js] <script> includes
if ( NODEJS ) {
var gObjPptxColors = require('../dist/pptxgen.colors.js');
var gObjPptxShapes = require('../dist/pptxgen.shapes.js');
}
var PptxGenJS = function(){
// APP
var APP_VER = "2.3.0";
var APP_BLD = "20180912";
// CONSTANTS
var MASTER_OBJECTS = {
'chart' : { name:'chart' },
'image' : { name:'image' },
'line' : { name:'line' },
'rect' : { name:'rect' },
'text' : { name:'text' },
'placeholder': { name:'placeholder' }
};
var LAYOUTS = {
'LAYOUT_4x3' : { name: 'screen4x3', width: 9144000, height: 6858000 },
'LAYOUT_16x9' : { name: 'screen16x9', width: 9144000, height: 5143500 },
'LAYOUT_16x10': { name: 'screen16x10', width: 9144000, height: 5715000 },
'LAYOUT_WIDE' : { name: 'custom', width: 12192000, height: 6858000 },
'LAYOUT_USER' : { name: 'custom', width: 12192000, height: 6858000 }
};
var BASE_SHAPES = {
'RECTANGLE': { 'displayName': 'Rectangle', 'name': 'rect', 'avLst': {} },
'LINE' : { 'displayName': 'Line', 'name': 'line', 'avLst': {} }
};
var PLACEHOLDER_TYPES = {
'title': { name: 'title' },
'body' : { name: 'body' },
'image': { name: 'pic' },
'chart': { name: 'chart' },
'table': { name: 'tbl' },
'media': { name: 'media' }
};
// NOTE: 20170304: BULLET_TYPES: Only default is used so far. I'd like to combine the two pieces of code that use these before implementing these as options
// Since we close <p> within the text object bullets, its slightly more difficult than combining into a func and calling to get the paraProp
// and i'm not sure if anyone will even use these... so, skipping for now.
var BULLET_TYPES = {
'DEFAULT' : "•",
'CHECK' : "✓",
'STAR' : "★",
'TRIANGLE': "▶"
};
var CHART_TYPES = {
'AREA' : { 'displayName':'Area Chart', 'name':'area' },
'BAR' : { 'displayName':'Bar Chart' , 'name':'bar' },
'BUBBLE' : { 'displayName':'Bubble Chart', 'name':'bubble' },
'DOUGHNUT': { 'displayName':'Doughnut Chart', 'name':'doughnut' },
'LINE' : { 'displayName':'Line Chart', 'name':'line' },
'PIE' : { 'displayName':'Pie Chart' , 'name':'pie' },
'RADAR' : { 'displayName':'Radar Chart', 'name':'radar' },
'SCATTER' : { 'displayName':'Scatter Chart', 'name':'scatter' }
};
var PIECHART_COLORS = ['5DA5DA','FAA43A','60BD68','F17CB0','B2912F','B276B2','DECF3F','F15854','A7A7A7', '5DA5DA','FAA43A','60BD68','F17CB0','B2912F','B276B2','DECF3F','F15854','A7A7A7'];
var BARCHART_COLORS = ['C0504D','4F81BD','9BBB59','8064A2','4BACC6','F79646','628FC6','C86360', 'C0504D','4F81BD','9BBB59','8064A2','4BACC6','F79646','628FC6','C86360'];
// IMAGES (base64)
{
var IMG_BROKEN = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAB3CAYAAAD1oOVhAAAGAUlEQVR4Xu2dT0xcRRzHf7tAYSsc0EBSIq2xEg8mtTGebVzEqOVIolz0siRE4gGTStqKwdpWsXoyGhMuyAVJOHBgqyvLNgonDkabeCBYW/8kTUr0wsJC+Wfm0bfuvn37Znbem9mR9303mJnf/Pb7ed95M7PDI5JIJPYJV5EC7e3t1N/fT62trdqViQCIu+bVgpIHEo/Hqbe3V/sdYVKHyWSSZmZm8ilVA0oeyNjYmEnaVC2Xvr6+qg5fAOJAz4DU1dURGzFSqZRVqtMpAFIGyMjICC0vL9PExIRWKADiAYTNshYWFrRCARAOEFZcCKWtrY0GBgaUTYkBRACIE4rKZwqACALR5RQAqQCIDqcASIVAVDsFQCSAqHQKgEgCUeUUAPEBRIVTAMQnEBvK5OQkbW9vk991CoAEAMQJxc86BUACAhKUUwAkQCBBOAVAAgbi1ykAogCIH6cAiCIgsk4BEIVAZJwCIIqBVLqiBxANQFgXS0tLND4+zl08AogmIG5OSSQS1gGKwgtANAIRcQqAaAbCe6YASBWA2E6xDyeyDUl7+AKQMkDYYevm5mZHabA/Li4uUiaTsYLau8QA4gLE/hU7wajyYtv1hReDAiAOxQcHBymbzark4BkbQKom/X8dp9Npmpqasn4BIAYAYSnYp+4BBEAMUcCwNOCQsAKZnp62NtQOw8WmwT09PUo+ijaHsOMx7GppaaH6+nolH0Z10K2tLVpdXbW6UfV3mNqBdHd3U1NTk2rtlMRfW1uj2dlZAFGirkRQAJEQTWUTAFGprkRsAJEQTWUTAFGprkRsAJEQTWUTAFGprkRsAJEQTWUTAFGprkRsAJEQTWUTAFGprkRsAJEQTWUTAGHqrm8caPzQ0WC1logbeiC7X3xJm0PvUmRzh45cuki1588FAmVn9BO6P3yF9utrqGH0MtW82S8UN9RA9v/4k7InjhcJFTs/TLVXLwmJV67S7vD7tHF5pKi46fYdosdOcOOGG8j1OcqefbFEJD9Q3GCwDhqT31HklS4A8VRgfYM2Op6k3bt/BQJl58J7lPvwg5JYNccepaMry0LPqFA7hCm39+NNyp2J0172b19QysGINj5CsRtpij57musOViH0QPJQXn6J9u7dlYJSFkbrMYolrwvDAJAC+WWdEpQz7FTgECeUCpzi6YxvvqXoM6eEhqnCSgDikEzUKUE7Aw7xuHctKB5OYU3dZlNR9syQdAaAcAYTC0pXF+39c09o2Ik+3EqxVKqiB7hbYAxZkk4pbBaEM+AQofv+wTrFwylBOQNABIGwavdfe4O2pg5elO+86l99nY58/VUF0byrYsjiSFluNlXYrOHcBar7+EogUADEQ0YRGHbzoKAASBkg2+9cpM1rV0tK2QOcXW7bLEFAARAXIF4w2DrDWoeUWaf4hQIgDiA8GPZ2iNfi0Q8UACkAIgrDbrJ385eDxaPLLrEsFAB5oG6lMPJQPLZZZKAACBGVhcG2Q+bmuLu2nk55e4jqPv1IeEoceiBeX7s2zCa5MAqdstl91vfXwaEGsv/rb5TtOFk6tWXOuJGh6KmnhO9sayrMninPx103JBtXblHkice58cINZP4Hyr5wpkgkdiChEmc4FWazLzenNKa/p0jncwDiqcD6BuWePk07t1asatZGoYQzSqA4nFJ7soNiP/+EUyfc25GI2GG53dHPrKo1g/1Cw4pIXLrzO+1c+/wg7tBbFDle/EbQcjFCPWQJCau5EoBoFpzXHYDwFNJcDiCaBed1ByA8hTSXA4hmwXndAQhPIc3lAKJZcF53AMJTSHM5gGgWnNcdgPAU0lwOIJoF53UHIDyFNJcfSiCdnZ0Ui8U0SxlMd7lcjubn561gh+Y1scFIU/0o/3sgeLO12E2k7UXKYumgFoAYdg8ACIAYpoBh6cAhAGKYAoalA4cAiGEKGJYOHAIghilgWDpwCIAYpoBh6cAhAGKYAoalA4cAiGEKGJYOHAIghilgWDpwCIAYpoBh6ZQ4JB6PKzviYthnNy4d9h+1M5mMlVckkUjsG5dhiBMCEMPg/wuOfrZZ/RSywQAAAABJRU5ErkJggg==';
var IMG_PLAYBTN = 'data:image/png;base64,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';
}
//
var CRLF = '\r\n'; // AKA: Chr(13) & Chr(10)
var EMU = 914400; // One (1) inch (OfficeXML measures in EMU (English Metric Units))
var ONEPT = 12700; // One (1) point (pt)
var LAYOUT_IDX_SERIES_BASE = 2147483649;
var LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
var JSZIP_OUTPUT_TYPES = ['arraybuffer', 'base64', 'binarystring', 'blob', 'nodebuffer', 'uint8array']; /** @see https://stuk.github.io/jszip/documentation/api_jszip/generate_async.html */
var REGEX_HEX_COLOR = /^[0-9a-fA-F]{6}$/;
var SLDNUMFLDID = '{F7021451-1387-4CA6-816F-3879F97B5CBC}';
//
var DEF_CELL_BORDER = { color:"666666" };
var DEF_CELL_MARGIN_PT = [3, 3, 3, 3]; // TRBL-style
var DEF_FONT_COLOR = '000000';
var DEF_FONT_SIZE = 12;
var DEF_FONT_TITLE_SIZE = 18;
var DEF_SLIDE_BKGD = 'FFFFFF';
var DEF_SLIDE_MARGIN_IN = [0.5, 0.5, 0.5, 0.5]; // TRBL-style
var DEF_CHART_GRIDLINE = { color: "888888", style: "solid", size: 1 };
var DEF_SHAPE_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 AXIS_ID_VALUE_PRIMARY = '2094734552';
var AXIS_ID_VALUE_SECONDARY = '2094734553';
var AXIS_ID_CATEGORY_PRIMARY = '2094734554';
var AXIS_ID_CATEGORY_SECONDARY = '2094734555';
// A: Create internal pptx object
var gObjPptx = {};
// B: Set Presentation Property Defaults
{
// Presentation props/metadata
gObjPptx.author = 'PptxGenJS';
gObjPptx.company = 'PptxGenJS';
gObjPptx.revision = '1';
gObjPptx.subject = 'PptxGenJS Presentation';
gObjPptx.title = 'PptxGenJS Presentation';
// PptxGenJS props
gObjPptx.isBrowser = false;
gObjPptx.fileName = 'Presentation';
gObjPptx.fileExtn = '.pptx';
gObjPptx.pptLayout = LAYOUTS['LAYOUT_16x9'];
gObjPptx.rtlMode = false;
gObjPptx.saveCallback = null;
// PptxGenJS data
/** @type {object} master slide layout object */
gObjPptx.masterSlide = {
slide: {},
data : [],
rels : [],
slideNumberObj: null
};
/** @type {Number} global counter for included charts (used for index in their filenames) */
gObjPptx.chartCounter = 0;
/** @type {Number} global counter for included images (used for index in their filenames) */
gObjPptx.imageCounter = 0;
/** @type {object[]} this Presentation's Slide objects */
gObjPptx.slides = [];
/** @type {object[]} slide layout definition objects, used for generating slide layout files */
gObjPptx.slideLayouts = [{
name : 'BLANK',
slide: {},
data : [],
rels : [],
margin: DEF_SLIDE_MARGIN_IN,
slideNumberObj: null
}];
}
// C: Expose shape library to clients
this.charts = CHART_TYPES;
this.colors = ( typeof gObjPptxColors !== 'undefined' ? gObjPptxColors : {} );
this.shapes = ( typeof gObjPptxShapes !== 'undefined' ? gObjPptxShapes : BASE_SHAPES );
// Declare only after `this.colors` is initialized
var SCHEME_COLOR_NAMES = Object.keys(this.colors).map(function(clrKey){return this.colors[clrKey]}.bind(this));
// D: Fall back to base shapes if shapes file was not linked
gObjPptxShapes = ( gObjPptxShapes || this.shapes );
/* ===============================================================================================
|
#####
# # ###### # # ###### ##### ## ##### #### ##### ####
# # ## # # # # # # # # # # # #
# #### ##### # # # ##### # # # # # # # # # ####
# # # # # # # ##### ###### # # # ##### #
# # # # ## # # # # # # # # # # # #
##### ###### # # ###### # # # # # #### # # ####
|
==================================================================================================
*/
var gObjPptxGenerators = {
/**
* 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
*/
addBackgroundDefinition: function addBackgroundDefinition(bkg, target) {
if ( typeof bkg === 'object' && (bkg.src || bkg.path || bkg.data) ) {
// Allow the use of only the data key (`path` isnt reqd)
bkg.src = bkg.src || bkg.path || null;
if (!bkg.src) bkg.src = 'preencoded.png';
var targetRels = target.rels;
var strImgExtn = bkg.src.split('.').pop() || 'png';
if ( strImgExtn == 'jpg' ) strImgExtn = 'jpeg'; // base64-encoded jpg's come out as "data:image/jpeg;base64,/9j/[...]", so correct exttnesion to avoid content warnings at PPT startup
var intRels = targetRels.length + 1;
targetRels.push({
path: bkg.src,
type: 'image/'+strImgExtn,
extn: strImgExtn,
data: (bkg.data || ''),
rId: intRels,
Target: '../media/image' + (++gObjPptx.imageCounter) + '.' + strImgExtn
});
target.slide.bkgdImgRid = intRels;
}
else if ( bkg && typeof bkg === 'string' ) {
target.slide.back = bkg;
}
},
/**
* 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
* @since: 1.0.0
*/
addTextDefinition: function addTextDefinition(text, opt, target, isPlaceholder) {
var opt = ( opt && typeof opt === 'object' ? opt : {} );
var resultObject = {};
var text = ( text || '' );
if ( Array.isArray(text) && text.length == 0 ) text = '';
// STEP 2: Set some options
// Placeholders should inherit their colors or override them, so don't default them
if ( !opt.placeholder ) {
opt.color = ( opt.color || target.slide.color || DEF_FONT_COLOR ); // Set color (options > inherit from Slide > default to black)
}
// 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 = isPlaceholder ? 'placeholder' : 'text';
resultObject.text = text;
// STEP 4: Set options
resultObject.options = opt;
if ( opt.shape && opt.shape.name == 'line' ) {
opt.line = (opt.line || '333333' );
opt.lineSize = (opt.lineSize || 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 || (!opt.placeholder ? 'ctr' : null)); // VALS: [t,ctr,b]
resultObject.options.bodyProp.rot = (opt.rotate || null); // VALS: degree * 60,000
resultObject.options.bodyProp.vert = (opt.vert || null); // VALS: [eaVert,horz,mongolianVert,vert,vert270,wordArtVert,wordArtVertRtl]
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);
}
target.data.push(resultObject);
createHyperlinkRels(text || '', target.rels);
return resultObject;
},
/**
* Adds Notes to a slide.
* @param {String} notes
* @param {Object} opt (*unused*)
* @param {Object} target slide object
* @since 2.3.0
*/
addNotesDefinition: function addNotesDefinition(notes, opt, target) {
var opt = ( opt && typeof opt === 'object' ? opt : {} );
var resultObject = {};
resultObject.type = 'notes';
resultObject.text = notes;
target.data.push(resultObject);
return resultObject;
},
/**
* Adds a placeholder object to a slide definition.
* @param {String} text
* @param {Object} opt
* @param {Object} target slide object that the placeholder should be added to
*/
addPlaceholderDefinition: function addPlaceholderDefinition(text, opt, target) {
return gObjPptxGenerators.addTextDefinition(text, opt, target, true);
},
/**
* 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 : {} );
if ( !shape || typeof shape !== 'object' ) {
console.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 || (options.w == 0 ? 0 : 1) );
options.h = ( options.h || (options.h == 0 ? 0 : 1) );
options.line = ( options.line || (shape.name == 'line' ? '333333' : null) );
options.lineSize = ( options.lineSize || (shape.name == 'line' ? 1 : null) );
if ( ['dash','dashDot','lgDash','lgDashDot','lgDashDotDot','solid','sysDash','sysDot'].indexOf(options.lineDash || '') < 0 ) options.lineDash = 'solid';
target.data.push(resultObject);
return resultObject;
},
/**
* 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 {Object} objImage - object containing `path`/`data`, `x`, `y`, etc.
* @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(objImage, target) {
var resultObject = {};
// FIRST: Set vars for this image (object param replaces positional args in 1.1.0)
var intPosX = (objImage.x || 0);
var intPosY = (objImage.y || 0);
var intWidth = (objImage.w || 0);
var intHeight = (objImage.h || 0);
var sizing = objImage.sizing || null;
var objHyperlink = (objImage.hyperlink || '');
var strImageData = (objImage.data || '');
var strImagePath = (objImage.path || '');
var imageRelId = target.rels.length + 1;
// 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 1: Set extension
var strImgExtn = strImagePath.split('.').pop() || '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];
}
// STEP 2: Set type/path
resultObject.type = 'image';
resultObject.image = (strImagePath || 'preencoded.png');
// STEP 3: Set image properties & options
// 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 ( !intWidth || !intHeight ) { var imgObj = getSizeFromImage(strImagePath);
var imgObj = { width:1, height:1 };
resultObject.options = {
x: (intPosX || 0),
y: (intPosY || 0),
cx: (intWidth || imgObj.width),
cy: (intHeight || imgObj.height),
rounding: (objImage.rounding || false),
sizing: sizing,
placeholder: objImage.placeholder
};
// 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({
path: (strImagePath || 'preencoded'+strImgExtn),
type: 'image/'+strImgExtn,
extn: strImgExtn,
data: (strImageData || ''),
rId: imageRelId,
Target: '../media/image'+ (++gObjPptx.imageCounter) +'.'+ strImgExtn
});
resultObject.imageRid = imageRelId;
// STEP 5: (Issue#77) Hyperlink support
if ( typeof objHyperlink === 'object' ) {
if ( !objHyperlink.url && !objHyperlink.slide ) console.log("ERROR: 'hyperlink requires either: `url` or `slide`'");
else {
var intRelId = imageRelId + 1;
target.rels.push({
type: 'hyperlink',
data: (objHyperlink.slide ? 'slide' : 'dummy'),
rId: intRelId,
Target: objHyperlink.url || objHyperlink.slide
});
objHyperlink.rId = intRelId;
resultObject.hyperlink = objHyperlink;
}
}
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 slide object that the chart should be added to
* @return {Object} chart object
* {
* 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 targetRels = target.rels;
var chartId = (++gObjPptx.chartCounter);
var chartRelId = target.rels.length + 1;
var resultObject = {};
// DESIGN: `type` can an object (ex: `pptx.charts.DOUGHNUT`) or an array of chart objects
// EX: addChartDefinition([ { type:pptx.charts.BAR, data:{name:'', labels:[], values[]} }, {<etc>} ])
// Multi-Type Charts
var tmpOpt;
var tmpData = [], options;
if (Array.isArray(type)) {
// For multi-type charts there needs to be data for each type,
// as well as a single data source for non-series operations.
// The data is indexed below to keep the data in order when segmented
// into types.
type.forEach(function(obj) {
tmpData = tmpData.concat(obj.data)
});
tmpOpt = data || opt;
}
else {
tmpData = data;
tmpOpt = opt;
}
tmpData.forEach(function(item,i){ item.index = i; });
options = ( tmpOpt && typeof tmpOpt === 'object' ? tmpOpt : {} );
// STEP 1: TODO: check for reqd fields, correct type, etc
// `type` exists 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 2: Set default options/decode user options
// A: Core
options.type = type;
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.name == 'pie' || options.type.name == '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';
if ( ['gap','span'].indexOf(options.displayBlanksAs || '') < 0 ) options.displayBlanksAs = 'span';
if ( ['standard','marker','filled'].indexOf(options.radarStyle || '') < 0 ) options.radarStyle = 'standard';
options.lineDataSymbolSize = ( options.lineDataSymbolSize && !isNaN(options.lineDataSymbolSize ) ? options.lineDataSymbolSize : 6 );
options.lineDataSymbolLineSize = ( options.lineDataSymbolLineSize && !isNaN(options.lineDataSymbolLineSize ) ? options.lineDataSymbolLineSize * ONEPT : 0.75 * ONEPT );
// `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
}
});
}
// Set gridline defaults
options.catGridLine = options.catGridLine || (type.name == 'scatter' ? { color:'D9D9D9', pt:1 } : 'none');
options.valGridLine = options.valGridLine || (type.name == 'scatter' ? { color:'D9D9D9', pt:1 } : {});
correctGridLineOptions(options.catGridLine);
correctGridLineOptions(options.valGridLine);
correctShadowOptions(options.shadow);
// C: Options: plotArea
options.showDataTable = (options.showDataTable == true || options.showDataTable == false ? options.showDataTable : false);
options.showDataTableHorzBorder = (options.showDataTableHorzBorder == true || options.showDataTableHorzBorder == false ? options.showDataTableHorzBorder : true );
options.showDataTableVertBorder = (options.showDataTableVertBorder == true || options.showDataTableVertBorder == false ? options.showDataTableVertBorder : true );
options.showDataTableOutline = (options.showDataTableOutline == true || options.showDataTableOutline == false ? options.showDataTableOutline : true );
options.showDataTableKeys = (options.showDataTableKeys == true || options.showDataTableKeys == false ? options.showDataTableKeys : true );
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);
options.catAxisLineShow = (typeof options.catAxisLineShow !== 'undefined' ? options.catAxisLineShow : true);
options.valAxisLineShow = (typeof options.valAxisLineShow !== 'undefined' ? options.valAxisLineShow : true);
// 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.name == 'pie' || options.type.name == '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';
//
if ( !options.dataLabelFormatCode && options.type.name === 'scatter' ) options.dataLabelFormatCode = "General";
options.dataLabelFormatCode = options.dataLabelFormatCode && typeof options.dataLabelFormatCode === 'string' ? options.dataLabelFormatCode : (options.type.name == 'pie' || options.type.name == '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: tmpData,
opts: options,
type: 'chart',
globalId: chartId,
fileName:'chart'+ chartId +'.xml',
Target: '/ppt/charts/chart'+ chartId +'.xml'
});
resultObject.chartRid = chartRelId;
target.data.push(resultObject);
return resultObject;
},
/* ===== */
/**
* 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) {
// STEP 1: Add background
if ( slideDef.bkgd ) {
gObjPptxGenerators.addBackgroundDefinition(slideDef.bkgd, target);
}
// STEP 2: Add all Slide Master objects in the order they were given (Issue#53)
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], 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, false);
else if ( MASTER_OBJECTS[key] && key == 'placeholder' ) {
// TODO: 20180820: Check for existing `name`?
object[key].options.placeholderName = object[key].options.name; delete object[key].options.name; // remap name for earier handling internally
object[key].options.placeholderType = object[key].options.type; delete object[key].options.type; // remap name for earier handling internally
object[key].options.placeholderIdx = (100+idx);
gObjPptxGenerators.addPlaceholderDefinition(object[key].text, object[key].options, target);
}
});
}
// STEP 3: Add Slide Numbers (NOTE: Do this last so numbers are not covered by objects!)
if ( slideDef.slideNumber && typeof slideDef.slideNumber === 'object' ) {
target.slideNumberObj = slideDef.slideNumber;
};
},
/**
* Transforms a slide object to resulting XML string.
* @param {Object} slideObject slide object created within gObjPptxGenerators.createSlideObject
* @return {String} XML string with <p:cSld> as the root
*/
slideObjectToXml: function slideObjectToXml(slideObject) {
var strSlideXml = slideObject.name ? '<p:cSld name="'+ slideObject.name +'">' : '<p:cSld>';
var intTableNum = 1;
// STEP 1: Add background
if ( slideObject.slide.back ) {
strSlideXml += genXmlColorSelection(false, slideObject.slide.back);
}
// STEP 2: Add background image (using Strech) (if any)
if ( slideObject.slide.bkgdImgRid ) {
// FIXME: We should be doing this in the slideLayout...
strSlideXml += '<p:bg>'
+ '<p:bgPr><a:blipFill dpi="0" rotWithShape="1">'
+ '<a:blip r:embed="rId'+ slideObject.slide.bkgdImgRid +'"><a:lum/></a:blip>'
+ '<a:srcRect/><a:stretch><a:fillRect/></a:stretch></a:blipFill>'
+ '<a:effectLst/></p:bgPr>'
+ '</p:bg>';
}
// STEP 3: Continue slide by starting spTree node
strSlideXml += '<p:spTree>';
strSlideXml += '<p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>';
strSlideXml += '<p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/>';
strSlideXml += '<a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>';
// STEP 4: Loop over all Slide.data objects and add them to this slide ===============================
slideObject.data.forEach(function(slideItemObj, idx) {
var x = 0, y = 0, cx = getSmartParseNumber('75%','X'), cy = 0, placeholderObj;
var locationAttr = "", shapeType = null;
if ( slideObject.layoutObj && slideObject.layoutObj.data && slideItemObj.options && slideItemObj.options.placeholder ) {
placeholderObj = slideObject.layoutObj.data.filter(function(layoutObj){ return layoutObj.options.placeholderName == slideItemObj.options.placeholder})[0];
}
// A: Set option vars
slideItemObj.options = slideItemObj.options || {};
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 ( 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 using a placeholder then inherit it's position
if ( placeholderObj ) {
if ( placeholderObj.options.x || placeholderObj.options.x == 0 ) x = getSmartParseNumber( placeholderObj.options.x , 'X' );
if ( placeholderObj.options.y || placeholderObj.options.y == 0 ) y = getSmartParseNumber( placeholderObj.options.y , 'Y' );
if ( placeholderObj.options.cx || placeholderObj.options.cx == 0 ) cx = getSmartParseNumber( placeholderObj.options.cx, 'X' );
if ( placeholderObj.options.cy || placeholderObj.options.cy == 0 ) cy = getSmartParseNumber( placeholderObj.options.cy, 'Y' );
}
//
if ( slideItemObj.options.shape ) shapeType = getShapeInfo( slideItemObj.options.shape );
//
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 ( slideItemObj.type ) {
case 'table':
// FIRST: Ensure we have rows - otherwise, bail!
if ( !slideItemObj.arrTabRows || (Array.isArray(slideItemObj.arrTabRows) && slideItemObj.arrTabRows.length == 0) ) break;
// Set table vars
var objTableGrid = {};
var arrTabRows = slideItemObj.arrTabRows;
var objTabOpts = slideItemObj.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;
intColCnt += ( cellOpts && cellOpts.colspan ? Number(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 = '<p:graphicFrame>'
+ ' <p:nvGraphicFramePr>'
+ ' <p:cNvPr id="'+ (intTableNum*slideObject.numb + 1) +'" name="Table '+ (intTableNum*slideObject.numb) +'"/>'
+ ' <p:cNvGraphicFramePr><a:graphicFrameLocks noGrp="1"/></p:cNvGraphicFramePr>'
+ ' <p:nvPr><p:extLst><p:ext uri="{D42A27DB-BD31-4B8C-83A1-F6EECF244321}"><p14:modId xmlns:p14="http://schemas.microsoft.com/office/powerpoint/2010/main" val="1579011935"/></p:ext></p:extLst></p:nvPr>'
+ ' </p:nvGraphicFramePr>'
+ ' <p:xfrm>'
+ ' <a:off x="'+ (x || EMU) +'" y="'+ (y || EMU) +'"/>'
+ ' <a:ext cx="'+ (cx || EMU) +'" cy="'+ (cy || EMU) +'"/>'
+ ' </p:xfrm>'
+ ' <a:graphic>'
+ ' <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">'
+ ' <a:tbl>'
+ ' <a:tblPr/>';
// + ' <a:tblPr bandRow="1"/>';
// FIXME: Support banded rows, first/last row, etc.
// NOTE: Bandi