Почему Graphics.MeasureString() возвращает больше ожидаемого числа?

Я генерирую квитанцию ​​и использую объект Graphics для вызова метода DrawString для вывода необходимого текста.

graphics.DrawString(string, font, brush, widthOfPage / 2F, yPoint, stringformat);

Это прекрасно работает для того, что мне нужно было сделать. Я всегда знал, что печатал, поэтому я мог вручную обрезать любые нити, чтобы они правильно помещались на чековой бумаге 80 мм. Затем мне пришлось добавить дополнительный функционал, который бы сделал это более гибким. Пользователь может передать строки, которые будут добавлены в конец.

Поскольку я не знал, что они собирались поместить, я просто создал свою собственную функцию переноса слов, которая принимает ряд символов для переноса и саму строку. Чтобы узнать количество символов, я делал что-то вроде этого:

float width = document.DefaultPageSettings.PrintableArea.Width;
int max = (int)(width / graphics.MeasureString("a", font).Width);

Теперь ширина возвращает мне 283, что в мм составляет около 72, что имеет смысл, если учитывать поля на 80-миллиметровой бумаге.

Но метод MeasureString возвращает 10,5 для шрифта Courier New 8pt. Таким образом, вместо того, чтобы обойти то, что я ожидал получить 36 - 40, я получаю 26, в результате чего 2 строки текста превращаются в 3-4.

Единицы для PrintableArea.Width составляют 1/100 дюйма, а PageUnit для графического объекта - "Дисплей" (который обычно равен 1/100 дюйма для принтеров). Так почему же я получаю только 26 назад?

3 ответа

Решение

Из WindowsClient.net:

GDI+ добавляет небольшое количество (1/6 em) к каждому концу каждой отображаемой строки. Эта 1/6 em учитывает глифы с нависающими концами (например, курсив ' f '), а также дает GDI+ небольшое количество свободы, чтобы помочь с расширением подгонки к сетке.

Действие по умолчанию DrawString будет работать против вас в отображении соседних трасс:

  • Сначала StringFormat по умолчанию добавляет дополнительные 1/6 em на каждом конце каждого вывода;
  • Во-вторых, когда ширина сетки меньше запланированной, струне разрешается сокращаться до em.

Чтобы избежать этих проблем:

  • Всегда проходить MeasureString а также DrawString StringFormat на основе типографского StringFormat (GenericTypographic).
    Установить графику TextRenderingHint в TextRenderingHintAntiAlias, Этот метод рендеринга использует сглаживание и расположение субпиксельных глифов, чтобы избежать необходимости подгонки к сетке, и, таким образом, по своей природе не зависит от разрешения.

Есть два способа рисования текста в.NET:

  • GDI+ (graphics.MeasureString а также graphics.DrawString)
  • GDI (TextRenderer.MeasureText а также TextRenderer.DrawText)

Из отличного блога Майкла Каплана (Rip) Sorting It All Out, в.NET 1.1 все использовали GDI+ для рендеринга текста. Но были некоторые проблемы:

  • Существуют некоторые проблемы с производительностью, вызванные в некоторой степени отсутствием состояния GDI+, когда контексты устройства устанавливаются, а затем исходный восстанавливается после каждого вызова.
  • Механизмы формирования международного текста неоднократно обновлялись для Windows/Uniscribe и для Avalon (Windows Presentation Foundation), но не обновлялись для GDI+, что приводит к тому, что поддержка международного рендеринга для новых языков не имеет того же уровня качества.

Таким образом, они знали, что хотят изменить.NET Framework, чтобы прекратить использовать систему рендеринга текста GDI+ и использовать GDI. Сначала они надеялись, что могут просто измениться:

graphics.DrawString

называть старым DrawText API вместо GDI+. Но они не могли сделать так, чтобы перенос текста и интервал соответствовали так, как это делал GDI+. Поэтому они были вынуждены сохранить graphics.DrawString позвонить GDI+ (причины совместимости; люди, которые звонили graphics.DrawString вдруг обнаружит, что их текст не обернут так, как раньше).

Новая статика TextRenderer класс был создан для переноса рендеринга текста GDI. У него есть два метода:

TextRenderer.MeasureText
TextRenderer.DrawText

Замечания: TextRenderer является оберткой вокруг GDI, в то время как graphics.DrawString все еще является оберткой вокруг GDI+.


Затем возникла проблема, что делать со всеми существующими элементами управления.NET, например:

  • Label
  • Button
  • TextBox

Они хотели переключить их на использование TextRenderer (т.е. GDI), но они должны были быть осторожными. Могут быть люди, которые зависели от того, как их элементы управления рисовали, как в.NET 1.1. И так родился " совместимый рендеринг текста ".

По умолчанию элементы управления в приложении ведут себя так же, как и в.NET 1.1 (они " совместимы ").

Вы выключаете режим совместимости, позвонив:

Application.SetCompatibleTextRenderingDefault(false);

Это делает ваше приложение лучше, быстрее, с лучшей международной поддержкой. Подводить итоги:

SetCompatibleTextRenderingDefault(true)  SetCompatibleTextRenderingDefault(false)
=======================================  ========================================
 default                                  opt-in
 bad                                      good
 the one we don't want to use             the one we want to use
 uses GDI+ for text rendering             uses GDI for text rendering
 graphics.MeasureString                   TextRenderer.MeasureText
 graphics.DrawString                      TextRenderer.DrawText
 Behaves same as 1.1                      Behaves *similar* to 1.1
                                          Looks better
                                          Localizes better
                                          Faster

Также полезно отметить соответствие между GDI+ TextRenderingHint и соответствующий LOGFONT Качество, используемое для рисования шрифтов GDI:

TextRenderingHint           mapped by TextRenderer to LOGFONT quality
========================    =========================================================
ClearTypeGridFit            CLEARTYPE_QUALITY (5) (Windows XP: CLEARTYPE_NATURAL (6))
AntiAliasGridFit            ANTIALIASED_QUALITY (4)
AntiAlias                   ANTIALIASED_QUALITY (4)
SingleBitPerPixelGridFit    PROOF_QUALITY (2)
SingleBitPerPixel           DRAFT_QUALITY (1)
else (e.g.SystemDefault)    DEFAULT_QUALITY (0)

образцы

Вот некоторые сравнения рендеринга текста GDI+ (graphics.DrawString) и GDI (TextRenderer.DrawText):

GDI+: TextRenderingHintClearTypeGridFit, GDI: CLEARTYPE_QUALITY:

GDI+: TextRenderingHintAntiAlias, GDI: ANTIALIASED_QUALITY:

GDI+: TextRenderingHintAntiAliasGridFit, GDI: не поддерживается, использует ANTIALIASED_QUALITY:

GDI+: TextRenderingHintSingleBitPerPixelGridFit, GDI: PROOF_QUALITY:

GDI+: TextRenderingHintSingleBitPerPixel, GDI: DRAFT_QUALITY:

я нахожу странным, что DRAFT_QUALITY идентично PROOF_QUALITY, который идентичен CLEARTYPE_QUALITY,

Смотрите также

Курьер Новый Размер 11

Когда вы создадите шрифт 'Courier New' с Size = 11, вы получите вывод, как на картинке выше. Вы видите, что высота составляет 14 пикселей, не включая подчеркивание. Ширина составляет ровно 14 пикселей (7 пикселей для каждого символа).

Таким образом, этот шрифт отображает 14x14 пикселей.

Но TextRenderer.MeasureText() вместо этого возвращает ширину 21 пикселя. Если вам нужны точные значения, это бесполезно.

Решением является следующий код:

Font i_Courier = new Font("Courier New", 11, GraphicsUnit.Pixel);

Win32.SIZE k_Size;
using (Bitmap i_Bmp = new Bitmap(200, 200, PixelFormat.Format24bppRgb))
{
    using (Graphics i_Graph = Graphics.FromImage(i_Bmp))
    {
        IntPtr h_DC = i_Graph.GetHdc();
        IntPtr h_OldFont = Win32.SelectObject(h_DC, i_Courier.ToHfont());

        Win32.GetTextExtentPoint32(h_DC, "Áp", 2, out k_Size);

        Win32.SelectObject(h_DC, h_OldFont);
        i_Graph.ReleaseHdc();
    }
}

k_Size будет содержать правильный размер: 14x14

ВАЖНО: этот код правильно измеряет обычный шрифт. Если вам нужны точные значения также для курсивных шрифтов (которые всегда имеют вылет справа), вы должны прочитать ссылки, упомянутые в этой статье: http://www.codeproject.com/Articles/14915/Width-of-text-in-italic-font

ПРИЛОЖЕНИЕ: Для тех, кто никогда не использовал вызовы API в C#, здесь есть подсказка, как создать класс Win32. Это не завершено. Для более подробной информации посмотрите на http://www.pinvoke.net/

using System.Runtime.InteropServices;

public class Win32
{       
    [StructLayout(LayoutKind.Sequential)]
    public struct SIZE
    {
        public int cx;
        public int cy;
    }

    [DllImport("Gdi32.dll")]
    public static extern bool GetTextExtentPoint32(IntPtr hdc, string lpString, int cbString, out SIZE lpSize);

    [DllImport("Gdi32.dll")]
    public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
}

Вот объяснение, которое может помочь вам понять, как это работает. и что вызывает пробелы более или менее до и после каждого символа.

Приложение GDI DrawString Configurator

Скриншот

Другие вопросы по тегам