From 60a448fb5414ddb6488c68c875a20055d0d5ec23 Mon Sep 17 00:00:00 2001 From: Samuel Gomes Date: Sun, 7 May 2023 22:12:47 +0530 Subject: [PATCH] Add _UCHARPOS() to get individual char pixel position --- internal/c/libqb/include/font.h | 5 +- internal/c/parts/video/font/font.cpp | 168 ++++++++++++++++-- source/qb64pe.bas | 24 ++- source/subs_functions/subs_functions.bas | 13 +- .../syntax_highlighter_list.bas | 2 +- 5 files changed, 191 insertions(+), 21 deletions(-) diff --git a/internal/c/libqb/include/font.h b/internal/c/libqb/include/font.h index 13d59cb9a..24a4c6922 100644 --- a/internal/c/libqb/include/font.h +++ b/internal/c/libqb/include/font.h @@ -19,11 +19,11 @@ FONT_DEBUG_PRINT("Condition (%s) failed", #_exp_) #else # ifdef _MSC_VER -# define FONT_DEBUG_PRINT(_fmt_, ...) // Don't do anything in release builds +# define FONT_DEBUG_PRINT(_fmt_, ...) // Don't do anything in release builds # else # define FONT_DEBUG_PRINT(_fmt_, _args_...) // Don't do anything in release builds # endif -# define FONT_DEBUG_CHECK(_exp_) // Don't do anything in release builds +# define FONT_DEBUG_CHECK(_exp_) // Don't do anything in release builds #endif #define INVALID_FONT_HANDLE 0 @@ -53,3 +53,4 @@ int32_t func__UFontHeight(int32_t qb64_fh, int32_t passed); int32_t func__UPrintWidth(const qbs *text, int32_t utf_encoding, int32_t qb64_fh, int32_t passed); int32_t func__ULineSpacing(int32_t qb64_fh, int32_t passed); void sub__UPrintString(int32_t start_x, int32_t start_y, const qbs *text, int32_t max_width, int32_t utf_encoding, int32_t qb64_fh, int32_t passed); +int32_t func__UCharPos(const qbs *text, void *arr, int32_t utf_encoding, int32_t qb64_fh, int32_t passed); diff --git a/internal/c/parts/video/font/font.cpp b/internal/c/parts/video/font/font.cpp index 60a3cef53..79e144d21 100644 --- a/internal/c/parts/video/font/font.cpp +++ b/internal/c/parts/video/font/font.cpp @@ -18,7 +18,7 @@ // Note: QB64 expects invalid font handles to be zero #define IS_VALID_FONT_HANDLE(_h_) ((_h_) > INVALID_FONT_HANDLE && (_h_) < fontManager.fonts.size() && fontManager.fonts[_h_]->isUsed) -#define IS_VALID_QB64_FONT_HANDLE(_h_) ((_h_) == 8 || (_h_) == 14 || (_h_) == 16 || ((_h_) >=32 && (_h_) <= lastfont && font[_h_])) +#define IS_VALID_QB64_FONT_HANDLE(_h_) ((_h_) == 8 || (_h_) == 14 || (_h_) == 16 || ((_h_) >= 32 && (_h_) <= lastfont && font[_h_])) #define IS_VALID_UTF_ENCODING(_e_) ((_e_) == 0 || (_e_) == 8 || (_e_) == 16 || (_e_) == 32) // These are from libqb.cpp @@ -85,7 +85,7 @@ class UTF32 { UTF32 &operator=(const UTF32 &) = delete; UTF32 &operator=(UTF32 &&) = delete; - std::vector codepoints; // UTF32 codepoint dyanamic array + std::vector codepoints; // UTF32 codepoint dynamic array /// @brief Converts an ASCII array to UTF-32 /// @param str The ASCII array @@ -132,7 +132,6 @@ class UTF32 { default: // Need to read continuation bytes continue; - break; } } @@ -160,7 +159,7 @@ class UTF32 { auto ch2 = str16[i + 1]; if (ch2 >= 0xDC00 && ch2 <= 0xDFFF) { cp = ((ch - 0xD800) << 10) + (ch2 - 0xDC00) + 0x10000; - ++i; // skip the second surrogate + ++i; // skip the second surrogate } else { cp = 0xFFFD; // invalid surrogate pair } @@ -879,18 +878,18 @@ bool FontRenderTextUTF32(int32_t fh, const uint32_t *codepoint, int32_t codepoin *out_y = fnt->defaultHeight; if (codepoints <= 0) - return codepoints == 0; // true if zero, false if -ve + return codepoints == 0; // true if zero, false if -ve auto isMonochrome = options & FONT_RENDER_MONOCHROME; // do we need to do monochrome rendering? FT_Vector strPixSize = { - fnt->GetStringPixelWidth(codepoint, codepoints), // get the total buffer width - fnt->defaultHeight // height is always set by the QB64 + fnt->GetStringPixelWidth(codepoint, codepoints), // get the total buffer width + fnt->defaultHeight // height is always set by the QB64 }; auto outBuf = (uint8_t *)calloc(strPixSize.x, strPixSize.y); if (!outBuf) return false; - FONT_DEBUG_PRINT("Allocated (%llu x %llu) buffer", strPixSize.x, strPixSize.y); + FONT_DEBUG_PRINT("Allocated (%lu x %lu) buffer", strPixSize.x, strPixSize.y); FT_Pos penX = 0; @@ -925,8 +924,8 @@ bool FontRenderTextUTF32(int32_t fh, const uint32_t *codepoint, int32_t codepoin glyph->bitmap = isMonochrome ? glyph->bitmapMono : glyph->bitmapGray; // select monochrome or gray bitmap glyph->RenderBitmap(outBuf, strPixSize.x, strPixSize.y, penX + glyph->bearing.x, fnt->baseline - glyph->bearing.y); - penX += glyph->advanceWidth; // add advance width - previousGlyph = glyph; // save the current glyph pointer for use later + penX += glyph->advanceWidth; // add advance width + previousGlyph = glyph; // save the current glyph pointer for use later } } } @@ -1194,19 +1193,19 @@ void sub__UPrintString(int32_t start_x, int32_t start_y, const qbs *text, int32_ if (!drawBuf) return; - FONT_DEBUG_PRINT("Allocated (%llu x %llu) buffer", strPixSize.x, strPixSize.y); + FONT_DEBUG_PRINT("Allocated (%lu x %lu) buffer", strPixSize.x, strPixSize.y); auto isMonochrome = (write_page->bytes_per_pixel == 1) || ((write_page->bytes_per_pixel == 4) && (write_page->alpha_disabled)) || (fontflags[qb64_fh] & FONT_LOAD_DONTBLEND); // do we need to do monochrome rendering? FT_Vector pen = {0, 0}; // set to buffer start if (qb64_fh < 32) { + FONT_DEBUG_PRINT("Rendering using built-in font"); + // Render using a built-in font FT_Vector draw, pixmap; uint8_t const *builtinFont = nullptr; - pen.y += 2; - for (size_t i = 0; i < codepoints; i++) { auto cp = str32[i]; if (cp > 255) @@ -1226,7 +1225,6 @@ void sub__UPrintString(int32_t start_x, int32_t start_y, const qbs *text, int32_ case 16: builtinFont = &charset8x16[cp][0][0]; - break; } for (draw.y = pen.y, pixmap.y = 0; pixmap.y < qb64_fh; draw.y++, pixmap.y++) { @@ -1238,6 +1236,8 @@ void sub__UPrintString(int32_t start_x, int32_t start_y, const qbs *text, int32_ pen.x += 8; } } else { + FONT_DEBUG_PRINT("Rendering using TrueType font"); + // Render using custom font pen.y += lroundf((float)face->ascender / (float)face->units_per_EM * (float)fnt->defaultHeight); @@ -1281,8 +1281,8 @@ void sub__UPrintString(int32_t start_x, int32_t start_y, const qbs *text, int32_ glyph->bitmap = isMonochrome ? glyph->bitmapMono : glyph->bitmapGray; // select monochrome or gray bitmap glyph->RenderBitmap(drawBuf, strPixSize.x, strPixSize.y, pen.x + glyph->bearing.x, pen.y - glyph->bearing.y); - pen.x += glyph->advanceWidth; // add advance width - previousGlyph = glyph; // save the current glyph pointer for use later + pen.x += glyph->advanceWidth; // add advance width + previousGlyph = glyph; // save the current glyph pointer for use later } } } @@ -1384,3 +1384,139 @@ void sub__UPrintString(int32_t start_x, int32_t start_y, const qbs *text, int32_ free(drawBuf); } + +/// @brief Calculate the starting pixel positions of each chancter to an array. First one being zero. +/// This also calculates the pixel position of the last + 1 character. +/// @param text Text for which the data needs to be calculated. This can be unicode encoded +/// @param arr A QB64 array. This should be codepoints + 1 long. If the array is shorter additional calculated data is ignored +/// @param utf_encoding The UTF encoding of the text (0 = ASCII, 8 = UTF-8, 16 - UTF-16, 32 = UTF-32) +/// @param qb64_fh A QB64 font handle (this can be a builtin font as well) +/// @param passed Optional arguments flag +/// @return Total codepoints in `text` +int32_t func__UCharPos(const qbs *text, void *arr, int32_t utf_encoding, int32_t qb64_fh, int32_t passed) { + libqb_mutex_guard lock(fontManager.m); + + if (new_error || !text->len) + return 0; + + // Check if have an array to work with + // If not then simply return the count of codepoints later + if (!(passed & 1)) { + FONT_DEBUG_PRINT("Array not passed"); + arr = nullptr; + } + + // Check UTF argument + if (passed & 2) { + if (!IS_VALID_UTF_ENCODING(utf_encoding)) { + error(5); + return 0; + } + } else { + utf_encoding = 0; + } + + // Check if a valid font handle was passed + if (passed & 4) { + if (!IS_VALID_QB64_FONT_HANDLE(qb64_fh)) { + error(258); + return 0; + } + } else { + qb64_fh = write_page->font; // else get the current write page font handle + } + + // Convert the string to UTF-32 if needed + uint32_t const *str32 = nullptr; + size_t codepoints = 0; + + switch (utf_encoding) { + case 32: // UTF-32: no conversion needed + str32 = (uint32_t *)text->chr; + codepoints = text->len / sizeof(uint32_t); + break; + + case 16: // UTF-16: conversion required + codepoints = utf32.ConvertUTF16(text->chr, text->len); + if (codepoints) + str32 = utf32.codepoints.data(); + break; + + case 8: // UTF-8: conversion required + codepoints = utf32.ConvertUTF8(text->chr, text->len); + if (codepoints) + str32 = utf32.codepoints.data(); + break; + + default: // ASCII: conversion required + codepoints = utf32.ConvertASCII(text->chr, text->len); + if (codepoints) + str32 = utf32.codepoints.data(); + } + + // Simply return the codepoint count if we do not have any array + if (!arr || !codepoints) + return codepoints; + + auto element = (uint16_t *)((byte_element_struct *)arr)->offset; + auto elements = ((byte_element_struct *)arr)->length / sizeof(uint16_t); + FontManager::Font *fnt = nullptr; + FT_Pos monospaceWidth; + + if (qb64_fh < 32) { + monospaceWidth = 8; // built-in fonts always have a width of 8 + } else { + FONT_DEBUG_CHECK(IS_VALID_FONT_HANDLE(font[qb64_fh])); + fnt = fontManager.fonts[font[qb64_fh]]; + monospaceWidth = fnt->monospaceWidth; + } + + if (monospaceWidth) { + FONT_DEBUG_PRINT("Calculating positions for monospaced font"); + + // Fixed width font character positions + for (size_t i = 0; i < codepoints; i++) { + if (i < elements) + element[i] = i * monospaceWidth; + } + + // (element[codepoints] - 1) = the end position of the last char in the string + if (codepoints < elements) + element[codepoints] = codepoints * monospaceWidth; + } else { + FONT_DEBUG_PRINT("Calculating positions for variable width font"); + + // Variable width font character positions + auto face = fnt->face; + auto hasKerning = FT_HAS_KERNING(fnt->face); // set to true if font has kerning info + FontManager::Font::Glyph *glyph = nullptr; + FontManager::Font::Glyph *previousGlyph = nullptr; + FT_Pos penX = 0; + + for (size_t i = 0; i < codepoints; i++) { + auto cp = str32[i]; + + glyph = fnt->GetGlyph(cp); + if (glyph) { + if (i < elements) + element[i] = penX; + + // Add kerning advance width if kerning table is available + if (hasKerning && previousGlyph && glyph) { + FT_Vector delta; + FT_Get_Kerning(fnt->face, previousGlyph->index, glyph->index, FT_KERNING_DEFAULT, &delta); + penX += delta.x / 64; + } + + penX += glyph->advanceWidth; // add advance width + previousGlyph = glyph; // save the current glyph pointer for use later + } + } + + // (element[codepoints] - 1) = the end position of the last char in the string + if (codepoints < elements) + element[codepoints] = penX; + } + + return codepoints; +} diff --git a/source/qb64pe.bas b/source/qb64pe.bas index 182fcb3c0..0bf090a59 100644 --- a/source/qb64pe.bas +++ b/source/qb64pe.bas @@ -3500,7 +3500,7 @@ DO ' Just try to concatenate the path with the source or include path and check if we are able to find the file IF inclevel > 0 AND _FILEEXISTS(getfilepath(incname(inclevel)) + MidiSoundFont$) THEN MidiSoundFont$ = getfilepath(incname(inclevel)) + MidiSoundFont$ - ELSEIF _FILEEXISTS(FixDirectoryName(path.source$) + MidiSoundFont$) Then + ELSEIF _FILEEXISTS(FixDirectoryName(path.source$) + MidiSoundFont$) THEN MidiSoundFont$ = FixDirectoryName(path.source$) + MidiSoundFont$ ELSEIF _FILEEXISTS(FixDirectoryName(idepath$) + MidiSoundFont$) THEN MidiSoundFont$ = FixDirectoryName(idepath$) + MidiSoundFont$ @@ -4557,6 +4557,7 @@ DO sfheader = 1 GOTO GotHeader END IF + ' a740g: Fallback to source path IF inclevel > 0 THEN libpath$ = getfilepath(incname(inclevel)) + og_libpath$ @@ -16774,6 +16775,27 @@ FUNCTION evaluatefunc$ (a2$, args AS LONG, typ AS LONG) END IF END IF + ' a740g: UCHARPOS special case for arg 2 + IF n$ = "_UCHARPOS" OR (n$ = "UCHARPOS" AND qb64prefix_set = 1) THEN + IF curarg = 2 THEN + ' It must be an array + IF (sourcetyp AND ISREFERENCE) = 0 OR (sourcetyp AND ISARRAY) = 0 THEN + Give_Error "Expected INTEGER array-name" + EXIT FUNCTION + END IF + + ' Cannot be one of these + IF (sourcetyp AND ISSTRING) OR (sourcetyp AND ISFLOAT) OR (sourcetyp AND ISOFFSET) OR (sourcetyp AND ISUDT) OR (sourcetyp AND 511) <> 16 THEN + Give_Error "Expected INTEGER array-name" + EXIT FUNCTION + END IF + + e$ = evaluatetotyp(e2$, -2) ' get byte_element_struct + + GOTO dontevaluate + END IF + END IF + '***special case*** IF n$ = "_MEM" OR (n$ = "MEM" AND qb64prefix_set = 1) THEN IF curarg = 1 THEN diff --git a/source/subs_functions/subs_functions.bas b/source/subs_functions/subs_functions.bas index 5c2c5863f..5a212c077 100644 --- a/source/subs_functions/subs_functions.bas +++ b/source/subs_functions/subs_functions.bas @@ -1464,7 +1464,6 @@ id.ret = LONGTYPE - ISPOINTER id.hr_syntax = "_ULINESPACING&[(fontHandle&)]" regid -regid clearid id.n = qb64prefix$ + "UPrintString" id.Dependency = DEPENDENCY_LOADFONT @@ -1476,6 +1475,18 @@ id.specialformat = "(?,?),?[,[?][,[?][,?]]]" id.hr_syntax = "_UPRINTSTRING (x&, y&), text$[, maxWidth&][, utfEncoding&][, fontHandle&]" regid +clearid +id.n = qb64prefix$ + "UCharPos" +id.Dependency = DEPENDENCY_LOADFONT +id.subfunc = 1 +id.callname = "func__UCharPos" +id.args = 4 +id.arg = MKL$(STRINGTYPE - ISPOINTER) + MKL$(-1) + MKL$(LONGTYPE - ISPOINTER) + MKL$(LONGTYPE - ISPOINTER) +id.specialformat = "?[,[?][,[?][,?]]]" +id.ret = LONGTYPE - ISPOINTER +id.hr_syntax = "_UCHARPOS&(text$[, posArray%()][, utfEncoding&][, fontHandle&])" +regid + 'WORKING WITH COLORS clearid diff --git a/source/subs_functions/syntax_highlighter_list.bas b/source/subs_functions/syntax_highlighter_list.bas index c819d856d..72a26f2df 100644 --- a/source/subs_functions/syntax_highlighter_list.bas +++ b/source/subs_functions/syntax_highlighter_list.bas @@ -6,5 +6,5 @@ listOfKeywords$ = listOfKeywords$ + "_GLPOPATTRIB@_GLPOPCLIENTATTRIB@_GLPOPMATRI listOfKeywords$ = listOfKeywords$ + "_SOFTWARE@_SQUAREPIXELS@_STRETCH@_ALLOWFULLSCREEN@_ALL@_ECHO@_INSTRREV@_TRIM$@_ACCEPTFILEDROP@_FINISHDROP@_TOTALDROPPEDFILES@_DROPPEDFILE@_DROPPEDFILE$@_SHR@_SHL@_ROR@_ROL@" ' a740g: added ROR & ROL listOfKeywords$ = listOfKeywords$ + "_DEFLATE$@_INFLATE$@_READBIT@_RESETBIT@_SETBIT@_TOGGLEBIT@$ASSERTS@_ASSERT@_CAPSLOCK@_NUMLOCK@_SCROLLLOCK@_TOGGLE@_CONSOLEFONT@_CONSOLECURSOR@_CONSOLEINPUT@_CINP@$NOPREFIX@$COLOR@$DEBUG@_ENVIRONCOUNT@$UNSTABLE@$MIDISOUNDFONT@" listOfKeywords$ = listOfKeywords$ + "_NOTIFYPOPUP@_MESSAGEBOX@_INPUTBOX$@_SELECTFOLDERDIALOG$@_COLORCHOOSERDIALOG@_OPENFILEDIALOG$@_SAVEFILEDIALOG$@" ' a740g: added common dialog keywords -listOfKeywords$ = listOfKeywords$ + "_STATUSCODE@_SNDNEW@_SCALEDWIDTH@_SCALEDHEIGHT@_UFONTHEIGHT@_UPRINTWIDTH@_ULINESPACING@_UPRINTSTRING@" +listOfKeywords$ = listOfKeywords$ + "_STATUSCODE@_SNDNEW@_SCALEDWIDTH@_SCALEDHEIGHT@_UFONTHEIGHT@_UPRINTWIDTH@_ULINESPACING@_UPRINTSTRING@_UCHARPOS@"