| 1 | use crate::RenderError; |
| 2 | use parley::fontique::Blob; |
| 3 | use std::ops::Range; |
| 4 | |
| 5 | /// Text the renderer can paint, as glyph runs in layer units. Owners recover their |
| 6 | /// concrete text from a primitive through `Any`. |
| 7 | pub trait Glyphs: std::any::Any { |
| 8 | fn runs( |
| 9 | &self, |
| 10 | paint: &mut dyn FnMut(GlyphRun<'_>) -> Result<(), RenderError>, |
| 11 | ) -> Result<(), RenderError>; |
| 12 | } |
| 13 | |
| 14 | /// Glyphs sharing one font, size and colour. |
| 15 | pub struct GlyphRun<'a> { |
| 16 | pub font: &'a Blob<u8>, |
| 17 | /// The face within a font collection. |
| 18 | pub index: u32, |
| 19 | pub size: f32, |
| 20 | /// Variation coordinates in F2Dot14 bits. |
| 21 | pub coords: &'a [i16], |
| 22 | pub embolden: bool, |
| 23 | /// Synthesized oblique angle in degrees. |
| 24 | pub skew: Option<f32>, |
| 25 | /// `None` paints in the primitive's ink. |
| 26 | pub color: Option<[f32; 4]>, |
| 27 | /// The colour painted behind the glyphs, such as a highlight; `None` for the layer's |
| 28 | /// backdrop. |
| 29 | pub backdrop: Option<[f32; 4]>, |
| 30 | /// Top and height of the run's line: lines outside the target are skipped, and |
| 31 | /// colour glyphs shrink to fit. |
| 32 | pub line: [f32; 2], |
| 33 | pub glyphs: &'a mut dyn Iterator<Item = Glyph>, |
| 34 | /// Underline and strikethrough, painted after the glyphs. |
| 35 | pub decorations: &'a [Decoration], |
| 36 | /// The source the glyphs show, for a document's text layer; the renderer ignores it. |
| 37 | pub text: &'a str, |
| 38 | /// Each cluster's bytes of `text` and how many glyphs show it, in `glyphs`' order; a |
| 39 | /// ligature's later clusters take none. |
| 40 | pub clusters: &'a mut dyn Iterator<Item = (Range<usize>, usize)>, |
| 41 | } |
| 42 | |
| 43 | /// A glyph's baseline position. |
| 44 | #[derive(Clone, Copy)] |
| 45 | pub struct Glyph { |
| 46 | pub id: u32, |
| 47 | pub x: f32, |
| 48 | pub y: f32, |
| 49 | } |
| 50 | |
| 51 | /// A decoration line whose top edge is `y`; it stays at least one device pixel thick. |
| 52 | #[derive(Clone, Copy)] |
| 53 | pub struct Decoration { |
| 54 | pub x: f32, |
| 55 | pub y: f32, |
| 56 | pub width: f32, |
| 57 | pub thickness: f32, |
| 58 | /// `None` paints in the primitive's ink. |
| 59 | pub color: Option<[f32; 4]>, |
| 60 | } |
| 61 | |
| 62 | /// Paints one parley glyph run of `text` laid out, with its line's baseline moved to |
| 63 | /// `baseline`, its glyphs raised by `rise` and `backdrop` behind them. |
| 64 | #[allow(clippy::too_many_arguments)] |
| 65 | pub fn paint_parley_run<B: parley::Brush>( |
| 66 | run: &parley::GlyphRun<'_, B>, |
| 67 | text: &str, |
| 68 | baseline: f32, |
| 69 | rise: f32, |
| 70 | line: [f32; 2], |
| 71 | color: impl Fn(&B) -> Option<[f32; 4]>, |
| 72 | backdrop: Option<[f32; 4]>, |
| 73 | paint: &mut dyn FnMut(GlyphRun<'_>) -> Result<(), RenderError>, |
| 74 | ) -> Result<(), RenderError> { |
| 75 | let shaped = run.run(); |
| 76 | let font = &shaped.font().font; |
| 77 | let coords: Vec<i16> = shaped |
| 78 | .normalized_coords() |
| 79 | .iter() |
| 80 | .map(|c| c.to_bits()) |
| 81 | .collect(); |
| 82 | let synthesis = shaped.synthesis(); |
| 83 | let style = run.style(); |
| 84 | let metrics = shaped.font_metrics(); |
| 85 | let decorations: Vec<_> = [ |
| 86 | ( |
| 87 | &style.underline, |
| 88 | metrics.underline_offset, |
| 89 | metrics.underline_size, |
| 90 | ), |
| 91 | ( |
| 92 | &style.strikethrough, |
| 93 | metrics.strikethrough_offset, |
| 94 | metrics.strikethrough_size, |
| 95 | ), |
| 96 | ] |
| 97 | .into_iter() |
| 98 | .filter_map(|(decoration, offset, thickness)| { |
| 99 | let decoration = decoration.as_ref()?; |
| 100 | Some(Decoration { |
| 101 | x: run.offset(), |
| 102 | y: baseline - rise - decoration.offset.unwrap_or(offset), |
| 103 | width: run.advance(), |
| 104 | thickness: decoration.size.unwrap_or(thickness), |
| 105 | color: color(&decoration.brush), |
| 106 | }) |
| 107 | }) |
| 108 | .collect(); |
| 109 | let line_baseline = run.baseline(); |
| 110 | let mut glyphs = run.positioned_glyphs().map(|glyph| Glyph { |
| 111 | id: glyph.id, |
| 112 | x: glyph.x, |
| 113 | y: glyph.y + baseline - line_baseline - rise, |
| 114 | }); |
| 115 | // The run's clusters are those of its style within its span of the line; walked only |
| 116 | // when a text layer asks. |
| 117 | let [start, end] = [run.offset(), run.offset() + run.advance()]; |
| 118 | let mut at = None; |
| 119 | let mut clusters = run.run().visual_clusters().filter_map(|cluster| { |
| 120 | let x = *at.get_or_insert_with(|| cluster.visual_offset().unwrap_or(start)); |
| 121 | at = Some(x + cluster.advance()); |
| 122 | let within = x > start - 0.01 && x < end - 0.01; |
| 123 | (cluster.style_index() == run.style_index() && within) |
| 124 | .then(|| (cluster.text_range(), cluster.glyphs().count())) |
| 125 | }); |
| 126 | paint(GlyphRun { |
| 127 | font: &font.data, |
| 128 | index: font.index, |
| 129 | size: shaped.font_size(), |
| 130 | coords: &coords, |
| 131 | embolden: synthesis.embolden(), |
| 132 | skew: synthesis.skew(), |
| 133 | color: color(&style.brush), |
| 134 | backdrop, |
| 135 | line, |
| 136 | glyphs: &mut glyphs, |
| 137 | decorations: &decorations, |
| 138 | text, |
| 139 | clusters: &mut clusters, |
| 140 | }) |
| 141 | } |