RustのWebAssembly で canvas に描画する

# RustのWebAssembly で canvas に描画する

rust の wasm-pack を使用して canvas に図形を描画する。

## 環境

- Ubuntu 18.04
- rustc 1.38.0

## wasm-pack をインストールする

wasm-pack
https://github.com/rustwasm/wasm-pack

[install](https://rustwasm.github.io/wasm-pack/installer/) にインストール方法が記載されている。

```shell-sesion
$ curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
```

## wasm-pack の node.js のプロジェクトを作成する

ドキュメントの [Getting Started](https://rustwasm.github.io/docs/wasm-pack/tutorials/hybrid-applications-with-webpack/getting-started.html) のコマンドを実行してプロジェクトを作成する。

```shell-sesion
$ npm init rust-webpack my-app
npx: 18個のパッケージを1.555秒でインストールしました。
 🦀 Rust + 🕸 WebAssembly + Webpack = ❤️
 Installed dependencies ✅
```

生成された package.json を確認する。

```json
{
  "author": "You <you@example.com>",
  "name": "rust-webpack-template",
  "version": "0.1.0",
  "scripts": {
    "build": "rimraf dist pkg && webpack",
    "start": "rimraf dist pkg && webpack-dev-server --open -d",
    "test": "cargo test && wasm-pack test --headless"
  },
  "devDependencies": {
    "@wasm-tool/wasm-pack-plugin": "^0.4.2",
    "copy-webpack-plugin": "^5.0.3",
    "webpack": "^4.33.0",
    "webpack-cli": "^3.3.3",
    "webpack-dev-server": "^3.7.1",
    "rimraf": "^2.6.3"
  }
}
```

`src/lib.rs` には console に `Hello wrold!` が出力されるコードが記述されている。

```rust
// This is like the `main` function, except for JavaScript.
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
    // This provides better error messages in debug mode.
    // It's disabled in release mode so it doesn't bloat up the file size.
    #[cfg(debug_assertions)]
    console_error_panic_hook::set_once();


    // Your code goes here!
    console::log_1(&JsValue::from_str("Hello world!"));

    Ok(())
}
```

`npm start` を実行すると Web サーバが起動する。

```shell-session
$ cd my-app/
$ npm start
> rust-webpack-template@0.1.0 start /tmp/my-app
> rimraf dist pkg && webpack-dev-server --open -d

🧐  Checking for wasm-pack...

✅  wasm-pack is installed.

ℹ️  Compiling your crate in development mode...

ℹ ｢wds｣: Project is running at http://localhost:8080/
ℹ ｢wds｣: webpack output is served from /
(省略)
Version: webpack 4.41.1
Time: 121ms
Built at: 2019-10-12 21:27:07
 3 assets
Entrypoint index = index.js
[./pkg/index.js] 4.38 KiB {0} [built]
[./pkg/index_bg.wasm] 145 KiB {0} [built]
    + 33 hidden modules
ℹ ｢wdm｣: Compiled successfully.
```

`http://localhost:8080/` を開くと console に `Hello world!` が出力される。
![console.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/509044/695a2a72-e973-b610-3108-88e680efa137.png)
## canvas に描画する

ドキュメントを参考にコードを実装する。
https://rustwasm.github.io/docs/wasm-bindgen/examples/2d-canvas.html

`Cargo.toml` の `web-sys`の項目を以下のように変更する。  
`features` に Rust から操作する要素の API を追加する。

```toml
# The `web-sys` crate allows you to interact with the various browser APIs,
# like the DOM.
[dependencies.web-sys]
version = "0.3.22"
features = [
  "console",
  'CanvasRenderingContext2d',
  'Document',
  'Element',
  'HtmlCanvasElement',
  'Window',
]
```

`static/index.html` に `canvas` を追加する。

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>My Rust + Webpack project!</title>
  </head>
  <body>
    <script src="index.js"></script>
    <canvas id="canvas"></canvas>
  </body>
</html>
```

`src/lib.rs` に `canvas` に描画するためのコードを追加する。

1. ドキュメントの `start` 関数の `#[wasm_bindgen(start)]` を削除してコピペ。
2. `main_js` 関数に `start()` を追加。

```rust
use std::f64;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::console;

// When the `wee_alloc` feature is enabled, this uses `wee_alloc` as the global
// allocator.
//
// If you don't want to use `wee_alloc`, you can safely delete this.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

// This is like the `main` function, except for JavaScript.
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
    // This provides better error messages in debug mode.
    // It's disabled in release mode so it doesn't bloat up the file size.
    #[cfg(debug_assertions)]
    console_error_panic_hook::set_once();

    // Your code goes here!
    console::log_1(&JsValue::from_str("Hello world!"));

    start();

    Ok(())
}

pub fn start() {
    let document = web_sys::window().unwrap().document().unwrap();
    let canvas = document.get_element_by_id("canvas").unwrap();
    let canvas: web_sys::HtmlCanvasElement = canvas
        .dyn_into::<web_sys::HtmlCanvasElement>()
        .map_err(|_| ())
        .unwrap();

    let context = canvas
        .get_context("2d")
        .unwrap()
        .unwrap()
        .dyn_into::<web_sys::CanvasRenderingContext2d>()
        .unwrap();

    context.begin_path();

    // Draw the outer circle.
    context
        .arc(75.0, 75.0, 50.0, 0.0, f64::consts::PI * 2.0)
        .unwrap();

    // Draw the mouth.
    context.move_to(110.0, 75.0);
    context.arc(75.0, 75.0, 35.0, 0.0, f64::consts::PI).unwrap();

    // Draw the left eye.
    context.move_to(65.0, 65.0);
    context
        .arc(60.0, 65.0, 5.0, 0.0, f64::consts::PI * 2.0)
        .unwrap();

    // Draw the right eye.
    context.move_to(95.0, 65.0);
    context
        .arc(90.0, 65.0, 5.0, 0.0, f64::consts::PI * 2.0)
        .unwrap();

    context.stroke();
}
```

`npm start` で起動した `http://localhost:8080/` を開くと `canvas` に絵が描画されている。
![drawing-canvas.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/509044/9d080cc1-8ed8-c9ec-a3b7-cae4714bfd0a.png)