From 123088b3e29575d5df8a1adaa3fc6ed60efd082a Mon Sep 17 00:00:00 2001 From: Ryan Hitchman Date: Fri, 3 Dec 2021 00:34:16 -0700 Subject: [PATCH] use SVG emoji instead of system defaults, they scale better --- .gitmodules | 3 ++ noto-emoji | 1 + src/app.jsx | 2 +- src/emoji.css | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 1 + src/style.css | 8 ++++ svg_pack.py | 97 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 .gitmodules create mode 160000 noto-emoji create mode 100644 src/emoji.css create mode 100755 svg_pack.py diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b849cc6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "noto-emoji"] + path = noto-emoji + url = git@github.com:googlefonts/noto-emoji.git diff --git a/noto-emoji b/noto-emoji new file mode 160000 index 0000000..9a5261d --- /dev/null +++ b/noto-emoji @@ -0,0 +1 @@ +Subproject commit 9a5261d871451f9b5183c93483cbd68ed916b1e9 diff --git a/src/app.jsx b/src/app.jsx index 97f1469..91a12ff 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -112,7 +112,7 @@ function App() { key={emoji} >
')} +.emoji-E29BA9EFB88F{background-image:url('data:image/svg+xml, ')} +.emoji-E29BB0EFB88F{background-image:url('data:image/svg+xml, ')} +.emoji-E29DA4EFB88F{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8C8B{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8C8E{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8C9D{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8C9E{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8CB4{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8CB5{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8CB7{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8CB8{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8CBB{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8D80{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8DAA{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8DB6{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8DBA{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8EA1{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8EB2{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8F83{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8F94EFB88F{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8F9B{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8F9F{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8FA0{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8FAF{background-image:url('data:image/svg+xml, ')} +.emoji-F09F8FB0{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9081{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9084{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9085{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9086{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9087{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9088{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9089{background-image:url('data:image/svg+xml, ')} +.emoji-F09F908B{background-image:url('data:image/svg+xml, ')} +.emoji-F09F908C{background-image:url('data:image/svg+xml, ')} +.emoji-F09F908E{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9090{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9092{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9093{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9095{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9096{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9098{background-image:url('data:image/svg+xml, ')} +.emoji-F09F909C{background-image:url('data:image/svg+xml, ')} +.emoji-F09F909D{background-image:url('data:image/svg+xml, ')} +.emoji-F09F909E{background-image:url('data:image/svg+xml, ')} +.emoji-F09F90A5{background-image:url('data:image/svg+xml, ')} +.emoji-F09F90A9{background-image:url('data:image/svg+xml, ')} +.emoji-F09F90AB{background-image:url('data:image/svg+xml, ')} +.emoji-F09F918C{background-image:url('data:image/svg+xml,')} +.emoji-F09F9283{background-image:url('data:image/svg+xml, ')} +.emoji-F09F928B{background-image:url('data:image/svg+xml, ')} +.emoji-F09F928C{background-image:url('data:image/svg+xml, ')} +.emoji-F09F928D{background-image:url('data:image/svg+xml, ')} +.emoji-F09F92AA{background-image:url('data:image/svg+xml,')} +.emoji-F09F92B3{background-image:url('data:image/svg+xml, ')} +.emoji-F09F92B6{background-image:url('data:image/svg+xml, ')} +.emoji-F09F92BB{background-image:url('data:image/svg+xml, ')} +.emoji-F09F938E{background-image:url('data:image/svg+xml, ')} +.emoji-F09F938F{background-image:url('data:image/svg+xml, ')} +.emoji-F09F93B1{background-image:url('data:image/svg+xml, ')} +.emoji-F09F93B7{background-image:url('data:image/svg+xml, ')} +.emoji-F09F94A5{background-image:url('data:image/svg+xml, ')} +.emoji-F09F958B{background-image:url('data:image/svg+xml, ')} +.emoji-F09F958C{background-image:url('data:image/svg+xml, ')} +.emoji-F09F958D{background-image:url('data:image/svg+xml, ')} +.emoji-F09F96A5EFB88F{background-image:url('data:image/svg+xml, ')} +.emoji-F09F97BB{background-image:url('data:image/svg+xml, ')} +.emoji-F09F97BC{background-image:url('data:image/svg+xml, ')} +.emoji-F09F97BD{background-image:url('data:image/svg+xml, ')} +.emoji-F09F97BE{background-image:url('data:image/svg+xml, ')} +.emoji-F09F97BF{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9882{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9A82{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9A92{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9A97{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9A9D{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9AAA{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9BB0EFB88F{background-image:url('data:image/svg+xml, ')} +.emoji-F09F9BBB{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA59A{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA686{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA689{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA68C{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA68D{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA68F{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA692{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA695{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA696{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA698{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA699{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA69A{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA69B{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA69F{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA6A3{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA6A7{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA6A9{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA6AC{background-image:url('data:image/svg+xml, ')} +.emoji-F09FA79CE2808DE29980EFB88F{background-image:url('data:image/svg+xml, ')} +.emoji-F09FAA90{background-image:url('data:image/svg+xml, ')} +.emoji-F09FAAB0{background-image:url('data:image/svg+xml, ')} diff --git a/src/index.js b/src/index.js index 08a1d5b..41695ac 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,5 @@ import "./style.css"; +import "./emoji.css"; import React from 'react'; import ReactDOM from 'react-dom'; diff --git a/src/style.css b/src/style.css index 907a9f7..af2bd60 100644 --- a/src/style.css +++ b/src/style.css @@ -56,6 +56,14 @@ a { text-align: center; line-height: 256px; transform-origin: bottom center; + + color: transparent; + margin-bottom: 32px; +} + +.emoji::selection { + color: transparent; + background: grey; } .emoji span { diff --git a/svg_pack.py b/svg_pack.py new file mode 100755 index 0000000..c528c6d --- /dev/null +++ b/svg_pack.py @@ -0,0 +1,97 @@ +#!/usr/bin/python3 + +import csv +import glob +import json +import os +import re +import subprocess +import sys +import unicodedata + + +def munge_svg(path, e=None, verbose=False): + svg_data = open(path).read() + svg_data = svg_data.replace('\n', ' ').strip() + if re.search(r']*width', svg_data): + if 'ENTITY' in svg_data and False: + svg_data = subprocess.check_output( + ['npx', 'svgo', '-o', '-', path]).decode('utf8') + svg_data_before = svg_data + # explicit width/height screws up automatic scaling + dim_re = re.compile(r' (width|height)="[^"]*"') + + def fix_header(m): + attrib = dict(re.findall(r'(\S+)="([^"]*)"', m.group(0))) + if 'viewBox' not in attrib: + attrib['viewBox'] = '0 0 %s %s' % (attrib['width'], + attrib['height']) + attrib.pop('width') + attrib.pop('height') + return '' % ' '.join('%s="%s"' % i for i in attrib.items()) + + svg_data = re.sub(r']*>', fix_header, svg_data) + if e and verbose: + print('stripping header dimensions from', e['char'], e['name'], + svg_data == svg_data_before) + return svg_data + + +def pack_svg(verbose=False): + ems = [] + for fname in glob.glob('src/data/*.csv'): + ems.extend(csv.reader(open(fname))) + + print(ems) + + char_ems = {e[0]: e for e in ems} + svgs = {} + + ems.sort(key=lambda e: float(e[1]) if e[1].isdigit() else 0) + + aliases = {} + for line in open('noto-emoji/emoji_aliases.txt'): + m = re.match(r'(.*);(\S*)', line) + if m: + aliases[m.group(1)] = m.group(2) + + c = 0 + for e in ems: + em, size, name = e + em = unicodedata.normalize('NFKD', em) + if em == 'EMOJI' or size == '?': + continue + svg_path = '' + em_code = '_'.join('%04x' % ord(c) for c in em) + svg_path = 'noto-emoji/svg/emoji_u%s.svg' % em_code + print(svg_path) + if not os.path.exists(svg_path) and em_code in aliases: + svg_path = svg_path.replace(em_code, aliases[em_code]) + if not os.path.exists(svg_path) and '_fe0f.' in svg_path: + svg_path = svg_path.replace('_fe0f.', '.') + assert os.path.exists(svg_path), (em, svg_path) + svg_data = munge_svg(svg_path, e, verbose) + if verbose: + print(em, e['name'], json.dumps(em), svg_path, len(svg_data)) + svgs[em] = svg_data + + with open('src/emoji.css', 'w') as f: + for char, svg in sorted(svgs.items()): + # based on https://yoksel.github.io/url-encoder/ + # and https://mathiasbynens.be/notes/css-escapes + svg = svg.strip().replace("'", "\\'") + svg = svg.replace('\n', '\\A').replace('#', '%23') + f.write( + ".emoji-%s{background-image:url('data:image/svg+xml,%s')}\n" % + (''.join('%02X'%c for c in char_ems[char][0].encode('utf8')), svg)) + size = f.tell() + print('wrote %.1dK (%d emoji, %dB each) to %s' % + (size / 1024, len(svgs), size / len(svgs), f.name)) + + +if __name__ == '__main__': + if sys.argv[1:]: + for fname in sys.argv[1:]: + print(fname, munge_svg(fname)) + else: + pack_svg()