Merge branch 'use-fcft'

This commit is contained in:
Daniel Eklöf 2019-12-01 13:52:30 +01:00
commit 526f65a392
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
18 changed files with 33 additions and 1511 deletions

8
.gitmodules vendored
View file

@ -1,4 +1,6 @@
[submodule "external/tllist"]
path = external/tllist
[submodule "subprojects/tllist"]
path = subprojects/tllist
url = https://codeberg.org/dnkl/tllist.git
branch = master
[submodule "subprojects/fcft"]
path = subprojects/fcft
url = https://codeberg.org/dnkl/fcft.git

View file

@ -13,19 +13,15 @@ pkgver() {
}
build() {
meson --prefix=/usr --buildtype=release -Db_lto=true -Dc_args="-fno-stack-protector" ..
meson --prefix=/usr --buildtype=release -Db_lto=true -Dc_args="-fno-stack-protector -Wno-missing-profile" ..
meson configure -Db_pgo=generate
ninja
ninja test
tmp_file=$(mktemp)
./foot --term=xterm -- sh -c "../scripts/generate-alt-random-writes.py --scroll --scroll-region --colors-regular --colors-bright --colors-rgb ${tmp_file} && cat ${tmp_file}"
rm "${tmp_file}"
# Need to execute *all* binaries... :/
./footclient --version
meson configure -Db_pgo=use
ninja
}

View file

@ -3,8 +3,9 @@
#include <stdint.h>
#include <stdbool.h>
#include <tllist.h>
#include "terminal.h"
#include "tllist.h"
struct config {
char *term;

1
external/tllist vendored

@ -1 +0,0 @@
Subproject commit d61be2b6238617d9bfd80aeb1ba0cef13d3a6aba

809
font.c
View file

@ -1,809 +0,0 @@
#include "font.h"
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <wchar.h>
#include <math.h>
#include <assert.h>
#include <threads.h>
#include <freetype/tttables.h>
#define LOG_MODULE "font"
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "stride.h"
#define min(x, y) ((x) < (y) ? (x) : (y))
static FT_Library ft_lib;
static mtx_t ft_lock;
static const size_t glyph_cache_size = 512;
struct font_cache_entry {
uint64_t hash;
struct font *font;
};
static tll(struct font_cache_entry) font_cache = tll_init();
static void __attribute__((constructor))
init(void)
{
FcInit();
FT_Init_FreeType(&ft_lib);
mtx_init(&ft_lock, mtx_plain);
#if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
{
int raw_version = FcGetVersion();
/* See FC_VERSION in <fontconfig/fontconfig.h> */
const int major = raw_version / 10000; raw_version %= 10000;
const int minor = raw_version / 100; raw_version %= 100;
const int patch = raw_version;
LOG_DBG("fontconfig: %d.%d.%d", major, minor, patch);
}
{
int major, minor, patch;
FT_Library_Version(ft_lib, &major, &minor, &patch);
LOG_DBG("freetype: %d.%d.%d", major, minor, patch);
}
#endif
}
static void __attribute__((destructor))
fini(void)
{
while (tll_length(font_cache) > 0)
font_destroy(tll_pop_front(font_cache).font);
mtx_destroy(&ft_lock);
FT_Done_FreeType(ft_lib);
FcFini();
}
static const char *
ft_error_string(FT_Error err)
{
#undef FTERRORS_H_
#undef __FTERRORS_H__
#define FT_ERRORDEF( e, v, s ) case e: return s;
#define FT_ERROR_START_LIST switch (err) {
#define FT_ERROR_END_LIST }
#include FT_ERRORS_H
return "unknown error";
}
static void
underline_strikeout_metrics(struct font *font)
{
FT_Face ft_face = font->face;
double y_scale = ft_face->size->metrics.y_scale / 65536.;
double height = ft_face->size->metrics.height / 64.;
double descent = ft_face->size->metrics.descender / 64.;
LOG_DBG("ft: y-scale: %f, height: %f, descent: %f",
y_scale, height, descent);
font->underline.position = ft_face->underline_position * y_scale / 64.;
font->underline.thickness = ft_face->underline_thickness * y_scale / 64.;
if (font->underline.position == 0.) {
font->underline.position = descent / 2.;
font->underline.thickness = fabs(descent / 5.);
}
LOG_DBG("underline: pos=%f, thick=%f",
font->underline.position, font->underline.thickness);
TT_OS2 *os2 = FT_Get_Sfnt_Table(ft_face, ft_sfnt_os2);
if (os2 != NULL) {
font->strikeout.position = os2->yStrikeoutPosition * y_scale / 64.;
font->strikeout.thickness = os2->yStrikeoutSize * y_scale / 64.;
}
if (font->strikeout.position == 0.) {
font->strikeout.position = height / 2. + descent;
font->strikeout.thickness = font->underline.thickness;
}
LOG_DBG("strikeout: pos=%f, thick=%f",
font->strikeout.position, font->strikeout.thickness);
}
static bool
from_font_set(FcPattern *pattern, FcFontSet *fonts, int start_idx,
struct font *font, bool is_fallback)
{
memset(font, 0, sizeof(*font));
FcChar8 *face_file = NULL;
FcPattern *final_pattern = NULL;
int font_idx = -1;
for (int i = start_idx; i < fonts->nfont; i++) {
FcPattern *pat = FcFontRenderPrepare(NULL, pattern, fonts->fonts[i]);
assert(pat != NULL);
if (FcPatternGetString(pat, FC_FT_FACE, 0, &face_file) != FcResultMatch) {
if (FcPatternGetString(pat, FC_FILE, 0, &face_file) != FcResultMatch) {
FcPatternDestroy(pat);
continue;
}
}
final_pattern = pat;
font_idx = i;
break;
}
assert(font_idx != -1);
assert(final_pattern != NULL);
double dpi;
if (FcPatternGetDouble(final_pattern, FC_DPI, 0, &dpi) != FcResultMatch)
dpi = 75;
double size;
if (FcPatternGetDouble(final_pattern, FC_SIZE, 0, &size) != FcResultMatch)
LOG_WARN("%s: failed to get size", face_file);
double pixel_size;
if (FcPatternGetDouble(final_pattern, FC_PIXEL_SIZE, 0, &pixel_size) != FcResultMatch) {
LOG_ERR("%s: failed to get pizel size", face_file);
FcPatternDestroy(final_pattern);
return false;
}
mtx_lock(&ft_lock);
FT_Face ft_face;
FT_Error ft_err = FT_New_Face(ft_lib, (const char *)face_file, 0, &ft_face);
mtx_unlock(&ft_lock);
if (ft_err != 0) {
LOG_ERR("%s: failed to create FreeType face", face_file);
FcPatternDestroy(final_pattern);
return false;
}
if ((ft_err = FT_Set_Pixel_Sizes(ft_face, 0, pixel_size)) != 0) {
LOG_WARN("%s: failed to set character size", face_file);
mtx_lock(&ft_lock);
FT_Done_Face(ft_face);
mtx_unlock(&ft_lock);
FcPatternDestroy(final_pattern);
return false;
}
FcBool scalable;
if (FcPatternGetBool(final_pattern, FC_SCALABLE, 0, &scalable) != FcResultMatch)
scalable = FcTrue;
FcBool outline;
if (FcPatternGetBool(final_pattern, FC_OUTLINE, 0, &outline) != FcResultMatch)
outline = FcTrue;
double pixel_fixup = 1.;
if (FcPatternGetDouble(final_pattern, "pixelsizefixupfactor", 0, &pixel_fixup) != FcResultMatch) {
/*
* Force a fixup factor on scalable bitmap fonts (typically
* emoji fonts). The fixup factor is
* requested-pixel-size / actual-pixels-size
*/
if (scalable && !outline) {
double requested_pixel_size;
if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &requested_pixel_size) != FcResultMatch) {
/* User didn't specify ":pixelsize=xy" */
double requested_size;
if (FcPatternGetDouble(pattern, FC_SIZE, 0, &requested_size) != FcResultMatch) {
/* User didn't specify ":size=xy" */
requested_size = size;
}
requested_pixel_size = size * dpi / 72;
}
pixel_fixup = requested_pixel_size / ft_face->size->metrics.y_ppem;
LOG_DBG("estimated pixel fixup factor to %f (from pixel size: %f)",
pixel_fixup, requested_pixel_size);
} else
pixel_fixup = 1.;
}
#if 0
LOG_DBG("FIXED SIZES: %d", ft_face->num_fixed_sizes);
for (int i = 0; i < ft_face->num_fixed_sizes; i++)
LOG_DBG(" #%d: height=%d, y_ppem=%f", i, ft_face->available_sizes[i].height, ft_face->available_sizes[i].y_ppem / 64.);
#endif
FcBool fc_hinting;
if (FcPatternGetBool(final_pattern, FC_HINTING,0, &fc_hinting) != FcResultMatch)
fc_hinting = FcTrue;
FcBool fc_antialias;
if (FcPatternGetBool(final_pattern, FC_ANTIALIAS, 0, &fc_antialias) != FcResultMatch)
fc_antialias = FcTrue;
int fc_hintstyle;
if (FcPatternGetInteger(final_pattern, FC_HINT_STYLE, 0, &fc_hintstyle) != FcResultMatch)
fc_hintstyle = FC_HINT_SLIGHT;
int fc_rgba;
if (FcPatternGetInteger(final_pattern, FC_RGBA, 0, &fc_rgba) != FcResultMatch)
fc_rgba = FC_RGBA_UNKNOWN;
int load_flags = 0;
if (!fc_antialias) {
if (!fc_hinting || fc_hintstyle == FC_HINT_NONE)
load_flags |= FT_LOAD_MONOCHROME | FT_LOAD_NO_HINTING | FT_LOAD_TARGET_NORMAL;
else
load_flags |= FT_LOAD_MONOCHROME | FT_LOAD_TARGET_MONO;
} else {
if (!fc_hinting || fc_hintstyle == FC_HINT_NONE)
load_flags |= FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING | FT_LOAD_TARGET_NORMAL;
else if (fc_hinting && fc_hintstyle == FC_HINT_SLIGHT)
load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_LIGHT;
else if (fc_rgba == FC_RGBA_RGB || fc_rgba == FC_RGBA_BGR)
load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_LCD;
else if (fc_rgba == FC_RGBA_VRGB || fc_rgba == FC_RGBA_VBGR)
load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_LCD_V;
else
load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_NORMAL;
}
FcBool fc_embeddedbitmap;
if (FcPatternGetBool(final_pattern, FC_EMBEDDED_BITMAP, 0, &fc_embeddedbitmap) != FcResultMatch)
fc_embeddedbitmap = FcTrue;
if (!fc_embeddedbitmap)
load_flags |= FT_LOAD_NO_BITMAP;
int render_flags = 0;
if (!fc_antialias)
render_flags |= FT_RENDER_MODE_MONO;
else {
if (fc_rgba == FC_RGBA_RGB || fc_rgba == FC_RGBA_BGR)
render_flags |= FT_RENDER_MODE_LCD;
else if (fc_rgba == FC_RGBA_VRGB || fc_rgba == FC_RGBA_VBGR)
render_flags |= FT_RENDER_MODE_LCD_V;
else
render_flags |= FT_RENDER_MODE_NORMAL;
}
int fc_lcdfilter;
if (FcPatternGetInteger(final_pattern, FC_LCD_FILTER, 0, &fc_lcdfilter) != FcResultMatch)
fc_lcdfilter = FC_LCD_DEFAULT;
switch (fc_lcdfilter) {
case FC_LCD_NONE: font->lcd_filter = FT_LCD_FILTER_NONE; break;
case FC_LCD_DEFAULT: font->lcd_filter = FT_LCD_FILTER_DEFAULT; break;
case FC_LCD_LIGHT: font->lcd_filter = FT_LCD_FILTER_LIGHT; break;
case FC_LCD_LEGACY: font->lcd_filter = FT_LCD_FILTER_LEGACY; break;
}
font->name = strdup((char *)face_file);
FcPatternDestroy(final_pattern);
mtx_init(&font->lock, mtx_plain);
font->face = ft_face;
font->load_flags = load_flags | FT_LOAD_COLOR;
font->render_flags = render_flags;
font->is_fallback = is_fallback;
font->pixel_size_fixup = pixel_fixup;
font->bgr = fc_rgba == FC_RGBA_BGR || fc_rgba == FC_RGBA_VBGR;
font->ref_counter = 1;
font->fc_idx = font_idx;
if (is_fallback) {
font->fc_pattern = NULL;
font->fc_fonts = NULL;
font->fc_loaded_fallbacks = NULL;
font->glyph_cache = NULL;
} else {
font->fc_pattern = !is_fallback ? pattern : NULL;
font->fc_fonts = !is_fallback ? fonts : NULL;
font->fc_loaded_fallbacks = calloc(
fonts->nfont, sizeof(font->fc_loaded_fallbacks[0]));
font->glyph_cache = calloc(glyph_cache_size, sizeof(font->glyph_cache[0]));
}
double max_x_advance = ft_face->size->metrics.max_advance / 64.;
double height= ft_face->size->metrics.height / 64.;
double descent = ft_face->size->metrics.descender / 64.;
double ascent = ft_face->size->metrics.ascender / 64.;
font->height = ceil(height * font->pixel_size_fixup);
font->descent = ceil(-descent * font->pixel_size_fixup);
font->ascent = ceil(ascent * font->pixel_size_fixup);
font->max_x_advance = ceil(max_x_advance * font->pixel_size_fixup);
LOG_DBG("%s: size=%f, pixel-size=%f, dpi=%f, fixup-factor: %f, "
"line-height: %d, ascent: %d, descent: %d, x-advance: %d",
font->name, size, pixel_size, dpi, font->pixel_size_fixup,
font->height, font->ascent, font->descent,
font->max_x_advance);
underline_strikeout_metrics(font);
return true;
}
static struct font *
from_name(const char *name, bool is_fallback)
{
LOG_DBG("instantiating %s", name);
FcPattern *pattern = FcNameParse((const unsigned char *)name);
if (pattern == NULL) {
LOG_ERR("%s: failed to lookup font", name);
return NULL;
}
if (!FcConfigSubstitute(NULL, pattern, FcMatchPattern)) {
LOG_ERR("%s: failed to do config substitution", name);
FcPatternDestroy(pattern);
return NULL;
}
FcDefaultSubstitute(pattern);
FcResult result;
FcFontSet *fonts = FcFontSort(NULL, pattern, FcTrue, NULL, &result);
if (result != FcResultMatch) {
LOG_ERR("%s: failed to match font", name);
FcPatternDestroy(pattern);
return NULL;
}
struct font *font = malloc(sizeof(*font));
if (!from_font_set(pattern, fonts, 0, font, is_fallback)) {
free(font);
FcFontSetDestroy(fonts);
FcPatternDestroy(pattern);
return NULL;
}
if (is_fallback) {
FcFontSetDestroy(fonts);
FcPatternDestroy(pattern);
}
return font;
}
static uint64_t
sdbm_hash(const char *s)
{
uint64_t hash = 0;
for (; *s != '\0'; s++) {
int c = *s;
hash = c + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
static uint64_t
font_hash(font_list_t names, const char *attributes)
{
uint64_t hash = 0;
tll_foreach(names, it)
hash ^= sdbm_hash(it->item);
if (attributes != NULL)
hash ^= sdbm_hash(attributes);
return hash;
}
struct font *
font_from_name(font_list_t names, const char *attributes)
{
if (tll_length(names) == 0)
return false;
uint64_t hash = font_hash(names, attributes);
tll_foreach(font_cache, it) {
if (it->item.hash == hash) {
it->item.font->ref_counter++;
return it->item.font;
}
}
struct font *font = NULL;
bool have_attrs = attributes != NULL && strlen(attributes) > 0;
size_t attr_len = have_attrs ? strlen(attributes) + 1 : 0;
bool first = true;
tll_foreach(names, it) {
const char *base_name = it->item;
char name[strlen(base_name) + attr_len + 1];
strcpy(name, base_name);
if (have_attrs) {
strcat(name, ":");
strcat(name, attributes);
}
if (first) {
first = false;
font = from_name(name, false);
if (font == NULL)
return NULL;
continue;
}
assert(font != NULL);
tll_push_back(
font->fallbacks, ((struct font_fallback){.pattern = strdup(name)}));
}
tll_push_back(font_cache, ((struct font_cache_entry){.hash = hash, .font = font}));
return font;
}
static size_t
hash_index(wchar_t wc)
{
return wc % glyph_cache_size;
}
static bool
glyph_for_wchar(const struct font *font, wchar_t wc, struct glyph *glyph)
{
*glyph = (struct glyph){
.wc = wc,
.valid = false,
};
/*
* LCD filter is per library instance. Thus we need to re-set it
* every time...
*
* Also note that many freetype builds lack this feature
* (FT_CONFIG_OPTION_SUBPIXEL_RENDERING must be defined, and isn't
* by default) */
FT_Error err = FT_Library_SetLcdFilter(ft_lib, font->lcd_filter);
if (err != 0 && err != FT_Err_Unimplemented_Feature) {
LOG_ERR("failed to set LCD filter: %s", ft_error_string(err));
goto err;
}
FT_UInt idx = FT_Get_Char_Index(font->face, wc);
if (idx == 0) {
/* No glyph in this font, try fallback fonts */
tll_foreach(font->fallbacks, it) {
if (it->item.font == NULL) {
it->item.font = from_name(it->item.pattern, true);
if (it->item.font == NULL)
continue;
}
if (glyph_for_wchar(it->item.font, wc, glyph)) {
LOG_DBG("%C: used fallback: %s", wc, it->item.font->name);
return true;
}
}
if (font->is_fallback)
return false;
/* Try fontconfig fallback fonts */
assert(font->fc_pattern != NULL);
assert(font->fc_fonts != NULL);
assert(font->fc_loaded_fallbacks != NULL);
assert(font->fc_idx != -1);
for (int i = font->fc_idx + 1; i < font->fc_fonts->nfont; i++) {
if (font->fc_loaded_fallbacks[i] == NULL) {
/* Load font */
struct font *fallback = malloc(sizeof(*fallback));
if (!from_font_set(font->fc_pattern, font->fc_fonts, i, fallback, true))
{
LOG_WARN("failed to load fontconfig fallback font");
free(fallback);
continue;
}
LOG_DBG("loaded new fontconfig fallback font");
assert(fallback->fc_idx >= i);
i = fallback->fc_idx;
font->fc_loaded_fallbacks[i] = fallback;
}
assert(font->fc_loaded_fallbacks[i] != NULL);
if (glyph_for_wchar(font->fc_loaded_fallbacks[i], wc, glyph)) {
LOG_DBG("%C: used fontconfig fallback: %s",
wc, font->fc_loaded_fallbacks[i]->name);
return true;
}
}
LOG_DBG("%C: no glyph found (in neither the main font, "
"nor any fallback fonts)", wc);
}
err = FT_Load_Glyph(font->face, idx, font->load_flags);
if (err != 0) {
LOG_ERR("%s: failed to load glyph #%d: %s",
font->name, idx, ft_error_string(err));
goto err;
}
err = FT_Render_Glyph(font->face->glyph, font->render_flags);
if (err != 0) {
LOG_ERR("%s: failed to render glyph: %s", font->name, ft_error_string(err));
goto err;
}
assert(font->face->glyph->format == FT_GLYPH_FORMAT_BITMAP);
FT_Bitmap *bitmap = &font->face->glyph->bitmap;
if (bitmap->width == 0)
goto err;
pixman_format_code_t pix_format;
int width;
int rows;
switch (bitmap->pixel_mode) {
case FT_PIXEL_MODE_MONO:
pix_format = PIXMAN_a1;
width = bitmap->width;
rows = bitmap->rows;
break;
case FT_PIXEL_MODE_GRAY:
pix_format = PIXMAN_a8;
width = bitmap->width;
rows = bitmap->rows;
break;
case FT_PIXEL_MODE_LCD:
pix_format = PIXMAN_x8r8g8b8;
width = bitmap->width / 3;
rows = bitmap->rows;
break;
case FT_PIXEL_MODE_LCD_V:
pix_format = PIXMAN_x8r8g8b8;
width = bitmap->width;
rows = bitmap->rows / 3;
break;
case FT_PIXEL_MODE_BGRA:
pix_format = PIXMAN_a8r8g8b8;
width = bitmap->width;
rows = bitmap->rows;
break;
default:
LOG_ERR("unimplemented: FT pixel mode: %d", bitmap->pixel_mode);
goto err;
break;
}
int stride = stride_for_format_and_width(pix_format, width);
assert(stride >= bitmap->pitch);
uint8_t *data = malloc(rows * stride);
/* Convert FT bitmap to pixman image */
switch (bitmap->pixel_mode) {
case FT_PIXEL_MODE_MONO:
for (size_t r = 0; r < bitmap->rows; r++) {
for (size_t c = 0; c < (bitmap->width + 7) / 8; c++) {
uint8_t v = bitmap->buffer[r * bitmap->pitch + c];
uint8_t reversed = 0;
for (size_t i = 0; i < min(8, bitmap->width - c * 8); i++)
reversed |= ((v >> (7 - i)) & 1) << i;
data[r * stride + c] = reversed;
}
}
break;
case FT_PIXEL_MODE_GRAY:
for (size_t r = 0; r < bitmap->rows; r++) {
for (size_t c = 0; c < bitmap->width; c++)
data[r * stride + c] = bitmap->buffer[r * bitmap->pitch + c];
}
break;
case FT_PIXEL_MODE_BGRA:
assert(stride == bitmap->pitch);
memcpy(data, bitmap->buffer, bitmap->rows * bitmap->pitch);
break;
case FT_PIXEL_MODE_LCD:
for (size_t r = 0; r < bitmap->rows; r++) {
for (size_t c = 0; c < bitmap->width; c += 3) {
unsigned char _r = bitmap->buffer[r * bitmap->pitch + c + (font->bgr ? 2 : 0)];
unsigned char _g = bitmap->buffer[r * bitmap->pitch + c + 1];
unsigned char _b = bitmap->buffer[r * bitmap->pitch + c + (font->bgr ? 0 : 2)];
uint32_t *p = (uint32_t *)&data[r * stride + 4 * (c / 3)];
*p = _r << 16 | _g << 8 | _b;
}
}
break;
case FT_PIXEL_MODE_LCD_V:
/* Unverified */
for (size_t r = 0; r < bitmap->rows; r += 3) {
for (size_t c = 0; c < bitmap->width; c++) {
unsigned char _r = bitmap->buffer[(r + (font->bgr ? 2 : 0)) * bitmap->pitch + c];
unsigned char _g = bitmap->buffer[(r + 1) * bitmap->pitch + c];
unsigned char _b = bitmap->buffer[(r + (font->bgr ? 0 : 2)) * bitmap->pitch + c];
uint32_t *p = (uint32_t *)&data[r / 3 * stride + 4 * c];
*p = _r << 16 | _g << 8 | _b;
}
}
break;
default:
abort();
break;
}
pixman_image_t *pix = pixman_image_create_bits_no_clear(
pix_format, width, rows, (uint32_t *)data, stride);
if (pix == NULL) {
free(data);
goto err;
}
pixman_image_set_component_alpha(
pix,
bitmap->pixel_mode == FT_PIXEL_MODE_LCD ||
bitmap->pixel_mode == FT_PIXEL_MODE_LCD_V);
if (font->pixel_size_fixup != 1.) {
struct pixman_transform scale;
pixman_transform_init_identity(&scale);
pixman_transform_scale(
&scale, NULL,
pixman_double_to_fixed(1.0 / font->pixel_size_fixup),
pixman_double_to_fixed(1.0 / font->pixel_size_fixup));
pixman_image_set_transform(pix, &scale);
pixman_image_set_filter(pix, PIXMAN_FILTER_BEST, NULL, 0);
}
int cols = wcwidth(wc);
if (cols < 0)
cols = 0;
*glyph = (struct glyph){
.wc = wc,
.cols = cols,
.pix = pix,
.x = font->face->glyph->bitmap_left * font->pixel_size_fixup,
.y = font->face->glyph->bitmap_top * font->pixel_size_fixup,
.width = width,
.height = rows,
.valid = true,
};
return true;
err:
assert(!glyph->valid);
return false;
}
const struct glyph *
font_glyph_for_wc(struct font *font, wchar_t wc)
{
mtx_lock(&font->lock);
assert(font->glyph_cache != NULL);
size_t hash_idx = hash_index(wc);
hash_entry_t *hash_entry = font->glyph_cache[hash_idx];
if (hash_entry != NULL) {
tll_foreach(*hash_entry, it) {
if (it->item.wc == wc) {
mtx_unlock(&font->lock);
return it->item.valid ? &it->item : NULL;
}
}
}
struct glyph glyph;
bool got_glyph = glyph_for_wchar(font, wc, &glyph);
if (hash_entry == NULL) {
hash_entry = calloc(1, sizeof(*hash_entry));
assert(font->glyph_cache[hash_idx] == NULL);
font->glyph_cache[hash_idx] = hash_entry;
}
assert(hash_entry != NULL);
tll_push_back(*hash_entry, glyph);
mtx_unlock(&font->lock);
return got_glyph ? &tll_back(*hash_entry) : NULL;
}
void
font_destroy(struct font *font)
{
if (font == NULL)
return;
if (--font->ref_counter > 0)
return;
tll_foreach(font_cache, it) {
if (it->item.font == font) {
tll_remove(font_cache, it);
break;
}
}
free(font->name);
tll_foreach(font->fallbacks, it) {
font_destroy(it->item.font);
free(it->item.pattern);
}
tll_free(font->fallbacks);
if (font->face != NULL) {
mtx_lock(&ft_lock);
FT_Done_Face(font->face);
mtx_unlock(&ft_lock);
}
mtx_destroy(&font->lock);
if (font->fc_fonts != NULL) {
assert(font->fc_loaded_fallbacks != NULL);
for (size_t i = 0; i < font->fc_fonts->nfont; i++)
font_destroy(font->fc_loaded_fallbacks[i]);
free(font->fc_loaded_fallbacks);
}
if (font->fc_pattern != NULL)
FcPatternDestroy(font->fc_pattern);
if (font->fc_fonts != NULL)
FcFontSetDestroy(font->fc_fonts);
for (size_t i = 0; i < glyph_cache_size && font->glyph_cache != NULL; i++) {
if (font->glyph_cache[i] == NULL)
continue;
tll_foreach(*font->glyph_cache[i], it) {
if (!it->item.valid)
continue;
void *image = pixman_image_get_data(it->item.pix);
pixman_image_unref(it->item.pix);
free(image);
}
tll_free(*font->glyph_cache[i]);
free(font->glyph_cache[i]);
}
free(font->glyph_cache);
free(font);
}

81
font.h
View file

@ -1,81 +0,0 @@
#pragma once
#include <stdbool.h>
#include <threads.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_LCD_FILTER_H
#include <fontconfig/fontconfig.h>
#include <pixman.h>
#include "tllist.h"
//#include "terminal.h"
typedef tll(const char *) font_list_t;
struct glyph {
wchar_t wc;
int cols;
pixman_image_t *pix;
int x;
int y;
int width;
int height;
bool valid;
};
typedef tll(struct glyph) hash_entry_t;
struct font_fallback {
char *pattern;
struct font *font;
};
struct font {
char *name;
mtx_t lock;
FT_Face face;
int load_flags;
int render_flags;
FT_LcdFilter lcd_filter;
double pixel_size_fixup; /* Scale factor - should only be used with ARGB32 glyphs */
bool bgr; /* True for FC_RGBA_BGR and FC_RGBA_VBGR */
/* font extents */
int height;
int descent;
int ascent;
int max_x_advance;
struct {
double position;
double thickness;
} underline;
struct {
double position;
double thickness;
} strikeout;
bool is_fallback;
tll(struct font_fallback) fallbacks;
size_t ref_counter;
/* Fields below are only valid for non-fallback fonts */
FcPattern *fc_pattern;
FcFontSet *fc_fonts;
int fc_idx;
struct font **fc_loaded_fallbacks; /* fc_fonts->nfont array */
hash_entry_t **glyph_cache;
};
struct font *font_from_name(font_list_t names, const char *attributes);
const struct glyph *font_glyph_for_wc(struct font *font, wchar_t wc);
void font_destroy(struct font *font);

3
main.c
View file

@ -10,13 +10,14 @@
#include <sys/sysinfo.h>
#include <fcft.h>
#define LOG_MODULE "main"
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "config.h"
#include "fdm.h"
#include "font.h"
#include "server.h"
#include "shm.h"
#include "terminal.h"

View file

@ -62,7 +62,19 @@ version = custom_target(
output: 'version.h',
command: [generate_version_sh, meson.project_version(), '@SOURCE_DIR@', '@OUTPUT@'])
tllist = subproject('tllist').get_variable('tllist')
tllist_proj = subproject('tllist')
if tllist_proj.found()
tllist = tllist_proj.get_variable('tllist')
else
tllist = dependency('tllist')
endif
fcft_proj = subproject('fcft')
if fcft_proj.found()
fcft = fcft_proj.get_variable('fcft')
else
fcft = dependency('fcft')
endif
executable(
'foot',
@ -72,7 +84,6 @@ executable(
'commands.c', 'commands.h',
'csi.c', 'csi.h',
'fdm.c', 'fdm.h',
'font.c', 'font.h',
'grid.c', 'grid.h',
'input.c', 'input.h',
'log.c', 'log.h',
@ -90,7 +101,7 @@ executable(
'wayland.c', 'wayland.h',
wl_proto_src + wl_proto_headers, version,
dependencies: [threads, math, freetype, fontconfig, pixman, wayland_client, wayland_cursor, xkb,
tllist],
tllist, fcft],
install: true)
executable(

View file

@ -10,12 +10,13 @@
#include <wayland-cursor.h>
#include <xdg-shell.h>
#include <fcft.h>
#define LOG_MODULE "render"
#define LOG_ENABLE_DBG 0
#include "log.h"
#include "shm.h"
#include "grid.h"
#include "font.h"
#define min(x, y) ((x) < (y) ? (x) : (y))
#define max(x, y) ((x) > (y) ? (x) : (y))

1
subprojects/fcft Submodule

@ -0,0 +1 @@
Subproject commit 6377db4464f9a411c17b681f4a41c36950f1a232

1
subprojects/tllist Submodule

@ -0,0 +1 @@
Subproject commit 5054d3dbbbd7d76e35f9d0d875e3c7f500fde52b

View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018 Daniel Eklöf
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.

View file

@ -1,301 +0,0 @@
# tllist
**tllist** is a *T*yped *L*inked *L*ist C header file only
library implemented using pre-processor macros.
1. [Description](#description)
1. [Usage](#usage)
1. [Declaring a variable](#declaring-a-variable)
1. [Adding items - basic](#adding-items-basic)
1. [List length](#list-length)
1. [Accessing items](#accessing-items)
1. [Iterating](#iterating)
1. [Removing items - basic](#removing-items-basic)
1. [Adding items - advanced](#adding-items-advanced)
1. [Removing items - advanced](#removing-items-advanced)
1. [Freeing](#freeing)
1. [Integrating](#integrating)
1. [Meson](#meson)
1. [API](#api)
1. [Cheat sheet](#cheat-sheet)
## Description
Most C implementations of linked list are untyped. That is, their data
carriers are typically `void *`. This is error prone since your
compiler will not be able to help you correct your mistakes (_oh, was
it pointer-to-a-pointer... I though it was just a pointer..._).
**tllist** addresses this by using pre-processor macros to implement
dynamic types, where the data carrier is typed to whatever you want;
both **primitive** data types are supported as well as aggregated ones
such as **structs**, **enums** and **unions**.
Being a double-linked list, most operations are constant in time
(including pushing and popping both to/from front and back).
The memory overhead is fairly small; each item carries, besides its
data, a _prev_ and _next_ pointer (i.e. a constant 16 byte overhead
per item on 64-bit architectures).
The list itself has two _head_ and _tail_ pointers, plus a _length_
variable (typically 8 bytes on 64-bit architectures) to make list
length lookup constant in time.
Thus, assuming 64-bit pointers (and a 64-bit `size_t` type), the total
overhead is `3*8 + n*2*8` bytes.
## Usage
### Declaring a variable
1. **Declare a variable**
```c
/* Declare a variable using an anonymous type */
tll(int) an_integer_list = tll_init();
```
2. **Typedef**
```c
/* First typedef the list type */
typedef tll(int) an_integer_list_t;
/* Then declare a variable using that typedef */
an_integer_list_t an_integer_list = tll_init();
```
3. **Named struct**
```c
/* First declare named struct */
tll(int, an_integer_list);
/* Then declare a variable using that named struct */
struct an_integer_list an_integer_list = tll_init();
```
### Adding items - basic
Use `tll_push_back()` or `tll_push_front()` to add elements to the
back or front of the list.
```c
tll_push_back(an_integer_list, 4711);
tll_push_front(an_integer_list, 1234);
```
### List length
`tll_length()` returns the length (number of items) in a list.
```c
tll_push_back(an_integer_list, 1234);
tll_push_back(an_integer_list, 5678);
printf("length: %zu\n", tll_length(an_integer_list));
```
Outputs:
length: 2
### Accessing items
For the front and back items, you can use `tll_front()` and
`tll_back()` respectively. For any other item in the list, you need to
iterate the list and find the item yourself.
```c
tll_push_back(an_integer_list, 1234);
tll_push_back(an_integer_list, 5555);
tll_push_back(an_integer_list, 6789);
printf("front: %d\n", tll_front(an_integer_list));
printf("back: %d\n", tll_back(an_integer_list));
```
Outputs:
front: 1234
back: 6789
### Iterating
You can iterate the list either forward or backwards, using
`tll_foreach()` and `tll_rforeach()` respectively.
The `it` variable should be treated as an opaque iterator type, where
`it->item` is the item.
In reality, it is simply a pointer to the linked list entry, and since
tllist is a header-only implementation, you do have access to e.g. the
next/prev pointers. There should not be any need to access anything
except `item` however.
Note that `it` can be named anything.
```c
tll_push_back(an_integer_list, 1);
tll_push_back(an_integer_list, 2);
tll_foreach(an_integer_list, it) {
printf("forward: %d\n", it->item);
}
tll_rforeach(an_integer_list, it) {
printf("reverse: %d\n", it->item);
}
```
Outputs:
forward: 1
forward: 2
reverse: 2
reverse: 1
### Removing items - basic
`tll_pop_front()` and `tll_pop_back()` removes the front/back item and
returns it.
```c
tll_push_back(an_integer_list, 1234);
tll_push_back(an_integer_list, 5678);
printf("front: %d\n", tll_pop_front(an_integer_list));
printf("back: %d\n", tll_pop_back(an_integer_list));
printf("length: %zu\n", tll_length(an_integer_list));
```
Outputs:
front: 1234
back: 5678
length: 0
### Adding items - advanced
Given an iterator, you can insert new items before or after that
iterator, using `tll_insert_before()` and `tll_insert_after()`.
```c
tll_foreach(an_integer_list, it) {
if (it->item == 1234) {
tll_insert_before(an_integer_list, it, 7777);
break;
}
}
```
Q: Why do I have to pass **both** the _list_ and the _iterator_ to
`tll_insert_before()`?
A: If not, **each** element in the list would have to contain a
pointer to the owning list, which would significantly increase the
overhead.
### Removing items - advanced
Similar to how you can add items while iterating a list, you can also
remove them.
Note that the `*foreach()` functions are **safe** in this regard - it
is perfectly OK to remove the "current" item.
```c
tll_foreach(an_integer_list, it) {
if (it->item.delete_me)
tll_remove(an_integer_list, it);
}
```
To make it slightly easier to handle cases where the item _itself_
must be free:d as well, there is also `tll_remove_and_free()`. It
works just like `tll_remove()`, but takes an additional argument; a
callback that will be called for each item.
```c
tll(int *) int_p_list = tll_init();
int *a = malloc(sizeof(*a));
int *b = malloc(sizeof(*b));
*a = 1234;
*b = 5678;
tll_push_back(int_p_list, a);
tll_push_back(int_p_list, b);
tll_foreach(int_p_list, it) {
tll_remove_and_free(int_p_list, it, free);
}
```
### Freeing
To remove **all** items, use `tll_free()`, or
`tll_free_and_free()`. These are just convenience functions and
calling these are equivalent to:
```c
tll_foreach(an_integer_list, it) {
tll_remove(an_integer_list, it);
}
```
Note that there is no need to call `tll_free()` on an empty
(`tll_length(list) == 0`) list.
## Integrating
The easiest way may be to simply copy `tllist.h` into your
project. But see sections below for other ways.
### Meson
You can use tllist as a subproject. In your main project's
`meson.build`, to something like:
```meson
tllist = subproject('tllist').get_variable('tllist')
executable('you-executable', ..., dependencies: [tllist])
```
## API
### Cheat sheet
| Function | Description | Context | Complexity |
|-------------------------------------|-------------------------------------------------------|--------------------|-----------:|
| `list = tll_init()` | initialize a new tllist variable to an empty list | Variable init | O(1) |
| `tll_length(list)` | returns the length (number of items) of a list | | O(1) |
| `tll_push_front(list, item)` | inserts _item_ at the beginning of the list | | O(1) |
| `tll_push_back(list, item)` | inserts _item_ at the end of the list | | O(1) |
| `tll_front(list)` | returns the first item in the list | | O(1) |
| `tll_back(list)` | returns the last item in the list | | O(1) |
| `tll_pop_front(list)` | removes and returns the first item in the list | | O(1) |
| `tll_pop_back(list)` | removes and returns the last item in the list | | O(1) |
| `tll_foreach(list, it)` | iterates the list from the beginning to the end | | O(n) |
| `tll_rforeach(list, it)` | iterates the list from the end to the beginning | | O(n) |
| `tll_insert_before(list, it, item)` | inserts _item_ before _it_. | `tll_(r)foreach()` | O(1) |
| `tll_insert_after(list, it, item)` | inserts _item_ after _it_. | `tll_(r)foreach()` | O(1) |
| `tll_remove(list, it)` | removes _it_ from the list. | `tll_(r)foreach()` | O(1) |
| `tll_remove_and_free(list, it, cb)` | removes _it_ from the list, and calls `cb(it->item)`. | `tll_(r)foreach()` | O(1) |
| `tll_free(list)` | removes **all** items from the list | | O(n) |
| `tll_free_and_free(list, cb)` | removes **all** items from the list, and calls `cb(it->item)` for each item. | | O(n) |

View file

@ -1,5 +0,0 @@
project('tllist', 'c', version: '1.0.0', license: 'MIT', meson_version: '>=0.50.0')
tllist = declare_dependency(include_directories: '.')
unittest = executable('unittest', 'test.c', dependencies: [tllist])
test('unittest', unittest)

View file

@ -1,96 +0,0 @@
#undef NDEBUG
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <tllist.h>
int
main(int argc, const char *const *argv)
{
tll(int) l = tll_init();
assert(tll_length(l) == 0);
/* push back */
tll_push_back(l, 123); assert(tll_length(l) == 1);
tll_push_back(l, 456); assert(tll_length(l) == 2);
tll_push_back(l, 789); assert(tll_length(l) == 3);
assert(tll_front(l) == 123);
assert(tll_back(l) == 789);
/* push front */
tll_push_front(l, 0xabc); assert(tll_length(l) == 4);
assert(tll_front(l) == 0xabc);
assert(tll_back(l) == 789);
/* Pop back */
assert(tll_pop_back(l) == 789);
assert(tll_back(l) == 456);
/* Pop front */
assert(tll_pop_front(l) == 0xabc);
assert(tll_front(l) == 123);
/* foreach */
assert(tll_length(l) == 2);
int seen[tll_length(l)];
memset(seen, 0, tll_length(l) * sizeof(seen[0]));
size_t count = 0;
tll_foreach(l, it)
seen[count++] = it->item;
assert(count == tll_length(l));
assert(seen[0] == 123);
assert(seen[1] == 456);
/* rforeach */
memset(seen, 0, tll_length(l) * sizeof(seen[0]));
count = 0;
tll_rforeach(l, it)
seen[count++] = it->item;
assert(count == tll_length(l));
assert(seen[0] == 456);
assert(seen[1] == 123);
/* remove */
tll_push_back(l, 789);
tll_foreach(l, it) {
if (it->item > 123 && it->item < 789)
tll_remove(l, it);
}
assert(tll_length(l) == 2);
assert(tll_front(l) == 123);
assert(tll_back(l) == 789);
/* insert before */
tll_foreach(l, it) {
if (it->item == 123)
tll_insert_before(l, it, 0xabc);
}
assert(tll_length(l) == 3);
assert(tll_front(l) == 0xabc);
assert(tll_back(l) == 789);
/* insert after */
tll_foreach(l, it) {
if (it->item == 789)
tll_insert_after(l, it, 999);
}
assert(tll_length(l) == 4);
assert(tll_front(l) == 0xabc);
assert(tll_back(l) == 999);
/* free */
tll_free(l);
assert(tll_length(l) == 0);
assert(l.head == NULL);
assert(l.tail == NULL);
return EXIT_SUCCESS;
}

View file

@ -1,180 +0,0 @@
#pragma once
#include <stdlib.h>
#include <stddef.h>
#include <assert.h>
#define TLL_PASTE2( a, b) a##b
#define TLL_PASTE( a, b) TLL_PASTE2( a, b)
/* Utility macro to generate a list element struct with a unique struct tag */
#define TLL_UNIQUE_INNER_STRUCT(TYPE, ID) \
struct TLL_PASTE(__tllist_ , ID) { \
TYPE item; \
struct TLL_PASTE(__tllist_, ID) *prev; \
struct TLL_PASTE(__tllist_, ID) *next; \
} *head, *tail;
/*
* Defines a new typed-list type, or directly instantiate a typed-list variable
*
* Example a, declare a variable (list of integers):
* tll(int) my_list;
*
* Example b, declare a type, and then use the type:
* tll(int, my_list_type);
* struct my_list_type my_list;
*/
#define tll(TYPE, ...) \
struct __VA_ARGS__ { \
TLL_UNIQUE_INNER_STRUCT(TYPE, __COUNTER__) \
size_t length; \
}
/* Initializer: tll(int) my_list = tll_init(); */
#define tll_init() {.head = NULL, .tail = NULL, .length = 0}
/* Length/size of list: printf("size: %zu\n", tll_length(my_list)); */
#define tll_length(list) (list).length
/* Adds a new item to the back of the list */
#define tll_push_back(list, new_item) \
do { \
tll_insert_after(list, (list).tail, new_item); \
if ((list).head == NULL) \
(list).head = (list).tail; \
} while (0)
/* Adds a new item to the front of the list */
#define tll_push_front(list, new_item) \
do { \
tll_insert_before(list, (list).head, new_item); \
if ((list).tail == NULL) \
(list).tail = (list).head; \
} while (0)
/*
* Iterates the list. <it> is an iterator pointer. You can access the
* list item with ->item:
*
* tll(int) my_list = vinit();
* tll_push_back(my_list, 5);
*
* tll_foreach(my_list i) {
* printf("%d\n", i->item);
* }
*/
#define tll_foreach(list, it) \
for (__typeof__(*(list).head) *it = (list).head, \
*it_next = it != NULL ? it->next : NULL; \
it != NULL; \
it = it_next, \
it_next = it_next != NULL ? it_next->next : NULL)
/* Same as tll_foreach(), but iterates backwards */
#define tll_rforeach(list, it) \
for (__typeof__(*(list).tail) *it = (list).tail, \
*it_prev = it != NULL ? it->prev : NULL; \
it != NULL; \
it = it_prev, \
it_prev = it_prev != NULL ? it_prev->prev : NULL)
/*
* Inserts a new item after <it>, which is an iterator. I.e. you can
* only call this from inside a tll_foreach() or tll_rforeach() loop.
*/
#define tll_insert_after(list, it, new_item) \
do { \
__typeof__((list).head) __e = malloc(sizeof(*__e)); \
__e->item = (new_item); \
__e->prev = (it); \
__e->next = (it) != NULL ? (it)->next : NULL; \
if ((it) != NULL) { \
if ((it)->next != NULL) \
(it)->next->prev = __e; \
(it)->next = __e; \
} \
if ((it) == (list).tail) \
(list).tail = __e; \
(list).length++; \
} while (0)
/*
* Inserts a new item before <it>, which is an iterator. I.e. you can
* only call this from inside a tll_foreach() or tll_rforeach() loop.
*/
#define tll_insert_before(list, it, new_item) \
do { \
__typeof__((list).head) __e = malloc(sizeof(*__e)); \
__e->item = (new_item); \
__e->prev = (it) != NULL ? (it)->prev : NULL; \
__e->next = (it); \
if ((it) != NULL) { \
if ((it)->prev != NULL) \
(it)->prev->next = __e; \
(it)->prev = __e; \
} \
if ((it) == (list).head) \
(list).head = __e; \
(list).length++; \
} while (0)
/*
* Removes an entry from the list. <it> is an iterator. I.e. you can
* only call this from inside a tll_foreach() or tll_rforeach() loop.
*/
#define tll_remove(list, it) \
do { \
assert((list).length > 0); \
__typeof__((list).head) __prev = it->prev; \
__typeof__((list).head) __next = it->next; \
if (__prev != NULL) \
__prev->next = __next; \
else \
(list).head = __next; \
if (__next != NULL) \
__next->prev = __prev; \
else \
(list).tail = __prev; \
free(it); \
(list).length--; \
} while (0)
/* Same as tll_remove(), but calls free_callback(it->item) */
#define tll_remove_and_free(list, it, free_callback) \
do { \
free_callback((it)->item); \
tll_remove((list), (it)); \
} while (0)
#define tll_front(list) (list).head->item
#define tll_back(list) (list).tail->item
/*
* Removes the first element from the list, and returns it (note:
* returns the *actual* item, not an iterator.
*/
#define tll_pop_front(list) \
({__typeof__((list).head) it = (list).head; \
__typeof__((list).head->item) __ret = it->item; \
tll_remove((list), it); \
__ret; \
})
/* Same as tll_pop_front(), but returns/removes the *last* element */
#define tll_pop_back(list) \
({__typeof__((list).tail) it = (list).tail; \
__typeof__((list).tail->item) __ret = it->item; \
tll_remove((list), it); \
__ret; \
})
/* Frees the list. This call is *not* needed if the list is already empty. */
#define tll_free(list) \
tll_foreach(list, __it) \
tll_remove(list, __it)
/* Same as tll_free(), but also calls free_callback(item) for every item */
#define tll_free_and_free(list, free_callback) \
tll_foreach(list, __it) \
tll_remove_and_free(list, __it, free_callback)

View file

@ -8,10 +8,11 @@
#include <threads.h>
#include <semaphore.h>
#include <tllist.h>
#include <fcft.h>
//#include "config.h"
#include "fdm.h"
#include "font.h"
#include "tllist.h"
#include "wayland.h"
#define likely(c) __builtin_expect(!!(c), 1)

View file

@ -9,8 +9,9 @@
#include <primary-selection-unstable-v1.h>
#include <xkbcommon/xkbcommon.h>
#include <tllist.h>
#include "fdm.h"
#include "tllist.h"
struct monitor {
struct wayland *wayl;