【问题标题】:What are the implications of using Canvas.TextOut?使用 Canvas.TextOut 有什么影响?
【发布时间】:2015-11-05 06:32:20
【问题描述】:

简介

我的问题来自过去几天我一直在处理的一个相当有趣的问题。我最近问了一个关于Writing a custom property inspector - How to handle inplace editor focus when validating values?的问题

此后,我在控件方面取得了一些不错的进展,例如在中间添加一个分隔线来分隔名称和值行,重要的是,分隔线可用于调整两列的大小。

这是我的问题开始的地方,在调整分隔线大小的同时显示就地编辑器导致我的控制稍微变慢。因此,我进一步更改了代码,仅在未调整分隔线大小时才显示就地编辑器。所以本质上,我使用Canvas.TextOut 将我的值绘制为字符串,如果选择了一行,则 Inplace 编辑器如上所示。如果调整分隔线的大小,就地编辑器将隐藏,一旦调整大小操作完成,就地编辑器将再次可见。

虽然这解决了我提到的轻微减速问题,但我遇到了一个新问题,即来自就地编辑器(基本上是 TEdit)的文本与我使用 Canvas.TextOut 绘制的文本略有不同


示例 1

差异非常微妙,但如果你看得足够近,你就能看到它:

图 1 Canvas.TextOut

图 2 DrawText

您可能需要使用屏幕放大镜才能看得更近,但在 SomeText 行中,Some 和 Text 之间以及 T 和 e 之间的间距更明显987654342@ 略有不同。


示例 2

一个更好的例子可能是将Canvas.TextOut 和DrawText 与就地编辑器(TEdit)文本进行比较:

图3比较

如您所见,这里的区别更为突出。使用Canvas.TextOut 时,字符串True 清楚地显示文本字符之间的间距要大得多,而DrawText 和inplace editor 呈现的文本完全相同。

当我使用Canvas.TextOut 时,我在调整检查器分隔符的大小以及显示和隐藏就地编辑器之间遇到了各种可怕的文本不匹配。如果我没有尝试和尝试替代文本绘制方法,我认为我永远不会意识到差异并找到解决方案。重要的是要知道,在将文本绘制到画布时,我使用的字体设置与我为就地编辑器定义的字体完全相同。

现在我使用的是DrawText 而不是Canvas.TextOut,一切都与就地编辑器和我想要的完全一致。


问题

我的问题是,是什么让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项目上,我得到的结果如下:

图 4 测试演示

在使用Canvas.TextOut 时再次注意字符串True 中的间距,在我看来,它与DrawText 以及TEdit 绘制其文本的方式明显不同。

下图与图 4 相同,但放大了 400%

图 5 测试演示放大 400%

Text 中的 T 和 e 以及 True 中的 T 和 r 之间存在显着差异。

图 6 文字“文本”放大 400% 并带有指南

您可以看到T 和e 之间的字距与DrawText 相比比Canvas.TextOut(使用ExtTextOut)更近一个像素。

图 7 True 这个词在指引下放大了 700%

您可以看到T 和r 之间的字距调整比Canvas.TextOut(使用ExtTextOut)更接近DrawText 和就地编辑器(TEdit)。


我测试了几种不同的字体,以下是我的发现:

好:

Arial、Cambria、Candara、Comic Sans MS、Consolas、Courier、Courier New、 Fixedsys, Georgia, Lucida Console, Lucida Sans Unicode, Microsoft Sans Serif、Tahoma、Terminal 和 Times New Roman。

不好:

Calibri、Corbel、Myriad Pro、Segoe UI、Trebuchet MS 和 Verdana。

好的字体是那些呈现文本的方式与DrawText 和Inpace Editor (TEdit) 控件使用Canvas.TextOut 的方式相同的字体。坏的表明Canvas.TextOut 呈现文本与其他方法略有不同。

这里可能有一些线索,虽然我不太确定,但我还是添加它以防万一。

【问题讨论】:

  • 开始一个新的 VCL 项目,在表单的 OnPaint 处理程序中放置 thre 行代码,这会重复问题。可以复制吗?我不能用W7。如果你也不能,那么没有案例就没有问题。
  • TextOut 内部使用ExtTextOut。
  • @SertacAkyuz 测试演示已添加,我使用的是 Windows 10。
  • 我隐约记得过去曾注意到这种差异。就好像这两个函数在内部使用了稍微不同的字距调整算法。不过,可能是不同渲染标志的情况。看看这里发现了什么会很有趣。
  • 无论确定的答案是什么,这都是个好问题。

标签: delphi delphi-xe7


【解决方案1】:

观察到的差异是由于使用了不同的 WinAPI 文本呈现函数及其行为。具体字符kerning

在排版中,字距调整(不太常见的榫眼)是 调整比例字体中字符之间的间距, 通常是为了达到视觉上令人愉悦的结果。字距调整 单个字母形式之间的空间,同时跟踪(字母间距) 在一系列字符上统一调整间距。

  1. DrawText

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)时尤其明显。不同的字体也有不同的默认字距,这就是为什么有些字体使用这两个函数在视觉上具有相同的渲染效果,而有些则没有。字距调整还取决于字体大小。例如,在9 pt 处使用Arial 渲染的字符AV 将具有相同的输出,而Arial 在12 pt 将导致不同的输出。

下图中的第一行使用ExtTextOut 使用无字距调整绘制,第二行使用DrawText 使用自动字距调整。

【讨论】:

  • 优秀的答案!我想知道为什么有些人无法重现这个问题而其他人却有?一旦我对字距调整和其他 cmets 中发布的一些链接进行了更多阅读,也许这会变得更加清晰。事实上,如果不是因为我一直在处理的自定义组件出现问题,我什至不会注意到或质疑 TextOut 和 DrawText 之间的区别,它甚至不是你通常会用屏幕放大镜寻找的东西对于差异,这一次我需要,我对我所看到的感到有点震惊。
  • 由于字距调整取决于组合的不同事物,如果您不确切知道要查找的内容,创建可重现的测试用例可能会很困难。即使使用不同的文本进行渲染也足以错过它。在您的情况下,使用字母 T 是最明显的触发器。
  • 观察到的差异还取决于操作系统版本,这不太符合您的解释。我认为您误解了 ExtTextOut 的文档,而 default spacing 是特定字体的字距调整信息,该函数无法兑现。 IMO 我们观察到的只是操作系统的一个错误。
  • @Sertac Windows 版本之间的解释可能会更改字符之间的默认间距,它可以是 default kerning 或 no-kerning。但 Windows 版本之间的字体定义也确实不同。例如,Windows 8.1 上的 Segoe UI 使用这两种方法呈现时没有字距调整(并且看起来相同),而 Arial 的呈现方式不同。 Windows 10 上的 Segoe UI 使用 DrawText 进行字距调整,没有使用 ExtTextOut。在 Windows 8.1 和 Windows 10 上,ExtTextOut 明确呈现文本而无需字距调整 - 称其为错误或功能
  • @SertacAkyuz 将 Windows 7 添加到 ExtTextOut 呈现文本而不进行字距调整的列表中,因此我将其称为功能并且我没有误解文档。
猜你喜欢
  • 2012-05-08
  • 1970-01-01
  • 2018-10-25
  • 2010-10-16
  • 2010-09-06
  • 2012-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多