Rustのcairoバインディングを使う

## はじめに
Rustからベクターグラフィックスライブラリcairoを使おうとしたら、ドキュメントが不親切で少しはまりどころがあったのでここにメモを残しておきます。
今回はRustのcairoバインディングとして[cairo-rs](https://crates.io/crates/cairo-rs)クレートを使用します。
使用したバージョンは0.7.0です。今後APIが変更される可能性があります。

## PDFで出力する

それではcairo-rsを使って何らかの描画結果をPDFに出力してみましょう。
cargoを使って適当な名前でプロジェクトを作り、`Cargo.toml`を編集します。

```toml: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](https://docs.rs)で閲覧することができますが、Docs.rsはクレートがfeaturesとして提供している機能のドキュメントはデフォルトでは生成しないようです。
`Cargo.toml`の`[package.metadata.docs.rs]`テーブルに適切な記述をすればfeaturesのドキュメントも生成されるのですが、2019年6月26日時点でcairo-rsはそれをしていません。

一方、Gtk-rsのサイトに設置されている[cairo-rsのドキュメント](http://gtk-rs.org/docs/cairo/)ではfeaturesのドキュメントも生成されていますからこちらを参照しましょう。

### `Context`構造体の作成

Cairoを使ってグラフィックの描画をする際には`Context`構造体に対する操作を通して描画を行います。
ドキュメントの`Context`構造体のページを見ましょう。
`Context`構造体を作るには`new`メソッドを呼び出せば良いので、まずは`Context::new`メソッドの宣言確認しておきます。

```rust
pub fn new(target: &Surface) -> Context
```
上記のように`Surface`構造体の参照を受け取って`Context`構造体を返します。
`Surface`構造体が必要そうなのでドキュメントの`Surface`構造体のページを参照したいのですが、こちらから探すとわかりづらいので先に答えを言ってしまいましょう。

PDFを出力するには`cairo::PdfSurface`構造体を使います。この`PdfSurface`構造体の`new`メソッドの宣言は

```rust
pub fn new<P: AsRef<Path>>(width: f64, height: f64, path: P) -> Self
```
となっているので、適当なサイズとファイルパスを渡して呼び出して`PdfSurface`構造体を得ます。
この構造体は`Deref<Target=Surface>`トレイトを実装しているので、その参照を`Context::new`の引数として渡すことができます。

ここまでに述べたことをコードにしておきましょう。`src/main.rs`は次のようになります：

```rust: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](https://github.com/ShotaroTsuji/example-cairo)

```rust:src/main.rs
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](https://github.com/ShotaroTsuji/example-cairo) ）
の`src/bin/sakura.rs`はもう少し面白いサンプルを書きました。