usvg/text/mod.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::sync::Arc;
use fontdb::{Database, ID};
use svgtypes::FontFamily;
use self::layout::DatabaseExt;
use crate::{Font, FontStretch, FontStyle, Text};
mod flatten;
mod colr;
/// Provides access to the layout of a text node.
pub mod layout;
/// A shorthand for [FontResolver]'s font selection function.
///
/// This function receives a font specification (families + a style, weight,
/// stretch triple) and a font database and should return the ID of the font
/// that shall be used (if any).
///
/// In the basic case, the function will search the existing fonts in the
/// database to find a good match, e.g. via
/// [`Database::query`](fontdb::Database::query). This is what the [default
/// implementation](FontResolver::default_font_selector) does.
///
/// Users with more complex requirements can mutate the database to load
/// additional fonts dynamically. To perform mutation, it is recommended to call
/// `Arc::make_mut` on the provided database. (This call is not done outside of
/// the callback to not needless clone an underlying shared database if no
/// mutation will be performed.) It is important that the database is only
/// mutated additively. Removing fonts or replacing the entire database will
/// break things.
pub type FontSelectionFn<'a> =
Box<dyn Fn(&Font, &mut Arc<Database>) -> Option<ID> + Send + Sync + 'a>;
/// A shorthand for [FontResolver]'s fallback selection function.
///
/// This function receives a specific character, a list of already used fonts,
/// and a font database. It should return the ID of a font that
/// - is not any of the already used fonts
/// - is as close as possible to the first already used font (if any)
/// - supports the given character
///
/// The function can search the existing database, but can also load additional
/// fonts dynamically. See the documentation of [`FontSelectionFn`] for more
/// details.
pub type FallbackSelectionFn<'a> =
Box<dyn Fn(char, &[ID], &mut Arc<Database>) -> Option<ID> + Send + Sync + 'a>;
/// A font resolver for `<text>` elements.
///
/// This type can be useful if you want to have an alternative font handling to
/// the default one. By default, only fonts specified upfront in
/// [`Options::fontdb`](crate::Options::fontdb) will be used. This type allows
/// you to load additional fonts on-demand and customize the font selection
/// process.
pub struct FontResolver<'a> {
/// Resolver function that will be used when selecting a specific font
/// for a generic [`Font`] specification.
pub select_font: FontSelectionFn<'a>,
/// Resolver function that will be used when selecting a fallback font for a
/// character.
pub select_fallback: FallbackSelectionFn<'a>,
}
impl Default for FontResolver<'_> {
fn default() -> Self {
FontResolver {
select_font: FontResolver::default_font_selector(),
select_fallback: FontResolver::default_fallback_selector(),
}
}
}
impl FontResolver<'_> {
/// Creates a default font selection resolver.
///
/// The default implementation forwards to
/// [`query`](fontdb::Database::query) on the font database specified in the
/// [`Options`](crate::Options).
pub fn default_font_selector() -> FontSelectionFn<'static> {
Box::new(move |font, fontdb| {
let mut name_list = Vec::new();
for family in &font.families {
name_list.push(match family {
FontFamily::Serif => fontdb::Family::Serif,
FontFamily::SansSerif => fontdb::Family::SansSerif,
FontFamily::Cursive => fontdb::Family::Cursive,
FontFamily::Fantasy => fontdb::Family::Fantasy,
FontFamily::Monospace => fontdb::Family::Monospace,
FontFamily::Named(s) => fontdb::Family::Name(s),
});
}
// Use the default font as fallback.
name_list.push(fontdb::Family::Serif);
let stretch = match font.stretch {
FontStretch::UltraCondensed => fontdb::Stretch::UltraCondensed,
FontStretch::ExtraCondensed => fontdb::Stretch::ExtraCondensed,
FontStretch::Condensed => fontdb::Stretch::Condensed,
FontStretch::SemiCondensed => fontdb::Stretch::SemiCondensed,
FontStretch::Normal => fontdb::Stretch::Normal,
FontStretch::SemiExpanded => fontdb::Stretch::SemiExpanded,
FontStretch::Expanded => fontdb::Stretch::Expanded,
FontStretch::ExtraExpanded => fontdb::Stretch::ExtraExpanded,
FontStretch::UltraExpanded => fontdb::Stretch::UltraExpanded,
};
let style = match font.style {
FontStyle::Normal => fontdb::Style::Normal,
FontStyle::Italic => fontdb::Style::Italic,
FontStyle::Oblique => fontdb::Style::Oblique,
};
let query = fontdb::Query {
families: &name_list,
weight: fontdb::Weight(font.weight),
stretch,
style,
};
let id = fontdb.query(&query);
if id.is_none() {
log::warn!(
"No match for '{}' font-family.",
font.families
.iter()
.map(|f| f.to_string())
.collect::<Vec<_>>()
.join(", ")
);
}
id
})
}
/// Creates a default font fallback selection resolver.
///
/// The default implementation searches through the entire `fontdb`
/// to find a font that has the correct style and supports the character.
pub fn default_fallback_selector() -> FallbackSelectionFn<'static> {
Box::new(|c, exclude_fonts, fontdb| {
let base_font_id = exclude_fonts[0];
// Iterate over fonts and check if any of them support the specified char.
for face in fontdb.faces() {
// Ignore fonts, that were used for shaping already.
if exclude_fonts.contains(&face.id) {
continue;
}
// Check that the new face has the same style.
let base_face = fontdb.face(base_font_id)?;
if base_face.style != face.style
&& base_face.weight != face.weight
&& base_face.stretch != face.stretch
{
continue;
}
if !fontdb.has_char(face.id, c) {
continue;
}
let base_family = base_face
.families
.iter()
.find(|f| f.1 == fontdb::Language::English_UnitedStates)
.unwrap_or(&base_face.families[0]);
let new_family = face
.families
.iter()
.find(|f| f.1 == fontdb::Language::English_UnitedStates)
.unwrap_or(&base_face.families[0]);
log::warn!("Fallback from {} to {}.", base_family.0, new_family.0);
return Some(face.id);
}
None
})
}
}
impl std::fmt::Debug for FontResolver<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("FontResolver { .. }")
}
}
/// Convert a text into its paths. This is done in two steps:
/// 1. We convert the text into glyphs and position them according to the rules specified in the
/// SVG specifiation. While doing so, we also calculate the text bbox (which is not based on the
/// outlines of a glyph, but instead the glyph metrics as well as decoration spans).
/// 2. We convert all of the positioned glyphs into outlines.
pub(crate) fn convert(
text: &mut Text,
resolver: &FontResolver,
fontdb: &mut Arc<fontdb::Database>,
) -> Option<()> {
let (text_fragments, bbox) = layout::layout_text(text, resolver, fontdb)?;
text.layouted = text_fragments;
text.bounding_box = bbox.to_rect();
text.abs_bounding_box = bbox.transform(text.abs_transform)?.to_rect();
let (group, stroke_bbox) = flatten::flatten(text, fontdb)?;
text.flattened = Box::new(group);
text.stroke_bounding_box = stroke_bbox.to_rect();
text.abs_stroke_bounding_box = stroke_bbox.transform(text.abs_transform)?.to_rect();
Some(())
}