summaryrefslogtreecommitdiffstatshomepage
path: root/src/osd/modules/font
diff options
context:
space:
mode:
author arbee <rb6502@users.noreply.github.com>2026-02-07 13:32:37 -0500
committer arbee <rb6502@users.noreply.github.com>2026-02-07 13:32:37 -0500
commit8349e95e3e0ea4d3081739e2b8bb5787b9e366ba (patch)
tree89c058b1454f3c898fb918dc2ce1c7a2fb701e4b /src/osd/modules/font
parentdba6d057f1186642e73eb7fa020aabf9647b163f (diff)
Initial support for SDL3. [R. Belmont, Vas Crabb]
* SDL3 is the default for macOS targets. Linux/Windows can build with OSD=sdl3 while we wait for better distro support for SDL3. Both X11 and Wayland sessions are fully supported on Linux. * SDL3 -sound=sdl supports both input and output. All other video, sound, and input functionality should otherwise be the same for now. SDL 3.4+ multiple keyboard/mouse support is planned.
Diffstat (limited to 'src/osd/modules/font')
-rw-r--r--src/osd/modules/font/font_sdl.cpp4
-rw-r--r--src/osd/modules/font/font_sdl3.cpp389
2 files changed, 391 insertions, 2 deletions
diff --git a/src/osd/modules/font/font_sdl.cpp b/src/osd/modules/font/font_sdl.cpp
index 514e0cc0784..9767f5d3cf8 100644
--- a/src/osd/modules/font/font_sdl.cpp
+++ b/src/osd/modules/font/font_sdl.cpp
@@ -1,13 +1,13 @@
// license:BSD-3-Clause
// copyright-holders:Olivier Galibert, R. Belmont, Vas Crabb
/*
- * font_sdl.c
+ * font_sdl.cpp
*
*/
#include "font_module.h"
-#if defined(SDLMAME_UNIX) && !defined(SDLMAME_MACOSX) && !defined(SDLMAME_HAIKU) && !defined(SDLMAME_ANDROID)
+#if defined(SDLMAME_UNIX) && !defined(SDLMAME_MACOSX) && !defined(SDLMAME_HAIKU) && !defined(SDLMAME_ANDROID) && !defined(SDLMAME_SDL3)
#include "corestr.h"
#include "emucore.h"
diff --git a/src/osd/modules/font/font_sdl3.cpp b/src/osd/modules/font/font_sdl3.cpp
new file mode 100644
index 00000000000..ff677e85bc2
--- /dev/null
+++ b/src/osd/modules/font/font_sdl3.cpp
@@ -0,0 +1,389 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert, R. Belmont, Vas Crabb
+/*
+ * font_sdl3.cpp
+ *
+ */
+
+#include "font_module.h"
+
+#if defined(SDLMAME_UNIX) && !defined(SDLMAME_MACOSX) && !defined(SDLMAME_HAIKU) && !defined(SDLMAME_ANDROID) && defined(SDLMAME_SDL3)
+
+#include "corestr.h"
+#include "emucore.h"
+#include "fileio.h"
+#include "unicode.h"
+#include "osdcore.h"
+
+#include <SDL3_ttf/SDL_ttf.h>
+
+#if !defined(SDLMAME_HAIKU) && !defined(SDLMAME_EMSCRIPTEN)
+#include <fontconfig/fontconfig.h>
+#endif
+
+
+//-------------------------------------------------
+// font_open - attempt to "open" a handle to the
+// font with the given name
+//-------------------------------------------------
+
+class osd_font_sdl : public osd_font
+{
+public:
+ osd_font_sdl() : m_font(nullptr, &TTF_CloseFont) { }
+ osd_font_sdl(osd_font_sdl &&obj) : m_font(std::move(obj.m_font)) { }
+ virtual ~osd_font_sdl() { close(); }
+
+ virtual bool open(std::string const &font_path, std::string const &name, int &height);
+ virtual void close();
+ virtual bool get_bitmap(char32_t chnum, bitmap_argb32 &bitmap, std::int32_t &width, std::int32_t &xoffs, std::int32_t &yoffs);
+
+ osd_font_sdl & operator=(osd_font_sdl &&obj)
+ {
+ using std::swap;
+ swap(m_font, obj.m_font);
+ return *this;
+ }
+
+private:
+ typedef std::unique_ptr<TTF_Font, void (*)(TTF_Font *)> TTF_Font_ptr;
+
+ osd_font_sdl(osd_font_sdl const &) = delete;
+ osd_font_sdl & operator=(osd_font_sdl const &) = delete;
+
+ static constexpr double POINT_SIZE = 144.0;
+
+#if !defined(SDLMAME_HAIKU) && !defined(SDLMAME_EMSCRIPTEN)
+ TTF_Font_ptr search_font_config(std::string const &family, std::string const &style, bool &bakedstyles);
+#endif
+ bool BDF_Check_Magic(std::string const &name);
+ TTF_Font_ptr TTF_OpenFont_Magic(std::string const &name, int fsize, long index);
+
+ TTF_Font_ptr m_font;
+};
+
+bool osd_font_sdl::open(std::string const &font_path, std::string const &_name, int &height)
+{
+ bool bakedstyles = false;
+
+ std::string name(_name);
+ if (name.compare("default") == 0)
+ {
+ name = "Liberation Sans|Regular";
+ }
+
+ // accept qualifiers from the name
+ bool const underline = (strreplace(name, "[U]", "") + strreplace(name, "[u]", "") > 0);
+ bool const strike = (strreplace(name, "[S]", "") + strreplace(name, "[s]", "") > 0);
+ std::string::size_type const separator = name.rfind('|');
+ std::string const family(name.substr(0, separator));
+ std::string const style((std::string::npos != separator) ? name.substr(separator + 1) : std::string());
+
+ // first up, try it as a filename
+ TTF_Font_ptr font = TTF_OpenFont_Magic(family, POINT_SIZE, 0);
+
+ // if no success, try the font path
+ if (!font)
+ {
+ osd_printf_verbose("Searching font %s in -%s path/s\n", family, font_path);
+ //emu_file file(options().font_path(), OPEN_FLAG_READ);
+ emu_file file(font_path, OPEN_FLAG_READ);
+ if (!file.open(family))
+ {
+ std::string full_name = file.fullpath();
+ font = TTF_OpenFont_Magic(full_name, POINT_SIZE, 0);
+ if (font)
+ osd_printf_verbose("Found font %s\n", full_name);
+ }
+ }
+
+ // if that didn't work, crank up the FontConfig database
+#if !defined(SDLMAME_HAIKU) && !defined(SDLMAME_EMSCRIPTEN)
+ if (!font)
+ {
+ font = search_font_config(family, style, bakedstyles);
+ }
+#endif
+
+ if (!font)
+ {
+ if (!BDF_Check_Magic(name))
+ {
+ osd_printf_verbose("font %s is not TrueType or BDF, using MAME default\n", name);
+ }
+ return false;
+ }
+
+ // apply styles
+ int styleflags = 0;
+ if (!bakedstyles)
+ {
+ if ((style.find("Bold") != std::string::npos) || (style.find("Black") != std::string::npos)) styleflags |= TTF_STYLE_BOLD;
+ if ((style.find("Italic") != std::string::npos) || (style.find("Oblique") != std::string::npos)) styleflags |= TTF_STYLE_ITALIC;
+ }
+ styleflags |= underline ? TTF_STYLE_UNDERLINE : 0;
+ // SDL_ttf 2.0.9 and earlier does not define TTF_STYLE_STRIKETHROUGH
+#if SDL_VERSIONNUM(SDL_TTF_MAJOR_VERSION, SDL_TTF_MINOR_VERSION, SDL_TTF_MICRO_VERSION) > SDL_VERSIONNUM(2,0,9)
+ styleflags |= strike ? TTF_STYLE_STRIKETHROUGH : 0;
+#else
+ if (strike)
+ osd_printf_warning("Ignoring strikethrough for SDL_TTF older than 2.0.10\n");
+#endif // PATCHLEVEL
+ TTF_SetFontStyle(font.get(), styleflags);
+
+ height = TTF_GetFontLineSkip(font.get());
+
+ m_font = std::move(font);
+ return true;
+}
+
+//-------------------------------------------------
+// font_close - release resources associated with
+// a given OSD font
+//-------------------------------------------------
+
+void osd_font_sdl::close()
+{
+ m_font.reset();
+}
+
+//-------------------------------------------------
+// font_get_bitmap - allocate and populate a
+// BITMAP_FORMAT_ARGB32 bitmap containing the
+// pixel values rgb_t(0xff,0xff,0xff,0xff)
+// or rgb_t(0x00,0xff,0xff,0xff) for each
+// pixel of a black & white font
+//-------------------------------------------------
+
+bool osd_font_sdl::get_bitmap(char32_t chnum, bitmap_argb32 &bitmap, std::int32_t &width, std::int32_t &xoffs, std::int32_t &yoffs)
+{
+ SDL_Color const fcol = { 0xff, 0xff, 0xff };
+ char ustr[16];
+ ustr[utf8_from_uchar(ustr, std::size(ustr), chnum)] = '\0';
+ std::unique_ptr<SDL_Surface, void (*)(SDL_Surface *)> const drawsurf(TTF_RenderText_Solid(m_font.get(), ustr, 0, fcol), &SDL_DestroySurface);
+
+ // was nothing returned?
+ if (drawsurf)
+ {
+ // allocate a MAME destination bitmap
+ bitmap.allocate(drawsurf->w, drawsurf->h);
+
+ // copy the rendered character image into it
+ for (int y = 0; y < bitmap.height(); y++)
+ {
+ std::uint32_t *const dstrow = &bitmap.pix(y);
+ std::uint8_t const *const srcrow = reinterpret_cast<std::uint8_t const *>(drawsurf->pixels) + (y * drawsurf->pitch);
+
+ for (int x = 0; x < drawsurf->w; x++)
+ {
+ dstrow[x] = srcrow[x] ? rgb_t(0xff, 0xff, 0xff, 0xff) : rgb_t(0x00, 0xff, 0xff, 0xff);
+ }
+ }
+
+ // what are these?
+ xoffs = yoffs = 0;
+ width = drawsurf->w;
+ }
+
+ return bitmap.valid();
+}
+
+osd_font_sdl::TTF_Font_ptr osd_font_sdl::TTF_OpenFont_Magic(std::string const &name, int fsize, long index)
+{
+ emu_file file(OPEN_FLAG_READ);
+ if (!file.open(name))
+ {
+ unsigned char const ttf_magic[] = { 0x00, 0x01, 0x00, 0x00, 0x00 };
+ unsigned char const ttc1_magic[] = { 0x74, 0x74, 0x63, 0x66, 0x00, 0x01, 0x00, 0x00 };
+ unsigned char const ttc2_magic[] = { 0x74, 0x74, 0x63, 0x66, 0x00, 0x02, 0x00, 0x00 };
+ unsigned char buffer[std::max({ sizeof(ttf_magic), sizeof(ttc1_magic), sizeof(ttc2_magic) })];
+ auto const bytes_read = file.read(buffer, std::size(buffer));
+ file.close();
+
+ if (((bytes_read >= sizeof(ttf_magic)) && !std::memcmp(buffer, ttf_magic, sizeof(ttf_magic))) ||
+ ((bytes_read >= sizeof(ttc1_magic)) && !std::memcmp(buffer, ttc1_magic, sizeof(ttc1_magic))) ||
+ ((bytes_read >= sizeof(ttc2_magic)) && !std::memcmp(buffer, ttc2_magic, sizeof(ttc2_magic))))
+ {
+ SDL_PropertiesID props = SDL_CreateProperties();
+
+ SDL_SetStringProperty(props, TTF_PROP_FONT_CREATE_FILENAME_STRING, name.c_str());
+ SDL_SetNumberProperty(props, TTF_PROP_FONT_CREATE_FACE_NUMBER, index);
+ SDL_SetFloatProperty(props, TTF_PROP_FONT_CREATE_SIZE_FLOAT, POINT_SIZE);
+
+ return TTF_Font_ptr(TTF_OpenFontWithProperties(props), &TTF_CloseFont);
+ }
+ }
+ return TTF_Font_ptr(nullptr, &TTF_CloseFont);
+}
+
+bool osd_font_sdl::BDF_Check_Magic(std::string const &name)
+{
+ emu_file file(OPEN_FLAG_READ);
+ if (!file.open(name))
+ {
+ unsigned char const magic[] = { 'S', 'T', 'A', 'R', 'T', 'F', 'O', 'N', 'T' };
+ unsigned char buffer[sizeof(magic)];
+ if ((sizeof(magic) != file.read(buffer, sizeof(magic))) || memcmp(buffer, magic, sizeof(magic)))
+ return true;
+ }
+ return false;
+}
+
+#if !defined(SDLMAME_HAIKU) && !defined(SDLMAME_EMSCRIPTEN)
+osd_font_sdl::TTF_Font_ptr osd_font_sdl::search_font_config(std::string const &family, std::string const &style, bool &bakedstyles)
+{
+ TTF_Font_ptr font(nullptr, &TTF_CloseFont);
+
+ FcConfig *const config = FcConfigGetCurrent();
+ std::unique_ptr<FcPattern, void (*)(FcPattern *)> pat(FcPatternCreate(), &FcPatternDestroy);
+ std::unique_ptr<FcObjectSet, void (*)(FcObjectSet *)> os(FcObjectSetCreate(), &FcObjectSetDestroy);
+ FcPatternAddString(pat.get(), FC_FAMILY, (const FcChar8 *)family.c_str());
+
+ // try and get a font with the requested styles baked-in
+ if (!style.empty())
+ FcPatternAddString(pat.get(), FC_STYLE, (const FcChar8 *)style.c_str());
+
+ FcPatternAddString(pat.get(), FC_FONTFORMAT, (const FcChar8 *)"TrueType");
+
+ FcObjectSetAdd(os.get(), FC_FILE);
+ FcObjectSetAdd(os.get(), FC_INDEX);
+ std::unique_ptr<FcFontSet, void (*)(FcFontSet *)> fontset(FcFontList(config, pat.get(), os.get()), &FcFontSetDestroy);
+
+ for (int i = 0; (i < fontset->nfont) && !font; i++)
+ {
+ FcValue val;
+ if ((FcPatternGet(fontset->fonts[i], FC_FILE, 0, &val) == FcResultMatch) && (val.type == FcTypeString))
+ {
+ osd_printf_verbose("Matching font: %s\n", val.u.s);
+
+ std::string const match_name((const char*)val.u.s);
+ long const index = ((FcPatternGet(fontset->fonts[i], FC_INDEX, 0, &val) == FcResultMatch) && (val.type == FcTypeInteger)) ? val.u.i : 0;
+ font = TTF_OpenFont_Magic(match_name, POINT_SIZE, index);
+
+ if (font)
+ bakedstyles = true;
+ }
+ }
+
+ // didn't get a font above? try again with no baked-in styles
+ // note that this simply returns the first match for the family name, which could be regular if you're lucky, but it could be bold oblique or something
+ if (!font && !style.empty())
+ {
+ pat.reset(FcPatternCreate());
+ FcPatternAddString(pat.get(), FC_FAMILY, (const FcChar8 *)family.c_str());
+ FcPatternAddString(pat.get(), FC_FONTFORMAT, (const FcChar8 *)"TrueType");
+ fontset.reset(FcFontList(config, pat.get(), os.get()));
+
+ for (int i = 0; (i < fontset->nfont) && !font; i++)
+ {
+ FcValue val;
+ if ((FcPatternGet(fontset->fonts[i], FC_FILE, 0, &val) == FcResultMatch) && (val.type == FcTypeString))
+ {
+ osd_printf_verbose("Matching unstyled font: %s\n", val.u.s);
+
+ std::string const match_name((const char*)val.u.s);
+ long const index = ((FcPatternGet(fontset->fonts[i], FC_INDEX, 0, &val) == FcResultMatch) && (val.type == FcTypeInteger)) ? val.u.i : 0;
+ font = TTF_OpenFont_Magic(match_name, POINT_SIZE, index);
+
+ if (font)
+ bakedstyles = false;
+ }
+ }
+ }
+
+ return font;
+}
+#endif
+
+
+class font_sdl : public osd_module, public font_module
+{
+public:
+ font_sdl() : osd_module(OSD_FONT_PROVIDER, "sdl"), font_module()
+ {
+ }
+
+ osd_font::ptr font_alloc() override
+ {
+ return std::make_unique<osd_font_sdl>();
+ }
+
+ virtual int init(osd_interface &osd, const osd_options &options) override
+ {
+ if (!TTF_Init())
+ {
+ osd_printf_error("SDL_ttf failed: %s\n", SDL_GetError());
+ return -1;
+ }
+ return 0;
+ }
+
+ virtual void exit() override
+ {
+ TTF_Quit();
+ }
+
+ virtual bool get_font_families(std::string const &font_path, std::vector<std::pair<std::string, std::string> > &result) override;
+};
+
+
+bool font_sdl::get_font_families(std::string const &font_path, std::vector<std::pair<std::string, std::string> > &result)
+{
+ result.clear();
+
+ // TODO: enumerate TTF files in font path, since we can load them, too
+
+#if !defined(SDLMAME_HAIKU) && !defined(SDLMAME_EMSCRIPTEN)
+ FcConfig *const config = FcConfigGetCurrent();
+ std::unique_ptr<FcPattern, void (*)(FcPattern *)> pat(FcPatternCreate(), &FcPatternDestroy);
+ FcPatternAddString(pat.get(), FC_FONTFORMAT, (const FcChar8 *)"TrueType");
+
+ std::unique_ptr<FcObjectSet, void (*)(FcObjectSet *)> os(FcObjectSetCreate(), &FcObjectSetDestroy);
+ FcObjectSetAdd(os.get(), FC_FAMILY);
+ FcObjectSetAdd(os.get(), FC_FILE);
+ FcObjectSetAdd(os.get(), FC_STYLE);
+
+ std::unique_ptr<FcFontSet, void (*)(FcFontSet *)> fontset(FcFontList(config, pat.get(), os.get()), &FcFontSetDestroy);
+ for (int i = 0; (i < fontset->nfont); i++)
+ {
+ FcValue val;
+ if ((FcPatternGet(fontset->fonts[i], FC_FILE, 0, &val) == FcResultMatch) &&
+ (val.type == FcTypeString) &&
+ (FcPatternGet(fontset->fonts[i], FC_FAMILY, 0, &val) == FcResultMatch) &&
+ (val.type == FcTypeString))
+ {
+ auto const compare_fonts = [](std::pair<std::string, std::string> const &a, std::pair<std::string, std::string> const &b) -> bool
+ {
+ int const second = core_stricmp(a.second, b.second);
+ if (second < 0) return true;
+ else if (second > 0) return false;
+ else return core_stricmp(b.first, b.first) < 0;
+ };
+ std::string config((const char *)val.u.s);
+ std::string display(config);
+ if ((FcPatternGet(fontset->fonts[i], FC_STYLE, 0, &val) == FcResultMatch) && (val.type == FcTypeString))
+ {
+ config.push_back('|');
+ config.append((const char *)val.u.s);
+ display.push_back(' ');
+ display.append((const char *)val.u.s);
+ }
+ std::pair<std::string, std::string> font(std::move(config), std::move(display));
+ auto const pos = std::lower_bound(result.begin(), result.end(), font, compare_fonts);
+ if ((result.end() == pos) || (pos->first != font.first)) result.emplace(pos, std::move(font));
+ }
+ }
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+#else /* SDLMAME_UNIX */
+
+MODULE_NOT_SUPPORTED(font_sdl, OSD_FONT_PROVIDER, "sdl")
+
+#endif
+
+MODULE_DEFINITION(FONT_SDL, font_sdl)