Bug 889401 - Part 2. Render color glyph using COLR/CPAL. r=jfkthame
authorMakoto Kato <m_kato@ga2.so-net.ne.jp>
Mon, 26 May 2014 19:07:24 +0900
changeset 192431 c80b51a801cede9a9070301631d2d70305a9932f
parent 192430 6f8be55236cb56af5e708220b8a6798a380f48e4
child 192432 8eb3006c6d6ff71bc3037db1a36462bb52b76682
push idunknown
push userunknown
push dateunknown
reviewersjfkthame
bugs889401
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 889401 - Part 2. Render color glyph using COLR/CPAL. r=jfkthame
gfx/thebes/gfxFont.cpp
gfx/thebes/gfxFont.h
gfx/thebes/gfxFontUtils.cpp
gfx/thebes/gfxFontUtils.h
gfx/thebes/gfxUserFontSet.cpp
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -118,19 +118,22 @@ gfxFontEntry::gfxFontEntry() :
     mHasSpaceFeaturesInitialized(false),
     mHasSpaceFeatures(false),
     mHasSpaceFeaturesKerning(false),
     mHasSpaceFeaturesNonKerning(false),
     mSkipDefaultFeatureSpaceCheck(false),
     mCheckedForGraphiteTables(false),
     mHasCmapTable(false),
     mGrFaceInitialized(false),
+    mCheckedForColorGlyph(false),
     mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
     mUVSOffset(0), mUVSData(nullptr),
     mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
+    mCOLR(nullptr),
+    mCPAL(nullptr),
     mUnitsPerEm(0),
     mHBFace(nullptr),
     mGrFace(nullptr),
     mGrFaceRefCnt(0)
 {
     memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
     memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
 }
@@ -148,30 +151,41 @@ gfxFontEntry::gfxFontEntry(const nsAStri
     mHasSpaceFeaturesInitialized(false),
     mHasSpaceFeatures(false),
     mHasSpaceFeaturesKerning(false),
     mHasSpaceFeaturesNonKerning(false),
     mSkipDefaultFeatureSpaceCheck(false),
     mCheckedForGraphiteTables(false),
     mHasCmapTable(false),
     mGrFaceInitialized(false),
+    mCheckedForColorGlyph(false),
     mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
     mUVSOffset(0), mUVSData(nullptr),
     mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
+    mCOLR(nullptr),
+    mCPAL(nullptr),
     mUnitsPerEm(0),
     mHBFace(nullptr),
     mGrFace(nullptr),
     mGrFaceRefCnt(0)
 {
     memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
     memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
 }
 
 gfxFontEntry::~gfxFontEntry()
 {
+    if (mCOLR) {
+        hb_blob_destroy(mCOLR);
+    }
+
+    if (mCPAL) {
+        hb_blob_destroy(mCPAL);
+    }
+
     // For downloaded fonts, we need to tell the user font cache that this
     // entry is being deleted.
     if (!mIsProxy && IsUserFont() && !IsLocalUserFont()) {
         gfxUserFontSet::UserFontCache::ForgetFont(this);
     }
 
     // By the time the entry is destroyed, all font instances that were
     // using it should already have been deleted, and so the HB and/or Gr
@@ -477,16 +491,49 @@ gfxFontEntry::GetMathVariantsSize(uint32
 bool
 gfxFontEntry::GetMathVariantsParts(uint32_t aGlyphID, bool aVertical,
                                    uint32_t aGlyphs[4])
 {
     NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first.");
     return mMathTable->GetMathVariantsParts(aGlyphID, aVertical, aGlyphs);
 }
 
+bool
+gfxFontEntry::TryGetColorGlyphs()
+{
+    if (mCheckedForColorGlyph) {
+        return (mCOLR && mCPAL);
+    }
+
+    mCheckedForColorGlyph = true;
+
+    mCOLR = GetFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R'));
+    if (!mCOLR) {
+        return false;
+    }
+
+    mCPAL = GetFontTable(TRUETYPE_TAG('C', 'P', 'A', 'L'));
+    if (!mCPAL) {
+        hb_blob_destroy(mCOLR);
+        mCOLR = nullptr;
+        return false;
+    }
+
+    // validation COLR and CPAL table
+    if (gfxFontUtils::ValidateColorGlyphs(mCOLR, mCPAL)) {
+        return true;
+    }
+
+    hb_blob_destroy(mCOLR);
+    hb_blob_destroy(mCPAL);
+    mCOLR = nullptr;
+    mCPAL = nullptr;
+    return false;
+}
+
 /**
  * FontTableBlobData
  *
  * See FontTableHashEntry for the general strategy.
  */
 
 class gfxFontEntry::FontTableBlobData {
 public:
@@ -825,16 +872,28 @@ gfxFontEntry::HasFontTable(uint32_t aTab
 }
 
 void
 gfxFontEntry::CheckForGraphiteTables()
 {
     mHasGraphiteTables = HasFontTable(TRUETYPE_TAG('S','i','l','f'));
 }
 
+bool
+gfxFontEntry::GetColorLayersInfo(uint32_t aGlyphId,
+                            nsTArray<uint16_t>& aLayerGlyphs,
+                            nsTArray<mozilla::gfx::Color>& aLayerColors)
+{
+    return gfxFontUtils::GetColorGlyphLayers(mCOLR,
+                                             mCPAL,
+                                             aGlyphId,
+                                             aLayerGlyphs,
+                                             aLayerColors);
+}
+
 /* static */ size_t
 gfxFontEntry::FontTableHashEntry::SizeOfEntryExcludingThis
     (FontTableHashEntry *aEntry,
      MallocSizeOf aMallocSizeOf,
      void* aUserArg)
 {
     size_t n = 0;
     if (aEntry->mBlob) {
@@ -2862,16 +2921,17 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
     const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
     const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
     const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit);
     bool isRTL = aTextRun->IsRightToLeft();
     double direction = aTextRun->GetDirection();
     gfxMatrix globalMatrix = aContext->CurrentMatrix();
 
     bool haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this);
+    bool haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs();
     nsAutoPtr<gfxTextContextPaint> contextPaint;
     if (haveSVGGlyphs && !aContextPaint) {
         // If no pattern is specified for fill, use the current pattern
         NS_ASSERTION((int(aDrawMode) & int(DrawMode::GLYPH_STROKE)) == 0, "no pattern supplied for stroking text");
         nsRefPtr<gfxPattern> fillPattern = aContext->GetPattern();
         contextPaint = new SimpleTextContextPaint(fillPattern, nullptr,
                                                  aContext->CurrentMatrix());
         aContextPaint = contextPaint;
@@ -2934,16 +2994,24 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
                   DrawMode mode = ForcePaintingDrawMode(aDrawMode);
                   if (RenderSVGGlyph(aContext, point, mode,
                                      glyphData->GetSimpleGlyph(), aContextPaint,
                                      aCallbacks, emittedGlyphs)) {
                       continue;
                   }
               }
 
+              if (haveColorGlyphs) {
+                  gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit),
+                                 ToDeviceUnits(y, devUnitsPerAppUnit));
+                  if (RenderColorGlyph(aContext, point, glyphData->GetSimpleGlyph())) {
+                      continue;
+                  }
+              }
+
               // Perhaps we should put a scale in the cairo context instead of
               // doing this scaling here...
               // Multiplying by the reciprocal may introduce tiny error here,
               // but we assume cairo is going to round coordinates at some stage
               // and this is faster
               glyph = glyphs.AppendGlyph();
               glyph->index = glyphData->GetSimpleGlyph();
               glyph->x = ToDeviceUnits(glyphX, devUnitsPerAppUnit);
@@ -2996,32 +3064,44 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
                                                                      appUnitsPerDevUnit);
                           }
                       } else {
                           double glyphX = x + details->mXOffset;
                           if (isRTL) {
                               glyphX -= advance;
                           }
 
-                          gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit),
-                                         ToDeviceUnits(y, devUnitsPerAppUnit));
-
                           if (haveSVGGlyphs) {
                               if (!paintSVGGlyphs) {
                                   continue;
                               }
+
+                              gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit),
+                                             ToDeviceUnits(y, devUnitsPerAppUnit));
+
                               DrawMode mode = ForcePaintingDrawMode(aDrawMode);
                               if (RenderSVGGlyph(aContext, point, mode,
                                                   details->mGlyphID,
                                                   aContextPaint, aCallbacks,
                                                   emittedGlyphs)) {
                                   continue;
                               }
                           }
 
+                          if (haveColorGlyphs) {
+                              gfxPoint point(ToDeviceUnits(glyphX,
+                                                           devUnitsPerAppUnit),
+                                             ToDeviceUnits(y + details->mYOffset,
+                                                           devUnitsPerAppUnit));
+                              if (RenderColorGlyph(aContext, point,
+                                                   details->mGlyphID)) {
+                                  continue;
+                              }
+                          }
+
                           glyph = glyphs.AppendGlyph();
                           glyph->index = details->mGlyphID;
                           glyph->x = ToDeviceUnits(glyphX, devUnitsPerAppUnit);
                           glyph->y = ToDeviceUnits(y + details->mYOffset, devUnitsPerAppUnit);
                           glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix);
 
                           if (IsSyntheticBold()) {
                               double strikeOffset = synBoldOnePixelOffset;
@@ -3152,16 +3232,28 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
                   DrawMode mode = ForcePaintingDrawMode(aDrawMode);
                   if (RenderSVGGlyph(aContext, point, mode,
                                      glyphData->GetSimpleGlyph(), aContextPaint,
                                      aCallbacks, emittedGlyphs)) {
                       continue;
                   }
               }
 
+              if (haveColorGlyphs) {
+                  mozilla::gfx::Point point(ToDeviceUnits(glyphX,
+                                                          devUnitsPerAppUnit),
+                                            ToDeviceUnits(y,
+                                                          devUnitsPerAppUnit));
+                  if (RenderColorGlyph(aContext, scaledFont, renderingOptions,
+                                       drawOptions, matInv * point,
+                                       glyphData->GetSimpleGlyph())) {
+                      continue;
+                  }
+              }
+
               // Perhaps we should put a scale in the cairo context instead of
               // doing this scaling here...
               // Multiplying by the reciprocal may introduce tiny error here,
               // but we assume cairo is going to round coordinates at some stage
               // and this is faster
               glyph = glyphs.AppendGlyph();
               glyph->mIndex = glyphData->GetSimpleGlyph();
               glyph->mPosition.x = ToDeviceUnits(glyphX, devUnitsPerAppUnit);
@@ -3238,16 +3330,29 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
                               if (RenderSVGGlyph(aContext, point, mode,
                                                  details->mGlyphID,
                                                  aContextPaint, aCallbacks,
                                                  emittedGlyphs)) {
                                   continue;
                               }
                           }
 
+                          if (haveColorGlyphs) {
+                              mozilla::gfx::Point point(ToDeviceUnits(glyphX,
+                                                                      devUnitsPerAppUnit),
+                                                        ToDeviceUnits(y + details->mYOffset,
+                                                                      devUnitsPerAppUnit));
+                              if (RenderColorGlyph(aContext, scaledFont,
+                                                   renderingOptions,
+                                                   drawOptions, matInv * point,
+                                                   details->mGlyphID)) {
+                                  continue;
+                              }
+                          }
+
                           glyph = glyphs.AppendGlyph();
                           glyph->mIndex = details->mGlyphID;
                           glyph->mPosition.x = ToDeviceUnits(glyphX, devUnitsPerAppUnit);
                           glyph->mPosition.y = ToDeviceUnits(y + details->mYOffset, devUnitsPerAppUnit);
                           glyph->mPosition = matInv * glyph->mPosition;
                           glyphs.Flush(dt, aContextPaint, scaledFont, aDrawMode,
                                        isRTL, renderingOptions, aContext, passedInvMatrix,
                                        drawOptions);
@@ -3338,16 +3443,83 @@ gfxFont::RenderSVGGlyph(gfxContext *aCon
     bool rendered = RenderSVGGlyph(aContext, aPoint, aDrawMode, aGlyphId,
                                    aContextPaint);
     if (aCallbacks) {
         aCallbacks->NotifyAfterSVGGlyphPainted();
     }
     return rendered;
 }
 
+bool
+gfxFont::RenderColorGlyph(gfxContext* aContext, gfxPoint& point,
+                          uint32_t aGlyphId)
+{
+    nsAutoTArray<uint16_t, 8> layerGlyphs;
+    nsAutoTArray<mozilla::gfx::Color, 8> layerColors;
+
+    if (!GetFontEntry()->GetColorLayersInfo(aGlyphId, layerGlyphs, layerColors)) {
+        return false;
+    }
+
+    cairo_t* cr = aContext->GetCairo();
+    cairo_save(cr);
+    for (uint32_t layerIndex = 0; layerIndex < layerGlyphs.Length();
+         layerIndex++) {
+
+        cairo_glyph_t glyph;
+        glyph.index = layerGlyphs[layerIndex];
+        glyph.x = point.x;
+        glyph.y = point.y;
+
+        mozilla::gfx::Color &color = layerColors[layerIndex];
+        cairo_pattern_t* pattern =
+            cairo_pattern_create_rgba(color.r, color.g, color.b, color.a);
+
+        cairo_set_source(cr, pattern);
+        cairo_show_glyphs(cr, &glyph, 1);
+        cairo_pattern_destroy(pattern);
+    }
+    cairo_restore(cr);
+
+    return true;
+}
+
+bool
+gfxFont::RenderColorGlyph(gfxContext* aContext,
+                          mozilla::gfx::ScaledFont* scaledFont,
+                          GlyphRenderingOptions* aRenderingOptions,
+                          mozilla::gfx::DrawOptions aDrawOptions,
+                          const mozilla::gfx::Point& aPoint,
+                          uint32_t aGlyphId)
+{
+    nsAutoTArray<uint16_t, 8> layerGlyphs;
+    nsAutoTArray<mozilla::gfx::Color, 8> layerColors;
+
+    if (!GetFontEntry()->GetColorLayersInfo(aGlyphId, layerGlyphs, layerColors)) {
+        return false;
+    }
+
+    RefPtr<DrawTarget> dt = aContext->GetDrawTarget();
+    for (uint32_t layerIndex = 0; layerIndex < layerGlyphs.Length();
+         layerIndex++) {
+        Glyph glyph;
+        glyph.mIndex = layerGlyphs[layerIndex];
+        glyph.mPosition = aPoint;
+
+        mozilla::gfx::GlyphBuffer buffer;
+        buffer.mGlyphs = &glyph;
+        buffer.mNumGlyphs = 1;
+
+        dt->FillGlyphs(scaledFont, buffer,
+                       ColorPattern(layerColors[layerIndex]),
+                       aDrawOptions, aRenderingOptions);
+    }
+    return true;
+}
+
 static void
 UnionRange(gfxFloat aX, gfxFloat* aDestMin, gfxFloat* aDestMax)
 {
     *aDestMin = std::min(*aDestMin, aX);
     *aDestMax = std::max(*aDestMax, aX);
 }
 
 // We get precise glyph extents if the textrun creator requested them, or
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -384,16 +384,21 @@ public:
     gfxFloat GetMathConstant(MathConstant aConstant);
     bool     GetMathItalicsCorrection(uint32_t aGlyphID,
                                       gfxFloat* aItalicCorrection);
     uint32_t GetMathVariantsSize(uint32_t aGlyphID, bool aVertical,
                                  uint16_t aSize);
     bool     GetMathVariantsParts(uint32_t aGlyphID, bool aVertical,
                                   uint32_t aGlyphs[4]);
 
+    bool     TryGetColorGlyphs();
+    bool     GetColorLayersInfo(uint32_t aGlyphId,
+                                nsTArray<uint16_t>& layerGlyphs,
+                                nsTArray<mozilla::gfx::Color>& layerColors);
+
     virtual bool MatchesGenericFamily(const nsACString& aGeneric) const {
         return true;
     }
     virtual bool SupportsLangGroup(nsIAtom *aLangGroup) const {
         return true;
     }
 
     // Access to raw font table data (needed for Harfbuzz):
@@ -526,16 +531,17 @@ public:
     bool             mHasSpaceFeatures : 1;
     bool             mHasSpaceFeaturesKerning : 1;
     bool             mHasSpaceFeaturesNonKerning : 1;
     bool             mSkipDefaultFeatureSpaceCheck : 1;
     bool             mHasGraphiteTables : 1;
     bool             mCheckedForGraphiteTables : 1;
     bool             mHasCmapTable : 1;
     bool             mGrFaceInitialized : 1;
+    bool             mCheckedForColorGlyph : 1;
 
     // bitvector of substitution space features per script, one each
     // for default and non-default features
     uint32_t         mDefaultSubSpaceFeatures[(MOZ_NUM_SCRIPT_CODES + 31) / 32];
     uint32_t         mNonDefaultSubSpaceFeatures[(MOZ_NUM_SCRIPT_CODES + 31) / 32];
 
     uint16_t         mWeight;
     int16_t          mStretch;
@@ -546,16 +552,20 @@ public:
     nsAutoPtr<gfxUserFontData> mUserFontData;
     nsAutoPtr<gfxSVGGlyphs> mSVGGlyphs;
     // list of gfxFonts that are using SVG glyphs
     nsTArray<gfxFont*> mFontsUsingSVGGlyphs;
     nsAutoPtr<gfxMathTable> mMathTable;
     nsTArray<gfxFontFeature> mFeatureSettings;
     uint32_t         mLanguageOverride;
 
+    // Color Layer font support
+    hb_blob_t*       mCOLR;
+    hb_blob_t*       mCPAL;
+
 protected:
     friend class gfxPlatformFontList;
     friend class gfxMacPlatformFontList;
     friend class gfxUserFcFontEntry;
     friend class gfxFontFamily;
     friend class gfxSingleFaceMacFontFamily;
 
     gfxFontEntry();
@@ -2130,16 +2140,24 @@ protected:
 
     bool RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint, DrawMode aDrawMode,
                         uint32_t aGlyphId, gfxTextContextPaint *aContextPaint);
     bool RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint, DrawMode aDrawMode,
                         uint32_t aGlyphId, gfxTextContextPaint *aContextPaint,
                         gfxTextRunDrawCallbacks *aCallbacks,
                         bool& aEmittedGlyphs);
 
+    bool RenderColorGlyph(gfxContext* aContext, gfxPoint& point, uint32_t aGlyphId);
+    bool RenderColorGlyph(gfxContext* aContext,
+                          mozilla::gfx::ScaledFont* scaledFont,
+                          mozilla::gfx::GlyphRenderingOptions* renderingOptions,
+                          mozilla::gfx::DrawOptions drawOptions,
+                          const mozilla::gfx::Point& aPoint,
+                          uint32_t aGlyphId);
+
     // Bug 674909. When synthetic bolding text by drawing twice, need to
     // render using a pixel offset in device pixels, otherwise text
     // doesn't appear bolded, it appears as if a bad text shadow exists
     // when a non-identity transform exists.  Use an offset factor so that
     // the second draw occurs at a constant offset in device pixels.
     // This helper calculates the scale factor we need to apply to the
     // synthetic-bold offset.
     static double CalcXScale(gfxContext *aContext);
--- a/gfx/thebes/gfxFontUtils.cpp
+++ b/gfx/thebes/gfxFontUtils.cpp
@@ -6,16 +6,17 @@
 #ifdef MOZ_LOGGING
 #define FORCE_PR_LOG /* Allow logging in the release build */
 #include "prlog.h"
 #endif
 
 #include "mozilla/ArrayUtils.h"
 
 #include "gfxFontUtils.h"
+#include "gfxColor.h"
 
 #include "nsServiceManagerUtils.h"
 
 #include "mozilla/dom/EncodingUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 
 #include "nsCOMPtr.h"
@@ -1434,16 +1435,225 @@ gfxFontUtils::ReadNames(const char *aNam
         if (!foundName)
             aNames.AppendElement(name);
 
     }
 
     return NS_OK;
 }
 
+#pragma pack(1)
+
+struct COLRBaseGlyphRecord {
+    AutoSwap_PRUint16    glyphId;
+    AutoSwap_PRUint16    firstLayerIndex;
+    AutoSwap_PRUint16    numLayers;
+};
+
+struct COLRLayerRecord {
+    AutoSwap_PRUint16    glyphId;
+    AutoSwap_PRUint16    paletteEntryIndex;
+};
+
+struct CPALColorRecord {
+    uint8_t              blue;
+    uint8_t              green;
+    uint8_t              red;
+    uint8_t              alpha;
+};
+
+#pragma pack()
+
+bool
+gfxFontUtils::ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL)
+{
+    unsigned int colrLength;
+    const COLRHeader* colr =
+        reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &colrLength));
+    unsigned int cpalLength;
+    const CPALHeaderVersion0* cpal =
+        reinterpret_cast<const CPALHeaderVersion0*>(hb_blob_get_data(aCPAL, &cpalLength));
+
+    if (!colr || !cpal || !colrLength || !cpalLength) {
+        return false;
+    }
+
+    if (uint16_t(colr->version) != 0 || uint16_t(cpal->version) != 0) {
+        // We only support version 0 headers.
+        return false;
+    }
+
+    const uint32_t offsetBaseGlyphRecord = colr->offsetBaseGlyphRecord;
+    const uint16_t numBaseGlyphRecord = colr->numBaseGlyphRecord;
+    const uint32_t offsetLayerRecord = colr->offsetLayerRecord;
+    const uint16_t numLayerRecords = colr->numLayerRecords;
+
+    const uint32_t offsetFirstColorRecord = cpal->offsetFirstColorRecord;
+    const uint16_t numColorRecords = cpal->numColorRecords;
+    const uint32_t numPaletteEntries = cpal->numPaletteEntries;
+
+    if (offsetBaseGlyphRecord >= colrLength) {
+        return false;
+    }
+
+    if (offsetLayerRecord >= colrLength) {
+        return false;
+    }
+
+    if (offsetFirstColorRecord >= cpalLength) {
+        return false;
+    }
+
+    if (!numPaletteEntries) {
+        return false;
+    }
+
+    if (sizeof(COLRBaseGlyphRecord) * numBaseGlyphRecord >
+            colrLength - offsetBaseGlyphRecord) {
+        // COLR base glyph record will be overflow
+        return false;
+    }
+
+    if (sizeof(COLRLayerRecord) * numLayerRecords >
+            colrLength - offsetLayerRecord) {
+        // COLR layer record will be overflow
+        return false;
+    }
+
+    if (sizeof(CPALColorRecord) * numColorRecords >
+            cpalLength - offsetFirstColorRecord) {
+        // CPAL color record will be overflow
+        return false;
+    }
+
+    if (numPaletteEntries * uint16_t(cpal->numPalettes) != numColorRecords ) {
+        // palette of CPAL color record will be overflow.
+        return false;
+    }
+
+    uint16_t lastGlyphId = 0;
+    const COLRBaseGlyphRecord* baseGlyph =
+        reinterpret_cast<const COLRBaseGlyphRecord*>(
+            reinterpret_cast<const uint8_t*>(colr) + offsetBaseGlyphRecord);
+
+    for (uint16_t i = 0; i < numBaseGlyphRecord; i++, baseGlyph++) {
+        const uint32_t firstLayerIndex = baseGlyph->firstLayerIndex;
+        const uint16_t numLayers = baseGlyph->numLayers;
+        const uint16_t glyphId = baseGlyph->glyphId;
+
+        if (lastGlyphId && lastGlyphId >= glyphId) {
+            // glyphId must be sorted
+            return false;
+        }
+        lastGlyphId = glyphId;
+
+        if (!numLayers) {
+            // no layer
+            return false;
+        }
+        if (firstLayerIndex + numLayers > numLayerRecords) {
+            // layer length of target glyph is overflow
+            return false;
+        }
+    }
+
+    const COLRLayerRecord* layer =
+        reinterpret_cast<const COLRLayerRecord*>(
+            reinterpret_cast<const uint8_t*>(colr) + offsetLayerRecord);
+
+    for (uint16_t i = 0; i < numLayerRecords; i++, layer++) {
+        if (uint16_t(layer->paletteEntryIndex) >= numPaletteEntries) {
+            // CPAL palette entry record is overflow
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static int
+CompareBaseGlyph(const void* key, const void* data)
+{
+    uint32_t glyphId = (uint32_t)(uintptr_t)key;
+    const COLRBaseGlyphRecord* baseGlyph =
+        reinterpret_cast<const COLRBaseGlyphRecord*>(data);
+    uint32_t baseGlyphId = uint16_t(baseGlyph->glyphId);
+
+    if (baseGlyphId == glyphId) {
+        return 0;
+    }
+
+    return baseGlyphId > glyphId ? -1 : 1;
+}
+
+static
+COLRBaseGlyphRecord*
+LookForBaseGlyphRecord(const COLRHeader* aCOLR, uint32_t aGlyphId)
+{
+    const uint8_t* baseGlyphRecords =
+        reinterpret_cast<const uint8_t*>(aCOLR) +
+        uint32_t(aCOLR->offsetBaseGlyphRecord);
+    // BaseGlyphRecord is sorted by glyphId
+    return reinterpret_cast<COLRBaseGlyphRecord*>(
+               bsearch((void*)(uintptr_t)aGlyphId,
+                       baseGlyphRecords,
+                       uint16_t(aCOLR->numBaseGlyphRecord),
+                       sizeof(COLRBaseGlyphRecord),
+                       CompareBaseGlyph));
+}
+
+bool
+gfxFontUtils::GetColorGlyphLayers(hb_blob_t* aCOLR,
+                                  hb_blob_t* aCPAL,
+                                  uint32_t aGlyphId,
+                                  nsTArray<uint16_t>& aGlyphs,
+                                  nsTArray<mozilla::gfx::Color>& aColors)
+{
+    unsigned int blobLength;
+    const COLRHeader* colr =
+        reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR,
+                                                             &blobLength));
+    MOZ_ASSERT(colr, "Cannot get COLR raw data");
+    MOZ_ASSERT(blobLength, "Found COLR data, but length is 0");
+
+    COLRBaseGlyphRecord* baseGlyph = LookForBaseGlyphRecord(colr, aGlyphId);
+    if (!baseGlyph) {
+        return false;
+    }
+
+    const CPALHeaderVersion0* cpal =
+        reinterpret_cast<const CPALHeaderVersion0*>(
+            hb_blob_get_data(aCPAL, &blobLength));
+    MOZ_ASSERT(cpal, "Cannot get CPAL raw data");
+    MOZ_ASSERT(blobLength, "Found CPAL data, but length is 0");
+
+    const COLRLayerRecord* layer =
+        reinterpret_cast<const COLRLayerRecord*>(
+            reinterpret_cast<const uint8_t*>(colr) +
+            uint32_t(colr->offsetLayerRecord) +
+            sizeof(COLRLayerRecord) * uint16_t(baseGlyph->firstLayerIndex));
+    const uint16_t numLayers = baseGlyph->numLayers;
+    const uint32_t offsetFirstColorRecord = cpal->offsetFirstColorRecord;
+
+    for (uint16_t layerIndex = 0; layerIndex < numLayers; layerIndex++) {
+        aGlyphs.AppendElement(uint16_t(layer->glyphId));
+        const CPALColorRecord* color =
+            reinterpret_cast<const CPALColorRecord*>(
+                reinterpret_cast<const uint8_t*>(cpal) +
+                offsetFirstColorRecord +
+                sizeof(CPALColorRecord) * uint16_t(layer->paletteEntryIndex));
+        aColors.AppendElement(mozilla::gfx::Color(color->red / 255.0,
+                                                  color->green / 255.0,
+                                                  color->blue / 255.0,
+                                                  color->alpha / 255.0));
+        layer++;
+    }
+    return true;
+}
+
 #ifdef XP_WIN
 
 /* static */
 bool
 gfxFontUtils::IsCffFont(const uint8_t* aFontData)
 {
     // this is only called after aFontData has passed basic validation,
     // so we know there is enough data present to allow us to read the version!
--- a/gfx/thebes/gfxFontUtils.h
+++ b/gfx/thebes/gfxFontUtils.h
@@ -600,16 +600,32 @@ struct KernTableVersion1 {
 };
 
 struct KernTableSubtableHeaderVersion1 {
     AutoSwap_PRUint32    length;
     AutoSwap_PRUint16    coverage;
     AutoSwap_PRUint16    tupleIndex;
 };
 
+struct COLRHeader {
+    AutoSwap_PRUint16    version;
+    AutoSwap_PRUint16    numBaseGlyphRecord;
+    AutoSwap_PRUint32    offsetBaseGlyphRecord;
+    AutoSwap_PRUint32    offsetLayerRecord;
+    AutoSwap_PRUint16    numLayerRecords;
+};
+
+struct CPALHeaderVersion0 {
+    AutoSwap_PRUint16    version;
+    AutoSwap_PRUint16    numPaletteEntries;
+    AutoSwap_PRUint16    numPalettes;
+    AutoSwap_PRUint16    numColorRecords;
+    AutoSwap_PRUint32    offsetFirstColorRecord;
+};
+
 #pragma pack()
 
 // Return just the highest bit of the given value, i.e., the highest
 // power of 2 that is <= value, or zero if the input value is zero.
 inline uint32_t
 FindHighestBit(uint32_t value)
 {
     // propagate highest bit into all lower bits of the value
@@ -921,16 +937,24 @@ public:
     
     // for a given font list pref name, set up a list of font names
     static void GetPrefsFontList(const char *aPrefName, 
                                  nsTArray<nsString>& aFontList);
 
     // generate a unique font name
     static nsresult MakeUniqueUserFontName(nsAString& aName);
 
+    // for color layer from glyph using COLR and CPAL tables
+    static bool ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL);
+    static bool GetColorGlyphLayers(hb_blob_t* aCOLR,
+                                    hb_blob_t* aCPAL,
+                                    uint32_t aGlyphId,
+                                    nsTArray<uint16_t> &aGlyphs,
+                                    nsTArray<mozilla::gfx::Color> &aColors);
+
 protected:
     static nsresult
     ReadNames(const char *aNameData, uint32_t aDataLen, uint32_t aNameID,
               int32_t aLangID, int32_t aPlatformID, nsTArray<nsString>& aNames);
 
     // convert opentype name-table platform/encoding/language values to a charset name
     // we can use to convert the name data to unicode, or "" if data is UTF16BE
     static const char*
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -328,23 +328,25 @@ private:
     size_t       mLength;
     const size_t mLimit;
     off_t        mOff;
 };
 
 static ots::TableAction
 OTSTableAction(uint32_t aTag, void *aUserData)
 {
-    // preserve Graphite and SVG tables
+    // preserve Graphite, color glyph and SVG tables
     if (aTag == TRUETYPE_TAG('S', 'i', 'l', 'f') ||
         aTag == TRUETYPE_TAG('S', 'i', 'l', 'l') ||
         aTag == TRUETYPE_TAG('G', 'l', 'o', 'c') ||
         aTag == TRUETYPE_TAG('G', 'l', 'a', 't') ||
         aTag == TRUETYPE_TAG('F', 'e', 'a', 't') ||
-        aTag == TRUETYPE_TAG('S', 'V', 'G', ' ')) {
+        aTag == TRUETYPE_TAG('S', 'V', 'G', ' ') ||
+        aTag == TRUETYPE_TAG('C', 'O', 'L', 'R') ||
+        aTag == TRUETYPE_TAG('C', 'P', 'A', 'L')) {
         return ots::TABLE_ACTION_PASSTHRU;
     }
     return ots::TABLE_ACTION_DEFAULT;
 }
 
 struct OTSCallbackUserData {
     gfxUserFontSet     *mFontSet;
     gfxMixedFontFamily *mFamily;