この技術は諸刃の剣かもしれません。あるフォントがシステムにインストールされているか検査出来ると、オペレーティングシステムの種類・バージョンが推測できてしまいます。現に、そうした技術の援用でクラックなどに使われているようです。
しかし、既に Adobe からそうした目的にも使えるフォントがオープンソースでリリース済みの現在、隠し立てすることの方がデメリットが大きいので、ここでは単に技術的な見地でこの手法を紹介します。その手法は既に公知ですが、私なりに実用的になるようにアレンジしています。
Adobe はオープンソースの取り組みとして様々な有用なフォントを公開しています。その一つに、AdobeBlank と名付けられた各種フォント形式、他に AND(Adobe NotDef) フォントなど特殊な用途のフォントが github.com/adobe-fonts/ にて公開されています。
これを HTML で使うには head タグ内に以下の CSS を記述します。
<style> @font-face { font-family: 'AdobeBlank'; src: url("data:font/woff;base64,ここに `curl -L 'https://github.com/adobe-fonts/adobe-blank/blob/master/AdobeBlank.otf.woff?raw=true' | base64 | pbcopy` のように base64 エンコードした AdobeBlank.otf.woff のコピーをペースト"); } </style>
ちなみに、font/opentype
ではなく font/woff
にしたので半分以下のサイズに抑えられているものの、約 10KB とサイズが大きいので SVG フォントで代替したいところですが、成功していません。
このフォントでもいいのですが、やはりサイズが大きいので、検査用の最小限の Unicode ポイントのグリフの幅をゼロにしたフォントを GetaBlank.woff
または GetaBlank.otf
を用意しました。これは GetaBlank.svg
フォントから FontForge で変換したもので、下駄記号「〓」および等号「=」のみのコードポイントを持ちます。
これを HTML で使うには head タグ内に以下の CSS を記述します。
<style> @font-face { font-family: 'GetaBlank'; src: url("data:font/woff;base64,ここに `curl -L 'https://www.aihara.co.jp/~taiji/browser-security/js/GetaBlank.woff' | base64 | pbcopy` のように base64 エンコードした GetaBlank.woff のコピーをペースト"); } </style>
または、
<style> @font-face { font-family: 'GetaBlank'; src: url("data:font/opentype;base64,ここに `curl -L 'https://www.aihara.co.jp/~taiji/browser-security/js/GetaBlank.otf' | base64 | pbcopy` のように base64 エンコードした GetaBlank.otf のコピーをペースト"); } </style>
これは、約 1 KB とサイズが小さいので使いでがよろしいかと思われます。ページの公開元に置くなら、GetaBlank.css
の方が良いでしょう。
head
タグ内の script
タグ内で以下の関数を定義しておきます。
function is_installed_ja_font_family(names) { const name_list = names.map(v=>v.replace(/^([^']*)$/, '"$1"').replace(/^'(.*)'$/, '$1')).join(', '); //const string = '='; const string = '〓'; const span_tag = document.createElement('span'); //span_tag.style.fontFamily = `${name_list}, AdobeBlank`; span_tag.style.fontFamily = `${name_list}, GetaBlank`; span_tag.innerHTML = `${string}`; document.body.appendChild(span_tag); const is_not_blank = span_tag.offsetWidth > .0; document.body.removeChild(span_tag); return is_not_blank; }
指定するフォントのフォールバック AdobeBlank GetaBlank において下駄印「〓」を表示してみて、幅がゼロより大きければ、指定したフォントが有効ということになります。AdobeBlank もしくは GetaBlank をフォールバックとして指定しないと Firefox, Safari, Chrome において、こういった判定はできないようです。
検査用の関数は必ず、ドキュメントの読み込みが完了した後に行います。次の例は、MS Windows, Apple macOS, ubuntu GNU/Linux, Solaris それぞれでインストール済みが期待されるフォント名を検査しています。
<script> window.addEventListener('load', function () { for (const font_names of [ [ '游ゴシック', 'Yu Gothic' ], // windows [ '游ゴシック体', 'YuGothic' ], // macOS [ 'Noto Sans CJK JP' ], // ubuntu [ 'HeiseiMin' ], // solaris ]) { const is_installed = is_installed_ja_font_family(font_names); if (is_installed) { console.log(font_names.join(', ')); // 有効なフォント名の場合の処理 } } }); </script>
例えば、手元の Windows だと以下のような結果がブラウザのコンソールに印字されます。
游ゴシック, Yu Gothic
フォントが無効(「〓」が未定義)なら以下にフォント名が赤で印字されます。そして、グリフがない場合未定義が 🅇 となる AND-Regular
フォントで代替されます。
⇥
) か enter (⌤
) で実行
このように好みのフォントファミリ名で、下記で行っているような試験結果を表示できます。
フォントが無効(「=」が未定義)なら以下にフォント名が赤で印字されます。そして、グリフがない場合未定義が 🅇 となる AND-Regular
フォントで代替されます。
⇥
) か enter (⌤
) で実行
このように好みのフォントファミリ名で、下記で行っているような試験結果を表示できます。
フォントが無効(「〓」が未定義)なら以下にフォント名が赤で印字されます。そして、グリフがない場合未定義が 🅇 となる AND-Regular
フォントで代替されます。また、ウェブフォントなどの確実な判定には至らないときもあります。
あくまで日本語フォントとして利用が見込めるかをたった一文字「〓」から判断しているので、単にフォントが存在するかであればイコール記号「=」が未定義かを調べれば十分かと思います。ちなみに、なぜゲタ「〓」を選んだのかと言えば、日本にはある環境で印字できない文字を表すとき、このゲタが使われる独特な慣わしがあり、かつ JIS X 0208 定義の文字だからです。より新しい JIS X 0213 対応日本語フォントであれば「〼」あたりを判定基準にしてもよいでしょう。
AdobeBlank
フォントで試みが成功して、のちに拙作 GetaBlank
で成功して、しかし、ここの記事としては(フォントの MIME タイプを別のを試したりして)中途半端な状態で一年以上放置してしまっていたようです。もし、読者に混乱を与えたなら申し訳ありません。現状の記事は少なくともキリのよい状態にはなっています。
以下、他所の記事の転載ですが、ちょっとした実験で、本文と関係ありません。すいません。
ここの話題は CSS 変数(カスタムプロパティ)とその寿命に関係する。
さて、table[border]
つまり、テーブルの border
属性が HTML Live Standard で廃止ということで、ブラウザが事実上サポートを続けるかもしれないが、準備はしておきたいと思った。
本節の末尾で紹介するクラス付きテーブルスタイル table.border
を使うと、border
属性付きテーブルとほぼ等価な見栄えになる。左がブラウザ既定、右がこのスタイルによる実現である。
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 |
---|---|---|---|---|---|---|---|
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | ||
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 |
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 |
---|---|---|---|---|---|---|---|
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | ||
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 |
これで実用的には十分なのかもしれないが、多少汎用性を持たせよう。border="2"
と指定したときのようにスタイルファイルで見栄えを実現する。そのために CSS 変数を活用してみよう。インラインスタイル定義としてテーブルの属性 style="--table-border-width: 2px;"
を付記すればよい。左がブラウザ既定、右がこのスタイルによる実現である。
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 |
---|---|---|---|---|---|---|---|
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | ||
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 |
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 |
---|---|---|---|---|---|---|---|
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | ||
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 |
Firefox, Chrome, Safari それぞれが異なるのでまったく同じにはならないが、むしろより自然な様相になってると思う。
さて、せっかくここまで再現できたので、少し調子に乗って他によく使われる table[border][rules='all']
だけはサポートしておこうと思った。
本節の末尾で紹介するクラス付きテーブルスタイル table.border_rules-all
を使うと、border
, rules='all'
属性付きテーブルとほぼ等価な見栄えになる。左がブラウザ既定、右がこのスタイルによる実現である。
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 |
---|---|---|---|---|---|---|---|
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | ||
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 |
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 |
---|---|---|---|---|---|---|---|
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | ||
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 |
素晴らしい!実用上、これらの属性(但し、rules='all'
のときのみ)がブラウザで廃止されても困らない。
一応、border="5"
としたときの対応として、インラインスタイル定義としてテーブルの属性 style="--table-border-width: 5px;"
を付記したものも実現しておこう。
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 |
---|---|---|---|---|---|---|---|
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | ||
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 |
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 |
---|---|---|---|---|---|---|---|
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | ||
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 |
rules='all'
以外のクラスもいずれ作成しておくかもしれない。
さて、ここで style="--table-border-width: 5px;"
などを付記しているので、万が一次のテーブルで枠線が太いままだったら困る。最初の例の表をそのまま載せてみる。
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 |
---|---|---|---|---|---|---|---|
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | ||
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 |
0,0 | 0,1 | 0,2 | 0,3 | 0,4 | 0,5 | 0,6 | 0,7 |
---|---|---|---|---|---|---|---|
1,0 | 1,1 | 1,2 | 1,3 | 1,4 | 1,5 | ||
2,0 | 2,1 | 2,2 | 2,3 | 2,4 | 2,5 |
流石に変数が上書きされてしまうようなことはない。からくりは CSS 変数はスタイルの継承によって参照・定義されるので、この場合、これらは要素の継承関係にはないので、意図しない変数の変更には至らない。至極当たり前のことなのだが、実際に確認してみて納得できた次第である。
では、最後にこのスタイルファイルを紹介しておく。
table.border { border-color: darkgray; border-style: outset; border-width: var(--table-border-width, 1px); border-collapse: separate; border-spacing: var(--table-border-spacing, 2px); } table.border > :is(thead, tbody, tfoot) > tr > :is(th, td) { border-color: darkgray; border-style: inset; border-width: var(--table-td-border-width, 1px); } table.border_rules-all { border-style: outset; border-width: var(--table-border-width, 1px); border-collapse: collapse; } table.border_rules-all > :is(thead, tbody, tfoot) > tr > :is(th, td) { border-style: solid; border-width: var(--table-td-border-width, 1px); }
但し、SeaMonkey ではセレクタ :is()
が働かないので多少工夫を要するのだが、時間が解決する問題だと思うのでそのままにしておく。