Packages

Emoji encoding swiss army knife for dealing with Unicode and other gotchas.

Current section

Files

Jump to
exmoji lib exmoji.ex
Raw

lib/exmoji.ex

defmodule Exmoji do
@moduledoc """
An Elixir/Erlang library providing low level operations for dealing with Emoji
glyphs in the Unicode standard.
Exmoji is like a swiss-army knife for dealing with Emoji encoding issues. If
all you need to do is translate `:poop:` into a smiling poop glyph, then there
are plenty of other libs out there that will probably do what you want. But
once you are dealing with Emoji as a fundamental part of your application, and
you start to realize the nightmare of doublebyte encoding or variants, then
this library may be your new best friend.
"""
alias Exmoji.EmojiChar
#
# Read and parse the Emoji library from our vendored data file.
#
vendor_data_file = "lib/vendor/emoji-data/emoji.json"
@external_resource vendor_data_file
rawfile = File.read!(vendor_data_file)
rawdata = Jason.decode!(rawfile, keys: :atoms)
emoji_chars =
for char <- rawdata do
%EmojiChar{
name: char.name,
unified: char.unified,
variations: char.variations,
short_name: char.short_name,
short_names: char.short_names,
text: char.text
}
end
@emoji_chars emoji_chars
@doc """
Returns a list of all #{Enum.count(@emoji_chars)} Emoji characters as `EmojiChar`.
"""
def all, do: @emoji_chars
@doc """
Returns a list of all `EmojiChar` that are represented as doublebyte encoding.
"""
@all_doublebyte_cache Enum.filter(@emoji_chars, &EmojiChar.doublebyte?/1)
def all_doublebyte, do: @all_doublebyte_cache
@doc """
Returns a list of all `EmojiChar` that have at least one variant encoding.
"""
@all_variant_cache Enum.filter(@emoji_chars, &EmojiChar.variant?/1)
def all_with_variants, do: @all_variant_cache
@doc """
Returns a list of all known Emoji characters rendered as Unicode bitstrings.
By default, the default rendering options for this library will be used.
However, if you pass the option of `include_variants: true` then all possible
renderings of a single glyph will be included, meaning that:
1. You will have "duplicate" emojis in your list.
2. This list is now suitable for exhaustably matching against in a search.
"""
def chars(opts \\ []) do
case Keyword.get(opts, :include_variants, false) do
false ->
Enum.map(@emoji_chars, &EmojiChar.render/1)
true ->
Enum.map(@emoji_chars, &EmojiChar.chars/1)
|> List.flatten()
end
end
@doc """
Returns a list of all known codepoints representing Emoji characters.
This function also accepts the `include_variants` option, for details on its
significance, see similar discussion for the `Exmoji.chars/1` function.
"""
def codepoints(opts \\ []) do
case Keyword.get(opts, :include_variants, false) do
false ->
Enum.map(@emoji_chars, & &1.unified)
true ->
Enum.map(@emoji_chars, &EmojiChar.codepoint_ids/1)
|> List.flatten()
end
end
@doc """
Finds any `EmojiChar` that contains given string in its official name.
## Examples
iex> Exmoji.find_by_name "father"
[%Exmoji.EmojiChar{name: "FATHER CHRISTMAS", short_name: "santa",
short_names: ["santa"], text: nil, unified: "1F385", variations: []}]
iex> for t <- Exmoji.find_by_name("tree"), do: t.name
["EVERGREEN TREE", "DECIDUOUS TREE", "PALM TREE", "CHRISTMAS TREE",
"TANABATA TREE"]
"""
def find_by_name(name) do
name = String.upcase(name)
Enum.filter(@emoji_chars, &String.contains?(&1.name, name))
end
@doc """
Find all `EmojiChar` that match substring in any of their associated short
name keywords.
"""
def find_by_short_name(sname) do
target = String.downcase(sname)
Enum.filter(@emoji_chars, &matches_short_name(&1, target))
end
defp matches_short_name(%EmojiChar{} = ec, target) do
Enum.any?(ec.short_names, &String.contains?(&1, target))
end
@doc """
Finds an `EmojiChar` based on its short name keyword.
Case insensitive. Otherwise must match exactly. Do not include the `:colon:`
identifiers if you are parsing text that uses them to indicate the presence of
a keyword.
"""
def from_short_name(sname) do
sname |> String.downcase() |> _from_short_name
end
for ec <- @emoji_chars, sn <- ec.short_names do
defp _from_short_name(unquote(sn)), do: unquote(Macro.escape(ec))
end
defp _from_short_name(_), do: nil
@doc """
Finds a specific `EmojiChar` based on the unified codepoint ID.
"""
def from_unified(uid) do
uid |> String.upcase() |> _from_unified
end
for ec <- @emoji_chars, cp <- EmojiChar.codepoint_ids(ec) do
defp _from_unified(unquote(cp)), do: unquote(Macro.escape(ec))
end
defp _from_unified(_), do: nil
@doc """
Convert a unified ID directly to its bitstring glyph representation.
## Example
iex> Exmoji.unified_to_char("1F47E")
"👾"
"""
def unified_to_char(uid) do
uid
|> String.split("-")
|> Enum.map(&String.to_integer(&1, 16))
|> List.to_string()
end
@doc """
Convert a native bitstring glyph to its unified codepoint ID.
This is a conversion operation, not a match, so it may produce unexpected
results with different types of values.
## Examples
iex> Exmoji.char_to_unified("👾")
"1F47E"
iex> Exmoji.char_to_unified("\x23\u{fe0f}\u{20e3}")
"0023-FE0F-20E3"
"""
def char_to_unified(char) do
char
|> String.codepoints()
|> Enum.map(&padded_hex_string/1)
|> Enum.join("-")
|> String.upcase()
end
# produce a string representation of the integer value of a codepoint, in hex
# this should be zero-padded to a minimum of 4 digits
defp padded_hex_string(<<cp_int_value::utf8>>) do
cp_int_value |> Integer.to_string(16) |> String.pad_leading(4, "0")
end
end