mirror of https://github.com/mgba-emu/mgba.git
Util: Add basic text rendering, expose to scripting
This commit is contained in:
parent
8a4750f4fe
commit
67b5a51614
|
@ -60,6 +60,7 @@ if(NOT LIBMGBA_ONLY)
|
|||
set(USE_ELF ON CACHE BOOL "Whether or not to enable ELF support")
|
||||
set(USE_LUA ON CACHE BOOL "Whether or not to enable Lua scripting support")
|
||||
set(USE_JSON_C ON CACHE BOOL "Whether or not to enable JSON-C support")
|
||||
set(USE_FREETYPE ON CACHE BOOL "Whether or not to enable font rendering for scripts")
|
||||
set(M_CORE_GBA ON CACHE BOOL "Build Game Boy Advance core")
|
||||
set(M_CORE_GB ON CACHE BOOL "Build Game Boy core")
|
||||
set(USE_LZMA ON CACHE BOOL "Whether or not to enable 7-Zip support")
|
||||
|
@ -486,6 +487,7 @@ endif()
|
|||
if(DISABLE_DEPS)
|
||||
set(ENABLE_GDB_STUB OFF)
|
||||
set(USE_DISCORD_RPC OFF)
|
||||
set(USE_FREETYPE OFF)
|
||||
set(USE_JSON_C OFF)
|
||||
set(USE_SQLITE3 OFF)
|
||||
set(USE_PNG OFF)
|
||||
|
@ -514,6 +516,7 @@ find_feature(USE_EPOXY "epoxy")
|
|||
find_feature(USE_CMOCKA "cmocka")
|
||||
find_feature(USE_SQLITE3 "SQLite3|sqlite3")
|
||||
find_feature(USE_ELF "libelf")
|
||||
find_feature(USE_FREETYPE "Freetype")
|
||||
find_feature(ENABLE_PYTHON "PythonLibs")
|
||||
|
||||
# Features
|
||||
|
@ -763,6 +766,12 @@ if(USE_ELF)
|
|||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libelf1")
|
||||
endif()
|
||||
|
||||
if (USE_FREETYPE)
|
||||
list(APPEND FEATURES FREETYPE)
|
||||
list(APPEND DEPENDENCY_LIB Freetype::Freetype)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libfreetype6")
|
||||
endif()
|
||||
|
||||
if (USE_DISCORD_RPC)
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.7")
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/discord-rpc discord-rpc EXCLUDE_FROM_ALL)
|
||||
|
@ -1343,6 +1352,7 @@ if(NOT QUIET AND NOT LIBMGBA_ONLY)
|
|||
message(STATUS " Lua: ${USE_LUA}")
|
||||
endif()
|
||||
message(STATUS " storage API: ${USE_JSON_C}")
|
||||
message(STATUS " Font rendering: ${USE_FREETYPE}")
|
||||
endif()
|
||||
message(STATUS "Frontends:")
|
||||
message(STATUS " Qt: ${BUILD_QT}")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
/* Copyright (c) 2013-2025 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
|
@ -101,8 +101,10 @@ struct mImage {
|
|||
enum mColorFormat format;
|
||||
};
|
||||
|
||||
struct mFont;
|
||||
struct mPainter {
|
||||
struct mImage* backing;
|
||||
struct mFont* font;
|
||||
bool blend;
|
||||
bool fill;
|
||||
unsigned strokeWidth;
|
||||
|
@ -110,6 +112,20 @@ struct mPainter {
|
|||
uint32_t fillColor;
|
||||
};
|
||||
|
||||
enum mAlignment {
|
||||
mALIGN_LEFT = 0x01,
|
||||
mALIGN_HCENTER = 0x02,
|
||||
mALIGN_RIGHT = 0x03,
|
||||
|
||||
mALIGN_TOP = 0x10,
|
||||
mALIGN_VCENTER = 0x20,
|
||||
mALIGN_BOTTOM = 0x30,
|
||||
mALIGN_BASELINE = 0x40,
|
||||
|
||||
mALIGN_HORIZONTAL = 0x03,
|
||||
mALIGN_VERTICAL = 0x70,
|
||||
};
|
||||
|
||||
struct VFile;
|
||||
struct mImage* mImageCreate(unsigned width, unsigned height, enum mColorFormat format);
|
||||
struct mImage* mImageCreateWithStride(unsigned width, unsigned height, unsigned stride, enum mColorFormat format);
|
||||
|
@ -147,6 +163,17 @@ void mPainterDrawMask(struct mPainter*, const struct mImage* mask, int x, int y)
|
|||
uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to);
|
||||
uint32_t mImageColorConvert(uint32_t color, const struct mImage* from, enum mColorFormat to);
|
||||
|
||||
#ifdef USE_FREETYPE
|
||||
struct mFont* mFontOpen(const char* path);
|
||||
void mFontDestroy(struct mFont*);
|
||||
|
||||
unsigned mFontSize(const struct mFont*);
|
||||
void mFontSetSize(struct mFont*, unsigned px);
|
||||
int mFontSpanWidth(struct mFont*, const char* text);
|
||||
|
||||
void mPainterDrawText(struct mPainter*, const char* text, int x, int y, enum mAlignment);
|
||||
#endif
|
||||
|
||||
#ifndef PYCPARSE
|
||||
static inline unsigned mColorFormatBytes(enum mColorFormat format) {
|
||||
switch (format) {
|
||||
|
|
|
@ -99,6 +99,10 @@
|
|||
#cmakedefine USE_FFMPEG
|
||||
#endif
|
||||
|
||||
#ifndef USE_FREETYPE
|
||||
#cmakedefine USE_FREETYPE
|
||||
#endif
|
||||
|
||||
#ifndef USE_JSON_C
|
||||
#cmakedefine USE_JSON_C
|
||||
#endif
|
||||
|
|
|
@ -58,6 +58,10 @@
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef USE_FREETYPE
|
||||
#include <freetype/freetype.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIBZIP
|
||||
#include <zip.h>
|
||||
#endif
|
||||
|
@ -149,6 +153,11 @@ void ReportView::generateReport() {
|
|||
#else
|
||||
swReport << QString("FFmpeg not linked");
|
||||
#endif
|
||||
#ifdef USE_FREETYPE
|
||||
swReport << QString("FreeType version: %1.%2.%3").arg(FREETYPE_MAJOR).arg(FREETYPE_MINOR).arg(FREETYPE_PATCH);
|
||||
#else
|
||||
swReport << QString("FreeType not linked");
|
||||
#endif
|
||||
#ifdef USE_EDITLINE
|
||||
swReport << QString("libedit version: %1.%2").arg(LIBEDIT_MAJOR).arg(LIBEDIT_MINOR);
|
||||
#else
|
||||
|
|
|
@ -124,6 +124,34 @@ void _mPainterSetStrokeColor(struct mPainter* painter, uint32_t color) {
|
|||
painter->strokeColor = color;
|
||||
}
|
||||
|
||||
#ifdef USE_FREETYPE
|
||||
void _mPainterLoadFont(struct mPainter* painter, const char* path) {
|
||||
struct mFont* font = mFontOpen(path);
|
||||
if (!font) {
|
||||
return;
|
||||
}
|
||||
if (painter->font) {
|
||||
mFontDestroy(painter->font);
|
||||
}
|
||||
painter->font = font;
|
||||
}
|
||||
|
||||
void _mPainterSetFontSize(struct mPainter* painter, float pt) {
|
||||
if (!painter->font) {
|
||||
return;
|
||||
}
|
||||
mFontSetSize(painter->font, pt * 64);
|
||||
}
|
||||
|
||||
|
||||
float _mPainterTextSpanWidth(struct mPainter* painter, const char* text) {
|
||||
if (!painter->font) {
|
||||
return 0;
|
||||
}
|
||||
return mFontSpanWidth(painter->font, text) / 64.f;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct mScriptValue* _mScriptPainterGet(struct mScriptPainter* painter, const char* name) {
|
||||
struct mScriptValue val;
|
||||
struct mScriptValue realPainter = mSCRIPT_MAKE(S(mPainter), &painter->painter);
|
||||
|
@ -139,6 +167,11 @@ static struct mScriptValue* _mScriptPainterGet(struct mScriptPainter* painter, c
|
|||
|
||||
void _mScriptPainterDeinit(struct mScriptPainter* painter) {
|
||||
mScriptValueDeref(painter->image);
|
||||
#ifdef USE_FREETYPE
|
||||
if (painter->painter.font) {
|
||||
mFontDestroy(painter->painter.font);
|
||||
}
|
||||
#endif
|
||||
free(painter);
|
||||
}
|
||||
|
||||
|
@ -151,6 +184,12 @@ mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mPainter, drawRectangle, mPainterDrawRectangl
|
|||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mPainter, drawLine, mPainterDrawLine, 4, S32, x1, S32, y1, S32, x2, S32, y2);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mPainter, drawCircle, mPainterDrawCircle, 3, S32, x, S32, y, S32, diameter);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mPainter, drawMask, mPainterDrawMask, 3, CS(mImage), mask, S32, x, S32, y);
|
||||
#ifdef USE_FREETYPE
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mPainter, drawText, mPainterDrawText, 4, CHARP, text, S32, x, S32, y, S32, alignment);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mPainter, loadFont, _mPainterLoadFont, 1, CHARP, path);
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mPainter, setFontSize, _mPainterSetFontSize, 1, U32, pt);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mPainter, F32, textSpanWidth, _mPainterTextSpanWidth, 1, CHARP, text);
|
||||
#endif
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT(mPainter)
|
||||
mSCRIPT_DEFINE_CLASS_DOCSTRING(
|
||||
|
@ -179,6 +218,16 @@ mSCRIPT_DEFINE_STRUCT(mPainter)
|
|||
"target color and use this function to draw it into a destination image."
|
||||
)
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mPainter, drawMask)
|
||||
#ifdef USE_FREETYPE
|
||||
mSCRIPT_DEFINE_DOCSTRING("Draw text with the currently set font and fill color")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mPainter, drawText)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Load a font from a given filename")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mPainter, loadFont)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Set the font size")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mPainter, setFontSize)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Get the pixel width of a span of text in the current font")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mPainter, textSpanWidth)
|
||||
#endif
|
||||
mSCRIPT_DEFINE_END;
|
||||
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mScriptPainter, W(mPainter), _get, _mScriptPainterGet, 1, CHARP, name);
|
||||
|
@ -206,4 +255,15 @@ void mScriptContextAttachImage(struct mScriptContext* context) {
|
|||
mScriptContextSetDocstring(context, "image.load", "Load an image from a path. Currently, only `PNG` format is supported");
|
||||
#endif
|
||||
mScriptContextSetDocstring(context, "image.newPainter", "Create a new painter from an existing image");
|
||||
|
||||
mScriptContextExportConstants(context, "ALIGN", (struct mScriptKVPair[]) {
|
||||
mSCRIPT_CONSTANT_PAIR(mALIGN, LEFT),
|
||||
mSCRIPT_CONSTANT_PAIR(mALIGN, HCENTER),
|
||||
mSCRIPT_CONSTANT_PAIR(mALIGN, RIGHT),
|
||||
mSCRIPT_CONSTANT_PAIR(mALIGN, TOP),
|
||||
mSCRIPT_CONSTANT_PAIR(mALIGN, VCENTER),
|
||||
mSCRIPT_CONSTANT_PAIR(mALIGN, BOTTOM),
|
||||
mSCRIPT_CONSTANT_PAIR(mALIGN, BASELINE),
|
||||
mSCRIPT_KV_SENTINEL
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ set(SOURCE_FILES
|
|||
geometry.c
|
||||
image.c
|
||||
image/export.c
|
||||
image/font.c
|
||||
image/png-io.c
|
||||
interpolator.c
|
||||
patch.c
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
/* Copyright (c) 2013-2025 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba-util/image.h>
|
||||
|
||||
#ifdef USE_FREETYPE
|
||||
|
||||
#include <mgba-util/string.h>
|
||||
|
||||
#include <freetype/freetype.h>
|
||||
|
||||
#define DPI 100
|
||||
|
||||
static _Atomic size_t libraryOpen = 0;
|
||||
static FT_Library library;
|
||||
|
||||
struct mFont {
|
||||
FT_Face face;
|
||||
unsigned emHeight;
|
||||
};
|
||||
|
||||
static void _makeTemporaryImage(struct mImage* out, FT_Bitmap* in) {
|
||||
out->data = in->buffer;
|
||||
out->width = in->width;
|
||||
out->height = in->rows;
|
||||
out->palette = NULL;
|
||||
if (in->pixel_mode == FT_PIXEL_MODE_GRAY) {
|
||||
out->stride = in->pitch;
|
||||
out->format = mCOLOR_L8;
|
||||
out->depth = 1;
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
struct mFont* mFontOpen(const char* path) {
|
||||
size_t opened = ++libraryOpen;
|
||||
if (opened == 1) {
|
||||
if (FT_Init_FreeType(&library)) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
FT_Face face;
|
||||
if (FT_New_Face(library, path, 0, &face)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mFont* font = calloc(1, sizeof(*font));
|
||||
font->face = face;
|
||||
mFontSetSize(font, 8 * 64);
|
||||
return font;
|
||||
}
|
||||
|
||||
void mFontDestroy(struct mFont* font) {
|
||||
FT_Done_Face(font->face);
|
||||
free(font);
|
||||
|
||||
size_t opened = --libraryOpen;
|
||||
if (opened == 0) {
|
||||
FT_Done_FreeType(library);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned mFontSize(const struct mFont* font) {
|
||||
return font->emHeight;
|
||||
}
|
||||
|
||||
void mFontSetSize(struct mFont* font, unsigned pt) {
|
||||
font->emHeight = pt;
|
||||
FT_Set_Char_Size(font->face, 0, pt, DPI, DPI);
|
||||
}
|
||||
|
||||
int mFontSpanWidth(struct mFont* font, const char* text) {
|
||||
FT_Face face = font->face;
|
||||
uint32_t lastGlyph = 0;
|
||||
int width = 0;
|
||||
|
||||
while (*text) {
|
||||
uint32_t glyph = utf8Char((const char**) &text, NULL);
|
||||
|
||||
if (FT_Load_Char(face, glyph, FT_LOAD_DEFAULT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FT_Vector kerning = {0};
|
||||
FT_Get_Kerning(face, lastGlyph, glyph, FT_KERNING_DEFAULT, &kerning);
|
||||
width += kerning.x;
|
||||
width += face->glyph->advance.x;
|
||||
|
||||
lastGlyph = glyph;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
void mPainterDrawText(struct mPainter* painter, const char* text, int x, int y, enum mAlignment alignment) {
|
||||
FT_Face face = painter->font->face;
|
||||
uint32_t lastGlyph = 0;
|
||||
|
||||
x <<= 6;
|
||||
y <<= 6;
|
||||
|
||||
switch (alignment & mALIGN_VERTICAL) {
|
||||
case mALIGN_TOP:
|
||||
y += face->size->metrics.ascender;
|
||||
break;
|
||||
case mALIGN_BASELINE:
|
||||
break;
|
||||
case mALIGN_VCENTER:
|
||||
y += face->size->metrics.ascender - face->size->metrics.height / 2;
|
||||
break;
|
||||
case mALIGN_BOTTOM:
|
||||
default:
|
||||
y += face->size->metrics.descender;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (alignment & mALIGN_HORIZONTAL) {
|
||||
case mALIGN_LEFT:
|
||||
default:
|
||||
break;
|
||||
case mALIGN_HCENTER:
|
||||
x -= mFontSpanWidth(painter->font, text) >> 1;
|
||||
break;
|
||||
case mALIGN_RIGHT:
|
||||
x -= mFontSpanWidth(painter->font, text);
|
||||
break;
|
||||
}
|
||||
|
||||
while (*text) {
|
||||
uint32_t glyph = utf8Char((const char**) &text, NULL);
|
||||
|
||||
if (FT_Load_Char(face, glyph, FT_LOAD_DEFAULT)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
struct mImage image;
|
||||
_makeTemporaryImage(&image, &face->glyph->bitmap);
|
||||
|
||||
FT_Vector kerning = {0};
|
||||
FT_Get_Kerning(face, lastGlyph, glyph, FT_KERNING_DEFAULT, &kerning);
|
||||
x += kerning.x;
|
||||
y += kerning.y;
|
||||
|
||||
mPainterDrawMask(painter, &image, (x >> 6) + face->glyph->bitmap_left, (y >> 6) - face->glyph->bitmap_top);
|
||||
x += face->glyph->advance.x;
|
||||
y += face->glyph->advance.y;
|
||||
|
||||
lastGlyph = glyph;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue