Documentation
¶
Overview ¶
Package texmf gets a TeX engine the support tree a document asks for, on a machine with no TeX distribution installed.
It redistributes nothing. Every byte of TeX macro source comes from a pinned upstream release, fetched onto the user's own machine, checked against a recorded SHA-256, and kept in the user cache. That is deliberate: beamer, for one, is offered "under the LaTeX Project Public License and/or under the GNU Public License", and downloading it is not a redistribution, so the choice between those two licences never has to be made on the user's behalf.
The result plugs straight into the engine:
tree, err := texmf.Open(ctx, texmf.Beamer, texmf.Options{})
if err == nil {
opt.Resolve = tree.Resolve
}
A caller that already has the files — a TeX distribution on the machine, a vendored copy, an air-gapped host — does not need this package at all: the engine's own search path and Options.Resolve already take precedence over anything fetched here.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var All = map[string]Bundle{ Beamer.Name: Beamer, }
All is the catalogue, keyed by bundle name. The publish workflow reads a pin from here rather than repeating it in YAML, so a mirror can never carry a version or digest this code does not name.
var Beamer = Bundle{ Name: "beamer", Version: beamerVersion, SHA256: "2ab4acf4c6be0d96d3f18161b08fd8e56ab54d380686f6440a42705e48c76f76", Prefix: "tex/latex/beamer/", Sources: []Source{ OCISource{ Registry: "ghcr.io", Repository: "go-tex/texmf/beamer", Reference: beamerVersion, Label: "ghcr.io/go-tex/texmf/beamer:" + beamerVersion, }, HTTPSource{ URL: "https://github.com/josephwright/beamer/releases/download/v" + beamerVersion + "/beamer.tds.zip", Label: "upstream release josephwright/beamer v" + beamerVersion, }, }, }
Beamer is the beamer presentation class and the files it loads.
Measured over 10025 real talks: with these files present the engine renders 99.9% of them and ~8.7 pages per talk; without them, falling back to the engine's own emulation, 92.3% and ~1.4 pages. The difference is not an edge case, it is most of the content.
The archive is beamer's TDS distribution. Only tex/latex/beamer/ is extracted: the rest is documentation and sources, thousands of files the engine never opens.
Routes, in order:
- ghcr.io/go-tex/texmf/beamer — a registry the project controls, so the default route does not depend on a third party staying up;
- the upstream release itself, so the bundle stays reachable when the registry is not.
Both must produce the same bytes: SHA256 is checked whichever route ran, so a route is a delivery mechanism and never a trust anchor.
var ErrNotCached = errors.New("texmf: bundle not cached and offline")
ErrNotCached is returned by Open when the bundle is absent from the cache and Options.Offline forbids fetching it.
Functions ¶
func UpstreamURL ¶
UpstreamURL returns the bundle's upstream route, which is by convention its last one: the registry mirrors exist to be tried first, and the publisher needs the source they mirror.
Types ¶
type Bundle ¶
type Bundle struct {
// Name and Version identify the bundle, and together form its cache
// directory ("beamer@3.77"). Version is the upstream release's own version,
// so a cache entry is unambiguous.
Name string
Version string
// Sources are tried in order until one yields the archive. The first is
// normally a registry the operator controls; the last should be the upstream
// release, so the bundle stays reachable when the registry is not.
Sources []Source
// SHA256 is the archive's digest, lowercase hex. Every source must produce
// exactly these bytes — a source is a delivery route, never a trust anchor.
SHA256 string
// Prefix is the archive directory holding the TeX macro files; only entries
// under it are extracted. Everything else in a TDS archive (documentation,
// sources, PDFs) is thousands of files the engine will never open.
Prefix string
}
A Bundle names one upstream release and says which of its files a TeX engine should be able to see. It is a value, not a fetch: nothing happens until Open.
type HTTPSource ¶
type HTTPSource struct {
// URL is fetched with GET, following redirects.
URL string
// Label names the route in messages ("upstream release josephwright/beamer
// v3.77"). Empty falls back to the URL.
Label string
// Client is used when set; nil means a client with a sane timeout.
Client *http.Client
}
HTTPSource fetches an archive from one URL. It is the plain route: a release asset published by the upstream project, pinned to an exact version so the bytes never move under us.
type OCISource ¶
type OCISource struct {
// Registry is the host ("ghcr.io").
Registry string
// Repository is the path within it ("go-tex/texmf/beamer").
Repository string
// Reference is a tag ("3.77") or a digest ("sha256:…").
Reference string
// Label names the route in messages. Empty builds one from the coordinates.
Label string
// Client is used when set; nil means a client with a sane timeout.
Client *http.Client
}
OCISource pulls an archive stored as a single-layer OCI artifact — the shape a registry the operator controls can serve, so the default route to a bundle is theirs rather than a third party's.
It speaks just enough of the distribution API to pull one blob: anonymous token auth, one manifest, one layer. Pulling one artifact does not need a registry client library, and not depending on one keeps this module's dependencies to the standard library.
type Options ¶
type Options struct {
// CacheDir overrides where extracted bundles live. Empty means
// <user cache>/go-tex/texmf, which is what the CLI wants.
CacheDir string
// Offline forbids every network fetch: a bundle already in the cache is
// used, and a bundle that is not returns ErrNotCached. This is what an
// air-gapped or reproducible build sets.
Offline bool
// Log, when set, is called with one line per notable step (which source was
// tried, what it produced). Nil discards them. The CLI wires this to its own
// stderr so a download is never silent.
Log func(string)
}
Options configures Open.
type Source ¶
type Source interface {
// Describe names the route for error messages ("upstream release
// josephwright/beamer v3.77").
Describe() string
// Fetch returns the archive bytes, or an error.
Fetch(ctx context.Context) ([]byte, error)
}
A Source is one route to a bundle's archive bytes. Fetch must return the complete archive; the digest is checked by the caller, so a Source is never trusted to validate itself.
type Tree ¶
type Tree struct {
// contains filtered or unexported fields
}
A Tree is an extracted bundle. Resolve answers the engine's file lookups from it, and is safe for concurrent use.
func Open ¶
Open makes the bundle available, fetching and extracting it only if the cache does not already hold it. The returned Tree reads from disk lazily.
func (*Tree) Dir ¶
Dir is where the tree was extracted. Useful for a caller that would rather put it on TEXINPUTS than go through Resolve.
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
texmf-pin
command
Command texmf-pin prints one catalogue bundle's pin as key=value lines, so that the publish workflow mirrors exactly the version and digest the Go code names — and cannot drift from it.
|
Command texmf-pin prints one catalogue bundle's pin as key=value lines, so that the publish workflow mirrors exactly the version and digest the Go code names — and cannot drift from it. |