Почему 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
,
Смотрите также
- UseCompatibleTextRendering - совместимо с whaaaaaat?
- Разбираемся в этом: быстрый взгляд на TextRenderer Уидби
- MSDN: структура LOGFONT
- AppCompat Guy: GDI против GDI+ производительность рендеринга текста
- GDI+ текст, независимость разрешения и методы рендеринга. Или - Почему мой текст выглядит по-разному в GDI+ и в GDI?
Когда вы создадите шрифт '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);
}
Вот объяснение, которое может помочь вам понять, как это работает. и что вызывает пробелы более или менее до и после каждого символа.