MS ゴシックの文字幅
こんな場末の日記をわざわざ見に来る方は UAX #11: East Asian Width なんかは当たり前に読み込んでいると思うんですが、読み込んだ人はきっと気づくと思うんです、このドキュメントはあてにならないことに。じゃあどうすればいいかってなるんですが、端的には融通の効かない奴に合わせるわけですな。それはアプリ側のこともフォント側のこともあるんですが、今回はフォント側の事を考えることにします。フォント側のことを考えるってことは、要するにフォントは不動と考えるってことで、そういうフォントって要するにMS ゴシックのことなので、これを測ることにするわけです。
さて、どうやって測るかというと、実際に描画して測るわけです。TextRenderer.MeasureTextとかでも行けそうに思えるんだけど、グリフがない場合に fallback しちゃうんでうまくいかない。
using System; using System.Drawing; using System.Windows.Forms; using System.Windows; class Program { [STAThread] static void Main(string[] args) { Application.Run(new FormA()); } } class FormA : Form { protected override void OnPaint(PaintEventArgs e) { for (int c = 0; c <= 0x10FFFF; c++) { if (0xD800 <= c && c <= 0xDFFF) continue; measureChar(e, c); } //measureChar(e, 0x20bb7); //measureChar(e, 0x20b9f); //measureChar(e, 0x216b4); Application.Exit(); } private void measureChar(PaintEventArgs e, int c) { Font stringFont = new Font("VL Gothic", 16.0F); RectangleF layoutRect = new RectangleF(0F, 0F, 300F, 300F); Pen pen = new Pen(Color.Red, 1); StringFormat stringFormat = new StringFormat(); stringFormat.FormatFlags = StringFormatFlags.NoFontFallback; string str = Char.ConvertFromUtf32(c); CharacterRange[] ranges = {new CharacterRange(0, str.Length)}; stringFormat.SetMeasurableCharacterRanges(ranges); Region[] stringRegions = e.Graphics.MeasureCharacterRanges(str, stringFont, layoutRect, stringFormat); RectangleF measureRect1 = stringRegions[0].GetBounds(e.Graphics); Console.WriteLine("{0,0:X6}: {1}", c, measureRect1.Width); //e.Graphics.DrawString(str, stringFont, Brushes.Black, 0, 0, stringFormat); //e.Graphics.DrawRectangle(pen, Rectangle.Round(measureRect1)); } }
で、これで測れたわけですがこのままだとまともに扱えないので、以下のようなスクリプトを使って区間にします。つまり、連続してる奴はまとめるわけです。
class CC def initialize(encoding=nil) @encoding = encoding @cc = [] end def <<(v) case v when Fixnum _bsearch_add2(v, v) when Range _bsearch_add2(v.first, v.last) when CC v.to_a.each_slice(2) do |from, to| _bsearch_add2(from, to) end else raise TypeError, "invalid value to add a charclass" end self end def to_s return to_char(@cc.first) if @encoding && @cc.size == 2 && @cc.first == @cc.last buf = '[' n = @encoding ? nil : 0 @cc.each_slice(2) do |from, to| buf << ',' if n && 1 < n += 1 buf << to_char(from) if @encoding.nil? || from != to buf << '-' if to - from > 1 buf << to_char(to) end end buf << ']' end def inspect to_s.inspect[1..-1] end def empty? @cc.empty? end private def to_char(v) #Regexp.quote(@encoding ? v.chr(@encoding) : v.to_s) @encoding ? '\u%04x' % v : v.to_s end def _bsearch_add2(from, to) pos1 = _bsearch(from) pos2 = from == to ? pos1 : _bsearch(to) if pos1.odd? pos1 -= 1 from = @cc[pos1] elsif pos1 > 0 && @cc[pos1-1] == from - 1 pos1 -= 2 from = @cc[pos1] end if pos2.odd? to = @cc[pos2] elsif to + 1 == @cc[pos2] || to == @cc[pos2] pos2 += 1 to = @cc[pos2] else pos2 -= 1 end @cc[pos1, pos2-pos1+1] = from, to end end cc = CC.new(Encoding::UTF_8) IO.read('msgothic.txt').scan(/(\w+): (\d+)/) do |x, y| a << x.to_i(16) if y.to_i == 22 end p cc
で、出来た全角文字にマッチする正規表現は以下の通りになります。うーん、なんか違う気がするのが混じってますね。やっぱり OpenType 直接読みに行かないとダメかなぁ。
/[\u0001-\u0008\u000b\u000c\u000e-\u001b\u007f-\u0083\u0086-\u009f\u00a7\u00a8 \u00b0\u00b1\u00b4\u00b6\u00d7\u00f7\u0180-\u0191\u0194-\u01c1\u01c3-\u01f7 \u0200-\u024f\u02a9-\u02c5\u02ca\u02cb\u02cd-\u02cf\u02d2-\u02d7\u02df-\u02e4 \u02ea-\u02ff\u0305\u0307\u0309\u030a\u030d\u030e\u0310-\u0317\u031b \u0321-\u0323\u0326-\u0328\u032b\u032d\u032e\u0331-\u0333\u0335-\u0338 \u033e-\u0360\u0362-\u0373\u0376-\u0379\u037b-\u037d\u037f-\u0383\u038b\u038d \u0391-\u03a9\u03b1-\u03c1\u03c3-\u03c9\u03cf-\u0401\u040d\u0410-\u0451\u045d \u0487-\u048f\u04c5\u04c6\u04c9\u04ca\u04cd-\u04cf\u04ec\u04ed\u04f6\u04f7 \u04fa-\u070e\u0710-\u09f1\u09f4-\u0e3e\u0e40-\u17da\u17dc-\u180a\u1810-\u1e3d \u1e40-\u1e7f\u1e86-\u1ef1\u1ef4-\u1f6f\u1f74-\u1fff\u2001\u2010\u2011\u2015\u2016 \u2018\u2019\u201c\u201d\u2020\u2021\u2025\u2026\u202f\u2030\u2032\u2033\u203b \u204a-\u2069\u2071-\u2073\u208f-\u209f\u20b2-\u20ff\u2103\u2116\u2121\u212b \u2139-\u2152\u2160-\u2193\u21d2\u21d4\u21eb-\u2200\u2202\u2203\u2207\u2208 \u220b\u2211\u221a\u221d-\u2220\u2225\u2227-\u222c\u222e\u2234\u2235\u223d \u2252\u2260\u2261\u2266\u2267\u226a\u226b\u2282\u2283\u2286\u2287\u22a5\u22bf \u22f2-\u2301\u2303\u2304\u2307-\u230f\u2311-\u231f\u2322-\u2503\u250c\u250f \u2510\u2513\u2514\u2517\u2518\u251b-\u251d\u2520\u2523-\u2525\u2528\u252b\u252c \u252f\u2530\u2533\u2534\u2537\u2538\u253b\u253c\u253f\u2542\u254b\u2596-\u25a1 \u25b2\u25b3\u25bc\u25bd\u25c6\u25c7\u25cb\u25ce\u25cf\u25ef-\u2638\u263d-\u265f \u2668\u266a\u266d\u266f-\u2933\u2936-\u2984\u2987-\u29f9\u29fc-\u2fff\u3001-\ud7ff \ue000-\ufb00\ufb03-\ufefe\uff00-\uff60\uffa0-\uffe7\uffef-\ufffe]/x