Forked from wutipong/sdl_render-text-with-direction.cpp
Created
January 2, 2022 19:51
-
-
Save stephenwithav/9785ec6ac68595f71dbb47dbaa20afbb to your computer and use it in GitHub Desktop.
SDL Render text with Freetype and harfbuzz, using outline render.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <ft2build.h> | |
#include FT_FREETYPE_H | |
#include FT_OUTLINE_H | |
#include <SDL2/SDL.h> | |
#include <SDL2/SDL_main.h> | |
#include <string> | |
#include <harfbuzz/hb.h> | |
#include <harfbuzz/hb-ft.h> | |
enum class Direction { | |
LeftToRight = HB_DIRECTION_LTR, | |
TopToBottom = HB_DIRECTION_TTB | |
}; | |
const std::wstring TEXT = | |
L"「大切な日本の皆様、私は無事です。ご安心ください。」"; | |
const std::string fontPath = "NotoSansCJKjp-Regular.otf"; | |
const SDL_Point start = {30, 100}; | |
const int fontSize = 24; | |
const unsigned int WIDTH = 1024; | |
const unsigned int HEIGHT = 768; | |
const SDL_Color color = {0xff, 0xa0, 0x80, 0xff}; | |
SDL_Renderer *renderer; | |
FT_Library library; | |
struct SpanAdditionData { | |
SDL_Point dest; | |
SDL_Color color; | |
SDL_Rect bound; | |
}; | |
void DrawSpansCallback(const int y, const int count, const FT_Span *const spans, | |
void *const user) { | |
SpanAdditionData *addl = static_cast<SpanAdditionData *>(user); | |
for (int i = 0; i < count; ++i) { | |
int x1 = spans[i].x + addl->dest.x; | |
int y1 = addl->bound.h - (addl->dest.y + y); | |
int x2 = x1 + spans[i].len; | |
int y2 = y1; | |
SDL_SetRenderDrawColor(renderer, addl->color.r, addl->color.g, | |
addl->color.b, spans[i].coverage); | |
SDL_RenderDrawLine(renderer, x1, y1, x2, y2); | |
} | |
} | |
void DrawText(const std::wstring &text, const SDL_Color &color, | |
const SDL_Point &start, const FT_Face &face, hb_font_t *hb_font, | |
SDL_Renderer *&renderer, | |
const Direction &direction = Direction::LeftToRight) { | |
hb_buffer_t *buffer = hb_buffer_create(); | |
hb_buffer_set_direction(buffer, (hb_direction_t)direction); | |
hb_buffer_set_script(buffer, HB_SCRIPT_COMMON); | |
hb_buffer_add_utf16(buffer, (unsigned short *)(text.c_str()), text.length(), | |
0, text.length()); | |
hb_shape(hb_font, buffer, NULL, 0); | |
const unsigned int glyph_count = hb_buffer_get_length(buffer); | |
const hb_glyph_info_t *glyph_infos = hb_buffer_get_glyph_infos(buffer, NULL); | |
const hb_glyph_position_t *glyph_positions = | |
hb_buffer_get_glyph_positions(buffer, NULL); | |
SpanAdditionData addl; | |
addl.color = color; | |
SDL_Rect bound{0, 0, 0, 0}; | |
bound.x = face->bbox.xMin >> 6; | |
bound.y = face->bbox.yMin >> 6; | |
if (direction == Direction::LeftToRight) { | |
bound.w = ((face->max_advance_width * glyph_count) - face->bbox.xMin) >> 6; | |
bound.h = (face->bbox.yMax - face->bbox.yMin) >> 6; | |
} else if (direction == Direction::TopToBottom) { | |
bound.w = (face->bbox.xMax - face->bbox.xMin) >> 6; | |
bound.h = ((face->max_advance_height * glyph_count) - face->bbox.yMin) >> 6; | |
bound.y = -(bound.h + bound.y); | |
} | |
auto currentTarget = SDL_GetRenderTarget(renderer); | |
auto target = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, | |
SDL_TEXTUREACCESS_TARGET, bound.w, bound.h); | |
SDL_SetRenderTarget(renderer, target); | |
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); | |
SDL_Point pos = {-bound.x, -bound.y}; | |
addl.bound = bound; | |
for (unsigned int i = 0; i < glyph_count; i++) { | |
FT_Load_Glyph(face, glyph_infos[i].codepoint, FT_LOAD_NO_BITMAP); | |
addl.dest = {pos.x + (glyph_positions[i].x_offset >> 6), | |
pos.y + (glyph_positions[i].y_offset >> 6)}; | |
if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { | |
FT_Raster_Params params; | |
memset(¶ms, 0, sizeof(params)); | |
params.flags = FT_RASTER_FLAG_AA | FT_RASTER_FLAG_DIRECT; | |
params.gray_spans = DrawSpansCallback; | |
params.user = &addl; | |
FT_Outline_Render(library, &face->glyph->outline, ¶ms); | |
} // No fallback | |
pos.x += (glyph_positions[i].x_advance >> 6); | |
pos.y += (glyph_positions[i].y_advance >> 6); | |
} | |
hb_buffer_destroy(buffer); | |
SDL_SetRenderTarget(renderer, currentTarget); | |
SDL_Rect dstRect{start.x + (face->bbox.xMin >> 6), | |
start.y - (face->bbox.yMax >> 6), bound.w, bound.h}; | |
SDL_SetRenderDrawColor(renderer, 0xff, 0xff, 0xff, 0xff); | |
SDL_RenderDrawRect(renderer, &dstRect); | |
SDL_SetTextureBlendMode(target, SDL_BLENDMODE_BLEND); | |
SDL_RenderCopyEx(renderer, target, NULL, &dstRect, 0, NULL, SDL_FLIP_NONE); | |
} | |
int main(int argc, char **argv) { | |
SDL_Init(SDL_INIT_EVERYTHING); | |
SDL_Window *window = | |
SDL_CreateWindow("Test Window", SDL_WINDOWPOS_UNDEFINED, | |
SDL_WINDOWPOS_UNDEFINED, WIDTH, HEIGHT, 0); | |
renderer = SDL_CreateRenderer(window, -1, 0); | |
FT_Init_FreeType(&library); | |
FT_Face face; | |
FT_New_Face(library, fontPath.c_str(), 0, &face); | |
FT_Set_Pixel_Sizes(face, 0, fontSize); | |
hb_font_t *hb_font = hb_ft_font_create(face, 0); | |
while (true) { | |
SDL_Event event; | |
if (SDL_PollEvent(&event)) { | |
if (event.type == SDL_QUIT) | |
break; | |
} | |
SDL_SetRenderDrawColor(renderer, 0x50, 0x82, 0xaa, 0xff); | |
SDL_RenderClear(renderer); | |
SDL_SetRenderDrawColor(renderer, 0xff, 0x00, 0xff, 0xff); | |
SDL_Rect baseLines{start.x, start.y, 5000, 5000}; | |
SDL_RenderDrawRect(renderer, &baseLines); | |
DrawText(TEXT, color, start, face, hb_font, renderer, | |
Direction::LeftToRight); | |
DrawText(TEXT, color, start, face, hb_font, renderer, | |
Direction::TopToBottom); | |
SDL_RenderPresent(renderer); | |
SDL_Delay(10); | |
} | |
hb_font_destroy(hb_font); | |
FT_Done_Face(face); | |
FT_Done_FreeType(library); | |
SDL_DestroyWindow(window); | |
SDL_Quit(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment