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

[feat] Add support for loading OpenType fonts from memory. #82

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
149 changes: 95 additions & 54 deletions fbink.c
Original file line number Diff line number Diff line change
Expand Up @@ -5624,19 +5624,10 @@ static __attribute__((cold)) const char*
}
}

// Load OT fonts for fbink_add_ot_font & fbink_add_ot_font_v2
// Load OT fonts fot add_ot_font
static __attribute__((cold)) int
add_ot_font(const char* filename, FONT_STYLE_T style, FBInkOTFonts* restrict ot_fonts)
add_ot_font_data(const unsigned char* data, FONT_STYLE_T style, FBInkOTFonts* restrict ot_fonts)
{
# ifdef FBINK_FOR_KOBO
// NOTE: Bail if we were passed a Kobo system font, as they're obfuscated,
// and some of them risk crashing stbtt because of bogus data...
const char blacklist[] = "/usr/local/Trolltech/QtEmbedded-4.6.2-arm/lib/fonts/";
if (!strncmp(filename, blacklist, sizeof(blacklist) - 1)) {
WARN("Cannot use font `%s`: it's an obfuscated Kobo system font", filename + sizeof(blacklist) - 1);
return ERRCODE(EXIT_FAILURE);
}
# endif

// Init libunibreak the first time we're called
if (!otInit) {
Expand All @@ -5645,70 +5636,34 @@ static __attribute__((cold)) int
}
otInit = true;

// Open font from given path, and load into buffer
FILE* f = fopen(filename, "r" STDIO_CLOEXEC);
unsigned char* restrict data = NULL;
if (!f) {
PFWARN("fopen: %m");
otInit = false;
return ERRCODE(EXIT_FAILURE);
} else {
const int fd = fileno(f);
struct stat st;
if (fstat(fd, &st) == -1) {
PFWARN("fstat: %m");
fclose(f);
otInit = false;
return ERRCODE(EXIT_FAILURE);
}
data = calloc((size_t) st.st_size, sizeof(*data));
if (!data) {
PFWARN("Error allocating font data buffer: %m");
fclose(f);
otInit = false;
return ERRCODE(EXIT_FAILURE);
}
if (fread(data, 1U, (size_t) st.st_size, f) < (size_t) st.st_size || ferror(f) != 0) {
free(data);
fclose(f);
otInit = false;
WARN("Error reading font file `%s`", filename);
return ERRCODE(EXIT_FAILURE);
}
fclose(f);
}
stbtt_fontinfo* font_info = calloc(1U, sizeof(stbtt_fontinfo));
stbtt_fontinfo* font_info = calloc(1U, sizeof(stbtt_fontinfo));
if (!font_info) {
PFWARN("Error allocating stbtt_fontinfo struct: %m");
free(data);
Copy link
Owner

Choose a reason for hiding this comment

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

ditto

return ERRCODE(EXIT_FAILURE);
}
// First, check if we can actually find a recognizable font format in the data...
const int fontcount = stbtt_GetNumberOfFonts(data);
if (fontcount == 0) {
free(data);
Copy link
Owner

Choose a reason for hiding this comment

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

ditto

free(font_info);
WARN("File `%s` doesn't appear to be a valid or supported font", filename);
WARN("File doesn't appear to be a valid or supported font");
return ERRCODE(EXIT_FAILURE);
} else if (fontcount > 1) {
LOG("Font file `%s` appears to be a font collection containing %d fonts, but we'll only use the first one!",
filename,
LOG("Font file appears to be a font collection containing %d fonts, but we'll only use the first one!",
fontcount);
}
// Then, get the offset to the first font
const int fontoffset = stbtt_GetFontOffsetForIndex(data, 0);
if (fontoffset == -1) {
free(data);
Copy link
Owner

@NiLuJe NiLuJe Jan 17, 2025

Choose a reason for hiding this comment

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

In the same vein, this needs to be free(font_info->data);

Which implies that we take ownership of the pointer, which might be a problem for your usecase (e.g., pointer to static content), so this may need to be more complex to add a way to say "nope, this data is immutable, don't ever try to free it".

Copy link
Owner

Choose a reason for hiding this comment

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

Weeelll, except font_info->data only exists after stbtt_InitFont ;p.

The point about ownership still stands, though ;p.

free(font_info);
WARN("File `%s` doesn't appear to contain valid font data at offset %d", filename, fontoffset);
WARN("File doesn't appear to contain valid font data at offset %d", fontoffset);
return ERRCODE(EXIT_FAILURE);
}
// And finally, initialize that font
// NOTE: We took the long way 'round to try to avoid crashes on invalid data...
if (!stbtt_InitFont(font_info, data, fontoffset)) {
free(font_info->data);
free(font_info);
WARN("Error initialising font `%s`", filename);
WARN("Error initialising font.");
return ERRCODE(EXIT_FAILURE);
}
// Assign the current font to its appropriate FBInkOTFonts struct member, depending on the style specified by the caller.
Expand Down Expand Up @@ -5741,13 +5696,65 @@ static __attribute__((cold)) int
default:
free(font_info->data);
free(font_info);
WARN("Cannot load font `%s`: requested style (%d) is invalid!", filename, style);
WARN("Cannot load font: requested style (%d) is invalid!", style);
return ERRCODE(EXIT_FAILURE);
}

ELOG("Font `%s` loaded for style '%s'", filename, font_style_to_string(style));
ELOG("Font loaded for style '%s'", font_style_to_string(style));
return EXIT_SUCCESS;
}

// Load OT fonts from a file for fbink_add_ot_font & fbink_add_ot_font_v2
static __attribute__((cold)) int
add_ot_font(const char* filename, FONT_STYLE_T style, FBInkOTFonts* restrict ot_fonts)
{
# ifdef FBINK_FOR_KOBO
// NOTE: Bail if we were passed a Kobo system font, as they're obfuscated,
// and some of them risk crashing stbtt because of bogus data...
const char blacklist[] = "/usr/local/Trolltech/QtEmbedded-4.6.2-arm/lib/fonts/";
if (!strncmp(filename, blacklist, sizeof(blacklist) - 1)) {
WARN("Cannot use font `%s`: it's an obfuscated Kobo system font", filename + sizeof(blacklist) - 1);
return ERRCODE(EXIT_FAILURE);
}
# endif


// Open font from given path, and load into buffer
FILE* f = fopen(filename, "r" STDIO_CLOEXEC);
unsigned char* restrict data = NULL;
if (!f) {
PFWARN("fopen: %m");
otInit = false;
return ERRCODE(EXIT_FAILURE);
} else {
const int fd = fileno(f);
struct stat st;
if (fstat(fd, &st) == -1) {
PFWARN("fstat: %m");
fclose(f);
otInit = false;
return ERRCODE(EXIT_FAILURE);
}
data = calloc((size_t) st.st_size, sizeof(*data));
if (!data) {
PFWARN("Error allocating font data buffer: %m");
fclose(f);
otInit = false;
return ERRCODE(EXIT_FAILURE);
}
if (fread(data, 1U, (size_t) st.st_size, f) < (size_t) st.st_size || ferror(f) != 0) {
free(data);
fclose(f);
otInit = false;
WARN("Error reading font file `%s`", filename);
return ERRCODE(EXIT_FAILURE);
}
fclose(f);
}
int result = add_ot_font_data(data, style, ot_fonts);
free(data);
Copy link
Owner

Choose a reason for hiding this comment

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

That's a problem ;).

(The data is still live, stbtt doesn't make a copy, it just keeps a reference to that same pointer).

return result;
}
#endif // FBINK_WITH_OPENTYPE

// Load font from given file path. Up to four font styles may be used by FBInk at any given time.
Expand All @@ -5764,6 +5771,20 @@ int
#endif // FBINK_WITH_OPENTYPE
}

// Load fron from memory. Up to four font styles may be used by FBInk at any given time.
int
fbink_add_ot_font_from_memory(const unsigned char* data UNUSED_BY_MINIMAL, FONT_STYLE_T style UNUSED_BY_MINIMAL)
{
#ifdef FBINK_WITH_OPENTYPE
// Legacy variant, using the global otFonts
LOG("Loading font data in the global font pool . . .");
return add_ot_font_data(data, style, &otFonts);
#else
WARN("OpenType support is disabled in this FBInk build");
return ERRCODE(ENOSYS);
#endif // FBINK_WITH_OPENTYPE
}

// Load font from given file path. Up to four font styles may be used per FBInkOTConfig instance.
int
fbink_add_ot_font_v2(const char* filename UNUSED_BY_MINIMAL,
Expand All @@ -5788,6 +5809,26 @@ int
#endif // FBINK_WITH_OPENTYPE
}

// Load font fron from memory. Up to four font styles may be used per FBInkOTConfig instance.
int fbink_add_ot_font_from_memory_v2(const unsigned char* data UNUSED_BY_MINIMAL, FONT_STYLE_T style UNUSED_BY_MINIMAL, FBInkOTConfig *restrict cfg UNUSED_BY_MINIMAL) {
#ifdef FBINK_WITH_OPENTYPE
// Start by allocating an FBInkOTFonts struct, if need be...
if (!cfg->font) {
cfg->font = calloc(1U, sizeof(FBInkOTFonts));
if (!cfg->font) {
PFWARN("Error allocating FBInkOTFonts struct: %m");
return ERRCODE(EXIT_FAILURE);
}
}
// New variant, using a per-FBInkOTConfig instance
LOG("Loading font data in a local FBInkOTFonts instance (%p) . . .", cfg->font);
return add_ot_font_data(data, style, (FBInkOTFonts*) cfg->font);
#else
WARN("OpenType support is disabled in this FBInk build");
return ERRCODE(ENOSYS);
#endif // FBINK_WITH_OPENTYPE
}

#ifdef FBINK_WITH_OPENTYPE
// Free an individual OpenType font structure
static __attribute__((cold)) int
Expand Down
10 changes: 10 additions & 0 deletions fbink.h
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,11 @@ FBINK_API int fbink_print(int fbfd, const char* restrict string, const FBInkConf
// Which leads me to a final, critical warning:
// NOTE: Don't try to pass non-font files or encrypted/obfuscated font files, because it *will* horribly segfault!
FBINK_API int fbink_add_ot_font(const char* filename, FONT_STYLE_T style) __attribute__((nonnull));

// Same as fbink_add_ot_font, but loads the font from memory instead of a file.
// data: Pointer to the font data in memory.
FBINK_API int fbink_add_ot_font_from_memory(const unsigned char* data, FONT_STYLE_T style) __attribute__((nonnull));

// Same API and behavior, except that the set of fonts being loaded is tied to this specific FBInkOTConfig instance,
// instead of being global.
// In which case, resources MUST be released via fbink_free_ot_fonts_v2()!
Expand All @@ -856,6 +861,11 @@ FBINK_API int fbink_add_ot_font(const char* filename, FONT_STYLE_T style) __attr
FBINK_API int fbink_add_ot_font_v2(const char* filename, FONT_STYLE_T style, FBInkOTConfig* restrict cfg)
__attribute__((nonnull));

// Same as fbink_add_ot_font_v2, but loads the font from memory instead of a file.
// data: Pointer to the font data in memory.
FBINK_API int fbink_add_ot_font_from_memory_v2(const unsigned char* data, FONT_STYLE_T style, FBInkOTConfig* restrict cfg)
__attribute__((nonnull));

// Free all loaded OpenType fonts. You MUST call this when you have finished all OT printing.
// NOTE: Safe to call even if no fonts were actually loaded.
FBINK_API int fbink_free_ot_fonts(void);
Expand Down