Fixes cases where images end up clipped when taking screenshots for pdf downloads#7933
Fixes cases where images end up clipped when taking screenshots for pdf downloads#7933
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Centralizes screenshot/download logic to prevent cell output images from being clipped during screenshot capture (used for PDF export and PNG downloads).
Changes:
- Introduces shared helpers to locate cell output elements, apply “printing” CSS, and capture PNG data URLs.
- Refactors screenshot capture in export hooks to reuse the centralized helper.
- Updates UI call sites to the new
downloadHTMLAsImage({ ... })API and adds/extends tests and e2e fixture content.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/utils/download.ts | Adds cell screenshot helpers + refactors downloadHTMLAsImage to accept an options object and optional preparation/cleanup. |
| frontend/src/utils/tests/download.test.tsx | Adds tests for new cell screenshot/download helpers and updated downloadHTMLAsImage API. |
| frontend/src/core/export/hooks.ts | Switches screenshot capture to use getImageDataUrlForCell. |
| frontend/src/components/export/export-output-button.tsx | Removes old cell output PNG download helper (superseded by utils). |
| frontend/src/components/editor/renderers/vertical-layout/vertical-layout.tsx | Updates call site to new downloadHTMLAsImage({ element, filename }) signature. |
| frontend/src/components/editor/actions/useNotebookActions.tsx | Updates call site to new downloadHTMLAsImage({ ... }) signature; adjusts PDF action configuration. |
| frontend/src/components/editor/actions/useCellActionButton.tsx | Switches “Export output as PNG” to downloadCellOutputAsImage. |
| frontend/e2e-tests/py/kitchen_sink.py | Updates e2e notebook fixture and adds Plotly output + config flags for export coverage. |
Comments suppressed due to low confidence (1)
frontend/src/core/export/hooks.ts:147
getImageDataUrlForCellalready catchestoPngfailures and returnsundefined, so this surroundingtry/catchinuseEnrichCellOutputswill no longer catch screenshot errors (and existing error logging becomes misleading). Consider either lettinggetImageDataUrlForCellthrow (and handle/log here), or remove this outertry/catchand rely on a single, consistent logging path (ideally including the original error object).
try {
const dataUrl = await getImageDataUrlForCell(cellId);
if (!dataUrl) {
Logger.error(`Failed to capture screenshot for cell ${cellId}`);
return null;
}
return [cellId, ["image/png", dataUrl]] as [
CellId,
["image/png", string],
];
} catch (error) {
Logger.error(`Error screenshotting cell ${cellId}:`, error);
return null;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
frontend/src/utils/download.ts
Outdated
| let cleanup: (() => void) | undefined; | ||
| if (prepare) { | ||
| cleanup = prepare(element); | ||
| } | ||
| // Typically used for downloading the entire notebook | ||
| document.body.classList.add("printing"); | ||
|
|
||
| // Capture current scroll position | ||
| const appEl = document.getElementById("App"); | ||
| const currentScrollY = appEl?.scrollTop ?? 0; |
There was a problem hiding this comment.
downloadHTMLAsImage captures currentScrollY after prepare() runs and after document.body.classList.add("printing"). If the printing/prepare styles change layout/scroll, the restored scroll position can be incorrect and cause a visible jump. Capture the scroll position before applying any printing/prepare mutations, and then restore to that pre-mutation value in the finally.
| let cleanup: (() => void) | undefined; | |
| if (prepare) { | |
| cleanup = prepare(element); | |
| } | |
| // Typically used for downloading the entire notebook | |
| document.body.classList.add("printing"); | |
| // Capture current scroll position | |
| const appEl = document.getElementById("App"); | |
| const currentScrollY = appEl?.scrollTop ?? 0; | |
| // Capture current scroll position before any layout/printing mutations | |
| const appEl = document.getElementById("App"); | |
| const currentScrollY = appEl?.scrollTop ?? 0; | |
| let cleanup: (() => void) | undefined; | |
| if (prepare) { | |
| cleanup = prepare(element); | |
| } | |
| // Typically used for downloading the entire notebook | |
| document.body.classList.add("printing"); |
frontend/src/utils/download.ts
Outdated
| } catch { | ||
| Logger.error("Failed to capture element as PNG."); |
There was a problem hiding this comment.
The catch in getImageDataUrlForCell swallows the exception and logs a generic message without the underlying error, which makes debugging capture failures difficult. Capture the thrown error (e.g., catch (error)) and include it in the log so callers can diagnose why toPng failed.
| } catch { | |
| Logger.error("Failed to capture element as PNG."); | |
| } catch (error) { | |
| Logger.error(`Failed to capture element as PNG: ${prettyError(error)}`); |
| it("should not add body.printing when prepare is provided", async () => { | ||
| vi.mocked(toPng).mockImplementation(async () => { | ||
| // body.printing should NOT be added by downloadHTMLAsImage when prepare is provided | ||
| // (the prepare function may add it itself) | ||
| return mockDataUrl; | ||
| }); | ||
| const prepare = vi.fn().mockReturnValue(() => { | ||
| // <noop> | ||
| }); | ||
|
|
||
| await downloadHTMLAsImage({ | ||
| element: mockElement, | ||
| filename: "test", | ||
| prepare, | ||
| }); | ||
|
|
||
| // After completion, body.printing should be removed by cleanup, not downloadHTMLAsImage | ||
| expect(document.body.classList.contains("printing")).toBe(false); | ||
| }); |
There was a problem hiding this comment.
This test name/comment asserts that downloadHTMLAsImage should not add body.printing when prepare is provided, but the implementation currently always adds it. The test also doesn't assert anything about the class during capture, so it can’t catch regressions either. Either update the implementation to match the intended behavior or adjust the test to assert the actual contract (and rename the test accordingly).
|
@Light2Dark lets say we have a cell in the marimo notebook, is the PR related to programmatically downloading that cell as a image or PDF from a CLI argument on just human clicking the drop down? |
Hey Yasin, unfortunately no. We did think about adding this programmatically (by running through a headless browser) using the existing convert commands. |
|
��� Development release published. You may be able to view the changes at https://marimo.app?v=0.19.5-dev43 |
…df downloads (marimo-team#7933) ## 📝 Summary <!-- Provide a concise summary of what this pull request is addressing. If this PR closes any issues, list them here by number (e.g., Closes marimo-team#123). --> Enforce consistent logic for taking screenshots and applying css ## 🔍 Description of Changes <!-- Detail the specific changes made in this pull request. Explain the problem addressed and how it was resolved. If applicable, provide before and after comparisons, screenshots, or any relevant details to help reviewers understand the changes easily. --> ## 📋 Checklist - [x] I have read the [contributor guidelines](https://github.com/marimo-team/marimo/blob/main/CONTRIBUTING.md). - [ ] For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on [Discord](https://marimo.io/discord?ref=pr), or the community [discussions](https://github.com/marimo-team/marimo/discussions) (Please provide a link if applicable). - [x] Tests have been added for the changes made. - [ ] Documentation has been updated where applicable, including docstrings for API changes. - [x] Pull request title is a good summary of the changes - it will be used in the [release notes](https://github.com/marimo-team/marimo/releases).
📝 Summary
Enforce consistent logic for taking screenshots and applying css
🔍 Description of Changes
📋 Checklist