- Docs
- Data and storage
- Documents
Documents
Render document sources to PDF, PNG, or JPG with a local, LaTeX, or remote renderer.
document.Document turns a source into a PDF or image with one Render call, a typed OutputFormat (pdf, png, jpg), and a Quality bounded to 0 to 100. Callers never learn whether LaTeX or a remote API did the work.
Quick start
Section titled “Quick start”local(default,adapters/document/local) renders HTML in headless Chrome.latexshells out to a fixed, configured compiler; use it for.texsources.remotesends the job to a rendering API over HTTP. It needsendpoint(https, or http withallow_insecure: true); a non-emptyapi_keyis sent as a bearer token. Outbound requests dial only globally routable addresses and never follow redirects; a local rendering sidecar needsallow_private: true, which logs a line at startup.
DOCUMENT_ADAPTER=localDOCUMENT_QUALITY=80DOCUMENT_DPI=150DOCUMENT_TMP_DIR=/tmp/zever-docDOCUMENT_MAX_OUTPUT_BYTES=67108864DOCUMENT_TIMEOUT=30squality must be 0 to 100 (Validate rejects other values; ClampQuality clamps user input before you pass it); dpi, max_output_bytes (default 64 MiB), and timeout must be 0 or more. Durations are nanoseconds, env accepts 30s. latex_command and latex_pdf_to_ppm (defaults pdflatex, pdftoppm) are config only, never interpolated from document content. latex_runs bounds compiler passes. For remote, endpoint must be an absolute URL with scheme and host (allow_insecure permits http). See the document options.
Render a document
Section titled “Render a document”Register the adapter first; the container wires nothing. Document() resolves lazily, caches, and retries.
package main
import ( "context" "fmt" "os"
documentlocal "github.com/zenta-dev/zever/adapters/document/local" "github.com/zenta-dev/zever/config" "github.com/zenta-dev/zever/container" "github.com/zenta-dev/zever/core/document")
func main() { documentlocal.Register() ctx := context.Background() c := container.New(config.Default()) defer c.Close(ctx)
d, err := c.Document() if err != nil { panic(err) } out, err := d.Render(ctx, []byte(`hello`), document.FormatPDF) if err != nil { panic(err) // out is nil on error, never a partial file } if err := os.WriteFile("/tmp/hello.pdf", out, 0o600); err != nil { panic(err) } fmt.Println("bytes:", len(out))}bytes: 1240The byte count varies by adapter and source. Check the error before writing to disk.
Production checklist
Section titled “Production checklist”- Sources are capped at 10 MiB and output at 64 MiB by default. Raise them deliberately and with a measured reason.
- The
latexadapter never interpolates a caller-supplied command, so document content cannot reach a shell. Lock downtmp_dirtoo. - LaTeX rendering is hardened by default: every render runs in a fresh temp dir as
cwd, with-no-shell-escapeand a minimal environment (onlyPATH,HOME, andTEXMF*are passed; all other parent variables, including credentials, are dropped).openout_any=pblocks\openoutwrites outside the render tree andopenin_any=prestricts\inputon TeX Live releases that still honor it. Since TeX Live 2026openin_anyis a documented no-op upstream (kpathseakpse_in_name_okalways returns true), so the engine alone cannot stop\input{/etc/passwd}; the controls below cover that gap. On timeout the whole process group is killed, so a hung render cannot leak processes or pipes. - Untrusted LaTeX input. Treat LaTeX as code. If any part of the source can come from users, set
latex_sandbox: requiredand install bubblewrap (bwrap). TeX then runs with--unshare-all(no network, no IPC or PID visibility),--die-with-parent, read-only system and TeX trees, and the per-render temp dir as the only writable path;/etc,/homeand your application files do not exist inside it, so\input{/etc/hostname}fails.latex_sandboxacceptsauto(default: use bwrap when it works, otherwise log one warning and continue unsandboxed),required(fail at open when bwrap is missing or unusable, for example when unprivileged user namespaces are disabled; in containers this usually needs a seccomp/userns allowance), andoff. A source pre-scan additionally rejects\input,\include,\openin,\openout,\verbatiminputand similar with absolute,..,~or piped paths,\write18, and shell-escape packages (shellesc,minted,pythontex, …) withErrUnsafeSource. The pre-scan is best effort (it does not see\csnameor\catcodetricks) and is never a substitute for the OS sandbox.latex_allow_unsafe_source: truedisables it for fully trusted sources. Also run the service as an unprivileged user, keeptmp_diron a dedicated mount, and settimeoutandmax_output_bytes; output is size-checked before it is read into memory. On Windows there is no OS sandbox (use a container or VM) and a timeout kills only the direct TeX process. - Bound renders with
timeoutand clean up temp artifacts on error paths. api_keynever appears in logs, andendpointis validated before the first request.
Troubleshooting
Section titled “Troubleshooting”Render returns nil bytes and an error
Cause: that is the contract, with no partial files. Fix: inspect the error, do not write the output, and check the source against the 10 MiB cap.
LaTeX renders fail with “command not found”
Cause: latex_command or latex_pdf_to_ppm is not on PATH (or not an absolute path). Fix: install them, or use the remote adapter.
Remote renders fail validation before any request
Cause: endpoint is not an absolute URL with scheme and host, or timeout is negative. Fix: correct the config. Nothing was sent.