How EmojiEmoji
A Japanese word (็ตตๆๅญ) meaning 'picture character' โ small graphical symbols used in digital communication to express ideas, emotions, and objects. Fonts Work
Standard OpenTypeOpenType
A font format developed by Microsoft and Adobe that supports color emoji rendering through multiple color table technologies. fonts store glyphs as monochrome vector outlines. Emoji fonts break this model completely โ they need full-color, high-detail images at multiple sizes. Four different technical approaches have emerged to solve this problem, each with different trade-offs in quality, scalability, and platform compatibility.
The Core Challenge
A regular font glyph is a set of Bรฉzier curves colored by the application. An emoji glyph needs:
- Full RGB (or RGBA) color information
- Multiple levels of detail at different sizes
- Complex compositions (e.g., skin tones, hair colors)
- Support for sequences of multiple code points
OpenType addresses this with four color font specifications, each stored in dedicated font tables.
COLR/CPALCOLR/CPAL (COLR)
OpenType color font tables that define emoji as layered vector shapes with a color palette, used by Windows and Chrome.: Layered Vector Glyphs
Used by: Segoe UI Emoji (Windows), Noto Color EmojiColor Emoji
Full-color emoji rendered using bitmap images or color vector graphics, as opposed to monochrome text-style rendering. (recent versions), TwemojiTwemoji
An open-source emoji set originally created by Twitter, providing SVG and PNG emoji assets that can be used in any project. Mozilla
COLR (Color Table) and CPAL (Color Palette Table) work together. A COLR glyph is composed of multiple layers, each a monochrome glyph from the font's regular outline table, filled with a color from the CPAL palette.
COLRv0 (Original)
The original COLR format (v0) supports flat fills only โ no gradients, no composite modes.
Glyph "๐" decomposition in COLRv0:
Layer 1: circle_outline โ color[0] = #FFCC33 (yellow)
Layer 2: eyes_outline โ color[1] = #000000 (black)
Layer 3: smile_outline โ color[2] = #000000 (black)
Layer 4: cheeks_outline โ color[3] = #FF9966 (orange-pink)
COLRv1 (2021)
COLRv1 extends COLR with gradients, composite modes, variable fonts, and parametric color palettes. This enables:
- Smooth gradients (linear, radial, sweep)
- Blend modes (screen, multiply, etc.)
- Font variation axes (including color axes)
# Inspect a COLR font with fonttools
pip install fonttools
from fontTools.ttLib import TTFont
font = TTFont("NotoColorEmoji-Regular.ttf")
# Check for COLR table
if "COLR" in font:
colr = font["COLR"]
print(f"COLR version: {colr.version}")
# List glyphs with color definitions
if colr.version == 1:
for glyph_name in list(colr.table.BaseGlyphList.BaseGlyphPaintRecord)[:5]:
print(glyph_name)
Advantages of COLR
- Scalable: Vector-based, renders sharply at any size
- Small file size: Shares glyph outlines across variations
- Variable font compatible: Supports
wght,ital, and custom axes - COLRv1: Supported in Chrome 98+, Firefox 105+, macOS Ventura+
CBDT/CBLCCBDT/CBLC (CBDT)
Color Bitmap Data Table and Color Bitmap Location Table โ OpenType tables for embedding bitmap color emoji in fonts.: Bitmap EmojiBitmap Emoji
Emoji rendered as pixel-based images at fixed resolutions, stored as PNG or similar raster formats within font files.
Used by: Noto Color Emoji (older versions), Android emoji fontEmoji Font
A digital font file containing color emoji glyph designs, using technologies like COLR, CBDT, SVG, or sbix for rendering.
CBDT (Color Bitmap Data Table) stores PNG or JPEG images directly in the font file. CBLC (Color Bitmap Location Table) provides an index of bitmap locations at different sizes.
CBLC structure:
Size 16ร16 โ offset โ CBDT PNG data
Size 32ร32 โ offset โ CBDT PNG data
Size 64ร64 โ offset โ CBDT PNG data
Size 128ร128 โ offset โ CBDT PNG data
Inspecting CBDT with fonttools
from fontTools.ttLib import TTFont
import io
from PIL import Image
font = TTFont("NotoColorEmoji.ttf")
# Extract the 128px bitmap for ๐
cblc = font["CBLC"]
cbdt = font["CBDT"]
# Find the glyph name for U+1F600
glyph_name = font.getBestCmap()[0x1F600] # "emoji_u1f600"
# Access strike data (128px size)
for strike in cblc.strikes:
if strike.bitmapSizeTable.ppemX == 128:
if glyph_name in strike.indexSubTables[0].names:
# Extract PNG bytes from CBDT
bitmap_data = cbdt.strikeData[strike][glyph_name]
img = Image.open(io.BytesIO(bitmap_data.data))
img.save("grinning_face_128.png")
break
Disadvantages of CBDT
- Large file size: Noto Color Emoji is ~10MB because it stores PNGs at multiple sizes
- Fixed resolution: Looks blurry at sizes not pre-baked into the font
- No variable font support: Cannot animate or vary parameters
sbix: Apple's Bitmap Approach
Used by: Apple Color Emoji (macOS, iOS)
The sbix (Standard Bitmap Graphics) table is Apple's proprietary format. It stores PNG (or TIFF, JPEG) images per glyph, per size strike, similar to CBDT but with Apple-specific extensions.
sbix strikes (Apple Color Emoji):
20px, 32px, 40px, 48px, 64px, 96px, 160px
Each size contains PNG data per emoji glyph
Apple Color Emoji is not publicly distributable (it is part of macOS/iOS). You can inspect it on a Mac:
# Location of Apple Color Emoji on macOS
ls /System/Library/Fonts/Apple\ Color\ Emoji.ttc
# Extract info with fonttools
python3 -c "
from fontTools.ttLib import TTFont
font = TTFont('/System/Library/Fonts/Apple Color Emoji.ttc', fontNumber=0)
if 'sbix' in font:
sbix = font['sbix']
print(f'sbix strikes: {[s.ppem for s in sbix.strikes]}')
print(f'Number of glyphs: {len(sbix.strikes[0].glyphs)}')
"
sbix Scalability
Apple augments sbix with vector outlines in the glyf table. The font renders the bitmap at small sizes and switches to a higher-resolution PNG at larger sizes. Some sbix glyphs also use dupe references to point to the same image in a different glyph.
SVG in OpenTypeSVG in OpenType (SVGinOT)
A method of embedding SVG (Scalable Vector Graphics) documents directly in OpenType font files for scalable color emoji.
Used by: Older Firefox versions, some experimental fonts
The SVG table embeds SVG documents directly in the font, one per glyph or glyph range. This enables full SVG capabilities: gradients, filters, animations (in theory), and arbitrary paths.
from fontTools.ttLib import TTFont
font = TTFont("emoji-with-svg.ttf")
if "SVG " in font:
svg_table = font["SVG "]
for doc_list in svg_table.docList:
svg_data, start_glyph_id, end_glyph_id = doc_list
print(f"SVG for glyph IDs {start_glyph_id}โ{end_glyph_id}")
print(svg_data[:200]) # Print first 200 chars of SVG
SVG Table Status
Browser support for SVG-in-OpenType has narrowed. Firefox dropped it in favor of COLRv1. It is still available for specialized print applications (InDesign, Illustrator) that render color fonts via SVG.
Font Format Comparison
| Format | Scale | File Size | Browser Support | Platform |
|---|---|---|---|---|
| COLRv0 | Vector | Small | Excellent | Windows, Linux, Web |
| COLRv1 | Vector + gradients | Small | Chrome 98+, Firefox 105+ | Windows, Linux, Web |
| CBDT/CBLC | Bitmap | Large (~10MB) | Good | Android, Web |
| sbix | Bitmap | Large | Safari, Chrome | macOS, iOS |
| SVG-OT | Vector | Medium | Limited | InDesign, legacy Firefox |
Color Font Rendering in CSS
Modern browsers support color fonts via CSS @font-face without special declarations. The browser automatically selects the best color table it supports:
@font-face {
font-family: "NotoColorEmoji";
src: url("/fonts/NotoColorEmoji-Regular.ttf") format("truetype");
}
.emoji-display {
font-family: "NotoColorEmoji", "Apple Color Emoji", "Segoe UI Emoji";
font-size: 32px;
/* No special color-font CSS needed */
}
For COLRv1 specifically, you can control the active palette with font-palette:
.emoji-dark {
font-palette: dark;
}
.emoji-custom {
font-palette: --my-palette;
}
@font-palette-values --my-palette {
font-family: "NotoColorEmoji";
base-palette: 0;
override-colors: 0 #ffcc00, 1 #ff6600;
}
Variable Color Fonts
COLRv1 enables a new class of fonts: variable color fonts, where color parameters can be animated or controlled by CSS:
@supports (font-variation-settings: "RNDM" 0) {
.animated-emoji {
animation: color-shift 2s infinite;
}
@keyframes color-shift {
from { font-variation-settings: "FILL" 0; }
to { font-variation-settings: "FILL" 1; }
}
}
The Bungee Color font and experimental emoji fonts from Google demonstrate this capability.
Practical Implications for Developers
- Bundle size: If embedding an emoji font in a web app, COLRv1 (Noto COLRv1) is significantly smaller than CBDT Noto (~2MB vsVariation Selector (VS)
Unicode characters (VS-15 U+FE0E and VS-16 U+FE0F) that modify whether a character renders in text (monochrome) or emoji (colorful) presentation. ~10MB) - Rendering consistency: For cross-platform consistency, use a bundled web font rather than relying on OS system fonts
- Subsetting: Use
pyftsubset(fonttools) to extract only the emoji you need:
pyftsubset NotoColorEmoji.ttf \
--unicodes="U+1F600,U+1F601,U+1F602,U+1F604" \
--output-file="emoji-subset.ttf"
Explore More on EmojiFYI
- See how specific emoji render across platforms: Compare Tool
- Inspect emoji code pointCode Point
A unique numerical value assigned to each character in the Unicode standard, written in the format U+XXXX (e.g., U+1F600 for ๐). data: Sequence Analyzer - Emoji font and rendering terminology: Glossary
- Access the emoji dataset: API Reference