0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Webの縦書きは2種類ある——writing-modeの「見た目」とSNSに貼れる「テキストの縦書き」を行列転置で作る

0
Posted at

「日本語を縦書きにしたい」には、実は2つの全く違う要求が混ざっている。1つは「画面で縦書きに見えればいい」(Webページのデザイン)。もう1つは「縦書きの文字列そのものが欲しい」(SNSやメモ帳に貼ったときに縦に並んでほしい)。前者は CSS 一行で終わるが、後者はプレーンテキストで渡す限り CSS では達成できない。文字列を物理的に組み替える必要がある。

横書きテキストを縦書きプレビューしつつ、SNS 貼り付け用の縦書き文字列も生成できるツールをぱんだツールズの1機能として作った。

この記事では、この2種類の縦書きの違いと、後者を行列の転置で実装する方法を解説する。

見た目の縦書き:writing-mode 一行

まず簡単な方。Webページ上で縦書きに見せるだけなら、CSS の writing-mode でブラウザがネイティブに縦組みしてくれる。

const writingModeStyle: React.CSSProperties =
  direction === 'rtl'
    ? { writingMode: 'vertical-rl', textOrientation: 'mixed' }  // 右→左(日本語標準)
    : { writingMode: 'vertical-lr', textOrientation: 'mixed' }  // 左→右
  • vertical-rl は「文字は上→下、列は右→左」。日本語の書籍・新聞と同じ並び。vertical-lr は列が左→右
  • text-orientation: mixed がポイントで、これを付けると日本語の句読点(。、)や括弧(「」)が縦書き用の正しい向きに自動回転し、全角の仮名・漢字は正立する。mixed は名前のとおり「全角は正立・半角ラテン(英数字)は時計回りに90度横倒し」を混在させる指定

これでプレビューは完成。white-space: pre-wrap を併せれば改行も効く。

<div style={writingModeStyle} className="font-serif whitespace-pre-wrap">
  {text}
</div>

ただしこれは DOM 上のレンダリングが縦になっているだけで、中身のテキストは元の横書き文字列のまま。この要素を選択してコピーしても、貼り付け先では普通の横書きに戻る。「見た目」と「データ」は別物、というのがこの記事の肝。

テキストの縦書き:CSS では不可能

X(旧Twitter)や LINE、メモ帳に「縦書きで」投稿したいとき、writing-mode は何の役にも立たない。それらは投稿欄のスタイルを上書きできないし、そもそもプレーンテキストしか受け取らない。

縦書きに見える文字列を渡すには、改行とスペースを使って文字を格子状に並べるしかない。たとえば「やうやう」「白く」の2行を縦書き風にするなら、こういう文字列を作る。

白や
くう
 や
 う

各行に1文字ずつ、右の列から順に詰めて、足りないところは全角スペースで埋める。これを等幅で表示すると、右列に「やうやう」、左列に「白く」が縦に並んで見える。やっていることは、**入力を行ごとの列に分け、行と列を入れ替える(転置する)**操作。

実装:右→左で読む行列転置

入力テキストを「改行ごとの列」とみなし、転置して縦書き文字列を組む。

const xPostText = useMemo(() => {
  // 1. 改行で分割 → 空行を除外 → 各行を文字配列に(スペースは除去)
  const columns = text
    .split('\n')
    .filter(p => p.trim() !== '')
    .map(p => [...p].filter(ch => ch !== ' '))

  if (columns.length === 0) return ''

  // 2. 一番長い列の長さ = 出力の行数
  const maxHeight = Math.max(...columns.map(col => col.length))

  const rows: string[] = []
  for (let r = 0; r < maxHeight; r++) {
    let row = ''
    // 3. 右列(index 0)を最右に。末尾の列から先頭へ向かって出力する
    for (let c = columns.length - 1; c >= 0; c--) {
      row += columns[c][r] ?? ' '   // 文字が無ければ全角スペースで桁を埋める
    }
    rows.push(row)
  }

  return rows.join('\n')
}, [text])

ポイントが3つある。

列の長さを揃える。 各列(=元の各行)は長さがバラバラなので、最長列に合わせて ?? ' '(全角スペース)でパディングする。これをしないと格子が崩れて縦の列がガタガタになる。半角スペースではなく全角スペースなのが重要で、等幅で日本語の全角文字と桁を合わせるには、埋め草も全角でなければズレる。

右から左へ出力する。 日本語の縦書きは右の列が先頭。入力の1行目(columns[0])を画面の最も右に置きたいので、出力ループは columns.length - 1 から 0逆順に回す。これで「入力順=右から左」になる。

[...p] で文字配列にする。 p.split('') ではなくスプレッド構文で分解しているのは、サロゲートペア(絵文字や一部の漢字)を1文字として扱うため。split('') は UTF-16 のコード単位で割るので、サロゲートペアが2つに割れて化ける。スプレッド/Array.from はコードポイント単位で反復するので安全。

転置後の文字列は、X やメモ帳のような等幅・縦書き非対応の環境に貼ると、全角スペースのパディングのおかげで縦に揃って見える。CSS に一切頼らず、文字列だけで縦書きを「偽装」している。

2つのコピーを用意する理由

このツールにはコピーボタンが2つある。役割が違う。

  • 「テキストをコピー」:入力をそのまま(横書きのまま)コピー。Word や一太郎のような縦書き対応アプリに貼る用。アプリ側で縦書き設定にすれば正しく縦組みされるので、文字列を組み替えてはいけない
  • 「縦書きでコピー」:上記の転置済み文字列をコピー。SNS・メモ帳のような縦書き非対応の環境に貼る用

「縦書きにしたい」と一口に言っても、貼り付け先が縦書きを解釈できるかで必要なものが真逆になる。対応アプリには素のテキストを、非対応環境には転置済みを渡す。この出し分けが、最初に書いた「見た目の縦書き」と「データの縦書き」の違いそのものになっている。

まとめ

Web で「縦書き」を実現する手段は、目的によって完全に別物だった。

  • 画面に縦書きで見せるだけなら writing-mode: vertical-rltext-orientation: mixed の CSS で済む。句読点・括弧も自動で正しい向きになる
  • ただし CSS の縦書きは DOM 表示専用。コピーすると横書きに戻る(データは変わらない)
  • SNS など縦書き非対応の場所に貼るには、文字列自体を行列転置して全角スペースで桁揃えする
  • 転置は「右列が先頭」なので逆順ループ。文字分解は [...str] でサロゲートペア対応
  • 貼り付け先が縦書き対応か否かで、渡すべきもの(素のテキスト/転置済み)は逆になる

「縦書き」という1つの言葉の裏に、CSS で解く問題と文字列操作で解く問題の2つが隠れていた、という気づきが面白い題材だった。

ぱんだツールズ では他にも PDF・画像・CSV・テキスト処理など、開発者向けのツールを多数公開している。全部無料・登録不要・ブラウザ完結で使える。
https://sakutto-panda.com


この記事は Zenn にも同じ内容を投稿しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?