diff --git a/.gitignore b/.gitignore index ee2a1375..2f983583 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ test *.wuhb *.zip +debug.py \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index e75fdf29..75fa48ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,7 @@ if(NINTENDO_3DS) ROMFS ${PROJECT_NAME}_ctr_romfs ) - set(APP_LIBS citro2d citro3d) + set(APP_LIBS citro3d) add_custom_target(dist COMMAND ${CMAKE_COMMAND} -E tar "cfv" "${PROJECT_NAME}-3ds-${COMMIT_HASH}.zip" --format=zip @@ -393,6 +393,8 @@ target_sources(${PROJECT_NAME} PRIVATE source/modules/window/wrap_window.cpp source/objects/beziercurve/beziercurve.cpp source/objects/beziercurve/wrap_beziercurve.cpp + source/objects/bmfontrasterizer/bmfontrasterizer.cpp + source/objects/imagerasterizer/imagerasterizer.cpp source/objects/channel/channel.cpp source/objects/channel/wrap_channel.cpp source/objects/compressedimagedata/compressedimagedata.cpp diff --git a/include/common/color.hpp b/include/common/color.hpp index 3eccf2e6..538ab4ec 100644 --- a/include/common/color.hpp +++ b/include/common/color.hpp @@ -159,3 +159,20 @@ struct Color return (uint8_t)(255.0f * std::clamp(in, 0.0f, 1.0f) + 0.5f); } }; + +struct Color32 +{ + public: + Color32() : r(0), g(0), b(0), a(0) + {} + + Color32(uint8_t r, uint8_t g, uint8_t b, uint8_t a) : r(r), g(g), b(b), a(a) + {} + + constexpr std::strong_ordering operator<=>(const Color32&) const noexcept = default; + + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +}; \ No newline at end of file diff --git a/include/modules/font/fontmodule.tcc b/include/modules/font/fontmodule.tcc index 09bf1b15..e7f5e210 100644 --- a/include/modules/font/fontmodule.tcc +++ b/include/modules/font/fontmodule.tcc @@ -8,6 +8,8 @@ #include +#include + #include #include @@ -34,6 +36,18 @@ namespace love return "love.font"; } + Rasterizer* NewBMFontRasterizer(FileData* data, + const std::vector*>& images, + float dpiScale) const; + + virtual Rasterizer* NewImageRasterizer(ImageData* data, + const std::string& text, int extraSpacing, + float dpiScale) const = 0; + + virtual Rasterizer* NewImageRasterizer(ImageData* data, uint32_t* glyphs, + int glyphCount, int extraSpacing, + float dpiScale) const = 0; + Rasterizer* NewTrueTypeRasterizer(int size, TrueTypeRasterizer<>::Hinting hinting) const; Rasterizer* NewTrueTypeRasterizer(int size, float dpiScale, diff --git a/include/modules/font/wrap_fontmodule.hpp b/include/modules/font/wrap_fontmodule.hpp index 3f558e98..f9876922 100644 --- a/include/modules/font/wrap_fontmodule.hpp +++ b/include/modules/font/wrap_fontmodule.hpp @@ -10,6 +10,10 @@ namespace Wrap_FontModule int NewTrueTypeRasterizer(lua_State* L); + int NewBMFontRasterizer(lua_State* L); + + int NewImageRasterizer(lua_State* L); + int Register(lua_State* L); extern std::span extensions; diff --git a/include/objects/bmfontrasterizer/bmfontrasterizer.hpp b/include/objects/bmfontrasterizer/bmfontrasterizer.hpp new file mode 100644 index 00000000..128c0829 --- /dev/null +++ b/include/objects/bmfontrasterizer/bmfontrasterizer.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include + +#include + +#include +#include + +namespace love +{ + class BMFontRasterizer : public Rasterizer + { + public: + BMFontRasterizer(FileData* fontDefinition, + const std::vector*>& imageList, float dpiScale); + + virtual ~BMFontRasterizer() + {} + + int GetLineHeight() const override; + + int GetGlyphSpacing(uint32_t glyph) const override; + + int GetGlyphIndex(uint32_t glyph) const override; + + GlyphData* GetGlyphDataForIndex(int index) const override; + + int GetGlyphCount() const override; + + bool HasGlyph(uint32_t glyph) const override; + + float GetKerning(uint32_t left, uint32_t right) const override; + + DataType GetDataType() const override; + + TextShaper* NewTextShaper() override; + + static bool Accepts(FileData* fontDefinition); + + private: + struct BMFontCharacter + { + int x; + int y; + int page; + GlyphData::GlyphMetrics metrics; + uint32_t glyph; + }; + + void ParseConfig(const std::string& config); + + std::string fontFolder; + std::unordered_map>> images; + + std::vector characters; + + std::unordered_map characterIndices; + std::unordered_map kernings; + + int fontSize; + bool unicode; + int lineHeight; + }; +} // namespace love \ No newline at end of file diff --git a/include/objects/imagedata/imagedata.tcc b/include/objects/imagedata/imagedata.tcc index f4dc1252..dce31cfc 100644 --- a/include/objects/imagedata/imagedata.tcc +++ b/include/objects/imagedata/imagedata.tcc @@ -437,7 +437,7 @@ namespace love } } - love::mutex& GetMutex() + love::mutex& GetMutex() const { return this->mutex; } diff --git a/include/objects/imagerasterizer/imagerasterizer.hpp b/include/objects/imagerasterizer/imagerasterizer.hpp new file mode 100644 index 00000000..a6abf338 --- /dev/null +++ b/include/objects/imagerasterizer/imagerasterizer.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include + +#include +#include + +#include + +namespace love +{ + class ImageRasterizer : public Rasterizer + { + public: + ImageRasterizer(ImageData* data, const uint32_t* glyphs, int glyphCount, + int extraSpacing, float dpiScale); + + virtual ~ImageRasterizer() + {} + + int GetLineHeight() const override; + + int GetGlyphSpacing(uint32_t glyph) const override; + + int GetGlyphIndex(uint32_t glyph) const override; + + GlyphData* GetGlyphDataForIndex(int index) const override; + + int GetGlyphCount() const override; + + bool HasGlyph(uint32_t glyph) const override; + + DataType GetDataType() const override; + + TextShaper* NewTextShaper() override; + + private: + struct ImageGlyphData + { + int x; + int width; + uint32_t glyph; + }; + + void Load(const uint32_t* glyphs, int glyphCount); + + StrongReference> imageData; + + int glyphCount; + int extraSpacing; + + std::vector imageGlyphs; + std::map glyphIndicies; + + Color32 spacer; + }; +} // namespace love \ No newline at end of file diff --git a/platform/cafe/include/modules/fontmodule_ext.hpp b/platform/cafe/include/modules/fontmodule_ext.hpp index f27abc5f..ec93ce6e 100644 --- a/platform/cafe/include/modules/fontmodule_ext.hpp +++ b/platform/cafe/include/modules/fontmodule_ext.hpp @@ -55,8 +55,12 @@ namespace love using FontModule::NewTrueTypeRasterizer; - Rasterizer* NewTrueTypeRasterizer(int size, TrueTypeRasterizer<>::Hinting hinting, - OSSharedDataType type) const; + Rasterizer* NewImageRasterizer(ImageData* data, const std::string& text, + int extraSpacing, float dpiScale) const override; + + Rasterizer* NewImageRasterizer(ImageData* data, uint32_t* glyphs, + int glyphCount, int extraSpacing, + float dpiScale) const override; Rasterizer* NewTrueTypeRasterizer(Data* data, int size, TrueTypeRasterizer<>::Hinting hinting) const override; diff --git a/platform/cafe/source/modules/fontmodule_ext.cpp b/platform/cafe/source/modules/fontmodule_ext.cpp index 826c9f92..42c84c93 100644 --- a/platform/cafe/source/modules/fontmodule_ext.cpp +++ b/platform/cafe/source/modules/fontmodule_ext.cpp @@ -2,6 +2,9 @@ #include +#include +#include + using namespace love; FontModule::FontModule() : FontModule() @@ -9,23 +12,47 @@ FontModule::FontModule() : FontModule() this->defaultFontData.Set(new SystemFont(), Acquire::NORETAIN); } +Rasterizer* FontModule::NewImageRasterizer(ImageData* data, + const std::string& text, int extraSpacing, + float dpiScale) const +{ + std::vector glyphs {}; + glyphs.reserve(text.size()); + + try + { + utf8::iterator it(text.begin(), text.begin(), text.end()); + utf8::iterator end(text.end(), text.begin(), text.end()); + + while (it != end) + glyphs.push_back(*it++); + } + catch (utf8::exception& e) + { + throw love::Exception("UTF-8 decoding error: %s", e.what()); + } + + return this->NewImageRasterizer(data, &glyphs[0], (int)glyphs.size(), extraSpacing, dpiScale); +} + +Rasterizer* FontModule::NewImageRasterizer(ImageData* data, + uint32_t* glyphs, int glyphCount, + int extraSpacing, float dpiScale) const +{ + return new ImageRasterizer(data, glyphs, glyphCount, extraSpacing, dpiScale); +} + Rasterizer* FontModule::NewRasterizer(FileData* data) const { if (TrueTypeRasterizer::Accepts(this->library, data)) return this->NewTrueTypeRasterizer(data, 12, TrueTypeRasterizer<>::HINTING_NORMAL); + else if (BMFontRasterizer::Accepts(data)) + return this->NewBMFontRasterizer(data, {}, 1.0f); throw love::Exception("Invalid font file: %s", data->GetFilename().c_str()); return nullptr; } -Rasterizer* FontModule::NewTrueTypeRasterizer(int size, - TrueTypeRasterizer<>::Hinting hinting, - OSSharedDataType type) const -{ - StrongReference data(new SystemFont(type), Acquire::NORETAIN); - return this->NewTrueTypeRasterizer(data.Get(), size, hinting); -} - Rasterizer* FontModule::NewTrueTypeRasterizer( Data* data, int size, TrueTypeRasterizer<>::Hinting hinting) const { diff --git a/platform/cafe/source/modules/wrap_fontmodule_ext.cpp b/platform/cafe/source/modules/wrap_fontmodule_ext.cpp deleted file mode 100644 index 9a487587..00000000 --- a/platform/cafe/source/modules/wrap_fontmodule_ext.cpp +++ /dev/null @@ -1,129 +0,0 @@ -#include - -#include - -#include - -#include - -#include - -using Rasterizer = love::Rasterizer; -using namespace love; - -#define instance() (Module::GetInstance>(Module::M_FONT)) - -int Wrap_FontModule::NewTrueTypeRasterizer(lua_State* L) -{ - ::Rasterizer* self = nullptr; - - if (lua_type(L, 1) == LUA_TNUMBER || lua_isnone(L, 1)) - { - int size = luaL_optinteger(L, 1, 13); - - const char* hint = luaL_optstring(L, 2, "normal"); - std::optional<::Rasterizer::Hinting> hinting; - - if (!(hinting = ::Rasterizer::hintings.Find(hint))) - return luax::EnumError(L, "TrueType font hinting mode", ::Rasterizer::hintings, hint); - - // clang-format off - if (lua_isnoneornil(L, 3)) - luax::CatchException(L, [&]() { self = instance()->NewTrueTypeRasterizer(size, *hinting); }); - else - { - float dpiScale = luaL_checknumber(L, 3); - luax::CatchException(L, [&]() { self = instance()->NewTrueTypeRasterizer(size, dpiScale, *hinting); }); - } - // clang-format on - } - else - { - Data* data = nullptr; - std::optional systemFont; - - if (luax::IsType(L, 1, Data::type)) - { - data = Wrap_Data::CheckData(L, 1); - data->Retain(); - } - else - { - const char* filename = luaL_checkstring(L, 1); - - if (!love::has_file_extension(filename)) - { - const auto systemFonts = FontModule::systemFonts; - if (!(systemFont = systemFonts.Find(filename))) - return luax::EnumError(L, "system font name", systemFonts, filename); - } - else - data = Wrap_Filesystem::GetFileData(L, 1); - } - - int size = luaL_optinteger(L, 2, 12); - - const char* hint = luaL_optstring(L, 3, "normal"); - std::optional<::Rasterizer::Hinting> hinting; - - if (!(hinting = ::Rasterizer::hintings.Find(hint))) - return luax::EnumError(L, "TrueType font hinting mode", ::Rasterizer::hintings, hint); - - if (lua_isnoneornil(L, 4)) - { - // clang-format off - if (systemFont) - { - luax::CatchException(L, - [&]() { self = instance()->NewTrueTypeRasterizer(size, *systemFont, *hinting);}); - } - else - { - luax::CatchException(L, - [&]() { self = instance()->NewTrueTypeRasterizer(data, size, *hinting); }, - [&](bool) { data->Release(); } - ); - } - // clang-format on - } - else - { - float dpiScale = luaL_checknumber(L, 4); - - // clang-format off - luax::CatchException(L, - [&]() { self = instance()->NewTrueTypeRasterizer(data, size, dpiScale, *hinting); }, - [&](bool) { data->Release(); } - ); - // clang-format on - } - } - - luax::PushType(L, self); - self->Release(); - - return 1; -} - -int Wrap_FontModule::NewSystemFontRasterizer(lua_State* L, uint8_t systemFont) -{ - ::Rasterizer* self = nullptr; - const auto type = (OSSharedDataType)systemFont; - const auto hinting = ::Rasterizer::HINTING_NORMAL; - - luax::CatchException(L, [&]() { self = instance()->NewTrueTypeRasterizer(13, type, hinting); }); - - luax::PushType(L, self); - self->Release(); - - return 1; -} - -// clang-format off -static constexpr luaL_Reg functions[] = -{ - { "newTrueTypeRasterizer", Wrap_FontModule::NewTrueTypeRasterizer } -}; -// clang-format on - -std::span Wrap_FontModule::extensions = functions; diff --git a/platform/ctr/include/modules/fontmodule_ext.hpp b/platform/ctr/include/modules/fontmodule_ext.hpp index aa8e6ed5..0923d476 100644 --- a/platform/ctr/include/modules/fontmodule_ext.hpp +++ b/platform/ctr/include/modules/fontmodule_ext.hpp @@ -61,6 +61,13 @@ namespace love using FontModule::NewTrueTypeRasterizer; + Rasterizer* NewImageRasterizer(ImageData* data, const std::string& text, + int extraSpacing, float dpiScale) const override; + + Rasterizer* NewImageRasterizer(ImageData* data, uint32_t* glyphs, + int glyphCount, int extraSpacing, + float dpiScale) const override; + Rasterizer* NewRasterizer(FileData* data) const; Rasterizer* NewTrueTypeRasterizer(Data* data, int size, diff --git a/platform/ctr/source/modules/fontmodule_ext.cpp b/platform/ctr/source/modules/fontmodule_ext.cpp index 0414c2f9..5bab0ee1 100644 --- a/platform/ctr/source/modules/fontmodule_ext.cpp +++ b/platform/ctr/source/modules/fontmodule_ext.cpp @@ -2,6 +2,9 @@ #include +#include +#include + using namespace love; SystemFont::SystemFont(CFG_Region region) @@ -101,6 +104,36 @@ FontModule::FontModule() this->defaultFontData.Set(new ByteData(data, size, true), Acquire::NORETAIN); } +Rasterizer* FontModule::NewImageRasterizer(ImageData* data, + const std::string& text, int extraSpacing, + float dpiScale) const +{ + std::vector glyphs {}; + glyphs.reserve(text.size()); + + try + { + utf8::iterator it(text.begin(), text.begin(), text.end()); + utf8::iterator end(text.end(), text.begin(), text.end()); + + while (it != end) + glyphs.push_back(*it++); + } + catch (utf8::exception& e) + { + throw love::Exception("UTF-8 decoding error: %s", e.what()); + } + + return this->NewImageRasterizer(data, &glyphs[0], (int)glyphs.size(), extraSpacing, dpiScale); +} + +Rasterizer* FontModule::NewImageRasterizer(ImageData* data, + uint32_t* glyphs, int glyphCount, + int extraSpacing, float dpiScale) const +{ + return new ImageRasterizer(data, glyphs, glyphCount, extraSpacing, dpiScale); +} + Rasterizer* FontModule::NewRasterizer(FileData* data) const { if (TrueTypeRasterizer::Accepts(this->library, data)) diff --git a/platform/ctr/source/objects/texture_ext.cpp b/platform/ctr/source/objects/texture_ext.cpp index dc4987ae..b53f089c 100644 --- a/platform/ctr/source/objects/texture_ext.cpp +++ b/platform/ctr/source/objects/texture_ext.cpp @@ -256,11 +256,15 @@ void Texture::ReplacePixels(const void* data, size_t size, int sli switch (this->GetPixelFormat()) { case PIXELFORMAT_RGB565_UNORM: + { _replacePixels(data, this->texture->data, rect, this->width, this->height); break; + } default: + { _replacePixels(data, this->texture->data, rect, this->width, this->height); break; + } } C3D_TexFlush(this->texture); diff --git a/platform/hac/include/modules/fontmodule_ext.hpp b/platform/hac/include/modules/fontmodule_ext.hpp index 62373994..9fde70ac 100644 --- a/platform/hac/include/modules/fontmodule_ext.hpp +++ b/platform/hac/include/modules/fontmodule_ext.hpp @@ -48,8 +48,12 @@ namespace love using FontModule::NewTrueTypeRasterizer; - Rasterizer* NewTrueTypeRasterizer(int size, TrueTypeRasterizer<>::Hinting hinting, - PlSharedFontType type) const; + Rasterizer* NewImageRasterizer(ImageData* data, const std::string& text, + int extraSpacing, float dpiScale) const override; + + Rasterizer* NewImageRasterizer(ImageData* data, uint32_t* glyphs, + int glyphCount, int extraSpacing, + float dpiScale) const override; Rasterizer* NewTrueTypeRasterizer(Data* data, int size, TrueTypeRasterizer<>::Hinting hinting) const override; diff --git a/platform/hac/source/modules/fontmodule_ext.cpp b/platform/hac/source/modules/fontmodule_ext.cpp index 578cd940..fc4d498e 100644 --- a/platform/hac/source/modules/fontmodule_ext.cpp +++ b/platform/hac/source/modules/fontmodule_ext.cpp @@ -2,6 +2,9 @@ #include +#include +#include + using namespace love; FontModule::FontModule() : FontModule() @@ -9,23 +12,47 @@ FontModule::FontModule() : FontModule() this->defaultFontData.Set(new SystemFont(), Acquire::NORETAIN); } +Rasterizer* FontModule::NewImageRasterizer(ImageData* data, + const std::string& text, int extraSpacing, + float dpiScale) const +{ + std::vector glyphs {}; + glyphs.reserve(text.size()); + + try + { + utf8::iterator it(text.begin(), text.begin(), text.end()); + utf8::iterator end(text.end(), text.begin(), text.end()); + + while (it != end) + glyphs.push_back(*it++); + } + catch (utf8::exception& e) + { + throw love::Exception("UTF-8 decoding error: %s", e.what()); + } + + return this->NewImageRasterizer(data, &glyphs[0], (int)glyphs.size(), extraSpacing, dpiScale); +} + +Rasterizer* FontModule::NewImageRasterizer(ImageData* data, + uint32_t* glyphs, int glyphCount, + int extraSpacing, float dpiScale) const +{ + return new ImageRasterizer(data, glyphs, glyphCount, extraSpacing, dpiScale); +} + Rasterizer* FontModule::NewRasterizer(FileData* data) const { if (TrueTypeRasterizer::Accepts(this->library, data)) return this->NewTrueTypeRasterizer(data, 12, TrueTypeRasterizer<>::HINTING_NORMAL); + else if (BMFontRasterizer::Accepts(data)) + return this->NewBMFontRasterizer(data, {}, 1.0f); throw love::Exception("Invalid font file: %s", data->GetFilename().c_str()); return nullptr; } -Rasterizer* FontModule::NewTrueTypeRasterizer(int size, - TrueTypeRasterizer<>::Hinting hinting, - PlSharedFontType type) const -{ - StrongReference data(new SystemFont(type), Acquire::NORETAIN); - return this->NewTrueTypeRasterizer(data.Get(), size, hinting); -} - Rasterizer* FontModule::NewTrueTypeRasterizer( Data* data, int size, TrueTypeRasterizer<>::Hinting hinting) const { diff --git a/source/modules/font/fontmodule.cpp b/source/modules/font/fontmodule.cpp index 2af11a57..62eb0525 100644 --- a/source/modules/font/fontmodule.cpp +++ b/source/modules/font/fontmodule.cpp @@ -4,6 +4,10 @@ #include +#include + +#include + using namespace love; template<> @@ -23,6 +27,13 @@ FontModule::~FontModule() #endif } +template<> +Rasterizer* FontModule::NewBMFontRasterizer( + FileData* data, const std::vector*>& images, float dpiScale) const +{ + return new BMFontRasterizer(data, images, dpiScale); +} + template<> Rasterizer* FontModule::NewTrueTypeRasterizer( int size, TrueTypeRasterizer<>::Hinting hinting) const diff --git a/source/modules/font/wrap_fontmodule.cpp b/source/modules/font/wrap_fontmodule.cpp index 614e81b7..b8881f7c 100644 --- a/source/modules/font/wrap_fontmodule.cpp +++ b/source/modules/font/wrap_fontmodule.cpp @@ -6,6 +6,8 @@ #include #include +#include + #include #include @@ -49,6 +51,8 @@ int Wrap_FontModule::NewRasterizer(lua_State* L) return 1; } + else + return Wrap_FontModule::NewBMFontRasterizer(L); return 0; } @@ -164,12 +168,98 @@ int Wrap_FontModule::NewGlyphData(lua_State* L) return 1; } +static void convertImageData(lua_State* L, int index) +{ + if (lua_type(L, 1) == LUA_TSTRING || luax::IsType(L, index, File::type) || + luax::IsType(L, index, FileData::type)) + { + luax::ConvertObject(L, index, "image", "newImageData"); + } +} + +int Wrap_FontModule::NewBMFontRasterizer(lua_State* L) +{ + if (Console::Is(Console::CTR)) + return luaL_error(L, "Cannot use BMFontRasterizer on the 3DS."); + + Rasterizer* rasterizer = nullptr; + + FileData* data = Wrap_Filesystem::GetFileData(L, 1); + + std::vector*> images {}; + float dpiScale = luaL_optnumber(L, 3, 1.0f); + + if (lua_istable(L, 2)) + { + const auto length = luax::ObjectLength(L, 2); + for (int index = 1; index <= (int)length; index++) + { + lua_rawgeti(L, 2, index); + + convertImageData(L, -1); + + auto* imageData = Wrap_ImageData::CheckImageData(L, -1); + images.push_back(imageData); + imageData->Retain(); + + lua_pop(L, 1); + } + } + else if (!lua_isnoneornil(L, 2)) + { + convertImageData(L, 2); + + auto* imageData = Wrap_ImageData::CheckImageData(L, 2); + images.push_back(imageData); + imageData->Retain(); + } + + luax::CatchException( + L, [&]() { rasterizer = instance()->NewBMFontRasterizer(data, images, dpiScale); }, + [&](bool) { + data->Release(); + for (auto* imageData : images) + imageData->Release(); + }); + + luax::PushType(L, rasterizer); + rasterizer->Release(); + + return 1; +} + +int Wrap_FontModule::NewImageRasterizer(lua_State* L) +{ + if (Console::Is(Console::CTR)) + return luaL_error(L, "Cannot use ImageRasterizer on the 3DS."); + + Rasterizer* rasterizer = nullptr; + + convertImageData(L, 1); + + auto* imageData = Wrap_ImageData::CheckImageData(L, 1); + std::string glyphs = luax::CheckString(L, 2); + int extraspacing = luaL_optinteger(L, 3, 0); + float dpiScale = luaL_optnumber(L, 4, 1.0f); + + luax::CatchException(L, [&]() { + rasterizer = instance()->NewImageRasterizer(imageData, glyphs, extraspacing, dpiScale); + }); + + luax::PushType(L, rasterizer); + rasterizer->Release(); + + return 1; +} + // clang-format off static constexpr luaL_Reg functions[] = { { "newGlyphData", Wrap_FontModule::NewGlyphData }, { "newRasterizer", Wrap_FontModule::NewRasterizer }, - { "newTrueTypeRasterizer", Wrap_FontModule::NewTrueTypeRasterizer } + { "newTrueTypeRasterizer", Wrap_FontModule::NewTrueTypeRasterizer }, + { "newBMFontRasterizer", Wrap_FontModule::NewBMFontRasterizer }, + { "newImageRasterizer", Wrap_FontModule::NewImageRasterizer } }; static constexpr lua_CFunction types[] = diff --git a/source/objects/bmfontrasterizer/bmfontrasterizer.cpp b/source/objects/bmfontrasterizer/bmfontrasterizer.cpp new file mode 100644 index 00000000..2d0c398e --- /dev/null +++ b/source/objects/bmfontrasterizer/bmfontrasterizer.cpp @@ -0,0 +1,332 @@ +#include + +#include +#include + +#include + +#include +#include + +namespace +{ + class BMFontLine + { + public: + BMFontLine(const std::string& line); + + const std::string& GetTag() const + { + return this->tag; + } + + int GetAttributeInt(const char* name) const; + + std::string GetAttributeString(const char* name) const; + + private: + std::string tag; + std::unordered_map attributes; + }; + + BMFontLine::BMFontLine(const std::string& line) + { + this->tag = line.substr(0, line.find(' ')); + size_t start = 0; + + while (start < line.length()) + { + const auto end = line.find('=', start); + if (end == std::string::npos || end + 1 >= line.length()) + break; + + auto keyStart = line.rfind(' ', end); + if (keyStart == std::string::npos) + break; + + keyStart++; + + const auto key = line.substr(keyStart, end - keyStart); + auto valueStart = end + 1; + auto valueEnd = valueStart + 1; + + if (line[valueStart] == '"') + { + valueStart++; + valueEnd = line.find('"', valueStart) - 1; + } + else + valueEnd = line.find(' ', valueStart + 1) - 1; + + valueEnd = std::min(valueEnd, line.length() - 1); + this->attributes[key] = line.substr(valueStart, valueEnd - valueStart + 1); + + start = valueEnd + 1; + } + } + + int BMFontLine::GetAttributeInt(const char* name) const + { + auto iterator = this->attributes.find(name); + if (iterator == this->attributes.end()) + return 0; + + return (int)std::strtol(iterator->second.c_str(), nullptr, 10); + } + + std::string BMFontLine::GetAttributeString(const char* name) const + { + auto iterator = this->attributes.find(name); + if (iterator == this->attributes.end()) + return ""; + + return iterator->second; + } +} // namespace + +using namespace love; + +BMFontRasterizer::BMFontRasterizer(FileData* fontDefinition, + const std::vector*>& images, + float dpiScale) : + fontSize(0), + unicode(false), + lineHeight(0) +{ + this->dpiScale = dpiScale; + + const auto& filename = fontDefinition->GetFilename(); + auto slashPosition = filename.rfind('/'); + + if (slashPosition != std::string::npos) + fontFolder = filename.substr(0, slashPosition); + + for (int index = 0; index < (int)images.size(); index++) + { + if (images[index]->GetFormat() != PIXELFORMAT_RGBA8_UNORM) + throw love::Exception("Only 32-bit RGBA images are supported in BMFonts."); + + this->images[index] = images[index]; + } + + std::string config((const char*)fontDefinition->GetData(), fontDefinition->GetSize()); + this->ParseConfig(config); +} + +void BMFontRasterizer::ParseConfig(const std::string& config) +{ + { + BMFontCharacter nullChar {}; + nullChar.glyph = 0; + nullChar.page = -1; + this->characters.push_back(nullChar); + this->characterIndices[0] = this->characters.size() - 1; + } + + std::string line {}; + std::stringstream stream(config); + + // TODO: fix love::get_line to auto update the stream position + while (std::getline(stream, line)) + { + BMFontLine bmLine(line); + const auto& tag = bmLine.GetTag(); + + if (tag == "info") + { + this->fontSize = bmLine.GetAttributeInt("size"); + this->unicode = bmLine.GetAttributeInt("unicode") != 0; + } + else if (tag == "common") + { + this->lineHeight = bmLine.GetAttributeInt("lineHeight"); + metrics.ascent = bmLine.GetAttributeInt("base"); + } + else if (tag == "page") + { + const auto& page = bmLine.GetAttributeInt("id"); + auto filename = bmLine.GetAttributeString("file"); + + if (!this->fontFolder.empty()) + filename = this->fontFolder + "/" + filename; + + if (this->images[page].Get() == nullptr) + { + auto* filesystem = Module::GetInstance(Module::M_FILESYSTEM); + auto* imageModule = Module::GetInstance(Module::M_IMAGE); + + if (!filesystem) + throw love::Exception("Filesystem module not loaded!"); + + if (!imageModule) + throw love::Exception("Image module not loaded!"); + + StrongReference data(filesystem->Read(filename.c_str()), + Acquire::NORETAIN); + + ImageData* image = imageModule->NewImageData(data.Get()); + if (image->GetFormat() != PIXELFORMAT_RGBA8_UNORM) + throw love::Exception("Only 32-bit RGBA images are supported in BMFonts."); + + this->images[page].Set(image, Acquire::NORETAIN); + } + } + else if (tag == "char") + { + BMFontCharacter character {}; + const auto codepoint = bmLine.GetAttributeInt("id"); + + character.glyph = codepoint; + character.x = bmLine.GetAttributeInt("x"); + character.y = bmLine.GetAttributeInt("y"); + character.page = bmLine.GetAttributeInt("page"); + + character.metrics.width = bmLine.GetAttributeInt("width"); + character.metrics.height = bmLine.GetAttributeInt("height"); + character.metrics.bearingX = bmLine.GetAttributeInt("xoffset"); + character.metrics.bearingY = bmLine.GetAttributeInt("yoffset"); + character.metrics.advance = bmLine.GetAttributeInt("xadvance"); + + this->characters.push_back(character); + this->characterIndices[codepoint] = (int)this->characters.size() - 1; + } + else + { + const auto first = bmLine.GetAttributeInt("first"); + const auto second = bmLine.GetAttributeInt("second"); + + uint64_t packedGlyphs = ((uint64_t)first << 32) | (uint64_t)second; + this->kernings[packedGlyphs] = bmLine.GetAttributeInt("amount"); + } + } + + if (this->characters.size() == 0) + throw love::Exception("Invalid BMFont file (no character definitions?)"); + + bool guessHeight = this->lineHeight == 0; + + for (const auto& character : this->characters) + { + if (character.glyph == 0) + continue; + + const auto width = character.metrics.width; + const auto height = character.metrics.height; + + if (!this->unicode && character.glyph > 127) + { + throw love::Exception( + "Invalid BMFont character id (only unicode and ASCII are supported)"); + } + + if (character.page < 0 || this->images[character.page].Get() == nullptr) + throw love::Exception("Invalid BMFont character page id: %d", character.page); + + const auto* data = this->images[character.page].Get(); + + if (!data->Inside(character.x, character.y)) + throw love::Exception("Invalid coordinates for BMFont character %u.", character.glyph); + + if (width > 0 && !data->Inside(character.x + width - 1, character.y)) + throw love::Exception("Invalid width for BMFont character %u.", character.glyph); + + if (height > 0 && !data->Inside(character.x, character.y + height - 1)) + throw love::Exception("Invalid height for BMFont character %u.", character.glyph); + + if (guessHeight) + this->lineHeight = std::max(this->lineHeight, character.metrics.height); + } + + this->metrics.height = this->lineHeight; +} + +int BMFontRasterizer::GetLineHeight() const +{ + return this->lineHeight; +} + +int BMFontRasterizer::GetGlyphSpacing(uint32_t glyph) const +{ + const auto iterator = this->characterIndices.find(glyph); + if (iterator == this->characterIndices.end()) + return 0; + + return this->characters[iterator->second].metrics.advance; +} + +int BMFontRasterizer::GetGlyphIndex(uint32_t glyph) const +{ + const auto iterator = this->characterIndices.find(glyph); + if (iterator == this->characterIndices.end()) + return 0; + + return iterator->second; +} + +GlyphData* BMFontRasterizer::GetGlyphDataForIndex(int index) const +{ + if (index < 0 || index >= (int)this->characters.size()) + return new GlyphData(0, GlyphData::GlyphMetrics {}, PIXELFORMAT_RGBA8_UNORM); + + const auto& character = this->characters[index]; + const auto& imagePair = this->images.find(character.page); + + if (imagePair == this->images.end()) + return new GlyphData(character.glyph, GlyphData::GlyphMetrics {}, PIXELFORMAT_RGBA8_UNORM); + + const auto* image = imagePair->second.Get(); + auto* data = new GlyphData(character.glyph, character.metrics, PIXELFORMAT_RGBA8_UNORM); + + const auto pixelSize = image->GetPixelSize(); + + auto* pixels = (uint8_t*)data->GetData(); + const auto* destination = (const uint8_t*)image->GetData(); + + std::unique_lock lock(image->GetMutex()); + + for (int y = 0; y < character.metrics.height; y++) + { + auto index = ((character.y + y) * image->GetWidth() + character.x) * pixelSize; + std::memcpy(&pixels[y * character.metrics.width * pixelSize], &destination[index], + character.metrics.width * pixelSize); + } + + return data; +} + +int BMFontRasterizer::GetGlyphCount() const +{ + return (int)this->characters.size(); +} + +bool BMFontRasterizer::HasGlyph(uint32_t glyph) const +{ + return this->characterIndices.find(glyph) != this->characterIndices.end(); +} + +float BMFontRasterizer::GetKerning(uint32_t left, uint32_t right) const +{ + const auto packedGlyphs = ((uint64_t)left << 32) | (uint64_t)right; + const auto iterator = this->kernings.find(packedGlyphs); + + if (iterator != this->kernings.end()) + return iterator->second; + + return 0.0f; +} + +Rasterizer::DataType BMFontRasterizer::GetDataType() const +{ + return Rasterizer::DATA_IMAGE; +} + +TextShaper* BMFontRasterizer::NewTextShaper() +{ + return new GenericShaper(this); +} + +bool BMFontRasterizer::Accepts(FileData* fontDefinition) +{ + const auto* data = (const char*)fontDefinition->GetData(); + return fontDefinition->GetSize() > 4 && std::memcmp(data, "info", 4) == 0; +} diff --git a/source/objects/font/font.cpp b/source/objects/font/font.cpp index 1e5e6a26..5c860722 100644 --- a/source/objects/font/font.cpp +++ b/source/objects/font/font.cpp @@ -364,7 +364,7 @@ const Font::Glyph& Font::AddGlyph(TextShaper::GlyphIndex glyphIndex) if (width > 0 && height > 0) { - const auto color = Color(Color::WHITE).array(); + const auto color = Color(1, 1, 1, 1).array(); // clang-format off const std::array vertices = diff --git a/source/objects/glyphdata/glyphdata.cpp b/source/objects/glyphdata/glyphdata.cpp index 57b42c99..0c1cdd1c 100644 --- a/source/objects/glyphdata/glyphdata.cpp +++ b/source/objects/glyphdata/glyphdata.cpp @@ -59,7 +59,7 @@ size_t GlyphData::GetPixelSize() const size_t GlyphData::GetSize() const { - return size_t(this->GetWidth() * this->GetHeight() * this->GetPixelSize()); + return size_t(this->GetWidth() * this->GetHeight()) * this->GetPixelSize(); } int GlyphData::GetHeight() const diff --git a/source/objects/imagerasterizer/imagerasterizer.cpp b/source/objects/imagerasterizer/imagerasterizer.cpp new file mode 100644 index 00000000..44924748 --- /dev/null +++ b/source/objects/imagerasterizer/imagerasterizer.cpp @@ -0,0 +1,159 @@ +#include + +#include + +#include + +#include + +#include + +using namespace love; + +ImageRasterizer::ImageRasterizer(ImageData* data, const uint32_t* glyphs, + int glyphCount, int extraSpacing, float dpiScale) : + imageData(data), + glyphCount(glyphCount + 1), + extraSpacing(extraSpacing) +{ + this->dpiScale = dpiScale; + + if (data->GetFormat() != PIXELFORMAT_RGBA8_UNORM) + throw love::Exception("Only 32-bit RGBA images are supported in Image Fonts!"); + + this->Load(glyphs, glyphCount); +} + +int ImageRasterizer::GetLineHeight() const +{ + return this->GetHeight(); +} + +int ImageRasterizer::GetGlyphSpacing(uint32_t glyph) const +{ + auto it = this->glyphIndicies.find(glyph); + if (it == this->glyphIndicies.end()) + return 0; + + return this->imageGlyphs[it->second].width + this->extraSpacing; +} + +int ImageRasterizer::GetGlyphIndex(uint32_t glyph) const +{ + auto it = this->glyphIndicies.find(glyph); + if (it == this->glyphIndicies.end()) + return 0; + + return it->second; +} + +GlyphData* ImageRasterizer::GetGlyphDataForIndex(int index) const +{ + GlyphData::GlyphMetrics glyphMetrics {}; + uint32_t glyph = 0; + + if (index >= 0 && index < (int)this->imageGlyphs.size()) + { + glyphMetrics.width = this->imageGlyphs[index].width; + glyphMetrics.advance = this->imageGlyphs[index].width + this->extraSpacing; + glyph = this->imageGlyphs[index].glyph; + } + + glyphMetrics.height = this->metrics.height; + + auto* glyphData = new GlyphData(glyph, glyphMetrics, PIXELFORMAT_RGBA8_UNORM); + + if (glyphMetrics.width == 0) + return glyphData; + + std::unique_lock lock(this->imageData->GetMutex()); + + auto* pixels = (Color32*)glyphData->GetData(); + const auto* source = (const Color32*)this->imageData->GetData(); + + const auto size = glyphData->GetWidth() * glyphData->GetHeight(); + const auto width = this->imageData->GetWidth(); + + for (int idx = 0; idx < size; idx++) + { + const auto add = (width * (idx / glyphMetrics.width)); + auto color = source[this->imageGlyphs[index].x + (idx % glyphMetrics.width) + add]; + + if (color == this->spacer) + pixels[idx] = Color32(0, 0, 0, 0); + else + pixels[idx] = color; + } + + return glyphData; +} + +void ImageRasterizer::Load(const uint32_t* glyphs, int glyphCount) +{ + const auto* pixels = (const Color32*)this->imageData->GetData(); + + const auto width = this->imageData->GetWidth(); + const auto height = this->imageData->GetHeight(); + + std::unique_lock lock(this->imageData->GetMutex()); + + this->metrics.height = height; + this->spacer = pixels[0]; + + int start = 0; + int end = 0; + + { + ImageGlyphData nullGlyph {}; + nullGlyph.x = 0; + nullGlyph.width = 0; + nullGlyph.glyph = 0; + + this->imageGlyphs.push_back(nullGlyph); + this->glyphIndicies[0] = (int)this->imageGlyphs.size() - 1; + } + + for (int index = 0; index < glyphCount; ++index) + { + start = end; + + while (start < width && pixels[start] == spacer) + ++start; + + end = start; + + while (end < width && pixels[end] != spacer) + ++end; + + if (start >= end) + break; + + ImageGlyphData imageGlyph {}; + imageGlyph.x = start; + imageGlyph.width = end - start; + imageGlyph.glyph = glyphs[index]; + + this->imageGlyphs.push_back(imageGlyph); + this->glyphIndicies[glyphs[index]] = (int)this->imageGlyphs.size() - 1; + } +} + +int ImageRasterizer::GetGlyphCount() const +{ + return this->glyphCount; +} + +bool ImageRasterizer::HasGlyph(uint32_t glyph) const +{ + return this->glyphIndicies.find(glyph) != this->glyphIndicies.end(); +} + +Rasterizer::DataType ImageRasterizer::GetDataType() const +{ + return Rasterizer::DataType::DATA_IMAGE; +} + +TextShaper* ImageRasterizer::NewTextShaper() +{ + return new GenericShaper(this); +} \ No newline at end of file diff --git a/source/objects/textbatch/textbatch.cpp b/source/objects/textbatch/textbatch.cpp index 6163fd26..e93c45cb 100644 --- a/source/objects/textbatch/textbatch.cpp +++ b/source/objects/textbatch/textbatch.cpp @@ -74,7 +74,7 @@ void TextBatch::AddTextData(const TextData& text) std::vector newCommands {}; TextShaper::TextInfo info {}; - Color constantColor(Color::WHITE); + Color constantColor(1, 1, 1, 1); if (text.align == Font::ALIGN_MAX_ENUM) { newCommands = this->font->GenerateVertices(text.codepoints, Range(), constantColor,