diff --git a/apps/designcraft-cli/src/main.rs b/apps/designcraft-cli/src/main.rs index 0c0a1526..3d18dcae 100644 --- a/apps/designcraft-cli/src/main.rs +++ b/apps/designcraft-cli/src/main.rs @@ -124,7 +124,7 @@ fn main() -> ExitCode { Some("bench") => report(perf::bench(&args[1..])), Some("validate-pdf") => report(validate_pdf(args.get(1).map(String::as_str))), Some("links") => { - use designcraft_engine::links::*; + use designcraft_engine::links::{APP_PAGE, DISCORD, GITHUB, ISSUES, WEBSITE}; outln!("Discord {DISCORD}\nWebsite {WEBSITE}\nApp page {APP_PAGE}\nGitHub {GITHUB}\nIssues {ISSUES}"); ExitCode::SUCCESS } @@ -345,33 +345,7 @@ fn run(args: &[String]) -> Result<(), String> { } "--export" => { let out = val()?; - match Export::of(&out)? { - Export::Image => export(&mut s, &out, page, scale)?, - Export::Pdf => { - let mut p = pdf_opts.clone(); - p["path"] = json!(out); - let r = s.execute("file.exportPdf", &p).map_err(|e| e.to_string())?; - eprintln!("wrote {out} ({} pages, {} bytes)", r["pages"], r["bytes"]); - for w in r["warnings"].as_array().into_iter().flatten() { - eprintln!("warning: {}", w.as_str().unwrap_or_default()); - } - } - Export::Document => { - s.execute("file.saveAs", &json!({"path": out})).map_err(|e| e.to_string())?; - eprintln!("saved {out}"); - } - Export::Pptx => { - let r = s.execute("file.exportPptx", &json!({"path": out})).map_err(|e| e.to_string())?; - eprintln!("wrote {out} ({} slides, {} bytes)", r["slides"], r["bytes"]); - for w in r["warnings"].as_array().into_iter().flatten() { - eprintln!("warning: {}", w.as_str().unwrap_or_default()); - } - } - Export::Command(cmd) => { - let r = s.execute(cmd, &json!({"path": out})).map_err(|e| e.to_string())?; - eprintln!("wrote {out} ({} bytes)", r["bytes"]); - } - } + handle_export(&mut s, &out, page, scale, &pdf_opts)?; } "--all-pages" => { let dir = val()?; @@ -387,6 +361,37 @@ fn run(args: &[String]) -> Result<(), String> { Ok(()) } +fn handle_export(s: &mut Session, out: &str, page: usize, scale: f64, pdf_opts: &Value) -> Result<(), String> { + match Export::of(out)? { + Export::Image => export(s, out, page, scale)?, + Export::Pdf => { + let mut p = pdf_opts.clone(); + p["path"] = json!(out); + let r = s.execute("file.exportPdf", &p).map_err(|e| e.to_string())?; + eprintln!("wrote {out} ({} pages, {} bytes)", r["pages"], r["bytes"]); + for w in r["warnings"].as_array().into_iter().flatten() { + eprintln!("warning: {}", w.as_str().unwrap_or_default()); + } + } + Export::Document => { + s.execute("file.saveAs", &json!({"path": out})).map_err(|e| e.to_string())?; + eprintln!("saved {out}"); + } + Export::Pptx => { + let r = s.execute("file.exportPptx", &json!({"path": out})).map_err(|e| e.to_string())?; + eprintln!("wrote {out} ({} slides, {} bytes)", r["slides"], r["bytes"]); + for w in r["warnings"].as_array().into_iter().flatten() { + eprintln!("warning: {}", w.as_str().unwrap_or_default()); + } + } + Export::Command(cmd) => { + let r = s.execute(cmd, &json!({"path": out})).map_err(|e| e.to_string())?; + eprintln!("wrote {out} ({} bytes)", r["bytes"]); + } + } + Ok(()) +} + /// A page as PNG, or as JPEG for `.jpg` and `.jpeg`. fn export(s: &mut Session, out: &str, page: usize, scale: f64) -> Result<(), String> { let st = s.doc().map_err(|e| e.to_string())?; diff --git a/crates/color/src/cms/mod.rs b/crates/color/src/cms/mod.rs index 3be294ae..1a1374cd 100644 --- a/crates/color/src/cms/mod.rs +++ b/crates/color/src/cms/mod.rs @@ -17,7 +17,7 @@ pub mod lab; use std::borrow::Cow; use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; -use std::sync::{Arc, Mutex, OnceLock, RwLock}; +use std::sync::{Arc, Mutex, OnceLock, PoisonError, RwLock}; use serde::{Deserialize, Serialize}; @@ -201,7 +201,7 @@ static USER: RwLock>> = RwLock::new(Vec::new()); fn builtin_rgb_cache(name: &str) -> Option> { static CACHE: Mutex>> = Mutex::new(Vec::new()); - let mut c = CACHE.lock().unwrap_or_else(|e| e.into_inner()); + let mut c = CACHE.lock().unwrap_or_else(PoisonError::into_inner); if let Some(p) = c.iter().find(|p| p.name == name) { return Some(p.clone()); } @@ -211,7 +211,7 @@ fn builtin_rgb_cache(name: &str) -> Option> { } fn user_profile(name: &str) -> Option> { - USER.read().unwrap_or_else(|e| e.into_inner()).iter().find(|p| p.name == name).cloned() + USER.read().unwrap_or_else(PoisonError::into_inner).iter().find(|p| p.name == name).cloned() } /// The default CMYK space: Coated FOGRA39, or Generic CMYK should that profile be unusable. @@ -255,7 +255,7 @@ pub fn profiles() -> Vec { .map(|n| ProfileInfo { name: (*n).into(), kind: ProfileKind::Rgb, builtin: true }) .chain([COATED_FOGRA39, GENERIC_CMYK, DEVICE_CMYK].iter().map(|n| ProfileInfo { name: (*n).into(), kind: ProfileKind::Cmyk, builtin: true })) .collect(); - for p in USER.read().unwrap_or_else(|e| e.into_inner()).iter() { + for p in USER.read().unwrap_or_else(PoisonError::into_inner).iter() { v.push(ProfileInfo { name: p.name.clone(), kind: p.kind, builtin: false }); } v @@ -275,7 +275,7 @@ pub fn register_icc(bytes: &[u8], name: Option) -> Result Arc { let key = format!("{:?}|{:?}|{:?}|{}", self.settings, proof.target, proof.intent, proof.simulate_paper); - let mut cache = PROOF_CACHE.lock().unwrap_or_else(|e| e.into_inner()); + let mut cache = PROOF_CACHE.lock().unwrap_or_else(PoisonError::into_inner); if let Some((_, l)) = cache.iter().find(|(k, _)| *k == key) { return l.clone(); } @@ -538,7 +538,7 @@ impl Cms { static PROOF_CACHE: Mutex)>> = Mutex::new(Vec::new()); fn clear_proof_cache() { - PROOF_CACHE.lock().unwrap_or_else(|e| e.into_inner()).clear(); + PROOF_CACHE.lock().unwrap_or_else(PoisonError::into_inner).clear(); } fn cmyk4(v: Option>) -> Option<[f32; 4]> { @@ -704,10 +704,10 @@ pub fn generation() -> u64 { /// The active colour-management context (Edit → Color Settings). pub fn active() -> Arc { - if let Some(c) = ACTIVE.read().unwrap_or_else(|e| e.into_inner()).as_ref() { + if let Some(c) = ACTIVE.read().unwrap_or_else(PoisonError::into_inner).as_ref() { return c.clone(); } - let mut w = ACTIVE.write().unwrap_or_else(|e| e.into_inner()); + let mut w = ACTIVE.write().unwrap_or_else(PoisonError::into_inner); w.get_or_insert_with(|| Arc::new(Cms::default())).clone() } @@ -721,7 +721,7 @@ pub fn set_active(settings: &ColorSettings) -> Result<(), CmsError> { let changed = active().settings != *settings; RGB_IS_SRGB.store(cms.rgb_is_srgb(), Ordering::Relaxed); CMYK_IS_DEVICE.store(matches!(cms.cmyk, CmykSpace::Device), Ordering::Relaxed); - *ACTIVE.write().unwrap_or_else(|e| e.into_inner()) = Some(Arc::new(cms)); + *ACTIVE.write().unwrap_or_else(PoisonError::into_inner) = Some(Arc::new(cms)); if changed { GENERATION.fetch_add(1, Ordering::Relaxed); } diff --git a/crates/compose/src/breaker.rs b/crates/compose/src/breaker.rs index 0dff3470..85b3a0dc 100644 --- a/crates/compose/src/breaker.rs +++ b/crates/compose/src/breaker.rs @@ -295,7 +295,7 @@ fn cjk_break(a: &Glyph, b: &Glyph, sp: &Spacing) -> bool { } pub fn is_forced(c: char) -> bool { - use designcraft_doc::story::*; + use designcraft_doc::story::{FORCED_LINE_BREAK, is_break_char}; c == FORCED_LINE_BREAK || is_break_char(c) } diff --git a/crates/compose/src/cache.rs b/crates/compose/src/cache.rs index 77bb30f9..6ca7c736 100644 --- a/crates/compose/src/cache.rs +++ b/crates/compose/src/cache.rs @@ -7,7 +7,7 @@ //! can't be reused by other allocations while the entry lives. use std::collections::HashMap; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, PoisonError}; use designcraft_doc::{Document, Item, Story, StoryId, Styles}; @@ -196,7 +196,7 @@ impl Cache { sig.push(std::hash::Hasher::finish(&h) as usize); } { - let mut g = self.map.lock().unwrap_or_else(|e| e.into_inner()); + let mut g = self.map.lock().unwrap_or_else(PoisonError::into_inner); g.1 += 1; let stamp = g.1; if let Some(e) = g.0.get_mut(&key) @@ -216,7 +216,7 @@ impl Cache { &ComposeOptions { page_name, section_marker, page, running, label: None, xrefs: with_xrefs.then(|| self.xref_index(doc)) }, &others.iter().map(|o| &**o).collect::>(), )); - let mut g = self.map.lock().unwrap_or_else(|e| e.into_inner()); + let mut g = self.map.lock().unwrap_or_else(PoisonError::into_inner); let stamp = g.1; if g.0.len() > 4096 { g.0.retain(|_, e| stamp - e.stamp < 64); @@ -228,7 +228,7 @@ impl Cache { && cross && let Some(old) = replaced { - self.previous.lock().unwrap_or_else(|e| e.into_inner()).insert(sid, (old.sig, old.out)); + self.previous.lock().unwrap_or_else(PoisonError::into_inner).insert(sid, (old.sig, old.out)); } out } @@ -241,7 +241,7 @@ impl Cache { // Only the geometry/story part of the signature (variables etc. add to it). let matches = |s: &[usize]| s.len() >= sig.len() && s[..sig.len()] == sig[..]; { - let g = self.map.lock().unwrap_or_else(|e| e.into_inner()); + let g = self.map.lock().unwrap_or_else(PoisonError::into_inner); for cross in [true, false] { if let Some(e) = g.0.get(&(sid, ShownOn::Own, cross)) && matches(&e.sig) @@ -250,7 +250,7 @@ impl Cache { } } } - let g = self.previous.lock().unwrap_or_else(|e| e.into_inner()); + let g = self.previous.lock().unwrap_or_else(PoisonError::into_inner); g.get(&sid).filter(|(s, _)| matches(s)).map(|(_, c)| c.clone()) } @@ -306,34 +306,34 @@ impl Cache { } pub fn clear(&self) { - self.map.lock().unwrap_or_else(|e| e.into_inner()).0.clear(); - *self.running.lock().unwrap_or_else(|e| e.into_inner()) = None; - *self.xrefs.lock().unwrap_or_else(|e| e.into_inner()) = None; + self.map.lock().unwrap_or_else(PoisonError::into_inner).0.clear(); + *self.running.lock().unwrap_or_else(PoisonError::into_inner) = None; + *self.xrefs.lock().unwrap_or_else(PoisonError::into_inner) = None; } /// Anchor pages for cross-references (rebuilt when the document changes). pub fn xref_index(&self, doc: &Document) -> Arc { let sig = doc_signature(doc); - if let Some((s, r)) = &*self.xrefs.lock().unwrap_or_else(|e| e.into_inner()) + if let Some((s, r)) = &*self.xrefs.lock().unwrap_or_else(PoisonError::into_inner) && *s == sig { return r.clone(); } let r = Arc::new(crate::xref::XrefIndex::build(doc, &|sid| self.get(doc, sid, None))); - *self.xrefs.lock().unwrap_or_else(|e| e.into_inner()) = Some((sig, r.clone())); + *self.xrefs.lock().unwrap_or_else(PoisonError::into_inner) = Some((sig, r.clone())); r } /// The running-header index for `doc` (rebuilt when the document changes). pub fn running_index(&self, doc: &Document) -> Arc { let sig = doc_signature(doc); - if let Some((s, r)) = &*self.running.lock().unwrap_or_else(|e| e.into_inner()) + if let Some((s, r)) = &*self.running.lock().unwrap_or_else(PoisonError::into_inner) && *s == sig { return r.clone(); } let r = Arc::new(RunningIndex::build(doc, &|sid| self.get(doc, sid, None))); - *self.running.lock().unwrap_or_else(|e| e.into_inner()) = Some((sig, r.clone())); + *self.running.lock().unwrap_or_else(PoisonError::into_inner) = Some((sig, r.clone())); r } } diff --git a/crates/compose/src/tests.rs b/crates/compose/src/tests.rs index b73806d2..3909998c 100644 --- a/crates/compose/src/tests.rs +++ b/crates/compose/src/tests.rs @@ -2892,7 +2892,7 @@ fn test_font(family: &str, os2_fields: Option<&[(usize, i16)]>) -> Vec { /// [`test_font`] with OS/2 `sTypoAscender` set to `typo_ascender`, or without OS/2 for `None`. fn typo_test_font(family: &str, typo_ascender: Option) -> Vec { - test_font(family, typo_ascender.map(|v| [(TYPO_ASCENDER, v)]).as_ref().map(|f| f.as_slice())) + test_font(family, typo_ascender.map(|v| [(TYPO_ASCENDER, v)]).as_ref().map(<[_]>::as_slice)) } /// First baseline (frame space) of a 20 pt line in `family` in a frame at y = 36 with a 4 pt top diff --git a/crates/compose/src/warichu.rs b/crates/compose/src/warichu.rs index 133e392e..3bd3fa99 100644 --- a/crates/compose/src/warichu.rs +++ b/crates/compose/src/warichu.rs @@ -224,6 +224,23 @@ fn layout(styles: &[RunStyle], line: &mut [PlacedGlyph], members: &[usize]) -> f // Bounded, so a hostile value can't move a row to an infinite or huge coordinate. let spacing = if st.warichu_line_spacing.is_finite() { st.warichu_line_spacing.clamp(-16.0 * st.size, 16.0 * st.size) } else { 0.0 }; let gap = if spacing == 0.0 { small } else { (small + spacing).max(0.0) }; + position_warichu_rows(line, members, &counts, &widths, block, old_x0, gap, st); + let delta = block - old_span; + shift_non_member_glyphs(line, members, &old_xs, old_x0, old_right, delta, scale); + delta +} + +fn position_warichu_rows( + line: &mut [PlacedGlyph], + members: &[usize], + counts: &[usize], + widths: &[f64], + block: f64, + old_x0: f64, + gap: f64, + st: &RunStyle, +) { + let rows = counts.len().max(1); let mut off = 0_usize; for (li, &c) in counts.iter().enumerate() { let w = widths[li]; @@ -242,7 +259,17 @@ fn layout(styles: &[RunStyle], line: &mut [PlacedGlyph], members: &[usize]) -> f } off += c; } - let delta = block - old_span; +} + +fn shift_non_member_glyphs( + line: &mut [PlacedGlyph], + members: &[usize], + old_xs: &[f64], + old_x0: f64, + old_right: f64, + delta: f64, + scale: f64, +) { let mut placed: Vec<(f64, f64)> = old_xs.iter().enumerate().map(|(i, ox)| (*ox, line[members[i]].x)).collect(); placed.sort_by(|a, b| a.0.total_cmp(&b.0)); let mut member_at = vec![false; line.len()]; @@ -265,7 +292,6 @@ fn layout(styles: &[RunStyle], line: &mut [PlacedGlyph], members: &[usize]) -> f g.x = anchor.1 + (g.x - anchor.0) * scale; } } - delta } #[cfg(test)] diff --git a/crates/engine/src/cmd/flex.rs b/crates/engine/src/cmd/flex.rs index 9b0f56b1..8d711785 100644 --- a/crates/engine/src/cmd/flex.rs +++ b/crates/engine/src/cmd/flex.rs @@ -82,44 +82,49 @@ pub(crate) fn sync(d: &mut Document) { } } -/// InDesign's detection when objects become a container: objects in a row (or column) keep it, -/// with Auto size and Space Between at their own spacing; others wrap in a row of their width. -fn detect(bounds: &[Rect]) -> FlexLayout { - let tol = 0.5; - let in_line = |row: bool| { - let mut v: Vec<&Rect> = bounds.iter().collect(); - v.sort_by(|a, b| if row { a.x0.total_cmp(&b.x0) } else { a.y0.total_cmp(&b.y0) }); - let apart = v.windows(2).all(|w| if row { w[1].x0 >= w[0].x1 - tol } else { w[1].y0 >= w[0].y1 - tol }); - let gaps: Vec = v.windows(2).map(|w| if row { w[1].x0 - w[0].x1 } else { w[1].y0 - w[0].y1 }).collect(); - (apart && bounds.len() > 1).then(|| gaps.iter().sum::() / gaps.len().max(1) as f64) - }; +fn in_line_gap(bounds: &[Rect], row: bool, tol: f64) -> Option { + let mut v: Vec<&Rect> = bounds.iter().collect(); + v.sort_by(|a, b| if row { a.x0.total_cmp(&b.x0) } else { a.y0.total_cmp(&b.y0) }); + let apart = v.windows(2).all(|w| if row { w[1].x0 >= w[0].x1 - tol } else { w[1].y0 >= w[0].y1 - tol }); + let gaps: Vec = v.windows(2).map(|w| if row { w[1].x0 - w[0].x1 } else { w[1].y0 - w[0].y1 }).collect(); + (apart && bounds.len() > 1).then(|| gaps.iter().sum::() / gaps.len().max(1) as f64) +} + +fn detect_align(bounds: &[Rect], row: bool, tol: f64) -> FlexAlign { let same = |f: &dyn Fn(&Rect) -> f64| bounds.windows(2).all(|w| (f(&w[0]) - f(&w[1])).abs() <= tol); - let align = |row: bool| { - let (a, c, e): (&dyn Fn(&Rect) -> f64, &dyn Fn(&Rect) -> f64, &dyn Fn(&Rect) -> f64) = - if row { (&|r| r.y0, &|r| r.center().y, &|r| r.y1) } else { (&|r| r.x0, &|r| r.center().x, &|r| r.x1) }; - if same(a) { - FlexAlign::Start - } else if same(c) { - FlexAlign::Center - } else if same(e) { - FlexAlign::End - } else { - FlexAlign::Start - } - }; - let auto = |gap: f64, row: bool| FlexLayout { + let (a, c, e): (&dyn Fn(&Rect) -> f64, &dyn Fn(&Rect) -> f64, &dyn Fn(&Rect) -> f64) = + if row { (&|r| r.y0, &|r| r.center().y, &|r| r.y1) } else { (&|r| r.x0, &|r| r.center().x, &|r| r.x1) }; + if same(a) { + FlexAlign::Start + } else if same(c) { + FlexAlign::Center + } else if same(e) { + FlexAlign::End + } else { + FlexAlign::Start + } +} + +fn auto_flex(gap: f64, row: bool, align: FlexAlign) -> FlexLayout { + FlexLayout { direction: if row { FlexDirection::Row } else { FlexDirection::Column }, justify: FlexJustify::SpaceBetween, - align: align(row), + align, width: FlexSize::Auto, height: FlexSize::Auto, gap_h: if row { gap.max(0.0) } else { 0.0 }, gap_v: if row { 0.0 } else { gap.max(0.0) }, ..Default::default() - }; - match (in_line(true), in_line(false)) { - (Some(gap), _) => auto(gap, true), - (None, Some(gap)) => auto(gap, false), + } +} + +/// InDesign's detection when objects become a container: objects in a row (or column) keep it, +/// with Auto size and Space Between at their own spacing; others wrap in a row of their width. +fn detect(bounds: &[Rect]) -> FlexLayout { + let tol = 0.5; + match (in_line_gap(bounds, true, tol), in_line_gap(bounds, false, tol)) { + (Some(gap), _) => auto_flex(gap, true, detect_align(bounds, true, tol)), + (None, Some(gap)) => auto_flex(gap, false, detect_align(bounds, false, tol)), _ => FlexLayout { wrap: true, height: FlexSize::Auto, ..Default::default() }, } } diff --git a/crates/engine/src/cmd/text.rs b/crates/engine/src/cmd/text.rs index dbfbcfe1..3a40844b 100644 --- a/crates/engine/src/cmd/text.rs +++ b/crates/engine/src/cmd/text.rs @@ -2105,7 +2105,7 @@ fn open_type_features(s: &mut Session, _p: &Value) -> Result { continue; } let names: std::collections::BTreeSet<&String> = fonts.values().filter_map(|f| f.stylistic_sets.get(&set)).collect(); - let name = if names.len() == 1 { names.first().map(|n| n.to_string()) } else { None }; + let name = if names.len() == 1 { names.first().map(ToString::to_string) } else { None }; let (applied, available) = measure(&tag); sets.push(json!({"set": set, "tag": tag, "name": name, "applied": applied, "available": available})); } diff --git a/crates/engine/src/math.rs b/crates/engine/src/math.rs index 22442095..16ef7e23 100644 --- a/crates/engine/src/math.rs +++ b/crates/engine/src/math.rs @@ -333,7 +333,7 @@ impl Parser<'_> { '+' => Node::Sym("+".into(), Class::Bin), '-' => Node::Sym("−".into(), Class::Bin), '*' => Node::Sym("∗".into(), Class::Bin), - '=' | '<' | '>' => Node::Sym(c.to_string(), Class::Rel), + '<'..='>' => Node::Sym(c.to_string(), Class::Rel), ',' | ';' => Node::Sym(c.to_string(), Class::Punct), '\'' => Node::Sym("′".into(), Class::Plain), c => Node::Sym(c.to_string(), Class::Plain), @@ -390,7 +390,7 @@ impl Parser<'_> { if self.peek().is_none() { break; } - if self.s[self.i..].starts_with(&['\\', 'r', 'i', 'g', 'h', 't']) && !self.s.get(self.i + 6).is_some_and(|c| c.is_ascii_alphabetic()) { + if self.s[self.i..].starts_with(&['\\', 'r', 'i', 'g', 'h', 't']) && !self.s.get(self.i + 6).is_some_and(char::is_ascii_alphabetic) { self.i += 6; break; } diff --git a/crates/engine/src/recovery.rs b/crates/engine/src/recovery.rs index eed263f0..7ec62d07 100644 --- a/crates/engine/src/recovery.rs +++ b/crates/engine/src/recovery.rs @@ -408,7 +408,7 @@ fn remove_emptied(dir: &Path) { let path = e.path(); let name = e.file_name(); let name = name.to_string_lossy(); - let numbered = path.file_stem().and_then(|s| s.to_str()).is_some_and(|s| s.parse::().is_ok()); + let numbered = path.file_stem().and_then(std::ffi::OsStr::to_str).is_some_and(|s| s.parse::().is_ok()); // Metadata whose document has gone, and a file a crash left half written. if (numbered && name.ends_with(".json")) || (name.starts_with(".designcraft-recovery-") && name.ends_with(".tmp")) { left.push(path); @@ -726,8 +726,8 @@ fn occupied_uids(dir: &Path) -> Result> { for entry in entries { let entry = entry.map_err(|e| EngineError::Other(format!("{}: {e}", dir.display())))?; let path = entry.path(); - if matches!(path.extension().and_then(|s| s.to_str()), Some("json" | "designcraft")) - && let Some(uid) = path.file_stem().and_then(|s| s.to_str()).and_then(|s| s.parse().ok()) + if matches!(path.extension().and_then(std::ffi::OsStr::to_str), Some("json" | "designcraft")) + && let Some(uid) = path.file_stem().and_then(std::ffi::OsStr::to_str).and_then(|s| s.parse().ok()) { ids.insert(uid); } @@ -756,10 +756,10 @@ pub fn list(dir: &Path) -> Vec<(u64, Value)> { let Ok(rd) = std::fs::read_dir(dir) else { return out }; for e in rd.flatten() { let p = e.path(); - if p.extension().and_then(|x| x.to_str()) != Some("designcraft") { + if p.extension().and_then(std::ffi::OsStr::to_str) != Some("designcraft") { continue; } - let Some(uid) = p.file_stem().and_then(|s| s.to_str()).and_then(|s| s.parse::().ok()) else { continue }; + let Some(uid) = p.file_stem().and_then(std::ffi::OsStr::to_str).and_then(|s| s.parse::().ok()) else { continue }; // Metadata is a file on disk: anything but an object (corrupt, hand-edited) counts as none. let meta = std::fs::read_to_string(p.with_extension("json")) .ok() diff --git a/crates/fonts/src/fontdb.rs b/crates/fonts/src/fontdb.rs index 352e0952..ef5314b3 100644 --- a/crates/fonts/src/fontdb.rs +++ b/crates/fonts/src/fontdb.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use std::sync::atomic::{AtomicU32, Ordering}; -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{Arc, Mutex, PoisonError, RwLock}; use kurbo::BezPath; use skrifa::instance::{Location, LocationRef, Size}; @@ -150,10 +150,10 @@ impl FaceRef { pub fn of(face: &Arc) -> FaceRef { static LEAKED: RwLock>> = RwLock::new(Vec::new()); let id = face.id as usize; - if let Some(Some(f)) = LEAKED.read().unwrap_or_else(|e| e.into_inner()).get(id) { + if let Some(Some(f)) = LEAKED.read().unwrap_or_else(PoisonError::into_inner).get(id) { return FaceRef(f); } - let mut w = LEAKED.write().unwrap_or_else(|e| e.into_inner()); + let mut w = LEAKED.write().unwrap_or_else(PoisonError::into_inner); if w.len() <= id { w.resize(id + 1, None); } @@ -1037,7 +1037,7 @@ pub fn same_style(a: &str, b: &str) -> bool { } fn norm(s: &str) -> String { - s.chars().filter(|c| c.is_alphanumeric()).flat_map(|c| c.to_lowercase()).collect() + s.chars().filter(|c| c.is_alphanumeric()).flat_map(char::to_lowercase).collect() } /// Weight implied by a style name. @@ -1107,7 +1107,7 @@ impl FontDb { pub fn set_system_fallback(&self, on: bool) { #[cfg(not(target_arch = "wasm32"))] { - self.sys.lock().unwrap_or_else(|e| e.into_inner()).enabled = on; + self.sys.lock().unwrap_or_else(PoisonError::into_inner).enabled = on; } #[cfg(target_arch = "wasm32")] let _ = on; @@ -1123,20 +1123,20 @@ impl FontDb { } fn read_faces(&self) -> std::sync::RwLockReadGuard<'_, Vec>> { - self.faces.read().unwrap_or_else(|e| e.into_inner()) + self.faces.read().unwrap_or_else(PoisonError::into_inner) } /// The system font catalog, scanned first if it hasn't been yet. #[cfg(not(target_arch = "wasm32"))] fn read_catalog(&self) -> std::sync::RwLockReadGuard<'_, Vec> { self.ensure_catalog(); - self.catalog.read().unwrap_or_else(|e| e.into_inner()) + self.catalog.read().unwrap_or_else(PoisonError::into_inner) } /// What the document with font scope `scope` sees: its own fonts ahead of the shared ones. /// Scope 0 (a document without fonts of its own) or a closed scope sees the shared fonts. pub fn scoped(&self, scope: u32) -> ScopedFonts<'_> { - let own = if scope == 0 { None } else { self.scopes.read().unwrap_or_else(|e| e.into_inner()).get(&scope).cloned() }; + let own = if scope == 0 { None } else { self.scopes.read().unwrap_or_else(PoisonError::into_inner).get(&scope).cloned() }; ScopedFonts { db: self, own } } @@ -1187,7 +1187,7 @@ impl FontDb { /// family's bold without its regular), skipping a family and style another thread has added /// meanwhile. Returns the number added. fn insert_faces(&self, new: Vec) -> usize { - let mut faces = self.faces.write().unwrap_or_else(|e| e.into_inner()); + let mut faces = self.faces.write().unwrap_or_else(PoisonError::into_inner); let mut added = 0; for f in new { if !has_face(&faces, &f.family, &f.style) { @@ -1238,7 +1238,7 @@ impl FontDb { let count = first.unwrap_or_else(|| self.scan_font_dirs()); // Retry characters and CJK chain families that newly installed fonts can now cover. // Reset outside scan_font_dirs: a chain lookup can hold sys during the initial scan. - let mut sys = self.sys.lock().unwrap_or_else(|e| e.into_inner()); + let mut sys = self.sys.lock().unwrap_or_else(PoisonError::into_inner); sys.generation = sys.generation.wrapping_add(1); sys.misses.clear(); sys.chain_tried.clear(); @@ -1257,7 +1257,7 @@ impl FontDb { // Rescan first: a lookup after the faces are dropped finds the new files. let cataloged = self.load_system_fonts(); let changed: Vec = { - let mut files = self.installed_files.lock().unwrap_or_else(|e| e.into_inner()); + let mut files = self.installed_files.lock().unwrap_or_else(PoisonError::into_inner); let mut changed: Vec<_> = files.iter().filter(|(p, stamp)| file_stamp(p).as_ref() != Some(*stamp)).map(|(p, _)| p.clone()).collect(); changed.sort(); for p in &changed { @@ -1268,15 +1268,15 @@ impl FontDb { if !changed.is_empty() { let stale = |f: &FontFace| matches!(&f.source, FontSource::Installed(p) if changed.contains(p)); let mut dropped = std::collections::HashSet::new(); - self.faces.write().unwrap_or_else(|e| e.into_inner()).retain(|f| { + self.faces.write().unwrap_or_else(PoisonError::into_inner).retain(|f| { let keep = !stale(f); if !keep { dropped.insert(f.id); } keep }); - self.instances.write().unwrap_or_else(|e| e.into_inner()).retain(|(base, _), f| !dropped.contains(base) && !stale(f)); - self.outlines.lock().unwrap_or_else(|e| e.into_inner()).retain(|(face, _), _| !dropped.contains(face)); + self.instances.write().unwrap_or_else(PoisonError::into_inner).retain(|(base, _), f| !dropped.contains(base) && !stale(f)); + self.outlines.lock().unwrap_or_else(PoisonError::into_inner).retain(|(face, _), _| !dropped.contains(face)); log::debug!("reloading {} changed font files ({} faces)", changed.len(), dropped.len()); } ReloadedFonts { cataloged, changed } @@ -1291,7 +1291,7 @@ impl FontDb { fn read_installed(&self, path: &std::path::Path) -> Option> { let stamp = file_stamp(path)?; let data = std::fs::read(path).ok()?; - self.installed_files.lock().unwrap_or_else(|e| e.into_inner()).insert(path.to_path_buf(), stamp); + self.installed_files.lock().unwrap_or_else(PoisonError::into_inner).insert(path.to_path_buf(), stamp); Some(data) } @@ -1351,7 +1351,7 @@ impl FontDb { } let n = found.len(); log::debug!("cataloged {n} system font faces"); - *self.catalog.write().unwrap_or_else(|e| e.into_inner()) = found; + *self.catalog.write().unwrap_or_else(PoisonError::into_inner) = found; n } @@ -1421,7 +1421,7 @@ impl FontDb { let name = path.file_name().map_or_else(|| path.display().to_string(), |n| n.to_string_lossy().into_owned()); // The listed length skips oversized files unread. let meta = std::fs::metadata(path).ok(); - let listed = meta.as_ref().map_or(0, |m| m.len()); + let listed = meta.as_ref().map_or(0, std::fs::Metadata::len); let faces = if listed > MAX_DOCUMENT_FONT_BYTES { Err(too_large()) } else if total.saturating_add(listed) > MAX_DOCUMENT_FONTS_TOTAL { @@ -1449,7 +1449,7 @@ impl FontDb { if !own.is_empty() { out.scope = NEXT_SCOPE.fetch_add(1, Ordering::Relaxed); out.faces = own.len(); - self.scopes.write().unwrap_or_else(|e| e.into_inner()).insert(out.scope, own.into()); + self.scopes.write().unwrap_or_else(PoisonError::into_inner).insert(out.scope, own.into()); } out } @@ -1466,7 +1466,7 @@ impl FontDb { room: u64, ) -> Result<(Vec>, u64), String> { use std::io::Read; - if let Some((len, m, faces)) = self.document_files.lock().unwrap_or_else(|e| e.into_inner()).get(path) + if let Some((len, m, faces)) = self.document_files.lock().unwrap_or_else(PoisonError::into_inner).get(path) && (*len, *m) == (listed, modified) { return Ok((faces.clone(), listed)); @@ -1494,14 +1494,14 @@ impl FontDb { if faces.is_empty() { return Err("not a font DesignCraft can read".into()); } - self.document_files.lock().unwrap_or_else(|e| e.into_inner()).insert(path.to_path_buf(), (listed, modified, faces.clone())); + self.document_files.lock().unwrap_or_else(PoisonError::into_inner).insert(path.to_path_buf(), (listed, modified, faces.clone())); Ok((faces, len)) } /// Close a document's font scope: its fonts are no longer found (in it or anywhere). Their /// files stay read, for the document opening again. pub fn close_scope(&self, scope: u32) { - self.scopes.write().unwrap_or_else(|e| e.into_inner()).remove(&scope); + self.scopes.write().unwrap_or_else(PoisonError::into_inner).remove(&scope); } /// The shared fonts' face for `family` + `style` (see [`ScopedFonts::face`]). @@ -1542,7 +1542,7 @@ impl FontDb { return false; } let generation = { - let sys = self.sys.lock().unwrap_or_else(|e| e.into_inner()); + let sys = self.sys.lock().unwrap_or_else(PoisonError::into_inner); if !sys.enabled || sys.misses.contains(&c) { return false; } @@ -1558,12 +1558,12 @@ impl FontDb { } } } - let mut paths: Vec = self.catalog.read().unwrap_or_else(|e| e.into_inner()).iter().map(|e| e.path.clone()).collect(); + let mut paths: Vec = self.catalog.read().unwrap_or_else(PoisonError::into_inner).iter().map(|e| e.path.clone()).collect(); paths.sort(); paths.dedup(); #[cfg(all(test, target_os = "linux"))] { - let hook = self.fallback_snapshot_hook.lock().unwrap_or_else(|e| e.into_inner()).take(); + let hook = self.fallback_snapshot_hook.lock().unwrap_or_else(PoisonError::into_inner).take(); if let Some(hook) = hook { let _ = hook.reached.send(()); hook.resume.wait(); @@ -1580,7 +1580,7 @@ impl FontDb { return true; } } - let mut sys = self.sys.lock().unwrap_or_else(|e| e.into_inner()); + let mut sys = self.sys.lock().unwrap_or_else(PoisonError::into_inner); if sys.generation == generation { sys.misses.insert(c); } @@ -1619,12 +1619,12 @@ impl FontDb { /// disappears. pub fn outline(&self, face: &FontFace, gid: u32) -> Arc { let key = (face.id, gid); - if let Some(p) = self.outlines.lock().unwrap_or_else(|e| e.into_inner()).get(&key) { + if let Some(p) = self.outlines.lock().unwrap_or_else(PoisonError::into_inner).get(&key) { return p.clone(); } let drawn = font_outline(face, gid); let p = Arc::new(if gid == 0 && drawn.elements().is_empty() { self.missing_box(face).unwrap_or(drawn) } else { drawn }); - let mut cache = self.outlines.lock().unwrap_or_else(|e| e.into_inner()); + let mut cache = self.outlines.lock().unwrap_or_else(PoisonError::into_inner); if cache.len() >= OUTLINE_CACHE_MAX { cache.clear(); } @@ -1986,7 +1986,7 @@ impl ScopedFonts<'_> { return Some(base); } let key = (base.id, style.to_string()); - if let Some(f) = self.db.instances.read().unwrap_or_else(|e| e.into_inner()).get(&key) { + if let Some(f) = self.db.instances.read().unwrap_or_else(PoisonError::into_inner).get(&key) { return Some(f.clone()); } let mut coords = base.coords.clone(); @@ -2004,7 +2004,7 @@ impl ScopedFonts<'_> { } } let f = make_face(base.bytes.clone(), base.source.clone(), base.index, base.family.clone(), style.to_string(), coords)?; - Some(self.db.instances.write().unwrap_or_else(|e| e.into_inner()).entry(key).or_insert_with(|| Arc::new(f)).clone()) + Some(self.db.instances.write().unwrap_or_else(PoisonError::into_inner).entry(key).or_insert_with(|| Arc::new(f)).clone()) } fn find(&self, family: &str, style: &str) -> Option> { @@ -2077,7 +2077,7 @@ impl ScopedFonts<'_> { #[cfg(not(target_arch = "wasm32"))] if !self.db.is_loaded(family) { // Held while loading, so a parallel lookup waits for the font instead of passing it by. - let mut sys = self.db.sys.lock().unwrap_or_else(|e| e.into_inner()); + let mut sys = self.db.sys.lock().unwrap_or_else(PoisonError::into_inner); if sys.enabled && sys.chain_tried.insert(family) { self.db.load_cataloged(family); } diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 6823fc00..66c5ef12 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -882,7 +882,7 @@ impl Renderer { let page = Rect::new(-l * page_w, -t * page_h, (1.0 - l) * page_w, (1.0 - t) * page_h); let Some(pm) = images::mip(&data, asset.page, page_w, on_screen) else { return }; // A coloured image is already in the plate's inks (its colours come from `color_of`). - let pm = match (colorize::colorized(f.doc, &g.fill, &asset.data, &pm), PLATE.with(|p| p.get())) { + let pm = match (colorize::colorized(f.doc, &g.fill, &asset.data, &pm), PLATE.with(std::cell::Cell::get)) { (Some(coloured), _) => coloured, (None, Some(plate)) => plate_pixmap(&pm, plate), (None, None) => pm, @@ -913,7 +913,7 @@ pub(crate) fn cmyk_table() -> std::sync::Arc> { type Lut = (designcraft_color::cms::ColorSettings, std::sync::Arc>); static LUT: std::sync::Mutex> = std::sync::Mutex::new(None); let settings = designcraft_color::cms::active_settings(); - let mut g = LUT.lock().unwrap_or_else(|e| e.into_inner()); + let mut g = LUT.lock().unwrap_or_else(std::sync::PoisonError::into_inner); match g.as_ref() { Some((s, t)) if *s == settings => t.clone(), _ => { @@ -961,7 +961,7 @@ fn plate_pixmap(pm: &std::sync::Arc, plate: u8) -> std::sync: let key = (std::sync::Arc::as_ptr(pm) as usize, plate); let settings = designcraft_color::cms::active_settings(); { - let mut guard = CACHE.lock().unwrap_or_else(|e| e.into_inner()); + let mut guard = CACHE.lock().unwrap_or_else(std::sync::PoisonError::into_inner); if let Some((previous, map, _)) = guard.as_ref() { if *previous == settings { if let Some((_, derived)) = map.get(&key) { @@ -994,7 +994,7 @@ fn plate_pixmap(pm: &std::sync::Arc, plate: u8) -> std::sync: let out = std::sync::Arc::new(vello_cpu::Pixmap::from_parts(data, pm.width(), pm.height())); let bytes = (pm.width() as usize).saturating_mul(pm.height() as usize).saturating_mul(8); if bytes <= BUDGET { - let mut guard = CACHE.lock().unwrap_or_else(|e| e.into_inner()); + let mut guard = CACHE.lock().unwrap_or_else(std::sync::PoisonError::into_inner); let (previous, map, used) = guard.get_or_insert_with(|| (settings.clone(), Default::default(), 0)); if *previous != settings || used.saturating_add(bytes) > BUDGET { map.clear(); @@ -1024,7 +1024,7 @@ thread_local! { /// Screen indicators and placeholders are display sRGB, independent of the document's RGB /// working profile. Encode their inks only while drawing a process plate or CMYK blend pass. pub(crate) fn screen_color(rgb: [u8; 3], alpha: u8) -> peniko::Color { - if PLATE.with(|plate| plate.get()).is_none() { + if PLATE.with(std::cell::Cell::get).is_none() { return peniko::Color::from_rgba8(rgb[0], rgb[1], rgb[2], alpha); } let [c, m, y, k] = cmyk_in(&cmyk_table(), rgb.map(|v| v as f32 / 255.0)); @@ -1032,7 +1032,7 @@ pub(crate) fn screen_color(rgb: [u8; 3], alpha: u8) -> peniko::Color { } pub fn color_of(c: &designcraft_color::Color, alpha: f32) -> peniko::Color { - if let Some(p) = PLATE.with(|p| p.get()) { + if let Some(p) = PLATE.with(std::cell::Cell::get) { let cmyk = match *c { designcraft_color::Color::Cmyk { c, m, y, k } => [c, m, y, k], _ => c.to_cmyk_managed(designcraft_color::cms::Intent::RelativeColorimetric), @@ -1045,7 +1045,7 @@ pub fn color_of(c: &designcraft_color::Color, alpha: f32) -> peniko::Color { let v = ink(p.min(3) as usize); return peniko::Color::from_rgba8(v, v, v, a); } - if RICH_BLACK.with(|r| r.get()) + if RICH_BLACK.with(std::cell::Cell::get) && let designcraft_color::Color::Cmyk { c: cc, m, y, k } = *c && cc <= 1e-3 && m <= 1e-3