Merge remote-tracking branch 'origin/main' into feat/all-tools-expand-view

# Conflicts:
#	ATTRIBUTION.toml
This commit is contained in:
alehel committed 2026-10-23 22:44:00 +02:00
commit baacdd4dc8
5 files changed
+63 -47

No files matched your search

+3 -2
View File
@@ -54,13 +54,14 @@ Anything else is forbidden, including assets whose licence you cannot prove from
- path or URL, title, author;
- source (URL, with version or commit);
- SPDX licence, and the licence file in the repo;
- SHA-256;
- SHA-256, except for translation catalogs (see below);
- how it is used.
- **Translation catalogs** (`crates/ui-egui/src/i18n/*.tsv`, `kind = "translation"`) are project files under the project licence. Their entries carry no `sha256`: git history records every change and who made it. Every other kind (fonts, images, data) is hashed.
- **`ATTRIBUTION.md`** is generated from it (`cargo xtask assets --write`). Never edit it by hand.
- **`NOTICE`** keeps the licence notices that the licences require.
- **`cargo xtask assets`** runs in `cargo xtask ci`. It fails if:
- an asset file in the repository has no entry;
- an entry's hash does not match;
- an entry's hash is missing or does not match (translation catalogs carry none);
- a licence is not on the allowlist;
- a licence file is missing;
- an image, icon or font names Adobe as author or source, except a font licensed under OFL-1.1 alone.
+1 -1
View File
@@ -2,7 +2,7 @@
<!-- Generated from ATTRIBUTION.toml by `cargo xtask assets --write`. Do not edit by hand. -->
Every asset PdfCraft includes, bundles or uses to build its published material, with its author, source and licence. The policy is in [AGENTS.md](AGENTS.md) §1. The machine-readable list, with SHA-256 hashes, is [ATTRIBUTION.toml](ATTRIBUTION.toml). Licence texts are kept beside the assets and summarised in [NOTICE](NOTICE).
Every asset PdfCraft includes, bundles or uses to build its published material, with its author, source and licence. The policy is in [AGENTS.md](AGENTS.md) §1. The machine-readable list, with the SHA-256 of every asset but the translation catalogs, is [ATTRIBUTION.toml](ATTRIBUTION.toml). Licence texts are kept beside the assets and summarised in [NOTICE](NOTICE).
## In this repository (288)
+2 -33
View File
@@ -7,12 +7,13 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Greek UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "691dafb857be58d31a9c18b0d38058864a1463fd92184c342e76c80964b7d343"
# ATTRIBUTION.toml: one entry per asset (AGENTS.md §1). The source of truth for provenance and licences.
# ATTRIBUTION.md is generated from this file: `cargo xtask assets --write`. Checked by `cargo xtask assets` (part of `xtask ci`).
#
# [[asset]] a file in this repository (committed or vendored); `sha256` must match the file.
# Translation catalogs (kind = "translation", crates/ui-egui/src/i18n/*.tsv) are project files
# under the project licence and carry no `sha256`: git history records their provenance.
# [[bundled]] a file compiled into PdfCraft by a Cargo dependency (its crate version must match Cargo.lock).
# [[fetched]] downloaded at build time by xtask (never committed), pinned by `sha256`.
# [[build_input]] an optional build input outside this repository (e.g. craft-fonts via CRAFT_FONTS_DIR);
@@ -30,7 +31,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "0ae8b903555e35dfe5a4ab245be07ddf02f8f0852db80c7513c30afc6052fc9b"
[[asset]]
path = "crates/ui-egui/src/i18n/zh-hant.tsv"
@@ -41,7 +41,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "8b5d3b451eb604428a065bf13c9de514981205d0f431164a0bac65a88da060ff"
[[asset]]
path = "crates/ui-egui/src/i18n/cs.tsv"
@@ -52,7 +51,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "f295d9e2808735aefce95f5b0f3cafef51d506668d81cdc81d26e7f1508b6229"
[[asset]]
path = "crates/ui-egui/src/i18n/pt-br.tsv"
@@ -63,7 +61,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "2af4de90c5100fbd4cf0c1b4656c6a6a09372d68d104ceca4baaed75e8fe3b9a"
[[asset]]
path = "crates/ui-egui/src/i18n/es.tsv"
@@ -74,7 +71,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "a73dfaae00f893eb2a7a99fa1018ddce25bca6c52835e6393d316484d75a74b7"
[[asset]]
path = "crates/ui-egui/src/i18n/fr.tsv"
@@ -85,7 +81,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "936ef1ef7c045e47388e2ff61bae306bd3bb056ab7a68abf8427fade52550507"
[[asset]]
path = "crates/ui-egui/src/i18n/de.tsv"
@@ -96,7 +91,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "d452a56975d2571b7b7eb2296c4a43a645d2cd4e49d68884e6f4573fe4312722"
[[asset]]
path = "crates/ui-egui/src/i18n/lo.tsv"
@@ -107,7 +101,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Lao UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "c350ce5f72c57923978408e8301e8f36ac9a74ddb72de0028b6a23b3d3c5f395"
[[asset]]
path = "crates/ui-egui/src/i18n/te.tsv"
@@ -118,7 +111,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "071b33c812bd363d293c022dc181e90d25476a18f6092a46f36921a0924be3f8"
[[asset]]
path = "crates/ui-egui/src/i18n/it.tsv"
@@ -129,7 +121,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "41d7aeb3ef977ed28e5592831c9eac4bd9dc1381075c28b21bfc9486f2686c93"
[[asset]]
path = "crates/ui-egui/src/i18n/pl.tsv"
@@ -140,7 +131,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "3e7a88430c901e13e0078441acffdf1ad7834491647ca6cf21d2d2ac0abd1fb1"
[[asset]]
path = "crates/ui-egui/src/i18n/ru.tsv"
@@ -151,7 +141,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "6bc64cb648ad410b44dd3f017426de3a5a9752babaf73ca99c77992fe38e782b"
[[asset]]
path = "crates/ui-egui/src/i18n/hu.tsv"
@@ -162,7 +151,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "187cfccd400f3359020014c93252d69dccc40902f386756a7827c07c19c12c05"
[[asset]]
path = "crates/ui-egui/src/i18n/bg.tsv"
@@ -173,7 +161,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "da770dbaec85ca063d4314b2d0288162217cdb61a6e12cdba59c09a4f33008cb"
[[asset]]
path = "crates/ui-egui/src/i18n/kk.tsv"
@@ -184,7 +171,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Kazakh UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "c85e6e349cb4284baff39d14ebdab7debe7110d5d4d38aca1ecb12019d351218"
[[asset]]
path = "crates/ui-egui/src/i18n/ja.tsv"
@@ -195,7 +181,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "c893242839bda4ccc56357d461d0d9f986d713ed0527a0abdadbe90e98600859"
[[asset]]
path = "crates/ui-egui/src/i18n/uk.tsv"
@@ -206,7 +191,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "dcff247e24984e4274a3ec4084b1f6a0a5198589f035b7e32b31ce6282067cf0"
[[asset]]
path = "crates/ui-egui/src/i18n/ar.tsv"
@@ -217,7 +201,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "482d8d1bc37e27225c9fe4a938859b773cde64266d3490963656c50464210858"
[[asset]]
path = "crates/ui-egui/src/i18n/ckb.tsv"
@@ -228,7 +211,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Central Kurdish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "f8976d3155dd67fb0fd67cf8ff85ad67964be2b052b8d97a68b12bf5d878366a"
[[asset]]
path = "crates/ui-egui/src/i18n/tr.tsv"
@@ -239,7 +221,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Turkish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "6e4dc51dc37a2d0d0752d5528a6288fe1e3809240ef5346ef5c2e02874de0092"
[[asset]]
path = "crates/ui-egui/src/i18n/lt.tsv"
@@ -250,7 +231,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Lithuanian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "2937d8503d34ada009441c90f9af5af0c3c0dab7166ec6e76c022bad84372a35"
[[asset]]
path = "crates/ui-egui/src/i18n/hi.tsv"
@@ -261,7 +241,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Hindi UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "f596b585ee1cbb2c63f8592999f9a75e560c100ef6db1ba5e1a8b7b56c1486e0"
[[asset]]
path = "crates/ui-egui/src/i18n/id.tsv"
title = "Indonesian interface translation catalog"
@@ -271,7 +250,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Indonesian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "e2daca8b4e824da29ce96786394d8e8d4a2a48e407a7e16e2abb958a9915219f"
[[asset]]
path = "crates/ui-egui/src/i18n/ko.tsv"
title = "Korean interface translation catalog"
@@ -281,7 +259,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Korean UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "c6a5c886efe0ce938db1a436d23218a73a160e8a9f249ff44eb6702925647dcb"
[[asset]]
path = "crates/ui-egui/src/i18n/ro.tsv"
@@ -292,7 +269,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Romanian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "d4851cbc65c62c3c4d9c663915364c8b76adfb47fdbdb7cfc87c1bd2310f03ab"
[[asset]]
@@ -304,7 +280,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Finnish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "bd5a3659a0a2874a82d44f850e06a52e68b5d577d3643613bb5d609371138b72"
[[asset]]
path = "assets/icons/accessibility.svg"
@@ -3426,7 +3401,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Vietnamese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "5514d6e705143b36bcae7b2daa37d9616eb9dc388bd93702b154475c9b6749ee"
[[asset]]
path = "crates/ui-egui/src/i18n/sl.tsv"
@@ -3437,7 +3411,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Slovene UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "fa1c5b6b782b3045440b66f624a6a9d94f95db363d37db841c61c130bd5400f7"
[[asset]]
@@ -3460,7 +3433,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Norwegian Bokmål UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "d1a582082d2b76a8ed25fc0bbd1540d7f842bbe0014f5a4363f941a0b96ee362"
[[asset]]
path = "assets/icons/superscript.svg"
@@ -3515,7 +3487,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Albanian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "a6e813f29d5911880991f14f08ab24eaa26157c12e5ba07677f215077524063a"
[[asset]]
path = "crates/ui-egui/src/i18n/th.tsv"
@@ -3526,7 +3497,6 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Thai UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "1da0904efd83e5606c4e5e515db7deb87e79b4a1167b3305732555486b24bfa4"
[[asset]]
path = "crates/ui-egui/src/i18n/ca.tsv"
@@ -3537,4 +3507,3 @@ licence = "MIT OR Apache-2.0"
licence_file = "LICENSE-MIT"
kind = "translation"
usage = "Catalan UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
sha256 = "6f98581f216372275b67db215eebb7a5cc14d110dc8a9cef7f2a38476c6cfe65"
+2 -2
View File
@@ -74,7 +74,7 @@ PdfCraft uses the same system as PhotoCraft (`crates/ui-egui/src/i18n/`):
- Placeholders (`{name}`) must appear in both columns. A trailing `…` must be kept. `i18n::fmt` fills placeholders in one pass, so a value such as a file name containing `{n}` is inserted as written.
- A missing entry never fails a build or a test: that string shows in English. `cargo xtask i18n-report`, a step of `cargo xtask ci`, prints for each language how many strings it has no translation for and the first few. It counts every command, All tools label, literal passed to `tl!` or `i18n::t` in the UI source, engine wording (below) and key of the bundled catalogs, so a new English string shows up there until each catalog has it.
- Catalogs are validated strictly: unknown escapes, unknown `@` contexts, placeholder or ellipsis mismatches, wrong plural form counts and duplicates are errors. The tests require the bundled catalogs to have none; at run time a bad line is skipped (and logged) and that string shows in English.
- Catalogs are assets: each `.tsv` has an `ATTRIBUTION.toml` entry with `kind = "translation"` and its SHA-256, checked by `cargo xtask assets`. After editing a catalog, put the new hash into its entry by hand (`sha256sum crates/ui-egui/src/i18n/<code>.tsv`), because `cargo xtask assets --write` does not touch `ATTRIBUTION.toml`; the hash check is deliberate, so a changed asset gets its provenance re-checked. Without it `cargo xtask assets` and the test `assets::tests::the_real_manifest_passes` fail.
- Each `.tsv` has an `ATTRIBUTION.toml` entry with `kind = "translation"` (title, author, source, licence), checked by `cargo xtask assets`. Catalogs are project files under the project licence, so the entry has no `sha256` and editing a catalog doesn't touch `ATTRIBUTION.toml`; git history records their provenance.
- Translations are clean-room: write them from the meaning of the English text, never from another product's string tables.
## Sentences the engine words
@@ -93,6 +93,6 @@ Error details that come from the operating system, and faults rather than mistak
1. Copy the header of `ja.tsv` into `xx.tsv` and translate entries.
2. Add one row to `LANGUAGES` in `crates/ui-egui/src/i18n/mod.rs`: code, native name, catalog and plural rule.
3. Add an `ATTRIBUTION.toml` entry for the catalog (`kind = "translation"`), with the catalog's `sha256sum` as `sha256`, then run `cargo xtask assets --write` (it regenerates `ATTRIBUTION.md` only) and `cargo xtask assets`.
3. Add an `ATTRIBUTION.toml` entry for the catalog (`kind = "translation"`, no `sha256`), then run `cargo xtask assets --write` (it regenerates `ATTRIBUTION.md` only) and `cargo xtask assets`.
The Preferences dropdown, the system-language match, the catalog tests (format, duplicates, placeholders, plural forms, command ids) and the translation gaps report then pick it up.
+55 -9
View File
@@ -3,7 +3,8 @@
//! `ATTRIBUTION.toml` lists every asset: committed or vendored files (`[[asset]]`), files that
//! Cargo dependencies compile into PdfCraft (`[[bundled]]`), and files xtask downloads at build
//! time (`[[fetched]]`). This gate fails when:
//! - an asset-like file in the repository has no entry, or its SHA-256 differs;
//! - an asset-like file in the repository has no entry, or its SHA-256 is missing or differs
//! (translation catalogs are project files and carry none);
//! - a licence is not on the allowlist, or a declared licence file is missing;
//! - an icon, image or font names Adobe as author or source, or an `adobe_data` entry is not on
//! the closed list approved in AGENTS.md §1.1;
@@ -49,6 +50,11 @@ pub const ADOBE_DATA_ALLOWED: &[(&str, &str)] = &[("hayro-cmap", "assets/cmaps.b
/// table has to be re-attested deliberately.
pub const ADOBE_DATA_FILES: &[&str] = &["crates/fonts/src/std14_metrics.rs", "vendor/hayro-cmap/assets/cmaps.brotli"];
/// Where the UI translation catalogs live. `kind = "translation"` entries for `.tsv` files here
/// are project files under the project licence: they carry no `sha256`, because git history
/// already records their provenance (AGENTS.md §1.3).
pub const TRANSLATION_DIR: &str = "crates/ui-egui/src/i18n/";
/// File extensions that count as assets wherever they appear in the repository.
const ASSET_EXTENSIONS: &[&str] = &[
"png", "jpg", "jpeg", "gif", "svg", "ico", "icns", "webp", "bmp", "tif", "tiff", "avif", "heic", "ttf", "otf", "ttc", "woff", "woff2", "pfb",
@@ -97,11 +103,20 @@ pub struct Asset {
pub licence_file: String,
pub kind: String,
pub usage: String,
pub sha256: String,
/// Required for every entry except a translation catalog ([`Asset::is_translation_catalog`]).
#[serde(default)]
pub sha256: Option<String>,
#[serde(default)]
pub adobe_data: bool,
}
impl Asset {
/// A UI translation catalog: a project file whose entry carries no hash.
pub fn is_translation_catalog(&self) -> bool {
self.kind == "translation" && self.path.starts_with(TRANSLATION_DIR) && self.path.ends_with(".tsv")
}
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Bundled {
@@ -162,6 +177,9 @@ pub fn run(args: &[String]) -> Result<()> {
std::fs::write(root.join("ATTRIBUTION.md"), &markdown)?;
println!("assets: wrote ATTRIBUTION.md");
}
for a in manifest.asset.iter().filter(|a| a.is_translation_catalog() && a.sha256.is_some()) {
println!("assets: note: {}: translation catalogs aren't hashed; its sha256 line can be deleted", a.path);
}
let mut problems = check(&root, &manifest, &repo_files(&root)?, &cargo_lock_versions(&root)?);
match std::fs::read_to_string(root.join("ATTRIBUTION.md")) {
Ok(existing) if existing == markdown => {}
@@ -257,10 +275,16 @@ pub fn check(root: &Path, m: &Manifest, repo_files: &[String], lock: &BTreeSet<(
if a.adobe_data && !(ADOBE_DATA_FILES.contains(&a.path.as_str()) && a.kind == "data") {
problems.push(format!("{}: adobe_data is only allowed for the closed list in AGENTS.md §1.1", a.path));
}
match std::fs::read(root.join(&a.path)) {
Ok(bytes) if sha256_hex(&bytes) == a.sha256 => {}
Ok(_) => problems.push(format!("{}: SHA-256 does not match ATTRIBUTION.toml (re-verify provenance, then update the entry)", a.path)),
Err(_) => problems.push(format!("{}: listed but missing", a.path)),
let catalog = a.is_translation_catalog();
match (std::fs::read(root.join(&a.path)), &a.sha256) {
(Err(_), _) => problems.push(format!("{}: listed but missing", a.path)),
// A leftover hash line (e.g. kept while resolving a rebase) is not checked; `run` notes it.
(Ok(_), _) if catalog => {}
(Ok(_), None) => problems.push(format!("{}: sha256 is missing (only translation catalogs go without one)", a.path)),
(Ok(bytes), Some(hash)) if sha256_hex(&bytes) == *hash => {}
(Ok(_), Some(_)) => {
problems.push(format!("{}: SHA-256 does not match ATTRIBUTION.toml (re-verify provenance, then update the entry)", a.path))
}
}
}
for f in repo_files {
@@ -378,7 +402,7 @@ pub fn render_markdown(m: &Manifest) -> String {
s.push_str("<!-- Generated from ATTRIBUTION.toml by `cargo xtask assets --write`. Do not edit by hand. -->\n\n");
s.push_str("Every asset PdfCraft includes, bundles or uses to build its published material, with its author, source and licence. ");
s.push_str(
"The policy is in [AGENTS.md](AGENTS.md) §1. The machine-readable list, with SHA-256 hashes, is [ATTRIBUTION.toml](ATTRIBUTION.toml). ",
"The policy is in [AGENTS.md](AGENTS.md) §1. The machine-readable list, with the SHA-256 of every asset but the translation catalogs, is [ATTRIBUTION.toml](ATTRIBUTION.toml). ",
);
s.push_str("Licence texts are kept beside the assets and summarised in [NOTICE](NOTICE).\n\n");
let esc = |t: &str| t.replace('|', "\\|");
@@ -463,7 +487,7 @@ mod tests {
licence_file: "Cargo.toml".into(),
kind: kind.into(),
usage: "u".into(),
sha256: sha256_hex(&std::fs::read(root().join(path)).unwrap_or_default()),
sha256: Some(sha256_hex(&std::fs::read(root().join(path)).unwrap_or_default())),
adobe_data: false,
}
}
@@ -530,6 +554,28 @@ mod tests {
assert!(check(&root(), &m, &[], &lock(&[])).iter().any(|p| p.contains("from Adobe is forbidden")));
}
/// One `[[asset]]` entry as ATTRIBUTION.toml spells it, with `extra` lines appended.
fn parsed(path: &str, kind: &str, extra: &str) -> Manifest {
let text = format!(
"[[asset]]\npath = \"{path}\"\ntitle = \"t\"\nauthor = \"a\"\nsource = \"s\"\nlicence = \"MIT\"\nlicence_file = \"Cargo.toml\"\nkind = \"{kind}\"\nusage = \"u\"\n{extra}"
);
toml::from_str(&text).unwrap()
}
/// Catalogs change in most UI PRs; a pinned hash would make every pair of them conflict.
#[test]
fn translation_catalogs_carry_no_hash_and_everything_else_does() {
let check_one = |m: Manifest| check(&root(), &m, &[], &lock(&[]));
let ja = "crates/ui-egui/src/i18n/ja.tsv";
assert_eq!(check_one(parsed(ja, "translation", "")), Vec::<String>::new());
let stale = format!("sha256 = \"{}\"\n", "0".repeat(64));
assert_eq!(check_one(parsed(ja, "translation", &stale)), Vec::<String>::new(), "a leftover hash is not checked");
for (path, kind) in [("assets/fonts/Inter-Regular.ttf", "font"), (ja, "data"), ("Cargo.toml", "translation")] {
let p = check_one(parsed(path, kind, ""));
assert!(p.iter().any(|p| p.contains("sha256 is missing")), "{path} {kind}: {p:?}");
}
}
#[test]
fn adobe_ofl_fonts_are_allowed_and_other_adobe_fonts_rejected() {
let l = lock(&[("noto", "1.0.0")]);
@@ -562,7 +608,7 @@ mod tests {
#[test]
fn changed_file_fails_hash_check() {
let mut a = asset("assets/icons/x.svg", "Lucide", "icon", "ISC");
a.sha256 = "0".repeat(64);
a.sha256 = Some("0".repeat(64));
let p = check(&root(), &Manifest { asset: vec![a], ..Default::default() }, &[], &lock(&[]));
assert!(p.iter().any(|p| p.contains("SHA-256 does not match")), "{p:?}");
}