HOME > 電算 > Inkscape メモ
Inkscape メモ
Inkscape もバージョンが上がって、このページも一新する必要が出てきた。ということで、漸々に作業を進めむ。(古いページ)
目次
概略
Inkscape 雑感
標準保存形式 Inkscape SVG
Inkscape の拡張スクリプト
おまけ——長方形や円をシェアにより分割する Inkscape の拡張スクリプト
他のファイル形式の読み書き
単位のこと
SVG から見た Inkscape の機能(クローンとコピー、レイヤー、変形、文字コード、テキストの流し込み、背景色、マーカ、パスを塗りつぶす
概略
Inkscape は、Linux, Windows, Mac OS X などのプラットホームで動作するオープンソースかつフリーのドローイング・ツール(ドロー・ソフト)である。SVG という形式にネイティブに対応しているのが著しい特徴。面白いなと思ったことを、備忘録ついでにメモしておく(当然誤りも含まれているかと思われる……)。ちなみに、私は Ubuntu(Linux の一ディストリビューション)上で Version 0.4.6(リリースノート)を使っている。
Inkscape 雑感
フリーのドローソフト(ドローイング・ツール)のなかで、最も使いやすいように思える。
Inkscape は SVG にネイティブに対応している。SVG は XML なので、基本的にテキスト書類である。このことが、他のプログラムやスクリプトの連携を著しく容易なものにしている。さまざまなフォーマットの画像を開いたり、編集中に使える愉快な拡張スクリプトを使ったり、いざとなればテキストエディタで中身を編集することもできる。
「職人の業をコンピュータ上で再現できるようにまずユーザ・インターフェース考えて、それを実現するためにデータ構造が決められているのがお絵描きソフトというものだ」と考えていると、Inkscape は実際以上に質素なものに見える。むしろ、SVG というデータ構造が先にあり、これを操作するための GUI を備えた実装が Inkscape であると考えるとしっくりいくし、ユーザインターフェースも合理的であるということに気付く。また、最初の印象よりもずっと多くの場面で使えるということを発見する。
標準保存形式 Inkscape SVG
Inkscape の標準的な保存形式(Inkscape SVG)は、たいへんキュートなものである。
Inkscape の標準的な保存形式は、SVG である。しかし、これは保存されたファイルから SVG の規格に定められているエレメントだけを拾って読んでも期待した通りにラスタライズできるという意味であり、実際には独自に定義したエレメントを含んでいるし、属性も追加されている。これらの追加されたエレメントや属性は、もっぱら Inkscape の GUI を使い勝手がよいものにするために用いられている。
たとえば、Inkscape で円は circle エレメントではなく、path エレメントとして作成される。この path エレメントは、円弧曲線を描くための M コマンドを 1 回、A コマンドを 2 回用いる(緑字まはた斜体部分)。
<path sodipodi:type="arc" style="opacity:1;fill:none;fill-opacity:1; fill-rule:evenodd;stroke:#000000;stroke-width:1; stroke-linecap:round;stroke-miterlimit:4; stroke-dasharray:none;stroke-dashoffset:0; stroke-opacity:1" id="path7567" sodipodi:cx="-17.172863" sodipodi:cy="236.15866" sodipodi:rx="125.25864" sodipodi:ry="125.25864" d="M 108.08577 236.15866 A 125.25864 125.25864 0 1 1 -142.4315,236.15866 A 125.25864 125.25864 0 1 1 108.08577 236.15866 z" />
(M コマンドは現在位置を移動する。A コマンドは、現在点を始点としたうえで、終点を定め、それらを結ぶ楕円の弧を描く。これを描くために、楕円の二つの半径、円弧を楕円のどの部分から切り取って使うのかを示す角度(あるいは、楕円を回転させる角度)、右まわりに行くか左まわりに行くかを示すフラグ、長いほうの弧を使うか短いほうの弧を使うかを示すフラグ、という情報を用いる。面白いことに、楕円の二つの中心(円ならばこれらは同じ点になる)の座標データは使われない。)
だが、Inkscape SVG は、この円を特定するために必要な情報をそっくりもう一組み保持している(赤字または太字部分)。これは、楕円の中心と二つの半径を持っている。
実際に Inkscape を使ってみてわかることは、Inkscape はファイルの読み込み時に後者の情報を利用していて、前者の情報はたんに保存時にファイルが SVG としても読めるように付加されているらしいということである。
Inkscape は独自のエレメント定義や属性定義を使うために、最上位にあたる svg エレメントの中で名前空間を追加している。
<!-- Plain SVG で保存した場合の名前空間 --> xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
<!-- Inkscape SVG で保存した場合の名前空間 --> xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://web.resource.org/cc/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns 属性は XML名前空間を識別するために用いられる。Inkscape はこの属性を厳格にチェックするので、手書きした SVG ファイルを Inkscape で読み込むような場合には注意が必要となる。
SVG でデフォルトの名前空間は http://www.w3.org/2000/svg である。しかし、名前空間の宣言が xmlns="http://www.w3.org/2000/svg" だけで済むとは限らない
たとえば、use エレメントを用いて、パスを再利用するような場合、 href という属性を使い、他の部分ですでに定義したパスを引用する(この仕組みは Inkscape の GUI インターフェースで「クローン」と呼ばれている)。しかし、ここでの href という属性はデフォルトの名前空間にはなく http://www.w3.org/1999/xlink という名前空間に属している。
そのため、use エレメントかそれより上位のエレメントで(たいがいは svg エレメントで)xmlns:xlink="http://www.w3.org/1999/xlink" のように名前空間の接頭辞を宣言しておき、use エレメントの中では xlink:href="#g2115" のように用いる必要がある。
Inkscape の拡張スクリプト
Inkscape はスクリプトを書いてやることにより、容易に拡張できる。スクリプトは、起動されるタイミングにより三つに分類できる。
(A) ファイルを読み込むとき
(B) ファイルを書き出すとき
(C) 編集中
上でいう (A) (B) の場合、ファイルを開く/保存する時にファイルタイプの判別/選択により起動されるので、ユーザはそれが外部スクリプトであることに気付かないかもしれない(というか、気付かないよねふつう)。もし Inkscape がどういうファイル形式を読み/書きできるのかに関心があるのなら、この種類のエクステンションが何をしているか(たとえば、どんな外部プログラムに下請けに出しているか)を知っておく必要がある。さいごの (C)はメニューに現れるので、すぐにそれとわかる。
エクステンションを実現手段により分類すると、
(甲) C や C++ による内部的な方法。いわゆるネイティブで対応していると考えられているものは、これを使っているそうで、そういう意味では一般にエクステンションと考えられているものとは違うかな。
(乙) SVG を標準入力から受け取り、あらたな SVG を標準出力に出力すような形で書くスクリプト向けのやり方。これは、どんな言語で書いてもかまわない。Python が多く使われているのは、xml の処理に使いやすいという理由だけだろう。
(丙) XSLT による方法
(丁) DOM による方法
がある。
参考、http://wiki.inkscape.org/wiki/index.php/ExtensionArchitecture#Functionality_Provided
以下、上記 (C)-(乙) のセンに沿って、Inkscape の拡張をやや詳しく見む。
inx ファイル
わしらが書くべきものは、xxx.inx という設定ファイルと、本体のスクリプトの二つである。これらは、わしの環境では /usr/share/inkscape/extensions ディレクトリに置いてある。Inkscape は起動時に xxx.inx ファイルを見つけ出して、エクステンション名をメニューに登録する。inx ファイルには、本体スクリプトの場所や、ユーザオプションを問い合わせるためのダイアログなどが定義してある。
スクリプト起動まで
ユーザから拡張機能が呼び出されると、Inkscape はそれぞれの拡張機能に対応した inx ファイルを読む。それにより、ユーザからオプションの値を得る必要があるかどうかを知り、必要があればダイアログを出し、値を獲得する。
つぎに、inx ファイルに指定されている SVG 処理用のスクリプトを起動し、パイプ経由で現在の SVG ファイルの内容を標準入力に流し込む。このとき、選択された状態にある SVG オブジェクトの id と、ダイアログボックスにより獲得したオプション名と値が、--opt1 value1 --opt2 value2 の形で、コマンドラインオプションとしてスクリプトに渡される。
スクリプトは、最終的に書き換えられた SVG を標準出力に出力し、Inkscape はこれでバッファを書き換える。
だから、コマンドラインで
$ cat old.svg | python script.py --id xxx --id xxx --your_opt xxx > new.svg
などとすることによって、SVG 処理用スクリプトのデバッグが可能である。上は Pythn の例だが、Perl でもシェル・スクリプトでもここまでは同じである。
Python による典型的な拡張スクリプト——全体の流れ
Inkscape には、inkex.py という Python のモジュールが付属してくる。エクステンションの多くがこれを利用している。これを利用したスクリプトにおける大まかな流れを見む。
スクリプトは最初に、inkex モジュールを読み込み、そこに定義された Effect クラスを継承した新たなクラスを定義する(以下、「わしらのクラス」とする)。スクリプトが実行されるとこのインスタンスが作成され(以下「わしらのインスタンス」)、つづいて、そのインスタンスの affect メソッドが呼び出され、スクリプトが終了する。これがすべてである。以下、その過程で起こることを漸々たどってみむ。
わしらのインスタンスの初期化
inkex モジュールは、コマンドラインオプションの受け取りのために、optparse モジュール(参考)をインポートしている。これはオプション解析のためのライブラリで、Effect クラスはこれを使って次のようにコマンドライン・オプションを処理する。
(ア) まず、OptionParser クラスのインスタンスを作成する。
(イ)次にそのインスタンスに対して add_option メソッドを適用して、期待されるオプションの名前を教え込む。
(ウ) sys.argv[1:] でコマンドラインオプションをリストにしたものを得て、これを引数にして parser.parse_args メソッドを呼び出す。これは {オプション名:値,...} という辞書と、オプション名を持たない固定オプションのリストを返す。
(ア)と(イ)が Effect クラスの __init__ に定義されており、(ウ)が effect クラスの getoptions メソッドにより行われる。
ただし、Effect クラスが定義している __init__ では、--id オプションに対する準備(add_option)しか行わない。したがって、他のオプションも受け取りたいのなら、わしらのクラスで、 __init__ メソッドを上書きで定義してやる必要がある。
たとえば、inx ファイルに書かれたユーザへの問い合わせが
<param name="mynumber" type="int" min="0" max="100" _gui-text="Number which you like.">7</param>
と定義されているならば、MyClass は、
class MyClass(inkex.Effect): def __init__(self): inkex.Effect.__init__(self) self.OptionParser.add_option("--mynumber", action="store", type="int", dest="mynum", default=7, help="Numbe which we like")
のようになるだろう。なお、値が複数並べられることを予想している場合は、add_option の引数中で、action="append" にして、default=[] などにすればよい。
affect メソッドの仕事
Effect クラスに定義されている affect メソッドは次のような順番で仕事をする。
(1) コマンドラインからオプションを受け取り解析して self.options に収め、残りの固定引数を self.args に 収める。
(2) 標準入力から得た SVG をパースし self.document に収める。これは、xml.etree.ElementTree.ElementTree クラス(参考)のインスタンスである。
(3) カレントレイヤーを得ようと試みカレントレイヤーがあればその名前を self.layername に収め、レイヤー(実体はグループ・オブジェクト)を self.layer に収める。カレントレイヤーがなければ、ルートを self.layer に収める。
(4) 選択された状態にあるノードを収集して self.selected 辞書に id をキーとして収める。(ここでは「ノード」といっても、SVG の path のコントロールポイントのことではない。SVG をツリー構造として見たときのノードのことであり、SVG の各オブジェクトのことである。ついでながらいうと、Inkscape は、選択中のコントロールポイントを拡張スクリプトに知らせる手段を持っていないようだ)
(5) ドキュメント中のすべての id をキーとする辞書 doc_ids をつくり、値をすべて 1 にセットする。リストじゃなくて辞書を使うのはきっと一意性の保証のためなんだろう。値はまあどうでもいいわけだね。
(6) self.effect メソッドを実行する。このメソッドの中身は何もない。
(7) SVG を標準出力に吐き出す。
実際に SVG に変更を加えるのは (6) の effect メソッドであるが、Effect クラスでその定義は空になっている。とうぜん、各自がそれを継承したクラスにおいて上書きして定義することが期待されているのである。
したがって、われわれは「(1),(3)〜(5) までにおいて得られたさまざまな SVG のオブジェクトを手がかりに (2) で得られた self.document に変更を加えるような effect メソッド」を定義すればよいということになる。おそらく ElementTree クラスに対する操作(参考)をいろいろと使うことになるだろう。
おまけ——長方形や円をシェアにより分割する Inkscape の拡張スクリプト
長方形から帯グラフを、円から円グラフを作成する拡張スクリプト。(Ubuntu 上で Inkscape 0.46 を使って試しただけ)
インストール chart.inx、chart.py を /usr/share/inkscape/extensions 等に放り込んで、Inkscape を立ち上げる。
使用方法
(1)長方形(または円)と、数字のリストを 1 行に書いたテキスト、を用意し、その両方を選択。
(2)エフェクト・メニューの「パスを変形」から Chart を選択
テキストの数字にしたがって、シェアに基づき分割される。
テキスト中に % 文字があると、数字はすべてパーセントと解釈され、残余項目が自動的に計算される。
他のファイル形式の読み書き
Inkscape は、さまざまなファイル形式の読み書きができるが、その多くは外部プログラムを利用して SVG 形式に変換しているのである。ユーザはそれと気付かずに利用することができる。Linux 環境の話であるが、Windows 版でも dill 版の python などが(たしか)標準でついてくるので、これを利用してかなりのことが行えると思うが、実際確かめてないよ。
Adobe Illustrator 形式
読み書きとも外部エクステンションを利用。
読み込み:ai_input.inx の指示により、処理が perl のスクリプトである ill2svg.pl (Inkscape についてくる)で行われる。これは、シェルからも使える独立したもので、多量に処理するときにはいいかも。でも試してない。
書き出しは、ai_output.inx の指示により処理が Ghostscript により行われる。コマンドは gs -q -dNODISPLAY -dSAFER ps2ai.ps
Adobe Illustrator の SVG
読み込みはネイティブで対応。ただし……
Illustrator の人が作った SVG のサイズがひどく大きいので、エディタで開いてみた。やたら未知のエレメントがあて、何がなんだかわからぬ。未知の名前空間がどっさり定義されていて、デフォルトの名前空間ですら、見なれた例のやつではない。
http://wiki.inkscape.org/wiki/index.php/FAQ#I_exported_an_SVG_file_from_Adobe_Illustrator.2C_edited_it_in_Inkscape.2C_and_imported_back_to_AI.2C_but_there_my_changes_are_lost.21
(和訳している人いた http://wikiwiki.jp/inkscape/?FAQ#uc6ab4c7)
てな頁を発見。ようするに、(1) Illustorator が生成した SVG は、SVG として読むことができる (2) しかし、独自定義のエレメントを追加して、Illustrator 用のバイナリをそっくり保持している、というのだ。ちなみに、Illustrator で保存するときに、Preserve Adobe Illustrator Editing と Optimize for Adobe SVG viewer のチェックを外せば、この太っちょな形式にはならないらしい。
Inkscape には XSLT で Illustrator が作った重い SVG 書類から不要な部分を削除するスクリプトがついてくる。私の環境では、/usr/share/inkscape/extensions/aisvg.xslt というのがそれだ。これは、「.ai.svg」 で終わるファイル名を読み込むときにフィルターとして作用するよう設計されているらしいが、私のところでは、ファイル名をそのやうに変えても機能しなかった。(※これは Inkscape 0.46 で使えるようになったもよう。リリースノートに This is used to support the XAML file format (both import and export) and the Adobe Illustrator SVG import which removes Adobe's stuff from SVG. とある)(余談:この inx ファイルのDescription によると、「Adobe Illustrator SVGs を開く前に cruft を削除する」と書いてある。cruft をリーダーズ(第 2 版)で引くと、「いやなもの; 粗末な作りの結果」とある。)
http://wiki.inkscape.org/wiki/index.php/ExtensionArchitecture#Implementation_Types によると、XSLT プロセッサには libxml のやつをリンクして使っているそうだが、I am not aware of the status of the XSLT implementation. としっかり書いてあるから、未実装なのかなあ。実際にやってみたけど動かなかったよ。仕方ないから、自前の xsltproc で、
$ xsltproc aisvg.xslt large_ai_file.svg > modest_one.svg
などとやってやったら、たとえば 1.9M あるファイルが 602 KB になった。
Plain SVG
W3C が定義しているエレメントや属性だけを SVG 書類だけ(ちょっと例外あり)を使った形式を、Inkscape では、Plain SVG と呼んで、デフォルトの Inkscape SVG と区別している。読み込み、書き出しともネイティブで対応。
Inkscape SVG を読み込んで、Plain SVG で保存し直すといくらかの情報が落とされて、ファイルサイズは小さくなる。ただ、たとえば、Adobe Illustrator で作成した SVG を読み込んで、Plain SVG で保存してもサイズは(ほとんど)変わらない。予想していない名前空間に属するエレメントや属性は Plain SVG で保存するときに、そっくり残しておいて安全を図っているらしい。
以下は、接頭辞のないエレメントと、接頭辞が svg のエレメント以外をすべて削除してして無理やり Plain SVG っぽいものにする XSLT スクリプト。(属性のほうには手を触れない。)なお、Adobe Illustrator の SVG の減量については、最初から Inkscape におまけ XSLT スクリプトがついてくる。
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" > <xsl:output method="xml" indent="no" /> <xsl:template match="/"> <xsl:apply-templates select="svg:svg" /> </xsl:template> <xsl:template match="@*|svg:*"> <xsl:copy> <xsl:apply-templates select="@*|svg:*" /> </xsl:copy> </xsl:template> </xsl:stylesheet>
参考
http://www.w3.org/TR/1999/REC-xslt-19991116#copying
(同邦訳 http://www.infoteria.com/jp/contents/xml-data/REC-xslt-19991116-jpn.htm#copying)
http://www.asahi-net.or.jp/~ps8a-okzk/xml/xslt10_1/copying.html の使用例
たとえば、プロセッサに xsltproc を使うなら、
$ xsltproc script.xsl old.svg > new.svg
てな感じで。うまくいくかどうか保証の限りにあらず。
Postscript
書き出しはネイティブで対応。
読み込みは外部エクステンションを利用。ようするに、「Postscript --(pstoedit)--> sketch,skencil 形式 --(skconvert.py)--> SVG 形式」ということですな。Inkscape が Postscript を読み込めないようなら、pstoedit と skencil をインストールしろということです。細かく見ていくと……
ps_input.inx の指示により pstoedit -f sk を実行。sk というのは、Sketch Format のこと。Scketch Format とは、以前 sketch と称されえた skencil というドローイング・プログラムで使う形式のこと。次に、ps_input.inx は、その Scketch Format を sk_input.inx という inx ファイルに任せる。この inx ファイルは、sk2svg.sh というシェルスクリプトを起動。これは skconvert というプログラムを起動するが、この skconvert というのは skconvert.py という python のスクリプトで、sckencil をインストールすると同時にインストールされるもの。これによって SVG ファイルに変換される。あーめんどい。
Dia 形式
Dia というのは、ダイアグラムエディタ。フローチャートなんか書くのが楽なやつ。Inkscape にもコネクタというのがあるが、Dia のほうが特化しているのでよい場合がある。
読み込みは dia.inx が dia2svg.sh に任せる。dia2svg.sh は、なんのことはない Dia を起動して SVG で保存するだけのもの。最初から Dia で SVG 保存したのと何ら変わることなし。
書き込みはできない。
DXF 形式
CAD が使うファイル形式だそうだが、私には未知のフォーマット。
読み込みは dxf_input.inx が dxf2svg というプログラムに下請けに出す。このプログラムの正体は私には不明。
書き出しは dxf_output.inx に指示が書いてあって、まず Postscript で出力してから、ps2dxf.sh という Inkscape 付属のシェル・スクリプトを使う。このシェル・スクリプトは pstoedit -f dxf をやるだけのもの。
書き出しらしきものにもう一つあって、dxf_outlines.inx が dxf_outlines.py という Inkscape についてくるプログラムを起動。これが何なのかは、私にはさっぱりわからない。
その他
まだまだいろいろあるが、またの機会に。
単位のこと
どうも、ピクセルという単位が気持ち悪いので、メモを作成して整理。(私の誤解、胡麻化し、誤解を生む表現などに満ちているはず。正しい情報は SVG と CSS2 の仕様書から得て頂きたく存じ候)
まず、あたり前の話
ピクセルとは、一般に出力デバイスが作りうる最小限のユニットということになってゐるやうである。つまり、それは「物」であり、一個二個と数えるものである。一方、この「物」の一辺の長さを一単位として、長さを測ることが行われる。それを○○ピクセルと表現し、この場合はピクセルは「物」ではなく、長さの単位である。とうぜん、1 ピクセルが何ミリメートルにあたるかは、出力デバイスに依存する。また、出力デバイスの解像度などを表現するために、1 インチあたりにいくつのピクセルが入るかという表現が行われる。dpi というのがそれである。ここで dpi が既知であれば、ピクセルをいくつつなげた長さであるかによって、出力デバイスに依存しない絶対的な長さを表現することができる。90 dpi で 90 ピクセルの長さといえば、1 インチというのと同じである。このように dpi とピクセル数で長さを表現するというのは、広く行われている。
Inkscape の仕様は質素か
私の環境の Inkscape は、1/90 インチを 1 ピクセルと定義し、これをデフォルトの単位としている(ビルドによってこれが異なるのかどうか、私は知らないので、以下読み替える必要があるかもしれない)。この関係は Inkscape では常に一定であり、設定し直すことはできない。これは、人気ペインティング・ツールの Gimp で、長さの単位であるピクセルとインチの関係を自由に設定することができるのに比べて、一見質素な仕様のように見える。
CSS2 にいうピクセル
まず、Inkscape は SVG 準拠である。その SVG にはピクセルというときは CSS2 の定義によるとある。CSS2 では、ピクセルを出力デバイスの解像度に依存しない単位として定義し、出力デバイスの最小単位としてのピクセルのほうは、デバイス・ピクセルと呼んでこれを区別している。
では、CSS2 では 1/90 インチを 1 ピクセルの長さと定義しているのかというと、そう単純なものではない。CSS2 は 1 ピクセルを「90 dpi の解像度をもつ出力における 1 デバイス・ピクセルを 28 インチ離れたところから見たのと同じ視角になる長さ」と定義している。
視角とは何ぞや
視角(visual angle)というのは、対象の端から端までが視る者にとってどのくらいの角度の中におさまって見えるかということで、これは対象の大きさと対象までの距離に依存する。
対象の大きさ、対象までの距離と視角の関係は
視角 = 2 arctan (対象の大きさ / 2 / 対象までの距離)
のようになる。(図に描くと単純なことなんだが、難しそうに見えるぞ)
28 インチ離れたところから、1/90 dpi の出力デバイスによる 1 デバイス・ピクセルを眺めると、視角は約 0.0277 度である。つまり、これが CSS2 のピクセルの正体だ。
(ここで、細かいツッコミをさせてくれ。CSS2 の規格では 1 ピクセルの長さを定めて、それが 10 個集まったら 10 ピクセルということになっているのであって、10 ピクセルの視角が 1 ピクセルの 10 倍の視角だと言っているのではない。もし、後者のような定義であったならば、視界の周辺部では 1 ピクセルの絶対的な長さがひどく大きなものになってしまい、ピクセルとミリメートルの変換なんてできたもんじゃない。それはそれで、プラネタリウム向きの規格かもしれないけどね)
ためしに、地球から太陽を見たとき何ピクセルになるか計算してみやう。地球から太陽までの距離はおよそ 1 億 4900 万 Km であり、この距離における 1 ピクセルの大きさは、7 億 2034 Km である。太陽の大きさは 139 万 Km であるから、これは 約 19.2 ピクセルにあたることになる。
ふたたび Inkscape のこと
そういうわけなので、Inkscape で 1 ピクセルが 1/90 インチに固定されていて、かつ、Inkscape が CSS2 に準拠しているということは、目と出力の距離に 28 インチが想定されていることを意味しているとも言える。
なお、「1 ピクセル = 1/90 インチ」というのは、Inkscape のディスプレイ上の表示にも、印刷の場合にも用いられる。もっとも、ディスプレイ画面の場合は、(プラットホームによって違いそうではあるが、私のところでは)解像度に 90 dpi のディスプレイ装置を想定していて、私の 85 dpi のディスプレイ装置では実際より若干大きく表示される。プリンタで印刷する場合には、たとえばポイントなどの絶対的な単位に換算して命令を送るなどするので、その長さは正確である。
ピクセルによる長さ指定の問題
版下(製版するための原稿)を作成するような場合、CSS2 に定めるピクセルで長さを指定すると困ることになる。CSS2 のピクセルで表現されたものは、目から出力物までの想定された距離に応じて出力物のサイズが変わってしまうからだ。こうした場合、ミリメートルやインチなどの絶対的な長さを表わす単位が望ましい。
ところが Inkscape では、デフォルトの単位をピクセルからミリメートルやインチに変えることはできない(「本当?」)。どうするか。
(じつのとろ、CSS2 が定めるピクセルというのは、出力デバイスに合わせて若干の出入りを許すものであるから、この面からもピクセルの使用は版下作成には向かない。一方で、ピクセルで指定しておいたほうが有利な場合もある。解像度の低いデバイスで出力する場合には、整数個のデバイス・ピクセルをまとめて CSS2 のピクセルを表現したほうが美しい。単位をピクセルのまま用いておけば SVG を読み込んだユーザ・エージェントが出力が美しく見えるようにうまく換算してくれる可能性がある。CSS のピクセル定義に若干の出入りが許されているのはこのためである)
そもそも Inkscape のデフォルト単位は本当にピクセルなのか
いきなり変なことを言い出すなら、Inkscape において、そしてそれが準拠している SVG において、ユーザがお絵描きする空間におけるデフォルトの単位はピクセルなんかではない。それは本当は、ユーザが勝手にこしらえた単位であって、SVG ではこれをユーザ・ユニットと呼んでいる。
ユーザ・ユニットで表現されたユーザ空間は、印刷される領域たるビュー・ポートと呼ばれる矩形内に写像される。このとき、初期(initial)の状態では、ユーザ・ユニットはピクセルに等しいと仮定したうえでビュー・ポートに写像されるから、これを簡単に「デフォルトはピクセルである」と言っているわけである。
また、「ユーザユニットはピクセルである」という初期の仮定を、「ユーザユニットはミリメートルである」とか「インチである」とかには変更できない。このことを簡単に、「デフォルト単位を変更することはできない」と言っているのである。
しかし、これはあくまでも初期の仮定であり、実際には SVG が読み込まれた後に起こる複雑な過程によって、ユーザユニットはさまざまに解釈されることになる。典型的には、SVG をより上位の SVG に埋め込んだ場合で、上位の SVG との交渉(ネゴシエーション)が起こり、これが変更される。
ビュー・ボックス
SVG 書類の製作者はユーザ空間をどうビューポートに写像するかを明示的に定義してやることも可能である。ビュー・ボックスと呼ばれる矩形を設定すると、ユーザ空間からビュー・ボックスで切り取られた範囲がビュー・ポートにフィットするように写像される。これにより、デフォルトの写像のやり方を変更することができる。
たとえば、ビューポートの大きさが 210 mm × 297 mm (A4) のとき、ビューボックスの大きさを 210 ユーザユニット × 297 ユーザユニットに指定しておけば、ユーザ空間における 1 ユーザユニットは、ビューポートにおける 1 mm に写像される。
ビューボックスとビューポートの縦横比が異なる場合、前者を後者にフィットさせるやり方がいくつか考えられる。SVG では、preserveAspectRatio と呼ばれる属性を svg エレメントに与えることにより、フィットの仕方をコントロールできることになっている。ただし、Inkscape 0.43 では、preserveAspectRatio 属性はサポートされていないし、この属性を指定しなかった場合の動作も SVG 通りにはなっていない。そのため、現在のところビューポートとビューボックスのそれぞれの縦横比が完全に等しくなるようにしておくのが無難であろう。
もっとも、Web 上で画像を公開するような場合、正確なサイズよりもユーザエージェントの計算量の削減が優先されるだろうから、ビューボックスを使わずに表示できるようにしたほうがいいように思われる。
なぜか、Inkscape の GUI メニューからはビューボックスを定義することができない。「編集」メニューから XML エディタを引っ張り出してきて手書きで加える必要がある。これはあるいは、複雑な画像の場合にビューボックスを作っておくことより生じるユーザエージェントの負担増を嫌っているということなのかもしれない。
余談ながら、ごく一般的な使用法においても、ビューボックスを追加しておくとよい場合がある。ビューボックスを作成しておくと、GUIメニューから「用紙サイズ」を変更しただけで、自動的に作画内容が用紙にフィットするように拡大されるからだ。こうしたことが SVG ファイルの受け取り手の便宜にかなうこともあるだろう。たとえば、この白地図はA4判だが、Inkscape でこれを開き、GUI メニューで用紙サイズをA3に変更しただけで、そのままA3判の白地図として利用することができる。
「でも、Inkscape には単位を設定するメニューがあるぜ」
変形ツール(黒矢印)選択時には、ツールコントロールバーに選択されたオブジェクトの位置と大きさが表示され、数字を入力することができる。この右側に、ピクセル、ミリメートルなど単位を選べるメニューがある。
このメニューで単位をミリメートルにしたとしても、内部的には相変わらずピクセルが用いられている。そして、保存したファイルをのぞいてみても、単位はピクセルのままである。これは、ユーザインターフェースの気配りというやつで、ユーザに示すときだけ選択された単位に換算した数字を見せているのである。
ついでに言っておくと、この欄に示される寸法は、パスの輪郭をストロークした外縁の大きさであり、x-min と y-min は原点をページ(ビューポート)の左下にして測ったものである(内部的にはビューポートの左上が原点)。
また、Inkscape 0.44 で、Document Properties ダイアログを開くと、Default Units という設定項目があり、mm とか inch とかさまざまな選択肢がある。しかし、これもやはり上記の設定メニューと同様に、ユーザに見せる単位だけお好みの単位に換算(1 ピクセル=1/90インチで)してくれるだけの話だ。
次のようなケースを考えてみよう。たとえば、Inkscape の Dafault Units を mm に設定し、10 mm の線を作成したとする。これを SVG ファイルに保存し、そのファイルを 1 ピクセルを 2/90 インチに換算する別のユーザエージェントで読み込むと、この線の長さは 20 mm で出力されるはずである。
「ビューポートのサイズをミリメートルにしとけばビューボックスはいらねんじゃない」
ビューポートのサイズをミリメートルで指定しただけで、ビューボックスを作らないでおくと、ユーザ空間における 1 ピクセルが何ミリメートルに当たるかということについては、ユーザエージェント任せになってしまう。Inkscape 以外のユーザエージェントでの読み込みを考慮すると不安。
かりに、ユーザエージェントが SVG に準拠していたとしても、CSS2 では 1 ピクセルの絶対的な大きさは出力デバイスの物理的な条件によっていくらか伸縮してもよいことになっているので、やはり不安が残る。
ビューボックスのかわりに「transform 属性を使うというのはどうよ」
そのうち書くよ。
SVG から見た Inkscape の機能
クローンとコピー
クローンとコピーは異なる。あるパスをコピーすると、オリジナルとそっくり(しばしば位置だけ異なる)の path エレメントがもう一つ作成されるのに対して、クローンの場合は use エレメントと使って、オリジナルのオブジェクトが引用されるに過ぎない。
オリジナルのパスをそのまま移動させたり拡大してもクローンの大きさには変化がないが、オリジナルのパスを構成するノードのうちの一つを動かしたり、ノードやセグメントの削除・追加を行うと、これはすぐさまクローンにも反映される。
これは、use エレメントは、オリジナルのオブジェクトとはべつに、独自に transform 属性を持っているからである。
レイヤー
レイヤーは、SVG に規定がない。Inkscape のレイヤーは、SVG 的にはただのグループに過ぎない。Inkscape では、グループオブジェクトに独自の名前空間で定義したプロパティを追加してユーザインターフェース上で特別な扱いがなされるようにして、それをレイヤーと呼んでいるのである。
Inkscape で作成したレイヤーつきの文書を Plain SVG で保存し、これを再度 Inkscape で開くと、独自定義のプロパティが失われた結果、一般的なグループが (root) 上に載っている状態として読み込まれる。
一般的なグループをレイヤにするためには、XML エディタを使い、グループオブジェクトに inkscape:groupmode プロパティを作成し値を layer にセットして、さらに inkscape:label プロパティを作成し値に任意のレイヤー名を与えてやればよい。(GUIファンは「グループの切取り、新レイヤ作成、同じ場所にペースト、グループ解除、レイヤの階層を移動」)
レイヤーがグループオブジェクトである以上、入れ子にすることができるかもしれないと思い、Inkscape 0.44 で試みてみた。できた。ウィンドウのレイヤー表示部には親レイヤーも子レイヤーも同等に表示されるが、親レイヤーを不可視にすると、それは子レイヤーにも及び、親レイヤーを削除すると子レイヤーも削除される。
変形
オブジェクトの変形・移動方法は大別すると二種類ある。一つは、オブジェクトを構成するコントロール・ポイント自体の位置情報を変えること。もう一つは、オブジェクトに、transform 属性を加えて移動・変形させることである。
SVG に定めのある transform 属性は、matrix, translate, scale, rotate, skewX, skewY という 6 種類の "transform difinitions" (いい訳思いつかぬ。変換指定といったところ)によってどう「変形」するかを指定することができる。これは、冗長な指定で translate, scale, rotate, skewX, skewY は、みな結局 matrix ひとつで指定できる変換である(ただし、計算を要する)。Inkscape にはこの matrix を編集するための GUI メニューも用意されている。
マウスを用いたオブジェクトの移動や回転はこれら二通りのやり方のうちのいずれかを使うが、どちらを使うかは Inkscape の設定によって決まる。
なお、transform 属性を用いた場合、オブジェクトを拡大すると、輪郭線も太くなるのが SVG 通りであるが、Inkscape では、輪郭が太くなったり細くなったりしないよう輪郭線の太さを調節するよう設定することもできる。
文字コード
SVG1.1 では、XML1.0 の文字を使うということになっている。(http://www.w3.org/TR/SVG11/text.html)。XML の規定(http://www.w3.org/TR/REC-xml/)を見ると、すべての XML プロセッサは、UTF-8 と UTF-16 を理解できなくてはならぬ、とある。そして、Inkscape のデフォルトが UTF-8 であり、何も考えずに Inkscape で新規書類をつくり、日本語を入力すると UTF-8 を用いて保存される。
こうした SVG ファイルを、UNICODE をサポートしていないテキスト・エディタで開いて、変更を加えて保存したりすると、悲惨なことになるかもしれない。(事実、そうなったことあり)。
とはいっても、SVG は必ずしも UTF-8 を使わなければならぬというわけではない。XML のほうでは、"ISO-2022-JP", "Shift_JIS", and "EUC-JP" のことも書いてあり、じじつ SVG ファイルの先頭に
<?xml version="1.0" encoding="Shift_JIS"?>
などと書いておいたら、テキストエレメントの中身がシフト JIS で書かれても Inkscape (少なくとも私の使っている Linux 版 Inkscape 0.44)はちゃんと読み込んでくれた。
しかーし! そのファイルを Inkscape の GUI を使って変更後再度保存したら、しれっと UTF-8 に戻っていました。じつはデフォルトにするコーディングシステムをどこかで設定できるのかもしれないけれど、私にはわからぬ。やっぱり Inkscape では UTF-8 が無難か。
テキストの流し込み
Inkscape は flowed text の機能を持ってはいる。flowed text というのは、「流し込まれたテキスト」。
SVG 1.1 には flowed text について定めていない。Inkscape の flowed text は SVG 1.2 の古い版の草稿をもとにしているので、今後 SVG 1.2 が確定しても、それに沿っている可能性は低い。(http://wiki.inkscape.org/wiki/index.php/FAQ#What_about_flowed_text.3F 参照)
テキスト流し込みは、Inkscape が SVG に準拠していない部分である。もちろん、他のユーザ・エージェントがきちんとこれを解釈する可能性は低い。
いちおう現段階の SVG 1.2 の draft くらいは見てみよう。http://www.w3.org/TR/2004/WD-SVG12-20041027/flow.html
http://wiki.inkscape.org/wiki/index.php/FAQ#What_about_flowed_text.3F では、Inkscape の flowing text を用いた SVG ファイルを他のエージェントで読み込ませるならば、あらかじめ「テキストに変換」を用いて flowing text を使わないように変換しておくよう勧めている。(私はこれを試してみたが、うまくいかない場合があった。なぜだかまだ調べていない)。
いちおう Inkscape で作成した SVG のコードを見てみる。(これは古い情報かもしれない)
flowRoot オブジェクトの中に flowRegion オブジェクトと flowPara オブジェクトがある。flowRegion オブジェクトの中に rect などの描画されるオブジェクトがあればよいらしい。典型的な階層関係は、たとえば
flowRoot flowRegion rect circle etc. ...... flowPara
てな具合だ。flowPara オブジェクトは流し込まれる文字列を保持し、flowRegion の子要素の rect や circle ... は、文字列が流し込まれるオブジェクトである。
背景色
SVG には背景色という概念がない。背景色はユーザエージェントに任されている。
一方、Inkscape の Document Properties ダイアログ・ボックスには、General のセクションに Background という項目があり、背景色とその不透明度を設定できるようになっている。
この「背景色」は、SVG の規格にはない情報を Inkscape が書き加えているのである。これを Inkscape SVG で保存したものを見ると、たとえば次のように保存されていることがわかる。
<sodipodi:namedview (中略) inkscape:pageopacity="0.7254902" (中略) bordercolor="#666666" (中略) />
このエレメントは SVG 外であるから、他のユーザエージェントで読み込んだ場合には、ほとんどの場合無視される。また、Inkscape でも Plain SVG で保存すると、この背景色情報はファイルに書き込まない。
マーカ
SVG では、シンボルというエレメントでひながたとなる図形オブジェクトを定義して、use エレメントによってこれを引用(正確には instantiate)することができる。同じ図形を複数回使うような場合、この symbol と use を使って実現すると楽ができる。
さらに、SVG は矢印の頭(鏃:やじり)を実現するために、シンボル機能を特殊化させた手段を用意している。こんな具合だ。
まず、marker というエレメントで、被引用側の図形を定義する。つぎに、path, line, polyline, polygon といった鏃をつけたいオブジェクト(を表現しているエレメント)に、marker-start, marker-end, marker-mid といったプロパティを与え、この値として先に定義したマーカの id を指定する。ここで、marker-start, marker-end, marker-mid の違いは、鏃を付加する場所の違いで、それぞれパス始点、終点、それ以外のポイント(ノード)に対応する。これで、鏃が実現される。(鏃はパスに対してつけられるものであり、サブパス——部分パス——に対してつけることはできない)
そんなわけで、矢印の棒の部分(パス)にストロークペイントを施しても、鏃(やじり)の部分の色が変わらない。たとえば、赤い箆(の)に黒い鏃(やじり)の矢ができてしまう。
XML エディタで、path エレメントの marker-end プロパティの値を読み、それが指す marker エレメントを探す(marker エレメントは、defs エレメントの下位に見つかるだろう。)当該 marker エレメントの fill プロパティの値は色を示しているので、これをいじる。ただし、Inkscape 0.44 Linux 版で試したところ、この変更は即座には画面に反映されない。一度保存して、再び読み込んでみると(「ファイルに復帰」)はじめて反映された。もっとも、これでは、以後同じ鏃(マーカ)を使うと、みなこの色が変わったものになってしまう。それが嫌ならば、該当する marker エレメントをコピーしてから変更し、その marker の id を鏃をつける path の marker-end プロパティに記してやればよい。
お急ぎの向きは、ストロークをパスに変換して鏃をフィルするのがいいかもしれない。
私の環境では、/usr/share/inkscape/tutorials/making_markers.svg というチュートリアルがインストールされていた。それによると、marker エレメントを書いて、marker.svg というファイル(私のとこでは /usr/share/inkscape/markers/markers.svg にあった)に追加しておくと、のちのち使いまわしができるとのこと。暇がないので試してないけど。ところで、このファイルをホームディレクトリ以下に置くことはできるのかなあ?
パスを塗りつぶす
Inkscape で複雑な形のパスを思い通りに塗りつぶすためには、SVG がパスの「内側」と「外側」をどう定義しているのかを見ておく必要がある(よね?)。
SVG では、ポイントをつぎつぎにセグメントでつないで作ったひと続きの線をサブパスといい、パスは一つ以上のサブパスの集合であるとしている。Gimp なんかとマニュアルでの用語が違うので注意だ。
SVG では端点が閉じていないサブパスは、フィルを行う(塗る)ときには、端点が閉じていると仮定される。Inkscape で V 字型のサブパスを塗りつぶすと、逆三角になるのは、これによるもの。
SVG において、パスはその内側を塗りつぶすことができる。外側を塗りつぶすということはできない。これは、お絵描きをする面は無限の広がりを持っていて、プリントアウトされるのはたまたまその一部であるという SVG の考え方に対応しているのだろう。
パスの内側を塗りつぶすときのやっかいな問題は、パスが自分自身を横切っていたり(NTTのマーク)、内部にサブパスを含んでいたり(ドーナツ型)する場合に起こる。「内側を塗る」のであるから、これはキャンバス上の任意の点を当該パスの「内側」と考えるか「外側」と考えるかという問題だと言い換えることができる。
内側・外側を決めるのは、パスを構成するセグメントの方向(自身と交じわらない閉じたサブパスの場合、これは時計まわりとか反時計まわりとかいう問題である)と、fill-rule というプロパティが考慮される。
SVG は、path エレメントと polyline エレメントは fill-rule プロパティを持つことができると定めている。fill-rule プロパティは nonzero もしくは evenodd という値をとる。これらの値の示す意味は以下の如し。
キャンバス(無限の広がりを持つのだ)上の任意の点から、無限の彼方に向けて半直線を引くことを想像する。SVG はこれを ray と表現している(たんに数学用語で半直線のことを ray というわけなのだが、光線だから方向があると考えると趣き深いか)。
ray は、はじめ 0 というカウントを持っている。
ray の進行方向に向かって右手を右側、左手を左側と呼ぶことにする。
問題となるパスを構成するセグメントが(これは方向を持っているので) ray の左側から右側に横切る場合、カウントに 1 が加算され、右側から左側に横切る場合カウントから 1 が引かれる。
こうして ray が無限の彼方に到達したときのカウントによって、ray の出発点が当該パスの内側であるか外側であるかが定まる。
fill-rule プロパティが nonzero の場合、カウントが 0 なら外側、そうでなければ内側と定義される。
fill-rule プロパティが evenodd の場合、カウントが奇数なら内側、偶数なら外側と定義される。
SVG では、fill-rule の初期値は nonzero である。
いずれの方向に向けて RAY を照射するかによって、その点が当該パスの「内側」であるか「外側」であるかの決定に違いは起こらない。前述のごとく、サブパスは必ず閉じていると仮定されているからである。
次の例は、いちばん小さい円の内部が「内側」か「外側」かを調べるために、同心円の中心から RAY を照射してカウントを調べている様子の図。いちばん大きい円の円周まで RAY が達してはじめて、いちばん小さい円の内部が「内側」か「外側」かが定まる。
なお、1 を足しても引いてもそれが偶奇に与える影響は同じなので、fill-rule が evenodd であればはパスの方向はまったく問題にならぬということになる。
蛇足ながら、SVG で塗りつぶしが定義されているのは、あくまでも一つのパスに対してであり、複数のパスに対してではない。上に掲げた同心円の例では、円ひとつひとつをサブパスとして、これらを一つのパスに結合しある。
Inkscape での実装 パスの方向は、もっとも単純な例では、「ベジエ曲線/直線ツール」でポイントを打つ順番によって決まる。最後に打ったポイントを終端とするようにそのサブパスの方向が決まる。パスの方向を視覚的に確認したいと思うならば、ストロークスタイルで矢印を選択してやればよい。
Inkscape 0.44 からは、カスケードメニューのパスを引っ張ると「逆転」というのがあり、これを使うと方向を反転させることができる。
「円/弧」ツールで円や楕円などを作成すると、パスの方向は時計まわりになる。
evenodd か nonzero かを選択するためには、「フィル/ストローク」ツールの「フィル」タブを選択したときに右上に出てくるハートじるしのようなもの二種のいずれかを選ぶ。左側のが evenodd で、右側のが nonzero。あるいは、XML エディタを起動して手動で fill-rule プロパティを修正することもできる。
Inkscape では、fill-rule プロパティがないパスエレメントを読み込むと nonzero と解釈されるが、これは SVG 通り。