くりーむわーかー

プログラムとか。作ってて ・試しててハマった事など。誰かのお役に立てば幸いかと。 その他、いろいろエトセトラ。。。

Unicode

各ブラウザのサロゲとIVSとIPAmj明朝の表示

IPAがやっている、IPA文字情報基盤というのがある。

https://mojikiban.ipa.go.jp/

そこで出しているフォントがIPAmj明朝。

結構前からやってたと思うのですが、そろそろ各自治体での利用が

広がりそうなのでちょっとまとめ。マイナンバーのフォントはこれ使ってるらしい。

ほんとかどうかは不明。

前に国が似たようなブラウザ表示の確認をやってて、報告書みたいなのがネットに転がってたと思うのですが、相当古かったと思うので今の時点だとどうかというところ。


日本語で気を付けないといけないのは、通常のUniの文字以外に、

UNI+IVS、サロゲ、サロゲ+IVSあたりがある。※あとは外字。

IPAmj明朝はそういうところもやってるので、表示が出来るかどうかというところ。

とりあえず、Chrome・Edge・Firefoxの結果。

使った各ブラウザのバージョンは下記。

Chrome
86.0.4240.183(Official Build) (64 ビット)

FireFox
82.0.2 (64 ビット)

Edge
Microsoft Edge 42.17134.1098.0
Microsoft EdgeHTML 17.17134


OSはWindows10

フォント指定のCSS

.ipafont {
  font-family: 'IPAmjMincho', 'IPAmj明朝';
}


font-test

実HTML置いておくので、自環境で確認したい場合はご利用ください。※クライアントにIPAmj明朝インストールしないとまともに表示されません。

パターン CODE 字(HTML) input textarea 正解画像
UNI u+845B MJ022336
UNI+IVS u+845B U+E0102 葛󠄂 MJ022335
サロゲ U+20000 𠀀 MJ030312
サロゲ+IVS U+20000 U+xE0101 𠀀󠄁 MJ030313

UNIとUNI+IVSはどのブラウザも問題無し。

問題はサロゲ。EdgeとFireFoxはOK。ChromeはIPAフォント使えてない...

Chromeさんバグってます?

あと、inputのmaxlength。

Edgeだけはどのパターンの文字も一文字としてカウントしてくれてる。

FireFoxとChromeさんは例えば、maxlength="4"って指定しても、サロゲ+IVSなんかだと、1文字しか入力できなくなる。

日本語の文字・フォントの扱いは「Edge > |超えられない壁| > FireFox > Chrome」って感じかしら。

Edgeがだいぶ優秀ですね。

つか、日本語みたいな少数民族の文字なんて英語圏の開発者からみたら正直どうでもいいでしょうしね。しょうがないと言えばしょうがない...

inputのmaxlengthは自前で作らないとダメそう。

chromeさんの表示がダメなのは治るのを期待して気長に待つしかないかな?

と思ったけど、別環境のchromeで見るとちゃんと表示されてる...

ん?何か違うか?謎。要確認。

追記

Chrome、複数環境で何個か確認したけど、やっぱりちゃんと表示されていた(Linux含む。

最初に確認した環境のみおかしいっぽい。Chromeさん濡れ衣でした。スミマセン。

でも何で最初の環境はおかしいんだろうか?EdgeとかFireFoxは大丈夫なんだけども...


あと、以下のIPAが出してる文字一覧でひそかに謎の定義が存在する。

ipa-moji

”ここだけ”IVS定義が重複して存在するという...

ホントこういうのは止めて欲しいところ。何か理由があるのでしょうけども。


2021/1/2追記

自治体システム標準化でこのフォントが標準になっていくと思うのですが、Webで扱うにはデカ過ぎるんですよね。本体30MBくらいあったと思う。これWebフォントにしろとか言わないよね...

標準化するならクライアントにこのフォントをインストールする前提で考えさせてほしいところ。

昔に比べたら十分早くはなっていると思うのですが、全面Webフォントとかクソ重くなりそう...

業務システム的に考えると、結局CSVとかエクセルとか落として、事務的な計算とかをやるので、そっちでフォント使えないと結局アレですし。ローカルインストール前提にして欲しいよね。

まーでも、人名や住所で外字がなくなるとは思えないので、IPAmj明朝にする意味がどこまであるのやら。。。

標準システムで、本気で外字の利用をNGにしてくれるんならいいんだけどなー。まー無理そう。国としてオープンデータの流れに持っていきたいのであれば、この辺の基礎的な部分はガチガチに固めてくれてもいいと思うんだけど。

SEの端くれ的な感覚で、エンジニア側の都合だけで考えれば統一してよって感じではある。何もしないで標準で動くのが一番良い。というか楽。

ただ、やっぱり自分の名前や文字っていうのは一つの文化なわけで、そこのこだわりを捨てろっていうのも暴論な気もするし難しいところですね。

全国で文字の字形の同定を行って外字に入ってる文字の統一は、作業的には出来なくはない(恐ろしく手間かかるし、泥臭い作業になりますが。。。

ただ、それをやった場合に、既存データの置き換えを自治体毎に置換のPG組まないとダメそうだし、データ移行な作業もとんでもないコストかかりそうだし、やっぱりなかなか難しそうですね。。。

サロゲートとIVSの正規表現

サロゲートとIVSの正規表現まとめ。

IVS含めたものってなかなか見ないので。

どうなんだろ。皆どうやってるんでしょね。


サロゲートのコード範囲

上位サロゲート:U+D800-U+DBFF

下位サロゲート:U+DC00-U+DFFF

IVSのコード範囲

32bit表現:U+E0100-U+E01EF

で、これの16bit表現がなかなか出てこなくて困った。

なので計算して求める。とりあえず結果。

16bit表現:U+DB40 [U+DD00-U+DDEF]


なので、正規表現は下の感じ。

// javascript

// サロゲートがあるかどうか
str.match(/([\uD800-\uDBFF][\uDC00-\uDFFF])/)

// IVSがあるかどうか
str.match(/(\uDB40[\uDD00-\uDDEF])/)

// サロゲート+IVSがあるかどうか
str.match(/([\uD800-\uDBFF][\uDC00-\uDFFF])|(\uDB40[\uDD00-\uDDEF])/)

// サロゲートを含む文字列を一文字ずつの配列にする
str.match(/([\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF])/g)

// サロゲート+IVSを含む文字列を一文字ずつの配列にする
str.match(/([\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF])(\uDB40[\uDD00-\uDDEF])?/g)

32bit表現が使える場合は下の感じ

//Python

// サロゲート+IVSを含む文字列を一文字ずつの配列にする
re.findall('([\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF])[\U000E0100-\U000E01EF]?', strs)


IVSの32bit表現を16bit表現にする計算。

計算方法:wiki : 拡張領域→サロゲートペアの項

0xE0100            1110 0000 0001 0000 0000
0xE01EF            1110 0000 0001 1110 1111

上をサロゲートペアとしてエンコーディング

①0x10000を引く

 0xE0100            1110 0000 0001 0000 0000
-0x10000            0001 0000 0001 0000 0000
=0xD0100            1101 0000 0001 0000 0000

 0xE01EF            1110 0000 0001 1110 1111
-0x10000            0001 0000 0001 0000 0000
=0xD01EF            1101 0000 0001 1110 1111

➁これを上位10ビット値と下位10ビット値に分割する。

0xD0100            1101 0000 00(0x0340)    01 0000 0000(0x0100)
0xD01EF            1101 0000 00(0x0340)    01 1110 1111(0x01EF)

➂ハイサロゲート形成として上位ビットに0xD800を加える。

 0x0340            0000 0011 0100 0000
+0xD800            1101 1000 0000 0000
=0xDB40            1101 1011 0100 0000

 0x0340            0000 0011 0100 0000
+0xD800            1101 1000 0000 0000
=0xDB40            1101 1011 0100 0000


④ローサロゲート形成として上位ビットに0xDC00を加える。

 0x0100            0000 0001 0000 0000
+0xDC00            1101 1100 0000 0000
=0xDD00            1101 1101 0000 0000

 0x01EF            0000 0001 1110 1111
+0xDC00            1101 1100 0000 0000
=0xDDEF            1101 1101 1110 1111

⑤上記から

0xE0100 ⇒ \uDB40\uDD00
0xE01EF ⇒ \uDB40\uDDEF

⑥なのでコードの範囲は下位8bitの範囲(00-EF:240個)として表現しちゃってよい(上16bit分は固定)ので下記正規表現

\uDB40[\uDD00-\uDDEF]

PythonとサロゲートとIVS

PythonのIVS(異字体セレクタ)対応を調べてたのですが、全く出てこない...

誰も困ってないのかしら?

Pythonで普通に文字数とか取得しようとするとIVSはとりあえずダメになると思うんだけど。

s = '\U00008FBB'
print(s, len(s))  # 辻(2点ツジ), 1
s = '\U00008FBB\U000E0102'
print(s, len(s))  # 辻(1点ツジ), 2

色々調べたのですが、lenとかsubstringとか良い感じにカウントしたり、処理したりしてくれるライブラリ的なものも無い。。。

どーなんだろ。

まーいいや。ないなら作るしかあるまい。

という事で作った。

# 文字数カウント
def newLen(s):
    l = re.findall(
        '([\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF])[\U000E0100-\U000E01EF]?', s)
    return len(l)

# substring
def newSubstring(s, st, ed):
    l = re.findall(
        '([\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF])[\U000E0100-\U000E01EF]?', s)
    return ''.join(l[st:ed])

substringの方はかなり適当でエラー処理的なものとかガン無視。

重要なのは正規表現でサロゲとIVS考慮で一文字ずつの配列に変換して、

その配列の要素数やsliceでsubstringの代替をする感じ。

出来上がる配列は↓の感じ。

"あ\uE000\U00008205\U00008204\U000E0101\U00020C25\U0002B7CB\U000E0102"
↓
["あ","\uE000","\U00008205","\U00008204\U000E0101","\U00020C25","\U0002B7CB\U000E0102"]
※[普通の字 , 外字 , UNI , UNI+IVS , サロゲ , サロゲ+IVS]

ちなみに、javascriptでもほぼ同じロジックで同じこと出来るはず。

ただ、まともに検証してないので、今度ちゃんとやろ。

問合せ