1use ab_glyph::{Font, PxScale};
7use wgpu::util::DeviceExt;
8
9use crate::{FontId, TextRenderer};
10
11#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
13pub(crate) struct Outline {
14 pub(crate) color: [f32; 4],
15 pub(crate) width: f32,
16}
17
18#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
19pub(crate) struct SdfTextData {
20 pub(crate) radius: f32,
21 pub(crate) outline: Option<Outline>,
22}
23
24#[derive(Debug, Clone, PartialEq, PartialOrd)]
25pub(crate) struct TextData {
26 pub(crate) text: String,
27 pub(crate) font: FontId,
28 pub(crate) position: [f32; 2],
29 pub(crate) color: [f32; 4],
30 pub(crate) scale: f32,
31 pub(crate) halign: HorizontalAlignment,
32 pub(crate) valign: VerticalAlignment,
33
34 pub(crate) sdf: Option<SdfTextData>,
35}
36
37impl TextData {
38 fn settings_uniform(&self) -> SettingsUniform {
39 SettingsUniform {
40 color: self.color,
41 text_position: self.position,
42 _padding: [0.; 2],
43 }
44 }
45
46 fn sdf_settings_uniform(&self) -> SdfSettingsUniform {
47 let sdf = &self
48 .sdf
49 .expect("sdf_settings_uniform called but no sdf data found");
50 let outline_color = sdf.outline.map(|o| o.color).unwrap_or([0.; 4]);
51 let outline_width = sdf.outline.map(|o| o.width).unwrap_or(0.);
52 let sdf_radius = sdf.radius;
53
54 SdfSettingsUniform {
55 color: self.color,
56 outline_color,
57 text_position: self.position,
58 outline_width,
59 sdf_radius,
60 image_scale: self.scale,
61 _padding: [0.; 3],
62 }
63 }
64}
65
66#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
68pub enum FontSize {
69 Pt(f32),
71 Px(f32),
73}
74
75impl FontSize {
76 pub(crate) fn scale(&self, font: &impl Font) -> PxScale {
77 match self {
78 FontSize::Px(px) => font.pt_to_px_scale(*px * (72. / 96.)).unwrap(),
79 FontSize::Pt(pt) => font.pt_to_px_scale(*pt).unwrap(),
80 }
81 }
82
83 pub(crate) fn px_size(&self, font: &impl Font) -> f32 {
84 self.scale(font).y
85 }
86}
87
88#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd)]
92pub enum HorizontalAlignment {
93 #[default]
97 Left,
98 Center,
100 Right,
104 Ratio(f32),
111}
112
113impl HorizontalAlignment {
114 pub fn proportion(&self) -> f32 {
118 match self {
119 Self::Left => 0.,
120 Self::Right => 1.,
121 Self::Center => 0.5,
122 Self::Ratio(r) => r.clamp(0., 1.),
123 }
124 }
125}
126
127#[derive(Default, Copy, Clone, Debug, PartialEq, PartialOrd)]
131pub enum VerticalAlignment {
132 #[default]
137 Baseline,
138 Top,
142 Middle,
145 Bottom,
149 Ratio(f32),
156}
157
158#[derive(Debug, Clone, PartialEq, PartialOrd)]
160pub struct TextBuilder {
161 text: String,
162 font: FontId,
163 position: [f32; 2],
164 outline: Option<Outline>,
165 color: [f32; 4],
166 scale: f32,
167 custom_font_size: Option<FontSize>,
168 halign: HorizontalAlignment,
169 valign: VerticalAlignment,
170}
171
172impl TextBuilder {
173 pub fn new(text: impl Into<String>, font: FontId, position: [f32; 2]) -> Self {
175 Self {
176 text: text.into(),
177 font,
178 position,
179
180 outline: None,
181 color: [0., 0., 0., 1.],
182 scale: 1.,
183 custom_font_size: None,
184 halign: Default::default(),
185 valign: Default::default(),
186 }
187 }
188
189 pub fn build(
192 &self,
193 device: &wgpu::Device,
194 queue: &wgpu::Queue,
195 text_renderer: &mut TextRenderer,
196 ) -> Text {
197 let scale = match self.custom_font_size {
198 None => self.scale,
199 Some(size) => {
200 let self_size = size.px_size(&text_renderer.fonts.get(self.font).font);
201 let font_size = text_renderer.fonts.get(self.font).px_size;
202
203 self.scale * (self_size / font_size)
204 }
205 };
206
207 let data = TextData {
208 text: self.text.clone(),
209 font: self.font,
210 position: self.position,
211 color: self.color,
212 scale,
213 halign: self.halign,
214 valign: self.valign,
215
216 sdf: text_renderer.font_uses_sdf(self.font).then(|| SdfTextData {
217 radius: text_renderer
218 .fonts
219 .get(self.font)
220 .sdf_settings
221 .unwrap()
222 .radius,
223 outline: self.outline,
224 }),
225 };
226 Text::new(data, device, queue, text_renderer)
227 }
228
229 pub fn text(&mut self, text: String) -> &mut Self {
231 self.text = text;
232 self
233 }
234
235 pub fn font(&mut self, font: FontId) -> &mut Self {
237 self.font = font;
238 self
239 }
240
241 pub fn position(&mut self, position: [f32; 2]) -> &mut Self {
243 self.position = position;
244 self
245 }
246
247 pub fn horizontal_align(&mut self, halign: HorizontalAlignment) -> &mut Self {
251 self.halign = halign;
252 self
253 }
254
255 pub fn vertical_align(&mut self, valign: VerticalAlignment) -> &mut Self {
259 self.valign = valign;
260 self
261 }
262
263 pub fn outlined(&mut self, color: [f32; 4], width: f32) -> &mut Self {
270 if width > 0. {
271 self.outline = Some(Outline { color, width });
272 } else {
273 self.outline = None;
274 }
275
276 self
277 }
278
279 pub fn no_outline(&mut self) -> &mut Self {
284 self.outline = None;
285 self
286 }
287
288 pub fn color(&mut self, color: [f32; 4]) -> &mut Self {
291 self.color = color;
292 self
293 }
294
295 pub fn scale(&mut self, scale: f32) -> &mut Self {
301 self.scale = scale;
302 self
303 }
304
305 pub fn font_size(&mut self, size: Option<FontSize>) -> &mut Self {
315 self.custom_font_size = size;
316 self
317 }
318}
319
320#[repr(C)]
321#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
322pub(crate) struct SettingsUniform {
323 color: [f32; 4],
324 text_position: [f32; 2],
325 _padding: [f32; 2],
326}
327
328#[repr(C)]
329#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
330pub(crate) struct SdfSettingsUniform {
331 color: [f32; 4],
332 outline_color: [f32; 4],
333 text_position: [f32; 2],
334 outline_width: f32,
335 sdf_radius: f32,
336 image_scale: f32,
337 _padding: [f32; 3],
338}
339
340#[derive(Debug)]
345pub struct Text {
346 pub(crate) data: TextData,
347 pub(crate) instance_buffer: wgpu::Buffer,
348 pub(crate) settings_bind_group: wgpu::BindGroup,
349
350 settings_buffer: wgpu::Buffer,
351 instance_capacity: usize,
352}
353
354impl Text {
355 fn new(
357 data: TextData,
358 device: &wgpu::Device,
359 queue: &wgpu::Queue,
360 text_renderer: &mut TextRenderer,
361 ) -> Self {
362 text_renderer.generate_char_textures(data.text.chars(), data.font, device, queue);
363 let instances = text_renderer.create_text_instances(&data);
364
365 let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
366 label: Some("kaku text instance buffer"),
367 contents: bytemuck::cast_slice(&instances),
368 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
369 });
370
371 let (settings_buffer, settings_bind_group) = if text_renderer.font_uses_sdf(data.font) {
372 let text_settings = data.sdf_settings_uniform();
373 let settings_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
374 label: Some("kaku sdf text settings uniform buffer"),
375 contents: bytemuck::cast_slice(&[text_settings]),
376 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
377 });
378
379 let settings_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
380 label: Some("kaku sdf text settings uniform bind group"),
381 layout: &text_renderer.sdf_settings_layout,
382 entries: &[wgpu::BindGroupEntry {
383 binding: 0,
384 resource: settings_buffer.as_entire_binding(),
385 }],
386 });
387
388 (settings_buffer, settings_bind_group)
389 } else {
390 let text_settings = data.settings_uniform();
391
392 let settings_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
393 label: Some("kaku text settings uniform buffer"),
394 contents: bytemuck::cast_slice(&[text_settings]),
395 usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
396 });
397
398 let settings_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
399 label: Some("kaku text settings uniform bind group"),
400 layout: &text_renderer.settings_layout,
401 entries: &[wgpu::BindGroupEntry {
402 binding: 0,
403 resource: settings_buffer.as_entire_binding(),
404 }],
405 });
406
407 (settings_buffer, settings_bind_group)
408 };
409
410 Self {
411 data,
412 instance_buffer,
413 settings_bind_group,
414 settings_buffer,
415 instance_capacity: instances.len(),
416 }
417 }
418
419 pub fn set_text(
424 &mut self,
425 text: String,
426 device: &wgpu::Device,
427 queue: &wgpu::Queue,
428 text_renderer: &mut TextRenderer,
429 ) {
430 text_renderer.generate_char_textures(text.chars(), self.data.font, device, queue);
431 self.data.text = text;
432 let new_instances = text_renderer.create_text_instances(&self.data);
433
434 if new_instances.len() > self.instance_capacity {
435 self.instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
436 label: Some("kaku text instance buffer"),
437 contents: bytemuck::cast_slice(&new_instances),
438 usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
439 });
440
441 self.instance_capacity = new_instances.len();
442 } else {
443 queue.write_buffer(
444 &self.instance_buffer,
445 0,
446 bytemuck::cast_slice(&new_instances),
447 );
448 }
449 }
450
451 fn update_settings_buffer(&self, queue: &wgpu::Queue) {
453 if self.data.sdf.is_some() {
454 queue.write_buffer(
455 &self.settings_buffer,
456 0,
457 bytemuck::cast_slice(&[self.data.sdf_settings_uniform()]),
458 );
459 } else {
460 queue.write_buffer(
461 &self.settings_buffer,
462 0,
463 bytemuck::cast_slice(&[self.data.settings_uniform()]),
464 );
465 }
466 }
467
468 pub fn set_color(&mut self, color: [f32; 4], queue: &wgpu::Queue) {
470 self.data.color = color;
471 self.update_settings_buffer(queue);
472 }
473
474 pub fn set_scale(&mut self, scale: f32, queue: &wgpu::Queue) {
476 self.data.scale = scale;
477 self.update_settings_buffer(queue);
478 }
479
480 pub fn set_position(&mut self, position: [f32; 2], queue: &wgpu::Queue) {
482 self.data.position = position;
483 self.update_settings_buffer(queue);
484 }
485
486 pub fn set_outline(&mut self, color: [f32; 4], width: f32, queue: &wgpu::Queue) {
491 if let Some(sdf) = &mut self.data.sdf {
492 if width > 0. {
493 sdf.outline = Some(Outline { color, width });
494 } else {
495 sdf.outline = None;
496 }
497 }
498
499 self.update_settings_buffer(queue);
500 }
501
502 pub fn set_no_outline(&mut self, queue: &wgpu::Queue) {
506 if let Some(sdf) = &mut self.data.sdf {
507 sdf.outline = None;
508 }
509
510 self.update_settings_buffer(queue)
511 }
512}