Skip to content

Commit 5d7b2f1

Browse files
committed
add wrap_text and an example
1 parent 2f7f3c8 commit 5d7b2f1

File tree

3 files changed

+106
-3
lines changed

3 files changed

+106
-3
lines changed

examples/text_wrap.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use macroquad::prelude::*;
2+
3+
static LOREM: &str = "Lorem ipsum odor amet, consectetuer adipiscing elit. Ultrices nostra volutpat facilisis magna mus. Rhoncus tempor feugiat netus maecenas pretium leo vitae. Eros aliquet maecenas eu diam aliquet varius hac elementum. Sociosqu platea per ultricies vitae praesent mauris nostra ridiculus. Est cursus pulvinar efficitur mus vel leo. Integer et nec eleifend non leo. Lorem rutrum ultrices potenti facilisis hendrerit facilisi metus sit. AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
4+
5+
Intentional newlines
6+
are preserved.
7+
";
8+
9+
#[macroquad::main("Text Wrap")]
10+
async fn main() {
11+
let font_size = 24;
12+
loop {
13+
clear_background(BLACK);
14+
15+
let maximum_line_length = f32::max(20.0, mouse_position().0 - 20.0);
16+
let text = wrap_text(LOREM, None, font_size, 1.0, maximum_line_length);
17+
18+
draw_multiline_text(&text, 20.0, 20.0, font_size as f32, Some(1.0), WHITE);
19+
draw_line(
20+
20.0 + maximum_line_length,
21+
0.0,
22+
20.0 + maximum_line_length,
23+
screen_height(),
24+
1.0,
25+
RED,
26+
);
27+
28+
next_frame().await
29+
}
30+
}

src/text.rs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ impl Font {
158158
font_size: u16,
159159
font_scale_x: f32,
160160
font_scale_y: f32,
161+
mut glyph_callback: impl FnMut(f32),
161162
) -> TextDimensions {
162163
let dpi_scaling = miniquad::window::dpi_scale();
163164
let font_size = (font_size as f32 * dpi_scaling).ceil() as u16;
@@ -176,7 +177,9 @@ impl Font {
176177

177178
let atlas = self.atlas.lock().unwrap();
178179
let glyph = atlas.get(font_data.sprite).unwrap().rect;
179-
width += font_data.advance * font_scale_x;
180+
let advance = font_data.advance * font_scale_x;
181+
glyph_callback(advance);
182+
width += advance;
180183
min_y = min_y.min(offset_y);
181184
max_y = max_y.max(glyph.h * font_scale_y + offset_y);
182185
}
@@ -462,7 +465,77 @@ pub fn measure_text(
462465
) -> TextDimensions {
463466
let font = font.unwrap_or_else(|| &get_context().fonts_storage.default_font);
464467

465-
font.measure_text(text, font_size, font_scale, font_scale)
468+
font.measure_text(text, font_size, font_scale, font_scale, |_| {})
469+
}
470+
471+
/// Converts word breaks to newlines wherever the text would otherwise exceed the given length.
472+
pub fn wrap_text(
473+
text: &str,
474+
font: Option<&Font>,
475+
font_size: u16,
476+
font_scale: f32,
477+
maximum_line_length: f32,
478+
) -> String {
479+
let font = font.unwrap_or_else(|| &get_context().fonts_storage.default_font);
480+
481+
// This is always a bit too much memory, but it saves a lot of reallocations.
482+
let mut new_text =
483+
String::with_capacity(text.len() + text.chars().filter(|c| c.is_whitespace()).count());
484+
485+
let mut current_word = String::with_capacity(64); // sufficiently large
486+
let mut characters = text.chars();
487+
let mut total_width = 0.0;
488+
let mut word_width = 0.0;
489+
490+
font.measure_text(text, font_size, font_scale, font_scale, |mut width| {
491+
// It's impossible this is called more often than the text has characters.
492+
let c = characters.next().unwrap();
493+
let mut keep_char = true;
494+
495+
if c.is_whitespace() {
496+
new_text.push_str(&current_word);
497+
new_text.push(c);
498+
current_word.clear();
499+
word_width = 0.0;
500+
keep_char = false;
501+
502+
// If we would wrap, ignore the whitespace.
503+
if total_width + width > maximum_line_length {
504+
width = 0.0;
505+
}
506+
}
507+
508+
// If a single word expands past the length limit, just break it up.
509+
if word_width + width > maximum_line_length {
510+
new_text.push_str(&current_word);
511+
new_text.push('\n');
512+
current_word.clear();
513+
total_width = 0.0;
514+
word_width = 0.0;
515+
}
516+
517+
if keep_char {
518+
current_word.push(c);
519+
word_width += width;
520+
}
521+
522+
if c == '\n' {
523+
total_width = 0.0;
524+
word_width = 0.0;
525+
return;
526+
}
527+
528+
total_width += width;
529+
530+
if total_width > maximum_line_length {
531+
new_text.push('\n');
532+
total_width = word_width;
533+
}
534+
});
535+
536+
new_text.push_str(&current_word);
537+
538+
new_text
466539
}
467540

468541
pub(crate) struct FontsStorage {

src/ui/render/painter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ impl Painter {
311311
font: &mut Font,
312312
font_size: u16,
313313
) -> TextDimensions {
314-
font.measure_text(label, font_size, 1.0, 1.0)
314+
font.measure_text(label, font_size, 1.0, 1.0, |_| {})
315315
}
316316

317317
/// If character is in font atlas - will return x advance from position to potential next character position

0 commit comments

Comments
 (0)