Skip to content

Commit

Permalink
Added support for animated GIFs
Browse files Browse the repository at this point in the history
Completes Issue #22.
  • Loading branch information
GitBrent authored and GitBrent committed Jan 19, 2017
1 parent 7441b65 commit 55369d6
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 26 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,10 @@ Syntax:
slide.addImage({OPTIONS});
```

Animated GIFs can be included in Presentations in one of two ways:
* Using Node.js: use either `data` or `path` options (Node can encode any image into base64)
* Client Browsers: pre-encode the gif and add it using the `data` option (encoding images into GIFs is beyond any current browser)

### Image Options
| Option | Type | Unit | Default | Description | Possible Values |
| :----------- | :------ | :----- | :-------- | :------------------ | :--------------- |
Expand Down
49 changes: 27 additions & 22 deletions dist/pptxgen.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,21 @@
* @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)
// 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;
};

// Node.js version of <script> includes
// [Node.js] <script> includes
if ( typeof module !== 'undefined' && module.exports ) {
var gObjPptxMasters = require('../dist/pptxgen.masters.js');
var gObjPptxShapes = require('../dist/pptxgen.shapes.js');
}

var PptxGenJS = function(){
// CONSTANTS
var APP_VER = "1.1.5";
var APP_REL = "20170117";
var APP_VER = "1.1.6";
var APP_REL = "20170118";
var LAYOUTS = {
'LAYOUT_4x3' : { name: 'screen4x3', width: 9144000, height: 6858000 },
'LAYOUT_16x9' : { name: 'screen16x9', width: 9144000, height: 5143500 },
Expand Down Expand Up @@ -147,37 +147,33 @@ var PptxGenJS = function(){
for ( var idy=0; idy<gObjPptx.slides[idx].rels.length; idy++ ) {
var id = gObjPptx.slides[idx].rels[idy].rId - 1;
var data = gObjPptx.slides[idx].rels[idy].data;
var extn = gObjPptx.slides[idx].rels[idy].extn;

// A: Users will undoubtedly pass in string in various formats, so lets deal with that issue
// A: Users will undoubtedly pass in string in various 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;

// B: Grab base64 encoding header (ex: "data:image/png;base64,iVB[...]=")
// B: Grab base64 encoding header (ex: "data:image/png;base64")
var header = data.substring(0, data.indexOf(","));
// NOTE: Trim the leading base64 header ('data:image/png;base64,') as image wont render correctly with this header!)

// C: Set content
// C: Set content and trim the leading base64 header ('data:image/png;base64,') JSZip only needs the data after ',' (will error otherwiose)
var content = data.substring(data.indexOf(",") + 1);

// D: Set extension - handle cases where user passes base64 without 'data:' at the beginning
var extn = 'png';
if ( data.toLowerCase().indexOf('data:') == 0 ) extn = /data:image\/(\w+)/.exec(header)[1];

// E: Add image
var isBase64 = /base64/.test(header);
zip.file( "ppt/media/image" + id + "." + extn, content, {base64:isBase64} );
// D: Add image
zip.file( "ppt/media/image"+id+"."+extn, content, {base64:true} );
}
}

zip.file("ppt/theme/theme1.xml", makeXmlTheme());
zip.file("ppt/presentation.xml", makeXmlPresentation());
zip.file("ppt/presProps.xml", makeXmlPresProps());
zip.file("ppt/tableStyles.xml", makeXmlTableStyles());
zip.file("ppt/viewProps.xml", makeXmlViewProps());
zip.file("ppt/presProps.xml", makeXmlPresProps());
zip.file("ppt/tableStyles.xml", makeXmlTableStyles());
zip.file("ppt/viewProps.xml", makeXmlViewProps());

// STEP 3: Push the PPTX file to browser
var strExportName = ((gObjPptx.fileName.toLowerCase().indexOf('.ppt') > -1) ? gObjPptx.fileName : gObjPptx.fileName+gObjPptx.fileExtn);
// [Node.js]
if ( typeof module !== 'undefined' && module.exports ) {
zip.generateAsync({type:'nodebuffer'}).then(function(content){ fs.writeFile(strExportName, content); });
}
Expand Down Expand Up @@ -607,6 +603,7 @@ var PptxGenJS = function(){
+ ' <Default Extension="xml" ContentType="application/xml"/>'
+ ' <Default Extension="jpeg" ContentType="image/jpeg"/>'
+ ' <Default Extension="png" ContentType="image/png"/>'
+ ' <Default Extension="gif" ContentType="image/gif"/>'
+ ' <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>'
+ ' <Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>'
+ ' <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>'
Expand Down Expand Up @@ -1615,14 +1612,22 @@ var PptxGenJS = function(){

// REALITY-CHECK:
if ( !strImagePath && !strImageData ) {
try { console.error("ERROR: addImage requires either 'data' or 'path' parameter!"); } catch(ex){}
console.error("ERROR: `addImage()` requires either 'data' or 'path' parameter!");
return null;
}
else if ( strImageData && strImageData.toLowerCase().indexOf('base64,') == -1 ) {
console.warn("Warning: Image `data` does not appear to be valid (lacks a header): "+strImageData+"\nExport will tank if this is invalid base64!");
}

// STEP 2: Set vars for this Slide
var slideObjNum = gObjPptx.slides[slideNum].data.length;
var slideObjRels = gObjPptx.slides[slideNum].rels;
var strImgExtn = 'png'; // Every image is encoded via canvas>base64, so they all come out as png (use of another extn will cause "needs repair" dialog on open in PPT)
// 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];
}

gObjPptx.slides[slideNum].data[slideObjNum] = {};
gObjPptx.slides[slideNum].data[slideObjNum].type = 'image';
Expand All @@ -1643,7 +1648,7 @@ var PptxGenJS = function(){
// 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.png'),
path: (strImagePath || 'preencoded'+strImgExtn),
type: 'image/'+strImgExtn,
extn: strImgExtn,
data: (strImageData || ''),
Expand Down Expand Up @@ -1953,7 +1958,7 @@ var PptxGenJS = function(){
}
};

// Node.js support (Usage: `var pptxgenjs = require("pptxgenjs").PptxGenJS;`)
// [Node.js] support
if ( typeof module !== 'undefined' && module.exports ) {
// A: Load 2 depdendencies
var fs = require("fs");
Expand Down
1 change: 1 addition & 0 deletions examples/images/base64Images.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 9 additions & 4 deletions examples/pptxgenjs-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<script type="text/javascript" src="../dist/pptxgen.shapes.js"></script>
<script type="text/javascript" src="../dist/pptxgen.masters.js"></script>
<script type="text/javascript" src="../dist/pptxgen.js"></script>
<script type="text/javascript" src="images/base64Images.js"></script>
<!-- Google Code Prettify -->
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?skin=sunburst"></script>

Expand Down Expand Up @@ -433,20 +434,24 @@
slide.addTable( [ [{ text:'Image Examples: Misc Image Types', opts:optsTitle }] ], { x:0.5, y:0.13, cx:12.5 } );

// Add an image using basic syntax
slide.addImage({ path:'images/cc_copyremix.gif', x:0.5, y:0.75, w:2.35, h:2.45 });
slide.addImage({ path:'images/cc_copyremix.gif', x:0.5, y:0.75, w:1.20, h:1.20 });
// Slide API calls return the same slide, so you can chain calls:
slide.addImage({ path:'images/cc_license_comp_chart.png', x:6.6, y:0.75, w:6.30, h:3.70 })
.addImage({ path:'images/cc_logo.jpg', x:0.5, y:3.50, w:5.00, h:3.70 })
.addImage({ path:'images/cc_symbols_trans.png', x:6.6, y:4.80, w:6.30, h:2.30 });

// 2: Images can be pre-encoded into base64, so they do not have to be on the webserver etc. (saves generation time and resources!)
slide.addImage({ x:3.0, y:0.8, w:0.5, h:0.5, data:'data: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=' });
// Also has the benefit of being able to be any type (path:images can only be exported as PNG)
slide.addImage({ x:1.8, y:0.7, w:1.78, h:1.78, data:GIF_ANIM_FIRE });
// NOTE: The 'data:' part of the encoded string is optional:
slide.addImage({ x:3.7, y:1.3, w:0.6, h:0.6, data:'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=' });
// TEST: Ensure framework corrects for missing type header
slide.addImage({ x:4.4, y:1.9, w:0.7, h:0.7, data:'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=' });
// TEST: Ensure framework corrects for missing all header
slide.addImage({ x:5.2, y:2.6, w:0.8, h:0.8, data:'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=' });

// TEST: Ensure framework corrects for missing all header (Please DO NOT pass base64 data without the header! This is a junky test)
//slide.addImage({ x:5.2, y:2.6, w:0.8, h:0.8, data:'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=' });
// NEGATIVE-TEST:
//slide.addImage({ data:'images/doh_this_isnt_base64_data.gif', x:0.5, y:0.5, w:1.0, h:1.0 });
}

// LAST: Export Presentation
Expand Down

0 comments on commit 55369d6

Please sign in to comment.