Fix font family mismatch between Typst and usvg

Fixes #2051
This commit is contained in:
Laurenz 2023-09-09 22:31:37 +02:00
parent 66a5958917
commit 073effc740
4 changed files with 44 additions and 42 deletions

View File

@ -168,7 +168,7 @@ impl Layout for ImageElem {
data.into(), data.into(),
format, format,
vt.world, vt.world,
families(styles).next().as_ref().map(|f| f.as_str()), families(styles).next().map(|s| s.as_str().into()),
self.alt(styles), self.alt(styles),
) )
.at(self.span())?; .at(self.span())?;

View File

@ -76,7 +76,7 @@ impl Image {
data: Bytes, data: Bytes,
format: ImageFormat, format: ImageFormat,
world: Tracked<dyn World + '_>, world: Tracked<dyn World + '_>,
fallback_family: Option<&str>, fallback_family: Option<EcoString>,
alt: Option<EcoString>, alt: Option<EcoString>,
) -> StrResult<Self> { ) -> StrResult<Self> {
let loader = WorldLoader::new(world, fallback_family); let loader = WorldLoader::new(world, fallback_family);
@ -313,31 +313,39 @@ fn load_svg_fonts(
tree: &usvg::Tree, tree: &usvg::Tree,
loader: Tracked<dyn SvgFontLoader + '_>, loader: Tracked<dyn SvgFontLoader + '_>,
) -> fontdb::Database { ) -> fontdb::Database {
let mut referenced = BTreeMap::<EcoString, bool>::new();
let mut fontdb = fontdb::Database::new(); let mut fontdb = fontdb::Database::new();
let mut load = |family_cased: &str| { let mut referenced = BTreeMap::<EcoString, Option<EcoString>>::new();
let family = EcoString::from(family_cased.trim()).to_lowercase();
if let Some(&success) = referenced.get(&family) { // Loads a font family by its Typst name and returns its usvg-compatible
return success; // name.
let mut load = |family: &str| -> Option<EcoString> {
let family = EcoString::from(family.trim()).to_lowercase();
if let Some(success) = referenced.get(&family) {
return success.clone();
} }
// We load all variants for the family, since we don't know which will // We load all variants for the family, since we don't know which will
// be used. // be used.
let mut success = false; let mut name = None;
for font in loader.load(&family) { for font in loader.load(&family) {
let source = Arc::new(font.data().clone()); let source = Arc::new(font.data().clone());
fontdb.load_font_source(fontdb::Source::Binary(source)); fontdb.load_font_source(fontdb::Source::Binary(source));
success = true; if name.is_none() {
name = font
.find_name(ttf_parser::name_id::TYPOGRAPHIC_FAMILY)
.or_else(|| font.find_name(ttf_parser::name_id::FAMILY))
.map(Into::into);
}
} }
referenced.insert(family, success); referenced.insert(family, name.clone());
success name
}; };
// Load fallback family. // Load fallback family.
let fallback_cased = loader.fallback(); let mut fallback_usvg_compatible = None;
if let Some(family_cased) = fallback_cased { if let Some(family) = loader.fallback_family() {
load(family_cased); fallback_usvg_compatible = load(family);
} }
// Find out which font families are referenced by the SVG. // Find out which font families are referenced by the SVG.
@ -345,10 +353,11 @@ fn load_svg_fonts(
let usvg::NodeKind::Text(text) = &mut *node.borrow_mut() else { return }; let usvg::NodeKind::Text(text) = &mut *node.borrow_mut() else { return };
for chunk in &mut text.chunks { for chunk in &mut text.chunks {
for span in &mut chunk.spans { for span in &mut chunk.spans {
for family_cased in &mut span.font.families { for family in &mut span.font.families {
if family_cased.is_empty() || !load(family_cased) { if family.is_empty() || load(family).is_none() {
let Some(fallback) = fallback_cased else { continue }; if let Some(fallback) = &fallback_usvg_compatible {
*family_cased = fallback.into(); *family = fallback.into();
}
} }
} }
} }
@ -384,40 +393,31 @@ where
#[comemo::track] #[comemo::track]
trait SvgFontLoader { trait SvgFontLoader {
/// Load all fonts for the given lowercased font family. /// Load all fonts for the given lowercased font family.
fn load(&self, lower_family: &str) -> EcoVec<Font>; fn load(&self, family: &str) -> EcoVec<Font>;
/// The case-sensitive name of the fallback family. /// The fallback family.
fn fallback(&self) -> Option<&str>; fn fallback_family(&self) -> Option<&str>;
} }
/// Loads fonts for an SVG from a world /// Loads fonts for an SVG from a world
struct WorldLoader<'a> { struct WorldLoader<'a> {
world: Tracked<'a, dyn World + 'a>, world: Tracked<'a, dyn World + 'a>,
seen: RefCell<BTreeMap<EcoString, EcoVec<Font>>>, seen: RefCell<BTreeMap<EcoString, EcoVec<Font>>>,
fallback_family_cased: Option<String>, fallback_family: Option<EcoString>,
} }
impl<'a> WorldLoader<'a> { impl<'a> WorldLoader<'a> {
fn new(world: Tracked<'a, dyn World + 'a>, fallback_family: Option<&str>) -> Self { fn new(
// Recover the non-lowercased version of the family because world: Tracked<'a, dyn World + 'a>,
// usvg is case sensitive. fallback_family: Option<EcoString>,
let book = world.book(); ) -> Self {
let fallback_family_cased = fallback_family Self { world, fallback_family, seen: Default::default() }
.and_then(|lowercase| book.select_family(lowercase).next())
.and_then(|index| book.info(index))
.map(|info| info.family.clone());
Self {
world,
fallback_family_cased,
seen: Default::default(),
}
} }
fn into_prepared(self) -> PreparedLoader { fn into_prepared(self) -> PreparedLoader {
PreparedLoader { PreparedLoader {
families: self.seen.into_inner(), families: self.seen.into_inner(),
fallback_family_cased: self.fallback_family_cased, fallback_family: self.fallback_family,
} }
} }
} }
@ -437,8 +437,8 @@ impl SvgFontLoader for WorldLoader<'_> {
.clone() .clone()
} }
fn fallback(&self) -> Option<&str> { fn fallback_family(&self) -> Option<&str> {
self.fallback_family_cased.as_deref() self.fallback_family.as_deref()
} }
} }
@ -446,7 +446,7 @@ impl SvgFontLoader for WorldLoader<'_> {
#[derive(Default, Hash)] #[derive(Default, Hash)]
struct PreparedLoader { struct PreparedLoader {
families: BTreeMap<EcoString, EcoVec<Font>>, families: BTreeMap<EcoString, EcoVec<Font>>,
fallback_family_cased: Option<String>, fallback_family: Option<EcoString>,
} }
impl SvgFontLoader for PreparedLoader { impl SvgFontLoader for PreparedLoader {
@ -454,8 +454,8 @@ impl SvgFontLoader for PreparedLoader {
self.families.get(family).cloned().unwrap_or_default() self.families.get(family).cloned().unwrap_or_default()
} }
fn fallback(&self) -> Option<&str> { fn fallback_family(&self) -> Option<&str> {
self.fallback_family_cased.as_deref() self.fallback_family.as_deref()
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,2 @@
#set text(font: "New Computer Modern")
#image("/files/diagram.svg")