Closed9

絵文字をひとつだけ取り出す技術

catnosecatnose

例えば、🐱😗という文字列があってとして、最初の絵文字の🐱だけを取り出すとする。これが簡単そうで超難しい。

使えないアプローチ

JSのcharAt()

javascript
const text = "🐱😗";
text.charAt(0); // => "�"

CSSの::first-letter疑似要素

CSSで一文字目以外を非表示にするという手段も考えてみたが、絵文字は要素内の一文字目に指定されていたとしても::first-letterの対象にはならない。

catnosecatnose

ZWJ Sequencesという壁

以下のような書き方をすれば1つめの絵文字だけ取り出すことはできるように見える。

javascript
String.fromCodePoint("🐱😗".codePointAt(0)); // => 🐱

しかしここで問題になるのが「ZWJ Sequences」。 ZWJ Sequencesの説明をEmojipediaから引用。簡単にいうと複数の絵文字を組み合わせて、

An Emoji ZWJ Sequence is a combination of multiple emojis which display as a single emoji on supported platforms. These sequences are joined with a Zero Width Joiner character.

ひとつの絵文字として表示しようというもの。たとえば👩‍💻という絵文字は👩💻という絵文字を並べることで実現されている。絵文字で人物の肌の色を選べたりするけど、あれもZWJ Sequencesにより実現されている。

そのため、さきほどの書き方をすると…

javascript
String.fromCodePoint("👩‍💻".codePointAt(0)); // => 👩

👩だけが取り出されてしまう。

catnosecatnose

結局絵文字のひとつめだけを取り出すには

試行錯誤した結果「正規表現でがんばるしかないのでは…?」という結論に達した。自分で正規表現を書くのは大変なのでライブラリを使うのが良い。JSならemoji-regexが定番。

それでも、OS固有のZWJ Sequencesな絵文字があったりするので「一部のOSでは分解されて2絵文字で表示されてしまう」問題が発生する。OSごとに出し分けるのは大変なので、unicode.orgの提供しているRecommended Emoji ZWJ Sequencesにのみ対応するのがベストかもしれない。

nabeyangnabeyang

go言語の場合はruneで文字単位に分離されるので、Zero Width Joinerに気を付ければ取り出し可能みたいです。

package main

import (
	"fmt"
)

//ZWJ is Zero Width Joiner
const ZWJ = 0x200d

func main() {
	chars := []rune("🐱😗👩‍💻👨‍👨‍👦‍👦")
	n := len(chars)
	j := 0
	for i := 0; i < n; i++ {
		b := []rune{}
		b = append(b, chars[i])
		for i < n-1 && int(chars[i+1]) == ZWJ {
			i++
			b = append(b, chars[i])
			i++
			b = append(b, chars[i])
		}
		j++
		fmt.Printf("%d:%s(%d)\n", j, string(b), len(b))
	}
}
出力結果
1:🐱(1)
2:😗(1)
3:👩‍💻(3)
4:👨‍👨‍👦‍👦(7)
catnosecatnose

なるほどー、参考になります。このアプローチでOS固有の絵文字対応以外はクリアできそうですね。ありがとうございます。

SpiegelSpiegel

ZWJ とか異体字セレクタとか Unicode はしんどいですw

https://text.baldanders.info/remark/2020/10/emoji-variation-and-markdown/

catnosecatnose

記事ありがとうございます!しんどさがよく分かる記事ですw
font-variant-emojiはじめて知りました。

This property allows web authors to select whether emoji presentation or text presentation is used for certain emoji code points. Traditionally, these presentation styles were selected by appending Variation Selector 15 (U+FE0E) or Variation Selector 16 (U+FE0F) to certain code points. However, font-variant-emoji allows web authors to set a default presentation style which can replace the variation selectors.

ややこしー

catnosecatnose

色々と試行錯誤した結果、連続した絵文字の2つ目以降をCSSで非表示にする方法がうまくいった。

.emoji {
  display: block;
  margin: 0 auto;
  overflow: hidden;
  white-space: nowrap;
  /* 以下 絵文字が欠けないように微調整 */
  width: 1.05em;
  letter-spacing: 0.05em;
}
このスクラップは2021/01/10にクローズされました