1use super::font::{FontDesc, FontManager};
2use super::Color;
3use anyhow::{Context, Result};
4use cairo::{Context as CairoContext, Format, ImageSurface};
5
6#[derive(Debug, Clone)]
7pub struct TextStyle {
8 pub font: FontDesc,
9 pub color: Color,
10 pub background: Option<Color>,
11}
12
13impl TextStyle {
14 pub fn new(font: FontDesc, color: Color) -> Self {
15 Self {
16 font,
17 color,
18 background: None,
19 }
20 }
21
22 pub fn with_background(mut self, background: Color) -> Self {
23 self.background = Some(background);
24 self
25 }
26}
27
28impl Default for TextStyle {
29 fn default() -> Self {
30 Self {
31 font: FontDesc::new("sans-serif", 12.0),
32 color: Color::WHITE,
33 background: None,
34 }
35 }
36}
37
38#[derive(Debug)]
39pub struct TextMetrics {
40 pub width: f64,
41 pub height: f64,
42 pub ascent: f64,
43 pub descent: f64,
44 pub x_advance: f64,
45 pub y_advance: f64,
46}
47
48pub struct TextRenderer {
49 font_manager: FontManager,
50 surface: ImageSurface,
51 context: CairoContext,
52}
53
54impl TextRenderer {
55 pub fn new(width: i32, height: i32) -> Result<Self> {
56 let surface = ImageSurface::create(Format::ARgb32, width, height)
57 .context("Failed to create Cairo surface")?;
58
59 let context = CairoContext::new(&surface).context("Failed to create Cairo context")?;
60
61 let font_manager = FontManager::new().context("Failed to initialize font manager")?;
62
63 Ok(Self {
64 font_manager,
65 surface,
66 context,
67 })
68 }
69
70 pub fn resize(&mut self, width: i32, height: i32) -> Result<()> {
71 self.surface = ImageSurface::create(Format::ARgb32, width, height)
72 .context("Failed to recreate Cairo surface")?;
73
74 self.context =
75 CairoContext::new(&self.surface).context("Failed to recreate Cairo context")?;
76
77 Ok(())
78 }
79
80 pub fn clear(&self) {
81 self.context.save().unwrap();
82 self.context.set_operator(cairo::Operator::Clear);
83 self.context.paint().unwrap();
84 self.context.restore().unwrap();
85 }
86
87 pub fn clear_with_color(&self, color: Color) {
88 self.context.save().unwrap();
89 self.context
90 .set_source_rgba(color.r, color.g, color.b, color.a);
91 self.context.set_operator(cairo::Operator::Source);
92 self.context.paint().unwrap();
93 self.context.restore().unwrap();
94 }
95
96 fn apply_font(&self, font_desc: &FontDesc) -> Result<()> {
97 self.context.select_font_face(
98 &font_desc.family,
99 font_desc.slant.to_cairo_slant(),
100 font_desc.weight.to_cairo_weight(),
101 );
102 self.context.set_font_size(font_desc.size);
103 Ok(())
104 }
105
106 pub fn measure_text(&self, text: &str, style: &TextStyle) -> Result<TextMetrics> {
107 self.apply_font(&style.font)?;
108
109 let text_extents = self
110 .context
111 .text_extents(text)
112 .context("Failed to get text extents")?;
113
114 let font_extents = self
115 .context
116 .font_extents()
117 .context("Failed to get font extents")?;
118
119 Ok(TextMetrics {
120 width: text_extents.width(),
121 height: text_extents.height(),
122 ascent: font_extents.ascent(),
123 descent: font_extents.descent(),
124 x_advance: text_extents.x_advance(),
125 y_advance: text_extents.y_advance(),
126 })
127 }
128
129 pub fn render_text(&self, text: &str, x: f64, y: f64, style: &TextStyle) -> Result<()> {
130 self.apply_font(&style.font)?;
131
132 if let Some(bg_color) = style.background {
133 let metrics = self.measure_text(text, style)?;
134
135 self.context.save().unwrap();
136 self.context
137 .set_source_rgba(bg_color.r, bg_color.g, bg_color.b, bg_color.a);
138 self.context
139 .rectangle(x, y - metrics.ascent, metrics.width, metrics.height);
140 self.context.fill().unwrap();
141 self.context.restore().unwrap();
142 }
143
144 self.context.move_to(x, y);
145 self.context
146 .set_source_rgba(style.color.r, style.color.g, style.color.b, style.color.a);
147 self.context
148 .show_text(text)
149 .context("Failed to render text")?;
150
151 Ok(())
152 }
153
154 pub fn render_text_multiline(
155 &self,
156 text: &str,
157 x: f64,
158 y: f64,
159 max_width: f64,
160 style: &TextStyle,
161 ) -> Result<f64> {
162 let metrics = self.measure_text("M", style)?;
163 let line_height = metrics.ascent + metrics.descent;
164 let mut current_y = y;
165
166 let lines = self.wrap_text(text, max_width, style)?;
167
168 for line in lines {
169 self.render_text(&line, x, current_y, style)?;
170 current_y += line_height;
171 }
172
173 Ok(current_y - y)
174 }
175
176 fn wrap_text(&self, text: &str, max_width: f64, style: &TextStyle) -> Result<Vec<String>> {
177 let mut lines = Vec::new();
178 let words: Vec<&str> = text.split_whitespace().collect();
179
180 if words.is_empty() {
181 return Ok(lines);
182 }
183
184 let mut current_line = String::new();
185
186 for word in words {
187 let test_line = if current_line.is_empty() {
188 word.to_string()
189 } else {
190 format!("{} {}", current_line, word)
191 };
192
193 let metrics = self.measure_text(&test_line, style)?;
194
195 if metrics.width <= max_width {
196 current_line = test_line;
197 } else {
198 if !current_line.is_empty() {
199 lines.push(current_line);
200 }
201 current_line = word.to_string();
202 }
203 }
204
205 if !current_line.is_empty() {
206 lines.push(current_line);
207 }
208
209 Ok(lines)
210 }
211
212 pub fn get_surface_data(&mut self) -> Result<Vec<u8>> {
213 self.surface.flush();
215
216 let _width = self.surface.width();
218 let _height = self.surface.height();
219
220 let dummy_surface =
223 ImageSurface::create(Format::ARgb32, 1, 1).context("Failed to create dummy surface")?;
224 let dummy_context =
225 CairoContext::new(&dummy_surface).context("Failed to create dummy context")?;
226
227 let _old_context = std::mem::replace(&mut self.context, dummy_context);
229
230 let data_result = self
232 .surface
233 .data()
234 .context("Failed to get surface data")
235 .map(|data| data.to_vec());
236
237 self.context = CairoContext::new(&self.surface)
239 .context("Failed to recreate Cairo context after surface data access")?;
240
241 data_result
242 }
243
244 pub fn get_width(&self) -> i32 {
245 self.surface.width()
246 }
247
248 pub fn get_height(&self) -> i32 {
249 self.surface.height()
250 }
251
252 pub fn font_manager(&self) -> &FontManager {
253 &self.font_manager
254 }
255}