diff --git a/package.json b/package.json index 7b0cb38b..cd404fe3 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,6 @@ "format-message-interpret": "^6.2.4", "format-message-parse": "^6.2.4", "glob": "^7.2.0", - "is-emoji-supported": "^0.0.5", "li": "^1.3.0", "localstorage-memory": "^1.0.3", "mkdirp": "^1.0.4", @@ -137,6 +136,7 @@ "Element", "Element", "Event", + "FontFace", "FormData", "HTMLElement", "IDBKeyRange", diff --git a/src/routes/_actions/autosuggestEmojiSearch.js b/src/routes/_actions/autosuggestEmojiSearch.js index 853a5410..8c775720 100644 --- a/src/routes/_actions/autosuggestEmojiSearch.js +++ b/src/routes/_actions/autosuggestEmojiSearch.js @@ -21,7 +21,7 @@ async function searchEmoji (searchText) { if (results.length === SEARCH_RESULTS_LIMIT) { break } - if (emoji.url || testEmojiSupported(emoji.unicode)) { // emoji.url is a custom emoji + if (emoji.url || await testEmojiSupported(emoji.unicode)) { // emoji.url is a custom emoji results.push(emoji) } } diff --git a/src/routes/_api/utils.js b/src/routes/_api/utils.js index 2ebbe94d..57705bc8 100644 --- a/src/routes/_api/utils.js +++ b/src/routes/_api/utils.js @@ -1,5 +1,5 @@ function targetIsLocalhost (instanceName) { - return instanceName.startsWith('localhost:') || instanceName.startsWith('127.0.0.1:') + return true // instanceName.startsWith('localhost:') || instanceName.startsWith('127.0.0.1:') } export function basename (instanceName) { diff --git a/src/routes/_static/fonts.js b/src/routes/_static/fonts.js index cd59d72d..25849152 100644 --- a/src/routes/_static/fonts.js +++ b/src/routes/_static/fonts.js @@ -1,3 +1,6 @@ // same as the one used for PinaforeEmoji export const FONT_FAMILY = '"Twemoji Mozilla","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol",' + '"Noto Color Emoji","EmojiOne Color","Android Emoji",sans-serif' + +export const COUNTRY_FLAG_FONT_FAMILY = 'Twemoji Mozilla' +export const COUNTRY_FLAG_FONT_URL = '/TwemojiCountryFlags.woff2' diff --git a/src/routes/_store/observers/countryFlagEmojiPolyfill.js b/src/routes/_store/observers/countryFlagEmojiPolyfill.js index 60982c9c..0c9cb00a 100644 --- a/src/routes/_store/observers/countryFlagEmojiPolyfill.js +++ b/src/routes/_store/observers/countryFlagEmojiPolyfill.js @@ -1,10 +1,17 @@ import { polyfillCountryFlagEmojis } from 'country-flag-emoji-polyfill' +import { store } from '../store.js' +import { COUNTRY_FLAG_FONT_FAMILY, COUNTRY_FLAG_FONT_URL } from '../../_static/fonts.js' let polyfilled = false export function countryFlagEmojiPolyfill () { if (!polyfilled) { polyfilled = true - polyfillCountryFlagEmojis('Twemoji Mozilla', '/TwemojiCountryFlags.woff2') + const numStylesBefore = document.head.querySelectorAll('style').length + polyfillCountryFlagEmojis(COUNTRY_FLAG_FONT_FAMILY, COUNTRY_FLAG_FONT_URL) + const numStylesAfter = document.head.querySelectorAll('style').length + // if a style was added, then the polyfill was activated + const polyfillActivated = numStylesAfter !== numStylesBefore + store.set({ 'polyfilledCountryFlagEmoji': polyfillActivated }) } } diff --git a/src/routes/_thirdparty/is-emoji-supported/LICENSE.md b/src/routes/_thirdparty/is-emoji-supported/LICENSE.md new file mode 100644 index 00000000..e8c0303f --- /dev/null +++ b/src/routes/_thirdparty/is-emoji-supported/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2016-2020 Koala Interactive, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/routes/_thirdparty/is-emoji-supported/is-emoji-supported.js b/src/routes/_thirdparty/is-emoji-supported/is-emoji-supported.js new file mode 100644 index 00000000..47274628 --- /dev/null +++ b/src/routes/_thirdparty/is-emoji-supported/is-emoji-supported.js @@ -0,0 +1,96 @@ +// via https://unpkg.com/is-emoji-supported@0.0.5/dist/esm/is-emoji-supported.js + +import { COUNTRY_FLAG_FONT_FAMILY, FONT_FAMILY } from '../../_static/fonts.js' + +/** + * @var {Object} cache + */ +let cache = new Map() + +/** + * Check if emoji is supported with cache + * + * @params {string} unicode + * @returns {boolean} + */ +function isEmojiSupported (unicode) { + if (cache.has(unicode)) { + return cache.get(unicode) + } + const supported = isSupported(unicode) + cache.set(unicode, supported) + return supported +} + +/** + * Request to handle cache directly + * + * @params {Map} store + */ +function setCacheHandler (store) { + cache = store +} + +/** + * Check if the two pixels parts are perfectly the sames + * + * @params {string} unicode + * @returns {boolean} + */ +const isSupported = (function () { + let ctx = null + + const CANVAS_HEIGHT = 25 + const CANVAS_WIDTH = 20 + + function createContext() { + if (ctx) { + return + } + ctx = document.createElement('canvas').getContext('2d') + const textSize = Math.floor(CANVAS_HEIGHT / 2) + // Initialize convas context + ctx.font = textSize + `px PinaforeEmoji` + ctx.textBaseline = 'top' + ctx.canvas.width = CANVAS_WIDTH * 2 + ctx.canvas.height = CANVAS_HEIGHT + } + + return function (unicode) { + createContext() + ctx.clearRect(0, 0, CANVAS_WIDTH * 2, CANVAS_HEIGHT) + // Draw in red on the left + ctx.fillStyle = '#FF0000' + ctx.fillText(unicode, 0, 22) + // Draw in blue on right + ctx.fillStyle = '#0000FF' + ctx.fillText(unicode, CANVAS_WIDTH, 22) + const a = ctx.getImageData(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT).data + const count = a.length + let i = 0 + // Search the first visible pixel + // eslint-disable-next-line no-empty + for (; i < count && !a[i + 3]; i += 4) {} + // No visible pixel + if (i >= count) { + return false + } + // Emoji has immutable color, so we check the color of the emoji in two different colors + // the result show be the same. + const x = CANVAS_WIDTH + ((i / 4) % CANVAS_WIDTH) + const y = Math.floor(i / 4 / CANVAS_WIDTH) + const b = ctx.getImageData(x, y, 1, 1).data + if (a[i] !== b[0] || a[i + 2] !== b[2]) { + return false + } + // Some emojis are a contraction of different ones, so if it's not + // supported, it will show multiple characters + if (ctx.measureText(unicode).width >= CANVAS_WIDTH) { + return false + } + // Supported + return true + } +})() + +export { isEmojiSupported, setCacheHandler } diff --git a/src/routes/_utils/testEmojiSupported.js b/src/routes/_utils/testEmojiSupported.js index 12f70b69..2328ae1a 100644 --- a/src/routes/_utils/testEmojiSupported.js +++ b/src/routes/_utils/testEmojiSupported.js @@ -1,8 +1,20 @@ -import { isEmojiSupported, setCacheHandler } from 'is-emoji-supported' +import { isEmojiSupported, setCacheHandler } from '../_thirdparty/is-emoji-supported/is-emoji-supported.js' import { QuickLRU } from '../_thirdparty/quick-lru/quick-lru.js' +import { store } from '../_store/store.js' +import { COUNTRY_FLAG_FONT_FAMILY, COUNTRY_FLAG_FONT_URL } from '../_static/fonts.js' // avoid recomputing emoji support over and over again // use our own LRU since the built-in one grows forever, which is a small memory leak, but still setCacheHandler(new QuickLRU({ maxSize: 500 })) -export const testEmojiSupported = isEmojiSupported +let loadedFlagEmojiPolyfillFont = false + +export async function testEmojiSupported(unicode) { + if (store.get().polyfilledCountryFlagEmoji && !loadedFlagEmojiPolyfillFont) { + // if we're using the country flag emoji polyfill, then we have to make sure the font is loaded first + const fontFace = new FontFace(COUNTRY_FLAG_FONT_FAMILY, `url(${JSON.stringify(COUNTRY_FLAG_FONT_URL)})`) + await fontFace.load() + loadedFlagEmojiPolyfillFont = true + } + return isEmojiSupported(unicode) +}