{"id":49842017,"url":"https://github.com/djdeveloperr/taffy-canvas","last_synced_at":"2026-05-14T07:02:09.682Z","repository":{"id":350395481,"uuid":"1206636651","full_name":"DjDeveloperr/taffy-canvas","owner":"DjDeveloperr","description":"Declarative server-side UI using Taffy \u0026 Skia","archived":false,"fork":false,"pushed_at":"2026-04-13T06:32:04.000Z","size":679,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-23T01:12:52.637Z","etag":null,"topics":["canvas","node","rust","skia","taffy"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DjDeveloperr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"DjDeveloperr"}},"created_at":"2026-04-10T05:30:47.000Z","updated_at":"2026-04-13T06:32:08.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/DjDeveloperr/taffy-canvas","commit_stats":null,"previous_names":["djdeveloperr/taffy-canvas"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/DjDeveloperr/taffy-canvas","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DjDeveloperr%2Ftaffy-canvas","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DjDeveloperr%2Ftaffy-canvas/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DjDeveloperr%2Ftaffy-canvas/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DjDeveloperr%2Ftaffy-canvas/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DjDeveloperr","download_url":"https://codeload.github.com/DjDeveloperr/taffy-canvas/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DjDeveloperr%2Ftaffy-canvas/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33014201,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"online","status_checked_at":"2026-05-14T02:00:06.663Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["canvas","node","rust","skia","taffy"],"created_at":"2026-05-14T07:02:04.203Z","updated_at":"2026-05-14T07:02:09.675Z","avatar_url":"https://github.com/DjDeveloperr.png","language":"Rust","funding_links":["https://github.com/sponsors/DjDeveloperr"],"categories":[],"sub_categories":[],"readme":"# Taffy Canvas\n\nTaffy Canvas is a server-side image renderer for game HUDs, Discord/message-game images, and open graph graphics.\n\nIt combines:\n\n- [`rust-skia`](https://github.com/rust-skia/rust-skia) for drawing and text measurement\n- [`taffy`](https://github.com/DioxusLabs/taffy) for layout\n- a small XML template format for declarative scene description\n- a `napi-rs` wrapper for Node.js\n- a Skia-backed wasm surface for browser-side image rendering\n\nThe goal is to describe an image once, bind data into it quickly, and render it repeatedly on CPU or GPU.\n\n## Workspace\n\n- [`crates/taffy-canvas-core`](/Users/dj/Developer/taffy-canvas/crates/taffy-canvas-core): Rust rendering engine\n- [`crates/taffy-canvas-node`](/Users/dj/Developer/taffy-canvas/crates/taffy-canvas-node): Node.js bindings and npm packaging\n- [`crates/taffy-canvas-wasm`](/Users/dj/Developer/taffy-canvas/crates/taffy-canvas-wasm): Skia-backed wasm exports for browser image rendering\n- [`packages/taffy-canvas-web`](/Users/dj/Developer/taffy-canvas/packages/taffy-canvas-web): JS wrapper around the wasm renderer\n- [`packages/taffy-canvas-vscode`](/Users/dj/Developer/taffy-canvas/packages/taffy-canvas-vscode): VS Code preview extension\n- [`examples`](/Users/dj/.codex/worktrees/5273/taffy-canvas/examples): sample `*.taffy.xml` templates\n- [`docs/rust.md`](/Users/dj/Developer/taffy-canvas/docs/rust.md): Rust API reference\n- [`docs/js.md`](/Users/dj/Developer/taffy-canvas/docs/js.md): JavaScript API reference\n- [`AGENTS.md`](/Users/dj/Developer/taffy-canvas/AGENTS.md): contributor guidance for coding agents\n\n## Features\n\n- XML templates with `view`, `text`, and `image` nodes\n- Template parameter substitution with `{{name}}` and dotted keys like `{{player.hp}}`\n- Structural XML helpers: `when`, `when-not`, `\u003cfor\u003e`, and root-level `\u003ccomponent\u003e` / `\u003cuse\u003e`\n- Rich inline text inside `text`:\n  - `\u003cspan\u003e`, `\u003ca\u003e`, `\u003cstrong\u003e`, `\u003cem\u003e`, `\u003cu\u003e`, `\u003cs\u003e`, `\u003csup\u003e`, `\u003csub\u003e`, `\u003csmall\u003e`, `\u003cmark\u003e`, `\u003cbr /\u003e`\n  - inline images using Skia paragraph placeholders\n  - text decoration, fragment background, text shadow, spacing, line height, baseline shift\n- Layout powered by Taffy:\n  - `flex`, `block`, `grid`, `none`\n  - fixed-size or auto-sized root views\n  - absolute and fixed positioning\n  - percentages, auto margins, aspect ratio\n  - gaps, per-side spacing, block/inline axis spacing shorthands\n  - named grid areas, repeat/minmax/fit-content tracks, start/end placement attributes\n- Rendering features:\n  - backgrounds, borders, border radius\n  - image fit modes: `fill`, `contain`, `cover`\n  - overflow clipping with `visible`, `hidden`, `clip`, `overflow-x`, and `overflow-y`\n  - PNG output-size tradeoffs: `fast`, `balanced`, `small`\n- Performance-oriented runtime:\n  - reusable renderer handles\n  - reusable resource handles\n  - prepared templates\n  - template sessions for base params plus per-render overrides\n  - per-render dynamic resource layering on top of prepared/session base resources\n  - decoded and prepared image caches\n- Tooling:\n  - XSD schema for XML autocomplete and external linting\n  - caller-relative template file loading in Node\n  - computed layout inspection in Node for debugging measured boxes and resolved text/image nodes\n  - local VS Code live preview using the browser wasm renderer\n  - `*.taffy.xml` file association plus bundled schema hookup in the VS Code extension\n- Backends:\n  - CPU everywhere\n  - GPU on macOS via Metal\n  - GPU on Linux and Windows via headless GL\n  - automatic CPU fallback through `RenderBackendPreference::Auto`\n\n## XML\n\nBasic example:\n\n```xml\n\u003cview width=\"320\" height=\"180\" background=\"#101820\"\u003e\n  \u003ctext color=\"#ffffff\"\u003eHello {{name}}\u003c/text\u003e\n  \u003cimage src=\"avatar\" width=\"64\" height=\"64\" fit=\"cover\" /\u003e\n\u003c/view\u003e\n```\n\nAuto-sized root for document-style flow:\n\n```xml\n\u003cview flex-direction=\"column\" background=\"#101820\"\u003e\n  \u003ctext color=\"#ffffff\"\u003eHeadline\u003c/text\u003e\n  \u003cview margin-top=\"8\" padding=\"12\" background=\"#1f2d3a\"\u003e\n    \u003ctext color=\"#ffffff\"\u003eThis root grows from layout instead of fixed bounds.\u003c/text\u003e\n  \u003c/view\u003e\n\u003c/view\u003e\n```\nRecommended file naming: `*.taffy.xml`, for example `card.taffy.xml`.\n\nInline styling:\n\n```xml\n\u003ctext color=\"#ffffff\"\u003e\n  \u003cstrong\u003e{{player.name}}\u003c/strong\u003e\n  \u003cspan color=\"#ff4f64\"\u003e{{player.hp}}\u003c/span\u003e\n  \u003cimage src=\"orb\" width=\"12\" height=\"12\" fit=\"contain\" /\u003e\n  \u003ca href=\"https://example.com/docs\"\u003edocs\u003c/a\u003e\n\u003c/text\u003e\n```\n\nRules:\n\n- Root must be `\u003cview\u003e`.\n- Root `width` and `height` are optional. When omitted, the root view auto-sizes from layout flow.\n- Root `width` and `height` must be absolute lengths when provided.\n- `\u003cpreview\u003e` is optional editor-only metadata and may only appear as a direct child of the root `\u003cview\u003e`.\n- `\u003ccomponent\u003e` is optional reusable metadata and may only appear as a direct child of the root `\u003cview\u003e`.\n- `\u003cpreview\u003e` may contain `\u003cproperty key=\"...\" value=\"...\"/\u003e` and nested `\u003cobject key=\"...\"\u003e...\u003c/object\u003e` entries.\n- `image` requires `src`.\n- Inline `image` requires explicit `width` and `height`.\n- `text` can use text content or a `value` attribute.\n\nPreview presets for the VS Code extension:\n\n```xml\n\u003cview width=\"320\" height=\"180\" background=\"#101820\"\u003e\n  \u003cpreview name=\"Default\"\u003e\n    \u003cproperty key=\"name\" value=\"Canvas\" /\u003e\n    \u003cobject key=\"stats\"\u003e\n      \u003cproperty key=\"hp\" value=\"42\" /\u003e\n    \u003c/object\u003e\n  \u003c/preview\u003e\n  \u003cpreview name=\"Boss\"\u003e\n    \u003cproperty key=\"name\" value=\"Nyx\" /\u003e\n    \u003cobject key=\"stats\"\u003e\n      \u003cproperty key=\"hp\" value=\"120\" /\u003e\n    \u003c/object\u003e\n  \u003c/preview\u003e\n\n  \u003ctext color=\"#ffffff\"\u003eHello {{name}}\u003c/text\u003e\n\u003c/view\u003e\n```\n\nThe preview extension reads these presets from the XML file and merges the selected one over `taffyCanvas.preview.params`.\n\nSchema:\n\n- npm package path: [`crates/taffy-canvas-node/schemas/taffy-canvas.xsd`](/Users/dj/Developer/taffy-canvas/crates/taffy-canvas-node/schemas/taffy-canvas.xsd)\n- CLI example: `xmllint --noout --schema \"$(node -p 'require(\\\"taffy-canvas\\\").schemaPath')\" card.xml`\n\n## Rust Usage\n\n```rust\nuse std::collections::BTreeMap;\n\nuse taffy_canvas_core::{\n    EncodedImageFormat, MemoryAssetProvider, OutputSize, RenderBackendPreference, RenderOptions,\n    Renderer, Template, TemplateParams, WebpEncodingMode,\n};\n\nlet template = Template::compile(\n    r##\"\n    \u003cview width=\"320\" height=\"180\" background=\"#101820\"\u003e\n      \u003ctext color=\"#ffffff\"\u003eHello {{name}}\u003c/text\u003e\n    \u003c/view\u003e\n    \"##,\n)?;\n\nlet mut params = TemplateParams::new();\nparams.insert(\"name\".to_string(), \"Canvas\".to_string());\n\nlet renderer = Renderer::default();\nlet resources = MemoryAssetProvider::new(BTreeMap::new());\nlet output = renderer.render(\n    \u0026template,\n    \u0026params,\n    \u0026resources,\n    RenderOptions {\n        backend: RenderBackendPreference::Auto,\n        output_format: EncodedImageFormat::Webp,\n        output_size: OutputSize::Fast,\n        webp_mode: WebpEncodingMode::Lossy,\n        webp_quality: 85.0,\n        ..RenderOptions::default()\n    },\n)?;\n\nstd::fs::write(\"out.webp\", output.encoded_bytes)?;\n# Ok::\u003c(), taffy_canvas_core::TaffyCanvasError\u003e(())\n```\n\nFile-based compile:\n\n```rust\nlet template = Template::compile_file(\"./templates/card.xml\")?;\n# Ok::\u003c(), taffy_canvas_core::TaffyCanvasError\u003e(())\n```\n\nPrepared template plus base session:\n\n```rust\nuse taffy_canvas_core::{MemoryAssetProvider, RenderOptions, Renderer, Template, TemplateParams};\n\nlet template = Template::compile(\n    r##\"\u003cview width=\"240\" height=\"80\"\u003e\u003ctext\u003e{{player.name}} {{player.hp}}\u003c/text\u003e\u003c/view\u003e\"##,\n)?;\n\nlet mut base = TemplateParams::new();\nbase.insert(\"player.name\".to_string(), \"Canvas\".to_string());\nbase.insert(\"player.hp\".to_string(), \"42\".to_string());\n\nlet session = Renderer::default()\n    .prepare(template, MemoryAssetProvider::default())\n    .with_base_params(base);\n\nlet mut frame = TemplateParams::new();\nframe.insert(\"player.hp\".to_string(), \"99\".to_string());\n\nlet output = session.render(\u0026frame, RenderOptions::default())?;\n# Ok::\u003c(), taffy_canvas_core::TaffyCanvasError\u003e(())\n```\n\n## JavaScript Usage\n\n```js\nconst {\n  createResourcesFromManifest,\n  createTemplateLoader,\n  createRenderer,\n  prepareTemplateWithRenderer,\n  createTemplateSession,\n  renderTemplateSession,\n} = require(\"taffy-canvas\");\n\nconst renderer = createRenderer({ minThreads: 2, maxThreads: 8, idleMs: 5000 });\nconst resources = createResourcesFromManifest(\"./assets/resources.json\");\nconst loader = createTemplateLoader(__filename);\nconst template = loader.compileTemplateFile(\"./templates/card.xml\");\n\nconst prepared = prepareTemplateWithRenderer(renderer, resources, template);\nconst session = createTemplateSession(prepared, {\n  player: { name: \"Canvas\" },\n  stats: { hp: 42 },\n});\n\nconst image = await renderTemplateSession(session, { stats: { hp: 99 } }, {\n  backend: 'auto',\n  outputFormat: 'webp',\n  outputSize: 'fast',\n  webpMode: 'lossy',\n  webpQuality: 85\n});\n```\n\nThe JS binding accepts nested objects and arrays and flattens them into dotted template keys automatically.\n\n## Development\n\nCommon project commands:\n\n```bash\nnpm run build\nnpm run build:wasm\nnpm run test\nnpm run ci\nnpm run bench\n```\n\n`npm run build:wasm` bootstraps a repo-local EMSDK under `.tools/emsdk` when needed and builds the exact Skia renderer for `wasm32-unknown-emscripten`. On macOS it expects LLVM/libclang to be available, such as Homebrew `llvm`.\n\nTo package the local VS Code preview extension as a self-contained VSIX, run:\n\n```bash\nnpm --workspace packages/taffy-canvas-vscode run package:vsix\n```\n\nThat command stages only the extension payload before invoking `vsce`, so packaging stays isolated from the rest of the monorepo.\n\nEquivalent lower-level commands still work, but the root npm scripts are the intended entrypoint for day-to-day development.\n\nNode-only smoke test:\n\n```bash\nnpm run smoke\n```\n\nThe repository also provides a root npm workspace in [`package.json`](/Users/dj/Developer/taffy-canvas/package.json) for Node-side development convenience.\n\n## AI Usage\n\nThis project is primarily developed using Codex, GPT 5.4 model. For contributors, I encourage the use of AI but please\ndisclose the usage.\n\nFor those who are interested, I've published a\n[trace of the first thread of this project](https://traces.com/s/jn7fvrqm6ph63f5j117p7ngrrn84mrwy).\n\n## License\n\nApache License 2.0. See [`LICENSE`](/Users/dj/Developer/taffy-canvas/LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdjdeveloperr%2Ftaffy-canvas","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdjdeveloperr%2Ftaffy-canvas","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdjdeveloperr%2Ftaffy-canvas/lists"}