/* * Copyright 2006-2012 The Android Open Source Project * Copyright 2012 Mozilla Foundation * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkBitmap.h" #include "SkCanvas.h" #include "SkColor.h" #include "SkColorData.h" #include "SkFDot6.h" #include "SkFontHost_FreeType_common.h" #include "SkPath.h" #include "SkTo.h" #include #include #include FT_FREETYPE_H #include FT_BITMAP_H #include FT_IMAGE_H #include FT_OUTLINE_H // In the past, FT_GlyphSlot_Own_Bitmap was defined in this header file. #include FT_SYNTHESIS_H // FT_LOAD_COLOR and the corresponding FT_Pixel_Mode::FT_PIXEL_MODE_BGRA // were introduced in FreeType 2.5.0. // The following may be removed once FreeType 2.5.0 is required to build. #ifndef FT_LOAD_COLOR # define FT_LOAD_COLOR ( 1L << 20 ) # define FT_PIXEL_MODE_BGRA 7 #endif //#define SK_SHOW_TEXT_BLIT_COVERAGE #ifdef SK_DEBUG const char* SkTraceFtrGetError(int e) { switch ((FT_Error)e) { #undef FTERRORS_H_ #define FT_ERRORDEF( e, v, s ) case v: return s; #define FT_ERROR_START_LIST #define FT_ERROR_END_LIST #include FT_ERRORS_H #undef FT_ERRORDEF #undef FT_ERROR_START_LIST #undef FT_ERROR_END_LIST default: return ""; } } #endif // SK_DEBUG namespace { FT_Pixel_Mode compute_pixel_mode(SkMask::Format format) { switch (format) { case SkMask::kBW_Format: return FT_PIXEL_MODE_MONO; case SkMask::kA8_Format: default: return FT_PIXEL_MODE_GRAY; } } /////////////////////////////////////////////////////////////////////////////// uint16_t packTriple(U8CPU r, U8CPU g, U8CPU b) { #ifdef SK_SHOW_TEXT_BLIT_COVERAGE r = SkTMax(r, (U8CPU)0x40); g = SkTMax(g, (U8CPU)0x40); b = SkTMax(b, (U8CPU)0x40); #endif return SkPack888ToRGB16(r, g, b); } uint16_t grayToRGB16(U8CPU gray) { #ifdef SK_SHOW_TEXT_BLIT_COVERAGE gray = SkTMax(gray, (U8CPU)0x40); #endif return SkPack888ToRGB16(gray, gray, gray); } int bittst(const uint8_t data[], int bitOffset) { SkASSERT(bitOffset >= 0); int lowBit = data[bitOffset >> 3] >> (~bitOffset & 7); return lowBit & 1; } /** * Copies a FT_Bitmap into an SkMask with the same dimensions. * * FT_PIXEL_MODE_MONO * FT_PIXEL_MODE_GRAY * FT_PIXEL_MODE_LCD * FT_PIXEL_MODE_LCD_V */ template void copyFT2LCD16(const FT_Bitmap& bitmap, const SkMask& mask, int lcdIsBGR, const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) { SkASSERT(SkMask::kLCD16_Format == mask.fFormat); if (FT_PIXEL_MODE_LCD != bitmap.pixel_mode) { SkASSERT(mask.fBounds.width() == static_cast(bitmap.width)); } if (FT_PIXEL_MODE_LCD_V != bitmap.pixel_mode) { SkASSERT(mask.fBounds.height() == static_cast(bitmap.rows)); } const uint8_t* src = bitmap.buffer; uint16_t* dst = reinterpret_cast(mask.fImage); const size_t dstRB = mask.fRowBytes; const int width = mask.fBounds.width(); const int height = mask.fBounds.height(); switch (bitmap.pixel_mode) { case FT_PIXEL_MODE_MONO: for (int y = height; y --> 0;) { for (int x = 0; x < width; ++x) { dst[x] = -bittst(src, x); } dst = (uint16_t*)((char*)dst + dstRB); src += bitmap.pitch; } break; case FT_PIXEL_MODE_GRAY: for (int y = height; y --> 0;) { for (int x = 0; x < width; ++x) { dst[x] = grayToRGB16(src[x]); } dst = (uint16_t*)((char*)dst + dstRB); src += bitmap.pitch; } break; case FT_PIXEL_MODE_LCD: SkASSERT(3 * mask.fBounds.width() == static_cast(bitmap.width)); for (int y = height; y --> 0;) { const uint8_t* triple = src; if (lcdIsBGR) { for (int x = 0; x < width; x++) { dst[x] = packTriple(sk_apply_lut_if(triple[2], tableR), sk_apply_lut_if(triple[1], tableG), sk_apply_lut_if(triple[0], tableB)); triple += 3; } } else { for (int x = 0; x < width; x++) { dst[x] = packTriple(sk_apply_lut_if(triple[0], tableR), sk_apply_lut_if(triple[1], tableG), sk_apply_lut_if(triple[2], tableB)); triple += 3; } } src += bitmap.pitch; dst = (uint16_t*)((char*)dst + dstRB); } break; case FT_PIXEL_MODE_LCD_V: SkASSERT(3 * mask.fBounds.height() == static_cast(bitmap.rows)); for (int y = height; y --> 0;) { const uint8_t* srcR = src; const uint8_t* srcG = srcR + bitmap.pitch; const uint8_t* srcB = srcG + bitmap.pitch; if (lcdIsBGR) { using std::swap; swap(srcR, srcB); } for (int x = 0; x < width; x++) { dst[x] = packTriple(sk_apply_lut_if(*srcR++, tableR), sk_apply_lut_if(*srcG++, tableG), sk_apply_lut_if(*srcB++, tableB)); } src += 3 * bitmap.pitch; dst = (uint16_t*)((char*)dst + dstRB); } break; default: SkDEBUGF("FT_Pixel_Mode %d", bitmap.pixel_mode); SkDEBUGFAIL("unsupported FT_Pixel_Mode for LCD16"); break; } } /** * Copies a FT_Bitmap into an SkMask with the same dimensions. * * Yes, No, Never Requested, Never Produced * * kBW kA8 k3D kARGB32 kLCD16 * FT_PIXEL_MODE_MONO Y Y NR N Y * FT_PIXEL_MODE_GRAY N Y NR N Y * FT_PIXEL_MODE_GRAY2 NP NP NR NP NP * FT_PIXEL_MODE_GRAY4 NP NP NR NP NP * FT_PIXEL_MODE_LCD NP NP NR NP NP * FT_PIXEL_MODE_LCD_V NP NP NR NP NP * FT_PIXEL_MODE_BGRA N N NR Y N * * TODO: All of these N need to be Y or otherwise ruled out. */ void copyFTBitmap(const FT_Bitmap& srcFTBitmap, SkMask& dstMask) { SkASSERTF(dstMask.fBounds.width() == static_cast(srcFTBitmap.width), "dstMask.fBounds.width() = %d\n" "static_cast(srcFTBitmap.width) = %d", dstMask.fBounds.width(), static_cast(srcFTBitmap.width) ); SkASSERTF(dstMask.fBounds.height() == static_cast(srcFTBitmap.rows), "dstMask.fBounds.height() = %d\n" "static_cast(srcFTBitmap.rows) = %d", dstMask.fBounds.height(), static_cast(srcFTBitmap.rows) ); const uint8_t* src = reinterpret_cast(srcFTBitmap.buffer); const FT_Pixel_Mode srcFormat = static_cast(srcFTBitmap.pixel_mode); // FT_Bitmap::pitch is an int and allowed to be negative. const int srcPitch = srcFTBitmap.pitch; const size_t srcRowBytes = SkTAbs(srcPitch); uint8_t* dst = dstMask.fImage; const SkMask::Format dstFormat = static_cast(dstMask.fFormat); const size_t dstRowBytes = dstMask.fRowBytes; const size_t width = srcFTBitmap.width; const size_t height = srcFTBitmap.rows; if (SkMask::kLCD16_Format == dstFormat) { copyFT2LCD16(srcFTBitmap, dstMask, false, nullptr, nullptr, nullptr); return; } if ((FT_PIXEL_MODE_MONO == srcFormat && SkMask::kBW_Format == dstFormat) || (FT_PIXEL_MODE_GRAY == srcFormat && SkMask::kA8_Format == dstFormat)) { size_t commonRowBytes = SkTMin(srcRowBytes, dstRowBytes); for (size_t y = height; y --> 0;) { memcpy(dst, src, commonRowBytes); src += srcPitch; dst += dstRowBytes; } } else if (FT_PIXEL_MODE_MONO == srcFormat && SkMask::kA8_Format == dstFormat) { for (size_t y = height; y --> 0;) { uint8_t byte = 0; int bits = 0; const uint8_t* src_row = src; uint8_t* dst_row = dst; for (size_t x = width; x --> 0;) { if (0 == bits) { byte = *src_row++; bits = 8; } *dst_row++ = byte & 0x80 ? 0xff : 0x00; bits--; byte <<= 1; } src += srcPitch; dst += dstRowBytes; } } else if (FT_PIXEL_MODE_BGRA == srcFormat && SkMask::kARGB32_Format == dstFormat) { // FT_PIXEL_MODE_BGRA is pre-multiplied. for (size_t y = height; y --> 0;) { const uint8_t* src_row = src; SkPMColor* dst_row = reinterpret_cast(dst); for (size_t x = 0; x < width; ++x) { uint8_t b = *src_row++; uint8_t g = *src_row++; uint8_t r = *src_row++; uint8_t a = *src_row++; *dst_row++ = SkPackARGB32(a, r, g, b); #ifdef SK_SHOW_TEXT_BLIT_COVERAGE *(dst_row-1) = SkFourByteInterp256(*(dst_row-1), SK_ColorWHITE, 0x40); #endif } src += srcPitch; dst += dstRowBytes; } } else { SkDEBUGF("FT_Pixel_Mode %d, SkMask::Format %d\n", srcFormat, dstFormat); SkDEBUGFAIL("unsupported combination of FT_Pixel_Mode and SkMask::Format"); } } inline int convert_8_to_1(unsigned byte) { SkASSERT(byte <= 0xFF); // Arbitrary decision that making the cutoff at 1/4 instead of 1/2 in general looks better. return (byte >> 6) != 0; } uint8_t pack_8_to_1(const uint8_t alpha[8]) { unsigned bits = 0; for (int i = 0; i < 8; ++i) { bits <<= 1; bits |= convert_8_to_1(alpha[i]); } return SkToU8(bits); } void packA8ToA1(const SkMask& mask, const uint8_t* src, size_t srcRB) { const int height = mask.fBounds.height(); const int width = mask.fBounds.width(); const int octs = width >> 3; const int leftOverBits = width & 7; uint8_t* dst = mask.fImage; const int dstPad = mask.fRowBytes - SkAlign8(width)/8; SkASSERT(dstPad >= 0); const int srcPad = srcRB - width; SkASSERT(srcPad >= 0); for (int y = 0; y < height; ++y) { for (int i = 0; i < octs; ++i) { *dst++ = pack_8_to_1(src); src += 8; } if (leftOverBits > 0) { unsigned bits = 0; int shift = 7; for (int i = 0; i < leftOverBits; ++i, --shift) { bits |= convert_8_to_1(*src++) << shift; } *dst++ = bits; } src += srcPad; dst += dstPad; } } inline SkMask::Format SkMaskFormat_for_SkColorType(SkColorType colorType) { switch (colorType) { case kAlpha_8_SkColorType: return SkMask::kA8_Format; case kN32_SkColorType: return SkMask::kARGB32_Format; default: SkDEBUGFAIL("unsupported SkBitmap::Config"); return SkMask::kA8_Format; } } inline SkColorType SkColorType_for_FTPixelMode(FT_Pixel_Mode pixel_mode) { switch (pixel_mode) { case FT_PIXEL_MODE_MONO: case FT_PIXEL_MODE_GRAY: return kAlpha_8_SkColorType; case FT_PIXEL_MODE_BGRA: return kN32_SkColorType; default: SkDEBUGFAIL("unsupported FT_PIXEL_MODE"); return kAlpha_8_SkColorType; } } inline SkColorType SkColorType_for_SkMaskFormat(SkMask::Format format) { switch (format) { case SkMask::kBW_Format: case SkMask::kA8_Format: case SkMask::kLCD16_Format: return kAlpha_8_SkColorType; case SkMask::kARGB32_Format: return kN32_SkColorType; default: SkDEBUGFAIL("unsupported destination SkBitmap::Config"); return kAlpha_8_SkColorType; } } } // namespace void SkScalerContext_FreeType_Base::generateGlyphImage( FT_Face face, const SkGlyph& glyph, const SkMatrix& bitmapTransform) { 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; 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; } memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); if (SkMask::kLCD16_Format == glyph.fMaskFormat) { FT_Outline_Translate(outline, dx, dy); FT_Error err = FT_Render_Glyph(face->glyph, doVert ? FT_RENDER_MODE_LCD_V : FT_RENDER_MODE_LCD); if (err) { SK_TRACEFTR(err, "Could not render glyph %x.", face->glyph); return; } SkMask mask; glyph.toMask(&mask); #ifdef SK_SHOW_TEXT_BLIT_COVERAGE memset(mask.fImage, 0x80, mask.fBounds.height() * mask.fRowBytes); #endif FT_GlyphSlotRec& ftGlyph = *face->glyph; if (!SkIRect::Intersects(mask.fBounds, SkIRect::MakeXYWH( ftGlyph.bitmap_left, -ftGlyph.bitmap_top, ftGlyph.bitmap.width, ftGlyph.bitmap.rows))) { return; } // If the FT_Bitmap extent is larger, discard bits of the bitmap outside the mask. // If the SkMask extent is larger, shrink mask to fit bitmap (clearing discarded). unsigned char* origBuffer = ftGlyph.bitmap.buffer; // First align the top left (origin). if (-ftGlyph.bitmap_top < mask.fBounds.fTop) { int32_t topDiff = mask.fBounds.fTop - (-ftGlyph.bitmap_top); ftGlyph.bitmap.buffer += ftGlyph.bitmap.pitch * topDiff; ftGlyph.bitmap.rows -= topDiff; ftGlyph.bitmap_top = -mask.fBounds.fTop; } if (ftGlyph.bitmap_left < mask.fBounds.fLeft) { int32_t leftDiff = mask.fBounds.fLeft - ftGlyph.bitmap_left; ftGlyph.bitmap.buffer += leftDiff; ftGlyph.bitmap.width -= leftDiff; ftGlyph.bitmap_left = mask.fBounds.fLeft; } if (mask.fBounds.fTop < -ftGlyph.bitmap_top) { mask.fImage += mask.fRowBytes * (-ftGlyph.bitmap_top - mask.fBounds.fTop); mask.fBounds.fTop = -ftGlyph.bitmap_top; } if (mask.fBounds.fLeft < ftGlyph.bitmap_left) { mask.fImage += sizeof(uint16_t) * (ftGlyph.bitmap_left - mask.fBounds.fLeft); mask.fBounds.fLeft = ftGlyph.bitmap_left; } // Origins aligned, clean up the width and height. int ftVertScale = (doVert ? 3 : 1); int ftHoriScale = (doVert ? 1 : 3); if (mask.fBounds.height() * ftVertScale < SkToInt(ftGlyph.bitmap.rows)) { ftGlyph.bitmap.rows = mask.fBounds.height() * ftVertScale; } if (mask.fBounds.width() * ftHoriScale < SkToInt(ftGlyph.bitmap.width)) { ftGlyph.bitmap.width = mask.fBounds.width() * ftHoriScale; } if (SkToInt(ftGlyph.bitmap.rows) < mask.fBounds.height() * ftVertScale) { mask.fBounds.fBottom = mask.fBounds.fTop + ftGlyph.bitmap.rows / ftVertScale; } if (SkToInt(ftGlyph.bitmap.width) < mask.fBounds.width() * ftHoriScale) { mask.fBounds.fRight = mask.fBounds.fLeft + ftGlyph.bitmap.width / ftHoriScale; } if (fPreBlend.isApplicable()) { copyFT2LCD16(ftGlyph.bitmap, mask, doBGR, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } else { copyFT2LCD16(ftGlyph.bitmap, mask, doBGR, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } // Restore the buffer pointer so FreeType can properly free it. ftGlyph.bitmap.buffer = origBuffer; } else { FT_BBox bbox; FT_Bitmap target; 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)); target.width = glyph.fWidth; target.rows = glyph.fHeight; target.pitch = glyph.rowBytes(); target.buffer = reinterpret_cast(glyph.fImage); target.pixel_mode = compute_pixel_mode( (SkMask::Format)fRec.fMaskFormat); target.num_grays = 256; FT_Outline_Get_Bitmap(face->glyph->library, outline, &target); #ifdef SK_SHOW_TEXT_BLIT_COVERAGE for (int y = 0; y < glyph.fHeight; ++y) { for (int x = 0; x < glyph.fWidth; ++x) { uint8_t& a = ((uint8_t*)glyph.fImage)[(glyph.rowBytes() * y) + x]; a = SkTMax(a, 0x20); } } #endif } } break; case FT_GLYPH_FORMAT_BITMAP: { FT_Pixel_Mode pixel_mode = static_cast(face->glyph->bitmap.pixel_mode); SkMask::Format maskFormat = static_cast(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 no scaling needed, directly copy glyph bitmap. if (bitmapTransform.isIdentity()) { 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; // TODO: mark this as sRGB when the blits will be sRGB. 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(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; // TODO: mark this as sRGB when the blits will be sRGB. 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); #ifdef SK_SHOW_TEXT_BLIT_COVERAGE canvas.clear(0x33FF0000); #else canvas.clear(SK_ColorTRANSPARENT); #endif canvas.translate(-glyph.fLeft, -glyph.fTop); canvas.concat(bitmapTransform); canvas.translate(face->glyph->bitmap_left, -face->glyph->bitmap_top); SkPaint paint; // Using kMedium FilterQuality will cause mipmaps to be generated. Use // kLow when the results will be roughly the same in order to avoid // the mipmap generation cost. // See skbug.com/6967 if (bitmapTransform.getMinScale() < 0.5) { paint.setFilterQuality(kMedium_SkFilterQuality); } else { paint.setFilterQuality(kLow_SkFilterQuality); } 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(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 } /////////////////////////////////////////////////////////////////////////////// namespace { int move_proc(const FT_Vector* pt, void* ctx) { SkPath* path = (SkPath*)ctx; path->close(); // to close the previous contour (if any) path->moveTo(SkFDot6ToScalar(pt->x), -SkFDot6ToScalar(pt->y)); return 0; } int line_proc(const FT_Vector* pt, void* ctx) { SkPath* path = (SkPath*)ctx; path->lineTo(SkFDot6ToScalar(pt->x), -SkFDot6ToScalar(pt->y)); return 0; } int quad_proc(const FT_Vector* pt0, const FT_Vector* pt1, void* ctx) { SkPath* path = (SkPath*)ctx; path->quadTo(SkFDot6ToScalar(pt0->x), -SkFDot6ToScalar(pt0->y), SkFDot6ToScalar(pt1->x), -SkFDot6ToScalar(pt1->y)); return 0; } int cubic_proc(const FT_Vector* pt0, const FT_Vector* pt1, const FT_Vector* pt2, void* ctx) { SkPath* path = (SkPath*)ctx; path->cubicTo(SkFDot6ToScalar(pt0->x), -SkFDot6ToScalar(pt0->y), SkFDot6ToScalar(pt1->x), -SkFDot6ToScalar(pt1->y), SkFDot6ToScalar(pt2->x), -SkFDot6ToScalar(pt2->y)); return 0; } } // namespace bool SkScalerContext_FreeType_Base::generateGlyphPath(FT_Face face, SkPath* path) { FT_Outline_Funcs funcs; funcs.move_to = move_proc; funcs.line_to = line_proc; funcs.conic_to = quad_proc; funcs.cubic_to = cubic_proc; funcs.shift = 0; funcs.delta = 0; FT_Error err = FT_Outline_Decompose(&face->glyph->outline, &funcs, path); if (err != 0) { path->reset(); return false; } path->close(); return true; }