Add kmscon_font type
authorDavid Herrmann <dh.herrmann@googlemail.com>
Sat, 26 Nov 2011 15:33:06 +0000 (16:33 +0100)
committerDavid Herrmann <dh.herrmann@googlemail.com>
Sat, 26 Nov 2011 15:33:06 +0000 (16:33 +0100)
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>
src/console.h
src/console_char.c

index b6c1131..aa653ba 100644 (file)
  * 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);
index c47e32c..a70cc62 100644 (file)
 #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;
+}