{"version":3,"file":"presentation-CBCh6VVL.cjs","sources":["../src/types.ts","../src/wasm.ts","../src/presentation.ts"],"sourcesContent":["/**\n * Public types. Everything a consumer of this package can name lives here.\n */\n\n/** How a slide is fitted into the viewport. */\nexport type Fit =\n  /** Scale to fit entirely, preserving aspect ratio, centred. The default. */\n  | 'contain'\n  /** Fill the viewport, preserving aspect ratio, cropping the overflow. */\n  | 'cover'\n  /** Ignore aspect ratio and stretch. */\n  | 'fill'\n  /** 1 slide point = 1 CSS pixel. */\n  | 'actual';\n\n/** Where the WASM module is loaded from. */\nexport type WasmSource =\n  /** A URL to `pptx_bg.wasm`. */\n  | string\n  /** Already-fetched bytes, for bundlers that inline the module. */\n  | ArrayBuffer\n  | Response;\n\nexport interface SlideInfo {\n  /** 0-based position in presentation order. */\n  index: number;\n  /** `<p:sldId id=\"...\">`, stable across reorderings. */\n  id: number;\n  /** Package part name, useful for diagnostics. */\n  part: string;\n}\n\nexport interface EmbeddedFontVariant {\n  rel: string;\n  bold: boolean;\n  italic: boolean;\n}\n\nexport interface EmbeddedFont {\n  typeface: string;\n  variants: EmbeddedFontVariant[];\n}\n\nexport interface PresentationInfo {\n  slideCount: number;\n  /** Slide size in points (1/72 inch). */\n  width: number;\n  height: number;\n  slides: SlideInfo[];\n  embeddedFonts: EmbeddedFont[];\n}\n\nexport interface RenderOptions {\n  /** Canvas size in CSS pixels. Defaults to the canvas's own client size. */\n  width?: number;\n  height?: number;\n  /** Defaults to `window.devicePixelRatio`. */\n  dpr?: number;\n  fit?: Fit;\n  /** Extra zoom on top of the fit. 1 = none. */\n  zoom?: number;\n  panX?: number;\n  panY?: number;\n}\n\n/** Anything that went wrong that the caller might want to know about. */\nexport class PptxError extends Error {\n  constructor(\n    message: string,\n    readonly cause?: unknown,\n  ) {\n    super(message);\n    this.name = 'PptxError';\n  }\n}\n\nexport interface PresentationViewerProps {\n  /** URL, `File`, `Blob`, or raw bytes of the `.pptx`. */\n  src: string | File | Blob | ArrayBuffer | Uint8Array;\n  /** CSS width of the viewer element. Default `100%`. */\n  width?: string | number;\n  /** CSS height of the viewer element. Default `100%`. */\n  height?: string | number;\n  /** 0-based slide to show first. Default 0. */\n  initialSlide?: number;\n  /** Controlled slide index. When set, the component does not manage its own. */\n  slide?: number;\n  fit?: Fit;\n  zoom?: number;\n  /** Respond to arrow keys, Page Up/Down, Home/End. Default true. */\n  keyboard?: boolean;\n  /**\n   * Lay a transparent, selectable copy of the slide's text over the canvas.\n   *\n   * A canvas draws pixels, so its text cannot be selected or copied with the mouse. This\n   * places a positioned `<span>` per run on top of it, invisible but selectable — the\n   * technique pdf.js uses. Screen readers and find-in-page already work without it, from\n   * the off-screen text block the component always renders.\n   *\n   * Off by default: it costs a DOM node per run, which on a dense slide is hundreds.\n   */\n  selectableText?: boolean;\n  /** Where to load the WASM module from. Defaults to the bundled one. */\n  wasm?: WasmSource;\n  /** Rendered instead of the canvas while the deck loads. */\n  loading?: React.ReactNode;\n  /** Rendered instead of the canvas when loading fails. */\n  renderError?: (error: PptxError) => React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n  onLoad?: (info: PresentationInfo) => void;\n  onError?: (error: PptxError) => void;\n  onSlideChange?: (index: number) => void;\n}\n\n/**\n * One run of text on a slide, positioned in device pixels.\n *\n * Everything needed to lay a transparent, correctly-sized element over the canvas so the\n * browser can handle selection, find-in-page and assistive technology — the things a\n * canvas cannot do for itself.\n */\nexport interface TextLayerRun {\n  text: string;\n  /** Left end of the baseline, in device pixels. */\n  x: number;\n  y: number;\n  /** Measured advance width in device pixels. */\n  width: number;\n  /** Font size in device pixels. */\n  size: number;\n  /** A CSS `font-family` list. */\n  family: string;\n  weight: number;\n  italic: boolean;\n  /** Clockwise rotation in radians. */\n  rotation: number;\n  /**\n   * The URL this run links to, or `null`.\n   *\n   * Only `http`, `https` and `mailto` survive parsing — a deck is untrusted input, and a\n   * `javascript:` target in an `href` is script injection with extra steps.\n   */\n  link: string | null;\n}\n\n/** A rectangle in device pixels. */\nexport interface TextRect {\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n}\n\n/** One occurrence of a search query on a slide. */\nexport interface SearchMatch {\n  /** The matched text as it appears on the slide, which for a case-insensitive search\n   *  is not necessarily what was typed. */\n  text: string;\n  /**\n   * Where to draw the highlight, in device pixels.\n   *\n   * More than one when the match spans several runs — \"the **bold** word\" is three runs,\n   * and one box across them would swallow the gaps between them.\n   */\n  rects: TextRect[];\n}\n\n/** Options for {@link Presentation.searchSlide}. */\nexport interface SearchOptions extends RenderOptions {\n  /** Default false: readers expect find-in-page to ignore case. */\n  caseSensitive?: boolean;\n}\n\n/** A slide that contains the query, from {@link Presentation.findSlides}. */\nexport interface SlideHit {\n  index: number;\n  /** How many times the query occurs on it. */\n  count: number;\n}\n","/**\n * WASM module loading.\n *\n * The module is instantiated once per page and shared. Two loading strategies are\n * supported and the default matters: `import init from '../../../crates/wasm/pkg/pptx.js'`\n * lets a bundler fingerprint and inline the `.wasm` next to the JS glue, which is what\n * makes the package work with no configuration. A consumer who would rather serve the\n * module from a CDN passes a URL to `initWasm` (or the `wasm` prop) instead.\n */\n\nimport initModule, * as bindings from '../../../crates/wasm/pkg/pptx.js';\nimport { PptxError, type WasmSource } from './types.js';\n\nexport type WasmModule = typeof bindings;\n\nlet ready: Promise<WasmModule> | null = null;\nlet loadedFrom: WasmSource | undefined;\n\n/**\n * Instantiates the WASM module, or returns the already-instantiated one.\n *\n * Calling this with a different `source` after the module has loaded is a no-op and warns:\n * a second instantiation would give two independent heaps and two independent image\n * caches, which is never what the caller meant.\n */\nexport async function initWasm(source?: WasmSource): Promise<WasmModule> {\n  if (ready) {\n    if (source !== undefined && source !== loadedFrom) {\n      console.warn(\n        '[pptx-wasm] the WASM module is already loaded; the new `wasm` source is ignored',\n      );\n    }\n    return ready;\n  }\n  loadedFrom = source;\n  ready = (async () => {\n    try {\n      // `initModule` accepts a URL, a Response, a module, or bytes; passing nothing lets\n      // the generated glue resolve the .wasm relative to itself.\n      await initModule(source === undefined ? undefined : ({ module_or_path: source } as never));\n      return bindings;\n    } catch (e) {\n      // A failed instantiation must not poison the cache: a transient network error\n      // should be retryable.\n      ready = null;\n      loadedFrom = undefined;\n      throw new PptxError(\n        `could not load the pptx-wasm WASM module: ${e instanceof Error ? e.message : String(e)}`,\n        e,\n      );\n    }\n  })();\n  return ready;\n}\n\n/** True once the module is instantiated. */\nexport function isWasmReady(): boolean {\n  return ready !== null;\n}\n\n/** Version of the loaded WASM module. */\nexport async function wasmVersion(): Promise<string> {\n  const m = await initWasm();\n  return m.version();\n}\n","/**\n * The framework-agnostic API layer.\n *\n * Everything asynchronous lives here — module instantiation, fetching, image decoding,\n * font loading — so the Rust side can stay synchronous. A synchronous wasm call cannot\n * await an `ImageBitmap`, so the shape of the contract is: Rust says what it needs, this\n * layer gets it, hands it back, and only then asks for a frame.\n */\n\nimport { initWasm, type WasmModule } from './wasm.js';\nimport {\n  PptxError,\n  type EmbeddedFont,\n  type PresentationInfo,\n  type RenderOptions,\n  type SlideInfo,\n  type WasmSource,\n  type SearchMatch,\n  type SearchOptions,\n  type SlideHit,\n  type TextLayerRun,\n} from './types.js';\n\nexport interface OpenOptions {\n  /** Where to load the WASM module from. */\n  wasm?: WasmSource;\n  /** Install the deck's embedded fonts as `FontFace`s. Default true. */\n  useEmbeddedFonts?: boolean;\n  signal?: AbortSignal;\n}\n\n/** An open presentation. Call {@link Presentation.destroy} when finished with it. */\nexport class Presentation {\n  #inner: InstanceType<WasmModule['Presentation']>;\n  #info: PresentationInfo;\n  #destroyed = false;\n  /** Image ids currently being decoded, so a re-render does not start a second decode. */\n  #decoding = new Set<number>();\n  /** Fonts already handed to `document.fonts.load()`. */\n  #fontsLoaded = new Set<string>();\n  #installedFonts: FontFace[] = [];\n  #assetListeners = new Set<() => void>();\n\n  private constructor(inner: InstanceType<WasmModule['Presentation']>, info: PresentationInfo) {\n    this.#inner = inner;\n    this.#info = info;\n  }\n\n  /** Opens a presentation from a URL, `File`/`Blob`, or raw bytes. */\n  static async open(\n    src: string | File | Blob | ArrayBuffer | Uint8Array,\n    options: OpenOptions = {},\n  ): Promise<Presentation> {\n    const wasm = await initWasm(options.wasm);\n    const bytes = await toBytes(src, options.signal);\n    let inner: InstanceType<WasmModule['Presentation']>;\n    try {\n      inner = new wasm.Presentation(bytes);\n    } catch (e) {\n      throw new PptxError(`could not open the presentation: ${errorText(e)}`, e);\n    }\n\n    const info: PresentationInfo = {\n      slideCount: inner.slideCount(),\n      width: inner.slideWidth(),\n      height: inner.slideHeight(),\n      slides: parseJson<SlideInfo[]>(inner.slideIndex(), []),\n      embeddedFonts: parseJson<EmbeddedFont[]>(inner.embeddedFonts(), []),\n    };\n\n    const presentation = new Presentation(inner, info);\n    if (options.useEmbeddedFonts !== false && info.embeddedFonts.length > 0) {\n      // Best-effort: a deck whose embedded fonts fail to install still renders, with\n      // substituted faces. Failing the open would be worse.\n      await presentation.#installEmbeddedFonts().catch((e) => {\n        console.warn('[pptx-wasm] embedded fonts could not be installed', e);\n      });\n    }\n    return presentation;\n  }\n\n  /**\n   * Subscribes to \"the assets a frame was missing have arrived\".\n   *\n   * Images decode asynchronously, so the first frame of a slide with pictures draws\n   * without them. Rather than have callers guess a delay and redraw on a timer, this\n   * fires once the outstanding decodes for a slide have finished. Returns an\n   * unsubscribe function.\n   */\n  onAssetsReady(listener: () => void): () => void {\n    this.#assetListeners.add(listener);\n    return () => this.#assetListeners.delete(listener);\n  }\n\n  /**\n   * How many images are still decoding.\n   *\n   * Zero does not mean a slide is fully drawn — it means nothing is outstanding. Test\n   * harnesses use it to know when a frame is final; application code should prefer\n   * {@link Presentation.onAssetsReady}.\n   */\n  pendingAssetCount(): number {\n    return this.#decoding.size;\n  }\n\n  #notifyAssetsReady(): void {\n    for (const listener of this.#assetListeners) {\n      try {\n        listener();\n      } catch (e) {\n        console.warn('[pptx-wasm] an onAssetsReady listener threw', e);\n      }\n    }\n  }\n\n  get info(): PresentationInfo {\n    return this.#info;\n  }\n\n  get slideCount(): number {\n    return this.#info.slideCount;\n  }\n\n  /** Slide size in points. */\n  get slideSize(): { width: number; height: number } {\n    return { width: this.#info.width, height: this.#info.height };\n  }\n\n  /**\n   * Lays a slide out without drawing it.\n   *\n   * Worth calling on the next and previous slides after a navigation: layout is the\n   * expensive half, and doing it ahead of time is what makes arrow-key navigation feel\n   * instant rather than merely fast.\n   */\n  prepare(index: number): void {\n    if (this.#destroyed || !this.#valid(index)) return;\n    try {\n      this.#inner.prepare(index);\n    } catch {\n      // Out of range or a slide we cannot parse; render() reports it properly.\n    }\n  }\n\n  /**\n   * Draws a slide into a canvas.\n   *\n   * Resolves once the slide is drawn *with everything that was ready*. Images and fonts\n   * that were not decoded yet are fetched, and the caller is told to draw again via the\n   * returned `complete` flag — a viewer typically renders once for the layout and once\n   * more when the assets arrive, rather than blocking on them.\n   */\n  async render(\n    index: number,\n    canvas: HTMLCanvasElement,\n    options: RenderOptions = {},\n  ): Promise<{ complete: boolean }> {\n    if (this.#destroyed) throw new PptxError('presentation has been destroyed');\n    if (!this.#valid(index)) {\n      throw new PptxError(`slide ${index} is out of range (0..${this.slideCount - 1})`);\n    }\n\n    const width = options.width ?? canvas.clientWidth ?? canvas.width;\n    const height = options.height ?? canvas.clientHeight ?? canvas.height;\n    const dpr = options.dpr ?? (typeof window !== 'undefined' ? window.devicePixelRatio : 1) ?? 1;\n\n    // Size the backing store, but only when it actually changed: assigning to\n    // canvas.width clears it, so an unconditional assignment flashes on every frame.\n    const pw = Math.max(1, Math.round(width * dpr));\n    const ph = Math.max(1, Math.round(height * dpr));\n    if (canvas.width !== pw) canvas.width = pw;\n    if (canvas.height !== ph) canvas.height = ph;\n\n    const ctx = canvas.getContext('2d');\n    if (!ctx) throw new PptxError('could not get a 2D context from the canvas');\n\n    await this.#ensureFonts(index);\n    const pending = this.#startDecoding(index);\n\n    try {\n      this.#inner.renderSlide(\n        index,\n        ctx,\n        width,\n        height,\n        dpr,\n        options.fit ?? 'contain',\n        options.zoom ?? 1,\n        options.panX ?? 0,\n        options.panY ?? 0,\n      );\n    } catch (e) {\n      throw new PptxError(`could not render slide ${index}: ${errorText(e)}`, e);\n    }\n    return { complete: pending.length === 0 };\n  }\n\n  /** A slide's text, in draw order. For search, selection and accessibility. */\n  text(index: number): string {\n    if (this.#destroyed || !this.#valid(index)) return '';\n    return this.#inner.slideText(index);\n  }\n\n  /** Speaker notes for a slide. */\n  notes(index: number): string {\n    if (this.#destroyed || !this.#valid(index)) return '';\n    return this.#inner.slideNotes(index);\n  }\n\n  /**\n   * A textual dump of a slide's draw commands.\n   *\n   * Diagnostic, not API: it is how you tell a layout bug from a rasterisation bug when a\n   * slide looks wrong. The format is not stable.\n   */\n  debugTrace(index: number, width = 960, height = 540): string {\n    if (this.#destroyed || !this.#valid(index)) return '';\n    return this.#inner.debugTrace(index, width, height);\n  }\n\n  /**\n   * Where the text is on a slide, in device pixels, for building a selectable layer.\n   *\n   * The positions come from the same walk the renderer uses, so a run culled from the\n   * canvas is absent here too — the overlay can never offer selectable text where\n   * nothing was painted.\n   *\n   * Pass the same options you passed to {@link render}, or the boxes will not line up.\n   */\n  textLayer(index: number, options: RenderOptions = {}): TextLayerRun[] {\n    if (this.#destroyed || !this.#valid(index)) return [];\n    const width = options.width ?? this.slideSize.width;\n    const height = options.height ?? this.slideSize.height;\n    const dpr = options.dpr ?? (globalThis.devicePixelRatio || 1);\n    const json = this.#inner.textLayer(\n      index,\n      width,\n      height,\n      dpr,\n      options.fit ?? 'contain',\n      options.zoom ?? 1,\n    );\n    try {\n      return JSON.parse(json) as TextLayerRun[];\n    } catch {\n      return [];\n    }\n  }\n\n  /**\n   * Finds `query` on one slide, with the rectangles to highlight each occurrence.\n   *\n   * Pass the same options you passed to {@link render}, or the boxes will not line up.\n   * Matches are positioned by the same walk that painted the glyphs, so a highlight\n   * cannot land where the text is not.\n   *\n   * A phrase is not matched across a line break: the words are not adjacent on screen,\n   * and boxing them as one would cover the unrelated text between them.\n   */\n  searchSlide(index: number, query: string, options: SearchOptions = {}): SearchMatch[] {\n    if (this.#destroyed || !this.#valid(index) || !query) return [];\n    const width = options.width ?? this.slideSize.width;\n    const height = options.height ?? this.slideSize.height;\n    const dpr = options.dpr ?? (globalThis.devicePixelRatio || 1);\n    const json = this.#inner.searchSlide(\n      index,\n      query,\n      options.caseSensitive ?? false,\n      width,\n      height,\n      dpr,\n      options.fit ?? 'contain',\n      options.zoom ?? 1,\n    );\n    try {\n      return JSON.parse(json) as SearchMatch[];\n    } catch {\n      return [];\n    }\n  }\n\n  /**\n   * Which slides contain `query`, and how many times.\n   *\n   * Cheaper than {@link searchSlide} per slide — it compares text and never asks for\n   * rectangles — but it still lays out every slide it has not seen, so on a large deck\n   * the first call is the expensive one. Use it to drive a result list, then ask\n   * {@link searchSlide} for boxes only on the slide actually being shown.\n   */\n  findSlides(query: string, options: { caseSensitive?: boolean } = {}): SlideHit[] {\n    if (this.#destroyed || !query) return [];\n    const needle = options.caseSensitive ? query : query.toLowerCase();\n    const hits: SlideHit[] = [];\n    for (let i = 0; i < this.slideCount; i += 1) {\n      const raw = this.text(i);\n      const hay = options.caseSensitive ? raw : raw.toLowerCase();\n      let count = 0;\n      let at = hay.indexOf(needle);\n      while (at !== -1) {\n        count += 1;\n        at = hay.indexOf(needle, at + needle.length);\n      }\n      if (count > 0) hits.push({ index: i, count });\n    }\n    return hits;\n  }\n\n  /** What a WebGPU backend would need for this slide. Diagnostic. */\n  gpuRequirements(index: number): string {\n    if (this.#destroyed || !this.#valid(index)) return '';\n    return this.#inner.gpuRequirements(index);\n  }\n\n  /** Drops cached layouts. Rebuilt on demand. */\n  evict(): void {\n    if (!this.#destroyed) this.#inner.evictLayouts();\n  }\n\n  /** Frees the WASM-side presentation and uninstalls any embedded fonts. */\n  destroy(): void {\n    if (this.#destroyed) return;\n    this.#destroyed = true;\n    for (const face of this.#installedFonts) {\n      try {\n        document.fonts.delete(face);\n      } catch {\n        // A font that was never added, or a document that has gone away.\n      }\n    }\n    this.#installedFonts = [];\n    this.#assetListeners.clear();\n    this.#inner.free();\n  }\n\n  #valid(index: number): boolean {\n    return Number.isInteger(index) && index >= 0 && index < this.slideCount;\n  }\n\n  /**\n   * Waits for the faces a slide draws with.\n   *\n   * `fillText` against a face the browser has not loaded silently substitutes it, and the\n   * result looks exactly like a layout bug — so this is awaited before the first frame\n   * rather than treated as an optimisation.\n   */\n  async #ensureFonts(index: number): Promise<void> {\n    if (typeof document === 'undefined' || !document.fonts) return;\n    const fonts = parseJson<string[]>(this.#inner.fontsNeeded(index), []);\n    const toLoad = fonts.filter((f) => !this.#fontsLoaded.has(f));\n    if (toLoad.length === 0) return;\n    for (const f of toLoad) this.#fontsLoaded.add(f);\n    await Promise.all(\n      toLoad.map((f) =>\n        // A face that is genuinely unavailable rejects; the fallback chain in the font\n        // spec already covers that case, so it is not an error.\n        document.fonts.load(f).catch(() => undefined),\n      ),\n    );\n  }\n\n  /**\n   * Kicks off decoding for a slide's images.\n   *\n   * Returns the ids that were not ready. Decoding is deliberately not awaited: a slide\n   * that is mostly text should appear immediately rather than after its photographs.\n   */\n  #startDecoding(index: number): number[] {\n    const raw = this.#inner.pendingImages(index);\n    if (!raw) return [];\n    const ids = raw\n      .split(',')\n      .map((s) => Number.parseInt(s, 10))\n      .filter((n) => Number.isInteger(n) && !this.#decoding.has(n));\n\n    for (const id of ids) {\n      this.#decoding.add(id);\n      void this.#decodeImage(id);\n    }\n    return ids;\n  }\n\n  async #decodeImage(id: number): Promise<void> {\n    try {\n      const bytes = this.#inner.imageBytes(id);\n      const mime = this.#inner.imageMime(id);\n      if (!bytes) {\n        this.#inner.markImageFailed(id);\n        return;\n      }\n      // EMF/WMF are vector metafiles no browser decodes. Marking them failed stops the\n      // viewer asking again every frame.\n      if (mime === 'image/x-emf' || mime === 'image/x-wmf') {\n        this.#inner.markImageFailed(id);\n        return;\n      }\n      // Copy out of the wasm heap before handing the bytes to the platform: growing the\n      // heap detaches every view into it, and an in-flight decode would see a zero-length\n      // buffer. The copy is also what makes the `Uint8Array` a plain `ArrayBuffer` view.\n      const blob = new Blob([copyBytes(bytes)], { type: mime });\n      const image = await decodeBlob(blob);\n      if (this.#destroyed) return;\n      this.#inner.setImage(id, image);\n    } catch (e) {\n      console.warn(`[pptx-wasm] image ${id} could not be decoded`, e);\n      if (!this.#destroyed) this.#inner.markImageFailed(id);\n    } finally {\n      this.#decoding.delete(id);\n      // Only once the whole batch is in: firing per image would redraw the slide once\n      // per picture, which on an image-heavy deck is worse than waiting.\n      if (this.#decoding.size === 0 && !this.#destroyed) this.#notifyAssetsReady();\n    }\n  }\n\n  async #installEmbeddedFonts(): Promise<void> {\n    if (typeof document === 'undefined' || !document.fonts || !('FontFace' in globalThis)) {\n      return;\n    }\n    for (const font of this.#info.embeddedFonts) {\n      for (const variant of font.variants) {\n        const bytes = this.#inner.embeddedFontBytes(variant.rel);\n        if (!bytes) continue;\n        try {\n          const face = new FontFace(font.typeface, copyBytes(bytes), {\n            weight: variant.bold ? '700' : '400',\n            style: variant.italic ? 'italic' : 'normal',\n          });\n          await face.load();\n          document.fonts.add(face);\n          this.#installedFonts.push(face);\n        } catch (e) {\n          // A single unusable variant should not stop the others installing.\n          console.warn(`[pptx-wasm] embedded font ${font.typeface} could not be used`, e);\n        }\n      }\n    }\n  }\n}\n\n/**\n * Copies bytes out of the WASM heap into a standalone `ArrayBuffer`.\n *\n * Necessary rather than merely tidy: any later WASM allocation can grow the heap, and\n * growing it detaches every `Uint8Array` view into it. A `Blob` or `FontFace` holding a\n * detached view sees zero bytes.\n */\nfunction copyBytes(bytes: Uint8Array): ArrayBuffer {\n  const buffer = new ArrayBuffer(bytes.byteLength);\n  new Uint8Array(buffer).set(bytes);\n  return buffer;\n}\n\n/** `createImageBitmap` where available, an `<img>` otherwise (Safari < 17 for SVG). */\nasync function decodeBlob(blob: Blob): Promise<ImageBitmap | HTMLImageElement> {\n  if (typeof createImageBitmap === 'function' && blob.type !== 'image/svg+xml') {\n    try {\n      return await createImageBitmap(blob);\n    } catch {\n      // Fall through to the <img> path, which handles a few formats createImageBitmap\n      // refuses.\n    }\n  }\n  const url = URL.createObjectURL(blob);\n  try {\n    const img = new Image();\n    img.decoding = 'sync';\n    await new Promise<void>((resolve, reject) => {\n      img.onload = () => resolve();\n      img.onerror = () => reject(new Error('image failed to load'));\n      img.src = url;\n    });\n    // `decode()` guarantees the bitmap is ready before the next draw; without it the\n    // first frame after loading can draw nothing.\n    if (typeof img.decode === 'function') await img.decode().catch(() => undefined);\n    return img;\n  } finally {\n    // Revoking immediately is safe: the image has been decoded into memory by now.\n    URL.revokeObjectURL(url);\n  }\n}\n\nasync function toBytes(\n  src: string | File | Blob | ArrayBuffer | Uint8Array,\n  signal?: AbortSignal,\n): Promise<Uint8Array> {\n  if (src instanceof Uint8Array) return src;\n  if (src instanceof ArrayBuffer) return new Uint8Array(src);\n  if (typeof Blob !== 'undefined' && src instanceof Blob) {\n    return new Uint8Array(await src.arrayBuffer());\n  }\n  if (typeof src === 'string') {\n    const response = await fetch(src, { signal });\n    if (!response.ok) {\n      throw new PptxError(`could not fetch ${src}: ${response.status} ${response.statusText}`);\n    }\n    return new Uint8Array(await response.arrayBuffer());\n  }\n  throw new PptxError('src must be a URL, File, Blob, ArrayBuffer or Uint8Array');\n}\n\nfunction parseJson<T>(text: string, fallback: T): T {\n  try {\n    return JSON.parse(text) as T;\n  } catch {\n    return fallback;\n  }\n}\n\nfunction errorText(e: unknown): string {\n  if (typeof e === 'string') return e;\n  if (e instanceof Error) return e.message;\n  return String(e);\n}\n"],"names":["PptxError","message","cause","ready","loadedFrom","initWasm","source","initModule","bindings","e","isWasmReady","wasmVersion","Presentation","#inner","#info","#destroyed","#decoding","#fontsLoaded","#installedFonts","#assetListeners","inner","info","src","options","wasm","bytes","toBytes","errorText","parseJson","presentation","#installEmbeddedFonts","listener","#notifyAssetsReady","index","#valid","canvas","width","height","dpr","pw","ph","ctx","#ensureFonts","pending","#startDecoding","json","query","needle","hits","i","raw","hay","count","at","face","toLoad","f","ids","s","n","id","#decodeImage","mime","blob","copyBytes","image","decodeBlob","font","variant","buffer","url","img","resolve","reject","signal","response","text","fallback"],"mappings":"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"}