libhydride/ftgl/texture-font.c
2017-11-09 22:54:55 +03:00

733 lines
21 KiB
C

/* Freetype GL - A C OpenGL Freetype engine
*
* Distributed under the OSI-approved BSD 2-Clause License. See accompanying
* file `LICENSE` for more details.
*/
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_STROKER_H
// #include FT_ADVANCES_H
#include FT_LCD_FILTER_H
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include "distance-field.h"
#include "texture-font.h"
#include "platform.h"
#include "utf8-utils.h"
#define HRES 64
#define HRESf 64.f
#define DPI 72
#undef __FTERRORS_H__
#define FT_ERRORDEF( e, v, s ) { e, s },
#define FT_ERROR_START_LIST {
#define FT_ERROR_END_LIST { 0, 0 } };
const struct {
int code;
const char* message;
} FT_Errors[] =
#include FT_ERRORS_H
// ------------------------------------------------- texture_font_load_face ---
static int
texture_font_load_face(texture_font_t *self, float size,
FT_Library *library, FT_Face *face)
{
FT_Error error;
FT_Matrix matrix = {
(int)((1.0/HRES) * 0x10000L),
(int)((0.0) * 0x10000L),
(int)((0.0) * 0x10000L),
(int)((1.0) * 0x10000L)};
assert(library);
assert(size);
/* Initialize library */
error = FT_Init_FreeType(library);
if(error) {
fprintf(stderr, "FT_Error (0x%02x) : %s\n",
FT_Errors[error].code, FT_Errors[error].message);
goto cleanup;
}
/* Load face */
switch (self->location) {
case TEXTURE_FONT_FILE:
error = FT_New_Face(*library, self->filename, 0, face);
break;
case TEXTURE_FONT_MEMORY:
error = FT_New_Memory_Face(*library,
self->memory.base, self->memory.size, 0, face);
break;
}
if(error) {
fprintf(stderr, "FT_Error (line %d, code 0x%02x) : %s\n",
__LINE__, FT_Errors[error].code, FT_Errors[error].message);
goto cleanup_library;
}
/* Select charmap */
error = FT_Select_Charmap(*face, FT_ENCODING_UNICODE);
if(error) {
fprintf(stderr, "FT_Error (line %d, code 0x%02x) : %s\n",
__LINE__, FT_Errors[error].code, FT_Errors[error].message);
goto cleanup_face;
}
/* Set char size */
error = FT_Set_Char_Size(*face, (int)(size * HRES), 0, DPI * HRES, DPI);
if(error) {
fprintf(stderr, "FT_Error (line %d, code 0x%02x) : %s\n",
__LINE__, FT_Errors[error].code, FT_Errors[error].message);
goto cleanup_face;
}
/* Set transform matrix */
FT_Set_Transform(*face, &matrix, NULL);
return 1;
cleanup_face:
FT_Done_Face( *face );
cleanup_library:
FT_Done_FreeType( *library );
cleanup:
return 0;
}
// ------------------------------------------------------ texture_glyph_new ---
texture_glyph_t *
texture_glyph_new(void)
{
texture_glyph_t *self = (texture_glyph_t *) malloc( sizeof(texture_glyph_t) );
if(self == NULL) {
fprintf( stderr,
"line %d: No more memory for allocating data\n", __LINE__);
return NULL;
}
self->codepoint = -1;
self->width = 0;
self->height = 0;
self->rendermode = RENDER_NORMAL;
self->outline_thickness = 0.0;
self->offset_x = 0;
self->offset_y = 0;
self->advance_x = 0.0;
self->advance_y = 0.0;
self->s0 = 0.0;
self->t0 = 0.0;
self->s1 = 0.0;
self->t1 = 0.0;
self->kerning = vector_new( sizeof(kerning_t) );
return self;
}
// --------------------------------------------------- texture_glyph_delete ---
void
texture_glyph_delete( texture_glyph_t *self )
{
assert( self );
vector_delete( self->kerning );
free( self );
}
// ---------------------------------------------- texture_glyph_get_kerning ---
float
texture_glyph_get_kerning( const texture_glyph_t * self,
const char * codepoint )
{
size_t i;
uint32_t ucodepoint = utf8_to_utf32( codepoint );
assert( self );
for( i=0; i<vector_size(self->kerning); ++i )
{
kerning_t * kerning = (kerning_t *) vector_get( self->kerning, i );
if( kerning->codepoint == ucodepoint )
{
return kerning->kerning;
}
}
return 0;
}
// ------------------------------------------ texture_font_generate_kerning ---
void
texture_font_generate_kerning( texture_font_t *self,
FT_Library *library, FT_Face *face )
{
size_t i, j;
FT_UInt glyph_index, prev_index;
texture_glyph_t *glyph, *prev_glyph;
FT_Vector kerning;
assert( self );
/* For each glyph couple combination, check if kerning is necessary */
/* Starts at index 1 since 0 is for the special backgroudn glyph */
for( i=1; i<self->glyphs->size; ++i )
{
glyph = *(texture_glyph_t **) vector_get( self->glyphs, i );
glyph_index = FT_Get_Char_Index( *face, glyph->codepoint );
vector_clear( glyph->kerning );
for( j=1; j<self->glyphs->size; ++j )
{
prev_glyph = *(texture_glyph_t **) vector_get( self->glyphs, j );
prev_index = FT_Get_Char_Index( *face, prev_glyph->codepoint );
FT_Get_Kerning( *face, prev_index, glyph_index, FT_KERNING_UNFITTED, &kerning );
// printf("%c(%d)-%c(%d): %ld\n",
// prev_glyph->codepoint, prev_glyph->codepoint,
// glyph_index, glyph_index, kerning.x);
if( kerning.x )
{
kerning_t k = {prev_glyph->codepoint, kerning.x / (float)(HRESf*HRESf)};
vector_push_back( glyph->kerning, &k );
}
}
}
}
// ------------------------------------------------------ texture_font_init ---
static int
texture_font_init(texture_font_t *self)
{
FT_Library library;
FT_Face face;
FT_Size_Metrics metrics;
assert(self->atlas);
assert(self->size > 0);
assert((self->location == TEXTURE_FONT_FILE && self->filename)
|| (self->location == TEXTURE_FONT_MEMORY
&& self->memory.base && self->memory.size));
self->glyphs = vector_new(sizeof(texture_glyph_t *));
self->height = 0;
self->ascender = 0;
self->descender = 0;
self->rendermode = RENDER_NORMAL;
self->outline_thickness = 0.0;
self->hinting = 1;
self->kerning = 1;
self->filtering = 1;
// FT_LCD_FILTER_LIGHT is (0x00, 0x55, 0x56, 0x55, 0x00)
// FT_LCD_FILTER_DEFAULT is (0x10, 0x40, 0x70, 0x40, 0x10)
self->lcd_weights[0] = 0x10;
self->lcd_weights[1] = 0x40;
self->lcd_weights[2] = 0x70;
self->lcd_weights[3] = 0x40;
self->lcd_weights[4] = 0x10;
if (!texture_font_load_face(self, self->size * 100.f, &library, &face))
return -1;
self->underline_position = face->underline_position / (float)(HRESf*HRESf) * self->size;
self->underline_position = roundf( self->underline_position );
if( self->underline_position > -2 )
{
self->underline_position = -2.0;
}
self->underline_thickness = face->underline_thickness / (float)(HRESf*HRESf) * self->size;
self->underline_thickness = roundf( self->underline_thickness );
if( self->underline_thickness < 1 )
{
self->underline_thickness = 1.0;
}
metrics = face->size->metrics;
self->ascender = (metrics.ascender >> 6) / 100.0;
self->descender = (metrics.descender >> 6) / 100.0;
self->height = (metrics.height >> 6) / 100.0;
self->linegap = self->height - self->ascender + self->descender;
FT_Done_Face( face );
FT_Done_FreeType( library );
/* NULL is a special glyph */
texture_font_get_glyph( self, NULL );
return 0;
}
// --------------------------------------------- texture_font_new_from_file ---
texture_font_t *
texture_font_new_from_file(texture_atlas_t *atlas, const float pt_size,
const char *filename)
{
texture_font_t *self;
assert(filename);
self = calloc(1, sizeof(*self));
if (!self) {
fprintf(stderr,
"line %d: No more memory for allocating data\n", __LINE__);
return NULL;
}
self->atlas = atlas;
self->size = pt_size;
self->location = TEXTURE_FONT_FILE;
self->filename = strdup(filename);
if (texture_font_init(self)) {
texture_font_delete(self);
return NULL;
}
return self;
}
// ------------------------------------------- texture_font_new_from_memory ---
texture_font_t *
texture_font_new_from_memory(texture_atlas_t *atlas, float pt_size,
const void *memory_base, size_t memory_size)
{
texture_font_t *self;
assert(memory_base);
assert(memory_size);
self = calloc(1, sizeof(*self));
if (!self) {
fprintf(stderr,
"line %d: No more memory for allocating data\n", __LINE__);
return NULL;
}
self->atlas = atlas;
self->size = pt_size;
self->location = TEXTURE_FONT_MEMORY;
self->memory.base = memory_base;
self->memory.size = memory_size;
if (texture_font_init(self)) {
texture_font_delete(self);
return NULL;
}
return self;
}
// ---------------------------------------------------- texture_font_delete ---
void
texture_font_delete( texture_font_t *self )
{
size_t i;
texture_glyph_t *glyph;
assert( self );
if(self->location == TEXTURE_FONT_FILE && self->filename)
free( self->filename );
for( i=0; i<vector_size( self->glyphs ); ++i)
{
glyph = *(texture_glyph_t **) vector_get( self->glyphs, i );
texture_glyph_delete( glyph);
}
vector_delete( self->glyphs );
free( self );
}
texture_glyph_t *
texture_font_find_glyph( texture_font_t * self,
const char * codepoint )
{
size_t i;
texture_glyph_t *glyph;
uint32_t ucodepoint = utf8_to_utf32( codepoint );
for( i = 0; i < self->glyphs->size; ++i )
{
glyph = *(texture_glyph_t **) vector_get( self->glyphs, i );
// If codepoint is -1, we don't care about outline type or thickness
if( (glyph->codepoint == ucodepoint) &&
((ucodepoint == -1) ||
((glyph->rendermode == self->rendermode) &&
(glyph->outline_thickness == self->outline_thickness)) ))
{
return glyph;
}
}
return NULL;
}
// ------------------------------------------------ texture_font_load_glyph ---
int
texture_font_load_glyph( texture_font_t * self,
const char * codepoint )
{
size_t i, x, y;
FT_Library library;
FT_Error error;
FT_Face face;
FT_Glyph ft_glyph;
FT_GlyphSlot slot;
FT_Bitmap ft_bitmap;
FT_UInt glyph_index;
texture_glyph_t *glyph;
FT_Int32 flags = 0;
int ft_glyph_top = 0;
int ft_glyph_left = 0;
ivec4 region;
size_t missed = 0;
if (!texture_font_load_face(self, self->size, &library, &face))
return 0;
/* Check if codepoint has been already loaded */
if (texture_font_find_glyph(self, codepoint)) {
FT_Done_Face(face);
FT_Done_FreeType(library);
return 1;
}
/* codepoint NULL is special : it is used for line drawing (overline,
* underline, strikethrough) and background.
*/
if( !codepoint )
{
ivec4 region = texture_atlas_get_region( self->atlas, 5, 5 );
texture_glyph_t * glyph = texture_glyph_new( );
static unsigned char data[4*4*3] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1};
if ( region.x < 0 )
{
fprintf( stderr, "Texture atlas is full (line %d)\n", __LINE__ );
FT_Done_Face( face );
FT_Done_FreeType( library );
return 0;
}
texture_atlas_set_region( self->atlas, region.x, region.y, 4, 4, data, 0 );
glyph->codepoint = -1;
glyph->s0 = (region.x+2)/(float)self->atlas->width;
glyph->t0 = (region.y+2)/(float)self->atlas->height;
glyph->s1 = (region.x+3)/(float)self->atlas->width;
glyph->t1 = (region.y+3)/(float)self->atlas->height;
vector_push_back( self->glyphs, &glyph );
FT_Done_Face(face);
FT_Done_FreeType(library);
return 1;
}
flags = 0;
ft_glyph_top = 0;
ft_glyph_left = 0;
glyph_index = FT_Get_Char_Index( face, (FT_ULong)utf8_to_utf32( codepoint ) );
// WARNING: We use texture-atlas depth to guess if user wants
// LCD subpixel rendering
if( self->rendermode != RENDER_NORMAL && self->rendermode != RENDER_SIGNED_DISTANCE_FIELD )
{
flags |= FT_LOAD_NO_BITMAP;
}
else
{
flags |= FT_LOAD_RENDER;
}
if( !self->hinting )
{
flags |= FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT;
}
else
{
flags |= FT_LOAD_FORCE_AUTOHINT;
}
if( self->atlas->depth == 3 )
{
FT_Library_SetLcdFilter( library, FT_LCD_FILTER_LIGHT );
flags |= FT_LOAD_TARGET_LCD;
if( self->filtering )
{
FT_Library_SetLcdFilterWeights( library, self->lcd_weights );
}
}
error = FT_Load_Glyph( face, glyph_index, flags );
if( error )
{
fprintf( stderr, "FT_Error (line %d, code 0x%02x) : %s\n",
__LINE__, FT_Errors[error].code, FT_Errors[error].message );
FT_Done_Face( face );
FT_Done_FreeType( library );
return 0;
}
if( self->rendermode == RENDER_NORMAL || self->rendermode == RENDER_SIGNED_DISTANCE_FIELD )
{
slot = face->glyph;
ft_bitmap = slot->bitmap;
ft_glyph_top = slot->bitmap_top;
ft_glyph_left = slot->bitmap_left;
}
else
{
FT_Stroker stroker;
FT_BitmapGlyph ft_bitmap_glyph;
error = FT_Stroker_New( library, &stroker );
if( error )
{
fprintf(stderr, "FT_Error (0x%02x) : %s\n",
FT_Errors[error].code, FT_Errors[error].message);
goto cleanup_stroker;
}
FT_Stroker_Set(stroker,
(int)(self->outline_thickness * HRES),
FT_STROKER_LINECAP_ROUND,
FT_STROKER_LINEJOIN_ROUND,
0);
error = FT_Get_Glyph( face->glyph, &ft_glyph);
if( error )
{
fprintf(stderr, "FT_Error (0x%02x) : %s\n",
FT_Errors[error].code, FT_Errors[error].message);
goto cleanup_stroker;
}
if( self->rendermode == RENDER_OUTLINE_EDGE )
error = FT_Glyph_Stroke( &ft_glyph, stroker, 1 );
else if ( self->rendermode == RENDER_OUTLINE_POSITIVE )
error = FT_Glyph_StrokeBorder( &ft_glyph, stroker, 0, 1 );
else if ( self->rendermode == RENDER_OUTLINE_NEGATIVE )
error = FT_Glyph_StrokeBorder( &ft_glyph, stroker, 1, 1 );
if( error )
{
fprintf(stderr, "FT_Error (0x%02x) : %s\n",
FT_Errors[error].code, FT_Errors[error].message);
goto cleanup_stroker;
}
if( self->atlas->depth == 1 )
error = FT_Glyph_To_Bitmap( &ft_glyph, FT_RENDER_MODE_NORMAL, 0, 1);
else
error = FT_Glyph_To_Bitmap( &ft_glyph, FT_RENDER_MODE_LCD, 0, 1);
if( error )
{
fprintf(stderr, "FT_Error (0x%02x) : %s\n",
FT_Errors[error].code, FT_Errors[error].message);
goto cleanup_stroker;
}
ft_bitmap_glyph = (FT_BitmapGlyph) ft_glyph;
ft_bitmap = ft_bitmap_glyph->bitmap;
ft_glyph_top = ft_bitmap_glyph->top;
ft_glyph_left = ft_bitmap_glyph->left;
cleanup_stroker:
FT_Stroker_Done( stroker );
if( error )
{
FT_Done_Face( face );
FT_Done_FreeType( library );
return 0;
}
}
struct {
int left;
int top;
int right;
int bottom;
} padding = { 0, 0, 1, 1 };
if( self->rendermode == RENDER_SIGNED_DISTANCE_FIELD )
{
padding.top = 1;
padding.left = 1;
}
size_t src_w = ft_bitmap.width/self->atlas->depth;
size_t src_h = ft_bitmap.rows;
size_t tgt_w = src_w + padding.left + padding.right;
size_t tgt_h = src_h + padding.top + padding.bottom;
region = texture_atlas_get_region( self->atlas, tgt_w, tgt_h );
if ( region.x < 0 )
{
fprintf( stderr, "Texture atlas is full (line %d)\n", __LINE__ );
FT_Done_Face( face );
FT_Done_FreeType( library );
return 0;
}
x = region.x;
y = region.y;
unsigned char *buffer = calloc( tgt_w * tgt_h * self->atlas->depth, sizeof(unsigned char) );
unsigned char *dst_ptr = buffer + (padding.top * tgt_w + padding.left) * self->atlas->depth;
unsigned char *src_ptr = ft_bitmap.buffer;
for( i = 0; i < src_h; i++ )
{
//difference between width and pitch: https://www.freetype.org/freetype2/docs/reference/ft2-basic_types.html#FT_Bitmap
memcpy( dst_ptr, src_ptr, ft_bitmap.width);
dst_ptr += tgt_w * self->atlas->depth;
src_ptr += ft_bitmap.pitch;
}
if( self->rendermode == RENDER_SIGNED_DISTANCE_FIELD )
{
unsigned char *sdf = make_distance_mapb( buffer, tgt_w, tgt_h );
free( buffer );
buffer = sdf;
}
texture_atlas_set_region( self->atlas, x, y, tgt_w, tgt_h, buffer, tgt_w * self->atlas->depth);
free( buffer );
glyph = texture_glyph_new( );
glyph->codepoint = utf8_to_utf32( codepoint );
glyph->width = tgt_w;
glyph->height = tgt_h;
glyph->rendermode = self->rendermode;
glyph->outline_thickness = self->outline_thickness;
glyph->offset_x = ft_glyph_left;
glyph->offset_y = ft_glyph_top;
glyph->s0 = x/(float)self->atlas->width;
glyph->t0 = y/(float)self->atlas->height;
glyph->s1 = (x + glyph->width)/(float)self->atlas->width;
glyph->t1 = (y + glyph->height)/(float)self->atlas->height;
// Discard hinting to get advance
FT_Load_Glyph( face, glyph_index, FT_LOAD_RENDER | FT_LOAD_NO_HINTING);
slot = face->glyph;
glyph->advance_x = slot->advance.x / HRESf;
glyph->advance_y = slot->advance.y / HRESf;
vector_push_back( self->glyphs, &glyph );
if( self->rendermode != RENDER_NORMAL && self->rendermode != RENDER_SIGNED_DISTANCE_FIELD )
FT_Done_Glyph( ft_glyph );
texture_font_generate_kerning( self, &library, &face );
FT_Done_Face( face );
FT_Done_FreeType( library );
return 1;
}
// ----------------------------------------------- texture_font_load_glyphs ---
size_t
texture_font_load_glyphs( texture_font_t * self,
const char * codepoints )
{
size_t i, c;
/* Load each glyph */
for( i = 0; i < strlen(codepoints); i += utf8_surrogate_len(codepoints + i) ) {
if( !texture_font_load_glyph( self, codepoints + i ) )
return utf8_strlen( codepoints + i );
}
return 0;
}
// ------------------------------------------------- texture_font_get_glyph ---
texture_glyph_t *
texture_font_get_glyph( texture_font_t * self,
const char * codepoint )
{
texture_glyph_t *glyph;
assert( self );
assert( self->filename );
assert( self->atlas );
/* Check if codepoint has been already loaded */
if( (glyph = texture_font_find_glyph( self, codepoint )) )
return glyph;
/* Glyph has not been already loaded */
if( texture_font_load_glyph( self, codepoint ) )
return texture_font_find_glyph( self, codepoint );
return NULL;
}
// ------------------------------------------------- texture_font_enlarge_atlas ---
void
texture_font_enlarge_atlas( texture_font_t * self, size_t width_new,
size_t height_new)
{
assert(self);
assert(self->atlas);
//ensure size increased
assert(width_new >= self->atlas->width);
assert(height_new >= self->atlas->height);
assert(width_new + height_new > self->atlas->width + self->atlas->height);
texture_atlas_t* ta = self->atlas;
size_t width_old = ta->width;
size_t height_old = ta->height;
//allocate new buffer
unsigned char* data_old = ta->data;
ta->data = calloc(1,width_new*height_new * sizeof(char)*ta->depth);
//update atlas size
ta->width = width_new;
ta->height = height_new;
//add node reflecting the gained space on the right
if(width_new>width_old){
ivec3 node;
node.x = width_old - 1;
node.y = 1;
node.z = width_new - width_old;
vector_push_back(ta->nodes, &node);
}
//copy over data from the old buffer, skipping first row and column because of the margin
size_t pixel_size = sizeof(char) * ta->depth;
size_t old_row_size = width_old * pixel_size;
texture_atlas_set_region(ta, 1, 1, width_old - 2, height_old - 2, data_old + old_row_size + pixel_size, old_row_size);
free(data_old);
//change uv coordinates of existing glyphs to reflect size change
float mulw = (float)width_old / width_new;
float mulh = (float)height_old / height_new;
size_t i;
for (i = 0; i < vector_size(self->glyphs); i++) {
texture_glyph_t* g = *(texture_glyph_t**)vector_get(self->glyphs, i);
g->s0 *= mulw;
g->s1 *= mulw;
g->t0 *= mulh;
g->t1 *= mulh;
}
}