mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-07 20:23:35 +00:00
Merge pull request #5592 from dotCooCoo/fix/setstyle-warning-skips-protected
Type: the font warning leaves out characters Missing Glyph Protection kept
This commit is contained in:
@@ -930,8 +930,8 @@ impl CharChange {
|
||||
|
||||
/// Type › Enable Missing Glyph Protection: after a font change, the characters of `runs` their new
|
||||
/// font has no glyph for, but their font `before` the change had, keep that font: its family,
|
||||
/// style, version and Variable Font settings.
|
||||
pub(crate) fn protect_missing_glyphs(before: &[TextRun], runs: &mut Vec<TextRun>) {
|
||||
/// style, version and Variable Font settings. Returns the byte ranges that kept their font, in order.
|
||||
pub(crate) fn protect_missing_glyphs(before: &[TextRun], runs: &mut Vec<TextRun>) -> Vec<(usize, usize)> {
|
||||
fn chars(runs: &[TextRun]) -> impl Iterator<Item = (usize, char, &CharStyle)> {
|
||||
runs.iter()
|
||||
.scan(0, |at, r| {
|
||||
@@ -963,6 +963,7 @@ pub(crate) fn protect_missing_glyphs(before: &[TextRun], runs: &mut Vec<TextRun>
|
||||
_ => keep.push((at, end, old)),
|
||||
}
|
||||
}
|
||||
let kept = keep.iter().map(|(a, b, _)| (*a, *b)).collect();
|
||||
for (a, b, old) in keep {
|
||||
edit::style_range(runs, a, b, |st| {
|
||||
st.font_family.clone_from(&old.font_family);
|
||||
@@ -971,6 +972,30 @@ pub(crate) fn protect_missing_glyphs(before: &[TextRun], runs: &mut Vec<TextRun>
|
||||
st.font_axes.clone_from(&old.font_axes);
|
||||
});
|
||||
}
|
||||
kept
|
||||
}
|
||||
|
||||
/// The runs of `runs` from byte `a` to `b`, leaving out the ranges `kept` (in order) that Missing
|
||||
/// Glyph Protection gave their earlier font: the characters a font command set its font on.
|
||||
pub(crate) fn unprotected_runs(runs: &[TextRun], a: usize, b: usize, kept: &[(usize, usize)]) -> Vec<TextRun> {
|
||||
let mut out = vec![];
|
||||
let mut at = a;
|
||||
for &(k0, k1) in kept {
|
||||
if k1 <= at {
|
||||
continue;
|
||||
}
|
||||
if k0 >= b {
|
||||
break;
|
||||
}
|
||||
if k0 > at {
|
||||
out.extend(edit::slice_runs(runs, at, k0));
|
||||
}
|
||||
at = k1;
|
||||
}
|
||||
if at < b {
|
||||
out.extend(edit::slice_runs(runs, at, b));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn set_range_style(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
@@ -1021,12 +1046,10 @@ fn set_range_style(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
let before = protect.then(|| t.runs.clone());
|
||||
edit::style_range(&mut t.runs, a, b, |st| change.apply(st));
|
||||
if let Some(before) = before {
|
||||
protect_missing_glyphs(&before, &mut t.runs);
|
||||
}
|
||||
let kept = before.map(|before| protect_missing_glyphs(&before, &mut t.runs)).unwrap_or_default();
|
||||
if sets_font {
|
||||
// Empty text has one run, which the empty range styles.
|
||||
let styled = if a == b { t.runs.clone() } else { edit::slice_runs(&t.runs, a, b) };
|
||||
// Empty text has one run, which the empty range styles. Characters that kept their font aren't the command's.
|
||||
let styled = if a == b { t.runs.clone() } else { unprotected_runs(&t.runs, a, b, &kept) };
|
||||
set.extend(styled.iter().map(|r| super::fonts::font_of(&r.style)));
|
||||
}
|
||||
refresh_bounds(t);
|
||||
|
||||
@@ -874,16 +874,16 @@ fn set_style(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
st.features = super::textedit::change_features(&st.features, c);
|
||||
}
|
||||
});
|
||||
if let Some(before) = before {
|
||||
super::textedit::protect_missing_glyphs(&before, &mut t.runs);
|
||||
}
|
||||
let kept = before.map(|before| super::textedit::protect_missing_glyphs(&before, &mut t.runs)).unwrap_or_default();
|
||||
if font.is_some() || style.is_some() {
|
||||
// All the text: every run, the one run of empty text included.
|
||||
// All the text: every run, the one run of empty text included. Characters that kept their font aren't the
|
||||
// command's.
|
||||
let styled = match range {
|
||||
None => t.runs.clone(),
|
||||
None if kept.is_empty() => t.runs.clone(),
|
||||
None => super::textedit::unprotected_runs(&t.runs, 0, vectorcraft_text::edit::runs_len(&t.runs), &kept),
|
||||
Some(r) => {
|
||||
let (a, b) = r.bytes(t);
|
||||
vectorcraft_text::edit::slice_runs(&t.runs, a, b)
|
||||
super::textedit::unprotected_runs(&t.runs, a, b, &kept)
|
||||
}
|
||||
};
|
||||
set.extend(styled.iter().map(|r| super::fonts::font_of(&r.style)));
|
||||
|
||||
@@ -1474,6 +1474,29 @@ fn missing_glyph_protection_keeps_the_version_of_the_font_kept() {
|
||||
assert_eq!(versions, [moved.clone(), kept, moved]);
|
||||
}
|
||||
|
||||
/// The font warning of `text.setStyle` and `text.setRangeStyle` names only the fonts the command
|
||||
/// set: a character that keeps an unavailable font through Missing Glyph Protection isn't reported.
|
||||
#[test]
|
||||
fn missing_glyph_protection_leaves_the_font_kept_out_of_the_warning() {
|
||||
let (from, to, c) = ("No Such Font Family", "JetBrains Mono", '\u{180}');
|
||||
let db = vectorcraft_text::FontDb::global();
|
||||
// The unavailable family is drawn in the fallback font, which has the character; the new font is installed and lacks it.
|
||||
let installed = db.resolve(to, "Regular").is_some_and(|(_, m)| m == vectorcraft_text::FontMatch::Exact);
|
||||
if !installed || !db.face(from, "Regular").is_some_and(|f| f.covers(c)) || db.face(to, "Regular").is_some_and(|f| f.covers(c)) {
|
||||
return; // The fonts installed here cover other characters.
|
||||
}
|
||||
let mut s = new_doc();
|
||||
let id = NodeId(s.execute("text.create", &json!({"x": 10, "y": 50, "text": "a\u{180}c", "font": from})).unwrap()["id"].as_u64().unwrap());
|
||||
s.execute("select.set", &json!({"ids": [id.0]})).unwrap();
|
||||
for change in [json!({"font": to}), json!({"id": id.0, "start": 0, "end": 4, "font": to})] {
|
||||
let cmd = if change.get("id").is_some() { "text.setRangeStyle" } else { "text.setStyle" };
|
||||
let out = s.execute(cmd, &change).unwrap();
|
||||
assert_eq!(style_at(&s, id, 1).font_family, from, "{cmd}: the character keeps its font");
|
||||
assert_eq!(out.get("warning"), None, "{cmd}: {out}");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- #394, fourth batch ----------
|
||||
|
||||
/// Type › Type Object Selection by Path Only (#394): on, a click among point type's glyphs no
|
||||
|
||||
Reference in New Issue
Block a user