3
1

More than 1 year has passed since last update.

にほんごだもの

 みつを。

 ワタクシとにかく色々なところで縦書きしたいワケですよ。
 別に何に使うとかではなく、なにしろとりあえず縦書きしたい!!
 やっぱり、日本語は縦書きですよねぇ~:heart:
 日本語プログラミング言語だもの!

何が問題なのか

 一番問題になるのが、括弧「」()や句読点、。など、横書きの場合と縦書きの場合で、向きや位置が異なる約物があることです。
 まずは、ちょっとこちらの記事をご覧ください。

 タブン一番原始的な縦書き変換の方法です。
 文字の並び順を組み替えてSNSなどで擬似的に縦書きを行うという記事ですが、こうして出来た配列を等幅フォントで反復して描画すれば縦書きの描画ができます。
 でも、変換したい文章に括弧や句読点が入ってきちゃったらどうなるかと言えば・・・
2022-11-29 (3).png
 こうなっちゃう:sweat:
 それで、なんかそれっぽい記号に変換したりしますけどね~。
 そういえば、記事中ですでに長音記号は変換済みでしたね。同様にして句点を半角半濁点にしたりカギ括弧を罫線記号にしたり・・・

   結果=結果の「{カッコ}」を「 ┐」に置換。
   結果=結果の「 {カッコ閉じ}」を「└ 」に置換。
   結果=結果の「…」を「:」に置換。
   結果=結果の「。」を「 ゚」に置換。

2022-11-29 (4).png
 おっと、できたできた♪
 ・・・ではなく!

 こんな代替の記号など使わずに、ちゃんと美しく日本語を縦書き描画したいのです。

v1でイメージ部品に描画する

 なでしこv1では、文字書体に「@」の付いた縦書き用のフォントを指定することが出来ます。
 もっとも、それで文字描画を行うと、文字が90度回転した状態の横書きで描画されてしまうので、画像左回転したところへ描画して、画像右回転で戻してやるという荒技が必要でした。

文字書体は「@MS 明朝|14|太字」 #「@」の付いた縦書きフォントを指定。
母艦を画像左回転。
10,10へ『「こんにちはー。」』を文字描画。
母艦を画像右回転。

こんにちは.png
 いい感じ☆

 ところが、ただそれだけでは三点リーダやダッシュがうまくいかない。
なぞ1.png
 こうなっちゃうのねん:sweat:
 長音記号はOKなのになぜ~。
 あと、三点リーダはもっとナゾで、他の文字とくっついている時と単体の時とで向きが異なるんですよ~。
なぞ2.png
 意味不明すぎる!
 ともかく、こうした記号を個別に対応してやる必要があるため、文章全てを一度に描画することは出来ず、一文字ずつ位置を指定して描画しては回転していかなければなりませんでした。つ、ツラみ~:cry:
 こんな感じ?
 

文字書体は「@MS 明朝|14|太字」 #「@」の付いた縦書きフォントを指定。
母艦の50,10へ『「こんにちはー。」
「こんにちは……」
「こんにちは――」』を縦書き文字描画。

*縦書文字描画({グループ=?}OBJのX,YへSを)
  YYは整数。YY=Y。
  文字用をイメージとして作成。文字用→可視はオフ。
  文字用→幅は「あ」の文字幅取得。
  文字用→高さは「あ」の文字高さ取得。
  もし、行間<1ならば、行間=1。
  もし、文字間隔<1ならば、文字間隔=1。

  (Sの文字数)回
    Sの回数から1文字抜き出す。
    文字はそれ。
    もし、それ=「{CHR(10)}」ならば、
      X=X-(「あ」の文字幅取得)*行間。
      Y=YY。
      続ける。

    文字用を画面クリア。
    OBJのX,Y,(文字用→幅),(文字用→高さ)を文字用の0,0へ画像部分コピー。
    もし、文字=「―」ならば、
      文字用の0,0へ文字を文字描画。描画処理反映。
    違えば、
      文字用を画像左回転。
      文字用の0,0へ文字を文字描画。描画処理反映。
      文字用を画像右回転。
    文字用をOBJのX,Yへ画像コピー。

    Y=Y+(「あ」の文字高さ取得)*文字間隔。
  VCL_FREE(文字用)

 ちなみに、すでに何か描画されているイメージ部品へ文字描画することを想定しています。
 なでしこv1のイメージ部品の何も描画されていない部分は透明ではなく白なので!
 ダッシュのような対応が必要な記号は他にもあるかもだけど、とりあえず……
2022-11-29 (7).png
 できました!

v3でcanvasに描画する

 キャンバスで同様のことが出来るかしらと思ったら、「@」をフォント名に付けて描画フォント設定しても認識してくれない!
 DOMへの表示はwriting-modevertical-rlを指定すれば自動で縦書き用のフォントが選択されて縦書き表示になるので明示して指定する必要はないんだけど、キャンバスへの文字描画には縦書き設定がない。
 なんとか縦書きフォントを指定する方法はないんかと調べてみたけど、どうやらJavascript的にないっぽい(?)

webフォント

 どうやら、webフォントを使うより他ないとゆうことのようです……
 といっても全ての文字というわけではなく、必要な約物を縦書き用の指定でサブセットフォント化すれば良いということらしい。

 うーん、なるほど。
 リンク先の武蔵システムさんで配布されているフリーの毛筆フォントにはたいへんお世話になっておりますですよ。
 もちろんWOFFコンバータもサブセットフォントメーカーも大変便利です!
 でも、そうゆう用途は思いつかなかった。神だね!

 上記サイトから入手すれば、やり方書く必要ないくらい簡単です☆
 ついでにWOFFコンバータも入手してwoffファイルにしておけばサイズも小さく、なでしこさんの動くモダンブラウザなら、みんなちゃんと表示できるハズです(たぶんね)

 なでしこさんでwebフォントを指定するには、スタイルシートの設定をHTML設定するのが早いです。

DOM親要素に「<style>
    @font-face {波カッコ}
        font-family: "縦書き用約物";
        src: url("https://...URL.../tategakiWSF.woff")  format('woff');
    {波カッコ閉じ}
</style>」をHTML設定。

 こんな感じ?
 そして、

「20px "縦書き用約物",serif」に描画フォント設定。

 みたいに設定してあげればOK。
 フォントファミリーは、前に指定したやつから優先的に適用されていくため、サブセットフォントを先頭に指定すれば、その中にある約物だけそこから適用されて、その他の文字はそれ以降に指定したフォントが通常通りに適用されるって寸法です。

縦書き文字描画

いちいち文字を回転させる必要が無い分、コードはぐっと簡単です。

●(xyにsを文字間隔で|xyへ)縦書き文字描画
 x,y=xy。sはsを文字列分解。
 sを反復。
    [x,y]に対象を文字描画。
    y=y+(「あ」の文字高さ取得)+文字間隔。
 ここまで。
ここまで。

 頭で考えるとしちめんどくさいけど、文字列をバラバラにして一文字ずつ、yをずらしながら描画していくだけですね。
 あっ、でも、通常の文字描画が一行ずつの描画で、テキストに改行があっても改行されない仕様なので忘れてましたが、v1の縦書き文字描画は複数行をまとめて描画できるようにしてたんだった!
 まとめて描画したいよね~。

※whは[行間,文字間隔]
●(xyにwhでsを|xyへ)縦書き文章描画
    x,y=xy。s=sを改行で区切る。
    もし、wh=空ならば、wh=[0,2]。
    行間,文字間隔=wh。
    w=(「あ」の文字幅取得)+行間。
    sを反復
        [x,y]に対象を文字間隔で縦書き文字描画。
        x=x-w。
    ここまで。
ここまで。

ウェブフォントの読み込み完了判定

 フォントが読み込まれる前に描画が走ってしまうと、当然フォントは適用されず、標準の向きや位置のおかしい約物が描画されてしまいます。
 これを判定する方法が長らく分かりませんでした。
 なんか、APIもあるらしいんだけど、よく分かんないんだよね~:sweat:
 でも、ここに参考になることが書いてありました!

 そもそもここに、APIを使った方がいいでしょうとか書かれているワケですが、気にしない!💧
 ようするに、適用したいwebフォントとserifを設定したキャンバスと、serifだけを設定したキャンバスを用意して、文字幅を比較し続け、違う値になったら読み込み完了した証拠というわけですね! 天才だね!!

 こんな感じ?

変数 読み込み完了ウェブフォント=空辞書。

#ウェブフォント設定
●(フォントの|フォントをテスト文字列で)ウェブフォント読み込み完了待機
    もし、テスト文字列=空ならば、テスト文字列=『「」-~…』
    テスト文字列でフォントのウェブフォント読み込み完了判定。
    (読み込み完了ウェブフォント[フォント]≠はい)の間
        もし、読み込み完了ウェブフォント[フォント]=いいえならば、
            続ける。
        違えば、もし、読み込み完了ウェブフォント[フォント]=「エラー」ならば、
            「{フォント}が読み込み出来ません」と言う。抜ける。
        ここまで。
        0.1秒待つ。
    ここまで。
    読み込み完了ウェブフォント[フォント]で戻る。
ここまで

●(フォントの|フォントをテスト文字列で)ウェブフォント読み込み完了判定
    読み込み完了ウェブフォント[フォント]=いいえ。
    キャンバスとは変数=描画中キャンバス。
    Aとは変数=[0,0]のキャンバス作成。
    「normal 36px {フォント}, serif」に描画フォント設定。
    Bとは変数=[0,0]のキャンバス作成。
    「normal 36px serif」に描画フォント設定。
    100回
        Aへ描画開始。
        AWとは変数=テスト文字列の文字幅取得。
        AHとは変数=テスト文字列の文字高さ取得。
        Bへ描画開始。
        BWとは変数=テスト文字列の文字幅取得。
        BHとは変数=テスト文字列の文字高さ取得。
        もし、(AW≠BW)または(AH≠BH)ならば、
            キャンバスへ描画開始。
            読み込み完了ウェブフォント[フォント]=はい。
            DOM親要素からAをDOM子要素削除。
            DOM親要素からBをDOM子要素削除。
            はいで戻る。
        ここまで。
        0.1秒待つ。
    ここまで。
    読み込み完了ウェブフォント[フォント]=「エラー」
    DOM親要素からAをDOM子要素削除。
    DOM親要素からBをDOM子要素削除。
    いいえで戻る。
ここまで。

 いちおうはうまくいっていると思います☆
 テスト文字列は、約物だけとか変体仮名だけとかいった、特殊文字のみのサブセットフォントに対応できるよう、変えれるようにしました。
 
 APIは……そのうちがんばるー💧

動作確認

 できました!

問題点

●フォント置き場が必要

 なでしこさんの貯蔵庫にアップロードできるファイルは画像、音声、テキストとなっていて、フォントファイルは出来ないので(愚かな民が巨大なttfとかあげようとしてきたりされたら困るしね!)自前の置き場所が必要です。
 とりあえずのお試しだから、github-pageに置いてるけどね。

●どのフォントを使うか問題

・約物と本文のスタイルが合わなくなる問題

 動作確認のサンプルでは本文も毛筆フォントをウェブフォントにして使用しており、その同じフォントから約物のサブセットを作成しているのでいい感じに表示されていますが、通常日本語の文章全体にウェブフォントを適用するコトってあんまり無いですよね~。
 日本語のフォント、サイズでかいですからねー><
 でも、同じ明朝体って言っても線の細さや色の濃さやサイズ感がぜんぜん違いますよね~。
 環境によって、括弧くらいは許すとしても、長音記号や小書きのかながめちゃくちゃ浮いて見える! とゆう事件が発生します。

・ライセンスの問題

 あと、フォントも著作権があるので、標準的にインストールされてるようなフォントをサブセットとはいえウェブフォントにすることは出来ないです。
 フリーのフォントでも、改変して再配布することが許可されていないものは、できません。
 そして、どうやらIPA関係の派生フォントはダメみたいなのねん:cry:
 改変して再配布も出来る規約なんですが、それを行うために満たさなければならない条件が色々むじゅかしいんだよねー。ウェブフォントのような形では、その再配布の条件を満たせないとゆうことラシイ。
 下記のサイトでは、実際に問い合わせてみたらダメって言われたって書いてる。

・縦書き用のグリフが入ってないフォントもある

 花園明朝、入ってないっぽい。しくしくしく:cry:

・ウェブフォント読み込み完了判定の問題

 Noto Serif JPSIL OPEN FONT LICENSE 1.1なので、OKそうですよ。
 Googleフォントで簡単に使えるので、それを本文に適用して縦書き用のサブセットを作ればよさそげ? と思ったんですが・・・
 えっ、入ってる漢字こんな少ない?! というような結果になりました。
 でも、実際にはちゃんとはいってたんですよねー。
 フォントの読み込みって、全部読み込まれてからじゃなくて、読み込まれた順に随時適用されていくみたい(?) そりゃ、そのほうが助かるよね~。
 でも、それ故に、上で作成した完了判定では、テスト文字列に指定した文字が読み込まれた時点で完了の判定となって描画を開始してしまうため、多くの漢字がまだ読み込まれてないうちに描画されてしまうようなんです。ダメダメ:sweat:

 でもですよ、サンプルで使った毛筆フォントじゃ、同じ文章でお試ししても、そんなこと起こらないですよよよよよ。
 Googleフォントの読み込み遅くない?
 単純にフォントの収録文字数って言うかファイルサイズの問題なのかな??
 それとも、font-faceの設定とimportの差でしょうか???

 API……そのうちがんばるるる……:sob:

とりあえずの結論

 キャンバスへの縦書き描画、ツラい!!! 以上(違)

 とりあえず、縦書き用約物のサブセットには、小書きの仮名だけでは明らかにアレなので、平仮名片仮名は全て含めることにする。
 Noto Serif JPのregularでお試し。

!『https://n3s.nadesi.com/plain/drawPlus.nako3』を取り込む。

#テスト用テキスト
文章=『「本日は、お日柄も宜しく――」
「兎ぴょんぴょん、猫ニャンニャン」』

#ウェブフォント設定
DOM親要素に「<style>
    @font-face {波カッコ}
        font-family: "縦書き用約物";
        src: url("https://snowdrops89.github.io/font/tategaki/NotoSerifR_tategakiWSF.woff")  format('woff');
    {波カッコ閉じ}
</style>」をHTML設定。
「縦書き用約物」のウェブフォント読み込み完了待機。

#フォント設定
フォント=「20px "縦書き用約物","游明朝",serif」
フォントに描画フォント設定。

#描画
[200,10]へ文章を縦書き文章描画。

 こんなもんですかね:sweat:
2022-12-07.png
 まあ、游明朝ならなんとか(?)
 MS 明朝とか、そもそも明朝体が無いとかでなければ(?)

 縦書き描画ウェブフォント読み込み完了判定(仮)の関数は、キャンバスへの描画を強化するNAKO3プラグインの中に突っ込みました。

v3でSVGに描画する

 まあ、そんなこんなでキャンバスへの縦書き描画がなかなか大変なので、SVG良くない? ってなって昨日の記事のような次第となりました。(えっ、まさかのこれから本題?!)

 では早速やってみましょう。

!『https://n3s.nadesi.com/plain/SVG.nako3』を取り込む。
#テスト用テキスト
文章=『「こんにちはー。」
「こんにちは……」
「こんにちは――」』

#SVG作成
縦書きテスト=[100,200]のSVG親要素作成。

#フォント設定
フォント=「serif」。文字サイズ=20。
フォントにSVG文字フォント設定。
「{文字サイズ}px」にSVG文字サイズ設定。

#描画
x=75。y=10。
文章=文章を改行で区切る。
文章を反復
    [x,10]へ対象をSVG縦書き文字描画。
    x=x-文字サイズ。
ここまで。

こんにちはsvg.png
 できました!
 カンタンです♪

webフォント使う

 SVGならこのように、別途縦書き用のサブセットを用意しなくても(ていうか縦書き用のフォントを設定するとむしろ逆に横向きます!)ふつーに縦書きが出来るんだけど、それとは別にウェブフォント使いたいことありますよね~。
 svgの中に定義領域(defs)を作って、そこにHTML設定すればOKでした。(defs以外のとこでも出来ることは出来るみたいですが、いちおう念のため)
 なんにしろ、svgの中にフォント設定のcssを埋め込むことでsvgファイル単体として開いた時にもちゃんとウェブフォントが適用されます(ネットにつながっていればですが)
 ネット環境でなくても適用されるようにするには、base64にして埋め込むという方法もあるみたいですが、これはアイコンとかタイトルとか数文字のサブセット的な用途ですよね? まるっと日本語フォントなんて無茶よね~:scream:

 とゆうわけで、こんな感じ。

#ウェブフォント指定
ウェブフォント指定=縦書きテストにSVG定義領域作成。
ウェブフォント指定に「<style>
    /*Googlフォント*/
    @import url('https://fonts.googleapis.com/css2?family=Noto+Serif+JP&display=swap');
</style>」をHTML設定。

#フォント
フォント=「"Noto Serif JP",serif」。文字サイズ=20。
フォントにSVG文字フォント設定。

 よさそげです!

こんにちは.svg
<svg id="nako3-svg-0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 200" width="100" height="200">
  <defs id="nako3-svg-1">
    <style>
      /*Googlフォント*/
      @import url('https://fonts.googleapis.com/css2?family=Noto+Serif+JP&amp;display=swap');
    </style>
  </defs>
  <text id="nako3-svg-2" x="75" y="10" writing-mode="vertical-rl" text-orientation="mixed" font-family="&quot;Noto Serif JP&quot;,serif" font-size="20px">「こんにちはー。」</text>
  <text id="nako3-svg-3" x="55" y="10" writing-mode="vertical-rl" text-orientation="mixed" font-family="&quot;Noto Serif JP&quot;,serif" font-size="20px">「こんにちは……」</text>
  <text id="nako3-svg-4" x="35" y="10" writing-mode="vertical-rl" text-orientation="mixed" font-family="&quot;Noto Serif JP&quot;,serif" font-size="20px">「こんにちは――」</text>
</svg>

 これを、UTF-8で拡張子svgのファイルに保存して開けば、svg単体でもちゃんとウェブフォントも適用されて表示されることが分かります。
 やったね☆

複数行をまとめて描画

 したいよねー、と思ったら、これがちょっとだけめんどかったです。
 文字幅の取得がうまくいかないんですよねー。文字サイズをpxで設定していたら、その値でもいいんですが、mediumとかもあるので!
 キャンバスへの描画での、TextMetricsのようなのが取得したいのですが、getBBoxgetBoundingClientRectも、欲しい値とちょっと違うのねん。
 もうね、得意の無理矢理系ですよね~。仮のキャンバス作ってTextMetrics取得するとか!💧
 なんかもっと良い手立てがあれば、教えてください:bow:

//whは[行間,文字間]
●(xyにwhでsを|xyへ)SVG縦書き文章描画
    x,y=xy。s=sを改行で区切る。
    もし、wh=空ならば、wh=[0,0]。
    行間,文字間=wh。
    もし、行間=未定義ならば、行間=0。
    もし、文字間=未定義ならば、文字間=0。
    w=SVG文字幅取得+行間。
    sを反復
        [x,y]に対象を文字間でSVG縦書き文字描画。
        x=x-w。
    ここまで。
ここまで。

##文字の幅と高さ取得
//「getBBox」も「getBoundingClientRect」も欲しい値と違うのでとりあえず無理矢理系;
●SVG文字幅取得
    文字太さ=SVG描画設定情報["文字"]["font-weight"]。
    文字サイズ=SVG描画設定情報["文字"]["font-size"]。
    フォント=SVG描画設定情報["文字"]["font-family"]。
    もし、フォント=空ならば、フォント=「sans-serif」。
    仮=[0,0]のキャンバス作成。仮へ描画開始。
    「{文字太さ} {文字サイズ} {フォント}」に描画フォント設定。
    TM=「あ」の文字描画幅取得。
    DOM親要素から仮をDOM子要素削除。# 消して終了。
    TM["width"]で戻る。
ここまで。

動作確認

 できました! やったね!!

おわりますがつづきます(よてい)

 と、ゆうわけで、ちょっとキャンバスに縦書きしたいと思ったら、予想以上にいろいろ大変だったことをぼやく記事でした(え💧)
 今回は、ぼやきにぼやいて長くなったので、SVGなら縦書き描画カンタンにできた~♪ というところで終わりますが、SVG画像をキャンバスに描画することが出来ることを知ったので、キャンバスへの縦書き描画ももうSVGで描画した物を貼っ付けたらいいんじゃないの? と思ったり思わなかったりする今日この頃です。
 最低もう一ネタSVGの話を書く予定なので、それはその時にでも。
 

3
1
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
3
1