Source: Text.js

/**
 * @license GPL-3.0-only
 *
 * @author Mark Mayes / mm-dev
 *
 *
 * @module Text
 *
 * @description
 * ## Manages the drawing of strings and groups of strings
 * - Can measure and lay out text
 * - Actual drawing of the pixels is passed over to `BitmapText`
 */

import * as GAME from "./PD/GAME.js";
import * as STRING from "./PD/STRING.js";

import { BitmapText } from "./BitmapText.js";
import { Display } from "./Display.js";
import { Game } from "./Game.js";
import { InternalTimer } from "./InternalTimer.js";
import { Layout } from "./Layout.js";
import { Player } from "./Player.js";

import { __, isEmpty, padString } from "./utils.js";

class Text {}

Text.init = function () {
  BitmapText.init();
  BitmapText.chooseBestFittingFont();

  Text.useShortVersions = BitmapText.useShortVersions;
  Text.drawnCharWidth = BitmapText.drawnCharWidth;
  Text.drawnCharHeight = BitmapText.drawnCharHeight;
  Text.letterSpacing = BitmapText.letterSpacing;
  Text.fullCharWidth = BitmapText.fullCharWidth;
  Text.lineSpacing = BitmapText.lineSpacing;
  Text.lineHeight = BitmapText.lineHeight;
  Text.drawnPixelSize = BitmapText.spritesheetScale;

  // TODO should the rest of this method happen on eg `window.resize()`?
  BitmapText.precacheAllCharacters();
};

Text.setupForLevel = function (_ob) {
  Text.defaultColor = _ob.defaultColor;
  Text.accentColor = _ob.accentColor;
  Text.shadowColor = _ob.shadowColor;
};

Text.getStringWidth = function (_str) {
  return _str.length * Text.fullCharWidth - Text.letterSpacing;
};

Text.draw = function (_ob) {
  var i,
    color,
    colorSecondary,
    line_str,
    itemPos,
    currentText,
    lineWidth,
    flashing,
    blockWidth = 0,
    bgMeasurements,
    text_ar = Array.isArray(_ob.text) ? _ob.text : [_ob.text],
    lines_rect_ar = [];

  //__("_ob: " + JSON.stringify(_ob));

  // find max width
  for (i = 0; i < text_ar.length; i++) {
    currentText = text_ar[i];
    if (typeof currentText === "object") {
      currentText = currentText.text;
    }
    line_str = currentText;
    lineWidth = Text.getStringWidth(line_str);
    if (lineWidth > blockWidth) {
      blockWidth = lineWidth;
    }
  }

  itemPos = Layout.getAlignedPos(
    _ob.alignH,
    _ob.alignV,
    blockWidth,
    Text.lineHeight * text_ar.length,
    _ob.offsetH,
    _ob.offsetV
  );

  // bg
  bgMeasurements = {
    left: itemPos.x - Layout.textBgPaddingProportional,
    top: itemPos.y - Layout.textBgPaddingProportional,
    width: blockWidth + Layout.textBgPaddingProportional * 2,
    height:
      Text.lineHeight * text_ar.length +
      Layout.textBgPaddingProportional * 2 -
      Text.lineSpacing,
  };

  if (_ob.drawBackground) {
    Display.ctx.fillStyle = Display.overlayBgColor;
    Display.ctx.fillRect(
      bgMeasurements.left,
      bgMeasurements.top,
      bgMeasurements.width,
      bgMeasurements.height
    );
  }

  for (i = 0; i < text_ar.length; i++) {
    // Presume the array contains bare strings
    currentText = text_ar[i];
    // But if the array contains objects, override
    if (typeof currentText === "object") {
      color = currentText.color || Text.defaultColor;
      colorSecondary = currentText.colorSecondary || Text.shadowColor;
      flashing = currentText.flashing;
      currentText = currentText.text;
    } else {
      color = _ob.color || Text.defaultColor;
      colorSecondary = _ob.colorSecondary || Text.shadowColor;
    }
    line_str = currentText;
    lineWidth = Text.getStringWidth(line_str);

    if (
      !_ob.measureOnly &&
      !isEmpty(line_str) &&
      (!flashing ||
        !Display.flashIsOff(GAME.TEXT_FLASH_ON_SECS, GAME.TEXT_FLASH_OFF_SECS))
    ) {
      BitmapText.lineToBitmap(
        line_str,
        Math.round(itemPos.x + (blockWidth - lineWidth) / 2),
        Math.round(itemPos.y + Text.lineHeight * i),
        color,
        colorSecondary
      );
    }

    lines_rect_ar[i] = {
      left: itemPos.x,
      right: itemPos.x + lineWidth + (blockWidth - lineWidth) / 2,
      top: itemPos.y + Text.lineHeight * i,
      bottom: itemPos.y + Text.lineHeight * (i + 1),
    };
  }

  // return info about drawn text
  return {
    bgMeasurements: bgMeasurements,
    blockWidth: blockWidth,
    lines_rect_ar: lines_rect_ar,
  };
};

Text.drawLevel = function () {
  var levelNumber =
      Game.isInLevelOutro || Game.isInGameOver
        ? STRING.NO_LEVEL
        : Game.levelsCompletedThisSession + 1,
    str = Text.useShortVersions
      ? Game.curLevelId + " " + padString(levelNumber, STRING.LEVEL_PADSTRING)
      : padString(levelNumber, STRING.LEVEL_PADSTRING) +
        "/" +
        padString(Game.lastLevelIndex, STRING.LEVEL_PADSTRING) +
        " " +
        Game.curLevelId;

  Text.draw({
    text: str,
    drawBackground: true,
    alignH: Layout.currentAspectUI.levelText.alignH,
    alignV: Layout.currentAspectUI.levelText.alignV,
    color: Text.accentColor,
  });
};

Text.drawFps = function () {
  Text.draw({
    text: InternalTimer.currentFps.toString() + "/" + GAME.TARGET_FPS,
    drawBackground: true,
    alignH: Layout.currentAspectUI.fps.alignH,
    alignV: Layout.currentAspectUI.fps.alignV,
    offsetH: Layout.currentAspectUI.fps.offsetByCharsH * Text.fullCharWidth,
    offsetV:
      Layout.currentAspectUI.fps.offsetByCharsV *
      (Text.lineHeight + Layout.textBgPaddingProportional * 2),
  });
};

Text.drawVersionInfo = function (_ob) {
  var alignH,
    alignV,
    offsetV,
    offsetH,
    str = window.PipeDream.versionInfo.displayString.toUpperCase();

  if (_ob?.isInLevelIntro) {
    alignH = Layout.currentAspectUI.versionInfoLevelIntro.alignH;
    alignV = Layout.currentAspectUI.versionInfoLevelIntro.alignV;
    offsetH =
      Layout.currentAspectUI.versionInfoLevelIntro.offsetByCharsH *
      Text.fullCharWidth;
    offsetV = 0;
  } else {
    alignH = Layout.currentAspectUI.mainTitle.alignH;
    alignV = Layout.currentAspectUI.mainTitle.alignV;
    offsetV = 0;
    offsetH =
      Math.round(
        (Layout.mainTitle_rect.right - Layout.mainTitle_rect.left) / 2 -
          Text.getStringWidth(str) / 2
      ) + Layout.textBgPaddingProportional;
    if (Display.isLandscapeAspect) {
      offsetV += Layout.mainTitle_rect.bottom - Layout.mainTitle_rect.top;
    }
  }

  Text.draw({
    text: str,
    color: _ob?.color || GAME.VERSIONNUMBER_COLOR,
    drawBackground: true,
    alignH: alignH,
    alignV: alignV,
    offsetH: offsetH,
    offsetV: offsetV,
  });
};

Text.drawTimeRemaining = function (_data) {
  var str = Text.useShortVersions
    ? Game.timeRemaining + " " + STRING.CLOCK
    : STRING.TIME_TEXT +
      padString(Game.timeRemaining || 0, STRING.TIME_PADSTRING);

  Text.draw({
    text: {
      text: str,
      flashing: _data?.timeIsLow,
    },
    drawBackground: true,
    alignH: Layout.currentAspectUI.timer.alignH,
    alignV: Layout.currentAspectUI.timer.alignV,
    offsetH: Layout.currentAspectUI.timer.offsetByCharsH * Text.fullCharWidth,
    offsetV:
      Layout.currentAspectUI.timer.offsetByCharsV *
      (Text.lineHeight + Layout.textBgPaddingProportional * 2),
  });
};

Text.drawCollected = function () {
  var str = Text.useShortVersions
    ? "! " + padString(Game.collectableRemaining || 0, STRING.COLLECT_PADSTRING)
    : STRING.COLLECT_TEXT +
      " " +
      padString(Game.collectableRemaining || 0, STRING.COLLECT_PADSTRING);

  Text.draw({
    text: str,
    drawBackground: true,
    alignH: Layout.currentAspectUI.collectText.alignH,
    alignV: Layout.currentAspectUI.collectText.alignV,
    offsetH:
      Layout.currentAspectUI.collectText.offsetByCharsH * Text.fullCharWidth,
    offsetV:
      Layout.currentAspectUI.collectText.offsetByCharsV *
      (Text.lineHeight + Layout.textBgPaddingProportional * 2),
  });
};

Text.drawInstructions = function (_ar) {
  var i, text_ob, alignH, alignV, offsetH, offsetV;

  for (i = 0; i < _ar.length; i++) {
    text_ob = _ar[i];

    if (Display.isLandscapeAspect) {
      alignH = text_ob.landscapeAlignH;
      alignV = text_ob.landscapeAlignV;
      offsetH = Math.round(text_ob.landscapeOffsetCharsH * Text.fullCharWidth);
      offsetV = Math.round(text_ob.landscapeOffsetCharsV * Text.lineHeight);
      if (text_ob.isPlayer) {
        offsetH += Player.pos.x;
      }
    } else {
      alignH = text_ob.portraitAlignH;
      alignV = text_ob.portraitAlignV;
      offsetH = Math.round(text_ob.portraitOffsetCharsH * Text.fullCharWidth);
      offsetV = Math.round(text_ob.portraitOffsetCharsV * Text.lineHeight);
      if (text_ob.isPlayer) {
        offsetV += Player.pos.y;
      }
    }

    Text.draw({
      text: text_ob.text,
      color: GAME.INSTRUCTIONTEXT_COLOR,
      drawBackground: true,
      alignH: alignH,
      alignV: alignV,
      offsetH: offsetH,
      offsetV: offsetV,
    });
  }
};

export { Text };