cosmic-text/src/font/mod.rs
Mohammad AlSaleh 329941c4a6 Try harder to succeed at fall-backing to a Monospace font
A combination of some ideas:

 * Try all Monospace fonts before giving up.
 * Relax exact weight restriction on font matching when trying Monospace
   fall-back. Try smaller weights if needed.
 * Make the fall-back try order weight-offset aware, AND script-aware.
 * And finally, add the option to adjust the font size of glyphs using
   fall-back Monospace fonts, so the width of them matches the default
   font width.

   For my use-case, the current fall-back attempt always fails with
   Arabic script. And none of the Arabic-supporting Monospace fonts in
   my system also support medium weight. So, if my default font is set
   to medium weight, script-aware fall-back alone will still not work.

Signed-off-by: Mohammad AlSaleh <CE.Mohammad.AlSaleh@gmail.com>
2024-01-17 13:31:03 -07:00

128 lines
3.4 KiB
Rust

// SPDX-License-Identifier: MIT OR Apache-2.0
pub(crate) mod fallback;
use core::fmt;
use alloc::sync::Arc;
use rustybuzz::Face as RustybuzzFace;
use self_cell::self_cell;
pub use self::system::*;
mod system;
self_cell!(
struct OwnedFace {
owner: Arc<dyn AsRef<[u8]> + Send + Sync>,
#[covariant]
dependent: RustybuzzFace,
}
);
/// A font
pub struct Font {
#[cfg(feature = "swash")]
swash: (u32, swash::CacheKey),
rustybuzz: OwnedFace,
data: Arc<dyn AsRef<[u8]> + Send + Sync>,
id: fontdb::ID,
monospace_em_width: Option<f32>,
scripts: Vec<[u8; 4]>,
}
impl fmt::Debug for Font {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Font")
.field("id", &self.id)
.finish_non_exhaustive()
}
}
impl Font {
pub fn id(&self) -> fontdb::ID {
self.id
}
pub fn monospace_em_width(&self) -> Option<f32> {
self.monospace_em_width
}
pub fn scripts(&self) -> &[[u8; 4]] {
&self.scripts
}
pub fn data(&self) -> &[u8] {
(*self.data).as_ref()
}
pub fn rustybuzz(&self) -> &RustybuzzFace<'_> {
self.rustybuzz.borrow_dependent()
}
#[cfg(feature = "swash")]
pub fn as_swash(&self) -> swash::FontRef<'_> {
let swash = &self.swash;
swash::FontRef {
data: self.data(),
offset: swash.0,
key: swash.1,
}
}
}
impl Font {
pub fn new(db: &fontdb::Database, id: fontdb::ID) -> Option<Self> {
let info = db.face(id)?;
let (monospace_em_width, scripts) = {
db.with_face_data(id, |font_data, face_index| {
let face = ttf_parser::Face::parse(font_data, face_index).ok()?;
let monospace_em_width = info.monospaced.then(|| {
let hor_advance = face.glyph_hor_advance(face.glyph_index(' ')?)? as f32;
let upem = face.units_per_em() as f32;
Some(hor_advance/upem)
}).flatten();
if info.monospaced && monospace_em_width.is_none() {
None?;
}
let scripts = face.tables().gpos.into_iter()
.chain(face.tables().gsub)
.map(|table| table.scripts)
.flatten()
.map(|script| script.tag.to_bytes())
.collect();
Some((monospace_em_width, scripts))
})?
}?;
let data = match &info.source {
fontdb::Source::Binary(data) => Arc::clone(data),
#[cfg(feature = "std")]
fontdb::Source::File(path) => {
log::warn!("Unsupported fontdb Source::File('{}')", path.display());
return None;
}
#[cfg(feature = "std")]
fontdb::Source::SharedFile(_path, data) => Arc::clone(data),
};
Some(Self {
id: info.id,
monospace_em_width,
scripts,
#[cfg(feature = "swash")]
swash: {
let swash = swash::FontRef::from_index((*data).as_ref(), info.index as usize)?;
(swash.offset, swash.key)
},
rustybuzz: OwnedFace::try_new(Arc::clone(&data), |data| {
RustybuzzFace::from_slice((**data).as_ref(), info.index).ok_or(())
})
.ok()?,
data,
})
}
}