Emoji sequences do not render properly.
authorenrica@apple.com <enrica@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Feb 2015 01:05:28 +0000 (01:05 +0000)
committerenrica@apple.com <enrica@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 17 Feb 2015 01:05:28 +0000 (01:05 +0000)
https://bugs.webkit.org/show_bug.cgi?id=141661
rdar://problem/19820463

Reviewed by Sam Weinig.

Source/WebCore:

Emoji sequences and emoji with variations should be rendered
using the Complex code path and should be treated as graphemes.
This change modifies advanceByCombiningCharacterSequence to add
this logic.

Test: fast/text/emoji.html

* WebCore.xcodeproj/project.pbxproj:
* platform/graphics/FontCascade.cpp:
(WebCore::FontCascade::characterRangeCodePath):
* platform/graphics/mac/ComplexTextController.cpp:
(WebCore::advanceByCombiningCharacterSequence): Implements a simple
logic to treat emoji sequences and emoji with variations as graphemes.
* platform/text/CharacterProperties.h: Added.
(WebCore::isEmojiGroupCandidate):
(WebCore::isEmojiModifier):
(WebCore::isVariationSelector):
* rendering/RenderText.cpp:
(WebCore::isEmojiGroupCandidate): Deleted.
(WebCore::isEmojiModifier): Deleted.

LayoutTests:

* TestExpectations:
* fast/text/emoji-expected.txt: Added.
* fast/text/emoji.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@180191 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/fast/text/emoji-expected.txt [new file with mode: 0644]
LayoutTests/fast/text/emoji.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/platform/graphics/FontCascade.cpp
Source/WebCore/platform/graphics/mac/ComplexTextController.cpp
Source/WebCore/platform/text/CharacterProperties.h [new file with mode: 0644]
Source/WebCore/rendering/RenderText.cpp

index ea7d76c..20153b4 100644 (file)
@@ -1,3 +1,15 @@
+2015-02-16  Enrica Casucci  <enrica@apple.com>
+
+        Emoji sequences do not render properly.
+        https://bugs.webkit.org/show_bug.cgi?id=141661
+        rdar://problem/19820463
+
+        Reviewed by Sam Weinig.
+
+        * TestExpectations:
+        * fast/text/emoji-expected.txt: Added.
+        * fast/text/emoji.html: Added.
+
 2015-02-16  Myles C. Maxfield  <mmaxfield@apple.com>
 
         Update fast/dom/{Element,Range}/getClientRects.html after r177774
index 330e2a1..c7066cf 100644 (file)
@@ -123,6 +123,8 @@ webkit.org/b/132791 svg/as-object/sizing/svg-in-object-placeholder-height-auto.h
 
 webkit.org/b/133057 fast/table/border-collapsing/collapsed-borders-adjoining-sections.html [ ImageOnlyFailure ]
 
+webkit.org/b/141661 fast/text/emoji.html [ Failure ]
+
 webkit.org/b/128736 inspector-protocol/debugger/setBreakpoint-dfg.html [ Failure Pass ]
 webkit.org/b/134982 inspector-protocol/debugger/setBreakpoint-dfg-and-modify-local.html [ Failure Pass ]
 
diff --git a/LayoutTests/fast/text/emoji-expected.txt b/LayoutTests/fast/text/emoji-expected.txt
new file mode 100644 (file)
index 0000000..b57025a
--- /dev/null
@@ -0,0 +1,23 @@
+layer at (0,0) size 800x600
+  RenderView at (0,0) size 800x600
+layer at (0,0) size 800x600
+  RenderBlock {HTML} at (0,0) size 800x600
+    RenderBody {BODY} at (8,8) size 784x584
+      RenderBlock (anonymous) at (0,0) size 784x18
+        RenderText {#text} at (0,0) size 470x18
+          text run at (0,0) width 470: "This test validate rendering of emoji sequences and emoji with modifiers."
+      RenderBlock {DIV} at (0,18) size 784x235
+        RenderText {#text} at (0,4) size 180x41
+          text run at (0,4) width 180: "\x{D83D}\x{DC66}\x{D83C}\x{DFFB}\x{D83D}\x{DC69}\x{D83C}\x{DFFC}\x{D83D}\x{DC66}\x{D83C}\x{DFFE}\x{2764}\x{FE0F}\x{D83D}\x{DC8B}"
+        RenderBR {BR} at (180,36) size 0x0
+        RenderText {#text} at (0,51) size 243x41
+          text run at (0,51) width 243: "\x{D83D}\x{DC68}\x{200D}\x{D83D}\x{DC69}\x{200D}\x{D83D}\x{DC66} \x{D83D}\x{DC68}\x{200D}\x{D83D}\x{DC69}\x{200D}\x{D83D}\x{DC67} \x{D83D}\x{DC6A} \x{D83D}\x{DC66}\x{D83C}\x{DFFB}\x{D83D}\x{DC69}\x{D83C}\x{DFFC}\x{D83D}\x{DC66}\x{D83C}\x{DFFE}"
+        RenderBR {BR} at (243,83) size 0x0
+        RenderText {#text} at (0,98) size 216x41
+          text run at (0,98) width 216: "\x{D83D}\x{DC69}\x{200D}\x{D83D}\x{DC69}\x{200D}\x{D83D}\x{DC66} \x{D83D}\x{DC69}\x{200D}\x{D83D}\x{DC69}\x{200D}\x{D83D}\x{DC67} \x{D83D}\x{DC69}\x{200D}\x{D83D}\x{DC69}\x{200D}\x{D83D}\x{DC67}\x{200D}\x{D83D}\x{DC66} \x{D83D}\x{DC69}\x{200D}\x{D83D}\x{DC69}\x{200D}\x{D83D}\x{DC66}\x{200D}\x{D83D}\x{DC66} \x{D83D}\x{DC69}\x{200D}\x{D83D}\x{DC69}\x{200D}\x{D83D}\x{DC67}\x{200D}\x{D83D}\x{DC67}"
+        RenderBR {BR} at (216,130) size 0x0
+        RenderText {#text} at (0,145) size 216x41
+          text run at (0,145) width 216: "\x{D83D}\x{DC68}\x{200D}\x{D83D}\x{DC68}\x{200D}\x{D83D}\x{DC66} \x{D83D}\x{DC68}\x{200D}\x{D83D}\x{DC68}\x{200D}\x{D83D}\x{DC67} \x{D83D}\x{DC68}\x{200D}\x{D83D}\x{DC68}\x{200D}\x{D83D}\x{DC67}\x{200D}\x{D83D}\x{DC66} \x{D83D}\x{DC68}\x{200D}\x{D83D}\x{DC68}\x{200D}\x{D83D}\x{DC66}\x{200D}\x{D83D}\x{DC66} \x{D83D}\x{DC68}\x{200D}\x{D83D}\x{DC68}\x{200D}\x{D83D}\x{DC67}\x{200D}\x{D83D}\x{DC67}"
+        RenderBR {BR} at (216,177) size 0x0
+        RenderText {#text} at (0,192) size 216x41
+          text run at (0,192) width 216: "\x{D83D}\x{DC69}\x{200D}\x{2764}\x{FE0F}\x{200D}\x{D83D}\x{DC69} \x{D83D}\x{DC68}\x{200D}\x{2764}\x{FE0F}\x{200D}\x{D83D}\x{DC68} \x{D83D}\x{DC69}\x{200D}\x{2764}\x{FE0F}\x{200D}\x{D83D}\x{DC8B}\x{200D}\x{D83D}\x{DC69} \x{D83D}\x{DC68}\x{200D}\x{2764}\x{FE0F}\x{200D}\x{D83D}\x{DC8B}\x{200D}\x{D83D}\x{DC68} \x{D83D}\x{DC69}\x{200D}\x{2764}\x{FE0F}\x{200D}\x{D83D}\x{DC69}"
diff --git a/LayoutTests/fast/text/emoji.html b/LayoutTests/fast/text/emoji.html
new file mode 100644 (file)
index 0000000..748ae18
--- /dev/null
@@ -0,0 +1,12 @@
+<html>
+<body>
+    This test validate rendering of emoji sequences and emoji with modifiers.
+<div style='font-size: 36px'>
+&#x1F466;&#x1F3FB;&#x1F469;&#x1F3FC;&#x1F466;&#x1F3FE;&#x2764;&#xFE0F;&#x1F48B;<br>
+&#x1F468;&#x200D;&#x1F469;&#x200D;&#x1F466; &#x1F468;&#x200D;&#x1F469;&#x200D;&#x1F467; &#x1F46A; &#x1F466;&#x1F3FB;&#x1F469;&#x1F3FC;&#x1F466;&#x1F3FE;<br>
+&#x1F469;&#x200D;&#x1F469;&#x200D;&#x1F466; &#x1F469;&#x200D;&#x1F469;&#x200D;&#x1F467; &#x1F469;&#x200D;&#x1F469;&#x200D;&#x1F467;&#x200D;&#x1F466; &#x1F469;&#x200D;&#x1F469;&#x200D;&#x1F466;&#x200D;&#x1F466; &#x1F469;&#x200D;&#x1F469;&#x200D;&#x1F467;&#x200D;&#x1F467;<br>
+&#x1F468;&#x200D;&#x1F468;&#x200D;&#x1F466; &#x1F468;&#x200D;&#x1F468;&#x200D;&#x1F467; &#x1F468;&#x200D;&#x1F468;&#x200D;&#x1F467;&#x200D;&#x1F466; &#x1F468;&#x200D;&#x1F468;&#x200D;&#x1F466;&#x200D;&#x1F466; &#x1F468;&#x200D;&#x1F468;&#x200D;&#x1F467;&#x200D;&#x1F467;<br>
+&#x1F469;&#x200D;&#x2764;&#xFE0F;&#x200D;&#x1F469; &#x1F468;&#x200D;&#x2764;&#xFE0F;&#x200D;&#x1F468; &#x1F469;&#x200D;&#x2764;&#xFE0F;&#x200D;&#x1F48B;&#x200D;&#x1F469; &#x1F468;&#x200D;&#x2764;&#xFE0F;&#x200D;&#x1F48B;&#x200D;&#x1F468; &#x1F469;&#x200D;&#x2764;&#xFE0F;&#x200D;&#x1F469;
+</div>
+</body>
+</html>
index 479ed65..a9dfe4d 100644 (file)
@@ -1,3 +1,32 @@
+2015-02-16  Enrica Casucci  <enrica@apple.com>
+
+        Emoji sequences do not render properly.
+        https://bugs.webkit.org/show_bug.cgi?id=141661
+        rdar://problem/19820463
+
+        Reviewed by Sam Weinig.
+
+        Emoji sequences and emoji with variations should be rendered
+        using the Complex code path and should be treated as graphemes.
+        This change modifies advanceByCombiningCharacterSequence to add
+        this logic.
+
+        Test: fast/text/emoji.html
+
+        * WebCore.xcodeproj/project.pbxproj:
+        * platform/graphics/FontCascade.cpp:
+        (WebCore::FontCascade::characterRangeCodePath):
+        * platform/graphics/mac/ComplexTextController.cpp:
+        (WebCore::advanceByCombiningCharacterSequence): Implements a simple
+        logic to treat emoji sequences and emoji with variations as graphemes.
+        * platform/text/CharacterProperties.h: Added.
+        (WebCore::isEmojiGroupCandidate):
+        (WebCore::isEmojiModifier):
+        (WebCore::isVariationSelector):
+        * rendering/RenderText.cpp:
+        (WebCore::isEmojiGroupCandidate): Deleted.
+        (WebCore::isEmojiModifier): Deleted.
+
 2015-02-16  Zalan Bujtas  <zalan@apple.com>
 
         RenderTableRow should check if it has access to its ancestor chain.
index d843c17..cac333e 100644 (file)
                C5278B0C17F212EA003A2998 /* PlatformPasteboardIOS.mm in Sources */ = {isa = PBXBuildFile; fileRef = C5278B0B17F212EA003A2998 /* PlatformPasteboardIOS.mm */; };
                C544274B11A57E7A0063A749 /* DOMStringList.h in Headers */ = {isa = PBXBuildFile; fileRef = C544274911A57E7A0063A749 /* DOMStringList.h */; settings = {ATTRIBUTES = (Private, ); }; };
                C55610F111A704EB00B82D27 /* DOMStringList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C55610F011A704EB00B82D27 /* DOMStringList.cpp */; };
+               C5592F781A92AA28001F8862 /* CharacterProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = C5592F771A92AA28001F8862 /* CharacterProperties.h */; };
                C55C7BA11718AFBA001327E4 /* RenderThemeIOS.mm in Sources */ = {isa = PBXBuildFile; fileRef = C55C7BA01718AFBA001327E4 /* RenderThemeIOS.mm */; };
                C572EE1F1201C9BC007D8F82 /* JSIDBIndex.h in Headers */ = {isa = PBXBuildFile; fileRef = C572EE1D1201C9BC007D8F82 /* JSIDBIndex.h */; };
                C57FEDE11212EE9C0097BE65 /* FileSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C57FEDE01212EE9C0097BE65 /* FileSystem.cpp */; };
                C544274911A57E7A0063A749 /* DOMStringList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DOMStringList.h; sourceTree = "<group>"; };
                C544274A11A57E7A0063A749 /* DOMStringList.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = DOMStringList.idl; sourceTree = "<group>"; };
                C55610F011A704EB00B82D27 /* DOMStringList.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DOMStringList.cpp; sourceTree = "<group>"; };
+               C5592F771A92AA28001F8862 /* CharacterProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CharacterProperties.h; sourceTree = "<group>"; };
                C55C7BA01718AFBA001327E4 /* RenderThemeIOS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RenderThemeIOS.mm; sourceTree = "<group>"; };
                C572EE1D1201C9BC007D8F82 /* JSIDBIndex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSIDBIndex.h; sourceTree = "<group>"; };
                C57FEDE01212EE9C0097BE65 /* FileSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileSystem.cpp; sourceTree = "<group>"; };
                                B2C3D9F30D006C1D00EF6F26 /* BidiContext.h */,
                                B2C3D9F40D006C1D00EF6F26 /* BidiResolver.h */,
                                A8C402921348B2220063F1E5 /* BidiRunList.h */,
+                               C5592F771A92AA28001F8862 /* CharacterProperties.h */,
                                0FDA7C2018832BCC00C954B5 /* DateTimeFormat.cpp */,
                                453EB635159C570400001BB7 /* DateTimeFormat.h */,
                                CECCFC3A141973D5002A0AC1 /* DecodeEscapeSequences.h */,
                                59A8F1D811A69520001AC34A /* DeviceOrientationClient.h in Headers */,
                                3140379D124BEA7F00AF40E4 /* DeviceOrientationClientIOS.h in Headers */,
                                59309A1311F4AE6A00250603 /* DeviceOrientationClientMock.h in Headers */,
+                               C5592F781A92AA28001F8862 /* CharacterProperties.h in Headers */,
                                59A8F1D611A69513001AC34A /* DeviceOrientationController.h in Headers */,
                                590E1B4911E4EF4B0069F784 /* DeviceOrientationData.h in Headers */,
                                59A85EA4119D68EC00DEF1EF /* DeviceOrientationEvent.h in Headers */,
index 0f666d4..08944b3 100644 (file)
@@ -24,6 +24,7 @@
 #include "config.h"
 #include "FontCascade.h"
 
+#include "CharacterProperties.h"
 #include "FloatRect.h"
 #include "FontCache.h"
 #include "GlyphBuffer.h"
@@ -771,6 +772,8 @@ FontCascade::CodePath FontCascade::characterRangeCodePath(const UChar* character
                 previousCharacterIsEmojiGroupCandidate = true;
                 continue;
             }
+            if (isEmojiModifier(supplementaryCharacter))
+                return Complex;
             if (supplementaryCharacter < 0xE0100) // U+E0100 through U+E01EF Unicode variation selectors.
                 continue;
             if (supplementaryCharacter <= 0xE01EF)
index 4efdfa6..9184354 100644 (file)
@@ -25,6 +25,7 @@
 #include "config.h"
 #include "ComplexTextController.h"
 
+#include "CharacterProperties.h"
 #include "FloatSize.h"
 #include "FontCascade.h"
 #include "RenderBlock.h"
@@ -258,6 +259,8 @@ int ComplexTextController::offsetForPosition(float h, bool includePartialGlyphs)
     return 0;
 }
 
+// FIXME: We should consider reimplementing this function using ICU to advance by grapheme.
+// The current implementation only considers explicitly emoji sequences and emoji variations.
 static bool advanceByCombiningCharacterSequence(const UChar*& iterator, const UChar* end, UChar32& baseCharacter, unsigned& markCount)
 {
     ASSERT(iterator < end);
@@ -268,17 +271,33 @@ static bool advanceByCombiningCharacterSequence(const UChar*& iterator, const UC
     unsigned remainingCharacters = end - iterator;
     U16_NEXT(iterator, i, remainingCharacters, baseCharacter);
     iterator = iterator + i;
-
     if (U_IS_SURROGATE(baseCharacter))
         return false;
 
     // Consume marks.
+    bool sawEmojiGroupCandidate = isEmojiGroupCandidate(baseCharacter);
+    bool sawJoiner = false;
     while (iterator < end) {
         UChar32 nextCharacter;
         int markLength = 0;
+        bool shouldContinue = false;
         U16_NEXT(iterator, markLength, end - iterator, nextCharacter);
-        if (!(U_GET_GC_MASK(nextCharacter) & U_GC_M_MASK))
+
+        if (isVariationSelector(nextCharacter) || isEmojiModifier(nextCharacter))
+            shouldContinue = true;
+
+        if (sawJoiner && isEmojiGroupCandidate(nextCharacter))
+            shouldContinue = true;
+
+        sawJoiner = false;
+        if (sawEmojiGroupCandidate && nextCharacter == zeroWidthJoiner) {
+            sawJoiner = true;
+            shouldContinue = true;
+        }
+        
+        if (!shouldContinue && !(U_GET_GC_MASK(nextCharacter) & U_GC_M_MASK))
             break;
+
         markCount += markLength;
         iterator += markLength;
     }
diff --git a/Source/WebCore/platform/text/CharacterProperties.h b/Source/WebCore/platform/text/CharacterProperties.h
new file mode 100644 (file)
index 0000000..00d0faf
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 Apple, Inc.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CharacterProperties_h
+#define CharacterProperties_h
+
+namespace WebCore {
+
+static inline bool isEmojiGroupCandidate(UChar32 character)
+{
+    return (character >= 0x1F466 && character <= 0x1F469) || character == 0x2764 || character == 0x1F48B;
+}
+
+static inline bool isEmojiModifier(UChar32 character)
+{
+    return character >= 0x1F3FB && character <= 0x1F3FF;
+}
+
+inline bool isVariationSelector(UChar32 character)
+{
+    return character >= 0xFE00 && character <= 0xFE0F;
+}
+
+}
+
+#endif
index 838ecad..15d014d 100644 (file)
@@ -26,6 +26,7 @@
 #include "RenderText.h"
 
 #include "AXObjectCache.h"
+#include "CharacterProperties.h"
 #include "EllipsisBox.h"
 #include "FloatQuad.h"
 #include "Frame.h"
@@ -1438,16 +1439,6 @@ static inline bool isRegionalIndicator(UChar32 character)
     return 0x1F1E6 <= character && character <= 0x1F1FF;
 }
 
-static inline bool isEmojiGroupCandidate(UChar32 character)
-{
-    return (character >= 0x1F466 && character <= 0x1F469) || character == 0x2764 || character == 0x1F48B;
-}
-
-static inline bool isEmojiModifier(UChar32 character)
-{
-    return character >= 0x1F3FB && character <= 0x1F3FF;
-}
-
 #endif
 
 int RenderText::previousOffsetForBackwardDeletion(int current) const