Skia-如何渲染文本(上)
渲染简单文本
示例代码如下:
// #include "include/core/SkFontMgr.h"
// #include "include/core/SkFontStyle.h"
// #include "include/ports/SkTypeface_win.h"
// #include "include/core/SkFont.h"
void drawText(SkCanvas *canvas)
{sk_sp<SkFontMgr> fontMgr = SkFontMgr_New_GDI();SkFontStyle fontStyle = SkFontStyle::Normal();sk_sp<SkTypeface> typeFace = fontMgr->matchFamilyStyle("Microsoft YaHei", fontStyle);SkFont font(typeFace,56);SkPaint paint;paint.setColor(0xFF00FFFF);canvas->drawString("Hello World!", 20, 120, font, paint);
}
程序运行结果如下所示:
这段代码完成了以下几项工作:
创建字体管理器:
SkFontMgr
SkFontMgr_New_GDI
方法负责创建一个基于 GDI 的字体管理器实例。该字体管理器使用 Windows GDI 来获取和管理字体。字体管理器的类型为
SkFontMgr
,这个对象可以查找 并使用 Windows 系统上的字体,也可以通过字体文件创建字体(这一点我们后文会介绍)。SkFontMgr_New_GDI
方法创建的字体管理器仅适用于Windows操作系统,Skia 还提供了很多其他创建字体管理器的方法,读者可以到这里来了解:skia\tools\fonts\FontToolUtils.cpp
(TestFontMgr方法,247行附近)。创建默认字体样式:
SkFontStyle
字体样式有普通、粗体、斜体、粗斜体之分,
SkFontStyle::Normal()
负责创建了一个普通字体样式。获取字型对象:
TypeFace
SkFontMgr
对象的matchFamilyStyle
方法通过字体名称(Microsoft YaHei
)和字体样式获取一个TypeFace
字型对象这个对象封装了字体、字型等信息。有了它之后,我们就可以创建字体对象了(对应的字体为:微软雅黑)。
创建字体对象:
SkFont
SkFont
的构造函数接收两个参数,一个是TypeFace
对象,一个是字体大小。绘制文本
有了字体对象之后,我们就可以用这个字体绘制文本了。
SkCanvas
对象的drawString
方法的第一个参数为被绘制的字符串,第二个和第三个参数为文本绘制的位置。第四个参数为字体对象,第五个参数为绘图对象,它让我们绘制的文本呈现蓝色。
如果把 "Hello World!" 改成中文字符串,你会发现这段代码就不工作了。这是为什么呢?
接下来就来解决这个问题。
绘制中文
SkCanvas
对象的 drawString
方法默认使用 UTF8
编码的方式渲染文本,UTF8
是一种变长编码格式,一个字符使用 1 到 4 个字节表示。
比如 A
就占据一个字节的空间,中
则占据三个字节的空间,𝄞
则占据四个字节的空间。
UTF16
也是一种变长编码格式,一个字符用 2 到 4 个字符表示。
比如 A
就占据两个字节的空间,中
也占据两个字节的空间,𝄞
则占据四个字节的空间。
Windows 系统内大量使用 UTF-16
编码的字符串。
想要让Skia绘制中文就得使用 UTF16
编码字符。
实际上中日韩(技术社区里称之为 CJK
字符)等所有 汉语言体系 的文本都得使用 UTF16
编码来绘制。
下面示例就是三种渲染中文文本的代码:
void drawCJKText(SkCanvas* canvas)
{SkPaint paint;paint.setColor(0xFF00FFFF);paint.setStroke(false);sk_sp<SkFontMgr> fontMgr = SkFontMgr_New_GDI();SkFontStyle fontStyle = SkFontStyle::Normal();sk_sp<SkTypeface> typeFace = fontMgr->matchFamilyStyle("Microsoft YaHei", fontStyle);SkFont font(typeFace, 38);std::wstring text1{ L"你好,世界!" };auto length = text1.size() * sizeof(wchar_t); canvas->drawSimpleText(text1.c_str(), length, SkTextEncoding::kUTF16, 20, 120, font, paint);auto text2 = wideStrToStr(text1);canvas->drawString(text2.c_str(), 20, 180, font, paint);//canvas->drawSimpleText(text2.c_str(), text2.size(), SkTextEncoding::kUTF8, 20, 180, font, paint);std::u16string text3{ u"你好,世界!" };length = text3.size() * sizeof(char16_t);canvas->drawSimpleText(text3.c_str(), length, SkTextEncoding::kUTF16, 20, 240, font, paint);
}
程序运行结果如下图所示:
第一种方法:
此方法使用宽字节字符串
渲染文本(这是 Windows 系统中最常用的字符串类型)
宽字节字符串(std::wstring
),不同于 std::string
,它内部存储的字符类型为wchar_t
,std::string 内部存储的是 char
。
std::wstring 字符串的字节数量为字符数量(text.size()
)乘以宽字符占据的字节数量( sizeof(wchar_t)
这个值在 Windows 平台和 Linux 平台并不相同)
之所以计算这个长度,是因为在绘制文本时,要把这个长度告知 Skia 库。
我们使用 SkCanvas 对象的 drawSimpleText
方法来绘制宽文本字符串。
这个方法第二个参数即为前面计算的 std::wstring 字符串字节数量,第三个参数为编码方式(UTF16
)。
其他参数与 drawString
方法的大同小异,不再赘述。
再来看 第二种方法:
此方法先把宽字节字符串(std::wstring
)转码为 UTF8
字符串(std::string
),再以 UTF8
字符串的方式渲染它。
此处我们使用的 canvas->drawString
方法其内部调用的也是drawSimpleText
方法。
字符串转码方法 wideStrToStr
如下所示:
std::string wideStrToStr(const std::wstring& wstr)
{const int count = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int)wstr.length(), NULL, 0, NULL, NULL);std::string str(count, 0);WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &str[0], count, NULL, NULL);return str;
}
此方法使用 Windows API
把宽字节字符串转型成 UTF8
字符串。
这个工具方法很有用,以后还会用到。
最后看 第三种方法
在此方法中使用 std::u16string
类型来表示 UTF16
字符串。它内部存储的字符类型为 char16_t
。
所以计算字符串字节数量时,应使用:text3.size() * sizeof(char16_t)
根据项目需要,选择任一种方法绘制 CJK
文本,这三种方法本身并没什么优略之分。
不过有以下几点需要读者注意:
UTF16
编码即可以用来绘制英文,又可以用来绘制中文,甚或中英文混杂的字符串,所以兼容性更好,虽然多占用一些内存,一般也会优先使用这种方式绘制字符串。有些字体文件不支持中文,所以当你把编码方式和文本长度都设置好之后,仍然无法绘制中文文本时,那就要看是不是选择了错误的字体了。
文本的位置
文本绘制到坐标 20, 120
处。
你可能觉得这是文本左上角的坐标,实际上不是,这个坐标位于文本右下方。
为绘制的文本添加两条参考线,如下代码所示:
void textPosition(SkCanvas* canvas)
{//... 省略了部分重复的代码paint.setColor(0xFFFFFF00);paint.setStroke(true);paint.setStrokeWidth(1);canvas->drawLine(SkPoint::Make(20, 0), SkPoint::Make(20, h), paint);canvas->drawLine(SkPoint::Make(0, 120), SkPoint::Make(w, 120), paint);//... 省略了部分重复的代码
}
再次运行程序,你会看到两条黄色的参考线,它们的交叉点坐标就是(20, 120)。
要想精确的控制文本的位置,就必须知道这个点与文本左上角的距离(x_Distance
,y_Distance
)。