ReceiptLine を使って Web アプリケーションからレシート・ラベルシールを印刷する

# 概要

タイトルの通り、[ReceiptLine](http://www.ofsc.or.jp/receiptline/) を使用し Web アプリケーションで生成したデータを元に、クライアント PC からレシートを印刷することを目指します。
次の構成図の通りの技術を使いましたが、Web アプリケーションの部分については javascript から HTTP リクエストをプリントサーバーに向けて POST するだけなので何でも良いです。

# 構成図
![receiptline.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8861/88fdfd8a-3419-fbdb-72b2-bcbb405a61d9.png)

# 今回使用したレシートプリンター
[EPSON TM-L90](https://www.epson.jp/products/label/tml90/)

今回は、ESC/POS を使ってみたかったので、本家本元 EPSON のロングセラーモデルを中古で入手しました。公式で「ラベルプリンター」と銘打たれているように、用紙を交換することで ラベルシール/レシートの両方を印刷できます。

# 1. プリンターの準備
詳細はマニュアルを確認の上、実施する必要がありますが、最低限、下記の設定を行う必要があります。

## (1) ネットワークの設定 
私の場合は `UB-E03` という LANインターフェイス付のものを入手したのでそのマニュアルを参照しました。

## (2) 紙幅の設定
用紙の紙幅を変える度に設定が必要です。

## (3) 用紙の自動レイアウト設定
特にラベルシールの印刷をする上では必須です。

これらの設定が完了し、`IP: 192.168.2.100` でアクセスできるようになったとの前提ですすめます。

# 2. デザイナーを使ってレシートプリンターから印刷する

付属のデザイナーを利用して、レシートのデザイン・印刷を行えるところまで進めます。

下記の公式ドキュメントの通り、インストール・設定を行います。

[ReceiptLine参照実装 始めにお読みください]
(http://www.ofsc.or.jp/receiptline_/readme_jp.html)

## (1) インストール

```bash:terminal
$ npm install receiptline
or 
$ yarn add receiptline
```

## (2) レシートプリンターの設定

`node_modules/receiptline/printers.json` を編集します。
最初から設定例としていくつかのプリンターが設定されています。
今回、レシート用の `receipt`, ラベルシール用の `label`、それとデバッグ用にそれぞれの仮想サーバー用の設定として `v-receipt`, `v-label` を追加しています。

仮想サーバーは送信したデータを16進数ダンプを表示してくれるので、特に直接コマンドを送信する場合のデバッグに役立ちます。

各設定項目の詳細については [https://github.com/receiptline/receiptline#printer-configuration] を参照してください。

`cpl`(1行あたりの文字数)については、`receipt`の 48 は紙幅 80mm のレシート紙, `label` の32 は紙幅 60mm のラベルロール紙に合わせています。このあたりは紙幅だけでなく機種・設定による変動がありますので、デザイナーを使って実機で試すのがてっとり早いかと思います。

```json:node_modules/receiptline/printers.json
{
    "receipt": {
        "host": "192.168.2.100",
        "port": 9100,
        "cpl": 48,
        "encoding": "cp932",
        "upsideDown": false,
        "cutting": true,
        "command": "escpos"
    },
    "label": {
        "host": "192.168.2.100",
        "port": 9100,
        "cpl": 32,
        "encoding": "cp932",
        "upsideDown": false,
        "cutting": true,
        "command": "escpos"
    },
    "v-receipt": {
        "host": "127.0.0.1",
        "port": 19100,
        "cpl": 48,
        "encoding": "cp932",
        "upsideDown": false,
        "cutting": true,
        "command": "escpos"
    },
    "v-label": {
        "host": "127.0.0.1",
        "port": 19100,
        "cpl": 32,
        "encoding": "cp932",
        "upsideDown": false,
        "cutting": true,
        "command": "escpos"
    },
    ...
}
```

## 3. デザイナーの起動

```bash:terminal
$ cd node_modules/receiptline/designer
$ npm start
or
$ yarn start

> Virtual printer running at 127.0.0.1:19100
> Server running at http://127.0.0.1:10080/

```

メッセージ通りにブラウザで `http://127.0.0.1:10080` にアクセスするとデザイナーが表示されます。

## 4. レシートの印刷

`node_modules/receiptline/example/data/ja` 内のテキストファイルの中身を適当にコピーし、
デザイナーの左側のテキストエリアにペーストすると、右側にレシートの画像が表示されます。
`Width` を 48cpl に設定し、右上の `Print` の下のテキストフィールドに'receipt' と入力し `Send` ボタンをクリックします。
ここまでの設定がうまくできていれば、レシートプリンターからレシートが印刷されるはずです。
 
# 3. プリントサーバーの準備

一旦、デザイナーは停止させ、今度は `node_modules/receiptline/exapmle/nodejs/`内のファイルに対して設定を行い、本番用のプリントサーバーの設定を行います。

### (1) サーバーの設定

`node_modules/receiptline/exapmle/nodejs/servers.json` を設定します。

``` json:node_modules/receiptline/exapmle/nodejs/servers.json
{
    "http": {
        "host": "127.0.0.1",
        ...
        }
    },
    "print": {
        "host": "127.0.0.1",
        "port": 19100
    },
    "_serial": {
        "host": "127.0.0.1",
        "port": 9100,
        "device": "COM9"
    }
}

```

初期値では host が `127.0.0.1` となっており、localhost からのリクエストしかリッスンしない設定になっています。
今回の構成では、Web アプリケーションのクライアントとプリントサーバーが同一ホスト内で動いているため、特に変更は必要ありません。

### 他の端末からも印刷リクエストを受け付ける場合

ただし、例えば同一ネットワークに接続している他のタブレットからも Web アプリケーションにアクセスしてレシートを印刷したい、という場合などには変更の必要があります。

``` json:node_modules/receiptline/exapmle/nodejs/printers.json
{
    "print": {
        "host": "0.0.0.0",
        "port": 19100
    },
}

```

### (2) プリンターの設定

`node_modules/receiptline/exapmle/nodejs/printers.json` を設定します。
こちらはデザイナーの方で編集した printers.json のコピペで OK です。

### (3) サーバーコードの修正 (CORS 対応)

node.js のコードを修正します。
`node_modules/receiptline/exapmle/nodejs/start.js` のままでも動作はしますが、Web アプリケーション(ブラウザ) から HTTP リクエスト(POST) を送信するときに CORS 制限に抵触するので レスポンスヘッダーを追加します。

``` javascript:node_modules/receiptline/exapmle/nodejs/start.js

if ('http' in servers) {
    const server = http.createServer((req, res) => {
      // 以下の4行を追加
      res.setHeader('Access-Control-Allow-Origin', '*');
      res.setHeader('Access-Control-Request-Method', '*');
      res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET');
      res.setHeader('Access-Control-Allow-Headers', '*');

      let pathname = url.parse(req.url).pathname;
      ...
```

## (4) サーバーの起動

``` bash:terminal
$ cd node_modules/receiptline/example/nodejs/
$ node start.js
```

エラーが発生すると停止するので、実運用時には [pm2](https://pm2.keymetrics.io/) を使うなどしてプロセスの永続化をするのが良さそうです。

# 4. フロントエンド側の実装

`axios` でサーバーのエンドポイントに、ReceiptLine マークダウン形式の文字列データを POST するだけです。

ホスト, ポートは `servers.json` の `"http"` の設定に、パスは `printers.json`で設定した識別子と同じになります。
したがって、今回の設定だと、レシート用の HTTP エンドポイントは `http://127.0.0.1:10080/receipt`、ラベルシール用は `http://127.0.0.1:10080/label` となります。

```javascript:index.vue
export default {
  ...,
  methods: {
    print_receipt(doc) {
      // doc は ReceiptLine マークダウン形式の文字列
      this.$axios.post('http://127.0.0.1:10080/receipt', doc)
        .then((res) => {
          console.log(res);
        })
        .catch((error) => {
          console.log(error); 
        })  
    }
  }
}

```

フロントエンド側での操作はこれだけです。

雑駁ではあるのですが、これで Web アプリケーションからレシートを印刷できるようになりました。
ReceiptLine マークダウン形式は非常にシンプルかつサイズも軽量で、バックエンドからでもフロントエンドからでも簡単に希望の帳票を印刷することができます。

### Mixed Content (混合コンテンツ) 対策

今回の構成では問題ないのですが、フロントエンドからプリントサーバーにリクエストを送る際に、
    
* Web アプリケーションは HTTPS で運用
* プリントサーバーが localhost ではない LAN 内のホストで起動している

という条件が揃うと、 Mixed Contents (混合コンテンツ: HTTPS サイト内への HTTP コンテンツの混入) の問題が発生します。
最近の Chrome ではこれがデフォルトでブロックされるため、プリントサーバーへのリクエストは失敗します。

これを回避するためには Chrome のサイトの設定で「安全でないコンテンツ」を許可する必要がありますが、URL 欄に「安全でない接続」と赤字で表示されるようになってしまいます。
とりあえず、私はこの状態で我慢しています。
プリントサーバーのホストに自己署名証明書を設定するなどの方法もあるのかもしれませんが試していません。


# (おまけ) ラベルシールの印刷

おまけ、というか苦労したという意味では本題です。
ReceiptLine について、ラベルシールの印刷について言及した記事を見かけなかったのですが、問題なく対応できることを確認しました。

今回使用した、ラベルシール用紙は [TRL060-902](https://www.epson.jp/products/supply/shoumouhin/data/shoumouhin/trl060-902.htm) です。

メーカー公式のページに画像がなかったので、[Google 画像検索のリンク](https://www.google.co.jp/search?q=TRL060-902&tbm=isch)を代わりに掲載します。

レシートの方は比較的何も考えなくても良いのですが、ラベルシールの場合はラベルの印字開始位置やカット位置の調整が必要になります。
ここからの記載は、あくまで TM-L90 に限定した話で他機種での動作を保証するものではありませんが、任意の ESC/POS コマンドの実行など、他機種においても必要となる知識です。

## (1) デザイナーでテキストを作成
今回は、農産物直売所の商品に貼り付けるラベルシールを作りたいので、デザイナーでテキストを作成します。

``` txt:designer
|230: いちご | `産地: ○×△村`|
|^^228: ○○フルーツ農園 |

|^^キングいちご |
| ^^^580円|
| (税込)|
{code:2023022805802; option:code128,2,72,hri}
| ニコニコファーマーズ XX店 |
```

![スクリーンショット 2021-01-30 15.45.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8861/ffdbf3d1-c82f-53d0-d1f9-fdcec43c8e82.png)

## (2) 印刷の他に必要な処理

デザインはとりあえずこれで良しとして、先述の通り、ラベルシールは印字開始位置やカット位置の合わせが必要になります。また、農産物直売所のラベルシールは連続で同じものを大量に印刷することが一般的ですので、ラベル本体の印刷後、次のように処理を行う必要があります。

・ (最後の1枚以外) ラベル本体の印刷 -> 次のラベル印字開始位置へ紙送り
・ (最後の1枚) ラベル本体の印刷 -> カット位置へ紙送り -> オートカット 

TM-L90 はカッター装置の保護のため、カッターの位置が適正なカット位置(ラベルとラベルの数 mm の間)にない場合はオートカット機能が動かないので、正確な位置に紙送りをする必要があります。

## (3) 任意コマンドの使い方 ({command: ...} または {x: ...})

紙送りを制御するためには製品のマニュアル、または EPSON 公式の[ESC/POS リファレンスマニュアル](https://reference.epson-biz.com/pos/reference_ja/)を参照し、当該のコマンドを調べます。

詳細は割愛しますが、調べた結果、今回必要なコマンドは下記の通りになります。(16進数)

``` bash:commands
// 次の印字開始位置まで紙送り
1C 28 4C 02 00 43 30

// 次のカット位置まで紙送り
1C 28 4C 02 00 42 31

// カット
1D 56 00

```

「ESC/POS リファレンス」は 16進数 でコマンドを掲載してくれていますが、パラメーターについては 10進数 のまま掲載されているので、コマンドに変換する場合は忘れずに 16進数 にする必要があります。

これを ReceiptLine マークダウン形式中で実行するためには `{command: ...}` または `{x: ...}` 記法を使い、下記のように記述します。

``` bash:commands
// 次の印字開始位置まで紙送り
{x:\x1c\x28\x4c\x02\x00\x43\x30}

// 次のカット位置まで紙送り
{x:\x1c\x28\x4c\x02\x00\x42\x31}

// カット
{x:\x1d\x56\x00}
```

したがって、さきほどデザイナーで作成したバーコードラベルを 2枚印刷する際の ReceiptLine マークダウン形式のテキストは下記の通りです。
`(1枚目のラベル本体の印刷) -> (次のラベルの印字開始位置まで紙送り) -> (2枚目のラベル本体の印刷) -> (次のカット位置まで紙送り) -> (カット)` という流れになります。


``` txt:designer
|230: いちご | `産地: ○×△村`|
|^^228: ○○フルーツ農園 |

|^^キングいちご |
| ^^^580円|
| (税込)|
{code:2023022805802; option:code128,2,72,hri}
| ニコニコファーマーズ XX店 |
{x:\x1c\x28\x4c\x02\x00\x43\x30}
|230: いちご | `産地: ○×△村`|
|^^228: ○○フルーツ農園 |

|^^キングいちご |
| ^^^580円|
| (税込)|
{code:2023022805802; option:code128,2,72,hri}
| ニコニコファーマーズ XX店 |
{x:\x1c\x28\x4c\x02\x00\x42\x31}
{x:\x1d\x56\x00}

```

実際に印刷すると、こんな感じになりました。

![5C5A1FB2-D5B2-4314-B49E-31E00B072AEA.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8861/e4defb2d-d510-5511-1431-15724bcf8d51.jpeg)

### 文字のスムージング

[@dopperi46](https://qiita.com/dopperi46) 様よりコメントをいただき知ったのですが、ESC/POS には文字のスムージングという仕様があります。4倍角以上の文字に対しての効果ですが、文字を大きくするほどジャギーが目立ちやすくなるので、有効にしておくメリットは大きいです。

``` bash:commands
// スムージングの指定 (16進数)
1D 62 01

// ReceiptLine マークダウン形式
{x:\x1d\x62\x01}

```

同じラベル (金額の部分のみ4倍角に変更) で試してみました。
(左がスムージングあり / 右がなし)

![EA8A2706-3295-438A-87A4-E104509D43E8.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/8861/6f2a272d-0d7f-7c73-38c7-07f9a7e7337b.jpeg)

### キャッシュドロアーの制御

TM-L90 にキャッシュドロアーを接続し、制御(といってもオープンだけですが)をする必要が発生したので追記です。
今回、キャッシュドロアーとして EPSON の [CD-A3336](https://www.epson.jp/products/receiptprinter/cash/#ANC01) を使うことにしました。

レシートプリンターの専用ポートにキャッシュドロアーから出ている 6 ピンケーブルを使って接続し、レシートプリンターから指定のパルス信号を送信することでドロアをオープンすることができます。

ESC/POS コマンドリファレンスを調べ、色々と実験したところ、下記のコマンドでキャッシュドロアーをオープンすることができました。

``` bash:commands
// 指定パルスの発生 (16進数) 
1B 70 00 04 14

// 3バイト目:パルスを送信するピン ... 0: 2番ピン / 1: 5番ピン
// 4バイト目:パルス ON の長さ (x2 msec)
// 5バイト目:パルス OFF の長さ (x2 msec) *4バイト目より長い必要がある

```

``` bash:commands
// ReceiptLine マークダウン形式
{x:\x1b\x70\x00\x04\x14}
```

最初、ESC/POS コマンドリファレンスの「プログラム例」の「レシート発行」にドロア制御の記載があり、それを参考に、4バイト目を 2 (=パルス ON 4msec)に設定しており、ドロアーが開かず首を捻っていたのですが、もう少し長めに設定することで動作しました。
このあたりはキャッシュドロアーの機種により調整の必要がありそうです。

# 最後に

[一般社団法人オープン・フードサービス・システム・コンソーシアム（OFSC）](http://www.ofsc.or.jp/) が OSS として公開されている ReceiptLine の存在を知り、どうしても実際に使ってみたくなってこの記事を執筆するまでに至りました。飲食業界の情報管理のための規格標準化を行う OFSC の活動内容は素晴らしいものです。

また、Qiita で ReceiptLine の記事を多数執筆されている [@dopperi46](https://qiita.com/dopperi46) 様の記事で大変勉強させていただきました。
イチオシは下記の記事です。

[簡単レシート印刷 receiptline と 20 行の JavaScript でレジプリンターをインスタントカメラにしてみた]
(https://qiita.com/dopperi46/items/1d2b92ba6a4cb6f79c19)

レシートプリンターはまだまだ業務用の機器というイメージですが、飲食店の省人化で受付用紙の発行に使われていたり、従来型のレジがタブレット端末 + レシートプリンターに置き換わっていたり、今までよりも目にする機会が増えたように感じます。この技術を活用できるシーンを今後も模索していきたいです。