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

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;//记录哪些属性被改变了,以便更新相关的缓存  
};  

字体绘制基本流程

字体绘制基本流程

SkCanvas绘制文字和下划线

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);  
    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产生:

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:

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:

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宏

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字体绘制