skrifa/outline/glyf/hint/engine/data.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
//! Reading and writing data.
//!
//! Implements 7 instructions.
//!
//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#reading-and-writing-data>
use super::{
super::{math, zone::ZonePointer},
Engine, OpResult,
};
impl<'a> Engine<'a> {
/// Get coordinate project in to the projection vector.
///
/// GC\[a\] (0x46 - 0x47)
///
/// a: 0: use current position of point p
/// 1: use the position of point p in the original outline
///
/// Pops: p: point number
/// Pushes: value: coordinate location (F26Dot6)
///
/// Measures the coordinate value of point p on the current
/// projection_vector and pushes the value onto the stack.
///
/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#get-coordinate-projected-onto-the-projection_vector>
/// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4512>
pub(super) fn op_gc(&mut self, opcode: u8) -> OpResult {
let p = self.value_stack.pop_usize()?;
let gs = &mut self.graphics;
if !gs.is_pedantic && !gs.in_bounds([(gs.zp2, p)]) {
self.value_stack.push(0)?;
return Ok(());
}
let value = if (opcode & 1) != 0 {
gs.dual_project(gs.zp2().original(p)?, Default::default())
} else {
gs.project(gs.zp2().point(p)?, Default::default())
};
self.value_stack.push(value.to_bits())?;
Ok(())
}
/// Set coordinate from the stack using projection vector and freedom
/// vector.
///
/// SCFS[] (0x48)
///
/// Pops: value: distance from origin to move point (F26Dot6)
/// p: point number
///
/// Moves point p from its current position along the freedom_vector so
/// that its component along the projection_vector becomes the value popped
/// off the stack.
///
/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#sets-coordinate-from-the-stack-using-projection_vector-and-freedom_vector>
/// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4550>
pub(super) fn op_scfs(&mut self) -> OpResult {
let value = self.value_stack.pop_f26dot6()?;
let p = self.value_stack.pop_usize()?;
let gs = &mut self.graphics;
let projection = gs.project(gs.zp2().point(p)?, Default::default());
gs.move_point(gs.zp2, p, value.wrapping_sub(projection))?;
if gs.zp2.is_twilight() {
let twilight = gs.zone_mut(ZonePointer::Twilight);
*twilight.original_mut(p)? = twilight.point(p)?;
}
Ok(())
}
/// Measure distance.
///
/// MD\[a\] (0x46 - 0x47)
///
/// a: 0: measure distance in grid-fitted outline
/// 1: measure distance in original outline
///
/// Pops: p1: point number
/// p2: point number
/// Pushes: distance (F26Dot6)
///
/// Measures the distance between outline point p1 and outline point p2.
/// The value returned is in pixels (F26Dot6) If distance is negative, it
/// was measured against the projection vector. Reversing the order in
/// which the points are listed will change the sign of the result.
///
/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#measure-distance>
/// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4593>
pub(super) fn op_md(&mut self, opcode: u8) -> OpResult {
let p1 = self.value_stack.pop_usize()?;
let p2 = self.value_stack.pop_usize()?;
let gs = &self.graphics;
if !gs.is_pedantic && !gs.in_bounds([(gs.zp0, p2), (gs.zp1, p1)]) {
self.value_stack.push(0)?;
return Ok(());
}
let distance = if (opcode & 1) != 0 {
// measure in grid fitted outline
gs.project(gs.zp0().point(p2)?, gs.zp1().point(p1)?)
.to_bits()
} else if gs.zp0.is_twilight() || gs.zp1.is_twilight() {
// special case for twilight zone
gs.dual_project(gs.zp0().original(p2)?, gs.zp1().original(p1)?)
.to_bits()
} else {
// measure in original unscaled outline
math::mul(
gs.dual_project_unscaled(gs.zp0().unscaled(p2), gs.zp1().unscaled(p1)),
gs.unscaled_to_pixels(),
)
};
self.value_stack.push(distance)
}
/// Measure pixels per em.
///
/// MPPEM[] (0x4B)
///
/// Pushes: ppem: pixels per em (uint32)
///
/// This instruction pushes the number of pixels per em onto the stack.
/// Pixels per em is a function of the resolution of the rendering device
/// and the current point size and the current transformation matrix.
///
/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#measure-pixels-per-em>
/// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2609>
pub(super) fn op_mppem(&mut self) -> OpResult {
self.value_stack.push(self.graphics.ppem)
}
/// Measure point size.
///
/// MPS[] (0x4C)
///
/// Pushes: pointSize: the size in points of the current glyph (F26Dot6)
///
/// Measure point size can be used to obtain a value which serves as the
/// basis for choosing whether to branch to an alternative path through the
/// instruction stream. It makes it possible to treat point sizes below or
/// above a certain threshold differently.
///
/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#measure-point-size>
/// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2623>
pub(super) fn op_mps(&mut self) -> OpResult {
// Note: FreeType computes this at
// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttdriver.c#L392>
// which is mul_div(ppem, 64 * 72, resolution) where resolution
// is always 72 for our purposes (Skia), resulting in ppem * 64.
self.value_stack.push(self.graphics.ppem * 64)
}
}
#[cfg(test)]
mod tests {
use super::super::{super::zone::ZonePointer, math, Engine, MockEngine};
use raw::types::F26Dot6;
#[test]
fn measure_ppem_and_point_size() {
let mut mock = MockEngine::new();
let mut engine = mock.engine();
let ppem = 20;
engine.graphics.ppem = ppem;
engine.op_mppem().unwrap();
assert_eq!(engine.value_stack.pop().unwrap(), ppem);
engine.op_mps().unwrap();
assert_eq!(engine.value_stack.pop().unwrap(), ppem * 64);
}
#[test]
fn gc() {
let mut mock = MockEngine::new();
let mut engine = mock.engine();
set_test_vectors(&mut engine);
// current point projected coord
let point = engine.graphics.zones[1].point_mut(1).unwrap();
point.x = F26Dot6::from_bits(132);
point.y = F26Dot6::from_bits(-256);
engine.value_stack.push(1).unwrap();
engine.op_gc(0).unwrap();
assert_eq!(engine.value_stack.pop().unwrap(), 4);
// original point projected coord
let point = engine.graphics.zones[1].original_mut(1).unwrap();
point.x = F26Dot6::from_bits(-64);
point.y = F26Dot6::from_bits(521);
engine.value_stack.push(1).unwrap();
engine.op_gc(1).unwrap();
assert_eq!(engine.value_stack.pop().unwrap(), 176);
}
#[test]
fn scfs() {
let mut mock = MockEngine::new();
let mut engine = mock.engine();
set_test_vectors(&mut engine);
// This instruction is a nop in backward compatibility mode
// and before IUP.
engine.graphics.backward_compatibility = false;
engine.graphics.did_iup_x = true;
engine.graphics.did_iup_y = true;
// use the twilight zone to test the optional code path
engine.graphics.zp2 = ZonePointer::Twilight;
let point = engine.graphics.zones[0].point_mut(1).unwrap();
point.x = F26Dot6::from_bits(132);
point.y = F26Dot6::from_bits(-256);
// assert we're not currently the same
assert_ne!(
engine.graphics.zones[0].point(1).unwrap(),
engine.graphics.zones[0].original(1).unwrap()
);
// push point number
engine.value_stack.push(1).unwrap();
// push value to match
engine.value_stack.push(42).unwrap();
// set coordinate from stack!
engine.op_scfs().unwrap();
let point = engine.graphics.zones[0].point(1).unwrap();
assert_eq!(point.x.to_bits(), 166);
assert_eq!(point.y.to_bits(), -239);
// ensure that we set original = point
assert_eq!(point, engine.graphics.zones[0].original(1).unwrap());
}
#[test]
fn md_scaled() {
let mut mock = MockEngine::new();
let mut engine = mock.engine();
set_test_vectors(&mut engine);
// first path, measure in grid fitted outline
let zone = engine.graphics.zone_mut(ZonePointer::Glyph);
let point1 = zone.point_mut(1).unwrap();
point1.x = F26Dot6::from_bits(132);
point1.y = F26Dot6::from_bits(-256);
let point2 = zone.point_mut(3).unwrap();
point2.x = F26Dot6::from_bits(-64);
point2.y = F26Dot6::from_bits(100);
// now measure
engine.value_stack.push(1).unwrap();
engine.value_stack.push(3).unwrap();
engine.op_md(1).unwrap();
assert_eq!(engine.value_stack.pop().unwrap(), 16);
}
#[test]
fn md_unscaled() {
let mut mock = MockEngine::new();
let mut engine = mock.engine();
set_test_vectors(&mut engine);
// second path, measure in original unscaled outline.
// unscaled points are set in mock engine but we need a scale
engine.graphics.scale = 375912;
engine.value_stack.push(1).unwrap();
engine.value_stack.push(3).unwrap();
engine.op_md(0).unwrap();
assert_eq!(engine.value_stack.pop().unwrap(), 11);
}
#[test]
fn md_twilight() {
let mut mock = MockEngine::new();
let mut engine = mock.engine();
set_test_vectors(&mut engine);
// final path, measure in original outline, in twilight zone
engine.graphics.zp0 = ZonePointer::Twilight;
engine.graphics.zp1 = ZonePointer::Twilight;
// set some points
let zone = engine.graphics.zone_mut(ZonePointer::Twilight);
let point1 = zone.original_mut(1).unwrap();
point1.x = F26Dot6::from_bits(132);
point1.y = F26Dot6::from_bits(-256);
let point2 = zone.original_mut(3).unwrap();
point2.x = F26Dot6::from_bits(-64);
point2.y = F26Dot6::from_bits(100);
// now measure
engine.value_stack.push(1).unwrap();
engine.value_stack.push(3).unwrap();
engine.op_md(0).unwrap();
assert_eq!(engine.value_stack.pop().unwrap(), 16);
}
fn set_test_vectors(engine: &mut Engine) {
let v = math::normalize14(100, 50);
engine.graphics.proj_vector = v;
engine.graphics.dual_proj_vector = v;
engine.graphics.freedom_vector = v;
engine.graphics.update_projection_state();
}
}