1use azul_css::{LayoutSize, LayoutRect, LayoutPoint};
5pub use azul_core::{
6 app_resources::{
7 Words, Word, WordType, GlyphInfo, GlyphPosition,
8 ScaledWords, ScaledWord, WordIndex, GlyphIndex, LineLength, IndexOfLineBreak,
9 RemainingSpaceToRight, LineBreaks, WordPositions, LayoutedGlyphs,
10 ClusterIterator, ClusterInfo, FontMetrics,
11 },
12 display_list::GlyphInstance,
13 ui_solver::{
14 ResolvedTextLayoutOptions, TextLayoutOptions, InlineTextLayout,
15 DEFAULT_LINE_HEIGHT, DEFAULT_WORD_SPACING, DEFAULT_LETTER_SPACING, DEFAULT_TAB_WIDTH,
16 },
17};
18
19#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
22pub enum TextOverflow {
23 IsOverflowing(f32),
25 InBounds(f32),
27}
28
29pub fn split_text_into_words(text: &str) -> Words {
31
32 use unicode_normalization::UnicodeNormalization;
33
34 let normalized_string = text.nfc().collect::<String>();
37 let normalized_chars = normalized_string.chars().collect::<Vec<char>>();
38
39 let mut words = Vec::new();
40
41 let mut current_word_start = 0;
45 let mut last_char_idx = 0;
46 let mut last_char_was_whitespace = false;
47
48 for (ch_idx, ch) in normalized_chars.iter().enumerate() {
49
50 let ch = *ch;
51 let current_char_is_whitespace = ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
52
53 let should_push_delimiter = match ch {
54 ' ' => {
55 Some(Word {
56 start: last_char_idx + 1,
57 end: ch_idx + 1,
58 word_type: WordType::Space
59 })
60 },
61 '\t' => {
62 Some(Word {
63 start: last_char_idx + 1,
64 end: ch_idx + 1,
65 word_type: WordType::Tab
66 })
67 },
68 '\n' => {
69 Some(if normalized_chars[last_char_idx] == '\r' {
70 Word {
72 start: last_char_idx,
73 end: ch_idx + 1,
74 word_type: WordType::Return,
75 }
76 } else {
77 Word {
79 start: last_char_idx + 1,
80 end: ch_idx + 1,
81 word_type: WordType::Return,
82 }
83 })
84 },
85 _ => None,
86 };
87
88 let should_push_word = if current_char_is_whitespace && !last_char_was_whitespace {
90 Some(Word {
91 start: current_word_start,
92 end: ch_idx,
93 word_type: WordType::Word
94 })
95 } else {
96 None
97 };
98
99 if current_char_is_whitespace {
100 current_word_start = ch_idx + 1;
101 }
102
103 let mut push_words = |arr: [Option<Word>;2]| {
104 words.extend(arr.iter().filter_map(|e| *e));
105 };
106
107 push_words([should_push_word, should_push_delimiter]);
108
109 last_char_was_whitespace = current_char_is_whitespace;
110 last_char_idx = ch_idx;
111 }
112
113 if current_word_start != last_char_idx + 1 {
115 words.push(Word {
116 start: current_word_start,
117 end: normalized_chars.len(),
118 word_type: WordType::Word
119 });
120 }
121
122 if let Some(Word { word_type: WordType::Return, .. }) = words.last() {
124 words.pop();
125 }
126
127 Words {
128 items: words,
129 internal_str: normalized_string,
130 internal_chars: normalized_chars,
131 }
132}
133
134pub fn words_to_scaled_words(
137 words: &Words,
138 font_bytes: &[u8],
139 font_index: u32,
140 font_metrics: FontMetrics,
141 font_size_px: f32,
142) -> ScaledWords {
143
144 use std::mem;
145 use std::char;
146 use crate::text_shaping::{self, HB_SCALE_FACTOR, HbBuffer, HbFont, HbScaledFont};
147
148 let hb_font = HbFont::from_bytes(font_bytes, font_index);
149 let hb_scaled_font = HbScaledFont::from_font(&hb_font, font_size_px);
150
151 let hb_space_buffer = HbBuffer::from_str(" ");
153 let hb_shaped_space = text_shaping::shape_word_hb(&hb_space_buffer, &hb_scaled_font);
154 let space_advance_px = hb_shaped_space.glyph_positions[0].x_advance as f32 / HB_SCALE_FACTOR;
155 let space_codepoint = hb_shaped_space.glyph_infos[0].codepoint;
156
157 let internal_str = words.internal_str.replace(char::is_whitespace, " ");
158
159 let hb_buffer_entire_paragraph = HbBuffer::from_str(&internal_str);
160 let hb_shaped_entire_paragraph = text_shaping::shape_word_hb(&hb_buffer_entire_paragraph, &hb_scaled_font);
161
162 let mut shaped_word_positions = Vec::<Vec<GlyphPosition>>::new();
163 let mut shaped_word_infos = Vec::<Vec<GlyphInfo>>::new();
164 let mut current_word_positions = Vec::new();
165 let mut current_word_infos = Vec::new();
166
167 for i in 0..hb_shaped_entire_paragraph.glyph_positions.len() {
168 let glyph_info = hb_shaped_entire_paragraph.glyph_infos[i];
169 let glyph_position = hb_shaped_entire_paragraph.glyph_positions[i];
170
171 let is_space = glyph_info.codepoint == space_codepoint;
172 if is_space {
173 shaped_word_positions.push(current_word_positions.clone());
174 shaped_word_infos.push(current_word_infos.clone());
175 current_word_positions.clear();
176 current_word_infos.clear();
177 } else {
178 current_word_positions.push(unsafe { mem::transmute(glyph_position) });
181 current_word_infos.push(unsafe { mem::transmute(glyph_info) });
182 }
183 }
184
185 if !current_word_positions.is_empty() {
186 shaped_word_positions.push(current_word_positions);
187 shaped_word_infos.push(current_word_infos);
188 }
189
190 let mut longest_word_width = 0.0_f32;
191
192 let scaled_words = words.items.iter()
193 .filter(|w| w.word_type == WordType::Word)
194 .enumerate()
195 .filter_map(|(word_idx, _)| {
196
197 let hb_glyph_positions = shaped_word_positions.get(word_idx)?.clone();
198 let hb_glyph_infos = shaped_word_infos.get(word_idx)?.clone();
199 let hb_word_width = text_shaping::get_word_visual_width_hb(&hb_glyph_positions);
200
201 longest_word_width = longest_word_width.max(hb_word_width.abs());
202
203 Some(ScaledWord {
204 glyph_infos: hb_glyph_infos,
205 glyph_positions: hb_glyph_positions,
206 word_width: hb_word_width,
207 })
208 }).collect();
209
210 ScaledWords {
211 font_size_px,
212 font_metrics,
213 baseline_px: font_size_px, items: scaled_words,
215 longest_word_width: longest_word_width,
216 space_advance_px,
217 space_codepoint,
218 }
219}
220
221pub fn position_words(
224 words: &Words,
225 scaled_words: &ScaledWords,
226 text_layout_options: &ResolvedTextLayoutOptions,
227) -> WordPositions {
228
229 use self::WordType::*;
230 use std::f32;
231
232 let font_size_px = text_layout_options.font_size_px;
233 let space_advance = scaled_words.space_advance_px;
234 let word_spacing_px = space_advance * text_layout_options.word_spacing.unwrap_or(DEFAULT_WORD_SPACING);
235 let line_height_px = space_advance * text_layout_options.line_height.unwrap_or(DEFAULT_LINE_HEIGHT);
236 let tab_width_px = space_advance * text_layout_options.tab_width.unwrap_or(DEFAULT_TAB_WIDTH);
237
238 let mut line_breaks = Vec::new();
239 let mut word_positions = Vec::new();
240
241 let mut line_number = 0;
242 let mut line_caret_x = 0.0;
243 let mut current_word_idx = 0;
244
245 macro_rules! advance_caret {($line_caret_x:expr) => ({
246 let caret_intersection = caret_intersects_with_holes(
247 $line_caret_x,
248 line_number,
249 font_size_px,
250 line_height_px,
251 &text_layout_options.holes[..],
252 text_layout_options.max_horizontal_width,
253 );
254
255 if let LineCaretIntersection::PushCaretOntoNextLine(_, _) = caret_intersection {
256 line_breaks.push((current_word_idx, line_caret_x));
257 }
258
259 advance_caret(
261 &mut $line_caret_x,
262 &mut line_number,
263 caret_intersection,
264 );
265 })}
266
267 advance_caret!(line_caret_x);
268
269 if let Some(leading) = text_layout_options.leading {
270 line_caret_x += leading;
271 advance_caret!(line_caret_x);
272 }
273
274 let mut word_idx = 0;
276
277 macro_rules! handle_word {() => ({
278
279 let scaled_word = match scaled_words.items.get(word_idx) {
280 Some(s) => s,
281 None => continue,
282 };
283
284 let reserved_letter_spacing_px = match text_layout_options.letter_spacing {
285 None => 0.0,
286 Some(spacing_multiplier) => spacing_multiplier * scaled_word.number_of_clusters().saturating_sub(1) as f32,
287 };
288
289 let word_advance_x = scaled_word.word_width + reserved_letter_spacing_px;
291
292 let mut new_caret_x = line_caret_x + word_advance_x;
293
294 let caret_intersection = caret_intersects_with_holes(
297 new_caret_x,
298 line_number,
299 font_size_px,
300 line_height_px,
301 &text_layout_options.holes,
302 text_layout_options.max_horizontal_width,
303 );
304
305 let mut is_line_break = false;
306 if let LineCaretIntersection::PushCaretOntoNextLine(_, _) = caret_intersection {
307 line_breaks.push((current_word_idx, line_caret_x));
308 is_line_break = true;
309 }
310
311 if !is_line_break {
312 let line_caret_y = get_line_y_position(line_number, font_size_px, line_height_px);
313 word_positions.push(LayoutPoint::new(line_caret_x, line_caret_y));
314 }
315
316 advance_caret(
318 &mut new_caret_x,
319 &mut line_number,
320 caret_intersection,
321 );
322
323 line_caret_x = new_caret_x;
324
325 if is_line_break {
327 let line_caret_y = get_line_y_position(line_number, font_size_px, line_height_px);
328 word_positions.push(LayoutPoint::new(line_caret_x, line_caret_y));
329 line_caret_x += word_advance_x;
332 }
333
334 word_idx += 1;
337 current_word_idx = word_idx;
338 })}
339
340 for word in words.items.iter().take(words.items.len().saturating_sub(1)) {
342 match word.word_type {
343 Word => {
344 handle_word!();
345 },
346 Return => {
347 line_breaks.push((current_word_idx, line_caret_x));
348 line_number += 1;
349 let mut new_caret_x = 0.0;
350 advance_caret!(new_caret_x);
351 line_caret_x = new_caret_x;
352 },
353 Space => {
354 let mut new_caret_x = line_caret_x + word_spacing_px;
355 advance_caret!(new_caret_x);
356 line_caret_x = new_caret_x;
357 },
358 Tab => {
359 let mut new_caret_x = line_caret_x + word_spacing_px + tab_width_px;
360 advance_caret!(new_caret_x);
361 line_caret_x = new_caret_x;
362 },
363 }
364 }
365
366 for word in &words.items[words.items.len().saturating_sub(1)..] {
368 if word.word_type == Word {
369 handle_word!();
370 }
371 line_breaks.push((current_word_idx, line_caret_x));
372 }
373
374 let trailing = line_caret_x;
375 let number_of_lines = line_number + 1;
376 let number_of_words = current_word_idx + 1;
377
378 let longest_line_width = line_breaks.iter().map(|(_word_idx, line_length)| *line_length).fold(0.0_f32, f32::max);
379 let content_size_y = get_line_y_position(line_number, font_size_px, line_height_px);
380 let content_size_x = text_layout_options.max_horizontal_width.unwrap_or(longest_line_width);
381 let content_size = LayoutSize::new(content_size_x, content_size_y);
382
383 WordPositions {
384 text_layout_options: text_layout_options.clone(),
385 trailing,
386 number_of_words,
387 number_of_lines,
388 content_size,
389 word_positions,
390 line_breaks,
391 }
392}
393
394pub fn word_positions_to_inline_text_layout(
396 word_positions: &WordPositions,
397 scaled_words: &ScaledWords
398) -> InlineTextLayout {
399
400 use azul_core::ui_solver::InlineTextLine;
401
402 let font_size_px = word_positions.text_layout_options.font_size_px;
403 let regular_line_height = scaled_words.font_metrics.get_height(font_size_px);
404 let space_advance = scaled_words.space_advance_px;
405 let line_height_px = space_advance * word_positions.text_layout_options.line_height.unwrap_or(DEFAULT_LINE_HEIGHT);
406
407 let mut last_word_index = 0;
408
409 InlineTextLayout {
410 lines: word_positions.line_breaks
411 .iter()
412 .enumerate()
413 .map(|(line_number, (word_idx, line_length))| {
414 let start_word_idx = last_word_index;
415 let line = InlineTextLine {
416 bounds: LayoutRect {
417 origin: LayoutPoint { x: 0.0, y: get_line_y_position(line_number, regular_line_height, line_height_px) },
418 size: LayoutSize { width: *line_length, height: regular_line_height },
419 },
420 word_start: start_word_idx,
421 word_end: *word_idx,
422 };
423 last_word_index = *word_idx;
424 line
425 }).collect(),
426 }
427}
428
429pub fn get_layouted_glyphs(
430 word_positions: &WordPositions,
431 scaled_words: &ScaledWords,
432 inline_text_layout: &InlineTextLayout,
433) -> LayoutedGlyphs {
434
435 use crate::text_shaping;
436
437 let letter_spacing_px = word_positions.text_layout_options.letter_spacing.unwrap_or(0.0);
438 let mut all_glyphs = Vec::with_capacity(scaled_words.items.len());
439 let baseline_px = scaled_words.font_metrics.get_ascender(scaled_words.font_size_px);
440
441 for line in inline_text_layout.lines.iter() {
442
443 let line_x = line.bounds.origin.x;
444 let line_y = line.bounds.origin.y - (line.bounds.size.height - baseline_px); let scaled_words_in_this_line = &scaled_words.items[line.word_start..line.word_end];
447 let word_positions_in_this_line = &word_positions.word_positions[line.word_start..line.word_end];
448
449 for (scaled_word, word_position) in scaled_words_in_this_line.iter().zip(word_positions_in_this_line.iter()) {
450 let mut glyphs = text_shaping::get_glyph_instances_hb(&scaled_word.glyph_infos, &scaled_word.glyph_positions);
451 for (glyph, cluster_info) in glyphs.iter_mut().zip(scaled_word.cluster_iter()) {
452 glyph.point.x += line_x + word_position.x + (letter_spacing_px * cluster_info.cluster_idx as f32);
453 glyph.point.y += line_y;
454 }
455
456 all_glyphs.append(&mut glyphs);
457 }
458 }
459
460 LayoutedGlyphs { glyphs: all_glyphs }
461}
462
463pub fn word_item_is_return(item: &Word) -> bool {
464 item.word_type == WordType::Return
465}
466
467pub fn get_line_y_position(line_number: usize, font_size_px: f32, line_height_px: f32) -> f32 {
470 ((font_size_px + line_height_px) * line_number as f32) + font_size_px
471}
472
473#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
474pub enum LineCaretIntersection {
475 NoIntersection,
477 AdvanceCaretTo(f32),
480 PushCaretOntoNextLine(usize, f32),
483}
484
485pub fn caret_intersects_with_holes(
495 line_caret_x: f32,
496 line_number: usize,
497 font_size_px: f32,
498 line_height_px: f32,
499 holes: &[LayoutRect],
500 max_width: Option<f32>,
501) -> LineCaretIntersection {
502
503 let mut new_line_caret_x = None;
504 let mut line_advance = 0;
505
506 if let Some(max_width) = max_width {
508 if line_caret_x > max_width {
509 new_line_caret_x = Some(0.0);
510 line_advance += 1;
511 }
512 }
513
514 for hole in holes {
515
516 let mut should_move_caret = false;
517 let mut current_line_advance = 0;
518 let mut new_line_number = line_number + current_line_advance;
519 let mut current_caret = LayoutPoint::new(
520 new_line_caret_x.unwrap_or(line_caret_x),
521 get_line_y_position(new_line_number, font_size_px, line_height_px)
522 );
523
524 while hole.contains(¤t_caret) {
527 should_move_caret = true;
528 if let Some(max_width) = max_width {
529 if hole.origin.x + hole.size.width >= max_width {
530 current_line_advance += 1;
532 new_line_number = line_number + current_line_advance;
533 current_caret = LayoutPoint::new(
534 new_line_caret_x.unwrap_or(line_caret_x),
535 get_line_y_position(new_line_number, font_size_px, line_height_px)
536 );
537 } else {
538 new_line_number = line_number + current_line_advance;
539 current_caret = LayoutPoint::new(
540 hole.origin.x + hole.size.width,
541 get_line_y_position(new_line_number, font_size_px, line_height_px)
542 );
543 }
544 } else {
545 new_line_number = line_number + current_line_advance;
547 current_caret = LayoutPoint::new(
548 hole.origin.x + hole.size.width,
549 get_line_y_position(new_line_number, font_size_px, line_height_px)
550 );
551 }
552 }
553
554 if should_move_caret {
555 new_line_caret_x = Some(current_caret.x);
556 line_advance += current_line_advance;
557 }
558 }
559
560 if let Some(new_line_caret_x) = new_line_caret_x {
561 if line_advance == 0 {
562 LineCaretIntersection::AdvanceCaretTo(new_line_caret_x)
563 } else {
564 LineCaretIntersection::PushCaretOntoNextLine(line_advance, new_line_caret_x)
565 }
566 } else {
567 LineCaretIntersection::NoIntersection
568 }
569}
570
571pub fn advance_caret(caret: &mut f32, line_number: &mut usize, intersection: LineCaretIntersection) {
572 use self::LineCaretIntersection::*;
573 match intersection {
574 NoIntersection => { },
575 AdvanceCaretTo(x) => { *caret = x; },
576 PushCaretOntoNextLine(num_lines, x) => { *line_number += num_lines; *caret = x; },
577 }
578}
579
580#[test]
581fn test_split_words() {
582
583 fn print_words(w: &Words) {
584 println!("-- string: {:?}", w.get_str());
585 for item in &w.items {
586 println!("{:?} - ({}..{}) = {:?}", w.get_substr(item), item.start, item.end, item.word_type);
587 }
588 }
589
590 fn string_to_vec(s: String) -> Vec<char> {
591 s.chars().collect()
592 }
593
594 fn assert_words(expected: &Words, got_words: &Words) {
595 for (idx, expected_word) in expected.items.iter().enumerate() {
596 let got = got_words.items.get(idx);
597 if got != Some(expected_word) {
598 println!("expected: ");
599 print_words(expected);
600 println!("got: ");
601 print_words(got_words);
602 panic!("Expected word idx {} - expected: {:#?}, got: {:#?}", idx, Some(expected_word), got);
603 }
604 }
605 }
606
607 let ascii_str = String::from("abc\tdef \nghi\r\njkl");
608 let words_ascii = split_text_into_words(&ascii_str);
609 let words_ascii_expected = Words {
610 internal_str: ascii_str.clone(),
611 internal_chars: string_to_vec(ascii_str),
612 items: vec![
613 Word { start: 0, end: 3, word_type: WordType::Word }, Word { start: 3, end: 4, word_type: WordType::Tab }, Word { start: 4, end: 7, word_type: WordType::Word }, Word { start: 7, end: 8, word_type: WordType::Space }, Word { start: 8, end: 9, word_type: WordType::Space }, Word { start: 9, end: 10, word_type: WordType::Return }, Word { start: 10, end: 13, word_type: WordType::Word }, Word { start: 13, end: 15, word_type: WordType::Return }, Word { start: 15, end: 18, word_type: WordType::Word }, ],
623 };
624
625 assert_words(&words_ascii_expected, &words_ascii);
626
627 let unicode_str = String::from("㌊㌋㌌㌍㌎㌏㌐㌑ ㌒㌓㌔㌕㌖㌗");
628 let words_unicode = split_text_into_words(&unicode_str);
629 let words_unicode_expected = Words {
630 internal_str: unicode_str.clone(),
631 internal_chars: string_to_vec(unicode_str),
632 items: vec![
633 Word { start: 0, end: 8, word_type: WordType::Word }, Word { start: 8, end: 9, word_type: WordType::Space }, Word { start: 9, end: 15, word_type: WordType::Word }, ],
637 };
638
639 assert_words(&words_unicode_expected, &words_unicode);
640
641 let single_str = String::from("A");
642 let words_single_str = split_text_into_words(&single_str);
643 let words_single_str_expected = Words {
644 internal_str: single_str.clone(),
645 internal_chars: string_to_vec(single_str),
646 items: vec![
647 Word { start: 0, end: 1, word_type: WordType::Word }, ],
649 };
650
651 assert_words(&words_single_str_expected, &words_single_str);
652}
653
654#[test]
655fn test_get_line_y_position() {
656
657 assert_eq!(get_line_y_position(0, 20.0, 0.0), 20.0);
658 assert_eq!(get_line_y_position(1, 20.0, 0.0), 40.0);
659 assert_eq!(get_line_y_position(2, 20.0, 0.0), 60.0);
660
661 assert_eq!(get_line_y_position(0, 20.0, 5.0), 20.0);
666 assert_eq!(get_line_y_position(1, 20.0, 5.0), 45.0);
667 assert_eq!(get_line_y_position(2, 20.0, 5.0), 70.0);
668}
669
670#[test]
682fn test_caret_intersects_with_holes_1() {
683 let line_caret_x = 0.0;
684 let line_number = 0;
685 let font_size_px = 20.0;
686 let line_height_px = 0.0;
687 let max_width = None;
688 let holes = vec![LayoutRect::new(LayoutPoint::new(0.0, 0.0), LayoutSize::new(200.0, 100.0))];
689
690 let result = caret_intersects_with_holes(
691 line_caret_x,
692 line_number,
693 font_size_px,
694 line_height_px,
695 &holes,
696 max_width,
697 );
698
699 assert_eq!(result, LineCaretIntersection::AdvanceCaretTo(200.0));
700}
701
702#[test]
717fn test_caret_intersects_with_holes_2() {
718 let line_caret_x = 0.0;
719 let line_number = 0;
720 let font_size_px = 20.0;
721 let line_height_px = 0.0;
722 let max_width = Some(200.0);
723 let holes = vec![LayoutRect::new(LayoutPoint::new(0.0, 0.0), LayoutSize::new(200.0, 100.0))];
724
725 let result = caret_intersects_with_holes(
726 line_caret_x,
727 line_number,
728 font_size_px,
729 line_height_px,
730 &holes,
731 max_width,
732 );
733
734 assert_eq!(result, LineCaretIntersection::PushCaretOntoNextLine(4, 0.0));
735}
736
737#[test]
752fn test_caret_intersects_with_holes_3() {
753 let line_caret_x = 450.0;
754 let line_number = 0;
755 let font_size_px = 20.0;
756 let line_height_px = 0.0;
757 let max_width = Some(400.0);
758 let holes = vec![LayoutRect::new(LayoutPoint::new(0.0, 0.0), LayoutSize::new(200.0, 100.0))];
759
760 let result = caret_intersects_with_holes(
761 line_caret_x,
762 line_number,
763 font_size_px,
764 line_height_px,
765 &holes,
766 max_width,
767 );
768
769 assert_eq!(result, LineCaretIntersection::PushCaretOntoNextLine(1, 200.0));
770}
771
772#[test]
787fn test_caret_intersects_with_holes_4() {
788 let line_caret_x = 40.0;
789 let line_number = 0;
790 let font_size_px = 20.0;
791 let line_height_px = 0.0;
792 let max_width = Some(400.0);
793 let holes = vec![LayoutRect::new(LayoutPoint::new(80.0, 20.0), LayoutSize::new(200.0, 100.0))];
794
795 let result = caret_intersects_with_holes(
796 line_caret_x,
797 line_number,
798 font_size_px,
799 line_height_px,
800 &holes,
801 max_width,
802 );
803
804 assert_eq!(result, LineCaretIntersection::NoIntersection);
805}