| 1 | use alloc::vec; |
| 2 | use ttf_parser::colr::{ClipBox, CompositeMode, Paint}; |
| 3 | use ttf_parser::{GlyphId, RectF, Transform}; |
| 4 | |
| 5 | /* |
| 6 | * This file implements bounds-extraction as well as boundedness |
| 7 | * computation of COLRv1 fonts as described in: |
| 8 | * |
| 9 | * https://learn.microsoft.com/en-us/typography/opentype/spec/colr#glyph-metrics-and-boundedness |
| 10 | */ |
| 11 | |
| 12 | #[derive (Copy, Clone)] |
| 13 | pub(crate) struct hb_extents_t { |
| 14 | pub x_min: f32, |
| 15 | pub y_min: f32, |
| 16 | pub x_max: f32, |
| 17 | pub y_max: f32, |
| 18 | } |
| 19 | |
| 20 | impl hb_extents_t { |
| 21 | pub fn is_empty(&self) -> bool { |
| 22 | self.x_min >= self.x_max || self.y_min >= self.y_max |
| 23 | } |
| 24 | pub fn is_void(&self) -> bool { |
| 25 | self.x_min > self.x_max |
| 26 | } |
| 27 | |
| 28 | pub fn union_(&mut self, o: &hb_extents_t) { |
| 29 | self.x_min = o.x_min.min(o.x_min); |
| 30 | self.y_min = o.y_min.min(o.y_min); |
| 31 | self.x_max = o.x_max.max(o.x_max); |
| 32 | self.y_max = o.y_max.max(o.y_max); |
| 33 | } |
| 34 | |
| 35 | pub fn intersect(&mut self, o: &hb_extents_t) { |
| 36 | self.x_min = o.x_min.max(o.x_min); |
| 37 | self.y_min = o.y_min.max(o.y_min); |
| 38 | self.x_max = o.x_max.min(o.x_max); |
| 39 | self.y_max = o.y_max.min(o.y_max); |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | impl From<RectF> for hb_extents_t { |
| 44 | fn from(val: RectF) -> Self { |
| 45 | Self { |
| 46 | x_min: val.x_min, |
| 47 | y_min: val.y_min, |
| 48 | x_max: val.x_max, |
| 49 | y_max: val.y_max, |
| 50 | } |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | #[derive (PartialEq, Eq, Clone, Copy)] |
| 55 | enum status_t { |
| 56 | EMPTY, |
| 57 | BOUNDED, |
| 58 | UNBOUNDED, |
| 59 | } |
| 60 | |
| 61 | #[derive (Clone, Copy)] |
| 62 | pub(crate) struct hb_bounds_t { |
| 63 | status: status_t, |
| 64 | extents: hb_extents_t, |
| 65 | } |
| 66 | |
| 67 | impl hb_bounds_t { |
| 68 | fn from_extents(extents: &hb_extents_t) -> Self { |
| 69 | let status = if extents.is_empty() { |
| 70 | status_t::EMPTY |
| 71 | } else { |
| 72 | status_t::BOUNDED |
| 73 | }; |
| 74 | |
| 75 | hb_bounds_t { |
| 76 | extents: *extents, |
| 77 | status, |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | fn from_status(status: status_t) -> Self { |
| 82 | hb_bounds_t { |
| 83 | status, |
| 84 | ..hb_bounds_t::default() |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | fn union(&mut self, o: &hb_bounds_t) { |
| 89 | if o.status == status_t::UNBOUNDED { |
| 90 | self.status = status_t::UNBOUNDED; |
| 91 | } else if o.status == status_t::BOUNDED { |
| 92 | if self.status == status_t::EMPTY { |
| 93 | *self = *o; |
| 94 | } else if self.status == status_t::BOUNDED { |
| 95 | self.extents.union_(&o.extents); |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | fn intersect(&mut self, o: &hb_bounds_t) { |
| 101 | if o.status == status_t::EMPTY { |
| 102 | self.status = status_t::EMPTY; |
| 103 | } else if o.status == status_t::BOUNDED { |
| 104 | if self.status == status_t::UNBOUNDED { |
| 105 | *self = *o; |
| 106 | } else if self.status == status_t::BOUNDED { |
| 107 | self.extents.intersect(&o.extents); |
| 108 | |
| 109 | if self.extents.is_empty() { |
| 110 | self.status = status_t::EMPTY; |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | impl Default for hb_bounds_t { |
| 118 | fn default() -> Self { |
| 119 | Self::from_extents(&hb_extents_t { |
| 120 | x_min: 0.0, |
| 121 | x_max: 0.0, |
| 122 | y_min: 0.0, |
| 123 | y_max: 0.0, |
| 124 | }) |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | pub(crate) struct hb_paint_extents_context_t<'a> { |
| 129 | clips: vec::Vec<hb_bounds_t>, |
| 130 | groups: vec::Vec<hb_bounds_t>, |
| 131 | transforms: vec::Vec<Transform>, |
| 132 | // Doesn't exist in harfbuzz. The reason we need it is that in harfbuzz, composite modes |
| 133 | // are passed as part of `pop`, while ttf-parser passes it as part of `push`, so we need to |
| 134 | // store it in the meanwhile. |
| 135 | composite_modes: vec::Vec<CompositeMode>, |
| 136 | face: &'a ttf_parser::Face<'a>, |
| 137 | current_glyph: GlyphId, |
| 138 | } |
| 139 | |
| 140 | impl<'a> hb_paint_extents_context_t<'a> { |
| 141 | pub(crate) fn new(face: &'a ttf_parser::Face<'a>) -> Self { |
| 142 | Self { |
| 143 | clips: vec![hb_bounds_t::from_status(status_t::UNBOUNDED)], |
| 144 | groups: vec![hb_bounds_t::from_status(status_t::EMPTY)], |
| 145 | transforms: vec![Transform::default()], |
| 146 | composite_modes: vec![CompositeMode::SourceOver], |
| 147 | face, |
| 148 | current_glyph: Default::default(), |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | pub(crate) fn get_extents(&self) -> hb_extents_t { |
| 153 | // harfbuzz doesn't have the unwrap_or_default part, but in a valid font |
| 154 | // this should always be valid anyway. |
| 155 | self.groups.last().copied().unwrap_or_default().extents |
| 156 | } |
| 157 | |
| 158 | fn push_transform(&mut self, trans: &Transform) { |
| 159 | let t = self |
| 160 | .transforms |
| 161 | .last() |
| 162 | .copied() |
| 163 | .unwrap_or(Transform::default()); |
| 164 | let new = Transform::combine(t, *trans); |
| 165 | self.transforms.push(new); |
| 166 | } |
| 167 | |
| 168 | fn pop_transform(&mut self) { |
| 169 | self.transforms.pop(); |
| 170 | } |
| 171 | |
| 172 | fn push_clip(&mut self, mut extents: hb_extents_t) { |
| 173 | if let Some(r) = self.transforms.last_mut() { |
| 174 | r.transform_extents(&mut extents); |
| 175 | } |
| 176 | |
| 177 | let b = hb_bounds_t::from_extents(&extents); |
| 178 | self.clips.push(b); |
| 179 | } |
| 180 | |
| 181 | fn pop_clip(&mut self) { |
| 182 | self.clips.pop(); |
| 183 | } |
| 184 | |
| 185 | fn push_group(&mut self) { |
| 186 | self.groups.push(hb_bounds_t::default()); |
| 187 | } |
| 188 | |
| 189 | fn pop_group(&mut self) { |
| 190 | if let Some(mode) = self.composite_modes.pop() { |
| 191 | if let Some(src_bounds) = self.groups.pop() { |
| 192 | if let Some(backdrop_bounds) = self.groups.last_mut() { |
| 193 | match mode { |
| 194 | CompositeMode::Clear => backdrop_bounds.status = status_t::EMPTY, |
| 195 | CompositeMode::Source | CompositeMode::SourceOut => { |
| 196 | *backdrop_bounds = src_bounds |
| 197 | } |
| 198 | CompositeMode::Destination | CompositeMode::DestinationOut => {} |
| 199 | CompositeMode::SourceIn | CompositeMode::DestinationIn => { |
| 200 | backdrop_bounds.intersect(&src_bounds) |
| 201 | } |
| 202 | _ => backdrop_bounds.union(&src_bounds), |
| 203 | } |
| 204 | } |
| 205 | } |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | fn paint(&mut self) { |
| 210 | if let (Some(clip), Some(group)) = (self.clips.last(), self.groups.last_mut()) { |
| 211 | group.union(clip); |
| 212 | } |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | impl ttf_parser::colr::Painter<'_> for hb_paint_extents_context_t<'_> { |
| 217 | fn outline_glyph(&mut self, glyph_id: GlyphId) { |
| 218 | self.current_glyph = glyph_id; |
| 219 | } |
| 220 | |
| 221 | fn paint(&mut self, _: Paint<'_>) { |
| 222 | self.paint(); |
| 223 | } |
| 224 | |
| 225 | fn push_clip(&mut self) { |
| 226 | if let Some(glyph_bbox) = self.face.glyph_bounding_box(self.current_glyph) { |
| 227 | self.push_clip(hb_extents_t { |
| 228 | x_min: glyph_bbox.x_min as f32, |
| 229 | y_min: glyph_bbox.y_min as f32, |
| 230 | x_max: glyph_bbox.x_max as f32, |
| 231 | y_max: glyph_bbox.y_max as f32, |
| 232 | }); |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | fn push_clip_box(&mut self, clipbox: ClipBox) { |
| 237 | self.push_clip(clipbox.into()); |
| 238 | } |
| 239 | |
| 240 | fn pop_clip(&mut self) { |
| 241 | self.pop_clip(); |
| 242 | } |
| 243 | |
| 244 | fn push_layer(&mut self, mode: CompositeMode) { |
| 245 | self.composite_modes.push(mode); |
| 246 | self.push_group(); |
| 247 | } |
| 248 | |
| 249 | fn pop_layer(&mut self) { |
| 250 | self.pop_group(); |
| 251 | } |
| 252 | |
| 253 | fn push_transform(&mut self, transform: Transform) { |
| 254 | self.push_transform(&transform); |
| 255 | } |
| 256 | |
| 257 | fn pop_transform(&mut self) { |
| 258 | self.pop_transform(); |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | trait TransformExt { |
| 263 | fn transform_distance(&self, dx: &mut f32, dy: &mut f32); |
| 264 | fn transform_point(&self, x: &mut f32, y: &mut f32); |
| 265 | fn transform_extents(&self, extents: &mut hb_extents_t); |
| 266 | } |
| 267 | |
| 268 | impl TransformExt for Transform { |
| 269 | fn transform_distance(&self, dx: &mut f32, dy: &mut f32) { |
| 270 | let new_x = self.a * (*dx) + self.c * (*dy); |
| 271 | let new_y = self.b * (*dx) + self.d * (*dy); |
| 272 | *dx = new_x; |
| 273 | *dy = new_y; |
| 274 | } |
| 275 | |
| 276 | fn transform_point(&self, x: &mut f32, y: &mut f32) { |
| 277 | self.transform_distance(x, y); |
| 278 | *x += self.e; |
| 279 | *y += self.f; |
| 280 | } |
| 281 | |
| 282 | fn transform_extents(&self, extents: &mut hb_extents_t) { |
| 283 | let mut quad_x = [0.0f32; 4]; |
| 284 | let mut quad_y = [0.0f32; 4]; |
| 285 | |
| 286 | quad_x[0] = extents.x_min; |
| 287 | quad_y[0] = extents.y_min; |
| 288 | quad_x[1] = extents.x_min; |
| 289 | quad_y[1] = extents.y_max; |
| 290 | quad_x[2] = extents.x_max; |
| 291 | quad_y[2] = extents.y_min; |
| 292 | quad_x[3] = extents.x_max; |
| 293 | quad_y[3] = extents.y_max; |
| 294 | |
| 295 | for i in 0..4 { |
| 296 | self.transform_point(&mut quad_x[i], &mut quad_y[i]) |
| 297 | } |
| 298 | |
| 299 | extents.x_max = quad_x[0]; |
| 300 | extents.x_min = extents.x_max; |
| 301 | extents.y_max = quad_y[0]; |
| 302 | extents.y_min = extents.y_max; |
| 303 | |
| 304 | for i in 1..4 { |
| 305 | extents.x_min = extents.x_min.min(quad_x[i]); |
| 306 | extents.y_min = extents.y_min.min(quad_y[i]); |
| 307 | extents.x_max = extents.x_max.max(quad_x[i]); |
| 308 | extents.y_max = extents.y_max.max(quad_y[i]); |
| 309 | } |
| 310 | } |
| 311 | } |
| 312 | |