Skip to content

Commit

Permalink
font-config: failed to resolve clusters like 3065,2686
Browse files Browse the repository at this point in the history
Root cause is that font-config will try to find a single font that
satisfies a query like `fc-list ':charset=2686 3065'`, but if that
is impossible, there are no results.

The resolution is to split the query up and look for each individual
codepoint.

refs: #4310
  • Loading branch information
wez committed Sep 21, 2023
1 parent f1be339 commit 50cc44a
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 54 deletions.
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ As features stabilize some brief notes about them will accumulate here.
* Using `CloseCurrentPane` could sometimes leave a stranded pane in a tab. #4030
* Wayland: wezterm wouldn't start on Plasma 6 or newer versions of sway. Thanks
to @hexchain! #3996 #4322.
* font-config: when resolving a fallback font for a text cluster like `U+3065,U+2686`
where no single font contains both glyphs, wezterm would fail to show a glyph
for either codepoint. We now split the fallback query up and query for each
individual codepoint separately. #4310

#### Updated
* Bundled harfbuzz to 8.2.1
Expand Down
140 changes: 86 additions & 54 deletions wezterm-font/src/locator/font_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,73 +165,105 @@ impl FontLocator for FontConfigFontLocator {
codepoints: &[char],
) -> anyhow::Result<Vec<ParsedFont>> {
log::trace!("locate_fallback_for_codepoints: {:?}", codepoints);
let mut charset = CharSet::new()?;
for &c in codepoints {
charset.add(c)?;
}
let mut fonts: Vec<ParsedFont> = vec![];

let mut fonts = vec![];
// In <https://github.com/wez/wezterm/issues/4310> we discover
// that a font-config query for a charset containing both
// 3065 and 2686 fails because no fonts contain both codepoints,
// but querying separately does find the separate fonts.
// We therefore need to break up our query so that we resolve
// each codepoint individually.
// However, if we need to resolve a block of characters that
// are found in the same font (eg: someone is printing an
// entire unicode block) we don't want to issue N queries
// that return the same font.
//
// So we check the fonts that have been resolved in earlier
// iterations to see if any of those cover a given codepoint
// and allow that to satisfy the query if they do.

// Make two passes to locate a fallback: first try to find any
// strictly monospace version, then, if we didn't find any matches,
// look for a version with any spacing.
for only_monospace in [true, false] {
let mut pattern = FontPattern::new()?;
pattern.add_charset(&charset)?;
pattern.add_integer("weight", 80)?;
pattern.add_integer("slant", 0)?;

let mut lists = vec![pattern
.list()
.context("pattern.list with no spacing constraint")?];

if only_monospace {
for &spacing in &SPACING {
pattern.delete_property("spacing")?;
pattern.add_integer("spacing", spacing)?;
lists.push(
pattern
.list()
.with_context(|| format!("pattern.list with spacing={}", spacing))?,
);
'next_codepoint: for &c in codepoints {
if !fonts.is_empty() {
let mut wanted_range = rangeset::RangeSet::new();
wanted_range.add(c as u32);
for f in &fonts {
match f.coverage_intersection(&wanted_range) {
Ok(r) if !r.is_empty() => {
// already found a font with this one!
continue 'next_codepoint;
}
_ => {}
}
}
}

for list in lists {
for pat in list.iter() {
let num = pat.charset_intersect_count(&charset)?;
if num == 0 {
log::error!(
"Skipping bogus font-config result {:?} because it doesn't overlap",
pat
let mut pushed_this_pass = 0;

let mut charset = CharSet::new()?;
charset.add(c)?;

// Make two passes to locate a fallback: first try to find any
// strictly monospace version, then, if we didn't find any matches,
// look for a version with any spacing.
for only_monospace in [true, false] {
let mut pattern = FontPattern::new()?;
pattern.add_charset(&charset)?;
pattern.add_integer("weight", 80)?;
pattern.add_integer("slant", 0)?;

let mut lists = vec![pattern
.list()
.context("pattern.list with no spacing constraint")?];

if only_monospace {
for &spacing in &SPACING {
pattern.delete_property("spacing")?;
pattern.add_integer("spacing", spacing)?;
lists.push(
pattern.list().with_context(|| {
format!("pattern.list with spacing={}", spacing)
})?,
);
continue;
}
}

if let Ok(file) = pat.get_file().context("pat.get_file") {
log::trace!("{file:?} has {num} codepoints from {codepoints:?}");
let handle = FontDataHandle {
source: FontDataSource::OnDisk(file.into()),
index: pat.get_integer("index")?.try_into()?,
variation: 0,
origin: FontOrigin::FontConfig,
coverage: pat.get_charset().ok().map(|c| c.to_range_set()),
};
if let Ok(parsed) = crate::parser::ParsedFont::from_locator(&handle) {
fonts.push(parsed);
for list in lists {
for pat in list.iter() {
let num = pat.charset_intersect_count(&charset)?;
if num == 0 {
log::error!(
"Skipping bogus font-config result {:?} because it doesn't overlap",
pat
);
continue;
}

if let Ok(file) = pat.get_file().context("pat.get_file") {
log::trace!("{file:?} has {num} codepoints from {codepoints:?}");
let handle = FontDataHandle {
source: FontDataSource::OnDisk(file.into()),
index: pat.get_integer("index")?.try_into()?,
variation: 0,
origin: FontOrigin::FontConfig,
coverage: pat.get_charset().ok().map(|c| c.to_range_set()),
};
if let Ok(parsed) = crate::parser::ParsedFont::from_locator(&handle) {
fonts.push(parsed);
pushed_this_pass += 1;
}
}
}
}
}

if fonts.is_empty() {
// If we get here on the first iteration, then we didn't
// find a monospace version of fonts with those codepoints,
// let's continue and try any matching font
continue;
}
if pushed_this_pass == 0 {
// If we get here on the first iteration, then we didn't
// find a monospace version of fonts with those codepoints,
// let's continue and try any matching font
continue;
}

break;
break;
}
}

Ok(fonts)
Expand Down

0 comments on commit 50cc44a

Please sign in to comment.