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.
Files changed (119) hide show
  1. pytex/__init__.py +87 -0
  2. pytex/commands/__init__.py +51 -0
  3. pytex/commands/biblatex.py +98 -0
  4. pytex/commands/builtin.py +598 -0
  5. pytex/commands/captions.py +56 -0
  6. pytex/commands/cleveref.py +43 -0
  7. pytex/commands/colors.py +60 -0
  8. pytex/commands/conditionals.py +62 -0
  9. pytex/commands/counters.py +85 -0
  10. pytex/commands/definitions.py +109 -0
  11. pytex/commands/floats.py +93 -0
  12. pytex/commands/font.py +138 -0
  13. pytex/commands/fontawesome.py +88 -0
  14. pytex/commands/fontspec.py +75 -0
  15. pytex/commands/geometry.py +25 -0
  16. pytex/commands/glossaries.py +126 -0
  17. pytex/commands/graphics.py +68 -0
  18. pytex/commands/hooks.py +58 -0
  19. pytex/commands/hyperref.py +57 -0
  20. pytex/commands/lengths.py +200 -0
  21. pytex/commands/listings.py +63 -0
  22. pytex/commands/mdframed.py +43 -0
  23. pytex/commands/picture.py +32 -0
  24. pytex/commands/setspace.py +38 -0
  25. pytex/commands/tables.py +123 -0
  26. pytex/helpers/__init__.py +3 -0
  27. pytex/helpers/coerce.py +13 -0
  28. pytex/helpers/parenting.py +13 -0
  29. pytex/helpers/sanitize.py +54 -0
  30. pytex/helpers/with_package.py +61 -0
  31. pytex/interface/__init__.py +3 -0
  32. pytex/interface/control_sequence.py +29 -0
  33. pytex/interface/package.py +52 -0
  34. pytex/interface/tex.py +41 -0
  35. pytex/model/__init__.py +25 -0
  36. pytex/model/color.py +203 -0
  37. pytex/model/concat.py +31 -0
  38. pytex/model/control_sequence.py +72 -0
  39. pytex/model/document.py +120 -0
  40. pytex/model/document_class.py +29 -0
  41. pytex/model/empty.py +19 -0
  42. pytex/model/environment.py +30 -0
  43. pytex/model/image.py +137 -0
  44. pytex/model/include.py +21 -0
  45. pytex/model/length.py +54 -0
  46. pytex/model/math.py +401 -0
  47. pytex/model/package.py +132 -0
  48. pytex/model/raw.py +61 -0
  49. pytex/packages.py +221 -0
  50. pytex/registry.py +49 -0
  51. pytex_builder/__init__.py +8 -0
  52. pytex_builder/build.py +175 -0
  53. pytex_builder/console.py +77 -0
  54. pytex_builder/render.py +90 -0
  55. pytex_builder/tectonic.py +370 -0
  56. pytex_hsrtreport/__init__.py +116 -0
  57. pytex_hsrtreport/assets/fonts/Blender/Blender-Bold.ttf +0 -0
  58. pytex_hsrtreport/assets/fonts/Blender/Blender-BoldItalic.ttf +0 -0
  59. pytex_hsrtreport/assets/fonts/Blender/Blender-Book.ttf +0 -0
  60. pytex_hsrtreport/assets/fonts/Blender/Blender-BookItalic.ttf +0 -0
  61. pytex_hsrtreport/assets/fonts/Blender/Blender-Medium.ttf +0 -0
  62. pytex_hsrtreport/assets/fonts/Blender/Blender-MediumItalic.ttf +0 -0
  63. pytex_hsrtreport/assets/fonts/Blender/Blender-Strong.ttf +0 -0
  64. pytex_hsrtreport/assets/fonts/Blender/Blender-Thin.ttf +0 -0
  65. pytex_hsrtreport/assets/fonts/Blender/Blender-ThinItalic.ttf +0 -0
  66. pytex_hsrtreport/assets/fonts/DIN/DIN-Black.ttf +0 -0
  67. pytex_hsrtreport/assets/fonts/DIN/DIN-Bold.ttf +0 -0
  68. pytex_hsrtreport/assets/fonts/DIN/DIN-BoldItalic.ttf +0 -0
  69. pytex_hsrtreport/assets/fonts/DIN/DIN-Italic.ttf +0 -0
  70. pytex_hsrtreport/assets/fonts/DIN/DIN-Medium.ttf +0 -0
  71. pytex_hsrtreport/assets/fonts/DIN/DIN-Regular.ttf +0 -0
  72. pytex_hsrtreport/assets/fonts/Times New Roman.ttf +0 -0
  73. pytex_hsrtreport/assets/logos/ASTA.svg +79 -0
  74. pytex_hsrtreport/assets/logos/DUMMY.png +0 -0
  75. pytex_hsrtreport/assets/logos/DUMMY_FOOT.png +0 -0
  76. pytex_hsrtreport/assets/logos/ECHO.svg +226 -0
  77. pytex_hsrtreport/assets/logos/HSRT.pdf +0 -0
  78. pytex_hsrtreport/assets/logos/INF.pdf +0 -0
  79. pytex_hsrtreport/assets/logos/STUPA.pdf +0 -0
  80. pytex_hsrtreport/assets/logos/Skyline.pdf +0 -0
  81. pytex_hsrtreport/boxes.py +215 -0
  82. pytex_hsrtreport/citations.py +21 -0
  83. pytex_hsrtreport/cleveref_names.py +47 -0
  84. pytex_hsrtreport/colors.py +30 -0
  85. pytex_hsrtreport/document.py +307 -0
  86. pytex_hsrtreport/fonts.py +66 -0
  87. pytex_hsrtreport/glossary.py +61 -0
  88. pytex_hsrtreport/hyperref_config.py +49 -0
  89. pytex_hsrtreport/listings.py +90 -0
  90. pytex_hsrtreport/logos.py +234 -0
  91. pytex_hsrtreport/pagebreak.py +67 -0
  92. pytex_hsrtreport/pagesetup.py +33 -0
  93. pytex_hsrtreport/tex/pagesetup.tex +76 -0
  94. pytex_hsrtreport/titlepage.py +136 -0
  95. pytex_hsrtreport/variants.py +24 -0
  96. pytex_hsrtreport/voting.py +96 -0
  97. pytex_hsrtreport/watermark.py +63 -0
  98. pytex_hsrtreport/wordcount.py +33 -0
  99. pytex_koma/__init__.py +90 -0
  100. pytex_koma/commands.py +296 -0
  101. pytex_koma/document.py +138 -0
  102. pytex_markdown/__init__.py +62 -0
  103. pytex_markdown/convert.py +271 -0
  104. pytex_markdown/escape.py +11 -0
  105. pytex_preprocessor-0.1.0.dist-info/METADATA +82 -0
  106. pytex_preprocessor-0.1.0.dist-info/RECORD +119 -0
  107. pytex_preprocessor-0.1.0.dist-info/WHEEL +5 -0
  108. pytex_preprocessor-0.1.0.dist-info/entry_points.txt +2 -0
  109. pytex_preprocessor-0.1.0.dist-info/top_level.txt +7 -0
  110. pytex_protocol/__init__.py +37 -0
  111. pytex_protocol/convert.py +202 -0
  112. pytex_protocol/document.py +91 -0
  113. pytex_protocol/entries.py +96 -0
  114. pytex_protocol/frontmatter.py +80 -0
  115. pytex_protocol/header.py +139 -0
  116. pytex_protocol/shortcodes.py +130 -0
  117. pytex_protocol/signatures.py +84 -0
  118. pytex_tikz/__init__.py +25 -0
  119. 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})