Measure rendered text without drawing it
akbasic needs the advance width of one character cell to build a terminal-style
text surface, and the wrapped size to know where a string crosses the right
margin. akgl_text_measure and akgl_text_measure_wrapped report both over
TTF_GetStringSize and TTF_GetStringSizeWrapped; neither needs a renderer, so
this half of the text subsystem is testable without the offscreen harness.
A negative wrap length is refused with AKERR_OUTOFBOUNDS: SDL_ttf reads it as a
very large unsigned width and quietly stops wrapping, which would return a
measurement that is wrong rather than an error the caller can see.
Also fixes akgl_text_loadfont, which checked name twice and passed an unchecked
filepath to TTF_OpenFont (TODO item 39).
The fixture font is a 10 KB monospaced ASCII subset of Liberation Mono, renamed
because "Liberation" is a Reserved Font Name; see
tests/assets/akgl_test_mono.LICENSE.txt. Monospaced so the suite can assert
width("AAAA") == 4 * width("A") rather than hardcode glyph metrics.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
45
src/text.c
45
src/text.c
@@ -16,7 +16,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_loadfont(char *name, char *filepath
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, name, AKERR_NULLPOINTER, "Null font name");
|
||||
FAIL_ZERO_RETURN(errctx, name, AKERR_NULLPOINTER, "Null filepath");
|
||||
FAIL_ZERO_RETURN(errctx, filepath, AKERR_NULLPOINTER, "Null filepath");
|
||||
font = TTF_OpenFont(filepath, size);
|
||||
FAIL_ZERO_RETURN(errctx, font, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
FAIL_ZERO_RETURN(
|
||||
@@ -64,3 +64,46 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_rendertextat(TTF_Font *font, char *
|
||||
SDL_DestroySurface(textsurf);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_text_measure(TTF_Font *font, char *text, int *w, int *h)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, font, AKERR_NULLPOINTER, "NULL font");
|
||||
FAIL_ZERO_RETURN(errctx, text, AKERR_NULLPOINTER, "NULL text string");
|
||||
FAIL_ZERO_RETURN(errctx, w, AKERR_NULLPOINTER, "NULL width destination");
|
||||
FAIL_ZERO_RETURN(errctx, h, AKERR_NULLPOINTER, "NULL height destination");
|
||||
// A zero length means "the string is null terminated", not "the empty
|
||||
// string" -- an empty text measures 0 wide and one line high.
|
||||
FAIL_ZERO_RETURN(
|
||||
errctx,
|
||||
TTF_GetStringSize(font, text, 0, w, h),
|
||||
AKGL_ERR_SDL,
|
||||
"%s",
|
||||
SDL_GetError());
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_text_measure_wrapped(TTF_Font *font, char *text, int wraplength, int *w, int *h)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, font, AKERR_NULLPOINTER, "NULL font");
|
||||
FAIL_ZERO_RETURN(errctx, text, AKERR_NULLPOINTER, "NULL text string");
|
||||
FAIL_ZERO_RETURN(errctx, w, AKERR_NULLPOINTER, "NULL width destination");
|
||||
FAIL_ZERO_RETURN(errctx, h, AKERR_NULLPOINTER, "NULL height destination");
|
||||
// SDL_ttf takes the wrap width as an int and reads a negative one as a
|
||||
// very large unsigned width, which silently disables wrapping instead of
|
||||
// reporting anything. Refuse it here rather than return a wrong measurement.
|
||||
FAIL_NONZERO_RETURN(
|
||||
errctx,
|
||||
(wraplength < 0),
|
||||
AKERR_OUTOFBOUNDS,
|
||||
"Wrap length %d is negative",
|
||||
wraplength);
|
||||
FAIL_ZERO_RETURN(
|
||||
errctx,
|
||||
TTF_GetStringSizeWrapped(font, text, 0, wraplength, w, h),
|
||||
AKGL_ERR_SDL,
|
||||
"%s",
|
||||
SDL_GetError());
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user