Blame src/libbluray/decoders/textst_render.c

Packit 5e46da
/*
Packit 5e46da
 * This file is part of libbluray
Packit 5e46da
 * Copyright (C) 2013  Petri Hintukainen <phintuka@users.sourceforge.net>
Packit 5e46da
 *
Packit 5e46da
 * This library is free software; you can redistribute it and/or
Packit 5e46da
 * modify it under the terms of the GNU Lesser General Public
Packit 5e46da
 * License as published by the Free Software Foundation; either
Packit 5e46da
 * version 2.1 of the License, or (at your option) any later version.
Packit 5e46da
 *
Packit 5e46da
 * This library is distributed in the hope that it will be useful,
Packit 5e46da
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 5e46da
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 5e46da
 * Lesser General Public License for more details.
Packit 5e46da
 *
Packit 5e46da
 * You should have received a copy of the GNU Lesser General Public
Packit 5e46da
 * License along with this library. If not, see
Packit 5e46da
 * <http://www.gnu.org/licenses/>.
Packit 5e46da
 */
Packit 5e46da
Packit 5e46da
#if HAVE_CONFIG_H
Packit 5e46da
#include "config.h"
Packit 5e46da
#endif
Packit 5e46da
Packit 5e46da
#include "util/macro.h"
Packit 5e46da
#include "util/logging.h"
Packit 5e46da
#include "bluray.h"          /* bd_char_code_e */
Packit 5e46da
Packit 5e46da
#include <stdint.h>
Packit 5e46da
Packit 5e46da
#ifdef HAVE_FT2
Packit 5e46da
#include <ft2build.h>
Packit 5e46da
#include FT_FREETYPE_H
Packit 5e46da
#include FT_SYNTHESIS_H
Packit 5e46da
#endif
Packit 5e46da
Packit 5e46da
#include "textst_render.h"
Packit 5e46da
Packit 5e46da
/*
Packit 5e46da
 *
Packit 5e46da
 */
Packit 5e46da
Packit 5e46da
#define TEXTST_ERROR(...) BD_DEBUG(DBG_GC | DBG_CRIT, __VA_ARGS__)
Packit 5e46da
#define TEXTST_TRACE(...) BD_DEBUG(DBG_GC,            __VA_ARGS__)
Packit 5e46da
Packit 5e46da
/*
Packit 5e46da
 * data
Packit 5e46da
 */
Packit 5e46da
#ifdef HAVE_FT2
Packit 5e46da
typedef struct {
Packit 5e46da
Packit 5e46da
  FT_Face  face;
Packit 5e46da
  void    *mem;
Packit 5e46da
Packit 5e46da
} FONT_DATA;
Packit 5e46da
Packit 5e46da
struct textst_render {
Packit 5e46da
Packit 5e46da
  FT_Library     ft_lib;
Packit 5e46da
Packit 5e46da
  unsigned       font_count;
Packit 5e46da
  FONT_DATA     *font;
Packit 5e46da
Packit 5e46da
  bd_char_code_e char_code;
Packit 5e46da
Packit 5e46da
};
Packit 5e46da
#endif
Packit 5e46da
Packit 5e46da
/*
Packit 5e46da
 * init / free
Packit 5e46da
 */
Packit 5e46da
Packit 5e46da
TEXTST_RENDER *textst_render_init(void)
Packit 5e46da
{
Packit 5e46da
#ifdef HAVE_FT2
Packit 5e46da
    TEXTST_RENDER *p = calloc(1, sizeof(TEXTST_RENDER));
Packit 5e46da
Packit 5e46da
    if (!p) {
Packit 5e46da
        return NULL;
Packit 5e46da
    }
Packit 5e46da
Packit 5e46da
    if (!FT_Init_FreeType(&p->ft_lib)) {
Packit 5e46da
        return p;
Packit 5e46da
    }
Packit 5e46da
Packit 5e46da
    X_FREE(p);
Packit 5e46da
    TEXTST_ERROR("Loading FreeType2 failed\n");
Packit 5e46da
#else
Packit 5e46da
    TEXTST_ERROR("TextST font support not compiled in\n");
Packit 5e46da
#endif
Packit 5e46da
    return NULL;
Packit 5e46da
}
Packit 5e46da
Packit 5e46da
void textst_render_free(TEXTST_RENDER **pp)
Packit 5e46da
{
Packit 5e46da
    if (pp && *pp) {
Packit 5e46da
#ifdef HAVE_FT2
Packit 5e46da
        TEXTST_RENDER *p = *pp;
Packit 5e46da
Packit 5e46da
        if (p->ft_lib) {
Packit 5e46da
            /* free fonts */
Packit 5e46da
            unsigned ii;
Packit 5e46da
            for (ii = 0; ii < p->font_count; ii++) {
Packit 5e46da
                if (p->font[ii].face) {
Packit 5e46da
                    FT_Done_Face(p->font[ii].face);
Packit 5e46da
                }
Packit 5e46da
                X_FREE(p->font[ii].mem);
Packit 5e46da
            }
Packit 5e46da
            X_FREE(p->font);
Packit 5e46da
Packit 5e46da
            FT_Done_FreeType(p->ft_lib);
Packit 5e46da
        }
Packit 5e46da
#endif
Packit 5e46da
        X_FREE(*pp);
Packit 5e46da
    }
Packit 5e46da
}
Packit 5e46da
Packit 5e46da
/*
Packit 5e46da
 * settings
Packit 5e46da
 */
Packit 5e46da
Packit 5e46da
int textst_render_add_font(TEXTST_RENDER *p, void *data, size_t size)
Packit 5e46da
{
Packit 5e46da
#ifdef HAVE_FT2
Packit 5e46da
    FONT_DATA *tmp = realloc(p->font, sizeof(*(p->font)) * (p->font_count + 1));
Packit 5e46da
    if (!tmp) {
Packit 5e46da
        TEXTST_ERROR("out of memory\n");
Packit 5e46da
        return -1;
Packit 5e46da
    }
Packit 5e46da
    p->font = tmp;
Packit 5e46da
Packit 5e46da
    if (FT_New_Memory_Face(p->ft_lib, (const FT_Byte*)data, (FT_Long)size, -1, NULL)) {
Packit 5e46da
        TEXTST_ERROR("Unsupport font file format\n");
Packit 5e46da
        return -1;
Packit 5e46da
    }
Packit 5e46da
Packit 5e46da
    if (!FT_New_Memory_Face(p->ft_lib, (const FT_Byte*)data, (FT_Long)size, 0, &p->font[p->font_count].face)) {
Packit 5e46da
        p->font[p->font_count].mem = data;
Packit 5e46da
        p->font_count++;
Packit 5e46da
        return 0;
Packit 5e46da
    }
Packit 5e46da
Packit 5e46da
    TEXTST_ERROR("Loading font %d failed\n", p->font_count);
Packit 5e46da
Packit 5e46da
#else
Packit 5e46da
    (void)p;
Packit 5e46da
    (void)data;
Packit 5e46da
    (void)size;
Packit 5e46da
#endif
Packit 5e46da
Packit 5e46da
    return -1;
Packit 5e46da
}
Packit 5e46da
Packit 5e46da
int textst_render_set_char_code(TEXTST_RENDER *p, int char_code)
Packit 5e46da
{
Packit 5e46da
#ifdef HAVE_FT2
Packit 5e46da
    p->char_code = (bd_char_code_e)char_code;
Packit 5e46da
    if (p->char_code != BLURAY_TEXT_CHAR_CODE_UTF8) {
Packit 5e46da
        TEXTST_ERROR("WARNING: unsupported TextST coding type %d\n", char_code);
Packit 5e46da
        return -1;
Packit 5e46da
    }
Packit 5e46da
Packit 5e46da
#else
Packit 5e46da
    (void)p;
Packit 5e46da
    (void)char_code;
Packit 5e46da
#endif
Packit 5e46da
Packit 5e46da
    return 0;
Packit 5e46da
}
Packit 5e46da
Packit 5e46da
/*
Packit 5e46da
 * UTF-8
Packit 5e46da
 */
Packit 5e46da
Packit 5e46da
#ifdef HAVE_FT2
Packit 5e46da
Packit 5e46da
static int _utf8_char_size(const uint8_t *s)
Packit 5e46da
{
Packit 5e46da
    if ((s[0] & 0xE0) == 0xC0 &&
Packit 5e46da
        (s[1] & 0xC0) == 0x80) {
Packit 5e46da
        return 2;
Packit 5e46da
    }
Packit 5e46da
    if ((s[0] & 0xF0) == 0xE0 &&
Packit 5e46da
        (s[1] & 0xC0) == 0x80 &&
Packit 5e46da
        (s[2] & 0xC0) == 0x80) {
Packit 5e46da
        return 3;
Packit 5e46da
    }
Packit 5e46da
    if ((s[0] & 0xF8) == 0xF0 &&
Packit 5e46da
        (s[1] & 0xC0) == 0x80 &&
Packit 5e46da
        (s[2] & 0xC0) == 0x80 &&
Packit 5e46da
        (s[3] & 0xC0) == 0x80) {
Packit 5e46da
        return 4;
Packit 5e46da
    }
Packit 5e46da
    return 1;
Packit 5e46da
}
Packit 5e46da
Packit 5e46da
static unsigned _utf8_char_get(const uint8_t *s, int char_size)
Packit 5e46da
{
Packit 5e46da
    if (!char_size) {
Packit 5e46da
        char_size = _utf8_char_size(s);
Packit 5e46da
    }
Packit 5e46da
Packit 5e46da
    switch (char_size) {
Packit 5e46da
        case 2: return ((s[0] & 0x1F) <<  6) | ((s[1] & 0x3F));
Packit 5e46da
        case 3: return ((s[0] & 0x0F) << 12) | ((s[1] & 0x3F) <<  6) | ((s[2] & 0x3F));
Packit 5e46da
        case 4: return ((s[0] & 0x07) << 18) | ((s[1] & 0x3F) << 12) | ((s[2] & 0x3F) << 6) | ((s[3] & 0x3F));
Packit 5e46da
        default: ;
Packit 5e46da
    }
Packit 5e46da
    return s[0];
Packit 5e46da
}
Packit 5e46da
Packit 5e46da
#endif /* HAVE_FT2 */
Packit 5e46da
Packit 5e46da
/*
Packit 5e46da
 * rendering
Packit 5e46da
 */
Packit 5e46da
Packit 5e46da
#ifdef HAVE_FT2
Packit 5e46da
Packit 5e46da
static int _draw_string(FT_Face face, const uint8_t *string, int length,
Packit 5e46da
                        TEXTST_BITMAP *bmp, int x, int y,
Packit 5e46da
                        BD_TEXTST_REGION_STYLE *style,
Packit 5e46da
                        int *baseline_pos)
Packit 5e46da
{
Packit 5e46da
    uint8_t  color = style->font_color;
Packit 5e46da
    unsigned char_code;
Packit 5e46da
    int      ii;
Packit 5e46da
    unsigned jj, kk;
Packit 5e46da
    unsigned flags;
Packit 5e46da
Packit 5e46da
    if (length <= 0) {
Packit 5e46da
        return -1;
Packit 5e46da
    }
Packit 5e46da
    if (!bmp) {
Packit 5e46da
        flags = FT_LOAD_DEFAULT;
Packit 5e46da
    } else {
Packit 5e46da
        flags = FT_LOAD_RENDER;
Packit 5e46da
    }
Packit 5e46da
Packit 5e46da
    for (ii = 0; ii < length; ii++) {
Packit 5e46da
        /*if (p->char_code == BLURAY_TEXT_CHAR_CODE_UTF8) {*/
Packit 5e46da
            int char_size = _utf8_char_size(string + ii);
Packit 5e46da
            char_code = _utf8_char_get(string + ii, char_size);
Packit 5e46da
            ii += char_size - 1;
Packit 5e46da
        /*}*/
Packit 5e46da
Packit 5e46da
        if (FT_Load_Char(face, char_code, flags /*| FT_LOAD_MONOCHROME*/) == 0) {
Packit 5e46da
Packit 5e46da
            if (style->font_style.bold && !(face->style_flags & FT_STYLE_FLAG_BOLD)) {
Packit 5e46da
                FT_GlyphSlot_Embolden( face->glyph );
Packit 5e46da
            }
Packit 5e46da
            if (style->font_style.italic && !(face->style_flags & FT_STYLE_FLAG_ITALIC)) {
Packit 5e46da
                FT_GlyphSlot_Oblique( face->glyph );
Packit 5e46da
            }
Packit 5e46da
Packit 5e46da
            if (bmp) {
Packit 5e46da
                for (jj = 0; jj < face->glyph->bitmap.rows; jj++) {
Packit 5e46da
                    for (kk = 0; kk < face->glyph->bitmap.width; kk++) {
Packit 5e46da
                        uint8_t pixel = face->glyph->bitmap.buffer[jj * face->glyph->bitmap.pitch + kk];
Packit 5e46da
                        if (pixel & 0x80) {
Packit 5e46da
                            int xpos = x + face->glyph->bitmap_left + kk;
Packit 5e46da
                            int ypos = y - face->glyph->bitmap_top + jj;
Packit 5e46da
                            if (xpos >= 0 && xpos < bmp->width && ypos >= 0 && ypos < bmp->height) {
Packit 5e46da
                                bmp->mem[xpos + ypos * bmp->stride] = color;
Packit 5e46da
                            }
Packit 5e46da
                        }
Packit 5e46da
                    }
Packit 5e46da
                }
Packit 5e46da
            }
Packit 5e46da
Packit 5e46da
            /* track max baseline when calculating line size */
Packit 5e46da
            if (baseline_pos) {
Packit 5e46da
                *baseline_pos = BD_MAX(*baseline_pos, (face->size->metrics.ascender >> 6) + 1);
Packit 5e46da
            }
Packit 5e46da
Packit 5e46da
            x += face->glyph->metrics.horiAdvance >> 6;
Packit 5e46da
        }
Packit 5e46da
    }
Packit 5e46da
Packit 5e46da
    return x;
Packit 5e46da
}
Packit 5e46da
Packit 5e46da
static void _update_face(TEXTST_RENDER *p, FT_Face *face, const BD_TEXTST_REGION_STYLE *style)
Packit 5e46da
{
Packit 5e46da
    if (style->font_id_ref >= p->font_count || !p->font[style->font_id_ref].face) {
Packit 5e46da
        TEXTST_ERROR("textst_Render: incorrect font index %d\n", style->font_id_ref);
Packit 5e46da
        if (!*face) {
Packit 5e46da
            *face = p->font[0].face;
Packit 5e46da
        }
Packit 5e46da
    } else {
Packit 5e46da
        *face = p->font[style->font_id_ref].face;
Packit 5e46da
    }
Packit 5e46da
    FT_Set_Char_Size(*face, 0, style->font_size << 6, 0, 0);
Packit 5e46da
}
Packit 5e46da
Packit 5e46da
static int _render_line(TEXTST_RENDER *p, TEXTST_BITMAP *bmp,
Packit 5e46da
                        const BD_TEXTST_REGION_STYLE *base_style,
Packit 5e46da
                        BD_TEXTST_REGION_STYLE *style,
Packit 5e46da
                        uint8_t **p_ptr, int *p_elem_count,
Packit 5e46da
                        int xpos, int ypos, int *baseline_pos)
Packit 5e46da
{
Packit 5e46da
    FT_Face  face = NULL;
Packit 5e46da
Packit 5e46da
    /* select font */
Packit 5e46da
    _update_face(p, &face, style);
Packit 5e46da
Packit 5e46da
    while ( (*p_elem_count) > 0) {
Packit 5e46da
        BD_TEXTST_DATA *elem = (BD_TEXTST_DATA*)*p_ptr;
Packit 5e46da
        (*p_ptr) += sizeof(BD_TEXTST_DATA);
Packit 5e46da
        (*p_elem_count)--;
Packit 5e46da
Packit 5e46da
        switch (elem->type) {
Packit 5e46da
            case BD_TEXTST_DATA_STRING:
Packit 5e46da
                xpos = _draw_string(face, elem->data.text.string, elem->data.text.length,
Packit 5e46da
                                    bmp, xpos, ypos, style, baseline_pos);
Packit 5e46da
                (*p_ptr) += elem->data.text.length;
Packit 5e46da
                break;
Packit 5e46da
Packit 5e46da
            case BD_TEXTST_DATA_NEWLINE:
Packit 5e46da
                return xpos;
Packit 5e46da
Packit 5e46da
            case BD_TEXTST_DATA_FONT_ID:
Packit 5e46da
                style->font_id_ref = elem->data.font_id_ref;
Packit 5e46da
                _update_face(p, &face, style);
Packit 5e46da
                break;
Packit 5e46da
Packit 5e46da
            case BD_TEXTST_DATA_FONT_STYLE:
Packit 5e46da
                style->font_style =  elem->data.style.style;
Packit 5e46da
                style->outline_color = elem->data.style.outline_color;
Packit 5e46da
                style->outline_thickness = elem->data.style.outline_thickness;
Packit 5e46da
                if (style->font_style.outline_border) {
Packit 5e46da
                    TEXTST_ERROR("textst_render: unsupported style: outline\n");
Packit 5e46da
                }
Packit 5e46da
                break;
Packit 5e46da
Packit 5e46da
            case BD_TEXTST_DATA_FONT_SIZE:
Packit 5e46da
                style->font_size = elem->data.font_size;
Packit 5e46da
                _update_face(p, &face, style);
Packit 5e46da
                break;
Packit 5e46da
Packit 5e46da
            case BD_TEXTST_DATA_FONT_COLOR:
Packit 5e46da
                style->font_color = elem->data.font_color;
Packit 5e46da
                break;
Packit 5e46da
Packit 5e46da
            case BD_TEXTST_DATA_RESET_STYLE:
Packit 5e46da
                memcpy(style, base_style, sizeof(*style));
Packit 5e46da
                _update_face(p, &face, style);
Packit 5e46da
                break;
Packit 5e46da
Packit 5e46da
            default:
Packit 5e46da
                TEXTST_ERROR("Unknown control code %d\n", elem->type);
Packit 5e46da
                break;
Packit 5e46da
        }
Packit 5e46da
    }
Packit 5e46da
Packit 5e46da
    return xpos;
Packit 5e46da
}
Packit 5e46da
Packit 5e46da
#endif /* HAVE_FT2 */
Packit 5e46da
Packit 5e46da
int textst_render(TEXTST_RENDER *p,
Packit 5e46da
                  TEXTST_BITMAP *bmp,
Packit 5e46da
                  const BD_TEXTST_REGION_STYLE *base_style,
Packit 5e46da
                  const BD_TEXTST_DIALOG_REGION *region)
Packit 5e46da
{
Packit 5e46da
#ifdef HAVE_FT2
Packit 5e46da
Packit 5e46da
    /* fonts loaded ? */
Packit 5e46da
    if (p->font_count < 1) {
Packit 5e46da
        TEXTST_ERROR("textst_render: no fonts loaded\n");
Packit 5e46da
        return -1;
Packit 5e46da
    }
Packit 5e46da
Packit 5e46da
    /* TODO: */
Packit 5e46da
    if (base_style->text_flow != BD_TEXTST_FLOW_LEFT_RIGHT) {
Packit 5e46da
        TEXTST_ERROR("textst_render: unsupported text flow type %d\n", base_style->text_flow);
Packit 5e46da
    }
Packit 5e46da
    if (bmp->argb) {
Packit 5e46da
        TEXTST_ERROR("textst_render: ARGB output not implemented\n");
Packit 5e46da
        return -1;
Packit 5e46da
    }
Packit 5e46da
    if (base_style->font_style.outline_border) {
Packit 5e46da
        /* TODO: styles: see ex. vlc/modules/text_renderer/freetype.c ; function GetGlyph() */
Packit 5e46da
        TEXTST_ERROR("textst_render: unsupported style: outline\n");
Packit 5e46da
    }
Packit 5e46da
Packit 5e46da
    /* */
Packit 5e46da
Packit 5e46da
    BD_TEXTST_REGION_STYLE s;   /* current style settings */
Packit 5e46da
    unsigned  line;
Packit 5e46da
    uint8_t  *ptr = (uint8_t*)region->elem;
Packit 5e46da
    int       elem_count = region->elem_count;
Packit 5e46da
    int       xpos = 0;
Packit 5e46da
    int       ypos = 0;
Packit 5e46da
Packit 5e46da
    /* settings can be changed and reset with inline codes. Use local copy. */
Packit 5e46da
    memcpy(&s, base_style, sizeof(s));
Packit 5e46da
Packit 5e46da
    /* apply vertical alignment */
Packit 5e46da
    switch (s.text_valign) {
Packit 5e46da
        case BD_TEXTST_VALIGN_TOP:
Packit 5e46da
            break;
Packit 5e46da
        case BD_TEXTST_VALIGN_BOTTOM:
Packit 5e46da
            ypos = s.text_box.height - region->line_count * s.line_space;
Packit 5e46da
            break;
Packit 5e46da
        case BD_TEXTST_VALIGN_MIDDLE:
Packit 5e46da
            ypos = (s.text_box.height - region->line_count * s.line_space) / 2;
Packit 5e46da
            break;
Packit 5e46da
        default:
Packit 5e46da
            TEXTST_ERROR("textst_render: unsupported vertical align %d\n", s.text_halign);
Packit 5e46da
            break;
Packit 5e46da
    }
Packit 5e46da
Packit 5e46da
    for (line = 0; line < region->line_count; line++) {
Packit 5e46da
Packit 5e46da
        /* calculate line width and max. ascender */
Packit 5e46da
        uint8_t *ptr_tmp = ptr;
Packit 5e46da
        int elem_count_tmp = elem_count;
Packit 5e46da
        BD_TEXTST_REGION_STYLE style_tmp;
Packit 5e46da
        int baseline = 0, line_width;
Packit 5e46da
Packit 5e46da
        /* dry-run: count line width and height */
Packit 5e46da
        memcpy(&style_tmp, &s, sizeof(s)); /* use copy in first pass */
Packit 5e46da
        line_width = _render_line(p, NULL, base_style, &style_tmp, &ptr_tmp, &elem_count_tmp, 0, 0, &baseline);
Packit 5e46da
Packit 5e46da
        /* adjust to baseline */
Packit 5e46da
        ypos += baseline;
Packit 5e46da
Packit 5e46da
        /* apply horizontal alignment */
Packit 5e46da
        xpos = 0;
Packit 5e46da
        switch (s.text_halign) {
Packit 5e46da
            case BD_TEXTST_HALIGN_LEFT:
Packit 5e46da
                break;
Packit 5e46da
            case BD_TEXTST_HALIGN_RIGHT:
Packit 5e46da
                xpos = s.text_box.width - line_width - 1;
Packit 5e46da
                break;
Packit 5e46da
            case BD_TEXTST_HALIGN_CENTER:
Packit 5e46da
                xpos = (s.text_box.width - line_width) / 2 - 1;
Packit 5e46da
                break;
Packit 5e46da
            default:
Packit 5e46da
                TEXTST_ERROR("textst_render: unsupported horizontal align %d\n", s.text_halign);
Packit 5e46da
                break;
Packit 5e46da
        }
Packit 5e46da
Packit 5e46da
        /* render line */
Packit 5e46da
        _render_line(p, bmp, base_style, &s, &ptr, &elem_count, xpos, ypos, NULL);
Packit 5e46da
Packit 5e46da
        ypos += s.line_space - baseline;
Packit 5e46da
    }
Packit 5e46da
Packit 5e46da
#else
Packit 5e46da
    (void)p;
Packit 5e46da
    (void)bmp;
Packit 5e46da
    (void)base_style;
Packit 5e46da
    (void)region;
Packit 5e46da
#endif /* HAVE_FT2 */
Packit 5e46da
Packit 5e46da
    return 0;
Packit 5e46da
}