Я разрабатываю программу для дизайна этикеток и столкнулся с очень странной проблемой, на которую не могу найти ответа нигде в Интернете.
Основное объяснение заключается в том, что когда для свойства layoutRectangle
DrawString()
установлено значение, немного превышающее размер длины текста, кернинг, похоже, просто отключается. Лучше всего это видно при сочетании символов «Те».
Исправьте кернинг с помощью «layoutRectangle» ровно настолько, чтобы он соответствовал тексту:
Неправильный кернинг: «layoutRectangle» больше, чем требуется для размещения текста:
Вы, вероятно, заметите, что буква «е» на втором изображении нарисована намного дальше, чем на первом изображении. Я попробовал это в новом проекте, происходит то же самое. Вы можете наблюдать такое же поведение, даже если вообще не указываете layoutRectangle
.
Вот строка кода, рисующая строку:
canvasGraphics.Graphics.DrawString(
_displayText.FormattedTextValue,
new Font(FontName, (float)((drawFontSizeToUse / 72d) * Canvas.RenderDPI), fontStyle, GraphicsUnit.Pixel),
Brushes.Black,
(Rectangle)new RectangleD(ScreenBounds.X, ScreenBounds.Y, ScreenBounds.Width, ScreenBounds.Height),
stringFormat
);
stringFormat
просто меняет выравнивание в зависимости от настройки.
Кто-нибудь знает, как решить эту проблему? Спасибо!
Я пробовал манипулировать разными StringFormat
, TextRenderingHint
, любыми Font
параметрами и т. д. (хотя могут быть такие, о которых я не знаю.)
Я не могу использовать TextRenderer, поскольку его нельзя использовать для печати.
@HansPassant Я не могу. TextRenderer нельзя использовать для печати, а мне нужна эта функциональность.
Я знаю, что «Те» выглядит неправильно, но в приведенном выше примере справа от «Т» показана буква «е». Я уверен, что если вы замените «Т» на «L», вы будете счастливы.
@WalterVerhoeven Я не могу, никогда не использую букву «Т».
Вам просто не хватает аргумента метода, который сообщает вам, что вывод отправляется на принтер. Если вы не хотите его добавлять, используйте Graphics.DpiY. Если оно меньше 300, используйте TextRenderer. Если вам нужно точное соответствие экрана и бумаги, вместо этого вам придется использовать WPF.
@HansPassant ну, сейчас для меня очень радикально использовать WPF. Этот проект находится в разработке некоторое время. Действительно ли это единственное решение? Насколько я знаю, даже Microsoft говорит, что TextRenderer нельзя использовать для печати, и да, его разрешение составляет 300 точек на дюйм и выше, также TextRenderer требует пространства имен Windows.Forms, и для моей основной библиотеки я старался не добавлять его, поскольку он должен быть автономным. библиотека, которая может работать с любым графическим компонентом, даже если это не Forms. И да, мне нужно точное соответствие между бумагой и тем, что на экране.
@HansPassant, вы можете использовать букву T, только если это заглавная буква T, затем просто переместите влево на 15% ширины букв, и вы получите то, что хотели бы видеть, вы сами контролируете, где вы печатаете буквы.
Не используйте Graphics.DrawString; он медленнее, устарел и больше не рекомендуется.
Используйте TextRenderer.DrawText.
В .NET существует два способа рисования текста:
graphics.MeasureString
и graphics.DrawString
)TextRenderer.MeasureText
и TextRenderer.DrawText
)Из превосходного блога Майкла Каплана (rip) Sorting It All Out, В .NET 1.1 все использовало GDI+ для рендеринга текста. Но были некоторые проблемы:
- Существуют некоторые проблемы с производительностью, вызванные несколько не сохраняющим состояние характером GDI+, когда контексты устройств устанавливаются, а затем восстанавливаются исходные значения после каждого вызова.
- Механизмы формирования международного текста неоднократно обновлялись для Windows/Uniscribe и Avalon (Windows Presentation Foundation), но не обновлялись для GDI+, что приводит к тому, что поддержка международного рендеринга для новых языков не имеет того же уровня качества.
Итак, они знали, что хотят изменить платформу .NET, чтобы прекратить использование системы рендеринга текста GDI+ и использовать GDI. Сначала они надеялись, что смогут просто измениться:
graphics.DrawString
для вызова старого DrawText
API вместо GDI+. Но они не смогли добиться точного совпадения переноса текста и интервалов так, как это сделал GDI+. Поэтому они были вынуждены продолжать graphics.DrawString
вызывать GDI+ (из соображений совместимости; люди, которые звонили graphics.DrawString
, внезапно обнаруживали, что их текст не переносился так, как раньше).
Был создан новый статический класс TextRenderer
для переноса рендеринга текста GDI. Он имеет два метода:
TextRenderer.MeasureText
TextRenderer.DrawText
Примечание.
TextRenderer
— это оболочка GDI, аgraphics.DrawString
— это оболочка GDI+.
Как я уже говорил, я не могу использовать TextRenderer, его нельзя использовать для печати, и он требует ссылки на формы, чего я хочу избежать в своей основной библиотеке. Хотя спасибо за объяснение!
Вы можете P/Invoke вызовы API GDI напрямую (или просто скопировать и вставить реализацию TextRenderer - все 10 ее строк). Это просто вызовы Windows API.
Это хорошая мысль! Я попробую, если это как-то сработает, хотя я не совсем уверен, что TextRenderer совместим с печатью, но я поиграюсь с ним и проведу несколько тестов.
@Velox Что ж, при печати в Windows, когда дело доходит до этого, он по-прежнему выдает вызовы рисования GDI, но вместо контекста устройства (DC), который представляет экран или растровое изображение, он представляет принтер. В конце концов, вы всегда можете использовать graphics.GetHDC , чтобы объект Graphics выделил DC, на котором вы затем сможете рисовать (это все, что делает TextRenderer). Да, вызовы WinAPI более утомительны и стары, но это все, что делает .NET.
В конце концов, Graphics.DrawString фактически вызывает собственные функции GDI+, а TextRenderer.DrawText фактически вызывает собственные функции GDI. Да, использование GDI из CLR означает, что ваше приложение должно работать на платформе с GDI — вы должны работать в Windows.
И Gdi, и Gdi+ являются важными инструментами в WinForms
, и каждый из них имеет свое применение. Это практически все, что у вас есть, если вы хотите придерживаться этой старой технологии и того, что она обеспечивает и поддерживает. Дело в том, что нам нужно знать и понимать, когда, где использовать каждый из них и почему.
Правило таково: TextRender.DrawText
на элементах управления и Graphics.DrawString
на изображениях и распечатках. Отлично. Однако в некоторых случаях это правило можно отменить. Например, какой из них следует использовать для:
ListBox
, ListView
и DataGridView
. Пример Ответ за вами.
Я понимаю, что ваш код выдает что-то вроде этого, когда вы изменяете размер элемента управления:
... и вы хотите - по неизвестной причине - избежать использования TextRenderer
для рисования текста, когда ни один из упомянутых выше сценариев не применим в этом контексте. Тем не менее, не используйте Graphics.DrawString
и вместо этого используйте GraphicsPath, чтобы исправить эту проблему и улучшить качество в целом.
public class SomeLabel : Label
{
protected override void OnPaint(PaintEventArgs e)
{
var cr = ClientRectangle;
using (var gp = new GraphicsPath())
using (var brFore = new SolidBrush(ForeColor))
using (var sf = new StringFormat(StringFormat.GenericTypographic))
using (var fnt = GetScaledFont(e.Graphics, Font, cr.Size, Text, 6, 128))
{
sf.FormatFlags |= StringFormatFlags.NoWrap;
gp.AddString(Text, fnt.FontFamily, (int)fnt.Style, fnt.Size, cr, sf);
e.Graphics.Clear(BackColor);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.FillPath(brFore, gp);
}
}
// Or your scaling logic ...
private Font GetScaledFont(
Graphics g,
Font srcFont,
Size canvasSize,
string text,
float minSize,
float maxSize)
{
float sz = GetScaledFontSize(g, srcFont, canvasSize, text, minSize, maxSize);
return new Font(srcFont.FontFamily, sz, srcFont.Style, GraphicsUnit.Pixel);
}
private float GetScaledFontSize(
Graphics g,
Font srcFont,
Size canvasSize,
string text,
float minSize,
float maxSize)
{
SizeF szf = g.MeasureString(text, srcFont);
float hRatio = canvasSize.Height / szf.Height;
float wRatio = canvasSize.Width / szf.Width;
float scaleRatio = Math.Min(hRatio, wRatio);
float scaledSize = Math.Max(minSize, Math.Min(maxSize, srcFont.Size * scaleRatio));
return scaledSize / 72f * g.DpiY;
}
}
Результат...
Итак, причина, по которой я не могу использовать TextRenderer, как я уже говорил, заключается в том, что, насколько мне известно, его нельзя использовать для печати. (Пожалуйста, сообщите мне, если я ошибаюсь!), Кроме того, я думаю, что TextRenderer имеет максимальное разрешение 300 точек на дюйм, я прав? Кроме того, TextRenderer требует ссылки на dll форм, чего я хочу избежать. Но спасибо за ответ. Я попробую, когда вернусь на работу в понедельник!
@Velox Здесь нужно различать две вещи. Как нарисовать элемент управления и как его распечатать. Эти две процедуры различны. То, что вам нужно увидеть на экране, не обязательно является тем, что вам нужно отправить на принтер. Вам просто нужно знать, как перевести/преобразовать то, что вы видите, чтобы получить желаемую распечатку. Да, сокращение ссылок на библиотеки — веская причина. Кстати, вы можете распечатать большие плакаты с изображениями в формате 300dpi
tif. Так что с этикетками проблем не будет.
Я понимаю, откуда вы пришли, но я думаю, что проще визуализировать представление этикетки один к одному, и это же представление фактически передается на принтер (я могу передать тот же код рендеринга в класс «PrintDocument», и все готово ) вместо того, чтобы пытаться придумать какую-нибудь утилиту-переводчик с еще дополнительными шагами. Я думаю, вы правы, 300 точек на дюйм для текста может подойти, хотя нам нужно убедиться, что штрих-коды имеют идеальное разрешение, поэтому я думаю, что не помешает убедиться, что текст тоже.
Я просто хотел убедиться, что этот вопрос решен и что он поможет кому-то в будущем.
Я хочу поблагодарить @Ian Boyd и @dr.null за их ответы, они действительно помогут в будущем. Я предлагаю всем, кто сталкивается с проблемами рисования текста, просмотреть свои ответы и попытаться адаптировать свои ответы.
Для моих целей, поскольку я действительно не хочу менять способ работы сейчас и мне нужна функция печати, я нашел очень простое решение этой проблемы, а именно:
Добавьте «\n» в конец строкового параметра, переданного в Graphics.DrawString. Это смешно и легко исправить, но это работает!
Вместо этого используйте TextRenderer.DrawText().