NimでSDL2 ⑥ 文字列を描画する

#はじめに
[NimでSDL2 ① 準備とウィンドウ表示](https://qiita.com/bucho666/items/afa37af16744a99b199e)
[NimでSDL2 ② 画像を表示](https://qiita.com/bucho666/items/ec3845317f99fa7c0b48)
[NimでSDL2 ③ 入力処理](https://qiita.com/bucho666/items/cc95bae676804edc0348)
[NimでSDL2 ④ FPS(frames per second)の調整](https://qiita.com/drafts/af6ab3bd0b22eb19ba99/edit)
[NimでSDL2 ⑤ 音を出す](https://qiita.com/bucho666/items/851a239438f9b26bf32b)

文字列を描画します。

#概要
まずはttfを初期化。
でttfファイルを読み込んで、FontPtrを作成。
そのFontPtrから文字列を指定してサーフェイスを作成。
そのサーフェイスをテクスチャーに変換して描画する。（ちとめんどい）

# コード
追記箇所はコメント参照

```nim
# sdl2/ttfをimport
import sdl2, sdl2/image, os, sugar, sets, sdl2/mixer, sdl2/ttf

template sdlFailIf(cond: typed, desc: string) =
  if cond: raise Exception.newException(
    desc & ": " & $getError())

type FPS* = ref object
  frameTime, targetFramePeriod: uint32

proc newFPS*(fps: int): FPS =
  FPS(targetFramePeriod: uint32(1000 / fps))

proc adjust*(self: FPS) =
  let now = getTicks()
  if self.frameTime > now:
    delay(self.frameTime - now)
  self.frameTime += self.targetFramePeriod

type SDL2Context = ref object
  window: WindowPtr
  renderer: RendererPtr
  cleanups: seq[proc ()]
  pressed: HashSet[cint]
  press: HashSet[cint]
  fps: FPS

proc cleanup(self: SDL2Context) =
  while self.cleanups.len > 0:
    (self.cleanups.pop)()

proc init(self: SDL2Context, title: string, w, h: cint): SDL2Context {.discardable.} =
  result = self
  sdlFailIf(not sdl2.init(INIT_VIDEO), "SDL2 init")
  self.cleanups.add(() => sdl2.quit())
  self.window = createWindow(title = title,
                            x = SDL_WINDOWPOS_CENTERED,
                            y = SDL_WINDOWPOS_CENTERED,
                            w = w, h = h,
                            flags = SDL_WINDOW_SHOWN)
  sdlFailIf(self.window.isNil, "createWindow")
  self.cleanups.add(() => self.window.destroy)
  sdlFailIf(image.init(IMG_INIT_PNG) != IMG_INIT_PNG, "image init")
  self.cleanups.add(() => image.quit())
  self.renderer = self.window.createRenderer(
    index = -1,
    flags = Renderer_Accelerated or Renderer_PresentVsync)
  sdlFailIf(self.renderer.isNil, "createRenderer")
  self.cleanups.add(() => self.renderer.destroy)
  self.renderer.setDrawColor(r=0, g=128, b=128)
  self.pressed = initHashSet[cint]()
  self.fps = newFPS(60)
  sdlFailIf(mixer.openAudio(
    MIX_DEFAULT_FREQUENCY,
    MIX_DEFAULT_FORMAT,
    MIX_DEFAULT_CHANNELS,
    4096) != 0, "openAudio")
  self.cleanups.add(() => closeAudio())
  # ttf初期化
  sdlFailif(ttfInit() == SdlError, "ttfInit")
  # クリーンアップ処理追加
  self.cleanups.add(() => ttfQuit())

proc loadTexture(self: SDL2Context, path: string): TexturePtr =
  result = self.renderer.loadTexture(path)
  sdlFailIf(result.isNil, "loadTexture")

proc keyStateUpdate*(self: SDL2Context, event: KeyboardEventPtr) =
  case event.kind
  of KeyDown:
    self.pressed.incl(event.keysym.sym)
    self.press.incl(event.keysym.sym)
  of KeyUp: self.pressed.excl(event.keysym.sym)
  else: discard

proc pollEvent(self: SDL2Context): bool =
  result = true
  var event = defaultEvent
  self.press.clear
  while pollEvent(event):
    case event.kind:
    of QuitEvent: return false
    of KeyDown, KeyUp: self.keyStateUpdate(event.key)
    else: discard

proc mainLoop(self: SDL2Context) =
  let
    w: cint = 256
    h: cint = 256
    speed: cint = 3
    neko = self.loadTexture("genbaneko.png")
  var
    windowWidth: cint
    windowHeight: cint
  let music = mixer.loadMUS("bgm.ogg")
  sdlFailIf(music.isNil, "loadMUS")
  let sound = mixer.loadWav("sound.wav")
  sdlFailIf(sound.isNil, "loadWav")
  self.window.getSize(windowWidth, windowHeight)
  var dst = rect(windowWidth div 2 - w div 2, windowHeight div 2 - h div 2, w, h)
  # ttfフォント読み込み
  let font = openFont("./mplus-1p-regular.ttf", 48)
  # エラーチェック
  sdlFailIf(font.isNil, "openFont")
  let
    # ttfフォントから文字列のサーフェイスを作成
    strSurface = font.renderUtf8Blended("ご安全に!!", color(255, 0, 0, 0))
    # サーフェイスをテクスチャーに変更
    strTexture = self.renderer.createTextureFromSurface(strSurface)
  # サーフェイスの描画先Rectを作成
  var strRect = rect(windowWidth div 2 - strSurface.w div 2, dst.y + dst.h - strSurface.h,
                     strSurface.w, strSurface.h)
  while self.pollEvent:
    if K_RIGHT in self.pressed: dst.x += speed
    if K_LEFT  in self.pressed: dst.x -= speed
    if K_DOWN  in self.pressed: dst.y += speed
    if K_UP    in self.pressed: dst.y -= speed
    if K_m in self.press:
      if playingMusic() == 0:
        sdlFailif(playMusic(music, 0) == -1, "playMusic")
      else:
        sdlFailif(haltMusic() == -1, "haltMusic")
    if K_s in self.press:
      sdlFailif(playChannel(-1, sound, 0) == -1, "playChannel")
    self.renderer.clear
    self.renderer.copyEx(neko, nil, dst.addr, 0.0, nil, SDL_FLIP_NONE)
    # 文字列のテクスチャーを描画
    self.renderer.copyEx(strTexture, nil, strRect.addr, 0.0, nil, SDL_FLIP_NONE)
    self.renderer.present
    self.fps.adjust

proc main() =
  let sdl2 = SDL2Context()
  defer: sdl2.cleanup
  sdl2.init("SDL2 Font sample", 640, 480).mainLoop

main()
```

# 結果
![スクリーンショット 2020-12-26 13.57.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268503/82fc54c7-0399-69a0-bf51-189ccfb92874.png)

# 終わりに
SDL2の基本的な機能はできたと感じ。
これまでのコードを参考に色々やってもらえればと〜。

以上〜。