rootvg_text/
buffer.rs

1use glyphon::cosmic_text::Align;
2use std::cell::{Ref, RefCell};
3use std::rc::Rc;
4
5use rootvg_core::math::Size;
6
7use super::{TextProperties, WRITE_LOCK_PANIC_MSG};
8
9#[derive(Debug)]
10struct TextBufferInner {
11    raw_buffer: glyphon::Buffer,
12    props: TextProperties,
13    bounds_size: Size,
14    has_text: bool,
15}
16
17#[derive(Debug)]
18pub struct RcTextBuffer {
19    inner: Rc<RefCell<TextBufferInner>>,
20}
21
22impl RcTextBuffer {
23    pub fn new(text: &str, props: TextProperties, bounds_size: Size) -> Self {
24        let mut font_system = super::font_system().write().expect(WRITE_LOCK_PANIC_MSG);
25
26        let mut raw_buffer = glyphon::Buffer::new(font_system.raw_mut(), props.metrics);
27
28        raw_buffer.set_size(font_system.raw_mut(), bounds_size.width, bounds_size.height);
29        raw_buffer.set_wrap(font_system.raw_mut(), props.wrap);
30        raw_buffer.set_text(font_system.raw_mut(), text, props.attrs, props.shaping);
31
32        let has_text = !text.is_empty();
33        if has_text {
34            shape(&mut raw_buffer, font_system.raw_mut(), props.align);
35        }
36
37        Self {
38            inner: Rc::new(RefCell::new(TextBufferInner {
39                raw_buffer,
40                props,
41                bounds_size,
42                has_text,
43            })),
44        }
45    }
46
47    pub fn bounds_size(&self) -> Size {
48        RefCell::borrow(&self.inner).bounds_size
49    }
50
51    pub fn props<'a>(&'a self) -> Ref<'a, TextProperties> {
52        let inner = RefCell::borrow(&self.inner);
53        Ref::map(inner, |inner| &inner.props)
54    }
55
56    /// The minimum size (in logical points) needed to fit the text contents.
57    pub fn measure(&self) -> Size {
58        let inner = RefCell::borrow(&self.inner);
59        let buffer = &inner.raw_buffer;
60
61        let (width, total_lines) = buffer
62            .layout_runs()
63            .fold((0.0, 0usize), |(width, total_lines), run| {
64                (run.line_w.max(width), total_lines + 1)
65            });
66
67        Size::new(width, total_lines as f32 * buffer.metrics().line_height)
68    }
69
70    pub fn set_text_and_props(&mut self, text: &str, props: TextProperties) {
71        let mut font_system = super::font_system().write().expect(WRITE_LOCK_PANIC_MSG);
72
73        let mut inner = RefCell::borrow_mut(&self.inner);
74
75        if inner.props.metrics != props.metrics {
76            inner
77                .raw_buffer
78                .set_metrics(font_system.raw_mut(), props.metrics)
79        }
80
81        if inner.props.wrap != props.wrap {
82            inner.raw_buffer.set_wrap(font_system.raw_mut(), props.wrap);
83        }
84
85        inner
86            .raw_buffer
87            .set_text(font_system.raw_mut(), text, props.attrs, props.shaping);
88
89        inner.has_text = !text.is_empty();
90
91        if inner.has_text {
92            shape(&mut inner.raw_buffer, font_system.raw_mut(), props.align);
93        }
94
95        inner.props = props;
96    }
97
98    pub fn set_text(&mut self, text: &str) {
99        let mut font_system = super::font_system().write().expect(WRITE_LOCK_PANIC_MSG);
100
101        let mut inner = RefCell::borrow_mut(&self.inner);
102        let TextBufferInner {
103            raw_buffer,
104            props,
105            bounds_size: _,
106            has_text,
107        } = &mut *inner;
108
109        raw_buffer.set_text(font_system.raw_mut(), text, props.attrs, props.shaping);
110
111        *has_text = !text.is_empty();
112
113        if *has_text {
114            shape(raw_buffer, font_system.raw_mut(), props.align);
115        }
116    }
117
118    /// Set the bounds of the text in logical points.
119    pub fn set_bounds(&mut self, bounds_size: Size) {
120        let mut inner = RefCell::borrow_mut(&self.inner);
121        let TextBufferInner {
122            raw_buffer,
123            props,
124            bounds_size: inner_bounds_size,
125            has_text,
126        } = &mut *inner;
127
128        if *inner_bounds_size == bounds_size {
129            return;
130        }
131        *inner_bounds_size = bounds_size;
132
133        let mut font_system = super::font_system().write().expect(WRITE_LOCK_PANIC_MSG);
134
135        raw_buffer.set_size(
136            font_system.raw_mut(),
137            bounds_size.width as f32,
138            bounds_size.height as f32,
139        );
140
141        if *has_text {
142            shape(raw_buffer, font_system.raw_mut(), props.align);
143        }
144    }
145
146    pub(crate) fn raw_buffer<'a>(&'a self) -> Ref<'a, glyphon::Buffer> {
147        let inner = RefCell::borrow(&self.inner);
148        Ref::map(inner, |inner| &inner.raw_buffer)
149    }
150}
151
152impl Clone for RcTextBuffer {
153    fn clone(&self) -> Self {
154        Self {
155            inner: Rc::clone(&self.inner),
156        }
157    }
158}
159
160impl PartialEq for RcTextBuffer {
161    fn eq(&self, other: &Self) -> bool {
162        Rc::ptr_eq(&self.inner, &other.inner)
163    }
164}
165
166fn shape(
167    buffer: &mut glyphon::Buffer,
168    font_system: &mut glyphon::FontSystem,
169    align: Option<Align>,
170) {
171    for line in buffer.lines.iter_mut() {
172        if line.align() != align {
173            line.set_align(align);
174        }
175    }
176
177    buffer.shape_until_scroll(font_system);
178}