Spec reference¶
Every key a spec can hold, with its default. For explanations and pictures, the authoring guide covers the same ground with worked examples.
Two spec types share most of this grammar:
type: |
Rendered by | Shape |
|---|---|---|
page (default) |
cmf render |
A grid of rows: and panels on a paper-sized canvas |
scene |
cmf scene |
One background filling the canvas, no grid |
type: is inferred when omitted — a top-level background with no rows: is
taken to be a scene — but declaring it turns a confusing crash into a clear
message, and lets validate check that the type matches the structure.
Page-level keys¶
| Key | Default | Meaning |
|---|---|---|
type |
inferred | page or scene |
title |
— | Bold caption strip across the top of the page |
title_style |
see below | {font_size, color, font} |
page |
A4 |
A4, A5, letter, or [width_mm, height_mm] |
bg |
#ffffff |
Paper colour |
px_per_mm |
4 |
Raster scale. PNG only — PDF is vector |
margin_mm |
12 |
Border around the whole grid |
gutter_mm |
5 |
Gap between panels and between rows |
frame |
see below | Panel outline, page-wide |
bubble_style |
see below | Bubble look, page-wide |
caption_style |
see below | Caption look, page-wide |
library |
— | Character directory. Required for any page |
scenes_dir |
— | Scene directory. Needed only if a scene: is used |
pixel_dir |
— | Sprite directory. Needed only for {art: …} references |
rows |
— | The grid. Required on a page spec |
Page sizes in millimetres: A4 is 210 × 297, A5 is 148 × 210, letter is
216 × 279.
A title takes its font size plus 14 px off the top of the grid. On a scene spec there is no strip — the title is drawn over the art at 22 px by default.
Rows¶
| Key | Default | Meaning |
|---|---|---|
height |
1 |
Relative weight for sharing the vertical space |
height_mm |
— | Fixed height instead of a weight |
panels |
— | Left-to-right list of panels. Required |
Fixed rows are measured first; weighted rows divide what is left. If every row is fixed and they do not fill the page, the remainder stays blank.
Panels¶
| Key | Default | Meaning |
|---|---|---|
width |
1 |
Relative weight for sharing the row's width |
bg |
#fbfaf6 |
Flat background colour |
frame |
inherits page | Outline override for this panel |
caption |
— | Narration band: a string, or {text, max_chars} |
scene |
— | Scene background |
image |
— | Raster background |
actors |
— | List of characters to place |
pixel |
— | List of pixel sprites |
bubbles |
— | List of bubbles |
speakers |
— | {name: [x, y]} — speaker head points in panel fractions, for bubbles' speaker: |
Any other key is a typo. validate flags it; render ignores it silently.
Draw order within a panel¶
bg → caption band → image → scene → pixel → actors (list order) →
bubbles → frame. Everything but the frame is clipped to the panel's rounded
rectangle.
frame¶
| Key | Default | Meaning |
|---|---|---|
width |
3.5 |
Stroke width in px. 0 = no outline |
color |
#21304a |
Stroke colour |
radius |
10 |
Corner radius — also clips the panel contents |
Set page-wide, override per panel. radius applies to the clip whether or not
the outline is drawn.
actors[]¶
| Key | Default | Meaning |
|---|---|---|
char |
required | Character name from the manifest |
pose |
character's default | Which pose to draw |
| any slot name | slot's default | face: happy, arms: wave, … |
x |
0.5 |
Centre, fraction of panel width |
y |
0.6 |
Centre, fraction of panel height |
scale |
0.8 |
Height, as a fraction of panel height |
flip |
false |
Mirror horizontally |
Drawn in list order — later actors cover earlier ones.
bubbles[]¶
| Key | Default | Meaning |
|---|---|---|
text |
required | The line |
kind |
speech |
speech, thought or shout |
speaker |
— | An actor's char in this panel, or a speakers: name (actors win): aligns the bubble and aims the tail at the head |
at |
— | Corner or edge: tl t tr l c r bl b br |
x |
speaker's x, else centre |
Centre, fraction of panel width |
y |
stacked below the previous bubble | Centre, fraction of panel height |
to |
speaker's head | Tail target [x, y] in panel fractions |
max_chars |
22 |
Wrap width in characters |
glue_singles |
bubble_style.glue_singles |
Keep a one-letter word with the word after it |
fs |
bubble_style.font_size |
Font size in px for this bubble |
uppercase |
bubble_style.uppercase |
Force this bubble to caps |
tail |
bubble_style.tail |
wedge, curve, line or none — any kind |
tail_bend |
bubble_style.tail_bend |
Bow of a curve / line tail, -1..1, 0 straight, positive to the right of travel; unset bends gently away from the nearest bubble (or the panel centre) |
tail_gap |
bubble_style.tail_gap |
Px a line tail stops short of its target |
tail_from |
bubble_style.tail_from |
Where the tail leaves: t/b/l/r, a position 0..1 along the auto edge, or {edge, pos} |
Precedence for placement: explicit x/y → at → speaker → stack from the
top. Every bubble is finally clamped to stay inside the panel; one too large to
fit is centred.
On a panel with speakers:, a bubble with a speaker and no x/y/at is
placed in reading order: on its speaker's side, its top at least half the
previous bubble's height below that bubble's top, clear of speaker points and
earlier tails. See bubbles.
at columns (l/c/r) each keep their own top and bottom stack, so tl and
tr sit side by side and bl climbs up from the bottom. c centres and does
not stack.
bubble_style¶
Page-wide; every per-bubble key above overrides it.
| Key | Default | Meaning |
|---|---|---|
font |
DejaVu Sans, Helvetica, Arial, sans-serif |
Font family |
font_size |
16 |
Text size in px |
pad |
14 |
Text inset from the outline |
radius |
18 |
Speech-bubble corner radius (capped at half the height) |
stroke |
#21304a |
Outline colour |
stroke_width |
3 |
Outline weight |
fill |
#ffffff |
Bubble interior |
ink |
#21304a |
Text colour |
uppercase |
false |
Render all bubble text in caps |
em |
1.0 |
Width scale of the text measure — lower for a narrow font |
tail |
wedge |
wedge (short slim tail), curve (tapered, curved sides), line (thin line to just short of the speaker) or none |
tail_bend |
auto | Bow of curve / line tails — see bubbles[] |
tail_gap |
12 |
Px a line tail stops short of its target |
tail_from |
auto | Where tails leave the bubble — see bubbles[] |
caption and caption_style¶
caption_style key |
Default | Meaning |
|---|---|---|
font |
DejaVu Sans stack | Font family |
font_size |
13 |
Text size in px |
ink |
#21304a |
Text colour |
bg |
#ffffff |
Band colour |
pad |
8 |
Text inset from the band edge |
max_chars |
60 |
Wrap width in characters |
glue_singles |
true |
Never end a line with a one-letter word — see Line breaks |
align |
left |
left or center |
rule |
true |
Hairline between art and band, in the frame colour |
uppercase |
false |
Render caption text in caps |
The band takes its height off the art box, so panel fractions stay relative to
the picture. Band height is
lines × font_size × 1.25 + 2 × pad; comicforge.caption.height(text, style)
computes it for you.
scene¶
Cover-scaled: enlarged until it fills the panel in both directions, centred,
overflow clipped. Requires scenes_dir:.
image¶
| Key | Default | Meaning |
|---|---|---|
src |
required | Path, resolved against the spec file's directory |
fit |
cover |
cover (fill + centre-crop) or contain (fit inside + letterbox) |
.png, .jpg/.jpeg, .gif and .webp. Embedded as a base64 data: URI, so
.svg and .pdf output stays self-contained — and carries the image's full
byte weight.
pixel[]¶
| Key | Default | Meaning |
|---|---|---|
art |
— | Sprite name from pixel_dir |
grid |
— | Inline: list of equal-length strings |
palette |
{} |
Inline: character → hex colour |
x |
0.5 |
Centre, fraction of panel width |
y |
0.5 |
Centre, fraction of panel height |
scale |
0.2 |
Height, as a fraction of panel height |
Give either art or grid + palette. . and space are transparent; a
character with no palette entry renders black.
Scene-spec keys¶
A type: scene spec takes every panel key at the top level, plus:
| Key | Default | Meaning |
|---|---|---|
scale |
4 with scene:, 1 with image: |
Output px per artwork unit |
Canvas size is the scene's viewBox × scale, or the image's pixel dimensions ×
scale. There is no page:, no margin, no gutter and no outline — though the
art is still clipped to the default frame.radius of 10 px, so set
frame: {radius: 0} for square corners.
Path resolution¶
| Where | Resolved against |
|---|---|
library:, scenes_dir:, pixel_dir:, a panel's image: |
The spec file's directory |
--library, --scenes, --pixel-dir |
Your current working directory |
Absolute paths are used as-is. A CLI flag overrides the corresponding spec key entirely.
This is why a spec in pages/ writes library: "../characters" and works no
matter where you run cmf from.
When a directory is missing¶
library: is required for any page. Leave it out and you get:
ValueError: library directory is required but was not provided.
Set 'library:' in the spec or pass the corresponding CLI flag.
scenes_dir: fails only when a panel actually asks for a scene. pixel_dir: is
needed only for {art: …} references — inline {grid, palette} sprites work
with no library at all.
A complete page¶
title: "Ztracená kostička"
title_style: {font_size: 22, color: "#21304a"}
page: [190, 95] # a custom [width_mm, height_mm] — or A4 / A5 / letter
px_per_mm: 5
margin_mm: 8
gutter_mm: 5
library: "../../examples/pes/characters"
scenes_dir: "../../examples/pes/scenes"
pixel_dir: "../../examples/pes/pixel"
frame: {width: 3, radius: 8}
bubble_style: {font_size: 13, em: 0.95}
rows:
- panels:
- scene: pokoj
caption: "Bára hledala celé odpoledne."
actors:
- {char: bara, pose: walk, face: neutral, x: 0.42, y: 0.68, scale: 0.48}
bubbles:
- {text: "Kde jenom je?", kind: thought, speaker: bara, max_chars: 12}
- scene: {name: dvur, weather: clear}
caption: "Tom ji našel u boudy."
actors:
- {char: tom, face: happy, arms: point, x: 0.34, y: 0.64, scale: 0.76}
- {char: bara, pose: sit, face: happy, x: 0.76, y: 0.70, scale: 0.38}
bubbles:
- {text: "Tady!", kind: speech, speaker: tom}
- caption: "Konec pátrání."
actors:
- {char: bara, pose: walk, face: happy, x: 0.5, y: 0.6, scale: 0.5}
pixel:
- {art: heart, x: 0.8, y: 0.28, scale: 0.2}
- {art: bone, x: 0.2, y: 0.32, scale: 0.18}
bubbles:
- {text: "Haf!", kind: shout, speaker: bara}
A complete illustration¶
# A standalone illustration: one background filling the canvas, no panel grid.
# Render it with `cmf scene`, not `cmf render`.
type: scene
scale: 2.4
library: "../../examples/pes/characters"
scenes_dir: "../../examples/pes/scenes"
scene: {name: dvur, weather: clear}
actors:
- {char: tom, face: happy, arms: wave, x: 0.36, y: 0.72, scale: 0.62}
- {char: bara, pose: sit, face: happy, x: 0.62, y: 0.82, scale: 0.34}
bubbles:
- {text: "Dobré ráno!", kind: speech, speaker: tom, max_chars: 14}