Text metrics API #88

Merged
MartinFouilleul merged 1 commits from text_metrics into main 2023-09-10 17:29:32 +00:00
6 changed files with 191 additions and 141 deletions

View File

@ -416,11 +416,11 @@ ORCA_EXPORT void oc_on_frame_refresh(void)
int fontSize = 18; int fontSize = 18;
oc_str8 text = oc_str8_pushf(oc_scratch(), "%d", blockHealth[i]); oc_str8 text = oc_str8_pushf(oc_scratch(), "%d", blockHealth[i]);
oc_rect textRect = oc_text_bounding_box(font, fontSize, text); oc_rect textRect = oc_font_text_metrics(font, fontSize, text).ink;
oc_vec2 textPos = { oc_vec2 textPos = {
r.x + r.w / 2 - textRect.w / 2, r.x + r.w / 2 - textRect.w / 2 - textRect.x,
r.y + 9, // TODO: oc_text_bounding_box is returning extremely wack results for height. r.y + r.h / 2 - textRect.h / 2 - textRect.y - textRect.h, //NOTE: we render with y-up so we need to flip bounding box coordinates.
}; };
oc_set_color_rgba(0.9, 0.9, 0.9, 1); oc_set_color_rgba(0.9, 0.9, 0.9, 1);
@ -449,10 +449,9 @@ ORCA_EXPORT void oc_on_frame_refresh(void)
// draw score text // draw score text
{ {
oc_move_to(10, 10); oc_move_to(20, 20);
oc_str8 text = oc_str8_pushf(oc_scratch(), "Destroy all %d blocks to win! Current score: %d", NUM_BLOCKS_TO_WIN, score); oc_str8 text = oc_str8_pushf(oc_scratch(), "Destroy all %d blocks to win! Current score: %d", NUM_BLOCKS_TO_WIN, score);
oc_rect textRect = oc_text_bounding_box(font, 20, text); oc_vec2 textPos = { 20, 20 };
oc_vec2 textPos = { 10, 10 };
oc_matrix_push(flip_y_at(textPos)); oc_matrix_push(flip_y_at(textPos));
{ {
oc_set_color_rgba(0.9, 0.9, 0.9, 1); oc_set_color_rgba(0.9, 0.9, 0.9, 1);

View File

@ -94,11 +94,13 @@ ORCA_EXPORT void oc_on_frame_refresh(void)
// clock face // clock face
for(int i = 0; i < oc_array_size(clockNumberStrings); ++i) for(int i = 0; i < oc_array_size(clockNumberStrings); ++i)
{ {
oc_rect textRect = oc_text_bounding_box(font, fontSize, clockNumberStrings[i]); oc_rect textRect = oc_font_text_metrics(font, fontSize, clockNumberStrings[i]).ink;
textRect.h -= 10 * uiScale; // oc_text_bounding_box height doesn't seem to be a tight fit around the glyph
const f32 angle = i * ((M_PI * 2) / 12.0f) - (M_PI / 2); const f32 angle = i * ((M_PI * 2) / 12.0f) - (M_PI / 2);
oc_mat2x3 transform = mat_transform(centerX - (textRect.w / 2), centerY + (textRect.h / 2), angle); oc_mat2x3 transform = mat_transform(centerX - (textRect.w / 2) - textRect.x,
centerY - (textRect.h / 2) - textRect.y,
angle);
oc_vec2 pos = oc_mat2x3_mul(transform, (oc_vec2){ clockRadius * 0.8f, 0 }); oc_vec2 pos = oc_mat2x3_mul(transform, (oc_vec2){ clockRadius * 0.8f, 0 });
oc_set_color_rgba(0.2, 0.2, 0.2, 1); oc_set_color_rgba(0.2, 0.2, 0.2, 1);

View File

@ -118,15 +118,15 @@ int main()
create_font("../../resources/CMUSerif-Roman.ttf"), create_font("../../resources/CMUSerif-Roman.ttf"),
create_font("../../resources/Courier.ttf") }; create_font("../../resources/Courier.ttf") };
oc_font_extents extents[FONT_COUNT]; oc_font_metrics extents[FONT_COUNT];
f32 fontScales[FONT_COUNT]; f32 fontScales[FONT_COUNT];
f32 lineHeights[FONT_COUNT]; f32 lineHeights[FONT_COUNT];
for(int i = 0; i < FONT_COUNT; i++) for(int i = 0; i < FONT_COUNT; i++)
{ {
extents[i] = oc_font_get_extents(fonts[i]); extents[i] = oc_font_get_metrics_unscaled(fonts[i]);
fontScales[i] = oc_font_get_scale_for_em_pixels(fonts[i], 14); fontScales[i] = oc_font_get_scale_for_em_pixels(fonts[i], 14);
lineHeights[i] = fontScales[i] * (extents[i].ascent + extents[i].descent + extents[i].leading); lineHeights[i] = fontScales[i] * (extents[i].ascent + extents[i].descent + extents[i].lineGap);
} }
int codePointCount = oc_utf8_codepoint_count_for_string(OC_STR8((char*)TEST_STRING)); int codePointCount = oc_utf8_codepoint_count_for_string(OC_STR8((char*)TEST_STRING));

View File

@ -185,27 +185,30 @@ typedef enum
OC_CAP_SQUARE OC_CAP_SQUARE
} oc_cap_type; } oc_cap_type;
typedef struct oc_font_extents typedef struct oc_font_metrics
{ {
f32 ascent; // the extent above the baseline (by convention a positive value extends above the baseline) f32 ascent; // the extent above the baseline (by convention a positive value extends above the baseline)
f32 descent; // the extent below the baseline (by convention, positive value extends below the baseline) f32 descent; // the extent below the baseline (by convention, positive value extends below the baseline)
f32 leading; // spacing between one row's descent and the next row's ascent f32 lineGap; // spacing between one row's descent and the next row's ascent
f32 xHeight; // height of the lower case letter 'x' f32 xHeight; // height of the lower case letter 'x'
f32 capHeight; // height of the upper case letter 'M' f32 capHeight; // height of the upper case letter 'M'
f32 width; // maximum width of the font f32 width; // maximum width of the font
} oc_font_extents; } oc_font_metrics;
typedef struct oc_text_extents typedef struct oc_glyph_metrics
{ {
f32 xBearing; oc_rect ink;
f32 yBearing; oc_vec2 advance;
f32 width; } oc_glyph_metrics;
f32 height;
f32 xAdvance;
f32 yAdvance;
} oc_text_extents; typedef struct oc_text_metrics
{
oc_rect ink;
oc_rect logical;
oc_vec2 advance;
} oc_text_metrics;
//------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------
//SECTION: graphics canvas //SECTION: graphics canvas
@ -222,32 +225,25 @@ ORCA_API void oc_render(oc_canvas canvas); //DOC: renders all canvas
//SECTION: fonts //SECTION: fonts
//------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------
ORCA_API oc_font oc_font_nil(void); ORCA_API oc_font oc_font_nil(void);
ORCA_API bool oc_font_is_nil(oc_font font);
ORCA_API oc_font oc_font_create_from_memory(oc_str8 mem, u32 rangeCount, oc_unicode_range* ranges); ORCA_API oc_font oc_font_create_from_memory(oc_str8 mem, u32 rangeCount, oc_unicode_range* ranges);
ORCA_API oc_font oc_font_create_from_file(oc_file file, u32 rangeCount, oc_unicode_range* ranges); ORCA_API oc_font oc_font_create_from_file(oc_file file, u32 rangeCount, oc_unicode_range* ranges);
ORCA_API oc_font oc_font_create_from_path(oc_str8 path, u32 rangeCount, oc_unicode_range* ranges); ORCA_API oc_font oc_font_create_from_path(oc_str8 path, u32 rangeCount, oc_unicode_range* ranges);
ORCA_API void oc_font_destroy(oc_font font); ORCA_API void oc_font_destroy(oc_font font);
//NOTE(martin): the following int valued functions return -1 if font is invalid or codepoint is not present in font//
//TODO(martin): add enum error codes
ORCA_API oc_font_extents oc_font_get_extents(oc_font font);
ORCA_API oc_font_extents oc_font_get_scaled_extents(oc_font font, f32 emSize);
ORCA_API f32 oc_font_get_scale_for_em_pixels(oc_font font, f32 emSize);
//NOTE(martin): if you need to process more than one codepoint, first convert your codepoints to glyph indices, then use the
// glyph index versions of the functions, which can take an array of glyph indices.
ORCA_API oc_str32 oc_font_get_glyph_indices(oc_font font, oc_str32 codePoints, oc_str32 backing); ORCA_API oc_str32 oc_font_get_glyph_indices(oc_font font, oc_str32 codePoints, oc_str32 backing);
ORCA_API oc_str32 oc_font_push_glyph_indices(oc_font font, oc_arena* arena, oc_str32 codePoints); ORCA_API oc_str32 oc_font_push_glyph_indices(oc_font font, oc_arena* arena, oc_str32 codePoints);
ORCA_API u32 oc_font_get_glyph_index(oc_font font, oc_utf32 codePoint); ORCA_API u32 oc_font_get_glyph_index(oc_font font, oc_utf32 codePoint);
ORCA_API int oc_font_get_codepoint_extents(oc_font font, oc_utf32 codePoint, oc_text_extents* outExtents); // metrics
ORCA_API oc_font_metrics oc_font_get_metrics(oc_font font, f32 emSize);
ORCA_API oc_font_metrics oc_font_get_metrics_unscaled(oc_font font);
ORCA_API f32 oc_font_get_scale_for_em_pixels(oc_font font, f32 emSize);
ORCA_API int oc_font_get_glyph_extents(oc_font font, oc_str32 glyphIndices, oc_text_extents* outExtents); ORCA_API oc_text_metrics oc_font_text_metrics_utf32(oc_font font, f32 fontSize, oc_str32 codepoints);
ORCA_API oc_text_metrics oc_font_text_metrics(oc_font font, f32 fontSize, oc_str8 text);
ORCA_API oc_rect oc_text_bounding_box_utf32(oc_font font, f32 fontSize, oc_str32 text);
ORCA_API oc_rect oc_text_bounding_box(oc_font font, f32 fontSize, oc_str8 text);
//------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------
//SECTION: images //SECTION: images

View File

@ -37,7 +37,7 @@ typedef struct oc_glyph_data
bool exists; bool exists;
oc_utf32 codePoint; oc_utf32 codePoint;
oc_path_descriptor pathDescriptor; oc_path_descriptor pathDescriptor;
oc_text_extents extents; oc_glyph_metrics metrics;
//... //...
} oc_glyph_data; } oc_glyph_data;
@ -62,7 +62,7 @@ typedef struct oc_font_data
oc_path_elt* outlines; oc_path_elt* outlines;
f32 unitsPerEm; f32 unitsPerEm;
oc_font_extents extents; oc_font_metrics metrics;
} oc_font_data; } oc_font_data;
@ -398,16 +398,16 @@ oc_font oc_font_create_from_memory(oc_str8 mem, u32 rangeCount, oc_unicode_range
stbtt_GetFontVMetrics(&stbttFontInfo, &ascent, &descent, &lineGap); stbtt_GetFontVMetrics(&stbttFontInfo, &ascent, &descent, &lineGap);
stbtt_GetFontBoundingBox(&stbttFontInfo, &x0, &y0, &x1, &y1); stbtt_GetFontBoundingBox(&stbttFontInfo, &x0, &y0, &x1, &y1);
font->extents.ascent = ascent; font->metrics.ascent = ascent;
font->extents.descent = -descent; font->metrics.descent = -descent;
font->extents.leading = lineGap; font->metrics.lineGap = lineGap;
font->extents.width = x1 - x0; font->metrics.width = x1 - x0;
stbtt_GetCodepointBox(&stbttFontInfo, 'x', &x0, &y0, &x1, &y1); stbtt_GetCodepointBox(&stbttFontInfo, 'x', &x0, &y0, &x1, &y1);
font->extents.xHeight = y1 - y0; font->metrics.xHeight = y1 - y0;
stbtt_GetCodepointBox(&stbttFontInfo, 'M', &x0, &y0, &x1, &y1); stbtt_GetCodepointBox(&stbttFontInfo, 'M', &x0, &y0, &x1, &y1);
font->extents.capHeight = y1 - y0; font->metrics.capHeight = y1 - y0;
//NOTE(martin): load codepoint ranges //NOTE(martin): load codepoint ranges
font->rangeCount = rangeCount; font->rangeCount = rangeCount;
@ -483,13 +483,16 @@ oc_font oc_font_create_from_memory(oc_str8 mem, u32 rangeCount, oc_unicode_range
stbtt_GetGlyphHMetrics(&stbttFontInfo, stbttGlyphIndex, &xAdvance, &xBearing); stbtt_GetGlyphHMetrics(&stbttFontInfo, stbttGlyphIndex, &xAdvance, &xBearing);
stbtt_GetGlyphBox(&stbttFontInfo, stbttGlyphIndex, &x0, &y0, &x1, &y1); stbtt_GetGlyphBox(&stbttFontInfo, stbttGlyphIndex, &x0, &y0, &x1, &y1);
glyph->extents.xAdvance = (f32)xAdvance; //NOTE(martin): stb stbtt_GetGlyphBox returns bottom left and top right corners, with y up,
glyph->extents.yAdvance = 0; // so we have to set .y = -y1
glyph->extents.xBearing = (f32)xBearing; glyph->metrics.ink = (oc_rect){
glyph->extents.yBearing = y0; .x = x0,
.y = -y1,
.w = x1 - x0,
.h = y1 - y0
};
glyph->extents.width = x1 - x0; glyph->metrics.advance = (oc_vec2){ xAdvance, 0 };
glyph->extents.height = y1 - y0;
//NOTE(martin): load glyph outlines //NOTE(martin): load glyph outlines
@ -673,34 +676,34 @@ oc_glyph_data* oc_font_get_glyph_data(oc_font_data* fontData, u32 glyphIndex)
return (&(fontData->glyphs[glyphIndex - 1])); return (&(fontData->glyphs[glyphIndex - 1]));
} }
oc_font_extents oc_font_get_extents(oc_font font) oc_font_metrics oc_font_get_metrics_unscaled(oc_font font)
{ {
oc_font_data* fontData = oc_font_data_from_handle(font); oc_font_data* fontData = oc_font_data_from_handle(font);
if(!fontData) if(!fontData)
{ {
return ((oc_font_extents){ 0 }); return ((oc_font_metrics){ 0 });
} }
return (fontData->extents); return (fontData->metrics);
} }
oc_font_extents oc_font_get_scaled_extents(oc_font font, f32 emSize) oc_font_metrics oc_font_get_metrics(oc_font font, f32 emSize)
{ {
oc_font_data* fontData = oc_font_data_from_handle(font); oc_font_data* fontData = oc_font_data_from_handle(font);
if(!fontData) if(!fontData)
{ {
return ((oc_font_extents){ 0 }); return ((oc_font_metrics){ 0 });
} }
f32 scale = emSize / fontData->unitsPerEm; f32 scale = emSize / fontData->unitsPerEm;
oc_font_extents extents = fontData->extents; oc_font_metrics metrics = fontData->metrics;
extents.ascent *= scale; metrics.ascent *= scale;
extents.descent *= scale; metrics.descent *= scale;
extents.leading *= scale; metrics.lineGap *= scale;
extents.xHeight *= scale; metrics.xHeight *= scale;
extents.capHeight *= scale; metrics.capHeight *= scale;
extents.width *= scale; metrics.width *= scale;
return (extents); return (metrics);
} }
f32 oc_font_get_scale_for_em_pixels(oc_font font, f32 emSize) f32 oc_font_get_scale_for_em_pixels(oc_font font, f32 emSize)
@ -713,9 +716,10 @@ f32 oc_font_get_scale_for_em_pixels(oc_font font, f32 emSize)
return (emSize / fontData->unitsPerEm); return (emSize / fontData->unitsPerEm);
} }
void oc_font_get_glyph_extents_from_font_data(oc_font_data* fontData, ////////////////////////////////////////////////////////////////////////////////////////////::
void oc_font_get_glyph_metrics_from_font_data(oc_font_data* fontData,
oc_str32 glyphIndices, oc_str32 glyphIndices,
oc_text_extents* outExtents) oc_glyph_metrics* outMetrics)
{ {
for(int i = 0; i < glyphIndices.len; i++) for(int i = 0; i < glyphIndices.len; i++)
{ {
@ -724,22 +728,23 @@ void oc_font_get_glyph_extents_from_font_data(oc_font_data* fontData,
continue; continue;
} }
oc_glyph_data* glyph = oc_font_get_glyph_data(fontData, glyphIndices.ptr[i]); oc_glyph_data* glyph = oc_font_get_glyph_data(fontData, glyphIndices.ptr[i]);
outExtents[i] = glyph->extents; outMetrics[i] = glyph->metrics;
} }
} }
int oc_font_get_glyph_extents(oc_font font, oc_str32 glyphIndices, oc_text_extents* outExtents) /*
int oc_font_get_glyph_metrics(oc_font font, oc_str32 glyphIndices, oc_text_metrics* outMetrics)
{ {
oc_font_data* fontData = oc_font_data_from_handle(font); oc_font_data* fontData = oc_font_data_from_handle(font);
if(!fontData) if(!fontData)
{ {
return (-1); return (-1);
} }
oc_font_get_glyph_extents_from_font_data(fontData, glyphIndices, outExtents); oc_font_get_glyph_metrics_from_font_data(fontData, glyphIndices, outMetrics);
return (0); return (0);
} }
int oc_font_get_codepoint_extents(oc_font font, oc_utf32 codePoint, oc_text_extents* outExtents) int oc_font_get_codepoint_metrics(oc_font font, oc_utf32 codePoint, oc_text_metrics* outMetrics)
{ {
oc_font_data* fontData = oc_font_data_from_handle(font); oc_font_data* fontData = oc_font_data_from_handle(font);
if(!fontData) if(!fontData)
@ -750,21 +755,23 @@ int oc_font_get_codepoint_extents(oc_font font, oc_utf32 codePoint, oc_text_exte
oc_str32 codePoints = { 1, &codePoint }; oc_str32 codePoints = { 1, &codePoint };
oc_str32 backing = { 1, &glyphIndex }; oc_str32 backing = { 1, &glyphIndex };
oc_str32 glyphs = oc_font_get_glyph_indices_from_font_data(fontData, codePoints, backing); oc_str32 glyphs = oc_font_get_glyph_indices_from_font_data(fontData, codePoints, backing);
oc_font_get_glyph_extents_from_font_data(fontData, glyphs, outExtents); oc_font_get_glyph_metrics_from_font_data(fontData, glyphs, outMetrics);
return (0); return (0);
} }
*/
/////////////////////////////////////////////////
oc_rect oc_text_bounding_box_utf32(oc_font font, f32 fontSize, oc_str32 codePoints) oc_text_metrics oc_font_text_metrics_utf32(oc_font font, f32 fontSize, oc_str32 codePoints)
{ {
if(!codePoints.len || !codePoints.ptr) if(!codePoints.len || !codePoints.ptr)
{ {
return ((oc_rect){ 0 }); return ((oc_text_metrics){ 0 });
} }
oc_font_data* fontData = oc_font_data_from_handle(font); oc_font_data* fontData = oc_font_data_from_handle(font);
if(!fontData) if(!fontData)
{ {
return ((oc_rect){ 0 }); return ((oc_text_metrics){ 0 });
} }
oc_arena_scope scratch = oc_scratch_begin(); oc_arena_scope scratch = oc_scratch_begin();
@ -772,86 +779,123 @@ oc_rect oc_text_bounding_box_utf32(oc_font font, f32 fontSize, oc_str32 codePoin
//NOTE(martin): find width of missing character //NOTE(martin): find width of missing character
//TODO(martin): should cache that at font creation... //TODO(martin): should cache that at font creation...
oc_text_extents missingGlyphExtents; oc_glyph_metrics missingGlyphMetrics = { 0 };
u32 missingGlyphIndex = oc_font_get_glyph_index_from_font_data(fontData, 0xfffd); u32 missingGlyphIndex = oc_font_get_glyph_index_from_font_data(fontData, 0xfffd);
if(missingGlyphIndex) if(missingGlyphIndex)
{ {
oc_font_get_glyph_extents_from_font_data(fontData, (oc_str32){ 1, &missingGlyphIndex }, &missingGlyphExtents); oc_font_get_glyph_metrics_from_font_data(fontData, (oc_str32){ 1, &missingGlyphIndex }, &missingGlyphMetrics);
} }
else else
{ {
//NOTE(martin): could not find replacement glyph, try to get an 'x' to get a somewhat correct width //NOTE(martin): could not find replacement glyph, try to get an 'X' to get a somewhat correct dimensions
// to render an empty rectangle. Otherwise just render with the max font width // to render an empty rectangle. Otherwise just render with the max font width
f32 boxWidth = fontData->extents.width * 0.8;
f32 xBearing = fontData->extents.width * 0.1;
f32 xAdvance = fontData->extents.width;
missingGlyphIndex = oc_font_get_glyph_index_from_font_data(fontData, 'x'); missingGlyphIndex = oc_font_get_glyph_index_from_font_data(fontData, 'X');
if(missingGlyphIndex) if(missingGlyphIndex)
{ {
oc_font_get_glyph_extents_from_font_data(fontData, (oc_str32){ 1, &missingGlyphIndex }, &missingGlyphExtents); oc_font_get_glyph_metrics_from_font_data(fontData, (oc_str32){ 1, &missingGlyphIndex }, &missingGlyphMetrics);
} }
else else
{ {
missingGlyphExtents.xBearing = fontData->extents.width * 0.1; missingGlyphMetrics = (oc_glyph_metrics){
missingGlyphExtents.yBearing = 0; .ink = {
missingGlyphExtents.width = fontData->extents.width * 0.8; .x = fontData->metrics.width * 0.1,
missingGlyphExtents.xAdvance = fontData->extents.width; .y = -fontData->metrics.ascent,
missingGlyphExtents.yAdvance = 0; .w = fontData->metrics.width * 0.8,
.h = fontData->metrics.ascent,
},
.advance = { .x = fontData->metrics.width, .y = 0 },
};
} }
} }
//NOTE(martin): accumulate text extents //NOTE(martin): accumulate text extents
f32 width = 0; oc_text_metrics metrics = { 0 };
f32 x = 0; f32 lineHeight = fontData->metrics.descent + fontData->metrics.ascent + fontData->metrics.lineGap;
f32 y = 0;
f32 lineHeight = fontData->extents.descent + fontData->extents.ascent; if(glyphIndices.len)
{
metrics.logical.y = -(fontData->metrics.ascent + fontData->metrics.lineGap);
metrics.logical.h += lineHeight + fontData->metrics.lineGap;
}
f32 inkX0 = 0, inkX1 = 0, inkY0 = 0, inkY1 = 0;
for(int i = 0; i < glyphIndices.len; i++) for(int i = 0; i < glyphIndices.len; i++)
{ {
//TODO(martin): make it failsafe for fonts that don't have a glyph for the line-feed codepoint ? //TODO(martin): make it failsafe for fonts that don't have a glyph for the line-feed codepoint ?
oc_glyph_data* glyph = 0; oc_glyph_data* glyph = 0;
oc_text_extents extents; oc_glyph_metrics glyphMetrics;
if(!glyphIndices.ptr[i] || glyphIndices.ptr[i] >= fontData->glyphCount) if(!glyphIndices.ptr[i] || glyphIndices.ptr[i] >= fontData->glyphCount)
{ {
extents = missingGlyphExtents; glyphMetrics = missingGlyphMetrics;
} }
else else
{ {
glyph = oc_font_get_glyph_data(fontData, glyphIndices.ptr[i]); glyph = oc_font_get_glyph_data(fontData, glyphIndices.ptr[i]);
extents = glyph->extents; glyphMetrics = glyph->metrics;
} }
x += extents.xAdvance;
y += extents.yAdvance; inkX0 = oc_min(inkX0, metrics.advance.x + glyphMetrics.ink.x);
inkX1 = oc_max(inkX1, metrics.advance.x + glyphMetrics.ink.x + glyphMetrics.ink.w);
inkY0 = oc_min(inkY0, metrics.advance.y + glyphMetrics.ink.y);
inkY1 = oc_max(inkY1, metrics.advance.y + glyphMetrics.ink.y + glyphMetrics.ink.h);
metrics.advance.x += glyphMetrics.advance.x;
metrics.advance.y += glyphMetrics.advance.y;
metrics.logical.w = oc_max(metrics.logical.w, metrics.advance.x);
if(glyph && glyph->codePoint == '\n') if(glyph && glyph->codePoint == '\n')
{ {
width = oc_max(width, x); metrics.advance.y += lineHeight;
x = 0; metrics.advance.x = 0;
y += lineHeight + fontData->extents.leading;
if(i < glyphIndices.len - 1)
{
metrics.logical.h += lineHeight;
} }
} }
width = oc_max(width, x); }
metrics.ink = (oc_rect){
inkX0,
inkY0,
inkX1 - inkX0,
inkY1 - inkY0
};
OC_ASSERT(metrics.ink.y <= 0);
f32 fontScale = oc_font_get_scale_for_em_pixels(font, fontSize); f32 fontScale = oc_font_get_scale_for_em_pixels(font, fontSize);
oc_rect rect = { 0, -fontData->extents.ascent * fontScale, width * fontScale, (y + lineHeight) * fontScale };
metrics.ink.x *= fontScale;
metrics.ink.y *= fontScale;
metrics.ink.w *= fontScale;
metrics.ink.h *= fontScale;
metrics.logical.x *= fontScale;
metrics.logical.y *= fontScale;
metrics.logical.w *= fontScale;
metrics.logical.h *= fontScale;
metrics.advance.x *= fontScale;
metrics.advance.y *= fontScale;
oc_scratch_end(scratch); oc_scratch_end(scratch);
return (rect); return (metrics);
} }
oc_rect oc_text_bounding_box(oc_font font, f32 fontSize, oc_str8 text) oc_text_metrics oc_font_text_metrics(oc_font font, f32 fontSize, oc_str8 text)
{ {
if(!text.len || !text.ptr) if(!text.len || !text.ptr)
{ {
return ((oc_rect){ 0 }); return ((oc_text_metrics){ 0 });
} }
oc_arena_scope scratch = oc_scratch_begin(); oc_arena_scope scratch = oc_scratch_begin();
oc_str32 codePoints = oc_utf8_push_to_codepoints(scratch.arena, text); oc_str32 codePoints = oc_utf8_push_to_codepoints(scratch.arena, text);
oc_rect result = oc_text_bounding_box_utf32(font, fontSize, codePoints); oc_text_metrics result = oc_font_text_metrics_utf32(font, fontSize, codePoints);
oc_scratch_end(scratch); oc_scratch_end(scratch);
return (result); return (result);
} }
@ -1377,31 +1421,40 @@ oc_rect oc_glyph_outlines_from_font_data(oc_font_data* fontData, oc_str32 glyphI
glyphIndex = oc_font_get_glyph_index_from_font_data(fontData, 0xfffd); glyphIndex = oc_font_get_glyph_index_from_font_data(fontData, 0xfffd);
if(!glyphIndex) if(!glyphIndex)
{ {
//NOTE(martin): could not find replacement glyph, try to get an 'x' to get a somewhat correct width //NOTE(martin): could not find replacement glyph, try to get an 'X' to get a somewhat correct dimensions
// to render an empty rectangle. Otherwise just render with the max font width // to render an empty rectangle. Otherwise just render with the max font width
f32 boxWidth = fontData->extents.width * 0.8;
f32 xBearing = fontData->extents.width * 0.1;
f32 xAdvance = fontData->extents.width;
glyphIndex = oc_font_get_glyph_index_from_font_data(fontData, 'x'); oc_glyph_metrics missingGlyphMetrics = { 0 };
if(glyphIndex) u32 missingGlyphIndex = oc_font_get_glyph_index_from_font_data(fontData, 'X');
if(missingGlyphIndex)
{ {
oc_glyph_data* glyph = &(fontData->glyphs[glyphIndex]); oc_font_get_glyph_metrics_from_font_data(fontData, (oc_str32){ .len = 1, .ptr = &missingGlyphIndex }, &missingGlyphMetrics);
boxWidth = glyph->extents.width;
xBearing = glyph->extents.xBearing;
xAdvance = glyph->extents.xAdvance;
} }
else
{
missingGlyphMetrics = (oc_glyph_metrics){
.ink = {
.x = fontData->metrics.width * 0.1,
.y = -fontData->metrics.ascent,
.w = fontData->metrics.width * 0.8,
.h = fontData->metrics.ascent,
},
.advance = { .x = fontData->metrics.width, .y = 0 },
};
}
f32 oldStrokeWidth = canvas->attributes.width; f32 oldStrokeWidth = canvas->attributes.width;
oc_set_width(boxWidth * 0.005); oc_set_width(missingGlyphMetrics.ink.w * 0.005);
oc_rectangle_stroke(xOffset + xBearing * scale, oc_rectangle_stroke(xOffset + missingGlyphMetrics.ink.x * scale,
yOffset, yOffset + missingGlyphMetrics.ink.y * scale,
boxWidth * scale * flip, missingGlyphMetrics.ink.w * scale * flip,
fontData->extents.capHeight * scale); missingGlyphMetrics.ink.h * scale);
oc_set_width(oldStrokeWidth); oc_set_width(oldStrokeWidth);
oc_move_to(xOffset + xAdvance * scale, yOffset); oc_move_to(xOffset + missingGlyphMetrics.advance.x * scale, yOffset);
maxWidth = oc_max(maxWidth, xOffset + xAdvance * scale - startX); maxWidth = oc_max(maxWidth, xOffset + missingGlyphMetrics.advance.x * scale - startX);
continue; continue;
} }
} }
@ -1419,11 +1472,11 @@ oc_rect oc_glyph_outlines_from_font_data(oc_font_data* fontData, oc_str32 glyphI
elements[eltIndex].p[pIndex].y = elements[eltIndex].p[pIndex].y * scale * flip + yOffset; elements[eltIndex].p[pIndex].y = elements[eltIndex].p[pIndex].y * scale * flip + yOffset;
} }
} }
oc_move_to(xOffset + scale * glyph->extents.xAdvance, yOffset); oc_move_to(xOffset + scale * glyph->metrics.advance.x, yOffset);
maxWidth = oc_max(maxWidth, xOffset + scale * glyph->extents.xAdvance - startX); maxWidth = oc_max(maxWidth, xOffset + scale * glyph->metrics.advance.x - startX);
} }
f32 lineHeight = (fontData->extents.ascent + fontData->extents.descent) * scale; f32 lineHeight = (fontData->metrics.ascent + fontData->metrics.descent) * scale;
oc_rect box = { startX, startY, maxWidth, canvas->subPathLastPoint.y - startY + lineHeight }; oc_rect box = { startX, startY, maxWidth, canvas->subPathLastPoint.y - startY + lineHeight };
return (box); return (box);
} }

View File

@ -964,7 +964,7 @@ void oc_ui_styling_prepass(oc_ui_context* ui, oc_ui_box* box, oc_list* before, o
if(desiredSize[OC_UI_AXIS_X].kind == OC_UI_SIZE_TEXT if(desiredSize[OC_UI_AXIS_X].kind == OC_UI_SIZE_TEXT
|| desiredSize[OC_UI_AXIS_Y].kind == OC_UI_SIZE_TEXT) || desiredSize[OC_UI_AXIS_Y].kind == OC_UI_SIZE_TEXT)
{ {
textBox = oc_text_bounding_box(style->font, style->fontSize, box->string); textBox = oc_font_text_metrics(style->font, style->fontSize, box->string).logical;
} }
for(int i = 0; i < OC_UI_AXIS_COUNT; i++) for(int i = 0; i < OC_UI_AXIS_COUNT; i++)
@ -1362,7 +1362,7 @@ void oc_ui_draw_box(oc_ui_box* box)
if(draw && (box->flags & OC_UI_FLAG_DRAW_TEXT)) if(draw && (box->flags & OC_UI_FLAG_DRAW_TEXT))
{ {
oc_rect textBox = oc_text_bounding_box(style->font, style->fontSize, box->string); oc_rect textBox = oc_font_text_metrics(style->font, style->fontSize, box->string).logical;
f32 x = 0; f32 x = 0;
f32 y = 0; f32 y = 0;
@ -2508,7 +2508,7 @@ oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_
oc_rect bbox = { 0 }; oc_rect bbox = { 0 };
for(int i = 0; i < info->optionCount; i++) for(int i = 0; i < info->optionCount; i++)
{ {
bbox = oc_text_bounding_box(button->style.font, button->style.fontSize, info->options[i]); bbox = oc_font_text_metrics(button->style.font, button->style.fontSize, info->options[i]).logical;
maxOptionWidth = oc_max(maxOptionWidth, bbox.w); maxOptionWidth = oc_max(maxOptionWidth, bbox.w);
} }
f32 buttonWidth = maxOptionWidth + 2 * button->style.layout.margin.x + button->rect.h; f32 buttonWidth = maxOptionWidth + 2 * button->style.layout.margin.x + button->rect.h;
@ -3305,11 +3305,11 @@ void oc_ui_text_box_render(oc_ui_box* box, void* data)
} }
oc_ui_style* style = &box->style; oc_ui_style* style = &box->style;
oc_font_extents extents = oc_font_get_scaled_extents(style->font, style->fontSize); oc_font_metrics extents = oc_font_get_metrics(style->font, style->fontSize);
f32 lineHeight = extents.ascent + extents.descent; f32 lineHeight = extents.ascent + extents.descent;
oc_str32 before = oc_str32_slice(codepoints, 0, firstDisplayedChar); oc_str32 before = oc_str32_slice(codepoints, 0, firstDisplayedChar);
oc_rect beforeBox = oc_text_bounding_box_utf32(style->font, style->fontSize, before); oc_rect beforeBox = oc_font_text_metrics_utf32(style->font, style->fontSize, before).logical;
f32 textX = box->rect.x - beforeBox.w; f32 textX = box->rect.x - beforeBox.w;
f32 textTop = box->rect.y + 0.5 * (box->rect.h - lineHeight); f32 textTop = box->rect.y + 0.5 * (box->rect.h - lineHeight);
@ -3321,7 +3321,7 @@ void oc_ui_text_box_render(oc_ui_box* box, void* data)
u32 selectEnd = oc_max(ui->editCursor, ui->editMark); u32 selectEnd = oc_max(ui->editCursor, ui->editMark);
oc_str32 beforeSelect = oc_str32_slice(codepoints, 0, selectStart); oc_str32 beforeSelect = oc_str32_slice(codepoints, 0, selectStart);
oc_rect beforeSelectBox = oc_text_bounding_box_utf32(style->font, style->fontSize, beforeSelect); oc_rect beforeSelectBox = oc_font_text_metrics_utf32(style->font, style->fontSize, beforeSelect).logical;
beforeSelectBox.x += textX; beforeSelectBox.x += textX;
beforeSelectBox.y += textY; beforeSelectBox.y += textY;
@ -3329,8 +3329,8 @@ void oc_ui_text_box_render(oc_ui_box* box, void* data)
{ {
oc_str32 select = oc_str32_slice(codepoints, selectStart, selectEnd); oc_str32 select = oc_str32_slice(codepoints, selectStart, selectEnd);
oc_str32 afterSelect = oc_str32_slice(codepoints, selectEnd, codepoints.len); oc_str32 afterSelect = oc_str32_slice(codepoints, selectEnd, codepoints.len);
oc_rect selectBox = oc_text_bounding_box_utf32(style->font, style->fontSize, select); oc_rect selectBox = oc_font_text_metrics_utf32(style->font, style->fontSize, select).logical;
oc_rect afterSelectBox = oc_text_bounding_box_utf32(style->font, style->fontSize, afterSelect); oc_rect afterSelectBox = oc_font_text_metrics_utf32(style->font, style->fontSize, afterSelect).logical;
selectBox.x += beforeSelectBox.x + beforeSelectBox.w; selectBox.x += beforeSelectBox.x + beforeSelectBox.w;
selectBox.y += textY; selectBox.y += textY;
@ -3432,7 +3432,7 @@ oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8
oc_ui_box* textBox = oc_ui_box_make("text", OC_UI_FLAG_CLIP | OC_UI_FLAG_DRAW_PROC); oc_ui_box* textBox = oc_ui_box_make("text", OC_UI_FLAG_CLIP | OC_UI_FLAG_DRAW_PROC);
oc_ui_tag_box(textBox, "text"); oc_ui_tag_box(textBox, "text");
oc_font_extents extents = oc_font_get_scaled_extents(font, fontSize); oc_font_metrics extents = oc_font_get_metrics(font, fontSize);
oc_ui_sig sig = oc_ui_box_sig(frame); oc_ui_sig sig = oc_ui_box_sig(frame);
@ -3464,7 +3464,7 @@ oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8
f32 x = 0; f32 x = 0;
for(int i = ui->editFirstDisplayedChar; i < codepoints.len; i++) for(int i = ui->editFirstDisplayedChar; i < codepoints.len; i++)
{ {
oc_rect bbox = oc_text_bounding_box_utf32(font, fontSize, oc_str32_slice(codepoints, i, i + 1)); oc_rect bbox = oc_font_text_metrics_utf32(font, fontSize, oc_str32_slice(codepoints, i, i + 1)).logical;
if(x < cursorX) if(x < cursorX)
{ {
hoveredChar = i; hoveredChar = i;
@ -3520,7 +3520,7 @@ oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8
} }
else if(ui->editSelectionMode == OC_UI_EDIT_MOVE_LINE) else if(ui->editSelectionMode == OC_UI_EDIT_MOVE_LINE)
{ {
oc_rect bbox = oc_text_bounding_box_utf32(font, fontSize, codepoints); oc_rect bbox = oc_font_text_metrics_utf32(font, fontSize, codepoints).logical;
if(fabsf(bbox.w - cursorX) < fabsf(cursorX)) if(fabsf(bbox.w - cursorX) < fabsf(cursorX))
{ {
ui->editCursor = codepoints.len; ui->editCursor = codepoints.len;
@ -3537,8 +3537,8 @@ oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8
if(oc_min(ui->editCursor, ui->editMark) == oc_min(ui->editWordSelectionInitialCursor, ui->editWordSelectionInitialMark) if(oc_min(ui->editCursor, ui->editMark) == oc_min(ui->editWordSelectionInitialCursor, ui->editWordSelectionInitialMark)
&& oc_max(ui->editCursor, ui->editMark) == oc_max(ui->editWordSelectionInitialCursor, ui->editWordSelectionInitialMark)) && oc_max(ui->editCursor, ui->editMark) == oc_max(ui->editWordSelectionInitialCursor, ui->editWordSelectionInitialMark))
{ {
oc_rect editCursorPrefixBbox = oc_text_bounding_box_utf32(font, fontSize, oc_str32_slice(codepoints, 0, ui->editCursor)); oc_rect editCursorPrefixBbox = oc_font_text_metrics_utf32(font, fontSize, oc_str32_slice(codepoints, 0, ui->editCursor)).logical;
oc_rect editMarkPrefixBbox = oc_text_bounding_box_utf32(font, fontSize, oc_str32_slice(codepoints, 0, ui->editMark)); oc_rect editMarkPrefixBbox = oc_font_text_metrics_utf32(font, fontSize, oc_str32_slice(codepoints, 0, ui->editMark)).logical;
f32 editCursorX = editCursorPrefixBbox.w; f32 editCursorX = editCursorPrefixBbox.w;
f32 editMarkX = editMarkPrefixBbox.w; f32 editMarkX = editMarkPrefixBbox.w;
if(fabsf(cursorX - editMarkX) < fabsf(cursorX - editCursorX)) if(fabsf(cursorX - editMarkX) < fabsf(cursorX - editCursorX))
@ -3665,13 +3665,13 @@ oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8
{ {
i32 firstDisplayedChar = ui->editFirstDisplayedChar; i32 firstDisplayedChar = ui->editFirstDisplayedChar;
oc_str32 firstToCursor = oc_str32_slice(codepoints, firstDisplayedChar, ui->editCursor); oc_str32 firstToCursor = oc_str32_slice(codepoints, firstDisplayedChar, ui->editCursor);
oc_rect firstToCursorBox = oc_text_bounding_box_utf32(font, fontSize, firstToCursor); oc_rect firstToCursorBox = oc_font_text_metrics_utf32(font, fontSize, firstToCursor).logical;
while(firstToCursorBox.w > textBox->rect.w) while(firstToCursorBox.w > textBox->rect.w)
{ {
firstDisplayedChar++; firstDisplayedChar++;
firstToCursor = oc_str32_slice(codepoints, firstDisplayedChar, ui->editCursor); firstToCursor = oc_str32_slice(codepoints, firstDisplayedChar, ui->editCursor);
firstToCursorBox = oc_text_bounding_box_utf32(font, fontSize, firstToCursor); firstToCursorBox = oc_font_text_metrics_utf32(font, fontSize, firstToCursor).logical;
} }
ui->editFirstDisplayedChar = firstDisplayedChar; ui->editFirstDisplayedChar = firstDisplayedChar;