Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
parent
aaaf68c6c7
commit
e9da654903
|
|
@ -37,6 +37,8 @@ pub struct CanvasState {
|
|||
pub focused: bool,
|
||||
pub render_ms: f64,
|
||||
pub ime_preedit: String,
|
||||
/// Document faces added to the interface fonts to draw the input method's composition (#565).
|
||||
pub(crate) ime_faces: Vec<std::sync::Arc<wordcraft_fonts::FontFace>>,
|
||||
pub want_focus: bool,
|
||||
pub context_issue: Option<serde_json::Value>,
|
||||
pub context_synonyms: Option<serde_json::Value>,
|
||||
|
|
@ -109,6 +111,7 @@ impl Default for CanvasState {
|
|||
focused: false,
|
||||
render_ms: 0.0,
|
||||
ime_preedit: String::new(),
|
||||
ime_faces: Vec::new(),
|
||||
want_focus: true,
|
||||
context_issue: None,
|
||||
context_synonyms: None,
|
||||
|
|
@ -676,6 +679,19 @@ pub fn show(app: &mut WordApp, ui: &mut Ui) {
|
|||
});
|
||||
}
|
||||
if !app.canvas.ime_preedit.is_empty() {
|
||||
// The composition is interface text, but it may be in a script the interface fonts lack
|
||||
// (Bangla, Thai ...): borrow the document fonts' fallback faces, or it draws as boxes (#565).
|
||||
let new = crate::theme::faces_for_preedit(
|
||||
&app.canvas.ime_preedit,
|
||||
&app.canvas.ime_faces,
|
||||
|c| (c as u32) < 0x250,
|
||||
|c| wordcraft_fonts::FontDb::global().fallback_for(c, u32::MAX),
|
||||
);
|
||||
if !new.is_empty() {
|
||||
app.canvas.ime_faces.extend(new);
|
||||
crate::theme::install_fonts_extra(ui.ctx(), app.fonts_hans, app.fonts_system_cjk, &app.canvas.ime_faces);
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
let g = painter.text(pos2(x, y1), egui::Align2::LEFT_BOTTOM, &app.canvas.ime_preedit, regular(c.height * scale * 0.8), caret_color);
|
||||
painter.line_segment([pos2(g.min.x, g.max.y), pos2(g.max.x, g.max.y)], Stroke::new(1.0, caret_color));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -945,7 +945,7 @@ impl WordApp {
|
|||
// interface that doesn't open the language list.
|
||||
let system_cjk = self.fonts_system_cjk || self.want_system_cjk || lang.uses_cjk();
|
||||
if !self.styled || lang.prefers_hans() != self.fonts_hans || system_cjk != self.fonts_system_cjk {
|
||||
theme::install_fonts_with(ctx, lang.prefers_hans(), system_cjk);
|
||||
theme::install_fonts_extra(ctx, lang.prefers_hans(), system_cjk, &self.canvas.ime_faces);
|
||||
self.fonts_hans = lang.prefers_hans();
|
||||
self.fonts_system_cjk = system_cjk;
|
||||
// Mod with -, = and 0 are Word shortcuts (optional hyphen, subscript, paragraph spacing);
|
||||
|
|
|
|||
|
|
@ -275,7 +275,52 @@ pub fn install_fonts_for(ctx: &egui::Context, prefer_hans: bool) {
|
|||
/// a glyph (#241, #486). Reading that font is a large file read, so callers ask for it only when
|
||||
/// CJK text is on screen: a CJK interface language, or the language names in Options.
|
||||
pub fn install_fonts_with(ctx: &egui::Context, prefer_hans: bool, system_cjk: bool) {
|
||||
ctx.set_fonts(font_definitions(prefer_hans, system_cjk));
|
||||
install_fonts_extra(ctx, prefer_hans, system_cjk, &[]);
|
||||
}
|
||||
|
||||
/// [`install_fonts_with`], also adding document-font `faces` as last-resort fallbacks (#565): the
|
||||
/// faces that cover the text an input method is composing.
|
||||
pub fn install_fonts_extra(ctx: &egui::Context, prefer_hans: bool, system_cjk: bool, faces: &[Arc<wordcraft_fonts::FontFace>]) {
|
||||
let mut fonts = font_definitions(prefer_hans, system_cjk);
|
||||
add_fallback_faces(&mut fonts, faces);
|
||||
ctx.set_fonts(fonts);
|
||||
}
|
||||
|
||||
/// Add `faces` after every other font of the interface families.
|
||||
pub fn add_fallback_faces(fonts: &mut FontDefinitions, faces: &[Arc<wordcraft_fonts::FontFace>]) {
|
||||
for f in faces {
|
||||
let name = format!("fallback {} {} {}", f.family, f.style, f.id());
|
||||
let mut data = FontData::from_owned(f.data().to_vec());
|
||||
data.index = f.index();
|
||||
fonts.font_data.insert(name.clone(), Arc::new(data));
|
||||
for fam in [FontFamily::Proportional, FontFamily::Name("medium".into()), FontFamily::Name("semibold".into())] {
|
||||
if let Some(v) = fonts.families.get_mut(&fam) {
|
||||
v.push(name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Document faces needed to draw `text` (the input method's composition), one for each character
|
||||
/// the interface fonts cannot draw, skipping faces in `have` (#565). Characters of the embedded
|
||||
/// interface fonts are not looked up; very large faces (system CJK collections) are left to the CJK
|
||||
/// fallback.
|
||||
pub fn faces_for_preedit(
|
||||
text: &str,
|
||||
have: &[Arc<wordcraft_fonts::FontFace>],
|
||||
covered: impl Fn(char) -> bool,
|
||||
lookup: impl Fn(char) -> Option<Arc<wordcraft_fonts::FontFace>>,
|
||||
) -> Vec<Arc<wordcraft_fonts::FontFace>> {
|
||||
let mut out: Vec<Arc<wordcraft_fonts::FontFace>> = Vec::new();
|
||||
for c in text.chars().filter(|c| !c.is_ascii() && !c.is_whitespace() && !covered(*c)) {
|
||||
if have.iter().chain(&out).any(|f| f.covers(c)) {
|
||||
continue;
|
||||
}
|
||||
if let Some(f) = lookup(c).filter(|f| f.data().len() <= 16 << 20 && !have.iter().chain(&out).any(|h| h.id() == f.id())) {
|
||||
out.push(f);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The interface fonts; see [`install_fonts_with`].
|
||||
|
|
@ -510,6 +555,26 @@ pub fn c32(c: wordcraft_doc::Rgb) -> Color32 {
|
|||
mod tests {
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
/// #565: the composition of an input method (Bangla through ibus) drew as boxes because only the
|
||||
/// interface fonts were used. Faces found for the uncovered characters are added as fallbacks,
|
||||
/// once, and not for characters the interface fonts draw.
|
||||
#[test]
|
||||
fn preedit_gets_document_fallback_faces() {
|
||||
let face = wordcraft_fonts::FontDb::global().face("Source Sans 3", "Regular");
|
||||
let lookup = |_: char| Some(face.clone());
|
||||
let bangla = "\u{0986}\u{09AE}\u{09BF}";
|
||||
let got = super::faces_for_preedit(bangla, &[], |_| false, lookup);
|
||||
assert_eq!(got.len(), 1, "one face, not one per character");
|
||||
assert!(super::faces_for_preedit(bangla, &got, |_| false, lookup).is_empty(), "already added");
|
||||
assert!(super::faces_for_preedit(bangla, &[], |_| true, lookup).is_empty(), "the interface fonts draw it");
|
||||
assert!(super::faces_for_preedit("abc", &[], |_| false, lookup).is_empty(), "ASCII never needs one");
|
||||
let mut defs = super::font_definitions(false, false);
|
||||
let before = defs.font_data.len();
|
||||
super::add_fallback_faces(&mut defs, &got);
|
||||
assert_eq!(defs.font_data.len(), before + 1);
|
||||
assert!(defs.families[&egui::FontFamily::Proportional].last().is_some_and(|n| n.starts_with("fallback ")));
|
||||
}
|
||||
|
||||
/// Symbols written as text in the interface code must exist in the interface fonts (Inter and
|
||||
/// egui's emoji fallbacks); a missing glyph draws as an empty box, as the panes' "✕" close
|
||||
/// button once did. Letters are the catalogs' business (see the i18n font tests).
|
||||
|
|
|
|||
Loading…
Reference in New Issue