packages feed

FreeTypeGL-0.0.1: src/text-buffer.c

/* ============================================================================
 * Freetype GL - A C OpenGL Freetype engine
 * Platform:    Any
 * WWW:         http://code.google.com/p/freetype-gl/
 * ----------------------------------------------------------------------------
 * Copyright 2011,2012 Nicolas P. Rougier. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY NICOLAS P. ROUGIER ''AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL NICOLAS P. ROUGIER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of Nicolas P. Rougier.
 * ============================================================================
 */
#include "text-buffer.h"

#include "opengl.h"
#include "shader.h"
#include <assert.h>
#include <wchar.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

text_buffer_t *text_buffer_new( GLuint shader, const ivec2 *size, int depth )
{

    text_buffer_t *self = (text_buffer_t *) malloc (sizeof(text_buffer_t));
    self->buffer = vertex_buffer_new( "v3f:t2f:c4f:1g1f:2g1f" );
    glyph_cache_init( &self->cache, size, depth );
    self->shader = shader;
    self->shader_texture = glGetUniformLocation(self->shader, "texture");
    self->shader_pixel = glGetUniformLocation(self->shader, "pixel");
    self->line_start = 0;
    self->line_ascender = 0;
    self->line_descender = 0;
    return self;
}

void
text_buffer_delete( text_buffer_t *self )
{
    vertex_buffer_delete(self->buffer);
    free (self);
}

// ----------------------------------------------------------------------------
void
text_buffer_render( text_buffer_t * self )
{
    texture_atlas_upload(&self->cache.atlas);

    glEnable( GL_BLEND );
    glEnable( GL_TEXTURE_2D );
    glColor4f( 1.0, 1.0, 1.0, 1.0);

    if( self->cache.atlas.depth == 1 ) {
        glDisable( GL_COLOR_MATERIAL );
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
        vertex_buffer_render( self->buffer, GL_TRIANGLES, "vtc" );
    } else {
        glEnable( GL_COLOR_MATERIAL );
        glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
        glBlendFunc( GL_ONE, GL_ONE_MINUS_SRC_ALPHA );
        glBlendColor( 1.0, 1.0, 1.0, 1.0 );
        glUseProgram( self->shader );
        glUniform1i( self->shader_texture, 0 );
        glUniform3f( self->shader_pixel,
                     1.0/self->cache.atlas.size.x,
                     1.0/self->cache.atlas.size.y,
                     self->cache.atlas.depth );
        vertex_buffer_render( self->buffer, GL_TRIANGLES, "vtc" );
        glUseProgram( 0 );
    }
}

/* WARNING: This is duplicated in the fmt strings given to
 * vertex_buffer_new, and perhaps the shader code. Keep in sync when
 * modifying)
 */
typedef struct {
    vec3 pos;                   /* x with fraction truncated away */
    vec2 texture_pos;
    vec4 color;
    float x_fraction;           /* x's truncated fraction */
    float color_gamma_correction;
} glyph_vertex_t;

struct coordinates {
    glyph_vertex_t *start_vertex;
    glyph_vertex_t *cur_vertex;
    glyph_vertex_t *end_vertex;
    GLuint *start_index;
    GLuint *cur_index;
    GLuint *end_index;
};

static GLuint
add_next_vertex( struct coordinates *coors, vec4 *color, float x, float y, float s, float t, float gamma )
{
    glyph_vertex_t *gv = coors->cur_vertex;
    coors->cur_vertex++;

    assert (gv < coors->end_vertex);
    gv->pos = (vec3){{ (int)x, y, 0 }};
    gv->texture_pos = (vec2){{ s, t }};
    gv->color = *color;
    gv->x_fraction = x - (int)x;
    gv->color_gamma_correction = gamma;

    return gv - coors->start_vertex;
}

static GLuint *
add_next_index( struct coordinates *coors )
{
    GLuint *i = coors->cur_index;
    coors->cur_index++;

    assert (i < coors->end_index);
    return i;
}

static void add_glyph(
    struct coordinates *coors,
    vec2 *pen, vec4 *color,
    float x0_offset, float y0_offset,
    float x1_offset, float y1_offset,
    float gamma,
    texture_glyph_t *glyph )
{
    float x0 = ( pen->x + x0_offset );
    float y0 = (int)( pen->y + y0_offset );
    float x1 = ( x0 + x1_offset );
    float y1 = (int)( y0 + y1_offset );
    float s0 = glyph->texture_pos0.s;
    float t0 = glyph->texture_pos0.t;
    float s1 = glyph->texture_pos1.s;
    float t1 = glyph->texture_pos1.t;
    GLuint v0 = add_next_vertex (coors, color, x0, y0, s0, t0, gamma);
    GLuint v1 = add_next_vertex (coors, color, x0, y1, s0, t1, gamma);
    GLuint v2 = add_next_vertex (coors, color, x1, y1, s1, t1, gamma);
    GLuint v3 = add_next_vertex (coors, color, x1, y0, s1, t0, gamma);
    *add_next_index(coors) = v0;
    *add_next_index(coors) = v1;
    *add_next_index(coors) = v2;
    *add_next_index(coors) = v0;
    *add_next_index(coors) = v2;
    *add_next_index(coors) = v3;
}

static void
coors_push_to_vector( struct coordinates *coors, vertex_buffer_t * buffer )
{
    vertex_buffer_push_back(
        buffer,
        coors->start_vertex,
        coors->cur_vertex - coors->start_vertex,
        coors->start_index,
        coors->cur_index - coors->start_index );
}

static int
text_buffer_add_wchar( text_buffer_t * self,
                       vec2 * pen, markup_t * markup,
                       texture_font_t * font,
                       wchar_t current, wchar_t previous )
{
    float gamma = markup->gamma;

    // Maximum number of vertices is 20 (= 5x2 triangles) per glyph:
    //  - 2 triangles for background
    //  - 2 triangles for overline
    //  - 2 triangles for underline
    //  - 2 triangles for strikethrough
    //  - 2 triangles for glyph
    glyph_vertex_t vertices[4*5];
    GLuint indices[6*5];
    struct coordinates coors = {
        vertices, vertices,
        vertices + sizeof vertices / sizeof *vertices,
        indices, indices,
        indices + sizeof indices / sizeof *indices
    };

    if( current == L'\n' )
    {
        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 );
        return 0;
    }

    texture_glyph_t *glyph = glyph_cache_get_glyph( &self->cache, font, current );
    if(glyph == NULL) return -1;

    texture_glyph_t *black = glyph_cache_get_glyph( &self->cache, NULL, -1 );

    float kerning = previous ? glyph_cache_get_kerning( &self->cache, glyph, previous ) : 0;
    pen->x += kerning;

    // Background
    if( markup->background_color.alpha > 0 ) {
        add_glyph(&coors, pen, &markup->background_color,
                  -kerning, font->descender,
                  glyph->advance.x, font->height + font->linegap,
                  gamma, black);
    }

    // Underline
    if( markup->underline ) {
        add_glyph(&coors, pen, &markup->underline_color,
                  -kerning, font->underline_position,
                  glyph->advance.x, font->underline_thickness,
                  gamma, black);
    }

    // Overline
    if( markup->overline ) {
        add_glyph(&coors, pen, &markup->overline_color,
                  -kerning, (int)font->ascender,
                  glyph->advance.x, (int)font->underline_thickness,
                  gamma, black);
    }

    // Actual glyph
    add_glyph(&coors, pen, &markup->foreground_color,
              glyph->bearing.x, glyph->bearing.y,
              glyph->size.x, -(float)glyph->size.y,
              gamma, glyph);

    /* Strikethrough */
    if( markup->strikethrough ) {
        add_glyph(&coors, pen, &markup->strikethrough_color,
                  -kerning, (int)font->ascender*.33,
                  glyph->advance.x, font->underline_thickness,
                  gamma, black);
    }

    coors_push_to_vector( &coors, self->buffer );
    pen->x += glyph->advance.x;
    return 0;
}

// ----------------------------------------------------------------------------
int text_buffer_add_text(
    text_buffer_t * self, vec2 * pen, markup_t * markup,
    texture_font_t * font, wchar_t * text )
{
    vertex_buffer_t * buffer = self->buffer;

    if( 0 == vertex_buffer_size( self->buffer ) ) self->origin = *pen;

    if( font->ascender > self->line_ascender ) {
        size_t i, j;
        float dy = (int)(font->ascender - self->line_ascender);
        for( i=self->line_start; i < vector_size( buffer->items ); ++i )
        {
            ivec4 *item = (ivec4 *) vector_get( buffer->items, i);
            for( j=item->vstart; j<item->vstart+item->vcount; ++j)
            {
                glyph_vertex_t * vertex =
                    (glyph_vertex_t *)  vector_get( buffer->vertices, j );
                vertex->pos.y -= dy;
            }
        }
        self->line_ascender = font->ascender;
        pen->y -= dy;
    }

    if( font->descender < self->line_descender ) {
        self->line_descender = font->descender;
    }

    if(!text[0]) return 0;

    int rc;
    if(0 != (rc = text_buffer_add_wchar( self, pen, markup, font, text[0], 0 ))) {
        return rc;
    }

    size_t i;
    for( i=1; text[i]; ++i ) {
        if(0 != (rc = text_buffer_add_wchar( self, pen, markup, font, text[i], text[i-1] ))) {
            return rc;
        }
    }
    return 0;
}