字体和文本输出到图形资源
除了在图形资源的数组中渲染单个像素外,我们还可以使用内置函数来显示文本。这些函数用于更改当前字体及其特性 (TextSetFont),获取给定字符串可以内切的矩形的尺寸 (TextGetSize),以及直接将标题插入到生成的图像 (TextOut) 中。
bool TextSetFont(const string name, int size, uint flags, int orientation = 0)
该函数为后续使用 TextOut 函数(见下文)在图像缓冲区中绘制文本设置字体及其特性。name 参数可以包含内置 Windows 字体的名称或通过资源指令连接的 ttf 字体文件(TrueType 字体)(如果名称以 "::" 开头)。
大小 (size) 可以用点(一种印刷计量单位)或像素(屏幕点)指定。正值表示计量单位是像素,负值以十分之一点为单位进行测量。以像素为单位的高度对于不同用户来说看起来会有所不同,具体取决于其显示器的技术能力和设置。以点为单位的高度对于每个人来说大致(“凭肉眼判断”)相同。
印刷点是一种物理长度单位,传统上等于 1/72 英寸。因此,1 点等于 0.352778 毫米。屏幕上的像素是一种虚拟的长度度量。其物理尺寸取决于屏幕的硬件分辨率。例如,屏幕密度为 96 DPI(每英寸点数)时,1 像素将占据 0.264583 毫米或 0.75 点。然而,大多数现代显示器具有更高的 DPI 值,因此像素更小。因此,包括 Windows 在内的操作系统长期以来都有增加界面元素可见比例的设置。因此,如果你以点为单位指定大小(负值),则文本的像素大小将取决于显示器和操作系统中的缩放设置(例如,“标准”100%,“中等”125% 或“大”150%)。
放大时,系统会人为地放大显示的像素。这相当于以像素为单位减小屏幕尺寸,系统会应用有效 DPI 来实现相同的物理尺寸。如果启用了缩放,则报告给程序(包括终端,然后是 MQL 程序)的是有效 DPI。如有必要,可以从 TERMINAL_SCREEN_DPI 属性中找出屏幕的 DPI(请参见 屏幕规格)。然而实际上,通过以点为单位设置字体大小,我们无需根据 DPI 重新计算其大小,因为系统会为我们完成此操作。
默认字体是 Arial,默认大小是 -120 (12 pt)。控件,特别是图表上的内置对象,也以点为单位操作字体大小。例如,如果在 MQL 程序中,你希望绘制与 OBJ_LABEL 对象中文本大小相同的文本,该对象的大小为 10 点,则应使用等于 -100 的参数 size。
flags 参数用于设置描述字体样式的标志组合。该组合是使用按位或运算符 ('|') 构成的位掩码。标志分为两组:样式标志和粗细标志。
下表列出了样式标志。它们可以混合使用。
标志 |
说明 |
---|---|
FONT_ITALIC |
斜体 |
FONT_UNDERLINE |
下划线 |
FONT_STRIKEOUT |
删除线 |
粗体标志具有与其对应的相对权重(用于比较预期效果)。
标志 |
说明 |
---|---|
FW_DONTCARE |
0(将应用系统默认值) |
FW_THIN |
100 |
FW_EXTRALIGHT, FW_ULTRALIGHT |
200 |
FW_LIGHT |
300 |
FW_NORMAL、FW_REGULAR |
400 |
FW_MEDIUM |
500 |
FW_SEMIBOLD、FW_DEMIBOLD |
600 |
FW_BOLD |
700 |
FW_EXTRABOLD、FW_ULTRABOLD |
800 |
FW_HEAVY、FW_BLACK |
900 |
在标志组合中仅使用这些值中的一个。
orientation 参数指定文本相对于水平线的角度,单位为十分之一度。例如,orientation = 0 表示正常文本输出,而 orientation = 450 将导致 45 度倾斜(逆时针)。
请注意,一次 TextSetFont 调用中所做的设置将影响所有后续的 TextOut 调用,直到它们被更改为止。
如果成功,函数返回 true;如果出现问题(例如,未找到字体),则返回 false。
在描述完所有这三个函数后,我们将考虑一个使用此函数的示例,以及另外两个函数的示例。
bool TextGetSize(const string text, uint &width, uint &height)
该函数返回当前字体设置下(可以是默认字体或先前调用 TextSetFont 时指定的字体)文本行的宽度和高度。
text 参数传递一个需要计算像素长度和宽度的字符串。维度值由函数根据 width 和 height 参数中的引用写入。
需要注意的是,调用 TextSetFont 时由 orientation 参数指定的显示文本的旋转(倾斜)不会以任何方式影响大小计算。换句话,如果文本应该旋转 45 度,那么 MQL 程序本身必须计算文本可以容纳的最小正方形。TextGetSize 函数计算标准(水平)位置的文本大小。
bool TextOut(const string text, int x, int y, uint anchor, uint &data[], uint width, uint height, uint color, ENUM_COLOR_FORMAT color_format)
该函数在图形缓冲区中的指定坐标处绘制文本,同时考虑颜色、格式和先前的设置(字体、样式和方向)。
文本在 text 参数中传递,并且必须是单行形式。
以像素为单位指定的 x 和 y 坐标定义了图形缓冲区中显示文本的点。生成的题字的哪个位置将位于点 (x, y) 取决于 anchor 参数中的绑定方法(见下文)。
缓冲区由 data 数组表示,尽管该数组是一维的,但它存储了一个维度为 width x height 个点的二维“画布”。该数组可以从 ResourceReadImage 函数获取,或者由 MQL 程序分配。在完成所有编辑操作(包括文本输出)后,应该基于此缓冲区创建一个新资源或将其应用于已有的资源。在这两种情况下,都应该调用 ResourceCreate。
文本的颜色和颜色的处理方式由参数 color 和 color_format 设置(请参见 ENUM_COLOR_FORMAT)。请注意,用于颜色的类型是 uint,即,要传递 color,应使用 ColorToARGB 进行转换。
由 anchor 参数指定的锚定方法是两个文本定位标志的组合:垂直和水平。
水平文本位置标志有:
- TA_LEFT 锚定到边界框的左侧
- TA_CENTER 锚定到矩形左右两侧的中间
- TA_RIGHT 锚定到边界框的右侧
垂直文本位置标志有:
- TA_TOP 锚定到边界框的顶部
- TA_VCENTER 锚定到矩形顶部和底部之间的中间
- TA_BOTTOM 锚定到边界框的底部
总共有 9 种有效的标志组合来描述锚定方法。
输出文本相对于锚点的位置
在这里,图片的中心包含一个在生成的图像中坐标为 (x, y) 的刻意放大的点。根据标志,文本相对于此点出现在指定位置(文本内容对应于应用的锚定方法)。
为便于参考,所有题字均以标准水平位置制作。但请注意,也可以对其中任何一个应用角度 (orientation),然后相应的题字将围绕该点旋转。在此图像中,仅旋转了在两个维度上都居中的标签。
这些标志不应与文本对齐混淆。边界框的大小始终适合文本,其相对于锚点的位置在某种意义上与标志名称相反。
我们看一些使用三个函数的示例。
首先,让我们检查设置字体粗细和样式的最简单选项。ResourceText.mq5 脚本允许在输入变量中选择字体名称、其大小以及背景和文本的颜色。标签将在图表上显示指定的秒数。
input string Font = "Arial"; // Font Name
|
每个粗细等级的名称将显示在标签文本中,因此为了简化过程(通过使用 EnumToString),声明了 ENUM_FONT_WEIGHTS 枚举。
enum ENUM_FONT_WEIGHTS
|
题注标志收集在 rendering 数组中,并从中选择随机组合。
const uint rendering[] =
|
为了在一定范围内获得随机数,有一个辅助函数 Random。
int Random(const int limit)
|
在脚本的主函数中,我们找到图表的大小并创建一个跨越整个空间的 OBJ_BITMAP_LABEL 对象。
void OnStart()
|
接下来,我们为图像缓冲区分配内存,用指定的背景颜色填充它(或默认情况下保持透明),基于该缓冲区创建一个资源,并将其绑定到对象。
uint data[];
|
以防万一,请注意,除非对象应该在两种状态之间切换,否则我们可以在 ObjectSetString 调用中设置 OBJPROP_BMPFILE 属性而无需修饰符(0 或 1)。
所有字体粗细都列在 weights 数组中。
const uint weights[] =
|
在循环中,我们依次使用 TextSetFont 为每行设置下一个粗细等级,预先选择一个随机样式。使用 TextOut 在缓冲区中绘制字体描述,包括其名称和粗细。
const int step = h / (nw + 2);
|
现在更新资源和图表。
ResourceCreate(name, data, w, h, 0, 0, w, COLOR_FORMAT_ARGB_RAW);
|
用户可以提前停止演示。
const uint timeout = GetTickCount() + Seconds * 1000;
|
最后,脚本删除资源和对象。
ObjectDelete(0, name);
|
脚本的结果如下图所示。
以不同粗细和样式绘制文本
在 ResourceFont.mq5 的第二个示例中,我们将通过包含自定义字体作为资源并使用 90 度增量的文本旋转来使任务更加困难。
字体文件位于脚本旁边。
#resource "a_LCDNova3DCmObl.ttf" |
可以在输入参数中更改消息。
input string Message = "Hello world!"; // Message |
这一次,OBJ_BITMAP_LABEL 不会占据整个窗口,因此在水平和垂直方向上都居中。
void OnStart()
|
首先,分配最小尺寸的缓冲区,只是为了完成资源创建。稍后我们将扩展它以适应题字的尺寸,为此保留了变量 width 和 height。
uint data[], width, height;
|
在测试时间倒计时的循环中,我们需要更改题字的方向,为此有 angle 变量(度数将在其中滚动)。方向将每秒更改一次,计数在 remain 变量中。
const uint timeout = GetTickCount() + Seconds * 1000;
|
在循环中,我们不断更改文本的旋转,并在文本本身中显示秒数的倒计时计数器。对于每个新的题字,使用 TextGetSize 计算其大小,并基于此重新分配缓冲区。
while(!IsStopped() && GetTickCount() < timeout)
|
请注意,如果文本是垂直的,则需要交换尺寸。更一般地,对于旋转到任意角度的文本,需要更多的数学运算才能获得适合整个文本的缓冲区大小。
最后,我们还删除对象和资源。
ObjectDelete(0, name);
|
脚本执行的某个时刻如下图所示。
带有自定义字体的题字
作为最后一个示例,我们看一下 ResourceTextAnchOrientation.mq5 脚本,它显示了文本的各种旋转和锚点。
该脚本使用指定的字体生成指定数量的标签 (ExampleCount)。
input string Font = "Arial"; // Font Name
|
锚点和旋转是随机选择的。
为了在标签中指定锚点的名称,声明了包含所有有效选项的 ENUM_TEXT_ANCHOR 枚举。因此,我们可以简单地对任何随机选择的元素调用 EnumToString。
enum ENUM_TEXT_ANCHOR
|
这些新常量的数组在 OnStart 处理程序中声明。
void OnStart()
|
初始对象和资源创建类似于 ResourceText.mq5 中的示例,因此我们在此省略它们。最有趣的事情发生在循环中。
for(int i = 0; i < ExampleCount; ++i)
|
剩下的只是更新图片和图表,然后等待用户的命令并释放资源。
ResourceCreate(name, data, w, h, 0, 0, w, COLOR_FORMAT_ARGB_NORMALIZE);
|
结果如下。
具有随机坐标、锚点和角度的文本输出
此外,为了独立学习,本书提供了一个玩具图形编辑器 SimpleDrawing.mq5。它被设计为一个无缓冲区的指标,并在其工作中使用先前考虑的形状类(参见 ResourceShapesDraw.mq5)。它们几乎没有改动地放在头文件 ShapesDrawing.mqh 中。以前,形状是由脚本随机生成的。现在用户可以在图表上选择并绘制它们。为此,根据注册的形状类的数量,实现了一个带有调色板和按钮栏的界面。该界面由 SimpleDrawing 类 (SimpleDrawing.mqh) 实现。
简单图形编辑器
面板和调色板可以沿图表的任何边界放置,展示了旋转标签的能力。
通过按下面板中的按钮来选择要绘制的下一个形状:按钮会“粘”在按下状态,其背景颜色表示选定的绘图颜色。要更改颜色,请点击调色板上的任意位置。
当在面板中选择其中一种形状类型时(其中一个按钮处于“活动”状态),点击绘图区域(图表的其余部分,用阴影表示)会在该位置绘制预定义大小的形状。此时,按钮“关闭”。在此状态下,当所有按钮都处于非活动状态时,你可以使用鼠标在工作区中移动形状。如果我们按住 Ctrl,则形状会调整大小而不是移动。“热点”位于每个形状的中心(敏感区域的大小由源代码中的宏设置,对于非常高 DPI 的显示器可能需要增加)。
请注意,编辑器在生成的资源的名称中包含绘图 ID (ChartID)。这允许在多个图表上并行运行编辑器。