Rotate bitmap strikes with FreeType.

BUG=skia:3490
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2139703002

Committed: https://skia.googlesource.com/skia/+/31e0c1379e6d0ce48196183e295b929af51fa74e
Review-Url: https://codereview.chromium.org/2139703002
diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp
index 2e0b8b6..d0f86d9 100644
--- a/src/ports/SkFontHost_FreeType.cpp
+++ b/src/ports/SkFontHost_FreeType.cpp
@@ -197,18 +197,23 @@
     SkUnichar generateGlyphToChar(uint16_t glyph) override;
 
 private:
-    FT_Face     fFace;              // reference to shared face in gFaceRecHead
-    FT_Size     fFTSize;            // our own copy
-    FT_Int      fStrikeIndex;
-    FT_F26Dot6  fScaleX, fScaleY;
-    FT_Matrix   fMatrix22;
-    uint32_t    fLoadGlyphFlags;
-    bool        fDoLinearMetrics;
-    bool        fLCDIsVert;
+    FT_Face   fFace;  // Shared face from gFaceRecHead.
+    FT_Size   fFTSize;  // The size on the fFace for this scaler.
+    FT_Int    fStrikeIndex;
 
-    // Need scalar versions for generateFontMetrics
-    SkVector    fScale;
-    SkMatrix    fMatrix22Scalar;
+    /** The rest of the matrix after FreeType handles the size.
+     *  With outline font rasterization this is handled by FreeType with FT_Set_Transform.
+     *  With bitmap only fonts this matrix must be applied to scale the bitmap.
+     */
+    SkMatrix  fMatrix22Scalar;
+    /** Same as fMatrix22Scalar, but in FreeType units and space. */
+    FT_Matrix fMatrix22;
+    /** The actual size requested. */
+    SkVector  fScale;
+
+    uint32_t  fLoadGlyphFlags;
+    bool      fDoLinearMetrics;
+    bool      fLCDIsVert;
 
     FT_Error setupSize();
     void getBBoxForCurrentGlyph(SkGlyph* glyph, FT_BBox* bbox,
@@ -219,6 +224,7 @@
     // Caller must lock gFTMutex before calling this function.
     // update FreeType2 glyph slot with glyph emboldened
     void emboldenIfNeeded(FT_Face face, FT_GlyphSlot glyph);
+    bool shouldSubpixelBitmap(const SkGlyph&, const SkMatrix&);
 };
 
 ///////////////////////////////////////////////////////////////////////////
@@ -747,47 +753,35 @@
     return true;
 }
 
+/** Returns the bitmap strike equal to or just larger than the requested size. */
 static FT_Int chooseBitmapStrike(FT_Face face, FT_F26Dot6 scaleY) {
-    // early out if face is bad
     if (face == nullptr) {
-        SkDEBUGF(("chooseBitmapStrike aborted due to nullptr face\n"));
+        SkDEBUGF(("chooseBitmapStrike aborted due to nullptr face.\n"));
         return -1;
     }
-    // determine target ppem
-    FT_Pos targetPPEM = scaleY;
-    // find a bitmap strike equal to or just larger than the requested size
+
+    FT_Pos requestedPPEM = scaleY;  // FT_Bitmap_Size::y_ppem is in 26.6 format.
     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) {
+        FT_Pos strikePPEM = face->available_sizes[strikeIndex].y_ppem;
+        if (strikePPEM == requestedPPEM) {
             // exact match - our search stops here
-            chosenPPEM = thisPPEM;
-            chosenStrikeIndex = strikeIndex;
-            break;
-        } else if (chosenPPEM < targetPPEM) {
+            return strikeIndex;
+        } else if (chosenPPEM < requestedPPEM) {
             // attempt to increase chosenPPEM
-            if (thisPPEM > chosenPPEM) {
-                chosenPPEM = thisPPEM;
+            if (chosenPPEM < strikePPEM) {
+                chosenPPEM = strikePPEM;
                 chosenStrikeIndex = strikeIndex;
             }
         } else {
-            // attempt to decrease chosenPPEM, but not below targetPPEM
-            if (thisPPEM < chosenPPEM && thisPPEM > targetPPEM) {
-                chosenPPEM = thisPPEM;
+            // attempt to decrease chosenPPEM, but not below requestedPPEM
+            if (requestedPPEM < strikePPEM && strikePPEM < chosenPPEM) {
+                chosenPPEM = strikePPEM;
                 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;
 }
 
@@ -814,14 +808,12 @@
     }
 
     fRec.computeMatrices(SkScalerContextRec::kFull_PreMatrixScale, &fScale, &fMatrix22Scalar);
-    fMatrix22Scalar.setSkewX(-fMatrix22Scalar.getSkewX());
-    fMatrix22Scalar.setSkewY(-fMatrix22Scalar.getSkewY());
 
-    fScaleX = SkScalarToFDot6(fScale.fX);
-    fScaleY = SkScalarToFDot6(fScale.fY);
+    FT_F26Dot6 scaleX = SkScalarToFDot6(fScale.fX);
+    FT_F26Dot6 scaleY = SkScalarToFDot6(fScale.fY);
     fMatrix22.xx = SkScalarToFixed(fMatrix22Scalar.getScaleX());
-    fMatrix22.xy = SkScalarToFixed(fMatrix22Scalar.getSkewX());
-    fMatrix22.yx = SkScalarToFixed(fMatrix22Scalar.getSkewY());
+    fMatrix22.xy = SkScalarToFixed(-fMatrix22Scalar.getSkewX());
+    fMatrix22.yx = SkScalarToFixed(-fMatrix22Scalar.getSkewY());
     fMatrix22.yy = SkScalarToFixed(fMatrix22Scalar.getScaleY());
 
     fLCDIsVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag);
@@ -900,7 +892,7 @@
         FT_Size size;
         FT_Error err = FT_New_Size(ftFace.get(), &size);
         if (err != 0) {
-            SkDEBUGF(("FT_New_Size returned %x for face %s\n", err, ftFace->family_name));
+            SkDEBUGF(("FT_New_Size(%s) returned 0x%x.\n", ftFace->family_name, err));
             return nullptr;
         }
         return size;
@@ -912,39 +904,54 @@
 
     FT_Error err = FT_Activate_Size(ftSize.get());
     if (err != 0) {
-        SkDEBUGF(("FT_Activate_Size(%08x, 0x%x, 0x%x) returned 0x%x\n",
-                         ftFace.get(), fScaleX,   fScaleY,       err));
+        SkDEBUGF(("FT_Activate_Size(%s) returned 0x%x.\n", ftFace->family_name, err));
         return;
     }
 
     if (FT_IS_SCALABLE(ftFace)) {
-        err = FT_Set_Char_Size(ftFace.get(), fScaleX, fScaleY, 72, 72);
+        err = FT_Set_Char_Size(ftFace.get(), scaleX, scaleY, 72, 72);
         if (err != 0) {
-            SkDEBUGF(("FT_Set_CharSize(%08x, 0x%x, 0x%x) returned 0x%x\n",
-                               ftFace.get(), fScaleX, fScaleY,      err));
+            SkDEBUGF(("FT_Set_CharSize(%s, %f, %f) returned 0x%x.\n",
+                      ftFace->family_name, fScale.fX, fScale.fY, err));
             return;
         }
-        FT_Set_Transform(ftFace.get(), &fMatrix22, nullptr);
     } else if (FT_HAS_FIXED_SIZES(ftFace)) {
-        fStrikeIndex = chooseBitmapStrike(ftFace.get(), fScaleY);
+        fStrikeIndex = chooseBitmapStrike(ftFace.get(), scaleY);
         if (fStrikeIndex == -1) {
-            SkDEBUGF(("no glyphs for font \"%s\" size %f?\n",
-                            ftFace->family_name,      SkFDot6ToScalar(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;
+            SkDEBUGF(("No glyphs for font \"%s\" size %f.\n", ftFace->family_name, fScale.fY));
+            return;
         }
+
+        err = FT_Select_Size(ftFace.get(), fStrikeIndex);
+        if (err != 0) {
+            SkDEBUGF(("FT_Select_Size(%s, %d) returned 0x%x.\n",
+                      ftFace->family_name, fStrikeIndex, err));
+            fStrikeIndex = -1;
+            return;
+        }
+
+        // A non-ideal size was picked, so recompute the matrix.
+        // This adjusts for the difference between FT_Set_Char_Size and FT_Select_Size.
+        fMatrix22Scalar.preScale(fScale.x() / ftFace->size->metrics.x_ppem,
+                                 fScale.y() / ftFace->size->metrics.y_ppem);
+        fMatrix22.xx = SkScalarToFixed(fMatrix22Scalar.getScaleX());
+        fMatrix22.xy = SkScalarToFixed(-fMatrix22Scalar.getSkewX());
+        fMatrix22.yx = SkScalarToFixed(-fMatrix22Scalar.getSkewY());
+        fMatrix22.yy = SkScalarToFixed(fMatrix22Scalar.getScaleY());
+
+        // FreeType does not 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;
     } else {
-        SkDEBUGF(("unknown kind of font \"%s\" size %f?\n",
-                            fFace->family_name,     SkFDot6ToScalar(fScaleY)));
+        SkDEBUGF(("Unknown kind of font \"%s\" size %f.\n", fFace->family_name, fScale.fY));
+        return;
     }
 
     fFTSize = ftSize.release();
@@ -973,14 +980,8 @@
     gFTMutex.assertHeld();
     FT_Error err = FT_Activate_Size(fFTSize);
     if (err != 0) {
-        SkDEBUGF(("SkScalerContext_FreeType::FT_Activate_Size(%s %s, 0x%x, 0x%x) returned 0x%x\n",
-                  fFace->family_name, fFace->style_name, fScaleX, fScaleY, err));
-        fFTSize = nullptr;
         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, nullptr);
     return 0;
 }
@@ -1037,7 +1038,7 @@
             glyph->fLsbDelta = 0;
             const SkScalar advanceScalar = SkFT_FixedToScalar(advance);
             glyph->fAdvanceX = SkScalarToFloat(fMatrix22Scalar.getScaleX() * advanceScalar);
-            glyph->fAdvanceY = -SkScalarToFloat(fMatrix22Scalar.getSkewY() * advanceScalar);
+            glyph->fAdvanceY = SkScalarToFloat(fMatrix22Scalar.getSkewY() * advanceScalar);
             return;
         }
     }
@@ -1111,15 +1112,20 @@
     }
 }
 
-inline void scaleGlyphMetrics(SkGlyph& glyph, SkScalar scale) {
-    glyph.fWidth *= scale;
-    glyph.fHeight *= scale;
-    glyph.fTop *= scale;
-    glyph.fLeft *= scale;
+bool SkScalerContext_FreeType::shouldSubpixelBitmap(const SkGlyph& glyph, const SkMatrix& matrix) {
+    // If subpixel rendering of a bitmap *can* be done.
+    bool mechanism = fFace->glyph->format == FT_GLYPH_FORMAT_BITMAP &&
+                     fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag &&
+                     (glyph.getSubXFixed() || glyph.getSubYFixed());
 
-    float floatScale = SkScalarToFloat(scale);
-    glyph.fAdvanceX *= floatScale;
-    glyph.fAdvanceY *= floatScale;
+    // If subpixel rendering of a bitmap *should* be done.
+    // 1. If the face is not scalable then always allow subpixel rendering.
+    //    Otherwise, if the font has an 8ppem strike 7 will subpixel render but 8 won't.
+    // 2. If the matrix is already not identity the bitmap will already be resampled,
+    //    so resampling slightly differently shouldn't make much difference.
+    bool policy = !FT_IS_SCALABLE(fFace) || !matrix.isIdentity();
+
+    return mechanism && policy;
 }
 
 void SkScalerContext_FreeType::generateMetrics(SkGlyph* glyph) {
@@ -1176,10 +1182,22 @@
             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);
-        glyph->fLeft    = SkToS16(fFace->glyph->bitmap_left);
+        {
+            SkRect rect = SkRect::MakeXYWH(SkIntToScalar(fFace->glyph->bitmap_left),
+                                          -SkIntToScalar(fFace->glyph->bitmap_top),
+                                           SkIntToScalar(fFace->glyph->bitmap.width),
+                                           SkIntToScalar(fFace->glyph->bitmap.rows));
+            fMatrix22Scalar.mapRect(&rect);
+            if (this->shouldSubpixelBitmap(*glyph, fMatrix22Scalar)) {
+                rect.offset(SkFixedToScalar(glyph->getSubXFixed()),
+                            SkFixedToScalar(glyph->getSubYFixed()));
+            }
+            SkIRect irect = rect.roundOut();
+            glyph->fWidth   = SkToU16(irect.width());
+            glyph->fHeight  = SkToU16(irect.height());
+            glyph->fTop     = SkToS16(irect.top());
+            glyph->fLeft    = SkToS16(irect.left());
+        }
         break;
 
       default:
@@ -1191,7 +1209,7 @@
     if (fRec.fFlags & SkScalerContext::kVertical_Flag) {
         if (fDoLinearMetrics) {
             const SkScalar advanceScalar = SkFT_FixedToScalar(fFace->glyph->linearVertAdvance);
-            glyph->fAdvanceX = -SkScalarToFloat(fMatrix22Scalar.getSkewX() * advanceScalar);
+            glyph->fAdvanceX = SkScalarToFloat(fMatrix22Scalar.getSkewX() * advanceScalar);
             glyph->fAdvanceY = SkScalarToFloat(fMatrix22Scalar.getScaleY() * advanceScalar);
         } else {
             glyph->fAdvanceX = -SkFDot6ToFloat(fFace->glyph->advance.x);
@@ -1201,7 +1219,7 @@
         if (fDoLinearMetrics) {
             const SkScalar advanceScalar = SkFT_FixedToScalar(fFace->glyph->linearHoriAdvance);
             glyph->fAdvanceX = SkScalarToFloat(fMatrix22Scalar.getScaleX() * advanceScalar);
-            glyph->fAdvanceY = -SkScalarToFloat(fMatrix22Scalar.getSkewY() * advanceScalar);
+            glyph->fAdvanceY = SkScalarToFloat(fMatrix22Scalar.getSkewY() * advanceScalar);
         } else {
             glyph->fAdvanceX = SkFDot6ToFloat(fFace->glyph->advance.x);
             glyph->fAdvanceY = -SkFDot6ToFloat(fFace->glyph->advance.y);
@@ -1213,15 +1231,7 @@
         }
     }
 
-    // If the font isn't scalable, scale the metrics from the non-scalable strike.
-    // This means do not try to scale embedded bitmaps; only scale bitmaps in bitmap only fonts.
-    if (!FT_IS_SCALABLE(fFace) && !SkScalarNearlyZero(fScale.fY) && fFace->size->metrics.y_ppem) {
-        // NOTE: both dimensions are scaled by y_ppem. this is WAI.
-        scaleGlyphMetrics(*glyph, fScale.fY / fFace->size->metrics.y_ppem);
-    }
-
 #ifdef ENABLE_GLYPH_SPEW
-    SkDEBUGF(("FT_Set_Char_Size(this:%p sx:%x sy:%x ", this, fScaleX, fScaleY));
     SkDEBUGF(("Metrics(glyph:%d flags:0x%x) w:%d\n", glyph->getGlyphID(), fLoadGlyphFlags, glyph->fWidth));
 #endif
 }
@@ -1247,7 +1257,15 @@
     }
 
     emboldenIfNeeded(fFace, fFace->glyph);
-    generateGlyphImage(fFace, glyph);
+    SkMatrix* bitmapMatrix = &fMatrix22Scalar;
+    SkMatrix subpixelBitmapMatrix;
+    if (this->shouldSubpixelBitmap(glyph, *bitmapMatrix)) {
+        subpixelBitmapMatrix = fMatrix22Scalar;
+        subpixelBitmapMatrix.postTranslate(SkFixedToScalar(glyph.getSubXFixed()),
+                                           SkFixedToScalar(glyph.getSubYFixed()));
+        bitmapMatrix = &subpixelBitmapMatrix;
+    }
+    generateGlyphImage(fFace, glyph, *bitmapMatrix);
 }
 
 
@@ -1303,7 +1321,7 @@
     FT_Face face = fFace;
     SkScalar scaleX = fScale.x();
     SkScalar scaleY = fScale.y();
-    SkScalar mxy = fMatrix22Scalar.getSkewX() * scaleY;
+    SkScalar mxy = -fMatrix22Scalar.getSkewX() * scaleY;
     SkScalar myy = fMatrix22Scalar.getScaleY() * scaleY;
 
     // fetch units/EM from "head" table if needed (ie for bitmap fonts)
diff --git a/src/ports/SkFontHost_FreeType_common.cpp b/src/ports/SkFontHost_FreeType_common.cpp
index 245ded1..26e02fd 100644
--- a/src/ports/SkFontHost_FreeType_common.cpp
+++ b/src/ports/SkFontHost_FreeType_common.cpp
@@ -177,8 +177,18 @@
  *  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() == static_cast<int>(srcFTBitmap.width));
-    SkASSERT(dstMask.fBounds.height() == static_cast<int>(srcFTBitmap.rows));
+    SkASSERTF(dstMask.fBounds.width() == static_cast<int>(srcFTBitmap.width),
+              "dstMask.fBounds.width() = %d\n"
+              "static_cast<int>(srcFTBitmap.width) = %d",
+              dstMask.fBounds.width(),
+              static_cast<int>(srcFTBitmap.width)
+    );
+    SkASSERTF(dstMask.fBounds.height() == static_cast<int>(srcFTBitmap.rows),
+              "dstMask.fBounds.height() = %d\n"
+              "static_cast<int>(srcFTBitmap.rows) = %d",
+              dstMask.fBounds.height(),
+              static_cast<int>(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);
@@ -334,7 +344,11 @@
     }
 }
 
-void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face, const SkGlyph& glyph) {
+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);
 
@@ -402,20 +416,8 @@
                      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)
-            {
+            if (bitmapTransform.isIdentity()) {
                 SkMask dstMask;
                 glyph.toMask(&dstMask);
                 copyFTBitmap(face->glyph->bitmap, dstMask);
@@ -426,6 +428,7 @@
 
             // 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),
@@ -447,6 +450,7 @@
                 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),
@@ -459,9 +463,15 @@
 
             // Scale unscaledBitmap into dstBitmap.
             SkCanvas canvas(dstBitmap);
+#ifdef SK_SHOW_TEXT_BLIT_COVERAGE
+            canvas.clear(0x33FF0000);
+#else
             canvas.clear(SK_ColorTRANSPARENT);
-            canvas.scale(SkIntToScalar(glyph.fWidth) / SkIntToScalar(face->glyph->bitmap.width),
-                         SkIntToScalar(glyph.fHeight) / SkIntToScalar(face->glyph->bitmap.rows));
+#endif
+            canvas.translate(-glyph.fLeft, -glyph.fTop);
+            canvas.concat(bitmapTransform);
+            canvas.translate(face->glyph->bitmap_left, -face->glyph->bitmap_top);
+
             SkPaint paint;
             paint.setFilterQuality(kMedium_SkFilterQuality);
             canvas.drawBitmap(unscaledBitmap, 0, 0, &paint);
diff --git a/src/ports/SkFontHost_FreeType_common.h b/src/ports/SkFontHost_FreeType_common.h
index 3801453..6b50af2 100644
--- a/src/ports/SkFontHost_FreeType_common.h
+++ b/src/ports/SkFontHost_FreeType_common.h
@@ -31,9 +31,8 @@
         : INHERITED(typeface, effects, desc)
     {}
 
-    void generateGlyphImage(FT_Face face, const SkGlyph& glyph);
+    void generateGlyphImage(FT_Face face, const SkGlyph& glyph, const SkMatrix& bitmapTransform);
     void generateGlyphPath(FT_Face face, SkPath* path);
-
 private:
     typedef SkScalerContext INHERITED;
 };