なでしこさんでは、標準でcanvasへの描画ができる命令が備わっており、手軽にいろいろお絵かきすることが出来ます。
でも、それとは別にSVG画像の描画がしたい! と思ったワケです。
SVGとは
SVGは、ベクター形式の画像ファイル形式です。
正しくは、すけーらぶるべくたーぐらふぃっく、とか言うラシイです。
JPEGやPNGなどのラスター形式の画像は、拡大するとぼやけてきちゃったりドットが見えてきちゃいます。キャンバスの描画もコレです。
ところが、ベクター形式ってのは、どんなに拡大しても画像が劣化しないってコトなんですよ!
まあ、写真なんかは逆にアレなんですが、図形やアイコンなんかにはとてもイイですよね☆
文字なども、無駄にアンチエイリアスがかからないのでいつでもくっきり、ぼやけることがありません。
わたし的にもっと良い点として、SVGはXML形式で記述されたテキストデータなので、図形でありながらテキストは文字としてコピペしたり出来ちゃうんですよ! 素晴らしい!!
そして、キャンバスへの描画だと、ウェブフォントを使って文字描画したい時、フォントが読み込まれる前に描画が走ってしまうと当然適用されないので、読み込みが完了するまで待って描画を始めなければならないんだけど、それがめっぽうむじゅかしくてよく分かんない
でも、SVGならそんな難しいこと考えなくていいんです☆
しかも!
日本語なら縦書きしたいこと、多々ありますよね~。
ところがキャンバスへの描画では、縦書きという設定が無く、一文字一文字位置を指定して描画しなければならない上に、フォントも縦書きにはならないので括弧や句読点など、縦書きと横書きで向きや位置が異なる約物の表示がおかしくなってしまうため、実質ウェブフォントが必須です。
でも、SVGならCSSで「writing-mode」に「vertical-rl」を設定するだけで縦書きになり、フォントもちゃんと縦書きフォントが設定されるんです☆
……と、ようするに文字を縦書き描画したい! というだけの理由でSVGに興味を持ったワタクシなのでした💧
ともかくそうゆうわけでSVG画像を描画したい。例によってなでしこさんで!
キャンバスへの描画と同じ感じに、[x,y]へsをSVG文字描画
とか、[x,y]へrのSVG円描画
とかで描きたいです。
NAKO3プラグインとして作っていこうと思います。
動的に要素を作成
当初わたしはとっても甘く考えていて、DOM操作の命令群でふつーに出来るかと思ったらそうカンタンにはいかず、色々調べたらなでしこの命令では出来ないことが分かり、じゃばすくりぷとの命令を使わなきゃだったので、覚え書きです。
要素を作る
当初の考えでは、ふつーになでしこのDOM要素作成
でタグを作って、DOM属性設定
で属性を設定していけばできるかと思ったんです。
だって、SVG画像の中身って、こんなのなんです。
<svg viewBox="0 0 100 100" width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="30" fill="pink" />
<text x="50" y="10" fill="black" font-size="20px" writing-mode="vertical-rl">なでしこ</text>
</svg>
これで、ピンクの丸に「なでしこ」の文字が縦書きで入った画像になります。(もっともこれは画面のスクショでsvgじゃありませんが。画像選択の拡張子にはsvgが入っているけど、なぜかうまくアップロードできないのねん)
ですから、「svg」や「circle」や「text」などのDOM要素作成して、それに「width」「height」とか「x」「y」とか「fill」といったものをDOM属性設定すれば良さそげに思えたんですが……
……出来ませんでしたonz
DOM要素作成は、じゃばすくりぷと的に言うとdocument.createElement
ってコトらしいんですが(近頃、なでしこ3のマニュアルに各命令のソースへのリンクが付いて、こうゆうこと確認するのが超便利になっております!)SVGはdocument.createElementNS
を使って作らなきゃダメなんだって。ねーむすぺーす? 名前空間? よく分かりませんがとにかくそうゆうわけでDOM要素作成では出来ないってコトのようです。
でも、なでしこさんには、Javascriptの命令を直接実行するための命令も各種取りそろっているので大丈夫!
JSメソッド実行
を使って、こんなかんじ?
SVG=「document」の「createElementNS」を['http://www.w3.org/2000/svg','svg']でJSメソッド実行。
第一引数は呪文。svgの場合は常にコレということラシイ。
第二引数は要素種別でsvg
以外のタグ(rect
とかcircle
とかtext
とかとか)も全部これで作成出来るもよう。
属性の設定
タグに属性を設定するのも、DOM属性設定
では出来ませんでした><
setAttributte
だかsetAttributteNS
だかを使えということラシイ。
どっちよ?
とりあえず、svgタグのxmlns
属性にhttp://www.w3.org/2000/svg
を指定する場合は、setAttributte
でなければ出来ませんでした。それ以外はどっちでもできたような……?
なんか、こんなかんじ。
SVGの「setAttribute」を['xmlns','http://www.w3.org/2000/svg']でJSメソッド実行。
SVGの「setAttributeNS」を[NULL,'viewbox','0 0 300 300']でJSメソッド実行。
setAttributeNS
の第一引数は、常にnullってコトでいいらしい。
属性値の設定と取得は、要素を作成する時以外にも大いに使いますから、なでしこの命令っぽくカンタンに出来るようにしたいです。
●(svgの属性に値を|属性へ)SVG属性設定
もし、(DOM和スタイルに属性が辞書キー存在)ならば、属性=DOM和スタイル[属性]。
もし、(DOM和スタイルに値が辞書キー存在)ならば、値=DOM和スタイル[値]。
svgの「setAttributeNS」を[NULL,属性,値]でJSメソッド実行。
ここまで。
●(svgの属性を|属性から)SVG属性取得
もし、(DOM和スタイルに属性が辞書キー存在)ならば、属性=DOM和スタイル[属性]。
svgの「getAttributeNS」を[NULL,属性]でJSメソッド実行。
ここまで。
これで、
SVGの「幅」に300をSVG属性設定。
SVGの「高さ」に300をSVG属性設定。
と出来るようになったよ。やったね☆
setAttributeは別にSVG用の命令ってわけじゃないけど気にしないで!
SVGようのライブラリとして作るから、全部SVGつけちゃうもんね。
図形描画
●(xyへrの|xyに)SVG円描画
変数 [x,y]=xy。
EL=「document」の「createElementNS」を['http://www.w3.org/2000/svg','circle']でJSメソッド実行。
ELの「cx」にxをSVG属性設定。
ELの「cy」にyをSVG属性設定。
ELの「r」にrをSVG属性設定。
DOM親要素へELをDOM子要素追加。
ELを戻す。
ここまで。
これで、
[50,50]へ30のSVG円描画。
とやれば、円が描けるように☆
線(line)や四角(rect)も同様に。
四角はrxとryを属性設定するだけで角丸にできるのうれしい(キャンバスで角丸四角を描けるようにするの結構大変でした……)
楕円(ellipse)についてはだいぶ違っていて、斜めの楕円とかは描けない。
円(circle)も、なでしこ的には関係ないけどJavascriptのarcとは違って円弧は描けない。
そうゆうのは全部、path
でやることになっているっぽい。
パスの描画
このパスの描画がむじゅかしいですよよよよよ。
単にd属性を指定するなら別に簡単ですけれどね。
●(dの|dを|dで)SVGパス描画
EL=「document」の「createElementNS」を['http://www.w3.org/2000/svg','path']でJSメソッド実行。
ELの「d」にdをSVG属性設定。
DOM親要素へELをDOM子要素追加。
ELを戻す。
ここまで。
「M 0,100 L 0,0 A 100,100 0 0 1 100,100 z」でSVGパス描画。
なんのことやら、ですよね~(´д`)
ちなみに扇型になります。ワタクシなにしろ扇形が描きたいんですw
パスがうまく使えれば、他にも様々なものが描けるようになりそうです。
ベジェ曲線も描けます!
カンタンに、いろいろ描けるようにしたいなぁ~。
パスのコマンドを学ぶ
https://developer.mozilla.org/ja/docs/Web/SVG/Tutorial/Paths
コマンド | 意味 | 引数 |
---|---|---|
M | 描画開始位置へ移動 | 始点x,始点y |
L | 直線の終点座標 | 終点x,終点y |
A | 円弧 | 半径x,半径y,回転度,弧が180より大きいか,時計回りか,終点x,終点y |
Z | パスを閉じる(終点から描画開始位置へ直線を引く) | なし |
Q | 二次ベジェ曲線 | 制御点x,制御点y,終点x,終点y |
T | 二次ベジェ曲線の続き | 終点x,終点y |
C | 三次ベジェ曲線 | 制御点1x,制御点1y,制御点2x,制御点2y,終点x,終点y |
S | 三次ベジェ曲線の続き | 制御点2x,制御点2y,終点x,終点y |
扇形
ふむふむ。
Mで中点に移動して、Lで円弧の描画開始位置まで線を引いて、Aで円弧を描いて、Zでパスを閉じれば、そっから中点まで線が引っ張られると。
やってみると、arcとは違って色々めんどくさい(x_x)
円弧の描画開始位置と描画終了位置は、自分で計算しなきゃいけないのねん。またまたさいんこさいんの呪文~💧
そして、同じ始点と終点でも、時計回りか否かと、弧が180度より大きいか小さいかのフラグで、描かれる弧が4種類に分かれるというんですよ……
向きは右回りに固定するとして、終了角から開始角を引いて、180度より大きいか小さいか確認すればいいですよね。
……と思ったらただ引いただけじゃダメで、-90とかで指定された時のために角度は0から360度の間に補正したり、終了角が開始角より小さくならないように補正したり……?
[0,100]へ100で(-90をラジアン変換)から0までSVG扇描画。
// 角度はラジアン。角度0は右端。
●(中点に開始角から終了角まで半径の|中点へ半径で)SVG扇描画
中点x,中点y=中点。
開始角=開始角のラジアン角度修正。
終了角=終了角のラジアン角度修正。
半径x,半径y=半径。もし、半径y=未定義ならば、半径y=半径x。
x1=半径x*COS(開始角)+中点x。
y1=半径y*SIN(開始角)+中点y。
x2=半径x*COS(終了角)+中点x。
y2=半径y*SIN(終了角)+中点y。
回転度=0。 # 回転度
大弧=いいえ。 # 弧が180度より大きいか
もし、終了角<開始角ならば、終了角=終了角+(360をラジアン変換)
もし、終了角-開始角>(180をラジアン変換)ならば、大弧=はい。
右回転=はい。 # 時計回りか
d=「M {中点x},{中点y} L {x1},{y1} A {半径x},{半径y} {回転度} {大弧} {右回転} {x2},{y2} z 」
dでSVGパス描画。
ここまで。
●(ラジアン度の)ラジアン角度修正 //(0~360をラジアン変換)の範囲に修正
修正ラジアン度=ラジアン度%(360をラジアン変換)
もし、修正ラジアン度<0ならば、修正ラジアン度=修正ラジアン度+(360をラジアン変換)
修正ラジアン度で戻る。
ここまで。
タブンこれであってると思うんだけど……
連続パス描画
でも、パスは線や円弧やベジェ曲線を任意に組み合わせて描画できないと意味ないですよね~。
線だけなら、別々に描いて位置を合わせても、それらしく見えますが、塗る場合はダメですよね。
こうなっちゃう💧
それで、SVG連続パス描画開始
としたら、以降は描画せずにd属性へ値を追加だけしていって、SVG連続パス描画終了
で描画するようにしてみましたよ。
#連続パス描画のテスト。
親SVG=[300,300]のSVG親要素作成。
黒色にSVG線色設定。1にSVG線太さ設定。紫色にSVG塗り色設定。
SVG連続パス描画開始。
[30,30]に-180をラジアン変換から0まで20のSVG円弧描画。
[70,30]に-180をラジアン変換から0まで20のSVG円弧描画。
[90,30]から[50,90]まで[90,60]でSVG二次ベジェ曲線描画。
[50,90]から[10,30]まで[10,60]でSVG二次ベジェ曲線描画。
SVG連続パス描画終了。
文字描画
いやいや、とりあえず必要なのが文字描画ですよ。忘れかけていましたが💧
といっても、別に図形描画と変わりません。こんだけ!
●(xyへSの|xyにSを)SVG文字描画
変数 [x,y]=xy。
EL=「document」の「createElementNS」を['http://www.w3.org/2000/svg','circle']でJSメソッド実行。
ELの「x」にxをSVG属性設定。
ELの「y」にyをSVG属性設定。
DOM親要素へELをDOM子要素追加。
ELにSをテキスト設定。
ELを戻す。
ここまで。
縦書き描画はこれに、
ELの「writing-mode」に「vertical-rl」をSVG属性設定。
ELの「text-orientation」に「mixed」をSVG属性設定。
を追加するだけでできました☆
「text-orientation」は、縦書きの時の文字の向きを設定するやつで、「mixed」にすると英数は横向きになります。
キャンバスへの描画だと、縁取り付きの文字は、fillTextとstrokeTextと2回書かなきゃですが、fillとstrokeをそれぞれ属性設定するだけでいいみたい。よきー。
と思ったけど、線が標準で付いてきちゃうから、いらない場合は「none」を設定しなきゃでした!
デフォルトはnoneだからいいんだけど、図形描いた後とか注意しなきゃー……
フォントや色の設定
SVGならフォントでも色でも何でも、属性設定で描画した後からだって変えたい放題なわけですが、なでしこの描画命令と同じように、先に塗り色設定や線色設定したら以後の描画にずっとそれが適用されるようにしたい、と思いました。
それで、設定を辞書型変数にしておいて、
変数 SVG描画設定情報={
"線":{
"stroke":無色,
"stroke-width":1,
"stroke-opacity":1,
"stroke-linecap":"butt",
"stroke-linejoin":"miter",
"stroke-dasharray":"none",
"stroke-dashoffset":0,
},
"塗":{
"fill":黒色,
"fill-opacity":1,
},
"文字":{
"font-family":空,
"font-weight":"normal",
"font-size":"medium",
}
}
設定する命令では、それに設定をして、
●(Vに|Vへ)SVG塗色設定
SVG描画設定情報["塗"]["fill"]=V。
ここまで。
描画する時に、反復でまるっと設定してやる。
●(svgに設定を)SVG描画初期設定
設定を反復
属性=対象キー。値=対象。デフォルト値=SVGデフォルト値一覧[属性]。
もし、(値≠デフォルト値)かつ(値!==空)ならば、svgの属性に値をSVG属性設定。
ここまで。
ここまで。
うまく出来たことは出来たんですよ。
[100,100]のSVG親要素作成。
「pink」にSVG塗り色設定。
[50,50]へ30のSVG円描画。
黒色にSVG塗り色設定。20にSVG文字サイズ設定。
[50,10]へ「なでしこ」をSVG縦書き文字描画。
これで、さっきのこれが描画できるようになりました~☆
SVGを外せばそのままキャンバスに描画できそうな勢いで記述できるようになり、ご満悦♪
ただし、フォントの設定はCSSのように一つで複数まとめて設定できる属性はなかったので、フォントファミリーとサイズと太さは別々に設定するようにしています。
実際のところ、別の方が使いやすいのねん。
問題点
ただ、ワタクシこのとき、SVGの仕様がよく分かってなかったんです(今も分かってませんが;)
SVGって描きたい場所に描きたい物を描くだけじゃ無いんだよねー。
「symbol」に見えないように描画しておいて、「use」して任意の位置に表示したりするんだよね。
ところが、「use」は、参照元の塗り色や線色や変形は継承されて上書き出来ないんだって。
描画時にこうやって色を設定していると、後から動的に色変え出来ない事件が発生しちゃって、しばらくハマりました💧
デフォルト値と空の時は設定しないようにしたから、参照元となる画像を描画する時は塗り色や線色など全部「空」にしとけばだいじょぶなんだけど、結局後からフツーに属性設定で色設定する率高めとなってしまったのでした><
うーん、むしろやっぱりフツーに今ある要素に対して線色設定とか塗り色設定とか出来るようにした方が良かったのか……?
つづきます
ここまでで、通常の描画が一通りできるようになりました。
実際にはもっといろいろな機能を追加したりしているんですが、とりあえあずここまでの通常描画+transformの機能を入れた基本版を貯蔵庫に公開しました。
プログラム冒頭で、
!『https://n3s.nadesi.com/plain/SVG.nako3』を取り込む。
とすれば使うことが出来ます。
ファイルを分けた理由としては、途中からフィルタを作るのが面白くなってしまって色々遊んだ結果ファイルサイズが肥大しましたが、わたしがもともとやろうとしてたことには必要なさそげなので!
追加版についても、もそっと確認をしてから、アドベントの期間中に公開します。
ってゆうか、カレンダーが埋まらず書くことがピンチですと、追加機能の苦労話でムダに長期連載になりますw
カレンダー後半はまだまだいっぱい空いています~。
みなさんどうかご参加下さい🙏