/** * @file text.c * @brief Implements the text subsystem. */ #include #include #include #include #include #include akerr_ErrorContext AKERR_NOIGNORE *akgl_text_loadfont(char *name, char *filepath, int size) { TTF_Font *font = NULL; PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, name, AKERR_NULLPOINTER, "Null font name"); 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()); // Loading over an existing name used to abandon the font it displaced -- // there was nothing in the library that could close one. The old font goes // back first now, and only once the new one has opened, so a failed load // leaves the caller with the font they already had. if ( SDL_GetPointerProperty(AKGL_REGISTRY_FONT, name, NULL) != NULL ) { PASS(errctx, akgl_text_unloadfont(name)); } FAIL_ZERO_RETURN( errctx, SDL_SetPointerProperty(AKGL_REGISTRY_FONT, name, (void *)font), AKERR_KEY, "Unable to add font %p to registry as %s : %s", (void *)font, name, SDL_GetError()); SUCCEED_RETURN(errctx); } akerr_ErrorContext AKERR_NOIGNORE *akgl_text_unloadfont(char *name) { TTF_Font *font = NULL; PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, name, AKERR_NULLPOINTER, "Null font name"); font = (TTF_Font *)SDL_GetPointerProperty(AKGL_REGISTRY_FONT, name, NULL); FAIL_ZERO_RETURN(errctx, font, AKERR_KEY, "No font named %s in the registry", name); // Cleared before the close, so a font is never reachable through the // registry after it has been handed back to SDL_ttf. SDL_ClearProperty(AKGL_REGISTRY_FONT, name); TTF_CloseFont(font); SUCCEED_RETURN(errctx); } akerr_ErrorContext AKERR_NOIGNORE *akgl_text_rendertextat(TTF_Font *font, char *text, SDL_Color color, int wraplength, int x, int y) { SDL_Surface *textsurf = NULL; SDL_Texture *texture = NULL; SDL_FRect dest; PREPARE_ERROR(errctx); FAIL_ZERO_RETURN(errctx, font, AKERR_NULLPOINTER, "NULL font"); FAIL_ZERO_RETURN(errctx, text, AKERR_NULLPOINTER, "NULL text string"); // Checked before anything is rasterized, and checked at all because a // backend that has an SDL_Renderer but was never run through // akgl_render_2d_bind has a NULL draw_texture -- which is exactly the state // akgl_render_2d_init used to be the only escape from. FAIL_ZERO_RETURN(errctx, akgl_renderer, AKERR_NULLPOINTER, "No renderer backend"); FAIL_ZERO_RETURN(errctx, akgl_renderer->sdl_renderer, AKERR_NULLPOINTER, "No valid SDL rendering backend"); FAIL_ZERO_RETURN(errctx, akgl_renderer->draw_texture, AKERR_NULLPOINTER, "Renderer backend has no draw_texture"); if ( wraplength > 0 ) { textsurf = TTF_RenderText_Blended_Wrapped( font, text, 0, color, wraplength); } else { textsurf = TTF_RenderText_Blended( font, text, 0, color); } FAIL_ZERO_RETURN(errctx, textsurf, AKERR_NULLPOINTER, "%s", SDL_GetError()); texture = SDL_CreateTextureFromSurface(akgl_renderer->sdl_renderer, textsurf); FAIL_ZERO_RETURN(errctx, texture, AKERR_NULLPOINTER, "%s", SDL_GetError()); dest.x = x; dest.y = y; SDL_GetTextureSize(texture, &dest.w, &dest.h); PASS(errctx, akgl_renderer->draw_texture(akgl_renderer, texture, NULL, &dest, 0, NULL, SDL_FLIP_NONE)); SDL_DestroyTexture(texture); 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); }