markfeyn 0.1.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 (135) hide show
  1. markfeyn-0.1.0/LICENSE +21 -0
  2. markfeyn-0.1.0/MANIFEST.in +12 -0
  3. markfeyn-0.1.0/PKG-INFO +458 -0
  4. markfeyn-0.1.0/README.md +427 -0
  5. markfeyn-0.1.0/THIRD_PARTY_NOTICES.md +15 -0
  6. markfeyn-0.1.0/docs/development.md +342 -0
  7. markfeyn-0.1.0/docs/examples/edges.md +156 -0
  8. markfeyn-0.1.0/docs/examples/from-the-literature.md +177 -0
  9. markfeyn-0.1.0/docs/examples/index.md +12 -0
  10. markfeyn-0.1.0/docs/examples/layout.md +274 -0
  11. markfeyn-0.1.0/docs/examples/loops.md +310 -0
  12. markfeyn-0.1.0/docs/examples/scattering-decays.md +443 -0
  13. markfeyn-0.1.0/docs/examples/symmetric-layout.md +274 -0
  14. markfeyn-0.1.0/docs/examples/vertices.md +129 -0
  15. markfeyn-0.1.0/docs/index.md +21 -0
  16. markfeyn-0.1.0/docs/installation.md +121 -0
  17. markfeyn-0.1.0/docs/syntax.md +656 -0
  18. markfeyn-0.1.0/licenses/ELKJS-EPL-2.0.md +264 -0
  19. markfeyn-0.1.0/mkdocs.yml +62 -0
  20. markfeyn-0.1.0/package-lock.json +711 -0
  21. markfeyn-0.1.0/package.json +31 -0
  22. markfeyn-0.1.0/properdocs.yml +64 -0
  23. markfeyn-0.1.0/pyproject.toml +72 -0
  24. markfeyn-0.1.0/setup.cfg +4 -0
  25. markfeyn-0.1.0/src/markfeyn/__init__.py +5 -0
  26. markfeyn-0.1.0/src/markfeyn/__main__.py +115 -0
  27. markfeyn-0.1.0/src/markfeyn/assets/feynman-diagrams-loader.js +55 -0
  28. markfeyn-0.1.0/src/markfeyn/assets/feynman-diagrams.js +216 -0
  29. markfeyn-0.1.0/src/markfeyn/core.py +72 -0
  30. markfeyn-0.1.0/src/markfeyn/mkdocs_plugin.py +21 -0
  31. markfeyn-0.1.0/src/markfeyn/properdocs_plugin.py +21 -0
  32. markfeyn-0.1.0/src/markfeyn/renderer/feynman-diagrams.js +101 -0
  33. markfeyn-0.1.0/src/markfeyn/renderer/geometry/metrics.js +47 -0
  34. markfeyn-0.1.0/src/markfeyn/renderer/geometry/paths.js +458 -0
  35. markfeyn-0.1.0/src/markfeyn/renderer/geometry/vertex-shapes.js +35 -0
  36. markfeyn-0.1.0/src/markfeyn/renderer/layout/coordinates.js +413 -0
  37. markfeyn-0.1.0/src/markfeyn/renderer/layout/crowding.js +44 -0
  38. markfeyn-0.1.0/src/markfeyn/renderer/layout/elk-compiler.js +242 -0
  39. markfeyn-0.1.0/src/markfeyn/renderer/layout/elk-options.js +74 -0
  40. markfeyn-0.1.0/src/markfeyn/renderer/layout/elk-runner.js +27 -0
  41. markfeyn-0.1.0/src/markfeyn/renderer/layout/external-order.js +189 -0
  42. markfeyn-0.1.0/src/markfeyn/renderer/layout/fallback.js +495 -0
  43. markfeyn-0.1.0/src/markfeyn/renderer/layout/incremental.js +157 -0
  44. markfeyn-0.1.0/src/markfeyn/renderer/layout/labels/boxes.js +371 -0
  45. markfeyn-0.1.0/src/markfeyn/renderer/layout/labels/geometry.js +608 -0
  46. markfeyn-0.1.0/src/markfeyn/renderer/layout/labels/placement.js +432 -0
  47. markfeyn-0.1.0/src/markfeyn/renderer/layout/labels/regions.js +187 -0
  48. markfeyn-0.1.0/src/markfeyn/renderer/layout/labels/scoring.js +192 -0
  49. markfeyn-0.1.0/src/markfeyn/renderer/layout/labels.js +11 -0
  50. markfeyn-0.1.0/src/markfeyn/renderer/layout/layout.js +239 -0
  51. markfeyn-0.1.0/src/markfeyn/renderer/layout/loop-candidates.js +655 -0
  52. markfeyn-0.1.0/src/markfeyn/renderer/layout/model.js +219 -0
  53. markfeyn-0.1.0/src/markfeyn/renderer/layout/multiloop.js +616 -0
  54. markfeyn-0.1.0/src/markfeyn/renderer/layout/normalization.js +74 -0
  55. markfeyn-0.1.0/src/markfeyn/renderer/layout/normalizers/alignment.js +57 -0
  56. markfeyn-0.1.0/src/markfeyn/renderer/layout/normalizers/edge-bounds.js +80 -0
  57. markfeyn-0.1.0/src/markfeyn/renderer/layout/normalizers/elk.js +107 -0
  58. markfeyn-0.1.0/src/markfeyn/renderer/layout/normalizers/process.js +419 -0
  59. markfeyn-0.1.0/src/markfeyn/renderer/layout/normalizers/spring.js +358 -0
  60. markfeyn-0.1.0/src/markfeyn/renderer/layout/normalizers/tikz-orientation.js +247 -0
  61. markfeyn-0.1.0/src/markfeyn/renderer/layout/options.js +80 -0
  62. markfeyn-0.1.0/src/markfeyn/renderer/layout/orientation.js +74 -0
  63. markfeyn-0.1.0/src/markfeyn/renderer/layout/parallel.js +36 -0
  64. markfeyn-0.1.0/src/markfeyn/renderer/layout/score.js +1029 -0
  65. markfeyn-0.1.0/src/markfeyn/renderer/layout/strategies.js +157 -0
  66. markfeyn-0.1.0/src/markfeyn/renderer/layout/symmetric-unclassified.js +255 -0
  67. markfeyn-0.1.0/src/markfeyn/renderer/layout/symmetric.js +432 -0
  68. markfeyn-0.1.0/src/markfeyn/renderer/layout/topology/biconnected.js +136 -0
  69. markfeyn-0.1.0/src/markfeyn/renderer/layout/topology/classification.js +120 -0
  70. markfeyn-0.1.0/src/markfeyn/renderer/layout/topology/cycles.js +249 -0
  71. markfeyn-0.1.0/src/markfeyn/renderer/layout/topology/fermion-flow.js +148 -0
  72. markfeyn-0.1.0/src/markfeyn/renderer/layout/topology/graph.js +98 -0
  73. markfeyn-0.1.0/src/markfeyn/renderer/layout/topology/parallel.js +41 -0
  74. markfeyn-0.1.0/src/markfeyn/renderer/layout/topology/regions.js +240 -0
  75. markfeyn-0.1.0/src/markfeyn/renderer/layout/topology.js +131 -0
  76. markfeyn-0.1.0/src/markfeyn/renderer/layout/validate.js +98 -0
  77. markfeyn-0.1.0/src/markfeyn/renderer/layout-engine.js +174 -0
  78. markfeyn-0.1.0/src/markfeyn/renderer/parser/braces.js +36 -0
  79. markfeyn-0.1.0/src/markfeyn/renderer/parser/constants.js +117 -0
  80. markfeyn-0.1.0/src/markfeyn/renderer/parser/directives.js +97 -0
  81. markfeyn-0.1.0/src/markfeyn/renderer/parser/edges.js +140 -0
  82. markfeyn-0.1.0/src/markfeyn/renderer/parser/feynman-parser.js +115 -0
  83. markfeyn-0.1.0/src/markfeyn/renderer/parser/index.js +56 -0
  84. markfeyn-0.1.0/src/markfeyn/renderer/parser/labels.js +125 -0
  85. markfeyn-0.1.0/src/markfeyn/renderer/parser/options.js +527 -0
  86. markfeyn-0.1.0/src/markfeyn/renderer/parser/vertices.js +293 -0
  87. markfeyn-0.1.0/src/markfeyn/renderer/render/braces.js +202 -0
  88. markfeyn-0.1.0/src/markfeyn/renderer/render/definitions.js +57 -0
  89. markfeyn-0.1.0/src/markfeyn/renderer/render/dom.js +15 -0
  90. markfeyn-0.1.0/src/markfeyn/renderer/render/edges.js +361 -0
  91. markfeyn-0.1.0/src/markfeyn/renderer/render/label-markup.js +423 -0
  92. markfeyn-0.1.0/src/markfeyn/renderer/render/labels.js +228 -0
  93. markfeyn-0.1.0/src/markfeyn/renderer/render/mathjax.js +201 -0
  94. markfeyn-0.1.0/src/markfeyn/renderer/render/paths.js +1 -0
  95. markfeyn-0.1.0/src/markfeyn/renderer/render/styles.js +203 -0
  96. markfeyn-0.1.0/src/markfeyn/renderer/render/svg-renderer.js +307 -0
  97. markfeyn-0.1.0/src/markfeyn/renderer/render/vertex-shapes.js +1 -0
  98. markfeyn-0.1.0/src/markfeyn/renderer/render/vertices.js +123 -0
  99. markfeyn-0.1.0/src/markfeyn/renderer/render/visual-defaults.js +60 -0
  100. markfeyn-0.1.0/src/markfeyn.egg-info/PKG-INFO +458 -0
  101. markfeyn-0.1.0/src/markfeyn.egg-info/SOURCES.txt +133 -0
  102. markfeyn-0.1.0/src/markfeyn.egg-info/dependency_links.txt +1 -0
  103. markfeyn-0.1.0/src/markfeyn.egg-info/entry_points.txt +5 -0
  104. markfeyn-0.1.0/src/markfeyn.egg-info/requires.txt +10 -0
  105. markfeyn-0.1.0/src/markfeyn.egg-info/top_level.txt +3 -0
  106. markfeyn-0.1.0/src/mkdocs_feynman_diagrams/__init__.py +5 -0
  107. markfeyn-0.1.0/src/mkdocs_feynman_diagrams/plugin.py +5 -0
  108. markfeyn-0.1.0/src/properdocs_feynman_diagrams/__init__.py +5 -0
  109. markfeyn-0.1.0/src/properdocs_feynman_diagrams/plugin.py +5 -0
  110. markfeyn-0.1.0/tests/canvas-scaling.test.js +94 -0
  111. markfeyn-0.1.0/tests/contact-star.test.js +243 -0
  112. markfeyn-0.1.0/tests/docs-examples.test.js +83 -0
  113. markfeyn-0.1.0/tests/feynman-visual-snapshots.test.js +170 -0
  114. markfeyn-0.1.0/tests/helpers/feynman.js +397 -0
  115. markfeyn-0.1.0/tests/labels.test.js +277 -0
  116. markfeyn-0.1.0/tests/layout-misc.test.js +170 -0
  117. markfeyn-0.1.0/tests/loader.test.js +92 -0
  118. markfeyn-0.1.0/tests/loops.test.js +375 -0
  119. markfeyn-0.1.0/tests/multiloop.test.js +275 -0
  120. markfeyn-0.1.0/tests/orientation-crossing.test.js +55 -0
  121. markfeyn-0.1.0/tests/orientation.test.js +510 -0
  122. markfeyn-0.1.0/tests/parser-core.test.js +93 -0
  123. markfeyn-0.1.0/tests/parser-identifiers.test.js +139 -0
  124. markfeyn-0.1.0/tests/render-diagnostics.test.js +373 -0
  125. markfeyn-0.1.0/tests/render-dom.test.js +473 -0
  126. markfeyn-0.1.0/tests/symmetric-refinement.test.js +505 -0
  127. markfeyn-0.1.0/tests/test_cli.py +114 -0
  128. markfeyn-0.1.0/tests/test_docs_config.py +20 -0
  129. markfeyn-0.1.0/tests/test_docs_language.py +34 -0
  130. markfeyn-0.1.0/tests/test_lazy_load.py +85 -0
  131. markfeyn-0.1.0/tests/test_mkdocs_integration.py +93 -0
  132. markfeyn-0.1.0/tests/test_plugin.py +142 -0
  133. markfeyn-0.1.0/tests/test_properdocs_integration.py +91 -0
  134. markfeyn-0.1.0/tests/test_version.py +15 -0
  135. markfeyn-0.1.0/tests/topology-performance.test.js +89 -0
markfeyn-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MarkFeyn contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,12 @@
1
+ include README.md
2
+ include LICENSE
3
+ include THIRD_PARTY_NOTICES.md
4
+ include package.json
5
+ include package-lock.json
6
+ include mkdocs.yml
7
+ include properdocs.yml
8
+ recursive-include licenses *.md
9
+ recursive-include src/markfeyn/renderer *.js
10
+ recursive-include src/markfeyn/assets *.js
11
+ recursive-include docs *.md
12
+ recursive-include tests *.py *.js
@@ -0,0 +1,458 @@
1
+ Metadata-Version: 2.4
2
+ Name: markfeyn
3
+ Version: 0.1.0
4
+ Summary: Render simple Feynman diagram code blocks as native SVG in MkDocs and ProperDocs.
5
+ Author: MarkFeyn contributors
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://lalmei.github.io/markfeyn/
8
+ Project-URL: Repository, https://github.com/lalmei/markfeyn
9
+ Keywords: mkdocs,properdocs,markdown,feynman,physics,svg
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Programming Language :: JavaScript
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Topic :: Documentation
17
+ Classifier: Topic :: Scientific/Engineering :: Physics
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ License-File: THIRD_PARTY_NOTICES.md
22
+ License-File: licenses/ELKJS-EPL-2.0.md
23
+ Provides-Extra: mkdocs
24
+ Requires-Dist: mkdocs>=1.5; extra == "mkdocs"
25
+ Provides-Extra: properdocs
26
+ Requires-Dist: properdocs>=1.6.7; extra == "properdocs"
27
+ Provides-Extra: all
28
+ Requires-Dist: mkdocs>=1.5; extra == "all"
29
+ Requires-Dist: properdocs>=1.6.7; extra == "all"
30
+ Dynamic: license-file
31
+
32
+ # MarkFeyn
33
+
34
+ [![CI](https://github.com/lalmei/markfeyn/actions/workflows/ci.yml/badge.svg)](https://github.com/lalmei/markfeyn/actions/workflows/ci.yml)
35
+ [![Docs](https://github.com/lalmei/markfeyn/actions/workflows/docs.yml/badge.svg)](https://lalmei.github.io/markfeyn/)
36
+
37
+ 📖 **Documentation:** https://lalmei.github.io/markfeyn/
38
+
39
+ MarkFeyn renders fenced `feynman` code blocks as native SVG diagrams in MkDocs and ProperDocs.
40
+
41
+ ## Examples
42
+
43
+ A short text-based syntax produces publication-style diagrams, from simple
44
+ tree-level scattering to loops and QCD vertices. More examples live in
45
+ [`docs/examples/`](docs/examples/).
46
+
47
+ <table>
48
+ <tr>
49
+ <th>Source</th>
50
+ <th>Rendered</th>
51
+ </tr>
52
+ <tr>
53
+ <td>
54
+
55
+ ```feynman
56
+ incoming e_minus e_plus
57
+ outgoing mu_plus mu_minus
58
+ fermion e_minus->ann
59
+ anti fermion e_plus->ann
60
+ photon ann->prod[momentum'=k]
61
+ anti fermion prod->mu_plus
62
+ fermion prod->mu_minus
63
+ label e_minus:e^- e_plus:e^+ mu_plus:\mu^+ mu_minus:\mu^- ann->prod:\gamma
64
+ ```
65
+
66
+ </td>
67
+ <td><img src="docs/assets/readme/ee-to-mumu.svg" alt="e+ e- to mu+ mu- s-channel annihilation diagram" width="360"></td>
68
+ </tr>
69
+ <tr>
70
+ <td>
71
+
72
+ ```feynman
73
+ incoming e_minus e_plus
74
+ outgoing e_plus_out e_minus_out
75
+ fermion e_minus->upper upper->e_minus_out
76
+ photon upper->lower[edge label=\gamma]
77
+ anti fermion e_plus->lower lower->e_plus_out
78
+ label e_minus:e^- e_plus:e^+ e_minus_out:e^- e_plus_out:e^+
79
+ ```
80
+
81
+ </td>
82
+ <td><img src="docs/assets/readme/bhabha-scattering.svg" alt="Bhabha scattering t-channel photon exchange diagram" width="300"></td>
83
+ </tr>
84
+ <tr>
85
+ <td>
86
+
87
+ ```feynman
88
+ incoming q
89
+ outgoing q2 g
90
+ fermion q->v1 v1->q2
91
+ gluon v1->g
92
+ label q:q q2:q g:g
93
+ ```
94
+
95
+ </td>
96
+ <td><img src="docs/assets/readme/gluon-emission.svg" alt="Quark radiating a gluon" width="280"></td>
97
+ </tr>
98
+ <tr>
99
+ <td>
100
+
101
+ ```feynman
102
+ incoming mu
103
+ outgoing numu e nue
104
+ fermion mu->v v->numu
105
+ boson v->w
106
+ fermion w->e
107
+ anti fermion w->nue
108
+ label mu:\mu^- numu:\nu_\mu e:e^- nue:\overline{\nu}_e v->w:W^-
109
+ ```
110
+
111
+ </td>
112
+ <td><img src="docs/assets/readme/muon-decay.svg" alt="Muon decay via a virtual W boson" width="320"></td>
113
+ </tr>
114
+ <tr>
115
+ <td>
116
+
117
+ ```feynman
118
+ incoming i1 i2
119
+ outgoing f1 f2
120
+ fermion i1->a b->f1 a->c[edge label'=q]
121
+ anti fermion i2->c d->f2 b->d[edge label=\nu_\mu]
122
+ photon a->b[edge label=W^-] c->d[edge label'=W^+]
123
+ label i1:d i2:\overline{s} f1:\mu^- f2:\mu^+
124
+ ```
125
+
126
+ </td>
127
+ <td><img src="docs/assets/readme/w-box-diagram.svg" alt="One-loop W boson box diagram" width="400"></td>
128
+ </tr>
129
+ </table>
130
+
131
+ The exact sources used to render these images (including the layout options
132
+ that were trimmed above for brevity) are under
133
+ [`docs/assets/readme/`](docs/assets/readme/), and can be re-rendered with
134
+ `npm run render:readme`.
135
+
136
+ ## Installation
137
+
138
+ From a checkout of this repository, install MarkFeyn into your documentation
139
+ project by passing the checkout path to uv. Choose the extra for your engine.
140
+
141
+ ```bash
142
+ uv add "/path/to/markfeyn[mkdocs]"
143
+ uv add "/path/to/markfeyn[properdocs]"
144
+ ```
145
+
146
+ Omit the extra if the engine is already installed.
147
+
148
+ Install Material for MkDocs when using the Material theme:
149
+
150
+ ```bash
151
+ uv add mkdocs-material
152
+ ```
153
+
154
+ From a source checkout:
155
+
156
+ ```bash
157
+ uv sync
158
+ ```
159
+
160
+ For local development:
161
+
162
+ ```bash
163
+ uv sync --group dev
164
+ ```
165
+
166
+ ## MkDocs Setup
167
+
168
+ Enable the plugin in `mkdocs.yml`:
169
+
170
+ ```yaml
171
+ theme:
172
+ name: material
173
+
174
+ plugins:
175
+ - search
176
+ - feynman-diagrams
177
+
178
+ markdown_extensions:
179
+ - pymdownx.highlight:
180
+ anchor_linenums: true
181
+ pygments_lang_class: true
182
+ - pymdownx.superfences:
183
+ custom_fences:
184
+ - name: feynman
185
+ class: language-feynman
186
+ format: !!python/name:pymdownx.superfences.fence_code_format
187
+ ```
188
+
189
+ ## ProperDocs Setup
190
+
191
+ Enable the same plugin in `properdocs.yml`:
192
+
193
+ ```yaml
194
+ theme:
195
+ name: material
196
+
197
+ plugins:
198
+ - feynman-diagrams
199
+
200
+ markdown_extensions:
201
+ - pymdownx.highlight:
202
+ anchor_linenums: true
203
+ pygments_lang_class: true
204
+ - pymdownx.superfences:
205
+ custom_fences:
206
+ - name: feynman
207
+ class: language-feynman
208
+ format: !!python/name:pymdownx.superfences.fence_code_format
209
+ ```
210
+
211
+ The plugin injects and copies the bundled browser renderer automatically. The Markdown parser stays lightweight because diagram parsing and SVG generation happen in the browser.
212
+
213
+ For ProperDocs with `theme: material`, do not add `search` explicitly; Material's `search` plugin is MkDocs-only.
214
+
215
+ To customize the emitted script path:
216
+
217
+ ```yaml
218
+ plugins:
219
+ - feynman-diagrams:
220
+ script_path: assets/javascripts/feynman-diagrams.js
221
+ ```
222
+
223
+ `script_path` is interpreted relative to the generated site directory. Parent-directory traversal such as `../feynman.js` is rejected.
224
+
225
+ By default the plugin loads the renderer lazily: it injects a tiny loader script (`feynman-diagrams-loader.js`, written next to `script_path`) and the full renderer is only downloaded on pages that contain a `feynman` block. Set `lazy_load: false` to load the renderer on every page instead:
226
+
227
+ ```yaml
228
+ plugins:
229
+ - feynman-diagrams:
230
+ lazy_load: false
231
+ ```
232
+
233
+ ## Syntax
234
+
235
+ ````markdown
236
+ ```feynman
237
+ incoming e_minus e_plus
238
+ outgoing mu_plus mu_minus
239
+ fermion e_minus->ann
240
+ anti fermion e_plus->ann
241
+ photon ann->prod[momentum'=k]
242
+ anti fermion prod->mu_plus
243
+ fermion prod->mu_minus
244
+ label e_minus:e^- e_plus:e^+ mu_plus:\mu^+ mu_minus:\mu^- ann->prod:\gamma
245
+ ```
246
+ ````
247
+
248
+ Visible degree-1 endpoints without `incoming` or `outgoing` declarations are
249
+ kept as unclassified external states and laid out symmetrically when no process
250
+ direction is declared. Balanced two-center trees and symmetric two-point
251
+ self-energy loops without explicit roles receive focused reflection-symmetric
252
+ refinement; this is not a full automorphism engine. Add `incoming` and
253
+ `outgoing` for left-to-right process diagrams and explicit terminal ordering.
254
+
255
+ Supported particles:
256
+
257
+ - `plain`, `line`, or `propagator`: solid line without an arrow
258
+ - `fermion`: solid line with centered arrow
259
+ - `anti fermion` or `anti-fermion`: solid line with reversed arrow
260
+ - `photon`: sinusoidal wave
261
+ - `boson`: alias for `photon`
262
+ - `gluon`: looped/cycloid path
263
+ - `scalar`: dashed line
264
+ - `ghost`: dotted line
265
+ - `invisible` or `hidden`: layout-only edge
266
+
267
+ Labels use `node:text`. Edge labels are also accepted with `from->to:text`.
268
+ Labels support a small TeX-like subset for common symbols and scripts:
269
+
270
+ ```feynman
271
+ label electron:e^- muon:\mu^- vertex:\gamma momentum:p_{T}
272
+ ```
273
+
274
+ Per-edge options support TikZ-Feynman-style curves and inline labels:
275
+
276
+ ```feynman
277
+ fermion b->c[half left, momentum=k] c->b[half left, momentum'=k-p]
278
+ boson d->e[bend left, edge label=W^+]
279
+ fermion f->g[out=180, in=45]
280
+ ```
281
+
282
+ Use `brace from->to[side]:label` for grouping braces on manually positioned
283
+ diagrams.
284
+
285
+ Diagram-level options can select layout algorithms, orientation, and sizing:
286
+
287
+ ````markdown
288
+ ```feynman
289
+ layout spring
290
+ orientation vertical
291
+ size small
292
+ options width=560 height=420
293
+ incoming mu
294
+ outgoing numu nue e
295
+ fermion mu->w w->numu
296
+ boson w->v
297
+ anti fermion v->nue
298
+ fermion v->e
299
+ invisible numu->e
300
+ label mu:\mu^- numu:\nu_\mu nue:\overline{\nu}_e e:e^- w->v:W^-
301
+ ```
302
+ ````
303
+
304
+ Supported layouts are `spring` (default), `spring-electrical`, `layered`, and
305
+ `tree`, all backed by bundled ELK.js graph layout. Pin nodes manually with
306
+ `position node x y` when an automatic layout is not enough.
307
+
308
+ Common loop topologies also get semantic candidate layouts: triangle loops, box
309
+ loops, tadpoles, generic polygon loops, centered vacuum one-loop diagrams, and
310
+ bounded two-loop nested or overlapping regions are detected and placed
311
+ deterministically before falling back to the general graph layout. Candidate
312
+ scoring includes approximate label, momentum, multiloop recognizability, and
313
+ previous-layout stability checks. Final layouts also run deterministic,
314
+ bounding-box-based label placement so node labels, edge labels, and momentum
315
+ labels can move away from nearby vertices, propagators, loop interiors, and
316
+ other labels while preserving explicit primed/right-side choices. It still does
317
+ not attempt arbitrary higher-order multiloop optimization or exact TeX/TikZ
318
+ font metrics.
319
+
320
+ TikZ-Feynman-style post-layout alignment is available with commands such as
321
+ `horizontal a to b`, `horizontal' a to b`, `vertical a to b`, and
322
+ `vertical' a to b`.
323
+
324
+ Vertex shapes can be selected with `vertex node:shape` pairs:
325
+
326
+ ````markdown
327
+ ```feynman
328
+ incoming a
329
+ outgoing b c
330
+ fermion a->v v->b
331
+ photon v->blob blob->c
332
+ vertex v:dot blob:blob
333
+ label a:e^- b:e^- c:\gamma
334
+ ```
335
+ ````
336
+
337
+ Internal vertices are unmarked by default; use `vertex v:dot` for a small
338
+ filled interaction point.
339
+
340
+ Supported shapes are `dot`, `square-dot`, `empty-dot`, `crossed-dot`, `cross`,
341
+ `blob`, and `disk`.
342
+
343
+ Blob and disk vertices can use optional bracketed styling:
344
+
345
+ ````markdown
346
+ ```feynman
347
+ incoming a
348
+ outgoing b
349
+ plain a->disk disk->b
350
+ vertex disk:disk[hatch=cross,width=96,height=44]
351
+ ```
352
+ ````
353
+
354
+ Supported hatch fills are `diagonal`, `diagonal-reverse`, `cross`,
355
+ `horizontal`, `vertical`, and `grid`; TikZ-style aliases such as
356
+ `north east lines` and `north west lines` are accepted. `size` and `radius`
357
+ set the circular radius in SVG pixels, and `diameter` sets the full circle
358
+ width. Use `width` and `height` for full elliptical extents, or `rx` and `ry`
359
+ for horizontal and vertical radii. Unequal horizontal and vertical radii render
360
+ as an SVG ellipse.
361
+
362
+ ## Manual JavaScript Setup
363
+
364
+ If you do not want to use a plugin, copy the bundled renderer. Manual mode loads the full renderer directly on every page:
365
+
366
+ ```bash
367
+ python -m markfeyn copy-asset docs/javascripts/feynman-diagrams.js
368
+ ```
369
+
370
+ (Source checkouts can also copy `src/markfeyn/assets/feynman-diagrams.js` directly.)
371
+
372
+ Then configure MkDocs or ProperDocs directly:
373
+
374
+ ```yaml
375
+ extra_javascript:
376
+ - javascripts/feynman-diagrams.js
377
+
378
+ markdown_extensions:
379
+ - pymdownx.highlight:
380
+ anchor_linenums: true
381
+ pygments_lang_class: true
382
+ - pymdownx.superfences:
383
+ custom_fences:
384
+ - name: feynman
385
+ class: language-feynman
386
+ format: !!python/name:pymdownx.superfences.fence_code_format
387
+ ```
388
+
389
+ ## Package Layout
390
+
391
+ ```text
392
+ src/markfeyn/
393
+ __init__.py
394
+ core.py
395
+ mkdocs_plugin.py
396
+ properdocs_plugin.py
397
+ renderer/
398
+ feynman-diagrams.js
399
+ assets/
400
+ feynman-diagrams.js
401
+ ```
402
+
403
+ The package exposes both plugin entry points:
404
+
405
+ ```toml
406
+ [project.entry-points."mkdocs.plugins"]
407
+ feynman-diagrams = "markfeyn.mkdocs_plugin:FeynmanDiagramsPlugin"
408
+
409
+ [project.entry-points."properdocs.plugins"]
410
+ feynman-diagrams = "markfeyn.properdocs_plugin:FeynmanDiagramsPlugin"
411
+ ```
412
+
413
+ ## Verification
414
+
415
+ ```bash
416
+ make test
417
+ make docs
418
+ make build
419
+ ```
420
+
421
+ Serve docs locally:
422
+
423
+ ```bash
424
+ make serve-properdocs
425
+ make serve-mkdocs
426
+ ```
427
+
428
+ The serve targets use `localhost:8001` by default. Override with `DOCS_ADDR=localhost:8010` if that port is busy.
429
+
430
+ ## Publishing
431
+
432
+ Build and publish to PyPI:
433
+
434
+ ```bash
435
+ make publish
436
+ ```
437
+
438
+ The publish targets upload only `dist/markfeyn-*` artifacts.
439
+
440
+ Publish to TestPyPI first:
441
+
442
+ ```bash
443
+ make publish-test
444
+ ```
445
+
446
+ `uv publish` reads credentials from the environment. For PyPI token auth, set:
447
+
448
+ ```bash
449
+ export UV_PUBLISH_TOKEN="pypi-..."
450
+ ```
451
+
452
+ ## Design Choices
453
+
454
+ - Rendering happens in the browser, so MkDocs and ProperDocs builds remain static and fast.
455
+ - MkDocs and ProperDocs use separate adapter classes over the same core implementation.
456
+ - The script supports themes that expose Material-style instant loading through `document$.subscribe`.
457
+ - It falls back to `DOMContentLoaded` for standard static documentation themes.
458
+ - The browser renderer has no JavaScript runtime dependencies.