tessera_ui_basic_components/pipelines/
text.rs1mod command;
2
3use std::sync::OnceLock;
4
5use glyphon::fontdb;
6use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
7use tessera_ui::{Color, DrawablePipeline, PxPosition, PxSize, wgpu};
8
9pub use command::{TextCommand, TextConstraint};
10
11static FONT_SYSTEM: OnceLock<RwLock<glyphon::FontSystem>> = OnceLock::new();
14
15#[cfg(target_os = "android")]
16fn init_font_system() -> RwLock<glyphon::FontSystem> {
17 let mut font_system = glyphon::FontSystem::new();
18
19 font_system.db_mut().load_fonts_dir("/system/fonts");
20 font_system.db_mut().set_sans_serif_family("Roboto");
21 font_system.db_mut().set_serif_family("Noto Serif");
22 font_system.db_mut().set_monospace_family("Droid Sans Mono");
23 font_system.db_mut().set_cursive_family("Dancing Script");
24 font_system.db_mut().set_fantasy_family("Dancing Script");
25
26 RwLock::new(font_system)
27}
28
29#[cfg(not(target_os = "android"))]
30fn init_font_system() -> RwLock<glyphon::FontSystem> {
31 RwLock::new(glyphon::FontSystem::new())
32}
33
34pub fn read_font_system() -> RwLockReadGuard<'static, glyphon::FontSystem> {
38 FONT_SYSTEM.get_or_init(init_font_system).read()
39}
40
41pub fn write_font_system() -> RwLockWriteGuard<'static, glyphon::FontSystem> {
45 FONT_SYSTEM.get_or_init(init_font_system).write()
46}
47
48pub struct GlyphonTextRender {
50 atlas: glyphon::TextAtlas,
52 #[allow(unused)]
54 cache: glyphon::Cache,
55 viewport: glyphon::Viewport,
57 swash_cache: glyphon::SwashCache,
59 msaa: wgpu::MultisampleState,
61}
62
63impl GlyphonTextRender {
64 pub fn new(
66 gpu: &wgpu::Device,
67 queue: &wgpu::Queue,
68 config: &wgpu::SurfaceConfiguration,
69 sample_count: u32,
70 ) -> Self {
71 let cache = glyphon::Cache::new(gpu);
72 let atlas = glyphon::TextAtlas::new(gpu, queue, &cache, config.format);
73 let viewport = glyphon::Viewport::new(gpu, &cache);
74 let swash_cache = glyphon::SwashCache::new();
75 let msaa = wgpu::MultisampleState {
76 count: sample_count,
77 mask: !0,
78 alpha_to_coverage_enabled: false,
79 };
80
81 Self {
82 atlas,
83 cache,
84 viewport,
85 swash_cache,
86 msaa,
87 }
88 }
89}
90
91#[allow(unused_variables)]
92impl DrawablePipeline<TextCommand> for GlyphonTextRender {
93 fn draw(
94 &mut self,
95 gpu: &wgpu::Device,
96 gpu_queue: &wgpu::Queue,
97 config: &wgpu::SurfaceConfiguration,
98 render_pass: &mut wgpu::RenderPass<'_>,
99 command: &TextCommand,
100 size: PxSize,
101 start_pos: PxPosition,
102 _scene_texture_view: &wgpu::TextureView,
103 ) {
104 let mut text_renderer = glyphon::TextRenderer::new(&mut self.atlas, gpu, self.msaa, None);
109
110 self.viewport.update(
111 gpu_queue,
112 glyphon::Resolution {
113 width: config.width,
114 height: config.height,
115 },
116 );
117
118 let text_areas = std::iter::once(command.data.text_area(start_pos));
119
120 text_renderer
121 .prepare(
122 gpu,
123 gpu_queue,
124 &mut write_font_system(),
125 &mut self.atlas,
126 &self.viewport,
127 text_areas,
128 &mut self.swash_cache,
129 )
130 .unwrap();
131
132 text_renderer
133 .render(&self.atlas, &self.viewport, render_pass)
134 .unwrap();
135 }
136}
137
138#[derive(Debug, Clone)]
139pub struct TextData {
140 text_buffer: glyphon::Buffer,
142 pub size: [u32; 2],
144}
145
146impl TextData {
147 pub fn new(
152 text: String,
153 color: Color,
154 size: f32,
155 line_height: f32,
156 constraint: TextConstraint,
157 ) -> TextData {
158 let mut text_buffer = glyphon::Buffer::new(
160 &mut write_font_system(),
161 glyphon::Metrics::new(size, line_height),
162 );
163 let color = glyphon::Color::rgba(
164 (color.r * 255.0) as u8,
165 (color.g * 255.0) as u8,
166 (color.b * 255.0) as u8,
167 (color.a * 255.0) as u8,
168 );
169 text_buffer.set_wrap(&mut write_font_system(), glyphon::Wrap::Glyph);
170 text_buffer.set_size(
171 &mut write_font_system(),
172 constraint.max_width,
173 constraint.max_height,
174 );
175 text_buffer.set_text(
176 &mut write_font_system(),
177 &text,
178 &glyphon::Attrs::new()
179 .family(fontdb::Family::SansSerif)
180 .color(color),
181 glyphon::Shaping::Advanced,
182 );
183 text_buffer.shape_until_scroll(&mut write_font_system(), false);
184 let mut run_width: f32 = 0.0;
187 let line_height =
189 text_buffer.layout_runs().count() as f32 * text_buffer.metrics().line_height;
190 for run in text_buffer.layout_runs() {
191 run_width = run_width.max(run.line_w);
193 }
194 Self {
196 text_buffer,
197 size: [run_width as u32, line_height as u32],
198 }
199 }
200
201 pub fn from_buffer(text_buffer: glyphon::Buffer) -> Self {
202 let mut run_width: f32 = 0.0;
205 let line_height =
207 text_buffer.layout_runs().count() as f32 * text_buffer.metrics().line_height;
208 for run in text_buffer.layout_runs() {
209 run_width = run_width.max(run.line_w);
211 }
212 Self {
214 text_buffer,
215 size: [run_width as u32, line_height as u32],
216 }
217 }
218
219 fn text_area(&'_ self, start_pos: PxPosition) -> glyphon::TextArea<'_> {
221 let bounds = glyphon::TextBounds {
222 left: start_pos.x.raw(),
223 top: start_pos.y.raw(),
224 right: start_pos.x.raw() + self.size[0] as i32,
225 bottom: start_pos.y.raw() + self.size[1] as i32,
226 };
227 glyphon::TextArea {
228 buffer: &self.text_buffer,
229 left: start_pos.x.to_f32(),
230 top: start_pos.y.to_f32(),
231 scale: 1.0,
232 bounds,
233 default_color: glyphon::Color::rgb(0, 0, 0), custom_glyphs: &[],
235 }
236 }
237}