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

Feature: Bring your own font #3020

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

Fesaa
Copy link
Contributor

@Fesaa Fesaa commented Jun 26, 2024

Added

  • Added: user may provide their own fonts to be used in the epub reader

THIS IS A DRAFT

I'm not done with this yet, quite a bit to do. But feel like it is time to ask for feedback on a few things. Opening a draft pull request, just so it's here as well. Will cross post in the discord after.

Loading the fonts.

They're currently being loaded in the book-reader, this should ensure they're only being loaded when needed. I've also not noticed any higher loading times, but I've only had max 4 fonts loaded. We may want to try with a bunch more, at the very least the default ones, to see if it has any noticeable impact.

Design choices

Need some feedback / greenlight for design choices as to call what what, and place what where.

  • Fonts are called EpubFonts
  • Fonts are stored in ${pwd}/config/fonts

If these are fine, how do we want to differentiate between the default fonts, and user fonts? Can we store them in the same folder, should we not?

If these are not fine, suggestions as to changes to do.

Link with AppUserPreferences

Currently, the font preference for a user is saved, as the fonts name. It's still doing this currently, and should work fine. However, we could change this to the ID of the font. Having it be the fonts name, does make it easier to load and reset them. As simple as changing it to default.

Default font
The dropdown menu in the UI, currently, isn't displaying the default font, as it's not an EpubFont. Where should I best inject this option? In the UI, or the controller; instinctively I would do it in the controller, such that the UI doesn't have too much extra logic it has no business of owning. But wanted to ask for opinions anyway, maybe there is a better place?

Notes

  • The controller currently has the wrong authorize annotations, still has to be changed.
  • The seed class doesn't have all default fonts in it yet. I only copied over one
  • I'll probably squash commits before merging and give them a better name

TODO's

Apart from "smaller" TODO's in the code. I think these are the big ones still to do

  • Add update task in Admin UI
  • Auto scan fonts
  • Scan update communication with UI

I may have missed some

will add comments in draft pull request
Copy link
Member

@majora2007 majora2007 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall a great start. I didn't pull it down and test it, but from the code, I got the jist of it. I think this will be a great enhancement, but I do strongly think moving to a more UI centered loading system would be really smart. This will reduce friction by having to have admin's add their own fonts via their filesystem and instead rely completely on the UI.

I would also argue that we could expand this functionality to non-admins (we would need a role for this). And if we really wanted to go to the next step, we could intake not only a file, but a Google Fonts url and download directly into the fonts directory.

Let me know your thoughts as that expands the scope (but would be really fun to code).


[ResponseCache(CacheProfileName = "10Minute")]
[AllowAnonymous]
[HttpGet("GetFonts")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not how API names are. Use either list or all

}

[AllowAnonymous]
[HttpGet("download-font")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave this as HttpGet only. So api is /api/font.

This need authentication via API Key. We cannot have an exposed api to allow downloading fonts off the server.

var font = await _unitOfWork.EpubFontRepository.GetFont(fontId);
if (font == null) return NotFound();
var contentType = GetContentType(font.FileName);
return File(await _fontService.GetContent(fontId), contentType, font.FileName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use PhysicalFile like other areas an use enable Range processing.

try
{
var font = await _unitOfWork.EpubFontRepository.GetFont(fontId);
if (font == null) return NotFound();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like to have a newline above if statements and return statements to help keep code easy to consume.

{
try
{
var font = await _unitOfWork.EpubFontRepository.GetFont(fontId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I don't follow always, having Async when the method returns a task is very helpful.

fonts.forEach(font => {
const fontFace = new FontFace(font.name, `url(${this.bookService.baseUrl}Font/download-font?fontId=${font.id})`);
fontFace.load().then(loadedFace => {
(document as any).fonts.add(loadedFace);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

document should come from the injected Document instance, to allow SSR if I ever want to go that route.

this.bookService.getEpubFonts().subscribe(fonts => {
fonts.forEach(font => {
const fontFace = new FontFace(font.name, `url(${this.bookService.baseUrl}Font/download-font?fontId=${font.id})`);
fontFace.load().then(loadedFace => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I'm worried about is timing. There is no way to know if all the fonts are loaded or not. I also don't see setting the loaded fonts somewhere for the user to select from.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second pass, I think instead what we should do is move this load fonts to the book service. To make this most efficient, we can cache the loaded fonts and return the fonts from that.

bookService.getEpubFonts() {
 getFonts().pipe(tap(f => {
            const fontFace = new FontFace(font.name, `url(${this.bookService.baseUrl}Font/download-font?fontId=${font.id})`);
            fontFace.load().then(loadedFace => { // cache then put on the document }
            // ...
}

We just need to bust this when we get a fontScan complete (or if we go the theme manager route, book theme modified event).

Thoughts?

User = 2,
}

export interface EpubFont {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be in their own ts file.

return [{title: 'default', family: 'default'}, {title: 'EBGaramond', family: 'EBGaramond'}, {title: 'Fira Sans', family: 'Fira_Sans'},
{title: 'Lato', family: 'Lato'}, {title: 'Libre Baskerville', family: 'Libre_Baskerville'}, {title: 'Merriweather', family: 'Merriweather'},
{title: 'Nanum Gothic', family: 'Nanum_Gothic'}, {title: 'RocknRoll One', family: 'RocknRoll_One'}, {title: 'Open Dyslexic', family: 'OpenDyslexic2'}];
getEpubFonts(): Observable<EpubFont[]> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to specify the return on the method signature since it's now obv.

this.bookService.getEpubFonts().subscribe(res => {
this.fontFamilies = res.map(f => f.name);
this.cdRef.markForCheck();
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing semicolon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants