Merge pull request #782 from dotCooCoo/feat/package-font-resolution

Package: fonts found by any of their names are copied, and skipped fonts are named
This commit is contained in:
BFlat
2026-10-10 01:43:01 +07:00
committed by GitHub
16 changed files with 152 additions and 20 deletions
+27 -10
View File
@@ -7,6 +7,7 @@ use serde_json::{Value, json};
use vectorcraft_color::Paint;
use vectorcraft_doc::{ColorMode, Document, Node, NodeId, NodeKind};
use vectorcraft_geom::{Affine, Rect};
use vectorcraft_text::embed::Embedding;
use super::*;
@@ -77,13 +78,17 @@ pub struct Section {
pub rows: Vec<(String, String)>,
}
/// What a font's `fsType` lets a copy of it do.
/// What a font's `fsType` lets a copy of it do, read as exports read it ([`Embedding`]): the
/// most permissive usage bit wins, and bitmap-only fonts may not be embedded.
pub fn embedding_label(fs_type: u16) -> &'static str {
match fs_type & 0x000f {
0x0002 => "embedding not allowed",
0x0004 => "embedding for preview and print",
0x0008 => "embedding for editing",
_ => "embedding allowed",
if Embedding::from_fs_type(fs_type) == Embedding::Forbidden {
"embedding not allowed"
} else if fs_type & 0x0008 != 0 {
"embedding for editing"
} else if fs_type & 0x0004 != 0 {
"embedding for preview and print"
} else {
"embedding allowed"
}
}
@@ -136,13 +141,19 @@ fn sections(d: &Document, info: &Value, fonts: &BTreeSet<(String, String)>, imag
fonts
.iter()
.map(|(family, style)| {
let detail = match db.face(family, style) {
Some(f) if f.family.eq_ignore_ascii_case(family) => {
// Found by any of its names; a style the family lacks shows in another.
let detail = match db.resolve(family, style) {
Some((f, m)) if m != vectorcraft_text::FontMatch::Missing => {
let file =
f.path().and_then(|p| p.file_name()).map_or_else(|| "built in".into(), |n| n.to_string_lossy().into_owned());
format!("{file}; {}", embedding_label(f.fs_type()))
let found = format!("{file}; {}", embedding_label(f.fs_type()));
if m == vectorcraft_text::FontMatch::Style {
format!("substituted: shown in {} {} ({found})", f.family, f.style)
} else {
found
}
}
Some(f) => format!("missing: shown in {} {}", f.family, f.style),
Some((f, _)) => format!("missing: shown in {} {}", f.family, f.style),
None => "missing".into(),
};
(format!("{family} {style}"), detail)
@@ -396,6 +407,12 @@ mod tests {
assert_eq!(embedding_label(0x0002), "embedding not allowed");
assert_eq!(embedding_label(0x0004 | 0x0100), "embedding for preview and print");
assert_eq!(embedding_label(0x0008), "embedding for editing");
// As exports read it: bitmap embedding only, the restricted bit with reserved bit 0, the
// most permissive of several usage bits.
assert_eq!(embedding_label(0x0200), "embedding not allowed");
assert_eq!(embedding_label(0x0003), "embedding not allowed");
assert_eq!(embedding_label(0x0006), "embedding for preview and print");
assert_eq!(embedding_label(0x000c), "embedding for editing");
}
#[test]
+13 -6
View File
@@ -109,8 +109,15 @@ fn package(s: &mut Session, p: &Value) -> Result<Value> {
lines.push("FONTS".to_string());
for (family, style) in super::fonts::used_fonts(&st.doc) {
let font = format!("{family} {style}");
let reason = match db.face(&family, &style) {
Some(f) if f.family.eq_ignore_ascii_case(&family) && f.embeddable() => {
// Found by any of its names, as the canvas draws it.
let resolved = db.resolve(&family, &style).filter(|(_, m)| *m != vectorcraft_text::FontMatch::Missing);
// A style the family lacks is shown in its closest style, whose file is copied.
let shown = match &resolved {
Some((f, vectorcraft_text::FontMatch::Style)) => format!(" (shown in {} {})", f.family, f.style),
_ => String::new(),
};
let reason = match resolved {
Some((f, _)) if f.embeddable() => {
let data = f.file_data();
let file = f.path().and_then(Path::file_name).map_or_else(
|| format!("{}-{}.{}", f.family, f.style, font_ext(data)).replace(|c: char| !(c.is_alphanumeric() || "-_.".contains(c)), ""),
@@ -121,13 +128,13 @@ fn package(s: &mut Session, p: &Value) -> Result<Value> {
entries.push((format!("Fonts/{file}"), data.to_vec()));
fonts += 1;
}
lines.push(format!("{font} → Fonts/{file}"));
lines.push(format!("{font}{shown} → Fonts/{file}"));
continue;
}
Some(f) if f.family.eq_ignore_ascii_case(&family) => "its licence doesn't allow embedding",
_ => "not available on this computer",
Some(_) => "its licence doesn't allow embedding",
None => "not available on this computer",
};
lines.push(format!("{font}: {reason}, not copied"));
lines.push(format!("{font}{shown}: {reason}, not copied"));
skipped.push(json!({ "font": font, "reason": reason }));
}
lines.push(String::new());