| 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"] |
|
|
| |
| 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 = '<img src="' + escape_text(src) + '"' |
| style = "" |
| if alt: |
| img += ' alt="' + escape_text(alt) + '"' |
| if width: |
| if width.isdigit(): |
| img += ' width="' + width + '"' |
| else: |
| style += "width:" + width + ";" |
| if height: |
| if height.isdigit(): |
| img += ' height="' + height + '"' |
| else: |
| style += "height:" + height + ";" |
| if style: |
| img += ' style="' + escape_text(style) + '"' |
|
|
| img += " />" |
|
|
| _cls = "block-image" |
| align = attrs.get("align") |
| if align: |
| _cls += " align-" + align |
|
|
| target = attrs.get("target") |
| if target: |
| href = self.safe_url(target) |
| outer = '<a class="' + _cls + '" href="' + href + '">' |
| return outer + img + "</a>\n" |
| else: |
| return '<div class="' + _cls + '">' + img + "</div>\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 = '<figure class="' + _cls + '"' |
| if figwidth: |
| html += ' style="width:' + figwidth + '"' |
| return html + ">\n" + text + "</figure>\n" |
|
|
|
|
| def render_figcaption(self: Any, text: str) -> str: |
| return "<figcaption>" + text + "</figcaption>\n" |
|
|
|
|
| def render_legend(self: Any, text: str) -> str: |
| return '<div class="legend">\n' + text + "</div>\n" |
|
|