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,28 @@
|
|
|
1
|
+
"""Runtime helpers for latexmk builds."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from .log import LatexStreamResult, stream_latexmk_output
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run_latex_engine(
|
|
14
|
+
argv: Sequence[str],
|
|
15
|
+
*,
|
|
16
|
+
workdir: Path,
|
|
17
|
+
env: Mapping[str, str],
|
|
18
|
+
console: Console,
|
|
19
|
+
verbosity: int = 0,
|
|
20
|
+
) -> LatexStreamResult:
|
|
21
|
+
"""Execute a latexmk command and stream structured output."""
|
|
22
|
+
return stream_latexmk_output(
|
|
23
|
+
argv,
|
|
24
|
+
cwd=str(workdir),
|
|
25
|
+
env=env,
|
|
26
|
+
console=console,
|
|
27
|
+
verbosity=verbosity,
|
|
28
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Runtime helpers for invoking the Tectonic engine."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping, Sequence
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
from ..latex import LatexStreamResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run_tectonic_engine(
|
|
15
|
+
argv: Sequence[str],
|
|
16
|
+
*,
|
|
17
|
+
workdir: Path,
|
|
18
|
+
env: Mapping[str, str],
|
|
19
|
+
console: Console,
|
|
20
|
+
) -> LatexStreamResult:
|
|
21
|
+
"""Execute a tectonic command, streaming plain output."""
|
|
22
|
+
with subprocess.Popen(
|
|
23
|
+
argv,
|
|
24
|
+
cwd=str(workdir),
|
|
25
|
+
env=dict(env),
|
|
26
|
+
stdout=subprocess.PIPE,
|
|
27
|
+
stderr=subprocess.STDOUT,
|
|
28
|
+
text=True,
|
|
29
|
+
bufsize=1,
|
|
30
|
+
encoding="utf-8",
|
|
31
|
+
errors="replace",
|
|
32
|
+
) as process:
|
|
33
|
+
assert process.stdout is not None
|
|
34
|
+
for line in process.stdout:
|
|
35
|
+
console.print(line.rstrip())
|
|
36
|
+
returncode = process.wait()
|
|
37
|
+
|
|
38
|
+
return LatexStreamResult(returncode=returncode, messages=[])
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Utilities for rendering LaTeX partials (snippets)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Iterable
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
from jinja2 import Environment, FileSystemLoader, Template
|
|
10
|
+
from requests.utils import requote_uri as requote_url
|
|
11
|
+
|
|
12
|
+
from .pygments import PygmentsLatexHighlighter
|
|
13
|
+
from .utils import escape_latex_chars
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
17
|
+
from texsmith.core.context import DocumentState
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
TEMPLATE_DIR = Path(__file__).resolve().parent / "partials"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def optimize_list(numbers: Iterable[int]) -> list[str]:
|
|
24
|
+
"""Merge consecutive integers into human-readable ranges."""
|
|
25
|
+
values = sorted(numbers)
|
|
26
|
+
if not values:
|
|
27
|
+
return []
|
|
28
|
+
|
|
29
|
+
optimized: list[str] = []
|
|
30
|
+
start = end = values[0]
|
|
31
|
+
|
|
32
|
+
for num in values[1:]:
|
|
33
|
+
if num == end + 1:
|
|
34
|
+
end = num
|
|
35
|
+
else:
|
|
36
|
+
optimized.append(f"{start}-{end}" if start != end else str(start))
|
|
37
|
+
start = end = num
|
|
38
|
+
|
|
39
|
+
optimized.append(f"{start}-{end}" if start != end else str(start))
|
|
40
|
+
return optimized
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class LaTeXFormatter:
|
|
44
|
+
"""Render LaTeX templates using Jinja2 with custom delimiters."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, template_dir: Path = TEMPLATE_DIR) -> None:
|
|
47
|
+
self.env = Environment(
|
|
48
|
+
block_start_string=r"\BLOCK{",
|
|
49
|
+
block_end_string=r"}",
|
|
50
|
+
variable_start_string=r"\VAR{",
|
|
51
|
+
variable_end_string=r"}",
|
|
52
|
+
comment_start_string=r"\COMMENT{",
|
|
53
|
+
comment_end_string=r"}",
|
|
54
|
+
loader=FileSystemLoader(template_dir),
|
|
55
|
+
)
|
|
56
|
+
self.legacy_latex_accents: bool = False
|
|
57
|
+
self.env.filters.setdefault("latex_escape", self._escape_latex)
|
|
58
|
+
self.env.filters.setdefault("escape_latex", self._escape_latex)
|
|
59
|
+
|
|
60
|
+
template_paths: list[Path] = []
|
|
61
|
+
for ext in (".tex", ".cls"):
|
|
62
|
+
template_paths.extend(template_dir.glob(f"**/*{ext}"))
|
|
63
|
+
|
|
64
|
+
self._template_names: dict[str, str] = {}
|
|
65
|
+
for path in template_paths:
|
|
66
|
+
relative = path.relative_to(template_dir)
|
|
67
|
+
key = self._normalise_key(relative.with_suffix("").as_posix())
|
|
68
|
+
self._template_names[key] = relative.as_posix()
|
|
69
|
+
|
|
70
|
+
self.templates: dict[str, Template] = {}
|
|
71
|
+
self.default_code_engine = "pygments"
|
|
72
|
+
self.default_code_style = "bw"
|
|
73
|
+
self._pygments: PygmentsLatexHighlighter | None = None
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def _normalise_key(name: str) -> str:
|
|
77
|
+
"""Normalise template identifiers to align with loader expectations."""
|
|
78
|
+
return name.replace("/", "_")
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def normalise_key(cls, name: str) -> str:
|
|
82
|
+
"""Public wrapper for normalising template identifiers."""
|
|
83
|
+
return cls._normalise_key(name)
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def template_names(self) -> set[str]:
|
|
87
|
+
"""Return the set of available template identifiers."""
|
|
88
|
+
return set(self._template_names)
|
|
89
|
+
|
|
90
|
+
def _get_template(self, key: str) -> Template:
|
|
91
|
+
"""Return a cached template instance loading it on demand."""
|
|
92
|
+
normalised = self._normalise_key(key)
|
|
93
|
+
template = self.templates.get(normalised)
|
|
94
|
+
if template is not None:
|
|
95
|
+
return template
|
|
96
|
+
|
|
97
|
+
template_name = self._template_names.get(normalised)
|
|
98
|
+
if template_name is None:
|
|
99
|
+
raise KeyError(key)
|
|
100
|
+
|
|
101
|
+
template = self.env.get_template(template_name)
|
|
102
|
+
self.templates[normalised] = template
|
|
103
|
+
return template
|
|
104
|
+
|
|
105
|
+
def __getattr__(self, method: str) -> Callable[..., str]:
|
|
106
|
+
"""Proxy calls to templates or custom handlers."""
|
|
107
|
+
mangled = f"handle_{method}"
|
|
108
|
+
try:
|
|
109
|
+
handler = object.__getattribute__(self, mangled)
|
|
110
|
+
except AttributeError:
|
|
111
|
+
handler = None
|
|
112
|
+
if handler is not None:
|
|
113
|
+
return handler # type: ignore[return-value]
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
template = self._get_template(method)
|
|
117
|
+
except KeyError:
|
|
118
|
+
raise AttributeError(f"Object has no template for '{method}'") from None
|
|
119
|
+
|
|
120
|
+
def render_template(*args: Any, **kwargs: Any) -> str:
|
|
121
|
+
"""Render the template with optional positional shorthand."""
|
|
122
|
+
if len(args) > 1:
|
|
123
|
+
msg = f"Expected at most 1 argument, got {len(args)}, use keyword arguments instead"
|
|
124
|
+
raise ValueError(msg)
|
|
125
|
+
if args:
|
|
126
|
+
kwargs["text"] = args[0]
|
|
127
|
+
return template.render(**kwargs)
|
|
128
|
+
|
|
129
|
+
return render_template
|
|
130
|
+
|
|
131
|
+
def __getitem__(self, key: str) -> Callable[..., str]:
|
|
132
|
+
return self._get_template(key).render
|
|
133
|
+
|
|
134
|
+
def _escape_url(self, url: str) -> str:
|
|
135
|
+
"""Escape a URL for safe use in LaTeX commands."""
|
|
136
|
+
return escape_latex_chars(requote_url(url), legacy_accents=self.legacy_latex_accents)
|
|
137
|
+
|
|
138
|
+
def handle_codeinlinett(self, text: str) -> str:
|
|
139
|
+
"""Render plain inline code inside \\texttt."""
|
|
140
|
+
escaped = escape_latex_chars(text, legacy_accents=self.legacy_latex_accents)
|
|
141
|
+
escaped = escaped.replace("-", "-\\allowbreak{}")
|
|
142
|
+
return self._get_template("codeinlinett").render(text=escaped)
|
|
143
|
+
|
|
144
|
+
def handle_codeblock(
|
|
145
|
+
self,
|
|
146
|
+
code: str,
|
|
147
|
+
language: str = "text",
|
|
148
|
+
filename: str | None = None,
|
|
149
|
+
lineno: bool = False,
|
|
150
|
+
highlight: Iterable[int] | None = None,
|
|
151
|
+
baselinestretch: float | None = None,
|
|
152
|
+
engine: str | None = None,
|
|
153
|
+
state: DocumentState | None = None,
|
|
154
|
+
**_: Any,
|
|
155
|
+
) -> str:
|
|
156
|
+
"""Render code blocks with optional line numbers and highlights."""
|
|
157
|
+
highlight = list(highlight or [])
|
|
158
|
+
optimized_highlight = optimize_list(highlight)
|
|
159
|
+
normalized_engine = (engine or self.default_code_engine or "pygments").lower()
|
|
160
|
+
if normalized_engine not in {"minted", "listings", "verbatim", "pygments"}:
|
|
161
|
+
normalized_engine = "pygments"
|
|
162
|
+
|
|
163
|
+
if normalized_engine == "pygments":
|
|
164
|
+
style_name = str(self.default_code_style or "bw").strip() or "bw"
|
|
165
|
+
if self._pygments is None or self._pygments.style != style_name:
|
|
166
|
+
self._pygments = PygmentsLatexHighlighter(style=style_name)
|
|
167
|
+
latex_code, style_defs = self._pygments.render(
|
|
168
|
+
code,
|
|
169
|
+
language,
|
|
170
|
+
linenos=lineno,
|
|
171
|
+
highlight_lines=highlight,
|
|
172
|
+
)
|
|
173
|
+
if state is not None and style_defs:
|
|
174
|
+
state.pygments_styles.setdefault(self._pygments.style_key, style_defs)
|
|
175
|
+
return self._get_template("codeblock_pygments").render(
|
|
176
|
+
code=latex_code,
|
|
177
|
+
language=language,
|
|
178
|
+
linenos=lineno,
|
|
179
|
+
filename=filename,
|
|
180
|
+
baselinestretch=baselinestretch,
|
|
181
|
+
highlight=optimized_highlight,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if normalized_engine == "listings":
|
|
185
|
+
return self._get_template("codeblock_listings").render(
|
|
186
|
+
code=code,
|
|
187
|
+
language=language,
|
|
188
|
+
linenos=lineno,
|
|
189
|
+
filename=filename,
|
|
190
|
+
baselinestretch=baselinestretch,
|
|
191
|
+
highlight=optimized_highlight,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if normalized_engine == "verbatim":
|
|
195
|
+
return self._get_template("codeblock_verbatim").render(
|
|
196
|
+
code=code,
|
|
197
|
+
language=language,
|
|
198
|
+
linenos=lineno,
|
|
199
|
+
filename=filename,
|
|
200
|
+
baselinestretch=baselinestretch,
|
|
201
|
+
highlight=optimized_highlight,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return self._get_template("codeblock").render(
|
|
205
|
+
code=code,
|
|
206
|
+
language=language,
|
|
207
|
+
linenos=lineno,
|
|
208
|
+
filename=filename,
|
|
209
|
+
baselinestretch=baselinestretch,
|
|
210
|
+
highlight=optimized_highlight,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
def url(self, text: str, url: str) -> str:
|
|
214
|
+
"""Render a URL, escaping special LaTeX characters."""
|
|
215
|
+
safe_url = self._escape_url(url)
|
|
216
|
+
return self._get_template("url").render(text=text, url=safe_url)
|
|
217
|
+
|
|
218
|
+
def handle_href(self, text: str, url: str) -> str:
|
|
219
|
+
"""Render \\href links with escaped URLs."""
|
|
220
|
+
return self._get_template("href").render(text=text, url=self._escape_url(url))
|
|
221
|
+
|
|
222
|
+
def handle_regex(self, text: str, url: str) -> str:
|
|
223
|
+
"""Render regex helper links with escaped URLs."""
|
|
224
|
+
return self._get_template("regex").render(text=text, url=self._escape_url(url))
|
|
225
|
+
|
|
226
|
+
def handle_codeinline(
|
|
227
|
+
self,
|
|
228
|
+
*,
|
|
229
|
+
language: str = "text",
|
|
230
|
+
text: str,
|
|
231
|
+
engine: str | None = None,
|
|
232
|
+
state: DocumentState | None = None,
|
|
233
|
+
delimiter: str | None = None,
|
|
234
|
+
) -> str:
|
|
235
|
+
"""Render inline code with engine-specific highlighting."""
|
|
236
|
+
normalized_engine = (engine or self.default_code_engine or "pygments").lower()
|
|
237
|
+
if normalized_engine == "minted":
|
|
238
|
+
delimiter = delimiter or "|"
|
|
239
|
+
return self._get_template("codeinline").render(
|
|
240
|
+
language=language or "text",
|
|
241
|
+
text=text,
|
|
242
|
+
delimiter=delimiter,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
if normalized_engine == "pygments":
|
|
246
|
+
style_name = str(self.default_code_style or "bw").strip() or "bw"
|
|
247
|
+
if self._pygments is None or self._pygments.style != style_name:
|
|
248
|
+
self._pygments = PygmentsLatexHighlighter(style=style_name)
|
|
249
|
+
latex_code, style_defs = self._pygments.render_inline(text, language)
|
|
250
|
+
if state is not None and style_defs:
|
|
251
|
+
state.pygments_styles.setdefault(self._pygments.style_key, style_defs)
|
|
252
|
+
return r"{\ttfamily " + latex_code + "}"
|
|
253
|
+
|
|
254
|
+
# listings/verbatim fallback to plain typewriter
|
|
255
|
+
return self.handle_codeinlinett(text)
|
|
256
|
+
|
|
257
|
+
def _escape_latex(self, value: str) -> str:
|
|
258
|
+
"""Escape helper that honours the formatter legacy accent setting."""
|
|
259
|
+
return escape_latex_chars(value, legacy_accents=self.legacy_latex_accents)
|
|
260
|
+
|
|
261
|
+
def svg(self, svg: str | Path) -> str:
|
|
262
|
+
"""Render an SVG image by converting it to PDF first."""
|
|
263
|
+
from ..transformers import svg2pdf
|
|
264
|
+
|
|
265
|
+
pdfpath = svg2pdf(svg, self.output_path) # type: ignore[attr-defined]
|
|
266
|
+
return f"\\includegraphics[width=1em]{{{pdfpath}}}"
|
|
267
|
+
|
|
268
|
+
def get_cover(self, name: str, **kwargs: Any) -> str:
|
|
269
|
+
"""Render a named cover template populated with book metadata."""
|
|
270
|
+
template = self._get_template(f"cover/{name}")
|
|
271
|
+
return template.render(
|
|
272
|
+
title=self.config.title, # type: ignore[attr-defined]
|
|
273
|
+
author=self.config.author, # type: ignore[attr-defined]
|
|
274
|
+
subtitle=self.config.subtitle, # type: ignore[attr-defined]
|
|
275
|
+
email=self.config.email, # type: ignore[attr-defined]
|
|
276
|
+
year=self.config.year, # type: ignore[attr-defined]
|
|
277
|
+
**self.config.cover.model_dump(), # type: ignore[attr-defined]
|
|
278
|
+
**kwargs,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def override_template(self, name: str, source: str | Path) -> None:
|
|
282
|
+
"""Override a built-in template snippet using an external payload."""
|
|
283
|
+
if isinstance(source, Path):
|
|
284
|
+
template_source = source.read_text(encoding="utf-8")
|
|
285
|
+
template_name = source.as_posix()
|
|
286
|
+
else:
|
|
287
|
+
template_source = source
|
|
288
|
+
template_name = name
|
|
289
|
+
|
|
290
|
+
template = self.env.from_string(template_source)
|
|
291
|
+
template.name = template_name
|
|
292
|
+
normalised = self._normalise_key(name)
|
|
293
|
+
self.templates[normalised] = template
|
|
294
|
+
self._template_names[normalised] = template_name
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
__all__ = ["LaTeXFormatter", "optimize_list"]
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Helpers for configuring latexmk commands and RC files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import shlex
|
|
8
|
+
import shutil
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from texsmith.adapters.latex import pyxindy
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(slots=True)
|
|
15
|
+
class LatexmkEngine:
|
|
16
|
+
"""Normalised latexmk engine invocation."""
|
|
17
|
+
|
|
18
|
+
command: list[str]
|
|
19
|
+
pdf_mode: int
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _perl_escape(value: str) -> str:
|
|
23
|
+
return value.replace("\\", "\\\\").replace("'", "\\'")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _pdf_mode_from_command(command: str) -> int:
|
|
27
|
+
lowered = Path(command).name.lower()
|
|
28
|
+
if lowered.endswith("xelatex"):
|
|
29
|
+
return 5
|
|
30
|
+
if lowered.endswith("lualatex"):
|
|
31
|
+
return 4
|
|
32
|
+
return 1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def normalise_engine_command(engine: str | None, *, shell_escape: bool) -> LatexmkEngine:
|
|
36
|
+
"""Return latexmk engine tokens and pdf mode derived from the provided string."""
|
|
37
|
+
tokens = shlex.split(engine.strip()) if engine else []
|
|
38
|
+
if not tokens:
|
|
39
|
+
tokens = ["pdflatex"]
|
|
40
|
+
|
|
41
|
+
if shell_escape and not any(token in {"-shell-escape", "--shell-escape"} for token in tokens):
|
|
42
|
+
tokens.append("--shell-escape")
|
|
43
|
+
|
|
44
|
+
pdf_mode = _pdf_mode_from_command(tokens[0])
|
|
45
|
+
return LatexmkEngine(command=tokens, pdf_mode=pdf_mode)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def latexmk_pdf_flag(pdf_mode: int) -> str:
|
|
49
|
+
"""Return the latexmk flag required for the requested pdf mode."""
|
|
50
|
+
if pdf_mode == 4:
|
|
51
|
+
return "-pdflua"
|
|
52
|
+
if pdf_mode == 5:
|
|
53
|
+
return "-pdfxe"
|
|
54
|
+
return "-pdf"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def build_engine_command(engine: LatexmkEngine) -> str:
|
|
58
|
+
"""Construct the pdflatex command string used by latexmk."""
|
|
59
|
+
return shlex.join([*engine.command, "%O", "%S"])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def normalise_index_engine(candidate: Any) -> str:
|
|
63
|
+
"""Resolve the effective index engine from template context or availability."""
|
|
64
|
+
if isinstance(candidate, str):
|
|
65
|
+
stripped = candidate.strip().lower()
|
|
66
|
+
if stripped in {"pyxindy", "texindy", "makeindex"}:
|
|
67
|
+
if stripped == "pyxindy" and not pyxindy.is_available():
|
|
68
|
+
stripped = "texindy" if shutil.which("texindy") else "makeindex"
|
|
69
|
+
return stripped
|
|
70
|
+
if pyxindy.is_available():
|
|
71
|
+
return "pyxindy"
|
|
72
|
+
return "texindy" if shutil.which("texindy") else "makeindex"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _engine_variable(pdf_mode: int) -> str:
|
|
76
|
+
if pdf_mode == 5:
|
|
77
|
+
return "xelatex"
|
|
78
|
+
if pdf_mode == 4:
|
|
79
|
+
return "lualatex"
|
|
80
|
+
return "pdflatex"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def build_latexmkrc_content(
|
|
84
|
+
*,
|
|
85
|
+
root_filename: str,
|
|
86
|
+
engine: str | None,
|
|
87
|
+
requires_shell_escape: bool,
|
|
88
|
+
bibliography: bool,
|
|
89
|
+
index_engine: str | None = None,
|
|
90
|
+
has_index: bool = False,
|
|
91
|
+
has_glossary: bool = False,
|
|
92
|
+
) -> str:
|
|
93
|
+
"""Produce a latexmkrc payload matching the selected template features."""
|
|
94
|
+
engine_config = normalise_engine_command(engine, shell_escape=requires_shell_escape)
|
|
95
|
+
command_string = build_engine_command(engine_config)
|
|
96
|
+
engine_var = _engine_variable(engine_config.pdf_mode)
|
|
97
|
+
safe_root = _perl_escape(root_filename or "texsmith-output")
|
|
98
|
+
|
|
99
|
+
lines = [
|
|
100
|
+
"# Auto-generated by texsmith\n",
|
|
101
|
+
f"$root_filename = '{safe_root}';\n",
|
|
102
|
+
f"$pdf_mode = {engine_config.pdf_mode};\n",
|
|
103
|
+
f"${engine_var} = '{_perl_escape(command_string)}';\n",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
if bibliography:
|
|
107
|
+
lines.extend(
|
|
108
|
+
[
|
|
109
|
+
"$bibtex_use = 2;\n",
|
|
110
|
+
"$biber = 'biber %O %B';\n",
|
|
111
|
+
"$clean_ext .= ' %R.run.xml %R.blg';\n",
|
|
112
|
+
]
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if has_index:
|
|
116
|
+
index_var = normalise_index_engine(index_engine)
|
|
117
|
+
if index_var == "pyxindy":
|
|
118
|
+
makeindex = pyxindy.latexmk_makeindex_command()
|
|
119
|
+
else:
|
|
120
|
+
makeindex = "texindy %O -o %D %S" if index_var == "texindy" else "makeindex %O -o %D %S"
|
|
121
|
+
lines.append(f"$makeindex = '{_perl_escape(makeindex)}';\n")
|
|
122
|
+
|
|
123
|
+
if has_glossary:
|
|
124
|
+
glossaries_cmd = pyxindy.latexmk_makeglossaries_command()
|
|
125
|
+
lines.extend(
|
|
126
|
+
[
|
|
127
|
+
"add_cus_dep('glo', 'gls', 0, 'run_makeglossaries');\n",
|
|
128
|
+
"add_cus_dep('acn', 'acr', 0, 'run_makeglossaries');\n",
|
|
129
|
+
"\n",
|
|
130
|
+
"sub run_makeglossaries {\n",
|
|
131
|
+
" my ($base) = @_;\n",
|
|
132
|
+
f' my $cmd = "{_perl_escape(glossaries_cmd)}";\n',
|
|
133
|
+
" return system($cmd);\n",
|
|
134
|
+
"}\n",
|
|
135
|
+
]
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return "".join(lines)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
__all__ = [
|
|
142
|
+
"LatexmkEngine",
|
|
143
|
+
"build_engine_command",
|
|
144
|
+
"build_latexmkrc_content",
|
|
145
|
+
"latexmk_pdf_flag",
|
|
146
|
+
"normalise_engine_command",
|
|
147
|
+
"normalise_index_engine",
|
|
148
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
\gls{\VAR{text}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
\begin{goodbox}\uline{\VAR{text}}\end{goodbox}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
\begin{goodbox}\uline{\VAR{text}}\end{goodbox}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
\cite{\VAR{key}}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
%\leavevmode % Required to avoid a bug in the tcolorbox package
|
|
2
|
+
\begin{code}{\VAR{language|default('text')}}{\BLOCK{if filename}\VAR{filename}\BLOCK{endif}}{
|
|
3
|
+
\BLOCK{- if baselinestretch}baselinestretch=\VAR{baselinestretch}, \BLOCK{endif}
|
|
4
|
+
\BLOCK{- if linenos}linenos, \BLOCK{endif -}
|
|
5
|
+
\BLOCK{- if highlight }, highlightlines={\VAR{','.join(highlight)}}\BLOCK{endif -}
|
|
6
|
+
}
|
|
7
|
+
\VAR{code}\end{code}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
\begin{code}{\VAR{language|default('text')}}{\BLOCK{if filename}\VAR{filename}\BLOCK{endif}}{
|
|
2
|
+
\BLOCK{- if linenos},numbers=left, firstnumber=1, stepnumber=1\BLOCK{endif -}
|
|
3
|
+
\BLOCK{- if highlight }, highlightlines={\VAR{','.join(highlight)}}\BLOCK{endif -}
|
|
4
|
+
}
|
|
5
|
+
\VAR{code}\end{code}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
\begin{code}{\VAR{language|default('text')}}{\BLOCK{if filename}\VAR{filename}\BLOCK{endif}}{
|
|
2
|
+
\BLOCK{- if baselinestretch}baselinestretch=\VAR{baselinestretch}, \BLOCK{endif}
|
|
3
|
+
}
|
|
4
|
+
\begin{Verbatim}[
|
|
5
|
+
breaklines,
|
|
6
|
+
breakanywhere,
|
|
7
|
+
commandchars=\\\{\},
|
|
8
|
+
\BLOCK{- if linenos}numbers=left, firstnumber=1, stepnumber=1,\BLOCK{endif -}
|
|
9
|
+
\BLOCK{- if highlight }highlightlines={\VAR{','.join(highlight)}}\BLOCK{endif}
|
|
10
|
+
]
|
|
11
|
+
\VAR{code}\end{Verbatim}
|
|
12
|
+
\end{code}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
\mintinline[breaklines=true]{\BLOCK{if language}\VAR{language}\BLOCK{else}text\BLOCK{endif}}\VAR{delimiter}\VAR{text}\VAR{delimiter}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
\texttt{\VAR{text}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
\begin{commentbox}/* \VAR{text} */\end{commentbox}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
\xout{\VAR{text}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
\xout{\VAR{text}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
\enquote{\VAR{text}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
\epigraph{\itshape ``\VAR{text}''}{--- \textit{\VAR{source}}}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
\begin{figure}[H]
|
|
2
|
+
\centering
|
|
3
|
+
\BLOCK{-if label}\label{\VAR{label}}\BLOCK{endif}
|
|
4
|
+
\BLOCK{if width}\BLOCK{set width_value = width.rstrip('%') if width.endswith('%') else width}\BLOCK{endif}
|
|
5
|
+
\BLOCK{if adjustbox}
|
|
6
|
+
\ifdefined\adjustbox
|
|
7
|
+
\BLOCK{if link}\href{\VAR{link}}{%
|
|
8
|
+
\adjustbox{max width=\textwidth}{%
|
|
9
|
+
\includegraphics[width=\BLOCK{if width}\BLOCK{if width.endswith('%')}\VAR{(width_value|float) / 100}\linewidth\BLOCK{else}\VAR{width}\BLOCK{endif}\BLOCK{else}\linewidth\BLOCK{endif}]{\VAR{path}}
|
|
10
|
+
}
|
|
11
|
+
}\BLOCK{else}
|
|
12
|
+
\adjustbox{max width=\textwidth}{%
|
|
13
|
+
\includegraphics[width=\BLOCK{if width}\BLOCK{if width.endswith('%')}\VAR{(width_value|float) / 100}\linewidth\BLOCK{else}\VAR{width}\BLOCK{endif}\BLOCK{else}\linewidth\BLOCK{endif}]{\VAR{path}}
|
|
14
|
+
}
|
|
15
|
+
\BLOCK{endif}
|
|
16
|
+
\else
|
|
17
|
+
\BLOCK{if link}\href{\VAR{link}}{%
|
|
18
|
+
\includegraphics[width=\BLOCK{if width}\BLOCK{if width.endswith('%')}\VAR{(width_value|float) / 100}\linewidth\BLOCK{else}\VAR{width}\BLOCK{endif}\BLOCK{else}\linewidth\BLOCK{endif}]{\VAR{path}}
|
|
19
|
+
}\BLOCK{else}
|
|
20
|
+
\includegraphics[width=\BLOCK{if width}\BLOCK{if width.endswith('%')}\VAR{(width_value|float) / 100}\linewidth\BLOCK{else}\VAR{width}\BLOCK{endif}\BLOCK{else}\linewidth\BLOCK{endif}]{\VAR{path}}
|
|
21
|
+
\BLOCK{endif}
|
|
22
|
+
\fi
|
|
23
|
+
\BLOCK{else}
|
|
24
|
+
\BLOCK{if link}\href{\VAR{link}}{%
|
|
25
|
+
\includegraphics[width=\BLOCK{if width}\BLOCK{if width.endswith('%')}\VAR{(width_value|float) / 100}\linewidth\BLOCK{else}\VAR{width}\BLOCK{endif}\BLOCK{else}\linewidth\BLOCK{endif}]{\VAR{path}}
|
|
26
|
+
}\BLOCK{else}
|
|
27
|
+
\includegraphics[width=\BLOCK{if width}\BLOCK{if width.endswith('%')}\VAR{(width_value|float) / 100}\linewidth\BLOCK{else}\VAR{width}\BLOCK{endif}\BLOCK{else}\linewidth\BLOCK{endif}]{\VAR{path}}
|
|
28
|
+
\BLOCK{endif}
|
|
29
|
+
\BLOCK{endif}
|
|
30
|
+
\BLOCK{-if caption}
|
|
31
|
+
\caption\BLOCK{if shortcaption}[\VAR{shortcaption}]\BLOCK{endif}{\VAR{caption}}
|
|
32
|
+
\BLOCK{-endif}
|
|
33
|
+
\end{figure}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
\begin{center}
|
|
2
|
+
\BLOCK{-if label}\label{\VAR{label}}
|
|
3
|
+
\BLOCK{endif-}
|
|
4
|
+
\BLOCK{if width}\BLOCK{set width_value = width.rstrip('%') if width.endswith('%') else width}\BLOCK{endif}
|
|
5
|
+
\BLOCK{-if caption}\captionsetup{type=figure}\BLOCK{endif}
|
|
6
|
+
\BLOCK{if link}\href{\VAR{link}}{%
|
|
7
|
+
\includegraphics[width=\BLOCK{if width}\BLOCK{if width.endswith('%')}\VAR{(width_value|float) / 100}\linewidth\BLOCK{else}\VAR{width}\BLOCK{endif}\BLOCK{else}\linewidth\BLOCK{endif}]{\VAR{path}}
|
|
8
|
+
}
|
|
9
|
+
\BLOCK{else}
|
|
10
|
+
\includegraphics[width=\BLOCK{if width}\BLOCK{if width.endswith('%')}\VAR{(width_value|float) / 100}\linewidth\BLOCK{else}\VAR{width}\BLOCK{endif}\BLOCK{else}\linewidth\BLOCK{endif}]{\VAR{path}}
|
|
11
|
+
\BLOCK{endif}
|
|
12
|
+
\BLOCK{-if caption}
|
|
13
|
+
\captionof{figure}\BLOCK{if shortcaption}[\VAR{shortcaption}]\BLOCK{endif}{\VAR{caption}}
|
|
14
|
+
\BLOCK{-endif}
|
|
15
|
+
\end{center}
|