File size: 3,221 Bytes
e66cfb4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
"""
components/render_utils.py
--------------------------
純渲染工具函式,不依賴任何 Dash callback 或 app 狀態。
供 cards.py、dashTest.py(未來統一後)等元件共用。

公開 API:
    _inline_math(latex, style=None)  → Dash element
    _block_math(latex, style=None)   → Dash element
    _runs_to_children(runs)          → List[Dash element]
"""

from __future__ import annotations
from typing import Any

from dash import html, dcc
import dash_mantine_components as dmc

try:
    from dash_latex import DashLatex
except Exception:
    DashLatex = None


# ---------------------------------------------------------------------------
# 數學渲染
# ---------------------------------------------------------------------------

def _inline_math(latex: str, style: dict | None = None) -> Any:
    """行內數學式。優先使用 DashLatex(KaTeX),退回 dcc.Markdown MathJax。"""
    merged = {"display": "inline-block", "margin": "0 2px", **(style or {})}
    if DashLatex:
        return html.Span(DashLatex(latex, displayMode=False), style=merged)
    src = latex if latex.strip().startswith("$") else f"${latex}$"
    return dcc.Markdown(src, mathjax=True, style=merged)


def _block_math(latex: str, style: dict | None = None) -> Any:
    """區塊數學式。優先使用 DashLatex(KaTeX),退回 dcc.Markdown MathJax。"""
    merged = {"margin": "8px 0", **(style or {})}
    if DashLatex:
        return html.Div(DashLatex(latex, displayMode=True), style=merged)
    src = latex.strip()
    if not (src.startswith("$$") and src.endswith("$$")):
        src = f"$$\n{src}\n$$"
    return dcc.Markdown(src, mathjax=True, style=merged)


# ---------------------------------------------------------------------------
# Run 列表轉 Dash 元素
# ---------------------------------------------------------------------------

def _runs_to_children(runs: list[dict] | None) -> list[Any]:
    """
    把 JSON block 中的 runs 陣列轉成 Dash 子元素清單。

    Run 格式:
        {"kind": "math", "latex": "..."}          → 行內數學
        {"text": "...", "style": "bold|italic|bold-italic",
         "color_light": "#xxx", "color_dark": "#xxx"}  → 文字 span
    """
    out: list[Any] = []
    for r in runs or []:
        # 數學 run
        if r.get("kind") == "math" and "latex" in r:
            out.append(_inline_math(r["latex"]))
            continue

        # 文字 run
        sty: dict[str, Any] = {}
        s = r.get("style", "")
        if s == "bold":
            sty["fontWeight"] = 700
        elif s == "italic":
            sty["fontStyle"] = "italic"
        elif s == "bold-italic":
            sty.update({"fontWeight": 700, "fontStyle": "italic"})

        # 主題雙色(light / dark)
        cls = None
        if r.get("color_light") or r.get("color_dark"):
            if r.get("color_light"):
                sty["--tc-light"] = r["color_light"]
            if r.get("color_dark"):
                sty["--tc-dark"] = r["color_dark"]
            sty["color"] = "var(--tc, var(--tc-light))"
            cls = "themed-color"

        out.append(html.Span(r.get("text", ""), style=sty, className=cls))

    return out