Detect macOS font smoothing behavior.

In macOS 14 the font smoothing behavior was changed. On macOS 13 and lower
requesting smoothing would either do nothing or apply fake bolding and
subpixel coverage, depending on a user setting. On macOS 14 this same user
setting causes smoothing to either do nothing or apply fake bolding. Skia
had been checking for subpixel coverage to know whether to ask for
smoothing, but since subpixel coverage is no longer provided Skia won't
ask for smoothing. However, some still want to see the fake bolding.

This changes behavior in Skia to detect if requesting smoothing does not
change the rendering, if it changes the rendering, or if it changes the
rendering and applies subpixel coverage. The code which conflated the
changing of rendering and subpixel coverage is updated to check for the
appropriate conditions.

This has the odd effect of making the setLCDRenderText setting in Skia on
macOS 14 not actually turn on subpixel coverage but instead apply
fake-bolding to closest match the behavior of the request on previous
versions of macOS.

Bug: chromium:858861
Change-Id: Ic7ff22e171a15be20e0bcccb10b0c4798cba8b72
Reviewed-on: https://skia-review.googlesource.com/157566
Reviewed-by: Mike Klein <mtklein@google.com>
Commit-Queue: Ben Wagner <bungeman@google.com>
diff --git a/src/ports/SkFontHost_mac.cpp b/src/ports/SkFontHost_mac.cpp
index 79d61f4..3242f9e 100644
--- a/src/ports/SkFontHost_mac.cpp
+++ b/src/ports/SkFontHost_mac.cpp
@@ -372,17 +372,29 @@
     0x00, 0x02, 0x00, 0x00
 };
 
+enum class SmoothBehavior {
+    none, // SmoothFonts produces no effect.
+    some, // SmoothFonts produces some effect, but not subpixel coverage.
+    subpixel, // SmoothFonts produces some effect and provides subpixel coverage.
+};
+
 /**
  * There does not appear to be a publicly accessable API for determining if lcd
  * font smoothing will be applied if we request it. The main issue is that if
  * smoothing is applied a gamma of 2.0 will be used, if not a gamma of 1.0.
  */
-static bool supports_LCD() {
-    static bool gSupportsLCD = []{
-        uint32_t bitmap[16][16] = {};
+static SmoothBehavior smooth_behavior() {
+    static SmoothBehavior gSmoothBehavior = []{
+        uint32_t noSmoothBitmap[16][16] = {};
+        uint32_t smoothBitmap[16][16] = {};
+
         SkUniqueCFRef<CGColorSpaceRef> colorspace(CGColorSpaceCreateDeviceRGB());
-        SkUniqueCFRef<CGContextRef> cgContext(
-                CGBitmapContextCreate(&bitmap, 16, 16, 8, 16*4, colorspace.get(), BITMAP_INFO_RGB));
+        SkUniqueCFRef<CGContextRef> noSmoothContext(
+                CGBitmapContextCreate(&noSmoothBitmap, 16, 16, 8, 16*4,
+                                      colorspace.get(), BITMAP_INFO_RGB));
+        SkUniqueCFRef<CGContextRef> smoothContext(
+                CGBitmapContextCreate(&smoothBitmap, 16, 16, 8, 16*4,
+                                      colorspace.get(), BITMAP_INFO_RGB));
 
         SkUniqueCFRef<CGDataProviderRef> data(
                 CGDataProviderCreateWithData(nullptr, kSpiderSymbol_ttf,
@@ -393,31 +405,43 @@
                 CTFontCreateWithGraphicsFont(cgFont.get(), 16, nullptr, nullptr));
         SkASSERT(ctFont);
 
-        CGContextSetShouldSmoothFonts(cgContext.get(), true);
-        CGContextSetShouldAntialias(cgContext.get(), true);
-        CGContextSetTextDrawingMode(cgContext.get(), kCGTextFill);
-        CGContextSetGrayFillColor(cgContext.get(), 1, 1);
+        CGContextSetShouldSmoothFonts(noSmoothContext.get(), false);
+        CGContextSetShouldAntialias(noSmoothContext.get(), true);
+        CGContextSetTextDrawingMode(noSmoothContext.get(), kCGTextFill);
+        CGContextSetGrayFillColor(noSmoothContext.get(), 1, 1);
+
+        CGContextSetShouldSmoothFonts(smoothContext.get(), true);
+        CGContextSetShouldAntialias(smoothContext.get(), true);
+        CGContextSetTextDrawingMode(smoothContext.get(), kCGTextFill);
+        CGContextSetGrayFillColor(smoothContext.get(), 1, 1);
+
         CGPoint point = CGPointMake(0, 3);
         CGGlyph spiderGlyph = 3;
-        CTFontDrawGlyphs(ctFont.get(), &spiderGlyph, &point, 1, cgContext.get());
+        CTFontDrawGlyphs(ctFont.get(), &spiderGlyph, &point, 1, noSmoothContext.get());
+        CTFontDrawGlyphs(ctFont.get(), &spiderGlyph, &point, 1, smoothContext.get());
 
         // For debugging.
-        //SkUniqueCFRef<CGImageRef> image(CGBitmapContextCreateImage(cgContext.get()));
+        //SkUniqueCFRef<CGImageRef> image(CGBitmapContextCreateImage(noSmoothContext()));
+        //SkUniqueCFRef<CGImageRef> image(CGBitmapContextCreateImage(smoothContext()));
 
+        SmoothBehavior smoothBehavior = SmoothBehavior::none;
         for (int x = 0; x < 16; ++x) {
             for (int y = 0; y < 16; ++y) {
-                uint32_t pixel = bitmap[x][y];
-                uint32_t r = (pixel >> 16) & 0xFF;
-                uint32_t g = (pixel >>  8) & 0xFF;
-                uint32_t b = (pixel >>  0) & 0xFF;
+                uint32_t smoothPixel = smoothBitmap[x][y];
+                uint32_t r = (smoothPixel >> 16) & 0xFF;
+                uint32_t g = (smoothPixel >>  8) & 0xFF;
+                uint32_t b = (smoothPixel >>  0) & 0xFF;
                 if (r != g || r != b) {
-                    return true;
+                    return SmoothBehavior::subpixel;
+                }
+                if (noSmoothBitmap[x][y] != smoothPixel) {
+                    smoothBehavior = SmoothBehavior::some;
                 }
             }
         }
-        return false;
+        return smoothBehavior;
     }();
-    return gSupportsLCD;
+    return gSmoothBehavior;
 }
 
 class Offscreen {
@@ -1000,7 +1024,7 @@
     if (!fRGBSpace) {
         //It doesn't appear to matter what color space is specified.
         //Regular blends and antialiased text are always (s*a + d*(1-a))
-        //and smoothed text is always g=2.0.
+        //and subpixel antialiased text is always g=2.0.
         fRGBSpace.reset(CGColorSpaceCreateDeviceRGB());
     }
 
@@ -1258,7 +1282,7 @@
  *  This will invert the gamma applied by CoreGraphics, so we can get linear
  *  values.
  *
- *  CoreGraphics obscurely defaults to 2.0 as the smoothing gamma value.
+ *  CoreGraphics obscurely defaults to 2.0 as the subpixel coverage gamma value.
  *  The color space used does not appear to affect this choice.
  */
 static constexpr auto gLinearCoverageFromCGLCDValue = SkMakeArray<256>(sk_pow2_table);
@@ -1348,18 +1372,20 @@
     CGGlyph cgGlyph = SkTo<CGGlyph>(glyph.getGlyphID());
 
     // FIXME: lcd smoothed un-hinted rasterization unsupported.
-    bool generateA8FromLCD = fRec.getHinting() != SkPaint::kNo_Hinting;
+    bool requestSmooth = fRec.getHinting() != SkPaint::kNo_Hinting;
 
     // Draw the glyph
     size_t cgRowBytes;
-    CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, cgGlyph, &cgRowBytes, generateA8FromLCD);
+    CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, cgGlyph, &cgRowBytes, requestSmooth);
     if (cgPixels == nullptr) {
         return;
     }
 
     // Fix the glyph
     if ((glyph.fMaskFormat == SkMask::kLCD16_Format) ||
-        (glyph.fMaskFormat == SkMask::kA8_Format && supports_LCD() && generateA8FromLCD))
+        (glyph.fMaskFormat == SkMask::kA8_Format
+         && requestSmooth
+         && smooth_behavior() == SmoothBehavior::subpixel))
     {
         const uint8_t* linear = gLinearCoverageFromCGLCDValue.data();
 
@@ -2256,14 +2282,14 @@
 
     rec->fFlags &= ~flagsWeDontSupport;
 
-    bool lcdSupport = supports_LCD();
+    SmoothBehavior smoothBehavior = smooth_behavior();
 
     // Only two levels of hinting are supported.
     // kNo_Hinting means avoid CoreGraphics outline dilation.
     // kNormal_Hinting means CoreGraphics outline dilation is allowed.
     // If there is no lcd support, hinting (dilation) cannot be supported.
     SkPaint::Hinting hinting = rec->getHinting();
-    if (SkPaint::kSlight_Hinting == hinting || !lcdSupport) {
+    if (SkPaint::kSlight_Hinting == hinting || smoothBehavior == SmoothBehavior::none) {
         hinting = SkPaint::kNo_Hinting;
     } else if (SkPaint::kFull_Hinting == hinting) {
         hinting = SkPaint::kNormal_Hinting;
@@ -2290,12 +2316,15 @@
     // [LCD][yes-hint]: generate LCD using CoreGraphic's LCD output.
 
     if (rec->fMaskFormat == SkMask::kLCD16_Format) {
-        if (lcdSupport) {
+        if (smoothBehavior == SmoothBehavior::subpixel) {
             //CoreGraphics creates 555 masks for smoothed text anyway.
             rec->fMaskFormat = SkMask::kLCD16_Format;
             rec->setHinting(SkPaint::kNormal_Hinting);
         } else {
             rec->fMaskFormat = SkMask::kA8_Format;
+            if (smoothBehavior == SmoothBehavior::some) {
+                rec->setHinting(SkPaint::kNormal_Hinting);
+            }
         }
     }