PPTX Studio
Packages

render-dom

The same slide as live SVG elements you can hit-test, zoom and overlay, plus an HTML text layer.

@pptx-studio/render-dom0.3.7runs inTabLive demo →npm →

The same slide as live SVG elements you can hit-test, zoom and overlay, plus an HTML text layer for selection and screen readers.

npm install @pptx-studio/render-dom @pptx-studio/opc @pptx-studio/model @pptx-studio/render-svg

What it is

mountSlide builds the tree render-svg describes as real <svg> elements in a host you give it, so a shape is an element you can find by its id and hit-test with a click. mountOverlay draws the debug chrome - paths, text rectangle, connection sites, draggable adjust handles - over one shape, and mountTextLayer puts real HTML text over the same line boxes for selection, an IME and a screen reader.

30-second example

mount.ts
const store = PartStore.open(bytes);
const doc = loadDocument(store);
const slide = doc.slides[0];
const host = document.querySelector('#stage');
if (slide === undefined || host === null) throw new Error('nothing to mount');

const text = createTextEngine({ defaultTextStyle: doc.defaultTextStyle }); // one measurer, every mount
const mounted = mountSlide(host, slide, doc.slideSize, {
  width: 960, // one CSS pixel per point
  devicePixelRatio: window.devicePixelRatio, // strokes round to device pixels
  media: mediaFromStore(store),
  text,
});
const first = mounted.placed[0];
if (first !== undefined) mountOverlay(mounted.root, first, { unit: doc.slideSize.cx / 960 });

// Zoom: unmount and mount again at the new width. The line breaks do not move.
mounted.unmount();

Key concepts

One tree, two renderers

renderSlideMarkup from this package is the string renderer applied to the same tree mountSlide instantiates; the tests check that the two agree.

A 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 at the new width. The text layout does not change.

The overlay writes a:avLst

Dragging a handle on the overlay produces a HandleEdit with the adjust list a save would write; mergeAdjust folds it into the shape's own.

The text layer

One div per shape, one per line, a span per piece, over the identical line boxes the SVG used. Real text nodes, so the browser's selection and accessibility tree see words, not paths.

API reference

20 exports of @pptx-studio/render-dom 0.3.7, read from the package this site installed. Click a name for its declaration.

src/errors.ts

  • RenderDomErrorCodetypeEvery failure in this package is a RenderDomError with a code.
    type RenderDomErrorCode =
    /** `mountSlide` was handed something that is not an element. */
    'RENDER_DOM_NO_HOST' |
    /** The host is detached and no `document` was passed. */
    'RENDER_DOM_NO_DOCUMENT';
  • RENDER_DOM_ERROR_CODESconst
    const RENDER_DOM_ERROR_CODES: readonly RenderDomErrorCode[];
  • RenderDomErrorclass
    class RenderDomError extends Error {
      readonly code: RenderDomErrorCode;
      constructor(code: RenderDomErrorCode, message: string);
    }
  • isRenderDomErrorfunction
    function isRenderDomError(value: unknown): value is RenderDomError;

src/mount.ts

  • createNodefunctionOne node into one element. Exported because the debug overlay wants it too.
    function createNode(document: Document, node: SvgNode): Node;
  • MountOptionstype
    interface MountOptions extends RenderOptions {
      /**
       * The `document` to build in.
       *
       * Passed rather than reached for, because this package must work in a Web
       * Worker with an `OffscreenCanvas` and no global `document` - and because a
       * test that has to stub a global is a test that can pass for the wrong reason.
       */
      readonly document?: Document;
    }
  • MountedSlidetype
    interface MountedSlide {
      readonly root: SVGSVGElement;
      readonly placed: readonly Placed[];
      /** The element drawing a given `p:cNvPr/@id`, for hit-testing and selection. */
      element(cNvPrId: number): SVGElement | null;
      /** Detach from the host. The nodes stay valid, so a caller may re-attach. */
      unmount(): void;
    }
  • mountSlidefunctionRender a sheet into a host element.
    function mountSlide(host: Element, sheet: Sheet, size: SlideSize, options?: MountOptions): MountedSlide;
  • renderSlideMarkupfunctionThe same slide as a string, from this package.
    function renderSlideMarkup(sheet: Sheet, size: SlideSize, options?: RenderOptions): string;

src/overlay.ts

  • HandleEdittypeWhat a completed drag produced.
    interface HandleEdit {
      /** Which `a:ahLst` entry was dragged. */
      readonly handle: number;
      /** The adjust values this handle controls, rounded as the file will hold them. */
      readonly adjust: Readonly<Record<string, number>>;
      /** The shape's whole `a:avLst` after the edit, ready to write back. */
      readonly avLst: readonly PresetGuide[];
    }
  • OverlayMountOptionstype
    interface OverlayMountOptions extends OverlayOptions {
      readonly document?: Document;
      /** Called on every pointer move during a drag, and once more on release. */
      onChange?: (edit: HandleEdit) => void;
      /** Called once on release. */
      onCommit?: (edit: HandleEdit) => void;
    }
  • MountedOverlaytype
    interface MountedOverlay {
      readonly root: SVGGElement;
      readonly overlay: ShapeOverlay;
      /** Redraw against a new placement - after a drag, or a resize. */
      update(placed: Placed): void;
      /** True while a handle is being dragged. */
      dragging(): boolean;
      unmount(): void;
    }
  • slidePointfunctionA point in the host <svg>'s user space - EMU - from a pointer event.
    function slidePoint(root: SVGSVGElement, clientX: number, clientY: number): Point | null;
  • mergeAdjustfunctionThe a:avLst a drag leaves behind.
    function mergeAdjust(avLst: readonly PresetGuide[], adjust: Readonly<Record<string, number>>): readonly PresetGuide[];
  • mountOverlayfunctionDraw the overlay for one shape into a host <svg>, and make its handles drag.
    function mountOverlay(root: SVGSVGElement, placed: Placed, options?: OverlayMountOptions): MountedOverlay;

src/text/layer.ts

  • LayerSizetypeA slide's size in points, which is what a block's coordinates are in.
    interface LayerSize {
      readonly widthPt: number;
      readonly heightPt: number;
    }
  • LayerBlocktypeOne shape's text, with the frame it is drawn in.
    interface LayerBlock {
      readonly block: TextBlock;
      /** The shape's frame, in points on the slide. */
      readonly leftPt: number;
      readonly topPt: number;
      readonly widthPt: number;
      readonly heightPt: number;
      /** `p:cNvPr/@id`, so a caller can find the element for a shape. */
      readonly cNvPrId: number;
      readonly name?: string | undefined;
    }
  • TextLayerOptionstype
    interface TextLayerOptions {
      readonly document?: Document | undefined;
      /** CSS pixels per point. One transform, never a re-layout. */
      readonly scale?: number | undefined;
      /** Let the layer take pointer events. Off by default: the SVG hit-tests. */
      readonly interactive?: boolean | undefined;
    }
  • MountedTextLayertype
    interface MountedTextLayer {
      readonly root: HTMLElement;
      /** The element holding one shape's text. */
      element(cNvPrId: number): HTMLElement | null;
      /** Rescale without re-laying anything out. */
      resize(scale: number): void;
      unmount(): void;
    }
  • mountTextLayerfunctionMount a text layer into a host, over a slide of size points.
    function mountTextLayer(host: Element, blocks: readonly LayerBlock[], size: LayerSize, options?: TextLayerOptions): MountedTextLayer;

Errors

Throws RenderDomError, whose code is one of:

  • RENDER_DOM_NO_HOST
  • RENDER_DOM_NO_DOCUMENT

Try it live

Reading the deck…

Open the full demo →

Read more

On this page