TrueTypeフォントのフォーマットを調べる その8

1.cmap - Character To Glyph Index Mapping Table

*1
 Cmapテーブルは文字コード(文字集合)と字形を対応させるテーブルです。文字コードとは文字に対する一意のIDのことを云います。文字の形(字形、グリフとも呼ばれる)にどのようなIDを振るかを決めたものを文字集合といいます。文字集合は代表的なものにUnicodeがありますが、日本ではJIS、中国や台湾などではGBやBig5、韓国や朝鮮などのハングル圏ではJohabと各々の文字を使う共同体の中で発展してきた歴史があります。ここで問題になるのは、同じ字体を表すIDが文字コードによって違うということです。例えば以下の例を見てください。下のIDはどちらも「文」を示しています。

文字コード ID
Shift-JIS 95B6
Unicode 6587

このように同じグリフであっても文字コードによって違うIDが割り振られています。これらはOSなどのプラットフォームによる影響も受けます。もし、素直に文字コードとグリフを結びつけるとOSや文字コードの数だけフォントが必要になってしまいます。
 そこでCmapです。Cmapは文字コードやプラットフォーム別に、文字コードIDとフォント内に格納されているグリフIDの対応テーブルを複数持っています。(グリフはフォント内で配列のように存在しており、グリフIDは配列のインデックスに当ります。)これにより一つのフォントで複数のプラットフォームと文字コードに対応させることができます。
 MicrosoftのTTFの仕様書ではプラットフォームは以下のように定義されています。

ID Platform Specific encoding
0 Apple Unicode none
1 Macintosh Script manager code
2 ISO ISO encoding
3 Microsoft Microsoft encoding

文字コードは以下のように定義されています。

Encoding ID Description 説明+
0 Symbol 記号
1 Unicode 世界標準となりつつある
2 ShiftJIS 主に日本
3 Big5 台湾
4 PRC
5 Wansung 中国
6 Johab 韓国

また規則や推奨として以下のことが上げられます。

  • そのフォント内で定義されていない文字を表すグリフ(四角である場合が多い)はグリフIndexが0と決まっています。
  • プラットフォームと文字コードで決まる変換テーブルは重複してはいけません。
  • Unicode「cmap」を使用することが強く推奨されます。
  • エントリは始めにプラットフォームID、次にエンコーディングIDによってソートされる必要があります。

2.フォーマット

Type Description 説明
USHORT Table version number (0). テーブルバージョン番号
USHORT Number of encoding tables, n. エンコーディングテーブルの数

次に以下のエントリを含む配列[n]が続きます。プラットフォームIDやエンコーディングIDは1で説明したものを使用します。

Type Description
USHORT Platform ID.
USHORT Platform-specific encoding ID.
ULONG サブテーブルのオフセット。ファイル全体のオフセットではないことに注意。

サブテーブルの実体は文字コードとグリフIDのマッピングです。TTFではエンコーディングに応じたマッピングを行えるようにいくつかのフォーマットを用意しています。フォーマットは以下のように定義されています。

format description
Format 0 Byte encoding table
Format 2 High-byte mapping through table
Format 4 Segment mapping to delta values
Format 6 Trimmed table mapping

注意すべきことはサブテーブルエントリからはサブテーブルがどのようなフォーマットを利用しているのか分からないところです。フォーマットを判別するにはサブテーブルの始めの4バイトがすべてフォーマット番号を表すことを利用して判別するしかありません。

3.format0

アップルの標準変換テーブルです。単純に一対一のマッピングをします。注意として256個のグリフしか扱えません。使う機会も見る機会も余りありません。

Type Name Description
USHORT format Format number is set to 0.
USHORT length This is the length in bytes of the subtable.
USHORT version Version number (starts at 0).
BYTE glyphIdArray[256] An array that maps character codes to glyph index values.

FontBoxでは以下のように実装されています。

//CMAPEncodingEntry.java
byte[] glyphMapping = data.read( 256 );
glyphIdToCharacterCode = new int[256];
for( int i=0;i<glyphMapping.length; i++ ){
  glyphIdToCharacterCode[i]=(glyphMapping[i]+256)%256;
}

256で割った余りを出しているところがよく分かりません。

4.format2

日本語、中国語、韓国語の文字コードに便利な1バイトと2バイトが混じったエンコーディングです。

Type Name Description
USHORT format Format number is set to 2.
USHORT length Length in bytes.
USHORT version Version number (starts at 0)
USHORT subHeaderKeys[256] Array that maps high bytes to subHeaders: value is subHeader index* 8.
4 words struct subHeaders[ ] Variable-length array of subHeader structures.
4 words-struct subHeaders[ ]
USHORT glyphIndexArray[ ] Variable-length array containing subarrays used for mapping the low byte of 2-byte characters.

subHeadersの構造は以下の様になっています。

Type Name Description
USHORT firstCode First valid low byte for this subHeader.
USHORT entryCount Number of valid low bytes for this subHeader.
SHORT idDelta See text below.
USHORT idRangeOffset See text below.

FontBoxのソースでは未実装になっていたので以下にshowttfのソースを示します。

	    int max_sub_head_key = 0, cnt, last;
	    struct subhead { uint16 first, cnt, delta, rangeoff; } *subheads;

	    for ( i=0; i<256; ++i ) {
		table[i] = getushort(ttf)/8;	/* Sub-header keys */
		if ( table[i]>max_sub_head_key )
		    max_sub_head_key = table[i];	/* The entry is a byte pointer, I want a pointer in units of struct subheader */
	    }
	    subheads = malloc((max_sub_head_key+1)*sizeof(struct subhead));
	    for ( i=0; i<=max_sub_head_key; ++i ) {
		subheads[i].first = getushort(ttf);
		subheads[i].cnt = getushort(ttf);
		subheads[i].delta = getushort(ttf);
		subheads[i].rangeoff = (getushort(ttf)-
				(max_sub_head_key-i)*sizeof(struct subhead)-
				sizeof(short))/sizeof(short);
	    }
	    cnt = (len-(ftell(ttf)-(info->encoding_start+encoff)))/sizeof(short);
	    /* The count is the number of glyph indexes to read. it is the */
	    /*  length of the entire subtable minus that bit we've read so far */
	    glyphs = malloc(cnt*sizeof(short));
	    for ( i=0; i<cnt; ++i )
		glyphs[i] = getushort(ttf);
	    last = -1;
	    for ( i=0; i<256; ++i ) {
		if ( table[i]==0 ) {
		    /* Special case, single byte encoding entry, look i up in */
		    /*  subhead */
		    /* In the one example I've got of this encoding (wcl-02.ttf) the chars */
		    /* 0xfd, 0xfe, 0xff are said to exist but there is no mapping */
		    /* for them. */
		    if ( last!=-1 )
			index = 0;	/* the subhead says there are 256 entries, but in fact there are only 193, so attempting to find these guys should give an error */
		    else if ( i<subheads[0].first || i>=subheads[0].first+subheads[0].cnt ||
			    subheads[0].rangeoff+(i-subheads[0].first)>=cnt )
			index = 0;
		    else if ( (index = glyphs[subheads[0].rangeoff+(i-subheads[0].first)])!= 0 )
			index = (uint32) (index+subheads[0].delta);
		    /* I assume the single byte codes are just ascii or latin1*/
		    if ( index!=0 && index<info->glyph_cnt ) {
			if ( info->glyph_unicode[index]==0 )
			    info->glyph_unicode[index] = i;
			else
			    info->dups = makedup(index,i,info->dups);
		    }
		} else {
		    int k = table[i];
		    for ( j=0; j<subheads[k].cnt; ++j ) {
			int enc;
			if ( subheads[k].rangeoff+j>=cnt )
			    index = 0;
			else if ( (index = glyphs[subheads[k].rangeoff+j])!= 0 )
			    index = (uint16) (index+subheads[k].delta);
			if ( index!=0 && index<info->glyph_cnt ) {
			    enc = (i<<8)|(j+subheads[k].first);
			    if ( info->glyph_unicode[index]==0 )
				info->glyph_unicode[index] = enc;
			    else
				info->dups = makedup(index,enc,info->dups);
			}
		    }
		    if ( last==-1 ) last = i;
		}
	    }
	    free(subheads);
	    free(glyphs);

正直よく分からないので説明は次回。

5.

  1. FontBox:「その7」の記事の参考資料
  2. showttf:「その3」の記事の参考資料
  3. vanillaの日記: OpenTypeフォントの続き(6)・・・cmapテーブル

*1:仕様書の訳じゃないです。根本的に間違っている可能性があります。

TrueTypeフォントのフォーマットを調べる その7

1.コンポーネントを動的に追加する

AというSwingのコンポーネントにBというコンポーネントを動的に貼り付けたい時は以下のようにします。(参考資料1)

JPanel A = new JPanel();
JButton B = new JButton("ボタンだよ。");
A.revalidate();//一回だけでいい。
//A.setLayout(new FlowLayout());

//ボタンを押すと呼び出されるメソッド
private void putB(java.awt.event.MouseEvent evt){
  A.add(B);
}

注意点としてはレイアウトを指定してやることです。特にNetBeansのデフォルトのデザイン(フリーデザイン)のままだと何をやっても追加できないのでフローレイアウトやボックスレイアウトにしてやる必要があります。

2.符号なし値の読み込み

FontBox(参考資料2)のソースより抜粋

//TTFDataStream.java
//FIXEDの読み込み
    public float read32Fixed() throws IOException
    {
        float retval = 0;
        retval = readSignedShort();
        retval += (readUnsignedShort()/65536);
        return retval;
    }
    public abstract short readSignedShort() throws IOException;

//ULONGの読み込み((readInt、readLongというメソッドが存在しますが、それらの符号なしの読み込みは存在しません。またreadLongは8バイトの読み込みを行います。(参考資料3)))
    public long readUnsignedInt() throws IOException
    {
        long byte1 = read();
        long byte2 = read();
        long byte3 = read();
        long byte4 = read();
        if( byte4 < 0 )
        {
            throw new EOFException();
        }
        return (byte1 << 24) + (byte2 << 16) + (byte3 << 8) + (byte4 << 0);
    }

 FIXEDの読み込みは前16bit(2バイト)を「符号あり」でとり、後ろ16bit(2バイト)を符号なしで其々取っています。後ろの方を65536で割っているのは前に説明したとおり小数点の場所をずらしていることと同義です。つまり、整数部と小数点部を取っていることになります。最後に二つ足し合わせて終わりです。主にバージョンの表示に使われています。
 符号なしの32bit(4バイト)の読み込みはオーソドックスに行われています。すなわち1バイトずつ読み込んでシフトし、合計を出しています。シフトの意味は基数で倍してやることです。例えば10進数で考えると25<<3は25000になります。(参考資料4)
 ここにはオーダーの話が入っていません。TrueTypeフォントは全てMSbです。

3.画面が欠けてしまうのを防ぐ

ツリーを展開するとマウスのところの描画がかけてしまいます。Swingのコンポーネント全体にそういった傾向が見られます。そういう時はアクションを起こした後にコンポーネントをrepaintします。

JTree tree = new JTree();
//ツリーをダブルクリックした時に呼び出されるメソッド
private void (){
 //操作
  tree.repaint();
}

4.次回

  • cmapのformat4など

TrueTypeフォントのフォーマットを調べる その6

1.ツールの作成

JTreeを使うときはDefaultMutableTreeNodeを使う。NetBeansコンポーネントを追加したら、右クリックからコードのカスタマイズ。バインドとか良く分からない。ダブルクリックのイベントを登録するには参考資料2のようにする。まずクリックのイベントを登録し、条件を判断してアクションを起こす。

            ___     ________
     ____,../     \   | |          |
    ノ   /           \ | |          |
  /   /               | |          |
 |     |::..           ...::::| |          |
 ヽ    -一ー_~、⌒)^),-、;;;;;:::/| |_________|
  ヽ ____,ノγ⌒ヽ)ニニ- ̄   | |  | 

2.サンプル


Fontをメニューバーから読み込むとTreeに展開します。メンバーをダブルクリックすると右側に値が出ます。
URL: http://www28095u.sakura.ne.jp/fonttooljava/tool02.zip


目標:

  1. im.UnsignedIntがないので実装する。じゃないと負数になる。
  2. Cmapの実装。中途半端でやめてしまった。

TrueTypeフォントのフォーマットを調べる その5

1.ツールの作成

フォントフォーマットを解析するツールを作ることにしました。

          ____        ) GUIのプログラムなんて余裕だお
        /⌒  ⌒\      ) 
      /( ●)  (●) \    )/⌒Y⌒Y⌒Y⌒Y⌒Y⌒Y⌒Y⌒Y⌒Y⌒Y⌒Y⌒Y⌒Y丶
     / ::::::⌒(__人__)⌒::::: \
    |      |r┬-|     |
     \       `ー'´     /
     ノ            \
   /´               ヽ                 カ
  |    l   l||l 从人 l||l      l||l 从人 l||l   カ    タ
  ヽ    -一''''''"~~``'ー--、   -一'''''''ー-、.     タ
   ヽ ____(⌒)(⌒)⌒) )  (⌒_(⌒)⌒)⌒))
      ┌┬┬┐┌┬┬┬┐┌┬┬┬┐┌┬┬┬┐
   ,. - ''"| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ρ ̄`l
    ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ノ ̄ ̄



3日後・・・
       ____
     /      \
   /  _ノ  ヽ、_  \    自分にはむりだお。
  /  o゚⌒   ⌒゚o  \   
  |     (__人__)    |   
  \     ` ⌒´     /    

2.完成

それでも何とか完成。

    i^,\ _,,_ /^l
    lノ  / i|l \ヾノ  こんなウンコみたいなプログラムに8時間もかかってしまったニャ。
   シ " ( ●)  (● )ミ  ソースコードは恥ずかしすぎて公開できないニャ・・・
  メ  = ⌒(__人__)⌒=ヽ
 彡           ;ミ
   ヾ         ン
   /     ""  |

zip:http://www28095u.sakura.ne.jp/fonttooljava/fonttool.zip

3.参考資料

  1. みんなのプログラミング無料講座 Java_Swing Lesson6
  2. InputStream (Java 2 プラットフォーム SE v1.4.0)
  3. SourceForge.net: FontBox
  4. ja: NetBeans 日本語サイト

TrueTypeフォントのフォーマットを調べる その4

1.最終目的

a.フォントファイルからデータを読み出して表示する。(フォントビューワー?)

ソースもあるし、がんばればできそう。
b.フォントを結合する。(合成フォント)
かぶさるグリフのどちらかを除去して、必要な値を再計算すればできる・・・かもしれない。
c.グリフデータをダイナミックに結合する(DynamicFont)
SVGFontがいつまでたっても普及しないので、必要なグリフを必要な分だけサーバーから送り、クライアント側でフォントを組み立てるということを既存のTTFでやってみる。頻繁にテーブルへのデータの追加が予想されるので途中挿入可能な可変長の配列にデータを落とし、必要に応じてストリームに変換するということを考えている。

2.ファイルの入出力

 ファイルの入出力はストリームという概念を使う。ストリームはクラスでインスタンス化して使う。ストリームは大きく分けて入力と出力、その中でバイナリファイルを読み込むかテキストファイルを読み込むかで4つに分かれる。
 基本的にストリームはファイルの先頭から末尾までの[流れ]なので途中から読み込んだり、現在位置を戻したりできない。そのようなことをしたい時は[RandomAccessFile]を利用する。またストリームの途中にデータを挿入したりすることはできない。上書きはできる。(参考資料1参照)

3.パイプ

入力と出力をつなぐ役割を持つ。中間ファイルを作成しなくても良いという点で容量と速度においてメリットがある。(参考資料2参照)

4.ファイルをバッファに格納する

ByteArrayOutputStreamを使う。大き目のファイルでも大丈夫。

5.可変長の配列

ストリームの途中に要素の追加はできないので、一度ファイルから可変長の配列(LinkedList)にデータを入れる。

6.固定小数点数(fixed-point number)

小数点部が16bitの固定小数点数を取るには以下のようにする。参考資料3のサンプルは非常に分かりやすい。

  1. in.readIntで数値を取る。
  2. Floatにキャストする。
  3. .小数点をずらす。(シフト演算子を使って(>> 16)とするのはFloatに対してはできない。Intに対して行うと小数点以下が消えてしまう。方法としては2^16=65536で割ってやる。)
((float)in.readInt())/65536;

なにか間違っている気がする。

JavaAppletで動的にフォントを読み込む 2

1.問題点

[JavaAppletで動的にフォントを読み込む1]で試したサンプルはFireFoxでエラーが出て停止する。また、アプレットのセキュリティ制限より同一ドメインの場所からしかファイルを読み込めない。

2.解決策

FireFoxでエラーが出るのはLiveConnectの部分なのでJavaScriptから直接フォント読み込み関数を動かすことをやめる。例えばファイルのURLだけをLiveConnectで送り、Appletのテキストエリア部分にフォーカスがあったときにイベントを動作させるようにしておいて、フォントを設定するようにすればいい。またアプレットのセキュリティを回避するにはJarファイルにまとめて、電子署名を施せばいい。電子署名の作成、署名方法は[Batik+JWSでサーバとの通信を行う]の記事で書いた。

JavaAppletで動的にフォントを読み込む

1.Javaのtextareaの種類

Javaに用意されている複数行入力用コンポーネントには

  1. TextArea
  2. JTextArea
  3. JTextPane
  4. JEditorPane

がある。大まかな違いは参考資料1を参照のこと。

1.TextArea

AWTの複数行入力用コンポーネント。国際化に対応していないのでフォントを宛がっても、Unocodeの文字が表示されない。(参考資料2)

2.JTextArea

Swingの複数行入力用コンポーネント。国際化に対応しているのでフォントを宛がうとUnicodeの文字もきちんと表示される。正し、宛がったフォントからしか読み込まないので、フォントにない文字(定義されていない文字)は表示されない。(参考資料3)

3.JTextPane

Swingのリッチテキストエリアコンポーネント。国際化に対応しているのでフォントを宛がうとUnicodeの文字もきちんと表示される。また、フォントにない文字も通常と同じように描画される。(参考資料4)

ここから考えて多言語入力用のアプレットを作るにはJTextPaneがベストである。

2.スクロールバー

Swingの軽量コンポーネントにはスクロールバーがついていないので自分で付ける必要がある。参考資料5,6。

3.枠線

枠線がついていないので付ける。参考資料7,8

4.リスナーの登録

リスナークラスを作成してJTextPaneに登録する。参考資料9,10,11

5.フォントファイルの読み込み

FontクラスのcreateFontメソッドを使うことでInputStreamからFontオブジェクトを作成することが出来る。(参考資料12,13)ここで参考資料14のサンプルをファイルから取るのではなく、URLから取るように変更することで動的にフォントオブジェクトを作成できる。(参考資料15,16)

6.サンプル

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=shift-jis">
<title>テスト</title>
<script language="JavaScript">
<!--
var baseURL ="http://www3.pf-x.net/~project-x/pj1/dynamicFontwithApplet/";
function setFont(obj){
	var selectNum =obj.selectedIndex;
	if(selectNum !=0){
		document.getElementById('applet').setFontfromJS(baseURL + obj[selectNum].value);
		document.getElementById('text1').value = baseURL + obj[selectNum].value;
	}
}
-->
</script>
</head>
<body>
<h1>Appletの動的フォント読み込み</h1>
<applet code="projectx.Test.class " width="300" height="200" id="applet" MAYSCRIPT>
</applet>
<br>
<select size="1" onchange="setFont(this)">
<option value="">フォントを選択してください</option>
<option value="MyaZedi06.ttf">ビルマ文字 99KB</option>
<option value="Jomolhari-alpha3c-0605331.ttf">チベット文字 2MB</option>
</select>
<br>
フォントURL<input id="text1" type="text">
<p>
下の文字をコピーして、上のアプレットのテキストエリアに貼り付けてください。(Ctrl+Vで貼り付けられます。)
<br>
ビルマ文字:&#4096;&#32;&#4097;&#32;&#4098;&#32;&#4099;&#32;&#4100;&#32;&#4101;&#32;&#4102;&#32;&#4103;&#32;&#4104;&#32;&#4105;&#32;&#4106;&#32;&#4107;&#32;&#4108;&#32;&#4109;&#32;&#4110;&#32;&#4111;
<br>
チベット文字:&#3906;&#4018;&#4013;&#3852;&#32;&#3913;&#4013;&#3852;&#32;&#3921;&#4013;&#3852;&#32;&#3938;&#4009;&#4013;&#3852;&#32;&#3930;&#4013;&#3852;&#32;&#3934;&#4013;&#3852;&#32;&#3935;&#4013;&#3852;&#32;&#3938;&#4013;&#3852;&#32;&#3939;&#4013;&#3852;&#32;&#3940;&#4013;&#3852;&#32;&#3943;&#4013;&#3852;
</p>
</body>
</html>
package projectx;
import java.awt.*;

import javax.swing.*;

import java.awt.event.*;
import java.io.*;
import java.net.URL;
//import netscape.javascript.*;


public class Test extends JApplet{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	JTextPane textpane1 = new JTextPane();
	
	
	public void init(){
		this.setSize(new Dimension(420,220));
		textpane1.setBorder(BorderFactory.createLineBorder(Color.black));
		
		keyEv keyEv1 = new keyEv();
		textpane1.addKeyListener(keyEv1);
		
		this.getContentPane().add(new JScrollPane(textpane1), BorderLayout.CENTER);
		
	}
	
	public void setFontfromJS(String urlstring){
		Font font = makeFont(urlstring);
		textpane1.setFont(font);
	}
	
    private static Font makeFont(String urlstring) {
        Font font = null;
        InputStream is = null;
        try {
        	URL url = new URL(urlstring);
            is = url.openStream();
            font = (Font.createFont(Font.TRUETYPE_FONT, is)).deriveFont(15.0f);
            is.close();
        }catch(IOException ioe) {
            ioe.printStackTrace();
            //throw new InternalError(ioe.getMessage());
        }catch(FontFormatException ffe) {
            ffe.printStackTrace();
            //throw new InternalError(ffe.getMessage());
        }finally{
            if(is!=null) {
                try {
                    is.close();
                }catch(IOException ioex) {
                    ioex.printStackTrace();
                    //throw new InternalError(ioex.getMessage());
                }
            }
        }
        return font;
    }
}

class keyEv implements KeyListener{

	public void keyPressed(KeyEvent e) {
		// TODO 自動生成されたメソッド・スタブ

	}

	public void keyReleased(KeyEvent e) {
		// TODO 自動生成されたメソッド・スタブ
	}

	public void keyTyped(KeyEvent e) {
		// TODO 自動生成されたメソッド・スタブ
        e.consume();
	}

}

サンプルページ:http://www28095u.sakura.ne.jp/dynamicFontwithApplet/1/test2.html

7.参考資料

  1. Using Text Components (The Java^(TM) Tutorials > Creating a GUI with JFC/Swing > Using Swing Components)
  2. TextArea (Java 2 Platform SE 5.0)
  3. JTextArea (Java 2 プラットフォーム SE v1.4.0)
  4. JTextPane (Java 2 プラットフォーム SE v1.4.0)
  5. スクロールペイン(JScrollPane)
  6. JScrollPane (Java 2 プラットフォーム SE v1.4.0)
  7. How to Use Borders (The Java^(TM) Tutorials > Creating a GUI with JFC/Swing > Using Swing Components)
  8. BorderFactory (Java 2 Platform SE 5.0)
  9. KeyEvent (Java 2 プラットフォーム SE v1.4.0)
  10. Java 入門 | Java Applet | AWT | リスナークラス
  11. CaretEvent (Java 2 プラットフォーム SE v1.4.0)
  12. New Features in Java 2D
  13. Font (Java 2 Platform SE 5.0)
  14. Fontをファイルから取得 - Java Swing Tips
  15. 外部ファイル読込み(java アプレット
  16. URL (Java 2 プラットフォーム SE v1.4.0)