Skip to content

Commit

Permalink
Support user-provided font loading callbacks (#769)
Browse files Browse the repository at this point in the history
  • Loading branch information
laurmaedje authored Jun 1, 2024
1 parent ac7e7d0 commit 0b33ace
Show file tree
Hide file tree
Showing 21 changed files with 455 additions and 425 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 22 additions & 53 deletions crates/c-api/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ use std::slice;

use resvg::tiny_skia;
use resvg::usvg;
#[cfg(feature = "text")]
use resvg::usvg::fontdb;

/// @brief List of possible errors.
#[repr(C)]
Expand Down Expand Up @@ -113,9 +111,7 @@ pub extern "C" fn resvg_init_log() {
/// Also, contains a fonts database used during text to path conversion.
/// The database is empty by default.
pub struct resvg_options {
options: usvg::Options,
#[cfg(feature = "text")]
fontdb: fontdb::Database,
options: usvg::Options<'static>,
}

/// @brief Creates a new #resvg_options object.
Expand All @@ -125,28 +121,17 @@ pub struct resvg_options {
pub extern "C" fn resvg_options_create() -> *mut resvg_options {
Box::into_raw(Box::new(resvg_options {
options: usvg::Options::default(),
#[cfg(feature = "text")]
fontdb: fontdb::Database::new(),
}))
}

#[inline]
fn cast_opt(opt: *mut resvg_options) -> &'static mut usvg::Options {
fn cast_opt(opt: *mut resvg_options) -> &'static mut usvg::Options<'static> {
unsafe {
assert!(!opt.is_null());
&mut (*opt).options
}
}

#[cfg(feature = "text")]
#[inline]
fn cast_fontdb(opt: *mut resvg_options) -> &'static mut fontdb::Database {
unsafe {
assert!(!opt.is_null());
&mut (*opt).fontdb
}
}

/// @brief Sets a directory that will be used during relative paths resolving.
///
/// Expected to be the same as the directory that contains the SVG file,
Expand Down Expand Up @@ -208,7 +193,9 @@ pub extern "C" fn resvg_options_set_font_size(opt: *mut resvg_options, size: f32
pub extern "C" fn resvg_options_set_serif_family(opt: *mut resvg_options, family: *const c_char) {
#[cfg(feature = "text")]
{
cast_fontdb(opt).set_serif_family(cstr_to_str(family).unwrap().to_string());
cast_opt(opt)
.fontdb_mut()
.set_serif_family(cstr_to_str(family).unwrap().to_string());
}
}

Expand All @@ -227,7 +214,9 @@ pub extern "C" fn resvg_options_set_sans_serif_family(
) {
#[cfg(feature = "text")]
{
cast_fontdb(opt).set_sans_serif_family(cstr_to_str(family).unwrap().to_string());
cast_opt(opt)
.fontdb_mut()
.set_sans_serif_family(cstr_to_str(family).unwrap().to_string());
}
}

Expand All @@ -243,7 +232,9 @@ pub extern "C" fn resvg_options_set_sans_serif_family(
pub extern "C" fn resvg_options_set_cursive_family(opt: *mut resvg_options, family: *const c_char) {
#[cfg(feature = "text")]
{
cast_fontdb(opt).set_cursive_family(cstr_to_str(family).unwrap().to_string());
cast_opt(opt)
.fontdb_mut()
.set_cursive_family(cstr_to_str(family).unwrap().to_string());
}
}

Expand All @@ -259,7 +250,9 @@ pub extern "C" fn resvg_options_set_cursive_family(opt: *mut resvg_options, fami
pub extern "C" fn resvg_options_set_fantasy_family(opt: *mut resvg_options, family: *const c_char) {
#[cfg(feature = "text")]
{
cast_fontdb(opt).set_fantasy_family(cstr_to_str(family).unwrap().to_string());
cast_opt(opt)
.fontdb_mut()
.set_fantasy_family(cstr_to_str(family).unwrap().to_string());
}
}

Expand All @@ -278,7 +271,9 @@ pub extern "C" fn resvg_options_set_monospace_family(
) {
#[cfg(feature = "text")]
{
cast_fontdb(opt).set_monospace_family(cstr_to_str(family).unwrap().to_string());
cast_opt(opt)
.fontdb_mut()
.set_monospace_family(cstr_to_str(family).unwrap().to_string());
}
}

Expand Down Expand Up @@ -408,13 +403,7 @@ pub extern "C" fn resvg_options_load_font_data(
#[cfg(feature = "text")]
{
let data = unsafe { slice::from_raw_parts(data as *const u8, len) };

let opt = unsafe {
assert!(!opt.is_null());
&mut *opt
};

opt.fontdb.load_font_data(data.to_vec())
cast_opt(opt).fontdb_mut().load_font_data(data.to_vec())
}
}

Expand All @@ -438,12 +427,7 @@ pub extern "C" fn resvg_options_load_font_file(
None => return resvg_error::NOT_AN_UTF8_STR as i32,
};

let opt = unsafe {
assert!(!opt.is_null());
&mut *opt
};

if opt.fontdb.load_font_file(file_path).is_ok() {
if cast_opt(opt).fontdb_mut().load_font_file(file_path).is_ok() {
resvg_error::OK as i32
} else {
resvg_error::FILE_OPEN_FAILED as i32
Expand Down Expand Up @@ -473,12 +457,7 @@ pub extern "C" fn resvg_options_load_font_file(
pub extern "C" fn resvg_options_load_system_fonts(opt: *mut resvg_options) {
#[cfg(feature = "text")]
{
let opt = unsafe {
assert!(!opt.is_null());
&mut *opt
};

opt.fontdb.load_system_fonts();
cast_opt(opt).fontdb_mut().load_system_fonts();
}
}

Expand Down Expand Up @@ -526,12 +505,7 @@ pub extern "C" fn resvg_parse_tree_from_file(
Err(_) => return resvg_error::FILE_OPEN_FAILED as i32,
};

let utree = usvg::Tree::from_data(
&file_data,
&raw_opt.options,
#[cfg(feature = "text")]
&raw_opt.fontdb,
);
let utree = usvg::Tree::from_data(&file_data, &raw_opt.options);

let utree = match utree {
Ok(tree) => tree,
Expand Down Expand Up @@ -569,12 +543,7 @@ pub extern "C" fn resvg_parse_tree_from_data(
&*opt
};

let utree = usvg::Tree::from_data(
data,
&raw_opt.options,
#[cfg(feature = "text")]
&raw_opt.fontdb,
);
let utree = usvg::Tree::from_data(data, &raw_opt.options);

let utree = match utree {
Ok(tree) => tree,
Expand Down
18 changes: 6 additions & 12 deletions crates/resvg/examples/custom_href_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,23 @@ fn main() {
let ferris_image = std::sync::Arc::new(std::fs::read("./examples/ferris.png").unwrap());

// We know that our SVG won't have DataUrl hrefs, just return None for such case.
let resolve_data = Box::new(
|_: &str, _: std::sync::Arc<Vec<u8>>, _: &usvg::Options, _: &usvg::fontdb::Database| None,
);
let resolve_data = Box::new(|_: &str, _: std::sync::Arc<Vec<u8>>, _: &usvg::Options| None);

// Here we handle xlink:href attribute as string,
// let's use already loaded Ferris image to match that string.
let resolve_string = Box::new(
move |href: &str, _: &usvg::Options, _: &usvg::fontdb::Database| match href {
"ferris_image" => Some(usvg::ImageKind::PNG(ferris_image.clone())),
_ => None,
},
);
let resolve_string = Box::new(move |href: &str, _: &usvg::Options| match href {
"ferris_image" => Some(usvg::ImageKind::PNG(ferris_image.clone())),
_ => None,
});

// Assign new ImageHrefResolver option using our closures.
opt.image_href_resolver = usvg::ImageHrefResolver {
resolve_data,
resolve_string,
};

let fontdb = usvg::fontdb::Database::new();

let svg_data = std::fs::read("./examples/custom_href_resolver.svg").unwrap();
let tree = usvg::Tree::from_data(&svg_data, &opt, &fontdb).unwrap();
let tree = usvg::Tree::from_data(&svg_data, &opt).unwrap();

let pixmap_size = tree.size().to_int_size();
let mut pixmap = tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap();
Expand Down
7 changes: 2 additions & 5 deletions crates/resvg/examples/draw_bboxes.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use usvg::fontdb;

fn main() {
let args: Vec<String> = std::env::args().collect();
if !(args.len() == 3 || args.len() == 5) {
Expand All @@ -23,11 +21,10 @@ fn main() {
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf()));

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

let svg_data = std::fs::read(&args[1]).unwrap();
let tree = usvg::Tree::from_data(&svg_data, &opt, &fontdb).unwrap();
let tree = usvg::Tree::from_data(&svg_data, &opt).unwrap();

let mut bboxes = Vec::new();
let mut stroke_bboxes = Vec::new();
Expand Down
7 changes: 2 additions & 5 deletions crates/resvg/examples/minimal.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use usvg::fontdb;

fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() != 3 {
Expand All @@ -14,11 +12,10 @@ fn main() {
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf()));

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

let svg_data = std::fs::read(&args[1]).unwrap();
usvg::Tree::from_data(&svg_data, &opt, &fontdb).unwrap()
usvg::Tree::from_data(&svg_data, &opt).unwrap()
};

let pixmap_size = tree.size().to_int_size();
Expand Down
12 changes: 7 additions & 5 deletions crates/resvg/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#![allow(clippy::uninlined_format_args)]

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

use usvg::fontdb;

Expand All @@ -30,7 +31,7 @@ where
}

fn process() -> Result<(), String> {
let args = match parse_args() {
let mut args = match parse_args() {
Ok(args) => args,
Err(e) => {
println!("{}", HELP);
Expand Down Expand Up @@ -85,15 +86,14 @@ 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, &mut fontdb)
load_fonts(&args.raw_args, args.usvg.fontdb_mut())
});
}

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

if args.query_all {
Expand Down Expand Up @@ -460,7 +460,7 @@ struct Args {
export_area_drawing: bool,
perf: bool,
quiet: bool,
usvg: usvg::Options,
usvg: usvg::Options<'static>,
fit_to: FitTo,
background: Option<svgtypes::Color>,
raw_args: CliArgs, // TODO: find a better way
Expand Down Expand Up @@ -562,6 +562,8 @@ fn parse_args() -> Result<Args, String> {
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 {
Expand Down
21 changes: 11 additions & 10 deletions crates/resvg/tests/integration/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use once_cell::sync::Lazy;
use rgb::{FromSlice, RGBA8};
use std::process::Command;
use std::sync::Arc;
use usvg::fontdb;

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

const IMAGE_SIZE: u32 = 300;

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

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

let tree = {
let svg_data = std::fs::read(&svg_path).unwrap();
let db = GLOBAL_FONTDB.lock().unwrap();
usvg::Tree::from_data(&svg_data, &opt, &db).unwrap()
usvg::Tree::from_data(&svg_data, &opt).unwrap()
};

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

let opt = usvg::Options::default();
let mut opt = usvg::Options::default();
opt.fontdb = GLOBAL_FONTDB.clone();

let tree = {
let svg_data = std::fs::read(&svg_path).unwrap();
let db = GLOBAL_FONTDB.lock().unwrap();
usvg::Tree::from_data(&svg_data, &opt, &db).unwrap()
usvg::Tree::from_data(&svg_data, &opt).unwrap()
};

let size = tree.size().to_int_size().scale_by(scale).unwrap();
Expand Down Expand Up @@ -140,12 +141,12 @@ pub fn render_node(name: &str, id: &str) -> usize {
let svg_path = format!("tests/{}.svg", name);
let png_path = format!("tests/{}.png", name);

let opt = usvg::Options::default();
let mut opt = usvg::Options::default();
opt.fontdb = GLOBAL_FONTDB.clone();

let tree = {
let svg_data = std::fs::read(&svg_path).unwrap();
let db = GLOBAL_FONTDB.lock().unwrap();
usvg::Tree::from_data(&svg_data, &opt, &db).unwrap()
usvg::Tree::from_data(&svg_data, &opt).unwrap()
};

let node = tree.node_by_id(id).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion crates/usvg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ simplecss = "0.2"
siphasher = "1.0" # perfect hash implementation

# text
fontdb = { version = "0.17.0", default-features = false, optional = true }
fontdb = { version = "0.18.0", default-features = false, optional = true }
rustybuzz = { version = "0.14.0", optional = true }
unicode-bidi = { version = "0.3", optional = true }
unicode-script = { version = "0.5", optional = true }
Expand Down
Loading

0 comments on commit 0b33ace

Please sign in to comment.