Created
October 26, 2021 17:44
-
-
Save kdchambers/c6533d43458af5304f976f2d8c3c068e to your computer and use it in GitHub Desktop.
This file is responsible for taking a font file (E.g ttf) and creating a texture bitmap containing all the requested glyphs at the desired size. This texture is then sampled by the vulkan engine to render text to screen
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
// SPDX-License-Identifier: GPL-3.0 | |
// Copyright (c) 2021 Keith Chambers | |
// This program is free software: you can redistribute it and/or modify it under the terms | |
// of the GNU General Public License as published by the Free Software Foundation, version 3. | |
// NOTE: This snippet is taken from https://github.com/kdchambers/VulkanGUIExample/blob/master/text.cpp | |
// This file is responsible for taking a font file (E.g ttf) and creating a texture bitmap containing all the | |
// requested glyphs at the desired size. This texture is then sampled by the vulkan engine to render text to screen | |
#include "text.h" | |
static bool mapCharTextureToMesh(FontBitmap& font_bitmap, const char c, glm::vec2* start_texture_map, uint16_t texture_map_stride); | |
static void printGlyphInformation(FT_GlyphSlot glyph); | |
static void createRectMesh(uint16_t * indices, glm::vec2 * vertices, uint16_t vertices_stride_bytes, uint16_t start_vertex_index, float x, float y, float height, float width); | |
inline double signedNormalizePixelPosition(const uint32_t position_pixels, const uint32_t length_pixels) | |
{ | |
assert(position_pixels <= length_pixels); | |
assert(length_pixels != 0); | |
double result = static_cast<double>(position_pixels) / length_pixels; | |
result *= 2; | |
result -= 1.0; | |
return result; | |
} | |
inline double unsignedNormalizePixelPosition(const uint32_t position_pixels, const uint32_t length_pixels) | |
{ | |
assert(position_pixels <= length_pixels); | |
assert(length_pixels != 0); | |
return static_cast<double>(position_pixels) / length_pixels; | |
} | |
static bool mapCharTextureToMesh(FontBitmap& font_bitmap, const char c, glm::vec2* start_texture_map, uint16_t texture_map_stride) | |
{ | |
float x_pos_norm = std::get<1>(font_bitmap.char_data[c]).x; | |
float y_pos_norm = std::get<1>(font_bitmap.char_data[c]).y; | |
float width_norm = static_cast<float>(unsignedNormalizePixelPosition(std::get<0>(font_bitmap.char_data[c]).width, font_bitmap.texture_width)); | |
float height_norm = static_cast<float>(unsignedNormalizePixelPosition(std::get<0>(font_bitmap.char_data[c]).height, font_bitmap.texture_height)); | |
uint8_t * byte_pos = reinterpret_cast<uint8_t *>(start_texture_map); | |
*reinterpret_cast<glm::vec2 *>(byte_pos) = {x_pos_norm, y_pos_norm + height_norm}; // Top, left | |
*reinterpret_cast<glm::vec2 *>(byte_pos + (texture_map_stride * 1)) = {x_pos_norm + width_norm, y_pos_norm + height_norm}; // Top, right | |
*reinterpret_cast<glm::vec2 *>(byte_pos + (texture_map_stride * 2)) = {x_pos_norm + width_norm, y_pos_norm}; // Bottom, right | |
*reinterpret_cast<glm::vec2 *>(byte_pos + (texture_map_stride * 3)) = {x_pos_norm, y_pos_norm}; // Bottom, left | |
return true; | |
} | |
bool instanciateFontBitmap(FontBitmap& outFontBitmap, FT_Face& face, const char * uniqueCharsString, uint16_t textureWidthCells, uint16_t cellSize ) | |
{ | |
if(! setupBitmapForCharacterSet(outFontBitmap, textureWidthCells, cellSize, strlen(uniqueCharsString)) ) { | |
return false; | |
} | |
while(*uniqueCharsString != '\0') | |
{ | |
if(! FontBitmap::instanciate_char_bitmap(outFontBitmap, face, *(uniqueCharsString)) ) { | |
return false; | |
} | |
uniqueCharsString++; | |
} | |
return true; | |
} | |
bool FontBitmap::instanciate_char_bitmap(FontBitmap& font_bitmap, FT_Face& face, const char c) | |
{ | |
if(font_bitmap.char_data.count(c) != 0) { | |
puts("Charactor already found in bitmap"); | |
return false; | |
} | |
if(FT_Load_Char(face, c, FT_LOAD_RENDER)) { | |
puts("Failed to load charactor"); | |
return false; | |
} | |
uint16_t bitmap_width = face->glyph->bitmap.width; | |
uint16_t bitmap_height = face->glyph->bitmap.rows; | |
assert(font_bitmap.cell_size != 0); | |
uint16_t width_cells = font_bitmap.texture_width / font_bitmap.cell_size; | |
if(font_bitmap.current_x_cell >= width_cells) { | |
font_bitmap.current_x_cell = 0; | |
font_bitmap.current_y_cell++; | |
} | |
uint16_t x_pixel_pos = font_bitmap.current_x_cell * font_bitmap.cell_size; | |
uint16_t y_pixel_pos = font_bitmap.current_y_cell * font_bitmap.cell_size; | |
RGBA_8UNORM * current_pixel = font_bitmap.bitmap_data + (font_bitmap.texture_width * y_pixel_pos) + x_pixel_pos; | |
assert(font_bitmap.texture_width > bitmap_width); | |
uint16_t texture_width = font_bitmap.texture_width; | |
uint16_t src_y_index; | |
for(uint16_t y = 0; y < bitmap_height; y++) | |
{ | |
src_y_index = bitmap_height - y - 1; // y is flipped for loaded bitmap | |
for(uint16_t x = 0; x < bitmap_width; x++) | |
{ | |
(current_pixel + (y * texture_width) + x)->r = 255 - *(face->glyph->bitmap.buffer + (src_y_index * bitmap_width) + x); | |
(current_pixel + (y * texture_width) + x)->g = 255 - *(face->glyph->bitmap.buffer + (src_y_index * bitmap_width) + x); | |
(current_pixel + (y * texture_width) + x)->b = 255 - *(face->glyph->bitmap.buffer + (src_y_index * bitmap_width) + x); | |
(current_pixel + (y * texture_width) + x)->a = *(face->glyph->bitmap.buffer + (src_y_index * bitmap_width) + x); | |
} | |
} | |
assert(font_bitmap.texture_width != 0); | |
assert(font_bitmap.texture_height != 0); | |
// Calculate normalized coordinates | |
float norm_x = static_cast<float>(unsignedNormalizePixelPosition(font_bitmap.current_x_cell * font_bitmap.cell_size, font_bitmap.texture_width)); | |
float norm_y = static_cast<float>(unsignedNormalizePixelPosition(font_bitmap.current_y_cell * font_bitmap.cell_size, font_bitmap.texture_height)); | |
FT_Long face_index = FT_Get_Char_Index(face, c); // face->face_index; | |
int16_t relative_baseline = ((face->glyph->metrics.height >> 6) - face->glyph->bitmap_top) * 0.85; | |
int16_t horizontal_advance = face->glyph->advance.x; | |
font_bitmap.char_data.insert({ c, { { bitmap_width, bitmap_height, reinterpret_cast<uint8_t *>(current_pixel), relative_baseline, horizontal_advance, face_index }, // CharBitmap | |
{ norm_x, norm_y } }}); // NormalizedPosition | |
font_bitmap.current_x_cell++; | |
return true; | |
} | |
bool setupBitmapForCharacterSet( FontBitmap& font_bitmap, | |
uint16_t texture_width_cells, | |
uint16_t cell_size, | |
uint16_t num_charactors) | |
{ | |
font_bitmap.current_x_cell = 0; | |
font_bitmap.current_y_cell = 0; | |
font_bitmap.texture_width = texture_width_cells * cell_size; | |
font_bitmap.texture_height = ((num_charactors / texture_width_cells) + 1) * cell_size; | |
font_bitmap.cell_size = cell_size; | |
font_bitmap.bitmap_data = nullptr; | |
uint32_t allocation_amount = font_bitmap.texture_width * font_bitmap.texture_height * 4; | |
font_bitmap.bitmap_data = (RGBA_8UNORM *) malloc( allocation_amount ); | |
font_bitmap.char_data.reserve(num_charactors); | |
memset(font_bitmap.bitmap_data, 0, allocation_amount); | |
return true; | |
} | |
inline void createRectMesh(uint16_t * indices, glm::vec2 * vertices, uint16_t vertices_stride_bytes, uint16_t start_vertex_index, float x, float y, float height, float width) | |
{ | |
uint8_t * data = reinterpret_cast<uint8_t *>(vertices); | |
*reinterpret_cast<glm::vec2 *>( (data + (0 * vertices_stride_bytes)) ) = {x, y}; // Top left | |
*reinterpret_cast<glm::vec2 *>( (data + (1 * vertices_stride_bytes)) ) = {x + width, y}; // Top right | |
*reinterpret_cast<glm::vec2 *>( (data + (2 * vertices_stride_bytes)) ) = {x + width, y + height}; // Bottom right | |
*reinterpret_cast<glm::vec2 *>( (data + (3 * vertices_stride_bytes)) ) = {x, y + height}; // Bottom left | |
*(indices + 0) = start_vertex_index; | |
*(indices + 1) = start_vertex_index + 1; | |
*(indices + 2) = start_vertex_index + 2; | |
*(indices + 3) = start_vertex_index; | |
*(indices + 4) = start_vertex_index + 2; | |
*(indices + 5) = start_vertex_index + 3; | |
} | |
void printGlyphInformation(FT_GlyphSlot glyph) | |
{ | |
printf("Width: %d\n", glyph->bitmap.width); | |
printf("Rows: %d\n", glyph->bitmap.rows); | |
printf("Left: %d\n", glyph->bitmap_left); | |
printf("Top: %d\n", glyph->bitmap_top); | |
printf("X: %ld\n", glyph->advance.x); | |
printf("Y: %ld\n", glyph->advance.y); | |
} | |
void updateAddVertexPositions( glm::vec2 * vertices, | |
uint32_t numberVertices, | |
uint32_t verticesStrideBytes, | |
float addToX, | |
float addToY ) | |
{ | |
while(numberVertices-- != 0) | |
{ | |
vertices->y += addToY; | |
vertices->x += addToX; | |
// Move forward by stride | |
vertices = reinterpret_cast<glm::vec2 *>( reinterpret_cast<uint8_t *>(vertices) + verticesStrideBytes ); | |
} | |
} | |
void updateMultVertexPositions( glm::vec2 * vertices, | |
uint32_t numberVertices, | |
uint32_t verticesStrideBytes, | |
float multByX, | |
float multByY ) | |
{ | |
while(numberVertices-- != 0) | |
{ | |
vertices->x /= multByX; | |
vertices->y /= multByY; | |
// Move forward by stride | |
vertices = reinterpret_cast<glm::vec2 *>( reinterpret_cast<uint8_t *>(vertices) + verticesStrideBytes ); | |
} | |
} | |
void generateTextMeshes( uint16_t * indices, | |
glm::vec2 * vertices, | |
uint16_t vertices_stride_bytes, | |
uint16_t start_vertex_index, | |
FontBitmap& font_bitmap, | |
glm::vec2 * texture_map_start, | |
uint16_t texture_map_stride, | |
std::string& text, | |
uint16_t start_x, | |
uint16_t start_y) | |
{ | |
float x_pos = static_cast<float>(signedNormalizePixelPosition(start_x, vconfig::INITIAL_WINDOW_WIDTH)); | |
float y_pos = static_cast<float>(signedNormalizePixelPosition(start_y, vconfig::INITIAL_WINDOW_HEIGHT)); | |
float norm_line_spacing = static_cast<float>(unsignedNormalizePixelPosition(TEXT_LINE_SPACING, vconfig::INITIAL_WINDOW_HEIGHT)); | |
double max_width_point = static_cast<double>(x_pos) + unsignedNormalizePixelPosition(MAX_LINE_WIDTH, vconfig::INITIAL_WINDOW_WIDTH); | |
float face_width; | |
float face_height; | |
uint16_t current_text_index = 0; | |
for(char c : text) | |
{ | |
CharBitmap char_data = std::get<0>(font_bitmap.char_data[c]); | |
face_width = static_cast<float>(unsignedNormalizePixelPosition(char_data.width, vconfig::INITIAL_WINDOW_WIDTH)); | |
face_height = static_cast<float>(unsignedNormalizePixelPosition(char_data.height, vconfig::INITIAL_WINDOW_HEIGHT)); | |
float norm_relative_baseline = unsignedNormalizePixelPosition(std::get<0>(font_bitmap.char_data[c]).relative_baseline, vconfig::INITIAL_WINDOW_WIDTH); | |
int16_t relative_kearning_offset = char_data.horizontal_advance / 70; | |
createRectMesh(indices, vertices, vertices_stride_bytes, start_vertex_index, x_pos, y_pos + (norm_relative_baseline * 1.8) - face_height, face_height, face_width); | |
start_vertex_index += 4; | |
indices += 6; | |
vertices = reinterpret_cast<glm::vec2 *>( reinterpret_cast<uint8_t *>(vertices) + (4 * vertices_stride_bytes) ); | |
mapCharTextureToMesh(font_bitmap, c, texture_map_start, texture_map_stride); | |
uint8_t * byte_pos = reinterpret_cast<uint8_t *>(texture_map_start); | |
// Move texture mapping array pointer forward | |
byte_pos += texture_map_stride * 4; | |
texture_map_start = reinterpret_cast<glm::vec2 *>(byte_pos); | |
// TODO: You're gonna need the advance, kearning, etc. Monospace atm | |
x_pos += static_cast<float>(unsignedNormalizePixelPosition(TEXT_SPACING + relative_kearning_offset, vconfig::INITIAL_WINDOW_WIDTH)); | |
if(x_pos > max_width_point) | |
{ | |
x_pos = max_width_point - static_cast<float>(unsignedNormalizePixelPosition(MAX_LINE_WIDTH, vconfig::INITIAL_WINDOW_WIDTH)); | |
y_pos += norm_line_spacing; | |
} | |
current_text_index++; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment