Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

704 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FrameForge v2

FrameForge v2 (2.11.0) keeps its documents, grammar, schema, prose, and Python code in sync — the Pydantic models are the source of truth and everything else is generated from or checked against them.

FrameForge aims to be an agent-native visual-authoring substrate — one structured, programmable foundation (SDK + MCP) for producing professional visual assets across documents, decks, diagrams, books, and letters today, extending toward UIs, vector graphics, logos, and design systems. See PURPOSE.md for the full why and scope.

Status: FrameForge v2 is a proposed, not-yet-conformantly-implemented system. The prose and grammar are design targets to verify. The parts you can actually run — the models, the generated schema, the validator, and the codemod — are the parts to trust.

Layout

src/frameforge/               ← integration package: conform.py, cli.py, fontpack.py.
docs/schema/
  frameforge-v2.schema.json   ← GENERATED from the models (119 $defs). Do not hand-edit.
  build_schema.py             ← regenerates the schema; `--check` fails if it drifts.
docs/grammar/
  frameforge-v2.ebnf          ← the consolidated CORE grammar (base + P1–P4); styling deferred to the module.
  frameforge-v2-style.ebnf    ← the AUTHORITATIVE CSS style module (adopted verbatim at 2.2.0).
docs/spec/frameforge-v2-spec.md ← the normative prose (folds P1–P4 + the style module + cascade + corrections).
tooling/
  validate.py                 ← structural (models) + static/geometric rules the schema can't express.
  codemod.py                  ← migrates a document to HEAD (stroke split, size→sizing, gradient, aliases).
  render_fixtures.py          ← SVG render CLI driver (re-exports the Renderer; `--check-overflow` text-fit gate).
  render_chromium.py          ← optional Headless-Chromium SVG→PNG raster renderer (CSS-fidelity path).
  render_fg_doc.py            ← the matplotlib PROXY renderer, patched to HEAD (sanity check only).
  pdf_to_frameforge_yml.py    ← optional PyMuPDF PDF → fixed-layout FrameForge YAML extractor.
  (HTML export lives in the package → `ff-render --to html`. It is driven by the SAME
   builder as SVG through `painters/html.py` (`HtmlPainter`) and assembled by
   `backends/html.py` (the DocumentRenderer port), so its object-type coverage equals
   SVG's — semantic HTML shell, inline SVG artwork, contract- and golden-tested.)
  gen_status.py               ← GENERATES docs/FIXTURE-STATUS.md from the validator (`--check` gates drift).
  gen_docs.py                 ← GENERATES the docs-site pages (reference/gallery/spec/grammar plus SDK docs).
  gen_capability_manifest.py  ← GENERATES docs/capability-manifest.json (core/SDK/MCP status per capability).
  check_grammar_sync.py       ← GATES grammar ⇄ models drift (core profile); `--strict` for full parity.
  check_accessibility.py      ← GATES page reading_order integrity; warns on missing image alt (a11y).
  render_golden.py            ← GATES b1/ oracle SVG output against a pinned hash lock (golden).
tests/fixtures/               ← the fixture corpus (declared versions span 2.0.0–2.4.x; top-level YAML gated by `make validate`, the b1/ oracle by test_head.py).
  b1/                         ← the 8 AUTHORITATIVE fixtures (the oracle the tests assert against).
conftest.py                   ← shared pytest bootstrap (sys.path + the frameforge shadow-module rule).
tests/
  test_head.py                ← assertions: authoritative fixtures validate, schema in sync, style surface, P3.
  test_docs_in_sync.py        ← doc drift gate: numbers, Layout paths, generated-doc policy, fixture status.
  test_doc_examples.py        ← validates every complete FrameForge example shown in the prose.
docs/ + mkdocs.yml            ← the MkDocs site: `index.md` is hand-written, `sdk*.md` are committed generated snapshots, transient generated pages are ignored, and non-site sources (schema/spec/grammar, plus seed/ and decisions/) are `exclude_docs`.
docs/capability-manifest.json ← GENERATED machine-readable capability status {core, sdk, mcp} (ADR-0002).
docs/error-codes.md           ← every validator finding code + SDK rule_id: meaning and fix (sync-tested).
docs/output-space.md          ← what FrameForge can generate: the verified-today backends + the conceptual output space (anchor drift-gated by tests/test_output_space_doc.py).
docs/BRAND.md                 ← the brand guideline (proposal); §3 governs the generated logo.
docs/FIXTURE-STATUS.md        ← GENERATED validator status for the delivered fixtures (gen_status.py).
docs/codebase-standards.md    ← the elevated engineering bar, status-tagged (Enforced / Adopted / Target).
AGENTS.md                     ← programmatic CLI/tooling reference (make targets, tooling flags, workflows).
Dockerfile + docker/          ← the font-rich SDK/MCP runtime image (`make docker-build`).
CHANGELOG.md                  ← version, the breaking change + migration, conformance classes, rec. resolution.
pyproject.toml + uv.lock      ← the real hatchling package since 2.5.0 (`[tool.uv] package = true`): `uv sync` installs it editable with the `ff-render`/`fg-font` console scripts; dep groups dev/render/browser/pdf/pdfout/metrics/mcp/vision.
Makefile + .github/workflows/ ← `make check` = the local gate; CI mirrors it (+ a docs build/deploy job).

The contract, the authoring SDK, and the renderer are separate distributions, not directories in this tree: frameforge-api (SOURCE OF TRUTH for the Pydantic document contract), frameforge-sdk (authoring, measurement, static validation) and frameforge-render (the renderer DDD boundary: domain, application, infrastructure). uv sync installs them from their repositories at the commits uv.lock pins — a checkout beside this one is not required, and is not what a build resolves.

Companion repositories

  • frameforge-viewer is the independent React display package extracted from this repository with its viewer history preserved. FrameForge publishes a versioned frameforge-render-bundle manifest plus measured SVG/PNG/PDF artifacts; the artifact viewer consumes those outputs without needing the viewing host’s fonts. Its direct-document mode remains an explicitly separate browser renderer.
  • frameforge-fonts owns the exact-face closure and shared metrics provider. SDK measurement/validation, frameforge.conform, the renderer, and MCP can all consume one provider over fp_version: 1 bytes.

The companions own their npm/Python dependencies, CI, releases, and upstream compatibility checks. They are siblings, not subdirectories of this codebase.

The sync guarantee (what "in sync" means here, concretely)

  1. Schema ⇄ models. docs/schema/frameforge-v2.schema.json is produced by Document.model_json_schema(). build_schema.py --check returns non-zero if the committed file differs from a fresh build — so they cannot silently drift.
  2. Validator ⇄ models. validate.py validates against the same Document model, then layers the §3.3/§3.6/§9.6 rules.
  3. Codemod ⇄ validator. The codemod's migrations are exactly the breaking/renamed forms the validator rejects; running it makes a legacy document pass.
  4. Grammar ⇄ models. The EBNF is a view of the models (the source). This is now enforced, not trusted: tooling/check_grammar_sync.py (the grammar-check gate) introspects the models and diffs the EBNF, failing CI on core-profile drift — a mismatched object/flow type discriminator or a divergent enum. Out-of-profile grammar (charts, the UML zoo, connectors) is reported as a non-blocking warning; --strict demands full parity.

Run it

The project is managed with uv. uv sync once creates .venv with the runtime deps and the dev group (see pyproject.toml), and installs the frameforge package itself — putting the ff-render and fg-font console scripts on PATH; prefix commands with uv run.

A fresh git clone is all it needs: the family packages resolve from their own repositories at the commits uv.lock pins. To develop the engine against a sibling checkout instead of the pinned commit, make dev-link reinstalls the ones you have next to this tree as editable; a later uv sync undoes it.

uv sync                                    # create/populate .venv

# schema is generated and in sync
uv run python docs/schema/build_schema.py --check

# validate the delivered tracked fixtures — 53/53 zero errors in docs/FIXTURE-STATUS.md
# text-fit diagnostics run by default; add --no-text-fit only for structure-only checks
make validate

# migrate a legacy v2 document to HEAD
uv run python tooling/codemod.py path/to/legacy.fg.json --in-place --bump

# the HEAD assertions (13/13 green)
uv run python tests/test_head.py
# or the full pytest suite (also run by `make check`)
uv run pytest

# SVG proxy renderer (dependency-free core) -> out/render/index.html
uv run python tooling/render_fixtures.py --all

# optional browser-fidelity raster renderer (install Playwright + Chromium first)
uv sync --group browser
uv run playwright install chromium
uv run python tooling/render_chromium.py tests/fixtures/effects.fg.yaml --out out/chromium

# optional PDF text/layout extractor (install PyMuPDF first)
uv sync --group pdf
uv run python tooling/pdf_to_frameforge_yml.py input.pdf output.frameforge.yml

# optional MCP server for AI feedback loops:
# Python SDK code -> generated FrameForge YAML -> validation + rendered SVG/PNG,
# plus the coordinate-aware measurement layer (see "Subsystems" below)
uv sync --group mcp
uv run --group mcp python -m frameforge_mcp
# or: make mcp

# optional local web UI over the same MCP feedback functions
make live                                  # http://127.0.0.1:8789
# choose a port when needed: make live LIVE_PORT=8790

# the whole local gate — every gate on the Makefile `check` target
make check

# build & browse the generated documentation site (Material theme, live reload)
make docs-serve

For positioned text, use the shared authoring/layout metric contract rather than an empirical slack factor:

from frameforge_sdk import closure_metrics, fit_width, validate_static_rules
from frameforge.conform import render_pages_with_stats

family = ["Inter", "DejaVu Sans", "sans-serif"]
provider = closure_metrics(
    "book.fp", strict=True,
    generics={"sans-serif": "Inter"},
)
w = fit_width(
    "Advanced   SQL", font_family=family, font_size=13,
    metrics_provider=provider)
layer.text([64, 64, w, 22], "Advanced   SQL",
           style={"font_family": family, "font_size": 13, "white_space": "pre"})
report = validate_static_rules(document, metrics_provider=provider)
svgs, stats = render_pages_with_stats(document, metrics_provider=provider)

Install the local umbrella group with uv sync --group metrics; published consumers install frameforge-sdk[metrics] plus frameforge-render[fonts]. The provider outranks real_metrics, and diagnostics report metrics_mode: closure. Without a provider, the historical host-bound real and deterministic estimate modes remain available. MCP render and fit_text calls accept the closure path as font_closure and an optional font_generics map.

See the closure migration guide and the runnable examples/closure_metrics.py.

The matplotlib proxy renderer (tooling/render_fg_doc.py) needs the extra render dependency group:

uv sync --group render                     # adds matplotlib + pillow

Subsystems beyond the core

  • MCP measurement layer (frameforge/mcp/ + frameforge/vision/). Besides the author→render loop, the MCP server exposes a coordinate-aware raster→vector reconstruction toolset: measure_image (grids/rulers/regions/ landmarks/zoom crops), mark_points, overlay_images, a stateful workspace pin board, construct_vectors, vectorize_image (region/outline/potrace/ layers tracing), score_reconstruction (numeric edge-match convergence), and map_coordinates (homography/warp). The live tool registry is enumerated in docs/capability-manifest.json; it needs the vision dependency group. Every runtime knob (FRAMEFORGE_* env vars — session/publish roots, transport budgets, render ceilings, Chromium flags, the VLM lane) is documented with its real default in the gated table at the frameforge-mcp README; container-only knobs live in docker/README.md.
  • Vector Construction Coach (src/frameforge/coach/). A staged construction loop over the SDK — style-grammar checks, layer-order rules, a silhouette gate, SVG ingest/cleaning, and figure-proportion helpers. It coaches construction discipline; it is not a curve-drawing engine. Demos: coach_demo.py and the other coach_* clients in the frameforge-example repo.
  • Docker runtime (Dockerfile + docker/). The font-rich canonical SDK/MCP runtime (thousands of font families baked in) for font-faithful raster verification: make docker-build / docker-mcp / docker-shell / docker-fonts; client wiring in docker/mcp.docker.json.
  • Live UI (frameforge/live/). A local web view over the same MCP feedback functions for humans watching/driving a session: make live (http://127.0.0.1:8789, port via LIVE_PORT).
  • Font determinism toolchain (src/frameforge/fontpack.py + tooling/render_chromium.py; ADR-0004). fg-font--list resolvable families, --check DOC (non-zero exit if a content font would substitute), --pack DOC --out P.fp (portable pack of the exact TTFs + sha256 manifest; --fetch provisions missing families from the open Google Fonts corpus so packs build on thin hosts), --install P.fp (scoped fontconfig) — and render_chromium.py --font-pack P.fp, which scopes fontconfig to the pack before Chromium launches so measure == render on any host. Installed as a console script since 2.5.0: uv run fg-font … (or the make font-* targets; tooling/fg_font.py remains as a direct-run shim). Silent font substitution is banned: layout emits a font_substitution warning to diagnostics and stderr whenever a requested face is missing.

Programmatic entry points for all of the above — every make target and tooling CLI with flags — are catalogued in AGENTS.md; the examples cookbook lives in the sibling frameforge-example repo (docs/examples.md there).

How the pre-HEAD (2.0.x) bundle was folded in (2.1.0–2.2.0)

At 2.2.0 the authoritative CSS style module is adopted verbatim (Style is the ~80-property bag; TextStyle/StrokeStyle are projections of it; fill/stroke are Paint; gradient stops use position; class + css escape). 2.1.0 folded Patches 1–4, made the stroke single-form breaking, renamed size → sizing, generated the schema from Pydantic, and added the validator + codemod. Full detail in CHANGELOG.md.

Provenance (how the earlier documents relate)

These predecessor documents are not included in this HEAD bundle — their content is folded into the artifacts above; they are listed only for historical context:

  • FrameForge-2.0.0-Specification.md — the spec reverse-engineered from the renderer, superseded by docs/spec/frameforge-v2-spec.md.
  • FrameForge-2.0.0-Specification-Complement.md — the reconciliation that produced the recommendations this release implements; its §8 actions are resolved in CHANGELOG.md.
  • The four standalone patch documents (P1–P4) are folded into the grammar, the spec, and the models here; they remain useful as rationale.

Honest limits (don't overclaim)

  • This is a proposed format. No renderer is conformant; the proxy renderer uses DejaVu stand-in fonts and is a sanity check, not a fidelity guarantee.
  • The grammar is consolidated and paren-balanced and is a view of the models — now kept honest by the grammar-check gate (check_grammar_sync.py) for the core profile; the models remain the authority if the two ever disagree.
  • Font pinning enables deterministic layout only up to a stated rounding tolerance (a defined shaping model is also required) — not pixel-exact identity (§9.6).
  • The current delivered top-level fixture status is generated in FIXTURE-STATUS.md; at this snapshot 53/53 have zero errors. Advisory warnings, when present, are recorded there instead of summarized by hand here.
  • What FrameForge can — and deliberately will not — generate is mapped in docs/output-space.md: the backends wired today (whose entry points are drift-gated) plus the conceptual output space the IR admits.
  • How FrameForge presents itself — name, voice, colour, type, and the logo — is the docs/BRAND.md guideline (a proposal). The logo is generated: the mark/wordmark source of truth is frameforge_logo.py in the sibling frameforge-example repo, which writes the masters out of tree (_tmp/brand/) — brand assets are non-core and are not tracked in this repository.

About

FrameForge aims to be an agent-native visual-authoring tool — one structured, programmable foundation (SDK + MCP) for producing professional visual assets across documents, decks, diagrams, books, and letters today, extending toward UIs, vector graphics, logos, and design systems.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages