Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make FontResolver a trait and use to provide access to a fontdb #784

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
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
6 changes: 4 additions & 2 deletions crates/resvg/examples/draw_bboxes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ fn main() {
1.0
};

let mut fontdb = usvg::fontdb::Database::new();
fontdb.load_system_fonts();

let mut opt = usvg::Options::default();
// Get file's absolute directory.
opt.resources_dir = std::fs::canonicalize(&args[1])
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf()));

opt.fontdb_mut().load_system_fonts();
opt.font_resolver = Some(&fontdb);

let svg_data = std::fs::read(&args[1]).unwrap();
let tree = usvg::Tree::from_data(&svg_data, &opt).unwrap();
Expand Down
6 changes: 4 additions & 2 deletions crates/resvg/examples/minimal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ fn main() {
}

let tree = {
let mut fontdb = usvg::fontdb::Database::new();
fontdb.load_system_fonts();

let mut opt = usvg::Options::default();
// Get file's absolute directory.
opt.resources_dir = std::fs::canonicalize(&args[1])
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf()));

opt.fontdb_mut().load_system_fonts();
opt.font_resolver = Some(&fontdb);

let svg_data = std::fs::read(&args[1]).unwrap();
usvg::Tree::from_data(&svg_data, &opt).unwrap()
Expand Down
83 changes: 44 additions & 39 deletions crates/resvg/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
#![allow(clippy::uninlined_format_args)]

use std::path;
use std::sync::Arc;

use usvg::fontdb;
use usvg::{fontdb, FontResolver};

fn main() {
if let Err(e) = process() {
Expand All @@ -31,7 +30,7 @@ where
}

fn process() -> Result<(), String> {
let mut args = match parse_args() {
let args = match parse_args() {
Ok(args) => args,
Err(e) => {
println!("{}", HELP);
Expand Down Expand Up @@ -86,14 +85,16 @@ fn process() -> Result<(), String> {
.descendants()
.any(|n| n.has_tag_name(("http://www.w3.org/2000/svg", "text")));

let mut fontdb = fontdb::Database::new();
if has_text_nodes {
timed(args.perf, "FontDB", || {
load_fonts(&args.raw_args, args.usvg.fontdb_mut())
load_fonts(&args.raw_args, &mut fontdb)
});
}

let options = args.usvg(&fontdb);
let tree = timed(args.perf, "SVG Parsing", || {
usvg::Tree::from_xmltree(&xml_tree, &args.usvg).map_err(|e| e.to_string())
usvg::Tree::from_xmltree(&xml_tree, &options).map_err(|e| e.to_string())
})?;

if args.query_all {
Expand Down Expand Up @@ -460,8 +461,8 @@ struct Args {
export_area_drawing: bool,
perf: bool,
quiet: bool,
usvg: usvg::Options<'static>,
fit_to: FitTo,
default_size: usvg::Size,
background: Option<svgtypes::Color>,
raw_args: CliArgs, // TODO: find a better way
}
Expand Down Expand Up @@ -534,38 +535,6 @@ fn parse_args() -> Result<Args, String> {
fit_to = FitTo::Zoom(z);
}

let resources_dir = match args.resources_dir {
Some(ref v) => Some(v.clone()),
None => {
if let InputFrom::File(ref input) = in_svg {
// Get input file absolute directory.
std::fs::canonicalize(input)
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
} else {
None
}
}
};

let usvg = usvg::Options {
resources_dir,
dpi: args.dpi as f32,
font_family: args
.font_family
.clone()
.unwrap_or_else(|| "Times New Roman".to_string()),
font_size: args.font_size as f32,
languages: args.languages.clone(),
shape_rendering: args.shape_rendering,
text_rendering: args.text_rendering,
image_rendering: args.image_rendering,
default_size,
image_href_resolver: usvg::ImageHrefResolver::default(),
font_resolver: usvg::FontResolver::default(),
fontdb: Arc::new(fontdb::Database::new()),
};

Ok(Args {
in_svg,
out_png,
Expand All @@ -575,13 +544,49 @@ fn parse_args() -> Result<Args, String> {
export_area_drawing: args.export_area_drawing,
perf: args.perf,
quiet: args.quiet,
usvg,
fit_to,
default_size,
background: args.background,
raw_args: args,
})
}

impl Args {
fn usvg<'a>(&self, font_resolver: &'a dyn FontResolver) -> usvg::Options<'a> {
let resources_dir = match self.raw_args.resources_dir {
Some(ref v) => Some(v.clone()),
None => {
if let InputFrom::File(ref input) = self.in_svg {
// Get input file absolute directory.
std::fs::canonicalize(input)
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
} else {
None
}
}
};

usvg::Options {
resources_dir,
dpi: self.raw_args.dpi as f32,
font_family: self
.raw_args
.font_family
.clone()
.unwrap_or_else(|| "Times New Roman".to_string()),
font_size: self.raw_args.font_size as f32,
languages: self.raw_args.languages.clone(),
shape_rendering: self.raw_args.shape_rendering,
text_rendering: self.raw_args.text_rendering,
image_rendering: self.raw_args.image_rendering,
default_size: self.default_size,
image_href_resolver: usvg::ImageHrefResolver::default(),
font_resolver: Some(font_resolver),
}
}
}

fn load_fonts(args: &CliArgs, fontdb: &mut fontdb::Database) {
if !args.skip_system_fonts {
fontdb.load_system_fonts();
Expand Down
11 changes: 5 additions & 6 deletions crates/resvg/tests/integration/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use once_cell::sync::Lazy;
use rgb::{FromSlice, RGBA8};
use std::sync::Arc;
use usvg::fontdb;

#[rustfmt::skip]
Expand All @@ -10,7 +9,7 @@ mod extra;

const IMAGE_SIZE: u32 = 300;

static GLOBAL_FONTDB: Lazy<Arc<fontdb::Database>> = Lazy::new(|| {
static GLOBAL_FONTDB: Lazy<fontdb::Database> = Lazy::new(|| {
if let Ok(()) = log::set_logger(&LOGGER) {
log::set_max_level(log::LevelFilter::Warn);
}
Expand All @@ -22,7 +21,7 @@ static GLOBAL_FONTDB: Lazy<Arc<fontdb::Database>> = Lazy::new(|| {
fontdb.set_cursive_family("Yellowtail");
fontdb.set_fantasy_family("Sedgwick Ave Display");
fontdb.set_monospace_family("Noto Mono");
Arc::new(fontdb)
fontdb
});

pub fn render(name: &str) -> usize {
Expand All @@ -36,7 +35,7 @@ pub fn render(name: &str) -> usize {
.unwrap()
.to_owned(),
);
opt.fontdb = GLOBAL_FONTDB.clone();
opt.font_resolver = Some(&*GLOBAL_FONTDB);

let tree = {
let svg_data = std::fs::read(&svg_path).unwrap();
Expand Down Expand Up @@ -91,7 +90,7 @@ pub fn render_extra_with_scale(name: &str, scale: f32) -> usize {
let png_path = format!("tests/{}.png", name);

let mut opt = usvg::Options::default();
opt.fontdb = GLOBAL_FONTDB.clone();
opt.font_resolver = Some(&*GLOBAL_FONTDB);

let tree = {
let svg_data = std::fs::read(&svg_path).unwrap();
Expand Down Expand Up @@ -141,7 +140,7 @@ pub fn render_node(name: &str, id: &str) -> usize {
let png_path = format!("tests/{}.png", name);

let mut opt = usvg::Options::default();
opt.fontdb = GLOBAL_FONTDB.clone();
opt.font_resolver = Some(&*GLOBAL_FONTDB);

let tree = {
let svg_data = std::fs::read(&svg_path).unwrap();
Expand Down
26 changes: 2 additions & 24 deletions crates/usvg/src/parser/converter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ use std::hash::{Hash, Hasher};
use std::str::FromStr;
use std::sync::Arc;

#[cfg(feature = "text")]
use fontdb::Database;
use svgtypes::{Length, LengthUnit as Unit, PaintOrderKind, TransformOrigin};

use super::svgtree::{self, AId, EId, FromValue, SvgNode};
Expand Down Expand Up @@ -36,11 +34,6 @@ pub struct State<'a> {

#[derive(Clone)]
pub struct Cache {
/// This fontdb is initialized from [`Options::fontdb`] and then populated
/// over the course of conversion.
#[cfg(feature = "text")]
pub fontdb: Arc<Database>,

pub clip_paths: HashMap<String, Arc<ClipPath>>,
pub masks: HashMap<String, Arc<Mask>>,
pub filters: HashMap<String, Arc<filter::Filter>>,
Expand All @@ -58,11 +51,8 @@ pub struct Cache {
}

impl Cache {
pub(crate) fn new(#[cfg(feature = "text")] fontdb: Arc<Database>) -> Self {
pub(crate) fn new() -> Self {
Self {
#[cfg(feature = "text")]
fontdb,

clip_paths: HashMap::new(),
masks: HashMap::new(),
filters: HashMap::new(),
Expand Down Expand Up @@ -303,8 +293,6 @@ pub(crate) fn convert_doc(svg_doc: &svgtree::Document, opt: &Options) -> Result<
clip_paths: Vec::new(),
masks: Vec::new(),
filters: Vec::new(),
#[cfg(feature = "text")]
fontdb: opt.fontdb.clone(),
};

if !svg.is_visible_element(opt) {
Expand All @@ -321,10 +309,7 @@ pub(crate) fn convert_doc(svg_doc: &svgtree::Document, opt: &Options) -> Result<
opt,
};

let mut cache = Cache::new(
#[cfg(feature = "text")]
opt.fontdb.clone(),
);
let mut cache = Cache::new();

for node in svg_doc.descendants() {
if let Some(tag) = node.tag_name() {
Expand Down Expand Up @@ -375,13 +360,6 @@ pub(crate) fn convert_doc(svg_doc: &svgtree::Document, opt: &Options) -> Result<
tree.root.collect_filters(&mut tree.filters);
tree.root.calculate_bounding_boxes();

// The fontdb might have been mutated and we want to apply these changes to
// the tree's fontdb.
#[cfg(feature = "text")]
{
tree.fontdb = cache.fontdb;
}

if restore_viewbox {
calculate_svg_bbox(&mut tree);
}
Expand Down
10 changes: 1 addition & 9 deletions crates/usvg/src/parser/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,15 +345,7 @@ pub(crate) fn load_sub_svg(data: &[u8], opt: &Options) -> Option<ImageKind> {
{
// In the referenced SVG, we start with the unmodified user-provided
// fontdb, not the one from the cache.
sub_opt.fontdb = opt.fontdb.clone();

// Can't clone the resolver, so we create a new one that forwards to it.
sub_opt.font_resolver = crate::FontResolver {
select_font: Box::new(|font, db| (opt.font_resolver.select_font)(font, db)),
select_fallback: Box::new(|c, used_fonts, db| {
(opt.font_resolver.select_fallback)(c, used_fonts, db)
}),
};
sub_opt.font_resolver = opt.font_resolver;
}

let tree = Tree::from_data(data, &sub_opt);
Expand Down
34 changes: 7 additions & 27 deletions crates/usvg/src/parser/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
// 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/.

#[cfg(feature = "text")]
use std::sync::Arc;

#[cfg(feature = "text")]
use crate::FontResolver;
use crate::{ImageHrefResolver, ImageRendering, ShapeRendering, Size, TextRendering};
Expand Down Expand Up @@ -82,20 +79,13 @@ pub struct Options<'a> {
/// Default: see type's documentation for details
pub image_href_resolver: ImageHrefResolver<'a>,

/// Specifies how fonts should be resolved and loaded.
#[cfg(feature = "text")]
pub font_resolver: FontResolver<'a>,

/// A database of fonts usable by text.
///
/// This is a base database. If a custom `font_resolver` is specified,
/// additional fonts can be loaded during parsing. Those will be added to a
/// copy of this database. The full database containing all fonts referenced
/// in a `Tree` becomes available as [`Tree::fontdb`](crate::Tree::fontdb)
/// after parsing. If no fonts were loaded dynamically, that database will
/// be the same as this one.
/// Provides access to the [`fontdb::Database`] and resolves font properties into fonts
///
/// By default this is `None`. To successfully process text in SVGs, use
/// [`DefaultFontResolver`](crate::DefaultFontResolver) or a custom
/// [`FontResolver`].
#[cfg(feature = "text")]
pub fontdb: Arc<fontdb::Database>,
pub font_resolver: Option<&'a dyn FontResolver>,
}

impl Default for Options<'_> {
Expand All @@ -113,9 +103,7 @@ impl Default for Options<'_> {
default_size: Size::from_wh(100.0, 100.0).unwrap(),
image_href_resolver: ImageHrefResolver::default(),
#[cfg(feature = "text")]
font_resolver: FontResolver::default(),
#[cfg(feature = "text")]
fontdb: Arc::new(fontdb::Database::new()),
font_resolver: None,
}
}
}
Expand All @@ -130,12 +118,4 @@ impl Options<'_> {
None => rel_path.into(),
}
}

/// Mutably acquires the database.
///
/// This clones the database if it is currently shared.
#[cfg(feature = "text")]
pub fn fontdb_mut(&mut self) -> &mut fontdb::Database {
Arc::make_mut(&mut self.fontdb)
}
}
10 changes: 6 additions & 4 deletions crates/usvg/src/parser/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,13 @@ pub(crate) fn convert(
layouted: vec![],
};

if text::convert(&mut text, &state.opt.font_resolver, &mut cache.fontdb).is_none() {
return;
}
if let Some(ref font_resolver) = state.opt.font_resolver {
if text::convert(&mut text, *font_resolver).is_none() {
return;
}

parent.children.push(Node::Text(Box::new(text)));
parent.children.push(Node::Text(Box::new(text)));
}
}

struct IterState {
Expand Down
Loading
Loading