Graphics.DrawString странный ненормальный кернинг

Я разрабатываю программу для дизайна этикеток и столкнулся с очень странной проблемой, на которую не могу найти ответа нигде в Интернете.

Основное объяснение заключается в том, что когда для свойства layoutRectangleDrawString() установлено значение, немного превышающее размер длины текста, кернинг, похоже, просто отключается. Лучше всего это видно при сочетании символов «Те».

Исправьте кернинг с помощью «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, поскольку его нельзя использовать для печати.

Вместо этого используйте TextRenderer.DrawText().

Hans Passant 12.04.2024 11:31

@HansPassant Я не могу. TextRenderer нельзя использовать для печати, а мне нужна эта функциональность.

Velox 12.04.2024 11:32

Я знаю, что «Те» выглядит неправильно, но в приведенном выше примере справа от «Т» показана буква «е». Я уверен, что если вы замените «Т» на «L», вы будете счастливы.

Walter Verhoeven 12.04.2024 11:36

@WalterVerhoeven Я не могу, никогда не использую букву «Т».

Velox 12.04.2024 11:37

Вам просто не хватает аргумента метода, который сообщает вам, что вывод отправляется на принтер. Если вы не хотите его добавлять, используйте Graphics.DpiY. Если оно меньше 300, используйте TextRenderer. Если вам нужно точное соответствие экрана и бумаги, вместо этого вам придется использовать WPF.

Hans Passant 12.04.2024 11:58

@HansPassant ну, сейчас для меня очень радикально использовать WPF. Этот проект находится в разработке некоторое время. Действительно ли это единственное решение? Насколько я знаю, даже Microsoft говорит, что TextRenderer нельзя использовать для печати, и да, его разрешение составляет 300 точек на дюйм и выше, также TextRenderer требует пространства имен Windows.Forms, и для моей основной библиотеки я старался не добавлять его, поскольку он должен быть автономным. библиотека, которая может работать с любым графическим компонентом, даже если это не Forms. И да, мне нужно точное соответствие между бумагой и тем, что на экране.

Velox 12.04.2024 12:01

@HansPassant, вы можете использовать букву T, только если это заглавная буква T, затем просто переместите влево на 15% ширины букв, и вы получите то, что хотели бы видеть, вы сами контролируете, где вы печатаете буквы.

Walter Verhoeven 12.04.2024 16:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
7
303
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Укороченная версия

Не используйте Graphics.DrawString; он медленнее, устарел и больше не рекомендуется.

Используйте TextRenderer.DrawText.

Графика.DrawString TextRenderer.DrawString плохой хороший тот, который мы не хотим использовать тот, который мы хотим использовать использует GDI+ для рендеринга текста использует GDI для рендеринга текста графика.MeasureString TextRenderer.MeasureText графика.DrawString TextRenderer.DrawText Выглядит лучше Лучше локализуется Быстрее

Длинная версия

В .NET существует два способа рисования текста:

  • GDI+ (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, его нельзя использовать для печати, и он требует ссылки на формы, чего я хочу избежать в своей основной библиотеке. Хотя спасибо за объяснение!

Velox 13.04.2024 22:57

Вы можете P/Invoke вызовы API GDI напрямую (или просто скопировать и вставить реализацию TextRenderer - все 10 ее строк). Это просто вызовы Windows API.

Ian Boyd 14.04.2024 03:37

Это хорошая мысль! Я попробую, если это как-то сработает, хотя я не совсем уверен, что TextRenderer совместим с печатью, но я поиграюсь с ним и проведу несколько тестов.

Velox 14.04.2024 04:29

@Velox Что ж, при печати в Windows, когда дело доходит до этого, он по-прежнему выдает вызовы рисования GDI, но вместо контекста устройства (DC), который представляет экран или растровое изображение, он представляет принтер. В конце концов, вы всегда можете использовать graphics.GetHDC , чтобы объект Graphics выделил DC, на котором вы затем сможете рисовать (это все, что делает TextRenderer). Да, вызовы WinAPI более утомительны и стары, но это все, что делает .NET.

Ian Boyd 14.04.2024 16:33

В конце концов, Graphics.DrawString фактически вызывает собственные функции GDI+, а TextRenderer.DrawText фактически вызывает собственные функции GDI. Да, использование GDI из CLR означает, что ваше приложение должно работать на платформе с GDI — вы должны работать в Windows.

Ian Boyd 14.04.2024 16:35

И 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 13.04.2024 22:54

@Velox Здесь нужно различать две вещи. Как нарисовать элемент управления и как его распечатать. Эти две процедуры различны. То, что вам нужно увидеть на экране, не обязательно является тем, что вам нужно отправить на принтер. Вам просто нужно знать, как перевести/преобразовать то, что вы видите, чтобы получить желаемую распечатку. Да, сокращение ссылок на библиотеки — веская причина. Кстати, вы можете распечатать большие плакаты с изображениями в формате 300dpitif. Так что с этикетками проблем не будет.

dr.null 14.04.2024 02:46

Я понимаю, откуда вы пришли, но я думаю, что проще визуализировать представление этикетки один к одному, и это же представление фактически передается на принтер (я могу передать тот же код рендеринга в класс «PrintDocument», и все готово ) вместо того, чтобы пытаться придумать какую-нибудь утилиту-переводчик с еще дополнительными шагами. Я думаю, вы правы, 300 точек на дюйм для текста может подойти, хотя нам нужно убедиться, что штрих-коды имеют идеальное разрешение, поэтому я думаю, что не помешает убедиться, что текст тоже.

Velox 14.04.2024 04:21
Ответ принят как подходящий

Я просто хотел убедиться, что этот вопрос решен и что он поможет кому-то в будущем.

Я хочу поблагодарить @Ian Boyd и @dr.null за их ответы, они действительно помогут в будущем. Я предлагаю всем, кто сталкивается с проблемами рисования текста, просмотреть свои ответы и попытаться адаптировать свои ответы.

Для моих целей, поскольку я действительно не хочу менять способ работы сейчас и мне нужна функция печати, я нашел очень простое решение этой проблемы, а именно:

Добавьте «\n» в конец строкового параметра, переданного в Graphics.DrawString. Это смешно и легко исправить, но это работает!

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