GDI+, рисующий текст разного размера на базовой линии, имеет проблемы off-by-1px

Мне нужно напечатать числа, в которых некоторые цифры в середине подчеркнуты путем увеличения размера и веса шрифта. В примере ниже подчеркнуто 456.

enter image description here

Шрифт и два используемых размера настраиваются пользователем.

Текущий код делает это с помощью трех вызовов Graphics.DrawString(...).

Проблема, с которой я сталкиваюсь, заключается в том, что с большинством шрифтов я вижу проблемы, связанные с смещением на 1 пиксель (относительно серой линии, 456 находится на лишний пиксель выше, чем остальные цифры):

enter image description here

Я приложил отладочный дамп (формула Боба Пауэлла) для различных шрифтов внизу моего сообщения. Другие методы дали схожие результаты.

Для того чтобы напечатать текст на общей базовой линии, необходимо рассчитать смещение базовой линии для конкретного шрифта. Я попробовал использовать три метода:

Во-первых, код MSDN: http://msdn.microsoft.com/en-us/library/xwf9s90b(v=vs.80).aspx

ascent = fontFamily.GetCellAscent(FontStyle.Regular);
ascentPixel = font.Size * ascent / fontFamily.GetEmHeight(FontStyle.Regular)

Во-вторых, код из: Using GDI+, what's the easiest approach to align text (drawn in several different fonts) along a common baseline?

Наконец, код из поста Боба Пауэлла: http://www.bobpowell.net/formattingtext.htm

Вот мой метод рисования:

private void DrawOnBaseline(Graphics g, string text, FontWithBaseline fwb, Brush brush, float x, float y) {
  g.DrawString(text, fwb.Font, brush, x, y - fwb.Baseline, StringFormat.GenericTypographic);
}

Где FontWithBaseline просто связывает шрифт с соответствующим вычислением базовой линии:

public class FontWithBaseline {
  private Font m_font;
  private float m_baseline;

  public FontWithBaseline(Font font) {
    m_font = font;
    m_baseline = CalculateBaseline(font);
  }

  public Font Font { get { return m_font; } }
  public float Baseline { get { return m_baseline; } }

  private static float CalculateBaseline(Font font) {
    ... // I've tried the three formulae here.
  }
}

Я еще не экспериментировал с Graphics.TestRenderingHint. Это волшебный соус? Чего мне не хватает? Есть ли альтернативный API, который я могу использовать, где я вызываю вызов рисования, поставляющий базовую координату Y?

enter image description here


Update 1

Я интерполировал свой код с @LarsTech. Он делал одно тонкое отличие; он добавлял 0.5f. Однако даже этот вариант не устраняет проблему. Вот код:

protected override void OnPaint(PaintEventArgs e) {
  base.OnPaint(e);
  TryLarsTechnique(e);
}

private void TryLarsTechnique(PaintEventArgs e) {
  base.OnPaint(e);
  Graphics g = e.Graphics;
  GraphicsContainer c = g.BeginContainer();
  g.Clear(Color.White);
  g.SmoothingMode = SmoothingMode.AntiAlias;
  g.TextRenderingHint = TextRenderingHint.AntiAlias;
  Font small = new Font("Arial", 13, FontStyle.Regular, GraphicsUnit.Pixel);
  Font large = new Font("Arial", 17, FontStyle.Bold, GraphicsUnit.Pixel);

  int x = 100;
  int y = 100;
  x += DrawLars(g, "12.3", small, x, y);
  x += DrawLars(g, "456", large, x, y);
  x += DrawLars(g, "8", small, x, y);
  g.EndContainer(c);
}

// returns width of text
private int DrawLars(Graphics g, string text, Font font, int x, int y) {
  float offset = font.SizeInPoints /
                 font.FontFamily.GetEmHeight(font.Style) *
                 font.FontFamily.GetCellAscent(font.Style);
  float pixels = g.DpiY / 72f * offset;
  int numTop = y - (int)(pixels + 0.5f);      
  TextRenderer.DrawText(g, text, font, new Point(x, numTop), Color.Black, Color.Empty, TextFormatFlags.NoPadding);
  return TextRenderer.MeasureText(g, text, font, Size.Empty, TextFormatFlags.NoPadding).Width;
}

Я задаюсь вопросом, не является ли указание размера шрифта с помощью GraphicsUnit.Pixel виновником. Возможно, есть способ узнать предпочтительные размеры для любого конкретного шрифта?


Обновление 2

Я попробовал указать размеры шрифта в пунктах вместо пикселей, и это тоже не полностью решило проблему. Обратите внимание, что использование только целых размеров пунктов не является вариантом в моем случае. Чтобы проверить, возможно ли это, я попробовал это в Windows Wordpad. При размере 96 dpi (и 72 точки на дюйм по определению), 17px, 13px переводятся в 12.75 и 9.75. Вот сравнительный результат:

enter image description here

Обратите внимание, что меньшие шрифты имеют одинаковую высоту на уровне пикселей. Wordpad каким-то образом умудряется сделать это правильно, не округляя размеры шрифтов до удобных значений.

10
задан Community 23 May 2017 в 12:00
поделиться