texsmith 0.0.2.dev0__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.
- texsmith/__init__.py +107 -0
- texsmith/_alias.py +59 -0
- texsmith/adapters/__init__.py +6 -0
- texsmith/adapters/docker.py +258 -0
- texsmith/adapters/handlers/__init__.py +3 -0
- texsmith/adapters/handlers/_assets.py +363 -0
- texsmith/adapters/handlers/_helpers.py +62 -0
- texsmith/adapters/handlers/_mermaid.py +122 -0
- texsmith/adapters/handlers/admonitions.py +267 -0
- texsmith/adapters/handlers/basic.py +228 -0
- texsmith/adapters/handlers/blocks.py +851 -0
- texsmith/adapters/handlers/code.py +309 -0
- texsmith/adapters/handlers/inline.py +937 -0
- texsmith/adapters/handlers/links.py +239 -0
- texsmith/adapters/handlers/media.py +380 -0
- texsmith/adapters/latex/__init__.py +10 -0
- texsmith/adapters/latex/engines/__init__.py +726 -0
- texsmith/adapters/latex/engines/latex/__init__.py +26 -0
- texsmith/adapters/latex/engines/latex/log.py +720 -0
- texsmith/adapters/latex/engines/latex/runner.py +28 -0
- texsmith/adapters/latex/engines/tectonic/__init__.py +38 -0
- texsmith/adapters/latex/formatter.py +297 -0
- texsmith/adapters/latex/latexmk.py +148 -0
- texsmith/adapters/latex/partials/acronym.tex +1 -0
- texsmith/adapters/latex/partials/add.tex +1 -0
- texsmith/adapters/latex/partials/addition.tex +1 -0
- texsmith/adapters/latex/partials/blockquote.tex +3 -0
- texsmith/adapters/latex/partials/callout.tex +3 -0
- texsmith/adapters/latex/partials/choices.tex +5 -0
- texsmith/adapters/latex/partials/citation.tex +1 -0
- texsmith/adapters/latex/partials/codeblock.tex +7 -0
- texsmith/adapters/latex/partials/codeblock_listings.tex +5 -0
- texsmith/adapters/latex/partials/codeblock_pygments.tex +5 -0
- texsmith/adapters/latex/partials/codeblock_verbatim.tex +12 -0
- texsmith/adapters/latex/partials/codeinline.tex +1 -0
- texsmith/adapters/latex/partials/codeinlinett.tex +1 -0
- texsmith/adapters/latex/partials/comment.tex +1 -0
- texsmith/adapters/latex/partials/del.tex +1 -0
- texsmith/adapters/latex/partials/deletion.tex +1 -0
- texsmith/adapters/latex/partials/description_list.tex +5 -0
- texsmith/adapters/latex/partials/enquote.tex +1 -0
- texsmith/adapters/latex/partials/epigraph.tex +1 -0
- texsmith/adapters/latex/partials/exercises_solutions.tex +7 -0
- texsmith/adapters/latex/partials/figure.tex +33 -0
- texsmith/adapters/latex/partials/figure_tcolorbox.tex +15 -0
- texsmith/adapters/latex/partials/footnote.tex +3 -0
- texsmith/adapters/latex/partials/glossary.tex +1 -0
- texsmith/adapters/latex/partials/heading.tex +14 -0
- texsmith/adapters/latex/partials/highlight.tex +25 -0
- texsmith/adapters/latex/partials/horizontal_rule.tex +1 -0
- texsmith/adapters/latex/partials/href.tex +1 -0
- texsmith/adapters/latex/partials/icon.tex +1 -0
- texsmith/adapters/latex/partials/include.tex +1 -0
- texsmith/adapters/latex/partials/index.tex +1 -0
- texsmith/adapters/latex/partials/italic.tex +1 -0
- texsmith/adapters/latex/partials/keystroke.tex +46 -0
- texsmith/adapters/latex/partials/label.tex +1 -0
- texsmith/adapters/latex/partials/list_acronyms.tex +5 -0
- texsmith/adapters/latex/partials/list_glossary.tex +8 -0
- texsmith/adapters/latex/partials/multicolumn.tex +3 -0
- texsmith/adapters/latex/partials/ordered_list.tex +5 -0
- texsmith/adapters/latex/partials/pagestyle.tex +1 -0
- texsmith/adapters/latex/partials/ref.tex +1 -0
- texsmith/adapters/latex/partials/regex.tex +1 -0
- texsmith/adapters/latex/partials/smallcaps.tex +1 -0
- texsmith/adapters/latex/partials/strikethrough.tex +1 -0
- texsmith/adapters/latex/partials/strong.tex +1 -0
- texsmith/adapters/latex/partials/subscript.tex +1 -0
- texsmith/adapters/latex/partials/substitution.tex +1 -0
- texsmith/adapters/latex/partials/superscript.tex +1 -0
- texsmith/adapters/latex/partials/tabbed.tex +3 -0
- texsmith/adapters/latex/partials/table.tex +48 -0
- texsmith/adapters/latex/partials/underline.tex +1 -0
- texsmith/adapters/latex/partials/unordered_list.tex +5 -0
- texsmith/adapters/latex/partials/url.tex +1 -0
- texsmith/adapters/latex/pygments.py +98 -0
- texsmith/adapters/latex/pyxindy.py +64 -0
- texsmith/adapters/latex/renderer.py +220 -0
- texsmith/adapters/latex/tectonic.py +366 -0
- texsmith/adapters/latex/utils.py +86 -0
- texsmith/adapters/markdown/__init__.py +342 -0
- texsmith/adapters/plugins/__init__.py +8 -0
- texsmith/adapters/plugins/material.py +174 -0
- texsmith/adapters/plugins/snippet.py +1734 -0
- texsmith/adapters/transformers/__init__.py +122 -0
- texsmith/adapters/transformers/base.py +140 -0
- texsmith/adapters/transformers/strategies.py +1456 -0
- texsmith/adapters/transformers/utils.py +59 -0
- texsmith/api/__init__.py +80 -0
- texsmith/api/_utils.py +56 -0
- texsmith/api/document.py +645 -0
- texsmith/api/pipeline.py +229 -0
- texsmith/api/service.py +648 -0
- texsmith/api/templates.py +287 -0
- texsmith/core/__init__.py +6 -0
- texsmith/core/bibliography/__init__.py +57 -0
- texsmith/core/bibliography/collection.py +354 -0
- texsmith/core/bibliography/doi.py +194 -0
- texsmith/core/bibliography/issues.py +15 -0
- texsmith/core/bibliography/parsing.py +45 -0
- texsmith/core/callouts.py +99 -0
- texsmith/core/config.py +191 -0
- texsmith/core/context.py +242 -0
- texsmith/core/conversion/__init__.py +103 -0
- texsmith/core/conversion/core.py +780 -0
- texsmith/core/conversion/debug.py +87 -0
- texsmith/core/conversion/inputs.py +474 -0
- texsmith/core/conversion/renderer.py +578 -0
- texsmith/core/conversion/templates.py +646 -0
- texsmith/core/conversion_contexts.py +95 -0
- texsmith/core/diagnostics.py +118 -0
- texsmith/core/exceptions.py +41 -0
- texsmith/core/fonts/__init__.py +3 -0
- texsmith/core/fragments/__init__.py +716 -0
- texsmith/core/fragments/base.py +117 -0
- texsmith/core/metadata.py +280 -0
- texsmith/core/mustache.py +86 -0
- texsmith/core/partials.py +20 -0
- texsmith/core/rules.py +394 -0
- texsmith/core/templates/__init__.py +58 -0
- texsmith/core/templates/base.py +343 -0
- texsmith/core/templates/builtins.py +68 -0
- texsmith/core/templates/context_usage.py +137 -0
- texsmith/core/templates/loader.py +303 -0
- texsmith/core/templates/manifest.py +861 -0
- texsmith/core/templates/runtime.py +347 -0
- texsmith/core/templates/text.py +14 -0
- texsmith/core/templates/wrapper.py +340 -0
- texsmith/core/user_dir.py +179 -0
- texsmith/devtools.py +28 -0
- texsmith/extensions/__init__.py +162 -0
- texsmith/extensions/index/__init__.py +21 -0
- texsmith/extensions/index/markdown.py +94 -0
- texsmith/extensions/index/mkdocs_plugin.py +136 -0
- texsmith/extensions/index/registry.py +57 -0
- texsmith/extensions/index/renderer.py +183 -0
- texsmith/extensions/index/templates/index.tex +1 -0
- texsmith/extensions/latex_raw.py +115 -0
- texsmith/extensions/latex_text.py +117 -0
- texsmith/extensions/mermaid.py +267 -0
- texsmith/extensions/missing_footnotes.py +125 -0
- texsmith/extensions/multi_citations.py +54 -0
- texsmith/extensions/progressbar/__init__.py +9 -0
- texsmith/extensions/progressbar/markdown.py +212 -0
- texsmith/extensions/progressbar/renderer.py +117 -0
- texsmith/extensions/smallcaps.py +48 -0
- texsmith/extensions/texlogos/__init__.py +10 -0
- texsmith/extensions/texlogos/markdown.py +236 -0
- texsmith/extensions/texlogos/renderer.py +81 -0
- texsmith/extensions/texlogos/specs.py +66 -0
- texsmith/fonts/__init__.py +57 -0
- texsmith/fonts/cache.py +46 -0
- texsmith/fonts/constants.py +60 -0
- texsmith/fonts/coverage.py +275 -0
- texsmith/fonts/downloader.py +103 -0
- texsmith/fonts/fallback.py +438 -0
- texsmith/fonts/html_scripts.py +168 -0
- texsmith/fonts/logging.py +127 -0
- texsmith/fonts/pipeline.py +338 -0
- texsmith/fonts/scripts.py +493 -0
- texsmith/fonts/ucharclasses.py +164 -0
- texsmith/fragments/__init__.py +11 -0
- texsmith/fragments/bibliography/__init__.py +65 -0
- texsmith/fragments/bibliography/fragment.toml +3 -0
- texsmith/fragments/bibliography/ts-bibliography-backmatter.jinja.tex +35 -0
- texsmith/fragments/bibliography/ts-bibliography.jinja.tex +10 -0
- texsmith/fragments/callouts/__init__.py +88 -0
- texsmith/fragments/callouts/fragment.toml +3 -0
- texsmith/fragments/callouts/ts-callouts.jinja.sty +129 -0
- texsmith/fragments/code/__init__.py +82 -0
- texsmith/fragments/code/fragment.toml +3 -0
- texsmith/fragments/code/ts-code.jinja.sty +140 -0
- texsmith/fragments/extra/__init__.py +180 -0
- texsmith/fragments/extra/fragment.toml +3 -0
- texsmith/fragments/extra/ts-extra.jinja.tex +13 -0
- texsmith/fragments/fonts/__init__.py +880 -0
- texsmith/fragments/fonts/fragment.toml +3 -0
- texsmith/fragments/fonts/ts-fonts.jinja.sty +292 -0
- texsmith/fragments/frame/__init__.py +170 -0
- texsmith/fragments/frame/fragment.toml +3 -0
- texsmith/fragments/frame/ts-frame.tex.jinja +49 -0
- texsmith/fragments/geometry/__init__.py +169 -0
- texsmith/fragments/geometry/fragment.toml +3 -0
- texsmith/fragments/geometry/paper.py +526 -0
- texsmith/fragments/geometry/ts_geometry.tex.jinja +53 -0
- texsmith/fragments/glossary/__init__.py +67 -0
- texsmith/fragments/glossary/fragment.toml +3 -0
- texsmith/fragments/glossary/ts-glossary-backmatter.jinja.tex +5 -0
- texsmith/fragments/glossary/ts-glossary.jinja.sty +28 -0
- texsmith/fragments/index/__init__.py +66 -0
- texsmith/fragments/index/fragment.toml +3 -0
- texsmith/fragments/index/ts-index-backmatter.jinja.tex +3 -0
- texsmith/fragments/index/ts-index.jinja.sty +12 -0
- texsmith/fragments/keystrokes/__init__.py +69 -0
- texsmith/fragments/keystrokes/fragment.toml +3 -0
- texsmith/fragments/keystrokes/ts-keystrokes.jinja.sty +20 -0
- texsmith/fragments/todolist/__init__.py +71 -0
- texsmith/fragments/todolist/fragment.toml +3 -0
- texsmith/fragments/todolist/ts-todolist.jinja.sty +21 -0
- texsmith/fragments/typesetting/__init__.py +214 -0
- texsmith/fragments/typesetting/fragment.toml +3 -0
- texsmith/fragments/typesetting/ts-typesetting.tex.jinja +81 -0
- texsmith/index.py +26 -0
- texsmith/plugins/__init__.py +14 -0
- texsmith/progressbar.py +8 -0
- texsmith/quotes.py +40 -0
- texsmith/smart_dashes.py +66 -0
- texsmith/templates/__init__.py +3 -0
- texsmith/templates/article/README.md +33 -0
- texsmith/templates/article/__init__.py +281 -0
- texsmith/templates/article/template/manifest.toml +125 -0
- texsmith/templates/article/template/mermaid-config.json +18 -0
- texsmith/templates/article/template/template.tex +74 -0
- texsmith/templates/book/README.md +26 -0
- texsmith/templates/book/__init__.py +75 -0
- texsmith/templates/book/overrides/codeblock.tex +7 -0
- texsmith/templates/book/overrides/codeinline.tex +1 -0
- texsmith/templates/book/template/fixtoc.sty +81 -0
- texsmith/templates/book/template/manifest.toml +198 -0
- texsmith/templates/book/template/template.tex +314 -0
- texsmith/templates/common/__init__.py +1 -0
- texsmith/templates/common/latexmkrc +46 -0
- texsmith/templates/letter/README.md +59 -0
- texsmith/templates/letter/__init__.py +495 -0
- texsmith/templates/letter/demo.md +31 -0
- texsmith/templates/letter/fonts/modernline bold.otf +0 -0
- texsmith/templates/letter/fonts/modernline.otf +0 -0
- texsmith/templates/letter/manifest.toml +197 -0
- texsmith/templates/letter/template/callouts.jinja.sty +290 -0
- texsmith/templates/letter/template/template.tex +123 -0
- texsmith/templates/snippet/README.md +21 -0
- texsmith/templates/snippet/__init__.py +80 -0
- texsmith/templates/snippet/template/manifest.toml +66 -0
- texsmith/templates/snippet/template/template.tex +47 -0
- texsmith/texlogos.py +15 -0
- texsmith/ui/__init__.py +6 -0
- texsmith/ui/cli/__init__.py +22 -0
- texsmith/ui/cli/_options.py +338 -0
- texsmith/ui/cli/app.py +65 -0
- texsmith/ui/cli/bibliography.py +300 -0
- texsmith/ui/cli/commands/__init__.py +14 -0
- texsmith/ui/cli/commands/render.py +1128 -0
- texsmith/ui/cli/commands/templates.py +397 -0
- texsmith/ui/cli/diagnostics.py +36 -0
- texsmith/ui/cli/presenter.py +663 -0
- texsmith/ui/cli/state.py +263 -0
- texsmith/ui/cli/utils.py +235 -0
- texsmith-0.0.2.dev0.dist-info/METADATA +187 -0
- texsmith-0.0.2.dev0.dist-info/RECORD +252 -0
- texsmith-0.0.2.dev0.dist-info/WHEEL +4 -0
- texsmith-0.0.2.dev0.dist-info/entry_points.txt +22 -0
- texsmith-0.0.2.dev0.dist-info/licenses/LICENSE.md +21 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
\BLOCK{ set font_family = fonts_family | default("lm") }
|
|
2
|
+
\BLOCK{ set fallback = fonts.get("fallback") if fonts is defined and fonts else {} }
|
|
3
|
+
\ProvidesPackage{ts-fonts}[2025/01/10 TeXSmith font selection (generated)]
|
|
4
|
+
|
|
5
|
+
\RequirePackage{iftex}
|
|
6
|
+
% Fallback definition replaced when emoji fonts are available.
|
|
7
|
+
\providecommand{\texsmithEmoji}[1]{\textbf{!}}
|
|
8
|
+
\catcode"2135=\active
|
|
9
|
+
\protected\def^^^^2135{\ensuremath{\aleph}}
|
|
10
|
+
|
|
11
|
+
\ifPDFTeX
|
|
12
|
+
\PackageError{ts-fonts}{ts-fonts requires XeLaTeX or LuaLaTeX}{Switch to xelatex or lualatex.}
|
|
13
|
+
\else
|
|
14
|
+
\RequirePackage{fontspec}
|
|
15
|
+
\defaultfontfeatures{Ligatures = TeX, Scale = MatchLowercase}
|
|
16
|
+
|
|
17
|
+
\makeatletter
|
|
18
|
+
\newcommand{\tsDeclareFontSet}[2]{%
|
|
19
|
+
\expandafter\gdef\csname tssetupfonts@#1\endcsname{#2}%
|
|
20
|
+
}
|
|
21
|
+
\newcommand{\tsUseFontSet}[1]{%
|
|
22
|
+
\@ifundefined{tssetupfonts@#1}{%
|
|
23
|
+
\PackageError{ts-fonts}{Unknown font family '#1'}%
|
|
24
|
+
{Use one of: lm, lm-sans, bonum, libertinus, pagella, termes, schola, heros, adventor, cursor, plex, pennstander.}%
|
|
25
|
+
}{%
|
|
26
|
+
\csname tssetupfonts@#1\endcsname
|
|
27
|
+
}%
|
|
28
|
+
}
|
|
29
|
+
\makeatother
|
|
30
|
+
|
|
31
|
+
% Latin Modern (lm)
|
|
32
|
+
\tsDeclareFontSet{lm}{%
|
|
33
|
+
\ifLuaTeX
|
|
34
|
+
\setmainfont{Latin Modern Roman}%
|
|
35
|
+
\setsansfont{Latin Modern Sans}%
|
|
36
|
+
\setmonofont{Latin Modern Mono}%
|
|
37
|
+
\else
|
|
38
|
+
\setmainfont{lmroman10}[%
|
|
39
|
+
Extension = .otf,
|
|
40
|
+
UprightFont = lmroman10-regular,
|
|
41
|
+
ItalicFont = lmroman10-italic,
|
|
42
|
+
BoldFont = lmroman10-bold,
|
|
43
|
+
BoldItalicFont = lmroman10-bolditalic,
|
|
44
|
+
SmallCapsFont = lmromancaps10-regular,
|
|
45
|
+
SlantedFont = lmromanslant10-regular,
|
|
46
|
+
BoldSlantedFont = lmromanslant10-bold,
|
|
47
|
+
Ligatures = TeX,
|
|
48
|
+
]%
|
|
49
|
+
\setsansfont{lmsans10}[%
|
|
50
|
+
Extension = .otf,
|
|
51
|
+
UprightFont = *-regular,
|
|
52
|
+
ItalicFont = *-oblique,
|
|
53
|
+
BoldFont = *-bold,
|
|
54
|
+
BoldItalicFont = *-boldoblique,
|
|
55
|
+
]%
|
|
56
|
+
\setmonofont{lmmono10}[%
|
|
57
|
+
Extension = .otf,
|
|
58
|
+
UprightFont = *-regular,
|
|
59
|
+
ItalicFont = *-italic,
|
|
60
|
+
BoldFont = *-regular,
|
|
61
|
+
BoldFeatures = {FakeBold=2},
|
|
62
|
+
BoldItalicFont = *-italic,
|
|
63
|
+
BoldItalicFeatures = {FakeBold=2},
|
|
64
|
+
SmallCapsFont = lmmonocaps10-regular,
|
|
65
|
+
SmallCapsFeatures = {Letters=SmallCaps},
|
|
66
|
+
]%
|
|
67
|
+
\fi
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
% Latin Modern Sans Serif (lm-sans)
|
|
71
|
+
\tsDeclareFontSet{lm-sans}{%
|
|
72
|
+
\ifLuaTeX
|
|
73
|
+
\setmainfont{Latin Modern Sans}%
|
|
74
|
+
\setsansfont{Latin Modern Sans}%
|
|
75
|
+
\setmonofont{Latin Modern Mono}%
|
|
76
|
+
\else
|
|
77
|
+
\setmainfont{lmsans10}[%
|
|
78
|
+
Extension = .otf,
|
|
79
|
+
UprightFont = *-regular,
|
|
80
|
+
ItalicFont = *-oblique,
|
|
81
|
+
BoldFont = *-bold,
|
|
82
|
+
BoldItalicFont = *-boldoblique,
|
|
83
|
+
Ligatures = TeX,
|
|
84
|
+
]%
|
|
85
|
+
\setsansfont{lmsans10}[%
|
|
86
|
+
Extension = .otf,
|
|
87
|
+
UprightFont = *-regular,
|
|
88
|
+
ItalicFont = *-oblique,
|
|
89
|
+
BoldFont = *-bold,
|
|
90
|
+
BoldItalicFont = *-boldoblique,
|
|
91
|
+
]%
|
|
92
|
+
\setmonofont{IBMPlexMono}[%
|
|
93
|
+
Extension = .otf,
|
|
94
|
+
UprightFont = *-Regular,
|
|
95
|
+
ItalicFont = *-Italic,
|
|
96
|
+
BoldFont = *-Bold,
|
|
97
|
+
BoldItalicFont = *-BoldItalic,
|
|
98
|
+
Scale=0.90,
|
|
99
|
+
FakeStretch = 0.92,
|
|
100
|
+
]%
|
|
101
|
+
\fi
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
% Tex Gyre Bonum (bonum)
|
|
105
|
+
\tsDeclareFontSet{bonum}{%
|
|
106
|
+
\RequirePackage{bonum-otf}%
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
% Libertinus (libertinus)
|
|
110
|
+
\tsDeclareFontSet{libertinus}{%
|
|
111
|
+
\RequirePackage{libertinus}%
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
% Tex Gyre Pagella (pagella)
|
|
115
|
+
\tsDeclareFontSet{pagella}{%
|
|
116
|
+
\RequirePackage{pagella-otf}%
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
% Tex Gyre Termes (termes)
|
|
120
|
+
\tsDeclareFontSet{termes}{%
|
|
121
|
+
\RequirePackage{termes-otf}%
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
% TeX Gyre Schola (schola)
|
|
125
|
+
\tsDeclareFontSet{schola}{%
|
|
126
|
+
\RequirePackage{schola-otf}%
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
% TeX Gyre Heros (heros)
|
|
130
|
+
\tsDeclareFontSet{heros}{%
|
|
131
|
+
\RequirePackage{heros-otf}%
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
% TeX Gyre Adventor (adventor)
|
|
135
|
+
\tsDeclareFontSet{adventor}{%
|
|
136
|
+
\RequirePackage{adventor-otf}%
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
% TeX Gyre Cursor (cursor)
|
|
140
|
+
\tsDeclareFontSet{cursor}{%
|
|
141
|
+
\RequirePackage{cursor-otf}%
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
% IBM Plex (plex)
|
|
145
|
+
\tsDeclareFontSet{plex}{%
|
|
146
|
+
\setmainfont{IBMPlexSerif}[%
|
|
147
|
+
Path=./fonts/plex-otf/,%
|
|
148
|
+
UprightFont=*-Regular,%
|
|
149
|
+
ItalicFont=*-Italic,%
|
|
150
|
+
BoldFont=*-Bold,%
|
|
151
|
+
BoldItalicFont=*-BoldItalic,%
|
|
152
|
+
]%
|
|
153
|
+
\setsansfont{IBMPlexSans}[%
|
|
154
|
+
Path=./fonts/plex-otf/,%
|
|
155
|
+
UprightFont=*-Regular,%
|
|
156
|
+
ItalicFont=*-Italic,%
|
|
157
|
+
BoldFont=*-Bold,%
|
|
158
|
+
BoldItalicFont=*-BoldItalic,%
|
|
159
|
+
]%
|
|
160
|
+
\setmonofont{IBMPlexMono}[%
|
|
161
|
+
Path=./fonts/plex-otf/,%
|
|
162
|
+
UprightFont=*-Regular,%
|
|
163
|
+
ItalicFont=*-Italic,%
|
|
164
|
+
BoldFont=*-Bold,%
|
|
165
|
+
BoldItalicFont=*-BoldItalic,%
|
|
166
|
+
]%
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
% Penn Stander (pennstander)
|
|
170
|
+
\tsDeclareFontSet{pennstander}{%
|
|
171
|
+
\setmainfont{Latin Modern Roman}%
|
|
172
|
+
\setsansfont{Latin Modern Sans}%
|
|
173
|
+
\setmonofont{Latin Modern Mono}%
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
\tsUseFontSet{\VAR{font_family}}
|
|
177
|
+
|
|
178
|
+
\BLOCK{- if fallback and fallback.get("entries") }
|
|
179
|
+
% Fallback script fonts
|
|
180
|
+
\makeatletter
|
|
181
|
+
\BLOCK{ for entry in fallback.entries }
|
|
182
|
+
\BLOCK{ set font_cmd = '\\' + entry.font_command }
|
|
183
|
+
\BLOCK{ set text_cmd = '\\' + entry.text_command }
|
|
184
|
+
% Declare font family for \VAR{entry.font_name}
|
|
185
|
+
\newfontfamily\VAR{font_cmd}[
|
|
186
|
+
Path = fonts/,
|
|
187
|
+
Extension = \VAR{entry.extension},
|
|
188
|
+
UprightFont = \VAR{entry.upright},
|
|
189
|
+
\BLOCK{ if entry.has_bold }
|
|
190
|
+
BoldFont = \VAR{entry.bold},
|
|
191
|
+
\BLOCK{ else }
|
|
192
|
+
FakeBold = 2,
|
|
193
|
+
\BLOCK{ endif }
|
|
194
|
+
Scale = MatchLowercase
|
|
195
|
+
]{\VAR{entry.font_name}}%
|
|
196
|
+
\DeclareRobustCommand\VAR{text_cmd}[1]{{\VAR{font_cmd}#1}}%
|
|
197
|
+
|
|
198
|
+
\BLOCK{ endfor }
|
|
199
|
+
\makeatother
|
|
200
|
+
|
|
201
|
+
\BLOCK{- if fallback and fallback.get("entries") }
|
|
202
|
+
% Ensure hyperref can build PDF strings without font selection commands.
|
|
203
|
+
\makeatletter
|
|
204
|
+
\AtBeginDocument{%
|
|
205
|
+
\BLOCK{ for entry in fallback.entries }
|
|
206
|
+
\BLOCK{ set text_cmd = '\\' + entry.text_command }
|
|
207
|
+
\pdfstringdefDisableCommands{%
|
|
208
|
+
\def\VAR{text_cmd}#1{#1}%
|
|
209
|
+
}%
|
|
210
|
+
\BLOCK{ endfor }
|
|
211
|
+
}
|
|
212
|
+
\makeatother
|
|
213
|
+
\BLOCK{- endif }
|
|
214
|
+
|
|
215
|
+
\BLOCK{- if fallback and fallback.get("missing_commands") }
|
|
216
|
+
% Define no-op wrappers for scripts without an available fallback font.
|
|
217
|
+
\BLOCK{ for text_cmd in fallback.missing_commands }
|
|
218
|
+
\providecommand\VAR{'\\' + text_cmd}[1]{#1}%
|
|
219
|
+
\BLOCK{ endfor }
|
|
220
|
+
\BLOCK{- endif }
|
|
221
|
+
|
|
222
|
+
\ifLuaTeX
|
|
223
|
+
\directlua{
|
|
224
|
+
luaotfload.add_fallback("texsmith-fallback-reg", {
|
|
225
|
+
\BLOCK{ for font in fallback.lua_regular }
|
|
226
|
+
"[fonts/\VAR{font}]:mode=harf",
|
|
227
|
+
\BLOCK{ endfor }
|
|
228
|
+
})
|
|
229
|
+
luaotfload.add_fallback("texsmith-fallback-bold", {
|
|
230
|
+
\BLOCK{ for font in fallback.lua_bold }
|
|
231
|
+
"[fonts/\VAR{font}]:mode=harf",
|
|
232
|
+
\BLOCK{ endfor }
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
\defaultfontfeatures{
|
|
236
|
+
Renderer = HarfBuzz,
|
|
237
|
+
RawFeature = {fallback=texsmith-fallback-reg},
|
|
238
|
+
BoldFeatures = {RawFeature={fallback=texsmith-fallback-bold}},
|
|
239
|
+
ItalicFeatures = {RawFeature={fallback=texsmith-fallback-reg}},
|
|
240
|
+
BoldItalicFeatures = {RawFeature={fallback=texsmith-fallback-bold}},
|
|
241
|
+
}
|
|
242
|
+
\addfontfeatures{RawFeature={fallback=texsmith-fallback-reg}}
|
|
243
|
+
\ExplSyntaxOn
|
|
244
|
+
\cs_new_protected:Npn \texsmith_apply_fallback_to_family:nn #1#2
|
|
245
|
+
{
|
|
246
|
+
\prop_get:cnNTF { g__fontspec_fontinfo_ #2 _prop } { fontname } \l_tmpa_tl
|
|
247
|
+
{
|
|
248
|
+
#1[
|
|
249
|
+
RawFeature = {fallback=texsmith-fallback-reg},
|
|
250
|
+
BoldFeatures = {RawFeature={fallback=texsmith-fallback-bold}},
|
|
251
|
+
ItalicFeatures = {RawFeature={fallback=texsmith-fallback-reg}},
|
|
252
|
+
BoldItalicFeatures = {RawFeature={fallback=texsmith-fallback-bold}},
|
|
253
|
+
]{\l_tmpa_tl}
|
|
254
|
+
}{}
|
|
255
|
+
}
|
|
256
|
+
\texsmith_apply_fallback_to_family:nn \setmainfont \rmdefault
|
|
257
|
+
\texsmith_apply_fallback_to_family:nn \setsansfont \sfdefault
|
|
258
|
+
\texsmith_apply_fallback_to_family:nn \setmonofont \ttdefault
|
|
259
|
+
\ExplSyntaxOff
|
|
260
|
+
\else\ifXeTeX
|
|
261
|
+
\BLOCK{- if fallback and fallback.get("transitions") }
|
|
262
|
+
\BLOCK{ set uc_options = ['Latin'] + (fallback.package_options if fallback else []) + ['Devanagari'] }
|
|
263
|
+
\RequirePackage[
|
|
264
|
+
\VAR{ uc_options | join(',\n ') }
|
|
265
|
+
]{ucharclasses}
|
|
266
|
+
\makeatletter
|
|
267
|
+
\newcommand{\texsmithFallbackFamily}{\rmfamily}
|
|
268
|
+
\newcommand{\texsmithSetFallbackFamily}[1]{\def\texsmithFallbackFamily{#1}}
|
|
269
|
+
\g@addto@macro\rmfamily{\texsmithSetFallbackFamily{\rmfamily}}
|
|
270
|
+
\g@addto@macro\sffamily{\texsmithSetFallbackFamily{\sffamily}}
|
|
271
|
+
\g@addto@macro\ttfamily{\texsmithSetFallbackFamily{\ttfamily}}
|
|
272
|
+
\BLOCK{ for entry in fallback.entries }
|
|
273
|
+
\expandafter\g@addto@macro\csname \VAR{entry.font_command}\endcsname{%
|
|
274
|
+
\texsmithSetFallbackFamily{\csname \VAR{entry.font_command}\endcsname}%
|
|
275
|
+
}
|
|
276
|
+
\BLOCK{ endfor }
|
|
277
|
+
\makeatother
|
|
278
|
+
\texsmithSetFallbackFamily{\rmfamily}
|
|
279
|
+
\setDefaultTransitions{\texsmithFallbackFamily}{\texsmithFallbackFamily}%
|
|
280
|
+
\BLOCK{ for transition in fallback.transitions }
|
|
281
|
+
\VAR{transition}
|
|
282
|
+
\BLOCK{ endfor }
|
|
283
|
+
\enableTransitionRules
|
|
284
|
+
\ifdefined\XeTeXinterchartoksstate
|
|
285
|
+
\XeTeXinterchartoksstate=1%
|
|
286
|
+
\AtBeginDocument{\XeTeXinterchartoksstate=1}%
|
|
287
|
+
\fi
|
|
288
|
+
\AtBeginDocument{\enableTransitionRules}%
|
|
289
|
+
\BLOCK{- endif }
|
|
290
|
+
\fi\fi
|
|
291
|
+
\BLOCK{- endif }
|
|
292
|
+
\fi
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, ClassVar
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationError, field_validator, model_validator
|
|
8
|
+
|
|
9
|
+
from texsmith.core.fragments.base import BaseFragment, FragmentPiece
|
|
10
|
+
from texsmith.core.templates.manifest import TemplateAttributeSpec, TemplateError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_DEFAULT_MARGIN = "0pt"
|
|
14
|
+
_DEFAULT_FOLD_SIZE = "10mm"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _coerce_bool(value: Any, *, default: bool = False) -> bool:
|
|
18
|
+
if value is None:
|
|
19
|
+
return default
|
|
20
|
+
if isinstance(value, bool):
|
|
21
|
+
return value
|
|
22
|
+
if isinstance(value, (int, float)):
|
|
23
|
+
return bool(value)
|
|
24
|
+
if isinstance(value, str):
|
|
25
|
+
token = value.strip().lower()
|
|
26
|
+
if not token:
|
|
27
|
+
return default
|
|
28
|
+
if token in {"true", "yes", "on", "1", "dogear"}:
|
|
29
|
+
return True
|
|
30
|
+
if token in {"false", "no", "off", "0", "border"}:
|
|
31
|
+
return False
|
|
32
|
+
return default
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FrameConfig(BaseModel):
|
|
36
|
+
"""Validated representation of the press.frame options."""
|
|
37
|
+
|
|
38
|
+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
|
|
39
|
+
|
|
40
|
+
enabled: bool = False
|
|
41
|
+
dogear: bool = False
|
|
42
|
+
margin: str | None = None
|
|
43
|
+
fold_size: str | None = Field(default=None, alias="fold-size")
|
|
44
|
+
|
|
45
|
+
@model_validator(mode="before")
|
|
46
|
+
@classmethod
|
|
47
|
+
def _coerce_payload(cls, data: Any) -> Any:
|
|
48
|
+
if isinstance(data, FrameConfig):
|
|
49
|
+
return data.model_dump()
|
|
50
|
+
if data is None:
|
|
51
|
+
return {"enabled": False}
|
|
52
|
+
if isinstance(data, Mapping):
|
|
53
|
+
payload = dict(data)
|
|
54
|
+
enabled = _coerce_bool(payload.get("enabled"), default=True)
|
|
55
|
+
mode = payload.get("mode")
|
|
56
|
+
dogear_value = payload.get("dogear")
|
|
57
|
+
if isinstance(mode, str):
|
|
58
|
+
token = mode.strip().lower()
|
|
59
|
+
if token == "border":
|
|
60
|
+
dogear_value = False
|
|
61
|
+
enabled = True
|
|
62
|
+
elif token in {"dogear", "fold"}:
|
|
63
|
+
dogear_value = True
|
|
64
|
+
enabled = True
|
|
65
|
+
dogear_flag = _coerce_bool(dogear_value, default=True)
|
|
66
|
+
fold_size = payload.get("fold-size") or payload.get("fold_size") or payload.get("fold")
|
|
67
|
+
margin = payload.get("margin")
|
|
68
|
+
if not enabled:
|
|
69
|
+
return {"enabled": False}
|
|
70
|
+
return {
|
|
71
|
+
"enabled": True,
|
|
72
|
+
"dogear": dogear_flag,
|
|
73
|
+
"margin": margin,
|
|
74
|
+
"fold_size": fold_size,
|
|
75
|
+
}
|
|
76
|
+
if isinstance(data, (bool, int, float)):
|
|
77
|
+
flag = bool(data)
|
|
78
|
+
return {"enabled": flag, "dogear": flag}
|
|
79
|
+
if isinstance(data, str):
|
|
80
|
+
token = data.strip().lower()
|
|
81
|
+
if not token or token in {"false", "off", "no", "0", "none"}:
|
|
82
|
+
return {"enabled": False}
|
|
83
|
+
if token == "border":
|
|
84
|
+
return {"enabled": True, "dogear": False}
|
|
85
|
+
if token in {"dogear", "true", "yes", "on", "1"}:
|
|
86
|
+
return {"enabled": True, "dogear": True}
|
|
87
|
+
raise TemplateError("press.frame accepts false, true, 'border', or 'dogear'.")
|
|
88
|
+
raise TemplateError("press.frame must be a boolean, string, or mapping.")
|
|
89
|
+
|
|
90
|
+
@field_validator("margin", "fold_size", mode="before")
|
|
91
|
+
@classmethod
|
|
92
|
+
def _normalise_length(cls, value: Any) -> str | None:
|
|
93
|
+
if value is None:
|
|
94
|
+
return None
|
|
95
|
+
if isinstance(value, (int, float)):
|
|
96
|
+
return f"{value}mm"
|
|
97
|
+
if isinstance(value, str):
|
|
98
|
+
trimmed = value.strip()
|
|
99
|
+
return trimmed or None
|
|
100
|
+
return str(value)
|
|
101
|
+
|
|
102
|
+
@model_validator(mode="after")
|
|
103
|
+
def _disable_when_empty(self) -> FrameConfig:
|
|
104
|
+
if not self.enabled:
|
|
105
|
+
object.__setattr__(self, "dogear", False)
|
|
106
|
+
return self
|
|
107
|
+
|
|
108
|
+
def effective_margin(self) -> str:
|
|
109
|
+
return self.margin or _DEFAULT_MARGIN
|
|
110
|
+
|
|
111
|
+
def effective_fold_size(self) -> str:
|
|
112
|
+
return self.fold_size or _DEFAULT_FOLD_SIZE
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class FrameFragment(BaseFragment[FrameConfig]):
|
|
116
|
+
"""Optional page frame with an optional folded corner."""
|
|
117
|
+
|
|
118
|
+
name: ClassVar[str] = "ts-frame"
|
|
119
|
+
description: ClassVar[str] = "Draw a page frame (with optional dogear) on each page."
|
|
120
|
+
pieces: ClassVar[list[FragmentPiece]] = [
|
|
121
|
+
FragmentPiece(
|
|
122
|
+
template_path=Path(__file__).with_name("ts-frame.tex.jinja"),
|
|
123
|
+
kind="inline",
|
|
124
|
+
slot="extra_packages",
|
|
125
|
+
)
|
|
126
|
+
]
|
|
127
|
+
attributes: ClassVar[dict[str, TemplateAttributeSpec]] = {
|
|
128
|
+
"frame_spec": TemplateAttributeSpec(
|
|
129
|
+
default=None,
|
|
130
|
+
sources=["frame"],
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
config_cls: ClassVar[type[FrameConfig]] = FrameConfig
|
|
134
|
+
source: ClassVar[Path] = Path(__file__).with_name("ts-frame.tex.jinja")
|
|
135
|
+
context_defaults: ClassVar[dict[str, Any]] = {
|
|
136
|
+
"ts_frame_enabled": False,
|
|
137
|
+
"ts_frame_dogear": False,
|
|
138
|
+
"ts_frame_margin": _DEFAULT_MARGIN,
|
|
139
|
+
"ts_frame_fold_size": _DEFAULT_FOLD_SIZE,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
def build_config(
|
|
143
|
+
self, context: Mapping[str, Any], overrides: Mapping[str, Any] | None = None
|
|
144
|
+
) -> FrameConfig:
|
|
145
|
+
_ = overrides
|
|
146
|
+
raw_value = context.get("frame_spec") or context.get("frame")
|
|
147
|
+
try:
|
|
148
|
+
return self.config_cls.model_validate(raw_value)
|
|
149
|
+
except ValidationError as exc:
|
|
150
|
+
raise TemplateError(f"Invalid frame settings: {exc}") from exc
|
|
151
|
+
|
|
152
|
+
def inject(
|
|
153
|
+
self,
|
|
154
|
+
config: FrameConfig,
|
|
155
|
+
context: dict[str, Any],
|
|
156
|
+
overrides: Mapping[str, Any] | None = None,
|
|
157
|
+
) -> None:
|
|
158
|
+
_ = overrides
|
|
159
|
+
context["ts_frame_enabled"] = config.enabled
|
|
160
|
+
context["ts_frame_dogear"] = bool(config.enabled and config.dogear)
|
|
161
|
+
context["ts_frame_margin"] = config.effective_margin()
|
|
162
|
+
context["ts_frame_fold_size"] = config.effective_fold_size()
|
|
163
|
+
|
|
164
|
+
def should_render(self, config: FrameConfig) -> bool:
|
|
165
|
+
return bool(config.enabled)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
fragment = FrameFragment()
|
|
169
|
+
|
|
170
|
+
__all__ = ["FrameConfig", "FrameFragment", "fragment"]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
% Page frame fragment (ts-frame)
|
|
2
|
+
\usepackage{tikz}
|
|
3
|
+
\usetikzlibrary{calc}
|
|
4
|
+
\usepackage{tikzpagenodes}
|
|
5
|
+
|
|
6
|
+
\newif\iftsframe
|
|
7
|
+
\newif\iftsframedogear
|
|
8
|
+
\BLOCK{ if ts_frame_enabled }\tsframetrue\BLOCK{ else }\tsframefalse\BLOCK{ endif }
|
|
9
|
+
\BLOCK{ if ts_frame_dogear }\tsframedogeartrue\BLOCK{ else }\tsframedogearfalse\BLOCK{ endif }
|
|
10
|
+
|
|
11
|
+
\newlength\TSFrameInset
|
|
12
|
+
\setlength\TSFrameInset{\VAR{ts_frame_margin|default('0pt')}}
|
|
13
|
+
\newlength\TSFrameFoldSize
|
|
14
|
+
\setlength\TSFrameFoldSize{\VAR{ts_frame_fold_size|default('10mm')}}
|
|
15
|
+
\newcommand\TSFrameColor{black}
|
|
16
|
+
\newcommand\TSFrameLineWidth{0.6pt}
|
|
17
|
+
|
|
18
|
+
\AddToHook{shipout/background}{%
|
|
19
|
+
\iftsframe
|
|
20
|
+
\begin{tikzpicture}[remember picture,overlay]
|
|
21
|
+
\coordinate (A) at ($(current page.north west)+(\TSFrameInset,-\TSFrameInset)$);
|
|
22
|
+
\coordinate (B) at ($(current page.north east)+(-\TSFrameInset,-\TSFrameInset)$);
|
|
23
|
+
\coordinate (C) at ($(current page.south east)+(-\TSFrameInset,\TSFrameInset)$);
|
|
24
|
+
\coordinate (D) at ($(current page.south west)+(\TSFrameInset,\TSFrameInset)$);
|
|
25
|
+
|
|
26
|
+
\coordinate (Bdown) at ($(B)-(0,\TSFrameFoldSize)$);
|
|
27
|
+
\coordinate (Bleft) at ($(B)-(\TSFrameFoldSize,0)$);
|
|
28
|
+
\coordinate (Corner) at ($(B)-(\TSFrameFoldSize,\TSFrameFoldSize)$);
|
|
29
|
+
|
|
30
|
+
\iftsframedogear
|
|
31
|
+
\draw[line width=\TSFrameLineWidth,draw=\TSFrameColor]
|
|
32
|
+
(A) -- (Bleft) -- (Bdown) -- (C) -- (D) -- cycle;
|
|
33
|
+
\draw[line width=\TSFrameLineWidth,draw=\TSFrameColor]
|
|
34
|
+
(Corner)
|
|
35
|
+
.. controls ($(Corner)+(0.3*\TSFrameFoldSize,0.1*\TSFrameFoldSize)$)
|
|
36
|
+
and ($(Bdown)+(-0.3*\TSFrameFoldSize,-0.1*\TSFrameFoldSize)$) ..
|
|
37
|
+
(Bdown);
|
|
38
|
+
\draw[line width=\TSFrameLineWidth,draw=\TSFrameColor]
|
|
39
|
+
(Bleft)
|
|
40
|
+
.. controls ($(Bleft)+(0.1*\TSFrameFoldSize,-0.3*\TSFrameFoldSize)$)
|
|
41
|
+
and ($(Corner)+(-0.1*\TSFrameFoldSize,0.3*\TSFrameFoldSize)$) ..
|
|
42
|
+
(Corner);
|
|
43
|
+
\else
|
|
44
|
+
\draw[line width=\TSFrameLineWidth,draw=\TSFrameColor]
|
|
45
|
+
(A) rectangle (C);
|
|
46
|
+
\fi
|
|
47
|
+
\end{tikzpicture}%
|
|
48
|
+
\fi
|
|
49
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, ClassVar
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
8
|
+
|
|
9
|
+
from texsmith.core.fragments.base import BaseFragment, FragmentPiece
|
|
10
|
+
from texsmith.core.templates.base import _build_environment
|
|
11
|
+
|
|
12
|
+
from .paper import (
|
|
13
|
+
GeometryResolution,
|
|
14
|
+
_coerce_marks_flag,
|
|
15
|
+
inject_geometry_context,
|
|
16
|
+
resolve_geometry_settings,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GeometryFragmentConfig(BaseModel):
|
|
21
|
+
"""Validated payload accepted by the ts-geometry fragment."""
|
|
22
|
+
|
|
23
|
+
paper: Mapping[str, Any] | None = None
|
|
24
|
+
geometry: dict[str, Any] = Field(default_factory=dict)
|
|
25
|
+
duplex: bool | None = None
|
|
26
|
+
margin: Any | None = None
|
|
27
|
+
orientation: Any | None = None
|
|
28
|
+
binding: Any | None = None
|
|
29
|
+
marks: bool | None = None
|
|
30
|
+
watermark: str | None = None
|
|
31
|
+
|
|
32
|
+
model_config = ConfigDict(extra="allow")
|
|
33
|
+
|
|
34
|
+
@model_validator(mode="before")
|
|
35
|
+
@classmethod
|
|
36
|
+
def _fold_paper(cls, data: Any) -> Any:
|
|
37
|
+
if data is None:
|
|
38
|
+
return {}
|
|
39
|
+
if not isinstance(data, Mapping):
|
|
40
|
+
return {"paper": data}
|
|
41
|
+
|
|
42
|
+
payload = dict(data)
|
|
43
|
+
paper_payload = payload.get("paper")
|
|
44
|
+
if isinstance(paper_payload, Mapping):
|
|
45
|
+
paper_data: dict[str, Any] = dict(paper_payload)
|
|
46
|
+
elif paper_payload is None:
|
|
47
|
+
paper_data = {}
|
|
48
|
+
else:
|
|
49
|
+
paper_data = {"format": paper_payload}
|
|
50
|
+
|
|
51
|
+
if "frame" in payload and "marks" not in payload:
|
|
52
|
+
payload["marks"] = payload.pop("frame")
|
|
53
|
+
|
|
54
|
+
for key in ("margin", "orientation", "binding", "marks", "watermark", "duplex"):
|
|
55
|
+
if key in payload and key not in paper_data:
|
|
56
|
+
paper_data[key] = payload[key]
|
|
57
|
+
|
|
58
|
+
extra_payload = payload.get("geometry") or payload.get("extra")
|
|
59
|
+
if isinstance(extra_payload, Mapping):
|
|
60
|
+
merged_extra = dict(paper_data.get("extra") or {})
|
|
61
|
+
merged_extra.update(extra_payload)
|
|
62
|
+
paper_data["extra"] = merged_extra
|
|
63
|
+
|
|
64
|
+
payload["paper"] = paper_data
|
|
65
|
+
return payload
|
|
66
|
+
|
|
67
|
+
def to_context(self) -> dict[str, Any]:
|
|
68
|
+
"""Return a context mapping suitable for ``resolve_geometry_settings``."""
|
|
69
|
+
context: dict[str, Any] = {}
|
|
70
|
+
if self.paper is not None:
|
|
71
|
+
context["paper"] = self.paper
|
|
72
|
+
if self.geometry:
|
|
73
|
+
context["geometry"] = self.geometry
|
|
74
|
+
if self.duplex is not None:
|
|
75
|
+
context["duplex"] = self.duplex
|
|
76
|
+
if self.margin is not None:
|
|
77
|
+
context["margin"] = self.margin
|
|
78
|
+
if self.orientation is not None:
|
|
79
|
+
context["orientation"] = self.orientation
|
|
80
|
+
if self.binding is not None:
|
|
81
|
+
context["binding"] = self.binding
|
|
82
|
+
if self.marks is not None:
|
|
83
|
+
context["marks"] = self.marks
|
|
84
|
+
if self.watermark is not None:
|
|
85
|
+
context["watermark"] = self.watermark
|
|
86
|
+
return context
|
|
87
|
+
|
|
88
|
+
def resolve(self) -> GeometryResolution:
|
|
89
|
+
"""Return a resolved geometry payload for this configuration."""
|
|
90
|
+
return resolve_geometry_settings(self.to_context())
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class GeometryFragment(BaseFragment[GeometryFragmentConfig]):
|
|
94
|
+
"""Programmatic renderer and fragment adapter for the ts-geometry fragment."""
|
|
95
|
+
|
|
96
|
+
name: ClassVar[str] = "ts-geometry"
|
|
97
|
+
description: ClassVar[str] = "Page layout setup driven by press.paper."
|
|
98
|
+
pieces: ClassVar[list[FragmentPiece]] = [
|
|
99
|
+
FragmentPiece(
|
|
100
|
+
template_path=Path(__file__).with_name("ts_geometry.tex.jinja"),
|
|
101
|
+
kind="inline",
|
|
102
|
+
slot="extra_packages",
|
|
103
|
+
)
|
|
104
|
+
]
|
|
105
|
+
attributes: ClassVar[dict[str, Any]] = {}
|
|
106
|
+
config_cls: ClassVar[type[GeometryFragmentConfig]] = GeometryFragmentConfig
|
|
107
|
+
source: ClassVar[Path] = Path(__file__).with_name("ts_geometry.tex.jinja")
|
|
108
|
+
context_defaults: ClassVar[dict[str, Any]] = {
|
|
109
|
+
"extra_packages": "",
|
|
110
|
+
"documentclass_options": "",
|
|
111
|
+
"geometry_options": "",
|
|
112
|
+
"geometry_options_list": [],
|
|
113
|
+
"geometry_extra_options": "",
|
|
114
|
+
"geometry_duplex": True,
|
|
115
|
+
"paper": {"format": "a4"},
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
def __init__(self, payload: Any | None = None) -> None:
|
|
119
|
+
self.config = GeometryFragmentConfig.model_validate(payload or {})
|
|
120
|
+
self.template_path = Path(__file__).with_name("ts_geometry.tex.jinja")
|
|
121
|
+
|
|
122
|
+
def build_config(
|
|
123
|
+
self, context: Mapping[str, Any], overrides: Mapping[str, Any] | None = None
|
|
124
|
+
) -> GeometryFragmentConfig:
|
|
125
|
+
_ = overrides
|
|
126
|
+
payload: dict[str, Any] = {}
|
|
127
|
+
for key in ("paper", "geometry", "duplex", "margin", "orientation", "binding", "watermark"):
|
|
128
|
+
if key in context:
|
|
129
|
+
payload[key] = context.get(key)
|
|
130
|
+
marks_flag = _coerce_marks_flag(context.get("marks"))
|
|
131
|
+
if marks_flag is not None:
|
|
132
|
+
payload["marks"] = marks_flag
|
|
133
|
+
return self.config_cls.model_validate(payload)
|
|
134
|
+
|
|
135
|
+
def inject(
|
|
136
|
+
self,
|
|
137
|
+
config: GeometryFragmentConfig,
|
|
138
|
+
context: dict[str, Any],
|
|
139
|
+
overrides: Mapping[str, Any] | None = None,
|
|
140
|
+
) -> None:
|
|
141
|
+
# Ensure raw config fields are present before resolution.
|
|
142
|
+
context.update(config.to_context())
|
|
143
|
+
inject_geometry_context(context, overrides)
|
|
144
|
+
|
|
145
|
+
def should_render(self, config: GeometryFragmentConfig) -> bool:
|
|
146
|
+
_ = config
|
|
147
|
+
return True
|
|
148
|
+
|
|
149
|
+
# Convenience renderer for direct use
|
|
150
|
+
def render(self) -> str:
|
|
151
|
+
context = self.config.to_context()
|
|
152
|
+
inject_geometry_context(context)
|
|
153
|
+
environment = _build_environment(self.template_path.parent)
|
|
154
|
+
template = environment.get_template(self.template_path.name)
|
|
155
|
+
return template.render(**context)
|
|
156
|
+
|
|
157
|
+
def get_latex(self) -> str:
|
|
158
|
+
"""Return the rendered LaTeX fragment."""
|
|
159
|
+
return self.render()
|
|
160
|
+
|
|
161
|
+
def getLatex(self) -> str: # noqa: N802 - API compatibility
|
|
162
|
+
"""CamelCase alias for front-end callers."""
|
|
163
|
+
return self.render()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
fragment = GeometryFragment()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
__all__ = ["GeometryFragment", "GeometryFragmentConfig", "fragment"]
|