Webページで一部フォントの位置がOS依存になる問題に対処する

[DIN Condensed](https://fonts.adobe.com/fonts/din-condensed)をVivliostyleで使用していたところ、WindowsとLinux（＆macOS）で組版結果が異なることに気づきました。Playwrightを直接使用して確認してみても同様の問題が発生していたため、おそらくWindows版のChromiumのレンダリング実装周りに問題があるのではないかと推測します。

|          Linux          |         Windows         |
| :---------------------: | :---------------------: |
| ![before.linux.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1550728/6a710025-98bb-4bbc-b3d2-6dfb5d5a967b.png) | ![before.win32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1550728/04dfd4df-13da-4a5a-b357-9973d41c0ee5.png) |

<details>
<summary>再現用ソースコード</summary>

```html
<html>
  <head>
    <style>
      @font-face {
        font-family: "DIN Condensed Bold";
        src: url("DIN Condensed Bold.ttf");
      }
    </style>
  </head>
  <body style="margin: 0">
    <div
      style="
        margin: 0;
        width: 100px;
        height: 100px;
        background-color: lightgray;
      "
    ></div>
    <p
      style='
        margin: 0;
        font-family: "DIN Condensed Bold";
        font-size: 48px;
        position: absolute;
        left: 100px;
        top: 100px;
        border: solid black 1px;
      '
    >
      0
    </p>
  </body>
</html>
```

```js
import os from "node:os";
import path from "node:path";
import * as url from "node:url";

import { chromium } from "playwright";

const indexPath = path.resolve(
  path.dirname(url.fileURLToPath(import.meta.url)),
  "index.html",
);
const indexUrl = url.pathToFileURL(indexPath).toString();

const browser = await chromium.launch();
try {
  const page = await browser.newPage();
  await page.goto(indexUrl);
  await page.screenshot({
    path: `screenshot.${os.platform()}.png`,
    clip: { x: 0, y: 0, width: 300, height: 300 },
  });
} finally {
  await browser.close();
}
```

メモ：

```
> docker run --rm --interactive --tty --mount type=bind,source=.,target=/workdir --entrypoint /bin/bash --workdir /workdir node:lts
# npm install && npx playwright install --with-deps chromium
```

</details>

CSSからフォントの設定値を上書きできれば解決できそうです。[`ascent-override`](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/ascent-override)・[`descent-override`](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/descent-override)・[`line-gap-override`](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/line-gap-override)プロパティが該当することがわかりました。

適切な値を設定するために、Pythonスクリプトでフォントのメトリクス情報を調べます。

```python
import fontTools.ttLib

def get_font_metrics_for_override(path: str):
    font = fontTools.ttLib.TTFont(path)

    units_per_em = font["head"].unitsPerEm
    os2 = font["OS/2"]

    ascent = os2.sTypoAscender
    descent = abs(os2.sTypoDescender)
    line_gap = os2.sTypoLineGap

    ascent_override = 100 * ascent / units_per_em
    descent_override = 100 * descent / units_per_em
    line_gap_override = 100 * line_gap / units_per_em

    return [ascent_override, descent_override, line_gap_override]


[ascent_override, descent_override, line_gap_override] = get_font_metrics_for_override(
    "DIN Condensed Bold.ttf"
)
print(f"ascent-override: {ascent_override}%;")
print(f"descent-override: {descent_override}%;")
print(f"line-gap-override: {line_gap_override}%;")
```

```
$ .venv/bin/pip install fonttools
$ .venv/bin/python main.py
ascent-override: 71.2%;
descent-override: 28.8%;
line-gap-override: 20.0%;
```

上記で得られた値を使って、CSSの`@font-face`ルールを修正します。

```diff
  @font-face {
    font-family: "DIN Condensed Bold";
    src: url("DIN Condensed Bold.ttf");
+   ascent-override: 71.2%;
+   descent-override: 28.8%;
+   line-gap-override: 20.0%;
  }
```

|         Linux          |        Windows         |
| :--------------------: | :--------------------: |
| ![after.linux.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1550728/22a904b9-d031-4b6d-9569-5c29c7a5f53c.png) | ![after.win32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1550728/2f261708-811a-41f5-9c4a-89265c3d8c7b.png) |

完全一致とはいきませんが実用上問題ない程度に改善することができました。