mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-14 14:22:14 +00:00
Tests: give the nested knockout draw-budget test two minutes, not twenty seconds, on a loaded runner (#3650)
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
This commit is contained in:
1 parent
a70eb03fa6
commit
05fc7f2835
27 files changed
+15656
-15654
No files matched your search
+6
-6
@@ -1,6 +1,6 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
reviews:
|
||||
auto_review:
|
||||
# Dependabot bumps are checked by CI; skip CodeRabbit reviews on them.
|
||||
ignore_usernames:
|
||||
- "dependabot[bot]"
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
reviews:
|
||||
auto_review:
|
||||
# Dependabot bumps are checked by CI; skip CodeRabbit reviews on them.
|
||||
ignore_usernames:
|
||||
- "dependabot[bot]"
|
||||
+11
-11
@@ -1,11 +1,11 @@
|
||||
*~
|
||||
plan/
|
||||
target/
|
||||
/dist
|
||||
/log
|
||||
.DS_Store
|
||||
.claude/worktrees/
|
||||
__pycache__/
|
||||
|
||||
# After Effects test corpus fetched by `cargo xtask aep-corpus`
|
||||
/corpus/
|
||||
*~
|
||||
plan/
|
||||
target/
|
||||
/dist
|
||||
/log
|
||||
.DS_Store
|
||||
.claude/worktrees/
|
||||
__pycache__/
|
||||
|
||||
# After Effects test corpus fetched by `cargo xtask aep-corpus`
|
||||
/corpus/
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"effectcraft": {
|
||||
"command": "cargo",
|
||||
"args": ["run", "--quiet", "--release", "-p", "effectcraft-cli", "--", "mcp", "--demo"]
|
||||
},
|
||||
"effectcraft-app": {
|
||||
"command": "cargo",
|
||||
"args": ["run", "--quiet", "--release", "-p", "effectcraft-cli", "--", "mcp", "--bridge", "9877"]
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"mcpServers": {
|
||||
"effectcraft": {
|
||||
"command": "cargo",
|
||||
"args": ["run", "--quiet", "--release", "-p", "effectcraft-cli", "--", "mcp", "--demo"]
|
||||
},
|
||||
"effectcraft-app": {
|
||||
"command": "cargo",
|
||||
"args": ["run", "--quiet", "--release", "-p", "effectcraft-cli", "--", "mcp", "--bridge", "9877"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,210 +1,210 @@
|
||||
<!-- craft-house-rules:start core source=dde09ea31e2e457f5b4653a6e76f7cdf55944bdf sha256=540e6cea75d12d0710c3dca89658328e81e508ecef102446ca5bb95b7c0b92bb -->
|
||||
## House rules
|
||||
|
||||
These apply in every Craft repository. A later rule may be stricter, not weaker. The source is `standards/house-rules-core.md` in `storytold/craft-contrib`. Propose changes there, not in a copy.
|
||||
|
||||
1. **A person is accountable.** Whoever runs an agent answers for what it opens and for its review comments.
|
||||
2. **Check, then claim.** Before writing code, read the issue comments, search pull requests for `#N` and the keywords, and check recent commits on `main`. Comment on the issue first. Release a claim you will not finish.
|
||||
3. **Verify, don't guess.** Open a definition before using it. Confirm the cause in the code, even when someone names a location.
|
||||
4. **Keep fixes narrow and tested.** Fix what the issue describes, where the bug is. Add a test that fails without the fix.
|
||||
5. **Review the diff before opening a pull request.** Identifiers exist as used, the test fails without the fix, nothing unrelated changed, and the description is true.
|
||||
6. **Look for a better pull request.** If another fixes the same thing, close yours when it is better or equal, and say why.
|
||||
7. **Report only what ran.** Say what you tested and what you could not run. Without a local build or test, say so and rely on CI before marking ready.
|
||||
8. **`main` changes through a pull request.** Do not push to it. Branch protection enforces this. There is no exception.
|
||||
9. **Stop and ask only about** licensing outside the allowlist, publishing, releases, a new remote, secrets, spending money, destructive git, and anything else hard to undo. Otherwise keep working. `plan/` is a gitignored notebook, not rules. Without it, do not ask for it and do not stop.
|
||||
10. **Keep secrets and personal details out** of the repository, logs and requests.
|
||||
11. **CI stays shared.** Workflows call `storytold/craft-common-actions`. Do not change CI in a feature or fix pull request. If CI cannot cover a change, open an issue there.
|
||||
12. **Guards say why.** A check that looks removable says what it prevents. Read that before changing it.
|
||||
13. **Instructions must be readable by everyone.** No notes about one machine, and no links to files a reader cannot open. `craftrules` is retired. Do not link to it.
|
||||
14. **Keep pull requests moving.** Concurrent pull requests are fine; unanswered actionable feedback is not. Respond in 7 days, a reminder at 14, closure eligible at 30, from when an actionable request waits on the author. Suggestions and unconfirmed bot findings do not start the clock. A fix, explanation or agreed plan resets it; unrelated activity does not. Waiting on a reviewer or maintainer pauses it and does not count against the author. A maintainer decides closure; it is never automatic. Reuse search results, do not poll, and stop at a rate limit.
|
||||
15. **No agent attribution.** A pull request does not say an agent wrote it. A repository rule may not require that.
|
||||
<!-- craft-house-rules:end -->
|
||||
<!-- craft-house-rules:start rust source=dde09ea31e2e457f5b4653a6e76f7cdf55944bdf sha256=387e0ccb4b49e567f60951d7d6fff7305054c89f43ad812b9993dc7c38528013 -->
|
||||
## Rust Craft repositories
|
||||
|
||||
These apply to native Craft apps and to shared Rust libraries. Select them by repository type. A repository that merely contains some Rust does not take this block. Rules later in a repository's file may be stricter, but not weaker. The source is `standards/house-rules-rust.md` in [storytold/craft-contrib](https://github.com/storytold/craft-contrib).
|
||||
|
||||
1. **Never crash.** Non-test code does not panic and has no `unsafe`, except where the repository file names an exception. Return errors, treat input as hostile, and bound recursion and allocations. New production crate roots copy the `deny` attribute from an existing crate: `clippy::unwrap_used`, `clippy::expect_used`, `clippy::panic`, `clippy::unimplemented`, `clippy::todo`, `clippy::unreachable`. Every crash fix comes with a test that panicked before the fix.
|
||||
2. **Clean room.** Learn from observed behaviour and public specifications. Do not copy another product's code, assets, strings or artwork. Check a dependency's licence before adding it.
|
||||
3. **Assets and fonts.** Every asset is original or suitably licensed, and is recorded in `ATTRIBUTION.md` in the same change. Font files live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), not in the app repository.
|
||||
4. **Rust only for native Craft apps.** No handwritten JavaScript or TypeScript, and no Tauri, Electron or webview shell, in those apps. Shared tooling follows its documented technical scope and maintainer-approved exceptions. Craft PR Review's existing Tauri and TypeScript UI is one such exception. It does not relax this rule for native apps. Preserve any existing web build.
|
||||
5. **Time goes through `chrono`.** Do not hand-roll calendar conversion. For dates beyond what `chrono` can represent, follow [standards/time.md](https://github.com/storytold/craft-contrib/blob/main/standards/time.md).
|
||||
6. **Contributor credits.** Add or edit only your own person's entry in `contributors/people.toml`, and only after asking them. Never guess a name or hand-edit `contributors.json`.
|
||||
|
||||
## Licence allowlist
|
||||
|
||||
Dependencies and assets use SPDX identifiers. An expression is allowed when every identifier is in this list. A repository may name a shorter list. It may not add an identifier here by editing its own copy of these rules.
|
||||
|
||||
- `ISC`
|
||||
- `MIT`
|
||||
- `BSD-2-Clause`
|
||||
- `BSD-3-Clause`
|
||||
- `Apache-2.0`
|
||||
- `OFL-1.1`
|
||||
- `Ubuntu-font-1.0`
|
||||
- `Bitstream-Vera`
|
||||
- `CC0-1.0`
|
||||
- `CC-BY-4.0`
|
||||
- `CC-BY-SA-4.0`
|
||||
- `LicenseRef-Public-Domain`
|
||||
|
||||
`LicenseRef-Storyteller-Trademark` is not in this list. It covers Storyteller brand marks only, where a repository already says so.
|
||||
<!-- craft-house-rules:end -->
|
||||
|
||||
# EffectCraft — rules for agents and contributors
|
||||
|
||||
These rules apply to every human and AI contributor. `CLAUDE.md` holds the working instructions; this
|
||||
file holds the rules that must never be broken. When the two disagree, this file wins.
|
||||
|
||||
## Never crash
|
||||
|
||||
People trust EffectCraft with hours of work. A malformed project, PSD, SVG, Lottie or media file, a bad
|
||||
expression or script, a broken plug-in, a bad control-channel or MCP argument, a corrupt preferences file
|
||||
or a full disk must produce an error the user (or agent) can act on, never a crash and never lost work.
|
||||
**This rule outranks feature work:** don't ship a feature by adding a panic path, and fix a crash before
|
||||
building on top of it.
|
||||
|
||||
- **Fail with `Result<T, E>`** through the crate's error type and propagate with `?`; add context
|
||||
(`map_err`, an error variant) rather than discarding it. Lenient readers fall back and record the repair.
|
||||
- **No panicking shortcuts in non-test code:** no `unwrap()`, `expect()`, `panic!`, `unreachable!`,
|
||||
`todo!` or `unimplemented!`. Use `?`, `ok_or(..)?`, `let … else`, `if let`, or `unwrap_or*` where a
|
||||
fallback is genuinely correct (never where it would silently corrupt a document). Unfinished features
|
||||
return an "unsupported" error. The only exception is a provably infallible literal, written as
|
||||
`#[allow(clippy::expect_used)]` + `.expect("why this cannot fail")`.
|
||||
- **No `unsafe`** (`unsafe_code = "forbid"` for the workspace).
|
||||
- **Every input-derived number is hostile.** Use `get()` instead of `[i]`/`[a..b]` when the position
|
||||
comes from a file, a user, an agent or arithmetic on those; slice strings only at char boundaries; use
|
||||
checked or saturating arithmetic for lengths, offsets and counts; guard division by zero and NaN/inf
|
||||
casts; cap allocations sized by input.
|
||||
- **Bound recursion:** projects, precomps, expressions and SVG/PSD trees can be cyclic or deeply nested;
|
||||
walk them with seen-sets or depth limits.
|
||||
- **Don't cascade:** handle lock poisoning (`lock().unwrap_or_else(PoisonError::into_inner)` or an
|
||||
error), and treat worker-thread join results as `Result`s.
|
||||
- **Last-resort guard:** the app wraps command execution and file import/export in a panic hook /
|
||||
`catch_unwind` boundary that turns an escaped panic into an error and keeps the document. It is a
|
||||
safety net, not a substitute for the rules above; keep `panic = "unwind"`.
|
||||
- **Prove it:** every crash fix comes with a small synthetic regression test that panicked before the fix.
|
||||
|
||||
Tests, benches, fuzz targets and `xtask` may `unwrap()`; `clippy.toml` allows it in tests. Crates that are
|
||||
clean carry `#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented,
|
||||
clippy::todo, clippy::unreachable)]`; new crates start with it.
|
||||
|
||||
## 1. Assets: no Adobe artwork, every asset licensed and attributed
|
||||
|
||||
This rule is absolute. Breaking it is the most serious mistake a contributor can make on this project.
|
||||
|
||||
**What counts as an asset:** any image, icon, cursor, logo, illustration, screenshot, font, colour LUT,
|
||||
preset, template, sound, music, video, 3D model, or other non-code media. This covers files in the
|
||||
repository, bytes embedded in code (`include_bytes!`, base64, data URIs), and data hard-coded to
|
||||
reproduce an image (for example, point lists traced from someone else's icon).
|
||||
|
||||
1. **Never use Adobe iconography, images or other Adobe assets**, in any form:
|
||||
- no icons, cursors, logos, splash screens, UI artwork or screenshots from any Adobe product;
|
||||
- no Adobe fonts, LUTs, presets, templates, sound effects, stock media or sample projects;
|
||||
- no traced, redrawn, recoloured or "inspired-by" copies of Adobe icons. Our icons may follow generic
|
||||
conventions (a play triangle, a razor blade, a stopwatch), but each must be drawn from scratch
|
||||
without reference to Adobe's artwork.
|
||||
2. **Every asset must be open**: open-source licensed (MIT, Apache-2.0, BSD, ISC, zlib, SIL OFL…),
|
||||
public domain / CC0, or Creative Commons (CC BY or CC BY-SA; no NC/ND licences), **or** created by a
|
||||
contributor who owns it and licenses it to the project under the project licence (MIT OR Apache-2.0).
|
||||
If the licence is unknown or unclear, the asset does not go in.
|
||||
3. **Every asset needs an attribution sidecar.** Next to each asset file `X`, add `X.attribution` with:
|
||||
```text
|
||||
asset: <file name>
|
||||
title: <what it is>
|
||||
author: <creator / copyright holder>
|
||||
source: <URL, or "original work" for contributor-made assets>
|
||||
license: <SPDX id or licence name>
|
||||
license-file: <path to the licence text, if the licence requires shipping it>
|
||||
added: <YYYY-MM-DD> by <contributor>
|
||||
notes: <how it was made or modified; for screenshots, what is shown>
|
||||
```
|
||||
Also add one line for the asset to [`ATTRIBUTION.md`](ATTRIBUTION.md). `cargo xtask assets` (part of
|
||||
`cargo xtask ci`) fails if any asset lacks a sidecar or index entry.
|
||||
4. **Screenshots** in the repo may show only EffectCraft (or other open projects), with media we generated
|
||||
or media that is itself openly licensed. Never commit screenshots of Adobe products.
|
||||
5. **Local reference material stays local.** After Effects reference screenshots and notes live only in
|
||||
`plan/aftereffects/` (gitignored). They must never be committed, bundled, embedded, traced or shipped.
|
||||
6. **Code-drawn assets** (icons in `crates/ui-egui/src/icons.rs`, procedural demo content in
|
||||
`crates/engine/src/demo.rs`, generators in `crates/effects`) are original work under the project licence
|
||||
and are listed in `ATTRIBUTION.md`.
|
||||
7. **When in doubt, leave it out** and draw or generate it yourself.
|
||||
8. **The one exception: first-party ArtCraft brand marks.** The ArtCraft name and logos in `docs/brand/`
|
||||
are trademarks of the ArtCraft Team, not open source, usable only unmodified and only in the context of
|
||||
EffectCraft under `docs/brand/LICENSE-brand.txt` (forks and modified versions must remove them). They still
|
||||
need a sidecar and an `ATTRIBUTION.md` row (licence `LicenseRef-ArtCraft-Trademark`). No other
|
||||
non-open asset is allowed, and this exception never covers third-party marks (Adobe, Discord, GitHub
|
||||
and other logos stay out; draw a generic icon instead).
|
||||
|
||||
## 2. Clean-room code
|
||||
|
||||
- Never read, disassemble or copy anything inside Adobe application bundles; file names and listings only.
|
||||
Observe behaviour by using the app; never capture its Home screen, recent projects, account info or
|
||||
file browsers.
|
||||
- Never copy GPL/LGPL/AGPL code (FFmpeg, Natron, Blender, Glaxnimate, Synfig, Friction, Olive, MLT, frei0r, G'MIC…).
|
||||
- Never read or copy Adobe expression/scripting implementations, `.ffx` presets or effect plug-ins; the public Expression Language Reference and Scripting Guide describe *behaviour* only.
|
||||
- Implement codecs and formats from public specifications (ITU-T/ISO/IEC standards, IETF RFCs, the VP9
|
||||
and AV1 bitstream specs, SMPTE documents, published container specs). Do not read the source of
|
||||
reference or third-party decoders/encoders while implementing a format, even permissively licensed
|
||||
ones (libvpx, libopus, libaom, dav1d, openh264…); spec text and conformance vectors only. Record the
|
||||
spec edition used in the crate README.
|
||||
- ffmpeg/ffprobe may be used only as external test oracles and fixture generators. They are never
|
||||
linked, bundled or shipped.
|
||||
- Dependencies must use permissive licences: MIT/Apache-2.0/BSD/ISC/Zlib/Unicode/CC0/BSL-1.0, or
|
||||
MPL-2.0 used unmodified.
|
||||
|
||||
## 3. Engineering rules
|
||||
|
||||
- **Layering** (`cargo xtask layers`): nothing below L5 depends on egui/eframe/winit/rfd/cpal; L0–L4 build for wasm32.
|
||||
- **Exact time:** `effectcraft_time::Tick` (254 016 000 000/s). Keyframe times are layer time.
|
||||
- **Property tree:** everything animatable is a `Property` in the layer's `PropGroup` tree, addressed by paths (`transform/position`, `effects/#1/blurriness`, `@uid`).
|
||||
- **Everything is a command** (`crates/engine`): id, label, menu path, shortcut, params, enabled(), run(). UI, CLI, control channel and MCP dispatch by id.
|
||||
- **Everything is agent-drivable:** every interactive widget registers an automation id; UI state is serde.
|
||||
- **Community links** (Help menu, About, header Discord button): https://discord.gg/artcraft, https://getartcraft.com, https://getartcraft.com/apps/effectcraft, https://github.com/storytold/effectcraft.
|
||||
- **Quality gates** before every commit: `cargo xtask ci` (fmt, clippy -D warnings, tests, layers, assets, wasm).
|
||||
- New workspace crates include `[lints] workspace = true` in `Cargo.toml`. Production crate roots declare the crate-level lint attributes before modules; crate-level `//!` documentation may precede them.
|
||||
- **Commits:** one task id per commit (`M6.3: trim paths`). Only green states.
|
||||
- `cargo run -p effectcraft -- --control 9877` opens the desktop app with the JSON-lines control server (`docs/control-protocol.md`). For UI work, look at the result, including the headless `snapshot` example.
|
||||
- `plan/` is an optional gitignored notebook (`plan/aftereffects/` may hold reference notes and captures). Do not ask for it, do not stop when it is absent, and never commit those captures.
|
||||
- Read [`docs/gaps.md`](docs/gaps.md) and [`docs/target-app-parity.md`](docs/target-app-parity.md) before choosing work. Fidelity, reliability and stability (G1–G3) come before new features.
|
||||
- Some workflow files still run their own jobs. Don't add to them. Moving a job into craft-common-actions is its own task.
|
||||
|
||||
|
||||
Shared real-file test corpora (Photoshop-authored PSDs, etc.) live in
|
||||
[`storytold/photocraft-corpus`](https://github.com/storytold/photocraft-corpus).
|
||||
Never commit large binary fixtures to this repo; fetch them pinned by commit and sha256-verified,
|
||||
as PhotoCraft does with `cargo xtask corpus`.
|
||||
|
||||
|
||||
## See also
|
||||
|
||||
- [ROADMAP.md](ROADMAP.md): stage, headline numbers and what's next
|
||||
- [docs/gaps.md](docs/gaps.md): every known shortfall against After Effects, ranked, and the prioritised workstreams (G1–G9); read it before choosing work
|
||||
- [docs/target-app-parity.md](docs/target-app-parity.md): the parity numbers and how they were measured, with the per-area docs it links (UI, file formats, hardware, localization, effects, scripting)
|
||||
- [CONTRIBUTING.md](CONTRIBUTING.md): setup, gates, commits, how to add things
|
||||
- [docs/architecture.md](docs/architecture.md): layers, data model, commands, pipeline
|
||||
- [docs/testing.md](docs/testing.md): oracle tests, criteria, benchmarks
|
||||
- [docs/agents.md](docs/agents.md): driving EffectCraft over MCP / the control channel, and the agent work loop
|
||||
- [docs/control-protocol.md](docs/control-protocol.md): control-channel method reference
|
||||
- [docs/contributors.md](docs/contributors.md): the About window's contributor and model credits
|
||||
- [ATTRIBUTION.md](ATTRIBUTION.md): asset index
|
||||
|
||||
## Contributor credits (About window)
|
||||
|
||||
- About ▸ Contributors/Models are compiled into the binary from `contributors/contributors.json`
|
||||
(commit stats; generated, never hand-edit) and `contributors/people.toml` (names people chose for
|
||||
themselves). See `docs/contributors.md`.
|
||||
- **Agents working for a contributor:** when you prepare a PR, check whether your human's GitHub
|
||||
username has a `[people.<username>]` entry in `contributors/people.toml`. If not, ask them once
|
||||
whether they want to be credited by more than their username: a real name, a display name, and/or
|
||||
their public GitHub profile name (`sync_github_name = true`). If yes, add **only their own** entry
|
||||
(copy the template at the top of the file)
|
||||
and include it in their PR, committed as them. If no, change nothing: they are credited as
|
||||
`@username` anyway.
|
||||
- Never add, edit, guess or copy anyone else's entry or name (not from git config, commit authors or
|
||||
GitHub profiles). Never hand-edit `contributors.json`.
|
||||
|
||||
## CI workflows
|
||||
- Workflows in `.github/workflows/` call the shared workflows in [`storytold/craft-common-actions`](https://github.com/storytold/craft-common-actions) at `@main`, with their inputs and secrets. No other jobs: if no shared workflow does what's needed, it doesn't go here.
|
||||
- Don't add or change CI in a feature or fix PR. If the existing workflows can't build or test a change, link an existing craft-common-actions issue or PR, or open a new issue, or a PR only for a small fix that is safe for every app and does not increase CI time.
|
||||
- Some workflow files here still run their own jobs. Don't add to them. Moving a job into craft-common-actions is its own task, never part of another PR, and the job stays here until a shared workflow runs it.
|
||||
<!-- craft-house-rules:start core source=dde09ea31e2e457f5b4653a6e76f7cdf55944bdf sha256=540e6cea75d12d0710c3dca89658328e81e508ecef102446ca5bb95b7c0b92bb -->
|
||||
## House rules
|
||||
|
||||
These apply in every Craft repository. A later rule may be stricter, not weaker. The source is `standards/house-rules-core.md` in `storytold/craft-contrib`. Propose changes there, not in a copy.
|
||||
|
||||
1. **A person is accountable.** Whoever runs an agent answers for what it opens and for its review comments.
|
||||
2. **Check, then claim.** Before writing code, read the issue comments, search pull requests for `#N` and the keywords, and check recent commits on `main`. Comment on the issue first. Release a claim you will not finish.
|
||||
3. **Verify, don't guess.** Open a definition before using it. Confirm the cause in the code, even when someone names a location.
|
||||
4. **Keep fixes narrow and tested.** Fix what the issue describes, where the bug is. Add a test that fails without the fix.
|
||||
5. **Review the diff before opening a pull request.** Identifiers exist as used, the test fails without the fix, nothing unrelated changed, and the description is true.
|
||||
6. **Look for a better pull request.** If another fixes the same thing, close yours when it is better or equal, and say why.
|
||||
7. **Report only what ran.** Say what you tested and what you could not run. Without a local build or test, say so and rely on CI before marking ready.
|
||||
8. **`main` changes through a pull request.** Do not push to it. Branch protection enforces this. There is no exception.
|
||||
9. **Stop and ask only about** licensing outside the allowlist, publishing, releases, a new remote, secrets, spending money, destructive git, and anything else hard to undo. Otherwise keep working. `plan/` is a gitignored notebook, not rules. Without it, do not ask for it and do not stop.
|
||||
10. **Keep secrets and personal details out** of the repository, logs and requests.
|
||||
11. **CI stays shared.** Workflows call `storytold/craft-common-actions`. Do not change CI in a feature or fix pull request. If CI cannot cover a change, open an issue there.
|
||||
12. **Guards say why.** A check that looks removable says what it prevents. Read that before changing it.
|
||||
13. **Instructions must be readable by everyone.** No notes about one machine, and no links to files a reader cannot open. `craftrules` is retired. Do not link to it.
|
||||
14. **Keep pull requests moving.** Concurrent pull requests are fine; unanswered actionable feedback is not. Respond in 7 days, a reminder at 14, closure eligible at 30, from when an actionable request waits on the author. Suggestions and unconfirmed bot findings do not start the clock. A fix, explanation or agreed plan resets it; unrelated activity does not. Waiting on a reviewer or maintainer pauses it and does not count against the author. A maintainer decides closure; it is never automatic. Reuse search results, do not poll, and stop at a rate limit.
|
||||
15. **No agent attribution.** A pull request does not say an agent wrote it. A repository rule may not require that.
|
||||
<!-- craft-house-rules:end -->
|
||||
<!-- craft-house-rules:start rust source=dde09ea31e2e457f5b4653a6e76f7cdf55944bdf sha256=387e0ccb4b49e567f60951d7d6fff7305054c89f43ad812b9993dc7c38528013 -->
|
||||
## Rust Craft repositories
|
||||
|
||||
These apply to native Craft apps and to shared Rust libraries. Select them by repository type. A repository that merely contains some Rust does not take this block. Rules later in a repository's file may be stricter, but not weaker. The source is `standards/house-rules-rust.md` in [storytold/craft-contrib](https://github.com/storytold/craft-contrib).
|
||||
|
||||
1. **Never crash.** Non-test code does not panic and has no `unsafe`, except where the repository file names an exception. Return errors, treat input as hostile, and bound recursion and allocations. New production crate roots copy the `deny` attribute from an existing crate: `clippy::unwrap_used`, `clippy::expect_used`, `clippy::panic`, `clippy::unimplemented`, `clippy::todo`, `clippy::unreachable`. Every crash fix comes with a test that panicked before the fix.
|
||||
2. **Clean room.** Learn from observed behaviour and public specifications. Do not copy another product's code, assets, strings or artwork. Check a dependency's licence before adding it.
|
||||
3. **Assets and fonts.** Every asset is original or suitably licensed, and is recorded in `ATTRIBUTION.md` in the same change. Font files live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), not in the app repository.
|
||||
4. **Rust only for native Craft apps.** No handwritten JavaScript or TypeScript, and no Tauri, Electron or webview shell, in those apps. Shared tooling follows its documented technical scope and maintainer-approved exceptions. Craft PR Review's existing Tauri and TypeScript UI is one such exception. It does not relax this rule for native apps. Preserve any existing web build.
|
||||
5. **Time goes through `chrono`.** Do not hand-roll calendar conversion. For dates beyond what `chrono` can represent, follow [standards/time.md](https://github.com/storytold/craft-contrib/blob/main/standards/time.md).
|
||||
6. **Contributor credits.** Add or edit only your own person's entry in `contributors/people.toml`, and only after asking them. Never guess a name or hand-edit `contributors.json`.
|
||||
|
||||
## Licence allowlist
|
||||
|
||||
Dependencies and assets use SPDX identifiers. An expression is allowed when every identifier is in this list. A repository may name a shorter list. It may not add an identifier here by editing its own copy of these rules.
|
||||
|
||||
- `ISC`
|
||||
- `MIT`
|
||||
- `BSD-2-Clause`
|
||||
- `BSD-3-Clause`
|
||||
- `Apache-2.0`
|
||||
- `OFL-1.1`
|
||||
- `Ubuntu-font-1.0`
|
||||
- `Bitstream-Vera`
|
||||
- `CC0-1.0`
|
||||
- `CC-BY-4.0`
|
||||
- `CC-BY-SA-4.0`
|
||||
- `LicenseRef-Public-Domain`
|
||||
|
||||
`LicenseRef-Storyteller-Trademark` is not in this list. It covers Storyteller brand marks only, where a repository already says so.
|
||||
<!-- craft-house-rules:end -->
|
||||
|
||||
# EffectCraft — rules for agents and contributors
|
||||
|
||||
These rules apply to every human and AI contributor. `CLAUDE.md` holds the working instructions; this
|
||||
file holds the rules that must never be broken. When the two disagree, this file wins.
|
||||
|
||||
## Never crash
|
||||
|
||||
People trust EffectCraft with hours of work. A malformed project, PSD, SVG, Lottie or media file, a bad
|
||||
expression or script, a broken plug-in, a bad control-channel or MCP argument, a corrupt preferences file
|
||||
or a full disk must produce an error the user (or agent) can act on, never a crash and never lost work.
|
||||
**This rule outranks feature work:** don't ship a feature by adding a panic path, and fix a crash before
|
||||
building on top of it.
|
||||
|
||||
- **Fail with `Result<T, E>`** through the crate's error type and propagate with `?`; add context
|
||||
(`map_err`, an error variant) rather than discarding it. Lenient readers fall back and record the repair.
|
||||
- **No panicking shortcuts in non-test code:** no `unwrap()`, `expect()`, `panic!`, `unreachable!`,
|
||||
`todo!` or `unimplemented!`. Use `?`, `ok_or(..)?`, `let … else`, `if let`, or `unwrap_or*` where a
|
||||
fallback is genuinely correct (never where it would silently corrupt a document). Unfinished features
|
||||
return an "unsupported" error. The only exception is a provably infallible literal, written as
|
||||
`#[allow(clippy::expect_used)]` + `.expect("why this cannot fail")`.
|
||||
- **No `unsafe`** (`unsafe_code = "forbid"` for the workspace).
|
||||
- **Every input-derived number is hostile.** Use `get()` instead of `[i]`/`[a..b]` when the position
|
||||
comes from a file, a user, an agent or arithmetic on those; slice strings only at char boundaries; use
|
||||
checked or saturating arithmetic for lengths, offsets and counts; guard division by zero and NaN/inf
|
||||
casts; cap allocations sized by input.
|
||||
- **Bound recursion:** projects, precomps, expressions and SVG/PSD trees can be cyclic or deeply nested;
|
||||
walk them with seen-sets or depth limits.
|
||||
- **Don't cascade:** handle lock poisoning (`lock().unwrap_or_else(PoisonError::into_inner)` or an
|
||||
error), and treat worker-thread join results as `Result`s.
|
||||
- **Last-resort guard:** the app wraps command execution and file import/export in a panic hook /
|
||||
`catch_unwind` boundary that turns an escaped panic into an error and keeps the document. It is a
|
||||
safety net, not a substitute for the rules above; keep `panic = "unwind"`.
|
||||
- **Prove it:** every crash fix comes with a small synthetic regression test that panicked before the fix.
|
||||
|
||||
Tests, benches, fuzz targets and `xtask` may `unwrap()`; `clippy.toml` allows it in tests. Crates that are
|
||||
clean carry `#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented,
|
||||
clippy::todo, clippy::unreachable)]`; new crates start with it.
|
||||
|
||||
## 1. Assets: no Adobe artwork, every asset licensed and attributed
|
||||
|
||||
This rule is absolute. Breaking it is the most serious mistake a contributor can make on this project.
|
||||
|
||||
**What counts as an asset:** any image, icon, cursor, logo, illustration, screenshot, font, colour LUT,
|
||||
preset, template, sound, music, video, 3D model, or other non-code media. This covers files in the
|
||||
repository, bytes embedded in code (`include_bytes!`, base64, data URIs), and data hard-coded to
|
||||
reproduce an image (for example, point lists traced from someone else's icon).
|
||||
|
||||
1. **Never use Adobe iconography, images or other Adobe assets**, in any form:
|
||||
- no icons, cursors, logos, splash screens, UI artwork or screenshots from any Adobe product;
|
||||
- no Adobe fonts, LUTs, presets, templates, sound effects, stock media or sample projects;
|
||||
- no traced, redrawn, recoloured or "inspired-by" copies of Adobe icons. Our icons may follow generic
|
||||
conventions (a play triangle, a razor blade, a stopwatch), but each must be drawn from scratch
|
||||
without reference to Adobe's artwork.
|
||||
2. **Every asset must be open**: open-source licensed (MIT, Apache-2.0, BSD, ISC, zlib, SIL OFL…),
|
||||
public domain / CC0, or Creative Commons (CC BY or CC BY-SA; no NC/ND licences), **or** created by a
|
||||
contributor who owns it and licenses it to the project under the project licence (MIT OR Apache-2.0).
|
||||
If the licence is unknown or unclear, the asset does not go in.
|
||||
3. **Every asset needs an attribution sidecar.** Next to each asset file `X`, add `X.attribution` with:
|
||||
```text
|
||||
asset: <file name>
|
||||
title: <what it is>
|
||||
author: <creator / copyright holder>
|
||||
source: <URL, or "original work" for contributor-made assets>
|
||||
license: <SPDX id or licence name>
|
||||
license-file: <path to the licence text, if the licence requires shipping it>
|
||||
added: <YYYY-MM-DD> by <contributor>
|
||||
notes: <how it was made or modified; for screenshots, what is shown>
|
||||
```
|
||||
Also add one line for the asset to [`ATTRIBUTION.md`](ATTRIBUTION.md). `cargo xtask assets` (part of
|
||||
`cargo xtask ci`) fails if any asset lacks a sidecar or index entry.
|
||||
4. **Screenshots** in the repo may show only EffectCraft (or other open projects), with media we generated
|
||||
or media that is itself openly licensed. Never commit screenshots of Adobe products.
|
||||
5. **Local reference material stays local.** After Effects reference screenshots and notes live only in
|
||||
`plan/aftereffects/` (gitignored). They must never be committed, bundled, embedded, traced or shipped.
|
||||
6. **Code-drawn assets** (icons in `crates/ui-egui/src/icons.rs`, procedural demo content in
|
||||
`crates/engine/src/demo.rs`, generators in `crates/effects`) are original work under the project licence
|
||||
and are listed in `ATTRIBUTION.md`.
|
||||
7. **When in doubt, leave it out** and draw or generate it yourself.
|
||||
8. **The one exception: first-party ArtCraft brand marks.** The ArtCraft name and logos in `docs/brand/`
|
||||
are trademarks of the ArtCraft Team, not open source, usable only unmodified and only in the context of
|
||||
EffectCraft under `docs/brand/LICENSE-brand.txt` (forks and modified versions must remove them). They still
|
||||
need a sidecar and an `ATTRIBUTION.md` row (licence `LicenseRef-ArtCraft-Trademark`). No other
|
||||
non-open asset is allowed, and this exception never covers third-party marks (Adobe, Discord, GitHub
|
||||
and other logos stay out; draw a generic icon instead).
|
||||
|
||||
## 2. Clean-room code
|
||||
|
||||
- Never read, disassemble or copy anything inside Adobe application bundles; file names and listings only.
|
||||
Observe behaviour by using the app; never capture its Home screen, recent projects, account info or
|
||||
file browsers.
|
||||
- Never copy GPL/LGPL/AGPL code (FFmpeg, Natron, Blender, Glaxnimate, Synfig, Friction, Olive, MLT, frei0r, G'MIC…).
|
||||
- Never read or copy Adobe expression/scripting implementations, `.ffx` presets or effect plug-ins; the public Expression Language Reference and Scripting Guide describe *behaviour* only.
|
||||
- Implement codecs and formats from public specifications (ITU-T/ISO/IEC standards, IETF RFCs, the VP9
|
||||
and AV1 bitstream specs, SMPTE documents, published container specs). Do not read the source of
|
||||
reference or third-party decoders/encoders while implementing a format, even permissively licensed
|
||||
ones (libvpx, libopus, libaom, dav1d, openh264…); spec text and conformance vectors only. Record the
|
||||
spec edition used in the crate README.
|
||||
- ffmpeg/ffprobe may be used only as external test oracles and fixture generators. They are never
|
||||
linked, bundled or shipped.
|
||||
- Dependencies must use permissive licences: MIT/Apache-2.0/BSD/ISC/Zlib/Unicode/CC0/BSL-1.0, or
|
||||
MPL-2.0 used unmodified.
|
||||
|
||||
## 3. Engineering rules
|
||||
|
||||
- **Layering** (`cargo xtask layers`): nothing below L5 depends on egui/eframe/winit/rfd/cpal; L0–L4 build for wasm32.
|
||||
- **Exact time:** `effectcraft_time::Tick` (254 016 000 000/s). Keyframe times are layer time.
|
||||
- **Property tree:** everything animatable is a `Property` in the layer's `PropGroup` tree, addressed by paths (`transform/position`, `effects/#1/blurriness`, `@uid`).
|
||||
- **Everything is a command** (`crates/engine`): id, label, menu path, shortcut, params, enabled(), run(). UI, CLI, control channel and MCP dispatch by id.
|
||||
- **Everything is agent-drivable:** every interactive widget registers an automation id; UI state is serde.
|
||||
- **Community links** (Help menu, About, header Discord button): https://discord.gg/artcraft, https://getartcraft.com, https://getartcraft.com/apps/effectcraft, https://github.com/storytold/effectcraft.
|
||||
- **Quality gates** before every commit: `cargo xtask ci` (fmt, clippy -D warnings, tests, layers, assets, wasm).
|
||||
- New workspace crates include `[lints] workspace = true` in `Cargo.toml`. Production crate roots declare the crate-level lint attributes before modules; crate-level `//!` documentation may precede them.
|
||||
- **Commits:** one task id per commit (`M6.3: trim paths`). Only green states.
|
||||
- `cargo run -p effectcraft -- --control 9877` opens the desktop app with the JSON-lines control server (`docs/control-protocol.md`). For UI work, look at the result, including the headless `snapshot` example.
|
||||
- `plan/` is an optional gitignored notebook (`plan/aftereffects/` may hold reference notes and captures). Do not ask for it, do not stop when it is absent, and never commit those captures.
|
||||
- Read [`docs/gaps.md`](docs/gaps.md) and [`docs/target-app-parity.md`](docs/target-app-parity.md) before choosing work. Fidelity, reliability and stability (G1–G3) come before new features.
|
||||
- Some workflow files still run their own jobs. Don't add to them. Moving a job into craft-common-actions is its own task.
|
||||
|
||||
|
||||
Shared real-file test corpora (Photoshop-authored PSDs, etc.) live in
|
||||
[`storytold/photocraft-corpus`](https://github.com/storytold/photocraft-corpus).
|
||||
Never commit large binary fixtures to this repo; fetch them pinned by commit and sha256-verified,
|
||||
as PhotoCraft does with `cargo xtask corpus`.
|
||||
|
||||
|
||||
## See also
|
||||
|
||||
- [ROADMAP.md](ROADMAP.md): stage, headline numbers and what's next
|
||||
- [docs/gaps.md](docs/gaps.md): every known shortfall against After Effects, ranked, and the prioritised workstreams (G1–G9); read it before choosing work
|
||||
- [docs/target-app-parity.md](docs/target-app-parity.md): the parity numbers and how they were measured, with the per-area docs it links (UI, file formats, hardware, localization, effects, scripting)
|
||||
- [CONTRIBUTING.md](CONTRIBUTING.md): setup, gates, commits, how to add things
|
||||
- [docs/architecture.md](docs/architecture.md): layers, data model, commands, pipeline
|
||||
- [docs/testing.md](docs/testing.md): oracle tests, criteria, benchmarks
|
||||
- [docs/agents.md](docs/agents.md): driving EffectCraft over MCP / the control channel, and the agent work loop
|
||||
- [docs/control-protocol.md](docs/control-protocol.md): control-channel method reference
|
||||
- [docs/contributors.md](docs/contributors.md): the About window's contributor and model credits
|
||||
- [ATTRIBUTION.md](ATTRIBUTION.md): asset index
|
||||
|
||||
## Contributor credits (About window)
|
||||
|
||||
- About ▸ Contributors/Models are compiled into the binary from `contributors/contributors.json`
|
||||
(commit stats; generated, never hand-edit) and `contributors/people.toml` (names people chose for
|
||||
themselves). See `docs/contributors.md`.
|
||||
- **Agents working for a contributor:** when you prepare a PR, check whether your human's GitHub
|
||||
username has a `[people.<username>]` entry in `contributors/people.toml`. If not, ask them once
|
||||
whether they want to be credited by more than their username: a real name, a display name, and/or
|
||||
their public GitHub profile name (`sync_github_name = true`). If yes, add **only their own** entry
|
||||
(copy the template at the top of the file)
|
||||
and include it in their PR, committed as them. If no, change nothing: they are credited as
|
||||
`@username` anyway.
|
||||
- Never add, edit, guess or copy anyone else's entry or name (not from git config, commit authors or
|
||||
GitHub profiles). Never hand-edit `contributors.json`.
|
||||
|
||||
## CI workflows
|
||||
- Workflows in `.github/workflows/` call the shared workflows in [`storytold/craft-common-actions`](https://github.com/storytold/craft-common-actions) at `@main`, with their inputs and secrets. No other jobs: if no shared workflow does what's needed, it doesn't go here.
|
||||
- Don't add or change CI in a feature or fix PR. If the existing workflows can't build or test a change, link an existing craft-common-actions issue or PR, or open a new issue, or a PR only for a small fix that is safe for every app and does not increase CI time.
|
||||
- Some workflow files here still run their own jobs. Don't add to them. Moving a job into craft-common-actions is its own task, never part of another PR, and the job stays here until a shared workflow runs it.
|
||||
+158
-158
@@ -1,158 +1,158 @@
|
||||
# Attribution
|
||||
|
||||
Every non-code asset in EffectCraft, with its author, source and licence. Each repository file listed here also has
|
||||
a `<file>.attribution` sidecar with full details. The rules are in [AGENTS.md](AGENTS.md) §1: no Adobe
|
||||
iconography, images or other Adobe assets; open licences only; every asset attributed.
|
||||
`cargo xtask assets` checks that this index and the sidecars cover every asset in the repository.
|
||||
The complete licence texts and copyright notices for the bundled fonts, including the fonts embedded
|
||||
by the pinned egui dependency, are reproduced in [`NOTICE`](NOTICE), which every package ships; the
|
||||
`assets/fonts/OFL-*.txt` files are the upstream copies kept beside the fonts.
|
||||
|
||||
## Bundled dependency fonts
|
||||
|
||||
These fonts are embedded by the pinned egui dependency (epaint_default_fonts 0.37 at
|
||||
`6d518fc799681571963842f95b55a6b68431b15a`); no font file is added to this repository.
|
||||
egui-icons is always embedded, `default_fonts` adds Ubuntu Light and Hack, and
|
||||
`monochrome_emoji_fonts` adds Noto Emoji and emoji-icon-font. The complete copyright notices
|
||||
and licence texts are in [`NOTICE`](NOTICE).
|
||||
|
||||
| Asset | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| `epaint_default_fonts/fonts/egui-icons.ttf` | John Slegers (emoji-icon-font) | https://github.com/storytold/egui/blob/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts/egui-icons.txt; original https://github.com/jslegers/emoji-icon-font | MIT (complete copyright and permission text in `NOTICE`) |
|
||||
| `epaint_default_fonts/fonts/emoji-icon-font.ttf` | John Slegers | https://github.com/storytold/egui/blob/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts/emoji-icon-font-mit-license.txt; original https://github.com/jslegers/emoji-icon-font | MIT (complete copyright and permission text in `NOTICE`) |
|
||||
| `epaint_default_fonts/fonts/NotoEmoji-Regular.ttf` | Google Inc. (The Noto Project Authors) | https://github.com/storytold/egui/blob/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts/OFL.txt | OFL-1.1 (complete text in `NOTICE`) |
|
||||
| `epaint_default_fonts/fonts/Ubuntu-Light.ttf` | Canonical Ltd. and Dalton Maag | https://github.com/storytold/egui/blob/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts/UFL.txt | Ubuntu Font Licence 1.0 (complete text in `NOTICE`) |
|
||||
| `epaint_default_fonts/fonts/Hack-Regular.ttf` | Source Foundry Authors; Bitstream Inc. | https://github.com/storytold/egui/blob/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts/Hack-Regular.txt; original https://github.com/source-foundry/Hack | MIT AND Bitstream-Vera (complete texts in `NOTICE`) |
|
||||
|
||||
## Asset files
|
||||
|
||||
| Asset | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| `assets/fonts/Inter-Regular.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) |
|
||||
| `assets/fonts/Inter-Medium.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) |
|
||||
| `assets/fonts/Inter-SemiBold.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) |
|
||||
| `assets/fonts/Inter-Bold.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) |
|
||||
| `assets/fonts/Inter-Italic.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) |
|
||||
| `assets/fonts/NotoSerif-Regular.ttf` | The Noto Project Authors | https://github.com/notofonts/latin-greek-cyrillic | OFL-1.1 (`assets/fonts/OFL-NotoSerif.txt`) |
|
||||
| `assets/fonts/NotoSansSymbols2-Regular.ttf` | The Noto Project Authors | https://github.com/notofonts/symbols | OFL-1.1 (`assets/fonts/OFL-NotoSansSymbols2.txt`) |
|
||||
| `assets/fonts/JetBrainsMono-Regular.ttf` | The JetBrains Mono Project Authors | https://github.com/JetBrains/JetBrainsMono | OFL-1.1 (`assets/fonts/OFL-JetBrainsMono.txt`) |
|
||||
| `apps/effectcraft-web/web/favicon.svg` | EffectCraft contributors | Original work: web app icon, a copy of `assets/app-icon/effectcraft-small.svg` (the unicorn app icon) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `packaging/macos/dmg/background.svg` | @XusBadia | Original work: macOS DMG Finder window background, derived from the EffectCraft app icon (`assets/app-icon/effectcraft.svg`); rendered to a TIFF by `packaging/macos/package.sh` at package time | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) |
|
||||
| `assets/app-icon/effectcraft-1024.png` | EffectCraft contributors | Original work: effectCraft app icon, 1024 px (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/effectcraft-macos-512.png` | EffectCraft contributors | Original work: effectCraft runtime Dock icon (macOS) (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/effectcraft-small.svg` | EffectCraft contributors | Original work: effectCraft app icon, light vector (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/effectcraft.icns` | EffectCraft contributors | Original work: effectCraft macOS app icon (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/effectcraft.ico` | EffectCraft contributors | Original work: effectCraft Windows app icon (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/effectcraft.svg` | EffectCraft contributors | Original work: effectCraft app icon, master vector (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/128x128/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 128x128 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/16x16/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 16x16 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/24x24/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 24x24 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/256x256/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 256x256 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/32x32/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 32x32 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/48x48/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 48x48 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/512x512/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 512x512 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 64x64 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/scalable/apps/ai.storyteller.effectcraft.svg` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor scalable (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `crates/lottie/tests/fixtures/bouncing-ball.json` | EffectCraft contributors | Original work: hand-written Lottie test fixture (bouncing ball, eased position keys) | MIT OR Apache-2.0 |
|
||||
| `crates/lottie/tests/fixtures/shapes-gradient.json` | EffectCraft contributors | Original work: hand-written Lottie test fixture (star, rectangle, gradient fill, dashed stroke, trim paths) | MIT OR Apache-2.0 |
|
||||
| `crates/lottie/tests/fixtures/precomp-matte-text.json` | EffectCraft contributors | Original work: hand-written Lottie test fixture (precomp, track matte, parented text) | MIT OR Apache-2.0 |
|
||||
| `crates/lottie/tests/fixtures/legacy-masks.json` | EffectCraft contributors | Original work: hand-written Lottie test fixture (legacy keyframes, masks) | MIT OR Apache-2.0 |
|
||||
| `crates/svg/tests/fixtures/basic-shapes.svg` | EffectCraft contributors | Original work: hand-written SVG test fixture (basic shapes, groups, transforms) | MIT OR Apache-2.0 |
|
||||
| `crates/svg/tests/fixtures/paths-gradients.svg` | EffectCraft contributors | Original work: hand-written SVG test fixture (path data, gradients, CSS, use) | MIT OR Apache-2.0 |
|
||||
| `crates/model/tests/fixtures/quad.gltf` | EffectCraft contributors | Original work: glTF 2.0 test fixture (textured quad, node hierarchy, translation animation; data URIs) | MIT OR Apache-2.0 |
|
||||
| `crates/model/tests/fixtures/quad.glb` | EffectCraft contributors | Original work: binary glTF test fixture (the same quad with its texture in the BIN chunk) | MIT OR Apache-2.0 |
|
||||
| `crates/model/tests/fixtures/cube.obj` | EffectCraft contributors | Original work: Wavefront OBJ test fixture (unit cube, two materials) | MIT OR Apache-2.0 |
|
||||
| `crates/model/tests/fixtures/cube.mtl` | EffectCraft contributors | Original work: MTL test fixture (materials of cube.obj) | MIT OR Apache-2.0 |
|
||||
|
||||
Three small After Effects test projects of `crates/aep` are in this repository: `crates/aep/tests/fixtures/synthetic.aep` (original work, made with After Effects 2026 from own data, working space None, the embedded colour profile removed; MIT OR Apache-2.0, with its `.attribution` sidecar), `crates/aep/tests/fixtures/ai-mask-3099.aep` (original work, made with After Effects 2026 from four hand-written one-page PDFs named `.ai`, footage paths and host name neutralised, the embedded colour profiles replaced by stubs made with `crates/aep/examples/stubicc.rs`; MIT OR Apache-2.0, with its `.attribution` sidecar) and `crates/aep/tests/fixtures/text-synthetic.aep` (original work, made with After Effects 2026 by script from own data, working space None, no colour profile embedded, nothing changed after the save; MIT OR Apache-2.0, with its `.attribution` sidecar). The other After Effects test projects of `crates/aep` are not in this repository. They are original work of an EffectCraft contributor, generated with After Effects 2026 as own test data (MIT OR Apache-2.0, each file with its `.attribution` sidecar); they are in [storytold/effectcraft-corpus](https://github.com/storytold/effectcraft-corpus) with their sidecars, fetched by `cargo xtask aep-corpus` (commit-pinned, SHA-256-verified), and the tests read them only from the folder named by `EFFECTCRAFT_AEP_FIXTURES` (opt-in). Their embedded colour profiles are replaced by stubs made with `crates/aep/examples/stubicc.rs` (a header and a description, no colour data), so no vendor profile data is in them.
|
||||
|
||||
## Screenshots
|
||||
|
||||
Screenshots of EffectCraft itself, rendered headlessly, showing only procedurally generated demo content.
|
||||
|
||||
| Asset | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| `docs/images/effectcraft-hero.png` | EffectCraft contributors | Original work: screenshot of EffectCraft (EffectCraft main window) | MIT OR Apache-2.0 |
|
||||
| `docs/images/effectcraft-3d.png` | EffectCraft contributors | Original work: screenshot of EffectCraft (EffectCraft Classic 3D) | MIT OR Apache-2.0 |
|
||||
| `docs/images/effectcraft-graph-editor.png` | EffectCraft contributors | Original work: screenshot of EffectCraft (EffectCraft Graph Editor) | MIT OR Apache-2.0 |
|
||||
| `docs/images/effectcraft-effects.png` | EffectCraft contributors | Original work: screenshot of EffectCraft (EffectCraft effects) | MIT OR Apache-2.0 |
|
||||
| `docs/images/effectcraft-render-queue.png` | EffectCraft contributors | Original work: screenshot of EffectCraft (EffectCraft Render Queue panel with the demo compositions queued for H.264 and ProRes export) | MIT OR Apache-2.0 |
|
||||
|
||||
## Code-drawn assets (original work, MIT OR Apache-2.0)
|
||||
|
||||
- UI icons drawn in code: `crates/ui-egui/src/icons.rs`, including the original filled favorite star (2026-10-11, EffectCraft contributors).
|
||||
- Procedural demo content (solids, shapes, gradients, text animations): `crates/engine/src/demo.rs`.
|
||||
- Built-in project templates (Lower Third, Title Card, Logo Reveal, Kinetic Type, Social Square, Vertical Story,
|
||||
Slideshow, 3D Text Orbit) built from engine commands, and their thumbnails rendered by our own renderer:
|
||||
`crates/engine/src/templates.rs`.
|
||||
- Text animator presets (Typewriter, Fade Up Characters, Bounce In Words, Tracking In, Scramble, Blur In, Jitter,
|
||||
Drop In Lines): `crates/engine/presets/text_animators.json` (sidecar `.attribution`).
|
||||
- Transition animation presets (23: wipes, clock and fan reveals, blinds, dissolves, card flips, CC wipes, fades):
|
||||
`crates/engine/presets/transitions.json` (sidecar `.attribution`).
|
||||
- Backgrounds animation presets (23: drifting gradients, fog, cloud and smoke, plasma and magma, cells and crystals, scrolling grids
|
||||
and checks, pulse rings, bokeh, light sweep): `crates/engine/presets/backgrounds.json` (sidecar `.attribution`).
|
||||
- Behaviors animation presets (22: wiggle, shake, float, orbit, bounce, drift, pendulum, spin, pulse, heartbeat, blink, strobe, flicker):
|
||||
`crates/engine/presets/behaviors.json` (sidecar `.attribution`).
|
||||
- Sample scripts (File ▸ Scripts) and ScriptUI panel: `crates/engine/scripts/Rename Layers.jsx`,
|
||||
`crates/engine/scripts/Sort Layers by In Point.jsx`, `crates/engine/scripts/Create Null at Selected Layers.jsx`,
|
||||
`crates/engine/scripts/Render Queue Batch.jsx`, `crates/engine/scripts/ScriptUI Panels/Layer Tools.jsx`
|
||||
(sidecars `.attribution`).
|
||||
- Bundled extension (ScriptUI panel) with its twelve built-in ease presets:
|
||||
`extensions/scriptui-panels/Ease Presets.jsx` (sidecar `.attribution`).
|
||||
- Example effect plug-in: `examples/plugins/posterize-bands` (WebAssembly ABI 1).
|
||||
|
||||
## Trained models (optional downloads, not bundled)
|
||||
|
||||
The repository and the installers contain no model weights. These models are downloaded only when
|
||||
a user asks (Settings ▸ Roto Brush, Settings ▸ Face Tracking, or the `roto.model.*` /
|
||||
`face.model.*` commands), unmodified from their authors' official location, checked against a
|
||||
pinned SHA-256 and stored in the user's `models` folder with a `<file>.NOTICE.txt` naming their
|
||||
authors, licence and source. EffectCraft runs them with its own Rust implementation and is not
|
||||
affiliated with or endorsed by their authors.
|
||||
|
||||
| Model | Authors | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| MobileSAM (`mobile_sam.pt`) | Chaoning Zhang et al., building on Meta AI's Segment Anything and Microsoft's TinyViT | https://github.com/ChaoningZhang/MobileSAM | Apache-2.0 |
|
||||
| MediaPipe Face Landmarker (`face_landmarker.task`: BlazeFace short-range detector, Face Mesh V2, blendshapes) | Google LLC | https://developers.google.com/edge/mediapipe/solutions/vision/face_landmarker (model cards: https://storage.googleapis.com/mediapipe-assets/MediaPipe%20BlazeFace%20Model%20Card%20(Short%20Range).pdf, https://storage.googleapis.com/mediapipe-assets/Model%20Card%20MediaPipe%20Face%20Mesh%20V2.pdf) | Apache-2.0 |
|
||||
|
||||
## Formats and parameters from other open-source projects
|
||||
|
||||
Written in Rust for EffectCraft; no code is copied. They follow published specifications and
|
||||
configuration from these Apache-2.0 projects:
|
||||
|
||||
- `crates/segment/src/tflite.rs` reads TensorFlow Lite files using the field layout of TensorFlow
|
||||
Lite's `schema.fbs` (The TensorFlow Authors, https://github.com/tensorflow/tensorflow,
|
||||
Apache-2.0).
|
||||
- `crates/segment/src/mediapipe.rs` follows MediaPipe's published face landmarker graph: the SSD
|
||||
anchor layout, detection decoding, crop sizes and the face-mesh point indices it uses (Google
|
||||
LLC, https://github.com/google-ai-edge/mediapipe, Apache-2.0).
|
||||
- `crates/segment/src/mobilesam.rs` follows the MobileSAM, TinyViT and Segment Anything
|
||||
architectures and their Apache-2.0 reference code.
|
||||
|
||||
## Frozen matte regression fixture
|
||||
|
||||
| Asset | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| `crates/engine/src/tests/fixtures/analysed-0.24.ecproj` | arnav-hiwarkar | Original work, synthetic project keyed by EffectCraft 0.24.1 | MIT OR Apache-2.0 |
|
||||
| `crates/engine/src/tests/fixtures/object-matte-0.22.ecproj` | lisn0 | Original work, synthetic project saved by EffectCraft 0.22.0 | MIT OR Apache-2.0 |
|
||||
| `crates/engine/src/tests/fixtures/tracked-0.22.ecproj` | arnav-hiwarkar | Original work, synthetic project saved by EffectCraft 0.22.0 | MIT OR Apache-2.0 |
|
||||
|
||||
## First-party brand marks
|
||||
|
||||
The ArtCraft name, wordmark and logos are trademarks of the ArtCraft Team. They are not open source and are not
|
||||
covered by the project licence; they may be used only unmodified and only as part of this repository and EffectCraft,
|
||||
under [`docs/brand/LICENSE-brand.txt`](docs/brand/LICENSE-brand.txt). Forks and modified versions must remove them
|
||||
(AGENTS.md §1.8).
|
||||
|
||||
| File | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| `docs/brand/artcraft-logo-white.png` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft wordmark, light ink for dark backgrounds) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
| `docs/brand/artcraft-logo-white.svg` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft wordmark, light ink for dark backgrounds) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
| `docs/brand/artcraft-logo.png` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft wordmark (symbol + "ARTCRAFT"), dark ink for light backgrounds) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
| `docs/brand/artcraft-logo.svg` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft wordmark (symbol + "ARTCRAFT"), dark ink for light backgrounds) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
| `docs/brand/artcraft-mark-black.png` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft symbol ("A" mark), black) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
| `docs/brand/artcraft-mark-black.svg` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft symbol ("A" mark), black) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
| `docs/brand/artcraft-mark.png` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft symbol ("A" mark), blue) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
| `docs/brand/artcraft-mark.svg` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft symbol ("A" mark), blue) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
|
||||
- Appearance theme previews, drawn procedurally from original shapes: `crates/ui-egui/src/panels/settings.rs` (original work, MIT OR Apache-2.0).
|
||||
# Attribution
|
||||
|
||||
Every non-code asset in EffectCraft, with its author, source and licence. Each repository file listed here also has
|
||||
a `<file>.attribution` sidecar with full details. The rules are in [AGENTS.md](AGENTS.md) §1: no Adobe
|
||||
iconography, images or other Adobe assets; open licences only; every asset attributed.
|
||||
`cargo xtask assets` checks that this index and the sidecars cover every asset in the repository.
|
||||
The complete licence texts and copyright notices for the bundled fonts, including the fonts embedded
|
||||
by the pinned egui dependency, are reproduced in [`NOTICE`](NOTICE), which every package ships; the
|
||||
`assets/fonts/OFL-*.txt` files are the upstream copies kept beside the fonts.
|
||||
|
||||
## Bundled dependency fonts
|
||||
|
||||
These fonts are embedded by the pinned egui dependency (epaint_default_fonts 0.37 at
|
||||
`6d518fc799681571963842f95b55a6b68431b15a`); no font file is added to this repository.
|
||||
egui-icons is always embedded, `default_fonts` adds Ubuntu Light and Hack, and
|
||||
`monochrome_emoji_fonts` adds Noto Emoji and emoji-icon-font. The complete copyright notices
|
||||
and licence texts are in [`NOTICE`](NOTICE).
|
||||
|
||||
| Asset | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| `epaint_default_fonts/fonts/egui-icons.ttf` | John Slegers (emoji-icon-font) | https://github.com/storytold/egui/blob/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts/egui-icons.txt; original https://github.com/jslegers/emoji-icon-font | MIT (complete copyright and permission text in `NOTICE`) |
|
||||
| `epaint_default_fonts/fonts/emoji-icon-font.ttf` | John Slegers | https://github.com/storytold/egui/blob/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts/emoji-icon-font-mit-license.txt; original https://github.com/jslegers/emoji-icon-font | MIT (complete copyright and permission text in `NOTICE`) |
|
||||
| `epaint_default_fonts/fonts/NotoEmoji-Regular.ttf` | Google Inc. (The Noto Project Authors) | https://github.com/storytold/egui/blob/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts/OFL.txt | OFL-1.1 (complete text in `NOTICE`) |
|
||||
| `epaint_default_fonts/fonts/Ubuntu-Light.ttf` | Canonical Ltd. and Dalton Maag | https://github.com/storytold/egui/blob/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts/UFL.txt | Ubuntu Font Licence 1.0 (complete text in `NOTICE`) |
|
||||
| `epaint_default_fonts/fonts/Hack-Regular.ttf` | Source Foundry Authors; Bitstream Inc. | https://github.com/storytold/egui/blob/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts/Hack-Regular.txt; original https://github.com/source-foundry/Hack | MIT AND Bitstream-Vera (complete texts in `NOTICE`) |
|
||||
|
||||
## Asset files
|
||||
|
||||
| Asset | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| `assets/fonts/Inter-Regular.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) |
|
||||
| `assets/fonts/Inter-Medium.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) |
|
||||
| `assets/fonts/Inter-SemiBold.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) |
|
||||
| `assets/fonts/Inter-Bold.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) |
|
||||
| `assets/fonts/Inter-Italic.ttf` | The Inter Project Authors | https://github.com/rsms/inter | OFL-1.1 (`assets/fonts/OFL-Inter.txt`) |
|
||||
| `assets/fonts/NotoSerif-Regular.ttf` | The Noto Project Authors | https://github.com/notofonts/latin-greek-cyrillic | OFL-1.1 (`assets/fonts/OFL-NotoSerif.txt`) |
|
||||
| `assets/fonts/NotoSansSymbols2-Regular.ttf` | The Noto Project Authors | https://github.com/notofonts/symbols | OFL-1.1 (`assets/fonts/OFL-NotoSansSymbols2.txt`) |
|
||||
| `assets/fonts/JetBrainsMono-Regular.ttf` | The JetBrains Mono Project Authors | https://github.com/JetBrains/JetBrainsMono | OFL-1.1 (`assets/fonts/OFL-JetBrainsMono.txt`) |
|
||||
| `apps/effectcraft-web/web/favicon.svg` | EffectCraft contributors | Original work: web app icon, a copy of `assets/app-icon/effectcraft-small.svg` (the unicorn app icon) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `packaging/macos/dmg/background.svg` | @XusBadia | Original work: macOS DMG Finder window background, derived from the EffectCraft app icon (`assets/app-icon/effectcraft.svg`); rendered to a TIFF by `packaging/macos/package.sh` at package time | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) |
|
||||
| `assets/app-icon/effectcraft-1024.png` | EffectCraft contributors | Original work: effectCraft app icon, 1024 px (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/effectcraft-macos-512.png` | EffectCraft contributors | Original work: effectCraft runtime Dock icon (macOS) (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/effectcraft-small.svg` | EffectCraft contributors | Original work: effectCraft app icon, light vector (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/effectcraft.icns` | EffectCraft contributors | Original work: effectCraft macOS app icon (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/effectcraft.ico` | EffectCraft contributors | Original work: effectCraft Windows app icon (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/effectcraft.svg` | EffectCraft contributors | Original work: effectCraft app icon, master vector (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/128x128/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 128x128 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/16x16/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 16x16 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/24x24/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 24x24 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/256x256/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 256x256 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/32x32/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 32x32 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/48x48/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 48x48 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/512x512/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 512x512 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor 64x64 (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `assets/app-icon/hicolor/scalable/apps/ai.storyteller.effectcraft.svg` | EffectCraft contributors | Original work: effectCraft app icon, Linux hicolor scalable (owner's ArtCraft unicorn drawing, vectorised) | MIT OR Apache-2.0 (`assets/app-icon/LICENSE.txt`) |
|
||||
| `crates/lottie/tests/fixtures/bouncing-ball.json` | EffectCraft contributors | Original work: hand-written Lottie test fixture (bouncing ball, eased position keys) | MIT OR Apache-2.0 |
|
||||
| `crates/lottie/tests/fixtures/shapes-gradient.json` | EffectCraft contributors | Original work: hand-written Lottie test fixture (star, rectangle, gradient fill, dashed stroke, trim paths) | MIT OR Apache-2.0 |
|
||||
| `crates/lottie/tests/fixtures/precomp-matte-text.json` | EffectCraft contributors | Original work: hand-written Lottie test fixture (precomp, track matte, parented text) | MIT OR Apache-2.0 |
|
||||
| `crates/lottie/tests/fixtures/legacy-masks.json` | EffectCraft contributors | Original work: hand-written Lottie test fixture (legacy keyframes, masks) | MIT OR Apache-2.0 |
|
||||
| `crates/svg/tests/fixtures/basic-shapes.svg` | EffectCraft contributors | Original work: hand-written SVG test fixture (basic shapes, groups, transforms) | MIT OR Apache-2.0 |
|
||||
| `crates/svg/tests/fixtures/paths-gradients.svg` | EffectCraft contributors | Original work: hand-written SVG test fixture (path data, gradients, CSS, use) | MIT OR Apache-2.0 |
|
||||
| `crates/model/tests/fixtures/quad.gltf` | EffectCraft contributors | Original work: glTF 2.0 test fixture (textured quad, node hierarchy, translation animation; data URIs) | MIT OR Apache-2.0 |
|
||||
| `crates/model/tests/fixtures/quad.glb` | EffectCraft contributors | Original work: binary glTF test fixture (the same quad with its texture in the BIN chunk) | MIT OR Apache-2.0 |
|
||||
| `crates/model/tests/fixtures/cube.obj` | EffectCraft contributors | Original work: Wavefront OBJ test fixture (unit cube, two materials) | MIT OR Apache-2.0 |
|
||||
| `crates/model/tests/fixtures/cube.mtl` | EffectCraft contributors | Original work: MTL test fixture (materials of cube.obj) | MIT OR Apache-2.0 |
|
||||
|
||||
Three small After Effects test projects of `crates/aep` are in this repository: `crates/aep/tests/fixtures/synthetic.aep` (original work, made with After Effects 2026 from own data, working space None, the embedded colour profile removed; MIT OR Apache-2.0, with its `.attribution` sidecar), `crates/aep/tests/fixtures/ai-mask-3099.aep` (original work, made with After Effects 2026 from four hand-written one-page PDFs named `.ai`, footage paths and host name neutralised, the embedded colour profiles replaced by stubs made with `crates/aep/examples/stubicc.rs`; MIT OR Apache-2.0, with its `.attribution` sidecar) and `crates/aep/tests/fixtures/text-synthetic.aep` (original work, made with After Effects 2026 by script from own data, working space None, no colour profile embedded, nothing changed after the save; MIT OR Apache-2.0, with its `.attribution` sidecar). The other After Effects test projects of `crates/aep` are not in this repository. They are original work of an EffectCraft contributor, generated with After Effects 2026 as own test data (MIT OR Apache-2.0, each file with its `.attribution` sidecar); they are in [storytold/effectcraft-corpus](https://github.com/storytold/effectcraft-corpus) with their sidecars, fetched by `cargo xtask aep-corpus` (commit-pinned, SHA-256-verified), and the tests read them only from the folder named by `EFFECTCRAFT_AEP_FIXTURES` (opt-in). Their embedded colour profiles are replaced by stubs made with `crates/aep/examples/stubicc.rs` (a header and a description, no colour data), so no vendor profile data is in them.
|
||||
|
||||
## Screenshots
|
||||
|
||||
Screenshots of EffectCraft itself, rendered headlessly, showing only procedurally generated demo content.
|
||||
|
||||
| Asset | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| `docs/images/effectcraft-hero.png` | EffectCraft contributors | Original work: screenshot of EffectCraft (EffectCraft main window) | MIT OR Apache-2.0 |
|
||||
| `docs/images/effectcraft-3d.png` | EffectCraft contributors | Original work: screenshot of EffectCraft (EffectCraft Classic 3D) | MIT OR Apache-2.0 |
|
||||
| `docs/images/effectcraft-graph-editor.png` | EffectCraft contributors | Original work: screenshot of EffectCraft (EffectCraft Graph Editor) | MIT OR Apache-2.0 |
|
||||
| `docs/images/effectcraft-effects.png` | EffectCraft contributors | Original work: screenshot of EffectCraft (EffectCraft effects) | MIT OR Apache-2.0 |
|
||||
| `docs/images/effectcraft-render-queue.png` | EffectCraft contributors | Original work: screenshot of EffectCraft (EffectCraft Render Queue panel with the demo compositions queued for H.264 and ProRes export) | MIT OR Apache-2.0 |
|
||||
|
||||
## Code-drawn assets (original work, MIT OR Apache-2.0)
|
||||
|
||||
- UI icons drawn in code: `crates/ui-egui/src/icons.rs`, including the original filled favorite star (2026-10-11, EffectCraft contributors).
|
||||
- Procedural demo content (solids, shapes, gradients, text animations): `crates/engine/src/demo.rs`.
|
||||
- Built-in project templates (Lower Third, Title Card, Logo Reveal, Kinetic Type, Social Square, Vertical Story,
|
||||
Slideshow, 3D Text Orbit) built from engine commands, and their thumbnails rendered by our own renderer:
|
||||
`crates/engine/src/templates.rs`.
|
||||
- Text animator presets (Typewriter, Fade Up Characters, Bounce In Words, Tracking In, Scramble, Blur In, Jitter,
|
||||
Drop In Lines): `crates/engine/presets/text_animators.json` (sidecar `.attribution`).
|
||||
- Transition animation presets (23: wipes, clock and fan reveals, blinds, dissolves, card flips, CC wipes, fades):
|
||||
`crates/engine/presets/transitions.json` (sidecar `.attribution`).
|
||||
- Backgrounds animation presets (23: drifting gradients, fog, cloud and smoke, plasma and magma, cells and crystals, scrolling grids
|
||||
and checks, pulse rings, bokeh, light sweep): `crates/engine/presets/backgrounds.json` (sidecar `.attribution`).
|
||||
- Behaviors animation presets (22: wiggle, shake, float, orbit, bounce, drift, pendulum, spin, pulse, heartbeat, blink, strobe, flicker):
|
||||
`crates/engine/presets/behaviors.json` (sidecar `.attribution`).
|
||||
- Sample scripts (File ▸ Scripts) and ScriptUI panel: `crates/engine/scripts/Rename Layers.jsx`,
|
||||
`crates/engine/scripts/Sort Layers by In Point.jsx`, `crates/engine/scripts/Create Null at Selected Layers.jsx`,
|
||||
`crates/engine/scripts/Render Queue Batch.jsx`, `crates/engine/scripts/ScriptUI Panels/Layer Tools.jsx`
|
||||
(sidecars `.attribution`).
|
||||
- Bundled extension (ScriptUI panel) with its twelve built-in ease presets:
|
||||
`extensions/scriptui-panels/Ease Presets.jsx` (sidecar `.attribution`).
|
||||
- Example effect plug-in: `examples/plugins/posterize-bands` (WebAssembly ABI 1).
|
||||
|
||||
## Trained models (optional downloads, not bundled)
|
||||
|
||||
The repository and the installers contain no model weights. These models are downloaded only when
|
||||
a user asks (Settings ▸ Roto Brush, Settings ▸ Face Tracking, or the `roto.model.*` /
|
||||
`face.model.*` commands), unmodified from their authors' official location, checked against a
|
||||
pinned SHA-256 and stored in the user's `models` folder with a `<file>.NOTICE.txt` naming their
|
||||
authors, licence and source. EffectCraft runs them with its own Rust implementation and is not
|
||||
affiliated with or endorsed by their authors.
|
||||
|
||||
| Model | Authors | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| MobileSAM (`mobile_sam.pt`) | Chaoning Zhang et al., building on Meta AI's Segment Anything and Microsoft's TinyViT | https://github.com/ChaoningZhang/MobileSAM | Apache-2.0 |
|
||||
| MediaPipe Face Landmarker (`face_landmarker.task`: BlazeFace short-range detector, Face Mesh V2, blendshapes) | Google LLC | https://developers.google.com/edge/mediapipe/solutions/vision/face_landmarker (model cards: https://storage.googleapis.com/mediapipe-assets/MediaPipe%20BlazeFace%20Model%20Card%20(Short%20Range).pdf, https://storage.googleapis.com/mediapipe-assets/Model%20Card%20MediaPipe%20Face%20Mesh%20V2.pdf) | Apache-2.0 |
|
||||
|
||||
## Formats and parameters from other open-source projects
|
||||
|
||||
Written in Rust for EffectCraft; no code is copied. They follow published specifications and
|
||||
configuration from these Apache-2.0 projects:
|
||||
|
||||
- `crates/segment/src/tflite.rs` reads TensorFlow Lite files using the field layout of TensorFlow
|
||||
Lite's `schema.fbs` (The TensorFlow Authors, https://github.com/tensorflow/tensorflow,
|
||||
Apache-2.0).
|
||||
- `crates/segment/src/mediapipe.rs` follows MediaPipe's published face landmarker graph: the SSD
|
||||
anchor layout, detection decoding, crop sizes and the face-mesh point indices it uses (Google
|
||||
LLC, https://github.com/google-ai-edge/mediapipe, Apache-2.0).
|
||||
- `crates/segment/src/mobilesam.rs` follows the MobileSAM, TinyViT and Segment Anything
|
||||
architectures and their Apache-2.0 reference code.
|
||||
|
||||
## Frozen matte regression fixture
|
||||
|
||||
| Asset | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| `crates/engine/src/tests/fixtures/analysed-0.24.ecproj` | arnav-hiwarkar | Original work, synthetic project keyed by EffectCraft 0.24.1 | MIT OR Apache-2.0 |
|
||||
| `crates/engine/src/tests/fixtures/object-matte-0.22.ecproj` | lisn0 | Original work, synthetic project saved by EffectCraft 0.22.0 | MIT OR Apache-2.0 |
|
||||
| `crates/engine/src/tests/fixtures/tracked-0.22.ecproj` | arnav-hiwarkar | Original work, synthetic project saved by EffectCraft 0.22.0 | MIT OR Apache-2.0 |
|
||||
|
||||
## First-party brand marks
|
||||
|
||||
The ArtCraft name, wordmark and logos are trademarks of the ArtCraft Team. They are not open source and are not
|
||||
covered by the project licence; they may be used only unmodified and only as part of this repository and EffectCraft,
|
||||
under [`docs/brand/LICENSE-brand.txt`](docs/brand/LICENSE-brand.txt). Forks and modified versions must remove them
|
||||
(AGENTS.md §1.8).
|
||||
|
||||
| File | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| `docs/brand/artcraft-logo-white.png` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft wordmark, light ink for dark backgrounds) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
| `docs/brand/artcraft-logo-white.svg` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft wordmark, light ink for dark backgrounds) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
| `docs/brand/artcraft-logo.png` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft wordmark (symbol + "ARTCRAFT"), dark ink for light backgrounds) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
| `docs/brand/artcraft-logo.svg` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft wordmark (symbol + "ARTCRAFT"), dark ink for light backgrounds) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
| `docs/brand/artcraft-mark-black.png` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft symbol ("A" mark), black) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
| `docs/brand/artcraft-mark-black.svg` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft symbol ("A" mark), black) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
| `docs/brand/artcraft-mark.png` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft symbol ("A" mark), blue) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
| `docs/brand/artcraft-mark.svg` | ArtCraft Team | Original work: first-party ArtCraft brand mark (ArtCraft symbol ("A" mark), blue) | ArtCraft brand terms, not open source (`docs/brand/LICENSE-brand.txt`) |
|
||||
|
||||
- Appearance theme previews, drawn procedurally from original shapes: `crates/ui-egui/src/panels/settings.rs` (original work, MIT OR Apache-2.0).
|
||||
+109
-109
@@ -1,109 +1,109 @@
|
||||
# Contributing to EffectCraft
|
||||
|
||||
Thanks for helping. Please read [AGENTS.md](AGENTS.md) first: its rules on assets and clean-room
|
||||
work apply to everyone, people and AI agents alike, and they are not negotiable.
|
||||
|
||||
## Setup
|
||||
|
||||
You need [Rust](https://rustup.rs/) 1.95 or newer.
|
||||
|
||||
On Linux the desktop app plays preview sound through `cpal`, so the build needs a usable ALSA
|
||||
pkg-config package: an `alsa.pc` that pkg-config can find, naming the linkable ALSA runtime
|
||||
library (usually `libasound.so.2`). `alsa-sys` runs pkg-config at build time; it does not compile
|
||||
ALSA's headers. Without a usable package the build fails at link time with an unrelated-looking
|
||||
error; `cargo xtask ci` probes pkg-config up front and names the package:
|
||||
|
||||
```sh
|
||||
sudo dnf install alsa-lib-devel # Fedora
|
||||
sudo apt install libasound2-dev # Debian / Ubuntu
|
||||
sudo pacman -S alsa-lib # Arch
|
||||
```
|
||||
|
||||
If you cannot install the package, point `PKG_CONFIG_PATH` at a directory holding an `alsa.pc`
|
||||
whose `Libs` line names your system's `libasound` (usually `libasound.so.2`); every CI run needs
|
||||
that variable set.
|
||||
|
||||
```sh
|
||||
cargo run -p effectcraft -- --demo # the desktop app, with the demo project
|
||||
cargo test --workspace
|
||||
cargo xtask ci # what every commit must pass (dev-profile checks and tests)
|
||||
```
|
||||
|
||||
The default `dev` profile keeps debug info to line tables: builds are fast and panics/backtraces
|
||||
still name the file and line, but a debugger cannot show local variables. For an interactive
|
||||
debugger session use the `dev-full` profile, which is the same build with full debug info:
|
||||
|
||||
```sh
|
||||
cargo run --profile dev-full -p effectcraft -- --demo
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
The MSVC toolchain, rustup's default on Windows, is what the releases use: with the Visual Studio
|
||||
C++ build tools installed, the commands above work as they are.
|
||||
|
||||
The GNU toolchain (`x86_64-pc-windows-gnu`) needs a current mingw-w64 GCC and binutils first on
|
||||
`PATH`. They provide the linker and the Windows import libraries, such as `libshlwapi.a` for
|
||||
opening links from the app. With MSYS2:
|
||||
|
||||
```powershell
|
||||
C:\msys64\usr\bin\pacman.exe -S --needed mingw-w64-x86_64-gcc
|
||||
$env:PATH = "C:\msys64\mingw64\bin;$env:PATH"
|
||||
cargo +stable-x86_64-pc-windows-gnu run -p effectcraft -- --demo
|
||||
```
|
||||
|
||||
An older MinGW earlier on `PATH` can break the build: CodeBlocks' GCC 8.1 fails to link
|
||||
`boa_engine` ([#1882](https://github.com/storytold/effectcraft/issues/1882)).
|
||||
|
||||
Rust's bundled LLD and self-contained libraries don't include every import library EffectCraft
|
||||
needs, so on their own they stop with `unable to find library` for `-lshlwapi`, `-lktmw32`,
|
||||
`-lpropsys` and `-lversion` ([#1886](https://github.com/storytold/effectcraft/issues/1886)). To
|
||||
link that way, copy those four from mingw-w64 into a folder of its own and add that folder to the
|
||||
search path in your own Cargo config (`%USERPROFILE%\.cargo\config.toml`). Don't add all of
|
||||
mingw-w64's `lib` folder: its C runtime clashes with Rust's bundled one
|
||||
(`undefined symbol: DllEntryPoint`).
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force C:\gnu-import-libs
|
||||
foreach ($lib in "shlwapi", "ktmw32", "propsys", "version") {
|
||||
Copy-Item "C:\msys64\mingw64\lib\lib$lib.a" C:\gnu-import-libs\
|
||||
}
|
||||
```
|
||||
|
||||
```toml
|
||||
[target.x86_64-pc-windows-gnu]
|
||||
linker = "rust-lld"
|
||||
rustflags = ["-C", "link-self-contained=yes", "-L", "native=C:/gnu-import-libs"]
|
||||
```
|
||||
|
||||
## Ground rules
|
||||
|
||||
- **Clean room.** Work from After Effects' public documentation and its observable behaviour only.
|
||||
Never use Adobe artwork, presets or code, and never copy GPL/LGPL/AGPL code.
|
||||
- **Every asset is attributed.** Add a `.attribution` sidecar and an [ATTRIBUTION.md](ATTRIBUTION.md)
|
||||
row; `cargo xtask assets` checks it.
|
||||
- **Layering.** Nothing below the interface layer depends on UI or OS crates; see
|
||||
[docs/architecture.md](docs/architecture.md).
|
||||
- **Everything is a command.** New features are engine commands with an id, label, menu path,
|
||||
shortcut and parameters, so the UI, the CLI and the MCP server get them at once. Interactive
|
||||
widgets register an automation id.
|
||||
- **Never crash.** Non-test code returns errors instead of panicking: no `unwrap()`, `expect()`,
|
||||
`panic!`, `unreachable!`, `todo!`, `unimplemented!` or `unsafe`, checked indexing and arithmetic on
|
||||
input-derived values, and a regression test with every crash fix. See the "Never crash" section of
|
||||
[AGENTS.md](AGENTS.md).
|
||||
- **Tests with every change**, and green `cargo xtask ci` before every commit. One task per commit.
|
||||
|
||||
## How to add…
|
||||
|
||||
- **An effect:** add an `EffectSpec` in `crates/effects` (AE display name, category, parameters
|
||||
with AE's names, defaults and ranges) and its render function, with a determinism test and a
|
||||
behaviour test. If it reads the clock directly, add it to `TIME_DEPENDENT`.
|
||||
- **A command:** register it with `cmd!` in `crates/engine/src/commands/`, place it in the menu
|
||||
tree in `crates/engine/src/menus.rs`, and test it including undo.
|
||||
- **A panel or dialog:** `crates/ui-egui/src/panels/`; look at the result with the headless
|
||||
snapshot tool described in [docs/testing.md](docs/testing.md).
|
||||
|
||||
## Talk to us
|
||||
|
||||
Questions, ideas and bug reports are welcome on [Discord](https://discord.gg/artcraft) and in
|
||||
GitHub issues.
|
||||
# Contributing to EffectCraft
|
||||
|
||||
Thanks for helping. Please read [AGENTS.md](AGENTS.md) first: its rules on assets and clean-room
|
||||
work apply to everyone, people and AI agents alike, and they are not negotiable.
|
||||
|
||||
## Setup
|
||||
|
||||
You need [Rust](https://rustup.rs/) 1.95 or newer.
|
||||
|
||||
On Linux the desktop app plays preview sound through `cpal`, so the build needs a usable ALSA
|
||||
pkg-config package: an `alsa.pc` that pkg-config can find, naming the linkable ALSA runtime
|
||||
library (usually `libasound.so.2`). `alsa-sys` runs pkg-config at build time; it does not compile
|
||||
ALSA's headers. Without a usable package the build fails at link time with an unrelated-looking
|
||||
error; `cargo xtask ci` probes pkg-config up front and names the package:
|
||||
|
||||
```sh
|
||||
sudo dnf install alsa-lib-devel # Fedora
|
||||
sudo apt install libasound2-dev # Debian / Ubuntu
|
||||
sudo pacman -S alsa-lib # Arch
|
||||
```
|
||||
|
||||
If you cannot install the package, point `PKG_CONFIG_PATH` at a directory holding an `alsa.pc`
|
||||
whose `Libs` line names your system's `libasound` (usually `libasound.so.2`); every CI run needs
|
||||
that variable set.
|
||||
|
||||
```sh
|
||||
cargo run -p effectcraft -- --demo # the desktop app, with the demo project
|
||||
cargo test --workspace
|
||||
cargo xtask ci # what every commit must pass (dev-profile checks and tests)
|
||||
```
|
||||
|
||||
The default `dev` profile keeps debug info to line tables: builds are fast and panics/backtraces
|
||||
still name the file and line, but a debugger cannot show local variables. For an interactive
|
||||
debugger session use the `dev-full` profile, which is the same build with full debug info:
|
||||
|
||||
```sh
|
||||
cargo run --profile dev-full -p effectcraft -- --demo
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
The MSVC toolchain, rustup's default on Windows, is what the releases use: with the Visual Studio
|
||||
C++ build tools installed, the commands above work as they are.
|
||||
|
||||
The GNU toolchain (`x86_64-pc-windows-gnu`) needs a current mingw-w64 GCC and binutils first on
|
||||
`PATH`. They provide the linker and the Windows import libraries, such as `libshlwapi.a` for
|
||||
opening links from the app. With MSYS2:
|
||||
|
||||
```powershell
|
||||
C:\msys64\usr\bin\pacman.exe -S --needed mingw-w64-x86_64-gcc
|
||||
$env:PATH = "C:\msys64\mingw64\bin;$env:PATH"
|
||||
cargo +stable-x86_64-pc-windows-gnu run -p effectcraft -- --demo
|
||||
```
|
||||
|
||||
An older MinGW earlier on `PATH` can break the build: CodeBlocks' GCC 8.1 fails to link
|
||||
`boa_engine` ([#1882](https://github.com/storytold/effectcraft/issues/1882)).
|
||||
|
||||
Rust's bundled LLD and self-contained libraries don't include every import library EffectCraft
|
||||
needs, so on their own they stop with `unable to find library` for `-lshlwapi`, `-lktmw32`,
|
||||
`-lpropsys` and `-lversion` ([#1886](https://github.com/storytold/effectcraft/issues/1886)). To
|
||||
link that way, copy those four from mingw-w64 into a folder of its own and add that folder to the
|
||||
search path in your own Cargo config (`%USERPROFILE%\.cargo\config.toml`). Don't add all of
|
||||
mingw-w64's `lib` folder: its C runtime clashes with Rust's bundled one
|
||||
(`undefined symbol: DllEntryPoint`).
|
||||
|
||||
```powershell
|
||||
New-Item -ItemType Directory -Force C:\gnu-import-libs
|
||||
foreach ($lib in "shlwapi", "ktmw32", "propsys", "version") {
|
||||
Copy-Item "C:\msys64\mingw64\lib\lib$lib.a" C:\gnu-import-libs\
|
||||
}
|
||||
```
|
||||
|
||||
```toml
|
||||
[target.x86_64-pc-windows-gnu]
|
||||
linker = "rust-lld"
|
||||
rustflags = ["-C", "link-self-contained=yes", "-L", "native=C:/gnu-import-libs"]
|
||||
```
|
||||
|
||||
## Ground rules
|
||||
|
||||
- **Clean room.** Work from After Effects' public documentation and its observable behaviour only.
|
||||
Never use Adobe artwork, presets or code, and never copy GPL/LGPL/AGPL code.
|
||||
- **Every asset is attributed.** Add a `.attribution` sidecar and an [ATTRIBUTION.md](ATTRIBUTION.md)
|
||||
row; `cargo xtask assets` checks it.
|
||||
- **Layering.** Nothing below the interface layer depends on UI or OS crates; see
|
||||
[docs/architecture.md](docs/architecture.md).
|
||||
- **Everything is a command.** New features are engine commands with an id, label, menu path,
|
||||
shortcut and parameters, so the UI, the CLI and the MCP server get them at once. Interactive
|
||||
widgets register an automation id.
|
||||
- **Never crash.** Non-test code returns errors instead of panicking: no `unwrap()`, `expect()`,
|
||||
`panic!`, `unreachable!`, `todo!`, `unimplemented!` or `unsafe`, checked indexing and arithmetic on
|
||||
input-derived values, and a regression test with every crash fix. See the "Never crash" section of
|
||||
[AGENTS.md](AGENTS.md).
|
||||
- **Tests with every change**, and green `cargo xtask ci` before every commit. One task per commit.
|
||||
|
||||
## How to add…
|
||||
|
||||
- **An effect:** add an `EffectSpec` in `crates/effects` (AE display name, category, parameters
|
||||
with AE's names, defaults and ranges) and its render function, with a determinism test and a
|
||||
behaviour test. If it reads the clock directly, add it to `TIME_DEPENDENT`.
|
||||
- **A command:** register it with `cmd!` in `crates/engine/src/commands/`, place it in the menu
|
||||
tree in `crates/engine/src/menus.rs`, and test it including undo.
|
||||
- **A panel or dialog:** `crates/ui-egui/src/panels/`; look at the result with the headless
|
||||
snapshot tool described in [docs/testing.md](docs/testing.md).
|
||||
|
||||
## Talk to us
|
||||
|
||||
Questions, ideas and bug reports are welcome on [Discord](https://discord.gg/artcraft) and in
|
||||
GitHub issues.
|
||||
Generated
+8262
-8262
File diff suppressed because it is too large.
Load diff
+249
-249
@@ -1,249 +1,249 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/*", "apps/*", "xtask"]
|
||||
default-members = ["crates/*", "apps/*"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.24.1"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.95"
|
||||
repository = "https://github.com/storytold/effectcraft"
|
||||
|
||||
[workspace.dependencies]
|
||||
craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", version = "=0.1.0", default-features = false, features = ["read"] }
|
||||
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-quit = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-ecproj-format = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-dpx = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-eps = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-exr = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-gif = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-psd = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-svg = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-webp = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-lottie = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-mesh = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", default-features = false, features = ["stl", "obj", "gltf", "fbx"] }
|
||||
craft-aep = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
# craft-update: app-specific pretend versions apply to startup and manual checks.
|
||||
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "47107f660191de2f0b425569506f4daa4faadac9", features = ["egui"] }
|
||||
# internal
|
||||
effectcraft-time = { path = "crates/time" }
|
||||
effectcraft-geom = { path = "crates/geom" }
|
||||
effectcraft-color = { path = "crates/color" }
|
||||
effectcraft-raster = { path = "crates/raster" }
|
||||
effectcraft-path = { path = "crates/path" }
|
||||
effectcraft-keyframe = { path = "crates/keyframe" }
|
||||
effectcraft-project = { path = "crates/project" }
|
||||
effectcraft-text = { path = "crates/text" }
|
||||
effectcraft-effects = { path = "crates/effects" }
|
||||
effectcraft-track = { path = "crates/track" }
|
||||
effectcraft-segment = { path = "crates/segment" }
|
||||
effectcraft-sam2 = { path = "crates/sam2" }
|
||||
effectcraft-model = { path = "crates/model" }
|
||||
effectcraft-render = { path = "crates/render" }
|
||||
effectcraft-gpu = { path = "crates/gpu" }
|
||||
effectcraft-media = { path = "crates/media" }
|
||||
effectcraft-export = { path = "crates/export" }
|
||||
effectcraft-expr = { path = "crates/expr" }
|
||||
effectcraft-lottie = { path = "crates/lottie" }
|
||||
effectcraft-interchange = { path = "crates/interchange" }
|
||||
effectcraft-pdf = { path = "crates/pdf" }
|
||||
effectcraft-psd = { path = "crates/psd" }
|
||||
effectcraft-aep = { path = "crates/aep" }
|
||||
effectcraft-svg = { path = "crates/svg" }
|
||||
effectcraft-engine = { path = "crates/engine" }
|
||||
effectcraft-host = { path = "crates/host" }
|
||||
effectcraft-ui-egui = { path = "crates/ui-egui" }
|
||||
effectcraft-automation = { path = "crates/automation" }
|
||||
effectcraft-opusenc = { path = "crates/opusenc" }
|
||||
effectcraft-script = { path = "crates/script" }
|
||||
effectcraft-plugin = { path = "crates/plugin" }
|
||||
effectcraft-helper-host = { path = "crates/helper-host" }
|
||||
|
||||
# foundation
|
||||
thiserror = "2"
|
||||
serde = { version = "1", features = ["derive", "rc"] }
|
||||
serde_json = { version = "1", features = ["float_roundtrip"] }
|
||||
log = "0.4"
|
||||
# All date/time conversion goes through chrono (no hand-rolled calendar code beyond the 400-year range extension; AGENTS.md).
|
||||
chrono = { version = "0.4.45", default-features = false, features = ["std"] }
|
||||
rayon = "1"
|
||||
# std::time::Instant/SystemTime on native; the browser clock on wasm32 (std's panic there).
|
||||
web-time = "1"
|
||||
gif = "0.14"
|
||||
proptest = "1"
|
||||
|
||||
# scripting (expressions)
|
||||
boa_engine = { version = "0.22", default-features = false }
|
||||
# WebAssembly effect plug-ins (sandboxed interpreter; MIT/Apache-2.0, pure Rust)
|
||||
wasmi = { version = "2", default-features = false, features = ["std", "validate", "wat", "deterministic", "portable-dispatch"] }
|
||||
|
||||
# geometry / fonts
|
||||
kurbo = { version = "0.13", features = ["serde"] }
|
||||
tiny-skia = { version = "0.12", default-features = false, features = ["std", "simd"] }
|
||||
skrifa = { version = "0.44", default-features = false, features = ["std"] }
|
||||
harfrust = { version = "0.12", default-features = false, features = ["std"] }
|
||||
exr = "1.74"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg"] }
|
||||
same-file = "1.0"
|
||||
tempfile = "3.27"
|
||||
|
||||
# codecs + containers shared by the Craft apps (crates/media, crates/export); keep all three on one rev.
|
||||
craft-media = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", default-features = false, features = ["isobmff", "matroska"] }
|
||||
craft-audio = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", default-features = false, features = ["aac-decode", "aac-encode", "opus-decode"] }
|
||||
craft-video = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", features = ["h264-encode", "prores", "hevc-encode", "vp9-encode", "av1", "threads"] }
|
||||
|
||||
# footage decoding: FilmCraft's pure-Rust crates, pinned to a revision (plan/adr/0001).
|
||||
# Only `crates/media`, `crates/export` and `crates/interchange` may depend on these.
|
||||
filmcraft-codecs = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-media = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-frame = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-color = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-time = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
# MPEG TS / PS headers read for AAC channel layouts (crates/media); craft-media has no MPEG TS reader
|
||||
filmcraft-mpegts = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
# decoders used as the round-trip oracles of craft-video's VP9, HEVC and AV1 encoders (crates/export
|
||||
# dev-dependencies only); craft-video has no decoder for them
|
||||
filmcraft-vp9 = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-hevc = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-av1 = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
# timeline interchange (EDL, FCP7 XML, FCPXML, OTIO) for Premiere Pro interop (crates/interchange only)
|
||||
filmcraft-interchange = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-project = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-geom = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
|
||||
# gpu (the same wgpu eframe / egui-wgpu use)
|
||||
wgpu = { version = "30.0.1", features = ["fragile-send-sync-non-atomic-wasm"] }
|
||||
bytemuck = "1"
|
||||
pollster = "1"
|
||||
half = "2.7"
|
||||
|
||||
# ui
|
||||
# Shared frontend components; keep this dependency above the engine boundary.
|
||||
# Shared UI and the complete egui family select reviewed immutable commits below.
|
||||
craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", features = ["serde"] }
|
||||
# `default_fonts` and `monochrome_emoji_fonts` are explicit: the app's `FontDefinitions::default()`
|
||||
# and its `✔`/`▣` chrome glyphs need both, and kittest's own dev-dependencies do not propagate.
|
||||
egui = { version = "0.37", default-features = false, features = ["default_fonts", "monochrome_emoji_fonts"] }
|
||||
eframe = { version = "0.37", default-features = false }
|
||||
# Resizable table columns (About ▸ Contributors / Models); no image loaders.
|
||||
egui_extras = { version = "0.37", default-features = false }
|
||||
egui_kittest = "0.37"
|
||||
rfd = "0.17"
|
||||
cpal = "0.16"
|
||||
# The operating system's interface language (MIT/Apache-2.0, no dependencies on desktop).
|
||||
sys-locale = "0.3"
|
||||
|
||||
# Local co-development against a FilmCraft checkout next to this repo: uncomment.
|
||||
# [patch."https://github.com/storytold/filmcraft"]
|
||||
# filmcraft-codecs = { path = "../filmcraft/crates/codecs" }
|
||||
# filmcraft-media = { path = "../filmcraft/crates/media" }
|
||||
# filmcraft-frame = { path = "../filmcraft/crates/frame" }
|
||||
# filmcraft-color = { path = "../filmcraft/crates/color" }
|
||||
# filmcraft-time = { path = "../filmcraft/crates/time" }
|
||||
# filmcraft-mpegts = { path = "../filmcraft/crates/mpegts" }
|
||||
|
||||
[workspace.lints.rust]
|
||||
unsafe_code = "forbid"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
cognitive_complexity = "deny"
|
||||
too_many_arguments = "deny"
|
||||
wildcard_imports = "deny"
|
||||
dbg_macro = "warn"
|
||||
todo = "warn"
|
||||
needless_range_loop = "allow"
|
||||
type_complexity = "allow"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 2
|
||||
debug = false
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
# Panics and backtraces keep file and line; full debug info only made the many test binaries large.
|
||||
debug = "line-tables-only"
|
||||
|
||||
# Same settings as `dev`, but with full debug info for interactive debugger sessions: local
|
||||
# variables, parameters and column breakpoints work. Use `--profile dev-full` when debugging;
|
||||
# everything else stays on the fast default. See CONTRIBUTING.md, "Setup".
|
||||
[profile.dev-full]
|
||||
inherits = "dev"
|
||||
debug = true
|
||||
|
||||
# Pixel hot loops: keep debug builds interactive.
|
||||
[profile.dev.package.effectcraft-raster]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-render]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-gpu]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-effects]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-track]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-path]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-media]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-opusenc]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-export]
|
||||
opt-level = 3
|
||||
|
||||
# craft-libs codec hot loops.
|
||||
[profile.dev.package.craft-video]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.craft-audio]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.craft-media]
|
||||
opt-level = 3
|
||||
|
||||
# FilmCraft codec hot loops (same settings as FilmCraft's own workspace).
|
||||
[profile.dev.package.filmcraft-h264]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.filmcraft-hevc]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.filmcraft-vp9]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.filmcraft-dnx]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.filmcraft-av1]
|
||||
opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
|
||||
[patch.crates-io]
|
||||
hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", version = "=0.7.2" }
|
||||
hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", version = "=0.3.0" }
|
||||
ecolor = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
emath = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
epaint = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
epaint_default_fonts = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
egui = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
egui-winit = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
egui-wgpu = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
egui_glow = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
egui_extras = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
egui_kittest = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
eframe = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
# Patch a crate only together with the dependency that needs it: an unused patch makes the Nix
|
||||
# build, which uses vendored sources, fail (`cargo xtask lock-patches`).
|
||||
winit = { git = "https://github.com/storytold/winit.git", rev = "a517b3846ee8457d84ee4975110bfda6797c44dc" }
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["crates/*", "apps/*", "xtask"]
|
||||
default-members = ["crates/*", "apps/*"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.24.1"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.95"
|
||||
repository = "https://github.com/storytold/effectcraft"
|
||||
|
||||
[workspace.dependencies]
|
||||
craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", version = "=0.1.0", default-features = false, features = ["read"] }
|
||||
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-quit = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-ecproj-format = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-dpx = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-eps = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-exr = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-gif = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-psd = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-svg = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-webp = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-lottie = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
craft-mesh = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", default-features = false, features = ["stl", "obj", "gltf", "fbx"] }
|
||||
craft-aep = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9" }
|
||||
# craft-update: app-specific pretend versions apply to startup and manual checks.
|
||||
craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "47107f660191de2f0b425569506f4daa4faadac9", features = ["egui"] }
|
||||
# internal
|
||||
effectcraft-time = { path = "crates/time" }
|
||||
effectcraft-geom = { path = "crates/geom" }
|
||||
effectcraft-color = { path = "crates/color" }
|
||||
effectcraft-raster = { path = "crates/raster" }
|
||||
effectcraft-path = { path = "crates/path" }
|
||||
effectcraft-keyframe = { path = "crates/keyframe" }
|
||||
effectcraft-project = { path = "crates/project" }
|
||||
effectcraft-text = { path = "crates/text" }
|
||||
effectcraft-effects = { path = "crates/effects" }
|
||||
effectcraft-track = { path = "crates/track" }
|
||||
effectcraft-segment = { path = "crates/segment" }
|
||||
effectcraft-sam2 = { path = "crates/sam2" }
|
||||
effectcraft-model = { path = "crates/model" }
|
||||
effectcraft-render = { path = "crates/render" }
|
||||
effectcraft-gpu = { path = "crates/gpu" }
|
||||
effectcraft-media = { path = "crates/media" }
|
||||
effectcraft-export = { path = "crates/export" }
|
||||
effectcraft-expr = { path = "crates/expr" }
|
||||
effectcraft-lottie = { path = "crates/lottie" }
|
||||
effectcraft-interchange = { path = "crates/interchange" }
|
||||
effectcraft-pdf = { path = "crates/pdf" }
|
||||
effectcraft-psd = { path = "crates/psd" }
|
||||
effectcraft-aep = { path = "crates/aep" }
|
||||
effectcraft-svg = { path = "crates/svg" }
|
||||
effectcraft-engine = { path = "crates/engine" }
|
||||
effectcraft-host = { path = "crates/host" }
|
||||
effectcraft-ui-egui = { path = "crates/ui-egui" }
|
||||
effectcraft-automation = { path = "crates/automation" }
|
||||
effectcraft-opusenc = { path = "crates/opusenc" }
|
||||
effectcraft-script = { path = "crates/script" }
|
||||
effectcraft-plugin = { path = "crates/plugin" }
|
||||
effectcraft-helper-host = { path = "crates/helper-host" }
|
||||
|
||||
# foundation
|
||||
thiserror = "2"
|
||||
serde = { version = "1", features = ["derive", "rc"] }
|
||||
serde_json = { version = "1", features = ["float_roundtrip"] }
|
||||
log = "0.4"
|
||||
# All date/time conversion goes through chrono (no hand-rolled calendar code beyond the 400-year range extension; AGENTS.md).
|
||||
chrono = { version = "0.4.45", default-features = false, features = ["std"] }
|
||||
rayon = "1"
|
||||
# std::time::Instant/SystemTime on native; the browser clock on wasm32 (std's panic there).
|
||||
web-time = "1"
|
||||
gif = "0.14"
|
||||
proptest = "1"
|
||||
|
||||
# scripting (expressions)
|
||||
boa_engine = { version = "0.22", default-features = false }
|
||||
# WebAssembly effect plug-ins (sandboxed interpreter; MIT/Apache-2.0, pure Rust)
|
||||
wasmi = { version = "2", default-features = false, features = ["std", "validate", "wat", "deterministic", "portable-dispatch"] }
|
||||
|
||||
# geometry / fonts
|
||||
kurbo = { version = "0.13", features = ["serde"] }
|
||||
tiny-skia = { version = "0.12", default-features = false, features = ["std", "simd"] }
|
||||
skrifa = { version = "0.44", default-features = false, features = ["std"] }
|
||||
harfrust = { version = "0.12", default-features = false, features = ["std"] }
|
||||
exr = "1.74"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg"] }
|
||||
same-file = "1.0"
|
||||
tempfile = "3.27"
|
||||
|
||||
# codecs + containers shared by the Craft apps (crates/media, crates/export); keep all three on one rev.
|
||||
craft-media = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", default-features = false, features = ["isobmff", "matroska"] }
|
||||
craft-audio = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", default-features = false, features = ["aac-decode", "aac-encode", "opus-decode"] }
|
||||
craft-video = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", features = ["h264-encode", "prores", "hevc-encode", "vp9-encode", "av1", "threads"] }
|
||||
|
||||
# footage decoding: FilmCraft's pure-Rust crates, pinned to a revision (plan/adr/0001).
|
||||
# Only `crates/media`, `crates/export` and `crates/interchange` may depend on these.
|
||||
filmcraft-codecs = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-media = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-frame = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-color = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-time = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
# MPEG TS / PS headers read for AAC channel layouts (crates/media); craft-media has no MPEG TS reader
|
||||
filmcraft-mpegts = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
# decoders used as the round-trip oracles of craft-video's VP9, HEVC and AV1 encoders (crates/export
|
||||
# dev-dependencies only); craft-video has no decoder for them
|
||||
filmcraft-vp9 = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-hevc = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-av1 = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
# timeline interchange (EDL, FCP7 XML, FCPXML, OTIO) for Premiere Pro interop (crates/interchange only)
|
||||
filmcraft-interchange = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-project = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
filmcraft-geom = { git = "https://github.com/storytold/filmcraft", rev = "d5ead4ba1ee2f1cd13cb86a4239aee2730a3d9f5" }
|
||||
|
||||
# gpu (the same wgpu eframe / egui-wgpu use)
|
||||
wgpu = { version = "30.0.1", features = ["fragile-send-sync-non-atomic-wasm"] }
|
||||
bytemuck = "1"
|
||||
pollster = "1"
|
||||
half = "2.7"
|
||||
|
||||
# ui
|
||||
# Shared frontend components; keep this dependency above the engine boundary.
|
||||
# Shared UI and the complete egui family select reviewed immutable commits below.
|
||||
craft-ui = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", features = ["serde"] }
|
||||
# `default_fonts` and `monochrome_emoji_fonts` are explicit: the app's `FontDefinitions::default()`
|
||||
# and its `✔`/`▣` chrome glyphs need both, and kittest's own dev-dependencies do not propagate.
|
||||
egui = { version = "0.37", default-features = false, features = ["default_fonts", "monochrome_emoji_fonts"] }
|
||||
eframe = { version = "0.37", default-features = false }
|
||||
# Resizable table columns (About ▸ Contributors / Models); no image loaders.
|
||||
egui_extras = { version = "0.37", default-features = false }
|
||||
egui_kittest = "0.37"
|
||||
rfd = "0.17"
|
||||
cpal = "0.16"
|
||||
# The operating system's interface language (MIT/Apache-2.0, no dependencies on desktop).
|
||||
sys-locale = "0.3"
|
||||
|
||||
# Local co-development against a FilmCraft checkout next to this repo: uncomment.
|
||||
# [patch."https://github.com/storytold/filmcraft"]
|
||||
# filmcraft-codecs = { path = "../filmcraft/crates/codecs" }
|
||||
# filmcraft-media = { path = "../filmcraft/crates/media" }
|
||||
# filmcraft-frame = { path = "../filmcraft/crates/frame" }
|
||||
# filmcraft-color = { path = "../filmcraft/crates/color" }
|
||||
# filmcraft-time = { path = "../filmcraft/crates/time" }
|
||||
# filmcraft-mpegts = { path = "../filmcraft/crates/mpegts" }
|
||||
|
||||
[workspace.lints.rust]
|
||||
unsafe_code = "forbid"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
cognitive_complexity = "deny"
|
||||
too_many_arguments = "deny"
|
||||
wildcard_imports = "deny"
|
||||
dbg_macro = "warn"
|
||||
todo = "warn"
|
||||
needless_range_loop = "allow"
|
||||
type_complexity = "allow"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 2
|
||||
debug = false
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
# Panics and backtraces keep file and line; full debug info only made the many test binaries large.
|
||||
debug = "line-tables-only"
|
||||
|
||||
# Same settings as `dev`, but with full debug info for interactive debugger sessions: local
|
||||
# variables, parameters and column breakpoints work. Use `--profile dev-full` when debugging;
|
||||
# everything else stays on the fast default. See CONTRIBUTING.md, "Setup".
|
||||
[profile.dev-full]
|
||||
inherits = "dev"
|
||||
debug = true
|
||||
|
||||
# Pixel hot loops: keep debug builds interactive.
|
||||
[profile.dev.package.effectcraft-raster]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-render]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-gpu]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-effects]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-track]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-path]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-media]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-opusenc]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.effectcraft-export]
|
||||
opt-level = 3
|
||||
|
||||
# craft-libs codec hot loops.
|
||||
[profile.dev.package.craft-video]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.craft-audio]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.craft-media]
|
||||
opt-level = 3
|
||||
|
||||
# FilmCraft codec hot loops (same settings as FilmCraft's own workspace).
|
||||
[profile.dev.package.filmcraft-h264]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.filmcraft-hevc]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.filmcraft-vp9]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.filmcraft-dnx]
|
||||
opt-level = 3
|
||||
|
||||
[profile.dev.package.filmcraft-av1]
|
||||
opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
|
||||
[patch.crates-io]
|
||||
hayro-syntax = { git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", version = "=0.7.2" }
|
||||
hayro-jbig2 = { git = "https://github.com/storytold/craft-libs", rev = "47107f660191de2f0b425569506f4daa4faadac9", version = "=0.3.0" }
|
||||
ecolor = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
emath = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
epaint = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
epaint_default_fonts = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
egui = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
egui-winit = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
egui-wgpu = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
egui_glow = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
egui_extras = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
egui_kittest = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
eframe = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }
|
||||
# Patch a crate only together with the dependency that needs it: an unused patch makes the Nix
|
||||
# build, which uses vendored sources, fail (`cargo xtask lock-patches`).
|
||||
winit = { git = "https://github.com/storytold/winit.git", rev = "a517b3846ee8457d84ee4975110bfda6797c44dc" }
|
||||
+177
-177
@@ -1,177 +1,177 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
+21
-21
@@ -1,21 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 ArtCraft Team and the EffectCraft contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 ArtCraft Team and the EffectCraft contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,322 +1,322 @@
|
||||
EffectCraft
|
||||
Copyright (c) 2026 ArtCraft Team and the EffectCraft contributors.
|
||||
Licensed under the MIT License or the Apache License, Version 2.0, at your option
|
||||
(LICENSE-MIT, LICENSE-APACHE).
|
||||
|
||||
The ArtCraft name, wordmark and logos in docs/brand/ are trademarks of the ArtCraft Team. They are
|
||||
not open source and are not covered by the license above; their terms are in
|
||||
docs/brand/LICENSE-brand.txt.
|
||||
|
||||
Third-party material
|
||||
|
||||
This product bundles third-party material under its own licenses, listed below. The per-file
|
||||
list (author, source, license) is in ATTRIBUTION.md.
|
||||
|
||||
Bundled fonts
|
||||
|
||||
EffectCraft embeds Inter, Noto Serif, JetBrains Mono and Noto Sans Symbols 2 from
|
||||
assets/fonts/. The pinned egui dependency (epaint_default_fonts, revision
|
||||
6d518fc799681571963842f95b55a6b68431b15a) additionally embeds Ubuntu Light, Hack,
|
||||
Noto Emoji, egui-icons and emoji-icon-font through EffectCraft's enabled `default_fonts`
|
||||
and `monochrome_emoji_fonts` features; no font file from that dependency is added to
|
||||
this repository. The complete copyright notices and licence texts required for all of
|
||||
these fonts follow.
|
||||
|
||||
The optional PDF document reader uses craft-pdf and Hayro-syntax 0.7.2, MIT OR Apache-2.0.
|
||||
The shared patched syntax source is centralized in storytold/craft-libs, transferred from
|
||||
PdfCraft revision 9ed3abe4907a127d36b12fbfeee71d7e26248da1. This parser-only use embeds no
|
||||
Hayro fonts, CMaps or image codecs; EffectCraft keeps its existing fonts and drawing filters.
|
||||
|
||||
Linux Wayland window decorations
|
||||
-------------------------------
|
||||
sctk-adwaita 0.10.1 (https://github.com/PolyMeilex/sctk-adwaita,
|
||||
commit d5f343c45c04d4a026db57f76760819b7325db00) is used by winit on Linux
|
||||
for vector window controls. Its title-font features are disabled: no Cantarell
|
||||
font is embedded by this dependency and no font file is added to this repo.
|
||||
|
||||
sctk-adwaita's MIT licence:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Bartłomiej Maryńczak
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
SIL Open Font License 1.1
|
||||
|
||||
Applies to Inter (assets/fonts/Inter-*.ttf), Noto Serif (assets/fonts/NotoSerif-Regular.ttf),
|
||||
JetBrains Mono (assets/fonts/JetBrainsMono-Regular.ttf), Noto Sans Symbols 2
|
||||
(assets/fonts/NotoSansSymbols2-Regular.ttf) and Noto Emoji (NotoEmoji-Regular.ttf).
|
||||
Copyright notices:
|
||||
|
||||
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)
|
||||
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
|
||||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/symbols)
|
||||
Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
The fonts in assets/fonts/ also keep their upstream licence files at
|
||||
assets/fonts/OFL-<family>.txt. The complete licence text follows.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Ubuntu Font Licence 1.0
|
||||
|
||||
Applies to Ubuntu Light (Ubuntu-Light.ttf). Copyright notice:
|
||||
|
||||
Copyright 2011 Canonical Ltd. Licensed under the Ubuntu Font Licence 1.0
|
||||
|
||||
-------------------------------
|
||||
UBUNTU FONT LICENCE Version 1.0
|
||||
-------------------------------
|
||||
|
||||
PREAMBLE
|
||||
This licence allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely. The fonts, including any derivative works, can be
|
||||
bundled, embedded, and redistributed provided the terms of this licence
|
||||
are met. The fonts and derivatives, however, cannot be released under
|
||||
any other licence. The requirement for fonts to remain under this
|
||||
licence does not require any document created using the fonts or their
|
||||
derivatives to be published under this licence, as long as the primary
|
||||
purpose of the document is not to be a vehicle for the distribution of
|
||||
the fonts.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this licence and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Original Version" refers to the collection of Font Software components
|
||||
as received under this licence.
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to
|
||||
a new environment.
|
||||
|
||||
"Copyright Holder(s)" refers to all individuals and companies who have a
|
||||
copyright ownership of the Font Software.
|
||||
|
||||
"Substantially Changed" refers to Modified Versions which can be easily
|
||||
identified as dissimilar to the Font Software by users of the Font
|
||||
Software comparing the Original Version with the Modified Version.
|
||||
|
||||
To "Propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification and with or without charging
|
||||
a redistribution fee), making available to the public, and in some
|
||||
countries other activities as well.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
This licence does not grant any rights under trademark law and all such
|
||||
rights are reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of the Font Software, to propagate the Font Software, subject to
|
||||
the below conditions:
|
||||
|
||||
1) Each copy of the Font Software must contain the above copyright
|
||||
notice and this licence. These can be included either as stand-alone
|
||||
text files, human-readable headers or in the appropriate machine-
|
||||
readable metadata fields within text or binary files as long as those
|
||||
fields can be easily viewed by the user.
|
||||
|
||||
2) The font name complies with the following:
|
||||
(a) The Original Version must retain its name, unmodified.
|
||||
(b) Modified Versions which are Substantially Changed must be renamed to
|
||||
avoid use of the name of the Original Version or similar names entirely.
|
||||
(c) Modified Versions which are not Substantially Changed must be
|
||||
renamed to both (i) retain the name of the Original Version and (ii) add
|
||||
additional naming elements to distinguish the Modified Version from the
|
||||
Original Version. The name of such Modified Versions must be the name of
|
||||
the Original Version, with "derivative X" where X represents the name of
|
||||
the new work, appended to that name.
|
||||
|
||||
3) The name(s) of the Copyright Holder(s) and any contributor to the
|
||||
Font Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except (i) as required by this licence, (ii) to
|
||||
acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
|
||||
their explicit written permission.
|
||||
|
||||
4) The Font Software, modified or unmodified, in part or in whole, must
|
||||
be distributed entirely under this licence, and must not be distributed
|
||||
under any other licence. The requirement for fonts to remain under this
|
||||
licence does not affect any document created using the Font Software,
|
||||
except any version of the Font Software extracted from a document
|
||||
created using the Font Software may only be distributed under this
|
||||
licence.
|
||||
|
||||
TERMINATION
|
||||
This licence becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
|
||||
DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Hack: MIT License and Bitstream Vera License
|
||||
|
||||
Applies to Hack (Hack-Regular.ttf). Upstream notice and both licence texts:
|
||||
|
||||
The work in the Hack project is Copyright 2018 Source Foundry Authors and licensed under the MIT License
|
||||
|
||||
The work in the DejaVu project was committed to the public domain.
|
||||
|
||||
Bitstream Vera Sans Mono Copyright 2003 Bitstream Inc. and licensed under the Bitstream Vera License with Reserved Font Names "Bitstream" and "Vera"
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Source Foundry Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
BITSTREAM VERA LICENSE
|
||||
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.
|
||||
|
||||
MIT License (egui-icons and emoji-icon-font)
|
||||
|
||||
Applies to egui-icons.ttf, a five-glyph subset of emoji-icon-font, and the parent
|
||||
emoji-icon-font.ttf, both by John Slegers, https://github.com/jslegers/emoji-icon-font.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014 John Slegers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
EffectCraft
|
||||
Copyright (c) 2026 ArtCraft Team and the EffectCraft contributors.
|
||||
Licensed under the MIT License or the Apache License, Version 2.0, at your option
|
||||
(LICENSE-MIT, LICENSE-APACHE).
|
||||
|
||||
The ArtCraft name, wordmark and logos in docs/brand/ are trademarks of the ArtCraft Team. They are
|
||||
not open source and are not covered by the license above; their terms are in
|
||||
docs/brand/LICENSE-brand.txt.
|
||||
|
||||
Third-party material
|
||||
|
||||
This product bundles third-party material under its own licenses, listed below. The per-file
|
||||
list (author, source, license) is in ATTRIBUTION.md.
|
||||
|
||||
Bundled fonts
|
||||
|
||||
EffectCraft embeds Inter, Noto Serif, JetBrains Mono and Noto Sans Symbols 2 from
|
||||
assets/fonts/. The pinned egui dependency (epaint_default_fonts, revision
|
||||
6d518fc799681571963842f95b55a6b68431b15a) additionally embeds Ubuntu Light, Hack,
|
||||
Noto Emoji, egui-icons and emoji-icon-font through EffectCraft's enabled `default_fonts`
|
||||
and `monochrome_emoji_fonts` features; no font file from that dependency is added to
|
||||
this repository. The complete copyright notices and licence texts required for all of
|
||||
these fonts follow.
|
||||
|
||||
The optional PDF document reader uses craft-pdf and Hayro-syntax 0.7.2, MIT OR Apache-2.0.
|
||||
The shared patched syntax source is centralized in storytold/craft-libs, transferred from
|
||||
PdfCraft revision 9ed3abe4907a127d36b12fbfeee71d7e26248da1. This parser-only use embeds no
|
||||
Hayro fonts, CMaps or image codecs; EffectCraft keeps its existing fonts and drawing filters.
|
||||
|
||||
Linux Wayland window decorations
|
||||
-------------------------------
|
||||
sctk-adwaita 0.10.1 (https://github.com/PolyMeilex/sctk-adwaita,
|
||||
commit d5f343c45c04d4a026db57f76760819b7325db00) is used by winit on Linux
|
||||
for vector window controls. Its title-font features are disabled: no Cantarell
|
||||
font is embedded by this dependency and no font file is added to this repo.
|
||||
|
||||
sctk-adwaita's MIT licence:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Bartłomiej Maryńczak
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
SIL Open Font License 1.1
|
||||
|
||||
Applies to Inter (assets/fonts/Inter-*.ttf), Noto Serif (assets/fonts/NotoSerif-Regular.ttf),
|
||||
JetBrains Mono (assets/fonts/JetBrainsMono-Regular.ttf), Noto Sans Symbols 2
|
||||
(assets/fonts/NotoSansSymbols2-Regular.ttf) and Noto Emoji (NotoEmoji-Regular.ttf).
|
||||
Copyright notices:
|
||||
|
||||
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
||||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)
|
||||
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
|
||||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/symbols)
|
||||
Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
The fonts in assets/fonts/ also keep their upstream licence files at
|
||||
assets/fonts/OFL-<family>.txt. The complete licence text follows.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Ubuntu Font Licence 1.0
|
||||
|
||||
Applies to Ubuntu Light (Ubuntu-Light.ttf). Copyright notice:
|
||||
|
||||
Copyright 2011 Canonical Ltd. Licensed under the Ubuntu Font Licence 1.0
|
||||
|
||||
-------------------------------
|
||||
UBUNTU FONT LICENCE Version 1.0
|
||||
-------------------------------
|
||||
|
||||
PREAMBLE
|
||||
This licence allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely. The fonts, including any derivative works, can be
|
||||
bundled, embedded, and redistributed provided the terms of this licence
|
||||
are met. The fonts and derivatives, however, cannot be released under
|
||||
any other licence. The requirement for fonts to remain under this
|
||||
licence does not require any document created using the fonts or their
|
||||
derivatives to be published under this licence, as long as the primary
|
||||
purpose of the document is not to be a vehicle for the distribution of
|
||||
the fonts.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this licence and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Original Version" refers to the collection of Font Software components
|
||||
as received under this licence.
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to
|
||||
a new environment.
|
||||
|
||||
"Copyright Holder(s)" refers to all individuals and companies who have a
|
||||
copyright ownership of the Font Software.
|
||||
|
||||
"Substantially Changed" refers to Modified Versions which can be easily
|
||||
identified as dissimilar to the Font Software by users of the Font
|
||||
Software comparing the Original Version with the Modified Version.
|
||||
|
||||
To "Propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification and with or without charging
|
||||
a redistribution fee), making available to the public, and in some
|
||||
countries other activities as well.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
This licence does not grant any rights under trademark law and all such
|
||||
rights are reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of the Font Software, to propagate the Font Software, subject to
|
||||
the below conditions:
|
||||
|
||||
1) Each copy of the Font Software must contain the above copyright
|
||||
notice and this licence. These can be included either as stand-alone
|
||||
text files, human-readable headers or in the appropriate machine-
|
||||
readable metadata fields within text or binary files as long as those
|
||||
fields can be easily viewed by the user.
|
||||
|
||||
2) The font name complies with the following:
|
||||
(a) The Original Version must retain its name, unmodified.
|
||||
(b) Modified Versions which are Substantially Changed must be renamed to
|
||||
avoid use of the name of the Original Version or similar names entirely.
|
||||
(c) Modified Versions which are not Substantially Changed must be
|
||||
renamed to both (i) retain the name of the Original Version and (ii) add
|
||||
additional naming elements to distinguish the Modified Version from the
|
||||
Original Version. The name of such Modified Versions must be the name of
|
||||
the Original Version, with "derivative X" where X represents the name of
|
||||
the new work, appended to that name.
|
||||
|
||||
3) The name(s) of the Copyright Holder(s) and any contributor to the
|
||||
Font Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except (i) as required by this licence, (ii) to
|
||||
acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
|
||||
their explicit written permission.
|
||||
|
||||
4) The Font Software, modified or unmodified, in part or in whole, must
|
||||
be distributed entirely under this licence, and must not be distributed
|
||||
under any other licence. The requirement for fonts to remain under this
|
||||
licence does not affect any document created using the Font Software,
|
||||
except any version of the Font Software extracted from a document
|
||||
created using the Font Software may only be distributed under this
|
||||
licence.
|
||||
|
||||
TERMINATION
|
||||
This licence becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
|
||||
DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Hack: MIT License and Bitstream Vera License
|
||||
|
||||
Applies to Hack (Hack-Regular.ttf). Upstream notice and both licence texts:
|
||||
|
||||
The work in the Hack project is Copyright 2018 Source Foundry Authors and licensed under the MIT License
|
||||
|
||||
The work in the DejaVu project was committed to the public domain.
|
||||
|
||||
Bitstream Vera Sans Mono Copyright 2003 Bitstream Inc. and licensed under the Bitstream Vera License with Reserved Font Names "Bitstream" and "Vera"
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Source Foundry Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
BITSTREAM VERA LICENSE
|
||||
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.
|
||||
|
||||
MIT License (egui-icons and emoji-icon-font)
|
||||
|
||||
Applies to egui-icons.ttf, a five-glyph subset of emoji-icon-font, and the parent
|
||||
emoji-icon-font.ttf, both by John Slegers, https://github.com/jslegers/emoji-icon-font.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014 John Slegers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,399 +1,399 @@
|
||||
<p align="center">
|
||||
<a href="https://getartcraft.com/">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="docs/brand/artcraft-logo-white.svg">
|
||||
<img alt="ArtCraft" src="docs/brand/artcraft-logo.svg" width="200">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/app-icon/hicolor/256x256/apps/ai.storyteller.effectcraft.png" width="128" height="128" alt="EffectCraft icon: an engraved unicorn on a pink tile">
|
||||
</p>
|
||||
|
||||
<h1 align="center">EffectCraft</h1>
|
||||
|
||||
<p align="center">
|
||||
<b>Motion graphics and visual effects; an open-source, professional motion graphics and visual effects studio, built in pure Rust.</b>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
A free, open-source compositor: compositions, layers,
|
||||
keyframes, 308 effects, layer styles, expressions, 3D cameras and lights, and a render queue, native on macOS,
|
||||
Windows and Linux, and in the browser. Young, moving fast, and already usable.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img alt="Status: young and moving fast" src="https://img.shields.io/badge/status-young%20and%20moving%20fast-e0368f?style=flat-square">
|
||||
<img alt="Written in Rust" src="https://img.shields.io/badge/rust-1.95%2B-b0206c?style=flat-square&logo=rust&logoColor=white">
|
||||
<img alt="License: MIT OR Apache-2.0" src="https://img.shields.io/badge/license-MIT%20%2F%20Apache--2.0-555?style=flat-square">
|
||||
<br>
|
||||
<a href="https://sonarcloud.io/project/overview?id=storytold_effectcraft"><img alt="Scanned by SonarCloud" src="https://img.shields.io/badge/Scanned_by-SonarCloud-4b9fd5?style=flat-square&logo=sonarqubecloud&logoColor=white"></a>
|
||||
<a href="https://coderabbit.ai"><img alt="Reviewed by CodeRabbit" src="https://img.shields.io/coderabbit/prs/github/storytold/effectcraft?style=flat-square&label=Reviewed%20by%20CodeRabbit&logo=coderabbit"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/artcraft"><img alt="Join the ArtCraft community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://getartcraft.com/apps/effectcraft"><b>EffectCraft on getartcraft.com</b></a> ·
|
||||
<a href="https://getartcraft.com/">ArtCraft</a> ·
|
||||
<a href="https://getartcraft.com/apps">All Crafting Apps</a>
|
||||
</p>
|
||||
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/images/effectcraft-hero.png" alt="EffectCraft's main window: the animated demo composition in the Composition panel, the Project panel, a Timeline with text, shape and solid layers, and the Properties panel showing the selected text layer's transform, font and paragraph settings" width="100%">
|
||||
</p>
|
||||
|
||||
> [!NOTE]
|
||||
> **ArtCraft is a community of artists from all walks of life.** Digital, generative, music,
|
||||
> games — if you make things, you're one of us. **[Come say hi on Discord](https://discord.gg/artcraft).**
|
||||
|
||||
<p align="center">
|
||||
<a href="#what-effectcraft-is">What it is</a> ·
|
||||
<a href="#animate">Animate</a> ·
|
||||
<a href="#effects">Effects</a> ·
|
||||
<a href="#3d">3D</a> ·
|
||||
<a href="#export">Export</a> ·
|
||||
<a href="#built-for-agents">Agents</a> ·
|
||||
<a href="#get-started">Get started</a> ·
|
||||
<a href="#where-it-stands">Status</a> ·
|
||||
<a href="#how-its-made">How it's made</a> ·
|
||||
<a href="#downloads">Downloads</a> ·
|
||||
<a href="#the-crafting-apps">The Crafting Apps</a> ·
|
||||
<a href="#license-and-credits">License</a>
|
||||
</p>
|
||||
|
||||
## What EffectCraft is
|
||||
|
||||
EffectCraft is for animated titles, motion graphics and compositing work: the kind of thing
|
||||
motion designers and VFX artists need. You build a composition out of layers (solids, shapes,
|
||||
text, footage, other compositions), animate their properties with keyframes, stack effects on
|
||||
them and render the result.
|
||||
|
||||
The interface feels natural and responsive, with dedicated panels (Project, Composition, Timeline,
|
||||
Layer, Footage, Effect Controls, Tools, Preview, Effects & Presets, Audio, Info, Paragraph,
|
||||
Character, Align, Progress, Render Queue) and standard motion graphics shortcuts, going further in
|
||||
a few places where it matters to us:
|
||||
|
||||
- **Lottie import and export built in**, so animations can go straight to the web and apps
|
||||
without a plugin.
|
||||
- **A project file you can read.** Projects are versioned JSON (`.ecproj`), so they diff cleanly
|
||||
in version control.
|
||||
- **Everything is scriptable.** Menu actions, timeline edits and property changes are exposed as
|
||||
commands. Engine commands are available through the CLI, JSON control channel and MCP;
|
||||
UI-only commands use the frontend dispatcher and can be invoked through the control channel
|
||||
or an attached MCP bridge.
|
||||
- **No FFmpeg.** Video and audio decoding and encoding are pure Rust: FilmCraft's codecs and
|
||||
EffectCraft's own VP9, AV1, HEVC and Opus encoders.
|
||||
|
||||
## Animate
|
||||
|
||||
The panels, menus and shortcuts follow standard motion design conventions:
|
||||
Project, Composition, Timeline, Effect Controls, Properties, Effects & Presets, Character,
|
||||
Paragraph, Align, Info, Preview, Audio and the Render Queue, docked the way you expect. Drag
|
||||
footage, comps and effects between them: onto the comp viewer, where they land under the pointer,
|
||||
or into the Timeline, between layers and at the time you point to.
|
||||
|
||||
- **Layers of every kind:** solids, shapes, text, footage, nested compositions, nulls,
|
||||
adjustment layers, cameras and lights; parenting, track mattes, all 38 blend modes, motion blur.
|
||||
- **Keyframes that behave the same:** linear, Bezier, hold, auto and continuous Bezier, roving
|
||||
keys, Easy Ease (F9), Keyframe Velocity and Interpolation dialogs, copy and paste at the current
|
||||
time, and a **Graph Editor** with value and speed graphs and draggable handles.
|
||||
- **Time:** time remapping, time stretch, time-reverse, freeze frame, work area, markers, exact
|
||||
frame-accurate timing at every frame rate including 29.97 drop-frame.
|
||||
- **Shapes and masks:** shape layers with trim paths, repeaters, round corners, offset, zig zag,
|
||||
twist, wiggle, merge paths and gradient strokes; masks drawn with the pen tool, with modes,
|
||||
feather, expansion and vertex editing in the viewer.
|
||||
- **Text:** point and paragraph text with real shaping in any font installed on your machine, the
|
||||
Character and Paragraph panels, and text animators with range selectors.
|
||||
- **Expressions:** JavaScript with the standard motion expressions model (`wiggle`, `loopOut`,
|
||||
`thisComp.layer("…")`, vector maths on arrays), an inline editor and the pick-whip.
|
||||
- **Timeline like you know it:** twirl layers open to Transform, masks, effects and the rest; drag
|
||||
rows to reorder layers; rename with Enter or a double-click; the property shortcuts (A, P, S,
|
||||
R, T, M, F, E, L, U and their double presses AA, PP, SS, RR, TT, MM, FF, EE, LL, UU), Shift to
|
||||
add properties, Alt+Shift+A/P/S/R/T to key the current time, Ctrl+` to twirl selected layers.
|
||||
- **Puppet tools:** Position, Advanced, Bend, Starch and Overlap pins on a mesh built from the
|
||||
layer, real-time pin recording, rotate/scale handles, pins driven by (or carrying) nulls for
|
||||
rigging, and follow-through for hair and cloth.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/images/effectcraft-graph-editor.png" alt="The Graph Editor showing an eased value curve for a text animator's Range Start, with the graph editor's button bar for hold, linear, auto Bezier and Easy Ease" width="100%">
|
||||
</p>
|
||||
|
||||
## Effects
|
||||
|
||||
308 built-in effects across its categories, each with its parameter names, order and
|
||||
defaults: blur and sharpen, channel, color correction (Curves, Levels, Hue/Saturation, a full
|
||||
color-grading panel…), distort (Warp, Bulge, Turbulent Displace, Perspective Pin…), generate (Fractal Noise,
|
||||
Gradient Ramp, Stroke, Write-on, Audio Spectrum…), keying, matte, noise and grain, perspective,
|
||||
**simulation** (Particle Emitter 3D, Rainfall, Shatter, Card Dance, Caustics, Wave World…),
|
||||
stylize (Glow, Glass Relief…), **time** (Echo, Posterize Time, Timewarp, Time Displacement…),
|
||||
**audio** (Reverb, Parametric EQ, Delay, Stereo Mixer…), text, transitions, utility and expression
|
||||
controls. All nine **Layer Styles** (Drop Shadow, Inner/Outer Glow, Bevel and Emboss, Satin,
|
||||
overlays, Stroke) with Global Light. Preview plays audio in sync, with meters and waveforms.
|
||||
|
||||
## 3D
|
||||
|
||||
Classic 3D compositing: 3D layers with orientation and material options,
|
||||
one- and two-node **cameras** with depth of field, **lights** (parallel, spot, point, ambient)
|
||||
with soft ray-traced shadows, layers that intersect correctly, orbit, pan and dolly camera
|
||||
tools, and Front, Top, Left and Custom views.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/images/effectcraft-3d.png" alt="The 3D Showcase demo in Custom View 1: intersecting cards lit by a spot light with soft shadows on a gridded floor, with the camera frustum and light drawn as wireframes" width="100%">
|
||||
</p>
|
||||
|
||||
## Export
|
||||
|
||||
A dedicated Render Queue, with Render Settings and Output Modules: **H.264** MP4 and
|
||||
**ProRes** MOV (Proxy to 4444 XQ with alpha) with audio, **HEVC** (Main / Main 10) and **AV1**
|
||||
MP4, **WebM** (VP9 with inter frames and alpha, or AV1; Opus audio), PNG, JPEG, TIFF, Targa and 32-bit EXR sequences, animated GIF and WAV/AIFF. Field
|
||||
rendering with 3:2 pulldown, effect/solo/guide/depth overrides, crop, region of interest and
|
||||
resize, Render Settings and Output Module templates with defaults, post-render actions, storage
|
||||
overflow and render logs. The same queue runs from the command line. Every encoder is pure
|
||||
Rust (craft-libs' H.264, ProRes, HEVC, VP9, AV1 and AAC; EffectCraft's own Opus); there is no
|
||||
FFmpeg inside.
|
||||
|
||||
**Lottie** goes both ways: File ▸ Export ▸ Lottie JSON… writes a composition (precomps, shape,
|
||||
solid, image, text and null layers, eased and spatial keyframes, masks, track mattes, blend
|
||||
modes, time remapping, optionally expressions) as `.json` or `.lottie`, and lists anything Lottie
|
||||
cannot express; File ▸ Import ▸ Lottie… opens one as a new composition.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/images/effectcraft-render-queue.png" alt="The Render Queue panel with two compositions queued" width="100%">
|
||||
</p>
|
||||
|
||||
## Built for agents
|
||||
|
||||
Engine commands are available through the CLI, JSON control channel and MCP. UI-only commands
|
||||
need the running app's frontend dispatcher: complete menu access requires the app's control
|
||||
channel or an attached MCP bridge. Headless MCP sessions expose supported engine commands,
|
||||
but not UI-only commands or bridge-only `ui_*` tools.
|
||||
|
||||
- **MCP server:** `effectcraft-cli mcp` speaks the Model Context Protocol over stdio, headless
|
||||
or bridged to the running app (`--bridge 9877`). Tools cover commands, the project and property
|
||||
tree, keyframes and rendered frames. This repository ships a ready [`.mcp.json`](.mcp.json).
|
||||
- **Command line:** one-shot calls with JSON output, for example
|
||||
`effectcraft-cli set Main '#1' transform/position '[100,360]' --time 0 main.ecproj --save`
|
||||
or `effectcraft-cli render --comp Main --out main.mp4`, or
|
||||
`effectcraft-cli exec file.exportLottie '{"comp":"Main","path":"main.json"}' main.ecproj`.
|
||||
- **Control channel:** `effectcraft --control 9877` accepts JSON lines to run commands, inspect
|
||||
and click any widget by its automation id, and take screenshots.
|
||||
|
||||
See [docs/agents.md](docs/agents.md) and [docs/control-protocol.md](docs/control-protocol.md).
|
||||
|
||||
## Get started
|
||||
|
||||
Installers for each version are on the [Releases](https://github.com/storytold/effectcraft/releases)
|
||||
page: a universal macOS app; Windows MSIs and portable zips for x64, x86 and ARM64; Linux
|
||||
AppImage, Flatpak, deb, rpm and tar.gz for x86_64 and aarch64, plus a riscv64 tar.gz; a FreeBSD x86_64 tarball; and the
|
||||
web build. [Downloads](#downloads) lists every file. The Windows ARM64 build runs natively on
|
||||
Windows on ARM, without x64 emulation. CI installs that MSI on Windows 11 ARM64 hardware, checks
|
||||
both programs are ARM64 and runs the command-line tool there, but it doesn't open the app's window
|
||||
or run the test suite natively on ARM64 yet, so please report anything that behaves differently.
|
||||
|
||||
On Gentoo, the community [::snakebyte overlay](https://github.com/switch87/snakebyte-overlay) packages
|
||||
the Linux release as `media-video/effectcraft-bin` (not maintained by the EffectCraft team):
|
||||
|
||||
```sh
|
||||
eselect repository add snakebyte git https://github.com/switch87/snakebyte-overlay.git
|
||||
emaint sync -r snakebyte
|
||||
echo 'media-video/effectcraft-bin ~amd64' >> /etc/portage/package.accept_keywords/effectcraft
|
||||
emerge --ask media-video/effectcraft-bin
|
||||
```
|
||||
|
||||
To build it yourself you need [Rust](https://rustup.rs/) 1.95 or newer.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/storytold/effectcraft
|
||||
cd effectcraft
|
||||
cargo run --release -p effectcraft # the app (add `-- --demo` to open the demo project)
|
||||
cargo run --release -p effectcraft-cli -- render --out intro.mp4 # render the demo headless
|
||||
cargo xtask web --serve 8765 # the browser build on http://127.0.0.1:8765/ (docs/web.md)
|
||||
```
|
||||
|
||||
New to it? [docs/footage.md](docs/footage.md) covers importing footage and image sequences and
|
||||
Interpret Footage.
|
||||
|
||||
To work on it: `cargo test --workspace`, and `cargo xtask ci` before every commit (format, lints,
|
||||
tests, layering, asset attribution and the WebAssembly build). See [CONTRIBUTING.md](CONTRIBUTING.md),
|
||||
[docs/architecture.md](docs/architecture.md) and [docs/testing.md](docs/testing.md).
|
||||
|
||||
## Where it stands
|
||||
|
||||
EffectCraft is young: its first commit was on 1 October 2026. A comprehensive feature set
|
||||
exists, including 308 effects, but breadth is not the same as full maturity across every edge
|
||||
case. Today it's a good place to try things and to tell us what breaks. It isn't yet recommended
|
||||
for production client work. In particular:
|
||||
|
||||
- **Native project format.** Projects are saved as `.ecproj`, readable versioned JSON. Proprietary
|
||||
project formats and After Effects SDK plug-ins are not supported.
|
||||
- **Fidelity is still being refined.** Automated rendering tests currently focus on small
|
||||
compositions, so please report visual discrepancies or unexpected behaviour.
|
||||
- **macOS is the most tested platform.** Linux and Windows users have hit basic interaction
|
||||
bugs, which we're fixing first.
|
||||
- **The trained models are new.** Roto Brush can use MobileSAM (an optional 40 MB download in
|
||||
Settings ▸ Roto Brush) and face tracking can use Google's MediaPipe Face Landmarker (an optional
|
||||
3.8 MB download in Settings ▸ Face Tracking). Both are actively undergoing evaluation and tuning.
|
||||
|
||||
EffectCraft is in **alpha**: about 91% of the features tracked in
|
||||
[docs/target-app-parity.md](docs/target-app-parity.md) exist, and we estimate it is about 45% of the
|
||||
way to being ready for full professional production work. The [ROADMAP](ROADMAP.md) has the plan,
|
||||
[docs/target-app-parity.md](docs/target-app-parity.md) the measurement and [docs/gaps.md](docs/gaps.md)
|
||||
every known shortfall. The web build ([docs/web.md](docs/web.md)) runs the full app in the browser.
|
||||
[Bug reports](https://github.com/storytold/effectcraft/issues) are the most useful thing you can
|
||||
send us right now.
|
||||
|
||||
## How it's made
|
||||
|
||||
- **Engine first.** A stack of small crates with strict layering. The interface (egui) sits on
|
||||
top and nothing underneath depends on it.
|
||||
- **Clean room.** We work from public documentation and observable behavior, never
|
||||
from proprietary files, icons, presets or code. No code is copied from GPL projects. The full rules
|
||||
are in [AGENTS.md](AGENTS.md).
|
||||
- **Private by default.** No telemetry, and no network access unless you ask for it.
|
||||
- **One family.** EffectCraft shares its time model and text engine design with FilmCraft, and
|
||||
gets its video and audio codecs from it.
|
||||
|
||||
## Downloads
|
||||
|
||||
**New to EffectCraft?** Download it from the [EffectCraft page on getartcraft.com](https://getartcraft.com/apps/effectcraft). That's the easiest way to install it.
|
||||
|
||||
**Want a specific build or format?** On GitHub, the [latest release](https://github.com/storytold/effectcraft/releases/latest) has every build listed below, and [all releases](https://github.com/storytold/effectcraft/releases) has earlier versions and their notes. `<ver>` in the file names is the version number, and `SHA256SUMS.txt` lists a checksum for every file.
|
||||
|
||||
### Windows
|
||||
|
||||
| Build | Installer | Portable |
|
||||
|---|---|---|
|
||||
| x64 (64-bit Intel/AMD) | `effectcraft-<ver>-windows-x64.msi` | `effectcraft-<ver>-windows-x64-portable.zip` |
|
||||
| arm64 (Snapdragon and other ARM PCs) | `effectcraft-<ver>-windows-arm64.msi` | `effectcraft-<ver>-windows-arm64-portable.zip` |
|
||||
| x86 (32-bit) | `effectcraft-<ver>-windows-x86.msi` | `effectcraft-<ver>-windows-x86-portable.zip` |
|
||||
|
||||
Installers and executables are code-signed. The installer asks who to install EffectCraft for and where. By default it installs for your
|
||||
account only, into `%LOCALAPPDATA%\Programs\ArtCraft\EffectCraft`, without an administrator prompt;
|
||||
"Everyone on this computer" installs into `C:\Program Files\ArtCraft\EffectCraft` (`Program Files (x86)` for the 32-bit build). Later versions
|
||||
install into the same folder. To install silently
|
||||
into a folder of your choice: `msiexec /i effectcraft-<ver>-windows-x64.msi /qn INSTALLFOLDER="D:\Apps\EffectCraft"`.
|
||||
A silent install is per-user by default. `INSTALLFOLDER` chooses the folder, not who it is installed for;
|
||||
add `ALLUSERS=1`, from an elevated terminal or deployment tool, for an all-users install.
|
||||
|
||||
### macOS
|
||||
|
||||
| Build | File | Notes |
|
||||
|---|---|---|
|
||||
| App, universal (Apple silicon + Intel) | `effectcraft-<ver>-macos-universal.dmg` | Signed and notarized |
|
||||
| Command-line tool, universal | `effectcraft-cli-<ver>-macos-universal.zip` | Signed and notarized |
|
||||
|
||||
### Linux
|
||||
|
||||
| Format | x86_64 | aarch64 (ARM64) | Notes |
|
||||
|---|---|---|---|
|
||||
| AppImage | `effectcraft-<ver>-linux-x86_64.AppImage` | `effectcraft-<ver>-linux-aarch64.AppImage` | Runs anywhere; updates itself with [AppImageUpdate](https://github.com/AppImageCommunity/AppImageUpdate) (`.zsync` files) |
|
||||
| Flatpak | `effectcraft-<ver>-linux-x86_64.flatpak` | `effectcraft-<ver>-linux-aarch64.flatpak` | Sandboxed; `flatpak install --user <file>` |
|
||||
| Debian/Ubuntu | `effectcraft-<ver>-linux-x86_64.deb` | `effectcraft-<ver>-linux-aarch64.deb` | |
|
||||
| Fedora/RHEL/openSUSE | `effectcraft-<ver>-linux-x86_64.rpm` | `effectcraft-<ver>-linux-aarch64.rpm` | |
|
||||
| Tarball | `effectcraft-<ver>-linux-x86_64.tar.gz` | `effectcraft-<ver>-linux-aarch64.tar.gz` | Unpack anywhere |
|
||||
|
||||
RISC-V 64-bit: `effectcraft-<ver>-linux-riscv64.tar.gz` (tarball only; needs glibc 2.39+, e.g. Ubuntu 24.04+ or Debian 13+).
|
||||
|
||||
**File drag-and-drop on Linux:** The current eframe/winit 0.30 native Wayland backend does not
|
||||
deliver file-hover or file-drop events. On Hyprland and other native Wayland sessions, use
|
||||
**File ▸ Import ▸ File…** (which accepts multiple files), or start EffectCraft via XWayland if
|
||||
XWayland is installed and `DISPLAY` is set:
|
||||
|
||||
```sh
|
||||
env -u WAYLAND_DISPLAY effectcraft
|
||||
```
|
||||
|
||||
When the backend delivers events (X11/XWayland, Windows and macOS), dropping media anywhere
|
||||
in the window imports it, and a drop-target hint appears while hovering. On native Wayland the
|
||||
app cannot detect an attempt that generates no event; it shows a startup notice with the
|
||||
File ▸ Import fallback instead.
|
||||
|
||||
### FreeBSD
|
||||
|
||||
| Build | File |
|
||||
|---|---|
|
||||
| x86_64 | `effectcraft-<ver>-freebsd-x86_64.tar.gz` |
|
||||
|
||||
### Web (WebAssembly)
|
||||
|
||||
| Build | File | Notes |
|
||||
|---|---|---|
|
||||
| Static site | `effectcraft-web-<ver>.zip` | Runs in a modern browser; host it on any static server |
|
||||
|
||||
## The Crafting Apps
|
||||
|
||||
EffectCraft is one of the **Crafting Apps**: free, open-source creative tools from the
|
||||
[ArtCraft](https://getartcraft.com/) team, each written from scratch in Rust and each able to
|
||||
stand on its own.
|
||||
|
||||
| | App | What it's for | Code | Learn more |
|
||||
|:-:|---|---|---|---|
|
||||
| <img src="https://raw.githubusercontent.com/storytold/photocraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.photocraft.png" alt="" width="32" height="32"> | **PhotoCraft** | Image editing: layers, masks, type and real PSD files | [GitHub](https://github.com/storytold/photocraft) | [Website](https://getartcraft.com/apps/photocraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/vectorcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.vectorcraft.png" alt="" width="32" height="32"> | **VectorCraft** | Vector illustration | [GitHub](https://github.com/storytold/vectorcraft) | [Website](https://getartcraft.com/apps/vectorcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/filmcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.filmcraft.png" alt="" width="32" height="32"> | **FilmCraft** | Video editing, color and sound | [GitHub](https://github.com/storytold/filmcraft) | [Website](https://getartcraft.com/apps/filmcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/lightcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.lightcraft.png" alt="" width="32" height="32"> | **LightCraft** | Photo library and raw development | [GitHub](https://github.com/storytold/lightcraft) | [Website](https://getartcraft.com/apps/lightcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/pdfcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.pdfcraft.png" alt="" width="32" height="32"> | **PdfCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/pdfcraft) | [Website](https://getartcraft.com/apps/pdfcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/effectcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png" alt="" width="32" height="32"> | **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) | [Website](https://getartcraft.com/apps/effectcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/designcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.designcraft.png" alt="" width="32" height="32"> | **DesignCraft** | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) | [Website](https://getartcraft.com/apps/designcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/figcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.figcraft.png" alt="" width="32" height="32"> | **FigCraft** | Interface design and prototyping | [GitHub](https://github.com/storytold/figcraft) | [Website](https://getartcraft.com/apps/figcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/wordcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.wordcraft.png" alt="" width="32" height="32"> | **WordCraft** | Word processing and documents | [GitHub](https://github.com/storytold/wordcraft) | [Website](https://getartcraft.com/apps/wordcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/soundcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.soundcraft.png" alt="" width="32" height="32"> | **SoundCraft** | Audio recording and editing | [GitHub](https://github.com/storytold/soundcraft) | [Website](https://getartcraft.com/apps/soundcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/deckcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.deckcraft.png" alt="" width="32" height="32"> | **DeckCraft** | Presentations and slide decks | [GitHub](https://github.com/storytold/deckcraft) | [Website](https://getartcraft.com/apps/deckcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/cadcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.cadcraft.png" alt="" width="32" height="32"> | **CADCraft** | Computer-aided design | [GitHub](https://github.com/storytold/cadcraft) | [Website](https://getartcraft.com/apps/cadcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/gridcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.gridcraft.png" alt="" width="32" height="32"> | **GridCraft** | Spreadsheets and data | [GitHub](https://github.com/storytold/gridcraft) | [Website](https://getartcraft.com/apps/gridcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/texturecraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.texturecraft.png" alt="" width="32" height="32"> | **TextureCraft** | 3D texture painting | [GitHub](https://github.com/storytold/texturecraft) | [Website](https://getartcraft.com/apps/texturecraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/craft-anything/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.craft-anything.png" alt="" width="32" height="32"> | **Craft Anything** | See how any software works, then build your own | [GitHub](https://github.com/storytold/craft-anything) | [Website](https://getartcraft.com/apps/craft-anything) |
|
||||
| <img src="https://raw.githubusercontent.com/tangency-labs/solidcraft/main/assets/icons/app/linux/exports/hicolor/64x64/apps/io.tangencylabs.solidcraft.png" alt="" width="32" height="32"> | **SolidCraft** | Parametric 3D CAD by Tangency Labs (community app) | [GitHub](https://github.com/tangency-labs/solidcraft) | |
|
||||
|
||||
And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video studio for artists who want real control.
|
||||
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/artcraft"><img alt="Join the ArtCraft community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
|
||||
</p>
|
||||
|
||||
<h3 align="center">Come make things with us</h3>
|
||||
|
||||
<p align="center">
|
||||
Our Discord is where artists of every kind hang out: people who paint, shoot, draw, cut film,
|
||||
set type, and people still figuring out what they like to make. Share what you're working on,
|
||||
ask for help, tell us what's broken, or tell us what you wish these tools could do.
|
||||
Whatever your medium and however long you've been at it, you're welcome here.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/artcraft"><b>discord.gg/artcraft</b></a> ·
|
||||
<a href="https://getartcraft.com/">getartcraft.com</a> ·
|
||||
<a href="https://getartcraft.com/apps">The Crafting Apps</a> ·
|
||||
<a href="https://getartcraft.com/apps/effectcraft">EffectCraft</a>
|
||||
</p>
|
||||
|
||||
## License and credits
|
||||
|
||||
EffectCraft is dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option.
|
||||
Copyright (c) 2026 ArtCraft Team and the EffectCraft contributors. Required notices are in [NOTICE](NOTICE).
|
||||
|
||||
Bundled fonts, icons, images and other assets keep their own open licenses; each one is listed
|
||||
with its author, source and license in [ATTRIBUTION.md](ATTRIBUTION.md).
|
||||
|
||||
The ArtCraft name, wordmark and logos in [`docs/brand/`](docs/brand/) are trademarks of the
|
||||
ArtCraft Team and are not covered by this license. They may be used only unmodified, and only as
|
||||
part of this repository and EffectCraft, under [`docs/brand/LICENSE-brand.txt`](docs/brand/LICENSE-brand.txt).
|
||||
Forks and modified versions must remove them.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://getartcraft.com/"><img alt="ArtCraft" src="docs/brand/artcraft-mark.svg" width="28"></a><br>
|
||||
<sub>Made by the <a href="https://getartcraft.com/">ArtCraft</a> team and community.</sub>
|
||||
</p>
|
||||
|
||||
## Star history
|
||||
|
||||
[](https://www.star-history.com/?repos=storytold%2Feffectcraft&type=date&legend=top-left)
|
||||
<p align="center">
|
||||
<a href="https://getartcraft.com/">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="docs/brand/artcraft-logo-white.svg">
|
||||
<img alt="ArtCraft" src="docs/brand/artcraft-logo.svg" width="200">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/app-icon/hicolor/256x256/apps/ai.storyteller.effectcraft.png" width="128" height="128" alt="EffectCraft icon: an engraved unicorn on a pink tile">
|
||||
</p>
|
||||
|
||||
<h1 align="center">EffectCraft</h1>
|
||||
|
||||
<p align="center">
|
||||
<b>Motion graphics and visual effects; an open-source, professional motion graphics and visual effects studio, built in pure Rust.</b>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
A free, open-source compositor: compositions, layers,
|
||||
keyframes, 308 effects, layer styles, expressions, 3D cameras and lights, and a render queue, native on macOS,
|
||||
Windows and Linux, and in the browser. Young, moving fast, and already usable.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img alt="Status: young and moving fast" src="https://img.shields.io/badge/status-young%20and%20moving%20fast-e0368f?style=flat-square">
|
||||
<img alt="Written in Rust" src="https://img.shields.io/badge/rust-1.95%2B-b0206c?style=flat-square&logo=rust&logoColor=white">
|
||||
<img alt="License: MIT OR Apache-2.0" src="https://img.shields.io/badge/license-MIT%20%2F%20Apache--2.0-555?style=flat-square">
|
||||
<br>
|
||||
<a href="https://sonarcloud.io/project/overview?id=storytold_effectcraft"><img alt="Scanned by SonarCloud" src="https://img.shields.io/badge/Scanned_by-SonarCloud-4b9fd5?style=flat-square&logo=sonarqubecloud&logoColor=white"></a>
|
||||
<a href="https://coderabbit.ai"><img alt="Reviewed by CodeRabbit" src="https://img.shields.io/coderabbit/prs/github/storytold/effectcraft?style=flat-square&label=Reviewed%20by%20CodeRabbit&logo=coderabbit"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/artcraft"><img alt="Join the ArtCraft community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://getartcraft.com/apps/effectcraft"><b>EffectCraft on getartcraft.com</b></a> ·
|
||||
<a href="https://getartcraft.com/">ArtCraft</a> ·
|
||||
<a href="https://getartcraft.com/apps">All Crafting Apps</a>
|
||||
</p>
|
||||
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/images/effectcraft-hero.png" alt="EffectCraft's main window: the animated demo composition in the Composition panel, the Project panel, a Timeline with text, shape and solid layers, and the Properties panel showing the selected text layer's transform, font and paragraph settings" width="100%">
|
||||
</p>
|
||||
|
||||
> [!NOTE]
|
||||
> **ArtCraft is a community of artists from all walks of life.** Digital, generative, music,
|
||||
> games — if you make things, you're one of us. **[Come say hi on Discord](https://discord.gg/artcraft).**
|
||||
|
||||
<p align="center">
|
||||
<a href="#what-effectcraft-is">What it is</a> ·
|
||||
<a href="#animate">Animate</a> ·
|
||||
<a href="#effects">Effects</a> ·
|
||||
<a href="#3d">3D</a> ·
|
||||
<a href="#export">Export</a> ·
|
||||
<a href="#built-for-agents">Agents</a> ·
|
||||
<a href="#get-started">Get started</a> ·
|
||||
<a href="#where-it-stands">Status</a> ·
|
||||
<a href="#how-its-made">How it's made</a> ·
|
||||
<a href="#downloads">Downloads</a> ·
|
||||
<a href="#the-crafting-apps">The Crafting Apps</a> ·
|
||||
<a href="#license-and-credits">License</a>
|
||||
</p>
|
||||
|
||||
## What EffectCraft is
|
||||
|
||||
EffectCraft is for animated titles, motion graphics and compositing work: the kind of thing
|
||||
motion designers and VFX artists need. You build a composition out of layers (solids, shapes,
|
||||
text, footage, other compositions), animate their properties with keyframes, stack effects on
|
||||
them and render the result.
|
||||
|
||||
The interface feels natural and responsive, with dedicated panels (Project, Composition, Timeline,
|
||||
Layer, Footage, Effect Controls, Tools, Preview, Effects & Presets, Audio, Info, Paragraph,
|
||||
Character, Align, Progress, Render Queue) and standard motion graphics shortcuts, going further in
|
||||
a few places where it matters to us:
|
||||
|
||||
- **Lottie import and export built in**, so animations can go straight to the web and apps
|
||||
without a plugin.
|
||||
- **A project file you can read.** Projects are versioned JSON (`.ecproj`), so they diff cleanly
|
||||
in version control.
|
||||
- **Everything is scriptable.** Menu actions, timeline edits and property changes are exposed as
|
||||
commands. Engine commands are available through the CLI, JSON control channel and MCP;
|
||||
UI-only commands use the frontend dispatcher and can be invoked through the control channel
|
||||
or an attached MCP bridge.
|
||||
- **No FFmpeg.** Video and audio decoding and encoding are pure Rust: FilmCraft's codecs and
|
||||
EffectCraft's own VP9, AV1, HEVC and Opus encoders.
|
||||
|
||||
## Animate
|
||||
|
||||
The panels, menus and shortcuts follow standard motion design conventions:
|
||||
Project, Composition, Timeline, Effect Controls, Properties, Effects & Presets, Character,
|
||||
Paragraph, Align, Info, Preview, Audio and the Render Queue, docked the way you expect. Drag
|
||||
footage, comps and effects between them: onto the comp viewer, where they land under the pointer,
|
||||
or into the Timeline, between layers and at the time you point to.
|
||||
|
||||
- **Layers of every kind:** solids, shapes, text, footage, nested compositions, nulls,
|
||||
adjustment layers, cameras and lights; parenting, track mattes, all 38 blend modes, motion blur.
|
||||
- **Keyframes that behave the same:** linear, Bezier, hold, auto and continuous Bezier, roving
|
||||
keys, Easy Ease (F9), Keyframe Velocity and Interpolation dialogs, copy and paste at the current
|
||||
time, and a **Graph Editor** with value and speed graphs and draggable handles.
|
||||
- **Time:** time remapping, time stretch, time-reverse, freeze frame, work area, markers, exact
|
||||
frame-accurate timing at every frame rate including 29.97 drop-frame.
|
||||
- **Shapes and masks:** shape layers with trim paths, repeaters, round corners, offset, zig zag,
|
||||
twist, wiggle, merge paths and gradient strokes; masks drawn with the pen tool, with modes,
|
||||
feather, expansion and vertex editing in the viewer.
|
||||
- **Text:** point and paragraph text with real shaping in any font installed on your machine, the
|
||||
Character and Paragraph panels, and text animators with range selectors.
|
||||
- **Expressions:** JavaScript with the standard motion expressions model (`wiggle`, `loopOut`,
|
||||
`thisComp.layer("…")`, vector maths on arrays), an inline editor and the pick-whip.
|
||||
- **Timeline like you know it:** twirl layers open to Transform, masks, effects and the rest; drag
|
||||
rows to reorder layers; rename with Enter or a double-click; the property shortcuts (A, P, S,
|
||||
R, T, M, F, E, L, U and their double presses AA, PP, SS, RR, TT, MM, FF, EE, LL, UU), Shift to
|
||||
add properties, Alt+Shift+A/P/S/R/T to key the current time, Ctrl+` to twirl selected layers.
|
||||
- **Puppet tools:** Position, Advanced, Bend, Starch and Overlap pins on a mesh built from the
|
||||
layer, real-time pin recording, rotate/scale handles, pins driven by (or carrying) nulls for
|
||||
rigging, and follow-through for hair and cloth.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/images/effectcraft-graph-editor.png" alt="The Graph Editor showing an eased value curve for a text animator's Range Start, with the graph editor's button bar for hold, linear, auto Bezier and Easy Ease" width="100%">
|
||||
</p>
|
||||
|
||||
## Effects
|
||||
|
||||
308 built-in effects across its categories, each with its parameter names, order and
|
||||
defaults: blur and sharpen, channel, color correction (Curves, Levels, Hue/Saturation, a full
|
||||
color-grading panel…), distort (Warp, Bulge, Turbulent Displace, Perspective Pin…), generate (Fractal Noise,
|
||||
Gradient Ramp, Stroke, Write-on, Audio Spectrum…), keying, matte, noise and grain, perspective,
|
||||
**simulation** (Particle Emitter 3D, Rainfall, Shatter, Card Dance, Caustics, Wave World…),
|
||||
stylize (Glow, Glass Relief…), **time** (Echo, Posterize Time, Timewarp, Time Displacement…),
|
||||
**audio** (Reverb, Parametric EQ, Delay, Stereo Mixer…), text, transitions, utility and expression
|
||||
controls. All nine **Layer Styles** (Drop Shadow, Inner/Outer Glow, Bevel and Emboss, Satin,
|
||||
overlays, Stroke) with Global Light. Preview plays audio in sync, with meters and waveforms.
|
||||
|
||||
## 3D
|
||||
|
||||
Classic 3D compositing: 3D layers with orientation and material options,
|
||||
one- and two-node **cameras** with depth of field, **lights** (parallel, spot, point, ambient)
|
||||
with soft ray-traced shadows, layers that intersect correctly, orbit, pan and dolly camera
|
||||
tools, and Front, Top, Left and Custom views.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/images/effectcraft-3d.png" alt="The 3D Showcase demo in Custom View 1: intersecting cards lit by a spot light with soft shadows on a gridded floor, with the camera frustum and light drawn as wireframes" width="100%">
|
||||
</p>
|
||||
|
||||
## Export
|
||||
|
||||
A dedicated Render Queue, with Render Settings and Output Modules: **H.264** MP4 and
|
||||
**ProRes** MOV (Proxy to 4444 XQ with alpha) with audio, **HEVC** (Main / Main 10) and **AV1**
|
||||
MP4, **WebM** (VP9 with inter frames and alpha, or AV1; Opus audio), PNG, JPEG, TIFF, Targa and 32-bit EXR sequences, animated GIF and WAV/AIFF. Field
|
||||
rendering with 3:2 pulldown, effect/solo/guide/depth overrides, crop, region of interest and
|
||||
resize, Render Settings and Output Module templates with defaults, post-render actions, storage
|
||||
overflow and render logs. The same queue runs from the command line. Every encoder is pure
|
||||
Rust (craft-libs' H.264, ProRes, HEVC, VP9, AV1 and AAC; EffectCraft's own Opus); there is no
|
||||
FFmpeg inside.
|
||||
|
||||
**Lottie** goes both ways: File ▸ Export ▸ Lottie JSON… writes a composition (precomps, shape,
|
||||
solid, image, text and null layers, eased and spatial keyframes, masks, track mattes, blend
|
||||
modes, time remapping, optionally expressions) as `.json` or `.lottie`, and lists anything Lottie
|
||||
cannot express; File ▸ Import ▸ Lottie… opens one as a new composition.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/images/effectcraft-render-queue.png" alt="The Render Queue panel with two compositions queued" width="100%">
|
||||
</p>
|
||||
|
||||
## Built for agents
|
||||
|
||||
Engine commands are available through the CLI, JSON control channel and MCP. UI-only commands
|
||||
need the running app's frontend dispatcher: complete menu access requires the app's control
|
||||
channel or an attached MCP bridge. Headless MCP sessions expose supported engine commands,
|
||||
but not UI-only commands or bridge-only `ui_*` tools.
|
||||
|
||||
- **MCP server:** `effectcraft-cli mcp` speaks the Model Context Protocol over stdio, headless
|
||||
or bridged to the running app (`--bridge 9877`). Tools cover commands, the project and property
|
||||
tree, keyframes and rendered frames. This repository ships a ready [`.mcp.json`](.mcp.json).
|
||||
- **Command line:** one-shot calls with JSON output, for example
|
||||
`effectcraft-cli set Main '#1' transform/position '[100,360]' --time 0 main.ecproj --save`
|
||||
or `effectcraft-cli render --comp Main --out main.mp4`, or
|
||||
`effectcraft-cli exec file.exportLottie '{"comp":"Main","path":"main.json"}' main.ecproj`.
|
||||
- **Control channel:** `effectcraft --control 9877` accepts JSON lines to run commands, inspect
|
||||
and click any widget by its automation id, and take screenshots.
|
||||
|
||||
See [docs/agents.md](docs/agents.md) and [docs/control-protocol.md](docs/control-protocol.md).
|
||||
|
||||
## Get started
|
||||
|
||||
Installers for each version are on the [Releases](https://github.com/storytold/effectcraft/releases)
|
||||
page: a universal macOS app; Windows MSIs and portable zips for x64, x86 and ARM64; Linux
|
||||
AppImage, Flatpak, deb, rpm and tar.gz for x86_64 and aarch64, plus a riscv64 tar.gz; a FreeBSD x86_64 tarball; and the
|
||||
web build. [Downloads](#downloads) lists every file. The Windows ARM64 build runs natively on
|
||||
Windows on ARM, without x64 emulation. CI installs that MSI on Windows 11 ARM64 hardware, checks
|
||||
both programs are ARM64 and runs the command-line tool there, but it doesn't open the app's window
|
||||
or run the test suite natively on ARM64 yet, so please report anything that behaves differently.
|
||||
|
||||
On Gentoo, the community [::snakebyte overlay](https://github.com/switch87/snakebyte-overlay) packages
|
||||
the Linux release as `media-video/effectcraft-bin` (not maintained by the EffectCraft team):
|
||||
|
||||
```sh
|
||||
eselect repository add snakebyte git https://github.com/switch87/snakebyte-overlay.git
|
||||
emaint sync -r snakebyte
|
||||
echo 'media-video/effectcraft-bin ~amd64' >> /etc/portage/package.accept_keywords/effectcraft
|
||||
emerge --ask media-video/effectcraft-bin
|
||||
```
|
||||
|
||||
To build it yourself you need [Rust](https://rustup.rs/) 1.95 or newer.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/storytold/effectcraft
|
||||
cd effectcraft
|
||||
cargo run --release -p effectcraft # the app (add `-- --demo` to open the demo project)
|
||||
cargo run --release -p effectcraft-cli -- render --out intro.mp4 # render the demo headless
|
||||
cargo xtask web --serve 8765 # the browser build on http://127.0.0.1:8765/ (docs/web.md)
|
||||
```
|
||||
|
||||
New to it? [docs/footage.md](docs/footage.md) covers importing footage and image sequences and
|
||||
Interpret Footage.
|
||||
|
||||
To work on it: `cargo test --workspace`, and `cargo xtask ci` before every commit (format, lints,
|
||||
tests, layering, asset attribution and the WebAssembly build). See [CONTRIBUTING.md](CONTRIBUTING.md),
|
||||
[docs/architecture.md](docs/architecture.md) and [docs/testing.md](docs/testing.md).
|
||||
|
||||
## Where it stands
|
||||
|
||||
EffectCraft is young: its first commit was on 1 October 2026. A comprehensive feature set
|
||||
exists, including 308 effects, but breadth is not the same as full maturity across every edge
|
||||
case. Today it's a good place to try things and to tell us what breaks. It isn't yet recommended
|
||||
for production client work. In particular:
|
||||
|
||||
- **Native project format.** Projects are saved as `.ecproj`, readable versioned JSON. Proprietary
|
||||
project formats and After Effects SDK plug-ins are not supported.
|
||||
- **Fidelity is still being refined.** Automated rendering tests currently focus on small
|
||||
compositions, so please report visual discrepancies or unexpected behaviour.
|
||||
- **macOS is the most tested platform.** Linux and Windows users have hit basic interaction
|
||||
bugs, which we're fixing first.
|
||||
- **The trained models are new.** Roto Brush can use MobileSAM (an optional 40 MB download in
|
||||
Settings ▸ Roto Brush) and face tracking can use Google's MediaPipe Face Landmarker (an optional
|
||||
3.8 MB download in Settings ▸ Face Tracking). Both are actively undergoing evaluation and tuning.
|
||||
|
||||
EffectCraft is in **alpha**: about 91% of the features tracked in
|
||||
[docs/target-app-parity.md](docs/target-app-parity.md) exist, and we estimate it is about 45% of the
|
||||
way to being ready for full professional production work. The [ROADMAP](ROADMAP.md) has the plan,
|
||||
[docs/target-app-parity.md](docs/target-app-parity.md) the measurement and [docs/gaps.md](docs/gaps.md)
|
||||
every known shortfall. The web build ([docs/web.md](docs/web.md)) runs the full app in the browser.
|
||||
[Bug reports](https://github.com/storytold/effectcraft/issues) are the most useful thing you can
|
||||
send us right now.
|
||||
|
||||
## How it's made
|
||||
|
||||
- **Engine first.** A stack of small crates with strict layering. The interface (egui) sits on
|
||||
top and nothing underneath depends on it.
|
||||
- **Clean room.** We work from public documentation and observable behavior, never
|
||||
from proprietary files, icons, presets or code. No code is copied from GPL projects. The full rules
|
||||
are in [AGENTS.md](AGENTS.md).
|
||||
- **Private by default.** No telemetry, and no network access unless you ask for it.
|
||||
- **One family.** EffectCraft shares its time model and text engine design with FilmCraft, and
|
||||
gets its video and audio codecs from it.
|
||||
|
||||
## Downloads
|
||||
|
||||
**New to EffectCraft?** Download it from the [EffectCraft page on getartcraft.com](https://getartcraft.com/apps/effectcraft). That's the easiest way to install it.
|
||||
|
||||
**Want a specific build or format?** On GitHub, the [latest release](https://github.com/storytold/effectcraft/releases/latest) has every build listed below, and [all releases](https://github.com/storytold/effectcraft/releases) has earlier versions and their notes. `<ver>` in the file names is the version number, and `SHA256SUMS.txt` lists a checksum for every file.
|
||||
|
||||
### Windows
|
||||
|
||||
| Build | Installer | Portable |
|
||||
|---|---|---|
|
||||
| x64 (64-bit Intel/AMD) | `effectcraft-<ver>-windows-x64.msi` | `effectcraft-<ver>-windows-x64-portable.zip` |
|
||||
| arm64 (Snapdragon and other ARM PCs) | `effectcraft-<ver>-windows-arm64.msi` | `effectcraft-<ver>-windows-arm64-portable.zip` |
|
||||
| x86 (32-bit) | `effectcraft-<ver>-windows-x86.msi` | `effectcraft-<ver>-windows-x86-portable.zip` |
|
||||
|
||||
Installers and executables are code-signed. The installer asks who to install EffectCraft for and where. By default it installs for your
|
||||
account only, into `%LOCALAPPDATA%\Programs\ArtCraft\EffectCraft`, without an administrator prompt;
|
||||
"Everyone on this computer" installs into `C:\Program Files\ArtCraft\EffectCraft` (`Program Files (x86)` for the 32-bit build). Later versions
|
||||
install into the same folder. To install silently
|
||||
into a folder of your choice: `msiexec /i effectcraft-<ver>-windows-x64.msi /qn INSTALLFOLDER="D:\Apps\EffectCraft"`.
|
||||
A silent install is per-user by default. `INSTALLFOLDER` chooses the folder, not who it is installed for;
|
||||
add `ALLUSERS=1`, from an elevated terminal or deployment tool, for an all-users install.
|
||||
|
||||
### macOS
|
||||
|
||||
| Build | File | Notes |
|
||||
|---|---|---|
|
||||
| App, universal (Apple silicon + Intel) | `effectcraft-<ver>-macos-universal.dmg` | Signed and notarized |
|
||||
| Command-line tool, universal | `effectcraft-cli-<ver>-macos-universal.zip` | Signed and notarized |
|
||||
|
||||
### Linux
|
||||
|
||||
| Format | x86_64 | aarch64 (ARM64) | Notes |
|
||||
|---|---|---|---|
|
||||
| AppImage | `effectcraft-<ver>-linux-x86_64.AppImage` | `effectcraft-<ver>-linux-aarch64.AppImage` | Runs anywhere; updates itself with [AppImageUpdate](https://github.com/AppImageCommunity/AppImageUpdate) (`.zsync` files) |
|
||||
| Flatpak | `effectcraft-<ver>-linux-x86_64.flatpak` | `effectcraft-<ver>-linux-aarch64.flatpak` | Sandboxed; `flatpak install --user <file>` |
|
||||
| Debian/Ubuntu | `effectcraft-<ver>-linux-x86_64.deb` | `effectcraft-<ver>-linux-aarch64.deb` | |
|
||||
| Fedora/RHEL/openSUSE | `effectcraft-<ver>-linux-x86_64.rpm` | `effectcraft-<ver>-linux-aarch64.rpm` | |
|
||||
| Tarball | `effectcraft-<ver>-linux-x86_64.tar.gz` | `effectcraft-<ver>-linux-aarch64.tar.gz` | Unpack anywhere |
|
||||
|
||||
RISC-V 64-bit: `effectcraft-<ver>-linux-riscv64.tar.gz` (tarball only; needs glibc 2.39+, e.g. Ubuntu 24.04+ or Debian 13+).
|
||||
|
||||
**File drag-and-drop on Linux:** The current eframe/winit 0.30 native Wayland backend does not
|
||||
deliver file-hover or file-drop events. On Hyprland and other native Wayland sessions, use
|
||||
**File ▸ Import ▸ File…** (which accepts multiple files), or start EffectCraft via XWayland if
|
||||
XWayland is installed and `DISPLAY` is set:
|
||||
|
||||
```sh
|
||||
env -u WAYLAND_DISPLAY effectcraft
|
||||
```
|
||||
|
||||
When the backend delivers events (X11/XWayland, Windows and macOS), dropping media anywhere
|
||||
in the window imports it, and a drop-target hint appears while hovering. On native Wayland the
|
||||
app cannot detect an attempt that generates no event; it shows a startup notice with the
|
||||
File ▸ Import fallback instead.
|
||||
|
||||
### FreeBSD
|
||||
|
||||
| Build | File |
|
||||
|---|---|
|
||||
| x86_64 | `effectcraft-<ver>-freebsd-x86_64.tar.gz` |
|
||||
|
||||
### Web (WebAssembly)
|
||||
|
||||
| Build | File | Notes |
|
||||
|---|---|---|
|
||||
| Static site | `effectcraft-web-<ver>.zip` | Runs in a modern browser; host it on any static server |
|
||||
|
||||
## The Crafting Apps
|
||||
|
||||
EffectCraft is one of the **Crafting Apps**: free, open-source creative tools from the
|
||||
[ArtCraft](https://getartcraft.com/) team, each written from scratch in Rust and each able to
|
||||
stand on its own.
|
||||
|
||||
| | App | What it's for | Code | Learn more |
|
||||
|:-:|---|---|---|---|
|
||||
| <img src="https://raw.githubusercontent.com/storytold/photocraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.photocraft.png" alt="" width="32" height="32"> | **PhotoCraft** | Image editing: layers, masks, type and real PSD files | [GitHub](https://github.com/storytold/photocraft) | [Website](https://getartcraft.com/apps/photocraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/vectorcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.vectorcraft.png" alt="" width="32" height="32"> | **VectorCraft** | Vector illustration | [GitHub](https://github.com/storytold/vectorcraft) | [Website](https://getartcraft.com/apps/vectorcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/filmcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.filmcraft.png" alt="" width="32" height="32"> | **FilmCraft** | Video editing, color and sound | [GitHub](https://github.com/storytold/filmcraft) | [Website](https://getartcraft.com/apps/filmcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/lightcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.lightcraft.png" alt="" width="32" height="32"> | **LightCraft** | Photo library and raw development | [GitHub](https://github.com/storytold/lightcraft) | [Website](https://getartcraft.com/apps/lightcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/pdfcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.pdfcraft.png" alt="" width="32" height="32"> | **PdfCraft** | Reading, organizing and protecting PDFs | [GitHub](https://github.com/storytold/pdfcraft) | [Website](https://getartcraft.com/apps/pdfcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/effectcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.effectcraft.png" alt="" width="32" height="32"> | **EffectCraft** | Motion graphics and visual effects | [GitHub](https://github.com/storytold/effectcraft) | [Website](https://getartcraft.com/apps/effectcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/designcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.designcraft.png" alt="" width="32" height="32"> | **DesignCraft** | Page layout and publishing | [GitHub](https://github.com/storytold/designcraft) | [Website](https://getartcraft.com/apps/designcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/figcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.figcraft.png" alt="" width="32" height="32"> | **FigCraft** | Interface design and prototyping | [GitHub](https://github.com/storytold/figcraft) | [Website](https://getartcraft.com/apps/figcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/wordcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.wordcraft.png" alt="" width="32" height="32"> | **WordCraft** | Word processing and documents | [GitHub](https://github.com/storytold/wordcraft) | [Website](https://getartcraft.com/apps/wordcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/soundcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.soundcraft.png" alt="" width="32" height="32"> | **SoundCraft** | Audio recording and editing | [GitHub](https://github.com/storytold/soundcraft) | [Website](https://getartcraft.com/apps/soundcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/deckcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.deckcraft.png" alt="" width="32" height="32"> | **DeckCraft** | Presentations and slide decks | [GitHub](https://github.com/storytold/deckcraft) | [Website](https://getartcraft.com/apps/deckcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/cadcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.cadcraft.png" alt="" width="32" height="32"> | **CADCraft** | Computer-aided design | [GitHub](https://github.com/storytold/cadcraft) | [Website](https://getartcraft.com/apps/cadcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/gridcraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.gridcraft.png" alt="" width="32" height="32"> | **GridCraft** | Spreadsheets and data | [GitHub](https://github.com/storytold/gridcraft) | [Website](https://getartcraft.com/apps/gridcraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/texturecraft/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.texturecraft.png" alt="" width="32" height="32"> | **TextureCraft** | 3D texture painting | [GitHub](https://github.com/storytold/texturecraft) | [Website](https://getartcraft.com/apps/texturecraft) |
|
||||
| <img src="https://raw.githubusercontent.com/storytold/craft-anything/main/assets/app-icon/hicolor/64x64/apps/ai.storyteller.craft-anything.png" alt="" width="32" height="32"> | **Craft Anything** | See how any software works, then build your own | [GitHub](https://github.com/storytold/craft-anything) | [Website](https://getartcraft.com/apps/craft-anything) |
|
||||
| <img src="https://raw.githubusercontent.com/tangency-labs/solidcraft/main/assets/icons/app/linux/exports/hicolor/64x64/apps/io.tangencylabs.solidcraft.png" alt="" width="32" height="32"> | **SolidCraft** | Parametric 3D CAD by Tangency Labs (community app) | [GitHub](https://github.com/tangency-labs/solidcraft) | |
|
||||
|
||||
And [**ArtCraft**](https://getartcraft.com/) itself, our AI image and video studio for artists who want real control.
|
||||
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/artcraft"><img alt="Join the ArtCraft community on Discord" src="https://img.shields.io/badge/Join%20us%20on%20Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white" height="40"></a>
|
||||
</p>
|
||||
|
||||
<h3 align="center">Come make things with us</h3>
|
||||
|
||||
<p align="center">
|
||||
Our Discord is where artists of every kind hang out: people who paint, shoot, draw, cut film,
|
||||
set type, and people still figuring out what they like to make. Share what you're working on,
|
||||
ask for help, tell us what's broken, or tell us what you wish these tools could do.
|
||||
Whatever your medium and however long you've been at it, you're welcome here.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://discord.gg/artcraft"><b>discord.gg/artcraft</b></a> ·
|
||||
<a href="https://getartcraft.com/">getartcraft.com</a> ·
|
||||
<a href="https://getartcraft.com/apps">The Crafting Apps</a> ·
|
||||
<a href="https://getartcraft.com/apps/effectcraft">EffectCraft</a>
|
||||
</p>
|
||||
|
||||
## License and credits
|
||||
|
||||
EffectCraft is dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APACHE), at your option.
|
||||
Copyright (c) 2026 ArtCraft Team and the EffectCraft contributors. Required notices are in [NOTICE](NOTICE).
|
||||
|
||||
Bundled fonts, icons, images and other assets keep their own open licenses; each one is listed
|
||||
with its author, source and license in [ATTRIBUTION.md](ATTRIBUTION.md).
|
||||
|
||||
The ArtCraft name, wordmark and logos in [`docs/brand/`](docs/brand/) are trademarks of the
|
||||
ArtCraft Team and are not covered by this license. They may be used only unmodified, and only as
|
||||
part of this repository and EffectCraft, under [`docs/brand/LICENSE-brand.txt`](docs/brand/LICENSE-brand.txt).
|
||||
Forks and modified versions must remove them.
|
||||
|
||||
<p align="center">
|
||||
<a href="https://getartcraft.com/"><img alt="ArtCraft" src="docs/brand/artcraft-mark.svg" width="28"></a><br>
|
||||
<sub>Made by the <a href="https://getartcraft.com/">ArtCraft</a> team and community.</sub>
|
||||
</p>
|
||||
|
||||
## Star history
|
||||
|
||||
[](https://www.star-history.com/?repos=storytold%2Feffectcraft&type=date&legend=top-left)
|
||||
+193
-193
@@ -1,193 +1,193 @@
|
||||
# EffectCraft Roadmap
|
||||
|
||||
**Stage: alpha** · next: beta, ~30 points of "ready for real work" and ~350–620 h away
|
||||
|
||||
> **Last reviewed:** 2026-10-22 · **Last updated:** 2026-11-05 · **Change:** minor (ProRes alpha final-run conformance; previously: features of 30 October in the progress log: text Line Join, Timeline drag-reorder and multi-select of effects, viewer edge pan, ScriptUI drag capture and dock size; previously: localization recount after #1774) · **Target:** Adobe After Effects 2026 (26.5.0.89)
|
||||
|
||||
EffectCraft aims to do what After Effects does, with the same panels, menus and behaviour, written
|
||||
from scratch in Rust. This page is the one-page summary; the evidence is in
|
||||
[docs/target-app-parity.md](docs/target-app-parity.md), the work list in
|
||||
[docs/gaps.md](docs/gaps.md), and the plan in [docs/roadmap.md](docs/roadmap.md).
|
||||
|
||||
## Headline numbers
|
||||
|
||||
| | Value | Kind |
|
||||
|---|---|---|
|
||||
| **Feature breadth** | **≈ 91%** | Estimated, weighted by area; the self-graded 92-item checklist says ≈ 99% |
|
||||
| **Ready for real work** | **≈ 45%** (40–50%) | Estimated; one live fidelity measurement so far |
|
||||
| **Mainstream practitioner** | **≈ 47%** | Estimated: the typical motion designer's weekly work ([how](docs/target-app-parity.md#mainstream-practitioner-and-essentials-user)) |
|
||||
| **Essentials user** | **≈ 52%** | Estimated: core features only ([how](docs/target-app-parity.md#essentials-user)) |
|
||||
| Remaining to **beta** | **≈ 350–620 Opus 5.5 agent-hours** | Estimated |
|
||||
| Remaining to **full parity** | **≈ 850–1,500 Opus 5.5 agent-hours** | Estimated; ≈ 70% parallelises |
|
||||
|
||||
| Audience | Ready % | Hours to ~95% | Work that dominates |
|
||||
|---|---:|---:|---|
|
||||
| Full target | ≈ 45% | ≈ 850–1,500 h | `.aep`, fidelity corpus, bug backlog, performance, formats, hardware, localization |
|
||||
| Mainstream practitioner | ≈ 47% | ≈ 280–530 h | `.aep` exchange, stability, everyday fidelity and bugs, preview speed |
|
||||
| Essentials user | ≈ 52% | ≈ 150–300 h | Opening tutorial `.aep` files, start-up stability, core bugs, preview with sound |
|
||||
|
||||
About 60–70% of these hours parallelise ([detail](docs/target-app-parity.md#readiness-by-audience)).
|
||||
|
||||
Hours are one Opus 5.5 agent working sequentially, calibrated on this repository's history
|
||||
([how](docs/target-app-parity.md#calibration-of-the-hours)).
|
||||
|
||||
### Stage
|
||||
|
||||
**Alpha**, because all six core workflows of the [alpha gate](docs/roadmap.md#alpha-gate)
|
||||
(set up, animate, text and shapes, composite, preview, save and deliver) work end to end on macOS
|
||||
and the work saves and reopens, and ready for real work (≈ 45%) is above the ≈ 40% bar. But: After Effects projects (`.aep`) can't be opened, which
|
||||
alone rules out beta; fidelity against After Effects is measured for one narrow case (scalar ease,
|
||||
168 samples); 216 issues are open on 12 October, six of them crash reports (#580, #501, #426, #341, #234, #879). Beta needs ≈ 75% ready and `.aep`
|
||||
opening reliably: the `.aep` owner decision and importer (80–160 h), the fidelity harness and
|
||||
P0/P1 corpus (60–100 h), the crash and bug backlog (40–80 h), performance (40–70 h),
|
||||
Linux/Windows reliability (30–50 h) and depth in animation, masks, effects and text (100–160 h).
|
||||
|
||||
## By dimension
|
||||
|
||||
| Dimension | % | Hours | Doc |
|
||||
|---|---:|---:|---|
|
||||
| Features: breadth | 91% | — | [target-app-parity.md](docs/target-app-parity.md#by-feature-area) |
|
||||
| Features: ready for real work | 50% | 360–660 | [target-app-parity.md](docs/target-app-parity.md#by-feature-area) |
|
||||
| UI/UX fidelity | 65% | 60–120 | [ui-parity.md](docs/ui-parity.md) |
|
||||
| File formats | 50% | 130–230 | [file-format-parity.md](docs/file-format-parity.md) |
|
||||
| Hardware | 40% | 60–120 | [hardware-parity.md](docs/hardware-parity.md) |
|
||||
| Localization | 24% | 60–110 + review | [localization-parity.md](docs/localization-parity.md) |
|
||||
| Performance | 35% | 50–100 | [gaps.md](docs/gaps.md#g5-performance-at-real-world-scale) |
|
||||
| Stability | 45% | 40–80 | [gaps.md](docs/gaps.md#g3-stability-and-a-green-main) |
|
||||
| Platforms | 65% | 30–60 | [gaps.md](docs/gaps.md#g2-real-user-reliability-on-every-platform) |
|
||||
| Ecosystem and plug-ins | 20% | 40–80 | [plugins.md](docs/plugins.md), [scripting-parity.md](docs/scripting-parity.md) |
|
||||
| AI features | 35% | 40–80 | [gaps.md](docs/gaps.md) |
|
||||
| Agent control (beyond After Effects) | ahead | — | [agents.md](docs/agents.md) |
|
||||
|
||||
## Features
|
||||
|
||||
| Area | Breadth | Ready | Hours |
|
||||
|---|---:|---:|---:|
|
||||
| Project, footage and import | 92% | 55% | 25–45 |
|
||||
| Compositions, layers and compositing | 98% | 60% | 30–55 |
|
||||
| Animation: keyframes, graph editor, motion paths, puppet | 97% | 55% | 35–60 |
|
||||
| Expressions and scripting ([detail](docs/scripting-parity.md)) | 93% | 65% | 20–40 |
|
||||
| Shapes, masks and roto | 93% | 45% | 30–50 |
|
||||
| Text and typography | 94% | 50% | 20–35 |
|
||||
| Effects and animation presets ([detail](docs/effects-parity.md)) | 85% | 40% | 70–130 |
|
||||
| 3D (Classic and Advanced) | 82% | 35% | 35–65 |
|
||||
| Tracking and AI-assisted tools | 80% | 35% | 35–70 |
|
||||
| Preview and cache | 92% | 40% | 25–50 |
|
||||
| Render queue and export | 85% | 55% | 25–45 |
|
||||
| Audio | 85% | 45% | 6–12 |
|
||||
|
||||
Weights and evidence per row: [target-app-parity.md](docs/target-app-parity.md#by-feature-area).
|
||||
|
||||
## Languages
|
||||
|
||||
UI strings translated out of ≈ 4,200 (detail: [localization-parity.md](docs/localization-parity.md)).
|
||||
|
||||
| Language | Status | % |
|
||||
|---|---|---:|
|
||||
| English | full | 100% |
|
||||
| Simplified Chinese | partial (menus, most of the panels) | 46% |
|
||||
| Spanish | menus only | 29% |
|
||||
| Hindi | none | 0% |
|
||||
| Arabic | none | 0% |
|
||||
| French | menus only | 16% |
|
||||
| Portuguese (Brazil) | partial (menus, part of the panels) | 34% |
|
||||
| Indonesian | none | 0% |
|
||||
| Japanese | partial (menus, most of the panels) | 46% |
|
||||
| German | menus only | 16% |
|
||||
| Korean | none | 0% |
|
||||
| Vietnamese | none | 0% |
|
||||
|
||||
Also shipped: Traditional Chinese (partial, 38%), Ukrainian (partial, 29%), Czech (partial, 29%), Polish (partial, 34%), Georgian (partial, 39%; native-speaker reviewed), Russian (menus only, 15%) and Swedish (menus, panels and effect names; not yet measured). No other language has had
|
||||
native-speaker review.
|
||||
|
||||
## Upcoming
|
||||
|
||||
Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gaps.md).
|
||||
|
||||
| | Next | Hours |
|
||||
|---|---|---:|
|
||||
| 1 | Fix the open crash reports; run `cargo xtask ci` on every pull request (G3) | 15–30 |
|
||||
| 2 | Fidelity harness against After Effects: rendered frames, a corpus case per P0/P1 feature (G1) | 60–100 |
|
||||
| 3 | Real-user reliability on Linux and Windows; the open mask, effect, text and 3D bugs (G2) | 100–190 |
|
||||
| 4 | `.aep` / `.aepx` import, after the owner's clean-room decision (G4) | 80–160 |
|
||||
| 5 | Performance: benchmarks at 1080p / 4K, real-time cached playback, hardware video (G5) | 50–100 |
|
||||
| 6 | Localization depth: effect names, all panels, Korean / Italian (G9) | 60–110 |
|
||||
|
||||
Come tell us what matters most to you on [Discord](https://discord.gg/artcraft).
|
||||
|
||||
## Progress log
|
||||
|
||||
- 2026-11-05: ProRes 4444 and XQ exports write the required run after the final alpha
|
||||
difference. The FilmCraft encoder pin fixes alpha movies rejected by native macOS
|
||||
decoding; an export regression checks the stored bytes for both profiles, alpha modes,
|
||||
and motion-blur settings (#293). Resolve itself still needs confirmation.
|
||||
- 2026-10-30: Text strokes take a Line Join (Miter, Round or Bevel) in the Character panel,
|
||||
Miter for new text as in After Effects (#2077). In the Timeline, effects, masks and shape items
|
||||
drag to a new place in their stack (#2210) and Shift-click adds them to the selection, so one
|
||||
animation preset can hold several (#2552). The Composition panel pans while a mask vertex is
|
||||
held at its edge (#999). Effect buffers that pad a small layer stay within a memory budget
|
||||
(#2261); an MP4 lasts as long as its edit list shows (#2592); the Render Queue's times tick
|
||||
between frames (#2593). ScriptUI drags keep their control past the window edge with
|
||||
`MouseEvent.buttons` (#2584), and docked panels get their dock's size and `onResize` (#2585).
|
||||
- 2026-10-28: A shape layer's Rectangle and Ellipse Size get the Constrain Proportions chain
|
||||
that Scale has, so a circle stays a circle while its size is typed, scrubbed or keyed; it
|
||||
starts off, so existing shapes behave as before (#2489).
|
||||
- 2026-10-24: Shape layers have the Wiggle Transform path operation (Contents ▸ Add, or
|
||||
`layer.addShapeItem {kind: "wiggler"}`): it wiggles the anchor point, position, scale and
|
||||
rotation of the paths above it by random amounts that change over time, up to the Transform
|
||||
values it holds. The copies of a Repeater above it wiggle independently as far as its
|
||||
Correlation allows; with the Repeater below it, they wiggle together.
|
||||
- 2026-10-22: Localization: the panel/dialog catalogs gain 303 rows in Japanese, Simplified
|
||||
Chinese and Traditional Chinese — panel tabs, form and dialog labels, panel context menus,
|
||||
tracker/graph/text-panel controls, File-panel footers and credits. Recounted after #1774:
|
||||
Japanese is 1,923 of ≈ 4,200 interface strings (46%, with 297 effect names); the Chinese
|
||||
panel catalogs hold 998 (Simplified) and 951 (Traditional) rows, and Simplified Chinese is
|
||||
46% with its 307 effect names; the overall share is ≈ 24%.
|
||||
- 2026-10-16: Convert To Bezier Path turns a shape layer's Rectangle, Ellipse or Polystar
|
||||
path into an editable Path 1 with the same geometry, as After Effects does; it used to
|
||||
apply to mask RotoBezier only, so a parametric shape path could not be converted at all
|
||||
and its vertices could not be edited or animated (#932).
|
||||
- 2026-10-14: Georgian (`ka`): every menu row, every panel-catalog row the Chinese catalogs have plus 195 Georgian-only ones, and the effect names and categories; Match System for `ka` / `ka-GE` / `ka_GE.UTF-8` locales. Reviewed by a native speaker.
|
||||
- 2026-10-12: Roto Brush span edits report strokes outside a trimmed layer without panicking;
|
||||
strokes and undo history are preserved so restoring the timing allows the span edit again.
|
||||
- 2026-10-11: Ukrainian panels caught up with the Chinese catalogs. The 95 rows the panel
|
||||
catalog gained since 9 October are translated: the Home screen (recent files, their Kind and
|
||||
Opened columns, the empty and no-match states), the viewer's toolbar tooltips, magnification,
|
||||
resolution, grid, channel and Fast Previews popups, the Appearance settings, the solid dialog
|
||||
and the project, import, export and colour-management forms. `uk` now carries 495 of the 496
|
||||
panel rows — `RGB` reads the same in Ukrainian, so it falls back to the English source by
|
||||
design — alongside its 618/618 menus.
|
||||
- 2026-10-10: Full parity re-measure against After Effects 2026 26.5 (installed bundle, 26.2–26.5
|
||||
release notes, repository counts): breadth ≈ 91%, ready ≈ 45%, stage alpha. Progress docs
|
||||
restructured: `docs/parity.md` became `docs/target-app-parity.md`; new
|
||||
`docs/roadmap.md`, `localization-parity.md`, `file-format-parity.md`, `hardware-parity.md`,
|
||||
`ui-parity.md`, `effects-parity.md` and `scripting-parity.md`.
|
||||
- 2026-10-10: Spanish (#460) and Brazilian Portuguese (#217) menu translations.
|
||||
- 2026-10-10: Japanese now translates the interface below the menu bar too: `crates/ui-egui/src/i18n/ui.rs` has a Japanese table with the same 396 rows as the Chinese ones (panels, dialogs, buttons and tooltips; the shape test keeps the three catalogs listing the same source strings). Menus were already Japanese.
|
||||
- 2026-10-09: Ukrainian now covers all 396 current panel-catalog strings, including dialogs, buttons and tooltips, alongside its menu translation. The catalog keeps the same source keys and placeholder counts as the Chinese catalogs; missing or engine-generated text still falls back to English.
|
||||
|
||||
- UI.6: Viewer breadcrumbs use theme colors for readable active text. The bottom control row scrolls horizontally at compact widths; keyboard focus reveals the selected control while dropdowns, exposure adjustments and snapshot holds retain their behavior.
|
||||
- UI.3: Shared docking transactions and pointer targets now handle panel moves, splits, tab ordering and floating groups, preserving accordion metadata and the saved workspace schema. Dock dividers use bounded shared geometry and keyboard-accessible resize handles. Tiny layouts no longer reach an invalid floating-point clamp; workspace trees and saved layouts are retained.
|
||||
|
||||
- 2026-10-09: Settings ▸ General ▸ Language now translates the interface below the menu bar as well: panels, dialogs, buttons and tooltips read their text through `crates/ui-egui/src/i18n/ui.rs`, keyed by the English source string. Simplified and Traditional Chinese are complete (384 strings); Japanese rows can follow in the same file. A test fails when a converted panel gains a hard-coded string, and another keeps the catalog free of unused rows.
|
||||
- 2026-10-08: `effectcraft-cli render --out` resolves a relative path against the working directory, like `--project` (it used the project's folder, which doubled a path that already named it). Render Queue outputs set in a project are unchanged.
|
||||
- 2026-10-08: Long-press a grouped toolbar button to choose its tools, including Horizontal/Vertical Type; releasing the hold keeps the menu open. Existing point/paragraph text creation is unchanged.
|
||||
- 2026-10-06: Settings ▸ General ▸ Language persists English/Japanese menu labels (`general.language`); native UI reuses installed Japanese font fallback, with no bundled CJK font. Dialog and panel contents remain English.
|
||||
- 2026-10-05: Honest assessment ([docs/gaps.md](docs/gaps.md)): breadth ≈ 99% by our own checklist, real use ≈ 30–50%; workstreams G1–G9.
|
||||
- 2026-10-04: Checklist audit at `d39c0e8`: 89 done / 3 partial / 0 missing of 92 features.
|
||||
- 2026-10-01: First commit.
|
||||
|
||||
## Revision history
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-30 | minor | Progress log: text Line Join, Timeline drag-reorder and Shift multi-select of effects, viewer edge pan, effect padding budget, MP4 edit-list duration, Render Queue clock, ScriptUI drag capture and docked-panel size |
|
||||
| 2026-10-22 | minor | Localization: 300 more panel/dialog rows in Japanese and the Chinese catalogs, plus the review's three missing rows (Basic, Advanced, Play/Stop) — panel tabs, form and dialog labels, context menus, tracker/graph/text-panel controls, File-panel footers, credits; language shares recounted after #1774 (Japanese 27% → 46% with 297 effect names, Simplified Chinese 29% → 46% with 307 effect names, Traditional Chinese 29% → 38%, overall 18% → 24%) |
|
||||
| 2026-10-12 | minor | Open-issue count (134 to 216), crash list and localization share (12% to 18%) updated to match docs/gaps.md |
|
||||
| 2026-10-12 | patch | Roto Brush span validation after trimming; command regressions for both layer edges |
|
||||
| 2026-10-12 | minor | Shared PDF object/xref parsing with bounded app-owned conversion and preserved stream budgets; drawing, EPS and image/font policy remain unchanged |
|
||||
| 2026-10-10 | minor | Hours to ≈ 95% for each audience |
|
||||
| 2026-10-10 | minor | Headline adds mainstream practitioner (≈ 47%) and essentials user (≈ 52%) |
|
||||
| 2026-10-10 | major | Progress docs restructured: stage, two numbers, dimensions, features, languages, upcoming, progress log; milestones moved to docs/roadmap.md |
|
||||
| 2026-10-10 | minor | Stage checked against the new core-workflow gate (all six pass; still alpha) |
|
||||
| 2026-10-05 | major | Two numbers (breadth vs real use) and workstreams G1–G9 |
|
||||
# EffectCraft Roadmap
|
||||
|
||||
**Stage: alpha** · next: beta, ~30 points of "ready for real work" and ~350–620 h away
|
||||
|
||||
> **Last reviewed:** 2026-10-22 · **Last updated:** 2026-11-05 · **Change:** minor (ProRes alpha final-run conformance; previously: features of 30 October in the progress log: text Line Join, Timeline drag-reorder and multi-select of effects, viewer edge pan, ScriptUI drag capture and dock size; previously: localization recount after #1774) · **Target:** Adobe After Effects 2026 (26.5.0.89)
|
||||
|
||||
EffectCraft aims to do what After Effects does, with the same panels, menus and behaviour, written
|
||||
from scratch in Rust. This page is the one-page summary; the evidence is in
|
||||
[docs/target-app-parity.md](docs/target-app-parity.md), the work list in
|
||||
[docs/gaps.md](docs/gaps.md), and the plan in [docs/roadmap.md](docs/roadmap.md).
|
||||
|
||||
## Headline numbers
|
||||
|
||||
| | Value | Kind |
|
||||
|---|---|---|
|
||||
| **Feature breadth** | **≈ 91%** | Estimated, weighted by area; the self-graded 92-item checklist says ≈ 99% |
|
||||
| **Ready for real work** | **≈ 45%** (40–50%) | Estimated; one live fidelity measurement so far |
|
||||
| **Mainstream practitioner** | **≈ 47%** | Estimated: the typical motion designer's weekly work ([how](docs/target-app-parity.md#mainstream-practitioner-and-essentials-user)) |
|
||||
| **Essentials user** | **≈ 52%** | Estimated: core features only ([how](docs/target-app-parity.md#essentials-user)) |
|
||||
| Remaining to **beta** | **≈ 350–620 Opus 5.5 agent-hours** | Estimated |
|
||||
| Remaining to **full parity** | **≈ 850–1,500 Opus 5.5 agent-hours** | Estimated; ≈ 70% parallelises |
|
||||
|
||||
| Audience | Ready % | Hours to ~95% | Work that dominates |
|
||||
|---|---:|---:|---|
|
||||
| Full target | ≈ 45% | ≈ 850–1,500 h | `.aep`, fidelity corpus, bug backlog, performance, formats, hardware, localization |
|
||||
| Mainstream practitioner | ≈ 47% | ≈ 280–530 h | `.aep` exchange, stability, everyday fidelity and bugs, preview speed |
|
||||
| Essentials user | ≈ 52% | ≈ 150–300 h | Opening tutorial `.aep` files, start-up stability, core bugs, preview with sound |
|
||||
|
||||
About 60–70% of these hours parallelise ([detail](docs/target-app-parity.md#readiness-by-audience)).
|
||||
|
||||
Hours are one Opus 5.5 agent working sequentially, calibrated on this repository's history
|
||||
([how](docs/target-app-parity.md#calibration-of-the-hours)).
|
||||
|
||||
### Stage
|
||||
|
||||
**Alpha**, because all six core workflows of the [alpha gate](docs/roadmap.md#alpha-gate)
|
||||
(set up, animate, text and shapes, composite, preview, save and deliver) work end to end on macOS
|
||||
and the work saves and reopens, and ready for real work (≈ 45%) is above the ≈ 40% bar. But: After Effects projects (`.aep`) can't be opened, which
|
||||
alone rules out beta; fidelity against After Effects is measured for one narrow case (scalar ease,
|
||||
168 samples); 216 issues are open on 12 October, six of them crash reports (#580, #501, #426, #341, #234, #879). Beta needs ≈ 75% ready and `.aep`
|
||||
opening reliably: the `.aep` owner decision and importer (80–160 h), the fidelity harness and
|
||||
P0/P1 corpus (60–100 h), the crash and bug backlog (40–80 h), performance (40–70 h),
|
||||
Linux/Windows reliability (30–50 h) and depth in animation, masks, effects and text (100–160 h).
|
||||
|
||||
## By dimension
|
||||
|
||||
| Dimension | % | Hours | Doc |
|
||||
|---|---:|---:|---|
|
||||
| Features: breadth | 91% | — | [target-app-parity.md](docs/target-app-parity.md#by-feature-area) |
|
||||
| Features: ready for real work | 50% | 360–660 | [target-app-parity.md](docs/target-app-parity.md#by-feature-area) |
|
||||
| UI/UX fidelity | 65% | 60–120 | [ui-parity.md](docs/ui-parity.md) |
|
||||
| File formats | 50% | 130–230 | [file-format-parity.md](docs/file-format-parity.md) |
|
||||
| Hardware | 40% | 60–120 | [hardware-parity.md](docs/hardware-parity.md) |
|
||||
| Localization | 24% | 60–110 + review | [localization-parity.md](docs/localization-parity.md) |
|
||||
| Performance | 35% | 50–100 | [gaps.md](docs/gaps.md#g5-performance-at-real-world-scale) |
|
||||
| Stability | 45% | 40–80 | [gaps.md](docs/gaps.md#g3-stability-and-a-green-main) |
|
||||
| Platforms | 65% | 30–60 | [gaps.md](docs/gaps.md#g2-real-user-reliability-on-every-platform) |
|
||||
| Ecosystem and plug-ins | 20% | 40–80 | [plugins.md](docs/plugins.md), [scripting-parity.md](docs/scripting-parity.md) |
|
||||
| AI features | 35% | 40–80 | [gaps.md](docs/gaps.md) |
|
||||
| Agent control (beyond After Effects) | ahead | — | [agents.md](docs/agents.md) |
|
||||
|
||||
## Features
|
||||
|
||||
| Area | Breadth | Ready | Hours |
|
||||
|---|---:|---:|---:|
|
||||
| Project, footage and import | 92% | 55% | 25–45 |
|
||||
| Compositions, layers and compositing | 98% | 60% | 30–55 |
|
||||
| Animation: keyframes, graph editor, motion paths, puppet | 97% | 55% | 35–60 |
|
||||
| Expressions and scripting ([detail](docs/scripting-parity.md)) | 93% | 65% | 20–40 |
|
||||
| Shapes, masks and roto | 93% | 45% | 30–50 |
|
||||
| Text and typography | 94% | 50% | 20–35 |
|
||||
| Effects and animation presets ([detail](docs/effects-parity.md)) | 85% | 40% | 70–130 |
|
||||
| 3D (Classic and Advanced) | 82% | 35% | 35–65 |
|
||||
| Tracking and AI-assisted tools | 80% | 35% | 35–70 |
|
||||
| Preview and cache | 92% | 40% | 25–50 |
|
||||
| Render queue and export | 85% | 55% | 25–45 |
|
||||
| Audio | 85% | 45% | 6–12 |
|
||||
|
||||
Weights and evidence per row: [target-app-parity.md](docs/target-app-parity.md#by-feature-area).
|
||||
|
||||
## Languages
|
||||
|
||||
UI strings translated out of ≈ 4,200 (detail: [localization-parity.md](docs/localization-parity.md)).
|
||||
|
||||
| Language | Status | % |
|
||||
|---|---|---:|
|
||||
| English | full | 100% |
|
||||
| Simplified Chinese | partial (menus, most of the panels) | 46% |
|
||||
| Spanish | menus only | 29% |
|
||||
| Hindi | none | 0% |
|
||||
| Arabic | none | 0% |
|
||||
| French | menus only | 16% |
|
||||
| Portuguese (Brazil) | partial (menus, part of the panels) | 34% |
|
||||
| Indonesian | none | 0% |
|
||||
| Japanese | partial (menus, most of the panels) | 46% |
|
||||
| German | menus only | 16% |
|
||||
| Korean | none | 0% |
|
||||
| Vietnamese | none | 0% |
|
||||
|
||||
Also shipped: Traditional Chinese (partial, 38%), Ukrainian (partial, 29%), Czech (partial, 29%), Polish (partial, 34%), Georgian (partial, 39%; native-speaker reviewed), Russian (menus only, 15%) and Swedish (menus, panels and effect names; not yet measured). No other language has had
|
||||
native-speaker review.
|
||||
|
||||
## Upcoming
|
||||
|
||||
Ranked; detail in [docs/roadmap.md](docs/roadmap.md) and [docs/gaps.md](docs/gaps.md).
|
||||
|
||||
| | Next | Hours |
|
||||
|---|---|---:|
|
||||
| 1 | Fix the open crash reports; run `cargo xtask ci` on every pull request (G3) | 15–30 |
|
||||
| 2 | Fidelity harness against After Effects: rendered frames, a corpus case per P0/P1 feature (G1) | 60–100 |
|
||||
| 3 | Real-user reliability on Linux and Windows; the open mask, effect, text and 3D bugs (G2) | 100–190 |
|
||||
| 4 | `.aep` / `.aepx` import, after the owner's clean-room decision (G4) | 80–160 |
|
||||
| 5 | Performance: benchmarks at 1080p / 4K, real-time cached playback, hardware video (G5) | 50–100 |
|
||||
| 6 | Localization depth: effect names, all panels, Korean / Italian (G9) | 60–110 |
|
||||
|
||||
Come tell us what matters most to you on [Discord](https://discord.gg/artcraft).
|
||||
|
||||
## Progress log
|
||||
|
||||
- 2026-11-05: ProRes 4444 and XQ exports write the required run after the final alpha
|
||||
difference. The FilmCraft encoder pin fixes alpha movies rejected by native macOS
|
||||
decoding; an export regression checks the stored bytes for both profiles, alpha modes,
|
||||
and motion-blur settings (#293). Resolve itself still needs confirmation.
|
||||
- 2026-10-30: Text strokes take a Line Join (Miter, Round or Bevel) in the Character panel,
|
||||
Miter for new text as in After Effects (#2077). In the Timeline, effects, masks and shape items
|
||||
drag to a new place in their stack (#2210) and Shift-click adds them to the selection, so one
|
||||
animation preset can hold several (#2552). The Composition panel pans while a mask vertex is
|
||||
held at its edge (#999). Effect buffers that pad a small layer stay within a memory budget
|
||||
(#2261); an MP4 lasts as long as its edit list shows (#2592); the Render Queue's times tick
|
||||
between frames (#2593). ScriptUI drags keep their control past the window edge with
|
||||
`MouseEvent.buttons` (#2584), and docked panels get their dock's size and `onResize` (#2585).
|
||||
- 2026-10-28: A shape layer's Rectangle and Ellipse Size get the Constrain Proportions chain
|
||||
that Scale has, so a circle stays a circle while its size is typed, scrubbed or keyed; it
|
||||
starts off, so existing shapes behave as before (#2489).
|
||||
- 2026-10-24: Shape layers have the Wiggle Transform path operation (Contents ▸ Add, or
|
||||
`layer.addShapeItem {kind: "wiggler"}`): it wiggles the anchor point, position, scale and
|
||||
rotation of the paths above it by random amounts that change over time, up to the Transform
|
||||
values it holds. The copies of a Repeater above it wiggle independently as far as its
|
||||
Correlation allows; with the Repeater below it, they wiggle together.
|
||||
- 2026-10-22: Localization: the panel/dialog catalogs gain 303 rows in Japanese, Simplified
|
||||
Chinese and Traditional Chinese — panel tabs, form and dialog labels, panel context menus,
|
||||
tracker/graph/text-panel controls, File-panel footers and credits. Recounted after #1774:
|
||||
Japanese is 1,923 of ≈ 4,200 interface strings (46%, with 297 effect names); the Chinese
|
||||
panel catalogs hold 998 (Simplified) and 951 (Traditional) rows, and Simplified Chinese is
|
||||
46% with its 307 effect names; the overall share is ≈ 24%.
|
||||
- 2026-10-16: Convert To Bezier Path turns a shape layer's Rectangle, Ellipse or Polystar
|
||||
path into an editable Path 1 with the same geometry, as After Effects does; it used to
|
||||
apply to mask RotoBezier only, so a parametric shape path could not be converted at all
|
||||
and its vertices could not be edited or animated (#932).
|
||||
- 2026-10-14: Georgian (`ka`): every menu row, every panel-catalog row the Chinese catalogs have plus 195 Georgian-only ones, and the effect names and categories; Match System for `ka` / `ka-GE` / `ka_GE.UTF-8` locales. Reviewed by a native speaker.
|
||||
- 2026-10-12: Roto Brush span edits report strokes outside a trimmed layer without panicking;
|
||||
strokes and undo history are preserved so restoring the timing allows the span edit again.
|
||||
- 2026-10-11: Ukrainian panels caught up with the Chinese catalogs. The 95 rows the panel
|
||||
catalog gained since 9 October are translated: the Home screen (recent files, their Kind and
|
||||
Opened columns, the empty and no-match states), the viewer's toolbar tooltips, magnification,
|
||||
resolution, grid, channel and Fast Previews popups, the Appearance settings, the solid dialog
|
||||
and the project, import, export and colour-management forms. `uk` now carries 495 of the 496
|
||||
panel rows — `RGB` reads the same in Ukrainian, so it falls back to the English source by
|
||||
design — alongside its 618/618 menus.
|
||||
- 2026-10-10: Full parity re-measure against After Effects 2026 26.5 (installed bundle, 26.2–26.5
|
||||
release notes, repository counts): breadth ≈ 91%, ready ≈ 45%, stage alpha. Progress docs
|
||||
restructured: `docs/parity.md` became `docs/target-app-parity.md`; new
|
||||
`docs/roadmap.md`, `localization-parity.md`, `file-format-parity.md`, `hardware-parity.md`,
|
||||
`ui-parity.md`, `effects-parity.md` and `scripting-parity.md`.
|
||||
- 2026-10-10: Spanish (#460) and Brazilian Portuguese (#217) menu translations.
|
||||
- 2026-10-10: Japanese now translates the interface below the menu bar too: `crates/ui-egui/src/i18n/ui.rs` has a Japanese table with the same 396 rows as the Chinese ones (panels, dialogs, buttons and tooltips; the shape test keeps the three catalogs listing the same source strings). Menus were already Japanese.
|
||||
- 2026-10-09: Ukrainian now covers all 396 current panel-catalog strings, including dialogs, buttons and tooltips, alongside its menu translation. The catalog keeps the same source keys and placeholder counts as the Chinese catalogs; missing or engine-generated text still falls back to English.
|
||||
|
||||
- UI.6: Viewer breadcrumbs use theme colors for readable active text. The bottom control row scrolls horizontally at compact widths; keyboard focus reveals the selected control while dropdowns, exposure adjustments and snapshot holds retain their behavior.
|
||||
- UI.3: Shared docking transactions and pointer targets now handle panel moves, splits, tab ordering and floating groups, preserving accordion metadata and the saved workspace schema. Dock dividers use bounded shared geometry and keyboard-accessible resize handles. Tiny layouts no longer reach an invalid floating-point clamp; workspace trees and saved layouts are retained.
|
||||
|
||||
- 2026-10-09: Settings ▸ General ▸ Language now translates the interface below the menu bar as well: panels, dialogs, buttons and tooltips read their text through `crates/ui-egui/src/i18n/ui.rs`, keyed by the English source string. Simplified and Traditional Chinese are complete (384 strings); Japanese rows can follow in the same file. A test fails when a converted panel gains a hard-coded string, and another keeps the catalog free of unused rows.
|
||||
- 2026-10-08: `effectcraft-cli render --out` resolves a relative path against the working directory, like `--project` (it used the project's folder, which doubled a path that already named it). Render Queue outputs set in a project are unchanged.
|
||||
- 2026-10-08: Long-press a grouped toolbar button to choose its tools, including Horizontal/Vertical Type; releasing the hold keeps the menu open. Existing point/paragraph text creation is unchanged.
|
||||
- 2026-10-06: Settings ▸ General ▸ Language persists English/Japanese menu labels (`general.language`); native UI reuses installed Japanese font fallback, with no bundled CJK font. Dialog and panel contents remain English.
|
||||
- 2026-10-05: Honest assessment ([docs/gaps.md](docs/gaps.md)): breadth ≈ 99% by our own checklist, real use ≈ 30–50%; workstreams G1–G9.
|
||||
- 2026-10-04: Checklist audit at `d39c0e8`: 89 done / 3 partial / 0 missing of 92 features.
|
||||
- 2026-10-01: First commit.
|
||||
|
||||
## Revision history
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-30 | minor | Progress log: text Line Join, Timeline drag-reorder and Shift multi-select of effects, viewer edge pan, effect padding budget, MP4 edit-list duration, Render Queue clock, ScriptUI drag capture and docked-panel size |
|
||||
| 2026-10-22 | minor | Localization: 300 more panel/dialog rows in Japanese and the Chinese catalogs, plus the review's three missing rows (Basic, Advanced, Play/Stop) — panel tabs, form and dialog labels, context menus, tracker/graph/text-panel controls, File-panel footers, credits; language shares recounted after #1774 (Japanese 27% → 46% with 297 effect names, Simplified Chinese 29% → 46% with 307 effect names, Traditional Chinese 29% → 38%, overall 18% → 24%) |
|
||||
| 2026-10-12 | minor | Open-issue count (134 to 216), crash list and localization share (12% to 18%) updated to match docs/gaps.md |
|
||||
| 2026-10-12 | patch | Roto Brush span validation after trimming; command regressions for both layer edges |
|
||||
| 2026-10-12 | minor | Shared PDF object/xref parsing with bounded app-owned conversion and preserved stream budgets; drawing, EPS and image/font policy remain unchanged |
|
||||
| 2026-10-10 | minor | Hours to ≈ 95% for each audience |
|
||||
| 2026-10-10 | minor | Headline adds mainstream practitioner (≈ 47%) and essentials user (≈ 52%) |
|
||||
| 2026-10-10 | major | Progress docs restructured: stage, two numbers, dimensions, features, languages, upcoming, progress log; milestones moved to docs/roadmap.md |
|
||||
| 2026-10-10 | minor | Stage checked against the new core-workflow gate (all six pass; still alpha) |
|
||||
| 2026-10-05 | major | Two numbers (breadth vs real use) and workstreams G1–G9 |
|
||||
+8
-8
@@ -1,8 +1,8 @@
|
||||
allow-unwrap-in-tests = true
|
||||
allow-expect-in-tests = true
|
||||
allow-panic-in-tests = true
|
||||
allow-indexing-slicing-in-tests = true
|
||||
disallowed-types = [
|
||||
{ path = "egui::ComboBox", reason = "use craft_ui::ComboBox: egui's combo box menu can't grow back while open (egui#5225)" },
|
||||
]
|
||||
cognitive-complexity-threshold = 30
|
||||
allow-unwrap-in-tests = true
|
||||
allow-expect-in-tests = true
|
||||
allow-panic-in-tests = true
|
||||
allow-indexing-slicing-in-tests = true
|
||||
disallowed-types = [
|
||||
{ path = "egui::ComboBox", reason = "use craft_ui::ComboBox: egui's combo box menu can't grow back while open (egui#5225)" },
|
||||
]
|
||||
cognitive-complexity-threshold = 30
|
||||
+20
-20
@@ -1,20 +1,20 @@
|
||||
[package]
|
||||
name = "effectcraft-svg"
|
||||
description = "SVG import: parses SVG 1.1 / SVG 2 static vector content (paths, basic shapes, groups, transforms, use, CSS styles, solid, gradient and pattern fills/strokes, opacity) and rasterises it at any scale"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
craft-svg = { workspace = true }
|
||||
effectcraft-geom = { workspace = true }
|
||||
effectcraft-raster = { workspace = true }
|
||||
effectcraft-path = { workspace = true }
|
||||
kurbo = { workspace = true }
|
||||
roxmltree = "0.21"
|
||||
rayon = { workspace = true }
|
||||
log = { workspace = true }
|
||||
[package]
|
||||
name = "effectcraft-svg"
|
||||
description = "SVG import: parses SVG 1.1 / SVG 2 static vector content (paths, basic shapes, groups, transforms, use, CSS styles, solid, gradient and pattern fills/strokes, opacity) and rasterises it at any scale"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
craft-svg = { workspace = true }
|
||||
effectcraft-geom = { workspace = true }
|
||||
effectcraft-raster = { workspace = true }
|
||||
effectcraft-path = { workspace = true }
|
||||
kurbo = { workspace = true }
|
||||
roxmltree = "0.21"
|
||||
rayon = { workspace = true }
|
||||
log = { workspace = true }
|
||||
+48
-48
@@ -1,48 +1,48 @@
|
||||
# effectcraft-svg
|
||||
|
||||
SVG import for EffectCraft: `.svg` files import as footage (rasterised at any scale, so
|
||||
Continuously Rasterize stays sharp) and Layer ▸ Create ▸ Create Shapes from Vector Layer turns
|
||||
them into editable shape layers.
|
||||
|
||||
Implemented from the W3C **Scalable Vector Graphics (SVG) 1.1 (Second Edition)** recommendation
|
||||
(16 August 2011) and the **SVG 2** candidate recommendation (4 October 2018) for the parts listed
|
||||
here, plus **CSS Color Module Level 3** for colour keywords and functions:
|
||||
|
||||
- document: `svg` (width/height, `viewBox`, `preserveAspectRatio`), nested `svg`, `g`, `defs`,
|
||||
`use` (x/y, `href` / `xlink:href`), `symbol` (through `use`), `title`/`desc`/`metadata` ignored;
|
||||
- shapes: `path` (all path data commands incl. elliptical arcs), `rect` (rx/ry), `circle`,
|
||||
`ellipse`, `line`, `polyline`, `polygon`;
|
||||
- `transform` lists (matrix, translate, scale, rotate with centre, skewX, skewY);
|
||||
- painting: `fill`, `fill-rule`, `fill-opacity`, `stroke`, `stroke-width`, `stroke-linecap`,
|
||||
`stroke-linejoin`, `stroke-miterlimit`, `stroke-dasharray`, `stroke-dashoffset`,
|
||||
`stroke-opacity`, `opacity`, `display`, `visibility`, `color`/`currentColor`;
|
||||
- `linearGradient` and `radialGradient` (with `fx`/`fy` focal point), `gradientUnits`,
|
||||
`gradientTransform`, `spreadMethod`, `href` inheritance of stops and attributes;
|
||||
- static (non-animated) `<pattern>` fill/stroke with `patternUnits`, `patternContentUnits`, `patternTransform`,
|
||||
tile `x`/`y`/`width`/`height`, local `href`/`xlink:href` inheritance and solid paints on
|
||||
supported shape/group children;
|
||||
- styling through presentation attributes, the `style` attribute and `<style>` sheets with
|
||||
type, class, id and universal selectors (simple selectors and selector lists).
|
||||
|
||||
Pattern `viewBox`, external pattern references, nested paint servers and non-shape pattern content
|
||||
are unsupported; invalid patterns use a `url(...)` fallback when present. Local pattern `href`
|
||||
chains are depth-limited and cycles are rejected. Tiles render once and are sampled across the
|
||||
painted area; both live surfaces and aggregate tile work are bounded. Pattern paints are
|
||||
rasterised in SVG footage; editable shape layers retain geometry and convert only a supplied
|
||||
`url(...)` fallback to solid fill/stroke.
|
||||
Path expansion and gradient resolution have per-import work limits. The rasterizer also caps live
|
||||
surface memory and cumulative pattern, clip-coverage, and source-image work. Over-budget imports
|
||||
return an error; over-budget raster operations are skipped, using a paint fallback where available.
|
||||
|
||||
`rasterize`/`rasterize_with` drop content refused by a budget and return the image drawn so far
|
||||
(an empty image if the canvas itself is refused). `try_rasterize`/`try_rasterize_with` return
|
||||
`Err(RasterError)` instead (`SurfaceLimit` for memory, `WorkLimit` for work). Budgets per render:
|
||||
512 MiB of live offscreen surfaces, 512 MiB of pattern tiles, 256 Mi units each of pattern
|
||||
render work, clip-path rasterisation work and image sampling work, and 2^31 pixels touched across
|
||||
all passes.
|
||||
|
||||
For SVG input, text, images, filters, clip paths, masks, markers and animation (including pattern
|
||||
animation) are unsupported and ignored. The PDF importers' documents do use clip, mask, blend and
|
||||
image nodes, which the rasterizer renders under the same budgets.
|
||||
XML parsing uses `roxmltree` (MIT/Apache-2.0). Test fixtures are original work (see
|
||||
`tests/fixtures/*.attribution`).
|
||||
# effectcraft-svg
|
||||
|
||||
SVG import for EffectCraft: `.svg` files import as footage (rasterised at any scale, so
|
||||
Continuously Rasterize stays sharp) and Layer ▸ Create ▸ Create Shapes from Vector Layer turns
|
||||
them into editable shape layers.
|
||||
|
||||
Implemented from the W3C **Scalable Vector Graphics (SVG) 1.1 (Second Edition)** recommendation
|
||||
(16 August 2011) and the **SVG 2** candidate recommendation (4 October 2018) for the parts listed
|
||||
here, plus **CSS Color Module Level 3** for colour keywords and functions:
|
||||
|
||||
- document: `svg` (width/height, `viewBox`, `preserveAspectRatio`), nested `svg`, `g`, `defs`,
|
||||
`use` (x/y, `href` / `xlink:href`), `symbol` (through `use`), `title`/`desc`/`metadata` ignored;
|
||||
- shapes: `path` (all path data commands incl. elliptical arcs), `rect` (rx/ry), `circle`,
|
||||
`ellipse`, `line`, `polyline`, `polygon`;
|
||||
- `transform` lists (matrix, translate, scale, rotate with centre, skewX, skewY);
|
||||
- painting: `fill`, `fill-rule`, `fill-opacity`, `stroke`, `stroke-width`, `stroke-linecap`,
|
||||
`stroke-linejoin`, `stroke-miterlimit`, `stroke-dasharray`, `stroke-dashoffset`,
|
||||
`stroke-opacity`, `opacity`, `display`, `visibility`, `color`/`currentColor`;
|
||||
- `linearGradient` and `radialGradient` (with `fx`/`fy` focal point), `gradientUnits`,
|
||||
`gradientTransform`, `spreadMethod`, `href` inheritance of stops and attributes;
|
||||
- static (non-animated) `<pattern>` fill/stroke with `patternUnits`, `patternContentUnits`, `patternTransform`,
|
||||
tile `x`/`y`/`width`/`height`, local `href`/`xlink:href` inheritance and solid paints on
|
||||
supported shape/group children;
|
||||
- styling through presentation attributes, the `style` attribute and `<style>` sheets with
|
||||
type, class, id and universal selectors (simple selectors and selector lists).
|
||||
|
||||
Pattern `viewBox`, external pattern references, nested paint servers and non-shape pattern content
|
||||
are unsupported; invalid patterns use a `url(...)` fallback when present. Local pattern `href`
|
||||
chains are depth-limited and cycles are rejected. Tiles render once and are sampled across the
|
||||
painted area; both live surfaces and aggregate tile work are bounded. Pattern paints are
|
||||
rasterised in SVG footage; editable shape layers retain geometry and convert only a supplied
|
||||
`url(...)` fallback to solid fill/stroke.
|
||||
Path expansion and gradient resolution have per-import work limits. The rasterizer also caps live
|
||||
surface memory and cumulative pattern, clip-coverage, and source-image work. Over-budget imports
|
||||
return an error; over-budget raster operations are skipped, using a paint fallback where available.
|
||||
|
||||
`rasterize`/`rasterize_with` drop content refused by a budget and return the image drawn so far
|
||||
(an empty image if the canvas itself is refused). `try_rasterize`/`try_rasterize_with` return
|
||||
`Err(RasterError)` instead (`SurfaceLimit` for memory, `WorkLimit` for work). Budgets per render:
|
||||
512 MiB of live offscreen surfaces, 512 MiB of pattern tiles, 256 Mi units each of pattern
|
||||
render work, clip-path rasterisation work and image sampling work, and 2^31 pixels touched across
|
||||
all passes.
|
||||
|
||||
For SVG input, text, images, filters, clip paths, masks, markers and animation (including pattern
|
||||
animation) are unsupported and ignored. The PDF importers' documents do use clip, mask, blend and
|
||||
image nodes, which the rasterizer renders under the same budgets.
|
||||
XML parsing uses `roxmltree` (MIT/Apache-2.0). Test fixtures are original work (see
|
||||
`tests/fixtures/*.attribution`).
|
||||
+64
-64
@@ -1,64 +1,64 @@
|
||||
use kurbo::{Affine, BezPath, Rect, Shape as _};
|
||||
|
||||
use crate::{Doc, Group, Node};
|
||||
|
||||
impl Doc {
|
||||
/// Geometric artwork bounds in document pixels, including strokes and clipping.
|
||||
/// Opacity and soft masks do not change the artwork's dimensions.
|
||||
pub fn artwork_bounds(&self) -> Option<Rect> {
|
||||
let page = Rect::new(0.0, 0.0, self.width, self.height);
|
||||
group_bounds(&self.root, Affine::IDENTITY, 0, page)
|
||||
}
|
||||
}
|
||||
|
||||
fn path_bounds(path: BezPath) -> Option<Rect> {
|
||||
if path.elements().is_empty() || !path.is_finite() {
|
||||
return None;
|
||||
}
|
||||
let r = path.bounding_box();
|
||||
(r.is_finite() && r.width() > 0.0 && r.height() > 0.0).then_some(r)
|
||||
}
|
||||
|
||||
fn group_bounds(group: &Group, transform: Affine, depth: usize, page: Rect) -> Option<Rect> {
|
||||
if depth > 256 {
|
||||
return Some(page);
|
||||
}
|
||||
let transform = transform * group.transform;
|
||||
if !transform.as_coeffs().iter().all(|v| v.is_finite()) {
|
||||
return None;
|
||||
}
|
||||
let mut bounds: Option<Rect> = None;
|
||||
for node in &group.children {
|
||||
let rect = match node {
|
||||
Node::Group(g) => group_bounds(g, transform, depth + 1, page),
|
||||
Node::Image(i) => {
|
||||
let rect = Rect::new(0.0, 0.0, f64::from(i.width), f64::from(i.height));
|
||||
path_bounds(transform * i.transform * rect.to_path(0.01))
|
||||
}
|
||||
Node::Shape(s) => {
|
||||
let m = transform * s.transform;
|
||||
let path = s.geom.to_path();
|
||||
let mut rect = s.fill.as_ref().and_then(|_| path_bounds(m * &path));
|
||||
if let Some(stroke) = &s.stroke {
|
||||
let scale = m.determinant().abs().sqrt();
|
||||
if scale.is_finite()
|
||||
&& path.is_finite()
|
||||
&& let Some(outline) = effectcraft_path::stroke_outline(std::slice::from_ref(&path), &stroke.style(), scale)
|
||||
&& let Some(r) = path_bounds(m * outline)
|
||||
{
|
||||
rect = Some(rect.map_or(r, |b| b.union(r)));
|
||||
}
|
||||
}
|
||||
rect
|
||||
}
|
||||
};
|
||||
if let Some(r) = rect {
|
||||
bounds = Some(bounds.map_or(r, |b| b.union(r)));
|
||||
}
|
||||
}
|
||||
let mut bounds = bounds?;
|
||||
for (clip, _) in &group.clip {
|
||||
bounds = bounds.intersect(path_bounds(transform * clip)?);
|
||||
}
|
||||
(bounds.width() > 0.0 && bounds.height() > 0.0).then_some(bounds)
|
||||
}
|
||||
use kurbo::{Affine, BezPath, Rect, Shape as _};
|
||||
|
||||
use crate::{Doc, Group, Node};
|
||||
|
||||
impl Doc {
|
||||
/// Geometric artwork bounds in document pixels, including strokes and clipping.
|
||||
/// Opacity and soft masks do not change the artwork's dimensions.
|
||||
pub fn artwork_bounds(&self) -> Option<Rect> {
|
||||
let page = Rect::new(0.0, 0.0, self.width, self.height);
|
||||
group_bounds(&self.root, Affine::IDENTITY, 0, page)
|
||||
}
|
||||
}
|
||||
|
||||
fn path_bounds(path: BezPath) -> Option<Rect> {
|
||||
if path.elements().is_empty() || !path.is_finite() {
|
||||
return None;
|
||||
}
|
||||
let r = path.bounding_box();
|
||||
(r.is_finite() && r.width() > 0.0 && r.height() > 0.0).then_some(r)
|
||||
}
|
||||
|
||||
fn group_bounds(group: &Group, transform: Affine, depth: usize, page: Rect) -> Option<Rect> {
|
||||
if depth > 256 {
|
||||
return Some(page);
|
||||
}
|
||||
let transform = transform * group.transform;
|
||||
if !transform.as_coeffs().iter().all(|v| v.is_finite()) {
|
||||
return None;
|
||||
}
|
||||
let mut bounds: Option<Rect> = None;
|
||||
for node in &group.children {
|
||||
let rect = match node {
|
||||
Node::Group(g) => group_bounds(g, transform, depth + 1, page),
|
||||
Node::Image(i) => {
|
||||
let rect = Rect::new(0.0, 0.0, f64::from(i.width), f64::from(i.height));
|
||||
path_bounds(transform * i.transform * rect.to_path(0.01))
|
||||
}
|
||||
Node::Shape(s) => {
|
||||
let m = transform * s.transform;
|
||||
let path = s.geom.to_path();
|
||||
let mut rect = s.fill.as_ref().and_then(|_| path_bounds(m * &path));
|
||||
if let Some(stroke) = &s.stroke {
|
||||
let scale = m.determinant().abs().sqrt();
|
||||
if scale.is_finite()
|
||||
&& path.is_finite()
|
||||
&& let Some(outline) = effectcraft_path::stroke_outline(std::slice::from_ref(&path), &stroke.style(), scale)
|
||||
&& let Some(r) = path_bounds(m * outline)
|
||||
{
|
||||
rect = Some(rect.map_or(r, |b| b.union(r)));
|
||||
}
|
||||
}
|
||||
rect
|
||||
}
|
||||
};
|
||||
if let Some(r) = rect {
|
||||
bounds = Some(bounds.map_or(r, |b| b.union(r)));
|
||||
}
|
||||
}
|
||||
let mut bounds = bounds?;
|
||||
for (clip, _) in &group.clip {
|
||||
bounds = bounds.intersect(path_bounds(transform * clip)?);
|
||||
}
|
||||
(bounds.width() > 0.0 && bounds.height() > 0.0).then_some(bounds)
|
||||
}
|
||||
+282
-282
@@ -1,282 +1,282 @@
|
||||
//! CSS colour values (CSS Color Module Level 3): keywords, `#rgb[a]`, `#rrggbb[aa]`, `rgb[a]()`,
|
||||
//! `hsl[a]()`.
|
||||
|
||||
/// The CSS colour keywords (CSS Color Module Level 3, §4.3 extended colour keywords).
|
||||
const NAMED: &[(&str, u32)] = &[
|
||||
("aliceblue", 0xF0F8FF),
|
||||
("antiquewhite", 0xFAEBD7),
|
||||
("aqua", 0x00FFFF),
|
||||
("aquamarine", 0x7FFFD4),
|
||||
("azure", 0xF0FFFF),
|
||||
("beige", 0xF5F5DC),
|
||||
("bisque", 0xFFE4C4),
|
||||
("black", 0x000000),
|
||||
("blanchedalmond", 0xFFEBCD),
|
||||
("blue", 0x0000FF),
|
||||
("blueviolet", 0x8A2BE2),
|
||||
("brown", 0xA52A2A),
|
||||
("burlywood", 0xDEB887),
|
||||
("cadetblue", 0x5F9EA0),
|
||||
("chartreuse", 0x7FFF00),
|
||||
("chocolate", 0xD2691E),
|
||||
("coral", 0xFF7F50),
|
||||
("cornflowerblue", 0x6495ED),
|
||||
("cornsilk", 0xFFF8DC),
|
||||
("crimson", 0xDC143C),
|
||||
("cyan", 0x00FFFF),
|
||||
("darkblue", 0x00008B),
|
||||
("darkcyan", 0x008B8B),
|
||||
("darkgoldenrod", 0xB8860B),
|
||||
("darkgray", 0xA9A9A9),
|
||||
("darkgreen", 0x006400),
|
||||
("darkgrey", 0xA9A9A9),
|
||||
("darkkhaki", 0xBDB76B),
|
||||
("darkmagenta", 0x8B008B),
|
||||
("darkolivegreen", 0x556B2F),
|
||||
("darkorange", 0xFF8C00),
|
||||
("darkorchid", 0x9932CC),
|
||||
("darkred", 0x8B0000),
|
||||
("darksalmon", 0xE9967A),
|
||||
("darkseagreen", 0x8FBC8F),
|
||||
("darkslateblue", 0x483D8B),
|
||||
("darkslategray", 0x2F4F4F),
|
||||
("darkslategrey", 0x2F4F4F),
|
||||
("darkturquoise", 0x00CED1),
|
||||
("darkviolet", 0x9400D3),
|
||||
("deeppink", 0xFF1493),
|
||||
("deepskyblue", 0x00BFFF),
|
||||
("dimgray", 0x696969),
|
||||
("dimgrey", 0x696969),
|
||||
("dodgerblue", 0x1E90FF),
|
||||
("firebrick", 0xB22222),
|
||||
("floralwhite", 0xFFFAF0),
|
||||
("forestgreen", 0x228B22),
|
||||
("fuchsia", 0xFF00FF),
|
||||
("gainsboro", 0xDCDCDC),
|
||||
("ghostwhite", 0xF8F8FF),
|
||||
("gold", 0xFFD700),
|
||||
("goldenrod", 0xDAA520),
|
||||
("gray", 0x808080),
|
||||
("grey", 0x808080),
|
||||
("green", 0x008000),
|
||||
("greenyellow", 0xADFF2F),
|
||||
("honeydew", 0xF0FFF0),
|
||||
("hotpink", 0xFF69B4),
|
||||
("indianred", 0xCD5C5C),
|
||||
("indigo", 0x4B0082),
|
||||
("ivory", 0xFFFFF0),
|
||||
("khaki", 0xF0E68C),
|
||||
("lavender", 0xE6E6FA),
|
||||
("lavenderblush", 0xFFF0F5),
|
||||
("lawngreen", 0x7CFC00),
|
||||
("lemonchiffon", 0xFFFACD),
|
||||
("lightblue", 0xADD8E6),
|
||||
("lightcoral", 0xF08080),
|
||||
("lightcyan", 0xE0FFFF),
|
||||
("lightgoldenrodyellow", 0xFAFAD2),
|
||||
("lightgray", 0xD3D3D3),
|
||||
("lightgreen", 0x90EE90),
|
||||
("lightgrey", 0xD3D3D3),
|
||||
("lightpink", 0xFFB6C1),
|
||||
("lightsalmon", 0xFFA07A),
|
||||
("lightseagreen", 0x20B2AA),
|
||||
("lightskyblue", 0x87CEFA),
|
||||
("lightslategray", 0x778899),
|
||||
("lightslategrey", 0x778899),
|
||||
("lightsteelblue", 0xB0C4DE),
|
||||
("lightyellow", 0xFFFFE0),
|
||||
("lime", 0x00FF00),
|
||||
("limegreen", 0x32CD32),
|
||||
("linen", 0xFAF0E6),
|
||||
("magenta", 0xFF00FF),
|
||||
("maroon", 0x800000),
|
||||
("mediumaquamarine", 0x66CDAA),
|
||||
("mediumblue", 0x0000CD),
|
||||
("mediumorchid", 0xBA55D3),
|
||||
("mediumpurple", 0x9370DB),
|
||||
("mediumseagreen", 0x3CB371),
|
||||
("mediumslateblue", 0x7B68EE),
|
||||
("mediumspringgreen", 0x00FA9A),
|
||||
("mediumturquoise", 0x48D1CC),
|
||||
("mediumvioletred", 0xC71585),
|
||||
("midnightblue", 0x191970),
|
||||
("mintcream", 0xF5FFFA),
|
||||
("mistyrose", 0xFFE4E1),
|
||||
("moccasin", 0xFFE4B5),
|
||||
("navajowhite", 0xFFDEAD),
|
||||
("navy", 0x000080),
|
||||
("oldlace", 0xFDF5E6),
|
||||
("olive", 0x808000),
|
||||
("olivedrab", 0x6B8E23),
|
||||
("orange", 0xFFA500),
|
||||
("orangered", 0xFF4500),
|
||||
("orchid", 0xDA70D6),
|
||||
("palegoldenrod", 0xEEE8AA),
|
||||
("palegreen", 0x98FB98),
|
||||
("paleturquoise", 0xAFEEEE),
|
||||
("palevioletred", 0xDB7093),
|
||||
("papayawhip", 0xFFEFD5),
|
||||
("peachpuff", 0xFFDAB9),
|
||||
("peru", 0xCD853F),
|
||||
("pink", 0xFFC0CB),
|
||||
("plum", 0xDDA0DD),
|
||||
("powderblue", 0xB0E0E6),
|
||||
("purple", 0x800080),
|
||||
("rebeccapurple", 0x663399),
|
||||
("red", 0xFF0000),
|
||||
("rosybrown", 0xBC8F8F),
|
||||
("royalblue", 0x4169E1),
|
||||
("saddlebrown", 0x8B4513),
|
||||
("salmon", 0xFA8072),
|
||||
("sandybrown", 0xF4A460),
|
||||
("seagreen", 0x2E8B57),
|
||||
("seashell", 0xFFF5EE),
|
||||
("sienna", 0xA0522D),
|
||||
("silver", 0xC0C0C0),
|
||||
("skyblue", 0x87CEEB),
|
||||
("slateblue", 0x6A5ACD),
|
||||
("slategray", 0x708090),
|
||||
("slategrey", 0x708090),
|
||||
("snow", 0xFFFAFA),
|
||||
("springgreen", 0x00FF7F),
|
||||
("steelblue", 0x4682B4),
|
||||
("tan", 0xD2B48C),
|
||||
("teal", 0x008080),
|
||||
("thistle", 0xD8BFD8),
|
||||
("tomato", 0xFF6347),
|
||||
("turquoise", 0x40E0D0),
|
||||
("violet", 0xEE82EE),
|
||||
("wheat", 0xF5DEB3),
|
||||
("white", 0xFFFFFF),
|
||||
("whitesmoke", 0xF5F5F5),
|
||||
("yellow", 0xFFFF00),
|
||||
("yellowgreen", 0x9ACD32),
|
||||
];
|
||||
|
||||
/// Parse a CSS colour into RGBA 0..1 (`transparent` is RGBA 0). `None` for anything else
|
||||
/// (`none`, `currentColor` and `url()` are handled by the caller).
|
||||
pub fn parse_color(s: &str) -> Option<[f64; 4]> {
|
||||
let s = s.trim();
|
||||
let lower = s.to_ascii_lowercase();
|
||||
if lower == "transparent" {
|
||||
return Some([0.0; 4]);
|
||||
}
|
||||
if let Some(h) = lower.strip_prefix('#') {
|
||||
let d: Vec<u32> = h.chars().map(|c| c.to_digit(16)).collect::<Option<_>>()?;
|
||||
let v = |i: usize| d[i] as f64 / 15.0;
|
||||
let w = |i: usize| (d[i] * 16 + d[i + 1]) as f64 / 255.0;
|
||||
return Some(match d.len() {
|
||||
3 => [v(0), v(1), v(2), 1.0],
|
||||
4 => [v(0), v(1), v(2), v(3)],
|
||||
6 => [w(0), w(2), w(4), 1.0],
|
||||
8 => [w(0), w(2), w(4), w(6)],
|
||||
_ => return None,
|
||||
});
|
||||
}
|
||||
if let Some(args) = func(&lower, "rgba").or_else(|| func(&lower, "rgb")) {
|
||||
let a: Vec<&str> = split_args(args);
|
||||
if a.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
let comp = |t: &str| -> Option<f64> {
|
||||
Some(if let Some(p) = t.strip_suffix('%') {
|
||||
p.trim().parse::<f64>().ok().filter(|v| v.is_finite())? / 100.0
|
||||
} else {
|
||||
t.parse::<f64>().ok().filter(|v| v.is_finite())? / 255.0
|
||||
})
|
||||
};
|
||||
let alpha = a.get(3).map_or(Some(1.0), |t| alpha(t))?;
|
||||
return Some([comp(a[0])?.clamp(0.0, 1.0), comp(a[1])?.clamp(0.0, 1.0), comp(a[2])?.clamp(0.0, 1.0), alpha]);
|
||||
}
|
||||
if let Some(args) = func(&lower, "hsla").or_else(|| func(&lower, "hsl")) {
|
||||
let a: Vec<&str> = split_args(args);
|
||||
if a.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
let h = a[0].trim_end_matches("deg").parse::<f64>().ok().filter(|v| v.is_finite())?.rem_euclid(360.0) / 360.0;
|
||||
let s = a[1].trim_end_matches('%').parse::<f64>().ok().filter(|v| v.is_finite())?.clamp(0.0, 100.0) / 100.0;
|
||||
let l = a[2].trim_end_matches('%').parse::<f64>().ok().filter(|v| v.is_finite())?.clamp(0.0, 100.0) / 100.0;
|
||||
let al = a.get(3).map_or(Some(1.0), |t| alpha(t))?;
|
||||
let [r, g, b] = hsl(h, s, l);
|
||||
return Some([r, g, b, al]);
|
||||
}
|
||||
NAMED.iter().find(|(n, _)| *n == lower).map(|(_, v)| [((v >> 16) & 255) as f64 / 255.0, ((v >> 8) & 255) as f64 / 255.0, (v & 255) as f64 / 255.0, 1.0])
|
||||
}
|
||||
|
||||
fn func<'a>(s: &'a str, name: &str) -> Option<&'a str> {
|
||||
s.strip_prefix(name)?.trim_start().strip_prefix('(')?.strip_suffix(')')
|
||||
}
|
||||
|
||||
fn split_args(s: &str) -> Vec<&str> {
|
||||
s.split(|c: char| c == ',' || c == '/' || c.is_whitespace()).filter(|t| !t.is_empty()).collect()
|
||||
}
|
||||
|
||||
fn alpha(t: &str) -> Option<f64> {
|
||||
Some(
|
||||
if let Some(p) = t.strip_suffix('%') {
|
||||
p.parse::<f64>().ok().filter(|v| v.is_finite())? / 100.0
|
||||
} else {
|
||||
t.parse::<f64>().ok().filter(|v| v.is_finite())?
|
||||
}
|
||||
.clamp(0.0, 1.0),
|
||||
)
|
||||
}
|
||||
|
||||
/// CSS HSL → RGB (CSS Color 3 §4.2.4).
|
||||
fn hsl(h: f64, s: f64, l: f64) -> [f64; 3] {
|
||||
let m2 = if l <= 0.5 { l * (s + 1.0) } else { l + s - l * s };
|
||||
let m1 = l * 2.0 - m2;
|
||||
let hue = |mut h: f64| {
|
||||
if h < 0.0 {
|
||||
h += 1.0;
|
||||
}
|
||||
if h > 1.0 {
|
||||
h -= 1.0;
|
||||
}
|
||||
if h * 6.0 < 1.0 {
|
||||
m1 + (m2 - m1) * h * 6.0
|
||||
} else if h * 2.0 < 1.0 {
|
||||
m2
|
||||
} else if h * 3.0 < 2.0 {
|
||||
m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6.0
|
||||
} else {
|
||||
m1
|
||||
}
|
||||
};
|
||||
[hue(h + 1.0 / 3.0), hue(h), hue(h - 1.0 / 3.0)]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::parse_color;
|
||||
|
||||
#[test]
|
||||
/// Non-finite channel values are rejected.
|
||||
fn non_finite_rejected() {
|
||||
for s in [
|
||||
"rgb(nan,0,0)",
|
||||
"rgb(inf,0,0)",
|
||||
"rgba(0,0,0,nan)",
|
||||
"rgba(0,0,0,inf)",
|
||||
"hsl(nan,100%,50%)",
|
||||
"hsl(120,nan%,50%)",
|
||||
"hsl(120,100%,inf%)",
|
||||
"hsl(120,100%,50%,nan)",
|
||||
] {
|
||||
assert_eq!(parse_color(s), None, "{s}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn colors() {
|
||||
assert_eq!(parse_color("#f00"), Some([1.0, 0.0, 0.0, 1.0]));
|
||||
assert_eq!(parse_color("#00ff0080").map(|c| (c[1], (c[3] * 255.0).round())), Some((1.0, 128.0)));
|
||||
assert_eq!(parse_color("rgb(255, 0, 0)"), Some([1.0, 0.0, 0.0, 1.0]));
|
||||
assert_eq!(parse_color("rgba(0,0,255,0.5)"), Some([0.0, 0.0, 1.0, 0.5]));
|
||||
assert_eq!(parse_color("rgb(100%, 50%, 0%)"), Some([1.0, 0.5, 0.0, 1.0]));
|
||||
assert_eq!(parse_color("hsl(120, 100%, 50%)"), Some([0.0, 1.0, 0.0, 1.0]));
|
||||
assert_eq!(parse_color("CornflowerBlue"), Some([100.0 / 255.0, 149.0 / 255.0, 237.0 / 255.0, 1.0]));
|
||||
assert_eq!(parse_color("transparent"), Some([0.0; 4]));
|
||||
assert_eq!(parse_color("nonsense"), None);
|
||||
}
|
||||
}
|
||||
//! CSS colour values (CSS Color Module Level 3): keywords, `#rgb[a]`, `#rrggbb[aa]`, `rgb[a]()`,
|
||||
//! `hsl[a]()`.
|
||||
|
||||
/// The CSS colour keywords (CSS Color Module Level 3, §4.3 extended colour keywords).
|
||||
const NAMED: &[(&str, u32)] = &[
|
||||
("aliceblue", 0xF0F8FF),
|
||||
("antiquewhite", 0xFAEBD7),
|
||||
("aqua", 0x00FFFF),
|
||||
("aquamarine", 0x7FFFD4),
|
||||
("azure", 0xF0FFFF),
|
||||
("beige", 0xF5F5DC),
|
||||
("bisque", 0xFFE4C4),
|
||||
("black", 0x000000),
|
||||
("blanchedalmond", 0xFFEBCD),
|
||||
("blue", 0x0000FF),
|
||||
("blueviolet", 0x8A2BE2),
|
||||
("brown", 0xA52A2A),
|
||||
("burlywood", 0xDEB887),
|
||||
("cadetblue", 0x5F9EA0),
|
||||
("chartreuse", 0x7FFF00),
|
||||
("chocolate", 0xD2691E),
|
||||
("coral", 0xFF7F50),
|
||||
("cornflowerblue", 0x6495ED),
|
||||
("cornsilk", 0xFFF8DC),
|
||||
("crimson", 0xDC143C),
|
||||
("cyan", 0x00FFFF),
|
||||
("darkblue", 0x00008B),
|
||||
("darkcyan", 0x008B8B),
|
||||
("darkgoldenrod", 0xB8860B),
|
||||
("darkgray", 0xA9A9A9),
|
||||
("darkgreen", 0x006400),
|
||||
("darkgrey", 0xA9A9A9),
|
||||
("darkkhaki", 0xBDB76B),
|
||||
("darkmagenta", 0x8B008B),
|
||||
("darkolivegreen", 0x556B2F),
|
||||
("darkorange", 0xFF8C00),
|
||||
("darkorchid", 0x9932CC),
|
||||
("darkred", 0x8B0000),
|
||||
("darksalmon", 0xE9967A),
|
||||
("darkseagreen", 0x8FBC8F),
|
||||
("darkslateblue", 0x483D8B),
|
||||
("darkslategray", 0x2F4F4F),
|
||||
("darkslategrey", 0x2F4F4F),
|
||||
("darkturquoise", 0x00CED1),
|
||||
("darkviolet", 0x9400D3),
|
||||
("deeppink", 0xFF1493),
|
||||
("deepskyblue", 0x00BFFF),
|
||||
("dimgray", 0x696969),
|
||||
("dimgrey", 0x696969),
|
||||
("dodgerblue", 0x1E90FF),
|
||||
("firebrick", 0xB22222),
|
||||
("floralwhite", 0xFFFAF0),
|
||||
("forestgreen", 0x228B22),
|
||||
("fuchsia", 0xFF00FF),
|
||||
("gainsboro", 0xDCDCDC),
|
||||
("ghostwhite", 0xF8F8FF),
|
||||
("gold", 0xFFD700),
|
||||
("goldenrod", 0xDAA520),
|
||||
("gray", 0x808080),
|
||||
("grey", 0x808080),
|
||||
("green", 0x008000),
|
||||
("greenyellow", 0xADFF2F),
|
||||
("honeydew", 0xF0FFF0),
|
||||
("hotpink", 0xFF69B4),
|
||||
("indianred", 0xCD5C5C),
|
||||
("indigo", 0x4B0082),
|
||||
("ivory", 0xFFFFF0),
|
||||
("khaki", 0xF0E68C),
|
||||
("lavender", 0xE6E6FA),
|
||||
("lavenderblush", 0xFFF0F5),
|
||||
("lawngreen", 0x7CFC00),
|
||||
("lemonchiffon", 0xFFFACD),
|
||||
("lightblue", 0xADD8E6),
|
||||
("lightcoral", 0xF08080),
|
||||
("lightcyan", 0xE0FFFF),
|
||||
("lightgoldenrodyellow", 0xFAFAD2),
|
||||
("lightgray", 0xD3D3D3),
|
||||
("lightgreen", 0x90EE90),
|
||||
("lightgrey", 0xD3D3D3),
|
||||
("lightpink", 0xFFB6C1),
|
||||
("lightsalmon", 0xFFA07A),
|
||||
("lightseagreen", 0x20B2AA),
|
||||
("lightskyblue", 0x87CEFA),
|
||||
("lightslategray", 0x778899),
|
||||
("lightslategrey", 0x778899),
|
||||
("lightsteelblue", 0xB0C4DE),
|
||||
("lightyellow", 0xFFFFE0),
|
||||
("lime", 0x00FF00),
|
||||
("limegreen", 0x32CD32),
|
||||
("linen", 0xFAF0E6),
|
||||
("magenta", 0xFF00FF),
|
||||
("maroon", 0x800000),
|
||||
("mediumaquamarine", 0x66CDAA),
|
||||
("mediumblue", 0x0000CD),
|
||||
("mediumorchid", 0xBA55D3),
|
||||
("mediumpurple", 0x9370DB),
|
||||
("mediumseagreen", 0x3CB371),
|
||||
("mediumslateblue", 0x7B68EE),
|
||||
("mediumspringgreen", 0x00FA9A),
|
||||
("mediumturquoise", 0x48D1CC),
|
||||
("mediumvioletred", 0xC71585),
|
||||
("midnightblue", 0x191970),
|
||||
("mintcream", 0xF5FFFA),
|
||||
("mistyrose", 0xFFE4E1),
|
||||
("moccasin", 0xFFE4B5),
|
||||
("navajowhite", 0xFFDEAD),
|
||||
("navy", 0x000080),
|
||||
("oldlace", 0xFDF5E6),
|
||||
("olive", 0x808000),
|
||||
("olivedrab", 0x6B8E23),
|
||||
("orange", 0xFFA500),
|
||||
("orangered", 0xFF4500),
|
||||
("orchid", 0xDA70D6),
|
||||
("palegoldenrod", 0xEEE8AA),
|
||||
("palegreen", 0x98FB98),
|
||||
("paleturquoise", 0xAFEEEE),
|
||||
("palevioletred", 0xDB7093),
|
||||
("papayawhip", 0xFFEFD5),
|
||||
("peachpuff", 0xFFDAB9),
|
||||
("peru", 0xCD853F),
|
||||
("pink", 0xFFC0CB),
|
||||
("plum", 0xDDA0DD),
|
||||
("powderblue", 0xB0E0E6),
|
||||
("purple", 0x800080),
|
||||
("rebeccapurple", 0x663399),
|
||||
("red", 0xFF0000),
|
||||
("rosybrown", 0xBC8F8F),
|
||||
("royalblue", 0x4169E1),
|
||||
("saddlebrown", 0x8B4513),
|
||||
("salmon", 0xFA8072),
|
||||
("sandybrown", 0xF4A460),
|
||||
("seagreen", 0x2E8B57),
|
||||
("seashell", 0xFFF5EE),
|
||||
("sienna", 0xA0522D),
|
||||
("silver", 0xC0C0C0),
|
||||
("skyblue", 0x87CEEB),
|
||||
("slateblue", 0x6A5ACD),
|
||||
("slategray", 0x708090),
|
||||
("slategrey", 0x708090),
|
||||
("snow", 0xFFFAFA),
|
||||
("springgreen", 0x00FF7F),
|
||||
("steelblue", 0x4682B4),
|
||||
("tan", 0xD2B48C),
|
||||
("teal", 0x008080),
|
||||
("thistle", 0xD8BFD8),
|
||||
("tomato", 0xFF6347),
|
||||
("turquoise", 0x40E0D0),
|
||||
("violet", 0xEE82EE),
|
||||
("wheat", 0xF5DEB3),
|
||||
("white", 0xFFFFFF),
|
||||
("whitesmoke", 0xF5F5F5),
|
||||
("yellow", 0xFFFF00),
|
||||
("yellowgreen", 0x9ACD32),
|
||||
];
|
||||
|
||||
/// Parse a CSS colour into RGBA 0..1 (`transparent` is RGBA 0). `None` for anything else
|
||||
/// (`none`, `currentColor` and `url()` are handled by the caller).
|
||||
pub fn parse_color(s: &str) -> Option<[f64; 4]> {
|
||||
let s = s.trim();
|
||||
let lower = s.to_ascii_lowercase();
|
||||
if lower == "transparent" {
|
||||
return Some([0.0; 4]);
|
||||
}
|
||||
if let Some(h) = lower.strip_prefix('#') {
|
||||
let d: Vec<u32> = h.chars().map(|c| c.to_digit(16)).collect::<Option<_>>()?;
|
||||
let v = |i: usize| d[i] as f64 / 15.0;
|
||||
let w = |i: usize| (d[i] * 16 + d[i + 1]) as f64 / 255.0;
|
||||
return Some(match d.len() {
|
||||
3 => [v(0), v(1), v(2), 1.0],
|
||||
4 => [v(0), v(1), v(2), v(3)],
|
||||
6 => [w(0), w(2), w(4), 1.0],
|
||||
8 => [w(0), w(2), w(4), w(6)],
|
||||
_ => return None,
|
||||
});
|
||||
}
|
||||
if let Some(args) = func(&lower, "rgba").or_else(|| func(&lower, "rgb")) {
|
||||
let a: Vec<&str> = split_args(args);
|
||||
if a.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
let comp = |t: &str| -> Option<f64> {
|
||||
Some(if let Some(p) = t.strip_suffix('%') {
|
||||
p.trim().parse::<f64>().ok().filter(|v| v.is_finite())? / 100.0
|
||||
} else {
|
||||
t.parse::<f64>().ok().filter(|v| v.is_finite())? / 255.0
|
||||
})
|
||||
};
|
||||
let alpha = a.get(3).map_or(Some(1.0), |t| alpha(t))?;
|
||||
return Some([comp(a[0])?.clamp(0.0, 1.0), comp(a[1])?.clamp(0.0, 1.0), comp(a[2])?.clamp(0.0, 1.0), alpha]);
|
||||
}
|
||||
if let Some(args) = func(&lower, "hsla").or_else(|| func(&lower, "hsl")) {
|
||||
let a: Vec<&str> = split_args(args);
|
||||
if a.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
let h = a[0].trim_end_matches("deg").parse::<f64>().ok().filter(|v| v.is_finite())?.rem_euclid(360.0) / 360.0;
|
||||
let s = a[1].trim_end_matches('%').parse::<f64>().ok().filter(|v| v.is_finite())?.clamp(0.0, 100.0) / 100.0;
|
||||
let l = a[2].trim_end_matches('%').parse::<f64>().ok().filter(|v| v.is_finite())?.clamp(0.0, 100.0) / 100.0;
|
||||
let al = a.get(3).map_or(Some(1.0), |t| alpha(t))?;
|
||||
let [r, g, b] = hsl(h, s, l);
|
||||
return Some([r, g, b, al]);
|
||||
}
|
||||
NAMED.iter().find(|(n, _)| *n == lower).map(|(_, v)| [((v >> 16) & 255) as f64 / 255.0, ((v >> 8) & 255) as f64 / 255.0, (v & 255) as f64 / 255.0, 1.0])
|
||||
}
|
||||
|
||||
fn func<'a>(s: &'a str, name: &str) -> Option<&'a str> {
|
||||
s.strip_prefix(name)?.trim_start().strip_prefix('(')?.strip_suffix(')')
|
||||
}
|
||||
|
||||
fn split_args(s: &str) -> Vec<&str> {
|
||||
s.split(|c: char| c == ',' || c == '/' || c.is_whitespace()).filter(|t| !t.is_empty()).collect()
|
||||
}
|
||||
|
||||
fn alpha(t: &str) -> Option<f64> {
|
||||
Some(
|
||||
if let Some(p) = t.strip_suffix('%') {
|
||||
p.parse::<f64>().ok().filter(|v| v.is_finite())? / 100.0
|
||||
} else {
|
||||
t.parse::<f64>().ok().filter(|v| v.is_finite())?
|
||||
}
|
||||
.clamp(0.0, 1.0),
|
||||
)
|
||||
}
|
||||
|
||||
/// CSS HSL → RGB (CSS Color 3 §4.2.4).
|
||||
fn hsl(h: f64, s: f64, l: f64) -> [f64; 3] {
|
||||
let m2 = if l <= 0.5 { l * (s + 1.0) } else { l + s - l * s };
|
||||
let m1 = l * 2.0 - m2;
|
||||
let hue = |mut h: f64| {
|
||||
if h < 0.0 {
|
||||
h += 1.0;
|
||||
}
|
||||
if h > 1.0 {
|
||||
h -= 1.0;
|
||||
}
|
||||
if h * 6.0 < 1.0 {
|
||||
m1 + (m2 - m1) * h * 6.0
|
||||
} else if h * 2.0 < 1.0 {
|
||||
m2
|
||||
} else if h * 3.0 < 2.0 {
|
||||
m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6.0
|
||||
} else {
|
||||
m1
|
||||
}
|
||||
};
|
||||
[hue(h + 1.0 / 3.0), hue(h), hue(h - 1.0 / 3.0)]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::parse_color;
|
||||
|
||||
#[test]
|
||||
/// Non-finite channel values are rejected.
|
||||
fn non_finite_rejected() {
|
||||
for s in [
|
||||
"rgb(nan,0,0)",
|
||||
"rgb(inf,0,0)",
|
||||
"rgba(0,0,0,nan)",
|
||||
"rgba(0,0,0,inf)",
|
||||
"hsl(nan,100%,50%)",
|
||||
"hsl(120,nan%,50%)",
|
||||
"hsl(120,100%,inf%)",
|
||||
"hsl(120,100%,50%,nan)",
|
||||
] {
|
||||
assert_eq!(parse_color(s), None, "{s}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn colors() {
|
||||
assert_eq!(parse_color("#f00"), Some([1.0, 0.0, 0.0, 1.0]));
|
||||
assert_eq!(parse_color("#00ff0080").map(|c| (c[1], (c[3] * 255.0).round())), Some((1.0, 128.0)));
|
||||
assert_eq!(parse_color("rgb(255, 0, 0)"), Some([1.0, 0.0, 0.0, 1.0]));
|
||||
assert_eq!(parse_color("rgba(0,0,255,0.5)"), Some([0.0, 0.0, 1.0, 0.5]));
|
||||
assert_eq!(parse_color("rgb(100%, 50%, 0%)"), Some([1.0, 0.5, 0.0, 1.0]));
|
||||
assert_eq!(parse_color("hsl(120, 100%, 50%)"), Some([0.0, 1.0, 0.0, 1.0]));
|
||||
assert_eq!(parse_color("CornflowerBlue"), Some([100.0 / 255.0, 149.0 / 255.0, 237.0 / 255.0, 1.0]));
|
||||
assert_eq!(parse_color("transparent"), Some([0.0; 4]));
|
||||
assert_eq!(parse_color("nonsense"), None);
|
||||
}
|
||||
}
|
||||
+1852
-1852
File diff suppressed because it is too large.
Load diff
+435
-435
@@ -1,435 +1,435 @@
|
||||
//! SVG path data (SVG 1.1 §8.3 / SVG 2 §9.3): every command, implicit repeats, relative
|
||||
//! coordinates, smooth curves and elliptical arcs (converted to cubics through kurbo's `SvgArc`).
|
||||
//! Parsing stops at the first error, keeping what was read (as the specification asks).
|
||||
|
||||
use kurbo::{BezPath, PathEl, Point, SvgArc, Vec2};
|
||||
|
||||
/// Maximum path elements retained from one SVG path or point list.
|
||||
pub(crate) const MAX_PATH_ELEMENTS: usize = 100_000;
|
||||
|
||||
/// Tokenizer over numbers and command letters.
|
||||
struct Lex<'a> {
|
||||
b: &'a [u8],
|
||||
i: usize,
|
||||
}
|
||||
|
||||
impl Lex<'_> {
|
||||
fn skip_sep(&mut self) {
|
||||
while self.i < self.b.len() && (self.b[self.i].is_ascii_whitespace() || self.b[self.i] == b',') {
|
||||
self.i += 1;
|
||||
}
|
||||
}
|
||||
fn command(&mut self) -> Option<u8> {
|
||||
self.skip_sep();
|
||||
let c = *self.b.get(self.i)?;
|
||||
if c.is_ascii_alphabetic() && c != b'e' && c != b'E' {
|
||||
self.i += 1;
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn at_number(&mut self) -> bool {
|
||||
self.skip_sep();
|
||||
self.b.get(self.i).is_some_and(|c| c.is_ascii_digit() || matches!(c, b'-' | b'+' | b'.'))
|
||||
}
|
||||
fn number(&mut self) -> Option<f64> {
|
||||
self.skip_sep();
|
||||
let start = self.i;
|
||||
let b = self.b;
|
||||
let mut i = self.i;
|
||||
if i < b.len() && matches!(b[i], b'-' | b'+') {
|
||||
i += 1;
|
||||
}
|
||||
let mut digits = false;
|
||||
while i < b.len() && b[i].is_ascii_digit() {
|
||||
i += 1;
|
||||
digits = true;
|
||||
}
|
||||
if i < b.len() && b[i] == b'.' {
|
||||
i += 1;
|
||||
while i < b.len() && b[i].is_ascii_digit() {
|
||||
i += 1;
|
||||
digits = true;
|
||||
}
|
||||
}
|
||||
if !digits {
|
||||
return None;
|
||||
}
|
||||
if i < b.len() && matches!(b[i], b'e' | b'E') {
|
||||
let mut j = i + 1;
|
||||
if j < b.len() && matches!(b[j], b'-' | b'+') {
|
||||
j += 1;
|
||||
}
|
||||
if j < b.len() && b[j].is_ascii_digit() {
|
||||
while j < b.len() && b[j].is_ascii_digit() {
|
||||
j += 1;
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
self.i = i;
|
||||
let number = std::str::from_utf8(&b[start..i]).ok()?.parse::<f64>().ok()?;
|
||||
number.is_finite().then_some(number)
|
||||
}
|
||||
/// Arc flags may be written without separators ("a1 1 0 00 1 1").
|
||||
fn flag(&mut self) -> Option<bool> {
|
||||
self.skip_sep();
|
||||
let c = *self.b.get(self.i)?;
|
||||
self.i += 1;
|
||||
match c {
|
||||
b'0' => Some(false),
|
||||
b'1' => Some(true),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse no more than `max_values`; the flag reports whether another valid value was present.
|
||||
pub(crate) fn numbers_bounded(s: &str, max_values: usize) -> (Vec<f64>, bool) {
|
||||
let mut l = Lex { b: s.as_bytes(), i: 0 };
|
||||
let mut v = vec![];
|
||||
while l.at_number() {
|
||||
if v.len() >= max_values {
|
||||
return (v, true);
|
||||
}
|
||||
match l.number() {
|
||||
Some(x) => v.push(x),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
(v, false)
|
||||
}
|
||||
|
||||
/// Parse path data into a kurbo path, retaining at most [`MAX_PATH_ELEMENTS`] elements.
|
||||
/// Malformed or overlong input returns the prefix parsed before the problem.
|
||||
pub fn parse_path_data(d: &str) -> BezPath {
|
||||
parse_path_data_bounded(d, MAX_PATH_ELEMENTS).0
|
||||
}
|
||||
|
||||
/// Pen position and reflection state while parsing path commands.
|
||||
struct PathState {
|
||||
/// Current point.
|
||||
current: Point,
|
||||
/// Start of the current subpath.
|
||||
start: Point,
|
||||
/// Last cubic control point, for smooth curves.
|
||||
last_cubic: Option<Point>,
|
||||
/// Last quadratic control point, for smooth curves.
|
||||
last_quad: Option<Point>,
|
||||
/// Whether a subpath is open.
|
||||
open: bool,
|
||||
}
|
||||
|
||||
/// Cap on path elements and whether it has been hit.
|
||||
struct PathLimit {
|
||||
/// Maximum number of path elements.
|
||||
max_elements: usize,
|
||||
/// Set when the cap or a non-finite coordinate stopped output.
|
||||
exceeded: bool,
|
||||
}
|
||||
|
||||
/// Parse at most `max_elements`, reporting when more path geometry was present.
|
||||
pub(crate) fn parse_path_data_bounded(d: &str, max_elements: usize) -> (BezPath, bool) {
|
||||
let mut l = Lex { b: d.as_bytes(), i: 0 };
|
||||
let mut p = BezPath::new();
|
||||
let mut state = PathState { current: Point::ZERO, start: Point::ZERO, last_cubic: None, last_quad: None, open: false };
|
||||
let mut limit = PathLimit { max_elements, exceeded: false };
|
||||
let mut cmd: Option<u8> = None;
|
||||
loop {
|
||||
if p.elements().len() >= limit.max_elements {
|
||||
l.skip_sep();
|
||||
limit.exceeded = l.i < l.b.len();
|
||||
break;
|
||||
}
|
||||
let c = match l.command() {
|
||||
Some(c) => c,
|
||||
None => match cmd {
|
||||
// Implicit repeat (a moveto repeats as lineto). Z takes no numbers, so a number
|
||||
// after it is an error: repeating Z would loop forever without consuming input.
|
||||
Some(c) if l.at_number() && !c.eq_ignore_ascii_case(&b'z') => match c {
|
||||
b'M' => b'L',
|
||||
b'm' => b'l',
|
||||
other => other,
|
||||
},
|
||||
_ => break,
|
||||
},
|
||||
};
|
||||
let rel = c.is_ascii_lowercase();
|
||||
let base = if rel { state.current.to_vec2() } else { Vec2::ZERO };
|
||||
let pt = |l: &mut Lex| -> Option<Point> { Some(Point::new(l.number()?, l.number()?) + base) };
|
||||
let ok = (|| -> Option<()> {
|
||||
match c.to_ascii_uppercase() {
|
||||
b'M' => {
|
||||
state.current = pt(&mut l)?;
|
||||
if !push_element(&mut p, PathEl::MoveTo(state.current), &mut limit) {
|
||||
return None;
|
||||
}
|
||||
state.start = state.current;
|
||||
state.open = true;
|
||||
state.last_cubic = None;
|
||||
state.last_quad = None;
|
||||
}
|
||||
b'Z' => {
|
||||
if state.open && !push_element(&mut p, PathEl::ClosePath, &mut limit) {
|
||||
return None;
|
||||
}
|
||||
state.current = state.start;
|
||||
state.last_cubic = None;
|
||||
state.last_quad = None;
|
||||
}
|
||||
_ if !state.open => {
|
||||
// A segment without a preceding moveto starts at the current point.
|
||||
if !push_element(&mut p, PathEl::MoveTo(state.current), &mut limit) {
|
||||
return None;
|
||||
}
|
||||
state.open = true;
|
||||
return segment(&mut l, c, base, &mut p, &mut state, &mut limit);
|
||||
}
|
||||
_ => return segment(&mut l, c, base, &mut p, &mut state, &mut limit),
|
||||
}
|
||||
Some(())
|
||||
})();
|
||||
if ok.is_none() {
|
||||
break;
|
||||
}
|
||||
if limit.exceeded {
|
||||
break;
|
||||
}
|
||||
cmd = Some(c);
|
||||
if c.eq_ignore_ascii_case(&b'z') {
|
||||
// After Z a new segment starts from the subpath start (an unused move is dropped
|
||||
// below).
|
||||
if !push_element(&mut p, PathEl::MoveTo(state.start), &mut limit) {
|
||||
break;
|
||||
}
|
||||
state.open = true;
|
||||
}
|
||||
}
|
||||
(strip_trailing_moves(p), limit.exceeded)
|
||||
}
|
||||
|
||||
/// Push an element if finite and under the cap; otherwise mark the limit exceeded and return false.
|
||||
fn push_element(p: &mut BezPath, element: PathEl, limit: &mut PathLimit) -> bool {
|
||||
let finite = match element {
|
||||
PathEl::MoveTo(point) | PathEl::LineTo(point) => point.x.is_finite() && point.y.is_finite(),
|
||||
PathEl::QuadTo(control, point) => [control.x, control.y, point.x, point.y].iter().all(|value| value.is_finite()),
|
||||
PathEl::CurveTo(first, second, point) => [first.x, first.y, second.x, second.y, point.x, point.y].iter().all(|value| value.is_finite()),
|
||||
PathEl::ClosePath => true,
|
||||
};
|
||||
if p.elements().len() >= limit.max_elements || !finite {
|
||||
limit.exceeded = true;
|
||||
return false;
|
||||
}
|
||||
p.push(element);
|
||||
true
|
||||
}
|
||||
|
||||
/// Parse one path command's arguments into `p`, returning `None` on malformed or over-limit input.
|
||||
fn segment(l: &mut Lex, c: u8, base: Vec2, p: &mut BezPath, state: &mut PathState, limit: &mut PathLimit) -> Option<()> {
|
||||
let num = |l: &mut Lex| l.number();
|
||||
let pt = |l: &mut Lex| -> Option<Point> { Some(Point::new(l.number()?, l.number()?) + base) };
|
||||
match c.to_ascii_uppercase() {
|
||||
b'L' => {
|
||||
let current = pt(l)?;
|
||||
if !push_element(p, PathEl::LineTo(current), limit) {
|
||||
return None;
|
||||
}
|
||||
state.current = current;
|
||||
state.last_cubic = None;
|
||||
state.last_quad = None;
|
||||
}
|
||||
b'H' => {
|
||||
let x = num(l)? + base.x;
|
||||
let current = Point::new(x, state.current.y);
|
||||
if !push_element(p, PathEl::LineTo(current), limit) {
|
||||
return None;
|
||||
}
|
||||
state.current = current;
|
||||
state.last_cubic = None;
|
||||
state.last_quad = None;
|
||||
}
|
||||
b'V' => {
|
||||
let y = num(l)? + base.y;
|
||||
let current = Point::new(state.current.x, y);
|
||||
if !push_element(p, PathEl::LineTo(current), limit) {
|
||||
return None;
|
||||
}
|
||||
state.current = current;
|
||||
state.last_cubic = None;
|
||||
state.last_quad = None;
|
||||
}
|
||||
b'C' => {
|
||||
let (c1, c2, e) = (pt(l)?, pt(l)?, pt(l)?);
|
||||
if !push_element(p, PathEl::CurveTo(c1, c2, e), limit) {
|
||||
return None;
|
||||
}
|
||||
state.last_cubic = Some(c2);
|
||||
state.last_quad = None;
|
||||
state.current = e;
|
||||
}
|
||||
b'S' => {
|
||||
let c1 = state.last_cubic.map_or(state.current, |c| state.current + (state.current - c));
|
||||
let (c2, e) = (pt(l)?, pt(l)?);
|
||||
if !push_element(p, PathEl::CurveTo(c1, c2, e), limit) {
|
||||
return None;
|
||||
}
|
||||
state.last_cubic = Some(c2);
|
||||
state.last_quad = None;
|
||||
state.current = e;
|
||||
}
|
||||
b'Q' => {
|
||||
let (q, e) = (pt(l)?, pt(l)?);
|
||||
if !push_element(p, PathEl::QuadTo(q, e), limit) {
|
||||
return None;
|
||||
}
|
||||
state.last_quad = Some(q);
|
||||
state.last_cubic = None;
|
||||
state.current = e;
|
||||
}
|
||||
b'T' => {
|
||||
let q = state.last_quad.map_or(state.current, |c| state.current + (state.current - c));
|
||||
let e = pt(l)?;
|
||||
if !push_element(p, PathEl::QuadTo(q, e), limit) {
|
||||
return None;
|
||||
}
|
||||
state.last_quad = Some(q);
|
||||
state.last_cubic = None;
|
||||
state.current = e;
|
||||
}
|
||||
b'A' => {
|
||||
let rx = num(l)?.abs();
|
||||
let ry = num(l)?.abs();
|
||||
let rot = num(l)?;
|
||||
let large = l.flag()?;
|
||||
let sweep = l.flag()?;
|
||||
let e = pt(l)?;
|
||||
let arc = SvgArc { from: state.current, to: e, radii: Vec2::new(rx, ry), x_rotation: rot.to_radians(), large_arc: large, sweep };
|
||||
let mut last_end = None;
|
||||
if let Some(a) = kurbo::Arc::from_svg_arc(&arc) {
|
||||
// Scale tolerance with the radius so the segment count stays bounded.
|
||||
let tol = 0.1_f64.max(a.radii.x.max(a.radii.y) * 1e-4);
|
||||
a.to_cubic_beziers(tol, |c1, c2, end| {
|
||||
if limit.exceeded {
|
||||
return;
|
||||
}
|
||||
if push_element(p, PathEl::CurveTo(c1, c2, end), limit) {
|
||||
last_end = Some(end);
|
||||
}
|
||||
});
|
||||
}
|
||||
if limit.exceeded {
|
||||
return None;
|
||||
}
|
||||
// Degenerate arcs (e.g. huge radii) may emit nothing or stop short of the end point.
|
||||
if last_end.is_none_or(|q| (q - e).hypot() > 1e-3 * (1.0 + e.to_vec2().hypot())) && !push_element(p, PathEl::LineTo(e), limit) {
|
||||
return None;
|
||||
}
|
||||
state.current = e;
|
||||
state.last_cubic = None;
|
||||
state.last_quad = None;
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Drop move-tos that start no segment (from closepath bookkeeping).
|
||||
fn strip_trailing_moves(p: BezPath) -> BezPath {
|
||||
use kurbo::PathEl;
|
||||
let els = p.elements();
|
||||
let mut out = BezPath::new();
|
||||
for (i, e) in els.iter().enumerate() {
|
||||
if let PathEl::MoveTo(_) = e {
|
||||
let next_is_seg = els.get(i + 1).is_some_and(|n| !matches!(n, PathEl::MoveTo(_)));
|
||||
if !next_is_seg {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
out.push(*e);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use kurbo::PathEl;
|
||||
|
||||
#[test]
|
||||
/// Huge arc radii stay bounded.
|
||||
fn huge_arc_radius_stays_bounded() {
|
||||
let p = parse_path_data("M0 0A1e36 1e36 0 1 1 1e30 0");
|
||||
assert!(p.elements().len() < 1000, "got {} elements", p.elements().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commands() {
|
||||
let p = parse_path_data("M10,10 L20,10 h10 v10 H10 z m5 5 l1-1.5.5.5e1 C 0 0 1 1 2 2 s3 3 4 4 Q1 1 2 2 t3 3 A5 5 0 0 1 20 20 a5 5 0 1010 0");
|
||||
let els = p.elements();
|
||||
assert_eq!(els[0], PathEl::MoveTo(Point::new(10.0, 10.0)));
|
||||
assert_eq!(els[2], PathEl::LineTo(Point::new(30.0, 10.0)));
|
||||
assert_eq!(els[3], PathEl::LineTo(Point::new(30.0, 20.0)));
|
||||
assert!(els.iter().any(|e| matches!(e, PathEl::ClosePath)));
|
||||
// Relative move after Z is relative to the subpath start (10,10).
|
||||
assert!(els.contains(&PathEl::MoveTo(Point::new(15.0, 15.0))));
|
||||
// "l1-1.5.5.5e1": two implicit linetos (1,-1.5) then (.5,5).
|
||||
assert!(els.contains(&PathEl::LineTo(Point::new(16.0, 13.5))));
|
||||
assert!(els.contains(&PathEl::LineTo(Point::new(16.5, 18.5))));
|
||||
assert!(els.iter().filter(|e| matches!(e, PathEl::CurveTo(..))).count() >= 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_keep_prefix() {
|
||||
let p = parse_path_data("M0 0 L10 0 L10 x10 L0 10");
|
||||
assert_eq!(p.elements().len(), 2);
|
||||
assert!(parse_path_data("garbage").elements().is_empty());
|
||||
assert!(parse_path_data("").elements().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_after_close_stops_instead_of_looping() {
|
||||
// "Z9": Z was repeated implicitly without consuming the number, appending close-paths
|
||||
// until memory ran out.
|
||||
let p = parse_path_data("M0 0 L10 0 L10 10 Z9 C1 2 3 4 5 6");
|
||||
assert_eq!(p.elements().len(), 4);
|
||||
assert!(parse_path_data("M0 0 L10 0 z 9 9").elements().len() <= 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Huge-radius arcs still reach their end point.
|
||||
fn huge_radius_arc_still_reaches_end_point() {
|
||||
for d in ["M0 0 A1e20 1e20 0 0 1 10 0 L10 10", "M0 0 A1e20 1e20 0 1 1 10 0 L10 10", "M0 0 a1e20 1e20 0 0 1 10 0 L10 10"] {
|
||||
let p = parse_path_data(d);
|
||||
let reaches = p.elements().iter().any(|e| match e {
|
||||
PathEl::LineTo(q) | PathEl::CurveTo(_, _, q) => (q.x - 10.0).abs() < 1e-6 && q.y.abs() < 1e-6,
|
||||
_ => false,
|
||||
});
|
||||
assert!(reaches, "{d}: {:?}", p.elements());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_lists() {
|
||||
assert_eq!(numbers_bounded("0 0,100 50", 8).0, vec![0.0, 0.0, 100.0, 50.0]);
|
||||
assert_eq!(numbers_bounded("-1-2.5.5", 8).0, vec![-1.0, -2.5, 0.5]);
|
||||
assert_eq!(numbers_bounded("1e2,2E-1", 8).0, vec![100.0, 0.2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// The element cap stops expansion and reports truncation.
|
||||
fn path_element_limit_stops_expansion_and_reports_truncation() {
|
||||
let data = format!("M0 0{}", "h1".repeat(MAX_PATH_ELEMENTS + 1));
|
||||
let (path, exceeded) = parse_path_data_bounded(&data, 32);
|
||||
assert!(exceeded);
|
||||
assert!(path.elements().len() <= 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// The number list cap stops retaining values.
|
||||
fn number_list_limit_stops_retaining_values() {
|
||||
let (values, exceeded) = numbers_bounded("1 2 3 4", 3);
|
||||
assert_eq!(values, [1.0, 2.0, 3.0]);
|
||||
assert!(exceeded);
|
||||
}
|
||||
}
|
||||
//! SVG path data (SVG 1.1 §8.3 / SVG 2 §9.3): every command, implicit repeats, relative
|
||||
//! coordinates, smooth curves and elliptical arcs (converted to cubics through kurbo's `SvgArc`).
|
||||
//! Parsing stops at the first error, keeping what was read (as the specification asks).
|
||||
|
||||
use kurbo::{BezPath, PathEl, Point, SvgArc, Vec2};
|
||||
|
||||
/// Maximum path elements retained from one SVG path or point list.
|
||||
pub(crate) const MAX_PATH_ELEMENTS: usize = 100_000;
|
||||
|
||||
/// Tokenizer over numbers and command letters.
|
||||
struct Lex<'a> {
|
||||
b: &'a [u8],
|
||||
i: usize,
|
||||
}
|
||||
|
||||
impl Lex<'_> {
|
||||
fn skip_sep(&mut self) {
|
||||
while self.i < self.b.len() && (self.b[self.i].is_ascii_whitespace() || self.b[self.i] == b',') {
|
||||
self.i += 1;
|
||||
}
|
||||
}
|
||||
fn command(&mut self) -> Option<u8> {
|
||||
self.skip_sep();
|
||||
let c = *self.b.get(self.i)?;
|
||||
if c.is_ascii_alphabetic() && c != b'e' && c != b'E' {
|
||||
self.i += 1;
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn at_number(&mut self) -> bool {
|
||||
self.skip_sep();
|
||||
self.b.get(self.i).is_some_and(|c| c.is_ascii_digit() || matches!(c, b'-' | b'+' | b'.'))
|
||||
}
|
||||
fn number(&mut self) -> Option<f64> {
|
||||
self.skip_sep();
|
||||
let start = self.i;
|
||||
let b = self.b;
|
||||
let mut i = self.i;
|
||||
if i < b.len() && matches!(b[i], b'-' | b'+') {
|
||||
i += 1;
|
||||
}
|
||||
let mut digits = false;
|
||||
while i < b.len() && b[i].is_ascii_digit() {
|
||||
i += 1;
|
||||
digits = true;
|
||||
}
|
||||
if i < b.len() && b[i] == b'.' {
|
||||
i += 1;
|
||||
while i < b.len() && b[i].is_ascii_digit() {
|
||||
i += 1;
|
||||
digits = true;
|
||||
}
|
||||
}
|
||||
if !digits {
|
||||
return None;
|
||||
}
|
||||
if i < b.len() && matches!(b[i], b'e' | b'E') {
|
||||
let mut j = i + 1;
|
||||
if j < b.len() && matches!(b[j], b'-' | b'+') {
|
||||
j += 1;
|
||||
}
|
||||
if j < b.len() && b[j].is_ascii_digit() {
|
||||
while j < b.len() && b[j].is_ascii_digit() {
|
||||
j += 1;
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
self.i = i;
|
||||
let number = std::str::from_utf8(&b[start..i]).ok()?.parse::<f64>().ok()?;
|
||||
number.is_finite().then_some(number)
|
||||
}
|
||||
/// Arc flags may be written without separators ("a1 1 0 00 1 1").
|
||||
fn flag(&mut self) -> Option<bool> {
|
||||
self.skip_sep();
|
||||
let c = *self.b.get(self.i)?;
|
||||
self.i += 1;
|
||||
match c {
|
||||
b'0' => Some(false),
|
||||
b'1' => Some(true),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse no more than `max_values`; the flag reports whether another valid value was present.
|
||||
pub(crate) fn numbers_bounded(s: &str, max_values: usize) -> (Vec<f64>, bool) {
|
||||
let mut l = Lex { b: s.as_bytes(), i: 0 };
|
||||
let mut v = vec![];
|
||||
while l.at_number() {
|
||||
if v.len() >= max_values {
|
||||
return (v, true);
|
||||
}
|
||||
match l.number() {
|
||||
Some(x) => v.push(x),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
(v, false)
|
||||
}
|
||||
|
||||
/// Parse path data into a kurbo path, retaining at most [`MAX_PATH_ELEMENTS`] elements.
|
||||
/// Malformed or overlong input returns the prefix parsed before the problem.
|
||||
pub fn parse_path_data(d: &str) -> BezPath {
|
||||
parse_path_data_bounded(d, MAX_PATH_ELEMENTS).0
|
||||
}
|
||||
|
||||
/// Pen position and reflection state while parsing path commands.
|
||||
struct PathState {
|
||||
/// Current point.
|
||||
current: Point,
|
||||
/// Start of the current subpath.
|
||||
start: Point,
|
||||
/// Last cubic control point, for smooth curves.
|
||||
last_cubic: Option<Point>,
|
||||
/// Last quadratic control point, for smooth curves.
|
||||
last_quad: Option<Point>,
|
||||
/// Whether a subpath is open.
|
||||
open: bool,
|
||||
}
|
||||
|
||||
/// Cap on path elements and whether it has been hit.
|
||||
struct PathLimit {
|
||||
/// Maximum number of path elements.
|
||||
max_elements: usize,
|
||||
/// Set when the cap or a non-finite coordinate stopped output.
|
||||
exceeded: bool,
|
||||
}
|
||||
|
||||
/// Parse at most `max_elements`, reporting when more path geometry was present.
|
||||
pub(crate) fn parse_path_data_bounded(d: &str, max_elements: usize) -> (BezPath, bool) {
|
||||
let mut l = Lex { b: d.as_bytes(), i: 0 };
|
||||
let mut p = BezPath::new();
|
||||
let mut state = PathState { current: Point::ZERO, start: Point::ZERO, last_cubic: None, last_quad: None, open: false };
|
||||
let mut limit = PathLimit { max_elements, exceeded: false };
|
||||
let mut cmd: Option<u8> = None;
|
||||
loop {
|
||||
if p.elements().len() >= limit.max_elements {
|
||||
l.skip_sep();
|
||||
limit.exceeded = l.i < l.b.len();
|
||||
break;
|
||||
}
|
||||
let c = match l.command() {
|
||||
Some(c) => c,
|
||||
None => match cmd {
|
||||
// Implicit repeat (a moveto repeats as lineto). Z takes no numbers, so a number
|
||||
// after it is an error: repeating Z would loop forever without consuming input.
|
||||
Some(c) if l.at_number() && !c.eq_ignore_ascii_case(&b'z') => match c {
|
||||
b'M' => b'L',
|
||||
b'm' => b'l',
|
||||
other => other,
|
||||
},
|
||||
_ => break,
|
||||
},
|
||||
};
|
||||
let rel = c.is_ascii_lowercase();
|
||||
let base = if rel { state.current.to_vec2() } else { Vec2::ZERO };
|
||||
let pt = |l: &mut Lex| -> Option<Point> { Some(Point::new(l.number()?, l.number()?) + base) };
|
||||
let ok = (|| -> Option<()> {
|
||||
match c.to_ascii_uppercase() {
|
||||
b'M' => {
|
||||
state.current = pt(&mut l)?;
|
||||
if !push_element(&mut p, PathEl::MoveTo(state.current), &mut limit) {
|
||||
return None;
|
||||
}
|
||||
state.start = state.current;
|
||||
state.open = true;
|
||||
state.last_cubic = None;
|
||||
state.last_quad = None;
|
||||
}
|
||||
b'Z' => {
|
||||
if state.open && !push_element(&mut p, PathEl::ClosePath, &mut limit) {
|
||||
return None;
|
||||
}
|
||||
state.current = state.start;
|
||||
state.last_cubic = None;
|
||||
state.last_quad = None;
|
||||
}
|
||||
_ if !state.open => {
|
||||
// A segment without a preceding moveto starts at the current point.
|
||||
if !push_element(&mut p, PathEl::MoveTo(state.current), &mut limit) {
|
||||
return None;
|
||||
}
|
||||
state.open = true;
|
||||
return segment(&mut l, c, base, &mut p, &mut state, &mut limit);
|
||||
}
|
||||
_ => return segment(&mut l, c, base, &mut p, &mut state, &mut limit),
|
||||
}
|
||||
Some(())
|
||||
})();
|
||||
if ok.is_none() {
|
||||
break;
|
||||
}
|
||||
if limit.exceeded {
|
||||
break;
|
||||
}
|
||||
cmd = Some(c);
|
||||
if c.eq_ignore_ascii_case(&b'z') {
|
||||
// After Z a new segment starts from the subpath start (an unused move is dropped
|
||||
// below).
|
||||
if !push_element(&mut p, PathEl::MoveTo(state.start), &mut limit) {
|
||||
break;
|
||||
}
|
||||
state.open = true;
|
||||
}
|
||||
}
|
||||
(strip_trailing_moves(p), limit.exceeded)
|
||||
}
|
||||
|
||||
/// Push an element if finite and under the cap; otherwise mark the limit exceeded and return false.
|
||||
fn push_element(p: &mut BezPath, element: PathEl, limit: &mut PathLimit) -> bool {
|
||||
let finite = match element {
|
||||
PathEl::MoveTo(point) | PathEl::LineTo(point) => point.x.is_finite() && point.y.is_finite(),
|
||||
PathEl::QuadTo(control, point) => [control.x, control.y, point.x, point.y].iter().all(|value| value.is_finite()),
|
||||
PathEl::CurveTo(first, second, point) => [first.x, first.y, second.x, second.y, point.x, point.y].iter().all(|value| value.is_finite()),
|
||||
PathEl::ClosePath => true,
|
||||
};
|
||||
if p.elements().len() >= limit.max_elements || !finite {
|
||||
limit.exceeded = true;
|
||||
return false;
|
||||
}
|
||||
p.push(element);
|
||||
true
|
||||
}
|
||||
|
||||
/// Parse one path command's arguments into `p`, returning `None` on malformed or over-limit input.
|
||||
fn segment(l: &mut Lex, c: u8, base: Vec2, p: &mut BezPath, state: &mut PathState, limit: &mut PathLimit) -> Option<()> {
|
||||
let num = |l: &mut Lex| l.number();
|
||||
let pt = |l: &mut Lex| -> Option<Point> { Some(Point::new(l.number()?, l.number()?) + base) };
|
||||
match c.to_ascii_uppercase() {
|
||||
b'L' => {
|
||||
let current = pt(l)?;
|
||||
if !push_element(p, PathEl::LineTo(current), limit) {
|
||||
return None;
|
||||
}
|
||||
state.current = current;
|
||||
state.last_cubic = None;
|
||||
state.last_quad = None;
|
||||
}
|
||||
b'H' => {
|
||||
let x = num(l)? + base.x;
|
||||
let current = Point::new(x, state.current.y);
|
||||
if !push_element(p, PathEl::LineTo(current), limit) {
|
||||
return None;
|
||||
}
|
||||
state.current = current;
|
||||
state.last_cubic = None;
|
||||
state.last_quad = None;
|
||||
}
|
||||
b'V' => {
|
||||
let y = num(l)? + base.y;
|
||||
let current = Point::new(state.current.x, y);
|
||||
if !push_element(p, PathEl::LineTo(current), limit) {
|
||||
return None;
|
||||
}
|
||||
state.current = current;
|
||||
state.last_cubic = None;
|
||||
state.last_quad = None;
|
||||
}
|
||||
b'C' => {
|
||||
let (c1, c2, e) = (pt(l)?, pt(l)?, pt(l)?);
|
||||
if !push_element(p, PathEl::CurveTo(c1, c2, e), limit) {
|
||||
return None;
|
||||
}
|
||||
state.last_cubic = Some(c2);
|
||||
state.last_quad = None;
|
||||
state.current = e;
|
||||
}
|
||||
b'S' => {
|
||||
let c1 = state.last_cubic.map_or(state.current, |c| state.current + (state.current - c));
|
||||
let (c2, e) = (pt(l)?, pt(l)?);
|
||||
if !push_element(p, PathEl::CurveTo(c1, c2, e), limit) {
|
||||
return None;
|
||||
}
|
||||
state.last_cubic = Some(c2);
|
||||
state.last_quad = None;
|
||||
state.current = e;
|
||||
}
|
||||
b'Q' => {
|
||||
let (q, e) = (pt(l)?, pt(l)?);
|
||||
if !push_element(p, PathEl::QuadTo(q, e), limit) {
|
||||
return None;
|
||||
}
|
||||
state.last_quad = Some(q);
|
||||
state.last_cubic = None;
|
||||
state.current = e;
|
||||
}
|
||||
b'T' => {
|
||||
let q = state.last_quad.map_or(state.current, |c| state.current + (state.current - c));
|
||||
let e = pt(l)?;
|
||||
if !push_element(p, PathEl::QuadTo(q, e), limit) {
|
||||
return None;
|
||||
}
|
||||
state.last_quad = Some(q);
|
||||
state.last_cubic = None;
|
||||
state.current = e;
|
||||
}
|
||||
b'A' => {
|
||||
let rx = num(l)?.abs();
|
||||
let ry = num(l)?.abs();
|
||||
let rot = num(l)?;
|
||||
let large = l.flag()?;
|
||||
let sweep = l.flag()?;
|
||||
let e = pt(l)?;
|
||||
let arc = SvgArc { from: state.current, to: e, radii: Vec2::new(rx, ry), x_rotation: rot.to_radians(), large_arc: large, sweep };
|
||||
let mut last_end = None;
|
||||
if let Some(a) = kurbo::Arc::from_svg_arc(&arc) {
|
||||
// Scale tolerance with the radius so the segment count stays bounded.
|
||||
let tol = 0.1_f64.max(a.radii.x.max(a.radii.y) * 1e-4);
|
||||
a.to_cubic_beziers(tol, |c1, c2, end| {
|
||||
if limit.exceeded {
|
||||
return;
|
||||
}
|
||||
if push_element(p, PathEl::CurveTo(c1, c2, end), limit) {
|
||||
last_end = Some(end);
|
||||
}
|
||||
});
|
||||
}
|
||||
if limit.exceeded {
|
||||
return None;
|
||||
}
|
||||
// Degenerate arcs (e.g. huge radii) may emit nothing or stop short of the end point.
|
||||
if last_end.is_none_or(|q| (q - e).hypot() > 1e-3 * (1.0 + e.to_vec2().hypot())) && !push_element(p, PathEl::LineTo(e), limit) {
|
||||
return None;
|
||||
}
|
||||
state.current = e;
|
||||
state.last_cubic = None;
|
||||
state.last_quad = None;
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Drop move-tos that start no segment (from closepath bookkeeping).
|
||||
fn strip_trailing_moves(p: BezPath) -> BezPath {
|
||||
use kurbo::PathEl;
|
||||
let els = p.elements();
|
||||
let mut out = BezPath::new();
|
||||
for (i, e) in els.iter().enumerate() {
|
||||
if let PathEl::MoveTo(_) = e {
|
||||
let next_is_seg = els.get(i + 1).is_some_and(|n| !matches!(n, PathEl::MoveTo(_)));
|
||||
if !next_is_seg {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
out.push(*e);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use kurbo::PathEl;
|
||||
|
||||
#[test]
|
||||
/// Huge arc radii stay bounded.
|
||||
fn huge_arc_radius_stays_bounded() {
|
||||
let p = parse_path_data("M0 0A1e36 1e36 0 1 1 1e30 0");
|
||||
assert!(p.elements().len() < 1000, "got {} elements", p.elements().len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commands() {
|
||||
let p = parse_path_data("M10,10 L20,10 h10 v10 H10 z m5 5 l1-1.5.5.5e1 C 0 0 1 1 2 2 s3 3 4 4 Q1 1 2 2 t3 3 A5 5 0 0 1 20 20 a5 5 0 1010 0");
|
||||
let els = p.elements();
|
||||
assert_eq!(els[0], PathEl::MoveTo(Point::new(10.0, 10.0)));
|
||||
assert_eq!(els[2], PathEl::LineTo(Point::new(30.0, 10.0)));
|
||||
assert_eq!(els[3], PathEl::LineTo(Point::new(30.0, 20.0)));
|
||||
assert!(els.iter().any(|e| matches!(e, PathEl::ClosePath)));
|
||||
// Relative move after Z is relative to the subpath start (10,10).
|
||||
assert!(els.contains(&PathEl::MoveTo(Point::new(15.0, 15.0))));
|
||||
// "l1-1.5.5.5e1": two implicit linetos (1,-1.5) then (.5,5).
|
||||
assert!(els.contains(&PathEl::LineTo(Point::new(16.0, 13.5))));
|
||||
assert!(els.contains(&PathEl::LineTo(Point::new(16.5, 18.5))));
|
||||
assert!(els.iter().filter(|e| matches!(e, PathEl::CurveTo(..))).count() >= 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_keep_prefix() {
|
||||
let p = parse_path_data("M0 0 L10 0 L10 x10 L0 10");
|
||||
assert_eq!(p.elements().len(), 2);
|
||||
assert!(parse_path_data("garbage").elements().is_empty());
|
||||
assert!(parse_path_data("").elements().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_after_close_stops_instead_of_looping() {
|
||||
// "Z9": Z was repeated implicitly without consuming the number, appending close-paths
|
||||
// until memory ran out.
|
||||
let p = parse_path_data("M0 0 L10 0 L10 10 Z9 C1 2 3 4 5 6");
|
||||
assert_eq!(p.elements().len(), 4);
|
||||
assert!(parse_path_data("M0 0 L10 0 z 9 9").elements().len() <= 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Huge-radius arcs still reach their end point.
|
||||
fn huge_radius_arc_still_reaches_end_point() {
|
||||
for d in ["M0 0 A1e20 1e20 0 0 1 10 0 L10 10", "M0 0 A1e20 1e20 0 1 1 10 0 L10 10", "M0 0 a1e20 1e20 0 0 1 10 0 L10 10"] {
|
||||
let p = parse_path_data(d);
|
||||
let reaches = p.elements().iter().any(|e| match e {
|
||||
PathEl::LineTo(q) | PathEl::CurveTo(_, _, q) => (q.x - 10.0).abs() < 1e-6 && q.y.abs() < 1e-6,
|
||||
_ => false,
|
||||
});
|
||||
assert!(reaches, "{d}: {:?}", p.elements());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_lists() {
|
||||
assert_eq!(numbers_bounded("0 0,100 50", 8).0, vec![0.0, 0.0, 100.0, 50.0]);
|
||||
assert_eq!(numbers_bounded("-1-2.5.5", 8).0, vec![-1.0, -2.5, 0.5]);
|
||||
assert_eq!(numbers_bounded("1e2,2E-1", 8).0, vec![100.0, 0.2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// The element cap stops expansion and reports truncation.
|
||||
fn path_element_limit_stops_expansion_and_reports_truncation() {
|
||||
let data = format!("M0 0{}", "h1".repeat(MAX_PATH_ELEMENTS + 1));
|
||||
let (path, exceeded) = parse_path_data_bounded(&data, 32);
|
||||
assert!(exceeded);
|
||||
assert!(path.elements().len() <= 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// The number list cap stops retaining values.
|
||||
fn number_list_limit_stops_retaining_values() {
|
||||
let (values, exceeded) = numbers_bounded("1 2 3 4", 3);
|
||||
assert_eq!(values, [1.0, 2.0, 3.0]);
|
||||
assert!(exceeded);
|
||||
}
|
||||
}
|
||||
+1505
-1505
File diff suppressed because it is too large.
Load diff
+1154
-1152
File diff suppressed because it is too large.
Load diff
Generated
+27
-27
@@ -1,27 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1791594108,
|
||||
"narHash": "sha256-XK4KceonkwD8LuMAt/vNrz982mXJM5jeHAxMwsUXXAQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6510408d147d8c5e6d76043e899e9251756f6a75",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1791594108,
|
||||
"narHash": "sha256-XK4KceonkwD8LuMAt/vNrz982mXJM5jeHAxMwsUXXAQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6510408d147d8c5e6d76043e899e9251756f6a75",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -1,106 +1,106 @@
|
||||
{
|
||||
description = "EffectCraft — motion graphics and visual effects, a free alternative to After Effects, in pure Rust";
|
||||
|
||||
inputs = {
|
||||
# EffectCraft needs edition 2024 and rustc ≥ 1.95, so the flake tracks unstable. Consumers on a
|
||||
# release channel can retarget it: `inputs.effectcraft.inputs.nixpkgs.follows = "nixpkgs";`
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
}:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
|
||||
# The AppStream metadata carries a release date. The flake source's own commit date is
|
||||
# reproducible, unlike "now"; a tarball or dirty tree without one falls back to the epoch.
|
||||
buildDate =
|
||||
let
|
||||
d = self.lastModifiedDate or "";
|
||||
in
|
||||
if builtins.stringLength d >= 8 then
|
||||
"${builtins.substring 0 4 d}-${builtins.substring 4 2 d}-${builtins.substring 6 2 d}"
|
||||
else
|
||||
"1970-01-01";
|
||||
|
||||
packageArgs = {
|
||||
inherit buildDate;
|
||||
};
|
||||
|
||||
forAllSystems = f: lib.genAttrs systems (system: f (import nixpkgs { inherit system; }) system);
|
||||
in
|
||||
{
|
||||
packages = forAllSystems (
|
||||
pkgs: _:
|
||||
let
|
||||
effectcraft = pkgs.callPackage ./nix/package.nix packageArgs;
|
||||
in
|
||||
{
|
||||
inherit effectcraft;
|
||||
default = effectcraft;
|
||||
}
|
||||
);
|
||||
|
||||
# `nixpkgs.overlays = [ inputs.effectcraft.overlays.default ]` → `pkgs.effectcraft` everywhere.
|
||||
overlays.default = final: _prev: {
|
||||
effectcraft = final.callPackage ./nix/package.nix packageArgs;
|
||||
};
|
||||
|
||||
apps = forAllSystems (
|
||||
_: system: {
|
||||
default = {
|
||||
type = "app";
|
||||
program = lib.getExe self.packages.${system}.effectcraft;
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
devShells = forAllSystems (
|
||||
pkgs: system:
|
||||
let
|
||||
effectcraft = self.packages.${system}.effectcraft;
|
||||
in
|
||||
{
|
||||
default = pkgs.mkShell ({
|
||||
# The package's own native inputs, so `cargo build` in the shell needs no extra setup.
|
||||
inputsFrom = [ effectcraft ];
|
||||
packages = with pkgs; [
|
||||
cargo
|
||||
rustc
|
||||
clippy
|
||||
rustfmt
|
||||
rust-analyzer
|
||||
];
|
||||
# `cargo run` dlopens the Vulkan loader, libwayland and libxkbcommon like the packaged binary.
|
||||
LD_LIBRARY_PATH = lib.makeLibraryPath effectcraft.passthru.runtimeLibs;
|
||||
RUST_BACKTRACE = "1";
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
checks = forAllSystems (
|
||||
pkgs: system: {
|
||||
effectcraft = self.packages.${system}.effectcraft;
|
||||
|
||||
# `nix fmt` formats the flake; this keeps it formatted.
|
||||
formatting =
|
||||
pkgs.runCommand "effectcraft-nix-formatting" { nativeBuildInputs = [ pkgs.nixfmt ]; }
|
||||
''
|
||||
nixfmt --check ${./flake.nix} ${./nix}
|
||||
touch $out
|
||||
'';
|
||||
}
|
||||
);
|
||||
|
||||
formatter = forAllSystems (pkgs: _: pkgs.nixfmt);
|
||||
};
|
||||
}
|
||||
{
|
||||
description = "EffectCraft — motion graphics and visual effects, a free alternative to After Effects, in pure Rust";
|
||||
|
||||
inputs = {
|
||||
# EffectCraft needs edition 2024 and rustc ≥ 1.95, so the flake tracks unstable. Consumers on a
|
||||
# release channel can retarget it: `inputs.effectcraft.inputs.nixpkgs.follows = "nixpkgs";`
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
}:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
|
||||
# The AppStream metadata carries a release date. The flake source's own commit date is
|
||||
# reproducible, unlike "now"; a tarball or dirty tree without one falls back to the epoch.
|
||||
buildDate =
|
||||
let
|
||||
d = self.lastModifiedDate or "";
|
||||
in
|
||||
if builtins.stringLength d >= 8 then
|
||||
"${builtins.substring 0 4 d}-${builtins.substring 4 2 d}-${builtins.substring 6 2 d}"
|
||||
else
|
||||
"1970-01-01";
|
||||
|
||||
packageArgs = {
|
||||
inherit buildDate;
|
||||
};
|
||||
|
||||
forAllSystems = f: lib.genAttrs systems (system: f (import nixpkgs { inherit system; }) system);
|
||||
in
|
||||
{
|
||||
packages = forAllSystems (
|
||||
pkgs: _:
|
||||
let
|
||||
effectcraft = pkgs.callPackage ./nix/package.nix packageArgs;
|
||||
in
|
||||
{
|
||||
inherit effectcraft;
|
||||
default = effectcraft;
|
||||
}
|
||||
);
|
||||
|
||||
# `nixpkgs.overlays = [ inputs.effectcraft.overlays.default ]` → `pkgs.effectcraft` everywhere.
|
||||
overlays.default = final: _prev: {
|
||||
effectcraft = final.callPackage ./nix/package.nix packageArgs;
|
||||
};
|
||||
|
||||
apps = forAllSystems (
|
||||
_: system: {
|
||||
default = {
|
||||
type = "app";
|
||||
program = lib.getExe self.packages.${system}.effectcraft;
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
devShells = forAllSystems (
|
||||
pkgs: system:
|
||||
let
|
||||
effectcraft = self.packages.${system}.effectcraft;
|
||||
in
|
||||
{
|
||||
default = pkgs.mkShell ({
|
||||
# The package's own native inputs, so `cargo build` in the shell needs no extra setup.
|
||||
inputsFrom = [ effectcraft ];
|
||||
packages = with pkgs; [
|
||||
cargo
|
||||
rustc
|
||||
clippy
|
||||
rustfmt
|
||||
rust-analyzer
|
||||
];
|
||||
# `cargo run` dlopens the Vulkan loader, libwayland and libxkbcommon like the packaged binary.
|
||||
LD_LIBRARY_PATH = lib.makeLibraryPath effectcraft.passthru.runtimeLibs;
|
||||
RUST_BACKTRACE = "1";
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
checks = forAllSystems (
|
||||
pkgs: system: {
|
||||
effectcraft = self.packages.${system}.effectcraft;
|
||||
|
||||
# `nix fmt` formats the flake; this keeps it formatted.
|
||||
formatting =
|
||||
pkgs.runCommand "effectcraft-nix-formatting" { nativeBuildInputs = [ pkgs.nixfmt ]; }
|
||||
''
|
||||
nixfmt --check ${./flake.nix} ${./nix}
|
||||
touch $out
|
||||
'';
|
||||
}
|
||||
);
|
||||
|
||||
formatter = forAllSystems (pkgs: _: pkgs.nixfmt);
|
||||
};
|
||||
}
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
max_width = 160
|
||||
use_small_heuristics = "Max"
|
||||
max_width = 160
|
||||
use_small_heuristics = "Max"
|
||||
+23
-23
@@ -1,23 +1,23 @@
|
||||
sonar.projectKey=storytold_effectcraft
|
||||
sonar.organization=storytold
|
||||
|
||||
# crates/aep/tests/fixtures/tools/ holds the contributor's offline generators of the AEP test
|
||||
# corpus (Node and ExtendScript run by hand against a local folder, see its README). They are not
|
||||
# shipped or run by the app or CI, so they are not analysed.
|
||||
sonar.exclusions=crates/aep/tests/fixtures/tools/**
|
||||
|
||||
# S1523 / S2137 (eval, Function): the expression runtime in crates/expr/src/prelude.js replaces
|
||||
# `eval` and `Function` on purpose, with wrappers that check the code they are given before it
|
||||
# runs (After Effects expressions may call them). The rules flag exactly that wrapping.
|
||||
#
|
||||
# S7773 / S8786 (Number.isFinite and friends, String.trim): the .jsx scripts are After Effects
|
||||
# ExtendScript, which is ES3. They also run in After Effects, which has neither.
|
||||
sonar.issue.ignore.multicriteria=prelude_eval,prelude_redefine,jsx_es3_globals,jsx_es3_trim
|
||||
sonar.issue.ignore.multicriteria.prelude_eval.ruleKey=javascript:S1523
|
||||
sonar.issue.ignore.multicriteria.prelude_eval.resourceKey=crates/expr/src/prelude.js
|
||||
sonar.issue.ignore.multicriteria.prelude_redefine.ruleKey=javascript:S2137
|
||||
sonar.issue.ignore.multicriteria.prelude_redefine.resourceKey=crates/expr/src/prelude.js
|
||||
sonar.issue.ignore.multicriteria.jsx_es3_globals.ruleKey=javascript:S7773
|
||||
sonar.issue.ignore.multicriteria.jsx_es3_globals.resourceKey=**/*.jsx
|
||||
sonar.issue.ignore.multicriteria.jsx_es3_trim.ruleKey=javascript:S8786
|
||||
sonar.issue.ignore.multicriteria.jsx_es3_trim.resourceKey=**/*.jsx
|
||||
sonar.projectKey=storytold_effectcraft
|
||||
sonar.organization=storytold
|
||||
|
||||
# crates/aep/tests/fixtures/tools/ holds the contributor's offline generators of the AEP test
|
||||
# corpus (Node and ExtendScript run by hand against a local folder, see its README). They are not
|
||||
# shipped or run by the app or CI, so they are not analysed.
|
||||
sonar.exclusions=crates/aep/tests/fixtures/tools/**
|
||||
|
||||
# S1523 / S2137 (eval, Function): the expression runtime in crates/expr/src/prelude.js replaces
|
||||
# `eval` and `Function` on purpose, with wrappers that check the code they are given before it
|
||||
# runs (After Effects expressions may call them). The rules flag exactly that wrapping.
|
||||
#
|
||||
# S7773 / S8786 (Number.isFinite and friends, String.trim): the .jsx scripts are After Effects
|
||||
# ExtendScript, which is ES3. They also run in After Effects, which has neither.
|
||||
sonar.issue.ignore.multicriteria=prelude_eval,prelude_redefine,jsx_es3_globals,jsx_es3_trim
|
||||
sonar.issue.ignore.multicriteria.prelude_eval.ruleKey=javascript:S1523
|
||||
sonar.issue.ignore.multicriteria.prelude_eval.resourceKey=crates/expr/src/prelude.js
|
||||
sonar.issue.ignore.multicriteria.prelude_redefine.ruleKey=javascript:S2137
|
||||
sonar.issue.ignore.multicriteria.prelude_redefine.resourceKey=crates/expr/src/prelude.js
|
||||
sonar.issue.ignore.multicriteria.jsx_es3_globals.ruleKey=javascript:S7773
|
||||
sonar.issue.ignore.multicriteria.jsx_es3_globals.resourceKey=**/*.jsx
|
||||
sonar.issue.ignore.multicriteria.jsx_es3_trim.ruleKey=javascript:S8786
|
||||
sonar.issue.ignore.multicriteria.jsx_es3_trim.resourceKey=**/*.jsx
|
||||
Reference in new issue
Block a user