テキストの長さを元に、divなどに収まるサイズのフォントサイズを計算するJSのメモ

なんかCSSとかJSでライブラリすでにありそうだなーって思いつつも自前で実装したほうが早そうだったので書いたら、既にCanvasで簡単かつ正確に取得できたので、両方残しておきます。

## やりたいこと

以下条件をもつときに、見切れたりすることなくテキストを表示したい。

* divやpタグ内にテキストを挿入し表示する。
* divやpタグの横幅は、一定ではない。
* 表示したいテキストの文字数は一定ではない。
* テキストは横向きに1行で表示し、改行はしない。ありえない。

### 補足:

* ViewportWidth 等は、テキストが一定ではないため使え無い。


## 実装（Goodな方）

[コメント](https://qiita.com/YKEI_mrn/items/0f9f1330ea69578d2f36#comment-cd0cedcb7dd77aa15714) でおしえていただきました。

非表示なcanvasを追加して、 `TextMetrics ctx.measureText(text);` で計測する方法。


### JS　&　HTML
```html
<canvas id="canvasForMeasureText" style="display: none;"></canvas>
```

```js
// 以下HTMLを追加しないと動かない。JSで追加する処理かいてない。
// <canvas id="canvasForMeasureText" style="display: none;"></canvas>
 
function makeBetterFontSize(boxWidthPx, text) {
  // Decorations for measure Text:
  // `fontName` is like `ヒラギノ角ゴ Pro W3`.
  // `fontSize` is Number.
  let decorations = {
    fontName: 'ヒラギノ角ゴ Pro W3',
    fontSize: 15,
  };
  // Decorate a ctx
  function decorateCtx(originalCtx, decorationSettings) {
    let ctx = originalCtx;
    if (decorationSettings.fontSize && decorationSettings.fontName) {
      const fontSetting = decorationSettings.fontSize + 'px "' + decorationSettings.fontName + '"';
      ctx.font = fontSetting
    }
    return ctx
  }

  // Default is 15px for my project.
  let fontSize = 30;
  if (boxWidthPx < 1) {
    console.warn('`boxWidthPx` must be over 1.');
    return fontSize;
  }
  if (typeof boxWidthPx !== 'number') {
    console.warn('`boxWidthPx` is not Number. return default font-size: ' + typeof boxWidthPx);
    return fontSize;
  }
  if (typeof text !== 'string') {
    console.warn('`text` is not String. return default font-size.' + typeof text);
    return fontSize;
  }

  // Setup decoration settings.
  while(fontSize > 1) {
    const canvas = document.getElementById('canvasForMeasureText');
    if (!canvas) {
      console.warn('`canvasForMeasureText` is null. return default font-size.');
      break;
    }
    const ctx = canvas.getContext('2d');
    decorations.fontSize = fontSize;
    const decoratedCtx = decorateCtx(ctx, decorations);
    const textMetrics = decoratedCtx.measureText(text);
    console.log(' ', 'text metrics', textMetrics, ' <=', boxWidthPx);
    if (textMetrics.width <= boxWidthPx) {
      break;
    }
    fontSize -= 1;
  }
  return fontSize;
}

```

### 検証用のコード

```Javascript
var boxWidth = 271;
var text = "99.99999999";
console.log("-", makeBetterFontSize(boxWidth, text));
// 30

boxWidth = 271;
text = "999999999.99999999";
console.log("A", makeBetterFontSize(boxWidth, text));
// 23

boxWidth = 271;
text = "9999999999999.99999999";
console.log("B", makeBetterFontSize(boxWidth, text));
// 19

boxWidth = 300;
text = "9999999999999.99999999";
console.log("C", makeBetterFontSize(boxWidth, text));
// 21

boxWidth = 30;
text = "9999999999999.99999999";
console.log("D", makeBetterFontSize(boxWidth, text));
// 2

boxWidth = 20;
text = "9999999999999.99999999";
console.log("E", makeBetterFontSize(boxWidth, text));
// 1

boxWidth = 271;
text = "9.99999999";
console.log("F", makeBetterFontSize(boxWidth, text));
// 30

boxWidth = "あああ";
text = "ほ";
console.log("G", makeBetterFontSize(boxWidth, text));
// 30
// ⚠️がでる。使い方が間違ってる。

boxWidth = 300;
text = [];
console.log("H", makeBetterFontSize(boxWidth, text));
// 30
// ⚠️がでる。使い方が間違ってる。

boxWidth = 0;
text = "111.0";
console.log("I", makeBetterFontSize(boxWidth, text));
// 30
// ⚠️がでる。使い方が間違ってる。
```


## 実装（NoGoodな方）

使用するフォントにより数値が不正確になってしまうため、Goodな方をつかったほうがよい。

### function

```Javascript
function divideBoxwitdhAndPxPerText(boxWidthPx, pxPerText) {
  return boxWidthPx / pxPerText;
}

function makeBetterFontSize(boxWidthPx, text){
  // Default is 15.(font-size:30px) for my proj.
  var pxPerText = 15;
  if (boxWidthPx < 1) {
    console.warn('`boxWidthPx` must be over 1.');
    return pxPerText * 2;
  }
  if (typeof boxWidth !== 'number') {
    console.warn('`boxWidthPx` is not Number. return default font-size.');
    return pxPerText * 2;
  }
  if (typeof text !== 'string') {
    console.warn('`text` is not String. return default font-size.');
    return pxPerText * 2;
  }
  while(pxPerText > 0) {
    var maxTextLength = divideBoxwitdhAndPxPerText(boxWidthPx, pxPerText);
    if (text.length <= maxTextLength) {
      break;
    }
    pxPerText -= 1;
  }
  if (pxPerText === 0) {
    pxPerText = 0.5;
  }
  // Font-size is `pxPerText * 2` for my proj.
  return pxPerText * 2;
}
```

### 検証用のコード

```Javascript
var boxWidth = 271;
var text = "999999999.99999999";
console.log("A", makeBetterFontSize(boxWidth, text));
// 30

boxWidth = 271;
text = "9999999999999.99999999";
console.log("B", makeBetterFontSize(boxWidth, text));
// 26

boxWidth = 300;
text = "9999999999999.99999999";
console.log("C", makeBetterFontSize(boxWidth, text));
// 30

boxWidth = 30;
text = "9999999999999.99999999";
console.log("D", makeBetterFontSize(boxWidth, text));
// 2

boxWidth = 20;
text = "9999999999999.99999999";
console.log("E", makeBetterFontSize(boxWidth, text));
// 1

boxWidth = 271;
text = "9.99999999";
console.log("F", makeBetterFontSize(boxWidth, text));
// 30

boxWidth = "あああ";
text = "ほ";
console.log("G", makeBetterFontSize(boxWidth, text));
// 30
// ⚠️がでる。使い方が間違ってる。

boxWidth = 300;
text = [];
console.log("H", makeBetterFontSize(boxWidth, text));
// 30
// ⚠️がでる。使い方が間違ってる。

boxWidth = 0;
text = "111.0";
console.log("I", makeBetterFontSize(boxWidth, text));
// 30
// ⚠️がでる。使い方が間違ってる。
```

#### 補足

`boxWidth` には実際のpxが入るので以下みたいなのをつかう想定。

```Javascript
var boxWidth = document.getElementById('my-id').clientWidth;
```

## 最後に

NoGoodな方も、何かの役に立つかも知れないので残しておきました。コメントで書いていましたが、誤差でるとおもってたら結構あるようで、おしえてもらってよかったという気持ち。

以上。