diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 41bec80..0bee837 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -15,7 +15,7 @@ jobs: - name: Install Deps run: | sudo apt update - sudo apt install libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor gettext + sudo apt install libsdl2-dev libfreetype-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor gettext - name: Build run: | diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 5afe67e..2c4aa02 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -30,7 +30,7 @@ jobs: msystem: ${{ matrix.sys }} pacboy: >- SDL2:p - SDL2_ttf:p + freetype:p SDL2_mixer:p libwebp:p ${{ matrix.deps }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b554e0..d60ee43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,14 +65,14 @@ if (EMSCRIPTEN) endfunction() add_emscripten_library(zlib -sUSE_ZLIB=1) add_emscripten_library(sdl2 -sUSE_SDL=2) - add_emscripten_library(sdl2_ttf -sUSE_SDL_TTF=2) + add_emscripten_library(freetype2 -sUSE_FREETYPE=1) set(DEFAULT_FONT_PATH /fonts/) fetch_webp() set(HAVE_WEBP 1) elseif (ANDROID) add_library(sdl2 ALIAS SDL2) - add_library(sdl2_ttf ALIAS SDL2_ttf) + add_library(freetype2 ALIAS freetype) add_library(sdl2_mixer ALIAS SDL2_mixer) find_library(ndk_log log) find_library(ndk_zlib z) @@ -90,18 +90,17 @@ else() # non-emscripten, non-android include(FindPkgConfig) pkg_check_modules(SDL2 REQUIRED IMPORTED_TARGET sdl2>=2.18.0) - pkg_check_modules(SDL2TTF REQUIRED IMPORTED_TARGET SDL2_ttf) + pkg_check_modules(FREETYPE REQUIRED IMPORTED_TARGET freetype2) pkg_check_modules(SDL2MIXER REQUIRED IMPORTED_TARGET SDL2_mixer) if (WIN32) add_static_library(sdl2 SDL2) - add_static_library(sdl2_ttf SDL2TTF) - # Workaround for linking error - set_property(TARGET sdl2_ttf PROPERTY INTERFACE_LINK_LIBRARIES - $) + add_static_library(freetype2 FREETYPE) + # harfbuzz and graphite2, which the static freetype depends on, are C++ libraries. + target_link_libraries(freetype2 INTERFACE -lstdc++) add_static_library(sdl2_mixer SDL2MIXER) else() add_library(sdl2 ALIAS PkgConfig::SDL2) - add_library(sdl2_ttf ALIAS PkgConfig::SDL2TTF) + add_library(freetype2 ALIAS PkgConfig::FREETYPE) add_library(sdl2_mixer ALIAS PkgConfig::SDL2MIXER) set(DEFAULT_FONT_PATH ${CMAKE_INSTALL_PREFIX}/share/xsystem35/fonts/) endif() diff --git a/README.md b/README.md index 00e8098..d79ce69 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ See [xsystem35 command manual](doc/xsystem35.6.adoc) for detailed usage. ### Linux (Debian / Ubuntu) ```bash -$ sudo apt install build-essential cmake libsdl2-dev libsdl2-ttf-dev libsdl2-mixer-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor +$ sudo apt install build-essential cmake libsdl2-dev libsdl2-mixer-dev libfreetype-dev libwebp-dev libportmidi-dev libcjson-dev asciidoctor $ mkdir -p out/debug $ cd out/debug $ cmake -DCMAKE_BUILD_TYPE=Debug ../../ @@ -117,7 +117,7 @@ $ make && make install [Homebrew](https://brew.sh/) is required. ```bash -$ brew install cmake pkg-config sdl2 sdl2_mixer sdl2_ttf webp portmidi cjson asciidoctor +$ brew install cmake pkg-config sdl2 sdl2_mixer freetype webp portmidi cjson asciidoctor $ mkdir -p out/debug $ cd out/debug $ cmake -DCMAKE_BUILD_TYPE=Debug ../../ @@ -129,7 +129,7 @@ $ make && make install [MSYS2](https://www.msys2.org) is required. ```bash -$ pacman -S cmake mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-SDL2 mingw-w64-ucrt-x86_64-SDL2_ttf mingw-w64-ucrt-x86_64-SDL2_mixer mingw-w64-ucrt-x86_64-libwebp mingw-w64-ucrt-x86_64-portmidi mingw-w64-ucrt-x86_64-cjson +$ pacman -S cmake mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-SDL2 mingw-w64-ucrt-x86_64-freetype mingw-w64-ucrt-x86_64-SDL2_mixer mingw-w64-ucrt-x86_64-libwebp mingw-w64-ucrt-x86_64-portmidi mingw-w64-ucrt-x86_64-cjson $ mkdir -p out/debug $ cd out/debug $ cmake -G"MSYS Makefiles" -DCMAKE_BUILD_TYPE=Debug ../../ diff --git a/android/app/jni/CMakeLists.txt b/android/app/jni/CMakeLists.txt index 065a422..d93f46c 100644 --- a/android/app/jni/CMakeLists.txt +++ b/android/app/jni/CMakeLists.txt @@ -11,9 +11,10 @@ FetchContent_Declare( URL_HASH SHA1=ce98fa93e31836a751feca374ab28a0770b63c16 ) FetchContent_Declare( - SDL_ttf - URL https://github.com/libsdl-org/SDL_ttf/releases/download/release-2.22.0/SDL2_ttf-2.22.0.tar.gz - URL_HASH SHA1=da5e86b601ad299a697878fab1af6f3be47b529d + freetype + URL https://downloads.sourceforge.net/project/freetype/freetype2/2.14.3/freetype-2.14.3.tar.gz + https://download.savannah.gnu.org/releases/freetype/freetype-2.14.3.tar.gz + URL_HASH SHA1=a911f65c6355ddcf7aaba46e250dbc00f67e1678 ) FetchContent_Declare( SDL_mixer @@ -21,9 +22,12 @@ FetchContent_Declare( URL_HASH SHA1=a58c69f9d00e44833b9e00e1adb58d85759ca499 ) -set(SDL2TTF_SAMPLES OFF CACHE BOOL "Build the SDL2_ttf sample program(s)" FORCE) -set(SDL2TTF_INSTALL OFF CACHE BOOL "Enable SDL2_ttf install target" FORCE) -set(SDL2TTF_VENDORED ON CACHE BOOL "Use vendored third-party libraries" FORCE) +# Only FreeType's own rasterizer is needed, none of its optional dependencies. +set(FT_DISABLE_ZLIB ON CACHE BOOL "Disable use of system zlib" FORCE) +set(FT_DISABLE_BZIP2 ON CACHE BOOL "Disable use of system libbz2" FORCE) +set(FT_DISABLE_PNG ON CACHE BOOL "Disable use of system libpng" FORCE) +set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "Disable use of harfbuzz" FORCE) +set(FT_DISABLE_BROTLI ON CACHE BOOL "Disable use of brotli" FORCE) set(SDL2MIXER_OPUS OFF CACHE BOOL "Enable Opus music" FORCE) set(SDL2MIXER_FLAC OFF CACHE BOOL "Enable FLAC music" FORCE) @@ -33,7 +37,7 @@ set(SDL2MIXER_WAVPACK OFF CACHE BOOL "Enable WavPack music" FORCE) set(SDL2MIXER_SAMPLES OFF CACHE BOOL "Build the SDL2_mixer sample program(s)" FORCE) set(SDL2MIXER_INSTALL OFF CACHE BOOL "Enable SDL2_mixer install target" FORCE) -FetchContent_MakeAvailable(SDL SDL_ttf SDL_mixer) +FetchContent_MakeAvailable(SDL freetype SDL_mixer) # The main CMakeLists.txt of xsystem35 add_subdirectory(${PROJECT_ROOT_DIR} xsystem35) @@ -49,7 +53,5 @@ file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/mincho.txt ${ASSETS_DIR}/licenses/mi file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/nanojpeg.txt ${ASSETS_DIR}/licenses/nanojpeg) file(COPY_FILE ${PROJECT_ROOT_DIR}/licenses/microui.txt ${ASSETS_DIR}/licenses/microui) file(COPY_FILE ${sdl_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL) -file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL_ttf) -file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/external/freetype/docs/GPLv2.TXT ${ASSETS_DIR}/licenses/freetype) -file(COPY_FILE ${sdl_ttf_SOURCE_DIR}/external/harfbuzz/COPYING ${ASSETS_DIR}/licenses/harfbuzz) +file(COPY_FILE ${freetype_SOURCE_DIR}/docs/GPLv2.TXT ${ASSETS_DIR}/licenses/freetype) file(COPY_FILE ${sdl_mixer_SOURCE_DIR}/LICENSE.txt ${ASSETS_DIR}/licenses/SDL_mixer) diff --git a/android/app/src/main/java/io/github/kichikuou/xsystem35/LicensesMenuActivity.kt b/android/app/src/main/java/io/github/kichikuou/xsystem35/LicensesMenuActivity.kt index 4050b81..65303b5 100644 --- a/android/app/src/main/java/io/github/kichikuou/xsystem35/LicensesMenuActivity.kt +++ b/android/app/src/main/java/io/github/kichikuou/xsystem35/LicensesMenuActivity.kt @@ -12,11 +12,9 @@ class LicensesMenuActivity : Activity() { Entry("xsystem35-sdl2", "xsystem35", "https://github.com/kichikuou/xsystem35-sdl2"), Entry("SDL", "SDL", "https://www.libsdl.org/"), Entry("SDL_mixer", "SDL_mixer", "https://github.com/libsdl-org/SDL_mixer"), - Entry("SDL_ttf", "SDL_ttf", "https://github.com/libsdl-org/SDL_ttf"), Entry("NanoJPEG", "nanojpeg", "https://keyj.emphy.de/nanojpeg/"), Entry("microui", "microui", "https://github.com/rxi/microui"), Entry("FreeType", "freetype", "https://freetype.org/"), - Entry("HarfBuzz", "harfbuzz", "https://harfbuzz.github.io/"), Entry("MotoyaLCedar W3 mono", "MTLc3m", "https://github.com/aosp-mirror/platform_frameworks_base/tree/lollipop-release/data/fonts"), Entry("IPA明朝", "mincho", "https://moji.or.jp/ipafont/"), ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 94fa7a1..fd657ca 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -146,7 +146,7 @@ if (NOT HAVE_LIBINTL) endif() target_link_libraries(src_lib PRIVATE sdl2 zlib) -target_link_libraries(xsystem35 PRIVATE m zlib sdl2 sdl2_ttf) +target_link_libraries(xsystem35 PRIVATE m zlib sdl2 freetype2) if (TARGET sdl2_mixer) target_link_libraries(xsystem35 PRIVATE sdl2_mixer) endif() diff --git a/src/font.c b/src/font.c index c97f1fd..3caeca6 100644 --- a/src/font.c +++ b/src/font.c @@ -19,10 +19,14 @@ */ #include "config.h" -#include -#include +#include #include -#include + +#include +#include FT_FREETYPE_H +#include FT_BITMAP_H +#include FT_OUTLINE_H +#include FT_TRUETYPE_TABLES_H #ifdef _WIN32 #include "win/resources.h" @@ -35,130 +39,253 @@ #include "portab.h" #include "system.h" #include "font.h" +#include "utfsjis.h" + +// 26.6 fixed point helpers. +#define FT_FLOOR(x) ((x) >> 6) +#define FT_CEIL(x) (((x) + 63) >> 6) typedef struct { - int size; - FontType type; - TTF_Font *id; -} FontTable; + FT_Face face; + int ascent; // distance from the top of a rendered surface to the baseline + int height; // height of a rendered surface +} Font; -#define FONTTABLEMAX 256 -static FontTable fonttbl[FONTTABLEMAX]; -static int fontcnt = 0; +static FT_Library ft_library; static struct { bool antialiase_on; const char *name[FONTTYPEMAX]; - int face[FONTTYPEMAX]; + int index[FONTTYPEMAX]; + FT_Face face[FONTTYPEMAX]; // opened on first use, shared between all sizes } this; -static void font_insert(int size, FontType type, TTF_Font *font) { - fonttbl[fontcnt].size = size; - fonttbl[fontcnt].type = type; - fonttbl[fontcnt].id = font; - - if (fontcnt >= (FONTTABLEMAX -1)) { - WARNING("Font table is full."); - } else { - fontcnt++; - } -} - -static FontTable *font_lookup(int size, FontType type) { - int i; - - for (i = 0; i < fontcnt; i++) { - if (fonttbl[i].size == size && fonttbl[i].type == type) { - return &fonttbl[i]; - } - } - return NULL; -} - -// Resolve a (type, size) pair to a TTF_Font, opening and caching it on first -// use, and apply the requested weight. Returns NULL for an invalid type. -static FontTable *font_resolve(FontSpec font) { - FontType type = font.type; - int size = font.size; - - if (type >= FONTTYPEMAX) { - WARNING("Invalid font type %d", type); +#if defined(_WIN32) || defined(__ANDROID__) +// Create a face from the whole content of `rw`. The memory holding the font +// file must outlive the face, and faces are never closed, so it is never freed. +static FT_Face face_from_rwops(SDL_RWops *rw, int index) { + Sint64 size = SDL_RWsize(rw); + if (size <= 0) { + SDL_RWclose(rw); return NULL; } + FT_Byte *buf = malloc(size); + if (SDL_RWread(rw, buf, 1, size) != (size_t)size) { + free(buf); + SDL_RWclose(rw); + return NULL; + } + SDL_RWclose(rw); - FontTable *tbl; - if (NULL == (tbl = font_lookup(size, type))) { - TTF_Font *fs = TTF_OpenFontIndex(this.name[type], size, this.face[type]); -#ifdef __ANDROID__ - // If `this.name[type]` is a custom font file specified in .xys35rc, - // SDL_RWFromFile used by TTF_OpenFontIndex does not work because it - // does not resolve relative path with the current directory. On the - // other hand, we can't just use fopen because SDL_RWFromFile can open - // apk assets and the default fonts are stored as assets. - if (!fs) { - FILE *fp = fopen(this.name[type], "r"); - if (fp) - fs = TTF_OpenFontIndexRW(SDL_RWFromFP(fp, true), true, size, this.face[type]); - } + FT_Face face; + if (FT_New_Memory_Face(ft_library, buf, size, index, &face)) { + free(buf); + return NULL; + } + return face; +} #endif + +static FT_Face open_face(const char *name, int index) { + FT_Face face = NULL; + #ifdef _WIN32 - SDL_RWops *r = open_resource(this.name[type], "fonts"); - if (r) - fs = TTF_OpenFontIndexRW(r, true, size, this.face[type]); + SDL_RWops *res = open_resource(name, "fonts"); + if (res) + face = face_from_rwops(res, index); #endif - if (!fs) - SYSERROR("Cannot open font %s", this.name[type]); - - font_insert(size, type, fs); - tbl = &fonttbl[fontcnt - 1]; + if (!face && FT_New_Face(ft_library, name, index, &face)) + face = NULL; // FT_New_Face() does not clear `face` on failure. +#ifdef __ANDROID__ + // The default fonts are stored as apk assets, which can only be opened + // through SDL_RWFromFile. (It is not used as the first choice because it + // does not resolve a relative path against the current directory, which a + // custom font specified in .xsys35rc may use.) + if (!face) { + SDL_RWops *rw = SDL_RWFromFile(name, "rb"); + if (rw) + face = face_from_rwops(rw, index); } - TTF_SetFontStyle(tbl->id, font.weight == FONT_WEIGHT_BOLD ? TTF_STYLE_BOLD : TTF_STYLE_NORMAL); - return tbl; +#endif + if (face) { + // Prefer a Unicode charmap; if there is none, keep FreeType's choice. + FT_Select_Charmap(face, FT_ENCODING_UNICODE); + } + return face; } -SDL_Surface *font_render_text(FontSpec font, const char *str_utf8, SDL_Color color, bool antialias) { - FontTable *fontset = font_resolve(font); - if (!fontset) +// Since a face is shared between all sizes of a FontType, the size has to be +// applied on every call. +static bool font_select(FontSpec spec, Font *font) { + if (spec.type >= FONTTYPEMAX) { + WARNING("Invalid font type %d", spec.type); + return false; + } + if (!this.face[spec.type]) { + this.face[spec.type] = open_face(this.name[spec.type], this.index[spec.type]); + if (!this.face[spec.type]) + SYSERROR("Cannot open font %s", this.name[spec.type]); + } + FT_Face face = this.face[spec.type]; + + // With a resolution of 0 (i.e. the default 72dpi), 1pt equals 1px. + if (FT_Set_Char_Size(face, 0, spec.size * 64, 0, 0) && + FT_Set_Pixel_Sizes(face, 0, spec.size)) { + WARNING("Cannot set the size of font %s to %d", this.name[spec.type], spec.size); + return false; + } + font->face = face; + font->ascent = FT_CEIL(face->size->metrics.ascender); + font->height = font->ascent - FT_FLOOR(face->size->metrics.descender); + return true; +} + +// Distance from the top of the character cell to the baseline, the same value +// GDI reports as TEXTMETRIC::tmAscent. The original engine positions text by +// the cell top, so this determines where a glyph lands on the screen. +static int cell_ascent(const Font *font) { + TT_OS2 *os2 = FT_Get_Sfnt_Table(font->face, FT_SFNT_OS2); + if (!os2 || os2->version == 0xffff || !os2->usWinAscent) + return font->ascent; + int upem = font->face->units_per_EM; + return (os2->usWinAscent * font->face->size->metrics.y_ppem + upem / 2) / upem; +} + +int font_cell_overhang(FontSpec spec) { + Font font; + if (!font_select(spec, &font)) + return 0; + return font.ascent - cell_ascent(&font); +} + +static bool load_glyph(FT_Face face, int code, bool bold) { + if (FT_Load_Char(face, code, FT_LOAD_DEFAULT)) + return false; + if (bold) { + // Approximate GDI's synthetic bold at small sizes: grow only to the + // right by 1px, without changing the advance. Expand outlines before + // rasterization so antialiased edges are generated only once. + FT_GlyphSlot slot = face->glyph; + if (slot->format == FT_GLYPH_FORMAT_OUTLINE) { + if (FT_Outline_EmboldenXY(&slot->outline, 64, 0)) + return false; + } else if (slot->format == FT_GLYPH_FORMAT_BITMAP) { + if (FT_GlyphSlot_Own_Bitmap(slot) || + FT_Bitmap_Embolden(ft_library, &slot->bitmap, 64, 0)) + return false; + } + } + return true; +} + +// Total advance of `str_utf8`, in pixels. A negative `len` means the whole +// string, otherwise only its first `len` bytes are measured. +static int text_width(FT_Face face, const char *str_utf8, int len) { + const char *end = len < 0 ? NULL : str_utf8 + len; + int pen = 0; // 26.6 + + while (*str_utf8 && (!end || str_utf8 < end)) { + if (load_glyph(face, utf8_next_codepoint(&str_utf8), false)) + pen += face->glyph->advance.x; + } + return FT_CEIL(pen); +} + +// Composite a rendered glyph into `dst`, which is either an ARGB8888 surface +// (the glyph is drawn in `color`, its coverage scaled by `color.a` becoming the +// alpha value) or an INDEX8 surface (covered pixels are set to the index 1). +static void blit_glyph(const FT_Bitmap *bmp, SDL_Surface *dst, int x, int y, SDL_Color color) { + int bpp = dst->format->BytesPerPixel; + uint32_t rgb = color.r << 16 | color.g << 8 | color.b; + + for (unsigned int row = 0; row < bmp->rows; row++) { + int dy = y + row; + if (dy < 0 || dy >= dst->h) + continue; + const uint8_t *src = bmp->buffer + row * bmp->pitch; + uint8_t *dst_row = (uint8_t *)dst->pixels + dy * dst->pitch; + for (unsigned int col = 0; col < bmp->width; col++) { + int dx = x + col; + if (dx < 0 || dx >= dst->w) + continue; + uint8_t coverage; + switch (bmp->pixel_mode) { + case FT_PIXEL_MODE_MONO: + coverage = src[col >> 3] & (0x80 >> (col & 7)) ? 255 : 0; + break; + case FT_PIXEL_MODE_GRAY: + coverage = src[col]; + break; + default: + continue; + } + if (!coverage) + continue; + uint8_t *dp = dst_row + dx * bpp; + if (bpp == 4) + *(uint32_t *)dp = (uint32_t)(coverage * color.a / 255) << 24 | rgb; + else + *dp = 1; + } + } +} + +SDL_Surface *font_render_text(FontSpec spec, const char *str_utf8, SDL_Color color, bool antialias) { + Font font; + if (!font_select(spec, &font)) return NULL; + bool bold = spec.weight == FONT_WEIGHT_BOLD; + int width = max(text_width(font.face, str_utf8, -1), 1); - SDL_Surface *fs = antialias - ? TTF_RenderUTF8_Blended(fontset->id, str_utf8, color) - : TTF_RenderUTF8_Solid(fontset->id, str_utf8, color); - if (!fs) - WARNING("Text rendering failed: %s", TTF_GetError()); + SDL_Surface *sf; + if (antialias) { + sf = SDL_CreateRGBSurfaceWithFormat(0, width, font.height, 32, SDL_PIXELFORMAT_ARGB8888); + if (sf) + SDL_SetSurfaceBlendMode(sf, SDL_BLENDMODE_BLEND); + } else { + sf = SDL_CreateRGBSurfaceWithFormat(0, width, font.height, 8, SDL_PIXELFORMAT_INDEX8); + if (sf) { + SDL_Color pal[2] = {{0, 0, 0, 0}, {color.r, color.g, color.b, 255}}; + SDL_SetPaletteColors(sf->format->palette, pal, 0, 2); + SDL_SetColorKey(sf, SDL_TRUE, 0); + } + } + if (!sf) { + WARNING("Text rendering failed: %s", SDL_GetError()); + return NULL; + } - return fs; + int pen = 0; // 26.6 + while (*str_utf8) { + if (!load_glyph(font.face, utf8_next_codepoint(&str_utf8), bold)) + continue; + FT_GlyphSlot slot = font.face->glyph; + if (!FT_Render_Glyph(slot, antialias ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO)) { + blit_glyph(&slot->bitmap, sf, FT_FLOOR(pen) + slot->bitmap_left, + font.ascent - slot->bitmap_top, color); + } + pen += slot->advance.x; + } + return sf; } -void font_measure_text(FontSpec font, const char *str_utf8, int len, int *w, int *h) { +void font_measure_text(FontSpec spec, const char *str_utf8, int len, int *w, int *h) { if (w) *w = 0; if (h) *h = 0; - FontTable *fontset = font_resolve(font); - if (!fontset) + Font font; + if (!font_select(spec, &font)) return; if (h) - *h = TTF_FontHeight(fontset->id); - if (!w) - return; - - // A negative length means the whole string. - if (len < 0) { - TTF_SizeUTF8(fontset->id, str_utf8, w, NULL); - } else { - char buf[256]; - if (len > (int)sizeof(buf) - 1) - len = sizeof(buf) - 1; - memcpy(buf, str_utf8, len); - buf[len] = '\0'; - TTF_SizeUTF8(fontset->id, buf, w, NULL); - } + *h = font.height; + if (w) + *w = text_width(font.face, str_utf8, len); } void font_init(void) { this.antialiase_on = false; - - if (TTF_Init() == -1) - SYSERROR("Failed to intialize SDL_ttf: %s", TTF_GetError()); + + if (FT_Init_FreeType(&ft_library)) + SYSERROR("Failed to initialize FreeType"); } void font_set_name_and_index(FontType type, const char *name, int index) { @@ -167,7 +294,7 @@ void font_set_name_and_index(FontType type, const char *name, int index) { return; } this.name[type] = name; - this.face[type] = index; + this.index[type] = index; } void font_set_antialias(bool enable) { diff --git a/src/font.h b/src/font.h index e2d7f0a..7f0c453 100644 --- a/src/font.h +++ b/src/font.h @@ -49,8 +49,11 @@ extern void font_init(void); extern void font_set_name_and_index(FontType type, const char *name, int index); extern void font_set_antialias(bool enable); extern bool font_get_antialias(void); -extern struct SDL_Surface *font_render_text(FontSpec font, const char *str_utf8, SDL_Color color, bool antialias); -extern void font_measure_text(FontSpec font, const char *str_utf8, int len, int *w, int *h); +extern struct SDL_Surface *font_render_text(FontSpec spec, const char *str_utf8, SDL_Color color, bool antialias); +extern void font_measure_text(FontSpec spec, const char *str_utf8, int len, int *w, int *h); +// Rows by which a rendered surface extends above the character cell. Subtract +// it from a Y coordinate that denotes the cell top before blitting. +extern int font_cell_overhang(FontSpec spec); #ifdef __EMSCRIPTEN__ extern bool load_mincho_font(void); diff --git a/src/gfx_draw.c b/src/gfx_draw.c index 14272dc..1d68599 100644 --- a/src/gfx_draw.c +++ b/src/gfx_draw.c @@ -336,10 +336,7 @@ SDL_Rect gfx_drawString(int x, int y, const char *str_utf8, uint8_t col, FontSpe if (!fs) return r_dst; - // Center vertically to the box. - int font_height; - font_measure_text(font, "", -1, NULL, &font_height); - y -= (font_height - font.size) / 2; + y -= font_cell_overhang(font); r_dst = (SDL_Rect){x, y, fs->w, fs->h}; if (main_surface->format->BitsPerPixel == 8 && antialias) {