-
Notifications
You must be signed in to change notification settings - Fork 241
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
Give the user more flexibility over font selection #754
Conversation
crates/usvg/src/text/mod.rs
Outdated
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(()) | ||
} | ||
|
||
pub trait FontProvider { | ||
fn resolve_font(&self, font: &Font) -> Option<ResolvedFont>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we return ResolvedFont
and not just ID
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And I think we should call it find_font
. Similar to the method below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, will change.
|
||
fn find_font_for_char(&self, c: char, exclude_fonts: &[ID]) -> Option<ResolvedFont>; | ||
|
||
fn fontdb(&self) -> &Database; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we even need this method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What method are you talking about? The fontdb
method? Yes, because eventually we need to access it to actually load the underlying font data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't something like font_provider as fontdb::Database
work?
Thanks. Looks good to me. |
Have you tried implementing |
I'm not sure if that's possible. Because the user has to store the fontdb somehow, and if we only provide a struct that contains three fields with callback functions and nothing else, the user doesn't really have a way of storing it... |
But the user stores fontdb already. Nothing changes here. And the Options trait would have fontdb as an argument. pub type FontResolverFn = Box<dyn Fn(&Font, &fontdb::Database) -> Option<ID> + Send + Sync>; Wouldn't this work? |
So basically, my understanding is that there are two problems to solve, one of which is a must for Typst, the other one is more of a nice-to-have:
So my envisioned usage would have been as follows: struct MyCustomFontProvider {
fontdb: RefCell<Database>
}
impl FontProvider for MyCustomFontProvider {
fn find_font(&self, font: &Font) -> Option<ID> {
// A custom logic for selecting a font, and then loading the font
// data in a vector (regardless of whether the data comes from locally or a remote server)
let font: Vec<u8> = my_custom_logic_for_selecting_a_font();
// Insert the font we selected and fetched into the fontdb, and then return the ID
let font_id = self.fontdb.borrow_mut().add_font(font);
Some(font_id)
}
fn find_fallback_font(&self, c: char, base_font_id: ID, used_fonts: &[ID]) -> Option<ID> {
// Basically the same as find_font, but instead for choosing a fallback font.
}
fn fontdb(&self) -> &Database {
// Here we give out a reference to the fontdb so that usvg can load the font data
// via with_face_data and get all the necessary metrics, etc. Since we only use
// IDs that have been fetched either via find_font or find_fallback_font, usvg
// is guaranteed to only access fonts that have been loaded into the database
&self.fontdb.borrow()
}
} However, I just realized that fontdb doesn't seem to return the assigned ID of a newly inserted font... It seems like the only way to get an ID is via the |
I'm starting to think that perhaps the real solution to the problem is to extend And I'm also not a 100% sure what is needed from Typst's side, so maybe it's best to wait for what @laurmaedje has to say. |
As far as I know,
Looks like I think the current interface should work for Typst and it looks reasonable to me. The only problem that stands out to me is the
|
Because we're adding fonts and fontdb stores faces. So when we add a The real issue with your solution is that What we probably want is to add Will it work? I don't know. We just have to try it out. To summarize: |
I would rather keep |
Nevermind. I will discuss it with @laurmaedje and will get back to this once we have a proper plan. |
@RazrFalcon continuing from before. My current approach would have been as follows: I think there are two additions to
I've implemented a prototype here (the code for storing the coverage I "stole" from Typst): RazrFalcon/fontdb@master...LaurenzV:fontdb:custom If we have this, then I think all that would be needed is to change the I think this should work, although as I mentioned I haven't tested it in Typst yet. And I'm also not sure about the memory footprint of storing the coverage in that way. |
That's a highly Typst-specific solution. I don't like it. |
FunctionalityRegarding functionality, I think this PR mostly works as-is, the only problem is that the Codepub trait FontProvider {
fn find_font(&self, font: &Font) -> Option<ID>;
fn find_fallback_font(&self, c: char, base_font_id: ID, used_fonts: &[ID]) -> Option<ID>;
fn with_database(&self, f: &mut dyn FnMut(&Database));
}
pub(crate) trait FontProviderExt {
fn with_face_data<P, T>(&self, id: ID, p: P) -> Option<T>
where
P: FnOnce(&[u8], u32) -> T;
...
}
impl<F: FontProvider + ?Sized> FontProviderExt for F {
fn with_face_data<P, T>(&self, id: ID, p: P) -> Option<T>
where
P: FnOnce(&[u8], u32) -> T,
{
let mut output = None;
let mut p = Some(p);
self.with_database(&mut |db| {
if let Some(p) = p.take() {
output = db.with_face_data(id, p);
}
});
output
}
...
} Internally, only DesignThe remaining question is whether the design of this PR is satisfactory to you.
I think immutable access to the database doesn't suffice as it needs to be populated. I'm not sure how exactly you're envisioning And then, there is the question of how to handle font fallback, which is also fairly simple in the trait object design. I would also like to note that something nice about the design in this PR is that |
Yes, there should be a mutable reference there. But your trait is immutable as well, no? |
I used interior mutability for that, which requires the whole dance with The trait methods could perhaps also be multiple |
I do not understand how it would work. Can you provide an example? Af for lifetimes, boxes and traits - it's not the important part. The important part is having a shared, mutable A reminder that embedded SVG fonts are also planned. Meaning that |
See my branch of resvg and my linked implementation in Typst. It's really just a
Do you mean just If usvg would have an Another small missing piece is to let |
I genuinely never used
Let's say we're parsing two SVG files from two threads using a single To allow this I suggest storing Basically, my solution is to treat the provided |
I see now. That makes sense. I think I assume that then the parser would do |
@laurmaedje Yep. That's the general idea. As for |
@RazrFalcon I have a working implementation of the Arc-approch on this branch. I implemented it exactly like Here is small overview of the changes:
PS: Usage in Typst looks like this (still rough around the edges). |
Looks really good! Thanks! I will accept this PR.
Haven't even considered it... |
Deprecated in favor of #769 |
An attempt to tackle #710 (comment). The basic idea is that we call
resolve_font
andfind_font_for_char
to tell the user which fonts we want (which the user can then dynamically load in the background) and then we request them in the end by requesting a reference tofontdb
and using that.In the beginning, I wanted to make the whole thing very generic. Meaning that I tried to write the trait in a way that completely abstracts away from fontdb and fontdb::ID, so that the user has full control over the font selection as well as storing, and it's (in principle) completely abstracted away from usvg. However, there were two big problems with this:
with_face_data
callback, which makes it really awkward to incorporate it into a trait because it doesn't seem to be possible to create trait objects if there are generics involved.So for now, this is the best design I was able to come up with, which is far from ideal, but it should be better than nothing. We just need to document it properly.
So the remaining steps would be:
cc @laurmaedje