Show a live slide with render-dom
Mount a slide as real SVG elements, zoom it, watch the device pixel ratio, and build a thumbnail strip.
Goal
A slide on the page as elements you can hit-test, at a zoom the user chooses, with a strip of thumbnails beneath.
You need
npm install @pptx-studio/opc @pptx-studio/model @pptx-studio/render-svg @pptx-studio/render-domSteps
1. One text engine per deck
Every mount measures text; sharing one engine shares the measurer and the font probe.
import { PartStore } from '@pptx-studio/opc';
import { loadDocument } from '@pptx-studio/model';
import { createTextEngine, mediaFromStore } from '@pptx-studio/render-svg';
const store = PartStore.open(bytes);
const doc = loadDocument(store);
const media = mediaFromStore(store);
const text = createTextEngine({ defaultTextStyle: doc.defaultTextStyle });2. Mount at a width
Zoom is CSS pixels per point: 100% draws a 13.333-inch slide 960 pixels wide. The device pixel ratio rounds the strokes; pass the display's.
import { mountSlide } from '@pptx-studio/render-dom';
const host = document.querySelector('#stage')!;
let mounted = mountSlide(host, doc.slides[0]!, doc.slideSize, {
width: 960,
devicePixelRatio: window.devicePixelRatio,
media,
text,
});3. Zoom is a fresh mount
Strokes are rounded to the device pixels of the mounted width, so the SVG for 25% is not the SVG for 400%. There is no resize(); unmount and mount again. The line breaks do not move.
function show(zoom: number) {
mounted.unmount();
mounted = mountSlide(host, doc.slides[0]!, doc.slideSize, {
width: Math.round((zoom * doc.slideSize.cx) / 12700),
devicePixelRatio: window.devicePixelRatio,
media,
text,
});
}4. Follow the display
A window dragged to a different monitor changes its ratio; listen for the change and mount again.
function watchRatio(onChange: () => void) {
const query = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
query.addEventListener(
'change',
() => {
onChange();
watchRatio(onChange);
},
{ once: true },
);
}
watchRatio(() => show(1));5. Hit-test and overlay
Every shape element carries data-shape with its p:cNvPr/@id; the text of a shape is a sibling group carrying data-text. The overlay draws the debug chrome for one shape.
import { mountOverlay } from '@pptx-studio/render-dom';
host.addEventListener('click', (event) => {
const hit = (event.target as Element).closest('[data-shape], [data-text]');
const id = Number(hit?.getAttribute('data-shape') ?? hit?.getAttribute('data-text'));
const placed = mounted.placed.find((one) => one.shape.cNvPrId === id);
if (placed !== undefined) mountOverlay(mounted.root, placed, { unit: doc.slideSize.cx / 960 });
});6. A strip of thumbnails
Mount each slide at 120 pixels wide in frame-sized chunks, so a hundred slides fill in without holding the main thread.
const queue = doc.slides.map((slide, index) => ({ slide, index }));
function pump() {
const startedAt = performance.now();
while (queue.length > 0 && performance.now() - startedAt < 12) {
const { slide, index } = queue.shift()!;
mountSlide(document.querySelector(`#thumb-${index}`)!, slide, doc.slideSize, {
width: 120,
media,
text,
});
}
if (queue.length > 0) requestAnimationFrame(pump);
}
requestAnimationFrame(pump);What can go wrong
RenderDomErrorwithRENDER_DOM_NO_HOST- The host element was null; query it after the DOM is ready.RenderDomErrorwithRENDER_DOM_NO_DOCUMENT- In a Worker there is no globaldocument; pass one inoptions.document.RenderErrorwithRENDER_DEVICE_PIXEL_RATIO- A ratio of zero or less; usewindow.devicePixelRatio.
Try it
Reading the deck…