import re from typing import TYPE_CHECKING, Any, Dict, List, Match, Optional from ..util import escape as escape_text from ..util import escape_url from ._base import BaseDirective, DirectivePlugin if TYPE_CHECKING: from ..block_parser import BlockParser from ..core import BlockState from ..markdown import Markdown from ..renderers.html import HTMLRenderer __all__ = ["Image", "Figure"] _num_re = re.compile(r"^\d+(?:\.\d*)?") _allowed_aligns = ["top", "middle", "bottom", "left", "center", "right"] def _parse_attrs(options: Dict[str, Any]) -> Dict[str, Any]: attrs = {} if "alt" in options: attrs["alt"] = options["alt"] # validate align align = options.get("align") if align and align in _allowed_aligns: attrs["align"] = align height = options.get("height") width = options.get("width") if height and _num_re.match(height): attrs["height"] = height if width and _num_re.match(width): attrs["width"] = width if "target" in options: attrs["target"] = escape_url(options["target"]) return attrs class Image(DirectivePlugin): NAME = "image" def parse(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Dict[str, Any]: options = dict(self.parse_options(m)) attrs = _parse_attrs(options) attrs["src"] = self.parse_title(m) return {"type": "block_image", "attrs": attrs} def __call__(self, directive: "BaseDirective", md: "Markdown") -> None: directive.register(self.NAME, self.parse) assert md.renderer is not None if md.renderer.NAME == "html": md.renderer.register("block_image", render_block_image) def render_block_image( self: "HTMLRenderer", src: str, alt: Optional[str] = None, width: Optional[str] = None, height: Optional[str] = None, **attrs: Any, ) -> str: img = '' return outer + img + "\n" else: return '
' + img + "
\n" class Figure(DirectivePlugin): NAME = "figure" def parse_directive_content( self, block: "BlockParser", m: Match[str], state: "BlockState" ) -> Optional[List[Dict[str, Any]]]: content = self.parse_content(m) if not content: return None tokens = list(self.parse_tokens(block, content, state)) caption = tokens[0] if caption["type"] == "paragraph": caption["type"] = "figcaption" children = [caption] if len(tokens) > 1: children.append({"type": "legend", "children": tokens[1:]}) return children return None def parse(self, block: "BlockParser", m: Match[str], state: "BlockState") -> Dict[str, Any]: options = dict(self.parse_options(m)) image_attrs = _parse_attrs(options) image_attrs["src"] = self.parse_title(m) align = image_attrs.pop("align", None) fig_attrs = {} if align: fig_attrs["align"] = align for k in ["figwidth", "figclass"]: if k in options: fig_attrs[k] = options[k] children = [{"type": "block_image", "attrs": image_attrs}] content = self.parse_directive_content(block, m, state) if content: children.extend(content) return { "type": "figure", "attrs": fig_attrs, "children": children, } def __call__(self, directive: "BaseDirective", md: "Markdown") -> None: directive.register(self.NAME, self.parse) assert md.renderer is not None if md.renderer.NAME == "html": md.renderer.register("figure", render_figure) md.renderer.register("block_image", render_block_image) md.renderer.register("figcaption", render_figcaption) md.renderer.register("legend", render_legend) def render_figure( self: Any, text: str, align: Optional[str] = None, figwidth: Optional[str] = None, figclass: Optional[str] = None, ) -> str: _cls = "figure" if align: _cls += " align-" + align if figclass: _cls += " " + figclass html = '
\n" + text + "
\n" def render_figcaption(self: Any, text: str) -> str: return "
" + text + "
\n" def render_legend(self: Any, text: str) -> str: return '
\n' + text + "
\n"