mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-14 00:08:46 +00:00
120 lines
3.9 KiB
TypeScript
120 lines
3.9 KiB
TypeScript
// Credit to Nolan Lawson for the original implementation.
|
|
// See: https://github.com/nolanlawson/emoji-picker-element/blob/master/src/picker/utils/testColorEmojiSupported.js
|
|
|
|
import { isDevelopment } from '@/flavours/glitch/utils/environment';
|
|
|
|
import {
|
|
EMOJI_MODE_NATIVE,
|
|
EMOJI_MODE_NATIVE_WITH_FLAGS,
|
|
EMOJI_MODE_TWEMOJI,
|
|
} from './constants';
|
|
import type { EmojiMode } from './types';
|
|
|
|
type Feature = Uint8ClampedArray;
|
|
|
|
// See: https://github.com/nolanlawson/emoji-picker-element/blob/master/src/picker/constants.js
|
|
const FONT_FAMILY =
|
|
'"Twemoji Mozilla","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol",' +
|
|
'"Noto Color Emoji","EmojiOne Color","Android Emoji",sans-serif';
|
|
|
|
function getTextFeature(text: string, color: string) {
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = canvas.height = 1;
|
|
|
|
const ctx = canvas.getContext('2d', {
|
|
// Improves the performance of `getImageData()`
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getContextAttributes#willreadfrequently
|
|
willReadFrequently: true,
|
|
});
|
|
if (!ctx) {
|
|
throw new Error('Canvas context not available');
|
|
}
|
|
ctx.textBaseline = 'top';
|
|
ctx.font = `100px ${FONT_FAMILY}`;
|
|
ctx.fillStyle = color;
|
|
ctx.scale(0.01, 0.01);
|
|
ctx.fillText(text, 0, 0);
|
|
|
|
return ctx.getImageData(0, 0, 1, 1).data satisfies Feature;
|
|
}
|
|
|
|
function compareFeatures(feature1: Feature, feature2: Feature) {
|
|
const feature1Str = [...feature1].join(',');
|
|
const feature2Str = [...feature2].join(',');
|
|
// This is RGBA, so for 0,0,0, we are checking that the first RGB is not all zeroes.
|
|
// Most of the time when unsupported this is 0,0,0,0, but on Chrome on Mac it is
|
|
// 0,0,0,61 - there is a transparency here.
|
|
return feature1Str === feature2Str && !feature1Str.startsWith('0,0,0,');
|
|
}
|
|
|
|
function testEmojiSupport(text: string) {
|
|
// Render white and black and then compare them to each other and ensure they're the same
|
|
// color, and neither one is black. This shows that the emoji was rendered in color.
|
|
const feature1 = getTextFeature(text, '#000');
|
|
const feature2 = getTextFeature(text, '#fff');
|
|
return compareFeatures(feature1, feature2);
|
|
}
|
|
|
|
const EMOJI_VERSION_TEST_EMOJI = '🫨'; // shaking head, from v15
|
|
const EMOJI_FLAG_TEST_EMOJI = '🇨🇭';
|
|
|
|
export function determineEmojiMode(style: string): EmojiMode {
|
|
if (style === EMOJI_MODE_NATIVE) {
|
|
// If flags are not supported, we replace them with Twemoji.
|
|
if (shouldReplaceFlags()) {
|
|
return EMOJI_MODE_NATIVE_WITH_FLAGS;
|
|
}
|
|
return EMOJI_MODE_NATIVE;
|
|
}
|
|
if (style === EMOJI_MODE_TWEMOJI) {
|
|
return EMOJI_MODE_TWEMOJI;
|
|
}
|
|
|
|
// Auto style so determine based on browser capabilities.
|
|
if (shouldUseTwemoji()) {
|
|
return EMOJI_MODE_TWEMOJI;
|
|
} else if (shouldReplaceFlags()) {
|
|
return EMOJI_MODE_NATIVE_WITH_FLAGS;
|
|
}
|
|
return EMOJI_MODE_NATIVE;
|
|
}
|
|
|
|
export function shouldUseTwemoji(): boolean {
|
|
if (typeof window === 'undefined') {
|
|
return false;
|
|
}
|
|
try {
|
|
// Test a known color emoji to see if 15.1 is supported.
|
|
return !testEmojiSupport(EMOJI_VERSION_TEST_EMOJI);
|
|
} catch (err: unknown) {
|
|
// If an error occurs, fall back to Twemoji to be safe.
|
|
if (isDevelopment()) {
|
|
console.warn(
|
|
'Emoji rendering test failed, defaulting to Twemoji. Error:',
|
|
err,
|
|
);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Based on https://github.com/talkjs/country-flag-emoji-polyfill/blob/master/src/index.ts#L19
|
|
export function shouldReplaceFlags(): boolean {
|
|
if (typeof window === 'undefined') {
|
|
return false;
|
|
}
|
|
try {
|
|
// Test a known flag emoji to see if it is rendered in color.
|
|
return !testEmojiSupport(EMOJI_FLAG_TEST_EMOJI);
|
|
} catch (err: unknown) {
|
|
// If an error occurs, assume flags should be replaced.
|
|
if (isDevelopment()) {
|
|
console.warn(
|
|
'Flag emoji rendering test failed, defaulting to replacement. Error:',
|
|
err,
|
|
);
|
|
}
|
|
return true;
|
|
}
|
|
}
|