177 أسطر
5.2 KiB
Python
177 أسطر
5.2 KiB
Python
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 = '<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"
|