Bug 889401 - Part 2. Render color glyph using COLR/CPAL. r=jfkthame

This commit is contained in:
Makoto Kato 2014-05-26 19:07:24 +09:00
parent aa083bc07a
commit c34e56df8b
5 changed files with 431 additions and 5 deletions

View File

@ -123,9 +123,12 @@ gfxFontEntry::gfxFontEntry() :
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),
@ -153,9 +156,12 @@ gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) :
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),
@ -167,6 +173,14 @@ gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) :
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()) {
@ -482,6 +496,39 @@ gfxFontEntry::GetMathVariantsParts(uint32_t aGlyphID, bool aVertical,
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
*
@ -830,6 +877,18 @@ 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,
@ -2867,6 +2926,7 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
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
@ -2939,6 +2999,14 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
}
}
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,
@ -3001,13 +3069,14 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
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,
@ -3017,6 +3086,17 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
}
}
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);
@ -3157,6 +3237,18 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
}
}
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,
@ -3243,6 +3335,19 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
}
}
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);
@ -3343,6 +3448,73 @@ gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint, DrawMode aDrawMod
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)
{

View File

@ -389,6 +389,11 @@ public:
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;
}
@ -531,6 +536,7 @@ public:
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
@ -551,6 +557,10 @@ public:
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;
@ -2135,6 +2145,14 @@ protected:
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

View File

@ -11,6 +11,7 @@
#include "mozilla/ArrayUtils.h"
#include "gfxFontUtils.h"
#include "gfxColor.h"
#include "nsServiceManagerUtils.h"
@ -1439,6 +1440,215 @@ gfxFontUtils::ReadNames(const char *aNameData, uint32_t aDataLen,
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 */

View File

@ -605,6 +605,22 @@ struct KernTableSubtableHeaderVersion1 {
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
@ -926,6 +942,14 @@ public:
// 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,

View File

@ -333,13 +333,15 @@ private:
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;