はじめに
Rustからベクターグラフィックスライブラリcairoを使おうとしたら、ドキュメントが不親切で少しはまりどころがあったのでここにメモを残しておきます。
今回はRustのcairoバインディングとしてcairo-rsクレートを使用します。
使用したバージョンは0.7.0です。今後APIが変更される可能性があります。
PDFで出力する
それではcairo-rsを使って何らかの描画結果をPDFに出力してみましょう。
cargoを使って適当な名前でプロジェクトを作り、Cargo.toml
を編集します。
[package]
name = "example-cairo"
version = "0.1.0"
authors = ["Your Name"]
edition = "2018"
[dependencies]
cairo-rs = { version = "0.7.0", features = ["pdf"] }
ここで気をつけておきたい点として、[dependencies]
の記述においてcairo-rsのpdf featureを有効にしておくことです。
これを書いておかないとcairo-rsのpdf
モジュールを利用できません。
ところでcrates.ioに登録されているRustのクレートのドキュメントはDocs.rsで閲覧することができますが、Docs.rsはクレートがfeaturesとして提供している機能のドキュメントはデフォルトでは生成しないようです。
Cargo.toml
の[package.metadata.docs.rs]
テーブルに適切な記述をすればfeaturesのドキュメントも生成されるのですが、2019年6月26日時点でcairo-rsはそれをしていません。
一方、Gtk-rsのサイトに設置されているcairo-rsのドキュメントではfeaturesのドキュメントも生成されていますからこちらを参照しましょう。
Context
構造体の作成
Cairoを使ってグラフィックの描画をする際にはContext
構造体に対する操作を通して描画を行います。
ドキュメントのContext
構造体のページを見ましょう。
Context
構造体を作るにはnew
メソッドを呼び出せば良いので、まずはContext::new
メソッドの宣言確認しておきます。
pub fn new(target: &Surface) -> Context
上記のようにSurface
構造体の参照を受け取ってContext
構造体を返します。
Surface
構造体が必要そうなのでドキュメントのSurface
構造体のページを参照したいのですが、こちらから探すとわかりづらいので先に答えを言ってしまいましょう。
PDFを出力するにはcairo::PdfSurface
構造体を使います。このPdfSurface
構造体のnew
メソッドの宣言は
pub fn new<P: AsRef<Path>>(width: f64, height: f64, path: P) -> Self
となっているので、適当なサイズとファイルパスを渡して呼び出してPdfSurface
構造体を得ます。
この構造体はDeref<Target=Surface>
トレイトを実装しているので、その参照をContext::new
の引数として渡すことができます。
ここまでに述べたことをコードにしておきましょう。src/main.rs
は次のようになります:
use cairo::*;
fn main() {
let surface = PdfSurface::new(600.0, 600.0, "test.pdf");
let context = Context::new(&surface);
}
このコードをビルドして実行すると、test.pdfに空白のPDFファイルができているはずです。
パスの描画
ここからはcairoに固有の知識が必要になります。
Cairoでパス(線)を描画するためには、Context
の内部で保持されている点をmove_to
やline_to
などのメソッドを使って動かしてから、stroke
メソッドを呼び出す必要があります。
また、パスを塗りつぶすにはset_source
メソッドでPattern
を設定してからfill
メソッドを呼ぶ必要があります。
細かな説明は省略しますが雰囲気を掴むために例を以下に書いておきます。
ソースコードは以下のURLのgithubリポジトリにも置いておきます。
https://github.com/ShotaroTsuji/example-cairo
use cairo::*;
fn draw_example(context: &Context) {
context.move_to(100.0, 100.0);
context.line_to(100.0, 150.0);
context.line_to(150.0, 100.0);
context.stroke();
context.move_to(300.0, 300.0);
context.rel_line_to(-100.0, 0.0);
context.rel_line_to(0.0, -100.0);
context.rel_line_to(100.0, 100.0);
context.close_path();
context.stroke();
context.set_dash(&[5.0, 2.5], 0.0);
context.set_line_width(5.0);
context.move_to(100.0, 400.0);
context.rel_curve_to(10.0, 10.0,
50.0, 20.0,
100.0, 0.0);
context.rel_curve_to(50.0, -20.0,
50.0, -60.0,
100.0, 0.0);
context.stroke();
let pat = SolidPattern::from_rgb(0.0, 1.0, 0.5);
context.set_source(&pat);
context.rectangle(500.0, 100.0, 50.0, 80.0);
context.fill();
context.set_font_size(30.0);
let ext = context.text_extents("Hello, world!");
let grad = LinearGradient::new(400.0, 400.0,
400.0+ext.width, 400.0+ext.height);
grad.add_color_stop_rgb(0.0, 1.0, 0.0, 0.0);
grad.add_color_stop_rgb(0.5, 0.8, 0.8, 0.0);
grad.add_color_stop_rgb(1.0, 0.0, 1.0, 0.0);
context.set_source(&grad);
context.move_to(400.0, 400.0);
context.show_text("Hello, world!");
}
fn main() {
let surface = PdfSurface::new(600.0, 600.0, "test.pdf");
let context = Context::new(&surface);
draw_example(&context);
}
まとめ
cairo-rsクレートを使ってみたのですが、個人的な感触としては割と生のままのバインディングという感じでRustっぽい感じはしませんでした。
今回作業している途中にcairo-rsのバージョンが0.6.0から0.7.0に上がったのですが、その際にAPIの変更が行われてしまい少し困惑しました。まだ1.0.0になっていないので変更されても仕方がないのですが……
それから、先ほど掲出したリポジトリ( https://github.com/ShotaroTsuji/example-cairo )
のsrc/bin/sakura.rs
はもう少し面白いサンプルを書きました。