Misskey v13ではMFMにおいてKaTeXが使用できなくなりました。
なお、以下記事で使用したKaTeXは新しいMFM関数$[position ]
でおおよそ代替できるはずです。
この記事はFediverse Advent Calendar 2021(第二会場)の8日目の記事です!!既に25日は終わってますが書く時間が取れたので書きます。
概要
Misskey(MFM + Misskey Web Pages + AiScript)で点・をつかって三角形を描いてみた。
概要を説明すると三角関数の和であらわされた目標とする図形をAiScriptを使って生成したMFMで表現しよう! という感じです。
最初にMFMでのみやろうとしたときの物、次にPages+AiScriptでやった時の物を説明します。
MFMとは?1
MFMとはMisskey Flavored Markdownの略で、Misskeyの様々な場所で使用できる専用のマークアップ言語です。
MFM | Misskey Hubから引用してきました。簡単に言うとテキストを装飾することができます。$\KaTeX$2も使うことができ、これは今回の実装において大事です。装飾の方法についてはMisskey WebのMFMチートシートや、📃MFMアートのちょっとした技など | Misskeyなどを参考にしてください。
ここでは今回使う構文について詳しく説明したいと思います。
回転 rotate
$[rotate 回転させたいもの]
MFM関数と呼ばれるもの(たぶん)で、$[(関数名).option 対象]
といった感じで書きます。入れ子にすることで多重に作用させることもできますが、改行が入ってはダメです。
rotateには
$[rotate.deg=90 🍮]
のように、角度を度数法で指定して反時計回りに回転させることができます。
アニメーション(回転)
$[spin.left,speed=2s 🍮]
のようにかいて、対象を回転させるアニメーションをすることができます。left
をright
にすれば回転の向きが逆になり、speed=
で回転の周期を指定できます3。今回は使いませんが、x
, y
, z
といったオプションで回転軸を指定することもできます(デフォルトはz
)。
大きく
$[x2 ]
で2倍にできます。3倍、4倍もできます。
中央寄せ
<center>MisskeyでFediverseの世界が広がります。</center>
で対象を中央寄せにできます。これはMFM関数ではないので一番外側で使います。
KaTeX
MFMで数式ことKaTeXを使ったマークアップを使用することもできます。詳しくはSupported Functions - KaTeXを参照してください。KaTeXの内側では他のMFMは使えないので一番内側で使うことになります。
今回、KaTeXはスペーシングのために使うので候補となるのは\kern{distance}
のような水平方向のスペーシングと\\[distance]
による垂直方向のスペーシング(正確には改行)になります。簡潔に説明するとdistanceの分、水平/垂直方向にスペースを設けることができます。
MFMで三角形に動かしたい!
突然ですが、大体の図形は三角関数の和で表すことができます。
そんなことを考えながらdesmosをいじってたらいい感じの三角形を見つけることができました。
$$
\left(x, y \right) = \left(5 \cos t + 1.4 \cos \left(-2 t \right), 5 \sin t + 1.4 \sin \left(-2 t\right) \right)
$$
といった感じで2つの回転の和であらわされているので、先ほど説明したMFMの$[spin ]
を使えば三角形の軌跡を描けそうです!!4
ここで課題となるのは、いかにして回転の半径を指定するかです。始めは\kern{}
を使って指定していましたが、それだとこんな感じに
回転の中心同士の距離ではなく、回転体同士の間の距離を指定することになるので、周りを回ってる物に合わせてマニュアルで(つまり計算結果とは違った)半径を指定する必要があります。
そこで今回使ったのはもう一つのスペーシング\\[]
です。
これを使うことで回転体の中心に対する距離を指定することができました。
それでできたのが
これです。
回転の影響が無視できるように回転対称な文字・を使いましたが、逆の回転をさせれば絵文字を使ったバージョンもできます。
三角形を描きたい
MFMで三角形の軌跡を描くことはできました。フレームレートには上限が存在するので複雑な図形ほど描くのは難しくなります5。
そこで今度はrotateをたくさん使って点を描いてみる方法を考えてみます。
点をいっぱい描いて描写するわけなので、一つ一つ計算して手動でMFMを書いていては埒があかないので、プログラムを実行して出力するようにしてやります。
プログラミング言語はどんなものでもできそうですが、せっかくなのでMisskey上で実行したいです。なのでPagesを介してAiScriptを使ってMFMを出力しようと思います!6
Pagesとは?
PagesとはMisskeyの機能の1つで、一般的な投稿(note)よりもフォーマルで長い文書を投稿するものです。
単純にMFMを使って長い文書を(セクション分けなどもできます)投稿することもできますが、組み込みできるブロックの1つに投稿フォームがあり、Pages上からnoteを直接投稿することもできます。
さらに、Pagesには変数や変数を使った場合分けを行うことができる上に、AiScriptの変数とのやり取り7を介してAiScriptを使ってPagesを制御することさえも可能です8。
AiScriptとは?
さっきからAiScriptとやらが登場してますが、AiScriptの説明を忘れてました。AiScript(あいすくりぷと)とはMisskeyで使用することができるスクリプト言語です9。AiScriptの簡単な実行環境としては、AiScript Playgroundがあります。詳しい説明についてはAiScriptのGitHubリポジトリや2020年Misskeyの旅やAiScriptを使ってみたを参照してください。
AiScriptは活発に更新されており、仕様が大きく変わることがよくあります。最終的には最新の情報を反映していると思われるGitHubのリポジトリを参照してください。
先日変数代入の演算子が<-
から=
に、比較の演算子は=
から==
になりました。
この仕様変化によって躓いた点については後で述べます。
できたもの
これです。
そしてこれがAiScriptのソースです。
@concatStr(str1, str2){
`{str1}{str2}`
}
$content <- ""
$step <- 1
$steps <- 30
// 角度計算
@deg(stepn) {
$out <- ((stepn / steps) * 360)
out
}
@onestep(stepm, content) {
// 1段階目
content <- concatStr(content, "$[rotate.deg=")
content <- concatStr(content, deg(stepm))
content <- concatStr(content, " ")
content <- concatStr(content, "\(\\[5em]\)")
// 2段階目
content <- concatStr(content, "$[rotate.deg=")
content <- concatStr(content, ((3 * 360) - (deg(stepm) * 3)))
content <- concatStr(content, " ")
content <- concatStr(content, "\(\\[1.4em]\)・")
// end
content <- concatStr(content, "]]")
content
}
// content <- onestep(1, content)
~ #i, steps {
content <- onestep(i, content)
content <- concatStr(content, "\(\\[-7.6em]\)")
}
content <- concatStr(content, "\(\\[7em]\)")
content <- concatStr("<center>", content)
content <- concatStr(content, "</center>")
これが出力されるMFM
<center>$[rotate.deg=12 \(\\[5em]\)$[rotate.deg=1044 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=24 \(\\[5em]\)$[rotate.deg=1008 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=36 \(\\[5em]\)$[rotate.deg=972 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=48 \(\\[5em]\)$[rotate.deg=936 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=60 \(\\[5em]\)$[rotate.deg=900 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=72 \(\\[5em]\)$[rotate.deg=864 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=84 \(\\[5em]\)$[rotate.deg=828 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=96 \(\\[5em]\)$[rotate.deg=792 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=108 \(\\[5em]\)$[rotate.deg=756 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=120 \(\\[5em]\)$[rotate.deg=720 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=132 \(\\[5em]\)$[rotate.deg=684 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=144 \(\\[5em]\)$[rotate.deg=648 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=156 \(\\[5em]\)$[rotate.deg=612 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=168 \(\\[5em]\)$[rotate.deg=576 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=180 \(\\[5em]\)$[rotate.deg=540 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=192 \(\\[5em]\)$[rotate.deg=504 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=204 \(\\[5em]\)$[rotate.deg=468 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=216 \(\\[5em]\)$[rotate.deg=432 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=228 \(\\[5em]\)$[rotate.deg=396 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=240 \(\\[5em]\)$[rotate.deg=360 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=251.99999999999997 \(\\[5em]\)$[rotate.deg=324.0000000000001 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=264 \(\\[5em]\)$[rotate.deg=288 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=276 \(\\[5em]\)$[rotate.deg=252 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=288 \(\\[5em]\)$[rotate.deg=216 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=300 \(\\[5em]\)$[rotate.deg=180 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=312 \(\\[5em]\)$[rotate.deg=144 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=324 \(\\[5em]\)$[rotate.deg=108 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=336 \(\\[5em]\)$[rotate.deg=72 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=348 \(\\[5em]\)$[rotate.deg=36 \(\\[1.4em]\)・]]\(\\[-7.6em]\)$[rotate.deg=360 \(\\[5em]\)$[rotate.deg=0 \(\\[1.4em]\)・]]\(\\[-7.6em]\)\(\\[7em]\)</center>
https://misskey.io/@qw/pages/1638653884525
簡単な解説
@concatStr(str1, str2)
は2つの文字列、数字を結合させるやつです。
forループ内の\(\\[-7.6em]\)
は1段ごとのずれを吸収するためのスペーシングです。
MFMがやってるのはこんな感じのことです。
AiScript PlaygroundとMisskey WebのPagesでのAiScriptの挙動の違い
今回AiScriptでの実装作業はAiScript Playgroundで行ったのですが、ここでのAiScriptのバージョンとMisskey PagesのAiScriptのバージョンが違っていたようで10、謎のエラーがでて困ったのでここに仕様の違いをまとめておきたいと思います11。
Playgroundで書いたコード
@concatStr(str1, str2){
`{str1}{str2}`
}
$content <- ""
$step <- 1
$steps <- 30
// 角度計算
@deg(step) {
(step / steps) * 360
}
@onestep(step, content) {
// 1段階目
content <- concatStr(content, "$[rotate.deg=")
content <- concatStr(content, deg(step))
content <- concatStr(content, " ")
content <- concatStr(content, "\(\\[5em]\)")
// 2段階目
content <- concatStr(content, "$[rotate.deg=")
content <- concatStr(content, 3 * 360 - deg(step) * 3)
content <- concatStr(content, " ")
content <- concatStr(content, "\(\\[1.4em]\)・")
// end
content <- concatStr(content, "]]")
content
}
for (#i, steps) {
content <- onestep(i, content)
content <- concatStr(content, "\(\\[-7.6em]\)")
}
<: onestep(1, content)
⚠このコードは代入演算子・比較演算子に関するアップデートの結果動かなくなりました。
for
Get startedの
for(#i, 100)
は使えないです。Pagesでは
~ #i, 100
と書く必要があります。
関数の戻り値
@double(var) {
var * 2
}
はエラーが起きます。
@double(var) {
(var * 2)
}
のようにすると期待通りの挙動をします。
変数代入・比較
最近追加された仕様変更です。
Playgroundでは代入が=
、比較が==
ですが、Pagesでは<-
, =
です。
さらなる一般化と課題
さて、私たちは三角形をMFMで描写することはできました(字数の制限上30個の点しかプロットすることはできませんでしたが)。
それ以外の図形は書くことはできないのでしょうか?
簡単な例
試しにDesmosのパラメータをいじってみてAdobeのAcrobat DCのアイコンみたいなのを描いてみました。
ちょっと変ですが12、おおよそそれらしい形は出力できました。
しかし、これを描写するときに1つ問題が起きました。それは#簡単な解説で述べた1行の差異のパラメータが違った点です。このパラメータ自体あてずっぽうで推定した物なので、より考える必要がありそうです。
いくつかの問題が残っていることが判明してしまいましたが、さらなる青写真を描いてみたいと思います。
フーリエ変換を使って一般化
フーリエ変換を使うと実変数関数を三角関数の和で書き表すことができます。
三角関数で書かれた図形はMFMのrotateを使って表現することができます。
では、三角関数に限らない一般的な関数もMFMで描画することができるのではないでしょうか。
詳しい説明は3B1Bの動画に譲る13として表したい関数$f(t)$があった時に、
$$
c_n = \int_0^1 e^{-2 in\pi t} f(t) dt
$$
であらわされる係数を用いた和
$$
\sum_{n=-\infty}^{\infty} c_n e^{n 2 \pi i t}
$$
で表すことができます。$e^{i \theta} = \cos \theta + i \sin \theta$が成り立ち、MFMには$\sin, \cos$を計算する関数がある14ので、積分を離散的に計算すれば、フーリエ係数を求め、最終的にAiScript+MFMで様々な図形を表現することができるかもしれないですね!
ロードマップ
現在できてないことをまとめておきます
- Pagesを介してAiScriptに値を与える方法
- 改行の差異を吸収するパラメータの計算方法を探す
- AiScriptでのフーリエ変換の実装
あとは最新のPages環境にも追従していきたいですね。
おわり15
ここまで長々と読んでいただきありがとうございました。
追記
今更気づきましたが、PagesのMFMなら文字数制限がないですね...
というわけで作ってみました
ちょっとずれてますね...
-
Misskeyについて知っている読者を想定しています。 ↩
-
QiitaはKaTeXは使えないようですね。残念... ↩
-
$\mathbb{R}_{+}$ の必要あり。 ↩
-
この時はrotateを知りませんでした。 ↩
-
三角形を描きたい!はあくまで第1段階です。 ↩
-
こんなことができちゃうのもMisskeyの好きなところの1つです。 ↩
-
非常にややこしいですが、AiScriptの変数とPagesの変数は別物なので、Pages上でAiScriptの変数と対応付けてやる必要があります。この仕様は今後修正されるとか... ↩
-
Pagesでのユーザーからの入力をAiScriptで受け取るということも可能らしいですが、やり方が分かりませんでした。有識者教えてください。 ↩
-
Misskey PagesでのAiScriptのバージョンを知っている方がいたら教えてもらいたいです。また、先述の通りPagesに大型のアップデートが行われる可能性もあるので、この記事の情報が陳腐化するのは想像に難くないです。 ↩
-
特に、Pagesではエラーメッセージが表示されないので(Nullとしか現れない)、エラーの推測が大変でした。 ↩
-
一番下の点の事ですが、原因は解明してません。 ↩
-
面倒だしそこまでの能力もありません ↩
-
https://github.com/syuilo/aiscript/blob/master/docs/std.md ↩
-
これはQiita編集体験に関する愚痴ですが、AiScriptのコードブロックをうまく折り畳みに入れることができませんでした。VS Codeではうまくいくのに... ↩