文字绘制主要包括编码转换(主要是中文)、字形解析(点线或image)和实际渲染三个步骤。在这个过程中,字形解析和实际渲染均是耗时步骤。Skia对文字解析的结果做了一套缓存机制。在中文字较多,使用多种字体,绘制的样式(粗/斜体)有变化时,这个缓存会变得很大,因此Skia文字缓存做了内存上的限制。
1、SkPaint
文字绘制与SkPaint的属性相关很大,先回头看下SkPaint相关的属性
class SkPaint
{
private
SkTypeface* fTypeface;//字体
SkPathEffect* fPathEffect;//路径绘制效果
SkShader* fShader;//取色器
SkXfermode* fXfermode;//混合模式,类似OpenGL里面的Blend设置
SkColorFilter* fColorFilter;//图像绘制时,自定义图像采样函数时使用
SkMaskFilter* fMaskFilter;//路径绘制时,按有无像素做进一步自定义改进处理时使用
SkRasterizer* fRasterizer;//路径绘制时自定义生成像素点的算法时使用
SkDrawLooper* fLooper;//循环绘制,SkCanvas里面的第二重循环,一般不用关注
SkImageFilter* fImageFilter;//SkCanvas的第一重循环,绘制后做后处理用,一般不用关注
SkAnnotation* fAnnotation;//暂时没用到的属性
SkScalar fTextSize;//文字大小
SkScalar fTextScaleX;//文字水平方向上的拉伸,仅用于PDF绘制
SkScalar fTextSkewX;//文字横向扭曲度,仅用于PDF绘制
SkColor fColor;//纯色,在fShader为空时使用
SkScalar fWidth;//带边界时(kStroke_Style/kStrokeAndFill_Style)生效,边界的宽度
SkScalar fMiterLimit;//drawPath时,连接各个path片断时,要求的圆滑连接阈值,Join 类型为默认的kMiter_Join时无效
/*一组不超过32位的属性*/
union {
struct {
// all of these bitfields should add up to 32
unsigned fFlags : 16;//包含所有的0/1二值属性:
/*
kAntiAlias_Flag = 0x01,//是否抗锯齿
kDither_Flag = 0x04,//是否做抖动处理
kUnderlineText_Flag = 0x08,//是否绘制文字下划线
kStrikeThruText_Flag = 0x10,//目前未看到其作用
kFakeBoldText_Flag = 0x20,
kLinearText_Flag = 0x40,
kSubpixelText_Flag = 0x80,//文字像素精确采样
kDevKernText_Flag = 0x100
kLCDRenderText_Flag = 0x200
kEmbeddedBitmapText_Flag = 0x400,
kAutoHinting_Flag = 0x800,
kVerticalText_Flag = 0x1000,//是否竖向绘制文字
kGenA8FromLCD_Flag = 0x2000,
kDistanceFieldTextTEMP_Flag = 0x4000,
kAllFlags = 0xFFFF
*/
unsigned fTextAlign : 2;//文字对齐方式,取值如下:
/*
enum Align {
kLeft_Align,//左对齐
kCenter_Align,//居中
kRight_Align,//右对齐
};
*/
unsigned fCapType : 2;//边界连接类型,分无连接,圆角连接,半方形连接
unsigned fJoinType : 2;//Path片断连接类型
unsigned fStyle : 2;//绘制模式,填充边界/区域
/*
enum Style {
kFill_Style, //填充区域
kStroke_Style,//绘制边界
kStrokeAndFill_Style,//填充区域并绘制边界
};
*/
unsigned fTextEncoding : 2;//文字编码格式,支持如下几种
enum TextEncoding {
kUTF8_TextEncoding,//utf-8,默认格式
kUTF16_TextEncoding,
kUTF32_TextEncoding,
kGlyphID_TextEncoding
};
unsigned fHinting : 2;
unsigned fFilterLevel : 2;//在图像绘制时提到的采样质量要求
//unsigned fFreeBits : 2;
};
uint32_t fBitfields;
};
uint32_t fDirtyBits;//记录哪些属性被改变了,以便更新相关的缓存
};
2、字体绘制基本流程
绘制文字和下划线
SkDraw
两种绘制方式:
(1)将文字解析为路径,然后绘制路径,缓存路径(drawText_asPaths)。
void SkDraw::drawText_asPaths(const char text[], size_t byteLength,
SkScalar x, SkScalar y,
const SkPaint& paint) const {
SkDEBUGCODE(this->validate();)
SkTextToPathIter iter(text, byteLength, paint, true);
SkMatrix matrix;
matrix.setScale(iter.getPathScale(), iter.getPathScale());
matrix.postTranslate(x, y);
const SkPath* iterPath;
SkScalar xpos, prevXPos = 0;
while (iter.next(&iterPath, &xpos)) {
matrix.postTranslate(xpos - prevXPos, 0);
if (iterPath) {
const SkPaint& pnt = iter.getPaint();
if (fDevice) {
fDevice->drawPath(*this, *iterPath, pnt, &matrix, false);
} else {
this->drawPath(*iterPath, pnt, &matrix, false);
}
}
prevXPos = xpos;
}
}
(2)将文字解析为Mask(32*32的A8图片),然后绘制模板,缓存模板。
SkDrawCacheProc glyphCacheProc = paint.getDrawCacheProc();
SkAutoGlyphCache autoCache(paint, &fDevice->fLeakyProperties, fMatrix);
SkGlyphCache* cache = autoCache.getCache();
// transform our starting point
{
SkPoint loc;
fMatrix->mapXY(x, y, &loc);