Add kmscon_font type

A kmscon_font object is used to store the current font information. It allows to
draw any kind of UTF-8 string to the screen. Internally, it uses kmscon_glyph to
store glyph information for every character that has been drawn so redrawing it
is much faster.

Currently, we only support GLYPH_LAYOUT as caching method which is quite slow.
However, it supports any kind of input and always works. Better and faster
caching algorithms like cairo_scaled_font_t will be added later.

Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
This commit is contained in:
David Herrmann 2011-11-26 16:33:06 +01:00
parent a33f9f589c
commit 27807e913e
2 changed files with 362 additions and 0 deletions

View File

@ -14,10 +14,12 @@
* of it.
*/
#include <cairo.h>
#include <inttypes.h>
#include <stdlib.h>
struct kmscon_char;
struct kmscon_font;
struct kmscon_console;
/* single printable characters */
@ -31,6 +33,15 @@ const char *kmscon_char_get_u8(const struct kmscon_char *ch);
size_t kmscon_char_get_len(const struct kmscon_char *ch);
int kmscon_char_append_u8(struct kmscon_char *ch, const char *str, size_t len);
/* font objects with cached glyphs */
int kmscon_font_new(struct kmscon_font **out);
void kmscon_font_ref(struct kmscon_font *font);
void kmscon_font_unref(struct kmscon_font *font);
int kmscon_font_draw(struct kmscon_font *font, const struct kmscon_char *ch,
cairo_t *cr, uint32_t x, uint32_t y);
/* console objects */
int kmscon_console_new(struct kmscon_console **out);

View File

@ -24,6 +24,10 @@
#include <stdlib.h>
#include <string.h>
#include <cairo.h>
#include <glib.h>
#include <pango/pango.h>
#include <pango/pangocairo.h>
#include "console.h"
/* maximum size of a single character */
@ -35,6 +39,31 @@ struct kmscon_char {
size_t len;
};
enum glyph_type {
GLYPH_NONE,
GLYPH_LAYOUT,
};
struct kmscon_glyph {
size_t ref;
struct kmscon_char *ch;
int type;
union {
struct layout {
PangoLayout *layout;
} layout;
} src;
};
struct kmscon_font {
size_t ref;
GHashTable *glyphs;
PangoContext *ctx;
};
int kmscon_char_new(struct kmscon_char **out)
{
struct kmscon_char *ch;
@ -157,3 +186,325 @@ int kmscon_char_append_u8(struct kmscon_char *ch, const char *str, size_t len)
return 0;
}
/*
* Create a hash for a kmscon_char. This uses a simple hash technique described
* by Daniel J. Bernstein.
*/
static guint kmscon_char_hash(gconstpointer key)
{
guint val = 5381;
size_t i;
const struct kmscon_char *ch = (void*)key;
for (i = 0; i < ch->len; ++i)
val = val * 33 + ch->buf[i];
return val;
}
/* compare two kmscon_char for equality */
static gboolean kmscon_char_equal(gconstpointer a, gconstpointer b)
{
const struct kmscon_char *ch1 = (void*)a;
const struct kmscon_char *ch2 = (void*)b;
if (ch1->len != ch2->len)
return FALSE;
return (memcpy(ch1->buf, ch2->buf, ch1->len) == 0);
}
/*
* Glyphs
* Glyphs are for internal use only! The outside world uses kmscon_char
* objects in combination with kmscon_font to draw characters. Internally, we
* cache a kmscon_glyph for every character that is drawn.
* This allows us to speed up the drawing operations because most characters are
* already cached.
*
* Glyphs are cached in a hash-table by each font. If a character is drawn, we
* look it up in the hash-table (or create a new one if none is found) and draw
* it to the framebuffer.
* A glyph may use several ways to cache the glyph description:
* GLYPH_NONE:
* No information is currently attached so the glyph cannot be drawn.
* GLYPH_LAYOUT:
* The most basic drawing operation. This is the slowest of all but can draw
* any text you want. It uses a PangoLayout internally and recalculates the
* character sizes each time we draw them.
*/
static int kmscon_glyph_new(struct kmscon_glyph **out,
const struct kmscon_char *ch)
{
struct kmscon_glyph *glyph;
int ret;
if (!out || !ch || !ch->len)
return -EINVAL;
glyph = malloc(sizeof(*glyph));
if (!glyph)
return -ENOMEM;
glyph->ref = 1;
glyph->type = GLYPH_NONE;
ret = kmscon_char_dup(&glyph->ch, ch);
if (ret)
goto err_free;
*out = glyph;
return 0;
err_free:
free(glyph);
return ret;
}
/*
* Reset internal glyph description. You must use kmscon_glyph_set() again to
* attach new glyph descriptions.
*/
static void kmscon_glyph_reset(struct kmscon_glyph *glyph)
{
if (!glyph)
return;
switch (glyph->type) {
case GLYPH_LAYOUT:
g_object_unref(glyph->src.layout.layout);
break;
}
glyph->type = GLYPH_NONE;
}
static void kmscon_glyph_ref(struct kmscon_glyph *glyph)
{
if (!glyph)
return;
++glyph->ref;
}
static void kmscon_glyph_unref(struct kmscon_glyph *glyph)
{
if (!glyph || !glyph->ref)
return;
if (--glyph->ref)
return;
kmscon_glyph_reset(glyph);
kmscon_char_free(glyph->ch);
free(glyph);
}
/*
* Generate glyph description.
* This connects the glyph with the given font an generates the fastest glyph
* description.
* Returns 0 on success.
*/
static int kmscon_glyph_set(struct kmscon_glyph *glyph,
struct kmscon_font *font)
{
PangoLayout *layout;
if (!glyph || !font)
return -EINVAL;
layout = pango_layout_new(font->ctx);
if (!layout)
return -EINVAL;
pango_layout_set_text(layout, glyph->ch->buf, glyph->ch->len);
kmscon_glyph_reset(glyph);
glyph->type = GLYPH_LAYOUT;
glyph->src.layout.layout = layout;
return 0;
}
/*
* Creates a new font
* Returns 0 on success and stores the new font in \out.
*/
int kmscon_font_new(struct kmscon_font **out)
{
struct kmscon_font *font;
int ret;
PangoFontDescription *desc;
PangoFontMap *map;
PangoLanguage *lang;
cairo_font_options_t *opt;
if (!out)
return -EINVAL;
font = malloc(sizeof(*font));
if (!font)
return -ENOMEM;
font->ref = 1;
map = pango_cairo_font_map_get_default();
if (!map) {
ret = -EFAULT;
goto err_free;
}
font->ctx = pango_font_map_create_context(map);
if (!font->ctx) {
ret = -EFAULT;
goto err_free;
}
pango_context_set_base_dir(font->ctx, PANGO_DIRECTION_LTR);
pango_cairo_context_set_resolution(font->ctx, 72);
desc = pango_font_description_from_string("monospace 18");
if (!desc) {
ret = -EFAULT;
goto err_ctx;
}
pango_context_set_font_description(font->ctx, desc);
pango_font_description_free(desc);
lang = pango_language_get_default();
if (!lang) {
ret = -EFAULT;
goto err_ctx;
}
pango_context_set_language(font->ctx, lang);
if (!pango_cairo_context_get_font_options(font->ctx)) {
opt = cairo_font_options_create();
if (!opt) {
ret = -EFAULT;
goto err_ctx;
}
pango_cairo_context_set_font_options(font->ctx, opt);
cairo_font_options_destroy(opt);
}
font->glyphs = g_hash_table_new_full(kmscon_char_hash,
kmscon_char_equal, (GDestroyNotify) kmscon_char_free,
(GDestroyNotify) kmscon_glyph_unref);
if (!font->glyphs) {
ret = -ENOMEM;
goto err_ctx;
}
*out = font;
return 0;
err_ctx:
g_object_unref(font->ctx);
err_free:
free(font);
return ret;
}
void kmscon_font_ref(struct kmscon_font *font)
{
if (!font)
return;
++font->ref;
}
void kmscon_font_unref(struct kmscon_font *font)
{
if (!font || !font->ref)
return;
if (--font->ref)
return;
g_hash_table_unref(font->glyphs);
g_object_unref(font->ctx);
free(font);
}
/*
* Get glyph for given key. If no glyph can be found in the hash-table, then a
* new glyph is created and added to the hash-table.
* Returns 0 on success and stores the glyph with a new reference in \out.
*/
static int kmscon_font_lookup(struct kmscon_font *font,
const struct kmscon_char *key, struct kmscon_glyph **out)
{
struct kmscon_glyph *glyph;
struct kmscon_char *ch;
int ret;
if (!font || !key || !out)
return -EINVAL;
glyph = g_hash_table_lookup(font->glyphs, key);
if (!glyph) {
ret = kmscon_char_dup(&ch, key);
if (ret)
return ret;
ret = kmscon_glyph_new(&glyph, key);
if (ret)
goto err_char;
ret = kmscon_glyph_set(glyph, font);
if (ret)
goto err_glyph;
g_hash_table_insert(font->glyphs, ch, glyph);
}
kmscon_glyph_ref(glyph);
*out = glyph;
return 0;
err_glyph:
kmscon_glyph_unref(glyph);
err_char:
kmscon_char_free(ch);
return ret;
}
/*
* This draws a glyph for characters \ch into the given cairo context \cr.
* The glyph will be drawn with the upper-left corner at x/y.
* Returns 0 on success.
*/
int kmscon_font_draw(struct kmscon_font *font, const struct kmscon_char *ch,
cairo_t *cr, uint32_t x, uint32_t y)
{
struct kmscon_glyph *glyph;
int ret;
if (!font || !ch || !cr)
return -EINVAL;
ret = kmscon_font_lookup(font, ch, &glyph);
if (ret)
return ret;
cairo_move_to(cr, x, y);
switch (glyph->type) {
case GLYPH_LAYOUT:
pango_cairo_update_layout(cr, glyph->src.layout.layout);
pango_cairo_show_layout(cr, glyph->src.layout.layout);
break;
default:
ret = -EFAULT;
break;
}
kmscon_glyph_unref(glyph);
return 0;
}