CSSのpaltで、日本語の文字のあいだを詰めて組む方法

​​CSS​Playwright,フォント,日本語組版

日本語の文字のあいだを詰めて組むには、CSS で font-feature-settings: "palt" を指定します。フォントの中にある詰め方の情報(OpenType の palt という機能)が使われ、かなやかっこ、句読点が、字形に合った幅で並びます。フォントがこの機能を持っていないと、指定しても何も変わりません。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。フォントは Noto Sans CJK JP、IPAゴシック、IPA Pゴシックを使い、検証用のページを Playwright で開いて、1文字ずつの幅を測りました。Firefox と Safari、Windows や macOS に入っているフォント、Web フォントとして読み込んだフォントでの表示は試していません。各ブラウザーの対応バージョンは、互換性データ(@mdn/browser-compat-data 8.1.8)の値です。

2つの見出しを縦に並べた画面。上の「指定なし」では、見出し『「ねこの手」を、借りたい。』のかっこや読点のまわりに空きがあり、下線の長さは13.00文字分。下の「font-feature-settings: "palt"」では同じ見出しの文字のあいだが詰まり、下線の長さは10.51文字分
上は指定なし、下は palt を指定した見出し。水色の下線は見出しの幅

詰めたい要素に palt を指定する

詰めたい要素に font-feature-settings: "palt" を指定します。

css
h1,
h2 {
  font-feature-settings: "palt";
}

"palt" のように機能の名前(4文字のタグ)だけを書くと、その機能がオンになります。MDN の説明では、名前のあとに on や 1 を付けても同じ意味で、off か 0 を付けるとオフになります。font-feature-settings は子要素に継承されます(CSS Fonts Level 4 の仕様)。

どの文字が、どれだけ詰まるか

検証環境で、Noto Sans CJK JP の 20px の文字を1つずつ測りました。単位は em(文字の大きさに対する割合)で、1.00 が全角です。

文字 指定なし palt
「 」 、 。 1.00 0.50
こ 1.00 0.87
り 1.00 0.83
ね 1.00 1.01
タ 1.00 0.88
手 借 1.00 1.00
A 0.61 0.61

かっこと句読点は半分の幅になりました。ひらがなとカタカナは、字の形に合わせて 0.83〜0.96 ほどに狭まります。ね のように、わずかに広くなる字もありました。漢字は全角のままで、英字の幅も変わりません。

「ねこの手」を、借りたい。カタカナとABC という行全体では、指定なしが 19.90em、palt が 16.95em で、3文字分ほど短くなりました。

かっこと句読点だけを詰めたいときは halt

Noto Sans CJK JP は、halt という機能も持っています。同じ行に font-feature-settings: "halt" を指定すると、かっこと句読点だけが 0.50 になり、かなは 1.00 のままでした。行全体は 17.90em です。

かっこが続くところも、半分より狭くはならなかった

」「 のように続くかっこの空きは、text-spacing-trim というプロパティでも詰められます。検証環境の Chromium と Noto Sans CJK JP では、何も書かなくても 」「 の 「 が 0.50 になりました(MDN では実験的な機能とされ、フォントが halt か chws を持っていないと効きません)。palt と重なって詰めすぎにならないかを、「東」「東」(東)、東。 という行で確かめました。

palt を指定すると、かっこと句読点はすべて 0.50 になり、行全体は 8.00em でした。続くかっこが 0.50 よりさらに狭くなることはありませんでした。palt に text-spacing-trim: space-all(詰めない指定)を足しても、8.00em のままでした。

フォントが palt を持っていないと変わらない

palt は、フォントの中にある情報を使います。Python のライブラリ fontTools で調べると、Noto Sans CJK JP は palt を持ち、IPAゴシックは持っていませんでした。次のコードで、IPAゴシックのファイル(ipag.ttf)を調べると False と出ます。

python
from fontTools.ttLib import TTFont

font = TTFont("ipag.ttf")
tags = set()
if "GPOS" in font:
    tags = {r.FeatureTag for r in font["GPOS"].table.FeatureList.FeatureRecord}
print("palt" in tags)

IPAゴシックで同じ行に palt を指定すると、すべての文字が指定なしと同じ幅で、行全体も 19.50em のまま変わりませんでした。指定しても見た目が変わらないときは、表示に使われているフォントがこの機能を持っているかを確かめてください。

IPA Pゴシックのように、字の幅が最初からそれぞれ違う(プロポーショナルな)フォントもあります。IPA Pゴシックは palt を持っていませんが、指定なしでも行全体は 16.67em で、Noto Sans CJK JP に palt を指定したときと近い幅でした。

font-variant-east-asian の proportional-width では詰まらない

MDN の font-feature-settings のページでは、できる限り font-variant-east-asian などの個別のプロパティを使うべきだとされています。ただ、font-variant-east-asian の proportional-width は、palt ではなく pwid という別の機能に対応しています。

検証環境で font-variant-east-asian: proportional-width を指定すると、かなとかっこ、句読点の幅は 1.00 のまま変わりませんでした。変わったのは、全角の英数字です。

文字 指定なし proportional-width palt
A 1.00 0.61 0.78
1 1.00 0.56 0.76
ね 1.00 1.00 1.01
、 1.00 1.00 0.50

全角の A は、半角の A と同じ 0.61 になりました。文字のあいだを詰める目的なら、font-feature-settings で palt を指定します。

CSS Fonts Level 4 の仕様にある横書きのカーニングの手順では、font-kerning でカーニングを有効にしたとき、フォントが palt を持っていれば palt を適用します。検証環境の Chromium 153 では、font-kerning: normal を指定しても、行全体は 19.90em のままで詰まりませんでした。

子要素で font-feature-settings を書くと、palt が外れる

font-feature-settings は、親と子の指定が足し合わされません。仕様の font-feature-settings の節にも、値はまとめて1回で設定され、継承した値に機能を1つずつ足したり外したりする方法はない、と書かれています。子要素で別の機能を書くと、親から継承した palt は外れます。

検証環境で、body に palt を指定し、中の span に次の3通りの指定をして、「ねこの手」 の6文字の幅を測りました。

css
body {
  font-feature-settings: "palt";
}

/* 1. palt が外れる */
span {
  font-feature-settings: "tnum";
}

/* 2. palt も並べて書く */
span {
  font-feature-settings: "palt", "tnum";
}

/* 3. 数字の幅は個別のプロパティで指定する */
span {
  font-variant-numeric: tabular-nums;
}
span の指定 計算された font-feature-settings 「ねこの手」 の幅
なし "palt" 4.84em
1. "tnum" だけ "tnum" 6.00em
2. "palt", "tnum" "palt", "tnum" 4.84em
3. font-variant-numeric "palt" 4.84em

"tnum" だけを書いた span では、palt を指定しない場合と同じ 6.00em に戻りました。子要素でほかの機能を足すときは、palt も並べて書くか、font-variant-numeric のような個別のプロパティを使います。

本文に使うと、行ごとの文字数と右端の位置が変わる

palt を指定すると、字によって幅が変わるので、1行に入る文字数も行ごとに変わります。

検証環境で、幅を15文字分にした段落に同じ文章を入れ、各行の文字数と右端の位置を測りました。

行 指定なし palt palt と justify
1行目 15文字、15.00em 15文字、14.11em 15文字、15.00em
2行目 15文字、15.00em 16文字、14.74em 16文字、15.00em
3行目 15文字、14.95em 16文字、14.97em 16文字、15.00em
4行目 14文字、13.90em 17文字、14.93em 17文字、15.00em
5行目 12文字、11.77em 7文字、5.80em 7文字、5.80em

指定なしでは、1〜3行目がどれも15文字で、右端は 15.00em と 14.95em でした。4行目は14文字で、右端は 13.90em でした。palt を指定すると、文字数が行ごとに15〜17文字と変わり、最後の行を除く右端は 14.11em から 14.97em まで、行によってずれました。段落に text-align: justify を足すと、最後の行以外の右端は 15.00em にそろいました。

縦書きでは vpal を使う

仕様のカーニングの手順では、横書きで palt、縦書きで vpal を使います。縦書き(writing-mode: vertical-rl)の段落に palt を指定しても、検証環境では文字の高さは変わりませんでした。縦書きで詰めるときは、vpal を指定します。

縦書きの列を3つ横に並べた画面。左の「指定なし」と中央の「"palt"」は、『「ねこの手」を、借りたい。』の文字が同じ間隔で並び、高さはどちらも13.00文字分。右の「"vpal"」は文字のあいだが詰まり、高さは10.55文字分
縦書きの比較。左から指定なし、palt、vpal。各列の右にある水色の線は、列の高さ
css
.vertical {
  writing-mode: vertical-rl;
  font-feature-settings: "vpal";
}

Noto Sans CJK JP は vpal も持っています。vpal では、かっこと句読点が 0.50、かなが 0.87〜0.96 になり、「ねこの手」を、借りたい。 の列の高さは 13.00em から 10.55em になりました。

主要なブラウザーで使える

互換性データでは、font-feature-settings は Chrome 48、Edge 12、Firefox 34、Safari 9.1、iOS の Safari 9.3、Android の Chrome 48 から使えます。ただし、詰まり方はフォントしだいです。閲覧する端末に入っているフォントが palt を持っていなければ、その端末では詰まりません。

​Posted by 86