AAをSVGで表示するWebComponentsを作成する、Stencilで。

この記事はWebComponent作成ライブラリStencilを使ってAA(アスキーアート)をSVGで表示するWebComponentを作成する記事です。

AAをSVGに変換することでテキストのまま、ズレなくレスポンシブに表示することができるようになります。

## Stencilについて
Ionicチームが作成したWebCopmonentを作成するためのライブラリです。
ざっくりいうと

* tsx(jsx + TypeScript)でWebComponentをかける
* Ionic4はStencilを使ってできている
* LazyLoadingやSSR、状態管理(Redux、Mobx)も使える

というライブラリです。今、React使ってる人なら瞬間で、使ったことない人も数分でWebComponentを記述できるようになると思います。

そして、ルーティングや状態管理、UIライブラリ(Ionic4)も使えるので簡単なWebアプリならStencilだけで作ることも可能です。


## SVGのWebComponentを作る
改めて今回はStencilを使っAAをSVGで表示するWebComponentを作ります。具体的に考える手順は以下の通りです。

1. StencilのWebComponentのスケルトンプロジェクトを立ち上げる
2. 新しいWebComponentを追加する
3. 文字列をSVGで表示するを作る

興味がある場合は引き続きこの記事を読んでみてください。


### 1. StencilのWebComponentのスケルトンプロジェクトを立ち上げる
Stencilで新しくWebComponentを作るなら以下のコマンドを入力します。

```bash
npx create-stencil component
```

プロジェクト名称とConfirmが聞かれますので、答えましょう。今回は「aa-svg」というプロジェクト名にしました。
![Screen Shot 2018-12-21 at 15.19.56.png](https://qiita-image-store.s3.amazonaws.com/0/165335/7d441d1e-8512-5dad-a9cd-8ea4c1d0addf.png)


プロジェクト生成完了後、作業ディレクトリを移動して`npm install`と`npm start`を行います。

```bash
cd aa-svg
npm install
npm start
```

プロジェクトが立ち上がり以下の画面が表示されれば成功です。
<img width="423" alt="Screen Shot 2018-12-21 at 15.23.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/165335/b851f03f-1993-e23c-764e-2c0936dd1760.png">

### 2. 新しいWebComponentを追加する
Stencilで新しいWebComponeとを生成する時はsrc/componentsをファイルを追加すればOKです。今回は、以下の2ファイルをsrc/components/aa-svgディレクトリに追加します。

```typescript:src/components/aa-svg/aa-svg.ts
import { Component } from "@stencil/core";

@Component({
  tag: "aa-svg",
  styleUrl: "aa-svg.css",
  shadow: true
})
export class AASvgComponent {
  render() {
    return <div>Hello, World!</div>;
  }
}
```

```css:src/components/aa-svg/aa-svg.css
// 空のCSSファイル
```

たったこれだけで新規WebComponentsが作成されました。試しに呼び出してみます。`src/index.html`を書き換えて`aa-svg`コンポネーントを呼び出してみましょう。

```html:src/index.html
<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0">
  <title>Stencil Component Starter</title>
  <script src="/build/mycomponent.js"></script>

</head>
<body>
  <aa-svg></aa-svg>
</body>
</html>
```

すでに`npm start`でプロジェクトを立ち上げている場合は、LiveReloadされます。`localhost:3333`で以下の画面が表示されていれば成功です。
<img width="310" alt="Screen Shot 2018-12-21 at 15.31.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/165335/d514b668-4146-d7e1-354b-4956309a4c6f.png">



### 3. 文字列をSVGで表示するを作る
いよいよ実際に文字列をSVGで表示する処理を作ります。必要な処理は以下になります。

1. AA用フォントを読み込む
2. aa-svgコンポーネントでAA(文字列)を受け取れるようにする
3. 文字列をSVGで描画する

#### 1. AA用フォントを読み込む
index.htmlでAAの表示が可能な日本語をフォントを読み込みましょう。今回は「Saitamaar」フォントを利用しました。

```html
  <style>
@font-face {
  font-family: "Saitamaar";
  src: url("/assets/fonts/Saitamaar/Saitamaar.woff2") format("woff2"),
  url("/assets/fonts/Saitamaar/Saitamaar.woff") format("woff"),
  url("/assets/fonts/Saitamaar/Saitamaar.ttf") format("ttf");
font-display: swap;
}

.Saitamaar {
  font-family: "Saitamaar";
}
  </style>
```

#### 2. aa-svgコンポーネントでAA(文字列)を受け取れるようにする
aa-svgコンポネーントに対して表示したいAAを入力できるようにしましょう。こちらは、StencilではPropというデコレータで宣言することができます。以下のソースコードは`aa`という変数を宣言しています。

```typescript
import { Component, Prop } from "@stencil/core";

@Component({
  tag: "aa-svg",
  styleUrl: "aa-svg.css",
  shadow: true
})
export class AASvgComponent {
  @Prop() aa: string;

// 〜〜〜以下略〜〜〜
```

親コンポーネントはaaという属性を利用して情報の受け渡しができます。

```html
<body>
  <aa-svg aa="　 ∧＿∧\n 　（　´∀｀）\n 　（　　　　）\n 　｜ ｜　|\n 　（_＿）＿）\n"></aa-svg>
</body>
</html>
```

#### 3. 文字列をSVGで描画する
tsxのrenderメソッドの中で受け取った文字列を描画します。まず、SVG描画時の横幅を決めるため、文字列の最大幅を計測します。以下は、受け取った文字列を計測する関数です。

```typescript
  get_tex_width(txt, font) {
    let element = document.createElement("canvas");
    let context = element.getContext("2d");
    context.font = font;
    return context.measureText(txt).width;
  }
```

また、SVGは文字列の改行ができないため、受け取った文字列を改行コードで分割し、line-Height分の縦幅をループでずらしながら描画します。

```typescript
  render() {
    const data = this.aa.split(/\\n|\r\n|\r|\n/);
    let text = [];
    let maxWidth = 0;
    let maxHeight = 16 * data.length;
    for (let i = 0; i < data.length; i++) {
      let width = this.get_tex_width(data[i], "16px 'Saitamaar'");
      if (maxWidth < width) {
        maxWidth = width;
      }
      text.push(
        <text x="0" y={16 * (i + 1) + 2}>
          {data[i]}
        </text>
      );
    }
    return (
      <svg
        xmlns="http://www.w3.org/2000/svg"
        viewBox={"0 0 " + maxWidth + " " + maxHeight}
      >
        <g style={{ fontFamily: "Saitamaar", fontSize: 16 }}>{text}</g>
      </svg>
    );
  }
```

最終的にで上がったソースコードは以下のようになります。

```typescript:src/components/aa-svg/aa-svg.tsx
import { Component, Prop } from "@stencil/core";

@Component({
  tag: "aa-svg",
  styleUrl: "aa-svg.css",
  shadow: true
})
export class AASvgComponent {
  @Prop() aa: string;

  get_tex_width(txt, font) {
    let element = document.createElement("canvas");
    let context = element.getContext("2d");
    context.font = font;
    return context.measureText(txt).width;
  }
  render() {
    const data = this.aa.split(/\\n|\r\n|\r|\n/);
    let text = [];
    let maxWidth = 0;
    let maxHeight = 16 * data.length;
    for (let i = 0; i < data.length; i++) {
      let width = this.get_tex_width(data[i], "16px 'Saitamaar'");
      if (maxWidth < width) {
        maxWidth = width;
      }
      text.push(
        <text x="0" y={16 * (i + 1) + 2}>
          {data[i]}
        </text>
      );
    }
    return (
      <svg
        xmlns="http://www.w3.org/2000/svg"
        viewBox={"0 0 " + maxWidth + " " + maxHeight}
      >
        <g style={{ fontFamily: "Saitamaar", fontSize: 16 }}>{text}</g>
      </svg>
    );
  }
}
```

```html:src/index.html
<!DOCTYPE html>
<html dir="ltr" lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0">
  <title>Stencil Component Starter</title>
  <script src="/build/mycomponent.js"></script>
  <style>
@font-face {
  font-family: "Saitamaar";
  src: url("/assets/fonts/Saitamaar/Saitamaar.woff2") format("woff2"),
  url("/assets/fonts/Saitamaar/Saitamaar.woff") format("woff"),
  url("/assets/fonts/Saitamaar/Saitamaar.ttf") format("ttf");
font-display: swap;
}

.Saitamaar {
  font-family: "Saitamaar";
}
  </style>

</head>
<body>

  <aa-svg aa="　 ∧＿∧\n 　（　´∀｀）\n 　（　　　　）\n 　｜ ｜　|\n 　（_＿）＿）\n"></aa-svg>

</body>
</html>
```

以上で完成です。完成したAAはSVGで表示しているため、画像のように完全にレスポンシブです。なおかつ単なるテキストでもあります。

![mona.gif](https://qiita-image-store.s3.amazonaws.com/0/165335/e09c0425-890f-c618-7c1a-c35b0f5e8a97.gif)


## ソースコード
[GitHub](https://github.com/AAHub/aa-svg)

## まとめ
本記事では、AAをSVGで表示するWebComponentを作成しました。SVGになることで、レスポンシブな表示が可能ですし、WebComponentなので、取り回しが非常に簡単です。

私も**AA箱**というサービスで利用しています。下のサイトは完全にStencil + Ionic4のみで作成しており一般的なフレームワークは使用していません。
[AA箱 - アスキーアートの保存＆共有サービス](https://b.aahub.org)


また、WebCopmonentの作成に使用したStencilは非常に簡単に使えますし、すでにIonic4という大きなフレームワークでの利用実績もあります。興味をもったなら是非とも利用してみてください。
それでは。