ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

commit 55009a9b2fc3477235ddf5cae6c45446e0117802
parent 38fe277a39863666574934ce221dcf9bd9ee4cad
Author: lumidify <nobody@lumidify.org>
Date:   Sun, 13 Sep 2020 21:28:30 +0200

Fix basic rendering

Diffstat:
MREADME.md | 5++++-
Mcolor.c | 1+
Mconfig.mk | 12+++++++++---
Mdefs.h | 7+++++++
Mdraw.c | 4----
Mgrid.c | 1-
Mltk.h | 4++--
Asocket_format.txt | 25+++++++++++++++++++++++++
Mtext.h | 18+++++++++++-------
Dtext_common.c | 452-------------------------------------------------------------------------------
Dtext_common.h | 66------------------------------------------------------------------
Mtext_line.c | 30+++++++++++++++++-------------
Mtext_line.h | 16++++++++++------
Mtext_pango.c | 32++++++++++++++++++++++++--------
Dtext_pango.h | 51---------------------------------------------------
Atext_stb.c | 579+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutil.c | 2++
17 files changed, 691 insertions(+), 614 deletions(-)

diff --git a/README.md b/README.md @@ -3,7 +3,10 @@ Not much to see here. WARNING: DON'T TRY TO USE THIS! IT IS ONLY A PLACE FOR ME TO TRY OUT MY WILDEST FANTASIES, NOT ACTUAL WORKING CODE. -Also, it currently only works with pango, until I fix the basic text again. +To build with or without pango: Follow instructions in config.mk and defs.h. + +Note: The basic (non-pango) text doesn't work properly on my i386 machine +because it's a bit of a hack. To test: diff --git a/color.c b/color.c @@ -1,5 +1,6 @@ #include <X11/Xlib.h> #include <X11/Xutil.h> +#include "defs.h" #include "util.h" #include "color.h" diff --git a/config.mk b/config.mk @@ -1,8 +1,14 @@ VERSION = -999 -CFLAGS = -g -std=c99 -w -fcommon -Wall -Werror -Wextra `pkg-config --cflags x11 fontconfig pangoxft` -pedantic -LDFLAGS = -lm `pkg-config --libs x11 fontconfig pangoxft` -OBJ = text_pango.o +CFLAGS = -g -std=c99 -w -fcommon -Wall -Werror -Wextra `pkg-config --cflags x11 fontconfig` -pedantic +LDFLAGS = -lm `pkg-config --libs x11 fontconfig` +# Comment when enabling pango rendering: +OBJ = stb_truetype.o text_stb.o + +# Uncomment to enable pango rendering: +#OBJ = text_pango.o +#CFLAGS += `pkg-config --cflags pangoxft` +#LDFLAGS += `pkg-config --libs pangoxft` # OpenBSD COMPATOBJ = diff --git a/defs.h b/defs.h @@ -1,2 +1,9 @@ +/* + Uncomment USE_PANGO and USE_XFT and comment USE_STB to enable pango, + and vice versa to enable basic rendering. +*/ +/* #define USE_PANGO #define USE_XFT +*/ +#define USE_STB diff --git a/draw.c b/draw.c @@ -28,10 +28,6 @@ #include <X11/Xlib.h> #include <X11/Xutil.h> #include "util.h" -#include "khash.h" -#include "stb_truetype.h" -#include <fontconfig/fontconfig.h> -#include "text_common.h" #include "ltk.h" #include "draw.h" diff --git a/grid.c b/grid.c @@ -31,7 +31,6 @@ #include <X11/Xutil.h> #include "util.h" #include "khash.h" -#include "text_common.h" #include "ltk.h" #include "grid.h" diff --git a/ltk.h b/ltk.h @@ -26,6 +26,8 @@ /* Requires the following includes: <X11/Xlib.h>, <X11/Xutil.h> */ +#include "khash.h" + typedef struct { int x; int y; @@ -128,8 +130,6 @@ typedef struct ltk_window { khash_t(widget) *widget_hash; } ltk_window; -char *ltk_read_file(const char *path, unsigned long *len); - void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect); void ltk_fatal(const char *msg); diff --git a/socket_format.txt b/socket_format.txt @@ -0,0 +1,25 @@ +Note: This is not implemented yet; it is just here to collect +my thoughts while I keep working. + +<widget type> <widget id> <command> <args> +> grid grd1 create 2 2 + +If the command takes a string, the string may contain newlines: +> button btn1 create "I'm a +> button!" + +The command line is read until the first newline that is not +within a string. + +Double quotes must be escaped in strings, like so: +> button btn1 create "Bla\"bla" + +When the server sends a reply, the format is the same, but +there are some special cases, such as "get-text". When the +client asks to get the text for a widget, only the text is +sent back, but still inside double quotes, with double quotes +belonging to the text escaped. + +Essentially, the individual messages are separated by line +breaks (\n), but line breaks within strings don't break the +message. diff --git a/text.h b/text.h @@ -4,13 +4,17 @@ #include "defs.h" #include "color.h" +typedef struct LtkTextLine LtkTextLine; + +void ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap cm); +void ltk_cleanup_text(void); +LtkTextLine *ltk_text_line_create(Window window, uint16_t font_size, char *text, int width); +void ltk_text_line_render(LtkTextLine *tl, LtkColor *bg, LtkColor *fg); +void ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y); +void ltk_text_line_set_width(LtkTextLine *tl, int width); +void ltk_text_line_get_size(LtkTextLine *tl, int *w, int *h); +void ltk_text_line_destroy(LtkTextLine *tl); + #ifdef USE_PANGO #include <pango/pangoxft.h> - #include "text_pango.h" -#endif - -/* Basic */ -#ifdef USE_BASIC_TEXT - #include "text_common.h" - #include "text_line.h" #endif diff --git a/text_common.c b/text_common.c @@ -1,452 +0,0 @@ -/* - * This file is part of the Lumidify ToolKit (LTK) - * Copyright (c) 2017, 2018, 2020 lumidify <nobody@lumidify.org> - * - * 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. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <stdint.h> -#include <limits.h> -#include <X11/Xlib.h> -#include <X11/Xutil.h> -#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */ -#include "khash.h" -#include <fontconfig/fontconfig.h> -#include "text_common.h" -#include "ltk.h" - -typedef struct ltk_font { - stbtt_fontinfo info; - char *path; - int index; /* index in font file */ - uint16_t id; - unsigned int refs; -} ltk_font; - -/* Hash definitions */ -/* glyph id -> glyph info struct */ -KHASH_MAP_INIT_INT(glyphinfo, ltk_glyph_info*) -/* font path, size -> glyph cache hash */ -KHASH_MAP_INIT_INT(glyphcache, khash_t(glyphinfo)*) - -static struct { - khash_t(glyphcache) *glyph_cache; - ltk_font **fonts; - int num_fonts; - int fonts_bufsize; - FcPattern *fcpattern; - ltk_font *default_font; - uint16_t font_id_cur; -} tm = {NULL, NULL, 0, 0, NULL, NULL, 1}; - -static const char *default_font; - -static void err(const char *msg); -static char *read_file(const char *path, unsigned long *len); -static ltk_font *ltk_get_font(char *path, int index); -static void ltk_init_text(void); -static ltk_glyph_info *ltk_create_glyph_info(ltk_font *font, int id, - float scale); -static voidltk_destroy_glyph_info(ltk_glyph_info *gi); -static ltk_glyph_info *ltk_get_glyph_info(ltk_font *font, int id, - float scale, khash_t(glyphinfo) *cache); -static khash_t(glyphinfo) *ltk_get_glyph_cache(uint16_t font_id, - uint16_t font_size); -static khint_t ltk_create_glyph_cache(uint16_t font_id, uint16_t font_size); -static void ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache); -static void ltk_load_default_font(void); -static ltk_font *ltk_create_font(char *path, uint16_t id, int index); -static void ltk_destroy_font(ltk_font *font); -static ltk_font *ltk_load_font(char *path, int index); -static ltk_font *ltk_get_font(char *path, int index); - -static void -err(const char *msg) { - perror(msg); - exit(1); -} - -static char * -read_file(const char *path, unsigned long *len) { - FILE *f; - char *file_contents; - f = fopen(path, "rb"); - fseek(f, 0, SEEK_END); - *len = ftell(f); - fseek(f, 0, SEEK_SET); - file_contents = malloc(*len + 1); - fread(file_contents, 1, *len, f); - file_contents[*len] = '\0'; - fclose(f); - - return file_contents; -} - -/* These unicode routines are taken from - * https://github.com/JeffBezanson/cutef8 */ - -/* is c the start of a utf8 sequence? */ -#define isutf(c) (((c)&0xC0)!=0x80) - -static const uint32_t offsetsFromUTF8[6] = { - 0x00000000UL, 0x00003080UL, 0x000E2080UL, - 0x03C82080UL, 0xFA082080UL, 0x82082080UL -}; - -/* next character without NUL character terminator */ -uint32_t u8_nextmemchar(const char *s, size_t *i) { - uint32_t ch = 0; - size_t sz = 0; - do { - ch <<= 6; - ch += (unsigned char)s[(*i)++]; - sz++; - } while (!isutf(s[*i])); - ch -= offsetsFromUTF8[sz-1]; - - return ch; -} - -/* number of characters in NUL-terminated string */ -size_t u8_strlen(const char *s) { - size_t count = 0; - size_t i = 0, lasti; - - while (1) { - lasti = i; - while (s[i] > 0) - i++; - count += (i-lasti); - if (s[i++]==0) break; - (void)(isutf(s[++i]) || isutf(s[++i]) || ++i); - count++; - } - return count; -} - -size_t u8_wc_toutf8(char *dest, uint32_t ch) { - if (ch < 0x80) { - dest[0] = (char)ch; - return 1; - } - if (ch < 0x800) { - dest[0] = (ch>>6) | 0xC0; - dest[1] = (ch & 0x3F) | 0x80; - return 2; - } - if (ch < 0x10000) { - dest[0] = (ch>>12) | 0xE0; - dest[1] = ((ch>>6) & 0x3F) | 0x80; - dest[2] = (ch & 0x3F) | 0x80; - return 3; - } - if (ch < 0x110000) { - dest[0] = (ch>>18) | 0xF0; - dest[1] = ((ch>>12) & 0x3F) | 0x80; - dest[2] = ((ch>>6) & 0x3F) | 0x80; - dest[3] = (ch & 0x3F) | 0x80; - return 4; - } - return 0; -} - -void -ltk_init_default_font(const char *font_name) { - default_font = strdup(font_name); - if (!default_font) err("ltk_init_default_font"); -} - -static void -ltk_init_text(void) { - tm.fonts_bufsize = 1; - tm.glyph_cache = kh_init(glyphcache); - tm.fonts = malloc(sizeof(ltk_font *)); - if (!tm.fonts) err("ltk_init_text"); - ltk_load_default_font(); -} - -void -ltk_cleanup_text(void) { - if (default_font) free(default_font); - for (int i = 0; i < tm.num_fonts; i++) { - ltk_destroy_font(&tm.fonts[i]); - } - if (!tm.glyph_cache) return; - for (int k = kh_begin(tm.glyph_cache); k != kh_end(tm.glyph_cache); k++) { - if (kh_exist(tm.glyph_cache, k)) { - ltk_destroy_glyph_cache(kh_value(tm.glyph_cache, k)); - } - } - kh_destroy(glyphcache, tm.glyph_cache); -} - -static ltk_glyph_info * -ltk_create_glyph_info(ltk_font *font, int id, float scale) { - ltk_glyph_info *glyph = malloc(sizeof(ltk_glyph_info)); - if (!glyph) err("ltk_create_glyph_info"); - - glyph->id = id; - glyph->refs = 0; - glyph->alphamap = stbtt_GetGlyphBitmap( - &font->info, scale, scale, id, &glyph->w, - &glyph->h, &glyph->xoff, &glyph->yoff - ); - - return glyph; -} - -static void -ltk_destroy_glyph_info(ltk_glyph_info *gi) { - free(gi->alphamap); - free(gi); -} - -static ltk_glyph_info * -ltk_get_glyph_info(ltk_font *font, int id, float scale, khash_t(glyphinfo) *cache) { - int ret; - khint_t k; - ltk_glyph_info *glyph; - k = kh_get(glyphinfo, cache, id); - if (k == kh_end(cache)) { - glyph = ltk_create_glyph_info(font, id, scale); - /* FIXME: error checking with ret */ - k = kh_put(glyphinfo, cache, id, &ret); - kh_value(cache, k) = glyph; - } else { - glyph = kh_value(cache, k); - } - - return glyph; -} - -static khash_t(glyphinfo) * -ltk_get_glyph_cache(uint16_t font_id, uint16_t font_size) { - if (!tm.glyph_cache) ltk_init_text(); - khint_t k; - uint32_t attr = (uint32_t)font_id << 16 + font_size; - k = kh_get(glyphcache, tm.glyph_cache, attr); - if (k == kh_end(tm.glyph_cache)) { - k = ltk_create_glyph_cache(font_id, font_size); - } - return kh_value(tm.glyph_cache, k); -} - -static khint_t -ltk_create_glyph_cache(uint16_t font_id, uint16_t font_size) { - if (!tm.glyph_cache) ltk_init_text(); - khash_t(glyphinfo) *cache = kh_init(glyphinfo); - int ret; - khint_t k; - /* I guess I can just ignore ret for now */ - k = kh_put(glyphcache, tm.glyph_cache, font_id << 16 + font_size, &ret); - kh_value(tm.glyph_cache, k) = cache; - - return k; -} - -static void -ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache) { - int k; - for (k = kh_begin(cache); k != kh_end(cache); k++) { - if (kh_exist(cache, k)) { - ltk_destroy_glyph_info(kh_value(cache, k)); - } - } - kh_destroy(glyphinfo, cache); -} - -static void -ltk_load_default_font(void) { - FcPattern *match; - FcResult result; - char *file; - int index; - uint16_t font; - - if (default_font) - tm.fcpattern = FcNameParse(default_font); - else - tm.fcpattern = FcPatternCreate(); - FcPatternAddString(tm.fcpattern, FC_FONTFORMAT, "truetype"); - FcConfigSubstitute(NULL, tm.fcpattern, FcMatchPattern); - FcDefaultSubstitute(tm.fcpattern); - match = FcFontMatch(NULL, tm.fcpattern, &result); - - FcPatternGetString(match, FC_FILE, 0, (FcChar8 **) &file); - FcPatternGetInteger(match, FC_INDEX, 0, &index); - - tm.default_font = ltk_get_font(file, index); - - FcPatternDestroy(match); -} - -static ltk_font * -ltk_create_font(char *path, uint16_t id, int index) { - long len; - ltk_font *font = malloc(sizeof(ltk_font)); - if (!font) err("ltk_create_font"); - char *contents = read_file(path, &len); - /* FIXME: error checking */ - int offset = stbtt_GetFontOffsetForIndex(contents, index); - if (!stbtt_InitFont(&font->info, contents, offset)) { - (void)fprintf(stderr, "Failed to load font %s\n", path); - exit(1); - } - font->id = id; - font->refs = 0; - font->index = index; - font->path = strdup(path); - if (!font->path) err("ltk_create_font"); - - return font; -} - -static void -ltk_destroy_font(ltk_font *font) { - free(font->info.data); - free(font); -} - -static ltk_font * -ltk_load_font(char *path, int index) { - ltk_font *font = ltk_create_font(path, tm.font_id_cur++, index); - if (tm.num_fonts == tm.fonts_bufsize) { - ltk_font *new = realloc(tm.fonts, tm.fonts_bufsize * 2 * sizeof(ltk_font *)); - if (!new) err("ltk_load_font"); - tm.fonts = new; - tm.fonts_bufsize *= 2; - } - tm.fonts[tm.num_fonts] = font; - tm.num_fonts++; - return font; -} - -static ltk_font * -ltk_get_font(char *path, int index) { - ltk_font *font = NULL; - for (int i = 0; i < tm.num_fonts; i++) { - if (tm.fonts[i]->index == index && - strcmp(tm.fonts[i]->path, path) == 0) { - font = &tm.fonts[i]; - break; - } - } - if (!font) - font = ltk_load_font(path, index); - return font; -} - -void -ltk_text_to_glyphs(ltk_glyph *glyphs, int num_glyphs, char *text, uint16_t font_size, - int *x_min, int *y_min, int *x_max, int *y_max) { - uint32_t c1, c2 = 0; - int gid; - int index; - char *file; - size_t inc = 0; - int x = 0, y, kern_advance, ax; - int x1_abs, x2_abs; - float scale; - int ascent, descent, line_gap; - *x_min = INT_MAX, *x_max = INT_MIN, *y_min = INT_MAX, *y_max = INT_MIN; - ltk_glyph_info *ginfo; - if (!tm.default_font) - ltk_init_text(); - - ltk_font *font = tm.default_font; - khash_t(glyphinfo) *glyph_cache = ltk_get_glyph_cache(font->id, font_size); - - scale = stbtt_ScaleForPixelHeight(&font->info, font_size); - stbtt_GetFontVMetrics(&font->info, &ascent, &descent, &line_gap); - ascent *= scale; - descent *= scale; - - c1 = u8_nextmemchar(text, &inc); - for (int i = 0; i < num_glyphs; i++) { - gid = stbtt_FindGlyphIndex(&font->info, c1); - if (!gid) { - /* Question: Why does this not work with FcPatternDuplicate? */ - FcPattern *pat = FcPatternCreate(); - FcPattern *match; - FcResult result; - FcPatternAddBool(pat, FC_SCALABLE, 1); - FcConfigSubstitute(NULL, pat, FcMatchPattern); - FcDefaultSubstitute(pat); - FcCharSet *cs = FcCharSetCreate(); - FcCharSetAddChar(cs, c1); - FcPatternAddCharSet(pat, FC_CHARSET, cs); - match = FcFontMatch(NULL, pat, &result); - FcPatternGetString(match, FC_FILE, 0, &file); - FcPatternGetInteger(match, FC_INDEX, 0, &index); - font = ltk_get_font(file, index); - glyph_cache = ltk_get_glyph_cache(font->id, font_size); - FcPatternDestroy(match); - FcPatternDestroy(pat); - gid = stbtt_FindGlyphIndex(&font->info, c1); - scale = stbtt_ScaleForPixelHeight(&font->info, font_size); - stbtt_GetFontVMetrics(&font->info, &ascent, &descent, &line_gap); - ascent *= scale; - descent *= scale; - } - ginfo = ltk_get_glyph_info(font, gid, scale, glyph_cache); - ginfo->refs++; - y = ascent + ginfo->yoff; - x1_abs = x + ginfo->xoff; - - glyphs[i].x = x1_abs; - glyphs[i].y = y; - - stbtt_GetGlyphHMetrics(&font->info, gid, &ax, 0); - x += (int) (ax * scale); - x2_abs = x; - - glyphs[i].info = ginfo; - if (x1_abs < *x_min) *x_min = x1_abs; - if (y < *y_min) *y_min = y; - if (x2_abs > *x_max) *x_max = x2_abs; - if (y + ginfo->h > *y_max) *y_max = y + ginfo->h; - - if (i != num_glyphs - 1) { - c2 = u8_nextmemchar(text, &inc); - kern_advance = stbtt_GetCodepointKernAdvance(&font->info, c1, c2); - x += (int) (kern_advance * scale); - } - c1 = c2; - } -} - -/* -void -ltk_unref_glyph(ltk_glyph *glyph, khash_t(glyphinfo) *cache) { - int k; - if (--glyph->info->refs < 1) { - k = kh_get(glyphinfo, cache, glyph->info->id); - kh_del(glyphinfo, cache, k); - ltk_destroy_glyph_info(glyph->info); - } -} - -void -ltk_unref_glyphs(ltk_glyph *glyphs, int num_glyphs) { - for (int i = 0; i < num_glyphs; i++) - ltk_unref_glyph(&glyphs[i]); -} -*/ diff --git a/text_common.h b/text_common.h @@ -1,66 +0,0 @@ -/* - * This file is part of the Lumidify ToolKit (LTK) - * Copyright (c) 2017, 2018, 2020 lumidify <nobody@lumidify.org> - * - * 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. - */ - -#ifndef _TEXT_COMMON_H_ -#define _TEXT_COMMON_H_ - -/* -Requires: <stdint.h> -*/ - -typedef struct ltk_font ltk_font; - -/* Contains general info on glyphs that doesn't change regardless of the context */ -typedef struct { - int id; - unsigned char *alphamap; - int w; - int h; - int xoff; /* x offset from origin to top left corner of glyph */ - int yoff; /* y offset from origin to top left corner of glyph */ - unsigned int refs; - /* FIXME: does refs need to be long? It could cause problems if a - program tries to cache/"keep alive" a lot of pages of text. */ -} ltk_glyph_info; - -/* Contains glyph info specific to one run of text */ -typedef struct { - ltk_glyph_info *info; - int x; - int y; -} ltk_glyph; - -uint32_t u8_nextmemchar(const char *s, size_t *i); - -size_t u8_strlen(const char *s); - -size_t u8_wc_toutf8(char *dest, uint32_t ch); - -void ltk_init_default_font(const char *font_name); - -void ltk_cleanup_text(void); - -void ltk_text_to_glyphs(ltk_glyph *glyphs, int num_glyphs, char *text, - uint16_t font_size, int *x_min, int *y_min, int *x_max, int *y_max); - -#endif /* _TEXT_COMMON_H_ */ diff --git a/text_line.c b/text_line.c @@ -28,20 +28,24 @@ #include <X11/Xutil.h> #include "text_common.h" #include "text_line.h" +#include "util.h" + +void ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap cm); +void ltk_cleanup_text(void); + +LtkTextLine *ltk_text_line_create(Window window, uint16_t font_size, char *text, int width); +void ltk_text_line_render(LtkTextLine *tl, LtkColor *bg, LtkColor *fg); +void ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y); +void ltk_text_line_set_width(LtkTextLine *tl, int width); +void ltk_text_line_get_size(LtkTextLine *tl, int *w, int *h); +void ltk_text_line_destroy(LtkTextLine *tl); -static void err(const char *msg); static void ltk_text_line_create_glyphs(struct ltk_text_line *tl); static void ltk_text_line_draw_glyph(ltk_glyph *glyph, int xoff, int yoff, XImage *img, XColor fg); static XImage *ltk_create_ximage(Display *dpy, int w, int h, int depth, XColor bg); -static void -err(const char *msg) { - perror(msg); - exit(1); -} - static XImage * ltk_create_ximage(Display *dpy, int w, int h, int depth, XColor bg) { XImage *img = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, NULL, w, h, 32, 0); @@ -107,7 +111,7 @@ ltk_text_line_render( } static void -ltk_text_line_create_glyphs(struct ltk_text_line *tl) { +ltk_text_line_create_glyphs(LtkTextLine *tl) { int x_min, x_max, y_min, y_max; ltk_text_to_glyphs(tl->glyphs, tl->glyph_len, tl->text, tl->font_size, &x_min, &y_min, &x_max, &y_max); @@ -118,10 +122,10 @@ ltk_text_line_create_glyphs(struct ltk_text_line *tl) { tl->h = y_max - y_min; } -struct ltk_text_line * -ltk_text_line_create(uint16_t font_size, char *text) { - struct ltk_text_line *line = malloc(sizeof(struct ltk_text_line)); - if (!line) err("ltk_text_line_create"); +LtkTextLine * +ltk_text_line_create(uint16_t font_size, char *text, int width) { + LtkTextLine *line = malloc(sizeof(LtkTextLine)); + if (!line) ltk_err("ltk_text_line_create (basic)"); line->text = text; line->glyph_len = u8_strlen(text); line->glyphs = malloc(line->glyph_len * sizeof(ltk_glyph)); @@ -131,7 +135,7 @@ ltk_text_line_create(uint16_t font_size, char *text) { } void -ltk_text_line_destroy(struct ltk_text_line *tl) { +ltk_text_line_destroy(LtkTextLine *tl) { free(tl->text); /* FIXME: Reference count glyph infos */ free(tl->glyphs); diff --git a/text_line.h b/text_line.h @@ -29,7 +29,7 @@ Requires the following includes: <X11/Xlib.h>, <X11/Xutil.h>, <stdint.h>, "text_common.h", */ -struct ltk_text_line { +typedef struct { char *text; ltk_glyph *glyphs; size_t glyph_len; @@ -38,11 +38,15 @@ struct ltk_text_line { int h; int x_min; int y_min; -}; +} LtkTextLine; -XImage *ltk_text_line_render(struct ltk_text_line *tl, Display *dpy, - Window window, GC gc, Colormap colormap, XColor fg, XColor bg); -struct ltk_text_line *ltk_text_line_create(uint16_t font_size, char *text); -void ltk_text_line_destroy(struct ltk_text_line *tl); +void ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap cm); +void ltk_cleanup_text(void); +LtkTextLine *ltk_text_line_create(Window window, uint16_t font_size, char *text, int width); +void ltk_text_line_render(LtkTextLine *tl, LtkColor *bg, LtkColor *fg); +void ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y); +void ltk_text_line_set_width(LtkTextLine *tl, int width); +void ltk_text_line_get_size(LtkTextLine *tl, int *w, int *h); +void ltk_text_line_destroy(LtkTextLine *tl); #endif /* _TEXT_LINE_H_ */ diff --git a/text_pango.c b/text_pango.c @@ -6,9 +6,19 @@ #include <X11/Xutil.h> #include <X11/Xos.h> #include <pango/pangoxft.h> -#include "color.h" -#include "text_pango.h" #include "util.h" +#include "text.h" + +struct LtkTextLine { + char *text; + uint16_t font_size; + int w; + int h; + Window window; + PangoLayout *layout; + XftDraw *draw; + Pixmap pixmap; +}; struct { PangoFontMap *fontmap; @@ -36,25 +46,31 @@ ltk_cleanup_text(void) { /* FIXME: destroy fontmap and context */ } +void +ltk_text_line_set_width(LtkTextLine *tl, int width) { + /* TODO: Implement! */ +} + LtkTextLine * ltk_text_line_create(Window window, uint16_t font_size, char *text, int width) { if (!tm.context) ltk_err("ltk_text_line_create (pango): text not initialized yet"); - /* FIXME: respect font size */ - /* - PangoFontDescription *desc = pango_font_description_from_string("Sans Bold 27"); - pango_layout_set_font_description(layout, desc); - pango_font_description_free(desc); - */ LtkTextLine *line = malloc(sizeof(LtkTextLine)); if (!line) ltk_err("ltk_text_line_create (pango)"); line->text = text; line->font_size = font_size; line->layout = pango_layout_new(tm.context); + if (width > 0) { pango_layout_set_width(line->layout, width * PANGO_SCALE); pango_layout_set_wrap(line->layout, PANGO_WRAP_WORD_CHAR); } + + PangoFontDescription *desc = pango_font_description_from_string(tm.default_font); + pango_font_description_set_size(desc, font_size * PANGO_SCALE); + pango_layout_set_font_description(line->layout, desc); + pango_font_description_free(desc); + pango_layout_set_text(line->layout, text, -1); pango_layout_get_size(line->layout, &line->w, &line->h); line->w /= PANGO_SCALE; diff --git a/text_pango.h b/text_pango.h @@ -1,51 +0,0 @@ -/* - * This file is part of the Lumidify ToolKit (LTK) - * Copyright (c) 2020 lumidify <nobody@lumidify.org> - * - * 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. - */ - -#ifndef _TEXT_PANGO_H_ -#define _TEXT_PANGO_H_ - -/* -Requires the following includes: -<X11/Xlib.h>, <X11/Xutil.h>, <stdint.h>, <pango/pangoxft.h> -*/ - -typedef struct { - char *text; - uint16_t font_size; - int w; - int h; - Window window; - PangoLayout *layout; - XftDraw *draw; - Pixmap pixmap; -} LtkTextLine; - -void ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap cm); -void ltk_cleanup_text(void); -LtkTextLine *ltk_text_line_create(Window window, uint16_t font_size, char *text, int width); -void ltk_text_line_render(LtkTextLine *tl, LtkColor *bg, LtkColor *fg); -void ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y); -void ltk_text_line_get_size(LtkTextLine *tl, int *w, int *h); -void ltk_text_line_destroy(LtkTextLine *tl); - -#endif /* _TEXT_PANGO_H_ */ diff --git a/text_stb.c b/text_stb.c @@ -0,0 +1,579 @@ +/* + * This file is part of the Lumidify ToolKit (LTK) + * Copyright (c) 2017, 2018, 2020 lumidify <nobody@lumidify.org> + * + * 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <limits.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include "stb_truetype.h" /* http://nothings.org/stb/stb_truetype.h */ +#include "khash.h" +#include <fontconfig/fontconfig.h> +#include "util.h" +#include "text.h" +#include "ltk.h" + +typedef struct { + stbtt_fontinfo info; + char *path; + int index; /* index in font file */ + uint16_t id; + unsigned int refs; +} LtkFont; + +/* Contains general info on glyphs that doesn't change regardless of the context */ +typedef struct { + int id; + unsigned char *alphamap; + int w; + int h; + int xoff; /* x offset from origin to top left corner of glyph */ + int yoff; /* y offset from origin to top left corner of glyph */ + unsigned int refs; + /* FIXME: does refs need to be long? It could cause problems if a + program tries to cache/"keep alive" a lot of pages of text. */ +} LtkGlyphInfo; + +/* Contains glyph info specific to one run of text */ +typedef struct { + LtkGlyphInfo *info; + int x; + int y; +} LtkGlyph; + +struct LtkTextLine { + Window window; + XImage *img; + char *text; + LtkGlyph *glyphs; + size_t glyph_len; + uint16_t font_size; + int w; + int h; + int x_min; + int y_min; +}; + +/* Hash definitions */ +/* glyph id -> glyph info struct */ +KHASH_MAP_INIT_INT(glyphinfo, LtkGlyphInfo*) +/* font path, size -> glyph cache hash */ +KHASH_MAP_INIT_INT(glyphcache, khash_t(glyphinfo)*) + +static struct { + khash_t(glyphcache) *glyph_cache; + LtkFont **fonts; + int num_fonts; + int fonts_bufsize; + FcPattern *fcpattern; + LtkFont *default_font; + uint16_t font_id_cur; + Display *dpy; + int screen; + Colormap cm; +} tm = {NULL, NULL, 0, 0, NULL, NULL, 1, NULL, 0, 0}; + + +static LtkFont *ltk_get_font(char *path, int index); +static LtkGlyphInfo *ltk_create_glyph_info(LtkFont *font, int id, + float scale); +static void ltk_destroy_glyph_info(LtkGlyphInfo *gi); +static LtkGlyphInfo *ltk_get_glyph_info(LtkFont *font, int id, + float scale, khash_t(glyphinfo) *cache); +static khash_t(glyphinfo) *ltk_get_glyph_cache(uint16_t font_id, + uint16_t font_size); +static khint_t ltk_create_glyph_cache(uint16_t font_id, uint16_t font_size); +static void ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache); +static void ltk_load_default_font(char *name); +static LtkFont *ltk_create_font(char *path, uint16_t id, int index); +static void ltk_destroy_font(LtkFont *font); +static LtkFont *ltk_load_font(char *path, int index); +static LtkFont *ltk_get_font(char *path, int index); +static void ltk_text_to_glyphs(LtkGlyph *glyphs, int num_glyphs, char *text, + uint16_t font_size, int *x_min, int *y_min, int *x_max, int *y_max); +static void ltk_text_line_create_glyphs(LtkTextLine *tl); +static void ltk_text_line_draw_glyph(LtkGlyph *glyph, int xoff, int yoff, + XImage *img, XColor fg); +static XImage *ltk_create_ximage(int w, int h, int depth, XColor bg); + + +/* These unicode routines are taken from + * https://github.com/JeffBezanson/cutef8 */ + +static size_t u8_wc_toutf8(char *dest, uint32_t ch); +static size_t u8_strlen(const char *s); +static uint32_t u8_nextmemchar(const char *s, size_t *i); + +/* is c the start of a utf8 sequence? */ +#define isutf(c) (((c)&0xC0)!=0x80) + +static const uint32_t offsetsFromUTF8[6] = { + 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL +}; + +/* next character without NUL character terminator */ +static uint32_t u8_nextmemchar(const char *s, size_t *i) { + uint32_t ch = 0; + size_t sz = 0; + do { + ch <<= 6; + ch += (unsigned char)s[(*i)++]; + sz++; + } while (!isutf(s[*i])); + ch -= offsetsFromUTF8[sz-1]; + + return ch; +} + +/* number of characters in NUL-terminated string */ +static size_t u8_strlen(const char *s) { + size_t count = 0; + size_t i = 0, lasti; + + while (1) { + lasti = i; + while (s[i] > 0) + i++; + count += (i-lasti); + if (s[i++]==0) break; + (void)(isutf(s[++i]) || isutf(s[++i]) || ++i); + count++; + } + return count; +} + +static size_t u8_wc_toutf8(char *dest, uint32_t ch) { + if (ch < 0x80) { + dest[0] = (char)ch; + return 1; + } + if (ch < 0x800) { + dest[0] = (ch>>6) | 0xC0; + dest[1] = (ch & 0x3F) | 0x80; + return 2; + } + if (ch < 0x10000) { + dest[0] = (ch>>12) | 0xE0; + dest[1] = ((ch>>6) & 0x3F) | 0x80; + dest[2] = (ch & 0x3F) | 0x80; + return 3; + } + if (ch < 0x110000) { + dest[0] = (ch>>18) | 0xF0; + dest[1] = ((ch>>12) & 0x3F) | 0x80; + dest[2] = ((ch>>6) & 0x3F) | 0x80; + dest[3] = (ch & 0x3F) | 0x80; + return 4; + } + return 0; +} + +void +ltk_init_text(const char *default_font, Display *dpy, int screen, Colormap cm) { + tm.fonts_bufsize = 1; + tm.glyph_cache = kh_init(glyphcache); + tm.fonts = malloc(sizeof(LtkFont *)); + if (!tm.fonts) ltk_err("ltk_init_text"); + ltk_load_default_font(default_font); + tm.dpy = dpy; + tm.screen = screen; + tm.cm = cm; +} + +void +ltk_cleanup_text(void) { + for (int i = 0; i < tm.num_fonts; i++) { + ltk_destroy_font(&tm.fonts[i]); + } + if (!tm.glyph_cache) return; + for (int k = kh_begin(tm.glyph_cache); k != kh_end(tm.glyph_cache); k++) { + if (kh_exist(tm.glyph_cache, k)) { + ltk_destroy_glyph_cache(kh_value(tm.glyph_cache, k)); + } + } + kh_destroy(glyphcache, tm.glyph_cache); +} + +static LtkGlyphInfo * +ltk_create_glyph_info(LtkFont *font, int id, float scale) { + LtkGlyphInfo *glyph = malloc(sizeof(LtkGlyphInfo)); + if (!glyph) ltk_err("ltk_create_glyph_info"); + + glyph->id = id; + glyph->refs = 0; + glyph->alphamap = stbtt_GetGlyphBitmap( + &font->info, scale, scale, id, &glyph->w, + &glyph->h, &glyph->xoff, &glyph->yoff + ); + + return glyph; +} + +static void +ltk_destroy_glyph_info(LtkGlyphInfo *gi) { + free(gi->alphamap); + free(gi); +} + +static LtkGlyphInfo * +ltk_get_glyph_info(LtkFont *font, int id, float scale, khash_t(glyphinfo) *cache) { + int ret; + khint_t k; + LtkGlyphInfo *glyph; + k = kh_get(glyphinfo, cache, id); + if (k == kh_end(cache)) { + glyph = ltk_create_glyph_info(font, id, scale); + /* FIXME: error checking with ret */ + k = kh_put(glyphinfo, cache, id, &ret); + kh_value(cache, k) = glyph; + } else { + glyph = kh_value(cache, k); + } + + return glyph; +} + +static khash_t(glyphinfo) * +ltk_get_glyph_cache(uint16_t font_id, uint16_t font_size) { + khint_t k; + uint32_t attr = (uint32_t)font_id << 16 + font_size; + k = kh_get(glyphcache, tm.glyph_cache, attr); + if (k == kh_end(tm.glyph_cache)) { + k = ltk_create_glyph_cache(font_id, font_size); + } + return kh_value(tm.glyph_cache, k); +} + +static khint_t +ltk_create_glyph_cache(uint16_t font_id, uint16_t font_size) { + khash_t(glyphinfo) *cache = kh_init(glyphinfo); + int ret; + khint_t k; + /* I guess I can just ignore ret for now */ + k = kh_put(glyphcache, tm.glyph_cache, font_id << 16 + font_size, &ret); + kh_value(tm.glyph_cache, k) = cache; + + return k; +} + +static void +ltk_destroy_glyph_cache(khash_t(glyphinfo) *cache) { + int k; + for (k = kh_begin(cache); k != kh_end(cache); k++) { + if (kh_exist(cache, k)) { + ltk_destroy_glyph_info(kh_value(cache, k)); + } + } + kh_destroy(glyphinfo, cache); +} + +static void +ltk_load_default_font(char *name) { + FcPattern *match; + FcResult result; + char *file; + int index; + uint16_t font; + + tm.fcpattern = FcNameParse(name); + /*tm.fcpattern = FcPatternCreate();*/ + FcPatternAddString(tm.fcpattern, FC_FONTFORMAT, "truetype"); + FcConfigSubstitute(NULL, tm.fcpattern, FcMatchPattern); + FcDefaultSubstitute(tm.fcpattern); + match = FcFontMatch(NULL, tm.fcpattern, &result); + + FcPatternGetString(match, FC_FILE, 0, (FcChar8 **) &file); + FcPatternGetInteger(match, FC_INDEX, 0, &index); + + tm.default_font = ltk_get_font(file, index); + + FcPatternDestroy(match); +} + +static LtkFont * +ltk_create_font(char *path, uint16_t id, int index) { + unsigned long len; + LtkFont *font = malloc(sizeof(LtkFont)); + if (!font) ltk_err("ltk_create_font (stb)"); + char *contents = ltk_read_file(path, &len); + /* FIXME: error checking */ + int offset = stbtt_GetFontOffsetForIndex(contents, index); + if (!stbtt_InitFont(&font->info, contents, offset)) { + (void)fprintf(stderr, "Failed to load font %s\n", path); + exit(1); + } + font->id = id; + font->refs = 0; + font->index = index; + font->path = strdup(path); + if (!font->path) ltk_err("ltk_create_font"); + + return font; +} + +static void +ltk_destroy_font(LtkFont *font) { + /* FIXME: why does this cause error? */ + free(font->info.data); + free(font); +} + +static LtkFont * +ltk_load_font(char *path, int index) { + LtkFont *font = ltk_create_font(path, tm.font_id_cur++, index); + if (tm.num_fonts == tm.fonts_bufsize) { + LtkFont *new = realloc(tm.fonts, tm.fonts_bufsize * 2 * sizeof(LtkFont *)); + if (!new) ltk_err("ltk_load_font"); + tm.fonts = new; + tm.fonts_bufsize *= 2; + } + tm.fonts[tm.num_fonts] = font; + tm.num_fonts++; + return font; +} + +static LtkFont * +ltk_get_font(char *path, int index) { + LtkFont *font = NULL; + for (int i = 0; i < tm.num_fonts; i++) { + if (tm.fonts[i]->index == index && + strcmp(tm.fonts[i]->path, path) == 0) { + font = &tm.fonts[i]; + break; + } + } + if (!font) + font = ltk_load_font(path, index); + return font; +} + +static void +ltk_text_to_glyphs(LtkGlyph *glyphs, int num_glyphs, char *text, uint16_t font_size, + int *x_min, int *y_min, int *x_max, int *y_max) { + uint32_t c1, c2 = 0; + int gid; + int index; + char *file; + size_t inc = 0; + int x = 0, y, kern_advance, ax; + int x1_abs, x2_abs; + float scale; + int ascent, descent, line_gap; + *x_min = INT_MAX, *x_max = INT_MIN, *y_min = INT_MAX, *y_max = INT_MIN; + LtkGlyphInfo *ginfo; + + LtkFont *font = tm.default_font; + khash_t(glyphinfo) *glyph_cache = ltk_get_glyph_cache(font->id, font_size); + + scale = stbtt_ScaleForPixelHeight(&font->info, font_size); + stbtt_GetFontVMetrics(&font->info, &ascent, &descent, &line_gap); + ascent *= scale; + descent *= scale; + + c1 = u8_nextmemchar(text, &inc); + for (int i = 0; i < num_glyphs; i++) { + gid = stbtt_FindGlyphIndex(&font->info, c1); + if (!gid) { + /* Question: Why does this not work with FcPatternDuplicate? */ + FcPattern *pat = FcPatternCreate(); + FcPattern *match; + FcResult result; + FcPatternAddBool(pat, FC_SCALABLE, 1); + FcConfigSubstitute(NULL, pat, FcMatchPattern); + FcDefaultSubstitute(pat); + FcCharSet *cs = FcCharSetCreate(); + FcCharSetAddChar(cs, c1); + FcPatternAddCharSet(pat, FC_CHARSET, cs); + match = FcFontMatch(NULL, pat, &result); + FcPatternGetString(match, FC_FILE, 0, &file); + FcPatternGetInteger(match, FC_INDEX, 0, &index); + font = ltk_get_font(file, index); + glyph_cache = ltk_get_glyph_cache(font->id, font_size); + FcPatternDestroy(match); + FcPatternDestroy(pat); + gid = stbtt_FindGlyphIndex(&font->info, c1); + scale = stbtt_ScaleForPixelHeight(&font->info, font_size); + stbtt_GetFontVMetrics(&font->info, &ascent, &descent, &line_gap); + ascent *= scale; + descent *= scale; + } + ginfo = ltk_get_glyph_info(font, gid, scale, glyph_cache); + ginfo->refs++; + y = ascent + ginfo->yoff; + x1_abs = x + ginfo->xoff; + + glyphs[i].x = x1_abs; + glyphs[i].y = y; + + stbtt_GetGlyphHMetrics(&font->info, gid, &ax, 0); + x += (int) (ax * scale); + x2_abs = x; + + glyphs[i].info = ginfo; + if (x1_abs < *x_min) *x_min = x1_abs; + if (y < *y_min) *y_min = y; + if (x2_abs > *x_max) *x_max = x2_abs; + if (y + ginfo->h > *y_max) *y_max = y + ginfo->h; + + if (i != num_glyphs - 1) { + c2 = u8_nextmemchar(text, &inc); + kern_advance = stbtt_GetCodepointKernAdvance(&font->info, c1, c2); + x += (int) (kern_advance * scale); + } + c1 = c2; + } +} + +/* +void +ltk_unref_glyph(ltk_glyph *glyph, khash_t(glyphinfo) *cache) { + int k; + if (--glyph->info->refs < 1) { + k = kh_get(glyphinfo, cache, glyph->info->id); + kh_del(glyphinfo, cache, k); + ltk_destroy_glyph_info(glyph->info); + } +} + +void +ltk_unref_glyphs(ltk_glyph *glyphs, int num_glyphs) { + for (int i = 0; i < num_glyphs; i++) + ltk_unref_glyph(&glyphs[i]); +} +*/ + +/* FIXME: Error checking that tm has been initialized */ + +static XImage * +ltk_create_ximage(int w, int h, int depth, XColor bg) { + XImage *img = XCreateImage(tm.dpy, CopyFromParent, depth, ZPixmap, 0, NULL, w, h, 32, 0); + img->data = calloc(img->bytes_per_line, img->height); + XInitImage(img); + + int b; + for (int i = 0; i < h; i++) { + b = img->bytes_per_line * i; + for (int j = 0; j < w; j++) { + img->data[b++] = bg.blue / 257; + img->data[b++] = bg.green / 257; + img->data[b++] = bg.red / 257; + b++; + } + } + + return img; +} + +/* based on http://codemadness.org/git/dwm-font/file/drw.c.html#l315 */ +static void +ltk_text_line_draw_glyph(LtkGlyph *glyph, int xoff, int yoff, XImage *img, XColor fg) { + int x = glyph->x + xoff; + int y = glyph->y + yoff; + double a; + int b; + for (int i = 0; i < glyph->info->h; i++) { + for (int j = 0; j < glyph->info->w; j++) { + if (y + i >= img->height || x + j >= img->width || + y + i < 0 || x + i < 0) + continue; + b = (y + i) * img->bytes_per_line + (x + j) * 4; + a = glyph->info->alphamap[i * glyph->info->w + j] / 255.0; + img->data[b] = (fg.blue * a + (1 - a) * (uint16_t)img->data[b] * 257) / 257; + img->data[b + 1] = (fg.green * a + (1 - a) * (uint16_t)img->data[b + 1] * 257) / 257; + img->data[b + 2] = (fg.red * a + (1 - a) * (uint16_t)img->data[b + 2] * 257) / 257; + } + } +} + +void +ltk_text_line_render( + LtkTextLine *tl, + LtkColor *bg, + LtkColor *fg) +{ + LtkGlyph *glyph; + + XWindowAttributes attrs; + XGetWindowAttributes(tm.dpy, tl->window, &attrs); + int depth = attrs.depth; + /* FIXME: pass old image; if it has same dimensions, just clear it */ + if (tl->img) + XDestroyImage(tl->img); + tl->img = ltk_create_ximage(tl->w, tl->h, depth, bg->xcolor); + for (int i = 0; i < tl->glyph_len; i++) { + ltk_text_line_draw_glyph(&tl->glyphs[i], -tl->x_min, -tl->y_min, tl->img, fg->xcolor); + } +} + +/* FIXME: error checking if img is rendered yet, tm initialized, etc. */ +void +ltk_text_line_draw(LtkTextLine *tl, GC gc, int x, int y) { + XPutImage(tm.dpy, tl->window, gc, tl->img, 0, 0, x, y, tl->w, tl->h); +} + +void +ltk_text_line_set_width(LtkTextLine *tl, int width) { + /* FIXME: implement */ +} + +void +ltk_text_line_get_size(LtkTextLine *tl, int *w, int *h) { + *w = tl->w; + *h = tl->h; +} + +static void +ltk_text_line_create_glyphs(LtkTextLine *tl) { + int x_min, x_max, y_min, y_max; + ltk_text_to_glyphs(tl->glyphs, tl->glyph_len, tl->text, tl->font_size, + &x_min, &y_min, &x_max, &y_max); + /* for drawing the glyphs at the right position on the image */ + tl->x_min = x_min; + tl->y_min = y_min; + tl->w = x_max - x_min; + tl->h = y_max - y_min; +} + +LtkTextLine * +ltk_text_line_create(Window window, uint16_t font_size, char *text, int width) { + LtkTextLine *line = malloc(sizeof(LtkTextLine)); + if (!line) ltk_err("ltk_text_line_create (basic)"); + line->window = window; + line->img = NULL; + line->text = text; + line->glyph_len = u8_strlen(text); + line->glyphs = malloc(line->glyph_len * sizeof(LtkGlyph)); + line->font_size = font_size; + ltk_text_line_create_glyphs(line); + return line; +} + +void +ltk_text_line_destroy(LtkTextLine *tl) { + free(tl->text); + /* FIXME: Reference count glyph infos */ + free(tl->glyphs); + free(tl); +} diff --git a/util.c b/util.c @@ -22,6 +22,7 @@ */ #include <stdio.h> +#include <stdlib.h> void ltk_err(const char *msg) { @@ -37,6 +38,7 @@ ltk_read_file(const char *path, unsigned long *len) { fseek(f, 0, SEEK_END); *len = ftell(f); fseek(f, 0, SEEK_SET); + /* FIXME: error checking */ file_contents = malloc(*len + 1); fread(file_contents, 1, *len, f); file_contents[*len] = '\0';