From ef6f4c2fbf0d4adcb4698db6b54372a3499aeb7c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 7 Dec 2017 09:52:48 +0530 Subject: [PATCH] Improve fontconfig fallback font queries for emoji --- .gitattributes | 1 + count-lines-of-code | 2 +- gen-emoji.py | 75 ++++++++++++ kitty/emoji.h | 287 ++++++++++++++++++++++++++++++++++++++++++++ kitty/fontconfig.c | 8 +- 5 files changed, 369 insertions(+), 4 deletions(-) create mode 100755 gen-emoji.py create mode 100644 kitty/emoji.h diff --git a/.gitattributes b/.gitattributes index 1b5a56bde..aca9cf858 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ kitty/wcwidth9.h linguist-generated=true +kitty/emoji.h linguist-generated=true kitty/keys.h linguist-generated=true kitty/charsets.c linguist-generated=true kitty/key_encoding.py linguist-generated=true diff --git a/count-lines-of-code b/count-lines-of-code index 0d5c4b1a3..2181d9694 100755 --- a/count-lines-of-code +++ b/count-lines-of-code @@ -1,2 +1,2 @@ #!/bin/bash -cloc --exclude-list-file <(echo -e 'kitty/wcwidth9.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c') kitty +cloc --exclude-list-file <(echo -e 'kitty/wcwidth9.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h') kitty diff --git a/gen-emoji.py b/gen-emoji.py new file mode 100755 index 000000000..547cdc07b --- /dev/null +++ b/gen-emoji.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2017, Kovid Goyal + +import os +from collections import defaultdict +from functools import partial +from itertools import groupby +from operator import itemgetter +from urllib.request import urlopen + +os.chdir(os.path.dirname(os.path.abspath(__file__))) + +raw = urlopen('http://unicode.org/Public/emoji/5.0/emoji-data.txt').read().decode('utf-8') +seen = set() +cmap = defaultdict(set) +for line in raw.splitlines(): + line = line.strip() + if not line or line.startswith('#'): + continue + spec, rest = line.partition(';')[::2] + spec, rest = spec.strip(), rest.strip().split(' ', 1)[0].strip() + if '.' in spec: + spec = tuple(map(lambda x: int(x, 16), filter(None, spec.split('.')))) + spec = set(range(spec[0], spec[1] + 1)) + else: + spec = {int(spec, 16)} + cmap[rest] |= spec + seen |= spec +items = list(seen) + + +def get_ranges(items): + items.sort() + for k, g in groupby(enumerate(items), lambda m: m[0]-m[1]): + group = tuple(map(itemgetter(1), g)) + a, b = group[0], group[-1] + if a == b: + yield a + else: + yield a, b + + +def write_case(spec, p): + if isinstance(spec, tuple): + p('\t\tcase 0x{:x} ... 0x{:x}:'.format(*spec)) + else: + p('\t\tcase 0x{:x}:'.format(spec)) + + +with open('kitty/emoji.h', 'w') as f: + p = partial(print, file=f) + p('#pragma once') + p('#include "data-types.h"\n') + p('START_ALLOW_CASE_RANGE') + p('static inline bool is_emoji(uint32_t code) {') + p('\tswitch(code) {') + for spec in get_ranges(items): + last = spec[1] if isinstance(spec, tuple) else spec + if last < 0x231a: + continue + write_case(spec, p) + p('\t\t\treturn true;') + p('\t\tdefault: return false;') + p('\t}') + p('\treturn false; \n}') + p('static inline bool is_emoji_modifier(uint32_t code) {') + p('\tswitch(code) {') + for spec in get_ranges(list(cmap['Emoji_Modifier'])): + write_case(spec, p) + p('\t\t\treturn true;') + p('\t\tdefault: return false;') + p('\t}') + p('\treturn false; \n}') + p('END_ALLOW_CASE_RANGE') diff --git a/kitty/emoji.h b/kitty/emoji.h new file mode 100644 index 000000000..fe76e1164 --- /dev/null +++ b/kitty/emoji.h @@ -0,0 +1,287 @@ +#pragma once +#include "data-types.h" + +START_ALLOW_CASE_RANGE +static inline bool is_emoji(uint32_t code) { + switch(code) { + case 0x231a ... 0x231b: + return true; + case 0x2328: + return true; + case 0x23cf: + return true; + case 0x23e9 ... 0x23f3: + return true; + case 0x23f8 ... 0x23fa: + return true; + case 0x24c2: + return true; + case 0x25aa ... 0x25ab: + return true; + case 0x25b6: + return true; + case 0x25c0: + return true; + case 0x25fb ... 0x25fe: + return true; + case 0x2600 ... 0x2604: + return true; + case 0x260e: + return true; + case 0x2611: + return true; + case 0x2614 ... 0x2615: + return true; + case 0x2618: + return true; + case 0x261d: + return true; + case 0x2620: + return true; + case 0x2622 ... 0x2623: + return true; + case 0x2626: + return true; + case 0x262a: + return true; + case 0x262e ... 0x262f: + return true; + case 0x2638 ... 0x263a: + return true; + case 0x2640: + return true; + case 0x2642: + return true; + case 0x2648 ... 0x2653: + return true; + case 0x2660: + return true; + case 0x2663: + return true; + case 0x2665 ... 0x2666: + return true; + case 0x2668: + return true; + case 0x267b: + return true; + case 0x267f: + return true; + case 0x2692 ... 0x2697: + return true; + case 0x2699: + return true; + case 0x269b ... 0x269c: + return true; + case 0x26a0 ... 0x26a1: + return true; + case 0x26aa ... 0x26ab: + return true; + case 0x26b0 ... 0x26b1: + return true; + case 0x26bd ... 0x26be: + return true; + case 0x26c4 ... 0x26c5: + return true; + case 0x26c8: + return true; + case 0x26ce ... 0x26cf: + return true; + case 0x26d1: + return true; + case 0x26d3 ... 0x26d4: + return true; + case 0x26e9 ... 0x26ea: + return true; + case 0x26f0 ... 0x26f5: + return true; + case 0x26f7 ... 0x26fa: + return true; + case 0x26fd: + return true; + case 0x2702: + return true; + case 0x2705: + return true; + case 0x2708 ... 0x270d: + return true; + case 0x270f: + return true; + case 0x2712: + return true; + case 0x2714: + return true; + case 0x2716: + return true; + case 0x271d: + return true; + case 0x2721: + return true; + case 0x2728: + return true; + case 0x2733 ... 0x2734: + return true; + case 0x2744: + return true; + case 0x2747: + return true; + case 0x274c: + return true; + case 0x274e: + return true; + case 0x2753 ... 0x2755: + return true; + case 0x2757: + return true; + case 0x2763 ... 0x2764: + return true; + case 0x2795 ... 0x2797: + return true; + case 0x27a1: + return true; + case 0x27b0: + return true; + case 0x27bf: + return true; + case 0x2934 ... 0x2935: + return true; + case 0x2b05 ... 0x2b07: + return true; + case 0x2b1b ... 0x2b1c: + return true; + case 0x2b50: + return true; + case 0x2b55: + return true; + case 0x3030: + return true; + case 0x303d: + return true; + case 0x3297: + return true; + case 0x3299: + return true; + case 0x1f004: + return true; + case 0x1f0cf: + return true; + case 0x1f170 ... 0x1f171: + return true; + case 0x1f17e ... 0x1f17f: + return true; + case 0x1f18e: + return true; + case 0x1f191 ... 0x1f19a: + return true; + case 0x1f1e6 ... 0x1f1ff: + return true; + case 0x1f201 ... 0x1f202: + return true; + case 0x1f21a: + return true; + case 0x1f22f: + return true; + case 0x1f232 ... 0x1f23a: + return true; + case 0x1f250 ... 0x1f251: + return true; + case 0x1f300 ... 0x1f321: + return true; + case 0x1f324 ... 0x1f393: + return true; + case 0x1f396 ... 0x1f397: + return true; + case 0x1f399 ... 0x1f39b: + return true; + case 0x1f39e ... 0x1f3f0: + return true; + case 0x1f3f3 ... 0x1f3f5: + return true; + case 0x1f3f7 ... 0x1f4fd: + return true; + case 0x1f4ff ... 0x1f53d: + return true; + case 0x1f549 ... 0x1f54e: + return true; + case 0x1f550 ... 0x1f567: + return true; + case 0x1f56f ... 0x1f570: + return true; + case 0x1f573 ... 0x1f57a: + return true; + case 0x1f587: + return true; + case 0x1f58a ... 0x1f58d: + return true; + case 0x1f590: + return true; + case 0x1f595 ... 0x1f596: + return true; + case 0x1f5a4 ... 0x1f5a5: + return true; + case 0x1f5a8: + return true; + case 0x1f5b1 ... 0x1f5b2: + return true; + case 0x1f5bc: + return true; + case 0x1f5c2 ... 0x1f5c4: + return true; + case 0x1f5d1 ... 0x1f5d3: + return true; + case 0x1f5dc ... 0x1f5de: + return true; + case 0x1f5e1: + return true; + case 0x1f5e3: + return true; + case 0x1f5e8: + return true; + case 0x1f5ef: + return true; + case 0x1f5f3: + return true; + case 0x1f5fa ... 0x1f64f: + return true; + case 0x1f680 ... 0x1f6c5: + return true; + case 0x1f6cb ... 0x1f6d2: + return true; + case 0x1f6e0 ... 0x1f6e5: + return true; + case 0x1f6e9: + return true; + case 0x1f6eb ... 0x1f6ec: + return true; + case 0x1f6f0: + return true; + case 0x1f6f3 ... 0x1f6f8: + return true; + case 0x1f910 ... 0x1f93a: + return true; + case 0x1f93c ... 0x1f93e: + return true; + case 0x1f940 ... 0x1f945: + return true; + case 0x1f947 ... 0x1f94c: + return true; + case 0x1f950 ... 0x1f96b: + return true; + case 0x1f980 ... 0x1f997: + return true; + case 0x1f9c0: + return true; + case 0x1f9d0 ... 0x1f9e6: + return true; + default: return false; + } + return false; +} +static inline bool is_emoji_modifier(uint32_t code) { + switch(code) { + case 0x1f3fb ... 0x1f3ff: + return true; + default: return false; + } + return false; +} +END_ALLOW_CASE_RANGE diff --git a/kitty/fontconfig.c b/kitty/fontconfig.c index ac1906a15..5ae94bdcb 100644 --- a/kitty/fontconfig.c +++ b/kitty/fontconfig.c @@ -9,6 +9,7 @@ #include "lineops.h" #include "fonts.h" #include +#include "emoji.h" #ifndef FC_COLOR #define FC_COLOR "color" #endif @@ -190,9 +191,10 @@ create_fallback_face(PyObject UNUSED *base_face, Cell* cell, bool bold, bool ita PyObject *ans = NULL; FcPattern *pat = FcPatternCreate(); if (pat == NULL) return PyErr_NoMemory(); - AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)"monospace", "family"); - if (bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); } - if (italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); } + bool emoji = is_emoji(cell->ch); + AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)(emoji ? "emoji" : "monospace"), "family"); + if (!emoji && bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); } + if (!emoji && italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); } size_t num = cell_as_unicode(cell, true, char_buf, ' '); add_charset(pat, num); PyObject *d = _fc_match(pat);