diff --git a/internal/c/libqb.cpp b/internal/c/libqb.cpp index c21416dc5..c362006b0 100644 --- a/internal/c/libqb.cpp +++ b/internal/c/libqb.cpp @@ -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; } diff --git a/internal/c/libqb/include/font.h b/internal/c/libqb/include/font.h index a372e1351..7d17778ae 100644 --- a/internal/c/libqb/include/font.h +++ b/internal/c/libqb/include/font.h @@ -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); diff --git a/internal/c/parts/video/font/font.cpp b/internal/c/parts/video/font/font.cpp index 1adcd78cc..71b31ee06 100644 --- a/internal/c/parts/video/font/font.cpp +++ b/internal/c/parts/video/font/font.cpp @@ -7,16 +7,13 @@ #include "font.h" #include "freetypeamalgam.h" #include "gui.h" +#include "libqb-common.h" #include #include #include // 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); diff --git a/internal/c/qbx.cpp b/internal/c/qbx.cpp index 516e8e7d3..aba120fe5 100755 --- a/internal/c/qbx.cpp +++ b/internal/c/qbx.cpp @@ -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); diff --git a/source/subs_functions/subs_functions.bas b/source/subs_functions/subs_functions.bas index 711cf1f1d..7488a2ecc 100644 --- a/source/subs_functions/subs_functions.bas +++ b/source/subs_functions/subs_functions.bas @@ -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