ilmplot 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ilmplot/mathtext.py ADDED
@@ -0,0 +1,69 @@
1
+ """Inline ``$...$`` mathtext helpers for the Plot Editor (pure Python, no Qt).
2
+
3
+ Mirrors ILM's ``src/utils/math_text.py`` rules, minus the Qt/pyplot parts:
4
+ stored text is never modified — sanitizing happens only when drawing. A
5
+ math segment matplotlib's mathtext can't parse gets its dollar signs
6
+ escaped so it renders literally instead of raising at draw time.
7
+ """
8
+
9
+ import functools
10
+ import re
11
+
12
+ MATH_RE = re.compile(r'\$[^$\n]+\$')
13
+
14
+ # mathtext has no \kern/\raisebox, so the logos are approximated with a
15
+ # raised small A, a lowered small E and negative \hspace kerning.
16
+ TEX_LOGO = r'\mathrm{T\hspace{-0.12}_{E}\hspace{-0.1}X}'
17
+ LATEX_LOGO = (r'\mathrm{L\hspace{-0.36}^{A}\hspace{-0.15}'
18
+ r'T\hspace{-0.12}_{E}\hspace{-0.1}X}')
19
+
20
+
21
+ def has_math(text):
22
+ """True when *text* contains at least one ``$...$`` segment."""
23
+ return bool(text) and bool(MATH_RE.search(text))
24
+
25
+
26
+ def preprocess(text):
27
+ """Rewrite ``\\LaTeX``/``\\TeX`` logo macros inside math segments only."""
28
+ def fix(seg):
29
+ return (seg
30
+ .replace(r'\LaTeXe', LATEX_LOGO[:-1] + r'\,2_{\epsilon}}')
31
+ .replace(r'\LaTeX', LATEX_LOGO)
32
+ .replace(r'\TeX', TEX_LOGO))
33
+ return MATH_RE.sub(lambda m: fix(m.group(0)), text)
34
+
35
+
36
+ @functools.lru_cache(maxsize=1024)
37
+ def _segment_parses(seg):
38
+ try:
39
+ from matplotlib.font_manager import FontProperties
40
+ from matplotlib.mathtext import MathTextParser
41
+ MathTextParser('path').parse(seg, dpi=72, prop=FontProperties())
42
+ return True
43
+ except Exception:
44
+ return False
45
+
46
+
47
+ @functools.lru_cache(maxsize=1024)
48
+ def safe_text(text):
49
+ """``preprocess`` + escape the dollars of any math segment that fails
50
+ to parse, so it draws literally. Stray dollars outside a ``$...$``
51
+ segment (``'$$'``, ``'$5'``) are escaped too: otherwise matplotlib
52
+ pairs them by count and enters math mode on garbage (``'$$'`` raised
53
+ at draw time) or drops real segments when the count turns odd.
54
+ Text without dollars is unchanged."""
55
+ if '$' not in text:
56
+ return text
57
+ text = preprocess(text)
58
+ out, pos = [], 0
59
+
60
+ def strays(chunk):
61
+ return re.sub(r'(?<!\\)\$', r'\\$', chunk)
62
+
63
+ for m in MATH_RE.finditer(text):
64
+ out.append(strays(text[pos:m.start()]))
65
+ seg = m.group(0)
66
+ out.append(seg if _segment_parses(seg) else seg.replace('$', r'\$'))
67
+ pos = m.end()
68
+ out.append(strays(text[pos:]))
69
+ return ''.join(out)
ilmplot/messages.py ADDED
@@ -0,0 +1,40 @@
1
+ """User-facing messages for ilmplot — English defaults plus a hook.
2
+
3
+ ILM (or another host application) registers its translator via
4
+ :func:`set_translator` so document parsing errors surface in the
5
+ application language. Without a translator the English strings below
6
+ are used.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ _ENGLISH = {
12
+ 'err_plot_newer_schema':
13
+ 'This plot was made by a newer version of the Plot Editor '
14
+ '(format {found}; this version reads {supported}). '
15
+ 'Update ILM to edit it.',
16
+ 'err_plot_newer_features':
17
+ 'This plot uses features from a newer version of the Plot '
18
+ 'Editor: {features}. Update ILM to edit it.',
19
+ 'err_svg_entities':
20
+ 'Unsupported SVG: entity declarations are not allowed',
21
+ }
22
+
23
+ _translator = None
24
+
25
+
26
+ def set_translator(fn):
27
+ """Register a ``tr(key, **fmt)`` callable, or None to reset."""
28
+ global _translator
29
+ _translator = fn
30
+
31
+
32
+ def text(key: str, **fmt) -> str:
33
+ """Message for *key*; translator first, English table on KeyError."""
34
+ if _translator is not None:
35
+ try:
36
+ return _translator(key, **fmt)
37
+ except KeyError:
38
+ pass
39
+ msg = _ENGLISH[key]
40
+ return msg.format(**fmt) if fmt else msg