にせねこメモ

はてなダイアリーがUTF-8じゃないので移ってきました。

UbuntuにX11転送を有効にしてsshで接続したらgeditなどがSegmentation Faultした

sshX11 forwardingを有効にしてUbuntuにログインし、ログイン先のnautilusやgeditなどを起動しようとすると、Segmentation Faultと表示されて動作しなかった。
一方で、xtermなどは正常に実行でき、ちゃんとそのウィンドウが表示された。

ログイン先のOSはUbuntu 16.04 64bitで、Nvidiaのグラボを利用している。オンボードグラフィックがないマザーボードを使っている。

解決策

Open GUI apps on a Ubuntu 16.04 machine via SSH from an Ubuntu 14.04 machine - Ask Ubuntu
これを見て解決した。

原因はNvidiaのドライバかなんかにあるらしい。 libGLX_indirect.so.0 がうまく見つけられてないとのこと。
次の様にリンクを張ると動作する様になった。

cd /usr/lib/x86_64-linux-gnu
sudo ln -s /usr/lib/nvidia-361/libGLX_indirect.so.0

nvidia-361の部分は環境(ドライバ)に合わせてといった感じになると思う。

GPOSのCursive Attachment Positioningについて

序説

OpenTypeフォントの高度組版機能においてグリフの位置調整を行うのがGPOSである。GPOSは主にペアカーニングやダイアクリティカルマークを適切な位置に表示するために利用される。

GPOSの中には更に、筆記体の接続を実現するためのCursive Attachment Positioningというものがある。筆記体の接続と言う名前ではあるが、文字が水平に並ぶ英語などの筆記体のためのものではなく、ペルシア語やウルドゥー語で用いられるアラビア文字ナスタアリーク体などのために追加されたものだろう。ナスアリーク体では文字の塊ごとに文字が右上から右下に流れ、グリフの上下位置が一定しないために、この機能がないと適切な表示ができない(例などは参考サイト参照)。

特にウルドゥー語などではこれがないと標準的な組版もできない様な機能ではあるが、日本語や欧文では不要である。
それでも、グリフ間の位置調整ができれば便利だという気持ちもあるので、使えないか調べてみる。

Fontforgeによる設定方法

さて、Fontforgeでどのように利用するかを見ながら実際の動作を見ていく。
M+フォントのmplus-1p-regular.ttfを利用することにする。

まず、Fontforgeでmplus-1p-regular.ttfを開く。最初にASCII外のグリフを削除し、GSUB, GPOSについても既存のものを削除しておいた。

GPOS lookupとanchor classの作成

メインウィンドウのメニューから、エレメント→フォント情報を開く。
Lookupsタブ→GPOSタブを開き、Add Lookupをクリックする。
f:id:nixeneko:20170114153645p:plain


次に開くLookupウィンドウで、

  • 種類: 「Cursive Position」を選択。
  • 機能: 「curs」を選択。
  • 用字系と言語は適切に。デフォルトで動くはず。
  • Lookup Name: 適当に設定。デフォルトでも良い。

など設定し、OKをクリック。
f:id:nixeneko:20170114154120p:plain


すると今設定したlookupが追加されるので、追加したlookupを選択した状態で「Add Subtable」をクリック。
f:id:nixeneko:20170114154312p:plain


サブテーブルの名前を聞かれるダイアログが開くので適当に名前をつけて(デフォルトでよい)、OKをクリック。
するとAnchor Class Nameを設定するダイアログが開くのでクラスを追加していく。<New Anchor Class>をクリックするとスロットが追加されるので適当に名前を入力して(ここでは“cursive”)、OKを押す。
f:id:nixeneko:20170114154833p:plain


「フォント情報」ウィンドウをOKを押して閉じる。

これによってアンカークラスが追加されたので、アンカー点が設定できるようになる。

Anchor pointの設定

適当なグリフ(ここではa)をダブルクリックしアウトラインウィンドウを開く。
メニューから、点→アンカーを追加(あるいはアウトライン領域で右クリック→アンカーを追加)を選ぶ。
f:id:nixeneko:20170114155520p:plain


するとアンカー点の情報が表示されるので、先ほど追加したcursiveクラスを選び、「筆記体の始点」を選択、OKを押す。座標はこのダイアログで設定してもいいし、青い+字点をドラッグして移動することもできる。このダイアログは、青い+字点を右クリック→情報を得る、などから再度表示させることができる。
f:id:nixeneko:20170114155840p:plain


同様に筆記体の終点を追加する。前とほぼ一緒で、違いは「筆記体の終点」を選択するところだけである。

f:id:nixeneko:20170114162155p:plain
追加したのが上の画像である。始点を(0, 0)に、終点を(548, 0)に置いてみた。y座標が一緒でx座標の差分がグリフの幅に一致しているため、この状態だとcursを有効にしてもなにも変化しない、はずである。

試しにメトリックウィンドウを開いて確認してみる。
f:id:nixeneko:20170114162617p:plain
有効無効によって変化がない。同じアンカークラスに属するものについて、次のグリフの始点が前のグリフの終点に重なるように移動する、というのがアンカーポイントによる位置調整なので、当然の動作であるように思う。


さて、今度はcursive終点の位置を少し上側にずらしてみる。
f:id:nixeneko:20170114162929p:plain


今度は次にくるaはcursive終点の位置に移動するはずなので、やや上に上がって表示されるはずだ。メトリックウィンドウで試してみる。
f:id:nixeneko:20170114163134p:plain
期待通りである。

同様に別のグリフにも対してもアンカー点を追加していってみる。小文字には始点・終点を設定し、大文字には終点だけ設定してみる。コンマピリオドにも始点を設定してみる。

最終的に、TrueTypeアウトラインのフォントとして出力する。

ダウンロード

出力したフォントは次からダウンロードできる。

ソフトウェア側の対応

さて、フォントが対応していてもフォントを扱う側のソフトウェアが対応していないと正しく表示できないのがフォントの世界である。この機能が必須であるウルドゥー語などを除いては機能を無効にしているなどといった可能性もある。

それでは、ソフトウェア側の対応を見ていく。OpenTypeタグ'curs'が有効にできればいいのであるが。なお以下、すべてWindows 10で動かした結果である。

Adobe InDesign CS6

Adobe InDesign CS6はダメっぽい。そもそも'curs'に対応する設定項目が見当たらない。他のAdobeソフトも対応は期待できなさそうな気がする。

Microsoft Word 2013

設定項目が見当たらないのでダメっぽい。

各種ブラウザ

CSS3でfont-feature-settingsプロパティが追加され、それによってOpenType featureが個別に有効を設定できる。期待できそう。
CSSだと、次のような設定をすることになる。

font-feature-settings: "curs";

これを実際にはフォント指定と組み合わせる。

Webフォントで表示させてみたのが次である。

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

各ブラウザの対応を見ていく。

Firefox (50.1.0)

f:id:nixeneko:20170114174319p:plain

Google Chrome (55.0.2883.87 m)

f:id:nixeneko:20170114174308p:plain

Microsoft Edge (25.10586.672.0)

f:id:nixeneko:20170114175208p:plain
EdgeやIE

font-feature-settings: curs;

ではだめで、

font-feature-settings: 'curs';

のように(シングルでもダブルでもいいけど)クォーテーションでくくらないとちゃんと動かないみたい。

さて、最近のブラウザ正しく動いてるようである。しかし、滅茶苦茶不安になるな、これ……。

LaTeX

LaTeXは、fontspecパッケージによって、OpenType featuresを個別に設定することができる。

\setmainfont[Path=./,Extension=.ttf,RawFeature=+curs]{Acurstest}
\newfontfamily\curstest[Path=./,Extension=.ttf,RawFeature=+curs]{Acurstest}

みたいにフォント設定時にRawFeature=+cursオプションを設定するか、あるいは、オプションを適用したいテキストの直前で

\addfontfeature{RawFeature=+curs}

と書いて指定する。

XeLaTeXとLuaLaTeXのみ検討する。なお以下はどちらもCygwinで導入したTeX Live 2016のものによる出力である。

XeLaTeX

f:id:nixeneko:20170114202027p:plain
U+2019を削除してしまったのでXeLaTeXではアポストロフィ'が豆腐になっている。

LuaLaTeX

f:id:nixeneko:20170114202152p:plain

どちらでも正しく動いているようであるが、aaaaa...の行が上と重ならないなど、行間の扱いがブラウザの場合と異なっているのがわかる。また、aaaaa...の行を見るに、XeLaTeXとLuaLaTeXでも行間の扱いが異なっているようである。どうなってるの…。

なお、XeLaTeXでは\begin{document}より前に\addfontfeatures{RawFeature=+curs}を書いても有効になったのだが、LuaLaTeXでは有効にならなかった。微妙に動作が異なるらしい。

まとめ

ソフトウェアの対応が悪い。使用できる環境が限られるので、万能ではない。先に使いたい環境で使えるかを確かめてからやらないといけなさそう。

参考サイト

『ギリシャ文字・キリル文字・ラテン文字』

サークル“ヒュアリニオス”として頒布した『ギリシャ文字キリル文字ラテン文字』(初出: コミックマーケット90)を公開します。文字の対応を見ながら、ギリシャ文字からキリル文字が作られた過程をラテン文字を絡めて説明している感じの漫画です。

サポートページ


ダウンロード

PDFファイルのダウンロードはこちらから: greek_latin_cyrillic.pdf (3.91MB)

こちらもどうぞ

ロシア語で使われているキリル文字の歴史の話です。

本文

f:id:nixeneko:20170105222233p:plain

続きを読む

LaTeX (TikZ)でキーボード配列表を作成

XeLaTeXでTikZを使ってキーボード配列表を作っていた。101キーボード向け。
多言語用のキーボード配列を作るときに便利…なはず。とても泥臭いので誰か改良してください。

ちなみにXeTeXを使ったのはモンゴル文字への対応がLuaLaTeXは良くないためで、試してないがfontspecが使えればLuaLaTeXなどでも普通に使えるかもしれない。

ソース

\documentclass{standalone}
\usepackage{fontspec}
\usepackage{lmodern}
\setmainfont{Times New Roman}

\usepackage{readarray}

\usepackage{calc}
\usepackage{xparse}
\usepackage{listings}
\usepackage{tikz}
\usetikzlibrary{positioning,shapes,fit}

\tikzstyle{abstract}=[rectangle, draw=black, rounded corners, fill=white,text centered, text=black, text width=8mm]
\tikzstyle{gkey}=[rectangle, draw=black, rounded corners, fill=gray!20,text centered, text=black, text width=8mm]

\newcommand{\mykey}[2]{%
\begin{tikzpicture} \node (Item) [abstract, rectangle split, rectangle split, rectangle split parts=2]%
{#1 \nodepart{second}#2};%
\end{tikzpicture}}


\tikzset{
    pics/vhsplit/.style n args = {4}{
        code = {
        \node[anchor=west] (A) at (-3mm,-0.5mm) {#1};
        \node[anchor=west] (B) at (-3mm,-6.5mm) {#2};
        \node[anchor=east] (C) at (9mm,-0.5mm) {#3};
        \node[anchor=east] (D) at (9mm,-6.5mm) {#4};
        \draw[rounded corners=1mm] (-3mm,-9.5mm) rectangle (9mm,2.5mm); 
        }
    }
}
\tikzset{
    pics/specialkey/.style n args = {2}{
        code = {
        \draw[rounded corners=1mm,fill=gray!20] (-3mm,-9.5mm) rectangle (#2-1mm,2.5mm);  
        \node[inner sep=1mm,anchor=south west,text width=#2,align=center] (A) at (-3mm,-9.5mm) {#1};  
        }
    }
}


\makeatletter

\newlength{\my@keylength}
\setlength{\my@keylength}{13mm}

% {name}{label}{xpos}{ypos}{width}
\def\my@specialkey#1#2#3#4#5{%
  \path pic (#1) at (#3\my@keylength,#4\my@keylength) {specialkey={#2}{#5\my@keylength}};
}
% {name}{index}{north-west}{south-west}{north-east}{south-east}{vertical-index}{left-offset}
\def\my@keypath#1#2#3#4#5#6#7#8{%
  \path pic (#1) at ([xshift=#8\my@keylength]#2\my@keylength,#7\my@keylength) {vhsplit={#3}{#4}{#5}{#6}};
  \typeout{#1/#2/}
}
% \keyassign{1st}{2nd}{3rd}{4th line}
\def\keyassign#1#2#3#4{%
  \begin{tikzpicture}%
    \newcount\my@keycount %
    % 1st line
    \my@keycount=0
    \@for\lp@elem:=#1\do{%
      \expandafter\def\expandafter\my@cnum\expandafter{\the\my@keycount} %
      \expandafter\def\expandafter\my@keyname\expandafter{\expandafter{\expandafter a\my@cnum}} %
      \expandafter\def\expandafter\my@keyindex\expandafter{\expandafter{\my@cnum}} %
      \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\my@keypath\expandafter\expandafter\expandafter\my@keyname\expandafter\my@keyindex\lp@elem{0}{0} %
      \advance\my@keycount by 1 %
    } %
    \my@specialkey{BS}{Back\\Space}{13}{0}{1.27}
    % 2nd line
    \my@specialkey{Tab}{Tab}{0}{-1}{1.27}
    \my@keycount=0
    \@for\lp@elem:=#2\do{%
      \expandafter\def\expandafter\my@cnum\expandafter{\the\my@keycount} %
      \expandafter\def\expandafter\my@keyname\expandafter{\expandafter{\expandafter b\my@cnum}} %
      \expandafter\def\expandafter\my@keyindex\expandafter{\expandafter{\my@cnum}} %
      \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\my@keypath\expandafter\expandafter\expandafter\my@keyname\expandafter\my@keyindex\lp@elem{-1}{1.5} %
      \advance\my@keycount by 1 %
    } %
    % 3rd line
    \my@specialkey{CapsLock}{Caps Lock}{0}{-2}{1.57}
    \my@keycount=0
    \@for\lp@elem:=#3\do{%
      \expandafter\def\expandafter\my@cnum\expandafter{\the\my@keycount} %
      \expandafter\def\expandafter\my@keyname\expandafter{\expandafter{\expandafter c\my@cnum}} %
      \expandafter\def\expandafter\my@keyindex\expandafter{\expandafter{\my@cnum}} %
      \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\my@keypath\expandafter\expandafter\expandafter\my@keyname\expandafter\my@keyindex\lp@elem{-2}{1.8} %
      \advance\my@keycount by 1 %
    } %
    \my@specialkey{Enter}{Enter}{12.80}{-2}{1.47}
    % 4th line
    \my@specialkey{Shift}{Shift}{0}{-3}{2.07}
    \my@keycount=0
    \@for\lp@elem:=#4\do{%
      \expandafter\def\expandafter\my@cnum\expandafter{\the\my@keycount} %
      \expandafter\def\expandafter\my@keyname\expandafter{\expandafter{\expandafter d\my@cnum}} %
      \expandafter\def\expandafter\my@keyindex\expandafter{\expandafter{\my@cnum}} %
      \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\expandafter\my@keypath\expandafter\expandafter\expandafter\my@keyname\expandafter\my@keyindex\lp@elem{-3}{2.3} %
      \advance\my@keycount by 1 %
    } %
    \my@specialkey{Shift}{Shift}{12.3}{-3}{1.97}
  \end{tikzpicture}%
}
\makeatother


\begin{document}

\keyassign{{}{}{\char"07E}{\char"060},{}{}!1,{}{}@2,{}{}\#3,{}{}\$4,{}{}\%5,{}{}{\char"05E}6,{}{}\&7,{}{}*8,{}{}(9,{}{})0,{}{}\_-,{}{}+=}%
{{}{}{}Q,{}{}{}W,{}{}{}E,{}{}{}R,{}{}{}T,{}{}{}Y,{}{}{}U,{}{}{}I,{}{}{}O,{}{}{}P,{}{}\{[,{}{}\}],{}{}|{\char"05C}}%
{{}{}{}A,{}{}{}S,{}{}{}D,{}{}{}F,{}{}{}G,{}{}{}H,{}{}{}J,{}{}{}K,{}{}{}L,{}{}{:}{;},{}{}{\char"022}{\char"027}}%
{{}{}{}Z,{}{}{}X,{}{}{}C,{}{}{}V,{}{}{}B,{}{}{}N,{}{}{}M,{}{}{<}{\char"02C},{}{}{>}.,{}{}?/}


\end{document}

これをXeLaTeXでコンパイルすると次の様に描画される。
f:id:nixeneko:20170106192541p:plain

雑な解説

簡単に解説しておくと、\keyassignマクロは4つの引数、すなわち順に(上から)第1行目(キー13個)、第2行目(キー13個)、第3行目(キー11個)、第4行目(キー10個)のキー配列を引数としてとり、キー配列を描画する。
キー配列はキーをカンマ“,”で区切ったものである。
キーは文字4つ(何も文字がないところには{}を置く)からなり、左上、左下、右上、右下の順である。複数文字を一つの場所に入れる場合には{}で囲む。なお(La)TeXの特殊記号となっているものはエスケープしたりしないといけない。


\begin{document}~\end{document}の中の\keyassignマクロの部分を変えると他のキーボード配列を作成することができる。例えばロシア語キーボード配列だと、

\keyassign{{\char"07E}{\`{}}{}Ё,!1{}{},@2{\char"022}{},\#3№{},\$4;{},\%5{}{},{\char"05E}6:{},\&7?{},*8{}{},(9{}{},)0{}{},\_-{}{},+={}{}}%
{Q{}{}Й,W{}{}Ц,E{}{}У,R{}{}К,T{}{}Е,Y{}{}Н,U{}{}Г,I{}{}Ш,O{}{}Щ,P{}{}З,\{[{}Х,\}]{}Ъ,|{\char"05C}/{}}%
{A{}{}Ф,S{}{}Ы,D{}{}В,F{}{}А,G{}{}П,H{}{}Р,J{}{}О,K{}{}Л,L{}{}Д,{:}{;}{}Ж,{\char"022}{\char"027}{}Э}%
{Z{}{}Я,X{}{}Ч,C{}{}С,V{}{}М,B{}{}И,N{}{}Т,M{}{}Ь,{<}{\char"02C}{}Б,{>}.{}Ю,?/.{\char"02C}}

のようにして、次のような画像が得られる。
f:id:nixeneko:20170106202632p:plain



なぜこんなものを作ったかというと、モンゴル文字キーボードのキー配列表を作りたかったからで、フォントの変更や文字の回転などを組み合わせて次のようになった。なかなかいい感じだと思う。
f:id:nixeneko:20170106200748p:plain


というか、作ってから時間たってるので、マクロ内部で何やってるのか覚えていない。何やら\expandafterとTikZに苦しんでいたのは覚えているが……。

無理な時に押すボタンをつくった

最近いろいろと無理なので無理な時に押すためのボタンをつくった。Arduino Unoを使って押すとむーりぃーって言うもの。

へぇボタンじゃねーか。

ハードウェア

Arduino Uno (R3)を使った。

全体図こんな感じ。
f:id:nixeneko:20161223184726p:plain

電源

Arduinoへの電源供給様に電池6Vを3端子レギュレータで5Vに降圧してArduinoのUSB端子につなげている。
普通にUSBで電源につなぐので問題ない。

ボタン

ボタンは100円ショップで売っている押すとオン・オフが切り替わるプッシュライトのスイッチを改造した。

上のサイトを参考に、スイッチ内部でカチカチと引っかかる部品を取り除き、押している間だけスイッチが切り替わるようにする。また、押している間通電するようにしたかったので元の配線から配線を付け替えている。
さらに、ライト部分も生かしたかったのでそこにもリード線をつけて外に引っ張りだした。

ライトを開けるとこんな感じ。
f:id:nixeneko:20161223185855p:plain

配線

こんな感じの回路図になると思う。入力ピンに繋がれる部分は、ボタンが押されてないときはGNDに、押されてる間は+5Vになる。
f:id:nixeneko:20161223182431p:plain

実装時には、あまり良くないけどプルダウン抵抗は省略した(要するにプッシュライトのLED用抵抗がプルダウン抵抗の役目を果たすことになる)が、一応問題なく動いているようである。
f:id:nixeneko:20161223191024p:plain

ソフトウェア

音声の出力については

を参考にした。

音声を用意する

今回はArduinoフラッシュメモリ(32kB)に書きこむので、データのサイズをメモリに乗る程度に小さくしないといけない。という訳でメモリぎりぎりのサイズになるようサンプリング周波数を調整した。

  • サンプリング周波数13800Hz
  • モノラル
  • 符号なし8bit
  • ヘッダなし

の音声ファイルをAudacityを使って用意した。音量は割れない程度に大きめにしておいた。
f:id:nixeneko:20161220003235p:plain
ファイル名はmuri13k8.rawとした。


次に、Arduinoで読み込めるように、.hファイルに8ビット符号なし整数の配列として書き出す。
適当なpythonスクリプトconv.pyを書いて変換する。

with open('muri13k8.raw', 'rb') as f:
    data = f.read()
print('const unsigned char muri[] PROGMEM = {')
for x in data[:-1]:
    print('{},'.format(x), end="")
print('{}}};'.format(data[-1]))

この出力をmuri.hとして保存し、スケッチのフォルダに突っ込んでおく。

python conv.py > muri.h

muri.hの内容はこんな感じになる。

const unsigned char muri[] PROGMEM = {
126,125,125,125,125,125,125,125,125,125,125,125,(以下略)
};

Arduinoコード

Arduinoのプログラムを書く。Arduino IDEのバージョンは1.6.11。

#include <avr/pgmspace.h>
#include "muri.h"
#define ARRAYSIZE(x)  (sizeof(x) / sizeof((x)[0]))

#define OUT_PIN 3
#define BUTTON 19

int i = 0;       // 音声の再生位置
int lastval = 0; // ボタン状態レジスタ

void setup()
{
  pinMode(BUTTON, INPUT);
  pinMode(OUT_PIN, OUTPUT); 

  // http://qiita.com/kinu/items/6cd5da0415e31834e7da から
  //   これを設定するとすごい速さで動くようになるらしい
  // Non-inverting fast PWM mode on Pin 3.
  // COM2B1:0 ==  10: Non-inverting mode
  // WGM22:0  == 011: Fast PWM mode, 256 cycle (16MHz / 256 == 62.5kHz)
  // CS2:0    == 001: No prescaler (runs at maximum rate, 62.5kHz)
  TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
  TCCR2B = _BV(CS20);

  i = ARRAYSIZE(muri); // 再生位置を最後に指定し自動再生されなくする
}

void loop()
{
    if(i < ARRAYSIZE(muri)){
      OCR2B = pgm_read_byte_near(&muri[i]); // 音声データ書き込み
      i++;
    }
    lastval = (lastval << 1) | digitalRead(BUTTON); // チャタリング回避
    if(lastval == 0x7FFF)            // ボタンが押されたら
      i=0;                           // 再生位置を0に

    delayMicroseconds(72);   // 1000000microsec / 13800Hz
}
  • setup()関数内でマイコンが早い周波数で動くように設定している。
  • 単純にloop()関数内で音声の周期(72μs)ごとに音データを書き込んでるだけで、ボタンが押されると音声データの配列のインデックスを0に更新することで最初から再生されるようにし、ボタンを連打できるようにしている。
  • チャタリング回避を行っている。具体的には、digitalReadの値がボタンが押されていないときに0、押されているときに1となるのを利用して、0のあと15回1が続いた場合、信号が安定したとみなして音の再生を行う。ボタンの数値のログを保管するのにはシフトレジスタを使い、毎回レジスタを1ビット左シフトしてLSBにdigitalReadの値を入れていく。シフトレジスタ(lastval)の値が0b0111 1111 1111 1111となったときにボタンが押されたときの操作を実行する(再生位置を0に更新する)。これによりチャタリングが抑制されボタンを離した時に音が再生されることがほぼなくなった。

最後に

むーりぃー
f:id:nixeneko:20161228020555p:plain

初めてのTrueType命令: Windowsでは見えないフォントをつくる

TrueType命令を利用して何も表示されないフォントを作った。
f:id:nixeneko:20161216002924p:plain

フォント概要

作成したフォントは、アウトライン情報は保持しているのだが、TrueType命令によってすべての点をグリフの原点へ移動し、描画されるビットマップが存在しない状態にすることで何も表示されなくしている。

Windowsでは何も表示されなくなる一方で、Macではヒント命令を実行しないので普通に文字が表示される。そのため、フォントレンダラがレンダリング時にTrueTypeヒント命令を実行するどうかを確認するために用いることができる。

ダウンロード

作り方・技術解説

まえがき

最近TrueType命令を触っている。
TrueType命令というのは、OpenTypeフォント規格でアウトラインの形式としてTrueTypeを選択した場合に使用できるヒンティング用プログラム言語である。もともとTrueTypeフォント規格に付随していた。
ヒンティングでは主に制御点を動かすことでレンダリングされるビットマップを調整するのだが、この命令セットが妙に高機能なので色々できそうだ、という訳である。

このTrueType命令だが、最近のmacOS (OS X)では単に無視されるらしい。ヒンティング自体が低解像度の画面で可読性の高い表示を行うためのものなので、高密度なretinaディスプレイの下ではお役御免といったところだろうか。

という訳で、それを使って何か作ってみよう、というのがこの記事の目的である。

なお、記事タイトルの「初めてのTrueType命令」というのは私が触るのが初めてだったからであり、初めての人がこの記事を読んでTrueType命令を書けるようになるかは定かでない。

方針

さて、見えないフォントを作ることにする。つまり、アウトライン情報は保持しているのだが、TrueType命令によってすべてを指定した点(グリフの原点)に移動してしまうことで描画されるビットマップが存在しない状態にしよう、というものである。

TrueType命令を実行しない場合にはグリフが表示されるため、レンダリングしている環境でTrueType命令が実行されているかを確認することに使える。前述したようにTrueType命令は最近のMacでは無視されるので、Windowsでは見えないフォントということになる。

Fontforgeのインストール

TrueType命令のデバッグにはFontforgeが使える。最近のWindowsインストーラでインストールされるものにはTrueTypeデバッガを有効にしてコンパイルしてあるもののようなのでインストールすればそのまま使える。今回はWindows版の2016-10-04 Release Installer (.exe)でインストールし使用した。

コンパイル方法については次のページに書いてあるのでもし必要があれば参照されたい。


また、必須ではないのだが、Fontforgeはファイルメニューの環境設定から「TTF」タブの「大変更時に命令を消去」をオフにしておいた方がいいかもしれない。というのは、誤って制御点を追加してしまい編集中の命令が全消去された経験があるからである。
f:id:nixeneko:20161215202944p:plain

使うフォントの用意

さて、制御点を動かすにはTrueType形式のアウトラインが必要である。
今回はM+フォントをダウンロードしてきて弄ることにする。mplus-1p-regular.ttfをFontforgeで開く。

Fontforgeのヒント命令編集機能

メインウィンドウの「ヒント」メニューからヒント命令の編集ができる。
f:id:nixeneko:20161215203613p:plain


また、アウトラインウィンドウの「ヒント」メニューで個別グリフのヒント命令を細かく編集したりデバッグを行うことができる。
f:id:nixeneko:20161215203829p:plain

前準備

さて、まずはヒント命令を削除する。
メインウィンドウの「ヒント」→「Remove Instr Tables」でTrueType命令を全削除できる。

また、M+フォントはなぜかEMサイズが1000なので、2の冪数である1024にしておく。「エレメント」→「フォント情報」からウィンドウを開き、「一般情報」タブで「EMの大きさ」を1024にしてOKを押す。

TrueType命令を書いていく

制御点の移動命令

さて、実際にTrueType命令を書いていこうと思う。

今回は、グリフの全ての点を原点(0.0, 0.0)に移動することがゴールである。
そのためには制御点を移動する必要がある。制御点を移動する命令は豊富にあるが、今回は座標を指定してそこに移動する命令SCFS[]を使うことにする。
仕様によると、命令は次の様になっている。

SCFS[] Sets Coordinate From the Stack using projection vector and freedom vector

Pops c:coordinate value (F26Dot6)
p:point number (uint32)
Pushes なし
Uses zp2, freedom vector, projection vector

座標c, 制御点番号pを受け取り、pで表される制御点を座標cに移動するというものである。制御点はfreedom vectorに沿って移動され、projection vectorに沿った座標がcとなる場所に移される、らしい。

スタック

ところで、TrueType命令を実行するものはスタックマシンである。データはスタックに積み上げられ、命令の引数というのもスタック上に積まれていく。上の表のPopsというのがスタックから取得するデータを表しており、取得した(popされた)データはスタックから取り除かれる。

上の表ではcとpがこの順番でpopされる。すなわち、この命令を実行する前に、スタックトップにはcが、その一つ下にpがある状態になっている必要がある。

                     ↓スタックトップ
 … | 点番号p | 座標c |

スタックにデータを積む(pushする)のは主にPUSHB命令とPUSHW命令を使う。それぞれ、命令に後続する1~8個のByte(8ビット符号なし整数)もしくはWord(16ビット符号あり整数)の値をスタックに積む。積む数値の個数は指定する必要がある(正確には個数によって別命令になっている)。スタックに積む際には数値は32ビットに拡張され、PUSHBでは符号なしの拡張、PUSHWでは符号拡張される。


次の例はFontforgeのTrueType命令編集機能で認識されるように書いたもので、TrueType命令のマニュアルに書かれている表記とも少し異なっている。

PUSHB_3
 1
 2
 3

とすると

              →スタックトップ
 … | 1 | 2 | 3 |

のようにスタックに積まれ、

PUSHW_2
 2456
 -100

とすると

              →スタックトップ
 … | 2456 | -100 |

のように積まれていく。PUSHB_3、PUSHW_2のように命令の右端についている数字はpushする数値の個数を表している。

制御点移動のコード(1)

さて、SCFSを実行するところに戻る。点番号1の制御点を座標0.0に移動するコードは次のようになる。

PUSHB_1
 1
PUSHW_1
 0x0000
SCFS

試しに実行してみよう。

グリフのアウトラインウィンドウの「ヒント」→「ヒント命令を編集」でヒント編集用のウィンドウを開き、「編集」ボタンを押す(ボタンが何も表示されていない場合、ウィンドウサイズを縦に拡大すると現れる)。上のコードをペーストしOKを押す。
次に「ヒント」→「デバッグ」を開く。「グリッド合わせのパラメータ」ダイアログが開くので適当にOKを押す。
f:id:nixeneko:20161215221739p:plain
上図のようにTrueType命令のステップ実行用のインタフェースが現れ、またTrueTypeのパラメータやスタックを表示するウィンドウが開かれる。

ステップ実行のボタンを押していくと命令をひとつづつ実行することができる。実行結果が次である。
f:id:nixeneko:20161215221912p:plain
点番号1の制御点がX座標0.0に移動していることがわかる。


要するに制御点は設定した一つの方向にしか移動できないということである。そのため、x座標軸、y座標軸両方に対して座標を指定して移動するためには、方向を変化させて2度別々に処理をする必要がある。

移動方向切り替え

移動方向を切り替えるコマンドがSVTCAである。

  • STVCA[0]でY軸方向
  • STVCA[1]でX軸方向

に移動ができるようになる。具体的には距離を測る方向(projection vector)と移動する方向(freedom vector)を座標軸に指定するということらしい。

制御点移動のコード(2): 2次元移動

さて、Y軸方向にも移動させてみよう。制御点1を原点(0.0, 0.0)に移動する。コードを次のように書き換える。

STVCA[1]
PUSHB_1
 1
PUSHW_1
 0x0000
SCFS
STVCA[0]
PUSHB_1
 1
PUSHW_1
 0x0000
SCFS

先ほどのコードの前にSTVCAで移動方向を切り替えるものがついて、先にX軸方向、次にY軸方向、と2回繰り返している訳である。

同様に実行してみる。
f:id:nixeneko:20161215223116p:plain
分かりづらいが原点に移動している。


さて、これを制御点の数だけ繰り返せば全ての点が原点に移動し何も表示されなくなるという訳である。
単純に上のコードを、点番号を変化させて制御点の数だけ並べてもいいが、さすがにアホっぽいのと容量が無駄なのでもっと効率化したい。

関数化

ここで関数が登場する。

関数は'fpgm'テーブルにおいて、FDEF命令で定義できる。FDEFはスタックから整数を一つpopして、その番号の関数の定義を行う。関数の終わりはENDFで示される。
例えば関数0の定義だと次のように書く。

PUSHB_1
0
FDEF
 <ここに関数の内容がくる>
ENDF

さて、先ほどの、制御点を指定して原点に移動させる、という機能を関数としてまとめてみたい。
移動する制御点を指定するために制御点番号を入力とする。呼び出されるときにスタックトップが制御点番号であることを期待するということであり、関数から抜ける際には引数として入力された数値はpopされてスタックから取り除かれた状態にする。

つまり、スタックが、コール時には

              ↓スタックトップ
 … | 点番号p |

となり、関数を抜けるときは

    ↓スタックトップ
 … | 

となるように設計する。

関数番号は0として定義してみよう。

PUSHB_1
0
FDEF
STVCA[1]
DUP
PUSHW_1
 0x0000
SCFS
STVCA[0]
PUSHW_1
 0x0000
SCFS
ENDF

ここで、DUPはスタックトップを複製する命令である。以下のようにスタックの中身を考えてみると動作がわかると思う。

PUSHB_1
0
FDEF      /* ...|p| */
STVCA[1]  /* ...|p| */
DUP       /* ...|p|p| */
PUSHW_1   /* ...|p|p|0.0| */
 0x0000
SCFS      /* ...|p| */
STVCA[0]  /* ...|p| */
PUSHW_1   /* ...|p|0.0| */
 0x0000
SCFS      /* ...| */
ENDF

実行前後のスタックは先ほど設計したのと合っている。

今定義した関数0を実行するのはCALL関数を使う。コードは次のようになる。

PUSHB_2
 1
 0
CALL

CALLは関数番号をpopし対応する関数を実行する。このコードでは0は関数番号、1は関数の引数とする制御点番号である。

さて、実際に関数が動くか実行してみよう。前述したとおり関数定義は'fpgm'テーブルに書かないといけない。これはFontforgeのメインウィンドウから「ヒント」→「'fpgm'テーブルを編集...」から設定することができる。ここで先ほどの関数定義を書き込み、OKを押す。
また、グリフのヒント命令を関数を実行する命令に書き換える。
そして実行してみる。
f:id:nixeneko:20161216020353p:plain
先ほどに実行した命令と同じ動作をしているので成功である。

制御点の数だけ繰り返す

さて、これを制御点の数だけ繰り返さないといけない。
単純に指定回数だけ関数の実行を繰り返すならLOOPCALLという命令が使えるのだが、今回はすべての制御点に対して操作を行うため何回目の実行かということを覚えておかないといけないため、条件分岐IFと相対ジャンプJMPRを使って実装することにした*1

疑似コードを次に示す。nmaxはグリフの最大の制御点番号を指定する。

  n_cur = nmax;
loop_label:
  if (ncur >= 0){
    func0(ncur);
    ncur--;
    goto loop_label;
  }

さて、実際にコードを書いてみる。
Aの制御点は最大の制御点番号は11なので、nmaxとして11を指定してやってみる。

PUSHB_1
 11
DUP
PUSHB_1
 0
GTEQ
IF
DUP
PUSHB_1
 0
CALL
PUSHB_1
 1
SUB
PUSHW_1
 -15
JMPR
EIF
POP

GTEQはスタックトップの2項目を比較しその結果をpushする命令である。

GTEQ            →スタックトップ
before …| a | b | 
after  …| a>=b |

a>=bのときは1、そうでないときは0がpushされる。

IFはスタックトップから整数を一つpopし、それが0でないときは次の命令を実行していき、0ならばEIFにジャンプするというものである。

JMPRは整数を一つpopし、それによって指定される相対位置へとジャンプする。マイナスだと遡る方向にジャンプする。

さて、同様にスタックの中身を考えてみる。

         /* …|              */
PUSHB_1  /* …|ncur<=nmax|   */
 11
         /* …|ncur|         */ /* JMPRでここに戻ってくる */
DUP      /* …|ncur|ncur|    */
PUSHB_1  /* …|ncur|ncur|0|  */
 0
GTEQ     /* …|ncur|ncur>=0| */ 
IF       /* …|ncur|         */ /* if ncur >= 0 then */
DUP      /* …|ncur|ncur|    */
PUSHB_1  /* …|ncur|ncur|0|  */
 0
CALL     /* …|ncur|         */ /* func0(ncur) */
PUSHB_1  /* …|ncur|1|       */
 1
SUB      /* …|ncur-1|       */ /* ncur--; */
PUSHW_1  /* …|ncur-1|-15|   */
 -15
JMPR     /* …|ncur-1|       */ /* goto loop_label */
EIF
POP      /* …|              */

良さそう。さてこのプログラムをグリフのTrueType命令に書き込んで実行してみる。
実行しながらスタックの中身を示すウィンドウを見ていくと、スタックの一番下にある値がひとつづつ減少していき最終的に-1となってループを抜けていることがわかる。
f:id:nixeneko:20161216222646p:plain
描画されるビットマップが何もないという状態になっている。成功である。

ループを関数化

さて、これも関数にする。
スタックがコールする前後で、

              →スタックトップ
before:  … | nmax |
after:   … | 

となるようにしよう。nmaxにはグリフの最大の制御点番号を指定する。
関数番号は1とする。

PUSHB_1
 1
FDEF
DUP
PUSHB_1
 0
GTEQ
IF
DUP
PUSHB_1
 0
CALL
PUSHB_1
 1
SUB
PUSHW_1
 -15
JMPR
EIF
POP
ENDF

これは単純に前のコードから、

PUSHB_1
 11

を除いて、

PUSHB_1
 1
FDEF
 ~
ENDF

で囲んだだけである。
これを'fpgm'テーブルに追記する。

さて、関数1をつくったのでグリフから呼ぶ必要がある。
Aの最大の制御点番号は11なので、11を引数として関数1を呼び出す。コードは次のようになる。

PUSHB_2
 11
 1
CALL

実行してみると次のようになる。
f:id:nixeneko:20161216222403p:plain
関数化前と同様に何も塗られるビットマップがないので成功である。


さて、ここではAにだけ消えるようなヒント命令を実装したが、これをすべてのグリフに対しても適用したい。
そこで、ひとまずフォントを出力する。

フォントを出力

フォント出力前に「ヒント」→「'maxp'テーブルを編集...」からmaxpテーブルの数値を編集する。
f:id:nixeneko:20161216000103p:plain

  • ゾーンは2固定。
  • ストレージは使ってないので0。
  • FDEFはさっき関数0, 1の2つを定義したので2としておこう。
  • トワイライトポイントは使ってないので個数0。
  • スタックの最大深さは……使ってたの5個位だった気がするけど多めに指定しておくか。
  • IDEFは使ってないので0。

とか指定する。使ってるインデックスがそれぞれの指定した値を超えるとうまく動かないことになるのだと思う。

フォント情報(エレメント→フォント情報)を適宜修正し、TrueTypeフォントを出力する(ファイル→フォントを出力)。
f:id:nixeneko:20161216000955p:plain

TTXでXMLに変換

さて、これをTTXを使ってXMLファイルに展開する。
TTXのインストールについては次のサイトを参考にされたい。

今回使用したTTXのバージョンは2.5である。
コマンドプロンプトなどで先ほど出力したフォントAdisappear.ttfがあるフォルダへと移動し、

ttx Adisappear.ttf

と指定して展開し、Adisppear.ttxを得る。

すべてのグリフに適用

Adisappear.ttxをテキストエディタで描き、Aのグリフを探し、紐づいているTrueType命令を調べる。<glyf>~</glyf>の中で<TTGlyph name="A">~</TTGlyph>がある。

    <TTGlyph name="A" xMin="36" yMin="0" xMax="674" yMax="748">
      <contour>
        <pt x="36" y="0" on="1"/>
        <pt x="309" y="748" on="1"/>
        <pt x="401" y="748" on="1"/>
        <pt x="674" y="0" on="1"/>
        <pt x="587" y="0" on="1"/>
        <pt x="511" y="218" on="1"/>
        <pt x="196" y="218" on="1"/>
        <pt x="121" y="0" on="1"/>
      </contour>
      <contour>
        <pt x="218" y="284" on="1"/>
        <pt x="488" y="284" on="1"/>
        <pt x="354" y="674" on="1"/>
        <pt x="352" y="674" on="1"/>
      </contour>
      <instructions><assembly>
          PUSH[ ]	/* 2 values pushed */
          11 1
          CALL[ ]	/* CallFunction */
        </assembly></instructions>
    </TTGlyph>

<instructions><assembly>~</assembly></instructions>に挟まれているのがAのグリフに対応する命令であり、その中の11がAの最大の制御点番号である。これはほかのグリフに適用する際、グリフに含まれる制御点<pt>の個数を数えて、(制御点の個数 - 1)個を指定すればよい。


これをすべてのグリフに対しても適用するようなpythonスクリプトを作成した。

#!/usr/bin/env python3
# conding: utf-8

import xml.etree.ElementTree as ET

INFILE = "Adisappear.ttx"
OUTFILE = "Adisappear-out.ttx"
xmltree = ET.parse(INFILE)
xmlroot = xmltree.getroot()

for glyph in xmlroot.find('glyf').findall('TTGlyph'):
    cnt = 0
    for contour in glyph.findall('contour'):
        cnt += len(contour.findall('pt'))
    if cnt > 0:
        prog ="""
          PUSH[ ]    /* 2 values pushed */
          {} 1
          CALL[ ]    /* CallFunction */
          """.format(cnt-1)
        glyph.find('instructions').find('assembly').text = prog

with open(OUTFILE, 'w') as w:
    w.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    xmlstr = ET.tostring(xmlroot, method='xml', encoding="unicode")
    #xmltree.write(OUTFILE)
    w.write(xmlstr)

やってることは各<TTGlyph>に対して<pt>の個数を求めて、もし個数が1以上なら(個数 - 1)を埋め込んだTrueType命令を<instructions><assembly>~</assembly></instructions>間に挿入するというものである。

これをapplyall.pyという名前でAdisappear.ttxと同じフォルダに入れ実行する。pythonは3系でないとうまく動かない。

python3 applyall.py

実行するとAdisappear-out.ttxが出力されるので、これをTTXを使って.ttfファイルへと変換する。

ttx Adisappear-out.ttx

結果

最終的に得られるフォント(Adisppear-out.ttf)をWindows 10でプレビューしたのが次である。
f:id:nixeneko:20161216002924p:plain
やったー! 何も見えないよ……。

他のOSで見てみると……

Mac OS X 10.5 Yosemite

f:id:nixeneko:20161217153953p:plain
噂通りヒンティングはされていないようである。
まあRetinaだし、ヒントでアウトラインを歪めなくてもオリジナルのアウトラインを綺麗に表示できるので……。

Ubuntu Desktop 16.04

f:id:nixeneko:20161216224852p:plain
ヒント効いてない。

どうやら、標準ではヒントが効かなくなっているらしい?

このページによると、gnome-tweak-toolを使ってヒンティングを調整できるようである。
f:id:nixeneko:20161216225417p:plain
「フォント」タブにて、ヒンティングがデフォルトではSlightになっているところをFullに変更する。

そしてフォントビューアを起動しなおすと……
f:id:nixeneko:20161216225543p:plain
やった!

参考サイト

*1:とはいえ、関数の中にカウンタを用意すればLOOPCALLでも問題なくできる気がする。

UbuntuでFontforgeをコンパイル

Ubuntuならapt一発でFontforgeがインストールできるものの、パッケージマネージャで入れられるものにはTrueType命令のデバッガが有効になっていないため、TrueTypeデバッガを使いたければ自前でコンパイルする必要があります。(Appleのヒンティングの特許が2010年に切れたので現在では標準のリポジトリに入ってるものでTrueTypeデバッガが有効になっています。)

この記事はUbuntuにおける、TrueTypeデバッガを有効にしたFontforgeコンパイルの手順を示します。
基本的には書いてあるコマンドを端から実行していけばインストールできるはずです。

環境

OS: Ubuntu 16.04 LTS 日本語 Remix

コンパイル・インストール手順

パッケージのインストール

次のパッケージをインストールします。

  • git
  • m4
  • libtool
  • autoconf
  • automake
  • libglib2.0-dev
  • libgio2.0-cil-dev
  • libfreetype6-dev
  • libxml2-dev
  • libspiro-dev
  • libtiff5-dev
  • libuninameslist-dev
  • libgif-dev
  • libreadline-dev
  • libzmq-dev
  • python-dev
  • potrace
  • libcairo2-dev
  • libpango1.0-dev

端末上で実行するコマンドだと

sudo apt install git m4 libtool autoconf automake libglib2.0-dev \
libgio2.0-cil-dev libfreetype6-dev libxml2-dev libspiro-dev libtiff5-dev \
libuninameslist-dev libgif-dev libreadline-dev libzmq-dev python-dev \
potrace libcairo2-dev libpango1.0-dev

とか。

Fontforgeソースコードの準備

cd ~
git clone git://github.com/fontforge/fontforge.git
cd fontforge
git checkout tags/20161005

git checkoutのところで、リリースを指定します。
Releases · fontforge/fontforge · GitHub
ここなんかを参照しながら好きなリリースを選ぶといいと思います。ここでは最新のリリースである20161005にしています。

Freetypeソースコードの準備

http://download.savannah.gnu.org/releases/freetype/
ここからFreetypeのソースをダウンロードしてきて展開します。

今回はインストールされているlibfreetype6パッケージとバージョンを合わせて2.6.1としました。

wget http://download.savannah.gnu.org/releases/freetype/freetype-2.6.1.tar.gz
tar zxvf freetype-2.6.1.tar.gz 

Fontforgeコンパイル

./configureで--enable-freetype-debuggerオプションをつけ、Freetypeソースのディレクトリを指定することで、TryeType命令のデバッガを有効にしています。

./bootstrap
./configure --enable-freetype-debugger=freetype-2.6.1/
make
sudo make install

Fontforgeがうまく起動できない場合は一度Ubuntuを再起動します。