├── .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 | 
4 |
5 | 
6 | 
7 | 
8 | 
9 | 
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 |
13 |
--------------------------------------------------------------------------------
/demos/browser/html/navbar.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PptxGenJS
6 |
7 |
9 |
10 |
11 |
12 |
19 |
20 |
21 | Latest Release
22 |
23 |
24 | Docs
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/demos/browser/html/navtabs.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | Introduction
6 |
7 |
8 |
9 |
11 | HTML to PPTX
12 |
13 |
14 |
15 |
17 | Charts
18 |
19 |
20 |
21 |
23 | Images & Media
24 |
25 |
26 |
27 |
29 | Shapes & Text
30 |
31 |
32 |
33 |
35 | Tables
36 |
37 |
38 |
39 |
41 | Slide Masters
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/demos/browser/html/tab-charts.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
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 |
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 |
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 |
119 | Include media demo slide 3
120 |
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 |
11 |
pptx.version
12 |
13 |
14 |
Scheme Colors
15 |
20 |
Object.keys(pptx.SchemeColor)
21 |
22 |
23 |
Chart Types
24 |
29 |
Object.keys(pptx.ChartType)
30 |
31 |
32 |
Shape Types
33 |
38 |
Object.keys(pptx.ShapeType)
39 |
40 |
41 |
42 |
43 |
44 |
49 |
50 |
51 |
52 |
53 |
Live Demo
54 |
Click below to create a basic presentation.
55 |
56 |
57 |
58 | Run Basic Demo
59 |
60 |
61 |
62 |
63 |
64 |
65 |
71 |
72 |
73 |
74 |
75 |
Editable Code
76 |
Use the area below to easily try out various library features.
77 |
78 |
79 |
80 | Execute Sandbox Code
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/demos/browser/html/tab-masters.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | Run Demo
38 |
39 |
40 |
41 |
How To
42 |
Create and Apply
43 |
44 |
45 |
46 |
47 |
48 |
PowerPoint
49 |
Example Slide Master Result
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/demos/browser/html/tab-shapes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
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 |
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 |
49 |
50 |
51 |
52 |
Please Wait...
53 | Creating and saving presentation
54 |
55 |
61 |
62 |
63 |
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 |
Generate PowerPoint (using Worker)
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
57 |
58 |
59 |
60 | PptxGenJS
61 |
62 |
69 |
70 |
71 |
72 |
79 |
80 |
"window.open('https://github.com/gitbrent/PptxGenJS/releases')"}>
81 | Latest Release
82 |
83 |
"window.open('https://gitbrent.github.io/PptxGenJS/docs/installation/')"}>
84 | Docs
85 |
86 |
87 |
"window.open('https://fosstodon.org/@gitbrent')"}>
88 |
89 |
90 |
"window.open('https://gitbrent.github.io/PptxGenJS')"}>
91 |
92 |
93 |
94 |
95 |
96 |
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 | col 1
117 | col 2
118 | col 3
119 |
120 |
121 |
122 |
123 | cell 1
124 | cell 2
125 | cell 3
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | runDemo()}>
134 | Run Test 1
135 | Demo Code
136 |
137 |
138 |
139 | testMainMethods()}>
140 | Run Test 2
141 | Misc Objects
142 |
143 |
144 |
145 | testTableMethod()}>
146 | Run Test 3
147 | Table-to-Slides
148 |
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,