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>
This commit is contained in:
parent
054b7da828
commit
329941c4a6
8 changed files with 177 additions and 39 deletions
|
|
@ -24,6 +24,7 @@ self_cell = "1.0.1"
|
||||||
swash = { version = "0.1.8", optional = true }
|
swash = { version = "0.1.8", optional = true }
|
||||||
syntect = { version = "5.1.0", optional = true }
|
syntect = { version = "5.1.0", optional = true }
|
||||||
sys-locale = { version = "0.3.1", optional = true }
|
sys-locale = { version = "0.3.1", optional = true }
|
||||||
|
ttf-parser = "0.20.0"
|
||||||
unicode-linebreak = "0.1.5"
|
unicode-linebreak = "0.1.5"
|
||||||
unicode-script = "0.5.5"
|
unicode-script = "0.5.5"
|
||||||
unicode-segmentation = "1.10.1"
|
unicode-segmentation = "1.10.1"
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,8 @@ impl<'a> Attrs<'a> {
|
||||||
//TODO: smarter way of including emoji
|
//TODO: smarter way of including emoji
|
||||||
face.post_script_name.contains("Emoji")
|
face.post_script_name.contains("Emoji")
|
||||||
|| (face.style == self.style
|
|| (face.style == self.style
|
||||||
&& face.weight == self.weight
|
// Relax exact weight matching for the Monospace fallback use-case
|
||||||
|
&& face.weight <= self.weight
|
||||||
&& face.stretch == self.stretch)
|
&& face.stretch == self.stretch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -232,6 +232,7 @@ pub struct Buffer {
|
||||||
/// True if a redraw is requires. Set to false after processing
|
/// True if a redraw is requires. Set to false after processing
|
||||||
redraw: bool,
|
redraw: bool,
|
||||||
wrap: Wrap,
|
wrap: Wrap,
|
||||||
|
monospace_width: Option<f32>,
|
||||||
|
|
||||||
/// Scratch buffer for shaping and laying out.
|
/// Scratch buffer for shaping and laying out.
|
||||||
scratch: ShapeBuffer,
|
scratch: ShapeBuffer,
|
||||||
|
|
@ -247,6 +248,7 @@ impl Clone for Buffer {
|
||||||
scroll: self.scroll,
|
scroll: self.scroll,
|
||||||
redraw: self.redraw,
|
redraw: self.redraw,
|
||||||
wrap: self.wrap,
|
wrap: self.wrap,
|
||||||
|
monospace_width: self.monospace_width,
|
||||||
scratch: ShapeBuffer::default(),
|
scratch: ShapeBuffer::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -275,6 +277,7 @@ impl Buffer {
|
||||||
redraw: false,
|
redraw: false,
|
||||||
wrap: Wrap::Word,
|
wrap: Wrap::Word,
|
||||||
scratch: ShapeBuffer::default(),
|
scratch: ShapeBuffer::default(),
|
||||||
|
monospace_width: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -313,6 +316,7 @@ impl Buffer {
|
||||||
self.metrics.font_size,
|
self.metrics.font_size,
|
||||||
self.width,
|
self.width,
|
||||||
self.wrap,
|
self.wrap,
|
||||||
|
self.monospace_width,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -492,6 +496,7 @@ impl Buffer {
|
||||||
self.metrics.font_size,
|
self.metrics.font_size,
|
||||||
self.width,
|
self.width,
|
||||||
self.wrap,
|
self.wrap,
|
||||||
|
self.monospace_width,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -523,6 +528,20 @@ impl Buffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current `monospace_width`
|
||||||
|
pub fn monospace_width(&self) -> Option<f32> {
|
||||||
|
self.monospace_width
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set monospace width monospace glyphs should be resized to match. `None` means don't resize
|
||||||
|
pub fn set_monospace_width(&mut self, font_system: &mut FontSystem, monospace_width: Option<f32>) {
|
||||||
|
if monospace_width != self.monospace_width {
|
||||||
|
self.monospace_width = monospace_width;
|
||||||
|
self.relayout(font_system);
|
||||||
|
self.shape_until_scroll(font_system, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the current buffer dimensions (width, height)
|
/// Get the current buffer dimensions (width, height)
|
||||||
pub fn size(&self) -> (f32, f32) {
|
pub fn size(&self) -> (f32, f32) {
|
||||||
(self.width, self.height)
|
(self.width, self.height)
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,7 @@ impl BufferLine {
|
||||||
font_size: f32,
|
font_size: f32,
|
||||||
width: f32,
|
width: f32,
|
||||||
wrap: Wrap,
|
wrap: Wrap,
|
||||||
|
match_mono_width: Option<f32>,
|
||||||
) -> &[LayoutLine] {
|
) -> &[LayoutLine] {
|
||||||
self.layout_in_buffer(
|
self.layout_in_buffer(
|
||||||
&mut ShapeBuffer::default(),
|
&mut ShapeBuffer::default(),
|
||||||
|
|
@ -191,6 +192,7 @@ impl BufferLine {
|
||||||
font_size,
|
font_size,
|
||||||
width,
|
width,
|
||||||
wrap,
|
wrap,
|
||||||
|
match_mono_width,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,12 +204,13 @@ impl BufferLine {
|
||||||
font_size: f32,
|
font_size: f32,
|
||||||
width: f32,
|
width: f32,
|
||||||
wrap: Wrap,
|
wrap: Wrap,
|
||||||
|
match_mono_width: Option<f32>,
|
||||||
) -> &[LayoutLine] {
|
) -> &[LayoutLine] {
|
||||||
if self.layout_opt.is_none() {
|
if self.layout_opt.is_none() {
|
||||||
let align = self.align;
|
let align = self.align;
|
||||||
let shape = self.shape_in_buffer(scratch, font_system);
|
let shape = self.shape_in_buffer(scratch, font_system);
|
||||||
let mut layout = Vec::with_capacity(1);
|
let mut layout = Vec::with_capacity(1);
|
||||||
shape.layout_to_buffer(scratch, font_size, width, wrap, align, &mut layout);
|
shape.layout_to_buffer(scratch, font_size, width, wrap, align, &mut layout, match_mono_width);
|
||||||
self.layout_opt = Some(layout);
|
self.layout_opt = Some(layout);
|
||||||
}
|
}
|
||||||
self.layout_opt.as_ref().expect("layout not found")
|
self.layout_opt.as_ref().expect("layout not found")
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,11 @@
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
#[cfg(not(feature = "std"))]
|
#[cfg(not(feature = "std"))]
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
use alloc::collections::BTreeSet;
|
||||||
use fontdb::Family;
|
use fontdb::Family;
|
||||||
use unicode_script::Script;
|
use unicode_script::Script;
|
||||||
|
|
||||||
use crate::{Font, FontSystem, ShapePlanCache};
|
use crate::{Font, FontSystem, ShapePlanCache, FontMatchKey};
|
||||||
|
|
||||||
use self::platform::*;
|
use self::platform::*;
|
||||||
|
|
||||||
|
|
@ -31,10 +32,21 @@ use log::debug as missing_warn;
|
||||||
#[cfg(feature = "warn_on_missing_glyphs")]
|
#[cfg(feature = "warn_on_missing_glyphs")]
|
||||||
use log::warn as missing_warn;
|
use log::warn as missing_warn;
|
||||||
|
|
||||||
|
// Match on lowest weight_offset, then script_non_matches
|
||||||
|
// Default font gets None for both `weight_offset` and `script_non_matches`, and thus, it is
|
||||||
|
// always the first to be popped from the set.
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct MonospaceFallbackInfo {
|
||||||
|
weight_offset: Option<u16>,
|
||||||
|
script_non_matches: Option<usize>,
|
||||||
|
id: fontdb::ID,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct FontFallbackIter<'a> {
|
pub struct FontFallbackIter<'a> {
|
||||||
font_system: &'a mut FontSystem,
|
font_system: &'a mut FontSystem,
|
||||||
font_ids: &'a [fontdb::ID],
|
font_match_keys: &'a [FontMatchKey],
|
||||||
default_families: &'a [&'a Family<'a>],
|
default_families: &'a [&'a Family<'a>],
|
||||||
|
monospace_fallbacks: BTreeSet<MonospaceFallbackInfo>,
|
||||||
default_i: usize,
|
default_i: usize,
|
||||||
scripts: &'a [Script],
|
scripts: &'a [Script],
|
||||||
script_i: (usize, usize),
|
script_i: (usize, usize),
|
||||||
|
|
@ -46,14 +58,15 @@ pub struct FontFallbackIter<'a> {
|
||||||
impl<'a> FontFallbackIter<'a> {
|
impl<'a> FontFallbackIter<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
font_system: &'a mut FontSystem,
|
font_system: &'a mut FontSystem,
|
||||||
font_ids: &'a [fontdb::ID],
|
font_match_keys: &'a [FontMatchKey],
|
||||||
default_families: &'a [&'a Family<'a>],
|
default_families: &'a [&'a Family<'a>],
|
||||||
scripts: &'a [Script],
|
scripts: &'a [Script],
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
font_system,
|
font_system,
|
||||||
font_ids,
|
font_match_keys,
|
||||||
default_families,
|
default_families,
|
||||||
|
monospace_fallbacks: BTreeSet::new(),
|
||||||
default_i: 0,
|
default_i: 0,
|
||||||
scripts,
|
scripts,
|
||||||
script_i: (0, 0),
|
script_i: (0, 0),
|
||||||
|
|
@ -76,7 +89,7 @@ impl<'a> FontFallbackIter<'a> {
|
||||||
"Failed to find preset fallback for {:?} locale '{}', used '{}': '{}'",
|
"Failed to find preset fallback for {:?} locale '{}', used '{}': '{}'",
|
||||||
self.scripts,
|
self.scripts,
|
||||||
self.font_system.locale(),
|
self.font_system.locale(),
|
||||||
self.face_name(self.font_ids[self.other_i - 1]),
|
self.face_name(self.font_match_keys[self.other_i - 1].id),
|
||||||
word
|
word
|
||||||
);
|
);
|
||||||
} else if !self.scripts.is_empty() && self.common_i > 0 {
|
} else if !self.scripts.is_empty() && self.common_i > 0 {
|
||||||
|
|
@ -119,34 +132,71 @@ impl<'a> FontFallbackIter<'a> {
|
||||||
impl<'a> Iterator for FontFallbackIter<'a> {
|
impl<'a> Iterator for FontFallbackIter<'a> {
|
||||||
type Item = Arc<Font>;
|
type Item = Arc<Font>;
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if let Some(fallback_info) = self.monospace_fallbacks.pop_first() {
|
||||||
|
if let Some(font) = self.font_system.get_font(fallback_info.id) {
|
||||||
|
return Some(font);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let font_match_keys_iter = |is_mono| self.font_match_keys
|
||||||
|
.iter()
|
||||||
|
.filter(move |m_key| m_key.weight_offset == 0 || is_mono);
|
||||||
|
|
||||||
while self.default_i < self.default_families.len() {
|
while self.default_i < self.default_families.len() {
|
||||||
self.default_i += 1;
|
self.default_i += 1;
|
||||||
let mut monospace_fallback = None;
|
let is_mono = self.default_families[self.default_i - 1] == &Family::Monospace;
|
||||||
for id in self.font_ids.iter() {
|
|
||||||
|
for m_key in font_match_keys_iter(is_mono) {
|
||||||
let default_family = self
|
let default_family = self
|
||||||
.font_system
|
.font_system
|
||||||
.db()
|
.db()
|
||||||
.family_name(self.default_families[self.default_i - 1]);
|
.family_name(self.default_families[self.default_i - 1]);
|
||||||
if self.face_contains_family(*id, default_family) {
|
if self.face_contains_family(m_key.id, default_family) {
|
||||||
if let Some(font) = self.font_system.get_font(*id) {
|
if let Some(font) = self.font_system.get_font(m_key.id) {
|
||||||
return Some(font);
|
if !is_mono {
|
||||||
|
return Some(font);
|
||||||
|
} else if m_key.weight_offset == 0 {
|
||||||
|
// Default font
|
||||||
|
let fallback_info = MonospaceFallbackInfo {
|
||||||
|
weight_offset: None,
|
||||||
|
script_non_matches: None,
|
||||||
|
id: m_key.id
|
||||||
|
};
|
||||||
|
assert_eq!(self.monospace_fallbacks.insert(fallback_info), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Set a monospace fallback if Monospace family is not found
|
// Set a monospace fallback if Monospace family is not found
|
||||||
if self.default_families[self.default_i - 1] == &Family::Monospace
|
if is_mono {
|
||||||
&& monospace_fallback.is_none()
|
let script_tags = self.scripts.iter()
|
||||||
{
|
.filter_map(|script| {
|
||||||
if let Some(face_info) = self.font_system.db().face(*id) {
|
let script_as_lower = script.short_name().to_lowercase();
|
||||||
|
<[u8; 4]>::try_from(script_as_lower.as_bytes()).ok()
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if let Some(face_info) = self.font_system.db().face(m_key.id) {
|
||||||
// Don't use emoji fonts as Monospace
|
// Don't use emoji fonts as Monospace
|
||||||
if face_info.monospaced && !face_info.post_script_name.contains("Emoji") {
|
if face_info.monospaced && !face_info.post_script_name.contains("Emoji") {
|
||||||
monospace_fallback = Some(id);
|
if let Some(font) = self.font_system.get_font(m_key.id) {
|
||||||
|
let script_non_matches = self.scripts.len() - script_tags.iter()
|
||||||
|
.filter(|&&script_tag| {
|
||||||
|
font.scripts().iter().any(|&tag_bytes| tag_bytes == script_tag)
|
||||||
|
}).count();
|
||||||
|
|
||||||
|
let fallback_info = MonospaceFallbackInfo {
|
||||||
|
weight_offset: Some(m_key.weight_offset),
|
||||||
|
script_non_matches: Some(script_non_matches),
|
||||||
|
id: m_key.id
|
||||||
|
};
|
||||||
|
assert_eq!(self.monospace_fallbacks.insert(fallback_info), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If default family is Monospace fallback to first monospaced font
|
// If default family is Monospace fallback to first monospaced font
|
||||||
if let Some(id) = monospace_fallback {
|
if let Some(fallback_info) = self.monospace_fallbacks.pop_first() {
|
||||||
if let Some(font) = self.font_system.get_font(*id) {
|
if let Some(font) = self.font_system.get_font(fallback_info.id) {
|
||||||
return Some(font);
|
return Some(font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -159,9 +209,9 @@ impl<'a> Iterator for FontFallbackIter<'a> {
|
||||||
while self.script_i.1 < script_families.len() {
|
while self.script_i.1 < script_families.len() {
|
||||||
let script_family = script_families[self.script_i.1];
|
let script_family = script_families[self.script_i.1];
|
||||||
self.script_i.1 += 1;
|
self.script_i.1 += 1;
|
||||||
for id in self.font_ids.iter() {
|
for m_key in font_match_keys_iter(false) {
|
||||||
if self.face_contains_family(*id, script_family) {
|
if self.face_contains_family(m_key.id, script_family) {
|
||||||
if let Some(font) = self.font_system.get_font(*id) {
|
if let Some(font) = self.font_system.get_font(m_key.id) {
|
||||||
return Some(font);
|
return Some(font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -182,9 +232,9 @@ impl<'a> Iterator for FontFallbackIter<'a> {
|
||||||
while self.common_i < common_families.len() {
|
while self.common_i < common_families.len() {
|
||||||
let common_family = common_families[self.common_i];
|
let common_family = common_families[self.common_i];
|
||||||
self.common_i += 1;
|
self.common_i += 1;
|
||||||
for id in self.font_ids.iter() {
|
for m_key in font_match_keys_iter(false) {
|
||||||
if self.face_contains_family(*id, common_family) {
|
if self.face_contains_family(m_key.id, common_family) {
|
||||||
if let Some(font) = self.font_system.get_font(*id) {
|
if let Some(font) = self.font_system.get_font(m_key.id) {
|
||||||
return Some(font);
|
return Some(font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -195,8 +245,8 @@ impl<'a> Iterator for FontFallbackIter<'a> {
|
||||||
//TODO: do we need to do this?
|
//TODO: do we need to do this?
|
||||||
//TODO: do not evaluate fonts more than once!
|
//TODO: do not evaluate fonts more than once!
|
||||||
let forbidden_families = forbidden_fallback();
|
let forbidden_families = forbidden_fallback();
|
||||||
while self.other_i < self.font_ids.len() {
|
while self.other_i < self.font_match_keys.len() {
|
||||||
let id = self.font_ids[self.other_i];
|
let id = self.font_match_keys[self.other_i].id;
|
||||||
self.other_i += 1;
|
self.other_i += 1;
|
||||||
if forbidden_families
|
if forbidden_families
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ pub struct Font {
|
||||||
rustybuzz: OwnedFace,
|
rustybuzz: OwnedFace,
|
||||||
data: Arc<dyn AsRef<[u8]> + Send + Sync>,
|
data: Arc<dyn AsRef<[u8]> + Send + Sync>,
|
||||||
id: fontdb::ID,
|
id: fontdb::ID,
|
||||||
|
monospace_em_width: Option<f32>,
|
||||||
|
scripts: Vec<[u8; 4]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Font {
|
impl fmt::Debug for Font {
|
||||||
|
|
@ -42,6 +44,14 @@ impl Font {
|
||||||
self.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] {
|
pub fn data(&self) -> &[u8] {
|
||||||
(*self.data).as_ref()
|
(*self.data).as_ref()
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +72,32 @@ impl Font {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Font {
|
impl Font {
|
||||||
pub fn new(info: &fontdb::FaceInfo) -> Option<Self> {
|
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 {
|
let data = match &info.source {
|
||||||
fontdb::Source::Binary(data) => Arc::clone(data),
|
fontdb::Source::Binary(data) => Arc::clone(data),
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
|
|
@ -76,6 +111,8 @@ impl Font {
|
||||||
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
id: info.id,
|
id: info.id,
|
||||||
|
monospace_em_width,
|
||||||
|
scripts,
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
swash: {
|
swash: {
|
||||||
let swash = swash::FontRef::from_index((*data).as_ref(), info.index as usize)?;
|
let swash = swash::FontRef::from_index((*data).as_ref(), info.index as usize)?;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,12 @@ use core::ops::{Deref, DerefMut};
|
||||||
pub use fontdb;
|
pub use fontdb;
|
||||||
pub use rustybuzz;
|
pub use rustybuzz;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct FontMatchKey {
|
||||||
|
pub(crate) weight_offset: u16,
|
||||||
|
pub(crate) id: fontdb::ID,
|
||||||
|
}
|
||||||
|
|
||||||
/// Access to the system fonts.
|
/// Access to the system fonts.
|
||||||
pub struct FontSystem {
|
pub struct FontSystem {
|
||||||
/// The locale of the system.
|
/// The locale of the system.
|
||||||
|
|
@ -21,7 +27,7 @@ pub struct FontSystem {
|
||||||
font_cache: HashMap<fontdb::ID, Option<Arc<Font>>>,
|
font_cache: HashMap<fontdb::ID, Option<Arc<Font>>>,
|
||||||
|
|
||||||
/// Cache for font matches.
|
/// Cache for font matches.
|
||||||
font_matches_cache: HashMap<AttrsOwned, Arc<Vec<fontdb::ID>>>,
|
font_matches_cache: HashMap<AttrsOwned, Arc<Vec<FontMatchKey>>>,
|
||||||
|
|
||||||
/// Cache for rustybuzz shape plans.
|
/// Cache for rustybuzz shape plans.
|
||||||
shape_plan_cache: ShapePlanCache,
|
shape_plan_cache: ShapePlanCache,
|
||||||
|
|
@ -111,11 +117,10 @@ impl FontSystem {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.db.make_shared_face_data(id);
|
self.db.make_shared_face_data(id);
|
||||||
}
|
}
|
||||||
let face = self.db.face(id)?;
|
match Font::new(&self.db, id) {
|
||||||
match Font::new(face) {
|
|
||||||
Some(font) => Some(Arc::new(font)),
|
Some(font) => Some(Arc::new(font)),
|
||||||
None => {
|
None => {
|
||||||
log::warn!("failed to load font '{}'", face.post_script_name);
|
log::warn!("failed to load font '{}'", self.db.face(id)?.post_script_name);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -123,7 +128,7 @@ impl FontSystem {
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_font_matches(&mut self, attrs: Attrs<'_>) -> Arc<Vec<fontdb::ID>> {
|
pub fn get_font_matches(&mut self, attrs: Attrs<'_>) -> Arc<Vec<FontMatchKey>> {
|
||||||
self.font_matches_cache
|
self.font_matches_cache
|
||||||
//TODO: do not create AttrsOwned unless entry does not already exist
|
//TODO: do not create AttrsOwned unless entry does not already exist
|
||||||
.entry(AttrsOwned::new(attrs))
|
.entry(AttrsOwned::new(attrs))
|
||||||
|
|
@ -131,20 +136,23 @@ impl FontSystem {
|
||||||
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
|
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
|
||||||
let now = std::time::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
|
|
||||||
let ids = self
|
let mut font_match_keys = self
|
||||||
.db
|
.db
|
||||||
.faces()
|
.faces()
|
||||||
.filter(|face| attrs.matches(face))
|
.filter(|face| attrs.matches(face))
|
||||||
.map(|face| face.id)
|
.map(|face| FontMatchKey{ weight_offset: attrs.weight.0 - face.weight.0, id: face.id })
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Sort so we get the keys with weight_offset=0 first
|
||||||
|
font_match_keys.sort();
|
||||||
|
|
||||||
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
|
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
|
||||||
{
|
{
|
||||||
let elapsed = now.elapsed();
|
let elapsed = now.elapsed();
|
||||||
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
|
log::debug!("font matches for {:?} in {:?}", attrs, elapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
Arc::new(ids)
|
Arc::new(font_match_keys)
|
||||||
})
|
})
|
||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
src/shape.rs
25
src/shape.rs
|
|
@ -144,6 +144,7 @@ fn shape_fallback(
|
||||||
y_offset,
|
y_offset,
|
||||||
ascent,
|
ascent,
|
||||||
descent,
|
descent,
|
||||||
|
font_monospace_em_width: font.monospace_em_width(),
|
||||||
font_id: font.id(),
|
font_id: font.id(),
|
||||||
glyph_id: info.glyph_id.try_into().expect("failed to cast glyph ID"),
|
glyph_id: info.glyph_id.try_into().expect("failed to cast glyph ID"),
|
||||||
//TODO: color should not be related to shaping
|
//TODO: color should not be related to shaping
|
||||||
|
|
@ -344,6 +345,7 @@ fn shape_skip(
|
||||||
|
|
||||||
let font = font_iter.next().expect("no default font found");
|
let font = font_iter.next().expect("no default font found");
|
||||||
let font_id = font.id();
|
let font_id = font.id();
|
||||||
|
let font_monospace_em_width = font.monospace_em_width();
|
||||||
let font = font.as_swash();
|
let font = font.as_swash();
|
||||||
|
|
||||||
let charmap = font.charmap();
|
let charmap = font.charmap();
|
||||||
|
|
@ -371,6 +373,7 @@ fn shape_skip(
|
||||||
y_offset: 0.0,
|
y_offset: 0.0,
|
||||||
ascent,
|
ascent,
|
||||||
descent,
|
descent,
|
||||||
|
font_monospace_em_width,
|
||||||
font_id,
|
font_id,
|
||||||
glyph_id,
|
glyph_id,
|
||||||
color_opt: attrs.color_opt,
|
color_opt: attrs.color_opt,
|
||||||
|
|
@ -392,6 +395,7 @@ pub struct ShapeGlyph {
|
||||||
pub y_offset: f32,
|
pub y_offset: f32,
|
||||||
pub ascent: f32,
|
pub ascent: f32,
|
||||||
pub descent: f32,
|
pub descent: f32,
|
||||||
|
pub font_monospace_em_width: Option<f32>,
|
||||||
pub font_id: fontdb::ID,
|
pub font_id: fontdb::ID,
|
||||||
pub glyph_id: u16,
|
pub glyph_id: u16,
|
||||||
pub color_opt: Option<Color>,
|
pub color_opt: Option<Color>,
|
||||||
|
|
@ -875,6 +879,7 @@ impl ShapeLine {
|
||||||
line_width: f32,
|
line_width: f32,
|
||||||
wrap: Wrap,
|
wrap: Wrap,
|
||||||
align: Option<Align>,
|
align: Option<Align>,
|
||||||
|
match_mono_width: Option<f32>,
|
||||||
) -> Vec<LayoutLine> {
|
) -> Vec<LayoutLine> {
|
||||||
let mut lines = Vec::with_capacity(1);
|
let mut lines = Vec::with_capacity(1);
|
||||||
self.layout_to_buffer(
|
self.layout_to_buffer(
|
||||||
|
|
@ -884,6 +889,7 @@ impl ShapeLine {
|
||||||
wrap,
|
wrap,
|
||||||
align,
|
align,
|
||||||
&mut lines,
|
&mut lines,
|
||||||
|
match_mono_width,
|
||||||
);
|
);
|
||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
|
|
@ -896,6 +902,7 @@ impl ShapeLine {
|
||||||
wrap: Wrap,
|
wrap: Wrap,
|
||||||
align: Option<Align>,
|
align: Option<Align>,
|
||||||
layout_lines: &mut Vec<LayoutLine>,
|
layout_lines: &mut Vec<LayoutLine>,
|
||||||
|
match_mono_width: Option<f32>,
|
||||||
) {
|
) {
|
||||||
// For each visual line a list of (span index, and range of words in that span)
|
// For each visual line a list of (span index, and range of words in that span)
|
||||||
// Note that a BiDi visual line could have multiple spans or parts of them
|
// Note that a BiDi visual line could have multiple spans or parts of them
|
||||||
|
|
@ -1236,8 +1243,20 @@ impl ShapeLine {
|
||||||
(false, true) => &word.glyphs[..ending_glyph],
|
(false, true) => &word.glyphs[..ending_glyph],
|
||||||
(true, true) => &word.glyphs[starting_glyph..ending_glyph],
|
(true, true) => &word.glyphs[starting_glyph..ending_glyph],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let match_mono_em_width = match_mono_width.map(|w| w / font_size);
|
||||||
|
|
||||||
for glyph in included_glyphs {
|
for glyph in included_glyphs {
|
||||||
let x_advance = font_size * glyph.x_advance
|
let glyph_font_size = match(match_mono_em_width, glyph.font_monospace_em_width) {
|
||||||
|
(Some(match_em_width), Some(glyph_em_width)) if glyph_em_width != match_em_width => {
|
||||||
|
let glyph_font_size = font_size * (match_em_width / glyph_em_width);
|
||||||
|
log::trace!("Adjusted glyph font size ({font_size} => {glyph_font_size})");
|
||||||
|
glyph_font_size
|
||||||
|
},
|
||||||
|
_ => font_size,
|
||||||
|
};
|
||||||
|
|
||||||
|
let x_advance = glyph_font_size * glyph.x_advance
|
||||||
+ if word.blank {
|
+ if word.blank {
|
||||||
justification_expansion
|
justification_expansion
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1246,8 +1265,8 @@ impl ShapeLine {
|
||||||
if self.rtl {
|
if self.rtl {
|
||||||
x -= x_advance;
|
x -= x_advance;
|
||||||
}
|
}
|
||||||
let y_advance = font_size * glyph.y_advance;
|
let y_advance = glyph_font_size * glyph.y_advance;
|
||||||
glyphs.push(glyph.layout(font_size, x, y, x_advance, span.level));
|
glyphs.push(glyph.layout(glyph_font_size, x, y, x_advance, span.level));
|
||||||
if !self.rtl {
|
if !self.rtl {
|
||||||
x += x_advance;
|
x += x_advance;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue