Skip to content

Fixes cases where images end up clipped when taking screenshots for pdf downloads#7933

Merged
mscolnick merged 6 commits intomainfrom
sham/fix-clipped-image-exports
Jan 22, 2026
Merged

Fixes cases where images end up clipped when taking screenshots for pdf downloads#7933
mscolnick merged 6 commits intomainfrom
sham/fix-clipped-image-exports

Conversation

@Light2Dark
Copy link
Collaborator

📝 Summary

Enforce consistent logic for taking screenshots and applying css

🔍 Description of Changes

📋 Checklist

  • I have read the contributor guidelines.
  • For large changes, or changes that affect the public API: this change was discussed or approved through an issue, on Discord, or the community discussions (Please provide a link if applicable).
  • Tests have been added for the changes made.
  • Documentation has been updated where applicable, including docstrings for API changes.
  • Pull request title is a good summary of the changes - it will be used in the release notes.
@vercel
Copy link

vercel bot commented Jan 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
marimo-docs Ready Ready Preview, Comment Jan 22, 2026 5:46pm

Request Review

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • getImageDataUrlForCell already catches toPng failures and returns undefined, so this surrounding try/catch in useEnrichCellOutputs will no longer catch screenshot errors (and existing error logging becomes misleading). Consider either letting getImageDataUrlForCell throw (and handle/log here), or remove this outer try/catch and 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.

Comment on lines 106 to 115
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;
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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");
Copilot uses AI. Check for mistakes.
Comment on lines 73 to 74
} catch {
Logger.error("Failed to capture element as PNG.");
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
} catch {
Logger.error("Failed to capture element as PNG.");
} catch (error) {
Logger.error(`Failed to capture element as PNG: ${prettyError(error)}`);
Copilot uses AI. Check for mistakes.
Comment on lines 288 to 306
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);
});
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
@Yasin197
Copy link

Yasin197 commented Jan 22, 2026

@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?

@Light2Dark
Copy link
Collaborator Author

Light2Dark commented Jan 22, 2026

@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.

@Light2Dark Light2Dark added the bug Something isn't working label Jan 22, 2026
mscolnick
mscolnick previously approved these changes Jan 22, 2026
@Light2Dark Light2Dark marked this pull request as ready for review January 22, 2026 18:04
@Light2Dark Light2Dark requested a review from manzt as a code owner January 22, 2026 18:04
@mscolnick mscolnick merged commit dfe3e28 into main Jan 22, 2026
29 of 30 checks passed
@mscolnick mscolnick deleted the sham/fix-clipped-image-exports branch January 22, 2026 18:06
@github-actions
Copy link

��� Development release published. You may be able to view the changes at https://marimo.app?v=0.19.5-dev43

botterYosuke pushed a commit to botterYosuke/marimo that referenced this pull request Jan 23, 2026
…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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

5 participants