E21: restore Latin coverage and enforce required font inputs

This commit is contained in:
skylartaylor
2026-10-25 05:21:56 +01:00
parent 8372d51552
commit 87cc53b098
7 changed files with 365 additions and 61 deletions
+6 -6
View File
@@ -17,15 +17,14 @@ concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
# The migration's font pin gate stays a local preflight. A reusable-workflow job cannot run
# custom steps or export CRAFT_FONTS_REQUIRED into gate.yml, so the check is its own job and
# the shared gate waits for it. Keep this pin identical to the `craft-fonts-ref` below,
# freebsd.yml and release.yml.
# Keep the early manifest preflight separate from the reusable workflow. The shared gate
# also runs with require-craft-fonts: true, so the build script enforces actual required faces.
# Keep this pin identical to gate, freebsd.yml and release.yml.
font-pin:
runs-on: ubuntu-latest
env:
# Same craft-fonts commit as release.yml.
CRAFT_FONTS_REF: b076417b5880bd4154bd22f00695b1d28c6bea83
CRAFT_FONTS_REF: 37827adc41fd165f9a9234ac40f94dbad353aa69
steps:
- name: Check out craft-fonts
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -55,7 +54,8 @@ jobs:
uses: storytold/craft-common-actions/.github/workflows/gate.yml@main
with:
# Same craft-fonts commit as release.yml.
craft-fonts-ref: b076417b5880bd4154bd22f00695b1d28c6bea83
craft-fonts-ref: 37827adc41fd165f9a9234ac40f94dbad353aa69
require-craft-fonts: true
packages: >-
libasound2-dev libxkbcommon-dev libwayland-dev libx11-dev libxrandr-dev libxi-dev
libgl1-mesa-dev
+1 -1
View File
@@ -31,4 +31,4 @@ jobs:
# CRAFT_FONTS_DIR to its tests but has no CRAFT_FONTS_REQUIRED input yet, so it does not
# qualify the required Chinese input after the pin advances; that gate stays explicitly
# pending in docs/egui-037-migration-plan.md section 5.1.
craft-fonts-ref: b076417b5880bd4154bd22f00695b1d28c6bea83
craft-fonts-ref: 37827adc41fd165f9a9234ac40f94dbad353aa69
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
# Keep identical to ci.yml and freebsd.yml. The shared release workflow sets
# CRAFT_FONTS_REQUIRED=1 in its packaging steps, so a pin without the reviewed Chinese
# manifest fails the release instead of silently skipping it.
craft-fonts-ref: b076417b5880bd4154bd22f00695b1d28c6bea83
craft-fonts-ref: 37827adc41fd165f9a9234ac40f94dbad353aa69
version: ${{ inputs.version }}
secrets:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
+241 -17
View File
@@ -4,9 +4,10 @@
//! `CRAFT_FONTS`, Latin first. Native builds additionally read
//! `fonts/simplified-chinese-manifest.txt`; it is optional for ordinary local builds but a required
//! native input (`CRAFT_FONTS_REQUIRED`) fails without it. Unset, `CRAFT_FONTS` is empty. Web
//! (wasm32) builds embed only Inter
//! Regular/Bold and BIZ UDPGothic Regular, to stay within the web size budget. It only reads the
//! local checkout: no network.
//! (wasm32) builds embed only Inter Regular/Bold (or a supported Variable face) and BIZ UDPGothic
//! Regular, to stay within the web size budget. Required builds also verify those faces survive
//! exclusion (and, on wasm, the web filter), so a pin without the Latin manifest fails instead of
//! silently dropping Inter. It only reads the local checkout: no network.
use std::fmt::Write as _;
use std::path::PathBuf;
@@ -23,6 +24,21 @@ fn is_excluded_family(family: &str) -> bool {
|| family.starts_with("Noto Sans Mono CJK")
}
/// The generated initialisers plus the faces that were actually embedded after exclusion and web
/// filtering. Validation must use the latter, not the raw manifest text.
#[derive(Default)]
struct FontInput {
source: String,
faces: Vec<(String, String)>,
}
impl FontInput {
fn append(&mut self, other: Self) {
self.source.push_str(&other.source);
self.faces.extend(other.faces);
}
}
fn main() {
println!("cargo::rerun-if-env-changed=CRAFT_FONTS_DIR");
println!("cargo::rerun-if-env-changed=CRAFT_FONTS_REQUIRED");
@@ -44,22 +60,40 @@ fn main() {
/// One `CraftFont { .. }` initialiser per manifest line.
fn input_fonts(dir: Option<&std::path::Path>, wasm: bool, required: bool) -> Result<String, String> {
load_fonts(dir, wasm, required).map(|input| input.source)
}
fn load_fonts(dir: Option<&std::path::Path>, wasm: bool, required: bool) -> Result<FontInput, String> {
match dir {
Some(dir) => craft_fonts(dir, wasm, required),
None if required => Err("CRAFT_FONTS_REQUIRED is set but CRAFT_FONTS_DIR is missing".into()),
None => Ok(String::new()),
None => Ok(FontInput::default()),
}
}
fn craft_fonts(dir: &std::path::Path, wasm: bool, required: bool) -> Result<String, String> {
let mut out = String::new();
fn craft_fonts(dir: &std::path::Path, wasm: bool, required: bool) -> Result<FontInput, String> {
let mut input = FontInput::default();
// The Latin presentation fonts are optional within craft-fonts (older checkouts lack them).
// A malformed or incomplete optional manifest is skipped as a unit: the main Japanese/symbol/RTL
// faces and the native Chinese addition stay usable, and required builds still fail validation.
let latin = dir.join("fonts/latin-manifest.txt");
println!("cargo::rerun-if-changed={}", latin.display());
if latin.exists() {
out.push_str(&manifest(dir, &latin, wasm)?);
let latin_exists = match latin.try_exists() {
Ok(exists) => exists,
Err(e) if required => return Err(format!("{}: {e}", latin.display())),
Err(e) => {
println!("cargo::warning=skipping optional Latin fonts: {}: {e}", latin.display());
false
}
};
if latin_exists {
match manifest(dir, &latin, wasm) {
Ok(entries) => input.append(entries),
Err(e) if required => return Err(e),
Err(e) => println!("cargo::warning=skipping optional Latin fonts: {e}"),
}
}
out.push_str(&manifest(dir, &dir.join("fonts/manifest.txt"), wasm)?);
input.append(manifest(dir, &dir.join("fonts/manifest.txt"), wasm)?);
// Native consumers opt in; older craft-fonts checkouts need not carry this manifest.
// An invalid optional addition must never discard the main Japanese/symbol/RTL faces.
if !wasm {
@@ -75,7 +109,7 @@ fn craft_fonts(dir: &std::path::Path, wasm: bool, required: bool) -> Result<Stri
};
if exists {
match manifest(dir, &chinese, false) {
Ok(entries) => out.push_str(&entries),
Ok(entries) => input.append(entries),
Err(e) if required => return Err(e),
Err(e) => println!("cargo::warning=skipping optional Chinese fonts: {e}"),
}
@@ -85,13 +119,16 @@ fn craft_fonts(dir: &std::path::Path, wasm: bool, required: bool) -> Result<Stri
return Err(format!("{}: CRAFT_FONTS_REQUIRED is set but the simplified-Chinese font manifest is missing", chinese.display()));
}
}
Ok(out)
if required {
validate_required_faces(&input.faces, wasm)?;
}
Ok(input)
}
fn manifest(dir: &std::path::Path, manifest: &std::path::Path, wasm: bool) -> Result<String, String> {
fn manifest(dir: &std::path::Path, manifest: &std::path::Path, wasm: bool) -> Result<FontInput, String> {
println!("cargo::rerun-if-changed={}", manifest.display());
let text = std::fs::read_to_string(manifest).map_err(|e| format!("{}: {e}", manifest.display()))?;
let mut out = String::new();
let mut input = FontInput::default();
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 {
@@ -107,14 +144,41 @@ fn manifest(dir: &std::path::Path, manifest: &std::path::Path, wasm: bool) -> Re
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();
input.faces.push(((*family).to_owned(), (*style).to_owned()));
let _ = writeln!(
out,
input.source,
" CraftFont {{ family: {family:?}, style: {style:?}, scripts: &[{}], bytes: include_bytes!({:?}) }},",
scripts.join(", "),
path.display().to_string(),
);
}
Ok(out)
Ok(input)
}
/// Required builds must actually carry the faces the platform promises: Inter Regular and Bold
/// (or one Variable face) and BIZ UDPGothic Regular everywhere, plus LXGW WenKai Lite Regular for
/// native builds. This runs after exclusion and, on wasm, the web filter, so an absent manifest,
/// empty/all-excluded rows, a wrong-family row or a partial catalog cannot satisfy the gate.
fn validate_required_faces(faces: &[(String, String)], wasm: bool) -> Result<(), String> {
let has = |family: &str, style: &str| faces.iter().any(|(f, s)| f == family && s == style);
let inter = has("Inter", "Variable") || (has("Inter", "Regular") && has("Inter", "Bold"));
let biz = has("BIZ UDPGothic", "Regular");
let chinese = wasm || has("LXGW WenKai Lite", "Regular");
let mut missing = Vec::new();
if !inter {
missing.push("Inter Regular/Bold (or Inter Variable)");
}
if !biz {
missing.push("BIZ UDPGothic Regular");
}
if !chinese {
missing.push("LXGW WenKai Lite Regular");
}
if missing.is_empty() {
Ok(())
} else {
Err(format!("CRAFT_FONTS_REQUIRED is set but the embedded font set after filtering lacks {}", missing.join(" and ")))
}
}
#[cfg(test)]
@@ -123,6 +187,7 @@ mod tests {
use std::sync::atomic::{AtomicU64, Ordering};
static NEXT_FIXTURE: AtomicU64 = AtomicU64::new(0);
const CHINESE_ROW: &str = "LXGW WenKai Lite | Regular | fonts/fixture.ttf | Hani,Hans | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n";
struct InputFixture(PathBuf);
@@ -140,6 +205,25 @@ mod tests {
Self(path)
}
fn main(&self, text: &str) {
std::fs::write(self.0.join("fonts/manifest.txt"), text).unwrap();
}
fn latin(&self, text: &str) {
std::fs::write(self.0.join("fonts/latin-manifest.txt"), text).unwrap();
}
fn no_latin(&self) {
let _ = std::fs::remove_file(self.0.join("fonts/latin-manifest.txt"));
}
fn valid_latin(&self) {
self.latin(
"Inter | Regular | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n\
Inter | Bold | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n",
);
}
fn optional(&self, text: &str) {
std::fs::write(self.0.join("fonts/simplified-chinese-manifest.txt"), text).unwrap();
}
@@ -172,8 +256,9 @@ mod tests {
#[test]
fn native_opt_in_does_not_change_wasm_embedded_bytes() {
let fixture = InputFixture::new();
fixture.valid_latin();
let before = input_fonts(Some(&fixture.0), true, true).unwrap();
fixture.optional("LXGW WenKai Lite | Regular | fonts/fixture.ttf | Hani,Hans | OFL-1.1 | OFL.txt | fixture | https://example.invalid");
fixture.optional(CHINESE_ROW);
let native = input_fonts(Some(&fixture.0), false, true).unwrap();
assert!(native.contains("LXGW WenKai Lite"));
assert_eq!(input_fonts(Some(&fixture.0), true, true).unwrap(), before);
@@ -184,6 +269,7 @@ mod tests {
#[test]
fn required_native_input_requires_the_chinese_manifest() {
let fixture = InputFixture::new();
fixture.valid_latin();
assert!(input_fonts(Some(&fixture.0), false, true).unwrap_err().contains("simplified-Chinese"));
// Optional native builds and wasm keep working without it.
assert!(input_fonts(Some(&fixture.0), false, false).is_ok());
@@ -210,7 +296,8 @@ mod tests {
assert!(!is_excluded_family(family), "{family} is an allowed input");
}
let fixture = InputFixture::new();
let mut rows = String::new();
fixture.valid_latin();
let mut rows = String::from(CHINESE_ROW);
for family in ["Noto Sans CJK SC", "Noto Serif CJK JP", "Noto Sans Mono CJK KR"] {
rows.push_str(&format!("{family} | Regular | fonts/fixture.ttf | Hani | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n"));
}
@@ -218,5 +305,142 @@ mod tests {
let native = input_fonts(Some(&fixture.0), false, true).unwrap();
assert!(!native.contains("Noto Sans CJK") && !native.contains("Noto Serif CJK") && !native.contains("Noto Sans Mono CJK"));
assert!(native.contains("BIZ UDPGothic"), "the allowed main manifest is retained");
assert!(native.contains("LXGW WenKai Lite"), "the valid Chinese row is retained");
}
#[test]
fn required_native_chinese_manifest_must_emit_the_reviewed_face() {
let fixture = InputFixture::new();
fixture.valid_latin();
let excluded = "Noto Sans CJK SC | Regular | fonts/fixture.ttf | Hani | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n\
Noto Serif CJK JP | Regular | fonts/fixture.ttf | Hani | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n";
let wrong_family = "LXGW WenKai | Regular | fonts/fixture.ttf | Hani,Hans | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n";
for rows in ["", excluded, wrong_family] {
fixture.optional(rows);
let e = input_fonts(Some(&fixture.0), false, true).unwrap_err();
assert!(e.contains("LXGW WenKai Lite Regular"), "rows={rows:?}: {e}");
assert!(input_fonts(Some(&fixture.0), false, false).is_ok(), "optional builds still preserve the valid core");
}
fixture.optional(CHINESE_ROW);
let native = input_fonts(Some(&fixture.0), false, true).unwrap();
assert!(native.contains("CraftFont { family: \"LXGW WenKai Lite\", style: \"Regular\""), "{native}");
assert!(native.contains("BIZ UDPGothic") && native.contains("Inter"), "the rest of the required catalog is retained");
}
#[test]
fn optional_broken_latin_manifest_is_skipped_without_losing_core_or_chinese() {
let fixture = InputFixture::new();
fixture.optional(CHINESE_ROW);
let missing_asset = "Inter | Regular | fonts/missing.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n";
for latin in [None, Some("malformed row"), Some(missing_asset)] {
match latin {
Some(text) => fixture.latin(text),
None => fixture.no_latin(),
}
for wasm in [false, true] {
let catalog = input_fonts(Some(&fixture.0), wasm, false).unwrap();
assert!(catalog.contains("BIZ UDPGothic"), "wasm={wasm}: the main manifest is retained");
assert!(!catalog.contains("Inter"), "wasm={wasm}: the broken Latin manifest is skipped as a unit");
if !wasm {
assert!(catalog.contains("LXGW WenKai Lite"), "the valid Chinese addition is retained");
}
}
assert!(input_fonts(Some(&fixture.0), false, true).is_err(), "required builds must not skip the missing required faces");
}
}
#[test]
fn full_prior_latin_catalog_keeps_permitted_native_families() {
let fixture = InputFixture::new();
fixture.latin(include_str!("tests/fixtures/latin-families.txt"));
fixture.optional(CHINESE_ROW);
let native = input_fonts(Some(&fixture.0), false, true).unwrap();
assert_eq!(native.matches("CraftFont {").count(), 45, "43 permitted Latin faces plus BIZ and LXGW");
for family in [
"Carlito",
"Caladea",
"Liberation Serif",
"Montserrat",
"Lato",
"Open Sans",
"Roboto",
"Merriweather",
"Playfair Display",
"Poppins",
"Nunito Sans",
] {
assert!(native.contains(family), "missing prior presentation family {family}");
}
let web = input_fonts(Some(&fixture.0), true, true).unwrap();
assert_eq!(web.matches("CraftFont {").count(), 3, "Inter Regular/Bold and BIZ only");
for catalog in [&native, &web] {
assert!(!catalog.contains("Source Sans 3"));
assert!(!catalog.contains("Source Serif 4"));
}
}
#[test]
fn required_builds_need_the_latin_catalog_for_inter() {
let fixture = InputFixture::new();
fixture.optional(CHINESE_ROW);
for wasm in [true, false] {
let e = input_fonts(Some(&fixture.0), wasm, true).unwrap_err();
assert!(e.contains("Inter Regular/Bold"), "wasm={wasm}: {e}");
assert!(input_fonts(Some(&fixture.0), wasm, false).is_ok(), "optional builds still work");
}
}
#[test]
fn required_builds_reject_partial_or_unrelated_faces() {
let fixture = InputFixture::new();
fixture.optional(CHINESE_ROW);
for latin in [
"Inter | Regular | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n",
"Inter | Bold | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n",
"Inter | Italic | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n\
Inter | SemiBold | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n",
"Carlito | Regular | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n",
"Source Sans 3 | Regular | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n\
Source Sans 3 | Bold | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n",
] {
fixture.latin(latin);
for wasm in [true, false] {
let e = input_fonts(Some(&fixture.0), wasm, true).unwrap_err();
assert!(e.contains("Inter Regular/Bold"), "wasm={wasm}: {e}");
assert!(input_fonts(Some(&fixture.0), wasm, false).is_ok(), "optional builds still work");
}
}
}
#[test]
fn required_builds_need_biz_udpgothic_regular() {
let fixture = InputFixture::new();
fixture.valid_latin();
fixture.main(
"Inter | Regular | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n\
Inter | Bold | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n",
);
fixture.optional(CHINESE_ROW);
for wasm in [true, false] {
let e = input_fonts(Some(&fixture.0), wasm, true).unwrap_err();
assert!(e.contains("BIZ UDPGothic Regular"), "wasm={wasm}: {e}");
assert!(input_fonts(Some(&fixture.0), wasm, false).is_ok(), "optional builds still work");
}
}
#[test]
fn required_builds_accept_static_or_variable_inter_with_biz() {
let fixture = InputFixture::new();
fixture.valid_latin();
fixture.optional(CHINESE_ROW);
for wasm in [true, false] {
let static_catalog = input_fonts(Some(&fixture.0), wasm, true).unwrap();
assert!(static_catalog.contains("Inter") && static_catalog.contains("BIZ UDPGothic"));
}
fixture.latin("Inter | Variable | fonts/fixture.ttf | Latn | OFL-1.1 | OFL.txt | fixture | https://example.invalid\n");
for wasm in [true, false] {
let variable_catalog = input_fonts(Some(&fixture.0), wasm, true).unwrap();
assert!(variable_catalog.contains("Inter") && variable_catalog.contains("BIZ UDPGothic"));
}
}
}
+20
View File
@@ -411,14 +411,34 @@ mod tests {
#[test]
fn craft_fonts_latin_when_present() {
let required = option_env!("CRAFT_FONTS_REQUIRED").is_some();
if !CRAFT_FONTS.iter().any(|f| f.family == "Inter") {
assert!(!required, "CRAFT_FONTS_REQUIRED must embed Inter rather than skip the optional Latin manifest");
eprintln!("skipped: built without craft-fonts' Latin manifest (set CRAFT_FONTS_DIR)");
return;
}
let inter = |style: &str| CRAFT_FONTS.iter().find(|f| f.family == "Inter" && f.style == style);
let (regular, bold, variable) = (inter("Regular"), inter("Bold"), inter("Variable"));
if required {
assert!(
variable.is_some() || (regular.is_some() && bold.is_some()),
"CRAFT_FONTS_REQUIRED must embed Inter Regular and Bold (or a Variable face)"
);
}
let db = FontDb::with_font_dirs(Vec::new());
db.set_system_fallback(false);
assert_eq!(db.face("Inter", "Regular").family, "Inter");
assert_eq!(db.face("Aptos", "Regular").family, "Inter");
if let (Some(regular), Some(bold)) = (regular, bold) {
// The build input carries one entry per face; real static Bold must not be the Regular
// file relabelled in the manifest.
assert_ne!(regular.bytes, bold.bytes, "Inter Bold must be a distinct face file from Regular");
let (r, b) = (db.face("Inter", "Regular"), db.face("Inter", "Bold"));
assert_eq!((r.family.as_str(), r.style.as_str()), ("Inter", "Regular"));
assert_eq!((b.family.as_str(), b.style.as_str()), ("Inter", "Bold"));
assert_ne!(r.id(), b.id(), "Inter Bold must load as its own face");
assert!(b.weight > r.weight, "Inter Bold weight {} is not above Regular {}", b.weight, r.weight);
}
if CRAFT_FONTS.iter().any(|f| f.family == "Carlito") {
assert_eq!(db.face("Calibri", "Bold").family, "Carlito");
}
+52
View File
@@ -0,0 +1,52 @@
# Synthetic paths; family/style inventory from craft-fonts 76ba5a0496503c0e2841abd626295038cbe07cde.
Inter | Regular | fonts/fixture.ttf | Latn
Inter | Italic | fonts/fixture.ttf | Latn
Inter | SemiBold | fonts/fixture.ttf | Latn
Inter | Bold | fonts/fixture.ttf | Latn
Carlito | Regular | fonts/fixture.ttf | Latn
Carlito | Bold | fonts/fixture.ttf | Latn
Carlito | Italic | fonts/fixture.ttf | Latn
Carlito | Bold Italic | fonts/fixture.ttf | Latn
Caladea | Regular | fonts/fixture.ttf | Latn
Caladea | Bold | fonts/fixture.ttf | Latn
Caladea | Italic | fonts/fixture.ttf | Latn
Caladea | Bold Italic | fonts/fixture.ttf | Latn
Liberation Sans | Regular | fonts/fixture.ttf | Latn
Liberation Sans | Bold | fonts/fixture.ttf | Latn
Liberation Sans | Italic | fonts/fixture.ttf | Latn
Liberation Sans | Bold Italic | fonts/fixture.ttf | Latn
Liberation Serif | Regular | fonts/fixture.ttf | Latn
Liberation Serif | Bold | fonts/fixture.ttf | Latn
Liberation Serif | Italic | fonts/fixture.ttf | Latn
Liberation Serif | Bold Italic | fonts/fixture.ttf | Latn
Liberation Mono | Regular | fonts/fixture.ttf | Latn
Liberation Mono | Bold | fonts/fixture.ttf | Latn
Liberation Mono | Italic | fonts/fixture.ttf | Latn
Liberation Mono | Bold Italic | fonts/fixture.ttf | Latn
Source Sans 3 | Regular | fonts/fixture.ttf | Latn
Source Sans 3 | Italic | fonts/fixture.ttf | Latn
Source Sans 3 | Semibold | fonts/fixture.ttf | Latn
Source Sans 3 | Bold | fonts/fixture.ttf | Latn
Source Serif 4 | Regular | fonts/fixture.ttf | Latn
Source Serif 4 | Italic | fonts/fixture.ttf | Latn
Source Serif 4 | Semibold | fonts/fixture.ttf | Latn
Source Serif 4 | Bold | fonts/fixture.ttf | Latn
Montserrat | Variable | fonts/fixture.ttf | Latn
Montserrat | Italic Variable | fonts/fixture.ttf | Latn
Lato | Regular | fonts/fixture.ttf | Latn
Lato | Bold | fonts/fixture.ttf | Latn
Lato | Italic | fonts/fixture.ttf | Latn
Open Sans | Variable | fonts/fixture.ttf | Latn
Open Sans | Italic Variable | fonts/fixture.ttf | Latn
Roboto | Variable | fonts/fixture.ttf | Latn
Roboto | Italic Variable | fonts/fixture.ttf | Latn
Merriweather | Regular | fonts/fixture.ttf | Latn
Merriweather | Bold | fonts/fixture.ttf | Latn
Merriweather | Italic | fonts/fixture.ttf | Latn
Playfair Display | Variable | fonts/fixture.ttf | Latn
Playfair Display | Italic Variable | fonts/fixture.ttf | Latn
Poppins | Regular | fonts/fixture.ttf | Latn
Poppins | Bold | fonts/fixture.ttf | Latn
Poppins | Italic | fonts/fixture.ttf | Latn
Nunito Sans | Variable | fonts/fixture.ttf | Latn
Nunito Sans | Italic Variable | fonts/fixture.ttf | Latn
+44 -36
View File
@@ -1,10 +1,12 @@
# DeckCraft: egui 0.37 migration plan
Status update (2026-10-25): local implementation includes main `6a1c7354c152564c33d8489f6ea07ac585da0651`, preserving its animation changes unchanged. All eight CI stages passed: 1,995 passing test executions, zero failed, six ignored across 60 summaries. Strict lint, assets, layering and WASM checks passed; the integration received independent review. Pins and lockfile are unchanged. Native/browser GUI, native IME and paired application performance remain open. Publication requires separate explicit approval; human review and human merge are required.
Current candidate font pin: `37827adc41fd165f9a9234ac40f94dbad353aa69` restores the complete prior Latin catalog alongside the migration fallbacks. All three workflows now use that revision; it must be published before this app branch.
Status update (2026-10-25): local implementation includes main `6a1c7354c152564c33d8489f6ea07ac585da0651`, preserving its animation changes unchanged. Managed run `egui037-deckcraft-font-final-ci-20261025-0724` passed the focused font tests, all eight CI stages and a separate required-font WASM check: 2,045 passing test executions, zero failures and six ignored across 63 summaries. These are execution counts, including repeated tests. Strict lint, assets, layering and full workspace WASM checks passed. Required native input now verifies Inter, BIZ and LXGW after filtering; required web input verifies Inter and BIZ. Broken optional Latin input preserves the valid core fonts. Fresh independent review found no blocking issue. The Cargo dependency pins and lockfile are unchanged by this font follow-up. Native/browser GUI, native IME and paired application performance remain open. Publication requires separate explicit approval; human review and human merge are required.
The original planning record below is historical.
## Current qualification — 2026-10-25
## Earlier qualification before the combined font input — 2026-10-25
The local candidate reconciles `main` through
`38d2ade2b77de39c0e06278ddfd44d1082ff80a8` with the migration base
@@ -17,8 +19,10 @@ The final regression additions exercise the Notes accessibility toggle, no-docum
Open/Actions/Preferences/File-menu shortcuts, ignored document-only shortcuts, and
typing/IME isolation. Earlier validation failures and source-completeness warnings
below describe retained history; the current review packet records the complete
commit and its parents. Native/browser workflows and paired app performance remain
open. No publication or merge is authorized by these results.
commit and its parents. **The current review/approval packet is stale for publication
pending a combined font pin:** `b076417…` has no Latin manifest/Inter, so the pinned
input cannot supply the default document family. Native/browser workflows and paired
app performance remain open. No publication or merge is authorized by these results.
## Historical planning and review records
@@ -706,28 +710,32 @@ so its root patch and lock are not crates.io publication artifacts, but releases
still resolve the public storytold revisions. No stage is pushed or published without fresh human
approval of the exact contents and validation.
**Font pin gate (before publication, not local work).** The native Simplified-Chinese fallback is
currently qualified against a local, dirty `craft-fonts-implementation` worktree: its
`fonts/simplified-chinese-manifest.txt`, `fonts/lxgw-wenkai-lite/` files and `fonts/manifest.txt`
row are uncommitted. That is expected unpublished work and must not be committed or published from
the DeckCraft checkout; it does not block local builds and tests, which read that worktree through
`CRAFT_FONTS_DIR`. Before any publication or release claims the Chinese migration, commit the
reviewed manifest and face in `craft-fonts`, obtain an immutable full SHA, and update **all three**
pins together: `.github/workflows/ci.yml` (`CRAFT_FONTS_REF`), `.github/workflows/freebsd.yml` and
`.github/workflows/release.yml` (`craft-fonts-ref`). Do not invent or pre-commit a future SHA: the
current shared pin remains `43913056ce82a3fdad0ef69f6b88d1dc922a8104`, whose tree has no Chinese
manifest.
**Font pin gate (before publication, not local work; stale pending a combined pin).** The three
workflow pins currently name `b076417b5880bd4154bd22f00695b1d28c6bea83`, whose tree carries the
reviewed Chinese manifest but has no `fonts/latin-manifest.txt` or Inter. The earlier
`43913056ce82a3fdad0ef69f6b88d1dc922a8104` tree carried the Latin manifest/Inter but not the
Chinese input. The parent is restoring the licensed Inter faces in the central
`craft-fonts-implementation` checkout; that work is unpublished and must not be committed from the
DeckCraft checkout. Therefore this approval packet is **stale**: do not approve or publish it until
the reviewed Latin manifest/faces and Chinese manifest are committed together in `craft-fonts`, an
immutable full SHA is obtained, and **all three** pins move together:
`.github/workflows/ci.yml` (`CRAFT_FONTS_REF`), `.github/workflows/freebsd.yml` and
`.github/workflows/release.yml` (`craft-fonts-ref`). Do not invent or pre-commit a future SHA, and do
not advance only one workflow. `crates/fonts/build.rs` now validates required builds after exclusion
and, for wasm, the web filter: Inter Regular/Bold (or a supported Variable face) and BIZ UDPGothic
Regular must actually be embedded, so a partial pin fails instead of silently losing the default
document/UI face.
The required-input gate is explicit but cannot fire until that pin advances:
The required-input gate is explicit, but CI and FreeBSD qualification remain partial:
- `ci.yml` runs the `Font pin gate` as a local preflight job before the shared gate. While the
pin is the known pending `43913056…` commit it emits a warning; any *other* pin without
`fonts/simplified-chinese-manifest.txt` fails the job, so advancing the pin cannot silently
keep the gate off. The shared `gate.yml` job does not accept a `CRAFT_FONTS_REQUIRED` input or
- `ci.yml` runs the `Font pin gate` as a local preflight job before the shared gate. It checks the
Chinese manifest but not the Latin manifest, so the build script's required-mode validation is
the guard for Inter. The shared `gate.yml` job does not accept a `CRAFT_FONTS_REQUIRED` input or
another workflow's exported environment, so the CI job verifies the pin but cannot yet force
the required-font environment into `cargo xtask ci`; release and FreeBSD notes below apply.
- The shared release workflow already sets `CRAFT_FONTS_REQUIRED=1` in every packaging step, so a
release on a pin without the manifest fails instead of shipping without Chinese fonts.
release on a pin without the Chinese manifest or the required Inter/BIZ faces fails instead of
shipping without them.
- The shared FreeBSD workflow passes `CRAFT_FONTS_DIR` to `cargo test` but currently exposes no
`CRAFT_FONTS_REQUIRED` input. Its required-font gate therefore remains **pending** until
`craft-common-actions` adds one (or the caller can otherwise set that environment); do not cite
@@ -1422,22 +1430,22 @@ This section records the final local state after
Earlier sections remain the historical plan and implementation amendments; where they describe an
earlier snapshot or an open plan-time gap, this section is the current local status.
### Source identity (uncommitted, not a qualified commit)
### Current review follow-up — 2026-10-25
- Local base HEAD: `770f4653bff0700b33d8475181e168da90a7ad4f`.
- Reviewed source: that base plus tracked edits and **five untracked source files**, which must be
included in any eventual commit:
`crates/engine/tests/show_paragraph_builds.rs`,
`crates/format/src/rtf.rs`,
`crates/pptx/tests/chart_data_labels.rs`,
`crates/ui-egui/src/field_frame_tests.rs` and
`crates/ui-egui/src/preferences_tests.rs`.
- `crates/format/src/lib.rs` declares `pub mod rtf;` and the UI modules declare the two
`*_tests` files, so a clean checkout or archive of `770f4653` alone cannot build or run the
reviewed source. `770f4653` is a local base for the dirty union, **not** a qualified commit.
- Focused reviewed hashes: `crates/ui-egui/src/thumbs.rs` `f21c09ba…`,
`crates/ui-egui/src/ime_tests.rs` `c433b847…`, `crates/ui-egui/src/lib.rs` `df8b48ba…`,
`Cargo.toml` `467ccc3e…`, `Cargo.lock` `33a3d3d8…`.
The candidate workflows now use `37827adc41fd165f9a9234ac40f94dbad353aa69`, combining
all prior Latin presentation fonts with the migration's core and Chinese fallbacks.
This commit must be published before these workflow pins can be fetched remotely.
The reusable CI gate explicitly sets `require-craft-fonts: true`; its current default
also enables required mode. The earlier statement that this cannot be passed to the
shared gate is obsolete.
The local base for this follow-up is `8372d51552c3ed943ffeb283f26a482e94a2c12f`.
The five source files listed in the earlier dirty-union review are already tracked in
that commit. The follow-up also includes `crates/fonts/tests/fixtures/latin-families.txt`;
its 51-row family/style inventory comes from the prior `76ba5a0` catalog, whose font
bytes are preserved by the combined candidate. Include that fixture with the code.
Full follow-up checks and the final local review commit are recorded in the next
publication packet; old dirty-union hashes below are historical evidence only.
### Final pins