diff --git a/examples/14_markdown.py b/examples/14_markdown.py index edc68aea7..9f5aa2fcb 100644 --- a/examples/14_markdown.py +++ b/examples/14_markdown.py @@ -6,7 +6,6 @@ import time from pathlib import Path -import imageio.v3 as iio import viser server = viser.ViserServer() @@ -17,10 +16,7 @@ def _(client: viser.ClientHandle) -> None: with open("./assets/mdx_example.mdx", "r") as mkdn: markdown = client.add_gui_markdown( - markdown=mkdn.read(), - images={ - "cal_logo": iio.imread(Path(__file__).parent / "assets/Cal_logo.png") - }, + markdown=mkdn.read(), image_root=Path(__file__).parent ) button = client.add_gui_button("Remove Markdown") diff --git a/examples/assets/mdx_example.mdx b/examples/assets/mdx_example.mdx index 49580f507..4c746a734 100644 --- a/examples/assets/mdx_example.mdx +++ b/examples/assets/mdx_example.mdx @@ -10,12 +10,13 @@ Anywhere where you can insert GUI elements, you can also insert `images`, `block In inline code blocks, you can show off colors with color chips: `#FED363` `hsl(0, 0%, 82%)` `rgb(255, 255, 255)` -Adding images from a remote origin is simple. For remote images, you must specify the image in the `images` prop of markdown, and then reference it -in your markdown as follows: +Adding images from a remote origin is simple. For remote images, you should specify a `image_root` that corresponds to the `Path` object that you'd like +your relative images to be scoped to when initializing your markdown. If no such `image_root` is provided, the file system will be scoped to the directory +that Viser is installed in.
![Viser Logo](http://nerfstudio-project.github.io/viser/_static/viser.svg) - ![Cal Logo](cal_logo) + ![Cal Logo](../examples/assets/Cal_logo.png)
Tables follow the standard markdown spec: @@ -37,24 +38,24 @@ Viser GUI has MDX 2 support (WIP) import time from pathlib import Path -import imageio.v3 as iio import viser server = viser.ViserServer() server.world_axes.visible = True -with open("./assets/mdx_example.mdx", "r") as mkdn: - markdown = server.add_gui_markdown( - markdown=mkdn.read(), - images={"cal_logo": iio.imread(Path(__file__).parent / "assets/Cal_logo.png")}, - ) -button = server.add_gui_button("Remove Markdown") +@server.on_client_connect +def _(client: viser.ClientHandle) -> None: + with open("./assets/mdx_example.mdx", "r") as mkdn: + markdown = client.add_gui_markdown( + markdown=mkdn.read(), image_root=Path(__file__).parent + ) + button = client.add_gui_button("Remove Markdown") -@button.on_click -def _(_): - markdown.remove() + @button.on_click + def _(_): + markdown.remove() while True: @@ -69,7 +70,7 @@ there is no guarantee that styling will look good once you stray from the standa
Cal Logo diff --git a/viser/_gui_api.py b/viser/_gui_api.py index 6216eaa19..fe24f0319 100644 --- a/viser/_gui_api.py +++ b/viser/_gui_api.py @@ -19,9 +19,10 @@ TypeVar, overload, ) - +from pathlib import Path import re +import imageio.v3 as iio import numpy as onp from typing_extensions import LiteralString import urllib.parse @@ -78,32 +79,41 @@ def _compute_precision_digits(x: float) -> int: return digits -def _get_repls(images: Dict[str, onp.ndarray]): - def _markdown_repl(match: re.Match[str]) -> str: - url = match.group(2) +def _get_repls(image_root: Optional[Path]): + root_selected = True + if image_root is None: + root_selected = False + image_root = Path(__file__).parent + + def _get_uri(match: re.Match[str], group: int) -> str: + url = match.group(group) - if url in images: - data_uri = _encode_image_base64(images[url], "png") + if not url.startswith("http") and not root_selected: + warnings.warn( + "No image_root provided. All relative paths will be scoped to viser's installation path.", + stacklevel=2, + ) + try: + image = iio.imread(image_root / url) + data_uri = _encode_image_base64(image, "png") url = urllib.parse.quote(f"{data_uri[1]}") - return f"![{match.group(1)}](data:{data_uri[0]};base64,{url})" - else: - return match.group() + return f"data:{data_uri[0]};base64,{url}" + except (IOError, FileNotFoundError): + return match.group(group) - def _src_repl(match: re.Match[str]) -> str: - url = match.group(1) + def _markdown_repl(match: re.Match[str]) -> str: + uri = _get_uri(match, 2) + return f"![{match.group(1)}]({uri})" - if url in images: - data_uri = _encode_image_base64(images[url], "png") - url = urllib.parse.quote(f"{data_uri[1]}") - return f'src="data:{data_uri[0]};base64,{url}"' - else: - return match.group() + def _src_repl(match: re.Match[str]) -> str: + uri = _get_uri(match, 1) + return f'src="{uri}"' return [_markdown_repl, _src_repl] -def _parse_markdown(markdown: str, images: Dict[str, onp.ndarray]) -> str: - repls = _get_repls(images) +def _parse_markdown(markdown: str, image_root: Optional[Path]) -> str: + repls = _get_repls(image_root) phase1 = re.sub(r"\!\[([^]]*)\]\(([^]]*)\)", repls[0], markdown) return re.sub(r'src="([^"]*)"', repls[1], phase1) @@ -167,11 +177,10 @@ def add_gui_tab_group(self) -> GuiTabGroupHandle: ) def add_gui_markdown( - self, markdown: str, images: Optional[Dict[str, onp.ndarray]] + self, markdown: str, image_root: Optional[Path] ) -> GuiMarkdownHandle: """Add markdown to the GUI.""" - if images is not None and len(images): - markdown = _parse_markdown(markdown, images) + markdown = _parse_markdown(markdown, image_root) markdown_id = _make_unique_id() self._get_api()._queue(