- add Local Render Mermaid - add page-starе для указания смещения страниц - add Гиперссылки в документе на списки литературы
This commit is contained in:
+125
-25
@@ -94,6 +94,13 @@ def configure_diagrams(
|
||||
global _CONFIG
|
||||
ensure_user_schemes()
|
||||
configure_schemes(schemes_path=schemes_path, md_dir=md_dir)
|
||||
try:
|
||||
from .mermaid_renderer import ensure_playwright_browsers_env, reset_mermaid_session
|
||||
|
||||
ensure_playwright_browsers_env()
|
||||
reset_mermaid_session()
|
||||
except Exception:
|
||||
pass
|
||||
fmt: DiagramFormat = "svg" if diagram_format == "svg" else "png"
|
||||
scale = DEFAULT_DIAGRAM_SCALE if diagram_scale is None else float(diagram_scale)
|
||||
if scale < 1:
|
||||
@@ -128,13 +135,19 @@ def _cache_root(cache_dir: str | None = None) -> Path:
|
||||
return path
|
||||
|
||||
|
||||
def _cache_digest(source: str) -> str:
|
||||
return hashlib.sha256(source.encode("utf-8")).hexdigest()[:24]
|
||||
def _cache_digest(source: str, *, cache_prefix: str = "") -> str:
|
||||
payload = (cache_prefix + source) if cache_prefix else source
|
||||
return hashlib.sha256(payload.encode("utf-8")).hexdigest()[:24]
|
||||
|
||||
|
||||
def _cache_paths(source: str, cache_dir: str | None = None) -> tuple[Path, Path]:
|
||||
def _cache_paths(
|
||||
source: str,
|
||||
cache_dir: str | None = None,
|
||||
*,
|
||||
cache_prefix: str = "",
|
||||
) -> tuple[Path, Path]:
|
||||
root = _cache_root(cache_dir)
|
||||
digest = _cache_digest(source)
|
||||
digest = _cache_digest(source, cache_prefix=cache_prefix)
|
||||
return root / f"{digest}.png", root / f"{digest}.svg"
|
||||
|
||||
|
||||
@@ -235,7 +248,14 @@ def diagram_engine_status(explicit_jar: str | None = None) -> str:
|
||||
"PlantUML: файла нет. Нажмите «Скачать PlantUML» (нужна Java) "
|
||||
"или оставьте как есть — картинки нарисует интернет (kroki.io)."
|
||||
)
|
||||
return java_line + "\n" + jar_line
|
||||
lines = java_line + "\n" + jar_line
|
||||
try:
|
||||
from .mermaid_renderer import mermaid_engine_status
|
||||
|
||||
lines = lines + "\n" + mermaid_engine_status()
|
||||
except Exception as exc:
|
||||
lines = lines + f"\nMermaid: статус недоступен ({exc})"
|
||||
return lines
|
||||
|
||||
|
||||
def _ensure_plantuml_png_scale(source: str, scale: float = DEFAULT_DIAGRAM_SCALE) -> str:
|
||||
@@ -394,7 +414,32 @@ def render_diagram(
|
||||
prepared = _ensure_plantuml_png_scale(prepared, scale)
|
||||
if prepared != before:
|
||||
pixel_scale = scale
|
||||
out_png, out_svg_path = _cache_paths(prepared, cache_dir)
|
||||
# Mermaid: pixel_scale set only after successful local hi-res render (not Kroki).
|
||||
|
||||
cache_prefix = ""
|
||||
if kroki_type == "mermaid":
|
||||
try:
|
||||
from .mermaid_renderer import CACHE_KEY_PREFIX
|
||||
|
||||
cache_prefix = CACHE_KEY_PREFIX
|
||||
except Exception:
|
||||
cache_prefix = "mmd-local-v4\n"
|
||||
|
||||
# Mermaid local hi-res uses a scale-specific cache key; Kroki uses unscaled key.
|
||||
local_prefix = cache_prefix
|
||||
if kroki_type == "mermaid" and scale > 1:
|
||||
local_prefix = f"{cache_prefix}scale={scale:g}\n"
|
||||
|
||||
out_png, out_svg_path = _cache_paths(
|
||||
prepared, cache_dir, cache_prefix=local_prefix if kroki_type == "mermaid" else cache_prefix,
|
||||
)
|
||||
# Kroki / remote paths (mermaid): never share the scaled local key.
|
||||
kroki_png, kroki_svg_path = out_png, out_svg_path
|
||||
if kroki_type == "mermaid":
|
||||
kroki_png, kroki_svg_path = _cache_paths(
|
||||
prepared, cache_dir, cache_prefix=cache_prefix,
|
||||
)
|
||||
|
||||
fmt: DiagramFormat = (
|
||||
diagram_format
|
||||
if diagram_format is not None
|
||||
@@ -402,29 +447,57 @@ def render_diagram(
|
||||
)
|
||||
want_svg = fmt == "svg"
|
||||
svg_target = out_svg_path if want_svg else None
|
||||
kroki_svg_target = kroki_svg_path if want_svg else None
|
||||
|
||||
if out_png.is_file() and out_png.stat().st_size > 0:
|
||||
has_svg = out_svg_path.is_file() and out_svg_path.stat().st_size > 0
|
||||
if not want_svg or has_svg:
|
||||
hit_scale = pixel_scale
|
||||
if kroki_type == "mermaid" and scale > 1 and local_prefix != cache_prefix:
|
||||
hit_scale = scale
|
||||
return DiagramRenderResult(
|
||||
png_path=str(out_png),
|
||||
svg_path=str(out_svg_path) if (want_svg and has_svg) else None,
|
||||
pixel_scale=pixel_scale,
|
||||
pixel_scale=hit_scale,
|
||||
)
|
||||
# PNG cached but SVG missing in svg mode — fall through to fill SVG.
|
||||
|
||||
# Mermaid: also accept unscaled Kroki cache hit
|
||||
if (
|
||||
kroki_type == "mermaid"
|
||||
and kroki_png != out_png
|
||||
and kroki_png.is_file()
|
||||
and kroki_png.stat().st_size > 0
|
||||
):
|
||||
has_svg = kroki_svg_path.is_file() and kroki_svg_path.stat().st_size > 0
|
||||
if not want_svg or has_svg:
|
||||
return DiagramRenderResult(
|
||||
png_path=str(kroki_png),
|
||||
svg_path=str(kroki_svg_path) if (want_svg and has_svg) else None,
|
||||
pixel_scale=1.0,
|
||||
)
|
||||
|
||||
jar = plantuml_jar if plantuml_jar is not None else _CONFIG.plantuml_jar
|
||||
jar = resolve_plantuml_jar(jar or None, download=False)
|
||||
local_kroki = kroki_url if kroki_url is not None else _CONFIG.kroki_url
|
||||
local_kroki = local_kroki or os.environ.get("KROKI_URL") or DEFAULT_KROKI_URL
|
||||
mode: FallbackMode = fallback if fallback is not None else _CONFIG.fallback
|
||||
|
||||
def _result() -> DiagramRenderResult:
|
||||
def _result(
|
||||
png: Path | None = None,
|
||||
svg_path: Path | None = None,
|
||||
*,
|
||||
pscale: float | None = None,
|
||||
) -> DiagramRenderResult:
|
||||
use_png = png or out_png
|
||||
use_svg_path = svg_path if svg_path is not None else out_svg_path
|
||||
svg = None
|
||||
if want_svg and out_svg_path.is_file() and out_svg_path.stat().st_size > 0:
|
||||
svg = str(out_svg_path)
|
||||
if want_svg and use_svg_path.is_file() and use_svg_path.stat().st_size > 0:
|
||||
svg = str(use_svg_path)
|
||||
return DiagramRenderResult(
|
||||
png_path=str(out_png), svg_path=svg, pixel_scale=pixel_scale,
|
||||
png_path=str(use_png),
|
||||
svg_path=svg,
|
||||
pixel_scale=pixel_scale if pscale is None else pscale,
|
||||
)
|
||||
|
||||
# 1) PlantUML jar — only for plantuml type
|
||||
@@ -432,16 +505,41 @@ def render_diagram(
|
||||
if _render_plantuml_jar(prepared, out_png, jar or "", out_svg=svg_target):
|
||||
return _result()
|
||||
|
||||
# 1b) Mermaid local: system browser → Playwright Chromium → QuickJS
|
||||
if kroki_type == "mermaid":
|
||||
try:
|
||||
from .mermaid_renderer import write_mermaid_outputs
|
||||
|
||||
if write_mermaid_outputs(
|
||||
prepared, out_png, svg_target, scale=scale,
|
||||
):
|
||||
return _result(pscale=scale if scale > 1 else 1.0)
|
||||
except Exception as exc:
|
||||
_log.warning("Mermaid local render failed: %s", exc)
|
||||
|
||||
# 2) Local Kroki
|
||||
if _render_kroki_pair(prepared, kroki_type, local_kroki, out_png, svg_target):
|
||||
return _result()
|
||||
if _render_kroki_pair(prepared, kroki_type, local_kroki, kroki_png, kroki_svg_target):
|
||||
if kroki_type == "mermaid":
|
||||
return _result(kroki_png, kroki_svg_path, pscale=1.0)
|
||||
return _result(kroki_png, kroki_svg_path)
|
||||
|
||||
def _png_fallback_or_raise(message: str) -> DiagramRenderResult:
|
||||
if out_png.is_file() and out_png.stat().st_size > 0:
|
||||
_log.warning("Диаграмма: SVG недоступен, вставляю только PNG")
|
||||
return DiagramRenderResult(
|
||||
png_path=str(out_png), svg_path=None, pixel_scale=pixel_scale,
|
||||
)
|
||||
for candidate in (out_png, kroki_png):
|
||||
if candidate.is_file() and candidate.stat().st_size > 0:
|
||||
_log.warning("Диаграмма: SVG недоступен, вставляю только PNG")
|
||||
pscale = pixel_scale
|
||||
if (
|
||||
kroki_type == "mermaid"
|
||||
and candidate == out_png
|
||||
and scale > 1
|
||||
and local_prefix != cache_prefix
|
||||
):
|
||||
pscale = scale
|
||||
elif kroki_type == "mermaid" and candidate == kroki_png:
|
||||
pscale = 1.0
|
||||
return DiagramRenderResult(
|
||||
png_path=str(candidate), svg_path=None, pixel_scale=pscale,
|
||||
)
|
||||
raise RuntimeError(message)
|
||||
|
||||
if mode == "off":
|
||||
@@ -451,8 +549,8 @@ def render_diagram(
|
||||
"(задайте --plantuml-jar или KROKI_URL; remote fallback отключён)"
|
||||
)
|
||||
return _png_fallback_or_raise(
|
||||
"Не удалось отрендерить диаграмму через локальный Kroki "
|
||||
"(задайте --kroki-url / KROKI_URL; remote fallback отключён)"
|
||||
"Не удалось отрендерить диаграмму локально "
|
||||
"(браузер / QuickJS / --kroki-url; remote fallback отключён)"
|
||||
)
|
||||
if mode == "local":
|
||||
if kroki_type == "plantuml":
|
||||
@@ -461,8 +559,8 @@ def render_diagram(
|
||||
"(Java+plantuml.jar или локальный Kroki)"
|
||||
)
|
||||
return _png_fallback_or_raise(
|
||||
"Локальный Kroki недоступен для диаграммы "
|
||||
f"(тип {kroki_type}; задайте работающий --kroki-url)"
|
||||
"Локальный рендер Mermaid недоступен "
|
||||
f"(браузер / Chromium / QuickJS / Kroki; тип {kroki_type})"
|
||||
)
|
||||
|
||||
# 3) Remote fallback
|
||||
@@ -470,11 +568,13 @@ def render_diagram(
|
||||
"Диаграмма: локальный рендер недоступен, использую remote %s",
|
||||
REMOTE_KROKI_URL,
|
||||
)
|
||||
if _render_kroki_pair(prepared, kroki_type, REMOTE_KROKI_URL, out_png, svg_target):
|
||||
return _result()
|
||||
if _render_kroki_pair(prepared, kroki_type, REMOTE_KROKI_URL, kroki_png, kroki_svg_target):
|
||||
if kroki_type == "mermaid":
|
||||
return _result(kroki_png, kroki_svg_path, pscale=1.0)
|
||||
return _result(kroki_png, kroki_svg_path)
|
||||
|
||||
return _png_fallback_or_raise(
|
||||
"Не удалось отрендерить диаграмму (PlantUML/Kroki)"
|
||||
"Не удалось отрендерить диаграмму (PlantUML/Mermaid/Kroki)"
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user