use fontcull_read_fonts::{
tables::postscript::{charstring::CommandSink, dict::Blues},
types::Fixed,
};
const ICF_TOP: Fixed = Fixed::from_i32(880);
const ICF_BOTTOM: Fixed = Fixed::from_i32(-120);
const MAX_BLUES: usize = 7;
const MAX_OTHER_BLUES: usize = 5;
const MAX_BLUE_ZONES: usize = MAX_BLUES + MAX_OTHER_BLUES;
const MAX_HINTS: usize = 96;
const HINT_MASK_SIZE: usize = MAX_HINTS.div_ceil(8);
const MIN_COUNTER: Fixed = Fixed::from_bits(0x8000);
const EPSILON: Fixed = Fixed::from_bits(1);
#[derive(Clone)]
pub(crate) struct HintParams {
pub blues: Blues,
pub family_blues: Blues,
pub other_blues: Blues,
pub family_other_blues: Blues,
pub blue_scale: Fixed,
pub blue_shift: Fixed,
pub blue_fuzz: Fixed,
pub language_group: i32,
}
impl Default for HintParams {
fn default() -> Self {
Self {
blues: Blues::default(),
other_blues: Blues::default(),
family_blues: Blues::default(),
family_other_blues: Blues::default(),
blue_scale: Fixed::from_f64(0.039625),
blue_shift: Fixed::from_i32(7),
blue_fuzz: Fixed::ONE,
language_group: 0,
}
}
}
#[derive(Copy, Clone, PartialEq, Default, Debug)]
struct BlueZone {
is_bottom: bool,
cs_bottom_edge: Fixed,
cs_top_edge: Fixed,
cs_flat_edge: Fixed,
ds_flat_edge: Fixed,
}
#[derive(Copy, Clone, PartialEq, Default)]
pub(crate) struct HintState {
scale: Fixed,
blue_scale: Fixed,
blue_shift: Fixed,
blue_fuzz: Fixed,
language_group: i32,
suppress_overshoot: bool,
do_em_box_hints: bool,
boost: Fixed,
darken_y: Fixed,
zones: [BlueZone; MAX_BLUE_ZONES],
zone_count: usize,
}
impl HintState {
pub fn new(params: &HintParams, scale: Fixed) -> Self {
let mut state = Self {
scale,
blue_scale: params.blue_scale,
blue_shift: params.blue_shift,
blue_fuzz: params.blue_fuzz,
language_group: params.language_group,
suppress_overshoot: false,
do_em_box_hints: false,
boost: Fixed::ZERO,
darken_y: Fixed::ZERO,
zones: [BlueZone::default(); MAX_BLUE_ZONES],
zone_count: 0,
};
state.build_zones(params);
state
}
fn zones(&self) -> &[BlueZone] {
&self.zones[..self.zone_count]
}
fn build_zones(&mut self, params: &HintParams) {
self.do_em_box_hints = false;
match (self.language_group, params.blues.values().len()) {
(1, 2) => {
let blues = params.blues.values();
if blues[0].0 < ICF_BOTTOM
&& blues[0].1 < ICF_BOTTOM
&& blues[1].0 > ICF_TOP
&& blues[1].1 > ICF_TOP
{
self.do_em_box_hints = true;
return;
}
}
(1, 0) => {
self.do_em_box_hints = true;
return;
}
_ => {}
}
let mut zones = [BlueZone::default(); MAX_BLUE_ZONES];
let mut max_zone_height = Fixed::ZERO;
let mut zone_ix = 0usize;
for blue in params.blues.values().iter().take(MAX_BLUES) {
let (bottom, top) = *blue;
let zone_height = top - bottom;
if zone_height < Fixed::ZERO {
continue;
}
max_zone_height = max_zone_height.max(zone_height);
let zone = &mut zones[zone_ix];
zone.cs_bottom_edge = bottom;
zone.cs_top_edge = top;
if zone_ix == 0 {
zone.is_bottom = true;
zone.cs_flat_edge = top;
} else {
zone.is_bottom = false;
zone.cs_top_edge += twice(self.darken_y);
zone.cs_bottom_edge += twice(self.darken_y);
zone.cs_flat_edge = zone.cs_bottom_edge;
}
zone_ix += 1;
}
for blue in params.other_blues.values().iter().take(MAX_OTHER_BLUES) {
let (bottom, top) = *blue;
let zone_height = top - bottom;
if zone_height < Fixed::ZERO {
continue;
}
max_zone_height = max_zone_height.max(zone_height);
let zone = &mut zones[zone_ix];
zone.is_bottom = true;
zone.cs_bottom_edge = bottom;
zone.cs_top_edge = top;
zone.cs_flat_edge = top;
zone_ix += 1;
}
let units_per_pixel = Fixed::ONE / self.scale;
for zone in &mut zones[..zone_ix] {
let flat = zone.cs_flat_edge;
let mut min_diff = Fixed::MAX;
if zone.is_bottom {
for blue in params.family_other_blues.values() {
let family_flat = blue.1;
let diff = (flat - family_flat).abs();
if diff < min_diff && diff < units_per_pixel {
zone.cs_flat_edge = family_flat;
min_diff = diff;
if diff == Fixed::ZERO {
break;
}
}
}
if !params.family_blues.values().is_empty() {
let family_flat = params.family_blues.values()[0].1;
let diff = (flat - family_flat).abs();
if diff < min_diff && diff < units_per_pixel {
zone.cs_flat_edge = family_flat;
}
}
} else {
for blue in params.family_blues.values().iter().skip(1) {
let family_flat = blue.0 + twice(self.darken_y);
let diff = (flat - family_flat).abs();
if diff < min_diff && diff < units_per_pixel {
zone.cs_flat_edge = family_flat;
min_diff = diff;
if diff == Fixed::ZERO {
break;
}
}
}
}
}
if max_zone_height > Fixed::ZERO && self.blue_scale > (Fixed::ONE / max_zone_height) {
self.blue_scale = Fixed::ONE / max_zone_height;
}
if self.scale < self.blue_scale {
self.suppress_overshoot = true;
self.boost =
Fixed::from_f64(0.6) - Fixed::from_f64(0.6).mul_div(self.scale, self.blue_scale);
self.boost = self.boost.min(Fixed::from_bits(0x7FFF));
}
if self.darken_y != Fixed::ZERO {
self.boost = Fixed::ZERO;
}
let scale = self.scale;
let boost = self.boost;
for zone in &mut zones[..zone_ix] {
let boost = if zone.is_bottom { -boost } else { boost };
zone.ds_flat_edge = (zone.cs_flat_edge * scale + boost).round();
}
self.zones = zones;
self.zone_count = zone_ix;
}
fn capture(&self, bottom_edge: &mut Hint, top_edge: &mut Hint) -> bool {
let fuzz = self.blue_fuzz;
let mut captured = false;
let mut adjustment = Fixed::ZERO;
for zone in self.zones() {
if zone.is_bottom
&& bottom_edge.is_bottom()
&& zone.cs_bottom_edge.wrapping_sub(fuzz) <= bottom_edge.cs_coord
&& bottom_edge.cs_coord <= zone.cs_top_edge.wrapping_add(fuzz)
{
adjustment = if self.suppress_overshoot {
zone.ds_flat_edge
} else if zone.cs_top_edge.wrapping_sub(bottom_edge.cs_coord) >= self.blue_shift {
bottom_edge
.ds_coord
.round()
.min(zone.ds_flat_edge - Fixed::ONE)
} else {
bottom_edge.ds_coord.round()
};
adjustment -= bottom_edge.ds_coord;
captured = true;
break;
}
if !zone.is_bottom
&& top_edge.is_top()
&& zone.cs_bottom_edge.wrapping_sub(fuzz) <= top_edge.cs_coord
&& top_edge.cs_coord <= zone.cs_top_edge.wrapping_add(fuzz)
{
adjustment = if self.suppress_overshoot {
zone.ds_flat_edge
} else if top_edge.cs_coord.wrapping_sub(zone.cs_bottom_edge) >= self.blue_shift {
top_edge
.ds_coord
.round()
.max(zone.ds_flat_edge + Fixed::ONE)
} else {
top_edge.ds_coord.round()
};
adjustment -= top_edge.ds_coord;
captured = true;
break;
}
}
if captured {
if bottom_edge.is_valid() {
bottom_edge.ds_coord += adjustment;
bottom_edge.lock();
}
if top_edge.is_valid() {
top_edge.ds_coord += adjustment;
top_edge.lock();
}
}
captured
}
}
#[derive(Copy, Clone, Default)]
struct StemHint {
is_used: bool,
min: Fixed,
max: Fixed,
ds_min: Fixed,
ds_max: Fixed,
}
const GHOST_BOTTOM: u8 = 0x1;
const GHOST_TOP: u8 = 0x2;
const PAIR_BOTTOM: u8 = 0x4;
const PAIR_TOP: u8 = 0x8;
const LOCKED: u8 = 0x10;
const SYNTHETIC: u8 = 0x20;
#[derive(Copy, Clone, PartialEq, Default, Debug)]
struct Hint {
flags: u8,
index: u8,
cs_coord: Fixed,
ds_coord: Fixed,
scale: Fixed,
}
impl Hint {
fn is_valid(&self) -> bool {
self.flags != 0
}
fn is_bottom(&self) -> bool {
self.flags & (GHOST_BOTTOM | PAIR_BOTTOM) != 0
}
fn is_top(&self) -> bool {
self.flags & (GHOST_TOP | PAIR_TOP) != 0
}
fn is_pair(&self) -> bool {
self.flags & (PAIR_BOTTOM | PAIR_TOP) != 0
}
fn is_pair_top(&self) -> bool {
self.flags & PAIR_TOP != 0
}
fn is_locked(&self) -> bool {
self.flags & LOCKED != 0
}
fn is_synthetic(&self) -> bool {
self.flags & SYNTHETIC != 0
}
fn lock(&mut self) {
self.flags |= LOCKED
}
fn setup(
&mut self,
stem: &StemHint,
index: u8,
origin: Fixed,
scale: Fixed,
darken_y: Fixed,
is_bottom: bool,
) {
const GHOST_BOTTOM_WIDTH: Fixed = Fixed::from_i32(-21);
const GHOST_TOP_WIDTH: Fixed = Fixed::from_i32(-20);
let width = stem.max - stem.min;
if width == GHOST_BOTTOM_WIDTH {
if is_bottom {
self.cs_coord = stem.max;
self.flags = GHOST_BOTTOM;
} else {
self.flags = 0;
}
} else if width == GHOST_TOP_WIDTH {
if !is_bottom {
self.cs_coord = stem.min;
self.flags = GHOST_TOP;
} else {
self.flags = 0;
}
} else if width < Fixed::ZERO {
if is_bottom {
self.cs_coord = stem.max;
self.flags = PAIR_BOTTOM;
} else {
self.cs_coord = stem.min;
self.flags = PAIR_TOP;
}
} else {
if is_bottom {
self.cs_coord = stem.min;
self.flags = PAIR_BOTTOM;
} else {
self.cs_coord = stem.max;
self.flags = PAIR_TOP;
}
}
if self.is_top() {
self.cs_coord += twice(darken_y);
}
self.cs_coord += origin;
self.scale = scale;
self.index = index;
if self.flags != 0 && stem.is_used {
if self.is_top() {
self.ds_coord = stem.ds_max;
} else {
self.ds_coord = stem.ds_min;
}
self.lock();
} else {
self.ds_coord = self.cs_coord * scale;
}
}
}
#[derive(Copy, Clone)]
struct HintMap {
edges: [Hint; MAX_HINTS],
len: usize,
is_valid: bool,
scale: Fixed,
}
impl HintMap {
fn new(scale: Fixed) -> Self {
Self {
edges: [Hint::default(); MAX_HINTS],
len: 0,
is_valid: false,
scale,
}
}
fn clear(&mut self) {
self.len = 0;
self.is_valid = false;
}
fn transform(&self, coord: Fixed) -> Fixed {
if self.len == 0 {
return coord * self.scale;
}
let limit = self.len - 1;
let mut i = 0;
while i < limit && coord >= self.edges[i + 1].cs_coord {
i += 1;
}
while i > 0 && coord < self.edges[i].cs_coord {
i -= 1;
}
let first_edge = &self.edges[0];
if i == 0 && coord < first_edge.cs_coord {
((coord - first_edge.cs_coord) * self.scale) + first_edge.ds_coord
} else {
let edge = &self.edges[i];
((coord - edge.cs_coord) * edge.scale) + edge.ds_coord
}
}
fn insert(&mut self, bottom: &Hint, top: &Hint, initial: Option<&HintMap>) {
let (is_pair, mut first_edge) = if !bottom.is_valid() {
(false, *top)
} else if !top.is_valid() {
(false, *bottom)
} else {
(true, *bottom)
};
let mut second_edge = *top;
if is_pair && top.cs_coord < bottom.cs_coord {
return;
}
let edge_count = if is_pair { 2 } else { 1 };
if self.len + edge_count > MAX_HINTS {
return;
}
let mut insert_ix = 0;
while insert_ix < self.len {
if self.edges[insert_ix].cs_coord >= first_edge.cs_coord {
break;
}
insert_ix += 1;
}
if insert_ix < self.len {
let current = &self.edges[insert_ix];
if (current.cs_coord == first_edge.cs_coord)
|| (is_pair && current.cs_coord <= second_edge.cs_coord)
|| current.is_pair_top()
{
return;
}
}
if !first_edge.is_locked() {
if let Some(initial) = initial {
if is_pair {
let mid =
initial.transform(midpoint(first_edge.cs_coord, second_edge.cs_coord));
let half_width = half(second_edge.cs_coord - first_edge.cs_coord) * self.scale;
first_edge.ds_coord = mid - half_width;
second_edge.ds_coord = mid + half_width;
} else {
first_edge.ds_coord = initial.transform(first_edge.cs_coord);
}
}
}
if insert_ix > 0 && first_edge.ds_coord < self.edges[insert_ix - 1].ds_coord {
return;
}
if insert_ix < self.len
&& ((is_pair && second_edge.ds_coord > self.edges[insert_ix].ds_coord)
|| first_edge.ds_coord > self.edges[insert_ix].ds_coord)
{
return;
}
if insert_ix != self.len {
let mut src_index = self.len - 1;
let mut dst_index = self.len + edge_count - 1;
loop {
self.edges[dst_index] = self.edges[src_index];
if src_index == insert_ix {
break;
}
src_index -= 1;
dst_index -= 1;
}
}
self.edges[insert_ix] = first_edge;
if is_pair {
self.edges[insert_ix + 1] = second_edge;
}
self.len += edge_count;
}
fn adjust(&mut self) {
let mut saved = [(0usize, Fixed::ZERO); MAX_HINTS];
let mut saved_count = 0usize;
let mut i = 0;
let limit = self.len;
while i < limit {
let is_pair = self.edges[i].is_pair();
let j = if is_pair { i + 1 } else { i };
if !self.edges[i].is_locked() {
let frac_down = self.edges[i].ds_coord.fract();
let frac_up = self.edges[j].ds_coord.fract();
let down_move_down = Fixed::ZERO - frac_down;
let up_move_down = Fixed::ZERO - frac_up;
let down_move_up = if frac_down == Fixed::ZERO {
Fixed::ZERO
} else {
Fixed::ONE - frac_down
};
let up_move_up = if frac_up == Fixed::ZERO {
Fixed::ZERO
} else {
Fixed::ONE - frac_up
};
let move_up = down_move_up.min(up_move_up);
let move_down = down_move_down.max(up_move_down);
let mut save_edge = false;
let adjustment;
if j >= self.len - 1
|| self.edges[j + 1].ds_coord
>= (self.edges[j].ds_coord + move_up + MIN_COUNTER)
{
if i == 0
|| self.edges[i - 1].ds_coord
<= (self.edges[i].ds_coord + move_down - MIN_COUNTER)
{
adjustment = if -move_down < move_up {
move_down
} else {
move_up
};
} else {
adjustment = move_up;
}
} else if i == 0
|| self.edges[i - 1].ds_coord
<= (self.edges[i].ds_coord + move_down - MIN_COUNTER)
{
adjustment = move_down;
save_edge = move_up < -move_down;
} else {
adjustment = Fixed::ZERO;
save_edge = true;
}
if save_edge && j < self.len - 1 && !self.edges[j + 1].is_locked() {
saved[saved_count] = (j, move_up - adjustment);
saved_count += 1;
}
self.edges[i].ds_coord += adjustment;
if is_pair {
self.edges[j].ds_coord += adjustment;
}
}
if i > 0 && self.edges[i].cs_coord != self.edges[i - 1].cs_coord {
let a = self.edges[i];
let b = self.edges[i - 1];
self.edges[i - 1].scale = (a.ds_coord - b.ds_coord) / (a.cs_coord - b.cs_coord);
}
if is_pair {
if self.edges[j].cs_coord != self.edges[j - 1].cs_coord {
let a = self.edges[j];
let b = self.edges[j - 1];
self.edges[j - 1].scale = (a.ds_coord - b.ds_coord) / (a.cs_coord - b.cs_coord);
}
i += 1;
}
i += 1;
}
for (j, adjustment) in saved[..saved_count].iter().copied().rev() {
if self.edges[j + 1].ds_coord >= (self.edges[j].ds_coord + adjustment + MIN_COUNTER) {
self.edges[j].ds_coord += adjustment;
if self.edges[j].is_pair() {
self.edges[j - 1].ds_coord += adjustment;
}
}
}
}
fn build(
&mut self,
state: &HintState,
mask: Option<HintMask>,
mut initial_map: Option<&mut HintMap>,
stems: &mut [StemHint],
origin: Fixed,
is_initial: bool,
) {
let scale = state.scale;
let darken_y = Fixed::ZERO;
if !is_initial {
if let Some(initial_map) = &mut initial_map {
if !initial_map.is_valid {
initial_map.build(state, Some(HintMask::all()), None, stems, origin, true);
}
}
}
let initial_map = initial_map.map(|x| x as &HintMap);
self.clear();
let mut mask = mask.unwrap_or_else(HintMask::all);
if !mask.is_valid {
mask = HintMask::all();
}
if state.do_em_box_hints {
let mut bottom = Hint::default();
bottom.cs_coord = ICF_BOTTOM - EPSILON;
bottom.ds_coord = (bottom.cs_coord * scale).round() - MIN_COUNTER;
bottom.scale = scale;
bottom.flags = GHOST_BOTTOM | LOCKED | SYNTHETIC;
let mut top = Hint::default();
top.cs_coord = ICF_TOP + EPSILON + twice(state.darken_y);
top.ds_coord = (top.cs_coord * scale).round() + MIN_COUNTER;
top.scale = scale;
top.flags = GHOST_TOP | LOCKED | SYNTHETIC;
let invalid = Hint::default();
self.insert(&bottom, &invalid, initial_map);
self.insert(&invalid, &top, initial_map);
}
let mut tmp_mask = mask;
for (i, stem) in stems.iter().enumerate() {
if !tmp_mask.get(i) {
continue;
}
let hint_ix = i as u8;
let mut bottom = Hint::default();
let mut top = Hint::default();
bottom.setup(stem, hint_ix, origin, scale, darken_y, true);
top.setup(stem, hint_ix, origin, scale, darken_y, false);
if bottom.is_locked() || top.is_locked() || state.capture(&mut bottom, &mut top) {
if is_initial {
self.insert(&bottom, &top, None);
} else {
self.insert(&bottom, &top, initial_map);
}
tmp_mask.clear(i);
}
}
if is_initial {
if self.len == 0
|| self.edges[0].cs_coord > Fixed::ZERO
|| self.edges[self.len - 1].cs_coord < Fixed::ZERO
{
let edge = Hint {
flags: GHOST_BOTTOM | LOCKED | SYNTHETIC,
scale,
..Default::default()
};
let invalid = Hint::default();
self.insert(&edge, &invalid, None);
}
} else {
for (i, stem) in stems.iter().enumerate() {
if !tmp_mask.get(i) {
continue;
}
let hint_ix = i as u8;
let mut bottom = Hint::default();
let mut top = Hint::default();
bottom.setup(stem, hint_ix, origin, scale, darken_y, true);
top.setup(stem, hint_ix, origin, scale, darken_y, false);
self.insert(&bottom, &top, initial_map);
}
}
self.adjust();
if !is_initial {
for edge in &self.edges[..self.len] {
if edge.is_synthetic() {
continue;
}
let stem = &mut stems[edge.index as usize];
if edge.is_top() {
stem.ds_max = edge.ds_coord;
} else {
stem.ds_min = edge.ds_coord;
}
stem.is_used = true;
}
}
self.is_valid = true;
}
}
#[derive(Copy, Clone, PartialEq, Default)]
struct HintMask {
mask: [u8; HINT_MASK_SIZE],
is_valid: bool,
}
impl HintMask {
fn new(bytes: &[u8]) -> Option<Self> {
let len = bytes.len();
if len > HINT_MASK_SIZE {
return None;
}
let mut mask = Self::default();
mask.mask[..len].copy_from_slice(&bytes[..len]);
mask.is_valid = true;
Some(mask)
}
fn all() -> Self {
Self {
mask: [0xFF; HINT_MASK_SIZE],
is_valid: true,
}
}
fn clear(&mut self, bit: usize) {
self.mask[bit >> 3] &= !msb_mask(bit);
}
fn get(&self, bit: usize) -> bool {
self.mask[bit >> 3] & msb_mask(bit) != 0
}
}
fn msb_mask(bit: usize) -> u8 {
1 << (7 - (bit & 0x7))
}
pub(super) struct HintingSink<'a, S> {
state: &'a HintState,
sink: &'a mut S,
stem_hints: [StemHint; MAX_HINTS],
stem_count: u8,
mask: HintMask,
initial_map: HintMap,
map: HintMap,
start_point: Option<[Fixed; 2]>,
pending_line: Option<[Fixed; 4]>,
}
impl<'a, S: CommandSink> HintingSink<'a, S> {
pub fn new(state: &'a HintState, sink: &'a mut S) -> Self {
let scale = state.scale;
Self {
state,
sink,
stem_hints: [StemHint::default(); MAX_HINTS],
stem_count: 0,
mask: HintMask::all(),
initial_map: HintMap::new(scale),
map: HintMap::new(scale),
start_point: None,
pending_line: None,
}
}
pub fn finish(&mut self) {
self.maybe_close_subpath();
}
fn maybe_close_subpath(&mut self) {
match (self.start_point.take(), self.pending_line.take()) {
(Some(start), Some([cs_x, cs_y, ds_x, ds_y])) => {
if start != [cs_x, cs_y] {
self.sink.line_to(ds_x, ds_y);
}
self.sink.close();
}
(Some(_), _) => self.sink.close(),
_ => {}
}
}
fn flush_pending_line(&mut self) {
if let Some([_, _, x, y]) = self.pending_line.take() {
self.sink.line_to(x, y);
}
}
fn hint(&mut self, coord: Fixed) -> Fixed {
if !self.map.is_valid {
self.build_hint_map(Some(self.mask), Fixed::ZERO);
}
trunc(self.map.transform(coord))
}
fn scale(&self, coord: Fixed) -> Fixed {
trunc(coord * self.state.scale)
}
fn add_stem(&mut self, min: Fixed, max: Fixed) {
let index = self.stem_count as usize;
if index >= MAX_HINTS || self.map.is_valid {
return;
}
let stem = &mut self.stem_hints[index];
stem.min = min;
stem.max = max;
stem.is_used = false;
stem.ds_min = Fixed::ZERO;
stem.ds_max = Fixed::ZERO;
self.stem_count = index as u8 + 1;
}
fn build_hint_map(&mut self, mask: Option<HintMask>, origin: Fixed) {
self.map.build(
self.state,
mask,
Some(&mut self.initial_map),
&mut self.stem_hints[..self.stem_count as usize],
origin,
false,
);
}
}
impl<S: CommandSink> CommandSink for HintingSink<'_, S> {
fn hstem(&mut self, min: Fixed, max: Fixed) {
self.add_stem(min, max);
}
fn hint_mask(&mut self, mask: &[u8]) {
let mask = HintMask::new(mask).unwrap_or_else(HintMask::all);
if mask != self.mask {
self.mask = mask;
self.map.is_valid = false;
}
}
fn counter_mask(&mut self, mask: &[u8]) {
let mask = HintMask::new(mask).unwrap_or_else(HintMask::all);
let mut map = HintMap::new(self.state.scale);
map.build(
self.state,
Some(mask),
Some(&mut self.initial_map),
&mut self.stem_hints[..self.stem_count as usize],
Fixed::ZERO,
false,
);
}
fn clear_hints(&mut self) {
self.stem_count = 0;
self.map = HintMap::new(self.state.scale);
self.initial_map = HintMap::new(self.state.scale);
self.mask = HintMask::all();
}
fn move_to(&mut self, x: Fixed, y: Fixed) {
self.maybe_close_subpath();
self.start_point = Some([x, y]);
let x = self.scale(x);
let y = self.hint(y);
self.sink.move_to(x, y);
}
fn line_to(&mut self, x: Fixed, y: Fixed) {
self.flush_pending_line();
let ds_x = self.scale(x);
let ds_y = self.hint(y);
self.pending_line = Some([x, y, ds_x, ds_y]);
}
fn curve_to(&mut self, cx1: Fixed, cy1: Fixed, cx2: Fixed, cy2: Fixed, x: Fixed, y: Fixed) {
self.flush_pending_line();
let cx1 = self.scale(cx1);
let cy1 = self.hint(cy1);
let cx2 = self.scale(cx2);
let cy2 = self.hint(cy2);
let x = self.scale(x);
let y = self.hint(y);
self.sink.curve_to(cx1, cy1, cx2, cy2, x, y);
}
fn close(&mut self) {
}
}
fn trunc(value: Fixed) -> Fixed {
Fixed::from_bits(value.to_bits() & !0x3FF)
}
fn half(value: Fixed) -> Fixed {
Fixed::from_bits(value.to_bits() / 2)
}
fn twice(value: Fixed) -> Fixed {
Fixed::from_bits(value.to_bits().wrapping_mul(2))
}
fn midpoint(a: Fixed, b: Fixed) -> Fixed {
a + half(b - a)
}
#[cfg(test)]
mod tests {
use fontcull_read_fonts::{
tables::postscript::charstring::CommandSink, types::F2Dot14, FontRef,
};
use super::{
BlueZone, Blues, Fixed, Hint, HintMap, HintMask, HintParams, HintState, HintingSink,
StemHint, GHOST_BOTTOM, GHOST_TOP, HINT_MASK_SIZE, LOCKED, PAIR_BOTTOM, PAIR_TOP,
};
fn make_hint_state() -> HintState {
fn make_blues(values: &[f64]) -> Blues {
Blues::new(values.iter().copied().map(Fixed::from_f64))
}
let params = HintParams {
blues: make_blues(&[
-15.0, 0.0, 536.0, 547.0, 571.0, 582.0, 714.0, 726.0, 760.0, 772.0,
]),
other_blues: make_blues(&[-255.0, -240.0]),
blue_scale: Fixed::from_f64(0.05),
blue_shift: Fixed::from_i32(7),
blue_fuzz: Fixed::ZERO,
..Default::default()
};
HintState::new(¶ms, Fixed::ONE / Fixed::from_i32(64))
}
#[test]
fn scaled_blue_zones() {
let state = make_hint_state();
assert!(!state.do_em_box_hints);
assert_eq!(state.zone_count, 6);
assert_eq!(state.boost, Fixed::from_bits(27035));
assert!(state.suppress_overshoot);
let expected_zones = &[
BlueZone {
cs_bottom_edge: Fixed::from_bits(-983040),
is_bottom: true,
..Default::default()
},
BlueZone {
cs_bottom_edge: Fixed::from_bits(35127296),
cs_top_edge: Fixed::from_bits(35848192),
cs_flat_edge: Fixed::from_bits(35127296),
ds_flat_edge: Fixed::from_bits(589824),
is_bottom: false,
},
BlueZone {
cs_bottom_edge: Fixed::from_bits(37421056),
cs_top_edge: Fixed::from_bits(38141952),
cs_flat_edge: Fixed::from_bits(37421056),
ds_flat_edge: Fixed::from_bits(589824),
is_bottom: false,
},
BlueZone {
cs_bottom_edge: Fixed::from_bits(46792704),
cs_top_edge: Fixed::from_bits(47579136),
cs_flat_edge: Fixed::from_bits(46792704),
ds_flat_edge: Fixed::from_bits(786432),
is_bottom: false,
},
BlueZone {
cs_bottom_edge: Fixed::from_bits(49807360),
cs_top_edge: Fixed::from_bits(50593792),
cs_flat_edge: Fixed::from_bits(49807360),
ds_flat_edge: Fixed::from_bits(786432),
is_bottom: false,
},
BlueZone {
cs_bottom_edge: Fixed::from_bits(-16711680),
cs_top_edge: Fixed::from_bits(-15728640),
cs_flat_edge: Fixed::from_bits(-15728640),
ds_flat_edge: Fixed::from_bits(-262144),
is_bottom: true,
},
];
assert_eq!(state.zones(), expected_zones);
}
#[test]
fn blue_zone_capture() {
let state = make_hint_state();
let bottom_edge = Hint {
flags: PAIR_BOTTOM,
ds_coord: Fixed::from_f64(2.3),
..Default::default()
};
let top_edge = Hint {
flags: PAIR_TOP,
cs_coord: Fixed::from_bits(35127297),
ds_coord: Fixed::from_f64(2.3),
..Default::default()
};
{
let (mut bottom_edge, mut top_edge) = (bottom_edge, top_edge);
assert!(state.capture(&mut bottom_edge, &mut top_edge));
assert!(bottom_edge.is_locked());
assert!(top_edge.is_locked());
}
{
let min_cs_coord = Fixed::MIN;
let mut bottom_edge = Hint {
cs_coord: min_cs_coord,
..bottom_edge
};
let mut top_edge = Hint {
cs_coord: min_cs_coord,
..top_edge
};
assert!(!state.capture(&mut bottom_edge, &mut top_edge));
assert!(!bottom_edge.is_locked());
assert!(!top_edge.is_locked());
}
{
let mut bottom_edge = bottom_edge;
let mut top_edge = Hint {
flags: 0,
..top_edge
};
assert!(state.capture(&mut bottom_edge, &mut top_edge));
assert!(bottom_edge.is_locked());
assert!(!top_edge.is_locked());
}
{
let mut bottom_edge = Hint {
flags: 0,
..bottom_edge
};
let mut top_edge = top_edge;
assert!(state.capture(&mut bottom_edge, &mut top_edge));
assert!(!bottom_edge.is_locked());
assert!(top_edge.is_locked());
}
}
#[test]
fn hint_mask_ops() {
const MAX_BITS: usize = HINT_MASK_SIZE * 8;
let all_bits = HintMask::all();
for i in 0..MAX_BITS {
assert!(all_bits.get(i));
}
let odd_bits = HintMask::new(&[0b01010101; HINT_MASK_SIZE]).unwrap();
for i in 0..MAX_BITS {
assert_eq!(i & 1 != 0, odd_bits.get(i));
}
let mut cleared_bits = odd_bits;
for i in 0..MAX_BITS {
if i & 1 != 0 {
cleared_bits.clear(i);
}
}
assert_eq!(cleared_bits.mask, HintMask::default().mask);
}
#[test]
fn hint_mapping() {
let font = FontRef::new(fontcull_font_test_data::CANTARELL_VF_TRIMMED).unwrap();
let cff_font = super::super::Outlines::new(&font).unwrap();
let state = cff_font
.subfont(0, Some(8.0), &[F2Dot14::from_f32(-1.0); 2])
.unwrap()
.hint_state;
let mut initial_map = HintMap::new(state.scale);
let mut map = HintMap::new(state.scale);
let mut stems = [
StemHint {
min: Fixed::from_bits(1376256),
max: Fixed::ZERO,
..Default::default()
},
StemHint {
min: Fixed::from_bits(16318464),
max: Fixed::from_bits(17563648),
..Default::default()
},
StemHint {
min: Fixed::from_bits(45481984),
max: Fixed::from_bits(44171264),
..Default::default()
},
];
map.build(
&state,
Some(HintMask::all()),
Some(&mut initial_map),
&mut stems,
Fixed::ZERO,
false,
);
let expected_edges = [
Hint {
index: 0,
cs_coord: Fixed::from_f64(0.0),
ds_coord: Fixed::from_f64(0.0),
scale: Fixed::from_bits(526),
flags: GHOST_BOTTOM | LOCKED,
},
Hint {
index: 1,
cs_coord: Fixed::from_bits(16318464),
ds_coord: Fixed::from_bits(131072),
scale: Fixed::from_bits(524),
flags: PAIR_BOTTOM,
},
Hint {
index: 1,
cs_coord: Fixed::from_bits(17563648),
ds_coord: Fixed::from_bits(141028),
scale: Fixed::from_bits(592),
flags: PAIR_TOP,
},
Hint {
index: 2,
cs_coord: Fixed::from_bits(45481984),
ds_coord: Fixed::from_bits(393216),
scale: Fixed::from_bits(524),
flags: GHOST_TOP | LOCKED,
},
];
assert_eq!(expected_edges, &map.edges[..map.len]);
let mappings = [
(0, 0), (44302336, 382564), (45481984, 393216), (16318464, 131072), (17563648, 141028), (49676288, 426752), (56754176, 483344), (57868288, 492252), (50069504, 429896), ];
for (coord, expected) in mappings {
assert_eq!(
map.transform(Fixed::from_bits(coord)),
Fixed::from_bits(expected)
);
}
}
#[test]
fn midpoint_avoids_overflow() {
let a = i16::MAX as i32;
let b = a - 1;
assert!(a + b > i16::MAX as i32);
let mid = super::midpoint(Fixed::from_i32(a), Fixed::from_i32(b));
assert_eq!((a + b) / 2, mid.to_bits() >> 16);
}
#[test]
fn hinting_sink_omits_closing_line_that_matches_start() {
let state = HintState {
scale: Fixed::ONE,
..Default::default()
};
let mut path = Path::default();
let mut sink = HintingSink::new(&state, &mut path);
let move1_2 = [Fixed::from_f64(1.0), Fixed::from_f64(2.0)];
let line2_3 = [Fixed::from_f64(2.0), Fixed::from_f64(3.0)];
let line1_2 = [Fixed::from_f64(1.0), Fixed::from_f64(2.0)];
let line3_4 = [Fixed::from_f64(3.0), Fixed::from_f64(4.0)];
let curve = [
Fixed::from_f64(3.0),
Fixed::from_f64(4.0),
Fixed::from_f64(5.0),
Fixed::from_f64(6.0),
Fixed::from_f64(1.0),
Fixed::from_f64(2.0),
];
sink.move_to(move1_2[0], move1_2[1]);
sink.line_to(line2_3[0], line2_3[1]);
sink.line_to(line1_2[0], line1_2[1]);
sink.move_to(move1_2[0], move1_2[1]);
sink.line_to(line2_3[0], line2_3[1]);
sink.line_to(line3_4[0], line3_4[1]);
sink.move_to(move1_2[0], move1_2[1]);
sink.line_to(line2_3[0], line2_3[1]);
sink.curve_to(curve[0], curve[1], curve[2], curve[3], curve[4], curve[5]);
sink.finish();
assert_eq!(
&path.0,
&[
MoveTo(move1_2),
LineTo(line2_3),
Close,
MoveTo(move1_2),
LineTo(line2_3),
LineTo(line3_4),
Close,
MoveTo(move1_2),
LineTo(line2_3),
CurveTo(curve),
Close,
]
);
}
#[derive(Copy, Clone, PartialEq, Debug)]
enum Command {
MoveTo([Fixed; 2]),
LineTo([Fixed; 2]),
CurveTo([Fixed; 6]),
Close,
}
use Command::*;
#[derive(Default)]
struct Path(Vec<Command>);
impl CommandSink for Path {
fn move_to(&mut self, x: Fixed, y: Fixed) {
self.0.push(MoveTo([x, y]));
}
fn line_to(&mut self, x: Fixed, y: Fixed) {
self.0.push(LineTo([x, y]));
}
fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed) {
self.0.push(CurveTo([cx0, cy0, cx1, cy1, x, y]));
}
fn close(&mut self) {
self.0.push(Close);
}
}
}