mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-09 03:51:42 +00:00
M4: weight steps reach a weight family's italics and read W weights after a width (#1831)
A family installed as one family per weight ("Foo Bold" with Regular and
Italic faces) now lists Bold and Bold Italic, the names resolve draws them
by, with the faces' own weights when they are read. A W-numbered weight
counts after other words too ("Condensed W7 Italic" is 700).
This commit is contained in:
1 parent
2c9226c021
commit
45c89c5a73
4 files changed
+67
-7
No files matched your search
@@ -176,10 +176,10 @@ fn slanted(style: &str) -> bool {
|
||||
s.contains("italic") || s.contains("oblique")
|
||||
}
|
||||
|
||||
/// The weight a style name says ("W3" and "W6 Italic" too).
|
||||
/// The weight a style name says ("W3", "W6 Italic" and "Condensed W7" too).
|
||||
pub(crate) fn name_weight(style: &str) -> u16 {
|
||||
let first = style.to_ascii_lowercase().replace('-', " ");
|
||||
first.split_whitespace().next().and_then(w_number).unwrap_or_else(|| figcraft_doc::style_weight(style))
|
||||
let words = style.to_ascii_lowercase().replace('-', " ");
|
||||
words.split_whitespace().find_map(w_number).unwrap_or_else(|| figcraft_doc::style_weight(style))
|
||||
}
|
||||
|
||||
/// The next heavier (`up`) or lighter style of `styles` (a family's with the weight each is listed
|
||||
|
||||
@@ -109,6 +109,10 @@ fn off_the_list_the_faces_own_weights_decide() {
|
||||
let hiragino = weighed(&[("W3 Italic", 300), ("W6 Italic", 600), ("W8 Italic", 800)]);
|
||||
assert_eq!(next_style(&hiragino, &at("W7 Italic"), false).as_deref(), Some("W6 Italic"));
|
||||
assert_eq!(next_style(&hiragino, &at("W7 Italic"), true).as_deref(), Some("W8 Italic"));
|
||||
// And after a width ("Condensed W7 Italic" is 700 too).
|
||||
let condensed = weighed(&[("Condensed W3 Italic", 300), ("Condensed W6 Italic", 600), ("Condensed W8 Italic", 800)]);
|
||||
assert_eq!(next_style(&condensed, &at("Condensed W7 Italic"), true).as_deref(), Some("Condensed W8 Italic"));
|
||||
assert_eq!(next_style(&condensed, &at("Condensed W7 Italic"), false).as_deref(), Some("Condensed W6 Italic"));
|
||||
}
|
||||
|
||||
fn font_style(s: &Session, t: NodeId) -> String {
|
||||
|
||||
@@ -767,9 +767,24 @@ impl FontDb {
|
||||
v.push((w, f.italic || italic_name(&inst.name), k, inst.name.clone()));
|
||||
}
|
||||
}
|
||||
// A family installed as one family per weight offers those weights too.
|
||||
for (_, style) in self.split_families(&key) {
|
||||
v.push((pending_weight(&style).clamp(1, 1000) as u16, italic_name(&style), 0, style));
|
||||
// A family installed as one family per weight offers those weights too, upright and italic
|
||||
// as that family's faces are ("Foo Bold" with Regular and Italic faces: Bold and Bold
|
||||
// Italic, the names `resolve` finds them by). A face read gives its own weight, one not
|
||||
// read the name's; the families aren't read for this.
|
||||
for (k, style) in self.split_families(&key) {
|
||||
let named = pending_weight(&style).clamp(1, 1000) as u16;
|
||||
let italic = italic_name(&style);
|
||||
let read = self.families.get(&k).map(|(_, idx)| idx.as_slice()).unwrap_or_default().iter().filter_map(|i| self.faces.get(*i));
|
||||
let unread = self.pending.get(&k).into_iter().flat_map(|(_, entries)| entries).map(|e| (named, italic_name(&e.style)));
|
||||
let faces: Vec<(u16, bool)> = read.map(|f| (f.weight, f.italic)).chain(unread).collect();
|
||||
if faces.is_empty() {
|
||||
v.push((named, italic, 0, style));
|
||||
continue;
|
||||
}
|
||||
for (w, slanted) in faces {
|
||||
let name = if slanted && !italic { format!("{style} Italic") } else { style.clone() };
|
||||
v.push((w, slanted || italic, 0, name));
|
||||
}
|
||||
}
|
||||
v.sort();
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
//! case; the picker lists what is installed. Synthetic family names only, over pending entries
|
||||
//! (no files) and the embedded faces when the build has them.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{CRAFT_FONTS, Face, FontBytes, FontDb, Pending, style_words};
|
||||
use super::{CRAFT_FONTS, Face, FontBytes, FontDb, LocalFont, Pending, scan_dir, style_words, system_font_dirs};
|
||||
|
||||
fn entry(style: &str) -> Pending {
|
||||
Pending { path: format!("no-such-dir/{style}.ttf").into(), index: 0, style: style.into(), postscript: None, local: None }
|
||||
@@ -104,3 +105,43 @@ fn faces_are_found_and_listed_under_either_name() {
|
||||
assert!(Arc::ptr_eq(&db.find("Synthetic Text SemiBold", "Regular").unwrap(), &semibold));
|
||||
assert!(Arc::ptr_eq(&db.find("synthetictext", "Semibold").unwrap(), &semibold));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_family_per_weight_offers_its_italics_too() {
|
||||
// Listed by name before anything is read: Synthetic Slab Bold's Italic face is Bold Italic.
|
||||
let local = |id: usize, family: &str, style: &str| LocalFont { id, family: family.into(), style: style.into(), postscript: String::new() };
|
||||
let mut db = FontDb::default();
|
||||
db.add_local(vec![
|
||||
local(0, "Synthetic Slab", "Regular"),
|
||||
local(1, "Synthetic Slab", "Italic"),
|
||||
local(2, "Synthetic Slab Bold", "Regular"),
|
||||
local(3, "Synthetic Slab Bold", "Italic"),
|
||||
]);
|
||||
let listed = |db: &mut FontDb| db.style_weights("Synthetic Slab");
|
||||
let want = |v: &[(&str, u16)]| v.iter().map(|(s, w)| ((*s).to_string(), *w)).collect::<Vec<_>>();
|
||||
assert_eq!(listed(&mut db), want(&[("Regular", 400), ("Italic", 400), ("Bold", 700), ("Bold Italic", 700)]));
|
||||
assert_eq!(resolved(&db, "Synthetic Slab", "Bold Italic"), pair("synthetic slab bold", "Italic"), "the style drawn by that face");
|
||||
// Faces read give their own weights: Synthetic Slab Semi Light's are 350, not the name's 300.
|
||||
let stand_in = CRAFT_FONTS.first().map(|f| f.bytes.to_vec()).or_else(|| {
|
||||
let mut installed = BTreeMap::new();
|
||||
for dir in system_font_dirs() {
|
||||
scan_dir(&dir, 0, &mut installed);
|
||||
}
|
||||
installed.values().flat_map(|(_, v)| v).find_map(|p| std::fs::read(&p.path).ok())
|
||||
});
|
||||
let Some(bytes) = stand_in.map(Arc::new) else {
|
||||
eprintln!("skipping the read faces: no font to stand in");
|
||||
return;
|
||||
};
|
||||
let face = |family: &str, style: &str, weight: u16, italic: bool| {
|
||||
let mut f = Face::from_bytes(FontBytes::Shared(bytes.clone()), 0, Some(family), Some(style)).unwrap();
|
||||
f.instances.clear();
|
||||
(f.weight, f.italic) = (weight, italic);
|
||||
f
|
||||
};
|
||||
let mut db = FontDb::default();
|
||||
db.add(face("Synthetic Slab", "Regular", 400, false));
|
||||
db.add(face("Synthetic Slab Semi Light", "Regular", 350, false));
|
||||
db.add(face("Synthetic Slab Semi Light", "Italic", 350, true));
|
||||
assert_eq!(listed(&mut db), want(&[("Semi Light", 350), ("Semi Light Italic", 350), ("Regular", 400)]));
|
||||
}
|
||||
Reference in new issue
Block a user