Monthly Archives: 8月 2023

2023-08-26 ,

Emacsの中で動く作図ツール 最近の変更点(2023年1月~8月)

Emacsの中で動く作図ツールですが、前回(2022年末)からの変更点をまとめました。

misohena/el-easydraw: Embedded drawing tool for Emacs

ちなみにインストールはpackage-vcやらstraightやらで出来るらしいです。私は普通にGitのサブモジュールとしてcloneしてload-pathを通しているだけです。

接着機能

図形(の中の点)を他の図形にくっつける(移動に追従させる)機能を追加しました。図形の間を線で結んだり(パスツールでCtrlを押しながら図形をクリック)、テキストを矩形などの中心に配置し(テキストツールでCtrlを押しながら図形をクリック)、移動してもその状態を維持し続けられます。

接着機能の使用例(注:矢印設定は現在は複数選択で一括で出来ます)
図1: 接着機能の使用例(注:矢印設定は現在は複数選択で一括で出来ます)

仕様はかなり悩みました。今のところ条件は限定されており、また、残っている問題も多いです。内部的には点接続という図形内のアンカーポイントなどの点を別の図形内の点に関連付ける仕組みで実現しています。接続関係を表すデータは、SVG内にdata属性として記録しています。コンテキストメニュー内にも接着関連のコマンドをいくつか追加しました。

変形機能の改善
  • transform属性を図形の座標に適用
  • グループの変形に対応
  • 変形方式設定を追加
  • グループ化解除時にtransform属性を子に適用
  • GUIで変形(C-t)

最低限の変形機能は出来たと思いますがまだまだ沢山の問題が残っています。

変形と一口に言っても何をどのように変えるのかはある程度選択の幅があります。図形の種類(SVG要素の種類)によっては出来ないこともあるので、そういうときにどうするかが難しいです。

変形コマンドの使用例
図2: 変形コマンドの使用例
別フレーム対応

プロパティエディタとシェイプピッカーは別フレームで表示できるようになりました。編集中にフレームにしたりウィンドウに戻したり柔軟に変更できます。メニューボタンやコンテキストメニューから操作できます。だだ、使ってみるとそれほど便利では無いなと思いました。それほど大きな図を描くわけではありませんし、フレームが開くのに少し時間がかかるというのもあるかもしれません。そもそも私はEmacsでフレームを使うことに慣れていません。

フレーム表示例
図3: フレーム表示例
カスタムシェイプにラベルを追加

ほとんど例としてでてすが、二種類ほど追加しておきました。すでにカスタムシェイプリストを編集してしまっているとリセットしないと反映されないと思います。

シェイプピッカーも色々直したいところがあります。とりあえず折りたたみ状態は保持したいところ。

手書きツールの改善
生成する点の数を大幅に削減しました。
複製機能

選択図形を複製するコマンドを追加しました。単にコピーしてペーストしてもいいのですが、Dキー一つでできます。

また、選択ツールにおいて、M-ドラッグで複製しつつ平行移動します。

M-矢印キーまたはM-S-矢印キーで複製しつつ平行移動します。S付きは10px単位で移動します。C-u 数値のプレフィックスを付けると数値のピクセル数だけ移動します。

S-ドラッグによる移動方向制限
シフトキーを押しながらドラッグする操作に対応しました。ツールによって意味は異なりますが、45度単位で移動することが多いです。
切り抜き機能
マウスで範囲を指定してドキュメント全体を切り抜く機能を追加しました。全体を平行移動してドキュメントのサイズを変更します。範囲外を自動的に削除したりは しません 。図を描いたら端が余ってしまったということが良くあるのでそういうときに使います。実は小さくするときだけでなく大きくしたいときにも使えます。
日本語化

current-language-environmentが "Japanese" の時は日本語で表示されます。 edraw-msg-file 変数で無効化できます。環境によっては文字化け等正しく表示されない場合もあります。その場合は環境のフォント設定等を見直すか、諦めて無効化してください。

日本語でメニューが表示されている様子
図4: 日本語でメニューが表示されている様子

元々全てのメッセージをedraw-msg関数で囲っていたのはこのためでした。Emacsはメッセージが多言語化されているところがほぼありません。見つかったのはチュートリアルくらいです。それがcurrent-language-environmentによって切り替わっているので、それにならいました。そもそもEmacsのdocstringには多言語に対応する仕組みが無いのは大いに不満ですね!(最低限言語を指定するメタデータがあって、ボタン一発で翻訳するとかどうだろう)

マーカーの改善

マーカー(パスに付ける矢印や丸印)の形状をある程度カスタマイズできるようにしました。プロパティエディタから変更したり、メインメニューや変数edraw-default-marker-propertiesでデフォルトを変更できます。

将来的には形状自体を増やせるようにしたいです。そのための下ごしらえも少ししました。

パスツールのM-クリック・ドラッグ操作
Alt+クリック・ドラッグでハンドルを確実にコントロールできます。ハンドルをM-ドラッグするとそのハンドルのみを移動します(反対側のハンドルは動きません)。アンカーをM-ドラッグするとそのアンカーの二つのハンドルを再作成します(スムーズ点になります)。アンカーをM-クリックするとそのアンカーの二つのハンドルを削除します(コーナー点になります)。いわゆる切り替えツールとほぼ同じです。
点の座標指定移動
パスのアンカーやハンドルのコンテキストメニューに「座標による移動コマンド」を追加しました。精密な操作が必要な場合に有用です。
座標の表示
色々な場面で座標やサイズをメッセージ出力するようにしました。
図形に対するコンテキストメニューの「設定」によく使うプロパティの変更を追加
image要素に対するhrefとtext要素に対するfont-sizeを追加しました。
テキストツールの既存テキストクリック操作

これまでクリックは単に新しくテキストを追加するだけでしたが、既存のテキストをクリックした場合はそのテキストの文字列を(ミニバッファで)入力するようにしました。これまでプロパティエディタを開いて変更していたのですが、やっぱりその方が便利かなと思いまして。既存テキストの近くに新しいテキストを追加したい場合はC-uプレフィックスを入力してからクリックしてください。強制的に追加になります。

テキストまわりはもっと沢山の設定項目が必要です。

ツールヘルプの表示
ツールの操作は気が付きにくいものが多いので、ツール切り替え時に簡単なガイドを表示するようにしました。
選択ツールのC-ドラッグ操作
Ctrl+ドラッグで範囲と重なる図形を選択、選択解除します。
複数選択図形一括操作
複数の選択図形をプロパティエディタで変更できるようにしました。また、コンテキストメニューの「設定」でも一度にプロパティ値を変更できるようにしました。
ビューサイズの改善
表示領域自動拡大の改善
ズーム時の表示領域自動拡大の上限をウィンドウサイズに対する比率で指定出来ようにしました(edraw-editor-auto-view-enlargement-max-size変数参照)。デフォルトは幅約94%、高さ約63%に設定されています。あまり大きくすると環境によっては不安定になりそうで心配しています。
edraw-set-view-size-specの単純化
ビューのサイズ指定は、単純に高さと幅を数値で指定するだけになりました。これにより誤入力を減らせます。指定の解除はビューのリセットコマンドを使用してください。

困ったら 0 や v 0 を押してください。元に戻ります。

本当はドラッグでビューサイズを変えられると良いんですけどね。右下につまみを表示しなければなりません。

キー割り当ての追加
  • 選択図形に対する操作
    • g : グループ化
    • G : グループ化解除
    • D : 複製
    • p f : fillを変更
    • p s : strokeを変更
    • p p または M-RET: プロパティエディタを開く
    • M-矢印キー または M-S-矢印キー または C-u N M-矢印キー : 複製後移動
    • TAB : 次の図形(M-]と同じ)
    • S-TAB : 前の図形(M-[と同じ)
    • C-t : インタラクティブ変形
  • i : 画像ツール
  • dvb : svg要素のviewBox属性を変更
カスタマイズ変数の追加
edraw-editor-image-scaling-factor
大きさの微調整をします。ノートPCで少し小さいと感じたので追加しました。Emacs自体が(create-image関数が)自動スケーリング機能を持っているのですがギリギリそれが働かない解像度だったようで、それとは別に用意しました。
edraw-editor-default-tool
起動したときに選択するツールです。これまでは矩形ツールだったのですが、選択ツールの方がいいかなと思ったので変更できるようにしました。
edraw-org-link-image-max-size variable
org-mode上でインライン画像表示するときの最大サイズを設定できるようにしました。ウィンドウからはみ出すのを防止できます。
SVGファイル・データ内のコメントを可能な限り維持
SVGファイルやデータ内にあるコメントを可能な限り維持するようにしました。 <!-- -*- mode: edraw -*- --> というヘッダーコメントや任意のフッターコメントを入れることを想定しています。svg要素内にあるコメントは図形の前後関係など、動作に支障を来す可能性があります。
カラーピッカーの改善
  • 最近使った色の保存
  • 最近使った色をキーで選択(C-1, C-2, ..., C-0)
  • ドラッグ時にピッカーの外に出たときの挙動を改善(境界線上の色を指定しやすくする)
  • 色テキストを追加・置換するコマンド(カラーピッカー単独での利用)の改善
    • 子フレーム対応
    • set-transient-mapによる安定したキー操作
    • edraw-color-picker-replace-or-insert-color-at-pointコマンドを追加
    • css-modeやmhtml-modeでの設定例を追加

カラーピッカーのedraw-editor以外からの利用(応用)まではなかなか手が回らずいくつか放置されていた問題がありましたが、修正したので大分使いやすくなったと思います。私は先日書いたような設定でcss等の色が書いてある部分をいつでもカラーピッカーで変更できるようになりました。他の色形式に対応したり、元の書き方を出来るだけ変えないようにする等まだ改善点は残っています。それと子フレームは環境によっては正しく動かない可能性があるので心配しています。一応子フレームを使わないようにするカスタマイズ変数もあります。

カラーピッカー自体には、後は固定のパレットの追加と他の表色系への対応を考えています。

web-modeでカラーピッカーを使っている様子
図5: web-modeでカラーピッカーを使っている様子
edrawコマンド
edraw-mode.elにedrawコマンドを追加しました。M-x edrawで新しいバッファを作成してedraw-modeを立ち上げるだけのシンプルなものです。素早く新しい図を作成したい場合に有用です。名前を付けて保存するにはC-x C-sやC-x C-wでOKですが、その時の拡張子やauto-mode-alistの設定等によってはedraw-modeが解除されてしまうのでその際は再度M-x edraw-modeしてください。
edraw-modeの改善

単体の.edraw.svgファイル(edraw-modeバッファ)を編集する際に支障がある問題を改善しました。

  • バッファが空の場合に表示されないバグを修正
  • ファイル保存時にUTF-8を強制
  • テキストを編集できてしまうバグを修正
  • (テキスト)カーソルの非表示
  • 他のモード(xml-mode等)との確実な切り替えを確認
  • メインメニューの「保存」を単にsave-bufferコマンド(C-x C-s)へ変更
  • メインメニューにxml-modeへの切り替えを追加(単にxml-modeを呼び出すだけ)

org-modeからの利用ばかり考えていて、長らくこちらはおろそかになっていました。ちょっと試す分にはedraw-modeの方が速い気がしたので手を付けました。

README
  • org-modeの非同期エクスポートを有効にしている場合の設定方法を追加
  • org-modeの通常のファイルリンクで使う方法を追加
  • SVGファイル内のコメントやmagic-mode-alistでedraw-modeを使う方法を追加(.edraw.svgという長い拡張子を使わない方法)
バグ修正
一部の環境でエディタが表示されない
librsvgのバージョンによってセーブして再び開くとエディタが表示されない不具合がありました。svg要素のxmlnsが消えてしまうのが原因でした。新しめのlibrsvgはxmlnsを厳格に解釈するようなので、その影響かもしれません。それに伴いimage要素ではhrefではなくxlink:href属性を使用するようにしました。
画像ツールのファイル名入力を改善

Macで画像ファイル名の拡張子が入力されない不具合を修正しました。

また、画像は参照元のファイルがあるディレクトリかそのサブディレクトリにあるものしか表示できません。これはlibrsvgがセキュリティ上課している制限です。

マウスカーソルのちらつきを改善
マウスカーソルが画面が更新されるたびに一瞬だけ別の形状に変わるのを出来るだけ抑制しました。

その他沢山の細かい修正があります。

2023-08-25 ,

Emacsのcss-modeやcustomize-face等でカラーピッカーを使う設定

以前(と言ってももうずいぶん前になりますが)Emacsで動くSVG実装のカラーピッカーを作りましたが、少し整えてからcss-modeやカスタマイズ機構で使うための設定を用意しました。作図エディタ本体の改良で忙しくカラーピッカー単体での利用の方はおろそかになっていました。ようやく手を付けられたので色々いじっているところです。

2023-08-25-color-picker.png

まず次のelispを導入します。

misohena/el-easydraw: Embedded drawing tool for Emacs

私は自分のemacs設定ディレクトリのサブモジュールになっていますが、package-vcやらstraightやらでも入れられるそうです。その辺り詳しいことは知りません。別にload-pathの通ったところに全部置いておけば済む話です。

それで、css-modeやmhtml-modeやらで使うには例えば次のようにします。

(2025-02-17追記: edraw-color-picker-modeとedraw-color-picker-global-modeを追加したので、それを使う場合は以下の設定は不要になりました。以下のやり方も引き続き利用可能です)

;; この辺りはパッケージ管理システムを使っていると自動的に作られているかも。
;; 一応;;;###autoload指定は入れてあるので。
(autoload 'edraw-color-picker-replace-color-at "edraw-color-picker" nil t)
(autoload 'edraw-color-picker-replace-or-insert-color-at-point "edraw-color-picker" nil t)

;; お好みでキー割り当て。
(defun my-edraw-color-picker-add-keys (map)
  ;; マウスのクリックでそこにある色名を置換。
  (define-key map [mouse-1] #'edraw-color-picker-replace-color-at)
  ;; C-c C-oでそこに色名があれば置換、無ければ挿入。
  (define-key map (kbd "C-c C-o")
              #'edraw-color-picker-replace-or-insert-color-at-point))

;; local-mapにキーを設定する関数。
(defun my-edraw-color-picker-enable ()
  (my-edraw-color-picker-add-keys (or (current-local-map)
                                      (let ((map (make-sparse-keymap)))
                                        (use-local-map map)
                                        map))))

;; お好みのモードにキー設定を追加。
(add-hook 'css-mode-hook 'my-edraw-color-picker-enable)
(add-hook 'mhtml-mode-hook 'my-edraw-color-picker-enable)
(add-hook 'web-mode-hook 'my-edraw-color-picker-enable)

EmacsのCustomize用のバッファ(customize-faceとか)で使うには次のようにします。

;; [追記:2025-02-11]この部分は不要になりました。メジャーモードから自動的に判断されます。
;; ;; Customize用のバッファではEmacsの色名を使う。
;; (defun my-edraw-color-picker-enable-for-custom-mode ()
;;   (setq-local edraw-color-picker-insert-default-color-scheme 'emacs))
;; (add-hook 'Custom-mode-hook 'my-edraw-color-picker-enable-for-custom-mode)

;; フィールドのキーマップにキー設定を追加。
;; local-mapに設定するとフィールド上では効かないので。
(with-eval-after-load "cus-edit"
  (my-edraw-color-picker-add-keys custom-field-keymap))

カラーピッカーは現在デフォルトでは子フレームで表示されるようになっていますが、環境によっては正しく表示されないかもしれません。その場合は (setq edraw-color-picker-use-frame-p nil) にするとオーバーレイを使った表示になります。それはそれで無理矢理行と行の間に差し込むのでレイアウトが崩れることもあるかもしれませんが。

カラーピッカーの大きさは edraw-color-picker-near-point-scale で変えられます。デフォルトは0.75です。

無理にポイントの近くに表示しなくても (edraw-color-picker-read-color) でミニバッファから入力した方が使いやすいような気もしますがどうでしょうね……。

今後の改良点としては:

  • 他の表色系に対応
  • 置換前の表記に出来るだけ合わせる
  • 現在値の表示

辺りでしょうか。

その前に作図エディタ側からの必要性で固定パレットが入ると思います。

基本作図エディタ本体が優先なのでいつになるかは分かりませんが、そのうち。

(2025-02-17追記: 続きをEmacs用のカラーピッカーに対する最近の変更に書きました)

2023-08-18

before-stringに別のオーバーレイのfaceが適用されない

before-stringプロパティを持つオーバーレイ(ov1)があったとします。そのオーバーレイ(ov1)を囲むように別のオーバーレイ(ov2)もあったとします。その別のオーバーレイ(ov2)がfaceプロパティを持っていた場合、そのfaceはov1のbefore-stringに影響するでしょうか。

[OV1BEFORE][OV1TEXT] OV2TEXT][OV2TEXT ov1の範囲ov2の範囲ov1のbefore-stringによって生成された部分青くハイライトするfaceが設定されている
図1: 二つのオーバーレイが重なる様子

色々試してみたのですが、なかなか影響させる方法が見つかりませんでした。

ov1のbefore-stringはov1が囲んでいるテキストの最初の文字のfaceテキストプロパティのみに影響を受けるようです(もちろんbefore-string自体にテキストプロパティが付いている場合は別です)。

これはtransient-mark-modeやhl-line-modeを使ってbefore-stringを持つオーバーレイを囲ってみればよく分かります。before-stringの部分だけハイライトされません。

Emacs Lispで再現するコードは次のようになります。

(let ((beg (point))
      (_ (insert "[OV2TEXT [OV1TEXT] OV2TEXT]"))
      (end (point)))
  (let ((ov1 (make-overlay (+ beg 9) (- end 9))))
    (overlay-put ov1 'evaporate t)
    (overlay-put ov1 'before-string "[OV1BEFORE]"))
  (let ((ov2 (make-overlay beg end)))
    (overlay-put ov2 'evaporate t)
    (overlay-put ov2 'face '(:background "#4080c0"))))

結果は次のようになります。

before-stringに他のオーバーレイのfaceプロパティが適用されない様子
図2: before-stringに他のオーバーレイのfaceプロパティが適用されない様子

色々変えて試してみました。

  • priorityプロパティを色々指定してみても変わりません。
  • ov2の範囲を色々変えても変わりません。
  • after-stringもbefore-stringと同様に影響を受けません。
  • [OV1TEXT]の先頭文字([)にfaceテキストプロパティ(実際にはfont-lock-face)を付けると、before-stringにはそのfaceが適用されます。つまり (put-text-property (+ beg 9) (+ beg 10) 'font-lock-face '(:background "red")) のように。これは回避策には利用できそうですがov2のfaceが適用されているわけではありません。ちなみにafter-stringは[OV1TEXT]の最後の文字……ではなく、その次の文字に設定したfaceが適用されます。
  • displayプロパティで表示した文字列([OV1TEXT]を置き換える)は影響を受けます。

displayプロパティは影響を受ける。その事実を知ったとき、私にはあるアイデアが浮かびました。before-stringにdisplayテキストプロパティを付けたらどうなるんだろう。つまり次のようにするわけです。

(let ((beg (point))
      (_ (insert "[OV2TEXT [OV1TEXT] OV2TEXT]"))
      (end (point)))
  (let ((ov1 (make-overlay (+ beg 9) (- end 9))))
    (overlay-put ov1 'evaporate t)
    (overlay-put ov1 'before-string
                 ;; ↓★displayテキストプロパティを設定する。
                 (propertize "_" 'display "[OV1BEFORE-DISPLAY]")))
  (let ((ov2 (make-overlay beg end)))
    (overlay-put ov2 'evaporate t)
    (overlay-put ov2 'face '(:background "#4080c0"))))

結果は何と……

before-stringに他のオーバーレイのfaceプロパティが適用されている様子
図3: before-stringに他のオーバーレイのfaceプロパティが適用されている様子

ちゃんと適用されました!

これらは一体どう解釈すれば良いのでしょうか。

まずbefore-stringに他のオーバーレイのfaceが適用されないのはバグでしょうか、意図した仕様でしょうか、それとも単に未定義動作(どうなっても文句は言えない)なだけでしょうか。前述したとおりtransient-mark-modeで範囲選択すればすぐに分かるので誰も気が付かないと言うことは無いと思うんですよね。さりとてこの挙動に何かメリットがあるのかと問われればあまり思いつきません。

一方before-stringのdisplayには効くというのはどうなのでしょうか。この挙動に頼って良いものなのでしょうか。

GNUのサイトに行ってバグトラッカーとメールのアーカイブを何度か行ったり来たりした後、嫌になって探すのを諦めました。

個人的にはどちらにも適用されるのが自然な挙動のように感じます。

今回の問題が気になったきっかけは、dired-details-rでhl-line-modeが正しく機能しなかったことです。dired-details-rでは、行末の"\n"部分にオーバーレイをかけてbefore-stringでファイルの詳細情報を表示しています1。なので、ファイルの詳細情報の部分は一切ハイライトされません。これでは現在の行をハイライトする意味がありません。

dired-details-rでhl-line-modeが正しく機能しない様子
図4: dired-details-rでhl-line-modeが正しく機能しない様子

すでに色々回避策を適用してしまったのですが、もしbefore-stringのdisplayに頼って良いのならもっとシンプルで安定したコードに出来そうです。

いやはや、Emacsのテキスト&オーバーレイプロパティまわりは何とも複雑ですね。

(追記:dired-details-rでhl-line-modeが正しく機能しない件は解決しました! ちなみにこのテクニックはall-the-icons-dired(私は色々独自に手を入れて使っています)でも有効です。あれはafter-stringでアイコンを挿入するので、そのままではhl-line-modeでアイコン部分がハイライトされません)

hl-line-modeが完全に機能するようになった様子
図5: hl-line-modeが完全に機能するようになった様子

脚注:

1

その辺りの経緯については以前に書いたと思います。たぶんEmacsでdisplayプロパティを使って改行を置き換えると非常に遅くなる件のあたり