diff options
author | bungeman@google.com <bungeman@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2013-12-10 18:09:36 +0000 |
---|---|---|
committer | bungeman@google.com <bungeman@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81> | 2013-12-10 18:09:36 +0000 |
commit | c9a8a7e23de576ac91e9b34a221382f7c0e69813 (patch) | |
tree | 765f14b890730d1f25546ec8a03b96d88a94c4f6 /src/ports | |
parent | 45dfe6bb55828b8e819933279edc815f2fd6229a (diff) |
improve bitmap font support (FreeType only)
This commit improves SkFontHost_FreeType's support for bitmap fonts,
adding a number of features:
- Intelligent bitmap strike selection.
- Inter-strike bitmap font scaling.
- Colour bitmap font support (FreeType 2.5.0+).
BUG=
R=reed@google.com
Review URL: https://codereview.chromium.org/23684041
git-svn-id: http://skia.googlecode.com/svn/trunk@12607 2bbb7eff-a529-9590-31e7-b0007b416f81
Diffstat (limited to 'src/ports')
-rw-r--r-- | src/ports/SkFontConfigInterface_direct.cpp | 2 | ||||
-rw-r--r-- | src/ports/SkFontHost_FreeType.cpp | 334 | ||||
-rw-r--r-- | src/ports/SkFontHost_FreeType_common.cpp | 484 |
3 files changed, 596 insertions, 224 deletions
diff --git a/src/ports/SkFontConfigInterface_direct.cpp b/src/ports/SkFontConfigInterface_direct.cpp index f1ac7342dc..13993f10c0 100644 --- a/src/ports/SkFontConfigInterface_direct.cpp +++ b/src/ports/SkFontConfigInterface_direct.cpp @@ -330,11 +330,13 @@ bool IsFallbackFontAllowed(const std::string& family) { } static bool valid_pattern(FcPattern* pattern) { +#ifdef SK_FONT_CONFIG_ONLY_ALLOW_SCALABLE_FONTS FcBool is_scalable; if (FcPatternGetBool(pattern, FC_SCALABLE, 0, &is_scalable) != FcResultMatch || !is_scalable) { return false; } +#endif // fontconfig can also return fonts which are unreadable const char* c_filename = get_name(pattern, FC_FILE); diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp index fd87a66dc6..745a58a996 100644 --- a/src/ports/SkFontHost_FreeType.cpp +++ b/src/ports/SkFontHost_FreeType.cpp @@ -58,6 +58,21 @@ #include <freetype/ftsynth.h> #endif +// 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 + +// FT_HAS_COLOR and the corresponding FT_FACE_FLAG_COLOR +// were introduced in FreeType 2.5.1 +// The following may be removed once FreeType 2.5.1 is required to build. +#ifndef FT_HAS_COLOR +# define FT_HAS_COLOR(face) false +#endif + //#define ENABLE_GLYPH_SPEW // for tracing calls //#define DUMP_STRIKE_CREATION @@ -184,6 +199,7 @@ private: SkFaceRec* fFaceRec; FT_Face fFace; // reference to shared face in gFaceRecHead FT_Size fFTSize; // our own copy + FT_Int fStrikeIndex; SkFixed fScaleX, fScaleY; FT_Matrix fMatrix22; uint32_t fLoadGlyphFlags; @@ -751,6 +767,50 @@ bool SkTypeface_FreeType::onGetKerningPairAdjustments(const uint16_t glyphs[], return true; } +static FT_Int chooseBitmapStrike(FT_Face face, SkFixed scaleY) { + // early out if face is bad + if (face == NULL) { + SkDEBUGF(("chooseBitmapStrike aborted due to NULL face\n")); + return -1; + } + // determine target ppem + FT_Pos targetPPEM = SkFixedToFDot6(scaleY); + // find a bitmap strike equal to or just larger than the requested size + FT_Int chosenStrikeIndex = -1; + FT_Pos chosenPPEM = 0; + for (FT_Int strikeIndex = 0; strikeIndex < face->num_fixed_sizes; ++strikeIndex) { + FT_Pos thisPPEM = face->available_sizes[strikeIndex].y_ppem; + if (thisPPEM == targetPPEM) { + // exact match - our search stops here + chosenPPEM = thisPPEM; + chosenStrikeIndex = strikeIndex; + break; + } else if (chosenPPEM < targetPPEM) { + // attempt to increase chosenPPEM + if (thisPPEM > chosenPPEM) { + chosenPPEM = thisPPEM; + chosenStrikeIndex = strikeIndex; + } + } else { + // attempt to decrease chosenPPEM, but not below targetPPEM + if (thisPPEM < chosenPPEM && thisPPEM > targetPPEM) { + chosenPPEM = thisPPEM; + chosenStrikeIndex = strikeIndex; + } + } + } + if (chosenStrikeIndex != -1) { + // use the chosen strike + FT_Error err = FT_Select_Size(face, chosenStrikeIndex); + if (err != 0) { + SkDEBUGF(("FT_Select_Size(%s, %d) returned 0x%x\n", face->family_name, + chosenStrikeIndex, err)); + chosenStrikeIndex = -1; + } + } + return chosenStrikeIndex; +} + SkScalerContext_FreeType::SkScalerContext_FreeType(SkTypeface* typeface, const SkDescriptor* desc) : SkScalerContext_FreeType_Base(typeface, desc) { @@ -764,6 +824,7 @@ SkScalerContext_FreeType::SkScalerContext_FreeType(SkTypeface* typeface, ++gFTCount; // load the font file + fStrikeIndex = -1; fFTSize = NULL; fFace = NULL; fFaceRec = ref_ft_face(typeface); @@ -823,9 +884,9 @@ SkScalerContext_FreeType::SkScalerContext_FreeType(SkTypeface* typeface, fLCDIsVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag); // compute the flags we send to Load_Glyph + bool linearMetrics = SkToBool(fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag); { FT_Int32 loadFlags = FT_LOAD_DEFAULT; - bool linearMetrics = SkToBool(fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag); if (SkMask::kBW_Format == fRec.fMaskFormat) { // See http://code.google.com/p/chromium/issues/detail?id=43252#c24 @@ -883,42 +944,57 @@ SkScalerContext_FreeType::SkScalerContext_FreeType(SkTypeface* typeface, loadFlags |= FT_LOAD_VERTICAL_LAYOUT; } + loadFlags |= FT_LOAD_COLOR; + fLoadGlyphFlags = loadFlags; - fDoLinearMetrics = linearMetrics; } - // now create the FT_Size - - { - FT_Error err; + FT_Error err = FT_New_Size(fFace, &fFTSize); + if (err != 0) { + SkDEBUGF(("FT_New_Size returned %x for face %s\n", err, fFace->family_name)); + fFace = NULL; + return; + } + err = FT_Activate_Size(fFTSize); + if (err != 0) { + SkDEBUGF(("FT_Activate_Size(%08x, 0x%x, 0x%x) returned 0x%x\n", fFace, fScaleX, fScaleY, + err)); + fFTSize = NULL; + return; + } - err = FT_New_Size(fFace, &fFTSize); + if (FT_IS_SCALABLE(fFace)) { + err = FT_Set_Char_Size(fFace, SkFixedToFDot6(fScaleX), SkFixedToFDot6(fScaleY), 72, 72); if (err != 0) { - SkDEBUGF(("SkScalerContext_FreeType::FT_New_Size(%x): FT_Set_Char_Size(0x%x, 0x%x) returned 0x%x\n", - fFaceRec->fFontID, fScaleX, fScaleY, err)); + SkDEBUGF(("FT_Set_CharSize(%08x, 0x%x, 0x%x) returned 0x%x\n", + fFace, fScaleX, fScaleY, err)); fFace = NULL; return; } - - err = FT_Activate_Size(fFTSize); - if (err != 0) { - SkDEBUGF(("SkScalerContext_FreeType::FT_Activate_Size(%x, 0x%x, 0x%x) returned 0x%x\n", - fFaceRec->fFontID, fScaleX, fScaleY, err)); - fFTSize = NULL; - } - - err = FT_Set_Char_Size( fFace, - SkFixedToFDot6(fScaleX), SkFixedToFDot6(fScaleY), - 72, 72); - if (err != 0) { - SkDEBUGF(("SkScalerContext_FreeType::FT_Set_Char_Size(%x, 0x%x, 0x%x) returned 0x%x\n", - fFaceRec->fFontID, fScaleX, fScaleY, err)); - fFace = NULL; - return; + FT_Set_Transform(fFace, &fMatrix22, NULL); + } else if (FT_HAS_FIXED_SIZES(fFace)) { + fStrikeIndex = chooseBitmapStrike(fFace, fScaleY); + if (fStrikeIndex == -1) { + SkDEBUGF(("no glyphs for font \"%s\" size %f?\n", + fFace->family_name, SkFixedToScalar(fScaleY))); + } else { + // FreeType does no provide linear metrics for bitmap fonts. + linearMetrics = false; + + // FreeType documentation says: + // FT_LOAD_NO_BITMAP -- Ignore bitmap strikes when loading. + // Bitmap-only fonts ignore this flag. + // + // However, in FreeType 2.5.1 color bitmap only fonts do not ignore this flag. + // Force this flag off for bitmap only fonts. + fLoadGlyphFlags &= ~FT_LOAD_NO_BITMAP; } - - FT_Set_Transform( fFace, &fMatrix22, NULL); + } else { + SkDEBUGF(("unknown kind of font \"%s\" size %f?\n", + fFace->family_name, SkFixedToScalar(fScaleY))); } + + fDoLinearMetrics = linearMetrics; } SkScalerContext_FreeType::~SkScalerContext_FreeType() { @@ -932,7 +1008,6 @@ SkScalerContext_FreeType::~SkScalerContext_FreeType() { unref_ft_face(fFace); } if (--gFTCount == 0) { -// SkDEBUGF(("FT_Done_FreeType\n")); FT_Done_FreeType(gFTLibrary); SkDEBUGCODE(gFTLibrary = NULL;) } @@ -942,18 +1017,18 @@ SkScalerContext_FreeType::~SkScalerContext_FreeType() { this face with other context (at different sizes). */ FT_Error SkScalerContext_FreeType::setupSize() { - FT_Error err = FT_Activate_Size(fFTSize); - + FT_Error err = FT_Activate_Size(fFTSize); if (err != 0) { SkDEBUGF(("SkScalerContext_FreeType::FT_Activate_Size(%x, 0x%x, 0x%x) returned 0x%x\n", - fFaceRec->fFontID, fScaleX, fScaleY, err)); + fFaceRec->fFontID, fScaleX, fScaleY, err)); fFTSize = NULL; - } else { - // seems we need to reset this every time (not sure why, but without it - // I get random italics from some other fFTSize) - FT_Set_Transform( fFace, &fMatrix22, NULL); + return err; } - return err; + + // seems we need to reset this every time (not sure why, but without it + // I get random italics from some other fFTSize) + FT_Set_Transform(fFace, &fMatrix22, NULL); + return 0; } unsigned SkScalerContext_FreeType::generateGlyphCount() { @@ -1063,6 +1138,17 @@ void SkScalerContext_FreeType::updateGlyphIfLCD(SkGlyph* glyph) { } } +inline void scaleGlyphMetrics(SkGlyph& glyph, SkScalar scale) { + glyph.fWidth *= scale; + glyph.fHeight *= scale; + glyph.fTop *= scale; + glyph.fLeft *= scale; + + SkFixed fixedScale = SkScalarToFixed(scale); + glyph.fAdvanceX = SkFixedMul(glyph.fAdvanceX, fixedScale); + glyph.fAdvanceY = SkFixedMul(glyph.fAdvanceY, fixedScale); +} + void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) { SkAutoMutexAcquire ac(gFTMutex); @@ -1087,32 +1173,28 @@ void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) { } switch ( fFace->glyph->format ) { - case FT_GLYPH_FORMAT_OUTLINE: { - FT_BBox bbox; - + case FT_GLYPH_FORMAT_OUTLINE: if (0 == fFace->glyph->outline.n_contours) { glyph->fWidth = 0; glyph->fHeight = 0; glyph->fTop = 0; glyph->fLeft = 0; - break; - } - - if (fRec.fFlags & kEmbolden_Flag) { - emboldenOutline(fFace, &fFace->glyph->outline); - } - - getBBoxForCurrentGlyph(glyph, &bbox, true); + } else { + if (fRec.fFlags & kEmbolden_Flag && !(fFace->style_flags & FT_STYLE_FLAG_BOLD)) { + emboldenOutline(fFace, &fFace->glyph->outline); + } - glyph->fWidth = SkToU16(SkFDot6Floor(bbox.xMax - bbox.xMin)); - glyph->fHeight = SkToU16(SkFDot6Floor(bbox.yMax - bbox.yMin)); - glyph->fTop = -SkToS16(SkFDot6Floor(bbox.yMax)); - glyph->fLeft = SkToS16(SkFDot6Floor(bbox.xMin)); + FT_BBox bbox; + getBBoxForCurrentGlyph(glyph, &bbox, true); - updateGlyphIfLCD(glyph); + glyph->fWidth = SkToU16(SkFDot6Floor(bbox.xMax - bbox.xMin)); + glyph->fHeight = SkToU16(SkFDot6Floor(bbox.yMax - bbox.yMin)); + glyph->fTop = -SkToS16(SkFDot6Floor(bbox.yMax)); + glyph->fLeft = SkToS16(SkFDot6Floor(bbox.xMin)); + updateGlyphIfLCD(glyph); + } break; - } case FT_GLYPH_FORMAT_BITMAP: if (fRec.fFlags & kEmbolden_Flag) { @@ -1129,6 +1211,10 @@ void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) { fFace->glyph->bitmap_top += SkFDot6Floor(vector.y); } + if (fFace->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) { + glyph->fMaskFormat = SkMask::kARGB32_Format; + } + glyph->fWidth = SkToU16(fFace->glyph->bitmap.width); glyph->fHeight = SkToU16(fFace->glyph->bitmap.rows); glyph->fTop = -SkToS16(fFace->glyph->bitmap_top); @@ -1163,6 +1249,11 @@ void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) { } } + if (fFace->glyph->format == FT_GLYPH_FORMAT_BITMAP && fScaleY && fFace->size->metrics.y_ppem) { + // NOTE: both dimensions are scaled by y_ppem. this is WAI. + scaleGlyphMetrics(*glyph, SkScalarDiv(SkFixedToScalar(fScaleY), + SkIntToScalar(fFace->size->metrics.y_ppem))); + } #ifdef ENABLE_GLYPH_SPEW SkDEBUGF(("FT_Set_Char_Size(this:%p sx:%x sy:%x ", this, fScaleX, fScaleY)); @@ -1250,77 +1341,104 @@ void SkScalerContext_FreeType::generateFontMetrics(SkPaint::FontMetrics* mx, } FT_Face face = fFace; - int upem = face->units_per_EM; - if (upem <= 0) { - goto ERROR; - } - - SkPoint pts[6]; - SkFixed ys[6]; + SkScalar scaleX = fScale.x(); SkScalar scaleY = fScale.y(); - SkScalar mxy = fMatrix22Scalar.getSkewX(); - SkScalar myy = fMatrix22Scalar.getScaleY(); - SkScalar xmin = SkIntToScalar(face->bbox.xMin) / upem; - SkScalar xmax = SkIntToScalar(face->bbox.xMax) / upem; - - int leading = face->height - (face->ascender + -face->descender); - if (leading < 0) { - leading = 0; + SkScalar mxy = fMatrix22Scalar.getSkewX() * scaleY; + SkScalar myy = fMatrix22Scalar.getScaleY() * scaleY; + + // fetch units/EM from "head" table if needed (ie for bitmap fonts) + SkScalar upem = SkIntToScalar(face->units_per_EM); + if (!upem) { + TT_Header* ttHeader = (TT_Header*)FT_Get_Sfnt_Table(face, ft_sfnt_head); + if (ttHeader) { + upem = SkIntToScalar(ttHeader->Units_Per_EM); + } } - // Try to get the OS/2 table from the font. This contains the specific - // average font width metrics which Windows uses. + // use the os/2 table as a source of reasonable defaults. + SkScalar x_height = 0.0f; + SkScalar avgCharWidth = 0.0f; TT_OS2* os2 = (TT_OS2*) FT_Get_Sfnt_Table(face, ft_sfnt_os2); - - ys[0] = -face->bbox.yMax; - ys[1] = -face->ascender; - ys[2] = -face->descender; - ys[3] = -face->bbox.yMin; - ys[4] = leading; - ys[5] = os2 ? os2->xAvgCharWidth : 0; - - SkScalar x_height; - if (os2 && os2->sxHeight) { - x_height = fScale.x() * os2->sxHeight / upem; - } else { - const FT_UInt x_glyph = FT_Get_Char_Index(fFace, 'x'); - if (x_glyph) { - FT_BBox bbox; - FT_Load_Glyph(fFace, x_glyph, fLoadGlyphFlags); - if (fRec.fFlags & kEmbolden_Flag) { - emboldenOutline(fFace, &fFace->glyph->outline); + if (os2) { + x_height = scaleX * SkIntToScalar(os2->sxHeight) / upem; + avgCharWidth = SkIntToScalar(os2->xAvgCharWidth) / upem; + } + + // pull from format-specific metrics as needed + SkScalar ascent, descent, leading, xmin, xmax, ymin, ymax; + if (face->face_flags & FT_FACE_FLAG_SCALABLE) { // scalable outline font + ascent = -SkIntToScalar(face->ascender) / upem; + descent = -SkIntToScalar(face->descender) / upem; + leading = SkIntToScalar(face->height + (face->descender - face->ascender)) / upem; + xmin = SkIntToScalar(face->bbox.xMin) / upem; + xmax = SkIntToScalar(face->bbox.xMax) / upem; + ymin = -SkIntToScalar(face->bbox.yMin) / upem; + ymax = -SkIntToScalar(face->bbox.yMax) / upem; + // we may be able to synthesize x_height from outline + if (!x_height) { + const FT_UInt x_glyph = FT_Get_Char_Index(fFace, 'x'); + if (x_glyph) { + FT_BBox bbox; + FT_Load_Glyph(fFace, x_glyph, fLoadGlyphFlags); + if ((fRec.fFlags & kEmbolden_Flag) && !(fFace->style_flags & FT_STYLE_FLAG_BOLD)) { + emboldenOutline(fFace, &fFace->glyph->outline); + } + FT_Outline_Get_CBox(&fFace->glyph->outline, &bbox); + x_height = SkIntToScalar(bbox.yMax) / 64.0f; } - FT_Outline_Get_CBox(&fFace->glyph->outline, &bbox); - x_height = bbox.yMax / 64.0f; - } else { - x_height = 0; } + } else if (fStrikeIndex != -1) { // bitmap strike metrics + SkScalar xppem = SkIntToScalar(face->size->metrics.x_ppem); + SkScalar yppem = SkIntToScalar(face->size->metrics.y_ppem); + ascent = -SkIntToScalar(face->size->metrics.ascender) / (yppem * 64.0f); + descent = -SkIntToScalar(face->size->metrics.descender) / (yppem * 64.0f); + leading = (SkIntToScalar(face->size->metrics.height) / (yppem * 64.0f)) + + ascent - descent; + xmin = 0.0f; + xmax = SkIntToScalar(face->available_sizes[fStrikeIndex].width) / xppem; + ymin = descent + leading; + ymax = ascent - descent; + if (!x_height) { + x_height = -ascent; + } + if (!avgCharWidth) { + avgCharWidth = xmax - xmin; + } + } else { + goto ERROR; + } + + // synthesize elements that were not provided by the os/2 table or format-specific metrics + if (!x_height) { + x_height = -ascent; + } + if (!avgCharWidth) { + avgCharWidth = xmax - xmin; } - // convert upem-y values into scalar points - for (int i = 0; i < 6; i++) { - SkScalar y = scaleY * ys[i] / upem; - pts[i].set(y * mxy, y * myy); + // disallow negative linespacing + if (leading < 0.0f) { + leading = 0.0f; } if (mx) { - mx->fTop = pts[0].fX; - mx->fAscent = pts[1].fX; - mx->fDescent = pts[2].fX; - mx->fBottom = pts[3].fX; - mx->fLeading = pts[4].fX; - mx->fAvgCharWidth = pts[5].fX; + mx->fTop = ymax * mxy; + mx->fAscent = ascent * mxy; + mx->fDescent = descent * mxy; + mx->fBottom = ymin * mxy; + mx->fLeading = leading * mxy; + mx->fAvgCharWidth = avgCharWidth * mxy; mx->fXMin = xmin; mx->fXMax = xmax; mx->fXHeight = x_height; } if (my) { - my->fTop = pts[0].fY; - my->fAscent = pts[1].fY; - my->fDescent = pts[2].fY; - my->fBottom = pts[3].fY; - my->fLeading = pts[4].fY; - my->fAvgCharWidth = pts[5].fY; + my->fTop = ymax * myy; + my->fAscent = ascent * myy; + my->fDescent = descent * myy; + my->fBottom = ymin * myy; + my->fLeading = leading * myy; + my->fAvgCharWidth = avgCharWidth * myy; my->fXMin = xmin; my->fXMax = xmax; my->fXHeight = x_height; diff --git a/src/ports/SkFontHost_FreeType_common.cpp b/src/ports/SkFontHost_FreeType_common.cpp index 2c486847b6..065a83a41c 100644 --- a/src/ports/SkFontHost_FreeType_common.cpp +++ b/src/ports/SkFontHost_FreeType_common.cpp @@ -6,17 +6,32 @@ * found in the LICENSE file. */ +#include "SkBitmap.h" +#include "SkCanvas.h" +#include "SkColor.h" #include "SkColorPriv.h" #include "SkFDot6.h" #include "SkFontHost_FreeType_common.h" #include "SkPath.h" #include <ft2build.h> -#include FT_OUTLINE_H +#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 + static FT_Pixel_Mode compute_pixel_mode(SkMask::Format format) { switch (format) { case SkMask::kBW_Format: @@ -29,13 +44,20 @@ static FT_Pixel_Mode compute_pixel_mode(SkMask::Format format) { /////////////////////////////////////////////////////////////////////////////// -static uint16_t packTriple(unsigned r, unsigned g, unsigned b) { - return SkPackRGB16(r >> 3, g >> 2, b >> 3); +static 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); } static uint16_t grayToRGB16(U8CPU gray) { - SkASSERT(gray <= 255); - return SkPackRGB16(gray >> 3, gray >> 2, gray >> 3); +#ifdef SK_SHOW_TEXT_BLIT_COVERAGE + gray = SkTMax(gray, (U8CPU)0x40); +#endif + return SkPack888ToRGB16(gray, gray, gray); } static int bittst(const uint8_t data[], int bitOffset) { @@ -44,78 +66,271 @@ static int bittst(const uint8_t data[], int bitOffset) { 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<bool APPLY_PREBLEND> -static void copyFT2LCD16(const SkGlyph& glyph, const FT_Bitmap& bitmap, - int lcdIsBGR, bool lcdIsVert, const uint8_t* tableR, - const uint8_t* tableG, const uint8_t* tableB) { - if (lcdIsVert) { - SkASSERT(3 * glyph.fHeight == bitmap.rows); - } else { - SkASSERT(glyph.fHeight == bitmap.rows); +static 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() == bitmap.width); + } + if (FT_PIXEL_MODE_LCD_V != bitmap.pixel_mode) { + SkASSERT(mask.fBounds.height() == bitmap.rows); } - uint16_t* dst = reinterpret_cast<uint16_t*>(glyph.fImage); - const size_t dstRB = glyph.rowBytes(); - const int width = glyph.fWidth; const uint8_t* src = bitmap.buffer; + uint16_t* dst = reinterpret_cast<uint16_t*>(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 = 0; y < glyph.fHeight; ++y) { + 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 = 0; y < glyph.fHeight; ++y) { + 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; - default: { - SkASSERT(lcdIsVert || (glyph.fWidth * 3 == bitmap.width)); - for (int y = 0; y < glyph.fHeight; y++) { - if (lcdIsVert) { // vertical stripes - const uint8_t* srcR = src; - const uint8_t* srcG = srcR + bitmap.pitch; - const uint8_t* srcB = srcG + bitmap.pitch; - if (lcdIsBGR) { - SkTSwap(srcR, srcB); - } + break; + case FT_PIXEL_MODE_LCD: + SkASSERT(3 * mask.fBounds.width() == 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<APPLY_PREBLEND>(*srcR++, tableR), - sk_apply_lut_if<APPLY_PREBLEND>(*srcG++, tableG), - sk_apply_lut_if<APPLY_PREBLEND>(*srcB++, tableB)); + dst[x] = packTriple(sk_apply_lut_if<APPLY_PREBLEND>(triple[2], tableR), + sk_apply_lut_if<APPLY_PREBLEND>(triple[1], tableG), + sk_apply_lut_if<APPLY_PREBLEND>(triple[0], tableB)); + triple += 3; } - src += 3 * bitmap.pitch; - } else { // horizontal stripes - const uint8_t* triple = src; - if (lcdIsBGR) { - for (int x = 0; x < width; x++) { - dst[x] = packTriple(sk_apply_lut_if<APPLY_PREBLEND>(triple[2], tableR), - sk_apply_lut_if<APPLY_PREBLEND>(triple[1], tableG), - sk_apply_lut_if<APPLY_PREBLEND>(triple[0], tableB)); - triple += 3; - } - } else { - for (int x = 0; x < width; x++) { - dst[x] = packTriple(sk_apply_lut_if<APPLY_PREBLEND>(triple[0], tableR), - sk_apply_lut_if<APPLY_PREBLEND>(triple[1], tableG), - sk_apply_lut_if<APPLY_PREBLEND>(triple[2], tableB)); - triple += 3; - } + } else { + for (int x = 0; x < width; x++) { + dst[x] = packTriple(sk_apply_lut_if<APPLY_PREBLEND>(triple[0], tableR), + sk_apply_lut_if<APPLY_PREBLEND>(triple[1], tableG), + sk_apply_lut_if<APPLY_PREBLEND>(triple[2], tableB)); + triple += 3; } - src += bitmap.pitch; } + src += bitmap.pitch; dst = (uint16_t*)((char*)dst + dstRB); } - } break; + break; + case FT_PIXEL_MODE_LCD_V: + SkASSERT(3 * mask.fBounds.height() == 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) { + SkTSwap(srcR, srcB); + } + for (int x = 0; x < width; x++) { + dst[x] = packTriple(sk_apply_lut_if<APPLY_PREBLEND>(*srcR++, tableR), + sk_apply_lut_if<APPLY_PREBLEND>(*srcG++, tableG), + sk_apply_lut_if<APPLY_PREBLEND>(*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 kLCD32 + * FT_PIXEL_MODE_MONO Y Y NR N Y NR + * FT_PIXEL_MODE_GRAY N Y NR N Y NR + * FT_PIXEL_MODE_GRAY2 NP NP NR NP NP NR + * FT_PIXEL_MODE_GRAY4 NP NP NR NP NP NR + * FT_PIXEL_MODE_LCD NP NP NR NP NP NR + * FT_PIXEL_MODE_LCD_V NP NP NR NP NP NR + * FT_PIXEL_MODE_BGRA N N NR Y N NR + * + * TODO: All of these N need to be Y or otherwise ruled out. + */ +static void copyFTBitmap(const FT_Bitmap& srcFTBitmap, SkMask& dstMask) { + SkASSERT(dstMask.fBounds.width() == srcFTBitmap.width); + SkASSERT(dstMask.fBounds.height() == srcFTBitmap.rows); + + const uint8_t* src = reinterpret_cast<const uint8_t*>(srcFTBitmap.buffer); + const FT_Pixel_Mode srcFormat = static_cast<FT_Pixel_Mode>(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<SkMask::Format>(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<false>(srcFTBitmap, dstMask, false, NULL, NULL, NULL); + 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<SkPMColor*>(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"); + } +} + +static 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; +} + +static 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); +} + +static 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_SkBitmapConfig(SkBitmap::Config config) { + switch (config) { + case SkBitmap::kA8_Config: + return SkMask::kA8_Format; + case SkBitmap::kARGB_8888_Config: + return SkMask::kARGB32_Format; + default: + SkDEBUGFAIL("unsupported SkBitmap::Config"); + return SkMask::kA8_Format; + } +} + +inline SkBitmap::Config SkBitmapConfig_for_FTPixelMode(FT_Pixel_Mode pixel_mode) { + switch (pixel_mode) { + case FT_PIXEL_MODE_MONO: + case FT_PIXEL_MODE_GRAY: + return SkBitmap::kA8_Config; + case FT_PIXEL_MODE_BGRA: + return SkBitmap::kARGB_8888_Config; + default: + SkDEBUGFAIL("unsupported FT_PIXEL_MODE"); + return SkBitmap::kA8_Config; + } +} + +inline SkBitmap::Config SkBitmapConfig_for_SkMaskFormat(SkMask::Format format) { + switch (format) { + case SkMask::kBW_Format: + case SkMask::kA8_Format: + case SkMask::kLCD16_Format: + return SkBitmap::kA8_Config; + case SkMask::kARGB32_Format: + return SkBitmap::kARGB_8888_Config; + default: + SkDEBUGFAIL("unsupported destination SkBitmap::Config"); + return SkBitmap::kA8_Config; } } @@ -129,7 +344,8 @@ void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face, const SkGly FT_BBox bbox; FT_Bitmap target; - if (fRec.fFlags & SkScalerContext::kEmbolden_Flag) { + if (fRec.fFlags & SkScalerContext::kEmbolden_Flag && + !(face->style_flags & FT_STYLE_FLAG_BOLD)) { emboldenOutline(face, outline); } @@ -154,11 +370,13 @@ void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face, const SkGly 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>(glyph, face->glyph->bitmap, doBGR, doVert, + copyFT2LCD16<true>(face->glyph->bitmap, mask, doBGR, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } else { - copyFT2LCD16<false>(glyph, face->glyph->bitmap, doBGR, doVert, + copyFT2LCD16<false>(face->glyph->bitmap, mask, doBGR, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); } } else { @@ -166,8 +384,7 @@ void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face, const SkGly 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.pixel_mode = compute_pixel_mode( (SkMask::Format)fRec.fMaskFormat); target.num_grays = 256; memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight); @@ -176,71 +393,106 @@ void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face, const SkGly } break; case FT_GLYPH_FORMAT_BITMAP: { - if (fRec.fFlags & SkScalerContext::kEmbolden_Flag) { + 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); + FT_Bitmap_Embolden(face->glyph->library, &face->glyph->bitmap, + kBitmapEmboldenStrength, 0); } - SkASSERT_CONTINUE(glyph.fWidth == face->glyph->bitmap.width); - SkASSERT_CONTINUE(glyph.fHeight == face->glyph->bitmap.rows); - SkASSERT_CONTINUE(glyph.fTop == -face->glyph->bitmap_top); - SkASSERT_CONTINUE(glyph.fLeft == face->glyph->bitmap_left); - - const uint8_t* src = (const uint8_t*)face->glyph->bitmap.buffer; - uint8_t* dst = (uint8_t*)glyph.fImage; - - if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY || - (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO && - glyph.fMaskFormat == SkMask::kBW_Format)) { - unsigned srcRowBytes = face->glyph->bitmap.pitch; - unsigned dstRowBytes = glyph.rowBytes(); - unsigned minRowBytes = SkMin32(srcRowBytes, dstRowBytes); - unsigned extraRowBytes = dstRowBytes - minRowBytes; - - for (int y = face->glyph->bitmap.rows - 1; y >= 0; --y) { - memcpy(dst, src, minRowBytes); - memset(dst + minRowBytes, 0, extraRowBytes); - src += srcRowBytes; - dst += dstRowBytes; - } - } else if (face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO && - glyph.fMaskFormat == SkMask::kA8_Format) { - for (int y = 0; y < face->glyph->bitmap.rows; ++y) { - uint8_t byte = 0; - int bits = 0; - const uint8_t* src_row = src; - uint8_t* dst_row = dst; - - for (int x = 0; x < face->glyph->bitmap.width; ++x) { - if (!bits) { - byte = *src_row++; - bits = 8; - } - - *dst_row++ = byte & 0x80 ? 0xff : 0; - bits--; - byte <<= 1; - } - src += face->glyph->bitmap.pitch; - dst += glyph.rowBytes(); - } - } else if (SkMask::kLCD16_Format == glyph.fMaskFormat) { - if (fPreBlend.isApplicable()) { - copyFT2LCD16<true>(glyph, face->glyph->bitmap, doBGR, doVert, - fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); - } else { - copyFT2LCD16<false>(glyph, face->glyph->bitmap, doBGR, doVert, - fPreBlend.fR, fPreBlend.fG, fPreBlend.fB); - } + // 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.setConfig(SkBitmapConfig_for_FTPixelMode(pixel_mode), + face->glyph->bitmap.width, face->glyph->bitmap.rows); + unscaledBitmap.allocPixels(); + + 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_SkBitmapConfig(unscaledBitmap.config()); + 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.setConfig(SkBitmapConfig_for_SkMaskFormat(maskFormat), + glyph.fWidth, glyph.fHeight, bitmapRowBytes); + if (SkMask::kBW_Format == maskFormat || SkMask::kLCD16_Format == maskFormat) { + dstBitmap.allocPixels(); } else { - SkDEBUGFAIL("unknown glyph bitmap transform needed"); + 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::kLow_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; + 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, |