Created
June 22, 2021 13:12
-
-
Save teidesu/d23866ed94d0274e8cd117f00a16b465 to your computer and use it in GitHub Desktop.
Revisions
-
teidesu created this gist
Jun 22, 2021 .There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,238 @@ /** * Emoji sprite and data generator. * * Data is taken from frankerfacez.com and name->char index is generated. * * Sprite generator works by parsing some of the code in DrKLO/Telegram (Telegram for Android) * and downloading emoji files contained there, while generating code and sprite. * * Can easily be modified to generate JSON instead of CSS or to download from some other source. * Can also be easily ported to TypeScript * * (c) teidesu 2020. This script is licensed under MIT */ const fetch = require('node-fetch') const { createWriteStream, existsSync, readFileSync, writeFileSync, mkdirSync } = require('fs') const { createCanvas, loadImage } = require('canvas') function extractCodeBlock (str, start = 0) { let i = start const stack = [] const closers = { ')': '(', ']': '[', '}': '{', } const openers = { '(': ')', '[': ']', '{': '}', } let ret = '' let inString = '' let inStringEscaped = false do { let chr = str[i] if (!inString) { if (chr === '\'' || chr === '"' || chr === '`') { inString = chr } if (openers[chr]) { stack.push(chr) } else if (closers[chr]) { if (stack[stack.length - 1] !== closers[chr]) { throw TypeError('Malformed code, expected ' + closers[stack[stack.length - 1]] + ' at position ' + i) } stack.pop() } } else { if (!inStringEscaped) { if (chr === inString) { inString = false } if (chr === '\\') { inStringEscaped = true } } else { inStringEscaped = false } } i++ if (stack.length > 0) { ret += chr } else if (ret !== '') { ret += chr break // first block code ended } } while (i < str.length) if (stack.length > 0) { throw TypeError('Malformed code, expected ' + openers[stack.pop()] + ' at position ' + i) } return { block: ret, end: i } } const toCharCode = (s) => { let ret = [] for (let i = 0; i < s.length; i++) { ret.push(s.charCodeAt(i).toString(16)) } return ret.join('_') } async function createEmojisFile () { let data = await fetch('https://cdn.frankerfacez.com/static/emoji/v3.2.json').then(i => i.json()) let result = { names: [], symbols: {}, } for (let it of data.e) { let names = it[2] let value = String.fromCodePoint(...it[4].split('-').map(i => parseInt(i, 16))) if (!Array.isArray(names)) names = [names] names.forEach((s) => { if (!result.symbols[s]) { result.symbols[s] = toCharCode(value) result.names.push(s) } }) } writeFileSync('emoji.json', JSON.stringify(result)) console.log('[v] Written emoji.json (%d entries)', result.names.length) } const EMOJI_CATEGORIES = [ 'faces', 'nature', 'food', 'activity', 'transport', 'objects', 'symbols', 'flags', ] // as const // export type EmojiCategory = typeof EMOJI_CATEGORIES[number] // type EmojiData = Record<EmojiCategory, string[]> async function createEmojisDataFile () { let shittyDrkloJavaCode = await fetch( 'https://raw.githubusercontent.com/DrKLO/Telegram/master/TMessagesProj/src/main/java/org/telegram/messenger/EmojiData.java', ).then(i => i.text()) let dataBlockStart = shittyDrkloJavaCode.match(/public static final String\[]\[] data = {/) if (!dataBlockStart) throw new Error('could not find data block') let { block: dataBlock } = extractCodeBlock( shittyDrkloJavaCode, dataBlockStart.index + dataBlockStart[0].length - 1, ) dataBlock = dataBlock.substring(1, dataBlock.length - 1).trim() let dataBlockPos = 0 let data = [] while (dataBlockPos < dataBlock.length) { try { let { block, end } = extractCodeBlock(dataBlock, dataBlockPos) dataBlockPos = end if (block === '[]') continue data.push(JSON.parse('[' + block.substring(1, block.length - 1) + ']')) } catch (e) { break } } let result = {} data.forEach((it, i) => result[EMOJI_CATEGORIES[i]] = it.map(toCharCode)) writeFileSync('emoji-data.json', JSON.stringify(result)) } async function downloadEmojiIfNeeded (name) { let path = `emoji/${name}.png` if (existsSync(path)) return loadImage(path) console.log('[i] downloading emoji %s', name) let output = createWriteStream(path) let res = await fetch(`https://raw.githubusercontent.com/DrKLO/Telegram/master/TMessagesProj/src/main/assets/emoji/${name}.png`) let pipe = res.body.pipe(output) await new Promise((res, rej) => { pipe.on('finish', res) pipe.on('error', rej) }) return loadImage(path) } const SPRITE_SIZE = 20 const SPRITE_PER_ROW = 50 const SPRITE_MAX_X = SPRITE_PER_ROW - 1 async function createEmojisSpriteAndCss () { if (!existsSync('emoji')) mkdirSync('emoji') if (!existsSync('emoji-data.json')) { console.log('[i] generating emoji-data.json') await createEmojisDataFile() } let emojiData = JSON.parse(readFileSync('emoji-data.json').toString('utf-8')) let totalEmojiCount = Object.values(emojiData).reduce((a, b) => a + b.length, 0) let x = 0 let y = 0 let width = SPRITE_SIZE * SPRITE_PER_ROW let height = SPRITE_SIZE * Math.ceil(totalEmojiCount / SPRITE_PER_ROW) // modify here to change output format const outputCss = createWriteStream('emoji.css') outputCss.write(` /* THIS FILE IS AUTO-GENERATED! */ .emojione { font-size: inherit; height: 20px; width: 20px; display: inline-block; line-height: normal; vertical-align: top; background-image: url(emoji.png); background-repeat: no-repeat; background-size: ${width}px ${height}px; } `.trim()) const canvas = createCanvas(width, height) const ctx = canvas.getContext('2d') for (let categoryId = 0; categoryId < EMOJI_CATEGORIES.length; categoryId++) { const categoryName = EMOJI_CATEGORIES[categoryId] const categoryEmojis = emojiData[categoryName] for (let emojiId = 0; emojiId < categoryEmojis.length; emojiId++) { const emojiCharacter = categoryEmojis[emojiId] const fullEmojiId = `${categoryId}_${emojiId}` const image = await downloadEmojiIfNeeded(fullEmojiId) ctx.drawImage(image, SPRITE_SIZE * x, SPRITE_SIZE * y, SPRITE_SIZE, SPRITE_SIZE) // modify here to change output format outputCss.write( `.emojione-${emojiCharacter}{background-position:${-SPRITE_SIZE * x}px ${-SPRITE_SIZE * y}px}`, ) x += 1 if (x === SPRITE_MAX_X) { y += 1 x = 0 } } } const pngStream = canvas.createPNGStream() const outputSpriteSheet = createWriteStream('emoji.png') await new Promise(res => pngStream.pipe(outputSpriteSheet).on('finish', res)) console.log('[v] generated emoji sprite sheet') } if (require.main === module) { createEmojisSpriteAndCss().catch(console.error) }