2019-06-13 16:24:35 +02:00
|
|
|
#include "font.h"
|
|
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
2019-07-30 18:04:28 +02:00
|
|
|
#include <stdint.h>
|
2019-06-13 16:24:35 +02:00
|
|
|
#include <stdbool.h>
|
2019-07-29 20:10:55 +02:00
|
|
|
#include <wchar.h>
|
2019-09-29 13:03:48 +02:00
|
|
|
#include <math.h>
|
2019-07-17 22:50:26 +02:00
|
|
|
#include <assert.h>
|
2019-07-30 18:04:28 +02:00
|
|
|
#include <threads.h>
|
2019-06-13 16:24:35 +02:00
|
|
|
|
2019-09-29 13:03:48 +02:00
|
|
|
#include <freetype/tttables.h>
|
|
|
|
|
|
2019-06-13 16:24:35 +02:00
|
|
|
#define LOG_MODULE "font"
|
2019-07-30 18:04:28 +02:00
|
|
|
#define LOG_ENABLE_DBG 0
|
2019-06-13 16:24:35 +02:00
|
|
|
#include "log.h"
|
2019-08-18 17:59:43 +02:00
|
|
|
#include "stride.h"
|
2019-06-13 16:24:35 +02:00
|
|
|
|
2019-07-29 20:10:55 +02:00
|
|
|
#define min(x, y) ((x) < (y) ? (x) : (y))
|
|
|
|
|
|
2019-07-28 12:09:22 +02:00
|
|
|
static FT_Library ft_lib;
|
2019-07-30 18:04:28 +02:00
|
|
|
static mtx_t ft_lock;
|
|
|
|
|
|
2019-11-30 12:41:09 +01:00
|
|
|
static const size_t glyph_cache_size = 512;
|
2019-07-28 12:09:22 +02:00
|
|
|
|
2019-11-01 20:40:42 +01:00
|
|
|
struct font_cache_entry {
|
|
|
|
|
uint64_t hash;
|
|
|
|
|
struct font *font;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static tll(struct font_cache_entry) font_cache = tll_init();
|
|
|
|
|
|
2019-06-13 16:24:35 +02:00
|
|
|
static void __attribute__((constructor))
|
|
|
|
|
init(void)
|
|
|
|
|
{
|
|
|
|
|
FcInit();
|
2019-07-28 12:09:22 +02:00
|
|
|
FT_Init_FreeType(&ft_lib);
|
2019-07-30 18:04:28 +02:00
|
|
|
mtx_init(&ft_lock, mtx_plain);
|
2019-06-13 16:24:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void __attribute__((destructor))
|
|
|
|
|
fini(void)
|
|
|
|
|
{
|
2019-11-01 20:40:42 +01:00
|
|
|
while (tll_length(font_cache) > 0)
|
2019-11-30 12:39:18 +01:00
|
|
|
font_destroy(tll_pop_front(font_cache).font);
|
2019-11-01 20:40:42 +01:00
|
|
|
|
2019-07-30 18:04:28 +02:00
|
|
|
mtx_destroy(&ft_lock);
|
2019-07-28 12:09:22 +02:00
|
|
|
FT_Done_FreeType(ft_lib);
|
2019-07-30 18:04:28 +02:00
|
|
|
FcFini();
|
2019-06-13 16:24:35 +02:00
|
|
|
}
|
2019-07-28 12:09:22 +02:00
|
|
|
|
2019-11-30 12:35:07 +01:00
|
|
|
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";
|
|
|
|
|
}
|
2019-09-29 13:03:48 +02:00
|
|
|
static void
|
|
|
|
|
underline_strikeout_metrics(struct font *font)
|
|
|
|
|
{
|
|
|
|
|
FT_Face ft_face = font->face;
|
2019-11-30 13:10:20 +01:00
|
|
|
double y_scale = ft_face->size->metrics.y_scale / 65536.;
|
2019-11-26 18:56:12 +01:00
|
|
|
double height = ft_face->size->metrics.height / 64.;
|
|
|
|
|
double descent = ft_face->size->metrics.descender / 64.;
|
2019-09-29 13:03:48 +02:00
|
|
|
|
2019-11-26 18:56:12 +01:00
|
|
|
LOG_DBG("ft: y-scale: %f, height: %f, descent: %f",
|
|
|
|
|
y_scale, height, descent);
|
2019-09-29 13:03:48 +02:00
|
|
|
|
2019-11-30 14:53:22 +01:00
|
|
|
font->underline.position = ft_face->underline_position * y_scale / 64.;
|
|
|
|
|
font->underline.thickness = ft_face->underline_thickness * y_scale / 64.;
|
2019-09-29 13:03:48 +02:00
|
|
|
|
|
|
|
|
if (font->underline.position == 0.) {
|
2019-11-30 14:53:22 +01:00
|
|
|
font->underline.position = descent / 2.;
|
|
|
|
|
font->underline.thickness = descent / 5.;
|
2019-09-29 13:03:48 +02:00
|
|
|
}
|
|
|
|
|
|
2019-11-30 14:53:22 +01:00
|
|
|
LOG_DBG("underline: pos=%f, thick=%f",
|
2019-09-29 13:03:48 +02:00
|
|
|
font->underline.position, font->underline.thickness);
|
|
|
|
|
|
|
|
|
|
TT_OS2 *os2 = FT_Get_Sfnt_Table(ft_face, ft_sfnt_os2);
|
|
|
|
|
if (os2 != NULL) {
|
2019-11-30 14:53:22 +01:00
|
|
|
font->strikeout.position = os2->yStrikeoutPosition * y_scale / 64.;
|
|
|
|
|
font->strikeout.thickness = os2->yStrikeoutSize * y_scale / 64.;
|
2019-09-29 13:03:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (font->strikeout.position == 0.) {
|
2019-11-30 14:53:22 +01:00
|
|
|
font->strikeout.position = height / 2. + descent;
|
2019-09-29 13:03:48 +02:00
|
|
|
font->strikeout.thickness = font->underline.thickness;
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-30 14:53:22 +01:00
|
|
|
LOG_DBG("strikeout: pos=%f, thick=%f",
|
2019-09-29 13:03:48 +02:00
|
|
|
font->strikeout.position, font->strikeout.thickness);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-30 18:04:28 +02:00
|
|
|
static bool
|
2019-10-17 17:43:40 +02:00
|
|
|
from_font_set(FcPattern *pattern, FcFontSet *fonts, int start_idx,
|
|
|
|
|
struct font *font, bool is_fallback)
|
2019-06-13 16:24:35 +02:00
|
|
|
{
|
2019-07-28 12:09:22 +02:00
|
|
|
memset(font, 0, sizeof(*font));
|
|
|
|
|
|
2019-08-10 20:34:22 +02:00
|
|
|
FcChar8 *face_file = NULL;
|
|
|
|
|
FcPattern *final_pattern = NULL;
|
|
|
|
|
int font_idx = -1;
|
2019-07-30 18:04:28 +02:00
|
|
|
|
2019-08-10 20:34:22 +02:00
|
|
|
for (int i = start_idx; i < fonts->nfont; i++) {
|
|
|
|
|
FcPattern *pat = FcFontRenderPrepare(NULL, pattern, fonts->fonts[i]);
|
|
|
|
|
assert(pat != NULL);
|
2019-07-30 18:04:28 +02:00
|
|
|
|
2019-08-10 20:34:22 +02:00
|
|
|
if (FcPatternGetString(pat, FC_FT_FACE, 0, &face_file) != FcResultMatch) {
|
|
|
|
|
if (FcPatternGetString(pat, FC_FILE, 0, &face_file) != FcResultMatch) {
|
|
|
|
|
FcPatternDestroy(pat);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-06-13 16:24:35 +02:00
|
|
|
|
2019-08-10 20:34:22 +02:00
|
|
|
final_pattern = pat;
|
|
|
|
|
font_idx = i;
|
|
|
|
|
break;
|
2019-06-13 16:24:35 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-10 20:34:22 +02:00
|
|
|
assert(font_idx != -1);
|
|
|
|
|
assert(final_pattern != NULL);
|
2019-06-13 16:24:35 +02:00
|
|
|
|
2019-07-28 12:09:22 +02:00
|
|
|
double dpi;
|
|
|
|
|
if (FcPatternGetDouble(final_pattern, FC_DPI, 0, &dpi) != FcResultMatch)
|
2019-11-26 18:57:18 +01:00
|
|
|
dpi = 75;
|
2019-07-28 12:09:22 +02:00
|
|
|
|
|
|
|
|
double size;
|
2019-11-26 19:00:21 +01:00
|
|
|
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);
|
2019-07-28 12:09:22 +02:00
|
|
|
FcPatternDestroy(final_pattern);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-30 18:04:28 +02:00
|
|
|
mtx_lock(&ft_lock);
|
2019-07-28 12:09:22 +02:00
|
|
|
FT_Face ft_face;
|
|
|
|
|
FT_Error ft_err = FT_New_Face(ft_lib, (const char *)face_file, 0, &ft_face);
|
2019-07-30 18:04:28 +02:00
|
|
|
mtx_unlock(&ft_lock);
|
2019-11-25 20:17:15 +01:00
|
|
|
if (ft_err != 0) {
|
2019-07-28 12:09:22 +02:00
|
|
|
LOG_ERR("%s: failed to create FreeType face", face_file);
|
2019-11-25 20:17:15 +01:00
|
|
|
FcPatternDestroy(final_pattern);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2019-07-28 12:09:22 +02:00
|
|
|
|
2019-11-26 19:00:21 +01:00
|
|
|
if ((ft_err = FT_Set_Pixel_Sizes(ft_face, 0, pixel_size)) != 0) {
|
2019-11-25 20:14:48 +01:00
|
|
|
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;
|
|
|
|
|
}
|
2019-07-28 12:09:22 +02:00
|
|
|
|
2019-11-26 19:00:21 +01:00
|
|
|
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.;
|
2019-11-28 21:32:28 +01:00
|
|
|
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) {
|
2019-11-30 12:38:54 +01:00
|
|
|
double requested_pixel_size;
|
|
|
|
|
if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &requested_pixel_size) != FcResultMatch) {
|
2019-11-26 19:00:21 +01:00
|
|
|
/* User didn't specify ":pixelsize=xy" */
|
2019-11-30 12:38:54 +01:00
|
|
|
double requested_size;
|
|
|
|
|
if (FcPatternGetDouble(pattern, FC_SIZE, 0, &requested_size) != FcResultMatch) {
|
2019-11-26 19:00:21 +01:00
|
|
|
/* User didn't specify ":size=xy" */
|
2019-11-30 12:38:54 +01:00
|
|
|
requested_size = size;
|
2019-11-26 19:00:21 +01:00
|
|
|
}
|
|
|
|
|
|
2019-11-30 12:38:54 +01:00
|
|
|
requested_pixel_size = size * dpi / 72;
|
2019-11-26 19:00:21 +01:00
|
|
|
}
|
|
|
|
|
|
2019-11-30 12:38:54 +01:00
|
|
|
pixel_fixup = requested_pixel_size / ft_face->size->metrics.y_ppem;
|
2019-11-26 19:00:21 +01:00
|
|
|
LOG_DBG("estimated pixel fixup factor to %f (from pixel size: %f)",
|
2019-11-30 12:38:54 +01:00
|
|
|
pixel_fixup, requested_pixel_size);
|
2019-11-28 21:32:28 +01:00
|
|
|
} else
|
2019-11-28 21:33:45 +01:00
|
|
|
pixel_fixup = 1.;
|
2019-11-26 19:00:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#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
|
|
|
|
|
|
2019-07-29 20:10:55 +02:00
|
|
|
FcBool fc_hinting;
|
2019-07-18 10:03:08 +02:00
|
|
|
if (FcPatternGetBool(final_pattern, FC_HINTING,0, &fc_hinting) != FcResultMatch)
|
2019-07-17 22:50:26 +02:00
|
|
|
fc_hinting = FcTrue;
|
|
|
|
|
|
2019-07-29 20:10:55 +02:00
|
|
|
FcBool fc_antialias;
|
2019-07-18 10:03:08 +02:00
|
|
|
if (FcPatternGetBool(final_pattern, FC_ANTIALIAS, 0, &fc_antialias) != FcResultMatch)
|
2019-07-17 22:50:26 +02:00
|
|
|
fc_antialias = FcTrue;
|
|
|
|
|
|
2019-07-29 20:10:55 +02:00
|
|
|
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;
|
2019-08-19 17:45:21 +02:00
|
|
|
else if (fc_rgba == FC_RGBA_RGB || fc_rgba == FC_RGBA_BGR)
|
2019-08-18 17:40:57 +02:00
|
|
|
load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_LCD;
|
2019-08-19 17:45:21 +02:00
|
|
|
else if (fc_rgba == FC_RGBA_VRGB || fc_rgba == FC_RGBA_VBGR)
|
2019-08-18 17:40:57 +02:00
|
|
|
load_flags |= FT_LOAD_DEFAULT | FT_LOAD_TARGET_LCD_V;
|
|
|
|
|
else
|
2019-07-29 20:10:55 +02:00
|
|
|
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 {
|
2019-08-19 17:45:21 +02:00
|
|
|
if (fc_rgba == FC_RGBA_RGB || fc_rgba == FC_RGBA_BGR)
|
2019-08-18 17:40:57 +02:00
|
|
|
render_flags |= FT_RENDER_MODE_LCD;
|
2019-08-19 17:45:21 +02:00
|
|
|
else if (fc_rgba == FC_RGBA_VRGB || fc_rgba == FC_RGBA_VBGR)
|
2019-08-18 17:40:57 +02:00
|
|
|
render_flags |= FT_RENDER_MODE_LCD_V;
|
|
|
|
|
else
|
2019-07-29 20:10:55 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-17 17:43:40 +02:00
|
|
|
font->name = strdup((char *)face_file);
|
2019-06-13 16:24:35 +02:00
|
|
|
FcPatternDestroy(final_pattern);
|
|
|
|
|
|
2019-07-29 20:10:55 +02:00
|
|
|
mtx_init(&font->lock, mtx_plain);
|
2019-07-28 12:09:22 +02:00
|
|
|
font->face = ft_face;
|
font: initial support for double-width *and* color emoji glyphs
Fonts are now loaded with FT_LOAD_COLOR and we recognize and support
the FT_PIXEL_MODE_BGRA pixel mode.
This is mapped to a CAIRO_FORMAT_ARGB32 surface, that is blitted
as-is (instead of used as a mask like we do for gray and mono glyphs).
Furthermore, since many emojis are double-width, we add initial
support for double-width glyphs.
These are assumed to always be utf8. When PRINT:ing an utf8 character,
we check its width, and add empty "spacer" cells after the cell with
the multi-column glyph.
When rendering, we render the columns in each row backwards. This
ensures the spacer cells get cleared *before* we render the glyph (so
that we don't end up erasing part of the glyph).
Finally, emoji fonts are usually bitmap fonts with *large*
glyphs. These aren't automatically scaled down. I.e. even if we
request a glyph of 13 pixels, we might end up getting a 100px glyph.
To handle this, fontconfig must be configured to scale bitmap
fonts. When it is, we can look at the 'scalable' and 'pixelsizefixup'
properties, and use these to scale the rendered glyph.
2019-07-31 18:03:35 +02:00
|
|
|
font->load_flags = load_flags | FT_LOAD_COLOR;
|
2019-07-29 20:10:55 +02:00
|
|
|
font->render_flags = render_flags;
|
2019-07-30 18:04:28 +02:00
|
|
|
font->is_fallback = is_fallback;
|
2019-11-26 19:00:21 +01:00
|
|
|
font->pixel_size_fixup = pixel_fixup;
|
2019-08-19 17:45:21 +02:00
|
|
|
font->bgr = fc_rgba == FC_RGBA_BGR || fc_rgba == FC_RGBA_VBGR;
|
2019-10-16 22:34:23 +02:00
|
|
|
font->ref_counter = 1;
|
2019-10-17 17:07:37 +02:00
|
|
|
font->fc_idx = font_idx;
|
2019-07-30 18:04:28 +02:00
|
|
|
|
2019-10-16 22:18:57 +02:00
|
|
|
if (is_fallback) {
|
|
|
|
|
font->fc_pattern = NULL;
|
|
|
|
|
font->fc_fonts = NULL;
|
2019-10-17 17:06:42 +02:00
|
|
|
font->fc_loaded_fallbacks = NULL;
|
2019-10-17 17:12:04 +02:00
|
|
|
font->glyph_cache = NULL;
|
2019-10-16 22:18:57 +02:00
|
|
|
} else {
|
|
|
|
|
font->fc_pattern = !is_fallback ? pattern : NULL;
|
|
|
|
|
font->fc_fonts = !is_fallback ? fonts : NULL;
|
2019-10-17 17:06:42 +02:00
|
|
|
font->fc_loaded_fallbacks = calloc(
|
|
|
|
|
fonts->nfont, sizeof(font->fc_loaded_fallbacks[0]));
|
2019-11-30 12:41:09 +01:00
|
|
|
font->glyph_cache = calloc(glyph_cache_size, sizeof(font->glyph_cache[0]));
|
2019-08-10 21:10:56 +02:00
|
|
|
}
|
|
|
|
|
|
2019-11-26 18:54:32 +01:00
|
|
|
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);
|
|
|
|
|
|
2019-09-29 13:03:48 +02:00
|
|
|
underline_strikeout_metrics(font);
|
2019-08-10 20:34:22 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-16 22:22:26 +02:00
|
|
|
static struct font *
|
2019-10-17 17:43:40 +02:00
|
|
|
from_name(const char *name, bool is_fallback)
|
2019-08-10 20:34:22 +02:00
|
|
|
{
|
|
|
|
|
LOG_DBG("instantiating %s", name);
|
|
|
|
|
|
|
|
|
|
FcPattern *pattern = FcNameParse((const unsigned char *)name);
|
|
|
|
|
if (pattern == NULL) {
|
|
|
|
|
LOG_ERR("%s: failed to lookup font", name);
|
2019-10-16 22:22:26 +02:00
|
|
|
return NULL;
|
2019-08-10 20:34:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!FcConfigSubstitute(NULL, pattern, FcMatchPattern)) {
|
|
|
|
|
LOG_ERR("%s: failed to do config substitution", name);
|
|
|
|
|
FcPatternDestroy(pattern);
|
2019-10-16 22:22:26 +02:00
|
|
|
return NULL;
|
2019-08-10 20:34:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2019-10-16 22:22:26 +02:00
|
|
|
return NULL;
|
2019-08-10 20:34:22 +02:00
|
|
|
}
|
|
|
|
|
|
2019-10-16 22:22:26 +02:00
|
|
|
struct font *font = malloc(sizeof(*font));
|
|
|
|
|
|
2019-10-17 17:43:40 +02:00
|
|
|
if (!from_font_set(pattern, fonts, 0, font, is_fallback)) {
|
2019-10-16 22:22:26 +02:00
|
|
|
free(font);
|
2019-08-10 20:34:22 +02:00
|
|
|
FcFontSetDestroy(fonts);
|
|
|
|
|
FcPatternDestroy(pattern);
|
2019-10-16 22:22:26 +02:00
|
|
|
return NULL;
|
2019-08-10 20:34:22 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-10 21:10:56 +02:00
|
|
|
if (is_fallback) {
|
2019-08-10 20:34:22 +02:00
|
|
|
FcFontSetDestroy(fonts);
|
|
|
|
|
FcPatternDestroy(pattern);
|
|
|
|
|
}
|
2019-07-30 18:04:28 +02:00
|
|
|
|
2019-10-16 22:22:26 +02:00
|
|
|
return font;
|
2019-06-13 16:24:35 +02:00
|
|
|
}
|
2019-07-28 20:37:59 +02:00
|
|
|
|
2019-11-01 20:40:42 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-16 21:52:12 +02:00
|
|
|
struct font *
|
|
|
|
|
font_from_name(font_list_t names, const char *attributes)
|
2019-07-28 20:37:59 +02:00
|
|
|
{
|
2019-07-30 18:04:28 +02:00
|
|
|
if (tll_length(names) == 0)
|
2019-07-28 20:37:59 +02:00
|
|
|
return false;
|
2019-08-10 20:34:22 +02:00
|
|
|
|
2019-11-01 20:40:42 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-17 17:53:03 +02:00
|
|
|
struct font *font = NULL;
|
2019-10-17 17:43:40 +02:00
|
|
|
|
|
|
|
|
bool have_attrs = attributes != NULL && strlen(attributes) > 0;
|
|
|
|
|
size_t attr_len = have_attrs ? strlen(attributes) + 1 : 0;
|
|
|
|
|
|
|
|
|
|
bool first = true;
|
2019-07-30 18:04:28 +02:00
|
|
|
tll_foreach(names, it) {
|
2019-10-17 17:43:40 +02:00
|
|
|
const char *base_name = it->item;
|
|
|
|
|
|
2019-10-17 17:53:03 +02:00
|
|
|
char name[strlen(base_name) + attr_len + 1];
|
|
|
|
|
strcpy(name, base_name);
|
2019-10-17 17:43:40 +02:00
|
|
|
if (have_attrs) {
|
2019-10-17 17:53:03 +02:00
|
|
|
strcat(name, ":");
|
|
|
|
|
strcat(name, attributes);
|
2019-07-30 18:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
2019-10-17 17:43:40 +02:00
|
|
|
if (first) {
|
|
|
|
|
first = false;
|
2019-10-17 17:53:03 +02:00
|
|
|
|
|
|
|
|
font = from_name(name, false);
|
|
|
|
|
if (font == NULL)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
2019-10-17 17:43:40 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2019-07-29 20:10:55 +02:00
|
|
|
|
2019-10-17 17:53:03 +02:00
|
|
|
assert(font != NULL);
|
|
|
|
|
tll_push_back(
|
|
|
|
|
font->fallbacks, ((struct font_fallback){.pattern = strdup(name)}));
|
2019-10-17 17:43:40 +02:00
|
|
|
}
|
2019-10-16 21:52:12 +02:00
|
|
|
|
2019-11-01 20:40:42 +01:00
|
|
|
tll_push_back(font_cache, ((struct font_cache_entry){.hash = hash, .font = font}));
|
2019-10-17 17:53:03 +02:00
|
|
|
return font;
|
2019-07-30 18:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static size_t
|
|
|
|
|
hash_index(wchar_t wc)
|
|
|
|
|
{
|
2019-11-30 12:41:09 +01:00
|
|
|
return wc % glyph_cache_size;
|
2019-07-30 18:04:28 +02:00
|
|
|
}
|
2019-07-29 20:10:55 +02:00
|
|
|
|
2019-07-30 18:04:28 +02:00
|
|
|
static bool
|
2019-08-19 17:45:21 +02:00
|
|
|
glyph_for_wchar(const struct font *font, wchar_t wc, struct glyph *glyph)
|
2019-07-30 18:04:28 +02:00
|
|
|
{
|
2019-10-18 19:50:48 +02:00
|
|
|
*glyph = (struct glyph){
|
|
|
|
|
.wc = wc,
|
|
|
|
|
.valid = false,
|
|
|
|
|
};
|
|
|
|
|
|
2019-07-29 20:10:55 +02:00
|
|
|
/*
|
|
|
|
|
* LCD filter is per library instance. Thus we need to re-set it
|
|
|
|
|
* every time...
|
2019-08-18 17:40:57 +02:00
|
|
|
*
|
2019-07-29 20:10:55 +02:00
|
|
|
* 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);
|
2019-11-30 12:35:07 +01:00
|
|
|
if (err != 0 && err != FT_Err_Unimplemented_Feature) {
|
|
|
|
|
LOG_ERR("failed to set LCD filter: %s", ft_error_string(err));
|
2019-07-29 20:10:55 +02:00
|
|
|
goto err;
|
2019-11-30 12:35:07 +01:00
|
|
|
}
|
2019-07-28 20:37:59 +02:00
|
|
|
|
|
|
|
|
FT_UInt idx = FT_Get_Char_Index(font->face, wc);
|
2019-07-30 18:04:28 +02:00
|
|
|
if (idx == 0) {
|
2019-08-08 17:57:58 +02:00
|
|
|
/* No glyph in this font, try fallback fonts */
|
2019-08-10 20:34:22 +02:00
|
|
|
|
2019-07-30 18:04:28 +02:00
|
|
|
tll_foreach(font->fallbacks, it) {
|
2019-10-17 17:53:03 +02:00
|
|
|
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)) {
|
2019-11-26 19:00:21 +01:00
|
|
|
LOG_DBG("%C: used fallback: %s", wc, it->item.font->name);
|
2019-10-16 22:22:26 +02:00
|
|
|
return true;
|
2019-07-30 18:04:28 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (font->is_fallback)
|
|
|
|
|
return false;
|
2019-08-08 17:57:58 +02:00
|
|
|
|
2019-08-10 20:34:22 +02:00
|
|
|
/* Try fontconfig fallback fonts */
|
|
|
|
|
|
|
|
|
|
assert(font->fc_pattern != NULL);
|
|
|
|
|
assert(font->fc_fonts != NULL);
|
2019-10-17 17:06:42 +02:00
|
|
|
assert(font->fc_loaded_fallbacks != NULL);
|
2019-08-10 20:34:22 +02:00
|
|
|
assert(font->fc_idx != -1);
|
|
|
|
|
|
|
|
|
|
for (int i = font->fc_idx + 1; i < font->fc_fonts->nfont; i++) {
|
2019-10-17 17:06:42 +02:00
|
|
|
if (font->fc_loaded_fallbacks[i] == NULL) {
|
2019-10-16 22:18:57 +02:00
|
|
|
/* Load font */
|
|
|
|
|
struct font *fallback = malloc(sizeof(*fallback));
|
2019-10-17 17:43:40 +02:00
|
|
|
if (!from_font_set(font->fc_pattern, font->fc_fonts, i, fallback, true))
|
2019-10-16 22:18:57 +02:00
|
|
|
{
|
|
|
|
|
LOG_WARN("failed to load fontconfig fallback font");
|
|
|
|
|
free(fallback);
|
|
|
|
|
continue;
|
2019-08-10 20:34:22 +02:00
|
|
|
}
|
|
|
|
|
|
2019-10-16 22:18:57 +02:00
|
|
|
LOG_DBG("loaded new fontconfig fallback font");
|
2019-10-17 17:07:37 +02:00
|
|
|
assert(fallback->fc_idx >= i);
|
|
|
|
|
|
|
|
|
|
i = fallback->fc_idx;
|
2019-10-17 17:06:42 +02:00
|
|
|
font->fc_loaded_fallbacks[i] = fallback;
|
2019-10-16 22:18:57 +02:00
|
|
|
}
|
|
|
|
|
|
2019-10-17 17:06:42 +02:00
|
|
|
assert(font->fc_loaded_fallbacks[i] != NULL);
|
2019-10-16 22:18:57 +02:00
|
|
|
|
2019-10-17 17:06:42 +02:00
|
|
|
if (glyph_for_wchar(font->fc_loaded_fallbacks[i], wc, glyph)) {
|
2019-10-17 17:43:40 +02:00
|
|
|
LOG_DBG("%C: used fontconfig fallback: %s",
|
|
|
|
|
wc, font->fc_loaded_fallbacks[i]->name);
|
2019-10-16 22:18:57 +02:00
|
|
|
return true;
|
2019-08-10 20:34:22 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-20 10:23:17 +01:00
|
|
|
LOG_DBG("%C: no glyph found (in neither the main font, "
|
|
|
|
|
"nor any fallback fonts)", wc);
|
2019-07-30 18:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-29 20:10:55 +02:00
|
|
|
err = FT_Load_Glyph(font->face, idx, font->load_flags);
|
|
|
|
|
if (err != 0) {
|
2019-11-30 12:35:07 +01:00
|
|
|
LOG_ERR("%s: failed to load glyph #%d: %s",
|
|
|
|
|
font->name, idx, ft_error_string(err));
|
2019-07-29 20:10:55 +02:00
|
|
|
goto err;
|
|
|
|
|
}
|
2019-07-28 20:37:59 +02:00
|
|
|
|
2019-07-29 20:10:55 +02:00
|
|
|
err = FT_Render_Glyph(font->face->glyph, font->render_flags);
|
2019-11-30 12:35:07 +01:00
|
|
|
if (err != 0) {
|
|
|
|
|
LOG_ERR("%s: failed to render glyph: %s", font->name, ft_error_string(err));
|
2019-07-29 20:10:55 +02:00
|
|
|
goto err;
|
2019-11-30 12:35:07 +01:00
|
|
|
}
|
2019-07-28 20:37:59 +02:00
|
|
|
|
2019-07-28 21:03:38 +02:00
|
|
|
assert(font->face->glyph->format == FT_GLYPH_FORMAT_BITMAP);
|
|
|
|
|
|
2019-07-28 20:37:59 +02:00
|
|
|
FT_Bitmap *bitmap = &font->face->glyph->bitmap;
|
2019-08-13 20:44:30 +02:00
|
|
|
if (bitmap->width == 0)
|
|
|
|
|
goto err;
|
|
|
|
|
|
2019-08-18 17:40:57 +02:00
|
|
|
pixman_format_code_t pix_format;
|
|
|
|
|
int width;
|
|
|
|
|
int rows;
|
2019-08-16 20:40:32 +02:00
|
|
|
|
2019-08-18 17:40:57 +02:00
|
|
|
switch (bitmap->pixel_mode) {
|
|
|
|
|
case FT_PIXEL_MODE_MONO:
|
|
|
|
|
pix_format = PIXMAN_a1;
|
|
|
|
|
width = bitmap->width;
|
|
|
|
|
rows = bitmap->rows;
|
|
|
|
|
break;
|
2019-08-16 20:40:32 +02:00
|
|
|
|
2019-08-18 17:40:57 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-18 17:59:43 +02:00
|
|
|
int stride = stride_for_format_and_width(pix_format, width);
|
2019-07-28 20:37:59 +02:00
|
|
|
assert(stride >= bitmap->pitch);
|
|
|
|
|
|
2019-08-18 17:40:57 +02:00
|
|
|
uint8_t *data = malloc(rows * stride);
|
2019-07-28 20:37:59 +02:00
|
|
|
|
2019-08-16 22:11:22 +02:00
|
|
|
/* Convert FT bitmap to pixman image */
|
2019-07-28 20:37:59 +02:00
|
|
|
switch (bitmap->pixel_mode) {
|
|
|
|
|
case FT_PIXEL_MODE_MONO:
|
|
|
|
|
for (size_t r = 0; r < bitmap->rows; r++) {
|
2019-07-29 20:10:55 +02:00
|
|
|
for (size_t c = 0; c < (bitmap->width + 7) / 8; c++) {
|
2019-07-28 20:37:59 +02:00
|
|
|
uint8_t v = bitmap->buffer[r * bitmap->pitch + c];
|
|
|
|
|
uint8_t reversed = 0;
|
2019-07-29 20:10:55 +02:00
|
|
|
for (size_t i = 0; i < min(8, bitmap->width - c * 8); i++)
|
|
|
|
|
reversed |= ((v >> (7 - i)) & 1) << i;
|
|
|
|
|
|
2019-07-28 20:37:59 +02:00
|
|
|
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;
|
|
|
|
|
|
font: initial support for double-width *and* color emoji glyphs
Fonts are now loaded with FT_LOAD_COLOR and we recognize and support
the FT_PIXEL_MODE_BGRA pixel mode.
This is mapped to a CAIRO_FORMAT_ARGB32 surface, that is blitted
as-is (instead of used as a mask like we do for gray and mono glyphs).
Furthermore, since many emojis are double-width, we add initial
support for double-width glyphs.
These are assumed to always be utf8. When PRINT:ing an utf8 character,
we check its width, and add empty "spacer" cells after the cell with
the multi-column glyph.
When rendering, we render the columns in each row backwards. This
ensures the spacer cells get cleared *before* we render the glyph (so
that we don't end up erasing part of the glyph).
Finally, emoji fonts are usually bitmap fonts with *large*
glyphs. These aren't automatically scaled down. I.e. even if we
request a glyph of 13 pixels, we might end up getting a 100px glyph.
To handle this, fontconfig must be configured to scale bitmap
fonts. When it is, we can look at the 'scalable' and 'pixelsizefixup'
properties, and use these to scale the rendered glyph.
2019-07-31 18:03:35 +02:00
|
|
|
case FT_PIXEL_MODE_BGRA:
|
|
|
|
|
assert(stride == bitmap->pitch);
|
|
|
|
|
memcpy(data, bitmap->buffer, bitmap->rows * bitmap->pitch);
|
|
|
|
|
break;
|
|
|
|
|
|
2019-08-18 17:40:57 +02:00
|
|
|
case FT_PIXEL_MODE_LCD:
|
|
|
|
|
for (size_t r = 0; r < bitmap->rows; r++) {
|
|
|
|
|
for (size_t c = 0; c < bitmap->width; c += 3) {
|
2019-08-19 17:45:21 +02:00
|
|
|
unsigned char _r = bitmap->buffer[r * bitmap->pitch + c + (font->bgr ? 2 : 0)];
|
2019-08-18 17:40:57 +02:00
|
|
|
unsigned char _g = bitmap->buffer[r * bitmap->pitch + c + 1];
|
2019-08-19 17:45:21 +02:00
|
|
|
unsigned char _b = bitmap->buffer[r * bitmap->pitch + c + (font->bgr ? 0 : 2)];
|
2019-08-18 17:40:57 +02:00
|
|
|
|
|
|
|
|
uint32_t *p = (uint32_t *)&data[r * stride + 4 * (c / 3)];
|
|
|
|
|
*p = _r << 16 | _g << 8 | _b;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
2019-08-19 17:45:21 +02:00
|
|
|
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;
|
|
|
|
|
|
2019-07-28 20:37:59 +02:00
|
|
|
default:
|
2019-08-18 17:40:57 +02:00
|
|
|
abort();
|
|
|
|
|
break;
|
2019-07-28 20:37:59 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-16 20:40:32 +02:00
|
|
|
pixman_image_t *pix = pixman_image_create_bits_no_clear(
|
2019-08-18 17:40:57 +02:00
|
|
|
pix_format, width, rows, (uint32_t *)data, stride);
|
2019-07-28 20:37:59 +02:00
|
|
|
|
2019-08-16 20:40:32 +02:00
|
|
|
if (pix == NULL) {
|
2019-07-28 20:37:59 +02:00
|
|
|
free(data);
|
2019-07-29 20:10:55 +02:00
|
|
|
goto err;
|
2019-07-28 20:37:59 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-18 17:40:57 +02:00
|
|
|
pixman_image_set_component_alpha(
|
|
|
|
|
pix,
|
|
|
|
|
bitmap->pixel_mode == FT_PIXEL_MODE_LCD ||
|
|
|
|
|
bitmap->pixel_mode == FT_PIXEL_MODE_LCD_V);
|
|
|
|
|
|
2019-11-26 19:00:21 +01:00
|
|
|
|
2019-08-16 20:40:32 +02:00
|
|
|
if (font->pixel_size_fixup != 1.) {
|
2019-11-27 21:33:08 +01:00
|
|
|
struct pixman_transform scale;
|
|
|
|
|
pixman_transform_init_identity(&scale);
|
2019-11-26 19:00:21 +01:00
|
|
|
pixman_transform_scale(
|
2019-11-27 21:33:08 +01:00
|
|
|
&scale, NULL,
|
2019-11-26 19:00:21 +01:00
|
|
|
pixman_double_to_fixed(1.0 / font->pixel_size_fixup),
|
|
|
|
|
pixman_double_to_fixed(1.0 / font->pixel_size_fixup));
|
2019-11-27 21:33:08 +01:00
|
|
|
pixman_image_set_transform(pix, &scale);
|
|
|
|
|
pixman_image_set_filter(pix, PIXMAN_FILTER_BEST, NULL, 0);
|
2019-08-16 20:40:32 +02:00
|
|
|
}
|
|
|
|
|
|
2019-11-30 00:15:54 +01:00
|
|
|
int cols = wcwidth(wc);
|
|
|
|
|
if (cols < 0)
|
|
|
|
|
cols = 0;
|
2019-11-26 19:00:21 +01:00
|
|
|
|
2019-07-28 20:37:59 +02:00
|
|
|
*glyph = (struct glyph){
|
2019-07-30 18:04:28 +02:00
|
|
|
.wc = wc,
|
2019-11-30 00:15:54 +01:00
|
|
|
.cols = cols,
|
2019-08-16 20:40:32 +02:00
|
|
|
.pix = pix,
|
2019-11-26 19:00:21 +01:00
|
|
|
.x = font->face->glyph->bitmap_left * font->pixel_size_fixup,
|
2019-08-16 20:40:32 +02:00
|
|
|
.y = font->face->glyph->bitmap_top * font->pixel_size_fixup,
|
2019-08-18 17:40:57 +02:00
|
|
|
.width = width,
|
|
|
|
|
.height = rows,
|
2019-08-02 18:19:07 +02:00
|
|
|
.valid = true,
|
2019-07-28 20:37:59 +02:00
|
|
|
};
|
2019-07-30 18:04:28 +02:00
|
|
|
|
2019-07-28 20:37:59 +02:00
|
|
|
return true;
|
2019-07-29 20:10:55 +02:00
|
|
|
|
|
|
|
|
err:
|
2019-10-18 19:50:48 +02:00
|
|
|
assert(!glyph->valid);
|
2019-07-29 20:10:55 +02:00
|
|
|
return false;
|
2019-07-28 20:37:59 +02:00
|
|
|
}
|
2019-07-28 21:03:38 +02:00
|
|
|
|
2019-07-30 18:04:28 +02:00
|
|
|
const struct glyph *
|
2019-08-02 18:19:07 +02:00
|
|
|
font_glyph_for_wc(struct font *font, wchar_t wc)
|
2019-07-30 18:04:28 +02:00
|
|
|
{
|
|
|
|
|
mtx_lock(&font->lock);
|
|
|
|
|
|
2019-10-17 17:12:04 +02:00
|
|
|
assert(font->glyph_cache != NULL);
|
2019-07-30 18:04:28 +02:00
|
|
|
size_t hash_idx = hash_index(wc);
|
2019-10-17 17:12:04 +02:00
|
|
|
hash_entry_t *hash_entry = font->glyph_cache[hash_idx];
|
2019-07-30 18:04:28 +02:00
|
|
|
|
|
|
|
|
if (hash_entry != NULL) {
|
|
|
|
|
tll_foreach(*hash_entry, it) {
|
|
|
|
|
if (it->item.wc == wc) {
|
|
|
|
|
mtx_unlock(&font->lock);
|
2019-08-13 20:41:21 +02:00
|
|
|
return it->item.valid ? &it->item : NULL;
|
2019-07-30 18:04:28 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct glyph glyph;
|
2019-08-02 18:19:07 +02:00
|
|
|
bool got_glyph = glyph_for_wchar(font, wc, &glyph);
|
2019-07-30 18:04:28 +02:00
|
|
|
|
|
|
|
|
if (hash_entry == NULL) {
|
|
|
|
|
hash_entry = calloc(1, sizeof(*hash_entry));
|
|
|
|
|
|
2019-10-17 17:12:04 +02:00
|
|
|
assert(font->glyph_cache[hash_idx] == NULL);
|
|
|
|
|
font->glyph_cache[hash_idx] = hash_entry;
|
2019-07-30 18:04:28 +02:00
|
|
|
}
|
2019-08-13 20:44:30 +02:00
|
|
|
|
2019-07-30 18:04:28 +02:00
|
|
|
assert(hash_entry != NULL);
|
|
|
|
|
tll_push_back(*hash_entry, glyph);
|
|
|
|
|
|
|
|
|
|
mtx_unlock(&font->lock);
|
2019-08-02 18:19:07 +02:00
|
|
|
return got_glyph ? &tll_back(*hash_entry) : NULL;
|
2019-07-30 18:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
2019-07-28 21:03:38 +02:00
|
|
|
void
|
|
|
|
|
font_destroy(struct font *font)
|
|
|
|
|
{
|
2019-10-16 22:18:57 +02:00
|
|
|
if (font == NULL)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-10-16 22:34:23 +02:00
|
|
|
if (--font->ref_counter > 0)
|
|
|
|
|
return;
|
|
|
|
|
|
2019-11-01 20:40:42 +01:00
|
|
|
tll_foreach(font_cache, it) {
|
|
|
|
|
if (it->item.font == font) {
|
|
|
|
|
tll_remove(font_cache, it);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-17 17:43:40 +02:00
|
|
|
free(font->name);
|
2019-10-17 17:53:03 +02:00
|
|
|
|
|
|
|
|
tll_foreach(font->fallbacks, it) {
|
|
|
|
|
font_destroy(it->item.font);
|
|
|
|
|
free(it->item.pattern);
|
|
|
|
|
}
|
|
|
|
|
tll_free(font->fallbacks);
|
2019-07-30 18:04:28 +02:00
|
|
|
|
|
|
|
|
if (font->face != NULL) {
|
|
|
|
|
mtx_lock(&ft_lock);
|
2019-07-28 21:03:38 +02:00
|
|
|
FT_Done_Face(font->face);
|
2019-07-30 18:04:28 +02:00
|
|
|
mtx_unlock(&ft_lock);
|
|
|
|
|
}
|
2019-07-28 21:03:38 +02:00
|
|
|
|
font: initial support for double-width *and* color emoji glyphs
Fonts are now loaded with FT_LOAD_COLOR and we recognize and support
the FT_PIXEL_MODE_BGRA pixel mode.
This is mapped to a CAIRO_FORMAT_ARGB32 surface, that is blitted
as-is (instead of used as a mask like we do for gray and mono glyphs).
Furthermore, since many emojis are double-width, we add initial
support for double-width glyphs.
These are assumed to always be utf8. When PRINT:ing an utf8 character,
we check its width, and add empty "spacer" cells after the cell with
the multi-column glyph.
When rendering, we render the columns in each row backwards. This
ensures the spacer cells get cleared *before* we render the glyph (so
that we don't end up erasing part of the glyph).
Finally, emoji fonts are usually bitmap fonts with *large*
glyphs. These aren't automatically scaled down. I.e. even if we
request a glyph of 13 pixels, we might end up getting a 100px glyph.
To handle this, fontconfig must be configured to scale bitmap
fonts. When it is, we can look at the 'scalable' and 'pixelsizefixup'
properties, and use these to scale the rendered glyph.
2019-07-31 18:03:35 +02:00
|
|
|
mtx_destroy(&font->lock);
|
2019-07-30 18:04:28 +02:00
|
|
|
|
2019-10-16 22:18:57 +02:00
|
|
|
if (font->fc_fonts != NULL) {
|
2019-10-17 17:06:42 +02:00
|
|
|
assert(font->fc_loaded_fallbacks != NULL);
|
2019-10-16 22:18:57 +02:00
|
|
|
|
|
|
|
|
for (size_t i = 0; i < font->fc_fonts->nfont; i++)
|
2019-10-17 17:06:42 +02:00
|
|
|
font_destroy(font->fc_loaded_fallbacks[i]);
|
2019-10-16 22:18:57 +02:00
|
|
|
|
2019-10-17 17:06:42 +02:00
|
|
|
free(font->fc_loaded_fallbacks);
|
2019-10-16 22:18:57 +02:00
|
|
|
}
|
|
|
|
|
|
2019-08-10 20:34:22 +02:00
|
|
|
if (font->fc_pattern != NULL)
|
|
|
|
|
FcPatternDestroy(font->fc_pattern);
|
|
|
|
|
if (font->fc_fonts != NULL)
|
|
|
|
|
FcFontSetDestroy(font->fc_fonts);
|
|
|
|
|
|
2019-07-30 18:04:28 +02:00
|
|
|
|
2019-11-30 12:41:09 +01:00
|
|
|
for (size_t i = 0; i < glyph_cache_size && font->glyph_cache != NULL; i++) {
|
2019-10-17 17:12:04 +02:00
|
|
|
if (font->glyph_cache[i] == NULL)
|
font: initial support for double-width *and* color emoji glyphs
Fonts are now loaded with FT_LOAD_COLOR and we recognize and support
the FT_PIXEL_MODE_BGRA pixel mode.
This is mapped to a CAIRO_FORMAT_ARGB32 surface, that is blitted
as-is (instead of used as a mask like we do for gray and mono glyphs).
Furthermore, since many emojis are double-width, we add initial
support for double-width glyphs.
These are assumed to always be utf8. When PRINT:ing an utf8 character,
we check its width, and add empty "spacer" cells after the cell with
the multi-column glyph.
When rendering, we render the columns in each row backwards. This
ensures the spacer cells get cleared *before* we render the glyph (so
that we don't end up erasing part of the glyph).
Finally, emoji fonts are usually bitmap fonts with *large*
glyphs. These aren't automatically scaled down. I.e. even if we
request a glyph of 13 pixels, we might end up getting a 100px glyph.
To handle this, fontconfig must be configured to scale bitmap
fonts. When it is, we can look at the 'scalable' and 'pixelsizefixup'
properties, and use these to scale the rendered glyph.
2019-07-31 18:03:35 +02:00
|
|
|
continue;
|
|
|
|
|
|
2019-10-17 17:12:04 +02:00
|
|
|
tll_foreach(*font->glyph_cache[i], it) {
|
2019-08-13 20:41:21 +02:00
|
|
|
if (!it->item.valid)
|
|
|
|
|
continue;
|
|
|
|
|
|
2019-08-16 20:40:32 +02:00
|
|
|
void *image = pixman_image_get_data(it->item.pix);
|
|
|
|
|
pixman_image_unref(it->item.pix);
|
font: initial support for double-width *and* color emoji glyphs
Fonts are now loaded with FT_LOAD_COLOR and we recognize and support
the FT_PIXEL_MODE_BGRA pixel mode.
This is mapped to a CAIRO_FORMAT_ARGB32 surface, that is blitted
as-is (instead of used as a mask like we do for gray and mono glyphs).
Furthermore, since many emojis are double-width, we add initial
support for double-width glyphs.
These are assumed to always be utf8. When PRINT:ing an utf8 character,
we check its width, and add empty "spacer" cells after the cell with
the multi-column glyph.
When rendering, we render the columns in each row backwards. This
ensures the spacer cells get cleared *before* we render the glyph (so
that we don't end up erasing part of the glyph).
Finally, emoji fonts are usually bitmap fonts with *large*
glyphs. These aren't automatically scaled down. I.e. even if we
request a glyph of 13 pixels, we might end up getting a 100px glyph.
To handle this, fontconfig must be configured to scale bitmap
fonts. When it is, we can look at the 'scalable' and 'pixelsizefixup'
properties, and use these to scale the rendered glyph.
2019-07-31 18:03:35 +02:00
|
|
|
free(image);
|
2019-07-30 18:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
2019-10-17 17:12:04 +02:00
|
|
|
tll_free(*font->glyph_cache[i]);
|
|
|
|
|
free(font->glyph_cache[i]);
|
2019-07-28 21:03:38 +02:00
|
|
|
}
|
2019-10-17 17:12:04 +02:00
|
|
|
free(font->glyph_cache);
|
2019-10-16 22:18:57 +02:00
|
|
|
free(font);
|
2019-07-28 21:03:38 +02:00
|
|
|
}
|