Repository: jupyter/nbconvert
Pull request #2196 (2024-12-03 21:37:37)

 Update base.tex.j2 for macro compatibility with newer versions of Pandoc
Added the macro \pandocbounded that is used by newer versions of pandoc.

PetalAdrift: Sorry about closing the previous PR on accident. Opened a new one.
krassowski: @PetalAdrift thank you! How can we test this PR? Is there a test that you could add to ensure this is tested on CI?

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c42f0a1f..ebdae243 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,35 @@
 
 <!-- <START NEW CHANGELOG ENTRY> -->
 
+## 7.16.5
+
+([Full Changelog](https://github.com/jupyter/nbconvert/compare/v7.16.4...18e10f6484afdfee39105248c28abe5ef0835b22))
+
+### Enhancements made
+
+- Add support for mistune 3.1.0 [#2199](https://github.com/jupyter/nbconvert/pull/2199) ([@fcollonval](https://github.com/fcollonval))
+- Allow including `text/x-rst` outputs in rst conversion, transition away from `text/restructuredtext` [#2167](https://github.com/jupyter/nbconvert/pull/2167) ([@takluyver](https://github.com/takluyver))
+
+### Bugs fixed
+
+- Do not display mathjax overlay [#2181](https://github.com/jupyter/nbconvert/pull/2181) ([@timkpaine](https://github.com/timkpaine))
+- Work around pip 24.1 bug which prevents installing pandocfilters 1.4.1 [#2168](https://github.com/jupyter/nbconvert/pull/2168) ([@takluyver](https://github.com/takluyver))
+- Don't die if template path cannot be read [#2162](https://github.com/jupyter/nbconvert/pull/2162) ([@stuaxo](https://github.com/stuaxo))
+- Fix markdown2asciidoc function for pandoc >= 3.0 (closes #2017) [#2152](https://github.com/jupyter/nbconvert/pull/2152) ([@thomasjm](https://github.com/thomasjm))
+
+### Maintenance and upkeep improvements
+
+- enhancement dep-chain: directly depend on bleach\[css\], instead of pulling in tinycss2. [#2166](https://github.com/jupyter/nbconvert/pull/2166) ([@xiacunshun](https://github.com/xiacunshun))
+- chore: update pre-commit hooks [#2146](https://github.com/jupyter/nbconvert/pull/2146) ([@pre-commit-ci](https://github.com/pre-commit-ci))
+
+### Contributors to this release
+
+([GitHub contributors page for this release](https://github.com/jupyter/nbconvert/graphs/contributors?from=2024-04-29&to=2025-01-02&type=c))
+
+[@bollwyvl](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Abollwyvl+updated%3A2024-04-29..2025-01-02&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Afcollonval+updated%3A2024-04-29..2025-01-02&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Akrassowski+updated%3A2024-04-29..2025-01-02&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Apre-commit-ci+updated%3A2024-04-29..2025-01-02&type=Issues) | [@stuaxo](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Astuaxo+updated%3A2024-04-29..2025-01-02&type=Issues) | [@t-makaro](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3At-makaro+updated%3A2024-04-29..2025-01-02&type=Issues) | [@takluyver](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Atakluyver+updated%3A2024-04-29..2025-01-02&type=Issues) | [@thomasjm](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Athomasjm+updated%3A2024-04-29..2025-01-02&type=Issues) | [@timkpaine](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Atimkpaine+updated%3A2024-04-29..2025-01-02&type=Issues) | [@xiacunshun](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Axiacunshun+updated%3A2024-04-29..2025-01-02&type=Issues)
+
+<!-- <END NEW CHANGELOG ENTRY> -->
+
 ## 7.16.4
 
 ([Full Changelog](https://github.com/jupyter/nbconvert/compare/v7.16.3...71fde294f623083f364bf68e14f07459dee952e6))
@@ -22,8 +51,6 @@
 
 [@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Ablink1073+updated%3A2024-03-21..2024-04-29&type=Issues) | [@mgeier](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Amgeier+updated%3A2024-03-21..2024-04-29&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter%2Fnbconvert+involves%3Apre-commit-ci+updated%3A2024-03-21..2024-04-29&type=Issues)
 
-<!-- <END NEW CHANGELOG ENTRY> -->
-
 ## 7.16.3
 
 ([Full Changelog](https://github.com/jupyter/nbconvert/compare/v7.16.2...9019037e300dfdbac5a8cb330d0b09eb82e12f62))
diff --git a/docs/api_examples/template_path/project_templates/nbconvert/templates/classic_clone/static/style.css b/docs/api_examples/template_path/project_templates/nbconvert/templates/classic_clone/static/style.css
index 881c1a53..dace08be 100644
--- a/docs/api_examples/template_path/project_templates/nbconvert/templates/classic_clone/static/style.css
+++ b/docs/api_examples/template_path/project_templates/nbconvert/templates/classic_clone/static/style.css
@@ -7671,9 +7671,6 @@ button.close {
 .fa-bar-chart:before {
   content: "\f080";
 }
-.fa-twitter-square:before {
-  content: "\f081";
-}
 .fa-facebook-square:before {
   content: "\f082";
 }
@@ -7741,9 +7738,6 @@ button.close {
 .fa-phone-square:before {
   content: "\f098";
 }
-.fa-twitter:before {
-  content: "\f099";
-}
 .fa-facebook-f:before,
 .fa-facebook:before {
   content: "\f09a";
diff --git a/nbconvert/_version.py b/nbconvert/_version.py
index 03b09e4d..363b18fa 100644
--- a/nbconvert/_version.py
+++ b/nbconvert/_version.py
@@ -4,7 +4,7 @@ import re
 from typing import List
 
 # Version string must appear intact for versioning
-__version__ = "7.16.4"
+__version__ = "7.16.5"
 
 # Build up version_info tuple for backwards compatibility
 pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)"
diff --git a/nbconvert/exporters/html.py b/nbconvert/exporters/html.py
index d63699c0..56724b06 100644
--- a/nbconvert/exporters/html.py
+++ b/nbconvert/exporters/html.py
@@ -15,6 +15,7 @@ import markupsafe
 from bs4 import BeautifulSoup
 from jupyter_core.paths import jupyter_path
 from traitlets import Bool, Unicode, default, validate
+from traitlets import Dict as TraitletsDict
 from traitlets.config import Config
 
 if tuple(int(x) for x in jinja2.__version__.split(".")[:3]) < (3, 0, 0):
@@ -183,6 +184,14 @@ class HTMLExporter(TemplateExporter):
 
     output_mimetype = "text/html"
 
+    lexer_options = TraitletsDict(
+        {},
+        help=(
+            "Options to be passed to the pygments lexer for highlighting markdown code blocks. "
+            "See https://pygments.org/docs/lexers/#available-lexers for available options."
+        ),
+    ).tag(config=True)
+
     @property
     def default_config(self):
         c = Config(
@@ -239,6 +248,7 @@ class HTMLExporter(TemplateExporter):
             path=path,
             anchor_link_text=self.anchor_link_text,
             exclude_anchor_links=self.exclude_anchor_links,
+            **self.lexer_options,
         )
         return MarkdownWithMath(renderer=renderer).render(source)
 
diff --git a/nbconvert/filters/highlight.py b/nbconvert/filters/highlight.py
index d29a4759..3a85bd17 100644
--- a/nbconvert/filters/highlight.py
+++ b/nbconvert/filters/highlight.py
@@ -136,7 +136,9 @@ class Highlight2Latex(NbConvertBase):
         return latex
 
 
-def _pygments_highlight(source, output_formatter, language="ipython", metadata=None):
+def _pygments_highlight(
+    source, output_formatter, language="ipython", metadata=None, **lexer_options
+):
     """
     Return a syntax-highlighted version of the input source
 
@@ -149,6 +151,10 @@ def _pygments_highlight(source, output_formatter, language="ipython", metadata=N
         language to highlight the syntax of
     metadata : NotebookNode cell metadata
         metadata of the cell to highlight
+    lexer_options : dict
+        Options to pass to the pygments lexer. See
+        https://pygments.org/docs/lexers/#available-lexers for more information about
+        valid lexer options
     """
     from pygments import highlight
     from pygments.lexers import get_lexer_by_name
@@ -179,7 +185,7 @@ def _pygments_highlight(source, output_formatter, language="ipython", metadata=N
 
     if lexer is None:
         try:
-            lexer = get_lexer_by_name(language, stripall=True)
+            lexer = get_lexer_by_name(language, **lexer_options)
         except ClassNotFound:
             warn("No lexer found for language %r. Treating as plain text." % language, stacklevel=2)
             from pygments.lexers.special import TextLexer
diff --git a/nbconvert/filters/markdown_mistune.py b/nbconvert/filters/markdown_mistune.py
index fb882816..ea46bba0 100644
--- a/nbconvert/filters/markdown_mistune.py
+++ b/nbconvert/filters/markdown_mistune.py
@@ -293,6 +293,7 @@ class IPythonRenderer(HTMLRenderer):
         anchor_link_text: str = "¶",
         path: str = "",
         attachments: Optional[Dict[str, Dict[str, str]]] = None,
+        **lexer_options,
     ):
         """Initialize the renderer."""
         super().__init__(escape, allow_harmful_protocols)
@@ -300,6 +301,7 @@ class IPythonRenderer(HTMLRenderer):
         self.exclude_anchor_links = exclude_anchor_links
         self.anchor_link_text = anchor_link_text
         self.path = path
+        self.lexer_options = lexer_options
         if attachments is not None:
             self.attachments = attachments
         else:
@@ -317,7 +319,7 @@ class IPythonRenderer(HTMLRenderer):
             try:
                 if info.strip().split(None, 1):
                     lang = info.strip().split(maxsplit=1)[0]
-                    lexer = get_lexer_by_name(lang, stripall=True)
+                    lexer = get_lexer_by_name(lang, **self.lexer_options)
             except ClassNotFound:
                 code = f"{lang}\n{code}"
                 lang = None
diff --git a/pyproject.toml b/pyproject.toml
index 9d876813..06fd056f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -224,7 +224,6 @@ ignore = [
   "T201",   # `print` found
   "RUF012", # Mutable class attributes should be annotated
   "UP031",  # Use format specifiers instead of percent format
-
 ]
 unfixable = [
   "T201",   # Don't touch print statements
@@ -253,6 +252,7 @@ unfixable = [
 "nbconvert/__init__.py" = ["F401", "F403"]
 # PLR2004 Magic value used in comparison
 "nbconvert/filters/ansi.py" = ["PLR2004"]
+"tests/exporters/test_html.py" = ["RUF001"]
 
 [tool.interrogate]
 ignore-init-module=true
diff --git a/share/templates/latex/base.tex.j2 b/share/templates/latex/base.tex.j2
index 493130b0..3f814615 100644
--- a/share/templates/latex/base.tex.j2
+++ b/share/templates/latex/base.tex.j2
@@ -164,7 +164,26 @@ override this.-=))
     \newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.49,0.56,0.16}{{#1}}}
     \newcommand{\InformationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}}
     \newcommand{\WarningTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}}
-
+    \makeatletter
+    \newsavebox\pandoc@box
+    \newcommand*\pandocbounded[1]{%
+      \sbox\pandoc@box{#1}%
+      % scaling factors for width and height
+      \Gscale@div\@tempa\textheight{\dimexpr\ht\pandoc@box+\dp\pandoc@box\relax}%
+      \Gscale@div\@tempb\linewidth{\wd\pandoc@box}%
+      % select the smaller of both
+      \ifdim\@tempb\p@<\@tempa\p@
+        \let\@tempa\@tempb
+      \fi
+      % scaling accordingly (\@tempa < 1)
+      \ifdim\@tempa\p@<\p@
+        \scalebox{\@tempa}{\usebox\pandoc@box}%
+      % scaling not needed, use as it is
+      \else
+        \usebox{\pandoc@box}%
+      \fi
+    }
+    \makeatother
 
     % Define a nice break command that doesn't care if a line doesn't already
     % exist.


Test patch:
diff --git a/tests/exporters/test_html.py b/tests/exporters/test_html.py
index 810de47e..f1bfb69b 100644
--- a/tests/exporters/test_html.py
+++ b/tests/exporters/test_html.py
@@ -5,6 +5,7 @@
 
 import re
 
+import pytest
 from nbformat import v4
 from traitlets.config import Config
 
@@ -262,3 +263,50 @@ class TestHTMLExporter(ExportersTestsBase):
         (output, resources) = exporter.from_filename(self._get_notebook())
 
         assert '<html lang="en">' in output
+
+
+@pytest.mark.parametrize(
+    ("lexer_options"),
+    [
+        {"stripall": True},
+        {"stripall": False},
+        {},
+    ],
+)
+def test_syntax_highlight_leading_whitespace(lexer_options):
+    """Test that syntax highlight doesn't strip leading spaces."""
+    nb = v4.reads(r"""
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "29da71a9-ae40-4098-8c3b-31a98e79fc12",
+   "metadata": {},
+   "source": [
+    "```APL\n",
+    "      1+2×⍳3\n",
+    "3 5 7\n",
+    "```\n",
+    "\n",
+    "```\n",
+    "      1+2×⍳3\n",
+    "3 5 7\n",
+    "```"
+   ]
+  }
+ ],
+ "metadata": {},
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
+    """)
+    output, _ = HTMLExporter(lexer_options=lexer_options).from_notebook_node(nb)
+    # Check that the second code block has the leading spaces
+    assert "<pre><code>      1+2×⍳3\n3 5 7\n</code></pre>" in output
+
+    if lexer_options.get("stripall"):
+        # Check that the APL-formatted code block has leading spaces stripped
+        assert '<span class="w">      </span>' not in output
+    else:
+        # Check that the APL-formatted code block has the leading spaces
+        assert '<span class="w">      </span>' in output