diff --git a/internal/c/libqb.cpp b/internal/c/libqb.cpp index c56fd4630..efad20c11 100644 --- a/internal/c/libqb.cpp +++ b/internal/c/libqb.cpp @@ -11413,7 +11413,7 @@ int32_t chrwidth(uint32_t character) { // Custom font // a740g: No need to render just to find the pixel length if ((fontflags[f] & FONT_LOAD_UNICODE)) { // UNICODE character - w = FontPrintWidthUTF32(font[f], (uint32_t *)&character, 1); + w = FontPrintWidthUTF32(font[f], (char32_t *)&character, 1); } else { // ASCII character character &= 255; w = FontPrintWidthASCII(font[f], (uint8_t *)&character, 1); @@ -30354,7 +30354,7 @@ void display() { render_option = 1; if (rt_data_last) free(rt_data_last); - ok = FontRenderTextUTF32(font[f], &chr_utf32, 1, render_option, &rt_data, &rt_w, &rt_h); + ok = FontRenderTextUTF32(font[f], (char32_t *)&chr_utf32, 1, render_option, &rt_data, &rt_w, &rt_h); rt_data_last = rt_data; cp2 = rt_data; f_pitch = 0; diff --git a/internal/c/libqb/include/font.h b/internal/c/libqb/include/font.h index cb6a8fb39..a98c06d61 100644 --- a/internal/c/libqb/include/font.h +++ b/internal/c/libqb/include/font.h @@ -1,6 +1,6 @@ //---------------------------------------------------------------------------------------------------------------------- // QB64-PE Font Library -// Powered by FreeType 2.4.12 (https://github.com/vinniefalco/FreeTypeAmalgam) +// Powered by FreeType (https://freetype.org/) //---------------------------------------------------------------------------------------------------------------------- #pragma once @@ -45,14 +45,15 @@ 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); -bool FontRenderTextUTF32(int32_t fh, const uint32_t *codepoint, int32_t codepoints, int32_t options, uint8_t **out_data, int32_t *out_x, int32_t *out_y); +bool FontRenderTextUTF32(int32_t fh, const char32_t *codepoint, int32_t codepoints, int32_t options, uint8_t **out_data, int32_t *out_x, int32_t *out_y); bool FontRenderTextASCII(int32_t fh, const uint8_t *codepoint, int32_t codepoints, int32_t options, uint8_t **out_data, int32_t *out_x, int32_t *out_y); -int32_t FontPrintWidthUTF32(int32_t fh, const uint32_t *codepoint, int32_t codepoints); +int32_t FontPrintWidthUTF32(int32_t fh, const char32_t *codepoint, int32_t codepoints); int32_t FontPrintWidthASCII(int32_t fh, const uint8_t *codepoint, int32_t codepoints); qbs *func__md5(qbs *text); 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); +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 dst_img, + 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 3949e9ddb..a9f7afd13 100644 --- a/internal/c/parts/video/font/font.cpp +++ b/internal/c/parts/video/font/font.cpp @@ -1,6 +1,6 @@ //---------------------------------------------------------------------------------------------------------------------- // QB64-PE Font Library -// Powered by FreeType 2.4.12 (https://github.com/vinniefalco/FreeTypeAmalgam) +// Powered by FreeType (https://freetype.org/) //---------------------------------------------------------------------------------------------------------------------- #define FONT_DEBUG 0 @@ -12,12 +12,15 @@ #include "libqb-common.h" #include "mutex.h" #include "rounding.h" +#include #include #include #include FT_FREETYPE_H extern "C" { #include "freetype/md5.h" } +#include +#include #include #include @@ -40,146 +43,82 @@ void pset_and_clip(int32_t x, int32_t y, uint32_t col); /// @brief A simple class that manages conversions from various encodings to UTF32 class UTF32 { - private: - // See DecodeUTF8() below for more details - enum UTF8DecoderState { ACCEPT = 0, REJECT = 1 }; - - /// @brief See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. - /// Copyright (c) 2008-2009 Bjoern Hoehrmann - /// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation - /// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, - /// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software - /// is furnished to do so, subject to the following conditions: - /// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - /// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - /// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - /// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR - /// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - /// @param state The current state of the decoder - /// @param codep The decoded codepoint after state changes to UTF8DecodeState::ACCEPT - /// @param byte The next UTF-8 byte in the input stream - /// @return UTF8DecodeState::ACCEPT if enough bytes have been read for a character, - /// UTF8DecodeState::REJECT if the byte is not allowed to occur at its position, - /// and some other positive value if more bytes have to be read - uint32_t DecodeUTF8(uint32_t *state, uint32_t *codep, uint8_t byte) { - // clang-format off - static const uint8_t utf8d[] = { - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf - 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df - 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef - 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff - 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0 - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2 - 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4 - 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6 - 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8 - }; - // clang-format on - - uint32_t type = utf8d[byte]; - *codep = (*state != UTF8DecoderState::ACCEPT) ? (byte & 0x3fu) | (*codep << 6) : (0xff >> type) & (byte); - *state = utf8d[256 + *state * 16 + type]; - - return *state; - } + static const uint32_t MAX_UNICODE_CODEPOINT = 0x10FFFFu; public: + std::u32string string; // UTF32 string + UTF32 &operator=(const UTF32 &) = delete; UTF32 &operator=(UTF32 &&) = delete; - std::vector codepoints; // UTF32 codepoint dynamic array - - /// @brief Converts an ASCII array to UTF-32 - /// @param str The ASCII array - /// @param byte_len The size of the array in bytes + /// @brief Converts an ASCII string to UTF-32 + /// @param str The ASCII string + /// @param len The size of the string in bytes /// @return The number of codepoints that were converted - size_t ConvertASCII(const uint8_t *str, size_t byte_len) { - // Clear the codepoint vector - codepoints.clear(); + size_t ConvertASCII(const uint8_t *str, size_t len) { + string.resize(len); - // Convert the ASCII string - for (size_t i = 0; i < byte_len; i++) - codepoints.push_back(codepage437_to_unicode16[str[i]]); + for (size_t i = 0; i < len; i++) + string[i] = codepage437_to_unicode16[str[i]]; - return codepoints.size(); + return string.size(); } - /// @brief Converts an UTF-8 array to UTF-32. This does not check for BOM - /// @param str The UTF-8 array - /// @param byte_len The size of the array in bytes + /// @brief Converts an UTF-8 string to UTF-32 + /// @param str The UTF-8 string + /// @param len The size of the string in bytes /// @return The number of codepoints that were converted - size_t ConvertUTF8(const uint8_t *str, size_t byte_len) { - // Clear the codepoint vector - codepoints.clear(); - - uint32_t prevState = UTF8DecoderState::ACCEPT, currentState = UTF8DecoderState::ACCEPT; - uint32_t cp; - - for (size_t i = 0; i < byte_len; i++, prevState = currentState) { - switch (DecodeUTF8(¤tState, &cp, str[i])) { - case UTF8DecoderState::ACCEPT: - // Good codepoint - codepoints.push_back(cp); - break; - - case UTF8DecoderState::REJECT: - // Codepoint would be U+FFFD (replacement character) - cp = 0xFFFD; - currentState = UTF8DecoderState::ACCEPT; - if (prevState != UTF8DecoderState::ACCEPT) - --i; - codepoints.push_back(cp); - break; - - default: - // Need to read continuation bytes - continue; - } + size_t ConvertUTF8(const uint8_t *str, size_t len) { + try { + string = std::wstring_convert, char32_t>().from_bytes((const char *)str, (const char *)str + len); + } catch (...) { + string.clear(); } - return codepoints.size(); + return string.size(); } - /// @brief Converts an UTF-16LE array to UTF-32. This does not check for BOM - /// @param str The UTF-16LE array - /// @param byte_len The size of the array in bytes + /// @brief Converts an UTF-16 LE/BE string (with BOM or naked) to UTF-32 + /// @param str The UTF-16 string. If no BOM is present, little-endian is assumed + /// @param len The size of the string in bytes /// @return The number of codepoints that were converted - size_t ConvertUTF16(const uint8_t *str, size_t byte_len) { - // Clear the codepoint vector - codepoints.clear(); - - // We'll assume the worst case scenario and allocate a buffer that is byte_len / 2 codepoints long - auto len16 = byte_len / sizeof(uint16_t); - auto str16 = (const uint16_t *)str; - uint32_t cp; - - for (size_t i = 0; i < len16; i++) { - auto ch = str16[i]; - - // If the character is a surrogate, we need to combine it with the next character to get the actual codepoint - if (ch >= 0xD800 && ch <= 0xDBFF && i + 1 < len16) { - auto ch2 = str16[i + 1]; - if (ch2 >= 0xDC00 && ch2 <= 0xDFFF) { - cp = ((ch - 0xD800) << 10) + (ch2 - 0xDC00) + 0x10000; - ++i; // skip the second surrogate + size_t ConvertUTF16(const uint8_t *str, size_t len) { + try { + if (len > 2) { + // Detect BOM + if (str[0] == 0xFF && str[1] == 0xFE) { + // Little-endian + string = std::wstring_convert< + std::codecvt_utf16(std::codecvt_mode::consume_header | std::codecvt_mode::little_endian)>, + char32_t>() + .from_bytes((const char *)str, (const char *)str + len); + } else if (str[0] == 0xFE && str[1] == 0xFF) { + // Big-endian (C++ default) + string = + std::wstring_convert, char32_t>().from_bytes( + (const char *)str, (const char *)str + len); } else { - cp = 0xFFFD; // invalid surrogate pair + // No BOM, assuming little-endian by default + string = std::wstring_convert< + std::codecvt_utf16(std::codecvt_mode::consume_header | std::codecvt_mode::little_endian)>, + char32_t>() + .from_bytes((const char *)str, (const char *)str + len); } - } else if (ch >= 0xDC00 && ch <= 0xDFFF) { - cp = 0xFFFD; // invalid surrogate pair } else { - cp = ch; + // Short string, assuming little-endian by default + string = std::wstring_convert< + std::codecvt_utf16(std::codecvt_mode::consume_header | std::codecvt_mode::little_endian)>, + char32_t>() + .from_bytes((const char *)str, (const char *)str + len); } - - codepoints.push_back(cp); + } catch (...) { + string.clear(); } - return codepoints.size(); + return string.size(); } }; @@ -317,7 +256,7 @@ struct FontManager { /// @param codepoint A valid UTF-32 codepoint /// @param parentFont The parent font object /// @return True if successful or if bitmap is already cached - bool CacheBitmap(uint32_t codepoint, Font *parentFont) { + bool CacheBitmap(char32_t codepoint, Font *parentFont) { if (!bitmap) { // Get the glyph index first and store it // Note that this can return a valid glyph index but the index need not have any glyph bitmap @@ -396,7 +335,7 @@ struct FontManager { } }; - std::unordered_map glyphs; // holds pointers to cached glyph data for codepoints + std::unordered_map glyphs; // holds pointers to cached glyph data for codepoints // Delete copy and move constructors and assignments Font(const Font &) = delete; @@ -436,7 +375,7 @@ struct FontManager { /// @param codepoint A valid UTF-32 codepoint /// @param isMono True for mono bitmap and false for gray /// @return The glyph pointer if successful or if the glyph is already in the map, nullptr otherwise - Glyph *GetGlyph(uint32_t codepoint, bool isMono) { + Glyph *GetGlyph(char32_t codepoint, bool isMono) { if (glyphs.count(codepoint) == 0) { // The glyph is not cached yet auto newGlyph = new Glyph; @@ -471,7 +410,7 @@ struct FontManager { /// @param codepoint The codepoint array (string) /// @param codepoints The number of codepoints in the array /// @return The length of the string in pixels - FT_Pos GetStringPixelWidth(const uint32_t *codepoint, size_t codepoints) { + FT_Pos GetStringPixelWidth(const char32_t *codepoint, size_t codepoints) { if (monospaceWidth) // return monospace width simply by multiplying the fixed width by the codepoints return monospaceWidth * codepoints; @@ -896,7 +835,7 @@ int32_t FontWidth(int32_t fh) { /// @param codepoint The UTF32 codepoint array /// @param codepoints The number of codepoints /// @return Length in pixels -int32_t FontPrintWidthUTF32(int32_t fh, const uint32_t *codepoint, int32_t codepoints) { +int32_t FontPrintWidthUTF32(int32_t fh, const char32_t *codepoint, int32_t codepoints) { libqb_mutex_guard lock(fontManager.m); if (codepoints > 0) { @@ -922,7 +861,7 @@ int32_t FontPrintWidthASCII(int32_t fh, const uint8_t *codepoint, int32_t codepo // Attempt to convert the string to UTF32 and get the actual width in pixels auto count = utf32.ConvertASCII(codepoint, codepoints); - return FontPrintWidthUTF32(fh, utf32.codepoints.data(), count); + return FontPrintWidthUTF32(fh, utf32.string.data(), count); } return 0; @@ -937,7 +876,7 @@ int32_t FontPrintWidthASCII(int32_t fh, const uint8_t *codepoint, int32_t codepo /// @param out_x A pointer to the output width of the rendered text in pixels /// @param out_y A pointer to the output height of the rendered text in pixels /// @return success = 1, failure = 0 -bool FontRenderTextUTF32(int32_t fh, const uint32_t *codepoint, int32_t codepoints, int32_t options, uint8_t **out_data, int32_t *out_x, int32_t *out_y) { +bool FontRenderTextUTF32(int32_t fh, const char32_t *codepoint, int32_t codepoints, int32_t options, uint8_t **out_data, int32_t *out_x, int32_t *out_y) { libqb_mutex_guard lock(fontManager.m); FONT_DEBUG_CHECK(IS_VALID_FONT_HANDLE(fh)); @@ -972,7 +911,7 @@ bool FontRenderTextUTF32(int32_t fh, const uint32_t *codepoint, int32_t codepoin auto glyph = fnt->GetGlyph(cp, isMonochrome); if (glyph) { glyph->RenderBitmap(outBuf, strPixSize.x, strPixSize.y, - penX + glyph->bitmap->bearing.x + fnt->monospaceWidth / 2 - glyph->bitmap->advanceWidth / 2, + penX + glyph->bitmap->bearing.x + (fnt->monospaceWidth >> 1) - (glyph->bitmap->advanceWidth >> 1), fnt->baseline - glyph->bitmap->bearing.y); penX += fnt->monospaceWidth; } @@ -1025,7 +964,7 @@ bool FontRenderTextASCII(int32_t fh, const uint8_t *codepoint, int32_t codepoint // Attempt to convert the string to UTF32 and forward to FontRenderTextUTF32() auto count = utf32.ConvertASCII(codepoint, codepoints); - return FontRenderTextUTF32(fh, utf32.codepoints.data(), count, options, out_data, out_x, out_y); + return FontRenderTextUTF32(fh, utf32.string.data(), count, options, out_data, out_x, out_y); } return false; @@ -1082,7 +1021,7 @@ int32_t func__UFontHeight(int32_t qb64_fh, int32_t passed) { auto face = fnt->face; if (FT_IS_SCALABLE(face)) - return (((FT_Pos)face->ascender - (FT_Pos)face->descender) * fnt->defaultHeight) / (FT_Pos)face->units_per_EM; + return FT_MulDiv(((FT_Long)face->ascender - (FT_Long)face->descender), (FT_Long)fnt->defaultHeight, (FT_Long)face->units_per_EM); return fnt->defaultHeight; } @@ -1120,31 +1059,31 @@ int32_t func__UPrintWidth(const qbs *text, int32_t utf_encoding, int32_t qb64_fh } // Convert the string to UTF-32 if needed - uint32_t const *str32 = nullptr; + char32_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); + str32 = (char32_t *)text->chr; + codepoints = text->len / sizeof(char32_t); break; case 16: // UTF-16: conversion required codepoints = utf32.ConvertUTF16(text->chr, text->len); if (codepoints) - str32 = utf32.codepoints.data(); + str32 = utf32.string.data(); break; case 8: // UTF-8: conversion required codepoints = utf32.ConvertUTF8(text->chr, text->len); if (codepoints) - str32 = utf32.codepoints.data(); + str32 = utf32.string.data(); break; default: // ASCII: conversion required codepoints = utf32.ConvertASCII(text->chr, text->len); if (codepoints) - str32 = utf32.codepoints.data(); + str32 = utf32.string.data(); } if (qb64_fh < 32) @@ -1184,7 +1123,7 @@ int32_t func__ULineSpacing(int32_t qb64_fh, int32_t passed) { auto face = fnt->face; if (FT_IS_SCALABLE(face)) - return ((FT_Pos)face->height * fnt->defaultHeight) / (FT_Pos)face->units_per_EM; + return FT_MulDiv((FT_Long)face->height, (FT_Long)fnt->defaultHeight, (FT_Long)face->units_per_EM); return fnt->defaultHeight; } @@ -1193,28 +1132,23 @@ int32_t func__ULineSpacing(int32_t qb64_fh, int32_t passed) { /// @param start_x The starting x position /// @param start_y The starting y position /// @param text The text that needs to be rendered -/// @param max_width The maximum width of the text (rendering will be clipped beyond width) -/// @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 max_width [optional] The maximum width of the text (rendering will be clipped beyond width) +/// @param utf_encoding [optional] The UTF encoding of the text (0 = ASCII, 8 = UTF-8, 16 - UTF-16, 32 = UTF-32) +/// @param qb64_fh [optional] A QB64 font handle (this can be a builtin font as well) +/// @param dst_img [optional] A QB64 image handle (zero designates current SCREEN page) /// @param passed Optional arguments flag -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) { +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 dst_img, + int32_t passed) { libqb_mutex_guard lock(fontManager.m); if (is_error_pending() || !text->len) return; - // Check if we are in text mode and generate an error if we are - if (write_page->text) { - error(QB_ERROR_ILLEGAL_FUNCTION_CALL); - return; - } - - FONT_DEBUG_PRINT("Graphics mode set. Proceeding..."); - // Check max width if (passed & 1) { - if (max_width < 1) + if (max_width < 1) { return; + } } else { max_width = 0; } @@ -1229,10 +1163,29 @@ void sub__UPrintString(int32_t start_x, int32_t start_y, const qbs *text, int32_ utf_encoding = 0; } + int32_t old_dst_img; + + if (passed & 8) { + old_dst_img = func__dest(); + sub__dest(dst_img); + } + + // Check if we are in text mode and generate an error if we are + if (write_page->text) { + error(QB_ERROR_ILLEGAL_FUNCTION_CALL); + if (passed & 8) + sub__dest(old_dst_img); + return; + } + + FONT_DEBUG_PRINT("Graphics mode set. Proceeding..."); + // Check if a valid font handle was passed if (passed & 4) { if (!IS_VALID_QB64_FONT_HANDLE(qb64_fh)) { error(QB_ERROR_INVALID_HANDLE); + if (passed & 8) + sub__dest(old_dst_img); return; } } else { @@ -1240,39 +1193,42 @@ void sub__UPrintString(int32_t start_x, int32_t start_y, const qbs *text, int32_ } // Convert the string to UTF-32 if needed - uint32_t const *str32 = nullptr; + char32_t const *str32 = nullptr; size_t codepoints = 0; switch (utf_encoding) { case 32: // UTF-32: no conversion needed FONT_DEBUG_PRINT("UTF-32 string. Skipping conversion"); - str32 = (uint32_t *)text->chr; - codepoints = text->len / sizeof(uint32_t); + str32 = (char32_t *)text->chr; + codepoints = text->len / sizeof(char32_t); break; case 16: // UTF-16: conversion required FONT_DEBUG_PRINT("UTF-16 string. Converting to UTF32"); codepoints = utf32.ConvertUTF16(text->chr, text->len); if (codepoints) - str32 = utf32.codepoints.data(); + str32 = utf32.string.data(); break; case 8: // UTF-8: conversion required FONT_DEBUG_PRINT("UTF-8 string. Converting to UTF32"); codepoints = utf32.ConvertUTF8(text->chr, text->len); if (codepoints) - str32 = utf32.codepoints.data(); + str32 = utf32.string.data(); break; default: // ASCII: conversion required FONT_DEBUG_PRINT("ASCII string. Converting to UTF32"); codepoints = utf32.ConvertASCII(text->chr, text->len); if (codepoints) - str32 = utf32.codepoints.data(); + str32 = utf32.string.data(); } - if (!codepoints) + if (!codepoints) { + if (passed & 8) + sub__dest(old_dst_img); return; + } FontManager::Font *fnt = nullptr; FT_Face face = nullptr; @@ -1290,7 +1246,7 @@ void sub__UPrintString(int32_t start_x, int32_t start_y, const qbs *text, int32_ strPixSize.x = fnt->GetStringPixelWidth(str32, codepoints); pen.x = 0; if (FT_IS_SCALABLE(face)) { - strPixSize.y = (((FT_Pos)face->ascender - (FT_Pos)face->descender) * fnt->defaultHeight) / (FT_Pos)face->units_per_EM; + strPixSize.y = FT_MulDiv(((FT_Long)face->ascender - (FT_Long)face->descender), (FT_Long)fnt->defaultHeight, (FT_Long)face->units_per_EM); pen.y = ((FT_Pos)face->ascender * fnt->defaultHeight) / (FT_Pos)face->units_per_EM; } else { strPixSize.y = fnt->defaultHeight; @@ -1305,8 +1261,11 @@ void sub__UPrintString(int32_t start_x, int32_t start_y, const qbs *text, int32_ strPixSize.x = max_width; auto drawBuf = (uint8_t *)calloc(strPixSize.x, strPixSize.y); - if (!drawBuf) + if (!drawBuf) { + if (passed & 8) + sub__dest(old_dst_img); return; + } FONT_DEBUG_PRINT("Allocated (%lu x %lu) buffer", strPixSize.x, strPixSize.y); @@ -1364,7 +1323,7 @@ void sub__UPrintString(int32_t start_x, int32_t start_y, const qbs *text, int32_ break; glyph->RenderBitmap(drawBuf, strPixSize.x, strPixSize.y, - pen.x + glyph->bitmap->bearing.x + fnt->monospaceWidth / 2 - glyph->bitmap->advanceWidth / 2, + pen.x + glyph->bitmap->bearing.x + (fnt->monospaceWidth >> 1) - (glyph->bitmap->advanceWidth >> 1), pen.y - glyph->bitmap->bearing.y); pen.x += fnt->monospaceWidth; } @@ -1506,6 +1465,9 @@ void sub__UPrintString(int32_t start_x, int32_t start_y, const qbs *text, int32_ } free(drawBuf); + + if (passed & 8) + sub__dest(old_dst_img); } /// @brief Calculate the starting pixel positions of each codepoint to an array. First one being zero. @@ -1550,31 +1512,31 @@ int32_t func__UCharPos(const qbs *text, void *arr, int32_t utf_encoding, int32_t } // Convert the string to UTF-32 if needed - uint32_t const *str32 = nullptr; + char32_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); + str32 = (char32_t *)text->chr; + codepoints = text->len / sizeof(char32_t); break; case 16: // UTF-16: conversion required codepoints = utf32.ConvertUTF16(text->chr, text->len); if (codepoints) - str32 = utf32.codepoints.data(); + str32 = utf32.string.data(); break; case 8: // UTF-8: conversion required codepoints = utf32.ConvertUTF8(text->chr, text->len); if (codepoints) - str32 = utf32.codepoints.data(); + str32 = utf32.string.data(); break; default: // ASCII: conversion required codepoints = utf32.ConvertASCII(text->chr, text->len); if (codepoints) - str32 = utf32.codepoints.data(); + str32 = utf32.string.data(); } // Simply return the codepoint count if we do not have any array diff --git a/internal/c/parts/video/font/stub_font.cpp b/internal/c/parts/video/font/stub_font.cpp index 3ad891d02..e3aa32414 100644 --- a/internal/c/parts/video/font/stub_font.cpp +++ b/internal/c/parts/video/font/stub_font.cpp @@ -24,7 +24,7 @@ int32_t FontWidth(int32_t fh) { return 0; } -bool FontRenderTextUTF32(int32_t fh, const uint32_t *codepoint, int32_t codepoints, int32_t options, uint8_t **out_data, int32_t *out_x, int32_t *out_y) { +bool FontRenderTextUTF32(int32_t fh, const char32_t *codepoint, int32_t codepoints, int32_t options, uint8_t **out_data, int32_t *out_x, int32_t *out_y) { (void)fh; (void)codepoint; (void)codepoints; @@ -46,7 +46,7 @@ bool FontRenderTextASCII(int32_t fh, const uint8_t *codepoint, int32_t codepoint return 0; } -int32_t FontPrintWidthUTF32(int32_t fh, const uint32_t *codepoint, int32_t codepoints) { +int32_t FontPrintWidthUTF32(int32_t fh, const char32_t *codepoint, int32_t codepoints) { (void)fh; (void)codepoint; (void)codepoints; diff --git a/source/subs_functions/subs_functions.bas b/source/subs_functions/subs_functions.bas index a59da6608..3934fca94 100644 --- a/source/subs_functions/subs_functions.bas +++ b/source/subs_functions/subs_functions.bas @@ -1537,10 +1537,10 @@ id.n = qb64prefix$ + "UPrintString" id.Dependency = DEPENDENCY_LOADFONT id.subfunc = 2 id.callname = "sub__UPrintString" -id.args = 6 -id.arg = MKL$(LONGTYPE - ISPOINTER) + MKL$(LONGTYPE - ISPOINTER) + MKL$(STRINGTYPE - ISPOINTER) + MKL$(LONGTYPE - ISPOINTER) + MKL$(LONGTYPE - ISPOINTER) + MKL$(LONGTYPE - ISPOINTER) -id.specialformat = "(?,?),?[,[?][,[?][,?]]]" -id.hr_syntax = "_UPRINTSTRING (x&, y&), text$[, maxWidth&][, utfEncoding&][, fontHandle&]" +id.args = 7 +id.arg = MKL$(LONGTYPE - ISPOINTER) + MKL$(LONGTYPE - ISPOINTER) + MKL$(STRINGTYPE - ISPOINTER) + MKL$(LONGTYPE - ISPOINTER) + MKL$(LONGTYPE - ISPOINTER) + MKL$(LONGTYPE - ISPOINTER) + MKL$(LONGTYPE - ISPOINTER) +id.specialformat = "(?,?),?[,[?][,[?][,[?][,?]]]]" +id.hr_syntax = "_UPRINTSTRING (x&, y&), text$[, maxWidth&][, utfEncoding&][, fontHandle&][, imageHandle&]" regid clearid diff --git a/tests/compile_tests/filesystem/filesys_test.bas b/tests/compile_tests/filesystem/filesys_test.bas index 01ae33598..c184e2db8 100644 --- a/tests/compile_tests/filesystem/filesys_test.bas +++ b/tests/compile_tests/filesystem/filesys_test.bas @@ -79,7 +79,7 @@ SYSTEM 1 FUNCTION CreateDummyFile$ (directory AS STRING, extension AS STRING) DO - DIM fileName AS STRING: fileName = directory + LTRIM$(STR$(100! * (TIMER + RND))) + extension + DIM fileName AS STRING: fileName = directory + LTRIM$(STR$(INT(100! * (TIMER + RND)))) + extension LOOP WHILE _FILEEXISTS(fileName) DIM h AS LONG: h = FREEFILE diff --git a/tests/compile_tests/font/test.bas b/tests/compile_tests/font/test.bas index 4ab92a161..e26ea304c 100644 --- a/tests/compile_tests/font/test.bas +++ b/tests/compile_tests/font/test.bas @@ -97,8 +97,8 @@ SUB PrintFontDetails (handle AS LONG, testFileName AS STRING) _DEST image _FONT handle COLOR _RGB32(255) - _UPRINTSTRING (0, 0), SAMPLE_TEXT_1 - _UPRINTSTRING (0, uLineSpacing), SAMPLE_TEXT_2 + _UPRINTSTRING (0, 0), SAMPLE_TEXT_1, , , handle + _UPRINTSTRING (0, uLineSpacing), SAMPLE_TEXT_2, , , , image '_SAVEIMAGE testFileName + "u", image, TEST_IMAGE_FORMAT _FONT 16 diff --git a/tests/compile_tests/font/test.output b/tests/compile_tests/font/test.output index dc896f5bb..4c4515e6b 100644 --- a/tests/compile_tests/font/test.output +++ b/tests/compile_tests/font/test.output @@ -65,8 +65,8 @@ Success, images are identical! Loading font from memory (automono) ... done. _FONTWIDTH = 0 _FONTHEIGHT = 22 -_UFONTHEIGHT = 27 -_ULINESPACING = 27 +_UFONTHEIGHT = 28 +_ULINESPACING = 28 _PRINTWIDTH = 1088 _UPRINTWIDTH = 1088 _UCHARPOS = 95 @@ -78,7 +78,7 @@ Loading font LiberationSans-Regular.ttf () ... done. _FONTWIDTH = 0 _FONTHEIGHT = 12 _UFONTHEIGHT = 13 -_ULINESPACING = 13 +_ULINESPACING = 14 _PRINTWIDTH = 606 _UPRINTWIDTH = 606 _UCHARPOS = 95 @@ -89,7 +89,7 @@ Success, images are identical! Loading font LiberationSans-Regular.ttf (monospace) ... done. _FONTWIDTH = 15 _FONTHEIGHT = 16 -_UFONTHEIGHT = 17 +_UFONTHEIGHT = 18 _ULINESPACING = 18 _PRINTWIDTH = 1425 _UPRINTWIDTH = 1425 @@ -101,7 +101,7 @@ Success, images are identical! Loading font LiberationSans-Regular.ttf (automono) ... done. _FONTWIDTH = 0 _FONTHEIGHT = 22 -_UFONTHEIGHT = 24 +_UFONTHEIGHT = 25 _ULINESPACING = 25 _PRINTWIDTH = 1094 _UPRINTWIDTH = 1094 @@ -114,7 +114,7 @@ Loading font LiberationSerif-Regular.ttf () ... done. _FONTWIDTH = 0 _FONTHEIGHT = 12 _UFONTHEIGHT = 13 -_ULINESPACING = 13 +_ULINESPACING = 14 _PRINTWIDTH = 583 _UPRINTWIDTH = 583 _UCHARPOS = 95 @@ -125,7 +125,7 @@ Success, images are identical! Loading font LiberationSerif-Regular.ttf (monospace) ... done. _FONTWIDTH = 15 _FONTHEIGHT = 16 -_UFONTHEIGHT = 17 +_UFONTHEIGHT = 18 _ULINESPACING = 18 _PRINTWIDTH = 1425 _UPRINTWIDTH = 1425 @@ -149,8 +149,8 @@ Success, images are identical! Loading font LiberationMono-Regular.ttf () ... done. _FONTWIDTH = 0 _FONTHEIGHT = 12 -_UFONTHEIGHT = 13 -_ULINESPACING = 13 +_UFONTHEIGHT = 14 +_ULINESPACING = 14 _PRINTWIDTH = 665 _UPRINTWIDTH = 665 _UCHARPOS = 95 @@ -173,8 +173,8 @@ Success, images are identical! Loading font LiberationMono-Regular.ttf (automono) ... done. _FONTWIDTH = 13 _FONTHEIGHT = 22 -_UFONTHEIGHT = 24 -_ULINESPACING = 24 +_UFONTHEIGHT = 25 +_ULINESPACING = 25 _PRINTWIDTH = 1235 _UPRINTWIDTH = 1235 _UCHARPOS = 95 diff --git a/tests/compile_tests/font/test06u.bmp b/tests/compile_tests/font/test06u.bmp index acea7a8ef..7b0e2d20d 100644 Binary files a/tests/compile_tests/font/test06u.bmp and b/tests/compile_tests/font/test06u.bmp differ diff --git a/tests/compile_tests/font/test07u.bmp b/tests/compile_tests/font/test07u.bmp index 117f55966..3a431862b 100644 Binary files a/tests/compile_tests/font/test07u.bmp and b/tests/compile_tests/font/test07u.bmp differ diff --git a/tests/compile_tests/font/test08u.bmp b/tests/compile_tests/font/test08u.bmp index 5b27e58f9..635cbe1aa 100644 Binary files a/tests/compile_tests/font/test08u.bmp and b/tests/compile_tests/font/test08u.bmp differ diff --git a/tests/compile_tests/font/test09u.bmp b/tests/compile_tests/font/test09u.bmp index 435f6329d..fc9dceeb7 100644 Binary files a/tests/compile_tests/font/test09u.bmp and b/tests/compile_tests/font/test09u.bmp differ diff --git a/tests/compile_tests/font/test10u.bmp b/tests/compile_tests/font/test10u.bmp index 5be7d9f69..37a2544c8 100644 Binary files a/tests/compile_tests/font/test10u.bmp and b/tests/compile_tests/font/test10u.bmp differ diff --git a/tests/compile_tests/font/test11u.bmp b/tests/compile_tests/font/test11u.bmp index 5aa82207d..70e5d3196 100644 Binary files a/tests/compile_tests/font/test11u.bmp and b/tests/compile_tests/font/test11u.bmp differ diff --git a/tests/compile_tests/font/test13u.bmp b/tests/compile_tests/font/test13u.bmp index 1c17ec9f0..1ad6fa4f7 100644 Binary files a/tests/compile_tests/font/test13u.bmp and b/tests/compile_tests/font/test13u.bmp differ diff --git a/tests/compile_tests/font/test15u.bmp b/tests/compile_tests/font/test15u.bmp index 19a9133d6..eb70696b4 100644 Binary files a/tests/compile_tests/font/test15u.bmp and b/tests/compile_tests/font/test15u.bmp differ diff --git a/tests/compile_tests/utilities/imageassert.bm b/tests/compile_tests/utilities/imageassert.bm index 8d12e8604..17b7e50c3 100644 --- a/tests/compile_tests/utilities/imageassert.bm +++ b/tests/compile_tests/utilities/imageassert.bm @@ -148,6 +148,9 @@ SUB AssertImage2 (originalActualImage AS LONG, expectedFileName AS STRING) DIM actual AS _MEM, expected AS _MEM + actual = _MEMIMAGE(actualImage) + expected = _MEMIMAGE(expectedImage) + IF _WIDTH(actualImage) <> _WIDTH(expectedImage) THEN PRINT "Failure! Image width differs, actual:"; _WIDTH(actualImage); ", Expected:"; _WIDTH(expectedImage) GOTO freeImages @@ -158,9 +161,6 @@ SUB AssertImage2 (originalActualImage AS LONG, expectedFileName AS STRING) GOTO freeImages END IF - actual = _MEMIMAGE(actualImage) - expected = _MEMIMAGE(expectedImage) - IF actual.SIZE <> expected.SIZE THEN PRINT "Failure! Image sizes differ, Actual:"; actual.SIZE; ", Expected:"; expected.SIZE GOTO freeImages