├── .github ├── ISSUE_TEMPLATE │ └── user-template.md └── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── .gitignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── RELEASING.md ├── TESTING.md ├── bower.json ├── demos ├── .prettierrc.json ├── browser │ ├── css │ │ └── style.css │ ├── html │ │ ├── header.html │ │ ├── navbar.html │ │ ├── navtabs.html │ │ ├── tab-charts.html │ │ ├── tab-html2pptx.html │ │ ├── tab-images.html │ │ ├── tab-intro.html │ │ ├── tab-masters.html │ │ ├── tab-shapes.html │ │ └── tab-tables.html │ ├── images │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.png │ │ ├── github.svg │ │ ├── html2pptx.png │ │ ├── info-circle.svg │ │ ├── mastodon.svg │ │ └── slide-master.png │ ├── index.html │ ├── js │ │ ├── FileSaver.min.js │ │ ├── FileSaver.min.js.map │ │ ├── browser.js │ │ ├── loadSections.js │ │ ├── main.js │ │ ├── pptxgen.bundle.js │ │ ├── pptxgen.bundle.js.map │ │ ├── pptxgenjs_worker.js │ │ ├── test_worker.js │ │ └── worker_test_main.js │ └── worker_test.html ├── browser_server.mjs ├── common │ ├── images │ │ ├── UPPERCASE.PNG │ │ ├── anim_campfire.gif │ │ ├── base64Images.js │ │ ├── brokenImage.gif │ │ ├── brokenImage.png │ │ ├── cc_copyremix.gif │ │ ├── cc_dj.gif │ │ ├── cc_license_comp.png │ │ ├── cc_logo.jpg │ │ ├── cc_symbols_trans.png │ │ ├── chicago_bean_bohne.jpg │ │ ├── cover_audio.png │ │ ├── cover_video_16x9.png │ │ ├── fediverse_actpub.png │ │ ├── fediverse_tree.jpg │ │ ├── image2.jpg │ │ ├── krita_splashscreen.jpeg │ │ ├── krita_square.jpg │ │ ├── lock-green.svg │ │ ├── logo_square.png │ │ ├── logo_square_25.png │ │ ├── logo_square_50.png │ │ ├── mastodon-logo-purple.svg │ │ ├── nyc-subway.png │ │ ├── peace4.png │ │ ├── pixelfed_icon.svg │ │ ├── play-button.png │ │ ├── png-gradient-hex.png │ │ ├── sample-hd-m4v-cover.png │ │ ├── sample_logo.png │ │ ├── starlabs_bkgd.jpg │ │ ├── starlabs_logo.png │ │ ├── sydney_harbour_bridge_night.jpg │ │ ├── title_bkgd.jpg │ │ ├── title_bkgd.png │ │ ├── title_bkgd_alt.jpg │ │ ├── tokyo-subway-route-map.jpg │ │ ├── trippy.gif │ │ ├── unite.png │ │ ├── video-mp4-thumb.png │ │ └── wiki-example.jpg │ └── media │ │ ├── earth-big.mp4 │ │ ├── sample-hd.m4v │ │ ├── sample.aif │ │ ├── sample.avi │ │ ├── sample.m4v │ │ ├── sample.mov │ │ ├── sample.mp3 │ │ ├── sample.mp4 │ │ ├── sample.mpg │ │ └── sample.wav ├── modules │ ├── demo_chart.mjs │ ├── demo_image.mjs │ ├── demo_master.mjs │ ├── demo_media.mjs │ ├── demo_shape.mjs │ ├── demo_table.mjs │ ├── demo_text.mjs │ ├── demos.mjs │ ├── enums.mjs │ ├── enums_charts.mjs │ ├── enums_tables.mjs │ ├── masters.mjs │ └── media.mjs ├── node │ ├── README.md │ ├── assets │ │ ├── image.png │ │ └── video.mp4 │ ├── demo.js │ ├── demo_stream.js │ ├── package-lock.json │ └── package.json └── vite-demo │ ├── .gitignore │ ├── README.md │ ├── eslint.config.js │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── public │ └── vite.svg │ ├── src │ ├── App.css │ ├── App.tsx │ ├── assets │ │ ├── logo.png │ │ └── react.svg │ ├── enums.ts │ ├── index.css │ ├── main.tsx │ ├── scss │ │ └── styles.scss │ ├── tstest │ │ ├── Test.tsx │ │ └── tslint.json │ └── vite-env.d.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── dist ├── pptxgen.bundle.js ├── pptxgen.bundle.js.map ├── pptxgen.cjs.js ├── pptxgen.es.js ├── pptxgen.min.js └── pptxgen.min.js.map ├── eslint.config.mjs ├── gulpfile.js ├── libs ├── jszip.min.js └── polyfill.min.js ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── src ├── core-enums.ts ├── core-interfaces.ts ├── gen-charts.ts ├── gen-media.ts ├── gen-objects.ts ├── gen-tables.ts ├── gen-utils.ts ├── gen-xml.ts ├── pptxgen.ts └── slide.ts ├── tools ├── data2chart.css └── data2chart.html ├── tsconfig.json └── types └── index.d.ts /.github/ISSUE_TEMPLATE/user-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: User Template 3 | about: Used for general issues, feature requests, etc. 4 | title: "[BUG|FEATURE]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | We appreciate your feedback - to help the team understand your needs please complete the following template to ensure we have the details to help. 11 | 12 | ### Submission Guidelines 13 | 14 | - **If you are not using the latest release, please update and see if the issue is resolved before submitting an issue** 15 | - General questions or high-level topics should be posted in [Discussions](https://github.com/gitbrent/PptxGenJS/discussions) 16 | - Please browse the online [Documentation](https://gitbrent.github.io/PptxGenJS/) to see if your question is already addressed there 17 | 18 | ### Issue Category 19 | 20 | - [ ] Enhancement 21 | - [ ] Bug 22 | - [ ] Question 23 | - [ ] Documentation gap/issue 24 | 25 | ### Product Versions 26 | 27 | - Please specify what version of the library you are using......: [ ] 28 | - Please specify what version(s) of PowerPoint you are targeting: [ ] 29 | - Please specify what web browser you are using.................: [ ] 30 | 31 | ### Desired Behavior 32 | 33 | 34 | 35 | ### Observed Behavior 36 | 37 | 38 | 39 | ### Steps to Reproduce 40 | 41 | 42 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Submission Guidelines 2 | 3 | - Only modify the `src/*.ts` files (do not submit `dist` or `src/bld` files) 4 | - New and updated properties must be added to `src/core-interfaces.ts` and `types/index.d.ts` 5 | - New and updated features must be included in the corresponding `demos/modules/*.mjs` file 6 | - Review previously accepted changes for examples on what to provide 7 | 8 | ## Change Summary 9 | 10 | 11 | ## Change Description 12 | 13 | 14 | 15 | ## Change Type 16 | 17 | - [ ] Bug fix 18 | - [ ] New feature 19 | - [ ] Documentation update 20 | 21 | ## Related Issue 22 | 23 | 24 | ## Motivation and Context 25 | 26 | 27 | ## Checklist before requesting a review 28 | 29 | - [ ] If it is a core feature, I have added new code under `/demos/modules/` 30 | - [ ] My code follows the style guidelines of this project 31 | - [ ] My changes generate no new eslint warnings 32 | - [ ] I have performed a self-review of my code 33 | - [ ] I have commented my code, particularly in hard-to-understand areas 34 | - [ ] I have included code/tests that prove my fix is effective or that my feature works 35 | - [ ] I have used the "Run All Demos" feature on the [browser demo](/demos/browser/index.html) and no errors were found 36 | 37 | ## Screenshots / Sample Code (if appropriate) 38 | 39 | Thanks for your contribution! 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### MacOS 2 | .DS_Store 3 | 4 | ### Others 5 | *.icloud 6 | .icloud 7 | bower_components/ 8 | node_modules/ 9 | npm-debug.log 10 | src/bld 11 | demo 12 | 13 | ### docusaurus 14 | .docusaurus 15 | build 16 | docs 17 | static 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2022 Brent Ely 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PptxGenJS 2 | 3 | ![PptxGenJS Sample Slides](https://raw.githubusercontent.com/gitbrent/PptxGenJS/gh-pages/img/readme_banner.png) 4 | 5 | ![jsdelivr downloads](https://data.jsdelivr.com/v1/package/gh/gitbrent/pptxgenjs/badge) 6 | ![NPM Downloads](https://img.shields.io/npm/dm/pptxgenjs?style=flat-square) 7 | ![GitHub Repo stars](https://img.shields.io/github/stars/gitbrent/pptxgenjs?style=flat-square) 8 | ![GitHub License](https://img.shields.io/github/license/gitbrent/pptxgenjs?style=flat-square) 9 | ![TypeScript defs](https://img.shields.io/npm/types/pptxgenjs?style=flat-square) 10 | 11 | ## 🚀 Features 12 | 13 | **PptxGenJS lets you generate professional PowerPoint presentations in JavaScript - directly from Node, React, Vite, Electron, or even the browser.** 14 | The library outputs standards-compliant Open Office XML (OOXML) files compatible with: 15 | 16 | - ✅ Microsoft PowerPoint 17 | - ✅ Apple Keynote 18 | - ✅ LibreOffice Impress 19 | - ✅ Google Slides (via import) 20 | 21 | Design custom slides, charts, images, tables, and templates programmatically - no PowerPoint install or license required. 22 | 23 | ### Works Everywhere 24 | 25 | - Supports every major modern browser - desktop and mobile 26 | - Seamlessly integrates with **Node.js**, **React**, **Angular**, **Vite**, and **Electron** 27 | - Compatible with **PowerPoint**, **Keynote**, **LibreOffice**, and other OOXML apps 28 | 29 | ### Full-Featured 30 | 31 | - Create all major slide objects: **text, tables, shapes, images, charts**, and more 32 | - Define custom **Slide Masters** for consistent academic or corporate branding 33 | - Supports **SVGs**, **animated GIFs**, **YouTube embeds**, **RTL text**, and **Asian fonts** 34 | 35 | ### Simple & Powerful 36 | 37 | - Ridiculously easy to use - create a presentation in 4 lines of code 38 | - Full **TypeScript definitions** for autocomplete and inline documentation 39 | - Includes **75+ demo slides** covering every feature and usage pattern 40 | 41 | ### Export Your Way 42 | 43 | - Instantly download `.pptx` files from the browser with proper MIME handling 44 | - Export as **base64**, **Blob**, **Buffer**, or **Node stream** 45 | - Supports compression and advanced output options for production use 46 | 47 | ### HTML to PowerPoint Magic 48 | 49 | - Convert any HTML `` to one or more slides with a single line of code → [Explore the HTML-to-PPTX feature](#html-to-powerpoint-magic) 50 | 51 | ## 🌐 Live Demos 52 | 53 | Try PptxGenJS right in your browser - no setup required. 54 | 55 | - [Basic Slide Demo](https://gitbrent.github.io/PptxGenJS/demos/) - Build a basic presentation in seconds 56 | - [Full Feature Showcase](https://gitbrent.github.io/PptxGenJS/demo/browser/index.html) - Explore every available feature 57 | 58 | > Perfect for testing compatibility or learning by example - all demos run 100% in the browser. 59 | 60 | ## 📦 Installation 61 | 62 | Choose your preferred method to install **PptxGenJS**: 63 | 64 | ### Quick Install (Node-based) 65 | 66 | ```bash 67 | npm install pptxgenjs 68 | ``` 69 | 70 | ```bash 71 | yarn add pptxgenjs 72 | ``` 73 | 74 | ### CDN (Browser Usage) 75 | 76 | Use the bundled or minified version via [jsDelivr](https://www.jsdelivr.com/package/gh/gitbrent/pptxgenjs): 77 | 78 | ```html 79 | 80 | ``` 81 | 82 | > Includes the sole dependency (JSZip) in one file. 83 | 84 | 📁 Advanced: Separate Files, Direct Download 85 | 86 | Download from GitHub: [Latest Release](https://github.com/gitbrent/PptxGenJS/releases/latest) 87 | 88 | ```html 89 | 90 | 91 | ``` 92 | 93 | ## 🚀 Universal Compatibility 94 | 95 | PptxGenJS works seamlessly in **modern web and Node environments**, thanks to dual ESM and CJS builds and zero runtime dependencies. Whether you're building a CLI tool, an Electron app, or a web-based presentation builder, the library adapts automatically to your stack. 96 | 97 | ### Supported Platforms 98 | 99 | - **Node.js** – generate presentations in backend scripts, APIs, or CLI tools 100 | - **React / Angular / Vite / Webpack** – just import and go, no config required 101 | - **Electron** – build native apps with full filesystem access and PowerPoint output 102 | - **Browser (Vanilla JS)** – embed in web apps with direct download support 103 | - **Serverless / Edge Functions** – use in AWS Lambda, Vercel, Cloudflare Workers, etc. 104 | 105 | > _Vite, Webpack, and modern bundlers automatically select the right build via the `exports` field in `package.json`._ 106 | 107 | ### Builds Provided 108 | 109 | - **CommonJS**: [`dist/pptxgen.cjs.js`](./dist/pptxgen.cjs.js) 110 | - **ES Module**: [`dist/pptxgen.es.js`](./dist/pptxgen.es.js) 111 | 112 | ## 📖 Documentation 113 | 114 | ### Quick Start Guide 115 | 116 | PptxGenJS PowerPoint presentations are created via JavaScript by following 4 basic steps: 117 | 118 | #### Angular/React, ES6, TypeScript 119 | 120 | ```typescript 121 | import pptxgen from "pptxgenjs"; 122 | 123 | // 1. Create a new Presentation 124 | let pres = new pptxgen(); 125 | 126 | // 2. Add a Slide 127 | let slide = pres.addSlide(); 128 | 129 | // 3. Add one or more objects (Tables, Shapes, Images, Text and Media) to the Slide 130 | let textboxText = "Hello World from PptxGenJS!"; 131 | let textboxOpts = { x: 1, y: 1, color: "363636" }; 132 | slide.addText(textboxText, textboxOpts); 133 | 134 | // 4. Save the Presentation 135 | pres.writeFile(); 136 | ``` 137 | 138 | #### Script/Web Browser 139 | 140 | ```javascript 141 | // 1. Create a new Presentation 142 | let pres = new PptxGenJS(); 143 | 144 | // 2. Add a Slide 145 | let slide = pres.addSlide(); 146 | 147 | // 3. Add one or more objects (Tables, Shapes, Images, Text and Media) to the Slide 148 | let textboxText = "Hello World from PptxGenJS!"; 149 | let textboxOpts = { x: 1, y: 1, color: "363636" }; 150 | slide.addText(textboxText, textboxOpts); 151 | 152 | // 4. Save the Presentation 153 | pres.writeFile(); 154 | ``` 155 | 156 | That's really all there is to it! 157 | 158 | ## 💥 HTML-to-PowerPoint Magic 159 | 160 | Convert any HTML `
` into fully formatted PowerPoint slides - automatically and effortlessly. 161 | 162 | ```javascript 163 | let pptx = new pptxgen(); 164 | pptx.tableToSlides("tableElementId"); 165 | pptx.writeFile({ fileName: "html2pptx-demo.pptx" }); 166 | ``` 167 | 168 | Perfect for transforming: 169 | 170 | - Dynamic dashboards and data reports 171 | - Exportable grids in web apps 172 | - Tabular content from CMS or BI tools 173 | 174 | [View Full Docs & Live Demo](https://gitbrent.github.io/PptxGenJS/html2pptx/) 175 | 176 | ## 📚 Full Documentation 177 | 178 | Complete API reference, tutorials, and integration guides are available on the official docs site: [https://gitbrent.github.io/PptxGenJS](https://gitbrent.github.io/PptxGenJS) 179 | 180 | ## 🛠️ Issues / Suggestions 181 | 182 | Please file issues or suggestions on the [issues page on github](https://github.com/gitbrent/PptxGenJS/issues/new), or even better, [submit a pull request](https://github.com/gitbrent/PptxGenJS/pulls). Feedback is always welcome! 183 | 184 | When reporting issues, please include a code snippet or a link demonstrating the problem. 185 | Here is a small [jsFiddle](https://jsfiddle.net/gitbrent/L1uctxm0/) that is already configured and uses the latest PptxGenJS code. 186 | 187 | ## 🆘 Need Help? 188 | 189 | Sometimes implementing a new library can be a difficult task and the slightest mistake will keep something from working. We've all been there! 190 | 191 | If you are having issues getting a presentation to generate, check out the code in the `demos` directory. There 192 | are demos for browser, node and, react that contain working examples of every available library feature. 193 | 194 | - Use a pre-configured jsFiddle to test with: [PptxGenJS Fiddle](https://jsfiddle.net/gitbrent/L1uctxm0/) 195 | - [View questions tagged `PptxGenJS` on StackOverflow](https://stackoverflow.com/questions/tagged/pptxgenjs?sort=votes&pageSize=50). If you can't find your question, [ask it yourself](https://stackoverflow.com/questions/ask?tags=PptxGenJS) - be sure to tag it `pptxgenjs`. 196 | - Ask your AI pair programmer! All major LLMs have ingested the pptxgenjs library and have the ability to answer functionality questions and provide code. 197 | 198 | ## 🙏 Contributors 199 | 200 | Thank you to everyone for the contributions and suggestions! ❤️ 201 | 202 | Special Thanks: 203 | 204 | - [Dzmitry Dulko](https://github.com/DzmitryDulko) - Getting the project published on NPM 205 | - [Michal Kacerovský](https://github.com/kajda90) - New Master Slide Layouts and Chart expertise 206 | - [Connor Bowman](https://github.com/conbow) - Adding Placeholders 207 | - [Reima Frgos](https://github.com/ReimaFrgos) - Multiple chart and general functionality patches 208 | - [Matt King](https://github.com/kyrrigle) - Chart expertise 209 | - [Mike Wilcox](https://github.com/clubajax) - Chart expertise 210 | - [Joonas](https://github.com/wyozi) - [react-pptx](https://github.com/wyozi/react-pptx) 211 | 212 | PowerPoint shape definitions and some XML code via [Officegen Project](https://github.com/Ziv-Barber/officegen) 213 | 214 | ## 🌟 Support the Open Source Community 215 | 216 | If you find this library useful, consider contributing to open-source projects, or sharing your knowledge on the open social web. Together, we can build free tools and resources that empower everyone. 217 | 218 | [@gitbrent@fosstodon.org](https://fosstodon.org/@gitbrent) 219 | 220 | ## 📜 License 221 | 222 | Copyright © 2015-present [Brent Ely](https://github.com/gitbrent/) 223 | 224 | [MIT](https://github.com/gitbrent/PptxGenJS/blob/master/LICENSE) 225 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # PptxGenJS Release Checklist 2 | 3 | > This guide documents how to perform a PptxGenJS release. 4 | > Maintainers should follow this checklist before pushing to npm or GitHub. 5 | 6 | ## 📋 Beta Releases 7 | 8 | 1. Update `package.json` version (ex: `4.1.0-beta.0`) 9 | 2. Update `src/pptxgen.ts` version 10 | 3. Build library: npm scripts > `ship` 11 | 4. `npm publish --tag beta` 12 | 13 | ## 🚀 Build Library, Update Files 14 | 15 | 1. Update `package.json` version 16 | 2. Update `src/pptxgen.ts` version (eg: `const VERSION = '4.0.1'`) 17 | 3. Update `CHANGELOG.md` with new date 18 | 4. Update `README.md` with new CDN links 19 | 5. Build library: npm scripts > `ship` 20 | 6. Consolidate new changes from `src/bld/*.ts` into `types/index.d.ts` and update version in head comment 21 | 7. Open `dist/*.js` and check headers 22 | 8. Update version in: `demos/node/package.json` 23 | 9. Update pptxgenjs dep version in: `demos/vite-demo/package.json` 24 | 25 | ## 🧪 Run Tests Before Release 26 | 27 | ### ⚠️ Run Standard Test Suite 28 | 29 | See [TESTING.md](./TESTING.md) for complete test instructions. 30 | 31 | ### ⚠️ Capture Testing Results 32 | 33 | | Dist File | Test | Tested Via | Result | 34 | | ----------------- | ---------- | ---------------------- | ------ | 35 | | pptxgen.es.js | Webpack 4 | SPFx (v1.16.1) project | ✅?🟡 | 36 | | pptxgen.es.js | Webpack 5 | SPFx (v1.19.1) project | ✅?🟡 | 37 | | pptxgen.es.js | Rollup 4 | Vite (v6) demo | ✅?🟡 | 38 | | pptxgen.cjs.js | Node/CJS | Node demo | ✅?🟡 | 39 | | pptxgen.bundle.js | Script | Browser demo (desktop) | ✅?🟡 | 40 | | pptxgen.bundle.js | Script | Browser demo (iOS) | ✅?🟡 | 41 | | pptxgen.bundle.js | Web Worker | worker_test demo | ✅?🟡 | 42 | 43 | ## 🚌 Release New Version 44 | 45 | ### 🟡 Pre-Release Checklist 46 | 47 | 1. Update: `demos/browser/index.html` head to use "RELEASE (CDN)" 48 | 2. Check: Is `version` updated in package.json? 49 | 3. Check: Is `version` updated in src/pptxgen.ts? 50 | 4. Check: Is `types/index.d.ts` version in header updated? 51 | 52 | ### 🟢 Release: GitHub 53 | 54 | 1. Checkin all changes via GitHub Desktop 55 | 2. Merge working branch into `main` 56 | 3. Copy CHANGELOG entry and draft new release: [Releases](https://github.com/gitbrent/PptxGenJS/releases) 57 | 4. Use "Version x.x.x" as title and "vX.X.X" as tag 58 | 5. Go back to Releases page, double-check title/tag, release when ready 59 | 60 | ### 🟢 Release: NPM 61 | 62 | ```bash 63 | cd ~/GitHub/PptxGenJS 64 | npm publish 65 | ``` 66 | 67 | ## 🏁 Post-Release Tasks 68 | 69 | 1. Test CDN links on README.md 70 | 2. Load **gh-pages** branch 71 | 3. Update `installation.md` with latest CDN version 72 | 4. Copy contents of the newest "build" folder (from above) into `./demo-react` folder 73 | 5. Update API documentation if needed 74 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | # PptxGenJS Testing Guide 2 | 3 | This document outlines how to manually test PptxGenJS across supported platforms and environments prior to release. 4 | 5 | > ✅ Run these tests to ensure compatibility with major bundlers, runtimes, and front-end frameworks. 6 | 7 | Config Notes 8 | 9 | > ⚠️ Disable any VPN on the machine being used to serve from, or clients using IP address cant connect." 10 | 11 | ## 🧪 Test Suites Overview 12 | 13 | | Platform | Tooling | Status | 14 | | --------------- | -------------------- | ------ | 15 | | Browser | Standalone HTML demo | ✅ | 16 | | Node.js | Native CLI | ✅ | 17 | | Web Worker | JS Worker demo | ✅ | 18 | | Vite/TypeScript | Modern front-end SPA | ✅ | 19 | | Webpack | SharePoint Framework | ✅ | 20 | 21 | --- 22 | 23 | ## 🌐 Browser Tests 24 | 25 | **Purpose:** Validate browser compatibility using the standalone bundle as script. 26 | 27 | ### Desktop & Mobile Browsers 28 | 29 | Run local test server: 30 | 31 | ```bash 32 | cd demos 33 | node browser_server.mjs 34 | ``` 35 | 36 | 1. Open the [Demo Page](http://localhost:8000/browser/index.html). 37 | 2. In DevTools, confirm the latest `pptxgen.bundle.js` is loaded (`Sources` tab). 38 | 3. Run all UI-driven demos and verify demo presentation render correctly. 39 | 4. Open the [Demo Page](http://192.168.254.x:8000/browser/index.html) on iPhone & test. 40 | 41 | ### Web Worker API 42 | 43 | 1. Open the [Web Worker Demo Page](localhost:8000/browser/worker_test.html). 44 | 2. Note: Use Chrome (Safari *will not work*) 45 | 3. Run the test; verify result & library version 46 | 47 | ### Microsoft 365 Check 48 | 49 | 1. Upload the full demo output from above to M365/Office/OneDrive. 50 | 2. Use web viewer to validate file 51 | 52 | --- 53 | 54 | ## 📦 Node.js Tests 55 | 56 | **Purpose:** Validate functionality of CommonJS module in pure Node environments. 57 | 58 | ### CLI Tests 59 | 60 | Run the following test commands: 61 | 62 | ```bash 63 | cd demos/node 64 | npm install 65 | npm run demo 66 | npm run demo-all 67 | ``` 68 | 69 | 1. Confirm console output and exported PPTX files are correct. 70 | 71 | ### Stream Test 72 | 73 | ```bash 74 | npm run demo-stream 75 | ``` 76 | 77 | 1. Confirm stream download PPTX file is correct. 78 | 2. Open the [Stream URL](http://192.168.254.x:3000/) on iPhone & test. 79 | 80 | --- 81 | 82 | ## ⚛️ Vite + TypeScript Tests 83 | 84 | **Purpose:** Validate integration in modern front-end SPA toolchains (Vite, TypeScript, React-compatible). 85 | 86 | Ensure the latest files below are copied to local `node_modules`: 87 | 88 | - `dist/pptxgen.es.js` 89 | - `types/index.d.ts` 90 | 91 | 1. Update `package.json` (and `package-lock.json` if needed) in `demos/vite-demo/` 92 | 2. Check for TS errors in files: 93 | 94 | - Open `src/tstest/Test.tsx` 95 | - Use IntelliSense to autocomplete things like `pptxgen.ChartType.` 96 | 97 | Start the app: 98 | 99 | ```bash 100 | cd demos/vite-demo 101 | npm install 102 | npm run dev 103 | ``` 104 | 105 | From your network: 106 | 107 | - MacBook..: [Demo](http://localhost:8080/PptxGenJS/) 108 | - iPhone...: [Demo](http://192.168.254.x:8080/PptxGenJS/) 109 | - Android..: [Demo](http://192.168.254.x:8080/PptxGenJS/) 110 | 111 | 1. Run test slides, export PowerPoint files. 112 | 2. Open files on each device to verify: 113 | 114 | - MIME type is valid 115 | - File renders as expected in PowerPoint or previewer 116 | 117 | --- 118 | 119 | ## 🚀 Build for gh-pages (Manual) 120 | 121 | After confirming the above: 122 | 123 | ```bash 124 | npm run build 125 | ``` 126 | 127 | 1. Copy the entire `dist` folder from `demos/vite-demo/` to a safe location. 128 | 2. Use this copy when updating the `gh-pages` branch after the release. 129 | 130 | > ⚠️ DO NOT use the "deploy" script displayed onscreen by Vite. Manual copying ensures full control over final content. 131 | 132 | --- 133 | 134 | ## 🏁 Test Completion Checklist 135 | 136 | | Dist File | Test | Tested Via | Result | 137 | | ----------------- | ---------- | ---------------------- | ------ | 138 | | pptxgen.es.js | Webpack 4 | SPFx (v1.16.1) project | ✅?🟡 | 139 | | pptxgen.es.js | Webpack 5 | SPFx (v1.19.1) project | ✅?🟡 | 140 | | pptxgen.es.js | Rollup 4 | Vite (v6) demo | ✅?🟡 | 141 | | pptxgen.es.js | Webworkers | worker_test demo | ✅?🟡 | 142 | | pptxgen.cjs.js | Node/CJS | Node demo | ✅?🟡 | 143 | | pptxgen.bundle.js | Script | Browser demo (desktop) | ✅?🟡 | 144 | | pptxgen.bundle.js | Script | Browser demo (iOS) | ✅?🟡 | 145 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pptxgenjs", 3 | "description": "JavaScript framework that creates PowerPoint (pptx) presentations", 4 | "main": [ 5 | "dist/pptxgen.js" 6 | ], 7 | "dependencies": { 8 | "jszip": "^3.10.1" 9 | }, 10 | "authors": [ 11 | "Brent Ely " 12 | ], 13 | "license": "MIT", 14 | "keywords": [ 15 | "javascript", 16 | "javascript-create-powerpoint", 17 | "javascript-create-pptx", 18 | "javascript-pptx", 19 | "javascript-powerpoint", 20 | "javascript-powerpoint-chart", 21 | "js-create-powerpoint", 22 | "js-create-powerpoint-chart", 23 | "js-generate-powerpoint", 24 | "js-generate-powerpoint-chart", 25 | "js-produce-powerpoint", 26 | "js-produce-powerpoint-chart", 27 | "js-powerpoint", 28 | "js-powerpoint-chart", 29 | "powerpoint", 30 | "powerpoint-presentation", 31 | "ppt", 32 | "pptx", 33 | "presentations" 34 | ], 35 | "homepage": "https://github.com/gitbrent/PptxGenJS", 36 | "ignore": [ 37 | "**/.*", 38 | "node_modules", 39 | "bower_components", 40 | "demo", 41 | "docs", 42 | "test", 43 | "tests" 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /demos/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 160, 3 | "useTabs": true, 4 | "tabWidth": 4, 5 | "semi": true, 6 | "singleQuote": false 7 | } 8 | -------------------------------------------------------------------------------- /demos/browser/css/style.css: -------------------------------------------------------------------------------- 1 | nav.navbar .form-inline i.bi { 2 | font-size: 24px; 3 | } 4 | 5 | main>.tab-pane { 6 | background-color: var(--bs-black); 7 | padding: 1.5rem; 8 | /* p-4 */ 9 | } 10 | 11 | main>.tab-pane .card-header { 12 | background: var(--bs-primary-bg-subtle) !important; 13 | color: var(--bs-primary-text-emphasis) !important; 14 | padding: 1rem !important; 15 | } 16 | 17 | main>.tab-pane>section { 18 | background: var(--bs-body-bg); 19 | } 20 | 21 | main>.tab-pane .card-body h6 { 22 | color: var(--bs-cyan); 23 | } 24 | 25 | .tab-pane .card-body .list-group-item { 26 | background-color: var(--bs-black); 27 | } 28 | 29 | .accordion-collapse { 30 | background: black; 31 | } 32 | 33 | .accordion-collapse>.accordion-body { 34 | padding: 1.5rem; 35 | /* p-4 */ 36 | } 37 | 38 | .lg-bm h6 { 39 | color: var(--bs-cyan); 40 | } 41 | 42 | .lg-bm ul.list-group { 43 | margin-bottom: 0.5rem; 44 | } 45 | 46 | /* Embed the SVG as a background image */ 47 | .info-icon { 48 | width: 16px; 49 | height: 16px; 50 | background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%23ffffff%22%20class%3D%22bi%20bi-info-circle%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cpath%20d%3D%22M8%2015A7%207%200%201%201%208%201a7%207%200%200%201%200%2014m0%201A8%208%200%201%200%208%200a8%208%200%200%200%200%2016%22/%3E%3Cpath%20d%3D%22m8.93%206.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738%203.468c-.194.897.105%201.319.808%201.319.545%200%201.178-.252%201.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275%200-.375-.193-.304-.533zM9%204.5a1%201%200%201%201-2%200%201%201%200%200%201%202%200%22/%3E%3C/svg%3E"); 51 | background-repeat: no-repeat; 52 | background-size: 16px 16px; 53 | } 54 | 55 | .icon24Mastodon { 56 | display: inline-block; 57 | vertical-align: middle; 58 | width: 24px; 59 | height: 24px; 60 | background-image: url("data:image/svg+xml,"); 61 | background-repeat: no-repeat; 62 | background-size: 24px 24px; 63 | } 64 | 65 | .icon24GitHub { 66 | display: inline-block; 67 | vertical-align: middle; 68 | width: 24px; 69 | height: 24px; 70 | background-image: url("data:image/svg+xml,"); 71 | background-repeat: no-repeat; 72 | background-size: 24px 24px; 73 | } 74 | 75 | /* 76 | 77 | */ 78 | 79 | #demo-sandbox:focus { 80 | /* Chrome draws a super-annoying outline around *each line* in the tag, so NOPE! */ 81 | outline: none; 82 | /*-webkit-focus-ring-color auto 5px;*/ 83 | } 84 | 85 | /* Works, but is always black color (WTF) */ 86 | .iconInfo24 { 87 | width: 24px; 88 | height: 24px; 89 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath d='M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z' /%3E%3C/svg%3E"); 90 | background-repeat: no-repeat; 91 | } 92 | 93 | /* -- brentstrap.css ---------- */ 94 | .text-sm { 95 | font-size: 0.8rem; 96 | } 97 | 98 | .card-body-text-sm .card .card-body { 99 | font-size: 0.8rem; 100 | } 101 | 102 | .cursor-help { 103 | cursor: help; 104 | } 105 | 106 | .nav-tabs .nav-item.show .nav-link, 107 | .nav-tabs .nav-link.active { 108 | background-color: var(--bs-black); 109 | border-bottom-color: var(--bs-black); 110 | } 111 | 112 | /* 113 | .bde-arrow-cont { 114 | cursor: pointer; 115 | -webkit-user-select: none; 116 | -moz-user-select: none; 117 | -ms-user-select: none; 118 | user-select: none; 119 | } 120 | 121 | .bde-arrow-cont span { 122 | font-size: 50% !important; 123 | color: #b9c9d9 !important; 124 | text-transform: none !important; 125 | margin-left: 4px; 126 | } 127 | 128 | .arrow { 129 | display: inline-block; 130 | height: 12px; 131 | width: 12px; 132 | border-top: 3px solid var(--bs-primary); 133 | border-right: 3px solid var(--bs-primary); 134 | margin-right: 10px; 135 | cursor: pointer; 136 | transition: all 0.25s ease; 137 | transform: rotate(45deg); 138 | } 139 | 140 | .arrow.active { 141 | transform: rotate(135deg); 142 | margin-bottom: 3px; 143 | } 144 | */ 145 | /* -- HTML2PPTX ---------- */ 146 | #tabAutoPaging thead tr th:first-child { 147 | background-color: var(--bs-cyan); 148 | color: var(--bs-white); 149 | } 150 | 151 | .tabHtmlToPpt { 152 | padding: 0.5rem; 153 | } 154 | .tabHtmlToPpt thead tr th { 155 | color: white; 156 | background-color: var(--bs-dark); 157 | } 158 | .tabHtmlToPpt tbody tr th { 159 | color: green; 160 | background-color: var(--bs-black); 161 | } 162 | .tabHtmlToPpt tbody tr td { 163 | color: #696969; 164 | background-color: var(--bs-black); 165 | } 166 | 167 | /* -- brentstrap.css ---------- */ 168 | /* OLD: 169 | body { 170 | background-color: var(--bs-light) !important; 171 | } 172 | .tab-pane { 173 | background-color: var(--bs-white) !important; 174 | } 175 | @media (prefers-color-scheme: dark) { 176 | :root { 177 | --bs-white: black; 178 | --bs-secondary: #232323; 179 | --bs-light: #363636; 180 | } 181 | } 182 | */ 183 | /* 184 | @media (prefers-color-scheme: dark) { 185 | .h1, 186 | .h2, 187 | .h3, 188 | .h4, 189 | .h5, 190 | .h6, 191 | h1, 192 | h2, 193 | h3, 194 | h4, 195 | h5, 196 | h6 { 197 | font-weight: 300 !important; 198 | } 199 | .tab-pane { 200 | background-color: #030303; 201 | } 202 | 203 | .form-label { 204 | text-transform: uppercase; 205 | font-size: 0.75rem; 206 | color: rgba(255, 255, 255, 0.6) !important; 207 | } 208 | .form-text { 209 | font-size: 0.7rem; 210 | color: rgba(255, 255, 255, 0.4) !important; 211 | } 212 | .form-control, 213 | .form-select { 214 | color: var(--bs-dark); 215 | } 216 | 217 | .bg-gray-100 { 218 | background-color: var(--bs-gray-900); 219 | } 220 | 221 | #tabAutoPaging tbody tr td { 222 | background-color: #030303; 223 | } 224 | #tabAutoPaging tbody td:first-child { 225 | background: var(--bs-gray-900); 226 | } 227 | 228 | .accordion-button { 229 | background-color: var(--bs-gray-700); 230 | } 231 | .accordion-button:not(.collapsed) { 232 | background-color: var(--bs-gray-400); 233 | color: var(--bs-gray-700); 234 | } 235 | .accordion-item { 236 | border: 1px solid var(--bs-gray-700); 237 | } 238 | 239 | .border { 240 | border: 1px solid var(--bs-gray-600) !important; 241 | } 242 | 243 | .card-body { 244 | background-color: #131313; 245 | } 246 | 247 | pre[class*="language-"] { 248 | background-color: #131313 !important; 249 | } 250 | 251 | .form-control:disabled, 252 | .form-control[readonly] { 253 | background-color: #131313 !important; 254 | } 255 | } 256 | @media (prefers-color-scheme: light) { 257 | .tab-pane { 258 | background-color: var(--bs-white); 259 | } 260 | // 20210829: cyborg comes first in stylehseet order, but the way its written (diff from yeti?), it overrides these colors to white, which sucks 261 | .nav-pills .nav-link, 262 | .nav-tabs .nav-link { 263 | color: var(--bs-primary) !important; 264 | } 265 | .nav-pills .nav-link.active, 266 | .nav-pills .show > .nav-link { 267 | color: white !important; 268 | } 269 | .nav-pills .nav-link:hover, 270 | .nav-tabs .nav-link:hover { 271 | background-color: var(--bs-primary) !important; 272 | color: var(--bs-white) !important; 273 | } 274 | 275 | .form-label { 276 | text-transform: uppercase; 277 | font-size: 0.75rem; 278 | color: var(--bs-gray); 279 | } 280 | .form-text { 281 | font-size: 0.7rem; 282 | color: rgba(0, 0, 0, 0.4) !important; 283 | } 284 | 285 | .bg-gray-100 { 286 | background-color: var(--bs-gray-100); 287 | } 288 | 289 | #tabAutoPaging tbody td:first-child { 290 | background: var(--bs-gray-100); 291 | } 292 | } 293 | */ 294 | -------------------------------------------------------------------------------- /demos/browser/html/header.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Interactive Feature Demos

5 |
6 |
7 | 10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /demos/browser/html/navbar.html: -------------------------------------------------------------------------------- 1 | 37 | -------------------------------------------------------------------------------- /demos/browser/html/navtabs.html: -------------------------------------------------------------------------------- 1 | 45 | -------------------------------------------------------------------------------- /demos/browser/html/tab-charts.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
Charts
7 |
8 | 13 |
14 |
15 |
16 |
17 |
18 |
Slides 1-5
19 |
Bar Chart
20 |
    21 |
  • Chart title, axis props
  • 22 |
  • Vertical and Horizontal
  • 23 |
  • Grid and axis options
  • 24 |
  • Stacked & PercentStacked
  • 25 |
  • Colors, units, formats
  • 26 |
27 |
28 |
29 |
Slide 6
30 |
3D Bar Chart
31 |
    32 |
  • 3D Bar, 3D Cone, 3D Cylinder, 3D Pyramid
  • 33 |
34 |
35 |
36 |
Slide 7
37 |
Tornado Chart
38 |
    39 |
  • Tornado Type
  • 40 |
41 |
42 |
43 |
Slides 8-10
44 |
Line Chart
45 |
    46 |
  • Smoothing, Shadow, Size, Symbols
  • 47 |
  • Data Symbol, Size
  • 48 |
  • Lots of Categories
  • 49 |
50 |
51 |
52 |
Slide 11
53 |
Area Chart
54 |
    55 |
  • Area Chart, Stacked Area Chart
  • 56 |
57 |
58 |
59 |
Slides 12-13
60 |
Pie Chart
61 |
    62 |
  • Various options
  • 63 |
  • Doughnut Type
  • 64 |
65 |
66 |
67 |
Slide 14
68 |
X Y (Scatter) Chart
69 |
    70 |
  • Various Options
  • 71 |
72 |
73 |
74 |
Slide 15
75 |
Bubble Chart
76 |
    77 |
  • Various Options
  • 78 |
79 |
80 |
81 |
Slide 16
82 |
Radar Chart
83 |
    84 |
  • Various Options
  • 85 |
86 |
87 |
88 |
Slides 17-18
89 |
Multi-Level Category Axes
90 |
    91 |
  • Multiple Chart Types
  • 92 |
  • Three Level Axes
  • 93 |
94 |
95 |
96 |
Slides 19-20
97 |
Combo Chart
98 |
    99 |
  • Example
  • 100 |
  • Various Options
  • 101 |
102 |
103 |
104 |
Slide 21
105 |
Misc Options
106 |
    107 |
  • Shadows and Transparent Color
  • 108 |
109 |
110 |
111 |
112 | 115 |
116 |
117 | -------------------------------------------------------------------------------- /demos/browser/html/tab-images.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
Images
7 |
8 | 13 |
14 |
15 |
16 |
17 |
18 |
Slide 1
19 |
Image Types
20 |
    21 |
  • Type: Animated GIF
  • 22 |
  • Type: GIF
  • 23 |
  • Type: JPG
  • 24 |
  • Type: PNG
  • 25 |
  • Type: SVG
  • 26 |
27 |
28 |
29 |
Slide 2
30 |
Image URLs
31 |
    32 |
  • Source: GitHub CDN
  • 33 |
  • Source: Wikimedia URL
  • 34 |
  • Source: URL variables
  • 35 |
36 |
37 |
38 |
Slide 3
39 |
Sizing/Rounding
40 |
    41 |
  • Rounding: options
  • 42 |
  • Sizing: contain
  • 43 |
  • Sizing: cover
  • 44 |
  • Sizing: crop
  • 45 |
46 |
47 |
48 |
Slide 4
49 |
Image Rotation
50 |
    51 |
  • Rotate: 45
  • 52 |
  • Rotate: 180
  • 53 |
  • Rotate: 315
  • 54 |
55 |
56 |
57 |
Slide 5
58 |
Image Shadows
59 |
    60 |
  • Type: Outer
  • 61 |
  • Type: None
  • 62 |
  • Type: Inner
  • 63 |
64 |
65 |
66 |
67 | 70 |
71 |
72 |
73 |
74 |
75 |
Media
76 |
77 | 82 |
83 |
84 |
85 |
86 |
87 |
Slide 1
88 |
Video Types
89 |
    90 |
  • Type: avi
  • 91 |
  • Type: m4v
  • 92 |
  • Type: mov
  • 93 |
  • Type: mp4
  • 94 |
95 |
96 |
97 |
Slide 2
98 |
Audio Types
99 |
    100 |
  • Type: mp3
  • 101 |
  • Type: aif
  • 102 |
  • Type: wav
  • 103 |
104 |
105 |
106 |
Slide 3
107 |
YouTube Videos
108 |
109 |
110 |

Notes

111 |
    112 |
  • Only supported in PowerPoint Online & desktop v16 and up.
  • 113 |
  • PowerPoint shows a warning banner when YouTube videos are present.
  • 114 |
115 |
116 |
117 | 118 | 121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | 131 |
132 |
133 | -------------------------------------------------------------------------------- /demos/browser/html/tab-intro.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
Library Version
6 |
7 | info 9 | 10 |
11 |
pptx.version
12 |
13 |
14 |
Scheme Colors
15 |
16 | info 18 | 19 |
20 |
Object.keys(pptx.SchemeColor)
21 |
22 |
23 |
Chart Types
24 |
25 | info 27 | 28 |
29 |
Object.keys(pptx.ChartType)
30 |
31 |
32 |
Shape Types
33 |
34 | info 36 | 37 |
38 |
Object.keys(pptx.ShapeType)
39 |
40 |
41 |
42 |
43 |
44 |

45 | 48 |

49 |
50 |
51 |
52 |
53 |

Live Demo

54 |
Click below to create a basic presentation.
55 |
56 |
57 |
58 | 59 |
60 |
61 |
62 |
63 |
64 |
65 |

66 | 70 |

71 |
72 |
73 |
74 |
75 |

Editable Code

76 |
Use the area below to easily try out various library features.
77 |
78 |
79 |
80 | 81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | -------------------------------------------------------------------------------- /demos/browser/html/tab-masters.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
Slide Masters / Layouts / Templates
7 |
8 | 13 |
14 |
15 |
16 |
17 |

Slide Master Support

18 |
    19 |
  • Create slide decks that match your existing school or company designs.
  • 20 |
  • Define a slide master with code - including logos, backgrounds, placeholders, and slide numbers.
  • 21 |
  • Tip: Slide Masters are reusable blueprints for layout and design, saving you from having to create the same margins, layout, etc.
  • 22 |
23 |
24 |
25 |
26 |
Demo Slides
27 |
Slides 1-6
28 |
    29 |
  • TITLE_SLIDE
  • 30 |
  • MASTER_SLIDE
  • 31 |
  • MASTER_SLIDE
  • 32 |
  • MASTER_SLIDE
  • 33 |
  • MASTER_SLIDE
  • 34 |
  • THANKS_SLIDE
  • 35 |
36 |
37 | 38 |
39 |
40 |
41 |
How To
42 |
Create and Apply
43 |
44 |
45 |
46 |
47 |
48 |
PowerPoint
49 |
Example Slide Master Result
50 | Slide Master Example 51 |
52 |
53 |
54 |
55 |
56 | -------------------------------------------------------------------------------- /demos/browser/html/tab-shapes.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
Shapes
7 |
8 | 13 |
14 |
15 |
16 |
17 |
18 |
Slide 1
19 |
Shapes without Text
20 |
    21 |
  • Shapes: Rectangle, Line, Oval, Triangle
  • 22 |
  • Shapes: Flipped Horizontal
  • 23 |
  • Shapes: Borders
  • 24 |
  • Lines: Arrowheads and Dashes
  • 25 |
26 |
27 |
28 |
Slide 2
29 |
Shapes with Text
30 |
    31 |
  • Shapes: Rectangle, Line, Oval, Triangle
  • 32 |
  • Shapes: Flipped Horizontal
  • 33 |
  • Shapes: Borders
  • 34 |
  • Lines: Arrowheads and Dashes
  • 35 |
36 |
37 |
38 |
39 | 42 |
43 |
44 |
45 |
46 |
47 |
Text
48 |
49 | 54 |
55 |
56 |
57 |
58 |
59 |
Slide 1
60 |
Alignment, Location, Sub/Super Script
61 |
    62 |
  • Text: alignment
  • 63 |
  • Text: locations
  • 64 |
  • Text: subscript / superscript
  • 65 |
66 |
67 |
68 |
Slide 2
69 |
Formatting, Line Breaks, Line Spacing
70 |
    71 |
  • Text: formatting
  • 72 |
  • Text: line-breaks
  • 73 |
  • Text: line-spacing
  • 74 |
75 |
76 |
77 |
Slide 3
78 |
Bullet Styles, Shapes, Indent
79 |
    80 |
  • Bullets: indent levels
  • 81 |
  • Bullets: spacing
  • 82 |
  • Bullets: custom styles
  • 83 |
  • Bullets: custom shapes
  • 84 |
85 |
86 |
87 |
Slide 4
88 |
Hyperlinks, Tab stops, Text Effects
89 |
    90 |
  • Text: hyperlinks
  • 91 |
  • Text: tab stops
  • 92 |
  • Effects: outline, glow, shadow
  • 93 |
94 |
95 |
96 |
Slide 5
97 |
Text Fit
98 |
    99 |
  • Fit: none
  • 100 |
  • Fit: resize
  • 101 |
  • Fit: shrink
  • 102 |
103 |
104 |
105 |
Slide 6
106 |
Scheme Colors
107 |
    108 |
  • Scheme Colors: background
  • 109 |
  • Scheme Colors: text
  • 110 |
111 |
112 |
113 |
114 | 117 |
118 |
119 | -------------------------------------------------------------------------------- /demos/browser/html/tab-tables.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
Tables
7 |
8 | 13 |
14 |
15 |
16 |
17 |
18 |
Table Layout/Format
19 |
Slide 1
20 |
    21 |
  • text alignment
  • 22 |
  • cell styles
  • 23 |
  • row height
  • 24 |
  • col width
  • 25 |
26 |
Slide 2
27 |
    28 |
  • colspans and rowspans
  • 29 |
30 |
Slide 3
31 |
    32 |
  • extreme colspans and rowspans
  • 33 |
34 |
35 |
36 |
Cell Formatting
37 |
Slide 4
38 |
    39 |
  • cell margins
  • 40 |
  • complex cell borders
  • 41 |
  • escaped special characters
  • 42 |
43 |
Slide 5
44 |
    45 |
  • cell text formatting overview
  • 46 |
47 |
Slide 6
48 |
    49 |
  • cell text formatting examples
  • 50 |
51 |
52 |
53 |
Auto-Paging Examples
54 |
Slides 7-8
55 |
    56 |
  • basic auto-paging example
  • 57 |
58 |
Slides 9-12
59 |
    60 |
  • paging with small table dimensions
  • 61 |
62 |
Slides 13-15
63 |
    64 |
  • auto-paging with a Master Page
  • 65 |
66 |
Slide 16
67 |
    68 |
  • auto-paging disabled {autoPage:false}
  • 69 |
70 |
71 |
72 |
Auto-Paging Props
73 |
Slides 17-19
74 |
    75 |
  • start at {y:4.0}, subsequent start at top margin
  • 76 |
77 |
Slides 20-22
78 |
    79 |
  • start at {y:4.0}, subsequent start at {autoPageSlideStartY:1.5}
  • 80 |
81 |
82 |
83 |
Auto-Paging Props
84 |
Slides 23-24
85 |
    86 |
  • various autoPageRepeatHeader thead configs
  • 87 |
88 |
Slides 25-28
89 |
    90 |
  • various autoPageLineWeight values
  • 91 |
92 |
Slides 29-32
93 |
    94 |
  • various autoPageCharWeight values
  • 95 |
96 |
97 |
98 |
Auto-Paging Complex Cell Text
99 |
Slides 33-35
100 |
    101 |
  • complex cell text
  • 102 |
103 |
Slides 36-39
104 |
    105 |
  • complex cell text with calculated lines
  • 106 |
107 |
108 |
109 |
110 | 113 |
114 |
115 | -------------------------------------------------------------------------------- /demos/browser/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/browser/images/favicon-16x16.png -------------------------------------------------------------------------------- /demos/browser/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/browser/images/favicon-32x32.png -------------------------------------------------------------------------------- /demos/browser/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/browser/images/favicon.png -------------------------------------------------------------------------------- /demos/browser/images/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /demos/browser/images/html2pptx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/browser/images/html2pptx.png -------------------------------------------------------------------------------- /demos/browser/images/info-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /demos/browser/images/mastodon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /demos/browser/images/slide-master.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/browser/images/slide-master.png -------------------------------------------------------------------------------- /demos/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | PptxGenJS | Interactive Feature Demos 14 | 15 | 17 | 19 | 21 | 22 | 23 | 25 | 27 | 28 | 29 | 31 | 32 | 33 | 38 | 43 | 46 | 47 | 48 | 64 | 65 |
66 | 67 | 68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /demos/browser/js/FileSaver.min.js: -------------------------------------------------------------------------------- 1 | (function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,"undefined"!=typeof module&&(module.exports=g)}); 2 | 3 | //# sourceMappingURL=FileSaver.min.js.map -------------------------------------------------------------------------------- /demos/browser/js/FileSaver.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/FileSaver.js"],"names":[],"mappings":"uLAkBA,QAAS,CAAA,CAAT,CAAc,CAAd,CAAoB,CAApB,CAA0B,OACJ,WAAhB,QAAO,CAAA,CADa,CACS,CAAI,CAAG,CAAE,OAAO,GAAT,CADhB,CAEC,QAAhB,QAAO,CAAA,CAFQ,GAGtB,OAAO,CAAC,IAAR,CAAa,oDAAb,CAHsB,CAItB,CAAI,CAAG,CAAE,OAAO,CAAE,CAAC,CAAZ,CAJe,EASpB,CAAI,CAAC,OAAL,EAAgB,6EAA6E,IAA7E,CAAkF,CAAI,CAAC,IAAvF,CATI,CAUf,GAAI,CAAA,IAAJ,CAAS,UAA8B,CAA9B,CAAT,CAA8C,CAAE,IAAI,CAAE,CAAI,CAAC,IAAb,CAA9C,CAVe,CAYjB,CACR,CAED,QAAS,CAAA,CAAT,CAAmB,CAAnB,CAAwB,CAAxB,CAA8B,CAA9B,CAAoC,CAClC,GAAI,CAAA,CAAG,CAAG,GAAI,CAAA,cAAd,CACA,CAAG,CAAC,IAAJ,CAAS,KAAT,CAAgB,CAAhB,CAFkC,CAGlC,CAAG,CAAC,YAAJ,CAAmB,MAHe,CAIlC,CAAG,CAAC,MAAJ,CAAa,UAAY,CACvB,CAAM,CAAC,CAAG,CAAC,QAAL,CAAe,CAAf,CAAqB,CAArB,CACP,CANiC,CAOlC,CAAG,CAAC,OAAJ,CAAc,UAAY,CACxB,OAAO,CAAC,KAAR,CAAc,yBAAd,CACD,CATiC,CAUlC,CAAG,CAAC,IAAJ,EACD,CAED,QAAS,CAAA,CAAT,CAAsB,CAAtB,CAA2B,CACzB,GAAI,CAAA,CAAG,CAAG,GAAI,CAAA,cAAd,CAEA,CAAG,CAAC,IAAJ,CAAS,MAAT,CAAiB,CAAjB,IAHyB,CAIzB,GAAI,CACF,CAAG,CAAC,IAAJ,EACD,CAAC,MAAO,CAAP,CAAU,CAAE,CACd,MAAqB,IAAd,EAAA,CAAG,CAAC,MAAJ,EAAmC,GAAd,EAAA,CAAG,CAAC,MACjC,CAGD,QAAS,CAAA,CAAT,CAAgB,CAAhB,CAAsB,CACpB,GAAI,CACF,CAAI,CAAC,aAAL,CAAmB,GAAI,CAAA,UAAJ,CAAe,OAAf,CAAnB,CACD,CAAC,MAAO,CAAP,CAAU,CACV,GAAI,CAAA,CAAG,CAAG,QAAQ,CAAC,WAAT,CAAqB,aAArB,CAAV,CACA,CAAG,CAAC,cAAJ,CAAmB,OAAnB,OAAwC,MAAxC,CAAgD,CAAhD,CAAmD,CAAnD,CAAsD,CAAtD,CAAyD,EAAzD,CACsB,EADtB,aACsD,CADtD,CACyD,IADzD,CAFU,CAIV,CAAI,CAAC,aAAL,CAAmB,CAAnB,CACD,CACF,C,GAtDG,CAAA,CAAO,CAAqB,QAAlB,QAAO,CAAA,MAAP,EAA8B,MAAM,CAAC,MAAP,GAAkB,MAAhD,CACV,MADU,CACe,QAAhB,QAAO,CAAA,IAAP,EAA4B,IAAI,CAAC,IAAL,GAAc,IAA1C,CACT,IADS,CACgB,QAAlB,QAAO,CAAA,MAAP,EAA8B,MAAM,CAAC,MAAP,GAAkB,MAAhD,CACP,MADO,O,CAyDP,CAAc,CAAG,YAAY,IAAZ,CAAiB,SAAS,CAAC,SAA3B,GAAyC,cAAc,IAAd,CAAmB,SAAS,CAAC,SAA7B,CAAzC,EAAoF,CAAC,SAAS,IAAT,CAAc,SAAS,CAAC,SAAxB,C,CAEtG,CAAM,CAAG,CAAO,CAAC,MAAR,GAEQ,QAAlB,QAAO,CAAA,MAAP,EAA8B,MAAM,GAAK,CAA1C,CACI,UAAmB,CAAc,CADrC,CAIG,YAAc,CAAA,iBAAiB,CAAC,SAAhC,EAA6C,CAAC,CAA/C,CACA,SAAiB,CAAjB,CAAuB,CAAvB,CAA6B,CAA7B,CAAmC,IAC/B,CAAA,CAAG,CAAG,CAAO,CAAC,GAAR,EAAe,CAAO,CAAC,SADE,CAE/B,CAAC,CAAG,QAAQ,CAAC,aAAT,CAAuB,GAAvB,CAF2B,CAGnC,CAAI,CAAG,CAAI,EAAI,CAAI,CAAC,IAAb,EAAqB,UAHO,CAKnC,CAAC,CAAC,QAAF,CAAa,CALsB,CAMnC,CAAC,CAAC,GAAF,CAAQ,UAN2B,CAWf,QAAhB,QAAO,CAAA,CAXwB,EAajC,CAAC,CAAC,IAAF,CAAS,CAbwB,CAc7B,CAAC,CAAC,MAAF,GAAa,QAAQ,CAAC,MAdO,CAmB/B,CAAK,CAAC,CAAD,CAnB0B,CAe/B,CAAW,CAAC,CAAC,CAAC,IAAH,CAAX,CACI,CAAQ,CAAC,CAAD,CAAO,CAAP,CAAa,CAAb,CADZ,CAEI,CAAK,CAAC,CAAD,CAAI,CAAC,CAAC,MAAF,CAAW,QAAf,CAjBsB,GAuBjC,CAAC,CAAC,IAAF,CAAS,CAAG,CAAC,eAAJ,CAAoB,CAApB,CAvBwB,CAwBjC,UAAU,CAAC,UAAY,CAAE,CAAG,CAAC,eAAJ,CAAoB,CAAC,CAAC,IAAtB,CAA6B,CAA5C,CAA8C,GAA9C,CAxBuB,CAyBjC,UAAU,CAAC,UAAY,CAAE,CAAK,CAAC,CAAD,CAAK,CAAzB,CAA2B,CAA3B,CAzBuB,CA2BpC,CA5BC,CA+BA,oBAAsB,CAAA,SAAtB,CACA,SAAiB,CAAjB,CAAuB,CAAvB,CAA6B,CAA7B,CAAmC,CAGnC,GAFA,CAAI,CAAG,CAAI,EAAI,CAAI,CAAC,IAAb,EAAqB,UAE5B,CAAoB,QAAhB,QAAO,CAAA,CAAX,CAUE,SAAS,CAAC,gBAAV,CAA2B,CAAG,CAAC,CAAD,CAAO,CAAP,CAA9B,CAA4C,CAA5C,CAVF,KACE,IAAI,CAAW,CAAC,CAAD,CAAf,CACE,CAAQ,CAAC,CAAD,CAAO,CAAP,CAAa,CAAb,CADV,KAEO,CACL,GAAI,CAAA,CAAC,CAAG,QAAQ,CAAC,aAAT,CAAuB,GAAvB,CAAR,CACA,CAAC,CAAC,IAAF,CAAS,CAFJ,CAGL,CAAC,CAAC,MAAF,CAAW,QAHN,CAIL,UAAU,CAAC,UAAY,CAAE,CAAK,CAAC,CAAD,CAAK,CAAzB,CACX,CAIJ,CAhBC,CAmBA,SAAiB,CAAjB,CAAuB,CAAvB,CAA6B,CAA7B,CAAmC,CAAnC,CAA0C,CAS1C,GANA,CAAK,CAAG,CAAK,EAAI,IAAI,CAAC,EAAD,CAAK,QAAL,CAMrB,CALI,CAKJ,GAJE,CAAK,CAAC,QAAN,CAAe,KAAf,CACA,CAAK,CAAC,QAAN,CAAe,IAAf,CAAoB,SAApB,CAAgC,gBAGlC,EAAoB,QAAhB,QAAO,CAAA,CAAX,CAA8B,MAAO,CAAA,CAAQ,CAAC,CAAD,CAAO,CAAP,CAAa,CAAb,CAAf,CATY,GAWtC,CAAA,CAAK,CAAiB,0BAAd,GAAA,CAAI,CAAC,IAXyB,CAYtC,CAAQ,CAAG,eAAe,IAAf,CAAoB,CAAO,CAAC,WAA5B,GAA4C,CAAO,CAAC,MAZzB,CAatC,CAAW,CAAG,eAAe,IAAf,CAAoB,SAAS,CAAC,SAA9B,CAbwB,CAe1C,GAAI,CAAC,CAAW,EAAK,CAAK,EAAI,CAAzB,EAAsC,CAAvC,GAAgF,WAAtB,QAAO,CAAA,UAArE,CAAiG,CAE/F,GAAI,CAAA,CAAM,CAAG,GAAI,CAAA,UAAjB,CACA,CAAM,CAAC,SAAP,CAAmB,UAAY,CAC7B,GAAI,CAAA,CAAG,CAAG,CAAM,CAAC,MAAjB,CACA,CAAG,CAAG,CAAW,CAAG,CAAH,CAAS,CAAG,CAAC,OAAJ,CAAY,cAAZ,CAA4B,uBAA5B,CAFG,CAGzB,CAHyB,CAGlB,CAAK,CAAC,QAAN,CAAe,IAAf,CAAsB,CAHJ,CAIxB,QAAQ,CAAG,CAJa,CAK7B,CAAK,CAAG,IACT,CAT8F,CAU/F,CAAM,CAAC,aAAP,CAAqB,CAArB,CACD,CAXD,IAWO,IACD,CAAA,CAAG,CAAG,CAAO,CAAC,GAAR,EAAe,CAAO,CAAC,SAD5B,CAED,CAAG,CAAG,CAAG,CAAC,eAAJ,CAAoB,CAApB,CAFL,CAGD,CAHC,CAGM,CAAK,CAAC,QAAN,CAAiB,CAHvB,CAIA,QAAQ,CAAC,IAAT,CAAgB,CAJhB,CAKL,CAAK,CAAG,IALH,CAML,UAAU,CAAC,UAAY,CAAE,CAAG,CAAC,eAAJ,CAAoB,CAApB,CAA0B,CAAzC,CAA2C,GAA3C,CACX,CACF,CA1FU,C,CA6Fb,CAAO,CAAC,MAAR,CAAiB,CAAM,CAAC,MAAP,CAAgB,C,CAEX,WAAlB,QAAO,CAAA,M,GACT,MAAM,CAAC,OAAP,CAAiB,C","file":"FileSaver.min.js","sourcesContent":["/*\n* FileSaver.js\n* A saveAs() FileSaver implementation.\n*\n* By Eli Grey, http://eligrey.com\n*\n* License : https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md (MIT)\n* source : http://purl.eligrey.com/github/FileSaver.js\n*/\n\n// The one and only way of getting global scope in all environments\n// https://stackoverflow.com/q/3277182/1008999\nvar _global = typeof window === 'object' && window.window === window\n ? window : typeof self === 'object' && self.self === self\n ? self : typeof global === 'object' && global.global === global\n ? global\n : this\n\nfunction bom (blob, opts) {\n if (typeof opts === 'undefined') opts = { autoBom: false }\n else if (typeof opts !== 'object') {\n console.warn('Deprecated: Expected third argument to be a object')\n opts = { autoBom: !opts }\n }\n\n // prepend BOM for UTF-8 XML and text/* types (including HTML)\n // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF\n if (opts.autoBom && /^\\s*(?:text\\/\\S*|application\\/xml|\\S*\\/\\S*\\+xml)\\s*;.*charset\\s*=\\s*utf-8/i.test(blob.type)) {\n return new Blob([String.fromCharCode(0xFEFF), blob], { type: blob.type })\n }\n return blob\n}\n\nfunction download (url, name, opts) {\n var xhr = new XMLHttpRequest()\n xhr.open('GET', url)\n xhr.responseType = 'blob'\n xhr.onload = function () {\n saveAs(xhr.response, name, opts)\n }\n xhr.onerror = function () {\n console.error('could not download file')\n }\n xhr.send()\n}\n\nfunction corsEnabled (url) {\n var xhr = new XMLHttpRequest()\n // use sync to avoid popup blocker\n xhr.open('HEAD', url, false)\n try {\n xhr.send()\n } catch (e) {}\n return xhr.status >= 200 && xhr.status <= 299\n}\n\n// `a.click()` doesn't work for all browsers (#465)\nfunction click (node) {\n try {\n node.dispatchEvent(new MouseEvent('click'))\n } catch (e) {\n var evt = document.createEvent('MouseEvents')\n evt.initMouseEvent('click', true, true, window, 0, 0, 0, 80,\n 20, false, false, false, false, 0, null)\n node.dispatchEvent(evt)\n }\n}\n\n// Detect WebView inside a native macOS app by ruling out all browsers\n// We just need to check for 'Safari' because all other browsers (besides Firefox) include that too\n// https://www.whatismybrowser.com/guides/the-latest-user-agent/macos\nvar isMacOSWebView = /Macintosh/.test(navigator.userAgent) && /AppleWebKit/.test(navigator.userAgent) && !/Safari/.test(navigator.userAgent)\n\nvar saveAs = _global.saveAs || (\n // probably in some web worker\n (typeof window !== 'object' || window !== _global)\n ? function saveAs () { /* noop */ }\n\n // Use download attribute first if possible (#193 Lumia mobile) unless this is a macOS WebView\n : ('download' in HTMLAnchorElement.prototype && !isMacOSWebView)\n ? function saveAs (blob, name, opts) {\n var URL = _global.URL || _global.webkitURL\n var a = document.createElement('a')\n name = name || blob.name || 'download'\n\n a.download = name\n a.rel = 'noopener' // tabnabbing\n\n // TODO: detect chrome extensions & packaged apps\n // a.target = '_blank'\n\n if (typeof blob === 'string') {\n // Support regular links\n a.href = blob\n if (a.origin !== location.origin) {\n corsEnabled(a.href)\n ? download(blob, name, opts)\n : click(a, a.target = '_blank')\n } else {\n click(a)\n }\n } else {\n // Support blobs\n a.href = URL.createObjectURL(blob)\n setTimeout(function () { URL.revokeObjectURL(a.href) }, 4E4) // 40s\n setTimeout(function () { click(a) }, 0)\n }\n }\n\n // Use msSaveOrOpenBlob as a second approach\n : 'msSaveOrOpenBlob' in navigator\n ? function saveAs (blob, name, opts) {\n name = name || blob.name || 'download'\n\n if (typeof blob === 'string') {\n if (corsEnabled(blob)) {\n download(blob, name, opts)\n } else {\n var a = document.createElement('a')\n a.href = blob\n a.target = '_blank'\n setTimeout(function () { click(a) })\n }\n } else {\n navigator.msSaveOrOpenBlob(bom(blob, opts), name)\n }\n }\n\n // Fallback to using FileReader and a popup\n : function saveAs (blob, name, opts, popup) {\n // Open a popup immediately do go around popup blocker\n // Mostly only available on user interaction and the fileReader is async so...\n popup = popup || open('', '_blank')\n if (popup) {\n popup.document.title =\n popup.document.body.innerText = 'downloading...'\n }\n\n if (typeof blob === 'string') return download(blob, name, opts)\n\n var force = blob.type === 'application/octet-stream'\n var isSafari = /constructor/i.test(_global.HTMLElement) || _global.safari\n var isChromeIOS = /CriOS\\/[\\d]+/.test(navigator.userAgent)\n\n if ((isChromeIOS || (force && isSafari) || isMacOSWebView) && typeof FileReader !== 'undefined') {\n // Safari doesn't allow downloading of blob URLs\n var reader = new FileReader()\n reader.onloadend = function () {\n var url = reader.result\n url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, 'data:attachment/file;')\n if (popup) popup.location.href = url\n else location = url\n popup = null // reverse-tabnabbing #460\n }\n reader.readAsDataURL(blob)\n } else {\n var URL = _global.URL || _global.webkitURL\n var url = URL.createObjectURL(blob)\n if (popup) popup.location = url\n else location.href = url\n popup = null // reverse-tabnabbing #460\n setTimeout(function () { URL.revokeObjectURL(url) }, 4E4) // 40s\n }\n }\n)\n\n_global.saveAs = saveAs.saveAs = saveAs\n\nif (typeof module !== 'undefined') {\n module.exports = saveAs;\n}\n"]} -------------------------------------------------------------------------------- /demos/browser/js/loadSections.js: -------------------------------------------------------------------------------- 1 | function loadSection(id, file) { 2 | fetch(file) 3 | .then((response) => { 4 | if (!response.ok) { 5 | throw new Error(`Failed to load ${file}: ${response.statusText}`); 6 | } 7 | return response.text(); 8 | }) 9 | .then((html) => { 10 | document.getElementById(id).outerHTML = html; 11 | //console.log(`Loaded ${file} into ${id}`); 12 | }) 13 | .catch((error) => console.error(error)); 14 | } 15 | 16 | // Load sections 17 | loadSection('navbar', './html/navbar.html'); 18 | loadSection('header', './html/header.html'); 19 | loadSection('navtabs', './html/navtabs.html'); 20 | loadSection('tab-intro', './html/tab-intro.html'); 21 | loadSection('tab-html2pptx', './html/tab-html2pptx.html'); 22 | loadSection('tab-charts', './html/tab-charts.html'); 23 | loadSection('tab-images', './html/tab-images.html'); 24 | loadSection('tab-shapes', './html/tab-shapes.html'); 25 | loadSection('tab-tables', './html/tab-tables.html'); 26 | loadSection('tab-masters', './html/tab-masters.html'); 27 | -------------------------------------------------------------------------------- /demos/browser/js/main.js: -------------------------------------------------------------------------------- 1 | import { 2 | doAppStart, execGenSlidesFunc, runAllDemos, 3 | table2slides1, table2slides2, table2slidesDemoForTab, 4 | doRunBasicDemo, doRunSandboxDemo, buildDataTable, padDataTable 5 | } from './browser.js'; 6 | 7 | // STEP 1: Add event listeners to "run demo" buttons 8 | document.getElementById('btnRunAllDemos').addEventListener('click', () => runAllDemos()); 9 | document.getElementById('btnRunBasicDemo').addEventListener('click', () => doRunBasicDemo()); 10 | document.getElementById('btnRunSandboxDemo').addEventListener('click', () => doRunSandboxDemo()); 11 | document.getElementById('btnGenFunc_Chart').addEventListener('click', () => execGenSlidesFunc('Chart')); 12 | document.getElementById('btnGenFunc_Image').addEventListener('click', () => execGenSlidesFunc('Image')); 13 | document.getElementById('btnGenFunc_Media').addEventListener('click', () => execGenSlidesFunc('Media')); 14 | document.getElementById('btnGenFunc_Shape').addEventListener('click', () => execGenSlidesFunc('Shape')); 15 | document.getElementById('btnGenFunc_Text').addEventListener('click', () => execGenSlidesFunc('Text')); 16 | document.getElementById('btnGenFunc_Table').addEventListener('click', () => execGenSlidesFunc('Table')); 17 | document.getElementById('btnGenFunc_Master').addEventListener('click', () => execGenSlidesFunc('Master')); 18 | 19 | // STEP 2: HTML-to-PPTX: Dynamic Table input handlers 20 | document.getElementById('table2slides1').addEventListener('click', () => table2slides1()); 21 | document.getElementById('table2slides2').addEventListener('click', () => table2slides2(false)); 22 | document.getElementById('table2slides3').addEventListener('click', () => table2slides2(true)); 23 | document.getElementById('tab2slides_tabNoStyle').addEventListener('click', () => table2slidesDemoForTab('tabNoStyle')); 24 | document.getElementById('tab2slides_tabInheritStyle').addEventListener('click', () => table2slidesDemoForTab('tabInheritStyle')); 25 | document.getElementById('tab2slides_tabColspan').addEventListener('click', () => table2slidesDemoForTab('tabColspan')); 26 | document.getElementById('tab2slides_tabRowspan').addEventListener('click', () => table2slidesDemoForTab('tabRowspan')); 27 | document.getElementById('tab2slides_tabRowColspan').addEventListener('click', () => table2slidesDemoForTab('tabRowColspan')); 28 | document.getElementById('tab2slides_tabLotsOfLines').addEventListener('click', () => table2slidesDemoForTab('tabLotsOfLines', { verbose: false })); 29 | document.getElementById('tab2slides_tabLargeCellText').addEventListener('click', () => table2slidesDemoForTab('tabLargeCellText', { verbose: false })); 30 | document.getElementById('numTab2SlideRows').addEventListener('change', () => buildDataTable()); 31 | document.getElementById('numTab2Padding').addEventListener('change', () => padDataTable()); 32 | 33 | // LAST: START! 34 | document.addEventListener('DOMContentLoaded', () => doAppStart()); 35 | -------------------------------------------------------------------------------- /demos/browser/js/pptxgenjs_worker.js: -------------------------------------------------------------------------------- 1 | // demos/browser/js/pptxgenjs_worker.js 2 | 3 | // IMPORTANT: You need to load pptxgenjs within the worker. 4 | // Assuming your built pptxgen.js is available relative to the worker script. 5 | // Adjust the path as needed based on your project structure. 6 | try { 7 | importScripts('./pptxgen.bundle.js'); 8 | console.log('pptxgenjs loaded successfully in worker.'); 9 | } catch (e) { 10 | console.error('Failed to load pptxgenjs in worker:', e); 11 | // You might want to post an error message back to the main thread 12 | } 13 | 14 | // Listen for messages from the main thread 15 | self.onmessage = async function(event) { 16 | console.log('Worker received message:', event.data); 17 | 18 | const message = event.data; 19 | 20 | if (message.type === 'generatePpt') { 21 | try { 22 | // Inform the main thread that generation is starting 23 | self.postMessage({ type: 'status', message: 'Generating presentation...' }); 24 | 25 | // *** pptxgenjs code runs here *** 26 | let pptx = new PptxGenJS(); 27 | let slide = pptx.addSlide(); 28 | slide.addText( 29 | '👷 Hello from Web Worker!', 30 | { x: 1, y: 1, w: 8, h: 1, fontSize: 24, fill: { color: 'FFFF00' } } 31 | ); 32 | slide.addText( 33 | `Generated at: ${new Date().toLocaleString()}`, 34 | { x: 1, y: 2, w: 4, h: 0.5, fontSize: 14 } 35 | ); 36 | slide.addText( 37 | `Library version: ${pptx.version}`, 38 | { x: 5, y: 2, w: 4, h: 0.5, fontSize: 14 } 39 | ); 40 | // Test with an image from a URL as Issue #1354 called this out) 41 | slide.addImage({ 42 | path: "https://raw.githubusercontent.com/gitbrent/PptxGenJS/master/demos/common/images/krita_square.jpg", 43 | x: 1.0, y: 3.1, w: 2.0, h: 2.0 44 | }); 45 | slide.addText("<-- test image via `path` URL", { x: 3.1, y: 4.7, w: 4, h: 0.5, fontSize: 14, color: '0000FF' }); 46 | 47 | // Generate the presentation as a Blob or ArrayBuffer to send back 48 | // Blob is often easiest for saving/downloading in the main thread 49 | const blob = await pptx.write('blob'); 50 | self.postMessage({ type: 'blobGenerated', data: blob }) 51 | // COMMENTED OUT: This is a test to see if the arrayBuffer works 52 | /* 53 | const buffer = await pptx.write('arraybuffer'); 54 | self.postMessage({ type: 'buffGenerated', buffer }, [buffer]) 55 | */ 56 | } catch (error) { 57 | console.error('Error generating presentation in worker:', error); 58 | // Send an error message back to the main thread 59 | self.postMessage({ type: 'error', message: error.message || 'An error occurred during generation.' }); 60 | } 61 | } 62 | }; 63 | 64 | console.log('Web Worker script loaded.'); 65 | -------------------------------------------------------------------------------- /demos/browser/js/test_worker.js: -------------------------------------------------------------------------------- 1 | // demos/browser/js/pptxgenjs_worker.js 2 | console.log('Simple worker script loaded successfully!'); 3 | 4 | self.onmessage = function(event) { 5 | console.log('Simple worker received message:', event.data); 6 | self.postMessage({ type: 'testSuccess', message: 'Worker received and responded!' }); 7 | }; 8 | 9 | self.postMessage({ type: 'status', message: 'Simple worker is alive.' }); 10 | -------------------------------------------------------------------------------- /demos/browser/js/worker_test_main.js: -------------------------------------------------------------------------------- 1 | // demos/browser/js/worker_test_main.js 2 | document.addEventListener('DOMContentLoaded', () => { 3 | const generateButton = document.getElementById('generatePptWorker'); 4 | const statusDiv = document.getElementById('workerStatus'); 5 | 6 | if (!generateButton || !statusDiv) { 7 | console.error('Required HTML elements not found!'); 8 | statusDiv.textContent = 'Error: Could not find necessary HTML elements.'; 9 | return; 10 | } 11 | 12 | // Create the Web Worker instance 13 | // The path is relative to the HTML file location 14 | const pptxWorker = new Worker('./js/pptxgenjs_worker.js'); 15 | // const pptxWorker = new Worker('./js/test_worker.js'); // TESTING ONLY 16 | 17 | // Listen for messages *from* the worker 18 | pptxWorker.onmessage = function(event) { 19 | console.log('Main thread received message from worker:', event.data); 20 | const message = event.data; 21 | 22 | if (message.type === 'status') { 23 | statusDiv.textContent = `Status: ${message.message}`; 24 | // Disable button while working 25 | generateButton.disabled = true; 26 | } else if (message.type === 'blobGenerated') { 27 | const pptBlob = message.data; 28 | //statusDiv.textContent = 'Status: Presentation generated successfully!'; 29 | 30 | // Use FileSaver.js to save the blob 31 | // You might need to include FileSaver.js in worker_test.html 32 | if (typeof saveAs === 'function') { 33 | saveAs(pptBlob, 'worker_demo.pptx'); 34 | statusDiv.textContent += ' Downloading blob...'; 35 | } else { 36 | statusDiv.textContent += ' Generated, but FileSaver.js not available to download.'; 37 | console.error('FileSaver.js not found. Cannot save the generated blob.'); 38 | } 39 | 40 | generateButton.disabled = false; 41 | } else if (message.type === 'buffGenerated') { 42 | const pptBlob = new Blob( 43 | [message.buffer], 44 | { type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation' } 45 | ) 46 | saveAs(pptBlob, 'worker_demo.pptx') 47 | statusDiv.textContent += ' Downloading arrayBuffer...'; 48 | } else if (message.type === 'error') { 49 | statusDiv.textContent = `Error: ${message.message}`; 50 | generateButton.disabled = false; // Re-enable button 51 | console.error('Error received from worker:', message.message); 52 | } 53 | }; 54 | 55 | // Handle potential errors from the worker itself (e.g. script loading failed) 56 | pptxWorker.onerror = function(error) { 57 | statusDiv.textContent = `Worker Error: ${error.message}`; 58 | generateButton.disabled = false; 59 | console.error('Web Worker encountered an error:', error); 60 | }; 61 | 62 | // Add event listener to the button 63 | generateButton.addEventListener('click', () => { 64 | statusDiv.textContent = 'Status: Sending request to worker...'; 65 | generateButton.disabled = true; // Disable button immediately 66 | 67 | // Send a message to the worker to start generation 68 | pptxWorker.postMessage({ type: 'generatePpt' }); 69 | }); 70 | 71 | statusDiv.textContent = 'Status: Page loaded, worker initialized.'; 72 | }); 73 | -------------------------------------------------------------------------------- /demos/browser/worker_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | PptxGenJS | Web Worker API Demo 10 | 12 | 13 | 14 |
15 |

PptxGenJS | Web Worker API Demo

16 |
17 |
About
18 |
    19 |
  • Test generating a PowerPoint presentation using pptxgenjs in a Worker.
  • 20 |
  • The generated pptx will download automatically.
  • 21 |
  • (NOTE: This will not run in Safari locally!)
  • 22 |
23 |
24 | 25 |
26 |
MESSAGES
27 |
Loading...
28 |
29 |
30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /demos/browser_server.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * NAME: browser_server.mjs 3 | * DESC: Local web server for ./demos/browser/index.html 4 | * DESC: since we moved to modules (`jsm`) with v3.6.0, merely opening the local file in browser gives CORS errors 5 | * REQS: express 6 | * USAGE: `node browser_server.mjs` 7 | * AUTH: Brent Ely (https://github.com/gitbrent/) 8 | * DATE: 20210602 9 | */ 10 | 11 | // Use `createRequire` as `require` wont work by default in modules 12 | import { createRequire } from "module"; 13 | const require = createRequire(import.meta.url); 14 | 15 | import express from "express"; 16 | const app = express(); 17 | const port = 8000; 18 | const DEMO_URL = `http://localhost:${port}/browser/index.html`; 19 | 20 | app.use("/browser", express.static("./browser")); 21 | app.use("/common", express.static("./common")); 22 | app.use("/modules", express.static("./modules")); 23 | 24 | app.listen(port, () => { 25 | console.log(`\n----------------------==~==~==~==[ SERVER RUNNING ]==~==~==~==----------------------\n`); 26 | console.log(`The pptxgenjs browser demo is now live at: ${DEMO_URL}\n`); 27 | console.log(`(Press Ctrl-C to stop)`); 28 | console.log(`\n----------------------==~==~==~==[ SERVER RUNNING ]==~==~==~==----------------------\n`); 29 | }); 30 | 31 | let start = process.platform == "darwin" ? "open" : process.platform == "win32" ? "start" : "xdg-open"; 32 | require("child_process").exec(start + " " + DEMO_URL); 33 | -------------------------------------------------------------------------------- /demos/common/images/UPPERCASE.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/UPPERCASE.PNG -------------------------------------------------------------------------------- /demos/common/images/anim_campfire.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/anim_campfire.gif -------------------------------------------------------------------------------- /demos/common/images/brokenImage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/brokenImage.gif -------------------------------------------------------------------------------- /demos/common/images/brokenImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/brokenImage.png -------------------------------------------------------------------------------- /demos/common/images/cc_copyremix.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/cc_copyremix.gif -------------------------------------------------------------------------------- /demos/common/images/cc_dj.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/cc_dj.gif -------------------------------------------------------------------------------- /demos/common/images/cc_license_comp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/cc_license_comp.png -------------------------------------------------------------------------------- /demos/common/images/cc_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/cc_logo.jpg -------------------------------------------------------------------------------- /demos/common/images/cc_symbols_trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/cc_symbols_trans.png -------------------------------------------------------------------------------- /demos/common/images/chicago_bean_bohne.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/chicago_bean_bohne.jpg -------------------------------------------------------------------------------- /demos/common/images/cover_audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/cover_audio.png -------------------------------------------------------------------------------- /demos/common/images/cover_video_16x9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/cover_video_16x9.png -------------------------------------------------------------------------------- /demos/common/images/fediverse_actpub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/fediverse_actpub.png -------------------------------------------------------------------------------- /demos/common/images/fediverse_tree.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/fediverse_tree.jpg -------------------------------------------------------------------------------- /demos/common/images/image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/image2.jpg -------------------------------------------------------------------------------- /demos/common/images/krita_splashscreen.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/krita_splashscreen.jpeg -------------------------------------------------------------------------------- /demos/common/images/krita_square.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/krita_square.jpg -------------------------------------------------------------------------------- /demos/common/images/logo_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/logo_square.png -------------------------------------------------------------------------------- /demos/common/images/logo_square_25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/logo_square_25.png -------------------------------------------------------------------------------- /demos/common/images/logo_square_50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/logo_square_50.png -------------------------------------------------------------------------------- /demos/common/images/mastodon-logo-purple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /demos/common/images/nyc-subway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/nyc-subway.png -------------------------------------------------------------------------------- /demos/common/images/peace4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/peace4.png -------------------------------------------------------------------------------- /demos/common/images/play-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/play-button.png -------------------------------------------------------------------------------- /demos/common/images/png-gradient-hex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/png-gradient-hex.png -------------------------------------------------------------------------------- /demos/common/images/sample-hd-m4v-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/sample-hd-m4v-cover.png -------------------------------------------------------------------------------- /demos/common/images/sample_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/sample_logo.png -------------------------------------------------------------------------------- /demos/common/images/starlabs_bkgd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/starlabs_bkgd.jpg -------------------------------------------------------------------------------- /demos/common/images/starlabs_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/starlabs_logo.png -------------------------------------------------------------------------------- /demos/common/images/sydney_harbour_bridge_night.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/sydney_harbour_bridge_night.jpg -------------------------------------------------------------------------------- /demos/common/images/title_bkgd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/title_bkgd.jpg -------------------------------------------------------------------------------- /demos/common/images/title_bkgd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/title_bkgd.png -------------------------------------------------------------------------------- /demos/common/images/title_bkgd_alt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/title_bkgd_alt.jpg -------------------------------------------------------------------------------- /demos/common/images/tokyo-subway-route-map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/tokyo-subway-route-map.jpg -------------------------------------------------------------------------------- /demos/common/images/trippy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/trippy.gif -------------------------------------------------------------------------------- /demos/common/images/unite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/unite.png -------------------------------------------------------------------------------- /demos/common/images/video-mp4-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/video-mp4-thumb.png -------------------------------------------------------------------------------- /demos/common/images/wiki-example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/images/wiki-example.jpg -------------------------------------------------------------------------------- /demos/common/media/earth-big.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/media/earth-big.mp4 -------------------------------------------------------------------------------- /demos/common/media/sample-hd.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/media/sample-hd.m4v -------------------------------------------------------------------------------- /demos/common/media/sample.aif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/media/sample.aif -------------------------------------------------------------------------------- /demos/common/media/sample.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/media/sample.avi -------------------------------------------------------------------------------- /demos/common/media/sample.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/media/sample.m4v -------------------------------------------------------------------------------- /demos/common/media/sample.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/media/sample.mov -------------------------------------------------------------------------------- /demos/common/media/sample.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/media/sample.mp3 -------------------------------------------------------------------------------- /demos/common/media/sample.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/media/sample.mp4 -------------------------------------------------------------------------------- /demos/common/media/sample.mpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/media/sample.mpg -------------------------------------------------------------------------------- /demos/common/media/sample.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/common/media/sample.wav -------------------------------------------------------------------------------- /demos/modules/demo_master.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * NAME: demo_master.js 3 | * AUTH: Brent Ely (https://github.com/gitbrent/) 4 | * DESC: Common test/demo slides for all library features 5 | * DEPS: Used by various demos (./demos/browser, ./demos/node, etc.) 6 | * VER.: 3.5.0 7 | * BLD.: 20210401 8 | */ 9 | 10 | import { IMAGE_PATHS } from "./enums.mjs"; 11 | 12 | export function genSlides_Master(pptx) { 13 | pptx.addSection({ title: "Masters" }); 14 | 15 | genSlide01(pptx); 16 | genSlide02(pptx); 17 | genSlide03(pptx); 18 | genSlide04(pptx); 19 | genSlide05(pptx); 20 | genSlide06(pptx); 21 | //genSlide07(pptx); 22 | } 23 | 24 | /** 25 | * SLIDE 1: 26 | * @param {PptxGenJS} pptx 27 | */ 28 | function genSlide01(pptx) { 29 | let slide = pptx.addSlide({ masterName: "TITLE_SLIDE", sectionTitle: "Masters" }); 30 | //let slide1 = pptx.addSlide({masterName:'TITLE_SLIDE', sectionTitle:'FAILTEST'}); // TEST: Should show console warning ("title not found") 31 | slide.addNotes("Master name: `TITLE_SLIDE`\nAPI Docs: https://gitbrent.github.io/PptxGenJS/docs/masters.html"); 32 | } 33 | 34 | /** 35 | * SLIDE 2: 36 | * @param {PptxGenJS} pptx 37 | */ 38 | function genSlide02(pptx) { 39 | let slide = pptx.addSlide({ masterName: "MASTER_SLIDE", sectionTitle: "Masters" }); 40 | slide.addNotes("Master name: `MASTER_SLIDE`\nAPI Docs: https://gitbrent.github.io/PptxGenJS/docs/masters.html"); 41 | } 42 | 43 | /** 44 | * SLIDE 3: 45 | * @param {PptxGenJS} pptx 46 | */ 47 | function genSlide03(pptx) { 48 | let slide = pptx.addSlide({ masterName: "MASTER_SLIDE", sectionTitle: "Masters" }); 49 | slide.addNotes("Master name: `MASTER_SLIDE` using pre-filled placeholders\nAPI Docs: https://gitbrent.github.io/PptxGenJS/docs/masters.html"); 50 | slide.addText("Text Placeholder", { placeholder: "header" }); 51 | slide.addText( 52 | [ 53 | { text: "Pre-filled placeholder bullets", options: { bullet: true, valign: "top" } }, 54 | { text: "Add any text, charts, whatever", options: { bullet: true, indentLevel: 1, color: "0000AB" } }, 55 | { text: "Check out the online API docs for more", options: { bullet: true, indentLevel: 2, color: "0000AB" } }, 56 | ], 57 | { placeholder: "body", valign: "top" } 58 | ); 59 | } 60 | 61 | /** 62 | * SLIDE 4: 63 | * @param {PptxGenJS} pptx 64 | */ 65 | function genSlide04(pptx) { 66 | let slide = pptx.addSlide({ masterName: "MASTER_SLIDE", sectionTitle: "Masters" }); 67 | slide.addNotes("Master name: `MASTER_SLIDE` using pre-filled placeholders\nAPI Docs: https://gitbrent.github.io/PptxGenJS/docs/masters.html"); 68 | slide.addText("Image Placeholder", { placeholder: "header" }); 69 | slide.addImage({ 70 | placeholder: "body", 71 | path: IMAGE_PATHS.starlabsBkgd.path, 72 | w: 12.0, 73 | h: 5.25, 74 | }); 75 | } 76 | 77 | /** 78 | * SLIDE 5: 79 | * @param {PptxGenJS} pptx 80 | */ 81 | function genSlide05(pptx) { 82 | let dataChartPieLocs = [ 83 | { 84 | name: "Location", 85 | labels: ["CN", "DE", "GB", "MX", "JP", "IN", "US"], 86 | values: [69, 35, 40, 85, 38, 99, 101], 87 | }, 88 | ]; 89 | let slide = pptx.addSlide({ masterName: "MASTER_SLIDE", sectionTitle: "Masters" }); 90 | slide.addNotes("Master name: `MASTER_SLIDE` using pre-filled placeholders\nAPI Docs: https://gitbrent.github.io/PptxGenJS/docs/masters.html"); 91 | slide.addText("Chart Placeholder", { placeholder: "header" }); 92 | slide.addChart(pptx.charts.PIE, dataChartPieLocs, { showLegend: true, legendPos: "l", placeholder: "body" }); 93 | } 94 | 95 | /** 96 | * SLIDE 6: 97 | * @param {PptxGenJS} pptx 98 | */ 99 | function genSlide06(pptx) { 100 | let slide = pptx.addSlide({ masterName: "THANKS_SLIDE", sectionTitle: "Masters" }); 101 | slide.addNotes("Master name: `THANKS_SLIDE`\nAPI Docs: https://gitbrent.github.io/PptxGenJS/docs/masters.html"); 102 | slide.addText("Thank You!", { placeholder: "thanksText" }); 103 | //slide.addText('github.com/gitbrent', { placeholder:'body' }); 104 | } 105 | 106 | /** 107 | * SLIDE 7: LEGACY-TEST-ONLY: To check deprecated functionality 108 | * @param {PptxGenJS} pptx 109 | */ 110 | function genSlide07(pptx) { 111 | if (pptx.masters && Object.keys(pptx.masters).length > 0) { 112 | let slide1 = pptx.addSlide(pptx.masters.TITLE_SLIDE); 113 | let slide2 = pptx.addSlide(pptx.masters.MASTER_SLIDE); 114 | let slide3 = pptx.addSlide(pptx.masters.THANKS_SLIDE); 115 | 116 | let slide4 = pptx.addSlide(pptx.masters.TITLE_SLIDE, { bkgd: "0088CC", slideNumber: { x: "50%", y: "90%", color: "0088CC" } }); 117 | let slide5 = pptx.addSlide(pptx.masters.MASTER_SLIDE, { 118 | bkgd: { path: "https://raw.githubusercontent.com/gitbrent/PptxGenJS/v2.1.0/examples/images/title_bkgd_alt.jpg" }, 119 | }); 120 | let slide6 = pptx.addSlide(pptx.masters.THANKS_SLIDE, { bkgd: "ffab33" }); 121 | //let slide7 = pptx.addSlide( pptx.masters.LEGACY_TEST_ONLY ); 122 | //let slide7 = pptx.addSlide('PLACEHOLDER_SLIDE'); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /demos/modules/demo_media.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * NAME: demo_media.mjs 3 | * AUTH: Brent Ely (https://github.com/gitbrent/) 4 | * DESC: Common test/demo slides for all library features 5 | * DEPS: Used by various demos (./demos/browser, ./demos/node, etc.) 6 | * VER.: 3.12.0 7 | * BLD.: 20230314 8 | */ 9 | 10 | /** 11 | * PowerPoint supports a variety of video formats. 12 | * The supported video file formats can depend on the version of PowerPoint being used. 13 | * 14 | * Here are some of the most common video file formats that are supported by PowerPoint: 15 | * .avi (audio video interleave) 16 | * .mp4 (MPEG-4 video) 17 | * .mov (QuickTime movie) 18 | * .mv4 (iTunes movie, Apple's version on MP4) 19 | * .wmv (windows media video) [[NOT USED IN DEMO]] 20 | * .mpg or .mpeg (DVD video) [[NOT USED IN DEMO]] 21 | * 22 | * It's worth noting that even if a video file format is supported by PowerPoint, 23 | * you may still encounter issues with playing the video if the video is encoded using a codec that is not supported by the computer you are using to present the slideshow. 24 | * It's a good idea to test your slideshow on the computer you will be using to present it to ensure that your videos will play correctly. 25 | */ 26 | 27 | /** 28 | * PowerPoint supports several audio file formats, including: 29 | * - MP3 (MPEG Audio Layer III) 30 | * - WAV (Waveform Audio Format) WAV files can contain a variety of audio codecs, including PCM, ADPCM, and others. They are widely supported by media players and software applications on both Windows and Mac operating systems. 31 | * - AIFF (Audio Interchange File Format) AIFF files can be played on both Mac and Windows computers, as well as on many other types of devices. They are often used in music production and editing applications, as well as for storing high-quality audio recordings. 32 | * - MIDI (Musical Instrument Digital Interface) MIDI files are typically small in size compared to other audio formats, and they can be edited and manipulated using specialized software. They are often used in music production and composition, as well as in live performances 33 | * - WMA (Windows Media Audio) [[not demoed]] 34 | * In addition to these formats, PowerPoint also supports embedding audio from online sources like YouTube and SoundCloud, 35 | * as well as recording audio directly within the presentation using the built-in audio recording feature. 36 | */ 37 | 38 | import { IMAGE_PATHS, BASE_TABLE_OPTS, BASE_TEXT_OPTS_L, BASE_TEXT_OPTS_R, BASE_CODE_OPTS, CODE_STYLE, TITLE_STYLE } from "./enums.mjs"; 39 | import { COVER_AUDIO, COVER_AUDIO_ROUND, COVER_VIDEO_16X9, COVER_VIDEO_MP4, COVER_YOUTUBE } from "./media.mjs"; 40 | 41 | export function genSlides_Media(pptx) { 42 | pptx.addSection({ title: "Media" }); 43 | 44 | genSlide01(pptx); 45 | genSlide02(pptx); 46 | if (typeof window !== "undefined" && document.querySelector("#chkYoutube")?.checked) { 47 | genSlide03(pptx); 48 | } 49 | //if (window && window.location.href.indexOf("localhost:8000") > -1) genSlide03(pptx); 50 | } 51 | 52 | /** 53 | * SLIDE 1: Various Video Formats 54 | * @param {PptxGenJS} pptx 55 | */ 56 | function genSlide01(pptx) { 57 | let slide = pptx.addSlide({ sectionTitle: "Media" }); 58 | slide.addTable([[{ text: "Media Examples: Video Types", options: BASE_TEXT_OPTS_L }, BASE_TEXT_OPTS_R]], BASE_TABLE_OPTS); 59 | slide.addNotes("API Docs: https://gitbrent.github.io/PptxGenJS/docs/api-media.html\r\nIt's worth noting that even if a video file format is supported by PowerPoint, you may still encounter issues with playing the video if the video is encoded using a codec that is not supported by the computer you are using to present the slideshow. It's a good idea to test your slideshow on the computer you will be using to present it to ensure that your videos will play correctly."); 60 | 61 | slide.addText([{ text: "Type: m4v" }], { ...BASE_CODE_OPTS, ...{ x: 0.5, y: 0.6, h: 0.4, w: 3.56 }, ...TITLE_STYLE }); 62 | slide.addMedia({ x: 0.5, y: 1.0, h: 2.0, w: 3.56, type: "video", path: IMAGE_PATHS.sample_m4v.path, cover: COVER_VIDEO_16X9 }); 63 | slide.addText([{ text: "`cover` image provided" }], { ...BASE_CODE_OPTS, ...{ x: 0.5, y: 3.0, h: 0.4, w: 3.56 }, ...CODE_STYLE }); 64 | 65 | slide.addText([{ text: "Type: m4v" }], { ...BASE_CODE_OPTS, ...{ x: 9.3, y: 0.6, h: 0.4, w: 3.56 }, ...TITLE_STYLE }); 66 | slide.addMedia({ x: 9.3, y: 1.0, h: 2.0, w: 3.56, type: "video", path: IMAGE_PATHS.sample_m4v.path }); 67 | slide.addText([{ text: "no `cover` image provided" }], { ...BASE_CODE_OPTS, ...{ x: 9.3, y: 3.0, h: 0.4, w: 3.56 }, ...CODE_STYLE }); 68 | 69 | // BOTTOM-ROW 70 | 71 | slide.addText([{ text: "Type: mp4" }], { ...BASE_CODE_OPTS, ...{ x: 0.5, y: 3.85, h: 0.4, w: 3.6 }, ...TITLE_STYLE }); 72 | slide.addMedia({ 73 | x: 0.5, 74 | y: 4.25, 75 | h: 2.7, 76 | w: 3.6, 77 | type: "video", 78 | path: IMAGE_PATHS.sample_mp4.path, 79 | cover: COVER_VIDEO_MP4, 80 | }); 81 | 82 | slide.addText([{ text: "Type: avi" }], { ...BASE_CODE_OPTS, ...{ x: 4.79, y: 3.85, h: 0.4, w: 3.6 }, ...TITLE_STYLE }); 83 | slide.addMedia({ 84 | x: 4.79, 85 | y: 4.25, 86 | h: 2.7, 87 | w: 3.6, 88 | type: "video", 89 | path: IMAGE_PATHS.sample_avi.path, 90 | }); 91 | 92 | slide.addText([{ text: "Type: mov" }], { ...BASE_CODE_OPTS, ...{ x: 9.08, y: 3.85, h: 0.4, w: 3.75 }, ...TITLE_STYLE }); 93 | slide.addMedia({ 94 | x: 9.08, 95 | y: 4.25, 96 | h: 2.7, 97 | w: 3.75, 98 | type: "video", 99 | path: IMAGE_PATHS.sample_mov.path, 100 | }); 101 | } 102 | 103 | /** 104 | * SLIDE 2: Various Audio Typrs 105 | * @param {PptxGenJS} pptx 106 | */ 107 | function genSlide02(pptx) { 108 | let slide = pptx.addSlide({ sectionTitle: "Media" }); 109 | slide.addNotes("API Docs: https://gitbrent.github.io/PptxGenJS/docs/api-media.html"); 110 | slide.addTable([[{ text: "Media Examples: Audio Types", options: BASE_TEXT_OPTS_L }, BASE_TEXT_OPTS_R]], BASE_TABLE_OPTS); 111 | 112 | slide.addText([{ text: "Type: mp3" }], { ...BASE_CODE_OPTS, ...{ x: 0.5, y: 0.6, h: 0.4, w: 3.5 }, ...TITLE_STYLE }); 113 | slide.addMedia({ 114 | x: 0.5, 115 | y: 1.0, 116 | h: 3.5, 117 | w: 3.5, 118 | type: "audio", 119 | path: IMAGE_PATHS.sample_mp3.path, 120 | cover: COVER_AUDIO, 121 | }); 122 | 123 | slide.addText([{ text: "Type: aiff" }], { ...BASE_CODE_OPTS, ...{ x: 4.92, y: 0.6, h: 3.9, w: 3.5 }, ...TITLE_STYLE }); 124 | slide.addMedia({ 125 | x: 4.92, 126 | y: 1.0, 127 | h: 3.5, 128 | w: 3.5, 129 | type: "audio", 130 | path: IMAGE_PATHS.sample_aif.path, 131 | cover: COVER_AUDIO_ROUND, 132 | }); 133 | 134 | slide.addText([{ text: "Type: wav" }], { ...BASE_CODE_OPTS, ...{ x: 9.33, y: 0.6, h: 0.4, w: 3.5 }, ...TITLE_STYLE }); 135 | slide.addMedia({ 136 | x: 9.33, 137 | y: 1.0, 138 | h: 3.5, 139 | w: 3.5, 140 | type: "audio", 141 | path: IMAGE_PATHS.sample_wav.path, 142 | cover: COVER_AUDIO, 143 | }); 144 | 145 | if (typeof window !== "undefined" && window.location.href.indexOf("gitbrent") > 0) { 146 | // TEST USING LOCAL FILES (OFFICE.COM) 147 | slide.addText('Audio: MP3 (path:"../media")', { x: 0.5, y: 4.6, w: 4.0, h: 0.4, color: "0088CC" }); 148 | slide.addMedia({ x: 0.5, y: 5.0, w: 4.0, h: 0.3, type: "audio", path: "media/sample.mp3" }); 149 | } 150 | } 151 | 152 | /** 153 | * SLIDE 3: YouTube 154 | * @param {PptxGenJS} pptx 155 | */ 156 | function genSlide03(pptx) { 157 | let slide = pptx.addSlide({ sectionTitle: "Media" }); 158 | slide.addNotes("API Docs: https://gitbrent.github.io/PptxGenJS/docs/api-media.html"); 159 | slide.addTable([[{ text: "Media Examples: YouTube Embed", options: BASE_TEXT_OPTS_L }, BASE_TEXT_OPTS_R]], BASE_TABLE_OPTS); 160 | 161 | slide.addText("Online: YouTube", { ...{ x: 0.5, y: 0.75, h: 5.6, w: 12.3 }, ...TITLE_STYLE }); 162 | // YouTube `link` is the embed URL (share > embed > copy URL like what you see below) 163 | slide.addMedia({ x: 2.1, y: 1.2, h: 5.1, w: 9.1, type: "online", link: "https://www.youtube.com/embed/g36-noRtKR4", cover: COVER_YOUTUBE }); 164 | slide.addText( 165 | [{ text: 'slide.addMedia({ type: "online", link: "https://www.youtube.com/embed/g36-noRtKR4" })' }], 166 | { ...BASE_CODE_OPTS, ...{ x: 0.5, y: 6.35, h: 0.4, w: 12.3 }, ...CODE_STYLE, ...{ align: 'center' } } 167 | ); 168 | 169 | // FOOTER 170 | slide.addText("Note: YouTube videos require newer versions of PowerPoint (v16+/M365). Older versions will show content warning messages.", { 171 | shape: pptx.shapes.RECTANGLE, 172 | x: 0.0, 173 | y: 7.0, 174 | w: "100%", 175 | h: 0.53, 176 | color: "BF9000", 177 | fill: { color: "FFFCCC" }, 178 | align: "center", 179 | fontSize: 12, 180 | }); 181 | } 182 | 183 | /** 184 | * SLIDE 3: Test large files are only added to export once 185 | * - filesize s/b ~24mb (the size of a single big-earth.mp4 file (17MB) plus other media files) 186 | * @param {PptxGenJS} pptx 187 | */ 188 | function genSlide_Test_LargeMedia(pptx) { 189 | let slide = pptx.addSlide({ sectionTitle: "Media" }); 190 | slide.addNotes("API Docs: https://gitbrent.github.io/PptxGenJS/docs/api-media.html"); 191 | slide.addTable([[{ text: "Media: Test: Large Files Only Added Once", options: BASE_TEXT_OPTS_L }, BASE_TEXT_OPTS_R]], BASE_TABLE_OPTS); 192 | 193 | slide.addText([{ text: IMAGE_PATHS.big_earth_mp4.path }], { 194 | x: 0.5, 195 | y: 0.5, 196 | w: 12.2, 197 | h: 1, 198 | fill: { color: "EEEEEE" }, 199 | margin: 0, 200 | color: "000000", 201 | }); 202 | 203 | slide.addMedia({ 204 | x: 0.5, 205 | y: 2.0, 206 | w: 6, 207 | h: 3.38, 208 | type: "video", 209 | path: `${typeof window === "undefined" ? ".." : ""}${IMAGE_PATHS.big_earth_mp4.path}`, // NOTE: Node will throw exception when using "/" path 210 | cover: COVER_VIDEO_16X9, 211 | }); 212 | 213 | slide.addMedia({ 214 | x: 6.83, 215 | y: 2.0, 216 | w: 6, 217 | h: 3.38, 218 | type: "video", 219 | path: `${typeof window === "undefined" ? ".." : ""}${IMAGE_PATHS.big_earth_mp4.path}`, // NOTE: Node will throw exception when using "/" path 220 | }); 221 | } 222 | -------------------------------------------------------------------------------- /demos/modules/demo_shape.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * NAME: demo_shapes.mjs 3 | * AUTH: Brent Ely (https://github.com/gitbrent/) 4 | * DESC: Common test/demo slides for all library features 5 | * DEPS: Used by various demos (./demos/browser, ./demos/node, etc.) 6 | * VER.: 3.5.0 7 | * BLD.: 20210401 8 | */ 9 | 10 | /** 11 | * CUSTOM GEOMETRY: 12 | * @see https://github.com/gitbrent/PptxGenJS/pull/872 13 | * Notes from the author [apresmoi](https://github.com/apresmoi): 14 | * I've implemented this by using a similar spec to the one used by `svg-points`. 15 | * The path or contour of the custom geometry is declared under the property points of the ShapeProps object. 16 | * With this implementation we are supporting all the custom geometry rules: moveTo, lnTo, arcTo, cubicBezTo, quadBezTo and close. 17 | * 18 | * A translation of an svg path to a custom geometry could be achieved by using the svg-points package and adding a custom translation between the arcs. 19 | * The svg arc is described by the variables x, y, rx, ry, xAxisRotation, largeArcFlag and sweepFlag. 20 | * On the other side the pptx freeform arc is described by x, y, hR, wR, stAng, swAng. 21 | * In order to add some sort of translation between svg-path and a custom geometry points array we should create a translation between those two representations of the arc. 22 | */ 23 | 24 | import { BASE_TABLE_OPTS, BASE_TEXT_OPTS_L, BASE_TEXT_OPTS_R } from "./enums.mjs"; 25 | 26 | export function genSlides_Shape(pptx) { 27 | pptx.addSection({ title: "Shapes" }); 28 | 29 | genSlide01(pptx); 30 | genSlide02(pptx); 31 | } 32 | 33 | /** 34 | * SLIDE 1: Misc Shape Types (no text) 35 | * @param {PptxGenJS} pptx 36 | */ 37 | function genSlide01(pptx) { 38 | let slide = pptx.addSlide({ sectionTitle: "Shapes" }); 39 | 40 | slide.addTable([[{ text: "Shape Examples 1: Misc Shape Types (no text)", options: BASE_TEXT_OPTS_L }, BASE_TEXT_OPTS_R]], BASE_TABLE_OPTS); 41 | slide.addNotes("API Docs: https://gitbrent.github.io/PptxGenJS/docs/api-shapes.html"); 42 | 43 | // TOP-ROW 44 | 45 | slide.addShape(pptx.shapes.RECTANGLE, { x: 0.5, y: 0.8, w: 1.5, h: 3.0, fill: { color: pptx.colors.ACCENT1 }, line: { type: "none" } }); 46 | slide.addShape(pptx.shapes.OVAL, { x: 2.2, y: 0.8, w: 3.0, h: 1.5, fill: { type: "solid", color: pptx.colors.ACCENT2 } }); 47 | slide.addShape(pptx.shapes.CUSTOM_GEOMETRY, { 48 | x: 2.5, 49 | y: 2.6, 50 | w: 2.0, 51 | h: 1.0, 52 | fill: { color: pptx.colors.ACCENT3 }, 53 | line: { color: "151515", width: 1 }, 54 | points: [ 55 | { x: 0.0, y: 0.0 }, 56 | { x: 0.5, y: 1.0 }, 57 | { x: 1.0, y: 0.8 }, 58 | { x: 1.5, y: 1.0 }, 59 | { x: 2.0, y: 0.0 }, 60 | { x: 0.0, y: 0.0, curve: { type: "quadratic", x1: 1.0, y1: 0.5 } }, 61 | { close: true }, 62 | ], 63 | }); 64 | slide.addShape(pptx.shapes.RECTANGLE, { x: 5.7, y: 0.8, w: 1.5, h: 3.0, fill: { color: pptx.colors.ACCENT4 }, rotate: 45 }); 65 | slide.addShape(pptx.shapes.OVAL, { x: 7.4, y: 1.5, w: 3.0, h: 1.5, fill: { color: pptx.colors.ACCENT6 }, rotate: 90 }); // TEST: no type 66 | slide.addShape(pptx.shapes.ROUNDED_RECTANGLE, { 67 | x: 10, 68 | y: 0.8, 69 | w: 3.0, 70 | h: 1.5, 71 | rectRadius: 1, 72 | fill: { color: pptx.colors.ACCENT5 }, 73 | line: "151515", 74 | lineSize: 1, 75 | }); // TEST: DEPRECATED: `fill`,`line`,`lineSize` 76 | slide.addShape(pptx.shapes.ARC, { x: 10.75, y: 2.45, w: 1.5, h: 1.45, fill: { color: pptx.colors.ACCENT3 }, angleRange: [45, 315] }); 77 | 78 | // BOTTOM ROW 79 | 80 | slide.addShape(pptx.shapes.LINE, { x: 4.2, y: 4.4, w: 5.0, h: 0.0, line: { color: pptx.colors.ACCENT2, width: 1, dashType: "lgDash" } }); 81 | slide.addShape(pptx.shapes.LINE, { 82 | x: 4.2, 83 | y: 4.8, 84 | w: 5.0, 85 | h: 0.0, 86 | line: { color: pptx.colors.ACCENT2, width: 2, dashType: "dashDot" }, 87 | lineHead: "arrow", 88 | }); // TEST: DEPRECATED: lineHead 89 | slide.addShape(pptx.shapes.LINE, { x: 4.2, y: 5.2, w: 5.0, h: 0.0, line: { color: pptx.colors.ACCENT2, width: 3, endArrowType: "triangle" } }); 90 | slide.addShape(pptx.shapes.LINE, { 91 | x: 4.2, 92 | y: 5.6, 93 | w: 5.0, 94 | h: 0.0, 95 | line: { color: pptx.colors.ACCENT2, width: 4, beginArrowType: "diamond", endArrowType: "oval" }, 96 | }); 97 | 98 | slide.addShape(pptx.shapes.RIGHT_TRIANGLE, { 99 | x: 0.4, 100 | y: 4.3, 101 | w: 6.0, 102 | h: 3.0, 103 | fill: { color: pptx.colors.ACCENT5 }, 104 | line: { color: pptx.colors.ACCENT1, width: 3 }, 105 | shapeName: "First Right Triangle", 106 | }); 107 | slide.addShape(pptx.shapes.RIGHT_TRIANGLE, { 108 | x: 7.0, 109 | y: 4.3, 110 | w: 6.0, 111 | h: 3.0, 112 | fill: { color: pptx.colors.ACCENT5 }, 113 | line: { color: pptx.colors.ACCENT1, width: 2 }, 114 | flipH: true, 115 | }); 116 | } 117 | 118 | /** 119 | * SLIDE 2: Misc Shape Types with Text 120 | * @param {PptxGenJS} pptx 121 | */ 122 | function genSlide02(pptx) { 123 | let slide = pptx.addSlide({ sectionTitle: "Shapes" }); 124 | 125 | slide.addTable([[{ text: "Shape Examples 2: Misc Shape Types (with text)", options: BASE_TEXT_OPTS_L }, BASE_TEXT_OPTS_R]], BASE_TABLE_OPTS); 126 | slide.addNotes("API Docs: https://gitbrent.github.io/PptxGenJS/docs/api-shapes.html"); 127 | 128 | slide.addText("RECTANGLE", { 129 | shape: pptx.shapes.RECTANGLE, 130 | x: 0.5, 131 | y: 0.8, 132 | w: 1.5, 133 | h: 3.0, 134 | fill: { color: pptx.colors.ACCENT1 }, 135 | align: "center", 136 | fontSize: 14, 137 | }); 138 | slide.addText("OVAL (transparency:50)", { 139 | shape: pptx.shapes.OVAL, 140 | x: 2.2, 141 | y: 0.8, 142 | w: 3.0, 143 | h: 1.5, 144 | fill: { type: "solid", color: pptx.colors.ACCENT2, transparency: 50 }, 145 | align: "center", 146 | fontSize: 14, 147 | }); 148 | slide.addText("CUSTOM", { 149 | shape: pptx.shapes.CUSTOM_GEOMETRY, 150 | x: 2.5, 151 | y: 2.6, 152 | w: 2.0, 153 | h: 1.0, 154 | fill: { color: pptx.colors.ACCENT3 }, 155 | line: { color: "151515", width: 1 }, 156 | points: [ 157 | { x: 0.0, y: 0.0 }, 158 | { x: 0.5, y: 1.0 }, 159 | { x: 1.0, y: 0.8 }, 160 | { x: 1.5, y: 1.0 }, 161 | { x: 2.0, y: 0.0 }, 162 | { x: 0.0, y: 0.0, curve: { type: "quadratic", x1: 1.0, y1: 0.5 } }, 163 | { close: true }, 164 | ], 165 | align: "center", 166 | fontSize: 14, 167 | }); 168 | slide.addText("RECTANGLE (rotate:45)", { 169 | shape: pptx.shapes.RECTANGLE, 170 | x: 5.7, 171 | y: 0.8, 172 | w: 1.5, 173 | h: 3.0, 174 | fill: { color: pptx.colors.ACCENT4 }, 175 | rotate: 45, 176 | align: "center", 177 | fontSize: 14, 178 | }); 179 | // TEST: DEPRECATED: `alpha` 180 | slide.addText("OVAL (rotate:90, transparency:75)", { 181 | shape: pptx.shapes.OVAL, 182 | x: 7.4, 183 | y: 1.5, 184 | w: 3.0, 185 | h: 1.5, 186 | fill: { type: "solid", color: pptx.colors.ACCENT6, alpha: 75 }, 187 | rotate: 90, 188 | align: "center", 189 | fontSize: 14, 190 | }); 191 | slide.addText("ROUNDED-RECTANGLE\ndashType:dash\nrectRadius:1", { 192 | shape: pptx.shapes.ROUNDED_RECTANGLE, 193 | x: 10, 194 | y: 0.8, 195 | w: 3.0, 196 | h: 1.5, 197 | fill: { color: pptx.colors.ACCENT5 }, 198 | align: "center", 199 | fontSize: 14, 200 | line: { color: "151515", size: 1, dashType: "dash" }, 201 | rectRadius: 1, 202 | }); 203 | slide.addText("ARC", { 204 | shape: pptx.shapes.ARC, 205 | x: 10.75, 206 | y: 2.45, 207 | w: 1.5, 208 | h: 1.45, 209 | fill: { color: pptx.colors.ACCENT3 }, 210 | angleRange: [45, 315], 211 | line: { color: "151515", width: 1 }, 212 | fontSize: 14, 213 | }); 214 | // 215 | slide.addText("LINE size=1", { 216 | shape: pptx.shapes.LINE, 217 | align: "center", 218 | x: 4.15, 219 | y: 4.4, 220 | w: 5, 221 | h: 0, 222 | line: { color: pptx.colors.ACCENT2, width: 1, dashType: "lgDash" }, 223 | }); 224 | slide.addText("LINE size=2", { 225 | shape: pptx.shapes.LINE, 226 | align: "left", 227 | x: 4.15, 228 | y: 4.8, 229 | w: 5, 230 | h: 0, 231 | line: { color: pptx.colors.ACCENT2, width: 2, dashType: "dashDot", endArrowType: "arrow" }, 232 | }); 233 | slide.addText("LINE size=3", { 234 | shape: pptx.shapes.LINE, 235 | align: "right", 236 | x: 4.15, 237 | y: 5.2, 238 | w: 5, 239 | h: 0, 240 | line: { color: pptx.colors.ACCENT2, width: 3, beginArrowType: "triangle" }, 241 | }); 242 | slide.addText("LINE size=4", { 243 | shape: pptx.shapes.LINE, 244 | x: 4.15, 245 | y: 5.6, 246 | w: 5, 247 | h: 0, 248 | line: { color: pptx.colors.ACCENT2, width: 4, beginArrowType: "diamond", endArrowType: "oval", transparency: 50 }, 249 | }); 250 | // 251 | slide.addText("RIGHT-TRIANGLE", { 252 | shape: pptx.shapes.RIGHT_TRIANGLE, 253 | align: "center", 254 | x: 0.4, 255 | y: 4.3, 256 | w: 6, 257 | h: 3, 258 | fill: { color: pptx.colors.ACCENT5 }, 259 | line: { color: "696969", width: 3 }, 260 | }); 261 | slide.addText("HYPERLINK-SHAPE", { 262 | shape: pptx.shapes.RIGHT_TRIANGLE, 263 | align: "center", 264 | x: 7.0, 265 | y: 4.3, 266 | w: 6, 267 | h: 3, 268 | fill: { color: pptx.colors.ACCENT5 }, 269 | line: { color: "696969", width: 2 }, 270 | flipH: true, 271 | hyperlink: { url: "https://github.com/gitbrent/pptxgenjs", tooltip: "Visit Homepage" }, 272 | }); 273 | } 274 | -------------------------------------------------------------------------------- /demos/modules/demos.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * NAME: demos.mjs 3 | * AUTH: Brent Ely (https://github.com/gitbrent/) 4 | * DESC: Common test/demo slides for all library features 5 | * DEPS: Used by various demos (./demos/browser, ./demos/node, etc.) 6 | * VER.: 3.12.0 7 | * BLD.: 20230319 8 | */ 9 | 10 | import { COMPRESS, CUST_NAME } from "../modules/enums.mjs"; 11 | import { createMasterSlides, testSlideBackgrounds } from "./masters.mjs"; 12 | import { genSlides_Chart } from "./demo_chart.mjs"; 13 | import { genSlides_Image } from "./demo_image.mjs"; 14 | import { genSlides_Master } from "./demo_master.mjs"; 15 | import { genSlides_Media } from "./demo_media.mjs"; 16 | import { genSlides_Shape } from "./demo_shape.mjs"; 17 | import { genSlides_Table } from "./demo_table.mjs"; 18 | import { genSlides_Text } from "./demo_text.mjs"; 19 | 20 | const DEPRECATED_TEST_MODE = false; 21 | 22 | // ================================================================================================================== 23 | 24 | export function runEveryTest(pptxgen) { 25 | return execGenSlidesFuncs(["Master", "Chart", "Image", "Media", "Shape", "Text", "Table"], pptxgen); 26 | 27 | // NOTE: Html2Pptx needs table to be visible (otherwise col widths are even and look horrible) 28 | // ....: Therefore, run it manually. // if ( typeof table2slides1 !== 'undefined' ) table2slides1(); 29 | } 30 | 31 | export function execGenSlidesFuncs(type, pptxgen) { 32 | // STEP 1: Instantiate new PptxGenJS object 33 | let pptx = typeof PptxGenJS !== "undefined" ? new PptxGenJS() : new pptxgen(); 34 | 35 | // STEP 2: Set Presentation props (as QA test only - these are not required) 36 | pptx.title = "PptxGenJS Test Suite Presentation"; 37 | pptx.subject = "PptxGenJS Test Suite Export"; 38 | pptx.author = "Brent Ely"; 39 | pptx.company = CUST_NAME; 40 | pptx.revision = "15"; 41 | // FYI: use `headFontFace` and/or `bodyFontFace` to set the default font for the entire presentation (including slide Masters) 42 | // pptx.theme = { bodyFontFace: "Arial" }; 43 | 44 | // STEP 3: Set layout 45 | pptx.layout = "LAYOUT_WIDE"; 46 | 47 | // STEP 4: Create Master Slides (from the old `pptxgen.masters.js` file - `gObjPptxMasters` items) 48 | createMasterSlides(pptx); 49 | 50 | // STEP 5: Run requested test 51 | let arrTypes = typeof type === "string" ? [type] : type; 52 | arrTypes.forEach((type) => { 53 | //if (console.time) console.time(type); 54 | if (type === "Master") { 55 | genSlides_Master(pptx); 56 | if (DEPRECATED_TEST_MODE) testSlideBackgrounds(pptx); 57 | } else if (type === "Chart") genSlides_Chart(pptx); 58 | else if (type === "Image") genSlides_Image(pptx); 59 | else if (type === "Media") genSlides_Media(pptx); 60 | else if (type === "Shape") genSlides_Shape(pptx); 61 | else if (type === "Table") genSlides_Table(pptx); 62 | else if (type === "Text") genSlides_Text(pptx); 63 | //if (console.timeEnd) console.timeEnd(type); 64 | }); 65 | 66 | // LAST: Export Presentation 67 | return pptx.writeFile({ 68 | fileName: `PptxGenJS_Demo_${type}_${new Date().toISOString().replace(/\D/gi, "")}`, 69 | compression: COMPRESS, 70 | }); 71 | } 72 | -------------------------------------------------------------------------------- /demos/modules/masters.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * NAME: masters.mjs 3 | * AUTH: Brent Ely (https://github.com/gitbrent/) 4 | * DESC: Common test/demo slides for all library features 5 | * DEPS: Used by various demos (./demos/browser, ./demos/node, etc.) 6 | * VER.: 3.6.0 7 | * BLD.: 20210421 8 | */ 9 | 10 | import { IMAGE_PATHS } from "../modules/enums.mjs"; 11 | import { STARLABS_LOGO_SM } from "../modules/media.mjs"; 12 | 13 | export function createMasterSlides(pptx) { 14 | let objBkg = { path: IMAGE_PATHS.starlabsBkgd.path }; 15 | let objImg = { path: IMAGE_PATHS.starlabsLogo.path, x: 4.6, y: 3.5, w: 4, h: 1.8 }; 16 | 17 | // TITLE_SLIDE 18 | pptx.defineSlideMaster({ 19 | title: "TITLE_SLIDE", 20 | background: objBkg, 21 | //bkgd: objBkg, // TEST: @deprecated 22 | objects: [ 23 | //{ 'line': { x:3.5, y:1.0, w:6.0, h:0.0, line:{color:'0088CC'}, lineSize:5 } }, 24 | //{ 'chart': { type:'PIE', data:[{labels:['R','G','B'], values:[10,10,5]}], options:{x:11.3, y:0.0, w:2, h:2, dataLabelFontSize:9} } }, 25 | //{ 'image': { x:11.3, y:6.4, w:1.67, h:0.75, data:STARLABS_LOGO_SM } }, 26 | { rect: { x: 0.0, y: 5.7, w: "100%", h: 0.75, fill: { color: "F1F1F1" } } }, 27 | { 28 | text: { 29 | text: "Global IT & Services :: Status Report", 30 | options: { 31 | x: 0.0, 32 | y: 5.7, 33 | w: "100%", 34 | h: 0.75, 35 | fontFace: "Arial", 36 | color: "363636", 37 | fontSize: 20, 38 | align: "center", 39 | valign: "middle", 40 | margin: 0, 41 | }, 42 | }, 43 | }, 44 | ], 45 | }); 46 | 47 | // MASTER_PLAIN 48 | pptx.defineSlideMaster({ 49 | title: "MASTER_PLAIN", 50 | background: { fill: "F1F1F1" }, // [[BACKWARDS-COMPAT/DEPRECATED CHECK:changed to `color` in v3.5.0]] 51 | margin: [0.5, 0.25, 1.0, 0.25], 52 | objects: [ 53 | { rect: { x: 0.0, y: 6.9, w: "100%", h: 0.6, fill: { color: "003b75" } } }, 54 | { image: { x: 11.45, y: 5.95, w: 1.67, h: 0.75, data: STARLABS_LOGO_SM } }, 55 | { 56 | text: { 57 | options: { x: 0, y: 6.9, w: "100%", h: 0.6, align: "center", valign: "middle", color: "FFFFFF", fontSize: 12 }, 58 | text: "S.T.A.R. Laboratories - Confidential", 59 | }, 60 | }, 61 | ], 62 | slideNumber: { x: 0.6, y: 7.1, color: "FFFFFF", fontFace: "Arial", fontSize: 10, align: "center" }, 63 | }); 64 | 65 | // MASTER_AUTO_PAGE_TABLE_PLACEHOLDER 66 | pptx.defineSlideMaster({ 67 | title: "MASTER_AUTO_PAGE_TABLE_PLACEHOLDER", 68 | background: { fill: "F1F1F1" }, 69 | margin: [0.5, 0.25, 1.0, 0.25], 70 | objects: [ 71 | { rect: { x: 0.0, y: 6.9, w: "100%", h: 0.6, fill: { color: "003b75" } } }, 72 | { image: { x: 11.45, y: 5.95, w: 1.67, h: 0.75, data: STARLABS_LOGO_SM } }, 73 | { 74 | placeholder: { 75 | options: { name: "footer", x: 0, y: 6.9, w: "100%", h: 0.6, align: "center", valign: "middle", color: "FFFFFF", fontSize: 12 }, 76 | text: "(footer placeholder)", 77 | }, 78 | }, 79 | ], 80 | slideNumber: { x: 0.6, y: 7.1, color: "FFFFFF", fontFace: "Arial", fontSize: 10, align: "center" }, 81 | }); 82 | 83 | // MASTER_SLIDE (MASTER_PLACEHOLDER) 84 | pptx.defineSlideMaster({ 85 | title: "MASTER_SLIDE", 86 | background: { color: "E1E1E1", transparency: 50 }, 87 | margin: [0.5, 0.25, 1.0, 0.25], 88 | slideNumber: { x: 0.6, y: 7.1, color: "FFFFFF", fontFace: "Arial", fontSize: 10, bold: true }, 89 | objects: [ 90 | //{ 'image': { x:11.45, y:5.95, w:1.67, h:0.75, data:STARLABS_LOGO_SM } }, 91 | { 92 | rect: { x: 0.0, y: 6.9, w: "100%", h: 0.6, fill: { color: "003b75" } }, 93 | }, 94 | { 95 | text: { 96 | options: { x: 0, y: 6.9, w: "100%", h: 0.6, align: "center", valign: "middle", color: "FFFFFF", fontSize: 12 }, 97 | text: "S.T.A.R. Laboratories - Confidential", 98 | }, 99 | }, 100 | { 101 | placeholder: { 102 | options: { 103 | name: "header", 104 | type: "title", 105 | x: 0.6, 106 | y: 0.2, 107 | w: 12, 108 | h: 1.0, 109 | margin: 0, 110 | align: "center", 111 | valign: "middle", 112 | color: "404040", 113 | //fontSize: 18, 114 | }, 115 | text: "", // USAGE: Leave blank to have powerpoint substitute default placeholder text (ex: "Click to add title") 116 | }, 117 | }, 118 | { 119 | placeholder: { 120 | options: { name: "body", type: "body", x: 0.6, y: 1.5, w: 12, h: 5.25, fontSize: 28 }, 121 | text: "(supports custom placeholder text!)", 122 | }, 123 | }, 124 | ], 125 | }); 126 | 127 | // THANKS_SLIDE (THANKS_PLACEHOLDER) 128 | pptx.defineSlideMaster({ 129 | title: "THANKS_SLIDE", 130 | background: { color: "36ABFF" }, // CORRECT WAY TO SET BACKGROUND COLOR 131 | //bkgd: "36ABFF", // [[BACKWARDS-COMPAT/DEPRECATED/UAT (`bkgd` will be removed in v4.x)]] **DO NOT USE THIS IN YOUR CODE** 132 | objects: [ 133 | { rect: { x: 0.0, y: 3.4, w: "100%", h: 2.0, fill: { color: "FFFFFF" } } }, 134 | { image: objImg }, 135 | { 136 | placeholder: { 137 | options: { 138 | name: "thanksText", 139 | type: "title", 140 | x: 0.0, 141 | y: 0.9, 142 | w: "100%", 143 | h: 1, 144 | fontFace: "Arial", 145 | color: "FFFFFF", 146 | fontSize: 60, 147 | align: "center", 148 | }, 149 | }, 150 | }, 151 | { 152 | placeholder: { 153 | options: { 154 | name: "body", 155 | type: "body", 156 | x: 0.0, 157 | y: 6.45, 158 | w: "100%", 159 | h: 1, 160 | fontFace: "Courier", 161 | color: "FFFFFF", 162 | fontSize: 32, 163 | align: "center", 164 | }, 165 | text: "(add homepage URL)", 166 | }, 167 | }, 168 | ], 169 | }); 170 | 171 | // MARGIN_SLIDE (used for demo/test) 172 | const MARGINS = [0.5, 0.5, 0.5, 0.5]; 173 | const TEXT_PROPS = { 174 | shape: pptx.shapes.RECTANGLE, 175 | fill: { color: "FFFCCC" }, 176 | color: "9f9f9f", 177 | align: "center", 178 | fontFace: "Courier New", 179 | fontSize: 10, 180 | }; 181 | pptx.defineSlideMaster({ 182 | title: "MARGIN_SLIDE", 183 | background: { color: "FFFFFF" }, 184 | margin: MARGINS, 185 | objects: [ 186 | { text: { text: "(margin-top)", options: { ...TEXT_PROPS, ...{ x: 0, y: 0, w: "100%", h: MARGINS[0] } } } }, 187 | { text: { text: "(margin-btm)", options: { ...TEXT_PROPS, ...{ x: 0, y: 7.5 - MARGINS[2], w: "100%", h: MARGINS[2], flipV: true } } } }, 188 | ], 189 | }); 190 | 191 | // MARGIN_SLIDE_STARTY15 (used for demo/test) 192 | pptx.defineSlideMaster({ 193 | title: "MARGIN_SLIDE_STARTY15", 194 | background: { color: "FFFFFF" }, 195 | margin: MARGINS, 196 | objects: [ 197 | { text: { text: "(4.0 inches H)", options: { ...TEXT_PROPS, ...{ x: 0, y: 0, w: 1, h: 4.0 } } } }, 198 | { text: { text: "(1.5 inches H)", options: { ...TEXT_PROPS, ...{ x: 1, y: 0, w: 1, h: 1.5 } } } }, 199 | { text: { text: "(margin-top)", options: { ...TEXT_PROPS, ...{ x: 0, y: 0, w: "100%", h: MARGINS[0] } } } }, 200 | { text: { text: "(margin-btm)", options: { ...TEXT_PROPS, ...{ x: 0, y: 7.5 - MARGINS[2], w: "100%", h: MARGINS[2], flipV: true } } } }, 201 | ], 202 | }); 203 | 204 | // PLACEHOLDER_SLIDE 205 | /* FUTURE: ISSUE#599 206 | pptx.defineSlideMaster({ 207 | title : 'PLACEHOLDER_SLIDE', 208 | margin: [0.5, 0.25, 1.00, 0.25], 209 | bkgd : 'FFFFFF', 210 | objects: [ 211 | { 'placeholder': 212 | { 213 | options: {type:'body'}, 214 | image: {x:11.45, y:5.95, w:1.67, h:0.75, data:STARLABS_LOGO_SM} 215 | } 216 | }, 217 | { 'placeholder': 218 | { 219 | options: { name:'body', type:'body', x:0.6, y:1.5, w:12, h:5.25 }, 220 | text: '(supports custom placeholder text!)' 221 | } 222 | } 223 | ], 224 | slideNumber: { x:1.0, y:7.0, color:'FFFFFF' } 225 | });*/ 226 | 227 | // MISC: Only used for Issues, ad-hoc slides etc (for screencaps) 228 | pptx.defineSlideMaster({ 229 | title: "DEMO_SLIDE", 230 | objects: [ 231 | { rect: { x: 0.0, y: 7.1, w: "100%", h: 0.4, fill: { color: "F1F1F1" } } }, 232 | { 233 | text: { 234 | text: "PptxGenJS - JavaScript PowerPoint Library - (github.com/gitbrent/PptxGenJS)", 235 | options: { x: 0.0, y: 7.1, w: "100%", h: 0.4, color: "6c6c6c", fontSize: 10, align: "center" }, 236 | }, 237 | }, 238 | ], 239 | }); 240 | } 241 | 242 | /** 243 | * Test Slide BACKGROUNDS 244 | */ 245 | export function testSlideBackgrounds(pptx) { 246 | let slide1 = pptx.addSlide(); 247 | slide1.bkgd = "909090"; 248 | slide1.addText([{ text: "TEST `bkgd:string`" }], { x: 1, y: 1, w: "80%", h: 3, align: "center", fill: { color: "a1a1a1" } }); 249 | 250 | let slide2 = pptx.addSlide(); 251 | slide2.background = { fill: "909090" }; 252 | slide2.addText([{ text: "TEST `background.fill`" }], { x: 1, y: 1, w: "80%", h: 3, align: "center", fill: { color: "a1a1a1" } }); 253 | 254 | let slide3 = pptx.addSlide(); 255 | slide3.background = { color: "909090", transparency: 50 }; 256 | slide3.addText([{ text: "TEST `background`[correct]" }], { x: 1, y: 1, w: "80%", h: 3, align: "center", fill: { color: "a1a1a1" } }); 257 | } 258 | -------------------------------------------------------------------------------- /demos/node/README.md: -------------------------------------------------------------------------------- 1 | # Node.js Demo 2 | 3 | ## Regular Node Demo 4 | 5 | ### Regular Usage 6 | 7 | Generate a simple presentation. 8 | 9 | ```bash 10 | node demo.js 11 | ``` 12 | 13 | Generate a presentation with all demo objects (like the browser demo). 14 | 15 | ```bash 16 | node demo.js All 17 | ``` 18 | 19 | Generate a presentation with selected demo objects (e.g.: 'Table', 'Text', etc.). 20 | (See `../common/demos.js` for all tests) 21 | 22 | ```bash 23 | node demo.js Text 24 | ``` 25 | 26 | ## Stream Demo 27 | 28 | The `demo_stream.js` file requires the `express` package to demonstrate streaming. 29 | 30 | ### Stream Usage 31 | 32 | ```bash 33 | node demo_stream.js 34 | ``` 35 | 36 | Then visit `http://localhost:3000/` on a local web browser to download the streamed file. 37 | -------------------------------------------------------------------------------- /demos/node/assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/node/assets/image.png -------------------------------------------------------------------------------- /demos/node/assets/video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/node/assets/video.mp4 -------------------------------------------------------------------------------- /demos/node/demo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NAME: demo.js 3 | * AUTH: Brent Ely (https://github.com/gitbrent/) 4 | * DATE: 20210502 5 | * DESC: PptxGenJS feature demos for Node.js 6 | * REQS: npm 4.x + `npm install pptxgenjs` 7 | * 8 | * USAGE: `node demo.js` (runs local tests with callbacks etc) 9 | * USAGE: `node demo.js All` (runs all pre-defined tests in `../common/demos.js`) 10 | * USAGE: `node demo.js Text` (runs pre-defined single test in `../common/demos.js`) 11 | */ 12 | 13 | import { execGenSlidesFuncs, runEveryTest } from "../modules/demos.mjs"; 14 | import pptxgen from "pptxgenjs"; 15 | 16 | // ============================================================================ 17 | 18 | const exportName = "PptxGenJS_Demo_Node"; 19 | let pptx = new pptxgen(); 20 | 21 | console.log(`\n\n--------------------==~==~==~==[ STARTING DEMO... ]==~==~==~==--------------------\n`); 22 | console.log(`* pptxgenjs ver: ${pptx.version}`); 23 | console.log(`* save location: ${process.cwd()}`); 24 | 25 | if (process.argv.length > 2) { 26 | // A: Run predefined test from `../common/demos.js` //-OR-// Local Tests (callbacks, etc.) 27 | Promise.resolve() 28 | .then(() => { 29 | if (process.argv[2].toLowerCase() === "all") return runEveryTest(pptxgen); 30 | return execGenSlidesFuncs(process.argv[2], pptxgen); 31 | }) 32 | .catch((err) => { 33 | throw new Error(err); 34 | }) 35 | .then((fileName) => { 36 | console.log(`EX1 exported: ${fileName}`); 37 | }) 38 | .catch((err) => { 39 | console.log(`ERROR: ${err}`); 40 | }); 41 | } else { 42 | // B: Omit an arg to run only these below 43 | let slide = pptx.addSlide(); 44 | //slide.addText("New Node Presentation", { x: 1.5, y: 1.5, w: 6, h: 2, margin: 0.1, fill: "FFFCCC" }); 45 | //slide.addShape(pptx.shapes.OVAL_CALLOUT, { x: 6, y: 2, w: 3, h: 2, fill: "00FF00", line: "000000", lineSize: 1 }); // Test shapes availablity 46 | // Title 47 | slide.addText("Node.js Diagnostic Slide", { 48 | x: 0.5, y: 0.3, w: 9, h: 0.75, fontSize: 24, bold: true, color: "107C10", align: "center" 49 | }); 50 | // Version display 51 | slide.addText(`App Version: ${pptx.version}`, { 52 | x: 0.5, y: 1.2, w: 9, h: 0.5, fontSize: 14, color: "333333", align: "center" 53 | }); 54 | // Main diagnostic area (rounded rectangle) 55 | slide.addText("System diagnostics successful.\nEnvironment checks passed.", { 56 | x: 1, y: 2, w: 6.5, h: 2.5, fill: "E0FFE0", fontSize: 16, align: "left", valign: "middle", shape: pptx.shapes.ROUNDED_RECTANGLE, line: "00AA00" 57 | }); 58 | // Fun node-like shape (hexagon!) 59 | slide.addShape(pptx.shapes.HEXAGON, { 60 | x: 7.2, y: 2.15, w: 2.5, h: 2.0, fill: "00A300", line: "006400", lineSize: 1 61 | }); 62 | slide.addText("Node\nReady", { 63 | x: 7.2, y: 2.0, w: 2.5, h: 2.3, fontSize: 28, color: "FFFFFF", align: "center", valign: "middle", fontFace: "Courier New" 64 | }); 65 | // Image Test: URL 66 | slide.addImage({ 67 | path: "https://raw.githubusercontent.com/gitbrent/PptxGenJS/master/demos/common/images/cc_logo.jpg", 68 | x: 0.25, y: 0.25, w: 2.0, h: 1.5 69 | }); 70 | // Image Test: Local 71 | slide.addImage({ 72 | path: "../common/images/cc_logo.jpg", 73 | x: 7.75, y: 0.25, w: 2.0, h: 1.5 74 | }); 75 | 76 | // EXAMPLE 1: Saves output file to the local directory where this process is running 77 | pptx.writeFile({ fileName: exportName }) 78 | .catch((err) => { 79 | throw new Error(err); 80 | }) 81 | .then((fileName) => { 82 | console.log(`EX1 exported: ${fileName}`); 83 | }) 84 | .catch((err) => { 85 | console.log(`ERROR: ${err}`); 86 | }); 87 | 88 | // EXAMPLE 2: Save in various formats - JSZip offers: ['arraybuffer', 'base64', 'binarystring', 'blob', 'nodebuffer', 'uint8array'] 89 | pptx.write("base64") 90 | .catch((err) => { 91 | throw new Error(err); 92 | }) 93 | .then((data) => { 94 | console.log(`BASE64 TEST: First 100 chars of 'data':\n`); 95 | console.log(data.substring(0, 99)); 96 | }) 97 | .catch((err) => { 98 | console.log(`ERROR: ${err}`); 99 | }); 100 | 101 | // **NOTE** If you continue to use the `pptx` variable, new Slides will be added to the existing set 102 | } 103 | 104 | // ============================================================================ 105 | 106 | console.log(`\n--------------------==~==~==~==[ ...DEMO COMPLETE ]==~==~==~==--------------------\n\n`); 107 | -------------------------------------------------------------------------------- /demos/node/demo_stream.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NAME: demo_stream.js 3 | * AUTH: Brent Ely (https://github.com/gitbrent/) 4 | * DATE: 20210410 5 | * DESC: PptxGenJS feature demos for Node.js 6 | * REQS: npm 4.x + `npm install pptxgenjs` 7 | * 8 | * USAGE: `node demo_stream.js` 9 | */ 10 | 11 | // ============================================================================ 12 | import pptxgen from "pptxgenjs"; 13 | import express from "express"; // @note Only required for streaming test (not a req for PptxGenJS) 14 | const app = express(); // @note Only required for streaming test (not a req for PptxGenJS) 15 | //let exportName = `PptxGenJS_Node_Demo_Stream_${new Date().toISOString()}.pptx`; 16 | let exportName = `PptxGenJS_Node_Demo_Stream.pptx`; 17 | 18 | // EXAMPLE: Export presentation to stream 19 | let pptx = new pptxgen(); 20 | let slide = pptx.addSlide(); 21 | slide.addText( 22 | [ 23 | { text: "PptxGenJS", options: { fontSize: 48, color: pptx.colors.ACCENT1, breakLine: true } }, 24 | { text: "Node Stream Demo", options: { fontSize: 24, color: pptx.colors.ACCENT6, breakLine: true } }, 25 | { text: "(pretty cool huh?)", options: { fontSize: 24, color: pptx.colors.ACCENT3 } }, 26 | ], 27 | { x: 1, y: 1, w: "80%", h: 3, align: "center", fill: pptx.colors.BACKGROUND2 } 28 | ); 29 | 30 | // Export presenation: Save to stream (instead of `write` or `writeFile`) 31 | pptx.stream() 32 | .catch((err) => { 33 | throw err; 34 | }) 35 | .then((data) => { 36 | app.get("/", (_req, res) => { 37 | res.writeHead(200, { "Content-disposition": `attachment;filename=${exportName}`, "Content-Length": data.length }); 38 | res.end(new Buffer.from(data, "binary")); 39 | }); 40 | 41 | app.listen(3000, () => { 42 | console.log(`\n\n--------------------==~==~==~==[ STARTING STREAM DEMO... ]==~==~==~==--------------------\n`); 43 | console.log(`* pptxgenjs ver: ${pptx.version}`); 44 | console.log(`* save location: ${process.cwd()}`); 45 | console.log(`\n`); 46 | console.log("PptxGenJS Node Stream Demo app listening on port 3000!"); 47 | console.log("Visit: http://localhost:3000/"); 48 | console.log(`\n`); 49 | console.log("(press Ctrl-C to quit demo)"); 50 | }); 51 | app.removeListener(() => { 52 | console.log("DONE!!!"); 53 | }); 54 | }) 55 | .catch((err) => { 56 | console.log("ERROR: " + err); 57 | console.log(`\n--------------------==~==~==~==[ ... STREAM DEMO COMPLETE ]==~==~==~==--------------------\n\n`); 58 | }); 59 | -------------------------------------------------------------------------------- /demos/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pptxgenjs-demos-node", 3 | "version": "4.0.0", 4 | "author": { 5 | "name": "Brent Ely", 6 | "url": "https://github.com/gitbrent/" 7 | }, 8 | "type": "module", 9 | "description": "Create PowerPoint Presentations with JavaScript", 10 | "homepage": "https://gitbrent.github.io/PptxGenJS/", 11 | "scripts": { 12 | "demo": "node demo.js", 13 | "demo-all": "node demo.js All", 14 | "demo-text": "node demo.js Text", 15 | "demo-stream": "node demo_stream.js" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/gitbrent/PptxGenJS.git" 20 | }, 21 | "license": "MIT", 22 | "dependencies": { 23 | "pptxgenjs": "^4.0.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /demos/vite-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /demos/vite-demo/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: 13 | 14 | ```js 15 | export default tseslint.config({ 16 | extends: [ 17 | // Remove ...tseslint.configs.recommended and replace with this 18 | ...tseslint.configs.recommendedTypeChecked, 19 | // Alternatively, use this for stricter rules 20 | ...tseslint.configs.strictTypeChecked, 21 | // Optionally, add this for stylistic rules 22 | ...tseslint.configs.stylisticTypeChecked, 23 | ], 24 | languageOptions: { 25 | // other options... 26 | parserOptions: { 27 | project: ['./tsconfig.node.json', './tsconfig.app.json'], 28 | tsconfigRootDir: import.meta.dirname, 29 | }, 30 | }, 31 | }) 32 | ``` 33 | 34 | You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: 35 | 36 | ```js 37 | // eslint.config.js 38 | import reactX from 'eslint-plugin-react-x' 39 | import reactDom from 'eslint-plugin-react-dom' 40 | 41 | export default tseslint.config({ 42 | plugins: { 43 | // Add the react-x and react-dom plugins 44 | 'react-x': reactX, 45 | 'react-dom': reactDom, 46 | }, 47 | rules: { 48 | // other rules... 49 | // Enable its recommended typescript rules 50 | ...reactX.configs['recommended-typescript'].rules, 51 | ...reactDom.configs.recommended.rules, 52 | }, 53 | }) 54 | ``` 55 | -------------------------------------------------------------------------------- /demos/vite-demo/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /demos/vite-demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | PptxGenJS Vite React Demo 7 | 8 | 10 | 12 | 13 | 14 |
15 | 16 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /demos/vite-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-demo", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --force --host", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@popperjs/core": "^2.11.8", 14 | "bootstrap": "^5.3.5", 15 | "pptxgenjs": "^4.0.0", 16 | "react": "^19.0.0", 17 | "react-dom": "^19.0.0" 18 | }, 19 | "devDependencies": { 20 | "@eslint/js": "^9.22.0", 21 | "@types/react": "^19.0.10", 22 | "@types/react-dom": "^19.0.4", 23 | "@vitejs/plugin-react": "^4.3.4", 24 | "eslint": "^9.22.0", 25 | "eslint-plugin-react-hooks": "^5.2.0", 26 | "eslint-plugin-react-refresh": "^0.4.19", 27 | "globals": "^16.0.0", 28 | "sass-embedded": "^1.87.0", 29 | "typescript": "~5.7.2", 30 | "typescript-eslint": "^8.26.1", 31 | "vite": "^6.3.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /demos/vite-demo/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demos/vite-demo/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /demos/vite-demo/src/App.tsx: -------------------------------------------------------------------------------- 1 | // NOTE: previous {create-react-app} is webpack-based and will use package.json `module: "dist/pptxgen.es.js"` value 2 | // NOTE: this Vite+React demo is using `main: "dist/pptxgen.cjs.js"` value, so we hard-code below to TEST 3 | /* // @ts-expect-error (manually import the es module for TESTING!) */ 4 | //import pptxgen from "pptxgenjs/dist/pptxgen.cjs.js"; 5 | import pptxgen from "pptxgenjs"; 6 | import { testMainMethods, testTableMethod } from "./tstest/Test"; 7 | import { demoCode } from "./enums"; 8 | import logo from "./assets/logo.png"; 9 | import './scss/styles.scss'; 10 | 11 | function App() { 12 | function runDemo() { 13 | const pptx = new pptxgen(); 14 | const slide = pptx.addSlide(); 15 | 16 | const dataChartRadar = [ 17 | { 18 | name: "Region 1", 19 | labels: ["May", "June", "July", "August", "September"], 20 | values: [26, 53, 100, 75, 41], 21 | }, 22 | ]; 23 | //slide.addChart(pptx.ChartType.radar, dataChartRadar, { x: 0.36, y: 2.25, w: 4.0, h: 4.0, radarStyle: "standard" }); 24 | 25 | //slide.addShape(pptx.ShapeType.rect, { x: 4.36, y: 2.36, w: 5, h: 2.5, fill: pptx.SchemeColor.background2 }); 26 | 27 | //slide.addText("React Demo!", { x: 1, y: 1, w: "80%", h: 1, fontSize: 36, fill: "eeeeee", align: "center" }); 28 | slide.addText("React Demo!", { 29 | x: 1, 30 | y: 0.5, 31 | w: "80%", 32 | h: 1, 33 | fontSize: 36, 34 | align: "center", 35 | fill: { color: "D3E3F3" }, 36 | color: "008899", 37 | }); 38 | 39 | slide.addChart(pptx.ChartType.radar, dataChartRadar, { x: 1, y: 1.9, w: 8, h: 3 }); 40 | 41 | slide.addText(`PpptxGenJS version: ${pptx.version}`, { 42 | x: 0, 43 | y: 5.3, 44 | w: "100%", 45 | h: 0.33, 46 | fontSize: 10, 47 | align: "center", 48 | fill: { color: "E1E1E1" }, //{ color: pptx.SchemeColor.background2 }, 49 | color: "A1A1A1", // pptx.SchemeColor.accent3, 50 | }); 51 | 52 | pptx.writeFile({ fileName: "pptxgenjs-demo-react.pptx" }); 53 | } 54 | 55 | const htmlNav = () => { 56 | return 97 | } 98 | 99 | const htmlMain = () => { 100 | return
101 |
102 |
103 |

Module Demo

104 |
105 | Sample React+TypeScript+Vite application demonstrating the PptxGenJS library as a module. 106 |
107 |
108 |
109 |
Demo Code (.tsx)
110 |
111 | 						{demoCode}
112 | 					
113 |
114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 |
col 1col 2col 3
cell 1cell 2cell 3
129 | 130 |
131 |
132 |
133 | 137 |
138 |
139 | 143 |
144 |
145 | 149 |
150 |
151 |
152 | 153 | 154 | } 155 | 156 | return ( 157 |
158 | {htmlNav()} 159 | {htmlMain()} 160 |
161 | ); 162 | } 163 | 164 | export default App 165 | -------------------------------------------------------------------------------- /demos/vite-demo/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitbrent/PptxGenJS/8692262f173fabe47b696f9aa5b35175f6c8ee0a/demos/vite-demo/src/assets/logo.png -------------------------------------------------------------------------------- /demos/vite-demo/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demos/vite-demo/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /demos/vite-demo/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import App from './App.tsx' 4 | //import './index.css' 5 | 6 | createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /demos/vite-demo/src/scss/styles.scss: -------------------------------------------------------------------------------- 1 | // Import all of Bootstrap's CSS 2 | @import "bootstrap/scss/bootstrap"; 3 | -------------------------------------------------------------------------------- /demos/vite-demo/src/tstest/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "array-type": false, 4 | "arrow-return-shorthand": [true, "multiline"], 5 | "no-duplicate-switch-case": true, 6 | "no-duplicate-variable": false, 7 | "no-empty": true, 8 | "no-eval": true, 9 | "no-string-literal": false, 10 | "no-string-throw": true, 11 | "no-use-before-declare": true, 12 | "no-var-keyword": false, 13 | "prefer-template": false, 14 | "switch-default": true, 15 | "triple-equals": false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /demos/vite-demo/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /demos/vite-demo/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /demos/vite-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /demos/vite-demo/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /demos/vite-demo/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | server: { 8 | port: 8080 9 | }, 10 | css: { 11 | preprocessorOptions: { 12 | scss: { 13 | silenceDeprecations: ['mixed-decls', 'color-functions', 'global-builtin', 'import'], 14 | quietDeps: true, // Add this line to suppress warnings (above needed for bootstrap SCSS Dart messages) 15 | //api: 'modern', 16 | }, 17 | } 18 | }, 19 | base: '/PptxGenJS/' 20 | }) 21 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslint from '@eslint/js'; 2 | import tseslint from 'typescript-eslint'; 3 | import stylistic from '@stylistic/eslint-plugin' 4 | 5 | export default tseslint.config({ 6 | plugins: { 7 | '@stylistic': stylistic 8 | }, 9 | files: ['**/*.ts'], 10 | extends: [ 11 | eslint.configs.recommended, 12 | tseslint.configs.recommended 13 | ], 14 | rules: { 15 | "@stylistic/comma-dangle": ["error", "only-multiline"], 16 | "@stylistic/indent": ["error", "tab", { "SwitchCase": 1, "ImportDeclaration": 1 }], 17 | "@stylistic/no-tabs": ["error", { allowIndentationTabs: true }], 18 | "@stylistic/quotes": ["error", "single"], 19 | "@stylistic/semi": ["error", "never"], 20 | "no-lone-blocks": 0, 21 | }, 22 | }); 23 | 24 | /* 25 | export defineConfig([ 26 | { 27 | files: ["src/*.ts"], 28 | languageOptions: { 29 | parser: tseslint.parser, 30 | parserOptions: { 31 | project: ['./tsconfig.json'], // enables “typed” rules 32 | }, 33 | }, 34 | ...tseslint.configs.recommendedTypeChecked[0], // base + type‑aware rules 35 | rules: { 36 | "no-unused-vars": "warn", 37 | "no-undef": "warn", 38 | "@typescript-eslint/indent": ["error", "tab"], 39 | "@typescript-eslint/prefer-nullish-coalescing": 0, // "warn", too many items! 40 | "@typescript-eslint/restrict-plus-operands": "warn", // TODO: "error" 41 | "@typescript-eslint/restrict-template-expressions": "warn", // TODO: "error" 42 | "@typescript-eslint/strict-boolean-expressions": "off", 43 | "comma-dangle": ["error", "only-multiline"], 44 | "no-lone-blocks": 0, 45 | "no-tabs": ["error", { allowIndentationTabs: true }], 46 | indent: ["error", "tab", { "SwitchCase": 1, "ImportDeclaration": 1 }], 47 | quotes: ["error", "single"], 48 | semi: ["error", "never"], 49 | }, 50 | }, 51 | ]); 52 | */ 53 | 54 | /* 55 | module.exports = { 56 | env: { 57 | browser: true, 58 | es2021: true, 59 | node: true, 60 | }, 61 | extends: [ 62 | "plugin:react/recommended", 63 | "standard-with-typescript", 64 | "plugin:@typescript-eslint/recommended", 65 | ], 66 | overrides: [], 67 | parserOptions: { 68 | ecmaVersion: "latest", 69 | sourceType: "module", 70 | project: ["./tsconfig.json"], 71 | }, 72 | plugins: ["react", "@typescript-eslint"], 73 | ignorePatterns: [".eslintrc.js", "*.mjs", "demos/*", "index.d.ts", "gulpfile.js"], 74 | rules: { 75 | "@typescript-eslint/indent": ["error", "tab"], 76 | "@typescript-eslint/prefer-nullish-coalescing": 0, // "warn", too many items! 77 | "@typescript-eslint/restrict-plus-operands": "warn", // TODO: "error" 78 | "@typescript-eslint/restrict-template-expressions": "warn", // TODO: "error" 79 | "@typescript-eslint/strict-boolean-expressions": 0, 80 | "comma-dangle": ["error", "only-multiline"], 81 | "no-lone-blocks": 0, 82 | "no-tabs": ["error", { allowIndentationTabs: true }], 83 | indent: ["error", "tab", { "SwitchCase": 1, "ImportDeclaration": 1 }], 84 | quotes: ["error", "single"], 85 | semi: ["error", "never"], 86 | }, 87 | }; 88 | */ 89 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const pkg = require('./package.json') 2 | const rollup = require('rollup') 3 | const { resolve } = require('@rollup/plugin-node-resolve') 4 | const { commonjs } = require('@rollup/plugin-commonjs') 5 | const typescript = require('rollup-plugin-typescript2') 6 | const { watch, series } = require('gulp') 7 | const gulp = require('gulp'), 8 | concat = require('gulp-concat'), 9 | ignore = require('gulp-ignore'), 10 | insert = require('gulp-insert'), 11 | source = require('gulp-sourcemaps'), 12 | uglify = require('gulp-uglify') 13 | 14 | gulp.task('build', () => { 15 | return rollup 16 | .rollup({ 17 | input: './src/pptxgen.ts', 18 | external: [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})], 19 | plugins: [typescript(), resolve, commonjs] 20 | }) 21 | .then(bundle => { 22 | bundle.write({ 23 | file: './src/bld/pptxgen.gulp.js', 24 | format: 'iife', 25 | name: 'PptxGenJS', 26 | globals: { 27 | jszip: 'JSZip' 28 | }, 29 | sourcemap: true 30 | }) 31 | return bundle 32 | }) 33 | .then(bundle => { 34 | bundle.write({ 35 | file: './src/bld/pptxgen.cjs.js', 36 | format: 'cjs', 37 | exports: 'default' 38 | }) 39 | return bundle 40 | }) 41 | .then(bundle => { 42 | return bundle.write({ 43 | file: './src/bld/pptxgen.es.js', 44 | format: 'es' 45 | }) 46 | }) 47 | }) 48 | 49 | gulp.task('min', () => { 50 | return gulp 51 | .src(['./src/bld/pptxgen.gulp.js']) 52 | .pipe(concat('pptxgen.min.js')) 53 | .pipe(uglify()) 54 | .pipe(insert.prepend('/* PptxGenJS ' + pkg.version + ' @ ' + new Date().toISOString() + ' */\n')) 55 | .pipe(source.init()) 56 | .pipe(ignore.exclude(['**/*.map'])) 57 | .pipe(source.write('./')) 58 | .pipe(gulp.dest('./dist/')) 59 | }) 60 | 61 | gulp.task('bundle', () => { 62 | return gulp 63 | .src(['./libs/*', './src/bld/pptxgen.gulp.js']) 64 | .pipe(concat('pptxgen.bundle.js')) 65 | .pipe(uglify()) 66 | .pipe(insert.prepend('/* PptxGenJS ' + pkg.version + ' @ ' + new Date().toISOString() + ' */\n')) 67 | .pipe(source.init()) 68 | .pipe(ignore.exclude(['**/*.map'])) 69 | .pipe(source.write('./')) 70 | .pipe(gulp.dest('./dist/')) 71 | .pipe(gulp.dest('./demos/browser/js/')) 72 | }) 73 | 74 | gulp.task('cjs', () => { 75 | return gulp 76 | .src(['./src/bld/pptxgen.cjs.js']) 77 | .pipe(insert.prepend('/* PptxGenJS ' + pkg.version + ' @ ' + new Date().toISOString() + ' */\n')) 78 | .pipe(gulp.dest('./dist/')) 79 | }) 80 | 81 | gulp.task('es', () => { 82 | return gulp 83 | .src(['./src/bld/pptxgen.es.js']) 84 | .pipe(insert.prepend('/* PptxGenJS ' + pkg.version + ' @ ' + new Date().toISOString() + ' */\n')) 85 | .pipe(gulp.dest('./dist/')) 86 | }) 87 | 88 | gulp.task('reactTestCode', () => { 89 | return gulp 90 | .src(['./dist/pptxgen.es.js']) 91 | .pipe(gulp.dest('./demos/vite-demo/node_modules/pptxgenjs/dist')) 92 | }) 93 | 94 | gulp.task('reactTestDefs', () => { 95 | return gulp 96 | .src(['./types/index.d.ts']) 97 | .pipe(gulp.dest('./demos/vite-demo/node_modules/pptxgenjs/types')) 98 | }) 99 | 100 | gulp.task('nodeTest', () => { 101 | return gulp 102 | .src(['./dist/pptxgen.cjs.js']) 103 | .pipe(gulp.dest('./demos/node/node_modules/pptxgenjs/dist')) 104 | }) 105 | 106 | // Build/Deploy (ad-hoc, no watch) 107 | gulp.task('ship', gulp.series('build', 'min', 'cjs', 'es', 'bundle', 'reactTestCode', 'reactTestDefs', 'nodeTest'), () => { 108 | console.log('... ./dist/*.js files created!') 109 | }) 110 | // Build/Deploy 111 | gulp.task('default', gulp.series('build', 'min', 'cjs', 'es', 'bundle', 'reactTestCode', 'reactTestDefs', 'nodeTest'), () => { 112 | console.log('... ./dist/*.js files created!') 113 | }) 114 | 115 | // Watch 116 | exports.default = function() { 117 | watch('src/*.ts', series('build', 'min', 'cjs', 'es', 'bundle', 'nodeTest')) 118 | } 119 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pptxgenjs", 3 | "version": "4.0.0", 4 | "author": { 5 | "name": "Brent Ely", 6 | "url": "https://github.com/gitbrent/" 7 | }, 8 | "description": "Create JavaScript PowerPoint Presentations", 9 | "homepage": "https://gitbrent.github.io/PptxGenJS/", 10 | "license": "MIT", 11 | "exports": { 12 | "types": "./types/index.d.ts", 13 | "import": "./dist/pptxgen.es.js", 14 | "require": "./dist/pptxgen.cjs.js" 15 | }, 16 | "main": "dist/pptxgen.cjs.js", 17 | "module": "dist/pptxgen.es.js", 18 | "files": [ 19 | "dist", 20 | "types" 21 | ], 22 | "types": "types", 23 | "scripts": { 24 | "build": "rollup -c --bundleConfigAsCjs", 25 | "start": "gulp", 26 | "ship": "gulp ship", 27 | "defs": "gulp reactTestDefs", 28 | "watch": "rollup -cw" 29 | }, 30 | "browser": { 31 | "express": false, 32 | "fs": false, 33 | "https": false, 34 | "image-size": false, 35 | "node:fs": false, 36 | "node:fs/promises": false, 37 | "node:https": false, 38 | "os": false, 39 | "path": false 40 | }, 41 | "dependencies": { 42 | "@types/node": "^22.8.1", 43 | "https": "^1.0.0", 44 | "image-size": "^1.1.1", 45 | "jszip": "^3.10.1" 46 | }, 47 | "devDependencies": { 48 | "@eslint/js": "^9.25.1", 49 | "@rollup/plugin-commonjs": "^28.0.1", 50 | "@rollup/plugin-node-resolve": "^16.0.1", 51 | "@stylistic/eslint-plugin": "^4.2.0", 52 | "@typescript-eslint/eslint-plugin": "^8.31.0", 53 | "@typescript-eslint/parser": "^8.31.0", 54 | "eslint": "^9.25.1", 55 | "express": "^5.1.0", 56 | "gulp": "^5.0.0", 57 | "gulp-concat": "^2.6.1", 58 | "gulp-delete-lines": "0.0.7", 59 | "gulp-ignore": "^3.0.0", 60 | "gulp-insert": "^0.5.0", 61 | "gulp-sourcemaps": "^3.0.0", 62 | "gulp-uglify": "^3.0.2", 63 | "rollup": "^4.24.2", 64 | "rollup-plugin-typescript2": "^0.36.0", 65 | "tslib": "^2.8.0", 66 | "typescript": "^5.6.3", 67 | "typescript-eslint": "^8.31.0" 68 | }, 69 | "repository": { 70 | "type": "git", 71 | "url": "git+https://github.com/gitbrent/PptxGenJS.git" 72 | }, 73 | "keywords": [ 74 | "es6-powerpoint", 75 | "html-to-powerpoint", 76 | "javascript-create-powerpoint", 77 | "javascript-create-pptx", 78 | "javascript-generate-pptx", 79 | "javascript-powerpoint", 80 | "javascript-powerpoint-charts", 81 | "javascript-pptx", 82 | "js-create-powerpoint", 83 | "js-create-pptx", 84 | "js-generate-powerpoint", 85 | "js-powerpoint", 86 | "js-powerpoint-library", 87 | "js-powerpoint-pptx", 88 | "node-powerpoint", 89 | "officejs-alternative", 90 | "react-powerpoint", 91 | "slide-generator", 92 | "typescript-powerpoint" 93 | ], 94 | "bugs": { 95 | "url": "https://github.com/gitbrent/PptxGenJS/issues" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import pkg from "./package.json" with { type: "json" }; 2 | import resolve from "@rollup/plugin-node-resolve"; 3 | import commonjs from "@rollup/plugin-commonjs"; 4 | import typescript from "rollup-plugin-typescript2"; 5 | 6 | const nodeBuiltinsRE = /^node:.*/; /* Regex that matches all Node built-in specifiers */ 7 | 8 | export default { 9 | input: "src/pptxgen.ts", 10 | output: [ 11 | { 12 | file: "./src/bld/pptxgen.js", 13 | format: "iife", 14 | name: "PptxGenJS", 15 | globals: { jszip: "JSZip" }, 16 | }, 17 | { file: "./src/bld/pptxgen.cjs.js", format: "cjs", exports: "default" }, 18 | { file: "./src/bld/pptxgen.es.js", format: "es" }, 19 | ], 20 | external: [ 21 | nodeBuiltinsRE, 22 | ...Object.keys(pkg.dependencies || {}), 23 | ...Object.keys(pkg.peerDependencies || {}), 24 | ], 25 | plugins: [ 26 | resolve({ preferBuiltins: true }), 27 | commonjs(), 28 | typescript({ typescript: require("typescript") }), 29 | ] 30 | }; 31 | -------------------------------------------------------------------------------- /src/gen-media.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * PptxGenJS: Media Methods 3 | */ 4 | 5 | import { IMG_BROKEN } from './core-enums' 6 | import { PresSlide, SlideLayout, ISlideRelMedia } from './core-interfaces' 7 | 8 | /** 9 | * Encode Image/Audio/Video into base64 10 | * @param {PresSlide | SlideLayout} layout - slide layout 11 | * @return {Promise} promise 12 | */ 13 | export function encodeSlideMediaRels(layout: PresSlide | SlideLayout): Array> { 14 | // STEP 1: Detect real Node runtime once 15 | const isNode = typeof process !== 'undefined' && !!process.versions?.node && process.release?.name === 'node' 16 | // These will be filled only when we’re in Node 17 | let fs: typeof import('node:fs') | undefined 18 | let https: typeof import('node:https') | undefined 19 | 20 | // STEP 2: Lazy-load Node built-ins if needed 21 | const loadNodeDeps = isNode 22 | ? async () => { 23 | ; ({ default: fs } = await import('node:fs')); ({ default: https } = await import('node:https')) 24 | } 25 | : async () => { } 26 | // Immediately start it when we know we’re in Node 27 | if (isNode) loadNodeDeps() 28 | 29 | // STEP 3: Prepare promises list 30 | const imageProms: Array> = [] 31 | 32 | // A: Capture all audio/image/video candidates for encoding (filtering online/pre-encoded) 33 | const candidateRels = layout._relsMedia.filter( 34 | rel => rel.type !== 'online' && !rel.data && (!rel.path || (rel.path && !rel.path.includes('preencoded'))) 35 | ) 36 | 37 | // B: PERF: Mark dupes (same `path`) to avoid loading the same media over-and-over! 38 | const unqPaths: string[] = [] 39 | candidateRels.forEach(rel => { 40 | if (!unqPaths.includes(rel.path)) { 41 | rel.isDuplicate = false 42 | unqPaths.push(rel.path) 43 | } else { 44 | rel.isDuplicate = true 45 | } 46 | }) 47 | 48 | // STEP 4: Read/Encode each unique media item 49 | candidateRels 50 | .filter(rel => !rel.isDuplicate) 51 | .forEach(rel => { 52 | imageProms.push( 53 | (async () => { 54 | if (!https) await loadNodeDeps() 55 | 56 | // ──────────── NODE LOCAL FILE ──────────── 57 | if (isNode && fs && rel.path.indexOf('http') !== 0) { 58 | try { 59 | const bitmap = fs.readFileSync(rel.path) 60 | rel.data = Buffer.from(bitmap).toString('base64') 61 | candidateRels 62 | .filter(dupe => dupe.isDuplicate && dupe.path === rel.path) 63 | .forEach(dupe => (dupe.data = rel.data)) 64 | return 'done' 65 | } catch (ex) { 66 | rel.data = IMG_BROKEN 67 | candidateRels 68 | .filter(dupe => dupe.isDuplicate && dupe.path === rel.path) 69 | .forEach(dupe => (dupe.data = rel.data)) 70 | throw new Error(`ERROR: Unable to read media: "${rel.path}"\n${String(ex)}`) 71 | } 72 | } 73 | 74 | // ──────────── NODE HTTP(S) ──────────── 75 | if (isNode && https && rel.path.startsWith('http')) { 76 | return await new Promise((resolve, reject) => { 77 | https.get(rel.path, res => { 78 | let raw = '' 79 | res.setEncoding('binary') // IMPORTANT: Only binary encoding works 80 | res.on('data', chunk => (raw += chunk)) 81 | res.on('end', () => { 82 | rel.data = Buffer.from(raw, 'binary').toString('base64') 83 | candidateRels 84 | .filter(dupe => dupe.isDuplicate && dupe.path === rel.path) 85 | .forEach(dupe => (dupe.data = rel.data)) 86 | resolve('done') 87 | }) 88 | res.on('error', () => { 89 | rel.data = IMG_BROKEN 90 | candidateRels 91 | .filter(dupe => dupe.isDuplicate && dupe.path === rel.path) 92 | .forEach(dupe => (dupe.data = rel.data)) 93 | reject(new Error(`ERROR! Unable to load image (https.get): ${rel.path}`)) 94 | }) 95 | }) 96 | }) 97 | } 98 | 99 | // ──────────── BROWSER ──────────── 100 | return await new Promise((resolve, reject) => { 101 | // A: build request 102 | const xhr = new XMLHttpRequest() 103 | xhr.onload = () => { 104 | const reader = new FileReader() 105 | reader.onloadend = () => { 106 | rel.data = reader.result as string 107 | candidateRels 108 | .filter(dupe => dupe.isDuplicate && dupe.path === rel.path) 109 | .forEach(dupe => (dupe.data = rel.data)) 110 | if (!rel.isSvgPng) { 111 | resolve('done') 112 | } else { 113 | createSvgPngPreview(rel) 114 | .then(() => resolve('done')) 115 | .catch(reject) 116 | } 117 | } 118 | reader.readAsDataURL(xhr.response) 119 | } 120 | xhr.onerror = () => { 121 | rel.data = IMG_BROKEN 122 | candidateRels 123 | .filter(dupe => dupe.isDuplicate && dupe.path === rel.path) 124 | .forEach(dupe => (dupe.data = rel.data)) 125 | reject(new Error(`ERROR! Unable to load image (xhr.onerror): ${rel.path}`)) 126 | } 127 | // B: execute request 128 | xhr.open('GET', rel.path) 129 | xhr.responseType = 'blob' 130 | xhr.send() 131 | }) 132 | })(), 133 | ) 134 | }) 135 | 136 | // STEP 5: SVG-PNG previews 137 | // ......: "SVG:" base64 data still requires a png to be generated 138 | // ......: (`isSvgPng` flag this as the preview image, not the SVG itself) 139 | layout._relsMedia 140 | .filter(rel => rel.isSvgPng && rel.data) 141 | .forEach(rel => { 142 | (async () => { 143 | if (isNode && !fs) await loadNodeDeps() 144 | if (isNode && fs) { 145 | // console.log('Sorry, SVG is not supported in Node (more info: https://github.com/gitbrent/PptxGenJS/issues/401)') 146 | rel.data = IMG_BROKEN 147 | imageProms.push(Promise.resolve('done')) 148 | } else { 149 | imageProms.push(createSvgPngPreview(rel)) 150 | } 151 | })() 152 | }) 153 | 154 | return imageProms 155 | } 156 | 157 | /** 158 | * Create SVG preview image 159 | * @param {ISlideRelMedia} rel - slide rel 160 | * @return {Promise} promise 161 | */ 162 | async function createSvgPngPreview(rel: ISlideRelMedia): Promise { 163 | return await new Promise((resolve, reject) => { 164 | // A: Create 165 | const image = new Image() 166 | 167 | // B: Set onload event 168 | image.onload = () => { 169 | // First: Check for any errors: This is the best method (try/catch wont work, etc.) 170 | if (image.width + image.height === 0) { 171 | image.onerror('h/w=0') 172 | } 173 | let canvas: HTMLCanvasElement = document.createElement('CANVAS') as HTMLCanvasElement 174 | const ctx = canvas.getContext('2d') 175 | canvas.width = image.width 176 | canvas.height = image.height 177 | ctx.drawImage(image, 0, 0) 178 | // Users running on local machine will get the following error: 179 | // "SecurityError: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported." 180 | // when the canvas.toDataURL call executes below. 181 | try { 182 | rel.data = canvas.toDataURL(rel.type) 183 | resolve('done') 184 | } catch (ex) { 185 | image.onerror(ex.toString()) 186 | } 187 | canvas = null 188 | } 189 | image.onerror = () => { 190 | rel.data = IMG_BROKEN 191 | reject(new Error(`ERROR! Unable to load image (image.onerror): ${rel.path}`)) 192 | } 193 | 194 | // C: Load image 195 | image.src = typeof rel.data === 'string' ? rel.data : IMG_BROKEN 196 | }) 197 | } 198 | 199 | /** 200 | * FIXME: TODO: currently unused 201 | * TODO: Should return a Promise 202 | */ 203 | /* 204 | function getSizeFromImage (inImgUrl: string): { width: number, height: number } { 205 | const sizeOf = typeof require !== 'undefined' ? require('sizeof') : null // NodeJS 206 | 207 | if (sizeOf) { 208 | try { 209 | const dimensions = sizeOf(inImgUrl) 210 | return { width: dimensions.width, height: dimensions.height } 211 | } catch (ex) { 212 | console.error('ERROR: sizeOf: Unable to load image: ' + inImgUrl) 213 | return { width: 0, height: 0 } 214 | } 215 | } else if (Image && typeof Image === 'function') { 216 | // A: Create 217 | const image = new Image() 218 | 219 | // B: Set onload event 220 | image.onload = () => { 221 | // FIRST: Check for any errors: This is the best method (try/catch wont work, etc.) 222 | if (image.width + image.height === 0) { 223 | return { width: 0, height: 0 } 224 | } 225 | const obj = { width: image.width, height: image.height } 226 | return obj 227 | } 228 | image.onerror = () => { 229 | console.error(`ERROR: image.onload: Unable to load image: ${inImgUrl}`) 230 | } 231 | 232 | // C: Load image 233 | image.src = inImgUrl 234 | } 235 | } 236 | */ 237 | -------------------------------------------------------------------------------- /src/gen-utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * PptxGenJS: Utility Methods 3 | */ 4 | 5 | import { EMU, REGEX_HEX_COLOR, DEF_FONT_COLOR, ONEPT, SchemeColor, SCHEME_COLORS } from './core-enums' 6 | import { PresLayout, TextGlowProps, PresSlide, ShapeFillProps, Color, ShapeLineProps, Coord, ShadowProps } from './core-interfaces' 7 | 8 | /** 9 | * Translates any type of `x`/`y`/`w`/`h` prop to EMU 10 | * - guaranteed to return a result regardless of undefined, null, etc. (0) 11 | * - {number} - 12800 (EMU) 12 | * - {number} - 0.5 (inches) 13 | * - {string} - "75%" 14 | * @param {number|string} size - numeric ("5.5") or percentage ("90%") 15 | * @param {'X' | 'Y'} xyDir - direction 16 | * @param {PresLayout} layout - presentation layout 17 | * @returns {number} calculated size 18 | */ 19 | export function getSmartParseNumber (size: Coord, xyDir: 'X' | 'Y', layout: PresLayout): number { 20 | // FIRST: Convert string numeric value if reqd 21 | if (typeof size === 'string' && !isNaN(Number(size))) size = Number(size) 22 | 23 | // CASE 1: Number in inches 24 | // Assume any number less than 100 is inches 25 | if (typeof size === 'number' && size < 100) return inch2Emu(size) 26 | 27 | // CASE 2: Number is already converted to something other than inches 28 | // Assume any number greater than 100 sure isnt inches! Just return it (assume value is EMU already). 29 | if (typeof size === 'number' && size >= 100) return size 30 | 31 | // CASE 3: Percentage (ex: '50%') 32 | if (typeof size === 'string' && size.includes('%')) { 33 | if (xyDir && xyDir === 'X') return Math.round((parseFloat(size) / 100) * layout.width) 34 | if (xyDir && xyDir === 'Y') return Math.round((parseFloat(size) / 100) * layout.height) 35 | 36 | // Default: Assume width (x/cx) 37 | return Math.round((parseFloat(size) / 100) * layout.width) 38 | } 39 | 40 | // LAST: Default value 41 | return 0 42 | } 43 | 44 | /** 45 | * Basic UUID Generator Adapted 46 | * @link https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript#answer-2117523 47 | * @param {string} uuidFormat - UUID format 48 | * @returns {string} UUID 49 | */ 50 | export function getUuid (uuidFormat: string): string { 51 | return uuidFormat.replace(/[xy]/g, function (c) { 52 | const r = (Math.random() * 16) | 0 53 | const v = c === 'x' ? r : (r & 0x3) | 0x8 54 | return v.toString(16) 55 | }) 56 | } 57 | 58 | /** 59 | * Replace special XML characters with HTML-encoded strings 60 | * @param {string} xml - XML string to encode 61 | * @returns {string} escaped XML 62 | */ 63 | export function encodeXmlEntities (xml: string): string { 64 | // NOTE: Dont use short-circuit eval here as value c/b "0" (zero) etc.! 65 | if (typeof xml === 'undefined' || xml == null) return '' 66 | return xml.toString().replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''') 67 | } 68 | 69 | /** 70 | * Convert inches into EMU 71 | * @param {number|string} inches - as string or number 72 | * @returns {number} EMU value 73 | */ 74 | export function inch2Emu (inches: number | string): number { 75 | // NOTE: Provide Caller Safety: Numbers may get conv<->conv during flight, so be kind and do some simple checks to ensure inches were passed 76 | // Any value over 100 damn sure isnt inches, so lets assume its in EMU already, therefore, just return the same value 77 | if (typeof inches === 'number' && inches > 100) return inches 78 | if (typeof inches === 'string') inches = Number(inches.replace(/in*/gi, '')) 79 | return Math.round(EMU * inches) 80 | } 81 | 82 | /** 83 | * Convert `pt` into points (using `ONEPT`) 84 | * @param {number|string} pt 85 | * @returns {number} value in points (`ONEPT`) 86 | */ 87 | export function valToPts (pt: number | string): number { 88 | const points = Number(pt) || 0 89 | return isNaN(points) ? 0 : Math.round(points * ONEPT) 90 | } 91 | 92 | /** 93 | * Convert degrees (0..360) to PowerPoint `rot` value 94 | * @param {number} d degrees 95 | * @returns {number} calculated `rot` value 96 | */ 97 | export function convertRotationDegrees (d: number): number { 98 | d = d || 0 99 | return Math.round((d > 360 ? d - 360 : d) * 60000) 100 | } 101 | 102 | /** 103 | * Converts component value to hex value 104 | * @param {number} c - component color 105 | * @returns {string} hex string 106 | */ 107 | export function componentToHex (c: number): string { 108 | const hex = c.toString(16) 109 | return hex.length === 1 ? '0' + hex : hex 110 | } 111 | 112 | /** 113 | * Converts RGB colors from css selectors to Hex for Presentation colors 114 | * @param {number} r - red value 115 | * @param {number} g - green value 116 | * @param {number} b - blue value 117 | * @returns {string} XML string 118 | */ 119 | export function rgbToHex (r: number, g: number, b: number): string { 120 | return (componentToHex(r) + componentToHex(g) + componentToHex(b)).toUpperCase() 121 | } 122 | 123 | /** TODO: FUTURE: TODO-4.0: 124 | * @date 2022-04-10 125 | * @tldr this s/b a private method with all current calls switched to `genXmlColorSelection()` 126 | * @desc lots of code calls this method 127 | * @example [gen-charts.tx] `strXml += '' + createColorElement(seriesColor, ``) + ''` 128 | * Thi sis wrong. We s/b calling `genXmlColorSelection()` instead as it returns `BLAH`!! 129 | */ 130 | /** 131 | * Create either a `a:schemeClr` - (scheme color) or `a:srgbClr` (hexa representation). 132 | * @param {string|SCHEME_COLORS} colorStr - hexa representation (eg. "FFFF00") or a scheme color constant (eg. pptx.SchemeColor.ACCENT1) 133 | * @param {string} innerElements - additional elements that adjust the color and are enclosed by the color element 134 | * @returns {string} XML string 135 | */ 136 | export function createColorElement (colorStr: string | SCHEME_COLORS, innerElements?: string): string { 137 | let colorVal = (colorStr || '').replace('#', '') 138 | 139 | if ( 140 | !REGEX_HEX_COLOR.test(colorVal) && 141 | colorVal !== SchemeColor.background1 && 142 | colorVal !== SchemeColor.background2 && 143 | colorVal !== SchemeColor.text1 && 144 | colorVal !== SchemeColor.text2 && 145 | colorVal !== SchemeColor.accent1 && 146 | colorVal !== SchemeColor.accent2 && 147 | colorVal !== SchemeColor.accent3 && 148 | colorVal !== SchemeColor.accent4 && 149 | colorVal !== SchemeColor.accent5 && 150 | colorVal !== SchemeColor.accent6 151 | ) { 152 | console.warn(`"${colorVal}" is not a valid scheme color or hex RGB! "${DEF_FONT_COLOR}" used instead. Only provide 6-digit RGB or 'pptx.SchemeColor' values!`) 153 | colorVal = DEF_FONT_COLOR 154 | } 155 | 156 | const tagName = REGEX_HEX_COLOR.test(colorVal) ? 'srgbClr' : 'schemeClr' 157 | const colorAttr = 'val="' + (REGEX_HEX_COLOR.test(colorVal) ? colorVal.toUpperCase() : colorVal) + '"' 158 | 159 | return innerElements ? `${innerElements}` : `` 160 | } 161 | 162 | /** 163 | * Creates `a:glow` element 164 | * @param {TextGlowProps} options glow properties 165 | * @param {TextGlowProps} defaults defaults for unspecified properties in `opts` 166 | * @see http://officeopenxml.com/drwSp-effects.php 167 | * { size: 8, color: 'FFFFFF', opacity: 0.75 }; 168 | */ 169 | export function createGlowElement (options: TextGlowProps, defaults: TextGlowProps): string { 170 | let strXml = '' 171 | const opts = { ...defaults, ...options } 172 | const size = Math.round(opts.size * ONEPT) 173 | const color = opts.color 174 | const opacity = Math.round(opts.opacity * 100000) 175 | 176 | strXml += `` 177 | strXml += createColorElement(color, ``) 178 | strXml += '' 179 | 180 | return strXml 181 | } 182 | 183 | /** 184 | * Create color selection 185 | * @param {Color | ShapeFillProps | ShapeLineProps} props fill props 186 | * @returns XML string 187 | */ 188 | export function genXmlColorSelection (props: Color | ShapeFillProps | ShapeLineProps): string { 189 | let fillType = 'solid' 190 | let colorVal = '' 191 | let internalElements = '' 192 | let outText = '' 193 | 194 | if (props) { 195 | if (typeof props === 'string') colorVal = props 196 | else { 197 | if (props.type) fillType = props.type 198 | if (props.color) colorVal = props.color 199 | if (props.alpha) internalElements += `` // DEPRECATED: @deprecated v3.3.0 200 | if (props.transparency) internalElements += `` 201 | } 202 | 203 | switch (fillType) { 204 | case 'solid': 205 | outText += `${createColorElement(colorVal, internalElements)}` 206 | break 207 | default: // @note need a statement as having only "break" is removed by rollup, then tiggers "no-default" js-linter 208 | outText += '' 209 | break 210 | } 211 | } 212 | 213 | return outText 214 | } 215 | 216 | /** 217 | * Get a new rel ID (rId) for charts, media, etc. 218 | * @param {PresSlide} target - the slide to use 219 | * @returns {number} count of all current rels plus 1 for the caller to use as its "rId" 220 | */ 221 | export function getNewRelId (target: PresSlide): number { 222 | return target._rels.length + target._relsChart.length + target._relsMedia.length + 1 223 | } 224 | 225 | /** 226 | * Checks shadow options passed by user and performs corrections if needed. 227 | * @param {ShadowProps} ShadowProps - shadow options 228 | */ 229 | export function correctShadowOptions (ShadowProps: ShadowProps): ShadowProps | undefined { 230 | if (!ShadowProps || typeof ShadowProps !== 'object') { 231 | // console.warn("`shadow` options must be an object. Ex: `{shadow: {type:'none'}}`") 232 | return 233 | } 234 | 235 | // OPT: `type` 236 | if (ShadowProps.type !== 'outer' && ShadowProps.type !== 'inner' && ShadowProps.type !== 'none') { 237 | console.warn('Warning: shadow.type options are `outer`, `inner` or `none`.') 238 | ShadowProps.type = 'outer' 239 | } 240 | 241 | // OPT: `angle` 242 | if (ShadowProps.angle) { 243 | // A: REALITY-CHECK 244 | if (isNaN(Number(ShadowProps.angle)) || ShadowProps.angle < 0 || ShadowProps.angle > 359) { 245 | console.warn('Warning: shadow.angle can only be 0-359') 246 | ShadowProps.angle = 270 247 | } 248 | 249 | // B: ROBUST: Cast any type of valid arg to int: '12', 12.3, etc. -> 12 250 | ShadowProps.angle = Math.round(Number(ShadowProps.angle)) 251 | } 252 | 253 | // OPT: `opacity` 254 | if (ShadowProps.opacity) { 255 | // A: REALITY-CHECK 256 | if (isNaN(Number(ShadowProps.opacity)) || ShadowProps.opacity < 0 || ShadowProps.opacity > 1) { 257 | console.warn('Warning: shadow.opacity can only be 0-1') 258 | ShadowProps.opacity = 0.75 259 | } 260 | 261 | // B: ROBUST: Cast any type of valid arg to int: '12', 12.3, etc. -> 12 262 | ShadowProps.opacity = Number(ShadowProps.opacity) 263 | } 264 | 265 | // OPT: `color` 266 | if (ShadowProps.color) { 267 | // INCORRECT FORMAT 268 | if (ShadowProps.color.startsWith('#')) { 269 | console.warn('Warning: shadow.color should not include hash (#) character, , e.g. "FF0000"') 270 | ShadowProps.color = ShadowProps.color.replace('#', '') 271 | } 272 | } 273 | 274 | return ShadowProps 275 | } 276 | -------------------------------------------------------------------------------- /src/slide.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * PptxGenJS: Slide Class 3 | */ 4 | 5 | import { CHART_NAME, SHAPE_NAME } from './core-enums' 6 | import { 7 | AddSlideProps, 8 | BackgroundProps, 9 | HexColor, 10 | IChartMulti, 11 | IChartOpts, 12 | IChartOptsLib, 13 | IOptsChartData, 14 | ISlideObject, 15 | ISlideRel, 16 | ISlideRelChart, 17 | ISlideRelMedia, 18 | ImageProps, 19 | MediaProps, 20 | PresLayout, 21 | PresSlide, 22 | ShapeProps, 23 | SlideLayout, 24 | SlideNumberProps, 25 | TableProps, 26 | TableRow, 27 | TextProps, 28 | TextPropsOptions, 29 | } from './core-interfaces' 30 | import * as genObj from './gen-objects' 31 | 32 | export default class Slide { 33 | private readonly _setSlideNum: (value: SlideNumberProps) => void 34 | 35 | public addSlide: (options?: AddSlideProps) => PresSlide 36 | public getSlide: (slideNum: number) => PresSlide 37 | public _name: string 38 | public _presLayout: PresLayout 39 | public _rels: ISlideRel[] 40 | public _relsChart: ISlideRelChart[] 41 | public _relsMedia: ISlideRelMedia[] 42 | public _rId: number 43 | public _slideId: number 44 | public _slideLayout: SlideLayout 45 | public _slideNum: number 46 | public _slideNumberProps: SlideNumberProps 47 | public _slideObjects: ISlideObject[] 48 | public _newAutoPagedSlides: PresSlide[] 49 | 50 | constructor(params: { 51 | addSlide: (options?: AddSlideProps) => PresSlide 52 | getSlide: (slideNum: number) => PresSlide 53 | presLayout: PresLayout 54 | setSlideNum: (value: SlideNumberProps) => void 55 | slideId: number 56 | slideRId: number 57 | slideNumber: number 58 | slideLayout?: SlideLayout 59 | }) { 60 | this.addSlide = params.addSlide 61 | this.getSlide = params.getSlide 62 | this._name = `Slide ${params.slideNumber}` 63 | this._presLayout = params.presLayout 64 | this._rId = params.slideRId 65 | this._rels = [] 66 | this._relsChart = [] 67 | this._relsMedia = [] 68 | this._setSlideNum = params.setSlideNum 69 | this._slideId = params.slideId 70 | this._slideLayout = params.slideLayout || null 71 | this._slideNum = params.slideNumber 72 | this._slideObjects = [] 73 | /** NOTE: Slide Numbers: In order for Slide Numbers to function they need to be in all 3 files: master/layout/slide 74 | * `defineSlideMaster` and `addNewSlide.slideNumber` will add {slideNumber} to `this.masterSlide` and `this.slideLayouts` 75 | * so, lastly, add to the Slide now. 76 | */ 77 | this._slideNumberProps = this._slideLayout?._slideNumberProps ? this._slideLayout._slideNumberProps : null 78 | } 79 | 80 | /** 81 | * Background color 82 | * @type {string|BackgroundProps} 83 | * @deprecated in v3.3.0 - use `background` instead 84 | */ 85 | private _bkgd: string | BackgroundProps 86 | public set bkgd(value: string | BackgroundProps) { 87 | this._bkgd = value 88 | if (!this._background || !this._background.color) { 89 | if (!this._background) this._background = {} 90 | if (typeof value === 'string') this._background.color = value 91 | } 92 | } 93 | 94 | public get bkgd(): string | BackgroundProps { 95 | return this._bkgd 96 | } 97 | 98 | /** 99 | * Background color or image 100 | * @type {BackgroundProps} 101 | * @example solid color `background: { color:'FF0000' }` 102 | * @example color+trans `background: { color:'FF0000', transparency:0.5 }` 103 | * @example base64 `background: { data:'image/png;base64,ABC[...]123' }` 104 | * @example url `background: { path:'https://some.url/image.jpg'}` 105 | * @since v3.3.0 106 | */ 107 | private _background: BackgroundProps 108 | public set background(props: BackgroundProps) { 109 | this._background = props 110 | // Add background (image data/path must be captured before `exportPresentation()` is called) 111 | if (props) genObj.addBackgroundDefinition(props, this) 112 | } 113 | 114 | public get background(): BackgroundProps { 115 | return this._background 116 | } 117 | 118 | /** 119 | * Default font color 120 | * @type {HexColor} 121 | */ 122 | private _color: HexColor 123 | public set color(value: HexColor) { 124 | this._color = value 125 | } 126 | 127 | public get color(): HexColor { 128 | return this._color 129 | } 130 | 131 | /** 132 | * @type {boolean} 133 | */ 134 | private _hidden: boolean 135 | public set hidden(value: boolean) { 136 | this._hidden = value 137 | } 138 | 139 | public get hidden(): boolean { 140 | return this._hidden 141 | } 142 | 143 | /** 144 | * @type {SlideNumberProps} 145 | */ 146 | public set slideNumber(value: SlideNumberProps) { 147 | // NOTE: Slide Numbers: In order for Slide Numbers to function they need to be in all 3 files: master/layout/slide 148 | this._slideNumberProps = value 149 | this._setSlideNum(value) 150 | } 151 | 152 | public get slideNumber(): SlideNumberProps { 153 | return this._slideNumberProps 154 | } 155 | 156 | public get newAutoPagedSlides(): PresSlide[] { 157 | return this._newAutoPagedSlides 158 | } 159 | 160 | /** 161 | * Add chart to Slide 162 | * @param {CHART_NAME|IChartMulti[]} type - chart type 163 | * @param {object[]} data - data object 164 | * @param {IChartOpts} options - chart options 165 | * @return {Slide} this Slide 166 | */ 167 | addChart(type: CHART_NAME | IChartMulti[], data: IOptsChartData[], options?: IChartOpts): Slide { 168 | // FUTURE: TODO-VERSION-4: Remove first arg - only take data and opts, with "type" required on opts 169 | // Set `_type` on IChartOptsLib as its what is used as object is passed around 170 | const optionsWithType: IChartOptsLib = options || {} 171 | optionsWithType._type = type 172 | genObj.addChartDefinition(this, type, data, options) 173 | return this 174 | } 175 | 176 | /** 177 | * Add image to Slide 178 | * @param {ImageProps} options - image options 179 | * @return {Slide} this Slide 180 | */ 181 | addImage(options: ImageProps): Slide { 182 | genObj.addImageDefinition(this, options) 183 | return this 184 | } 185 | 186 | /** 187 | * Add media (audio/video) to Slide 188 | * @param {MediaProps} options - media options 189 | * @return {Slide} this Slide 190 | */ 191 | addMedia(options: MediaProps): Slide { 192 | genObj.addMediaDefinition(this, options) 193 | return this 194 | } 195 | 196 | /** 197 | * Add speaker notes to Slide 198 | * @docs https://gitbrent.github.io/PptxGenJS/docs/speaker-notes.html 199 | * @param {string} notes - notes to add to slide 200 | * @return {Slide} this Slide 201 | */ 202 | addNotes(notes: string): Slide { 203 | genObj.addNotesDefinition(this, notes) 204 | return this 205 | } 206 | 207 | /** 208 | * Add shape to Slide 209 | * @param {SHAPE_NAME} shapeName - shape name 210 | * @param {ShapeProps} options - shape options 211 | * @return {Slide} this Slide 212 | */ 213 | addShape(shapeName: SHAPE_NAME, options?: ShapeProps): Slide { 214 | // NOTE: As of v3.1.0,