blob: 6abaac0a0ef62a61281a53069c7e1d7f635de132 [file]
// Copyright 2026 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Based on https://github.com/googlefonts/fontations/pull/1820
#![allow(dead_code)]
use read_fonts::{
model::pen::OutlinePen,
ps::{
cff::{
charset::Charset as CffCharset, CffFontRef, Encoding as CffEncoding,
Metadata as CffMetadata, Subfont as CffSubfont,
},
charmap::Charmap as PsCharmap,
encoding::PredefinedEncoding,
string::Sid,
type1::Type1Font,
},
types::GlyphId,
TableProvider,
};
use skrifa::MetadataProvider;
#[cxx::bridge(namespace = "skrifa")]
mod skrifa_ffi {
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum PsEncodingKind {
None = 0,
Standard = 1,
Expert = 2,
IsoLatin1 = 3,
Custom = 4,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum FsType {
RestrictedLicenseEmbedding = 0x0002,
BitmapEmbeddingOnly = 0x0200,
}
// Should match SkPathVerb
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum PathVerb {
MoveTo = 0,
LineTo = 1,
QuadTo = 2,
CurveTo = 4,
Close = 5,
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct Point {
pub x: f32,
pub y: f32,
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct BoundingBox {
pub x_min: f32,
pub y_min: f32,
pub x_max: f32,
pub y_max: f32,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct CodePageRange {
pub range1: u32,
pub range2: u32,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct Os2Panose {
pub b0: u8,
pub b1: u8,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct UnicodeRange {
pub range1: u32,
pub range2: u32,
pub range3: u32,
pub range4: u32,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct CharCodeAndIndex {
pub char_code: u32,
pub glyph_index: u32,
}
#[derive(Clone, Debug)]
pub struct Outline {
pub verbs: Vec<PathVerb>,
pub points: Vec<Point>,
pub advance_width: f32,
}
extern "Rust" {
type PsFont<'a>;
unsafe fn new_ps_font<'a>(data: &'a [u8]) -> Box<PsFont<'a>>;
fn is_ok(&self) -> bool;
unsafe fn name<'a>(&'a self) -> &'a str;
unsafe fn family_name<'a>(&'a self) -> &'a str;
fn units_per_em(&self) -> i32;
fn ascent(&self) -> f32;
fn descent(&self) -> f32;
fn num_glyphs(&self) -> u32;
fn is_cid(&self) -> bool;
fn cid_to_gid(&self, cid: u16) -> u32;
fn unicode_to_gid(&self, unicode: u32) -> u32;
fn encoding(&self) -> PsEncodingKind;
fn code_to_gid(&self, code: u8) -> u32;
fn scaled_outline(&self, gid: u32, ppem: f32, outline: &mut Outline) -> bool;
fn unscaled_outline(&self, gid: u32, outline: &mut Outline) -> bool;
fn get_os2_code_page_range(data: &[u8], range: &mut CodePageRange) -> bool;
fn get_os2_panose(data: &[u8], panose: &mut Os2Panose) -> bool;
fn get_os2_fs_type(data: &[u8], fs_type: &mut u16) -> bool;
fn get_os2_unicode_range(data: &[u8], range: &mut UnicodeRange) -> bool;
fn get_style_name(data: &[u8]) -> String;
fn get_glyph_name(data: &[u8], gid: u32) -> String;
fn get_name_index(data: &[u8], name: &str) -> u32;
fn agl_name_to_unicode(name: &str, unicode: &mut u32) -> bool;
fn agl_unicode_to_name(unicode: u32, name: &mut [u8]) -> bool;
fn get_char_codes_and_indices(data: &[u8], max_char: u32) -> Vec<CharCodeAndIndex>;
fn has_glyph_names(data: &[u8]) -> bool;
fn is_fixed_pitch(data: &[u8]) -> bool;
fn is_scalable(data: &[u8]) -> bool;
fn get_font_format(data: &[u8]) -> String;
fn get_glyph_bounds(data: &[u8], glyph_index: u32) -> BoundingBox;
}
unsafe extern "C++" {
include!("outlines.h");
fn run(font_path: &str);
}
}
use skrifa_ffi::{Outline, PathVerb, Point, PsEncodingKind};
pub enum PsFont<'a> {
Type1(Type1Font),
Cff(CffFont<'a>),
Error,
}
pub struct CffFont<'a> {
font: CffFontRef<'a>,
meta: Option<CffMetadata<'a>>,
charset: Option<CffCharset<'a>>,
encoding: Option<CffEncoding<'a>>,
unicode_cmap: Option<PsCharmap>,
subfonts: Vec<Option<CffSubfont>>,
}
pub fn new_ps_font(data: &[u8]) -> Box<PsFont<'_>> {
let font = if let Ok(cff) = CffFontRef::new(data, 0, None) {
let meta = cff.metadata();
let charset = cff.charset();
let encoding = cff.encoding();
let subfonts = (0..cff.num_subfonts()).map(|i| cff.subfont(i, &[]).ok()).collect();
let unicode_cmap = if let Some(charset) = charset.as_ref() {
Some(PsCharmap::from_glyph_names(charset.iter().filter_map(|(gid, sid)| {
Some((gid, core::str::from_utf8(cff.string(sid)?).ok()?))
})))
} else {
None
};
PsFont::Cff(CffFont { font: cff, meta, charset, encoding, unicode_cmap, subfonts })
} else if let Ok(type1) = Type1Font::new(data) {
PsFont::Type1(type1)
} else {
PsFont::Error
};
Box::new(font)
}
impl PsFont<'_> {
fn is_ok(&self) -> bool {
!matches!(self, Self::Error)
}
fn name(&self) -> &str {
match self {
Self::Type1(type1) => type1.name().unwrap_or_default(),
Self::Cff(cff) => cff.meta.as_ref().and_then(|meta| meta.name()).unwrap_or_default(),
Self::Error => "",
}
}
fn family_name(&self) -> &str {
match self {
Self::Type1(type1) => type1.family_name().unwrap_or_default(),
Self::Cff(cff) => {
cff.meta.as_ref().and_then(|meta| meta.family_name()).unwrap_or_default()
}
Self::Error => "",
}
}
fn units_per_em(&self) -> i32 {
match self {
Self::Type1(type1) => type1.upem(),
Self::Cff(cff) => cff.font.upem(),
Self::Error => 0,
}
}
fn ascent(&self) -> f32 {
let bbox = match self {
Self::Type1(type1) => type1.bbox(),
Self::Cff(cff) => cff.meta.as_ref().map(|meta| meta.bbox()).unwrap_or_default(),
Self::Error => return 0.0,
};
bbox.y_max.to_f32()
}
fn descent(&self) -> f32 {
let bbox = match self {
Self::Type1(type1) => type1.bbox(),
Self::Cff(cff) => cff.meta.as_ref().map(|meta| meta.bbox()).unwrap_or_default(),
Self::Error => return 0.0,
};
bbox.y_min.to_f32()
}
fn num_glyphs(&self) -> u32 {
match self {
Self::Type1(type1) => type1.num_glyphs(),
Self::Cff(cff) => cff.font.num_glyphs(),
Self::Error => 0,
}
}
fn unicode_to_gid(&self, unicode: u32) -> u32 {
let gid = match self {
Self::Type1(type1) => type1.unicode_charmap().map(unicode),
Self::Cff(cff) => cff.unicode_cmap.as_ref().and_then(|cmap| cmap.map(unicode)),
Self::Error => return 0,
};
gid.unwrap_or_default().to_u32()
}
fn encoding(&self) -> PsEncodingKind {
let maybe_predefined = match self {
Self::Type1(type1) => type1.encoding().map(|encoding| encoding.predefined()),
Self::Cff(cff) => cff.encoding.as_ref().map(|encoding| encoding.predefined()),
Self::Error => return PsEncodingKind::None,
};
let Some(predefined) = maybe_predefined else {
return PsEncodingKind::None;
};
match predefined {
Some(PredefinedEncoding::Standard) => PsEncodingKind::Standard,
Some(PredefinedEncoding::Expert) => PsEncodingKind::Custom,
Some(PredefinedEncoding::IsoLatin1) => PsEncodingKind::IsoLatin1,
None => PsEncodingKind::Custom,
}
}
fn code_to_gid(&self, code: u8) -> u32 {
let gid = match self {
Self::Type1(type1) => type1.encoding().and_then(|encoding| encoding.map(code)),
Self::Cff(cff) => cff.encoding.as_ref().and_then(|encoding| encoding.map(code)),
Self::Error => return 0,
};
let gid = gid.unwrap_or_default().to_u32();
if gid < self.num_glyphs() {
gid
} else {
0
}
}
fn is_cid(&self) -> bool {
match self {
Self::Cff(cff) => cff.font.is_cid(),
_ => false,
}
}
fn cid_to_gid(&self, cid: u16) -> u32 {
match self {
Self::Cff(cff) if cff.font.is_cid() => cff
.charset
.as_ref()
.and_then(|charset| charset.glyph_id(Sid::new(cid)).ok())
.unwrap_or_default()
.to_u32(),
_ => 0,
}
}
fn scaled_outline(&self, gid: u32, ppem: f32, outline: &mut Outline) -> bool {
self.outline_impl(gid, Some(ppem), outline).is_some()
}
fn unscaled_outline(&self, gid: u32, outline: &mut Outline) -> bool {
self.outline_impl(gid, None, outline).is_some()
}
fn outline_impl(&self, gid: u32, ppem: Option<f32>, outline: &mut Outline) -> Option<()> {
outline.verbs.clear();
outline.points.clear();
outline.advance_width = 0.0;
let width = match self {
Self::Type1(type1) => type1.draw(gid.into(), ppem, outline).ok()??,
Self::Cff(cff) => {
let gid = GlyphId::new(gid);
let subfont = cff.subfonts.get(cff.font.subfont_index(gid)? as usize)?.as_ref()?;
cff.font.draw(subfont, gid, &[], ppem, outline).ok()??
}
Self::Error => return None,
};
outline.advance_width = width;
Some(())
}
}
impl Point {
fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
}
impl Outline {
fn push<const N: usize>(&mut self, verb: PathVerb, points: [(f32, f32); N]) {
self.verbs.push(verb);
self.points.extend(points.into_iter().map(|(x, y)| Point::new(x, y)));
}
}
impl OutlinePen for Outline {
fn move_to(&mut self, x: f32, y: f32) {
self.push(PathVerb::MoveTo, [(x, y)]);
}
fn line_to(&mut self, x: f32, y: f32) {
self.push(PathVerb::LineTo, [(x, y)]);
}
fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
self.push(PathVerb::QuadTo, [(cx0, cy0), (x, y)]);
}
fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
self.push(PathVerb::CurveTo, [(cx0, cy0), (cx1, cy1), (x, y)]);
}
fn close(&mut self) {
self.push(PathVerb::Close, []);
}
}
fn agl_name_to_unicode(name: &str, unicode: &mut u32) -> bool {
if let Some(uni) = read_fonts::ps::agl::name_to_char(name) {
*unicode = uni as u32;
true
} else {
false
}
}
fn agl_unicode_to_name(unicode: u32, name: &mut [u8]) -> bool {
read_fonts::ps::agl::char_to_name(unicode, name).is_some()
}
pub fn get_os2_code_page_range(data: &[u8], range: &mut skrifa_ffi::CodePageRange) -> bool {
if let Ok(font) = read_fonts::FontRef::new(data) {
use read_fonts::TableProvider;
if let Ok(os2) = font.os2() {
range.range1 = os2.ul_code_page_range_1().unwrap_or(0);
range.range2 = os2.ul_code_page_range_2().unwrap_or(0);
return true;
}
}
false
}
pub fn get_os2_panose(data: &[u8], panose: &mut skrifa_ffi::Os2Panose) -> bool {
if let Ok(font) = read_fonts::FontRef::new(data) {
use read_fonts::TableProvider;
if let Ok(os2) = font.os2() {
let p = os2.panose_10();
panose.b0 = p[0];
panose.b1 = p[1];
return true;
}
}
false
}
pub fn get_os2_fs_type(data: &[u8], fs_type: &mut u16) -> bool {
if let Ok(font) = read_fonts::FontRef::new(data) {
use read_fonts::TableProvider;
if let Ok(os2) = font.os2() {
*fs_type = os2.fs_type();
return true;
}
}
false
}
pub fn get_os2_unicode_range(data: &[u8], range: &mut skrifa_ffi::UnicodeRange) -> bool {
if let Ok(font) = read_fonts::FontRef::new(data) {
use read_fonts::TableProvider;
if let Ok(os2) = font.os2() {
range.range1 = os2.ul_unicode_range_1();
range.range2 = os2.ul_unicode_range_2();
range.range3 = os2.ul_unicode_range_3();
range.range4 = os2.ul_unicode_range_4();
return true;
}
}
false
}
pub fn get_style_name(data: &[u8]) -> String {
if let Ok(font) = skrifa::FontRef::new(data) {
use skrifa::string::StringId;
use skrifa::MetadataProvider;
if let Some(name) = font.localized_strings(StringId::SUBFAMILY_NAME).english_or_first() {
return name.to_string();
}
}
String::new()
}
pub fn get_glyph_name(data: &[u8], gid: u32) -> String {
if let Ok(font) = read_fonts::FontRef::new(data) {
let glyph_names = skrifa::GlyphNames::new(&font);
if let Some(name) = glyph_names.get(skrifa::GlyphId::new(gid)) {
return name.to_string();
}
}
String::new()
}
pub fn get_name_index(data: &[u8], name: &str) -> u32 {
if let Ok(font) = read_fonts::FontRef::new(data) {
let glyph_names = skrifa::GlyphNames::new(&font);
if let Some(gid) = glyph_names.iter().find(|(_id, n)| n.as_str() == name).map(|(id, _n)| id)
{
return gid.to_u32();
}
}
0
}
pub fn get_char_codes_and_indices(data: &[u8], max_char: u32) -> Vec<skrifa_ffi::CharCodeAndIndex> {
let mut results = Vec::new();
if let Ok(font) = read_fonts::FontRef::new(data) {
let charmap = font.charmap();
if charmap.has_map() {
for (char_code, glyph_id) in charmap.mappings() {
if char_code > max_char {
break;
}
results.push(skrifa_ffi::CharCodeAndIndex {
char_code,
glyph_index: glyph_id.to_u32(),
});
}
return results;
}
if let Ok(cmap) = font.cmap() {
for record in cmap.encoding_records() {
if let Ok(read_fonts::tables::cmap::CmapSubtable::Format0(format0)) =
record.subtable(cmap.offset_data())
{
for (code, &gid) in format0.glyph_id_array().iter().enumerate() {
if gid != 0 {
let char_code = code as u32;
if char_code <= max_char {
results.push(skrifa_ffi::CharCodeAndIndex {
char_code,
glyph_index: gid as u32,
});
}
}
}
return results;
}
}
if let Some((_, _, subtable)) = cmap.best_subtable() {
for (char_code, glyph_id) in subtable.iter() {
if char_code > max_char {
continue;
}
results.push(skrifa_ffi::CharCodeAndIndex {
char_code,
glyph_index: glyph_id.to_u32(),
});
}
results.sort_by_key(|r| r.char_code);
if let Some(pos) = results.iter().position(|r| r.char_code > max_char) {
results.truncate(pos);
}
return results;
}
}
}
results
}
pub fn has_glyph_names(data: &[u8]) -> bool {
if let Ok(font) = read_fonts::FontRef::new(data) {
let glyph_names = skrifa::GlyphNames::new(&font);
return glyph_names.source() != skrifa::GlyphNameSource::Synthesized;
}
false
}
pub fn is_fixed_pitch(data: &[u8]) -> bool {
if let Ok(font) = read_fonts::FontRef::new(data) {
use read_fonts::TableProvider;
if let Ok(post) = font.post() {
return post.is_fixed_pitch() != 0;
}
}
false
}
pub fn is_scalable(data: &[u8]) -> bool {
if let Ok(font) = read_fonts::FontRef::new(data) {
use read_fonts::TableProvider;
return font.glyf().is_ok() || font.cff().is_ok() || font.cff2().is_ok();
}
if read_fonts::ps::cff::CffFontRef::new(data, 0, None).is_ok() {
return true;
}
if read_fonts::ps::type1::Type1Font::new(data).is_ok() {
return true;
}
false
}
pub fn get_font_format(data: &[u8]) -> String {
if read_fonts::ps::type1::Type1Font::new(data).is_ok() {
return "Type 1".to_string();
}
if let Ok(font) = read_fonts::FontRef::new(data) {
use read_fonts::TableProvider;
if font.cff().is_ok() || font.cff2().is_ok() {
return "CFF".to_string();
}
if font.glyf().is_ok() {
return "TrueType".to_string();
}
}
if read_fonts::ps::cff::CffFontRef::new(data, 0, None).is_ok() {
return "CFF".to_string();
}
String::new()
}
pub fn get_glyph_bounds(data: &[u8], glyph_index: u32) -> skrifa_ffi::BoundingBox {
if let Ok(font) = read_fonts::FontRef::new(data) {
let metrics = skrifa::metrics::GlyphMetrics::new(
&font,
skrifa::instance::Size::unscaled(),
skrifa::instance::LocationRef::default(),
);
if let Some(bbox) = metrics.bounds(skrifa::GlyphId::new(glyph_index)) {
return skrifa_ffi::BoundingBox {
x_min: bbox.x_min,
y_min: bbox.y_min,
x_max: bbox.x_max,
y_max: bbox.y_max,
};
}
}
skrifa_ffi::BoundingBox { x_min: 0.0, y_min: 0.0, x_max: 0.0, y_max: 0.0 }
}
fn main() {
skrifa_ffi::run("");
}