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,234 @@
|
|
|
1
|
+
from collections.abc import Iterator
|
|
2
|
+
from importlib.resources import files
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Final
|
|
5
|
+
|
|
6
|
+
from pytex.commands.colors import SelectColor
|
|
7
|
+
from pytex.commands.graphics import Includegraphics
|
|
8
|
+
from pytex.interface.tex import TeX
|
|
9
|
+
from pytex.model.concat import Concat
|
|
10
|
+
from pytex.model.image import IncludeImage
|
|
11
|
+
from pytex.registry import Registry
|
|
12
|
+
|
|
13
|
+
from .variants import Variant
|
|
14
|
+
|
|
15
|
+
LOGO_DIR: Final[Path] = Path(str(files("pytex_hsrtreport").joinpath("assets/logos")))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
KNOWN_LOGOS: Final[dict[str, str]] = {
|
|
19
|
+
"HSRT": "HSRT.pdf",
|
|
20
|
+
"INF": "INF.pdf",
|
|
21
|
+
"ASTA": "ASTA.svg",
|
|
22
|
+
"STUPA": "STUPA.pdf",
|
|
23
|
+
"ECHO": "ECHO.svg",
|
|
24
|
+
"Skyline": "Skyline.pdf",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def logo_path(name: str) -> Path:
|
|
29
|
+
if name not in KNOWN_LOGOS:
|
|
30
|
+
raise ValueError(f"unknown HSRT logo {name!r}; known: {sorted(KNOWN_LOGOS)}")
|
|
31
|
+
return LOGO_DIR / KNOWN_LOGOS[name]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Output dir for logos referenced by the tikz overlays, relative to the .tex
|
|
35
|
+
# file. `HSRTReport.write_inline_logos` copies the (svg->pdf converted) files
|
|
36
|
+
# here. The overlays emit these relative paths instead of absolute source
|
|
37
|
+
# paths so tectonic can read them (absolute paths outside the build cwd trip
|
|
38
|
+
# tectonic's "non-reproducible" warning and forbid reading the .bb bbox file).
|
|
39
|
+
LOGO_OUTPUT_DIR: Final[str] = "logos"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def logo_output_name(name: str) -> str:
|
|
43
|
+
"""Filename of a logo as materialised in the output ``logos/`` dir.
|
|
44
|
+
|
|
45
|
+
SVG sources are converted to PDF, so they get a ``.pdf`` suffix.
|
|
46
|
+
"""
|
|
47
|
+
src = logo_path(name)
|
|
48
|
+
if src.suffix.lower() == ".svg":
|
|
49
|
+
return f"{src.stem}.pdf"
|
|
50
|
+
return src.name
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def logo_output_rel(name: str) -> str:
|
|
54
|
+
"""Path of a logo relative to the .tex file (e.g. ``logos/INF.pdf``)."""
|
|
55
|
+
return f"{LOGO_OUTPUT_DIR}/{logo_output_name(name)}"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@Registry.add
|
|
59
|
+
def Logo(
|
|
60
|
+
name: str,
|
|
61
|
+
scale: float = 1.0,
|
|
62
|
+
height: str | None = None,
|
|
63
|
+
inline_base64: bool = True,
|
|
64
|
+
) -> TeX:
|
|
65
|
+
"""Vendored HSRT logo via `IncludeImage` (auto-bakes base64 by default)."""
|
|
66
|
+
return IncludeImage(
|
|
67
|
+
path=logo_path(name),
|
|
68
|
+
inline_base64=inline_base64,
|
|
69
|
+
scale=None if height is not None else str(scale),
|
|
70
|
+
height=height,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@Registry.add
|
|
75
|
+
def LogoStrip(
|
|
76
|
+
names: tuple[str, ...],
|
|
77
|
+
scale: float = 1.0,
|
|
78
|
+
height: str | None = None,
|
|
79
|
+
separator: str = "\\hspace{0.5cm}",
|
|
80
|
+
inline_base64: bool = True,
|
|
81
|
+
) -> TeX:
|
|
82
|
+
"""Horizontal sequence of vendored logos."""
|
|
83
|
+
if not names:
|
|
84
|
+
from pytex.model.empty import Empty
|
|
85
|
+
|
|
86
|
+
return Empty
|
|
87
|
+
logos = (
|
|
88
|
+
Logo(name, scale=scale, height=height, inline_base64=inline_base64)
|
|
89
|
+
for name in names
|
|
90
|
+
)
|
|
91
|
+
sep = _sep(separator)
|
|
92
|
+
# Interleave a separator before every logo except the first.
|
|
93
|
+
return Concat(
|
|
94
|
+
*(
|
|
95
|
+
piece
|
|
96
|
+
for i, logo in enumerate(logos)
|
|
97
|
+
for piece in ((sep, logo) if i else (logo,))
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _sep(text: str) -> TeX:
|
|
103
|
+
from pytex.model.raw import Raw
|
|
104
|
+
|
|
105
|
+
return Raw(text)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@Registry.add
|
|
109
|
+
def DefaultLogos(
|
|
110
|
+
variant: Variant,
|
|
111
|
+
scale: float = 1.0,
|
|
112
|
+
height: str | None = None,
|
|
113
|
+
inline_base64: bool = True,
|
|
114
|
+
) -> TeX:
|
|
115
|
+
from .variants import default_logo_names
|
|
116
|
+
|
|
117
|
+
return LogoStrip(
|
|
118
|
+
default_logo_names(variant),
|
|
119
|
+
scale=scale,
|
|
120
|
+
height=height,
|
|
121
|
+
inline_base64=inline_base64,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def titlepage_logo_overlay(
|
|
126
|
+
names: tuple[str, ...],
|
|
127
|
+
logo_height: str = "1.4cm",
|
|
128
|
+
xshift: str = "1.5cm",
|
|
129
|
+
yshift: str = "-1.5cm",
|
|
130
|
+
node_sep: str = "0.5cm",
|
|
131
|
+
prefix: str = "tplogo",
|
|
132
|
+
) -> str:
|
|
133
|
+
"""Raw LaTeX: tikz overlay placing logos at the top-left of the page.
|
|
134
|
+
|
|
135
|
+
Mirrors the ``\\foreach`` loop in ``tmp/Pages/Titlepage.tex`` but
|
|
136
|
+
unrolled in Python so no LaTeX arrays or counters are needed.
|
|
137
|
+
"""
|
|
138
|
+
if not names:
|
|
139
|
+
return ""
|
|
140
|
+
|
|
141
|
+
def lines() -> Iterator[str]:
|
|
142
|
+
yield r"\begin{tikzpicture}[overlay, remember picture]"
|
|
143
|
+
# Invisible dummy anchor at the top-left page corner — the chain
|
|
144
|
+
# start (logo0), mirroring the DUMMY_FOOT node in the original.
|
|
145
|
+
yield (
|
|
146
|
+
" \\node[anchor=north west, inner sep=0pt, "
|
|
147
|
+
+ f"xshift={xshift}, yshift={yshift}, opacity=0] ({prefix}0) "
|
|
148
|
+
+ f"at (current page.north west) {{\\rule{{0pt}}{{{logo_height}}}}};"
|
|
149
|
+
)
|
|
150
|
+
for i, name in enumerate(names, 1):
|
|
151
|
+
path = logo_output_rel(name)
|
|
152
|
+
yield (
|
|
153
|
+
f" \\node[anchor=west, inner sep=0pt, xshift={node_sep}] "
|
|
154
|
+
+ f"({prefix}{i}) at ({prefix}{i - 1}.east) "
|
|
155
|
+
+ f"{{\\includegraphics[height={logo_height}]{{{path}}}}};"
|
|
156
|
+
)
|
|
157
|
+
yield r"\end{tikzpicture}"
|
|
158
|
+
|
|
159
|
+
return "\n".join(lines())
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def footer_logo_hook(
|
|
163
|
+
names: tuple[str, ...],
|
|
164
|
+
logo_height: str = "0.8cm",
|
|
165
|
+
xshift: str = "-2cm",
|
|
166
|
+
yshift: str = "1.5em",
|
|
167
|
+
node_sep: str = "-0.3cm",
|
|
168
|
+
prefix: str = "fllogo",
|
|
169
|
+
skyline: bool = True,
|
|
170
|
+
) -> str:
|
|
171
|
+
"""Raw LaTeX: ``\\AddToHook{shipout/background}`` block placing logos at
|
|
172
|
+
the bottom-right of every page.
|
|
173
|
+
|
|
174
|
+
Mirrors the ``\\foreach`` loop in ``tmp/Modules/Layout/Logos.tex`` but
|
|
175
|
+
unrolled in Python. Logos are chained right-to-left from a dummy anchor
|
|
176
|
+
at the page's south-east corner. The skyline graphic is placed at the
|
|
177
|
+
south-west corner when ``skyline=True`` (matching the original template).
|
|
178
|
+
|
|
179
|
+
The footer logos are wrapped in ``\\ifHSRTTitlePage\\else...\\fi`` so they
|
|
180
|
+
are suppressed on the title page (which sets ``\\HSRTTitlePagetrue``); the
|
|
181
|
+
skyline graphic is unconditional.
|
|
182
|
+
"""
|
|
183
|
+
if not names and not skyline:
|
|
184
|
+
return ""
|
|
185
|
+
|
|
186
|
+
def lines() -> Iterator[str]:
|
|
187
|
+
yield r"\AddToHook{shipout/background}{%"
|
|
188
|
+
yield r" \begin{tikzpicture}[overlay, remember picture]"
|
|
189
|
+
if names:
|
|
190
|
+
# Suppress footer logos on the title page.
|
|
191
|
+
yield r" \ifHSRTTitlePage\else"
|
|
192
|
+
# Invisible dummy anchor at the bottom-right page corner.
|
|
193
|
+
yield (
|
|
194
|
+
" \\node[anchor=south east, inner sep=0pt, "
|
|
195
|
+
+ f"xshift={xshift}, yshift={yshift}, opacity=0] ({prefix}0) "
|
|
196
|
+
+ f"at (current page.south east) {{\\rule{{0pt}}{{{logo_height}}}}};"
|
|
197
|
+
)
|
|
198
|
+
# Chain logos from right to left (anchor=east, stepping west).
|
|
199
|
+
for i, name in enumerate(names, 1):
|
|
200
|
+
path = logo_output_rel(name)
|
|
201
|
+
yield (
|
|
202
|
+
" \\node[anchor=east, inner sep=0pt, "
|
|
203
|
+
+ f"xshift={node_sep}, yshift=2pt] ({prefix}{i}) "
|
|
204
|
+
+ f"at ({prefix}{i - 1}.west) "
|
|
205
|
+
+ f"{{\\includegraphics[height={logo_height}]{{{path}}}}};"
|
|
206
|
+
)
|
|
207
|
+
yield r" \fi"
|
|
208
|
+
if skyline:
|
|
209
|
+
skyline_path = logo_output_rel("Skyline")
|
|
210
|
+
yield (
|
|
211
|
+
" \\node[anchor=south west, inner sep=0pt, yshift=0em] "
|
|
212
|
+
+ "at (current page.south west) "
|
|
213
|
+
+ f"{{\\includegraphics[width=1.5\\paperwidth]{{{skyline_path}}}}};"
|
|
214
|
+
)
|
|
215
|
+
yield r" \end{tikzpicture}%"
|
|
216
|
+
yield r"}"
|
|
217
|
+
|
|
218
|
+
return "\n".join(lines())
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# Keep SelectColor import alive for callers that previously imported via this module
|
|
222
|
+
__all__ = [
|
|
223
|
+
"LOGO_OUTPUT_DIR",
|
|
224
|
+
"DefaultLogos",
|
|
225
|
+
"Includegraphics",
|
|
226
|
+
"Logo",
|
|
227
|
+
"LogoStrip",
|
|
228
|
+
"SelectColor",
|
|
229
|
+
"footer_logo_hook",
|
|
230
|
+
"logo_output_name",
|
|
231
|
+
"logo_output_rel",
|
|
232
|
+
"logo_path",
|
|
233
|
+
"titlepage_logo_overlay",
|
|
234
|
+
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from pytex.interface.tex import TeX
|
|
2
|
+
from pytex.model.control_sequence import ControlSequence, Parameter
|
|
3
|
+
from pytex.model.environment import Environment
|
|
4
|
+
from pytex.model.raw import Raw
|
|
5
|
+
from pytex.registry import Registry
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Conditionalpagebreak",
|
|
9
|
+
"Critical",
|
|
10
|
+
"Keeptogether",
|
|
11
|
+
"Smartsection",
|
|
12
|
+
"Smartsubsection",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@Registry.add
|
|
17
|
+
def Keeptogether(body: TeX | str) -> TeX:
|
|
18
|
+
"""Wraps body in a minipage to keep it on the same page."""
|
|
19
|
+
return Environment("minipage", body, (Parameter(r"\linewidth"),))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@Registry.add
|
|
23
|
+
def Conditionalpagebreak(amount: str = "10\\baselineskip") -> TeX:
|
|
24
|
+
return ControlSequence("needspace", (Parameter(amount),))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@Registry.add
|
|
28
|
+
def Critical(body: TeX | str) -> TeX:
|
|
29
|
+
"""High-penalty samepage env: prevents widows/clubs/linebreaks inside."""
|
|
30
|
+
return Environment(
|
|
31
|
+
"samepage",
|
|
32
|
+
Raw(
|
|
33
|
+
r"\interlinepenalty=10000\widowpenalty=10000\clubpenalty=10000"
|
|
34
|
+
+ str(body if isinstance(body, str) else body.rendered)
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@Registry.add
|
|
40
|
+
def Smartsection(title: TeX | str, short: TeX | str | None = None) -> TeX:
|
|
41
|
+
"""Section that asks for `\\sectionminspace` before breaking the page."""
|
|
42
|
+
head = (
|
|
43
|
+
ControlSequence("section", (Parameter(title),))
|
|
44
|
+
if short is None
|
|
45
|
+
else ControlSequence(
|
|
46
|
+
"section",
|
|
47
|
+
(Parameter(short, optional=True), Parameter(title)),
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
return Raw(
|
|
51
|
+
r"\vfil\penalty-9999\vfilneg\needspace{\sectionminspace}" + head.rendered
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@Registry.add
|
|
56
|
+
def Smartsubsection(title: TeX | str, short: TeX | str | None = None) -> TeX:
|
|
57
|
+
head = (
|
|
58
|
+
ControlSequence("subsection", (Parameter(title),))
|
|
59
|
+
if short is None
|
|
60
|
+
else ControlSequence(
|
|
61
|
+
"subsection",
|
|
62
|
+
(Parameter(short, optional=True), Parameter(title)),
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
return Raw(
|
|
66
|
+
r"\vfil\penalty-9999\vfilneg\needspace{\subsectionminspace}" + head.rendered
|
|
67
|
+
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from importlib.resources import files
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Final
|
|
4
|
+
|
|
5
|
+
from pytex.interface.tex import TeX
|
|
6
|
+
from pytex.model.include import IncludeTeX
|
|
7
|
+
from pytex.registry import Registry
|
|
8
|
+
|
|
9
|
+
__all__ = ["HSRTPageSetup"]
|
|
10
|
+
|
|
11
|
+
# The full preamble lives in tex/pagesetup.tex (too large to inline as a raw
|
|
12
|
+
# string); it is shipped as package data and loaded verbatim at render time.
|
|
13
|
+
SETUP_TEX: Final[Path] = Path(
|
|
14
|
+
str(files("pytex_hsrtreport").joinpath("tex/pagesetup.tex"))
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@Registry.add
|
|
19
|
+
def HSRTPageSetup() -> TeX:
|
|
20
|
+
"""KOMA scrheadings, section fonts, chapter-name tracking, typography.
|
|
21
|
+
|
|
22
|
+
Mirrors ``Config/PageSetup.tex``, ``Config/Sections.tex``, and
|
|
23
|
+
``Config/Typography.tex`` from the original HSRTReport template.
|
|
24
|
+
|
|
25
|
+
Must be emitted in the preamble *before* font setup: the ``\\providecommand``
|
|
26
|
+
fallbacks here define ``\\blenderfont``/``\\dinfont`` as safe defaults, and
|
|
27
|
+
``HSRTFontSetup`` then overrides them with ``\\renewcommand`` once the real
|
|
28
|
+
font families are declared.
|
|
29
|
+
|
|
30
|
+
Defines ``\\ifHSRTBackMatter`` — set it to true in back matter to suppress
|
|
31
|
+
chapter-specific headers/footers without calling mode-sensitive KOMA commands.
|
|
32
|
+
"""
|
|
33
|
+
return IncludeTeX(SETUP_TEX, allow_replacements=False)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
\makeatletter
|
|
2
|
+
\def\@title{}
|
|
3
|
+
\def\@author{}
|
|
4
|
+
\newif\ifHSRTBackMatter
|
|
5
|
+
\newif\ifHSRTTitlePage
|
|
6
|
+
% Minimum space \Smartsection/\Smartsubsection demand before breaking a page.
|
|
7
|
+
\newlength{\sectionminspace}\setlength{\sectionminspace}{4\baselineskip}
|
|
8
|
+
\newlength{\subsectionminspace}\setlength{\subsectionminspace}{3\baselineskip}
|
|
9
|
+
\providecommand{\blenderfont}{\sffamily}
|
|
10
|
+
\providecommand{\dinfont}{\rmfamily}
|
|
11
|
+
\let\Chaptermark\chaptermark
|
|
12
|
+
\def\chaptermark#1{\def\Chaptername{#1}\Chaptermark{#1}}
|
|
13
|
+
\let\Sectionmark\sectionmark
|
|
14
|
+
\def\sectionmark#1{\def\Sectionname{#1}\Sectionmark{#1}}
|
|
15
|
+
\AtEndDocument{\immediate\write\@auxout{\string\gdef\string\@lastpage{\thepage}}}
|
|
16
|
+
\clearpairofpagestyles
|
|
17
|
+
\setkomafont{pageheadfoot}{\color{gray}\blenderfont}
|
|
18
|
+
\setkomafont{pagenumber}{\color{gray}\blenderfont}
|
|
19
|
+
\setlength{\footskip}{20pt}
|
|
20
|
+
\ohead*{\ifHSRTBackMatter\else\ifnum\value{chapter}>0\relax\Roman{\thechapter}~–~\Chaptername\fi\fi}
|
|
21
|
+
\ifoot{\@author}
|
|
22
|
+
\cfoot{\ifHSRTBackMatter\else Seite~\thepage\if@mainmatter\@ifundefined{@lastpage}{}{~von~\@lastpage}\fi\fi}
|
|
23
|
+
\ohead{\ifHSRTBackMatter\else\ifnum\value{chapter}>0\relax\thechapter~–~\Chaptername\fi\fi}
|
|
24
|
+
\ihead{\@title}
|
|
25
|
+
\pagestyle{scrheadings}
|
|
26
|
+
\renewcommand*{\chapterpagestyle}{scrheadings}
|
|
27
|
+
\setkomafont{disposition}{\blenderfont\bfseries}
|
|
28
|
+
% ToC consistency: section entries default to the main font (DIN), and their
|
|
29
|
+
% page numbers are wrapped in \normalfont by \@dottedtocline (also DIN), giving
|
|
30
|
+
% the ToC two families. Force the whole ToC into \blenderfont after its heading
|
|
31
|
+
% and alias \normalfont to it (locally to the ToC group) so entries, leaders and
|
|
32
|
+
% page numbers all match the headings. Chapter entry/number keep an explicit
|
|
33
|
+
% bold komafont.
|
|
34
|
+
\AfterTOCHead{\blenderfont\let\normalfont\blenderfont}
|
|
35
|
+
\setkomafont{chapterentry}{\blenderfont\bfseries}
|
|
36
|
+
\setkomafont{chapterentrypagenumber}{\blenderfont\bfseries}
|
|
37
|
+
\setkomafont{chapter}{\LARGE\blenderfont\bfseries}
|
|
38
|
+
\setkomafont{section}{\Large\blenderfont\bfseries}
|
|
39
|
+
\setkomafont{subsection}{\large\blenderfont\bfseries}
|
|
40
|
+
\setkomafont{subsubsection}{\large\blenderfont\bfseries}
|
|
41
|
+
\RedeclareSectionCommand[beforeskip=3ex plus 1ex minus 0.5ex,afterskip=1.5ex plus 0.3ex,style=section]{chapter}
|
|
42
|
+
\RedeclareSectionCommand[beforeskip=4.5ex plus 1.5ex minus 0.5ex,afterskip=1.5ex plus 0.3ex]{section}
|
|
43
|
+
\RedeclareSectionCommand[beforeskip=3.5ex plus 1ex minus 0.5ex,afterskip=1ex plus 0.2ex]{subsection}
|
|
44
|
+
\RedeclareSectionCommand[beforeskip=2ex plus 0.5ex minus 0.3ex,afterskip=0.8ex plus 0.1ex]{subsubsection}
|
|
45
|
+
\counterwithin{figure}{chapter}
|
|
46
|
+
\counterwithin{table}{chapter}
|
|
47
|
+
\counterwithout{equation}{chapter}
|
|
48
|
+
\renewcommand{\thefigure}{\thechapter.\arabic{figure}}
|
|
49
|
+
\renewcommand{\thetable}{\thechapter.\arabic{table}}
|
|
50
|
+
\renewcommand{\baselinestretch}{1}
|
|
51
|
+
\renewcommand{\arraystretch}{1.5}
|
|
52
|
+
\hyphenpenalty=500
|
|
53
|
+
\exhyphenpenalty=500
|
|
54
|
+
\tolerance=1000
|
|
55
|
+
\emergencystretch=3em
|
|
56
|
+
\widowpenalty=10000
|
|
57
|
+
\clubpenalty=10000
|
|
58
|
+
\raggedbottom
|
|
59
|
+
\setlength{\parskip}{0.5em plus 0.2em minus 0.1em}
|
|
60
|
+
\setlength{\parindent}{0pt}
|
|
61
|
+
% Prevent \mainmatter from inserting a blank page via \cleardoublepage.
|
|
62
|
+
% scrbook's \mainmatter calls \cleardoublepage to force chapters onto a
|
|
63
|
+
% right-hand page; for single-sided reports that just produces an unwanted
|
|
64
|
+
% blank page between the ToC and the first chapter.
|
|
65
|
+
\renewcommand{\mainmatter}{%
|
|
66
|
+
\clearpage
|
|
67
|
+
\@mainmattertrue
|
|
68
|
+
\pagenumbering{arabic}%
|
|
69
|
+
}
|
|
70
|
+
% The running head is set in \blenderfont, which is taller than the default
|
|
71
|
+
% 17pt \headheight; the surplus spilled into the body and triggered an overfull
|
|
72
|
+
% \vbox "while \output is active" on every page. Enlarge \headheight to fit.
|
|
73
|
+
% Done at begin-document so it survives geometry's deferred layout pass, and it
|
|
74
|
+
% leaves the 2cm horizontal margins untouched.
|
|
75
|
+
\AtBeginDocument{\setlength{\headheight}{22pt}}
|
|
76
|
+
\makeatother
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Final, override
|
|
3
|
+
|
|
4
|
+
from pytex.commands.builtin import (
|
|
5
|
+
Blenderfont,
|
|
6
|
+
Hspace,
|
|
7
|
+
Newline,
|
|
8
|
+
Noindent,
|
|
9
|
+
Rule,
|
|
10
|
+
SectionStar,
|
|
11
|
+
Textbf,
|
|
12
|
+
Vfill,
|
|
13
|
+
Vspace,
|
|
14
|
+
)
|
|
15
|
+
from pytex.commands.colors import SelectColor
|
|
16
|
+
from pytex.commands.floats import Titlepage as TitlepageEnv
|
|
17
|
+
from pytex.commands.font import HugeBig
|
|
18
|
+
from pytex.commands.setspace import Setstretch
|
|
19
|
+
from pytex.commands.tables import Tabular
|
|
20
|
+
from pytex.helpers.parenting import attach
|
|
21
|
+
from pytex.interface.tex import TeX
|
|
22
|
+
from pytex.model.concat import Concat
|
|
23
|
+
from pytex.model.environment import Environment
|
|
24
|
+
from pytex.model.raw import Raw
|
|
25
|
+
from pytex.registry import Registry
|
|
26
|
+
|
|
27
|
+
from .logos import titlepage_logo_overlay
|
|
28
|
+
|
|
29
|
+
__all__ = ["TitlePage", "TitlePageDataLine"]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class TitlePageDataLine:
|
|
34
|
+
label: str
|
|
35
|
+
value: TeX | str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _data_table_body(lines: tuple[TitlePageDataLine, ...]) -> TeX:
|
|
39
|
+
parts: list[TeX | str] = ["&"]
|
|
40
|
+
for line in lines:
|
|
41
|
+
parts.extend((Newline(), Textbf(line.label), " & ", line.value))
|
|
42
|
+
return Concat(*parts)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@Registry.add
|
|
46
|
+
@dataclass
|
|
47
|
+
class TitlePage(TeX):
|
|
48
|
+
"""HSRT titlepage with abstract, keywords, and data table.
|
|
49
|
+
|
|
50
|
+
``logo_names`` drives a tikz overlay that chains logos left-to-right from
|
|
51
|
+
the top-left corner of the page, mirroring the ``\\foreach`` loop in
|
|
52
|
+
``tmp/Pages/Titlepage.tex`` (loop unrolled in Python).
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
title: Final[TeX | str]
|
|
56
|
+
abstract: Final[TeX | str] = ""
|
|
57
|
+
keywords: Final[TeX | str] = ""
|
|
58
|
+
data_lines: Final[tuple[TitlePageDataLine, ...]] = ()
|
|
59
|
+
logo_names: Final[tuple[str, ...]] = ()
|
|
60
|
+
_parent: "TeX | None" = field(default=None, init=False, compare=False, repr=False)
|
|
61
|
+
|
|
62
|
+
def __post_init__(self) -> None:
|
|
63
|
+
attach(self, self.title, self.abstract, self.keywords)
|
|
64
|
+
for line in self.data_lines:
|
|
65
|
+
attach(self, line.value)
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
@override
|
|
69
|
+
def children(self) -> tuple[TeX, ...]:
|
|
70
|
+
candidates = (
|
|
71
|
+
self.title,
|
|
72
|
+
self.abstract,
|
|
73
|
+
self.keywords,
|
|
74
|
+
*(line.value for line in self.data_lines),
|
|
75
|
+
)
|
|
76
|
+
return tuple(v for v in candidates if isinstance(v, TeX))
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
@override
|
|
80
|
+
def rendered(self) -> str:
|
|
81
|
+
# Tikz overlay: logos chained left-to-right from the top-left corner,
|
|
82
|
+
# matching tmp/Pages/Titlepage.tex but with the foreach unrolled here.
|
|
83
|
+
logo_overlay = Raw(
|
|
84
|
+
titlepage_logo_overlay(self.logo_names),
|
|
85
|
+
allow_replacements=False,
|
|
86
|
+
)
|
|
87
|
+
# Flag true while the titlepage ships out so footer_logo_hook
|
|
88
|
+
# suppresses the bottom-right footer logos on this page only.
|
|
89
|
+
# Reset after \end{titlepage} (which \clearpages, shipping the page
|
|
90
|
+
# while the flag is still true).
|
|
91
|
+
return Concat(
|
|
92
|
+
Raw(r"\HSRTTitlePagetrue", allow_replacements=False),
|
|
93
|
+
TitlepageEnv(
|
|
94
|
+
Concat(
|
|
95
|
+
logo_overlay,
|
|
96
|
+
Vspace("4cm"),
|
|
97
|
+
Environment(
|
|
98
|
+
"flushleft",
|
|
99
|
+
Concat(
|
|
100
|
+
Raw(r"\hyphenpenalty=10000\exhyphenpenalty=10000"),
|
|
101
|
+
Noindent(),
|
|
102
|
+
SelectColor("black"),
|
|
103
|
+
Textbf(
|
|
104
|
+
Concat(
|
|
105
|
+
Blenderfont(),
|
|
106
|
+
HugeBig(),
|
|
107
|
+
Hspace("-2.5pt", star=True),
|
|
108
|
+
self.title,
|
|
109
|
+
)
|
|
110
|
+
),
|
|
111
|
+
Raw(r"\par"),
|
|
112
|
+
Vspace("-0.5em"),
|
|
113
|
+
Rule(r"\textwidth", "0.5mm"),
|
|
114
|
+
),
|
|
115
|
+
),
|
|
116
|
+
Vspace("2em"),
|
|
117
|
+
Setstretch("1.0"),
|
|
118
|
+
SectionStar("Abstract"),
|
|
119
|
+
Vspace("-1em"),
|
|
120
|
+
self.abstract,
|
|
121
|
+
Vspace("1em", star=True),
|
|
122
|
+
Raw(r"\par\noindent "),
|
|
123
|
+
Textbf("Keywords"),
|
|
124
|
+
Raw(r"\par\noindent "),
|
|
125
|
+
self.keywords,
|
|
126
|
+
Vfill(),
|
|
127
|
+
Noindent(),
|
|
128
|
+
Setstretch("1.0"),
|
|
129
|
+
Tabular(
|
|
130
|
+
r"@{} p{30mm} p{\dimexpr\textwidth-30mm-2\tabcolsep\relax} @{}",
|
|
131
|
+
_data_table_body(self.data_lines),
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
),
|
|
135
|
+
Raw(r"\HSRTTitlePagefalse", allow_replacements=False),
|
|
136
|
+
).rendered
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
__all__ = ["Variant", "default_logo_names"]
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Variant(Enum):
|
|
7
|
+
"""HSRT report variant — picks default logo set on the title page."""
|
|
8
|
+
|
|
9
|
+
INF = "inf"
|
|
10
|
+
STUPA = "stupa"
|
|
11
|
+
ASTA = "asta"
|
|
12
|
+
ECHO = "echo"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
DEFAULT_LOGOS: dict[Variant, tuple[str, ...]] = {
|
|
16
|
+
Variant.INF: ("INF",),
|
|
17
|
+
Variant.STUPA: ("STUPA",),
|
|
18
|
+
Variant.ASTA: ("ASTA",),
|
|
19
|
+
Variant.ECHO: ("ECHO",),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def default_logo_names(variant: Variant) -> tuple[str, ...]:
|
|
24
|
+
return DEFAULT_LOGOS.get(variant, ())
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Final, override
|
|
3
|
+
|
|
4
|
+
from pytex.commands.builtin import Textbf, Vspace
|
|
5
|
+
from pytex.commands.floats import Columnbreak, Multicols
|
|
6
|
+
from pytex.commands.fontawesome import FaIcon
|
|
7
|
+
from pytex.helpers.parenting import attach
|
|
8
|
+
from pytex.interface.package import PackageProtocol
|
|
9
|
+
from pytex.interface.tex import TeX
|
|
10
|
+
from pytex.model.concat import Concat
|
|
11
|
+
from pytex.model.package import DefinePackage
|
|
12
|
+
from pytex.packages import FONTAWESOME, MDFRAMED, XCOLOR
|
|
13
|
+
from pytex.registry import Registry
|
|
14
|
+
|
|
15
|
+
from .boxes import ColoredBox, CustomBox
|
|
16
|
+
|
|
17
|
+
__all__ = ["VotingResults"]
|
|
18
|
+
|
|
19
|
+
# `multicol` is not in pytex.packages; the multicols env needs it. Declared here
|
|
20
|
+
# because VotingResults builds the Multicols node inside `.rendered`, so the
|
|
21
|
+
# command's own requirements never reach the document's package collector.
|
|
22
|
+
MULTICOL: Final = DefinePackage("multicol")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _vote_color(yes: int, no: int) -> str:
|
|
26
|
+
if yes > no:
|
|
27
|
+
return "britishracinggreen"
|
|
28
|
+
if yes < no:
|
|
29
|
+
return "red"
|
|
30
|
+
return "eggplant"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@Registry.add
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class VotingResults(TeX):
|
|
36
|
+
"""Voting tally box. Header color picked in Python from yes/no counts."""
|
|
37
|
+
|
|
38
|
+
yes: Final[int]
|
|
39
|
+
no: Final[int]
|
|
40
|
+
abstain: Final[int]
|
|
41
|
+
body: Final[TeX | str] = ""
|
|
42
|
+
_parent: "TeX | None" = field(default=None, init=False, compare=False, repr=False)
|
|
43
|
+
|
|
44
|
+
def __post_init__(self) -> None:
|
|
45
|
+
attach(self, self.body)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def color(self) -> str:
|
|
49
|
+
return _vote_color(self.yes, self.no)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
@override
|
|
53
|
+
def children(self) -> tuple[TeX, ...]:
|
|
54
|
+
return (self.body,) if isinstance(self.body, TeX) else ()
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
@override
|
|
58
|
+
def requires(self) -> frozenset[PackageProtocol]:
|
|
59
|
+
return frozenset({MDFRAMED, XCOLOR, FONTAWESOME, MULTICOL})
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
@override
|
|
63
|
+
def rendered(self) -> str:
|
|
64
|
+
return ColoredBox(
|
|
65
|
+
body=Concat(
|
|
66
|
+
self.body,
|
|
67
|
+
Vspace("-2em", star=True),
|
|
68
|
+
Multicols(
|
|
69
|
+
3,
|
|
70
|
+
Concat(
|
|
71
|
+
CustomBox(
|
|
72
|
+
Concat(Textbf("Ja:"), " ", str(self.yes)),
|
|
73
|
+
"thumbs-up",
|
|
74
|
+
"britishracinggreen",
|
|
75
|
+
),
|
|
76
|
+
Columnbreak(),
|
|
77
|
+
CustomBox(
|
|
78
|
+
Concat(Textbf("Nein:"), " ", str(self.no)),
|
|
79
|
+
"thumbs-down",
|
|
80
|
+
"red",
|
|
81
|
+
),
|
|
82
|
+
Columnbreak(),
|
|
83
|
+
CustomBox(
|
|
84
|
+
Concat(Textbf("Enthaltung:"), " ", str(self.abstain)),
|
|
85
|
+
None,
|
|
86
|
+
"eggplant",
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
),
|
|
91
|
+
icon=FaIcon("vote-yea"),
|
|
92
|
+
icon_color=self.color,
|
|
93
|
+
icon_size="24pt",
|
|
94
|
+
icon_offset_x="-0.2cm",
|
|
95
|
+
background_color=self.color,
|
|
96
|
+
).rendered
|