Flutterには標準で縦書き(縦組み)のテキストレイアウト機能がありません。しかし、日本語アプリでは小説リーダーや和風UIなど、縦書きが必要になる場面は少なくありません。
「Flutter 縦書き」で検索して見つけたのが tategaki と kumihan の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,
)
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),
),
)
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,
)
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),
),
)
比較ポイント
| 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)],
)
tategakiの KentenStyle:
| スタイル | 表示 | スタイル | 表示 |
|---|---|---|---|
sesame |
ゴマ点 | filledCircle |
● |
circle |
○ | doubleCircle |
◎ |
filledTriangle |
▲ | triangle |
△ |
filledDiamond |
◆ | diamond |
◇ |
filledSquare |
■ | square |
□ |
filledStar |
★ | star |
☆ |
x |
× |
kumihan
青空文庫の注記記法で傍点を指定します。[#「対象テキスト」に傍点] のように書きます。
KumihanCanvas.aozora(
text: ' ここぞという箇所には'
'傍点[#「傍点」に傍点]を打つ。'
'丸い傍点[#「傍点」に丸傍点]や'
'白丸の傍点[#「傍点」に白丸傍点]もある。'
'さらに三角の傍点[#「傍点」に黒三角傍点]や'
'二重丸の傍点[#「傍点」に二重丸傍点]など、'
'種類は豊富だ。',
// ...
)
比較ポイント
| 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,
),
],
)
tategakiの線種: sideline(実線)、doubleSideline(二重線)、wavySideline(波線)、dottedSideline(点線)
kumihan
青空文庫の注記記法で傍線を指定します。
KumihanCanvas.aozora(
text: ' 傍線も引ける。'
'ここに傍線[#「ここに傍線」に傍線]を引き、'
'二重傍線[#「二重傍線」に二重傍線]で強調し、'
'波線[#「波線」に波線]で柔らかく示す。',
// ...
)
比較ポイント
| 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),
],
)
kumihan
青空文庫の注記記法で [#「数字」は縦中横] と指定します。
KumihanCanvas.aozora(
text: ' 明治42[#「42」は縦中横]年、'
'夏目漱石が「それから」を発表した。'
'西暦では1909[#「1909」は縦中横]年にあたる。',
// ...
)
比較ポイント
| 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,
)
kumihan
青空文庫の注記記法で [#割り注]...[#割り注終わり] と囲みます。
KumihanCanvas.aozora(
text: ' 本文の中に[#割り注]割り注は、本文より小さい文字で'
'二行に分けて組まれる注釈である。[#割り注終わり]'
'このように補足説明を挿入できる。',
// ...
)
比較ポイント
| tategaki | kumihan | |
|---|---|---|
| 指定方法 |
Warichu オブジェクト(位置+テキスト) |
青空文庫注記で範囲指定 |
| 分割位置 |
splitIndex で手動制御可能 |
自動分割 |
| プレースホルダ | 本文中に代替文字(★ 等)が必要 |
本文の流れにそのまま挿入 |
後半: 各ライブラリの独自機能
tategaki の独自機能
リッチテキスト(VerticalRichText)
VerticalRichText と TextSpanV を使うと、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,
)
RubySpan・KentenSpan・TatechuyokoSpan・WarichuSpan を自由に組み合わせて、複雑なテキスト装飾を実現できます。
その他の 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),
),
)
対応しているMarkdown要素:
- 見出し(h1〜h6)
- 太字・斜体
- 引用(blockquote)
- リスト(箇条書き・番号付き)
- コードブロック
- テーブル
- 水平線
- リンク・画像
テーマシステム
KumihanThemeData を変えるだけで、紙色・文字色・ルビ色・リンク色などを一括で切り替えられます。紙テクスチャの重ね合わせにも対応しています。
// 和紙風
const KumihanThemeData(
paperColor: Color(0xFFF5ECD7),
textColor: Color(0xFF4A3728),
)
// ダークモード(夜更け)
const KumihanThemeData(
paperColor: Color(0xFF1A1A2E),
textColor: Color(0xFFF5F0E8),
)
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),
),
)
前半の比較で見たように、ルビ・傍点・傍線・縦中横・割注はすべてこの青空文庫注記を通じて利用できます。
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といった使い分けが効果的でしょう。















