mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 03:41:28 +00:00
Type: the font warning leaves out characters Missing Glyph Protection kept
text.setStyle and text.setRangeStyle collected the fonts for their warning after Missing Glyph Protection had run. A character the new font has no glyph for keeps its earlier font, and when that font was not available the warning named it, although the command did not set it. protect_missing_glyphs now returns the byte ranges that kept their font, and both commands leave those ranges out of the warning's fonts.
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