When working with the Jupyter Notebook (formerly IPython Notebook) one often wants to export the whole notebook including Python code and output of each cell to a document format like PDF which is more suitable for handing in reports or assignments. Jupyter’s nbconvert is a tool which allows you to convert the IPython notebook format (*.ipynb) to \(\LaTeX\), PDF, HTML, markup languages such as Markdown or reStructuredText (RST) or even to a browser-based slideshow powered by Reveal.js.

I am primarily using the \(\LaTeX\) exporter or the PDF exporter, where the latter is in fact only a \(\LaTeX\) export followed by a call to pdflatex to obtain a PDF document. Doing this, I always found it particularly annoying that long lines both in input cells (i.e. code blocks) and in output cells do not get wrapped. If you take a look in the filters documentation you will find that there is indeed already a filter which would suffice, wrap_text(), however, it is simply not used in the default templates. Furthermore, the default font size used in the templates always seemed much too large for my purposes, and there is no easy way to change this except for editing the templates.

Adding a custom template

The templates are all using the Jinja2 template engine and are therefore easy to customize. I am using version 4.2.0. Here are the changes I made to the default template article.tplx:

  • I’ve added the wrap_text() filter to source code cells (line 8).
  • I’ve changed the font size for output cells to \footnotesize (line 17).
  • I’ve added the wrap_text() filter to output cells (line 18).
  • I’ve changed the font size for source code cells to \scriptsize using a slightly modified add_prompt() macro (lines 8, 33).

If you decide to use a different font size you would have to adjust the number of characters for the line wrapping. Below is the customized template better-article.tplx in /usr/lib/python3.5/site-packages/nbconvert/templates/ that I ended up with.

((*- extends 'article.tplx' -*))

% Input

((* block input scoped *))
   ((( custom_add_prompt(cell.source | wrap_text(88) | highlight_code(strip_verbatim=True), cell, 'In ', 'incolor') )))
((* endblock input *))

% Output

% Display stream ouput with coloring
((* block stream *))
((( output.text | wrap_text(86) | escape_latex | ansi2latex )))
((* endblock stream *))

% Define macro custom_add_prompt() (derived from add_prompt() macro in style_ipython.tplx)

((* macro custom_add_prompt(text, cell, prompt, prompt_color) -*))
    ((*- if cell.execution_count is defined -*))
    ((*- set execution_count = "" ~ (cell.execution_count | replace(None, " ")) -*))
    ((*- else -*))
    ((*- set execution_count = " " -*))
    ((*- endif -*))
    ((*- set indention =  " " * (execution_count | length + 7) -*))
((( text | add_prompts(first='{\color{' ~ prompt_color ~ '}' ~ prompt ~ '[{\\color{' ~ prompt_color ~ '}' ~ execution_count ~ '}]:} ', cont=indention) )))
((*- endmacro *))

You can then use this template to render to PDF or \(\LaTeX\) using commands like the following:

  • jupyter nbconvert --to pdf --template better-article <filename>.ipynb
  • jupyter nbconvert --to latex --template better-article <filename>.ipynb

Further thoughts

  1. There is probably a way to put the template in ~/.jupyter/, as well, but you would need to either modify the configuration file (generated by jupyter nbconvert --generate-config) or specify the full path to the template every time.

  2. It would probably be better to extend wrap_text() to be able to insert a character like \ (implied line continuation) when wrapping Python code to indicate that the statement is continued in the next line. On the other hand, people would hopefully not care to much about being able to copy the code from the PDF directly.