Androidで縦書きを実現する

androidのTextViewは縦書きには対応していません。
縦書きを実現するためには自分で実装するしかありません。
縦書きについてさっぱり知らない状態から実装しました。
twitterでのやりとり

Togetter - 「Android縦書き」 
http://togetter.com/li/92001

誤っている箇所があれば指摘お願いします!
完成はこんな感じです。

参考にしたサイト

字体 - Wikipedia 
http://ja.wikipedia.org/wiki/%E5%AD%97%E4%BD%93

グリフがフォント1文字分、グリフがたくさん集まってフォントという単位になる

禁則処理 - Wikipedia 
http://ja.wikipedia.org/wiki/%E7%A6%81%E5%89%87%E5%87%A6%E7%90%86

行の最初に「、」「。」などの文字が配置される場合、前行の最後に表示するべきという処理
*1

ハイフネーションとは【hyphenation】 - 意味/解説/説明/定義 : IT用語辞典 
http://e-words.jp/w/E3838FE382A4E38395E3838DE383BCE382B7E383A7E383B3.html

英語版禁則処理みたいなもの
行をまたいで英単語を描画する場合ハイフンを使って繋ぐ
*2

ルビ - Wikipedia
http://ja.wikipedia.org/wiki/%E3%83%AB%E3%83%93

ふりがなに使うルビ
*3

[SOLVED] Android: Using Custom Fonts « RussenReaktor's Weblog 
http://russenreaktor.wordpress.com/2010/04/29/solved-android-using-custom-fonts/

自分で用意したフォントを使う例
assetsには1MBまでしか入りません。この例ではassetsにフォントを入れていますが、これは英字フォントのみだからこそできることです。
日本語のフォントが入っていてると1MBなんて余裕で超えちゃうので別途SDカードに入れてあげる必要があります。
他のAndroid縦書きアプリでは大抵アプリインストール後にダウンロードする仕組みになっています。

テキストの描画(FontMetrics) - Android Wiki* 
http://wikiwiki.jp/android/?%A5%C6%A5%AD%A5%B9%A5%C8%A4%CE%C9%C1%B2%E8(FontMetrics)

FontMetrixについて
Canvas.drawText()ではbaseLineを基準に描かれます。

簡易禁則入ってるソース - inuchin104がiPhoneと格闘したメモ 
http://d.hatena.ne.jp/inuchin104/20110121

ちゃんと読めていませんが、
[twitter:@inuchin] さんの作ったDirectXを使ったiPhone用縦書きコードらしいです。
ルビと簡易禁則処理がついているそうです。

Typeface | Android Developers 
http://developer.android.com/reference/android/graphics/Typeface.html

フォントのクラスです。

Paint | Android Developers 
http://developer.android.com/reference/android/graphics/Paint.html

getFontから始まるメソッドを見ておくといいです。

基本的な作り

独自Viewを作り、onDrawでCanvas#drawText()を使って文字を縦に並べていきます。
ひらがなや、漢字などのだいたいの文字の表示は大丈夫ですが、横書き用フォントなので「ー」や「、」や「ぁ」などを使った場合表示が狂った様に見えます。特別な小文字や記号などには別途設定を設けて位置を調整するようにします。
横フォント問題の解決法は縦書きビューワを作られている[twitter:@2SC1815J] さんにもご意見頂きました。

今回は横フォントの位置を調整する方法をとってみようかと思います。

実装

独自フォントのロード
Typeface typeface = Typeface.createFromFile(FONT_PATH);
Paintm paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setTextSize(FONT_SIZE);
paint.setColor(Color.BLACK);
paint.setTypeface(typeface);
canvas.drawText("こんにちは!", x, y, paint);


このコードを使えば独自フォントを使った文字列を表示する事ができます。
フォントはIPA明朝を使用しました。

IPAフォントのダウンロード || OSS iPedia 
http://ossipedia.ipa.go.jp/ipafont/index.html

こんにちはの文字は横に表示されていますが、縦書きにするために1文字ずつ描画します。

文字サイズ

テキストを縦に並べるためには1文字のサイズを取得する必要があります。
文字サイズはPaint#getFontSpacing()でピクセルサイズを取得する事ができます。
これを使えば固定ピッチで表示する事が可能です。
行間を1文字分、最初の行を1文字落とす設定で走れメロスの冒頭を表示してみました。

この状態でもけっこう様になってます。
しかし「、」「。」「っ」の位置がおかしいです。
横用フォントのため、位置がおかしく表示されます。

文字位置を調整する

文字位置調整の対象はだいたい思いつくもので

  • 記号
  • ひらがな小文字
  • カタカナ小文字
  • アルファベット半角大文字
  • アルファベット半角小文字
  • アルファベット全角大文字
  • アルファベット全角小文字

などがあります。
ひらがな・カタカナ小文字については位置の調整でいいですが、
アルファベット・記号については90度回転する必要があります。
管理が楽になるように設定用のクラスを作っておいてexcelで生成できるようにします。

public class CharSetting {
  /**
   * 文字
   */
  public final String charcter;

  /**
   * 回転角度
   */
  public final float angle;

  /**
   * xの差分
   * Paint#getFontSpacing() * xが足される
   * -0.5fが設定された場合、1/2文字分左にずれる
   */
  public final float x;

  /**
   * xの差分
   * Paint#getFontSpacing() * yが足される
   * -0.5fが設定された場合、1/2文字分上にずれる
   */
  public final float y;

  public CharSetting(String charcter, float angle, float x, float y) {
    super();
    this.charcter = charcter;
    this.angle = angle;
    this.x = x;
    this.y = y;
  }

  public static final CharSetting[] settings = {
      new CharSettings("、", 0.0f, 0.7f, -0.6f), new CharSettings("。", 0.0f, 0.7f, -0.6f),
      new CharSettings("「", 90.0f, -1.0f, -0.3f), new CharSettings("」", 90.0f, -1.0f, 0.0f),
      new CharSettings("ぁ", 0.0f, 0.1f, -0.1f), new CharSettings("ぃ", 0.0f, 0.1f, -0.1f),
      new CharSettings("ぅ", 0.0f, 0.1f, -0.1f), new CharSettings("ぇ", 0.0f, 0.1f, -0.1f),
      new CharSettings("ぉ", 0.0f, 0.1f, -0.1f), new CharSettings("っ", 0.0f, 0.1f, -0.1f),
      // 略
      new CharSettings("V", 90.0f, 0.0f, -0.1f), new CharSettings("W", 90.0f, 0.0f, -0.1f),
      new CharSettings("X", 90.0f, 0.0f, -0.1f), new CharSettings("Y", 90.0f, 0.0f, -0.1f),
      new CharSettings("Z", 90.0f, 0.0f, -0.1f), new CharSettings(":", 90.0f, 0.0f, -0.1f),
      new CharSettings(";", 90.0f, 0.0f, -0.1f), new CharSettings("/", 90.0f, 0.0f, -0.1f),
      new CharSettings("", 90.0f, 0.0f, -0.1f), new CharSettings(":", 90.0f, 0.0f, -0.1f),
      new CharSettings(";", 90.0f, 0.0f, -0.1f), new CharSettings("/", 90.0f, 0.0f, -0.1f),
      new CharSettings(".", 90.0f, 0.0f, -0.1f),
  };

  public static CharSetting getSetting(String character) {
    for (int i = 0; i < settings.length; i++) {
      if (settings[i].charcter.equals(character)) {
        return settings[i];
      }
    }
    return null;
  }
}

文字を描画する前にCharSetting#getSetting()を呼び、設定があったらその設定通りに描画します。
nullが帰ってきた場合、回転も位置調整もせずに描画します。
コードで表すとこんな感じ

CharSettings setting = CharSettings.getSetting(s);
if (setting == null) {
  canvas.drawText(s, x, y, paint);
} else {
  canvas.save();
  canvas.rotate(setting.angle, x, y);
  canvas.drawText(s, x + fontSpacing * setting.x, y + fontSpacing * setting.y, paint);
  canvas.restore();
}

また、CharSettingsをたくさん生成しているところはexcelで管理して生成できるようにしました。

CharSettings作成シート
https://spreadsheets.google.com/ccc?key=0AmA_8rkF2TwodHIyUWpMdldicVhhMGxZWVItNmQ1UkE&hl=ja&authkey=CK_XjqsK

これを使って描画したのがこれ

がんばって調整したのでそれっぽい位置に「、」や「っ」が描画されました。

android縦書き完成

あとは表示位置がおかしい文字の設定を随時追加していったり、
行末に「、」「。」などがくるよう禁則処理したり、
ルビの表示を実装したりすればいいと思います。
特にルビはデータをどのように保持するか、またパースのやり方を考えないといけないので大変かもしれません。

*1:今回は実装していません

*2:今回は実装していません

*3:今回は実装していません

*4:事前にSDカード内にフォントを用意しないとエラーで落ちます