Skip to main content

pc — Panel Compiler

Compose scientific figures from SVG plots, PDF figures, standalone TeX figures, LaTeX equations, and raster images (PNG/JPEG) into a single SVG or PDF panel. This is intended for the case that these subfigures are generated by scripts and you automatically want to keep your panels up to date.

Define a panel template in Inkscape with named group (<g>) or rectangle (<rect>) placeholders (set via the layer/group label field), then write a small YAML config that maps each label to an SVG file, PDF, standalone .tex file, raster image (.png/.jpg), or a LaTeX string. pc scales each figure to fit its placeholder and writes the compiled panel. Raster images are embedded as-is at their pixel size and scaled to the placeholder, so a molecular render or photo can drop in directly:

structure:
  file: laci_operator.png
  fit: contain

It is often convenient to let pc overwrite the old panel (labels are preserved for repeated execution).

Example

An example is given in example. Assume you would like to create a panel with 3 subfigures. Two are figures created via python code, and one contains a LaTeX formula.

We start from the panel template example/panel.svg that was created in Inkscape. Here, the template is simple and contains only three rect placeholders, each of which has set its label (via Inkscape) set to one of: formula, logistic, and logistic_log.

This is the panel:

panel template

The python script that generates the subfigures is example/subfigures.py with the following subfigures being generated:

logistic.svg logistic_log.svg
logistic logistic_log

Next we prepared a configuration example/pc.yaml that tells the panel-compiler how to compile the panel:

panel: panel.svg
output:
  - out.svg
  - panel.pdf

formula:
  tex: $\frac{dN}{dt} = rN\!\left(1 - \frac{N}{K}\right)$
  size: 10pt

logistic:
  file: logistic.svg
  fit: contain

logistic_log:
  file: logistic_log.svg
  fit: contain

The panel is finally compiled via:

cd example
python subfigures.py
pc pc.yaml

Resulting in the generation of example/out.svg:

compiled panel

Installation

From PyPI:

uv tool install panel-compiler

or with pip:

pip install panel-compiler

From the repository:

uv tool install git+https://github.com/mfuegger/pc.git

or with pip:

pip install git+https://github.com/mfuegger/pc.git

For LaTeX rendering and standalone .tex figures, pdflatex must be on your PATH. For PDF/TeX figures and PDF output, pdf2svg and inkscape must be on your PATH. On macOS: brew install pdf2svg. SVG and raster (PNG/JPEG) figures need no external tool.

Usage

pc [config.yaml]
Argument Default Description
config.yaml pc.yaml YAML config (see below)

The output path is taken from the output key in the config. If omitted, it defaults to the config filename with .svg extension (e.g. pc.yaml → pc.svg).

Config format

Single panel

panel: panel.svg    # required — path to the panel SVG, relative to this config file
output: out.svg     # optional — defaults to <config-stem>.svg if omitted
content_style: "stroke: none; fill: initial;"  # optional — empty by default

output can also be a list to produce multiple formats in one run. Supported formats are SVG, PDF, and PNG (chosen by the file extension); PDF and PNG are exported through Inkscape:

output:
  - out.svg
  - out.pdf
  - out.png

For raster output, set the resolution per entry with dpi (the panel's physical width/height in mm then fix the pixel dimensions). Either nest the options under the filename or use the explicit file: form:

output:
  - out.svg
  - out.png:        # keyed form
      dpi: 600
  - file: thumb.png # file form
    dpi: 150

dpi is ignored for SVG; for PDF it sets the resolution at which Inkscape rasterises filter effects (drop shadows, blurs).

content_style is an optional CSS declaration block applied to all paths, circles, and polygons inside embedded SVG figures. It is empty by default, so each figure keeps its own strokes and fills. Set it only when the panel template ships type-selector rules (e.g. a <style> with path { stroke: ... }) that would otherwise bleed into embedded content; the value then overrides them (e.g. stroke: none; fill: initial;). LaTeX labels always get stroke: none regardless of this setting.

Each remaining key is a label that must match an element in the panel SVG. pc looks it up by inkscape:label first, then label, then id. The element can be a <g> group or a <rect> placeholder — a <rect> is automatically replaced by a <g> positioned at the rect's x/y. A warning (including the panel filename) is emitted for any label not found; compilation of the remaining entries continues.

Figure entry (SVG, PDF, or TeX)

plot:
  file: results.svg   # path relative to this config file; .svg, .pdf, or .tex  (alias: svg:)
  fit: contain        # contain | height | width  (default: contain)
  width: 200          # optional — fallback target width  (SVG user units)
  height: 100         # optional — fallback target height (SVG user units)

Fit strategies:

  • contain — scale uniformly to fit within the placeholder box (default)
  • height — scale to match the placeholder height exactly
  • width — scale to match the placeholder width exactly

Target dimensions come from width/height attributes on the group element in the panel SVG. The config width/height are used as a fallback when the group has no such attributes.

PDF files are converted via pdf2svg before embedding. TeX files are compiled with pdflatex, converted via pdf2svg, then embedded and scaled like SVG/PDF figures.

Use .tex files for complete documents, not snippets. In practice these are often standalone documents containing TikZ, algorithms, proof trees, tables, or anything else that is easier to draw in LaTeX than in Inkscape:

algorithm:
  file: algorithm.tex
  fit: contain

For example:

\documentclass[tikz,border=2pt]{standalone}
\usepackage{tikz}

\begin{document}
\begin{tikzpicture}
  \draw (0,0) rectangle (2,1);
  \node at (1,.5) {example};
\end{tikzpicture}
\end{document}

If the TeX file does not compile, pc prints the pdflatex command, the working directory, the exit code, and the tail of the LaTeX log. That usually includes the familiar file.tex:line: error message, so the first thing to check is the reported line in the .tex file. The file is compiled from its own directory, so relative \input{...} and image paths should be written as they would be when running pdflatex next to that file.

LaTeX entry

label:
  tex: $y = \sin(x)$   # any LaTeX — math mode, text, amsmath, …
  size: 12pt           # font size (default: 10pt); scales the rendered output

Rendered via pdflatex + inkscape. No fit scaling — the output is sized by size alone.

Shorthand

A plain string value is treated as an SVG file path with fit: contain:

other: path/to/fig.svg

Multiple panels

Use a YAML list; each entry must have its own output. A plain mapping (non-list) with duplicate keys — the easy mistake when copy-pasting a second panel block — triggers a warning and the second value silently wins; use the list form to avoid this.

- panel: panel1.svg
  output: out1.svg
  plot:
    file: results.svg

- panel: panel2.svg
  output:
    - out2.svg
    - out2.pdf
  label:
    tex: $E = mc^2$
    size: 12pt

Development and tests

Run the full test suite with coverage:

uv run pytest
# or: make test

Every run measures line and branch coverage for panel_compiler and the legacy pc.py module. The terminal report lists uncovered lines and branches; the combined coverage must be at least 95% or the command fails. The settings live in pyproject.toml and use pytest-cov with coverage.py.

Open htmlcov/index.html for the annotated source report. coverage.xml is also generated for CI tools. These generated files are ignored by Git.

GitHub Actions runs the same checks on every push and pull request with Python 3.12 and 3.14, adds coverage to the job summary, and saves the HTML/XML reports as downloadable artifacts. External converter commands are mocked in the test suite, so running it does not require Inkscape or a LaTeX installation.

For a focused regression test without the full-suite coverage threshold:

uv run pytest --no-cov tests/test_scaling.py

Coverage records which code the tests exercise; the regression assertions check the resulting SVG content, dimensions, references, and error handling.

License

Apache 2.0 — see LICENSE.

Metadata

Release files for panel-compiler 0.1.11

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for panel-compiler 0.1.11
File Size Uploaded Provenance Actions
panel_compiler-0.1.11.tar.gz 51.0 kB With provenance Details for panel_compiler-0.1.11.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for panel-compiler 0.1.11
File Interpreter ABI Platform Actions
panel_compiler-0.1.11-py3-none-any.whl Python 3 none any Details for panel_compiler-0.1.11-py3-none-any.whl

Total release size: 74.3 kB

Release files / panel_compiler-0.1.11.tar.gz

Download URL panel_compiler-0.1.11.tar.gz
Size 51.0 kB
Tags Source
SHA-256 checksum
How to use checksums
8ef5f0b6c853146827b5261db090b29b71d26fd93029233d3f2dab9f0e7624e1
BLAKE2b-256 checksum
How to use checksums
53d771c0f091ce96ce04e66b3148eb4c8bbdd4533e5adf1977997ab9addc906d
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 7, 2026.

Transparency log

Release files / panel_compiler-0.1.11-py3-none-any.whl

Download URL panel_compiler-0.1.11-py3-none-any.whl
Size 23.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1d02ed6db642c7f3d8ac70673d5774782f62b21195d5d5e33cabc7e75fe953c5
BLAKE2b-256 checksum
How to use checksums
61a5a26a54cbee4d1547b3f920b6e08a527bfbe62f87fa3f2fab07868b6622cc
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 7, 2026.

Transparency log

Security for panel-compiler 0.1.11

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for panel_compiler-0.1.11.tar.gz

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 7, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page