зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1740530 - patch 3 - Replace GetColorGlyphLayers and the layer-rendering code in gfxFont with a zero-copy implementation in COLRFonts. r=gfx-reviewers,lsalzman
Depends on D152037 Differential Revision: https://phabricator.services.mozilla.com/D152038
This commit is contained in:
Родитель
f80351990d
Коммит
15a732fbb5
|
@ -7,11 +7,17 @@
|
|||
#include "gfxFontUtils.h"
|
||||
#include "gfxUtils.h"
|
||||
#include "harfbuzz/hb.h"
|
||||
#include "mozilla/gfx/Helpers.h"
|
||||
#include "TextDrawTarget.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
#pragma pack(1)
|
||||
namespace { // anonymous namespace for implementation internals
|
||||
|
||||
#pragma pack(1) // ensure no padding is added to the COLR structs
|
||||
|
||||
struct LayerRecord;
|
||||
|
||||
struct COLRHeader {
|
||||
AutoSwap_PRUint16 version;
|
||||
|
@ -19,17 +25,16 @@ struct COLRHeader {
|
|||
AutoSwap_PRUint32 offsetBaseGlyphRecord;
|
||||
AutoSwap_PRUint32 offsetLayerRecord;
|
||||
AutoSwap_PRUint16 numLayerRecords;
|
||||
};
|
||||
|
||||
struct COLRBaseGlyphRecord {
|
||||
AutoSwap_PRUint16 glyphId;
|
||||
AutoSwap_PRUint16 firstLayerIndex;
|
||||
AutoSwap_PRUint16 numLayers;
|
||||
};
|
||||
const COLRBaseGlyphRecord* GetBaseGlyphRecords() const {
|
||||
return reinterpret_cast<const COLRBaseGlyphRecord*>(
|
||||
reinterpret_cast<const char*>(this) + offsetBaseGlyphRecord);
|
||||
}
|
||||
|
||||
struct COLRLayerRecord {
|
||||
AutoSwap_PRUint16 glyphId;
|
||||
AutoSwap_PRUint16 paletteEntryIndex;
|
||||
const LayerRecord* GetLayerRecords() const {
|
||||
return reinterpret_cast<const LayerRecord*>(
|
||||
reinterpret_cast<const char*>(this) + offsetLayerRecord);
|
||||
}
|
||||
};
|
||||
|
||||
struct CPALHeaderVersion0 {
|
||||
|
@ -48,8 +53,82 @@ struct CPALColorRecord {
|
|||
uint8_t alpha;
|
||||
};
|
||||
|
||||
struct PaintState {
|
||||
const COLRHeader* mHeader;
|
||||
const CPALColorRecord* mPalette;
|
||||
DrawTarget* mDrawTarget;
|
||||
ScaledFont* mScaledFont;
|
||||
DrawOptions mDrawOptions;
|
||||
sRGBColor mCurrentColor;
|
||||
};
|
||||
|
||||
static const CPALColorRecord* GetPaletteByIndex(hb_blob_t* aCPAL,
|
||||
uint32_t aIndex) {
|
||||
const auto* cpal = reinterpret_cast<const CPALHeaderVersion0*>(
|
||||
hb_blob_get_data(aCPAL, nullptr));
|
||||
const uint32_t offset = cpal->offsetFirstColorRecord;
|
||||
return reinterpret_cast<const CPALColorRecord*>(
|
||||
reinterpret_cast<const uint8_t*>(cpal) + offset);
|
||||
}
|
||||
|
||||
static DeviceColor DoGetColor(PaintState& aState, uint16_t aPaletteIndex,
|
||||
float aAlpha) {
|
||||
sRGBColor color;
|
||||
if (aPaletteIndex == 0xFFFF) {
|
||||
color = aState.mCurrentColor;
|
||||
} else {
|
||||
const CPALColorRecord& c = aState.mPalette[uint16_t(aPaletteIndex)];
|
||||
color = sRGBColor(c.red / 255.0, c.green / 255.0, c.blue / 255.0,
|
||||
c.alpha / 255.0 * aAlpha);
|
||||
}
|
||||
return ToDeviceColor(color);
|
||||
}
|
||||
|
||||
struct LayerRecord {
|
||||
AutoSwap_PRUint16 glyphId;
|
||||
AutoSwap_PRUint16 paletteEntryIndex;
|
||||
|
||||
bool Paint(PaintState& aState, float aAlpha, const Point& aPoint) const {
|
||||
Glyph glyph{uint16_t(glyphId), aPoint};
|
||||
// TODO validate glyph.mIndex is within range for the font
|
||||
GlyphBuffer buffer{&glyph, 1};
|
||||
aState.mDrawTarget->FillGlyphs(
|
||||
aState.mScaledFont, buffer,
|
||||
ColorPattern(DoGetColor(aState, paletteEntryIndex, aAlpha)),
|
||||
aState.mDrawOptions);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#pragma pack()
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
namespace mozilla::gfx {
|
||||
|
||||
struct COLRBaseGlyphRecord {
|
||||
AutoSwap_PRUint16 glyphId;
|
||||
AutoSwap_PRUint16 firstLayerIndex;
|
||||
AutoSwap_PRUint16 numLayers;
|
||||
|
||||
bool Paint(PaintState& aState, float aAlpha, const Point& aPoint) const {
|
||||
uint32_t layerIndex = uint16_t(firstLayerIndex);
|
||||
uint32_t end = layerIndex + uint16_t(numLayers);
|
||||
if (end > uint16_t(aState.mHeader->numLayerRecords)) {
|
||||
MOZ_ASSERT_UNREACHABLE("bad COLRv0 table");
|
||||
return false;
|
||||
}
|
||||
const auto* layers = aState.mHeader->GetLayerRecords();
|
||||
while (layerIndex < end) {
|
||||
if (!layers[layerIndex].Paint(aState, aAlpha, aPoint)) {
|
||||
return false;
|
||||
}
|
||||
++layerIndex;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
bool COLRFonts::ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL) {
|
||||
unsigned int colrLength;
|
||||
const COLRHeader* colr =
|
||||
|
@ -98,8 +177,7 @@ bool COLRFonts::ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (sizeof(COLRLayerRecord) * numLayerRecords >
|
||||
colrLength - offsetLayerRecord) {
|
||||
if (sizeof(LayerRecord) * numLayerRecords > colrLength - offsetLayerRecord) {
|
||||
// COLR layer record will be overflow
|
||||
return false;
|
||||
}
|
||||
|
@ -141,7 +219,7 @@ bool COLRFonts::ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL) {
|
|||
}
|
||||
}
|
||||
|
||||
const COLRLayerRecord* layer = reinterpret_cast<const COLRLayerRecord*>(
|
||||
const LayerRecord* layer = reinterpret_cast<const LayerRecord*>(
|
||||
reinterpret_cast<const uint8_t*>(colr) + offsetLayerRecord);
|
||||
|
||||
for (uint16_t i = 0; i < numLayerRecords; i++, layer++) {
|
||||
|
@ -155,80 +233,63 @@ bool COLRFonts::ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static int CompareBaseGlyph(const void* key, const void* data) {
|
||||
const COLRBaseGlyphRecord* COLRFonts::GetGlyphLayers(hb_blob_t* aCOLR,
|
||||
uint32_t aGlyphId) {
|
||||
unsigned int length;
|
||||
const COLRHeader* colr =
|
||||
reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &length));
|
||||
// This should never be called unless we have checked that the COLR table is
|
||||
// structurally valid, so it will be safe to read the header fields.
|
||||
MOZ_RELEASE_ASSERT(colr && length >= sizeof(COLRHeader), "bad COLR table!");
|
||||
auto compareBaseGlyph = [](const void* key, const void* data) -> int {
|
||||
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));
|
||||
bsearch((void*)(uintptr_t)aGlyphId, colr->GetBaseGlyphRecords(),
|
||||
uint16_t(colr->numBaseGlyphRecord), sizeof(COLRBaseGlyphRecord),
|
||||
compareBaseGlyph));
|
||||
}
|
||||
|
||||
bool COLRFonts::GetColorGlyphLayers(
|
||||
hb_blob_t* aCOLR, hb_blob_t* aCPAL, uint32_t aGlyphId,
|
||||
const mozilla::gfx::DeviceColor& aDefaultColor, nsTArray<uint16_t>& aGlyphs,
|
||||
nsTArray<mozilla::gfx::DeviceColor>& 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));
|
||||
if (uint16_t(layer->paletteEntryIndex) == 0xFFFF) {
|
||||
aColors.AppendElement(aDefaultColor);
|
||||
} else {
|
||||
const CPALColorRecord* color = reinterpret_cast<const CPALColorRecord*>(
|
||||
reinterpret_cast<const uint8_t*>(cpal) + offsetFirstColorRecord +
|
||||
sizeof(CPALColorRecord) * uint16_t(layer->paletteEntryIndex));
|
||||
aColors.AppendElement(
|
||||
mozilla::gfx::ToDeviceColor(mozilla::gfx::sRGBColor::FromU8(
|
||||
color->red, color->green, color->blue, color->alpha)));
|
||||
}
|
||||
layer++;
|
||||
}
|
||||
bool COLRFonts::PaintGlyphLayers(
|
||||
hb_blob_t* aCOLR, hb_blob_t* aCPAL, const COLRBaseGlyphRecord* aLayers,
|
||||
DrawTarget* aDrawTarget, layout::TextDrawTarget* aTextDrawer,
|
||||
ScaledFont* aScaledFont, DrawOptions aDrawOptions,
|
||||
const sRGBColor& aCurrentColor, const Point& aPoint) {
|
||||
// Default to opaque rendering (non-webrender applies alpha with a layer)
|
||||
float alpha = 1.0;
|
||||
if (aTextDrawer) {
|
||||
// defaultColor is the one that comes from CSS, so it has transparency info.
|
||||
bool hasComplexTransparency =
|
||||
0.0 < aCurrentColor.a && aCurrentColor.a < 1.0;
|
||||
if (hasComplexTransparency && uint16_t(aLayers->numLayers) > 1) {
|
||||
// WebRender doesn't support drawing multi-layer transparent color-glyphs,
|
||||
// as it requires compositing all the layers before applying transparency.
|
||||
// (pretend to succeed, output doesn't matter, we will emit a blob)
|
||||
aTextDrawer->FoundUnsupportedFeature();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool COLRFonts::HasColorLayersForGlyph(hb_blob_t* aCOLR, uint32_t aGlyphId) {
|
||||
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");
|
||||
|
||||
return LookForBaseGlyphRecord(colr, aGlyphId);
|
||||
// If we get here, then either alpha is 0 or 1, or there's only one layer
|
||||
// which shouldn't have composition issues. In all of these cases, applying
|
||||
// transparency directly to the glyph should work perfectly fine.
|
||||
//
|
||||
// Note that we must still emit completely transparent emoji, because they
|
||||
// might be wrapped in a shadow that uses the text run's glyphs.
|
||||
alpha = aCurrentColor.a;
|
||||
}
|
||||
auto* colr =
|
||||
reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, nullptr));
|
||||
auto* palette = GetPaletteByIndex(aCPAL, 0); // TODO: font-palette support
|
||||
PaintState state{colr, palette, aDrawTarget,
|
||||
aScaledFont, aDrawOptions, aCurrentColor};
|
||||
return aLayers->Paint(state, alpha, aPoint);
|
||||
}
|
||||
|
||||
} // end namespace mozilla::gfx
|
||||
|
|
|
@ -6,27 +6,36 @@
|
|||
#ifndef COLR_FONTS_H
|
||||
#define COLR_FONTS_H
|
||||
|
||||
#include "nsTArray.h"
|
||||
#include "mozilla/gfx/2D.h"
|
||||
|
||||
struct hb_blob_t;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace layout {
|
||||
class TextDrawTarget;
|
||||
}
|
||||
|
||||
namespace gfx {
|
||||
struct DeviceColor;
|
||||
struct COLRBaseGlyphRecord;
|
||||
|
||||
class COLRFonts {
|
||||
public:
|
||||
// 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,
|
||||
const mozilla::gfx::DeviceColor& aDefaultColor,
|
||||
nsTArray<uint16_t>& aGlyphs,
|
||||
nsTArray<mozilla::gfx::DeviceColor>& aColors);
|
||||
static bool HasColorLayersForGlyph(hb_blob_t* aCOLR, uint32_t aGlyphId);
|
||||
|
||||
static const COLRBaseGlyphRecord* GetGlyphLayers(hb_blob_t* aCOLR,
|
||||
uint32_t aGlyphId);
|
||||
|
||||
static bool PaintGlyphLayers(
|
||||
hb_blob_t* aCOLR, hb_blob_t* aCPAL, const COLRBaseGlyphRecord* aLayers,
|
||||
DrawTarget* aDrawTarget, layout::TextDrawTarget* aTextDrawer,
|
||||
ScaledFont* aScaledFont, DrawOptions aDrawOptions,
|
||||
const sRGBColor& aCurrentColor, const Point& aPoint);
|
||||
};
|
||||
|
||||
} // namespace gfx
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // COLR_FONTS_H
|
||||
|
|
|
@ -2464,63 +2464,25 @@ bool gfxFont::RenderSVGGlyph(gfxContext* aContext,
|
|||
|
||||
bool gfxFont::RenderColorGlyph(DrawTarget* aDrawTarget, gfxContext* aContext,
|
||||
layout::TextDrawTarget* aTextDrawer,
|
||||
mozilla::gfx::ScaledFont* scaledFont,
|
||||
mozilla::gfx::DrawOptions aDrawOptions,
|
||||
const mozilla::gfx::Point& aPoint,
|
||||
ScaledFont* aScaledFont,
|
||||
DrawOptions aDrawOptions, const Point& aPoint,
|
||||
uint32_t aGlyphId) const {
|
||||
AutoTArray<uint16_t, 8> layerGlyphs;
|
||||
AutoTArray<mozilla::gfx::DeviceColor, 8> layerColors;
|
||||
DeviceColor ctxColor;
|
||||
sRGBColor currentColor = aContext->GetDeviceColor(ctxColor)
|
||||
? sRGBColor::FromABGR(ctxColor.ToABGR())
|
||||
: sRGBColor::OpaqueBlack();
|
||||
|
||||
mozilla::gfx::DeviceColor ctxColor;
|
||||
if (!aContext->GetDeviceColor(ctxColor)) {
|
||||
ctxColor = ToDeviceColor(mozilla::gfx::sRGBColor::OpaqueBlack());
|
||||
if (const auto* layers =
|
||||
COLRFonts::GetGlyphLayers(GetFontEntry()->GetCOLR(), aGlyphId)) {
|
||||
return COLRFonts::PaintGlyphLayers(GetFontEntry()->GetCOLR(),
|
||||
GetFontEntry()->GetCPAL(), layers,
|
||||
aDrawTarget, aTextDrawer, aScaledFont,
|
||||
aDrawOptions, currentColor, aPoint);
|
||||
}
|
||||
if (!COLRFonts::GetColorGlyphLayers(GetFontEntry()->GetCOLR(),
|
||||
GetFontEntry()->GetCPAL(), aGlyphId,
|
||||
ctxColor, layerGlyphs, layerColors)) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Default to opaque rendering (non-webrender applies alpha with a layer)
|
||||
float alpha = 1.0;
|
||||
if (aTextDrawer) {
|
||||
// defaultColor is the one that comes from CSS, so it has transparency info.
|
||||
bool hasComplexTransparency = 0.f < ctxColor.a && ctxColor.a < 1.f;
|
||||
if (hasComplexTransparency && layerGlyphs.Length() > 1) {
|
||||
// WebRender doesn't support drawing multi-layer transparent color-glyphs,
|
||||
// as it requires compositing all the layers before applying transparency.
|
||||
// (pretend to succeed, output doesn't matter, we will emit a blob)
|
||||
aTextDrawer->FoundUnsupportedFeature();
|
||||
return true;
|
||||
}
|
||||
|
||||
// If we get here, then either alpha is 0 or 1, or there's only one layer
|
||||
// which shouldn't have composition issues. In all of these cases, applying
|
||||
// transparency directly to the glyph should work perfectly fine.
|
||||
//
|
||||
// Note that we must still emit completely transparent emoji, because they
|
||||
// might be wrapped in a shadow that uses the text run's glyphs.
|
||||
alpha = ctxColor.a;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
mozilla::gfx::DeviceColor layerColor = layerColors[layerIndex];
|
||||
layerColor.a *= alpha;
|
||||
aDrawTarget->FillGlyphs(scaledFont, buffer, ColorPattern(layerColor),
|
||||
aDrawOptions);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gfxFont::HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh) {
|
||||
// Bitmap fonts are assumed to provide "color" glyphs for all supported chars.
|
||||
gfxFontEntry* fe = GetFontEntry();
|
||||
|
@ -2544,7 +2506,7 @@ bool gfxFont::HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh) {
|
|||
}
|
||||
// Check if there is a COLR/CPAL or SVG glyph for this ID.
|
||||
if (fe->TryGetColorGlyphs() &&
|
||||
COLRFonts::HasColorLayersForGlyph(fe->GetCOLR(), gid)) {
|
||||
COLRFonts::GetGlyphLayers(fe->GetCOLR(), gid)) {
|
||||
return true;
|
||||
}
|
||||
if (fe->TryGetSVGData(this) && fe->HasSVGGlyph(gid)) {
|
||||
|
|
|
@ -58,9 +58,6 @@ namespace fontlist {
|
|||
struct Face;
|
||||
struct Family;
|
||||
} // namespace fontlist
|
||||
namespace gfx {
|
||||
struct DeviceColor;
|
||||
}
|
||||
} // namespace mozilla
|
||||
|
||||
typedef struct gr_face gr_face;
|
||||
|
|
Загрузка…
Ссылка в новой задаче