Skip to content

fix(export): wrap long code lines in WebPDF export#10295

Draft
Abdulrehman-PIAIC80387 wants to merge 3 commits into
marimo-team:mainfrom
Abdulrehman-PIAIC80387:fix/pdf-code-line-wrapping-9421
Draft

fix(export): wrap long code lines in WebPDF export#10295
Abdulrehman-PIAIC80387 wants to merge 3 commits into
marimo-team:mainfrom
Abdulrehman-PIAIC80387:fix/pdf-code-line-wrapping-9421

Conversation

@Abdulrehman-PIAIC80387

@Abdulrehman-PIAIC80387 Abdulrehman-PIAIC80387 commented Jul 23, 2026

Copy link
Copy Markdown

This pull request was authored by a coding agent.

📝 Summary

Closes #9421

Long lines in code cells are truncated rather than wrapped in PDFs produced via WebPDF, silently dropping source text. As @philipdeboer reported, a 79-character comment was cut off after the "ch" in "chosen".

🔍 Root cause

JupyterLab's stylesheet (shipped with nbconvert) renders the code input area as:

<div class="jp-CodeMirrorEditor jp-Editor jp-InputArea-editor">
  <div class="highlight hl-ipython3"><pre>…long line…</pre></div>

with .jp-InputArea-editor { overflow: hidden; } and the <pre> left at the default white-space: pre. There is no white-space / word-break / overflow-wrap rule for the code input area at all — outputs already wrap via .jp-OutputArea-output pre { word-break: … }.

So a long line never wraps, overflows its container, and is clipped. In JupyterLab that's fine because the editor scrolls; a PDF has no scrolling, so the text is simply gone.

The LaTeX path is unaffected and already breaks lines via fancyvrb / \Wrappedbreaksatpunct — consistent with @philipdeboer finding the --no-webpdf screenshot "promising".

🔧 What changed

Registers an nbconvert preprocessor that inlines a stylesheet wrapping the code input area. Preprocessors run after nbconvert populates resources, so appending to resources["inlining"]["css"] is what reaches the rendered HTML — no template files, so nothing new to ship as package data.

.jp-InputArea-editor { overflow: visible !important; }
.jp-InputArea-editor .highlight pre {
  white-space: pre-wrap !important;
  overflow-wrap: anywhere !important;
}
  • Scoped to the input area; outputs are untouched.
  • pre-wrap (not pre-line) preserves Python's leading indentation.
  • overflow-wrap: anywhere covers a single unbroken token wider than the page, e.g. a long URL.
  • !important is needed because this is inlined ahead of the JupyterLab rules — the same approach the slides PDF path already uses to override nbconvert styling.

This sits in _render_webpdf_with_nbconvert, so it covers both explicit --webpdf and the fallback taken when standard LaTeX export fails. The fallback is likely the more common path — see the note below.

🧪 Verification

Generated real PDFs through the actual WebPDF export path, then extracted their text:

probe before after
to be chosen (the reported line) ❌ absent ✅ present
zeta=6 (long call signature) ❌ absent ✅ present
cannot/wrap/naturally (unbreakable URL) ❌ absent ✅ present

The characters are genuinely missing from the PDF, so this is content loss rather than a purely visual clip.

image image

Also:

  • Added a regression test asserting the stylesheet actually reaches the rendered document — a misresolved preprocessor would otherwise fail silently and leave the bug in place.
  • tests/_server/export/ goes from 55 → 56 passing; the 20 pre-existing failures in that file are unrelated (identical on a clean main).
  • mypy and ruff check / ruff format clean on the touched files.

📋 Note — possibly related, not fixed here

export_as_pdf catches OSError, PandocMissing, ConversionException and bare Exception, then silently falls back to WebPDF. A failing LaTeX run therefore produces a successful-looking export that is actually WebPDF output, with no indication of which renderer ran. That may be why installing TeX still gave @philipdeboer the same result. It seems worth a separate issue rather than bundling here — happy to file one.

📋 Pre-Review Checklist

  • For large changes, or changes that affect the public API: discussed on Export to pdf truncates code cells #9421, including the choice of injection mechanism.
  • Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it.
  • Video or media evidence is provided for any visual changes (optional).

✅ Merge Checklist

  • I have read the contributor guidelines.
  • Documentation has been updated where applicable — no user-facing API or docs change.
  • Tests have been added for the changes made.

Long lines in code cells were truncated rather than wrapped in PDFs
produced via WebPDF, silently losing source text.

JupyterLab's stylesheet (shipped with nbconvert) styles the code input
area with `overflow: hidden` and leaves the `<pre>` at the default
`white-space: pre`. That works in JupyterLab, where the editor scrolls,
but a PDF has no scrolling, so overflowing text is clipped away. Outputs
were unaffected because `.jp-OutputArea-output pre` already sets
`word-break`.

Register an nbconvert preprocessor that inlines a stylesheet wrapping the
code input area. Preprocessors run after nbconvert populates `resources`,
so appending to `resources["inlining"]["css"]` there reaches the rendered
HTML without shipping template files.

This covers both explicit `--webpdf` and the fallback taken when standard
LaTeX export fails; the LaTeX path already breaks lines via fancyvrb and
is unchanged.

Closes marimo-team#9421
@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Jul 23, 2026 1:38pm

Request Review

Registering a `Preprocessor` subclass required importing
`nbconvert.preprocessors`, and calling `register_preprocessor` broke the
Windows event-loop-policy test, which stubs `nbconvert` with a
single-module fake exposing only `from_notebook_node`.

nbconvert accepts a plain `(nb, resources)` callable, so drop the
subclass. That removes the submodule import entirely and keeps the
preprocessor a single function. Teach the stub `register_preprocessor`
so it stays a faithful stand-in for the exporter API we now use.
peter-gy
peter-gy previously approved these changes Jul 23, 2026

@peter-gy peter-gy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks great, thank you!

Image

Comment thread tests/_server/export/test_exporter.py Outdated
rendered_without, _ = exporter.from_notebook_node(notebook)
assert WEBPDF_CODE_WRAP_CSS not in rendered_without

exporter.register_preprocessor(_inline_code_wrap_css, enabled=True)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nitpick, but could we directly test _render_webpdf_with_nbconvert here? This test registers _inline_code_wrap_css itself, so it would still pass if the production registration were removed. We could patch WebPDFExporter.run_playwright to capture the generated HTML and return dummy PDF bytes.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch — you're right, and it was a real gap rather than a nitpick: dropping the register_preprocessor call from _render_webpdf_with_nbconvert left the old test green.

Fixed in c8a212a. It now drives _render_webpdf_with_nbconvert directly and stubs WebPDFExporter.run_playwright, which receives the fully rendered HTML — so the assertion covers the real registration, still without needing Chromium.

Verified by mutation: with the production register_preprocessor call removed the test fails, and passes with it restored.

The test registered `_inline_code_wrap_css` itself, so it verified the
preprocessor worked but not that production wired it up; dropping the
`register_preprocessor` call in `_render_webpdf_with_nbconvert` left it
green.

Drive `_render_webpdf_with_nbconvert` directly and stub
`WebPDFExporter.run_playwright`, which receives the fully rendered HTML,
so the assertion covers the real registration without needing Chromium.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Export to pdf truncates code cells

2 participants