1use crate::error::{ChartError, Result};
4use glyphon::{
5 Attrs, Buffer, Cache, Color, Family, FontSystem, Metrics, Resolution, Shaping, SwashCache,
6 TextArea, TextAtlas, TextBounds, TextRenderer as GlyphonRenderer, Viewport as GlyphonViewport,
7};
8use wgpu::{Device, Queue, TextureFormat};
9
10#[derive(Debug, Clone, Copy, PartialEq)]
12pub enum TextAnchor {
13 Start,
14 Middle,
15 End,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq)]
20pub enum TextBaseline {
21 Top,
22 Middle,
23 Bottom,
24}
25
26#[derive(Debug, Clone)]
28pub struct TextItem {
29 pub text: String,
30 pub x: f32,
31 pub y: f32,
32 pub font_size: f32,
33 pub color: crate::theme::Color,
34 pub anchor: TextAnchor,
35 pub baseline: TextBaseline,
36}
37
38pub struct TextRenderer {
40 font_system: FontSystem,
41 swash_cache: SwashCache,
42 viewport: GlyphonViewport,
43 atlas: TextAtlas,
44 renderer: GlyphonRenderer,
45 viewport_width: u32,
46 viewport_height: u32,
47 text_buffers: Vec<Buffer>,
48}
49
50impl TextRenderer {
51 pub fn new(
53 device: &Device,
54 queue: &Queue,
55 format: TextureFormat,
56 width: u32,
57 height: u32,
58 ) -> Result<Self> {
59 let font_system = FontSystem::new();
61
62 let swash_cache = SwashCache::new();
64 let cache = Cache::new(device);
65 let viewport = GlyphonViewport::new(device, &cache);
66 let mut atlas = TextAtlas::new(device, queue, &cache, format);
67 let renderer =
68 GlyphonRenderer::new(&mut atlas, device, wgpu::MultisampleState::default(), None);
69
70 Ok(Self {
71 font_system,
72 swash_cache,
73 viewport,
74 atlas,
75 renderer,
76 viewport_width: width,
77 viewport_height: height,
78 text_buffers: Vec::new(),
79 })
80 }
81
82 pub fn resize(&mut self, _device: &Device, queue: &Queue, width: u32, height: u32) {
84 self.viewport_width = width;
85 self.viewport_height = height;
86 self.viewport.update(
87 queue,
88 Resolution {
89 width: self.viewport_width,
90 height: self.viewport_height,
91 },
92 );
93 }
94
95 pub fn prepare(
97 &mut self,
98 device: &Device,
99 queue: &Queue,
100 text_items: &[TextItem],
101 ) -> Result<()> {
102 self.text_buffers.clear();
104
105 for item in text_items {
107 let mut buffer = Buffer::new(
109 &mut self.font_system,
110 Metrics::new(item.font_size, item.font_size * 1.2),
111 );
112
113 buffer.set_size(
115 &mut self.font_system,
116 Some(self.viewport_width as f32),
117 Some(self.viewport_height as f32),
118 );
119
120 let attrs = Attrs::new().family(Family::SansSerif);
122 buffer.set_text(&mut self.font_system, &item.text, &attrs, Shaping::Advanced);
123
124 buffer.shape_until_scroll(&mut self.font_system, false);
126
127 self.text_buffers.push(buffer);
129 }
130
131 let mut text_areas = Vec::new();
133
134 for (i, item) in text_items.iter().enumerate() {
135 let buffer = &self.text_buffers[i];
136
137 let (text_width, text_height) = self.measure_buffer(buffer);
139
140 let left = match item.anchor {
142 TextAnchor::Start => item.x,
143 TextAnchor::Middle => item.x - text_width / 2.0,
144 TextAnchor::End => item.x - text_width,
145 };
146
147 let top = match item.baseline {
148 TextBaseline::Top => item.y,
149 TextBaseline::Middle => item.y - text_height / 2.0,
150 TextBaseline::Bottom => item.y - text_height,
151 };
152
153 let text_area = TextArea {
155 buffer,
156 left,
157 top,
158 scale: 1.0,
159 bounds: TextBounds {
160 left: left as i32,
161 top: top as i32,
162 right: (left + self.viewport_width as f32) as i32,
163 bottom: (top + self.viewport_height as f32) as i32,
164 },
165 default_color: Color::rgba(
166 (item.color.r * 255.0) as u8,
167 (item.color.g * 255.0) as u8,
168 (item.color.b * 255.0) as u8,
169 (item.color.a * 255.0) as u8,
170 ),
171 custom_glyphs: &[],
172 };
173
174 text_areas.push(text_area);
175 }
176
177 self.viewport.update(
179 queue,
180 Resolution {
181 width: self.viewport_width,
182 height: self.viewport_height,
183 },
184 );
185
186 self.renderer
188 .prepare(
189 device,
190 queue,
191 &mut self.font_system,
192 &mut self.atlas,
193 &self.viewport,
194 text_areas,
195 &mut self.swash_cache,
196 )
197 .map_err(|e| ChartError::TextRendering(format!("Failed to prepare text: {:?}", e)))?;
198
199 Ok(())
200 }
201
202 pub fn render<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) -> Result<()> {
204 self.renderer
205 .render(&self.atlas, &self.viewport, render_pass)
206 .map_err(|e| ChartError::TextRendering(format!("Failed to render text: {:?}", e)))?;
207 Ok(())
208 }
209
210 fn measure_buffer(&self, buffer: &Buffer) -> (f32, f32) {
212 let mut width = 0.0f32;
213 let mut height = 0.0f32;
214
215 for run in buffer.layout_runs() {
216 width = width.max(run.line_w);
217 height += run.line_height;
218 }
219
220 (width, height)
221 }
222
223 pub fn clear_cache(&mut self) {
225 self.atlas.trim();
226 }
227}