mirror of
https://github.com/raspberrypi/userland.git
synced 2025-12-06 04:49:12 +00:00
407 lines
12 KiB
C
407 lines
12 KiB
C
/*
|
|
Copyright (c) 2012, Broadcom Europe Ltd
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* 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.
|
|
* Neither the name of the copyright holder nor the
|
|
names of its contributors may be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT HOLDER 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.
|
|
*/
|
|
|
|
// Font handling for graphicsx
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "graphics_x_private.h"
|
|
#include "vgft.h"
|
|
|
|
static FT_Library lib;
|
|
|
|
int vgft_init(void)
|
|
{
|
|
if (FT_Init_FreeType(&lib) == 0)
|
|
return 0;
|
|
else
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
void vgft_term(void)
|
|
{
|
|
FT_Done_FreeType(lib);
|
|
}
|
|
|
|
#define SEGMENTS_COUNT_MAX 256
|
|
#define COORDS_COUNT_MAX 1024
|
|
|
|
static VGuint segments_count;
|
|
static VGubyte segments[SEGMENTS_COUNT_MAX];
|
|
static VGuint coords_count;
|
|
static VGfloat coords[COORDS_COUNT_MAX];
|
|
|
|
static VGfloat float_from_26_6(FT_Pos x)
|
|
{
|
|
return (VGfloat)x / 64.0f;
|
|
}
|
|
|
|
static void convert_contour(const FT_Vector *points, const char *tags, short points_count)
|
|
{
|
|
int first_coords = coords_count;
|
|
|
|
int first = 1;
|
|
char last_tag = 0;
|
|
int c = 0;
|
|
|
|
for (; points_count != 0; ++points, ++tags, --points_count) {
|
|
++c;
|
|
|
|
char tag = *tags;
|
|
if (first) {
|
|
assert(tag & 0x1);
|
|
assert(c==1); c=0;
|
|
segments[segments_count++] = VG_MOVE_TO;
|
|
first = 0;
|
|
} else if (tag & 0x1) {
|
|
/* on curve */
|
|
|
|
if (last_tag & 0x1) {
|
|
/* last point was also on -- line */
|
|
assert(c==1); c=0;
|
|
segments[segments_count++] = VG_LINE_TO;
|
|
} else {
|
|
/* last point was off -- quad or cubic */
|
|
if (last_tag & 0x2) {
|
|
/* cubic */
|
|
assert(c==3); c=0;
|
|
segments[segments_count++] = VG_CUBIC_TO;
|
|
} else {
|
|
/* quad */
|
|
assert(c==2); c=0;
|
|
segments[segments_count++] = VG_QUAD_TO;
|
|
}
|
|
}
|
|
} else {
|
|
/* off curve */
|
|
|
|
if (tag & 0x2) {
|
|
/* cubic */
|
|
|
|
assert((last_tag & 0x1) || (last_tag & 0x2)); /* last either on or off and cubic */
|
|
} else {
|
|
/* quad */
|
|
|
|
if (!(last_tag & 0x1)) {
|
|
/* last was also off curve */
|
|
|
|
assert(!(last_tag & 0x2)); /* must be quad */
|
|
|
|
/* add on point half-way between */
|
|
assert(c==2); c=1;
|
|
segments[segments_count++] = VG_QUAD_TO;
|
|
VGfloat x = (coords[coords_count - 2] + float_from_26_6(points->x)) * 0.5f;
|
|
VGfloat y = (coords[coords_count - 1] + float_from_26_6(points->y)) * 0.5f;
|
|
coords[coords_count++] = x;
|
|
coords[coords_count++] = y;
|
|
}
|
|
}
|
|
}
|
|
last_tag = tag;
|
|
|
|
coords[coords_count++] = float_from_26_6(points->x);
|
|
coords[coords_count++] = float_from_26_6(points->y);
|
|
}
|
|
|
|
if (last_tag & 0x1) {
|
|
/* last point was also on -- line (implicit with close path) */
|
|
assert(c==0);
|
|
} else {
|
|
++c;
|
|
|
|
/* last point was off -- quad or cubic */
|
|
if (last_tag & 0x2) {
|
|
/* cubic */
|
|
assert(c==3); c=0;
|
|
segments[segments_count++] = VG_CUBIC_TO;
|
|
} else {
|
|
/* quad */
|
|
assert(c==2); c=0;
|
|
segments[segments_count++] = VG_QUAD_TO;
|
|
}
|
|
|
|
coords[coords_count++] = coords[first_coords + 0];
|
|
coords[coords_count++] = coords[first_coords + 1];
|
|
}
|
|
|
|
segments[segments_count++] = VG_CLOSE_PATH;
|
|
}
|
|
|
|
static void convert_outline(const FT_Vector *points, const char *tags, const short *contours, short contours_count, short points_count)
|
|
{
|
|
segments_count = 0;
|
|
coords_count = 0;
|
|
|
|
short last_contour = 0;
|
|
for (; contours_count != 0; ++contours, --contours_count) {
|
|
short contour = *contours + 1;
|
|
convert_contour(points + last_contour, tags + last_contour, contour - last_contour);
|
|
last_contour = contour;
|
|
}
|
|
assert(last_contour == points_count);
|
|
|
|
assert(segments_count <= SEGMENTS_COUNT_MAX); /* oops... we overwrote some memory */
|
|
assert(coords_count <= COORDS_COUNT_MAX);
|
|
}
|
|
|
|
VCOS_STATUS_T vgft_font_init(VGFT_FONT_T *font)
|
|
{
|
|
font->ft_face = NULL;
|
|
font->vg_font = vgCreateFont(0);
|
|
if (font->vg_font == VG_INVALID_HANDLE)
|
|
{
|
|
return VCOS_ENOMEM;
|
|
}
|
|
return VCOS_SUCCESS;
|
|
}
|
|
|
|
VCOS_STATUS_T vgft_font_load_mem(VGFT_FONT_T *font, void *mem, size_t len)
|
|
{
|
|
if (FT_New_Memory_Face(lib, mem, len, 0, &font->ft_face))
|
|
{
|
|
return VCOS_EINVAL;
|
|
}
|
|
return VCOS_SUCCESS;
|
|
}
|
|
|
|
VCOS_STATUS_T vgft_font_load_file(VGFT_FONT_T *font, const char *file)
|
|
{
|
|
if (FT_New_Face(lib, file, 0, &font->ft_face)) {
|
|
return VCOS_EINVAL;
|
|
}
|
|
return VCOS_SUCCESS;
|
|
}
|
|
|
|
VCOS_STATUS_T vgft_font_convert_glyphs(VGFT_FONT_T *font, unsigned int char_height, unsigned int dpi_x, unsigned int dpi_y)
|
|
{
|
|
FT_UInt glyph_index;
|
|
FT_ULong ch;
|
|
|
|
if (FT_Set_Char_Size(font->ft_face, 0, char_height, dpi_x, dpi_y))
|
|
{
|
|
FT_Done_Face(font->ft_face);
|
|
vgDestroyFont(font->vg_font);
|
|
return VCOS_EINVAL;
|
|
}
|
|
|
|
ch = FT_Get_First_Char(font->ft_face, &glyph_index);
|
|
|
|
while (ch != 0)
|
|
{
|
|
if (FT_Load_Glyph(font->ft_face, glyph_index, FT_LOAD_DEFAULT)) {
|
|
FT_Done_Face(font->ft_face);
|
|
vgDestroyFont(font->vg_font);
|
|
return VCOS_ENOMEM;
|
|
}
|
|
|
|
VGPath vg_path;
|
|
FT_Outline *outline = &font->ft_face->glyph->outline;
|
|
if (outline->n_contours != 0) {
|
|
vg_path = vgCreatePath(VG_PATH_FORMAT_STANDARD, VG_PATH_DATATYPE_F, 1.0f, 0.0f, 0, 0, VG_PATH_CAPABILITY_ALL);
|
|
assert(vg_path != VG_INVALID_HANDLE);
|
|
|
|
convert_outline(outline->points, outline->tags, outline->contours, outline->n_contours, outline->n_points);
|
|
vgAppendPathData(vg_path, segments_count, segments, coords);
|
|
} else {
|
|
vg_path = VG_INVALID_HANDLE;
|
|
}
|
|
|
|
VGfloat origin[] = { 0.0f, 0.0f };
|
|
VGfloat escapement[] = { float_from_26_6(font->ft_face->glyph->advance.x), float_from_26_6(font->ft_face->glyph->advance.y) };
|
|
vgSetGlyphToPath(font->vg_font, glyph_index, vg_path, VG_FALSE, origin, escapement);
|
|
|
|
if (vg_path != VG_INVALID_HANDLE) {
|
|
vgDestroyPath(vg_path);
|
|
}
|
|
ch = FT_Get_Next_Char(font->ft_face, ch, &glyph_index);
|
|
}
|
|
|
|
return VCOS_SUCCESS;
|
|
}
|
|
|
|
void vgft_font_term(VGFT_FONT_T *font)
|
|
{
|
|
if (font->ft_face)
|
|
FT_Done_Face(font->ft_face);
|
|
if (font->vg_font)
|
|
vgDestroyFont(font->vg_font);
|
|
memset(font, 0, sizeof(*font));
|
|
}
|
|
|
|
|
|
#define CHAR_COUNT_MAX 200
|
|
static VGuint glyph_indices[CHAR_COUNT_MAX];
|
|
static VGfloat adjustments_x[CHAR_COUNT_MAX];
|
|
static VGfloat adjustments_y[CHAR_COUNT_MAX];
|
|
|
|
// Draws the first char_count characters from text, with adjustments, starting
|
|
// from the current origin. The peek argument indicates whether to peek ahead
|
|
// and get a final adjustment based on the next character past char_count, or
|
|
// else just assume that this is the end of the text and add no final
|
|
// adjustment.
|
|
|
|
static void draw_chars(VGFT_FONT_T *font, const char *text, int char_count, VGbitfield paint_modes, int peek) {
|
|
// Put in first character
|
|
glyph_indices[0] = FT_Get_Char_Index(font->ft_face, text[0]);
|
|
int prev_glyph_index = glyph_indices[0];
|
|
|
|
// Calculate glyph_indices and adjustments
|
|
int i;
|
|
FT_Vector kern;
|
|
for (i = 1; i != char_count; ++i) {
|
|
int glyph_index = FT_Get_Char_Index(font->ft_face, text[i]);
|
|
if (!glyph_index) { return; }
|
|
glyph_indices[i] = glyph_index;
|
|
|
|
if (FT_Get_Kerning(font->ft_face, prev_glyph_index, glyph_index, FT_KERNING_DEFAULT, &kern)) assert(0);
|
|
adjustments_x[i - 1] = float_from_26_6(kern.x);
|
|
adjustments_y[i - 1] = float_from_26_6(kern.y);
|
|
|
|
prev_glyph_index = glyph_index;
|
|
}
|
|
|
|
// Get the last adjustment?
|
|
if (peek) {
|
|
int peek_glyph_index = FT_Get_Char_Index(font->ft_face, text[i]);
|
|
if (!peek_glyph_index) { return; }
|
|
if (FT_Get_Kerning(font->ft_face, prev_glyph_index, peek_glyph_index, FT_KERNING_DEFAULT, &kern)) assert(0);
|
|
adjustments_x[char_count - 1] = float_from_26_6(kern.x);
|
|
adjustments_y[char_count - 1] = float_from_26_6(kern.y);
|
|
} else {
|
|
adjustments_x[char_count - 1] = 0.0f;
|
|
adjustments_y[char_count - 1] = 0.0f;
|
|
}
|
|
|
|
vgDrawGlyphs(font->vg_font, char_count, glyph_indices, adjustments_x, adjustments_y, paint_modes, VG_FALSE);
|
|
}
|
|
|
|
// Goes to the x,y position and draws arbitrary number of characters, draws
|
|
// iteratively if the char_count exceeds the max buffer size given above.
|
|
|
|
static void draw_line(VGFT_FONT_T *font, VGfloat x, VGfloat y, const char *text, int char_count, VGbitfield paint_modes) {
|
|
if (char_count == 0) return;
|
|
|
|
// Set origin to requested x,y
|
|
VGfloat glor[] = { x, y };
|
|
vgSetfv(VG_GLYPH_ORIGIN, 2, glor);
|
|
|
|
// Draw the characters in blocks to reuse buffer memory
|
|
const char *curr_text = text;
|
|
int chars_left = char_count;
|
|
while (chars_left > CHAR_COUNT_MAX) {
|
|
draw_chars(font, curr_text, CHAR_COUNT_MAX, paint_modes, 1);
|
|
chars_left -= CHAR_COUNT_MAX;
|
|
curr_text += CHAR_COUNT_MAX;
|
|
}
|
|
|
|
// Draw the last block
|
|
draw_chars(font, curr_text, chars_left, paint_modes, 0);
|
|
}
|
|
|
|
void vgft_font_draw(VGFT_FONT_T *font, VGfloat x, VGfloat y, const char *text, unsigned text_length, VGbitfield paint_modes)
|
|
{
|
|
VGfloat descent = float_from_26_6(font->ft_face->size->metrics.descender);
|
|
int last_draw = 0;
|
|
int i = 0;
|
|
y -= descent;
|
|
for (;;) {
|
|
int last = !text[i] || (text_length && i==text_length);
|
|
|
|
if ((text[i] == '\n') || last)
|
|
{
|
|
draw_line(font, x, y, text + last_draw, i - last_draw, paint_modes);
|
|
last_draw = i+1;
|
|
y -= float_from_26_6(font->ft_face->size->metrics.height);
|
|
}
|
|
if (last)
|
|
{
|
|
break;
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
|
|
// Get text extents for a single line
|
|
|
|
static void line_extents(VGFT_FONT_T *font, VGfloat *x, VGfloat *y, const char *text, int chars_count)
|
|
{
|
|
int i;
|
|
int prev_glyph_index = 0;
|
|
if (chars_count == 0) return;
|
|
|
|
for (i=0; i < chars_count; i++)
|
|
{
|
|
int glyph_index = FT_Get_Char_Index(font->ft_face, text[i]);
|
|
if (!glyph_index) return;
|
|
|
|
if (i != 0)
|
|
{
|
|
FT_Vector kern;
|
|
if (FT_Get_Kerning(font->ft_face, prev_glyph_index, glyph_index,
|
|
FT_KERNING_DEFAULT, &kern))
|
|
{
|
|
assert(0);
|
|
}
|
|
*x += float_from_26_6(kern.x);
|
|
*y += float_from_26_6(kern.y);
|
|
}
|
|
FT_Load_Glyph(font->ft_face, glyph_index, FT_LOAD_DEFAULT);
|
|
*x += float_from_26_6(font->ft_face->glyph->advance.x);
|
|
}
|
|
}
|
|
|
|
// Text extents for some ASCII text.
|
|
//
|
|
// Use text_length if non-zero, otherwise look for trailing '\0'.
|
|
|
|
void vgft_get_text_extents(VGFT_FONT_T *font,
|
|
const char *text,
|
|
unsigned text_length,
|
|
VGfloat start_x, VGfloat start_y,
|
|
VGfloat *w, VGfloat *h) {
|
|
int last_draw = 0;
|
|
VGfloat max_x = start_x;
|
|
VGfloat y = start_y;
|
|
|
|
int i, last;
|
|
for (i = 0, last = 0; !last; ++i) {
|
|
last = !text[i] || (text_length && i==text_length);
|
|
if ((text[i] == '\n') || last) {
|
|
VGfloat x = 0;
|
|
line_extents(font, &x, &y, text + last_draw, i - last_draw);
|
|
last_draw = i+1;
|
|
y -= float_from_26_6(font->ft_face->size->metrics.height);
|
|
if (x > max_x) max_x = x;
|
|
}
|
|
}
|
|
*w = max_x - start_x;
|
|
*h = start_y - y;
|
|
}
|