C#
OCRBというフォント
OCRBというフォントは、機械での読み取りに適しているらしく、印刷でときどき使うことがある。でも、枠の中央に数字を描画しようとすると、印刷プレビューのときだけちょっとした調整が必要だ。いつも適当な感じで、「だいたいこのぐらいずらせばOKさ」みたいにしていたけど、暇を持て余したので、自分なりに調べてみた。
private void Document_PrintPage(object sender, PrintPageEventArgs e)
{
e.Graphics.PageUnit = GraphicsUnit.Point;
RectangleF _Rect = new RectangleF(72.0f, 72.0f, 96.0f, 144.0f);
e.Graphics.DrawRectangle(Pens.Black, _Rect.X, _Rect.Y, _Rect.Width, _Rect.Height);
using (StringFormat _Format = new StringFormat(StringFormat.GenericTypographic))
{
_Format.Alignment = StringAlignment.Center;
_Format.LineAlignment = StringAlignment.Center;
using (Font _Font = new Font("OCRB", 80.0f))
{
e.Graphics.DrawString("0", _Font, Brushes.Black, _Rect, _Format);
}
}
}
いろいろ調べてみたら、フォントにはAscent, Descent, InternalLeading, ExternalLeadingといったプロパティがあるらしい。C#ではFontFamilyにGetCellAscent()などのメソッドがあって、CellAscent, CellDescent, EmHeight, LineSpacingを取得でき、あとは計算でCellHeight, InternalLeading, ExternalLeadingを求めるようだ。これらの値はデザイン単位ということになっている。要するに各部の比率を表していると思う。
int CellAscent = _Font.FontFamily.GetCellAscent(FontStyle.Regular);
int CellDescent = _Font.FontFamily.GetCellDescent(FontStyle.Regular);
int EmHeight = _Font.FontFamily.GetEmHeight(FontStyle.Regular);
int LineSpacing = _Font.FontFamily.GetLineSpacing(FontStyle.Regular);
int CellHeight = CellAscent + CellDescent;
int InternalLeading = CellHeight - EmHeight;
int ExternalLeading = LineSpacing - CellHeight;
OCRBはどうなっているかというと、他のフォントと比べてExternalLeadingがやけに大きい。InternalLeadingがマイナスなのも気になるが、そのようなフォントは他にもあり、あまり問題ではないようだ。
試行錯誤して、CellDescent + ExternalLeadingの分だけ、枠を下に拡げればだいたい合うとわかった。上下中央寄せにしても、下寄せにしても、2行にしても、3行にしても、だいたいうまくいく。だいたいうまくいくんだけど、どうしてもちょっと合わない。よく見ると「0」はグリフがCellAscentに収まってなくて、わずかに下にずれているような感じだ。
ビットマップに描画して調べた。EmHeight = 2048に対して「0」の高さは1445、上の隙間は16だった。枠を上に16ずらし、高さを2920 - 1445 = 1475拡げれば、ぴったり収まる。印刷のときはEmHeightだけ考えるようで、上に16ずらし、高さを16 + CellDescent = 16 + 337 = 353拡げれば良い。
private void Document_PrintPage(object sender, PrintPageEventArgs e)
{
e.Graphics.PageUnit = GraphicsUnit.Point;
RectangleF _Rect = new RectangleF(72.0f, 72.0f, 96.0f, 144.0f);
e.Graphics.DrawRectangle(Pens.Black, _Rect.X, _Rect.Y, _Rect.Width, _Rect.Height);
using (StringFormat _Format = new StringFormat(StringFormat.GenericTypographic))
{
_Format.Alignment = StringAlignment.Center;
_Format.LineAlignment = StringAlignment.Center;
using (Font _Font = new Font("OCRB", 80.0f))
{
if (((PrintDocument)sender).PrintController.IsPreview)
{
_Rect.Y -= (_Font.SizeInPoints * 16.0f) / 2048.0f;
_Rect.Height += (_Font.SizeInPoints * 1475.0f) / 2048.0f;
}
else
{
_Rect.Y -= (_Font.SizeInPoints * 16.0f) / 2048.0f;
_Rect.Height += (_Font.SizeInPoints * 353.0f) / 2048.0f;
}
e.Graphics.DrawString("0", _Font, Brushes.Black, _Rect, _Format);
}
}
}
OCRBはこれで良いけど、それじゃ他のフォントはどうなっているんだろう。どうしてこんなことしなくても真ん中に出るんだろうな。自分のマシンにインストールされているフォントを見た限りだけど、多くのフォントはInternalLeadingやExternalLeadingをうまく使ってLineSpacingの中央辺りにグリフを配置しているみたいだ。それで“何となくうまくいってる”らしい。実際、中央に描画しても、少し上にずれていたり、少し下にずれていたりすることがある。みなみな個性豊かで、いろいろなバリエーションがあり、ぴたりと位置を合わせようとすれば、OCRBよりもっと苦労するだろう。君子危うきに近寄らず。
その他のメモ
- EmHeightがフォントのサイズにあたる。10ptを指定すれば、EmHeightが10ptになるようにフォントが作られる。
-
Graphics.MeasureString()が返す値は、正確なサイズよりも少し大きい。幅はフォントサイズの0.333007813倍大きく、高さはフォントサイズの0.125倍大きい。ただし、この余白は多分、あったほうが良いのだろうと思う。
float PaddingW = _Font.SizeInPoints * 0.333007813f;
float PaddingH = _Font.SizeInPoints * 0.125f;
- OCRBで円記号を使おうとするとバックスラッシュになってしまうけど、あれは '\\' じゃなくて '\xa5' を使えば大丈夫。
(2017/12/02 更新)
(2017/11/24 更新)
(2017/11/22 初稿)