22#ifdef SCIMESH_STB_TRUETYPE_IMPL
26#ifndef STB_TRUETYPE_IMPLEMENTATION
27#define STB_TRUETYPE_IMPLEMENTATION
30#define STBTT_assert(x) ((void)0)
31#include "stb_truetype.h"
38bool file_readable(
const std::string &path) {
42 std::ifstream in(path.c_str(), std::ios::binary);
48std::vector<uint8_t> read_file_bytes(
const std::string &path) {
49 std::ifstream in(path.c_str(), std::ios::binary);
51 throw std::runtime_error(
"scimesh: cannot open font file '" + path +
"'");
53 in.seekg(0, std::ios::end);
54 const std::streamoff size = in.tellg();
56 throw std::runtime_error(
"scimesh: font file '" + path +
"' is empty");
58 in.seekg(0, std::ios::beg);
59 std::vector<uint8_t> data(
static_cast<size_t>(size));
60 in.read(
reinterpret_cast<char *
>(data.data()), size);
62 throw std::runtime_error(
"scimesh: failed to read font file '" + path +
"'");
72std::string utf16be_to_utf8(
const unsigned char *bytes,
int length) {
74 for (
int i = 0; i + 1 < length; i += 2) {
76 static_cast<uint32_t
>(bytes[i] << 8) |
static_cast<uint32_t
>(bytes[i + 1]);
77 uint32_t cp = code_unit;
78 if (code_unit >= 0xD800 && code_unit <= 0xDBFF && i + 3 < length) {
79 uint32_t low =
static_cast<uint32_t
>(bytes[i + 2] << 8) |
80 static_cast<uint32_t
>(bytes[i + 3]);
81 if (low >= 0xDC00 && low <= 0xDFFF) {
82 cp = 0x10000 + ((code_unit - 0xD800) << 10) + (low - 0xDC00);
87 }
else if (code_unit >= 0xDC00 && code_unit <= 0xDFFF) {
92 out +=
static_cast<char>(cp);
93 }
else if (cp < 0x800) {
94 out +=
static_cast<char>(0xC0 | (cp >> 6));
95 out +=
static_cast<char>(0x80 | (cp & 0x3F));
96 }
else if (cp < 0x10000) {
97 out +=
static_cast<char>(0xE0 | (cp >> 12));
98 out +=
static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
99 out +=
static_cast<char>(0x80 | (cp & 0x3F));
101 out +=
static_cast<char>(0xF0 | (cp >> 18));
102 out +=
static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
103 out +=
static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
104 out +=
static_cast<char>(0x80 | (cp & 0x3F));
111std::mutex g_font_cache_mutex;
112std::map<std::string, Font> g_font_cache;
126 Impl(std::shared_ptr<
const std::vector<uint8_t>> bytes,
float size, std::string path)
129 throw std::runtime_error(
"scimesh: empty font data");
131 const int offset = stbtt_GetFontOffsetForIndex(
data->data(), 0);
133 throw std::runtime_error(
134 "scimesh: not a usable TrueType/OpenType font" +
137 if (!stbtt_InitFont(&
info,
data->data(), offset)) {
138 throw std::runtime_error(
139 "scimesh: failed to parse font" +
143 int ascent = 0, descent = 0, line_gap = 0;
144 stbtt_GetFontVMetrics(&
info, &ascent, &descent, &line_gap);
150 std::shared_ptr<const std::vector<uint8_t>>
data;
163 mutable std::map<uint32_t, GlyphBitmap>
cache;
172 throw std::invalid_argument(
"scimesh: font pixel size must be > 0");
174 auto bytes = std::make_shared<const std::vector<uint8_t>>(read_file_bytes(path));
175 return Font(std::make_shared<Impl>(std::move(bytes),
pixel_size, path));
180 throw std::invalid_argument(
"scimesh: font pixel size must be > 0");
182 auto bytes = std::make_shared<const std::vector<uint8_t>>(std::move(data));
183 return Font(std::make_shared<Impl>(std::move(bytes),
pixel_size, std::string()));
190 if (!(new_pixel_size > 0.0f)) {
191 throw std::invalid_argument(
"scimesh: font pixel size must be > 0");
194 return Font(std::make_shared<Impl>(impl_->data, new_pixel_size, impl_->source_path));
211 int advance_width = 0, left_side_bearing = 0;
212 stbtt_GetCodepointHMetrics(&impl_->info,
static_cast<int>(codepoint),
213 &advance_width, &left_side_bearing);
214 return static_cast<float>(advance_width) * impl_->scale;
221 const int kern = stbtt_GetCodepointKernAdvance(&impl_->info,
222 static_cast<int>(first),
223 static_cast<int>(second));
224 return static_cast<float>(kern) * impl_->scale;
231 const std::vector<uint32_t> codepoints =
decode_utf8(text);
233 uint32_t previous = 0;
234 bool have_previous =
false;
235 for (uint32_t cp : codepoints) {
241 have_previous =
true;
250 std::lock_guard<std::mutex> lock(impl_->mutex);
251 auto it = impl_->cache.find(codepoint);
252 if (it != impl_->cache.end()) {
257 const int glyph_index = stbtt_FindGlyphIndex(&impl_->info,
static_cast<int>(codepoint));
258 int advance_width = 0, left_side_bearing = 0;
259 stbtt_GetGlyphHMetrics(&impl_->info, glyph_index, &advance_width, &left_side_bearing);
260 glyph.
advance =
static_cast<float>(advance_width) * impl_->scale;
262 if (glyph_index != 0) {
263 int width = 0, height = 0, x_offset = 0, y_offset = 0;
264 unsigned char *
bitmap = stbtt_GetGlyphBitmap(&impl_->info, impl_->scale, impl_->scale,
265 glyph_index, &
width, &height,
266 &x_offset, &y_offset);
272 const size_t count =
static_cast<size_t>(
width) *
static_cast<size_t>(height);
275 stbtt_FreeBitmap(
bitmap, impl_->info.userdata);
279 auto inserted = impl_->cache.emplace(codepoint, std::move(
glyph));
280 return &inserted.first->second;
285 return std::string();
289 const int name_ids[] = {1, 4};
290 for (
int name_id : name_ids) {
292 const char *raw = stbtt_GetFontNameString(&impl_->info, &length,
293 STBTT_PLATFORM_ID_MICROSOFT,
294 STBTT_MS_EID_UNICODE_BMP,
295 STBTT_MS_LANG_ENGLISH, name_id);
296 if (raw && length > 0) {
297 std::string name = utf16be_to_utf8(
298 reinterpret_cast<const unsigned char *
>(raw), length);
304 return std::string();
312 std::vector<uint32_t> out;
313 out.reserve(text.size());
314 const size_t n = text.size();
317 const unsigned char byte =
static_cast<unsigned char>(text[i]);
322 }
else if ((
byte & 0xE0) == 0xC0) {
325 }
else if ((
byte & 0xF0) == 0xE0) {
328 }
else if ((
byte & 0xF8) == 0xF0) {
332 out.push_back(0xFFFD);
337 bool valid = (i + extra < n);
338 for (
size_t k = 1;
valid && k <= extra; ++k) {
339 const unsigned char cont =
static_cast<unsigned char>(text[i + k]);
340 if ((cont & 0xC0) != 0x80) {
344 cp = (cp << 6) | (cont & 0x3Fu);
347 out.push_back(0xFFFD);
359 for (
char c : text) {
372 const char *env = std::getenv(
"SCIMESH_FONT");
373 if (env && *env && file_readable(env)) {
374 return std::string(env);
376#ifdef SCIMESH_DEFAULT_FONT
377 if (file_readable(SCIMESH_DEFAULT_FONT)) {
378 return std::string(SCIMESH_DEFAULT_FONT);
383 static const char *candidates[] = {
384 "inst/extdata/Inter-Regular.ttf",
385 "../inst/extdata/Inter-Regular.ttf",
386 "../../inst/extdata/Inter-Regular.ttf",
387 "../../../inst/extdata/Inter-Regular.ttf",
388 "../../../../inst/extdata/Inter-Regular.ttf",
390 for (
const char *candidate : candidates) {
391 if (file_readable(candidate)) {
392 return std::string(candidate);
395 return std::string();
407 if (resolved.empty()) {
408 throw std::runtime_error(
409 "scimesh: no font available. Pass a font file, use the bundled "
410 "'inst/extdata/Inter-Regular.ttf', or set the SCIMESH_FONT "
411 "environment variable to a readable .ttf file.");
413 if (!(pixel_size > 0.0f)) {
414 throw std::invalid_argument(
"scimesh: font pixel size must be > 0");
417 const std::string key = resolved +
'\x1f' + std::to_string(pixel_size);
418 std::lock_guard<std::mutex> lock(g_font_cache_mutex);
419 auto it = g_font_cache.find(key);
420 if (it != g_font_cache.end()) {
424 g_font_cache.emplace(key, font);
429 std::lock_guard<std::mutex> lock(g_font_cache_mutex);
430 g_font_cache.clear();
A TrueType font, loaded at a fixed pixel size, with a glyph cache.
static size_t line_count(const std::string &text)
Number of lines in a string (newline-separated, at least 1).
std::string family_name() const
Family name of the font, e.g. "Inter" (empty if unavailable).
static std::vector< uint32_t > decode_utf8(const std::string &text)
Decode a UTF-8 string into Unicode code points.
static Font from_file(const std::string &path, float pixel_size)
Load a TrueType font from a file.
FontMetrics metrics() const
Vertical metrics (ascent, descent, line gap) in pixels.
Font with_pixel_size(float new_pixel_size) const
A font handle for the same typeface at a different pixel size.
float advance(uint32_t codepoint) const
Pen advance of one code point in pixels.
std::string source_path() const
Path this font was loaded from, or an empty string for from_memory() fonts.
float pixel_size() const
Em size of this font in pixels (0 for an invalid font).
Font()=default
Construct an empty (invalid) font. See valid().
float measure(const std::string &text) const
Width of a single line of text in pixels, including kerning.
const GlyphBitmap * glyph(uint32_t codepoint) const
The rasterized glyph for a code point, rasterizing it on first use.
bool valid() const
Whether this handle refers to a loaded font.
float kerning(uint32_t first, uint32_t second) const
Kerning adjustment between two code points, in pixels.
static Font from_memory(std::vector< uint8_t > data, float pixel_size)
Create a font from TTF data already in memory.
TrueType font loading, glyph rasterization and text measurement.
std::string resolve_font_path(const std::string &path)
Turn a possibly empty font path into a path that exists.
void clear_font_cache()
Drop all fonts from the process-wide font cache.
std::string default_font_path()
Path of the bundled default font, or as overridden by the SCIMESH_FONT environment variable.
Font cached_font(const std::string &path, float pixel_size)
Load a font, re-using an already loaded one when possible.
Vertical metrics of a font at a given pixel size.
float ascent
Distance from the baseline to the top of the line, in pixels.
float line_gap
Recommended extra spacing between two lines, in pixels.
float descent
Distance from the baseline to the bottom of the line, in pixels.
Shared font state: the font bytes, the parsed font and the glyph cache.
std::string source_path
Origin of the font, if any.
stbtt_fontinfo info
Parsed font tables.
float pixel_size
Em size in pixels.
std::map< uint32_t, GlyphBitmap > cache
Rasterized glyphs, filled on demand.
Impl(std::shared_ptr< const std::vector< uint8_t > > bytes, float size, std::string path)
std::mutex mutex
Guards cache; makes a Font safe to share between threads.
std::shared_ptr< const std::vector< uint8_t > > data
Raw font file bytes.
FontMetrics metrics
Vertical metrics in pixels.
float scale
Font units to pixel scale.
One rasterized glyph: an 8-bit coverage bitmap plus placement info.
float advance
Pen advance for this glyph, in pixels.
int x_offset
Horizontal offset from the pen position to the bitmap's left edge.
int y_offset
Vertical offset from the baseline to the bitmap's top edge.
int height
Bitmap height in pixels.
std::vector< uint8_t > coverage
Coverage values, row-major, width * height entries.
int width
Bitmap width in pixels.
const GlyphBitmap * bitmap