-
Notifications
You must be signed in to change notification settings - Fork 227
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
base: main
Are you sure you want to change the base?
Conversation
Do I understand correctly that your approach doesn't allow sharing fontdb between threads? That's one of the requirements. |
I'll delegate to @laurmaedje. :p |
I think it should but haven't tested. Specifically, there is a
|
Both the current and the old implementations allowed sharing |
Fixed: there's no longer a need for I think the previous code would have cloned the DB anyway when calling Certainly now it shouldn't be cloning the |
Just so you know I briefly talked to @laurmaedje and he said he would comment why he implemented it the way he did, although I'm not sure when he will get to it. But might be good to wait a bit before merging. |
Ok, will wait then. I do not want to break typst again. |
For context, the current implementation comes from #769. This implementation was preceded by the earlier PR #754. The design in this PR is closer to #754 than #769. The discussion of that PR provides some insights into problems, but I'll try to summarize things here. The main conceptual problem, from my understanding, is how support for fonts in embedded in SVGs would work. In the current model, since usvg manages the database, it can mutate it itself. The On another note, if I'm not mistaken the implementation in this PR doesn't really allow for lazy font loading. Since both If I may ask: Which concrete use case is the motivation for changing things again? The The one somewhat costly thing I can see is that if you parse a bunch of SVGs and lazy load, the fontdb is populated over and over again. But you can cache things in the background and keep that relatively cheap (in the grand scheme of what usvg does I would be surprised if this was a large factor). Moreover, it helps a bit with correctness because in the current design it's harder to mess things up in a way where parsing of one SVG affects font selection of another SVG (because of an already populated database). |
Another reason for this is that in the future we will support fonts embedded in CSS, which must not be added to the "global" fontdb. That's why we need an ability to deeply clone the database on-demand. |
The intention is that a wrapper type be used to manage that lock... which also requires an associated type (something I forgot to add previously)... pub trait FontResolver: Debug + Sync {
type DbGuard<'a>: std::ops::Deref<Target = Database>;
fn fontdb(&self) -> Self::DbGuard<'_>;
// ...
}
#[derive(Debug)]
struct LockingFontResolver {
db: std::sync::Mutex<Database>,
}
impl FontResolver for LockingFontResolver {
type DbGuard<'a> = std::sync::MutexGuard<'a, Database>;
fn fontdb(&self) -> Self::DbGuard<'_> {
self.db.lock().unwrap()
}
} ... and thus usage of A better option might be to do this: pub trait FontResolver: Debug + Sync {
fn with_fontdb_dyn(&self, f: Box<dyn FnOnce(&Database) + '_>);
// ...
}
pub trait FontResolverExt: FontResolver {
fn with_fontdb<R>(&self, f: impl (FnOnce(&Database) -> R)) -> Option<R> {
let mut result = None;
let out = &mut result;
self.with_fontdb_dyn(Box::new(|db| *out = Some(f(db))));
result
}
}
impl<FR: FontResolver + ?Sized> FontResolverExt for FR {} Then we can still use pub(crate) fn convert(text: &mut Text, resolver: &dyn FontResolver) -> Option<()> {
let (text_fragments, bbox) = layout::layout_text(text, resolver)?;
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) = resolver.with_fontdb(|fontdb| 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(())
} |
Admittedly, this is a big motivation to keep the existing design (passing |
I went down the same path of thoughts during #754 and ended up with a very similar approach here, but I think it's far from pretty. |
Edit: doesn't work because you need clone support. I'd also be satisfied with |
Could you elaborate more on your motivation? The three bullet points from the original post all sound like conceptual concerns rather than motivating a specific use case that isn't currently achievable. |
To further explain my motivation: Ideally therefore I would implement I could store the whole |
For what it's worth, Typst also has its own font loading and metadata stack and its implementation of Since the font selection functions are only needed during parsing, I think you could maybe just borrow from your |
It seems this approach is viable. Work on the font management side is here: kas-gui/kas-text#87. (The SVG font resolver is WIP.) |
Summary
usvg::FontResolver
is changed from a struct to a trait, with a default implementationDefaultFontResolver
.usvg::Options
andusvg::Tree
no longer containfontdb
; instead thenFontResolver
provides access as required.Motivation
The existing model:
fontdb
be shared under anArc
, which shouldn't be necessaryfontdb
before modifyingfontdb
accessible throughusvg::Tree
, which doesn't appear to be the right place for itInstead, the user is expected to provide a
&dyn FontResolver
instance in order to process any SVG involving text. As a consequence:fontdb
instance (without cloning) and may even insert additional fonts directly into this DB (viaRefCell
,Mutex
or similar)fontdb
instance used withoutArc
font_resolver
is not declared beforeusvg::Options
).