pptx-angular-viewer 1.1.23 → 1.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,86 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+ This file is generated from [Conventional Commits](https://www.conventionalcommits.org)
5
+ by [git-cliff](https://git-cliff.org); do not edit it by hand.
6
+
7
+ ## [1.1.29](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.1.29) - 2026-06-21
8
+
9
+ ### Features
10
+
11
+ - **shared:** Add Three.js SmartArt 3D model + scene runtime (by @ChristopherVR) ([f949213](https://github.com/ChristopherVR/pptx-viewer/commit/f949213b33ed0dca4c52d5d1ab414c3dba67efe7))
12
+ - **angular:** Opt-in Three.js SmartArt renderer (by @ChristopherVR) ([be6d858](https://github.com/ChristopherVR/pptx-viewer/commit/be6d85818b4a2f70cf644ee91467fd44dc4506de))
13
+
14
+ ### Documentation
15
+
16
+ - Sharpen npm descriptions and keywords for discoverability (by @ChristopherVR) ([8fea56d](https://github.com/ChristopherVR/pptx-viewer/commit/8fea56d7650f7dc2f3167dea97b94b612a03a4e7))
17
+ - **angular:** Reword README in plain language (by @ChristopherVR) ([ba72266](https://github.com/ChristopherVR/pptx-viewer/commit/ba722668b0c4846e86837b2cf255198231ab2631))
18
+
19
+ ## [1.1.24](https://github.com/ChristopherVR/pptx-viewer/releases/tag/pptx-angular-viewer@1.1.24) - 2026-06-20
20
+
21
+ ### Features
22
+
23
+ - **vue:** Add pptx-vue-viewer package + bundled pptx-viewer-shared (by @ChristopherVR) ([1b7a958](https://github.com/ChristopherVR/pptx-viewer/commit/1b7a958ce91792a6d174f174932800bc8ff40ef9))
24
+ - **angular:** Add pptx-angular-viewer package + demo (by @ChristopherVR) ([81255a9](https://github.com/ChristopherVR/pptx-viewer/commit/81255a9251e855bc51b97c8dc68b55e71e206882))
25
+ - **angular:** Image & gradient fills in element-style (by @ChristopherVR) ([2457aa2](https://github.com/ChristopherVR/pptx-viewer/commit/2457aa2163e6e6504aa36d464d3686d58f625338))
26
+ - **angular:** Connector, table & clip-path renderers (by @ChristopherVR) ([12bb060](https://github.com/ChristopherVR/pptx-viewer/commit/12bb060841d9cdc2d473c5d3794f37502b6047eb))
27
+ - **angular:** PNG + PDF export (html2canvas-pro + jspdf) (by @ChristopherVR) ([e5aec3d](https://github.com/ChristopherVR/pptx-viewer/commit/e5aec3d58b84407629ca84292fe7c3407bd9d87e))
28
+ - **angular:** Wire signatures panel (parts-reading) into the viewer (by @ChristopherVR) ([d11afb9](https://github.com/ChristopherVR/pptx-viewer/commit/d11afb96195c998f9a56a218fa641b8adbf62fb6))
29
+
30
+ ### Bug Fixes
31
+
32
+ - **build:** Make all packages build + publish cleanly; align Vue README (by @ChristopherVR) ([7db5de6](https://github.com/ChristopherVR/pptx-viewer/commit/7db5de6a343887fc1a32dd526ae1ab68e1e3e6e0))
33
+
34
+ ### Other
35
+
36
+ - **angular:** Tailwind 4 Office ribbon + pt→px font fix (by @ChristopherVR) ([ad5da60](https://github.com/ChristopherVR/pptx-viewer/commit/ad5da60e73c4a6ea780cda94773b4a74dcea9786))
37
+
38
+ ### Refactor
39
+
40
+ - **react:** Consume theme + loader from pptx-viewer-shared (by @ChristopherVR) ([1b93d1f](https://github.com/ChristopherVR/pptx-viewer/commit/1b93d1fccff378b0ac402810a0cbddea46add29c))
41
+ - **angular:** Keep core peer as workspace:\*, resolve at build time (by @ChristopherVR) ([b123ac9](https://github.com/ChristopherVR/pptx-viewer/commit/b123ac99e9611b7f585197d827ba2ac35217997e))
42
+ - **angular:** Remove em-dashes from code comments and prose (by @ChristopherVR) ([0166321](https://github.com/ChristopherVR/pptx-viewer/commit/01663210fd84f60b29c7c6176def02951e3903f3))
43
+
44
+ ### Documentation
45
+
46
+ - **angular:** Update PORTING for charts, table rich text, connector routing (by @ChristopherVR) ([3baddb5](https://github.com/ChristopherVR/pptx-viewer/commit/3baddb5363294aa2bcbe08c18f51a7b8a0be4f1d))
47
+ - **angular:** Update PORTING for SmartArt/ink/OLE/3D/zoom + effects (by @ChristopherVR) ([d5393c6](https://github.com/ChristopherVR/pptx-viewer/commit/d5393c6269812941f2314f03e33432c076f39c79))
48
+ - **angular:** Record slide background + hyperlink rendering (by @ChristopherVR) ([0c21fe3](https://github.com/ChristopherVR/pptx-viewer/commit/0c21fe31ad18006c97807b0ea3c3e39bb950d163))
49
+ - **angular:** Update PORTING for parity waves 1-3 (by @ChristopherVR) ([e51e7b9](https://github.com/ChristopherVR/pptx-viewer/commit/e51e7b98e4ee833a28f4f2e2ba1cc0e8b8af881b))
50
+ - **angular:** Record export + editor foundation in PORTING (by @ChristopherVR) ([d6f494d](https://github.com/ChristopherVR/pptx-viewer/commit/d6f494df7c59b0f88c9b4a66bce5705c43c4603c))
51
+ - **angular:** Mark editor interaction UI + save-back done (by @ChristopherVR) ([ce0fddf](https://github.com/ChristopherVR/pptx-viewer/commit/ce0fddff33728155cee86930c5f530c66ed400d3))
52
+ - **angular:** Record drag/resize + inspector panel (by @ChristopherVR) ([04dd906](https://github.com/ChristopherVR/pptx-viewer/commit/04dd9069dec0a384bda83d7d1a8262eb39fb5eba))
53
+ - **angular:** Record editor chrome (panels, toolbar, clipboard, align) (by @ChristopherVR) ([69ba935](https://github.com/ChristopherVR/pptx-viewer/commit/69ba9354218ea9ce3066ea84fa4fa60659ff85d8))
54
+ - **angular:** Record rotation/marquee/group + direct-manipulation complete (by @ChristopherVR) ([bb45ccd](https://github.com/ChristopherVR/pptx-viewer/commit/bb45ccd938b83881d735f205a245c1eb1df3d8c3))
55
+ - **angular:** Record snap guides + slide props; parity summary (by @ChristopherVR) ([f748b18](https://github.com/ChristopherVR/pptx-viewer/commit/f748b1871307c97e2c81b2fa64cc9611f2b559d1))
56
+ - **angular:** Record advanced-subsystem waves 1-2 in PORTING.md (by @ChristopherVR) ([63c78ff](https://github.com/ChristopherVR/pptx-viewer/commit/63c78ff996efe208540a1825d9c301feebb36956))
57
+ - **angular:** Record advanced-subsystem wiring in PORTING.md (by @ChristopherVR) ([ebc72a7](https://github.com/ChristopherVR/pptx-viewer/commit/ebc72a79bf5edb1c13d58a70b7b96a3cecc8a810))
58
+ - **angular:** Record bubble & radar chart kinds in PORTING.md (by @ChristopherVR) ([ea8dd22](https://github.com/ChristopherVR/pptx-viewer/commit/ea8dd226cca868fec18e1c6a2375d25ad942a03f))
59
+ - **angular:** Record parity push (charts, connectors, duotone, editor, export, subsystem wiring) (by @ChristopherVR) ([127a233](https://github.com/ChristopherVR/pptx-viewer/commit/127a2333f84eeb0ffb0956dd2a2d15518f18269e))
60
+ - **angular:** Record full feature parity (animation playback/authoring, mobile chrome) (by @ChristopherVR) ([e48a258](https://github.com/ChristopherVR/pptx-viewer/commit/e48a258e0e2a873f75121aaeff906d56de5b43c6))
61
+ - **angular:** Record depth batch (chart overlays, text warp, annotations, transitions) (by @ChristopherVR) ([e68f07e](https://github.com/ChristopherVR/pptx-viewer/commit/e68f07e3a56ba881bbd0f178e518695baf34d139))
62
+ - **angular:** Record framework-neutral e2e contract parity (by @ChristopherVR) ([6200fc9](https://github.com/ChristopherVR/pptx-viewer/commit/6200fc9cdf38f2c1623b3b519d1499cdc98515b3))
63
+ - **angular:** Record visual-parity audit vs React + remaining chrome gap (by @ChristopherVR) ([f3ae199](https://github.com/ChristopherVR/pptx-viewer/commit/f3ae19991d6208a2654a7137aff08dd3ee43b22e))
64
+ - Streamline npm READMEs and add badges, screenshots, demo links (by @ChristopherVR) ([92e980d](https://github.com/ChristopherVR/pptx-viewer/commit/92e980d434900abd223c4d70c6cae19a623f9ca8))
65
+ - **vue,angular:** Point Try-demo links at per-framework demos (by @ChristopherVR) ([b5e6915](https://github.com/ChristopherVR/pptx-viewer/commit/b5e6915c416075f4f50630d76dfedbc324cde03e))
66
+ - **angular:** Log ribbon-port kickoff + Tailwind foundation status (by @ChristopherVR) ([40e0408](https://github.com/ChristopherVR/pptx-viewer/commit/40e04083c02b2f59f77253743df74218c4bca5b3))
67
+ - **angular:** Log ribbon shell + status bar landed, preflight verified (by @ChristopherVR) ([2b10e74](https://github.com/ChristopherVR/pptx-viewer/commit/2b10e74db1ae065785c8ac8ec50d4f46d3635ad7))
68
+ - **angular:** Log ribbon e2e status (8/10) + known mobile gaps + pt→px note (by @ChristopherVR) ([e0cb539](https://github.com/ChristopherVR/pptx-viewer/commit/e0cb5394473b96c453fce71e9b83205976ebd803))
69
+ - **angular:** Mark ribbon merged to main (by @ChristopherVR) ([9226bdc](https://github.com/ChristopherVR/pptx-viewer/commit/9226bdc08a3e87dc2d2322fbe06c3491d6476c35))
70
+ - **angular:** Log advanced ribbon tabs landed (Transitions/Animations wired, Design partial, Draw UI-only) (by @ChristopherVR) ([deeb6c1](https://github.com/ChristopherVR/pptx-viewer/commit/deeb6c1adfd25516b3d3effe19b136721c9ee8a1))
71
+ - **angular:** Mobile e2e now 10/10 (notes-sheet flow fix); log trunk spec fix (by @ChristopherVR) ([92f4c44](https://github.com/ChristopherVR/pptx-viewer/commit/92f4c44a931233258981e452ecaccd5fe25f1a39))
72
+ - **angular:** Log Insert/View/Design/Draw depth landed (2148 tests, e2e 10/10) (by @ChristopherVR) ([cbe9dc7](https://github.com/ChristopherVR/pptx-viewer/commit/cbe9dc7cb1998d766def97fbb20db4169c869680))
73
+ - **angular:** Log niche wave (snap-to-grid/guides/eyedropper/selection-pane/custom-shows); 2161 tests, e2e 10/10 (by @ChristopherVR) ([fa77b49](https://github.com/ChristopherVR/pptx-viewer/commit/fa77b497938ea9bae60aa3794902f16f710efe54))
74
+ - **angular:** Niche list complete — custom-show present + equation OMML; functional parity reached (by @ChristopherVR) ([ac719c7](https://github.com/ChristopherVR/pptx-viewer/commit/ac719c79af0a78d3d98902577c7c35a38c42423e))
75
+ - **angular:** Trim PORTING.md to status + what's-missing (drop session log) (by @ChristopherVR) ([393d5e2](https://github.com/ChristopherVR/pptx-viewer/commit/393d5e2e10361cd9158cf502c2f15e67bbe9e09c))
76
+ - **angular:** E2e now 28/0 (no skips); remaining = refactor/cosmetic debts only (by @ChristopherVR) ([7817092](https://github.com/ChristopherVR/pptx-viewer/commit/7817092780173c1a288029addfadc6c6e571e871))
77
+ - Remove em-dashes and clarify demo link in viewer packages (by @ChristopherVR) ([f52afff](https://github.com/ChristopherVR/pptx-viewer/commit/f52afffd935016b747116a9909c523021b492225))
78
+
79
+ ### Build & CI
80
+
81
+ - **angular:** Adopt Tailwind 4 pipeline for ribbon chrome parity (by @ChristopherVR) ([65cf58f](https://github.com/ChristopherVR/pptx-viewer/commit/65cf58fbcce1fbf3ac0c3ce0f3b49b3c9604d1b1))
82
+
83
+ ### Dependencies
84
+
85
+ - **deps:** Update dependencies and CI actions to latest (by @ChristopherVR) ([b1a84a2](https://github.com/ChristopherVR/pptx-viewer/commit/b1a84a26814bfdb9b5d5ef7dd87aeabc4fa82c04))
86
+ - **deps:** Bump all workspace manifest floors to latest (by @ChristopherVR) ([890c33d](https://github.com/ChristopherVR/pptx-viewer/commit/890c33d667a39480a69e6a3da893964382993b29))
package/NOTICE CHANGED
@@ -7,7 +7,7 @@ attribution it contains) in any derivative works you distribute.
7
7
 
8
8
  This package bundles the following component under a different license:
9
9
 
10
- mtx-decompressor MicroType Express (MTX) font decompressor
10
+ mtx-decompressor - MicroType Express (MTX) font decompressor
11
11
  License: Mozilla Public License 2.0 (MPL-2.0)
12
12
  Source: https://github.com/ChristopherVR/pptx-viewer/tree/main/packages/mtx-decompressor
13
13
  Origin: Ported from libeot (https://github.com/nicowilliams/libeot)
package/README.md CHANGED
@@ -1,35 +1,43 @@
1
1
  # pptx-angular-viewer
2
2
 
3
- Render Microsoft PowerPoint (`.pptx`) presentations directly in an Angular app —
4
- no server, no conversion step, no PowerPoint install. Drop in a `<pptx-viewer>`
5
- component, hand it the file bytes, and it parses and displays the slides as
6
- scalable HTML/CSS with slide navigation and zoom.
3
+ [![npm version](https://img.shields.io/npm/v/pptx-angular-viewer.svg)](https://www.npmjs.com/package/pptx-angular-viewer)
4
+ [![license](https://img.shields.io/npm/l/pptx-angular-viewer.svg)](https://github.com/ChristopherVR/pptx-viewer/blob/main/LICENSE)
7
5
 
8
- Parsing is powered by the framework-agnostic `pptx-viewer-core` engine
9
- (OpenXML a structured slide model); this package is the Angular rendering
10
- layer on top of it.
6
+ Show Microsoft PowerPoint (`.pptx`) presentations directly in an Angular app:
7
+ no server, no conversion step, no PowerPoint install required. Drop in a
8
+ `<pptx-viewer>` component, hand it the file's bytes, and it reads and displays
9
+ the slides as real HTML and CSS, with slide navigation and zoom.
10
+
11
+ ![PowerPoint editor UI rendered in the browser](https://raw.githubusercontent.com/ChristopherVR/pptx-viewer/main/.github/assets/editor.png)
12
+
13
+ > The screenshot shows the full-featured **React** editor. This Angular package
14
+ > is at a **read-only viewer** milestone today; see [Limitations](#limitations).
15
+
16
+ The reading is done by the framework-agnostic [`pptx-viewer-core`](https://www.npmjs.com/package/pptx-viewer-core) engine (also published as [`@christophervr/pptx-viewer`](https://www.npmjs.com/package/@christophervr/pptx-viewer) -- the two names are identical releases), which turns a `.pptx` file into a structured slide model. This package is the Angular layer that draws that model on screen, and the engine is **bundled in**, so you install just one package.
17
+
18
+ <samp>**[▶️ Try the live demo](https://christophervr.github.io/pptx-viewer/demo-angular/)** · **[📦 npm](https://www.npmjs.com/package/pptx-angular-viewer)** · **[📖 Full docs](https://christophervr.github.io/pptx-viewer/)**</samp>
19
+
20
+ > **[▶️ Try the live demo](https://christophervr.github.io/pptx-viewer/demo-angular/)**: open a `.pptx` and render it in your browser, no install required.
11
21
 
12
22
  ## Features
13
23
 
14
- - **Standalone component** `<pptx-viewer>`, no NgModule required.
15
- - **Signals-based & zoneless-friendly** `OnPush` everywhere, built on the
16
- Angular signals API.
17
- - **CSS rendering** slides are real DOM (scaled HTML/SVG), so text stays sharp
18
- at any zoom and is selectable/accessible.
19
- - **Slide navigation** — thumbnail rail, prev/next, slide counter.
20
- - **Zoom** in/out/reset.
21
- - **Themeable** semantic color tokens via CSS custom properties.
22
- - **Loads from anywhere** — `ArrayBuffer` / `Uint8Array` from a file input,
23
- `fetch`, drag-and-drop, etc.
24
+ - **Standalone component**: `<pptx-viewer>`, no NgModule needed.
25
+ - **Modern Angular**: built on signals and `OnPush`, and works without Zone.js.
26
+ - **Real HTML rendering**: slides are drawn as ordinary HTML and SVG, not as a
27
+ picture, so text stays sharp at any zoom and is selectable and accessible.
28
+ - **Slide navigation**: a thumbnail rail, previous/next, and a slide counter.
29
+ - **Zoom**: in, out, and reset.
30
+ - **Themeable**: change colours through CSS custom properties.
31
+ - **Loads from anywhere**: an `ArrayBuffer` or `Uint8Array` from a file input,
32
+ a `fetch`, drag-and-drop, and so on.
24
33
 
25
34
  ## Installation
26
35
 
27
36
  ```bash
28
- npm install pptx-angular-viewer pptx-viewer-core
37
+ npm install pptx-angular-viewer
29
38
  ```
30
39
 
31
- **Peer requirements:** Angular 20+ (`@angular/core`, `@angular/common`),
32
- `rxjs`, and `pptx-viewer-core`.
40
+ **Peer requirements:** Angular 22+ (`@angular/core`, `@angular/common`) and `rxjs`.
33
41
 
34
42
  ## Usage
35
43
 
@@ -98,7 +106,7 @@ bootstrapApplication(AppComponent, {
98
106
 
99
107
  ### Reading the current presentation back
100
108
 
101
- `getContent()` serialises the in-memory presentation to `.pptx` bytes. Reach it
109
+ `getContent()` turns the current presentation back into `.pptx` bytes. Reach it
102
110
  via a template reference or `viewChild`:
103
111
 
104
112
  ```ts
@@ -117,10 +125,10 @@ async save() {
117
125
  | Input | Type | Default | Description |
118
126
  | --------------- | ----------------------------------- | ------- | ----------------------------------------------------------- |
119
127
  | `content` | `Uint8Array \| ArrayBuffer \| null` | `null` | The `.pptx` bytes to render. |
120
- | `theme` | `ViewerTheme` | | Color/radius overrides applied as CSS custom properties. |
128
+ | `theme` | `ViewerTheme` | n/a | Color/radius overrides applied as CSS custom properties. |
121
129
  | `class` | `string` | `''` | Class applied to the root element. |
122
130
  | `canEdit` | `boolean` | `false` | Reserved for the editor (not yet implemented). |
123
- | `collaboration` | `CollaborationConfig` | | Reserved for real-time collaboration (not yet implemented). |
131
+ | `collaboration` | `CollaborationConfig` | n/a | Reserved for real-time collaboration (not yet implemented). |
124
132
 
125
133
  ### Outputs
126
134
 
@@ -144,59 +152,22 @@ async save() {
144
152
 
145
153
  ## Limitations
146
154
 
147
- This package is at a **read-only viewer** milestone. Today it renders the
148
- structural content of a slide; rich visual effects and editing are not yet wired
149
- up.
150
-
151
- **Element coverage**
152
-
153
- - Rendered: text (rich runs bold/italic/underline/strike/color/size),
154
- shapes (solid fill, stroke, basic preset corners), pictures/images, media
155
- poster frames, and nested groups.
156
- - ⚠️ Shown as labelled placeholders: tables, charts, SmartArt, connectors, ink,
157
- OLE objects, 3D models, and zoom links.
158
-
159
- **Rendering fidelity (current gaps)**
160
-
161
- - Shape fills are solid-color only gradients, pattern, and picture fills are
162
- not yet applied.
163
- - No custom-geometry clip-paths; only a few common preset shapes get rounded/
164
- elliptical corners.
165
- - No shadows, glow, reflection, soft-edge, 3D bevels, or image effects.
166
- - No text warp / WordArt, and equations (OMML) are not rendered.
167
- - Slide background images render; advanced background fills are simplified.
168
- - Fonts use whatever is available in the browser; embedded-font injection is not
169
- yet wired up, which can affect text metrics.
170
-
171
- **Playback & interaction**
172
-
173
- - Media shows the poster frame only — audio/video playback is not implemented.
174
- - No animations, slide transitions, or presentation mode yet.
175
- - The viewer is read-only: no selection, editing, toolbar, or inspector.
176
-
177
- **Export**
178
-
179
- - No image/PDF/GIF/video export yet.
180
-
181
- If you need any of the above today, the underlying `pptx-viewer-core` engine
182
- already parses most of this data — you can read it from the parsed model even
183
- where this UI layer doesn't render it yet.
184
-
185
- ## Roadmap
186
-
187
- Actively being worked on, roughly in priority order:
188
-
189
- 1. **Richer rendering** — gradient/pattern fills, preset-geometry clip-paths,
190
- then tables, connectors, and charts.
191
- 2. **Effects** — shadows, glow, image effects, and 3D styling.
192
- 3. **Editing** — selection, transform, and an editor chrome (toolbar/inspector),
193
- unlocking `canEdit`, `dirtyChange`, and `contentChange`.
194
- 4. **Animations, transitions, and presentation mode.**
195
- 5. **Export** — PNG/PDF/GIF/video.
196
- 6. **Real-time collaboration** (the `collaboration` input).
197
- 7. **Font embedding/injection** for higher text fidelity.
198
-
199
- Progress and design notes live in [`PORTING.md`](./PORTING.md).
155
+ This package is at a **read-only viewer** milestone: it shows the content of a
156
+ slide, but richer visual effects and editing are not built yet.
157
+
158
+ - **Shown today:** text (with mixed formatting), shapes (solid fill, outline, and
159
+ basic rounded corners), pictures and images, the still frame of a video, and
160
+ nested groups.
161
+ - **Shown as placeholders:** tables, charts, SmartArt, connectors, ink, OLE
162
+ objects, 3D models, and zoom links appear as labelled boxes for now.
163
+ - **Not built yet:** gradient, pattern, and image fills; custom shape outlines;
164
+ effects (shadows, glow, 3D, image filters); warped text and equations;
165
+ embedded fonts; video and audio playback; animations, transitions, and
166
+ presentation mode; editing (selecting, the toolbar, the inspector); and export.
167
+
168
+ The `pptx-viewer-core` engine already reads most of this data, so you can get it
169
+ from the parsed model even where this UI does not draw it yet. Progress, the
170
+ roadmap, and design notes live in [`PORTING.md`](./PORTING.md).
200
171
 
201
172
  ## Build (contributing)
202
173
 
@@ -0,0 +1,358 @@
1
+ import { CanvasTexture, SRGBColorSpace, LinearFilter, Shape, ExtrudeGeometry, MeshStandardMaterial, Color, Mesh, EdgesGeometry, LineBasicMaterial, LineSegments, PlaneGeometry, MeshBasicMaterial, Euler, Vector3, BufferGeometry, Line, Group, WebGLRenderer, Scene, PerspectiveCamera, AmbientLight, DirectionalLight } from 'three';
2
+ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
3
+
4
+ /**
5
+ * Three.js SmartArt renderer - front-face text textures.
6
+ *
7
+ * Renders a node label onto an offscreen 2D canvas and wraps it as a
8
+ * `THREE.CanvasTexture`, sized to sit on the extruded block's front face. Text
9
+ * is word-wrapped and auto-shrunk to fit the node footprint. Returns `null` in
10
+ * non-DOM environments (SSR / unit tests).
11
+ */
12
+ /** Supersampling factor for crisp text at oblique camera angles. */
13
+ const SUPERSAMPLE = 4;
14
+ /** Fraction of the footprint the text plane occupies (inset padding). */
15
+ const FILL = 0.86;
16
+ /** Greedily word-wrap `text` to lines that fit `maxWidth` at the given font. */
17
+ function wrapLines(ctx, text, maxWidth) {
18
+ const words = text.split(/\s+/u).filter(Boolean);
19
+ if (words.length === 0) {
20
+ return [];
21
+ }
22
+ const lines = [];
23
+ let line = words[0];
24
+ for (let i = 1; i < words.length; i++) {
25
+ const candidate = `${line} ${words[i]}`;
26
+ if (ctx.measureText(candidate).width <= maxWidth) {
27
+ line = candidate;
28
+ }
29
+ else {
30
+ lines.push(line);
31
+ line = words[i];
32
+ }
33
+ }
34
+ lines.push(line);
35
+ return lines;
36
+ }
37
+ /**
38
+ * Build a label texture for a node's front face.
39
+ *
40
+ * @param text Label text.
41
+ * @param color CSS colour for the text.
42
+ * @param fontSize Requested font size in layout pixels.
43
+ * @param footW Node footprint width (layout pixels).
44
+ * @param footH Node footprint height (layout pixels).
45
+ * @returns The texture + plane size, or `null` when no DOM / empty text.
46
+ */
47
+ function makeTextTexture(text, color, fontSize, footW, footH) {
48
+ if (typeof document === 'undefined' || !text.trim() || footW <= 0 || footH <= 0) {
49
+ return null;
50
+ }
51
+ const worldWidth = footW * FILL;
52
+ const worldHeight = footH * FILL;
53
+ const canvas = document.createElement('canvas');
54
+ canvas.width = Math.max(8, Math.round(worldWidth * SUPERSAMPLE));
55
+ canvas.height = Math.max(8, Math.round(worldHeight * SUPERSAMPLE));
56
+ const ctx = canvas.getContext('2d');
57
+ if (!ctx) {
58
+ return null;
59
+ }
60
+ const family = 'system-ui, -apple-system, Segoe UI, Roboto, sans-serif';
61
+ const maxTextWidth = canvas.width * 0.94;
62
+ // Shrink the font until the wrapped block fits the canvas height.
63
+ let px = Math.max(6, fontSize) * SUPERSAMPLE;
64
+ let lines = [];
65
+ for (let attempt = 0; attempt < 12; attempt++) {
66
+ ctx.font = `600 ${px}px ${family}`;
67
+ lines = wrapLines(ctx, text, maxTextWidth);
68
+ const lineHeight = px * 1.2;
69
+ if (lines.length * lineHeight <= canvas.height * 0.96 || px <= 6 * SUPERSAMPLE) {
70
+ break;
71
+ }
72
+ px *= 0.88;
73
+ }
74
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
75
+ ctx.fillStyle = color;
76
+ ctx.textAlign = 'center';
77
+ ctx.textBaseline = 'middle';
78
+ ctx.font = `600 ${px}px ${family}`;
79
+ const lineHeight = px * 1.2;
80
+ const blockHeight = lines.length * lineHeight;
81
+ const startY = canvas.height / 2 - blockHeight / 2 + lineHeight / 2;
82
+ for (let i = 0; i < lines.length; i++) {
83
+ ctx.fillText(lines[i], canvas.width / 2, startY + i * lineHeight);
84
+ }
85
+ const texture = new CanvasTexture(canvas);
86
+ texture.colorSpace = SRGBColorSpace;
87
+ texture.minFilter = LinearFilter;
88
+ texture.magFilter = LinearFilter;
89
+ texture.needsUpdate = true;
90
+ return { texture, worldWidth, worldHeight };
91
+ }
92
+
93
+ /**
94
+ * Three.js SmartArt renderer - mesh-group construction.
95
+ *
96
+ * Turns a pure {@link SmartArt3DModel} into a `THREE.Group` of extruded blocks
97
+ * (with bevels, edge outlines, and front-face text planes) plus connector
98
+ * lines. All allocated GPU resources are tracked so the caller can dispose them
99
+ * deterministically.
100
+ */
101
+ /** Build the extruded geometry for one node. */
102
+ function extrudeGeometry(m) {
103
+ const shape = new Shape();
104
+ m.outline.forEach((p, i) => {
105
+ if (i === 0) {
106
+ shape.moveTo(p.x, p.y);
107
+ }
108
+ else {
109
+ shape.lineTo(p.x, p.y);
110
+ }
111
+ });
112
+ shape.closePath();
113
+ const bevelEnabled = m.bevel > 0;
114
+ return new ExtrudeGeometry(shape, {
115
+ depth: m.depth,
116
+ bevelEnabled,
117
+ bevelThickness: m.bevel,
118
+ bevelSize: m.bevel,
119
+ bevelSegments: 2,
120
+ curveSegments: m.rounded ? 24 : 1,
121
+ steps: 1,
122
+ });
123
+ }
124
+ /** Add the extruded block + edge outline for one node to the group. */
125
+ function addBlock(group, disposables, m) {
126
+ const geo = extrudeGeometry(m);
127
+ const material = new MeshStandardMaterial({
128
+ color: new Color(m.fill),
129
+ metalness: 0.12,
130
+ roughness: 0.52,
131
+ transparent: m.opacity < 1,
132
+ opacity: m.opacity,
133
+ });
134
+ const mesh = new Mesh(geo, material);
135
+ mesh.position.set(m.position.x, m.position.y, m.position.z);
136
+ mesh.rotation.set(m.rotation.x, m.rotation.y, m.rotation.z);
137
+ group.add(mesh);
138
+ disposables.push(geo, material);
139
+ if (m.strokeWidth > 0) {
140
+ const edges = new EdgesGeometry(geo, 30);
141
+ const lineMaterial = new LineBasicMaterial({ color: new Color(m.stroke) });
142
+ const line = new LineSegments(edges, lineMaterial);
143
+ line.position.copy(mesh.position);
144
+ line.rotation.copy(mesh.rotation);
145
+ group.add(line);
146
+ disposables.push(edges, lineMaterial);
147
+ }
148
+ return geo;
149
+ }
150
+ /** Add a front-face text plane for one node, if it has a label. */
151
+ function addLabel(group, disposables, m) {
152
+ if (!m.text) {
153
+ return;
154
+ }
155
+ const tex = makeTextTexture(m.text, m.textColor, m.fontSize, m.halfWidth * 2, m.halfHeight * 2);
156
+ if (!tex) {
157
+ return;
158
+ }
159
+ const planeGeo = new PlaneGeometry(tex.worldWidth, tex.worldHeight);
160
+ const planeMaterial = new MeshBasicMaterial({
161
+ map: tex.texture,
162
+ transparent: true,
163
+ depthWrite: false,
164
+ });
165
+ const plane = new Mesh(planeGeo, planeMaterial);
166
+ // Float just past the front (+z) face, clearing any bevel, following the
167
+ // mesh's rotation so the label sits flat on the (possibly rotated) face.
168
+ const euler = new Euler(m.rotation.x, m.rotation.y, m.rotation.z);
169
+ const offset = new Vector3(0, 0, m.depth + m.bevel + 0.4).applyEuler(euler);
170
+ plane.position.set(m.position.x + offset.x, m.position.y + offset.y, m.position.z + offset.z);
171
+ plane.rotation.copy(euler);
172
+ group.add(plane);
173
+ disposables.push(planeGeo, planeMaterial, tex.texture);
174
+ }
175
+ /** Add a connector poly-line on the base plane. */
176
+ function addConnectors(group, disposables, model) {
177
+ for (const c of model.connectors) {
178
+ if (c.points.length < 2) {
179
+ continue;
180
+ }
181
+ const geo = new BufferGeometry().setFromPoints(c.points.map((p) => new Vector3(p.x, p.y, p.z)));
182
+ const material = new LineBasicMaterial({
183
+ color: new Color(c.color),
184
+ transparent: true,
185
+ opacity: 0.7,
186
+ });
187
+ group.add(new Line(geo, material));
188
+ disposables.push(geo, material);
189
+ }
190
+ }
191
+ /**
192
+ * Build a `THREE.Group` for a SmartArt 3D model. The group is centred on the
193
+ * origin (the model positions already are); callers add it to the scene.
194
+ */
195
+ function buildMeshGroup(model) {
196
+ const group = new Group();
197
+ const disposables = [];
198
+ for (const m of model.meshes) {
199
+ addBlock(group, disposables, m);
200
+ addLabel(group, disposables, m);
201
+ }
202
+ addConnectors(group, disposables, model);
203
+ return {
204
+ group,
205
+ dispose() {
206
+ for (const d of disposables) {
207
+ d.dispose();
208
+ }
209
+ },
210
+ };
211
+ }
212
+
213
+ /**
214
+ * Three.js SmartArt renderer - vanilla scene runtime.
215
+ *
216
+ * Frames a {@link SmartArt3DModel} in a WebGL scene (lights, perspective
217
+ * camera, optional OrbitControls, render loop) on a caller-provided canvas.
218
+ * Pure vanilla three.js - no framework code - so the React, Vue, and Angular
219
+ * bindings all mount it through a thin canvas wrapper. `three` is imported here
220
+ * only; this module lives behind the `pptx-viewer-shared/smartart-3d` subpath so
221
+ * it is lazily loaded and `three` stays an optional dependency.
222
+ */
223
+ const FOV = 42;
224
+ /** Bounding sphere of all meshes (expanded by footprint/depth) + connectors. */
225
+ function contentSphere(model) {
226
+ let minX = Infinity;
227
+ let minY = Infinity;
228
+ let minZ = Infinity;
229
+ let maxX = -Infinity;
230
+ let maxY = -Infinity;
231
+ let maxZ = -Infinity;
232
+ const expand = (x, y, z, r) => {
233
+ minX = Math.min(minX, x - r);
234
+ minY = Math.min(minY, y - r);
235
+ minZ = Math.min(minZ, z - r);
236
+ maxX = Math.max(maxX, x + r);
237
+ maxY = Math.max(maxY, y + r);
238
+ maxZ = Math.max(maxZ, z + r);
239
+ };
240
+ for (const m of model.meshes) {
241
+ const r = Math.max(m.halfWidth, m.halfHeight) + m.depth + m.bevel;
242
+ expand(m.position.x, m.position.y, m.position.z, r);
243
+ }
244
+ for (const c of model.connectors) {
245
+ for (const p of c.points) {
246
+ expand(p.x, p.y, p.z, 1);
247
+ }
248
+ }
249
+ if (!Number.isFinite(minX)) {
250
+ const fallback = Math.max(model.bounds.width, model.bounds.height) / 2 || 1;
251
+ return { cx: 0, cy: 0, cz: 0, radius: fallback };
252
+ }
253
+ return {
254
+ cx: (minX + maxX) / 2,
255
+ cy: (minY + maxY) / 2,
256
+ cz: (minZ + maxZ) / 2,
257
+ radius: 0.5 * Math.hypot(maxX - minX, maxY - minY, maxZ - minZ) || 1,
258
+ };
259
+ }
260
+ /** Camera distance that frames a bounding sphere of `radius` at the given FOV. */
261
+ function frameDistance(radius, aspect) {
262
+ const vFov = (FOV * Math.PI) / 180;
263
+ const hFov = 2 * Math.atan(Math.tan(vFov / 2) * aspect);
264
+ const minFov = Math.min(vFov, hFov);
265
+ return (radius / Math.sin(minFov / 2)) * 1.1;
266
+ }
267
+ /**
268
+ * Mount a SmartArt 3D model onto a canvas and start rendering.
269
+ *
270
+ * @returns a handle for resizing, toggling interactivity, and disposal.
271
+ */
272
+ function mountSmartArt3D(canvas, model, width, height, options = {}) {
273
+ const renderer = new WebGLRenderer({ canvas, antialias: true, alpha: !options.background });
274
+ renderer.setPixelRatio(Math.min(typeof window === 'undefined' ? 1 : window.devicePixelRatio || 1, options.maxPixelRatio ?? 2));
275
+ renderer.setSize(width, height, false);
276
+ const scene = new Scene();
277
+ if (options.background) {
278
+ scene.background = new Color(options.background);
279
+ }
280
+ const { cx, cy, cz, radius } = contentSphere(model);
281
+ const aspect = width / Math.max(1, height);
282
+ const dist = frameDistance(radius, aspect);
283
+ const camera = new PerspectiveCamera(FOV, aspect, 0.1, dist * 8 + radius * 4);
284
+ // A slight elevation + offset gives the extrusion/spatial depth a readable
285
+ // three-quarter presence, framing the content's own centroid.
286
+ camera.position.set(cx + radius * 0.25, cy + radius * 0.3, cz + dist);
287
+ camera.lookAt(cx, cy, cz);
288
+ scene.add(new AmbientLight(0xffffff, 0.62));
289
+ const key = new DirectionalLight(0xffffff, 0.95);
290
+ key.position.set(cx + radius, cy + radius * 1.4, cz + dist);
291
+ scene.add(key);
292
+ const fill = new DirectionalLight(0xffffff, 0.3);
293
+ fill.position.set(cx - radius, cy - radius * 0.6, cz + dist * 0.6);
294
+ scene.add(fill);
295
+ const built = buildMeshGroup(model);
296
+ scene.add(built.group);
297
+ let controls = null;
298
+ const enableControls = (on) => {
299
+ if (on && !controls) {
300
+ controls = new OrbitControls(camera, canvas);
301
+ controls.enablePan = false;
302
+ controls.target.set(cx, cy, cz);
303
+ controls.minDistance = dist * 0.4;
304
+ controls.maxDistance = dist * 3;
305
+ controls.update();
306
+ }
307
+ else if (!on && controls) {
308
+ controls.dispose();
309
+ controls = null;
310
+ }
311
+ if (controls) {
312
+ controls.autoRotate = Boolean(options.autoRotate);
313
+ controls.autoRotateSpeed = 1.2;
314
+ }
315
+ };
316
+ enableControls(Boolean(options.interactive));
317
+ let frame = 0;
318
+ let disposed = false;
319
+ const renderLoop = () => {
320
+ if (disposed) {
321
+ return;
322
+ }
323
+ frame = requestAnimationFrame(renderLoop);
324
+ controls?.update();
325
+ renderer.render(scene, camera);
326
+ };
327
+ frame = requestAnimationFrame(renderLoop);
328
+ return {
329
+ resize(w, h) {
330
+ camera.aspect = w / Math.max(1, h);
331
+ camera.updateProjectionMatrix();
332
+ renderer.setSize(w, h, false);
333
+ },
334
+ setInteractive(on) {
335
+ enableControls(on);
336
+ },
337
+ dispose() {
338
+ disposed = true;
339
+ cancelAnimationFrame(frame);
340
+ controls?.dispose();
341
+ built.dispose();
342
+ renderer.dispose();
343
+ },
344
+ };
345
+ }
346
+
347
+ /**
348
+ * `pptx-viewer-shared/smartart-3d` - vanilla three.js SmartArt scene runtime.
349
+ *
350
+ * Lazily imported by each binding's SmartArt 3D wrapper so `three` stays an
351
+ * optional dependency: when it is not installed the dynamic import rejects and
352
+ * the binding falls back to the SVG `SmartArtRenderer`. The pure model builder
353
+ * (`buildSmartArt3DModel`) and its types live in the main barrel
354
+ * (`pptx-viewer-shared`) and should be imported from there directly.
355
+ */
356
+
357
+ export { mountSmartArt3D };
358
+ //# sourceMappingURL=pptx-angular-viewer-index-BfKeZbQy.mjs.map