1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-23 23:21:08 +03:00

fonts: switch parsing over to freetype

This is leading up to multi-master font support
This commit is contained in:
Wez Furlong 2021-04-07 16:14:58 -07:00
parent cf9f9c849b
commit 38d6c45194
3 changed files with 80 additions and 233 deletions

View File

@ -5,6 +5,7 @@ use anyhow::{anyhow, Context};
use config::{configuration, FreeTypeLoadTarget};
pub use freetype::*;
use std::borrow::Cow;
use std::ffi::CStr;
use std::ptr;
#[inline]
@ -93,6 +94,40 @@ struct FaceSize {
}
impl Face {
pub fn family_name(&self) -> String {
unsafe {
if (*self.face).family_name.is_null() {
"".to_string()
} else {
let c = CStr::from_ptr((*self.face).family_name);
c.to_string_lossy().to_string()
}
}
}
pub fn style_name(&self) -> String {
unsafe {
if (*self.face).style_name.is_null() {
"".to_string()
} else {
let c = CStr::from_ptr((*self.face).style_name);
c.to_string_lossy().to_string()
}
}
}
pub fn postscript_name(&self) -> String {
unsafe {
let c = FT_Get_Postscript_Name(self.face);
if c.is_null() {
"".to_string()
} else {
let c = CStr::from_ptr(c);
c.to_string_lossy().to_string()
}
}
}
/// This is a wrapper around set_char_size and select_size
/// that accounts for some weirdness with eg: color emoji
pub fn set_font_size(&mut self, point_size: f64, dpi: u32) -> anyhow::Result<(f64, f64)> {

View File

@ -39,12 +39,14 @@ fn extract_font_data(font: HFONT, attr: &FontAttributes) -> anyhow::Result<FontD
let mut data = vec![0u8; ttc_size as usize];
GetFontData(hdc, ttc_table, 0, data.as_mut_ptr() as *mut _, ttc_size);
let data = Cow::Owned(data);
// Determine which of the contained fonts is the one
// that we asked for.
let index =
crate::parser::resolve_font_from_ttc_data(&attr, &data)?.unwrap_or(0) as u32;
Ok(FontDataHandle::Memory {
data: Cow::Owned(data),
data,
index,
name: attr.family.clone(),
})

View File

@ -1,10 +1,9 @@
use crate::locator::FontDataHandle;
use crate::shaper::GlyphInfo;
use anyhow::anyhow;
use config::FontAttributes;
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use ttf_parser::{fonts_in_collection, Face, Name, PlatformId};
use ttf_parser::fonts_in_collection;
#[derive(Debug)]
pub enum MaybeShaped {
@ -26,224 +25,34 @@ pub struct Names {
pub postscript_name: Option<String>,
}
/// Computes a score for a given name record; font files can contain
/// multiple variants of the same logical name encoded differently
/// for various operating systems and languages.
/// This function assigns a weight to each of the combinations;
/// we generally prefer the English rendition of the name in unicode.
///
/// Borrowed from a similar bit of code in the allsorts crate.
fn score(name: &Name) -> Option<usize> {
match (name.platform_id(), name.encoding_id(), name.language_id()) {
(PlatformId::Windows, 10, _) => Some(1000),
(PlatformId::Unicode, 6, 0) => Some(900),
(PlatformId::Unicode, 4, 0) => Some(800),
(PlatformId::Windows, 1, 0x409) => Some(750),
(PlatformId::Windows, 1, lang) if lang != 0x409 => Some(700),
(PlatformId::Unicode, 3, 0) => Some(600),
(PlatformId::Unicode, 2, 0) => Some(500),
(PlatformId::Unicode, 1, 0) => Some(400),
(PlatformId::Unicode, 0, 0) => Some(300),
(PlatformId::Windows, 0, _) => Some(200),
(PlatformId::Macintosh, 0, 0) => Some(150),
(PlatformId::Macintosh, 0, lang) if lang != 0 => Some(100),
_ => None,
}
}
/// Maybe convert a MacRoman byte to a unicode char.
/// Borrowed from the allsorts crate.
fn macroman_to_char(b: u8) -> Option<char> {
match b {
0..=127 => Some(b as char),
128 => Some('Ä'), // A dieresis
129 => Some('Å'), // A ring
130 => Some('Ç'), // C cedilla
131 => Some('É'), // E acute
132 => Some('Ñ'), // N tilde
133 => Some('Ö'), // O dieresis
134 => Some('Ü'), // U dieresis
135 => Some('á'), // a acute
136 => Some('à'), // a grave
137 => Some('â'), // a circumflex
138 => Some('ä'), // a dieresis
139 => Some('ã'), // a tilde
140 => Some('å'), // a ring
141 => Some('ç'), // c cedilla
142 => Some('é'), // e acute
143 => Some('è'), // e grave
144 => Some('ê'), // e circumflex
145 => Some('ë'), // e dieresis
146 => Some('í'), // i acute
147 => Some('ì'), // i grave
148 => Some('î'), // i circumflex
149 => Some('ï'), // i dieresis
150 => Some('ñ'), // n tilde
151 => Some('ó'), // o acute
152 => Some('ò'), // o grave
153 => Some('ô'), // o circumflex
154 => Some('ö'), // o dieresis
155 => Some('õ'), // o tilde
156 => Some('ú'), // u acute
157 => Some('ù'), // u grave
158 => Some('û'), // u circumflex
159 => Some('ü'), // u dieresis
160 => Some('†'), // dagger
161 => Some('°'), // degree
162 => Some('¢'), // cent
163 => Some('£'), // sterling
164 => Some('§'), // section
165 => Some('•'), // bullet
166 => Some('¶'), // paragraph
167 => Some('ß'), // German double s
168 => Some('®'), // registered
169 => Some('©'), // copyright
170 => Some('™'), // trademark
171 => Some('´'), // acute
172 => Some('¨'), // diaeresis
174 => Some('Æ'), // AE
175 => Some('Ø'), // O slash
177 => Some('±'), // plusminus
180 => Some('¥'), // yen
181 => Some('µ'), // micro
187 => Some('ª'), // ordfeminine
188 => Some('º'), // ordmasculine
190 => Some('æ'), // ae
191 => Some('ø'), // o slash
192 => Some('¿'), // question down
193 => Some('¡'), // exclamation down
194 => Some('¬'), // not
196 => Some('ƒ'), // florin
199 => Some('«'), // left guille
200 => Some('»'), // right guille
201 => Some('…'), // ellipsis
202 => Some(' '), // non-breaking space
203 => Some('À'), // A grave
204 => Some('Ã'), // A tilde
205 => Some('Õ'), // O tilde
206 => Some('Œ'), // OE
207 => Some('œ'), // oe
208 => Some(''), // endash
209 => Some('—'), // emdash
210 => Some('“'), // ldquo
211 => Some('”'), // rdquo
212 => Some(''), // lsquo
213 => Some(''), // rsquo
214 => Some('÷'), // divide
216 => Some('ÿ'), // y dieresis
217 => Some('Ÿ'), // Y dieresis
218 => Some(''), // fraction
219 => Some('¤'), // currency
220 => Some(''), // left single guille
221 => Some(''), // right single guille
222 => Some('fi'), // fi
223 => Some('fl'), // fl
224 => Some('‡'), // double dagger
225 => Some('·'), // middle dot
226 => Some(''), // single quote base
227 => Some('„'), // double quote base
228 => Some('‰'), // perthousand
229 => Some('Â'), // A circumflex
230 => Some('Ê'), // E circumflex
231 => Some('Á'), // A acute
232 => Some('Ë'), // E dieresis
233 => Some('È'), // E grave
234 => Some('Í'), // I acute
235 => Some('Î'), // I circumflex
236 => Some('Ï'), // I dieresis
237 => Some('Ì'), // I grave
238 => Some('Ó'), // O acute
239 => Some('Ô'), // O circumflex
241 => Some('Ò'), // O grave
242 => Some('Ú'), // U acute
243 => Some('Û'), // U circumflex
244 => Some('Ù'), // U grave
245 => Some('ı'), // dot-less i
246 => Some('^'), // circumflex
247 => Some('˜'), // tilde
248 => Some('¯'), // macron
249 => Some('˘'), // breve
250 => Some('˙'), // dot accent
251 => Some('˚'), // ring
252 => Some('¸'), // cedilla
253 => Some('˝'), // Hungarian umlaut (double acute accent)
254 => Some('˛'), // ogonek
255 => Some('ˇ'), // caron
_ => None,
}
}
/// Return a unicode version of the name
fn decode_name(name: &Name) -> Option<String> {
if name.platform_id() == PlatformId::Macintosh {
Some(
name.name()
.iter()
.filter_map(|&b| macroman_to_char(b))
.collect::<String>(),
)
} else {
name.to_string()
}
}
impl Names {
fn from_face(face: &Face) -> anyhow::Result<Names> {
// The names table isn't very amenable to a direct lookup, and there
// can be multiple candidate encodings for a given font name.
// Since we need to lookup multiple names, we copy potential
// candidates into a vector and then sort it so that the best
// candidates are towards the front of the vector.
// This should result in less overall work to extract the names.
let mut names = face
.names()
.filter(|name| {
let id = name.name_id();
let interesting_id = id == ttf_parser::name_id::FAMILY
|| id == ttf_parser::name_id::SUBFAMILY
|| id == ttf_parser::name_id::FULL_NAME
|| id == ttf_parser::name_id::POST_SCRIPT_NAME;
interesting_id && (name.is_unicode() || name.platform_id() == PlatformId::Macintosh)
})
.collect::<Vec<_>>();
// Best scores at the front
names.sort_by(|a, b| score(a).cmp(&score(b)).reverse());
fn from_ft_face(face: &crate::ftwrap::Face) -> Names {
let postscript_name = face.postscript_name();
let family = face.family_name();
let sub_family = face.style_name();
// Now looking up a name is a simple matter of finding the
// first entry with the desired id
fn get_name(names: &[Name], id: u16) -> Option<String> {
let name = names.iter().find(|n| n.name_id() == id)?;
decode_name(name)
let full_name = if sub_family.is_empty() {
family.to_string()
} else {
format!("{} {}", family, sub_family)
};
Names {
full_name,
family: Some(family),
sub_family: Some(sub_family),
postscript_name: Some(postscript_name),
}
Ok(Names {
full_name: get_name(&names, ttf_parser::name_id::FULL_NAME)
.ok_or_else(|| anyhow!("missing full name"))?,
family: get_name(&names, ttf_parser::name_id::FAMILY),
sub_family: get_name(&names, ttf_parser::name_id::SUBFAMILY),
postscript_name: get_name(&names, ttf_parser::name_id::POST_SCRIPT_NAME),
})
}
}
impl ParsedFont {
pub fn from_locator(handle: &FontDataHandle) -> anyhow::Result<Self> {
match handle {
FontDataHandle::OnDisk { path, index } => {
let data = std::fs::read(path)?;
let face = Face::from_slice(&data, *index)?;
Ok(Self {
names: Names::from_face(&face)?,
})
}
FontDataHandle::Memory { data, index, .. } => {
let face = Face::from_slice(data, *index)?;
Ok(Self {
names: Names::from_face(&face)?,
})
}
}
let lib = crate::ftwrap::Library::new()?;
let face = lib.face_from_locator(handle)?;
Ok(Self {
names: Names::from_ft_face(&face),
})
}
pub fn names(&self) -> &Names {
@ -281,20 +90,21 @@ pub fn font_info_matches(attr: &FontAttributes, names: &Names) -> bool {
/// the index of a singular TTF file, if it matches.
pub fn resolve_font_from_ttc_data(
attr: &FontAttributes,
data: &[u8],
data: &Cow<'static, [u8]>,
) -> anyhow::Result<Option<usize>> {
let lib = crate::ftwrap::Library::new()?;
if let Some(size) = fonts_in_collection(data) {
for index in 0..size {
let face = Face::from_slice(data, index)?;
let names = Names::from_face(&face)?;
let face = lib.new_face_from_slice(data.clone(), index.into())?;
let names = Names::from_ft_face(&face);
if font_info_matches(attr, &names) {
return Ok(Some(index as usize));
}
}
Ok(None)
} else {
let face = Face::from_slice(data, 0)?;
let names = Names::from_face(&face)?;
let face = lib.new_face_from_slice(data.clone(), 0)?;
let names = Names::from_ft_face(&face);
if font_info_matches(attr, &names) {
Ok(Some(0))
} else {
@ -315,6 +125,7 @@ pub(crate) fn load_built_in_fonts(
(include_bytes!($font) as &'static [u8], $font)
};
}
let lib = crate::ftwrap::Library::new()?;
for (data, name) in &[
font!("../../assets/fonts/JetBrainsMono-BoldItalic.ttf"),
font!("../../assets/fonts/JetBrainsMono-Bold.ttf"),
@ -334,8 +145,8 @@ pub(crate) fn load_built_in_fonts(
font!("../../assets/fonts/PowerlineExtraSymbols.otf"),
font!("../../assets/fonts/LastResortHE-Regular.ttf"),
] {
let face = Face::from_slice(data, 0)?;
let names = Names::from_face(&face)?;
let face = lib.new_face_from_slice(Cow::Borrowed(data), 0)?;
let names = Names::from_ft_face(&face);
font_info.push((
names,
PathBuf::from(name),
@ -354,30 +165,29 @@ pub(crate) fn parse_and_collect_font_info(
path: &Path,
font_info: &mut Vec<(Names, PathBuf, FontDataHandle)>,
) -> anyhow::Result<()> {
let data = std::fs::read(path)?;
let data = Cow::Owned(std::fs::read(path)?);
let lib = crate::ftwrap::Library::new()?;
let size = fonts_in_collection(&data).unwrap_or(0);
fn load_one(
data: &[u8],
lib: &crate::ftwrap::Library,
path: &Path,
index: u32,
font_info: &mut Vec<(Names, PathBuf, FontDataHandle)>,
) -> anyhow::Result<()> {
let face = Face::from_slice(data, index)?;
let names = Names::from_face(&face)?;
font_info.push((
names,
path.to_path_buf(),
FontDataHandle::OnDisk {
path: path.to_path_buf(),
index,
},
));
let locator = FontDataHandle::OnDisk {
path: path.to_path_buf(),
index,
};
let face = lib.face_from_locator(&locator)?;
let names = Names::from_ft_face(&face);
font_info.push((names, path.to_path_buf(), locator));
Ok(())
}
for index in 0..=size {
if let Err(err) = load_one(&data, path, index, font_info) {
if let Err(err) = load_one(&lib, path, index, font_info) {
log::trace!(
"error while parsing {} index {}: {}",
path.display(),