🖼️ fix: Show EMF and WMF Pictures in PowerPoint Previews - #16672
Open
TomasPalsson wants to merge 1 commit into
Open
TomasPalsson wants to merge 1 commit into
TomasPalsson wants to merge 1 commit into
Conversation
pptx-preview turns EMF/WMF media into data:image/x-emf and data:image/x-wmf URIs, which no browser decodes, so template logos stored as Windows metafiles render as broken images in the artifact panel. Convert them to SVG on the server with emf-converter (pinned 4.8.7, Apache-2.0, no dependencies), embed the SVGs keyed by a hash of each entry's base64, and swap them into the rendered <img> elements when the bootstrap finalizes. Conversion is bounded per entry (512 KB, 10,000 records) and per deck (64 entries, 8 MB input, 1 s, 384 KB of SVG). If the map would push a deck past the preview cap, the deck is rendered without it, so no deck that previews today changes path.
TomasPalsson
added a commit
to aproorg/LibreChat
that referenced
this pull request
Oct 2, 2026
pptx-preview turns EMF/WMF media into data:image/x-emf and data:image/x-wmf URIs, which no browser decodes, so template logos stored as Windows metafiles render as broken images in the artifact panel. Convert them to SVG on the server with emf-converter (pinned 4.8.7, Apache-2.0, no dependencies), embed the SVGs keyed by a hash of each entry's base64, and swap them into the rendered <img> elements when the bootstrap finalizes. Conversion is bounded per entry (512 KB, 10,000 records) and per deck (64 entries, 8 MB input, 1 s, 384 KB of SVG). If the map would push a deck past the preview cap, the deck is rendered without it, so no deck that previews today changes path. (cherry picked from commit 6b076b2) Upstream: LibreChat-AI#16672. Adapted for the storage-backed file shell on this branch: buildPptxCdnDocument/pptxToHtmlViaCdn keep their fileShell parameter, a shell over the cap retries without the map, and layout.spec.ts/html.spec.ts cover the shell path.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a code run produces a PowerPoint deck built from a corporate template, logos stored as Windows metafiles (
.emf/.wmf) show as broken-image icons in the artifact panel.pptx-preview@1.0.7loads everyppt/media/image*entry into adata:URI with the typeimage/x-emforimage/x-wmf, and no browser can decode either format. Templates exported from PowerPoint commonly keep their logos this way, usually in the slide layouts, so the broken icon repeats on almost every slide. A real template that prompted this has 8 of its 11 media files as EMF.This PR converts those metafiles to SVG on the server while the preview HTML is built, embeds the SVGs in the preview document, and swaps them into the rendered
<img>elements once the slides are drawn. Decks without metafiles render exactly as before.How it works
The key works because pptx-preview builds each image source as
"data:" + mime + ";base64," + zip.async("base64"), which is the same standard base64 thatBuffer.toString('base64')produces, so the iframe hashes the part after the comma and looks it up. The hash is defined once in TypeScript (metafileKey) and once as ES5 source for the bootstrap (METAFILE_KEY_JS), and a test runs both on the same inputs.swapMetafilesis wrapped intry/catchand runs insidefinalize(), so a failure can never reveal the slide-list fallback.Conversion is synchronous CPU work, so it is bounded. Per entry: 512 KB of input and at most 10,000 records, counted by walking the record headers before converting. Per deck: 64 entries, 8 MB of input, 1 s of conversion (checked between entries), 256 KB per SVG and 384 KB of SVG in total. An entry over a limit, or one the converter rejects, keeps its original source, which is the same broken image as today. If the embedded map would push the document past
OFFICE_HTML_OUTPUT_CAP, the document is rebuilt without it before any existing fallback runs, so no deck that gets the rendered preview today drops to the slide list.Type of change
Testing
Tested environments/configuration:
pptx-preview@1.0.7from jsDelivr.devand with this branch:dev(img loaded / broken)lc-metafilesblock emitted)Automated tests:
metafiles.spec.ts: EMF and WMF conversion, name filtering (onlyppt/media/image*), corrupt bytes skipped without throwing, a non-zip buffer resolves{}, the per-entry byte cap, the record budget, the time budget, and parity betweenmetafileKeyandMETAFILE_KEY_JS.metafiles.runtime.spec.ts: runs the real bootstrap in JSDOM against a fakepptxPreviewthat renders an EMF<img>and a PNG<img>. The EMF source becomes an SVG data URI, the PNG is untouched, and the fallback stays hidden. A deck without metafiles is left as is. Removing theswapMetafiles()call fails this spec.html.spec.ts: the map is emitted, a hostile map value cannot close the<script>, decks without metafiles emit no map, and an over-cap map falls back to the map-free document.<escaping, ignoring.wmf, removing the time budget, or returning the over-cap document each fail at least one test.cd packages/api && npx jest --ci --testPathIgnorePatterns='\.*integration\.|\.*helper\.|__tests__/helpers/|\.*manual\.spec\.' -- src/files: 1143 passed.libreoffice.spec.tshas 3 failures on this machine that also fail on unmodifieddev: LibreOffice is installed locally while in-process conversion is disabled, so the spec does not skip.npx tsc --noEmit, ESLint and Prettier are clean on the changed files.Screenshots / recordings
The first slide of D1, rendered in the preview iframe at the same size from
devand from this branch. The logo is a synthetic three-shape vector EMF placed in the slide layout.Before (
dev): a broken-image icon where the layout logo should be. After (this branch): the vector logo rendered as SVG. Screenshots are being added through the web editor.Risk / compatibility
emf-converter@4.8.7is pinned exactly inpackages/apiandapi. It is Apache-2.0 and has no dependencies.@napi-rs/canvasis an optional peer and is not installed, so the few EMF+ records that need it fail to convert and keep today's broken image.<img>pictures are swapped. Metafiles used as a slide background or a shape fill, which pptx-preview paints through CSS or an SVG pattern, still show nothing, as today. Media whose file name does not start withimageis never loaded by pptx-preview, so it is not converted either. DOCX previews are unchanged.img-srcalready allowsdata:. SVG shown through<img>cannot run script or load remote resources, and emf-converter only emitsdata:URLs for embedded bitmaps. The JSON map is escaped against</script>break-out.Checklist