Skip to content

Commit

Permalink
[skrifa] new hinting options type (#1087)
Browse files Browse the repository at this point in the history
Replaces the current HintingMode with a new HintingOptions type to support autohinting. Specifically, it allows for selecting the hinting engine (interpreter, auto or auto fallback). Also reintroduces a "light" mode to match FT_RENDER_MODE_LIGHT since this actually represents a different mode in the autohinter (but didn't for TrueType or CFF).

Adds the symmetric_rendering flag as well (closes #1080).
  • Loading branch information
dfrg authored Aug 28, 2024
1 parent 2914485 commit 61cd661
Show file tree
Hide file tree
Showing 15 changed files with 388 additions and 114 deletions.
3 changes: 2 additions & 1 deletion skrifa/src/outline/autohint/hint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ mod tests {
super::{latin, metrics::Scale, style},
*,
};
use crate::MetadataProvider;
use crate::{attribute::Style, MetadataProvider};
use raw::{
types::{F2Dot14, GlyphId},
FontRef, TableProvider,
Expand Down Expand Up @@ -413,6 +413,7 @@ mod tests {
let scale = Scale::new(
size,
font.head().unwrap().units_per_em() as i32,
Style::Normal,
Default::default(),
);
latin::hint_outline(&mut outline, &metrics, &scale);
Expand Down
2 changes: 1 addition & 1 deletion skrifa/src/outline/autohint/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use raw::TableProvider;
/// Set of derived glyph styles that are used for automatic hinting.
///
/// These are invariant per font so can be precomputed and reused for multiple
/// instances when requesting automatic hinting.
/// instances when requesting automatic hinting with [`Engine::Auto`](super::super::hint::Engine::Auto).
#[derive(Clone, Debug)]
pub struct GlyphStyles(Arc<GlyphStyleMap>);

Expand Down
3 changes: 2 additions & 1 deletion skrifa/src/outline/autohint/latin/edges.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ mod tests {
},
*,
};
use crate::MetadataProvider;
use crate::{attribute::Style, MetadataProvider};
use raw::{types::GlyphId, FontRef, TableProvider};

#[test]
Expand All @@ -301,6 +301,7 @@ mod tests {
let scale = metrics::Scale::new(
16.0,
font.head().unwrap().units_per_em() as i32,
Style::Normal,
Default::default(),
);
let scaled_metrics = latin::metrics::scale_style_metrics(&unscaled_metrics, scale);
Expand Down
3 changes: 2 additions & 1 deletion skrifa/src/outline/autohint/latin/hint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ mod tests {
},
*,
};
use crate::MetadataProvider;
use crate::{attribute::Style, MetadataProvider};
use raw::{types::GlyphId, FontRef, TableProvider};

#[test]
Expand All @@ -574,6 +574,7 @@ mod tests {
let scale = metrics::Scale::new(
16.0,
font.head().unwrap().units_per_em() as i32,
Style::Normal,
Default::default(),
);
let scaled_metrics = latin::metrics::scale_style_metrics(&unscaled_metrics, scale);
Expand Down
2 changes: 2 additions & 0 deletions skrifa/src/outline/autohint/latin/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ fn scale_axis_metrics(
#[cfg(test)]
mod tests {
use super::{super::super::style, *};
use crate::attribute::Style;
use raw::{FontRef, TableProvider};

#[test]
Expand All @@ -219,6 +220,7 @@ mod tests {
let scale = Scale::new(
16.0,
font.head().unwrap().units_per_em() as i32,
Style::Normal,
Default::default(),
);
let scaled_metrics = scale_style_metrics(&unscaled_metrics, scale);
Expand Down
30 changes: 11 additions & 19 deletions skrifa/src/outline/autohint/metrics.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! Autohinting specific metrics.

use super::{
super::{HintingMode, LcdLayout},
super::Target,
axis::Dimension,
style::{GlyphStyleMap, StyleClass},
};
use crate::{collections::SmallVec, FontRef};
use crate::{attribute::Style, collections::SmallVec, FontRef};
use alloc::vec::Vec;
use raw::types::{F2Dot14, Fixed, GlyphId};
#[cfg(feature = "std")]
Expand Down Expand Up @@ -218,40 +218,32 @@ pub(crate) struct Scale {
}

impl Scale {
/// Create initial scaling parameters from font size, units per em
/// and hinting mode.
pub fn new(size: f32, units_per_em: i32, mode: HintingMode) -> Self {
/// Create initial scaling parameters from metrics and hinting target.
pub fn new(size: f32, units_per_em: i32, font_style: Style, target: Target) -> Self {
let scale =
(Fixed::from_bits((size * 64.0) as i32) / Fixed::from_bits(units_per_em)).to_bits();
let is_mono = mode == HintingMode::Strong;
let lcd = match mode {
HintingMode::Smooth { lcd_subpixel, .. } => lcd_subpixel,
_ => None,
};
// TODO: handle light hinting mode and italic flag
let is_light = false;
let is_italic = false;
let is_lcd = lcd == Some(LcdLayout::Horizontal);
let is_lcd_v = lcd == Some(LcdLayout::Horizontal);
let mut flags = 0;
let is_italic = font_style != Style::Normal;
let is_mono = target == Target::Mono;
let is_light = target.is_light() || target.preserve_linear_metrics();
// Snap vertical stems for monochrome and horizontal LCD rendering.
if is_mono || is_lcd {
if is_mono || target.is_lcd() {
flags |= Self::HORIZONTAL_SNAP;
}
// Snap horizontal stems for monochrome and vertical LCD rendering.
if is_mono || is_lcd_v {
if is_mono || target.is_vertical_lcd() {
flags |= Self::VERTICAL_SNAP;
}
// Adjust stems to full pixels unless in LCD or light modes.
if !(is_lcd || is_light) {
if !(target.is_lcd() || is_light) {
flags |= Self::STEM_ADJUST;
}
if is_mono {
flags |= Self::MONO;
}
// Disable horizontal hinting completely for LCD, light hinting
// and italic fonts.
if is_lcd || is_light || is_italic {
if target.is_lcd() || is_light || is_italic {
flags |= Self::NO_HORIZONTAL;
}
Self {
Expand Down
4 changes: 2 additions & 2 deletions skrifa/src/outline/glyf/hint/engine/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ impl<'a> Engine<'a> {
self.graphics.reset_retained();
}
// Set backward compatibility mode
if self.graphics.mode.preserve_linear_metrics() {
if self.graphics.target.preserve_linear_metrics() {
self.graphics.backward_compatibility = true;
} else if self.graphics.mode.is_smooth() {
} else if self.graphics.target.is_smooth() {
self.graphics.backward_compatibility =
(self.graphics.instruct_control & 0x4) == 0;
} else {
Expand Down
2 changes: 1 addition & 1 deletion skrifa/src/outline/glyf/hint/engine/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ impl<'a> Engine<'a> {
}
// If preserving linear metrics, prevent modification of the backward
// compatibility flag.
if selector == 3 && self.graphics.mode.preserve_linear_metrics() {
if selector == 3 && self.graphics.target.preserve_linear_metrics() {
return Ok(());
}
match (self.program.initial, selector) {
Expand Down
29 changes: 16 additions & 13 deletions skrifa/src/outline/glyf/hint/engine/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,15 @@ impl<'a> Engine<'a> {
result |= FONT_VARIATIONS_RESULT_BIT;
}
// The following only apply for smooth hinting.
if self.graphics.mode.is_smooth() {
if self.graphics.target.is_smooth() {
// Subpixel hinting [cleartype enabled] (selector bit: 6, result bit: 13)
// (always enabled)
if (selector & SUBPIXEL_HINTING_SELECTOR_BIT) != 0 {
result |= SUBPIXEL_HINTING_RESULT_BIT;
}
// Vertical LCD subpixels? (selector bit: 8, result bit: 15)
if (selector & VERTICAL_LCD_SELECTOR_BIT) != 0 && self.graphics.mode.is_vertical_lcd() {
if (selector & VERTICAL_LCD_SELECTOR_BIT) != 0 && self.graphics.target.is_vertical_lcd()
{
result |= VERTICAL_LCD_RESULT_BIT;
}
// Subpixel positioned? (selector bit: 10, result bit: 17)
Expand All @@ -58,16 +59,17 @@ impl<'a> Engine<'a> {
result |= SUBPIXEL_POSITIONED_RESULT_BIT;
}
// Symmetrical smoothing (selector bit: 11, result bit: 18)
// Note: FreeType always enables this but we deviate when our own
// preserve linear metrics flag is enabled.
// Note: FreeType always enables this but we allow direct control
// with our own flag.
// See <https://github.com/googlefonts/fontations/issues/1080>
if (selector & SYMMETRICAL_SMOOTHING_SELECTOR_BIT) != 0
&& !self.graphics.mode.preserve_linear_metrics()
&& self.graphics.target.symmetric_rendering()
{
result |= SYMMETRICAL_SMOOTHING_RESULT_BIT;
}
// ClearType hinting and grayscale rendering (selector bit: 12, result bit: 19)
if (selector & GRAYSCALE_CLEARTYPE_SELECTOR_BIT) != 0
&& self.graphics.mode.is_grayscale_cleartype()
&& self.graphics.target.is_grayscale_cleartype()
{
result |= GRAYSCALE_CLEARTYPE_RESULT_BIT;
}
Expand Down Expand Up @@ -176,7 +178,7 @@ mod getinfo {
#[cfg(test)]
mod tests {
use super::super::{
super::{super::super::LcdLayout, HintingMode},
super::super::super::{SmoothMode, Target},
Engine, HintErrorKind, MockEngine,
};
use raw::types::F2Dot14;
Expand Down Expand Up @@ -210,7 +212,7 @@ mod tests {
engine.axis_count = 1;
engine.getinfo_test(FONT_VARIATIONS_SELECTOR_BIT, FONT_VARIATIONS_RESULT_BIT);
// in strong hinting mode, the following selectors are always disabled
engine.graphics.mode = HintingMode::Strong;
engine.graphics.target = Target::Mono;
for selector in [
SUBPIXEL_HINTING_SELECTOR_BIT,
VERTICAL_LCD_SELECTOR_BIT,
Expand All @@ -221,7 +223,7 @@ mod tests {
engine.getinfo_test(selector, 0);
}
// set back to smooth mode
engine.graphics.mode = HintingMode::default();
engine.graphics.target = Target::default();
for (selector, result) in [
// default smooth mode is grayscale cleartype
(
Expand All @@ -238,17 +240,18 @@ mod tests {
engine.getinfo_test(selector, result);
}
// vertical lcd
engine.graphics.mode = HintingMode::Smooth {
lcd_subpixel: Some(LcdLayout::Vertical),
engine.graphics.target = Target::Smooth {
mode: SmoothMode::VerticalLcd,
preserve_linear_metrics: true,
symmetric_rendering: false,
};
engine.getinfo_test(VERTICAL_LCD_SELECTOR_BIT, VERTICAL_LCD_RESULT_BIT);
// symmetical smoothing is disabled when preserving linear metrics
// symmetical smoothing is disabled
engine.getinfo_test(SYMMETRICAL_SMOOTHING_SELECTOR_BIT, 0);
// grayscale cleartype is disabled when lcd_subpixel is not None
engine.getinfo_test(GRAYSCALE_CLEARTYPE_SELECTOR_BIT, 0);
// reset to default to disable preserve linear metrics
engine.graphics.mode = HintingMode::default();
engine.graphics.target = Target::default();
// now symmetrical smoothing is enabled
engine.getinfo_test(
SYMMETRICAL_SMOOTHING_SELECTOR_BIT,
Expand Down
16 changes: 8 additions & 8 deletions skrifa/src/outline/glyf/hint/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use super::{
round::RoundState,
zone::{Zone, ZonePointer},
F26Dot6, HintingMode, Point,
F26Dot6, Point, Target,
};
use core::ops::{Deref, DerefMut};

Expand Down Expand Up @@ -150,11 +150,11 @@ impl<'a> GraphicsState<'a> {
pub fn reset_retained(&mut self) {
let scale = self.scale;
let ppem = self.ppem;
let mode = self.mode;
let mode = self.target;
self.retained = RetainedGraphicsState {
scale,
ppem,
mode,
target: mode,
..Default::default()
}
}
Expand Down Expand Up @@ -251,8 +251,8 @@ pub struct RetainedGraphicsState {
///
/// See <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM04/Chap4.html#single_width_value>
pub single_width: F26Dot6,
/// The user requested hinting mode.
pub mode: HintingMode,
/// The user requested hinting target.
pub target: Target,
/// The scale factor for the current instance. Conversion from font units
/// to 26.6 for current ppem.
pub scale: i32,
Expand All @@ -265,11 +265,11 @@ pub struct RetainedGraphicsState {
}

impl RetainedGraphicsState {
pub fn new(scale: i32, ppem: i32, mode: HintingMode) -> Self {
pub fn new(scale: i32, ppem: i32, target: Target) -> Self {
Self {
scale,
ppem,
mode,
target,
..Default::default()
}
}
Expand All @@ -292,7 +292,7 @@ impl Default for RetainedGraphicsState {
scan_type: 0,
single_width_cutin: F26Dot6::ZERO,
single_width: F26Dot6::ZERO,
mode: Default::default(),
target: Default::default(),
scale: 0,
ppem: 0,
is_rotated: false,
Expand Down
10 changes: 5 additions & 5 deletions skrifa/src/outline/glyf/hint/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use super::{
program::{Program, ProgramState},
value_stack::ValueStack,
zone::Zone,
HintOutline, HintingMode, PointFlags,
HintOutline, PointFlags, Target,
};
use alloc::vec::Vec;
use raw::{
Expand Down Expand Up @@ -38,7 +38,7 @@ impl HintInstance {
outlines: &Outlines,
scale: i32,
ppem: i32,
mode: HintingMode,
target: Target,
coords: &[F2Dot14],
) -> Result<(), HintError> {
self.setup(outlines, scale, coords);
Expand All @@ -53,7 +53,7 @@ impl HintInstance {
let glyph = Zone::default();
let mut stack_buf = vec![0; self.max_stack];
let value_stack = ValueStack::new(&mut stack_buf, false);
let graphics = RetainedGraphicsState::new(scale, ppem, mode);
let graphics = RetainedGraphicsState::new(scale, ppem, target);
let mut engine = Engine::new(
outlines,
ProgramState::new(outlines.fpgm, outlines.prep, &[], Program::Font),
Expand Down Expand Up @@ -92,9 +92,9 @@ impl HintInstance {
/// by the hinter settings or the `prep` table.
pub fn backward_compatibility(&self) -> bool {
// Set backward compatibility mode
if self.graphics.mode.preserve_linear_metrics() {
if self.graphics.target.preserve_linear_metrics() {
true
} else if self.graphics.mode.is_smooth() {
} else if self.graphics.target.is_smooth() {
(self.graphics.instruct_control & 0x4) == 0
} else {
false
Expand Down
41 changes: 1 addition & 40 deletions skrifa/src/outline/glyf/hint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ mod storage;
mod value_stack;
mod zone;

use super::super::{HintingMode, LcdLayout};
use super::super::Target;

use read_fonts::{
tables::glyf::PointFlags,
Expand Down Expand Up @@ -45,42 +45,3 @@ pub struct HintOutline<'a> {
pub is_composite: bool,
pub coords: &'a [F2Dot14],
}

// Helpers for deriving various flags from the mode which
// change the behavior of certain instructions.
// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttgload.c#L2222>
impl HintingMode {
fn is_smooth(&self) -> bool {
matches!(self, Self::Smooth { .. })
}

fn is_grayscale_cleartype(&self) -> bool {
matches!(
self,
Self::Smooth {
lcd_subpixel: None,
..
}
)
}

fn is_vertical_lcd(&self) -> bool {
matches!(
self,
Self::Smooth {
lcd_subpixel: Some(LcdLayout::Vertical),
..
}
)
}

fn preserve_linear_metrics(&self) -> bool {
matches!(
self,
Self::Smooth {
preserve_linear_metrics: true,
..
}
)
}
}
Loading

0 comments on commit 61cd661

Please sign in to comment.