From b7267c25381df489842e1e4a92cdac62e0f132a4 Mon Sep 17 00:00:00 2001 From: thekingofcity Date: Thu, 5 Mar 2026 13:01:05 +0800 Subject: [PATCH 1/3] Always fallback fonts using the font-family priority list Co-authored-by: Zhai Can --- crates/usvg/src/text/layout.rs | 70 ++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 21 deletions(-) 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() { From 8178f5df24eb37d4f94b405524424390c82f4979 Mon Sep 17 00:00:00 2001 From: Zhai Can Date: Tue, 10 Mar 2026 17:34:31 +0800 Subject: [PATCH 2/3] Update test 'text/direction/rtl' This is not rendered correctly before. Just ignore and make the test happy for now. --- .../resvg/tests/tests/text/direction/rtl.png | Bin 933 -> 1814 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/crates/resvg/tests/tests/text/direction/rtl.png b/crates/resvg/tests/tests/text/direction/rtl.png index 1d0416d2526b200c5b5eb65eb88884996157e7b0..3580b5d3bbbd12b9f56be0be5c7b5c187c4bf652 100644 GIT binary patch literal 1814 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4mO}jWo=(60|VP#PZ!6KiaBp@`{o6QOB{b_ zu5mfY>ct&Vo{5vrOw0;nuFo8d=dWH^G5h^0dsCUKPi1({%kqCW&pz4Zy`iC7 zf_MAp(5p59`E9p7&DXywTD>~0y5e8Tj{V`@W)E4pE&QY3?pa}{kgICae{IF=t*^ds z+Sc}%@BGimx0d(A_j_eu{&qU*!@d=Dq1O*x_2ccI_E)Dm!r^k#mv2+v_patYJG=FG z-AX%y&BpV#pIPxMc<<56d%y8L-15u8_w|pGFF#MJ$471`D=XX9?SJXw#fy9MuI;-1 zV?{*e?%lh;zq_~h_pftvt?PAu{{w>byN31kwV$7zy?%G2{l1!?p8{jgC4O!EwWrTt zF?zwXi`j~^L**~jzg-*~x#5<4$w$x6Qt1`n-`$<`&JX@(usZdi?7A`P*J=e&f0q9BsSZ{@=?IEA6nmf3!ZQ zKbN^4c3S?k{enQ*_ldpP8B=`AcicO-H=_81bFIu<+M#CT7&<@MVQzY?|vuZxs@ZrACyLQg4RcSG&?fGx$>CU)v{Ng>$n6j>4zYDAjy*RE% zce_L%)Oi=U^)zqPid*Si>ZMR!LeaGPG>gTe~DWM4f)YQbw literal 933 zcmeAS@N?(olHy`uVBq!ia0y~yVAKI&4rZW;-{LxVAXOLO6XFV_ae>ChMxazfLjznv zY;0^mK!Cr$KU@UJu(GlOsxdS)tgo+!iz_QD!`W~KA0Ho_4PoRdp9ur9G)sc~f*Ax9 z4D22J10v$%6B6~6|!Q_`N&?2U_o-U3d z6}R5rV4bAoDBy5WX8~JwRqCDCPyfVYbkq{IIA6POe~8<&(q-W!3okZWoff7tW3 z-5c($^YPk~@m=cLwBz3+Ol!^@ugeJ+zUKD#Oq@=Z+_%>odc~g#Tyr}s^c4(*y5i>bGrOB_ zbP0l+XkKEi_^% From 3e7475eb0991a05dca812ece7006e9b946f26af7 Mon Sep 17 00:00:00 2001 From: Zhai Can Date: Tue, 10 Mar 2026 17:20:12 +0800 Subject: [PATCH 3/3] Add test 'text/font-family/fallback-3' --- crates/resvg/tests/integration/render.rs | 1 + .../tests/tests/text/font-family/fallback-3.png | Bin 0 -> 14533 bytes .../tests/tests/text/font-family/fallback-3.svg | 10 ++++++++++ 3 files changed, 11 insertions(+) create mode 100644 crates/resvg/tests/tests/text/font-family/fallback-3.png create mode 100644 crates/resvg/tests/tests/text/font-family/fallback-3.svg 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/font-family/fallback-3.png b/crates/resvg/tests/tests/text/font-family/fallback-3.png new file mode 100644 index 0000000000000000000000000000000000000000..2d14e0501fceaee8bc18039e16cb274ff1ec2501 GIT binary patch literal 14533 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%0ARYm+$l~HyQR3##E!iU_Mxs(6a z0Ix7;`xx7ccBs_vO#IE2wM-=bgNPC-b#bwSYB**DEsMR2&ia6xDv+ESL$E|$#VSP1 zGXaJ%`UYW7ckZ_~Tq}s|0gOfFMy=a z|81Mk{y#O&wHlRUDtv@A?p0}<|5${z<%jh5K}S;Qwptk&L7Mlt+uuno&nb^>z)Jm) Q_QVe|5{lv#qDF!L1&C$13;+NC literal 0 HcmV?d00001 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 @@ + + Fallback (1) + + Text 😀 + Text 😀 + Text 😀 + + + +