1use std::cell::RefCell;
5use std::rc::Rc;
6
7use cosmic_text::{CacheKey, FontSystem};
8use guillotiere::AllocId;
9
10use crate::color::{Premultiplied, sRGB32};
11use crate::graphics::{GlyphCache, GlyphRegion};
12use crate::render::atlas::{Atlas, Size};
13use crate::render::compositor::{CompositorView, DataFlags};
14use crate::{Error, PxRect};
15
16use swash::scale::{Render, ScaleContext, Source, StrikeWith};
17use swash::zeno::{Format, Vector};
18
19pub use swash::scale::image::{Content, Image};
20pub use swash::zeno::{Angle, Command, Placement, Transform};
21
22pub struct Instance {
23 pub text_buffer: Rc<RefCell<cosmic_text::Buffer>>,
24 pub padding: std::cell::Cell<crate::PxPerimeter>,
25}
26
27impl Instance {
28 pub fn get_glyph(key: CacheKey, glyphs: &GlyphCache) -> Option<&GlyphRegion> {
29 glyphs.get(&key)
30 }
31
32 pub fn draw_glyph(
33 font_system: &mut FontSystem,
34 context: &mut ScaleContext,
35 cache_key: CacheKey,
36 ) -> Option<Image> {
37 let font = match font_system.get_font(cache_key.font_id, cache_key.font_weight) {
38 Some(some) => some,
39 None => {
40 debug_assert!(false, "did not find font {:?}", cache_key.font_id);
41 return None;
42 }
43 };
44
45 let mut scaler = context
47 .builder(font.as_swash())
48 .size(f32::from_bits(cache_key.font_size_bits))
49 .hint(true)
50 .build();
51
52 let offset = Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float());
55
56 Render::new(&[
58 Source::ColorOutline(0),
60 Source::ColorBitmap(StrikeWith::BestFit),
62 Source::Outline,
64 ])
65 .format(Format::Alpha)
67 .offset(offset)
69 .render(&mut scaler, cache_key.glyph_id)
71 }
72
73 pub fn write_glyph(
74 key: CacheKey,
75 font_system: &mut FontSystem,
76 glyphs: &mut GlyphCache,
77 device: &wgpu::Device,
78 queue: &wgpu::Queue,
79 atlas: &mut Atlas,
80 cache: &mut ScaleContext,
81 ) -> Result<(), Error> {
82 if glyphs.get(&key).is_some() {
83 return Ok(());
85 }
86
87 let Some(mut image) = Self::draw_glyph(font_system, cache, key) else {
88 return Err(Error::GlyphRenderFailure);
89 };
90
91 let region = if image.data.is_empty() {
92 super::atlas::Region {
93 id: AllocId::deserialize(u32::MAX),
94 uv: guillotiere::euclid::Box2D::zero(),
95 index: 0,
96 }
97 } else {
98 atlas.reserve(
100 device,
101 Size::new(image.placement.width as i32, image.placement.height as i32),
102 None,
103 None,
104 )?
105 };
106
107 if !image.data.is_empty() {
108 match image.content {
109 Content::Mask => {
110 let mask = image.data;
111 image.data = mask
112 .iter()
113 .flat_map(|x| sRGB32::new(255, 255, 255, *x).as_f32().srgb_pre().as_bgra())
114 .collect();
115 }
116 Content::Color => {
119 for c in image.data.as_mut_slice().chunks_exact_mut(4) {
120 c.copy_from_slice(
122 &sRGB32::new(c[0], c[1], c[2], c[3])
123 .as_f32()
124 .srgb_pre()
125 .as_bgra(),
126 );
127 }
128 }
129 Content::SubpixelMask => {
130 let len = image.data.len() / 4;
133 let slice = image.data.as_mut_slice();
134 for i in 0..len {
135 let idx = i * 4;
136 slice.swap(idx, idx + 2);
137 }
139 }
140 }
141
142 atlas.queue_data(
143 &image.data,
144 ®ion,
145 queue,
146 image.placement.width,
147 image.placement.height,
148 );
149 }
150
151 if let Some(mut old) = glyphs.insert(
152 key,
153 GlyphRegion {
154 offset: [image.placement.left, image.placement.top],
155 region,
156 },
157 ) {
158 atlas.destroy(&mut old.region);
159 }
160
161 Ok(())
162 }
163
164 pub fn prepare_glyph(
165 x: i32,
166 y: i32,
167 line_y: f32,
168 scale_factor: f32,
169 color: cosmic_text::Color,
170 bounds_min_x: i32,
171 bounds_min_y: i32,
172 bounds_max_x: i32,
173 bounds_max_y: i32,
174 glyph: &GlyphRegion,
175 ) -> Result<Option<super::compositor::Data>, Error> {
176 if glyph.region.uv.area() == 0 {
177 return Ok(None);
178 }
179 let mut x = x + glyph.offset[0];
182 let mut y = (line_y * scale_factor).round() as i32 + y - glyph.offset[1];
183
184 let mut u = glyph.region.uv.min.x;
185 let mut v = glyph.region.uv.min.y;
186
187 let mut width = glyph.region.uv.width();
188 let mut height = glyph.region.uv.height();
189
190 let max_x = x + width;
192 if x > bounds_max_x || max_x < bounds_min_x {
193 return Ok(None);
194 }
195
196 let max_y = y + height;
198 if y > bounds_max_y || max_y < bounds_min_y {
199 return Ok(None);
200 }
201
202 if x < bounds_min_x {
204 let right_shift = bounds_min_x - x;
205
206 x = bounds_min_x;
207 width = max_x - bounds_min_x;
208 u += right_shift;
209 }
210
211 if x + width > bounds_max_x {
213 width = bounds_max_x - x;
214 }
215
216 if y < bounds_min_y {
218 let bottom_shift = bounds_min_y - y;
219
220 y = bounds_min_y;
221 height = max_y - bounds_min_y;
222 v += bottom_shift;
223 }
224
225 if y + height > bounds_max_y {
227 height = bounds_max_y - y;
228 }
229
230 Ok(Some(super::compositor::Data {
231 pos: [x as f32, y as f32].into(),
232 dim: [width as f32, height as f32].into(),
233 uv: [u as f32, v as f32].into(),
234 uvdim: [width as f32, height as f32].into(),
235 color: u32::from_be_bytes(color.as_rgba()),
236 rotation: 0.0,
237 flags: DataFlags::new().with_tex(glyph.region.index).into(),
238 ..Default::default()
239 }))
240 }
241
242 fn evaluate(
243 buffer: &cosmic_text::Buffer,
244 pos: crate::PxPoint,
245 scale: f32,
246 mut bounds: PxRect,
247 color: cosmic_text::Color,
248 compositor: &mut super::compositor::CompositorView<'_>,
249 font_system: &mut FontSystem,
250 glyphs: &mut GlyphCache,
251 device: &wgpu::Device,
252 queue: &wgpu::Queue,
253 atlas: &mut Atlas,
254 cache: &mut ScaleContext,
255 ) -> Result<(), Error> {
256 bounds = bounds.intersect(compositor.current_clip());
257 let bounds_top = bounds.topleft().y as i32;
258 let bounds_bottom = bounds.bottomright().y as i32;
259 let bounds_min_x = (bounds.topleft().x as i32).max(0);
260 let bounds_min_y = bounds_top.max(0);
261 let bounds_max_x = bounds.bottomright().x as i32;
262 let bounds_max_y = bounds_bottom;
263
264 let is_run_visible = |run: &cosmic_text::LayoutRun| {
265 let start_y_physical = (pos.y + (run.line_top * scale)) as i32;
266 let end_y_physical = start_y_physical + (run.line_height * scale) as i32;
267
268 start_y_physical <= bounds_max_y && bounds_top <= end_y_physical
269 };
270
271 let layout_runs = buffer
272 .layout_runs()
273 .skip_while(|run| !is_run_visible(run))
274 .take_while(is_run_visible);
275
276 for run in layout_runs {
277 for glyph in run.glyphs.iter() {
278 let physical_glyph = glyph.physical((pos.x, pos.y), scale);
279
280 let glyphcolor = match glyph.color_opt {
281 Some(some) => some,
282 None => color,
283 };
284
285 Self::write_glyph(
286 physical_glyph.cache_key,
287 font_system,
288 glyphs,
289 device,
290 queue,
291 atlas,
292 cache,
293 )?;
294
295 if let Some(data) = Self::prepare_glyph(
296 physical_glyph.x,
297 physical_glyph.y,
298 run.line_y,
299 scale,
300 glyphcolor,
301 bounds_min_x,
302 bounds_min_y,
303 bounds_max_x,
304 bounds_max_y,
305 Self::get_glyph(physical_glyph.cache_key, glyphs)
306 .ok_or(Error::GlyphCacheFailure)?,
307 )? {
308 compositor.preprocessed(data);
309 }
310 }
311 }
312
313 Ok(())
314 }
315}
316
317impl super::Renderable for Instance {
318 fn render(
319 &self,
320 area: PxRect,
321 driver: &crate::graphics::Driver,
322 compositor: &mut CompositorView<'_>,
323 ) -> Result<(), Error> {
324 let padding = self.padding.get();
325
326 Self::evaluate(
327 &self.text_buffer.borrow(),
328 area.topleft().add_size(&padding.topleft()),
329 1.0,
330 area,
331 cosmic_text::Color::rgb(255, 255, 255),
332 compositor,
333 &mut driver.font_system.write(),
334 &mut driver.glyphs.write(),
335 &driver.device,
336 &driver.queue,
337 &mut driver.atlas.write(),
338 &mut driver.swash_cache.write(),
339 )
340 }
341}