text-compositor
A tool for technical documents — design docs, architecture notes, and similar deliverables — that mix multiple diagram formats such as Mermaid, PlantUML, D2, and Graphviz with prose. It treats each source file as an independent fragment and deterministically composes them into a single human-readable PDF, with no diagram toolchain to set up yourself: each tool's compiler or renderer is fetched and cached automatically. It works just as well for existing, hand-authored documents you want to organize and consolidate, or for AI-generated drafts refined by humans.
You could wire up the same diagram conversions directly with pandoc or Typst — the underlying capability isn't new. What text-compositor adds is packaging that toolchain setup (dependency installation, caching, CI support) for Mermaid/PlantUML/D2 behind a single config.yaml, instead of you assembling it yourself for each tool. See doc/diff.md for how this compares to Typst directly and to general-purpose tools like Quarto.
This README covers only the essentials needed to get started quickly. For a full walkthrough of config.yaml and how to write your source documents, see the usage guide (which can also be built into a PDF with cd doc/usage && python ../../build.py). For detailed design rationale and implementation status, see doc/spec.md.
Features
- Combines multiple text files (chapters) into a single PDF
- Markdown → AST via markdown-it-py → deterministic conversion to Typst syntax → PDF output
- Outputs other text files to the PDF unchanged
- Separates the tool itself from the documentation (source files), which can live anywhere outside the repository
- Python-centric with minimal downloads, no dependency on external servers or SaaS (runs the same way on GitHub Actions and locally on Windows/Linux/macOS)
Files listed in chapters are handled differently depending on their extension:
.md/.markdown: converted as Markdown.yaml/.yml/.json: rendered as monospaced text with syntax highlighting.dot/.gv,.mmd,.puml/.plantuml/.pu,.d2,.pikchr,.dsl: each rendered as a one-chapter diagram (Graphviz, Mermaid, PlantUML, D2, Pikchr, Structurizr respectively — Structurizr is off by default, see "Using Structurizr diagrams" below).csv: rendered as a structured Typst table.cho/.chordpro/.pro: rendered as a ChordPro chord sheet (chords above the lyrics; the same as a```chordprofence).bizmodel/.bmv: rendered as a business model diagram (the flow of goods and money, or a business model canvas withtype canvas; the same as a```bizmodelfence)- everything else (plain text, code files, etc.): rendered as plain monospaced text
See the usage guide for details.
Requirements
- Python 3.10+
Windows note — Microsoft Store Python is not supported, even inside
pipx/venv: If Python was installed from the Microsoft Store, this tool cannot be used with it, full stop —pipxor avenvdoes not work around this.Windows redirects that Python's writes under
%LOCALAPPDATA%into a package-private folder, and this redirection follows the Store installation into anyvenv/pipxenvironment created from it (verified by testing), breaking every subprocess-based feature (PlantUML, Mermaid, D2) even though the files appear to exist to Python itself.Before installing this tool, install Python from python.org (or another non-Store distribution such as
winget install Python.Python.3.12) and use that Python for the steps below. Runtext-compositor --check-envafterward to confirm the environment is set up correctly.
There are two ways to install and run this tool.
Option A: pip install (recommended)
pipx install text-compositor
pipx installs the tool into an isolated environment and exposes the text-compositor command — recommended for most users. If you're developing this tool itself, use a venv instead so an editable install (pip install -e .) picks up code changes immediately:
python -m venv .venv
.venv/bin/pip install -e . # Windows: .venv\Scripts\pip install -e .
Option B: Clone and run directly (no installation)
pip install -r requirements.txt
No Typst compiler binary is bundled; it's obtained from the typst package (PyPI wheel) installed via pip install above. No additional downloads or installation steps are needed.
Keeping up to date
Security and bug fixes ship as new releases. There is no automatic update, so use the latest release: pipx upgrade text-compositor (or pip install -U text-compositor). For the Obunzu Markdown Viewer, download the latest ZIP from the Releases page. The Viewer bundles its own Python, so a new ZIP also updates that Python and the other bundled parts. A guide to using Obunzu (in Japanese) is in doc/obunzu-guide/; its PDF is attached to each release.
Using Mermaid diagrams (optional)
This is only needed if your source documents use ```mermaid fences (or specify an .mmd file directly in chapters). With Option A, install the mermaid extra:
pipx install "text-compositor[mermaid]"
# or, if already installed: pipx inject text-compositor playwright==1.63.0
With Option B (clone and run directly):
pip install playwright==1.63.0
- A Google Chrome or Microsoft Edge installation already present on your system (no new download by default; it's auto-detected and reused at build time)
- The
playwrightpackage above (used only to connect to the existing browser via CDP; Playwright's own browser-download feature is not used by default)
Node.js/npm is not required. At build time, the official single-file Mermaid bundle (mermaid.min.js, ~5.5MB) is fetched and loaded into a headless browser to convert diagrams to SVG (the bundle JS itself is cached under an OS-standard user cache directory — e.g. %LOCALAPPDATA%\text-compositor\Cache on Windows, ~/.cache/text-compositor on Linux — and conversion results are cached under .text-compositor/cache/; neither is re-fetched afterward). None of this is needed for documents that don't use Mermaid.
If no Chrome/Edge is found on the system, the build fails by default. Setting plugins: { mermaid_auto_download: true } instead auto-fetches Playwright's own Chromium, but this download is about 700MB (this setting exists as a last resort for when no pre-installed browser is available; downloading that much by default is intentionally avoided).
Using PlantUML diagrams (optional)
No extra config.yaml settings are needed to use ```plantuml fences in your source documents (or to specify a .puml file directly in chapters) — plugins.plantuml defaults to true and no additional pip install is required.
- If Java (11+) is available locally, it's reused as-is
- Otherwise, Eclipse Temurin JRE (Adoptium distribution, ~49.7MB) is auto-fetched and cached by default under the same user cache directory as above. Setting
plugins: { plantuml_auto_download: false }disables the auto-fetch and fails the build instead - GitHub Actions'
ubuntu-latestships with Java by default, so no extra download happens on CI
The layout engine is the pure-Java Smetana implementation, so no external binary like Graphviz (dot) is required. PlantUML itself (MIT edition, ~17.6MB) is cached under the same user cache directory, and conversion results are cached under .text-compositor/cache/, same as Mermaid.
PlantUML's sprite notation works too — both custom sprite definitions and the icon libraries bundled inside the jar (aws/awslib14, azure, gcp, k8s, office, archimate, tupadr3, and about 30 others). These stdlib icon sets ship inside the jar itself, so they render without network access (verified with !include <office/Servers/database_server> combined with !include <tupadr3/common>, producing an inline PNG icon in the output SVG). Some libraries (e.g. office, awslib14) need that tupadr3/common include alongside them for their macros (OFF_DATABASE_SERVER(id, "label"), etc.) — see PlantUML's stdlib docs for the full library list and exact macro names.
Using D2 diagrams (optional)
No extra config.yaml settings are needed to use ```d2 fences in your source documents (or to specify a .d2 file directly in chapters) — plugins.d2 defaults to true and no additional pip install is required.
- If the
d2command is available locally, it's reused as-is - Otherwise, the official D2 CLI binary (a single Go executable, ~13MB) is auto-fetched and cached by default under the same user cache directory as above. Setting
plugins: { d2_auto_download: false }disables the auto-fetch and fails the build instead
Unlike Java (used by PlantUML) or a browser (used by Mermaid), GitHub Actions' ubuntu-latest does not ship with D2 preinstalled, so a d2 diagram triggers this ~13MB download on every CI run (this project's workflows don't persist the cache directory across separate runs).
Using Structurizr diagrams (C4 model, optional and off by default)
Unlike Mermaid/PlantUML/D2, plugins.structurizr defaults to false — set plugins: { structurizr: true } to use ```structurizr fences (or a .dsl file directly in chapters). It's off by default because the tool it uses internally, the official structurizr-cli, is a ~99MB download (the other plugins are 13-18MB) — most of that size is Kotlin/JRuby/Groovy scripting support this project doesn't use, but there's no lighter official build to fetch instead.
Internally, the DSL is exported to PlantUML via structurizr-cli and rendered through the same PlantUML/Smetana pipeline described above — no separate rendering engine. Java is required, same as PlantUML, controlled by its own plugins.structurizr_auto_download (defaults true) independent of plugins.plantuml_auto_download.
A workspace must define exactly one view per fence — structurizr-cli has no option to pick a single view when a workspace defines several, so this tool fails fast instead of silently picking one. Split multiple views into separate fences, sharing the model between them with the DSL's own !include if needed.
Usage
If installed via pip/pipx (Option A):
text-compositor --config <path/to/text-compositor.config.yaml>
If cloned and run directly (Option B):
python build.py --config <path/to/text-compositor.config.yaml>
If --config is omitted, text-compositor.config.yaml (or .json) located directly in the current directory is auto-detected.
cd my-project/
python /path/to/text-compositor/build.py
Run text-compositor --check-env (or python build.py --check-env) to check your environment (dependencies, Typst version, cached assets, Mermaid/PlantUML/D2 prerequisites) without running a build.
To try a single Markdown file without a config.yaml, pass it directly: text-compositor note.md writes note.pdf next to it. -o OUTPUT sets the output path and -t TEMPLATE picks the template (template, slide, paper, or a .typ path). Paper size and orientation are not options here; use front matter or a config.yaml. See the usage guide for the flags that cannot be combined with it.
For config-based builds, the other flags control runtime behavior only (not document content, which stays entirely in config.yaml): -q/--quiet suppresses [Info]-level logging, -v/--verbose adds extra detail (which chapter is being processed, cache reuse), and --keep-temp keeps the intermediate temp_build.typ around after a successful build instead of deleting it (useful for debugging; it's always kept after a failed build).
--watch keeps running after the first build and rebuilds whenever it detects a save in the config, the input files, or a custom .typ template. A failed build does not stop it; fix the file and save again. Press Ctrl+C to stop. Changes to files outside the project directory (e.g. images referenced via ../) are not detected.
--if-changed skips the build, like make, when the output PDF is newer than the config, the input files, the template, and text-compositor itself (judged by modification time only). Without it, the PDF is always regenerated. Changes to Typst's version, to files outside the project directory, or to environment variables used by variables are not detected. On CI, actions/checkout resets every file's modification time, so restore the output directory from a cache if you want the skip to take effect.
--clean deletes the output PDF and the intermediate files under .text-compositor/ (temp_build.typ, _template.typ, _common.typ) without building. --clean-cache additionally deletes the diagram cache (.text-compositor/cache/). Input files and the config are never deleted.
Python API
text_compositor.Session and build_markdown() turn a single Markdown file into a PDF without a config.yaml, and return warnings and errors as structured diagnostics (Markdown file and line included) instead of printing them. A resident worker (python -m text_compositor.worker) exposes the same over JSON lines on stdin/stdout, for GUI viewers. See the usage guide chapter "Using from Python" and section 14 of doc/spec.md.
from text_compositor import Session
with Session() as session: # reuses the Mermaid browser and the Typst compiler across builds
result = session.build("doc.md", "out/doc.pdf")
print(result.ok, result.pdf_path, [d.message for d in result.diagnostics])
session.render_html("doc.md") (experimental, #161) writes a self-contained HTML file instead. Diagrams (Mermaid, PlantUML, D2, Structurizr, Graphviz, Pikchr, CeTZ, Fletcher, timeliney, finite, Vega-Lite, Vega, WaveDrom, Bytefield, svg) become images next to it. typst-exec and raw HTML are not supported yet and are shown as code with a warning. Graphviz, Pikchr, CeTZ, Fletcher and timeliney are drawn with the Typst packages diagraph, kip, cetz, fletcher and timeliney, the same as in the PDF (Graphviz's shape=record and a graph-level label cannot be drawn and produce a warning).
See sample/text-compositor.config.yaml for how to write the config file, and the usage guide for details on document:/plugins:, front matter, Marp directives, and more. The Markdown files listed in chapters are concatenated in order to produce the PDF.
Trying the sample
cd sample/
python ../build.py
This generates sample/SampleDocument.pdf. Two more samples show other templates: sample/paper/ (two-column paper layout, template.path: paper) and sample/universe-ilm/ (a Typst Universe template used through an adapter; needs network access on the first build).
Implementation status
The core feature implemented so far is combining multiple files into a single PDF according to the config file specified via --config (or auto-detected). A single Markdown file can also be built without a config (FILE.md, -o, -t). Further CLI options (e.g., passing several files or directories directly) are still in the planning stage. See GitHub Issues for known issues and upcoming plans.
Policy for adding diagram notations
This tool is free for commercial use, runs locally, and puts no restrictions on the people who use it (see chapter 2 of doc/spec.md). A diagram tool or notation is therefore not added when it has one of these:
- A strong license constraint. Strong copyleft such as GPL or AGPL, a watermark or credit that must stay in the output, a fee, or a usage limit.
- A heavy runtime. It needs Docker or a similar container, or a large runtime that most users do not have.
- An external service. It sends the manuscript to a server.
Candidates that fail these checks are closed as "not planned" on GitHub Issues, with the reason recorded.
License
Metadata
Release files for text-compositor 0.4.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| text_compositor-0.4.6.tar.gz | 319.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| text_compositor-0.4.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 541.3 kB
Release files / text_compositor-0.4.6.tar.gz
| Download URL | text_compositor-0.4.6.tar.gz |
|---|---|
| Size | 319.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aa940f7dd7368f9462e71c888250ce81cb0182fe8247ed83bf5800f7bd8a15cc
|
|
BLAKE2b-256 checksum How to use checksums |
d8e5ed3b0a405180144b97fe8c4c5eb50b403986e44a5224783d63686cb351e4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / text_compositor-0.4.6-py3-none-any.whl
| Download URL | text_compositor-0.4.6-py3-none-any.whl |
|---|---|
| Size | 221.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bbac84fb2943907a76366ca34b4b93ab42499ebb5e7d140d1fc3917dcbc03a53
|
|
BLAKE2b-256 checksum How to use checksums |
03f4c9170876569e8a854d7368176ddef05557cb181e2758960def81ae8eb4cc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency log