pytex-preprocessor 0.4.2__tar.gz → 0.4.4__tar.gz

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 (133) hide show
  1. {pytex_preprocessor-0.4.2/src/pytex_preprocessor.egg-info → pytex_preprocessor-0.4.4}/PKG-INFO +1 -1
  2. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/pyproject.toml +1 -1
  3. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/builtin.py +15 -0
  4. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/helpers/sanitize.py +5 -0
  5. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/packages.py +4 -0
  6. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_builder/variants.py +41 -0
  7. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_markdown/convert.py +37 -3
  8. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4/src/pytex_preprocessor.egg-info}/PKG-INFO +1 -1
  9. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/LICENSE +0 -0
  10. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/README.md +0 -0
  11. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/setup.cfg +0 -0
  12. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/__init__.py +0 -0
  13. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/__init__.py +0 -0
  14. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/biblatex.py +0 -0
  15. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/captions.py +0 -0
  16. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/cleveref.py +0 -0
  17. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/colors.py +0 -0
  18. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/conditionals.py +0 -0
  19. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/counters.py +0 -0
  20. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/definitions.py +0 -0
  21. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/floats.py +0 -0
  22. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/font.py +0 -0
  23. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/fontawesome.py +0 -0
  24. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/fontspec.py +0 -0
  25. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/geometry.py +0 -0
  26. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/glossaries.py +0 -0
  27. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/graphics.py +0 -0
  28. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/hooks.py +0 -0
  29. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/hyperref.py +0 -0
  30. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/lengths.py +0 -0
  31. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/listings.py +0 -0
  32. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/mdframed.py +0 -0
  33. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/picture.py +0 -0
  34. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/setspace.py +0 -0
  35. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/commands/tables.py +0 -0
  36. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/helpers/__init__.py +0 -0
  37. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/helpers/coerce.py +0 -0
  38. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/helpers/parenting.py +0 -0
  39. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/helpers/with_package.py +0 -0
  40. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/interface/__init__.py +0 -0
  41. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/interface/control_sequence.py +0 -0
  42. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/interface/package.py +0 -0
  43. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/interface/tex.py +0 -0
  44. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/__init__.py +0 -0
  45. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/color.py +0 -0
  46. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/comment.py +0 -0
  47. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/concat.py +0 -0
  48. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/control_sequence.py +0 -0
  49. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/document.py +0 -0
  50. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/document_class.py +0 -0
  51. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/empty.py +0 -0
  52. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/environment.py +0 -0
  53. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/image.py +0 -0
  54. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/include.py +0 -0
  55. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/length.py +0 -0
  56. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/math.py +0 -0
  57. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/package.py +0 -0
  58. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/model/raw.py +0 -0
  59. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/registry.py +0 -0
  60. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex/template.py +0 -0
  61. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_analyze/__init__.py +0 -0
  62. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_analyze/analyze.py +0 -0
  63. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_analyze/optimize.py +0 -0
  64. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_builder/__init__.py +0 -0
  65. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_builder/build.py +0 -0
  66. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_builder/console.py +0 -0
  67. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_builder/render.py +0 -0
  68. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_builder/tectonic.py +0 -0
  69. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_builder/tex2py.py +0 -0
  70. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_builder/tree.py +0 -0
  71. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/__init__.py +0 -0
  72. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Bold.ttf +0 -0
  73. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-BoldItalic.ttf +0 -0
  74. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Book.ttf +0 -0
  75. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-BookItalic.ttf +0 -0
  76. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Medium.ttf +0 -0
  77. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-MediumItalic.ttf +0 -0
  78. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Strong.ttf +0 -0
  79. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Thin.ttf +0 -0
  80. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-ThinItalic.ttf +0 -0
  81. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Black.ttf +0 -0
  82. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Bold.ttf +0 -0
  83. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-BoldItalic.ttf +0 -0
  84. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Italic.ttf +0 -0
  85. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Medium.ttf +0 -0
  86. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Regular.ttf +0 -0
  87. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/fonts/Times New Roman.ttf +0 -0
  88. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/logos/ASTA.svg +0 -0
  89. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/logos/DUMMY.png +0 -0
  90. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/logos/DUMMY_FOOT.png +0 -0
  91. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/logos/ECHO.svg +0 -0
  92. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/logos/HSRT.pdf +0 -0
  93. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/logos/INF.pdf +0 -0
  94. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/logos/STUPA.pdf +0 -0
  95. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/assets/logos/Skyline.pdf +0 -0
  96. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/boxes.py +0 -0
  97. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/citations.py +0 -0
  98. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/cleveref_names.py +0 -0
  99. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/colors.py +0 -0
  100. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/document.py +0 -0
  101. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/fonts.py +0 -0
  102. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/glossary.py +0 -0
  103. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/hyperref_config.py +0 -0
  104. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/listings.py +0 -0
  105. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/logos.py +0 -0
  106. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/pagebreak.py +0 -0
  107. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/pagesetup.py +0 -0
  108. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/tex/pagesetup.tex +0 -0
  109. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/titlepage.py +0 -0
  110. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/variants.py +0 -0
  111. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/voting.py +0 -0
  112. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/watermark.py +0 -0
  113. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_hsrtreport/wordcount.py +0 -0
  114. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_koma/__init__.py +0 -0
  115. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_koma/commands.py +0 -0
  116. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_koma/document.py +0 -0
  117. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_markdown/__init__.py +0 -0
  118. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_markdown/escape.py +0 -0
  119. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_preprocessor.egg-info/SOURCES.txt +0 -0
  120. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_preprocessor.egg-info/dependency_links.txt +0 -0
  121. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_preprocessor.egg-info/entry_points.txt +0 -0
  122. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_preprocessor.egg-info/requires.txt +0 -0
  123. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_preprocessor.egg-info/top_level.txt +0 -0
  124. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_protocol/__init__.py +0 -0
  125. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_protocol/convert.py +0 -0
  126. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_protocol/document.py +0 -0
  127. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_protocol/entries.py +0 -0
  128. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_protocol/frontmatter.py +0 -0
  129. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_protocol/header.py +0 -0
  130. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_protocol/shortcodes.py +0 -0
  131. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_protocol/signatures.py +0 -0
  132. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_tikz/__init__.py +0 -0
  133. {pytex_preprocessor-0.4.2 → pytex_preprocessor-0.4.4}/src/pytex_tikz/tikz.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytex-preprocessor
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Type-safe LaTeX document generation with Python
5
5
  Author-email: Frederik Beimgraben <frederik@beimgraben.net>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pytex-preprocessor"
7
- version = "0.4.2"
7
+ version = "0.4.4"
8
8
  authors = [
9
9
  { name="Frederik Beimgraben", email="frederik@beimgraben.net" },
10
10
  ]
@@ -1,8 +1,10 @@
1
+ from ..helpers.with_package import with_package
1
2
  from ..interface.tex import TeX
2
3
  from ..model.concat import Concat
3
4
  from ..model.control_sequence import ControlSequence, Parameter
4
5
  from ..model.environment import Environment
5
6
  from ..model.raw import Raw
7
+ from ..packages import EUROSYM
6
8
  from ..registry import Registry
7
9
 
8
10
  __all__ = [
@@ -24,6 +26,7 @@ __all__ = [
24
26
  "Emph",
25
27
  "EndAccSupp",
26
28
  "Enumerate",
29
+ "Euro",
27
30
  "FlushLeft",
28
31
  "FlushRight",
29
32
  "Footnote",
@@ -235,6 +238,18 @@ def Newline() -> TeX:
235
238
  return Raw("\\\\")
236
239
 
237
240
 
241
+ @Registry.add
242
+ @with_package(EUROSYM)
243
+ def Euro() -> TeX:
244
+ """Euro sign via ``eurosym``'s ``\\euro{}``.
245
+
246
+ eurosym ships its own glyph, so it renders even when the text font (e.g.
247
+ DIN) has no euro glyph. The trailing empty group stops the macro from
248
+ gobbling a following space, keeping ``50\\euro{} each`` spaced correctly.
249
+ """
250
+ return ControlSequence("euro", (Parameter(Raw("")),))
251
+
252
+
238
253
  @Registry.add
239
254
  def Linebreak(n: int | None = None) -> TeX:
240
255
  if n is None:
@@ -34,6 +34,11 @@ ESCAPES: Final[dict[str, str]] = {
34
34
  "}": r"\}",
35
35
  "~": r"\textasciitilde{}",
36
36
  "^": r"\textasciicircum{}",
37
+ # babel ngerman (always loaded) makes `"` an active shorthand char, so a
38
+ # literal double quote would be swallowed/mangled. `\textquotedbl{}` is a
39
+ # font-encoding macro (T1/textcomp), not a shorthand, so it prints an
40
+ # upright double quote untouched; the empty group stops space-gobbling.
41
+ '"': r"\textquotedbl{}",
37
42
  }
38
43
 
39
44
 
@@ -113,6 +113,9 @@ ARYDSHLN: Package = DefinePackage("arydshln")
113
113
  LMODERN: Package = DefinePackage("lmodern")
114
114
  HYPHENAT: Package = DefinePackage("hyphenat")
115
115
  FP: Package = DefinePackage("fp")
116
+ # Ships its own euro glyph, so `\euro{}` renders independent of the text font
117
+ # (e.g. DIN, whose font set lacks a euro glyph and would otherwise tofu).
118
+ EUROSYM: Package = DefinePackage("eurosym")
116
119
 
117
120
 
118
121
  class Packages(Enum):
@@ -219,3 +222,4 @@ class Packages(Enum):
219
222
  LMODERN = LMODERN
220
223
  HYPHENAT = HYPHENAT
221
224
  FP = FP
225
+ EUROSYM = EUROSYM
@@ -26,6 +26,7 @@ from pytex.model.concat import Concat
26
26
  from pytex.model.document import Document
27
27
  from pytex.model.raw import Raw
28
28
  from pytex_hsrtreport.document import HSRTReport
29
+ from pytex_hsrtreport.titlepage import TitlePageDataLine
29
30
  from pytex_hsrtreport.variants import Variant
30
31
  from pytex_markdown import Markdown, escape_latex
31
32
  from pytex_protocol.document import build_protocol
@@ -108,6 +109,9 @@ def _report(body: str, options: dict[str, object]) -> TeX:
108
109
  show_toc=True,
109
110
  title=escape_latex(title) if title is not None else None,
110
111
  author=_escaped(_str(options, "author", "autor")),
112
+ abstract=_escaped(_str(options, "abstract", "zusammenfassung")),
113
+ keywords=_escaped(_keywords(options)),
114
+ data_lines=_data_lines(options),
111
115
  # Map the shallowest heading in the body to \chapter, so headings nest
112
116
  # under it. Without this, a doc whose top level is `##` (because `#` was
113
117
  # consumed as the title) would render chapterless sections numbered 0.x.
@@ -153,6 +157,43 @@ def _escaped(value: str | None) -> str | None:
153
157
  return escape_latex(value) if value is not None else None
154
158
 
155
159
 
160
+ def _keywords(options: Mapping[str, object]) -> str | None:
161
+ """Title-page keywords from `keywords`/`schlagworte` (string or list)."""
162
+ for key in ("keywords", "schlagworte"):
163
+ value = options.get(key)
164
+ if isinstance(value, list) and value:
165
+ return ", ".join(str(item) for item in value) # pyright: ignore[reportUnknownVariableType, reportUnknownArgumentType]
166
+ if isinstance(value, str) and value:
167
+ return value
168
+ return None
169
+
170
+
171
+ def _data_lines(options: Mapping[str, object]) -> tuple[TitlePageDataLine, ...]:
172
+ """Title-page data table from `datalines`/`data`.
173
+
174
+ Each entry is a ``"Label: value"`` string (frontmatter has no nested maps),
175
+ given as a block list, flow list, or a single scalar. Entries without a
176
+ colon are skipped.
177
+ """
178
+ raw = options.get("datalines", options.get("data"))
179
+ if isinstance(raw, list):
180
+ items = [str(item) for item in raw] # pyright: ignore[reportUnknownVariableType, reportUnknownArgumentType]
181
+ elif isinstance(raw, str) and raw:
182
+ items = [raw]
183
+ else:
184
+ return ()
185
+ lines: list[TitlePageDataLine] = []
186
+ for item in items:
187
+ label, sep, value = item.partition(":")
188
+ if sep and label.strip():
189
+ lines.append(
190
+ TitlePageDataLine(
191
+ escape_latex(label.strip()), escape_latex(value.strip())
192
+ )
193
+ )
194
+ return tuple(lines)
195
+
196
+
156
197
  def _derive_title(body: str) -> tuple[str | None, str]:
157
198
  """Pull the first ATX `#` heading out of `body` to use as the title.
158
199
 
@@ -10,6 +10,7 @@ this module depends on ``pytex_hsrtreport``.
10
10
  from __future__ import annotations
11
11
 
12
12
  import re
13
+ from pathlib import Path
13
14
  from typing import TYPE_CHECKING, Any, Final, cast
14
15
 
15
16
  from pytex.commands.builtin import (
@@ -17,6 +18,7 @@ from pytex.commands.builtin import (
17
18
  Chapter,
18
19
  Emph,
19
20
  Enumerate,
21
+ Euro,
20
22
  Itemize,
21
23
  Newline,
22
24
  Noindent,
@@ -101,6 +103,11 @@ CALLOUTS: Final[dict[str, Callable[[TeX | str], TeX]]] = {
101
103
  "ERROR": WarningBox,
102
104
  }
103
105
 
106
+ # U+20AC EURO SIGN. The DIN text font has no euro glyph, so the raw char would
107
+ # render as tofu; we splice in a real ``Euro`` node (eurosym ``\euro{}``) which
108
+ # ships its own glyph and registers the package requirement.
109
+ EURO_SIGN: Final[str] = "€"
110
+
104
111
  PARBREAK: Final[TeX] = Raw("\n\n")
105
112
 
106
113
  # GFM cell alignment -> tabularx ``X`` column spec. ``X`` columns share the
@@ -146,6 +153,26 @@ def _escape_text(text: str) -> str:
146
153
  return "".join(out)
147
154
 
148
155
 
156
+ def _prose(text: str) -> TeX:
157
+ """Escape prose, splitting literal euro signs into ``Euro`` nodes.
158
+
159
+ Each ``€`` becomes a real :func:`Euro` node (eurosym ``\\euro{}``) instead
160
+ of a raw char so the preamble auto-loads ``eurosym`` and the glyph renders
161
+ even under the DIN font. Text between the euros keeps its arrow/escape
162
+ handling, and the split preserves surrounding spacing exactly (e.g. ``50€``
163
+ stays glyph-adjacent, ``€ 50`` keeps its space).
164
+ """
165
+ if EURO_SIGN not in text:
166
+ return Raw(_escape_text(text))
167
+ parts: list[TeX] = []
168
+ for i, segment in enumerate(text.split(EURO_SIGN)):
169
+ if i:
170
+ parts.append(Euro())
171
+ if segment:
172
+ parts.append(Raw(_escape_text(segment)))
173
+ return Concat(*parts)
174
+
175
+
149
176
  class MarkdownConverter:
150
177
  """Walk a marko AST, producing a single ``TeX`` tree.
151
178
 
@@ -169,7 +196,7 @@ class MarkdownConverter:
169
196
  # RawText / CodeSpan / Literal etc. carry a plain string.
170
197
  if kind == "CodeSpan":
171
198
  return Texttt(Raw(escape_latex(text)))
172
- return Raw(_escape_text(text))
199
+ return _prose(text)
173
200
 
174
201
  if kind == "StrongEmphasis":
175
202
  return Bold(self.inlines(node))
@@ -182,7 +209,14 @@ class MarkdownConverter:
182
209
  # Relative/local/anchor target: keep the text, drop the dead link.
183
210
  return self.inlines(node)
184
211
  if kind == "Image":
185
- return IncludeImage(str(getattr(node, "dest", "")))
212
+ dest = str(getattr(node, "dest", ""))
213
+ if EXTERNAL_URL_RE.match(dest):
214
+ return IncludeImage(dest)
215
+ # The .tex is compiled in the build dir, not next to the Markdown
216
+ # source, so a relative path would not resolve. Make it absolute
217
+ # (relative to the CWD the build runs from) so \includegraphics
218
+ # finds the file without copying or base64-embedding it.
219
+ return IncludeImage(str(Path(dest).resolve()))
186
220
  if kind == "LineBreak":
187
221
  # Hard break -> newline; soft break -> a plain space.
188
222
  soft = bool(getattr(node, "soft", False))
@@ -238,7 +272,7 @@ class MarkdownConverter:
238
272
  # Rebuild the first paragraph with the marker stripped, keep the rest.
239
273
  stripped = first_text[match.end() :]
240
274
  head = Concat(
241
- Raw(_escape_text(stripped)),
275
+ _prose(stripped),
242
276
  *(self.inline(c) for c in inner[1:]),
243
277
  )
244
278
  body_blocks = [head, *(self.block(b) for b in kids[1:])]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytex-preprocessor
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: Type-safe LaTeX document generation with Python
5
5
  Author-email: Frederik Beimgraben <frederik@beimgraben.net>
6
6
  License: GNU GENERAL PUBLIC LICENSE