vectex 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.
- vectex-0.1.0/.gitignore +12 -0
- vectex-0.1.0/AGENTS.md +53 -0
- vectex-0.1.0/CHANGELOG.md +48 -0
- vectex-0.1.0/LICENSE +21 -0
- vectex-0.1.0/PKG-INFO +332 -0
- vectex-0.1.0/README.md +299 -0
- vectex-0.1.0/docs/development.md +60 -0
- vectex-0.1.0/docs/fragments.md +103 -0
- vectex-0.1.0/docs/index.md +59 -0
- vectex-0.1.0/docs/rendering.md +193 -0
- vectex-0.1.0/pyproject.toml +96 -0
- vectex-0.1.0/src/vectex/__init__.py +63 -0
- vectex-0.1.0/src/vectex/adapters.py +39 -0
- vectex-0.1.0/src/vectex/api.py +578 -0
- vectex-0.1.0/src/vectex/cache.py +106 -0
- vectex-0.1.0/src/vectex/cli.py +213 -0
- vectex-0.1.0/src/vectex/compiler.py +314 -0
- vectex-0.1.0/src/vectex/converter.py +121 -0
- vectex-0.1.0/src/vectex/exceptions.py +76 -0
- vectex-0.1.0/src/vectex/fragment.py +91 -0
- vectex-0.1.0/src/vectex/normalizer.py +526 -0
- vectex-0.1.0/src/vectex/process.py +93 -0
- vectex-0.1.0/tests/conftest.py +20 -0
- vectex-0.1.0/tests/fixtures/complex.svg +22 -0
- vectex-0.1.0/tests/fixtures/simple.svg +3 -0
- vectex-0.1.0/tests/test_adapters.py +32 -0
- vectex-0.1.0/tests/test_api.py +389 -0
- vectex-0.1.0/tests/test_cli.py +259 -0
- vectex-0.1.0/tests/test_integration.py +110 -0
- vectex-0.1.0/tests/test_normalizer.py +265 -0
- vectex-0.1.0/tests/test_package_metadata.py +16 -0
- vectex-0.1.0/tests/test_process.py +213 -0
- vectex-0.1.0/zensical.toml +16 -0
vectex-0.1.0/.gitignore
ADDED
vectex-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Vectex contributor guide
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
`vectex` compiles LaTeX and normalizes converter output into one
|
|
6
|
+
self-contained SVG `<g>` fragment. It does not insert fragments into destination
|
|
7
|
+
documents, implement an SVG editor, or provide a GUI.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
- Keep a single canonical `lxml` pipeline: compiler -> converter -> normalizer
|
|
12
|
+
-> `VectexFragment` -> optional adapters.
|
|
13
|
+
- Keep external process execution in `compiler.py` and `converter.py`; never use
|
|
14
|
+
`shell=True`.
|
|
15
|
+
- Keep SVG trust-boundary checks and ID/reference rewriting in `normalizer.py`.
|
|
16
|
+
- Preserve TexText interoperability on the outer group. Its namespaced `text`,
|
|
17
|
+
`preamble`, and `scale` attributes are a compatibility contract in addition
|
|
18
|
+
to Vectex's canonical `<metadata>` record.
|
|
19
|
+
- Treat the serialized normalized group owned by `VectexFragment` as immutable.
|
|
20
|
+
Return copies or new wrapper objects from public conversion methods.
|
|
21
|
+
- Preserve optional dependency boundaries. Import `svg.py` (`import svg`) and
|
|
22
|
+
`drawsvg` only inside their adapters.
|
|
23
|
+
- Before 1.0, make API changes directly: update consumers, tests, and docs, and
|
|
24
|
+
do not add compatibility aliases, deprecated wrappers, or legacy option shims.
|
|
25
|
+
|
|
26
|
+
## Development
|
|
27
|
+
|
|
28
|
+
- Supported Python: 3.11 and newer.
|
|
29
|
+
- Install a complete development environment with `uv sync --all-extras`.
|
|
30
|
+
- Run `uv run ruff format --check .`, `uv run ruff check .`,
|
|
31
|
+
`uv run mypy src`, and `uv run pytest` before reporting a change complete.
|
|
32
|
+
- Unit tests must not require TeX or dvisvgm. Real-tool integration
|
|
33
|
+
tests must be explicitly enabled and skip cleanly when tools are absent.
|
|
34
|
+
- Add a regression fixture or mocked-process test for every normalization,
|
|
35
|
+
security, or command-construction bug.
|
|
36
|
+
- Keep `docs/` in sync with user-visible code changes: update affected guides,
|
|
37
|
+
reference material, and examples in the same change. Build the site with
|
|
38
|
+
`uv run zensical build` before reporting documentation changes complete.
|
|
39
|
+
- Keep `CHANGELOG.md` current for notable user-facing changes. Follow the
|
|
40
|
+
Keep a Changelog 1.1.0 format: add concise, human-readable entries under
|
|
41
|
+
`Unreleased`, grouped only as Added, Changed, Deprecated, Removed, Fixed, or
|
|
42
|
+
Security; move them into a dated release section when a version is released.
|
|
43
|
+
|
|
44
|
+
## Packaging and releases
|
|
45
|
+
|
|
46
|
+
- `pyproject.toml` is canonical for Python metadata and pip builds.
|
|
47
|
+
- Build both wheel and sdist for package qualification.
|
|
48
|
+
- Do not commit, tag, upload, or publish unless the user explicitly requests it.
|
|
49
|
+
- The project is distributed under the MIT License; keep the license metadata
|
|
50
|
+
and `LICENSE` file in sync for public releases.
|
|
51
|
+
|
|
52
|
+
Keep maintained behavior and examples in `README.md` and `docs/`; completed
|
|
53
|
+
planning notes are not part of the long-lived project documentation.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-08-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- The `vectex` CLI renders a TeX document body to stdout as an SVG `<g>`
|
|
15
|
+
fragment; `--as-doc` selects a standalone SVG document, and `--output PATH`
|
|
16
|
+
writes either selected form to a file. It provides `--help`, `--version`, and
|
|
17
|
+
options for file or standard-input source, inline or file-backed preambles,
|
|
18
|
+
the render cache, engine, size, scale, timeout, ID prefix, and repeatable
|
|
19
|
+
`--executable NAME=PATH` tool overrides.
|
|
20
|
+
- `RenderItem` as a single record for everything that shapes a fragment.
|
|
21
|
+
`render()` accepts one, and `render_many()` groups items that share a
|
|
22
|
+
compilation, so a batch keeps one invocation when only sizes differ and
|
|
23
|
+
splits when a preamble, engine, or converter does.
|
|
24
|
+
- `VectexFragment.to_svg_document()` and `write_svg_document()` for a
|
|
25
|
+
standalone, backend-free SVG document; `to_svg()` remains the raw `<g>`
|
|
26
|
+
fragment.
|
|
27
|
+
- `refresh=True` on `render()` and `render_many()` to recompile and replace one
|
|
28
|
+
cache record without clearing the rest.
|
|
29
|
+
- Cache keys that identify the installed tools, so records compiled before a
|
|
30
|
+
TeX or dvisvgm upgrade are not served afterwards. Components may declare an
|
|
31
|
+
`identity()` of their own.
|
|
32
|
+
- A placement recipe in the fragments guide covering anchors and baselines.
|
|
33
|
+
- MIT licensing for the project and Python distribution.
|
|
34
|
+
- User documentation built with Zensical.
|
|
35
|
+
- Cropped TeX fragments with measured inline baselines and direct `size_pt`
|
|
36
|
+
sizing.
|
|
37
|
+
- Safe package-name convenience through `extra_packages=(...)` and repeatable
|
|
38
|
+
CLI `--extra-package NAME`, without requiring a custom preamble. A nonempty
|
|
39
|
+
`preamble` is the complete preamble, must contain `\documentclass`, and is
|
|
40
|
+
mutually exclusive with `extra_packages`.
|
|
41
|
+
- Deterministic SVG namespaces with an opt-in unique-ID mode.
|
|
42
|
+
- Checksummed persistent caching and one-process `render_many` batches.
|
|
43
|
+
- Inherited label colour for easy styling after insertion.
|
|
44
|
+
- Support for Python 3.11 and newer.
|
|
45
|
+
- One canonical package version shared by `vectex.__version__`, the CLI, and
|
|
46
|
+
`pyproject.toml` metadata.
|
|
47
|
+
- Literal TeX document-body input, as in TexText, using normal delimiters and
|
|
48
|
+
environments without automatic math wrapping.
|
vectex-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrea Maiani
|
|
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.
|
vectex-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: vectex
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Render LaTeX as portable, editable SVG group fragments
|
|
5
|
+
Project-URL: Documentation, https://maiani.github.io/vectex/
|
|
6
|
+
Project-URL: Issues, https://github.com/maiani/vectex/issues
|
|
7
|
+
Project-URL: Source, https://github.com/maiani/vectex
|
|
8
|
+
Author-email: Andrea Maiani <dev@andreamaiani.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: latex,svg,vector-graphics
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: lxml>=5.0
|
|
24
|
+
Requires-Dist: typer>=0.12
|
|
25
|
+
Provides-Extra: all
|
|
26
|
+
Requires-Dist: drawsvg>=2.0; extra == 'all'
|
|
27
|
+
Requires-Dist: svg-py>=1.5; extra == 'all'
|
|
28
|
+
Provides-Extra: drawsvg
|
|
29
|
+
Requires-Dist: drawsvg>=2.0; extra == 'drawsvg'
|
|
30
|
+
Provides-Extra: svg-py
|
|
31
|
+
Requires-Dist: svg-py>=1.5; extra == 'svg-py'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# vectex
|
|
35
|
+
|
|
36
|
+
[](https://github.com/maiani/vectex/actions/workflows/test.yml)
|
|
37
|
+
[](https://pypi.org/project/vectex/)
|
|
38
|
+
[](https://www.python.org/)
|
|
39
|
+
[](LICENSE)
|
|
40
|
+
|
|
41
|
+
`vectex` compiles LaTeX source and returns one portable SVG
|
|
42
|
+
`<g>` fragment. It is a library-level reimplementation of the rendering and
|
|
43
|
+
normalization boundary behind TexText: it does not require Inkscape or access to
|
|
44
|
+
the destination SVG document.
|
|
45
|
+
|
|
46
|
+
The returned group is also recognizable as an editable TexText object after a
|
|
47
|
+
caller inserts it into an Inkscape SVG. Vectex stores both TexText-compatible
|
|
48
|
+
attributes and a richer, versioned metadata record.
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
The required runtime is Python 3.11 or newer; installation includes `lxml` and
|
|
53
|
+
the command-line dependency `typer`:
|
|
54
|
+
|
|
55
|
+
```console
|
|
56
|
+
python -m pip install vectex
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Install an optional object-model adapter with one of:
|
|
60
|
+
|
|
61
|
+
```console
|
|
62
|
+
python -m pip install 'vectex[svg-py]'
|
|
63
|
+
python -m pip install 'vectex[drawsvg]'
|
|
64
|
+
python -m pip install 'vectex[all]'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The distribution names and imports are `svg.py` / `import svg` and `drawsvg` /
|
|
68
|
+
`import drawsvg`.
|
|
69
|
+
|
|
70
|
+
## Command line
|
|
71
|
+
|
|
72
|
+
The installed `vectex` command renders a TeX document body to a portable SVG
|
|
73
|
+
fragment on standard output:
|
|
74
|
+
|
|
75
|
+
```console
|
|
76
|
+
vectex '$E = mc^2$'
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Pass `--as-doc` to emit a complete, openable SVG document rather than a
|
|
80
|
+
fragment. Without `-o`, either form is written to standard output:
|
|
81
|
+
|
|
82
|
+
```console
|
|
83
|
+
vectex '$E = mc^2$' --as-doc > einstein.svg
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Use `--output` (or `-o`) to write the selected form to a file:
|
|
87
|
+
|
|
88
|
+
```console
|
|
89
|
+
vectex '$E = mc^2$' -o einstein-fragment.svg
|
|
90
|
+
vectex '$E = mc^2$' --as-doc -o einstein.svg
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For multiline input, read UTF-8 source from a file or standard input:
|
|
94
|
+
|
|
95
|
+
```console
|
|
96
|
+
vectex --input equation.tex --as-doc -o equation.svg
|
|
97
|
+
printf '%s\n' '$E = mc^2$' | vectex - --as-doc > einstein.svg
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`--preamble-file preamble.tex` reads a complete preamble from a file and also
|
|
101
|
+
records its absolute path for later TexText editing. It is an alternative to
|
|
102
|
+
inline `--preamble`. For ordinary package loading, repeat
|
|
103
|
+
`--extra-package NAME` instead of writing a preamble. Reuse persistent render
|
|
104
|
+
records with `--cache-dir PATH`; add `--refresh` to recompile and replace the
|
|
105
|
+
selected record.
|
|
106
|
+
|
|
107
|
+
Use `--executable NAME=PATH` to override a tool location; repeat it for both
|
|
108
|
+
the engine and `dvisvgm` when needed. Run `vectex --help` for the complete
|
|
109
|
+
option list; `vectex --version` reports the installed version.
|
|
110
|
+
|
|
111
|
+
## Minimal use
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
import vectex
|
|
115
|
+
|
|
116
|
+
fragment = vectex.render(
|
|
117
|
+
r"mass $m$ and energy $E = mc^2$",
|
|
118
|
+
engine="pdflatex",
|
|
119
|
+
)
|
|
120
|
+
expression = vectex.render(r"$E = mc^2$")
|
|
121
|
+
vector = vectex.render(r"$\bm{n}$", extra_packages=("bm",))
|
|
122
|
+
|
|
123
|
+
svg_text = fragment.to_svg()
|
|
124
|
+
lxml_group = fragment.to_lxml()
|
|
125
|
+
document = fragment.to_svg_document() # complete file-ready SVG
|
|
126
|
+
fragment.write_svg_document("label.svg") # same document, written to disk
|
|
127
|
+
|
|
128
|
+
print(fragment.width, fragment.height, fragment.view_box)
|
|
129
|
+
print(fragment.source, fragment.engine, fragment.metadata)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
TeX input is always a literal document body, the same convention TexText uses:
|
|
133
|
+
`$...$` marks inline mathematics, `\[...\]` marks display mathematics, and
|
|
134
|
+
everything else is prose. Complete environments such as `align*` can be used
|
|
135
|
+
directly; inner environments need their normal TeX context. `amsmath` is loaded
|
|
136
|
+
by default, so `\text{...}` works in math expressions.
|
|
137
|
+
|
|
138
|
+
The default TeX template uses a zero-border `standalone` page cropped to each
|
|
139
|
+
fragment and loads `amsmath`. A nonempty `preamble` replaces that complete
|
|
140
|
+
preamble and must contain `\documentclass`, so
|
|
141
|
+
`preamble=r"\documentclass{article}"` restores full-page geometry. Use
|
|
142
|
+
`extra_packages=("bm",)` when only additional `\usepackage` declarations are
|
|
143
|
+
needed. `preamble` and `extra_packages` are mutually exclusive.
|
|
144
|
+
|
|
145
|
+
Use either `size_pt=7` to express a desired font size or the lower-level
|
|
146
|
+
`scale=0.7`; passing both is an error. TeX sizing is resolved against the
|
|
147
|
+
selected document class (10 pt by default).
|
|
148
|
+
|
|
149
|
+
Every call uses a fresh temporary directory and runs two stages:
|
|
150
|
+
|
|
151
|
+
```text
|
|
152
|
+
source -> pdflatex/xelatex/lualatex -> PDF -> dvisvgm -> SVG -> lxml -> <g>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Embedding and adapters
|
|
156
|
+
|
|
157
|
+
`to_lxml()` returns a fresh element on every call, so appending or editing it
|
|
158
|
+
cannot mutate the fragment's canonical serialization:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from lxml import etree
|
|
162
|
+
|
|
163
|
+
document = etree.fromstring('<svg xmlns="http://www.w3.org/2000/svg"/>')
|
|
164
|
+
document.append(fragment.to_lxml())
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The optional adapters deliberately preserve the complete normalized XML rather
|
|
168
|
+
than translating arbitrary SVG into a smaller object model:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
import svg
|
|
172
|
+
import drawsvg
|
|
173
|
+
|
|
174
|
+
svg_py_group = fragment.to_svg_py()
|
|
175
|
+
svg_py_document = svg.SVG(
|
|
176
|
+
width=fragment.width,
|
|
177
|
+
height=fragment.height,
|
|
178
|
+
elements=[svg_py_group],
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
drawing = drawsvg.Drawing(fragment.width, fragment.height)
|
|
182
|
+
drawing.append(fragment.to_drawsvg())
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## TexText editing in Inkscape
|
|
186
|
+
|
|
187
|
+
TexText detects editable nodes from attributes in its namespace on the selected
|
|
188
|
+
outer `<g>`. Vectex emits the current compatibility fields: encoded source,
|
|
189
|
+
compiler, PDF-to-SVG converter marker, preamble-file path, scale, alignment,
|
|
190
|
+
version, and transform Jacobian.
|
|
191
|
+
|
|
192
|
+
Insert the outer group itself into an SVG and select that whole group before
|
|
193
|
+
opening TexText. Selecting only a nested path or subgroup is intentionally
|
|
194
|
+
rejected by TexText.
|
|
195
|
+
|
|
196
|
+
The stored TexText `text` is the source itself, since both tools treat it as a
|
|
197
|
+
document body. The same `$...$`, `\[...\]`, and environment syntax therefore
|
|
198
|
+
recompiles without translation when the object is edited in TexText.
|
|
199
|
+
|
|
200
|
+
TexText represents its preamble as a file path, while Vectex accepts preamble
|
|
201
|
+
content. If re-editing must use the same custom preamble, pass both values:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
fragment = vectex.render(
|
|
205
|
+
r"$\operatorname{rank}(A)$",
|
|
206
|
+
preamble="\\documentclass{standalone}\n\\usepackage{amsmath}",
|
|
207
|
+
textext_preamble_file="/absolute/shared/preamble.tex",
|
|
208
|
+
)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The path must remain accessible to TexText on the editing machine. The preamble
|
|
212
|
+
content itself is retained in Vectex metadata, but TexText's compatibility field
|
|
213
|
+
can carry only its path. Pass `textext_compatible=False` to omit all TexText
|
|
214
|
+
attributes.
|
|
215
|
+
|
|
216
|
+
## Executable discovery and configuration
|
|
217
|
+
|
|
218
|
+
Built-in components use `shutil.which` to resolve `pdflatex`, `xelatex`,
|
|
219
|
+
`lualatex`, and `dvisvgm`. Exact overrides make discovery explicit and
|
|
220
|
+
testable:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
fragment = vectex.render(
|
|
224
|
+
"$x+y$",
|
|
225
|
+
executable_overrides={
|
|
226
|
+
"pdflatex": "/opt/texlive/bin/pdflatex",
|
|
227
|
+
"dvisvgm": "/opt/texlive/bin/dvisvgm",
|
|
228
|
+
},
|
|
229
|
+
timeout=20,
|
|
230
|
+
compiler_args=("--synctex=0",),
|
|
231
|
+
converter_args=("--precision=6",),
|
|
232
|
+
)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Argument options are sequences, never shell command strings. Vectex never uses
|
|
236
|
+
`shell=True`. Nonzero exits and timeouts raise structured `CompilationError` or
|
|
237
|
+
`ConversionError` instances with argv, return code, stdout, and stderr.
|
|
238
|
+
|
|
239
|
+
Applications may implement the small `Compiler` and `Converter` protocols and
|
|
240
|
+
pass component objects instead of built-in names.
|
|
241
|
+
|
|
242
|
+
## Batch rendering and disk cache
|
|
243
|
+
|
|
244
|
+
`render_many([a, b, ...])` shares one compiler and one dvisvgm invocation while
|
|
245
|
+
preserving each expression's crop and measurable baseline. A source may also be
|
|
246
|
+
a `RenderItem` carrying any option that shapes its fragment; those left as
|
|
247
|
+
`None` take the batch value. Items that share a compilation are grouped and
|
|
248
|
+
rendered together, so a batch of labels differing only in size still costs one
|
|
249
|
+
invocation, while an item with its own preamble or engine forms its own group.
|
|
250
|
+
Fragments are returned in input order, and `render()` accepts a `RenderItem`
|
|
251
|
+
as well. `cache_dir`, `refresh`, and `unique_ids` describe how a call runs
|
|
252
|
+
rather than what it produces, and stay on the call.
|
|
253
|
+
|
|
254
|
+
The optional persistent cache is enabled with `cache_dir=` or
|
|
255
|
+
`VECTEX_CACHE_DIR`. Entries are keyed by all output-driving options and by the
|
|
256
|
+
identity of the installed tools -- built-in components contribute the resolved
|
|
257
|
+
path and reported version of their executable, so records are not reused across
|
|
258
|
+
a TeX or dvisvgm upgrade, and a component object may declare its own
|
|
259
|
+
`identity()`. Entries are checksummed and written atomically; corrupt entries
|
|
260
|
+
are treated as misses. `refresh=True` recompiles and replaces one record, and
|
|
261
|
+
`vectex.clear_cache(directory)` removes only Vectex's namespaced records and
|
|
262
|
+
returns the number removed.
|
|
263
|
+
|
|
264
|
+
## Fragment guarantees
|
|
265
|
+
|
|
266
|
+
A successful render returns exactly one SVG `<g>` root with:
|
|
267
|
+
|
|
268
|
+
- copied converter definitions and visible elements;
|
|
269
|
+
- a deterministic input-derived ID prefix and rewritten `href`, `xlink:href`, and
|
|
270
|
+
`url(#...)` references, including inline style attributes;
|
|
271
|
+
- the source viewport represented by an inner matrix transform;
|
|
272
|
+
- normalized width, height, view box, scale, and measurable baseline properties;
|
|
273
|
+
- inheritable default black glyph fills, so `fill` on an enclosing SVG group
|
|
274
|
+
recolours a label, while explicitly authored non-black colours are preserved;
|
|
275
|
+
- deterministic repeated serialization of that fragment;
|
|
276
|
+
- a Vectex `<metadata>` child containing format version, original source,
|
|
277
|
+
engine, converter, geometry, preamble/options, and adapter-independent data;
|
|
278
|
+
- TexText-recognized edit attributes unless explicitly disabled.
|
|
279
|
+
|
|
280
|
+
Identical render inputs serialize identically, while changed output-driving
|
|
281
|
+
inputs receive a different namespace. Use `unique_ids=True` when embedding the
|
|
282
|
+
same render more than once in one SVG, or supply an explicit `id_prefix`.
|
|
283
|
+
`render_many(..., id_prefix="labels")` suffixes it by input position.
|
|
284
|
+
|
|
285
|
+
The outer group is named from that prefix: `id_prefix="einstein"` gives
|
|
286
|
+
`id="einstein-root"`, while rewritten definitions use IDs such as
|
|
287
|
+
`einstein-0`. The CLI exposes this as `--id-prefix einstein`.
|
|
288
|
+
|
|
289
|
+
## Security and trust assumptions
|
|
290
|
+
|
|
291
|
+
The XML parser disables DTD loading, entity resolution, network access, recovery,
|
|
292
|
+
comments, and processing instructions. Normalization rejects scripts,
|
|
293
|
+
`foreignObject`, SVG animation, event handlers, document CSS `<style>` elements,
|
|
294
|
+
CSS imports, external hrefs/URLs, duplicate IDs, and unresolved local references.
|
|
295
|
+
This conservative policy avoids active content and dependencies on destination
|
|
296
|
+
document CSS.
|
|
297
|
+
|
|
298
|
+
LaTeX is a powerful program, not a safe sandbox. Vectex passes
|
|
299
|
+
`-no-shell-escape` to built-in TeX engines, but a malicious source or trusted
|
|
300
|
+
extra compiler option can still read files or consume resources according to the
|
|
301
|
+
compiler's capabilities. Only compile trusted source, and use an OS/container
|
|
302
|
+
sandbox when processing untrusted input. Executable overrides, preamble content,
|
|
303
|
+
and extra argv values are trusted application configuration.
|
|
304
|
+
|
|
305
|
+
## Development and packaging
|
|
306
|
+
|
|
307
|
+
Unit tests use checked-in SVG fixtures and mocked subprocesses; they need no TeX
|
|
308
|
+
installation:
|
|
309
|
+
|
|
310
|
+
```console
|
|
311
|
+
uv sync --all-extras
|
|
312
|
+
uv run ruff format --check .
|
|
313
|
+
uv run ruff check .
|
|
314
|
+
uv run mypy src
|
|
315
|
+
uv run pytest
|
|
316
|
+
uv run python -m build
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Run optional real-tool tests only when explicitly requested:
|
|
320
|
+
|
|
321
|
+
```console
|
|
322
|
+
VECTEX_RUN_INTEGRATION=1 uv run pytest -m integration
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Vectex is distributed under the MIT License.
|
|
326
|
+
|
|
327
|
+
## Scope
|
|
328
|
+
|
|
329
|
+
Vectex produces static, self-contained SVG fragments; it does not manipulate a
|
|
330
|
+
destination SVG document. See [Rendering](docs/rendering.md) for the
|
|
331
|
+
built-in pipeline, baseline behavior, TexText contract, and trust policy,
|
|
332
|
+
and [Fragments](docs/fragments.md) for placement and caller integration.
|