Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save stephenwithav/9785ec6ac68595f71dbb47dbaa20afbb to your computer and use it in GitHub Desktop.
Save stephenwithav/9785ec6ac68595f71dbb47dbaa20afbb to your computer and use it in GitHub Desktop.
SDL Render text with Freetype and harfbuzz, using outline render.
#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(&params, 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, &params);
} // 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