From f780ce0a49f2b221ca06d081363447645b7fde56 Mon Sep 17 00:00:00 2001 From: Gary Pendergast Date: Fri, 14 Jul 2017 05:46:19 +0000 Subject: [PATCH] Emoji: Port the Twemoji regex to PHP. Previously, `wp_encode_emoji()` and `wp_staticize_emoji()` used inaccurate regular expressions to find emoji, and transform then into HTML entities or ``s, respectively. This would result in emoji not being correctly transformed, or occasionally, non-emoji being incorrectly transformed. This commit adds a new `grunt` task - `grunt precommit:emoji`. It finds the regex in `twemoji.js`, transforms it into a PHP-friendly version, and adds it to `formatting.php`. This task is also automatically run by `grunt precommit`, when it detects that `twemoji.js` has changed. The new regex requires features introduced in PCRE 8.32, which was introduced in PHP 5.4.14, though it was also backported to later releases of the PHP 5.3 series. For versions of PHP that don't support this, it will fall back to an updated version of the loose-matching regex. For short posts, the performance difference between the old and new regex is negligible. As the posts get longer, however, the new method is exponentially faster. Fixes #35293. git-svn-id: https://develop.svn.wordpress.org/trunk@41043 602fd350-edb4-49c9-b593-d223f7449a82 --- Gruntfile.js | 100 ++++++++++++++++ package.json | 1 + src/wp-includes/formatting.php | 130 +++++++++++++-------- tests/phpunit/tests/formatting/Emoji.php | 111 +++++++++++++++--- tests/phpunit/tests/formatting/Smilies.php | 1 + 5 files changed, 278 insertions(+), 65 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index c7e7f73b2b..7e99ae9183 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -607,6 +607,97 @@ module.exports = function(grunt) { dest: '.' } }, + replace: { + emojiRegex: { + options: { + patterns: [ + { + match: /\/\/ START: emoji regex[\S\s]*\/\/ END: emoji regex/g, + replacement: function () { + var twemoji = grunt.file.read( SOURCE_DIR + 'wp-includes/js/twemoji.js' ), + found = twemoji.match( /re = \/(.*)\/g,/ ), + emojiRegex = found[1], + regex = '', + entities = ''; + + /* + * Twemoji does some nifty regex optimisations, splitting up surrogate pairs unit, searching by + * ranges of individual units, and compressing sets of individual units. This is super useful for + * reducing the size of the regex. + * + * Unfortunately, PCRE doesn't allow regexes to search for individual units, so we can't just + * blindly copy the Twemoji regex. + * + * The good news is, we don't have to worry about size restrictions, so we can just unravel the + * entire regex, and convert it to a PCRE-friendly format. + */ + + // Convert ranges: "\udc68-\udc6a" becomes "\udc68\udc69\udc6a". + emojiRegex = emojiRegex.replace( /(\\u\w{4})\-(\\u\w{4})/g, function ( match, first, last ) { + var start = parseInt( first.substr( 2 ), 16 ); + var end = parseInt( last.substr( 2 ), 16 ); + + var replace = ''; + + for( var counter = start; counter <= end; counter++ ) { + replace += '\\u' + counter.toString( 16 ); + } + + return replace; + } ); + + // Convert sets: "\u200d[\u2640\u2642]\ufe0f" becomes "\u200d\u2640\ufe0f|\u200d\u2642\ufe0f". + emojiRegex = emojiRegex.replace( /((?:\\u\w{4})*)\[((?:\\u\w{4})+)\]((?:\\u\w{4})*)/g, function ( match, before, middle, after ) { + //return params[1].split( '\\u' ).join( '|' + params[0] + '\\u' ).substr( 1 ); + if ( ! before && ! after ) { + return match; + } + var set = middle.match( /.{1,6}/g ); + + return before + set.join( after + '|' + before ) + after; + } ); + + // Convert surrogate pairs to their equivalent unicode scalar: "\ud83d\udc68" becomes "\u1f468". + emojiRegex = emojiRegex.replace( /(\\ud[89a-f][0-9a-f]{2})(\\ud[89a-f][0-9a-f]{2})/g, function ( match, first, second ) { + var high = parseInt( first.substr( 2 ), 16 ); + var low = parseInt( second.substr( 2 ), 16 ); + + var scalar = ( ( high - 0xD800 ) * 0x400 ) + ( low - 0xDC00 ) + 0x10000; + + return '\\u' + scalar.toString( 16 ); + } ); + + // Convert JavaScript-style code points to PHP-style: "\u1f468" becomes "\x{1f468}". + emojiRegex = emojiRegex.replace( /\\u(\w+)/g, '\\x{$1}' ); + + // Convert PHP-style code points to HTML entities: "\x{1f468}" becomes "👨". + entities = emojiRegex.replace( /\\x{(\w+)}/g, '&#x$1;' ); + entities = entities.replace( /\[([^\]]+)\]/g, function( match, codepoint ) { + return '(?:' + codepoint.replace( /;&/g, ';|&' ) + ')'; + } ); + + regex += '// START: emoji regex\n'; + regex += '\t$codepoints = \'/(' + emojiRegex + ')/u\';\n'; + regex += '\t$entities = \'/(' + entities + ')/u\';\n'; + regex += '\t// END: emoji regex'; + + return regex; + } + } + ] + }, + files: [ + { + expand: true, + flatten: true, + src: [ + SOURCE_DIR + 'wp-includes/formatting.php' + ], + dest: SOURCE_DIR + 'wp-includes/' + } + ] + } + }, _watch: { all: { files: [ @@ -718,6 +809,10 @@ module.exports = function(grunt) { 'phpunit' ] ); + grunt.registerTask( 'precommit:emoji', [ + 'replace:emojiRegex' + ] ); + grunt.registerTask( 'precommit', 'Runs test and build tasks in preparation for a commit', function() { var done = this.async(); var map = { @@ -783,6 +878,11 @@ module.exports = function(grunt) { taskList.push( 'precommit:' + extension ); } } ); + + if ( [ 'twemoji.js' ].some( testPath ) ) { + grunt.log.writeln( 'twemoji.js has updated. Running `precommit:emoji.' ); + taskList.push( 'precommit:emoji' ); + } } grunt.task.run( taskList ); diff --git a/package.json b/package.json index 047d5fc35d..0fda642c49 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "grunt-legacy-util": "^0.2.0", "grunt-patch-wordpress": "~0.4.2", "grunt-postcss": "~0.7.1", + "grunt-replace": "~1.0.1", "grunt-rtlcss": "~2.0.1", "grunt-sass": "~1.2.1", "matchdep": "~1.0.0" diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index 3972c9fb9f..bac3ad787d 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -5094,10 +5094,7 @@ function _print_emoji_detection_script() { } /** - * Convert any 4 byte emoji in a string to their equivalent HTML entity. - * - * Currently, only Unicode 7 emoji are supported. Skin tone modifiers are allowed, - * all other Unicode 8 emoji will be added when the spec is finalised. + * Convert emoji characters to their equivalent HTML entity. * * This allows us to store emoji in a DB using the utf8 character set. * @@ -5108,28 +5105,26 @@ function _print_emoji_detection_script() { */ function wp_encode_emoji( $content ) { if ( function_exists( 'mb_convert_encoding' ) ) { - $regex = '/( - \x23\xE2\x83\xA3 # Digits - [\x30-\x39]\xE2\x83\xA3 - | \xF0\x9F[\x85-\x88][\xA6-\xBF] # Enclosed characters - | \xF0\x9F[\x8C-\x97][\x80-\xBF] # Misc - | \xF0\x9F\x98[\x80-\xBF] # Smilies - | \xF0\x9F\x99[\x80-\x8F] - | \xF0\x9F\x9A[\x80-\xBF] # Transport and map symbols - )/x'; + $regex = wp_emoji_regex( 'codepoints' ); $matches = array(); if ( preg_match_all( $regex, $content, $matches ) ) { if ( ! empty( $matches[1] ) ) { - foreach ( $matches[1] as $emoji ) { + foreach ( array_unique( $matches[1] ) as $emoji ) { /* * UTF-32's hex encoding is the same as HTML's hex encoding. * So, by converting the emoji from UTF-8 to UTF-32, we magically - * get the correct hex encoding. + * get the correct hex encoding, 0 padded to 8 characters. */ $unpacked = unpack( 'H*', mb_convert_encoding( $emoji, 'UTF-32', 'UTF-8' ) ); if ( isset( $unpacked[1] ) ) { - $entity = '&#x' . ltrim( $unpacked[1], '0' ) . ';'; + $parts = str_split( $unpacked[1], 8 ); + $entity = ''; + + foreach ( $parts as $part ) { + $entity .= '&#x' . ltrim( $part, '0' ) . ';'; + } + $content = str_replace( $emoji, $entity, $content ); } } @@ -5151,12 +5146,6 @@ function wp_encode_emoji( $content ) { function wp_staticize_emoji( $text ) { $text = wp_encode_emoji( $text ); - /** This filter is documented in wp-includes/formatting.php */ - $cdn_url = apply_filters( 'emoji_url', 'https://s.w.org/images/core/emoji/2.3/72x72/' ); - - /** This filter is documented in wp-includes/formatting.php */ - $ext = apply_filters( 'emoji_ext', '.png' ); - $output = ''; /* * HTML loop taken from smiley function, which was taken from texturize function. @@ -5171,6 +5160,8 @@ function wp_staticize_emoji( $text ) { $tags_to_ignore = 'code|pre|style|script|textarea'; $ignore_block_element = ''; + $regex = wp_emoji_regex( 'entities' ); + for ( $i = 0; $i < $stop; $i++ ) { $content = $textarr[$i]; @@ -5181,34 +5172,7 @@ function wp_staticize_emoji( $text ) { // If it's not a tag and not in ignore block. if ( '' == $ignore_block_element && strlen( $content ) > 0 && '<' != $content[0] ) { - $matches = array(); - if ( preg_match_all( '/(DZ(e[6-9a-f]|f[0-9a-f]);){2}/', $content, $matches ) ) { - if ( ! empty( $matches[0] ) ) { - foreach ( $matches[0] as $flag ) { - $chars = str_replace( array( '&#x', ';'), '', $flag ); - - list( $char1, $char2 ) = str_split( $chars, 5 ); - $entity = sprintf( '%s', $cdn_url . $char1 . '-' . $char2 . $ext, html_entity_decode( $flag ) ); - - $content = str_replace( $flag, $entity, $content ); - } - } - } - - // Loosely match the Emoji Unicode range. - $regex = '/(&#x[2-3][0-9a-f]{3};|[1-6][0-9a-f]{2};)/'; - - $matches = array(); - if ( preg_match_all( $regex, $content, $matches ) ) { - if ( ! empty( $matches[1] ) ) { - foreach ( $matches[1] as $emoji ) { - $char = str_replace( array( '&#x', ';'), '', $emoji ); - $entity = sprintf( '%s', $cdn_url . $char . $ext, html_entity_decode( $emoji ) ); - - $content = str_replace( $emoji, $entity, $content ); - } - } - } + $content = preg_replace_callback( $regex, '_wp_staticize_emoji', $content ); } // Did we exit ignore block. @@ -5222,6 +5186,28 @@ function wp_staticize_emoji( $text ) { return $output; } +/** + * Callback for wp_staticize_emoji() to turn matched emoji glyphs into images. + * + * @since 4.8.1 + * @access private + * + * @param array $matches The matched data. + * @return string HTML for the static emoji image. + */ +function _wp_staticize_emoji( $matches ) { + /** This filter is documented in wp-includes/formatting.php */ + $cdn_url = apply_filters( 'emoji_url', 'https://s.w.org/images/core/emoji/2.3/72x72/' ); + + /** This filter is documented in wp-includes/formatting.php */ + $ext = apply_filters( 'emoji_ext', '.png' ); + + $char = str_replace( ';&#x', '-', $matches[1] ); + $char = str_replace( array( '&#x', ';'), '', $char ); + + return sprintf( '%s', $cdn_url . $char . $ext, html_entity_decode( $matches[1] ) ); +} + /** * Convert emoji in emails into static images. * @@ -5289,6 +5275,50 @@ function wp_staticize_emoji_for_email( $mail ) { return $mail; } +/** + * Returns a regex string to match all emoji that WordPress recognises. + * + * This regex is automatically built from the regex in twemoji.js - if it needs to be updated, + * you should update the regex there, then run the `grunt precommit:emoji` job. + * + * @since 4.8.1 + * + * @param string $type Optional. Which regex type to return. Accepts 'codepoints' or 'entities', default 'coidepoints'. + * @return string A regex to match all emoji that WordPress recognises. + */ +function wp_emoji_regex( $type = 'codepoints' ) { + // If we're using a PCRE version that doesn't support Unicode, return a loose match regex. + if ( 'codepoints' === $type && ( ! defined( 'PCRE_VERSION' ) || version_compare( PCRE_VERSION, '8.32', '<=' ) ) ) { + return '/( + \xE2\x98[\x80-\xFF] # Symbols + | \xE2\x99[\x00-\xFF] + | [\xE3-\xED][\x00-\xFF]{2} + | [\x23\x30-\x39]\xE2\x83\xA3 # Digits + | \xF0\x9F[\x85-\x88][\xA6-\xBF] # Enclosed characters + | \xF0\x9F[\x8C-\x97][\x80-\xBF] # Misc + | \xF0\x9F\x98[\x80-\xBF] # Smilies + | \xF0\x9F\x99[\x80-\x8F] + | \xF0\x9F[\xA4-\xA7][\x00-\xFF] + | \xF0\x9F\x9A[\x80-\xBF] # Transport and map symbols + | \xE2\x80\x8D # Zero Width Joiner + | \xEF\xB8\x8F # Emoji Variation Selector + )/x'; + } + + // Do not remove the START/END comments - they're used to find where to insert the regex. + + // START: emoji regex + $codepoints = '/(\x{1f468}|\x{1f469}(?:\x{1f3fb}|\x{1f3fc}|\x{1f3fd}|\x{1f3fe}|\x{1f3ff})?\x{200d}(?:\x{2695}\x{fe0f}|\x{2696}\x{fe0f}|\x{2708}\x{fe0f}|\x{1f33e}|\x{1f373}|\x{1f393}|\x{1f3a4}|\x{1f3a8}|\x{1f3eb}|\x{1f3ed}|\x{1f4bb}|\x{1f4bc}|\x{1f527}|\x{1f52c}|\x{1f680}|\x{1f692})|(?:\x{1f3cb}|\x{1f3cc}|\x{1f575}|\x{26f9})(?:\x{fe0f}|\x{1f3fb}|\x{1f3fc}|\x{1f3fd}|\x{1f3fe}|\x{1f3ff})\x{200d}\x{2640}\x{fe0f}|\x{200d}\x{2642}\x{fe0f}|(?:\x{1f3c3}|\x{1f3c4}|\x{1f3ca}|\x{1f46e}|\x{1f471}|\x{1f473}|\x{1f477}|\x{1f481}|\x{1f482}|\x{1f486}|\x{1f487}|\x{1f645}|\x{1f646}|\x{1f647}|\x{1f64b}|\x{1f64d}|\x{1f64e}|\x{1f6a3}|\x{1f6b4}|\x{1f6b5}|\x{1f6b6}|\x{1f926}|\x{1f937}|\x{1f938}|\x{1f939}|\x{1f93d}|\x{1f93e}|\x{1f9d6}|\x{1f9d7}|\x{1f9d8}|\x{1f9d9}|\x{1f9da}|\x{1f9db}|\x{1f9dc}|\x{1f9dd})(?:\x{1f3fb}|\x{1f3fc}|\x{1f3fd}|\x{1f3fe}|\x{1f3ff})?\x{200d}\x{2640}\x{fe0f}|\x{200d}\x{2642}\x{fe0f}|\x{1f468}\x{200d}\x{2764}\x{fe0f}\x{200d}\x{1f48b}\x{200d}\x{1f468}|\x{1f468}\x{200d}\x{1f468}\x{200d}\x{1f466}\x{200d}\x{1f466}|\x{1f468}\x{200d}\x{1f468}\x{200d}\x{1f467}\x{200d}\x{1f466}|\x{1f468}\x{200d}\x{1f468}\x{200d}\x{1f467}\x{200d}\x{1f467}|\x{1f468}\x{200d}\x{1f469}\x{200d}\x{1f466}\x{200d}\x{1f466}|\x{1f468}\x{200d}\x{1f469}\x{200d}\x{1f467}\x{200d}\x{1f466}|\x{1f468}\x{200d}\x{1f469}\x{200d}\x{1f467}\x{200d}\x{1f467}|\x{1f469}\x{200d}\x{2764}\x{fe0f}\x{200d}\x{1f48b}\x{200d}\x{1f468}|\x{1f469}\x{200d}\x{2764}\x{fe0f}\x{200d}\x{1f48b}\x{200d}\x{1f469}|\x{1f469}\x{200d}\x{1f469}\x{200d}\x{1f466}\x{200d}\x{1f466}|\x{1f469}\x{200d}\x{1f469}\x{200d}\x{1f467}\x{200d}\x{1f466}|\x{1f469}\x{200d}\x{1f469}\x{200d}\x{1f467}\x{200d}\x{1f467}|\x{1f468}\x{200d}\x{2764}\x{fe0f}\x{200d}\x{1f468}|\x{1f468}\x{200d}\x{1f466}\x{200d}\x{1f466}|\x{1f468}\x{200d}\x{1f467}\x{200d}\x{1f466}|\x{1f468}\x{200d}\x{1f467}\x{200d}\x{1f467}|\x{1f468}\x{200d}\x{1f468}\x{200d}\x{1f466}|\x{1f468}\x{200d}\x{1f468}\x{200d}\x{1f467}|\x{1f468}\x{200d}\x{1f469}\x{200d}\x{1f466}|\x{1f468}\x{200d}\x{1f469}\x{200d}\x{1f467}|\x{1f469}\x{200d}\x{2764}\x{fe0f}\x{200d}\x{1f468}|\x{1f469}\x{200d}\x{2764}\x{fe0f}\x{200d}\x{1f469}|\x{1f469}\x{200d}\x{1f466}\x{200d}\x{1f466}|\x{1f469}\x{200d}\x{1f467}\x{200d}\x{1f466}|\x{1f469}\x{200d}\x{1f467}\x{200d}\x{1f467}|\x{1f469}\x{200d}\x{1f469}\x{200d}\x{1f466}|\x{1f469}\x{200d}\x{1f469}\x{200d}\x{1f467}|\x{1f3f3}\x{fe0f}\x{200d}\x{1f308}|\x{1f3f4}\x{200d}\x{2620}\x{fe0f}|\x{1f441}\x{200d}\x{1f5e8}|\x{1f468}\x{200d}\x{1f466}|\x{1f468}\x{200d}\x{1f467}|\x{1f469}\x{200d}\x{1f466}|\x{1f469}\x{200d}\x{1f467}|\x{1f46f}\x{200d}\x{2640}\x{fe0f}|\x{1f46f}\x{200d}\x{2642}\x{fe0f}|\x{1f93c}\x{200d}\x{2640}\x{fe0f}|\x{1f93c}\x{200d}\x{2642}\x{fe0f}|\x{1f9de}\x{200d}\x{2640}\x{fe0f}|\x{1f9de}\x{200d}\x{2642}\x{fe0f}|\x{1f9df}\x{200d}\x{2640}\x{fe0f}|\x{1f9df}\x{200d}\x{2642}\x{fe0f}|(?:[\x{0023}\x{002a}\x{30}\x{31}\x{32}\x{33}\x{34}\x{35}\x{36}\x{37}\x{38}\x{39}])\x{fe0f}?\x{20e3}|(?:(?:\x{1f3cb}|\x{1f3cc}|\x{1f574}|\x{1f575}|\x{1f590}|[\x{261d}\x{26f7}\x{26f9}\x{270c}\x{270d}])(?:\x{fe0f}|(?!\x{fe0e}))|\x{1f385}|\x{1f3c2}|\x{1f3c3}|\x{1f3c4}|\x{1f3c7}|\x{1f3ca}|\x{1f442}|\x{1f443}|\x{1f446}|\x{1f447}|\x{1f448}|\x{1f449}|\x{1f44a}|\x{1f44b}|\x{1f44c}|\x{1f44d}|\x{1f44e}|\x{1f44f}|\x{1f450}|\x{1f466}|\x{1f467}|\x{1f468}|\x{1f469}|\x{1f46e}|\x{1f470}|\x{1f471}|\x{1f472}|\x{1f473}|\x{1f474}|\x{1f475}|\x{1f476}|\x{1f477}|\x{1f478}|\x{1f47c}|\x{1f481}|\x{1f482}|\x{1f483}|\x{1f485}|\x{1f486}|\x{1f487}|\x{1f4aa}|\x{1f57a}|\x{1f595}|\x{1f596}|\x{1f645}|\x{1f646}|\x{1f647}|\x{1f64b}|\x{1f64c}|\x{1f64d}|\x{1f64e}|\x{1f64f}|\x{1f6a3}|\x{1f6b4}|\x{1f6b5}|\x{1f6b6}|\x{1f6c0}|\x{1f6cc}|\x{1f918}|\x{1f919}|\x{1f91a}|\x{1f91b}|\x{1f91c}|\x{1f91e}|\x{1f91f}|\x{1f926}|\x{1f930}|\x{1f931}|\x{1f932}|\x{1f933}|\x{1f934}|\x{1f935}|\x{1f936}|\x{1f937}|\x{1f938}|\x{1f939}|\x{1f93d}|\x{1f93e}|\x{1f9d1}|\x{1f9d2}|\x{1f9d3}|\x{1f9d4}|\x{1f9d5}|\x{1f9d6}|\x{1f9d7}|\x{1f9d8}|\x{1f9d9}|\x{1f9da}|\x{1f9db}|\x{1f9dc}|\x{1f9dd}|[\x{270a}\x{270b}])(?:\x{1f3fb}|\x{1f3fc}|\x{1f3fd}|\x{1f3fe}|\x{1f3ff}|)|\x{1f3f4}\x{e0067}\x{e0062}\x{e0065}\x{e006e}\x{e0067}\x{e007f}|\x{1f3f4}\x{e0067}\x{e0062}\x{e0073}\x{e0063}\x{e0074}\x{e007f}|\x{1f3f4}\x{e0067}\x{e0062}\x{e0077}\x{e006c}\x{e0073}\x{e007f}|\x{1f1e6}\x{1f1e8}|\x{1f1e6}\x{1f1e9}|\x{1f1e6}\x{1f1ea}|\x{1f1e6}\x{1f1eb}|\x{1f1e6}\x{1f1ec}|\x{1f1e6}\x{1f1ee}|\x{1f1e6}\x{1f1f1}|\x{1f1e6}\x{1f1f2}|\x{1f1e6}\x{1f1f4}|\x{1f1e6}\x{1f1f6}|\x{1f1e6}\x{1f1f7}|\x{1f1e6}\x{1f1f8}|\x{1f1e6}\x{1f1f9}|\x{1f1e6}\x{1f1fa}|\x{1f1e6}\x{1f1fc}|\x{1f1e6}\x{1f1fd}|\x{1f1e6}\x{1f1ff}|\x{1f1e7}\x{1f1e6}|\x{1f1e7}\x{1f1e7}|\x{1f1e7}\x{1f1e9}|\x{1f1e7}\x{1f1ea}|\x{1f1e7}\x{1f1eb}|\x{1f1e7}\x{1f1ec}|\x{1f1e7}\x{1f1ed}|\x{1f1e7}\x{1f1ee}|\x{1f1e7}\x{1f1ef}|\x{1f1e7}\x{1f1f1}|\x{1f1e7}\x{1f1f2}|\x{1f1e7}\x{1f1f3}|\x{1f1e7}\x{1f1f4}|\x{1f1e7}\x{1f1f6}|\x{1f1e7}\x{1f1f7}|\x{1f1e7}\x{1f1f8}|\x{1f1e7}\x{1f1f9}|\x{1f1e7}\x{1f1fb}|\x{1f1e7}\x{1f1fc}|\x{1f1e7}\x{1f1fe}|\x{1f1e7}\x{1f1ff}|\x{1f1e8}\x{1f1e6}|\x{1f1e8}\x{1f1e8}|\x{1f1e8}\x{1f1e9}|\x{1f1e8}\x{1f1eb}|\x{1f1e8}\x{1f1ec}|\x{1f1e8}\x{1f1ed}|\x{1f1e8}\x{1f1ee}|\x{1f1e8}\x{1f1f0}|\x{1f1e8}\x{1f1f1}|\x{1f1e8}\x{1f1f2}|\x{1f1e8}\x{1f1f3}|\x{1f1e8}\x{1f1f4}|\x{1f1e8}\x{1f1f5}|\x{1f1e8}\x{1f1f7}|\x{1f1e8}\x{1f1fa}|\x{1f1e8}\x{1f1fb}|\x{1f1e8}\x{1f1fc}|\x{1f1e8}\x{1f1fd}|\x{1f1e8}\x{1f1fe}|\x{1f1e8}\x{1f1ff}|\x{1f1e9}\x{1f1ea}|\x{1f1e9}\x{1f1ec}|\x{1f1e9}\x{1f1ef}|\x{1f1e9}\x{1f1f0}|\x{1f1e9}\x{1f1f2}|\x{1f1e9}\x{1f1f4}|\x{1f1e9}\x{1f1ff}|\x{1f1ea}\x{1f1e6}|\x{1f1ea}\x{1f1e8}|\x{1f1ea}\x{1f1ea}|\x{1f1ea}\x{1f1ec}|\x{1f1ea}\x{1f1ed}|\x{1f1ea}\x{1f1f7}|\x{1f1ea}\x{1f1f8}|\x{1f1ea}\x{1f1f9}|\x{1f1ea}\x{1f1fa}|\x{1f1eb}\x{1f1ee}|\x{1f1eb}\x{1f1ef}|\x{1f1eb}\x{1f1f0}|\x{1f1eb}\x{1f1f2}|\x{1f1eb}\x{1f1f4}|\x{1f1eb}\x{1f1f7}|\x{1f1ec}\x{1f1e6}|\x{1f1ec}\x{1f1e7}|\x{1f1ec}\x{1f1e9}|\x{1f1ec}\x{1f1ea}|\x{1f1ec}\x{1f1eb}|\x{1f1ec}\x{1f1ec}|\x{1f1ec}\x{1f1ed}|\x{1f1ec}\x{1f1ee}|\x{1f1ec}\x{1f1f1}|\x{1f1ec}\x{1f1f2}|\x{1f1ec}\x{1f1f3}|\x{1f1ec}\x{1f1f5}|\x{1f1ec}\x{1f1f6}|\x{1f1ec}\x{1f1f7}|\x{1f1ec}\x{1f1f8}|\x{1f1ec}\x{1f1f9}|\x{1f1ec}\x{1f1fa}|\x{1f1ec}\x{1f1fc}|\x{1f1ec}\x{1f1fe}|\x{1f1ed}\x{1f1f0}|\x{1f1ed}\x{1f1f2}|\x{1f1ed}\x{1f1f3}|\x{1f1ed}\x{1f1f7}|\x{1f1ed}\x{1f1f9}|\x{1f1ed}\x{1f1fa}|\x{1f1ee}\x{1f1e8}|\x{1f1ee}\x{1f1e9}|\x{1f1ee}\x{1f1ea}|\x{1f1ee}\x{1f1f1}|\x{1f1ee}\x{1f1f2}|\x{1f1ee}\x{1f1f3}|\x{1f1ee}\x{1f1f4}|\x{1f1ee}\x{1f1f6}|\x{1f1ee}\x{1f1f7}|\x{1f1ee}\x{1f1f8}|\x{1f1ee}\x{1f1f9}|\x{1f1ef}\x{1f1ea}|\x{1f1ef}\x{1f1f2}|\x{1f1ef}\x{1f1f4}|\x{1f1ef}\x{1f1f5}|\x{1f1f0}\x{1f1ea}|\x{1f1f0}\x{1f1ec}|\x{1f1f0}\x{1f1ed}|\x{1f1f0}\x{1f1ee}|\x{1f1f0}\x{1f1f2}|\x{1f1f0}\x{1f1f3}|\x{1f1f0}\x{1f1f5}|\x{1f1f0}\x{1f1f7}|\x{1f1f0}\x{1f1fc}|\x{1f1f0}\x{1f1fe}|\x{1f1f0}\x{1f1ff}|\x{1f1f1}\x{1f1e6}|\x{1f1f1}\x{1f1e7}|\x{1f1f1}\x{1f1e8}|\x{1f1f1}\x{1f1ee}|\x{1f1f1}\x{1f1f0}|\x{1f1f1}\x{1f1f7}|\x{1f1f1}\x{1f1f8}|\x{1f1f1}\x{1f1f9}|\x{1f1f1}\x{1f1fa}|\x{1f1f1}\x{1f1fb}|\x{1f1f1}\x{1f1fe}|\x{1f1f2}\x{1f1e6}|\x{1f1f2}\x{1f1e8}|\x{1f1f2}\x{1f1e9}|\x{1f1f2}\x{1f1ea}|\x{1f1f2}\x{1f1eb}|\x{1f1f2}\x{1f1ec}|\x{1f1f2}\x{1f1ed}|\x{1f1f2}\x{1f1f0}|\x{1f1f2}\x{1f1f1}|\x{1f1f2}\x{1f1f2}|\x{1f1f2}\x{1f1f3}|\x{1f1f2}\x{1f1f4}|\x{1f1f2}\x{1f1f5}|\x{1f1f2}\x{1f1f6}|\x{1f1f2}\x{1f1f7}|\x{1f1f2}\x{1f1f8}|\x{1f1f2}\x{1f1f9}|\x{1f1f2}\x{1f1fa}|\x{1f1f2}\x{1f1fb}|\x{1f1f2}\x{1f1fc}|\x{1f1f2}\x{1f1fd}|\x{1f1f2}\x{1f1fe}|\x{1f1f2}\x{1f1ff}|\x{1f1f3}\x{1f1e6}|\x{1f1f3}\x{1f1e8}|\x{1f1f3}\x{1f1ea}|\x{1f1f3}\x{1f1eb}|\x{1f1f3}\x{1f1ec}|\x{1f1f3}\x{1f1ee}|\x{1f1f3}\x{1f1f1}|\x{1f1f3}\x{1f1f4}|\x{1f1f3}\x{1f1f5}|\x{1f1f3}\x{1f1f7}|\x{1f1f3}\x{1f1fa}|\x{1f1f3}\x{1f1ff}|\x{1f1f4}\x{1f1f2}|\x{1f1f5}\x{1f1e6}|\x{1f1f5}\x{1f1ea}|\x{1f1f5}\x{1f1eb}|\x{1f1f5}\x{1f1ec}|\x{1f1f5}\x{1f1ed}|\x{1f1f5}\x{1f1f0}|\x{1f1f5}\x{1f1f1}|\x{1f1f5}\x{1f1f2}|\x{1f1f5}\x{1f1f3}|\x{1f1f5}\x{1f1f7}|\x{1f1f5}\x{1f1f8}|\x{1f1f5}\x{1f1f9}|\x{1f1f5}\x{1f1fc}|\x{1f1f5}\x{1f1fe}|\x{1f1f6}\x{1f1e6}|\x{1f1f7}\x{1f1ea}|\x{1f1f7}\x{1f1f4}|\x{1f1f7}\x{1f1f8}|\x{1f1f7}\x{1f1fa}|\x{1f1f7}\x{1f1fc}|\x{1f1f8}\x{1f1e6}|\x{1f1f8}\x{1f1e7}|\x{1f1f8}\x{1f1e8}|\x{1f1f8}\x{1f1e9}|\x{1f1f8}\x{1f1ea}|\x{1f1f8}\x{1f1ec}|\x{1f1f8}\x{1f1ed}|\x{1f1f8}\x{1f1ee}|\x{1f1f8}\x{1f1ef}|\x{1f1f8}\x{1f1f0}|\x{1f1f8}\x{1f1f1}|\x{1f1f8}\x{1f1f2}|\x{1f1f8}\x{1f1f3}|\x{1f1f8}\x{1f1f4}|\x{1f1f8}\x{1f1f7}|\x{1f1f8}\x{1f1f8}|\x{1f1f8}\x{1f1f9}|\x{1f1f8}\x{1f1fb}|\x{1f1f8}\x{1f1fd}|\x{1f1f8}\x{1f1fe}|\x{1f1f8}\x{1f1ff}|\x{1f1f9}\x{1f1e6}|\x{1f1f9}\x{1f1e8}|\x{1f1f9}\x{1f1e9}|\x{1f1f9}\x{1f1eb}|\x{1f1f9}\x{1f1ec}|\x{1f1f9}\x{1f1ed}|\x{1f1f9}\x{1f1ef}|\x{1f1f9}\x{1f1f0}|\x{1f1f9}\x{1f1f1}|\x{1f1f9}\x{1f1f2}|\x{1f1f9}\x{1f1f3}|\x{1f1f9}\x{1f1f4}|\x{1f1f9}\x{1f1f7}|\x{1f1f9}\x{1f1f9}|\x{1f1f9}\x{1f1fb}|\x{1f1f9}\x{1f1fc}|\x{1f1f9}\x{1f1ff}|\x{1f1fa}\x{1f1e6}|\x{1f1fa}\x{1f1ec}|\x{1f1fa}\x{1f1f2}|\x{1f1fa}\x{1f1f3}|\x{1f1fa}\x{1f1f8}|\x{1f1fa}\x{1f1fe}|\x{1f1fa}\x{1f1ff}|\x{1f1fb}\x{1f1e6}|\x{1f1fb}\x{1f1e8}|\x{1f1fb}\x{1f1ea}|\x{1f1fb}\x{1f1ec}|\x{1f1fb}\x{1f1ee}|\x{1f1fb}\x{1f1f3}|\x{1f1fb}\x{1f1fa}|\x{1f1fc}\x{1f1eb}|\x{1f1fc}\x{1f1f8}|\x{1f1fd}\x{1f1f0}|\x{1f1fe}\x{1f1ea}|\x{1f1fe}\x{1f1f9}|\x{1f1ff}\x{1f1e6}|\x{1f1ff}\x{1f1f2}|\x{1f1ff}\x{1f1fc}|\x{10000}|\x{1f0cf}|\x{1f18e}|\x{1f191}|\x{1f192}|\x{1f193}|\x{1f194}|\x{1f195}|\x{1f196}|\x{1f197}|\x{1f198}|\x{1f199}|\x{1f19a}|\x{1f1e6}|\x{1f1e7}|\x{1f1e8}|\x{1f1e9}|\x{1f1ea}|\x{1f1eb}|\x{1f1ec}|\x{1f1ed}|\x{1f1ee}|\x{1f1ef}|\x{1f1f0}|\x{1f1f1}|\x{1f1f2}|\x{1f1f3}|\x{1f1f4}|\x{1f1f5}|\x{1f1f6}|\x{1f1f7}|\x{1f1f8}|\x{1f1f9}|\x{1f1fa}|\x{1f1fb}|\x{1f1fc}|\x{1f1fd}|\x{1f1fe}|\x{1f1ff}|\x{1f201}|\x{1f232}|\x{1f233}|\x{1f234}|\x{1f235}|\x{1f236}|\x{1f238}|\x{1f239}|\x{1f23a}|\x{1f250}|\x{1f251}|\x{1f300}|\x{1f301}|\x{1f302}|\x{1f303}|\x{1f304}|\x{1f305}|\x{1f306}|\x{1f307}|\x{1f308}|\x{1f309}|\x{1f30a}|\x{1f30b}|\x{1f30c}|\x{1f30d}|\x{1f30e}|\x{1f30f}|\x{1f310}|\x{1f311}|\x{1f312}|\x{1f313}|\x{1f314}|\x{1f315}|\x{1f316}|\x{1f317}|\x{1f318}|\x{1f319}|\x{1f31a}|\x{1f31b}|\x{1f31c}|\x{1f31d}|\x{1f31e}|\x{1f31f}|\x{1f320}|\x{1f32d}|\x{1f32e}|\x{1f32f}|\x{1f330}|\x{1f331}|\x{1f332}|\x{1f333}|\x{1f334}|\x{1f335}|\x{1f337}|\x{1f338}|\x{1f339}|\x{1f33a}|\x{1f33b}|\x{1f33c}|\x{1f33d}|\x{1f33e}|\x{1f33f}|\x{1f340}|\x{1f341}|\x{1f342}|\x{1f343}|\x{1f344}|\x{1f345}|\x{1f346}|\x{1f347}|\x{1f348}|\x{1f349}|\x{1f34a}|\x{1f34b}|\x{1f34c}|\x{1f34d}|\x{1f34e}|\x{1f34f}|\x{1f350}|\x{1f351}|\x{1f352}|\x{1f353}|\x{1f354}|\x{1f355}|\x{1f356}|\x{1f357}|\x{1f358}|\x{1f359}|\x{1f35a}|\x{1f35b}|\x{1f35c}|\x{1f35d}|\x{1f35e}|\x{1f35f}|\x{1f360}|\x{1f361}|\x{1f362}|\x{1f363}|\x{1f364}|\x{1f365}|\x{1f366}|\x{1f367}|\x{1f368}|\x{1f369}|\x{1f36a}|\x{1f36b}|\x{1f36c}|\x{1f36d}|\x{1f36e}|\x{1f36f}|\x{1f370}|\x{1f371}|\x{1f372}|\x{1f373}|\x{1f374}|\x{1f375}|\x{1f376}|\x{1f377}|\x{1f378}|\x{1f379}|\x{1f37a}|\x{1f37b}|\x{1f37c}|\x{1f37e}|\x{1f37f}|\x{1f380}|\x{1f381}|\x{1f382}|\x{1f383}|\x{1f384}|\x{1f386}|\x{1f387}|\x{1f388}|\x{1f389}|\x{1f38a}|\x{1f38b}|\x{1f38c}|\x{1f38d}|\x{1f38e}|\x{1f38f}|\x{1f390}|\x{1f391}|\x{1f392}|\x{1f393}|\x{1f3a0}|\x{1f3a1}|\x{1f3a2}|\x{1f3a3}|\x{1f3a4}|\x{1f3a5}|\x{1f3a6}|\x{1f3a7}|\x{1f3a8}|\x{1f3a9}|\x{1f3aa}|\x{1f3ab}|\x{1f3ac}|\x{1f3ad}|\x{1f3ae}|\x{1f3af}|\x{1f3b0}|\x{1f3b1}|\x{1f3b2}|\x{1f3b3}|\x{1f3b4}|\x{1f3b5}|\x{1f3b6}|\x{1f3b7}|\x{1f3b8}|\x{1f3b9}|\x{1f3ba}|\x{1f3bb}|\x{1f3bc}|\x{1f3bd}|\x{1f3be}|\x{1f3bf}|\x{1f3c0}|\x{1f3c1}|\x{1f3c5}|\x{1f3c6}|\x{1f3c8}|\x{1f3c9}|\x{1f3cf}|\x{1f3d0}|\x{1f3d1}|\x{1f3d2}|\x{1f3d3}|\x{1f3e0}|\x{1f3e1}|\x{1f3e2}|\x{1f3e3}|\x{1f3e4}|\x{1f3e5}|\x{1f3e6}|\x{1f3e7}|\x{1f3e8}|\x{1f3e9}|\x{1f3ea}|\x{1f3eb}|\x{1f3ec}|\x{1f3ed}|\x{1f3ee}|\x{1f3ef}|\x{1f3f0}|\x{1f3f4}|\x{1f3f8}|\x{1f3f9}|\x{1f3fa}|\x{1f3fb}|\x{1f3fc}|\x{1f3fd}|\x{1f3fe}|\x{1f3ff}|\x{1f400}|\x{1f401}|\x{1f402}|\x{1f403}|\x{1f404}|\x{1f405}|\x{1f406}|\x{1f407}|\x{1f408}|\x{1f409}|\x{1f40a}|\x{1f40b}|\x{1f40c}|\x{1f40d}|\x{1f40e}|\x{1f40f}|\x{1f410}|\x{1f411}|\x{1f412}|\x{1f413}|\x{1f414}|\x{1f415}|\x{1f416}|\x{1f417}|\x{1f418}|\x{1f419}|\x{1f41a}|\x{1f41b}|\x{1f41c}|\x{1f41d}|\x{1f41e}|\x{1f41f}|\x{1f420}|\x{1f421}|\x{1f422}|\x{1f423}|\x{1f424}|\x{1f425}|\x{1f426}|\x{1f427}|\x{1f428}|\x{1f429}|\x{1f42a}|\x{1f42b}|\x{1f42c}|\x{1f42d}|\x{1f42e}|\x{1f42f}|\x{1f430}|\x{1f431}|\x{1f432}|\x{1f433}|\x{1f434}|\x{1f435}|\x{1f436}|\x{1f437}|\x{1f438}|\x{1f439}|\x{1f43a}|\x{1f43b}|\x{1f43c}|\x{1f43d}|\x{1f43e}|\x{1f440}|\x{1f444}|\x{1f445}|\x{1f451}|\x{1f452}|\x{1f453}|\x{1f454}|\x{1f455}|\x{1f456}|\x{1f457}|\x{1f458}|\x{1f459}|\x{1f45a}|\x{1f45b}|\x{1f45c}|\x{1f45d}|\x{1f45e}|\x{1f45f}|\x{1f460}|\x{1f461}|\x{1f462}|\x{1f463}|\x{1f464}|\x{1f465}|\x{1f46a}|\x{1f46b}|\x{1f46c}|\x{1f46d}|\x{1f46f}|\x{1f479}|\x{1f47a}|\x{1f47b}|\x{1f47d}|\x{1f47e}|\x{1f47f}|\x{1f480}|\x{1f484}|\x{1f488}|\x{1f489}|\x{1f48a}|\x{1f48b}|\x{1f48c}|\x{1f48d}|\x{1f48e}|\x{1f48f}|\x{1f490}|\x{1f491}|\x{1f492}|\x{1f493}|\x{1f494}|\x{1f495}|\x{1f496}|\x{1f497}|\x{1f498}|\x{1f499}|\x{1f49a}|\x{1f49b}|\x{1f49c}|\x{1f49d}|\x{1f49e}|\x{1f49f}|\x{1f4a0}|\x{1f4a1}|\x{1f4a2}|\x{1f4a3}|\x{1f4a4}|\x{1f4a5}|\x{1f4a6}|\x{1f4a7}|\x{1f4a8}|\x{1f4a9}|\x{1f4ab}|\x{1f4ac}|\x{1f4ad}|\x{1f4ae}|\x{1f4af}|\x{1f4b0}|\x{1f4b1}|\x{1f4b2}|\x{1f4b3}|\x{1f4b4}|\x{1f4b5}|\x{1f4b6}|\x{1f4b7}|\x{1f4b8}|\x{1f4b9}|\x{1f4ba}|\x{1f4bb}|\x{1f4bc}|\x{1f4bd}|\x{1f4be}|\x{1f4bf}|\x{1f4c0}|\x{1f4c1}|\x{1f4c2}|\x{1f4c3}|\x{1f4c4}|\x{1f4c5}|\x{1f4c6}|\x{1f4c7}|\x{1f4c8}|\x{1f4c9}|\x{1f4ca}|\x{1f4cb}|\x{1f4cc}|\x{1f4cd}|\x{1f4ce}|\x{1f4cf}|\x{1f4d0}|\x{1f4d1}|\x{1f4d2}|\x{1f4d3}|\x{1f4d4}|\x{1f4d5}|\x{1f4d6}|\x{1f4d7}|\x{1f4d8}|\x{1f4d9}|\x{1f4da}|\x{1f4db}|\x{1f4dc}|\x{1f4dd}|\x{1f4de}|\x{1f4df}|\x{1f4e0}|\x{1f4e1}|\x{1f4e2}|\x{1f4e3}|\x{1f4e4}|\x{1f4e5}|\x{1f4e6}|\x{1f4e7}|\x{1f4e8}|\x{1f4e9}|\x{1f4ea}|\x{1f4eb}|\x{1f4ec}|\x{1f4ed}|\x{1f4ee}|\x{1f4ef}|\x{1f4f0}|\x{1f4f1}|\x{1f4f2}|\x{1f4f3}|\x{1f4f4}|\x{1f4f5}|\x{1f4f6}|\x{1f4f7}|\x{1f4f8}|\x{1f4f9}|\x{1f4fa}|\x{1f4fb}|\x{1f4fc}|\x{1f4ff}|\x{1f500}|\x{1f501}|\x{1f502}|\x{1f503}|\x{1f504}|\x{1f505}|\x{1f506}|\x{1f507}|\x{1f508}|\x{1f509}|\x{1f50a}|\x{1f50b}|\x{1f50c}|\x{1f50d}|\x{1f50e}|\x{1f50f}|\x{1f510}|\x{1f511}|\x{1f512}|\x{1f513}|\x{1f514}|\x{1f515}|\x{1f516}|\x{1f517}|\x{1f518}|\x{1f519}|\x{1f51a}|\x{1f51b}|\x{1f51c}|\x{1f51d}|\x{1f51e}|\x{1f51f}|\x{1f520}|\x{1f521}|\x{1f522}|\x{1f523}|\x{1f524}|\x{1f525}|\x{1f526}|\x{1f527}|\x{1f528}|\x{1f529}|\x{1f52a}|\x{1f52b}|\x{1f52c}|\x{1f52d}|\x{1f52e}|\x{1f52f}|\x{1f530}|\x{1f531}|\x{1f532}|\x{1f533}|\x{1f534}|\x{1f535}|\x{1f536}|\x{1f537}|\x{1f538}|\x{1f539}|\x{1f53a}|\x{1f53b}|\x{1f53c}|\x{1f53d}|\x{1f54b}|\x{1f54c}|\x{1f54d}|\x{1f54e}|\x{1f550}|\x{1f551}|\x{1f552}|\x{1f553}|\x{1f554}|\x{1f555}|\x{1f556}|\x{1f557}|\x{1f558}|\x{1f559}|\x{1f55a}|\x{1f55b}|\x{1f55c}|\x{1f55d}|\x{1f55e}|\x{1f55f}|\x{1f560}|\x{1f561}|\x{1f562}|\x{1f563}|\x{1f564}|\x{1f565}|\x{1f566}|\x{1f567}|\x{1f5a4}|\x{1f5fb}|\x{1f5fc}|\x{1f5fd}|\x{1f5fe}|\x{1f5ff}|\x{1f600}|\x{1f601}|\x{1f602}|\x{1f603}|\x{1f604}|\x{1f605}|\x{1f606}|\x{1f607}|\x{1f608}|\x{1f609}|\x{1f60a}|\x{1f60b}|\x{1f60c}|\x{1f60d}|\x{1f60e}|\x{1f60f}|\x{1f610}|\x{1f611}|\x{1f612}|\x{1f613}|\x{1f614}|\x{1f615}|\x{1f616}|\x{1f617}|\x{1f618}|\x{1f619}|\x{1f61a}|\x{1f61b}|\x{1f61c}|\x{1f61d}|\x{1f61e}|\x{1f61f}|\x{1f620}|\x{1f621}|\x{1f622}|\x{1f623}|\x{1f624}|\x{1f625}|\x{1f626}|\x{1f627}|\x{1f628}|\x{1f629}|\x{1f62a}|\x{1f62b}|\x{1f62c}|\x{1f62d}|\x{1f62e}|\x{1f62f}|\x{1f630}|\x{1f631}|\x{1f632}|\x{1f633}|\x{1f634}|\x{1f635}|\x{1f636}|\x{1f637}|\x{1f638}|\x{1f639}|\x{1f63a}|\x{1f63b}|\x{1f63c}|\x{1f63d}|\x{1f63e}|\x{1f63f}|\x{1f640}|\x{1f641}|\x{1f642}|\x{1f643}|\x{1f644}|\x{1f648}|\x{1f649}|\x{1f64a}|\x{1f680}|\x{1f681}|\x{1f682}|\x{1f683}|\x{1f684}|\x{1f685}|\x{1f686}|\x{1f687}|\x{1f688}|\x{1f689}|\x{1f68a}|\x{1f68b}|\x{1f68c}|\x{1f68d}|\x{1f68e}|\x{1f68f}|\x{1f690}|\x{1f691}|\x{1f692}|\x{1f693}|\x{1f694}|\x{1f695}|\x{1f696}|\x{1f697}|\x{1f698}|\x{1f699}|\x{1f69a}|\x{1f69b}|\x{1f69c}|\x{1f69d}|\x{1f69e}|\x{1f69f}|\x{1f6a0}|\x{1f6a1}|\x{1f6a2}|\x{1f6a4}|\x{1f6a5}|\x{1f6a6}|\x{1f6a7}|\x{1f6a8}|\x{1f6a9}|\x{1f6aa}|\x{1f6ab}|\x{1f6ac}|\x{1f6ad}|\x{1f6ae}|\x{1f6af}|\x{1f6b0}|\x{1f6b1}|\x{1f6b2}|\x{1f6b3}|\x{1f6b7}|\x{1f6b8}|\x{1f6b9}|\x{1f6ba}|\x{1f6bb}|\x{1f6bc}|\x{1f6bd}|\x{1f6be}|\x{1f6bf}|\x{1f6c1}|\x{1f6c2}|\x{1f6c3}|\x{1f6c4}|\x{1f6c5}|\x{1f6d0}|\x{1f6d1}|\x{1f6d2}|\x{1f6eb}|\x{1f6ec}|\x{1f6f4}|\x{1f6f5}|\x{1f6f6}|\x{1f6f7}|\x{1f6f8}|\x{1f910}|\x{1f911}|\x{1f912}|\x{1f913}|\x{1f914}|\x{1f915}|\x{1f916}|\x{1f917}|\x{1f91d}|\x{1f920}|\x{1f921}|\x{1f922}|\x{1f923}|\x{1f924}|\x{1f925}|\x{1f927}|\x{1f928}|\x{1f929}|\x{1f92a}|\x{1f92b}|\x{1f92c}|\x{1f92d}|\x{1f92e}|\x{1f92f}|\x{1f93a}|\x{1f93c}|\x{1f940}|\x{1f941}|\x{1f942}|\x{1f943}|\x{1f944}|\x{1f945}|\x{1f947}|\x{1f948}|\x{1f949}|\x{1f94a}|\x{1f94b}|\x{1f94c}|\x{1f950}|\x{1f951}|\x{1f952}|\x{1f953}|\x{1f954}|\x{1f955}|\x{1f956}|\x{1f957}|\x{1f958}|\x{1f959}|\x{1f95a}|\x{1f95b}|\x{1f95c}|\x{1f95d}|\x{1f95e}|\x{1f95f}|\x{1f960}|\x{1f961}|\x{1f962}|\x{1f963}|\x{1f964}|\x{1f965}|\x{1f966}|\x{1f967}|\x{1f968}|\x{1f969}|\x{1f96a}|\x{1f96b}|\x{1f980}|\x{1f981}|\x{1f982}|\x{1f983}|\x{1f984}|\x{1f985}|\x{1f986}|\x{1f987}|\x{1f988}|\x{1f989}|\x{1f98a}|\x{1f98b}|\x{1f98c}|\x{1f98d}|\x{1f98e}|\x{1f98f}|\x{1f990}|\x{1f991}|\x{1f992}|\x{1f993}|\x{1f994}|\x{1f995}|\x{1f996}|\x{1f997}|\x{1f9c0}|\x{1f9d0}|\x{1f9de}|\x{1f9df}|\x{1f9e0}|\x{1f9e1}|\x{1f9e2}|\x{1f9e3}|\x{1f9e4}|\x{1f9e5}|\x{1f9e6}|[\x{23e9}\x{23ea}\x{23eb}\x{23ec}\x{23f0}\x{23f3}\x{2640}\x{2642}\x{2695}\x{26ce}\x{2705}\x{2728}\x{274c}\x{274e}\x{2753}\x{2754}\x{2755}\x{2795}\x{2796}\x{2797}\x{27b0}\x{27bf}\x{e50a}]|(?:\x{1f004}|\x{1f170}|\x{1f171}|\x{1f17e}|\x{1f17f}|\x{1f202}|\x{1f21a}|\x{1f22f}|\x{1f237}|\x{1f321}|\x{1f324}|\x{1f325}|\x{1f326}|\x{1f327}|\x{1f328}|\x{1f329}|\x{1f32a}|\x{1f32b}|\x{1f32c}|\x{1f336}|\x{1f37d}|\x{1f396}|\x{1f397}|\x{1f399}|\x{1f39a}|\x{1f39b}|\x{1f39e}|\x{1f39f}|\x{1f3cd}|\x{1f3ce}|\x{1f3d4}|\x{1f3d5}|\x{1f3d6}|\x{1f3d7}|\x{1f3d8}|\x{1f3d9}|\x{1f3da}|\x{1f3db}|\x{1f3dc}|\x{1f3dd}|\x{1f3de}|\x{1f3df}|\x{1f3f3}|\x{1f3f5}|\x{1f3f7}|\x{1f43f}|\x{1f441}|\x{1f4fd}|\x{1f549}|\x{1f54a}|\x{1f56f}|\x{1f570}|\x{1f573}|\x{1f576}|\x{1f577}|\x{1f578}|\x{1f579}|\x{1f587}|\x{1f58a}|\x{1f58b}|\x{1f58c}|\x{1f58d}|\x{1f5a5}|\x{1f5a8}|\x{1f5b1}|\x{1f5b2}|\x{1f5bc}|\x{1f5c2}|\x{1f5c3}|\x{1f5c4}|\x{1f5d1}|\x{1f5d2}|\x{1f5d3}|\x{1f5dc}|\x{1f5dd}|\x{1f5de}|\x{1f5e1}|\x{1f5e3}|\x{1f5e8}|\x{1f5ef}|\x{1f5f3}|\x{1f5fa}|\x{1f6cb}|\x{1f6cd}|\x{1f6ce}|\x{1f6cf}|\x{1f6e0}|\x{1f6e1}|\x{1f6e2}|\x{1f6e3}|\x{1f6e4}|\x{1f6e5}|\x{1f6e9}|\x{1f6f0}|\x{1f6f3}|[\x{00a9}\x{00ae}\x{203c}\x{2049}\x{2122}\x{2139}\x{2194}\x{2195}\x{2196}\x{2197}\x{2198}\x{2199}\x{21a9}\x{21aa}\x{231a}\x{231b}\x{2328}\x{23cf}\x{23ed}\x{23ee}\x{23ef}\x{23f1}\x{23f2}\x{23f8}\x{23f9}\x{23fa}\x{24c2}\x{25aa}\x{25ab}\x{25b6}\x{25c0}\x{25fb}\x{25fc}\x{25fd}\x{25fe}\x{2600}\x{2601}\x{2602}\x{2603}\x{2604}\x{260e}\x{2611}\x{2614}\x{2615}\x{2618}\x{2620}\x{2622}\x{2623}\x{2626}\x{262a}\x{262e}\x{262f}\x{2638}\x{2639}\x{263a}\x{2648}\x{2649}\x{264a}\x{264b}\x{264c}\x{264d}\x{264e}\x{264f}\x{2650}\x{2651}\x{2652}\x{2653}\x{2660}\x{2663}\x{2665}\x{2666}\x{2668}\x{267b}\x{267f}\x{2692}\x{2693}\x{2694}\x{2696}\x{2697}\x{2699}\x{269b}\x{269c}\x{26a0}\x{26a1}\x{26aa}\x{26ab}\x{26b0}\x{26b1}\x{26bd}\x{26be}\x{26c4}\x{26c5}\x{26c8}\x{26cf}\x{26d1}\x{26d3}\x{26d4}\x{26e9}\x{26ea}\x{26f0}\x{26f1}\x{26f2}\x{26f3}\x{26f4}\x{26f5}\x{26f8}\x{26fa}\x{26fd}\x{2702}\x{2708}\x{2709}\x{270f}\x{2712}\x{2714}\x{2716}\x{271d}\x{2721}\x{2733}\x{2734}\x{2744}\x{2747}\x{2757}\x{2763}\x{2764}\x{27a1}\x{2934}\x{2935}\x{2b05}\x{2b06}\x{2b07}\x{2b1b}\x{2b1c}\x{2b50}\x{2b55}\x{3030}\x{303d}\x{3297}\x{3299}])(?:\x{fe0f}|(?!\x{fe0e})))/u'; + $entities = '/(👨|👩(?:🏻|🏼|🏽|🏾|🏿)?‍(?:⚕️|⚖️|✈️|🌾|🍳|🎓|🎤|🎨|🏫|🏭|💻|💼|🔧|🔬|🚀|🚒)|(?:🏋|🏌|🕵|⛹)(?:️|🏻|🏼|🏽|🏾|🏿)‍♀️|‍♂️|(?:🏃|🏄|🏊|👮|👱|👳|👷|💁|💂|💆|💇|🙅|🙆|🙇|🙋|🙍|🙎|🚣|🚴|🚵|🚶|🤦|🤷|🤸|🤹|🤽|🤾|🧖|🧗|🧘|🧙|🧚|🧛|🧜|🧝)(?:🏻|🏼|🏽|🏾|🏿)?‍♀️|‍♂️|👨‍❤️‍💋‍👨|👨‍👨‍👦‍👦|👨‍👨‍👧‍👦|👨‍👨‍👧‍👧|👨‍👩‍👦‍👦|👨‍👩‍👧‍👦|👨‍👩‍👧‍👧|👩‍❤️‍💋‍👨|👩‍❤️‍💋‍👩|👩‍👩‍👦‍👦|👩‍👩‍👧‍👦|👩‍👩‍👧‍👧|👨‍❤️‍👨|👨‍👦‍👦|👨‍👧‍👦|👨‍👧‍👧|👨‍👨‍👦|👨‍👨‍👧|👨‍👩‍👦|👨‍👩‍👧|👩‍❤️‍👨|👩‍❤️‍👩|👩‍👦‍👦|👩‍👧‍👦|👩‍👧‍👧|👩‍👩‍👦|👩‍👩‍👧|🏳️‍🌈|🏴‍☠️|👁‍🗨|👨‍👦|👨‍👧|👩‍👦|👩‍👧|👯‍♀️|👯‍♂️|🤼‍♀️|🤼‍♂️|🧞‍♀️|🧞‍♂️|🧟‍♀️|🧟‍♂️|(?:(?:#|*|0|1|2|3|4|5|6|7|8|9))️?⃣|(?:(?:🏋|🏌|🕴|🕵|🖐|(?:☝|⛷|⛹|✌|✍))(?:️|(?!︎))|🎅|🏂|🏃|🏄|🏇|🏊|👂|👃|👆|👇|👈|👉|👊|👋|👌|👍|👎|👏|👐|👦|👧|👨|👩|👮|👰|👱|👲|👳|👴|👵|👶|👷|👸|👼|💁|💂|💃|💅|💆|💇|💪|🕺|🖕|🖖|🙅|🙆|🙇|🙋|🙌|🙍|🙎|🙏|🚣|🚴|🚵|🚶|🛀|🛌|🤘|🤙|🤚|🤛|🤜|🤞|🤟|🤦|🤰|🤱|🤲|🤳|🤴|🤵|🤶|🤷|🤸|🤹|🤽|🤾|🧑|🧒|🧓|🧔|🧕|🧖|🧗|🧘|🧙|🧚|🧛|🧜|🧝|(?:✊|✋))(?:🏻|🏼|🏽|🏾|🏿|)|🏴󠁧󠁢󠁥󠁮󠁧󠁿|🏴󠁧󠁢󠁳󠁣󠁴󠁿|🏴󠁧󠁢󠁷󠁬󠁳󠁿|🇦🇨|🇦🇩|🇦🇪|🇦🇫|🇦🇬|🇦🇮|🇦🇱|🇦🇲|🇦🇴|🇦🇶|🇦🇷|🇦🇸|🇦🇹|🇦🇺|🇦🇼|🇦🇽|🇦🇿|🇧🇦|🇧🇧|🇧🇩|🇧🇪|🇧🇫|🇧🇬|🇧🇭|🇧🇮|🇧🇯|🇧🇱|🇧🇲|🇧🇳|🇧🇴|🇧🇶|🇧🇷|🇧🇸|🇧🇹|🇧🇻|🇧🇼|🇧🇾|🇧🇿|🇨🇦|🇨🇨|🇨🇩|🇨🇫|🇨🇬|🇨🇭|🇨🇮|🇨🇰|🇨🇱|🇨🇲|🇨🇳|🇨🇴|🇨🇵|🇨🇷|🇨🇺|🇨🇻|🇨🇼|🇨🇽|🇨🇾|🇨🇿|🇩🇪|🇩🇬|🇩🇯|🇩🇰|🇩🇲|🇩🇴|🇩🇿|🇪🇦|🇪🇨|🇪🇪|🇪🇬|🇪🇭|🇪🇷|🇪🇸|🇪🇹|🇪🇺|🇫🇮|🇫🇯|🇫🇰|🇫🇲|🇫🇴|🇫🇷|🇬🇦|🇬🇧|🇬🇩|🇬🇪|🇬🇫|🇬🇬|🇬🇭|🇬🇮|🇬🇱|🇬🇲|🇬🇳|🇬🇵|🇬🇶|🇬🇷|🇬🇸|🇬🇹|🇬🇺|🇬🇼|🇬🇾|🇭🇰|🇭🇲|🇭🇳|🇭🇷|🇭🇹|🇭🇺|🇮🇨|🇮🇩|🇮🇪|🇮🇱|🇮🇲|🇮🇳|🇮🇴|🇮🇶|🇮🇷|🇮🇸|🇮🇹|🇯🇪|🇯🇲|🇯🇴|🇯🇵|🇰🇪|🇰🇬|🇰🇭|🇰🇮|🇰🇲|🇰🇳|🇰🇵|🇰🇷|🇰🇼|🇰🇾|🇰🇿|🇱🇦|🇱🇧|🇱🇨|🇱🇮|🇱🇰|🇱🇷|🇱🇸|🇱🇹|🇱🇺|🇱🇻|🇱🇾|🇲🇦|🇲🇨|🇲🇩|🇲🇪|🇲🇫|🇲🇬|🇲🇭|🇲🇰|🇲🇱|🇲🇲|🇲🇳|🇲🇴|🇲🇵|🇲🇶|🇲🇷|🇲🇸|🇲🇹|🇲🇺|🇲🇻|🇲🇼|🇲🇽|🇲🇾|🇲🇿|🇳🇦|🇳🇨|🇳🇪|🇳🇫|🇳🇬|🇳🇮|🇳🇱|🇳🇴|🇳🇵|🇳🇷|🇳🇺|🇳🇿|🇴🇲|🇵🇦|🇵🇪|🇵🇫|🇵🇬|🇵🇭|🇵🇰|🇵🇱|🇵🇲|🇵🇳|🇵🇷|🇵🇸|🇵🇹|🇵🇼|🇵🇾|🇶🇦|🇷🇪|🇷🇴|🇷🇸|🇷🇺|🇷🇼|🇸🇦|🇸🇧|🇸🇨|🇸🇩|🇸🇪|🇸🇬|🇸🇭|🇸🇮|🇸🇯|🇸🇰|🇸🇱|🇸🇲|🇸🇳|🇸🇴|🇸🇷|🇸🇸|🇸🇹|🇸🇻|🇸🇽|🇸🇾|🇸🇿|🇹🇦|🇹🇨|🇹🇩|🇹🇫|🇹🇬|🇹🇭|🇹🇯|🇹🇰|🇹🇱|🇹🇲|🇹🇳|🇹🇴|🇹🇷|🇹🇹|🇹🇻|🇹🇼|🇹🇿|🇺🇦|🇺🇬|🇺🇲|🇺🇳|🇺🇸|🇺🇾|🇺🇿|🇻🇦|🇻🇨|🇻🇪|🇻🇬|🇻🇮|🇻🇳|🇻🇺|🇼🇫|🇼🇸|🇽🇰|🇾🇪|🇾🇹|🇿🇦|🇿🇲|🇿🇼|𐀀|🃏|🆎|🆑|🆒|🆓|🆔|🆕|🆖|🆗|🆘|🆙|🆚|🇦|🇧|🇨|🇩|🇪|🇫|🇬|🇭|🇮|🇯|🇰|🇱|🇲|🇳|🇴|🇵|🇶|🇷|🇸|🇹|🇺|🇻|🇼|🇽|🇾|🇿|🈁|🈲|🈳|🈴|🈵|🈶|🈸|🈹|🈺|🉐|🉑|🌀|🌁|🌂|🌃|🌄|🌅|🌆|🌇|🌈|🌉|🌊|🌋|🌌|🌍|🌎|🌏|🌐|🌑|🌒|🌓|🌔|🌕|🌖|🌗|🌘|🌙|🌚|🌛|🌜|🌝|🌞|🌟|🌠|🌭|🌮|🌯|🌰|🌱|🌲|🌳|🌴|🌵|🌷|🌸|🌹|🌺|🌻|🌼|🌽|🌾|🌿|🍀|🍁|🍂|🍃|🍄|🍅|🍆|🍇|🍈|🍉|🍊|🍋|🍌|🍍|🍎|🍏|🍐|🍑|🍒|🍓|🍔|🍕|🍖|🍗|🍘|🍙|🍚|🍛|🍜|🍝|🍞|🍟|🍠|🍡|🍢|🍣|🍤|🍥|🍦|🍧|🍨|🍩|🍪|🍫|🍬|🍭|🍮|🍯|🍰|🍱|🍲|🍳|🍴|🍵|🍶|🍷|🍸|🍹|🍺|🍻|🍼|🍾|🍿|🎀|🎁|🎂|🎃|🎄|🎆|🎇|🎈|🎉|🎊|🎋|🎌|🎍|🎎|🎏|🎐|🎑|🎒|🎓|🎠|🎡|🎢|🎣|🎤|🎥|🎦|🎧|🎨|🎩|🎪|🎫|🎬|🎭|🎮|🎯|🎰|🎱|🎲|🎳|🎴|🎵|🎶|🎷|🎸|🎹|🎺|🎻|🎼|🎽|🎾|🎿|🏀|🏁|🏅|🏆|🏈|🏉|🏏|🏐|🏑|🏒|🏓|🏠|🏡|🏢|🏣|🏤|🏥|🏦|🏧|🏨|🏩|🏪|🏫|🏬|🏭|🏮|🏯|🏰|🏴|🏸|🏹|🏺|🏻|🏼|🏽|🏾|🏿|🐀|🐁|🐂|🐃|🐄|🐅|🐆|🐇|🐈|🐉|🐊|🐋|🐌|🐍|🐎|🐏|🐐|🐑|🐒|🐓|🐔|🐕|🐖|🐗|🐘|🐙|🐚|🐛|🐜|🐝|🐞|🐟|🐠|🐡|🐢|🐣|🐤|🐥|🐦|🐧|🐨|🐩|🐪|🐫|🐬|🐭|🐮|🐯|🐰|🐱|🐲|🐳|🐴|🐵|🐶|🐷|🐸|🐹|🐺|🐻|🐼|🐽|🐾|👀|👄|👅|👑|👒|👓|👔|👕|👖|👗|👘|👙|👚|👛|👜|👝|👞|👟|👠|👡|👢|👣|👤|👥|👪|👫|👬|👭|👯|👹|👺|👻|👽|👾|👿|💀|💄|💈|💉|💊|💋|💌|💍|💎|💏|💐|💑|💒|💓|💔|💕|💖|💗|💘|💙|💚|💛|💜|💝|💞|💟|💠|💡|💢|💣|💤|💥|💦|💧|💨|💩|💫|💬|💭|💮|💯|💰|💱|💲|💳|💴|💵|💶|💷|💸|💹|💺|💻|💼|💽|💾|💿|📀|📁|📂|📃|📄|📅|📆|📇|📈|📉|📊|📋|📌|📍|📎|📏|📐|📑|📒|📓|📔|📕|📖|📗|📘|📙|📚|📛|📜|📝|📞|📟|📠|📡|📢|📣|📤|📥|📦|📧|📨|📩|📪|📫|📬|📭|📮|📯|📰|📱|📲|📳|📴|📵|📶|📷|📸|📹|📺|📻|📼|📿|🔀|🔁|🔂|🔃|🔄|🔅|🔆|🔇|🔈|🔉|🔊|🔋|🔌|🔍|🔎|🔏|🔐|🔑|🔒|🔓|🔔|🔕|🔖|🔗|🔘|🔙|🔚|🔛|🔜|🔝|🔞|🔟|🔠|🔡|🔢|🔣|🔤|🔥|🔦|🔧|🔨|🔩|🔪|🔫|🔬|🔭|🔮|🔯|🔰|🔱|🔲|🔳|🔴|🔵|🔶|🔷|🔸|🔹|🔺|🔻|🔼|🔽|🕋|🕌|🕍|🕎|🕐|🕑|🕒|🕓|🕔|🕕|🕖|🕗|🕘|🕙|🕚|🕛|🕜|🕝|🕞|🕟|🕠|🕡|🕢|🕣|🕤|🕥|🕦|🕧|🖤|🗻|🗼|🗽|🗾|🗿|😀|😁|😂|😃|😄|😅|😆|😇|😈|😉|😊|😋|😌|😍|😎|😏|😐|😑|😒|😓|😔|😕|😖|😗|😘|😙|😚|😛|😜|😝|😞|😟|😠|😡|😢|😣|😤|😥|😦|😧|😨|😩|😪|😫|😬|😭|😮|😯|😰|😱|😲|😳|😴|😵|😶|😷|😸|😹|😺|😻|😼|😽|😾|😿|🙀|🙁|🙂|🙃|🙄|🙈|🙉|🙊|🚀|🚁|🚂|🚃|🚄|🚅|🚆|🚇|🚈|🚉|🚊|🚋|🚌|🚍|🚎|🚏|🚐|🚑|🚒|🚓|🚔|🚕|🚖|🚗|🚘|🚙|🚚|🚛|🚜|🚝|🚞|🚟|🚠|🚡|🚢|🚤|🚥|🚦|🚧|🚨|🚩|🚪|🚫|🚬|🚭|🚮|🚯|🚰|🚱|🚲|🚳|🚷|🚸|🚹|🚺|🚻|🚼|🚽|🚾|🚿|🛁|🛂|🛃|🛄|🛅|🛐|🛑|🛒|🛫|🛬|🛴|🛵|🛶|🛷|🛸|🤐|🤑|🤒|🤓|🤔|🤕|🤖|🤗|🤝|🤠|🤡|🤢|🤣|🤤|🤥|🤧|🤨|🤩|🤪|🤫|🤬|🤭|🤮|🤯|🤺|🤼|🥀|🥁|🥂|🥃|🥄|🥅|🥇|🥈|🥉|🥊|🥋|🥌|🥐|🥑|🥒|🥓|🥔|🥕|🥖|🥗|🥘|🥙|🥚|🥛|🥜|🥝|🥞|🥟|🥠|🥡|🥢|🥣|🥤|🥥|🥦|🥧|🥨|🥩|🥪|🥫|🦀|🦁|🦂|🦃|🦄|🦅|🦆|🦇|🦈|🦉|🦊|🦋|🦌|🦍|🦎|🦏|🦐|🦑|🦒|🦓|🦔|🦕|🦖|🦗|🧀|🧐|🧞|🧟|🧠|🧡|🧢|🧣|🧤|🧥|🧦|(?:⏩|⏪|⏫|⏬|⏰|⏳|♀|♂|⚕|⛎|✅|✨|❌|❎|❓|❔|❕|➕|➖|➗|➰|➿|)|(?:🀄|🅰|🅱|🅾|🅿|🈂|🈚|🈯|🈷|🌡|🌤|🌥|🌦|🌧|🌨|🌩|🌪|🌫|🌬|🌶|🍽|🎖|🎗|🎙|🎚|🎛|🎞|🎟|🏍|🏎|🏔|🏕|🏖|🏗|🏘|🏙|🏚|🏛|🏜|🏝|🏞|🏟|🏳|🏵|🏷|🐿|👁|📽|🕉|🕊|🕯|🕰|🕳|🕶|🕷|🕸|🕹|🖇|🖊|🖋|🖌|🖍|🖥|🖨|🖱|🖲|🖼|🗂|🗃|🗄|🗑|🗒|🗓|🗜|🗝|🗞|🗡|🗣|🗨|🗯|🗳|🗺|🛋|🛍|🛎|🛏|🛠|🛡|🛢|🛣|🛤|🛥|🛩|🛰|🛳|(?:©|®|‼|⁉|™|ℹ|↔|↕|↖|↗|↘|↙|↩|↪|⌚|⌛|⌨|⏏|⏭|⏮|⏯|⏱|⏲|⏸|⏹|⏺|Ⓜ|▪|▫|▶|◀|◻|◼|◽|◾|☀|☁|☂|☃|☄|☎|☑|☔|☕|☘|☠|☢|☣|☦|☪|☮|☯|☸|☹|☺|♈|♉|♊|♋|♌|♍|♎|♏|♐|♑|♒|♓|♠|♣|♥|♦|♨|♻|♿|⚒|⚓|⚔|⚖|⚗|⚙|⚛|⚜|⚠|⚡|⚪|⚫|⚰|⚱|⚽|⚾|⛄|⛅|⛈|⛏|⛑|⛓|⛔|⛩|⛪|⛰|⛱|⛲|⛳|⛴|⛵|⛸|⛺|⛽|✂|✈|✉|✏|✒|✔|✖|✝|✡|✳|✴|❄|❇|❗|❣|❤|➡|⤴|⤵|⬅|⬆|⬇|⬛|⬜|⭐|⭕|〰|〽|㊗|㊙))(?:️|(?!︎)))/u'; + // END: emoji regex + + if ( 'entities' === $type ) { + return $entities; + } + + return $codepoints; +} + /** * Shorten a URL, to be used as link text. * diff --git a/tests/phpunit/tests/formatting/Emoji.php b/tests/phpunit/tests/formatting/Emoji.php index 6e0ed1774c..cc98b4290d 100644 --- a/tests/phpunit/tests/formatting/Emoji.php +++ b/tests/phpunit/tests/formatting/Emoji.php @@ -2,19 +2,21 @@ /** * @group formatting + * @group emoji */ class Tests_Formatting_Emoji extends WP_UnitTestCase { + + private $png_cdn = 'https://s.w.org/images/core/emoji/2.3/72x72/'; + private $svn_cdn = 'https://s.w.org/images/core/emoji/2.3/svg/'; + /** * @ticket 36525 */ public function test_unfiltered_emoji_cdns() { - $png_cdn = 'https://s.w.org/images/core/emoji/2.3/72x72/'; - $svn_cdn = 'https://s.w.org/images/core/emoji/2.3/svg/'; - $output = get_echo( '_print_emoji_detection_script' ); - $this->assertContains( wp_json_encode( $png_cdn ), $output ); - $this->assertContains( wp_json_encode( $svn_cdn ), $output ); + $this->assertContains( wp_json_encode( $this->png_cdn ), $output ); + $this->assertContains( wp_json_encode( $this->svn_cdn ), $output ); } public function _filtered_emoji_svn_cdn( $cdn = '' ) { @@ -25,17 +27,14 @@ class Tests_Formatting_Emoji extends WP_UnitTestCase { * @ticket 36525 */ public function test_filtered_emoji_svn_cdn() { - $png_cdn = 'https://s.w.org/images/core/emoji/2.3/72x72/'; - $svn_cdn = 'https://s.w.org/images/core/emoji/2.3/svg/'; - $filtered_svn_cdn = $this->_filtered_emoji_svn_cdn(); add_filter( 'emoji_svg_url', array( $this, '_filtered_emoji_svn_cdn' ) ); $output = get_echo( '_print_emoji_detection_script' ); - $this->assertContains( wp_json_encode( $png_cdn ), $output ); - $this->assertNotContains( wp_json_encode( $svn_cdn ), $output ); + $this->assertContains( wp_json_encode( $this->png_cdn ), $output ); + $this->assertNotContains( wp_json_encode( $this->svn_cdn ), $output ); $this->assertContains( wp_json_encode( $filtered_svn_cdn ), $output ); remove_filter( 'emoji_svg_url', array( $this, '_filtered_emoji_svn_cdn' ) ); @@ -49,9 +48,6 @@ class Tests_Formatting_Emoji extends WP_UnitTestCase { * @ticket 36525 */ public function test_filtered_emoji_png_cdn() { - $png_cdn = 'https://s.w.org/images/core/emoji/2.3/72x72/'; - $svn_cdn = 'https://s.w.org/images/core/emoji/2.3/svg/'; - $filtered_png_cdn = $this->_filtered_emoji_png_cdn(); add_filter( 'emoji_url', array( $this, '_filtered_emoji_png_cdn' ) ); @@ -59,10 +55,95 @@ class Tests_Formatting_Emoji extends WP_UnitTestCase { $output = get_echo( '_print_emoji_detection_script' ); $this->assertContains( wp_json_encode( $filtered_png_cdn ), $output ); - $this->assertNotContains( wp_json_encode( $png_cdn ), $output ); - $this->assertContains( wp_json_encode( $svn_cdn ), $output ); + $this->assertNotContains( wp_json_encode( $this->png_cdn ), $output ); + $this->assertContains( wp_json_encode( $this->svn_cdn ), $output ); remove_filter( 'emoji_url', array( $this, '_filtered_emoji_png_cdn' ) ); } + /** + * @ticket 35293 + */ + public function test_wp_emoji_regex_returns_regexen() { + $default = wp_emoji_regex(); + $this->assertNotEmpty( $default ); + + $codepoints = wp_emoji_regex( 'codepoints' ); + $this->assertNotEmpty( $codepoints ); + + $this->assertSame( $default, $codepoints ); + + $entities = wp_emoji_regex( 'entities' ); + $this->assertNotEmpty( $entities ); + + $this->assertNotSame( $default, $entities ); + } + + public function data_wp_encode_emoji() { + return array( + array( + // Not emoji + '’', + '’', + ), + array( + // Simple emoji + '🙂', + '🙂', + ), + array( + // Skin tone, gender, ZWJ, emoji selector + '👮🏼‍♀️', + '👮🏼‍♀️', + ), + array( + // Unicode 10 + '🧚', + '🧚', + ), + + ); + } + + /** + * @ticket 35293 + * @dataProvider data_wp_encode_emoji + */ + public function test_wp_encode_emoji( $emoji, $expected ) { + $this->assertSame( $expected, wp_encode_emoji( $emoji ) ); + } + + public function data_wp_staticize_emoji() { + return array( + array( + // Not emoji + '’', + '’', + ), + array( + // Simple emoji + '🙂', + '🙂', + ), + array( + // Skin tone, gender, ZWJ, emoji selector + '👮🏼‍♀️', + '👮🏼‍♀️', + ), + array( + // Unicode 10 + '🧚', + '🧚', + ), + + ); + } + + /** + * @ticket 35293 + * @dataProvider data_wp_staticize_emoji + */ + public function test_wp_staticize_emoji( $emoji, $expected ) { + $this->assertSame( $expected, wp_staticize_emoji( $emoji ) ); + } } diff --git a/tests/phpunit/tests/formatting/Smilies.php b/tests/phpunit/tests/formatting/Smilies.php index 7d7ae0cef5..6536ab794d 100644 --- a/tests/phpunit/tests/formatting/Smilies.php +++ b/tests/phpunit/tests/formatting/Smilies.php @@ -2,6 +2,7 @@ /** * @group formatting + * @group emoji */ class Tests_Formatting_Smilies extends WP_UnitTestCase {