mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-03 17:12:52 -06:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a553966498 |
@@ -171,9 +171,7 @@ impl Dictionary {
|
||||
["COMPOUNDWORDMAX", n, ..] => d.compound_max = n.parse().unwrap_or(0),
|
||||
["REP", n] => {
|
||||
for _ in 0..n.parse::<usize>().unwrap_or(0) {
|
||||
// The count is the file's claim: stop when the records run out.
|
||||
let Some(line) = lines.next() else { break };
|
||||
let r: Vec<&str> = line.split_whitespace().collect();
|
||||
let r: Vec<&str> = lines.next().unwrap_or("").split_whitespace().collect();
|
||||
if let [_, a, b, ..] = r.as_slice() {
|
||||
d.rep.push((a.replace('_', " "), b.replace('_', " ")));
|
||||
}
|
||||
@@ -184,8 +182,7 @@ impl Dictionary {
|
||||
let Some(flag) = one_flag(Some(flag), mode) else { continue };
|
||||
let cross = cross.eq_ignore_ascii_case("y");
|
||||
for _ in 0..n.parse::<usize>().unwrap_or(0) {
|
||||
let Some(line) = lines.next() else { break };
|
||||
let r: Vec<&str> = line.split_whitespace().collect();
|
||||
let r: Vec<&str> = lines.next().unwrap_or("").split_whitespace().collect();
|
||||
let [_, _, strip, add, rest @ ..] = r.as_slice() else { continue };
|
||||
let (add, cont) = match add.split_once('/') {
|
||||
Some((a, c)) => (a, parse_flags(c, mode)),
|
||||
@@ -579,15 +576,6 @@ fn cache() -> &'static Cache {
|
||||
CACHE.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
/// Language strings remembered by [`dictionary`]; a string past it is resolved again on each call.
|
||||
const MAX_CACHED_LANGS: usize = 256;
|
||||
|
||||
/// Parsed dictionaries by their affix file, so aliases of one language share it.
|
||||
fn by_files() -> &'static Mutex<HashMap<PathBuf, Arc<Dictionary>>> {
|
||||
static M: OnceLock<Mutex<HashMap<PathBuf, Arc<Dictionary>>>> = OnceLock::new();
|
||||
M.get_or_init(Default::default)
|
||||
}
|
||||
|
||||
/// The dictionary for `lang` (a BCP-47 tag such as `en-US`), loaded once per process.
|
||||
pub fn dictionary(lang: &str) -> Option<Arc<Dictionary>> {
|
||||
let cache = cache();
|
||||
@@ -598,23 +586,10 @@ pub fn dictionary(lang: &str) -> Option<Arc<Dictionary>> {
|
||||
return d.clone();
|
||||
}
|
||||
let loaded = find_in(&search_dirs(), lang).and_then(|(a, d)| {
|
||||
// Tags that resolve to the same files share one parsed dictionary.
|
||||
if let Ok(m) = by_files().lock()
|
||||
&& let Some(dict) = m.get(&a)
|
||||
{
|
||||
return Some(dict.clone());
|
||||
}
|
||||
let (bytes_a, bytes_d) = (std::fs::read(&a).ok()?, std::fs::read(d).ok()?);
|
||||
let dict = Arc::new(Dictionary::parse(&bytes_a, &bytes_d));
|
||||
// A concurrent loader of the same files may have inserted first: share its copy.
|
||||
if let Ok(mut m) = by_files().lock() {
|
||||
return Some(m.entry(a).or_insert(dict).clone());
|
||||
}
|
||||
Some(dict)
|
||||
let (a, d) = (std::fs::read(a).ok()?, std::fs::read(d).ok()?);
|
||||
Some(Arc::new(Dictionary::parse(&a, &d)))
|
||||
});
|
||||
if let Ok(mut c) = cache.lock()
|
||||
&& c.len() < MAX_CACHED_LANGS
|
||||
{
|
||||
if let Ok(mut c) = cache.lock() {
|
||||
c.insert(key, loaded.clone());
|
||||
}
|
||||
loaded
|
||||
@@ -905,14 +880,6 @@ SFX D Y 1\nSFX D 0 ed/S .\n";
|
||||
assert!(d.suggest("workingslides", 5).contains(&"working slides".to_string()));
|
||||
}
|
||||
|
||||
/// An affix header can claim billions of records; parsing stops where the file does.
|
||||
#[test]
|
||||
fn a_header_count_past_the_records_ends_with_them() {
|
||||
let aff = b"SFX S Y 4000000000\nSFX S 0 s .\nREP 4000000000\nREP a b\n";
|
||||
let d = Dictionary::parse(aff, b"1\ncat/S\n".as_slice());
|
||||
assert!(d.check("cats"));
|
||||
}
|
||||
|
||||
/// #1175: compound splitting is quadratic in the word's length, so a word longer than
|
||||
/// `MAX_CHECK_CHARS` is not checked; shorter ones still are.
|
||||
#[test]
|
||||
|
||||
@@ -76,16 +76,12 @@ fn tokens(l: &TextLayout, chars: bool) -> (Vec<Token>, Vec<(usize, usize)>) {
|
||||
|
||||
/// For each token of `b`, the token of `a` with the same text, in reading order, each used once.
|
||||
fn matches(a: &[Token], b: &[Token]) -> Vec<Option<usize>> {
|
||||
let mut used = vec![false; a.len()];
|
||||
b.iter()
|
||||
.map(|tb| {
|
||||
let i = a.iter().enumerate().position(|(i, ta)| !used.get(i).copied().unwrap_or(true) && ta.key == tb.key)?;
|
||||
if let Some(u) = used.get_mut(i) {
|
||||
*u = true;
|
||||
}
|
||||
Some(i)
|
||||
})
|
||||
.collect()
|
||||
// Unused tokens of `a` by text, in reading order, so each lookup is a pop rather than a scan.
|
||||
let mut free: std::collections::HashMap<&str, std::collections::VecDeque<usize>> = std::collections::HashMap::new();
|
||||
for (i, ta) in a.iter().enumerate() {
|
||||
free.entry(ta.key.as_str()).or_default().push_back(i);
|
||||
}
|
||||
b.iter().map(|tb| free.get_mut(tb.key.as_str())?.pop_front()).collect()
|
||||
}
|
||||
|
||||
fn layout(ctx: &Ctx, s: &Shape, x: Xfrm, fields: &dyn Fields) -> Option<TextLayout> {
|
||||
@@ -228,3 +224,19 @@ pub(crate) fn draw(
|
||||
pub(crate) fn token_keys(l: &TextLayout, chars: bool) -> Vec<String> {
|
||||
tokens(l, chars).0.into_iter().map(|t| t.key).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod match_tests {
|
||||
use super::*;
|
||||
|
||||
fn tok(k: &str) -> Token {
|
||||
Token { key: k.into(), glyphs: Vec::new(), origin: Point::ZERO, run: 0 }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_source_token_is_used_once_in_reading_order() {
|
||||
let a: Vec<Token> = ["a", "b", "a"].map(tok).into();
|
||||
let b: Vec<Token> = ["a", "a", "a", "b", "c"].map(tok).into();
|
||||
assert_eq!(matches(&a, &b), vec![Some(0), Some(2), None, Some(1), None]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ pub struct GlyphRun {
|
||||
pub link: bool,
|
||||
/// Opacity of the fill on top of `color`'s own alpha (0 for no fill).
|
||||
pub alpha: f64,
|
||||
pub gradient: Option<deckcraft_model::style::Gradient>,
|
||||
pub gradient: Option<std::sync::Arc<deckcraft_model::style::Gradient>>,
|
||||
/// Shadow and glow behind the glyphs (`a:rPr/a:effectLst`).
|
||||
pub effects: Option<RunEffects>,
|
||||
/// East Asian vertical text (`eaVert`): each glyph stands upright, turned a quarter turn
|
||||
|
||||
@@ -46,7 +46,7 @@ pub(crate) struct Style {
|
||||
pub(crate) highlight: Option<Rgba>,
|
||||
pub(crate) outline: Option<(Rgba, f64)>,
|
||||
pub(crate) link: bool,
|
||||
pub(crate) gradient: Option<deckcraft_model::style::Gradient>,
|
||||
pub(crate) gradient: Option<std::sync::Arc<deckcraft_model::style::Gradient>>,
|
||||
pub(crate) effects: Option<RunEffects>,
|
||||
/// "Regular", "Bold"… for the slot and fallback faces.
|
||||
pub(crate) style_name: &'static str,
|
||||
@@ -136,7 +136,7 @@ pub(crate) fn make_style(ctx: &Ctx, r: &RunProps, scale: f64, prompt: Option<Rgb
|
||||
for s in &mut g.stops {
|
||||
s.color = deckcraft_model::ColorRef::rgb(ctx.color(&s.color, None));
|
||||
}
|
||||
(g.stops.first().map(|s| ctx.color(&s.color, None)).unwrap_or(Rgba::BLACK), 1.0, Some(g))
|
||||
(g.stops.first().map(|s| ctx.color(&s.color, None)).unwrap_or(Rgba::BLACK), 1.0, Some(std::sync::Arc::new(g)))
|
||||
}
|
||||
Some(Fill::None) => (Rgba::TRANSPARENT, 0.0, None),
|
||||
_ => (resolve::text_color(ctx, r), 1.0, None),
|
||||
|
||||
Reference in New Issue
Block a user