1use super::{
13 Drawing, Faces, HashMap, HostTexture, HostTexturePixels, Layout, PixelBox, PixelLook,
14 Premultiplication, RasterColorSpace, RequestLook, TextFill, TextRasterError, TextRasterRequest,
15 TextRasterResult, TextRasterizer, TextTexture, decoration_extents, dilate, disc_extent,
16 gradient, normalize_mapped, over, point, stroke_radius, validate_request,
17};
18use std::ops::Range;
19use std::sync::Arc;
20use unicode_segmentation::UnicodeSegmentation;
21
22#[derive(Clone, Debug, PartialEq)]
24pub struct TextLayout {
25 pub clusters: Vec<ClusterInfo>,
27 pub lines: Vec<LineInfo>,
29 pub font_size: f32,
31}
32
33#[derive(Clone, Debug, PartialEq)]
35pub struct ClusterInfo {
36 pub bytes: Range<usize>,
38 pub line: u32,
40 pub word: Option<u32>,
43 pub is_space: bool,
45 pub x: f32,
47 pub advance: f32,
49 pub baseline: f32,
51}
52
53#[derive(Clone, Debug, PartialEq)]
55pub struct LineInfo {
56 pub clusters: Range<usize>,
58 pub left: f32,
60 pub width: f32,
62 pub baseline: f32,
64}
65
66#[derive(Copy, Clone, Debug, PartialEq)]
69pub struct ColorMix {
70 pub keep: f32,
72 pub add: [f32; 4],
74}
75
76#[derive(Copy, Clone, Debug, PartialEq)]
78pub struct ClusterState {
79 pub offset: [f32; 2],
81 pub scale: [f32; 2],
83 pub rotation: f32,
85 pub opacity: f32,
87 pub fill: Option<ColorMix>,
89 pub stroke: Option<ColorMix>,
91 pub tracking: f32,
93}
94
95impl ClusterState {
96 pub const IDENTITY: Self = Self {
98 offset: [0.0, 0.0],
99 scale: [1.0, 1.0],
100 rotation: 0.0,
101 opacity: 1.0,
102 fill: None,
103 stroke: None,
104 tracking: 0.0,
105 };
106
107 #[must_use]
109 pub fn is_identity(&self) -> bool {
110 *self == Self::IDENTITY
111 }
112
113 fn is_valid(&self) -> bool {
114 let mix = |mix: &Option<ColorMix>| {
115 mix.is_none_or(|mix| {
116 (0.0..=1.0).contains(&mix.keep)
117 && mix.add.iter().all(|value| (0.0..=1.0).contains(value))
118 })
119 };
120 self.offset.iter().all(|value| value.is_finite())
121 && self
122 .scale
123 .iter()
124 .all(|value| value.is_finite() && *value >= 0.0)
125 && self.rotation.is_finite()
126 && (0.0..=1.0).contains(&self.opacity)
127 && self.tracking.is_finite()
128 && mix(&self.fill)
129 && mix(&self.stroke)
130 }
131}
132
133type Affine = [f32; 6];
136
137fn apply(affine: Affine, [x, y]: [f32; 2]) -> [f32; 2] {
138 [
139 affine[0] * x + affine[2] * y + affine[4],
140 affine[1] * x + affine[3] * y + affine[5],
141 ]
142}
143
144fn cluster_affine(state: &ClusterState, pivot: [f32; 2]) -> Affine {
146 let (sine, cosine) = (libm::sinf(state.rotation), libm::cosf(state.rotation));
147 let [sx, sy] = state.scale;
148 let (a, b, c, d) = (cosine * sx, sine * sx, -sine * sy, cosine * sy);
150 let [px, py] = pivot;
151 [
152 a,
153 b,
154 c,
155 d,
156 px - a * px - c * py + state.offset[0],
157 py - b * px - d * py + state.offset[1],
158 ]
159}
160
161fn invert(affine: Affine) -> Option<Affine> {
162 let determinant = affine[0] * affine[3] - affine[1] * affine[2];
163 if determinant.abs() <= f32::MIN_POSITIVE {
164 return None;
165 }
166 let (a, b, c, d) = (
167 affine[3] / determinant,
168 -affine[1] / determinant,
169 -affine[2] / determinant,
170 affine[0] / determinant,
171 );
172 Some([
173 a,
174 b,
175 c,
176 d,
177 -(a * affine[4] + c * affine[5]),
178 -(b * affine[4] + d * affine[5]),
179 ])
180}
181
182struct Tile {
185 left: i64,
186 top: i64,
187 width: usize,
188 height: usize,
189 coverage: Vec<u8>,
190 colors: Option<Vec<u8>>,
192}
193
194impl Tile {
195 fn empty() -> Self {
196 Self {
197 left: 0,
198 top: 0,
199 width: 0,
200 height: 0,
201 coverage: Vec::new(),
202 colors: None,
203 }
204 }
205
206 fn grow(&mut self, bounds: PixelBox) {
207 if self.width == 0 || self.height == 0 {
208 *self = Self {
209 left: bounds.left,
210 top: bounds.top,
211 width: usize::try_from(bounds.right - bounds.left).unwrap_or(0),
212 height: usize::try_from(bounds.bottom - bounds.top).unwrap_or(0),
213 coverage: Vec::new(),
214 colors: None,
215 };
216 self.coverage = vec![0; self.width * self.height];
217 return;
218 }
219 let right = (self.left + i64::try_from(self.width).unwrap_or(0)).max(bounds.right);
220 let bottom = (self.top + i64::try_from(self.height).unwrap_or(0)).max(bounds.bottom);
221 let (left, top) = (self.left.min(bounds.left), self.top.min(bounds.top));
222 if left == self.left
223 && top == self.top
224 && right == self.left + i64::try_from(self.width).unwrap_or(0)
225 && bottom == self.top + i64::try_from(self.height).unwrap_or(0)
226 {
227 return;
228 }
229 let width = usize::try_from(right - left).unwrap_or(0);
230 let height = usize::try_from(bottom - top).unwrap_or(0);
231 let (dx, dy) = (
232 usize::try_from(self.left - left).unwrap_or(0),
233 usize::try_from(self.top - top).unwrap_or(0),
234 );
235 let mut coverage = vec![0; width * height];
236 let mut colors = self.colors.as_ref().map(|_| vec![0; width * height * 4]);
237 for row in 0..self.height {
238 let to = (row + dy) * width + dx;
239 coverage[to..to + self.width]
240 .copy_from_slice(&self.coverage[row * self.width..(row + 1) * self.width]);
241 if let (Some(colors), Some(held)) = (&mut colors, &self.colors) {
242 colors[to * 4..(to + self.width) * 4]
243 .copy_from_slice(&held[row * self.width * 4..(row + 1) * self.width * 4]);
244 }
245 }
246 *self = Self {
247 left,
248 top,
249 width,
250 height,
251 coverage,
252 colors,
253 };
254 }
255
256 fn index(&self, x: i64, y: i64) -> Option<usize> {
257 let (x, y) = (
258 usize::try_from(x - self.left).ok()?,
259 usize::try_from(y - self.top).ok()?,
260 );
261 (x < self.width && y < self.height).then_some(y * self.width + x)
262 }
263
264 fn bounds(&self) -> Option<PixelBox> {
265 (self.width > 0 && self.height > 0).then(|| PixelBox {
266 left: self.left,
267 top: self.top,
268 right: self.left + i64::try_from(self.width).unwrap_or(0),
269 bottom: self.top + i64::try_from(self.height).unwrap_or(0),
270 })
271 }
272}
273
274#[allow(clippy::cast_possible_truncation)]
276fn mapped_box(bounds: [f32; 4], origin: [f32; 2], scale: f32, affine: Affine) -> PixelBox {
277 let [x_min, y_min, x_max, y_max] = bounds;
278 let corners = [
279 [x_min, y_min],
280 [x_max, y_min],
281 [x_min, y_max],
282 [x_max, y_max],
283 ]
284 .map(|[x, y]| apply(affine, [origin[0] + x * scale, origin[1] - y * scale]));
285 let (mut left, mut top, mut right, mut bottom) = (f32::MAX, f32::MAX, f32::MIN, f32::MIN);
286 for [x, y] in corners {
287 (left, top, right, bottom) = (left.min(x), top.min(y), right.max(x), bottom.max(y));
288 }
289 PixelBox {
290 left: left.floor() as i64,
291 top: top.floor() as i64,
292 right: right.ceil() as i64,
293 bottom: bottom.ceil() as i64,
294 }
295}
296
297#[allow(
299 clippy::cast_possible_truncation,
300 clippy::cast_possible_wrap,
301 clippy::cast_sign_loss,
302 clippy::cast_precision_loss,
303 clippy::many_single_char_names
304)]
305fn resample(rgba8: &[u8], source: PixelBox, target: PixelBox, inverse: Affine) -> Vec<u8> {
306 let source_width = usize::try_from(source.right - source.left).unwrap_or(0);
307 let source_height = usize::try_from(source.bottom - source.top).unwrap_or(0);
308 let width = usize::try_from(target.right - target.left).unwrap_or(0);
309 let height = usize::try_from(target.bottom - target.top).unwrap_or(0);
310 let mut out = vec![0_u8; width * height * 4];
311 let texel = |x: i64, y: i64| -> [u32; 4] {
312 if x < 0 || y < 0 || x >= source_width as i64 || y >= source_height as i64 {
313 return [0; 4];
314 }
315 let at = (y as usize * source_width + x as usize) * 4;
316 std::array::from_fn(|channel| u32::from(rgba8[at + channel]))
317 };
318 for row in 0..height {
319 for column in 0..width {
320 let [x, y] = apply(
321 inverse,
322 [
323 (target.left + column as i64) as f32 + 0.5,
324 (target.top + row as i64) as f32 + 0.5,
325 ],
326 );
327 let (u, v) = (x - source.left as f32 - 0.5, y - source.top as f32 - 0.5);
329 let (u0, v0) = (u.floor(), v.floor());
330 let fx = ((u - u0) * 256.0) as u32;
331 let fy = ((v - v0) * 256.0) as u32;
332 let (u0, v0) = (u0 as i64, v0 as i64);
333 let [a, b, c, d] = [
334 texel(u0, v0),
335 texel(u0 + 1, v0),
336 texel(u0, v0 + 1),
337 texel(u0 + 1, v0 + 1),
338 ];
339 let at = (row * width + column) * 4;
340 for channel in 0..4 {
341 let top = a[channel] * (256 - fx) + b[channel] * fx;
342 let bottom = c[channel] * (256 - fx) + d[channel] * fx;
343 let value = (top * (256 - fy) + bottom * fy + 32_768) >> 16;
344 out[at + channel] = value.min(255) as u8;
345 }
346 }
347 }
348 out
349}
350
351fn mixed(color: [u8; 4], mix: &ColorMix) -> [u8; 4] {
352 let base = gradient::premultiplied_linear(color.map(|value| f32::from(value) / 255.0));
353 gradient::stored_rgba8(std::array::from_fn(|channel| {
354 base[channel] * mix.keep + mix.add[channel]
355 }))
356}
357
358struct ClusterLook<'a> {
360 base: RequestLook<'a>,
361 states: &'a [ClusterState],
362 fill_owner: Vec<u32>,
364 band_owner: Vec<Vec<u32>>,
365 color_owner: Vec<u32>,
367 solid: Vec<Option<[u8; 4]>>,
368 strokes: Vec<Vec<[u8; 4]>>,
369}
370
371impl PixelLook for ClusterLook<'_> {
372 fn fill(&self, index: usize, point: [f32; 2]) -> [u8; 4] {
373 let owner = self.fill_owner[index] as usize;
374 let Some(state) = self.states.get(owner) else {
375 return self.base.fill(index, point);
376 };
377 match (&self.base.gradient, state.fill) {
378 (Some(gradient), Some(mix)) => {
379 let base = gradient.linear_at(point);
380 gradient::stored_rgba8(std::array::from_fn(|channel| {
381 base[channel] * mix.keep + mix.add[channel]
382 }))
383 }
384 (None, Some(_)) => self.solid[owner].unwrap_or(self.base.foreground),
385 (_, None) => self.base.fill(index, point),
386 }
387 }
388
389 fn band(&self, stroke: usize, index: usize) -> [u8; 4] {
390 let owner = self.band_owner[stroke][index] as usize;
391 self.strokes[stroke]
392 .get(owner)
393 .copied()
394 .unwrap_or_else(|| self.base.band(stroke, index))
395 }
396
397 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
399 fn fade(&self, index: usize) -> Option<u32> {
400 let owner = [self.fill_owner[index], self.color_owner[index]]
401 .into_iter()
402 .chain(self.band_owner.iter().map(|owners| owners[index]))
403 .find(|&owner| owner != u32::MAX)?;
404 let opacity = (self.states.get(owner as usize)?.opacity * 255.0).round() as u32;
405 (opacity < 255).then_some(opacity)
406 }
407}
408
409fn place_max(
412 plane: (&mut [u8], &mut [u32], u32),
413 margin: [u32; 2],
414 tile: &Tile,
415 coverage: &[u8],
416 cluster: u32,
417) {
418 let (plane, owners, width) = plane;
419 let height = plane.len() / width.max(1) as usize;
420 for row in 0..tile.height {
421 let y = tile.top + i64::try_from(row).unwrap_or(0) + i64::from(margin[1]);
422 let Ok(y) = usize::try_from(y) else { continue };
423 if y >= height {
424 continue;
425 }
426 for column in 0..tile.width {
427 let value = coverage[row * tile.width + column];
428 if value == 0 {
429 continue;
430 }
431 let x = tile.left + i64::try_from(column).unwrap_or(0) + i64::from(margin[0]);
432 let Ok(x) = usize::try_from(x) else { continue };
433 if x >= width as usize {
434 continue;
435 }
436 let at = y * width as usize + x;
437 if value >= plane[at] {
438 plane[at] = value;
439 owners[at] = cluster;
440 }
441 }
442 }
443}
444
445impl TextRasterizer {
446 pub fn layout(&self, request: &TextRasterRequest) -> Result<TextLayout, TextRasterError> {
451 validate_request(request)?;
452 if self.fonts.is_empty() {
453 return Err(TextRasterError::NoFonts);
454 }
455 let faces = Faces::new(&self.fonts, request.font_weight)?;
456 let layout = self.lay_out(&faces, request)?;
457 Ok(text_layout(&layout, request))
458 }
459
460 pub fn rasterize_animated(
468 &mut self,
469 request: &TextRasterRequest,
470 states: &[ClusterState],
471 ) -> Result<Arc<TextRasterResult>, TextRasterError> {
472 validate_request(request)?;
473 if self.fonts.is_empty() {
474 return Err(TextRasterError::NoFonts);
475 }
476 if !states.iter().all(ClusterState::is_valid) {
477 return Err(TextRasterError::InvalidRequest);
478 }
479 if states.iter().all(ClusterState::is_identity) {
480 let result = self.rasterize(request)?;
481 let clusters = self.layout(request)?.clusters.len();
482 return if states.len() == clusters {
483 Ok(result)
484 } else {
485 Err(TextRasterError::InvalidRequest)
486 };
487 }
488 if let Some(cached) = self.cache.get(request.content_version) {
489 return Ok(cached);
490 }
491 let mut tiles = std::mem::take(&mut self.tiles);
492 let result = self.rasterize_states(request, states, &mut tiles);
493 self.tiles = tiles;
494 let result = Arc::new(result?);
495 self.cache.insert(request.content_version, &result);
496 Ok(result)
497 }
498
499 #[allow(
500 clippy::cast_possible_truncation,
501 clippy::cast_possible_wrap,
502 clippy::cast_precision_loss,
503 clippy::cast_sign_loss,
504 clippy::too_many_lines
505 )]
506 fn rasterize_states(
507 &self,
508 request: &TextRasterRequest,
509 states: &[ClusterState],
510 cache: &mut TileCache,
511 ) -> Result<TextRasterResult, TextRasterError> {
512 let faces = Faces::new(&self.fonts, request.font_weight)?;
513 let layout = self.lay_out(&faces, request)?;
514 if states.len() != layout.clusters.len() {
515 return Err(TextRasterError::InvalidRequest);
516 }
517 let (box_width, box_height) = (layout.box_width, layout.box_height);
518 let ascent = layout.ascent;
519 let pivot_drop = request.font_size * 0.3;
520 let mut placed = vec![(Vec::<Placed>::new(), [0.0_f32; 6]); states.len()];
521 let mut drawings = HashMap::<(usize, u16), Drawing>::new();
522 let (mut ink_ascent, mut ink_descent) = (layout.ascent, layout.descent);
523 for (index, line) in layout.lines.iter().enumerate() {
524 let baseline = layout.line_top(index) + ascent;
525 let mut shifts = Vec::with_capacity(line.glyphs.len());
527 let mut shift = 0.0_f32;
528 let mut previous: Option<usize> = None;
529 for glyph in &line.glyphs {
530 if let Some(before) = previous.filter(|&before| before != glyph.cluster) {
531 shift += states[before].tracking;
532 }
533 previous = Some(glyph.cluster);
534 shifts.push(shift);
535 }
536 let width = line.width + shift;
537 let offset = line_start(&layout, line.rtl, width, request);
538 let mut extents = HashMap::<usize, (f32, f32)>::new();
540 for (glyph, shift) in line.glyphs.iter().zip(&shifts) {
541 let left = glyph.x + shift + offset;
542 let extent = extents.entry(glyph.cluster).or_insert((left, left));
543 *extent = (extent.0.min(left), extent.1.max(left + glyph.advance));
544 }
545 for (glyph, shift) in line.glyphs.iter().zip(&shifts) {
546 let (left, right) = extents[&glyph.cluster];
547 let affine = cluster_affine(
548 &states[glyph.cluster],
549 [f32::midpoint(left, right), baseline - pivot_drop],
550 );
551 let drawing = drawings
552 .entry((glyph.font, glyph.glyph_index))
553 .or_insert_with(|| Drawing::of(&faces, glyph.font, glyph.glyph_index, request));
554 let Some(bounds) = drawing.bounds() else {
555 continue;
556 };
557 let scale = request.font_size / faces.units_per_em(glyph.font);
558 ink_ascent = ink_ascent.max(bounds[3] * scale + glyph.y_offset);
559 ink_descent = ink_descent.max(-(bounds[1] * scale + glyph.y_offset));
560 let slot = &mut placed[glyph.cluster];
561 slot.1 = affine;
562 slot.0.push(Placed {
563 key: (glyph.font, glyph.glyph_index),
564 origin: [
565 glyph.x + shift + glyph.x_offset + offset,
566 baseline - glyph.y_offset,
567 ],
568 scale,
569 });
570 }
571 }
572 let look_key = look_key(request);
575 let mut tiles = Vec::with_capacity(states.len());
576 for (glyphs, affine) in &placed {
577 let Some(first) = glyphs.first() else {
578 tiles.push(None);
579 continue;
580 };
581 let [x, y] = apply(*affine, first.origin);
582 let whole = [x.floor(), y.floor()];
583 let local = [
584 affine[0],
585 affine[1],
586 affine[2],
587 affine[3],
588 affine[4] - whole[0],
589 affine[5] - whole[1],
590 ];
591 let key = cluster_key(look_key, glyphs, local);
592 let drawn = if let Some(drawn) = cache.get(key) {
593 drawn
594 } else {
595 let drawn = Arc::new(draw_cluster(request, &drawings, glyphs, local));
596 cache.insert(key, &drawn);
597 drawn
598 };
599 tiles.push(Some((drawn, [whole[0] as i64, whole[1] as i64])));
600 }
601 let (outline, padding) = decoration_extents(request);
603 let (mut overflow_x, mut overflow_y) = (0_i64, 0_i64);
604 for bounds in tiles
605 .iter()
606 .flatten()
607 .filter_map(|(drawn, at)| drawn.tile.bounds().map(|bounds| bounds.moved(*at)))
608 {
609 overflow_x = overflow_x
610 .max(bounds.left.saturating_neg())
611 .max(bounds.right.saturating_sub(i64::from(box_width)));
612 overflow_y = overflow_y
613 .max(bounds.top.saturating_neg())
614 .max(bounds.bottom.saturating_sub(i64::from(box_height)));
615 }
616 let margin = |overflow: i64| -> Result<u32, TextRasterError> {
617 u32::try_from(overflow)
618 .map_err(|_| TextRasterError::TooLarge)?
619 .checked_add(outline)
620 .map(|margin| margin.max(outline.max(padding)))
621 .ok_or(TextRasterError::TooLarge)
622 };
623 let (margin_x, margin_y) = (margin(overflow_x)?, margin(overflow_y)?);
624 let width = margin_x
625 .checked_mul(2)
626 .and_then(|margins| box_width.checked_add(margins))
627 .ok_or(TextRasterError::TooLarge)?;
628 let height = margin_y
629 .checked_mul(2)
630 .and_then(|margins| box_height.checked_add(margins))
631 .ok_or(TextRasterError::TooLarge)?;
632 if width > 16_384 || height > 16_384 {
633 return Err(TextRasterError::TooLarge);
634 }
635 let pixel_count = width as usize * height as usize;
636 let margins = [margin_x, margin_y];
637 let mut coverage = vec![0_u8; pixel_count];
638 let mut fill_owner = vec![u32::MAX; pixel_count];
639 let mut color_owner = vec![u32::MAX; pixel_count];
640 let mut colors: Option<Vec<u8>> = None;
641 let mut bands = vec![vec![0_u8; pixel_count]; request.strokes.len()];
642 let mut band_owner = vec![vec![u32::MAX; pixel_count]; request.strokes.len()];
643 for (cluster, (drawn, at)) in tiles
644 .iter()
645 .enumerate()
646 .filter_map(|(cluster, tile)| tile.as_ref().map(|tile| (cluster, tile)))
647 {
648 let cluster = u32::try_from(cluster).map_err(|_| TextRasterError::TooLarge)?;
649 let tile = drawn.tile.moved(*at);
650 place_max(
651 (&mut coverage, &mut fill_owner, width),
652 margins,
653 &tile,
654 &drawn.tile.coverage,
655 cluster,
656 );
657 if let Some(held) = &drawn.tile.colors {
658 let plane = colors.get_or_insert_with(|| vec![0; pixel_count * 4]);
659 for row in 0..tile.height {
660 for column in 0..tile.width {
661 let source = &held[(row * tile.width + column) * 4..][..4];
662 if source[3] == 0 {
663 continue;
664 }
665 let x = tile.left + column as i64 + i64::from(margin_x);
666 let y = tile.top + row as i64 + i64::from(margin_y);
667 if x < 0 || y < 0 || x >= i64::from(width) || y >= i64::from(height) {
668 continue;
669 }
670 let pixel = y as usize * width as usize + x as usize;
671 color_owner[pixel] = cluster;
672 let at = pixel * 4;
673 let slot = &mut plane[at..at + 4];
674 let blended = over(
675 std::array::from_fn(|channel| u32::from(source[channel])),
676 std::array::from_fn(|channel| u32::from(slot[channel])),
677 );
678 for (byte, value) in slot.iter_mut().zip(blended) {
679 *byte = u8::try_from(value.min(255)).unwrap_or(u8::MAX);
680 }
681 }
682 }
683 }
684 let padded = drawn.padded.moved(*at);
685 for ((band, owners), dilated) in bands
686 .iter_mut()
687 .zip(band_owner.iter_mut())
688 .zip(&drawn.bands)
689 {
690 place_max((band, owners, width), margins, &padded, dilated, cluster);
691 }
692 }
693 let mut line_boxes = Vec::with_capacity(layout.lines.len());
694 for (index, line) in layout.lines.iter().enumerate() {
695 if line.width > 0.0 {
696 line_boxes.push((
697 layout.line_offset(line, request.align),
698 line.width,
699 layout.line_top(index),
700 ));
701 }
702 }
703 let base = RequestLook::of(request, [layout.width, layout.height]);
704 let solid = match &request.fill {
705 TextFill::Solid(color) => states
706 .iter()
707 .map(|state| state.fill.map(|mix| mixed(*color, &mix)))
708 .collect(),
709 TextFill::Gradient(_) => vec![None; states.len()],
710 };
711 let strokes = request
712 .strokes
713 .iter()
714 .map(|stroke| {
715 states
716 .iter()
717 .map(|state| {
718 state
719 .stroke
720 .map_or(stroke.color, |mix| mixed(stroke.color, &mix))
721 })
722 .collect()
723 })
724 .collect();
725 let look = ClusterLook {
726 base,
727 states,
728 fill_owner,
729 band_owner,
730 color_owner,
731 solid,
732 strokes,
733 };
734 let (rgba8, background) = super::compose(
735 request,
736 &coverage,
737 colors.as_deref(),
738 width,
739 margins,
740 &line_boxes,
741 &bands,
742 &look,
743 );
744 let texture = HostTexture {
745 content_version: request.content_version,
746 width,
747 height,
748 premultiplication: Premultiplication::Premultiplied,
749 color_space: RasterColorSpace::Srgb,
750 };
751 Ok(TextRasterResult {
752 descriptor: TextTexture {
753 texture: texture.clone(),
754 layout_x: margin_x as f32,
755 layout_y: margin_y as f32,
756 layout_width: layout.width,
757 layout_height: layout.height,
758 first_baseline: layout.half_leading + layout.ascent,
759 ascent: ink_ascent,
760 descent: ink_descent,
761 line_count: layout.line_count,
762 },
763 pixels: HostTexturePixels { texture, rgba8 },
764 background,
765 })
766 }
767}
768
769#[derive(Clone, Debug)]
771struct Placed {
772 key: (usize, u16),
773 origin: [f32; 2],
774 scale: f32,
775}
776
777struct DrawnCluster {
779 tile: Tile,
780 padded: Tile,
781 bands: Vec<Vec<u8>>,
782}
783
784impl Tile {
785 fn moved(&self, [x, y]: [i64; 2]) -> Self {
786 Self {
787 left: self.left + x,
788 top: self.top + y,
789 width: self.width,
790 height: self.height,
791 coverage: Vec::new(),
792 colors: None,
793 }
794 }
795}
796
797impl PixelBox {
798 fn moved(self, [x, y]: [i64; 2]) -> Self {
799 Self {
800 left: self.left + x,
801 top: self.top + y,
802 right: self.right + x,
803 bottom: self.bottom + y,
804 }
805 }
806}
807
808#[derive(Debug, Default)]
810pub(crate) struct TileCache {
811 entries: HashMap<u64, (Arc<DrawnCluster>, u64)>,
812 next_use: u64,
813}
814
815impl std::fmt::Debug for DrawnCluster {
816 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
817 formatter
818 .debug_struct("DrawnCluster")
819 .field("width", &self.tile.width)
820 .field("height", &self.tile.height)
821 .finish_non_exhaustive()
822 }
823}
824
825const MAX_CACHED_CLUSTERS: usize = 4_096;
826
827impl TileCache {
828 fn get(&mut self, key: u64) -> Option<Arc<DrawnCluster>> {
829 self.next_use += 1;
830 let use_now = self.next_use;
831 self.entries.get_mut(&key).map(|(drawn, used)| {
832 *used = use_now;
833 Arc::clone(drawn)
834 })
835 }
836
837 fn insert(&mut self, key: u64, drawn: &Arc<DrawnCluster>) {
838 if self.entries.len() >= MAX_CACHED_CLUSTERS {
839 let mut uses = self
841 .entries
842 .values()
843 .map(|(_, used)| *used)
844 .collect::<Vec<_>>();
845 uses.sort_unstable();
846 let cut = uses[uses.len() / 2];
847 self.entries.retain(|_, (_, used)| *used > cut);
848 }
849 self.entries.insert(key, (Arc::clone(drawn), self.next_use));
850 }
851
852 pub(crate) fn clear(&mut self) {
853 self.entries.clear();
854 }
855}
856
857fn look_key(request: &TextRasterRequest) -> u64 {
859 let mut hash = Fnv::default();
860 hash.feed(&request.font_weight.to_le_bytes());
861 hash.feed(&[request.emoji_policy as u8]);
862 hash.feed(&request.fill.foreground());
863 for stroke in &request.strokes {
864 hash.feed(&stroke.width.to_bits().to_le_bytes());
865 }
866 hash.0
867}
868
869fn cluster_key(look: u64, glyphs: &[Placed], affine: Affine) -> u64 {
870 let mut hash = Fnv(look);
871 let first = glyphs.first().map_or([0.0; 2], |glyph| glyph.origin);
872 for glyph in glyphs {
873 hash.feed(&glyph.key.0.to_le_bytes());
874 hash.feed(&glyph.key.1.to_le_bytes());
875 hash.feed(&glyph.scale.to_bits().to_le_bytes());
876 for value in [glyph.origin[0] - first[0], glyph.origin[1] - first[1]] {
877 hash.feed(&value.to_bits().to_le_bytes());
878 }
879 }
880 let [x, y] = apply(affine, first);
882 for value in [affine[0], affine[1], affine[2], affine[3], x, y] {
883 hash.feed(&value.to_bits().to_le_bytes());
884 }
885 hash.0
886}
887
888struct Fnv(u64);
889
890impl Default for Fnv {
891 fn default() -> Self {
892 Self(0xcbf2_9ce4_8422_2325)
893 }
894}
895
896impl Fnv {
897 fn feed(&mut self, bytes: &[u8]) {
898 for byte in bytes {
899 self.0 = (self.0 ^ u64::from(*byte)).wrapping_mul(0x0000_0100_0000_01b3);
900 }
901 }
902}
903
904#[allow(
905 clippy::cast_possible_truncation,
906 clippy::cast_possible_wrap,
907 clippy::cast_precision_loss,
908 clippy::cast_sign_loss,
909 clippy::too_many_lines
910)]
911fn draw_cluster(
912 request: &TextRasterRequest,
913 drawings: &HashMap<(usize, u16), Drawing>,
914 glyphs: &[Placed],
915 affine: Affine,
916) -> DrawnCluster {
917 let mut tile = Tile::empty();
918 for glyph in glyphs {
919 let drawing = &drawings[&glyph.key];
920 let Some(bounds) = drawing.bounds() else {
921 continue;
922 };
923 let (origin, scale) = (glyph.origin, glyph.scale);
924 let target = mapped_box(bounds, origin, scale, affine);
925 if target.right <= target.left || target.bottom <= target.top {
926 continue;
927 }
928 tile.grow(target);
929 let width = usize::try_from(target.right - target.left).unwrap_or(0);
930 match drawing {
931 Drawing::Outline(outline) => {
932 let height = usize::try_from(target.bottom - target.top).unwrap_or(0);
933 let (left, top) = (target.left as f32, target.top as f32);
934 let raster = outline.raster(width, height, |[x, y]| {
935 let [x, y] = apply(affine, [origin[0] + x * scale, origin[1] - y * scale]);
936 point(x - left, y - top)
937 });
938 raster.for_each_pixel(|at, coverage| {
939 let value = (coverage.min(1.0) * 255.0).round() as u8;
940 let x = target.left + (at % width) as i64;
941 let y = target.top + (at / width) as i64;
942 if let Some(slot) = tile.index(x, y) {
943 tile.coverage[slot] = tile.coverage[slot].max(value);
944 }
945 });
946 }
947 drawing => {
948 let Some(inverse) = invert(affine) else {
949 continue;
950 };
951 let upright = PixelBox::of(bounds, origin, scale);
952 let rgba8 = resample(
953 &drawing.draw_color(origin, scale, upright),
954 upright,
955 target,
956 inverse,
957 );
958 let size = tile.width * tile.height * 4;
959 let slots = (0..rgba8.len() / 4)
960 .map(|at| {
961 tile.index(
962 target.left + (at % width) as i64,
963 target.top + (at / width) as i64,
964 )
965 })
966 .collect::<Vec<_>>();
967 let colors = tile.colors.get_or_insert_with(|| vec![0; size]);
968 for (source, slot) in rgba8.as_chunks::<4>().0.iter().zip(slots) {
969 let Some(slot) = slot.filter(|_| source[3] > 0) else {
970 continue;
971 };
972 let held = &mut colors[slot * 4..slot * 4 + 4];
973 let blended = over(
974 source.map(u32::from),
975 std::array::from_fn(|channel| u32::from(held[channel])),
976 );
977 for (byte, value) in held.iter_mut().zip(blended) {
978 *byte = u8::try_from(value.min(255)).unwrap_or(u8::MAX);
979 }
980 }
981 }
982 }
983 }
984 let reach = stroke_radius(&request.strokes).map_or(0, disc_extent) as usize;
986 let padded = Tile {
987 left: tile.left - reach as i64,
988 top: tile.top - reach as i64,
989 width: tile.width + 2 * reach,
990 height: tile.height + 2 * reach,
991 coverage: Vec::new(),
992 colors: None,
993 };
994 let mut bands = Vec::with_capacity(request.strokes.len());
995 if !request.strokes.is_empty() && tile.width > 0 && tile.height > 0 {
996 let mut ink = vec![0_u8; padded.width * padded.height];
997 for row in 0..tile.height {
998 for column in 0..tile.width {
999 let at = row * tile.width + column;
1000 let alpha = tile.colors.as_ref().map_or(0, |held| held[at * 4 + 3]);
1001 ink[(row + reach) * padded.width + column + reach] = tile.coverage[at].max(alpha);
1002 }
1003 }
1004 let mut radius = 0.0;
1005 for stroke in &request.strokes {
1006 radius += stroke.width;
1007 bands.push(dilate(
1008 &ink,
1009 u32::try_from(padded.width).unwrap_or(u32::MAX),
1010 u32::try_from(padded.height).unwrap_or(u32::MAX),
1011 radius,
1012 ));
1013 }
1014 }
1015 DrawnCluster {
1016 tile,
1017 padded,
1018 bands,
1019 }
1020}
1021
1022fn line_start(layout: &Layout, rtl: bool, width: f32, request: &TextRasterRequest) -> f32 {
1024 use alpha2_media::TextAlign;
1025 match (request.align, rtl) {
1026 (TextAlign::Center, _) => (layout.width - width) * 0.5,
1027 (TextAlign::Start, false) | (TextAlign::End, true) => 0.0,
1028 (TextAlign::End, false) | (TextAlign::Start, true) => layout.width - width,
1029 }
1030}
1031
1032fn text_layout(layout: &Layout, request: &TextRasterRequest) -> TextLayout {
1033 let mut map = Vec::new();
1034 normalize_mapped(&request.text, request.emoji_policy, Some(&mut map));
1035 let original = |at: usize| map.get(at).copied().unwrap_or(request.text.len());
1036 let words = request
1037 .text
1038 .split_word_bound_indices()
1039 .filter(|(_, word)| word.chars().any(char::is_alphanumeric))
1040 .map(|(start, word)| start..start + word.len())
1041 .collect::<Vec<_>>();
1042 let mut clusters = layout
1043 .clusters
1044 .iter()
1045 .map(|span| {
1046 let bytes = original(span.bytes.start)..original(span.bytes.end);
1047 let text = &request.text[bytes.clone()];
1048 let word = words
1049 .partition_point(|word| word.end <= bytes.start)
1050 .try_into()
1051 .ok()
1052 .filter(|&index: &u32| {
1053 words
1054 .get(index as usize)
1055 .is_some_and(|word| word.contains(&bytes.start))
1056 });
1057 ClusterInfo {
1058 is_space: !text.is_empty() && text.chars().all(char::is_whitespace),
1059 bytes,
1060 line: u32::try_from(span.line).unwrap_or(u32::MAX),
1061 word,
1062 x: 0.0,
1063 advance: 0.0,
1064 baseline: 0.0,
1065 }
1066 })
1067 .collect::<Vec<_>>();
1068 let mut lines = Vec::with_capacity(layout.lines.len());
1069 let mut first_cluster = 0;
1070 for (index, line) in layout.lines.iter().enumerate() {
1071 let left = layout.line_offset(line, request.align);
1072 let baseline = layout.line_top(index) + layout.ascent;
1073 let line_number = u32::try_from(index).unwrap_or(u32::MAX);
1074 let last_cluster = clusters[first_cluster..]
1075 .iter()
1076 .position(|cluster| cluster.line != line_number)
1077 .map_or(clusters.len(), |count| first_cluster + count);
1078 let mut extents = HashMap::<usize, (f32, f32)>::new();
1079 for glyph in &line.glyphs {
1080 let start = glyph.x + left;
1081 let extent = extents.entry(glyph.cluster).or_insert((start, start));
1082 *extent = (extent.0.min(start), extent.1.max(start + glyph.advance));
1083 }
1084 for (cluster, info) in clusters[first_cluster..last_cluster].iter_mut().enumerate() {
1085 let (start, end) = extents
1086 .get(&(first_cluster + cluster))
1087 .copied()
1088 .unwrap_or((left + line.width, left + line.width));
1089 info.x = start;
1090 info.advance = end - start;
1091 info.baseline = baseline;
1092 }
1093 lines.push(LineInfo {
1094 clusters: first_cluster..last_cluster,
1095 left,
1096 width: line.width,
1097 baseline,
1098 });
1099 first_cluster = last_cluster;
1100 }
1101 TextLayout {
1102 clusters,
1103 lines,
1104 font_size: request.font_size,
1105 }
1106}