Skip to content

Instantly share code, notes, and snippets.

@kawaz
Last active October 12, 2022 00:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kawaz/c1ae0901f08e914e6d6c444cd0a1282e to your computer and use it in GitHub Desktop.
Save kawaz/c1ae0901f08e914e6d6c444cd0a1282e to your computer and use it in GitHub Desktop.
javascriptのcodePointAtとcharCodeAtの使い方や文字数の数え方などの練習
const toCodeUnit = s => s.split("").map(u => `\\u${u.charCodeAt(0).toString(16).toUpperCase().padStart(4, 0)}`).join("")
const toCodePoint = s => [...s].map(p => `\\u{${p.codePointAt(0).toString(16).toUpperCase()}}`).join("")
const splitToVisualChars = s => [...s].reduce((c, p, i, a) => {
let description = p
if (p == "\u200D") {
// ZERO WITH JOINER
description = "ZWJ"
} else if ("\u{180B}" <= p && p <= "\u{180D}") {
// モンゴル文字専用のモンゴル自由字形選択子(3個)
description = `FVS${p.codePointAt(0) - 0x180B + 1}`
} else if ("\u{FE00}" <= p && p <= "\u{FE0F}") {
// SVS用異体字セレクタ(16個, VS1~VS16)
description = `VS${p.codePointAt(0) - 0xFE00 + 1}`
} else if ("\u{E0100}" <= p && p <= "\u{E01EF}") {
// IVS用異体字セレクタ(240個, VS17~VS256)
description = `VS${p.codePointAt(0) - 0xE0100 + 1}`
} if ("\u{1F3FB}" <= p && p <= "\u{1F3FF}") {
// 絵文字修飾子(5個) https://ja.wikipedia.org/wiki/その他の記号及び絵記号#emoji_modifier
// 主に人の肌色修飾に使われてるが、🏳+🌈→🏳‍🌈 のようなのもある
description = `EM${p.codePointAt(0) - 0x1F3FB + 1}`
}
// 異体字セレクタ or 絵文字修飾子 or ZWJ or 1個前がZWJ なら前に繋げる
if(description !== p || a[i - 1] == "\u200D") {
c[c.length - 1].c += p
c[c.length - 1].d.push(description)
} else {
c.push({ c: p, d: [description] })
}
return c
}, [])
const analyzeChars = (s, {normalize=true}={}) => splitToVisualChars(normalize?s.normalize("NFC"):s).map(c => ({ char: c.c, length: c.c.length, description: c.d.join(" "), codePoint: toCodePoint(c.c), codeUnit: toCodeUnit(c.c) }))
// テスト
s = "aあガ𩸽󠄀𩸽󠄁🍎👪👨‍👩‍👧‍👦👨‍❤️‍👨👨🏻‍🤝‍👨🏿👨🏿‍🦰🏳‍🌈❤️‍🩹😮‍💨"
console.table(analyzeChars(s))
// Unicode正規化についてのメモ
console.log("ガ".normalize("NFC")) //Unicodeの正規化は基本NFCで良い
console.log("ガ".normalize("NFD")) //NFDは「ガ」が「カ」と「'゙」に分解されてキモい(最近はもう大丈夫だけど数年前のmacOSのファイルシステムの悪夢なアレ)
console.log("ガ".normalize("NFD").normalize("NFC")) //NFDが混じってる不安がある場合は一度NFCに正規化すれば綺麗になる
// 比較対象をどちらも NFC か NFD に寄せておけば一致チェックが出来る、があえて NFD にする必要性は無いので NFC に寄せるのが正義。
console.log("ガ" === "ガ".normalize("NFC")) //true
console.log("ガ" === "ガ".normalize("NFD")) //false
console.log("ガ" === "ガ".normalize("NFD").normalize("NFC")) //true
console.log("ガ".normalize("NFC") === "ガ".normalize("NFC")) //true
console.log("ガ".normalize("NFD") === "ガ".normalize("NFD")) //true
//
// ・𩸽󠄀とかみたいに絵文字と関係ない文字も異体字が存在したりするので文字数カウントはその辺も考慮が必要(異体字セレクタ)
//
// ・文字の表示単位数を計算するには、異体字セレクタや絵文字修飾子を考慮しないといけない。
//
// ・基本はコードポイント単位で文字を分割して、結合や合成に関係する文字に注意して表示文字単位で再結合していく。
//
// ・コードポイント単位で分ける前に正規化するのも重要。その場合基本は NFC を使う。
//
// ・改行制御に関しては文字分割とは別なアルゴリズムがある。だが文字数カウントとはまた別の話。
// [UAX #14: Unicode Line Breaking Algorithm](https://www.unicode.org/reports/tr14/)
//
// General Category は Unicode における文字列のカテゴリを表す。
// https://en.wikipedia.org/wiki/Template:General_Category_(Unicode)
//
// Unicode 総本山
// https://www.unicode.org/
//
// 扱いやすいデータになっているのでコード書くときはここを参照すると良い。
// https://www.unicode.org/Public/
//
// コードポイント、名前、ジェネラルカテゴリ、... のリスト
// https://www.unicode.org/Public/UNIDATA/UnicodeData.txt
//
// バリエーションやセレクタが利用されてる絵文字一覧は↓この辺りが網羅的ぽくて助かる。URLの14.0の部分は絵文字バージョンが出る度に新しいURLが出る。
// http://www.unicode.org/Public/emoji/14.0/emoji-zwj-sequences.txt
@kawaz
Copy link
Author

kawaz commented Sep 27, 2021

こんな感じに出来た。

スクリーンショット 2021-09-29 12 31 12

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment