pytex-preprocessor 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.
- pytex/__init__.py +87 -0
- pytex/commands/__init__.py +51 -0
- pytex/commands/biblatex.py +98 -0
- pytex/commands/builtin.py +598 -0
- pytex/commands/captions.py +56 -0
- pytex/commands/cleveref.py +43 -0
- pytex/commands/colors.py +60 -0
- pytex/commands/conditionals.py +62 -0
- pytex/commands/counters.py +85 -0
- pytex/commands/definitions.py +109 -0
- pytex/commands/floats.py +93 -0
- pytex/commands/font.py +138 -0
- pytex/commands/fontawesome.py +88 -0
- pytex/commands/fontspec.py +75 -0
- pytex/commands/geometry.py +25 -0
- pytex/commands/glossaries.py +126 -0
- pytex/commands/graphics.py +68 -0
- pytex/commands/hooks.py +58 -0
- pytex/commands/hyperref.py +57 -0
- pytex/commands/lengths.py +200 -0
- pytex/commands/listings.py +63 -0
- pytex/commands/mdframed.py +43 -0
- pytex/commands/picture.py +32 -0
- pytex/commands/setspace.py +38 -0
- pytex/commands/tables.py +123 -0
- pytex/helpers/__init__.py +3 -0
- pytex/helpers/coerce.py +13 -0
- pytex/helpers/parenting.py +13 -0
- pytex/helpers/sanitize.py +54 -0
- pytex/helpers/with_package.py +61 -0
- pytex/interface/__init__.py +3 -0
- pytex/interface/control_sequence.py +29 -0
- pytex/interface/package.py +52 -0
- pytex/interface/tex.py +41 -0
- pytex/model/__init__.py +25 -0
- pytex/model/color.py +203 -0
- pytex/model/concat.py +31 -0
- pytex/model/control_sequence.py +72 -0
- pytex/model/document.py +120 -0
- pytex/model/document_class.py +29 -0
- pytex/model/empty.py +19 -0
- pytex/model/environment.py +30 -0
- pytex/model/image.py +137 -0
- pytex/model/include.py +21 -0
- pytex/model/length.py +54 -0
- pytex/model/math.py +401 -0
- pytex/model/package.py +132 -0
- pytex/model/raw.py +61 -0
- pytex/packages.py +221 -0
- pytex/registry.py +49 -0
- pytex_builder/__init__.py +8 -0
- pytex_builder/build.py +175 -0
- pytex_builder/console.py +77 -0
- pytex_builder/render.py +90 -0
- pytex_builder/tectonic.py +370 -0
- pytex_hsrtreport/__init__.py +116 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-Bold.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-BoldItalic.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-Book.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-BookItalic.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-Medium.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-MediumItalic.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-Strong.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-Thin.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Blender/Blender-ThinItalic.ttf +0 -0
- pytex_hsrtreport/assets/fonts/DIN/DIN-Black.ttf +0 -0
- pytex_hsrtreport/assets/fonts/DIN/DIN-Bold.ttf +0 -0
- pytex_hsrtreport/assets/fonts/DIN/DIN-BoldItalic.ttf +0 -0
- pytex_hsrtreport/assets/fonts/DIN/DIN-Italic.ttf +0 -0
- pytex_hsrtreport/assets/fonts/DIN/DIN-Medium.ttf +0 -0
- pytex_hsrtreport/assets/fonts/DIN/DIN-Regular.ttf +0 -0
- pytex_hsrtreport/assets/fonts/Times New Roman.ttf +0 -0
- pytex_hsrtreport/assets/logos/ASTA.svg +79 -0
- pytex_hsrtreport/assets/logos/DUMMY.png +0 -0
- pytex_hsrtreport/assets/logos/DUMMY_FOOT.png +0 -0
- pytex_hsrtreport/assets/logos/ECHO.svg +226 -0
- pytex_hsrtreport/assets/logos/HSRT.pdf +0 -0
- pytex_hsrtreport/assets/logos/INF.pdf +0 -0
- pytex_hsrtreport/assets/logos/STUPA.pdf +0 -0
- pytex_hsrtreport/assets/logos/Skyline.pdf +0 -0
- pytex_hsrtreport/boxes.py +215 -0
- pytex_hsrtreport/citations.py +21 -0
- pytex_hsrtreport/cleveref_names.py +47 -0
- pytex_hsrtreport/colors.py +30 -0
- pytex_hsrtreport/document.py +307 -0
- pytex_hsrtreport/fonts.py +66 -0
- pytex_hsrtreport/glossary.py +61 -0
- pytex_hsrtreport/hyperref_config.py +49 -0
- pytex_hsrtreport/listings.py +90 -0
- pytex_hsrtreport/logos.py +234 -0
- pytex_hsrtreport/pagebreak.py +67 -0
- pytex_hsrtreport/pagesetup.py +33 -0
- pytex_hsrtreport/tex/pagesetup.tex +76 -0
- pytex_hsrtreport/titlepage.py +136 -0
- pytex_hsrtreport/variants.py +24 -0
- pytex_hsrtreport/voting.py +96 -0
- pytex_hsrtreport/watermark.py +63 -0
- pytex_hsrtreport/wordcount.py +33 -0
- pytex_koma/__init__.py +90 -0
- pytex_koma/commands.py +296 -0
- pytex_koma/document.py +138 -0
- pytex_markdown/__init__.py +62 -0
- pytex_markdown/convert.py +271 -0
- pytex_markdown/escape.py +11 -0
- pytex_preprocessor-0.1.0.dist-info/METADATA +82 -0
- pytex_preprocessor-0.1.0.dist-info/RECORD +119 -0
- pytex_preprocessor-0.1.0.dist-info/WHEEL +5 -0
- pytex_preprocessor-0.1.0.dist-info/entry_points.txt +2 -0
- pytex_preprocessor-0.1.0.dist-info/top_level.txt +7 -0
- pytex_protocol/__init__.py +37 -0
- pytex_protocol/convert.py +202 -0
- pytex_protocol/document.py +91 -0
- pytex_protocol/entries.py +96 -0
- pytex_protocol/frontmatter.py +80 -0
- pytex_protocol/header.py +139 -0
- pytex_protocol/shortcodes.py +130 -0
- pytex_protocol/signatures.py +84 -0
- pytex_tikz/__init__.py +25 -0
- pytex_tikz/tikz.py +272 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Inline ``{{shortcode}}`` expansion for protocol Markdown.
|
|
2
|
+
|
|
3
|
+
Shortcodes are the inline counterpart to the block callouts: small widgets
|
|
4
|
+
(``{{time 18:30}}``, ``{{vote ja=12 nein=3 enthaltung=2}}``) and references to
|
|
5
|
+
frontmatter fields (``{{anwesend}}``, ``{{count anwesend}}``, ``{{datum}}``).
|
|
6
|
+
|
|
7
|
+
Unknown shortcodes are rendered back verbatim (escaped) so a typo is visible
|
|
8
|
+
in the PDF rather than silently dropped.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
from typing import TYPE_CHECKING, Final
|
|
15
|
+
|
|
16
|
+
from pytex.commands.builtin import Textbf
|
|
17
|
+
from pytex.commands.colors import Textcolor
|
|
18
|
+
from pytex.helpers.sanitize import escape_latex
|
|
19
|
+
from pytex.model.concat import Concat
|
|
20
|
+
from pytex.model.raw import Raw
|
|
21
|
+
|
|
22
|
+
from .entries import Timestamp
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from collections.abc import Mapping
|
|
26
|
+
|
|
27
|
+
from pytex.interface.tex import TeX
|
|
28
|
+
|
|
29
|
+
from .frontmatter import FrontmatterValue
|
|
30
|
+
|
|
31
|
+
__all__ = ["SHORTCODE_RE", "expand_inline_shortcodes", "expand_shortcode"]
|
|
32
|
+
|
|
33
|
+
SHORTCODE_RE: Final[re.Pattern[str]] = re.compile(r"\{\{\s*(.*?)\s*\}\}")
|
|
34
|
+
|
|
35
|
+
# Frontmatter keys that can be referenced inline by their bare name.
|
|
36
|
+
_FIELD_ALIASES: Final[dict[str, tuple[str, ...]]] = {
|
|
37
|
+
"gremium": ("gremium",),
|
|
38
|
+
"datum": ("datum", "date"),
|
|
39
|
+
"beginn": ("beginn", "start"),
|
|
40
|
+
"ende": ("ende", "end"),
|
|
41
|
+
"ort": ("ort",),
|
|
42
|
+
"sitzungsleitung": ("sitzungsleitung",),
|
|
43
|
+
"protokoll": ("protokoll",),
|
|
44
|
+
"anwesend": ("anwesend",),
|
|
45
|
+
"abwesend": ("abwesend",),
|
|
46
|
+
"entschuldigt": ("entschuldigt",),
|
|
47
|
+
"gaeste": ("gaeste", "gäste"),
|
|
48
|
+
}
|
|
49
|
+
_LIST_FIELDS: Final[frozenset[str]] = frozenset(
|
|
50
|
+
{"anwesend", "abwesend", "entschuldigt", "gaeste"}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _as_list(value: FrontmatterValue | None) -> list[str]:
|
|
55
|
+
if value is None:
|
|
56
|
+
return []
|
|
57
|
+
if isinstance(value, list):
|
|
58
|
+
return value
|
|
59
|
+
return [v.strip() for v in value.split(",") if v.strip()]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _lookup(
|
|
63
|
+
meta: Mapping[str, FrontmatterValue], field: str
|
|
64
|
+
) -> FrontmatterValue | None:
|
|
65
|
+
for key in _FIELD_ALIASES.get(field, (field,)):
|
|
66
|
+
if key in meta:
|
|
67
|
+
return meta[key]
|
|
68
|
+
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _verbatim(inner: str) -> TeX:
|
|
72
|
+
return Raw(escape_latex(f"{{{{{inner}}}}}"))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _vote(args: dict[str, str]) -> TeX:
|
|
76
|
+
def count(*keys: str) -> int:
|
|
77
|
+
for key in keys:
|
|
78
|
+
if key in args:
|
|
79
|
+
try:
|
|
80
|
+
return int(args[key])
|
|
81
|
+
except ValueError:
|
|
82
|
+
return 0
|
|
83
|
+
return 0
|
|
84
|
+
|
|
85
|
+
yes, no, abstain = (
|
|
86
|
+
count("ja", "yes"),
|
|
87
|
+
count("nein", "no"),
|
|
88
|
+
count("enthaltung", "enth", "abstain"),
|
|
89
|
+
)
|
|
90
|
+
color = "britishracinggreen" if yes > no else "red" if yes < no else "eggplant"
|
|
91
|
+
summary = f"Ja {yes} · Nein {no} · Enthaltung {abstain}"
|
|
92
|
+
return Concat(Textbf("Abstimmung: "), Textcolor(color, summary))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _parse_kwargs(rest: str) -> dict[str, str]:
|
|
96
|
+
return dict(token.split("=", 1) for token in rest.split() if "=" in token)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def expand_shortcode(inner: str, meta: Mapping[str, FrontmatterValue]) -> TeX:
|
|
100
|
+
"""Expand a single ``{{...}}`` body (without braces) to a TeX node."""
|
|
101
|
+
name, _, rest = inner.strip().partition(" ")
|
|
102
|
+
name = name.lower()
|
|
103
|
+
rest = rest.strip()
|
|
104
|
+
|
|
105
|
+
if name == "time" and rest:
|
|
106
|
+
return Timestamp(rest)
|
|
107
|
+
if name == "vote":
|
|
108
|
+
return _vote(_parse_kwargs(rest))
|
|
109
|
+
if name == "count":
|
|
110
|
+
return Raw(str(len(_as_list(_lookup(meta, rest.lower())))))
|
|
111
|
+
if name in _FIELD_ALIASES:
|
|
112
|
+
value = _lookup(meta, name)
|
|
113
|
+
if name in _LIST_FIELDS:
|
|
114
|
+
return Raw(escape_latex(", ".join(_as_list(value))))
|
|
115
|
+
return Raw(escape_latex(value if isinstance(value, str) else ""))
|
|
116
|
+
return _verbatim(inner)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def expand_inline_shortcodes(text: str, meta: Mapping[str, FrontmatterValue]) -> TeX:
|
|
120
|
+
"""Split `text` on ``{{...}}`` markers, escaping prose and expanding codes."""
|
|
121
|
+
parts: list[TeX] = []
|
|
122
|
+
pos = 0
|
|
123
|
+
for match in SHORTCODE_RE.finditer(text):
|
|
124
|
+
if match.start() > pos:
|
|
125
|
+
parts.append(Raw(escape_latex(text[pos : match.start()])))
|
|
126
|
+
parts.append(expand_shortcode(match.group(1), meta))
|
|
127
|
+
pos = match.end()
|
|
128
|
+
if pos < len(text):
|
|
129
|
+
parts.append(Raw(escape_latex(text[pos:])))
|
|
130
|
+
return Concat(*parts)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Signature lines for protocol sign-off (Sitzungsleitung, Schriftführung, …).
|
|
2
|
+
|
|
3
|
+
Rendered as side-by-side blocks (signature rule, printed name, role), two per
|
|
4
|
+
row, under an "Unterschriften" heading. Names are pulled from the frontmatter
|
|
5
|
+
when known; otherwise the line is left blank to be signed by hand.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from pytex.helpers.sanitize import escape_latex
|
|
13
|
+
from pytex.model.raw import Raw
|
|
14
|
+
from pytex.registry import Registry
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Mapping
|
|
18
|
+
|
|
19
|
+
from pytex.interface.tex import TeX
|
|
20
|
+
|
|
21
|
+
from .frontmatter import FrontmatterValue
|
|
22
|
+
|
|
23
|
+
__all__ = ["SignatureLines", "signature_block_from_meta"]
|
|
24
|
+
|
|
25
|
+
# Role label (lower-cased) -> frontmatter keys that may hold the signer's name.
|
|
26
|
+
_ROLE_NAME_KEYS: dict[str, tuple[str, ...]] = {
|
|
27
|
+
"sitzungsleitung": ("sitzungsleitung",),
|
|
28
|
+
"schriftführung": ("protokoll", "schriftführung", "schriftfuehrung"),
|
|
29
|
+
"schriftfuehrung": ("protokoll", "schriftfuehrung"),
|
|
30
|
+
"protokoll": ("protokoll",),
|
|
31
|
+
"vorstand": ("vorstand",),
|
|
32
|
+
}
|
|
33
|
+
_RULE_WIDTH = "5cm"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _cell(role: str, name: str) -> str:
|
|
37
|
+
printed = escape_latex(name) if name else "~"
|
|
38
|
+
return (
|
|
39
|
+
r"\begin{minipage}[t]{0.46\linewidth}\centering"
|
|
40
|
+
+ rf"\rule{{{_RULE_WIDTH}}}{{0.4pt}}\\[0.4em]"
|
|
41
|
+
+ printed
|
|
42
|
+
+ r"\\{\small "
|
|
43
|
+
+ escape_latex(role)
|
|
44
|
+
+ r"}\end{minipage}"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@Registry.add
|
|
49
|
+
def SignatureLines(*signers: tuple[str, str] | str) -> TeX:
|
|
50
|
+
"""Side-by-side signature blocks (rule, name, role), two per row.
|
|
51
|
+
|
|
52
|
+
Each signer is a ``(role, name)`` pair; a bare string is a role with a
|
|
53
|
+
blank line to be signed by hand.
|
|
54
|
+
"""
|
|
55
|
+
pairs = [(s, "") if isinstance(s, str) else s for s in signers]
|
|
56
|
+
if not pairs:
|
|
57
|
+
return Raw("")
|
|
58
|
+
rows = [
|
|
59
|
+
r"\noindent "
|
|
60
|
+
+ r"\hfill".join(_cell(role, name) for role, name in pairs[i : i + 2])
|
|
61
|
+
for i in range(0, len(pairs), 2)
|
|
62
|
+
]
|
|
63
|
+
block = r"\par\vspace{3.5em}".join(rows)
|
|
64
|
+
return Raw(r"\par\vspace{2em}\section*{Unterschriften}\par\vspace{3em}" + block)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _name_for(role: str, meta: Mapping[str, FrontmatterValue]) -> str:
|
|
68
|
+
key = role.lower().strip()
|
|
69
|
+
for candidate in _ROLE_NAME_KEYS.get(key, (key,)):
|
|
70
|
+
value = meta.get(candidate)
|
|
71
|
+
if isinstance(value, str) and value:
|
|
72
|
+
return value
|
|
73
|
+
return ""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def signature_block_from_meta(meta: Mapping[str, FrontmatterValue]) -> TeX | None:
|
|
77
|
+
"""Build a signature block from an ``unterschriften`` frontmatter list.
|
|
78
|
+
|
|
79
|
+
Returns None when the key is absent, so callers can append conditionally.
|
|
80
|
+
"""
|
|
81
|
+
roles = meta.get("unterschriften")
|
|
82
|
+
if not isinstance(roles, list) or not roles:
|
|
83
|
+
return None
|
|
84
|
+
return SignatureLines(*((str(role), _name_for(str(role), meta)) for role in roles))
|
pytex_tikz/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from . import tikz
|
|
2
|
+
from .tikz import (
|
|
3
|
+
Circle,
|
|
4
|
+
Coordinate,
|
|
5
|
+
Draw,
|
|
6
|
+
Fill,
|
|
7
|
+
Node,
|
|
8
|
+
Rectangle,
|
|
9
|
+
Scope,
|
|
10
|
+
TikzLibrary,
|
|
11
|
+
TikzPicture,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Circle",
|
|
16
|
+
"Coordinate",
|
|
17
|
+
"Draw",
|
|
18
|
+
"Fill",
|
|
19
|
+
"Node",
|
|
20
|
+
"Rectangle",
|
|
21
|
+
"Scope",
|
|
22
|
+
"TikzLibrary",
|
|
23
|
+
"TikzPicture",
|
|
24
|
+
"tikz",
|
|
25
|
+
]
|
pytex_tikz/tikz.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Final, override
|
|
3
|
+
|
|
4
|
+
from pytex.helpers.parenting import attach
|
|
5
|
+
from pytex.interface.package import PackageProtocol
|
|
6
|
+
from pytex.interface.tex import TeX
|
|
7
|
+
from pytex.packages import PGF, TIKZ
|
|
8
|
+
from pytex.registry import Registry
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"Circle",
|
|
12
|
+
"Coordinate",
|
|
13
|
+
"Draw",
|
|
14
|
+
"Fill",
|
|
15
|
+
"Node",
|
|
16
|
+
"Rectangle",
|
|
17
|
+
"Scope",
|
|
18
|
+
"TikzLibrary",
|
|
19
|
+
"TikzPicture",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
type TikzOption = str | tuple[str, str]
|
|
23
|
+
type TikzCoord = str | tuple[float, float] | "Coordinate" | "Node"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _render_options(options: tuple[TikzOption, ...]) -> str:
|
|
27
|
+
if not options:
|
|
28
|
+
return ""
|
|
29
|
+
return (
|
|
30
|
+
"["
|
|
31
|
+
+ ",".join(o if isinstance(o, str) else f"{o[0]}={o[1]}" for o in options)
|
|
32
|
+
+ "]"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _render_pos(pos: TikzCoord) -> str:
|
|
37
|
+
if isinstance(pos, Coordinate):
|
|
38
|
+
return f"({pos.name})"
|
|
39
|
+
if isinstance(pos, Node):
|
|
40
|
+
if pos.name is None:
|
|
41
|
+
raise ValueError("Node used as position must have a name")
|
|
42
|
+
return f"({pos.name})"
|
|
43
|
+
if isinstance(pos, tuple):
|
|
44
|
+
return f"({pos[0]},{pos[1]})"
|
|
45
|
+
if pos.startswith("("):
|
|
46
|
+
return pos
|
|
47
|
+
return f"({pos})"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@Registry.add
|
|
51
|
+
@dataclass(frozen=True)
|
|
52
|
+
class Coordinate(TeX):
|
|
53
|
+
name: Final[str]
|
|
54
|
+
at: Final[tuple[float, float] | None] = None
|
|
55
|
+
options: Final[tuple[TikzOption, ...]] = ()
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
@override
|
|
59
|
+
def rendered(self) -> str:
|
|
60
|
+
opts = _render_options(self.options)
|
|
61
|
+
if self.at is None:
|
|
62
|
+
return f"\\coordinate{opts} ({self.name});"
|
|
63
|
+
return f"\\coordinate{opts} ({self.name}) at ({self.at[0]},{self.at[1]});"
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
@override
|
|
67
|
+
def requires(self) -> frozenset[PackageProtocol]:
|
|
68
|
+
return frozenset({TIKZ})
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@Registry.add
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class Node(TeX):
|
|
74
|
+
label: Final[TeX | str] = ""
|
|
75
|
+
name: Final[str | None] = None
|
|
76
|
+
at: Final[TikzCoord | None] = None
|
|
77
|
+
options: Final[tuple[TikzOption, ...]] = ()
|
|
78
|
+
_parent: "TeX | None" = field(default=None, init=False, compare=False, repr=False)
|
|
79
|
+
|
|
80
|
+
def __post_init__(self) -> None:
|
|
81
|
+
attach(self, self.label)
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
@override
|
|
85
|
+
def rendered(self) -> str:
|
|
86
|
+
parts: list[str] = ["\\node"]
|
|
87
|
+
parts.append(_render_options(self.options))
|
|
88
|
+
if self.name is not None:
|
|
89
|
+
parts.append(f" ({self.name})")
|
|
90
|
+
if self.at is not None:
|
|
91
|
+
parts.append(f" at {_render_pos(self.at)}")
|
|
92
|
+
parts.append(f" {{{self.label}}};")
|
|
93
|
+
return "".join(parts)
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
@override
|
|
97
|
+
def children(self) -> tuple[TeX, ...]:
|
|
98
|
+
return (self.label,) if isinstance(self.label, TeX) else ()
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
@override
|
|
102
|
+
def requires(self) -> frozenset[PackageProtocol]:
|
|
103
|
+
return frozenset({TIKZ})
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@Registry.add
|
|
107
|
+
@dataclass(frozen=True)
|
|
108
|
+
class Draw(TeX):
|
|
109
|
+
points: Final[tuple[TikzCoord, ...]]
|
|
110
|
+
op: Final[str] = "--"
|
|
111
|
+
options: Final[tuple[TikzOption, ...]] = ()
|
|
112
|
+
cycle: Final[bool] = False
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
@override
|
|
116
|
+
def rendered(self) -> str:
|
|
117
|
+
opts = _render_options(self.options)
|
|
118
|
+
path = f" {self.op} ".join(_render_pos(p) for p in self.points)
|
|
119
|
+
if self.cycle:
|
|
120
|
+
path = f"{path} {self.op} cycle"
|
|
121
|
+
return f"\\draw{opts} {path};"
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
@override
|
|
125
|
+
def requires(self) -> frozenset[PackageProtocol]:
|
|
126
|
+
return frozenset({TIKZ})
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@Registry.add
|
|
130
|
+
@dataclass(frozen=True)
|
|
131
|
+
class Fill(TeX):
|
|
132
|
+
points: Final[tuple[TikzCoord, ...]]
|
|
133
|
+
options: Final[tuple[TikzOption, ...]] = ()
|
|
134
|
+
op: Final[str] = "--"
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
@override
|
|
138
|
+
def rendered(self) -> str:
|
|
139
|
+
opts = _render_options(self.options)
|
|
140
|
+
path = f" {self.op} ".join(_render_pos(p) for p in self.points)
|
|
141
|
+
return f"\\fill{opts} {path} -- cycle;"
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
@override
|
|
145
|
+
def requires(self) -> frozenset[PackageProtocol]:
|
|
146
|
+
return frozenset({TIKZ})
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@Registry.add
|
|
150
|
+
@dataclass(frozen=True)
|
|
151
|
+
class Circle(TeX):
|
|
152
|
+
center: Final[TikzCoord]
|
|
153
|
+
radius: Final[float | str]
|
|
154
|
+
options: Final[tuple[TikzOption, ...]] = ()
|
|
155
|
+
fill: Final[bool] = False
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
@override
|
|
159
|
+
def rendered(self) -> str:
|
|
160
|
+
opts = _render_options(self.options)
|
|
161
|
+
cmd = "\\fill" if self.fill else "\\draw"
|
|
162
|
+
return f"{cmd}{opts} {_render_pos(self.center)} circle ({self.radius});"
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
@override
|
|
166
|
+
def requires(self) -> frozenset[PackageProtocol]:
|
|
167
|
+
return frozenset({TIKZ})
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@Registry.add
|
|
171
|
+
@dataclass(frozen=True)
|
|
172
|
+
class Rectangle(TeX):
|
|
173
|
+
a: Final[TikzCoord]
|
|
174
|
+
b: Final[TikzCoord]
|
|
175
|
+
options: Final[tuple[TikzOption, ...]] = ()
|
|
176
|
+
fill: Final[bool] = False
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
@override
|
|
180
|
+
def rendered(self) -> str:
|
|
181
|
+
opts = _render_options(self.options)
|
|
182
|
+
cmd = "\\fill" if self.fill else "\\draw"
|
|
183
|
+
return f"{cmd}{opts} {_render_pos(self.a)} rectangle {_render_pos(self.b)};"
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
@override
|
|
187
|
+
def requires(self) -> frozenset[PackageProtocol]:
|
|
188
|
+
return frozenset({TIKZ})
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@Registry.add
|
|
192
|
+
@dataclass(frozen=True)
|
|
193
|
+
class TikzPicture(TeX):
|
|
194
|
+
elements: Final[tuple[TeX, ...]]
|
|
195
|
+
options: Final[tuple[TikzOption, ...]] = ()
|
|
196
|
+
|
|
197
|
+
def __init__(
|
|
198
|
+
self,
|
|
199
|
+
*elements: TeX,
|
|
200
|
+
options: tuple[TikzOption, ...] = (),
|
|
201
|
+
) -> None:
|
|
202
|
+
object.__setattr__(self, "elements", elements)
|
|
203
|
+
object.__setattr__(self, "options", options)
|
|
204
|
+
object.__setattr__(self, "_parent", None)
|
|
205
|
+
attach(self, *elements)
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
@override
|
|
209
|
+
def rendered(self) -> str:
|
|
210
|
+
opts = _render_options(self.options)
|
|
211
|
+
inner = "\n".join(e.rendered for e in self.elements)
|
|
212
|
+
return f"\\begin{{tikzpicture}}{opts}\n{inner}\n\\end{{tikzpicture}}"
|
|
213
|
+
|
|
214
|
+
@property
|
|
215
|
+
@override
|
|
216
|
+
def children(self) -> tuple[TeX, ...]:
|
|
217
|
+
return self.elements
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
@override
|
|
221
|
+
def requires(self) -> frozenset[PackageProtocol]:
|
|
222
|
+
return frozenset({TIKZ, PGF})
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@Registry.add
|
|
226
|
+
@dataclass(frozen=True)
|
|
227
|
+
class TikzLibrary(TeX):
|
|
228
|
+
name: Final[str]
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
@override
|
|
232
|
+
def rendered(self) -> str:
|
|
233
|
+
return f"\\usetikzlibrary{{{self.name}}}"
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
@override
|
|
237
|
+
def requires(self) -> frozenset[PackageProtocol]:
|
|
238
|
+
return frozenset({TIKZ})
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@Registry.add
|
|
242
|
+
@dataclass(frozen=True)
|
|
243
|
+
class Scope(TeX):
|
|
244
|
+
elements: Final[tuple[TeX, ...]]
|
|
245
|
+
options: Final[tuple[TikzOption, ...]] = ()
|
|
246
|
+
|
|
247
|
+
def __init__(
|
|
248
|
+
self,
|
|
249
|
+
*elements: TeX,
|
|
250
|
+
options: tuple[TikzOption, ...] = (),
|
|
251
|
+
) -> None:
|
|
252
|
+
object.__setattr__(self, "elements", elements)
|
|
253
|
+
object.__setattr__(self, "options", options)
|
|
254
|
+
object.__setattr__(self, "_parent", None)
|
|
255
|
+
attach(self, *elements)
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
@override
|
|
259
|
+
def rendered(self) -> str:
|
|
260
|
+
opts = _render_options(self.options)
|
|
261
|
+
inner = "\n".join(e.rendered for e in self.elements)
|
|
262
|
+
return f"\\begin{{scope}}{opts}\n{inner}\n\\end{{scope}}"
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
@override
|
|
266
|
+
def children(self) -> tuple[TeX, ...]:
|
|
267
|
+
return self.elements
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
@override
|
|
271
|
+
def requires(self) -> frozenset[PackageProtocol]:
|
|
272
|
+
return frozenset({TIKZ})
|