diff --git a/desktop_version/CMakeLists.txt b/desktop_version/CMakeLists.txt index 6945f5f8..7f181ef3 100644 --- a/desktop_version/CMakeLists.txt +++ b/desktop_version/CMakeLists.txt @@ -76,6 +76,7 @@ set(VVV_SRC src/Entity.cpp src/FileSystemUtils.cpp src/Finalclass.cpp + src/Font.cpp src/Game.cpp src/Graphics.cpp src/GraphicsResources.cpp diff --git a/desktop_version/src/CustomLevels.cpp b/desktop_version/src/CustomLevels.cpp index e9feafec..2fbb7f62 100644 --- a/desktop_version/src/CustomLevels.cpp +++ b/desktop_version/src/CustomLevels.cpp @@ -14,6 +14,7 @@ #include "Editor.h" #include "Enums.h" #include "FileSystemUtils.h" +#include "Font.h" #include "Game.h" #include "Graphics.h" #include "GraphicsUtil.h" @@ -1313,6 +1314,7 @@ next: #endif loc::loadtext_custom(_path.c_str()); + font::load_custom(); version=2; diff --git a/desktop_version/src/Font.cpp b/desktop_version/src/Font.cpp new file mode 100644 index 00000000..c1470d78 --- /dev/null +++ b/desktop_version/src/Font.cpp @@ -0,0 +1,348 @@ +#include "Font.h" + +#include +#include + +#include "Alloc.h" +#include "FileSystemUtils.h" +#include "Graphics.h" +#include "UtilityClass.h" +#include "XMLUtils.h" + +// Sigh... This is the second forward-declaration, we need to put this in a header file +SDL_Texture* LoadImage(const char *filename, const TextureLoadType loadtype); + +namespace font +{ + +Font temp_bfont; // replace with like, a vector of all loaded fonts + +static void codepoint_split( + const uint32_t codepoint, + short* page, + short* glyph +) +{ + // Splits a code point (0x10FFFF) into page (0x10F) and glyph (0xFFF) + if (codepoint > 0x10FFFF) + { + codepoint_split(0xFFFD, page, glyph); + return; + } + *page = codepoint >> 12; + *glyph = codepoint % FONT_PAGE_SIZE; +} + +static GlyphInfo* get_glyphinfo( + const Font* f, + const uint32_t codepoint +) +{ + short page, glyph; + codepoint_split(codepoint, &page, &glyph); + + if (f->glyph_page[page] == NULL) + { + return NULL; + } + + return &f->glyph_page[page][glyph]; +} + +static void add_glyphinfo( + Font* f, + const uint32_t codepoint, + const int image_idx +) +{ + if (image_idx < 0 || image_idx > 65535) + { + return; + } + + short page, glyph; + codepoint_split(codepoint, &page, &glyph); + + if (f->glyph_page[page] == NULL) + { + f->glyph_page[page] = (GlyphInfo*) SDL_calloc(FONT_PAGE_SIZE, sizeof(GlyphInfo)); + if (f->glyph_page[page] == NULL) + { + return; + } + } + + f->glyph_page[page][glyph].image_idx = image_idx; + f->glyph_page[page][glyph].advance = f->glyph_w; + f->glyph_page[page][glyph].flags = GLYPH_EXISTS; +} + +static bool glyph_is_valid(const GlyphInfo* glyph) +{ + return glyph->flags & GLYPH_EXISTS; +} + +static GlyphInfo* find_glyphinfo(const Font* f, const uint32_t codepoint) +{ + /* Get the GlyphInfo for a specific codepoint, or or ? if it doesn't exist. + * As a last resort, may return NULL. */ + GlyphInfo* glyph = get_glyphinfo(f, codepoint); + if (glyph != NULL && glyph_is_valid(glyph)) + { + return glyph; + } + glyph = get_glyphinfo(f, 0xFFFD); + if (glyph != NULL && glyph_is_valid(glyph)) + { + return glyph; + } + glyph = get_glyphinfo(f, '?'); + if (glyph != NULL && glyph_is_valid(glyph)) + { + return glyph; + } + + return NULL; +} + +static bool decode_xml_range(tinyxml2::XMLElement* elem, unsigned* start, unsigned* end) +{ + // We do support hexadecimal start/end like "0x10FFFF" + if (elem->QueryUnsignedAttribute("start", start) != tinyxml2::XML_SUCCESS + || elem->QueryUnsignedAttribute("end", end) != tinyxml2::XML_SUCCESS + || *end < *start || *start > 0x10FFFF + ) + { + return false; + } + + *end = SDL_min(*end, 0x10FFFF); + return true; +} + +static void load_font(Font* f, const char* name) +{ + char name_png[256]; + char name_txt[256]; + char name_xml[256]; + SDL_snprintf(name_png, sizeof(name_png), "graphics/%s.png", name); + SDL_snprintf(name_txt, sizeof(name_txt), "graphics/%s.txt", name); + SDL_snprintf(name_xml, sizeof(name_xml), "graphics/%s.fontmeta", name); + + f->glyph_w = 8; + f->glyph_h = 8; + + bool white_teeth = false; + + tinyxml2::XMLDocument doc; + tinyxml2::XMLHandle hDoc(&doc); + tinyxml2::XMLElement* pElem; + bool xml_loaded = false; + + if (FILESYSTEM_areAssetsInSameRealDir(name_png, name_xml) + && FILESYSTEM_loadAssetTiXml2Document(name_xml, doc) + ) + { + xml_loaded = true; + hDoc = hDoc.FirstChildElement("font_metadata"); + + if ((pElem = hDoc.FirstChildElement("width").ToElement()) != NULL) + { + f->glyph_w = help.Int(pElem->GetText()); + } + if ((pElem = hDoc.FirstChildElement("height").ToElement()) != NULL) + { + f->glyph_h = help.Int(pElem->GetText()); + } + if ((pElem = hDoc.FirstChildElement("white_teeth").ToElement()) != NULL) + { + // If 1, we don't need to whiten the entire font (like in old versions) + white_teeth = help.Int(pElem->GetText()); + } + } + + f->image = LoadImage(name_png, white_teeth ? TEX_COLOR : TEX_WHITE); + SDL_zeroa(f->glyph_page); + + if (f->image == NULL) + { + return; + } + + /* We may have a 2.3-style font.txt with all the characters. + * font.txt takes priority over in the XML. + * If neither exist, it's just ASCII. */ + bool charset_loaded = false; + unsigned char* charmap = NULL; + size_t length; + if (FILESYSTEM_areAssetsInSameRealDir(name_png, name_txt)) + { + FILESYSTEM_loadAssetToMemory(name_txt, &charmap, &length, false); + } + if (charmap != NULL) + { + charset_loaded = true; + unsigned char* current = charmap; + unsigned char* end = charmap + length; + int pos = 0; + while (current != end) + { + uint32_t codepoint = utf8::unchecked::next(current); + add_glyphinfo(f, codepoint, pos); + ++pos; + } + + VVV_free(charmap); + } + + if (xml_loaded) + { + if (!charset_loaded && (pElem = hDoc.FirstChildElement("chars").ToElement()) != NULL) + { + // in the XML is only looked at if we haven't already seen font.txt. + int pos = 0; + tinyxml2::XMLElement* subElem; + FOR_EACH_XML_SUB_ELEMENT(pElem, subElem) + { + EXPECT_ELEM(subElem, "range"); + + unsigned start, end; + if (!decode_xml_range(subElem, &start, &end)) + { + continue; + } + + for (uint32_t codepoint = start; codepoint <= end; codepoint++) + { + add_glyphinfo(f, codepoint, pos); + ++pos; + } + } + charset_loaded = true; + } + + if ((pElem = hDoc.FirstChildElement("special").ToElement()) != NULL) + { + tinyxml2::XMLElement* subElem; + FOR_EACH_XML_SUB_ELEMENT(pElem, subElem) + { + EXPECT_ELEM(subElem, "range"); + + unsigned start, end; + if (!decode_xml_range(subElem, &start, &end)) + { + continue; + } + + int advance = subElem->IntAttribute("advance", -1); + int color = subElem->IntAttribute("color", -1); + + for (uint32_t codepoint = start; codepoint <= end; codepoint++) + { + GlyphInfo* glyph = get_glyphinfo(f, codepoint); + if (glyph == NULL) + { + continue; + } + if (advance >= 0 && advance < 256) + { + glyph->advance = advance; + } + if (color == 0) + { + glyph->flags &= ~GLYPH_COLOR; + } + else if (color == 1) + { + glyph->flags |= GLYPH_COLOR; + } + } + } + } + } + + if (!charset_loaded) + { + /* If we don't have font.txt and no tag either, + * this font is 2.2-and-below-style plain ASCII. */ + for (uint8_t codepoint = 0x00; codepoint < 0x80; codepoint++) + { + add_glyphinfo(f, codepoint, codepoint); + } + } +} + +void load_main(void) +{ + // TODO PHYSFS_enumerateFiles, load everything that matches *.fontmeta or font.png (but not font.fontmeta) + load_font(&temp_bfont, "font"); +} + +void load_custom(void) +{ + // Custom (level-specific assets) fonts NYI +} + +void unload_custom(void) +{ + /* Unload all custom fonts */ + +} + +void destroy(void) +{ + /* Unload all fonts (main and custom) for exiting */ + Font* f = &temp_bfont; + VVV_freefunc(SDL_DestroyTexture, f->image); + + for (int i = 0; i < FONT_N_PAGES; i++) + { + VVV_free(f->glyph_page[i]); + } +} + +int get_advance(const Font* f, const uint32_t codepoint) +{ + /* Get the width of a single character in a font */ + GlyphInfo* glyph = find_glyphinfo(f, codepoint); + if (glyph == NULL) + { + return f->glyph_w; + } + + return glyph->advance; +} + +int print_char( + const Font* f, + const uint32_t codepoint, + const int x, + const int y, + const int scale, + const uint8_t r, + const uint8_t g, + const uint8_t b, + const uint8_t a +) +{ + /* Draws the glyph for a codepoint at x,y. + * Returns the amount of pixels to advance the cursor. */ + GlyphInfo* glyph = find_glyphinfo(f, codepoint);; + if (glyph == NULL) + { + return f->glyph_w * scale; + } + + if (glyph->flags & GLYPH_COLOR) + { + graphics.draw_grid_tile(f->image, glyph->image_idx, x, y, f->glyph_w, f->glyph_h, 255, 255, 255, 255, scale, scale * (graphics.flipmode ? -1 : 1)); + } + else + { + graphics.draw_grid_tile(f->image, glyph->image_idx, x, y, f->glyph_w, f->glyph_h, r, g, b, a, scale, scale * (graphics.flipmode ? -1 : 1)); + } + + return glyph->advance * scale; +} + +} // namespace font diff --git a/desktop_version/src/Font.h b/desktop_version/src/Font.h new file mode 100644 index 00000000..c460cc03 --- /dev/null +++ b/desktop_version/src/Font.h @@ -0,0 +1,50 @@ +#ifndef FONT_H +#define FONT_H + +#include +#include + +#include "GraphicsUtil.h" + +namespace font +{ + +#define GLYPH_EXISTS 0x1 +#define GLYPH_COLOR 0x2 + +struct GlyphInfo +{ + uint16_t image_idx; + uint8_t advance; + uint8_t flags; +}; + +/* Codepoints go up to U+10FFFF, so we have 0x110 (272) pages + * of 0x1000 (4096) glyphs, allocated as needed */ +#define FONT_N_PAGES 0x110 +#define FONT_PAGE_SIZE 0x1000 + +struct Font +{ + uint8_t glyph_w; + uint8_t glyph_h; + + SDL_Texture* image; + + GlyphInfo* glyph_page[FONT_N_PAGES]; +}; + +extern Font temp_bfont; + +void load_main(void); +void load_custom(void); +void unload_custom(void); +void destroy(void); + +int get_advance(const Font* f, uint32_t codepoint); // TODO de-api +int print_char(const Font* f, uint32_t codepoint, int x, int y, int scale, uint8_t r, uint8_t g, uint8_t b, uint8_t a); // TODO de-api + +} // namespace font + + +#endif // FONT_H diff --git a/desktop_version/src/Game.cpp b/desktop_version/src/Game.cpp index 6e0ea7b6..bda92e17 100644 --- a/desktop_version/src/Game.cpp +++ b/desktop_version/src/Game.cpp @@ -13,6 +13,7 @@ #include "Entity.h" #include "Enums.h" #include "FileSystemUtils.h" +#include "Font.h" #include "GlitchrunnerMode.h" #include "Graphics.h" #include "Localization.h" @@ -7028,6 +7029,7 @@ void Game::quittomenu(void) graphics.fademode = FADE_START_FADEIN; FILESYSTEM_unmountAssets(); loc::unloadtext_custom(); + font::unload_custom(); cliplaytest = false; graphics.titlebg.tdrawback = true; graphics.flipmode = false; diff --git a/desktop_version/src/Graphics.cpp b/desktop_version/src/Graphics.cpp index 6d62c8af..8aa0c5fb 100644 --- a/desktop_version/src/Graphics.cpp +++ b/desktop_version/src/Graphics.cpp @@ -10,6 +10,7 @@ #include "Entity.h" #include "Exit.h" #include "FileSystemUtils.h" +#include "Font.h" #include "GraphicsUtil.h" #include "Localization.h" #include "Map.h" @@ -210,28 +211,6 @@ void Graphics::destroy_buffers(void) VVV_freefunc(SDL_DestroyTexture, backgroundTexture); } -int Graphics::font_idx(uint32_t ch) -{ - if (font_positions.size() > 0) - { - std::map::iterator iter = font_positions.find(ch); - if (iter == font_positions.end()) - { - iter = font_positions.find('?'); - if (iter == font_positions.end()) - { - WHINE_ONCE("font.txt missing fallback character!"); - return -1; - } - } - return iter->second; - } - else - { - return ch; - } -} - void Graphics::drawspritesetcol(int x, int y, int t, int c) { draw_grid_tile(grphx.im_sprites, t, x, y, sprites_rect.w, sprites_rect.h, getcol(c)); @@ -298,47 +277,6 @@ void Graphics::updatetitlecolours(void) #define PROCESS_TILESHEET(tilesheet, tile_square, extra_code) \ PROCESS_TILESHEET_RENAME(tilesheet, tilesheet, tile_square, extra_code) -bool Graphics::Makebfont(void) -{ - unsigned char* charmap = NULL; - size_t length; - if (FILESYSTEM_areAssetsInSameRealDir("graphics/font.png", "graphics/font.txt")) - { - FILESYSTEM_loadAssetToMemory("graphics/font.txt", &charmap, &length, false); - } - if (charmap != NULL) - { - unsigned char* current = charmap; - unsigned char* end = charmap + length; - int pos = 0; - while (current != end) - { - int codepoint = utf8::unchecked::next(current); - font_positions[codepoint] = pos; - ++pos; - } - VVV_free(charmap); - } - else - { - font_positions.clear(); - } - - return true; -} - -int Graphics::bfontlen(uint32_t ch) -{ - if (ch < 32) - { - return 6; - } - else - { - return 8; - } -} - bool Graphics::MakeSpriteArray(void) { PROCESS_TILESHEET(sprites_surf, 32, {}) @@ -409,6 +347,8 @@ void Graphics::do_print( int a, const int scale ) { + // TODO do something with flipmode + int position = 0; std::string::const_iterator iter = text.begin(); @@ -420,11 +360,7 @@ void Graphics::do_print( while (iter != text.end()) { const uint32_t character = utf8::unchecked::next(iter); - const int idx = font_idx(character); - - draw_grid_tile(grphx.im_bfont, idx, (x + position), y, 8, 8, r, g, b, a, scale, scale * (flipmode ? -1 : 1)); - - position += bfontlen(character) * scale; + position += font::print_char(&font::temp_bfont, character, x + position, y, scale, r, g, b, a); } } @@ -467,7 +403,7 @@ bool Graphics::next_wrap( goto next; } - linewidth += bfontlen(str[idx]); + linewidth += font::get_advance(&font::temp_bfont, str[idx]); switch (str[idx]) { @@ -620,13 +556,13 @@ void Graphics::bigbprint(int x, int y, const std::string& s, int r, int g, int b int Graphics::len(const std::string& t) { - int bfontpos = 0; + int text_len = 0; std::string::const_iterator iter = t.begin(); while (iter != t.end()) { int cur = utf8::unchecked::next(iter); - bfontpos += bfontlen(cur); + text_len += font::get_advance(&font::temp_bfont, cur); } - return bfontpos; + return text_len; } std::string Graphics::string_wordwrap(const std::string& s, int maxwidth, short *lines /*= NULL*/) @@ -3805,7 +3741,6 @@ bool Graphics::reloadresources(void) destroy(); MAYBE_FAIL(MakeSpriteArray()); - MAYBE_FAIL(Makebfont()); images[IMAGE_LEVELCOMPLETE] = grphx.im_image0; images[IMAGE_MINIMAP] = grphx.im_image1; diff --git a/desktop_version/src/Graphics.h b/desktop_version/src/Graphics.h index 38d051ae..ce32c136 100644 --- a/desktop_version/src/Graphics.h +++ b/desktop_version/src/Graphics.h @@ -51,11 +51,6 @@ public: GraphicsResources grphx; - int bfontlen(uint32_t ch); - int font_idx(uint32_t ch); - - bool Makebfont(void); - SDL_Color huetilegetcol(int t); SDL_Color bigchunkygetcol(int t); @@ -388,8 +383,6 @@ public: bool translucentroomname; - std::map font_positions; - #ifndef GAME_DEFINITION float inline lerp(const float v0, const float v1) { diff --git a/desktop_version/src/GraphicsResources.cpp b/desktop_version/src/GraphicsResources.cpp index fb1f4fc9..0da78040 100644 --- a/desktop_version/src/GraphicsResources.cpp +++ b/desktop_version/src/GraphicsResources.cpp @@ -158,7 +158,7 @@ static SDL_Texture* LoadTextureFromRaw(const char* filename, SDL_Surface* loaded return texture; } -static SDL_Texture* LoadImage(const char *filename, const TextureLoadType loadtype) +SDL_Texture* LoadImage(const char *filename, const TextureLoadType loadtype) { unsigned char* data; @@ -269,7 +269,6 @@ void GraphicsResources::init(void) LoadSprites("graphics/flipsprites.png", &im_flipsprites, &im_flipsprites_surf); im_tiles3 = LoadImage("graphics/tiles3.png"); - im_bfont = LoadImage("graphics/font.png", TEX_WHITE); im_teleporter = LoadImage("graphics/teleporter.png", TEX_WHITE); im_image0 = LoadImage("graphics/levelcomplete.png"); @@ -309,7 +308,6 @@ void GraphicsResources::destroy(void) CLEAR(im_entcolours_tint); CLEAR(im_sprites); CLEAR(im_flipsprites); - CLEAR(im_bfont); CLEAR(im_teleporter); CLEAR(im_image0); diff --git a/desktop_version/src/GraphicsResources.h b/desktop_version/src/GraphicsResources.h index f95fd66d..1d598c6b 100644 --- a/desktop_version/src/GraphicsResources.h +++ b/desktop_version/src/GraphicsResources.h @@ -29,7 +29,6 @@ public: SDL_Texture* im_entcolours_tint; SDL_Texture* im_sprites; SDL_Texture* im_flipsprites; - SDL_Texture* im_bfont; SDL_Texture* im_teleporter; SDL_Texture* im_image0; SDL_Texture* im_image1; diff --git a/desktop_version/src/main.cpp b/desktop_version/src/main.cpp index 5fe7905a..a45e4975 100644 --- a/desktop_version/src/main.cpp +++ b/desktop_version/src/main.cpp @@ -11,6 +11,7 @@ #include "Entity.h" #include "Exit.h" #include "FileSystemUtils.h" +#include "Font.h" #include "Game.h" #include "Graphics.h" #include "GraphicsUtil.h" @@ -628,6 +629,8 @@ int main(int argc, char *argv[]) gameScreen.init(&screen_settings); } + font::load_main(); + // This loads music too... if (!graphics.reloadresources()) { @@ -802,6 +805,7 @@ static void cleanup(void) graphics.destroy_buffers(); gameScreen.destroy(); graphics.destroy(); + font::destroy(); music.destroy(); map.destroy(); NETWORK_shutdown();