FifeGUI 0.3.0
A C++ GUI library designed for games.
fontloader.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later OR BSD-3-Clause
2// SPDX-FileCopyrightText: 2013 - 2026 Fifengine contributors
3
4// Corresponding header include
5#include "fifechan/fontloader.hpp"
6
7// Standard library includes
8#include <algorithm>
9#include <array>
10#include <filesystem>
11#include <iterator>
12#include <memory>
13#include <string>
14#include <vector>
15
16// Project headers (subdirs before local)
17#include "fifechan/exception.hpp"
18
19#ifdef _WIN32
20 #include <windows.h>
21#else
22 #include <climits>
23
24 #include <unistd.h>
25#endif
26
27namespace fcn::font
28{
29
30 namespace
31 {
32
33 std::filesystem::path getExecutablePath()
34 {
35#ifdef _WIN32
36 wchar_t path[MAX_PATH];
37 GetModuleFileNameW(nullptr, path, MAX_PATH);
38 return std::filesystem::path(path);
39#else
40 std::array<char, PATH_MAX> path{};
41 ssize_t const len = readlink("/proc/self/exe", path.data(), path.size() - 1);
42 if (len != -1) {
43 path.at(static_cast<size_t>(len)) = '\0';
44 return {path.data()};
45 }
46 return {};
47#endif
48 }
49
50 std::vector<std::filesystem::path> getSystemFontPaths()
51 {
52 std::vector<std::filesystem::path> paths;
53#ifdef _WIN32
54 // Windows system font directories
55 wchar_t const * winDir = _wgetenv(L"WINDIR");
56 if (winDir) {
57 paths.emplace_back(std::filesystem::path(winDir) / L"Fonts");
58 } else {
59 paths.emplace_back(L"C:\\Windows\\Fonts");
60 }
61#else
62 // Linux/Unix system font directories
63 paths.emplace_back("/usr/share/fonts");
64 paths.emplace_back("/usr/local/share/fonts");
65 paths.emplace_back(std::filesystem::path(getenv("HOME")) / ".local/share/fonts");
66 paths.emplace_back(std::filesystem::path(getenv("HOME")) / ".fonts");
67#endif
68 return paths;
69 }
70
71 std::vector<std::string> getFontExtensions()
72 {
73 return {".ttf", ".otf", ".TTF", ".OTF", ".woff", ".WOFF"};
74 }
75
76 } // anonymous namespace
77
78 std::vector<std::filesystem::path> FontLoader::getDefaultSearchPaths()
79 {
80 std::vector<std::filesystem::path> paths;
81
82 // Current working directory
83 paths.emplace_back(std::filesystem::current_path());
84
85 // Executable directory
86 auto exePath = getExecutablePath();
87 if (!exePath.empty()) {
88 paths.push_back(exePath.parent_path());
89 }
90
91 // System font directories
92 auto systemPaths = getSystemFontPaths();
93 paths.insert(paths.end(), systemPaths.begin(), systemPaths.end());
94
95 // Common project-relative paths (for development/testing)
96 paths.emplace_back("tests/resources");
97 paths.emplace_back("../tests/resources");
98 paths.emplace_back("./tests/resources");
99
100 // Remove duplicates while preserving order
101 std::vector<std::filesystem::path> uniquePaths;
102 std::ranges::copy_if(
103 paths,
104
105 std::back_inserter(uniquePaths),
106 [&uniquePaths](std::filesystem::path const & p) {
107 return std::ranges::find(uniquePaths, p) == uniquePaths.end();
108 });
109
110 return uniquePaths;
111 }
112
113 std::filesystem::path FontLoader::findFontFile(
114 std::string const & fontName, std::vector<std::filesystem::path> const & searchPaths)
115 {
116 // Use provided paths or defaults
117 auto paths = searchPaths.empty() ? FontLoader::getDefaultSearchPaths() : searchPaths;
118
119 // Check if fontName already has an extension
120 std::filesystem::path const fontPath(fontName);
121 bool const hasExtension = fontPath.has_extension();
122
123 auto extensions = getFontExtensions();
124
125 for (auto const & dir : paths) {
126 if (hasExtension) {
127 // Font name includes extension - try direct match
128 std::filesystem::path fullPath = dir / fontName;
129 if (std::filesystem::exists(fullPath)) {
130 return fullPath;
131 }
132 } else {
133 // Try each extension
134 for (auto const & ext : extensions) {
135 std::filesystem::path fullPath = dir / (fontName + ext);
136 if (std::filesystem::exists(fullPath)) {
137 return fullPath;
138 }
139 }
140 }
141 }
142
143 return {}; // Not found
144 }
145
146 std::shared_ptr<Font> FontLoader::loadFont(
147 Graphics& graphics,
148 std::string const & fontName,
149 int size,
150 std::vector<std::filesystem::path> const & searchPaths)
151 {
152 auto fontFile = FontLoader::findFontFile(fontName, searchPaths);
153
154 if (fontFile.empty()) {
155 throw fcn::Exception("loadFont: Could not find font '" + fontName + "'");
156 }
157
158 auto font = graphics.createFont(fontFile.string(), size);
159 if (!font) {
160 throw fcn::Exception("loadFont: Failed to load font from '" + fontFile.string() + "'");
161 }
162
163 return font;
164 }
165
166} // namespace fcn::font
An exception class containing a message, a file, and a line number where the exception occurred.
Definition exception.hpp:35
Abstract interface providing primitive drawing functions (lines, rectangles, etc.).
Definition graphics.hpp:58
virtual std::shared_ptr< Font > createFont(std::string const &filename, int size)
Creates a font for this graphics backend.
Definition graphics.cpp:96
static std::filesystem::path findFontFile(std::string const &fontName, std::vector< std::filesystem::path > const &searchPaths={})
Find a font file by name searching the provided paths.
static std::vector< std::filesystem::path > getDefaultSearchPaths()
Returns default search paths used when locating font files on the current platform.
static std::shared_ptr< Font > loadFont(Graphics &graphics, std::string const &fontName, int size, std::vector< std::filesystem::path > const &searchPaths={})
Load a Font object for the given font name and size using the provided Graphics implementation.