30
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Flutterで日本語の縦書きを実現する②

Last updated at Posted at 2022-11-20

Flutterで日本語の縦書きを実現する方法 第2弾です。
前回の記事(2021年12月20日)では、TextPainter を使用して縦書き文章を描画する方法を解説しましたが、「改行に対応してない」「実装コードが複雑」といった欠点がありました。

↓ 前回の記事はこちら

そこで、今回は改良版である Wrap ウィジェットを使用した描画方法について解説したいと思います。

今回の記事(Wrapを使用する方法) 前回の記事

完成版

main.dart
import 'package:flutter/material.dart';
import 'package:untitled/tategaki.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: _MyHomePage(),
    );
  }
}

class _MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Demo Home Page'),
      ),
      body: Container(
        margin: const EdgeInsets.all(12),
        child: Tategaki(
          "吾輩は猫である。名前はまだ無い。どこで生れたかとんと見当がつかぬ。"
          "何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。\n"
          "吾輩はここで始めて人間というものを見た。しかもあとで聞くとそれは書生という人間中で一番獰悪な種族であったそうだ。"
          "この書生というのは時々我々を捕えて煮て食うという話である。しかしその当時は何という考もなかったから別段恐しいとも思わなかった。"
          "ただ彼の掌に載せられてスーと持ち上げられた時何だかフワフワした感じがあったばかりである。",
          style: const TextStyle(fontSize: 24, height: 1.0),
        ),
      ),
    );
  }
}
tategaki.dart
import 'package:flutter/material.dart';
import 'package:untitled/vertical_rotated.dart';

class Tategaki extends StatelessWidget {
  Tategaki(
    this.text, {
    this.style,
    this.space = 12,
  });

  final String text;
  final TextStyle? style;
  final double space;

  @override
  Widget build(BuildContext context) {
    final splitText = text.split("\n");
    return Row(
      textDirection: TextDirection.rtl,
      children: [
        for (var s in splitText) _textBox(s.runes),
      ],
    );
  }

  Widget _textBox(Runes runes) {
    return Wrap(
      textDirection: TextDirection.rtl,
      direction: Axis.vertical,
      children: [
        for (var rune in runes)
          Row(
            children: [
              SizedBox(width: space),
              _character(String.fromCharCode(rune)),
            ],
          )
      ],
    );
  }

  Widget _character(String char) {
    if (VerticalRotated.map[char] != null) {
      return Text(VerticalRotated.map[char]!, style: style);
    } else {
      return Text(char, style: style);
    }
  }
}
vertical_rotated.dart
class VerticalRotated {
  static const map = {
    ' ': ' ',
    '↑': '→',
    '↓': '←',
    '←': '↑',
    '→': '↓',
    '。': '︒',
    '、': '︑',
    'ー': '丨',
    '─': '丨',
    '-': '丨',
    'ー': '丨',
    '_': '丨 ',
    '−': '丨',
    '-': '丨',
    '—': '丨',
    '〜': '丨',
    '~': '丨',
    '/': '\',
    '…': '︙',
    '‥': '︰',
    '︙': '…',
    ':': '︓',
    ':': '︓',
    ';': '︔',
    ';': '︔',
    '=': '॥',
    '=': '॥',
    '(': '︵',
    '(': '︵',
    ')': '︶',
    ')': '︶',
    '[': '﹇',
    "[": '﹇',
    ']': '﹈',
    ']': '﹈',
    '{': '︷',
    '{': '︷',
    '<': '︿',
    '<': '︿',
    '>': '﹀',
    '>': '﹀',
    '}': '︸',
    '}': '︸',
    '「': '﹁',
    '」': '﹂',
    '『': '﹃',
    '』': '﹄',
    '【': '︻',
    '】': '︼',
    '〖': '︗',
    '〗': '︘',
    '「': '﹁',
    '」': '﹂',
    ',': '︐',
    '、': '︑',
  };
}

コードの解説

①文字の描画

Widget _character(String char) {
  if (VerticalRotated.map[char] != null) {
    return Text(VerticalRotated.map[char]!, style: style);
  } else {
    return Text(char, style: style);
  }
}

ここでは、赤で囲った部分(1文字分)を描画しています。

横書き時と縦書き時で向きや配置が変わるものについては、vertical_rotated.dartから描画時に置き換えています。
例)、 。 ー 「」など

②文章を縦書きで描画

Widget _textBox(Runes runes) {
  return Wrap(
    textDirection: TextDirection.rtl,
    direction: Axis.vertical,
    children: [
      for (var rune in runes)
        Row(
          children: [
            SizedBox(width: space),
            _character(String.fromCharCode(rune)),
          ],
        )
    ],
  );
}

ここでは、改行された部分を区切りとして、文章を縦書きにして描画しています。
Wrap ウィジェットを使用することで Text を縦並びにすることができます。

String 型のテキストは runes プロパティから、1文字ずつ(Unicodeコードポイント)のリストとして取得することができます。
元に戻したい場合は String.fromCharCode() を使用します。

③文章を配置

@override
Widget build(BuildContext context) {
  final splitText = text.split("\n");
  return Row(
    textDirection: TextDirection.rtl,
    children: [
      for (var s in splitText) _textBox(s.runes),
    ],
  );
}

最後に、縦書き文章を右から左(Right to Left)に配置します。
連続した複数の改行に対応したい場合は、s.isEmpty の場合に SizeBox 等で隙間を作るように分岐すると良いと思います。

30
15
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
30
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?