BigUpdate 0.5.0
Python application / build (push) Has been cancelled

- add Local Render Mermaid
- add page-starе для указания смещения страниц
- add Гиперссылки в документе на списки литературы
This commit is contained in:
Igor20264
2026-09-07 09:54:33 +03:00
parent ac0aa33399
commit 1a5b35eb54
29 changed files with 4118 additions and 69 deletions
+125 -25
View File
@@ -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)"
)