diff --git a/src/text.h b/src/text.h new file mode 100644 index 0000000..906b897 --- /dev/null +++ b/src/text.h @@ -0,0 +1,108 @@ +/* + * kmscon - Text Renderer + * + * Copyright (c) 2012 David Herrmann + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Text Renderer + * The Text-Renderer subsystem provides a simple way to draw text into a + * framebuffer. The system is modular and several different backends are + * available that can be used. + * The system is split into: + * - Font renderer: The font renderer allows selecting fonts and rendering + * single glyphs into memory-buffers + * - Text renderer: The text renderer uses the font renderer to draw a whole + * console into a framebuffer. + */ + +#ifndef KMSCON_TEXT_H +#define KMSCON_TEXT_H + +#include +#include "unicode.h" +#include "uterm.h" + +/* fonts */ + +struct kmscon_font_attr; +struct kmscon_glyph; +struct kmscon_font; +struct kmscon_font_ops; + +#define KMSCON_FONT_MAX_NAME 128 +#define KMSCON_FONT_DEFAULT_NAME "monospace" +#define KMSCON_FONT_DEFAULT_PPI 72 + +struct kmscon_font_attr { + char name[KMSCON_FONT_MAX_NAME]; + unsigned int ppi; + unsigned int points; + bool bold; + bool italic; + unsigned int height; + unsigned int width; +}; + +void kmscon_font_attr_normalize(struct kmscon_font_attr *attr); +bool kmscon_font_attr_match(const struct kmscon_font_attr *a1, + const struct kmscon_font_attr *a2); + +struct kmscon_glyph { + unsigned int ascent; + unsigned int descent; + struct uterm_video_buffer buf; +}; + +struct kmscon_font { + unsigned long ref; + const struct kmscon_font_ops *ops; + struct kmscon_font_attr attr; + void *data; +}; + +struct kmscon_font_ops { + const char *name; + int (*init) (struct kmscon_font *out, + const struct kmscon_font_attr *attr); + void (*destroy) (struct kmscon_font *font); + int (*render) (struct kmscon_font *font, kmscon_symbol_t sym, + const struct kmscon_glyph **out); + void (*drop) (struct kmscon_font *font, + const struct kmscon_glyph *glyph); +}; + +int kmscon_font_register(const struct kmscon_font_ops *ops); +void kmscon_font_unregister(const char *name); + +int kmscon_font_find(struct kmscon_font **out, + const struct kmscon_font_attr *attr, + const char *backend); +void kmscon_font_ref(struct kmscon_font *font); +void kmscon_font_unref(struct kmscon_font *font); + +int kmscon_font_render(struct kmscon_font *font, kmscon_symbol_t sym, + const struct kmscon_glyph **out); +void kmscon_font_drop(struct kmscon_font *font, + const struct kmscon_glyph *glyph); + +#endif /* KMSCON_TEXT_H */ diff --git a/src/text_font.c b/src/text_font.c new file mode 100644 index 0000000..561be62 --- /dev/null +++ b/src/text_font.c @@ -0,0 +1,486 @@ +/* + * kmscon - Font handling of Text Renderer + * + * Copyright (c) 2012 David Herrmann + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * SECTION:text_font + * @short_description: Font handling of text renderer + * @include: text.h + * + * The text renderer needs a backend that draws glyphs which then can be shown + * on the screen. This font handling subsystem provides a very simple API to + * load arbitrary font-renderer backends. That is, you can choose from + * in-memory bitmap fonts up to full Unicode compatible font libraries like + * pango during runtime. + * + * This system does not provide any renderer by itself. You need to register one + * of the available font-renderers first which then is used as backend for this + * system. kmscon_font_register() and kmscon_font_unregister() can be used to + * register font-renderers manually. + * + * @kmscon_font_attr is used to specify font-attributes for the fonts you want. + * Please see kmscon_font_find() for more information on font-attributes. This + * function returns a matching font which then can be used for drawing. + * kmscon_font_ref()/kmscon_font_unref() are used for reference counting. + * kmscon_font_render() renders a single unicode glyph and returns the glyph + * buffer. kmscon_font_drop() frees this buffer again. A kmscon_glyph object + * contains a memory-buffer with the renderered glyph plus some metrics like + * height/width but also ascent/descent. + * + * Font-backends must take into account that this API must be thread-safe as it + * is shared between different threads to reduce memory-footprint. + */ + +#include +#include +#include +#include +#include "log.h" +#include "static_misc.h" +#include "text.h" +#include "uterm.h" + +#define LOG_SUBSYSTEM "text_font" + +struct font_backend { + struct kmscon_dlist list; + const struct kmscon_font_ops *ops; +}; + +static pthread_mutex_t font_mutex = PTHREAD_MUTEX_INITIALIZER; +static struct kmscon_dlist font__list = KMSCON_DLIST_INIT(font__list); + +static void font_lock() +{ + pthread_mutex_lock(&font_mutex); +} + +static void font_unlock() +{ + pthread_mutex_unlock(&font_mutex); +} + +/** + * kmscon_font_attr_normalize: + * @attr: Attribute to normalize + * + * This normalizes @attr and fills out missing entries. The following is done: + * - If attr->name is empty, then it is set to KMSCON_FONT_DEFAULT_NAME + * - If attr->ppi is 0, it is set to KMSCON_FONT_DEFAULT_PPI + * - If attr->height is not set but attr->points is given, then attr->heights is + * calculated from attr->points. + * - If attr->height is set, then attr->points is recalculated and overwritten + * + * The other fields are not changed. If attr->points is set but attr->height is + * not set, then the height is calculated and after that the points are + * recalculated so we will never have division-errors. + */ +void kmscon_font_attr_normalize(struct kmscon_font_attr *attr) +{ + if (!attr) + return; + + if (!*attr->name) + memcpy(attr->name, KMSCON_FONT_DEFAULT_NAME, + sizeof(KMSCON_FONT_DEFAULT_NAME)); + + if (!attr->ppi) + attr->ppi = KMSCON_FONT_DEFAULT_PPI; + + if (!attr->height && attr->points) + attr->height = attr->points * attr->ppi / 72; + if (attr->height) + attr->points = attr->height * 72 / attr->ppi; +} + +/** + * kmscon_font_attr_match: + * @a1: First attribute to match + * @a2: Second attribute to match + * + * Compares @a1 and @a2 and returns true if they match. Both must be normalized + * before comparing them, otherwise the comparison may return inexact results. + * If width, height or *name is 0, then the fields are _not_ compared so you can + * have wildmask matches. + * points and dpi are never compared as the normalization already computes the + * height correctly. So there is no need to use these. + * + * Returns: true if they match, otherwise false + */ +bool kmscon_font_attr_match(const struct kmscon_font_attr *a1, + const struct kmscon_font_attr *a2) +{ + if (!a1 || !a2) + return false; + + if (a1->width && a2->width && a1->width != a2->width) + return false; + if (a1->height && a2->height && a1->height != a2->height) + return false; + if (a1->bold != a2->bold) + return false; + if (a1->italic != a2->italic) + return false; + if (*a1->name && *a2->name && strcmp(a1->name, a2->name)) + return false; + + return true; +} + +/** + * kmscon_font_register: + * @ops: Font operations and name for new font backend + * + * This register a new font backend with operations set to @ops. The name + * @ops->name must be valid. + * + * The first font that is registered automatically becomes the default font and + * the fallback font. So make sure you register a safe fallback as first font. + * If this font is unregistered, the next font in the list becomes the default + * and fallback font. + * + * Returns: 0 on success, negative error code on failure + */ +int kmscon_font_register(const struct kmscon_font_ops *ops) +{ + struct kmscon_dlist *iter; + struct font_backend *be; + int ret; + + if (!ops || !ops->name) + return -EINVAL; + + log_debug("register font backend %s", ops->name); + + font_lock(); + + kmscon_dlist_for_each(iter, &font__list) { + be = kmscon_dlist_entry(iter, struct font_backend, list); + if (!strcmp(be->ops->name, ops->name)) { + log_error("registering already available font backend %s", + ops->name); + ret = -EALREADY; + goto out_unlock; + } + } + + be = malloc(sizeof(*be)); + if (!be) { + log_error("cannot allocate memory for font backend"); + ret = -ENOMEM; + goto out_unlock; + } + + memset(be, 0, sizeof(*be)); + be->ops = ops; + kmscon_dlist_link(&font__list, &be->list); + + ret = 0; + +out_unlock: + font_unlock(); + return ret; +} + +/** + * kmscon_font_unregister: + * @name: Name of font backend + * + * This unregisters the font-backend that is registered with name @name. If + * @name is not found, a warning is printed but nothing else is done. + */ +void kmscon_font_unregister(const char *name) +{ + struct kmscon_dlist *iter; + struct font_backend *be; + + if (!name) + return; + + log_debug("unregister font backend %s", name); + + font_lock(); + + kmscon_dlist_for_each(iter, &font__list) { + be = kmscon_dlist_entry(iter, struct font_backend, list); + if (strcmp(name, be->ops->name)) + continue; + + kmscon_dlist_unlink(&be->list); + break; + } + + if (iter == &font__list) + be = NULL; + + font_unlock(); + + if (!be) { + log_error("cannot unregister font backend %s: not found", name); + } else { + free(be); + } +} + +/** + * kmscon_font_find: + * @out: A pointer to the new font is stored here + * @attr: Attribute describing the font + * @backend: Backend to use or NULL for default backend + * + * Lookup a font by the given attributes. It uses the font backend @backend. If + * it is NULL, the default backend is used. If the given backend cannot find the + * a suitable font, the fallback backend is tried. This backend should always + * find a suitable font. + * + * Stores a pointer to the new font in @out and returns 0. Otherwise, @out is + * not touched and an error is returned. + * + * The attributes in @attr are not always matched. There are even font backends + * which have only one fixed font and always return this one so you cannot rely + * on this behavior. That is, this function cannot be used to get an exact + * match, it rather returns the best matching font. + * There is currently no need to get an exact match so no API is available to + * get this. Instead, you should always use the best match and the user must be + * happy. We do print warnings if no close match can be found, though. The user + * should read them if they want more information what font fallback was used. + * + * If this functions fails, you must not assume that there is another font that + * might work. Moreover, you must not implement a fallback font yourself as this + * is already implemented inside of this function! This function fails only due + * to internal errors like failed memory allocations. If it fails, the chances + * that you can allocate your own fallback font are pretty small so don't do it. + * + * About DPI and Point Sizes: + * Many computer graphics systems use "Points" as measurement for font sizes. + * However, most of them also use 72 or 96 as fixed DPI size for monitors. This + * means, the Point sizes can be directly converted into pixels. But lets + * look at the facts: + * 1 Point is defined as 1/72 of an inch. That is, a 10 Point font will be + * exactly 10 / 72 inches, which is ~0.13889 inches, which is + * 0.13889 * 2.54 cm, which is approximately 0.3528 cm. This applies to + * printed paper. If we want the same on a monitor, we must need more + * information. First, the monitor renders in pixels, that is, we must know + * how many Pixels per Inch (PPI) are displayed. Often the same information is + * given as Dots per Inch (DPI) but these two are identical in this context. + * If the DPI is 96, we know that our 10 Point font is 10 / 72 inches. Which + * then means it is 10 / 72 * 96 pixels, which is ~13.333 pixels. So we + * internally render the font with 13 pixels and display it as 13 pixels. This + * guarantees, that the font will be 10 Point big which means 0.3528 cm on the + * display. This of course requires that we know the exact PPI/DPI of the + * display. + * But if we take into account that Windows uses fixed 96 PPI and Mac OS X 72 + * PPI (independent of the monitor), they drop all this information and instead + * render the font in pixel sizes. Because if you use fixed 72 PPI, a 10 Point + * font will always be 10 / 72 * 72 = 10 pixels high. This means, it would be + * rather convenient to directly specify pixel-sizes on the monitor. If you want + * to work with documents that shall be printed, you want to specify Points so + * the printed result will look nice. But the disadvantage is, that your monitor + * can print this font in the weirdest size if it uses PPI much bigger or lower + * than the common 96 or 72. Therefore, if you work with a monitor you probably + * want to also specify the pixel-height of the font as you probably don't know + * the PPI of your monitor and don't want to do all that math in your head. + * Therefore, for applications that will probably never print their output (like + * the virtual (!) console this is for), it is often requested that we can + * specify the pixel size instead of the Point size of a font so you can + * predict the output better. + * Hence, we provide both. If pixel information is given, that is, attr->height + * is not 0, then we try to return a font with this pixel height. + * If it is 0, attr->points is used together with attr->ppi to calculate the + * pixel size. If attr->ppi is 0, then 72 is used. + * After the font was chosen, all fields "points", "ppi", "height" and "width" + * will contain the exact values for this font. If "ppi" was zero and pixel + * sizes where specified, then the resulting "points" size is calculated with + * "ppi" = 72 again. So if you use the "points" field please always specify + * "ppi", either. + * + * Returns: 0 on success, error code on failure + */ +int kmscon_font_find(struct kmscon_font **out, + const struct kmscon_font_attr *attr, + const char *backend) +{ + struct kmscon_font *font; + struct kmscon_dlist *iter; + struct font_backend *be, *def; + int ret; + + if (!out || !attr) + return -EINVAL; + + font_lock(); + + log_debug("searching for: be: %s nm: %s ppi: %u pt: %u b: %d i: %d he: %u wt: %u", + backend, attr->name, attr->ppi, attr->points, + attr->bold, attr->italic, attr->height, + attr->width); + + if (kmscon_dlist_empty(&font__list)) { + log_error("no font backend available"); + ret = -EFAULT; + } else { + ret = 0; + def = kmscon_dlist_entry(font__list.next, + struct font_backend, + list); + if (!backend) { + be = def; + } else { + kmscon_dlist_for_each(iter, &font__list) { + be = kmscon_dlist_entry(iter, + struct font_backend, + list); + if (!strcmp(backend, be->ops->name)) + break; + } + if (iter == &font__list) { + log_warning("requested backend %s not found", + backend); + be = def; + } + } + } + + if (ret) + goto out_unlock; + + font = malloc(sizeof(*font)); + if (!font) { + log_error("cannot allocate memory for new font"); + ret = -ENOMEM; + goto out_unlock; + } + memset(font, 0, sizeof(*font)); + font->ref = 1; + font->ops = be->ops; + + ret = font->ops->init(font, attr); + if (ret) { + if (be == def) { + log_error("default backend %s cannot find font", + font->ops->name); + goto err_free; + } + + log_warning("backend %s cannot find font; trying default backend %s", + be->ops->name, def->ops->name); + + memset(font, 0, sizeof(*font)); + font->ref = 1; + font->ops = def->ops; + + ret = font->ops->init(font, attr); + if (ret) { + log_error("default backend %s cannot find font", + font->ops->name); + goto err_free; + } + } + + log_debug("using: be: %s nm: %s ppi: %u pt: %u b: %d i: %d he: %u wt: %u", + font->ops->name, font->attr.name, font->attr.ppi, + font->attr.points, font->attr.bold, font->attr.italic, + font->attr.height, font->attr.width); + *out = font; + ret = 0; + goto out_unlock; + +err_free: + free(font); +out_unlock: + font_unlock(); + return ret; +} + +/** + * kmscon_font_ref: + * @font: Valid font object + * + * This increases the reference count of @font by one. + */ +void kmscon_font_ref(struct kmscon_font *font) +{ + if (!font || !font->ref) + return; + + ++font->ref; +} + +/** + * kmscon_font_unref: + * @font: Valid font object + * + * This decreases the reference count of @font by one. If it drops to zero, the + * object is freed. + */ +void kmscon_font_unref(struct kmscon_font *font) +{ + if (!font || !font->ref || --font->ref) + return; + + font_lock(); + log_debug("freeing font"); + font->ops->destroy(font); + free(font); + font_unlock(); +} + +/** + * kmscon_font_render: + * @font: Valid font object + * @sym: Symbol to find a glyph for + * @out: Output buffer for glyph + * + * Renders the glyph for symbol @sym and places a pointer to the glyph in @out. + * If the glyph cannot be found or is invalid, an error is returned. You must + * release the glyph with kmscon_font_drop() if you don't need it anymore. + * + * Returns: 0 on success, negative error code on failure + */ +int kmscon_font_render(struct kmscon_font *font, kmscon_symbol_t sym, + const struct kmscon_glyph **out) +{ + if (!font || !out) + return -EINVAL; + + return font->ops->render(font, sym, out); +} + +/** + * kmscon_font_drop: + * @font: Valid font object + * @glyph: Glyph object from font @font + * + * This releases a glyph that was previously retrieved via kmscon_font_render(). + * Please note that the glyphs are shared and this does not necessarily free the + * memory if there are still other users. + */ +void kmscon_font_drop(struct kmscon_font *font, + const struct kmscon_glyph *glyph) +{ + if (!font || !glyph) + return; + + font->ops->drop(font, glyph); +}