From 52d7e4f3dbfe27952b1727fdc908a5134ea90004 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 9 May 2024 15:44:02 -0700 Subject: [PATCH] PR #2514 - Don't split up happy emoji sequences Loosely based on https://phabricator.services.mozilla.com/D25101 Includes changes to `GetEmojiPresentation`, added in e38b57ca. The `EmojiComponent` response includes hair color, skin tone, ZWJ, VS16, Combining Enclosing Keycap, and the Tag unicode characters used for subdivision flag emojis. This means that `IsClusterExtender` should support all these characters, and be forward compatible with any future Unicode changes. This also lets us restructure `ClusterIterator::Next` to check for EmojiComponents after a TextDefault character, without any stupid constants. --- intl/unicharutil/util/nsUnicodeProperties.cpp | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/intl/unicharutil/util/nsUnicodeProperties.cpp b/intl/unicharutil/util/nsUnicodeProperties.cpp index 8a2b04a8b0..b9c8b83fb2 100644 --- a/intl/unicharutil/util/nsUnicodeProperties.cpp +++ b/intl/unicharutil/util/nsUnicodeProperties.cpp @@ -152,7 +152,8 @@ IsClusterExtender(uint32_t aCh, uint8_t aCategory) { return ((aCategory >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK && aCategory <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) || - (aCh >= 0x200c && aCh <= 0x200d) || // ZWJ, ZWNJ + (GetEmojiPresentation(aCh) == EmojiComponent) || + (aCh == 0x200c) || // ZWNJ (aCh >= 0xff9e && aCh <= 0xff9f)); // katakana sound marks } @@ -224,8 +225,25 @@ ClusterIterator::Next() } } + const uint32_t kZWJ = 0x200d; + uint32_t aNextCh = 0; + if (mPos + 1 < mLimit) { + aNextCh = *mPos; + uint32_t aLowCh = *(mPos + 1); + if (NS_IS_HIGH_SURROGATE(aNextCh) && NS_IS_LOW_SURROGATE(aLowCh)) { + aNextCh = SURROGATE_TO_UCS4(aNextCh, aLowCh); + } + } + + bool baseIsEmoji = (GetEmojiPresentation(ch) == EmojiDefault) || + (GetEmojiPresentation(ch) == EmojiComponent) || + (GetEmojiPresentation(ch) == TextDefault && + GetEmojiPresentation(aNextCh) == EmojiComponent); + bool prevWasZwj = false; + while (mPos < mLimit) { ch = *mPos; + size_t chLen = 1; // Check for surrogate pairs; note that isolated surrogates will just // be treated as generic (non-cluster-extending) characters here, @@ -233,16 +251,30 @@ ClusterIterator::Next() if (NS_IS_HIGH_SURROGATE(ch) && mPos < mLimit - 1 && NS_IS_LOW_SURROGATE(*(mPos + 1))) { ch = SURROGATE_TO_UCS4(ch, *(mPos + 1)); + chLen = 2; } - if (!IsClusterExtender(ch)) { + uint32_t aExtCh = 0; + if (mPos + chLen < mLimit) { + aExtCh = *(mPos + chLen); + uint32_t aLowCh = *(mPos + chLen + 1); + if (NS_IS_HIGH_SURROGATE(aExtCh) && NS_IS_LOW_SURROGATE(aLowCh)) { + aExtCh = SURROGATE_TO_UCS4(aExtCh, aLowCh); + } + } + bool extendCluster = + IsClusterExtender(ch) || + (baseIsEmoji && prevWasZwj && + ((GetEmojiPresentation(ch) == EmojiDefault) || + (GetEmojiPresentation(ch) == EmojiComponent) || + (GetEmojiPresentation(ch) == TextDefault && + GetEmojiPresentation(aExtCh) == EmojiComponent))); + if (!extendCluster) { break; } - mPos++; - if (!IS_IN_BMP(ch)) { - mPos++; - } + prevWasZwj = (ch == kZWJ); + mPos += chLen; } NS_ASSERTION(mText < mPos && mPos <= mLimit,