Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@ type_complexity = "allow"
unit_arg = "allow"
needless_lifetimes = "allow"
neg_cmp_op_on_partial_ord = "allow"

[patch.crates-io.fontique]
path = "../../text/parley/fontique"
45 changes: 20 additions & 25 deletions src/display/text_runs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::conv::{to_u32, to_usize};
use crate::fonts::{self, FontSelector, NoFontMatch};
use crate::format::FormattableText;
use crate::{script_to_fontique, shaper, Direction};
use fontique::UnicodeRange;
use swash::text::cluster::Boundary;
use swash::text::LineBreak as LB;
use unicode_bidi::{BidiClass, BidiInfo, LTR_LEVEL, RTL_LEVEL};
Expand Down Expand Up @@ -168,10 +169,13 @@ impl TextDisplay {
}
}

let script = script_to_fontique(props.script());
if input.script == UNKNOWN_SCRIPT && props.script().is_real() {
input.script = script_to_fontique(props.script());
input.script = script;
}

let unicode_range = UnicodeRange::find(c as u32);

let opt_last_face = if matches!(
classes[pos],
BidiClass::L | BidiClass::R | BidiClass::AL | BidiClass::EN | BidiClass::AN
Expand All @@ -180,12 +184,11 @@ impl TextDisplay {
} else {
face_id
};
let font_id = fonts.select_font(&font, input.script)?;
let font_id = fonts.select_font(&font, script, unicode_range)?;
let new_face_id = fonts
.face_for_char(font_id, opt_last_face, c)
.expect("invalid FontId")
.or(face_id);
let font_break = face_id.is_some() && new_face_id != face_id;
.expect("invalid FontId");
let font_break = face_id.is_some() && Some(new_face_id) != face_id;

if hard_break || control_break || bidi_break || font_break {
// TODO: sometimes this results in empty runs immediately
Expand All @@ -201,7 +204,7 @@ impl TextDisplay {
_ => RunSpecial::NoBreak,
};

let face = face_id.expect("have a set face_id");
let face = face_id.unwrap_or(new_face_id);
self.runs
.push(shaper::shape(input, range, face, breaks, special));

Expand All @@ -218,10 +221,15 @@ impl TextDisplay {

last_is_control = is_control;
last_is_htab = is_htab;
face_id = new_face_id;
face_id = Some(new_face_id);
input.dpem = dpem;
}

let Some(face) = face_id else {
// Text is empty
return Ok(());
};

debug_assert!(analyzer.next().is_none());
let hard_break = last_props
.map(|props| match props.line_break() {
Expand All @@ -241,38 +249,25 @@ impl TextDisplay {
_ => RunSpecial::None,
};

let font_id = fonts.select_font(&font, input.script)?;
if let Some(id) = face_id {
if !fonts.contains_face(font_id, id).expect("invalid FontId") {
face_id = None;
}
}
let face_id =
face_id.unwrap_or_else(|| fonts.first_face_for(font_id).expect("invalid FontId"));
self.runs
.push(shaper::shape(input, range, face_id, breaks, special));
.push(shaper::shape(input, range, face, breaks, special));

// Following a hard break we have an implied empty line.
if hard_break {
let range = (text.len()..text.len()).into();
input.level = default_para_level.unwrap_or(LTR_LEVEL);
breaks = Default::default();
self.runs.push(shaper::shape(
input,
range,
face_id,
breaks,
RunSpecial::None,
));
self.runs
.push(shaper::shape(input, range, face, breaks, RunSpecial::None));
}

/*
println!("text: {}", text);
for run in &self.runs {
let slice = &text[run.range];
print!(
"\t{:?}, text[{}..{}]: '{}', ",
run.level, run.range.start, run.range.end, slice
"\t{:?}, text[{}..{}]: '{}', {:?}, ",
run.level, run.range.start, run.range.end, slice, run.script
);
match run.special {
RunSpecial::None => (),
Expand Down
2 changes: 1 addition & 1 deletion src/fonts/face.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub struct ScaledFaceRef<'a>(&'a Face<'a>, DPU);
impl<'a> ScaledFaceRef<'a> {
/// Unscaled face
#[inline]
pub fn face(&self) -> FaceRef {
pub fn face(&self) -> FaceRef<'_> {
FaceRef(self.0)
}

Expand Down
29 changes: 20 additions & 9 deletions src/fonts/library.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

use super::{FaceRef, FontSelector, Resolver};
use crate::conv::{to_u32, to_usize};
use fontique::{Blob, QueryStatus, Script, Synthesis};
use fontique::{Blob, QueryStatus, Script, Synthesis, UnicodeRange};
use std::collections::hash_map::{Entry, HashMap};
use std::sync::{LazyLock, Mutex, MutexGuard, RwLock};
use thiserror::Error;
Expand Down Expand Up @@ -222,7 +222,7 @@ pub struct FontLibrary {
/// Font management
impl FontLibrary {
/// Get a reference to the font resolver
pub fn resolver(&self) -> MutexGuard<Resolver> {
pub fn resolver(&self) -> MutexGuard<'_, Resolver> {
self.resolver.lock().unwrap()
}

Expand All @@ -244,7 +244,7 @@ impl FontLibrary {
///
/// This is a wrapper around [`FontLibrary::first_face_for`] and [`FontLibrary::get_face`].
#[inline]
pub fn get_first_face(&self, font_id: FontId) -> Result<FaceRef, InvalidFontId> {
pub fn get_first_face(&self, font_id: FontId) -> Result<FaceRef<'_>, InvalidFontId> {
let face_id = self.first_face_for(font_id)?;
Ok(self.get_face(face_id))
}
Expand All @@ -268,13 +268,15 @@ impl FontLibrary {
///
/// Otherwise, return the first face of `font_id` which covers `c`.
///
/// Otherwise (if no face covers `c`) return `None`.
/// Otherwise (if no face covers `c`), return `last_face_id` (if some) or
/// else the first face listed for `font_id`. The idea here is to ensure
/// that shaping can continue without causing unnecessary font breaks.
pub fn face_for_char(
&self,
font_id: FontId,
last_face_id: Option<FaceId>,
c: char,
) -> Result<Option<FaceId>, InvalidFontId> {
) -> Result<FaceId, InvalidFontId> {
// TODO: `face.glyph_index` is a bit slow to use like this where several
// faces may return no result before we find a match. Caching results
// in a HashMap helps. Perhaps better would be to (somehow) determine
Expand All @@ -294,14 +296,16 @@ impl FontLibrary {
let face = &faces.faces[face_id.get()];
// TODO(opt): should we cache this lookup?
if face.face.glyph_index(c).is_some() {
return Ok(Some(face_id));
return Ok(face_id);
}
}
}

Ok(match font.2.entry(c) {
// Check the cache for c
let result = match font.2.entry(c) {
Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => {
// Not cached: look for the first suitable face
let mut id: Option<FaceId> = None;
for face_id in font.1.iter() {
let face = &faces.faces[face_id.get()];
Expand All @@ -310,10 +314,15 @@ impl FontLibrary {
break;
}
}

entry.insert(id);
id
}
})
};

Ok(result
.or(last_face_id)
.unwrap_or_else(|| *font.1.first().unwrap()))
}

/// Select a font
Expand All @@ -324,6 +333,7 @@ impl FontLibrary {
&self,
selector: &FontSelector,
script: Script,
range: Option<UnicodeRange>,
) -> Result<FontId, NoFontMatch> {
let sel_hash = {
use std::collections::hash_map::DefaultHasher;
Expand All @@ -332,6 +342,7 @@ impl FontLibrary {
let mut s = DefaultHasher::new();
selector.hash(&mut s);
script.hash(&mut s);
range.hash(&mut s);
s.finish()
};

Expand All @@ -348,7 +359,7 @@ impl FontLibrary {
let mut resolver = self.resolver.lock().unwrap();
let mut face_list = self.faces.write().unwrap();

selector.select(&mut resolver, script, |qf| {
selector.select(&mut resolver, script, range, |qf| {
if log::log_enabled!(log::Level::Debug) {
families.push(qf.family);
}
Expand Down
17 changes: 12 additions & 5 deletions src/fonts/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use super::{FontStyle, FontWeight, FontWidth};
use fontique::{
Attributes, Collection, FamilyId, GenericFamily, QueryFamily, QueryFont, QueryStatus, Script,
SourceCache,
SourceCache, UnicodeRange,
};
use log::debug;
#[cfg(feature = "serde")]
Expand Down Expand Up @@ -195,21 +195,26 @@ impl FontSelector {
/// Resolve font faces for each matching font
///
/// All font faces matching steps 1-4 will be returned through the `add_face` closure.
pub(crate) fn select<F>(&self, resolver: &mut Resolver, script: Script, add_face: F)
where
pub(crate) fn select<F>(
&self,
resolver: &mut Resolver,
script: Script,
range: Option<UnicodeRange>,
add_face: F,
) where
F: FnMut(&QueryFont) -> QueryStatus,
{
let mut query = resolver.collection.query(&mut resolver.cache);
if let Some(gf) = self.family.as_generic() {
debug!(
"select: Script::{:?}, GenericFamily::{:?}, {:?}, {:?}, {:?}",
"select: Script::{:?}, {range:?}, GenericFamily::{:?}, {:?}, {:?}, {:?}",
&script, gf, &self.weight, &self.width, &self.style
);

query.set_families([gf]);
} else if let Some(set) = resolver.families.get(&self.family) {
debug!(
"select: Script::{:?}, {:?}, {:?}, {:?}, {:?}",
"select: Script::{:?}, {range:?}, {:?}, {:?}, {:?}, {:?}",
&script, set, &self.weight, &self.width, &self.style
);

Expand All @@ -224,6 +229,8 @@ impl FontSelector {

query.set_fallbacks(script);

query.set_range(range);

query.matches_with(add_face);
}
}
Expand Down
Loading