Last active
December 7, 2024 01:27
-
-
Save kawaz/c1ae0901f08e914e6d6c444cd0a1282e to your computer and use it in GitHub Desktop.
javascriptのcodePointAtとcharCodeAtの使い方や文字数の数え方などの練習
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
こんな感じに出来た。