pebble/src/fw/applib/graphics/text_resources.c
2025-01-27 11:38:16 -08:00

653 lines
25 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "text.h"
#include "text_resources.h"
#include "syscall/syscall.h"
#include "applib/fonts/fonts.h"
#include "applib/fonts/fonts_private.h"
#include "resource/resource_ids.auto.h"
#include "system/logging.h"
#include "system/passert.h"
#include "system/profiler.h"
#include "util/math.h"
#include "util/size.h"
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#define RLE4_UNITS_BIT_WIDTH (4)
#define RLE4_UNITS_PER_BYTE (8 / RLE4_UNITS_BIT_WIDTH)
static const size_t s_font_md_size[] = {
0, // There currently is no font version 0. This makes decoding much easier & consistent
sizeof(FontMetaDataV1),
sizeof(FontMetaData),
sizeof(FontMetaDataV3)
};
static uint8_t prv_font_hash(Codepoint codepoint, uint8_t table_size) {
return (codepoint % table_size);
}
static Codepoint prv_offset_table_get_codepoint(FontCache *font_cache, const FontMetaData *md,
int index) {
const bool offset_16 = HAS_FEATURE(md->version, VERSION_FIELD_FEATURE_OFFSET_16);
if (md->codepoint_bytes == 2) {
return offset_16 ? font_cache->offsets_buffer_2_2[index].codepoint :
font_cache->offsets_buffer_2_4[index].codepoint;
} else {
return offset_16 ? font_cache->offsets_buffer_4_2[index].codepoint :
font_cache->offsets_buffer_4_4[index].codepoint;
}
}
static uint32_t prv_offset_table_get_offset(FontCache *font_cache, const FontMetaData *md,
int index) {
const bool offset_16 = HAS_FEATURE(md->version, VERSION_FIELD_FEATURE_OFFSET_16);
if (md->codepoint_bytes == 2) {
return offset_16 ? font_cache->offsets_buffer_2_2[index].offset :
font_cache->offsets_buffer_2_4[index].offset;
} else {
return offset_16 ? font_cache->offsets_buffer_4_2[index].offset :
font_cache->offsets_buffer_4_4[index].offset;
}
}
static uint32_t prv_offset_table_entry_size(const FontMetaData *md) {
const bool offset_16 = HAS_FEATURE(md->version, VERSION_FIELD_FEATURE_OFFSET_16);
if (md->codepoint_bytes == 2) {
return offset_16 ? sizeof(OffsetTableEntry_2_2) : sizeof(OffsetTableEntry_2_4);
} else {
return offset_16 ? sizeof(OffsetTableEntry_4_2) : sizeof(OffsetTableEntry_4_4);
}
}
static int prv_offset_table_get_id(const FontMetaData *md, Codepoint codepoint) {
if (FONT_VERSION(md->version) == FONT_VERSION_1) {
return (1);
} else {
return prv_font_hash(codepoint, md->hash_table_size);
}
}
static int prv_load_offset_table(Codepoint codepoint, FontCache *font_cache,
const FontResource *font_res) {
const int table_id = prv_offset_table_get_id(&font_res->md, codepoint);
if (table_id == font_cache->offset_table_id) {
return font_cache->offset_table_size;
}
size_t num_bytes, offset, num_entries;
const uint8_t version = FONT_VERSION(font_res->md.version);
if (version == FONT_VERSION_1) {
offset = s_font_md_size[FONT_VERSION_1];
num_bytes = font_res->md.number_of_glyphs * prv_offset_table_entry_size(&font_res->md);
num_entries = font_res->md.number_of_glyphs;
PBL_ASSERTN(num_bytes <= sizeof(font_cache->offsets_buffer_2_2));
} else {
FontHashTableEntry table_entry;
Codepoint hash_entry_offset = s_font_md_size[version] + table_id * sizeof(FontHashTableEntry);
// find which bucket the codepoint was put into TODO: cache hash table?
PBL_LOG_D(LOG_DOMAIN_TEXT, LOG_LEVEL_DEBUG,
"HTE read: table_id:%d, cp:%"PRIx32", offset:%"PRIx32,
table_id, codepoint, hash_entry_offset);
SYS_PROFILER_NODE_START(text_render_flash);
sys_resource_load_range(font_res->app_num, font_res->resource_id, hash_entry_offset,
(uint8_t*)&table_entry, sizeof(FontHashTableEntry));
SYS_PROFILER_NODE_STOP(text_render_flash);
offset = s_font_md_size[version] +
(sizeof(FontHashTableEntry) * font_res->md.hash_table_size) + table_entry.offset;
num_bytes = table_entry.count * prv_offset_table_entry_size(&font_res->md);
num_entries = table_entry.count;
PBL_ASSERTN(num_bytes <= sizeof(font_cache->offsets_buffer_4_4));
}
PBL_LOG_D(LOG_DOMAIN_TEXT, LOG_LEVEL_DEBUG, "HT read: offset: %zx, bytes: %zu", offset,
num_bytes);
SYS_PROFILER_NODE_START(text_render_flash);
sys_resource_load_range(font_res->app_num, font_res->resource_id, offset,
(uint8_t *)font_cache->offsets_buffer_4_4, num_bytes);
SYS_PROFILER_NODE_STOP(text_render_flash);
font_cache->offset_table_id = table_id;
font_cache->offset_table_size = num_entries;
return num_entries;
}
static uint32_t prv_get_cache_key(const FontResource *font_res, Codepoint codepoint) {
// Ideally we'd be able to use the full app_num, resource_id and codepoint combined into a key
// in a unique matter, but unfortunately there aren't enough bits. Note that this value needs to
// be unique, there's no collision handling and if one does occur you'll just end up reading the
// wrong metadata. Luckily we don't need to store all the bits due to assumptions we can make.
// We know that for a given FontCache we'll only use a combination of fonts from the running app
// and system fonts and we'll never use custom fonts from two different app banks at the same
// time. This means we only need a single bit to store whether the font is for the system bank
// (bank 0) or an app bank (bank > 0).
// resource_id is technically a full 32-bit id but in practice it's much smaller. The firmware
// only uses 400~ unique resources at the time of writing so 14 bits (16384 resources) should be
// enough.
// Therefore our key layout becomes:
// is_app:1
// resource_id:14
// codepoint:17
const bool is_app = (font_res->app_num != 0);
return (is_app ? 1 << 31 : 0) |
((font_res->resource_id << 17) |
(codepoint & 0x0001FFFF));
}
static uint32_t prv_get_glyph_table_offset(FontCache *font_cache, Codepoint codepoint,
const FontResource *font_res) {
int min_idx = 0;
int max_idx = prv_load_offset_table(codepoint, font_cache, font_res);
uint32_t offset = 0;
while (max_idx >= min_idx) {
int mid_idx = (max_idx + min_idx) / 2;
Codepoint codepoint_at_mid_idx = prv_offset_table_get_codepoint(font_cache, &font_res->md,
mid_idx);
if (codepoint_at_mid_idx < codepoint) {
min_idx = mid_idx + 1;
} else if (codepoint_at_mid_idx > codepoint) {
max_idx = mid_idx - 1;
} else {
offset = prv_offset_table_get_offset(font_cache, &font_res->md, mid_idx);
break;
}
}
return offset;
}
static uint32_t prv_get_glyph_data_offset(Codepoint codepoint, FontCache *font_cache,
const FontResource *font_res) {
const uint32_t offset = prv_get_glyph_table_offset(font_cache, codepoint, font_res);
if (offset <= 0) {
return 0;
}
// Compute the offset of the glyph data (relative to the beginning
// of the font blob).
//
// See: https://pebbletechnology.atlassian.net/wiki/display/DEV/Pebble+Resource+Pack+Format
uint32_t address;
const uint32_t version = FONT_VERSION(font_res->md.version);
if (version == FONT_VERSION_1) {
address = s_font_md_size[FONT_VERSION_1] +
(font_res->md.number_of_glyphs * prv_offset_table_entry_size(&font_res->md)) +
(sizeof(uint32_t) * offset);
} else {
address = s_font_md_size[version] +
(sizeof(FontHashTableEntry) * font_res->md.hash_table_size) +
(prv_offset_table_entry_size(&font_res->md) * font_res->md.number_of_glyphs) +
offset;
}
return address;
}
//! Decode RLE4 decoded glyph data in place.
//! @return g or NULL on error.
#define RLE4_SYMBOL_MASK 0x08
#define RLE4_LENGTH_MASK 0x07
static GlyphData *prv_decompress_glyph_data(GlyphData *g, uint8_t *src) {
// This RLE4 decompressor expects GlyphData to be formatted like this:
// [ <header> | <free space> | <encoded glyph> ]
// *src is a pointer to the beginning of <encoded glyph>
//
// Once decompressed, Glyph Data will be formatted like this:
// [ <header> | <decoded glyph> | <free space & remenants of encoded glyph data> ]
//
// The glyph is decoded in-place, so obviously, it's imperative that <decoded glyph> and
// <encoded glyph> not overlap at any time. This is checked by fontgen.py and confirmed
// by the code below.
//
// RLE4 data is encoded as a stream of RLE Units.
// 0 1 2 3
// +-+-+-+-+
// |*| Len | Where * is the encoded symbol [0,1] and 'Len + 1' is the number of symbols
// +-+-+-+-+ in the run [1,8]. For example, 1000 expands to '1' and 0100 expands to '00000'
//
// RLE Units are packed as pairs -- two to a byte.
//
// Decoding is done by expanding the bit patterns into a buffer until we have at least 8 bits of
// pixels to write.
PBL_ASSERTN(g);
PBL_ASSERTN(src > (uint8_t *)g->data);
uint8_t *dst = (uint8_t *)g->data;
unsigned total_pixels_decoded = 0;
unsigned num_rle_units = g->header.num_rle_units;
// Decoded pixel buffer. At least 16 bits (to hold 2 decoded RLE4s)
uint16_t buf = 0;
int8_t buf_num_bits = 0;
PBL_ASSERTN(num_rle_units <= (CACHE_GLYPH_SIZE * RLE4_UNITS_PER_BYTE));
while (num_rle_units) {
PBL_ASSERTN(src < &((uint8_t *)g->data)[CACHE_GLYPH_SIZE]);
uint8_t rle_unit_pair = *src++;
for (unsigned int i = 0; i < RLE4_UNITS_PER_BYTE; ++i) {
if (!num_rle_units) {
break; // Handle a padded, odd number of rle units
}
// Number of bits in this run
uint8_t length = (rle_unit_pair & RLE4_LENGTH_MASK) + 1;
// Symbol of this run. We don't need to generate a pattern of 0s. ;-)
if (rle_unit_pair & RLE4_SYMBOL_MASK) {
uint8_t pattern = ((1 << length) - 1); // List of 'length' 1s
buf |= (pattern << buf_num_bits);
}
buf_num_bits += length;
total_pixels_decoded += length;
// Store 8 bits worth of pixels
if (buf_num_bits >= 8) {
PBL_ASSERTN(dst < src);
*dst++ = (buf & 0xFF);
buf >>= 8;
buf_num_bits -= 8;
}
// Now process the second nibble
rle_unit_pair >>= 4;
num_rle_units--;
}
}
// Flush out any remaining pixels
while (buf_num_bits > 0) {
PBL_ASSERTN(dst < &((uint8_t *)g->data)[CACHE_GLYPH_SIZE]);
*dst++ = (buf & 0xFF);
buf >>= 8;
buf_num_bits -= 8;
}
// Fix-up the height to reflect the bit pattern instead of the number of RLE units.
if (g->header.width_px) {
g->header.height_px = total_pixels_decoded / g->header.width_px;
}
return g;
}
static bool prv_load_glyph_bitmap(Codepoint codepoint, const FontResource *font_res,
LineCacheData *data) {
GlyphData *g = &data->glyph_data;
const size_t bitmap_offset = (FONT_VERSION(font_res->md.version) == FONT_VERSION_1) ?
sizeof(GlyphHeaderDataV1) : sizeof(GlyphHeaderData);
const uint32_t bitmap_addr = data->resource_offset + bitmap_offset;
size_t glyph_size_bytes;
// Handle RLE4 compressed glyphs. header.height_px has been 'borrowed' to mean the number of
// 4-bit RLE units used to encode the glyph. We determine the height by decoding the number of
// bits and then dividing by the width. glyph.height_px must be updated!
if (HAS_FEATURE(font_res->md.version, VERSION_FIELD_FEATURE_RLE4)) {
// Two RLE4 units per byte. Round up to the next whole byte
glyph_size_bytes = (g->header.height_px + (RLE4_UNITS_PER_BYTE - 1)) / RLE4_UNITS_PER_BYTE;
} else {
// Number of bytes, make sure we round up to the next whole byte
glyph_size_bytes = ((g->header.width_px * g->header.height_px) + (8 - 1)) / 8;
}
PBL_ASSERT(glyph_size_bytes <= CACHE_GLYPH_SIZE,
"text codepoint %"PRIx32" is %zu bytes, overflowing %zu max size", codepoint,
glyph_size_bytes, CACHE_GLYPH_SIZE);
if (glyph_size_bytes) {
uint8_t *target;
PBL_LOG_D(LOG_DOMAIN_TEXT, LOG_LEVEL_DEBUG,
"GD read: cp: %"PRIx32", res_bank: %"PRIu32", res_id: %"PRIu32", "
"offset: %"PRIx32", bytes: %zu",
codepoint, font_res->app_num, font_res->resource_id, bitmap_addr, glyph_size_bytes);
if (HAS_FEATURE(font_res->md.version, VERSION_FIELD_FEATURE_RLE4)) {
// Load the glyph data at the end of the buffer
target = &((uint8_t *)g->data)[CACHE_GLYPH_SIZE - glyph_size_bytes];
} else {
target = (uint8_t *)g->data;
}
SYS_PROFILER_NODE_START(text_render_flash);
size_t num_bytes_loaded = sys_resource_load_range(font_res->app_num, font_res->resource_id,
bitmap_addr, target, glyph_size_bytes);
SYS_PROFILER_NODE_STOP(text_render_flash);
if (glyph_size_bytes && !num_bytes_loaded) {
PBL_LOG(LOG_LEVEL_WARNING,
"Failed to load glyph bitmap from resources; cp: %"PRIx32", addr: %"PRIx32,
codepoint, bitmap_addr);
return false;
}
if (HAS_FEATURE(font_res->md.version, VERSION_FIELD_FEATURE_RLE4)) {
SYS_PROFILER_NODE_START(text_render_compress);
g = prv_decompress_glyph_data(g, target);
SYS_PROFILER_NODE_STOP(text_render_compress);
}
}
data->is_bitmap_loaded = true;
return true;
}
static const GlyphData *prv_get_glyph_metadata_from_spi(Codepoint codepoint,
FontCache *font_cache,
const FontResource *font_res,
bool need_bitmap) {
const uint32_t cache_key = prv_get_cache_key(font_res, codepoint);
LineCacheData *cached = NULL;
// If we don't have bitmap caching, we have a single glyph_buffer that contains the last used
// glyph. If this matches the glyph we're looking for right now, that's what we want to use.
// Potentially this also has the bitmap loaded already.
#if !CAPABILITY_HAS_GLYPH_BITMAP_CACHING
if (font_cache->glyph_buffer_key == cache_key) {
cached = (LineCacheData *)(font_cache->glyph_buffer);
}
#endif
PBL_LOG_D(LOG_DOMAIN_TEXT, LOG_LEVEL_DEBUG, "looking up cp: %"PRIx32", key:%"PRIx32,
codepoint, cache_key);
// If the glyph_buffer doesn't match this glyph, or we have bitmap caching, check the
// keyed_circular_cache for this glyph.
if (!cached) {
cached = keyed_circular_cache_get(&font_cache->line_cache, cache_key);
#if !CAPABILITY_HAS_GLYPH_BITMAP_CACHING
// If we don't have bitmap caching, the keyed_circular_cache entry cannot store the bitmap.
// Therefore, we need to copy the matched entry into `glyph_buffer` which does have the space
// to store the bitmap.
if (cached) {
memcpy(font_cache->glyph_buffer, cached, sizeof(LineCacheData));
font_cache->glyph_buffer_key = cache_key;
// Point `cached` at the glyph buffer.
cached = (LineCacheData *)(font_cache->glyph_buffer);
cached->is_bitmap_loaded = false;
}
#endif
}
if (cached) {
if (cached->resource_offset == 0) {
// missing character
return NULL;
}
if (need_bitmap &&
!cached->is_bitmap_loaded &&
!prv_load_glyph_bitmap(codepoint, font_res, cached)) {
return NULL;
}
return &cached->glyph_data;
}
// We missed the cache, so we need to build a new cache entry.
LineCacheData *data = &font_cache->cache_data_scratch;
data->is_bitmap_loaded = false;
data->resource_offset = prv_get_glyph_data_offset(codepoint, font_cache, font_res);
GlyphData *g = &data->glyph_data;
if (data->resource_offset == 0) {
PBL_LOG_D(LOG_DOMAIN_TEXT, LOG_LEVEL_DEBUG, "offset for cp: %"PRIx32" is NULL", codepoint);
// Put the missing character into our cache so we don't waste time looking for it again
keyed_circular_cache_push(&font_cache->line_cache, cache_key, data);
return NULL;
}
size_t num_bytes_loaded;
if (FONT_VERSION(font_res->md.version) == FONT_VERSION_1) {
GlyphHeaderDataV1 header;
PBL_LOG_D(LOG_DOMAIN_TEXT, LOG_LEVEL_DEBUG, "LGMD READ: offset: %"PRIx32", bytes: %zu",
data->resource_offset, sizeof(header));
SYS_PROFILER_NODE_START(text_render_flash);
num_bytes_loaded = sys_resource_load_range(font_res->app_num, font_res->resource_id,
data->resource_offset, (uint8_t *)&header,
sizeof(header));
SYS_PROFILER_NODE_STOP(text_render_flash);
// convert to a GlyphHeaderData struct
memcpy(&g->header, &header, sizeof(GlyphHeaderData));
g->header.horiz_advance = header.horiz_advance;
} else {
PBL_LOG_D(LOG_DOMAIN_TEXT, LOG_LEVEL_DEBUG,
"GMD read: cp: %"PRIx32", offset: %"PRId32", bytes: %zu", codepoint,
data->resource_offset, sizeof(GlyphHeaderData));
SYS_PROFILER_NODE_START(text_render_flash);
num_bytes_loaded = sys_resource_load_range(font_res->app_num, font_res->resource_id,
data->resource_offset, (uint8_t *)&g->header,
sizeof(GlyphHeaderData));
SYS_PROFILER_NODE_STOP(text_render_flash);
}
if (!num_bytes_loaded) {
PBL_LOG(LOG_LEVEL_WARNING,
"Failed to load glyph metadata from resources; cp: %"PRIx32", offset: %"PRIx32,
codepoint, data->resource_offset);
return NULL;
}
LineCacheData *final_data;
#if !CAPABILITY_HAS_GLYPH_BITMAP_CACHING
// Copy the info into the glyph_buffer.
// This must be done _before_ loading the bitmap, otherwise loading the bitmap may modify the
// metadata! We will use `glyph_buffer` as the final data, and leave `data` as the uncooked
// version, that way we can push `data` into the circular cache.
memcpy(font_cache->glyph_buffer, data, sizeof(LineCacheData));
font_cache->glyph_buffer_key = cache_key;
final_data = (LineCacheData *)(font_cache->glyph_buffer);
#else
final_data = data;
#endif
if (need_bitmap &&
!prv_load_glyph_bitmap(codepoint, font_res, final_data)) {
return NULL;
}
// We push `data`, which will be cooked data if the bitmap is stored along with it, or
// the uncooked data if it's not. In reality, this only matters to compressed glyphs, since
// compressed glyphs are the only case where the metadata gets modified.
// The only time the data is cooked is when loading a bitmap and the glyph is compressed, in
// which case the `num_rle_units` field is turned back into `height_px`.
keyed_circular_cache_push(&font_cache->line_cache, cache_key, data);
// We return `final_data` though, because that has the actual metadata info that needs to be
// used.
return &final_data->glyph_data;
}
static void prv_check_font_cache(FontCache *font_cache, const FontResource *font_res) {
// Invalidate the offset table
if (font_cache->cached_font != font_res) {
font_cache->offset_table_id = -1;
font_cache->cached_font = font_res;
}
}
static bool prv_load_font_res(ResAppNum app_num, uint32_t resource_id, FontResource *font_res,
bool is_extended) {
font_res->resource_id = resource_id;
font_res->app_num = app_num;
if (resource_id != RESOURCE_ID_FONT_FALLBACK_INTERNAL &&
!sys_resource_is_valid(app_num, resource_id)) {
if (!is_extended) {
PBL_LOG(LOG_LEVEL_WARNING, "Invalid text resource id %"PRId32, resource_id);
}
return false;
}
if (app_num == SYSTEM_APP && !sys_resource_get_and_cache(app_num, resource_id)) {
return false;
}
PBL_LOG_D(LOG_DOMAIN_TEXT, LOG_LEVEL_DEBUG, "FMD read: bytes:%d", (int)sizeof(FontMetaDataV3));
FontMetaDataV3 header;
SYS_PROFILER_NODE_START(text_render_flash);
uint32_t bytes_read = sys_resource_load_range(app_num, resource_id, 0,
(uint8_t*)&header, sizeof(FontMetaDataV3));
SYS_PROFILER_NODE_STOP(text_render_flash);
if (bytes_read != sizeof(FontMetaDataV3)) {
PBL_LOG(LOG_LEVEL_ERROR, "Tried to load resource too small to have metadata for res %"PRId32,
resource_id);
return false;
}
memcpy(&font_res->md, &header, sizeof(FontMetaData));
switch (header.version) {
case FONT_VERSION_1:
// no hash table, no variable codepoint size, no feature bits
font_res->md.hash_table_size = 0;
// Version 1 fonts do use 16 bit offsets and 16 bit codepoints. This simplifies the code above
font_res->md.codepoint_bytes = 2;
font_res->md.version |= VERSION_FIELD_FEATURE_OFFSET_16;
break;
case FONT_VERSION_2:
break;
case FONT_VERSION_3:
// Make sure that the font header is internally consistent
PBL_ASSERTN(header.size == sizeof(FontMetaDataV3));
// HACK alert: Copy the feature bits to the top two bits of the header version.
if (header.features & FEATURE_OFFSET_16) {
font_res->md.version |= VERSION_FIELD_FEATURE_OFFSET_16;
}
if (header.features & FEATURE_RLE4) {
font_res->md.version |= VERSION_FIELD_FEATURE_RLE4;
}
break;
default:
PBL_LOG(LOG_LEVEL_ERROR, "Unknown font resource version %"PRIu8, header.version);
return false;
}
return true;
}
static const FontResource *prv_font_res_for_codepoint(Codepoint codepoint,
const FontInfo *font_info) {
if (!codepoint_is_latin(codepoint) &&
!codepoint_is_emoji(codepoint) &&
!codepoint_is_special(codepoint) &&
font_info->extended) {
// Latin & emoji codepoints are in base, others are in extension
return (&font_info->extension);
} else if (codepoint_is_emoji(codepoint) &&
font_info->base.app_num == SYSTEM_APP) {
// Assuming we are using base
FontInfo *emoji_font = fonts_get_system_emoji_font_for_size(font_info->max_height);
if (emoji_font) {
return &emoji_font->base;
}
}
return (&font_info->base);
}
static void prv_resource_changed_callback(void *data) {
FontInfo *font_info = (FontInfo *)data;
font_info->loaded = false;
font_info->extended = false;
}
///////////////////////////
// Public API
bool text_resources_init_font(ResAppNum app_num, uint32_t font_resource,
uint32_t extended_resource, FontInfo *font_info) {
// load the base of the font or bail
if (!font_resource ||
!prv_load_font_res(app_num, font_resource, &font_info->base, false /* is_extended */)) {
return false;
}
// look for an extension font and load it
if (extended_resource) {
// if you want 3rd party apps to use extended fonts, you'll have to unwatch when they unload
// and create a syscall for resource_watch
PBL_ASSERTN(app_num == SYSTEM_APP);
if (font_info->extension_changed_cb == NULL) {
font_info->extension_changed_cb = resource_watch(app_num, extended_resource,
prv_resource_changed_callback, font_info);
}
font_info->extended = prv_load_font_res(app_num, extended_resource, &font_info->extension,
true /* is_extended */);
}
font_info->max_height = MAX(font_info->extension.md.max_height, font_info->base.md.max_height);
font_info->loaded = true;
return true;
}
static const GlyphData *prv_get_glyph(FontCache *font_cache, Codepoint codepoint,
FontInfo *font_info, bool need_bitmap) {
if (!font_info->loaded) {
sys_font_reload_font(font_info);
}
// if we cannot find the codepoint we are looking for, we should always be
// able to find the wildcard (square box) or ' ' character to display. We use
// the wildcard codepoint from the base font in case the extension pack has
// been deleted
const Codepoint codepoint_list[] = { codepoint, font_info->base.md.wildcard_codepoint, ' ' };
for (unsigned int i = 0; i < ARRAY_LENGTH(codepoint_list); i++) {
const FontResource *font_res = prv_font_res_for_codepoint(codepoint_list[i], font_info);
prv_check_font_cache(font_cache, font_res);
const GlyphData *data = prv_get_glyph_metadata_from_spi(codepoint_list[i], font_cache,
font_res, need_bitmap);
if (data) {
return data;
}
}
PBL_LOG(LOG_LEVEL_WARNING, "failed to load glyph or wildcard");
return NULL;
}
int8_t text_resources_get_glyph_horiz_advance(FontCache *font_cache, const Codepoint codepoint,
FontInfo *font_info) {
const GlyphData *g = prv_get_glyph(font_cache, codepoint, font_info, false /* need_bitmap */);
if (!g) {
return 0;
}
return g->header.horiz_advance;
}
const GlyphData *text_resources_get_glyph(FontCache *font_cache, const Codepoint codepoint,
FontInfo *font_info) {
return prv_get_glyph(font_cache, codepoint, font_info, true /* need_bitmap */);
}