c4se記:さっちゃんですよ☆

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

.。oO(此のblogは、主に音樂考察Programming に分類されますよ。ヾ(〃l _ l)ノ゙♬♪♡)

音樂は SoundCloud に公開中です。

考察は現在は主に Scrapbox で公表中です。

Programming は GitHub で開發中です。

Unicode舊字をAndroidで表示する為に、Notoフォントの部分フォントを作りWebフォントにする

c4seを六年ぶりに更新してゐる。さすがにリニューアルした。 http://c4se.jp である。このblogは最近は舊かなで書いてゐる。c4seは漢字も全て舊字にすることにした。参画してゐる橘榛名の意向に依るものだ。
今回は舊字とWeb fontの話である。
さて、コンピューターは文字を表示することができる。文字列に関する議論は濟んだとみなして、ある一文字を認識できたところからはじめよう (つまり符号化=文字コードについては考へずに済む) 。それぞれの文字は或る整数値で表される。この整数値は、巨大な對照表 (コードポイント) により文字と結びついてゐる。或る人間が、この文字はこの整數で表すと決めたのだ。この對照表に基づき、別の人間がフォント (字體) といふものを作ってゐる。この整數は、こういふ形で表示するとよい、とコンピューターに提示するものだ。文字といふ抽象物を整數に対応すると決め、整數をこの形で表示すると決める。このふたつの規則が対応してゐるかから、文字を正しい形で表示でき、しかも高速に通信できるのだ。
文字と整數との對照表は、いろんな人がいろんなタイミングでつくってゐる。JISの人々が定めたものと、Unicodeの人々が作ったものが、日本のWebでは良く使はれてゐるだらう。JISの人々が定めたものを、通信できる文字列に符號化する方式が、Shift JISやEUC-JPだったりする (コードポイントと文字コードを区別せよ、階層の異なる概念だ)。Unicodeの人々が定めたものを符號化する方式が、UTF-8といった方式だ。ここで、JISの作った對照表は、Unicodeを作るときに取り込まれてゐる。Unicodeは世界中の文字を格納せねばならないので、日本といふ大きな国の文字を取り込むために、JISコードといふ一般的なコードを使ふことで済ませた。つまり、JISコードに入ってゐる文字は、Unicodeでも使ふことができる。しかし逆はできない。Unicodeは頻繁に更新される。JISに入ってゐないが、日本で使はれる文字は、後からUnicodeに収録されることになる。これらはJISコードでは利用できない。
フォントも、いろんな人がいろんなタイミングでつくってゐる。日本語フォントも多くの人が作ってゐる。ここでフォントを作りはじめるときに編集方針をきめなければならない。後のちの苦労を決めてしまうために、一番大きいのは、どのコード對照表にしたがふかといふ問題だ。英語圈やそれに近い人々には縁のない問題だ。彼らはASCIIコード約64文字か、拡張ASCIIコード数百文字かを選択するだけで済む。大した決定ではない。ところがCJKあるいはCJKV (中国・日本・韓国・ベトナム) に代表される文化圏の人々はちがふ。日本ではフォント制作者は、JISコードにしたがふか、JISコードなら第二、三、四水準までつくるか第一水準で済ませるか、Unicode漢字に対応するか、迫られる。第一水準までは根氣でつくれる。だがその先は果てしない。
多くの売られてゐるフォントは、ありがたいことにJIS第四水準まで対応してゐる。果てしない。第三水準まであれば、新漢字はもちろん舊字の殆ども表示できる。果てしない。Android OSに搭載されてゐるフォントも第ニ水準まで対応してゐるので、殆どの環境で舊字は表示できることになる。Windows OSとMacOS Xに搭載された游フォントなどはUnicode漢字に対応してゐるので、実質全ての舊字を表示できる。万歳だ。あれ? AndroidUnicode舊字は…?
そう。AndroidUnicode舊字は表示できないのだ。表示しようとすると不思議な半角空白がそこにはある。なにも見えない。あきらめるのか。JISに入ってゐない漢字は新字で書くしかないのか。
しかしここにWeb fontといふものがある。
WebブラウザやOSといふのは賢いもので、文字列の表示に特定のフォントを指定してゐても、そのフォントに文字がなければ、その文字があるフォントを探してきて、その文字だけをそのフォントに切替へて表示することができる。日本語Web fontの出番である。Unicode舊字だけが入ったフォントをつくりWeb fontとして配信し、通常のJISコード漢字はOS通常のフォントを使はせればいい。そしてこれはうまくいく。

まずJIS第二水準に含まれないUnicode舊字の一覧が必要だ。異体字セレクタは考慮する能がないのでしてゐない。これは橘榛名に作って貰ふ。現時点のものは以下にある。
cf. Unicode舊字JIS舊字對照表.txt https://github.com/ne-sachirou/c4se-web/blob/89eb31cca22e7ca44571253aea7d1e596db40c3a/lib/SeiJi/Unicode%E8%88%8A%E5%AD%97JIS%E8%88%8A%E5%AD%97%E5%B0%8D%E7%85%A7%E8%A1%A8.txt
Unicode舊字→JIS字の形式にしてあるのは、部分フォントの作成が失敗した場合に備へ、Android OS上ではこの表をもとにUnicode舊字をJIS字に変換してしまおうと目論んだからだ。

部分フォントを作る。素に、GoogleAdobeが作った、ライセンスの自由なNotoフォントをもってくる。果てしないフォントだ。NotoSansCJKjp-Regular.otfを使ふ。
フォントを操作するにはFontForgeを使ふ。FontForgeスクリプト機能は貧弱だが、この程度の仕事はできるのだ。
方針としては、Notoフォントから、不必要な文字を全て取り除き、別名で保存する。
必要な文字の一覧は別の方法で一覧し、これを処理するFontForgeスクリプトのファイルを自動生成する。
上記のUnicode舊字JIS舊字對照表から、Unicode舊字の一覧を抽出するBashスクリプトは以下である。

#!/bin/bash

cwd=$(pwd)
cd $(dirname $0)/../lib/SeiJi
chars=`cat Unicode舊字JIS舊字對照表.txt | perl -e '$in=join("",<STDIN>);$in=~s/\A---.*\n---\n//ms;print $in' | sed -e 's/#.*$//g' | sed -e '/^\s*$/d' | awk '{print $1}'`
echo $chars | php uniseiji_font.pe.php > uniseiji_font.pe
rm ../../lib/assets/uniseiji.ttf 2> /dev/null
fontforge -script uniseiji_font.pe
cd $cwd

cat | perl | sed | sed | awk | php | fontforge といふUnixぶりだ。なにもBashスクリプトでやってゐない。
Unicode舊字一覧からFontForgeスクリプトを生成するPHPは以下だ。

<?php
function uOrd($char)
{
    return unpack('N', mb_convert_encoding($char, 'UCS-4BE', 'UTF-8'))[1];
}

$chars     = explode(' ', trim(fgets(STDIN)));
$charsOrig = $chars;
$chars     = array_map(
    function ($char) {
        return '0u'.dechex(uOrd($char));
    },
    $chars
);
$charsOrig = array_map(
    function ($char) {
        return '"'.$char.'"';
    },
    $charsOrig
)
?>#!/usr/bin/env fontforge

Open('../../src/fonts/NotoSansCJKjp-Regular.otf')
# CIDChangeSubFont('NotoSansCJKjp-Regular-Ideographs')
CIDFlatten()
Reencode('unicode')
chars     = [<?php echo implode(',', $chars    ); ?>]
charsOrig = [<?php echo implode(',', $charsOrig); ?>]
i         = 0
iz        = SizeOf(chars)
while (i < iz)
    if (InFont(chars[i]))
        Print(charsOrig[i])
        SelectMoreSingletons(chars[i])
    else
        Print("No " + charsOrig[i])
    endif
    ++i
endloop
SelectInvert()
Clear()
SetFondName('UniSeiJi')
Generate('../../lib/assets/uniseiji.ttf')
Close()

ほとんどFontForgeスクリプトのそのままである。
Reencode() は CIDFlatten() の後に必要だ。フォントの必要な文字を一つづつ選択してゆき、選択を反転させ、選択してゐるグリフを全て消し、別名で保存する。SelectAll() してから SelectFewer() で選択を外してゆく方法もあるとおもふが、どちらでもよいのではないか。一回の SelectSingletons() で必要なグリフを全て選択してしまはないのは、百数十個といふ多くの引数を一度に渡さうとすると、エラーでクラッシュするFontForgeの貧弱さの爲だ。
UTF-8字からUnicodeコードポイントを得る関数は、以下のものである。
cf. PHPのordはASCII文字にしか対応してゐない。Unicode版を書く http://c4se.hatenablog.com/entry/2015/05/03/035347

ここから以下のFontForgeスクリプトが生成される。

#!/usr/bin/env fontforge

Open('../../src/fonts/NotoSansCJKjp-Regular.otf')
# CIDChangeSubFont('NotoSansCJKjp-Regular-Ideographs')
CIDFlatten()
Reencode('unicode')
chars     = [0ufa67,0ufa1e,0u92b3,0ufa17,0u6085,0ufa62,0u95b1,0u2f8fc,0u7de3,0u2f995,0ufa45,0ufa3e,0u69ea,0ufa36,0u6e34,0ufa60,0u2f822,0u9592,0ufa47,0ufa4e,0u65e3,0u2f862,0ufa38,0u9115,0ufa69,0u8173,0u865b,0ufa34,0ufa63,0ufa52,0ufa3d,0u9ec3,0u5bec,0ufa2c,0u85b0,0uf909,0u63ed,0u654e,0u64ca,0u9830,0u784f,0u6236,0u5433,0u5a1b,0u543f,0u9ed1,0ufa54,0u6b72,0u5de2,0u7abb,0uf970,0u7522,0ufa4d,0ufa61,0ufa2b,0u2f83f,0ufa5c,0u20b9f,0ufa4c,0ufa5b,0ufa48,0u5c19,0ufa1a,0u72c0,0u2f852,0u2f828,0u2f921,0ufa51,0ufa43,0ufa5a,0u7dd6,0ufa22,0ufa19,0u2f8b2,0u9751,0u6df8,0ufa12,0ufa1d,0u7a05,0ufa56,0u8aaa,0u7d55,0u6d89,0ufa50,0ufa31,0ufa3b,0u7626,0u589e,0ufa3f,0ufa65,0u537d,0ufa03,0u812b,0u2f91a,0ufa37,0ufa5f,0u5fb5,0ufa40,0ufa10,0u2f89a,0u2f90f,0u5861,0ufa26,0u2f81a,0u9b2d,0u5fb7,0ufa55,0u5167,0ufa68,0uf95f,0ufa44,0u525d,0ufa2a,0ufa59,0u665a,0ufa35,0ufa4b,0ufa64,0ufa6a,0ufa41,0ufa30,0ufa1b,0u5002,0ufa39,0u7501,0u8c93,0ufa33,0u6b65,0ufa3a,0u6bcf,0ufa32,0u2f9d0,0u2f9df,0u8cf4,0u7028,0u90de,0uf929,0uf928,0uf91d,0uf9dc,0uf983,0uf936,0u7da0,0u6dda,0uf9d0,0u623e,0u66c6,0u6b77,0uf99a,0uf9a2,0ufa57,0u934a,0u9304,0u6a6b,0u6eab,0u2f80f,0u2f833,0u5f65,0u6451,0ufa46,0ufa6a,0ufa16,0ufa4a,0u79b1,0uf91f,0u91ac,0ufa1c,0u985a,0u9dd7]
charsOrig = ["","","","","","","","沿","","芽","","","","","","","割","","","","","姬","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","周","","𠮟","","","","","","","城","勺","爵","","","","","","","成","","","","","","","","","","","","","","","憎","","","","","炭","","","","","","彫","潮","","","冬","","","","","","","","","","","","","","","","","","","","","","","","","","","","諭","輸","","","","","","","","","","","","","","","","","","","","","","","兔","卿","","","","","","","","","","","",""]
i         = 0
iz        = SizeOf(chars)
while (i < iz)
    if (InFont(chars[i]))
        Print(charsOrig[i])
        SelectMoreSingletons(chars[i])
    else
        Print("No " + charsOrig[i])
    endif
    ++i
endloop
SelectInvert()
Clear()
SetFondName('UniSeiJi')
Generate('../../lib/assets/uniseiji.ttf')
Close()

これが自動で実行され、uniseiji.ttfといふ、Unicode舊字だけを含んだTTFフォントができあがる。113.7Kbだ。NotoSansCJKjp-Regular.otfは14.6Mbである。無駄が多い気がするが面倒なのでほおって置く。
CSSは以下のとおりに指定した。Web fontの配信はとても簡単だ。

@font-face {
  font-family : UniSeiJi;
  src         : url("/lib/assets/uniseiji.ttf");
}

body {
  font-family : 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, UniSeiJi, 'ヒラギノ角ゴ ProN W3', 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif;
}

それだけ。