1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flutterで縦書きを実現する2つのライブラリ — tategaki vs kumihan

1
Posted at

Flutterには標準で縦書き(縦組み)のテキストレイアウト機能がありません。しかし、日本語アプリでは小説リーダーや和風UIなど、縦書きが必要になる場面は少なくありません。

「Flutter 縦書き」で検索して見つけたのが tategakikumihan の2つです。tategakiはすでにv0.6.4まで進んでおり、短期間で10バージョンを重ねるなど活発に更新されています。一方のkumihanはv0.0.3と生まれたてですが、Markdown・HTML・青空文庫形式のパーサーやページ送りなど、ビューアー向けの機能を最初から備えています。

この記事では、両ライブラリを同じテキストで並べて比較しながら、それぞれの使い方と独自機能を紹介します。

対象読者

  • Flutterで日本語の縦書き表示を実装したい方
  • どちらのライブラリを選ぶべきか判断材料がほしい方

セットアップ

# pubspec.yaml
dependencies:
  tategaki: ^0.6.4
  kumihan: ^0.0.3
import 'package:tategaki/tategaki.dart';
import 'package:kumihan/kumihan.dart';

前半: 共通機能の比較

両ライブラリとも、日本語縦書き組版に必要な主要機能を備えています。ただしAPIの設計思想が大きく異なります。

  • tategaki: Dartオブジェクト(RubyText, Kenten 等)でプログラム的に指定
  • kumihan: 青空文庫の注記記法をテキストに埋め込んで指定

1. 基本的な縦書きテキスト

tategaki

VerticalText ウィジェットにテキストを渡すだけで縦書き表示できます。VerticalTextStyle でフォントサイズ・字間・行間を細かく制御できます。

VerticalText(
  '国境の長いトンネルを抜けると雪国であった。'
  '夜の底が白くなった。',
  style: const VerticalTextStyle(
    baseStyle: TextStyle(fontSize: 22, color: Color(0xFF2E221B)),
    characterSpacing: 4,
    lineSpacing: 18,
  ),
  maxHeight: 380,
)

01_basic_tategaki.png

maxHeight を指定すると、テキストが指定の高さを超えた場合に自動で次の行(左の列)へ折り返します。

kumihan

KumihanCanvas はキャンバスベースの組版エンジンで、ページ単位のレイアウトを行います。.aozora().markdown() などのファクトリコンストラクタで手軽にテキストを渡せます。

KumihanCanvas.aozora(
  text: '国境の長いトンネルを抜けると雪国であった。'
      '夜の底が白くなった。',
  initialSpread: KumihanSpreadMode.single,
  initialWritingMode: KumihanWritingMode.vertical,
  layout: const KumihanLayoutData(
    fontSize: 22,
    showTitle: false,
    showPageNumber: false,
  ),
  theme: const KumihanThemeData(
    paperColor: Color(0xFFFAF7F2),
    textColor: Color(0xFF2E221B),
  ),
)

02_basic_kumihan.png

kumihanは「ページ」という概念を持ち、テキストが溢れると自動で次のページに送られます。タップでページ送りも可能です。

比較ポイント

tategaki kumihan
描画単位 ウィジェット(文字単位の配置) キャンバス(ページ単位の組版)
レイアウト制御 maxHeight で折り返し ページサイズに自動フィット
組み込みやすさ 通常のWidgetとして配置 SizedBox 等でサイズ指定が必要

2. ルビ(振り仮名)

tategaki

RubyText で開始位置・文字数・ルビテキストを指定します。ルビのスタイルも個別に設定可能です。

VerticalText(
  '東京都庁舎の展望室',
  style: const VerticalTextStyle(
    baseStyle: TextStyle(fontSize: 28, color: Color(0xFF2E221B)),
    rubyStyle: TextStyle(fontSize: 12, color: Color(0xFF8B6914)),
    characterSpacing: 6,
    lineSpacing: 28,
  ),
  ruby: const [
    RubyText(startIndex: 0, length: 2, ruby: 'とうきょう'),
    RubyText(startIndex: 2, length: 2, ruby: 'とちょう'),
    RubyText(startIndex: 4, length: 1, ruby: 'しゃ'),
    RubyText(startIndex: 6, length: 2, ruby: 'てんぼう'),
    RubyText(startIndex: 8, length: 1, ruby: 'しつ'),
  ],
  maxHeight: 380,
)

03_ruby_tategaki.png

kumihan

kumihanでは青空文庫形式のルビ記法 |漢字《ふりがな》 をそのまま使えます。テキストに埋め込むだけなので直感的です。

KumihanCanvas.aozora(
  text: '|東京《とうきょう》|都庁《とちょう》|舎《しゃ》の'
      '|展望《てんぼう》|室《しつ》',
  initialSpread: KumihanSpreadMode.single,
  initialWritingMode: KumihanWritingMode.vertical,
  layout: const KumihanLayoutData(fontSize: 28, showTitle: false, showPageNumber: false),
  theme: const KumihanThemeData(
    paperColor: Color(0xFFFAF7F2),
    textColor: Color(0xFF2E221B),
    rubyColor: Color(0xFF8B6914),
  ),
)

04_ruby_kumihan.png

比較ポイント

tategaki kumihan
指定方法 RubyText オブジェクトで位置指定 青空文庫記法 |漢字《ふりがな》
柔軟性 個別にスタイル設定可能 テーマの rubyColor で一括設定
可読性 コードで明示的に管理 テキストに自然に埋め込める

3. 圏点(傍点)

文字の横に小さな記号を付けて強調する日本語特有の表現です。

tategaki

Kenten オブジェクトで位置とスタイルを指定します。13種類の圏点スタイルが用意されています。

VerticalText(
  'これは重要です',
  style: const VerticalTextStyle(
    baseStyle: TextStyle(fontSize: 24, color: Color(0xFF2E221B)),
    characterSpacing: 6,
  ),
  kenten: [Kenten(startIndex: 3, length: 2, style: KentenStyle.sesame)],
)

05_tategaki_kenten.png

tategakiの KentenStyle:

スタイル 表示 スタイル 表示
sesame ゴマ点 filledCircle
circle doubleCircle
filledTriangle triangle
filledDiamond diamond
filledSquare square
filledStar star
x ×

kumihan

青空文庫の注記記法で傍点を指定します。[#「対象テキスト」に傍点] のように書きます。

KumihanCanvas.aozora(
  text: ' ここぞという箇所には'
      '傍点[#「傍点」に傍点]を打つ。'
      '丸い傍点[#「傍点」に丸傍点]や'
      '白丸の傍点[#「傍点」に白丸傍点]もある。'
      'さらに三角の傍点[#「傍点」に黒三角傍点]や'
      '二重丸の傍点[#「傍点」に二重丸傍点]など、'
      '種類は豊富だ。',
  // ...
)

06_kumihan_kenten.png

比較ポイント

tategaki kumihan
指定方法 Kenten オブジェクト 青空文庫注記 [#「…」に傍点]
種類 13種類(enum) 9種類(傍点・丸傍点・白丸・黒三角・白三角・二重丸・蛇の目・ばつ・白ゴマ)
色の指定 テキストの色に追従 テーマの文字色に追従

4. 傍線(テキスト装飾)

文字の横に線を引いて強調する機能です。

tategaki

TextDecorationAnnotation で線種・色・太さを個別に指定できます。ルビとの組み合わせも可能です。

VerticalText(
  '東京は首都です',
  style: const VerticalTextStyle(
    baseStyle: TextStyle(fontSize: 24, color: Color(0xFF2E221B)),
    characterSpacing: 4,
  ),
  ruby: const [
    RubyText(startIndex: 0, length: 2, ruby: 'とうきょう'),
    RubyText(startIndex: 3, length: 2, ruby: 'しゅと'),
  ],
  decorations: [
    TextDecorationAnnotation(
      startIndex: 3, length: 2,
      type: TextDecorationLineType.sideline,
      color: Colors.red,
    ),
  ],
)

07_tategaki_decorations.png

tategakiの線種: sideline(実線)、doubleSideline(二重線)、wavySideline(波線)、dottedSideline(点線)

kumihan

青空文庫の注記記法で傍線を指定します。

KumihanCanvas.aozora(
  text: ' 傍線も引ける。'
      'ここに傍線[#「ここに傍線」に傍線]を引き、'
      '二重傍線[#「二重傍線」に二重傍線]で強調し、'
      '波線[#「波線」に波線]で柔らかく示す。',
  // ...
)

08_kumihan_decorations.png

比較ポイント

tategaki kumihan
指定方法 TextDecorationAnnotation 青空文庫注記 [#「…」に傍線]
線種 4種(実線・二重・波・点線) 3種(傍線・二重傍線・波線)
色の個別指定 可能(color パラメータ) 不可(テキスト色に追従)
ルビとの組み合わせ 同一文字に重ねがけ可能 可能(青空文庫記法で併用)

5. 縦中横

縦書きの中で数字やアルファベットを横向きに配置する機能です。

tategaki

autoTatechuyoko: true で2桁の数字を自動検出できるほか、Tatechuyoko で手動指定も可能です。

// 自動検出
VerticalText(
  '令和06年12月25日',
  style: const VerticalTextStyle(
    baseStyle: TextStyle(fontSize: 24),
    characterSpacing: 4,
  ),
  autoTatechuyoko: true,
)

// 手動指定
VerticalText(
  '午後03時45分',
  style: const VerticalTextStyle(
    baseStyle: TextStyle(fontSize: 24),
    characterSpacing: 4,
  ),
  tatechuyoko: const [
    Tatechuyoko(startIndex: 2, length: 2),
    Tatechuyoko(startIndex: 5, length: 2),
  ],
)

09_tategaki_tatechuyoko.png

kumihan

青空文庫の注記記法で [#「数字」は縦中横] と指定します。

KumihanCanvas.aozora(
  text: ' 明治42[#「42」は縦中横]年、'
      '夏目漱石が「それから」を発表した。'
      '西暦では1909[#「1909」は縦中横]年にあたる。',
  // ...
)

10_kumihan_tatechuyoko.png

比較ポイント

tategaki kumihan
自動検出 autoTatechuyoko: true で2桁数字を自動変換 なし(明示的に注記が必要)
手動指定 Tatechuyoko オブジェクト 青空文庫注記 [#「…」は縦中横]
4桁以上 手動指定で対応可能 注記で対応可能

6. 割注

本文中に小さな2行の注釈を挿入する日本語組版の伝統的な手法です。

tategaki

Warichu で位置と注釈テキストを指定します。splitIndex で2行の分割位置を手動制御することもできます。

VerticalText(
  '源氏物語★は日本文学の最高傑作である。',
  style: const VerticalTextStyle(
    baseStyle: TextStyle(fontSize: 24, color: Color(0xFF2E221B)),
    characterSpacing: 4,
    lineSpacing: 20,
  ),
  warichu: const [
    Warichu(startIndex: 4, length: 1, text: '紫式部著、全五十四帖'),
  ],
  maxHeight: 400,
)

11_tategaki_warichu.png

kumihan

青空文庫の注記記法で [#割り注]...[#割り注終わり] と囲みます。

KumihanCanvas.aozora(
  text: ' 本文の中に[#割り注]割り注は、本文より小さい文字で'
      '二行に分けて組まれる注釈である。[#割り注終わり]'
      'このように補足説明を挿入できる。',
  // ...
)

12_kumihan_warichu.png

比較ポイント

tategaki kumihan
指定方法 Warichu オブジェクト(位置+テキスト) 青空文庫注記で範囲指定
分割位置 splitIndex で手動制御可能 自動分割
プレースホルダ 本文中に代替文字( 等)が必要 本文の流れにそのまま挿入

後半: 各ライブラリの独自機能

tategaki の独自機能

リッチテキスト(VerticalRichText)

VerticalRichTextTextSpanV を使うと、1つのテキスト内で色・サイズ・太さを変えたり、ルビや圏点をスパン単位で適用できます。これはtategakiならではの機能です。

VerticalRichText(
  textSpan: TextSpanV(
    children: [
      TextSpanV(
        text: '吾輩',
        style: const TextStyle(
          fontSize: 28, fontWeight: FontWeight.w700, color: Color(0xFF8B2500),
        ),
      ),
      const TextSpanV(text: 'は', style: TextStyle(fontSize: 24)),
      RubySpan(text: '猫', ruby: 'ねこ',
        style: const TextStyle(fontSize: 28, color: Color(0xFF1B5E20)),
      ),
      const TextSpanV(text: 'である。名前はまだ', style: TextStyle(fontSize: 24)),
      KentenSpan(text: '無い', kentenStyle: KentenStyle.sesame,
        style: const TextStyle(fontSize: 24, color: Color(0xFF1A237E)),
      ),
      const TextSpanV(text: '。', style: TextStyle(fontSize: 24)),
    ],
  ),
  maxHeight: 420,
)

13_tategaki_richtext.png

RubySpanKentenSpanTatechuyokoSpanWarichuSpan を自由に組み合わせて、複雑なテキスト装飾を実現できます。


その他の tategaki 独自機能

  • SelectableVerticalText: テキスト選択・コピーが可能な縦書きウィジェット
  • SelectionAreaVerticalText: Flutterの SelectionArea と連携した選択機能
  • 外字(Gaiji): 画像で特殊文字を表示
  • 禁則処理: 句読点が行頭に来ないよう自動調整(enableKinsoku
  • 約物調整: 句読点の位置を半角扱いにして詰める(enableHalfWidthYakumono
  • 字下げ: indent(全行)・firstLineIndent(段落冒頭)
  • デバッググリッド: showGrid: true で文字配置のグリッドを表示

kumihan の独自機能

Markdown レンダリング

kumihanの大きな特徴は、Markdownテキストをそのまま縦書きで組版できることです。見出し・太字・引用・水平線など、一般的なMarkdown記法がサポートされています。

KumihanCanvas.markdown(
  text: '''# 走れメロス

メロスは激怒した。必ず、かの **邪智暴虐** の王を除かなければならぬと決意した。

| 登場人物 | 役割 |
|---|---|
| メロス | 主人公 |
| セリヌンティウス | 親友 |
| ディオニス | 暴君 |

> けれども邪悪に対しては、人一倍に敏感であった。

---

*太宰治*
''',
  title: '走れメロス',
  author: '太宰治',
  initialSpread: KumihanSpreadMode.single,
  initialWritingMode: KumihanWritingMode.vertical,
  layout: const KumihanLayoutData(
    fontSize: 18,
    showTitle: true,
    showPageNumber: true,
  ),
  theme: const KumihanThemeData(
    paperColor: Color(0xFFFFFDF1),
    textColor: Color(0xFF444444),
  ),
)

14_kumihan_markdown.png

対応しているMarkdown要素:

  • 見出し(h1〜h6)
  • 太字・斜体
  • 引用(blockquote)
  • リスト(箇条書き・番号付き)
  • コードブロック
  • テーブル
  • 水平線
  • リンク・画像

テーマシステム

KumihanThemeData を変えるだけで、紙色・文字色・ルビ色・リンク色などを一括で切り替えられます。紙テクスチャの重ね合わせにも対応しています。

// 和紙風
const KumihanThemeData(
  paperColor: Color(0xFFF5ECD7),
  textColor: Color(0xFF4A3728),
)

// ダークモード(夜更け)
const KumihanThemeData(
  paperColor: Color(0xFF1A1A2E),
  textColor: Color(0xFFF5F0E8),
)

15_kumihan_themes.png

KumihanThemeData のプロパティ:

プロパティ 説明
paperColor 背景色
textColor 本文の色
rubyColor ルビの色
captionColor キャプション・見出しの色
linkColor 外部リンクの色
internalLinkColor 内部リンクの色
paperTexture 紙テクスチャ画像
paperTextureOpacity テクスチャの不透明度

青空文庫形式

kumihanは青空文庫の注記記法をネイティブにサポートしています。既存の青空文庫テキストをそのまま表示できるため、日本文学の電子書籍ビューアーなどに最適です。

KumihanCanvas.aozora(
  text: '|吾輩《わがはい》は|猫《ねこ》である。'
      '名前はまだ無い。\n'
      'どこで生れたかとんと|見当《けんとう》がつかぬ。',
  title: '吾輩は猫である',
  author: '夏目漱石',
  initialSpread: KumihanSpreadMode.single,
  initialWritingMode: KumihanWritingMode.vertical,
  layout: const KumihanLayoutData(fontSize: 20, showTitle: true),
  theme: const KumihanThemeData(
    paperColor: Color(0xFFF5ECD7),
    textColor: Color(0xFF3A2E22),
  ),
)

16_kumihan_aozora.png

前半の比較で見たように、ルビ・傍点・傍線・縦中横・割注はすべてこの青空文庫注記を通じて利用できます。


KumihanController によるページ操作

KumihanController を使うと、ページ送り・表示モード切替などをプログラムから制御できます。

final controller = KumihanController();

// ページ操作
controller.next();          // 次のページ
controller.prev();          // 前のページ
controller.showPage(5);     // 5ページ目へジャンプ
controller.showFirstPage(); // 最初のページへ
controller.showLastPage();  // 最後のページへ

// 表示モード切替
controller.toggleSpread();      // 見開き ⇔ 単ページ
controller.toggleWritingMode(); // 縦書き ⇔ 横書き

// 現在の状態を取得
final snapshot = controller.snapshot;
print('${snapshot.currentPage + 1} / ${snapshot.totalPages}');

その他の kumihan 独自機能

  • 見開き表示: KumihanSpreadMode.doublePage で書籍のような2ページ見開き
  • 横書きモード: KumihanWritingMode.horizontal に切り替え可能
  • HTML パーサー: KumihanCanvas.html() でHTMLからの組版にも対応
  • 表紙ページ: includeCover: true でタイトル・著者の表紙を自動生成
  • タップハンドラ: タップ位置に応じたページ送りや独自操作のカスタマイズ
  • 日本語フォント内蔵: IPA明朝・IPAゴシック等を内蔵(Webでも動作)
  • スナップショットコールバック: onSnapshotChanged でページ遷移を監視

まとめ: どちらを選ぶか

観点 tategaki kumihan
主な用途 UIパーツとしての縦書きテキスト 書籍・文書ビューアー
描画方式 Widget単位で柔軟に配置 ページ単位のキャンバス組版
テキスト装飾 Dartオブジェクトで個別に制御 青空文庫注記 / Markdown / HTMLで記述
共通機能 ルビ・圏点・傍線・縦中横・割注 ルビ・圏点・傍線・縦中横・割注
テキスト選択 SelectableVerticalText で対応 未対応
リッチテキスト VerticalRichText でスパン単位の装飾 テキスト記法に依存
ページ管理 なし(スクロールで対応) 組み込み済み(ページ送り・見開き)
テーマ 自前でスタイル設定 KumihanThemeData で一括管理
入力形式 Dart オブジェクト テキスト(Markdown/HTML/青空文庫)

tategaki がおすすめのケース:

  • 和風UIの部分的な縦書き表示(年賀状、俳句、メニューなど)
  • ルビ・圏点・傍線などの細かいスタイル制御が必要(色・太さの個別指定)
  • テキスト選択・コピー機能が必要
  • 他のWidgetと自由に組み合わせたい
  • リッチテキストで1つのテキスト内に複数スタイルを混在させたい

kumihan がおすすめのケース:

  • 小説や文書のビューアーアプリ
  • 青空文庫のテキストを表示したい
  • Markdownコンテンツを縦書きで表示したい
  • ページ送り・見開き表示が必要
  • テーマ切替で読書体験をカスタマイズしたい

もちろん、両方を組み合わせて使うことも可能です。UIの装飾にはtategaki、長文の書籍表示にはkumihanといった使い分けが効果的でしょう。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?