Skip to main content

yog_ui/
text.rs

1//! Text wrapping utilities shared by layout (measuring) and render (drawing).
2
3/// Approximate pixel width per character at font_scale=1.0 in Minecraft's default font.
4pub const CHAR_W: f32 = 6.0;
5/// Line height at font_scale=1.0.
6pub const LINE_H: f32 = 10.0;
7/// Gap between wrapped lines.
8pub const LINE_GAP: f32 = 2.0;
9
10/// Break `text` into lines that fit within `max_w` pixels at `font_scale`.
11/// Words that are longer than one line get hard-broken at the character boundary.
12pub fn wrap_text(text: &str, max_w: f32, font_scale: f32) -> Vec<String> {
13    let char_w = CHAR_W * font_scale;
14    if char_w <= 0.0 || max_w <= 0.0 {
15        return vec![text.to_owned()];
16    }
17    let max_chars = ((max_w / char_w).floor() as usize).max(1);
18    let mut lines: Vec<String> = Vec::new();
19    let mut cur = String::new();
20
21    for word in text.split(' ') {
22        if word.is_empty() { continue; }
23        if cur.is_empty() {
24            // First word on a line — may still need hard-breaking.
25            let mut w = word;
26            while w.len() > max_chars {
27                lines.push(w[..max_chars].to_owned());
28                w = &w[max_chars..];
29            }
30            cur.push_str(w);
31        } else if cur.len() + 1 + word.len() <= max_chars {
32            cur.push(' ');
33            cur.push_str(word);
34        } else {
35            lines.push(std::mem::take(&mut cur));
36            let mut w = word;
37            while w.len() > max_chars {
38                lines.push(w[..max_chars].to_owned());
39                w = &w[max_chars..];
40            }
41            cur.push_str(w);
42        }
43    }
44    if !cur.is_empty() || lines.is_empty() {
45        lines.push(cur);
46    }
47    lines
48}
49
50/// Number of lines that `text` wraps to, at the given scale in `max_w` pixels.
51pub fn line_count(text: &str, max_w: f32, font_scale: f32) -> usize {
52    wrap_text(text, max_w, font_scale).len().max(1)
53}
54
55/// Total height (px) of wrapped `text`.
56pub fn text_height(text: &str, max_w: f32, font_scale: f32) -> f32 {
57    let n = line_count(text, max_w, font_scale);
58    n as f32 * LINE_H * font_scale + (n.saturating_sub(1)) as f32 * LINE_GAP
59}