pytex-preprocessor 1.1.0__tar.gz → 1.2.0__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 (148) hide show
  1. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/PKG-INFO +1 -1
  2. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/pyproject.toml +1 -1
  3. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_markdown/convert.py +104 -2
  4. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_preprocessor.egg-info/PKG-INFO +1 -1
  5. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/LICENSE +0 -0
  6. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/README.md +0 -0
  7. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/setup.cfg +0 -0
  8. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/__init__.py +0 -0
  9. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/__init__.py +0 -0
  10. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/biblatex.py +0 -0
  11. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/builtin.py +0 -0
  12. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/captions.py +0 -0
  13. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/cleveref.py +0 -0
  14. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/colors.py +0 -0
  15. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/conditionals.py +0 -0
  16. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/counters.py +0 -0
  17. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/definitions.py +0 -0
  18. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/floats.py +0 -0
  19. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/font.py +0 -0
  20. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/fontawesome.py +0 -0
  21. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/fontspec.py +0 -0
  22. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/geometry.py +0 -0
  23. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/glossaries.py +0 -0
  24. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/graphics.py +0 -0
  25. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/hooks.py +0 -0
  26. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/hyperref.py +0 -0
  27. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/lengths.py +0 -0
  28. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/listings.py +0 -0
  29. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/mdframed.py +0 -0
  30. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/picture.py +0 -0
  31. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/setspace.py +0 -0
  32. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/commands/tables.py +0 -0
  33. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/helpers/__init__.py +0 -0
  34. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/helpers/coerce.py +0 -0
  35. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/helpers/parenting.py +0 -0
  36. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/helpers/sanitize.py +0 -0
  37. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/helpers/with_package.py +0 -0
  38. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/interface/__init__.py +0 -0
  39. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/interface/control_sequence.py +0 -0
  40. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/interface/package.py +0 -0
  41. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/interface/tex.py +0 -0
  42. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/__init__.py +0 -0
  43. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/color.py +0 -0
  44. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/comment.py +0 -0
  45. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/concat.py +0 -0
  46. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/control_sequence.py +0 -0
  47. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/document.py +0 -0
  48. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/document_class.py +0 -0
  49. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/empty.py +0 -0
  50. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/environment.py +0 -0
  51. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/image.py +0 -0
  52. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/include.py +0 -0
  53. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/length.py +0 -0
  54. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/math.py +0 -0
  55. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/package.py +0 -0
  56. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/model/raw.py +0 -0
  57. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/packages.py +0 -0
  58. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/registry.py +0 -0
  59. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex/template.py +0 -0
  60. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_analyze/__init__.py +0 -0
  61. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_analyze/analyze.py +0 -0
  62. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_analyze/optimize.py +0 -0
  63. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_api/__init__.py +0 -0
  64. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_api/_compile.py +0 -0
  65. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_api/_models.py +0 -0
  66. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_api/_policy.py +0 -0
  67. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_api/_render.py +0 -0
  68. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_api/_sandbox.py +0 -0
  69. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_api/_security.py +0 -0
  70. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_api/sandbox_init.py +0 -0
  71. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_builder/__init__.py +0 -0
  72. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_builder/build.py +0 -0
  73. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_builder/console.py +0 -0
  74. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_builder/render.py +0 -0
  75. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_builder/tectonic.py +0 -0
  76. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_builder/tex2py.py +0 -0
  77. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_builder/tree.py +0 -0
  78. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_builder/variants.py +0 -0
  79. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_components/__init__.py +0 -0
  80. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_components/boxes.py +0 -0
  81. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_components/citations.py +0 -0
  82. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_components/cleveref_names.py +0 -0
  83. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_components/pagebreak.py +0 -0
  84. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_components/voting.py +0 -0
  85. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_components/watermark.py +0 -0
  86. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_components/wordcount.py +0 -0
  87. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/__init__.py +0 -0
  88. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Bold.ttf +0 -0
  89. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-BoldItalic.ttf +0 -0
  90. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Book.ttf +0 -0
  91. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-BookItalic.ttf +0 -0
  92. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Medium.ttf +0 -0
  93. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-MediumItalic.ttf +0 -0
  94. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Strong.ttf +0 -0
  95. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-Thin.ttf +0 -0
  96. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/Blender/Blender-ThinItalic.ttf +0 -0
  97. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Black.ttf +0 -0
  98. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Bold.ttf +0 -0
  99. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-BoldItalic.ttf +0 -0
  100. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Italic.ttf +0 -0
  101. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Medium.ttf +0 -0
  102. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/DIN/DIN-Regular.ttf +0 -0
  103. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/fonts/Times New Roman.ttf +0 -0
  104. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/logos/ASTA.svg +0 -0
  105. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/logos/DUMMY.png +0 -0
  106. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/logos/DUMMY_FOOT.png +0 -0
  107. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/logos/ECHO.svg +0 -0
  108. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/logos/HSRT.pdf +0 -0
  109. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/logos/INF.pdf +0 -0
  110. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/logos/MAKERS-Icon.svg +0 -0
  111. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/logos/MAKERS-RAlign.svg +0 -0
  112. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/logos/MAKERS.svg +0 -0
  113. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/logos/STUPA.pdf +0 -0
  114. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/assets/logos/Skyline.pdf +0 -0
  115. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/colors.py +0 -0
  116. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/document.py +0 -0
  117. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/fonts.py +0 -0
  118. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/glossary.py +0 -0
  119. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/hyperref_config.py +0 -0
  120. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/listings.py +0 -0
  121. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/logos.py +0 -0
  122. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/pagesetup.py +0 -0
  123. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/tex/pagesetup.tex +0 -0
  124. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/titlepage.py +0 -0
  125. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_hsrtreport/variants.py +0 -0
  126. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_koma/__init__.py +0 -0
  127. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_koma/commands.py +0 -0
  128. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_koma/document.py +0 -0
  129. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_markdown/__init__.py +0 -0
  130. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_markdown/escape.py +0 -0
  131. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_markdown/frontmatter.py +0 -0
  132. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_markdown/glyphs.py +0 -0
  133. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_markdown/protocol/__init__.py +0 -0
  134. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_markdown/protocol/convert.py +0 -0
  135. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_markdown/protocol/document.py +0 -0
  136. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_markdown/protocol/entries.py +0 -0
  137. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_markdown/protocol/header.py +0 -0
  138. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_markdown/protocol/shortcodes.py +0 -0
  139. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_markdown/protocol/signatures.py +0 -0
  140. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_preprocessor.egg-info/SOURCES.txt +0 -0
  141. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_preprocessor.egg-info/dependency_links.txt +0 -0
  142. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_preprocessor.egg-info/entry_points.txt +0 -0
  143. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_preprocessor.egg-info/requires.txt +0 -0
  144. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_preprocessor.egg-info/top_level.txt +0 -0
  145. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_protocol/__init__.py +0 -0
  146. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_protocol/frontmatter.py +0 -0
  147. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_tikz/__init__.py +0 -0
  148. {pytex_preprocessor-1.1.0 → pytex_preprocessor-1.2.0}/src/pytex_tikz/tikz.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytex-preprocessor
3
- Version: 1.1.0
3
+ Version: 1.2.0
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 = "1.1.0"
7
+ version = "1.2.0"
8
8
  authors = [
9
9
  { name="Frederik Beimgraben", email="frederik@beimgraben.net" },
10
10
  ]
@@ -36,11 +36,14 @@ from pytex.commands.builtin import (
36
36
  from pytex.commands.hyperref import Href
37
37
  from pytex.commands.listings import Lstlisting
38
38
  from pytex.commands.tables import Bottomrule, Midrule, Tabularx, Toprule
39
+ from pytex.helpers.with_package import WithPackage
39
40
  from pytex.interface.tex import TeX
40
41
  from pytex.model.concat import Concat
41
42
  from pytex.model.empty import Empty
42
43
  from pytex.model.image import IncludeImage
44
+ from pytex.model.math import DisplayMath, InlineMath
43
45
  from pytex.model.raw import Raw, pytex_namespace
46
+ from pytex.packages import AMSFONTS, AMSMATH
44
47
  from pytex_components.boxes import ImportantBox, InfoBox, SuccessBox, WarningBox
45
48
 
46
49
  from .escape import escape_latex
@@ -138,6 +141,17 @@ _LSTLISTING_END_RE: Final[re.Pattern[str]] = re.compile(
138
141
  r"^(?P<lead>[ \t]*)\\end\{lstlisting\}", re.MULTILINE
139
142
  )
140
143
 
144
+ # TeX math in Markdown, the syntax that KaTeX and pandoc use. `$…$` sets a
145
+ # formula in the line, a paragraph `$$…$$` sets it on its own line. The opening
146
+ # `$` carries no space behind it, the closing `$` no space in front of it and no
147
+ # digit behind it, so a price such as "$5 und $6" stays prose. A `\$` is a
148
+ # dollar sign and opens no formula. The body of a formula is LaTeX, so it
149
+ # reaches the document unescaped.
150
+ MATH_RE: Final[re.Pattern[str]] = re.compile(
151
+ r"(?<!\\)\$(?![\s$])((?:\\.|[^\\$\n])*?)(?<!\s)\$(?!\d)"
152
+ )
153
+ DISPLAY_MATH_RE: Final[re.Pattern[str]] = re.compile(r"\$\$(.+?)\$\$", re.DOTALL)
154
+
141
155
  PARBREAK: Final[TeX] = Raw("\n\n")
142
156
 
143
157
  # Vertical space above and below a rendered table. `\addvspace` (and not
@@ -176,6 +190,51 @@ def _text(node: object) -> str | None:
176
190
  return ch if isinstance(ch, str) else None
177
191
 
178
192
 
193
+ def _source_text(node: object) -> str | None:
194
+ """Return the Markdown source of one plain inline node.
195
+
196
+ marko drops the backslash of an escape, so a `Literal` node holds the bare
197
+ character. A formula body is LaTeX and needs its backslashes, so the
198
+ function puts the backslash back. It returns `None` for every node that
199
+ carries structure, for example an emphasis or a code span. A formula never
200
+ crosses such a node.
201
+ """
202
+ kind = _kind(node)
203
+ if kind == "RawText":
204
+ return _text(node)
205
+ if kind == "Literal":
206
+ return "\\" + (_text(node) or "")
207
+ if kind == "LineBreak":
208
+ return "\n"
209
+ return None
210
+
211
+
212
+ def _math(node: TeX) -> TeX:
213
+ """Load the math packages for a formula that a person wrote.
214
+
215
+ The body is LaTeX from a Markdown document. It commonly uses a macro of
216
+ amsmath (`\\text`, `\\dfrac`) or a symbol of amsfonts, so the formula
217
+ carries both packages into the preamble.
218
+ """
219
+ return WithPackage(WithPackage(node, AMSMATH), AMSFONTS)
220
+
221
+
222
+ def _display_math(node: object) -> TeX | None:
223
+ """Return the display formula of a `$$…$$` paragraph, or `None`.
224
+
225
+ A paragraph that holds nothing but one `$$…$$` block becomes a formula on
226
+ its own line. Every other paragraph stays prose.
227
+ """
228
+ parts = [_source_text(c) for c in _children(node)]
229
+ if not parts or any(part is None for part in parts):
230
+ return None
231
+ match = DISPLAY_MATH_RE.fullmatch("".join(cast("list[str]", parts)).strip())
232
+ if match is None:
233
+ return None
234
+ body = match.group(1).strip()
235
+ return _math(DisplayMath(Raw(body))) if body else None
236
+
237
+
179
238
  def _escape_text(text: str) -> str:
180
239
  """Escape prose for LaTeX and turn each ASCII arrow into a math arrow.
181
240
 
@@ -332,7 +391,49 @@ class MarkdownConverter:
332
391
  return self.inlines(node) if kids else Empty
333
392
 
334
393
  def inlines(self, node: object) -> TeX:
335
- return Concat(*(self.inline(c) for c in _children(node)))
394
+ return Concat(*self._inline_nodes(_children(node)))
395
+
396
+ def _inline_nodes(self, kids: list[object]) -> Iterator[TeX]:
397
+ """Convert the inline children and set each `$…$` span as a formula.
398
+
399
+ A formula spans several marko nodes as soon as its body holds an
400
+ escape, so the function first rebuilds the Markdown source of the
401
+ children. It then reads the formulas off that source and converts every
402
+ node that no formula covers the ordinary way.
403
+ """
404
+ spans: list[tuple[object, int, int]] = []
405
+ source = ""
406
+ for child in kids:
407
+ text = _source_text(child)
408
+ # A node with structure stands in the source as a newline. A
409
+ # formula holds no newline, so it never spans such a node.
410
+ plain = text if text is not None else "\n"
411
+ end = len(source) + len(plain) if text is not None else -1
412
+ spans.append((child, len(source), end))
413
+ source += plain
414
+ formulas = [(m.start(), m.end(), m.group(1)) for m in MATH_RE.finditer(source)]
415
+ if not formulas:
416
+ yield from (self.inline(c) for c in kids)
417
+ return
418
+ for child, start, end in spans:
419
+ if end < 0:
420
+ yield self.inline(child)
421
+ continue
422
+ cursor = start
423
+ for first, last, latex in formulas:
424
+ if last <= cursor or first >= end:
425
+ continue
426
+ if first > cursor:
427
+ yield _inline_text(source[cursor:first])
428
+ if first >= start:
429
+ yield _math(InlineMath(Raw(latex)))
430
+ cursor = max(cursor, last)
431
+ if cursor >= end:
432
+ continue
433
+ if cursor == start:
434
+ yield self.inline(child)
435
+ else:
436
+ yield _inline_text(source[cursor:end])
336
437
 
337
438
  # -- blocks -----------------------------------------------------------
338
439
 
@@ -480,7 +581,8 @@ class MarkdownConverter:
480
581
  if kind == "Heading":
481
582
  return self._heading(node)
482
583
  if kind == "Paragraph":
483
- return self.inlines(node)
584
+ display = _display_math(node)
585
+ return display if display is not None else self.inlines(node)
484
586
  if kind == "List":
485
587
  return self._list(node)
486
588
  if kind == "Quote":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytex-preprocessor
3
- Version: 1.1.0
3
+ Version: 1.2.0
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