ベクター形式でお絵描きをしてみる 【JavaScript】

Python

ベクター形式(SVG 形式)でのお絵描きをトライします。お絵描きツールごと JavaScript で自作することにします。
実質的に、無料のドロー系ソフトの作成です。
インターネットやサーバーなどの環境がなくてもベクター画像が作成できるよう意図しています。

以下の環境で動作確認をしています。
環境: Windows パソコン、Microsoft Edge ブラウザ

背景 ~ ベクター形式でお絵描きトライ!

以前に、ラスター形式(PNG 形式など)でお絵描きができる Web アプリについてプログラムをまとめ、公開しています(下記の関連リンク参照)。
とはいえ、ラスター形式は画像のデータ量が多くなりがちで、拡大するとジャギーになったりします。図形を再利用、再編集しようとすると、図形の作り直しという無駄が発生しやすいです。

一方、ベクター形式(SVG 形式など)が扱えるようになると、データ量を削減したり、画質の改善が可能になります。作った図形は、後から色や位置を変えて使いまわすことが可能となり、グラデーションをつけたり、アニメーションにすることも可能です。

ベクター形式の描画では、Adobe のイラストレーターがよく知られています。とはいえ、保存ファイルの仕様が非公開で中身がブラックボックスです。また、図形を詳細にカスタマイズしたいとなると、操作が複雑化してしまって使いこなすのも大変です。
ベクトル描画でやっているのは、けっきょくのところ、ブラウザでいえば SVG フォーマットの数値の設定程度です。図形の座標がベクトルとして並んでいる程度です。ベクター形式の代表といえる SVG フォーマットは、仕様が公開されています。すると、アプリごと自作することも可能なはずです。

ということで、ベクター形式のアプリごと作成し、お絵描きを試みることにします。
アプリの画面上では、描画をすると、SVG のソースを自動生成するようにします。SVG のソースは、直接編集することで図形に反映できるようにします。
いくらか、プログラミングや数学がわかるのであれば(アプリごとに異なる複雑な GUI 画面を覚えるよりも)、直接、SVG ファイルのソースを編集できるようにしてしまったほうが話が早いではないか、ということです。
ベクター形式の図形をブラウザ上で作成、編集できるようにしてしまえば、ブラウザ自体は多くのデバイスで動きます。すると、パソコンの OS 環境やイラストレータなどの束縛から逃れることができ、特定の企業の環境等にトラップされずに済みます(Adobe税の節税効果が期待できる)。
ということで、プログラミング言語は JavaScript とし、ソースコードも公開しておきます。

SVG 形式での作図が自由自在になると、Web サイト用のちょっとした図形やマップ、動画を作ることが可能になります。
作成した図形やアイコンを Web 上のボタンにすることも可能です。
また、今回の要素技術をサーバーや他のプログラムの機能と組み合わせることで、応用範囲も格段に広がることになります。

スクリプトの設定手順

設定手順は以下のとおりです。
① 以下を参考にパソコン内に、ベクター画像を扱うためのフォルダを作ります。
例: C\user\vector_web_app1
② ①のフォルダの中に vector_web_app1.html という名前でテキストファイルを作り、下記のサンプルスクリプトを貼りつけて保存します。
動かし方
③ ②のファイルをダブルクリックしてください。ウェブアプリが起動したら設定まで完了です。

使い方

使い方の概要

まずは、以下の画面上でマウスでドラッグし、お絵描きをしてみてください。

・ 画面右側の白色の描画領域でドラッグ操作をすることで、図形を描画できます。
マウスボタンを押し下げた座標から描画をスタートし、マウスボタンを上げた地点で描画を実行します(原則)。
・ 画面左側で詳細な設定が可能です。左側上部の欄で描画するオブジェクトを選択できます。
line(直線)、rectangle(長方形)、circle (円)など、図形を設定すると描画が可能です。
・ 図形をすべて消したいときは、[clear] ボタンをクリックしてください。
・ [undo] ボタンをクリックすると、直前に描画した状態に戻ります。ただし、保存されるデータは2つまでとしており、クリックし続けると何もデータのない初期画面に戻ります。
・ [save svg] ボタンをクリックすると、描画した図形を SVG 形式で保存できます。
・ [save png] ボタンをクリックすると、PNG 形式で保存できます。

描画の仕組みと詳細

ベクターデータの直接編集が可能!

・ 描画領域の下側にテキストボックスがあり、描画する図形のタグをテキストで入力、表示できるようにしてあります。
[draw] ボタンをクリックすると、テキストボックス欄に書かれた内容を <svg> </svg> タグでくくって、描画欄に差し込み、表示する仕組みになっています。
・ 描画領域でマウス操作をすると、テキストボックス欄に SVG のタグが追加されていくようになっています。
マウスドラッグを終えたタイミングで、[draw] ボタンに相当するコマンドを実行し、SVG のスクリプト欄に書かれたタグに対応する図形が画面に表示されるようにしています。
・ SVG ファイルを保存する場合も同様に、テキストボックスに書かれた内容を <svg></svg> タグでくくって SVG ファイル形式で保存するようにしています。
・ SVG のタグは、描画の順に追加され、重ね書きされる動きとなっています。イラストレーター Illustrator などでのレイヤーに対応しています。したがって、タグの順番を手作業で編集すると、図形の描画順(レイヤー、重ね書きの順番)を変更することができます。
・ 最後に描画した図形(実際はタグ)を消したいときは、[delete last tag] ボタンをクリックしてください。テキストボックスの末尾の1行が削除されるようにしています。
・ たとえば、長方形を描くと、色の設定やクリックした座標がそのまま保存されていることがわかると思います。図形を描画して、テキストボックス欄で、色(#xxxxxx )の指定部分を変更し、[draw] ボタンをクリックしてみてください。また、数値を変更してみてください。図形の色や位置、形状などを変更、微調整できます。
なお、パソコンの表示での座標系は、一般に、画面左上を原点とし、画面右方向に +X 軸、画面下方向に +Y 軸を取ります。実際に数値を編集してみると座標系を理解できると思います。

SVGファイルの内容について

・ 保存した SVG ファイルは、ダブルクリックすることでブラウザに表示できます。また、テキストエディタで開くと、過去に作成した図形を編集、再利用することが可能です。色や大きさを変えて流用することも可能です。
・ たとえば、保存した SVG ファイルで、<svg>…</svg> タグで囲まれた 「… 」の部分を、今回のプログラムのテキストボックス欄に貼りつけ、[draw] ボタンをクリックしてみてください。図形を表示できます。必要な図形(タグ)の要素だけを残し、新たな図形を追加するなど、図形のカスタマイズが可能です。
・ また、生成した SVG データは、任意の HTML から呼び出したり、スクリプトに貼りつけて表示させるなど、使いまわしが可能です。
・ また、HTML 内にボタン <button>…</button> を設置し、「…」の部分に今回の SVG データを差し込むことで、ホームページのボタンのデザインを、自作のアイコン化することも可能です。

操作画面について

・ 画面の “stroke” 欄は、図形の外形のラインの色の設定欄です。
・ ”fill” 欄は、図形の内部の領域を塗りつぶしする際の色の設定欄です。
画面の矩形の部分をクリックすることで、マウス操作で色を設定することができます。また、”#ff0000″、”#00ff00″、”red” などと、文字列で入力することでも色の指定が可能です。
・ ラインや領域の色を指定しない場合は、”none” と入力します。すると、ラインや領が透明な状態になります。
・ ”stroke-width” 欄で、線幅の設定が可能です。
・ ”rotate” 欄で、基本図形を表示する際の角度を設定することが可能です。基本図形については回転角を設定できるようにしています。いったん、45度等と設定しておき、後からタグを編集することで、角度や位置を微調整することが可能です。

文字列(テキスト)の表示について

・ テキストを表示する場合は、まず、左上のリストから、 “text”、 “text_box”、 “rounded_text_box”、 “circle_text” のいずれかを選択してください。”text” はテキストのみを表示します。”text_box” は枠で囲んだテキストです。”rounded_text_box” は角丸の枠で囲んだテキストです。”circle_text” は、円周状にテキストを配置して表示します。
・ テキストを表示するには、先に左側の”text” 欄に文字列を入力したのち、描画領域でドラッグ操作をしてください。
SVG ファイルに無駄なタグは入れたくないため、文字入力がない場合は、テキストの描画はしない(テキストのタグは生成しない)ようにしています。
・ 文字のフォントの設定については、”font-style” の欄で設定が可能です。SVG、HTML、CSS で使われている書式に従って記載することで、この文字列がタグ内に差し込まれ、タグに反映するようにしています。
たとえば、文字色を変更する場合は、font-style 欄の stroke を “red” に変更する等してみてください。HTML、CSS を知っている人であれば、すぐにわかるかと思います。Web サイトのデザインなどで、特殊なフォントや設定を使っている場合は、SVG タグで使える CSS を差し込むことで、凝ったフォント設定にすることも可能です。

図形の入力方式について

・ 図形の入力方式は、基本はドラッグ操作(マウスダウンで開始、マウスアップで描画実行)としています。
とはいえ、多角形の描画などで座標を正確に入力したい場合は、1回のドラッグ操作のみでの描画は難しくなります。
そこで、クリック操作を繰り返すことでも多角形等を描画できるようにしています。(試用版のつもりで機能を入れています。)
・ クリック操作で図形を入力する場合は、“polygon_click” または “polyline_click” (… _click とあるもの)を選択してください。
選択後、画面上でクリックをしていき、描画を終える場合は、ダブルクリックすることで、多角形等を描画することが可能です。

・ フリーハンドで曲線などを描画するには、ポリゴン polygon やポリライン polyline も使ってみてください。
・ これらの図形については、smoothing 欄で回数を設定することで、スムージング処理を入れることが可能です。0 は、スムージング処理なしです。数値を設定すると設定された回数だけ、入力された座標の列に対し、スムージング処理を繰り返します。
スムージング処理は、お試しの機能のつもりで入れています。具体的には、ドラッグの際に通過した点の列を取得し、任意の1点について、その座標と、その前後の点の平均座標との平均をさらに取って差し替えるといった処理をしています(詳細は省略します)。
現状のプログラムでは、描画された点の座標を平均的な位置に差し替えることでラインをなめらかにしています。したがって、設定値を大きくすると、スムージング処理を強くできる反面、打点した位置からだんだんずれていくことになります。
あとから制御点を編集できるようにしたり、ベジェ曲線を使うようにするなどもできると思いますが、プログラムが長くなってしまうため、現状は上記の程度としています。
なお、ベジェ曲線のプログラミングや数学に興味のある方、余裕のある方は、関連リンク「Python で自由曲線を描く」なども参照してみてください。ベジェ曲線の定義や、ベジェ曲線を生成するスクリプトについてまとめています。

グリッドの設定、画像サイズの設定について

・ マウスの位置をグリッド上で固定したい場合は、グリッド間隔を grid 欄で設定してください。
10px、20px などと設定すると、マウスで描画する際の座標を 10画素おき、20画素おきに固定することが可能です。方眼紙の交点に固定するような感覚で図形を描画できます。
・ 画像のサイズは、width、height 欄で設定します。
アイコンを作成する場合は、縦横を 24px、24px の正方形とすることが多いようです。

外部の画像の挿入について

・ jpg や png、svg の画像を挿入することも可能です。
まず、上記の①のフォルダに画像を入れておき、[add image tag] をクリックします。
SVG のスクリプト欄にタグが表示されますので、サンプルとして記載されているファイル名を、表示したい画像のファイル名に書き換えて [draw] ボタンをクリックします。これにより、外部の画像を挿入できます。
ファイル名を sample1.svg 等とすることで、過去に作成した SVG 画像等の挿入も可能です。
・ 図の挿入にあたり、表示位置やサイズ、透明度など、よく使うであろうタグの設定は明記してありますので、数値を調整するなどカスタマイズをしてください。

グラデーションについて

・ [add gradient tag] ボタンをクリックすると、円のグラデーションのサンプルを挿入できます。円のスクリプトの部分(<circle></circle>)を他の図形に差し替えることで、任意の図形にグラデーションを設定することが可能です。
・ たとえば、事前に長方形を作成しておき、テキストボックス欄でコピー&ペーストにより差し替えることで、長方形の図形にグラデーションを設定することが可能です。
・ なお、タグの記載で <rect … /> は、<rect … ></rect> の省略形です。HTML がわかる方は、必要により、末尾の閉じタグを明記して間に処理を入れるなど自由にカスタマイズしてください。

色を変えるアニメーションについて

・ [add animation tag 1] をクリックすると、円の色を変化させるサンプルを表示できます。
上記と同様にタグを差し替えていくことで、自作の図形の色をアニメーションで変化させることが可能です。

動きをつけたアニメーションについて

・ 作成したオブジェクトを動かしたい場合は、まず、[clear] ボタンのクリック後、path_polyline など、path_で始まるオブジェクトを選択し、パス(軌跡)を描いてください。
その後、[add animation tag 2] をクリックしてください。描いた軌跡に沿って円が動くサンプルを入れてあります。
・ 上記と同様に、円のサンプルのタグ部分(<circle … >)を、自作した任意の画像のタグ(複数設定可)で差し替えることで、任意の図形を任意の軌跡上で動かして、アニメーション動画を作成することが可能です。
・ パスの描画と [add animation tag 2] を交互に設定していくことで、複数のオブジェクトを動かすことも可能です。
・ なお、アニメーションでオブジェクトを動かす場合は、SVG の仕様に沿って、どのオブジェクト(グループタグ、group1など)を、どのパス(path1など)に沿って動かすのかを指定する必要があります。
そこで、上記の手順でアニメーションを設定すると、パス(<path></path>)には id=”path1″ 等といったパスを特定する ID、グループ(<g></g>)には id=”group1″ といったグループを特定する ID を自動で振るようにしています。加えて、それらを指定してアニメーションで動かすタグを追加しています。
したがって、動かしたい軌跡の ID を差し替える等により、後から、動かしたいオブジェクトや軌跡、動作時間や動作回数などを自由にカスタマイズできるようにしています。

サンプルスクリプトの説明、SVG 形式について

・ 今回のスクリプトは、このサイトで公開してきている下記の関連リンクの要素を組み合わせた応用編となっています。使い方の説明がかなり長くなってしまったので、サンプルスクリプトの詳細説明は省略します。需要がありそうでしたら、後日、追記しようと思います。誰も見なければ無駄ですし。。
・ SVG 形式については、下記の「Python で自由曲線を描く」、「Python で回路図を描画する」あたりにもまとめています。
プログラミング言語は異なりますが、SVG の仕様にのっとって、<svg> タグの生成をしている程度で、中身は同じようなものです。

まとめ

ベクター形式でのソフトウェアについてスクリプトをまとめ、お絵描きをトライしてみました。
ドローソフト、ドローイングソフトとして自分が図形のデザインで使いたいと思う基本機能は、ほぼ盛り込んでみました。実質的に無償のオープンソースですので、機能の追加も可能です。プログラミングに興味のある方は、外観を洗練させたり、機能を改善してみてください。

ネット環境、ログイン、サインインなど一切不要で、ロゴの作成などが可能になりました。個人的にはイラストレータなども不要かな(Adobe 税フリー)といったところです。

なお、今回の描画アプリを使って地図を描き、ファイルサイズが軽い24時間時計なども作っています。
もし関心があるようでしたら、以下の関連リンクも参照してみてください。

関連リンク
・ ベクター画像で世界時計を作る 【JavaScript】
・ ブラウザで動くお絵描きアプリ 【JavaScript】
・ 
Python で自由曲線を描く 【HTML & SVG】
・ Python で回路図を描画する 【HTML & SVG】
・ フラクタル図形を描画してみる 【Python & tkinter】

外部リンク
・ 概要 – SVG: スケーラブルベクターグラフィック | MDN (mozilla.org)

サンプルスクリプト

ベクトル描画ツール vector_web_app1.html



<!DOCTYPE html>
<html lang="ja">
<title>vector web app v0.1</title> 
<head>
<meta charset="UTF-8"/>
<script>

let drag1 = {
    target_id1 : null, 
    target_id2 : null, 
    object1 : null, 
    mode1 : "", 
    mode1_click1: 0, 
    mousedown1 : false, 
    clientX1 : 0, 
    clientY1 : 0, 
    mouseX1 : 0, 
    mouseY1 : 0, 
    mouseX2 : 0, 
    mouseY2 : 0, 
    mouseX3 : 0, 
    mouseY3 : 0, 
    mouseX4 : 0, 
    mouseY4 : 0, 
    color1 : "#ff0000", 
    color2 : "#ff0000", 
    s_width1 : 5, 
    font_size1 : 30, 
    rotate1: 0, 
    text1 : "", 
    font_style1: "", 
    str_points1 : "", 
    script1: "", 
    script2: "", 
    script3: "", 
    path_id1: 1, 
    smoothing1: 0, 
    grid1 : -1, 
    width1 : 800, 
    height1 : 400, 
    timer1: null, 
} 

document.onmousemove = onmousemove1; 
document.onmouseover = onmouseover1; 
document.onmousedown = onmousedown1; 
document.onmouseup   = onmouseup1; 

window.onload = onload1; 

function onload1() {
    let obj1 = document.getElementById("select1"); 
    obj1.options[4].selected = true; 
    select1(); 
    obj1 = document.getElementById("select2"); 
    obj1.options[2].selected = true; 
    select2(); 
    obj1 = document.getElementById("select3"); 
    obj1.options[8].selected = true; 
    select3(); 
    obj1 = document.getElementById("select4"); 
    obj1.options[3].selected = true; 
    select4(); 
    obj1 = document.getElementById("select5"); 
    obj1.options[0].selected = true; 
    select5(); 
    obj1 = document.getElementById("select7"); 
    obj1.options[5].selected = true; 
    select7(); 
    obj1 = document.getElementById("select8"); 
    obj1.options[3].selected = true; 
    select8(); 
    obj1 = document.getElementById("select9"); 
    obj1.options[0].selected = true; 
    select9(); 
    document.getElementById("textbox6").value = "TEXT ..."; 
    textbox6(); 
    let str1 = "stroke:none; \nfill:#000000; \nfont-family:sans-serif; \nfont-weight:normal; \ntext-anchor:middle; \nletter-spacing:normal; "; 
    document.getElementById("textarea1").value = str1; 
    textarea1(); 
    document.getElementById('color1').addEventListener('input', set_color1 ); 
    document.getElementById('color2').addEventListener('input', set_color2 ); 
    draw1(); 
} 

function onmousemove1( event1 ) { 
    drag1.clientX1 = event1.clientX; 
    drag1.clientY1 = event1.clientY; 
    set_mouseXY1( event1 ); 
    show1( event1 ); 
    if (drag1.mousedown1 == true) { 
        if (drag1.mode1_click1 == 0 ) { 
            add_trace1(0); 
        } 
        draw1_dragging(1); 
    } 
} 

function onmouseover1( event1 ) { 
    if (drag1.mousedown1 == false) { 
        drag1.target_id1 = event1.target.id; 
        drag1.target_id2 = event1.target.parentNode.id; 
        drag1.object1 = document.getElementById( drag1.target_id1 );  
    } 
} 

function onmousedown1( event1 ) { 
    if ( (event1.target.id == "svg01") || (event1.target.parentNode.id == "svg01") ) { 
        drag1.mousedown1 = true; 
        drag1.mouseX3 = drag1.mouseX2; 
        drag1.mouseY3 = drag1.mouseY2; 
        drag1.mouseX2 = drag1.mouseX1; 
        drag1.mouseY2 = drag1.mouseY1; 
        if (drag1.mode1_click1 == 0) { 
            drag1.str_points1 = ""; 
        } 
    } else { 
        drag1.mousedown1 = false; 
        drag1.str_points1 = ""; 
        drag1.mouseX3 = -1; 
        drag1.mouseY3 = -1; 
        drag1.mouseX2 = -1; 
        drag1.mouseY2 = -1; 
    } 
} 

function onmouseup1( event1 ) { 
    if ( (event1.target.id == "svg01") || (event1.target.parentNode.id == "svg01") ) { 
        drag1.mousedown1 = false; 
        draw1_dragging(-1); 
        if (drag1.mode1_click1 == 0) { 
            add_trace1(1); 
            drag1.mouseX3 = drag1.mouseX2; 
            drag1.mouseY3 = drag1.mouseY2; 
            drag1.mouseX2 = drag1.mouseX1; 
            drag1.mouseY2 = drag1.mouseY1; 
            onsglclick1( event1 ); 
            drag1.str_points1 = ""; 
        } else { 
            drag1.mouseX3 = drag1.mouseX2; 
            drag1.mouseY3 = drag1.mouseY2; 
            drag1.mouseX2 = drag1.mouseX1; 
            drag1.mouseY2 = drag1.mouseY1; 
            if (drag1.mouseX3 == drag1.mouseX2 && drag1.mouseY3 == drag1.mouseY2) { 
                add_trace1(1); 
                if (drag1.timer1) { 
                    clearTimeout(drag1.timer1); 
                    drag1.timer1 = null; 
                    ondblclick1( event1 ); 
                    drag1.str_points1 = ""; 
                } else { 
                    drag1.timer1 = setTimeout( function() { 
                        onsglclick1( event1 ); 
                        drag1.timer1 = null; 
                    }, 300 ); 
                } 
            } 
        } 
    } 
} 

function onsglclick1( event1 ) { 
    show1( event1 ); 
    if (drag1.mouseX3 < 0 || drag1.mouseY3 < 0 || drag1.mouseX2 < 0 || drag1.mouseY2 < 0 ) { 
    } else if (drag1.mouseX2 != drag1.mouseX3 || drag1.mouseY2 != drag1.mouseY3 ) { 
        add_and_draw1(); 
    } else if (drag1.mode1 == "dot1") { 
        add_and_draw1(); 
    } 
} 

function ondblclick1( event1 ) { 
        add_and_draw1(); 
        drag1.str_points1 = ""; 
} 

function add_trace1(n1) { 
    let thr1 = 2;  
    if ((drag1.mouseX1 == drag1.mouseX4) && (drag1.mouseY1 == drag1.mouseY4) ) { 
    } else if (Math.abs(drag1.mouseX1-drag1.mouseX4) > thr1 || Math.abs(drag1.mouseY1-drag1.mouseY4) > thr1 || n1 > 0 ) { 
        drag1.str_points1 = drag1.str_points1 + Math.round(drag1.mouseX1) + " " + Math.round(drag1.mouseY1) + " "; 
        drag1.mouseX4 = drag1.mouseX1; 
        drag1.mouseY4 = drag1.mouseY1; 
    } 
} 

function draw1_dragging(n1) { 
    if (drag1.mode1 == "polyline1" || drag1.mode1 == "polygon1" || drag1.mode1 == "path_polygon1" ) { } else { 
        let x1 = drag1.mouseX2; 
        let y1 = drag1.mouseY2; 
        let x2 = drag1.mouseX1; 
        let y2 = drag1.mouseY1; 
        if (n1 < 0 ) { 
            x1 = -100; 
            y1 = -100; 
            x2 = -50; 
            y2 = -50; 
            draw1_dragging_line(x1, y1, x2, y2); 
            draw1_dragging_area(x1, y1, x2, y2); 
        } else { 
            if (drag1.mode1 == "line1" || drag1.mode1 == "dotted_line1" || drag1.mode1 == "rounded_end_line1" || drag1.mode1 == "arrow1" || drag1.mode1 == "semicircle1" || drag1.mode1 == "path_line1" ) { 
                draw1_dragging_line(x1, y1, x2, y2); 
            } else { 
                draw1_dragging_area(x1, y1, x2, y2); 
            } 
        } 
    } 
} 

function draw1_dragging_area(x1, y1, x2, y2) { 
    if (x1>x2) { 
        x3 = x2; 
        x2 = x1; 
        x1 = x3; 
    } 
    if (y1>y2) { 
        y3 = y2; 
        y2 = y1; 
        y1 = y3; 
    } 
    let obj1 = document.getElementById( "dragging_area1" ); 
    obj1.setAttribute("x"     ,    x1); 
    obj1.setAttribute("y"     ,    y1); 
    obj1.setAttribute("width" , x2-x1); 
    obj1.setAttribute("height", y2-y1); 
} 

function draw1_dragging_line(x1, y1, x2, y2) { 
    let obj1 = document.getElementById( "dragging_line1" ); 
    obj1.setAttribute("x1", x1); 
    obj1.setAttribute("y1", y1); 
    obj1.setAttribute("x2", x2); 
    obj1.setAttribute("y2", y2); 
} 

function set_mouseXY0( event1 ) { 
    drag1.mouseX1 = event1.clientX; 
    drag1.mouseY1 = event1.clientY; 
} 

function set_mouseXY1( event1 ) { 
    let svg1 = document.getElementById( "svg01" ); 
    let point1 = svg1.createSVGPoint(); 
    point1.x = drag1.clientX1; 
    point1.y = drag1.clientY1; 
    let point2 = point1.matrixTransform( svg1.getScreenCTM().inverse() ); 
    if (drag1.grid1 < 0) { 
        drag1.mouseX1 = point2.x; 
        drag1.mouseY1 = point2.y; 
    } else { 
        drag1.mouseX1 = Math.round( point2.x/drag1.grid1 )*drag1.grid1; 
        drag1.mouseY1 = Math.round( point2.y/drag1.grid1 )*drag1.grid1; 
    } 
} 

function show1( event1 ) { 
    let str1 = "x1: " + Math.round(drag1.mouseX1) + " y1:" + Math.round(drag1.mouseY1) + " ";
    document.getElementById( "label01" ).innerHTML = str1; 
} 

function add_and_draw1() { 
    let str1 = ""; 
    if (drag1.mode1 == "line1") { 
        str1 = svg1_line(); 
    } else if (drag1.mode1 == "dotted_line1") { 
        str1 = svg1_dotted_line(); 
    } else if (drag1.mode1 == "rectangle1") { 
        str1 = svg1_rectangle(); 
    } else if (drag1.mode1 == "rounded_end_line1") { 
        str1 = svg1_rounded_end_line(); 
    } else if (drag1.mode1 == "rounded_rectangle1") { 
        str1 = svg1_rounded_rectangle(); 
    } else if (drag1.mode1 == "dotted_rectangle1") { 
        str1 = svg1_dotted_rectangle(); 
    } else if (drag1.mode1 == "circle1") { 
        str1 = svg1_circle(); 
    } else if (drag1.mode1 == "semicircle1") { 
        str1 = svg1_semicircle(); 
    } else if (drag1.mode1 == "arc1" ) { 
        str1 = svg1_arc(0); 
    } else if (drag1.mode1 == "arc2" ) { 
        str1 = svg1_arc(1); 
    } else if (drag1.mode1 == "arc_arrow1" ) { 
        str1 = svg1_arc_arrow(0); 
    } else if (drag1.mode1 == "arc_arrow2" ) { 
        str1 = svg1_arc_arrow(1); 
    } else if (drag1.mode1 == "ellipse1") { 
        str1 = svg1_ellipse(); 
    } else if (drag1.mode1 == "dot1") { 
        str1 = svg1_dot(); 
    } else if (drag1.mode1 == "arrow1") { 
        str1 = svg1_arrow(); 
    } else if (drag1.mode1 == "triangle1") { 
        str1 = svg1_triangle(); 
    } else if (drag1.mode1 == "rounded_triangle1") { 
        str1 = svg1_rounded_triangle(); 
    } else if (drag1.mode1 == "diamond1" ) { 
        str1 = svg1_diamond(); 
    } else if (drag1.mode1 == "text1") { 
        str1 = svg1_text(); 
    } else if (drag1.mode1 == "text_box1") { 
        str1 = svg1_text_box(); 
    } else if (drag1.mode1 == "rounded_text_box1") { 
        str1 = svg1_rounded_text_box(); 
    } else if (drag1.mode1 == "circle_text1") { 
        str1 = svg1_circle_text(); 
    } else if (drag1.mode1 == "polygon1") { 
        str1 = svg1_polygon(); 
    } else if (drag1.mode1 == "polyline1") { 
        str1 = svg1_polyline(); 
    } else if (drag1.mode1 == "polygon_click1") { 
        str1 = svg1_polygon(); 
    } else if (drag1.mode1 == "polyline_click1") { 
        str1 = svg1_polyline(); 

    } else if (drag1.mode1 == "path_line1") { 
        str1 = svg1_path_line(); 
    } else if (drag1.mode1 == "path_ellipse1") { 
        str1 = svg1_path_ellipse(); 
    } else if (drag1.mode1 == "path_polygon1") { 
        str1 = svg1_path_polygon(); 

    } 
    if (str1 != "") { 
        let str2 = document.getElementById("textarea2").value; 
        str2 = (str2.trim().replace("\n\n", "\n") + "\n" + str1).trim(); 
        document.getElementById("textarea2").value = str2; 
        draw1(); 
    } 
} 

function draw1() {
    let str1 = document.getElementById("textarea2").value.trim(); 
    let str2 = str1 + "\n" + svg1_dragging_area(); 
    if (str1 != drag1.script1) { 
        drag1.script3 = drag1.script2; 
        drag1.script2 = drag1.script1; 
        drag1.script1 = str1; 
    } 
    try { 
        document.getElementById("svg01").innerHTML = str2; 
    } catch (error) {
        console.error("Invalid SVG script:", error);
    }
} 

function svg1_stroke1() { 
    let str1 = 'stroke="' + drag1.color1 + '" stroke-width="' + drag1.s_width1 + '" '; 
    return str1; 
} 

function svg1_stroke2() { 
    let str1 = 'stroke="' + drag1.color1 + '" fill="' + drag1.color2 + '" stroke-width="' + drag1.s_width1 + '" '; 
    return str1; 
} 

function svg1_rotate1( x1, y1 ) { 
    str1 = ""; 
    if ( drag1.rotate1 == 0 ) { } else { 
        str1 = 'transform="rotate( ' + drag1.rotate1 + ' ' + x1 + ' ' + y1 + ')" '; 
    } 
    return str1; 
} 

function svg1_line() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let str1 = '<line ' + 'x1="' + x1 + '" y1="' + y1 + '" x2="' + x2 + '" y2="' + y2 + '" '; 
    str1 = str1 + svg1_stroke1(); 
    str1 = str1 + '/>'; 
    return str1 
} 

function svg1_dotted_line() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let str1 = '<line ' + 'x1="' + x1 + '" y1="' + y1 + '" x2="' + x2 + '" y2="' + y2 + '" '; 
    str1 = str1 + svg1_stroke1(); 
    str1 = str1 + 'stroke-dasharray = "20,10" '; 
    str1 = str1 + '/>'; 
    return str1 
} 

function svg1_rounded_end_line() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let str1 = '<line ' + 'x1="' + x1 + '" y1="' + y1 + '" x2="' + x2 + '" y2="' + y2 + '" '; 
    str1 = str1 + svg1_stroke1(); 
    str1 = str1 + 'stroke-linecap="round" '; 
    str1 = str1 + '/>\n'; 
    return str1 
} 

function svg1_arrow() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let d1 = Math.pow( Math.pow(x2-x1, 2) + Math.pow(y2-y1, 2), 0.5 ); 
    let x3 = (x2-x1)/d1*drag1.s_width1; 
    let y3 = (y2-y1)/d1*drag1.s_width1; 
    let x4 = x2-4.5*x3; 
    let y4 = y2-4.5*y3; 
    let str1 = '<line ' + 'x1="' + x1 + '" y1="' + y1 + '" x2="' + Math.round(x4) + '" y2="' + Math.round(y4) + '" '; 
    str1 = str1 + svg1_stroke1(); 
    str1 = str1 + '/>'; 
    let str2 = svg1_arrow_head( x1, y1, x2, y2 ); 
    str1 = str1 + "\n" + str2; 
    return str1 
} 

function svg1_arrow_head(x1, y1, x2, y2) { 
    let d1 = Math.pow( Math.pow(x2-x1, 2) + Math.pow(y2-y1, 2), 0.5 ); 
    let x3 = (x2-x1)/d1*drag1.s_width1; 
    let y3 = (y2-y1)/d1*drag1.s_width1; 
    let x4 = x2-5.0*x3; 
    let y4 = y2-5.0*y3; 
    let x5 = -y3; 
    let y5 =  x3; 
    let x6 = x4+1.5*x5; 
    let y6 = y4+1.5*y5; 
    let x7 = x4-1.5*x5; 
    let y7 = y4-1.5*y5; 
    let str1 = '<polygon points="'; 
    str1 = str1 + Math.round(x2*100)/100 + ' ' + Math.round(y2*100)/100 + ' '; 
    str1 = str1 + Math.round(x6*100)/100 + ' ' + Math.round(y6*100)/100 + ' '; 
    str1 = str1 + Math.round(x7*100)/100 + ' ' + Math.round(y7*100)/100; 
    str1 = str1 + '" fill="' + drag1.color1 + '" />'; 
    return str1; 
} 

function smoothing1(n1) { 
    let str1 = drag1.str_points1.trim(); 
    let n2 = Number(drag1.smoothing1); 
    if (n2>0) { 
        let a1 = str1.split( " " ); 
        let n3 = a1.length/2; 
        let a2 = []; 
        let v11 = -1; 
        let v12 = -1; 
        for (let i1 = 0; i1 < n3; i1++ ) { 
            let v21 = Number( a1[2*i1  ] ); 
            let v22 = Number( a1[2*i1+1] ); 
            let v3 = Math.pow((v11-v21), 2) + Math.pow((v12-v22), 2); 
            if (v3 > 16 || i1 == 0 || i1 == n3-1 ) { 
                a2.push( v21 ); 
                a2.push( v22 ); 
            } 
            v11 = v21; 
            v12 = v22; 
        } 
        a1 = a2; 
        n3 = a1.length; 
        let n4 = n3 - 4*(1-Number(n1));                  // n1 = 0 or 1 
        for (let i1 = 0; i1 < n2 ; i1++ ) { 
            for (let i2 = 0; i2 < n4; i2++) { 
                let v1 = ( a1[(i2 % n3)] + a1[(i2+2) % n3] + a1[(i2+4) % n3] )/3.0; 
                v1 = Math.round( v1*100.0 )/100.0; 
                a1[(i2+2)%n3] = v1; 
            } 
        } 
        str1 = a1.join( " " ); 
    } 
    return str1; 
} 

function svg1_polyline() { 
    let str1 = ""; 
    if (drag1.str_points1.trim().split(" ").length > 3) { 
        str1 = str1 + '<polyline points="' + smoothing1(0) + '" '; 
        str1 = str1 + svg1_stroke2(); 
        str1 = str1 + '/>'
    } 
    return str1; 
} 

function svg1_polygon() { 
    let str1 = ""; 
    if (drag1.str_points1.trim().split(" ").length > 3) { 
        str1 = '<polygon points="' + smoothing1(1) + '" '; 
        str1 = str1 + svg1_stroke2(); 
        str1 = str1 + '/>'
    } 
    return str1; 
} 

function path_id1() { 
    let str1 = document.getElementById("textarea2").value.trim();
    let str2 = 'path' + drag1.path_id1; 
    let str3 = ' id="' + str2 + '" '; 
    if ( str1.includes( str3 ) ) { 
        drag1.path_id1 = drag1.path_id1 + 1; 
        str2 = 'path' + drag1.path_id1; 
    } 
    return str2; 
} 

function svg1_path_line() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let id1 = path_id1(); 
    let str1 = '<path id="' + id1 + '" d="M ' + x1 + ' ' + y1 + ' ' + x2 + ' ' + y2 + ' Z" '; 
    str1 = str1 + 'stroke="' + drag1.color1 + '" stroke-width="1" '; 
    str1 = str1 + '/>'
    return str1; 
} 

function svg1_path_ellipse() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let rx1 = Math.abs((x2-x1)/2); 
    let ry1 = Math.abs((y2-y1)/2); 
    let cx1 = Math.abs((x2+x1)/2); 
    let cy1 = Math.abs((y2+y1)/2); 
    let id1 = path_id1(); 
    let str1 = '<path id="' + id1 + '" d="M ' + cx1 + ' ' + cy1 + ' m ' + (x1-cx1) + ' ' + 0 + ' '; 
    str1 = str1 + ' a ' + rx1 + ' ' + ry1 + ' 0 1 0 ' + 2*rx1 + ' ' + 0 + ' '; 
    str1 = str1 + ' a ' + rx1 + ' ' + ry1 + ' 0 1 0 ' + (-2*rx1) + ' ' + 0 + ' Z" '; 
    str1 = str1 + 'stroke="' + drag1.color1 + '" fill="none" stroke-width="1" '; 
    str1 = str1 + '/>'
    return str1; 
} 

function svg1_path_polygon() { 
    let id1 = path_id1(); 
    let str1 = '<path id="' + id1 + '" d="M ' + smoothing1(1) + ' Z" '; 
    str1 = str1 + 'stroke="' + drag1.color1 + '" fill="none" stroke-width="1" '; 
    str1 = str1 + '/>'
    return str1; 
} 

function svg1_rectangle() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let cx1 = Math.round((x1+x2)/2.0); 
    let cy1 = Math.round((y1+y2)/2.0); 
    let x3 = x1; 
    let y3 = y1; 
    let w1 = x2 - x1; 
    let h1 = y2 - y1; 
    if (x1>x2) { 
        x3 = x2; 
        w1 = -w1; 
    } 
    if (y1>y2) { 
        y3 = y2; 
        h1 = -h1; 
    } 
    let str1 = 'x="' + x3 + '" y="' + y3 + '" width="' + w1 + '" height="' + h1 + '"' ; 
    let str2 = '<rect ' + str1 + ' '; 
    str2 = str2 + svg1_rotate1( cx1, cy1 );  
    str2 = str2 + svg1_stroke2(); 
    str2 = str2 + '/>'; 
    return str2; 
} 

function svg1_dotted_rectangle() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let cx1 = Math.round((x1+x2)/2.0); 
    let cy1 = Math.round((y1+y2)/2.0); 
    let x3 = x1; 
    let y3 = y1; 
    let w1 = x2 - x1; 
    let h1 = y2 - y1; 
    if (x1>x2) { 
        x3 = x2; 
        w1 = -w1; 
    } 
    if (y1>y2) { 
        y3 = y2; 
        h1 = -h1; 
    } 
    let str1 = 'x="' + x3 + '" y="' + y3 + '" width="' + w1 + '" height="' + h1 + '"' ; 
    let str2 = '<rect ' + str1 + ' '; 
    str2 = str2 + svg1_rotate1( cx1, cy1 );  
    str2 = str2 + svg1_stroke2(); 
    str2 = str2 + 'stroke-dasharray = "20,10" '; 
    str2 = str2 + '/>'; 
    return str2; 
} 

function svg1_rounded_rectangle() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let cx1 = Math.round((x1+x2)/2.0); 
    let cy1 = Math.round((y1+y2)/2.0); 
    let x3 = x1; 
    let y3 = y1; 
    let w1 = x2 - x1; 
    let h1 = y2 - y1; 
    if (x1>x2) { 
        x3 = x2; 
        w1 = -w1; 
    } 
    if (y1>y2) { 
        y3 = y2; 
        h1 = -h1; 
    } 
    let str1 = '<rect ' + 'x="' + x3 + '" y="' + y3 + '" width="' + w1 + '" height="' + h1 + '" rx="10" ' + 'ry="10" '; 
    str1 = str1 + svg1_rotate1( cx1, cy1 );  
    str1 = str1 + svg1_stroke2(); 
    str1 = str1 + '/>'; 
    return str1; 
} 

function svg1_ellipse() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let cx1 = Math.round((x1+x2)/2.0); 
    let cy1 = Math.round((y1+y2)/2.0); 
    let rx1 = Math.abs((x2-x1)/2.0); 
    let ry1 = Math.abs((y2-y1)/2.0); 
    let str1 = '<ellipse cx="' + cx1 + '" cy="' + cy1 + '" rx="' + rx1 + '" ry="' + ry1 + '" '; 
    str1 = str1 + svg1_rotate1( cx1, cy1 ); 
    str1 = str1 + svg1_stroke2(); 
    str1 = str1 + '/>'; 
    return str1; 
} 

function svg1_circle() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let cx1 = Math.round((x1+x2)/2.0); 
    let cy1 = Math.round((y1+y2)/2.0); 
    let rx1 = Math.abs((x2-x1)/2.0); 
    let str1 = '<circle cx="' + cx1 + '" cy="' + cy1 + '" r="' + rx1 + '" '; 
    str1 = str1 + svg1_stroke2(); 
    str1 = str1 + '/>'; 
    return str1; 
} 

function svg1_dot() { 
    let x1 = Math.round(drag1.mouseX2); 
    let y1 = Math.round(drag1.mouseY2); 
    let r1 = 1; 
    let str1 = '<circle cx="' + x1 + '" cy="' + y1 + '" r="' + r1 + '" '; 
    str1 = str1 + svg1_stroke2(); 
    str1 = str1 + '/>'; 
    return str1; 
} 

function svg1_arc(n1) { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let rx1 = Math.abs(x2-x1); 
    let ry1 = Math.abs(y2-y1); 
    let str1 = '<path d="M ' + x1 + ' ' + y1 + ' '; 
    str1 = str1 + 'a ' + rx1 + ' ' + ry1 + ' '; 
    str1 = str1 + '0 0 ' + n1 + ' ';                 // rotation, large_arc, clockwise 
    str1 = str1 + (x2-x1) + ' ' + (y2-y1) + '" '; 
    str1 = str1 + svg1_rotate1( x1, y1 ); 
    str1 = str1 + svg1_stroke2(); 
    str1 = str1 + '/>'; 
    return str1; 
} 

function svg1_arc_arrow(n1) { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let rx1 = Math.abs(x2-x1); 
    let ry1 = Math.abs(y2-y1); 
    let d0 = Math.pow( Math.pow(rx1, 2) + Math.pow(ry1, 2), 0.5 ); 
    let x3 = rx1/d0; 
    let y3 = ry1/d0; 
    if (x1>x2) { x3 = -x3; } 
    if (y1>y2) { y3 = -y3; } 
    let v1 = 0.1 + drag1.s_width1/rx1 + drag1.s_width1/d0; 
    let v2 = 0.1 + drag1.s_width1/ry1 + drag1.s_width1/d0; 
    if ((y2-y1)*(x2-x1)*(0.5-n1) > 0) { y3 = v1*y3; } else { x3 = v2*x3; } 
    let d1 = Math.pow( Math.pow(x3, 2) + Math.pow(y3, 2), 0.5 ); 
    x3 = x3/d1*drag1.s_width1; 
    y3 = y3/d1*drag1.s_width1; 
    let x4 = x2-4.5*x3; 
    let y4 = y2-4.5*y3; 
    let str1 = '<path d="M ' + x1 + ' ' + y1 + ' '; 
    str1 = str1 + 'a ' + rx1 + ' ' + ry1 + ' '; 
    str1 = str1 + '0 0 ' + n1 + ' ';                 // rotation, large_arc, clockwise 
    str1 = str1 + (x4-x1) + ' ' + (y4-y1) + '" '; 
    str1 = str1 + svg1_rotate1( x1, y1 ); 
    str1 = str1 + svg1_stroke2(); 
    str1 = str1 + '/>'; 
    let str2 = svg1_arrow_head( x4, y4, x2, y2 ); 
    str1 = str1 + "\n" + str2; 
    return str1; 
} 

function svg1_semicircle() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let r1 = Math.round( Math.pow((x2-x1)**2 + (y2-y1)**2, 0.5)/2.0 ); 
    let cx1 = Math.round( (x1+x2)/2.0 ); 
    let cy1 = Math.round( (y1+y2)/2.0 ); 
    let str1 = '<path d="M ' + x1 + ' ' + y1 + ' '; 
    str1 = str1 + 'A ' + r1 + ' ' + r1 + ' '; 
    str1 = str1 + '0 0 1 ';                          // rotation, large_arc, clockwise 
    str1 = str1 + x2 + ' ' + y2 + '" '; 
    str1 = str1 + svg1_rotate1( cx1, cy1 ); 
    str1 = str1 + svg1_stroke2(); 
    str1 = str1 + '/>'; 
    return str1; 
} 

function svg1_diamond() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let cx1 = Math.round((x1+x2)/2); 
    let cy1 = Math.round((y1+y2)/2); 
    let x3 = cx1; 
    let y3 = y1; 
    let x4 = x2; 
    let y4 = cy1; 
    let x5 = cx1; 
    let y5 = y2; 
    let x6 = x1; 
    let y6 = cy1; 
    let str1 = '<polygon points="' + x3 + ' ' + y3 + ' ' + x4 + ' ' + y4 + ' ' + x5 + ' ' + y5 + ' ' + x6 + ' ' + y6 + '" '; 
    str1 = str1 + svg1_rotate1( cx1, cy1 ); 
    str1 = str1 + svg1_stroke2(); 
    str1 = str1 + '/>'; 
    return str1; 
} 

function svg1_triangle() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let cx1 = Math.round((x1+x2)/2); 
    let cy1 = Math.round((y1+y2)/2); 
    let x3 = cx1; 
    let y3 = y2; 
    let x4 = x2; 
    let y4 = y1; 
    let str1 = '<polygon points="' + x1 + ' ' + y1 + ' ' + x3 + ' ' + y3 + ' ' + x4 + ' ' + y4 + '" '; 
    str1 = str1 + svg1_rotate1( cx1, y1 ); 
    str1 = str1 + svg1_stroke2(); 
    str1 = str1 + '/>'; 
    return str1; 
} 

function svg1_rounded_triangle() { 
    let x1 = Math.round(drag1.mouseX3); 
    let y1 = Math.round(drag1.mouseY3); 
    let x2 = Math.round(drag1.mouseX2); 
    let y2 = Math.round(drag1.mouseY2); 
    let cx1 = Math.round((x1+x2)/2); 
    let cy1 = Math.round((y1+y2)/2); 
    let x3 = cx1; 
    let y3 = y2; 
    let x4 = x2; 
    let y4 = y1; 
    let str1 = '<polygon points="' + x1 + ' ' + y1 + ' ' + x3 + ' ' + y3 + ' ' + x4 + ' ' + y4 + '" '; 
    str1 = str1 + svg1_rotate1( cx1, y1 ); 
    str1 = str1 + svg1_stroke2(); 
    str1 = str1 + 'stroke-linejoin="round" '; 
    str1 = str1 + '/>'; 
    return str1; 
} 

function svg1_text() { 
    let str1 = document.getElementById("textbox6").value.trim(); 
    let str2 = ""; 
    if (str1 != "" ) { 
        let x1 = Math.round(drag1.mouseX3); 
        let y1 = Math.round(drag1.mouseY3); 
        let x2 = Math.round(drag1.mouseX2); 
        let y2 = Math.round(drag1.mouseY2); 
        let cx1 = Math.round((x1+x2)/2.0); 
        let cy1 = Math.round((y1+y2)/2.0); 
        str2 = str2 + '<text x="' + cx1 + '" y="' + cy1 + '" '; 
        str2 = str2 + 'font-size="' + drag1.font_size1 + '" '; 
        str2 = str2 + 'style="' + drag1.font_style1 + '" '; 
        str2 = str2 + 'dy="0.35em" '; 
        str2 = str2 + svg1_rotate1( cx1, cy1 );  
        str2 = str2 + '>'; 
        str2 = str2 + str1 + '</text>'; 
    } 
    return str2; 
} 

function svg1_text_box() { 
    let str1 = svg1_rectangle(); 
    let str2 = svg1_text(); 
    str1 = (str1 + "\n" + str2).trim(); 
    return str1; 
} 

function svg1_rounded_text_box() { 
    let str1 = svg1_rounded_rectangle(); 
    let str2 = svg1_text(); 
    str1 = (str1 + "\n" + str2).trim(); 
    return str1; 
} 

function svg1_circle_text() { 
    let str1 = document.getElementById("textbox6").value.trim(); 
    let str2 = ""; 
    if (str1 != "" ) { 
        let x1 = Math.round(drag1.mouseX3); 
        let y1 = Math.round(drag1.mouseY3); 
        let x2 = Math.round(drag1.mouseX2); 
        let y2 = Math.round(drag1.mouseY2); 
        let cx1 = Math.round((x1+x2)/2.0); 
        let cy1 = Math.round((y1+y2)/2.0); 
        let rx1 = Math.round(Math.abs((x2-x1)/2.0)-drag1.font_size1); 
        let ry1 = Math.round(Math.abs((y2-y1)/2.0)-drag1.font_size1); 
        let id1 = path_id1(); 
        let str3 = '<path id="' + id1 + '" d="M ' + cx1 + ' ' + cy1 + ' m 0 ' + ry1 + ' '; 
        str3 = str3 + 'a ' + rx1 + ' ' + ry1 + ' 0 1 1 ' +  '0 ' + (-2*ry1) + ' '; 
        str3 = str3 + 'a ' + rx1 + ' ' + ry1 + ' 0 1 1 ' +  '0 ' + ( 2*ry1) + '" '; 
        str3 = str3 + 'stroke="none" fill="none" ';
        str3 = str3 + '/>'; 
        let str4 = '<text font-size="' + drag1.font_size1 + '" '; 
        str4 = str4 + 'style="' + drag1.font_style1 + '" '; 
        str4 = str4 + svg1_rotate1( cx1, cy1 ); 
        str4 = str4 + 'dy="0.0em" '; 
        str4 = str4 + '>'; 
        str4 = str4 + '<textPath href="#' + id1 + '" '; 
        str4 = str4 + 'startOffset="50%">' + str1 + '</textPath>'; 
        str4 = str4 + '</text>'; 
        str2 = str3 + str4; 
    } 
    return str2; 
} 

function svg1_dragging_area() { 
    let str1 = '<rect id="dragging_area1" x="-100" y="-100" width="50" height="50" stroke="#777777" stroke-dasharray = "5,5" fill="none" />\n'; 
    str1 = str1 + '<line id="dragging_line1" x1="-100" y1="-100" x2="-50" y2="-50" stroke="#777777" stroke-dasharray = "5,5" fill="none" />'; 
    return str1; 
} 

function clear1() { 
    drag1.path_id1 = 1; 
    document.getElementById("textarea2").value = ""; 
    draw1(); 
} 

function undo1() { 
    document.getElementById("textarea2").value = drag1.script2; 
    drag1.script1 = drag1.script2; 
    drag1.script2 = drag1.script3; 
    drag1.script3 = ""; 
    if ( drag1.script1 == "" && drag1.script2 == "" ) { 
        clear1(); 
    } else { 
        draw1(); 
    } 
} 

function delete_last1() { 
    let obj1 = document.getElementById("textarea2"); 
    let str1 = obj1.value.trim(); 
    let a1 = str1.split( "\n" ); 
    a1.splice( -1, 1 ); 
    str1 = a1.join( "\n" ); 
    obj1.value = str1; 
    draw1(); 
} 

function save_svg1() { 
    let file1 = "svg01.svg" 
    let str1 = document.getElementById("textarea2").value.trim();
    str1 = '<svg x="0" y="0" width="' + drag1.width1 + '" height="' + drag1.height1 + '" xmlns="http://www.w3.org/2000/svg">\n' + str1 + '\n</svg>'
    let blob1 = new Blob([str1], {type: 'text/plain'});
    let link1 = document.createElement('a');
    link1.href = URL.createObjectURL( blob1 );
    link1.download = file1; 
    link1.click(); 
} 

function save_png1() { 
    let file1 = "image01.png"; 
    let svg1 = document.getElementById('svg01');
    let str1 = new XMLSerializer().serializeToString(svg1);
    let canvas1 = document.createElement('canvas');
    let context1 = canvas1.getContext('2d');
    let img1 = new Image();
    let blog1 = new Blob([str1], {type: 'image/svg+xml;charset=utf-8'});
    let url1 = URL.createObjectURL( blog1 );
    img1.onload = function() {
        canvas1.width = svg1.clientWidth;
        canvas1.height = svg1.clientHeight;
        context1.drawImage(img1, 0, 0);
        URL.revokeObjectURL(url1);
        let url2 = canvas1.toDataURL('image/png').replace('image/png', 'image/octet-stream');
        let link1 = document.createElement('a');
        link1.href = url2;
        link1.download = file1; 
        document.body.appendChild(link1);
        link1.click();
        document.body.removeChild(link1);
    }; 
    img1.src = url1; 
} 

function add_image1() { 
    let str1 = document.getElementById("textarea2").value.trim();
    let str2 = '<image href="image1.png" x="0" y="0" width="' + drag1.width1 + '" height="' + drag1.height1 + '" opacity="0.5" ';
    str2 = str2 + 'onerror="' + "this.outerHTML=''" + ';" '; 
    str2 = str2 + '/>'; 
    str1 = (str1 + '\n' + str2).trim(); 
    document.getElementById("textarea2").value = str1; 
    draw1(); 
} 

function add_grad1() { 
    let str1 = document.getElementById("textarea2").value.trim();
    let str2 = ""; 
    str2 = str2 + '<defs><linearGradient id="grad1" x1="0%" y1="100%" x2="100%" y2="0%">\n'; 
    str2 = str2 + '<stop offset="0%" style="stop-color:rgb(255,0,0);stop-opacity:1" />\n'; 
    str2 = str2 + '<stop offset="100%" style="stop-color:rgb(255,255,127);stop-opacity:1" />\n'; 
    str2 = str2 + '</linearGradient></defs>\n'; 
    str2 = str2 + '<circle cx="100" cy="100" r="20" stroke="none" stroke-width="40" fill="url(#grad1)" />\n'; 
    str1 = (str1 + '\n' + str2).trim(); 
    document.getElementById("textarea2").value = str1; 
    draw1(); 
} 

function add_animation1() { 
    let str1 = document.getElementById("textarea2").value.trim();
    let str2 = '<circle cx="200" cy="100" r="20" fill="red">\n'; 
    str2 = str2 + '<animate attributeName="fill" values="red;red;green;green;red" keyTimes="0;0.49;0.5;0.99;1" dur="2s" repeatCount="indefinite"/>'; 
    str2 = str2 + '</circle>'; 
    str1 = (str1 + '\n' + str2).trim(); 
    document.getElementById("textarea2").value = str1; 
    draw1(); 
} 

function add_animation2() { 
    let str1 = document.getElementById("textarea2").value.trim();
    let str2 = '<defs><g id="group1" transform="translate(-100, -100) scale(1.0)">\n'; 
    str2 = str2 + '<circle cx="100" cy="100" r="20" stroke="none" fill="blue" stroke-width="40" />\n'; 
    str2 = str2 + '</g></defs>\n'; 
    str2 = str2 + '<use href="#group1"><animateMotion dur="10s" repeatCount="indefinite">'; 
    str2 = str2 + '<mpath href="#path'+ drag1.path_id1 + '"/>'; 
    str2 = str2 + '</animateMotion></use>'; 
    str1 = (str1 + '\n' + str2).trim(); 
    document.getElementById("textarea2").value = str1; 
    draw1(); 
} 

function select1() { 
    let obj1 = document.getElementById("select1"); 
    let str1 = obj1.options[obj1.selectedIndex].value; 
    let str2 = obj1.options[obj1.selectedIndex].text; 
    document.getElementById("textbox1").value = str2; 
    drag1.mode1 = str1; 
    if (str1 == "polyline1") { 
        obj1 = document.getElementById("select3"); 
        obj1.options[8].selected = true; 
        select3(); 
    } else if (str1 == "polyline_click1" || str1 == "polygon_click1" ) { 
        obj1 = document.getElementById("select8"); 
        obj1.options[0].selected = true; 
        select8(); 
        drag1.mode1_click1 = 1; 
    } else { 
        drag1.mode1_click1 = 0; 
    } 

    drag1.str_points1 = ""; 

} 

function select2() { 
    let obj1 = document.getElementById("select2"); 
    let str1 = obj1.options[obj1.selectedIndex].value; 
    document.getElementById("textbox2").value = str1; 
    obj1.selectedIndex = -1; 
    textbox2(); 
} 

function select3() { 
    let obj1 = document.getElementById("select3"); 
    let str1 = obj1.options[obj1.selectedIndex].value; 
    document.getElementById("textbox3").value = str1; 
    obj1.selectedIndex = -1; 
    textbox3(); 
} 

function select4() { 
    let obj1 = document.getElementById("select4"); 
    let str1 = obj1.options[obj1.selectedIndex].value; 
    document.getElementById("textbox4").value = str1; 
    drag1.s_width1 = str1; 
} 

function select5() { 
    let obj1 = document.getElementById("select5"); 
    let str1 = obj1.options[obj1.selectedIndex].value; 
    let v1 = Number(str1); 
    document.getElementById("textbox5").value = str1; 
    drag1.rotate1 = v1; 
} 

function select7() { 
    let obj1 = document.getElementById("select7"); 
    let str1 = obj1.options[obj1.selectedIndex].value; 
    document.getElementById("textbox7").value = str1; 
    drag1.font_size1 = str1; 
} 

function select8() { 
    let obj1 = document.getElementById("select8"); 
    let str1 = obj1.options[obj1.selectedIndex].value; 
    document.getElementById("textbox8").value = str1; 
    drag1.smoothing1 = str1; 
} 

function select9() { 
    let obj1 = document.getElementById("select9"); 
    let str1 = obj1.options[obj1.selectedIndex].value; 
    let n1 = Number(str1); 
    document.getElementById("textbox9").value = str1; 
    if (n1 > 0) {} else { n1 = -1; } 
    drag1.grid1 = n1; 
} 

function is_color1( str1 ) { 
    let pattern1 = /^#([0-9a-f]{3})$/i; 
    let pattern2 = /^#([0-9a-f]{6})$/i; 
    let ret1 = pattern1.test(str1) || pattern2.test(str1); 
    return ret1; 
} 

function textbox2() { 
    let str1 = document.getElementById("textbox2").value; 
    let obj1 = document.getElementById('color1'); 
    if (is_color1(str1)) {    
        drag1.color1 = str1; 
        if ( obj1.value != str1 ) { 
            obj1.value = str1; 
        } 
    } else if ( str1 == "none" ) { 
        drag1.color1 = str1; 
        obj1.value = "#ffffff"; 
    } else { 
        obj1.value = "#ffffff"; 
    } 
} 

function textbox3() { 
    let str1 = document.getElementById("textbox3").value; 
    let obj1 = document.getElementById('color2'); 
    if (is_color1(str1)) {    
        drag1.color2 = str1; 
        if ( obj1.value != str1 ) { 
            obj1.value = str1; 
        } 
    } else if ( str1 == "none" ) { 
        drag1.color2 = str1; 
        obj1.value = "#ffffff"; 
    } else { 
        obj1.value = "#ffffff"; 
    } 
} 

function textbox4() { 
    drag1.s_width1 = document.getElementById("textbox4").value; 
} 

function textbox5() { 
    let v1 = document.getElementById("textbox5").value; 
    drag1.rotate1  = Number( v1 ); 
} 

function textbox6() { 
    drag1.text1  = document.getElementById("textbox6").value; 
} 
function textbox7() { 
    drag1.font_size1  = document.getElementById("textbox7").value; 
} 

function textbox8() { 
    drag1.smoothing1  = document.getElementById("textbox8").value; 
} 

function textbox9() { 
    drag1.grid1  = document.getElementById("textbox9").value; 
} 

function textbox10() { 
    drag1.width1  = document.getElementById("textbox10").value; 
    drag1.height1 = document.getElementById("textbox11").value; 
    let str1 = '0 0 ' + drag1.width1 + ' ' + drag1.height1; 
    document.getElementById('svg01').setAttribute('width', drag1.width1); 
    document.getElementById('svg01').setAttribute('height', drag1.height1); 
    document.getElementById('svg01').setAttribute('viewBox', str1); 
} 

function textarea1() { 
    let str1 = document.getElementById("textarea1").value.trim().replace( /\n/g, " " ).replace( /  /g, " " ); 
    drag1.font_style1 = str1; 
} 

function set_color1() {
    document.getElementById('textbox2').value = document.getElementById('color1').value; 
    textbox2(); 
} 

function set_color2() {
    document.getElementById('textbox3').value = document.getElementById('color2').value; 
    textbox3(); 
} 

</script>
</head>

<body style="background-color:#eeeeee; font-family: helvetica, arial, sans-serif, 'Meiryo UI', Meiryo; ">

<table style="user-select:none; -webkit-user-drag:none;" >
<tr><td width="250">vector web app</td><td>
    <button onclick="clear1()">clear</button>
    <button onclick="undo1()">undo</button>
    <button onclick="delete_last1()">delete last tag</button>
    <button onclick="draw1()">draw</button>
    <button onclick="save_svg1()">save svg</button>
    <button onclick="save_png1()">save png</button>   
    <label id="label01"></label> 
</td></tr> 

<tr><td valign="top"> 
<br> 

<input type="text" id="textbox1" style="width:170px; height:14px; margin-right:-5px;" readonly> 
<select id="select1" style="width:18px;" onchange="select1()">
    <option value="line1">line</option>
    <option value="dotted_line1">dotted line</option>
    <option value="rounded_end_line1">rounded_end line</option>
    <option value="rectangle1">rectangle</option>
    <option value="rounded_rectangle1">rounded_rectangle</option>
    <option value="dotted_rectangle1">dotted_rectangle</option>
    <option value="dot1">dot</option>
    <option value="circle1">circle</option>
    <option value="ellipse1">ellipse</option>
    <option value="arrow1">arrow</option>
    <option value="arc1">arc 1</option>
    <option value="arc2">arc 2</option>
    <option value="arc_arrow1">arc arrow 1</option>
    <option value="arc_arrow2">arc arrow 2</option>
    <option value="semicircle1">semicircle</option>
    <option value="diamond1">diamond</option>
    <option value="triangle1">triangle</option>
    <option value="rounded_triangle1">rounded triangle</option>
    <option value="polyline1">polyline</option>
    <option value="polygon1">polygon</option>
    <option value="polyline_click1">polyline_click</option>
    <option value="polygon_click1">polygon_click</option>
    <option value="text1">text</option>
    <option value="text_box1">text_box</option>
    <option value="rounded_text_box1">rounded_text_box</option>
    <option value="circle_text1">circle text</option>
    <option value="path_line1">path_line</option>
    <option value="path_ellipse1">path ellipse</option>
    <option value="path_polygon1">path_polygon</option>
</select>
<br><br> 

<input type="text" id="textbox2" style="width:60px; height:14px; margin-right:-5px;" oninput="textbox2()"> 
<select id="select2" style="width:18px;" onchange="select2()">
    <option value="#ff0000" >red</option>
    <option value="#00ff00">green</option>
    <option value="#0000ff">blue</option>
    <option value="#00ffff">cyan</option>
    <option value="#ff00ff">magenta</option>
    <option value="#ffff00">yellow</option>
    <option value="#ffffff">white</option>
    <option value="#000000">black</option>
    <option value="none">none</option>
</select> 
<input type="color" id="color1" value="#0000ff" style="width:30px;padding:0;margin:0;background-color:transparent;border:none;vertical-align:middle;">
: stroke 
<br>

<input type="text" id="textbox3" style="width:60px; height:14px; margin-right:-5px;" oninput="textbox3()">
<select id="select3" style="width:18px;" onchange="select3()">
    <option value="#ff0000">red</option>
    <option value="#00ff00">green</option>
    <option value="#0000ff">blue</option>
    <option value="#00ffff">cyan</option>
    <option value="#ff00ff">magenta</option>
    <option value="#ffff00">yellow</option>
    <option value="#ffffff">white</option>
    <option value="#000000">black</option>
    <option value="none">none</option>
</select>
<input type="color" id="color2" value="#eeeeee" style="width:30px;padding:0;margin:0;background-color:transparent;border:none;vertical-align:middle;">
: fill 
<br><br> 

<input type="text" id="textbox4" style="width:60px; height:14px; margin-right:-5px;" onchange="textbox4()"> 
<select id="select4" style="width:18px;" onchange="select4()">
    <option value="1">1</option>
    <option value="2">2</option>
    <option value="5">5</option>
    <option value="10">10</option>
    <option value="15">15</option>
    <option value="20">20</option>
    <option value="25">25</option>
    <option value="30">30</option>
    <option value="40">40</option>
    <option value="50">50</option>
    <option value="60">60</option>
</select> : stroke-width 
<br> 

<input type="text" id="textbox5" style="width:60px; height:14px; margin-right:-5px;" onchange="textbox5()">
<select id="select5" style="width:18px;" onchange="select5()">
    <option value=   "0">0</option>
    <option value=  "15">+15</option>
    <option value= "-15">-15</option>
    <option value=  "30">+30</option>
    <option value= "-30">-30</option>
    <option value=  "45">+45</option>
    <option value= "-45">-45</option>
    <option value=  "90">+90</option>
    <option value= "-90">+90</option>
    <option value= "180">+180</option>
    <option value="-180">+180</option>
</select> : rotate 
<br><br> 

<input type="text" id="textbox6" style="width:140px; height:14px;" value="" onchange="textbox6()"> 
: text 
<br> 

<input type="text" id="textbox7" style="width:60px; height:14px; margin-right:-5px;" onchange="textbox7()">
<select id="select7" style="width:18px;" onchange="select7()">
    <option value="12">12</option>
    <option value="14">14</option>
    <option value="16">16</option>
    <option value="18">18</option>
    <option value="24">24</option>
    <option value="36">36</option>
    <option value="48">48</option>
    <option value="60">60</option>
    <option value="80">80</option>
</select> : font-size 
<br>

<textarea id="textarea1" rows="3" cols="27" spellcheck="false" onchange="textarea1()"></textarea><br>
: font-style1 
<br>

<input type="text" id="textbox8" style="width:60px; height:14px; margin-right:-5px;" onchange="textbox8()">
<select id="select8" style="width:18px;" onchange="select8()">
    <option value="0">none</option>
    <option value="1">1</option>
    <option value="5">5</option>
    <option value="10">10</option>
    <option value="20">20</option>
</select> : smoothing 
<br><br> 

<input type="text" id="textbox9" style="width:60px; height:14px; margin-right:-5px;" onchange="textbox9()">
<select id="select9" style="width:18px;" onchange="select9()">
    <option value="none">none</option>
    <option value="5">5</option>
    <option value="10">10</option>
    <option value="20">20</option>
    <option value="30">30</option>
    <option value="40">40</option>
    <option value="50">50</option>
    <option value="60">60</option>
</select> : grid 

<br> 
<input type="text" id="textbox10" style="width:60px; height:14px;" value="800" onchange="textbox10()">
: width 
<br> 
<input type="text" id="textbox11" style="width:60px; height:14px;" value="400" onchange="textbox10()">
: height 
<br> 

</td><td valign="top"> 
<div id="div01"> 
    <svg id="svg01" x="0" y="0" width="800" height="400" viewBox= "0 0 800 400" style="background-color:#ffffff" xmlns="http://www.w3.org/2000/svg">
    </svg><br>
</div> 
</td></tr> 
<tr> 
    <td valign="top">
    <br><br> 
    <button style="width:140px;" onclick="add_grad1()">add gradient tag</button><br>
    <button style="width:140px;" onclick="add_animation1()">add animation tag 1</button><br>
    <button style="width:140px;" onclick="add_animation2()">add animation tag 2</button><br>
    <button style="width:140px;" onclick="add_image1()">add image tag</button><br>
    </td> 
<td>
    <textarea id="textarea2" rows="10" cols="115" placeholder="SVG script"></textarea>
</td> 
</tr>
</table> 

</body>
</html>



タイトルとURLをコピーしました