Каковы последствия использования Canvas.TextOut?

Введение

Мой вопрос исходит из довольно интересной проблемы, с которой я имел дело в течение последних нескольких дней. Я недавно задал вопрос о Написание персонализированного инспектора свойств - Как обрабатывать фокус редактора inplace при проверке значений?

С тех пор я немного улучшил свой контроль, например, добавив разделитель посередине для разделения между строками Name и Value, и, что важно, разделитель может использоваться для изменения размера двух столбцов.

Вот где начались мои проблемы, поскольку видимый редактор inplace пока изменял размер разделителя, что немного снизило мой контроль. Поэтому я еще больше изменил код, чтобы отображать только редактор inplace, если разделитель не был изменен. Поэтому, по существу, я использовал Canvas.TextOut для рисования моих значений в виде строк, если выбрана строка, тогда редактор Inplace показан выше. Редактор inplace становится скрытым, если размер разделителя был изменен, после завершения операции изменения размера редактор inplace снова становится видимым.

В то время как это решило проблему небольшого замедления, о которой я упоминал, мне пришлось столкнуться с новой проблемой в том, что текст из редактора inplace (который в основном представляет собой TEdit) немного отличался от текста, который я рисовал, используя Canvas.TextOut


Пример 1

Разница довольно тонкая, но если вы посмотрите достаточно близко, вы можете просто увидеть ее:

fig.1 Canvas.TextOut

введите описание изображения здесь

fig.2 DrawText

Вам может потребоваться использовать экранную лупу, чтобы выглядеть ближе, но с рядом SomeText более заметно, что расстояние между Some и Text, а также между T и e в Text немного отличается.


Пример 2

Немного лучший пример - это сравнение между Canvas.TextOut и DrawText с текстом редактора inplace (TEdit):

введите описание изображения здесь

fig.3 Сравнение

Как вы можете видеть, разница здесь гораздо более заметна. Строка True наглядно показывает гораздо большее расстояние между текстовыми символами при использовании Canvas.TextOut, где в качестве текста текста DrawText и inplace editor точно совпадают.

Когда я использовал Canvas.TextOut, я получал всевозможные ужасные текстовые несоответствия между изменением размера моего делителя-инспектора и показом и скрытием редактора inplace. Если бы я не экспериментировал и не пытался использовать альтернативные методы рисования текста, я не думаю, что когда-либо бы понял разницу и нашел решение. Важно знать, что я использовал точные настройки шрифта при рисовании моего текста на холсте в качестве шрифта, который я определил для редактора inplace.

Теперь, когда я использую DrawText вместо Canvas.TextOut, все работает в унисон с редактором inplace и точно, как я хочу.


Вопрос

Мой вопрос заключается в том, что делает Canvas.TextOut визуализировать текст так иначе, как DrawText? Из моего примера и решения моей текущей проблемы ясно, что Canvas.TextOut не отображает текст так же, как TEdit с теми же настройками шрифта, но DrawText делает текст, казалось бы, правильным способом.

Это заставляет меня сомневаться в использовании Canvas.TextOut, если он не отображает текст правильно, должен ли я всегда использовать DrawText вместо этого?


Демо-версия

Вы можете проверить это для себя следующим кодом:

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormPaint(Sender: TObject);
  private
    FFont: TFont;
    FRect: TRect;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FFont       := TFont.Create;
  FFont.Color := clNavy;
  FFont.Name  := 'Segoe UI';
  FFont.Size  := 9;
  FFont.Style := [];
  FRect       := Rect(10, 30, 100, 100);

  Canvas.Font.Assign(FFont);
  Edit1.Font.Assign(FFont);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FFont.Free;
end;

procedure TForm1.FormPaint(Sender: TObject);
begin 
  Canvas.TextOut(10, 10, 'Canvas.TextOut: [True]');
  DrawText(Canvas.Handle, PChar('DrawText: [True]'), Length('DrawText: [True]'), FRect, DT_LEFT);
end;

С приведенным выше запуском в совершенно новом проекте VCL результат, который я получаю, выглядит следующим образом:

введите описание изображения здесь

fig.4 Test Demo

Снова обратите внимание на интервал в строке True при использовании Canvas.TextOut, с моего конца он явно отличается от DrawText и способ, которым TEdit рисует свой текст.

Ниже показано то же изображение, что и на рисунке 4, но увеличено на 400%

введите описание изображения здесь

fig.5 Test Demo увеличено на 400%

Заметные различия наблюдаются между T и e в Text, а также T и r в True.

введите описание изображения здесь

fig.6 Слово "Текст" увеличено на 400% с рекомендациями

Вы можете видеть, что кернинг между T и e ближе к пикселю с DrawText, чем к Canvas.TextOut (который использует ExtTextOut.)

введите описание изображения здесь

fig.7 Слово True увеличилось на 700% с рекомендациями

Вы можете видеть, что кернинг между T и r находится на один пиксель ближе к DrawText и редактору Inplace (TEdit), чем к Canvas.TextOut (который использует ExtTextOut.)


Я тестировал несколько разных шрифтов, и вот мои выводы:

Хорошо:

Arial, Cambria, Candara, Comic Sans MS, Consolas, Courier, Courier New, Fixedsys, Грузия, Lucida Console, Lucida Sans Unicode, Microsoft Sans Сериф, Тахома, Терминал и Times New Roman.

Плохо:

Calibri, Corbel, Myriad Pro, Segoe UI, Trebuchet MS и Verdana.

Хорошие шрифты - это те, которые, как представляется, отображают текст так же, как DrawText, а элементы управления пространственным редактором (TEdit) используют Canvas.TextOut. Плохие показывают, что Canvas.TextOut делает текст немного отличающимся от других методов.

Здесь может быть какая-то подсказка, хотя я не слишком уверен, но я все равно добавляю ее на всякий случай.

Ответы

Ответ 1

Наблюдаемая разница связана с использованием различных функций визуализации текста WinAPI и их поведения. В частности, символ kerning

В типографии кернинг (реже врезной) - это процесс регулировка расстояния между символами пропорционального шрифта, обычно для достижения визуально приятного результата. Кернинг регулирует пространство между отдельными формами букв, а отслеживание (расстояние между буквами) равномерно распределяет интервал по диапазону символов.

Функция DrawText рисует отформатированный текст в указанном прямоугольнике. Он форматирует текст в соответствии с указанным методом (расширение вкладок, оправдание символов, нарушение линий и т.д.).

  1. ExtTextOut (используется Canvas.TextOut)

ExtTextOut Объявление:

BOOL ExtTextOut(
  _In_       HDC     hdc,
  _In_       int     X,
  _In_       int     Y,
  _In_       UINT    fuOptions,
  _In_ const RECT    *lprc,
  _In_       LPCTSTR lpString,
  _In_       UINT    cbCount,
  _In_ const INT     *lpDx
);

Если параметр lpDx имеет значение NULL, функция ExtTextOut использует интервал между символами. Породы символов и содержимое массива, на которое указывает параметр lpDx, указывается в логических единицах. Порождение символьной ячейки определяется как верхний левый угол ячейки символа.

В принципе DrawText будет автоматически рисовать форматированный текст и включает в себя настройку расстояния между символами (кернинг), а ExtTextOut будет по умолчанию использовать интервал между символами (без кернинга). Если вы хотите отрегулировать расстояние между символами, вам нужно будет вычислить и предоставить параметр кернинга (lpDx).

Эти различия особенно заметны с некоторыми комбинациями символов, такими как T и маленькими буквами, которые визуально соответствуют под T, или AV, где один V подходит к A. Различные шрифты также имеют разные ядра по умолчанию, и это причина, по которой некоторые шрифты имеют визуально одинаковый рендеринг с использованием обеих функций, а некоторые нет. Кернинг также зависит от размера шрифта. Например, символы AV, отображаемые с помощью Arial at 9 pt, будут иметь одинаковый вывод с обеими функциями, а Arial at 12 pt приведет к разным выходам.

Первая строка следующего изображения рисуется без кернинга с использованием ExtTextOut и второй строки с автоматическим кернированием с использованием DrawText.

введите описание изображения здесь