Files
pcsx2/3rdparty/plutosvg/plutovg/source/plutovg-font.c
2025-11-18 14:18:26 -07:00

415 lines
14 KiB
C

#include "plutovg.h"
#include "plutovg-utils.h"
#include <stdio.h>
#include <assert.h>
#define STBTT_STATIC
#define STB_TRUETYPE_IMPLEMENTATION
#include "plutovg-stb-truetype.h"
static int plutovg_text_iterator_length(const void* data, int length, plutovg_text_encoding_t encoding)
{
if(length != -1)
return length;
length = 0;
switch(encoding) {
case PLUTOVG_TEXT_ENCODING_UTF8:
case PLUTOVG_TEXT_ENCODING_LATIN1: {
const uint8_t* text = data;
while(*text++)
length++;
break;
} case PLUTOVG_TEXT_ENCODING_UTF16: {
const uint16_t* text = data;
while(*text++)
length++;
break;
} case PLUTOVG_TEXT_ENCODING_UTF32: {
const uint32_t* text = data;
while(*text++)
length++;
break;
} default:
assert(false);
}
return length;
}
void plutovg_text_iterator_init(plutovg_text_iterator_t* it, const void* text, int length, plutovg_text_encoding_t encoding)
{
it->text = text;
it->length = plutovg_text_iterator_length(text, length, encoding);
it->encoding = encoding;
it->index = 0;
}
bool plutovg_text_iterator_has_next(const plutovg_text_iterator_t* it)
{
return it->index < it->length;
}
plutovg_codepoint_t plutovg_text_iterator_next(plutovg_text_iterator_t* it)
{
plutovg_codepoint_t codepoint = 0;
switch(it->encoding) {
case PLUTOVG_TEXT_ENCODING_UTF8: {
static const uint8_t trailing[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5
};
static const uint32_t offsets[6] = {
0x00000000, 0x00003080, 0x000E2080, 0x03C82080, 0xFA082080, 0x82082080
};
const uint8_t* text = it->text;
int trailing_bytes = trailing[text[it->index]];
if(it->index + trailing_bytes >= it->length)
trailing_bytes = 0;
switch(trailing_bytes) {
case 5: codepoint += text[it->index++]; codepoint <<= 6;
case 4: codepoint += text[it->index++]; codepoint <<= 6;
case 3: codepoint += text[it->index++]; codepoint <<= 6;
case 2: codepoint += text[it->index++]; codepoint <<= 6;
case 1: codepoint += text[it->index++]; codepoint <<= 6;
case 0: codepoint += text[it->index++];
}
codepoint -= offsets[trailing_bytes];
break;
} case PLUTOVG_TEXT_ENCODING_UTF16: {
const uint16_t* text = it->text;
codepoint = text[it->index++];
if(((codepoint) & 0xfffffc00) == 0xd800) {
if(it->index < it->length && (((codepoint) & 0xfffffc00) == 0xdc00)) {
uint16_t trail = text[it->index++];
codepoint = (codepoint << 10) + trail - ((0xD800u << 10) - 0x10000u + 0xDC00u);
}
}
break;
} case PLUTOVG_TEXT_ENCODING_UTF32: {
const uint32_t* text = it->text;
codepoint = text[it->index++];
break;
} case PLUTOVG_TEXT_ENCODING_LATIN1: {
const uint8_t* text = it->text;
codepoint = text[it->index++];
break;
} default:
assert(false);
}
return codepoint;
}
typedef struct {
stbtt_vertex* vertices;
int nvertices;
int index;
int advance_width;
int left_side_bearing;
int x1;
int y1;
int x2;
int y2;
} glyph_t;
#define GLYPH_CACHE_SIZE 256
struct plutovg_font_face {
int ref_count;
int ascent;
int descent;
int line_gap;
int x1;
int y1;
int x2;
int y2;
stbtt_fontinfo info;
glyph_t** glyphs[GLYPH_CACHE_SIZE];
plutovg_destroy_func_t destroy_func;
void* closure;
};
plutovg_font_face_t* plutovg_font_face_load_from_file(const char* filename, int ttcindex)
{
FILE* fp = fopen(filename, "rb");
if(fp == NULL) {
return NULL;
}
fseek(fp, 0, SEEK_END);
long length = ftell(fp);
if(length == -1L) {
fclose(fp);
return NULL;
}
void* data = malloc(length);
if(data == NULL) {
fclose(fp);
return NULL;
}
fseek(fp, 0, SEEK_SET);
size_t nread = fread(data, 1, length, fp);
fclose(fp);
if(nread != length) {
free(data);
return NULL;
}
return plutovg_font_face_load_from_data(data, length, ttcindex, free, data);
}
plutovg_font_face_t* plutovg_font_face_load_from_data(const void* data, unsigned int length, int ttcindex, plutovg_destroy_func_t destroy_func, void* closure)
{
stbtt_fontinfo info;
int offset = stbtt_GetFontOffsetForIndex(data, ttcindex);
if(offset == -1 || !stbtt_InitFont(&info, data, offset)) {
if(destroy_func)
destroy_func(closure);
return NULL;
}
plutovg_font_face_t* face = malloc(sizeof(plutovg_font_face_t));
face->ref_count = 1;
face->info = info;
stbtt_GetFontVMetrics(&face->info, &face->ascent, &face->descent, &face->line_gap);
stbtt_GetFontBoundingBox(&face->info, &face->x1, &face->y1, &face->x2, &face->y2);
memset(face->glyphs, 0, sizeof(face->glyphs));
face->destroy_func = destroy_func;
face->closure = closure;
return face;
}
plutovg_font_face_t* plutovg_font_face_reference(plutovg_font_face_t* face)
{
if(face == NULL)
return NULL;
++face->ref_count;
return face;
}
void plutovg_font_face_destroy(plutovg_font_face_t* face)
{
if(face == NULL)
return;
if(--face->ref_count == 0) {
for(int i = 0; i < GLYPH_CACHE_SIZE; i++) {
if(face->glyphs[i] == NULL)
continue;
for(int j = 0; j < GLYPH_CACHE_SIZE; j++) {
glyph_t* glyph = face->glyphs[i][j];
if(glyph == NULL)
continue;
stbtt_FreeShape(&face->info, glyph->vertices);
free(glyph);
}
free(face->glyphs[i]);
}
if(face->destroy_func)
face->destroy_func(face->closure);
free(face);
}
}
int plutovg_font_face_get_reference_count(const plutovg_font_face_t* face)
{
if(face)
return face->ref_count;
return 0;
}
static float plutovg_font_face_get_scale(const plutovg_font_face_t* face, float size)
{
return stbtt_ScaleForMappingEmToPixels(&face->info, size);
}
void plutovg_font_face_get_metrics(const plutovg_font_face_t* face, float size, float* ascent, float* descent, float* line_gap, plutovg_rect_t* extents)
{
float scale = plutovg_font_face_get_scale(face, size);
if(ascent) *ascent = face->ascent * scale;
if(descent) *descent = face->descent * scale;
if(line_gap) *line_gap = face->line_gap * scale;
if(extents) {
extents->x = face->x1 * scale;
extents->y = face->y2 * -scale;
extents->w = (face->x2 - face->x1) * scale;
extents->h = (face->y1 - face->y2) * -scale;
}
}
static glyph_t* plutovg_font_face_get_glyph(plutovg_font_face_t* face, plutovg_codepoint_t codepoint)
{
unsigned int msb = (codepoint >> 8) & 0xFF;
if(face->glyphs[msb] == NULL) {
face->glyphs[msb] = calloc(GLYPH_CACHE_SIZE, sizeof(glyph_t*));
}
unsigned int lsb = codepoint & 0xFF;
if(face->glyphs[msb][lsb] == NULL) {
glyph_t* glyph = malloc(sizeof(glyph_t));
glyph->index = stbtt_FindGlyphIndex(&face->info, codepoint);
glyph->nvertices = stbtt_GetGlyphShape(&face->info, glyph->index, &glyph->vertices);
stbtt_GetGlyphHMetrics(&face->info, glyph->index, &glyph->advance_width, &glyph->left_side_bearing);
if(!stbtt_GetGlyphBox(&face->info, glyph->index, &glyph->x1, &glyph->y1, &glyph->x2, &glyph->y2))
glyph->x1 = glyph->y1 = glyph->x2 = glyph->y2 = 0;
face->glyphs[msb][lsb] = glyph;
}
return face->glyphs[msb][lsb];
}
void plutovg_font_face_get_glyph_metrics(plutovg_font_face_t* face, float size, plutovg_codepoint_t codepoint, float* advance_width, float* left_side_bearing, plutovg_rect_t* extents)
{
float scale = plutovg_font_face_get_scale(face, size);
glyph_t* glyph = plutovg_font_face_get_glyph(face, codepoint);
if(advance_width) *advance_width = glyph->advance_width * scale;
if(left_side_bearing) *left_side_bearing = glyph->left_side_bearing * scale;
if(extents) {
extents->x = glyph->x1 * scale;
extents->y = glyph->y2 * -scale;
extents->w = (glyph->x2 - glyph->x1) * scale;
extents->h = (glyph->y1 - glyph->y2) * -scale;
}
}
static void glyph_traverse_func(void* closure, plutovg_path_command_t command, const plutovg_point_t* points, int npoints)
{
plutovg_path_t* path = (plutovg_path_t*)(closure);
switch(command) {
case PLUTOVG_PATH_COMMAND_MOVE_TO:
plutovg_path_move_to(path, points[0].x, points[0].y);
break;
case PLUTOVG_PATH_COMMAND_LINE_TO:
plutovg_path_line_to(path, points[0].x, points[0].y);
break;
case PLUTOVG_PATH_COMMAND_CUBIC_TO:
plutovg_path_cubic_to(path, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y);
break;
case PLUTOVG_PATH_COMMAND_CLOSE:
assert(false);
}
}
float plutovg_font_face_get_glyph_path(plutovg_font_face_t* face, float size, float x, float y, plutovg_codepoint_t codepoint, plutovg_path_t* path)
{
return plutovg_font_face_traverse_glyph_path(face, size, x, y, codepoint, glyph_traverse_func, path);
}
float plutovg_font_face_traverse_glyph_path(plutovg_font_face_t* face, float size, float x, float y, plutovg_codepoint_t codepoint, plutovg_path_traverse_func_t traverse_func, void* closure)
{
float scale = plutovg_font_face_get_scale(face, size);
plutovg_matrix_t matrix;
plutovg_matrix_init_translate(&matrix, x, y);
plutovg_matrix_scale(&matrix, scale, -scale);
plutovg_point_t points[3];
plutovg_point_t current_point = {0, 0};
glyph_t* glyph = plutovg_font_face_get_glyph(face, codepoint);
for(int i = 0; i < glyph->nvertices; i++) {
switch(glyph->vertices[i].type) {
case STBTT_vmove:
points[0].x = glyph->vertices[i].x;
points[0].y = glyph->vertices[i].y;
current_point = points[0];
plutovg_matrix_map_points(&matrix, points, points, 1);
traverse_func(closure, PLUTOVG_PATH_COMMAND_MOVE_TO, points, 1);
break;
case STBTT_vline:
points[0].x = glyph->vertices[i].x;
points[0].y = glyph->vertices[i].y;
current_point = points[0];
plutovg_matrix_map_points(&matrix, points, points, 1);
traverse_func(closure, PLUTOVG_PATH_COMMAND_LINE_TO, points, 1);
break;
case STBTT_vcurve:
points[0].x = 2.f / 3.f * glyph->vertices[i].cx + 1.f / 3.f * current_point.x;
points[0].y = 2.f / 3.f * glyph->vertices[i].cy + 1.f / 3.f * current_point.y;
points[1].x = 2.f / 3.f * glyph->vertices[i].cx + 1.f / 3.f * glyph->vertices[i].x;
points[1].y = 2.f / 3.f * glyph->vertices[i].cy + 1.f / 3.f * glyph->vertices[i].y;
points[2].x = glyph->vertices[i].x;
points[2].y = glyph->vertices[i].y;
current_point = points[2];
plutovg_matrix_map_points(&matrix, points, points, 3);
traverse_func(closure, PLUTOVG_PATH_COMMAND_CUBIC_TO, points, 3);
break;
case STBTT_vcubic:
points[0].x = glyph->vertices[i].cx;
points[0].y = glyph->vertices[i].cy;
points[1].x = glyph->vertices[i].cx1;
points[1].y = glyph->vertices[i].cy1;
points[2].x = glyph->vertices[i].x;
points[2].y = glyph->vertices[i].y;
current_point = points[2];
plutovg_matrix_map_points(&matrix, points, points, 3);
traverse_func(closure, PLUTOVG_PATH_COMMAND_CUBIC_TO, points, 3);
break;
default:
assert(false);
}
}
return glyph->advance_width * scale;
}
float plutovg_font_face_text_extents(plutovg_font_face_t* face, float size, const void* text, int length, plutovg_text_encoding_t encoding, plutovg_rect_t* extents)
{
plutovg_text_iterator_t it;
plutovg_text_iterator_init(&it, text, length, encoding);
plutovg_rect_t* text_extents = NULL;
float total_advance_width = 0.f;
while(plutovg_text_iterator_has_next(&it)) {
plutovg_codepoint_t codepoint = plutovg_text_iterator_next(&it);
float advance_width;
if(extents == NULL) {
plutovg_font_face_get_glyph_metrics(face, size, codepoint, &advance_width, NULL, NULL);
total_advance_width += advance_width;
continue;
}
plutovg_rect_t glyph_extents;
plutovg_font_face_get_glyph_metrics(face, size, codepoint, &advance_width, NULL, &glyph_extents);
glyph_extents.x += total_advance_width;
total_advance_width += advance_width;
if(text_extents == NULL) {
text_extents = extents;
*text_extents = glyph_extents;
continue;
}
float x1 = plutovg_min(text_extents->x, glyph_extents.x);
float y1 = plutovg_min(text_extents->y, glyph_extents.y);
float x2 = plutovg_max(text_extents->x + text_extents->w, glyph_extents.x + glyph_extents.w);
float y2 = plutovg_max(text_extents->y + text_extents->h, glyph_extents.y + glyph_extents.h);
text_extents->x = x1;
text_extents->y = y1;
text_extents->w = x2 - x1;
text_extents->h = y2 - y1;
}
if(extents && !text_extents) {
extents->x = 0;
extents->y = 0;
extents->w = 0;
extents->h = 0;
}
return total_advance_width;
}