pptx-react-viewer 1.0.0 → 1.0.7
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/LICENSE +21 -0
- package/NOTICE +16 -0
- package/README.md +8 -208
- package/node_modules/emf-converter/LICENSE +21 -0
- package/node_modules/emf-converter/README.md +9 -260
- package/node_modules/emf-converter/package.json +12 -1
- package/node_modules/mtx-decompressor/LICENSE +373 -0
- package/node_modules/mtx-decompressor/README.md +1 -1
- package/node_modules/mtx-decompressor/package.json +8 -1
- package/node_modules/pptx-viewer-core/LICENSE +21 -0
- package/node_modules/pptx-viewer-core/NOTICE +16 -0
- package/node_modules/pptx-viewer-core/README.md +9 -267
- package/node_modules/pptx-viewer-core/dist/cli/index.mjs +0 -0
- package/node_modules/pptx-viewer-core/package.json +13 -1
- package/package.json +16 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present pptx-viewer contributors
|
|
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.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
pptx-viewer
|
|
2
|
+
Copyright (c) 2025-present pptx-viewer contributors
|
|
3
|
+
|
|
4
|
+
This package is licensed under the MIT License. See the LICENSE file for details.
|
|
5
|
+
|
|
6
|
+
This package bundles the following component under a different license:
|
|
7
|
+
|
|
8
|
+
mtx-decompressor — MicroType Express (MTX) font decompressor
|
|
9
|
+
License: Mozilla Public License 2.0 (MPL-2.0)
|
|
10
|
+
Source: https://github.com/ChristopherVR/pptx-viewer/tree/main/packages/mtx-decompressor
|
|
11
|
+
Origin: Ported from libeot (https://github.com/nicowilliams/libeot)
|
|
12
|
+
by Brennan T. Vincent, licensed under MPL-2.0.
|
|
13
|
+
|
|
14
|
+
Under MPL-2.0, the source code of the mtx-decompressor component remains
|
|
15
|
+
available under the terms of the Mozilla Public License 2.0. The full text
|
|
16
|
+
of the MPL-2.0 can be found at: https://mozilla.org/MPL/2.0/
|
package/README.md
CHANGED
|
@@ -296,123 +296,17 @@ import {
|
|
|
296
296
|
|
|
297
297
|
### High-Level Component Tree
|
|
298
298
|
|
|
299
|
-
|
|
300
|
-
flowchart TB
|
|
301
|
-
PPV["PowerPointViewer<br/>(root orchestrator)"]
|
|
302
|
-
|
|
303
|
-
PPV --> VTS["ViewerToolbarSection<br/>Toolbar + ModeSwitcher"]
|
|
304
|
-
PPV --> VMC["ViewerMainContent<br/>Canvas + sidebars + inspector"]
|
|
305
|
-
PPV --> VBP["ViewerBottomPanels<br/>Notes + status bar"]
|
|
306
|
-
PPV --> VDG["ViewerDialogGroup<br/>All modal dialogs"]
|
|
307
|
-
PPV --> VO["ViewerOverlays<br/>Shortcuts, accessibility, slide sorter"]
|
|
308
|
-
PPV --> VPL["ViewerPresentationLayer<br/>Fullscreen slideshow"]
|
|
309
|
-
|
|
310
|
-
VMC --> VSP["ViewerSidePanels<br/>Slides pane + master pane"]
|
|
311
|
-
VMC --> VCA["ViewerCanvasArea<br/>SlideCanvas + overlays"]
|
|
312
|
-
VMC --> VI["ViewerInspector<br/>Properties panel"]
|
|
313
|
-
|
|
314
|
-
VCA --> SC["SlideCanvas<br/>Main slide rendering"]
|
|
315
|
-
SC --> ER["ElementRenderer<br/>Per-element dispatch"]
|
|
316
|
-
ER --> SH["Shape / Text"]
|
|
317
|
-
ER --> IM["Image"]
|
|
318
|
-
ER --> TB["Table"]
|
|
319
|
-
ER --> CH["Chart (SVG)"]
|
|
320
|
-
ER --> CN["Connector"]
|
|
321
|
-
ER --> SA["SmartArt"]
|
|
322
|
-
ER --> M3["3D Model"]
|
|
323
|
-
ER --> MD["Media (audio/video)"]
|
|
324
|
-
ER --> IK["Ink"]
|
|
325
|
-
```
|
|
299
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/react/README.md) for visual representations._
|
|
326
300
|
|
|
327
301
|
### Hook Composition
|
|
328
302
|
|
|
329
303
|
The viewer's logic is decomposed into 67+ custom hooks, composed in `PowerPointViewer.tsx`:
|
|
330
304
|
|
|
331
|
-
|
|
332
|
-
flowchart TD
|
|
333
|
-
subgraph "Core State"
|
|
334
|
-
VS["useViewerState<br/>All mutable state"]
|
|
335
|
-
VCS["useViewerCoreState<br/>Slides, selection, canvas"]
|
|
336
|
-
VUI["useViewerUIState<br/>Panels, dialogs, UI flags"]
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
subgraph "Derived State"
|
|
340
|
-
DSS["useDerivedSlideState<br/>Visible indexes, sections"]
|
|
341
|
-
DES["useDerivedElementState<br/>Selected element computed props"]
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
subgraph "Core Operations"
|
|
345
|
-
EH["useEditorHistory<br/>Undo/redo snapshot stack"]
|
|
346
|
-
ZV["useZoomViewport<br/>Zoom level + viewport"]
|
|
347
|
-
EO["useEditorOperations<br/>Compose all editor ops"]
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
subgraph "Feature Hooks"
|
|
351
|
-
LC["useLoadContent<br/>Parse PPTX on mount"]
|
|
352
|
-
PM["usePresentationMode<br/>Slideshow navigation"]
|
|
353
|
-
PA["usePresentationAnnotations<br/>Pen/highlighter tools"]
|
|
354
|
-
IE["useInsertElements<br/>Shape/image/table insertion"]
|
|
355
|
-
EM["useElementManipulation<br/>Move/resize/delete"]
|
|
356
|
-
PH["usePointerHandlers<br/>Mouse/touch events"]
|
|
357
|
-
KS["useKeyboardShortcuts<br/>Hotkey bindings"]
|
|
358
|
-
EX["useExportHandlers<br/>PNG/SVG/PDF/video export"]
|
|
359
|
-
PR["usePrintHandlers<br/>Print dialog"]
|
|
360
|
-
SM["useSlideManagement<br/>Add/delete/reorder slides"]
|
|
361
|
-
TO["useTableOperations<br/>Row/column/cell ops"]
|
|
362
|
-
FR["useFindReplace<br/>Text search"]
|
|
363
|
-
CM["useComments<br/>Comment management"]
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
subgraph "Collaboration"
|
|
367
|
-
YJS["useYjsProvider<br/>WebSocket lifecycle"]
|
|
368
|
-
PT["usePresenceTracking<br/>Remote cursors"]
|
|
369
|
-
CS["useCollaborativeState<br/>CRDT shared state"]
|
|
370
|
-
CH2["useCollaborativeHistory<br/>Collab undo/redo"]
|
|
371
|
-
end
|
|
372
|
-
|
|
373
|
-
VS --> VCS
|
|
374
|
-
VS --> VUI
|
|
375
|
-
VCS --> DSS
|
|
376
|
-
VS --> EH
|
|
377
|
-
VS --> ZV
|
|
378
|
-
EH --> EO
|
|
379
|
-
ZV --> EO
|
|
380
|
-
EO --> IE
|
|
381
|
-
EO --> EM
|
|
382
|
-
EO --> PH
|
|
383
|
-
EO --> SM
|
|
384
|
-
EO --> TO
|
|
385
|
-
EO --> FR
|
|
386
|
-
EO --> CM
|
|
387
|
-
```
|
|
305
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/react/README.md) for visual representations._
|
|
388
306
|
|
|
389
307
|
### Data Flow
|
|
390
308
|
|
|
391
|
-
|
|
392
|
-
sequenceDiagram
|
|
393
|
-
participant P as Parent App
|
|
394
|
-
participant V as PowerPointViewer
|
|
395
|
-
participant H as PptxHandler (core)
|
|
396
|
-
participant S as State (hooks)
|
|
397
|
-
participant C as SlideCanvas
|
|
398
|
-
|
|
399
|
-
P->>V: content={arrayBuffer}
|
|
400
|
-
V->>S: useViewerState({ content })
|
|
401
|
-
S->>H: handler.load(buffer)
|
|
402
|
-
H-->>S: PptxData { slides, theme, ... }
|
|
403
|
-
S-->>V: { slides, activeSlide, canvasSize, ... }
|
|
404
|
-
V->>C: Render active slide
|
|
405
|
-
C->>C: Map elements -> React components
|
|
406
|
-
|
|
407
|
-
Note over C: User edits element
|
|
408
|
-
C->>S: setState(updatedSlides)
|
|
409
|
-
S->>S: Push undo snapshot
|
|
410
|
-
S-->>V: Re-render with updated state
|
|
411
|
-
|
|
412
|
-
Note over V: User saves
|
|
413
|
-
V->>H: handler.save(slides)
|
|
414
|
-
H-->>P: Uint8Array via onContentChange
|
|
415
|
-
```
|
|
309
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/react/README.md) for visual representations._
|
|
416
310
|
|
|
417
311
|
### Rendering Pipeline
|
|
418
312
|
|
|
@@ -423,37 +317,7 @@ Slides are rendered using **CSS positioning and transforms**, not HTML Canvas. T
|
|
|
423
317
|
- DOM-based interaction (click, drag, resize)
|
|
424
318
|
- Standard CSS effects (shadows, gradients, borders)
|
|
425
319
|
|
|
426
|
-
|
|
427
|
-
flowchart LR
|
|
428
|
-
subgraph "Data Model"
|
|
429
|
-
E["PptxElement<br/>(from core)"]
|
|
430
|
-
end
|
|
431
|
-
|
|
432
|
-
subgraph "React Components"
|
|
433
|
-
ER["ElementRenderer<br/>Type dispatch"]
|
|
434
|
-
EB["ElementBody<br/>Visual rendering"]
|
|
435
|
-
RH["ResizeHandles<br/>Selection UI"]
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
subgraph "CSS Output"
|
|
439
|
-
POS["position: absolute<br/>left/top/width/height"]
|
|
440
|
-
ROT["transform: rotate()"]
|
|
441
|
-
FIL["background: fill style"]
|
|
442
|
-
BOR["border: stroke style"]
|
|
443
|
-
SHD["box-shadow: effects"]
|
|
444
|
-
TXT["text styling (font, color, align)"]
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
E --> ER
|
|
448
|
-
ER --> EB
|
|
449
|
-
ER --> RH
|
|
450
|
-
EB --> POS
|
|
451
|
-
EB --> ROT
|
|
452
|
-
EB --> FIL
|
|
453
|
-
EB --> BOR
|
|
454
|
-
EB --> SHD
|
|
455
|
-
EB --> TXT
|
|
456
|
-
```
|
|
320
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/react/README.md) for visual representations._
|
|
457
321
|
|
|
458
322
|
---
|
|
459
323
|
|
|
@@ -601,27 +465,7 @@ The right-side inspector (`InspectorPane`) displays contextual property editors:
|
|
|
601
465
|
|
|
602
466
|
Activated by setting `mode` to `"present"`. The `ViewerPresentationLayer` takes over with:
|
|
603
467
|
|
|
604
|
-
|
|
605
|
-
flowchart TD
|
|
606
|
-
PM["usePresentationMode"] --> SN["useSlideNavigation<br/>Next/prev/goto slide"]
|
|
607
|
-
PM --> AP["useAnimationPlayback<br/>Trigger animations in sequence"]
|
|
608
|
-
PM --> PK["usePresentationKeyboard<br/>Arrow keys, Esc, N-key toggle"]
|
|
609
|
-
PM --> RT["useRehearsalTimings<br/>Practice mode with timer"]
|
|
610
|
-
|
|
611
|
-
subgraph "Rendering"
|
|
612
|
-
ST["Slide Transition<br/>(CSS + framer-motion)"]
|
|
613
|
-
AN["Animation Overlays<br/>(keyframe sequences)"]
|
|
614
|
-
AO["Annotation Overlay<br/>(pen/highlighter SVG)"]
|
|
615
|
-
PV["Presenter View<br/>(dual-screen notes + preview + timer)"]
|
|
616
|
-
TB2["Slideshow Toolbar<br/>(auto-hide on mouse idle)"]
|
|
617
|
-
end
|
|
618
|
-
|
|
619
|
-
SN --> ST
|
|
620
|
-
AP --> AN
|
|
621
|
-
PM --> AO
|
|
622
|
-
PM --> PV
|
|
623
|
-
PM --> TB2
|
|
624
|
-
```
|
|
468
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/react/README.md) for visual representations._
|
|
625
469
|
|
|
626
470
|
**Slide transitions** (`slide-transitions.ts`, `transition-keyframes.ts`):
|
|
627
471
|
|
|
@@ -636,25 +480,7 @@ flowchart TD
|
|
|
636
480
|
|
|
637
481
|
The animation system (`viewer/utils/animation*.ts`, ~14 files) processes OOXML timing trees:
|
|
638
482
|
|
|
639
|
-
|
|
640
|
-
flowchart LR
|
|
641
|
-
A["PptxElementAnimation<br/>(from core)"] --> B["animation-timeline.ts<br/>Build timeline"]
|
|
642
|
-
B --> C["animation-sequencer.ts<br/>Resolve triggers:<br/>onClick, afterPrevious,<br/>withPrevious"]
|
|
643
|
-
C --> D["animation-keyframes.ts<br/>Generate CSS @keyframes"]
|
|
644
|
-
D --> E["animation-effects.ts<br/>Apply to DOM elements"]
|
|
645
|
-
|
|
646
|
-
subgraph "Effect Types"
|
|
647
|
-
F["Entrance<br/>(appear, fly in, fade, zoom)"]
|
|
648
|
-
G["Emphasis<br/>(pulse, spin, grow/shrink, color)"]
|
|
649
|
-
H["Exit<br/>(disappear, fly out, fade)"]
|
|
650
|
-
I["Motion Path<br/>(custom path with auto-rotation)"]
|
|
651
|
-
end
|
|
652
|
-
|
|
653
|
-
D --> F
|
|
654
|
-
D --> G
|
|
655
|
-
D --> H
|
|
656
|
-
D --> I
|
|
657
|
-
```
|
|
483
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/react/README.md) for visual representations._
|
|
658
484
|
|
|
659
485
|
Supports 40+ animation presets with configurable duration, delay, repeat, color animations, motion path auto-rotation, and text-build options (by word, by letter, by paragraph).
|
|
660
486
|
|
|
@@ -685,26 +511,7 @@ Chart chrome (axes, legends, titles, gridlines, data labels, data tables, displa
|
|
|
685
511
|
|
|
686
512
|
The export pipeline (`viewer/utils/export*.ts`, `viewer/hooks/useExportHandlers.ts`):
|
|
687
513
|
|
|
688
|
-
|
|
689
|
-
flowchart TD
|
|
690
|
-
A["Export Request"] --> B{Format?}
|
|
691
|
-
|
|
692
|
-
B -->|PNG/JPEG| C["renderToCanvas (html2canvas)<br/>-> canvas.toDataURL()"]
|
|
693
|
-
B -->|SVG| D["DOM -> SVG serialisation<br/>(export-svg.ts)"]
|
|
694
|
-
B -->|PDF| E["jspdf + renderToCanvas<br/>-> multi-page PDF<br/>(with notes pages + overflow)"]
|
|
695
|
-
B -->|GIF| F["export-gif-encoder.ts<br/>-> animated GIF frames"]
|
|
696
|
-
B -->|Video| G["export-video.ts<br/>-> MediaRecorder API"]
|
|
697
|
-
B -->|PPTX| H["PptxHandler.save()<br/>-> Uint8Array download"]
|
|
698
|
-
B -->|Slides| I["PptxHandler.exportSlides()<br/>-> per-slide PPTX files"]
|
|
699
|
-
|
|
700
|
-
C --> J["Download / Blob URL"]
|
|
701
|
-
D --> J
|
|
702
|
-
E --> J
|
|
703
|
-
F --> J
|
|
704
|
-
G --> J
|
|
705
|
-
H --> J
|
|
706
|
-
I --> J
|
|
707
|
-
```
|
|
514
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/react/README.md) for visual representations._
|
|
708
515
|
|
|
709
516
|
The `renderToCanvas` wrapper (`lib/canvas-export.ts`) patches `html2canvas` to work around oklch colour space parsing issues in browsers that don't fully support it.
|
|
710
517
|
|
|
@@ -712,14 +519,7 @@ The `renderToCanvas` wrapper (`lib/canvas-export.ts`) patches `html2canvas` to w
|
|
|
712
519
|
|
|
713
520
|
Connectors between shapes use a graph-based routing algorithm:
|
|
714
521
|
|
|
715
|
-
|
|
716
|
-
flowchart LR
|
|
717
|
-
A["Start Shape<br/>(connection point)"] --> B["connector-router.ts<br/>A* pathfinding"]
|
|
718
|
-
B --> C["connector-router-graph.ts<br/>Build obstacle grid"]
|
|
719
|
-
C --> D["connector-router-astar.ts<br/>Find shortest path"]
|
|
720
|
-
D --> E["connector-path.tsx<br/>SVG path rendering"]
|
|
721
|
-
E --> F["Arrow markers<br/>(start/end heads)"]
|
|
722
|
-
```
|
|
522
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/react/README.md) for visual representations._
|
|
723
523
|
|
|
724
524
|
The router avoids overlapping shapes by building an obstacle grid and using A\* pathfinding with Manhattan distance heuristics. Supports straight, elbow (bent), and curved connector types.
|
|
725
525
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present pptx-viewer contributors
|
|
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.
|
|
@@ -104,169 +104,25 @@ Converts a WMF binary buffer to a PNG data URL.
|
|
|
104
104
|
|
|
105
105
|
The converter follows a three-phase pipeline: **Parse → Replay → Export**.
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
flowchart LR
|
|
109
|
-
A[/"Binary Buffer<br/>(ArrayBuffer)"/] --> B{Format?}
|
|
110
|
-
B -->|EMF| C[Parse EMF Header]
|
|
111
|
-
B -->|WMF| D[Parse WMF Header]
|
|
112
|
-
C --> E[Create Canvas]
|
|
113
|
-
D --> E
|
|
114
|
-
E --> F{Replay Records}
|
|
115
|
-
F -->|GDI Records| G[GDI Record Handlers]
|
|
116
|
-
F -->|EMF+ Comments| H[EMF+ Record Handlers]
|
|
117
|
-
F -->|WMF Records| I[WMF Record Handlers]
|
|
118
|
-
G --> J[Canvas 2D Context]
|
|
119
|
-
H --> J
|
|
120
|
-
I --> J
|
|
121
|
-
J --> K[Process Deferred Images]
|
|
122
|
-
K --> L[Export to PNG Data URL]
|
|
123
|
-
L --> M[/"data:image/png;base64,..."/]
|
|
124
|
-
```
|
|
107
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/emf-converter/README.md) for visual representations._
|
|
125
108
|
|
|
126
109
|
### Module Map
|
|
127
110
|
|
|
128
111
|
Every source file has a specific responsibility. Here's how they connect:
|
|
129
112
|
|
|
130
|
-
|
|
131
|
-
graph TB
|
|
132
|
-
subgraph "Public API"
|
|
133
|
-
A[emf-converter.ts]
|
|
134
|
-
B[index.ts]
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
subgraph "Header & Setup"
|
|
138
|
-
C[emf-header-parser.ts]
|
|
139
|
-
D[emf-canvas-helpers.ts]
|
|
140
|
-
E[emf-constants.ts]
|
|
141
|
-
F[emf-types.ts]
|
|
142
|
-
G[emf-logging.ts]
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
subgraph "EMF GDI Replay"
|
|
146
|
-
H[emf-record-replay.ts]
|
|
147
|
-
I[emf-gdi-state-handlers.ts]
|
|
148
|
-
J[emf-gdi-draw-handlers.ts]
|
|
149
|
-
K[emf-gdi-poly-path-handlers.ts]
|
|
150
|
-
L[emf-gdi-transform-handlers.ts]
|
|
151
|
-
M[emf-gdi-object-handlers.ts]
|
|
152
|
-
N[emf-gdi-draw-shapes.ts]
|
|
153
|
-
O[emf-gdi-draw-text-bitmap.ts]
|
|
154
|
-
P[emf-gdi-coord.ts]
|
|
155
|
-
Q[emf-gdi-polypolygon-helpers.ts]
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
subgraph "EMF+ GDI+ Replay"
|
|
159
|
-
R[emf-plus-replay.ts]
|
|
160
|
-
S[emf-plus-object-parser.ts]
|
|
161
|
-
T[emf-plus-draw-handlers.ts]
|
|
162
|
-
U[emf-plus-text-image-handlers.ts]
|
|
163
|
-
V[emf-plus-state-handlers.ts]
|
|
164
|
-
W[emf-plus-path.ts]
|
|
165
|
-
X[emf-plus-read-helpers.ts]
|
|
166
|
-
Y[emf-plus-object-complex.ts]
|
|
167
|
-
Z[emf-plus-bitmap-decoder.ts]
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
subgraph "WMF Replay"
|
|
171
|
-
AA[wmf-replay.ts]
|
|
172
|
-
AB[wmf-draw-handlers.ts]
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
subgraph "Bitmap Decoding"
|
|
176
|
-
AC[emf-dib-decoder.ts]
|
|
177
|
-
AD[emf-dib-rle-decoder.ts]
|
|
178
|
-
AE[emf-dib-uncompressed.ts]
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
subgraph "Color Helpers"
|
|
182
|
-
AF[emf-color-helpers.ts]
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
B --> A
|
|
186
|
-
A --> C
|
|
187
|
-
A --> D
|
|
188
|
-
A --> H
|
|
189
|
-
A --> AA
|
|
190
|
-
|
|
191
|
-
H --> I
|
|
192
|
-
H --> J
|
|
193
|
-
H --> K
|
|
194
|
-
H --> R
|
|
195
|
-
|
|
196
|
-
I --> L
|
|
197
|
-
I --> M
|
|
198
|
-
J --> N
|
|
199
|
-
J --> O
|
|
200
|
-
|
|
201
|
-
AA --> AB
|
|
202
|
-
```
|
|
113
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/emf-converter/README.md) for visual representations._
|
|
203
114
|
|
|
204
115
|
### EMF Record Replay Loop
|
|
205
116
|
|
|
206
117
|
The core of the EMF converter is a sequential record-scanning loop that dispatches each record to the appropriate handler:
|
|
207
118
|
|
|
208
|
-
|
|
209
|
-
flowchart TD
|
|
210
|
-
Start([Start Replay]) --> Read["Read record at offset:<br/>type = uint32, size = uint32"]
|
|
211
|
-
Read --> Check{Record Type?}
|
|
212
|
-
|
|
213
|
-
Check -->|EMR_COMMENT| Plus["Check for EMF+ signature<br/>(0x2B464D45 = 'EMF+')"]
|
|
214
|
-
Plus -->|EMF+ found| PlusReplay["replayEmfPlusRecords()<br/>Returns deferred images"]
|
|
215
|
-
Plus -->|Not EMF+| Skip1[Skip record]
|
|
216
|
-
|
|
217
|
-
Check -->|EMR_EOF| Done([End — return deferred images])
|
|
218
|
-
|
|
219
|
-
Check -->|State records| State["handleEmfGdiStateRecord()<br/>SaveDC, RestoreDC, SetTextColor,<br/>SetBkMode, transforms, objects"]
|
|
220
|
-
|
|
221
|
-
Check -->|Draw records| Draw["handleEmfGdiDrawRecord()<br/>Shapes → emf-gdi-draw-shapes<br/>Text/Bitmap → emf-gdi-draw-text-bitmap"]
|
|
222
|
-
|
|
223
|
-
Check -->|Poly/Path records| Poly["handleEmfGdiPolyPathRecord()<br/>Polygon, Polyline, Bezier,<br/>BeginPath, FillPath, StrokePath"]
|
|
224
|
-
|
|
225
|
-
Check -->|Ignored| Skip2["Skip: EMR_HEADER,<br/>EMR_SETBRUSHORGEX, etc."]
|
|
226
|
-
|
|
227
|
-
PlusReplay --> Next
|
|
228
|
-
Skip1 --> Next
|
|
229
|
-
State --> Next
|
|
230
|
-
Draw --> Next
|
|
231
|
-
Poly --> Next
|
|
232
|
-
Skip2 --> Next
|
|
233
|
-
|
|
234
|
-
Next["offset += recSize"] --> Guard{"offset + 8 ≤ bufferLen<br/>AND recordCount < 50,000?"}
|
|
235
|
-
Guard -->|Yes| Read
|
|
236
|
-
Guard -->|No| Done
|
|
237
|
-
```
|
|
119
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/emf-converter/README.md) for visual representations._
|
|
238
120
|
|
|
239
121
|
### EMF+ Dual-Mode Processing
|
|
240
122
|
|
|
241
123
|
EMF files can contain embedded EMF+ records inside `EMR_COMMENT` records. When detected, these are processed by a parallel GDI+ replay engine:
|
|
242
124
|
|
|
243
|
-
|
|
244
|
-
flowchart LR
|
|
245
|
-
subgraph "EMF Record Stream"
|
|
246
|
-
E1[EMR_HEADER]
|
|
247
|
-
E2[EMR_SELECTOBJECT]
|
|
248
|
-
E3["EMR_COMMENT<br/>(EMF+ signature)"]
|
|
249
|
-
E4[EMR_RECTANGLE]
|
|
250
|
-
E5["EMR_COMMENT<br/>(EMF+ signature)"]
|
|
251
|
-
E6[EMR_EOF]
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
subgraph "EMF+ Record Stream (inside comments)"
|
|
255
|
-
P1[EMFPLUS_HEADER]
|
|
256
|
-
P2[EMFPLUS_OBJECT ×N]
|
|
257
|
-
P3[EMFPLUS_FillRects]
|
|
258
|
-
P4[EMFPLUS_DrawPath]
|
|
259
|
-
P5[EMFPLUS_DrawString]
|
|
260
|
-
P6[EMFPLUS_DrawImage]
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
E3 --> P1
|
|
264
|
-
E3 --> P2
|
|
265
|
-
E3 --> P3
|
|
266
|
-
E5 --> P4
|
|
267
|
-
E5 --> P5
|
|
268
|
-
E5 --> P6
|
|
269
|
-
```
|
|
125
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/emf-converter/README.md) for visual representations._
|
|
270
126
|
|
|
271
127
|
The EMF+ state (object table, world transform, save stack) **persists across multiple `EMR_COMMENT` records** within the same file, allowing complex drawings to span several comment blocks.
|
|
272
128
|
|
|
@@ -274,24 +130,7 @@ The EMF+ state (object table, world transform, save stack) **persists across mul
|
|
|
274
130
|
|
|
275
131
|
WMF uses a simpler 16-bit record format with word-aligned record sizes:
|
|
276
132
|
|
|
277
|
-
|
|
278
|
-
flowchart TD
|
|
279
|
-
Start([Parse WMF Header]) --> Magic{"Magic bytes?"}
|
|
280
|
-
Magic -->|0x9AC6CDD7<br/>Aldus Placeable| APM["Read bounds & DPI<br/>from APM header"]
|
|
281
|
-
Magic -->|Standard| Std["Use default bounds<br/>(800×600)"]
|
|
282
|
-
|
|
283
|
-
APM --> Main
|
|
284
|
-
Std --> Main
|
|
285
|
-
|
|
286
|
-
Main["Sequential Record Loop"] --> RecType{"Record Type?"}
|
|
287
|
-
RecType -->|Drawing| WDraw["handleWmfDrawRecord()<br/>MoveTo, LineTo, Rectangle,<br/>Ellipse, Polygon, Text, etc."]
|
|
288
|
-
RecType -->|State| WState["Inline handlers:<br/>SetWindowOrg, SetWindowExt,<br/>SaveDC, RestoreDC, CreatePen,<br/>CreateBrush, CreateFont,<br/>SelectObject, DeleteObject"]
|
|
289
|
-
RecType -->|META_EOF| WDone([Done])
|
|
290
|
-
|
|
291
|
-
WDraw --> Next2[offset += recSize]
|
|
292
|
-
WState --> Next2
|
|
293
|
-
Next2 --> Main
|
|
294
|
-
```
|
|
133
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/emf-converter/README.md) for visual representations._
|
|
295
134
|
|
|
296
135
|
---
|
|
297
136
|
|
|
@@ -368,21 +207,7 @@ The EMF+ replay engine (`emf-plus-replay.ts`) dispatches to:
|
|
|
368
207
|
|
|
369
208
|
The converter manages multiple coordinate mapping systems:
|
|
370
209
|
|
|
371
|
-
|
|
372
|
-
graph TD
|
|
373
|
-
subgraph "EMF GDI Coordinates"
|
|
374
|
-
A["Logical Coordinates<br/>(from metafile)"] -->|"Simple Scale"| B["Canvas Pixels<br/>x = (logX - bounds.left) × sx<br/>y = (logY - bounds.top) × sy"]
|
|
375
|
-
A -->|"Mapping Mode"| C["Window/Viewport Transform<br/>x = ((logX - winOrg.x) / winExt.cx) × vpExt.cx + vpOrg.x"]
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
subgraph "EMF+ GDI+ Coordinates"
|
|
379
|
-
D["GDI+ Logical Units"] -->|"World Transform"| E["Canvas via 6-element<br/>affine matrix [a,b,c,d,e,f]"]
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
subgraph "WMF Coordinates"
|
|
383
|
-
F["WMF Logical Units"] -->|"Window → Canvas"| G["mx(x) = ((x - winOrg.x) / winExt.cx) × canvasW"]
|
|
384
|
-
end
|
|
385
|
-
```
|
|
210
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/emf-converter/README.md) for visual representations._
|
|
386
211
|
|
|
387
212
|
**GDI coordinates** (`emf-gdi-coord.ts`) use either simple bounds-based scaling or full window/viewport mapping mode, activated when the metafile sets `SetWindowExtEx`/`SetViewportExtEx`.
|
|
388
213
|
|
|
@@ -394,34 +219,7 @@ graph TD
|
|
|
394
219
|
|
|
395
220
|
Both GDI and GDI+ maintain their own object tables — essentially registries of reusable drawing resources:
|
|
396
221
|
|
|
397
|
-
|
|
398
|
-
graph LR
|
|
399
|
-
subgraph "GDI Object Table (Map<number, GdiObject>)"
|
|
400
|
-
P1["Slot 0: Pen<br/>style=0 (solid), width=2, color=#000"]
|
|
401
|
-
B1["Slot 1: Brush<br/>style=0 (solid), color=#ff0000"]
|
|
402
|
-
F1["Slot 2: Font<br/>height=12, weight=700, family=Arial"]
|
|
403
|
-
end
|
|
404
|
-
|
|
405
|
-
subgraph "EMF+ Object Table (Map<number, EmfPlusObject>)"
|
|
406
|
-
PB["Slot 0: plus-brush<br/>color=rgba(0,0,255,1)"]
|
|
407
|
-
PP["Slot 1: plus-pen<br/>color=#000, width=1.5"]
|
|
408
|
-
PH["Slot 3: plus-path<br/>points=[...], types=[...]"]
|
|
409
|
-
PI["Slot 5: plus-image<br/>data=ArrayBuffer, type=Bitmap"]
|
|
410
|
-
PF["Slot 6: plus-font<br/>emSize=14, family=Calibri"]
|
|
411
|
-
end
|
|
412
|
-
|
|
413
|
-
CR["CreatePen / CreateBrushIndirect /<br/>ExtCreateFontIndirectW"] --> P1
|
|
414
|
-
CR --> B1
|
|
415
|
-
CR --> F1
|
|
416
|
-
SO["SelectObject(slot)"] -.->|"Applies to state"| Canvas["Canvas Context"]
|
|
417
|
-
|
|
418
|
-
OBJ["EMFPLUS_OBJECT"] --> PB
|
|
419
|
-
OBJ --> PP
|
|
420
|
-
OBJ --> PH
|
|
421
|
-
OBJ --> PI
|
|
422
|
-
OBJ --> PF
|
|
423
|
-
DRAW["FillPath(pathId) /<br/>DrawString(fontId)"] -.->|"References by ID"| Canvas
|
|
424
|
-
```
|
|
222
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/emf-converter/README.md) for visual representations._
|
|
425
223
|
|
|
426
224
|
**GDI objects** are created via `EMR_CREATEPEN`, `EMR_CREATEBRUSHINDIRECT`, `EMR_EXTCREATEFONTINDIRECTW`, etc., and selected into the drawing context with `EMR_SELECTOBJECT`. Stock objects (base index `0x80000000`) provide system defaults like `WHITE_BRUSH`, `BLACK_PEN`, etc.
|
|
427
225
|
|
|
@@ -431,33 +229,7 @@ graph LR
|
|
|
431
229
|
|
|
432
230
|
Metafiles can contain embedded bitmaps as Device-Independent Bitmaps (DIBs). The decoder pipeline handles:
|
|
433
231
|
|
|
434
|
-
|
|
435
|
-
flowchart TD
|
|
436
|
-
A["DIB Header<br/>(40+ bytes BITMAPINFOHEADER)"] --> B{Compression?}
|
|
437
|
-
B -->|BI_RGB| C["emf-dib-uncompressed.ts<br/>Decode raw pixel rows"]
|
|
438
|
-
B -->|BI_RLE8| D["emf-dib-rle-decoder.ts<br/>Run-length decode 8bpp"]
|
|
439
|
-
B -->|BI_RLE4| E["emf-dib-rle-decoder.ts<br/>Run-length decode 4bpp"]
|
|
440
|
-
B -->|BI_BITFIELDS| F["emf-dib-uncompressed.ts<br/>Custom R/G/B channel masks"]
|
|
441
|
-
|
|
442
|
-
C --> G{Bit Depth}
|
|
443
|
-
F --> G
|
|
444
|
-
G -->|1 bpp| H["Monochrome with colour table"]
|
|
445
|
-
G -->|4 bpp| I["16-colour indexed"]
|
|
446
|
-
G -->|8 bpp| J["256-colour indexed"]
|
|
447
|
-
G -->|16 bpp| K["5-5-5 or bitfield masks"]
|
|
448
|
-
G -->|24 bpp| L["B-G-R byte triplets"]
|
|
449
|
-
G -->|32 bpp| M["B-G-R-A quadruplets"]
|
|
450
|
-
|
|
451
|
-
H --> N["ImageData (RGBA pixels)"]
|
|
452
|
-
I --> N
|
|
453
|
-
J --> N
|
|
454
|
-
K --> N
|
|
455
|
-
L --> N
|
|
456
|
-
M --> N
|
|
457
|
-
|
|
458
|
-
D --> N
|
|
459
|
-
E --> N
|
|
460
|
-
```
|
|
232
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/emf-converter/README.md) for visual representations._
|
|
461
233
|
|
|
462
234
|
EMF+ also has its own bitmap format (`emf-plus-bitmap-decoder.ts`) supporting GDI+ pixel formats:
|
|
463
235
|
|
|
@@ -470,30 +242,7 @@ EMF+ also has its own bitmap format (`emf-plus-bitmap-decoder.ts`) supporting GD
|
|
|
470
242
|
|
|
471
243
|
Image draws (both GDI `StretchDIBits`/`BitBlt` and EMF+ `DrawImage`/`DrawImagePoints`) that reference bitmaps or embedded metafiles are collected as **deferred images** during the synchronous replay phase. After all records are processed, these are resolved asynchronously:
|
|
472
244
|
|
|
473
|
-
|
|
474
|
-
sequenceDiagram
|
|
475
|
-
participant R as Record Replay (sync)
|
|
476
|
-
participant D as Deferred Queue
|
|
477
|
-
participant P as Post-Processor (async)
|
|
478
|
-
participant C as Canvas Context
|
|
479
|
-
|
|
480
|
-
R->>D: Push image draw {data, position, transform, isMetafile}
|
|
481
|
-
R->>D: Push image draw ...
|
|
482
|
-
R->>D: Push image draw ...
|
|
483
|
-
Note over R: EMR_EOF — replay complete
|
|
484
|
-
|
|
485
|
-
P->>D: Iterate deferred images
|
|
486
|
-
loop For each deferred image
|
|
487
|
-
alt Is embedded metafile
|
|
488
|
-
P->>P: Recursively call convertEmfToDataUrl() / convertWmfToDataUrl()
|
|
489
|
-
P->>C: drawImage(bitmap, dx, dy, dw, dh)
|
|
490
|
-
else Is raster bitmap
|
|
491
|
-
P->>P: createImageBitmap(blob)
|
|
492
|
-
P->>C: drawImage(bitmap, dx, dy, dw, dh)
|
|
493
|
-
end
|
|
494
|
-
end
|
|
495
|
-
Note over C: Canvas now has all images composited
|
|
496
|
-
```
|
|
245
|
+
_See the [architecture diagrams on GitHub](https://github.com/ChristopherVR/pptx-viewer/blob/main/packages/emf-converter/README.md) for visual representations._
|
|
497
246
|
|
|
498
247
|
This two-phase approach is necessary because `createImageBitmap()` is asynchronous, while the GDI record replay loop is synchronous for performance.
|
|
499
248
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "emf-converter",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Convert EMF/WMF metafile binaries to PNG data URLs using Canvas",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"canvas",
|
|
@@ -11,9 +11,20 @@
|
|
|
11
11
|
"windows-metafile",
|
|
12
12
|
"wmf"
|
|
13
13
|
],
|
|
14
|
+
"homepage": "https://github.com/ChristopherVR/pptx-viewer",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/ChristopherVR/pptx-viewer/issues"
|
|
17
|
+
},
|
|
14
18
|
"license": "MIT",
|
|
19
|
+
"author": "ChristopherVR",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/ChristopherVR/pptx-viewer.git",
|
|
23
|
+
"directory": "packages/emf-converter"
|
|
24
|
+
},
|
|
15
25
|
"files": [
|
|
16
26
|
"dist/",
|
|
27
|
+
"LICENSE",
|
|
17
28
|
"README.md"
|
|
18
29
|
],
|
|
19
30
|
"main": "dist/index.js",
|