1use crate::context::GpuContext;
8use crate::core::{Color, Position, Size, TextStyle};
9use crate::types::TextureFormat;
10use glyphon::{
11 Attrs, Buffer as GlyphonBuffer, Cache, Family, FontSystem, Metrics, Resolution, Shaping,
12 SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer as GlyphonRenderer, Viewport,
13};
14
15pub struct TextEngine {
17 pub font_system: FontSystem,
18 pub swash_cache: SwashCache,
19 pub atlas: TextAtlas,
20 pub viewport: Viewport,
21 pub renderer: GlyphonRenderer,
22 pending: Vec<TextDraw>,
24}
25
26struct TextDraw {
27 buffer: GlyphonBuffer,
28 x: f32,
29 y: f32,
30 color: Color,
31 bounds: TextBounds,
32}
33
34impl TextEngine {
35 pub fn new(gpu: &GpuContext, format: TextureFormat) -> Self {
37 Self::with_msaa(gpu, format, 1)
38 }
39
40 pub fn with_msaa(gpu: &GpuContext, format: TextureFormat, msaa_samples: u32) -> Self {
42 let device = gpu.device();
43 let queue = gpu.queue();
44 let font_system = FontSystem::new();
45 let swash_cache = SwashCache::new();
46 let cache = Cache::new(device);
47 let mut atlas = TextAtlas::new(device, queue, &cache, format);
48 let viewport = Viewport::new(device, &cache);
49 let renderer = GlyphonRenderer::new(
50 &mut atlas,
51 device,
52 wgpu::MultisampleState {
53 count: msaa_samples,
54 mask: !0,
55 alpha_to_coverage_enabled: false,
56 },
57 None,
58 );
59 Self {
60 font_system,
61 swash_cache,
62 atlas,
63 viewport,
64 renderer,
65 pending: Vec::new(),
66 }
67 }
68
69 pub fn measure(&mut self, text: &str, style: &TextStyle) -> Size {
71 let metrics = Metrics::new(style.font_size, style.font_size * 1.2);
72 let mut buffer = GlyphonBuffer::new(&mut self.font_system, metrics);
73 buffer.set_size(
74 &mut self.font_system,
75 Some(f32::MAX),
76 Some(style.font_size * 2.0),
77 );
78 buffer.set_text(
79 &mut self.font_system,
80 text,
81 Attrs::new().family(Family::SansSerif),
82 Shaping::Advanced,
83 );
84 buffer.shape_until_scroll(&mut self.font_system, false);
85
86 let mut width: f32 = 0.0;
87 let mut height: f32 = 0.0;
88 for run in buffer.layout_runs() {
89 width = width.max(run.line_w);
90 height += metrics.line_height;
91 }
92 if width == 0.0 {
94 width = style.font_size * 0.6 * text.len() as f32;
95 }
96 if height == 0.0 {
97 height = style.font_size * 1.2;
98 }
99 Size::new(width, height)
100 }
101
102 pub fn draw(
104 &mut self,
105 pos: Position,
106 text: &str,
107 style: &TextStyle,
108 clip: Option<crate::core::Rect>,
109 ) {
110 let metrics = Metrics::new(style.font_size, style.font_size * 1.2);
111 let mut buffer = GlyphonBuffer::new(&mut self.font_system, metrics);
112 buffer.set_size(
113 &mut self.font_system,
114 Some(4096.0),
115 Some(style.font_size * 2.0),
116 );
117 buffer.set_text(
118 &mut self.font_system,
119 text,
120 Attrs::new().family(Family::SansSerif),
121 Shaping::Advanced,
122 );
123 buffer.shape_until_scroll(&mut self.font_system, false);
124
125 let bounds = if let Some(r) = clip {
126 TextBounds {
127 left: r.x as i32,
128 top: r.y as i32,
129 right: (r.x + r.width) as i32,
130 bottom: (r.y + r.height) as i32,
131 }
132 } else {
133 TextBounds {
134 left: 0,
135 top: 0,
136 right: i32::MAX,
137 bottom: i32::MAX,
138 }
139 };
140
141 self.pending.push(TextDraw {
142 buffer,
143 x: pos.x,
144 y: pos.y,
145 color: style.color,
146 bounds,
147 });
148 }
149
150 pub fn flush(
152 &mut self,
153 gpu: &GpuContext,
154 pass: &mut wgpu::RenderPass<'_>,
155 width: u32,
156 height: u32,
157 ) {
158 if self.pending.is_empty() {
159 return;
160 }
161
162 let device = gpu.device();
163 let queue = gpu.queue();
164
165 let text_areas: Vec<TextArea<'_>> = self
166 .pending
167 .iter()
168 .map(|td| {
169 let c = td.color;
170 TextArea {
171 buffer: &td.buffer,
172 left: td.x,
173 top: td.y,
174 scale: 1.0,
175 bounds: td.bounds,
176 default_color: glyphon::Color::rgba(
177 (c.r * 255.0) as u8,
178 (c.g * 255.0) as u8,
179 (c.b * 255.0) as u8,
180 (c.a * 255.0) as u8,
181 ),
182 custom_glyphs: &[],
183 }
184 })
185 .collect();
186
187 self.viewport.update(queue, Resolution { width, height });
188
189 self.renderer
190 .prepare(
191 device,
192 queue,
193 &mut self.font_system,
194 &mut self.atlas,
195 &self.viewport,
196 text_areas,
197 &mut self.swash_cache,
198 )
199 .expect("agpu: text prepare failed");
200
201 self.renderer
202 .render(&self.atlas, &self.viewport, pass)
203 .expect("agpu: text render failed");
204
205 self.pending.clear();
206 }
207
208 pub fn trim(&mut self) {
210 self.atlas.trim();
211 }
212}