Почему Graphics.MeasureString () возвращает больше ожидаемого числа? - PullRequest
44 голосов
/ 30 июля 2009

Я генерирую квитанцию ​​и использую объект 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 ]

145 голосов
/ 20 июня 2011

Из 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) Сортировка всего этого , в .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, например ::1010*.

  • 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:

enter image description here

GDI + : TextRenderingHintAntiAlias, GDI : ANTIALIASED_QUALITY:

enter image description here

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

enter image description here

GDI + : TextRenderingHintSingleBitPerPixelGridFit, GDI : PROOF_QUALITY:

enter image description here

GDI + : TextRenderingHintSingleBitPerPixel, GDI : DRAFT_QUALITY:

enter image description here

Мне кажется странным, что DRAFT_QUALITY идентичен PROOF_QUALITY, что идентично CLEARTYPE_QUALITY.

См. Также

5 голосов
/ 08 мая 2014

Courier New Size 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

ВАЖНО: Этот код правильно измеряет обычный шрифт. Если вам нужны точные значения также для курсивных шрифтов (которые всегда имеют вылет справа), вы должны прочитать ссылки, упомянутые в этой статье:

ПРИЛОЖЕНИЕ: Для тех, кто никогда не использовал вызовы 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);
}
0 голосов
/ 12 июня 2018

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

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

Снимок экрана

...