skrifa/outline/glyf/hint/engine/cvt.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
//! Managing the control value table.
//!
//! Implements 3 instructions.
//!
//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#managing-the-control-value-table>
use super::{super::math::mul, Engine, F26Dot6, OpResult};
impl<'a> Engine<'a> {
/// Write control value table in pixel units.
///
/// WCVTP[] (0x44)
///
/// Pops: value: number in pixels (F26Dot6 fixed point number),
/// location: Control Value Table location (uint32)
///
/// Pops a location and a value from the stack and puts that value in the
/// specified location in the Control Value Table. This instruction assumes
/// the value is in pixels and not in FUnits.
///
/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#write-control-value-table-in-pixel-units>
/// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3044>
pub(super) fn op_wcvtp(&mut self) -> OpResult {
let value = self.value_stack.pop_f26dot6()?;
let location = self.value_stack.pop_usize()?;
let result = self.cvt.set(location, value);
if self.graphics.is_pedantic {
result
} else {
Ok(())
}
}
/// Write control value table in font units.
///
/// WCVTF[] (0x70)
///
/// Pops: value: number in pixels (F26Dot6 fixed point number),
/// location: Control Value Table location (uint32)
///
/// Pops a location and a value from the stack and puts the specified
/// value in the specified address in the Control Value Table. This
/// instruction assumes the value is expressed in FUnits and not pixels.
/// The value is scaled before being written to the table.
///
/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#write-control-value-table-in-funits>
/// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3067>
pub(super) fn op_wcvtf(&mut self) -> OpResult {
let value = self.value_stack.pop()?;
let location = self.value_stack.pop_usize()?;
let result = self.cvt.set(
location,
F26Dot6::from_bits(mul(value, self.graphics.scale)),
);
if self.graphics.is_pedantic {
result
} else {
Ok(())
}
}
/// Read control value table.
///
/// RCVT[] (0x45)
///
/// Pops: location: CVT entry number
/// Pushes: value: CVT value (F26Dot6)
///
/// Pops a location from the stack and pushes the value in the location
/// specified in the Control Value Table onto the stack.
///
/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#read-control-value-table>
/// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3090>
pub(super) fn op_rcvt(&mut self) -> OpResult {
let location = self.value_stack.pop()? as usize;
let maybe_value = self.cvt.get(location);
let value = if self.graphics.is_pedantic {
maybe_value?
} else {
maybe_value.unwrap_or_default()
};
self.value_stack.push(value.to_bits())
}
}
#[cfg(test)]
mod tests {
use super::super::{super::math, HintErrorKind, MockEngine};
#[test]
fn write_read() {
let mut mock = MockEngine::new();
let mut engine = mock.engine();
for i in 0..8 {
engine.value_stack.push(i).unwrap();
engine.value_stack.push(i * 2).unwrap();
engine.op_wcvtp().unwrap();
}
for i in 0..8 {
engine.value_stack.push(i).unwrap();
engine.op_rcvt().unwrap();
assert_eq!(engine.value_stack.pop().unwrap(), i * 2);
}
}
#[test]
fn write_scaled_read() {
let mut mock = MockEngine::new();
let mut engine = mock.engine();
let scale = 64;
engine.graphics.scale = scale;
for i in 0..8 {
engine.value_stack.push(i).unwrap();
engine.value_stack.push(i * 2).unwrap();
// WCVTF takes a value in font units and converts to pixels
// with the current scale
engine.op_wcvtf().unwrap();
}
for i in 0..8 {
engine.value_stack.push(i).unwrap();
engine.op_rcvt().unwrap();
let value = engine.value_stack.pop().unwrap();
assert_eq!(value, math::mul(i * 2, scale));
}
}
#[test]
fn pedantry() {
let mut mock = MockEngine::new();
let mut engine = mock.engine();
let oob_index = 1000;
// Disable pedantic mode: OOB writes are ignored, OOB reads
// push 0
engine.graphics.is_pedantic = false;
engine.value_stack.push(oob_index).unwrap();
engine.value_stack.push(0).unwrap();
engine.op_wcvtp().unwrap();
engine.value_stack.push(oob_index).unwrap();
engine.value_stack.push(0).unwrap();
engine.op_wcvtf().unwrap();
engine.value_stack.push(oob_index).unwrap();
engine.op_rcvt().unwrap();
// Enable pedantic mode: OOB reads/writes error
engine.graphics.is_pedantic = true;
engine.value_stack.push(oob_index).unwrap();
engine.value_stack.push(0).unwrap();
assert_eq!(
engine.op_wcvtp(),
Err(HintErrorKind::InvalidCvtIndex(oob_index as _))
);
engine.value_stack.push(oob_index).unwrap();
engine.value_stack.push(0).unwrap();
assert_eq!(
engine.op_wcvtf(),
Err(HintErrorKind::InvalidCvtIndex(oob_index as _))
);
engine.value_stack.push(oob_index).unwrap();
assert_eq!(
engine.op_rcvt(),
Err(HintErrorKind::InvalidCvtIndex(oob_index as _))
);
}
}