【RunCPM】オレオレ EGR シーケンスを実装してみる

# はじめに

robo8080 氏がスタンドアロン動作する RunCPM を公開されていまして、ちょっとハマっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/3279c8a6-a63f-8ab4-b7f7-2d480cf01351.png)

**See also:**

 - [RunCPM + VT100 Emu for M5Stack (Github)](https://github.com/robo8080/M5Stack_RunCPM_vt100_CardKB)
 - [RunCPM + VT100 Emu for Wio Terminal (CardKB) (Github)](https://github.com/robo8080/Wio_RunCPM_vt100_CardKB)
 - [RunCPM + VT100 Emu for Wio Terminal (USB KB) (Github)](https://github.com/robo8080/Wio_RunCPM_vt100_UsbKB)

# EGR

その昔、カノープス社より `EGR-98` という**エスケープシーケンスでグラフィックを描画**できるようになるグラフィクドライバが発売されていたそうです。

**See also:**

 - [グラスバレー (企業情報@Wiki)](https://w.atwiki.jp/sysd/pages/3838.html)


## オレオレ EGR

実際に EGR-98 を使ったことがないので、EGR-98 のエスケープシーケンス体系を知らないのですが、オレオレ EGR シーケンスを定義し、**LovyanGFX** の描画メソッドをエスケープシーケンスを通して各種プログラミング言語から透過的に使えるようにしてみたいと思いました。

画像でお判り頂けると思いますが、使用している **RunCPM + VT100 Emu** は Wio Terminal 向けの USB キーボード版です。

| 文字 | 機能 | パラメータ | 説明 |
|:---:|:---|:---|:---|
| A  |drawArc       |x, y, r0, r1, angle0, angle1   | 円弧の外周            |
| a  |fillArc       |x, y, r0, r1, angle0, angle1   | 円弧の塗り            |
| B  |drawBezier    |x0, y0, x1, y1, x2, y2         | ３点間のベジエ曲線    |
|    |drawBezier    |x0, y0, x1, y1, x2, y2, x3, y3 | ４点間のベジエ曲線    |
| C  |drawCircle    |x, y, r                        | 円の外周              |
| c  |fillCircle    |x, y, r                        | 円の塗り              |
| E  |drawEllipse   |x, y, rx, ry                   | 楕円の外周            |
| e  |fillEllipse   |x, y, rx, ry                   | 楕円の塗り            |
| F  |setColor      |r, g, b                        | 前景色設定            |
| H  |drawFastHLine |x, y, w                        | 水平線                |
| K  |setBaseColor  |r, g, b                        | 背景色設定            |
| L  |drawLine      |x0, y0, x1, y1                 | ２点間の直線          |
| O  |drawRoundRect |x, y, w, h, r                  | 角丸の矩形の外周      |
| o  |fillRoundRect |x, y, w, h, r                  | 角丸の矩形の塗り      |
| P  |drawPixel     |x, y                           | 点                    |
| R  |drawRect      |x, y, w, h                     | 矩形の外周            |
| r  |fillRect      |x, y, w, h                     | 矩形の塗り            |
| S  |clear         |                               | クリア                |
| s  |fillScreen    |                               | 画面全体の塗り潰し    |
| T  |drawTriangle  |x0, y0, x1, y1, x2, y2         | ３点間の三角形の外周  |
| t  |fillTriangle  |x0, y0, x1, y1, x2, y2         | ３点間の三角形の塗り  |
| V  |drawFastVLine |x, y, h                        | 垂直線                |
          

※ EGR = `ESC %`
           
**See also:**

 - [LovyanGFX (Github)](https://github.com/lovyan03/LovyanGFX)
                                                       

## コード

下記コードを **RunCPM + VT100 Emu** の `Wio_RunPCM_vt100_xxxxxx.ino` に組み込んで使います。まずは列挙型の項目を追加します。

```cpp
PROGMEM enum class em {NONE,  ES, CSI, CSI2, LSC, G0S, G1S, EGR}; // EGR を追加
```

次のコードを組み込む場所は `printChar()` の中です。

```cpp
...
      case '%':
        // EGR セット シーケンス へ
        clearParams(em::EGR);
        break;
...
  // "*" EGR シーケンス
  if (escMode == em::EGR) {
    if (isdigit(c)) {
      // [パラメータ]
      vals[nVals] = vals[nVals] * 10 + (c - '0');
      hasParam = true;
    } else if (c == ';') {
      // [セパレータ]
      nVals++;
      hasParam = false;
    } else {
      if (hasParam) nVals++;
      switch (c) {
        case 'A':
          // drawArc
          lcd.drawArc(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]);
          break;
        case 'a':
          // fillArc
          lcd.fillArc(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]);
          break;
        case 'B':
          // drawBezier
          if (nVals == 8)
            lcd.drawBezier(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5], vals[6], vals[7]);
          else
            lcd.drawBezier(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]);
          break;
        case 'C':
          // drawCircle
          lcd.drawCircle(vals[0], vals[1], vals[2]);
          break;
        case 'c':
          // fillCircle
          lcd.fillCircle(vals[0], vals[1], vals[2]);
          break;
        case 'E':
          // drawEllipse
          lcd.drawEllipse(vals[0], vals[1], vals[2], vals[3]);
          break;
        case 'e':
          // fillEllipse
          lcd.fillEllipse(vals[0], vals[1], vals[2], vals[3]);
          break;
        case 'F':
          // setColor 
          lcd.setColor(lcd.color565(vals[0], vals[1], vals[2]));
          break;
        case 'H':
          // drawFastHLine
          lcd.drawFastHLine(vals[0], vals[1], vals[2]);
          break;
        case 'K':
          // setBaseColor 
          lcd.setBaseColor(lcd.color565(vals[0], vals[1], vals[2]));
          break;
        case 'L':
          // drawLine
          lcd.drawLine(vals[0], vals[1], vals[2], vals[3]);
          break;
        case 'O':
          // drawRoundRect
          lcd.drawRoundRect(vals[0], vals[1], vals[2], vals[3], vals[4]);
          break;
        case 'o':
          // fillRoundRect
          lcd.fillRoundRect(vals[0], vals[1], vals[2], vals[3], vals[4]);
          break;
        case 'P':
          // drawPixel
          lcd.drawPixel(vals[0], vals[1]);
          break;
        case 'R':
          // drawRect
          lcd.drawRect(vals[0], vals[1], vals[2], vals[3]);
          break;
        case 'r':
          // fillRect
          lcd.fillRect(vals[0], vals[1], vals[2], vals[3]);
          break;
        case 'S':
          // clear
          lcd.clear();
          break;
        case 's':
          // fillScreen
          lcd.fillScreen();
          break;
        case 'T':
          // drawTriangle
          lcd.drawTriangle(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]);
          break;
        case 't':
          // fillTriangle
          lcd.fillTriangle(vals[0], vals[1], vals[2], vals[3], vals[4], vals[5]);
          break;
        case 'V':
          // drawFastVLine
          lcd.drawFastVLine(vals[0], vals[1], vals[2]);
          break;
        default:
          // 未確認のシーケンス
          unknownSequence(escMode, c);
          break;
      }
      clearParams(em::NONE);
    }
    return;
  }
...
```

コードに何の捻りもありませんし、パラメータ数のチェックもしていません。

## Turbo Pascal からの使用 (その 1)

エスケープシーケンスなので、標準出力に Write() / Writeln() するだけです。

```pascal:GRTEST.pas
program GRTEST;
begin
  { <ESC>*255;255;0F }
  Write(#$1B, '%', '255;255;0'  , 'F'); { 前景色を黄色にする              }
  { <ESC>*160;120;100c }
  Write(#$1B, '%', '160;120;100', 'c'); { (160,120) に半径 100 の円を描く }
end.
```

黄色い円が描けました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/0d97c884-5303-68f8-b3bc-d3d0b800d7f5.png)
エスケープシーケンスなので、どんなプログラミング言語からも使えます。**MBASIC** とかでも OK です。

## Turbo Pascal からの使用 (その 2)
次のようなファイル (`EGR.INC`) を用意すると、

```pascal:EGR.INC
{* EGR.INC *}

const
  _EGR = #$1B'%';

  procedure Clear;
  begin
    Write(_EGR, 'S');
  end;

  procedure DrawArc(x, y, r0, r1, angle0, angle1: Integer);
  begin
    Write(_EGR, x, ';', y, ';', r0, ';', r1, ';', angle0, ';', angle1, 'A');
  end;

  procedure DrawBezier(x0, y0, x1, y1, x2, y2: Integer);
  begin
    Write(_EGR, x0, ';', y0, ';', x1, ';', y1, ';', x2, ';', y2, 'B');
  end;

  procedure DrawBezier2(x0, y0, x1, y1, x2, y2, x3, y3: Integer);
  begin
    Write(_EGR, x0, ';', y0, ';', x1, ';', y1, ';', x2, ';', y2, ';', x3, ';', y3, 'B');
  end;

  procedure DrawCircle(x, y, r: Integer);
  begin
    Write(_EGR, x, ';', y, ';', r, 'C');
  end;

  procedure DrawEllipse(x, y, rx, ry: Integer);
  begin
    Write(_EGR, x, ';', y, ';', rx, ';', ry, 'E');
  end;

  procedure DrawFastHLine(x, y, w: Integer);
  begin
    Write(_EGR, x, ';', y, ';', w, 'H');
  end;

  procedure DrawFastVLine(x, y, h: Integer);
  begin
    Write(_EGR, x, ';', y, ';', h, 'V');
  end;

  procedure DrawLine(x0, y0, x1, y1: Integer);
  begin
    Write(_EGR, x0, ';', y0, ';', x1, ';', y1, 'L');
  end;

  procedure DrawPixel(x, y: Integer);
  begin
    Write(_EGR, x, ';', y, 'P');
  end;

  procedure DrawRect(x, y, w, h: Integer);
  begin
    Write(_EGR, x, ';', y, ';', w, ';', h, 'R');
  end;

  procedure DrawRoundRect(x, y, w, h, r: Integer);
  begin
    Write(_EGR, x, ';', y, ';', w, ';', h, ';', r, 'O');
  end;

  procedure DrawTriangle(x0, y0, x1, y1, x2, y2: Integer);
  begin
    Write(_EGR, x0, ';', y0, ';', x1, ';', x1, ';', x2, ';', y2, 'T');
  end;

  procedure FillArc(x, y, r0, r1, angle0, angle1: Integer);
  begin
    Write(_EGR, x, ';', y, ';', r0, ';', r1, ';', angle0, ';', angle1, 'a');
  end;

  procedure FillCircle(x, y, r: Integer);
  begin
    Write(_EGR, x, ';', y, ';', r, 'c');
  end;

  procedure FillEllipse(x, y, rx, ry: Integer);
  begin
    Write(_EGR, x, ';', y, ';', rx, ';', ry, 'e');
  end;

  procedure FillRect(x, y, w, h: Integer);
  begin
    Write(_EGR, x, ';', y, ';', w, ';', h, 'r');
  end;

  procedure FillRoundRect(x, y, w, h, r: Integer);
  begin
    Write(_EGR, x, ';', y, ';', w, ';', h, 'o');
  end;

  procedure FillScreen;
  begin
    Write(_EGR, 's');
  end;

  procedure FillTriangle(x0, y0, x1, y1, x2, y2: Integer);
  begin
    Write(_EGR, x0, ';', y0, ';', x1, ';', y1, ';', x2, ';', y2, 't');
  end;

  procedure SetBaseColor(r, g, b: Integer);
  begin
    Write(_EGR, r, ';', g, ';', b, 'K');
  end;

  procedure SetColor(r, g, b: Integer);
  begin
    Write(_EGR, r, ';', g, ';', b, 'F');
  end;

  procedure InitGraph;
  begin
    Write(#$1B'[H'#$1B'[2J');
    SetBaseColor(0, 0, 0);
    Clear;
    SetColor(255, 255, 255);
  end;

```

先程のコードは次のように書けます。

```pascal:GRTEST.pas
program GRTEST;
{$I EGR.INC} 
begin
  SetColor($FF, $FF, $00);   { 前景色を黄色にする              }
  FillCircle(160, 120, 100); { (160,120) に半径 100 の円を描く }
end.
```

# 終わりに
`EGR-98` のエスケープシーケンス体系をご存知の方がいらっしゃったら教えてください m(_ _)m

**See also:**

 - [Wio Terminal (ht-deko.com)](https://ht-deko.com/arduino/wio_terminal.html)
 - [RunCPM (Z80 CP/M 2.2 エミュレータ) (ht-deko.com)](https://ht-deko.com/arduino/runcpm.html)