/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "COLRFonts.h"
#include "gfxFontUtils.h"
#include "gfxUtils.h"
#include "harfbuzz/hb.h"
#include "harfbuzz/hb-ot.h"
#include "mozilla/gfx/Helpers.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "TextDrawTarget.h"
#include <limits>
using namespace mozilla;
using namespace mozilla::gfx;
namespace {
// anonymous namespace for implementation internals
#pragma pack(1)
// ensure no padding is added to the COLR structs
// Alias bigendian-reading types from gfxFontUtils to names used in the spec.
using int16 = AutoSwap_PRInt16;
using uint16 = AutoSwap_PRUint16;
using int32 = AutoSwap_PRInt32;
using uint32 = AutoSwap_PRUint32;
using FWORD = AutoSwap_PRInt16;
using UFWORD = AutoSwap_PRUint16;
using Offset16 = AutoSwap_PRUint16;
using Offset24 = AutoSwap_PRUint24;
using Offset32 = AutoSwap_PRUint32;
struct COLRv1Header;
struct ClipList;
struct LayerRecord;
struct BaseGlyphRecord;
struct DeltaSetIndexMap;
struct ItemVariationStore;
struct COLRHeader {
uint16 version;
uint16 numBaseGlyphRecords;
Offset32 baseGlyphRecordsOffset;
Offset32 layerRecordsOffset;
uint16 numLayerRecords;
const BaseGlyphRecord* GetBaseGlyphRecords()
const {
return reinterpret_cast<
const BaseGlyphRecord*>(
reinterpret_cast<
const char*>(
this) + baseGlyphRecordsOffset);
}
const LayerRecord* GetLayerRecords()
const {
return reinterpret_cast<
const LayerRecord*>(
reinterpret_cast<
const char*>(
this) + layerRecordsOffset);
}
bool Validate(uint64_t aLength)
const;
};
struct BaseGlyphPaintRecord {
uint16 glyphID;
Offset32 paintOffset;
};
struct BaseGlyphList {
uint32 numBaseGlyphPaintRecords;
// BaseGlyphPaintRecord baseGlyphPaintRecords[numBaseGlyphPaintRecords];
const BaseGlyphPaintRecord* baseGlyphPaintRecords()
const {
return reinterpret_cast<
const BaseGlyphPaintRecord*>(
this + 1);
}
bool Validate(
const COLRv1Header* aHeader, uint64_t aLength)
const;
};
struct LayerList {
uint32 numLayers;
// uint32 paintOffsets[numLayers];
const uint32* paintOffsets()
const {
return reinterpret_cast<
const uint32*>(
this + 1);
}
bool Validate(
const COLRv1Header* aHeader, uint64_t aLength)
const;
};
struct COLRv1Header {
COLRHeader base;
Offset32 baseGlyphListOffset;
Offset32 layerListOffset;
Offset32 clipListOffset;
Offset32 varIndexMapOffset;
Offset32 itemVariationStoreOffset;
bool Validate(uint64_t aLength)
const;
const BaseGlyphList* baseGlyphList()
const {
uint32_t offset = baseGlyphListOffset;
if (!offset) {
return nullptr;
}
const char* ptr =
reinterpret_cast<
const char*>(
this) + offset;
return reinterpret_cast<
const struct BaseGlyphList*>(ptr);
}
const LayerList* layerList()
const {
uint32_t offset = layerListOffset;
if (!offset) {
return nullptr;
}
const char* ptr =
reinterpret_cast<
const char*>(
this) + offset;
return reinterpret_cast<
const LayerList*>(ptr);
}
const struct ClipList* clipList()
const {
uint32_t offset = clipListOffset;
if (!offset) {
return nullptr;
}
const char* ptr =
reinterpret_cast<
const char*>(
this) + offset;
return reinterpret_cast<
const ClipList*>(ptr);
}
const struct DeltaSetIndexMap* varIndexMap()
const {
uint32_t offset = varIndexMapOffset;
if (!offset) {
return nullptr;
}
const char* ptr =
reinterpret_cast<
const char*>(
this) + offset;
return reinterpret_cast<
const DeltaSetIndexMap*>(ptr);
}
const struct ItemVariationStore* itemVariationStore()
const {
uint32_t offset = itemVariationStoreOffset;
if (!offset) {
return nullptr;
}
const char* ptr =
reinterpret_cast<
const char*>(
this) + offset;
return reinterpret_cast<
const ItemVariationStore*>(ptr);
}
const BaseGlyphPaintRecord* GetBaseGlyphPaint(uint32_t aGlyphId)
const;
};
struct PaintState {
union {
const COLRHeader* v0;
const COLRv1Header* v1;
} mHeader;
const sRGBColor* mPalette;
DrawTarget* mDrawTarget;
ScaledFont* mScaledFont;
const int* mCoords;
DrawOptions mDrawOptions;
uint32_t mCOLRLength;
sRGBColor mCurrentColor;
float mFontUnitsToPixels;
uint16_t mNumColors;
uint16_t mCoordCount;
nsTArray<uint32_t>* mVisited;
const char* COLRv1BaseAddr()
const {
return reinterpret_cast<
const char*>(mHeader.v1);
}
DeviceColor GetColor(uint16_t aPaletteIndex,
float aAlpha)
const;
// Convert from FUnits (either integer or Fixed 16.16) to device pixels.
template <
typename T>
float F2P(T aPixels)
const {
return mFontUnitsToPixels *
float(aPixels);
}
};
DeviceColor PaintState::GetColor(uint16_t aPaletteIndex,
float aAlpha)
const {
sRGBColor color;
if (aPaletteIndex < mNumColors) {
color = mPalette[uint16_t(aPaletteIndex)];
}
else if (aPaletteIndex == 0xffff) {
color = mCurrentColor;
}
else {
// Palette index out of range! Return transparent black.
color = sRGBColor();
}
color.a *= aAlpha;
return ToDeviceColor(color);
}
static bool DispatchPaint(
const PaintState& aState, uint32_t aOffset,
const Rect* aBounds
/* may be nullptr if unknown */);
static UniquePtr<Pattern> DispatchMakePattern(
const PaintState& aState,
uint32_t aOffset);
static Rect DispatchGetBounds(
const PaintState& aState, uint32_t aOffset);
static Matrix DispatchGetMatrix(
const PaintState& aState, uint32_t aOffset);
// Variation-data types
struct Fixed {
enum { kFractionBits = 16 };
operator float()
const {
return float(int32_t(value)) /
float(1 << kFractionBits);
}
int32_t intRepr()
const {
return int32_t(value); }
private:
int32 value;
};
struct F2DOT14 {
enum { kFractionBits = 14 };
operator float()
const {
return float(int16_t(value)) /
float(1 << kFractionBits);
}
int32_t intRepr()
const {
return int16_t(value); }
private:
int16 value;
};
// Saturating addition used for variation indexes to avoid wrap-around.
static uint32_t SatAdd(uint32_t a, uint32_t b) {
if (a <= std::numeric_limits<uint32_t>::max() - b) {
return a + b;
}
return std::numeric_limits<uint32_t>::max();
}
struct RegionAxisCoordinates {
F2DOT14 startCoord;
F2DOT14 peakCoord;
F2DOT14 endCoord;
};
struct VariationRegion {
// RegionAxisCoordinates regionAxes[axisCount];
const RegionAxisCoordinates* regionAxes()
const {
return reinterpret_cast<
const RegionAxisCoordinates*>(
this);
}
};
struct VariationRegionList {
uint16 axisCount;
uint16 regionCount;
// VariationRegion variationRegions[regionCount];
const char* variationRegionsBase()
const {
return reinterpret_cast<
const char*>(
this + 1);
}
size_t regionSize()
const {
return uint16_t(axisCount) *
sizeof(RegionAxisCoordinates);
}
const VariationRegion* getRegion(uint16_t i)
const {
if (i >= uint16_t(regionCount)) {
return nullptr;
}
return reinterpret_cast<
const VariationRegion*>(variationRegionsBase() +
i * regionSize());
}
bool Validate(
const COLRv1Header* aHeader, uint64_t aLength)
const {
if (variationRegionsBase() -
reinterpret_cast<
const char*>(aHeader) +
uint16_t(regionCount) * regionSize() >
aLength) {
return false;
}
return true;
}
};
struct DeltaSet {
// (int16 and int8)
// *or*
// (int32 and int16) deltaData[regionIndexCount];
};
struct DeltaSetIndexMap {
enum { INNER_INDEX_BIT_COUNT_MASK = 0x0f, MAP_ENTRY_SIZE_MASK = 0x30 };
uint8_t format;
uint8_t entryFormat;
union {
struct {
uint16 mapCount;
// uint8 mapData[variable];
} v0;
struct {
uint32 mapCount;
// uint8 mapData[variable];
} v1;
};
uint32_t entrySize()
const {
return (((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1);
}
uint32_t map(uint32_t aIndex)
const {
uint32_t mapCount;
const uint8_t* mapData;
switch (format) {
case 0:
mapCount = uint32_t(v0.mapCount);
mapData =
reinterpret_cast<
const uint8_t*>(&v0.mapCount) +
sizeof(v0.mapCount);
break;
case 1:
mapCount = uint32_t(v1.mapCount);
mapData =
reinterpret_cast<
const uint8_t*>(&v1.mapCount) +
sizeof(v1.mapCount);
break;
default:
// unknown DeltaSetIndexMap format
return aIndex;
}
if (!mapCount) {
return aIndex;
}
if (aIndex >= mapCount) {
aIndex = mapCount - 1;
}
const uint8_t* entryData = mapData + aIndex * entrySize();
uint32_t entry = 0;
for (uint32_t i = 0; i < entrySize(); ++i) {
entry = (entry << 8) + entryData[i];
}
uint16_t outerIndex =
entry >> ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1);
uint16_t innerIndex =
entry & ((1 << ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1)) - 1);
return (uint32_t(outerIndex) << 16) + innerIndex;
}
bool Validate(
const COLRv1Header* aHeader, uint64_t aLength)
const;
};
enum EntryFormatMasks {
INNER_INDEX_BIT_COUNT_MASK = 0x0f,
MAP_ENTRY_SIZE_MASK = 0x30
};
struct ItemVariationData {
enum { WORD_DELTA_COUNT_MASK = 0x7FFF, LONG_WORDS = 0x8000 };
uint16 itemCount;
uint16 wordDeltaCount;
uint16 regionIndexCount;
// uint16 regionIndexes[regionIndexCount];
const uint16* regionIndexes()
const {
return reinterpret_cast<
const uint16*>(
reinterpret_cast<
const char*>(
this + 1));
}
// DeltaSet deltaSets[itemCount];
const DeltaSet* deltaSets()
const {
return reinterpret_cast<
const DeltaSet*>(
reinterpret_cast<
const char*>(
this + 1) +
uint16_t(regionIndexCount) *
sizeof(uint16));
}
bool Validate(
const COLRv1Header* aHeader, uint64_t aLength)
const;
};
struct ItemVariationStore {
uint16 format;
Offset32 variationRegionListOffset;
uint16 itemVariationDataCount;
// Offset32 itemVariationDataOffsets[itemVariationDataCount];
const Offset32* itemVariationDataOffsets()
const {
return reinterpret_cast<
const Offset32*>(
reinterpret_cast<
const char*>(
this + 1));
}
const VariationRegionList* variationRegionList()
const {
return reinterpret_cast<
const VariationRegionList*>(
reinterpret_cast<
const char*>(
this) + variationRegionListOffset);
}
bool Validate(
const COLRv1Header* aHeader, uint64_t aLength)
const;
};
static int32_t ApplyVariation(
const PaintState& aState, int32_t aValue,
uint32_t aIndex) {
if (aIndex == 0xffffffff) {
return aValue;
}
const auto* store = aState.mHeader.v1->itemVariationStore();
if (!store || uint16_t(store->format) != 1) {
return aValue;
}
const DeltaSetIndexMap* map = aState.mHeader.v1->varIndexMap();
uint32_t mappedIndex = map ? map->map(aIndex) : aIndex;
uint16_t outerIndex = mappedIndex >> 16;
uint16_t innerIndex = mappedIndex & 0xffff;
const auto* itemVariationDataOffsets = store->itemVariationDataOffsets();
if (mappedIndex == 0xffffffff ||
outerIndex >= uint16_t(store->itemVariationDataCount) ||
!itemVariationDataOffsets[outerIndex]) {
return aValue;
}
const auto* regionList = store->variationRegionList();
if (outerIndex >= uint16_t(store->itemVariationDataCount)) {
return aValue;
}
const auto* variationData =
reinterpret_cast<
const ItemVariationData*>(
reinterpret_cast<
const char*>(store) +
itemVariationDataOffsets[outerIndex]);
if (innerIndex >= uint16_t(variationData->itemCount)) {
return aValue;
}
const auto* regionIndexes = variationData->regionIndexes();
uint16_t regionIndexCount = variationData->regionIndexCount;
const DeltaSet* deltaSets = variationData->deltaSets();
uint16_t wordDeltaCount = variationData->wordDeltaCount;
bool longWords = wordDeltaCount & ItemVariationData::LONG_WORDS;
wordDeltaCount &= ItemVariationData::WORD_DELTA_COUNT_MASK;
uint32_t deltaSetSize = (regionIndexCount + wordDeltaCount) << longWords;
const uint8_t* deltaData =
reinterpret_cast<
const uint8_t*>(deltaSets) + deltaSetSize * innerIndex;
uint16_t deltaSize = longWords ? 4 : 2;
int32_t result = aValue;
for (uint16_t i = 0; i < regionIndexCount; ++i, deltaData += deltaSize) {
if (i == wordDeltaCount) {
deltaSize >>= 1;
}
const auto* region = regionList->getRegion(uint16_t(regionIndexes[i]));
if (!region) {
return aValue;
}
// XXX Should we do the calculations here in fixed-point? Check spec.
float scalar = -1.0;
for (uint16_t axisIndex = 0; axisIndex < uint16_t(regionList->axisCount);
++axisIndex) {
const auto& axis = region->regionAxes()[axisIndex];
float peak = axis.peakCoord;
if (peak == 0.0) {
// This axis cannot contribute to scalar.
continue;
}
float start = axis.startCoord;
float end = axis.endCoord;
float value = axisIndex < aState.mCoordCount
?
float(aState.mCoords[axisIndex]) / 16384.0f
: 0.0;
if (value < start || value > end) {
// Out of range: this region is not applicable.
scalar = -1.0;
break;
}
if (scalar < 0.0) {
scalar = 1.0;
}
if (value == peak) {
continue;
}
if (value < peak && peak > start) {
scalar *= (value - start) / (peak - start);
}
else if (value > peak && peak < end) {
scalar *= (end - value) / (end - peak);
}
}
if (scalar <= 0.0) {
continue;
}
int32_t delta = *
reinterpret_cast<
const int8_t*>(deltaData);
// sign-extend
for (uint16_t j = 1; j < deltaSize; ++j) {
delta = (delta << 8) | deltaData[j];
}
delta = int32_t(floorf((
float(delta) * scalar) + 0.5f));
result += delta;
}
return result;
};
static float ApplyVariation(
const PaintState& aState, Fixed aValue,
uint32_t aIndex) {
return float(ApplyVariation(aState, aValue.intRepr(), aIndex)) /
(1 << Fixed::kFractionBits);
}
static float ApplyVariation(
const PaintState& aState, F2DOT14 aValue,
uint32_t aIndex) {
return float(ApplyVariation(aState, aValue.intRepr(), aIndex)) /
(1 << F2DOT14::kFractionBits);
}
struct ClipBoxFormat1 {
enum { kFormat = 1 };
uint8_t format;
FWORD xMin;
FWORD yMin;
FWORD xMax;
FWORD yMax;
Rect GetRect(
const PaintState& aState)
const {
MOZ_ASSERT(format == kFormat);
int32_t x0 = int16_t(xMin);
int32_t y0 = int16_t(yMin);
int32_t x1 = int16_t(xMax);
int32_t y1 = int16_t(yMax);
// Flip the y-coordinates to map from OpenType to Moz2d space.
return Rect(aState.F2P(x0), -aState.F2P(y1), aState.F2P(x1 - x0),
aState.F2P(y1 - y0));
}
};
struct ClipBoxFormat2 :
public ClipBoxFormat1 {
enum { kFormat = 2 };
uint32 varIndexBase;
Rect GetRect(
const PaintState& aState)
const {
MOZ_ASSERT(format == kFormat);
int32_t x0 = ApplyVariation(aState, int16_t(xMin), varIndexBase);
int32_t y0 = ApplyVariation(aState, int16_t(yMin), SatAdd(varIndexBase, 1));
int32_t x1 = ApplyVariation(aState, int16_t(xMax), SatAdd(varIndexBase, 2));
int32_t y1 = ApplyVariation(aState, int16_t(yMax), SatAdd(varIndexBase, 3));
return Rect(aState.F2P(x0), -aState.F2P(y1), aState.F2P(x1 - x0),
aState.F2P(y1 - y0));
}
};
struct Clip {
uint16 startGlyphID;
uint16 endGlyphID;
Offset24 clipBoxOffset;
Rect GetRect(
const PaintState& aState)
const {
uint32_t offset = aState.mHeader.v1->clipListOffset + clipBoxOffset;
const auto* box = aState.COLRv1BaseAddr() + offset;
switch (*box) {
case 1:
return reinterpret_cast<
const ClipBoxFormat1*>(box)->GetRect(aState);
case 2:
return reinterpret_cast<
const ClipBoxFormat2*>(box)->GetRect(aState);
default:
// unknown ClipBoxFormat
break;
}
return Rect();
}
bool Validate(
const COLRv1Header* aHeader, uint64_t aLength)
const;
};
struct ClipList {
uint8_t format;
uint32 numClips;
// Clip clips[numClips]
const Clip* clips()
const {
return reinterpret_cast<
const Clip*>(
this + 1); }
const Clip* GetClip(uint32_t aGlyphId)
const {
auto compare = [](
const void* key,
const void* data) ->
int {
uint32_t glyphId = (uint32_t)(uintptr_t)key;
const auto* clip =
reinterpret_cast<
const Clip*>(data);
uint32_t start = uint16_t(clip->startGlyphID);
uint32_t end = uint16_t(clip->endGlyphID);
if (start <= glyphId && end >= glyphId) {
return 0;
}
return start > glyphId ? -1 : 1;
};
return reinterpret_cast<
const Clip*>(bsearch((
void*)(uintptr_t)aGlyphId,
clips(), uint32_t(numClips),
sizeof(Clip), compare));
}
bool Validate(
const COLRv1Header* aHeader, uint64_t aLength)
const;
};
struct LayerRecord {
uint16 glyphId;
uint16 paletteEntryIndex;
bool Paint(
const PaintState& aState,
float aAlpha,
const Point& aPoint)
const {
Glyph glyph{uint16_t(glyphId), aPoint};
GlyphBuffer buffer{&glyph, 1};
aState.mDrawTarget->FillGlyphs(
aState.mScaledFont, buffer,
ColorPattern(aState.GetColor(paletteEntryIndex, aAlpha)),
aState.mDrawOptions);
return true;
}
};
struct BaseGlyphRecord {
uint16 glyphId;
uint16 firstLayerIndex;
uint16 numLayers;
bool Paint(
const 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.v0->numLayerRecords)) {
MOZ_ASSERT_UNREACHABLE(
"bad COLRv0 table");
return false;
}
const auto* layers = aState.mHeader.v0->GetLayerRecords();
while (layerIndex < end) {
if (!layers[layerIndex].Paint(aState, aAlpha, aPoint)) {
return false;
}
++layerIndex;
}
return true;
}
};
struct ColorStop {
F2DOT14 stopOffset;
uint16 paletteIndex;
F2DOT14 alpha;
float GetStopOffset(
const PaintState& aState)
const {
return stopOffset; }
uint16_t GetPaletteIndex()
const {
return paletteIndex; }
float GetAlpha(
const PaintState& aState)
const {
return alpha; }
};
struct VarColorStop :
public ColorStop {
uint32 varIndexBase;
float GetStopOffset(
const PaintState& aState)
const {
return ApplyVariation(aState, stopOffset, varIndexBase);
}
float GetAlpha(
const PaintState& aState)
const {
return ApplyVariation(aState, alpha, SatAdd(varIndexBase, 1));
}
};
template <
typename T>
struct ColorLineT {
enum { EXTEND_PAD = 0, EXTEND_REPEAT = 1, EXTEND_REFLECT = 2 };
uint8_t extend;
uint16 numStops;
const T* colorStops()
const {
return reinterpret_cast<
const T*>(
this + 1); }
// If the color line has only one stop, return it as a simple ColorPattern.
UniquePtr<Pattern> AsSolidColor(
const PaintState& aState)
const {
if (uint16_t(numStops) != 1) {
return nullptr;
}
const auto* stop = colorStops();
return MakeUnique<ColorPattern>(
aState.GetColor(stop->GetPaletteIndex(), stop->GetAlpha(aState)));
}
// Retrieve the color stops into an array of GradientStop records. The stops
// are normalized to the range [0 .. 1], and the original offsets of the
// first and last stops are returned.
// If aReverse is true, the color line is reversed.
void CollectGradientStops(
const PaintState& aState,
nsTArray<GradientStop>& aStops,
float* aFirstStop,
float* aLastStop,
bool aReverse =
false)
const {
MOZ_ASSERT(aStops.IsEmpty());
uint16_t count = numStops;
if (!count) {
return;
}
const auto* stop = colorStops();
if (
reinterpret_cast<
const char*>(stop) + count *
sizeof(T) >
aState.COLRv1BaseAddr() + aState.mCOLRLength) {
return;
}
aStops.SetCapacity(count);
for (uint16_t i = 0; i < count; ++i, ++stop) {
DeviceColor color =
aState.GetColor(stop->GetPaletteIndex(), stop->GetAlpha(aState));
aStops.AppendElement(GradientStop{stop->GetStopOffset(aState), color});
}
if (count == 1) {
*aFirstStop = *aLastStop = aStops[0].offset;
return;
}
aStops.StableSort();
if (aReverse) {
float a = aStops[0].offset;
float b = aStops.LastElement().offset;
aStops.Reverse();
for (
auto& gs : aStops) {
gs.offset = a + b - gs.offset;
}
}
// Normalize stops to the range 0.0 .. 1.0, and return the original
// start & end.
// Note that if all stops are at the same offset, no normalization
// will be done.
*aFirstStop = aStops[0].offset;
*aLastStop = aStops.LastElement().offset;
if ((*aLastStop > *aFirstStop) &&
(*aLastStop != 1.0f || *aFirstStop != 0.0f)) {
float f = 1.0f / (*aLastStop - *aFirstStop);
for (
auto& gs : aStops) {
gs.offset = (gs.offset - *aFirstStop) * f;
}
}
}
// Create a gfx::GradientStops representing the given color line stops,
// applying our extend mode.
already_AddRefed<GradientStops> MakeGradientStops(
const PaintState& aState, nsTArray<GradientStop>& aStops)
const {
auto mapExtendMode = [](uint8_t aExtend) -> ExtendMode {
switch (aExtend) {
case EXTEND_REPEAT:
return ExtendMode::REPEAT;
case EXTEND_REFLECT:
return ExtendMode::REFLECT;
case EXTEND_PAD:
default:
return ExtendMode::CLAMP;
}
};
return aState.mDrawTarget->CreateGradientStops(
aStops.Elements(), aStops.Length(), mapExtendMode(extend));
}
already_AddRefed<GradientStops> MakeGradientStops(
const PaintState& aState,
float* aFirstStop,
float* aLastStop,
bool aReverse =
false)
const {
AutoTArray<GradientStop, 8> stops;
CollectGradientStops(aState, stops, aFirstStop, aLastStop, aReverse);
if (stops.IsEmpty()) {
return nullptr;
}
return MakeGradientStops(aState, stops);
}
};
using ColorLine = ColorLineT<ColorStop>;
using VarColorLine = ColorLineT<VarColorStop>;
// Used to check for cycles in the paint graph, and bail out to avoid infinite
// recursion when traversing the graph in Paint() or GetBoundingRect(). (Only
// PaintColrLayers and PaintColrGlyph can cause cycles; all other paint types
// have only forward references within the table.)
#define IF_CYCLE_RETURN(retval) \
if (aState.mVisited->Contains(aOffset)) { \
return retval; \
} \
aState.mVisited->AppendElement(aOffset); \
ScopeExit e([aState]() { aState.mVisited->RemoveLastElement(); })
struct PaintColrLayers {
enum { kFormat = 1 };
uint8_t format;
uint8_t numLayers;
uint32 firstLayerIndex;
bool Paint(
const PaintState& aState, uint32_t aOffset,
const Rect* aBounds)
const {
MOZ_ASSERT(format == kFormat);
IF_CYCLE_RETURN(
true);
const auto* layerList = aState.mHeader.v1->layerList();
if (!layerList) {
return false;
}
if (uint64_t(firstLayerIndex) + numLayers > layerList->numLayers) {
return false;
}
const auto* paintOffsets = layerList->paintOffsets() + firstLayerIndex;
for (uint32_t i = 0; i < numLayers; i++) {
if (!DispatchPaint(aState,
aState.mHeader.v1->layerListOffset + paintOffsets[i],
aBounds)) {
return false;
}
}
return true;
}
Rect GetBoundingRect(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
IF_CYCLE_RETURN(Rect());
const auto* layerList = aState.mHeader.v1->layerList();
if (!layerList) {
return Rect();
}
if (uint64_t(firstLayerIndex) + numLayers > layerList->numLayers) {
return Rect();
}
Rect result;
const auto* paintOffsets = layerList->paintOffsets() + firstLayerIndex;
for (uint32_t i = 0; i < numLayers; i++) {
result = result.
Union(DispatchGetBounds(
aState, aState.mHeader.v1->layerListOffset + paintOffsets[i]));
}
return result;
}
};
struct PaintPatternBase {
bool Paint(
const PaintState& aState, uint32_t aOffset,
const Rect* aBounds)
const {
Matrix m = aState.mDrawTarget->GetTransform();
if (m.Invert()) {
if (
auto pattern = DispatchMakePattern(aState, aOffset)) {
aState.mDrawTarget->FillRect(
m.TransformBounds(IntRectToRect(aState.mDrawTarget->GetRect())),
*pattern, aState.mDrawOptions);
return true;
}
}
return false;
}
Rect GetBoundingRect(
const PaintState& aState, uint32_t aOffset)
const {
return Rect();
}
};
struct PaintSolid :
public PaintPatternBase {
enum { kFormat = 2 };
uint8_t format;
uint16 paletteIndex;
F2DOT14 alpha;
UniquePtr<Pattern> MakePattern(
const PaintState& aState,
uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
return MakeUnique<ColorPattern>(aState.GetColor(paletteIndex, alpha));
}
};
struct PaintVarSolid :
public PaintSolid {
enum { kFormat = 3 };
uint32 varIndexBase;
UniquePtr<Pattern> MakePattern(
const PaintState& aState,
uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
return MakeUnique<ColorPattern>(aState.GetColor(
paletteIndex, ApplyVariation(aState, alpha, varIndexBase)));
}
};
struct PaintLinearGradient :
public PaintPatternBase {
enum { kFormat = 4 };
uint8_t format;
Offset24 colorLineOffset;
FWORD x0;
FWORD y0;
FWORD x1;
FWORD y1;
FWORD x2;
FWORD y2;
UniquePtr<Pattern> MakePattern(
const PaintState& aState,
uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
uint32_t clOffset = aOffset + colorLineOffset;
if (clOffset +
sizeof(ColorLine) +
sizeof(ColorStop) > aState.mCOLRLength) {
return nullptr;
}
const auto* colorLine =
reinterpret_cast<
const ColorLine*>(aState.COLRv1BaseAddr() + clOffset);
Point p0(aState.F2P(int16_t(x0)), aState.F2P(int16_t(y0)));
Point p1(aState.F2P(int16_t(x1)), aState.F2P(int16_t(y1)));
Point p2(aState.F2P(int16_t(x2)), aState.F2P(int16_t(y2)));
return NormalizeAndMakeGradient(aState, colorLine, p0, p1, p2);
}
template <
typename T>
UniquePtr<Pattern> NormalizeAndMakeGradient(
const PaintState& aState,
const T* aColorLine, Point p0,
Point p1, Point p2)
const {
// Ill-formed gradient should not be rendered.
if (p1 == p0 || p2 == p0) {
return MakeUnique<ColorPattern>(DeviceColor());
}
UniquePtr<Pattern> solidColor = aColorLine->AsSolidColor(aState);
if (solidColor) {
return solidColor;
}
float firstStop, lastStop;
AutoTArray<GradientStop, 8> stopArray;
aColorLine->CollectGradientStops(aState, stopArray, &firstStop, &lastStop);
if (stopArray.IsEmpty()) {
return MakeUnique<ColorPattern>(DeviceColor());
}
if (firstStop != 0.0 || lastStop != 1.0) {
if (firstStop == lastStop) {
if (aColorLine->extend != T::EXTEND_PAD) {
return MakeUnique<ColorPattern>(DeviceColor());
}
// For extend-pad, when the color line is zero-length, we add a "fake"
// color stop to create a [0.0..1.0]-normalized color line, so that the
// projection of points below works as expected.
for (
auto& gs : stopArray) {
gs.offset = 0.0f;
}
stopArray.AppendElement(
GradientStop{1.0f, stopArray.LastElement().color});
lastStop += 1.0f;
}
// Adjust positions of the points to account for normalization of the
// color line stop offsets.
Point v = p1 - p0;
p0 += v * firstStop;
p1 -= v * (1.0f - lastStop);
// Move the rotation vector to maintain the same direction from p0.
p2 += v * firstStop;
}
Point p3;
if (FuzzyEqualsMultiplicative(p2.y, p0.y)) {
// rotation vector is horizontal
p3 = Point(p0.x, p1.y);
}
else if (FuzzyEqualsMultiplicative(p2.x, p0.x)) {
// rotation vector is vertical
p3 = Point(p1.x, p0.y);
}
else {
float m = (p2.y - p0.y) / (p2.x - p0.x);
// slope of line p0->p2
float mInv = -1.0f / m;
// slope of desired perpendicular p0->p3
float c1 = p0.y - mInv * p0.x;
// line p0->p3 is m * x - y + c1 = 0
float c2 = p1.y - m * p1.x;
// line p1->p3 is mInv * x - y + c2 = 0
float x3 = (c1 - c2) / (m - mInv);
float y3 = (c1 * m - c2 * mInv) / (m - mInv);
p3 = Point(x3, y3);
}
RefPtr stops = aColorLine->MakeGradientStops(aState, stopArray);
return MakeUnique<LinearGradientPattern>(p0, p3, std::move(stops),
Matrix::Scaling(1.0, -1.0));
}
};
struct PaintVarLinearGradient :
public PaintLinearGradient {
enum { kFormat = 5 };
uint32 varIndexBase;
UniquePtr<Pattern> MakePattern(
const PaintState& aState,
uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
uint32_t clOffset = aOffset + colorLineOffset;
if (clOffset +
sizeof(VarColorLine) +
sizeof(VarColorStop) >
aState.mCOLRLength) {
return nullptr;
}
const auto* colorLine =
reinterpret_cast<
const VarColorLine*>(
aState.COLRv1BaseAddr() + clOffset);
Point p0(aState.F2P(ApplyVariation(aState, int16_t(x0), varIndexBase)),
aState.F2P(
ApplyVariation(aState, int16_t(y0), SatAdd(varIndexBase, 1))));
Point p1(aState.F2P(
ApplyVariation(aState, int16_t(x1), SatAdd(varIndexBase, 2))),
aState.F2P(
ApplyVariation(aState, int16_t(y1), SatAdd(varIndexBase, 3))));
Point p2(aState.F2P(
ApplyVariation(aState, int16_t(x2), SatAdd(varIndexBase, 4))),
aState.F2P(
ApplyVariation(aState, int16_t(y2), SatAdd(varIndexBase, 5))));
return NormalizeAndMakeGradient(aState, colorLine, p0, p1, p2);
}
};
struct PaintRadialGradient :
public PaintPatternBase {
enum { kFormat = 6 };
uint8_t format;
Offset24 colorLineOffset;
FWORD x0;
FWORD y0;
UFWORD radius0;
FWORD x1;
FWORD y1;
UFWORD radius1;
UniquePtr<Pattern> MakePattern(
const PaintState& aState,
uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
uint32_t clOffset = aOffset + colorLineOffset;
if (clOffset +
sizeof(ColorLine) +
sizeof(ColorStop) > aState.mCOLRLength) {
return nullptr;
}
const auto* colorLine =
reinterpret_cast<
const ColorLine*>(aState.COLRv1BaseAddr() + clOffset);
Point c1(aState.F2P(int16_t(x0)), aState.F2P(int16_t(y0)));
Point c2(aState.F2P(int16_t(x1)), aState.F2P(int16_t(y1)));
float r1 = aState.F2P(uint16_t(radius0));
float r2 = aState.F2P(uint16_t(radius1));
return NormalizeAndMakeGradient(aState, colorLine, c1, c2, r1, r2);
}
// Helper function to trim the gradient stops array at the start or end.
void TruncateGradientStops(nsTArray<GradientStop>& aStops,
float aStart,
float aEnd)
const {
// For pad mode, we may need a sub-range of the line: figure out which
// stops to trim, and interpolate as needed at truncation point(s).
// (Currently this is only ever used to trim one end of the color line,
// so edge cases that may occur when trimming both ends are untested.)
MOZ_ASSERT(aStart == 0.0f || aEnd == 1.0f,
"Trimming both ends of color-line is untested!");
// Create a color that is |r| of the way from c1 to c2.
auto interpolateColor = [](DeviceColor c1, DeviceColor c2,
float r) {
return DeviceColor(
c2.r * r + c1.r * (1.0f - r), c2.g * r + c1.g * (1.0f - r),
c2.b * r + c1.b * (1.0f - r), c2.a * r + c1.a * (1.0f - r));
};
size_t count = aStops.Length();
MOZ_ASSERT(count > 1);
// Truncate at the start of the color line?
if (aStart > 0.0f) {
// Skip forward past any stops that can be dropped.
size_t i = 0;
while (i < count - 1 && aStops[i].offset < aStart) {
++i;
}
// If we're not truncating exactly at a color-stop offset, shift the
// preceding stop to the truncation offset and interpolate its color.
if (i && aStops[i].offset > aStart) {
auto& prev = aStops[i - 1];
auto& curr = aStops[i];
float ratio = (aStart - prev.offset) / (curr.offset - prev.offset);
prev.color = interpolateColor(prev.color, curr.color, ratio);
prev.offset = aStart;
--i;
// We don't want to remove this stop, as we adjusted it.
}
aStops.RemoveElementsAt(0, i);
// Re-normalize the remaining stops to the [0, 1] range.
if (aStart < 1.0f) {
float r = 1.0f / (1.0f - aStart);
for (
auto& gs : aStops) {
gs.offset = r * (gs.offset - aStart);
}
}
}
// Truncate at the end of the color line?
if (aEnd < 1.0f) {
// Skip back over any stops that can be dropped.
size_t i = count - 1;
while (i && aStops[i].offset > aEnd) {
--i;
}
// If we're not truncating exactly at a color-stop offset, shift the
// following stop to the truncation offset and interpolate its color.
if (i + 1 < count && aStops[i].offset < aEnd) {
auto& next = aStops[i + 1];
auto& curr = aStops[i];
float ratio = (aEnd - curr.offset) / (next.offset - curr.offset);
next.color = interpolateColor(curr.color, next.color, ratio);
next.offset = aEnd;
++i;
}
aStops.RemoveElementsAt(i + 1, count - i - 1);
// Re-normalize the remaining stops to the [0, 1] range.
if (aEnd > 0.0f) {
float r = 1.0f / aEnd;
for (
auto& gs : aStops) {
gs.offset = r * gs.offset;
}
}
}
}
template <
typename T>
UniquePtr<Pattern> NormalizeAndMakeGradient(
const PaintState& aState,
const T* aColorLine, Point c1,
Point c2,
float r1,
float r2)
const {
if ((c1 == c2 && r1 == r2) || (r1 == 0.0 && r2 == 0.0)) {
return MakeUnique<ColorPattern>(DeviceColor());
}
UniquePtr<Pattern> solidColor = aColorLine->AsSolidColor(aState);
if (solidColor) {
return solidColor;
}
float firstStop, lastStop;
AutoTArray<GradientStop, 8> stopArray;
aColorLine->CollectGradientStops(aState, stopArray, &firstStop, &lastStop);
if (stopArray.IsEmpty()) {
return MakeUnique<ColorPattern>(DeviceColor());
}
// If the color stop offsets had to be normalized to the [0, 1] range,
// adjust the circle positions and radii to match.
if (firstStop != 0.0f || lastStop != 1.0f) {
if (firstStop == lastStop) {
if (aColorLine->extend != T::EXTEND_PAD) {
return MakeUnique<ColorPattern>(DeviceColor());
}
// For extend-pad, when the color line is zero-length, we add a "fake"
// color stop to ensure we'll maintain the orientation of the cone,
// otherwise when we adjust circles to account for the normalized color
// stops, the centers will coalesce and the cone or cylinder collapses.
for (
auto& gs : stopArray) {
gs.offset = 0.0f;
}
stopArray.AppendElement(
GradientStop{1.0f, stopArray.LastElement().color});
lastStop += 1.0f;
}
// Adjust centers along the vector between them, and scale radii for
// gradient line defined from 0.0 to 1.0.
Point vec = c2 - c1;
c1 += vec * firstStop;
c2 -= vec * (1.0f - lastStop);
float deltaR = r2 - r1;
r1 = r1 + deltaR * firstStop;
r2 = r2 - deltaR * (1.0f - lastStop);
}
if ((r1 < 0.0f || r2 < 0.0f) && aColorLine->extend == T::EXTEND_PAD) {
// For EXTEND_PAD, we can restrict the gradient definition to just its
// visible portion because the shader doesn't need to see any part of the
// color line that extends into the negative-radius "virtual cone".
if (r1 < 0.0f && r2 < 0.0f) {
// If both radii are negative, then only the color at the closer circle
// will appear in the projected positive cone (or if they're equal,
// nothing will be visible at all).
if (r1 == r2) {
return MakeUnique<ColorPattern>(DeviceColor());
}
// The defined range of the color line is entirely in the invisible
// cone; all that will project into visible space is a single color.
if (r1 < r2) {
// Keep only the last color stop.
stopArray.RemoveElementsAt(0, stopArray.Length() - 1);
}
else {
// Keep only the first color stop.
stopArray.RemoveElementsAt(1, stopArray.Length() - 1);
}
}
else {
// Truncate the gradient at the tip of the visible cone: find the color
// stops closest to that point and interpolate between them.
if (r1 < r2) {
float start = r1 / (r1 - r2);
TruncateGradientStops(stopArray, start, 1.0f);
r1 = 0.0f;
c1 = c1 * (1.0f - start) + c2 * start;
}
else if (r2 < r1) {
float end = 1.0f - r2 / (r2 - r1);
TruncateGradientStops(stopArray, 0.0f, end);
r2 = 0.0f;
c2 = c1 * (1.0f - end) + c2 * end;
}
}
}
// Handle negative radii, which the shader won't understand directly, by
// projecting the circles along the cones such that both radii are positive.
if (r1 < 0.0f || r2 < 0.0f) {
float deltaR = r2 - r1;
// If deltaR is zero, then nothing is visible because the cone has
// degenerated into a negative-radius cylinder, and does not project
// into visible space at all.
if (deltaR == 0.0f) {
return MakeUnique<ColorPattern>(DeviceColor());
}
Point vec = c2 - c1;
if (aColorLine->extend == T::EXTEND_REFLECT) {
deltaR *= 2.0f;
vec = vec * 2.0f;
}
if (r2 < r1) {
vec = -vec;
deltaR = -deltaR;
}
// Number of repeats by which we need to shift.
float n = std::ceil(std::max(-r1, -r2) / deltaR);
deltaR *= n;
r1 += deltaR;
r2 += deltaR;
vec = vec * n;
c1 += vec;
c2 += vec;
}
RefPtr stops = aColorLine->MakeGradientStops(aState, stopArray);
if (!stops) {
return MakeUnique<ColorPattern>(DeviceColor());
}
return MakeUnique<RadialGradientPattern>(c1, c2, r1, r2, std::move(stops),
Matrix::Scaling(1.0, -1.0));
}
};
struct PaintVarRadialGradient :
public PaintRadialGradient {
enum { kFormat = 7 };
uint32 varIndexBase;
UniquePtr<Pattern> MakePattern(
const PaintState& aState,
uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
uint32_t clOffset = aOffset + colorLineOffset;
if (clOffset +
sizeof(VarColorLine) +
sizeof(VarColorStop) >
aState.mCOLRLength) {
return nullptr;
}
const auto* colorLine =
reinterpret_cast<
const VarColorLine*>(
aState.COLRv1BaseAddr() + clOffset);
Point c1(aState.F2P(ApplyVariation(aState, int16_t(x0), varIndexBase)),
aState.F2P(
ApplyVariation(aState, int16_t(y0), SatAdd(varIndexBase, 1))));
float r1 = aState.F2P(
ApplyVariation(aState, uint16_t(radius0), SatAdd(varIndexBase, 2)));
Point c2(aState.F2P(
ApplyVariation(aState, int16_t(x1), SatAdd(varIndexBase, 3))),
aState.F2P(
ApplyVariation(aState, int16_t(y1), SatAdd(varIndexBase, 4))));
float r2 = aState.F2P(
ApplyVariation(aState, uint16_t(radius1), SatAdd(varIndexBase, 5)));
return NormalizeAndMakeGradient(aState, colorLine, c1, c2, r1, r2);
}
};
struct PaintSweepGradient :
public PaintPatternBase {
enum { kFormat = 8 };
uint8_t format;
Offset24 colorLineOffset;
FWORD centerX;
FWORD centerY;
F2DOT14 startAngle;
F2DOT14 endAngle;
UniquePtr<Pattern> MakePattern(
const PaintState& aState,
uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
uint32_t clOffset = aOffset + colorLineOffset;
if (clOffset +
sizeof(ColorLine) +
sizeof(ColorStop) > aState.mCOLRLength) {
return nullptr;
}
const auto* colorLine =
reinterpret_cast<
const ColorLine*>(aState.COLRv1BaseAddr() + clOffset);
float start =
float(startAngle) + 1.0f;
float end =
float(endAngle) + 1.0f;
Point center(aState.F2P(int16_t(centerX)), aState.F2P(int16_t(centerY)));
return NormalizeAndMakeGradient(aState, colorLine, center, start, end);
}
template <
typename T>
UniquePtr<Pattern> NormalizeAndMakeGradient(
const PaintState& aState,
const T* aColorLine,
Point aCenter,
float aStart,
float aEnd)
const {
if (aStart == aEnd && aColorLine->extend != T::EXTEND_PAD) {
return MakeUnique<ColorPattern>(DeviceColor());
}
UniquePtr<Pattern> solidColor = aColorLine->AsSolidColor(aState);
if (solidColor) {
return solidColor;
}
// ConicGradientPattern works counterclockwise. If the gradient is defined
// clockwise (with aStart greater than aEnd), we'll reverse the color line
// and swap the start and end angles.
bool reverse = aEnd < aStart;
float firstStop, lastStop;
RefPtr stops =
aColorLine->MakeGradientStops(aState, &firstStop, &lastStop, reverse);
if (!stops) {
return nullptr;
}
if (firstStop != 0.0 || lastStop != 1.0) {
if (firstStop == lastStop) {
if (aColorLine->extend != T::EXTEND_PAD) {
return MakeUnique<ColorPattern>(DeviceColor());
}
}
else {
float sweep = aEnd - aStart;
aStart = aStart + sweep * firstStop;
aEnd = aStart + sweep * (lastStop - firstStop);
}
}
if (reverse) {
std::swap(aStart, aEnd);
}
return MakeUnique<ConicGradientPattern>(aCenter, M_PI / 2.0, aStart / 2.0,
aEnd / 2.0, std::move(stops),
Matrix::Scaling(1.0, -1.0));
}
};
struct PaintVarSweepGradient :
public PaintSweepGradient {
enum { kFormat = 9 };
uint32 varIndexBase;
UniquePtr<Pattern> MakePattern(
const PaintState& aState,
uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
uint32_t clOffset = aOffset + colorLineOffset;
if (clOffset +
sizeof(VarColorLine) +
sizeof(VarColorStop) >
aState.mCOLRLength) {
return nullptr;
}
const auto* colorLine =
reinterpret_cast<
const VarColorLine*>(
aState.COLRv1BaseAddr() + clOffset);
float start =
ApplyVariation(aState, startAngle, SatAdd(varIndexBase, 2)) + 1.0f;
float end =
ApplyVariation(aState, endAngle, SatAdd(varIndexBase, 3)) + 1.0f;
Point center(
aState.F2P(ApplyVariation(aState, int16_t(centerX), varIndexBase)),
aState.F2P(
ApplyVariation(aState, int16_t(centerY), SatAdd(varIndexBase, 1))));
return NormalizeAndMakeGradient(aState, colorLine, center, start, end);
}
};
struct PaintGlyph {
enum { kFormat = 10 };
uint8_t format;
Offset24 paintOffset;
uint16 glyphID;
bool Paint(
const PaintState& aState, uint32_t aOffset,
const Rect* aBounds)
const {
MOZ_ASSERT(format == kFormat);
if (!paintOffset) {
return true;
}
Glyph g{uint16_t(glyphID), Point()};
GlyphBuffer buffer{&g, 1};
// If the paint is a simple fill (rather than a sub-graph of further paint
// records), we can just use FillGlyphs to render it instead of setting up
// a clip.
UniquePtr<Pattern> fillPattern =
DispatchMakePattern(aState, aOffset + paintOffset);
if (fillPattern) {
// On macOS we can't use FillGlyphs because when we render the glyph,
// Core Text's own color font support may step in and ignore the
// pattern. So to avoid this, fill the glyph as a path instead.
#if XP_MACOSX
RefPtr<Path> path = GetPathForGlyphs(aState, buffer);
aState.mDrawTarget->Fill(path, *fillPattern, aState.mDrawOptions);
#else
aState.mDrawTarget->FillGlyphs(aState.mScaledFont, buffer, *fillPattern,
aState.mDrawOptions);
#endif
return true;
}
RefPtr<Path> path = GetPathForGlyphs(aState, buffer);
aState.mDrawTarget->PushClip(path);
bool ok = DispatchPaint(aState, aOffset + paintOffset, aBounds);
aState.mDrawTarget->PopClip();
return ok;
}
Rect GetBoundingRect(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
Glyph g{uint16_t(glyphID), Point()};
GlyphBuffer buffer{&g, 1};
RefPtr<Path> path = GetPathForGlyphs(aState, buffer);
return path->GetFastBounds();
}
private:
RefPtr<Path> GetPathForGlyphs(
const PaintState& aState,
const GlyphBuffer& buffer)
const {
if (aState.mDrawTarget->GetBackendType() == BackendType::WEBRENDER_TEXT) {
RefPtr dt = gfxPlatform::ThreadLocalScreenReferenceDrawTarget();
return aState.mScaledFont->GetPathForGlyphs(buffer, dt);
}
return aState.mScaledFont->GetPathForGlyphs(buffer, aState.mDrawTarget);
}
};
struct PaintColrGlyph {
enum { kFormat = 11 };
uint8_t format;
uint16 glyphID;
// Factored out as a helper because this is also used by the top-level
// PaintGlyphGraph function.
static bool DoPaint(
const PaintState& aState,
const BaseGlyphPaintRecord* aBaseGlyphPaint,
uint32_t aGlyphId,
const Rect* aBounds) {
AutoPopClips clips(aState.mDrawTarget);
Rect clipRect;
if (
const auto* clipList = aState.mHeader.v1->clipList()) {
if (
const auto* clip = clipList->GetClip(aGlyphId)) {
clipRect = clip->GetRect(aState);
clips.PushClipRect(clipRect);
if (!aBounds) {
aBounds = &clipRect;
}
}
}
return DispatchPaint(
aState,
aState.mHeader.v1->baseGlyphListOffset + aBaseGlyphPaint->paintOffset,
aBounds);
}
bool Paint(
const PaintState& aState, uint32_t aOffset,
const Rect* aBounds)
const {
MOZ_ASSERT(format == kFormat);
IF_CYCLE_RETURN(
true);
const auto* base = aState.mHeader.v1->GetBaseGlyphPaint(glyphID);
return base ? DoPaint(aState, base, uint16_t(glyphID), aBounds) :
false;
}
Rect GetBoundingRect(
const PaintState& aState, uint32_t aOffset)
const {
IF_CYCLE_RETURN(Rect());
if (
const auto* clipList = aState.mHeader.v1->clipList()) {
if (
const auto* clip = clipList->GetClip(uint16_t(glyphID))) {
return clip->GetRect(aState);
}
}
if (
const auto* base =
aState.mHeader.v1->GetBaseGlyphPaint(uint16_t(glyphID))) {
return DispatchGetBounds(
aState, aState.mHeader.v1->baseGlyphListOffset + base->paintOffset);
}
return Rect();
}
};
#undef IF_CYCLE_RETURN
struct Affine2x3 {
Fixed xx;
Fixed yx;
Fixed xy;
Fixed yy;
Fixed dx;
Fixed dy;
Matrix AsMatrix(
const PaintState& aState)
const {
// Flip signs because of opposite y-axis direction in moz2d vs opentype.
return Matrix(
float(xx), -
float(yx), -
float(xy),
float(yy),
aState.F2P(
float(dx)), -aState.F2P(
float(dy)));
}
};
struct VarAffine2x3 :
public Affine2x3 {
uint32 varIndexBase;
Matrix AsMatrix(
const PaintState& aState)
const {
return Matrix(
ApplyVariation(aState, xx, varIndexBase),
-ApplyVariation(aState, yx, SatAdd(varIndexBase, 1)),
-ApplyVariation(aState, xy, SatAdd(varIndexBase, 2)),
ApplyVariation(aState, yy, SatAdd(varIndexBase, 3)),
aState.F2P(ApplyVariation(aState, dx, SatAdd(varIndexBase, 4))),
-aState.F2P(ApplyVariation(aState, dy, SatAdd(varIndexBase, 5))));
};
};
struct PaintTransformBase {
uint8_t format;
Offset24 paintOffset;
bool Paint(
const PaintState& aState, uint32_t aOffset,
const Rect* aBounds)
const {
if (!paintOffset) {
return true;
}
AutoRestoreTransform saveTransform(aState.mDrawTarget);
aState.mDrawTarget->ConcatTransform(DispatchGetMatrix(aState, aOffset));
return DispatchPaint(aState, aOffset + paintOffset, aBounds);
}
Rect GetBoundingRect(
const PaintState& aState, uint32_t aOffset)
const {
if (!paintOffset) {
return Rect();
}
Rect bounds = DispatchGetBounds(aState, aOffset + paintOffset);
bounds = DispatchGetMatrix(aState, aOffset).TransformBounds(bounds);
return bounds;
}
};
struct PaintTransform :
public PaintTransformBase {
enum { kFormat = 12 };
Offset24 transformOffset;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
if (aOffset + transformOffset +
sizeof(Affine2x3) > aState.mCOLRLength) {
return Matrix();
}
const auto* t =
reinterpret_cast<
const Affine2x3*>(
aState.COLRv1BaseAddr() + aOffset + transformOffset);
return t->AsMatrix(aState);
}
};
struct PaintVarTransform :
public PaintTransformBase {
enum { kFormat = 13 };
Offset24 transformOffset;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
if (aOffset + transformOffset +
sizeof(VarAffine2x3) > aState.mCOLRLength) {
return Matrix();
}
const auto* t =
reinterpret_cast<
const VarAffine2x3*>(
aState.COLRv1BaseAddr() + aOffset + transformOffset);
return t->AsMatrix(aState);
}
};
struct PaintTranslate :
public PaintTransformBase {
enum { kFormat = 14 };
FWORD dx;
FWORD dy;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
return Matrix::Translation(aState.F2P(int16_t(dx)),
-aState.F2P(int16_t(dy)));
}
};
struct PaintVarTranslate :
public PaintTranslate {
enum { kFormat = 15 };
uint32 varIndexBase;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
return Matrix::Translation(
aState.F2P(ApplyVariation(aState, int16_t(dx), varIndexBase)),
-aState.F2P(
ApplyVariation(aState, int16_t(dy), SatAdd(varIndexBase, 1))));
}
};
struct PaintScale :
public PaintTransformBase {
enum { kFormat = 16 };
F2DOT14 scaleX;
F2DOT14 scaleY;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
return Matrix::Scaling(
float(scaleX),
float(scaleY));
}
};
struct PaintVarScale :
public PaintScale {
enum { kFormat = 17 };
uint32 varIndexBase;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
return Matrix::Scaling(
ApplyVariation(aState, scaleX, varIndexBase),
ApplyVariation(aState, scaleY, SatAdd(varIndexBase, 1)));
}
};
struct PaintScaleAroundCenter :
public PaintTransformBase {
enum { kFormat = 18 };
F2DOT14 scaleX;
F2DOT14 scaleY;
FWORD centerX;
FWORD centerY;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY)));
return Matrix::Translation(center)
.PreScale(
float(scaleX),
float(scaleY))
.PreTranslate(-center);
}
};
struct PaintVarScaleAroundCenter :
public PaintScaleAroundCenter {
enum { kFormat = 19 };
uint32 varIndexBase;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX),
SatAdd(varIndexBase, 2))),
-aState.F2P(ApplyVariation(aState, int16_t(centerY),
SatAdd(varIndexBase, 3))));
return Matrix::Translation(center)
.PreScale(ApplyVariation(aState, scaleX, varIndexBase),
ApplyVariation(aState, scaleY, SatAdd(varIndexBase, 1)))
.PreTranslate(-center);
}
};
struct PaintScaleUniform :
public PaintTransformBase {
enum { kFormat = 20 };
F2DOT14 scale;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
return Matrix::Scaling(
float(scale),
float(scale));
}
};
struct PaintVarScaleUniform :
public PaintScaleUniform {
enum { kFormat = 21 };
uint32 varIndexBase;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
float sc = ApplyVariation(aState, scale, varIndexBase);
return Matrix::Scaling(sc, sc);
}
};
struct PaintScaleUniformAroundCenter :
public PaintTransformBase {
enum { kFormat = 22 };
F2DOT14 scale;
FWORD centerX;
FWORD centerY;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY)));
return Matrix::Translation(center)
.PreScale(
float(scale),
float(scale))
.PreTranslate(-center);
}
};
struct PaintVarScaleUniformAroundCenter :
public PaintScaleUniformAroundCenter {
enum { kFormat = 23 };
uint32 varIndexBase;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
float sc = ApplyVariation(aState, scale, varIndexBase);
Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX),
SatAdd(varIndexBase, 1))),
-aState.F2P(ApplyVariation(aState, int16_t(centerY),
SatAdd(varIndexBase, 2))));
return Matrix::Translation(center).PreScale(sc, sc).PreTranslate(-center);
}
};
struct PaintRotate :
public PaintTransformBase {
enum { kFormat = 24 };
F2DOT14 angle;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
return Matrix::Rotation(-
float(angle) *
float(M_PI));
}
};
struct PaintVarRotate :
public PaintRotate {
enum { kFormat = 25 };
uint32 varIndexBase;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
float ang = ApplyVariation(aState, angle, varIndexBase);
return Matrix::Rotation(-ang *
float(M_PI));
}
};
struct PaintRotateAroundCenter :
public PaintTransformBase {
enum { kFormat = 26 };
F2DOT14 angle;
FWORD centerX;
FWORD centerY;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY)));
return Matrix::Translation(center)
.PreRotate(-
float(angle) *
float(M_PI))
.PreTranslate(-center);
}
};
struct PaintVarRotateAroundCenter :
public PaintRotateAroundCenter {
enum { kFormat = 27 };
uint32 varIndexBase;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
float ang = ApplyVariation(aState, angle, varIndexBase);
Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX),
SatAdd(varIndexBase, 1))),
-aState.F2P(ApplyVariation(aState, int16_t(centerY),
SatAdd(varIndexBase, 2))));
return Matrix::Translation(center)
.PreRotate(-ang *
float(M_PI))
.PreTranslate(-center);
}
};
static inline Matrix SkewMatrix(
float aSkewX,
float aSkewY) {
float xy = tanf(aSkewX *
float(M_PI));
float yx = tanf(aSkewY *
float(M_PI));
return std::isnan(xy) || std::isnan(yx) ? Matrix()
: Matrix(1.0, -yx, xy, 1.0, 0.0, 0.0);
}
struct PaintSkew :
public PaintTransformBase {
enum { kFormat = 28 };
F2DOT14 xSkewAngle;
F2DOT14 ySkewAngle;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
return SkewMatrix(
float(xSkewAngle),
float(ySkewAngle));
}
};
struct PaintVarSkew :
public PaintSkew {
enum { kFormat = 29 };
uint32 varIndexBase;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
return SkewMatrix(
float(ApplyVariation(aState, xSkewAngle, varIndexBase)),
float(ApplyVariation(aState, ySkewAngle, SatAdd(varIndexBase, 1))));
}
};
struct PaintSkewAroundCenter :
public PaintTransformBase {
enum { kFormat = 30 };
F2DOT14 xSkewAngle;
F2DOT14 ySkewAngle;
FWORD centerX;
FWORD centerY;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY)));
return Matrix::Translation(center)
.PreMultiply(SkewMatrix(
float(xSkewAngle),
float(ySkewAngle)))
.PreTranslate(-center);
}
};
struct PaintVarSkewAroundCenter :
public PaintSkewAroundCenter {
enum { kFormat = 31 };
uint32 varIndexBase;
Matrix GetMatrix(
const PaintState& aState, uint32_t aOffset)
const {
MOZ_ASSERT(format == kFormat);
Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX),
SatAdd(varIndexBase, 2))),
-aState.F2P(ApplyVariation(aState, int16_t(centerY),
SatAdd(varIndexBase, 3))));
return Matrix::Translation(center)
.PreMultiply(SkewMatrix(
ApplyVariation(aState, xSkewAngle, varIndexBase),
ApplyVariation(aState, ySkewAngle, SatAdd(varIndexBase, 1))))
.PreTranslate(-center);
}
};
struct PaintComposite {
enum { kFormat = 32 };
uint8_t format;
Offset24 sourcePaintOffset;
uint8_t compositeMode;
Offset24 backdropPaintOffset;
enum {
COMPOSITE_CLEAR = 0,
COMPOSITE_SRC = 1,
COMPOSITE_DEST = 2,
COMPOSITE_SRC_OVER = 3,
COMPOSITE_DEST_OVER = 4,
COMPOSITE_SRC_IN = 5,
COMPOSITE_DEST_IN = 6,
COMPOSITE_SRC_OUT = 7,
COMPOSITE_DEST_OUT = 8,
COMPOSITE_SRC_ATOP = 9,
COMPOSITE_DEST_ATOP = 10,
COMPOSITE_XOR = 11,
COMPOSITE_PLUS = 12,
COMPOSITE_SCREEN = 13,
COMPOSITE_OVERLAY = 14,
COMPOSITE_DARKEN = 15,
COMPOSITE_LIGHTEN = 16,
COMPOSITE_COLOR_DODGE = 17,
COMPOSITE_COLOR_BURN = 18,
COMPOSITE_HARD_LIGHT = 19,
COMPOSITE_SOFT_LIGHT = 20,
COMPOSITE_DIFFERENCE = 21,
COMPOSITE_EXCLUSION = 22,
COMPOSITE_MULTIPLY = 23,
COMPOSITE_HSL_HUE = 24,
COMPOSITE_HSL_SATURATION = 25,
COMPOSITE_HSL_COLOR = 26,
COMPOSITE_HSL_LUMINOSITY = 27
};
bool Paint(
const PaintState& aState, uint32_t aOffset,
const Rect* aBounds)
const {
MOZ_ASSERT(format == kFormat);
if (!backdropPaintOffset || !sourcePaintOffset) {
return true;
}
auto mapCompositionMode = [](uint8_t aMode) -> CompositionOp {
switch (aMode) {
default:
return CompositionOp::OP_SOURCE;
case COMPOSITE_CLEAR:
case COMPOSITE_SRC:
case COMPOSITE_DEST:
MOZ_ASSERT_UNREACHABLE(
"should have short-circuited");
return CompositionOp::OP_SOURCE;
case COMPOSITE_SRC_OVER:
return CompositionOp::OP_OVER;
case COMPOSITE_DEST_OVER:
return CompositionOp::OP_DEST_OVER;
case COMPOSITE_SRC_IN:
return CompositionOp::OP_IN;
case COMPOSITE_DEST_IN:
return CompositionOp::OP_DEST_IN;
case COMPOSITE_SRC_OUT:
return CompositionOp::OP_OUT;
case COMPOSITE_DEST_OUT:
return CompositionOp::OP_DEST_OUT;
case COMPOSITE_SRC_ATOP:
return CompositionOp::OP_ATOP;
case COMPOSITE_DEST_ATOP:
return CompositionOp::OP_DEST_ATOP;
case COMPOSITE_XOR:
return CompositionOp::OP_XOR;
case COMPOSITE_PLUS:
return CompositionOp::OP_ADD;
case COMPOSITE_SCREEN:
return CompositionOp::OP_SCREEN;
case COMPOSITE_OVERLAY:
return CompositionOp::OP_OVERLAY;
case COMPOSITE_DARKEN:
return CompositionOp::OP_DARKEN;
case COMPOSITE_LIGHTEN:
return CompositionOp::OP_LIGHTEN;
case COMPOSITE_COLOR_DODGE:
return CompositionOp::OP_COLOR_DODGE;
case COMPOSITE_COLOR_BURN:
return CompositionOp::OP_COLOR_BURN;
case COMPOSITE_HARD_LIGHT:
return CompositionOp::OP_HARD_LIGHT;
case COMPOSITE_SOFT_LIGHT:
return CompositionOp::OP_SOFT_LIGHT;
case COMPOSITE_DIFFERENCE:
return CompositionOp::OP_DIFFERENCE;
case COMPOSITE_EXCLUSION:
return CompositionOp::OP_EXCLUSION;
case COMPOSITE_MULTIPLY:
return CompositionOp::OP_MULTIPLY;
case COMPOSITE_HSL_HUE:
return CompositionOp::OP_HUE;
case COMPOSITE_HSL_SATURATION:
return CompositionOp::OP_SATURATION;
case COMPOSITE_HSL_COLOR:
return CompositionOp::OP_COLOR;
case COMPOSITE_HSL_LUMINOSITY:
return CompositionOp::OP_LUMINOSITY;
}
};
// Short-circuit cases:
if (compositeMode == COMPOSITE_CLEAR) {
return true;
}
if (compositeMode == COMPOSITE_SRC) {
return DispatchPaint(aState, aOffset + sourcePaintOffset, aBounds);
}
if (compositeMode == COMPOSITE_DEST) {
return DispatchPaint(aState, aOffset + backdropPaintOffset, aBounds);
}
// We need bounds for the temporary surfaces; so if we didn't have
// explicitly-provided bounds from a clipList entry for the top-level
// glyph, then we need to determine the bounding rect here.
Rect bounds = aBounds ? *aBounds : GetBoundingRect(aState, aOffset);
if (bounds.IsEmpty()) {
return true;
}
bounds.RoundOut();
PaintState state = aState;
state.mDrawOptions.mCompositionOp = CompositionOp::OP_OVER;
IntSize intSize(
int(bounds.width),
int(bounds.height));
if (!aState.mDrawTarget->CanCreateSimilarDrawTarget(
intSize, SurfaceFormat::B8G8R8A8)) {
// We're not going to be able to render this, so just bail out.
// (Returning true rather than false means we'll just not paint this
// part of the glyph, but won't return an error and likely fall back
// to an ugly black blob.)
return true;
}
// Draw the backdrop paint graph to a temporary surface.
RefPtr backdrop = aState.mDrawTarget->CreateSimilarDrawTarget(
intSize, SurfaceFormat::B8G8R8A8);
if (!backdrop) {
return true;
}
backdrop->SetTransform(Matrix::Translation(-bounds.TopLeft()));
state.mDrawTarget = backdrop;
if (!DispatchPaint(state, aOffset + backdropPaintOffset, &bounds)) {
return false;
}
// Draw the source paint graph to another temp surface.
RefPtr source = aState.mDrawTarget->CreateSimilarDrawTarget(
intSize, SurfaceFormat::B8G8R8A8);
if (!source) {
return true;
}
source->SetTransform(Matrix::Translation(-bounds.TopLeft()));
state.mDrawTarget = source;
if (!DispatchPaint(state, aOffset + sourcePaintOffset, &bounds)) {
return false;
}
// Composite the source onto the backdrop using the specified operation.
Rect localBounds(Point(), bounds.Size());
RefPtr snapshot = source->Snapshot();
backdrop->SetTransform(Matrix());
backdrop->DrawSurface(snapshot, localBounds, localBounds,
DrawSurfaceOptions(),
DrawOptions(1.0, mapCompositionMode(compositeMode)));
// And copy the composited result to our final destination.
snapshot = backdrop->Snapshot();
aState.mDrawTarget->DrawSurface(snapshot, bounds, localBounds);
return true;
}
Rect GetBoundingRect(
const PaintState& aState, uint32_t aOffset)
const {
if (!backdropPaintOffset || !sourcePaintOffset) {
return Rect();
}
// For now, just return the maximal bounds that could result; this could be
// smarter, returning just one of the rects or their intersection when
// appropriate for the composite mode in effect.
return DispatchGetBounds(aState, aOffset + backdropPaintOffset)
.
Union(DispatchGetBounds(aState, aOffset + sourcePaintOffset));
}
};
#pragma pack()
const BaseGlyphPaintRecord* COLRv1Header::GetBaseGlyphPaint(
uint32_t aGlyphId)
const {
const auto* list = baseGlyphList();
if (!list) {
return nullptr;
}
--> --------------------
--> maximum size reached
--> --------------------