文字绘制主要包括编码转换(主要是中文)、字形解析(点线或image)和实际渲染三个步骤。在这个过程中,字形解析和实际渲染均是耗时步骤。Skia对文字解析的结果做了一套缓存机制。在中文字较多,使用多种字体,绘制的样式(粗/斜体)有变化时,这个缓存会变得很大,因此Skia文字缓存做了内存上的限制。

SkPaint

文字绘制与SkPaint的属性相关很大,先回头看下SkPaint相关的属性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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;//记录哪些属性被改变了,以便更新相关的缓存  
};  

字体绘制基本流程

字体绘制基本流程

SkCanvas绘制文字和下划线

SkDraw两种绘制方式:

(1)将文字解析为路径,然后绘制路径,缓存路径(drawText_asPaths)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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图片),然后绘制模板,缓存模板。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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);  
    x = loc.fX;  
    y = loc.fY;  
}  
  
// need to measure first  
if (paint.getTextAlign() != SkPaint::kLeft_Align) {  
    SkVector    stop;  
  
    measure_text(cache, glyphCacheProc, text, byteLength, &stop);  
  
    SkScalar    stopX = stop.fX;  
    SkScalar    stopY = stop.fY;  
  
    if (paint.getTextAlign() == SkPaint::kCenter_Align) {  
        stopX = SkScalarHalf(stopX);  
        stopY = SkScalarHalf(stopY);  
    }  
    x -= stopX;  
    y -= stopY;  
}  
  
const char* stop = text + byteLength;  
  
SkAAClipBlitter     aaBlitter;  
SkAutoBlitterChoose blitterChooser;  
SkBlitter*          blitter = NULL;  
if (needsRasterTextBlit(*this)) {  
    blitterChooser.choose(*fBitmap, *fMatrix, paint);  
    blitter = blitterChooser.get();  
    if (fRC->isAA()) {  
        aaBlitter.init(blitter, &fRC->aaRgn());  
        blitter = &aaBlitter;  
    }  
}  
  
SkAutoKern          autokern;  
SkDraw1Glyph        d1g;  
SkDraw1Glyph::Proc  proc = d1g.init(this, blitter, cache, paint);  
  
SkFixed fxMask = ~0;  
SkFixed fyMask = ~0;  
if (cache->isSubpixel()) {  
    SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(*fMatrix);  
    if (kX_SkAxisAlignment == baseline) {  
        fyMask = 0;  
        d1g.fHalfSampleY = SK_FixedHalf;  
    } else if (kY_SkAxisAlignment == baseline) {  
        fxMask = 0;  
        d1g.fHalfSampleX = SK_FixedHalf;  
    }  
}  
  
SkFixed fx = SkScalarToFixed(x) + d1g.fHalfSampleX;  
SkFixed fy = SkScalarToFixed(y) + d1g.fHalfSampleY;  
  
while (text < stop) {  
    const SkGlyph& glyph = glyphCacheProc(cache, &text, fx & fxMask, fy & fyMask);  
  
    fx += autokern.adjust(glyph);  
  
    if (glyph.fWidth) {  
        proc(d1g, fx, fy, glyph);  
    }  
  
    fx += glyph.fAdvanceX;  
    fy += glyph.fAdvanceY;  
}  

cacheProc是翻译字符编码的函数,由SkPaint::getDrawCacheProc产生:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
SkDrawCacheProc SkPaint::getDrawCacheProc() const {  
    static const SkDrawCacheProc gDrawCacheProcs[] = {  
        sk_getMetrics_utf8_00,  
        sk_getMetrics_utf16_00,  
        sk_getMetrics_utf32_00,  
        sk_getMetrics_glyph_00,  
  
        sk_getMetrics_utf8_xy,  
        sk_getMetrics_utf16_xy,  
        sk_getMetrics_utf32_xy,  
        sk_getMetrics_glyph_xy  
    };  
  
    unsigned index = this->getTextEncoding();  
    if (fFlags & kSubpixelText_Flag) {  
        index += 4;  
    }  
  
    SkASSERT(index < SK_ARRAY_COUNT(gDrawCacheProcs));  
    return gDrawCacheProcs[index];  
}  

SkGlyphCache:字形解析的结果缓存。

SkScalerContext:负责字形的解析,有多种实现。Android中是用FreeType:SkScalerContext_FreeType。主要是generateImage和generatePath两个方法:

generateImage:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) {  
    SkAutoMutexAcquire  ac(gFTMutex);  
  
    FT_Error    err;  
  
    if (this->setupSize()) {  
        goto ERROR;  
    }  
  
    err = FT_Load_Glyph( fFace, glyph.getGlyphID(fBaseGlyphCount), fLoadGlyphFlags);  
    if (err != 0) {  
        SkDEBUGF(("SkScalerContext_FreeType::generateImage: FT_Load_Glyph(glyph:%d width:%d height:%d rb:%d flags:%d) returned 0x%x\n",  
                    glyph.getGlyphID(fBaseGlyphCount), glyph.fWidth, glyph.fHeight, glyph.rowBytes(), fLoadGlyphFlags, err));  
    ERROR:  
        memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight);  
        return;  
    }  
  
    emboldenIfNeeded(fFace, fFace->glyph);  
    generateGlyphImage(fFace, glyph);  
}  
void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face, const SkGlyph& glyph) {  
    const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag);  
    const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag);  
  
    switch ( face->glyph->format ) {  
        case FT_GLYPH_FORMAT_OUTLINE: {  
            FT_Outline* outline = &face->glyph->outline;  
            FT_BBox     bbox;  
            FT_Bitmap   target;  
  
            int dx = 0, dy = 0;  
            if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) {  
                dx = SkFixedToFDot6(glyph.getSubXFixed());  
                dy = SkFixedToFDot6(glyph.getSubYFixed());  
                // negate dy since freetype-y-goes-up and skia-y-goes-down  
                dy = -dy;  
            }  
            FT_Outline_Get_CBox(outline, &bbox);  
            /* 
                what we really want to do for subpixel is 
                    offset(dx, dy) 
                    compute_bounds 
                    offset(bbox & !63) 
                but that is two calls to offset, so we do the following, which 
                achieves the same thing with only one offset call. 
            */  
            FT_Outline_Translate(outline, dx - ((bbox.xMin + dx) & ~63),  
                                          dy - ((bbox.yMin + dy) & ~63));  
  
            if (SkMask::kLCD16_Format == glyph.fMaskFormat) {  
                FT_Render_Glyph(face->glyph, doVert ? FT_RENDER_MODE_LCD_V : FT_RENDER_MODE_LCD);  
                SkMask mask;  
                glyph.toMask(&mask);  
                if (fPreBlend.isApplicable()) {  
                    copyFT2LCD16<true>(face->glyph->bitmap, mask, doBGR,  
                                       fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);  
                } else {  
                    copyFT2LCD16<false>(face->glyph->bitmap, mask, doBGR,  
                                        fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);  
                }  
            } else {  
                target.width = glyph.fWidth;  
                target.rows = glyph.fHeight;  
                target.pitch = glyph.rowBytes();  
                target.buffer = reinterpret_cast<uint8_t*>(glyph.fImage);  
                target.pixel_mode = compute_pixel_mode( (SkMask::Format)fRec.fMaskFormat);  
                target.num_grays = 256;  
  
                memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight);  
                FT_Outline_Get_Bitmap(face->glyph->library, outline, &target);  
            }  
        } break;  
  
        case FT_GLYPH_FORMAT_BITMAP: {  
            FT_Pixel_Mode pixel_mode = static_cast<FT_Pixel_Mode>(face->glyph->bitmap.pixel_mode);  
            SkMask::Format maskFormat = static_cast<SkMask::Format>(glyph.fMaskFormat);  
  
            // Assume that the other formats do not exist.  
            SkASSERT(FT_PIXEL_MODE_MONO == pixel_mode ||  
                     FT_PIXEL_MODE_GRAY == pixel_mode ||  
                     FT_PIXEL_MODE_BGRA == pixel_mode);  
  
            // These are the only formats this ScalerContext should request.  
            SkASSERT(SkMask::kBW_Format == maskFormat ||  
                     SkMask::kA8_Format == maskFormat ||  
                     SkMask::kARGB32_Format == maskFormat ||  
                     SkMask::kLCD16_Format == maskFormat);  
  
            if (fRec.fFlags & SkScalerContext::kEmbolden_Flag &&  
                !(face->style_flags & FT_STYLE_FLAG_BOLD))  
            {  
                FT_GlyphSlot_Own_Bitmap(face->glyph);  
                FT_Bitmap_Embolden(face->glyph->library, &face->glyph->bitmap,  
                                   kBitmapEmboldenStrength, 0);  
            }  
  
            // If no scaling needed, directly copy glyph bitmap.  
            if (glyph.fWidth == face->glyph->bitmap.width &&  
                glyph.fHeight == face->glyph->bitmap.rows &&  
                glyph.fTop == -face->glyph->bitmap_top &&  
                glyph.fLeft == face->glyph->bitmap_left)  
            {  
                SkMask dstMask;  
                glyph.toMask(&dstMask);  
                copyFTBitmap(face->glyph->bitmap, dstMask);  
                break;  
            }  
  
            // Otherwise, scale the bitmap.  
  
            // Copy the FT_Bitmap into an SkBitmap (either A8 or ARGB)  
            SkBitmap unscaledBitmap;  
            unscaledBitmap.allocPixels(SkImageInfo::Make(face->glyph->bitmap.width,  
                                                         face->glyph->bitmap.rows,  
                                                         SkColorType_for_FTPixelMode(pixel_mode),  
                                                         kPremul_SkAlphaType));  
  
            SkMask unscaledBitmapAlias;  
            unscaledBitmapAlias.fImage = reinterpret_cast<uint8_t*>(unscaledBitmap.getPixels());  
            unscaledBitmapAlias.fBounds.set(0, 0, unscaledBitmap.width(), unscaledBitmap.height());  
            unscaledBitmapAlias.fRowBytes = unscaledBitmap.rowBytes();  
            unscaledBitmapAlias.fFormat = SkMaskFormat_for_SkColorType(unscaledBitmap.colorType());  
            copyFTBitmap(face->glyph->bitmap, unscaledBitmapAlias);  
  
            // Wrap the glyph's mask in a bitmap, unless the glyph's mask is BW or LCD.  
            // BW requires an A8 target for resizing, which can then be down sampled.  
            // LCD should use a 4x A8 target, which will then be down sampled.  
            // For simplicity, LCD uses A8 and is replicated.  
            int bitmapRowBytes = 0;  
            if (SkMask::kBW_Format != maskFormat && SkMask::kLCD16_Format != maskFormat) {  
                bitmapRowBytes = glyph.rowBytes();  
            }  
            SkBitmap dstBitmap;  
            dstBitmap.setInfo(SkImageInfo::Make(glyph.fWidth, glyph.fHeight,  
                                                SkColorType_for_SkMaskFormat(maskFormat),  
                                                kPremul_SkAlphaType),  
                              bitmapRowBytes);  
            if (SkMask::kBW_Format == maskFormat || SkMask::kLCD16_Format == maskFormat) {  
                dstBitmap.allocPixels();  
            } else {  
                dstBitmap.setPixels(glyph.fImage);  
            }  
  
            // Scale unscaledBitmap into dstBitmap.  
            SkCanvas canvas(dstBitmap);  
            canvas.clear(SK_ColorTRANSPARENT);  
            canvas.scale(SkIntToScalar(glyph.fWidth) / SkIntToScalar(face->glyph->bitmap.width),  
                         SkIntToScalar(glyph.fHeight) / SkIntToScalar(face->glyph->bitmap.rows));  
            SkPaint paint;  
            paint.setFilterLevel(SkPaint::kMedium_FilterLevel);  
            canvas.drawBitmap(unscaledBitmap, 0, 0, &paint);  
  
            // If the destination is BW or LCD, convert from A8.  
            if (SkMask::kBW_Format == maskFormat) {  
                // Copy the A8 dstBitmap into the A1 glyph.fImage.  
                SkMask dstMask;  
                glyph.toMask(&dstMask);  
                packA8ToA1(dstMask, dstBitmap.getAddr8(0, 0), dstBitmap.rowBytes());  
            } else if (SkMask::kLCD16_Format == maskFormat) {  
                // Copy the A8 dstBitmap into the LCD16 glyph.fImage.  
                uint8_t* src = dstBitmap.getAddr8(0, 0);  
                uint16_t* dst = reinterpret_cast<uint16_t*>(glyph.fImage);  
                for (int y = dstBitmap.height(); y --> 0;) {  
                    for (int x = 0; x < dstBitmap.width(); ++x) {  
                        dst[x] = grayToRGB16(src[x]);  
                    }  
                    dst = (uint16_t*)((char*)dst + glyph.rowBytes());  
                    src += dstBitmap.rowBytes();  
                }  
            }  
  
        } break;  
  
        default:  
            SkDEBUGFAIL("unknown glyph format");  
            memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight);  
            return;  
    }  
  
// We used to always do this pre-USE_COLOR_LUMINANCE, but with colorlum,  
// it is optional  
#if defined(SK_GAMMA_APPLY_TO_A8)  
    if (SkMask::kA8_Format == glyph.fMaskFormat && fPreBlend.isApplicable()) {  
        uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage;  
        unsigned rowBytes = glyph.rowBytes();  
  
        for (int y = glyph.fHeight - 1; y >= 0; --y) {  
            for (int x = glyph.fWidth - 1; x >= 0; --x) {  
                dst[x] = fPreBlend.fG[dst[x]];  
            }  
            dst += rowBytes;  
        }  
    }  
#endif  
}  

generatePath:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void SkScalerContext_FreeType::generatePath(const SkGlyph& glyph,  
                                            SkPath* path) {  
    SkAutoMutexAcquire  ac(gFTMutex);  
  
    SkASSERT(&glyph && path);  
  
    if (this->setupSize()) {  
        path->reset();  
        return;  
    }  
  
    uint32_t flags = fLoadGlyphFlags;  
    flags |= FT_LOAD_NO_BITMAP; // ignore embedded bitmaps so we're sure to get the outline  
    flags &= ~FT_LOAD_RENDER;   // don't scan convert (we just want the outline)  
  
    FT_Error err = FT_Load_Glyph( fFace, glyph.getGlyphID(fBaseGlyphCount), flags);  
  
    if (err != 0) {  
        SkDEBUGF(("SkScalerContext_FreeType::generatePath: FT_Load_Glyph(glyph:%d flags:%d) returned 0x%x\n",  
                    glyph.getGlyphID(fBaseGlyphCount), flags, err));  
        path->reset();  
        return;  
    }  
    emboldenIfNeeded(fFace, fFace->glyph);  
  
    generateGlyphPath(fFace, path);  
  
    // The path's origin from FreeType is always the horizontal layout origin.  
    // Offset the path so that it is relative to the vertical origin if needed.  
    if (fRec.fFlags & SkScalerContext::kVertical_Flag) {  
        FT_Vector vector;  
        vector.x = fFace->glyph->metrics.vertBearingX - fFace->glyph->metrics.horiBearingX;  
        vector.y = -fFace->glyph->metrics.vertBearingY - fFace->glyph->metrics.horiBearingY;  
        FT_Vector_Transform(&vector, &fMatrix22);  
        path->offset(SkFDot6ToScalar(vector.x), -SkFDot6ToScalar(vector.y));  
    }  
}  

字体缓存管理

SkTypeface是Skia中的字体类,对应可有多种字体库解析实现。

SkTypeface 记录一个字体的id,在使用时,到链表中查出相关的字体。

对一个字体和样式,建一个 SkGlyphCache缓存,内含一个 SkScalerContext 和一个 SkGlyph 的哈希表,SkGlyph 缓存一个字体中一个字解析出来的位图。此有内存容量限制,当超过容量时,会清除之前缓存的位图。Hash冲突时,直接生成新字形替换原来的字形。

缓存限制的内存宏详见:src/core/SkGlyphCache_Globals.h。和include/core/SkUserConfig.h中的SK_DEFAULT_FONT_CACHE_LIMIT宏

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct SkGlyph {  
    void*       fImage;  
    SkPath*     fPath;  
    SkFixed     fAdvanceX, fAdvanceY;  
  
    uint32_t    fID;  
    uint16_t    fWidth, fHeight;  
    int16_t     fTop, fLeft;  
  
    void*       fDistanceField;  
    uint8_t     fMaskFormat;  
    int8_t      fRsbDelta, fLsbDelta;  // used by auto-kerning  
};  

当绘制字体只绘边界或者位图缓存机制不好处理时,将字体解析成点线,构成SkPath,也做缓存。

SKIA字体绘制