This is a fork of https://github.com/swaywm/sway that replaces pango with https://codeberg.org/dnkl/fcft for font rendering. Note: do not use the master branch; use one of the fontconfig branches
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

395 lines
11 KiB

#define _XOPEN_SOURCE 500
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <stdbool.h>
#include <locale.h>
#include <assert.h>
#include <cairo.h>
#include <fcft/fcft.h>
#include "tllist.h"
#include "fthack.h"
#define max(x, y) ((x) > (y) ? (x) : (y))
size_t
escape_markup_text(const char *src, char *dest)
{
strcpy(dest, src);
return strlen(dest);
}
PangoLayout *
get_pango_layout(cairo_t *cairo, const char *font,
const char *text, double scale, bool markup)
{
return NULL;
}
struct cache_entry {
char *name;
struct fcft_font *font;
};
static tll(struct cache_entry) cache = tll_init();
struct text_run_cache_entry {
uint64_t hash;
struct fcft_text_run *run;
int width;
};
static const size_t text_run_cache_size = 2048;
static struct text_run_cache_entry text_run_cache[2048];
static void __attribute__((constructor))
init(void)
{
for (size_t i = 0; i < text_run_cache_size; i++)
text_run_cache[i].hash = (uint64_t)-1L;
}
static void __attribute__((destructor))
fini(void)
{
for (size_t i = 0; i < text_run_cache_size; i++)
fcft_text_run_destroy(text_run_cache[i].run);
tll_foreach(cache, it) {
free(it->item.name);
fcft_destroy(it->item.font);
}
tll_free(cache);
}
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 size_t
hash_index(uint64_t hash)
{
return (hash * 2654435761) & (text_run_cache_size - 1);
}
static struct fcft_font *
get_font(const char *name)
{
tll_foreach(cache, it) {
if (strcmp(it->item.name, name) == 0) {
return it->item.font;
}
}
struct fcft_font *f = fcft_from_name(1, (const char *[]){name}, NULL);
if (f == NULL)
return NULL;
tll_push_front(
cache,
((struct cache_entry){.name = strdup(name), .font = f}));
return f;
}
void
get_text_metrics(const char *font, int *height, int *baseline)
{
struct fcft_font *f = get_font(font);
if (f == NULL) {
if (height != NULL)
*height = 0;
if (baseline != NULL)
*baseline = 0;
}
if (baseline != NULL)
*baseline = f->ascent;
if (height != NULL)
*height = max(f->height, f->ascent + f->descent);
}
void
get_text_size(cairo_t *cairo, const char *font, int *width, int *height,
int *baseline, double scale, bool markup, const char *fmt, ...)
{
struct fcft_font *f = get_font(font);
if (f == NULL) {
if (baseline != NULL)
*baseline = 0;
if (width != NULL)
*width = 0;
if (height != NULL)
*height = 0;
return;
}
int _height = max(f->height, f->ascent + f->descent);
int ascent = f->ascent;
if (height != NULL)
*height = _height;
if (baseline != NULL)
*baseline = ascent;
if (width == NULL)
return;
va_list ap1, ap2;
va_start(ap1, fmt);
va_copy(ap2, ap1);
int len = vsnprintf(NULL, 0, fmt, ap1);
va_end(ap1);
char *text = malloc(len + 1);
vsnprintf(text, len + 1, fmt, ap2);
va_end(ap2);
uint64_t hash = sdbm_hash(text);
size_t hash_idx = hash_index(hash);
struct text_run_cache_entry *cached = &text_run_cache[hash_idx];
if (cached->hash == hash) {
*width = cached->width;
free(text);
return;
}
char *old_locale = setlocale(LC_ALL, NULL);
setlocale(LC_ALL, "");
int wlen = mbstowcs(NULL, text, 0);
wchar_t *wtext = calloc(wlen + 1, sizeof(wtext[0]));
mbstowcs(wtext, text, wlen + 1);
free(text);
setlocale(LC_ALL, old_locale);
*width = 0;
enum fcft_subpixel subpixel = FCFT_SUBPIXEL_DEFAULT;
switch (cairo_get_antialias(cairo)) {
case CAIRO_ANTIALIAS_DEFAULT:
case CAIRO_ANTIALIAS_NONE:
subpixel = FCFT_SUBPIXEL_DEFAULT;
break;
case CAIRO_ANTIALIAS_GRAY:
case CAIRO_ANTIALIAS_FAST:
case CAIRO_ANTIALIAS_GOOD:
case CAIRO_ANTIALIAS_BEST:
subpixel = FCFT_SUBPIXEL_NONE;
break;
case CAIRO_ANTIALIAS_SUBPIXEL: {
cairo_font_options_t *fopts = cairo_font_options_create();
cairo_get_font_options(cairo, fopts);
switch (cairo_font_options_get_subpixel_order(fopts)) {
case CAIRO_SUBPIXEL_ORDER_DEFAULT: subpixel = FCFT_SUBPIXEL_DEFAULT; break;
case CAIRO_SUBPIXEL_ORDER_RGB: subpixel = FCFT_SUBPIXEL_HORIZONTAL_RGB; break;
case CAIRO_SUBPIXEL_ORDER_BGR: subpixel = FCFT_SUBPIXEL_HORIZONTAL_BGR; break;
case CAIRO_SUBPIXEL_ORDER_VRGB: subpixel = FCFT_SUBPIXEL_VERTICAL_RGB; break;
case CAIRO_SUBPIXEL_ORDER_VBGR: subpixel = FCFT_SUBPIXEL_VERTICAL_BGR; break;
}
cairo_font_options_destroy(fopts);
break;
}
}
if (fcft_capabilities() & FCFT_CAPABILITY_TEXT_RUN_SHAPING) {
struct fcft_text_run *run = fcft_text_run_rasterize(f, wcslen(wtext), wtext, subpixel);
if (run != NULL) {
int w = 0;
for (size_t i = 0; i < run->count; i++)
w += run->glyphs[i]->advance.x;
fcft_text_run_destroy(cached->run);
cached->hash = hash;
cached->run = run;
cached->width = w;
*width = w;
free(wtext);
return;
}
}
for (size_t i = 0; i < wcslen(wtext); i++) {
wchar_t wc = wtext[i];
const struct fcft_glyph *glyph = fcft_glyph_rasterize(f, wc, subpixel);
if (glyph == NULL)
continue;
*width += glyph->advance.x;
}
free(wtext);
}
void
pango_printf(cairo_t *cairo, const char *font,
double scale, bool markup, const char *fmt, ...)
{
struct fcft_font *f = get_font(font);
if (f == NULL)
return;
int ascent = f->ascent;
va_list ap1, ap2;
va_start(ap1, fmt);
va_copy(ap2, ap1);
int len = vsnprintf(NULL, 0, fmt, ap1);
va_end(ap1);
char *text = malloc(len + 1);
vsnprintf(text, len + 1, fmt, ap2);
va_end(ap2);
char *old_locale = setlocale(LC_ALL, NULL);
setlocale(LC_ALL, "");
uint64_t hash = sdbm_hash(text);
size_t hash_idx = hash_index(hash);
const struct text_run_cache_entry *cached = &text_run_cache[hash_idx];
const struct fcft_glyph **glyphs = NULL;
const struct fcft_glyph **allocated_glyphs = NULL;
size_t count = 0;
if (cached->hash == hash) {
glyphs = cached->run->glyphs;
count = cached->run->count;
} else {
int wlen = mbstowcs(NULL, text, 0);
wchar_t *wtext = calloc(wlen + 1, sizeof(wtext[0]));
mbstowcs(wtext, text, wlen + 1);
/* Translate cairo's antialias+subpixel settings to fcft */
enum fcft_subpixel subpixel = FCFT_SUBPIXEL_DEFAULT;
switch (cairo_get_antialias(cairo)) {
case CAIRO_ANTIALIAS_DEFAULT:
case CAIRO_ANTIALIAS_NONE:
subpixel = FCFT_SUBPIXEL_DEFAULT;
break;
case CAIRO_ANTIALIAS_GRAY:
case CAIRO_ANTIALIAS_FAST:
case CAIRO_ANTIALIAS_GOOD:
case CAIRO_ANTIALIAS_BEST:
subpixel = FCFT_SUBPIXEL_NONE;
break;
case CAIRO_ANTIALIAS_SUBPIXEL: {
cairo_font_options_t *fopts = cairo_font_options_create();
cairo_get_font_options(cairo, fopts);
switch (cairo_font_options_get_subpixel_order(fopts)) {
case CAIRO_SUBPIXEL_ORDER_DEFAULT: subpixel = FCFT_SUBPIXEL_DEFAULT; break;
case CAIRO_SUBPIXEL_ORDER_RGB: subpixel = FCFT_SUBPIXEL_HORIZONTAL_RGB; break;
case CAIRO_SUBPIXEL_ORDER_BGR: subpixel = FCFT_SUBPIXEL_HORIZONTAL_BGR; break;
case CAIRO_SUBPIXEL_ORDER_VRGB: subpixel = FCFT_SUBPIXEL_VERTICAL_RGB; break;
case CAIRO_SUBPIXEL_ORDER_VBGR: subpixel = FCFT_SUBPIXEL_VERTICAL_BGR; break;
}
cairo_font_options_destroy(fopts);
break;
}
}
allocated_glyphs = malloc(wcslen(wtext) * sizeof(glyphs[0]));
for (size_t i = 0; i < wcslen(wtext); i++) {
const struct fcft_glyph *g = fcft_glyph_rasterize(f, wtext[i], subpixel);
if (g != NULL)
allocated_glyphs[count++] = g;
}
glyphs = allocated_glyphs;
free(wtext);
}
free(text);
setlocale(LC_ALL, old_locale);
cairo_surface_t *surf = cairo_get_target(cairo);
assert(cairo_surface_get_type(surf) == CAIRO_SURFACE_TYPE_IMAGE);
assert(cairo_image_surface_get_format(surf) == CAIRO_FORMAT_ARGB32);
int width = cairo_image_surface_get_width(surf);
int height = cairo_image_surface_get_height(surf);
int stride = cairo_image_surface_get_stride(surf);
cairo_surface_flush(surf);
void *surf_data = cairo_image_surface_get_data(surf);
pixman_image_t *pix = pixman_image_create_bits_no_clear(
PIXMAN_a8r8g8b8, width, height, surf_data, stride);
pixman_region32_t clip;
pixman_region32_init_rect(&clip, 0, 0, width, height);
pixman_image_set_clip_region32(pix, &clip);
pixman_region32_fini(&clip);
double x, y;
cairo_get_current_point(cairo, &x, &y);
cairo_pattern_t *src_pat = cairo_get_source(cairo);
double r, g, b, a;
if (cairo_pattern_get_rgba(src_pat, &r, &g, &b, &a) != CAIRO_STATUS_SUCCESS)
return;
pixman_color_t src_color;
if (a == 0.)
src_color = (pixman_color_t){0, 0, 0, 0};
else {
uint16_t alpha = a * 65535;
int alpha_div = 0xffff / alpha;
src_color = (pixman_color_t){
.red = r * 65535 / alpha_div,
.green = g * 65535 / alpha_div,
.blue = b * 65535 / alpha_div,
.alpha = alpha,
};
}
for (size_t i = 0; i < count; i++) {
const struct fcft_glyph *glyph = glyphs[i];
if (pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) {
/* Glyph surface is a pre-rendered image (typically a color emoji...) */
pixman_image_composite32(
PIXMAN_OP_OVER, glyph->pix, NULL, pix, 0, 0, 0, 0,
x + glyph->x, y + ascent - glyph->y,
glyph->width, glyph->height);
} else {
/* Glyph surface is an alpha mask */
pixman_image_t *src = pixman_image_create_solid_fill(&src_color);
pixman_image_composite32(
PIXMAN_OP_OVER, src, glyph->pix, pix, 0, 0, 0, 0,
x + glyph->x, y + ascent - glyph->y,
glyph->width, glyph->height);
pixman_image_unref(src);
}
x += glyph->advance.x;
}
free(allocated_glyphs);
pixman_image_unref(pix);
cairo_surface_mark_dirty(surf);
}