1use std::cell::RefCell;
2
3use crate::{
4 Bounds, CameraView, Color, DrawOrder, GpuRenderer, GraphicsError, Index,
5 OrderedIndex, TextAtlas, TextVertex, Vec2, Vec3,
6};
7use cosmic_text::{
8 Align, Attrs, Buffer, Cursor, FontSystem, Metrics, SwashCache,
9 SwashContent, Wrap,
10};
11#[cfg(feature = "rayon")]
12use rayon::prelude::*;
13
14#[derive(Debug)]
17pub struct TextOptions {
18 pub shaping: cosmic_text::Shaping,
19 pub metrics: Option<Metrics>,
20 pub buffer_width: Option<f32>,
21 pub buffer_height: Option<f32>,
22 pub scale: f32,
23 pub wrap: Wrap,
24}
25
26impl Default for TextOptions {
27 fn default() -> Self {
28 Self {
29 shaping: cosmic_text::Shaping::Advanced,
30 metrics: Some(Metrics::new(16.0, 16.0).scale(1.0)),
31 buffer_width: None,
32 buffer_height: None,
33 scale: 1.0,
34 wrap: Wrap::None,
35 }
36 }
37}
38
39#[derive(Debug)]
42pub struct VisibleDetails {
43 pub width: f32,
45 pub lines: usize,
47 pub line_height: f32,
49}
50
51#[derive(Debug)]
54pub struct Text {
55 pub buffer: Buffer,
57 pub pos: Vec3,
59 pub size: Vec2,
61 pub scale: f32,
63 pub default_color: Color,
65 pub bounds: Bounds,
67 pub store_id: Index,
69 pub order: DrawOrder,
71 pub cursor: Cursor,
73 pub line: usize,
75 pub scroll: cosmic_text::Scroll,
77 pub wrap: Wrap,
79 pub camera_view: CameraView,
81 pub changed: bool,
83}
84
85thread_local! {
86 static GLYPH_VERTICES: RefCell<Vec<TextVertex>> = RefCell::new(Vec::with_capacity(1024));
87}
88
89impl Text {
90 pub fn create_quad(
93 &mut self,
94 cache: &mut SwashCache,
95 atlas: &mut TextAtlas,
96 renderer: &mut GpuRenderer,
97 ) -> Result<(), GraphicsError> {
98 #[cfg(feature = "rayon")]
99 let count: usize = self
100 .buffer
101 .lines
102 .par_iter()
103 .map(|line| line.text().len())
104 .sum();
105
106 #[cfg(not(feature = "rayon"))]
107 let count: usize =
108 self.buffer.lines.iter().map(|line| line.text().len()).sum();
109
110 let mut is_alpha = false;
111 let screensize = renderer.size();
112 let bounds_min_x = self.bounds.left.max(0.0);
113 let bounds_min_y = self.bounds.bottom.max(0.0);
114 let bounds_max_x = self.bounds.right.min(screensize.width);
115 let bounds_max_y = self.bounds.top.min(screensize.height);
116
117 GLYPH_VERTICES.with_borrow_mut(|vertices| {
118 vertices.clear();
119
120 if vertices.capacity() < count {
121 vertices.reserve(count);
122 }
123 });
124
125 let is_run_visible = |run: &cosmic_text::LayoutRun| {
127 let start_y = self.pos.y + self.size.y - run.line_top;
128 let end_y = self.pos.y + self.size.y
129 - run.line_top
130 - (run.line_height * 0.5);
131
132 start_y <= bounds_max_y + (run.line_height * 0.5)
133 && bounds_min_y <= end_y
134 };
135
136 self.buffer
137 .layout_runs()
138 .skip_while(|run| !is_run_visible(run))
139 .take_while(is_run_visible)
140 .for_each(|run| {
141 run.glyphs.iter().for_each(|glyph| {
142 let physical_glyph = glyph.physical(
143 (self.pos.x, self.pos.y + self.size.y),
144 self.scale,
145 );
146
147 let (allocation, is_color) =
148 if let Some((allocation, is_color)) =
149 atlas.get_by_key(&physical_glyph.cache_key)
150 {
151 (allocation, is_color)
152 } else {
153 let image = cache
154 .get_image_uncached(
155 &mut renderer.font_sys,
156 physical_glyph.cache_key,
157 )
158 .unwrap();
159
160 if image.placement.width > 0
161 && image.placement.height > 0
162 {
163 atlas
164 .upload_with_alloc(
165 renderer,
166 image.content == SwashContent::Color,
167 physical_glyph.cache_key,
168 &image,
169 )
170 .unwrap()
171 } else {
172 return;
173 }
174 };
175
176 let position = allocation.data;
177 let (u, v, width, height) = allocation.rect();
178 let (mut u, mut v, mut width, mut height) =
179 (u as f32, v as f32, width as f32, height as f32);
180 let (mut x, mut y) = (
181 physical_glyph.x as f32 + position.x,
182 physical_glyph.y as f32
183 + ((position.y - height)
184 - (run.line_y * self.scale).round()),
185 );
186 let color = if is_color {
187 Color::rgba(255, 255, 255, 255)
188 } else {
189 glyph.color_opt.unwrap_or(self.default_color)
190 };
191
192 if color.a() < 255 {
193 is_alpha = true;
194 }
195
196 let max_x = x + width;
198 if x > bounds_max_x || max_x < bounds_min_x {
199 return;
200 }
201
202 if x < bounds_min_x {
204 let right_shift = bounds_min_x - x;
205
206 x = bounds_min_x;
207 width = max_x - bounds_min_x;
208 u += right_shift;
209 }
210
211 if x + width > bounds_max_x {
213 width = bounds_max_x - x;
214 }
215
216 if y < bounds_min_y {
218 height -= bounds_min_y - y;
219 y = bounds_min_y;
220 }
221
222 if y + height > bounds_max_y {
224 let bottom_shift = (y + height) - bounds_max_y;
225
226 v += bottom_shift;
227 height -= bottom_shift;
228 }
229
230 GLYPH_VERTICES.with_borrow_mut(|vertices| {
231 vertices.push(TextVertex {
232 pos: [x, y, self.pos.z],
233 size: [width, height],
234 tex_coord: [u, v],
235 layer: allocation.layer as u32,
236 color: color.0,
237 camera_view: self.camera_view as u32,
238 is_color: is_color as u32,
239 })
240 });
241 });
242 });
243
244 if let Some(store) = renderer.get_buffer_mut(self.store_id) {
245 GLYPH_VERTICES.with_borrow(|vertices| {
246 let bytes: &[u8] = bytemuck::cast_slice(vertices);
247
248 if bytes.len() != store.store.len() {
249 store.store.resize_with(bytes.len(), || 0);
250 }
251
252 store.store.copy_from_slice(bytes);
253 store.changed = true;
254 });
255 }
256
257 self.order.alpha = is_alpha;
258
259 Ok(())
260 }
261
262 pub fn new(
266 renderer: &mut GpuRenderer,
267 metrics: Option<Metrics>,
268 pos: Vec3,
269 size: Vec2,
270 scale: f32,
271 order_layer: u32,
272 ) -> Self {
273 let text_starter_size =
274 bytemuck::bytes_of(&TextVertex::default()).len() * 64;
275
276 Self {
277 buffer: Buffer::new(
278 &mut renderer.font_sys,
279 metrics.unwrap_or(Metrics::new(16.0, 16.0).scale(scale)),
280 ),
281 pos,
282 size,
283 bounds: Bounds::default(),
284 store_id: renderer.new_buffer(text_starter_size, 0),
285 order: DrawOrder::new(false, pos, order_layer),
286 changed: true,
287 default_color: Color::rgba(0, 0, 0, 255),
288 camera_view: CameraView::default(),
289 cursor: Cursor::default(),
290 wrap: Wrap::Word,
291 line: 0,
292 scroll: cosmic_text::Scroll::default(),
293 scale,
294 }
295 }
296
297 pub fn set_camera_view(&mut self, camera_view: CameraView) {
300 self.camera_view = camera_view;
301
302 self.changed = true;
303 }
304
305 pub fn unload(self, renderer: &mut GpuRenderer) {
308 renderer.remove_buffer(self.store_id);
309 }
310
311 pub fn set_order_override(&mut self, order_override: Vec3) -> &mut Self {
315 self.order.set_pos(order_override);
316 self
317 }
318
319 pub fn set_order_layer(&mut self, order_layer: u32) -> &mut Self {
322 self.order.order_layer = order_layer;
323 self
324 }
325
326 pub fn set_text(
329 &mut self,
330 renderer: &mut GpuRenderer,
331 text: &str,
332 attrs: &Attrs,
333 shaping: cosmic_text::Shaping,
334 alignment: Option<Align>,
335 ) -> &mut Self {
336 self.buffer.set_text(
337 &mut renderer.font_sys,
338 text,
339 attrs,
340 shaping,
341 alignment,
342 );
343 self.changed = true;
344 self
345 }
346
347 pub fn set_rich_text<'r, 's, I>(
350 &mut self,
351 renderer: &mut GpuRenderer,
352 spans: I,
353 default_attr: &Attrs,
354 shaping: cosmic_text::Shaping,
355 alignment: Option<Align>,
356 ) -> &mut Self
357 where
358 I: IntoIterator<Item = (&'s str, Attrs<'r>)>,
359 {
360 self.buffer.set_rich_text(
361 &mut renderer.font_sys,
362 spans,
363 default_attr,
364 shaping,
365 alignment,
366 );
367 self.changed = true;
368 self
369 }
370
371 pub fn get_text_buffer(&mut self) -> &mut Buffer {
375 &mut self.buffer
376 }
377
378 pub fn shape_until_cursor(
381 &mut self,
382 renderer: &mut GpuRenderer,
383 cursor: Cursor,
384 ) -> &mut Self {
385 if self.cursor != cursor || self.changed {
386 self.cursor = cursor;
387 self.line = 0;
388 self.changed = true;
389 self.buffer.shape_until_cursor(
390 &mut renderer.font_sys,
391 cursor,
392 false,
393 );
394 self.scroll = self.buffer.scroll();
395 }
396
397 self
398 }
399
400 pub fn shape_until(
403 &mut self,
404 renderer: &mut GpuRenderer,
405 line: usize,
406 ) -> &mut Self {
407 if self.line != line || self.changed {
408 self.cursor = Cursor::new(line, 0);
409 self.line = line;
410 self.changed = true;
411 self.buffer.shape_until_cursor(
412 &mut renderer.font_sys,
413 self.cursor,
414 false,
415 );
416 }
417 self
418 }
419
420 pub fn shape_until_scroll(
423 &mut self,
424 renderer: &mut GpuRenderer,
425 ) -> &mut Self {
426 if self.changed {
427 self.buffer
428 .shape_until_scroll(&mut renderer.font_sys, false);
429 }
430
431 self
432 }
433
434 pub fn set_scroll(
437 &mut self,
438 renderer: &mut GpuRenderer,
439 scroll: cosmic_text::Scroll,
440 ) -> &mut Self {
441 if self.scroll != scroll {
442 self.scroll = scroll;
443 self.buffer.set_scroll(scroll);
444 self.changed = true;
445 self.buffer
446 .shape_until_scroll(&mut renderer.font_sys, false);
447 }
448
449 self
450 }
451
452 pub fn set_change(&mut self, changed: bool) -> &mut Self {
455 self.changed = changed;
456 self
457 }
458
459 pub fn set_wrap(
462 &mut self,
463 renderer: &mut GpuRenderer,
464 wrap: Wrap,
465 ) -> &mut Self {
466 if self.wrap != wrap {
467 self.wrap = wrap;
468 self.buffer.set_wrap(&mut renderer.font_sys, wrap);
469 self.changed = true;
470 }
471
472 self
473 }
474
475 pub fn set_bounds(&mut self, bounds: Bounds) -> &mut Self {
478 self.bounds = bounds;
479 self.changed = true;
480 self
481 }
482
483 pub fn set_pos(&mut self, pos: Vec3) -> &mut Self {
486 self.pos = pos;
487 self.order.set_pos(pos);
488 self.changed = true;
489 self
490 }
491
492 pub fn set_default_color(&mut self, color: Color) -> &mut Self {
495 self.default_color = color;
496 self.changed = true;
497 self
498 }
499
500 pub fn set_buffer_size(
503 &mut self,
504 renderer: &mut GpuRenderer,
505 width: Option<f32>,
506 height: Option<f32>,
507 ) -> &mut Self {
508 self.buffer.set_size(&mut renderer.font_sys, width, height);
509 self.changed = true;
510 self
511 }
512
513 pub fn clear(&mut self, renderer: &mut GpuRenderer) -> &mut Self {
516 self.buffer.set_text(
517 &mut renderer.font_sys,
518 "",
519 &cosmic_text::Attrs::new(),
520 cosmic_text::Shaping::Basic,
521 None,
522 );
523 self.changed = true;
524 self
525 }
526
527 pub fn update(
531 &mut self,
532 cache: &mut SwashCache,
533 atlas: &mut TextAtlas,
534 renderer: &mut GpuRenderer,
535 ) -> Result<OrderedIndex, GraphicsError> {
536 if self.changed {
537 self.create_quad(cache, atlas, renderer)?;
538 self.changed = false;
539 }
540
541 Ok(OrderedIndex::new(self.order, self.store_id, 0))
542 }
543
544 pub fn check_mouse_bounds(&self, mouse_pos: Vec2) -> bool {
547 mouse_pos[0] > self.pos.x
548 && mouse_pos[0] < self.pos.x + self.size.x
549 && mouse_pos[1] > self.pos.y
550 && mouse_pos[1] < self.pos.y + self.size.y
551 }
552
553 pub fn visible_details(&self) -> VisibleDetails {
555 #[cfg(not(feature = "rayon"))]
556 let (width, lines) = self.buffer.layout_runs().fold(
557 (0.0, 0usize),
558 |(width, total_lines), run| {
559 (run.line_w.max(width), total_lines + 1)
560 },
561 );
562
563 #[cfg(feature = "rayon")]
564 let (width, lines) = self
565 .buffer
566 .layout_runs()
567 .par_bridge()
568 .fold(
569 || (0.0, 0usize),
570 |(width, total_lines), run| {
571 (run.line_w.max(width), total_lines + 1)
572 },
573 )
574 .reduce(
575 || (0.0, 0usize),
576 |(w1, t1), (w2, t2)| (w1.max(w2), t1 + t2),
577 );
578
579 VisibleDetails {
580 line_height: self.buffer.metrics().line_height,
581 lines,
582 width,
583 }
584 }
585
586 pub fn measure(&self) -> Vec2 {
589 let details = self.visible_details();
590
591 let (max_width, max_height) = self.buffer.size();
592 let height = details.lines as f32 * details.line_height;
593
594 Vec2::new(
595 details
596 .width
597 .min(max_width.unwrap_or(0.0).max(details.width)),
598 height.min(max_height.unwrap_or(0.0).max(height)),
599 )
600 }
601
602 pub fn measure_string(
606 font_system: &mut FontSystem,
607 text: &str,
608 attrs: &Attrs,
609 options: TextOptions,
610 alignment: Option<Align>,
611 ) -> Vec2 {
612 let mut buffer = Buffer::new(
613 font_system,
614 options
615 .metrics
616 .unwrap_or(Metrics::new(16.0, 16.0).scale(options.scale)),
617 );
618
619 buffer.set_wrap(font_system, options.wrap);
620 buffer.set_size(
621 font_system,
622 options.buffer_width,
623 options.buffer_height,
624 );
625 buffer.set_text(font_system, text, attrs, options.shaping, alignment);
626
627 #[cfg(not(feature = "rayon"))]
628 let (width, total_lines) = buffer.layout_runs().fold(
629 (0.0, 0usize),
630 |(width, total_lines), run| {
631 (run.line_w.max(width), total_lines + 1)
632 },
633 );
634
635 #[cfg(feature = "rayon")]
636 let (width, total_lines) = buffer
637 .layout_runs()
638 .par_bridge()
639 .fold(
640 || (0.0, 0usize),
641 |(width, total_lines), run| {
642 (run.line_w.max(width), total_lines + 1)
643 },
644 )
645 .reduce(
646 || (0.0, 0usize),
647 |(w1, t1), (w2, t2)| (w1.max(w2), t1 + t2),
648 );
649
650 let (max_width, max_height) = buffer.size();
651 let height = total_lines as f32 * buffer.metrics().line_height;
652
653 Vec2::new(
654 width.min(max_width.unwrap_or(0.0).max(width)),
655 height.min(max_height.unwrap_or(0.0).max(height)),
656 )
657 }
658
659 pub fn measure_glyphs(
663 font_system: &mut FontSystem,
664 text: &str,
665 attrs: &Attrs,
666 options: TextOptions,
667 alignment: Option<Align>,
668 ) -> Vec<Vec2> {
669 let mut buffer = Buffer::new(
670 font_system,
671 options
672 .metrics
673 .unwrap_or(Metrics::new(16.0, 16.0).scale(options.scale)),
674 );
675
676 buffer.set_wrap(font_system, options.wrap);
677 buffer.set_size(
678 font_system,
679 options.buffer_width,
680 options.buffer_height,
681 );
682
683 text.char_indices()
684 .map(|(_position, ch)| {
685 let n = ch.len_utf8();
688 let mut buf = vec![0; n];
689 let u = ch.encode_utf8(&mut buf);
690
691 buffer.set_text(
692 font_system,
693 u,
694 attrs,
695 options.shaping,
696 alignment,
697 );
698
699 #[cfg(not(feature = "rayon"))]
700 let (width, total_lines) = buffer.layout_runs().fold(
701 (0.0, 0usize),
702 |(width, total_lines), run| {
703 (run.line_w.max(width), total_lines + 1)
704 },
705 );
706
707 #[cfg(feature = "rayon")]
708 let (width, total_lines) = buffer
709 .layout_runs()
710 .par_bridge()
711 .fold(
712 || (0.0, 0usize),
713 |(width, total_lines), run| {
714 (run.line_w.max(width), total_lines + 1)
715 },
716 )
717 .reduce(
718 || (0.0, 0usize),
719 |(w1, t1), (w2, t2)| (w1.max(w2), t1 + t2),
720 );
721
722 let (max_width, max_height) = buffer.size();
723 let height = total_lines as f32 * buffer.metrics().line_height;
724
725 Vec2::new(
726 width.min(max_width.unwrap_or(0.0).max(width)),
727 height.min(max_height.unwrap_or(0.0).max(height)),
728 )
729 })
730 .collect()
731 }
732}