В GDI + при отрисовке текста разных размеров на базовой линии возникают проблемы размером не более 1 пикселя - PullRequest
10 голосов
/ 01 февраля 2012

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

enter image description here

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

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

Проблема, с которой я сталкиваюсь, заключается в том, что с большинством шрифтов я вижу проблемы размером 1 пиксель (по отношению к серой линии 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

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

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 каким-то образом удается исправить это без округления размеров шрифта до удобных значений.

Ответы [ 3 ]

5 голосов
/ 01 февраля 2012

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

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

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 голосов
/ 01 февраля 2012

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

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

1 голос
/ 06 января 2016

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

...