縦書きHTMLにおける文字の向きはどのように定まるか

ドワンゴ教育事業Webフロントエンドチームの berlysia です*1

はじめに

この記事では、日本語の縦書きHTMLにおいて、「ある1文字が縦組みのなかで違和感なく縦書きとして表示される」とはどのように成り立っているのか、意図しない表記になりやすい文字とその理由について紹介します。

最後まで読むと、縦書き時の文字の縦横に関する問題をたちどころに分解できるようになるはずです。とりあえずフォントのせいだろうかと疑う日々には、これでおさらばしましょう*2

N予備校における日本語縦書きHTML

ドワンゴ教育事業が提供するeラーニングサービスの「N予備校」には2万を超える多彩な教材があり、これらはHTMLを中心としたWebの技術で実現しています。とくに国語の現代文、古典、漢文*3では縦書きの表現をしています。*4

教材はPCブラウザ、iOSアプリ、Androidアプリから表示するので、ブラウザやWebView、端末にインストールされているフォントなどの影響を受けます。

N予備校で提供する縦書き教材の一例

CSS の関連仕様

HTMLとCSSで縦書きを実現する仕様の最新版である CSS Writing Modes Level 4 はまだ勧告候補の段階であり、2022年6月現在で十分にサポートされている CSS Writing Modes Level 3 を参照して説明します。 *5

日本語の一般的な縦書きに設定する

以後断りのない限り、画像はWindows 10 Pro上のGoogle Chrome 102で撮影しています。後述しますが、動作がもっとも素朴でした。

縦書きの例

要素の writing-mode プロパティに vertical-rl を指定すると、見慣れた日本語の縦書き表示になる、あるいは近づきます。

半角英数字類が縦書きの中でも横倒しになっています。このあたりの制御には、 text-orientation プロパティを使います。初期値は mixed です。

縦中横の表現も、 text-combine-upright プロパティによって実現できます。

うまくいかないことが起こりやすい文字たち

問題が顕著に起こりやすい文字は、次のような整理ができます:

  • 矢印のようにそれ自体が方向を伴い、何を示したいかによって要求が異なる記号類
  • ダッシュやカッコ、句点読点や小文字など、文字の並ぶ方向との相対関係が重要な文字・記号類

うまくいかない文字がうまくいかない理由を理解する

HTML と CSS で日本語の縦書きをやるときに遭遇しやすい問題について、HTML と CSS の仕様に基づいて理解していきましょう。

縦書きを考慮した文字の周りの方向の定義

CSS Writing Modes Level 3 / 1. Introduction to Writing Modes から、説明に必要な言葉を参照します。

  • インライン基底方向(inline base direction)
    • 行の先頭から末尾に向けた方向を、インライン基底方向と呼びます。
  • ブロックフロー方向(block flow direction)
    • CSSにおけるブロックが積み重なって行く方向であり、その中で行が積み重なって行く方向を、ブロックフロー方向と呼びます。

CSS における縦書きでの文字の縦横

上述した例では writing-modevertical-rl にしたうえで、 text-orientation をいくらか変化させていました。

CSS Writing Modes Level 3 / 5.1.1. Vertical Typesetting and Font Features の記載から、各状態でどのように表示が定まっていくかを追いかけます。

upright typesetting

縦方向の行の中に縦書き用のフォントメトリクスを伴って、一文字*6ずつ正立に(横書きのときに一文字が見えているのと同じ姿で)植字することを言います。

もしフォントが縦書き用のグリフや変形に対応している場合は、そのグリフや変形を使おうとします。

text-orientation: upright のときの動作はこれです。

sideways typesetting

縦方向の行の中に横書き用のフォントメトリクスを伴って、90 度時計回りに回転した状態で植字することを言います。

もしフォントが縦書きの中で横転するとき用のグリフや変形に対応している場合は、それを使おうとします。

text-orientation: sideways のときの動作はこれです。

混合して表示する場合

text-orientation: mixed に相当するとき、各文字の Vertical_Orientation(vo) の値によって、後述する upright / sideways typesetting のどちらが使われるかが定まります。 R であれば sideways 、 U Tu Tr であれば upright になります。

Vertical_Orientation(vo)

Unicode では、各文字*7に Vertical_Orientation(vo) プロパティを定めています。Unicode の付帯文書である Unicode Standard Annex #50 / Unicode Vertical Text Layout と対応する別表 Unicode Vertical Text Layout Property Data File に記載があります。

Vertical_Orientation(vo) プロパティは、次のうちいずれかの値をとり、次のような意味を伴います:

  • U: 正立(upright)に表示する文字。コードチャート上に見えるのと同じ向きに表示する。
  • R: 横倒し(sideways)に表示する文字。コードチャートと比較して 90 度時計回りに回転して表示する。
  • Tu: 正立とも横倒しとも定まらないが、一般的に縦書きではコードチャート上とは異なるグリフが求められる。フォールバックとしてコードチャートと同じように表示できる。
  • Tr: Tu と同様だが、フォールバックとしてコードチャートと比較して 90 度時計回りに回転して表示できる。

Unicodeのコードチャートは Unicode 15.0 Character Code Charts から確認できます。

フォント側の縦書き用グリフの字形

ここまで「縦書き用のグリフや変形に対応している」などと説明してきました。縦書きのときに適した外見となるように、字形があらかじめ 90 度回転された状態になっている、句点読点が右上寄せになっているなど、異なる字形がフォントに用意されていることを指します。

縦書き用のグリフは、OpenType では vert なるタグ名の OpenType feature として用意されます。「縦書きの中で横転するとき用のグリフや変形に対応している」ことは、 vrtr というタグ名で用意されます(が、今回いくつかのフォントの中身を覗いてみた限りでは、全部見たわけではないものの、用意されている事例はありませんでした)。*8 *9

フォントによって字形にばらつきがあり注意を要する文字

この項はとくに UTR50(Unicode 縦書きの文字の向き仕様)で注意を要する文字 | CSS 組版ブログ を参照のうえ、独自に簡単な調査を行って、具体例を用意しました。

網羅度は下がっていますので他にも注意が必要な文字は存在することに注意してください。この例示は具体的にどういう状態にあるかを示すために十分な範囲に絞っています。

Vertical_Orientation が U の文字で注意が必要そうな文字

Vertical OrientationがUで注意が必要な文字とその各種状態の表示

text-orientationmixedupright といった upright typesetting になる条件のもとで正立することを期待していると、フォントによって回転した状態の縦書き用グリフが用意されているために、そうならないことがあります。フォントによらず確実に正立させたい場合は text-combine-upright: all の指定により縦中横を利用するとよさそうです。

また、Noto Sans JPは全く問題が起こらないように見えます。

Vertical_Orientation が R の文字で注意が必要そうな文字

Vertical OrientationがRで注意が必要な文字とその各種状態の表示

三点リーダー U+2026 「…」はかなり意図的な抜粋になっていますが、vo が R で、かつこうしたインライン基底方向に沿っていることを期待される記号で縦書き用グリフが与えられない場合、 text-orientation: upright な指定ではあらぬ方向を向きます。縦書きに対応したフォントを使いましょう。

三点リーダーが縦書きグリフの有無であらぬ方向を向く例

矢印類については、既に 90 度時計回りに回転した状態のグリフが縦書き用に用意されていることが多いです。「上」といったときに、それが紙面・画面における上方向なのか、ブロックフロー方向の始点側という上方向なのかを意識して書く必要があります。

どちらにしても向きをフォントによらず安定させたい場合は縦中横か text-orientation: sideways を使うとよさそうです。

Noto Sans JPは、今回確認したフォントのうちで矢印に強く特徴がありました。 text-orientation: upright のとき他フォントは回転したグリフを持っている一方、Noto Sans JP は「upright とはコードチャート上と同じ見た目である」というのを守っているようにも見えます。Web フォントをただ入れれば安心というわけでもなく、そのフォントがどう実装されているかを睨んでおくべきでしょう。

Noto Sans JPとメイリオで矢印の向きが異なる例。もはや意味が通らない

Vertical_Orientation が Tu の文字で注意が必要そうな文字

Vertical OrientationがTuで注意が必要な文字とその各種状態の表示

Tu については調べた限り深刻な問題はなさそうでしたが、日本の年号の組み文字は意図しない表記になるかもしれません。

Vertical_Orientation が Tr の文字で注意が必要そうな文字

Vertical OrientationがTrで注意が必要な文字とその各種状態の表示

縦書き用グリフの有無と、縦書き用グリフ自体がどの向きで収録されているかに繊細に影響を受けるようです。

U+301A「〚」 U+301B 「〛」 U+3008「〈」U+3009「〉」U+2329「〈」U+232A「〉」は、Windows 10 の游ゴシックに縦書き用グリフがなく、 text-orientationmixedupright のときに横書きと同じ見た目になってしまいます。

U+3030「〰」は、今回調べたうちMSゴシック以外のフォントで縦書きグリフが横書きと同じ見た目になっていて、インライン基底方向に沿っていないので、波ダッシュとして「ああ〰〰」という表現をしようとすると、意図せず横を向いてしまうことが多いです。

波ダッシュがあらぬ方向を向いている例

U+FF1B「;」は今回調べた限りで縦書きグリフをもつフォントがなく、インライン基底方向に沿った表示が期待される*10ものの実際にはそうならないことがほとんどです。なる場合は後述します。

ブラウザ側の実装によっても動作が変わることがある

Windows 10 の Firefox から先述した 4 文字を表示してみると、游ゴシックの場合だけ他のブラウザと異なる表示になりました。ここで Windows 10 の游ゴシックの U+301A U+301B はどちらも縦書きグリフを持ちません。

Windows 10のChromeとFirefoxで表示が異なる例

これは筆者の仕様読解及び実装読解の限りですが、Firefox も Chrome もどちらも仕様に則った実装をしていそうです。 text-orientation: mixed かつ Tr な文字は upright typesetting することになっていますが、もし縦書き用のグリフがフォントにない場合は、 sideways typesetting したものなどで合成することが MAY WISH TO であると書かれていて、Chrome はこのとき upright typesetting しているだけですが、Firefox は後半も含めてこの通りの動作をしていると言えそうです。

In the case of Tr characters, if such vertical alternate glyphs are missing from the font, the UA may wish to [RFC6919] (but is not expected to) synthesize the missing glyphs by typesetting them sideways etc.

CSS Writing Modes Level 3 / 5.1.1. Vertical Typesetting and Font Features / upright typesetting

Firefox はさらに縦書き文字へのテーブルがある

Unicode には縦書き用の文字がいくつか存在します。たとえば三点リーダー U+2026 「…」に対して、縦の三点リーダー U+fe19 「︙」があります。

Firefox はこのような縦書き用文字への対応テーブル*11を一部持っていて、とくに text-orientation: mixed かつ Tr な文字の場合は、タグ名 vert の OpenType feature が提供するテーブルより先に、フォント内の対応する縦書き用文字を参照するようになっているようでした*12

先述の 4 文字はこの対応テーブルに定義がありません。逆に定義があるのが全角セミコロン U+FF1B「;」で、縦書き全角セミコロン U+FE14 「︔」が対応付けられています。

Windows 10 のメイリオ、游ゴシックには共に U+FF1BU+FE14 も縦書き用グリフがなく、どちらも同じように正立した見た目をしています。これらだけでは Firefox と Chrome の結果は同じですが、さらに U+FE14 のグリフを持たない MS ゴシックを並べると、次のような結果になります。

Firefoxで特有の処理が行われていることが察せる例

少し難解な例になってしまっていますが、Firefox でメイリオと游ゴシックの場合、先述したテーブルを使って縦書き全角セミコロンを upright typesetting しています。一方 MS ゴシックの場合、テーブルを使って見に行った先にフォントがなく、元の全角セミコロンの縦書き用グリフもなかったので、sideways typesetting したものを使っていて横転していることがわかります。

Safari にも何らかの特別扱い処理が存在しそう

画像中の文字は上から順に U+2329 U+232A U+3008 U+3009 U+301A U+301B U+3030 U+FF1B であり、すべて vo の値は Tr です。グリフの有無も確認しづらく未確認ではありますが、Firefox とも異なる別のルールがありそうな表示になっていました。Webkit のソースを追いきれていないためこれ以上の情報はありませんが、タレコミがあればご連絡いただけるとうれしいです。

macOSのFirefox, Chrome, Safariで動作が少しずつ違う例

游ゴシックは同名のフォントでもグリフの有無が違うことがある

游ゴシックは macOS に入っているものと Windows に入っているものとで入っているグリフが違うようです*13

この記事の関心である縦書き用のグリフについては、確認方法の都合で広く確認ができませんでしたが、少なくとも U+3008「〈」U+3009「〉」で違いがありました。

U+3008 の縦書き用グリフがmacOSだとあるがWindowsだとない様子

うまくいかない理由と各対処

ここまで挙げてきた要素を使うと、文字の方向がうまく決まらない問題は精度高く説明できます。要素を書き下してみましょう。

  • text-orientation の値が upright / sideways / mixed のどれか
  • その文字の Vertical_Orientation(vo) が U / R / Tu / Tr のどれか
  • その文字を表示するのにどのフォントが使われているか
  • 使われているフォントにその文字に対応する縦書き用のグリフが あるか / ないか / ある場合に向きが回転しているか否か
  • どのブラウザを使っているか

インライン基底方向に沿って回転してほしい文字がコードチャート通りに表示されてしまうとき

text-orientation: upright のとき:

  • vo の値が RTr のとき、フォントが縦書き用グリフを持っていないことを疑います。
    • R の文字だと三点リーダー「…」がわかりやすいですが、他多数でも発生します。そもそもフォントが日本語に対応していない可能性を疑ってください。
    • Tr の文字だと U+301A「〚」 U+301B「〛」 U+301E「〞」 U+3030「〰」 U+FF1B「;」 が要注意です。フォントによって縦書き用グリフがないことがあり、そのときはこの問題が発生します。
  • vo の値が UTu のとき、コードチャート通りに表示されるのが普通です。
    • U の文字だとU+2016「‖」 U+2702「✂」 U+3013「〓」が要注意です。フォントによって縦書き用グリフがコードチャートから回転していることがあり、意図した通りに回転するように見えてしまいますが、フォント依存です。

text-orientation: mixed のとき:

  • vo の値が R ならば、問題は発生しないと思われます。発生する場合は、そもそもフォントが日本語に対応していない可能性を疑ってください。
  • vo の値が Tr であるなら:
    • Chrome の場合はフォントによって縦書き用グリフを持っていないことを疑います。
    • Firefox の場合は変換テーブルを参照して処理対象かどうかを確認します。
      • 対象の場合は対応する縦書き文字のグリフが元の文字のコードチャート通りに見えることを疑います。
      • 対象でない場合は元の文字の縦書き用グリフがコードチャート通りであることを疑いますが、今回の調査で該当する事例はありませんでした。
      • ここで縦書き用グリフがない場合、sideways typesetting されて問題が発生しないはずです。
  • vo の値が UTu のとき、コードチャート通りに表示されるのが普通です。
    • U の文字だとU+2016「‖」 U+2702「✂」 U+3013「〓」が要注意です。フォントによって縦書き用グリフがコードチャートから回転していることがあり、意図した通りに正しく回転するように見えてしまいますが、フォント依存です。

対策

これらの文字で常にインライン基底方向に沿わせたい場合、次のことをします:

  • 縦書きに対応したフォントがロードされるように注意します。以下、グリフの有無はここでロードすることにしたフォントの集合に対して考えます。
  • vo が Tr でかつ縦書き用グリフがないことがある文字について、登場ごとに text-orientation: sideways を指定しておきます。
    • U+301A「〚」 U+301B「〛」 U+301E「〞」 U+3030「〰」 U+FF1B「;」 が該当します。
  • vo が U である文字について、縦書き用グリフが回転していることがある文字を含む場合、登場ごとに text-orientation: sideways を指定しておきます。
    • U+2016「‖」 U+2702「✂」 U+3013「〓」 が該当します。
  • Firefox のために、変換テーブルの縦書き文字側のグリフ収録内容も確認しておきます。

対策を示していない組み合わせは、「インライン基底方向に沿わせたい」の需要が極めて限定的であると考えられます。

text-orientation: sideways を指定するとメトリクスが横書き用のものになるので、縦書きとしては指定しなくて済むほうが綺麗な表示になりやすいです。むやみに指定するべきではありません。

コードチャート通りに正立してほしい文字が意図せず回転してしまうとき

text-orientation: upright のとき:

  • vo の値が UR のとき、コードチャート通りに表示されるのが普通ですが、縦書き用グリフが事前に回転していることを疑います。
    • R について、 U+2190「←」 をはじめとする矢印類の文字が広く該当します。
    • U について、 U+2016「‖」 U+2702「✂」 U+3013「〓」 が該当します。
  • vo の値が Tu のとき、コードチャート通りに表示されるのが普通ですが、組み文字類は表示が変わることがあります。
  • vo の値が Tr のとき、回転して表示されるのが普通ですが、縦書き用グリフがなくて正立してしまうことがあります。
    • U+301A「〚」 U+301B「〛」 U+301E「〞」 U+3030「〰」 U+FF1B「;」 が該当します。

text-orientation: mixed のとき:

  • vo の値が U のとき、コードチャート通りに表示されるのが普通ですが、縦書き用グリフが事前に回転していることを疑います。
    • U+2016「‖」 U+2702「✂」 U+3013「〓」 が該当します。
  • vo の値が R のとき、回転して表示されるのが普通です。
  • vo の値が Tu のとき、コードチャート通りに表示されるのが普通ですが、組み文字類は表示が変わることがあります。
  • vo の値が Tr のとき、回転して表示されるのが普通ですが、縦書き用グリフがなくて正立してしまうことがあります。
    • U+301A「〚」 U+301B「〛」 U+301E「〞」 U+3030「〰」 U+FF1B「;」 が該当します。

対策

これらの文字で常にコードチャート通りに正立させたい場合、次のことをします:

  • 縦書きに対応したフォントがロードされるように注意します。以下、グリフの有無はここでロードすることにしたフォントの集合に対して考えます。
  • text-orientation: upright のとき:
    • vo の値が R である文字について、回転した縦書き用グリフを持つフォントを使う場合、登場ごとに text-combine-upright: all を指定しておきます。
      • U+2190「←」 をはじめとする矢印類の文字が広く該当します。
    • vo の値が U である文字について、回転した縦書き用グリフを持つフォントを使う場合、登場ごとに text-combine-upright: all を指定しておきます。
      • U+2016「‖」 U+2702「✂」 U+3013「〓」 が該当します。
  • text-orientation: mixed のとき:
    • vo の値が U である文字について、回転した縦書き用グリフを持つフォントを使う場合、登場ごとに text-combine-upright: all を指定しておきます。
      • U+2016「‖」 U+2702「✂」 U+3013「〓」 が該当します。
  • vo の値が Tu である文字について、主に組み文字類を制御したい場合は、登場ごとに text-combine-upright: all を指定しておきます。

対策を示していない組み合わせは、「コードチャート通りに正立させたい」の需要が極めて限定的であると考えられます。

text-combine-upright: all を指定するとメトリクスが横書き用のものになるので、縦書きとしては指定しなくて済むほうが綺麗な表示になりやすいです。むやみに指定するべきではありません。

なぜこんな細かいマークアップが必要になっているのか

10 年前の記事ですが、日本語でよくまとまった情報を CSS3 と Unicode 仕様の縦書きの文字の向きの議論について | CSS 組版ブログ に見つけました。筆者もこの記事を起点にいくらか経緯を追いかけましたが、この記事で詳述することは避けます。

ともあれ、現在の仕様の言葉でマークアップが難しい理由を述べようとすると、次の点が重要になりそうです:

  • Vertical_Orientation(vo) の値が英数字をデフォルトで横転させるように定まる一方、記号類での設定に一貫性がない
  • upright typesetting は強制的に文字を正立させようとするが、このときフォントの縦書き用グリフを参照するので、フォント側のサポートの仕方を考慮しなければならない

おわりに

N予備校でとくに国語の教材を取り扱うにあたり、縦書きの表現は避けて通れないものです。これら教材が HTML として適切かつ簡単にマークアップできることは、我が国におけるオンライン学習やeラーニングの可能性を開拓するに等しいです。短期的には紙面と同等の表現ができることはデジタルと紙との両立を容易にし、長期的には紙ではできないレベルのアクセシブルな教材と学習体験の実現に繋がります。

この記事で扱った知識はまだ教材作成の現場まで届けられていませんが、これまでに経験したいくつかのトラブルシュートがこの記事の元になっています。

ドワンゴ教育事業のWebフロントエンドでは、精緻な理解から大胆な手法を編み出すWebフロントエンドエンジニアを募集しています。


使用したバージョン

  • Windows
    • Windows 10 Pro
    • Google Chrome 102.0.5005.115
    • Firefox 102.0
  • macOS
    • macOS Monterey 12.3.1
    • Google Chrome 103.0.5060.53
    • Firefox 102.0
    • Safari 15.4

参考資料


We are hiring!

株式会社ドワンゴの教育事業では、一緒に未来の当たり前の教育をつくるメンバーを募集しています。

カジュアル面談も行っています。 お気軽にご連絡ください!

カジュアル面談応募フォームはこちら

www.nnn.ed.nico

開発チームの取り組み、教育事業の今後については、他の記事や採用資料をご覧ください。

speakerdeck.com

*1:この記事はドワンゴ Advent Calendar 2021 の17日目の記事です!!!!(この記述は正しいです)

*2:とりあえずフォントを疑う日々にはおさらばだと言いましたが、残念ながら精度高い理解のもとでフォントを疑うことになります。

*3:漢文の返り点等は現時点で画像化されており、ここもHTMLで表現したいのは山々なのですが、HTMLでの漢文表現でまともに使えるよく知られたものは存在しません。

*4:2022年6月現在、縦書きでの表示は歴史的経緯でChrome限定となっていますが、これは検証を経たのちに解除する方針です。UAで判定して機能を制限することは極力回避していきます。

*5:CSSの各モジュールのLevelは下位のものを含むようになっているので、大きな外し方はしないと言えます

*6:正確には Typographic character unit を指す

*7:正確には書記素クラスター(Grapheme Cluster)

*8:TrueType だとどうなるんだろう? いったん今回の関心では十分だったことから、調べていません。

*9: vrt2 というタグ名の OpenType feature もありますが、これは CSS では使用されていないことが明記されています。 https://www.w3.org/TR/css-writing-modes-3/#vertical-orientations

*10:実際には、日本語ではインライン基底方向に沿うことを期待するが、中国語ではブロックフロー方向に沿うことを期待するようで、単純ではないらしい https://lists.w3.org/Archives/Public/public-i18n-japanese/2021JulSep/0057.html

*11:https://searchfox.org/mozilla-central/rev/c454f13d83dc85f399fd1eb449ea9ccb156299df/gfx/thebes/gfxHarfBuzzShaper.cpp#197

*12:https://searchfox.org/mozilla-central/rev/c454f13d83dc85f399fd1eb449ea9ccb156299df/gfx/thebes/gfxTextRun.cpp#3510-3535

*13:macOS Safari で游ゴシックが表示できない件などもありますが、それはさておき