🏛️

そして世界に作図ツールがまたひとつ

2024/10/16に公開

要約一行

Draw.ioみたいな作図ツールがもっと欲しいから作っている。

https://doc.no-mans-folly.com/ja/

モチベーションという名のポエム

世の中にはいい感じのカジュアルな作図ツールが少ない。ここでいうカジュアルとは、CADのような精密さは必要なく、多少座標がずれてても建物は倒壊しないし人も死なないという意味でのカジュアルである。Figmaみたいなデザインツールともまた違う。FigJamはそうかもしれない。tldrawはきっとそうだろう。言ってしまえばDraw.ioだ、そうつまりDraw.ioである。

というわけでカジュアル作図ツールの大御所と言えばやはりDraw.io(Diagram.net)だろう。何かを作図したくなってweb検索したことがある方ならきっと一度は使ったことがあるはずの、まさにカジュアル作図ツール界の金字塔。しかも無料。これでいいじゃんの代名詞。

実際にこれでいいと納得できる箇所は非常に多い。まずwebアプリではありつつもクラウド前提のサービスではないので手元の端末にただのファイルとして作成物を保存できる。SaaS全盛期の時代に逆行するような特徴であるが、自分で作ったものはまずは手元に置いておき、その上で必要ならクラウドストレージにもバックアップしておくローカルファースト方式も未だに捨て難い。境界が明白かつ最高のポータビリティを備えたファイルという形式には積み上げられた歴史が語る良さがきっとある。

次にDraw.ioは明確に「作図ツール」であることを重視していること。それを象徴するかのようなこんな一文がREADMEには書かれている。

Note, in particular, we don't have support for collaborative editing in this project. If this is important, one of the projects above is likely a better choice.

https://github.com/jgraph/drawio

なぜ共同編集系機能をサポートしないかの具体的な理由は特に述べられていないが、これは「作図ツール」としての機能に集中するというメッセージ性なのではないかと勝手に共感している。本当にそうなのかは不明なのでもしかしたらある日突然実装されるかもしれない。それはそれで悪くはないので良しとする。

Draw.io以外にも最近のリモートワーク環境を追い風に作図ツールは乱立しているのではという意見もあるかもしれないし、実際にそういうオンラインホワイトボード系サービスは多くある。しかしそういったサービスはオンラインでのコラボレーションにフォーカスしているため、純粋な作図ツールとしての機能が物足りないことが多い。わいわいとみんなで付箋を貼りながらコミュニケーションできるようなオンラインホワイトボードはまさに時代の要請であることは間違いない。しかし我々には依然として、孤独に、黙々と、作図に向き合わせてくれるツールが必要ではないのだろうか。ジャンル違いであるが孤独をテーマにした漫画の有名な一説をここに引用しておきたい。

モノを食べる時はね 誰にも邪魔されず自由で なんというか救われてなきゃあダメなんだ 独りで静かで豊かで……  漫画、「孤独のグルメ」より

目指す方向が見えてきた一方で、立ちふさがるはやはりこの問題、「Draw.ioではだめなのか?」。正直なところよほどのだめな理由はない。実はopen sourceでもopen contributeでもないという点は確かに気になるところではあるが受け入れる余地は十分にあるだろう。

Draw.ioが素晴らしいことは分かった、それで他の選択肢は?ない。

いや全くなくはないのだが、大体が「こういう用途においてはDraw.io代替」だったり「ほぼ同等あるいは上位互換かもしれないがSaaS」だったりで、「スタンドアロン汎用作図ツール」であるDraw.ioと比較できるような存在は(おそらく)ない。無料で機能制限なく利用できることまで求めたらきっと本当にない。tldrawはかなり近い位置にあるかもしれない。しかしやはり作図ツールとして見たときに物足りなさを否めない。

確かに我々にはDraw.ioがある。しかしDraw.ioしか存在しないこの世界で本当にいいのだろうか?もしかしてDraw.ioですら実は作図ツールとして見たら物足りないのではないか??そうかもしれない。現状のキングであるDraw.io以上の比較対象はないため真に物足りるラインがどこにあるのかまだ誰も知らない可能性がある。
webベースの作図ツールという世界に少なからず関わってきた経験があり、作図ツールには一家言あると自負できる人間がここに一人いる。やるしかないだろう。まずは我が為に、あわよくば同じ世界を望んでいた他の誰かに届くことも願いつつ。「スタンドアロン汎用作図ツール」と真正面から、覚悟を決めて向き合ってみようとこのプロジェクトは幕を上げた。

長々と書いたが要するに作りたいし作れる自信があるから作った、それが個人制作。

アプリケーション構成

アプリケーション本体はスタンドアロンなSPAで外部サーバに依存せず機能する。一部アイコンなどのインポート可能なアセット類は専用サーバから配信しているためオフラインで使えない機能も一部あるが、主要な機能は問題なく動く。
保存先はローカル端末なのでwebベースではあるが昔ながらのアプリケーションに近い。Google Driveにも保存先を向けられるようになっているのでオンラインを前提にデータを常にクラウドに置いておくことも可能。保存データの実態はとにかくフォルダとファイルなので後は好きなように管理してくれ方針。

インフラ周りは完全にCloudflareにぶら下がっている。domain、hosting、workersとなんでも調達できるし維持費が恐ろしく安い、というより無料枠ほとんどなんとかなる。Cloudflareさんありがとう。

https://www.cloudflare.com/

フロントエンドフレームワーク

React.jsを採用している。理由らしい理由はあまりなく、前回作った大きめの個人制作はVue.jsだったので。後述するが今回はレンダリングにおいてSVGに頼っていないため、作図ツールとしての命となるシェイプなどのレンダリングはHTMLCanvasで行うためDOMが絡んでこない。よってReact.jsでもVue.jsでも大きな差は生まれなかっただろうと思われる。
しかしながらVue.jsに比べてReact.jsの練度は圧倒的に低い状態でスタートしたのでその分の苦労は多かったように思う。重要なのはどうせHTMLCanvasの部分なんだからまぁいいだろうの精神でわりと適当に作っている可能性が否めない。やりたいことを実現するのが第一、それが複雑GUI。

状態管理ライブラリは導入せず、React.jsのcontextやstateでやりくりしている。こちらも後述するばデータ構造のコアにYjsを利用している都合、そこへさらに状態管理ライブラリを混ぜ合わせることを躊躇ったため利用を避けた。迷ったら自力でやる、これ個人開発の鉄則。

データ構造

Yjsをデータ構造のコアに採用している。共同編集を重視しないモチベーションと早速矛盾しているようにも聞こえるが、同時編集は単独利用においても非常に有用なため将来的な実装の可能性も踏まえてCRDTを採用した。

https://github.com/yjs/yjs

今のところ同時編集機能は一切実装していないものの、Yjsが提供する機能は非常に強力で実装コスト削減と面白い機能の導入に大きく貢献している。

例えばエディタ系ツールに必須なUndo/Redo機能はYjsがまさに提供しているので実装コストはほぼかかっていない。シェイプ単位のUndo/Redo実装はまだしも、テキスト編集のUndo/Redo実装を丸々気にしなくていいという恩恵は非常に大きい。なんと編集位置カーソルの復元機能まで備えているので隙がない。

他にはCRDT故に、保存したプロジェクトのファイルをマージすることができる。マージ結果の一貫性はデータ構造レベルで保証されているため、こちらもアプリ側でややこしい競合解決などは一切する必要がなく、Yjsにおんぶに抱っこ状態の機能実装となっている。マージ結果に一貫性があるとはいえその結果が期待通りかはまた別の話なので有用性には若干の疑問が残る。クラウドストレージに上げたバックアップに対して、最新状態なのか不明なローカルのファイルをマージしても事故が起こらないと考えればきっと有用なこともあると思われる。

将来的に中継サーバを用意して同時編集も導入したい気持ちはあるが、ホスト側のファイルをマスターとして更新していくような構成を想像するだけで未だ着手の目処はなく優先度も高くない。

Yjsを採用したことにはデメリットもあり、CRDTを成立させるための内部的なメタデータが残りがちになる。そこまで巨大ではないとはいえ塵も積もればがやはり気になってくる。この問題に対する根本的な良い解決は見つけられておらず、新規シートに移動したりクリーンアップボタンを押してもらったりで誤魔化している。ファイルマージなどCRDT特有の機能を諦めればそれでそれでいいのだがまだ結論は先送り中。このメタデータをさくっと削除できる点から見ると、CRDTは非同時編集なローカルファーストアプリに気軽に採用するのが実は正解なのかもしれない。

シェイプ / テキストレンダリング

100%HTMLCanvasでレンダリングしている。一番大きな理由は最近作るものがSVGに偏りがちだったためCanvas芸人に復帰したくなったから。次に大きな理由はテキスト周りを作り込むにあたってSVGがとても嫌だったから。HTMLCanvasでもテキスト周りの実装が辛いのは間違いないが、どうせ辛いなら半端にSVGに頼るよりHTMLCanvasでフルスクラッチする方がきっと面白いと己を鼓舞して出走した。Google Docという巨人だってHTMLCanvasでゴリ押してるんだから少なくとも不可能ではない。

アプリケーションに組み込んで使えるようなリッチテキストエディタライブラリもいくらか探してみたが、HTMLCanvasベースでそのようなライブラリはなく、作図ツールとして期待されるテキストエディタの挙動はどうせ汎用品に収まらないだろうと判断して早々に諦めた。フルスクラッチ!分かってたけど辛い!テキスト関連のコードはおそらくこのプロジェクトで最も難解になってしまっている箇所の1つ。

アプリケーション内の様子はこちら。インラインとブロックでの定番スタイルは実現できていると思われる。経験者ならきっと分かる地雷の宝庫、絵文字も表示できる。画像埋め込みやテキストの回り込みもやってみたい気持ちはあるものの、「作図ツール内のテキストの中にさらに画像を埋め込むのか?周辺に適当に貼り付けられるのに?」という疑念に阻まれてまだ実行には移っていない。

HTMLCanvasでテキストレンダリングをフルスクラッチした恩恵として、SVG出力を実装したときは非常に楽になった。なぜなら各テキストの具体的なレンダリング位置は全て自力で算出しているため、SVGの無慈悲な<text>要素の挙動を気にすることなく位置を数値で直接指定できるのである。もちろんCSSに頼る必要もない。みんなもやろうHTMLCanvasでテキストレンダリングフルスクラッチ。幸いにも最近はIntl関連機能が優秀なので単語分割など楽ができる場面も多い。言語特有の禁則事項たちはあまり頑張っていない。

HTMLCanvasでのテキストレンダリング実装に関する話題は無限にありそうなのでいつか別記事にまとめたい、と気持ちだけでもここに残しておこう。

もうひとつのState Management

用語が紛らわしいがいわゆるstate patternでキャンバスの状態、スクロール中だとかラインを引いているだとか、を管理している。これについては以前書いた記事の方が具体例も多くイメージしやすいと思うのでリンクだけ掲載。

https://zenn.dev/miyanokomiya/articles/ff91e4f99937e9

こだわりポイント1: シェイプスナッピング

せっかくなのでこだわりポイントをいくつか。Draw.ioでいいじゃんを散々擦っていたがそうは言っても細かく気になるポイントは多くある。特に気になるのがシェイプのスナッピング。何が気になるかを具体的に書き下すのはなんとも難しい。曖昧なまま説明するなら、「吸い付いてくれそうな部分が意外と吸い付かない」となるだろうか。特にライン。ラインはシェイプ同士を繋ぐ存在であるというおそらくダイアグラムベースの考えに引っ張られすぎて、ラインとしてのより自由で柔軟なスナッピングが得られない。

お気に入りスナッピング機能はこちら。直線を維持した状態で他の候補にスナップしてくれる機能である。このような「何かしら動き方に制約がかかった」状態で、「残された自由度の範囲でスナップする」実装は非常に頑張っている。褒めてほしい。しかしまだまだ動かない組み合わせは多いし実装もあまり綺麗になっていない。もう少しでこのあたりのうまい抽象が掴めていい感じにできそうな予感はしているもののまだ一歩届いていない。シェイプスナッピングはきっと永遠の頑張りポイントであり続けるだろう。

他にも、グリッドはDraw.ioに限らず大抵の作図ツールにおける不満ポイントの1つである。これについては以前単独記事で色々書いたのでここではリンクだけ掲載。

https://zenn.dev/miyanokomiya/articles/7603d99de2e740

こだわりポイント2: ライン → ポリゴン変換

この機能って意外とどの作図ツールにもないのではと思っている機能がこちらのライン→ポリゴン変換。本ツールのラインはもちろん2次ベジェに対応しているので多彩な形状を描くことができる。そしてそのラインの形状のままポリゴンに変換することができる。さらにラインにも戻せるので都度微調整も可能。2次ベジェの表現に限定される代わりにスナップ機能も汎用処理としてそのまま機能してくれる。

いくら汎用シェイプは色々と用意しようと今この場で欲しいあの形状がいつもそこにあるとは限らない。ならばやはり究極的には任意のポリゴンを作成できるようにして、あとは使い手に任せるが一番に違いない。

clip機能と合わせて使えばこんな表現だってできる(やや分かりにくいがちゃんと透明にくり抜かれている)。任意ポリゴンの可能性は無限大、つまりそれと組み合わさった他機能の可能性も無限大なのである。

分解するとこんなシェイプ達で構成されている。

こだわりポイント3: 多分完全互換SVG出力

すでに述べたようにシェイプレンダリングはHTMLCanvasで自前実装している。つまりレンダリングにブラックボックスは存在せず、一つ一つのレンダリング処理を「SVGで」忠実に再現することが可能となっている。「SVGで」再現可能、つまりCSSも<foreignObject>も登場しない。

しかしこれは裏を返せばHTMLCanvasとSVG両方で再現できないレンディングは導入できないという縛りにもなってしまう。今のところはそれを理由に導入を諦めた機能は出てきていない(大苦戦した機能はいくつかある)のでなんとかなっているが、もしかしたら将来どこかで諦めざるを得ない場面が出てくるかもしれない。

実際のところSVGにはフォント問題が付きまとうので厳密な完全互換かと言われると怪しい。フォント情報も埋め込んでしまえばなんとかなりそうな気はするものの、ファイル容量が激増しかねないのであまり頑張っていない。そしてそもそもフォント選択機能はまだない。フォントを読むこむだけといえばそうだがこのSVG問題とかファイルサイズでかすぎ問題とかフォントはフォントに難しいのでひたすら先延ばしして目を背けている。

その代わりというわけでもないがアプリケーションで再編集可能なメタデータを含んだSVGとしても出力できるようになっている。Draw.ioにも同様の機能がある、しかし互換性は当然ながらない。この機能の汎用性が想定よりも高く、実はアプリ内の「テンプレート機能」もこのメタデータ付きSVGファイルの仕組みをそのまま利用している。もちろん自前のテンプレートファイルとして手元にまとめておくこともできると、ファイルであることの取り回しの良さはここでも生きている。

残念ながらZennにはSVGが貼れないのでサンプルはここに貼れず。Zennさん、SVG勢は待っています。

ライセンス

ライセンスはAGPL3を採用。実はコードを公開するかどうかも含めてライセンスをどうするかはつい最近まで迷っていた。結局open sourceにしたのは、突然使えなくなるリスクが常にある状態は脱却しておくべきだと判断したため。ストレージは持っていないし運営コストなんてたかが知れているからそのリスクは非常に低いものの、作者以外からしたらコードにアクセスできて自力でアプリを立ち上げられない以上は常にそのリスクがちらついてしまう。それは我々が目指している作図ツールの姿としてはきっと間違っているし、別に非公開にしておく強い利用もない(マネタイズできそうな予感も予定もないし)ということで公開へ。そしてせっかく公開にしたのだから紹介もしておこうと書き出したのが本記事。

gitリポジトリごと公開しているのだからリリースで多少壊れたり互換が崩れたりしても究極的にはセルフで過去コードから立ち上げてなんとかできるから許してくれるよね甘えができるのではないかと密かに期待を込めている説がないこともない。

Google Driveと連携するために認証を通す小さなAPI(Cloudflare Workers)が一応存在していて、これは運営側への依存度が高く、またセキュリティ的な懸念もしたいということで引き続き非公開。

素材として定番なAWSやGoogle Cloudのアイコン類がアプリケーション内で使えるようになっているものの、こちらも当然本ライセンスの範疇にはなく各プロバイダの定める通り。

ということでリポジトリはこちら。鋭意制作進行中。

https://github.com/miyanokomiya/no-mans-folly

No-man's folly

作図を愛する人々が独りで静かで、そして自由でいられるツールであることを願って。こうして世界に作図ツールがまたひとつ。

Discussion