fix(export): wrap long code lines in WebPDF export#10295
fix(export): wrap long code lines in WebPDF export#10295Abdulrehman-PIAIC80387 wants to merge 3 commits into
Conversation
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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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.
| 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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.

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:
with
.jp-InputArea-editor { overflow: hidden; }and the<pre>left at the defaultwhite-space: pre. There is nowhite-space/word-break/overflow-wraprule 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-webpdfscreenshot "promising".🔧 What changed
Registers an nbconvert preprocessor that inlines a stylesheet wrapping the code input area. Preprocessors run after nbconvert populates
resources, so appending toresources["inlining"]["css"]is what reaches the rendered HTML — no template files, so nothing new to ship as package data.pre-wrap(notpre-line) preserves Python's leading indentation.overflow-wrap: anywherecovers a single unbroken token wider than the page, e.g. a long URL.!importantis 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--webpdfand 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:
to be chosen(the reported line)zeta=6(long call signature)cannot/wrap/naturally(unbreakable URL)The characters are genuinely missing from the PDF, so this is content loss rather than a purely visual clip.
Also:
tests/_server/export/goes from 55 → 56 passing; the 20 pre-existing failures in that file are unrelated (identical on a cleanmain).mypyandruff check/ruff formatclean on the touched files.📋 Note — possibly related, not fixed here
export_as_pdfcatchesOSError,PandocMissing,ConversionExceptionand bareException, 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
✅ Merge Checklist