44
551. Convert MyST admonitions to HTML ``<div class="alert ...">`` blocks.
662. Convert MyST dropdowns to ``<details>`` elements (with literalinclude inlined).
7- 3. Strip MyST-only inline roles to plain text.
8- 4. Remove target labels and self-referential download links.
7+ 3. Inline ``{image}``/``{figure}`` directives as self-contained base64 ``<img>`` tags.
8+ 4. Strip MyST-only inline roles to plain text.
9+ 5. Remove target labels and self-referential download links.
910
1011The setup cell's ``%run -i setup_tutorial.py`` is left untouched: the bootstrap at
1112the top of that cell fetches the helpers at runtime, so the notebook is
1516
1617from __future__ import annotations
1718
19+ import base64
1820import json
21+ import mimetypes
1922import re
2023from pathlib import Path
2124from typing import Any
@@ -68,6 +71,33 @@ def _read_include(rel_path: str, source_dir: Path) -> str | None:
6871 return None
6972
7073
74+ def _render_image (rel_path : str , options : dict [str , str ], source_dir : Path ) -> str :
75+ """Render an ``{image}``/``{figure}`` target as a self-contained ``<img>`` tag.
76+
77+ The image bytes are embedded as a base64 data URI so the downloaded notebook
78+ displays them without depending on the working directory or the runtime fetch
79+ of ``include/``. Falls back to the relative path if the file is missing.
80+ """
81+ path = source_dir / rel_path
82+ attrs = ''
83+ if (width := options .get ('width' )) is not None :
84+ attrs += f' width="{ width } "'
85+ if (alt := options .get ('alt' )) is not None :
86+ attrs += f' alt="{ alt } "'
87+
88+ if path .is_file ():
89+ mime = mimetypes .guess_type (path .name )[0 ] or 'application/octet-stream'
90+ data = base64 .b64encode (path .read_bytes ()).decode ('ascii' )
91+ src = f'data:{ mime } ;base64,{ data } '
92+ else :
93+ src = rel_path
94+
95+ img = f'<img src="{ src } "{ attrs } >'
96+ if options .get ('align' ) in ('center' , 'left' , 'right' ):
97+ return f'<div align="{ options ["align" ]} ">\n { img } \n </div>'
98+ return img
99+
100+
71101def _convert_myst_block (lines : list [str ], source_dir : Path ) -> list [str ]:
72102 """Process a markdown cell's lines, converting MyST block directives."""
73103 output : list [str ] = []
@@ -77,6 +107,22 @@ def _convert_myst_block(lines: list[str], source_dir: Path) -> list[str]:
77107 line = lines [i ]
78108 m = _FENCE_OPEN .match (line )
79109 if m is None :
110+ bm = _BACKTICK_DIRECTIVE .match (line )
111+ if bm is not None and bm .group (1 ) in ('image' , 'figure' ):
112+ rel_path = bm .group (2 ).strip ()
113+ i += 1
114+ options = {}
115+ while i < len (lines ) and (om := _DIRECTIVE_OPT .match (lines [i ])) is not None :
116+ options [om .group (1 )] = om .group (2 )
117+ i += 1
118+ # Drop any remaining content (e.g. a figure caption) up to the
119+ # closing backtick fence.
120+ while i < len (lines ) and not lines [i ].strip ().startswith ('```' ):
121+ i += 1
122+ if i < len (lines ):
123+ i += 1 # consume the closing fence
124+ output .append (_render_image (rel_path , options , source_dir ))
125+ continue
80126 output .append (line )
81127 i += 1
82128 continue
0 commit comments