mirror of
https://github.com/wez/wezterm.git
synced 2024-12-03 19:53:40 +03:00
wezterm-font: teach gdi locator how to search for fallback fonts
This commit uses a bit of DirectWrite to discover which font(s) can be used to render a set of codepoints. While hooking this up, I found that the method we were using to extract the font data didn't handle TTC data so this commit improves some parser diagnostics and handling for that. refs: https://github.com/wez/wezterm/issues/299
This commit is contained in:
parent
58eb8e3614
commit
ba44548d46
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -1048,15 +1048,16 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
|
||||
|
||||
[[package]]
|
||||
name = "dwrote"
|
||||
version = "0.9.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bd1369e02db5e9b842a9b67bce8a2fcc043beafb2ae8a799dd482d46ea1ff0d"
|
||||
checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"winapi 0.3.9",
|
||||
"wio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4552,6 +4553,15 @@ dependencies = [
|
||||
"xml-rs 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wio"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ws2_32-sys"
|
||||
version = "0.2.1"
|
||||
|
@ -31,7 +31,7 @@ window = { path = "../window" }
|
||||
fontconfig = { path = "../deps/fontconfig" }
|
||||
|
||||
[target."cfg(windows)".dependencies]
|
||||
dwrote = "0.9"
|
||||
dwrote = "0.11"
|
||||
winapi = "0.3"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::locator::{new_locator, FontDataHandle, FontLocator, FontLocatorSelection};
|
||||
use crate::rasterizer::{new_rasterizer, FontRasterizer};
|
||||
use crate::shaper::{new_shaper, FontShaper, FontShaperSelection};
|
||||
use anyhow::{anyhow, Error};
|
||||
use anyhow::Error;
|
||||
use config::{configuration, ConfigHandle, FontRasterizerSelection, TextStyle};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
@ -24,7 +24,7 @@ pub use crate::rasterizer::RasterizedGlyph;
|
||||
pub use crate::shaper::{FallbackIdx, FontMetrics, GlyphInfo};
|
||||
|
||||
pub struct LoadedFont {
|
||||
rasterizers: Vec<RefCell<Option<Box<dyn FontRasterizer>>>>,
|
||||
rasterizers: RefCell<HashMap<FallbackIdx, Box<dyn FontRasterizer>>>,
|
||||
handles: RefCell<Vec<FontDataHandle>>,
|
||||
shaper: RefCell<Box<dyn FontShaper>>,
|
||||
metrics: FontMetrics,
|
||||
@ -58,22 +58,29 @@ impl LoadedFont {
|
||||
err,
|
||||
no_glyphs.iter().collect::<String>().escape_debug()
|
||||
),
|
||||
Ok(handles) if handles.is_empty() => {
|
||||
log::error!(
|
||||
"No fonts have glyphs for {}",
|
||||
no_glyphs.iter().collect::<String>().escape_debug()
|
||||
)
|
||||
}
|
||||
Ok(handles) if handles.is_empty() => log::error!(
|
||||
"No fonts have glyphs for {}",
|
||||
no_glyphs.iter().collect::<String>().escape_debug()
|
||||
),
|
||||
Ok(extra_handles) => {
|
||||
let mut loaded = false;
|
||||
{
|
||||
let mut handles = self.handles.borrow_mut();
|
||||
for h in extra_handles {
|
||||
if !handles.iter().any(|existing| *existing == h) {
|
||||
if crate::parser::ParsedFont::from_locator(&h).is_ok() {
|
||||
let idx = handles.len() - 1;
|
||||
handles.insert(idx, h);
|
||||
loaded = true;
|
||||
match crate::parser::ParsedFont::from_locator(&h) {
|
||||
Ok(_parsed) => {
|
||||
let idx = handles.len() - 1;
|
||||
handles.insert(idx, h);
|
||||
loaded = true;
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Failed to parse font from {:?}: {:?}",
|
||||
h,
|
||||
err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -87,8 +94,8 @@ impl LoadedFont {
|
||||
return self.shape(text);
|
||||
} else {
|
||||
log::error!(
|
||||
"No fonts have glyphs for {}",
|
||||
no_glyphs.iter().collect::<String>().escape_debug()
|
||||
"No fonts have glyphs for {}, even though fallback suggested some.",
|
||||
no_glyphs.iter().collect::<String>().escape_debug(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -110,23 +117,18 @@ impl LoadedFont {
|
||||
glyph_pos: u32,
|
||||
fallback: FallbackIdx,
|
||||
) -> anyhow::Result<RasterizedGlyph> {
|
||||
let cell = self
|
||||
.rasterizers
|
||||
.get(fallback)
|
||||
.ok_or_else(|| anyhow!("no such fallback index: {}", fallback))?;
|
||||
let mut opt_raster = cell.borrow_mut();
|
||||
if opt_raster.is_none() {
|
||||
let mut rasterizers = self.rasterizers.borrow_mut();
|
||||
if let Some(raster) = rasterizers.get(&fallback) {
|
||||
raster.rasterize_glyph(glyph_pos, self.font_size, self.dpi)
|
||||
} else {
|
||||
let raster = new_rasterizer(
|
||||
FontRasterizerSelection::get_default(),
|
||||
&(self.handles.borrow())[fallback],
|
||||
)?;
|
||||
opt_raster.replace(raster);
|
||||
let result = raster.rasterize_glyph(glyph_pos, self.font_size, self.dpi);
|
||||
rasterizers.insert(fallback, raster);
|
||||
result
|
||||
}
|
||||
|
||||
opt_raster
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.rasterize_glyph(glyph_pos, self.font_size, self.dpi)
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,10 +258,6 @@ impl FontConfigInner {
|
||||
}
|
||||
}
|
||||
|
||||
let mut rasterizers = vec![];
|
||||
for _ in &handles {
|
||||
rasterizers.push(RefCell::new(None));
|
||||
}
|
||||
let shaper = new_shaper(FontShaperSelection::get_default(), &handles)?;
|
||||
|
||||
let config = configuration();
|
||||
@ -268,7 +266,7 @@ impl FontConfigInner {
|
||||
let metrics = shaper.metrics(font_size, dpi)?;
|
||||
|
||||
let loaded = Rc::new(LoadedFont {
|
||||
rasterizers,
|
||||
rasterizers: RefCell::new(HashMap::new()),
|
||||
handles: RefCell::new(handles),
|
||||
shaper: RefCell::new(shaper),
|
||||
metrics,
|
||||
|
@ -2,8 +2,11 @@
|
||||
|
||||
use crate::locator::{FontDataHandle, FontLocator};
|
||||
use config::FontAttributes;
|
||||
use dwrote::{FontStretch, FontStyle, FontWeight};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
use winapi::shared::windef::HFONT;
|
||||
use winapi::um::dwrite::*;
|
||||
use winapi::um::wingdi::{
|
||||
CreateCompatibleDC, CreateFontIndirectW, DeleteDC, DeleteObject, GetFontData, SelectObject,
|
||||
FIXED_PITCH, GDI_ERROR, LF_FACESIZE, LOGFONTW, OUT_TT_ONLY_PRECIS,
|
||||
@ -13,24 +16,55 @@ use winapi::um::wingdi::{
|
||||
/// functions provided by the font-loader crate.
|
||||
pub struct GdiFontLocator {}
|
||||
|
||||
fn extract_font_data(font: HFONT, name: &str) -> anyhow::Result<FontDataHandle> {
|
||||
fn extract_font_data(font: HFONT, attr: &FontAttributes) -> anyhow::Result<FontDataHandle> {
|
||||
unsafe {
|
||||
let hdc = CreateCompatibleDC(std::ptr::null_mut());
|
||||
SelectObject(hdc, font as *mut _);
|
||||
|
||||
let size = GetFontData(hdc, 0, 0, std::ptr::null_mut(), 0);
|
||||
let result = match size {
|
||||
_ if size > 0 && size != GDI_ERROR => {
|
||||
let mut data = vec![0u8; size as usize];
|
||||
GetFontData(hdc, 0, 0, data.as_mut_ptr() as *mut _, size);
|
||||
Ok(FontDataHandle::Memory {
|
||||
data,
|
||||
index: 0,
|
||||
name: name.to_string(),
|
||||
})
|
||||
// GetFontData can retrieve different parts of the font data.
|
||||
// We want to fetch the entire font file, but things are made
|
||||
// more complicated because the file may be a TTC file.
|
||||
// In that case, the full file data isn't full parsable
|
||||
// as a TTF so we need to ask specifically for the TTC file,
|
||||
// and then try to reverse engineer which element of the TTC
|
||||
// is the one we were looking for.
|
||||
|
||||
// See if we can retrieve the ttc data as a first try
|
||||
let ttc_table = 0x66637474; // 'ttcf'
|
||||
|
||||
let ttc_size = GetFontData(hdc, ttc_table, 0, std::ptr::null_mut(), 0);
|
||||
|
||||
let result = if ttc_size > 0 && ttc_size != GDI_ERROR {
|
||||
let mut data = vec![0u8; ttc_size as usize];
|
||||
GetFontData(hdc, ttc_table, 0, data.as_mut_ptr() as *mut _, ttc_size);
|
||||
|
||||
// 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,
|
||||
index,
|
||||
name: attr.family.clone(),
|
||||
})
|
||||
} else {
|
||||
// Otherwise: presumably a regular ttf
|
||||
|
||||
let size = GetFontData(hdc, 0, 0, std::ptr::null_mut(), 0);
|
||||
match size {
|
||||
_ if size > 0 && size != GDI_ERROR => {
|
||||
let mut data = vec![0u8; size as usize];
|
||||
GetFontData(hdc, 0, 0, data.as_mut_ptr() as *mut _, size);
|
||||
Ok(FontDataHandle::Memory {
|
||||
data,
|
||||
index: 0,
|
||||
name: attr.family.clone(),
|
||||
})
|
||||
}
|
||||
_ => Err(anyhow::anyhow!("Failed to get font data")),
|
||||
}
|
||||
_ => Err(anyhow::anyhow!("Failed to get font data")),
|
||||
};
|
||||
|
||||
DeleteDC(hdc);
|
||||
result
|
||||
}
|
||||
@ -45,6 +79,43 @@ fn wide_string(s: &str) -> Vec<u16> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn load_font(font_attr: &FontAttributes) -> anyhow::Result<FontDataHandle> {
|
||||
let mut log_font = LOGFONTW {
|
||||
lfHeight: 0,
|
||||
lfWidth: 0,
|
||||
lfEscapement: 0,
|
||||
lfOrientation: 0,
|
||||
lfWeight: if font_attr.bold { 700 } else { 0 },
|
||||
lfItalic: if font_attr.italic { 1 } else { 0 },
|
||||
lfUnderline: 0,
|
||||
lfStrikeOut: 0,
|
||||
lfCharSet: 0,
|
||||
lfOutPrecision: OUT_TT_ONLY_PRECIS as u8,
|
||||
lfClipPrecision: 0,
|
||||
lfQuality: 0,
|
||||
lfPitchAndFamily: FIXED_PITCH as u8,
|
||||
lfFaceName: [0u16; 32],
|
||||
};
|
||||
|
||||
let name = wide_string(&font_attr.family);
|
||||
if name.len() > LF_FACESIZE {
|
||||
anyhow::bail!(
|
||||
"family name {:?} is too large for LOGFONTW",
|
||||
font_attr.family
|
||||
);
|
||||
}
|
||||
for (i, &c) in name.iter().enumerate() {
|
||||
log_font.lfFaceName[i] = c;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let font = CreateFontIndirectW(&log_font);
|
||||
let result = extract_font_data(font, font_attr);
|
||||
DeleteObject(font as *mut _);
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl FontLocator for GdiFontLocator {
|
||||
fn load_fonts(
|
||||
&self,
|
||||
@ -53,43 +124,7 @@ impl FontLocator for GdiFontLocator {
|
||||
) -> anyhow::Result<Vec<FontDataHandle>> {
|
||||
let mut fonts = Vec::new();
|
||||
for font_attr in fonts_selection {
|
||||
let mut log_font = LOGFONTW {
|
||||
lfHeight: 0,
|
||||
lfWidth: 0,
|
||||
lfEscapement: 0,
|
||||
lfOrientation: 0,
|
||||
lfWeight: if font_attr.bold { 700 } else { 0 },
|
||||
lfItalic: if font_attr.italic { 1 } else { 0 },
|
||||
lfUnderline: 0,
|
||||
lfStrikeOut: 0,
|
||||
lfCharSet: 0,
|
||||
lfOutPrecision: OUT_TT_ONLY_PRECIS as u8,
|
||||
lfClipPrecision: 0,
|
||||
lfQuality: 0,
|
||||
lfPitchAndFamily: FIXED_PITCH as u8,
|
||||
lfFaceName: [0u16; 32],
|
||||
};
|
||||
|
||||
let name = wide_string(&font_attr.family);
|
||||
if name.len() > LF_FACESIZE {
|
||||
log::error!(
|
||||
"family name {:?} is too large for LOGFONTW",
|
||||
font_attr.family
|
||||
);
|
||||
continue;
|
||||
}
|
||||
for (i, &c) in name.iter().enumerate() {
|
||||
log_font.lfFaceName[i] = c;
|
||||
}
|
||||
|
||||
let handle = unsafe {
|
||||
let font = CreateFontIndirectW(&log_font);
|
||||
let result = extract_font_data(font, &font_attr.family);
|
||||
DeleteObject(font as *mut _);
|
||||
result
|
||||
};
|
||||
|
||||
if let Ok(handle) = handle {
|
||||
if let Ok(handle) = load_font(font_attr) {
|
||||
if let Ok(parsed) = crate::parser::ParsedFont::from_locator(&handle) {
|
||||
if crate::parser::font_info_matches(font_attr, parsed.names()) {
|
||||
fonts.push(handle);
|
||||
@ -104,8 +139,103 @@ impl FontLocator for GdiFontLocator {
|
||||
|
||||
fn locate_fallback_for_codepoints(
|
||||
&self,
|
||||
_codepoints: &[char],
|
||||
codepoints: &[char],
|
||||
) -> anyhow::Result<Vec<FontDataHandle>> {
|
||||
Ok(vec![])
|
||||
let text: Vec<u16> = codepoints
|
||||
.iter()
|
||||
.map(|&c| c as u16)
|
||||
.chain(std::iter::once(0))
|
||||
.collect();
|
||||
|
||||
let collection = dwrote::FontCollection::system();
|
||||
struct Source {
|
||||
locale: String,
|
||||
len: u32,
|
||||
};
|
||||
impl dwrote::TextAnalysisSourceMethods for Source {
|
||||
fn get_locale_name<'a>(&'a self, _: u32) -> (Cow<'a, str>, u32) {
|
||||
(Cow::Borrowed(&self.locale), self.len)
|
||||
}
|
||||
fn get_paragraph_reading_direction(&self) -> u32 {
|
||||
DWRITE_READING_DIRECTION_LEFT_TO_RIGHT
|
||||
}
|
||||
}
|
||||
|
||||
let source = dwrote::TextAnalysisSource::from_text(
|
||||
Box::new(Source {
|
||||
locale: "".to_string(),
|
||||
len: codepoints.len() as u32,
|
||||
}),
|
||||
Cow::Borrowed(&text),
|
||||
);
|
||||
|
||||
let mut handles = vec![];
|
||||
let mut resolved = HashSet::new();
|
||||
|
||||
if let Some(fallback) = dwrote::FontFallback::get_system_fallback() {
|
||||
let mut start = 0usize;
|
||||
let mut len = codepoints.len();
|
||||
loop {
|
||||
let result = fallback.map_characters(
|
||||
&source,
|
||||
start as u32,
|
||||
len as u32,
|
||||
&collection,
|
||||
None,
|
||||
FontWeight::Regular,
|
||||
FontStyle::Normal,
|
||||
FontStretch::Normal,
|
||||
);
|
||||
|
||||
if let Some(font) = result.mapped_font {
|
||||
log::trace!(
|
||||
"DirectWrite Suggested fallback: {} {}",
|
||||
font.family_name(),
|
||||
font.face_name()
|
||||
);
|
||||
let attr = FontAttributes {
|
||||
bold: match font.weight() {
|
||||
FontWeight::Thin
|
||||
| FontWeight::ExtraLight
|
||||
| FontWeight::Light
|
||||
| FontWeight::SemiLight
|
||||
| FontWeight::Regular
|
||||
| FontWeight::Medium => false,
|
||||
FontWeight::SemiBold
|
||||
| FontWeight::Bold
|
||||
| FontWeight::ExtraBold
|
||||
| FontWeight::Black
|
||||
| FontWeight::ExtraBlack => true,
|
||||
FontWeight::Unknown(n) => n > 80,
|
||||
},
|
||||
italic: false,
|
||||
family: font.family_name(),
|
||||
is_fallback: true,
|
||||
};
|
||||
|
||||
if !resolved.contains(&attr) {
|
||||
resolved.insert(attr.clone());
|
||||
|
||||
match load_font(&attr) {
|
||||
Ok(handle) => handles.push(handle),
|
||||
Err(err) => log::error!("Failed to load {:?} {:?}", attr, err),
|
||||
}
|
||||
}
|
||||
}
|
||||
if result.mapped_length > 0 {
|
||||
start += result.mapped_length
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if start == codepoints.len() {
|
||||
break;
|
||||
}
|
||||
len = codepoints.len() - start;
|
||||
}
|
||||
} else {
|
||||
log::error!("Unable to get system fallback from dwrote");
|
||||
}
|
||||
|
||||
Ok(handles)
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ use allsorts::tables::{
|
||||
HeadTable, HheaTable, HmtxTable, MaxpTable, OffsetTable, OpenTypeFile, OpenTypeFont,
|
||||
};
|
||||
use allsorts::tag;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::{anyhow, Context};
|
||||
use config::{Config, FontAttributes};
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryInto;
|
||||
@ -62,7 +62,7 @@ pub struct Names {
|
||||
impl Names {
|
||||
fn from_name_table_data(name_table: &[u8]) -> anyhow::Result<Names> {
|
||||
Ok(Names {
|
||||
full_name: get_name(name_table, 4)?,
|
||||
full_name: get_name(name_table, 4).context("full_name")?,
|
||||
unique: get_name(name_table, 3).ok(),
|
||||
family: get_name(name_table, 1).ok(),
|
||||
sub_family: get_name(name_table, 2).ok(),
|
||||
@ -160,21 +160,30 @@ impl ParsedFont {
|
||||
// extend the lifetime of the OpenTypeFile that we produce here.
|
||||
// That in turn allows us to store all of these derived items
|
||||
// into a struct and manage their lifetimes together.
|
||||
let file: OpenTypeFile<'static> =
|
||||
unsafe { std::mem::transmute(owned_scope.scope().read::<OpenTypeFile>()?) };
|
||||
let file: OpenTypeFile<'static> = unsafe {
|
||||
std::mem::transmute(
|
||||
owned_scope
|
||||
.scope()
|
||||
.read::<OpenTypeFile>()
|
||||
.context("read OpenTypeFile")?,
|
||||
)
|
||||
};
|
||||
|
||||
let otf = locate_offset_table(&file, index)?;
|
||||
let name_table = name_table_data(&otf, &file.scope)?;
|
||||
let names = Names::from_name_table_data(name_table)?;
|
||||
let otf = locate_offset_table(&file, index).context("locate_offset_table")?;
|
||||
let name_table = name_table_data(&otf, &file.scope).context("name_table_data")?;
|
||||
let names =
|
||||
Names::from_name_table_data(name_table).context("Names::from_name_table_data")?;
|
||||
|
||||
let head = otf
|
||||
.read_table(&file.scope, tag::HEAD)?
|
||||
.ok_or_else(|| anyhow!("HEAD table missing or broken"))?
|
||||
.read::<HeadTable>()?;
|
||||
.read::<HeadTable>()
|
||||
.context("read HeadTable")?;
|
||||
let cmap = otf
|
||||
.read_table(&file.scope, tag::CMAP)?
|
||||
.ok_or_else(|| anyhow!("CMAP table missing or broken"))?
|
||||
.read::<Cmap>()?;
|
||||
.read::<Cmap>()
|
||||
.context("read Cmap")?;
|
||||
let cmap_subtable: CmapSubtable<'static> = read_cmap_subtable(&cmap)?
|
||||
.ok_or_else(|| anyhow!("CMAP subtable not found"))?
|
||||
.1;
|
||||
@ -182,30 +191,37 @@ impl ParsedFont {
|
||||
let maxp = otf
|
||||
.read_table(&file.scope, tag::MAXP)?
|
||||
.ok_or_else(|| anyhow!("MAXP table not found"))?
|
||||
.read::<MaxpTable>()?;
|
||||
.read::<MaxpTable>()
|
||||
.context("read MaxpTable")?;
|
||||
let num_glyphs = maxp.num_glyphs;
|
||||
|
||||
let post = otf
|
||||
.read_table(&file.scope, tag::POST)?
|
||||
.ok_or_else(|| anyhow!("POST table not found"))?
|
||||
.read::<PostTable>()?;
|
||||
.read::<PostTable>()
|
||||
.context("read PostTable")?;
|
||||
|
||||
let hhea = otf
|
||||
.read_table(&file.scope, tag::HHEA)?
|
||||
.ok_or_else(|| anyhow!("HHEA table not found"))?
|
||||
.read::<HheaTable>()?;
|
||||
.read::<HheaTable>()
|
||||
.context("read HheaTable")?;
|
||||
let hmtx = otf
|
||||
.read_table(&file.scope, tag::HMTX)?
|
||||
.ok_or_else(|| anyhow!("HMTX table not found"))?
|
||||
.read_dep::<HmtxTable>((
|
||||
usize::from(maxp.num_glyphs),
|
||||
usize::from(hhea.num_h_metrics),
|
||||
))?;
|
||||
))
|
||||
.context("read_dep HmtxTable")?;
|
||||
|
||||
let gdef_table: Option<GDEFTable> = otf
|
||||
.find_table_record(tag::GDEF)
|
||||
.map(|gdef_record| -> anyhow::Result<GDEFTable> {
|
||||
Ok(gdef_record.read_table(&file.scope)?.read::<GDEFTable>()?)
|
||||
Ok(gdef_record
|
||||
.read_table(&file.scope)?
|
||||
.read::<GDEFTable>()
|
||||
.context("read GDEFTable")?)
|
||||
})
|
||||
.transpose()?;
|
||||
let opt_gpos_table = otf
|
||||
@ -213,7 +229,8 @@ impl ParsedFont {
|
||||
.map(|gpos_record| -> anyhow::Result<LayoutTable<GPOS>> {
|
||||
Ok(gpos_record
|
||||
.read_table(&file.scope)?
|
||||
.read::<LayoutTable<GPOS>>()?)
|
||||
.read::<LayoutTable<GPOS>>()
|
||||
.context("read LayoutTable<GPOS>")?)
|
||||
})
|
||||
.transpose()?;
|
||||
let gpos_cache = opt_gpos_table.map(new_layout_cache);
|
||||
@ -221,7 +238,10 @@ impl ParsedFont {
|
||||
let gsub_cache = otf
|
||||
.find_table_record(tag::GSUB)
|
||||
.map(|gsub| -> anyhow::Result<LayoutTable<GSUB>> {
|
||||
Ok(gsub.read_table(&file.scope)?.read::<LayoutTable<GSUB>>()?)
|
||||
Ok(gsub
|
||||
.read_table(&file.scope)?
|
||||
.read::<LayoutTable<GSUB>>()
|
||||
.context("read LayoutTable<GSUB>")?)
|
||||
})
|
||||
.transpose()?
|
||||
.map(new_layout_cache);
|
||||
@ -559,6 +579,50 @@ pub fn font_info_matches(attr: &FontAttributes, names: &Names) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a blob representing a True Type Collection (.ttc) file,
|
||||
/// and a desired font, enumerate the collection to resolve the index of
|
||||
/// the font inside that collection that matches it.
|
||||
/// Even though this is intended to work with a TTC, this also returns
|
||||
/// the index of a singular TTF file, if it matches.
|
||||
pub fn resolve_font_from_ttc_data(
|
||||
attr: &FontAttributes,
|
||||
data: &[u8],
|
||||
) -> anyhow::Result<Option<usize>> {
|
||||
let scope = allsorts::binary::read::ReadScope::new(&data);
|
||||
let file = scope.read::<OpenTypeFile>()?;
|
||||
|
||||
match &file.font {
|
||||
OpenTypeFont::Single(ttf) => {
|
||||
let name_table_data = ttf
|
||||
.read_table(&file.scope, allsorts::tag::NAME)?
|
||||
.ok_or_else(|| anyhow!("name table is not present"))?;
|
||||
|
||||
let names = Names::from_name_table_data(name_table_data.data())?;
|
||||
if font_info_matches(attr, &names) {
|
||||
Ok(Some(0))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
OpenTypeFont::Collection(ttc) => {
|
||||
for (index, offset_table_offset) in ttc.offset_tables.iter().enumerate() {
|
||||
let ttf = file
|
||||
.scope
|
||||
.offset(offset_table_offset as usize)
|
||||
.read::<OffsetTable>()?;
|
||||
let name_table_data = ttf
|
||||
.read_table(&file.scope, allsorts::tag::NAME)?
|
||||
.ok_or_else(|| anyhow!("name table is not present"))?;
|
||||
let names = Names::from_name_table_data(name_table_data.data())?;
|
||||
if font_info_matches(attr, &names) {
|
||||
return Ok(Some(index));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// In case the user has a broken configuration, or no configuration,
|
||||
/// we bundle JetBrains Mono and Noto Color Emoji to act as reasonably
|
||||
/// sane fallback fonts.
|
||||
@ -696,7 +760,8 @@ fn name_table_data<'a>(otf: &OffsetTable<'a>, scope: &ReadScope<'a>) -> anyhow::
|
||||
|
||||
/// Extract a name from the name table
|
||||
fn get_name(name_table_data: &[u8], name_id: u16) -> anyhow::Result<String> {
|
||||
let cstr = allsorts::get_name::fontcode_get_name(name_table_data, name_id)?
|
||||
let cstr = allsorts::get_name::fontcode_get_name(name_table_data, name_id)
|
||||
.with_context(|| anyhow!("fontcode_get_name name_id:{}", name_id))?
|
||||
.ok_or_else(|| anyhow!("name_id {} not found", name_id))?;
|
||||
cstr.into_string()
|
||||
.map_err(|e| anyhow!("name_id {} is not representable as String: {}", name_id, e))
|
||||
|
Loading…
Reference in New Issue
Block a user