GDI + рисунок текста с различными размерами на базовой линии имеет проблемы, связанные с 1px

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

enter image description here

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

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

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

enter image description here

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

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

Во-первых, код 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)

Во-вторых, код из: Используя GDI +, какой самый простой способ выровнять текст (в нескольких разных шрифтах) по общей базовой линии?

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

Здесь мой метод draw:

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


Обновление 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 точек на дюйм (и 72 точки на дюйм по определению), 17px, 13px перевести на 12,75 и 9,75. Здесь результат сравним:

enter image description here

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

Ответ 1

У вас недостаточно кода для воспроизведения проблемы, так что вот рабочий пример с использованием примера Боба Пауэлла, который вы дали.

Только демонстрационный код:

private void panel1_Paint(object sender, PaintEventArgs e) {
  e.Graphics.Clear(Color.White);
  e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
  e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;

  string[] numbers = new string[] { "1", "2", ".", "3", "4", "5", "6", "8" };

  int x = 10;
  int y = 30;

  foreach (string num in numbers) {
    Font testFont;
    if (num == "4" || num == "5" || num == "6")
      testFont = new Font("Arial", 16, FontStyle.Bold);
    else
      testFont = new Font("Arial", 11, FontStyle.Regular);

    float offset = testFont.SizeInPoints / 
                   testFont.FontFamily.GetEmHeight(testFont.Style) * 
                   testFont.FontFamily.GetCellAscent(testFont.Style);
    float pixels = e.Graphics.DpiY / 72f * offset;

    int numTop = y - (int)(pixels + 0.5f);

    TextRenderer.DrawText(e.Graphics, num, testFont, new Point(x, numTop), 
                          Color.Black, Color.Empty, TextFormatFlags.NoPadding);

    x += TextRenderer.MeasureText(e.Graphics, num, testFont, 
                                  Size.Empty, TextFormatFlags.NoPadding).Width;
  }

  e.Graphics.DrawLine(Pens.Red, new Point(5, y + 1), new Point(x + 5, y + 1));
}

Это дает:

enter image description here

Ответ 2

Возможно, проблема в том, что вы указываете размер шрифта в pixels, в то время как ваше смещение рассчитывается в points и преобразуется обратно в pixels. Это может привести к разным неточностям.

Попробуйте указать размер шрифта в points и посмотрите, работает ли он.

Ответ 3

Если для параметра Graphics.TextRenderingHint установлено использование сетки, фактические показатели масштабированного шрифта TrueType в пикселях определяются GDI + через поиск шрифта таблица VDMX, а не просто масштабированием и округлением его расчетных показателей. Это то, что я понял, перешагнув Graphics.DrawString при разборке.