TL;DR;
FlutterWebでも使えるPlaintext Editorって全然存在しないこと知ってましたか?
Flutter Quill, zeflyなどなど、便利な機能がたくさんあるエディターは存在しますが、これら全部まともに使えません。
なぜなら、全部日本語の表示が崩れるからです。
今回は、日本語を入力させても絶対に表示が崩れない、かつ必ず正確に表示し、高速に動作する。
そんなエディターを開発しました。
demo
コード
このプレーンテキストエディタの強みは?
表示部分にFlutterのNative要素を用いていない
これにより、表示部分のテキストの柔軟性が大きく向上します!
これまで、Flutterで改行を含む文字の入力を受け付ける手段は、TextFieldしかありませんでした。
これは、そもそもFlutterのネイティブの要素として、TextField以外、文字入力を受け付ける手段が存在しなかったためです(逆にいうと、それぐらいTextFieldが最低限の機能を確実に実装してたとも言える)。
ですが、TextFieledには大きな問題点がありました...
TextFieldが持つ"Ideographic"な文字の表示不具合
これは、日本語に含まれる"ひらがな"や"カタカナ"、"漢字"(中国語圏も含む)がFlutter上では正しく表示できないという問題です。
具体的には、Flutterの持つ文字のレンダリングエンジンが、”文字の高さ情報を文字の形で計算”してしまうのがすっごく問題なんです。
普通にサイズが崩れるのも問題なんですけど、FlutterWebに限って、TextFieldの多段入力でえげつない崩れ方すんですよ。
実際は下のやつみたいになります。
若干、日本語のbaselineと文字列変換(TextCompose状態)のアンダーラインが、ずれています。
ちなみにこのずれ方はまだいい方で、文字サイズをもう少し大きくしたり、行間を広げたりするともっと大変なことになります。
ちなみにこの問題の前にStructStyleは無力です。
以下、この問題におけるFlutter本体のIssueとQiitaの記事です。
結論、WebViewを実装しないと実現できそうにない。
既存のTextFieldに対して、次のことを試しました。
- StructStyleの適用
- TextStyleの調整
- サイズの調整
- etc...
全く改善されそうにもありません。
ずっとずれたままです。
よくよく調べると、上のIssueにもあるように、Flutterの根底に問題がありそうなんですよ。
つまり、私たちじゃどうしようもないんです。
なら、どうするか。
文字のレンダリングエンジンをFlutterのエンジンから別のやつに載せ替えるしかない。
ここで出てくるのが、HTMLのレンダリングエンジン。
というわけです。
Flutterの基盤言語Dartには、dart:webというブラウザのAPIへ直接接続するためのラッパークラスとdart:js_interopというWebのためだけのライブラリが存在します。
これを使えば、次のことができるようになります。
- HTMLオブジェクトをFlutterのWidgetとして扱える
- オブジェクト単位でJavaScriptの相互設定ができる
- Dart -> JavaScript (JavaScriptで設定された関数の実行)
- JavaScript -> Dart(JavaScriptのイベントを検知しDartのCallBackを実行可能)
これを使えば、テキストエディタ実装できそうですよね?
しかも、HTMLのレンダーは各ブラウザが搭載しているものを使います。
ってことは、表示が崩れる責任をブラウザのせいにできます。
しかも、大半のブラウザは日本語正式対応です。
つまり勝ち確です。
HTMLへの接続
これは、dart標準ライブラリを使いました。
flutter_in_appweb_viewやwebview_flutterなど、公式、サードパーティ関係なく、HTMLを扱うライブラリはたくさん存在します。
ですが、これらは"普通のネイティブアプリにブラウザの機能を追加する"みたいなことをすルためのライブラリなので、Flutter Webとはそもそも趣向が異なります。
なぜならFlutter Webはそもそも、表示される場所が、ブラウザーなので。
そのため、制限的なインポートを使いつつ、ブラウザ専用APIのラッパーであるdart:webを使うことにしました。
他にもdart:htmlが存在します。ですが、これは最新のDartでは非推奨パッケージです。公式もdart:webへの移行を推奨しています。
成果物の説明
EditControllerクラス
これは、PlaintextEditorのためのControllerです。
使い方は、TextEditingControllerなどと同じです。
もし、PlaintextEditorを実装する場合、必ずPlaintextEditor内でこのコントローラーを受け取り、テキストコールバックを設定してください。
つまり、このコントローラーはインターフェース的な役割しか担ってません。
PlaintextEditorをライブラリとしてExportするコード
これは、コンパイルエラーを回避するためのものです。
PlaintextEditor Webにはdart:webライブラリが必要ですが、これはFlutter Webのplatformにしか存在しません。そのため、一般にコンパイルするとエラーが出ます。
そのため、存在するライブラリによって、外部に出力するコード、ライブラリを替えるために、export文を使っています。
具体的には、stubを基本読み込みの対象として、存在するライブラリによって、出力するコードを切り替えています。
PlaintextEditor Stub
これは、コンパイラーを騙すための、クラスの形を決めるためのクラスです。
いわば抽象クラスみたいなものです。
PlaintextEditor Web
実際にdart:webを使って実装しています。
PlaintextEditor Native
まだ実装していません。
最後までお読みいただきありがとうございます。
また時間ができたら、もう少しFlutter Webの問題を解決するために躓いた点と解決策をメモって書いていきます!

