|
Packit |
0ec9dd |
/* Pango
|
|
Packit |
0ec9dd |
* pango-emoji.c: Emoji handling
|
|
Packit |
0ec9dd |
*
|
|
Packit |
0ec9dd |
* Copyright (C) 2017 Google, Inc.
|
|
Packit |
0ec9dd |
*
|
|
Packit |
0ec9dd |
* This library is free software; you can redistribute it and/or
|
|
Packit |
0ec9dd |
* modify it under the terms of the GNU Library General Public
|
|
Packit |
0ec9dd |
* License as published by the Free Software Foundation; either
|
|
Packit |
0ec9dd |
* version 2 of the License, or (at your option) any later version.
|
|
Packit |
0ec9dd |
*
|
|
Packit |
0ec9dd |
* This library is distributed in the hope that it will be useful,
|
|
Packit |
0ec9dd |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
0ec9dd |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Packit |
0ec9dd |
* Library General Public License for more details.
|
|
Packit |
0ec9dd |
*
|
|
Packit |
0ec9dd |
* You should have received a copy of the GNU Library General Public
|
|
Packit |
0ec9dd |
* License along with this library; if not, write to the
|
|
Packit |
0ec9dd |
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
Packit |
0ec9dd |
* Boston, MA 02111-1307, USA.
|
|
Packit |
0ec9dd |
*
|
|
Packit |
0ec9dd |
* Implementation of pango_emoji_iter is derived from Chromium:
|
|
Packit |
0ec9dd |
*
|
|
Packit |
0ec9dd |
* https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/fonts/FontFallbackPriority.h
|
|
Packit |
0ec9dd |
* https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/text/CharacterEmoji.cpp
|
|
Packit |
0ec9dd |
* https://cs.chromium.org/chromium/src/third_party/WebKit/Source/platform/fonts/SymbolsIterator.cpp
|
|
Packit |
0ec9dd |
*
|
|
Packit |
0ec9dd |
* // Copyright 2015 The Chromium Authors. All rights reserved.
|
|
Packit |
0ec9dd |
* // Use of this source code is governed by a BSD-style license that can be
|
|
Packit |
0ec9dd |
* // found in the LICENSE file.
|
|
Packit |
0ec9dd |
*/
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
#include "config.h"
|
|
Packit |
0ec9dd |
#include <stdlib.h>
|
|
Packit |
0ec9dd |
#include <string.h>
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
#include "pango-emoji-private.h"
|
|
Packit |
0ec9dd |
#include "pango-emoji-table.h"
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
static int
|
|
Packit |
0ec9dd |
interval_compare (const void *key, const void *elt)
|
|
Packit |
0ec9dd |
{
|
|
Packit |
0ec9dd |
gunichar c = GPOINTER_TO_UINT (key);
|
|
Packit |
0ec9dd |
struct Interval *interval = (struct Interval *)elt;
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
if (c < interval->start)
|
|
Packit |
0ec9dd |
return -1;
|
|
Packit |
0ec9dd |
if (c > interval->end)
|
|
Packit |
0ec9dd |
return +1;
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
return 0;
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
#define DEFINE_pango_Is_(name) \
|
|
Packit |
0ec9dd |
static gboolean \
|
|
Packit |
0ec9dd |
_pango_Is_##name (gunichar ch) \
|
|
Packit |
0ec9dd |
{ \
|
|
Packit |
0ec9dd |
/* bsearch() is declared attribute(nonnull(1)) so we can't validly search \
|
|
Packit |
0ec9dd |
* for a NULL key */ \
|
|
Packit |
0ec9dd |
/* \
|
|
Packit |
0ec9dd |
if (G_UNLIKELY (ch == 0)) \
|
|
Packit |
0ec9dd |
return FALSE; \
|
|
Packit |
0ec9dd |
*/ \
|
|
Packit |
0ec9dd |
\
|
|
Packit |
0ec9dd |
if (bsearch (GUINT_TO_POINTER (ch), \
|
|
Packit |
0ec9dd |
_pango_##name##_table, \
|
|
Packit |
0ec9dd |
G_N_ELEMENTS (_pango_##name##_table), \
|
|
Packit |
0ec9dd |
sizeof _pango_##name##_table[0], \
|
|
Packit |
0ec9dd |
interval_compare)) \
|
|
Packit |
0ec9dd |
return TRUE; \
|
|
Packit |
0ec9dd |
\
|
|
Packit |
0ec9dd |
return FALSE; \
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
DEFINE_pango_Is_(Emoji)
|
|
Packit |
0ec9dd |
DEFINE_pango_Is_(Emoji_Presentation)
|
|
Packit |
0ec9dd |
DEFINE_pango_Is_(Emoji_Modifier)
|
|
Packit |
0ec9dd |
DEFINE_pango_Is_(Emoji_Modifier_Base)
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
static gboolean
|
|
Packit |
0ec9dd |
_pango_Is_Emoji_Text_Default (gunichar ch)
|
|
Packit |
0ec9dd |
{
|
|
Packit |
0ec9dd |
return _pango_Is_Emoji (ch) && !_pango_Is_Emoji_Presentation (ch);
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
static gboolean
|
|
Packit |
0ec9dd |
_pango_Is_Emoji_Emoji_Default (gunichar ch)
|
|
Packit |
0ec9dd |
{
|
|
Packit |
0ec9dd |
return _pango_Is_Emoji_Presentation (ch);
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
static gboolean
|
|
Packit |
0ec9dd |
_pango_Is_Emoji_Keycap_Base (gunichar ch)
|
|
Packit |
0ec9dd |
{
|
|
Packit |
0ec9dd |
return (ch >= '0' && ch <= '9') || ch == '#' || ch == '*';
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
static gboolean
|
|
Packit |
0ec9dd |
_pango_Is_Regional_Indicator (gunichar ch)
|
|
Packit |
0ec9dd |
{
|
|
Packit |
0ec9dd |
return (ch >= 0x1F1E6 && ch <= 0x1F1FF);
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
const gunichar kCombiningEnclosingCircleBackslashCharacter = 0x20E0;
|
|
Packit |
0ec9dd |
const gunichar kCombiningEnclosingKeycapCharacter = 0x20E3;
|
|
Packit |
0ec9dd |
const gunichar kEyeCharacter = 0x1F441;
|
|
Packit |
0ec9dd |
const gunichar kFemaleSignCharacter = 0x2640;
|
|
Packit |
0ec9dd |
const gunichar kLeftSpeechBubbleCharacter = 0x1F5E8;
|
|
Packit |
0ec9dd |
const gunichar kMaleSignCharacter = 0x2642;
|
|
Packit |
0ec9dd |
const gunichar kRainbowCharacter = 0x1F308;
|
|
Packit |
0ec9dd |
const gunichar kStaffOfAesculapiusCharacter = 0x2695;
|
|
Packit |
0ec9dd |
const gunichar kVariationSelector15Character = 0xFE0E;
|
|
Packit |
0ec9dd |
const gunichar kVariationSelector16Character = 0xFE0F;
|
|
Packit |
0ec9dd |
const gunichar kWavingWhiteFlagCharacter = 0x1F3F3;
|
|
Packit |
0ec9dd |
const gunichar kZeroWidthJoinerCharacter = 0x200D;
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
typedef enum {
|
|
Packit |
0ec9dd |
PANGO_EMOJI_TYPE_INVALID,
|
|
Packit |
0ec9dd |
PANGO_EMOJI_TYPE_TEXT, /* For regular non-symbols text */
|
|
Packit |
0ec9dd |
PANGO_EMOJI_TYPE_EMOJI_TEXT, /* For emoji in text presentaiton */
|
|
Packit |
0ec9dd |
PANGO_EMOJI_TYPE_EMOJI_EMOJI /* For emoji in emoji presentation */
|
|
Packit |
0ec9dd |
} PangoEmojiType;
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
static PangoEmojiType
|
|
Packit |
0ec9dd |
_pango_get_emoji_type (gunichar codepoint)
|
|
Packit |
0ec9dd |
{
|
|
Packit |
0ec9dd |
/* Those should only be Emoji presentation as combinations of two. */
|
|
Packit |
0ec9dd |
if (_pango_Is_Emoji_Keycap_Base (codepoint) ||
|
|
Packit |
0ec9dd |
_pango_Is_Regional_Indicator (codepoint))
|
|
Packit |
0ec9dd |
return PANGO_EMOJI_TYPE_TEXT;
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
if (codepoint == kCombiningEnclosingKeycapCharacter)
|
|
Packit |
0ec9dd |
return PANGO_EMOJI_TYPE_EMOJI_EMOJI;
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
if (_pango_Is_Emoji_Emoji_Default (codepoint) ||
|
|
Packit |
0ec9dd |
_pango_Is_Emoji_Modifier_Base (codepoint) ||
|
|
Packit |
0ec9dd |
_pango_Is_Emoji_Modifier (codepoint))
|
|
Packit |
0ec9dd |
return PANGO_EMOJI_TYPE_EMOJI_EMOJI;
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
if (_pango_Is_Emoji_Text_Default (codepoint))
|
|
Packit |
0ec9dd |
return PANGO_EMOJI_TYPE_EMOJI_TEXT;
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
return PANGO_EMOJI_TYPE_TEXT;
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
PangoEmojiIter *
|
|
Packit |
0ec9dd |
_pango_emoji_iter_init (PangoEmojiIter *iter,
|
|
Packit |
0ec9dd |
const char *text,
|
|
Packit |
0ec9dd |
int length)
|
|
Packit |
0ec9dd |
{
|
|
Packit |
0ec9dd |
iter->text_start = text;
|
|
Packit |
0ec9dd |
if (length >= 0)
|
|
Packit |
0ec9dd |
iter->text_end = text + length;
|
|
Packit |
0ec9dd |
else
|
|
Packit |
0ec9dd |
iter->text_end = text + strlen (text);
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
iter->start = text;
|
|
Packit |
0ec9dd |
iter->end = text;
|
|
Packit |
0ec9dd |
iter->is_emoji = (gboolean) 2; /* HACK */
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
_pango_emoji_iter_next (iter);
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
return iter;
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
void
|
|
Packit |
0ec9dd |
_pango_emoji_iter_fini (PangoEmojiIter *iter)
|
|
Packit |
0ec9dd |
{
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
#define PANGO_EMOJI_TYPE_IS_EMOJI(typ) ((typ) == PANGO_EMOJI_TYPE_EMOJI_EMOJI)
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
gboolean
|
|
Packit |
0ec9dd |
_pango_emoji_iter_next (PangoEmojiIter *iter)
|
|
Packit |
0ec9dd |
{
|
|
Packit |
0ec9dd |
PangoEmojiType current_emoji_type = PANGO_EMOJI_TYPE_INVALID;
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
if (iter->end == iter->text_end)
|
|
Packit |
0ec9dd |
return FALSE;
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
iter->start = iter->end;
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
for (; iter->end < iter->text_end; iter->end = g_utf8_next_char (iter->end))
|
|
Packit |
0ec9dd |
{
|
|
Packit |
0ec9dd |
gunichar ch = g_utf8_get_char (iter->end);
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
/* Except at the beginning, ZWJ just carries over the emoji or neutral
|
|
Packit |
0ec9dd |
* text type, VS15 & VS16 we just carry over as well, since we already
|
|
Packit |
0ec9dd |
* resolved those through lookahead. Also, don't downgrade to text
|
|
Packit |
0ec9dd |
* presentation for emoji that are part of a ZWJ sequence, example
|
|
Packit |
0ec9dd |
* U+1F441 U+200D U+1F5E8, eye (text presentation) + ZWJ + left speech
|
|
Packit |
0ec9dd |
* bubble, see below. */
|
|
Packit |
0ec9dd |
if ((!(ch == kZeroWidthJoinerCharacter && !iter->is_emoji) &&
|
|
Packit |
0ec9dd |
ch != kVariationSelector15Character &&
|
|
Packit |
0ec9dd |
ch != kVariationSelector16Character &&
|
|
Packit |
0ec9dd |
ch != kCombiningEnclosingCircleBackslashCharacter &&
|
|
Packit |
0ec9dd |
!_pango_Is_Regional_Indicator(ch) &&
|
|
Packit |
0ec9dd |
!((ch == kLeftSpeechBubbleCharacter ||
|
|
Packit |
0ec9dd |
ch == kRainbowCharacter ||
|
|
Packit |
0ec9dd |
ch == kMaleSignCharacter ||
|
|
Packit |
0ec9dd |
ch == kFemaleSignCharacter ||
|
|
Packit |
0ec9dd |
ch == kStaffOfAesculapiusCharacter) &&
|
|
Packit |
0ec9dd |
!iter->is_emoji)) ||
|
|
Packit |
0ec9dd |
current_emoji_type == PANGO_EMOJI_TYPE_INVALID) {
|
|
Packit |
0ec9dd |
current_emoji_type = _pango_get_emoji_type (ch);
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
if (g_utf8_next_char (iter->end) < iter->text_end) /* Optimize. */
|
|
Packit |
0ec9dd |
{
|
|
Packit |
0ec9dd |
gunichar peek_char = g_utf8_get_char (g_utf8_next_char (iter->end));
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
/* Variation Selectors */
|
|
Packit |
0ec9dd |
if (current_emoji_type ==
|
|
Packit |
0ec9dd |
PANGO_EMOJI_TYPE_EMOJI_EMOJI &&
|
|
Packit |
0ec9dd |
peek_char == kVariationSelector15Character) {
|
|
Packit |
0ec9dd |
current_emoji_type = PANGO_EMOJI_TYPE_EMOJI_TEXT;
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
if ((current_emoji_type ==
|
|
Packit |
0ec9dd |
PANGO_EMOJI_TYPE_EMOJI_TEXT ||
|
|
Packit |
0ec9dd |
_pango_Is_Emoji_Keycap_Base(ch)) &&
|
|
Packit |
0ec9dd |
peek_char == kVariationSelector16Character) {
|
|
Packit |
0ec9dd |
current_emoji_type = PANGO_EMOJI_TYPE_EMOJI_EMOJI;
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
/* Combining characters Keycap... */
|
|
Packit |
0ec9dd |
if (_pango_Is_Emoji_Keycap_Base(ch) &&
|
|
Packit |
0ec9dd |
peek_char == kCombiningEnclosingKeycapCharacter) {
|
|
Packit |
0ec9dd |
current_emoji_type = PANGO_EMOJI_TYPE_EMOJI_EMOJI;
|
|
Packit |
0ec9dd |
};
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
/* Regional indicators */
|
|
Packit |
0ec9dd |
if (_pango_Is_Regional_Indicator(ch) &&
|
|
Packit |
0ec9dd |
_pango_Is_Regional_Indicator(peek_char)) {
|
|
Packit |
0ec9dd |
current_emoji_type = PANGO_EMOJI_TYPE_EMOJI_EMOJI;
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
/* Upgrade text presentation emoji to emoji presentation when followed by
|
|
Packit |
0ec9dd |
* ZWJ, Example U+1F441 U+200D U+1F5E8, eye + ZWJ + left speech bubble. */
|
|
Packit |
0ec9dd |
if ((ch == kEyeCharacter ||
|
|
Packit |
0ec9dd |
ch == kWavingWhiteFlagCharacter) &&
|
|
Packit |
0ec9dd |
peek_char == kZeroWidthJoinerCharacter) {
|
|
Packit |
0ec9dd |
current_emoji_type = PANGO_EMOJI_TYPE_EMOJI_EMOJI;
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
if (iter->is_emoji == (gboolean) 2)
|
|
Packit |
0ec9dd |
iter->is_emoji = !PANGO_EMOJI_TYPE_IS_EMOJI (current_emoji_type);
|
|
Packit |
0ec9dd |
if (iter->is_emoji == PANGO_EMOJI_TYPE_IS_EMOJI (current_emoji_type))
|
|
Packit |
0ec9dd |
{
|
|
Packit |
0ec9dd |
iter->is_emoji = !PANGO_EMOJI_TYPE_IS_EMOJI (current_emoji_type);
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
/* Make sure we make progress. Weird sequences, like a VC15 followed
|
|
Packit |
0ec9dd |
* by VC16, can trick us into stalling otherwise. */
|
|
Packit |
0ec9dd |
if (iter->start == iter->end)
|
|
Packit |
0ec9dd |
iter->end = g_utf8_next_char (iter->end);
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
return TRUE;
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
iter->is_emoji = PANGO_EMOJI_TYPE_IS_EMOJI (current_emoji_type);
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
return TRUE;
|
|
Packit |
0ec9dd |
}
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
|
|
Packit |
0ec9dd |
/**********************************************************
|
|
Packit |
0ec9dd |
* End of code from Chromium
|
|
Packit |
0ec9dd |
**********************************************************/
|