mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-29 20:56:47 -06:00
refactor(engine): split find change and used fonts (Sonar S3776)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
8299d26400
commit
88dd71cc51
@@ -323,18 +323,38 @@ fn replacement(p: &Value) -> String {
|
||||
if bool_or(p, "grep", false) { translate_replacement(c) } else { metachars(c) }
|
||||
}
|
||||
|
||||
/// Repair byte validity of a text selection once against the final story, without remapping valid offsets.
|
||||
fn repair_selection(t: &mut designcraft_doc::TextSel, st: &designcraft_doc::Story) {
|
||||
let range = t.range();
|
||||
let start = floor_char_boundary(&st.text, range.start);
|
||||
let mut end = range.end.min(st.len());
|
||||
if t.is_caret() {
|
||||
end = start;
|
||||
} else if !st.text.is_char_boundary(end) {
|
||||
let floor = floor_char_boundary(&st.text, end);
|
||||
end = st.text.get(floor..).and_then(|rest| rest.chars().next()).map_or(floor, |ch| floor + ch.len_utf8());
|
||||
}
|
||||
(t.anchor, t.focus) = if t.anchor <= t.focus { (start, end) } else { (end, start) };
|
||||
}
|
||||
|
||||
/// The character overrides a Change applies to every replacement, from its `attrs` object.
|
||||
fn char_overrides(attrs: Option<&Value>) -> Result<designcraft_doc::CharAttrs> {
|
||||
let mut over = designcraft_doc::CharAttrs::default();
|
||||
if let Some(o) = attrs.and_then(Value::as_object) {
|
||||
for (k, v) in o {
|
||||
over.set_json(k, v).map_err(|e| bad("find.change", e))?;
|
||||
}
|
||||
}
|
||||
Ok(over)
|
||||
}
|
||||
|
||||
fn change(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let re = build(p)?;
|
||||
let grep = bool_or(p, "grep", false);
|
||||
let rep = replacement(p);
|
||||
let first_only = bool_or(p, "first", false);
|
||||
let attrs = p.get("attrs").cloned();
|
||||
let mut over = designcraft_doc::CharAttrs::default();
|
||||
if let Some(o) = attrs.as_ref().and_then(Value::as_object) {
|
||||
for (k, v) in o {
|
||||
over.set_json(k, v).map_err(|e| bad("find.change", e))?;
|
||||
}
|
||||
}
|
||||
let over = char_overrides(attrs.as_ref())?;
|
||||
super::text::validate_char_fill(&s.doc()?.doc, &over, "find.change")?;
|
||||
let targets = scope(s, p)?.targets;
|
||||
s.edit(|d, sel| {
|
||||
@@ -361,17 +381,7 @@ fn change(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
count += 1;
|
||||
}
|
||||
if let Some(t) = sel.text.as_mut().filter(|t| t.story == sid && t.cell == cell) {
|
||||
// Repair byte validity once against the final story, without remapping valid offsets.
|
||||
let range = t.range();
|
||||
let start = floor_char_boundary(&st.text, range.start);
|
||||
let mut end = range.end.min(st.len());
|
||||
if t.is_caret() {
|
||||
end = start;
|
||||
} else if !st.text.is_char_boundary(end) {
|
||||
let floor = floor_char_boundary(&st.text, end);
|
||||
end = st.text.get(floor..).and_then(|rest| rest.chars().next()).map_or(floor, |ch| floor + ch.len_utf8());
|
||||
}
|
||||
(t.anchor, t.focus) = if t.anchor <= t.focus { (start, end) } else { (end, start) };
|
||||
repair_selection(t, st);
|
||||
}
|
||||
// `first` is the first match of the whole search, not of each story.
|
||||
if first_only && count > 0 {
|
||||
|
||||
@@ -81,36 +81,41 @@ fn list(d: &Document) -> Vec<Value> {
|
||||
used_fonts(d).into_iter().map(|font| json!(font)).collect()
|
||||
}
|
||||
|
||||
pub(super) fn used_fonts(d: &Document) -> Vec<UsedFont> {
|
||||
let db = designcraft_fonts::FontDb::global().scoped(d.font_scope);
|
||||
let mut used: BTreeMap<(String, String), (usize, std::collections::BTreeSet<char>)> = BTreeMap::new();
|
||||
for_each_story(d, &mut |st| {
|
||||
let ranges = st.para_ranges();
|
||||
for (pi, r) in ranges.iter().enumerate() {
|
||||
let (_, base) = d.styles.resolve_para(&st.paras[pi]);
|
||||
for (rr, fmt) in st.runs() {
|
||||
let n = st.text[rr.start.max(r.start)..rr.end.min(r.end).max(rr.start.max(r.start))].chars().count();
|
||||
if n == 0 && !(r.is_empty() && rr.contains(&r.start)) {
|
||||
continue;
|
||||
}
|
||||
let p = d.styles.resolve_char(&base, fmt);
|
||||
if let Some(f) = d.styles.composite_font(&p.font_family) {
|
||||
let text = st.text.get(rr.start.max(r.start)..rr.end.min(r.end).max(rr.start.max(r.start))).unwrap_or("");
|
||||
for c in text.chars() {
|
||||
if let Some(e) = f.entry(c) {
|
||||
let entry = used.entry((e.family.clone(), e.style.clone())).or_default();
|
||||
entry.0 += 1;
|
||||
entry.1.insert(c);
|
||||
}
|
||||
type FontTally = BTreeMap<(String, String), (usize, std::collections::BTreeSet<char>)>;
|
||||
|
||||
/// Count the characters of every run of one story against the font each resolves to.
|
||||
fn tally_story(d: &Document, st: &Story, used: &mut FontTally) {
|
||||
let ranges = st.para_ranges();
|
||||
for (pi, r) in ranges.iter().enumerate() {
|
||||
let (_, base) = d.styles.resolve_para(&st.paras[pi]);
|
||||
for (rr, fmt) in st.runs() {
|
||||
let n = st.text[rr.start.max(r.start)..rr.end.min(r.end).max(rr.start.max(r.start))].chars().count();
|
||||
if n == 0 && !(r.is_empty() && rr.contains(&r.start)) {
|
||||
continue;
|
||||
}
|
||||
let p = d.styles.resolve_char(&base, fmt);
|
||||
if let Some(f) = d.styles.composite_font(&p.font_family) {
|
||||
let text = st.text.get(rr.start.max(r.start)..rr.end.min(r.end).max(rr.start.max(r.start))).unwrap_or("");
|
||||
for c in text.chars() {
|
||||
if let Some(e) = f.entry(c) {
|
||||
let entry = used.entry((e.family.clone(), e.style.clone())).or_default();
|
||||
entry.0 += 1;
|
||||
entry.1.insert(c);
|
||||
}
|
||||
} else {
|
||||
let entry = used.entry((p.font_family, p.font_style)).or_default();
|
||||
entry.0 += n;
|
||||
entry.1.extend(st.text.get(rr.start.max(r.start)..rr.end.min(r.end).max(rr.start.max(r.start))).unwrap_or("").chars());
|
||||
}
|
||||
} else {
|
||||
let entry = used.entry((p.font_family, p.font_style)).or_default();
|
||||
entry.0 += n;
|
||||
entry.1.extend(st.text.get(rr.start.max(r.start)..rr.end.min(r.end).max(rr.start.max(r.start))).unwrap_or("").chars());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn used_fonts(d: &Document) -> Vec<UsedFont> {
|
||||
let db = designcraft_fonts::FontDb::global().scoped(d.font_scope);
|
||||
let mut used = FontTally::new();
|
||||
for_each_story(d, &mut |st| tally_story(d, st, &mut used));
|
||||
let mut out: Vec<UsedFont> = used
|
||||
.into_iter()
|
||||
.map(|((family, style), (n, characters))| {
|
||||
|
||||
Reference in New Issue
Block a user