Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable

- name: fauntlet compare
run: cargo run --release -p fauntlet -- compare-glyphs --hinting-engine all --hinting-target all --exit-on-fail fauntlet/test_fonts/*.*tf
run: cargo run --release -p fauntlet -- compare-glyphs --print-paths --hinting-engine all --hinting-target all --exit-on-fail fauntlet/test_fonts/*.*tf
- name: fauntlet compare type1
run: cargo run --release -p fauntlet -- compare-glyphs --print-paths --exit-on-fail font-test-data/test_data/type1/*.pf*

28 changes: 25 additions & 3 deletions fauntlet/src/font/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::{
use ::freetype::{face::LoadFlag, Library};
use ::skrifa::{
outline::{HintingOptions, SmoothMode, Target},
raw::{types::F2Dot14, FontRef, TableProvider},
raw::{tables::postscript::font::Type1Font, types::F2Dot14, FontRef, TableProvider},
};

mod freetype;
Expand Down Expand Up @@ -101,6 +101,7 @@ impl<'a> InstanceOptions<'a> {
pub struct Font {
path: PathBuf,
data: SharedFontData,
pub(crate) type1: Option<Type1Font>,
count: usize,
ft_library: Library,
}
Expand All @@ -110,11 +111,21 @@ impl Font {
let path = path.as_ref().to_owned();
let file = std::fs::File::open(&path).ok()?;
let data = SharedFontData(unsafe { Arc::new(memmap2::Mmap::map(&file).ok()?) });
let count = FontRef::fonts(data.0.as_ref()).count();
let type1 = if check_type1(&data.0) {
Some(Type1Font::new(&data.0).ok()?)
} else {
None
};
let count = if type1.is_some() {
1
} else {
FontRef::fonts(data.0.as_ref()).count()
};
let _ft_library = ::freetype::Library::init().ok()?;
Some(Self {
path,
data,
type1,
count,
ft_library: _ft_library,
})
Expand All @@ -124,6 +135,10 @@ impl Font {
&self.path
}

pub fn is_type1(&self) -> bool {
self.type1.is_some()
}

pub fn count(&self) -> usize {
self.count
}
Expand All @@ -145,7 +160,7 @@ impl Font {
options: &InstanceOptions,
) -> Option<(FreeTypeInstance, SkrifaInstance<'_>)> {
let ft_instance = FreeTypeInstance::new(&self.ft_library, &self.data, options)?;
let skrifa_instance = SkrifaInstance::new(&self.data, options)?;
let skrifa_instance = SkrifaInstance::new(self, options)?;
Some((ft_instance, skrifa_instance))
}
}
Expand All @@ -158,3 +173,10 @@ impl Borrow<[u8]> for SharedFontData {
self.0.as_ref()
}
}

fn check_type1(data: &[u8]) -> bool {
fn check(data: &[u8]) -> bool {
data.starts_with(b"%!PS-AdobeFont") || data.starts_with(b"%!FontType")
}
check(data) || data.get(6..).map(check).unwrap_or(false)
}
130 changes: 121 additions & 9 deletions fauntlet/src/font/skrifa.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,86 @@
use ::skrifa::{
outline::{DrawError, DrawSettings, HintingInstance, OutlinePen},
use super::{InstanceOptions, SharedFontData};
use crate::Font;
use skrifa::{
outline::{DrawError, DrawSettings, HintingInstance, HintingOptions, OutlinePen},
prelude::{LocationRef, Size},
raw::types::F2Dot14,
raw::{FontRef, TableProvider},
raw::{
tables::postscript::{
charstring::{CommandSink, NopFilterSink, TransformSink},
font::Type1Font,
},
types::{F2Dot14, Fixed},
FontRef, TableProvider,
},
GlyphId, MetadataProvider, OutlineGlyphCollection,
};
use skrifa::outline::HintingOptions;

use super::{InstanceOptions, SharedFontData};
pub enum SkrifaInstance<'a> {
Sfnt(SkrifaSfntInstance<'a>),
Type1(SkrifaType1Instance<'a>),
}

impl<'a> SkrifaInstance<'a> {
pub fn new(font: &'a Font, options: &InstanceOptions) -> Option<Self> {
if let Some(type1) = font.type1.as_ref() {
Some(Self::Type1(SkrifaType1Instance {
font: type1,
ppem: (options.ppem != 0.0).then_some(options.ppem),
}))
} else {
SkrifaSfntInstance::new(&font.data, options).map(Self::Sfnt)
}
}

pub fn is_tricky(&self) -> bool {
match self {
Self::Sfnt(sfnt) => sfnt.is_tricky(),
_ => false,
}
}

pub fn glyph_count(&self) -> u16 {
match self {
Self::Sfnt(sfnt) => sfnt.glyph_count(),
Self::Type1(type1) => type1.font.num_glyphs() as _,
}
}

pub fn advance(&mut self, glyph_id: GlyphId) -> Option<f32> {
match self {
Self::Sfnt(sfnt) => sfnt.advance(glyph_id),
_ => None,
}
}

pub fn hvar_and_gvar_advance_deltas(&self, glyph_id: GlyphId) -> Option<(i32, i32)> {
match self {
Self::Sfnt(sfnt) => sfnt.hvar_and_gvar_advance_deltas(glyph_id),
_ => None,
}
}

/// Returns the scaler adjusted advance width when available.
pub fn outline(
&mut self,
glyph_id: GlyphId,
pen: &mut impl OutlinePen,
) -> Result<Option<f32>, DrawError> {
match self {
Self::Sfnt(sfnt) => sfnt.outline(glyph_id, pen),
Self::Type1(type1) => type1.outline(glyph_id, pen),
}
}
}

pub struct SkrifaInstance<'a> {
pub struct SkrifaSfntInstance<'a> {
font: FontRef<'a>,
size: Size,
coords: Vec<F2Dot14>,
outlines: OutlineGlyphCollection<'a>,
hinter: Option<HintingInstance>,
}

impl<'a> SkrifaInstance<'a> {
impl<'a> SkrifaSfntInstance<'a> {
pub fn new(data: &'a SharedFontData, options: &InstanceOptions) -> Option<Self> {
let font = FontRef::from_index(data.0.as_ref(), options.index as u32).ok()?;
let size = if options.ppem != 0.0 {
Expand Down Expand Up @@ -49,7 +112,7 @@ impl<'a> SkrifaInstance<'a> {
} else {
None
};
Some(SkrifaInstance {
Some(Self {
font,
size,
coords: options.coords.into(),
Expand Down Expand Up @@ -111,3 +174,52 @@ impl<'a> SkrifaInstance<'a> {
.map(|metrics| metrics.advance_width)
}
}

pub struct SkrifaType1Instance<'a> {
font: &'a Type1Font,
ppem: Option<f32>,
}

impl SkrifaType1Instance<'_> {
pub fn outline(
&mut self,
glyph_id: GlyphId,
pen: &mut impl OutlinePen,
) -> Result<Option<f32>, DrawError> {
let mut pen = PenCommandSink(pen);
let mut nop_filter = NopFilterSink::new(&mut pen);
let scale = self.ppem.map(|ppem| self.font.scale_for_ppem(ppem));
let mut transformer = TransformSink::new(&mut nop_filter, self.font.matrix(), scale);
self.font
.evaluate_charstring(glyph_id, &mut transformer)
.map_err(DrawError::PostScript)?;
Ok(None)
}
}

struct PenCommandSink<'a, P: OutlinePen>(&'a mut P);

impl<P: OutlinePen> CommandSink for PenCommandSink<'_, P> {
fn move_to(&mut self, x: Fixed, y: Fixed) {
self.0.move_to(x.to_f32(), y.to_f32());
}

fn line_to(&mut self, x: Fixed, y: Fixed) {
self.0.line_to(x.to_f32(), y.to_f32());
}

fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed) {
self.0.curve_to(
cx0.to_f32(),
cy0.to_f32(),
cx1.to_f32(),
cy1.to_f32(),
x.to_f32(),
y.to_f32(),
);
}

fn close(&mut self) {
self.0.close()
}
}
15 changes: 11 additions & 4 deletions fauntlet/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ fn main() {
use std::sync::atomic::{AtomicBool, Ordering};
let ok = AtomicBool::new(true);
files.par_iter().for_each(|font_path| {
if print_paths {
writeln!(std::io::stdout(), "[{font_path:?}]").unwrap();
}
if let Some(mut font_data) = fauntlet::Font::new(font_path) {
if print_paths {
writeln!(std::io::stdout(), "[{font_path:?}]").unwrap();
}
for font_ix in 0..font_data.count() {
for ppem in &ppem_sizes {
let axis_count = font_data.axis_count(font_ix) as usize;
Expand Down Expand Up @@ -135,7 +135,12 @@ fn main() {
}
}
} else {
for hinting in &hinting_matrix {
let hinting_matrix = if font_data.is_type1() {
&[Hinting::None]
} else {
hinting_matrix.as_slice()
};
for hinting in hinting_matrix {
let options =
InstanceOptions::new(font_ix, *ppem, &[], *hinting);
if print_instances {
Expand All @@ -156,6 +161,8 @@ fn main() {
) {
ok.store(false, Ordering::Release);
}
} else {
writeln!(std::io::stdout(), "warning: failed to load font instances for {font_path:?}").unwrap();
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion read-fonts/src/tables/postscript/font.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! Model for PostScript fonts.

mod cff;
#[expect(unused)]
#[cfg(feature = "std")]
mod type1;

pub use cff::{CffFontRef, CffSubfont};
#[cfg(feature = "std")]
pub use type1::Type1Font;

use super::dict::Blues;
use types::Fixed;
Expand Down
15 changes: 7 additions & 8 deletions read-fonts/src/tables/postscript/font/type1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ impl CharstringContext for Type1Font {
Ok(self.subrs.get(index as u32).ok_or(ReadError::OutOfBounds)?)
}

fn global_subr(&self, index: i32) -> Result<&[u8], Error> {
fn global_subr(&self, _index: i32) -> Result<&[u8], Error> {
// Type1 fonts don't have global subroutines
Err(Error::MissingSubroutines)
}
Expand All @@ -178,7 +178,7 @@ impl<'a> RawDicts<'a> {
verify_header(data)?;
let (base_dict, raw_private_dict) = data.split_at_checked(base_size as usize)?;
// Decrypt private dict segments
let mut private_dict = decrypt(
let private_dict = decrypt(
decode_pfb_binary_segments(raw_private_dict)
.flat_map(|segment| segment.iter().copied()),
EEXEC_SEED,
Expand All @@ -196,7 +196,7 @@ impl<'a> RawDicts<'a> {
// Now find the start of the private dictionary
let start = find_eexec_data(data)?;
let (base_dict, raw_private_dict) = data.split_at_checked(start)?;
let mut private_dict = if raw_private_dict.len() > 3
let private_dict = if raw_private_dict.len() > 3
&& raw_private_dict[..4].iter().all(|b| b.is_ascii_hexdigit())
{
// Hex decode and then decrypt
Expand Down Expand Up @@ -598,7 +598,6 @@ impl<'a> Parser<'a> {
}
}
}
None
}

fn peek(&self) -> Option<Token<'a>> {
Expand Down Expand Up @@ -792,7 +791,7 @@ impl Parser<'_> {
/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1load.c#L1720>
fn read_subrs(&mut self, len_iv: i64) -> Option<Subrs> {
let mut subrs = Subrs::default();
let num_subrs: usize = match self.next()? {
let _: usize = match self.next()? {
Token::Raw(b"[") => {
// Just an empty array
self.expect(Token::Raw(b"]"))?;
Expand Down Expand Up @@ -845,7 +844,7 @@ impl Parser<'_> {
/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/type1/t1load.c#L1919>
fn read_charstrings(&mut self, len_iv: i64) -> Option<Charstrings> {
let mut charstrings = Charstrings::default();
let num_glyphs: usize = match self.next()? {
let _: usize = match self.next()? {
Token::Int(n) => n.try_into().ok()?,
_ => return None,
};
Expand Down Expand Up @@ -1594,7 +1593,7 @@ mod tests {
break;
}
}
let mut charstrings = charstrings.unwrap();
let charstrings = charstrings.unwrap();
assert_eq!(charstrings.num_glyphs(), 9);
assert!(charstrings.orig_notdef_index.is_none());
let expected_names = [
Expand All @@ -1612,7 +1611,7 @@ mod tests {
.map(|idx| charstrings.name(idx).unwrap())
.collect::<Vec<_>>();
assert_eq!(names, expected_names);
/// Prefix (up to 8 bytes), extracted from FreeType
// Prefix (up to 8 bytes), extracted from FreeType
let expected_charstrings_prefix: [&[u8]; 9] = [
&[139, 248, 236, 13, 14],
&[177, 249, 173, 13, 139, 4, 247, 183],
Expand Down
Loading