diff --git a/README.md b/README.md index d029b57..fef3ec4 100644 --- a/README.md +++ b/README.md @@ -57,4 +57,6 @@ This project is licensed under the MIT license ([LICENSE] or - + + + diff --git a/src/mouse.rs b/src/mouse.rs index cd15ea0..32e04ca 100644 --- a/src/mouse.rs +++ b/src/mouse.rs @@ -84,11 +84,7 @@ fn measure_grid() -> Option { .flatten() .or_else(|| grid.first_element_child())?; let pre_rect = pre.get_bounding_client_rect(); - let span = pre.query_selector("span").ok().flatten(); - let cell_w = span - .map(|el| el.get_bounding_client_rect().width() as f32) - .filter(|w| *w > 1.0) - .unwrap_or(FALLBACK_CELL_WIDTH_PX); + let cell_w = ascii_cell_width(&pre).unwrap_or(FALLBACK_CELL_WIDTH_PX); let cell_h = if pre_rect.height() > 1.0 { pre_rect.height() as f32 } else { @@ -102,6 +98,21 @@ fn measure_grid() -> Option { }) } +fn ascii_cell_width(pre: &web_sys::Element) -> Option { + let mut child = pre.first_element_child(); + while let Some(el) = child { + let text = el.text_content().unwrap_or_default(); + if display_width(&text) == 1 { + let width = el.get_bounding_client_rect().width() as f32; + if width > 1.0 { + return Some(width); + } + } + child = el.next_element_sibling(); + } + None +} + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct MouseState { pos: (u32, u32), diff --git a/src/ui/blog.rs b/src/ui/blog.rs index 2e18715..9d9ba77 100644 --- a/src/ui/blog.rs +++ b/src/ui/blog.rs @@ -486,19 +486,26 @@ fn copy_hit_span(area: Rect) -> Option { /// Split `Title - date` so the date is shown as `(date)` on the right. fn format_post_label(title: &str, width: u16) -> String { + let width = width as usize; + if width == 0 { + return String::new(); + } let Some((name, date)) = content::split_trailing_date(title) else { - return title.to_string(); + return truncate_display(title, width); }; let right = format!("({date})"); - let name_w = display_width(name); let right_w = display_width(&right); - let width = width as usize; - if name_w + 1 + right_w >= width { - format!("{name} {right}") - } else { - let pad = width - name_w - right_w; - format!("{name}{:pad$}{right}", "") + if right_w >= width { + return truncate_display(&right, width); } + let name_budget = width.saturating_sub(right_w + 1); + let name_part = if display_width(name) <= name_budget { + name.to_string() + } else { + truncate_display(name, name_budget) + }; + let pad = width.saturating_sub(display_width(&name_part) + right_w); + format!("{name_part}{:pad$}{right}", "") } #[cfg(test)] @@ -532,16 +539,38 @@ mod tests { let label = format_post_label("TEST-contents - 2025.12.12", 40); assert!(label.starts_with("TEST-contents")); assert!(label.ends_with("(2025.12.12)")); - assert_eq!(label.chars().count(), 40); + assert_eq!(crate::width::display_width(&label), 40); assert!(!label.contains(" - ")); } + #[test] + fn korean_label_fits_display_width() { + let label = format_post_label("한글제목 - 2025.12.12", 24); + assert!(label.ends_with("(2025.12.12)")); + assert_eq!(crate::width::display_width(&label), 24); + } + + #[test] + fn korean_label_truncates_long_titles() { + let label = format_post_label("한글제목이아주깁니다 - 2025.12.12", 18); + assert!(label.ends_with("(2025.12.12)")); + assert_eq!(crate::width::display_width(&label), 18); + assert!(label.contains('…')); + } + #[test] fn text_height_includes_wrap() { let segment = PostSegment::Text("abcdefghij".into()); assert_eq!(segment_height(&segment, 5), 2); } + #[test] + fn korean_text_height_counts_double_width() { + let segment = PostSegment::Text("한글한글".into()); + assert_eq!(segment_height(&segment, 4), 2); + assert_eq!(segment_height(&segment, 8), 1); + } + #[test] fn image_height_uses_aspect_and_border() { let image = PostImage { diff --git a/src/width.rs b/src/width.rs index 043b030..b00f56b 100644 --- a/src/width.rs +++ b/src/width.rs @@ -80,4 +80,14 @@ mod tests { assert_eq!(wrapped_rows("ab\ncd", 10), 2); assert_eq!(wrapped_rows("", 10), 1); } + + #[test] + fn wrap_counts_hangul_as_two_columns() { + assert_eq!(display_width("한글"), 4); + assert_eq!(super::char_width('한'), 2); + assert_eq!(super::char_width('ㄱ'), 2); + assert_eq!(wrapped_rows("한글", 2), 2); + assert_eq!(wrapped_rows("한글한글", 4), 2); + assert_eq!(wrapped_rows("한글", 4), 1); + } }