Home
🪴

On emoji

This blog uses Emoji for favicons, page icons, and when displaying relative links (e.g. have you checked out my post on Topological sort yet?). It's important to me that they are consistent across browsers.
I thought it might be interesting to talk about that code.

From Notion

The Page object in the Notion API provides us with an icon field that can contain either an image (one which I've uploaded to Notion) or an emoji.
{
  object: 'page',
  id: '81c174cf-b876-4ade-81ec-ea6927743413',
  created_time: '2021-11-24T21:10:00.000Z',
  last_edited_time: '2021-11-24T21:13:00.000Z',
  cover: null,
  icon: { type: 'emoji', emoji: '🪴' },
  ...
}
The emoji returned from the API is a unicode character so depending on your browser/OS combo, you may not actually see the potted plant in the JSON object above. Sure enough, though, you will see Apple's version of it in both the favicon of this page and next to the title at the top of the screen. How?

Image files

The emoji-datasource-apple package contains (among other things) 64x64 pixels of all Apple emoji (Twitter and Google emoji are also available and linked from that page). We can list 'em out if we want.
$ ls node_modules/emoji-datasource-apple/img/apple/64 | head
0023-fe0f-20e3.png
002a-fe0f-20e3.png
0030-fe0f-20e3.png
0031-fe0f-20e3.png
0032-fe0f-20e3.png
0033-fe0f-20e3.png
0034-fe0f-20e3.png
0035-fe0f-20e3.png
0036-fe0f-20e3.png
0037-fe0f-20e3.png 
We quickly notice, however, that our filenames consist of hexadecimal numbers. These are the unicode characters which eventually cause our emoji to appear on the screen, written out in base 16. (For more info on this, Monica Dinculescu has a fantastic write-up which includes all the history and corner cases).
To view these characters, we can leverage the emoji-unicode package.
emojiUnicode('🥰')
// => '1f970'
emojiUnicode('🇺🇸')
// => '1f1fa 1f1f8'
And looking once again in our emoji-datasource-apple folder, we can find the 🥰 emoji without issue.
$ ls node_modules/emoji-datasource-apple/img/apple/64 | grep 1f970
1f970.png
The flag is a little harder to find, but sure enough it's there. We just need some dashes. (Exercise for the reader: what might those other 1f1fa's be?)
$ ls node_modules/emoji-datasource-apple/img/apple/64 | grep 1f1fa
1f1e6-1f1fa.png
1f1e8-1f1fa.png
1f1ea-1f1fa.png
1f1ec-1f1fa.png
1f1ed-1f1fa.png
1f1f1-1f1fa.png
1f1f2-1f1fa.png
1f1f3-1f1fa.png
1f1f7-1f1fa.png
1f1fa-1f1e6.png
1f1fa-1f1ec.png
1f1fa-1f1f2.png
1f1fa-1f1f3.png
1f1fa-1f1f8.png    # (we found it)
1f1fa-1f1fe.png
1f1fa-1f1ff.png
1f1fb-1f1fa.png

All together now

We've now determined our code must do the following:
Then we just need to place the file somewhere. The code in its entirety can be found below and on Github.
async function saveFavicon(emoji) {
  const codepoints = emojiUnicode(emoji)
    .split(" ")
    .join("-");
  const basename = `${codepoints}.png`;
  const filename = path.join(
    __dirname,
    "node_modules/emoji-datasource-apple/img/apple/64",
    basename
  );
  if (!fs.existsSync(filename)) {
    console.log(
      "Unknown emoji --",
      emoji,
      codepoints
    );
  }
  const dest = path.join(
    outputDir,
    basename
  );
	// Some light optimization, no need to copy
  // files which already exist in the output
  if (!fs.existsSync(dest)) {
    await fsPromises.copyFile(
      filename,
      dest
    );
  }
  return basename;
}
saveFavicon gives us the filename, which I can happily place in any html template knowing that the image file has safely arrived at its destination.
const headingIcon = icon
  ? `<img
        width="32"
        height="32"
        alt="${icon.emoji}"
        src="${favicon}"
     />`
  : null;
And voilà, emoji!