#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>
#include <assert.h>
#include <math.h>
#include "opengl.h"
#include "text-buffer.h"
#include "utf8-utils.h"
#define SET_GLYPH_VERTEX(value,x0,y0,z0,s0,t0,r,g,b,a,sh,gm) { \
glyph_vertex_t *gv=&value; \
gv->x=x0; gv->y=y0; gv->z=z0; \
gv->u=s0; gv->v=t0; \
gv->r=r; gv->g=g; gv->b=b; gv->a=a; \
gv->shift=sh; gv->gamma=gm;}
text_buffer_t *
text_buffer_new( )
{
text_buffer_t *self = (text_buffer_t *) malloc (sizeof(text_buffer_t));
self->buffer = vertex_buffer_new(
"vertex:3f,tex_coord:2f,color:4f,ashift:1f,agamma:1f" );
self->line_start = 0;
self->line_ascender = 0;
self->base_color.r = 0.0;
self->base_color.g = 0.0;
self->base_color.b = 0.0;
self->base_color.a = 1.0;
self->line_descender = 0;
self->lines = vector_new( sizeof(line_info_t) );
self->bounds.left = 0.0;
self->bounds.top = 0.0;
self->bounds.width = 0.0;
self->bounds.height = 0.0;
return self;
}
void
text_buffer_delete( text_buffer_t * self )
{
vector_delete( self->lines );
vertex_buffer_delete( self->buffer );
free( self );
}
void
text_buffer_clear( text_buffer_t * self )
{
assert( self );
vertex_buffer_clear( self->buffer );
self->line_start = 0;
self->line_ascender = 0;
self->line_descender = 0;
vector_clear( self->lines );
self->bounds.left = 0.0;
self->bounds.top = 0.0;
self->bounds.width = 0.0;
self->bounds.height = 0.0;
}
void
text_buffer_printf( text_buffer_t * self, vec2 *pen, ... )
{
markup_t *markup;
char *text;
va_list args;
if( vertex_buffer_size( self->buffer ) == 0 )
{
self->origin = *pen;
}
va_start ( args, pen );
do {
markup = va_arg( args, markup_t * );
if( markup == NULL )
{
return;
}
text = va_arg( args, char * );
text_buffer_add_text( self, pen, markup, text, 0 );
} while( markup != 0 );
va_end ( args );
}
void
text_buffer_move_last_line( text_buffer_t * self, float dy )
{
size_t i;
int j;
for( i=self->line_start; i < vector_size( self->buffer->items ); ++i )
{
ivec4 *item = (ivec4 *) vector_get( self->buffer->items, i);
for( j=item->vstart; j<item->vstart+item->vcount; ++j)
{
glyph_vertex_t * vertex =
(glyph_vertex_t *) vector_get( self->buffer->vertices, j );
vertex->y -= dy;
}
}
}
static void
text_buffer_finish_line( text_buffer_t * self, vec2 * pen, bool advancePen )
{
float line_left = self->line_left;
float line_right = pen->x;
float line_width = line_right - line_left;
float line_top = pen->y + self->line_ascender;
float line_height = self->line_ascender - self->line_descender;
float line_bottom = line_top - line_height;
line_info_t line_info;
line_info.line_start = self->line_start;
line_info.bounds.left = line_left;
line_info.bounds.top = line_top;
line_info.bounds.width = line_width;
line_info.bounds.height = line_height;
vector_push_back( self->lines, &line_info);
if (line_left < self->bounds.left)
{
self->bounds.left = line_left;
}
if (line_top > self->bounds.top)
{
self->bounds.top = line_top;
}
float self_right = self->bounds.left + self->bounds.width;
float self_bottom = self->bounds.top - self->bounds.height;
if (line_right > self_right)
{
self->bounds.width = line_right - self->bounds.left;
}
if (line_bottom < self_bottom)
{
self->bounds.height = self->bounds.top - line_bottom;
}
if ( advancePen )
{
pen->x = self->origin.x;
pen->y += (int)(self->line_descender);
}
self->line_descender = 0;
self->line_ascender = 0;
self->line_start = vector_size( self->buffer->items );
self->line_left = pen->x;
}
void
text_buffer_add_text( text_buffer_t * self,
vec2 * pen, markup_t * markup,
const char * text, size_t length )
{
size_t i;
const char * prev_character = NULL;
if( markup == NULL )
{
return;
}
if( !markup->font )
{
fprintf( stderr, "Houston, we've got a problem !\n" );
return;
}
if( length == 0 )
{
length = utf8_strlen(text);
}
if( vertex_buffer_size( self->buffer ) == 0 )
{
self->origin = *pen;
self->line_left = pen->x;
self->bounds.left = pen->x;
self->bounds.top = pen->y;
}
else
{
if (pen->x < self->origin.x)
{
self->origin.x = pen->x;
}
if (pen->y != self->last_pen_y)
{
text_buffer_finish_line(self, pen, false);
}
}
for( i = 0; length; i += utf8_surrogate_len( text + i ) )
{
text_buffer_add_char( self, pen, markup, text + i, prev_character );
prev_character = text + i;
length--;
}
self->last_pen_y = pen->y;
}
void
text_buffer_add_char( text_buffer_t * self,
vec2 * pen, markup_t * markup,
const char * current, const char * previous )
{
size_t vcount = 0;
size_t icount = 0;
vertex_buffer_t * buffer = self->buffer;
texture_font_t * font = markup->font;
float gamma = markup->gamma;
glyph_vertex_t vertices[4*5];
GLuint indices[6*5];
texture_glyph_t *glyph;
texture_glyph_t *black;
float kerning = 0.0f;
if( markup->font->ascender > self->line_ascender )
{
float y = pen->y;
pen->y -= (markup->font->ascender - self->line_ascender);
text_buffer_move_last_line( self, (float)(int)(y-pen->y) );
self->line_ascender = markup->font->ascender;
}
if( markup->font->descender < self->line_descender )
{
self->line_descender = markup->font->descender;
}
if( *current == '\n' )
{
text_buffer_finish_line(self, pen, true);
return;
}
glyph = texture_font_get_glyph( font, current );
black = texture_font_get_glyph( font, NULL );
if( glyph == NULL )
{
return;
}
if( previous && markup->font->kerning )
{
kerning = texture_glyph_get_kerning( glyph, previous );
}
pen->x += kerning;
if( markup->background_color.alpha > 0 )
{
float r = markup->background_color.r;
float g = markup->background_color.g;
float b = markup->background_color.b;
float a = markup->background_color.a;
float x0 = ( pen->x -kerning );
float y0 = (float)(int)( pen->y + font->descender );
float x1 = ( x0 + glyph->advance_x );
float y1 = (float)(int)( y0 + font->height + font->linegap );
float s0 = black->s0;
float t0 = black->t0;
float s1 = black->s1;
float t1 = black->t1;
SET_GLYPH_VERTEX(vertices[vcount+0],
(float)(int)x0,y0,0, s0,t0, r,g,b,a, x0-((int)x0), gamma );
SET_GLYPH_VERTEX(vertices[vcount+1],
(float)(int)x0,y1,0, s0,t1, r,g,b,a, x0-((int)x0), gamma );
SET_GLYPH_VERTEX(vertices[vcount+2],
(float)(int)x1,y1,0, s1,t1, r,g,b,a, x1-((int)x1), gamma );
SET_GLYPH_VERTEX(vertices[vcount+3],
(float)(int)x1,y0,0, s1,t0, r,g,b,a, x1-((int)x1), gamma );
indices[icount + 0] = vcount+0;
indices[icount + 1] = vcount+1;
indices[icount + 2] = vcount+2;
indices[icount + 3] = vcount+0;
indices[icount + 4] = vcount+2;
indices[icount + 5] = vcount+3;
vcount += 4;
icount += 6;
}
if( markup->underline )
{
float r = markup->underline_color.r;
float g = markup->underline_color.g;
float b = markup->underline_color.b;
float a = markup->underline_color.a;
float x0 = ( pen->x - kerning );
float y0 = (float)(int)( pen->y + font->underline_position );
float x1 = ( x0 + glyph->advance_x );
float y1 = (float)(int)( y0 + font->underline_thickness );
float s0 = black->s0;
float t0 = black->t0;
float s1 = black->s1;
float t1 = black->t1;
SET_GLYPH_VERTEX(vertices[vcount+0],
(float)(int)x0,y0,0, s0,t0, r,g,b,a, x0-((int)x0), gamma );
SET_GLYPH_VERTEX(vertices[vcount+1],
(float)(int)x0,y1,0, s0,t1, r,g,b,a, x0-((int)x0), gamma );
SET_GLYPH_VERTEX(vertices[vcount+2],
(float)(int)x1,y1,0, s1,t1, r,g,b,a, x1-((int)x1), gamma );
SET_GLYPH_VERTEX(vertices[vcount+3],
(float)(int)x1,y0,0, s1,t0, r,g,b,a, x1-((int)x1), gamma );
indices[icount + 0] = vcount+0;
indices[icount + 1] = vcount+1;
indices[icount + 2] = vcount+2;
indices[icount + 3] = vcount+0;
indices[icount + 4] = vcount+2;
indices[icount + 5] = vcount+3;
vcount += 4;
icount += 6;
}
if( markup->overline )
{
float r = markup->overline_color.r;
float g = markup->overline_color.g;
float b = markup->overline_color.b;
float a = markup->overline_color.a;
float x0 = ( pen->x -kerning );
float y0 = (float)(int)( pen->y + (int)font->ascender );
float x1 = ( x0 + glyph->advance_x );
float y1 = (float)(int)( y0 + (int)font->underline_thickness );
float s0 = black->s0;
float t0 = black->t0;
float s1 = black->s1;
float t1 = black->t1;
SET_GLYPH_VERTEX(vertices[vcount+0],
(float)(int)x0,y0,0, s0,t0, r,g,b,a, x0-((int)x0), gamma );
SET_GLYPH_VERTEX(vertices[vcount+1],
(float)(int)x0,y1,0, s0,t1, r,g,b,a, x0-((int)x0), gamma );
SET_GLYPH_VERTEX(vertices[vcount+2],
(float)(int)x1,y1,0, s1,t1, r,g,b,a, x1-((int)x1), gamma );
SET_GLYPH_VERTEX(vertices[vcount+3],
(float)(int)x1,y0,0, s1,t0, r,g,b,a, x1-((int)x1), gamma );
indices[icount + 0] = vcount+0;
indices[icount + 1] = vcount+1;
indices[icount + 2] = vcount+2;
indices[icount + 3] = vcount+0;
indices[icount + 4] = vcount+2;
indices[icount + 5] = vcount+3;
vcount += 4;
icount += 6;
}
if( markup->strikethrough )
{
float r = markup->strikethrough_color.r;
float g = markup->strikethrough_color.g;
float b = markup->strikethrough_color.b;
float a = markup->strikethrough_color.a;
float x0 = ( pen->x -kerning );
float y0 = (float)(int)( pen->y + (int)font->ascender*.33f);
float x1 = ( x0 + glyph->advance_x );
float y1 = (float)(int)( y0 + (int)font->underline_thickness );
float s0 = black->s0;
float t0 = black->t0;
float s1 = black->s1;
float t1 = black->t1;
SET_GLYPH_VERTEX(vertices[vcount+0],
(float)(int)x0,y0,0, s0,t0, r,g,b,a, x0-((int)x0), gamma );
SET_GLYPH_VERTEX(vertices[vcount+1],
(float)(int)x0,y1,0, s0,t1, r,g,b,a, x0-((int)x0), gamma );
SET_GLYPH_VERTEX(vertices[vcount+2],
(float)(int)x1,y1,0, s1,t1, r,g,b,a, x1-((int)x1), gamma );
SET_GLYPH_VERTEX(vertices[vcount+3],
(float)(int)x1,y0,0, s1,t0, r,g,b,a, x1-((int)x1), gamma );
indices[icount + 0] = vcount+0;
indices[icount + 1] = vcount+1;
indices[icount + 2] = vcount+2;
indices[icount + 3] = vcount+0;
indices[icount + 4] = vcount+2;
indices[icount + 5] = vcount+3;
vcount += 4;
icount += 6;
}
{
float r = markup->foreground_color.red;
float g = markup->foreground_color.green;
float b = markup->foreground_color.blue;
float a = markup->foreground_color.alpha;
float x0 = ( pen->x + glyph->offset_x );
float y0 = (float)(int)( pen->y + glyph->offset_y );
float x1 = ( x0 + glyph->width );
float y1 = (float)(int)( y0 - glyph->height );
float s0 = glyph->s0;
float t0 = glyph->t0;
float s1 = glyph->s1;
float t1 = glyph->t1;
SET_GLYPH_VERTEX(vertices[vcount+0],
(float)(int)x0,y0,0, s0,t0, r,g,b,a, x0-((int)x0), gamma );
SET_GLYPH_VERTEX(vertices[vcount+1],
(float)(int)x0,y1,0, s0,t1, r,g,b,a, x0-((int)x0), gamma );
SET_GLYPH_VERTEX(vertices[vcount+2],
(float)(int)x1,y1,0, s1,t1, r,g,b,a, x1-((int)x1), gamma );
SET_GLYPH_VERTEX(vertices[vcount+3],
(float)(int)x1,y0,0, s1,t0, r,g,b,a, x1-((int)x1), gamma );
indices[icount + 0] = vcount+0;
indices[icount + 1] = vcount+1;
indices[icount + 2] = vcount+2;
indices[icount + 3] = vcount+0;
indices[icount + 4] = vcount+2;
indices[icount + 5] = vcount+3;
vcount += 4;
icount += 6;
vertex_buffer_push_back( buffer, vertices, vcount, indices, icount );
pen->x += glyph->advance_x * (1.0f + markup->spacing);
}
}
void
text_buffer_align( text_buffer_t * self, vec2 * pen,
enum Align alignment )
{
if (ALIGN_LEFT == alignment)
{
return;
}
size_t total_items = vector_size( self->buffer->items );
if ( self->line_start != total_items )
{
text_buffer_finish_line( self, pen, false );
}
size_t i, j;
int k;
float self_left, self_right, self_center;
float line_left, line_right, line_center;
float dx;
self_left = self->bounds.left;
self_right = self->bounds.left + self->bounds.width;
self_center = (self_left + self_right) / 2;
line_info_t* line_info;
size_t lines_count, line_end;
lines_count = vector_size( self->lines );
for ( i = 0; i < lines_count; ++i )
{
line_info = (line_info_t*)vector_get( self->lines, i );
if ( i + 1 < lines_count )
{
line_end = ((line_info_t*)vector_get( self->lines, i + 1 ))->line_start;
}
else
{
line_end = vector_size( self->buffer->items );
}
line_right = line_info->bounds.left + line_info->bounds.width;
if ( ALIGN_RIGHT == alignment )
{
dx = self_right - line_right;
}
else {
line_left = line_info->bounds.left;
line_center = (line_left + line_right) / 2;
dx = self_center - line_center;
}
dx = roundf( dx );
for( j=line_info->line_start; j < line_end; ++j )
{
ivec4 *item = (ivec4 *) vector_get( self->buffer->items, j);
for( k=item->vstart; k<item->vstart+item->vcount; ++k)
{
glyph_vertex_t * vertex =
(glyph_vertex_t *)vector_get( self->buffer->vertices, k );
vertex->x += dx;
}
}
}
}
vec4
text_buffer_get_bounds( text_buffer_t * self, vec2 * pen )
{
size_t total_items = vector_size( self->buffer->items );
if ( self->line_start != total_items )
{
text_buffer_finish_line( self, pen, false );
}
return self->bounds;
}