Python API¶
Most projects never import ComicForge — the CLI is the interface, and a project is data. Reach for the API when you are generating specs programmatically, building a preview server, or writing tests around your own art.
Rendering¶
render_spec(spec, out_path, library=None, scenes=None, pixel_library=None)¶
Renders a comic page. spec is a path or a dict. The output format comes from
out_path's extension (.svg, .png, .pdf). Returns the composed SVG as a
string.
from comicforge import render_spec
render_spec("pages/slepice.yaml", "slepice.pdf")
svg = render_spec("pages/slepice.yaml", "slepice.svg")
render_scene(spec, out_path, …)¶
The same, for a standalone illustration (type: scene).
Each refuses the other's spec type with a message naming the right function.
load_spec(path)¶
Reads and parses a spec into a plain dict — useful for inspecting or transforming one before rendering.
A dict spec has no directory
Relative paths in a spec resolve against the spec file's directory. A
dict has no such directory, so its relative paths fall back to the current
working directory instead — library: "../characters" will mean something
different depending on where the process runs. Use absolute paths, pass
spec_dir= to the build_* functions, or build the libraries yourself and
pass them in.
Passing libraries explicitly¶
The three library objects override the spec's own keys, exactly as the CLI flags do. This is how you render a dict spec, and how you avoid re-reading the same art for every page in a batch — each library caches what it loads.
from comicforge import render_spec
from comicforge.library import Library
from comicforge.scene import SceneLibrary
from comicforge.pixelart import PixelLibrary
lib = Library("characters")
scenes = SceneLibrary("scenes")
pixel = PixelLibrary("pixel")
for page in ("slepice", "kosticka"):
render_spec(f"pages/{page}.yaml", f"out/{page}.png",
library=lib, scenes=scenes, pixel_library=pixel)
Panels¶
from comicforge.render import render_panel, render_all_panels
render_panel("pages/strip.yaml", "panel.png", row=0, col=1, scale=1.0)
paths = render_all_panels("pages/strip.yaml", "panels/", scale=0.5, ext=".png")
render_all_panels writes panel_r<R>c<C>.<ext> into the directory and returns
the list of paths.
One character¶
from comicforge.render import render_character
from comicforge.library import Library
lib = Library("characters")
render_character("bara", "bara.png", {"face": "happy"}, pose="walk",
library=lib, scale=2.0, bg="#ffffff", flip=False)
Building SVG without writing a file¶
Every renderer has a build_* counterpart that returns the SVG string and
touches no disk:
from comicforge.render import (
build_svg, # a page
build_scene_svg, # a standalone illustration
build_panel_svg, # one panel
build_character_svg, # one character
)
svg = build_svg(spec, library=lib, scenes=scenes, spec_dir=Path("pages"))
Pass spec_dir when the spec came from a dict but its relative paths should
still resolve — it is the directory they resolve against.
Validation¶
from comicforge.validate import validate_spec
problems = validate_spec("pages/strip.yaml")
if problems:
for p in problems:
print(p)
Returns a list of strings, empty when the spec is sound. It never raises for a bad spec — that is the point.
from comicforge.validate import check_spec
report = check_spec("pages/strip.yaml")
report.problems # the same list validate_spec returns
report.warnings # bubble layout warnings, computed only when there are no problems
Bubble layout¶
bubble_layout(spec, row=0, col=0, spec_dir=None)¶
Where every bubble of one panel lands, without rendering — for an editor that
draws handles over a panel. spec is a dict or a path; a scene spec is its own
single panel. Every point is in panel fractions of the art box (the panel less
its caption band), the same units the spec uses.
{"width": 492.0, "height": 307.5, # art box, page px
"speakers": {"hen": [0.42, 0.78]}, # every resolvable speaker point
"bubbles": [
{"index": 0, "text": "Kvok!", "kind": "speech", "speaker": "hen",
"auto": True, # placed in reading order
"center": [0.2, 0.08], "box": [0.12, 0.02, 0.28, 0.14], # x0, y0, x1, y1
"tail": {"edge": "b", "shape": "wedge",
"bend": 0.0, # -1..1, the resolved bow
"start": [0.21, 0.14], # where it leaves the bubble
"control": [0.22, 0.21], # the quadratic control point
"tip": [0.24, 0.29], # where the drawn tail ends
"target": [0.42, 0.78]}}, # what it aims at; tail is None
# without a target or with `tail: none`
],
"warnings": []} # as `cmf validate` words them
comicforge.render.panel_bubble_layout(panel, width, height, bubble_style)
does the same for a bare panel dict and an art-box size you already know.
Panel layout¶
panel_layout(spec, spec_dir=None, on_page=False, ext=".png")¶
Where every panel of a page sits, in page px, without rendering — to place the
panel renders again as the page does. The JSON cmf panel --layout prints and
--all writes as layout.json; see panel for the shape.
image is the area a panel render covers: the panel box, or with on_page
the box plus half a gutter. file is the name --all gives it, with ext.
from comicforge import panel_layout
for p in panel_layout("pages/strip.yaml", on_page=True)["panels"]:
print(p["file"], p["image"])
The manifests¶
from comicforge.library import Library
from comicforge.scene import SceneLibrary
Library("characters").manifest() # same JSON as `cmf characters`
SceneLibrary("scenes").manifest() # same JSON as `cmf scenes`
Use these to drive a generator: read what art exists, then emit specs that only reference variants that are really there.
Measuring things¶
Occasionally you need a size before you render — to pick a row height, or to decide whether a line will fit.
from comicforge import caption
from comicforge.bubbles import bubble_size, text_width
caption.height("Later that afternoon.", {"font_size": 13})
bubble_size("Ahoj!", "speech", max_chars=22, fs=16)
text_width("Ahoj!", 16)
Widths are estimates from per-glyph advance tables, not real font metrics — close enough to lay out a bubble, not exact.
Scaffolding¶
from comicforge.scaffold import init_project
created = init_project("my-comic", force=False) # list of Paths written
Stability¶
The CLI subcommands and the JSON manifests are the stable surface — other tools
depend on them. The Python functions above are stable in practice but not
versioned as an API contract; if you build on the internals (Character,
Pose, Scene, cover, place), pin the engine version.