【JS体操第3問ヒント①】Zalgo Text のできるまで

こんにちは!面白プロデュース事業部のおばらです。

今回は、先日スタートしたJS体操第3問のテーマでもある「Zalgo Text(ザルゴ・テキスト)」の作り方を JavaScript のサンプルコードと併せて簡単にご紹介します。

hubspot.kayac.com


目次


Zalgo とは

まず、Zalgo ってなんでしょう?

Zalgo(ザルゴ)とは、Flash アニメータ Dave Kally によって生み出された架空の生物。インターネット上の人々を狂気に陥れ破滅へと導く恐ろしい生物らしいです。

試しに Adobe Firefly を使って Zalgo のイメージを AI 生成してみました。たぶんこんな生物です。
(若干の Zalgo Text 感も入れてみました)

Zalgo Text とは

Zalgo Text は Zalgo っぽいテキスト、文字列です。

「ダイアクリティカルマーク」という種類の「結合文字(Combining Character)」をまとい、まるで Zalgo のような狂気に満ちた呪われた文字列。

例えばこんな感じ。背景はこれまた Adobe Firefly で AI 生成してみました。

en.wikipedia.org



Zalgo Text を作ってみる

Zalgo Text を JavaScript で作るには、「結合文字」「ダイアクリティカルマーク」「コードポイント」「エスケープシーケンス」などの知識が必要です。まずはそれらを簡単に説明します。

「結合文字」

「結合文字」とは、元になる一文字「基底文字」と組み合わせることで、別の(装飾された)文字を合成できる特別な文字です。
英語だと「Combining Characters」。

Wikipedia を見ると結合文字には、

  • ダイアクリティカルマーク
  • 仮名の結合可能な濁点・半濁点
  • ヘブライ文字のニクダー
  • アラビア文字のシャクル
  • ブラーフミー系文字の母音記号

など、いろいろな種類があるようです。

ja.wikipedia.org


「ダイアクリティカルマーク」

「ダイアクリティカルマーク」は「結合文字」の一種。
英語だと「Combining Diacritical Marks」。

「ダイアクリティカルマーク」の「Unicode のコードポイント」の範囲は、16進数で 30036f です。16進数表記であることを表す 0x を先頭に付けて書くと 0x3000x36f です。

今回はこの「ダイアクリティカルマーク」を使い「Zalgo Text」を作っていきます。

ja.wikipedia.org


「Unicode エスケープシーケンス」と「コードポイント」

例えば「A」という文字(列)を JavaScript で普通に表すと以下です。

'A'

でも別の表し方もあります。
それは「Unicode のコードポイント」で表す方法、つまり「Unicode のエスケプシーケンス」で表す方法です。

「A」という文字の「Unicode のコードポイント」は10進数で 65 です。

'A'.codePointAt(0) // => 65

10進数表記の 65 を16進数表記に書き換えると 41 です。

'A'.codePointAt(0).toString(16) // => '41'

この16進数の「Unicode のコードポイント」と「Unicode エスケープシーケンス」を用いると、「A」という文字(列)は以下のように書けます。

'\u{41}' // => 'A'

コードポイントが2桁なら、以下でも OK です。

`\x41` // => 'A'

コードポイントが4桁なら、以下でも OK です。
41 を無理やり4桁にするため、左2桁を 0 でパディングし 0041 としています)

'\u0041' // => 'A'

以降、このコードポイント4桁の記法を用いて説明します。


Unicode のエスケプシーケンス、各種文字エスケープに関してもっと詳しく知りたい!という方は以下をご覧ください。
developer.mozilla.org


「A」という文字に「ダイアクリティカルマーク」を加えてみる

さて、「A」という文字に適当な「ダイアクリティカルマーク」をひとつ加えてみます。 「ダイアクリティカルマーク」の「Unicode のコードポイント」の範囲は、前述のとおり16進数で 30036f です。 試しに 300 の「ダイアクリティカルマーク」を使ってみましょう。

'\u0041' + '\u0300' // => 'À'

「A」の頭に変な印が付きました。

もちろん、以下のように書いても同じです。

'\u0041\u0300' // => 'À'

ちなみに「ダイアクリティカルマーク」(などの結合文字)のみを表示したい場合は、元の文字、つまり「基底文字」を「ノーブレークスペース(NBSP)」にすると良いらしいです。

「ノーブレークスペース」のコードポイントは 16進数で a0 なので、例えば以下。

'\u00a0\u0300' // => ' ̀'

「A」の頭に付いていた印、「ダイアクリティカルマーク」だけを表示することができました。


「ダイアクリティカルマーク」の数を増やしてみる

「ダイアクリティカルマーク」の数を増やしてみましょう。同じコードポイント 0x300、つまり16進数で 300 の「ダイアクリティカルマーク」をたくさん増やしてみます。

'\u0041\u0300\u0300' // => 'À̀'
'\u0041\u0300\u0300\u0300' // => 'À̀̀'
'\u0041\u0300\u0300\u0300\u0300' // => 'À̀̀̀'
'\u0041\u0300\u0300\u0300\u0300\u0300' // => 'À̀̀̀̀'
'\u0041\u0300\u0300\u0300\u0300\u0300\u0300' // => 'À̀̀̀̀̀'
'\u0041\u0300\u0300\u0300\u0300\u0300\u0300\u0300' // => 'À̀̀̀̀̀̀'
'\u0041\u0300\u0300\u0300\u0300\u0300\u0300\u0300\u0300' // => 'À̀̀̀̀̀̀̀'

「A」の頭の変な印がどんどん増えていきますね。


次は「ダイアクリティカルマーク」の種類(コードポイント)をいろいろ変えてみましょう。

'\u0041\u0300\u0301' // => 'À́'
'\u0041\u0300\u0301\u0302' // => 'À́̂'
'\u0041\u0300\u0301\u0302\u0303' // => 'À́̂̃'
'\u0041\u0300\u0301\u0302\u0303\u0304' // => 'À́̂̃̄'
'\u0041\u0300\u0301\u0302\u0303\u0304\u0305' // => 'À́̂̃̄̅'
'\u0041\u0300\u0301\u0302\u0303\u0304\u0305\u0306' // => 'À́̂̃̄̅̆'
'\u0041\u0300\u0301\u0302\u0303\u0304\u0305\u0306\u0307' // => 'À́̂̃̄̅̆̇'

だんだん「Zalgo Text」っぽくなってきました。


「基底文字」も増やしてみる

「A」だけじゃなく「B」も増やしましょう。 「B」という文字(列)の「Unicode のコードポイント」は10進数表記で 66 です。

'B'.codePointAt(0) // => 66

16進数表記だと 42 です。

'B'.codePointAt(0).toString(16) // => '42'


コードも文字列も、だんだん怪しさが増してきました。

'\u0041\u0300\u0042\u0301' // => 'ÀB́'
'\u0041\u0300\u0301\u0042\u0302\u0303' // => 'À́B̂̃'
'\u0041\u0300\u0301\u0302\u0042\u0303\u0304\u0305' // => 'À́̂B̃̄̅'
'\u0041\u0300\u0301\u0302\u0303\u0042\u0304\u0305\u0306\u0307' // => 'À́̂̃B̄̅̆̇'
'\u0041\u0300\u0301\u0302\u0303\u0304\u0042\u0305\u0306\u0307\u0308\u0309' // => 'À́̂̃̄B̅̆̇̈̉'
'\u0041\u0300\u0301\u0302\u0303\u0304\u0305\u0042\u0306\u0307\u0308\u0309\u030a\u030b' // => 'À́̂̃̄̅B̆̇̈̉̊̋'
'\u0041\u0300\u0301\u0302\u0303\u0304\u0305\u0306\u0042\u0307\u0308\u0309\u030a\u030b\u030c\u030d' // => 'À́̂̃̄̅̆Ḃ̈̉̊̋̌̍'


ちなみに「A」と「B」を「Unicode のエスケープシーケンス」ではなく普通に書くと以下です。

'A\u0300B\u0301' // => 'ÀB́'
'A\u0300\u0301B\u0302\u0303' // => 'À́B̂̃'
'A\u0300\u0301\u0302B\u0303\u0304\u0305' // => 'À́̂B̃̄̅'
'A\u0300\u0301\u0302\u0303B\u0304\u0305\u0306\u0307' // => 'À́̂̃B̄̅̆̇'
'A\u0300\u0301\u0302\u0303\u0304B\u0305\u0306\u0307\u0308\u0309' // => 'À́̂̃̄B̅̆̇̈̉'
'A\u0300\u0301\u0302\u0303\u0304\u0305B\u0306\u0307\u0308\u0309\u030a\u030b' // => 'À́̂̃̄̅B̆̇̈̉̊̋'
'A\u0300\u0301\u0302\u0303\u0304\u0305\u0306B\u0307\u0308\u0309\u030a\u030b\u030c\u030d' // => 'À́̂̃̄̅̆Ḃ̈̉̊̋̌̍'


さらに「基底文字」と「結合文字」を区別しやすくするために + で分割してみます。

'A' + '\u0300' + 'B' + '\u0301' // => 'ÀB́'
'A' + '\u0300\u0301' + 'B' + '\u0302\u0303' // => 'À́B̂̃'
'A' + '\u0300\u0301\u0302' + 'B' + '\u0303\u0304\u0305' // => 'À́̂B̃̄̅'
'A' + '\u0300\u0301\u0302\u0303' + 'B' + '\u0304\u0305\u0306\u0307' // => 'À́̂̃B̄̅̆̇'
'A' + '\u0300\u0301\u0302\u0303\u0304' + 'B' + '\u0305\u0306\u0307\u0308\u0309' // => 'À́̂̃̄B̅̆̇̈̉'
'A' + '\u0300\u0301\u0302\u0303\u0304\u0305' + 'B' + '\u0306\u0307\u0308\u0309\u030a\u030b' // => 'À́̂̃̄̅B̆̇̈̉̊̋'
'A' + '\u0300\u0301\u0302\u0303\u0304\u0305\u0306' + 'B' + '\u0307\u0308\u0309\u030a\u030b\u030c\u030d' // => 'À́̂̃̄̅̆Ḃ̈̉̊̋̌̍'

JavaScript 的には多少冗長ですが、このほうが仕組みはわかりやすいですね。

こんな感じで結合文字の数や種類をいい感じにするコードを書けば、任意の文字列を世にも恐ろしい「Zalgo Text」に変換できます。


例えば 'JavaScript''J̡͈͓͎ͥ͌͢͟a͇̩̪͍̱̿͘v̸͕̤͂̆ā̬͔̉ͣͣͬS̢͉̰̝̗ͥͤc̖͎ͤ̑ͮͥr̕͟ȋ̧̯̩̜͂́͌p̯̭͘t̤̼ͩ͞'に。

'J' + '\u0348\u0365\u0353\u0321\u0362\u034e\u035f\u034c' +
'a' + '\u033f\u0347\u0329\u032a\u034d\u0358\u0331' +
'v' + '\u0342\u0355\u0338\u0306\u0324' +
'a' + '\u032c\u0304\u0309\u0354\u0363\u0363\u036c' +
'S' + '\u0349\u0322\u0330\u031d\u0317\u0365\u0364' +
'c' + '\u0364\u0311\u0316\u036e\u0365\u034e' +
'r' + '\u035f\u0315' +
'i' + '\u0311\u032f\u0342\u0329\u031c\u0301\u034c\u0327' +
'p' + '\u032f\u0358\u032d' +
't' + '\u0369\u0324\u033c\u035e' // => 'J̡͈͓͎ͥ͌͢͟a͇̩̪͍̱̿͘v̸͕̤͂̆ā̬͔̉ͣͣͬS̢͉̰̝̗ͥͤc̖͎ͤ̑ͮͥr̕͟ȋ̧̯̩̜͂́͌p̯̭͘t̤̼ͩ͞'



まとめ

最後まで読んでいただき、ありがとうございます。
「Zalgo Text」ができる仕組み、なんとなくお分かりいただけたでしょうか?

「Zalgo Text」って面白いな!もっといろんな文字で作ってみたいな!と思った方は、ぜひ現在開催中のJS体操第3問「Zalgo Textの生成」に挑戦してみてください。

『JS体操』とは面白法人カヤックが主催する JavaScript のコードゴルフ大会。
その第3問は任意の文字列を「Zalgo Text」に変換する JavaScript の長〜いコードをできるだけ短くする!というお題です。 元々2222文字もあるコードを、なんと115文字以下まで減らせます。

みなさまのご参加をお待ちしております!

JS体操第3問「Zalgo Text の生成」

JS体操第3問「Zalgo Text の生成」編集画面 JS体操第3問「Zalgo Text の生成」編集画面(エラー)

hubspot.kayac.com