yokogaki 0.10.2 copy "yokogaki: ^0.10.2" to clipboard
yokogaki: ^0.10.2 copied to clipboard

Flutter package for Japanese horizontal text (yokogaki) layout with ruby, kenten, warichu, and rich text support

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:yokogaki/yokogaki.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Yokogaki Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
        textTheme: GoogleFonts.notoSansJpTextTheme(),
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  TextAlignment _alignment = TextAlignment.center;

  String get _alignmentLabel {
    switch (_alignment) {
      case TextAlignment.start:
        return '天付き(左揃え)';
      case TextAlignment.center:
        return '中央揃え';
      case TextAlignment.end:
        return '地付き(右揃え)';
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Yokogaki - 横書き Demo'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              'Basic Horizontal Text',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            HorizontalText(
              text: 'これは横書きのテストです。',
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 24),
              ),
            ),
            const SizedBox(height: 32),
            const Text(
              'With Line Breaking (maxWidth: 300)',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            HorizontalText(
              text: 'これは横書きのテストです。日本語の横書きレイアウトを実装しました。禁則処理も対応しています。',
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 24),
              ),
              maxWidth: 300,
            ),
            const SizedBox(height: 32),
            const Text(
              'With Grid (Debug Mode)',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            HorizontalText(
              text: 'グリッド表示テスト',
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 32),
              ),
              showGrid: true,
            ),
            const SizedBox(height: 32),
            const Text(
              'With Punctuation (Kinsoku)',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            HorizontalText(
              text: '吾輩は猫である。名前はまだ無い。どこで生まれたか頓と見当がつかぬ。',
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 24),
              ),
              maxWidth: 350,
            ),
            const SizedBox(height: 32),
            const Text(
              'With Ruby (Furigana)',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            HorizontalText(
              text: '日本語',
              rubyList: const [
                RubyText(startIndex: 0, length: 3, ruby: 'にほんご'),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 32),
                rubyStyle: TextStyle(fontSize: 14, color: Colors.red),
              ),
            ),
            const SizedBox(height: 32),
            const Text(
              'Ruby with Line Breaking',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            HorizontalText(
              text: '漢字はとても難しいです。',
              rubyList: const [
                RubyText(startIndex: 0, length: 2, ruby: 'かんじ'), // 漢字
                RubyText(startIndex: 6, length: 1, ruby: 'むずか'), // 難
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 24),
                rubyStyle: TextStyle(fontSize: 12, color: Colors.blue),
              ),
              maxWidth: 300,
            ),
            const SizedBox(height: 32),
            const Text(
              'With Kenten (Emphasis Marks)',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            HorizontalText(
              text: '重要な部分を強調します。',
              kentenList: const [
                Kenten(startIndex: 0, length: 2, style: KentenStyle.sesame), // 重要
                Kenten(startIndex: 3, length: 2, style: KentenStyle.filledCircle), // 部分
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 28),
              ),
            ),
            const SizedBox(height: 32),
            const Text(
              'Kenten Types Showcase',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            HorizontalText(
              text: 'ゴマ 白丸 黒丸 三角 黒三角 二重丸',
              kentenList: const [
                Kenten(startIndex: 0, length: 2, style: KentenStyle.sesame),
                Kenten(startIndex: 3, length: 2, style: KentenStyle.circle),
                Kenten(startIndex: 6, length: 2, style: KentenStyle.filledCircle),
                Kenten(startIndex: 9, length: 2, style: KentenStyle.triangle),
                Kenten(startIndex: 12, length: 3, style: KentenStyle.filledTriangle),
                Kenten(startIndex: 16, length: 3, style: KentenStyle.doubleCircle),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 28),
              ),
            ),
            const SizedBox(height: 32),
            const Text(
              'Combined: Ruby + Kenten',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            HorizontalText(
              text: '重要な日本語を学びます。',
              rubyList: const [
                RubyText(startIndex: 0, length: 2, ruby: 'じゅうよう'),
                RubyText(startIndex: 3, length: 3, ruby: 'にほんご'),
                RubyText(startIndex: 7, length: 2, ruby: 'まな'),
              ],
              kentenList: const [
                Kenten(startIndex: 0, length: 2, style: KentenStyle.filledCircle),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 26),
                rubyStyle: TextStyle(fontSize: 12, color: Colors.green),
              ),
            ),
            const SizedBox(height: 32),
            const Text(
              'With Warichu (Inline Annotations)',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            HorizontalText(
              text: '本文(注釈)の例です。',
              warichuList: const [
                Warichu(startIndex: 3, length: 0, text: 'ここに注釈'),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 28),
              ),
            ),
            const SizedBox(height: 32),
            const Text(
              'Warichu with Longer Text',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            HorizontalText(
              text: '日本語の文章(説明文)があります。',
              warichuList: const [
                Warichu(startIndex: 7, length: 0, text: 'せつめいぶん'),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 28),
              ),
            ),
            const SizedBox(height: 32),
            const Text(
              'All Features Combined',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            HorizontalText(
              text: '重要(注)な文章です。',
              rubyList: const [
                RubyText(startIndex: 0, length: 2, ruby: 'じゅうよう'),
              ],
              kentenList: const [
                Kenten(startIndex: 0, length: 2, style: KentenStyle.sesame),
              ],
              warichuList: const [
                Warichu(startIndex: 3, length: 0, text: 'ちゅう'),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 26),
                rubyStyle: TextStyle(fontSize: 12, color: Colors.purple),
              ),
            ),
            const SizedBox(height: 32),
            const Text(
              'Rich Text - Multiple Styles',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            HorizontalRichText(
              span: GroupHorizontalTextSpan(
                children: [
                  SimpleHorizontalTextSpan(
                    text: 'これは',
                    style: const TextStyle(fontSize: 24, color: Colors.black),
                  ),
                  SimpleHorizontalTextSpan(
                    text: '重要',
                    style: const TextStyle(fontSize: 24, color: Colors.red, fontWeight: FontWeight.bold),
                    kentenList: const [
                      Kenten(startIndex: 0, length: 2, style: KentenStyle.filledCircle),
                    ],
                  ),
                  SimpleHorizontalTextSpan(
                    text: 'な',
                    style: const TextStyle(fontSize: 24, color: Colors.black),
                  ),
                  SimpleHorizontalTextSpan(
                    text: 'テキスト',
                    style: const TextStyle(fontSize: 24, color: Colors.blue, fontStyle: FontStyle.italic),
                  ),
                  SimpleHorizontalTextSpan(
                    text: 'です。',
                    style: const TextStyle(fontSize: 24, color: Colors.black),
                  ),
                ],
              ),
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 24),
              ),
            ),
            const SizedBox(height: 32),
            const Text(
              'Rich Text - With Ruby',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            HorizontalRichText(
              span: GroupHorizontalTextSpan(
                children: [
                  SimpleHorizontalTextSpan(
                    text: '日本語',
                    style: const TextStyle(fontSize: 28, color: Colors.black),
                    rubyList: const [
                      RubyText(startIndex: 0, length: 3, ruby: 'にほんご'),
                    ],
                  ),
                  SimpleHorizontalTextSpan(
                    text: 'は',
                    style: const TextStyle(fontSize: 28, color: Colors.black),
                  ),
                  SimpleHorizontalTextSpan(
                    text: '美しい',
                    style: const TextStyle(fontSize: 28, color: Colors.pink, fontWeight: FontWeight.bold),
                    rubyList: const [
                      RubyText(startIndex: 0, length: 3, ruby: 'うつく'),
                    ],
                  ),
                  SimpleHorizontalTextSpan(
                    text: 'です。',
                    style: const TextStyle(fontSize: 28, color: Colors.black),
                  ),
                ],
              ),
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 28),
                rubyStyle: const TextStyle(fontSize: 14, color: Colors.deepOrange),
              ),
            ),
            const SizedBox(height: 32),
            const Text(
              'Selectable Text - Drag to Select',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            const Text(
              'Tap or drag to select text. Long press to copy.',
              style: TextStyle(fontSize: 14, color: Colors.grey),
            ),
            const SizedBox(height: 16),
            SelectableHorizontalText(
              text: 'これは選択可能なテキストです。ドラッグして選択してください。',
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 24),
              ),
              maxWidth: 350,
            ),
            const SizedBox(height: 32),
            const Text(
              'Selectable Text - With Ruby',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            SelectableHorizontalText(
              text: '日本語の選択可能なテキスト',
              rubyList: const [
                RubyText(startIndex: 0, length: 3, ruby: 'にほんご'),
                RubyText(startIndex: 4, length: 4, ruby: 'せんたくかのう'),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 28),
                rubyStyle: const TextStyle(fontSize: 14, color: Colors.blue),
              ),
            ),
            const SizedBox(height: 32),
            const Text(
              'Line Spacing Test - Long Text',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            const Text(
              'Testing how kenten and ruby affect line spacing with multi-line text',
              style: TextStyle(fontSize: 14, color: Colors.grey),
            ),
            const SizedBox(height: 16),
            const Text(
              'Debug: Character indices',
              style: TextStyle(fontSize: 14, color: Colors.orange, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            HorizontalText(
              text: '01234567890123456789',
              rubyList: const [
                RubyText(startIndex: 0, length: 1, ruby: '0'),
                RubyText(startIndex: 5, length: 1, ruby: '5'),
                RubyText(startIndex: 10, length: 1, ruby: 'A'),
                RubyText(startIndex: 15, length: 1, ruby: 'F'),
              ],
              kentenList: const [
                Kenten(startIndex: 2, length: 1, style: KentenStyle.filledCircle),
                Kenten(startIndex: 7, length: 1, style: KentenStyle.filledCircle),
                Kenten(startIndex: 12, length: 1, style: KentenStyle.filledCircle),
                Kenten(startIndex: 17, length: 1, style: KentenStyle.filledCircle),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 24, color: Colors.grey),
                rubyStyle: const TextStyle(fontSize: 12, color: Colors.red),
                lineSpacing: 16.0,
              ),
              maxWidth: 400,
            ),
            const SizedBox(height: 16),
            const Text(
              '1. Kenten only:',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            HorizontalText(
              text: '吾輩は猫である。名前はまだ無い。どこで生まれたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。',
              kentenList: const [
                Kenten(startIndex: 0, length: 2, style: KentenStyle.sesame), // 吾輩
                Kenten(startIndex: 8, length: 2, style: KentenStyle.filledCircle), // 名前
                Kenten(startIndex: 19, length: 4, style: KentenStyle.sesame), // 生まれた
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 24),
                lineSpacing: 16.0,
              ),
              maxWidth: 400,
            ),
            const SizedBox(height: 24),
            const Text(
              '2. Ruby only:',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            HorizontalText(
              text: '吾輩は猫である。名前はまだ無い。どこで生まれたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。',
              rubyList: const [
                RubyText(startIndex: 0, length: 2, ruby: 'わがはい'), // 吾輩
                RubyText(startIndex: 3, length: 1, ruby: 'ねこ'), // 猫
                RubyText(startIndex: 8, length: 2, ruby: 'なまえ'), // 名前
                RubyText(startIndex: 27, length: 2, ruby: 'けんとう'), // 見当
                RubyText(startIndex: 37, length: 2, ruby: 'うすぐら'), // 薄暗
                RubyText(startIndex: 63, length: 2, ruby: 'きおく'), // 記憶
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 24),
                rubyStyle: const TextStyle(fontSize: 12, color: Colors.red),
                lineSpacing: 16.0,
              ),
              maxWidth: 400,
            ),
            const SizedBox(height: 24),
            const Text(
              '3. Ruby + Kenten combined:',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            HorizontalText(
              text: '吾輩は猫である。名前はまだ無い。どこで生まれたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。',
              rubyList: const [
                RubyText(startIndex: 0, length: 2, ruby: 'わがはい'), // 吾輩
                RubyText(startIndex: 3, length: 1, ruby: 'ねこ'), // 猫
                RubyText(startIndex: 8, length: 2, ruby: 'なまえ'), // 名前
                RubyText(startIndex: 27, length: 2, ruby: 'けんとう'), // 見当
                RubyText(startIndex: 37, length: 2, ruby: 'うすぐら'), // 薄暗
                RubyText(startIndex: 63, length: 2, ruby: 'きおく'), // 記憶
              ],
              kentenList: const [
                Kenten(startIndex: 0, length: 2, style: KentenStyle.sesame), // 吾輩
                Kenten(startIndex: 8, length: 2, style: KentenStyle.filledCircle), // 名前
                Kenten(startIndex: 27, length: 2, style: KentenStyle.sesame), // 見当
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 24),
                rubyStyle: const TextStyle(fontSize: 12, color: Colors.red),
                lineSpacing: 16.0,
              ),
              maxWidth: 400,
            ),
            const SizedBox(height: 32),
            const Text(
              'Text Alignment (地付き)',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            Text(
              'Current: $_alignmentLabel',
              style: const TextStyle(fontSize: 16, color: Colors.grey),
            ),
            const SizedBox(height: 16),
            Row(
              mainAxisAlignment: MainAxisAlignment.start,
              children: [
                ElevatedButton(
                  onPressed: () {
                    setState(() {
                      _alignment = TextAlignment.start;
                    });
                  },
                  style: ElevatedButton.styleFrom(
                    backgroundColor: _alignment == TextAlignment.start
                        ? Theme.of(context).colorScheme.primary
                        : null,
                  ),
                  child: const Text('天付き'),
                ),
                const SizedBox(width: 8),
                ElevatedButton(
                  onPressed: () {
                    setState(() {
                      _alignment = TextAlignment.center;
                    });
                  },
                  style: ElevatedButton.styleFrom(
                    backgroundColor: _alignment == TextAlignment.center
                        ? Theme.of(context).colorScheme.primary
                        : null,
                  ),
                  child: const Text('中央'),
                ),
                const SizedBox(width: 8),
                ElevatedButton(
                  onPressed: () {
                    setState(() {
                      _alignment = TextAlignment.end;
                    });
                  },
                  style: ElevatedButton.styleFrom(
                    backgroundColor: _alignment == TextAlignment.end
                        ? Theme.of(context).colorScheme.primary
                        : null,
                  ),
                  child: const Text('地付き'),
                ),
              ],
            ),
            const SizedBox(height: 16),
            Container(
              decoration: BoxDecoration(
                border: Border.all(color: Colors.grey),
                color: Colors.grey.shade50,
              ),
              child: HorizontalText(
                text: '吾輩は猫である。\n名前はまだ無い。\nどこで生まれたか\n頓と見当がつかぬ。',
                style: HorizontalTextStyle(
                  baseStyle: const TextStyle(fontSize: 24),
                  alignment: _alignment,
                ),
                maxWidth: 350,
              ),
            ),
            const SizedBox(height: 16),
            const SizedBox(
              width: 500,
              child: Text(
                '説明:\n'
                '• 天付き:各行を左端に揃えます\n'
                '• 中央揃え:各行を中央に配置します\n'
                '• 地付き:各行を右端に揃えます',
                style: TextStyle(fontSize: 14, color: Colors.grey),
              ),
            ),
            const SizedBox(height: 32),
            const Text(
              'Text Decorations (下線・上線)',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            const Text(
              '1. Underline Types:',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            Row(
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text('下線', style: TextStyle(fontSize: 12, color: Colors.grey)),
                      HorizontalText(
                        text: '重要な文章',
                        decorationList: const [
                          TextDecorationAnnotation(
                            startIndex: 0,
                            length: 2,
                            type: TextDecorationLineType.underline,
                          ),
                        ],
                        style: HorizontalTextStyle(
                          baseStyle: const TextStyle(fontSize: 24),
                        ),
                      ),
                    ],
                  ),
                ),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text('二重下線', style: TextStyle(fontSize: 12, color: Colors.grey)),
                      HorizontalText(
                        text: '重要な文章',
                        decorationList: const [
                          TextDecorationAnnotation(
                            startIndex: 0,
                            length: 2,
                            type: TextDecorationLineType.doubleUnderline,
                          ),
                        ],
                        style: HorizontalTextStyle(
                          baseStyle: const TextStyle(fontSize: 24),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text('波下線', style: TextStyle(fontSize: 12, color: Colors.grey)),
                      HorizontalText(
                        text: '重要な文章',
                        decorationList: const [
                          TextDecorationAnnotation(
                            startIndex: 0,
                            length: 2,
                            type: TextDecorationLineType.wavyUnderline,
                          ),
                        ],
                        style: HorizontalTextStyle(
                          baseStyle: const TextStyle(fontSize: 24),
                        ),
                      ),
                    ],
                  ),
                ),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text('点線下線', style: TextStyle(fontSize: 12, color: Colors.grey)),
                      HorizontalText(
                        text: '重要な文章',
                        decorationList: const [
                          TextDecorationAnnotation(
                            startIndex: 0,
                            length: 2,
                            type: TextDecorationLineType.dottedUnderline,
                          ),
                        ],
                        style: HorizontalTextStyle(
                          baseStyle: const TextStyle(fontSize: 24),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
            const SizedBox(height: 24),
            const Text(
              '2. Overline Types:',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            Row(
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text('上線', style: TextStyle(fontSize: 12, color: Colors.grey)),
                      HorizontalText(
                        text: '重要な文章',
                        decorationList: const [
                          TextDecorationAnnotation(
                            startIndex: 0,
                            length: 2,
                            type: TextDecorationLineType.overline,
                          ),
                        ],
                        style: HorizontalTextStyle(
                          baseStyle: const TextStyle(fontSize: 24),
                        ),
                      ),
                    ],
                  ),
                ),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text('波上線', style: TextStyle(fontSize: 12, color: Colors.grey)),
                      HorizontalText(
                        text: '重要な文章',
                        decorationList: const [
                          TextDecorationAnnotation(
                            startIndex: 0,
                            length: 2,
                            type: TextDecorationLineType.wavyOverline,
                          ),
                        ],
                        style: HorizontalTextStyle(
                          baseStyle: const TextStyle(fontSize: 24),
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
            const SizedBox(height: 24),
            const Text(
              '3. Colored Decoration:',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            HorizontalText(
              text: '赤い下線と青い下線の例',
              decorationList: const [
                TextDecorationAnnotation(
                  startIndex: 0,
                  length: 2,
                  type: TextDecorationLineType.underline,
                  color: Colors.red,
                  thickness: 2.0,
                ),
                TextDecorationAnnotation(
                  startIndex: 5,
                  length: 2,
                  type: TextDecorationLineType.underline,
                  color: Colors.blue,
                  thickness: 2.0,
                ),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 24),
              ),
            ),
            const SizedBox(height: 24),
            const Text(
              '4. Combined with Ruby:',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            HorizontalText(
              text: '東京は首都です',
              rubyList: const [
                RubyText(startIndex: 0, length: 2, ruby: 'とうきょう'),
                RubyText(startIndex: 3, length: 2, ruby: 'しゅと'),
              ],
              decorationList: const [
                TextDecorationAnnotation(
                  startIndex: 3,
                  length: 2,
                  type: TextDecorationLineType.underline,
                  color: Colors.red,
                ),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 28),
                rubyStyle: const TextStyle(fontSize: 14, color: Colors.green),
              ),
            ),
            const SizedBox(height: 16),
            const Text(
              '5. Ruby + Overline:',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            HorizontalText(
              text: '重要な言葉を強調',
              rubyList: const [
                RubyText(startIndex: 0, length: 2, ruby: 'じゅうよう'),
                RubyText(startIndex: 3, length: 2, ruby: 'ことば'),
                RubyText(startIndex: 6, length: 2, ruby: 'きょうちょう'),
              ],
              decorationList: const [
                TextDecorationAnnotation(
                  startIndex: 0,
                  length: 2,
                  type: TextDecorationLineType.overline,
                  color: Colors.blue,
                ),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 28),
                rubyStyle: const TextStyle(fontSize: 14, color: Colors.purple),
              ),
            ),
            const SizedBox(height: 16),
            const Text(
              '6. Ruby + Wavy Overline:',
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            HorizontalText(
              text: '日本語の勉強は楽しい',
              rubyList: const [
                RubyText(startIndex: 0, length: 3, ruby: 'にほんご'),
                RubyText(startIndex: 4, length: 2, ruby: 'べんきょう'),
                RubyText(startIndex: 7, length: 2, ruby: 'たの'),
              ],
              decorationList: const [
                TextDecorationAnnotation(
                  startIndex: 4,
                  length: 2,
                  type: TextDecorationLineType.wavyOverline,
                  color: Colors.orange,
                ),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 28),
                rubyStyle: const TextStyle(fontSize: 14, color: Colors.teal),
              ),
            ),
            const SizedBox(height: 32),
            const Text(
              'SelectionArea Integration (Selection API)',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            const Text(
              'Use with SelectionArea to select across multiple widgets',
              style: TextStyle(fontSize: 14, color: Colors.grey),
            ),
            const SizedBox(height: 16),
            Container(
              padding: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                border: Border.all(color: Colors.indigo.shade200),
                borderRadius: BorderRadius.circular(8),
              ),
              child: SelectionArea(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const SelectableText(
                      '通常の SelectableText',
                      style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(height: 8),
                    SizedBox(
                      height: 60,
                      child: SelectionAreaHorizontalText(
                        text: '日本語の横書きテキスト',
                        rubyList: const [
                          RubyText(startIndex: 0, length: 3, ruby: 'にほんご'),
                          RubyText(startIndex: 4, length: 3, ruby: 'よこがき'),
                        ],
                        style: HorizontalTextStyle(
                          baseStyle: const TextStyle(fontSize: 24, color: Colors.black),
                          rubyStyle: const TextStyle(fontSize: 12, color: Colors.red),
                        ),
                      ),
                    ),
                    const SizedBox(height: 8),
                    const SelectableText(
                      '上下のテキストをドラッグで一括選択できます。',
                      style: TextStyle(fontSize: 14, color: Colors.grey),
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 32),
            const Text(
              'Gaiji (外字 - Custom Character Images)',
              style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            const Text(
              'Replace placeholder characters with custom images. Example: 挿 → 插 (old form)',
              style: TextStyle(fontSize: 14, color: Colors.grey),
            ),
            const SizedBox(height: 16),
            HorizontalText(
              text: '「〓入」の旧字体の例',
              gaijiList: const [
                Gaiji(
                  startIndex: 1, // 「〓」の位置
                  image: AssetImage('assets/image.png'),
                ),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 28),
              ),
            ),
            const SizedBox(height: 16),
            const Text(
              'Long text with gaiji in middle:',
              style: TextStyle(fontSize: 14, color: Colors.grey),
            ),
            const SizedBox(height: 8),
            HorizontalText(
              text: '日本語の文章の中に〓入という旧字体を使った例文です。',
              gaijiList: const [
                Gaiji(
                  startIndex: 9, // 「〓」の位置(0から数えて9番目)
                  image: AssetImage('assets/image.png'),
                ),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 28),
              ),
              showGrid: true,
            ),
            const SizedBox(height: 16),
            const Text(
              'Multi-line text (maxWidth: 300):',
              style: TextStyle(fontSize: 14, color: Colors.grey),
            ),
            const SizedBox(height: 8),
            HorizontalText(
              text: '昔々あるところに、〓入という技術を持つ職人がいました。その職人は毎日丁寧に仕事をしていました。',
              gaijiList: const [
                Gaiji(
                  startIndex: 9, // 「〓」の位置(0から数えて9番目)
                  image: AssetImage('assets/image.png'),
                ),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 24),
              ),
              maxWidth: 300,
              showGrid: true,
            ),
            const SizedBox(height: 16),
            const Text(
              'Small font (18px):',
              style: TextStyle(fontSize: 14, color: Colors.grey),
            ),
            const SizedBox(height: 8),
            HorizontalText(
              text: 'この文章には〓入という外字が含まれています。',
              gaijiList: const [
                Gaiji(
                  startIndex: 6, // 「〓」の位置
                  image: AssetImage('assets/image.png'),
                ),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 18),
              ),
              showGrid: true,
            ),
            const SizedBox(height: 16),
            const Text(
              'Gaiji after line break (2nd line):',
              style: TextStyle(fontSize: 14, color: Colors.grey),
            ),
            const SizedBox(height: 8),
            HorizontalText(
              text: 'あいうえおかきくけこ〓入さしすせそ', // 〓は10番目
              gaijiList: const [
                Gaiji(
                  startIndex: 10, // 「〓」の位置(0から数えて10番目)
                  image: AssetImage('assets/image.png'),
                ),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 28),
              ),
              maxWidth: 200, // 狭いmaxWidthで強制的に改行
              showGrid: true,
            ),
            const SizedBox(height: 16),
            const Text(
              'Gaiji on 3rd line:',
              style: TextStyle(fontSize: 14, color: Colors.grey),
            ),
            const SizedBox(height: 8),
            HorizontalText(
              text: 'あいうえおかきくけこさしすせそたちつてと〓入なにぬねの', // 〓は20番目
              gaijiList: const [
                Gaiji(
                  startIndex: 20, // 「〓」の位置(0から数えて20番目)
                  image: AssetImage('assets/image.png'),
                ),
              ],
              style: HorizontalTextStyle(
                baseStyle: const TextStyle(fontSize: 24),
              ),
              maxWidth: 200, // 狭いmaxWidthで複数行に
              showGrid: true,
            ),
            const SizedBox(height: 32),
          ],
        ),
      ),
    );
  }
}
1
likes
160
points
237
downloads

Publisher

verified publisherpub.nyapic.com

Weekly Downloads

Flutter package for Japanese horizontal text (yokogaki) layout with ruby, kenten, warichu, and rich text support

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, kinsoku

More

Packages that depend on yokogaki