1
1
Fork 0
mirror of https://github.com/QB64-Phoenix-Edition/QB64pe.git synced 2024-07-06 01:10:23 +00:00

Add font load-from-memory support

This commit is contained in:
Samuel Gomes 2023-04-20 09:23:13 +05:30
parent 469d0a11e3
commit e4b36ca8ad
5 changed files with 213 additions and 193 deletions

View file

@ -1379,8 +1379,6 @@ void showvalue(__int64);
#endif
void sub_beep();
int32 func__loadfont(qbs *filename, int32 size, qbs *requirements, int32 passed);
int32 lastfont = 48;
int32 *font = (int32 *)calloc(4 * (48 + 1), 1); // NULL=unused index
int32 *fontheight = (int32 *)calloc(4 * (48 + 1), 1);
@ -13784,9 +13782,12 @@ void printchr(int32 character) {
} // printchr
int32 chrwidth(uint32 character) {
// Note: Only called by qbs_print()
// Supports "UNICODE" _LOADFONT option
/// @brief Returns the width of a character in pixels.
/// Note: Only called by qbs_print().
/// Supports "UNICODE" _LOADFONT option
/// @param character The character to return the width for (this can be UTF32)
/// @return The width in pixels
int32_t chrwidth(uint32_t character) {
auto im = write_page;
auto f = im->font;
auto w = fontwidth[f];
@ -13795,7 +13796,6 @@ int32 chrwidth(uint32 character) {
// Custom font
// a740g: No need to render just to find the pixel length
if ((fontflags[f] & 32)) { // UNICODE character
w = FontPrintWidthUTF32(font[f], (uint32_t *)&character, 1);
} else { // ASCII character
@ -24871,148 +24871,119 @@ int32 func__printwidth(qbs *text, int32 screenhandle, int32 passed) {
screenhandle = write_page_index;
}
if (text->len == 0)
return 0; // No point calculating an empty string
if (img[screenhandle].text) { // Return LEN(text) in non-graphic modes
// error(5);
return text->len;
}
if (text->len == 0)
return 0; // No point calculating an empty string
int32 fonthandle = img[screenhandle].font; // Get the font used in screenhandle
int32 fwidth = func__fontwidth(fonthandle, 1); // Try and get the font width
if (fwidth != 0)
return fwidth * (text->len); // if it's not a variable width font, return the width * the letter count
auto fonthandle = img[screenhandle].font; // Get the font used in screenhandle
auto fwidth = func__fontwidth(fonthandle, 1); // Try and get the font width
if (fwidth)
return fwidth * text->len; // if it's not a variable width font, return the width * the letter count
// a740g: For variable width fonts
return FontPrintWidthASCII(font[fonthandle], text->chr, text->len);
}
int32 func__loadfont(qbs *f, int32 size, qbs *requirements, int32 passed) {
// f=_LOADFONT(ttf_filename$,height[,"bold,italic,underline,monospace,dontblend,unicode"])
/// @brief f = _LOADFONT(ttf_filename$, height[, "monospace,dontblend,unicode,memory"][, index])
/// @param f The font file path name or a font memory buffer when loading from memory
/// @param size The size of the font height in pixels
/// @param requirements This can be monospace, dontblend, unicode, memory
/// @param font_index The index of the font to load from a font collection
/// @param passed Which optional arguments were passed
/// @return Retuns a valid handle on success or zero on failure
int32_t func__loadfont(qbs *file_name, int32_t size, qbs *requirements, int32_t font_index, int32_t passed) {
// Some QB strings that we'll need
static qbs *fileNameZ = nullptr;
static qbs *reqs = nullptr;
if (new_error)
return NULL;
qbs *s1 = NULL;
s1 = qbs_new(0, 0);
qbs *req = NULL;
req = qbs_new(0, 0);
qbs *s3 = NULL;
s3 = qbs_new(0, 0);
uint8 r[32];
int32 i, i2, i3;
static int32 recall;
if (new_error || !file_name->len)
return INVALID_FONT_HANDLE; // return invalid handle for any garbage input
// validate size
if (size < 1) {
error(5);
return NULL;
return INVALID_FONT_HANDLE;
}
if (size > 2048)
return -1;
// load the file
if (!f->len)
return -1; // return invalid handle if null length string
int32 fh, result;
int64 bytes;
fh = gfs_open(f, 1, 0, 0);
if (!fileNameZ)
fileNameZ = qbs_new(0, 0);
#ifdef QB64_WINDOWS // rather than just immediately tossing an error, let's try looking in the default OS folder for the font first in case the user left off
// the filepath.
if (fh < 0 && recall == 0) {
recall = -1; // to set a flag so we don't get trapped endlessly recalling the routine when the font actually doesn't exist
i = func__loadfont(qbs_add(qbs_new_txt("C:/Windows/Fonts/"), f), size, requirements, passed); // Look in the default windows font location
return i;
}
#endif
recall = 0;
if (fh < 0)
return -1; // If we still can't load the font, then we just can't load the font... Send an error code back.
if (!reqs)
reqs = qbs_new(0, 0);
// check requirements
memset(r, 0, 32);
if (passed) {
if (requirements->len) {
i = 1;
qbs_set(req, qbs_ucase(requirements)); // convert tmp str to perm str
nextrequirement:
i2 = func_instr(i, req, qbs_new_txt(","), 1);
if (i2) {
qbs_set(s1, func_mid(req, i, i2 - i, 1));
} else {
qbs_set(s1, func_mid(req, i, req->len - i + 1, 1));
}
qbs_set(s1, qbs_rtrim(qbs_ltrim(s1)));
if (qbs_equal(s1, qbs_new_txt("BOLD"))) {
r[0]++;
goto valid;
}
if (qbs_equal(s1, qbs_new_txt("ITALIC"))) {
r[1]++;
goto valid;
}
if (qbs_equal(s1, qbs_new_txt("UNDERLINE"))) {
r[2]++;
goto valid;
}
if (qbs_equal(s1, qbs_new_txt("DONTBLEND"))) {
r[3]++;
goto valid;
}
if (qbs_equal(s1, qbs_new_txt("MONOSPACE"))) {
r[4]++;
goto valid;
}
if (qbs_equal(s1, qbs_new_txt("UNICODE"))) {
r[5]++;
goto valid;
}
error(5);
return NULL; // invalid requirements
valid:
if (i2) {
i = i2 + 1;
goto nextrequirement;
}
for (i = 0; i < 32; i++)
if (r[i] > 1) {
error(5);
return NULL;
} // cannot define requirements twice
} //->len
} // passed
int32 options;
options = r[0] + (r[1] << 1) + (r[2] << 2) + (r[3] << 3) + (r[4] << 4) + (r[5] << 5);
// 1 bold TTF_STYLE_BOLD
// 2 italic TTF_STYLE_ITALIC
// 4 underline TTF_STYLE_UNDERLINE
auto isLoadFromMemory = false; // should the font be loaded from memory?
int32_t options = 0; // font flags that we'll prepare and save to fontflags[]
FONT_DEBUG_PRINT("passed = %i", passed);
// Check requirements
// 8 dontblend (blending is the default in 32-bit alpha-enabled modes)
// 16 monospace
// 32 unicode
if ((passed & 1) && requirements->len) {
FONT_DEBUG_PRINT("Parsing requirements");
bytes = gfs_lof(fh);
static uint8 *content;
content = (uint8 *)malloc(bytes);
if (!content) {
gfs_close(fh);
return -1;
qbs_set(reqs, qbs_ucase(requirements)); // Convert tmp str to perm str
if (func_instr(1, reqs, qbs_new_txt("DONTBLEND"), 1)) {
options |= FONT_LOAD_DONTBLEND;
FONT_DEBUG_PRINT("DONTBLEND requested");
}
if (func_instr(1, reqs, qbs_new_txt("MONOSPACE"), 1)) {
options |= FONT_LOAD_MONOSPACE;
FONT_DEBUG_PRINT("MONOSPACE requested");
}
if (func_instr(1, reqs, qbs_new_txt("UNICODE"), 1)) {
options |= FONT_LOAD_UNICODE;
FONT_DEBUG_PRINT("UNICODE requested");
}
if (func_instr(1, reqs, qbs_new_txt("MEMORY"), 1)) {
isLoadFromMemory = true;
FONT_DEBUG_PRINT("MEMORY requested");
}
}
result = gfs_read(fh, -1, content, bytes);
gfs_close(fh);
if (result < 0) {
free(content);
return -1;
// Check if a font index was requested
if (passed & 2) {
FONT_DEBUG_PRINT("Loading font index %i", font_index);
} else {
FONT_DEBUG_PRINT("Loading default font index (0)");
font_index = 0;
}
uint8_t *content;
int32_t bytes;
if (isLoadFromMemory) {
content = file_name->chr; // we should not free this!!!
bytes = file_name->len;
FONT_DEBUG_PRINT("Loading font from memory. Size = %i", bytes);
} else {
qbs_set(fileNameZ, qbs_add(file_name, qbs_new_txt_len("\0", 1))); // s1 = filename + CHR$(0)
content = FontLoadFileToMemory((char *)fileNameZ->chr, &bytes); // this we must free!!!
FONT_DEBUG_PRINT("Loading font from file %s", fileNameZ->chr);
}
if (!content)
return INVALID_FONT_HANDLE; // Return invalid handle if loading the image failed
// open the font
// get free font handle
int32_t i;
for (i = 32; i <= lastfont; i++) {
if (!font[i])
goto got_font_index;
}
// increase handle range
// a740g: none of this stuff is safe and ought to be rewritten
// We will need to look at how to do this better
lastfont++;
font = (int32 *)realloc(font, 4 * (lastfont + 1));
font[lastfont] = NULL;
@ -25020,71 +24991,21 @@ int32 func__loadfont(qbs *f, int32 size, qbs *requirements, int32 passed) {
fontwidth = (int32 *)realloc(fontwidth, 4 * (lastfont + 1));
fontflags = (int32 *)realloc(fontflags, 4 * (lastfont + 1));
i = lastfont;
got_font_index:
static int32 h;
h = FontLoad(content, bytes, size, -1, options);
free(content);
auto h = FontLoad(content, bytes, size, font_index, options);
if (!isLoadFromMemory)
free(content); // free font memory if it was loaded from a file
if (!h)
return -1;
return INVALID_FONT_HANDLE;
font[i] = h;
fontheight[i] = size;
fontwidth[i] = FontWidth(h);
fontflags[i] = options;
return i;
}
int32 fontopen(qbs *f, int32 size, int32 options) { // Note: Just a stripped down verions of FUNC__LOADFONT
static int32 i;
// load the file
if (!f->len)
return -1; // return invalid handle if null length string
static int32 fh, result;
static int64 bytes;
fh = gfs_open(f, 1, 0, 0);
if (fh < 0)
return -1;
bytes = gfs_lof(fh);
static uint8 *content;
content = (uint8 *)malloc(bytes);
if (!content) {
gfs_close(fh);
return -1;
}
result = gfs_read(fh, -1, content, bytes);
gfs_close(fh);
if (result < 0) {
free(content);
return -1;
}
// open the font
// get free font handle
for (i = 32; i <= lastfont; i++) {
if (!font[i])
goto got_font_index;
}
// increase handle range
lastfont++;
font = (int32 *)realloc(font, 4 * (lastfont + 1));
font[lastfont] = NULL;
fontheight = (int32 *)realloc(fontheight, 4 * (lastfont + 1));
fontwidth = (int32 *)realloc(fontwidth, 4 * (lastfont + 1));
fontflags = (int32 *)realloc(fontflags, 4 * (lastfont + 1));
i = lastfont;
got_font_index:
static int32 h;
h = FontLoad(content, bytes, size, -1, options);
free(content);
if (!h)
return -1;
font[i] = h;
fontheight[i] = size;
fontwidth[i] = FontWidth(h);
fontflags[i] = options;
return i;
}

View file

@ -26,9 +26,19 @@
# define FONT_DEBUG_CHECK(_exp_) // Don't do anything in release builds
#endif
#define INVALID_FONT_HANDLE 0
// Font load options
#define FONT_LOAD_DONTBLEND 8
#define FONT_LOAD_MONOSPACE 16
#define FONT_LOAD_UNICODE 32
// Font render options
#define FONT_RENDER_MONOCHROME 1
/// @brief CP437 to UTF-16 LUT
extern uint16_t codepage437_to_unicode16[];
uint8_t *FontLoadFileToMemory(const char *file_path_name, int32_t *out_bytes);
int32_t FontLoad(const uint8_t *content_original, int32_t content_bytes, int32_t default_pixel_height, int32_t which_font, int32_t options);
void FontFree(int32_t fh);
int32_t FontWidth(int32_t fh);

View file

@ -7,16 +7,13 @@
#include "font.h"
#include "freetypeamalgam.h"
#include "gui.h"
#include "libqb-common.h"
#include <cmath>
#include <unordered_map>
#include <vector>
// QB64 expects invalid font handles to be zero
#define INVALID_FONT_HANDLE 0
#define IS_FONT_HANDLE_VALID(_handle_) ((_handle_) > INVALID_FONT_HANDLE && (_handle_) < fontManager.fonts.size() && fontManager.fonts[_handle_]->isUsed)
// Font options
#define FONT_MONOSPACE 16
#define FONT_MONOCHROME 1
// See FontManager::Font::utf8Decode() below for more details
#define UTF8_ACCEPT 0
@ -581,6 +578,107 @@ struct FontManager {
/// @brief Global font manager object
static FontManager fontManager;
/// @brief Loads a whole font file from disk to memory.
/// This will search for the file in known places if it is not found in the current directory
/// @param file_path_name The font file name. This can be a relative path
/// @param out_bytes The size of the data that was loaded. This cannot be NULL
/// @return A pointer to a buffer with the data. NULL on failure. The caller is responsible for freeing this memory
uint8_t *FontLoadFileToMemory(const char *file_path_name, int32_t *out_bytes) {
// This is simply a list of known locations to look for a font
static const char *const FONT_PATHS[][2] = {
#ifdef QB64_WINDOWS
{"%s/Microsoft/Windows/Fonts/%s", "LOCALAPPDATA"}, {"%s/Fonts/%s", "SystemRoot"}
#elif defined(QB64_MACOSX)
{"%s/Library/Fonts/%s", "HOME"}, {"%s/Library/Fonts/%s", nullptr}, {"%s/System/Library/Fonts/%s", nullptr}
#elif defined(QB64_LINUX)
{"%s/.fonts/%s", "HOME"},
{"%s/.local/share/fonts/%s", "HOME"},
{"%s/usr/local/share/fonts/%s", nullptr},
{"%s/usr/share/fonts/%s", nullptr},
{"%s/usr/share/fonts/opentype/%s", nullptr},
{"%s/usr/share/fonts/truetype/%s", nullptr}
#endif
};
// Attempt to open the file with the current file pathname
auto fontFile = fopen(file_path_name, "rb");
if (!fontFile) {
FONT_DEBUG_PRINT("Failed to open font file: %s", file_path_name);
FONT_DEBUG_PRINT("Attempting to load font file using known paths");
static const auto PATH_BUFFER_SIZE = 4096;
auto pathName = (char *)malloc(PATH_BUFFER_SIZE);
if (!pathName) {
FONT_DEBUG_PRINT("Failed to allocate working buffer");
return nullptr;
}
FONT_DEBUG_PRINT("Allocate working buffer");
// Go over the known locations and see what works
for (auto i = 0; i < (sizeof(FONT_PATHS) / sizeof(uintptr_t) / 2); i++) {
memset(pathName, 0, PATH_BUFFER_SIZE);
if (FONT_PATHS[i][1] && getenv(FONT_PATHS[i][1]))
sprintf_s(pathName, PATH_BUFFER_SIZE, FONT_PATHS[i][0], getenv(FONT_PATHS[i][1]), file_path_name);
else
sprintf_s(pathName, PATH_BUFFER_SIZE, FONT_PATHS[i][0], "", file_path_name);
FONT_DEBUG_PRINT("Attempting to load %s", pathName);
fontFile = fopen(pathName, "rb");
if (fontFile)
break; // exit the loop if something worked
}
free(pathName);
FONT_DEBUG_PRINT("Working buffer freed");
if (!fontFile) {
FONT_DEBUG_PRINT("No know locations worked");
return nullptr; // return NULL if all attempts failed
}
}
if (fseek(fontFile, 0, SEEK_END) != 0) {
FONT_DEBUG_PRINT("Failed to seek end of font file: %s", file_path_name);
fclose(fontFile);
return nullptr;
}
*out_bytes = ftell(fontFile);
if (*out_bytes < 0) {
FONT_DEBUG_PRINT("Failed to determine size of font file: %s", file_path_name);
fclose(fontFile);
return nullptr;
}
if (fseek(fontFile, 0, SEEK_SET) != 0) {
FONT_DEBUG_PRINT("Failed to seek beginning of font file: %s", file_path_name);
fclose(fontFile);
return nullptr;
}
auto buffer = (uint8_t *)malloc(*out_bytes);
if (!buffer) {
FONT_DEBUG_PRINT("Failed to allocate memory for font file: %s", file_path_name);
fclose(fontFile);
return nullptr;
}
if (fread(buffer, *out_bytes, 1, fontFile) != 1) {
FONT_DEBUG_PRINT("Failed to read font file: %s", file_path_name);
fclose(fontFile);
free(buffer);
return nullptr;
}
fclose(fontFile);
FONT_DEBUG_PRINT("Successfully loaded font file: %s", file_path_name);
return buffer;
}
/// @brief Loads a FreeType font from memory. The font data is locally copied and is kept alive while in use
/// @param content_original The original font data in memory that is copied
/// @param content_bytes The length of the data in bytes
@ -635,7 +733,7 @@ int32_t FontLoad(const uint8_t *content_original, int32_t content_bytes, int32_t
lroundf((((float)fontManager.fonts[h]->face->size->metrics.ascender / 64.0f) / ((float)fontManager.fonts[h]->face->size->metrics.height / 64.0f)) *
(float)default_pixel_height);
if (options & FONT_MONOSPACE) {
if (options & FONT_LOAD_MONOSPACE) {
// Get the width of upper case W
if (FT_Load_Char(fontManager.fonts[h]->face, 'W', FT_LOAD_DEFAULT)) {
FONT_DEBUG_PRINT("FT_Load_Char() 'W' failed");
@ -700,7 +798,7 @@ bool FontRenderTextUTF32(int32_t fh, const uint32_t *codepoint, int32_t codepoin
if (codepoints <= 0)
return codepoints == 0; // true if zero, false if -ve
auto isMonochrome = options & FONT_MONOCHROME; // do we need to do monochrome rendering?
auto isMonochrome = options & FONT_RENDER_MONOCHROME; // do we need to do monochrome rendering?
auto outBufW = font->GetStringPixelWidth((FT_ULong *)codepoint, (FT_ULong)codepoints); // get the total buffer width
auto outBufH = (size_t)font->defaultHeight; // height is always set by the QB64
auto outBuf = (uint8_t *)calloc(outBufW, outBufH);

View file

@ -161,7 +161,6 @@ extern void sub__memcopy(void *sblk, ptrszint soff, ptrszint bytes, void *dblk,
ptrszint doff);
extern mem_block func__memnew(ptrszint);
extern mem_block func__memimage(int32, int32);
extern mem_block func__memsound(int32 i, int32 targetChannel);
extern int64 func__shellhide(qbs *str);
extern int64 func_shell(qbs *str);
@ -200,7 +199,6 @@ extern void sub__maptriangle(int32 cull_options, float sx1, float sy1,
float dy3, float dz3, int32 di,
int32 smooth_options, int32 passed);
extern void sub__depthbuffer(int32 options, int32 dst, int32 passed);
extern int32 func_play(int32 ignore);
extern void sub_paletteusing(void *element, int32 bits);
extern int64 func_read_int64(uint8 *data, ptrszint *data_offset,
ptrszint data_size);
@ -260,7 +258,6 @@ extern void sub_draw(qbs *);
extern void qbs_maketmp(qbs *);
extern void sub_run(qbs *);
extern void sub_run_init();
extern void sndcloseall();
extern void freeallimages();
extern void call_interrupt(int32, void *, void *);
extern void call_interruptx(int32, void *, void *);
@ -271,12 +268,10 @@ extern int32 freeimg(uint32);
extern void imgrevert(int32);
extern int32 imgframe(uint8 *o, int32 x, int32 y, int32 bpp);
extern int32 imgnew(int32 x, int32 y, int32 bpp);
extern int32 imgload(char *filename, int32 bpp);
extern void sub__putimage(double f_dx1, double f_dy1, double f_dx2,
double f_dy2, int32 src, int32 dst, double f_sx1,
double f_sy1, double f_sx2, double f_sy2,
int32 passed);
extern int32 fontopen(char *name, double d_height, int32 flags);
extern int32 selectfont(int32 f, img_struct *im);
extern void sndsetup();
extern uint32 sib();
@ -423,8 +418,7 @@ extern double func_point(float x, float y, int32 passed);
extern void sub_pset(float x, float y, uint32 col, int32 passed);
extern void sub_preset(float x, float y, uint32 col, int32 passed);
extern void printchr(int32 character);
extern int32 printchr2(int32 x, int32 y, uint32 character, int32 i);
extern int32 chrwidth(int32 character);
extern int32_t chrwidth(uint32_t character);
extern void newline();
extern void makefit(qbs *text);
extern void lprint_makefit(qbs *text);
@ -567,8 +561,6 @@ extern void sub__mouseinputpipe(int32 context);
extern void sub__mousepipeclose(int32 context);
extern void call_absolute(int32 args, uint16 offset);
extern void call_interrupt(int32 i);
extern void sub_play(qbs *str);
extern int32 func__newimage(int32 x, int32 y, int32 bpp, int32 passed);
extern int32 func__copyimage(int32 i, int32 mode, int32 passed);
extern void sub__freeimage(int32 i, int32 passed);
@ -594,8 +586,7 @@ extern void sub__copypalette(int32 i, int32 i2, int32 passed);
extern void sub__printstring(float x, float y, qbs *text, int32 i,
int32 passed);
extern int32 func__printwidth(qbs *text, int32 i, int32 passed);
extern int32 func__loadfont(qbs *filename, int32 size, qbs *requirements,
int32 passed);
extern int32_t func__loadfont(qbs *file_name, int32_t size, qbs *requirements, int32_t font_index, int32_t passed);
extern void sub__font(int32 f, int32 i, int32 passed);
extern int32 func__fontwidth(int32 f, int32 passed);
extern int32 func__fontheight(int32 f, int32 passed);

View file

@ -1325,11 +1325,11 @@ clearid
id.n = qb64prefix$ + "LoadFont": id.Dependency = DEPENDENCY_LOADFONT
id.subfunc = 1
id.callname = "func__loadfont"
id.args = 3
id.arg = MKL$(STRINGTYPE - ISPOINTER) + MKL$(DOUBLETYPE - ISPOINTER) + MKL$(STRINGTYPE - ISPOINTER)
id.specialformat = "?,?[,?]"
id.args = 4
id.arg = MKL$(STRINGTYPE - ISPOINTER) + MKL$(LONGTYPE - ISPOINTER) + MKL$(STRINGTYPE - ISPOINTER) + MKL$(LONGTYPE - ISPOINTER)
id.specialformat = "?,?[,[?][,[?]]]"
id.ret = LONGTYPE - ISPOINTER
id.hr_syntax = "_LOADFONT(fileName$, size%[, " + CHR$(34) + "{MONOSPACE|, BOLD|, ITALIC|, UNDERLINE|, UNICODE|, DONTBLEND}" + CHR$(34) + "])"
id.hr_syntax = "_LOADFONT(fileName$, size&[, " + CHR$(34) + "{MONOSPACE|, UNICODE|, DONTBLEND|, MEMORY}" + CHR$(34) + "][, fontIndex])"
regid
clearid