chore: resolve SonarCloud maintainability issues (#1698)

* chore: resolve SonarCloud maintainability issues

- Replace lambda closures with method references (S1612)
- Replace wildcard imports with explicit imports (S2208)
- Refactor complex functions with extracted helpers (S3776)
- Replace OR pattern with range pattern in math parser (S9047)

* style: apply rustfmt to warichu helper

* fix(compose): revert slice conversion in tests and remove unused var in warichu

* Group warichu row layout scalars to satisfy S107 parameter limit

* style: satisfy rustfmt in warichu.rs

---------

Co-authored-by: Dylan Taylor <dylan@craftmaker.dev>
This commit is contained in:
Dylan M. Taylor
2026-10-24 03:54:24 -04:00
committed by GitHub
co-authored by Dylan Taylor
parent 5449bc4757
commit f90d286721
11 changed files with 159 additions and 130 deletions
+33 -28
View File
@@ -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())?;
+10 -10
View File
@@ -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<Vec<Arc<IccProfile>>> = RwLock::new(Vec::new());
fn builtin_rgb_cache(name: &str) -> Option<Arc<IccProfile>> {
static CACHE: Mutex<Vec<Arc<IccProfile>>> = 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<Arc<IccProfile>> {
}
fn user_profile(name: &str) -> Option<Arc<IccProfile>> {
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<ProfileInfo> {
.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<String>) -> Result<ProfileInfo, C
return Err(CmsError::Unsupported(format!("{}: no usable transform to/from sRGB", p.name)));
}
let info = ProfileInfo { name: p.name.clone(), kind: p.kind, builtin: false };
let mut u = USER.write().unwrap_or_else(|e| e.into_inner());
let mut u = USER.write().unwrap_or_else(PoisonError::into_inner);
u.retain(|x| x.name != info.name);
u.push(Arc::new(p));
drop(u);
@@ -522,7 +522,7 @@ impl Cms {
/// A cached 3-D lookup table implementing [`Cms::proof_srgb`] for 8-bit pixels.
pub fn proof_lut(&self, proof: &ProofSetup) -> Arc<ProofLut> {
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<Vec<(String, Arc<ProofLut>)>> = 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<Vec<f32>>) -> Option<[f32; 4]> {
@@ -704,10 +704,10 @@ pub fn generation() -> u64 {
/// The active colour-management context (Edit → Color Settings).
pub fn active() -> Arc<Cms> {
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);
}
+1 -1
View File
@@ -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)
}
+13 -13
View File
@@ -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::<Vec<_>>(),
));
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<crate::xref::XrefIndex> {
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<RunningIndex> {
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
}
}
+22 -3
View File
@@ -217,13 +217,31 @@ fn layout(styles: &[RunStyle], line: &mut [PlacedGlyph], members: &[usize]) -> f
restore(line, members, &saved);
return 0.0;
}
let rows = counts.len().max(1);
let small = st.size * scale;
// 0 is the default (one small em). Any other value, including a negative one, is added to
// that em and then clamped so the rows cannot swap order.
// 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, RowLayout { block, x0: old_x0, gap }, st);
let delta = block - old_span;
shift_non_member_glyphs(line, members, &old_xs, old_x0, old_right, delta, scale);
delta
}
#[derive(Clone, Copy)]
struct RowLayout {
block: f64,
x0: f64,
gap: f64,
}
fn position_warichu_rows(line: &mut [PlacedGlyph], members: &[usize], counts: &[usize], widths: &[f64], layout: RowLayout, st: &RunStyle) {
let block = layout.block;
let old_x0 = layout.x0;
let gap = layout.gap;
let rows = counts.len().max(1);
let mut off = 0_usize;
for (li, &c) in counts.iter().enumerate() {
let w = widths[li];
@@ -242,7 +260,9 @@ 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 +285,6 @@ fn layout(styles: &[RunStyle], line: &mut [PlacedGlyph], members: &[usize]) -> f
g.x = anchor.1 + (g.x - anchor.0) * scale;
}
}
delta
}
#[cfg(test)]
+35 -30
View File
@@ -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<f64> = 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::<f64>() / gaps.len().max(1) as f64)
};
fn in_line_gap(bounds: &[Rect], row: bool, tol: f64) -> Option<f64> {
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<f64> = 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::<f64>() / 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() },
}
}
+1 -1
View File
@@ -2105,7 +2105,7 @@ fn open_type_features(s: &mut Session, _p: &Value) -> Result<Value> {
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}));
}
+2 -2
View File
@@ -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;
}
+5 -5
View File
@@ -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::<u64>().is_ok());
let numbered = path.file_stem().and_then(std::ffi::OsStr::to_str).is_some_and(|s| s.parse::<u64>().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<std::collections::HashSet<u64>> {
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::<u64>().ok()) else { continue };
let Some(uid) = p.file_stem().and_then(std::ffi::OsStr::to_str).and_then(|s| s.parse::<u64>().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()
+30 -30
View File
@@ -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<FontFace>) -> FaceRef {
static LEAKED: RwLock<Vec<Option<&'static FontFace>>> = 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<Arc<FontFace>>> {
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<CatalogEntry>> {
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<FontFace>) -> 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<std::path::PathBuf> = {
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<Vec<u8>> {
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<Arc<FontFace>>, 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<std::path::PathBuf> = self.catalog.read().unwrap_or_else(|e| e.into_inner()).iter().map(|e| e.path.clone()).collect();
let mut paths: Vec<std::path::PathBuf> = 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<BezPath> {
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<Arc<FontFace>> {
@@ -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);
}
+7 -7
View File
@@ -888,7 +888,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,
@@ -919,7 +919,7 @@ pub(crate) fn cmyk_table() -> std::sync::Arc<Vec<[f32; 4]>> {
type Lut = (designcraft_color::cms::ColorSettings, std::sync::Arc<Vec<[f32; 4]>>);
static LUT: std::sync::Mutex<Option<Lut>> = 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(),
_ => {
@@ -967,7 +967,7 @@ fn plate_pixmap(pm: &std::sync::Arc<vello_cpu::Pixmap>, 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) {
@@ -1000,7 +1000,7 @@ fn plate_pixmap(pm: &std::sync::Arc<vello_cpu::Pixmap>, 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();
@@ -1030,7 +1030,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));
@@ -1038,7 +1038,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),
@@ -1051,7 +1051,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