comicforge 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {comicforge-0.2.0 → comicforge-0.2.1}/.claude/skills/comicforge/SKILL.md +2 -1
- {comicforge-0.2.0 → comicforge-0.2.1}/.claude/skills/comicforge/reference.md +3 -0
- comicforge-0.2.1/.github/workflows/docs.yml +69 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/.gitignore +9 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/CLAUDE.md +26 -1
- {comicforge-0.2.0 → comicforge-0.2.1}/PKG-INFO +20 -8
- {comicforge-0.2.0 → comicforge-0.2.1}/README.md +19 -7
- {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/scaffold.py +2 -0
- comicforge-0.2.1/docs/art/characters.md +205 -0
- comicforge-0.2.1/docs/art/inspire.md +147 -0
- comicforge-0.2.1/docs/art/pixel.md +91 -0
- comicforge-0.2.1/docs/art/scenes.md +145 -0
- comicforge-0.2.1/docs/assets/favicon.svg +5 -0
- comicforge-0.2.1/docs/assets/logo.svg +6 -0
- comicforge-0.2.1/docs/claude.md +118 -0
- comicforge-0.2.1/docs/concepts.md +144 -0
- comicforge-0.2.1/docs/contributing.md +129 -0
- comicforge-0.2.1/docs/demos/arms.yaml +15 -0
- comicforge-0.2.1/docs/demos/bubble-at.yaml +21 -0
- comicforge-0.2.1/docs/demos/bubble-stack.yaml +15 -0
- comicforge-0.2.1/docs/demos/bubbles.yaml +19 -0
- comicforge-0.2.1/docs/demos/captions.yaml +18 -0
- comicforge-0.2.1/docs/demos/coords.yaml +17 -0
- comicforge-0.2.1/docs/demos/faces.yaml +18 -0
- comicforge-0.2.1/docs/demos/frames.yaml +19 -0
- comicforge-0.2.1/docs/demos/grid.yaml +24 -0
- comicforge-0.2.1/docs/demos/illustration.yaml +14 -0
- comicforge-0.2.1/docs/demos/images.yaml +24 -0
- comicforge-0.2.1/docs/demos/lettering.yaml +23 -0
- comicforge-0.2.1/docs/demos/pixel.yaml +28 -0
- comicforge-0.2.1/docs/demos/poses.yaml +12 -0
- comicforge-0.2.1/docs/demos/raster-art/backdrop.png +0 -0
- comicforge-0.2.1/docs/demos/scenes.yaml +13 -0
- comicforge-0.2.1/docs/demos/strip.yaml +37 -0
- comicforge-0.2.1/docs/demos/tutorial-art/characters/pip/base.svg +12 -0
- comicforge-0.2.1/docs/demos/tutorial-art/characters/pip/character.yaml +6 -0
- comicforge-0.2.1/docs/demos/tutorial-art/characters/pip/face-happy.svg +7 -0
- comicforge-0.2.1/docs/demos/tutorial-art/characters/pip/face-neutral.svg +7 -0
- comicforge-0.2.1/docs/demos/tutorial-art/pages/first.yaml +16 -0
- comicforge-0.2.1/docs/demos/tutorial-art/pixel/heart.yaml +10 -0
- comicforge-0.2.1/docs/gallery.md +133 -0
- comicforge-0.2.1/docs/guide/actors.md +157 -0
- comicforge-0.2.1/docs/guide/bubbles.md +201 -0
- comicforge-0.2.1/docs/guide/captions-frames.md +153 -0
- comicforge-0.2.1/docs/guide/illustrations.md +113 -0
- comicforge-0.2.1/docs/guide/images.md +116 -0
- comicforge-0.2.1/docs/guide/pages.md +159 -0
- comicforge-0.2.1/docs/guide/pixel-art.md +126 -0
- comicforge-0.2.1/docs/guide/scenes.md +91 -0
- comicforge-0.2.1/docs/hooks/render_demos.py +105 -0
- comicforge-0.2.1/docs/index.md +127 -0
- comicforge-0.2.1/docs/install.md +126 -0
- comicforge-0.2.1/docs/quickstart.md +191 -0
- comicforge-0.2.1/docs/reference/cli.md +331 -0
- comicforge-0.2.1/docs/reference/python-api.md +179 -0
- comicforge-0.2.1/docs/reference/spec.md +259 -0
- comicforge-0.2.1/docs/reference/troubleshooting.md +182 -0
- comicforge-0.2.1/docs/starting-a-project.md +118 -0
- comicforge-0.2.1/docs/stylesheets/extra.css +188 -0
- comicforge-0.2.1/mkdocs.yml +164 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/poe_tasks.toml +8 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/pyproject.toml +4 -1
- {comicforge-0.2.0 → comicforge-0.2.1}/skills/comicforge/SKILL.md +2 -1
- {comicforge-0.2.0 → comicforge-0.2.1}/skills/comicforge/reference.md +3 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/uv.lock +423 -1
- comicforge-0.2.0/docs/starting-a-project.md +0 -70
- {comicforge-0.2.0 → comicforge-0.2.1}/.github/dependabot.yml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/.github/workflows/ci.yml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/.github/workflows/release.yml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/.python-version +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/__init__.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/__main__.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/bubbles.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/caption.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/cli.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/inspire.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/library.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/pixelart.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/raster.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/render.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/scene.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/comicforge/validate.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/README.md +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/README.md.old +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/bara/character.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/bara/face-happy.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/bara/face-neutral.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/bara/poses/sit/base.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/bara/poses/sit/pose.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/bara/poses/walk/base.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/bara/poses/walk/pose.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/arms-crossed.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/arms-down.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/arms-hips.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/arms-point.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/arms-thumbsup.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/arms-wave.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/base.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/character.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/face-angry.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/face-happy.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/face-laugh.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/face-neutral.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/face-sad.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/face-surprised.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/characters/tom/face-wink.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/pages/dvur-scene.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/pages/kosticka.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/pages/slepice.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/pixel/bone.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/pixel/heart.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/pixel/star.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/pixel/sun.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/references.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/scenes/dvur/base.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/scenes/dvur/scene.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/scenes/dvur/weather-clear.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/scenes/dvur/weather-rain.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/scenes/pokoj/base.svg +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/scenes/pokoj/scene.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/examples/pes/theme.yaml +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/tests/__init__.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/tests/conftest.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_bubbles.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_caption.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_cli.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_inspire.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_library.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_pixelart.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_raster.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_render.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_scaffold.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_scene.py +0 -0
- {comicforge-0.2.0 → comicforge-0.2.1}/tests/test_validate.py +0 -0
|
@@ -17,7 +17,8 @@ description: >
|
|
|
17
17
|
You write a **comic page as a YAML spec**; the engine renders it to SVG/PNG/PDF.
|
|
18
18
|
This file is the authoring contract. For deeper explanations (path resolution,
|
|
19
19
|
how to add characters/scenes, pixel-art format, Python API, full CLI reference)
|
|
20
|
-
see [`reference.md`](reference.md) in this skill directory
|
|
20
|
+
see [`reference.md`](reference.md) in this skill directory, or the documentation
|
|
21
|
+
site at <https://mojzis.github.io/comicforge/>.
|
|
21
22
|
|
|
22
23
|
## Loop
|
|
23
24
|
|
|
@@ -4,6 +4,9 @@ This guide covers everything you need to author comics with ComicForge — for b
|
|
|
4
4
|
humans and LLMs. The [SKILL.md](SKILL.md) file is the quick-reference contract;
|
|
5
5
|
this document goes deeper on structure, paths, and how all the pieces fit together.
|
|
6
6
|
|
|
7
|
+
The same material, with rendered examples beside every spec, is published at
|
|
8
|
+
<https://mojzis.github.io/comicforge/>.
|
|
9
|
+
|
|
7
10
|
---
|
|
8
11
|
|
|
9
12
|
## Engine + self-contained projects
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- "docs/**"
|
|
8
|
+
- "mkdocs.yml"
|
|
9
|
+
- "examples/**" # the gallery renders from the demo project
|
|
10
|
+
- "comicforge/**" # …through the engine, so a renderer change matters
|
|
11
|
+
- "pyproject.toml"
|
|
12
|
+
- ".github/workflows/docs.yml"
|
|
13
|
+
pull_request:
|
|
14
|
+
paths:
|
|
15
|
+
- "docs/**"
|
|
16
|
+
- "mkdocs.yml"
|
|
17
|
+
- "examples/**"
|
|
18
|
+
- "comicforge/**"
|
|
19
|
+
workflow_dispatch:
|
|
20
|
+
|
|
21
|
+
concurrency:
|
|
22
|
+
group: docs-${{ github.ref }}
|
|
23
|
+
cancel-in-progress: true
|
|
24
|
+
|
|
25
|
+
permissions: {}
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
build:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v7
|
|
32
|
+
|
|
33
|
+
# Every image on the site is rendered at build time by
|
|
34
|
+
# docs/hooks/render_demos.py, so the docs build needs cairo just like a
|
|
35
|
+
# normal render does.
|
|
36
|
+
- name: Install cairo
|
|
37
|
+
run: sudo apt-get update && sudo apt-get install -y libcairo2
|
|
38
|
+
|
|
39
|
+
- name: Install uv
|
|
40
|
+
uses: astral-sh/setup-uv@v10.0.1
|
|
41
|
+
with:
|
|
42
|
+
enable-cache: true
|
|
43
|
+
|
|
44
|
+
- name: Sync docs dependencies
|
|
45
|
+
run: uv sync --group docs
|
|
46
|
+
|
|
47
|
+
# --strict fails the build on a broken cross-reference, a dead anchor, a
|
|
48
|
+
# missing snippet, or a demo spec that no longer renders.
|
|
49
|
+
- name: Build site
|
|
50
|
+
run: uv run mkdocs build --strict
|
|
51
|
+
|
|
52
|
+
- uses: actions/upload-pages-artifact@v4
|
|
53
|
+
with:
|
|
54
|
+
path: site/
|
|
55
|
+
|
|
56
|
+
deploy:
|
|
57
|
+
# Pull requests get the build as a check; only main publishes.
|
|
58
|
+
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
|
59
|
+
needs: build
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
environment:
|
|
62
|
+
name: github-pages
|
|
63
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
64
|
+
permissions:
|
|
65
|
+
pages: write
|
|
66
|
+
id-token: write
|
|
67
|
+
steps:
|
|
68
|
+
- id: deployment
|
|
69
|
+
uses: actions/deploy-pages@v4
|
|
@@ -34,3 +34,12 @@ review.html
|
|
|
34
34
|
|
|
35
35
|
# Source archive (redundant after flattening)
|
|
36
36
|
*.zip
|
|
37
|
+
|
|
38
|
+
# Docs site: `mkdocs build` output, and the demo renders the build hook draws
|
|
39
|
+
# (every image on the site is regenerated from a spec — never checked in).
|
|
40
|
+
site/
|
|
41
|
+
docs/assets/renders/
|
|
42
|
+
|
|
43
|
+
# …except the one raster the `image:` demo needs as *input*, which `*.png` above
|
|
44
|
+
# would otherwise swallow.
|
|
45
|
+
!docs/demos/raster-art/*.png
|
|
@@ -21,6 +21,7 @@ comicforge/ The engine — pure code, no bundled art.
|
|
|
21
21
|
examples/pes/ A self-contained demo project (characters, scenes, pixel, pages).
|
|
22
22
|
Develop against the engine the same way a downstream project would.
|
|
23
23
|
skills/comicforge/ The portable authoring skill (SKILL.md + reference.md).
|
|
24
|
+
docs/ The documentation site (MkDocs Material) — see below.
|
|
24
25
|
tests/ pytest suite.
|
|
25
26
|
```
|
|
26
27
|
|
|
@@ -82,6 +83,29 @@ cmf scene examples/pes/pages/dvur-scene.yaml -o dvur.png
|
|
|
82
83
|
cmf panel examples/pes/pages/slepice.yaml -o panels/ --all
|
|
83
84
|
```
|
|
84
85
|
|
|
86
|
+
## Docs site
|
|
87
|
+
|
|
88
|
+
`docs/` is a MkDocs Material site published to GitHub Pages by
|
|
89
|
+
`.github/workflows/docs.yml`.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
uv sync --group docs
|
|
93
|
+
poe docs # serve with live reload
|
|
94
|
+
poe docs-build # mkdocs build --strict — what CI runs
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Every image on the site is rendered at build time** by
|
|
98
|
+
`docs/hooks/render_demos.py`, from a spec in `docs/demos/` or from
|
|
99
|
+
`examples/pes/`. Nothing is a checked-in screenshot, so a renderer change that
|
|
100
|
+
breaks a demo breaks the docs build. To add an illustrated example: write
|
|
101
|
+
`docs/demos/<name>.yaml` (small custom `page:`, `library:` pointing at
|
|
102
|
+
`../../examples/pes/characters`), `cmf validate` it, then reference
|
|
103
|
+
`assets/renders/<name>.png` and include the spec with a `pymdownx.snippets`
|
|
104
|
+
include — see `docs/guide/bubbles.md` for the pattern.
|
|
105
|
+
|
|
106
|
+
`--strict` plus the `validation:` block in `mkdocs.yml` fails the build on a
|
|
107
|
+
broken cross-reference, a dead anchor, a missing snippet or a missing render.
|
|
108
|
+
|
|
85
109
|
## Conventions / invariants
|
|
86
110
|
|
|
87
111
|
- **Content-free engine:** never hardcode an asset name, path, or default project
|
|
@@ -91,7 +115,8 @@ cmf panel examples/pes/pages/slepice.yaml -o panels/ --all
|
|
|
91
115
|
- The CLI subcommands (`render`/`scene`/`panel`/`characters`/`scenes`) are the
|
|
92
116
|
stable surface; `characters`/`scenes` emit JSON manifests other tools depend on.
|
|
93
117
|
- When you change the spec grammar or CLI, update the authoring skill in
|
|
94
|
-
`skills/comicforge/` (both `SKILL.md` and `reference.md`)
|
|
118
|
+
`skills/comicforge/` (both `SKILL.md` and `reference.md`) **and** the affected
|
|
119
|
+
pages under `docs/` in the same change.
|
|
95
120
|
- Demo art is hand/LLM-authored crisp SVG, edited directly in `examples/pes/`
|
|
96
121
|
(there is no procedural generator). Use `comicforge inspire` for *reference*
|
|
97
122
|
images only — never auto-vectorize them; it breaks overlay/anchor registration.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: comicforge
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: A tiny, scriptable comic-page engine — author comics as YAML, render to SVG / PNG / PDF
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
6
|
Requires-Dist: cairosvg>=2.7
|
|
@@ -13,12 +13,18 @@ Description-Content-Type: text/markdown
|
|
|
13
13
|
|
|
14
14
|
# ComicForge
|
|
15
15
|
|
|
16
|
+
**📖 Documentation: <https://mojzis.github.io/comicforge/>**
|
|
17
|
+
|
|
16
18
|
A tiny, scriptable comic-page engine. Characters are a **base SVG + stackable
|
|
17
19
|
variant overlays** (faces, arms, …); a comic page is a **declarative YAML spec**;
|
|
18
20
|
output is **SVG / PNG / PDF**. Built so an LLM (or you) can author pages as plain
|
|
19
|
-
text
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
text.
|
|
22
|
+
|
|
23
|
+
- [Your first comic](https://mojzis.github.io/comicforge/quickstart/) — a project, a character, a strip
|
|
24
|
+
- [Spec reference](https://mojzis.github.io/comicforge/reference/spec/) · [CLI reference](https://mojzis.github.io/comicforge/reference/cli/) · [Gallery](https://mojzis.github.io/comicforge/gallery/)
|
|
25
|
+
- [`skills/comicforge/SKILL.md`](skills/comicforge/SKILL.md) — the authoring contract an LLM reads
|
|
26
|
+
- Working **on the engine**? See [`CLAUDE.md`](CLAUDE.md) and
|
|
27
|
+
[Contributing](https://mojzis.github.io/comicforge/contributing/).
|
|
22
28
|
|
|
23
29
|
## Start a project
|
|
24
30
|
|
|
@@ -32,13 +38,17 @@ cmf init my-comic # scaffold characters/ scenes/ pixel/ pages/
|
|
|
32
38
|
cd my-comic && cmf render pages/hello.yaml
|
|
33
39
|
```
|
|
34
40
|
|
|
35
|
-
See [
|
|
36
|
-
setup, version pinning, and when to make it a real Python
|
|
41
|
+
See [Starting a project](https://mojzis.github.io/comicforge/starting-a-project/)
|
|
42
|
+
for the data-only setup, version pinning, and when to make it a real Python
|
|
43
|
+
project.
|
|
37
44
|
|
|
38
45
|
## Working on the engine
|
|
39
46
|
|
|
40
47
|
```bash
|
|
41
48
|
uv sync # create .venv and install deps
|
|
49
|
+
uv run poe test # the tight loop
|
|
50
|
+
uv run poe check # everything, before opening a PR
|
|
51
|
+
uv run poe docs # serve the documentation site locally
|
|
42
52
|
```
|
|
43
53
|
|
|
44
54
|
## Use
|
|
@@ -114,8 +124,10 @@ pixel_dir: "../pixel" # pixel-art sprites
|
|
|
114
124
|
CLI flags (`--library`, `--scenes`, `--pixel-dir`) override spec keys and are
|
|
115
125
|
treated as relative to the current working directory.
|
|
116
126
|
|
|
117
|
-
|
|
118
|
-
resolution rule, how to add characters/scenes/sprites, and the complete
|
|
127
|
+
The [documentation site](https://mojzis.github.io/comicforge/) covers the full
|
|
128
|
+
path-resolution rule, how to add characters / scenes / sprites, and the complete
|
|
129
|
+
CLI and spec references — every picture on it is rendered from a spec at build
|
|
130
|
+
time.
|
|
119
131
|
|
|
120
132
|
## Design choices
|
|
121
133
|
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
# ComicForge
|
|
2
2
|
|
|
3
|
+
**📖 Documentation: <https://mojzis.github.io/comicforge/>**
|
|
4
|
+
|
|
3
5
|
A tiny, scriptable comic-page engine. Characters are a **base SVG + stackable
|
|
4
6
|
variant overlays** (faces, arms, …); a comic page is a **declarative YAML spec**;
|
|
5
7
|
output is **SVG / PNG / PDF**. Built so an LLM (or you) can author pages as plain
|
|
6
|
-
text
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
text.
|
|
9
|
+
|
|
10
|
+
- [Your first comic](https://mojzis.github.io/comicforge/quickstart/) — a project, a character, a strip
|
|
11
|
+
- [Spec reference](https://mojzis.github.io/comicforge/reference/spec/) · [CLI reference](https://mojzis.github.io/comicforge/reference/cli/) · [Gallery](https://mojzis.github.io/comicforge/gallery/)
|
|
12
|
+
- [`skills/comicforge/SKILL.md`](skills/comicforge/SKILL.md) — the authoring contract an LLM reads
|
|
13
|
+
- Working **on the engine**? See [`CLAUDE.md`](CLAUDE.md) and
|
|
14
|
+
[Contributing](https://mojzis.github.io/comicforge/contributing/).
|
|
9
15
|
|
|
10
16
|
## Start a project
|
|
11
17
|
|
|
@@ -19,13 +25,17 @@ cmf init my-comic # scaffold characters/ scenes/ pixel/ pages/
|
|
|
19
25
|
cd my-comic && cmf render pages/hello.yaml
|
|
20
26
|
```
|
|
21
27
|
|
|
22
|
-
See [
|
|
23
|
-
setup, version pinning, and when to make it a real Python
|
|
28
|
+
See [Starting a project](https://mojzis.github.io/comicforge/starting-a-project/)
|
|
29
|
+
for the data-only setup, version pinning, and when to make it a real Python
|
|
30
|
+
project.
|
|
24
31
|
|
|
25
32
|
## Working on the engine
|
|
26
33
|
|
|
27
34
|
```bash
|
|
28
35
|
uv sync # create .venv and install deps
|
|
36
|
+
uv run poe test # the tight loop
|
|
37
|
+
uv run poe check # everything, before opening a PR
|
|
38
|
+
uv run poe docs # serve the documentation site locally
|
|
29
39
|
```
|
|
30
40
|
|
|
31
41
|
## Use
|
|
@@ -101,8 +111,10 @@ pixel_dir: "../pixel" # pixel-art sprites
|
|
|
101
111
|
CLI flags (`--library`, `--scenes`, `--pixel-dir`) override spec keys and are
|
|
102
112
|
treated as relative to the current working directory.
|
|
103
113
|
|
|
104
|
-
|
|
105
|
-
resolution rule, how to add characters/scenes/sprites, and the complete
|
|
114
|
+
The [documentation site](https://mojzis.github.io/comicforge/) covers the full
|
|
115
|
+
path-resolution rule, how to add characters / scenes / sprites, and the complete
|
|
116
|
+
CLI and spec references — every picture on it is rendered from a spec at build
|
|
117
|
+
time.
|
|
106
118
|
|
|
107
119
|
## Design choices
|
|
108
120
|
|
|
@@ -82,6 +82,8 @@ A **data-only** ComicForge project: YAML specs + SVG / pixel art, no Python.
|
|
|
82
82
|
Paths in a spec resolve relative to the spec file, so `library: ../characters`
|
|
83
83
|
works from anywhere. See the skill for the full spec grammar, and add your own
|
|
84
84
|
characters under `characters/` to start placing actors.
|
|
85
|
+
|
|
86
|
+
Documentation: https://mojzis.github.io/comicforge/
|
|
85
87
|
"""
|
|
86
88
|
|
|
87
89
|
_GITIGNORE = "output/\n"
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Making a character
|
|
2
|
+
|
|
3
|
+
A character is a directory of SVG files and one manifest. No code, no build
|
|
4
|
+
step, no registration anywhere in the engine — drop the directory into
|
|
5
|
+
`characters/` and it exists.
|
|
6
|
+
|
|
7
|
+
There are two on-disk shapes. Use the simple one until you need more than one
|
|
8
|
+
body.
|
|
9
|
+
|
|
10
|
+
## Single-pose (flat)
|
|
11
|
+
|
|
12
|
+
Everything drawn in one `viewBox`:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
characters/<name>/
|
|
16
|
+
base.svg the body
|
|
17
|
+
<slot>-<variant>.svg overlays, in the SAME viewBox
|
|
18
|
+
character.yaml the manifest
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```yaml title="character.yaml"
|
|
22
|
+
name: tom # internal id — must match the directory name
|
|
23
|
+
label: Tom # display name, used in the manifest
|
|
24
|
+
viewbox: [200, 320] # the shared local canvas [width, height]
|
|
25
|
+
default:
|
|
26
|
+
face: neutral # used whenever a spec omits the slot
|
|
27
|
+
arms: down
|
|
28
|
+
slots:
|
|
29
|
+
arms: [down, wave, point, crossed, hips, thumbsup]
|
|
30
|
+
face: [neutral, happy, surprised, sad, angry, laugh, wink]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Every file — base and overlays alike — declares the same `viewBox`. That is the
|
|
34
|
+
whole registration mechanism: the engine strips each SVG to its inner markup and
|
|
35
|
+
stacks them in one coordinate system, so a mouth drawn at `(100, 92)` lands at
|
|
36
|
+
`(100, 92)` on the body.
|
|
37
|
+
|
|
38
|
+
The stacking order is the slot order in `character.yaml`, so a slot listed later
|
|
39
|
+
draws over one listed earlier. Put `face` after `arms` if a raised hand should
|
|
40
|
+
never cover the mouth.
|
|
41
|
+
|
|
42
|
+
### Drawing rules that matter
|
|
43
|
+
|
|
44
|
+
- **One `viewBox`, no exceptions.** An overlay with a different viewBox will be
|
|
45
|
+
scaled to the pose's and land in the wrong place.
|
|
46
|
+
- **Draw only the part that varies.** `face-happy.svg` contains eyes and a
|
|
47
|
+
mouth, not a head. The head is in `base.svg`, and drawing it twice means one
|
|
48
|
+
outline peeking out from behind the other.
|
|
49
|
+
- **Leave the head alone across expressions.** Same size, same position — the
|
|
50
|
+
variation is in the features.
|
|
51
|
+
- **Name files `<slot>-<variant>.svg`.** The file name *is* the lookup; a slot
|
|
52
|
+
listed in the manifest with no matching file fails at load time.
|
|
53
|
+
|
|
54
|
+
Here is a complete, minimal character — the one from
|
|
55
|
+
[Your first comic](../quickstart.md):
|
|
56
|
+
|
|
57
|
+
=== "base.svg"
|
|
58
|
+
|
|
59
|
+
```svg
|
|
60
|
+
--8<-- "demos/tutorial-art/characters/pip/base.svg"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
=== "face-neutral.svg"
|
|
64
|
+
|
|
65
|
+
```svg
|
|
66
|
+
--8<-- "demos/tutorial-art/characters/pip/face-neutral.svg"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
=== "face-happy.svg"
|
|
70
|
+
|
|
71
|
+
```svg
|
|
72
|
+
--8<-- "demos/tutorial-art/characters/pip/face-happy.svg"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
=== "character.yaml"
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
--8<-- "demos/tutorial-art/characters/pip/character.yaml"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Multiple poses
|
|
82
|
+
|
|
83
|
+
When the **body** changes — sitting versus walking, legs in a different place —
|
|
84
|
+
one viewBox is no longer enough. Give each pose its own base, and let the
|
|
85
|
+
expressions stay shared.
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
characters/<name>/
|
|
89
|
+
character.yaml identity + SHARED slots + default + the pose list
|
|
90
|
+
<slot>-<variant>.svg SHARED overlays, drawn around the canonical anchor
|
|
91
|
+
poses/
|
|
92
|
+
sit/
|
|
93
|
+
pose.yaml viewbox, anchor, optional pose-specific slots
|
|
94
|
+
base.svg this pose's body
|
|
95
|
+
walk/
|
|
96
|
+
pose.yaml
|
|
97
|
+
base.svg
|
|
98
|
+
<slot>-<variant>.svg optional overlays that only exist in this pose
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```yaml title="character.yaml"
|
|
102
|
+
name: bara
|
|
103
|
+
label: Bára
|
|
104
|
+
anchor: [120, 72] # the canonical point shared overlays draw around
|
|
105
|
+
slots: {face: [neutral, happy]} # SHARED across poses
|
|
106
|
+
default: {pose: sit, face: neutral}
|
|
107
|
+
poses: [sit, walk]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```yaml title="poses/walk/pose.yaml"
|
|
111
|
+
viewbox: [240, 180]
|
|
112
|
+
anchor: [132, 64] # where THIS pose's head centre lands
|
|
113
|
+
slots: {arms: [trot]} # optional, pose-specific
|
|
114
|
+
default: {arms: trot}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### The anchor
|
|
118
|
+
|
|
119
|
+
The anchor is the trick that makes one face file work on every body. It is a
|
|
120
|
+
reference point — the head centre is the natural choice — that you agree on
|
|
121
|
+
once:
|
|
122
|
+
|
|
123
|
+
- The **character's** `anchor` is where the head centre sits in the coordinate
|
|
124
|
+
system the shared overlays are drawn in.
|
|
125
|
+
- Each **pose's** `anchor` is where that same head centre sits in *that pose's*
|
|
126
|
+
viewBox.
|
|
127
|
+
|
|
128
|
+
When composing, the engine shifts every shared overlay by
|
|
129
|
+
`pose.anchor − character.anchor`. So `face-happy.svg`, authored once, lands
|
|
130
|
+
correctly whether Bára is sitting or walking.
|
|
131
|
+
|
|
132
|
+
<figure class="cf-demo" markdown>
|
|
133
|
+

|
|
134
|
+
<figcaption>Two bodies, one set of faces. The <code>happy</code> face in the middle and right panels is the same file as the one drawn for <code>sit</code>.</figcaption>
|
|
135
|
+
</figure>
|
|
136
|
+
|
|
137
|
+
!!! warning "Translate only"
|
|
138
|
+
|
|
139
|
+
The shift is a translation and nothing else — no scaling, no rotation. Keep
|
|
140
|
+
the head the **same size** across every pose and move only its position. A
|
|
141
|
+
pose that draws a bigger head will wear a face that is too small for it.
|
|
142
|
+
|
|
143
|
+
### Composition order
|
|
144
|
+
|
|
145
|
+
For one actor, the engine assembles:
|
|
146
|
+
|
|
147
|
+
1. the chosen pose's `base.svg`
|
|
148
|
+
2. that pose's **pose-specific** overlays, in `pose.yaml` slot order
|
|
149
|
+
3. the **shared** overlays, in `character.yaml` slot order, each wrapped in the
|
|
150
|
+
anchor translate
|
|
151
|
+
|
|
152
|
+
Then the whole thing is scaled to the requested height and centred at the
|
|
153
|
+
requested point.
|
|
154
|
+
|
|
155
|
+
### Flat is a special case of posed
|
|
156
|
+
|
|
157
|
+
A flat character is the posed model with one implicit pose whose anchor is
|
|
158
|
+
`[0, 0]` and whose viewBox is the character's. Both render through exactly the
|
|
159
|
+
same code path, which is why adding poses to an existing flat character later is
|
|
160
|
+
a restructuring rather than a rewrite.
|
|
161
|
+
|
|
162
|
+
## Adding to an existing character
|
|
163
|
+
|
|
164
|
+
| You want | Do this |
|
|
165
|
+
|---|---|
|
|
166
|
+
| A new shared expression | Draw it around the canonical anchor, save as `<slot>-<variant>.svg` at the character root, add the variant to `slots` in `character.yaml` |
|
|
167
|
+
| A new pose | Add `poses/<name>/` with its own `base.svg` and `pose.yaml` (set its `anchor`), and list the name under `poses:` |
|
|
168
|
+
| A new overlay on a flat character | Save `<slot>-<variant>.svg` in the shared viewBox, add it to `slots` |
|
|
169
|
+
| A whole new slot | Add the slot to `slots`, add a variant file for each value, and give it an entry in `default` |
|
|
170
|
+
|
|
171
|
+
No code changes in any case.
|
|
172
|
+
|
|
173
|
+
## Checking your work
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
cmf characters --library characters # what does the engine think exists?
|
|
177
|
+
cmf character bara walk happy --library characters
|
|
178
|
+
cmf character tom --library characters # everything at its default
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`cmf character` renders one character alone on a plain canvas, cropped to its
|
|
182
|
+
pose — the fastest way to see whether an overlay lines up. It writes the full
|
|
183
|
+
render plus a smaller `<name>.small.png` companion; when an agent is reading the
|
|
184
|
+
result back, the small one costs far fewer tokens.
|
|
185
|
+
|
|
186
|
+
Common failures and what they mean:
|
|
187
|
+
|
|
188
|
+
| Symptom | Cause |
|
|
189
|
+
|---|---|
|
|
190
|
+
| `is not a well-formed <svg>…</svg> file` | The overlay is missing its outer `<svg>` element |
|
|
191
|
+
| Overlay lands in the wrong place | Its `viewBox` differs from the base's, or the pose anchor is wrong |
|
|
192
|
+
| `slot 'face' has no variant 'grin'` | The variant is not listed in the manifest — or the file name does not match |
|
|
193
|
+
| A double outline around the head | The overlay redraws part of the base |
|
|
194
|
+
| The face is too small on one pose | That pose draws the head at a different size — anchors translate, they do not scale |
|
|
195
|
+
|
|
196
|
+
## Where the art comes from
|
|
197
|
+
|
|
198
|
+
The demo art in `examples/pes/` is hand- and LLM-authored SVG, edited directly.
|
|
199
|
+
There is no procedural generator, and there is no vectorizer in the pipeline.
|
|
200
|
+
|
|
201
|
+
A visual model is genuinely useful for *inspiration* — generate a reference
|
|
202
|
+
image, then author the crisp SVG from it. [`cmf inspire`](inspire.md) does that
|
|
203
|
+
part. What you must not do is auto-vectorize the result: tracing produces
|
|
204
|
+
thousands of paths in one flat layer, which destroys the slot structure and the
|
|
205
|
+
anchor registration that make the character poseable at all.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Reference images
|
|
2
|
+
|
|
3
|
+
`cmf inspire` blends a project-wide **theme** with per-item **descriptions** and
|
|
4
|
+
asks an image model to paint them. The output is **reference art to draw from** —
|
|
5
|
+
never a shipped asset.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cmf inspire references.yaml --dry-run # compose the prompts, call nothing
|
|
9
|
+
cmf inspire references.yaml --review # generate, plus a review.html grid
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
!!! danger "Do not auto-vectorize the result"
|
|
13
|
+
|
|
14
|
+
Tracing a generated image produces thousands of paths in one flat layer.
|
|
15
|
+
That destroys the slot structure and the anchor registration that make a
|
|
16
|
+
character poseable — you end up with a picture, not a character. Look at the
|
|
17
|
+
reference, then author the SVG. See
|
|
18
|
+
[Making a character](characters.md).
|
|
19
|
+
|
|
20
|
+
## Two files
|
|
21
|
+
|
|
22
|
+
### `theme.yaml` — the project's look
|
|
23
|
+
|
|
24
|
+
Applied to every image, so the whole cast comes back in one style.
|
|
25
|
+
|
|
26
|
+
```yaml title="theme.yaml"
|
|
27
|
+
style: >
|
|
28
|
+
Hand-drawn children's-book comic illustration. Bold, clean black ink outlines
|
|
29
|
+
with flat cheerful fills and simple friendly shapes. Light, uncluttered
|
|
30
|
+
background. Centered subject filling the frame.
|
|
31
|
+
|
|
32
|
+
palette:
|
|
33
|
+
- "#f4d35e" # sunny yellow
|
|
34
|
+
- "#ee964b" # warm orange
|
|
35
|
+
- "#0d3b66" # deep blue
|
|
36
|
+
- "#faf0ca" # cream
|
|
37
|
+
- "#f95738" # coral
|
|
38
|
+
|
|
39
|
+
mood: warm, playful, gentle
|
|
40
|
+
|
|
41
|
+
negative: No text, no letters, no words, no watermark, no signature.
|
|
42
|
+
|
|
43
|
+
aspect_ratio: "1:1"
|
|
44
|
+
# model: google/imagen-3
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
| Key | Default | Meaning |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `style` | — | The look, in words. Prepended to every prompt |
|
|
50
|
+
| `palette` | — | Hex colours fed to the model as a colour scale |
|
|
51
|
+
| `mood` | — | One line of tone |
|
|
52
|
+
| `negative` | no text / watermark / signature | What to avoid |
|
|
53
|
+
| `aspect_ratio` | `"1:1"` | Passed to the model |
|
|
54
|
+
| `model` | `google/imagen-3` | Any Replicate model id |
|
|
55
|
+
|
|
56
|
+
A missing `theme.yaml` is not an error — you just get the defaults.
|
|
57
|
+
|
|
58
|
+
### `references.yaml` — the things to depict
|
|
59
|
+
|
|
60
|
+
```yaml title="references.yaml"
|
|
61
|
+
items:
|
|
62
|
+
- id: tom
|
|
63
|
+
prompt: >
|
|
64
|
+
A cheerful 8-year-old boy named Tom in a striped t-shirt and shorts,
|
|
65
|
+
full body, standing, neutral friendly expression, simple cartoon style.
|
|
66
|
+
|
|
67
|
+
- id: dvur
|
|
68
|
+
prompt: >
|
|
69
|
+
A sunny Czech village farmyard: a wooden fence, a small chicken coop,
|
|
70
|
+
a few hens pecking, a cobbled yard, a tree to one side.
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Each entry needs an id (`id` or `name`) and a description (`prompt`,
|
|
74
|
+
`description` or `desc`). A bare list without the `items:` key works too. The id
|
|
75
|
+
becomes the filename.
|
|
76
|
+
|
|
77
|
+
## The composed prompt
|
|
78
|
+
|
|
79
|
+
`style` → `Subject: <prompt>` → palette → mood → negative, joined by blank
|
|
80
|
+
lines:
|
|
81
|
+
|
|
82
|
+
```text title="references/tom.prompt.txt"
|
|
83
|
+
Hand-drawn children's-book comic illustration. Bold, clean black ink outlines
|
|
84
|
+
with flat cheerful fills and simple friendly shapes. Light, uncluttered
|
|
85
|
+
background. Centered subject filling the frame.
|
|
86
|
+
|
|
87
|
+
Subject: A cheerful 8-year-old boy named Tom in a striped t-shirt and shorts,
|
|
88
|
+
full body, standing, neutral friendly expression, simple cartoon style.
|
|
89
|
+
|
|
90
|
+
Use this color palette: #f4d35e, #ee964b, #0d3b66, #faf0ca, #f95738.
|
|
91
|
+
|
|
92
|
+
Mood: warm, playful, gentle.
|
|
93
|
+
|
|
94
|
+
No text, no letters, no words, no watermark, no signature.
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The sidecar is written next to every image, so you can always see exactly what
|
|
98
|
+
produced a picture. `--dry-run` writes only the sidecars — use it to iterate on
|
|
99
|
+
wording without spending a call or needing a token.
|
|
100
|
+
|
|
101
|
+
## Output and flags
|
|
102
|
+
|
|
103
|
+
Each item writes `<out_dir>/<id>.png` and `<out_dir>/<id>.prompt.txt`.
|
|
104
|
+
`theme.yaml` and the output directory default to siblings of the references
|
|
105
|
+
spec.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
cmf inspire references.yaml -o refs/ # choose the output directory
|
|
109
|
+
cmf inspire references.yaml --theme t.yaml # a different theme
|
|
110
|
+
cmf inspire references.yaml --only tom,dvur # a subset
|
|
111
|
+
cmf inspire references.yaml --force # regenerate images that already exist
|
|
112
|
+
cmf inspire references.yaml --review # also write review.html
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
By default an item whose `.png` already exists is skipped, so re-running after
|
|
116
|
+
adding one entry costs one call. `--review` writes a `review.html` grid of every
|
|
117
|
+
image beside its prompt — the fastest way to judge a batch and decide what to
|
|
118
|
+
re-word.
|
|
119
|
+
|
|
120
|
+
## Setup for live generation
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
uv tool install "comicforge[inspire]" # adds replicate + python-dotenv
|
|
124
|
+
export REPLICATE_API_TOKEN=... # or a .env beside the spec
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The token is read from the environment, or from a `.env` file next to the
|
|
128
|
+
references spec or in the current directory. Live calls are paced — the default
|
|
129
|
+
model caps at roughly six requests a minute, so a batch takes about eleven
|
|
130
|
+
seconds per image.
|
|
131
|
+
|
|
132
|
+
Neither the extra nor the token is needed for `--dry-run`.
|
|
133
|
+
|
|
134
|
+
## How to actually use the output
|
|
135
|
+
|
|
136
|
+
1. Write the description, run `--dry-run`, read the composed prompt.
|
|
137
|
+
2. Generate a handful with `--review` and pick the one that reads best as a
|
|
138
|
+
*shape* — silhouette, proportions, the pose you want as a base.
|
|
139
|
+
3. Author `base.svg` by hand from it: clean outlines, flat fills, in one
|
|
140
|
+
viewBox.
|
|
141
|
+
4. Draw the overlays around the same head position, and write
|
|
142
|
+
`character.yaml`.
|
|
143
|
+
5. Keep the reference in `references/` as documentation of the intent. It never
|
|
144
|
+
goes into `characters/`.
|
|
145
|
+
|
|
146
|
+
Step 3 is the work, and there is no shortcut through it. The reference exists to
|
|
147
|
+
make a design decision, not to become the asset.
|