- FreeType绘制文字流程
# 以下step,都能在libpag里找到对应的代码:
FT_Library library;
FT_Face face;
FT_Error error;
FT_Size ftSize = nullptr;
/* step 1
Initialize FreeType library
*/
error = FT_Init_FreeType( &library ); // Initialize the library
/* step 2
Load a font file using FT_New_Face or FT_Open_Face
*/
error = FT_New_Face( library, "path/to/font.ttf", 0, &face );
//libpag里是FT_Open_Face
// auto err = FT_Open_Face(gFTLibrary->library(), &args, data.ttcIndex, &face->face);
// 和 auto err = FT_New_Face(gFTLibrary->library(), data.path.c_str(), 0, &face->face);
// 是同样的功能,除了FT_Open_Face还支持自定义的字体文件数据流
/* step 3
Set the font size
*/
err = FT_New_Size(_face->face, &ftSize);
err = FT_Activate_Size(ftSize);
error = FT_Set_Pixel_Sizes(ft_face, 0, font_size);
// libpag里是
//err = FT_Set_Char_Size(_face->face, scaleX, scaleY, 72, 72);
//一样的功能
/* step 4
load glyph to face
*/
FT_UInt glyph_index = FT_Get_Char_Index(ft_face, character);
FT_Load_Glyph(ft_face, glyph_index, FT_LOAD_DEFAULT);
//libpag里在FT_Load_Glyph之前,通过FT_Set_Transform设置了字形图像的二维仿射变换
/* step 5
Render the character as a bitmap
*/
error = FT_Render_Glyph(ft_face->glyph, FT_RENDER_MODE_NORMAL);
//bitmap now is in face->glyph->bitmap
// libpag里对应的是FT_Outline_Get_Bitmap(ftLibrary, &(outline->outline), &bitmap);
// 表示在bitmap上叠加字形的轮廓
/* step 6
release resources
*/
FT_Done_Face(face);
FT_Done_Size(ftSize);
FT_Done_Library(_library);
从FreeType得到的字形,包含以下用于布局排版的属性:
freeType.png
- 文字渲染核心类
TextAnimatorRenderer
计算当前帧应用文字动画后的结果,即更新字体的相关属性
TextRenderer.cpp
存储和更新字体属性,包括从TextAnimatorRenderer 获取当前帧的字体动画属性,然后用于在RenderTexts 等函数里用于计算文字排版等渲染文字所需要的TextContent/Graphic/Path等中间结果
TextSelectorRenderer
文字动画里的插值计算,即动画选择器。对字符逐个应用动画,计算对应的字符属性
- 文字相关类的UML图
-
TextAnimatorRenderer相关
TextAnimationRenderer.jpg -
TextRun相关
TextRun.png
-
文字引擎的选择( iOS 和 web 平台,用 freetype 没法使用系统字体)
平台 path渲染
iOS & Mac CoreGraphics //也可以配置成使用freetype
Android freetype
linux freetype
windows freetype
web Canvas2D //也可以配置成使用freetype
在Cmake文件里,即编译时,配置使用哪个文字引擎
Mac端的对应代码是if ENV["PAG_USE_FREETYPE"] == 'ON' and ENV["PLATFORM"] == "mac",因此在终端里执行 PAG_USE_FREETYPE=ON pod install表示在mac上使用freetype文字引擎,而直接 pod install会使用Mac系统的CoreGraphics文字引擎 -
文字动画流程(以FreeType为例)
先预处理渲染数据然后执行渲染
- 预处理渲染数据
PAGPlayer 在flushInternal方法里预处理渲染数据
auto renderingStart = GetTimer();
if (contentVersion != stage->getContentVersion()) {
contentVersion = stage->getContentVersion();
Recorder recorder = {};
stage->draw(&recorder)
lastGraphic = recorder.makeGraphic();
}
auto presentingStart = GetTimer();
if (lastGraphic) {
lastGraphic->prepare(renderCache);
}
stage->draw(&recorder) 是关键代码,会在内部继续调用
PAGComposition::draw(recorder) -> PAGComposition::DrawChildLayer(recorder, childLayer) -> PAGTextLayer::getContent() -> TextContentCache::createContent(layerFrame) -> TextRender::RenderTexts(sourceText,pathOption,moreOption, animators,layerFrame)
也就是在RenderTexts 方法里,最终执行文字渲染相关物料的生产和计算:
1.1 创建字型
if (hasTypeface) {
glyphId = typeface->getGlyphID(name);
if (glyphId != 0) {
textFont.setTypeface(typeface);
}
}
if (glyphId == 0) {
// 为每个字符寻找匹配的字体,所以有可能一行文字里有不同的字体
//比如本行包含Emoji字符,但本行的字体Helvetica的FT_HAS_COLOR为false,即不支持emoji,就会被分配支持Emoji的字体
auto fallbackTypeface = FontManager::GetFallbackTypeface(name, &glyphId);
textFont.setTypeface(fallbackTypeface);
}
1.2 根据文本属性和字形列表,计算文字的排版:
auto textLayout = CreateTextLayout(textDocument.get(), glyphList);
1.3 对字形列表应用文字动画
TextAnimatorRenderer::ApplyToGlyphs(glyphList, animators, textDocument, layerFrame)
1.31 文字动画里包含选择器信息,分为范围选择器RangeSelectorRenderer和摇摆选择器WigglySelectorRenderer,循环选择器列表selectorRenderers来叠加动画变换
float TextSelectorRenderer::CalculateFactorFromSelectors(
const std::vector<TextSelectorRenderer*>& selectorRenderers, size_t index, bool* pBiasFlag) {
float totalFactor = 1.0f;
bool isFirstSelector = true;
for (auto selectorRenderer : selectorRenderers) {
bool biasFlag = false;
auto factor = selectorRenderer->calculateFactorByIndex(index, &biasFlag);
if (pBiasFlag != nullptr) {
*pBiasFlag |= biasFlag;
}
totalFactor = selectorRenderer->overlayFactor(totalFactor, factor, isFirstSelector);
isFirstSelector = false;
}
return totalFactor;
}
1.4 将背景/黑白字符/彩色字符(emoji等),即background/normalText/colorText,依次转化成Graphic对象,最后三者一起存放到GraphicContent里并返回
std::vector<std::shared_ptr<Graphic>> contents = {};
if (textDocument->backgroundAlpha > 0) {
auto background = RenderTextBackground(glyphLines, textDocument.get());
if (background) {
contents.push_back(background);
}
}
std::vector<GlyphHandle> simpleGlyphs = {};
std::vector<GlyphHandle> colorGlyphs = {};
for (auto& line : glyphLines) {
for (auto& glyph : line) {
simpleGlyphs.push_back(glyph);
auto typeface = glyph->getFont().getTypeface();
if (typeface->hasColor()) {
colorGlyphs.push_back(glyph);
}
}
}
auto normalText = Text::MakeFrom(simpleGlyphs, hasAnimators ? nullptr : &textBounds);
if (normalText) {
contents.push_back(normalText);
}
auto graphic = Graphic::MakeCompose(contents);
auto colorText = Text::MakeFrom(colorGlyphs);
return std::unique_ptr<TextContent>(new TextContent(std::move(graphic), std::move(colorText)));
1.5 执行GraphicContent::draw(Recorder* recorder)来把Graphics信息存入Recorder里。
1.6 执行 stage->draw(&recorder) 的下一行 lastGraphic = recorder.makeGraphic() 通过这个recorder再生成一个具体用于渲染的Graphic对象
至此,完成文本渲染物料的准备工作
- 执行渲染
PAGPlayer 在flushInternal函数里,代码!pagSurface->draw(renderCache, lastGraphic, signalSemaphore, _autoClear)以上一步的结果lastGraphic 作为输入,函数内部会在graphic树形结构里递归执行draw(canvas, cache)来渲染,文字对应的Graphic就是Text类:
void Text::draw(Canvas* canvas, RenderCache*) const {
drawTextRuns(static_cast<Canvas*>(canvas), 0);
drawTextRuns(static_cast<Canvas*>(canvas), 1);
}
void Text::drawTextRuns(Canvas* canvas, int paintIndex) const {
auto totalMatrix = canvas->getMatrix();
for (auto& textRun : textRuns) {
auto textPaint = textRun->paints[paintIndex];
if (!textPaint) {
continue;
}
canvas->setMatrix(totalMatrix);
canvas->concat(textRun->matrix);
auto glyphs = &textRun->glyphIDs[0];
auto positions = &textRun->positions[0];
canvas->drawGlyphs(glyphs, positions, textRun->glyphIDs.size(), textRun->textFont, *textPaint);
}
canvas->setMatrix(totalMatrix);
}
2.1 绘制字形
上面代码块的17行canvas->drawGlyphs方法负责绘制每个字符
void GLCanvas::drawGlyphs(const GlyphID glyphIDs[], const Point positions[], size_t glyphCount,
const Font& font, const Paint& paint) {
auto textBlob = TextBlob::MakeFrom(glyphIDs, positions, glyphCount, font);
if (textBlob == nullptr) {
return;
}
if (font.getTypeface()->hasColor()) {
drawColorGlyphs(glyphIDs, positions, glyphCount, font, paint);
return;
}
Path path = {};
auto stroke = paint.getStyle() == PaintStyle::Stroke ? paint.getStroke() : nullptr;
if (textBlob->getPath(&path, stroke)) {
auto shader = Shader::MakeColorShader(paint.getColor());
fillPath(path, shader.get());
return;
}
drawMaskGlyphs(textBlob.get(), paint);
}
2.11 如果字体自带颜色,比如emoji等(代码块里font.getTypeface()->hasColor()的判断)
执行GLCanvas::drawColorGlyphs,通过getGlyphImage -> FTTypeface::getGlyphImage -> FTScalerContext::generateImage -> face->glyph->bitmap 获取glyph上的文字图像数据并存到PixelBuffer上,然后将PixelBuffer转化成Texture,配置一下本字符的绘制区域对应的matrix,再执行drawTexture,就完成了本字符的绘制。此时忽略字体的颜色属性,而是直接显示glyph的图像
2.12 如果字体不包含颜色信息
先通过freetype的接口 FT_Outline_Decompose( outline, func_interface, user ) 将字形转换成path, 即线段和贝塞尔曲线,然后执行GLCanvas::fillPath函数,内部会先通过path获取FT_Outline列表,然后循环执行FT_Outline_Get_Bitmap(ftLibrary, &(outline->outline), &bitmap)函数来将路径线段叠加绘制到最后的参数目标bitmap。这样就在目标bitmap绘制好了文本。把绘制好的文本转换成texture,再执行drawMask(quad, maskTexture.get(), shader),就完成了文本的着色。
这里没有和2.11一样直接使用字形的Bitmap来叠加,而是将字形先转换成path,再把path绘制到Bitmap。这样做的好处是,可以对中间数据path应用PathEffect,来变换path,从而实现描边/虚线/圆角等效果
2.13 描边效果
void Text::draw(Canvas* canvas, RenderCache*) const {
//第一行是以填充的模式绘制字形轮廓,即实心的线条;设置的代码为textRun->paints[0] = CreateFillPaint(firstGlyph).release();
drawTextRuns(static_cast<Canvas*>(canvas), 0);
//第二行是以描边的模式绘制字形,即空心的线条,对应的行是textRun->paints[1] = CreateStrokePaint(firstGlyph).release();
drawTextRuns(static_cast<Canvas*>(canvas), 1);
//两者叠加就是文字的描边效果了
}
描边效果是通过 pathEffect = PathEffect::MakeStroke(*stroke)来实现。
此effect会将原始path通过SkPaint::getFillPath的方法转换成有stroke效果的path。
文字颜色是通过GLConstColorProcessor类来处理;Emoji的纹理绘制是GLTextureFragmentProcessor类来处理。
至此,基本梳理了LibPag里文字渲染和动画的流程
PS:
- LibPag通过contentVersion/FrameCache/TransformCache/ContentCache/excludeVaryingRanges等机制对绘制过程做了缓存, 极大的提高了绘制效率
- 增加自定义文字动画的方式:
动画的执行代码:
for (auto animator : *animators) {
TextAnimatorRenderer animatorRenderer(animator, textDocument, count, layerFrame);
animatorRenderer.apply(glyphList);
}
所以自定义的动画属性,需要适配这两行代码。可以是扩展TextAnimator类,然后析构函数/excludeVaryingRanges/verify等方法都要对应更新
网友评论