{"_id":"@bilalaideveloper/pptx-renderer","_rev":"2-ba7ec371f8b96329552e40b56f261e66","name":"@bilalaideveloper/pptx-renderer","dist-tags":{"latest":"1.2.6"},"versions":{"1.2.4":{"name":"@bilalaideveloper/pptx-renderer","version":"1.2.4","keywords":["pptx","powerpoint","renderer","ooxml","slides"],"license":"Apache-2.0","_id":"@bilalaideveloper/pptx-renderer@1.2.4","maintainers":[{"name":"bilalaideveloper","email":"bilal.ai.developer@gmail.com"}],"homepage":"https://github.com/aiden0z/pptx-renderer#readme","bugs":{"url":"https://github.com/aiden0z/pptx-renderer/issues"},"dist":{"shasum":"766e6c7fe47fabd73835f90dba3fb1d2625b084d","tarball":"https://registry.npmjs.org/@bilalaideveloper/pptx-renderer/-/pptx-renderer-1.2.4.tgz","fileCount":75,"integrity":"sha512-Pj4TBhUvgh5d8jy7HviqkFOhkR8aPiwYC14n3/6swJk/du1xYk4y3jvjbyBqgS0XIptEsxxrnGrW7BeYMk9FfA==","signatures":[{"sig":"MEUCIH+Yjg/3XI/j9ioKOTA9gmcwTIQZzQDtT2tXUb1IecXWAiEA39dh+NZi7Zp//mUMf/Fej/4PAY7kSXOyFy/Vy3+MjWk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":2764236},"main":"./dist/aiden0z-pptx-renderer.cjs","type":"module","types":"./dist/types/index.d.ts","module":"./dist/aiden0z-pptx-renderer.es.js","exports":{".":{"import":{"types":"./dist/types/index.d.ts","default":"./dist/aiden0z-pptx-renderer.es.js"},"require":{"types":"./dist/types/index.d.cts","default":"./dist/aiden0z-pptx-renderer.cjs"}},"./browser":{"types":"./dist/types/index.d.ts","import":"./dist/aiden0z-pptx-renderer.browser.es.js","default":"./dist/aiden0z-pptx-renderer.browser.es.js"}},"gitHead":"68cb570940fb28d5c4628f31d1365016c4483521","scripts":{"dev":"vite","knip":"knip","lint":"eslint src/","size":"size-limit","test":"vitest run","build":"node scripts/build.mjs","format":"prettier --write 'src/**/*.ts'","dev:api":"cd test/e2e && .venv/bin/python server.py","dev:e2e":"bash ./scripts/dev-e2e.sh","prepare":"husky || exit 0","preview":"vite preview","publint":"publint","lint:fix":"eslint src/ --fix","typecheck":"tsc --noEmit","build:demo":"vite build --config vite.config.demo.ts","test:watch":"vitest","format:check":"prettier --check 'src/**/*.ts'","test:browser":"playwright test --config test/browser/playwright.config.ts","test:package":"node scripts/verify-package.mjs","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build","test:pdfjs-contract":"node scripts/verify-pdfjs-contract.mjs"},"_npmUser":{"name":"bilalaideveloper","email":"bilal.ai.developer@gmail.com"},"repository":{"url":"git+https://github.com/aiden0z/pptx-renderer.git","type":"git"},"size-limit":[{"gzip":true,"path":"dist/aiden0z-pptx-renderer.es.js","limit":"1400 kB"},{"gzip":true,"path":"dist/aiden0z-pptx-renderer.browser.es.js","limit":"500 kB"}],"_npmVersion":"11.11.0","description":"Parse and render PPTX files in the browser with a TypeScript-first engine.","directories":{},"lint-staged":{"src/**/*.ts":["eslint --fix","prettier --write"],"*.{json,md,html,css}":["prettier --write"]},"sideEffects":["./src/renderer/chart/echartsRuntime.ts","./dist/aiden0z-pptx-renderer.es.js","./dist/aiden0z-pptx-renderer.cjs","./dist/aiden0z-pptx-renderer.browser.es.js"],"_nodeVersion":"25.8.1","dependencies":{"jszip":"^3.10.1","echarts":"^6.0.0","mtx-decompressor":"^1.4.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@9.15.9","devDependencies":{"knip":"^5.85.0","vite":"^6.0.0","husky":"^9.1.7","jsdom":"^26.0.0","pngjs":"^7.0.0","canvas":"^3.2.1","eslint":"^10.0.2","vitest":"^3.0.0","publint":"^0.3.17","prettier":"^3.8.1","@eslint/js":"^10.0.1","pdfjs-dist":"^5.4.624","pixelmatch":"^7.1.0","size-limit":"^12.0.0","typescript":"^5.7.0","lint-staged":"^16.2.7","@commitlint/cli":"^20.4.2","@playwright/test":"^1.61.1","@size-limit/file":"^12.0.0","typescript-eslint":"^8.56.1","@vitest/coverage-v8":"^3.2.4","eslint-config-prettier":"^10.1.8","@commitlint/config-conventional":"^20.4.2"},"peerDependencies":{"pdfjs-dist":">=5 <7"},"peerDependenciesMeta":{"pdfjs-dist":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/pptx-renderer_1.2.4_1784894643502_0.1846036394709536","host":"s3://npm-registry-packages-npm-production"}},"1.2.6":{"name":"@bilalaideveloper/pptx-renderer","version":"1.2.6","packageManager":"pnpm@9.15.9","description":"Parse and render PPTX files in the browser with a TypeScript-first engine.","license":"Apache-2.0","homepage":"https://github.com/aiden0z/pptx-renderer#readme","repository":{"type":"git","url":"git+https://github.com/aiden0z/pptx-renderer.git"},"bugs":{"url":"https://github.com/aiden0z/pptx-renderer/issues"},"keywords":["pptx","powerpoint","renderer","ooxml","slides"],"type":"module","main":"./dist/aiden0z-pptx-renderer.cjs","module":"./dist/aiden0z-pptx-renderer.es.js","types":"./dist/types/index.d.ts","exports":{".":{"import":{"types":"./dist/types/index.d.ts","default":"./dist/aiden0z-pptx-renderer.es.js"},"require":{"types":"./dist/types/index.d.cts","default":"./dist/aiden0z-pptx-renderer.cjs"}},"./browser":{"types":"./dist/types/index.d.ts","import":"./dist/aiden0z-pptx-renderer.browser.es.js","default":"./dist/aiden0z-pptx-renderer.browser.es.js"}},"sideEffects":["./src/renderer/chart/echartsRuntime.ts","./dist/aiden0z-pptx-renderer.es.js","./dist/aiden0z-pptx-renderer.cjs","./dist/aiden0z-pptx-renderer.browser.es.js"],"publishConfig":{"access":"public"},"scripts":{"dev":"vite","dev:api":"cd test/e2e && .venv/bin/python server.py","dev:e2e":"bash ./scripts/dev-e2e.sh","build":"node scripts/build.mjs","build:demo":"vite build --config vite.config.demo.ts","preview":"vite preview","test":"vitest run","test:package":"node scripts/verify-package.mjs","test:browser":"playwright test --config test/browser/playwright.config.ts","test:pdfjs-contract":"node scripts/verify-pdfjs-contract.mjs","test:coverage":"vitest run --coverage","test:watch":"vitest","lint":"eslint src/","lint:fix":"eslint src/ --fix","format":"prettier --write 'src/**/*.ts'","format:check":"prettier --check 'src/**/*.ts'","typecheck":"tsc --noEmit","publint":"publint","knip":"knip","size":"size-limit","prepublishOnly":"npm run build","prepare":"husky || exit 0"},"size-limit":[{"path":"dist/aiden0z-pptx-renderer.es.js","limit":"1400 kB","gzip":true},{"path":"dist/aiden0z-pptx-renderer.browser.es.js","limit":"500 kB","gzip":true}],"lint-staged":{"src/**/*.ts":["eslint --fix","prettier --write"],"*.{json,md,html,css}":["prettier --write"]},"dependencies":{"echarts":"^6.0.0","jszip":"^3.10.1","mtx-decompressor":"^1.4.2"},"peerDependencies":{"pdfjs-dist":">=5 <7"},"peerDependenciesMeta":{"pdfjs-dist":{"optional":true}},"devDependencies":{"@commitlint/cli":"^20.4.2","@commitlint/config-conventional":"^20.4.2","@eslint/js":"^10.0.1","@playwright/test":"^1.61.1","@size-limit/file":"^12.0.0","@vitest/coverage-v8":"^3.2.4","canvas":"^3.2.1","eslint":"^10.0.2","eslint-config-prettier":"^10.1.8","husky":"^9.1.7","jsdom":"^26.0.0","knip":"^5.85.0","lint-staged":"^16.2.7","pdfjs-dist":"^5.4.624","pixelmatch":"^7.1.0","pngjs":"^7.0.0","prettier":"^3.8.1","publint":"^0.3.17","size-limit":"^12.0.0","typescript":"^5.7.0","typescript-eslint":"^8.56.1","vite":"^6.0.0","vitest":"^3.0.0"},"gitHead":"1c41707dc7c5f400528785f577c313d97f09668f","_id":"@bilalaideveloper/pptx-renderer@1.2.6","_nodeVersion":"25.8.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-zo4mcHZt2R1bv8Bt23D4LA/72j3T/58/HLtBO9KvIFYkWf2H/K+mV1XZZp1D32C65ug3h5OCRCY1F08uZ+lXAg==","shasum":"fb80cec53c970bcbb79718868600e4e425b929a2","tarball":"https://registry.npmjs.org/@bilalaideveloper/pptx-renderer/-/pptx-renderer-1.2.6.tgz","fileCount":75,"unpackedSize":2764936,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBxkEGx+AJTIBGJ+83ftszW8Slz7j3v8bwk2xNenlvcUAiEA074yDgjfz7L/lK5LSd2cTDrQ+mE+X1syyaxTT+HYeSA="}]},"_npmUser":{"name":"bilalaideveloper","email":"bilal.ai.developer@gmail.com"},"directories":{},"maintainers":[{"name":"bilalaideveloper","email":"bilal.ai.developer@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/pptx-renderer_1.2.6_1785131830612_0.9292487566443377"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-24T12:04:03.311Z","modified":"2026-07-27T05:57:10.941Z","1.2.4":"2026-07-24T12:04:03.734Z","1.2.6":"2026-07-27T05:57:10.782Z"},"bugs":{"url":"https://github.com/aiden0z/pptx-renderer/issues"},"license":"Apache-2.0","homepage":"https://github.com/aiden0z/pptx-renderer#readme","keywords":["pptx","powerpoint","renderer","ooxml","slides"],"repository":{"type":"git","url":"git+https://github.com/aiden0z/pptx-renderer.git"},"description":"Parse and render PPTX files in the browser with a TypeScript-first engine.","maintainers":[{"name":"bilalaideveloper","email":"bilal.ai.developer@gmail.com"}],"readme":"# @bilalaideveloper/pptx-renderer\r\n\r\n> **Fork notice.** This is a fork of [`aiden0z/pptx-renderer`](https://github.com/aiden0z/pptx-renderer)\r\n> (Apache-2.0), published under a different scope. It adds source-identity `data-*`\r\n> attributes on the rendered DOM — `data-shape-id` (OOXML spid) on shape containers,\r\n> `data-para-index` on paragraph `<div>`s, and `data-run-index` on run `<span>`s —\r\n> so consumers can join the rendered output back to their own parsed model without\r\n> geometry or text matching. All original license and attribution notices are retained\r\n> (see `LICENSE` and `THIRD_PARTY_NOTICES.md`).\r\n\r\n[![License](https://img.shields.io/github/license/aiden0z/pptx-renderer)](https://github.com/aiden0z/pptx-renderer/blob/main/LICENSE) [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) [![Node](https://img.shields.io/badge/Node.js-≥20-339933?logo=node.js&logoColor=white)](https://nodejs.org/)\r\n\r\nA high-fidelity, browser-native PPTX renderer that parses Office Open XML (`.pptx`) files and renders slides as HTML/SVG DOM.\r\n\r\nSupports shapes, text, images, tables, charts, SmartArt, groups, backgrounds, gradients, pattern fills, and the full OOXML color pipeline — covering the vast majority of real-world PowerPoint content.\r\n\r\n## Rendering Example\r\n\r\nA complex slide with charts, text styles, shapes, and SmartArt — PowerPoint ground truth vs browser-rendered output:\r\n\r\n<table>\r\n<tr>\r\n<th>PowerPoint (Ground Truth)</th>\r\n<th>pptx-renderer (Browser)</th>\r\n</tr>\r\n<tr>\r\n<td><img src=\"docs/example/1-chart-and-complex/slides/Slide1.png\" alt=\"PowerPoint ground truth\" width=\"480\" /></td>\r\n<td><img src=\"docs/example/1-chart-and-complex/rendered-result.png\" alt=\"pptx-renderer output\" width=\"480\" /></td>\r\n</tr>\r\n</table>\r\n\r\n## Visual Regression Testing\r\n\r\nEvery rendering capability is automatically verified against PowerPoint output. **452+ visual regression cases** with zero failures — covering 187+ preset shapes, 134+ SmartArt layouts, 36+ fill/stroke/gradient variants, and 101 python-pptx cases (text, shape adjustments, composites, charts).\r\n\r\n<img src=\"docs/example/e2e-test-page.png\" alt=\"E2E evaluation dashboard\" width=\"800\" />\r\n\r\n<sup>E2E evaluation dashboard: side-by-side ground truth vs rendered output with SSIM, color histogram, and IoU metrics per slide.</sup>\r\n\r\n> Ground truth data (PPTX + PDF pairs) is not committed to the repository due to file size. It can be regenerated locally via `scripts/one_shot_full_ground_truth.py` with Microsoft PowerPoint installed (macOS and Windows both supported) — see [`docs/TESTING.md`](docs/TESTING.md) for details.\r\n\r\n## Install\r\n\r\n```bash\r\nnpm install @aiden0z/pptx-renderer\r\n# or\r\npnpm add @aiden0z/pptx-renderer\r\n\r\n# Optional: only needed for SmartArt / EMF files with embedded PDF fallback previews\r\npnpm add pdfjs-dist\r\n```\r\n\r\nRequires Node.js 20+ for development. Runtime is browser-only.\r\n\r\n## Quick Start\r\n\r\nFor bundlers and npm-based apps:\r\n\r\n```ts\r\nimport { PptxViewer, RECOMMENDED_ZIP_LIMITS } from '@aiden0z/pptx-renderer';\r\n\r\nconst container = document.getElementById('pptx-container')!;\r\nconst resp = await fetch('/slides/demo.pptx');\r\n\r\n// One-liner: parse, build model, and render\r\nconst viewer = await PptxViewer.open(await resp.arrayBuffer(), container, {\r\n  zipLimits: RECOMMENDED_ZIP_LIMITS,\r\n  listOptions: { windowed: true },\r\n});\r\n```\r\n\r\nFor direct browser usage without a bundler, import the standalone browser ESM build. This\r\nentry bundles JSZip and ECharts, and replaces Node-style `process.env` checks at build time.\r\nPDF.js remains optional and is only needed for EMF-embedded PDF fallback previews:\r\n\r\n```html\r\n<script type=\"module\">\r\n  import {\r\n    PptxViewer,\r\n    RECOMMENDED_ZIP_LIMITS,\r\n  } from '/vendor/pptx-renderer/aiden0z-pptx-renderer.browser.es.js';\r\n\r\n  const container = document.getElementById('pptx-container');\r\n  const resp = await fetch('/slides/demo.pptx');\r\n  await PptxViewer.open(await resp.arrayBuffer(), container, {\r\n    zipLimits: RECOMMENDED_ZIP_LIMITS,\r\n  });\r\n</script>\r\n```\r\n\r\nCopy the standalone artifact from the published package into your application's versioned\r\nstatic assets. The entry bundles JSZip and the ECharts chart types supported by this\r\nrenderer; PDF.js remains an optional external asset. A pinned CDN URL can also be used,\r\nbut the project does not depend on or deploy through a CDN provider.\r\n\r\nFor large decks, combine windowed mounting with on-demand slide parsing and media decoding:\r\n\r\n```ts\r\nconst viewer = await PptxViewer.open(buffer, container, {\r\n  zipLimits: RECOMMENDED_ZIP_LIMITS,\r\n  lazySlides: true,\r\n  lazyMedia: true,\r\n  listOptions: { windowed: true, initialSlides: 4, batchSize: 4 },\r\n});\r\n```\r\n\r\n### Optional PDF.js Fallback for SmartArt/EMF Preview Images\r\n\r\nPowerPoint often stores SmartArt or pasted vector artwork as EMF fallback images. This\r\nlibrary does **not** implement a full EMF/WMF vector renderer. It can render the common\r\nOffice fallback cases where an EMF contains an embedded PDF preview or bitmap preview.\r\n\r\nFor EMF files with embedded PDF previews, install `pdfjs-dist` and pass explicit asset\r\nURLs. This keeps PDF.js optional and avoids forcing every consumer bundle to include it.\r\nThis is only needed for EMF-PDF fallback previews; ordinary PPTX rendering does not\r\nrequire any code changes.\r\n\r\n```ts\r\nimport { PptxViewer } from '@aiden0z/pptx-renderer';\r\n\r\nconst pdfjs = {\r\n  moduleUrl: new URL('pdfjs-dist/build/pdf.min.mjs', import.meta.url).toString(),\r\n  workerUrl: new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString(),\r\n};\r\n\r\nconst viewer = await PptxViewer.open(buffer, container, {\r\n  pdfjs,\r\n});\r\n```\r\n\r\nIf your app uses a CDN or pre-copied assets, point those fields at your hosted files.\r\nSet `pdfjs: false` to disable EMF-PDF fallback rendering entirely. With no `pdfjs`\r\nconfiguration, the renderer attempts only a best-effort automatic resolution and\r\notherwise degrades gracefully.\r\n\r\n```ts\r\ntype PdfjsConfig =\r\n  | {\r\n      moduleUrl?: string;\r\n      workerUrl?: string;\r\n    }\r\n  | false;\r\n```\r\n\r\nFor a no-bundler deployment, copy the two PDF.js files into your application's static\r\nassets and pass absolute self-hosted URLs:\r\n\r\n```ts\r\nconst pdfjs = {\r\n  moduleUrl: '/vendor/pdfjs/pdf.min.mjs',\r\n  workerUrl: '/vendor/pdfjs/pdf.worker.min.mjs',\r\n};\r\n```\r\n\r\nThe PDF fallback uses short-lived blob Workers. A restrictive Content Security Policy\r\nmust allow the chosen asset origin and `blob:` Workers, for example:\r\n\r\n```text\r\nscript-src 'self';\r\nworker-src 'self' blob:;\r\nimg-src 'self' data: blob:;\r\n```\r\n\r\nIf a CDN is used instead, pin exact package versions and add only that origin to the\r\nrelevant CSP directives.\r\n\r\nOr with more control over each step:\r\n\r\n```ts\r\nimport {\r\n  PptxViewer,\r\n  parseZip,\r\n  buildPresentation,\r\n  RECOMMENDED_ZIP_LIMITS,\r\n} from '@aiden0z/pptx-renderer';\r\n\r\nconst container = document.getElementById('pptx-container')!;\r\nconst viewer = new PptxViewer(container, { fitMode: 'contain' });\r\n\r\nconst files = await parseZip(arrayBuffer, RECOMMENDED_ZIP_LIMITS);\r\nconst presentation = buildPresentation(files);\r\nviewer.load(presentation);\r\nawait viewer.renderList({ windowed: true, batchSize: 8 });\r\n```\r\n\r\nTo delay both media decompression and slide node parsing until rendered slides actually need\r\nthem, use `parseZipLazyMedia()` and pass `{ lazySlides: true }` to `buildPresentation()`:\r\n\r\n```ts\r\nimport {\r\n  PptxViewer,\r\n  parseZipLazyMedia,\r\n  buildPresentation,\r\n  RECOMMENDED_ZIP_LIMITS,\r\n} from '@aiden0z/pptx-renderer';\r\n\r\nconst files = await parseZipLazyMedia(arrayBuffer, RECOMMENDED_ZIP_LIMITS);\r\nconst presentation = buildPresentation(files, { lazySlides: true });\r\n\r\nconst viewer = new PptxViewer(container);\r\nviewer.load(presentation);\r\nawait viewer.renderList({ windowed: true, initialSlides: 4 });\r\n```\r\n\r\n## API\r\n\r\n### `PptxViewer` (primary, extends `EventTarget`)\r\n\r\n#### `PptxViewer.open(input, container, options?)` — Static Factory\r\n\r\nParse, build, and render in one call. Returns a `Promise<PptxViewer>`.\r\n\r\n```ts\r\nconst viewer = await PptxViewer.open(buffer, container, {\r\n  renderMode: 'list', // 'list' (default) | 'slide'\r\n  zipLimits: RECOMMENDED_ZIP_LIMITS,\r\n  listOptions: { windowed: true, batchSize: 8 },\r\n  signal: abortController.signal, // optional AbortSignal\r\n  // ...ViewerOptions\r\n});\r\n```\r\n\r\n#### `new PptxViewer(container, options?)`\r\n\r\n| Option               | Type                       | Default       | Description                                                                                                       |\r\n| -------------------- | -------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------- |\r\n| `width`              | `number`                   | --            | Container width hint (omit for auto-detect)                                                                       |\r\n| `fitMode`            | `'contain' \\| 'none'`      | `'contain'`   | Responsive fit or fixed size                                                                                      |\r\n| `zoomPercent`        | `number`                   | `100`         | Zoom level (10–400)                                                                                               |\r\n| `scrollContainer`    | `HTMLElement`              | --            | Scroll container for IntersectionObserver root                                                                    |\r\n| `zipLimits`          | `ZipParseLimits`           | --            | Security limits for ZIP parsing (used by `.open()`). Use `RECOMMENDED_ZIP_LIMITS` for untrusted input.            |\r\n| `lazyMedia`          | `boolean`                  | `false`       | Decode embedded media on demand instead of during ZIP parsing. Best for large decks with windowed list rendering. |\r\n| `lazySlides`         | `boolean`                  | `false`       | Parse slide shape/table/chart nodes on demand. Best for large decks with windowed list rendering.                 |\r\n| `pdfjs`              | `PdfjsConfig`              | --            | Optional PDF.js URLs for EMF-embedded PDF fallback rendering, or `false` to disable it.                           |\r\n| `embeddedFontLimits` | `EmbeddedFontLimits`       | safe defaults | Optional embedded-font resource limit overrides. Omitted fields retain the built-in defaults.                     |\r\n| `onSlideChange`      | `(index) => void`          | --            | Shorthand for `slidechange` event                                                                                 |\r\n| `onSlideRendered`    | `(index, element) => void` | --            | Shorthand for `sliderendered` event                                                                               |\r\n| `onSlideError`       | `(index, error) => void`   | --            | Shorthand for `slideerror` event                                                                                  |\r\n| `onSlideUnmounted`   | `(index) => void`          | --            | Shorthand for `slideunmounted` event                                                                              |\r\n| `onNodeError`        | `(nodeId, error) => void`  | --            | Shorthand for `nodeerror` event                                                                                   |\r\n| `onRenderStart`      | `() => void`               | --            | Shorthand for `renderstart` event                                                                                 |\r\n| `onRenderComplete`   | `() => void`               | --            | Shorthand for `rendercomplete` event                                                                              |\r\n\r\nAll shorthand callbacks are also available as `EventTarget` events (e.g. `viewer.addEventListener('slidechange', ...)`).\r\n\r\nEmbedded font decompression is bounded by default. Trusted applications can provide partial\r\n`embeddedFontLimits` overrides; see the [performance guide](docs/PERFORMANCE.md#embedded-font-limits)\r\nfor defaults, examples, and the soft processing-time boundary.\r\n\r\n#### Instance Methods\r\n\r\n```ts\r\nviewer.load(presentation);                              // Load a PresentationData model (no render)\r\nawait viewer.renderList({ windowed: true });             // Render all slides in scrollable list\r\nawait viewer.renderSlide(0);                             // Render a single slide (no built-in nav UI)\r\n\r\n// Load from binary input (parse → build → render). Cleans up previous state on re-open.\r\nawait viewer.open(buffer, { renderMode: 'list', signal: abortController.signal });\r\n\r\nawait viewer.goToSlide(index);                           // Jump to slide (0-based), returns Promise<void>\r\nawait viewer.goToSlide(index, { behavior: 'instant' }); // Custom ScrollIntoViewOptions (list mode)\r\nawait viewer.setZoom(150);                               // Runtime zoom (10–400)\r\nawait viewer.setFitMode('none');                         // Switch fit mode\r\nconst matches = viewer.searchText('GPU');                 // Search parsed model text\r\nconst hit = await viewer.highlightSearchResult(matches[0]); // Default node overlay highlight\r\nhit?.dispose();\r\nviewer.clearSearchHighlights();                          // Remove active search overlays\r\n\r\n// Render a single slide into an external container (React/Vue integration, thumbnails).\r\n// Returns a SlideHandle; caller owns it and must call handle.dispose() when done.\r\nconst handle = viewer.renderSlideToContainer(index, container, scale?);\r\nhandle.dispose();                                        // Clean up slide-specific resources\r\n\r\n// Render a lightweight scaled slide preview into an external container.\r\n// This preserves the original slide layout and uses transform scaling; it is\r\n// not a bitmap thumbnail generator, so use lazy/windowed mounting for decks.\r\nconst thumb = viewer.renderThumbnailToContainer(index, sidebarItem, { width: 180 });\r\nawait thumb?.ready;\r\nthumb?.dispose();\r\n\r\n// Query which slides are currently mounted in the DOM\r\nviewer.isSlideMounted(index);   // boolean\r\nviewer.getMountedSlides();      // number[] (sorted)\r\n\r\n// Typed event helpers (return `this` for chaining)\r\nviewer.on('slidechange', (e) => console.log(e.detail.index));\r\nviewer.off('slidechange', listener);\r\n\r\nviewer.destroy();               // Cleanup blob URLs, observers, and DOM\r\nviewer[Symbol.dispose]();       // TC39 Explicit Resource Management (calls destroy)\r\n```\r\n\r\n#### Text Search\r\n\r\n`PptxViewer.searchText(query, options?)` searches the parsed `PresentationData` model,\r\nnot the rendered DOM. This keeps search available before or after a slide is mounted and\r\navoids mutating renderer-generated text runs.\r\n\r\nString queries are case-insensitive by default. Pass `matchCase: true` when you need\r\nexact casing. RegExp queries keep their own flags, so `/GPU/` remains case-sensitive\r\nand `/GPU/i` remains case-insensitive.\r\n\r\n```ts\r\nimport type { TextSearchResult } from '@aiden0z/pptx-renderer';\r\n\r\nconst matches: TextSearchResult[] = viewer.searchText('GPU', {\r\n  matchCase: false,\r\n  wholeWord: true,\r\n  snippetRadius: 48,\r\n});\r\n\r\nconst exactMatches = viewer.searchText('GPU', { matchCase: true });\r\nconst regexMatches = viewer.searchText(/GPU|CPU/i);\r\n\r\nfor (const match of matches) {\r\n  await viewer.goToSlide(match.slideIndex, { behavior: 'smooth', block: 'center' });\r\n  // Use match.bounds for node-level highlight overlays in your own UI.\r\n}\r\n```\r\n\r\nEach `TextSearchResult` includes `slideIndex`, `nodeId`, `nodePath`, `nodeType`,\r\n`textKind`, full `text`, `matchStart`, `matchEnd`, `snippet`, and `bounds`.\r\n`bounds` is the matched shape or table bounds in intrinsic slide coordinates, so\r\napplication code can draw node-level highlight overlays on top of a rendered slide.\r\n\r\nThe renderer intentionally does not rewrite text nodes for character-level text highlighting.\r\nCharacter-level text highlighting would require mapping match offsets back to shaped Office\r\ntext runs and line layout, which is a separate, higher-risk renderer feature. Today the\r\nstable API boundary is model-level search plus node-level bounds.\r\n\r\nFor the common UI case, `highlightSearchResult(result, options?)` draws a node-level\r\noverlay using a default highlight style. Pass `SearchHighlightOptions` for custom colors,\r\nspacing, shadows, and class names:\r\n\r\n```ts\r\nconst hit = await viewer.highlightSearchResult(matches[0], {\r\n  className: 'my-search-hit',\r\n  borderColor: '#22c55e',\r\n  backgroundColor: 'rgba(34, 197, 94, 0.18)',\r\n  borderRadius: 6,\r\n  borderWidth: 2,\r\n  boxShadow: '0 0 0 2px rgba(15, 23, 42, 0.35)',\r\n  padding: 3,\r\n});\r\n\r\n// The caller owns returned highlight handles.\r\nhit?.dispose();\r\nviewer.clearSearchHighlights();\r\n```\r\n\r\n#### Scaled Slide Previews\r\n\r\n`PptxViewer.renderThumbnailToContainer(index, container, options?)` renders a slide at\r\nits intrinsic layout size and applies CSS transform scaling inside a clipped wrapper.\r\nThis avoids the layout drift that can happen if a PPTX slide is rendered directly into a\r\nsmall thumbnail-sized container.\r\n\r\n```ts\r\nconst thumb = viewer.renderThumbnailToContainer(slideIndex, thumbnailEl, { width: 96 });\r\nawait thumb?.ready;\r\n\r\n// The caller owns externally rendered previews.\r\nthumb?.dispose();\r\n```\r\n\r\nThis is not a bitmap thumbnail generator: it still creates a scaled DOM/SVG slide\r\npreview, so large decks should mount previews lazily with `IntersectionObserver` or a\r\nwindowed list and dispose handles when they scroll out of view.\r\n\r\n#### `ListRenderOptions`\r\n\r\n| Option             | Type      | Default | Description                        |\r\n| ------------------ | --------- | ------- | ---------------------------------- |\r\n| `windowed`         | `boolean` | `false` | Use IntersectionObserver windowing |\r\n| `batchSize`        | `number`  | `12`    | Slides per render batch            |\r\n| `initialSlides`    | `number`  | `4`     | Initial slides to mount (windowed) |\r\n| `overscanViewport` | `number`  | `1.5`   | Viewport overscan multiplier       |\r\n\r\n#### `ZipParseLimits` and Resource Safety\r\n\r\n`parseZip(buffer)` defaults to no ZIP limits for backward compatibility. For files from users or other untrusted sources, pass `RECOMMENDED_ZIP_LIMITS` or stricter values:\r\n\r\n| Limit                       | Recommended value | What it protects                            |\r\n| --------------------------- | ----------------- | ------------------------------------------- |\r\n| `maxEntries`                | `4000`            | Archives with excessive file counts         |\r\n| `maxEntryUncompressedBytes` | `32 MiB`          | A single oversized XML/media entry          |\r\n| `maxTotalUncompressedBytes` | `256 MiB`         | Total decompressed archive size             |\r\n| `maxMediaBytes`             | `192 MiB`         | Total media payload size under `ppt/media/` |\r\n| `maxConcurrency`            | `8`               | Parallel ZIP entry reads                    |\r\n\r\nWhen ZIP metadata does not expose a reliable uncompressed size, the parser falls back to the actual decoded entry size before accepting the entry. This keeps `maxEntryUncompressedBytes`, `maxTotalUncompressedBytes`, and `maxMediaBytes` effective for XML/text entries and media entries alike.\r\n\r\nRenderer-level guards also apply after ZIP parsing:\r\n\r\n- Chart caches do not allocate from oversized `c:ptCount`; chart point indexes are capped at `10,000` per cache.\r\n- EMF bitmap previews are rejected when decoded size exceeds `16,777,216` pixels, dimensions exceed `8192x8192`, or pixel payload is shorter than the declared bitmap.\r\n- External audio/video relationships only render for safe `http`/`https` URLs with `TargetMode=\"External\"` and are created with `preload=\"none\"`.\r\n\r\n#### Events (`PptxViewerEventMap`)\r\n\r\n```ts\r\nviewer.addEventListener('renderstart', () => {\r\n  /* render cycle began */\r\n});\r\nviewer.addEventListener('rendercomplete', () => {\r\n  /* render cycle finished (fires even on error) */\r\n});\r\nviewer.addEventListener('slidechange', (e) => console.log(e.detail.index));\r\nviewer.addEventListener('sliderendered', (e) => console.log(e.detail.index, e.detail.element));\r\nviewer.addEventListener('slideerror', (e) => console.error(e.detail.index, e.detail.error));\r\nviewer.addEventListener('slideunmounted', (e) => console.log(e.detail.index));\r\nviewer.addEventListener('nodeerror', (e) => console.warn(e.detail.nodeId, e.detail.error));\r\n```\r\n\r\n`slidechange` fires both on `goToSlide()` navigation and after each render cycle (initial render included). `renderstart`/`rendercomplete` bracket every render cycle (renderList, renderSlide, setZoom, setFitMode). When calls overlap, the newer render request supersedes older queued or batched work, so stale list batches stop before appending more DOM.\r\n\r\n#### Instance Properties (read-only)\r\n\r\n```ts\r\nviewer.presentationData; // PresentationData | null — the parsed model, null before load()\r\nviewer.slideCount; // number — total slides (0 if not loaded)\r\nviewer.slideWidth; // number — intrinsic slide width in px\r\nviewer.slideHeight; // number — intrinsic slide height in px\r\nviewer.currentSlideIndex; // number — currently active slide (0-based)\r\nviewer.isRendering; // boolean — true between renderstart and rendercomplete\r\nviewer.zoomPercent; // number — current zoom level (e.g. 100, 200)\r\nviewer.fitMode; // FitMode — current fit mode ('contain' | 'none')\r\n```\r\n\r\n### `PptxRenderer` (deprecated v1 compat)\r\n\r\n`PptxRenderer` extends `PptxViewer` and provides the legacy `preview(input)` API with built-in nav buttons in slide mode. Migrate to `PptxViewer` for new code.\r\n\r\n```ts\r\nimport { PptxRenderer } from '@aiden0z/pptx-renderer';\r\n\r\nconst renderer = new PptxRenderer(container, { mode: 'list', listMountStrategy: 'windowed' });\r\nawait renderer.preview(buffer); // deprecated — use PptxViewer.open() instead\r\n```\r\n\r\n`PptxRenderer` accepts the same optional `lazyMedia`, `lazySlides`, and `pdfjs`\r\nconfiguration as `PptxViewer`, so legacy users can enable performance options or\r\nEMF-PDF fallback rendering without changing APIs.\r\n\r\n### Utility Exports\r\n\r\n```ts\r\nimport {\r\n  parseZip,\r\n  parseZipLazyMedia,\r\n  buildPresentation,\r\n  materializeAllSlideNodes,\r\n  serializePresentation,\r\n  buildTextIndex,\r\n  searchText,\r\n  searchPresentation,\r\n  RECOMMENDED_ZIP_LIMITS,\r\n} from '@aiden0z/pptx-renderer';\r\n\r\nconst files = await parseZip(arrayBuffer, RECOMMENDED_ZIP_LIMITS); // PptxFiles\r\nconst lazyFiles = await parseZipLazyMedia(arrayBuffer, RECOMMENDED_ZIP_LIMITS); // media resolves on demand\r\nconst presentation = buildPresentation(files); // PresentationData\r\nconst lazyPresentation = buildPresentation(lazyFiles, { lazySlides: true }); // slide nodes parse on demand\r\nmaterializeAllSlideNodes(lazyPresentation); // optional: force full model materialization\r\nconst json = serializePresentation(presentation); // SerializedPresentation (JSON-safe)\r\nconst index = buildTextIndex(presentation); // TextIndexEntry[]\r\nconst matches = searchText(index, '算力'); // TextSearchResult[]\r\nconst directMatches = searchPresentation(presentation, /GPU|CPU/i); // TextSearchResult[]\r\n```\r\n\r\n#### Headless Slide Rendering\r\n\r\nFor advanced use cases (server-side screenshot, custom rendering pipeline):\r\n\r\n```ts\r\nimport { renderSlide } from '@aiden0z/pptx-renderer';\r\nimport type { SlideHandle } from '@aiden0z/pptx-renderer';\r\n\r\nconst handle = renderSlide(presentation, presentation.slides[0], {\r\n  onNodeError: (nodeId, err) => console.warn(nodeId, err),\r\n  mediaUrlCache: new Map(), // optional shared cache for blob URLs\r\n  pdfjs, // optional, only for EMF-embedded PDF fallback rendering\r\n});\r\ndocument.body.appendChild(handle.element);\r\n\r\n// Await async media such as EMF-PDF fallback previews before screenshots/exports.\r\nawait handle.ready;\r\n\r\n// Cancels pending PDF fallbacks and disposes charts + owned blob URLs.\r\nhandle.dispose();\r\n```\r\n\r\n#### Model Types\r\n\r\nAll model types are exported for consumers building custom tooling:\r\n\r\n```ts\r\nimport type {\r\n  PresentationData,\r\n  BuildPresentationOptions,\r\n  SlideData,\r\n  SlideNode,\r\n  ThemeData,\r\n  BaseNodeData,\r\n  ShapeNodeData,\r\n  PicNodeData,\r\n  TableNodeData,\r\n  GroupNodeData,\r\n  ChartNodeData,\r\n  TextBody,\r\n  TextParagraph,\r\n  TextRun,\r\n  Position,\r\n  Size,\r\n  NodeType,\r\n  SerializedPresentation,\r\n  SerializedSlide,\r\n  SerializedNode,\r\n  PptxFiles,\r\n  ZipParseLimits,\r\n  FitMode,\r\n  PreviewInput,\r\n  ViewerOptions,\r\n  ListRenderOptions,\r\n  ThumbnailRenderOptions,\r\n  SearchHighlightHandle,\r\n  SearchHighlightOptions,\r\n  PptxViewerEventMap,\r\n  SlideHandle,\r\n  PdfjsOptions,\r\n  PdfjsConfig,\r\n  TextBounds,\r\n  TextIndexEntry,\r\n  TextIndexOptions,\r\n  TextSearchOptions,\r\n  TextSearchResult,\r\n} from '@aiden0z/pptx-renderer';\r\n```\r\n\r\n## Rendering Capabilities\r\n\r\n### Shapes — 187+ Presets + Custom Geometry\r\n\r\nAll commonly used OOXML `DrawingML` preset shapes, organized by category:\r\n\r\n| Category          | Count | Highlights                                                     |\r\n| ----------------- | ----: | -------------------------------------------------------------- |\r\n| Basic & Geometric |    70 | Rectangles, ovals, polygons, stars, arcs, clouds, gears, etc.  |\r\n| Flowchart         |    30 | All standard flowchart shapes                                  |\r\n| Arrows            |    22 | Directional, bent, curved, striped, chevron                    |\r\n| Stars & Banners   |    17 | N-point stars, explosions, ribbons, scrolls                    |\r\n| Callouts          |    17 | Rectangular, rounded, oval, cloud, line callout variants       |\r\n| Connectors        |    12 | Straight, bent, curved (2-5 segments)                          |\r\n| Action Buttons    |     9 | Multi-path 3D with darken/lighten face modifiers               |\r\n| Math & Brackets   |    12 | Plus, minus, multiply, division, brackets, braces              |\r\n| **Multi-path 3D** |   33+ | Bevel, cube, can, ribbons — multi-layer SVG with 3D appearance |\r\n\r\nCustom geometry (`<a:custGeom>`) is also supported via a general-purpose OOXML path interpreter.\r\n\r\n### Text — 7-Level Style Inheritance\r\n\r\nFull OOXML text cascade: master → layout → shape → paragraph → run. Supports theme fonts, numbered/symbol/picture bullets, multi-level indent, vertical text, superscript/subscript, hyperlinks, and per-shape text insets.\r\n\r\n### Charts via ECharts\r\n\r\nPowered by [ECharts](https://echarts.apache.org/). Supports Bar/Column (clustered, stacked, 100% stacked), Line/Area (standard, stacked, 100% stacked), Pie, multi-ring Doughnut, Radar, Scatter, Bubble, and Stock/Candlestick charts, with axis labels, legends, data labels, grid lines, chart color-style palettes, marker symbols, and custom number formats.\r\n\r\nThe renderer registers only the ECharts charts, components, features, and Canvas renderer\r\nthat it uses. Bundler consumers keep ECharts external; the standalone browser entry\r\ncontains this same tree-shakeable runtime.\r\n\r\nOOXML 3D chart elements such as `bar3DChart`, `line3DChart`, `pie3DChart`, `area3DChart`, and `surface3DChart` are parsed as graceful 2D fallbacks where possible. True 3D perspective, depth walls, and surface meshes are not PowerPoint-perfect.\r\n\r\n### Fill, Stroke & Color\r\n\r\n- **Fills**: solid, linear/radial/rectangular gradient, 52+ pattern fills, image (stretch/tile)\r\n- **Strokes**: 8 dash styles, 5 arrowhead types, compound lines, line joins\r\n- **Colors**: full OOXML pipeline — `schemeClr` → `colorMap` remap → theme lookup → modifiers (lumMod, lumOff, tint, shade, alpha, satMod, etc.). All 6 color spaces supported.\r\n\r\n### SmartArt, Tables, Images & More\r\n\r\n- **SmartArt**: 134+ layouts via PowerPoint fallback data. EMF-embedded PDF previews can be rendered with optional [pdfjs-dist](https://mozilla.github.io/pdf.js/) configuration.\r\n- **Tables**: OOXML table styles, cell merge, border inheritance\r\n- **Images**: blob URL with crop, stretch/tile, video/audio placeholders\r\n- **Groups**: coordinate remapping with recursive child rendering\r\n- **Backgrounds**: slide → layout → master inheritance chain\r\n\r\n## Architecture\r\n\r\nThree-layer pipeline: **Parse -> Model -> Render**\r\n\r\n```\r\nArrayBuffer (.pptx)\r\n  -> ZipParser (jszip extraction)\r\n  -> XmlParser (DOMParser + SafeXmlNode null-safe wrapper)\r\n  -> buildPresentation() (assembles slides/layouts/masters/themes with relationship chains)\r\n  -> SlideRenderer (background -> master shapes -> layout shapes -> slide shapes -> DOM)\r\n```\r\n\r\nKey design decisions:\r\n\r\n- **SafeXmlNode**: Null-safe XML traversal — returns empty nodes instead of null, enabling deep chaining without null checks.\r\n- **Lazy slide parsing**: Optional `lazySlides` mode keeps per-slide nodes deferred until render, search, serialization, or explicit materialization.\r\n- **Lazy group parsing**: Group children stored as raw XML, parsed during rendering to avoid deep recursion in model layer.\r\n- **Error isolation**: Per-node try/catch. A failed shape renders as a dashed-red placeholder; the slide continues.\r\n- **No external CSS**: All styles inline. The library outputs self-contained HTML fragments.\r\n- **Blob URL lifecycle**: Created for images/media, tracked in `mediaUrlCache`, revoked on `destroy()`.\r\n\r\n## Performance\r\n\r\nThe default behavior stays eager for compatibility: parse the package, build the full\r\nmodel, and render according to the selected mode. For large or media-heavy decks, opt\r\ninto the lazy/windowed path so the first visible slides can render without materializing\r\nevery slide and every media entry up front.\r\n\r\nUse this preset for interactive viewers:\r\n\r\n```ts\r\nconst viewer = await PptxViewer.open(buffer, container, {\r\n  zipLimits: RECOMMENDED_ZIP_LIMITS,\r\n  lazySlides: true,\r\n  lazyMedia: true,\r\n  listOptions: {\r\n    windowed: true,\r\n    batchSize: 8,\r\n    initialSlides: 4,\r\n    overscanViewport: 1.5,\r\n  },\r\n});\r\n```\r\n\r\nRecommended choices:\r\n\r\n| Scenario                              | Recommended options                                     | Main benefit                                 |\r\n| ------------------------------------- | ------------------------------------------------------- | -------------------------------------------- |\r\n| User-uploaded PPTX                    | `zipLimits: RECOMMENDED_ZIP_LIMITS`                     | Bounds ZIP parsing work and decoded payloads |\r\n| Long scrollable viewer                | `listOptions.windowed: true`                            | Keeps off-screen slides out of the DOM       |\r\n| Large decks with many slide elements  | `lazySlides: true` plus windowed list rendering         | Defers per-slide node parsing until needed   |\r\n| Media-heavy decks                     | `lazyMedia: true` plus windowed list rendering          | Defers image/audio/video byte decoding       |\r\n| Export, print, or full comparison job | Eager defaults, or explicitly materialize before export | Ensures all slides are ready in one pass     |\r\n\r\nIn local benchmarks, `lazySlides` reduced model build time by roughly 52-66% on medium\r\nand large decks, and lowered first-window parse + build + render time by roughly 16-22%.\r\nFor media-heavy windowed viewers, `lazyMedia` reduced initially decompressed media bytes\r\nby about 72-97%, depending on deck content. These options preserve rendering semantics;\r\nthey mainly move work from initial load to the moment a slide or media item is actually\r\nneeded.\r\n\r\nManual pipelines can use the same building blocks:\r\n\r\n```ts\r\nconst files = await parseZipLazyMedia(buffer, RECOMMENDED_ZIP_LIMITS);\r\nconst presentation = buildPresentation(files, { lazySlides: true });\r\n\r\nconst viewer = new PptxViewer(container);\r\nviewer.load(presentation);\r\nawait viewer.renderList({ windowed: true, initialSlides: 4 });\r\n```\r\n\r\nDetails: [`docs/PERFORMANCE.md`](docs/PERFORMANCE.md)\r\n\r\n## Security\r\n\r\n- Treat PPTX input as untrusted. Start with `RECOMMENDED_ZIP_LIMITS`, then tighten for your deployment.\r\n- External hyperlinks are protocol-filtered (no `javascript:`, `data:`, etc.).\r\n- Reporting: [`docs/SECURITY.md`](docs/SECURITY.md)\r\n\r\n## Development\r\n\r\n```bash\r\npnpm install\r\npnpm dev          # Vite dev server\r\npnpm test         # Unit tests (vitest)\r\npnpm test:coverage # Coverage report → coverage/\r\npnpm build        # Production build\r\npnpm test:package # Verify ESM, CJS, and standalone package entries\r\npnpm test:browser # Real Chromium checks for standalone, charts, and PDF.js\r\npnpm dev:e2e      # Dev server + Python E2E API server\r\npnpm lint         # ESLint\r\npnpm typecheck    # tsc --noEmit\r\npnpm knip         # Dead code / unused exports detection\r\n```\r\n\r\nDev pages at `http://127.0.0.1:5173`:\r\n\r\n| Page                            | Purpose                                   |\r\n| ------------------------------- | ----------------------------------------- |\r\n| `/test/pages/index.html`        | Upload preview with search and thumbnails |\r\n| `/test/pages/render-slide.html` | Single slide at native resolution         |\r\n| `/test/pages/e2e-compare.html`  | Side-by-side PDF vs HTML with SSIM scores |\r\n| `/test/pages/export.html`       | Model JSON tree viewer                    |\r\n\r\n## Documentation\r\n\r\n| Doc                                       | Content                                                                |\r\n| ----------------------------------------- | ---------------------------------------------------------------------- |\r\n| [`ARCHITECTURE.md`](docs/ARCHITECTURE.md) | Parse/model/render pipeline design                                     |\r\n| [`PERFORMANCE.md`](docs/PERFORMANCE.md)   | Tuning options and presets                                             |\r\n| [`TESTING.md`](docs/TESTING.md)           | Unit/E2E strategy, two-layer metric system, visual regression workflow |\r\n| [`CONTRIBUTING.md`](docs/CONTRIBUTING.md) | PR checklist, code quality tools, and workflow                         |\r\n| [`SECURITY.md`](docs/SECURITY.md)         | Vulnerability reporting                                                |\r\n\r\n## What's Not Yet Supported\r\n\r\n3D effects, true 3D chart perspective/depth/surface meshes, animations/transitions, equations (OMML), full EMF/WMF vector rendering, shadow/reflection/glow effects, embedded OLE objects, and slide notes rendering.\r\n\r\n## FAQ\r\n\r\n**Does this run on Node.js?**\r\nNo. Rendering depends on browser DOM APIs.\r\n\r\n**Why is my PPTX rendering incomplete?**\r\nOOXML is a vast spec. Please open a compatibility issue with a minimal PPTX sample — the visual regression pipeline makes it straightforward to add coverage for new cases.\r\n\r\n**How do I render 100+ slide decks efficiently?**\r\nUse `windowed: true` in `listOptions`, and enable `lazySlides: true`. For media-heavy\r\ndecks, also enable `lazyMedia: true`.\r\n\r\n## License\r\n\r\nApache License 2.0. See `LICENSE`.\r\n","readmeFilename":"README.md"}