MarketingPipeline / Emoji-Fallback.js

Provide support for emojis on ALL web browsers!
MIT License
31 stars 1 forks source link

Does not handle flag emoji on Windows #2

Open MuTsunTsai opened 1 week ago

MuTsunTsai commented 1 week ago

Describe the bug On Windows 10+, although there is native support for most of the emoji, the national flags such as ๐Ÿ‡น๐Ÿ‡ผ are not really "supported" and one will only see two characters instead of the actual flag. For the moment Emoji-Fallback doesn't handle that, as it only checks whether "๐Ÿ˜ƒ" is rendered correctly. It will be good if Emoji-Fallback can handle the flags separately.

MarketingPip commented 1 week ago

@MuTsunTsai good catch! I will implement a solution for this. If you have any ideas of how to go about checking dynamically via regex or even a non regex solution (that doesn't use a table) would be much appreciated. Hope you are having a great weekend!

MarketingPip commented 1 week ago

@MuTsunTsai - just to clarify too. Can you confirm on your browser (Windows 10) when ONLY using a national flag that the fallback support does NOT work on your device via a picture / screenshot? And if confirmed, do you know any other emojis that are not support in Windows? (Unicode 14 - etc...)

MuTsunTsai commented 1 week ago

If you have any ideas of how to go about checking dynamically via regex

For the latest browsers, it will be /\p{RGI_Emoji_Flag_Sequence}/v, but the support is limited (see caniuse). For better browser compatibility, use /[\u{1f1e6}-\u{1f1ff}]{2}/u, or even /(?:\ud83c[\udde6-\uddff]){2}/.

Can you confirm on your browser (Windows 10) when ONLY using a national flag that the fallback support does NOT work on your device via a picture / screenshot?

image

Do you know any other emojis that are not support in Windows?

I just checked the list here, and the followings also are not rendered as emoji, but only as ugly Unicode characters.

โ˜บ โ†— โ†™ โ†• โ†” ยฉ ยฎ โ„ข โ–ซ โ–ช

While the followings get rendered sometimes, but not always. (I don't know why. Maybe it depends on the context.)

โ™ ๏ธ โ™ฅ๏ธ โ™ฆ๏ธ โ™ฃ๏ธ โ†˜ โ†– ใ€ฐ

MarketingPip commented 1 week ago

@MuTsunTsai - thank you very much.

Can I get you to possibly test this?

/*
Notes to self:
imageData = imageData[0] !== 0;  // Check if there's any color
imageData = imageData[3] !== 0;  // Check transparency (alpha channel)
*/ 
export const emojisSupported = () => {
  const emojiList = [
  '\ud83d\ude03', // ๐Ÿ˜€ Grinning Face with Smiling Eyes
  '\u1f1fa\u1f1f8', // ๐Ÿ‡บ๐Ÿ‡ธ United States Flag
  '๐Ÿ‘ฉโ€๐Ÿฆฐ', // Woman with red hair
  '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ',  // Family emoji
  '๐Ÿ‘ฉ',  // Single character (woman)
  '๐Ÿซฃ'  // Face with peeking eye 
];

  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');

  // Check if canvas and fillText method are available
  if (!ctx || typeof ctx.fillText !== 'function') {
    canvas.remove();
    return false;
  }

  ctx.textBaseline = 'top';
  ctx.font = '32px Arial';

  // Loop through the emojiList and check each emoji
  for (let emoji of emojiList) {
    ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear canvas before each test
    ctx.fillText(emoji, 0, 0);

    // Get the pixel data from the canvas at position (0, 0)
    let imageData = ctx.getImageData(0, 0, 1, 1).data;

    // Check if any pixels
    if (imageData[0] !== 0) {
      // Emoji is supported
      canvas.remove();
      return true;
    }
  }

  // No emoji in the list was supported
  canvas.remove();
  return false;
};
console.log(emojiSupported2())
MuTsunTsai commented 1 week ago

@MarketingPip There are several issues with this code (other than the obvious typo of function name).

  1. It should be '\u{1f1fa}\u{1f1f8}' instead, as \uXXXX format only takes 4 characters that follows.
  2. This function seems to test "if any of the emoji is supported" rather than all of them, and I doubt that this is intended.
  3. By the way text is drawn on the canvas here, you naturally won't have anything at position (0,0) (see picture). You should be testing somewhere other than (0,0). In your codebase you test for (16,16) which seems like a good choice. image

Also, when it comes to cross-platform testing, I'd highly recommend BrowserStack. As an open-source project author, you can apply for their free plan here.

MarketingPip commented 1 week ago

@MuTsunTsai - you do NOT need to draw ALL of them.. (this only draws 1 emoji and tests if it was rendered). Which that code above should cover newer version of emojis.

Please do verify with the function I gave you if you return a true statement...

MuTsunTsai commented 1 week ago

@MarketingPip The function as it is, it returns false (especially because of point 3 I mentioned). Now assuming that you changed it to test (16,16) instead. Then the very first emoji "๐Ÿ˜€" already makes the function returning true inside the loop, so the rest of the checks are pointless. Get it?

MarketingPip commented 1 week ago

@MuTsunTsai - yo my bad, I sent you wrong code ๐Ÿคฆ (Thank you for bearing with me too)

/*
Notes to self:
imageData = imageData[0] !== 0;  // Check if there's any color
imageData = imageData[3] !== 0;  // Check transparency (alpha channel)
*/ 
export const emojiSupported2 = () => {
  const emojiList = [
  '\ud83d\ude03', // ๐Ÿ˜€ Grinning Face with Smiling Eyes
  '\u1f1fa\u1f1f8', // ๐Ÿ‡บ๐Ÿ‡ธ United States Flag
  '๐Ÿ‘ฉโ€๐Ÿฆฐ', // Woman with red hair
  '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ',  // Family emoji
  '๐Ÿ‘ฉ',  // Single character (woman)
  '๐Ÿซฃ'  // Face with peeking eye 
];

  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');

  // Check if canvas and fillText method are available
  if (!ctx || typeof ctx.fillText !== 'function') {
    canvas.remove();
    return false;
  }

  ctx.textBaseline = 'top';
  ctx.font = '32px Arial';

  // Loop through the emojiList and check each emoji
  for (let emoji of emojiList) {
    ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear canvas before each test
    ctx.fillText(emoji, 0, 0);

    // Get the pixel data from the canvas at position (0, 0)
    let imageData = ctx.getImageData(16, 16, 1, 1).data;

    // Check if any pixels
    if (imageData[0] !== 0) {
      // Emoji is supported
      canvas.remove();
      return true;
    }
  }

  // No emoji in the list was supported
  canvas.remove();
  return false;
};
console.log(emojiSupported2())

And I didn't catch what you were saying but I get what you mean now.

Would you prefer the code do a check like this?

export const emojiSupported2 = () => {
  const emojiList = [
  '\ud83d\ude03', // ๐Ÿ˜€ Grinning Face with Smiling Eyes
  '\u1f1fa\u1f1f8', // ๐Ÿ‡บ๐Ÿ‡ธ United States Flag
  '๐Ÿ‘ฉโ€๐Ÿฆฐ', // Woman with red hair
  '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ',  // Family emoji
  '๐Ÿ‘ฉ',  // Single character (woman)
  '๐Ÿซฃ'  // Face with peeking eye 
];

  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');

  // Check if canvas and fillText method are available
  if (!ctx || typeof ctx.fillText !== 'function') {
    canvas.remove();
    return false;
  }

  ctx.textBaseline = 'top';
  ctx.font = '32px Arial';

  // Loop through the emojiList and check each emoji
  for (let emoji of emojiList) {
    ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear canvas before each test
    ctx.fillText(emoji, 0, 0);

    // Get the pixel data from the canvas at position (0, 0)
    let imageData = ctx.getImageData(16, 16, 1, 1).data;

    // Check if any pixels
    if (imageData[0] === 0) {
      // Emoji is NOT supported
      canvas.remove();
      return false
    }
  }

  // All emoji in the list were supported
  canvas.remove();
  return true
};
MarketingPip commented 1 week ago

@MuTsunTsai - see the above comment (we posted at same minute / second lol!)

MuTsunTsai commented 1 week ago

@MarketingPip Yeah, the logic of the last version makes much more sense.

MarketingPip commented 1 week ago

@MuTsunTsai - thank you! (my apologizes too for not understanding off first bat - and replying sounding like an assh*le above! - 2AM here lol).

Very good catch. I can gladly implement this or I more than welcome you too being you did help solve this issue. (If you would like to earn a PR etc - I just ask you to follow my style of commit messages / changes etc).

Feel free to let me know - if you're not interested, I can wrap this up by tomorrow ๐Ÿ‘

MuTsunTsai commented 1 week ago

@MarketingPip It's OK. Sure, I can certainly make a PR, but before that, I have one more suggestion.

Basically, as it is right now, it checks "whether any of the listed emojis is not supported", and if so, it will call the underlying twemoji and replace ALL emojis on the page. This seems overkill, however, for the present issue here. For Windows, it suffices to replace just the flags, as almost all the rest are supported. If you agree, this can be implemented as follows:

  1. Having at least two tests, one for the common emojis, and one for the flags (if the first test passes, that is; if the first test already fails there no need for this).
  2. If only the second test fails, call twemoji.parse with the options object instead, which one can assign a callback function to filter out those emojis that are not flags (see here for example), so that only flags are replaced.

Let me know if this sounds right, and then I will make the PR.

MarketingPip commented 1 week ago

@MuTsunTsai - this seems more then reasonable to me! Please do verify on your system before making the PR that certain emojis are being parsed / replaced.

And please do use this commit message for changes to the source code:

Updated emoji-fallback.js ๐Ÿ“ฅ
MuTsunTsai commented 1 week ago

@MarketingPip Done. Meanwhile, one more minor thing. According to the docs, the method twemoji.parse always runs synchronously, so there's no need to await it. (This is irrelevant to the present issue, so I didn't touch that part.)

MarketingPip commented 1 week ago

@MuTsunTsai - parseEmoji() allows it to be awaited / tho is not needed. Reason why it allows to be awaited so developers can add support for example awaiting for emojis to render while loading screen etc... (OR if they prefer not too - simply just not use await infront)