diff --git a/crates/resvg/tests/integration/render.rs b/crates/resvg/tests/integration/render.rs
index ad79dd7e5..35f423490 100644
--- a/crates/resvg/tests/integration/render.rs
+++ b/crates/resvg/tests/integration/render.rs
@@ -1418,6 +1418,7 @@ use crate::render;
#[test] fn text_font_family_double_quoted() { assert_eq!(render("tests/text/font-family/double-quoted"), 0); }
#[test] fn text_font_family_fallback_1() { assert_eq!(render("tests/text/font-family/fallback-1"), 0); }
#[test] fn text_font_family_fallback_2() { assert_eq!(render("tests/text/font-family/fallback-2"), 0); }
+#[test] fn text_font_family_fallback_3() { assert_eq!(render("tests/text/font-family/fallback-3"), 0); }
#[test] fn text_font_family_fantasy() { assert_eq!(render("tests/text/font-family/fantasy"), 0); }
#[test] fn text_font_family_font_list() { assert_eq!(render("tests/text/font-family/font-list"), 0); }
#[test] fn text_font_family_monospace() { assert_eq!(render("tests/text/font-family/monospace"), 0); }
diff --git a/crates/resvg/tests/tests/text/direction/rtl.png b/crates/resvg/tests/tests/text/direction/rtl.png
index 1d0416d25..3580b5d3b 100644
Binary files a/crates/resvg/tests/tests/text/direction/rtl.png and b/crates/resvg/tests/tests/text/direction/rtl.png differ
diff --git a/crates/resvg/tests/tests/text/font-family/fallback-3.png b/crates/resvg/tests/tests/text/font-family/fallback-3.png
new file mode 100644
index 000000000..2d14e0501
Binary files /dev/null and b/crates/resvg/tests/tests/text/font-family/fallback-3.png differ
diff --git a/crates/resvg/tests/tests/text/font-family/fallback-3.svg b/crates/resvg/tests/tests/text/font-family/fallback-3.svg
new file mode 100644
index 000000000..1073effe4
--- /dev/null
+++ b/crates/resvg/tests/tests/text/font-family/fallback-3.svg
@@ -0,0 +1,10 @@
+
diff --git a/crates/usvg/src/text/layout.rs b/crates/usvg/src/text/layout.rs
index 448d6545d..56d6a9ff7 100644
--- a/crates/usvg/src/text/layout.rs
+++ b/crates/usvg/src/text/layout.rs
@@ -5,8 +5,9 @@ use std::collections::{HashMap, HashSet};
use std::num::NonZeroU16;
use std::sync::Arc;
-use fontdb::{Database, ID};
+use fontdb::{Family, ID, Query};
use kurbo::{ParamCurve, ParamCurveArclen, ParamCurveDeriv};
+use log::debug;
use rustybuzz::ttf_parser;
use rustybuzz::ttf_parser::{GlyphId, Tag};
use strict_num::NonZeroPositiveF32;
@@ -900,14 +901,9 @@ fn process_chunk(
let mut glyphs = Vec::new();
for span in &chunk.spans {
- let font = match fonts_cache.get(&span.font) {
- Some(v) => v.clone(),
- None => continue,
- };
-
let tmp_glyphs = shape_text(
&chunk.text,
- font,
+ &span.font,
span.small_caps,
span.apply_kerning,
&span.font.variations,
@@ -1203,7 +1199,7 @@ pub(crate) trait DatabaseExt {
fn has_char(&self, id: ID, c: char) -> bool;
}
-impl DatabaseExt for Database {
+impl DatabaseExt for fontdb::Database {
#[inline(never)]
fn load_font(&self, id: ID) -> Option {
self.with_face_data(id, |data, face_index| -> Option {
@@ -1289,10 +1285,21 @@ impl DatabaseExt for Database {
}
}
+fn as_fontdb_family(family: &'_ crate::FontFamily) -> Family<'_> {
+ match family {
+ crate::FontFamily::Serif => Family::Serif,
+ crate::FontFamily::SansSerif => Family::SansSerif,
+ crate::FontFamily::Cursive => Family::Cursive,
+ crate::FontFamily::Fantasy => Family::Fantasy,
+ crate::FontFamily::Monospace => Family::Monospace,
+ crate::FontFamily::Named(s) => Family::Name(s),
+ }
+}
+
/// Text shaping with font fallback.
pub(crate) fn shape_text(
text: &str,
- font: Arc,
+ span_font: &Font,
small_caps: bool,
apply_kerning: bool,
variations: &[crate::FontVariation],
@@ -1301,9 +1308,15 @@ pub(crate) fn shape_text(
resolver: &FontResolver,
fontdb: &mut Arc,
) -> Vec {
+ let Some(initial_font) =
+ (resolver.select_font)(span_font, fontdb).and_then(|x| fontdb.load_font(x))
+ else {
+ return vec![];
+ };
+ let initial_font = Arc::new(initial_font);
let mut glyphs = shape_text_with_font(
text,
- font.clone(),
+ initial_font.clone(),
small_caps,
apply_kerning,
variations,
@@ -1314,7 +1327,7 @@ pub(crate) fn shape_text(
.unwrap_or_default();
// Remember all fonts used for shaping.
- let mut used_fonts = vec![font.id];
+ let mut used_fonts = vec![initial_font.id];
// Loop until all glyphs become resolved or until no more fonts are left.
'outer: loop {
@@ -1327,12 +1340,34 @@ pub(crate) fn shape_text(
}
if let Some(c) = missing {
- let fallback_font = match (resolver.select_fallback)(c, &used_fonts, fontdb)
- .and_then(|id| fontdb.load_font(id))
- {
+ let mut fallback_id = None;
+
+ for family in &span_font.families {
+ let query = Query {
+ families: &[as_fontdb_family(family)],
+ weight: fontdb::Weight(span_font.weight),
+ stretch: span_font.stretch.into(),
+ style: span_font.style.into(),
+ };
+ if let Some(id) = fontdb.query(&query) {
+ if !used_fonts.contains(&id) && fontdb.has_char(id, c) {
+ fallback_id = Some(id);
+ break;
+ }
+ }
+ }
+
+ let fallback_id =
+ fallback_id.or_else(|| (resolver.select_fallback)(c, &used_fonts, fontdb));
+
+ let fallback_font = match fallback_id.and_then(|id| fontdb.load_font(id)) {
Some(v) => Arc::new(v),
None => break 'outer,
};
+ debug!(
+ "Fallback font postscript name: {}",
+ fontdb.face(fallback_font.id).unwrap().post_script_name
+ );
// Shape again, using a new font.
let fallback_glyphs = shape_text_with_font(
@@ -1347,13 +1382,6 @@ pub(crate) fn shape_text(
)
.unwrap_or_default();
- let all_matched = fallback_glyphs.iter().all(|g| !g.is_missing());
- if all_matched {
- // Replace all glyphs when all of them were matched.
- glyphs = fallback_glyphs;
- break 'outer;
- }
-
// We assume, that shaping with an any font will produce the same amount of glyphs.
// This is incorrect, but good enough for now.
if glyphs.len() != fallback_glyphs.len() {