mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-11 16:30:12 -06:00
M6: Sketch import: text with styled runs and font names
Text layers keep their string and styled runs (font, size, colour, line height, letter spacing, decorations, text transform; ranges in UTF-16 units, clamped), paragraph alignment and spacing, vertical alignment and how the box sizes. Fonts named by PostScript name become family and style; the engine then matches families to installed ones ignoring spaces (PingFangSC → PingFang SC). Fonts embedded in newer files are named in the import report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ff9d966ea6
commit
a0542e581c
@@ -558,11 +558,46 @@ fn load_with(
|
||||
fn load_sketch(bytes: &[u8], name: &str) -> Option<Result<figcraft_format::Loaded>> {
|
||||
let r = figcraft_format::sketch::try_import(bytes, name)?;
|
||||
Some(r.map_err(|e| EngineError::Other(e.to_string())).map(|(mut doc, warnings)| {
|
||||
match_font_families(&mut doc);
|
||||
link_instances(&mut doc);
|
||||
figcraft_format::Loaded { issues: warnings, ..doc.into() }
|
||||
}))
|
||||
}
|
||||
|
||||
/// Font families a `.sketch` import guessed from PostScript names ("Ping Fang SC" from
|
||||
/// "PingFangSC-Semibold") take the installed family's name when they differ only in spaces and
|
||||
/// case.
|
||||
fn match_font_families(doc: &mut Document) {
|
||||
let installed = figcraft_text::fontdb::family_names();
|
||||
let mut cache: std::collections::BTreeMap<String, Option<String>> = std::collections::BTreeMap::new();
|
||||
let mut fix = |style: &mut figcraft_doc::TextStyle| {
|
||||
let found = cache.entry(style.font_family.clone()).or_insert_with(|| installed_family(&style.font_family, &installed));
|
||||
if let Some(f) = found {
|
||||
style.font_family = f.clone();
|
||||
}
|
||||
};
|
||||
let ids: Vec<NodeId> = doc.nodes().filter(|n| matches!(n.kind, NodeKind::Text(_))).map(|n| n.id).collect();
|
||||
for id in ids {
|
||||
if let Some(NodeKind::Text(td)) = doc.node_mut(id).map(|n| &mut n.kind) {
|
||||
fix(&mut td.style);
|
||||
for r in &mut td.runs {
|
||||
fix(&mut r.style);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The installed family `family` means when it isn't installed under that exact name: the one
|
||||
/// whose name is the same without spaces, ignoring case.
|
||||
pub(crate) fn installed_family(family: &str, installed: &[String]) -> Option<String> {
|
||||
let key = |s: &str| s.chars().filter(|c| !c.is_whitespace()).flat_map(char::to_lowercase).collect::<String>();
|
||||
if installed.iter().any(|f| f.eq_ignore_ascii_case(family)) {
|
||||
return None;
|
||||
}
|
||||
let want = key(family);
|
||||
installed.iter().find(|f| key(f) == want).cloned()
|
||||
}
|
||||
|
||||
/// Gives `None` when `bytes` are not a `.fig` file. Then other readers can try.
|
||||
pub(crate) fn load_fig(bytes: &[u8], name: &str) -> Option<Result<figcraft_format::Loaded>> {
|
||||
let r = figcraft_format::fig::try_import(bytes, name)?;
|
||||
|
||||
@@ -134,3 +134,14 @@ fn sketch_masks_show_their_content() {
|
||||
assert!(m.alpha() > 200 && m.red() > 150, "mask fill: {m:?}");
|
||||
assert!(px(2, 2).alpha() < 50);
|
||||
}
|
||||
|
||||
/// Family names guessed from PostScript names find the installed family whatever its spaces.
|
||||
#[test]
|
||||
fn sketch_font_families_match_installed_ones() {
|
||||
let installed = ["PingFang SC".to_string(), "Helvetica Neue".to_string(), "IBM Plex Sans".to_string()];
|
||||
let m = |f: &str| crate::cmd::file::installed_family(f, &installed);
|
||||
assert_eq!(m("Ping Fang SC").as_deref(), Some("PingFang SC"));
|
||||
assert_eq!(m("Helvetica Neue"), None, "already right");
|
||||
assert_eq!(m("IBMPlex Sans").as_deref(), Some("IBM Plex Sans"));
|
||||
assert_eq!(m("Comic Sans"), None);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user