Merge branch 'use-fcft'

This commit is contained in:
Daniel Eklöf 2019-12-01 16:02:04 +01:00
commit dea021e8ef
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
20 changed files with 45 additions and 1333 deletions

7
.gitmodules vendored
View file

@ -2,6 +2,9 @@
path = external/wlr-protocols path = external/wlr-protocols
url = https://github.com/swaywm/wlr-protocols.git url = https://github.com/swaywm/wlr-protocols.git
branch = master branch = master
[submodule "external/tllist"] [submodule "subprojects/tllist"]
path = external/tllist path = subprojects/tllist
url = https://codeberg.org/dnkl/tllist.git url = https://codeberg.org/dnkl/tllist.git
[submodule "subprojects/fcft"]
path = subprojects/fcft
url = https://codeberg.org/dnkl/fcft.git

View file

@ -9,12 +9,13 @@ makedepends=('meson' 'ninja' 'scdoc')
depends=( depends=(
'libxcb' 'xcb-util' 'xcb-util-cursor' 'xcb-util-wm' 'libxcb' 'xcb-util' 'xcb-util-cursor' 'xcb-util-wm'
'wayland' 'wlroots' 'wayland' 'wlroots'
'freetype2' 'fontconfig' 'pixman' 'pixman'
'libyaml' 'libyaml'
'alsa-lib' 'alsa-lib'
'libudev.so' 'libudev.so'
'json-c' 'json-c'
'libmpdclient') 'libmpdclient'
'tllist' 'fcft')
optdepends=('xcb-util-errors: better X error messages') optdepends=('xcb-util-errors: better X error messages')
source=() source=()

View file

@ -10,12 +10,13 @@ provides=('yambar')
makedepends=('meson' 'ninja' 'scdoc') makedepends=('meson' 'ninja' 'scdoc')
depends=( depends=(
'wayland' 'wlroots' 'wayland' 'wlroots'
'freetype2' 'fontconfig' 'pixman' 'pixman'
'libyaml' 'libyaml'
'alsa-lib' 'alsa-lib'
'libudev.so' 'libudev.so'
'json-c' 'json-c'
'libmpdclient') 'libmpdclient'
'tllist' 'fcft')
source=() source=()
pkgver() { pkgver() {

View file

@ -67,7 +67,13 @@ conf_to_color(const struct yml_node *node)
struct font * struct font *
conf_to_font(const struct yml_node *node) conf_to_font(const struct yml_node *node)
{ {
return font_from_name(yml_value_as_string(node)); font_list_t font_list = tll_init();
tll_push_back(font_list, yml_value_as_string(node));
struct font *font = font_from_name(font_list, NULL);
tll_free(font_list);
return font;
} }
struct deco * struct deco *
@ -263,7 +269,11 @@ conf_to_bar(const struct yml_node *bar, enum bar_backend backend)
* and particles. This allows us to specify a default font and * and particles. This allows us to specify a default font and
* foreground color at top-level. * foreground color at top-level.
*/ */
struct font *font = font_from_name("sans"); font_list_t font_list = tll_init();
tll_push_back(font_list, "sans");
struct font *font = font_from_name(font_list, NULL);
tll_free(font_list);
pixman_color_t foreground = {0xffff, 0xffff, 0xffff, 0xffff}; /* White */ pixman_color_t foreground = {0xffff, 0xffff, 0xffff, 0xffff}; /* White */
const struct yml_node *font_node = yml_get_value(bar, "font"); const struct yml_node *font_node = yml_get_value(bar, "font");

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "font.h" #include <fcft/fcft.h>
#include "yml.h" #include "yml.h"
#include "bar/bar.h" #include "bar/bar.h"

View file

@ -1,4 +1,4 @@
deco_sdk = declare_dependency(dependencies: [freetype, pixman, tllist]) deco_sdk = declare_dependency(dependencies: [pixman, tllist])
decorations = [] decorations = []
foreach deco : ['background', 'stack', 'underline'] foreach deco : ['background', 'stack', 'underline']

1
external/tllist vendored

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

642
font.c
View file

@ -1,642 +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 tll(const struct font *) font_cache = tll_init();
static const size_t glyph_cache_size = 512;
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;
#endif
LOG_DBG("fontconfig: %d.%d.%d", major, minor, patch);
}
static void __attribute__((destructor))
fini(void)
{
assert(tll_length(font_cache) == 0);
mtx_destroy(&ft_lock);
FT_Done_FreeType(ft_lib);
FcFini();
}
static bool
font_destroy_no_free(struct font *font)
{
assert(font->ref_counter > 0);
if (--font->ref_counter > 0)
return false;
if (font->face != NULL) {
mtx_lock(&ft_lock);
FT_Done_Face(font->face);
mtx_unlock(&ft_lock);
}
if (font->fc_pattern != NULL)
FcPatternDestroy(font->fc_pattern);
if (font->fc_fonts != NULL)
FcFontSetDestroy(font->fc_fonts);
free(font->name);
if (font->cache == NULL)
return true;
for (size_t i = 0; i < glyph_cache_size; i++) {
if (font->cache[i] == NULL)
continue;
tll_foreach(*font->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->cache[i]);
free(font->cache[i]);
}
free(font->cache);
tll_foreach(font_cache, it) {
if (it->item == font) {
tll_remove(font_cache, it);
break;
}
}
return true;
}
/* Not used in yambar */
#if 0
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);
}
#endif
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 = 96;
double size;
double pixel_size;
if (FcPatternGetDouble(final_pattern, FC_SIZE, 0, &size) != FcResultMatch ||
FcPatternGetDouble(final_pattern, FC_PIXEL_SIZE, 0, &pixel_size) != FcResultMatch)
{
LOG_ERR("%s: failed to get size", face_file);
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;
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) {
double requested_size;
if (FcPatternGetDouble(pattern, FC_SIZE, 0, &requested_size) != FcResultMatch)
requested_size = size;
requested_pixel_size = requested_size * dpi / 72;
}
pixel_fixup = requested_pixel_size / pixel_size;
LOG_DBG("estimated fixup factor to %f (requested pixel size: %f, actual: %f)",
pixel_fixup, requested_pixel_size, pixel_size);
} else
pixel_fixup = 1.;
}
LOG_DBG("loading: %s", face_file);
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);
if ((ft_err = FT_Set_Pixel_Sizes(ft_face, 0, pixel_size)) != 0) {
LOG_WARN("failed to set pixel size");
mtx_lock(&ft_lock);
FT_Done_Face(ft_face);
mtx_unlock(&ft_lock);
FcPatternDestroy(final_pattern);
return false;
}
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;
}
FcPatternDestroy(final_pattern);
font->face = ft_face;
font->load_flags = load_flags | FT_LOAD_COLOR;
font->render_flags = render_flags;
font->pixel_size_fixup = pixel_fixup;
font->bgr = fc_rgba == FC_RGBA_BGR || fc_rgba == FC_RGBA_VBGR;
font->fc_idx = font_idx;
font->is_fallback = is_fallback;
font->ref_counter = 1;
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 = height * font->pixel_size_fixup;
font->descent = -descent * font->pixel_size_fixup;
font->ascent = ascent * font->pixel_size_fixup;
font->max_x_advance = max_x_advance * font->pixel_size_fixup;
LOG_DBG("metrics: height: %d, descent: %d, ascent: %d, x-advance: %d",
font->height, font->descent, font->ascent, font->max_x_advance);
if (!is_fallback) {
font->fc_pattern = pattern;
font->fc_fonts = fonts;
font->cache = calloc(glyph_cache_size, sizeof(font->cache[0]));
}
/* Not used in yambar */
/* underline_strikeout_metrics(font); */
return true;
}
struct font *
font_from_name(const char *name)
{
LOG_DBG("instantiating %s", name);
tll_foreach(font_cache, it) {
if (strcmp(it->item->name, name) == 0)
return font_clone(it->item);
}
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, false)) {
free(font);
FcFontSetDestroy(fonts);
FcPatternDestroy(pattern);
return NULL;
}
font->name = strdup(name);
tll_push_back(font_cache, 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)
{
/*
* 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)
goto err;
FT_UInt idx = FT_Get_Char_Index(font->face, wc);
if (idx == 0) {
if (font->is_fallback)
return false;
/* No glyph in this font, try fontconfig fallback fonts */
/* Try fontconfig fallback fonts */
assert(font->fc_pattern != NULL);
assert(font->fc_fonts != NULL);
assert(font->fc_idx != -1);
for (int i = font->fc_idx + 1; i < font->fc_fonts->nfont; i++) {
struct font fallback;
if (!from_font_set(font->fc_pattern, font->fc_fonts, i, &fallback, true))
continue;
if (glyph_for_wchar(&fallback, wc, glyph)) {
LOG_DBG("%C: used fontconfig fallback", wc);
font_destroy_no_free(&fallback);
return true;
}
font_destroy_no_free(&fallback);
}
LOG_WARN("%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("load failed");
goto err;
}
err = FT_Render_Glyph(font->face->glyph, font->render_flags);
if (err != 0)
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_scale(
&scale,
pixman_double_to_fixed(1. / font->pixel_size_fixup),
pixman_double_to_fixed(1. / font->pixel_size_fixup));
pixman_image_set_transform(pix, &scale);
pixman_image_set_filter(pix, PIXMAN_FILTER_BEST, NULL, 0);
}
*glyph = (struct glyph){
.wc = wc,
.cols = wcwidth(wc),
.pix = pix,
.x = font->face->glyph->bitmap_left * font->pixel_size_fixup,
.y = ceil(font->face->glyph->bitmap_top * font->pixel_size_fixup),
.x_advance = ceil(font->face->glyph->advance.x / 64) * font->pixel_size_fixup,
.width = width,
.height = rows,
.valid = true,
};
return true;
err:
*glyph = (struct glyph){
.wc = wc,
.valid = false,
};
return false;
}
const struct glyph *
font_glyph_for_wc(struct font *font, wchar_t wc)
{
assert(font->cache != NULL);
size_t hash_idx = hash_index(wc);
hash_entry_t *hash_entry = font->cache[hash_idx];
if (hash_entry != NULL) {
tll_foreach(*hash_entry, it) {
if (it->item.wc == wc)
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->cache[hash_idx] == NULL);
font->cache[hash_idx] = hash_entry;
}
assert(hash_entry != NULL);
tll_push_back(*hash_entry, glyph);
return got_glyph ? &tll_back(*hash_entry) : NULL;
}
struct font *
font_clone(const struct font *_font)
{
struct font *font = (struct font *)_font;
font->ref_counter++;
return font;
}
void
font_destroy(struct font *font)
{
if (font_destroy_no_free(font))
free(font);
}

68
font.h
View file

@ -1,68 +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"
struct glyph {
wchar_t wc;
int cols;
pixman_image_t *pix;
int x;
int y;
int x_advance;
int width;
int height;
bool valid;
};
typedef tll(struct glyph) hash_entry_t;
struct font {
char *name;
FcPattern *fc_pattern;
FcFontSet *fc_fonts;
int fc_idx;
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 */
int height;
int descent;
int ascent;
int max_x_advance;
struct {
double position;
double thickness;
} underline;
struct {
double position;
double thickness;
} strikeout;
hash_entry_t **cache;
bool is_fallback;
int ref_counter;
};
struct font *font_from_name(const char *name);
struct font *font_clone(const struct font *font);
const struct glyph *font_glyph_for_wc(struct font *font, wchar_t wc);
void font_destroy(struct font *font);

View file

@ -14,8 +14,6 @@ plugs_as_libs = get_option('core-plugins-as-shared-libraries')
cc = meson.get_compiler('c') cc = meson.get_compiler('c')
dl = cc.find_library('dl') dl = cc.find_library('dl')
threads = dependency('threads') threads = dependency('threads')
freetype = dependency('freetype2')
fontconfig = dependency('fontconfig')
pixman = dependency('pixman-1') pixman = dependency('pixman-1')
yaml = dependency('yaml-0.1') yaml = dependency('yaml-0.1')
@ -57,7 +55,19 @@ if backend_x11
install_headers('xcb.h', subdir: 'yambar') install_headers('xcb.h', subdir: 'yambar')
endif endif
tllist = subproject('tllist').get_variable('tllist') tllist_proj = subproject('tllist', required: false)
if tllist_proj.found()
tllist = tllist_proj.get_variable('tllist')
else
tllist = dependency('tllist')
endif
fcft_proj = subproject('fcft', required: false)
if fcft_proj.found()
fcft = fcft_proj.get_variable('fcft')
else
fcft = dependency('fcft')
endif
subdir('completions') subdir('completions')
subdir('doc') subdir('doc')
@ -79,7 +89,6 @@ yambar = executable(
'config-verify.c', 'config-verify.h', 'config-verify.c', 'config-verify.h',
'config.c', 'config.h', 'config.c', 'config.h',
'decoration.h', 'decoration.h',
'font.c', 'font.h',
'log.c', 'log.h', 'log.c', 'log.h',
'main.c', 'main.c',
'module.c', 'module.h', 'module.c', 'module.h',
@ -88,7 +97,7 @@ yambar = executable(
'tag.c', 'tag.h', 'tag.c', 'tag.h',
'yml.c', 'yml.h', 'yml.c', 'yml.h',
version, version,
dependencies: [bar, freetype, fontconfig, pixman, yaml, threads, dl, tllist] + dependencies: [bar, pixman, yaml, threads, dl, tllist, fcft] +
decorations + particles + modules, decorations + particles + modules,
build_rpath: '$ORIGIN/modules:$ORIGIN/decorations:$ORIGIN/particles', build_rpath: '$ORIGIN/modules:$ORIGIN/decorations:$ORIGIN/particles',
export_dynamic: true, export_dynamic: true,
@ -104,7 +113,6 @@ install_headers(
'config.h', 'config.h',
'config-verify.h', 'config-verify.h',
'decoration.h', 'decoration.h',
'font.h',
'log.h', 'log.h',
'module.h', 'module.h',
'particle.h', 'particle.h',

View file

@ -1,4 +1,4 @@
module_sdk = declare_dependency(dependencies: [freetype, pixman, threads, tllist]) module_sdk = declare_dependency(dependencies: [pixman, threads, tllist])
modules = [] modules = []

View file

@ -1,9 +1,10 @@
#pragma once #pragma once
#include <pixman.h> #include <pixman.h>
#include <fcft/fcft.h>
#include "color.h" #include "color.h"
#include "decoration.h" #include "decoration.h"
#include "font.h"
#include "tag.h" #include "tag.h"
struct bar; struct bar;

View file

@ -1,4 +1,4 @@
particle_sdk = declare_dependency(dependencies: [freetype, fontconfig, pixman, tllist]) particle_sdk = declare_dependency(dependencies: [pixman, tllist, fcft])
particles = [] particles = []
foreach particle : ['empty', 'list', 'map', 'progress-bar', 'ramp', 'string'] foreach particle : ['empty', 'list', 'map', 'progress-bar', 'ramp', 'string']

1
subprojects/fcft Submodule

@ -0,0 +1 @@
Subproject commit 69760222af15611216d2e6b38f004d5ad2377651

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)