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/__init__.py +47 -0
- ilmplot/bridge.py +1469 -0
- ilmplot/document.py +2064 -0
- ilmplot/mathtext.py +69 -0
- ilmplot/messages.py +40 -0
- ilmplot/render.py +2574 -0
- ilmplot/stats.py +103 -0
- ilmplot-0.1.0.dist-info/METADATA +127 -0
- ilmplot-0.1.0.dist-info/RECORD +12 -0
- ilmplot-0.1.0.dist-info/WHEEL +5 -0
- ilmplot-0.1.0.dist-info/licenses/LICENSE +201 -0
- ilmplot-0.1.0.dist-info/top_level.txt +1 -0
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
|