にせねこメモ

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

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を再起動します。

CygwinでFontforgeをコンパイル

TrueType Instruction用のデバッガがFontForgeには載ってるのだが、有効にするためには自前でコンパイルしないといけないらしい。

という訳で、Cygwinからやってみる。

環境

Fontforgeコンパイル

fontforge/CONTRIBUTING.md at master · fontforge/fontforge · GitHub
ここに書いてある通りにしてコンパイル等をする。

準備

まずはSetup.exe(Setup-x86_64.exe)を使って必要なパッケージをインストール。手元の環境では次のパッケージが追加で必要になった。今までにいろいろ入れてるので更に必要であると思う。

  • m4
  • libtool
  • autoconf
  • automake
  • libspiro-devel
  • libtiff-devel
  • libuninameslist-devel
  • giflib
  • libgif-devel
  • libreadline-devel
  • libzmq-devel
  • python-devel
  • gettext-devel
  • libpango1.0-devel
  • libxml2-devel
  • libiconv-devel

ソースをダウンロード:

  • libfreetype6

また、Cygwinを使ってFontforgeを入れてた場合はアンインストールしておく。

コンパイル&インストール。

基本的には一般的な手順だが、./configureの前に./bootstrapを実行し依存性チェックやconfigureファイルの生成をする必要があるようだ。
また、 ./configure には --enabele-freetype-debugger オプションの設定とFreetypeのソースの入っているディレクトリを設定することでTrueTypeデバッガが有効になるらしい。
./checkoutや./configureの様子を見ながら足りないパッケージは適宜インストールする。

$ cd ~
$ mkdir ff
$ cd ff
$ git clone git://github.com/fontforge/fontforge.git
$ cd fontforge/
$ git checkout tags/20161005
$ ./bootstrap
$ cd ..
$ cp /usr/src/freetype2-2.6.5-1.src/freetype-2.6.5.tar.bz2 .
$ tar jxvf freetype-2.6.5.tar.bz2
$ cd ../fontforge
$ ./configure --enable-freetype-debugger=../freetype-2.6.5
$ make
$ make install

libzmq-devel等をインストールしたものの、./configureが言うには、zeromq (libzmq)のHave?がnoのまま。つまり認識されていない?とりあえずoptionalなライブラリなので無くてもなんとかなるようだが。


cygwinからstartxwinなどでxtermを起動してfontforgeを実行すると無事に起動した。めでたしめでたし。

(2016-11-10 一部修正)

キングブレードX10III Neo分解

f:id:nixeneko:20161016180341j:plain
キンブレ買ったので分解した。分解手順を記録しておく。実際には組み立てていくところを逆から追っていった感じ。

分解したのは、キングブレードX10III Neoのスモーク。


まず先端についてる筒を外す。
f:id:nixeneko:20161016180641j:plain
f:id:nixeneko:20161016180958j:plain

この状態では懐中電灯として使える。
f:id:nixeneko:20161016181007j:plain


リングを外す。なかなか外れず苦労したが、足で柄を抑えて下の縁に爪をひっかけて上に引っ張ったら外れた。うまい具合に上方向に安定した力をかけられるなら何でもいいのだろうが。
f:id:nixeneko:20161016181621j:plain

本体側に溝が掘ってあり、リング裏の出っ張りとかみ合ってリングが回転しないようになっている。そのためリングを外す際にはまっすぐ上に引っ張る必要がある。
f:id:nixeneko:20161016181630j:plain
f:id:nixeneko:20161016182046j:plain


電池蓋を外し、電池を外す。
f:id:nixeneko:20161016182543j:plain

電池が入るところの下部にあるネジ2つを外す。
f:id:nixeneko:20161016182831j:plain


横側の溝にマイナスドライバーの先を突っ込んでこじ開ける。ネジで留まっていた下の方はある程度開くのでそこから入れて少しずつ広げていくといいかもしれない。失敗すると傷がつく(ついた)。逆側も同様にして開くと覆いが外れる。
f:id:nixeneko:20161016183445j:plain

こんな風に切り欠きと出っ張りで嚙み合わさっている。
f:id:nixeneko:20161016183603j:plain
f:id:nixeneko:20161016183743j:plain


開けたところ(先頭部分)。
f:id:nixeneko:20161016184431j:plain

光を均一にするためのレンズを取り外す。LED基盤と一緒に溝にはまってるだけなので引き出す。
f:id:nixeneko:20161016184649j:plain

外したレンズ。
f:id:nixeneko:20161016185002j:plain
f:id:nixeneko:20161016185014j:plain


レンズ外す際に基盤も一緒に外れるが、ここでは嵌めなおしてある。
f:id:nixeneko:20161016184027j:plain
f:id:nixeneko:20161016190140j:plain

下のスイッチ側も外す。ここもスイッチ用の基盤が溝にはまっているだけなので引けば外れる。
f:id:nixeneko:20161016190353j:plain
はんだごてを使わない分解はこれにて終了。


LED基盤上部はこんな感じ。RGBWのフルカラーLEDを搭載している。基盤の裏はアルミ板か何かになっているようで、放熱用だろうか。
f:id:nixeneko:20161016190735j:plain

制御基盤? LED基盤と半田で垂直に固定されている。
f:id:nixeneko:20161016191043j:plain

制御基盤逆面。
f:id:nixeneko:20161016191224j:plain

記事内の画像のライセンス

この記事の画像はCC0ライセンスにより提供されます(2017/03/19~)。

CC0
To the extent possible under law, nixeneko has waived all copyright and related or neighboring rights to キンブレ分解写真. This work is published from: 日本.

さくらのサーバに置いたWebフォントをはてなブログから使う

さくらのレンタルサーバ上にWebフォントのファイルを置いて、それを外部のブログやサイトから参照できるようにする話。

経緯

今年の8/31をもってGoogle DriveのWebホスティングサービスが終了したので、Google Drive上に上げていたWebフォントが使えなくなった。
という訳でさくらのレンタルサーバ上に移行しようとしたのだが、フォントファイルをサーバ上に移動させたものの、はてなブログ(このブログ)からはフォントファイルが読み込まれなかった。

問題点

どうやらクロスドメインによるアクセスが問題のよう。
具体的にはHTTPレスポンスヘッダに“Access-Control-Allow-Origin”がないといけないらしい。
Access-Control-Allow-Originは、それによって指定されたドメインからのアクセスなら読み込んでいいよってものらしい。"*"を指定するとどんなドメインからのアクセスでも読み込めるようになる。

解決方法

現在のさくらのレンタルサーバでは.htaccessでHeaderが効くらしいので、

.htaccessファイルを作成し、

Header set Access-Control-Allow-Origin: "*"

などと書き込んでフォルダと同じディレクトリにぶち込むととりあえずはてなブログからも読み込めるようになる。
セキュリティを考えるとちゃんとドメインを指定したり、フォントに限定して設定できるよう工夫した方がよさそう。

フォントの気持ちになる

フォントの気持ちになるですよ。
f:id:nixeneko:20161007231818p:plain

……正確にはフォントというより、フォントレンダリングソフトウェアの気持ちになってフォントを読んでみよう、という感じでごぜーますが。

フォントの準備

…………。
さて。

読むフォントは、容量が小さいとありがたいのですが、ちょっと探しただけでは見つからなかったので、Noto Sans RegularをFont Squirrel Webfont Generator使ってASCIIの小文字アルファベットだけにしたものを用意しました。だいたい9KB位。

用意したフォントはここからダウンロードできます: font.ttf


さて、適当なバイナリエディタでフォントを開き、16進ダンプを印刷します。
f:id:nixeneko:20161007234742j:plain
一応、印刷したPDFのリンクを張っておきます: font.pdf


……えーと、別に印刷する必要なかったですね。バイナリエディタで見ていきます。今回はStirlingを使いました。


今回はWindows用の情報をたどって、"cat"を出力することにしましょう。UnicodeでU+0063 U+0061 U+0074です。

あと、長くなってしまったので今回表示に関係ないところについてはデフォルトで表示されないようにしています。クリックで展開できるので興味があれば開いてください。

OpenType Font File

OpenType specification - Typography | Microsoft Docs
このOpenTypeの仕様をみながら読んでいきます。

特に、OpenTypeフォントファイルの仕様についての次のページを参考にします。
OpenType font file - Typography | Microsoft Docs
最初にデータ型について書かれています。
特に説明を要するものもなさそうですが、固定小数点数を10進数に直すには、整数として読み出し、2小数部分のビット数で割ると実際の値になるようです。


さて、初めから読んでいきます。OpenTypeフォーマットはバイトオーダがビッグエンディアンなので直感的ですね。
最初に来るのが0x00010000だから(TTC Headerでなく)Offset Tableなので、TTCでない単一のフォントでかつTrueTypeアウトラインのものだとわかります。

Offset Table

Type Name Value 備考
ULONG sfntVersion 0x00010000 TrueType Outline
USHORT numTables 0x0013 19
USHORT searchRange 0x0100 256
USHORT entrySelector 0x0004 4
USHORT rangeShift 0x0030 48

numTablesはテーブルの数で19個あるとわかり、そのあとの3つはnumTablesから計算される数です。
entrySelectorは  2^n \leq \text{numTables} なる最大の整数 nで、他は  \text{searchRange} = 16\cdot2^\text{entrySelector},  \text{rangeShift} = 16\cdot\text{numTables} - \text{searchRange} だそうです。計算してみると上の表のとおりになることが分かります。

Table Record

Type Name Description
ULONG tag テーブルを特定するための4文字の識別子
ULONG checkSum テーブルのチェックサム
ULONG offset テーブルのフォントファイルの先頭からのオフセット
ULONG length テーブルの長さ

直後にくるのがTable Recordで、これがOffset TableのnumTables……つまり19個並んでいるはずです。読みだすと次のようになっていました。
チェックサムの確認は面倒なのでしませんでしたが、offsetとlengthから計算できる値で計算方法は仕様を参照。

tag checkSum offset length
'FFTM' 0x61D6EAF7 0x013C (316) 0x001C (28)
'GDEF' 0x005C0024 0x0158 (344) 0x0028 (40)
'GPOS' 0xA176C2E4 0x0180 (384) 0x00D0 (208)
'GSUB' 0x9966BC6F 0x0250 (592) 0x0076 (118)
'OS/2' 0x79FC780F 0x02C8 (712) 0x0060 (96)
'cmap' 0xD6E8B4E8 0x0328 (808) 0x0182 (386)
'cvt ' 0x0F5312F5 0x04AC (1196) 0x0048 (72)
'fpgm' 0x53B42FA7 0x04F4 (1268) 0x0265 (613)
'gasp' 0x00160023 0x075C (1884) 0x0010 (16)
'glyf' 0xF470EA57 0x076C (1900) 0x1164 (4452)
'head' 0x0D957EF6 0x18D0 (6352) 0x0036 (54)
'hhea' 0x0EF404F9 0x1908 (6408) 0x0024 (36)
'hmtx' 0x9AC30ABF 0x192C (6444) 0x00B4 (180)
'loca' 0x7CD081CC 0x19E0 (6624) 0x005C (92)
'maxp' 0x014700FD 0x1A3C (6716) 0x0020 (32)
'name' 0x8B41D5BC 0x1A5C (6748) 0x06A4 (1700)
'post' 0xE1EBCFD6 0x2100 (8448) 0x0103 (259)
'prep' 0x7730038F 0x2204 (8708) 0x015A (346)
'webf' 0x62B657D9 0x2360 (9056) 0x0006 (6)

必ずしもあるテーブルについのoffset+lengthが次のテーブルのoffsetとなっていないのは、テーブルが4バイト境界にアラインメントされるためで、lengthには実際のテーブルの長さが記録されるのだそうです。実際のテーブルから次の4バイト境界までの隙間は0で埋められます。

'FFTM'はFontForge Time StampでFontForgeが出力するデータ、'webf'は不明ですがおそらくWebfontの略か、それぞれ独自テーブルなので無視します。

さて、OpenType tableのリストとその位置が得られたので、見ていきます。
どういう順番で見てくのがいいんでしょうか。とりあえずheadから行きます。

'head' - Font Header Table

フォントヘッダだそうです。開始オフセットが0x18D0なのでそこから見ていきます。54バイトなので短い。

Type Name Value 備考
USHORT majorVersion 0x0001 1固定
USHORT minorVersion 0x0000 0固定
Fixed fontRevision 0x00010F5C 1.06くらい
ULONG checkSumAdjustment 0xEC47630E
ULONG magicNumber 0x5F0F3CF5 0x5F0F3CF5固定
USHORT flags 0x001F ベースラインがy=0;
Left sidebearing point at x=0;
Instructions may depend on point size;
Force ppem to integer values for all internal scaler math;
Instructions may alter advance width
USHORT unitsPerEm 0x0800 2048
LONGDATETIME created 0x00000000D3FF1335 2016-09-14T14:46:13+00:00
mac時間らしい
LONGDATETIME modified 0x00000000D3FF1335 2016-09-14T14:46:13+00:00
SHORT xMin 0xFF8F -113 バウンディングボックスのx最小値
SHORT yMin 0xFE14 -492 同y最小値
SHORT xMax 0x06D5 1749 同x最大値
SHORT yMax 0x061F 1567 同y最大値
USHORT macStyle 0x0000 なし
USHORT lowestRecPPEM 0x0008 最小可読サイズ。8px
SHORT fontDirectionHint 0x0002 2固定。廃止
SHORT indexToLocFormat 0x0000 short offsets
SHORT glyphDataFormat 0x0000 0固定

'OS/2' - OS/2 and Windows Metrics Table

offsetが0x02C8なのでそこから読みます。
OS/2 - OS/2 and Windows metrics table specification - Typography | Microsoft Docs (Version 5)
実質Windows専用なんですかね。
はじめ2バイトのversionが0x0004であるからこれがVersion 4のOS/2テーブルということが分かります。
OS/2 - OS/2 and Windows metrics table specification (version 4) - Typography | Microsoft Docs (Version 4)

Type Name of Entry Value comment
USHORT version 0x0004 4
SHORT xAvgCharWidth 0x0387 903; 幅が0でないグリフの幅の算術平均
USHORT usWeightClass 0x0190 400 = Normal (Regular)
USHORT usWidthClass 0x0005 5 = Medium (normal)
USHORT fsType 0x0004 4 = 表示・印刷用にフォント埋め込み可(編集不可)
SHORT ySubscriptXSize 0x059A 1434; 下付き文字の大きさの目安
SHORT ySubscriptYSize 0x0533 1331
SHORT ySubscriptXOffset 0x0000 0; 下付き文字の位置の目安
SHORT ySubscriptYOffset 0x011F 287
SHORT ySuperscriptXSize 0x059A 1434; 上付き文字の大きさの目安
SHORT ySuperscriptYSize 0x0533 1331
SHORT ySuperscriptXOffset 0x0000 0; 上付き文字の位置の目安
SHORT ySuperscriptYOffset 0x03D1 977
SHORT yStrikeoutSize 0x0066 102; 打消線の幅
SHORT yStrikeoutPosition 0x0200 512; 打消線の上側の位置
SHORT sFamilyClass 0x0802 2050
BYTE panose[10] 0x020B0502040504020204
ULONG ulUnicodeRange1 0xE00002FF Basic Latin, Latin-1 Supplement, Latin Extended-A, Latin Extended-B, IPA Extensions, Phonetic Extensions, Phonetic Extensions Supplement, Spacing Modifier Letters, Modifier Tone Letters, Combining Diacritical Marks, Combining Diacritical Marks Supplement, Greek and Coptic, Cyrillic, Cyrillic Supplement, Cyrillic Extended-A, Cyrillic Extended-B, Latin Extended Additional, Latin Extended-C, Latin Extended-D, Greek Extended, General Punctuation, Supplemental Punctuation
ULONG ulUnicodeRange2 0x00000000
ULONG ulUnicodeRange3 0x00000000
ULONG ulUnicodeRange4 0x00000000
CHAR achVendID[4] 0x474F4F47 'GOOG'; 大文字IDはMicrosoftに登録しないと使えないらしい
USHORT fsSelection 0x0040 REGULAR
USHORT usFirstCharIndex 0x000D CR…フォント内の最小のUnicode index
USHORT usLastCharIndex 0x25FC '◼'…フォント内の最大のUnicode index
SHORT sTypoAscender 0x088D 2189; typographic ascender; 'hhea'のAscenderとは異なる
SHORT sTypoDescender 0xFDA8 -600; typographic descender; 'hhea'のDescenderとは異なる
SHORT sTypoLineGap 0x0000 0; typographic line gap; 'hhea'のLineGapとは異なる
USHORT usWinAscent 0x088D 2189; ascender metric for Windows
USHORT usWinDescent 0x0258 600; descender metric for Windows
ULONG ulCodePageRange1 0x2000019F サポートするコードページ: Latin 1, Latin 2: Eastern Europe, Cyrillic, Greek, Turkish, Windows Baltic, Vietnamese, Macintosh Character Set (US Roman),
ULONG ulCodePageRange2 0xDFD70000 IBM Greek, MS-DOS Russian, MS-DOS Nordic, MS-DOS Canadian French, MS-DOS Icelandic, MS-DOS Portuguese, IBM Turkish, IBM Cyrillic; primarily Russian, Latin 2, MS-DOS Baltic, Greek; former 437 G, WE/Latin 1
SHORT sxHeight 0x044A 1098; エックスハイト
SHORT sCapHeight 0x0000 0; キャップハイトあるいは大文字Hの高さ
USHORT usDefaultChar 0x0000 0
USHORT usBreakChar 0x0020 ' ' スペース
USHORT usMaxContext 0x0002 2 = ペアカーニングや2文字までを対象したフィーチャしかない

Windows用のソフトでは、sTypoAscender, sTypoDescender, sTypoLineGapを利用して標準のline-spaceingを計算するのが推奨されてるらしいです。

'hhea' - Horizontal Header Table

offsetが0x1908なのでそこから読んでいきます。
hhea - Horizontal header table specification - Typography | Microsoft Docs

Type Name of Entry Value comment
USHORT majorVersion 0x0001 1固定
USHORT minorVersion 0x0000 0固定
FWORD Ascender 0x088D 2189; Typographic ascent
FWORD Descender 0xFDA8 -600; Typographic descent
FWORD LineGap 0x0000 0; Typographic line gap
UFWORD advanceWidthMax 0x077B 1915; Maximum advance width value in 'hmtx'
FWORD minLeftSideBearing 0xFF8F -113; Minimum left sidebearing value in 'hmtx'
FWORD minRightSideBearing 0xFFA8 -88
FWORD xMaxExtent 0x06D5 1749; Max(lsb + (xMax - xMin))
SHORT caretSlopeRise 0x0001 1 for vertical
SHORT caretSlopeRun 0x0000 0 for vertical
SHORT caretOffset 0x0000 0 for non-slanted fonts
SHORT (reserved) 0x0000 0固定
SHORT (reserved) 0x0000 0固定
SHORT (reserved) 0x0000 0固定
SHORT (reserved) 0x0000 0固定
SHORT metricDataFormat 0x0000 0固定
USHORT numberOfHMetrics 0x002D 45; 'hmtx'におけるhMetricエントリの数

ascender, descender, linegapはMac用。とはいえ'OS/2'にあるものと一致してますね。

'maxp' - Maximum Profile

offsetが0x1A3Cなのでそこから読みます。
maxp - Maximum Profile table specification - Typography | Microsoft Docs
最初が0x00010000なのでversion 1.0であることがわかります。それに従って読んでいくと……

Type Name value comment
Fixed Table version number 0x00010000 version 1.0
USHORT numGlyphs 0x002D 45; グリフの数
USHORT maxPoints 0x0029 41; Maximum points in a non-composite glyph.
USHORT maxContours 0x0002 2; Maximum contours in a non-composite glyph.
USHORT maxCompositePoints 0x0000 0; Maximum points in a composite glyph.
USHORT maxCompositeContours 0x0000 0; Maximum contours in a composite glyph.
USHORT maxZones 0x0002 instructions use the twilight zone (Z0); 大体は2
USHORT maxTwilightPoints 0x0001 1; Maximum points used in Z0.
USHORT maxStorage 0x0002 2; Number of Storage Area locations.
USHORT maxFunctionDefs 0x0016 22; Number of FDEFs
USHORT maxInstructionDefs 0x0000 0; Number of IDEFs.
USHORT maxStackElements 0x0100 256; Maximum stack depth.
USHORT maxSizeOfInstructions 0x00D0 208; Maximum byte count for glyph instructions.
USHORT maxComponentElements 0x0000 0; Maximum number of components referenced at “top level” for any composite glyph.
USHORT maxComponentDepth 0x0000 0; Maximum levels of recursion

'cmap' - Character To Glyph Index Mapping Table

offsetが0x0328からなのでそこから読みます。
cmap - Character To Glyph Index Mapping Table - Typography | Microsoft Docs
文字コードを実際のグリフの内部コード(Glyph IDもしくはGID)にマッピングするテーブルです。

cmap Header

Type Name value comment
USHORT version 0x0000 0
USHORT numTables 0x0003 3; エンコーディングテーブルの数

Encoding Record

つぎに来るのがencoding recordの列で、cmapヘッダから3つあることがわかります。レコードの形式は次の様になっています。

Type Name 備考
USHORT platformID
USHORT encodingID
ULONG offset サブテーブルへのオフセット

platformIDやencodingIDは'name'テーブルのものと同じらしい。さらにサブテーブルがあるようですね。

これに従って読んでいくと……

platformID encodingID offset
0x0000 (Unicode) 0x0003 (Unicode 2.0 and onwards semantics, Unicode BMP only) 0x0000001C
0x0001 (Macintosh) 0x0000 (Roman) 0x0000007C
0x0003 (Windows) 0x0001 (Unicode BMP (UCS-2)) 0x0000001C

よく見てみると、UnicodeWindowsのレコードのoffsetが同一です。サブテーブルは実際には2つだけ含まれているということでしょう。

Unicode, Windows用サブテーブル

0x0328 + 0x001C = 0x0344 から読みます。というか前と連続してますね。

先頭が0x0004なのでFormat 4サブテーブルのようです。

Type Name value 備考
USHORT format 0x0004 Format number 4
USHORT length 0x0060 96; このsubtableの長さ
USHORT language 0x0000 Mac用以外では0
USHORT segCountX2 0x0014 20; 2×segCount= 20 よって segCount=10
USHORT searchRange 0x0010 16; segCount以上で最小の2の冪乗数
USHORT entrySelector 0x0003 3; log2(searchRange/2)
USHORT rangeShift 0x0004 4; 2×segCount - searchRange = 20 - 16 = 4
USHORT endCount[segCount] 0x0000 End characterCode for each segment
0x000D
0x0020
0x007A
0x00A0
0x200A
0x202F
0x205F
0x25FC
0xFFFF last=0xFFFF
USHORT reservedPad 0x0000 0固定
USHORT startCount[segCount] 0x0000 Start character code for each segment
0x000D
0x0020
0x0061
0x00A0
0x2000
0x202F
0x205F
0x25FC
0xFFFF
SHORT idDelta[segCount] 0x0001 Delta for all character codes in segment
0xFFF5
0xFFE3
0xFFA3
0xFF7E
0xE01F
0xDFFB
0xDFCC
0xDA30
0x0001
USHORT idRangeOffset[segCount] 0x0000 0; Offsets into glyphIdArray or 0
0x0000
0x0000
0x0000
0x0000
0x0000
0x0000
0x0000
0x0000
0x0000
USHORT glyphIdArray[ ] なし

segCountが分からないとendCount以降が読み込めないので、先にsegCountを読んで配列の個数を確定させてから読んでいく必要があります。
さて、このformat 4は文字コードとglyph indexが連続して対応してる場合にコンパクトに記録できる方式のようです。

まとめます。

index endCount startCount idDelta idRangeOffset
0 0x0000 0x0000 0x0001 (1) 0x0000
1 0x000D 0x000D 0xFFF5 (-11) 0x0000
2 0x0020 0x0020 0xFFE3 (-29) 0x0000
3 0x007A 0x0061 0xFFA3 (-93) 0x0000
4 0x00A0 0x00A0 0xFF7E (-130) 0x0000
5 0x200A 0x2000 0xE01F (-8161) 0x0000
6 0x202F 0x202F 0xDFFB (-8197) 0x0000
7 0x205F 0x205F 0xDFCC (-8244) 0x0000
8 0x25FC 0x25FC 0xDA30 (-9680) 0x0000
9 0xFFFF 0xFFFF 0x0001 (1) 0x0000

さて、Unicodeスカラ値とグリフの対応を見てみましょう。今回欲しいのは“cat”(0x0063, 0x0061, 0x0074)なので、それに対応するものを探しましょう。
これらはendCount順にソートされています。endCountを順に見ていくと、4番目(index = 3)の0x007Aで初めてc (0x0063), a (0x0061), t (0x0074)の値以上になります。
そのためそれに対応するstartCountを見ると0x0061となっていて、c, a, tはいずれもそれ以上であり、したがってマッピングが定義されていることが分かります。
そこで、対応するidDeltaとidRangeOffset (ここでは0なので気にしない)からGlyph IDを計算します。ここでは、文字コードにidDelta = -93を加えると(=93を引くと)求めるGlyph IDになります。

文字 文字コード Glyph ID
c 0x0063 6
a 0x0061 4
t 0x0074 23

Macintosh用subtable

0x0328 + 0x007C = 0x03A4 から。

'hmtx' - Horizontal Metrics

offsetが0x192Cなのでそこから読みます。
hmtx - Horizontal metrix table specification - Typography | Microsoft Docs
グリフ幅と左サイドベアリングが記録されているようです。

Field Type
hMetrics longHorMetric[numberOfHMetrics]
leftSideBearing SHORT[ ]

'hhea'のnumberOfHMetricsが45であることから、45個hMetricsデータが連なってるはずです。

という訳で順番に読んでいきます。なおlongHorMetricは

  • USHORT advanceWidth;
  • SHORT lsb;

からなる構造体です。(lsbは左サイドベアリング)

hMetrics

index advanceWidth lsb
0 0x02EC 0x0044
1 0x0000 0x0000
2 0x0414 0x0000
3 0x0214 0x0000
4 0x047D 0x005E
5 0x04EC 0x00AE
6 0x03D7 0x0071
7 0x04EC 0x0071
8 0x0483 0x0071
9 0x02C1 0x001F
10 0x04EC 0x0071
11 0x04F2 0x00AE
12 0x0210 0x00A0
13 0x0210 0xFF8F
14 0x0446 0x00AE
15 0x0210 0x00AE
16 0x077B 0x00AE
17 0x04F2 0x00AE
18 0x04D7 0x0071
19 0x04EC 0x00AE
20 0x04EC 0x0071
21 0x034E 0x00AE
22 0x03D5 0x0068
23 0x02E3 0x0021
24 0x04F2 0x00A2
25 0x0410 0x0000
26 0x064A 0x0017
27 0x043B 0x0025
28 0x0414 0x0002
29 0x03C3 0x0050
30 0x0214 0x0000
31 0x030F 0x0000
32 0x061F 0x0000
33 0x030F 0x0000
34 0x061F 0x0000
35 0x020A 0x0000
36 0x0187 0x0000
37 0x0105 0x0000
38 0x0105 0x0000
39 0x00C3 0x0000
40 0x0139 0x0000
41 0x0057 0x0000
42 0x0139 0x0000
43 0x0187 0x0000
44 0x044C 0x0000

あれ、これでこのテーブル終わりのようです。leftSideBearingフィールドの入る余地がないですね。
leftSideBearingの個数は'maxp'のnumGlyphsを使って numGlyphs - numberOfHMetrics で計算されるらしいので、 45 - 45 = 0 なのであってます。主に等幅のグリフのためのものだとか。

"cat"の3文字についてまとめます。

文字 Glyph ID advanceWidth lsb
c 6 0x03D7 (983) 0x0071 (113)
a 4 0x047D (1149) 0x005E (94)
t 23 0x02E3 (739) 0x0021 (33)

'name' - Naming Table

offsetは0x1A5Cなのでそこから読んでいきます。
name - Naming table specification - Typography | Microsoft Docs
グリフを得るのには特に必要なさそうです。

'post' - PostScript Table

offsetは0x2100なのでそこから読みます。
post — PostScript Table specification - Typography | Microsoft Docs
PostScriptプリンタのために必要な情報を記録するところとのことで、グリフ名とかが格納されているようです。

'GDEF'

offsetは0x0158。
GDEF — Glyph Definition Table - Typography | Microsoft Docs
グリフの属するクラスを定義したり、リガチャのグリフのキャレット位置を指定したりするテーブルの様です。

'GPOS'

offsetは0x0180
GPOS — Glyph Positioning Table - Typography | Microsoft Docs
カーニングの調整、合成記号がどこにつくかの指定など、条件つきのグリフの位置調整を指定するテーブルです。

'GSUB'

offsetは0x0250。
GSUB — Glyph Substitution Table - Typography | Microsoft Docs
合字や、アラビア文字などにおける独立・語頭・語中・語末形の切り替えなど、特定の条件でグリフを置き換えることを指定するテーブルです。

'gasp' — Grid-fitting And Scan-conversion Procedure Table

offsetは0x075C。
gasp — Grid-fitting And Scan-conversion Procedure Table - Typography | Microsoft Docs
PPEMサイズの違いによりラスタライザの描画品質を向上させるためのフラグ設定等が書かれているようです。

'cvt ' - Control Value Table

offsetは0x04AC。
cvt - Control value table - Typography | Microsoft Docs

TrueType instructionから参照される値がつまってるようです。

FWORDの値が並んでいます。

0 1 2 3 4 5 6 7 8 9
0x 0x0000 0x044A 0x05B6 0x00A2 0x00E5 0x006A 0x007D 0x008C 0x0091 0x0095
1x 0x009A 0x009D 0x0054 0x00B4 0x00D3 0x0081 0x008F 0x0094 0x00AA 0x00AE
2x 0x00B4 0x00B8 0x00BE 0x00C2 0x006B 0x00A6 0x00BA 0x0084 0x00B2 0x008A
3x 0x0077 0x007B 0x00AC 0x00B0 0x0044 0x0511

'fpgm' - Font Program

offsetは0x04F4。
fpgm - Font program table - Typography | Microsoft Docs
BYTE[ n ]
最初に一回だけ実行されるプログラムだそうで、TrueType命令によって関数定義を行っているようです。

また、TrueType命令については、最近のMac OSからは単純に無視される様です。高DPI環境には不要ということでしょうか。今回描画に必要でないので省略します。

'prep' - Control Value Program

offsetは0x2204。
prep - Control value program table - Typography | Microsoft Docs

フォントサイズや変換行列などが変わる度に呼び出されるプログラムらしいです。グリフ固有の処理はできません。TrueType命令が並んでいます。

'loca' - Index to Location

offsetは0x19E0。
loca - Index-to-location - Typography | Microsoft Docs

このテーブルには2種類の形式がありますが、headテーブルのindexToLocFormat = 0であることからshort版であることがわかります。

indexToLoc table short version

Type Name 説明
USHORT offsets[n] 2で割ったローカルオフセット
n = numGlyphs('maxp') + 1

maxpテーブルのnumGlyphs = 45なので、USHORT型が46個並んだ配列となっているはずです。

index 0 1 2 3 4 5 6 7
0o00 0x0000 0x002C 0x002C 0x002C 0x002C 0x00A4 0x010E 0x0154
0o10 0x01BA 0x021E 0x0272 0x02EC 0x033C 0x0376 0x03C2 0x03FE
0o20 0x0418 0x0482 0x04CC 0x051A 0x0580 0x05E6 0x0622 0x0688
0o30 0x06EC 0x073A 0x0766 0x0806 0x0838 0x0878 0x08A6 0x08A6
0o40 0x08A6 0x08A6 0x08A6 0x08A6 0x08A6 0x08A6 0x08A6 0x08A6
0o50 0x08A6 0x08A6 0x08A6 0x08A6 0x08A6 0x08B2

'glyf' - Glyf Data

offsetは0x076C。
glyf - Glyf data table - Typography | Microsoft Docs

これにはグリフのデータが並んでいます。実際に描画される文字図形の本体ですね。フォントファイルの大部分を占めています。
locaテーブルに書かれているGlyphIDに対応するオフセットにアクセスするとそのグリフが得られるようです。

さすがに全部順番に読んでいくのは無理なので、今回描こうとしている“cat”の三文字について見ていきましょうか。

cmapテーブルから、c, a, tのGlyph IDはそれぞれ6, 4, 23であることがわかります。locaテーブルから対応するオフセットを求めると……

文字 glyph ID offset アドレス
c 6 0x021C 0x0988 - 0x0A14
a 4 0x0058 0x07C4 - 0x08B4
t 23 0x0D10 0x147C - 0x1544

となるので、それぞれを見ていきましょう。

'c' 0x0988 - 0x0A14

ヘッダー
Type Name value 備考
SHORT numberOfContours 0x0001 1; >= 0 なのでsingle glyph
SHORT xMin 0x0071 Minimum x for coordinate data.
SHORT yMin 0xFFEC Minimum y for coordinate data.
SHORT xMax 0x0393 Maximum x for coordinate data.
SHORT yMax 0x045E Maximum y for coordinate data.

numberOfCoutours >= 0 で単体のグリフなので、次のデータが来ます。

Simple Glyph Description
Type Name value Description
USHORT endPtsOfContours[n] 0x0016 22; Array of last points of each contour; n = numberOfCoutours
USHORT instructionLength 0x003D 61; instructionsのバイト数
BYTE instructions[n] 以下 Array of instructions for each glyph; n is the number of instructions.
BYTE flags[n] 以下 Array of flags for each coordinate in outline; n is the number of flags.
BYTE or SHORT xCoordinates[ ] 以下 First coordinates relative to (0,0); others are relative to previous point.
BYTE or SHORT yCoordinates[ ] 以下 First coordinates relative to (0,0); others are relative to previous point.
TrueType命令 (0x0996~0x9D3)

制御点列とその座標

flagsの配列の大きさは最初から確定できないので順番に読んでいく必要があるようです。インデックスがendPtsOfContoursの最大の値に等しくなるまで読んでいきます。
flagには、x,yがそれぞれBYTE(さらに正か負)かSHORTか前の値と同じかについての情報が含まれているので、それに従ってまず各制御点のx座標を、次にy座標を読みだしていきます。記録されている座標の値は前の座標との差分になっていて、記録スペースを圧縮することができるようになっているようです。

1バイト正数を+1b, 1バイト負数を-1b, 2バイト整数を2b, 前と変化なしをsameとします。

idx flag On Curve x y Δx Δy x座標 y座標
0 0x13 (0b00010011) +1b 2b +113 +543 113 543
1 0x10 (0b00010000) same 2b 0 +276 113 819
2 0x00 (0b00000000) 2b 2b +267 +299 380 1118
3 0x33 (0b00110011) +1b same +247 0 627 1118
4 0x32 (0b00110010) +1b same +80 0 707 1118
5 0x16 (0b00010110) +1b -1b +157 -33 864 1085
6 0x17 (0b00010111) +1b -1b +51 -26 915 1059
7 0x07 (0b00000111) -1b -1b -55 -150 860 909
8 0x26 (0b00100110) -1b +1b -139 +52 721 961
9 0x23 (0b00100011) -1b same -98 0 623 961
10 0x22 (0b00100010) -1b same -166 0 457 961
11 0x06 (0b00000110) -1b -1b -158 -209 299 752
12 0x15 (0b00010101) same -1b 0 -207 299 545
13 0x14 (0b00010100) same -1b 0 -199 299 346
14 0x16 (0b00010110) +1b -1b +158 -211 457 135
15 0x33 (0b00110011) +1b same +155 0 612 135
16 0x32 (0b00110010) +1b same +145 0 757 135
17 0x37 (0b00110111) +1b +1b +140 +64 897 199
18 0x15 (0b00010101) same -1b 0 -160 897 39
19 0x06 (0b00000110) -1b -1b -114 -59 783 -20
20 0x23 (0b00100011) -1b same -169 0 614 -20
21 0x22 (0b00100010) -1b same -237 0 377 -20
22 0x00 (0b00000000) 2b 2b -264 +291 113 271

座標の単位はFUNITです。

とりあえず制御点列が得られたので、プロットしていきましょう。簡
単のため、今回は10 FUNITを1mmとします。このフォントはEMが2048 FUNITなので、EMスクエアは204.8mmとなる計算です。
ここでは、黒い点がon-curve point、赤い×がoff-curve pointです。
f:id:nixeneko:20161001195629p:plain

それっぽいですね。

'a' 0x07C4 - 0x08B4

同様に読んでいきます。

ヘッダー
Type Name value 備考
SHORT numberOfContours 0x0002 2; >= 0 なのでsingle glyph
SHORT xMin 0x005E Minimum x for coordinate data.
SHORT yMin 0xFFEC Minimum y for coordinate data.
SHORT xMax 0x03D7 Maximum x for coordinate data.
SHORT yMax 0x045C Maximum y for coordinate data.

numberOfCoutours >= 0 なので、つまり合成グリフでないので、次のテーブルが後続します。

Simple Glyph Description
Type Name value Description
USHORT endPtsOfContours[n] 0x001A 26; Array of last points of each contour; n = numberOCoutours
0x0025 37
USHORT instructionLength 0x007B 123; instructionsのバイト数
BYTE instructions[n] 以下 TrueType命令
BYTE flags[n] 以下 このフラグに従って座標データを読む
BYTE or SHORT xCoordinates[ ] 以下 x座標データ差分
BYTE or SHORT yCoordinates[ ] 以下 y座標データ差分
TrueType命令: 0x07D4~0x084F

制御点列とその座標
idx flag On Curve x y Δx Δy x座標 y座標
0 0x13 (0b00010011) +1b 2b +94 +305 94 305
1 0x10 (0b00010000) same 2b 0 +334 94 639
2 0x25 (0b00100101) 2b +1b +527 +16 621 655
3 0x37 (0b00110111) +1b +1b +186 +7 807 662
4 0x35 (0b00110101) same +1b 0 +65 807 727
5 0x34 (0b00110100) same +1b 0 +125 807 852
6 0x26 (0b00100110) -1b +1b -108 +119 699 971
7 0x23 (0b00100011) -1b same -119 0 580 971
8 0x22 (0b00100010) -1b same -87 0 493 971
9 0x06 (0b00000110) -1b -1b -155 -52 338 919
10 0x07 (0b00000111) -1b -1b -68 -32 270 887
11 0x27 (0b00100111) -1b +1b -55 +135 215 1022
12 0x3E (0b00111110) +1b +1b +83 +44 298 1066
13 0x01 +1b +1b +196 +50 494 1116
14 0x33 (0b00110011) +1b same +96 0 590 1116
15 0x32 (0b00110010) +1b same +199 0 789 1116
16 0x16 (0b00010110) +1b -1b +194 -176 983 940
17 0x15 (0b00010101) same -1b 0 -192 983 748
18 0x11 (0b00010001) same 2b 0 -748 983 0
19 0x23 (0b00100011) -1b same -131 0 852 0
20 0x27 (0b00100111) -1b +1b -35 +156 817 156
21 0x23 (0b00100011) -1b same -8 0 809 156
22 0x0E (0b00001110) -1b -1b -82 -103 727 53
23 0x01 -1b -1b -163 -73 564 -20
24 0x23 (0b00100011) -1b same -124 0 440 -20
25 0x22 (0b00100010) -1b same -162 0 278 -20
26 0x26 (0b00100110) -1b +1b -184 +170 94 150
27 0x37 (0b00110111) +1b +1b +187 +153 281 303
28 0x14 (0b00010100) same -1b 0 -86 281 217
29 0x16 (0b00010110) +1b -1b +105 -92 386 125
30 0x33 (0b00110011) +1b same +95 0 481 125
31 0x32 (0b00110010) +1b same +151 0 632 125
32 0x36 (0b00110110) +1b +1b +173 +163 805 288
33 0x3D (0b00111101) same +1b 0 +150 805 438
34 0x01 same +1b 0 +99 805 537
35 0x07 (0b00000111) -1b -1b -162 -7 643 530
36 0x0E (0b00001110) -1b -1b -189 -7 454 523
37 0x01 -1b -1b -173 106 281 417

制御点の0-26と27-37が別々のcontourになります。
cと同様にaの制御点列もプロットしていきましょう。'hmtx'で見たようにcは幅983なので、aは制御点のx座標に983を加えた場所にプロットします。
f:id:nixeneko:20161001201014p:plain

't' 0x147C - 0x1544

これも同様に読んでいきます。

ヘッダー
Type Name value 備考
SHORT numberOfContours 0x0001 1; >= 0 なのでsingle glyph
SHORT xMin 0x0021 Minimum x for coordinate data.
SHORT yMin 0xFFEC Minimum y for coordinate data.
SHORT xMax 0x02B6 Maximum x for coordinate data.
SHORT yMax 0x0546 Maximum y for coordinate data.

numberOfCoutours >= 0 なので、つまり合成グリフでないので、次のテーブルが後続します。

Simple Glyph Description
Type Name value Description
USHORT endPtsOfContours[n] 0x0016 22; Array of last points of each contour; n = numberOCoutours
USHORT instructionLength 0x007A 122; instructionsのバイト数
BYTE instructions[n] 以下 TrueType命令
BYTE flags[n] 以下 このフラグに従って座標データを読む
BYTE or SHORT xCoordinates[ ] 以下 x座標データ差分
BYTE or SHORT yCoordinates[ ] 以下 y座標データ差分
TrueType命令 (0x148A-0x1504)

制御点列とその座標
idx flag On Curve x y Δx Δy x座標 y座標
0 0x13 (0b00010011) +1b 2b +33 +958 33 958
1 0x35 (0b00110101) same +1b 0 +86 33 1044
2 0x3F (0b00111111) +1b +1b +157 +72 190 1116
3 0x01 +1b +1b +72 +234 262 1350
4 0x33 (0b00110011) +1b same +107 0 369 1350
5 0x15 (0b00010101) same -1b 0 -252 369 1098
6 0x21 (0b00100001) 2b same +317 0 686 1098
7 0x15 (0b00010101) same -1b 0 -140 686 958
8 0x21 (0b00100001) 2b same -317 0 369 958
9 0x11 (0b00010001) same 2b 0 -634 369 324
10 0x14 (0b00010100) same -1b 0 -95 369 229
11 0x16 (0b00010110) +1b -1b +91 -102 460 127
12 0x33 (0b00110011) +1b same +81 0 541 127
13 0x32 (0b00110010) +1b same +35 0 576 127
14 0x36 (0b00110110) +1b +1b +94 +14 670 141
15 0x37 (0b00110111) +1b +1b +24 +9 694 150
16 0x15 (0b00010101) same -1b 0 -138 694 12
17 0x0E (0b00001110) -1b -1b -25 -11 669 1
18 0x01 -1b -1b -105 -21 564 -20
19 0x23 (0b00100011) -1b same -54 0 510 -20
20 0x20 (0b00100000) 2b same -322 0 188 -20
21 0x19 (0b00011001) same 2b 0 +339 188 319
22 0x01 same 2b 0 +639 188 958

また、tについてもプロットしていきましょう。cとaの幅 = 983 + 1149をx座標に加えた位置にプロットします。
f:id:nixeneko:20161001201414p:plain

アウトライン描画

さて、これら制御点列からアウトラインを描いていきましょう。
on-curve pointはその名前の通りグリフの輪郭(=contour)の上にある点です。最終的なアウトラインはon-curve pointを通ります。
一方でoff-curve pointは曲線の曲がり方を制御する点で、最終的なアウトラインは普通この点を通りません。


on-curve pointが2つ連続すると直線になります。連続するon-curve pointを黒線で結びます。
f:id:nixeneko:20161001201811p:plain
tは大体直線でできていますね。

次に、曲線を求めていかないといけないのですが、まず準備として制御点列を線で結びます。
f:id:nixeneko:20161001202006p:plain

そして、連続するoff-curve pointの中点にon-curve pointを打っていきます。
f:id:nixeneko:20161001202148p:plain
こうすることで、曲線の部分は2次ベジエ(Bézier)曲線の集まりとみなせます。


さて、2次ベジエを描いていきましょう。2次ベジエは3つの制御点からなる曲線で、両端の点を通り真ん中の点の方に近づくカーブを描きます。ここでは、on-curve・off-curve・on-curveの3点の並び(●-×-●の並び)が2次ベジエ曲線です。

今回は、ド・カステリョ(De Casteljau)のアルゴリズムの考え方によって、ベジエ曲線を半分半分に繰り返し分割していくことで求めて行きたいと思います。

まず、2次ベジエの隣り合う制御点の中点同士を結びます。
f:id:nixeneko:20161001203213p:plain
次にその中点を求めるとそれがon-curve pointとなります。
f:id:nixeneko:20161001203402p:plain

さて、分割されたベジエ曲線は次の図の●-+-●の並びになります。
f:id:nixeneko:20161001203553p:plain

間隔が十分に狭いところを除いて、さらに繰り返し分割していきます。
f:id:nixeneko:20161001204005p:plain
制御点を結んだ線がどんどん求める形に近づいていきますね。
もう一回分割します。
f:id:nixeneko:20161001204126p:plain
も、もういいかな……さらに分割してもいいですが面倒なのと狭くてうまく描けないのであとはフリーハンドで黒い点をなめらかに結んでいきます。
f:id:nixeneko:20161001204306p:plain
完成です! 良さそうですね! これをさらに、制御点を順番にたどっていったときの右手側を塗りつぶすとフォントのレンダリング結果になります。

さて、実際にPhotoshopでフォントを表示したものを重ねてみます。
f:id:nixeneko:20161001204527p:plain
いい感じに一致してますね!

感想

  • こうやって見ていくと自然に仕様書とにらめっこすることになり、フォントファイルについての理解が深まりますね。マゾい暇な人にお勧めします。
  • こんなことするより仕様に従ってフォントを読み込むソフトウェア作ったほうが生産性があるかもしれません。
  • データを格納する際に、容量が小さくなるような工夫が随所にあるのがわかります。オフセット指定によって内容が同一なものは複数記録する必要がなかったり、座標は差分のみを記録することで容量を節約したり、ランレングス圧縮的な要素もあります。
  • フォントヒンティングに関するTrueType命令の動作を把握することについては、フォントから制御点の座標を読み出す以上に面倒そうなので途中で投げました。詳しい人に動作を教わりたいです。
  • もうやらない。

逆引きTrueType instruction set

TrueType instruction setについて、バイナリからmnemonicが引けるようなものが見つからなかったので作った。

Code range Mnemonic Description
0x00 - 0x01 SVTCA[a] Set freedom and projection Vectors To Coordinate Axis
0x02 - 0x03 SPVTCA[a] Set Projection_Vector To Coordinate Axis
0x04 - 0x05 SFVTCA[a] Set Freedom_Vector to Coordinate Axis
0x06 - 0x07 SPVTL[a] Set Projection_Vector To Line
0x08 - 0x09 SFVTL[a] Set Freedom_Vector To Line
0x0A SPVFS[ ] Set Projection_Vector From Stack
0x0B SFVFS[ ] Set Freedom_Vector From Stack
0x0C GPV[ ] Get Projection_Vector
0x0D GFV[ ] Get Freedom_Vector
0x0E SFVTPV[ ] Set Freedom_Vector To Projection Vector
0x0F ISECT[] moves point p to the InterSECTion of two lines
0x10 SRP0[ ] Set Reference Point 0
0x11 SRP1[ ] Set Reference Point 1
0x12 SRP2[ ] Set Reference Point 2
0x13 SZP0[ ] Set Zone Pointer 0
0x14 SZP1[ ] Set Zone Pointer 1
0x15 SZP2[ ] Set Zone Pointer 2
0x16 SZPS[ ] Set Zone PointerS
0x17 SLOOP[ ] Set LOOP variable
0x18 RTG[ ] Round To Grid
0x19 RTHG[ ] Round To Half Grid
0x1A SMD[ ] Set Minimum_ Distance
0x1B ELSE[] ELSE clause
0x1C JMPR[] JuMP Relative
0x1D SCVTCI[ ] Set Control Value Table Cut In
0x1E SSWCI[ ] Set Single_Width_Cut_In
0x1F SSW[ ] Set Single-width
0x20 DUP[] DUPlicate top stack element
0x21 POP[] POP top stack element
0x22 CLEAR[] CLEAR the stack
0x23 SWAP[] SWAP the top two elements on the stack
0x24 DEPTH[] DEPTH of the stack
0x25 CINDEX[] Copy the INDEXed element to the top of the stack
0x26 MINDEX[] Move the INDEXed element to the top of the stack
0x27 ALIGNPTS[] ALIGN Points
0x28
0x29 UTP[] UnTouch Point
0x2A LOOPCALL[] LOOP and CALL function
0x2B CALL[] CALL function
0x2C FDEF[] Function DEFinition
0x2D ENDF[] END Function definition
0x2E - 0x2F MDAP[a] Move Direct Absolute Point
0x30 - 0x31 IUP[a] Interpolate Untouched Points through the outline
0x32 - 0x33 SHP[a] SHift Point using reference point
0x34 - 0x35 SHC[a] SHift Contour using reference point
0x36 - 0x37 SHZ[a] SHift Zone using reference point
0x38 SHPIX[] SHift point by a PIXel amount
0x39 IP[] Interpolate Point
0x3A - 0x3B MSIRP[a] Move Stack Indirect Relative Point
0x3C ALIGNRP[] ALIGN to Reference Point
0x3D RTDG[ ] Round To Double Grid
0x3E - 0x3F MIAP[a] Move Indirect Absolute Point
0x40 NPUSHB[ ] PUSH N Bytes
0x41 NPUSHW[ ] PUSH N Words
0x42 WS[ ] Write Store
0x43 RS[ ] Read Store
0x44 WCVTP[ ] Write Control Value Table in Pixel units
0x45 RCVT[ ] Read Control Value Table
0x46 - 0x47 GC[a] Get Coordinate projected onto the projection_vector
0x48 SCFS[ ] Sets Coordinate From the Stack using projection_vector and freedom_vector
0x49 - 0x4A MD[a] Measure Distance
0x4B MPPEM[ ] Measure Pixels Per EM
0x4C MPS[ ] Measure Point Size
0x4D FLIPON[ ] Set the auto_flip Boolean to ON
0x4E FLIPOFF[ ] Set the auto_flip Boolean to OFF
0x4F DEBUG[] DEBUG call
0x50 LT[] Less Than
0x51 LTEQ[] Less Than or EQual
0x52 GT[] Greater Than
0x53 GTEQ[] Greater Than or Equal
0x54 EQ[] Equal
0x55 NEQ[] Not EQual
0x56 ODD[] ODD
0x57 EVEN[] EVEN
0x58 IF[] IF test
0x59 EIF[] End IF
0x5A AND[] logical AND
0x5B OR[] logical OR
0x5C NOT[] logical NOT
0x5D DELTAP1[] DELTA exception P1
0x5E SDB[ ] Set Delta_Base in the graphics state
0x5F SDS[ ] Set Delta_Shift in the graphics state
0x60 ADD[] ADD
0x61 SUB[] SUBtract
0x62 DIV[] DIVide
0x63 MUL[] MULtiply
0x64 ABS[] ABSolute value
0x65 NEG[] NEGate
0x66 FLOOR[] FLOOR
0x67 CEILING[] CEILING
0x68 - 0x6B ROUND[ab] ROUND value
0x6C - 0x6F NROUND[ab] No ROUNDing of value
0x70 WCVTF[ ] Write Control Value Table in Funits
0x71 DELTAP2[] DELTA exception P2
0x72 DELTAP3[] DELTA exception P3
0x73 DELTAC1[] DELTA exception C1
0x74 DELTAC2[] DELTA exception C2
0x75 DELTAC3[] DELTA exception C3
0x76 SROUND[ ] Super ROUND
0x77 S45ROUND[ ] Super ROUND 45 degrees
0x78 JROT[] Jump Relative On True
0x79 JROF[] Jump Relative On False
0x7A ROFF[ ] Round OFF
0x7B
0x7C RUTG[ ] Round Up To Grid
0x7D RDTG[ ] Round Down To Grid
0x7E SANGW[ ] Set Angle _Weight
0x7F AA[] Adjust Angle
0x80 FLIPPT[] FLIP PoinT
0x81 FLIPRGON[] FLIP RanGe ON
0x82 FLIPRGOFF[] FLIP RanGe OFF
0x83 - 0x84
0x85 SCANCTRL[ ] SCAN conversion ConTRoL
0x86 - 0x87 SDPVTL[a] Set Dual Projection_Vector To Line
0x88 GETINFO[] GET INFOrmation
0x89 IDEF[] Instruction DEFinition
0x8A ROLL[] ROLL the top three stack elements
0x8B MAX[] MAXimum of top two stack elements
0x8C MIN[] MINimum of top two stack elements
0x8D SCANTYPE[ ] SCANTYPE
0x8E INSTCTRL[] INSTRuction execution ConTRoL
0x8F - 0x90
0x91 GETVARIATION[ ] GET VARIATION
0x92 - 0xAF
0xB0 - 0xB7 PUSHB[abc] PUSH Bytes
0xB8 - 0xBF PUSHW[abc] PUSH Words
0xC0 - 0xDF MDRP[abcde] Move Direct Relative Point
0xE0 - 0xFF MIRP[abcde] Move Indirect Relative Point

0x28, 0x7B, 0x83 - 0x84, 0x8F - 0xAF には何が当てられているのだろうか?

(2016-09-20 追記)


(追記終)

マイクモジュールをArduinoに繋ぎPCでProcessingを使って録音する

Arduinoに乗っけて音に反応する何かをつくれないかと、「エレクトレットマイクアンプモジュール ADA-1063」を買った。
www.switch-science.com
これは、

使い方はシンプルで、電源を与えるだけ。増幅された信号が、VCCの半分でバイアスされてOUTピンから出てきます。

だそうである。


とりあえず、マイクモジュールの動作確認を兼ねて、出力をArduino経由でPCに転送し録音してみた。

概略

  • Arduinoでマイクモジュールの出力を読み取り、シリアル通信で送信する。
    • 簡単のため、シリアル通信で送受信するデータを1バイト(0~255)の範囲とした。
  • PCで受信側ではProcessingを使うことにした。
  • 受信したデータを8bitモノラルのWAVEファイルとして書き出す。

使ったもの

ハード

  • Arduino Uno R3
  • エレクトレットマイクアンプモジュール ADA-1063

ソフト

接続

Arduino Unoとの接続はこんな感じ。
f:id:nixeneko:20160914050500j:plain

  • 5V ↔ Vcc
  • GND ↔ GND
  • A0 ↔ OUT

として結んでいる。出力はAnalog InのA0ピンとした。

素人考えで直結しただけだけどこんなんでいいのだろうか。

ソースコード

Arduino

#define BAUD 57600 //シリアル通信の転送速度。受信側と合わせる
#define PIN 0      //使用するアナログ入力ピンの指定 A0…0.
int d;

void setup() {
  // put your setup code here, to run once:
  pinMode(PIN, INPUT);
  Serial.begin(BAUD);
}

void loop() {
  // put your main code here, to run repeatedly:
  d = analogRead(PIN);
  Serial.write(d>>2);
  //delay(100);
}

analogRead関数では0~1023のデータが返るっぽいので、4で割って8bitに収まる様にしてシリアル出力に書き出している。
マイクモジュールのVccに5Vを与えているので、無音なら2.5Vとなるようである。アナログ入力が0~5Vの範囲だろうから、ちょうど真ん中になる。8bit WAVEファイルは符号なし整数0~255で無音が128と真ん中にくるので、読み取ったデータを8bitにマップしてWaveファイルにそのまま突っ込めばいい。

Processing

import processing.serial.*;

final int BAUD = 57600;            //シリアル通信の転送速度。送信側と合わせる
final int ARYSIZE = 100000;        //サンプル数-44。100000で20秒弱になった
final String SERIALPORT = "COM3";  //シリアルポート。環境に合わせる
final String OUTFILE = "test.wav"; //出力WAVファイル名

Serial myPort; 
byte x;
int cnt;
byte[] data;  //受信データ保存用配列

float time_start; //サンプリング周波数計算用
float time_end;   //同上

boolean outputflag;

void setup(){
  myPort = new Serial(this, SERIALPORT, BAUD);  //シリアルポート初期化
  data = new byte[ARYSIZE];  //受信データ保存用配列
  cnt = 0;                   //カウンタ初期化
  time_start = millis();
  outputflag = false;
}

void draw(){
  
}

//Waveヘッダ書込用。4バイト整数をbyte型4つ分書き出し
void set4bytes(byte[] ary, int idx, int val){
  ary[idx  ] = (byte)(val % 256);
  ary[idx+1] = (byte)(val / 0x100 % 256);
  ary[idx+2] = (byte)(val / 0x10000 % 256);
  ary[idx+3] = (byte)(val / 0x1000000);
}

//Waveヘッダ書込用。2バイト整数をbyte型2つ分書き出し
void set2bytes(byte[] ary, int idx, int val){
  ary[idx  ] = (byte)(val % 256);
  ary[idx+1] = (byte)(val / 0x100 % 256);
}

//Waveファイルに書き出し
void writeWav(){
  time_end = millis();
  
  //WAVEヘッダの書き込み

  data[0] = 'R';
  data[1] = 'I';
  data[2] = 'F';
  data[3] = 'F';
  
  int fsizemin8 = ARYSIZE - 8;
  set4bytes(data, 4, fsizemin8);
  
  data[8] = 'W';
  data[9] = 'A';
  data[10] = 'V';
  data[11] = 'E';
  
  data[12] = 'f';
  data[13] = 'm';
  data[14] = 't';
  data[15] = ' ';
  
  int fmtsize = 16;
  set4bytes(data, 16, fmtsize);
  
  int fmtcode = 1;
  set2bytes(data, 20, fmtcode);
  
  int numch = 1;                //モノラル
  set2bytes(data, 22, numch);
  
  //サンプリング周波数
  int samprate = int(ARYSIZE / ((time_end - time_start) / 1000));
  set4bytes(data, 24, samprate);
  
  int bytepersec = samprate;    //秒あたりバイト数
  set4bytes(data, 28, bytepersec);
  
  int blockboundary = 1;
  set2bytes(data, 32, blockboundary);
  
  int bitpersample = 8;         //8bit
  set2bytes(data, 34, bitpersample);
  
  data[36] = 'd';
  data[37] = 'a';
  data[38] = 't';
  data[39] = 'a';
  
  int sizeremained = ARYSIZE - 126;
  set4bytes(data, 40, sizeremained);
  
  //出力
  saveBytes(OUTFILE, data);
  exit();
}

//シリアル通信で受信すると呼び出される
void serialEvent(Serial p){
  x = (byte) p.read(); //受信データ
  println(cnt, x);
  if (cnt < ARYSIZE){
    data[cnt] = x;
    cnt++;
  }else{
    if (!outputflag) { //複数回実行されないように
      outputflag = true;
      writeWav();
    }
  }
}

シリアル通信で受信したデータを配列に貯め、予め決めたサンプル数だけ貯まったらWAVEファイルとして書き出すことをしている。

WAVEファイルのヘッダを書き込む部分が大部分である。泥臭い。ヘッダについては次のページを参考にした。

Processingにはunsignedなintやbyteがないので、多少ややこしくなっている。

録音例

mp3にエンコードしています。

1.1倍速で再生してちょうどよいくらいの速度っぽい。プログラムのボーレート変えると再生速度も微妙に変化するので、シリアル通信回りは少なくともある程度影響してるのだろうが…。あるいは、サンプリング周波数の計算や送受信するデータの方が悪いのかもしれない…?

今回はとりあえずマイクが動いてるのを確認できたのでよしとする。

(2016-09-15追記)
サンプリング周波数の計算のために時間を計測しているが、これが実際より1.5秒程度長くなっているのが原因だったらしい。
具体的には開始時間の計測に難があった。

Processingの開始時には1.5秒ほどシリアル通信が何も受信しない期間があるようなので、これを含めないように時間の取得をしないといけない。

draw()が最初に呼ばれたときにはserial.available()が200~300あたりになっているが、これがその後1.5秒ほど変化しない(新しいデータが来ない?)っぽいので、安定してデータが受信できるようになるまで待ってから時間の計測とデータの取得をするといいようだ。

改変したprocessingコードが次である。

import processing.serial.*;

final int BAUD = 57600;            //シリアル通信の転送速度。送信側と合わせる
final int ARYSIZE = 100000;        //サンプル数-44。100000で20秒弱になった
final String SERIALPORT = "COM3";  //シリアルポート。環境に合わせる
final String OUTFILE = "test.wav"; //出力WAVファイル名

Serial myPort; 
byte x;
int cnt;
byte[] data;  //受信データ保存用配列

int time_start; //サンプリング周波数計算用
int time_end;   //同上

boolean outputflag;
boolean initflag;

void setup(){
  myPort = new Serial(this, SERIALPORT, BAUD);  //シリアルポート初期化
  data = new byte[ARYSIZE];  //受信データ保存用配列
  cnt = 0;                   //カウンタ初期化
  time_start = millis();
  outputflag = true;
  initflag = true;
}

void draw(){
  
}

//Waveヘッダ書込用。4バイト整数をbyte型4つ分書き出し
void set4bytes(byte[] ary, int idx, int val){
  ary[idx  ] = (byte)(val % 256);
  ary[idx+1] = (byte)(val / 0x100 % 256);
  ary[idx+2] = (byte)(val / 0x10000 % 256);
  ary[idx+3] = (byte)(val / 0x1000000);
}

//Waveヘッダ書込用。2バイト整数をbyte型2つ分書き出し
void set2bytes(byte[] ary, int idx, int val){
  ary[idx  ] = (byte)(val % 256);
  ary[idx+1] = (byte)(val / 0x100 % 256);
}

//Waveファイルに書き出し
void writeWav(){
  //WAVEヘッダの書き込み

  data[0] = 'R';
  data[1] = 'I';
  data[2] = 'F';
  data[3] = 'F';
  
  int fsizemin8 = ARYSIZE - 8;
  set4bytes(data, 4, fsizemin8);
  
  data[8]  = 'W';
  data[9]  = 'A';
  data[10] = 'V';
  data[11] = 'E';
  
  data[12] = 'f';
  data[13] = 'm';
  data[14] = 't';
  data[15] = ' ';
  
  int fmtsize = 16;
  set4bytes(data, 16, fmtsize);
  
  int fmtcode = 1;
  set2bytes(data, 20, fmtcode);
  
  int numch = 1;                //モノラル
  set2bytes(data, 22, numch);
  
  //サンプリング周波数
  int samprate = int(ARYSIZE / ((time_end - time_start) / 1000.0));
  set4bytes(data, 24, samprate);
  
  int bytepersec = samprate;    //秒あたりバイト数
  set4bytes(data, 28, bytepersec);
  
  int blockboundary = 1;
  set2bytes(data, 32, blockboundary);
  
  int bitpersample = 8;         //8bit
  set2bytes(data, 34, bitpersample);
  
  data[36] = 'd';
  data[37] = 'a';
  data[38] = 't';
  data[39] = 'a';
  
  int sizeremained = ARYSIZE - 126;
  set4bytes(data, 40, sizeremained);
  
  //出力
  saveBytes(OUTFILE, data);
  println("start:", time_start, "end:", time_end, "samprate:", samprate);
  exit();
}

//シリアル通信で受信すると呼び出される
void serialEvent(Serial p){
  if(initflag && cnt > 1000){ //受信が安定した頃に一回だけ実行
    time_start = millis();  //開始時間の計測
    println("time!");
    initflag = false;
    cnt = 0;  //カウンタの初期化→今までの受信データは上書きして捨てられる
  }
  x = (byte) p.read(); //受信データ
  if (cnt % 100 == 1) println(cnt, x); //見づらいのでコンソール出力を間引いた
  if (cnt < ARYSIZE){
    data[cnt] = x;
    cnt++;
  }else{
    if (outputflag) { //複数回実行されないように
      time_end = millis();
      outputflag = false;
      writeWav();
    }
  }
}

ここでは最初に受信したデータの累積数が1000個(=カウンタが1000以上)になるまで待って、そこから開始時間の計測とデータの取得を開始している。これで大体等倍速が実現できた。めでたしめでたし。

別の方法

serialEventイベントハンドラで受信データを扱う以外にも、drawループの中で受信データを取得することもできる。
上のコードの void serialEvent(){ ~ } 部分を削除し、 void draw(){ } を次のように変更すると同じような結果が得られる。

void draw(){
  if(initflag && (cnt > 1000)){
    cnt = 0;
    time_start = millis();
    initflag = false;
    println("time!");
  }
  while(myPort.available() > 0){
    x = (byte) myPort.read();
        
    if (cnt < ARYSIZE){
      data[cnt] = x;
      cnt++;
    }else{
      if (outputflag) {
        time_end = millis();
        println("time!");
        outputflag = false;
        writeWav();
      }
    }
  }
  println(cnt, x);
}

(追記終)

感想

ノイズがでかい。かなりマイクに近づけて録音してもこのようにノイズが大きめに聞こえるので、このままでは実用は難しいかもしれない。

このページによると同じモジュールではないが電源切り離し等すればノイズが減ったとのことである。今手元にちょうどいい電源がないのでそのうち検証したい。

(2016-09-16追記)
マイクモジュールにつなぐ電源を、単4電池4本直列→三端子レギュレータで5Vに降圧したものに切り替えたところ、ノイズが気にならない位になった。この場合、レギュレータの出力の5VをArduinoのAREFと接続する必要があるようだ。

(2017-02-17追記)

Using it is simple: connect GND to ground, VCC to 2.4-5VDC. For the best performance, use the "quietest" supply available (on an Arduino, this would be the 3.3V supply).

https://www.adafruit.com/products/1063

……。
はい、Arduinoの3.3V端子から電源供給したら見事にノイズが少なくなりました。ノイズに敏感なものを扱う際にはArduinoの3.3Vを使いましょう、5Vではなく……。