UNPKG

pptxgenjs

Version:

JavaScript library that creates PowerPoint presentations

785 lines (708 loc) 287 kB
/*\ |*| :: 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' : "&#x2022;", 'CHECK' : "&#x2713;", 'STAR' : "&#x2605;", 'TRIANGLE': "&#x25B6;" }; 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,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'; var IMG_PLAYBTN = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAHCCAYAAAAXY63IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAFRdJREFUeNrs3WFz2lbagOEnkiVLxsYQsP//z9uZZmMswJIlS3k/tPb23U3TOAUM6Lpm8qkzbXM4A7p1dI4+/etf//oWAAAAB3ARETGdTo0EAACwV1VVRWIYAACAQxEgAACAAAEAAAQIAACAAAEAAAQIAACAAAEAAAQIAAAgQAAAAAQIAAAgQAAAAAQIAAAgQAAAAAECAAAgQAAAAAECAAAgQAAAAAECAAAIEAAAAAECAAAIEAAAAAECAAAIEAAAQIAAAAAIEAAAQIAAAAAIEAAAQIAAAAACBAAAQIAAAAACBAAAQIAAAAACBAAAQIAAAAACBAAAECAAAAACBAAAECAAAAACBAAAECAAAIAAAQAAECAAAIAAAQAAECAAAIAAAQAABAgAAIAAAQAABAgAAIAAAQAABAgAACBAAAAABAgAACBAAAAABAgAACBAAAAAAQIAACBAAAAAAQIAACBAAAAAAQIAACBAAAAAAQIAAAgQAAAAAQIAAAgQAAAAAQIAAAgQAABAgAAAAAgQAABAgAAAAAgQAABAgAAAAAIEAABAgAAAAAIEAABAgAAAAAIEAAAQIAAAAAIEAAAQIAAAAAIEAAAQIAAAgAABAAAQIAAAgAABAAAQIAAAgAABAAAQIAAAgAABAAAECAAAgAABAAAECAAAgAABAAAECAAAIEAAAAAECAAAIEAAAAAECAAAIEAAAAABAgAAIEAAAAABAgAAIEAAAAABAgAACBAAAAABAgAACBAAAAABAgAACBAAAECAAAAACBAAAECAAAAACBAAAECAAAAAAgQAAECAAAAAAgQAAECAAAAAAgQAAECAAAAAAgQAABAgAAAAAgQAABAgAAAAAgQAABAgAACAAAEAABAgAACAAAEAABAgAACAAAEAAAQIAACAAAEAAAQIAACAAAEAAAQIAAAgQAAAAPbnwhAA8CuGYYiXl5fv/7hcXESSuMcFgAAB4G90XRffvn2L5+fniIho2zYiIvq+j77vf+nfmaZppGkaERF5nkdExOXlZXz69CmyLDPoAAIEgDFo2zaen5/j5eUl+r6Pruv28t/5c7y8Bs1ms3n751mWRZqmcXFxEZeXl2+RAoAAAeBEDcMQbdu+/dlXbPyKruve/n9ewyTLssjz/O2PR7oABAgAR67v+2iaJpqmeVt5OBWvUbLdbiPi90e3iqKIoijeHucCQIAAcATRsd1uo2maX96zcYxeV26qqoo0TaMoiphMJmIEQIAAcGjDMERd11HX9VE9WrXvyNput5FlWZRlGWVZekwLQIAAsE+vjyjVdT3qMei6LqqqirIsYzKZOFkLQIAAsEt1XcfT09PJ7es4xLjUdR15nsfV1VWUZWlQAAQIAP/kAnu9Xp/V3o59eN0vsl6v4+bmRogACBAAhMf+9X0fq9VKiAAIEAB+RtM0UVWV8NhhiEyn0yiKwqAACBAAXr1uqrbHY/ch8vDwEHmex3Q6tVkdQIAAjNswDLHZbN5evsd+tG0bX758iclkEtfX147vBRAgAOPTNE08Pj7GMAwG40BejzC+vb31WBaAAAEYh9f9CR63+hjDMLw9ljWfz62GAOyZb1mAD9Q0TXz58kV8HIG2beO3336LpmkMBsAeWQEB+ADDMERVVaN+g/mxfi4PDw9RlmVMp1OrIQACBOD0dV0XDw8PjtY9YnVdR9u2MZ/PnZQFsGNu7QAc+ML269ev4uME9H0fX79+tUoFsGNWQAAOZLVauZg9McMwxGq1iufn55jNZgYEQIAAnMZF7MPDg43mJ6yu6+j73ilZADvgWxRgj7qui69fv4qPM9C2rcfnAAQIwPHHR9d1BuOMPtMvX774TAEECMBxxoe3mp+fYRiEJYAAATgeryddiY/zjxAvLQQQIAAfHh+r1Up8jCRCHh4enGwGIEAAPkbTNLFarQzEyKxWKyshAAIE4LC6rovHx0cDMVKPj4/2hAAIEIDDxYc9H+NmYzqAAAEQH4gQAAECcF4XnI+Pj+IDcwJAgADs38PDg7vd/I+u6+Lh4cFAAAgQgN1ZrVbRtq2B4LvatnUiGoAAAdiNuq69+wHzBECAAOxf13VRVZWB4KdUVeUxPQABAvBrXt98bYMx5gyAAAHYu6qqou97A8G79H1v1QxAgAC8T9M0nufnl9V1HU3TGAgAAQLw9/q+j8fHx5P6f86yLMqy9OEdEe8HARAgAD9ltVqd3IXjp0+fYjabxWKxiDzPfYhH4HU/CIAAAeAvNU1z0u/7yPM8FotFzGazSBJf+R+tbVuPYgECxBAAfN8wDCf36NVfKcsy7u7u4vr62gf7wTyKBQgQAL5rs9mc1YVikiRxc3MT9/f3URSFD/gDw3az2RgIQIAA8B9d18V2uz3Lv1uapjGfz2OxWESWZT7sD7Ddbr2gEBAgAPzHGN7bkOd5LJfLmE6n9oeYYwACBOCjnPrG8/eaTCZxd3cXk8nEh39ANqQDAgSAiBjnnekkSWI6ncb9/b1je801AAECcCh1XUff96P9+6dpGovFIhaLRaRpakLsWd/3Ude1gQAECMBYrddrgxC/7w+5v7+P6+tr+0PMOQABArAPY1/9+J6bm5u4u7uLsiwNxp5YBQEECMBIuRP9Fz8USRKz2SyWy6X9IeYegAAB2AWrH38vy7JYLBYxn8/tD9kxqyCAAAEYmaenJ4Pwk4qiiOVyaX+IOQggQAB+Rdd1o3rvx05+PJIkbm5uYrlc2h+yI23bejs6IEAAxmC73RqEX5Smacxms1gsFpFlmQExFwEECMCPDMPg2fsdyPM8lstlzGYzj2X9A3VdxzAMBgIQIADnfMHH7pRlGXd3d3F9fW0wzEkAAQLgYu8APyx/7A+5v7+PoigMiDkJIEAAIn4/+tSm3/1J0zTm83ksFgvH9r5D13WOhAYECMA5suH3MPI8j/v7+5hOp/aHmJsAAgQYr6ZpDMIBTSaTuLu7i8lkYjDMTUCAAIxL3/cec/mIH50kiel0Gvf395HnuQExPwEBAjAO7jB/rDRNY7FYxHw+tz/EHAUECICLOw6jKIq4v7+P6+tr+0PMUUCAAJynYRiibVsDcURubm7i7u4uyrI0GH9o29ZLCQEBAnAuF3Yc4Q9SksRsNovlcml/iLkKCBAAF3UcRpZlsVgsYjabjX5/iLkKnKMLQwC4qOMYlWUZl5eXsd1u4+npaZSPI5mrwDmyAgKMjrefn9CPVJLEzc1NLJfLUe4PMVcBAQJw4txRPk1pmsZsNovFYhFZlpmzAAIE4DQ8Pz8bhBOW53ksl8uYzWajObbXnAXOjT0gwKi8vLwYhDPw5/0hm83GnAU4IVZAgFHp+94gnMsP2B/7Q+7v78/62F5zFhAgACfMpt7zk6ZpLBaLWCwWZ3lsrzkLCBAAF3IcoTzP4/7+PqbT6dntDzF3AQECcIK+fftmEEZgMpnE3d1dTCYTcxdAgAB8HKcJjejHLUliOp3Gcrk8i/0h5i4gQADgBGRZFovFIubz+VnuDwE4RY7hBUbDC93GqyiKKIoi1ut1PD09xTAM5i7AB7ECAsBo3NzcxN3dXZRlaTAABAjAfnmfAhG/7w+ZzWaxWCxOZn+IuQsIEAABwonL8zwWi0XMZrOj3x9i7gLnxB4QAEatLMu4vLyM7XZ7kvtDAE6NFRAA/BgmSdzc3MRyuYyiKAwIgAAB+Gfc1eZnpGka8/k8FotFZFlmDgMIEIBf8/LyYhD4aXmex3K5jNlsFkmSmMMAO2QPCAD8hT/vD9lsNgYEYAesgADAj34o/9gfcn9/fzLH9gIIEAAAgPAIFgD80DAMsdlsYrvdGgwAAQIA+/O698MJVAACBOB9X3YXvu74eW3bRlVV0XWdOQwgQADe71iOUuW49X0fVVVF0zTmMIAAAYD9GIbBUbsAAgQA9q+u61iv19H3vcEAECAAu5OmqYtM3rRtG+v1Otq2PYm5CyBAAAQIJ6jv+1iv11HX9UnNXQABAgAnZr1ex9PTk2N1AQQIwP7leX4Sj9uwe03TRFVVJ7sClue5DxEQIABw7Lqui6qqhCeAAAE4vMvLS8esjsQwDLHZbGK73Z7N3AUQIAAn5tOnTwZhBF7f53FO+zzMXUCAAJygLMsMwhlr2zZWq9VZnnRm7gICBOCEL+S6rjMQZ6Tv+1itVme7z0N8AAIE4ISlaSpAzsQwDG+PW537nAUQIACn+qV34WvvHNR1HVVVjeJ9HuYsIEAATpiTsE5b27ZRVdWoVrGcgAUIEIBT/tJzN/kk9X0fVVVF0zSj+7t7CSEgQABOWJIkNqKfkNd9Hk9PT6N43Oq/2YAOCBCAM5DnuQA5AXVdx3q9Pstjdd8zVwEECMAZXNSdyxuyz1HXdVFV1dkeqytAAAEC4KKOIzAMQ1RVFXVdGwxzFRAgAOcjSZLI89wd9iOyXq9Hu8/jR/GRJImBAAQIwDkoikKAHIGmaaKqqlHv8/jRHAUQIABndHFXVZWB+CB938dqtRKBAgQQIADjkKZppGnqzvuBDcMQm83GIQA/OT8BBAjAGSmKwoXwAW2329hsNvZ5/OTcBBAgAGdmMpkIkANo2zZWq5XVpnfOTQABAnBm0jT1VvQ96vs+qqqKpmkMxjtkWebxK0CAAJyrsiwFyI4Nw/D2uBW/NicBBAjAGV/sOQ1rd+q6jqqq7PMQIAACBOB7kiSJsiy9ffsfats2qqqymrSD+PDyQUCAAJy5q6srAfKL+r6P9Xpt/HY4FwEECMCZy/M88jz3Urx3eN3n8fT05HGrHc9DAAECMAJXV1cC5CfVdR3r9dqxunuYgwACBGAkyrJ0Uf03uq6LqqqE2h6kaWrzOSBAAMbm5uYmVquVgfgvwzBEVVX2eex57gEIEICRsQryv9brtX0ee2b1AxAgACNmFeR3bdvGarUSYweacwACBGCkxr4K0vd9rFYr+zwOxOoHIEAAGOUqyDAMsdlsYrvdmgAHnmsAAgRg5MqyjKenp9GsAmy329hsNvZ5HFie51Y/gFFKDAHA/xrDnem2bePLly9RVZX4MMcADsYKCMB3vN6dPsejZ/u+j6qqomkaH/QHKcvSW88BAQLA/zedTuP5+flsVgeGYXh73IqPkyRJTKdTAwGM93vQEAD89YXi7e3tWfxd6rqO3377TXwcgdvb20gSP7/AeFkBAfiBoigiz/OT3ZDetm2s12vH6h6JPM+jKAoDAYyaWzAAf2M2m53cHetv377FarWKf//73+LjWH5wkyRms5mBAHwfGgKAH0vT9OQexeq67iw30J+y29vbSNPUQAACxBAA/L2iKDw6g/kDIEAADscdbH7FKa6gAQgQgGP4wkySmM/nBoJ3mc/nTr0CECAAvybLMhuJ+Wmz2SyyLDMQAAIE4NeVZRllWRoIzBMAAQJwGO5s8yNWygAECMDOff78WYTw3fj4/PmzgQAQIAA7/gJNkri9vbXBGHMCQIAAHMbr3W4XnCRJYlUMQIAAiBDEB4AAATjDCJlOpwZipKbTqfgAECAAh1WWpZOPRmg2mzluF+AdLgwBwG4jJCKiqqoYhsGAnLEkSWI6nYoPgPd+fxoCgN1HiD0h5x8fnz9/Fh8AAgTgONiYfv7xYc8HgAABOMoIcaHqMwVAgAC4YOVd8jz3WQIIEIAT+KJNklgul/YLnLCyLGOxWHikDkCAAJyO2WzmmF6fG8DoOYYX4IDKsoyLi4t4eHiIvu8NyBFL0zTm87lHrgB2zAoIwIFlWRbL5TKKojAYR6ooilgul+IDYA+sgAB8gCRJYj6fR9M08fj46KWFR/S53N7eikMAAQJwnoqiiCzLYrVaRdu2BuQD5Xkes9ks0jQ1GAACBOB8pWkai8XCasgHseoBIEAARqkoisjzPKqqirquDcgBlGUZ0+nU8boAAgRgnJIkidlsFldXV7Ferz2WtSd5nsd0OrXJHECAAPB6gbxYLKKu61iv147s3ZE0TWM6nXrcCkCAAPA9ZVlGWZZCZAfhcXNz4230AAIEACEiPAAECABHHyJPT0/2iPyFPM/j6upKeAAIEAB2GSJt28bT05NTs/40LpPJxOZyAAECwD7kef52olNd11HXdXRdN6oxyLLsLcgcpwsgQAA4gCRJYjKZxGQyib7vY7vdRtM0Z7tXJE3TKIoiJpOJN5cDCBAAPvrifDqdxnQ6jb7vo2maaJrm5PeL5HkeRVFEURSiA0CAAHCsMfK6MjIMQ7Rt+/bn2B/VyrLs7RGzPM89XgUgQAA4JUmSvK0gvGrbNp6fn+Pl5SX6vv+wKMmyLNI0jYuLi7i8vIw8z31gAAIEgHPzurrwZ13Xxbdv3+L5+fktUiIi+r7/5T0laZq+PTb1+t+7vLyMT58+ObEKQIAAMGavQfB3qxDDMMTLy8v3f1wuLjwyBYAAAWB3kiTxqBQA7//9MAQAAIAAAQAABAgAAIAAAQAABAgAAIAAAQAABAgAACBAAAAABAgAACBAAAAABAgAACBAAAAAAQIAACBAAAAAAQIAACBAAAAAAQIAAAgQAAAAAQIAAAgQAAAAAQIAAAgQAABAgAAAAAgQAABAgAAAAAgQAABAgAAAAAIEAABAgAAAAAIEAABAgAAAAAIEAABAgAAAAAIEAAAQIAAAAAIEAAAQIAAAAAIEAAAQIAAAgAABAAAQIAAAgAABAAAQIAAAgAABAAAECAAAgAABAAAECAAAgAABAAAECAAAIEAAAAAECAAAIEAAAAAECAAAIEAAAAABAgAAIEAAAAABAgAAIEAAAAABAgAAIEAAAAABAgAACBAAAAABAgAACBAAAAABAgAACBAAAECAAAAACBAAAECAAAAACBAAAECAAAAAAgQAAECAAAAAAgQAAECAAAAAAgQAABAgAAAAAgQAABAgAAAAAgQAABAgAACAAAEAABAgAACAAAEAABAgAACAAAEAAASIIQAAAAQIAAAgQAAAAAQIAAAgQAAAAAQIAAAgQAAAAAECAAAgQAAAAAECAAAgQAAAAAECAAAIEAAAAAECAAAIEAAAAAECAAAIEAAAQIAAAAAIEAAAQIAAAAAIEAAAQIAAAAACBAAAQIAAAAACBAAAQIAAAAACBAAAECAAAAACBAAAECAAAAACBAAAECAAAAACBAAAECAAAIAAAQAAECAAAIAAAQAAECAAAIAAAQAABAgAAIAAAQAABAgAAIAAAQAABAgAACBAAAAAdu0iIqKqKiMBAADs3f8NAFFjCf5mB+leAAAAAElFTkSuQmCC'; } // 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