Skip to content
Open
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
1 change: 1 addition & 0 deletions crates/resvg/tests/integration/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand Down
Binary file modified crates/resvg/tests/tests/text/direction/rtl.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions crates/resvg/tests/tests/text/font-family/fallback-3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 49 additions & 21 deletions crates/usvg/src/text/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<ResolvedFont> {
self.with_face_data(id, |data, face_index| -> Option<ResolvedFont> {
Expand Down Expand Up @@ -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<ResolvedFont>,
span_font: &Font,
small_caps: bool,
apply_kerning: bool,
variations: &[crate::FontVariation],
Expand All @@ -1301,9 +1308,15 @@ pub(crate) fn shape_text(
resolver: &FontResolver,
fontdb: &mut Arc<fontdb::Database>,
) -> Vec<Glyph> {
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,
Expand All @@ -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 {
Expand All @@ -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(
Expand All @@ -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() {
Expand Down
Loading