diff --git a/flat/src/lib.rs b/flat/src/lib.rs
index 81c6f21..1494c91 100644
--- a/flat/src/lib.rs
+++ b/flat/src/lib.rs
@@ -170,7 +170,11 @@ fn hero<'a>(si: usize, f: usize) -> Svgr<'a> {
// The newest glyph arrives overexposed and settles.
let fresh = n - 1 - i < 2 && f <= hud::TYPE_END && s.kind == Kind::Typing;
let fill = if fresh { "#ffffff" } else { "#e9e9e9" };
- fframes::svgr!({c.to_string()})
+ if hud::is_wide(c) {
+ fframes::svgr!({c.to_string()})
+ } else {
+ fframes::svgr!({c.to_string()})
+ }
})
.collect();
let col = if s.kind == Kind::Cursor { 2 } else { n };
diff --git a/hud/src/lib.rs b/hud/src/lib.rs
index c50bb77..696a817 100644
--- a/hud/src/lib.rs
+++ b/hud/src/lib.rs
@@ -21,6 +21,17 @@ const CHAIN: usize = 5;
/// JetBrains Mono advances 0.6 em per character.
pub const MONO_ADV: f32 = 0.6;
+/// Chinese/Japanese/Korean and full-width characters take a full em.
+pub const WIDE_ADV: f32 = 1.0;
+
+/// True for characters that need the CJK font and a full-width cell.
+pub fn is_wide(c: char) -> bool {
+ matches!(c as u32, 0x1100..=0x115F | 0x2E80..=0xA4CF | 0xAC00..=0xD7A3 | 0xF900..=0xFAFF | 0xFE30..=0xFE4F | 0xFF00..=0xFF60 | 0xFFE0..=0xFFE6)
+}
+
+fn adv_of(c: Option) -> f32 {
+ match c { Some(c) if is_wide(c) => WIDE_ADV, _ => MONO_ADV }
+}
pub const LOG_SIZE: f32 = 26.0;
pub const LOG_LINE: f32 = 46.0;
pub const LOG_X: f32 = 150.0;
@@ -292,11 +303,12 @@ pub fn visible(si: usize, f: usize) -> usize {
/// Cell of character `i` of the hero line: x, y (top), w, h.
pub fn cell(si: usize, i: usize) -> (f32, f32, f32, f32) {
let s = scene(si);
- let adv = s.size * MONO_ADV;
+ let chars: Vec = s.text.chars().collect();
+ let before: f32 = (0..i).map(|k| adv_of(chars.get(k).copied())).sum();
(
- s.hero.0 + i as f32 * adv,
+ s.hero.0 + before * s.size,
s.hero.1 - s.size * 0.78,
- adv,
+ adv_of(chars.get(i).copied()) * s.size,
s.size * 0.98,
)
}