Fonts: use craft-fonts as an optional build input

crates/text/build.rs embeds the fonts of a storytold/craft-fonts checkout
named by CRAFT_FONTS_DIR as filmcraft_text::fonts::CRAFT_FONTS (native:
all of them; wasm32: only BIZ UDPGothic Regular). Unset, CRAFT_FONTS is
empty and nothing changes. A bad CRAFT_FONTS_DIR is a warning, or an
error with CRAFT_FONTS_REQUIRED=1. Never a Cargo dependency, no network.

- UI: the Japanese faces are the last fallback of every egui font family
  (BIZ UDPGothic first, Bold first in the semibold/medium families).
- Text engine: registered after the bundled faces; fallback_for picks
  them after the bundled faces and before system faces (Mincho for serif
  text, Gothic otherwise, nearest weight).
- Tests: Japanese glyphs (no tofu) in the UI and title paths, skipped
  without craft-fonts; the theme works with CRAFT_FONTS empty.
- Release: every build job checks out craft-fonts at a pinned commit and
  builds with CRAFT_FONTS_DIR + CRAFT_FONTS_REQUIRED; packages carry
  OFL-<family>.txt. NOTICE, ATTRIBUTION.md (optional build input), AGENTS.md,
  CLAUDE.md, README and docs/contributing.md describe it.

FilmCraft main bundles no Japanese font, so no font files move.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Brandon ThomasandClaude Opus 5.5 committed 2026-10-06 11:40:29 -04:00
1 parent a2f6ba8b44
commit 9953befe28
15 files changed
+356 -10

No files matched your search

+34
View File
@@ -70,6 +70,12 @@ jobs:
FILMCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }} FILMCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# Fonts (optional build input, required for releases): docs/contributing.md#building-with-craft-fonts
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: storytold/craft-fonts
ref: abb83316d96aa59c1cf64784289e378fe9fa5695 # bump deliberately
path: craft-fonts
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with: with:
targets: aarch64-apple-darwin, x86_64-apple-darwin targets: aarch64-apple-darwin, x86_64-apple-darwin
@@ -90,6 +96,8 @@ jobs:
APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
CRAFT_FONTS_REQUIRED: "1"
run: packaging/macos/package.sh --arch universal run: packaging/macos/package.sh --arch universal
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
@@ -120,6 +128,12 @@ jobs:
FILMCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }} FILMCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# Fonts (optional build input, required for releases): docs/contributing.md#building-with-craft-fonts
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: storytold/craft-fonts
ref: abb83316d96aa59c1cf64784289e378fe9fa5695 # bump deliberately
path: craft-fonts
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with: with:
targets: ${{ matrix.target }} targets: ${{ matrix.target }}
@@ -144,6 +158,8 @@ jobs:
AZURE_SIGNING_ENDPOINT: ${{ secrets.AZURE_SIGNING_ENDPOINT }} AZURE_SIGNING_ENDPOINT: ${{ secrets.AZURE_SIGNING_ENDPOINT }}
AZURE_SIGNING_ACCOUNT: ${{ secrets.AZURE_SIGNING_ACCOUNT }} AZURE_SIGNING_ACCOUNT: ${{ secrets.AZURE_SIGNING_ACCOUNT }}
AZURE_CERT_PROFILE: ${{ secrets.AZURE_CERT_PROFILE }} AZURE_CERT_PROFILE: ${{ secrets.AZURE_CERT_PROFILE }}
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
CRAFT_FONTS_REQUIRED: "1"
run: ./packaging/windows/package.ps1 -Arch ${{ matrix.arch }} run: ./packaging/windows/package.ps1 -Arch ${{ matrix.arch }}
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
@@ -170,6 +186,12 @@ jobs:
NFPM_VERSION: 2.47.0 NFPM_VERSION: 2.47.0
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# Fonts (optional build input, required for releases): docs/contributing.md#building-with-craft-fonts
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: storytold/craft-fonts
ref: abb83316d96aa59c1cf64784289e378fe9fa5695 # bump deliberately
path: craft-fonts
- name: Install build and packaging tools - name: Install build and packaging tools
run: | run: |
sudo apt-get update sudo apt-get update
@@ -187,6 +209,9 @@ jobs:
if: inputs.version != '' if: inputs.version != ''
run: cargo xtask version set "$FILMCRAFT_VERSION" run: cargo xtask version set "$FILMCRAFT_VERSION"
- name: Build and package (AppImage, deb, rpm, tar.gz) - name: Build and package (AppImage, deb, rpm, tar.gz)
env:
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
CRAFT_FONTS_REQUIRED: "1"
run: packaging/linux/package.sh run: packaging/linux/package.sh
- name: Check the packages - name: Check the packages
run: | run: |
@@ -215,6 +240,12 @@ jobs:
WASM_BINDGEN_VERSION: 0.2.129 WASM_BINDGEN_VERSION: 0.2.129
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# Fonts (optional build input, required for releases): docs/contributing.md#building-with-craft-fonts
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: storytold/craft-fonts
ref: abb83316d96aa59c1cf64784289e378fe9fa5695 # bump deliberately
path: craft-fonts
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
with: with:
targets: wasm32-unknown-unknown targets: wasm32-unknown-unknown
@@ -232,6 +263,9 @@ jobs:
if: inputs.version != '' if: inputs.version != ''
run: cargo xtask version set "$FILMCRAFT_VERSION" run: cargo xtask version set "$FILMCRAFT_VERSION"
- name: Build and zip - name: Build and zip
env:
CRAFT_FONTS_DIR: ${{ github.workspace }}/craft-fonts
CRAFT_FONTS_REQUIRED: "1"
run: packaging/web/package.sh run: packaging/web/package.sh
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
with: with:
+7
View File
@@ -92,6 +92,13 @@ reproduce an image (for example, point lists traced from someone else's icon).
need a sidecar and an `ATTRIBUTION.md` row (licence `LicenseRef-ArtCraft-Trademark`). No other 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 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). and other logos stay out; draw a generic icon instead).
9. **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this
repo.** Don't commit new font files (the small Latin UI fonts already in `assets/fonts/` stay). A
font FilmCraft needs is added to craft-fonts, which the app reads through the optional build input
`CRAFT_FONTS_DIR=<craft-fonts checkout>` (`crates/text/build.rs` embeds the fonts in its
`fonts/manifest.txt` as `filmcraft_text::fonts::CRAFT_FONTS`; unset, it is empty and the app uses its
bundled and system fonts). Never add craft-fonts to a `Cargo.toml`. Code using `CRAFT_FONTS` must work
when it is empty. Standard: [`craftrules/standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md).
## 2. Clean-room code ## 2. Clean-room code
+15
View File
@@ -106,6 +106,21 @@ is traced or derived from Adobe artwork.
| Lumetri Presets (Effects panel: Cinematic, Film Emulation, Monochrome, Technical) and their preview picture | `crates/render/src/lumetri_presets.rs`: Lumetri parameter values chosen by contributors, descriptive names, and a procedural preview picture (dusk sky, ridges, lake, grey ramp, colour chips) | | Lumetri Presets (Effects panel: Cinematic, Film Emulation, Monochrome, Technical) and their preview picture | `crates/render/src/lumetri_presets.rs`: Lumetri parameter values chosen by contributors, descriptive names, and a procedural preview picture (dusk sky, ridges, lake, grey ramp, colour chips) |
| Colour palette and layout metrics | `crates/ui-egui/src/theme.rs`: colour values and sizes only; no artwork | | Colour palette and layout metrics | `crates/ui-egui/src/theme.rs`: colour values and sizes only; no artwork |
## Optional build input: craft-fonts
These fonts are **not files in this repository**. Builds made with the optional build input
`CRAFT_FONTS_DIR` (all official releases; see [docs/contributing.md](docs/contributing.md#building-with-craft-fonts))
embed them from [storytold/craft-fonts](https://github.com/storytold/craft-fonts), where each file,
its author, pinned upstream source and licence text are listed in its
[ATTRIBUTION.md](https://github.com/storytold/craft-fonts/blob/main/ATTRIBUTION.md). Release packages
carry each font's licence as `OFL-<family>.txt`.
| Font | Use in FilmCraft | Licence |
|---|---|---|
| BIZ UDPGothic Regular, Bold (Morisawa) | Japanese UI text; Japanese fallback for sans titles (the web build embeds only Regular) | OFL-1.1 |
| Shippori Mincho Regular (FONTDASU) | Japanese fallback for serif titles (native builds) | OFL-1.1 |
| BIZ UDMincho Regular (Morisawa) | Japanese fallback for serif titles (native builds) | OFL-1.1 |
## First-party brand marks ## First-party brand marks
The ArtCraft name, wordmark and logos are trademarks of the ArtCraft Team. They are not open source and The ArtCraft name, wordmark and logos are trademarks of the ArtCraft Team. They are not open source and
+1
View File
@@ -11,6 +11,7 @@ FilmCraft is a clean-room, open-source, pure-Rust non-linear video editor target
## Non-negotiables ## Non-negotiables
**Read [`AGENTS.md`](AGENTS.md) first; its rules override everything here.** **Read [`AGENTS.md`](AGENTS.md) first; its rules override everything here.**
- **Never crash** ([`AGENTS.md`](AGENTS.md) §0, standard: [`craftrules/standards/never-crash.md`](https://github.com/storytold/craftrules/blob/main/standards/never-crash.md)). People depend on FilmCraft; a crash loses their work, so this outranks feature work. No `unwrap`/`expect`/`panic!`/`unreachable!`/`todo!`/`unimplemented!` or `unsafe` outside tests: return `Result<T, E>` and propagate with `?` (`ok_or`, `let … else`, `if let`). Treat every number from media, project files, presets or CLI/MCP/control params as hostile: `get()` instead of `[i]`, checked/saturating math, no division by possibly-zero values, capped allocations, bounded recursion. `lock().unwrap_or_else(PoisonError::into_inner)`; background jobs run under `catch_unwind`. The panic hook + UI `catch_unwind` (`crates/ui-egui/src/crash.rs`) are the last-resort guard, not a licence. Every crash fix gets a regression test that panicked before. In particular (§1): never use Adobe iconography, images or any other Adobe asset. Every asset must be openly licensed (OSS / public domain / Creative Commons, or original work by a contributor) and must have a `<file>.attribution` sidecar plus an entry in `ATTRIBUTION.md`. `cargo xtask assets` enforces this. - **Never crash** ([`AGENTS.md`](AGENTS.md) §0, standard: [`craftrules/standards/never-crash.md`](https://github.com/storytold/craftrules/blob/main/standards/never-crash.md)). People depend on FilmCraft; a crash loses their work, so this outranks feature work. No `unwrap`/`expect`/`panic!`/`unreachable!`/`todo!`/`unimplemented!` or `unsafe` outside tests: return `Result<T, E>` and propagate with `?` (`ok_or`, `let … else`, `if let`). Treat every number from media, project files, presets or CLI/MCP/control params as hostile: `get()` instead of `[i]`, checked/saturating math, no division by possibly-zero values, capped allocations, bounded recursion. `lock().unwrap_or_else(PoisonError::into_inner)`; background jobs run under `catch_unwind`. The panic hook + UI `catch_unwind` (`crates/ui-egui/src/crash.rs`) are the last-resort guard, not a licence. Every crash fix gets a regression test that panicked before. In particular (§1): never use Adobe iconography, images or any other Adobe asset. Every asset must be openly licensed (OSS / public domain / Creative Commons, or original work by a contributor) and must have a `<file>.attribution` sidecar plus an entry in `ATTRIBUTION.md`. `cargo xtask assets` enforces this.
- **Fonts** live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this repo (no new font files here; [`AGENTS.md`](AGENTS.md) §1.9, standard: [`craftrules/standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md)). Build with them: `git clone https://github.com/storytold/craft-fonts ../craft-fonts && CRAFT_FONTS_DIR=../craft-fonts cargo run -p filmcraft` (`crates/text/build.rs` embeds them as `filmcraft_text::fonts::CRAFT_FONTS`, used for Japanese in the UI and titles). Unset, `CRAFT_FONTS` is empty and everything must still build, test and run; never add craft-fonts to a `Cargo.toml`. Tests on its glyphs skip when it is empty, so run the gates with and without `CRAFT_FONTS_DIR` when you touch fonts.
- **Clean-room.** Never read/disassemble anything inside Adobe app bundles (names/listings only). Never copy Adobe icons, presets, LUTs, fonts. Never copy GPL/LGPL/AGPL code (FFmpeg, x264, x265, MLT, Kdenlive, Shotcut, Olive, LAME…). ffmpeg/ffprobe run only as external test oracles / fixture generators — never linked or shipped. - **Clean-room.** Never read/disassemble anything inside Adobe app bundles (names/listings only). Never copy Adobe icons, presets, LUTs, fonts. Never copy GPL/LGPL/AGPL code (FFmpeg, x264, x265, MLT, Kdenlive, Shotcut, Olive, LAME…). ffmpeg/ffprobe run only as external test oracles / fixture generators — never linked or shipped.
- **Pure Rust** in the product. OS media APIs only via Rust bindings in `crates/platform` (hardware decoding; the one crate allowed `unsafe`, see `AGENTS.md` §0.3 and `docs/adr/0001-platform-ffi.md`), optional, behind traits, with our decoders as the fallback. - **Pure Rust** in the product. OS media APIs only via Rust bindings in `crates/platform` (hardware decoding; the one crate allowed `unsafe`, see `AGENTS.md` §0.3 and `docs/adr/0001-platform-ffi.md`), optional, behind traits, with our decoders as the fallback.
- **Layering** ([`docs/architecture.md`](docs/architecture.md) §1, enforced by `cargo xtask layers`): nothing below L5 depends on egui/eframe/winit/rfd/cpal. L0 codec/container crates depend only on `filmcraft-bitstream`. - **Layering** ([`docs/architecture.md`](docs/architecture.md) §1, enforced by `cargo xtask layers`): nothing below L5 depends on egui/eframe/winit/rfd/cpal. L0 codec/container crates depend only on `filmcraft-bitstream`.
+3
View File
@@ -15,4 +15,7 @@ list (author, source, license) is in ATTRIBUTION.md.
Inter font (assets/fonts/Inter-*.ttf) — OFL-1.1. See assets/fonts/OFL-Inter.txt. Inter font (assets/fonts/Inter-*.ttf) — OFL-1.1. See assets/fonts/OFL-Inter.txt.
Noto Serif font (assets/fonts/NotoSerif-Regular.ttf) — OFL-1.1. See assets/fonts/OFL-NotoSerif.txt. Noto Serif font (assets/fonts/NotoSerif-Regular.ttf) — OFL-1.1. See assets/fonts/OFL-NotoSerif.txt.
JetBrains Mono font (assets/fonts/JetBrainsMono-Regular.ttf) — OFL-1.1. See assets/fonts/OFL-JetBrainsMono.txt. JetBrains Mono font (assets/fonts/JetBrainsMono-Regular.ttf) — OFL-1.1. See assets/fonts/OFL-JetBrainsMono.txt.
Builds made with craft-fonts (CRAFT_FONTS_DIR; all official releases) also embed the fonts listed in
https://github.com/storytold/craft-fonts/blob/main/ATTRIBUTION.md (BIZ UDPGothic, Shippori Mincho,
BIZ UDMincho) — OFL-1.1. Their licence texts ship with the packages as OFL-<family>.txt.
Public-domain film and NASA frames and CC0 music shown in README screenshots (docs/images/) — public domain / CC0-1.0. See ATTRIBUTION.md. Public-domain film and NASA frames and CC0 music shown in README screenshots (docs/images/) — public domain / CC0-1.0. See ATTRIBUTION.md.
+10 -1
View File
@@ -252,6 +252,13 @@ cargo run --release -p filmcraft-cli -- commands # list every engine c
cargo run --release -p filmcraft-cli -- mcp # MCP server (headless) cargo run --release -p filmcraft-cli -- mcp # MCP server (headless)
``` ```
Japanese text in the interface and in titles comes from [craft-fonts](https://github.com/storytold/craft-fonts), an optional build input (release builds always include it; without it FilmCraft uses its own and the system's fonts):
```sh
git clone https://github.com/storytold/craft-fonts ../craft-fonts
CRAFT_FONTS_DIR=../craft-fonts cargo run --release -p filmcraft
```
The control protocol is documented in [docs/control-protocol.md](docs/control-protocol.md). Stuck, or want to show what you made? Ask in [Discord](https://discord.gg/artcraft). The control protocol is documented in [docs/control-protocol.md](docs/control-protocol.md). Stuck, or want to show what you made? Ask in [Discord](https://discord.gg/artcraft).
## Documentation ## Documentation
@@ -351,7 +358,9 @@ FilmCraft is dual-licensed under [MIT](LICENSE-MIT) or [Apache-2.0](LICENSE-APAC
Copyright (c) 2026 ArtCraft Team and the FilmCraft contributors. Required notices are in [NOTICE](NOTICE). Copyright (c) 2026 ArtCraft Team and the FilmCraft contributors. Required notices are in [NOTICE](NOTICE).
Bundled fonts, icons, images and other assets keep their own open licenses; each one is listed 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). with its author, source and license in [ATTRIBUTION.md](ATTRIBUTION.md). Builds made with
[craft-fonts](https://github.com/storytold/craft-fonts) (all official releases) also embed its fonts
(OFL-1.1), listed in its [ATTRIBUTION.md](https://github.com/storytold/craft-fonts/blob/main/ATTRIBUTION.md).
**Footage and music in the screenshots:** NASA's Apollo 11 film and television footage from [images.nasa.gov](https://images.nasa.gov) (launch, Launch Control Center, lunar surface and recovery) and the Apollo 11 air-to-ground voice transcript, all US Government works in the public domain (NASA does not endorse this project); *Night of the Living Dead* (1968), *Carnival of Souls* (1962) and *Charade* (1963), all in the US public domain; *Earth Views from the ISS* by NASA; Chopin's Nocturne Op. 48 No. 1 and Ballade No. 1, performed for Musopen and released under CC0. The media itself is not in this repository. Sources and details for every asset are in [ATTRIBUTION.md](ATTRIBUTION.md). **Footage and music in the screenshots:** NASA's Apollo 11 film and television footage from [images.nasa.gov](https://images.nasa.gov) (launch, Launch Control Center, lunar surface and recovery) and the Apollo 11 air-to-ground voice transcript, all US Government works in the public domain (NASA does not endorse this project); *Night of the Living Dead* (1968), *Carnival of Souls* (1962) and *Charade* (1963), all in the US public domain; *Earth Views from the ISS* by NASA; Chopin's Nocturne Op. 48 No. 1 and Ballade No. 1, performed for Musopen and released under CC0. The media itself is not in this repository. Sources and details for every asset are in [ATTRIBUTION.md](ATTRIBUTION.md).
+1 -1
View File
@@ -6,7 +6,7 @@ and the Timecode / Clip Name effects. Pure Rust; it builds for `wasm32-unknown-u
| Module | What | | Module | What |
|---|---| |---|---|
| `fonts` | Font database. Bundled faces (always present, also on the web): Inter Regular / Medium / SemiBold / Bold / Italic, JetBrains Mono Regular, Noto Serif Regular — all SIL OFL 1.1, unmodified, in `assets/fonts/` with `.attribution` sidecars. More faces come from `FontSource`s: `DirectorySource::system()` scans the platform font folders on first need (only the `name` and `OS/2` tables are read, via `sfnt`); on wasm the scan finds nothing and hosts can call `add_font_data`. `resolve(family, style)` picks a face, synthesises bold/italic when the family lacks them, and falls back to Inter (flagged `missing`). `fallback_for` finds a face for characters the chosen face lacks. | | `fonts` | Font database. Bundled faces (always present, also on the web): Inter Regular / Medium / SemiBold / Bold / Italic, JetBrains Mono Regular, Noto Serif Regular — all SIL OFL 1.1, unmodified, in `assets/fonts/` with `.attribution` sidecars. More faces come from `FontSource`s: `DirectorySource::system()` scans the platform font folders on first need (only the `name` and `OS/2` tables are read, via `sfnt`); on wasm the scan finds nothing and hosts can call `add_font_data`. `resolve(family, style)` picks a face, synthesises bold/italic when the family lacks them, and falls back to Inter (flagged `missing`). `fallback_for` finds a face for characters the chosen face lacks: bundled faces first, then the optional craft-fonts faces (`CRAFT_FONTS`, embedded by `build.rs` when built with `CRAFT_FONTS_DIR`; Mincho for serif text, Gothic otherwise, nearest weight), then loaded system faces. |
| `layout` | Paragraph layout: per-character font fallback → bidi levels (UAX #9) → items (runs of one face, direction and size) shaped with harfrust (OpenType kerning, ligatures, marks, complex scripts) → tracking → line breaking at UAX #14 opportunities (area text) → visual reordering per line → alignment (left / centre / right / justify), leading (added to the natural line height), baseline shift, all caps / synthesised small caps (78 %), underline. Every character boundary gets a caret stop (ligatures are split evenly), so editors can place carets, hit-test and draw selections. Layouts are cached by text + style (512 entries, LRU). | | `layout` | Paragraph layout: per-character font fallback → bidi levels (UAX #9) → items (runs of one face, direction and size) shaped with harfrust (OpenType kerning, ligatures, marks, complex scripts) → tracking → line breaking at UAX #14 opportunities (area text) → visual reordering per line → alignment (left / centre / right / justify), leading (added to the natural line height), baseline shift, all caps / synthesised small caps (78 %), underline. Every character boundary gets a caret stop (ligatures are split evenly), so editors can place carets, hit-test and draw selections. Layouts are cached by text + style (512 entries, LRU). |
| `layout_rich` | Per-character style runs (`StyleRun`: a byte range with its own `TextStyle`): runs split shaping items (font, size, tracking, caps, faux styles), baseline shift and underline apply per run, a line holding a larger run is pushed down from the line above (baseline distance = max(base line height, previous descent + this line's ascent + leading)), and every glyph / underline carries its style index so renderers can colour runs (`render::draw_run`). | | `layout_rich` | Per-character style runs (`StyleRun`: a byte range with its own `TextStyle`): runs split shaping items (font, size, tracking, caps, faux styles), baseline shift and underline apply per run, a line holding a larger run is pushed down from the line above (baseline distance = max(base line height, previous descent + this line's ascent + leading)), and every glyph / underline carries its style index so renderers can colour runs (`render::draw_run`). |
| `raster` | Our anti-aliased rasteriser (non-zero winding, 5 sub-scanlines, exact horizontal coverage, active-edge list), contour paths with transforms, rounded rectangles, ellipses, polygons and outline emboldening. Coverage is linear, so compositing in linear light is correct. | | `raster` | Our anti-aliased rasteriser (non-zero winding, 5 sub-scanlines, exact horizontal coverage, active-edge list), contour paths with transforms, rounded rectangles, ellipses, polygons and outline emboldening. Coverage is linear, so compositing in linear light is correct. |
+63
View File
@@ -0,0 +1,63 @@
//! Optional craft-fonts build input (see storytold/craft-fonts `docs/integration.md` and
//! craftrules `standards/fonts.md`).
//!
//! With `CRAFT_FONTS_DIR=<craft-fonts checkout>` this embeds every font listed in its
//! `fonts/manifest.txt` as [`CRAFT_FONTS`](crate::fonts::CRAFT_FONTS); unset, `CRAFT_FONTS` is
//! empty and nothing else changes. A `CRAFT_FONTS_DIR` that is not a checkout is a warning, or an
//! error with `CRAFT_FONTS_REQUIRED=1` (release builds).
//!
//! Native builds embed every font. The web (wasm32) build embeds only the UI font (BIZ UDPGothic
//! Regular, ~4.5 MB) to stay under the web size budget (Cloudflare serves files up to 25 MiB).
//! Only the local directory is read: no network.
use std::fmt::Write as _;
use std::path::PathBuf;
fn main() {
println!("cargo::rerun-if-changed=build.rs");
println!("cargo::rerun-if-env-changed=CRAFT_FONTS_DIR");
println!("cargo::rerun-if-env-changed=CRAFT_FONTS_REQUIRED");
let mut src = String::from("pub static CRAFT_FONTS: &[CraftFont] = &[\n");
if let Some(dir) = std::env::var_os("CRAFT_FONTS_DIR").map(PathBuf::from) {
match craft_fonts(&dir) {
Ok(entries) => src.push_str(&entries),
Err(e) if std::env::var_os("CRAFT_FONTS_REQUIRED").is_some() => {
println!("cargo::error=CRAFT_FONTS_DIR={}: {e}", dir.display());
}
Err(e) => println!("cargo::warning=building without craft-fonts: CRAFT_FONTS_DIR={}: {e}", dir.display()),
}
}
src.push_str("];\n");
let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap_or_default()).join("craft_fonts.rs");
if let Err(e) = std::fs::write(&out, src) {
println!("cargo::error=writing {}: {e}", out.display());
}
}
/// One `CraftFont { .. }` initialiser per manifest line.
fn craft_fonts(dir: &std::path::Path) -> Result<String, String> {
let manifest = dir.join("fonts/manifest.txt");
println!("cargo::rerun-if-changed={}", manifest.display());
let text = std::fs::read_to_string(&manifest).map_err(|e| format!("{}: {e}", manifest.display()))?;
let wasm = std::env::var("CARGO_CFG_TARGET_ARCH").as_deref() == Ok("wasm32");
let mut out = String::new();
for line in text.lines().map(str::trim).filter(|l| !l.is_empty() && !l.starts_with('#')) {
let f: Vec<&str> = line.split(" | ").map(str::trim).collect();
let [family, style, file, scripts, ..] = f.as_slice() else {
return Err(format!("malformed manifest line: {line}"));
};
if wasm && !(*family == "BIZ UDPGothic" && *style == "Regular") {
continue;
}
let path = dir.join(file).canonicalize().map_err(|e| format!("{file}: {e}"))?;
println!("cargo::rerun-if-changed={}", path.display());
let scripts: Vec<String> = scripts.split(',').map(|s| format!("{:?}", s.trim())).collect();
let _ = writeln!(
out,
" CraftFont {{ family: {family:?}, style: {style:?}, scripts: &[{}], bytes: include_bytes!({:?}) }},",
scripts.join(", "),
path.display().to_string(),
);
}
Ok(out)
}
+80 -3
View File
@@ -1,5 +1,6 @@
//! The font database: bundled fonts (always available, also on the web) plus fonts discovered by //! The font database: bundled fonts (always available, also on the web), the optional
//! [`FontSource`]s (system font folders on native platforms). //! [`CRAFT_FONTS`] (present when built with `CRAFT_FONTS_DIR`, also on the web) plus fonts
//! discovered by [`FontSource`]s (system font folders on native platforms).
//! //!
//! Faces are addressed by [`FaceId`] (an index that never changes for the life of the process). //! Faces are addressed by [`FaceId`] (an index that never changes for the life of the process).
//! [`resolve`] maps a family + style name (as stored in projects) to a face and the synthetic //! [`resolve`] maps a family + style name (as stored in projects) to a face and the synthetic
@@ -22,6 +23,27 @@ pub static INTER_ITALIC: &[u8] = include_bytes!("../../../assets/fonts/Inter-Ita
pub static JETBRAINS_MONO_REGULAR: &[u8] = include_bytes!("../../../assets/fonts/JetBrainsMono-Regular.ttf"); pub static JETBRAINS_MONO_REGULAR: &[u8] = include_bytes!("../../../assets/fonts/JetBrainsMono-Regular.ttf");
pub static NOTO_SERIF_REGULAR: &[u8] = include_bytes!("../../../assets/fonts/NotoSerif-Regular.ttf"); pub static NOTO_SERIF_REGULAR: &[u8] = include_bytes!("../../../assets/fonts/NotoSerif-Regular.ttf");
/// A font from the optional craft-fonts build input (empty unless built with `CRAFT_FONTS_DIR`; see
/// `build.rs` and storytold/craft-fonts `docs/integration.md`).
pub struct CraftFont {
pub family: &'static str,
pub style: &'static str,
/// ISO 15924 scripts the font is for, e.g. `"Jpan"`.
pub scripts: &'static [&'static str],
pub bytes: &'static [u8],
}
include!(concat!(env!("OUT_DIR"), "/craft_fonts.rs"));
/// The craft-fonts entries for Japanese (`Jpan`), in [`CRAFT_FONTS`] order. Empty when the app was
/// built without craft-fonts.
pub fn craft_japanese() -> impl Iterator<Item = &'static CraftFont> {
CRAFT_FONTS.iter().filter(|f| f.scripts.contains(&"Jpan"))
}
/// Origin of faces registered from [`CRAFT_FONTS`].
pub const CRAFT_ORIGIN: &str = "craft-fonts";
/// The default family for new text. /// The default family for new text.
pub const DEFAULT_FAMILY: &str = "Inter"; pub const DEFAULT_FAMILY: &str = "Inter";
@@ -240,6 +262,13 @@ fn db() -> &'static RwLock<Db> {
push(&mut db, info_from(n, FaceData::Static(b), "bundled")); push(&mut db, info_from(n, FaceData::Static(b), "bundled"));
} }
} }
// after the bundled faces, before anything a FontSource finds (a system copy of the same
// family + style is then skipped as a duplicate)
for f in CRAFT_FONTS {
for n in read_faces_bytes(f.bytes) {
push(&mut db, info_from(n, FaceData::Static(f.bytes), CRAFT_ORIGIN));
}
}
RwLock::new(db) RwLock::new(db)
}) })
} }
@@ -394,7 +423,15 @@ fn resolve_in(family: &str, style: &str) -> Option<Resolved> {
Some(Resolved { face: best.id, synth_bold: w >= 600 && best.info.weight <= 500, synth_italic: it && !best.info.italic, missing: false }) Some(Resolved { face: best.id, synth_bold: w >= 600 && best.info.weight <= 500, synth_italic: it && !best.info.italic, missing: false })
} }
/// A face that covers `c`, preferring `prefer`; bundled and already-registered faces are searched. /// Whether a family name reads as a serif face (Noto Serif, Shippori Mincho, …), to pick a Mincho
/// fallback for serif text and a Gothic one otherwise.
fn is_serif(family: &str) -> bool {
let f = family.to_ascii_lowercase();
(f.contains("serif") && !f.contains("sans")) || f.contains("mincho")
}
/// A face that covers `c`, preferring `prefer`; bundled, craft-fonts and already-registered faces
/// are searched, in that order.
pub fn fallback_for(c: char, prefer: FaceId) -> FaceId { pub fn fallback_for(c: char, prefer: FaceId) -> FaceId {
let p = face(prefer); let p = face(prefer);
if p.has_char(c) || c.is_control() { if p.has_char(c) || c.is_control() {
@@ -407,6 +444,15 @@ pub fn fallback_for(c: char, prefer: FaceId) -> FaceId {
return f.id; return f.id;
} }
} }
// then the craft-fonts faces (Japanese): Mincho for serif text, Gothic otherwise, nearest weight
let serif = is_serif(&p.info.family);
let mut craft: Vec<&Arc<Face>> = faces.iter().filter(|f| f.info.origin == CRAFT_ORIGIN).collect();
craft.sort_by_key(|f| (is_serif(&f.info.family) != serif, f.info.italic != p.info.italic, f.info.weight.abs_diff(p.info.weight)));
for f in craft {
if f.has_char(c) {
return f.id;
}
}
for f in faces.iter().filter(|f| f.info.origin != "bundled" && f.bytes.get().is_some()) { for f in faces.iter().filter(|f| f.info.origin != "bundled" && f.bytes.get().is_some()) {
if f.has_char(c) { if f.has_char(c) {
return f.id; return f.id;
@@ -464,6 +510,37 @@ mod tests {
} }
} }
/// craft-fonts faces are registered after the bundled ones and serve Japanese characters the
/// requested face lacks: Gothic for sans text, Mincho for serif text.
#[test]
fn japanese_falls_back_to_craft_fonts() {
let craft: Vec<_> = all_faces().into_iter().filter(|f| f.info.origin == CRAFT_ORIGIN).collect();
if craft_japanese().next().is_none() {
eprintln!("SKIPPED: built without craft-fonts (set CRAFT_FONTS_DIR to run)");
assert!(craft.is_empty());
return;
}
assert!(craft.len() >= craft_japanese().count(), "{} faces", craft.len());
let bundled = all_faces().iter().take_while(|f| f.info.origin == "bundled").count();
assert!(craft.iter().all(|f| f.id >= bundled), "craft-fonts faces come after the bundled faces");
let sans = resolve("Inter", "Regular").face;
let serif = resolve("Noto Serif", "Regular").face;
for c in "日本語の文字".chars() {
let g = face(fallback_for(c, sans));
assert_eq!(g.info.origin, CRAFT_ORIGIN, "{c}");
assert!(g.has_char(c) && !is_serif(&g.info.family), "{c}: {}", g.info.family);
let m = face(fallback_for(c, serif));
assert!(m.has_char(c) && is_serif(&m.info.family), "{c}: {}", m.info.family);
}
// the bold UI face for bold text
let bold = face(fallback_for('日', resolve("Inter", "Bold").face));
assert!(bold.info.weight >= 600, "{} {}", bold.info.family, bold.info.style);
// and laid out with real glyphs (no tofu)
let l = crate::layout("日本語の文字", &crate::TextStyle { size: 40.0, ..Default::default() }, &Default::default());
assert_eq!(l.glyphs.len(), 6);
assert!(l.glyphs.iter().all(|g| g.id != 0), "{:?}", l.glyphs);
}
#[test] #[test]
fn metrics_and_coverage() { fn metrics_and_coverage() {
let f = face(resolve("Inter", "Regular").face); let f = face(resolve("Inter", "Regular").face);
+2 -1
View File
@@ -1,6 +1,7 @@
//! FilmCraft text engine (layer L1). //! FilmCraft text engine (layer L1).
//! //!
//! - [`fonts`]: font database — bundled OFL fonts (Inter, JetBrains Mono, Noto Serif) plus faces //! - [`fonts`]: font database — bundled OFL fonts (Inter, JetBrains Mono, Noto Serif), the optional
//! craft-fonts ([`fonts::CRAFT_FONTS`], Japanese; empty unless built with `CRAFT_FONTS_DIR`) plus faces
//! discovered by [`fonts::FontSource`]s (system font folders on native platforms; nothing on the //! discovered by [`fonts::FontSource`]s (system font folders on native platforms; nothing on the
//! web unless the host registers font data), family/style resolution with synthetic bold/italic //! web unless the host registers font data), family/style resolution with synthetic bold/italic
//! and per-character fallback. //! and per-character fallback.
+89 -3
View File
@@ -230,13 +230,14 @@ impl Tokens {
} }
} }
/// Install fonts (Inter, Inter SemiBold, JetBrains Mono) and egui visuals. /// Every font family the theme defines (fallback fonts such as the Japanese fonts are added to each
/// Every font family the theme defines (fallback fonts such as the system Japanese font are added to /// of them).
/// each of them).
pub fn font_families() -> Vec<FontFamily> { pub fn font_families() -> Vec<FontFamily> {
vec![FontFamily::Proportional, FontFamily::Monospace, FontFamily::Name("semibold".into()), FontFamily::Name("medium".into())] vec![FontFamily::Proportional, FontFamily::Monospace, FontFamily::Name("semibold".into()), FontFamily::Name("medium".into())]
} }
/// Install fonts (Inter, Inter SemiBold, JetBrains Mono, then the Japanese craft-fonts when built with
/// `CRAFT_FONTS_DIR`) and egui visuals.
pub fn install(ctx: &egui::Context, t: &Tokens) { pub fn install(ctx: &egui::Context, t: &Tokens) {
let mut fonts = FontDefinitions::default(); let mut fonts = FontDefinitions::default();
fonts.font_data.insert("inter".into(), Arc::new(FontData::from_static(filmcraft_text::fonts::INTER_REGULAR))); fonts.font_data.insert("inter".into(), Arc::new(FontData::from_static(filmcraft_text::fonts::INTER_REGULAR)));
@@ -247,10 +248,32 @@ pub fn install(ctx: &egui::Context, t: &Tokens) {
fonts.families.entry(FontFamily::Monospace).or_default().insert(0, "jbmono".into()); fonts.families.entry(FontFamily::Monospace).or_default().insert(0, "jbmono".into());
fonts.families.insert(FontFamily::Name("semibold".into()), vec!["inter-semibold".into(), "inter".into()]); fonts.families.insert(FontFamily::Name("semibold".into()), vec!["inter-semibold".into(), "inter".into()]);
fonts.families.insert(FontFamily::Name("medium".into()), vec!["inter-medium".into(), "inter".into()]); fonts.families.insert(FontFamily::Name("medium".into()), vec!["inter-medium".into(), "inter".into()]);
add_craft_fonts(&mut fonts);
ctx.set_fonts(fonts); ctx.set_fonts(fonts);
apply_visuals(ctx, t); apply_visuals(ctx, t);
} }
/// Name of the egui font for a craft-fonts entry.
fn craft_font_name(f: &filmcraft_text::fonts::CraftFont) -> String {
format!("craft:{} {}", f.family, f.style)
}
/// Append the Japanese craft-fonts (empty unless built with `CRAFT_FONTS_DIR`) as the last fallbacks
/// of every font family, after the app's own fonts: BIZ UDPGothic first (bold before regular in
/// the "semibold" and "medium" families), then the other Japanese faces.
fn add_craft_fonts(fonts: &mut FontDefinitions) {
let jpan: Vec<_> = filmcraft_text::fonts::craft_japanese().collect();
for f in &jpan {
fonts.font_data.insert(craft_font_name(f), Arc::new(FontData::from_static(f.bytes)));
}
for (family, stack) in fonts.families.iter_mut() {
let heavy = matches!(family, FontFamily::Name(n) if matches!(n.as_ref(), "semibold" | "medium"));
let mut order = jpan.clone();
order.sort_by_key(|f| (!f.family.contains("Gothic"), (f.style == "Bold") != heavy));
stack.extend(order.iter().map(|f| craft_font_name(f)));
}
}
pub fn apply_visuals(ctx: &egui::Context, t: &Tokens) { pub fn apply_visuals(ctx: &egui::Context, t: &Tokens) {
let mut v = if t.kind == ThemeKind::Light { Visuals::light() } else { Visuals::dark() }; let mut v = if t.kind == ThemeKind::Light { Visuals::light() } else { Visuals::dark() };
v.panel_fill = t.panel_bg; v.panel_fill = t.panel_bg;
@@ -300,3 +323,66 @@ pub fn apply_visuals(ctx: &egui::Context, t: &Tokens) {
s.animation_time = 0.12; s.animation_time = 0.12;
}); });
} }
#[cfg(test)]
mod tests {
use super::*;
const JAPANESE: &str = "日本語の文字";
fn installed() -> egui::Context {
let ctx = egui::Context::default();
install(&ctx, &Tokens::for_kind(ThemeKind::default()));
let mut out = ctx.run_ui(egui::RawInput::default(), |_| {});
out.textures_delta.clear();
ctx
}
/// Built with craft-fonts: every font family ends with the Japanese faces (BIZ UDPGothic
/// first), and Japanese text gets real glyphs, not the replacement box.
#[test]
fn japanese_renders_with_craft_fonts() {
if filmcraft_text::fonts::craft_japanese().next().is_none() {
eprintln!("SKIPPED: built without craft-fonts (set CRAFT_FONTS_DIR to run)");
return;
}
let ctx = installed();
ctx.fonts_mut(|fonts| {
let defs = fonts.definitions().clone();
for (family, stack) in &defs.families {
let first = stack.iter().position(|n| n.starts_with("craft:")).unwrap_or(stack.len());
assert!(first > 0 && stack[first..].iter().all(|n| n.starts_with("craft:")), "{family:?}: {stack:?}");
assert!(stack[first].starts_with("craft:BIZ UDPGothic"), "{family:?}: {stack:?}");
}
for family in [FontFamily::Proportional, FontFamily::Monospace] {
let font = FontId::new(13.0, family);
for ch in JAPANESE.chars() {
assert!(fonts.has_glyph(&font, ch), "missing {ch} in {font:?}");
}
}
});
// laid out and drawn: the replacement box (U+FFFD) would show as one glyph per character
// from the fallback face; a real face gives each character its own width
let galley = ctx.fonts_mut(|f| f.layout_no_wrap(JAPANESE.into(), FontId::new(13.0, FontFamily::Proportional), Color32::WHITE));
assert_eq!(galley.rows.iter().map(|r| r.glyphs.len()).sum::<usize>(), JAPANESE.chars().count());
assert!(galley.size().x > 13.0 * 4.0, "{:?}", galley.size());
}
/// Built without craft-fonts: the theme installs only the app's own fonts and Latin text
/// works as before.
#[test]
fn works_without_craft_fonts() {
let ctx = installed();
let n = filmcraft_text::fonts::craft_japanese().count();
ctx.fonts_mut(|fonts| {
let defs = fonts.definitions().clone();
assert_eq!(defs.font_data.keys().filter(|k| k.starts_with("craft:")).count(), n);
assert_eq!(defs.families[&FontFamily::Proportional].first().map(String::as_str), Some("inter"));
assert_eq!(defs.families[&FontFamily::Monospace].first().map(String::as_str), Some("jbmono"));
assert!(fonts.has_glyph(&FontId::new(13.0, FontFamily::Proportional), 'A'));
if n == 0 {
assert!(defs.families.values().flatten().all(|name| !name.starts_with("craft:")));
}
});
}
}
+28
View File
@@ -45,6 +45,26 @@ code 2 instead of a window.
Dev builds compile dependencies at `opt-level = 2` and workspace crates at `opt-level = 1`. For Dev builds compile dependencies at `opt-level = 2` and workspace crates at `opt-level = 1`. For
playback and codec speed, use `--release`. playback and codec speed, use `--release`.
### Building with craft-fonts
Font assets live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never in this
repo ([standard](https://github.com/storytold/craftrules/blob/main/standards/fonts.md)). It is an optional build input, not a Cargo
dependency:
```sh
git clone https://github.com/storytold/craft-fonts ../craft-fonts
CRAFT_FONTS_DIR=../craft-fonts cargo run --release -p filmcraft
```
`crates/text/build.rs` reads its `fonts/manifest.txt` and embeds the fonts as
`filmcraft_text::fonts::CRAFT_FONTS` (native: all of them; the web build: only BIZ UDPGothic
Regular, to keep the wasm small). The Japanese ones become the last fallback of every UI font family
(`crates/ui-egui/src/theme.rs`, BIZ UDPGothic first) and fallback faces in the text engine (Gothic
for sans text, Mincho for serif text). Without `CRAFT_FONTS_DIR`, `CRAFT_FONTS` is empty and
FilmCraft uses its bundled and system fonts. A `CRAFT_FONTS_DIR` that is not a checkout is a build
warning, or an error with `CRAFT_FONTS_REQUIRED=1` (release builds set both). Tests on these fonts'
glyphs skip when it is empty; when you touch fonts, run the gates both with and without it.
## 3. Quality gates ## 3. Quality gates
Every commit must pass all of these: Every commit must pass all of these:
@@ -59,6 +79,8 @@ Every commit must pass all of these:
| wasm | `cargo xtask wasm` (checks every L0–L4 crate, `filmcraft-ui-egui` and `filmcraft-web` for `wasm32-unknown-unknown`) | | wasm | `cargo xtask wasm` (checks every L0–L4 crate, `filmcraft-ui-egui` and `filmcraft-web` for `wasm32-unknown-unknown`) |
| All of the above | `cargo xtask ci` (runs clippy and tests with `--release`) | | All of the above | `cargo xtask ci` (runs clippy and tests with `--release`) |
Run them with `CRAFT_FONTS_DIR` set too when you change font code ([craft-fonts](#building-with-craft-fonts)).
`cargo xtask` is an alias in `.cargo/config.toml` for `cargo run -p xtask --`. `cargo xtask` is an alias in `.cargo/config.toml` for `cargo run -p xtask --`.
### Never crash ### Never crash
@@ -209,6 +231,12 @@ In short:
- Screenshots show only FilmCraft (or other open projects) with media we generated or that is - Screenshots show only FilmCraft (or other open projects) with media we generated or that is
openly licensed. openly licensed.
- **Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), not here.**
Don't commit new font files to this repo (the Latin UI fonts already in `assets/fonts/` stay): add
the font to craft-fonts (file, licence, manifest line, `ATTRIBUTION.md` row) and use it through
`filmcraft_text::fonts::CRAFT_FONTS`. See [Building with craft-fonts](#building-with-craft-fonts)
and [`craftrules/standards/fonts.md`](https://github.com/storytold/craftrules/blob/main/standards/fonts.md).
For each asset file `X`, commit a sidecar `X.attribution`. This is the real one for For each asset file `X`, commit a sidecar `X.attribution`. This is the real one for
`assets/fonts/Inter-Regular.ttf`: `assets/fonts/Inter-Regular.ttf`:
+15 -1
View File
@@ -44,12 +44,26 @@ warn() {
if [ -n "${GITHUB_ACTIONS:-}" ]; then echo "::warning::$*"; else echo "warning: $*" >&2; fi if [ -n "${GITHUB_ACTIONS:-}" ]; then echo "::warning::$*"; else echo "warning: $*" >&2; fi
} }
# Copy licence and readme files that exist into a package directory. # Copy licence and readme files that exist into a package directory, plus the licences of the
# craft-fonts fonts the build embedded (only when built with CRAFT_FONTS_DIR).
copy_docs() { copy_docs() {
local dest="$1" f local dest="$1" f
for f in README.md LICENSE LICENSE-MIT LICENSE-APACHE COPYRIGHT; do for f in README.md LICENSE LICENSE-MIT LICENSE-APACHE COPYRIGHT; do
if [ -f "$ROOT/$f" ]; then cp "$ROOT/$f" "$dest/"; fi if [ -f "$ROOT/$f" ]; then cp "$ROOT/$f" "$dest/"; fi
done done
copy_font_licences "$dest"
}
# Copy each craft-fonts licence ($CRAFT_FONTS_DIR/fonts/<family>/OFL.txt) to <dest>/OFL-<family>.txt.
# Does nothing without CRAFT_FONTS_DIR.
copy_font_licences() {
local dest="$1" lic family
if [ -z "${CRAFT_FONTS_DIR:-}" ]; then return 0; fi
for lic in "$CRAFT_FONTS_DIR"/fonts/*/OFL.txt; do
[ -f "$lic" ] || continue
family="$(basename "$(dirname "$lic")")"
cp "$lic" "$dest/OFL-$family.txt"
done
} }
# Portable SHA-256 of a file (prints just the hash). # Portable SHA-256 of a file (prints just the hash).
+2
View File
@@ -108,6 +108,8 @@ sed -e "s/@VERSION@/$VERSION/g" -e "s/@SHORT_VERSION@/$SHORT_VERSION/g" \
"$HERE/Info.plist.in" >"$APP/Contents/Info.plist" "$HERE/Info.plist.in" >"$APP/Contents/Info.plist"
plutil -lint "$APP/Contents/Info.plist" plutil -lint "$APP/Contents/Info.plist"
printf 'APPL????' >"$APP/Contents/PkgInfo" printf 'APPL????' >"$APP/Contents/PkgInfo"
# licences of the embedded craft-fonts fonts (builds with CRAFT_FONTS_DIR)
copy_font_licences "$APP/Contents/Resources"
# Sign inside-out: nested code first, then the bundle itself (no --deep on the final signature). # Sign inside-out: nested code first, then the bundle itself (no --deep on the final signature).
# Today the only nested code is the main executable; frameworks/helpers would be signed here too. # Today the only nested code is the main executable; frameworks/helpers would be signed here too.
+6
View File
@@ -110,6 +110,12 @@ foreach ($f in 'README.md', 'LICENSE', 'LICENSE-MIT', 'LICENSE-APACHE') {
$p = Join-Path $Root $f $p = Join-Path $Root $f
if (Test-Path $p) { Copy-Item $p $Portable } if (Test-Path $p) { Copy-Item $p $Portable }
} }
# Licences of the embedded craft-fonts fonts (builds with CRAFT_FONTS_DIR) as OFL-<family>.txt.
if ($env:CRAFT_FONTS_DIR) {
foreach ($lic in Get-ChildItem -Path (Join-Path $env:CRAFT_FONTS_DIR 'fonts\*\OFL.txt') -ErrorAction SilentlyContinue) {
Copy-Item $lic.FullName (Join-Path $Portable "OFL-$($lic.Directory.Name).txt")
}
}
$Zip = Join-Path $Dist "filmcraft-$Version-windows-$Arch-portable.zip" $Zip = Join-Path $Dist "filmcraft-$Version-windows-$Arch-portable.zip"
Remove-Item -Force $Zip -ErrorAction SilentlyContinue Remove-Item -Force $Zip -ErrorAction SilentlyContinue
Compress-Archive -Path $Portable -DestinationPath $Zip Compress-Archive -Path $Portable -DestinationPath $Zip