Skip to main content

A high-level Python wrapper for Tectonic LaTeX compiler with project-aware compilation

Project description

B8TeX

A modern, type-safe Python wrapper for the Tectonic LaTeX compiler.

B8TeX provides a high-level, Pythonic interface for compiling LaTeX documents with support for:

  • In-memory compilation from strings
  • Custom style files and packages
  • Multi-file projects with dependency tracking
  • Type-safe API with comprehensive error handling
  • Project-aware builds with multiple output formats

Features

  • Template System (NEW in v0.2.0): Professional document templates (NeurIPS, ICLR, ACL) with programmatic Python API
  • Data-Driven: Pandas DataFrames → LaTeX tables, Matplotlib → embedded figures
  • Modern Python: Built for Python 3.13+ with full type hints
  • Auto-Install: Automatically downloads the Tectonic binary on first use
  • Flexible Sources: Compile from files, strings, or in-memory sources
  • Custom Packages: Bundle custom .sty, .cls, and resource files with your documents
  • Project Support: Multi-target builds with Tectonic V1 and V2 interface support
  • Type-Safe: Comprehensive type hints and runtime validation
  • Secure: Built-in security policies with LaTeX validation, resource limits, and configurable timeouts
  • Resilient: Automatic retry with exponential backoff and graceful failure handling
  • Rich Results: Structured compilation results with warnings, errors, and artifacts
  • Cross-Platform: Supports Linux, macOS, and Windows (x86_64 and aarch64)

Security & Resource Management

B8TeX includes comprehensive security features to protect against malicious or resource-intensive LaTeX documents:

LaTeX Content Validation

Validates documents for security and resource constraints:

from b8tex.core.validation import ContentValidator, ValidationLimits

# Configure validation limits
limits = ValidationLimits(
    max_file_size=10 * 1024 * 1024,  # 10MB per file
    max_total_size=50 * 1024 * 1024,  # 50MB total
    max_files=100,                    # Max 100 files
    allow_shell_escape=False,         # Block shell-escape packages
    dangerous_packages={"shellesc", "minted", "pythontex"}  # Block dangerous packages
)

validator = ContentValidator(limits=limits, mode="strict")
errors = validator.validate_document(document)

Validation modes:

  • strict: Raise errors immediately on validation failure
  • permissive: Collect warnings, only error on critical issues (default)
  • disabled: Skip validation entirely

Resource Limits

Control memory, CPU, and output size limits:

from b8tex.core.runner import ResourceLimits, ProcessRunner

limits = ResourceLimits(
    memory_mb=1024,            # 1GB memory limit
    timeout_seconds=300.0,     # 5 minute timeout
    max_output_size_mb=100,    # Max 100MB output
    cpu_time_seconds=300       # Max 5 minutes CPU time (Unix only)
)

runner = ProcessRunner(limits=limits)

Configurable Timeouts

Set different timeouts for different operations with document size-based scaling:

from b8tex.core.timeouts import TimeoutConfig

timeouts = TimeoutConfig(
    compilation=600.0,          # 10 minute compilation timeout
    download=300.0,             # 5 minute download timeout
    probe=5.0,                  # 5 second probe timeout
    global_multiplier=2.0,      # Double all timeouts for slow systems
    enable_size_scaling=True,   # Scale timeout by document size
    size_scale_factor=0.1       # +0.1s per KB above base size
)

# Get effective timeout for a 500KB document
timeout = timeouts.get("compilation", document_size_kb=500)  # 640s (600 + 400*0.1)

Retry Policies & Graceful Failure

Handle transient failures with automatic retry and exponential backoff:

from b8tex.core.retry import RetryPolicy, retry_with_policy

policy = RetryPolicy(
    max_attempts=3,
    initial_delay=1.0,
    max_delay=30.0,
    exponential_backoff=True,
    backoff_factor=2.0,
    retryable_exceptions=(OSError, IOError, TimeoutError)
)

result = retry_with_policy(
    policy,
    lambda: compile_document(doc),
    operation_name="compilation"
)

Security Configuration

All security settings can be configured via ~/.config/b8tex/config.toml:

# Cache limits
max_cache_size_mb = 1000
max_cache_age_days = 30

[validation]
mode = "permissive"  # "strict", "permissive", or "disabled"

[validation.limits]
max_file_size = 10485760      # 10MB
max_total_size = 52428800     # 50MB
max_files = 100
allow_shell_escape = false    # Block minted, pythontex, etc.

[resource_limits]
memory_mb = 1024
timeout_seconds = 300.0
max_output_size_mb = 100

[timeouts]
compilation = 300.0
download = 300.0
global_multiplier = 1.0
enable_size_scaling = true

Or via environment variables:

# Validation
export B8TEX_VALIDATION_MODE=strict

# Resource limits
export B8TEX_MEMORY_LIMIT_MB=2048
export B8TEX_CPU_TIME_LIMIT=600

# Timeouts
export B8TEX_TIMEOUT_COMPILATION=600
export B8TEX_TIMEOUT_MULTIPLIER=2.0

# Cache
export B8TEX_MAX_CACHE_SIZE_MB=2000

Installation

Simple Installation (Recommended)

B8TeX will automatically download and install the Tectonic binary on first use:

# Using uv (recommended)
uv pip install b8tex

# Or with pip
pip install b8tex

That's it! When you first use B8TeX, it will automatically download the appropriate Tectonic binary for your platform.

Manual Tectonic Installation (Optional)

If you prefer to manage Tectonic yourself:

# macOS
brew install tectonic

# Linux (see https://tectonic-typesetting.github.io/install.html)
# Windows (see https://tectonic-typesetting.github.io/install.html)

Or use the B8TeX CLI to manually install:

# Download and install Tectonic binary
b8tex install-binary

# Download a specific version
b8tex install-binary --version=0.15.0

# Force re-download
b8tex install-binary --force

Configuration

B8TeX can be configured via environment variables or a configuration file.

Environment Variables

# Disable automatic downloads
export B8TEX_NO_AUTO_DOWNLOAD=1

# Specify Tectonic version
export B8TEX_TECTONIC_VERSION=0.15.0

# Use custom Tectonic binary
export TECTONIC_PATH=/usr/local/bin/tectonic

# Override config file location
export B8TEX_CONFIG_PATH=~/.config/b8tex/config.toml

Configuration File

Create a configuration file at ~/.config/b8tex/config.toml (or use b8tex init-config):

# Binary management
auto_download = true
# tectonic_version = "0.15.0"
# binary_path = "/usr/local/bin/tectonic"

# Cache configuration
max_cache_size_mb = 1000
max_cache_age_days = 30

# Security and validation
[validation]
mode = "permissive"  # "strict", "permissive", or "disabled"

[validation.limits]
max_file_size = 10485760      # 10MB
max_total_size = 52428800     # 50MB
max_files = 100
allow_shell_escape = false

# Resource limits
[resource_limits]
memory_mb = 1024
timeout_seconds = 300.0
max_output_size_mb = 100

# Timeouts
[timeouts]
compilation = 300.0
download = 300.0
global_multiplier = 1.0
enable_size_scaling = true

CLI Commands

# Initialize default config file
b8tex init-config

# Show current configuration
b8tex config

# Install Tectonic binary
b8tex install-binary

# Show help
b8tex help

For Development

git clone https://github.com/samehkamaleldin/pytex.git
cd b8tex
uv venv
source .venv/bin/activate  # or `.venv\Scripts\activate` on Windows
uv pip install -e ".[dev]"

Quick Start

Basic Usage

Compile LaTeX from a string:

from b8tex import compile_string

latex = r"""
\documentclass{article}
\title{Hello B8TeX}
\begin{document}
\maketitle
Hello, world!
\end{document}
"""

result = compile_string(latex)
if result.success:
    print(f"PDF generated: {result.pdf_path}")

Custom Style Files

Use custom packages and styles:

from b8tex import Document, InMemorySource, Resource, compile_document

# Define a custom style
style = InMemorySource("mystyle.sty", r"""
\ProvidesPackage{mystyle}
\newcommand{\mycommand}{Custom text}
""")

# Main document
doc = Document(
    name="custom",
    entrypoint=InMemorySource("main.tex", r"""
\documentclass{article}
\usepackage{mystyle}
\begin{document}
\mycommand
\end{document}
"""),
    resources=[Resource(style)]
)

result = compile_document(doc)

Project Builds

Compile multi-target projects:

from b8tex import Project, Target, Document, BuildOptions, OutputFormat

project = Project.from_directory(Path("my_project"))
project.add_target(Target(
    name="pdf",
    document=Document.from_path(Path("main.tex")),
    options=BuildOptions(outfmt=OutputFormat.PDF)
))

from b8tex import TectonicCompiler
compiler = TectonicCompiler()
result = compiler.build_project(project, target="pdf")

Template System

New in v0.2.0: B8TeX now includes a complete template system for programmatic document generation. Write Python code instead of LaTeX preambles!

Why Templates?

  • No LaTeX Boilerplate: Focus on content, not preambles
  • Type-Safe: Full type hints and autocomplete
  • Professional Styles: NeurIPS, ICLR, ACL conference templates
  • Data-Driven: Pandas DataFrames → tables, Matplotlib → figures
  • Validation: Catch errors before compilation

Quick Example

from b8tex.templates import compile_template, Author, Section

result = compile_template(
    template="neurips",
    title="My Research Paper",
    authors=[
        Author(name="Alice", affiliation="MIT"),
        Author(name="Bob", affiliation="Stanford"),
    ],
    abstract="This paper presents a novel approach...",
    sections=[
        Section(title="Introduction", content="Our work addresses..."),
        Section(title="Methods", content="We propose..."),
        Section(title="Results", content="Experiments show..."),
    ],
    status="draft",  # or "final", "confidential"
)

print(f"PDF: {result.pdf_path}")

Template Features

1. Simple API for Quick Documents

from b8tex.templates import compile_template

# One-liner compilation
result = compile_template(
    template="neurips",
    title="Quick Paper",
    authors="John Doe",
    content=r"\section{Intro} Hello world!",
)

2. Fluent Builder with Context Managers

from b8tex.templates import DocumentBuilder

with DocumentBuilder(template="neurips", title="My Paper") as builder:
    builder.add_author("Alice", affiliation="MIT")
    builder.set_abstract("This paper presents...")

    with builder.section("Introduction"):
        builder.add_text("Our motivation is...")

        with builder.subsection("Background"):
            builder.add_text("Previous work includes...")

    doc = builder.build()

result = b8tex.compile_template_document(doc)

3. Data-Driven Documents

Requires optional dependencies: pip install b8tex[templates]

from b8tex.templates import DocumentBuilder
import pandas as pd
import matplotlib.pyplot as plt

with DocumentBuilder(template="neurips", title="Results Report") as builder:
    # Pandas DataFrame → LaTeX table
    df = pd.DataFrame({
        "Model": ["ResNet-18", "ResNet-34", "ResNet-50"],
        "Accuracy": [94.2, 95.8, 96.5],
        "Params (M)": [11.7, 21.8, 25.6],
    })

    with builder.section("Results"):
        builder.add_table(
            df,
            caption="Model comparison",
            label="tab:results"
        )

        # Matplotlib figure → embedded PDF
        fig, ax = plt.subplots()
        ax.plot([1, 2, 3], [94, 95, 96])
        ax.set_title("Accuracy over time")

        builder.add_plot(
            fig,
            caption="Training progress",
            label="fig:training"
        )

    doc = builder.build()

result = b8tex.compile_template_document(doc)

Available Templates

  • NeurIPS: Conference on Neural Information Processing Systems
  • ICLR: International Conference on Learning Representations
  • ACL: Association for Computational Linguistics

Status Modes

  • final: Clean, publication-ready output
  • draft: Line numbers, watermarks, TODOs highlighted
  • confidential: Watermark for internal documents
  • internal: Organization-specific formatting

Template Variables (Jinja2)

from b8tex.templates import compile_template

result = compile_template(
    template="neurips",
    title="Experiment Results",
    content="Accuracy: {{ accuracy }}% on {{ dataset }}",
    template_variables={
        "accuracy": 95.5,
        "dataset": "CIFAR-10",
    },
)

Validation

from b8tex.templates import validate_template_document

# Validate before compilation
is_valid, issues = validate_template_document(doc)

if not is_valid:
    for issue in issues:
        print(f"[{issue.severity}] {issue.message}")

More Examples

See examples/ directory:

  • 08_template_basic.py - Simple template usage
  • 09_neurips_template.py - Full-featured paper with nested sections
  • 10_data_driven_template.py - Tables and plots from data

Documentation

For more examples, see the examples/ directory.

Core Concepts

  • Document: A LaTeX document with entrypoint and resources
  • Resource: Style files, images, bibliographies, etc.
  • BuildOptions: Compilation settings (output format, reruns, security, etc.)
  • CompileResult: Structured result with artifacts, warnings, and errors
  • Project: Multi-target build configuration

API Reference

Main Functions

  • compile_string(content, **options) - Compile LaTeX source code
  • compile_document(document, **options) - Compile a Document object
  • TectonicCompiler() - Main compiler interface for advanced usage

Core Classes

  • Document - LaTeX document representation
  • InMemorySource - In-memory file content
  • Resource - Additional files (styles, images, etc.)
  • BuildOptions - Compilation configuration
  • Project - Multi-target project
  • CompileResult - Compilation results

Development

Setup

# Clone and install
git clone https://github.com/samehkamaleldin/pytex.git
cd b8tex
uv venv
source .venv/bin/activate
uv pip install -e ".[dev]"

Running Tests

# Run all tests
pytest

# Run with coverage
pytest --cov=b8tex --cov-report=html

# Run type checking
mypy src/b8tex

# Run linting
ruff check src tests

Code Quality

This project uses:

  • mypy for static type checking
  • ruff for linting and formatting
  • pytest for testing

All code must pass type checking and linting before merging.

Requirements

  • Python 3.13+
  • Tectonic LaTeX compiler (automatically downloaded if not found)
  • uv (recommended) or pip

Supported Platforms

B8TeX can automatically download Tectonic for the following platforms:

  • Linux: x86_64, aarch64
  • macOS: x86_64 (Intel), aarch64 (Apple Silicon)
  • Windows: x86_64

If automatic download is not available for your platform, you can install Tectonic manually following the official installation guide.

How Auto-Download Works

When you first use B8TeX:

  1. B8TeX checks for Tectonic in the following order:

    • Custom binary path from config file
    • TECTONIC_PATH environment variable
    • System PATH
    • B8TeX cache directory (~/.cache/b8tex/ or platform equivalent)
  2. If Tectonic is not found and auto-download is enabled:

    • Downloads the appropriate binary from Tectonic GitHub releases
    • Extracts and caches it in ~/.cache/b8tex/
    • Uses the cached binary for all future compilations
  3. If auto-download is disabled or fails:

    • Provides clear instructions for manual installation
    • Suggests using b8tex install-binary command

You can disable auto-download by:

  • Setting B8TEX_NO_AUTO_DOWNLOAD=1 environment variable
  • Setting auto_download = false in ~/.config/b8tex/config.toml

Troubleshooting

Common Issues

Binary Not Found

Problem: MissingBinaryError: Tectonic binary not found

Solutions:

  1. Enable auto-download (default): B8TeX will automatically download Tectonic
  2. Manual installation: b8tex install-binary
  3. Install Tectonic system-wide: brew install tectonic (macOS) or follow official guide
  4. Set custom path: export TECTONIC_PATH=/path/to/tectonic

Compilation Fails

Problem: Compilation returns errors but unclear why

Solutions:

  1. Check result.errors for specific error messages
  2. Use options.print_log=True to see full Tectonic output
  3. Enable options.keep_logs=True to preserve log files
  4. Verify LaTeX syntax is valid (try with tectonic command directly)

Permission Errors

Problem: PermissionError when compiling

Solutions:

  1. Check write permissions in output directory
  2. Verify cache directory is writable: ~/.cache/b8tex/
  3. Try running with elevated permissions (not recommended)
  4. Use custom output directory: workdir=Path("/tmp/b8tex")

Platform Not Supported

Problem: UnsupportedPlatformError on auto-download

Solutions:

  1. Install Tectonic manually for your platform
  2. Set TECTONIC_PATH to your manual installation
  3. Open an issue if you think your platform should be supported

Slow Compilation

Problem: Compilation is slower than expected

Solutions:

  1. Enable caching (on by default): TectonicCompiler(use_cache=True)
  2. Use only_cached=True for offline/fast builds
  3. Reduce LaTeX reruns: BuildOptions(reruns=1)
  4. Check if cache is being invalidated unnecessarily
  5. See Performance Tips section below

Cache Issues

Problem: Cache not working or corrupted

Solutions:

  1. Clear cache: b8tex clean-cache or cache.clear()
  2. Check cache stats: cache.stats()
  3. Verify cache directory exists: ~/.cache/b8tex/build_cache.db
  4. Clean old entries: cache.cleanup(max_age_days=30)

FAQ

General

Q: Do I need to install LaTeX? A: No! B8TeX uses Tectonic, which is a self-contained LaTeX distribution. You don't need TeX Live, MiKTeX, or any other LaTeX distribution.

Q: What Python versions are supported? A: B8TeX requires Python 3.13 or later for modern type hints and language features.

Q: Is B8TeX production-ready? A: B8TeX is currently in active development. The core API is stable, but advanced features are still being added. Check the version number and release notes.

Q: How does B8TeX compare to other LaTeX wrappers? A: B8TeX focuses on modern Python features, type safety, and developer experience. It's built specifically for Tectonic and offers features like automatic binary management, build caching, and in-memory compilation.

Usage

Q: Can I compile LaTeX files from disk? A: Yes! Use Document.from_path(Path("document.tex")) or pass a Path object directly.

Q: How do I include images in my document? A: Add images as resources:

from b8tex import Document, Resource
doc = Document(
    entrypoint=Path("main.tex"),
    resources=[Resource(Path("image.png"))]
)

Q: Can I compile multiple documents in parallel? A: Yes! Use the async API or Python's concurrent.futures. See examples/04_async_compilation.py.

Q: How do I use custom LaTeX packages? A: Add them as resources with kind="sty":

Resource(Path("mypackage.sty"), kind="sty")

Q: Can I compile without internet access? A: Yes! Once Tectonic is installed and packages are cached, use BuildOptions(only_cached=True).

Configuration

Q: Where is the cache directory? A: Platform-dependent:

  • macOS: ~/Library/Caches/b8tex/
  • Linux: ~/.cache/b8tex/
  • Windows: %LOCALAPPDATA%\b8tex\cache\

Q: How do I disable auto-download? A: Set B8TEX_NO_AUTO_DOWNLOAD=1 or auto_download = false in config file.

Q: Can I use a specific Tectonic version? A: Yes! Set in config file:

tectonic_version = "0.15.0"

Or use: b8tex install-binary --version=0.15.0

Q: How do I change the output directory? A: Use the workdir parameter:

result = compiler.compile_document(doc, workdir=Path("/tmp/output"))

Performance

Q: Why is the first compilation slow? A: Tectonic downloads required LaTeX packages on first use. Subsequent compilations are much faster thanks to package caching.

Q: Does B8TeX cache compilation results? A: Yes! Build caching is enabled by default. See examples/05_cache_demo.py.

Q: How much space does the cache use? A: Check with cache.stats(). Typically 10-100MB depending on usage. Clean old entries with cache.cleanup().

Performance Tips

1. Enable Build Caching (Default)

Build caching can speed up repeated compilations by 10-100x:

from b8tex import TectonicCompiler, BuildCache

# Caching is enabled by default
compiler = TectonicCompiler(use_cache=True)

# Or explicitly create cache
cache = BuildCache()
compiler = TectonicCompiler(cache=cache)

When cache helps:

  • Repeated compilation of unchanged documents
  • CI/CD pipelines
  • Development with frequent rebuilds
  • Batch processing similar documents

Cache invalidation: The cache automatically invalidates when:

  • Document content changes
  • Build options change
  • Tectonic version changes
  • Dependencies are modified

2. Use Only-Cached Mode

For fastest compilation when all packages are already downloaded:

from b8tex import BuildOptions

options = BuildOptions(only_cached=True)
result = compiler.compile_document(doc, options=options)

This prevents Tectonic from downloading packages, ideal for offline work or CI.

3. Reduce Reruns

LaTeX sometimes needs multiple passes. Limit reruns for faster compilation:

options = BuildOptions(reruns=1)  # Default is automatic

Trade-off: Cross-references and bibliographies may not resolve correctly.

4. Parallel Compilation

For batch processing, use async or parallel execution:

from concurrent.futures import ThreadPoolExecutor

def compile_doc(content):
    return compile_string(content)

with ThreadPoolExecutor(max_workers=4) as executor:
    results = list(executor.map(compile_doc, documents))

Speedup: Near-linear with number of cores for independent documents.

5. Persistent Workspaces

Reuse workspace directories to avoid file I/O:

workdir = Path("/tmp/b8tex_workspace")
workdir.mkdir(exist_ok=True)

for doc in documents:
    result = compiler.compile_document(doc, workdir=workdir)

6. In-Memory Compilation

Use in-memory sources instead of file I/O when possible:

from b8tex import InMemorySource, Document

doc = Document(
    entrypoint=InMemorySource("doc.tex", latex_content),
    resources=[InMemorySource("style.sty", style_content)]
)

Benefit: Faster file operations, no disk cleanup needed.

7. Cache Management

Keep cache healthy with periodic cleanup:

# Remove entries older than 30 days
cache.cleanup(max_age_days=30)

# Check cache size
stats = cache.stats()
print(f"Cache entries: {stats['total_entries']}")
print(f"Cache size: {stats['db_size_bytes'] / 1024 / 1024:.1f} MB")

# Clear if needed
if stats['db_size_bytes'] > 100_000_000:  # > 100 MB
    cache.clear()

8. Minimize Document Changes

The cache fingerprint includes all content. To maximize cache hits:

  • Use variables/templates for changing content
  • Separate frequently-changing content from static boilerplate
  • Consider using LaTeX's \input for modular documents

Performance Benchmarks

Typical compilation times on modern hardware:

Scenario First Compile Cached Speedup
Simple document (1 page) 2-5s 0.1-0.5s 10-20x
Medium document (10 pages) 5-15s 0.2-1s 15-25x
Large document (100 pages) 15-60s 1-3s 15-30x
With images/complex math 10-30s 0.5-2s 15-20x

Note: First compile includes package downloads. Subsequent first compiles (different documents) are faster as packages are cached by Tectonic.

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new features
  4. Ensure all tests pass and types check
  5. Submit a pull request

Acknowledgments

  • Built on top of the excellent Tectonic LaTeX compiler
  • Inspired by modern Python packaging and type safety best practices

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

b8tex-0.2.0.tar.gz (19.9 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

b8tex-0.2.0-py3-none-any.whl (97.1 kB view details)

Uploaded Python 3

File details

Details for the file b8tex-0.2.0.tar.gz.

File metadata

  • Download URL: b8tex-0.2.0.tar.gz
  • Upload date:
  • Size: 19.9 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.21

File hashes

Hashes for b8tex-0.2.0.tar.gz
Algorithm Hash digest
SHA256 82c06b4662c4942534d1dee338566bd1ceb2d3aa859e0c277a8042f41d748819
MD5 3287e442262a45cbdf6a0414d819df85
BLAKE2b-256 0b87675c60adb57f9de3803505959d3a7edb5e6404a20444a9241b2a584c0331

See more details on using hashes here.

File details

Details for the file b8tex-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: b8tex-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 97.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.21

File hashes

Hashes for b8tex-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a1f4b595f4288e075d1b6c9e1eceb0b03cda567eaf26346a9cea6afb4da21305
MD5 9e2e0d1c65d28daa5273b35bc14e7b58
BLAKE2b-256 c07af6db8b0d4fe118bda5de03d8a28ea26b45e35bb4d02cb56e0f25a1ea4f72

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page