Blame pango/pango-emoji.c

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
 **********************************************************/