Bug 1856663 - Add more chunks for Android mochitest-plain. r=jmaher,taskgraph-reviewe...
[gecko.git] / gfx / thebes / COLRFonts.cpp
blob2916f9c93436da31d7828d8e39ccdba305c710e0
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "COLRFonts.h"
7 #include "gfxFontUtils.h"
8 #include "gfxUtils.h"
9 #include "harfbuzz/hb.h"
10 #include "harfbuzz/hb-ot.h"
11 #include "mozilla/gfx/Helpers.h"
12 #include "mozilla/ScopeExit.h"
13 #include "mozilla/StaticPrefs_gfx.h"
14 #include "TextDrawTarget.h"
16 #include <limits>
18 using namespace mozilla;
19 using namespace mozilla::gfx;
21 namespace { // anonymous namespace for implementation internals
23 #pragma pack(1) // ensure no padding is added to the COLR structs
25 // Alias bigendian-reading types from gfxFontUtils to names used in the spec.
26 using int16 = AutoSwap_PRInt16;
27 using uint16 = AutoSwap_PRUint16;
28 using int32 = AutoSwap_PRInt32;
29 using uint32 = AutoSwap_PRUint32;
30 using FWORD = AutoSwap_PRInt16;
31 using UFWORD = AutoSwap_PRUint16;
32 using Offset16 = AutoSwap_PRUint16;
33 using Offset24 = AutoSwap_PRUint24;
34 using Offset32 = AutoSwap_PRUint32;
36 struct COLRv1Header;
37 struct ClipList;
38 struct LayerRecord;
39 struct BaseGlyphRecord;
40 struct DeltaSetIndexMap;
41 struct ItemVariationStore;
43 struct COLRHeader {
44 uint16 version;
45 uint16 numBaseGlyphRecords;
46 Offset32 baseGlyphRecordsOffset;
47 Offset32 layerRecordsOffset;
48 uint16 numLayerRecords;
50 const BaseGlyphRecord* GetBaseGlyphRecords() const {
51 return reinterpret_cast<const BaseGlyphRecord*>(
52 reinterpret_cast<const char*>(this) + baseGlyphRecordsOffset);
55 const LayerRecord* GetLayerRecords() const {
56 return reinterpret_cast<const LayerRecord*>(
57 reinterpret_cast<const char*>(this) + layerRecordsOffset);
60 bool Validate(uint64_t aLength) const;
63 struct BaseGlyphPaintRecord {
64 uint16 glyphID;
65 Offset32 paintOffset;
68 struct BaseGlyphList {
69 uint32 numBaseGlyphPaintRecords;
70 // BaseGlyphPaintRecord baseGlyphPaintRecords[numBaseGlyphPaintRecords];
71 const BaseGlyphPaintRecord* baseGlyphPaintRecords() const {
72 return reinterpret_cast<const BaseGlyphPaintRecord*>(this + 1);
74 bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const;
77 struct LayerList {
78 uint32 numLayers;
79 // uint32 paintOffsets[numLayers];
80 const uint32* paintOffsets() const {
81 return reinterpret_cast<const uint32*>(this + 1);
83 bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const;
86 struct COLRv1Header {
87 COLRHeader base;
88 Offset32 baseGlyphListOffset;
89 Offset32 layerListOffset;
90 Offset32 clipListOffset;
91 Offset32 varIndexMapOffset;
92 Offset32 itemVariationStoreOffset;
94 bool Validate(uint64_t aLength) const;
96 const BaseGlyphList* baseGlyphList() const {
97 uint32_t offset = baseGlyphListOffset;
98 if (!offset) {
99 return nullptr;
101 const char* ptr = reinterpret_cast<const char*>(this) + offset;
102 return reinterpret_cast<const struct BaseGlyphList*>(ptr);
105 const LayerList* layerList() const {
106 uint32_t offset = layerListOffset;
107 if (!offset) {
108 return nullptr;
110 const char* ptr = reinterpret_cast<const char*>(this) + offset;
111 return reinterpret_cast<const LayerList*>(ptr);
114 const struct ClipList* clipList() const {
115 uint32_t offset = clipListOffset;
116 if (!offset) {
117 return nullptr;
119 const char* ptr = reinterpret_cast<const char*>(this) + offset;
120 return reinterpret_cast<const ClipList*>(ptr);
123 const struct DeltaSetIndexMap* varIndexMap() const {
124 uint32_t offset = varIndexMapOffset;
125 if (!offset) {
126 return nullptr;
128 const char* ptr = reinterpret_cast<const char*>(this) + offset;
129 return reinterpret_cast<const DeltaSetIndexMap*>(ptr);
132 const struct ItemVariationStore* itemVariationStore() const {
133 uint32_t offset = itemVariationStoreOffset;
134 if (!offset) {
135 return nullptr;
137 const char* ptr = reinterpret_cast<const char*>(this) + offset;
138 return reinterpret_cast<const ItemVariationStore*>(ptr);
141 const BaseGlyphPaintRecord* GetBaseGlyphPaint(uint32_t aGlyphId) const;
144 struct PaintState {
145 union {
146 const COLRHeader* v0;
147 const COLRv1Header* v1;
148 } mHeader;
149 const sRGBColor* mPalette;
150 DrawTarget* mDrawTarget;
151 ScaledFont* mScaledFont;
152 const int* mCoords;
153 DrawOptions mDrawOptions;
154 uint32_t mCOLRLength;
155 sRGBColor mCurrentColor;
156 float mFontUnitsToPixels;
157 uint16_t mNumColors;
158 uint16_t mCoordCount;
159 nsTArray<uint32_t>* mVisited;
161 const char* COLRv1BaseAddr() const {
162 return reinterpret_cast<const char*>(mHeader.v1);
165 DeviceColor GetColor(uint16_t aPaletteIndex, float aAlpha) const;
167 // Convert from FUnits (either integer or Fixed 16.16) to device pixels.
168 template <typename T>
169 float F2P(T aPixels) const {
170 return mFontUnitsToPixels * float(aPixels);
174 DeviceColor PaintState::GetColor(uint16_t aPaletteIndex, float aAlpha) const {
175 sRGBColor color;
176 if (aPaletteIndex < mNumColors) {
177 color = mPalette[uint16_t(aPaletteIndex)];
178 } else if (aPaletteIndex == 0xffff) {
179 color = mCurrentColor;
180 } else { // Palette index out of range! Return transparent black.
181 color = sRGBColor();
183 color.a *= aAlpha;
184 return ToDeviceColor(color);
187 static bool DispatchPaint(const PaintState& aState, uint32_t aOffset,
188 const Rect* aBounds /* may be nullptr if unknown */);
189 static UniquePtr<Pattern> DispatchMakePattern(const PaintState& aState,
190 uint32_t aOffset);
191 static Rect DispatchGetBounds(const PaintState& aState, uint32_t aOffset);
192 static Matrix DispatchGetMatrix(const PaintState& aState, uint32_t aOffset);
194 // Variation-data types
195 struct Fixed {
196 enum { kFractionBits = 16 };
197 operator float() const {
198 return float(int32_t(value)) / float(1 << kFractionBits);
200 int32_t intRepr() const { return int32_t(value); }
202 private:
203 int32 value;
206 struct F2DOT14 {
207 enum { kFractionBits = 14 };
208 operator float() const {
209 return float(int16_t(value)) / float(1 << kFractionBits);
211 int32_t intRepr() const { return int16_t(value); }
213 private:
214 int16 value;
217 // Saturating addition used for variation indexes to avoid wrap-around.
218 static uint32_t SatAdd(uint32_t a, uint32_t b) {
219 if (a <= std::numeric_limits<uint32_t>::max() - b) {
220 return a + b;
222 return std::numeric_limits<uint32_t>::max();
225 struct RegionAxisCoordinates {
226 F2DOT14 startCoord;
227 F2DOT14 peakCoord;
228 F2DOT14 endCoord;
231 struct VariationRegion {
232 // RegionAxisCoordinates regionAxes[axisCount];
233 const RegionAxisCoordinates* regionAxes() const {
234 return reinterpret_cast<const RegionAxisCoordinates*>(this);
238 struct VariationRegionList {
239 uint16 axisCount;
240 uint16 regionCount;
241 // VariationRegion variationRegions[regionCount];
242 const char* variationRegionsBase() const {
243 return reinterpret_cast<const char*>(this + 1);
245 size_t regionSize() const {
246 return uint16_t(axisCount) * sizeof(RegionAxisCoordinates);
248 const VariationRegion* getRegion(uint16_t i) const {
249 if (i >= uint16_t(regionCount)) {
250 return nullptr;
252 return reinterpret_cast<const VariationRegion*>(variationRegionsBase() +
253 i * regionSize());
255 bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const {
256 if (variationRegionsBase() - reinterpret_cast<const char*>(aHeader) +
257 uint16_t(regionCount) * regionSize() >
258 aLength) {
259 return false;
261 return true;
265 struct DeltaSet {
266 // (int16 and int8)
267 // *or*
268 // (int32 and int16) deltaData[regionIndexCount];
271 struct DeltaSetIndexMap {
272 enum { INNER_INDEX_BIT_COUNT_MASK = 0x0f, MAP_ENTRY_SIZE_MASK = 0x30 };
273 uint8_t format;
274 uint8_t entryFormat;
275 union {
276 struct {
277 uint16 mapCount;
278 // uint8 mapData[variable];
279 } v0;
280 struct {
281 uint32 mapCount;
282 // uint8 mapData[variable];
283 } v1;
285 uint32_t entrySize() const {
286 return (((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1);
288 uint32_t map(uint32_t aIndex) const {
289 uint32_t mapCount;
290 const uint8_t* mapData;
291 switch (format) {
292 case 0:
293 mapCount = uint32_t(v0.mapCount);
294 mapData = reinterpret_cast<const uint8_t*>(&v0.mapCount) +
295 sizeof(v0.mapCount);
296 break;
297 case 1:
298 mapCount = uint32_t(v1.mapCount);
299 mapData = reinterpret_cast<const uint8_t*>(&v1.mapCount) +
300 sizeof(v1.mapCount);
301 break;
302 default:
303 // unknown DeltaSetIndexMap format
304 return aIndex;
306 if (!mapCount) {
307 return aIndex;
309 if (aIndex >= mapCount) {
310 aIndex = mapCount - 1;
312 const uint8_t* entryData = mapData + aIndex * entrySize();
313 uint32_t entry = 0;
314 for (uint32_t i = 0; i < entrySize(); ++i) {
315 entry = (entry << 8) + entryData[i];
317 uint16_t outerIndex =
318 entry >> ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1);
319 uint16_t innerIndex =
320 entry & ((1 << ((entryFormat & INNER_INDEX_BIT_COUNT_MASK) + 1)) - 1);
321 return (uint32_t(outerIndex) << 16) + innerIndex;
323 bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const;
326 enum EntryFormatMasks {
327 INNER_INDEX_BIT_COUNT_MASK = 0x0f,
328 MAP_ENTRY_SIZE_MASK = 0x30
331 struct ItemVariationData {
332 enum { WORD_DELTA_COUNT_MASK = 0x7FFF, LONG_WORDS = 0x8000 };
333 uint16 itemCount;
334 uint16 wordDeltaCount;
335 uint16 regionIndexCount;
336 // uint16 regionIndexes[regionIndexCount];
337 const uint16* regionIndexes() const {
338 return reinterpret_cast<const uint16*>(
339 reinterpret_cast<const char*>(this + 1));
341 // DeltaSet deltaSets[itemCount];
342 const DeltaSet* deltaSets() const {
343 return reinterpret_cast<const DeltaSet*>(
344 reinterpret_cast<const char*>(this + 1) +
345 uint16_t(regionIndexCount) * sizeof(uint16));
347 bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const;
350 struct ItemVariationStore {
351 uint16 format;
352 Offset32 variationRegionListOffset;
353 uint16 itemVariationDataCount;
354 // Offset32 itemVariationDataOffsets[itemVariationDataCount];
355 const Offset32* itemVariationDataOffsets() const {
356 return reinterpret_cast<const Offset32*>(
357 reinterpret_cast<const char*>(this + 1));
359 const VariationRegionList* variationRegionList() const {
360 return reinterpret_cast<const VariationRegionList*>(
361 reinterpret_cast<const char*>(this) + variationRegionListOffset);
363 bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const;
366 static int32_t ApplyVariation(const PaintState& aState, int32_t aValue,
367 uint32_t aIndex) {
368 if (aIndex == 0xffffffff) {
369 return aValue;
371 const auto* store = aState.mHeader.v1->itemVariationStore();
372 if (!store || uint16_t(store->format) != 1) {
373 return aValue;
375 const DeltaSetIndexMap* map = aState.mHeader.v1->varIndexMap();
376 uint32_t mappedIndex = map ? map->map(aIndex) : aIndex;
377 uint16_t outerIndex = mappedIndex >> 16;
378 uint16_t innerIndex = mappedIndex & 0xffff;
379 const auto* itemVariationDataOffsets = store->itemVariationDataOffsets();
380 if (mappedIndex == 0xffffffff ||
381 outerIndex >= uint16_t(store->itemVariationDataCount) ||
382 !itemVariationDataOffsets[outerIndex]) {
383 return aValue;
385 const auto* regionList = store->variationRegionList();
386 if (outerIndex >= uint16_t(store->itemVariationDataCount)) {
387 return aValue;
389 const auto* variationData = reinterpret_cast<const ItemVariationData*>(
390 reinterpret_cast<const char*>(store) +
391 itemVariationDataOffsets[outerIndex]);
392 if (innerIndex >= uint16_t(variationData->itemCount)) {
393 return aValue;
395 const auto* regionIndexes = variationData->regionIndexes();
396 uint16_t regionIndexCount = variationData->regionIndexCount;
397 const DeltaSet* deltaSets = variationData->deltaSets();
398 uint16_t wordDeltaCount = variationData->wordDeltaCount;
399 bool longWords = wordDeltaCount & ItemVariationData::LONG_WORDS;
400 wordDeltaCount &= ItemVariationData::WORD_DELTA_COUNT_MASK;
401 uint32_t deltaSetSize = (regionIndexCount + wordDeltaCount) << longWords;
402 const uint8_t* deltaData =
403 reinterpret_cast<const uint8_t*>(deltaSets) + deltaSetSize * innerIndex;
404 uint16_t deltaSize = longWords ? 4 : 2;
405 int32_t result = aValue;
406 for (uint16_t i = 0; i < regionIndexCount; ++i, deltaData += deltaSize) {
407 if (i == wordDeltaCount) {
408 deltaSize >>= 1;
410 const auto* region = regionList->getRegion(uint16_t(regionIndexes[i]));
411 if (!region) {
412 return aValue;
414 // XXX Should we do the calculations here in fixed-point? Check spec.
415 float scalar = -1.0;
416 for (uint16_t axisIndex = 0; axisIndex < uint16_t(regionList->axisCount);
417 ++axisIndex) {
418 const auto& axis = region->regionAxes()[axisIndex];
419 float peak = axis.peakCoord;
420 if (peak == 0.0) {
421 // This axis cannot contribute to scalar.
422 continue;
424 float start = axis.startCoord;
425 float end = axis.endCoord;
426 float value = axisIndex < aState.mCoordCount
427 ? float(aState.mCoords[axisIndex]) / 16384.0f
428 : 0.0;
429 if (value < start || value > end) {
430 // Out of range: this region is not applicable.
431 scalar = -1.0;
432 break;
434 if (scalar < 0.0) {
435 scalar = 1.0;
437 if (value == peak) {
438 continue;
440 if (value < peak && peak > start) {
441 scalar *= (value - start) / (peak - start);
442 } else if (value > peak && peak < end) {
443 scalar *= (end - value) / (end - peak);
446 if (scalar <= 0.0) {
447 continue;
449 int32_t delta = *reinterpret_cast<const int8_t*>(deltaData); // sign-extend
450 for (uint16_t j = 1; j < deltaSize; ++j) {
451 delta = (delta << 8) | deltaData[j];
453 delta = int32_t(floorf((float(delta) * scalar) + 0.5f));
454 result += delta;
456 return result;
459 static float ApplyVariation(const PaintState& aState, Fixed aValue,
460 uint32_t aIndex) {
461 return float(ApplyVariation(aState, aValue.intRepr(), aIndex)) /
462 (1 << Fixed::kFractionBits);
465 static float ApplyVariation(const PaintState& aState, F2DOT14 aValue,
466 uint32_t aIndex) {
467 return float(ApplyVariation(aState, aValue.intRepr(), aIndex)) /
468 (1 << F2DOT14::kFractionBits);
471 struct ClipBoxFormat1 {
472 enum { kFormat = 1 };
473 uint8_t format;
474 FWORD xMin;
475 FWORD yMin;
476 FWORD xMax;
477 FWORD yMax;
479 Rect GetRect(const PaintState& aState) const {
480 MOZ_ASSERT(format == kFormat);
481 int32_t x0 = int16_t(xMin);
482 int32_t y0 = int16_t(yMin);
483 int32_t x1 = int16_t(xMax);
484 int32_t y1 = int16_t(yMax);
485 // Flip the y-coordinates to map from OpenType to Moz2d space.
486 return Rect(aState.F2P(x0), -aState.F2P(y1), aState.F2P(x1 - x0),
487 aState.F2P(y1 - y0));
491 struct ClipBoxFormat2 : public ClipBoxFormat1 {
492 enum { kFormat = 2 };
493 uint32 varIndexBase;
495 Rect GetRect(const PaintState& aState) const {
496 MOZ_ASSERT(format == kFormat);
497 int32_t x0 = ApplyVariation(aState, int16_t(xMin), varIndexBase);
498 int32_t y0 = ApplyVariation(aState, int16_t(yMin), SatAdd(varIndexBase, 1));
499 int32_t x1 = ApplyVariation(aState, int16_t(xMax), SatAdd(varIndexBase, 2));
500 int32_t y1 = ApplyVariation(aState, int16_t(yMax), SatAdd(varIndexBase, 3));
501 return Rect(aState.F2P(x0), -aState.F2P(y1), aState.F2P(x1 - x0),
502 aState.F2P(y1 - y0));
506 struct Clip {
507 uint16 startGlyphID;
508 uint16 endGlyphID;
509 Offset24 clipBoxOffset;
511 Rect GetRect(const PaintState& aState) const {
512 uint32_t offset = aState.mHeader.v1->clipListOffset + clipBoxOffset;
513 const auto* box = aState.COLRv1BaseAddr() + offset;
514 switch (*box) {
515 case 1:
516 return reinterpret_cast<const ClipBoxFormat1*>(box)->GetRect(aState);
517 case 2:
518 return reinterpret_cast<const ClipBoxFormat2*>(box)->GetRect(aState);
519 default:
520 // unknown ClipBoxFormat
521 break;
523 return Rect();
525 bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const;
528 struct ClipList {
529 uint8_t format;
530 uint32 numClips;
531 // Clip clips[numClips]
532 const Clip* clips() const { return reinterpret_cast<const Clip*>(this + 1); }
533 const Clip* GetClip(uint32_t aGlyphId) const {
534 auto compare = [](const void* key, const void* data) -> int {
535 uint32_t glyphId = (uint32_t)(uintptr_t)key;
536 const auto* clip = reinterpret_cast<const Clip*>(data);
537 uint32_t start = uint16_t(clip->startGlyphID);
538 uint32_t end = uint16_t(clip->endGlyphID);
539 if (start <= glyphId && end >= glyphId) {
540 return 0;
542 return start > glyphId ? -1 : 1;
544 return reinterpret_cast<const Clip*>(bsearch((void*)(uintptr_t)aGlyphId,
545 clips(), uint32_t(numClips),
546 sizeof(Clip), compare));
548 bool Validate(const COLRv1Header* aHeader, uint64_t aLength) const;
551 struct LayerRecord {
552 uint16 glyphId;
553 uint16 paletteEntryIndex;
555 bool Paint(const PaintState& aState, float aAlpha,
556 const Point& aPoint) const {
557 Glyph glyph{uint16_t(glyphId), aPoint};
558 GlyphBuffer buffer{&glyph, 1};
559 aState.mDrawTarget->FillGlyphs(
560 aState.mScaledFont, buffer,
561 ColorPattern(aState.GetColor(paletteEntryIndex, aAlpha)),
562 aState.mDrawOptions);
563 return true;
567 struct BaseGlyphRecord {
568 uint16 glyphId;
569 uint16 firstLayerIndex;
570 uint16 numLayers;
572 bool Paint(const PaintState& aState, float aAlpha,
573 const Point& aPoint) const {
574 uint32_t layerIndex = uint16_t(firstLayerIndex);
575 uint32_t end = layerIndex + uint16_t(numLayers);
576 if (end > uint16_t(aState.mHeader.v0->numLayerRecords)) {
577 MOZ_ASSERT_UNREACHABLE("bad COLRv0 table");
578 return false;
580 const auto* layers = aState.mHeader.v0->GetLayerRecords();
581 while (layerIndex < end) {
582 if (!layers[layerIndex].Paint(aState, aAlpha, aPoint)) {
583 return false;
585 ++layerIndex;
587 return true;
591 struct ColorStop {
592 F2DOT14 stopOffset;
593 uint16 paletteIndex;
594 F2DOT14 alpha;
596 float GetStopOffset(const PaintState& aState) const { return stopOffset; }
597 uint16_t GetPaletteIndex() const { return paletteIndex; }
598 float GetAlpha(const PaintState& aState) const { return alpha; }
601 struct VarColorStop : public ColorStop {
602 uint32 varIndexBase;
604 float GetStopOffset(const PaintState& aState) const {
605 return ApplyVariation(aState, stopOffset, varIndexBase);
607 float GetAlpha(const PaintState& aState) const {
608 return ApplyVariation(aState, alpha, SatAdd(varIndexBase, 1));
612 template <typename T>
613 struct ColorLineT {
614 enum { EXTEND_PAD = 0, EXTEND_REPEAT = 1, EXTEND_REFLECT = 2 };
615 uint8_t extend;
616 uint16 numStops;
617 const T* colorStops() const { return reinterpret_cast<const T*>(this + 1); }
619 // If the color line has only one stop, return it as a simple ColorPattern.
620 UniquePtr<Pattern> AsSolidColor(const PaintState& aState) const {
621 if (uint16_t(numStops) != 1) {
622 return nullptr;
624 const auto* stop = colorStops();
625 return MakeUnique<ColorPattern>(
626 aState.GetColor(stop->GetPaletteIndex(), stop->GetAlpha(aState)));
629 // Retrieve the color stops into an array of GradientStop records. The stops
630 // are normalized to the range [0 .. 1], and the original offsets of the
631 // first and last stops are returned.
632 // If aReverse is true, the color line is reversed.
633 void CollectGradientStops(const PaintState& aState,
634 nsTArray<GradientStop>& aStops, float* aFirstStop,
635 float* aLastStop, bool aReverse = false) const {
636 MOZ_ASSERT(aStops.IsEmpty());
637 uint16_t count = numStops;
638 if (!count) {
639 return;
641 const auto* stop = colorStops();
642 if (reinterpret_cast<const char*>(stop) + count * sizeof(T) >
643 aState.COLRv1BaseAddr() + aState.mCOLRLength) {
644 return;
646 aStops.SetCapacity(count);
647 for (uint16_t i = 0; i < count; ++i, ++stop) {
648 DeviceColor color =
649 aState.GetColor(stop->GetPaletteIndex(), stop->GetAlpha(aState));
650 aStops.AppendElement(GradientStop{stop->GetStopOffset(aState), color});
652 if (count == 1) {
653 *aFirstStop = *aLastStop = aStops[0].offset;
654 return;
656 aStops.StableSort();
657 if (aReverse) {
658 float a = aStops[0].offset;
659 float b = aStops.LastElement().offset;
660 aStops.Reverse();
661 for (auto& gs : aStops) {
662 gs.offset = a + b - gs.offset;
665 // Normalize stops to the range 0.0 .. 1.0, and return the original
666 // start & end.
667 // Note that if all stops are at the same offset, no normalization
668 // will be done.
669 *aFirstStop = aStops[0].offset;
670 *aLastStop = aStops.LastElement().offset;
671 if ((*aLastStop > *aFirstStop) &&
672 (*aLastStop != 1.0f || *aFirstStop != 0.0f)) {
673 float f = 1.0f / (*aLastStop - *aFirstStop);
674 for (auto& gs : aStops) {
675 gs.offset = (gs.offset - *aFirstStop) * f;
680 // Create a gfx::GradientStops representing the given color line stops,
681 // applying our extend mode.
682 already_AddRefed<GradientStops> MakeGradientStops(
683 const PaintState& aState, nsTArray<GradientStop>& aStops) const {
684 auto mapExtendMode = [](uint8_t aExtend) -> ExtendMode {
685 switch (aExtend) {
686 case EXTEND_REPEAT:
687 return ExtendMode::REPEAT;
688 case EXTEND_REFLECT:
689 return ExtendMode::REFLECT;
690 case EXTEND_PAD:
691 default:
692 return ExtendMode::CLAMP;
695 return aState.mDrawTarget->CreateGradientStops(
696 aStops.Elements(), aStops.Length(), mapExtendMode(extend));
699 already_AddRefed<GradientStops> MakeGradientStops(
700 const PaintState& aState, float* aFirstStop, float* aLastStop,
701 bool aReverse = false) const {
702 AutoTArray<GradientStop, 8> stops;
703 CollectGradientStops(aState, stops, aFirstStop, aLastStop, aReverse);
704 if (stops.IsEmpty()) {
705 return nullptr;
707 return MakeGradientStops(aState, stops);
711 using ColorLine = ColorLineT<ColorStop>;
712 using VarColorLine = ColorLineT<VarColorStop>;
714 // Used to check for cycles in the paint graph, and bail out to avoid infinite
715 // recursion when traversing the graph in Paint() or GetBoundingRect(). (Only
716 // PaintColrLayers and PaintColrGlyph can cause cycles; all other paint types
717 // have only forward references within the table.)
718 #define IF_CYCLE_RETURN(retval) \
719 if (aState.mVisited->Contains(aOffset)) { \
720 return retval; \
722 aState.mVisited->AppendElement(aOffset); \
723 ScopeExit e([aState]() { aState.mVisited->RemoveLastElement(); })
725 struct PaintColrLayers {
726 enum { kFormat = 1 };
727 uint8_t format;
728 uint8_t numLayers;
729 uint32 firstLayerIndex;
731 bool Paint(const PaintState& aState, uint32_t aOffset,
732 const Rect* aBounds) const {
733 MOZ_ASSERT(format == kFormat);
734 IF_CYCLE_RETURN(true);
735 const auto* layerList = aState.mHeader.v1->layerList();
736 if (!layerList) {
737 return false;
739 if (uint64_t(firstLayerIndex) + numLayers > layerList->numLayers) {
740 return false;
742 const auto* paintOffsets = layerList->paintOffsets() + firstLayerIndex;
743 for (uint32_t i = 0; i < numLayers; i++) {
744 if (!DispatchPaint(aState,
745 aState.mHeader.v1->layerListOffset + paintOffsets[i],
746 aBounds)) {
747 return false;
750 return true;
753 Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const {
754 MOZ_ASSERT(format == kFormat);
755 IF_CYCLE_RETURN(Rect());
756 const auto* layerList = aState.mHeader.v1->layerList();
757 if (!layerList) {
758 return Rect();
760 if (uint64_t(firstLayerIndex) + numLayers > layerList->numLayers) {
761 return Rect();
763 Rect result;
764 const auto* paintOffsets = layerList->paintOffsets() + firstLayerIndex;
765 for (uint32_t i = 0; i < numLayers; i++) {
766 result = result.Union(DispatchGetBounds(
767 aState, aState.mHeader.v1->layerListOffset + paintOffsets[i]));
769 return result;
773 struct PaintPatternBase {
774 bool Paint(const PaintState& aState, uint32_t aOffset,
775 const Rect* aBounds) const {
776 Matrix m = aState.mDrawTarget->GetTransform();
777 if (m.Invert()) {
778 if (auto pattern = DispatchMakePattern(aState, aOffset)) {
779 aState.mDrawTarget->FillRect(
780 m.TransformBounds(IntRectToRect(aState.mDrawTarget->GetRect())),
781 *pattern, aState.mDrawOptions);
782 return true;
785 return false;
788 Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const {
789 return Rect();
793 struct PaintSolid : public PaintPatternBase {
794 enum { kFormat = 2 };
795 uint8_t format;
796 uint16 paletteIndex;
797 F2DOT14 alpha;
799 UniquePtr<Pattern> MakePattern(const PaintState& aState,
800 uint32_t aOffset) const {
801 MOZ_ASSERT(format == kFormat);
802 return MakeUnique<ColorPattern>(aState.GetColor(paletteIndex, alpha));
806 struct PaintVarSolid : public PaintSolid {
807 enum { kFormat = 3 };
808 uint32 varIndexBase;
810 UniquePtr<Pattern> MakePattern(const PaintState& aState,
811 uint32_t aOffset) const {
812 MOZ_ASSERT(format == kFormat);
813 return MakeUnique<ColorPattern>(aState.GetColor(
814 paletteIndex, ApplyVariation(aState, alpha, varIndexBase)));
818 struct PaintLinearGradient : public PaintPatternBase {
819 enum { kFormat = 4 };
820 uint8_t format;
821 Offset24 colorLineOffset;
822 FWORD x0;
823 FWORD y0;
824 FWORD x1;
825 FWORD y1;
826 FWORD x2;
827 FWORD y2;
829 UniquePtr<Pattern> MakePattern(const PaintState& aState,
830 uint32_t aOffset) const {
831 MOZ_ASSERT(format == kFormat);
832 uint32_t clOffset = aOffset + colorLineOffset;
833 if (clOffset + sizeof(ColorLine) + sizeof(ColorStop) > aState.mCOLRLength) {
834 return nullptr;
836 const auto* colorLine =
837 reinterpret_cast<const ColorLine*>(aState.COLRv1BaseAddr() + clOffset);
838 Point p0(aState.F2P(int16_t(x0)), aState.F2P(int16_t(y0)));
839 Point p1(aState.F2P(int16_t(x1)), aState.F2P(int16_t(y1)));
840 Point p2(aState.F2P(int16_t(x2)), aState.F2P(int16_t(y2)));
841 return NormalizeAndMakeGradient(aState, colorLine, p0, p1, p2);
844 template <typename T>
845 UniquePtr<Pattern> NormalizeAndMakeGradient(const PaintState& aState,
846 const T* aColorLine, Point p0,
847 Point p1, Point p2) const {
848 // Ill-formed gradient should not be rendered.
849 if (p1 == p0 || p2 == p0) {
850 return MakeUnique<ColorPattern>(DeviceColor());
852 UniquePtr<Pattern> solidColor = aColorLine->AsSolidColor(aState);
853 if (solidColor) {
854 return solidColor;
856 float firstStop, lastStop;
857 AutoTArray<GradientStop, 8> stopArray;
858 aColorLine->CollectGradientStops(aState, stopArray, &firstStop, &lastStop);
859 if (stopArray.IsEmpty()) {
860 return MakeUnique<ColorPattern>(DeviceColor());
862 if (firstStop != 0.0 || lastStop != 1.0) {
863 if (firstStop == lastStop) {
864 if (aColorLine->extend != T::EXTEND_PAD) {
865 return MakeUnique<ColorPattern>(DeviceColor());
867 // For extend-pad, when the color line is zero-length, we add a "fake"
868 // color stop to create a [0.0..1.0]-normalized color line, so that the
869 // projection of points below works as expected.
870 for (auto& gs : stopArray) {
871 gs.offset = 0.0f;
873 stopArray.AppendElement(
874 GradientStop{1.0f, stopArray.LastElement().color});
875 lastStop += 1.0f;
877 // Adjust positions of the points to account for normalization of the
878 // color line stop offsets.
879 Point v = p1 - p0;
880 p0 += v * firstStop;
881 p1 -= v * (1.0f - lastStop);
882 // Move the rotation vector to maintain the same direction from p0.
883 p2 += v * firstStop;
885 Point p3;
886 if (FuzzyEqualsMultiplicative(p2.y, p0.y)) {
887 // rotation vector is horizontal
888 p3 = Point(p0.x, p1.y);
889 } else if (FuzzyEqualsMultiplicative(p2.x, p0.x)) {
890 // rotation vector is vertical
891 p3 = Point(p1.x, p0.y);
892 } else {
893 float m = (p2.y - p0.y) / (p2.x - p0.x); // slope of line p0->p2
894 float mInv = -1.0f / m; // slope of desired perpendicular p0->p3
895 float c1 = p0.y - mInv * p0.x; // line p0->p3 is m * x - y + c1 = 0
896 float c2 = p1.y - m * p1.x; // line p1->p3 is mInv * x - y + c2 = 0
897 float x3 = (c1 - c2) / (m - mInv);
898 float y3 = (c1 * m - c2 * mInv) / (m - mInv);
899 p3 = Point(x3, y3);
901 RefPtr stops = aColorLine->MakeGradientStops(aState, stopArray);
902 return MakeUnique<LinearGradientPattern>(p0, p3, std::move(stops),
903 Matrix::Scaling(1.0, -1.0));
907 struct PaintVarLinearGradient : public PaintLinearGradient {
908 enum { kFormat = 5 };
909 uint32 varIndexBase;
911 UniquePtr<Pattern> MakePattern(const PaintState& aState,
912 uint32_t aOffset) const {
913 MOZ_ASSERT(format == kFormat);
914 uint32_t clOffset = aOffset + colorLineOffset;
915 if (clOffset + sizeof(VarColorLine) + sizeof(VarColorStop) >
916 aState.mCOLRLength) {
917 return nullptr;
919 const auto* colorLine = reinterpret_cast<const VarColorLine*>(
920 aState.COLRv1BaseAddr() + clOffset);
921 Point p0(aState.F2P(ApplyVariation(aState, int16_t(x0), varIndexBase)),
922 aState.F2P(
923 ApplyVariation(aState, int16_t(y0), SatAdd(varIndexBase, 1))));
924 Point p1(aState.F2P(
925 ApplyVariation(aState, int16_t(x1), SatAdd(varIndexBase, 2))),
926 aState.F2P(
927 ApplyVariation(aState, int16_t(y1), SatAdd(varIndexBase, 3))));
928 Point p2(aState.F2P(
929 ApplyVariation(aState, int16_t(x2), SatAdd(varIndexBase, 4))),
930 aState.F2P(
931 ApplyVariation(aState, int16_t(y2), SatAdd(varIndexBase, 5))));
932 return NormalizeAndMakeGradient(aState, colorLine, p0, p1, p2);
936 struct PaintRadialGradient : public PaintPatternBase {
937 enum { kFormat = 6 };
938 uint8_t format;
939 Offset24 colorLineOffset;
940 FWORD x0;
941 FWORD y0;
942 UFWORD radius0;
943 FWORD x1;
944 FWORD y1;
945 UFWORD radius1;
947 UniquePtr<Pattern> MakePattern(const PaintState& aState,
948 uint32_t aOffset) const {
949 MOZ_ASSERT(format == kFormat);
950 uint32_t clOffset = aOffset + colorLineOffset;
951 if (clOffset + sizeof(ColorLine) + sizeof(ColorStop) > aState.mCOLRLength) {
952 return nullptr;
954 const auto* colorLine =
955 reinterpret_cast<const ColorLine*>(aState.COLRv1BaseAddr() + clOffset);
956 Point c1(aState.F2P(int16_t(x0)), aState.F2P(int16_t(y0)));
957 Point c2(aState.F2P(int16_t(x1)), aState.F2P(int16_t(y1)));
958 float r1 = aState.F2P(uint16_t(radius0));
959 float r2 = aState.F2P(uint16_t(radius1));
960 return NormalizeAndMakeGradient(aState, colorLine, c1, c2, r1, r2);
963 // Helper function to trim the gradient stops array at the start or end.
964 void TruncateGradientStops(nsTArray<GradientStop>& aStops, float aStart,
965 float aEnd) const {
966 // For pad mode, we may need a sub-range of the line: figure out which
967 // stops to trim, and interpolate as needed at truncation point(s).
968 // (Currently this is only ever used to trim one end of the color line,
969 // so edge cases that may occur when trimming both ends are untested.)
970 MOZ_ASSERT(aStart == 0.0f || aEnd == 1.0f,
971 "Trimming both ends of color-line is untested!");
973 // Create a color that is |r| of the way from c1 to c2.
974 auto interpolateColor = [](DeviceColor c1, DeviceColor c2, float r) {
975 return DeviceColor(
976 c2.r * r + c1.r * (1.0f - r), c2.g * r + c1.g * (1.0f - r),
977 c2.b * r + c1.b * (1.0f - r), c2.a * r + c1.a * (1.0f - r));
980 size_t count = aStops.Length();
981 MOZ_ASSERT(count > 1);
983 // Truncate at the start of the color line?
984 if (aStart > 0.0f) {
985 // Skip forward past any stops that can be dropped.
986 size_t i = 0;
987 while (i < count - 1 && aStops[i].offset < aStart) {
988 ++i;
990 // If we're not truncating exactly at a color-stop offset, shift the
991 // preceding stop to the truncation offset and interpolate its color.
992 if (i && aStops[i].offset > aStart) {
993 auto& prev = aStops[i - 1];
994 auto& curr = aStops[i];
995 float ratio = (aStart - prev.offset) / (curr.offset - prev.offset);
996 prev.color = interpolateColor(prev.color, curr.color, ratio);
997 prev.offset = aStart;
998 --i; // We don't want to remove this stop, as we adjusted it.
1000 aStops.RemoveElementsAt(0, i);
1001 // Re-normalize the remaining stops to the [0, 1] range.
1002 if (aStart < 1.0f) {
1003 float r = 1.0f / (1.0f - aStart);
1004 for (auto& gs : aStops) {
1005 gs.offset = r * (gs.offset - aStart);
1010 // Truncate at the end of the color line?
1011 if (aEnd < 1.0f) {
1012 // Skip back over any stops that can be dropped.
1013 size_t i = count - 1;
1014 while (i && aStops[i].offset > aEnd) {
1015 --i;
1017 // If we're not truncating exactly at a color-stop offset, shift the
1018 // following stop to the truncation offset and interpolate its color.
1019 if (i + 1 < count && aStops[i].offset < aEnd) {
1020 auto& next = aStops[i + 1];
1021 auto& curr = aStops[i];
1022 float ratio = (aEnd - curr.offset) / (next.offset - curr.offset);
1023 next.color = interpolateColor(curr.color, next.color, ratio);
1024 next.offset = aEnd;
1025 ++i;
1027 aStops.RemoveElementsAt(i + 1, count - i - 1);
1028 // Re-normalize the remaining stops to the [0, 1] range.
1029 if (aEnd > 0.0f) {
1030 float r = 1.0f / aEnd;
1031 for (auto& gs : aStops) {
1032 gs.offset = r * gs.offset;
1038 template <typename T>
1039 UniquePtr<Pattern> NormalizeAndMakeGradient(const PaintState& aState,
1040 const T* aColorLine, Point c1,
1041 Point c2, float r1,
1042 float r2) const {
1043 if ((c1 == c2 && r1 == r2) || (r1 == 0.0 && r2 == 0.0)) {
1044 return MakeUnique<ColorPattern>(DeviceColor());
1046 UniquePtr<Pattern> solidColor = aColorLine->AsSolidColor(aState);
1047 if (solidColor) {
1048 return solidColor;
1050 float firstStop, lastStop;
1051 AutoTArray<GradientStop, 8> stopArray;
1052 aColorLine->CollectGradientStops(aState, stopArray, &firstStop, &lastStop);
1053 if (stopArray.IsEmpty()) {
1054 return MakeUnique<ColorPattern>(DeviceColor());
1056 // If the color stop offsets had to be normalized to the [0, 1] range,
1057 // adjust the circle positions and radii to match.
1058 if (firstStop != 0.0f || lastStop != 1.0f) {
1059 if (firstStop == lastStop) {
1060 if (aColorLine->extend != T::EXTEND_PAD) {
1061 return MakeUnique<ColorPattern>(DeviceColor());
1063 // For extend-pad, when the color line is zero-length, we add a "fake"
1064 // color stop to ensure we'll maintain the orientation of the cone,
1065 // otherwise when we adjust circles to account for the normalized color
1066 // stops, the centers will coalesce and the cone or cylinder collapses.
1067 for (auto& gs : stopArray) {
1068 gs.offset = 0.0f;
1070 stopArray.AppendElement(
1071 GradientStop{1.0f, stopArray.LastElement().color});
1072 lastStop += 1.0f;
1074 // Adjust centers along the vector between them, and scale radii for
1075 // gradient line defined from 0.0 to 1.0.
1076 Point vec = c2 - c1;
1077 c1 += vec * firstStop;
1078 c2 -= vec * (1.0f - lastStop);
1079 float deltaR = r2 - r1;
1080 r1 = r1 + deltaR * firstStop;
1081 r2 = r2 - deltaR * (1.0f - lastStop);
1083 if ((r1 < 0.0f || r2 < 0.0f) && aColorLine->extend == T::EXTEND_PAD) {
1084 // For EXTEND_PAD, we can restrict the gradient definition to just its
1085 // visible portion because the shader doesn't need to see any part of the
1086 // color line that extends into the negative-radius "virtual cone".
1087 if (r1 < 0.0f && r2 < 0.0f) {
1088 // If both radii are negative, then only the color at the closer circle
1089 // will appear in the projected positive cone (or if they're equal,
1090 // nothing will be visible at all).
1091 if (r1 == r2) {
1092 return MakeUnique<ColorPattern>(DeviceColor());
1094 // The defined range of the color line is entirely in the invisible
1095 // cone; all that will project into visible space is a single color.
1096 if (r1 < r2) {
1097 // Keep only the last color stop.
1098 stopArray.RemoveElementsAt(0, stopArray.Length() - 1);
1099 } else {
1100 // Keep only the first color stop.
1101 stopArray.RemoveElementsAt(1, stopArray.Length() - 1);
1103 } else {
1104 // Truncate the gradient at the tip of the visible cone: find the color
1105 // stops closest to that point and interpolate between them.
1106 if (r1 < r2) {
1107 float start = r1 / (r1 - r2);
1108 TruncateGradientStops(stopArray, start, 1.0f);
1109 r1 = 0.0f;
1110 c1 = c1 * (1.0f - start) + c2 * start;
1111 } else if (r2 < r1) {
1112 float end = 1.0f - r2 / (r2 - r1);
1113 TruncateGradientStops(stopArray, 0.0f, end);
1114 r2 = 0.0f;
1115 c2 = c1 * (1.0f - end) + c2 * end;
1119 // Handle negative radii, which the shader won't understand directly, by
1120 // projecting the circles along the cones such that both radii are positive.
1121 if (r1 < 0.0f || r2 < 0.0f) {
1122 float deltaR = r2 - r1;
1123 // If deltaR is zero, then nothing is visible because the cone has
1124 // degenerated into a negative-radius cylinder, and does not project
1125 // into visible space at all.
1126 if (deltaR == 0.0f) {
1127 return MakeUnique<ColorPattern>(DeviceColor());
1129 Point vec = c2 - c1;
1130 if (aColorLine->extend == T::EXTEND_REFLECT) {
1131 deltaR *= 2.0f;
1132 vec = vec * 2.0f;
1134 if (r2 < r1) {
1135 vec = -vec;
1136 deltaR = -deltaR;
1138 // Number of repeats by which we need to shift.
1139 float n = std::ceil(std::max(-r1, -r2) / deltaR);
1140 deltaR *= n;
1141 r1 += deltaR;
1142 r2 += deltaR;
1143 vec = vec * n;
1144 c1 += vec;
1145 c2 += vec;
1147 RefPtr stops = aColorLine->MakeGradientStops(aState, stopArray);
1148 if (!stops) {
1149 return MakeUnique<ColorPattern>(DeviceColor());
1151 return MakeUnique<RadialGradientPattern>(c1, c2, r1, r2, std::move(stops),
1152 Matrix::Scaling(1.0, -1.0));
1156 struct PaintVarRadialGradient : public PaintRadialGradient {
1157 enum { kFormat = 7 };
1158 uint32 varIndexBase;
1160 UniquePtr<Pattern> MakePattern(const PaintState& aState,
1161 uint32_t aOffset) const {
1162 MOZ_ASSERT(format == kFormat);
1163 uint32_t clOffset = aOffset + colorLineOffset;
1164 if (clOffset + sizeof(VarColorLine) + sizeof(VarColorStop) >
1165 aState.mCOLRLength) {
1166 return nullptr;
1168 const auto* colorLine = reinterpret_cast<const VarColorLine*>(
1169 aState.COLRv1BaseAddr() + clOffset);
1170 Point c1(aState.F2P(ApplyVariation(aState, int16_t(x0), varIndexBase)),
1171 aState.F2P(
1172 ApplyVariation(aState, int16_t(y0), SatAdd(varIndexBase, 1))));
1173 float r1 = aState.F2P(
1174 ApplyVariation(aState, uint16_t(radius0), SatAdd(varIndexBase, 2)));
1175 Point c2(aState.F2P(
1176 ApplyVariation(aState, int16_t(x1), SatAdd(varIndexBase, 3))),
1177 aState.F2P(
1178 ApplyVariation(aState, int16_t(y1), SatAdd(varIndexBase, 4))));
1179 float r2 = aState.F2P(
1180 ApplyVariation(aState, uint16_t(radius1), SatAdd(varIndexBase, 5)));
1181 return NormalizeAndMakeGradient(aState, colorLine, c1, c2, r1, r2);
1185 struct PaintSweepGradient : public PaintPatternBase {
1186 enum { kFormat = 8 };
1187 uint8_t format;
1188 Offset24 colorLineOffset;
1189 FWORD centerX;
1190 FWORD centerY;
1191 F2DOT14 startAngle;
1192 F2DOT14 endAngle;
1194 UniquePtr<Pattern> MakePattern(const PaintState& aState,
1195 uint32_t aOffset) const {
1196 MOZ_ASSERT(format == kFormat);
1197 uint32_t clOffset = aOffset + colorLineOffset;
1198 if (clOffset + sizeof(ColorLine) + sizeof(ColorStop) > aState.mCOLRLength) {
1199 return nullptr;
1201 const auto* colorLine =
1202 reinterpret_cast<const ColorLine*>(aState.COLRv1BaseAddr() + clOffset);
1203 float start = float(startAngle) + 1.0f;
1204 float end = float(endAngle) + 1.0f;
1205 Point center(aState.F2P(int16_t(centerX)), aState.F2P(int16_t(centerY)));
1206 return NormalizeAndMakeGradient(aState, colorLine, center, start, end);
1209 template <typename T>
1210 UniquePtr<Pattern> NormalizeAndMakeGradient(const PaintState& aState,
1211 const T* aColorLine,
1212 Point aCenter, float aStart,
1213 float aEnd) const {
1214 if (aStart == aEnd && aColorLine->extend != T::EXTEND_PAD) {
1215 return MakeUnique<ColorPattern>(DeviceColor());
1217 UniquePtr<Pattern> solidColor = aColorLine->AsSolidColor(aState);
1218 if (solidColor) {
1219 return solidColor;
1221 // ConicGradientPattern works counterclockwise. If the gradient is defined
1222 // clockwise (with aStart greater than aEnd), we'll reverse the color line
1223 // and swap the start and end angles.
1224 bool reverse = aEnd < aStart;
1225 float firstStop, lastStop;
1226 RefPtr stops =
1227 aColorLine->MakeGradientStops(aState, &firstStop, &lastStop, reverse);
1228 if (!stops) {
1229 return nullptr;
1231 if (firstStop != 0.0 || lastStop != 1.0) {
1232 if (firstStop == lastStop) {
1233 if (aColorLine->extend != T::EXTEND_PAD) {
1234 return MakeUnique<ColorPattern>(DeviceColor());
1236 } else {
1237 float sweep = aEnd - aStart;
1238 aStart = aStart + sweep * firstStop;
1239 aEnd = aStart + sweep * (lastStop - firstStop);
1242 if (reverse) {
1243 std::swap(aStart, aEnd);
1245 return MakeUnique<ConicGradientPattern>(aCenter, M_PI / 2.0, aStart / 2.0,
1246 aEnd / 2.0, std::move(stops),
1247 Matrix::Scaling(1.0, -1.0));
1251 struct PaintVarSweepGradient : public PaintSweepGradient {
1252 enum { kFormat = 9 };
1253 uint32 varIndexBase;
1255 UniquePtr<Pattern> MakePattern(const PaintState& aState,
1256 uint32_t aOffset) const {
1257 MOZ_ASSERT(format == kFormat);
1258 uint32_t clOffset = aOffset + colorLineOffset;
1259 if (clOffset + sizeof(VarColorLine) + sizeof(VarColorStop) >
1260 aState.mCOLRLength) {
1261 return nullptr;
1263 const auto* colorLine = reinterpret_cast<const VarColorLine*>(
1264 aState.COLRv1BaseAddr() + clOffset);
1265 float start =
1266 ApplyVariation(aState, startAngle, SatAdd(varIndexBase, 2)) + 1.0f;
1267 float end =
1268 ApplyVariation(aState, endAngle, SatAdd(varIndexBase, 3)) + 1.0f;
1269 Point center(
1270 aState.F2P(ApplyVariation(aState, int16_t(centerX), varIndexBase)),
1271 aState.F2P(
1272 ApplyVariation(aState, int16_t(centerY), SatAdd(varIndexBase, 1))));
1273 return NormalizeAndMakeGradient(aState, colorLine, center, start, end);
1277 struct PaintGlyph {
1278 enum { kFormat = 10 };
1279 uint8_t format;
1280 Offset24 paintOffset;
1281 uint16 glyphID;
1283 bool Paint(const PaintState& aState, uint32_t aOffset,
1284 const Rect* aBounds) const {
1285 MOZ_ASSERT(format == kFormat);
1286 if (!paintOffset) {
1287 return true;
1289 Glyph g{uint16_t(glyphID), Point()};
1290 GlyphBuffer buffer{&g, 1};
1291 // If the paint is a simple fill (rather than a sub-graph of further paint
1292 // records), we can just use FillGlyphs to render it instead of setting up
1293 // a clip.
1294 UniquePtr<Pattern> fillPattern =
1295 DispatchMakePattern(aState, aOffset + paintOffset);
1296 if (fillPattern) {
1297 // On macOS we can't use FillGlyphs because when we render the glyph,
1298 // Core Text's own color font support may step in and ignore the
1299 // pattern. So to avoid this, fill the glyph as a path instead.
1300 #if XP_MACOSX
1301 RefPtr<Path> path =
1302 aState.mScaledFont->GetPathForGlyphs(buffer, aState.mDrawTarget);
1303 aState.mDrawTarget->Fill(path, *fillPattern, aState.mDrawOptions);
1304 #else
1305 aState.mDrawTarget->FillGlyphs(aState.mScaledFont, buffer, *fillPattern,
1306 aState.mDrawOptions);
1307 #endif
1308 return true;
1310 RefPtr<Path> path =
1311 aState.mScaledFont->GetPathForGlyphs(buffer, aState.mDrawTarget);
1312 aState.mDrawTarget->PushClip(path);
1313 bool ok = DispatchPaint(aState, aOffset + paintOffset, aBounds);
1314 aState.mDrawTarget->PopClip();
1315 return ok;
1318 Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const {
1319 MOZ_ASSERT(format == kFormat);
1320 Glyph g{uint16_t(glyphID), Point()};
1321 GlyphBuffer buffer{&g, 1};
1322 RefPtr<Path> path =
1323 aState.mScaledFont->GetPathForGlyphs(buffer, aState.mDrawTarget);
1324 return path->GetFastBounds();
1328 struct PaintColrGlyph {
1329 enum { kFormat = 11 };
1330 uint8_t format;
1331 uint16 glyphID;
1333 // Factored out as a helper because this is also used by the top-level
1334 // PaintGlyphGraph function.
1335 static bool DoPaint(const PaintState& aState,
1336 const BaseGlyphPaintRecord* aBaseGlyphPaint,
1337 uint32_t aGlyphId, const Rect* aBounds) {
1338 AutoPopClips clips(aState.mDrawTarget);
1339 Rect clipRect;
1340 if (const auto* clipList = aState.mHeader.v1->clipList()) {
1341 if (const auto* clip = clipList->GetClip(aGlyphId)) {
1342 clipRect = clip->GetRect(aState);
1343 clips.PushClipRect(clipRect);
1344 if (!aBounds) {
1345 aBounds = &clipRect;
1349 return DispatchPaint(
1350 aState,
1351 aState.mHeader.v1->baseGlyphListOffset + aBaseGlyphPaint->paintOffset,
1352 aBounds);
1355 bool Paint(const PaintState& aState, uint32_t aOffset,
1356 const Rect* aBounds) const {
1357 MOZ_ASSERT(format == kFormat);
1358 IF_CYCLE_RETURN(true);
1359 const auto* base = aState.mHeader.v1->GetBaseGlyphPaint(glyphID);
1360 return base ? DoPaint(aState, base, uint16_t(glyphID), aBounds) : false;
1363 Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const {
1364 IF_CYCLE_RETURN(Rect());
1365 if (const auto* clipList = aState.mHeader.v1->clipList()) {
1366 if (const auto* clip = clipList->GetClip(uint16_t(glyphID))) {
1367 return clip->GetRect(aState);
1370 if (const auto* base =
1371 aState.mHeader.v1->GetBaseGlyphPaint(uint16_t(glyphID))) {
1372 return DispatchGetBounds(
1373 aState, aState.mHeader.v1->baseGlyphListOffset + base->paintOffset);
1375 return Rect();
1379 #undef IF_CYCLE_RETURN
1381 struct Affine2x3 {
1382 Fixed xx;
1383 Fixed yx;
1384 Fixed xy;
1385 Fixed yy;
1386 Fixed dx;
1387 Fixed dy;
1389 Matrix AsMatrix(const PaintState& aState) const {
1390 // Flip signs because of opposite y-axis direction in moz2d vs opentype.
1391 return Matrix(float(xx), -float(yx), -float(xy), float(yy),
1392 aState.F2P(float(dx)), -aState.F2P(float(dy)));
1396 struct VarAffine2x3 : public Affine2x3 {
1397 uint32 varIndexBase;
1399 Matrix AsMatrix(const PaintState& aState) const {
1400 return Matrix(
1401 ApplyVariation(aState, xx, varIndexBase),
1402 -ApplyVariation(aState, yx, SatAdd(varIndexBase, 1)),
1403 -ApplyVariation(aState, xy, SatAdd(varIndexBase, 2)),
1404 ApplyVariation(aState, yy, SatAdd(varIndexBase, 3)),
1405 aState.F2P(ApplyVariation(aState, dx, SatAdd(varIndexBase, 4))),
1406 -aState.F2P(ApplyVariation(aState, dy, SatAdd(varIndexBase, 5))));
1410 struct PaintTransformBase {
1411 uint8_t format;
1412 Offset24 paintOffset;
1414 bool Paint(const PaintState& aState, uint32_t aOffset,
1415 const Rect* aBounds) const {
1416 if (!paintOffset) {
1417 return true;
1419 AutoRestoreTransform saveTransform(aState.mDrawTarget);
1420 aState.mDrawTarget->ConcatTransform(DispatchGetMatrix(aState, aOffset));
1421 return DispatchPaint(aState, aOffset + paintOffset, aBounds);
1424 Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const {
1425 if (!paintOffset) {
1426 return Rect();
1428 Rect bounds = DispatchGetBounds(aState, aOffset + paintOffset);
1429 bounds = DispatchGetMatrix(aState, aOffset).TransformBounds(bounds);
1430 return bounds;
1434 struct PaintTransform : public PaintTransformBase {
1435 enum { kFormat = 12 };
1436 Offset24 transformOffset;
1438 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1439 MOZ_ASSERT(format == kFormat);
1440 if (aOffset + transformOffset + sizeof(Affine2x3) > aState.mCOLRLength) {
1441 return Matrix();
1443 const auto* t = reinterpret_cast<const Affine2x3*>(
1444 aState.COLRv1BaseAddr() + aOffset + transformOffset);
1445 return t->AsMatrix(aState);
1449 struct PaintVarTransform : public PaintTransformBase {
1450 enum { kFormat = 13 };
1451 Offset24 transformOffset;
1453 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1454 MOZ_ASSERT(format == kFormat);
1455 if (aOffset + transformOffset + sizeof(VarAffine2x3) > aState.mCOLRLength) {
1456 return Matrix();
1458 const auto* t = reinterpret_cast<const VarAffine2x3*>(
1459 aState.COLRv1BaseAddr() + aOffset + transformOffset);
1460 return t->AsMatrix(aState);
1464 struct PaintTranslate : public PaintTransformBase {
1465 enum { kFormat = 14 };
1466 FWORD dx;
1467 FWORD dy;
1469 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1470 MOZ_ASSERT(format == kFormat);
1471 return Matrix::Translation(aState.F2P(int16_t(dx)),
1472 -aState.F2P(int16_t(dy)));
1476 struct PaintVarTranslate : public PaintTranslate {
1477 enum { kFormat = 15 };
1478 uint32 varIndexBase;
1480 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1481 MOZ_ASSERT(format == kFormat);
1482 return Matrix::Translation(
1483 aState.F2P(ApplyVariation(aState, int16_t(dx), varIndexBase)),
1484 -aState.F2P(
1485 ApplyVariation(aState, int16_t(dy), SatAdd(varIndexBase, 1))));
1489 struct PaintScale : public PaintTransformBase {
1490 enum { kFormat = 16 };
1491 F2DOT14 scaleX;
1492 F2DOT14 scaleY;
1494 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1495 MOZ_ASSERT(format == kFormat);
1496 return Matrix::Scaling(float(scaleX), float(scaleY));
1500 struct PaintVarScale : public PaintScale {
1501 enum { kFormat = 17 };
1502 uint32 varIndexBase;
1504 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1505 MOZ_ASSERT(format == kFormat);
1506 return Matrix::Scaling(
1507 ApplyVariation(aState, scaleX, varIndexBase),
1508 ApplyVariation(aState, scaleY, SatAdd(varIndexBase, 1)));
1512 struct PaintScaleAroundCenter : public PaintTransformBase {
1513 enum { kFormat = 18 };
1514 F2DOT14 scaleX;
1515 F2DOT14 scaleY;
1516 FWORD centerX;
1517 FWORD centerY;
1519 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1520 MOZ_ASSERT(format == kFormat);
1521 Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY)));
1522 return Matrix::Translation(center)
1523 .PreScale(float(scaleX), float(scaleY))
1524 .PreTranslate(-center);
1528 struct PaintVarScaleAroundCenter : public PaintScaleAroundCenter {
1529 enum { kFormat = 19 };
1530 uint32 varIndexBase;
1532 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1533 MOZ_ASSERT(format == kFormat);
1534 Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX),
1535 SatAdd(varIndexBase, 2))),
1536 -aState.F2P(ApplyVariation(aState, int16_t(centerY),
1537 SatAdd(varIndexBase, 3))));
1538 return Matrix::Translation(center)
1539 .PreScale(ApplyVariation(aState, scaleX, varIndexBase),
1540 ApplyVariation(aState, scaleY, SatAdd(varIndexBase, 1)))
1541 .PreTranslate(-center);
1545 struct PaintScaleUniform : public PaintTransformBase {
1546 enum { kFormat = 20 };
1547 F2DOT14 scale;
1549 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1550 MOZ_ASSERT(format == kFormat);
1551 return Matrix::Scaling(float(scale), float(scale));
1555 struct PaintVarScaleUniform : public PaintScaleUniform {
1556 enum { kFormat = 21 };
1557 uint32 varIndexBase;
1559 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1560 MOZ_ASSERT(format == kFormat);
1561 float sc = ApplyVariation(aState, scale, varIndexBase);
1562 return Matrix::Scaling(sc, sc);
1566 struct PaintScaleUniformAroundCenter : public PaintTransformBase {
1567 enum { kFormat = 22 };
1568 F2DOT14 scale;
1569 FWORD centerX;
1570 FWORD centerY;
1572 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1573 MOZ_ASSERT(format == kFormat);
1574 Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY)));
1575 return Matrix::Translation(center)
1576 .PreScale(float(scale), float(scale))
1577 .PreTranslate(-center);
1581 struct PaintVarScaleUniformAroundCenter : public PaintScaleUniformAroundCenter {
1582 enum { kFormat = 23 };
1583 uint32 varIndexBase;
1585 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1586 MOZ_ASSERT(format == kFormat);
1587 float sc = ApplyVariation(aState, scale, varIndexBase);
1588 Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX),
1589 SatAdd(varIndexBase, 1))),
1590 -aState.F2P(ApplyVariation(aState, int16_t(centerY),
1591 SatAdd(varIndexBase, 2))));
1592 return Matrix::Translation(center).PreScale(sc, sc).PreTranslate(-center);
1596 struct PaintRotate : public PaintTransformBase {
1597 enum { kFormat = 24 };
1598 F2DOT14 angle;
1600 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1601 MOZ_ASSERT(format == kFormat);
1602 return Matrix::Rotation(-float(angle) * float(M_PI));
1606 struct PaintVarRotate : public PaintRotate {
1607 enum { kFormat = 25 };
1608 uint32 varIndexBase;
1610 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1611 MOZ_ASSERT(format == kFormat);
1612 float ang = ApplyVariation(aState, angle, varIndexBase);
1613 return Matrix::Rotation(-ang * float(M_PI));
1617 struct PaintRotateAroundCenter : public PaintTransformBase {
1618 enum { kFormat = 26 };
1619 F2DOT14 angle;
1620 FWORD centerX;
1621 FWORD centerY;
1623 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1624 MOZ_ASSERT(format == kFormat);
1625 Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY)));
1626 return Matrix::Translation(center)
1627 .PreRotate(-float(angle) * float(M_PI))
1628 .PreTranslate(-center);
1632 struct PaintVarRotateAroundCenter : public PaintRotateAroundCenter {
1633 enum { kFormat = 27 };
1634 uint32 varIndexBase;
1636 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1637 MOZ_ASSERT(format == kFormat);
1638 float ang = ApplyVariation(aState, angle, varIndexBase);
1639 Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX),
1640 SatAdd(varIndexBase, 1))),
1641 -aState.F2P(ApplyVariation(aState, int16_t(centerY),
1642 SatAdd(varIndexBase, 2))));
1643 return Matrix::Translation(center)
1644 .PreRotate(-ang * float(M_PI))
1645 .PreTranslate(-center);
1649 static inline Matrix SkewMatrix(float aSkewX, float aSkewY) {
1650 float xy = tanf(aSkewX * float(M_PI));
1651 float yx = tanf(aSkewY * float(M_PI));
1652 return std::isnan(xy) || std::isnan(yx) ? Matrix()
1653 : Matrix(1.0, -yx, xy, 1.0, 0.0, 0.0);
1656 struct PaintSkew : public PaintTransformBase {
1657 enum { kFormat = 28 };
1658 F2DOT14 xSkewAngle;
1659 F2DOT14 ySkewAngle;
1661 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1662 MOZ_ASSERT(format == kFormat);
1663 return SkewMatrix(float(xSkewAngle), float(ySkewAngle));
1667 struct PaintVarSkew : public PaintSkew {
1668 enum { kFormat = 29 };
1669 uint32 varIndexBase;
1671 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1672 MOZ_ASSERT(format == kFormat);
1673 return SkewMatrix(
1674 float(ApplyVariation(aState, xSkewAngle, varIndexBase)),
1675 float(ApplyVariation(aState, ySkewAngle, SatAdd(varIndexBase, 1))));
1679 struct PaintSkewAroundCenter : public PaintTransformBase {
1680 enum { kFormat = 30 };
1681 F2DOT14 xSkewAngle;
1682 F2DOT14 ySkewAngle;
1683 FWORD centerX;
1684 FWORD centerY;
1686 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1687 MOZ_ASSERT(format == kFormat);
1688 Point center(aState.F2P(int16_t(centerX)), -aState.F2P(int16_t(centerY)));
1689 return Matrix::Translation(center)
1690 .PreMultiply(SkewMatrix(float(xSkewAngle), float(ySkewAngle)))
1691 .PreTranslate(-center);
1695 struct PaintVarSkewAroundCenter : public PaintSkewAroundCenter {
1696 enum { kFormat = 31 };
1697 uint32 varIndexBase;
1699 Matrix GetMatrix(const PaintState& aState, uint32_t aOffset) const {
1700 MOZ_ASSERT(format == kFormat);
1701 Point center(aState.F2P(ApplyVariation(aState, int16_t(centerX),
1702 SatAdd(varIndexBase, 2))),
1703 -aState.F2P(ApplyVariation(aState, int16_t(centerY),
1704 SatAdd(varIndexBase, 3))));
1705 return Matrix::Translation(center)
1706 .PreMultiply(SkewMatrix(
1707 ApplyVariation(aState, xSkewAngle, varIndexBase),
1708 ApplyVariation(aState, ySkewAngle, SatAdd(varIndexBase, 1))))
1709 .PreTranslate(-center);
1713 struct PaintComposite {
1714 enum { kFormat = 32 };
1715 uint8_t format;
1716 Offset24 sourcePaintOffset;
1717 uint8_t compositeMode;
1718 Offset24 backdropPaintOffset;
1720 enum {
1721 COMPOSITE_CLEAR = 0,
1722 COMPOSITE_SRC = 1,
1723 COMPOSITE_DEST = 2,
1724 COMPOSITE_SRC_OVER = 3,
1725 COMPOSITE_DEST_OVER = 4,
1726 COMPOSITE_SRC_IN = 5,
1727 COMPOSITE_DEST_IN = 6,
1728 COMPOSITE_SRC_OUT = 7,
1729 COMPOSITE_DEST_OUT = 8,
1730 COMPOSITE_SRC_ATOP = 9,
1731 COMPOSITE_DEST_ATOP = 10,
1732 COMPOSITE_XOR = 11,
1733 COMPOSITE_PLUS = 12,
1734 COMPOSITE_SCREEN = 13,
1735 COMPOSITE_OVERLAY = 14,
1736 COMPOSITE_DARKEN = 15,
1737 COMPOSITE_LIGHTEN = 16,
1738 COMPOSITE_COLOR_DODGE = 17,
1739 COMPOSITE_COLOR_BURN = 18,
1740 COMPOSITE_HARD_LIGHT = 19,
1741 COMPOSITE_SOFT_LIGHT = 20,
1742 COMPOSITE_DIFFERENCE = 21,
1743 COMPOSITE_EXCLUSION = 22,
1744 COMPOSITE_MULTIPLY = 23,
1745 COMPOSITE_HSL_HUE = 24,
1746 COMPOSITE_HSL_SATURATION = 25,
1747 COMPOSITE_HSL_COLOR = 26,
1748 COMPOSITE_HSL_LUMINOSITY = 27
1751 bool Paint(const PaintState& aState, uint32_t aOffset,
1752 const Rect* aBounds) const {
1753 MOZ_ASSERT(format == kFormat);
1754 if (!backdropPaintOffset || !sourcePaintOffset) {
1755 return true;
1757 auto mapCompositionMode = [](uint8_t aMode) -> CompositionOp {
1758 switch (aMode) {
1759 default:
1760 return CompositionOp::OP_SOURCE;
1761 case COMPOSITE_CLEAR:
1762 case COMPOSITE_SRC:
1763 case COMPOSITE_DEST:
1764 MOZ_ASSERT_UNREACHABLE("should have short-circuited");
1765 return CompositionOp::OP_SOURCE;
1766 case COMPOSITE_SRC_OVER:
1767 return CompositionOp::OP_OVER;
1768 case COMPOSITE_DEST_OVER:
1769 return CompositionOp::OP_DEST_OVER;
1770 case COMPOSITE_SRC_IN:
1771 return CompositionOp::OP_IN;
1772 case COMPOSITE_DEST_IN:
1773 return CompositionOp::OP_DEST_IN;
1774 case COMPOSITE_SRC_OUT:
1775 return CompositionOp::OP_OUT;
1776 case COMPOSITE_DEST_OUT:
1777 return CompositionOp::OP_DEST_OUT;
1778 case COMPOSITE_SRC_ATOP:
1779 return CompositionOp::OP_ATOP;
1780 case COMPOSITE_DEST_ATOP:
1781 return CompositionOp::OP_DEST_ATOP;
1782 case COMPOSITE_XOR:
1783 return CompositionOp::OP_XOR;
1784 case COMPOSITE_PLUS:
1785 return CompositionOp::OP_ADD;
1786 case COMPOSITE_SCREEN:
1787 return CompositionOp::OP_SCREEN;
1788 case COMPOSITE_OVERLAY:
1789 return CompositionOp::OP_OVERLAY;
1790 case COMPOSITE_DARKEN:
1791 return CompositionOp::OP_DARKEN;
1792 case COMPOSITE_LIGHTEN:
1793 return CompositionOp::OP_LIGHTEN;
1794 case COMPOSITE_COLOR_DODGE:
1795 return CompositionOp::OP_COLOR_DODGE;
1796 case COMPOSITE_COLOR_BURN:
1797 return CompositionOp::OP_COLOR_BURN;
1798 case COMPOSITE_HARD_LIGHT:
1799 return CompositionOp::OP_HARD_LIGHT;
1800 case COMPOSITE_SOFT_LIGHT:
1801 return CompositionOp::OP_SOFT_LIGHT;
1802 case COMPOSITE_DIFFERENCE:
1803 return CompositionOp::OP_DIFFERENCE;
1804 case COMPOSITE_EXCLUSION:
1805 return CompositionOp::OP_EXCLUSION;
1806 case COMPOSITE_MULTIPLY:
1807 return CompositionOp::OP_MULTIPLY;
1808 case COMPOSITE_HSL_HUE:
1809 return CompositionOp::OP_HUE;
1810 case COMPOSITE_HSL_SATURATION:
1811 return CompositionOp::OP_SATURATION;
1812 case COMPOSITE_HSL_COLOR:
1813 return CompositionOp::OP_COLOR;
1814 case COMPOSITE_HSL_LUMINOSITY:
1815 return CompositionOp::OP_LUMINOSITY;
1818 // Short-circuit cases:
1819 if (compositeMode == COMPOSITE_CLEAR) {
1820 return true;
1822 if (compositeMode == COMPOSITE_SRC) {
1823 return DispatchPaint(aState, aOffset + sourcePaintOffset, aBounds);
1825 if (compositeMode == COMPOSITE_DEST) {
1826 return DispatchPaint(aState, aOffset + backdropPaintOffset, aBounds);
1829 // We need bounds for the temporary surfaces; so if we didn't have
1830 // explicitly-provided bounds from a clipList entry for the top-level
1831 // glyph, then we need to determine the bounding rect here.
1832 Rect bounds = aBounds ? *aBounds : GetBoundingRect(aState, aOffset);
1833 if (bounds.IsEmpty()) {
1834 return true;
1836 bounds.RoundOut();
1838 PaintState state = aState;
1839 state.mDrawOptions.mCompositionOp = CompositionOp::OP_OVER;
1840 IntSize intSize(int(bounds.width), int(bounds.height));
1842 if (!aState.mDrawTarget->CanCreateSimilarDrawTarget(
1843 intSize, SurfaceFormat::B8G8R8A8)) {
1844 // We're not going to be able to render this, so just bail out.
1845 // (Returning true rather than false means we'll just not paint this
1846 // part of the glyph, but won't return an error and likely fall back
1847 // to an ugly black blob.)
1848 return true;
1851 // Draw the backdrop paint graph to a temporary surface.
1852 RefPtr backdrop = aState.mDrawTarget->CreateSimilarDrawTarget(
1853 intSize, SurfaceFormat::B8G8R8A8);
1854 if (!backdrop) {
1855 return true;
1857 backdrop->SetTransform(Matrix::Translation(-bounds.TopLeft()));
1858 state.mDrawTarget = backdrop;
1859 if (!DispatchPaint(state, aOffset + backdropPaintOffset, &bounds)) {
1860 return false;
1863 // Draw the source paint graph to another temp surface.
1864 RefPtr source = aState.mDrawTarget->CreateSimilarDrawTarget(
1865 intSize, SurfaceFormat::B8G8R8A8);
1866 if (!source) {
1867 return true;
1869 source->SetTransform(Matrix::Translation(-bounds.TopLeft()));
1870 state.mDrawTarget = source;
1871 if (!DispatchPaint(state, aOffset + sourcePaintOffset, &bounds)) {
1872 return false;
1875 // Composite the source onto the backdrop using the specified operation.
1876 Rect localBounds(Point(), bounds.Size());
1877 RefPtr snapshot = source->Snapshot();
1878 backdrop->SetTransform(Matrix());
1879 backdrop->DrawSurface(snapshot, localBounds, localBounds,
1880 DrawSurfaceOptions(),
1881 DrawOptions(1.0, mapCompositionMode(compositeMode)));
1883 // And copy the composited result to our final destination.
1884 snapshot = backdrop->Snapshot();
1885 aState.mDrawTarget->DrawSurface(snapshot, bounds, localBounds);
1887 return true;
1890 Rect GetBoundingRect(const PaintState& aState, uint32_t aOffset) const {
1891 if (!backdropPaintOffset || !sourcePaintOffset) {
1892 return Rect();
1894 // For now, just return the maximal bounds that could result; this could be
1895 // smarter, returning just one of the rects or their intersection when
1896 // appropriate for the composite mode in effect.
1897 return DispatchGetBounds(aState, aOffset + backdropPaintOffset)
1898 .Union(DispatchGetBounds(aState, aOffset + sourcePaintOffset));
1902 #pragma pack()
1904 const BaseGlyphPaintRecord* COLRv1Header::GetBaseGlyphPaint(
1905 uint32_t aGlyphId) const {
1906 const auto* list = baseGlyphList();
1907 if (!list) {
1908 return nullptr;
1910 auto compare = [](const void* key, const void* data) -> int {
1911 uint32_t glyphId = (uint32_t)(uintptr_t)key;
1912 const auto* paintRecord =
1913 reinterpret_cast<const BaseGlyphPaintRecord*>(data);
1914 uint32_t baseGlyphId = uint16_t(paintRecord->glyphID);
1915 if (baseGlyphId == glyphId) {
1916 return 0;
1918 return baseGlyphId > glyphId ? -1 : 1;
1920 return reinterpret_cast<const BaseGlyphPaintRecord*>(
1921 bsearch((void*)(uintptr_t)aGlyphId, list + 1,
1922 uint32_t(list->numBaseGlyphPaintRecords),
1923 sizeof(BaseGlyphPaintRecord), compare));
1926 #define DO_CASE_VAR(T) \
1927 DO_CASE(Paint##T); \
1928 DO_CASE(PaintVar##T)
1930 // Process paint table at aOffset from start of COLRv1 table.
1931 static bool DispatchPaint(const PaintState& aState, uint32_t aOffset,
1932 const Rect* aBounds) {
1933 if (aOffset >= aState.mCOLRLength) {
1934 return false;
1937 const char* paint = aState.COLRv1BaseAddr() + aOffset;
1938 // All paint table formats start with an 8-bit 'format' field.
1939 uint8_t format = uint8_t(*paint);
1941 #define DO_CASE(T) \
1942 case T::kFormat: \
1943 return aOffset + sizeof(T) <= aState.mCOLRLength \
1944 ? reinterpret_cast<const T*>(paint)->Paint(aState, aOffset, \
1945 aBounds) \
1946 : false
1948 switch (format) {
1949 DO_CASE(PaintColrLayers);
1950 DO_CASE_VAR(Solid);
1951 DO_CASE_VAR(LinearGradient);
1952 DO_CASE_VAR(RadialGradient);
1953 DO_CASE_VAR(SweepGradient);
1954 DO_CASE(PaintGlyph);
1955 DO_CASE(PaintColrGlyph);
1956 DO_CASE_VAR(Transform);
1957 DO_CASE_VAR(Translate);
1958 DO_CASE_VAR(Scale);
1959 DO_CASE_VAR(ScaleAroundCenter);
1960 DO_CASE_VAR(ScaleUniform);
1961 DO_CASE_VAR(ScaleUniformAroundCenter);
1962 DO_CASE_VAR(Rotate);
1963 DO_CASE_VAR(RotateAroundCenter);
1964 DO_CASE_VAR(Skew);
1965 DO_CASE_VAR(SkewAroundCenter);
1966 DO_CASE(PaintComposite);
1967 default:
1968 break;
1971 #undef DO_CASE
1973 return false;
1976 // Get a gfx::Pattern corresponding to the given paint table, if it is a
1977 // simple format that can be used as a fill (not a sub-graph).
1978 static UniquePtr<Pattern> DispatchMakePattern(const PaintState& aState,
1979 uint32_t aOffset) {
1980 if (aOffset >= aState.mCOLRLength) {
1981 return nullptr;
1984 const char* paint = aState.COLRv1BaseAddr() + aOffset;
1985 // All paint table formats start with an 8-bit 'format' field.
1986 uint8_t format = uint8_t(*paint);
1988 #define DO_CASE(T) \
1989 case T::kFormat: \
1990 return aOffset + sizeof(T) <= aState.mCOLRLength \
1991 ? reinterpret_cast<const T*>(paint)->MakePattern(aState, \
1992 aOffset) \
1993 : nullptr;
1995 switch (format) {
1996 DO_CASE_VAR(Solid);
1997 DO_CASE_VAR(LinearGradient);
1998 DO_CASE_VAR(RadialGradient);
1999 DO_CASE_VAR(SweepGradient);
2000 default:
2001 break;
2004 #undef DO_CASE
2006 return nullptr;
2009 static Matrix DispatchGetMatrix(const PaintState& aState, uint32_t aOffset) {
2010 if (aOffset >= aState.mCOLRLength) {
2011 return Matrix();
2014 const char* paint = aState.COLRv1BaseAddr() + aOffset;
2015 // All paint table formats start with an 8-bit 'format' field.
2016 uint8_t format = uint8_t(*paint);
2018 #define DO_CASE(T) \
2019 case T::kFormat: \
2020 return aOffset + sizeof(T) <= aState.mCOLRLength \
2021 ? reinterpret_cast<const T*>(paint)->GetMatrix(aState, aOffset) \
2022 : Matrix();
2024 switch (format) {
2025 DO_CASE_VAR(Transform);
2026 DO_CASE_VAR(Translate);
2027 DO_CASE_VAR(Scale);
2028 DO_CASE_VAR(ScaleAroundCenter);
2029 DO_CASE_VAR(ScaleUniform);
2030 DO_CASE_VAR(ScaleUniformAroundCenter);
2031 DO_CASE_VAR(Rotate);
2032 DO_CASE_VAR(RotateAroundCenter);
2033 DO_CASE_VAR(Skew);
2034 DO_CASE_VAR(SkewAroundCenter);
2035 default:
2036 break;
2039 #undef DO_CASE
2041 return Matrix();
2044 static Rect DispatchGetBounds(const PaintState& aState, uint32_t aOffset) {
2045 if (aOffset >= aState.mCOLRLength) {
2046 return Rect();
2049 const char* paint = aState.COLRv1BaseAddr() + aOffset;
2050 // All paint table formats start with an 8-bit 'format' field.
2051 uint8_t format = uint8_t(*paint);
2053 #define DO_CASE(T) \
2054 case T::kFormat: \
2055 return aOffset + sizeof(T) <= aState.mCOLRLength \
2056 ? reinterpret_cast<const T*>(paint)->GetBoundingRect(aState, \
2057 aOffset) \
2058 : Rect();
2060 switch (format) {
2061 DO_CASE(PaintColrLayers);
2062 DO_CASE_VAR(Solid);
2063 DO_CASE_VAR(LinearGradient);
2064 DO_CASE_VAR(RadialGradient);
2065 DO_CASE_VAR(SweepGradient);
2066 DO_CASE(PaintGlyph);
2067 DO_CASE(PaintColrGlyph);
2068 DO_CASE_VAR(Transform);
2069 DO_CASE_VAR(Translate);
2070 DO_CASE_VAR(Scale);
2071 DO_CASE_VAR(ScaleAroundCenter);
2072 DO_CASE_VAR(ScaleUniform);
2073 DO_CASE_VAR(ScaleUniformAroundCenter);
2074 DO_CASE_VAR(Rotate);
2075 DO_CASE_VAR(RotateAroundCenter);
2076 DO_CASE_VAR(Skew);
2077 DO_CASE_VAR(SkewAroundCenter);
2078 DO_CASE(PaintComposite);
2079 default:
2080 break;
2083 #undef DO_CASE
2085 return Rect();
2088 #undef DO_CASE_VAR
2090 bool COLRHeader::Validate(uint64_t aLength) const {
2091 uint64_t count;
2092 if ((count = numBaseGlyphRecords)) {
2093 if (baseGlyphRecordsOffset + count * sizeof(BaseGlyphRecord) > aLength) {
2094 return false;
2097 if ((count = numLayerRecords)) {
2098 if (layerRecordsOffset + count * sizeof(LayerRecord) > aLength) {
2099 return false;
2102 // Check ordering of baseGlyphRecords, and that layer indices are in bounds.
2103 int32_t lastGlyphId = -1;
2104 const auto* baseGlyph = reinterpret_cast<const BaseGlyphRecord*>(
2105 reinterpret_cast<const char*>(this) + baseGlyphRecordsOffset);
2106 for (uint16_t i = 0; i < uint16_t(numBaseGlyphRecords); i++, baseGlyph++) {
2107 uint16_t glyphId = baseGlyph->glyphId;
2108 if (lastGlyphId >= int32_t(glyphId)) {
2109 return false;
2111 if (uint32_t(baseGlyph->firstLayerIndex) + uint32_t(baseGlyph->numLayers) >
2112 uint32_t(numLayerRecords)) {
2113 return false;
2115 lastGlyphId = glyphId;
2117 // We don't need to validate all the layer paletteEntryIndex fields here,
2118 // because PaintState.GetColor will range-check them at paint time.
2119 return true;
2122 bool COLRv1Header::Validate(uint64_t aLength) const {
2123 if (!base.Validate(aLength)) {
2124 return false;
2126 if (baseGlyphListOffset + sizeof(BaseGlyphList) > aLength ||
2127 layerListOffset + sizeof(LayerList) > aLength ||
2128 clipListOffset + sizeof(ClipList) > aLength ||
2129 varIndexMapOffset + sizeof(DeltaSetIndexMap) > aLength ||
2130 itemVariationStoreOffset + sizeof(ItemVariationStore) > aLength) {
2131 return false;
2133 const auto* b = baseGlyphList();
2134 if (b && !b->Validate(this, aLength)) {
2135 return false;
2137 const auto* l = layerList();
2138 if (l && !l->Validate(this, aLength)) {
2139 return false;
2141 const auto* c = clipList();
2142 if (c && !c->Validate(this, aLength)) {
2143 return false;
2145 const auto* v = varIndexMap();
2146 if (v && !v->Validate(this, aLength)) {
2147 return false;
2149 const auto* i = itemVariationStore();
2150 if (i && !i->Validate(this, aLength)) {
2151 return false;
2153 return true;
2156 bool BaseGlyphList::Validate(const COLRv1Header* aHeader,
2157 uint64_t aLength) const {
2158 uint64_t count = numBaseGlyphPaintRecords;
2159 if (aHeader->baseGlyphListOffset + sizeof(BaseGlyphList) +
2160 count * sizeof(BaseGlyphPaintRecord) >
2161 aLength) {
2162 return false;
2164 // Check ordering of baseGlyphPaint records.
2165 const auto* records = baseGlyphPaintRecords();
2166 int32_t prevGlyphID = -1;
2167 for (uint32_t i = 0; i < numBaseGlyphPaintRecords; ++i) {
2168 const auto& base = records[i];
2169 if (int32_t(uint16_t(base.glyphID)) <= prevGlyphID) {
2170 return false;
2172 prevGlyphID = base.glyphID;
2174 return true;
2177 bool LayerList::Validate(const COLRv1Header* aHeader, uint64_t aLength) const {
2178 // Check that paintOffsets array fits.
2179 uint64_t count = numLayers;
2180 uint32_t listOffset = aHeader->layerListOffset;
2181 if (listOffset + sizeof(LayerList) + count * sizeof(uint32) > aLength) {
2182 return false;
2184 // Check that values in paintOffsets are within bounds.
2185 const auto* offsets = paintOffsets();
2186 for (uint32_t i = 0; i < count; i++) {
2187 if (listOffset + offsets[i] >= aLength) {
2188 return false;
2191 return true;
2194 bool Clip::Validate(const COLRv1Header* aHeader, uint64_t aLength) const {
2195 uint32_t offset = aHeader->clipListOffset + clipBoxOffset;
2196 if (offset >= aLength) {
2197 return false;
2199 // ClipBox format begins with a 1-byte format field.
2200 const uint8_t* box = reinterpret_cast<const uint8_t*>(aHeader) + offset;
2201 switch (*box) {
2202 case 1:
2203 if (offset <= aLength - sizeof(ClipBoxFormat1)) {
2204 return true;
2206 break;
2207 case 2:
2208 if (offset <= aLength - sizeof(ClipBoxFormat2)) {
2209 return true;
2211 break;
2212 default:
2213 // unknown ClipBoxFormat
2214 break;
2216 return false;
2219 bool ClipList::Validate(const COLRv1Header* aHeader, uint64_t aLength) const {
2220 uint64_t count = numClips;
2221 if (aHeader->clipListOffset + sizeof(ClipList) + count * sizeof(Clip) >
2222 aLength) {
2223 return false;
2225 // Check ordering of clip records, and that they are within bounds.
2226 const auto* clipArray = clips();
2227 int32_t prevEnd = -1;
2228 for (uint32_t i = 0; i < count; ++i) {
2229 const auto& clip = clipArray[i];
2230 if (int32_t(uint16_t(clip.startGlyphID)) <= prevEnd) {
2231 return false;
2233 if (!clip.Validate(aHeader, aLength)) {
2234 return false;
2236 prevEnd = uint16_t(clip.endGlyphID);
2238 return true;
2241 bool DeltaSetIndexMap::Validate(const COLRv1Header* aHeader,
2242 uint64_t aLength) const {
2243 uint64_t entrySize = ((entryFormat & MAP_ENTRY_SIZE_MASK) >> 4) + 1;
2244 uint64_t mapCount;
2245 uint64_t baseSize;
2246 switch (format) {
2247 case 0:
2248 mapCount = uint32_t(v0.mapCount);
2249 baseSize = 4;
2250 break;
2251 case 1:
2252 mapCount = uint32_t(v1.mapCount);
2253 baseSize = 6;
2254 break;
2255 default:
2256 return false;
2258 if (aHeader->varIndexMapOffset + baseSize + mapCount * entrySize > aLength) {
2259 return false;
2261 return true;
2264 bool ItemVariationStore::Validate(const COLRv1Header* aHeader,
2265 uint64_t aLength) const {
2266 uint64_t offset = reinterpret_cast<const char*>(this) -
2267 reinterpret_cast<const char*>(aHeader);
2268 if (offset + variationRegionListOffset + sizeof(VariationRegionList) >
2269 aLength) {
2270 return false;
2272 if (!variationRegionList()->Validate(aHeader, aLength)) {
2273 return false;
2275 uint16_t count = itemVariationDataCount;
2276 if (offset + sizeof(ItemVariationStore) + count * sizeof(Offset32) >
2277 aLength) {
2278 return false;
2280 const auto* ivdOffsets = itemVariationDataOffsets();
2281 for (uint16_t i = 0; i < count; ++i) {
2282 uint32_t o = ivdOffsets[i];
2283 if (offset + o + sizeof(ItemVariationData) > aLength) {
2284 return false;
2286 const auto* variationData = reinterpret_cast<const ItemVariationData*>(
2287 reinterpret_cast<const char*>(this) + ivdOffsets[i]);
2288 if (!variationData->Validate(aHeader, aLength)) {
2289 return false;
2292 return true;
2295 bool ItemVariationData::Validate(const COLRv1Header* aHeader,
2296 uint64_t aLength) const {
2297 if (reinterpret_cast<const char*>(regionIndexes() +
2298 uint16_t(regionIndexCount)) >
2299 reinterpret_cast<const char*>(aHeader) + aLength) {
2300 return false;
2302 uint16_t wordDeltaCount = this->wordDeltaCount;
2303 bool longWords = wordDeltaCount & LONG_WORDS;
2304 wordDeltaCount &= WORD_DELTA_COUNT_MASK;
2305 uint32_t deltaSetSize =
2306 (uint16_t(regionIndexCount) + uint16_t(wordDeltaCount)) << longWords;
2307 if (reinterpret_cast<const char*>(deltaSets()) +
2308 uint16_t(itemCount) * deltaSetSize >
2309 reinterpret_cast<const char*>(aHeader) + aLength) {
2310 return false;
2312 return true;
2315 } // end anonymous namespace
2317 namespace mozilla::gfx {
2319 bool COLRFonts::ValidateColorGlyphs(hb_blob_t* aCOLR, hb_blob_t* aCPAL) {
2320 struct ColorRecord {
2321 uint8_t blue;
2322 uint8_t green;
2323 uint8_t red;
2324 uint8_t alpha;
2327 struct CPALHeaderVersion0 {
2328 uint16 version;
2329 uint16 numPaletteEntries;
2330 uint16 numPalettes;
2331 uint16 numColorRecords;
2332 Offset32 colorRecordsArrayOffset;
2333 // uint16 colorRecordIndices[numPalettes];
2334 const uint16* colorRecordIndices() const {
2335 return reinterpret_cast<const uint16*>(this + 1);
2339 unsigned int cpalLength;
2340 const CPALHeaderVersion0* cpal = reinterpret_cast<const CPALHeaderVersion0*>(
2341 hb_blob_get_data(aCPAL, &cpalLength));
2342 if (!cpal || cpalLength < sizeof(CPALHeaderVersion0)) {
2343 return false;
2346 // We can handle either version 0 or 1.
2347 if (uint16_t(cpal->version) > 1) {
2348 return false;
2351 uint16_t numPaletteEntries = cpal->numPaletteEntries;
2352 uint16_t numPalettes = cpal->numPalettes;
2353 uint16_t numColorRecords = cpal->numColorRecords;
2354 uint32_t colorRecordsArrayOffset = cpal->colorRecordsArrayOffset;
2355 const auto* indices = cpal->colorRecordIndices();
2356 if (colorRecordsArrayOffset >= cpalLength) {
2357 return false;
2359 if (!numPaletteEntries || !numPalettes ||
2360 numColorRecords < numPaletteEntries) {
2361 return false;
2363 if (sizeof(ColorRecord) * numColorRecords >
2364 cpalLength - colorRecordsArrayOffset) {
2365 return false;
2367 if (sizeof(uint16) * numPalettes > cpalLength - sizeof(CPALHeaderVersion0)) {
2368 return false;
2370 for (uint16_t i = 0; i < numPalettes; ++i) {
2371 uint32_t index = indices[i];
2372 if (index + numPaletteEntries > numColorRecords) {
2373 return false;
2377 // The additional fields in CPALv1 are not checked here; the harfbuzz code
2378 // handles reading them safely.
2380 unsigned int colrLength;
2381 const COLRHeader* colr =
2382 reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &colrLength));
2383 if (!colr || colrLength < sizeof(COLRHeader)) {
2384 return false;
2387 if (uint16_t(colr->version) == 1) {
2388 return StaticPrefs::gfx_font_rendering_colr_v1_enabled() &&
2389 colrLength >= sizeof(COLRv1Header) &&
2390 reinterpret_cast<const COLRv1Header*>(colr)->Validate(colrLength);
2393 if (uint16_t(colr->version) != 0) {
2394 // We only support version 1 (above) or version 0 headers.
2395 return false;
2398 return colr->Validate(colrLength);
2401 const COLRFonts::GlyphLayers* COLRFonts::GetGlyphLayers(hb_blob_t* aCOLR,
2402 uint32_t aGlyphId) {
2403 unsigned int length;
2404 const COLRHeader* colr =
2405 reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &length));
2406 // This should never be called unless we have checked that the COLR table is
2407 // structurally valid, so it will be safe to read the header fields.
2408 MOZ_RELEASE_ASSERT(colr && length >= sizeof(COLRHeader), "bad COLR table!");
2409 auto compareBaseGlyph = [](const void* key, const void* data) -> int {
2410 uint32_t glyphId = (uint32_t)(uintptr_t)key;
2411 const auto* baseGlyph = reinterpret_cast<const BaseGlyphRecord*>(data);
2412 uint32_t baseGlyphId = uint16_t(baseGlyph->glyphId);
2413 if (baseGlyphId == glyphId) {
2414 return 0;
2416 return baseGlyphId > glyphId ? -1 : 1;
2418 return reinterpret_cast<const GlyphLayers*>(
2419 bsearch((void*)(uintptr_t)aGlyphId, colr->GetBaseGlyphRecords(),
2420 uint16_t(colr->numBaseGlyphRecords), sizeof(BaseGlyphRecord),
2421 compareBaseGlyph));
2424 bool COLRFonts::PaintGlyphLayers(
2425 hb_blob_t* aCOLR, hb_face_t* aFace, const GlyphLayers* aLayers,
2426 DrawTarget* aDrawTarget, layout::TextDrawTarget* aTextDrawer,
2427 ScaledFont* aScaledFont, DrawOptions aDrawOptions, const Point& aPoint,
2428 const sRGBColor& aCurrentColor, const nsTArray<sRGBColor>* aColors) {
2429 const auto* glyphRecord = reinterpret_cast<const BaseGlyphRecord*>(aLayers);
2430 // Default to opaque rendering (non-webrender applies alpha with a layer)
2431 float alpha = 1.0;
2432 if (aTextDrawer) {
2433 // defaultColor is the one that comes from CSS, so it has transparency info.
2434 bool hasComplexTransparency =
2435 0.0 < aCurrentColor.a && aCurrentColor.a < 1.0;
2436 if (hasComplexTransparency && uint16_t(glyphRecord->numLayers) > 1) {
2437 // WebRender doesn't support drawing multi-layer transparent color-glyphs,
2438 // as it requires compositing all the layers before applying transparency.
2439 // (pretend to succeed, output doesn't matter, we will emit a blob)
2440 aTextDrawer->FoundUnsupportedFeature();
2441 return true;
2444 // If we get here, then either alpha is 0 or 1, or there's only one layer
2445 // which shouldn't have composition issues. In all of these cases, applying
2446 // transparency directly to the glyph should work perfectly fine.
2448 // Note that we must still emit completely transparent emoji, because they
2449 // might be wrapped in a shadow that uses the text run's glyphs.
2450 alpha = aCurrentColor.a;
2453 unsigned int length;
2454 const COLRHeader* colr =
2455 reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &length));
2456 PaintState state{{colr},
2457 aColors->Elements(),
2458 aDrawTarget,
2459 aScaledFont,
2460 nullptr, // variations not needed
2461 aDrawOptions,
2462 length,
2463 aCurrentColor,
2464 0.0, // fontUnitsToPixels not needed
2465 uint16_t(aColors->Length()),
2467 nullptr};
2468 return glyphRecord->Paint(state, alpha, aPoint);
2471 const COLRFonts::GlyphPaintGraph* COLRFonts::GetGlyphPaintGraph(
2472 hb_blob_t* aCOLR, uint32_t aGlyphId) {
2473 if (!StaticPrefs::gfx_font_rendering_colr_v1_enabled()) {
2474 return nullptr;
2476 unsigned int blobLength;
2477 const auto* colr =
2478 reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &blobLength));
2479 MOZ_ASSERT(colr, "Cannot get COLR raw data");
2480 MOZ_ASSERT(blobLength >= sizeof(COLRHeader), "COLR data too small");
2482 uint16_t version = colr->version;
2483 if (version == 1) {
2484 MOZ_ASSERT(blobLength >= sizeof(COLRv1Header), "COLRv1 data too small");
2485 const auto* colrv1 = reinterpret_cast<const COLRv1Header*>(colr);
2486 return reinterpret_cast<const GlyphPaintGraph*>(
2487 colrv1->GetBaseGlyphPaint(aGlyphId));
2490 return nullptr;
2493 bool COLRFonts::PaintGlyphGraph(
2494 hb_blob_t* aCOLR, hb_font_t* aFont, const GlyphPaintGraph* aPaintGraph,
2495 DrawTarget* aDrawTarget, layout::TextDrawTarget* aTextDrawer,
2496 ScaledFont* aScaledFont, DrawOptions aDrawOptions, const Point& aPoint,
2497 const sRGBColor& aCurrentColor, const nsTArray<sRGBColor>* aColors,
2498 uint32_t aGlyphId, float aFontUnitsToPixels) {
2499 if (aTextDrawer) {
2500 // Currently we always punt to a blob for COLRv1 glyphs.
2501 aTextDrawer->FoundUnsupportedFeature();
2502 return true;
2505 unsigned int coordCount;
2506 const int* coords = hb_font_get_var_coords_normalized(aFont, &coordCount);
2508 AutoTArray<uint32_t, 32> visitedOffsets;
2509 PaintState state{{nullptr},
2510 aColors->Elements(),
2511 aDrawTarget,
2512 aScaledFont,
2513 coords,
2514 aDrawOptions,
2515 hb_blob_get_length(aCOLR),
2516 aCurrentColor,
2517 aFontUnitsToPixels,
2518 uint16_t(aColors->Length()),
2519 uint16_t(coordCount),
2520 &visitedOffsets};
2521 state.mHeader.v1 =
2522 reinterpret_cast<const COLRv1Header*>(hb_blob_get_data(aCOLR, nullptr));
2523 AutoRestoreTransform saveTransform(aDrawTarget);
2524 aDrawTarget->ConcatTransform(Matrix::Translation(aPoint));
2525 return PaintColrGlyph::DoPaint(
2526 state, reinterpret_cast<const BaseGlyphPaintRecord*>(aPaintGraph),
2527 aGlyphId, nullptr);
2530 Rect COLRFonts::GetColorGlyphBounds(hb_blob_t* aCOLR, hb_font_t* aFont,
2531 uint32_t aGlyphId, DrawTarget* aDrawTarget,
2532 ScaledFont* aScaledFont,
2533 float aFontUnitsToPixels) {
2534 unsigned int coordCount;
2535 const int* coords = hb_font_get_var_coords_normalized(aFont, &coordCount);
2537 AutoTArray<uint32_t, 32> visitedOffsets;
2538 PaintState state{{nullptr},
2539 nullptr, // palette is not needed
2540 aDrawTarget,
2541 aScaledFont,
2542 coords,
2543 DrawOptions(),
2544 hb_blob_get_length(aCOLR),
2545 sRGBColor(),
2546 aFontUnitsToPixels,
2547 0, // numPaletteEntries
2548 uint16_t(coordCount),
2549 &visitedOffsets};
2550 state.mHeader.v1 =
2551 reinterpret_cast<const COLRv1Header*>(hb_blob_get_data(aCOLR, nullptr));
2552 MOZ_ASSERT(uint16_t(state.mHeader.v1->base.version) == 1);
2553 // If a clip rect is provided, return this as the glyph bounds.
2554 const auto* clipList = state.mHeader.v1->clipList();
2555 if (clipList) {
2556 const auto* clip = clipList->GetClip(aGlyphId);
2557 if (clip) {
2558 return clip->GetRect(state);
2561 // Otherwise, compute bounds by walking the paint graph.
2562 const auto* base = state.mHeader.v1->GetBaseGlyphPaint(aGlyphId);
2563 if (base) {
2564 return DispatchGetBounds(
2565 state, state.mHeader.v1->baseGlyphListOffset + base->paintOffset);
2567 return Rect();
2570 uint16_t COLRFonts::GetColrTableVersion(hb_blob_t* aCOLR) {
2571 unsigned int blobLength;
2572 const auto* colr =
2573 reinterpret_cast<const COLRHeader*>(hb_blob_get_data(aCOLR, &blobLength));
2574 MOZ_ASSERT(colr, "Cannot get COLR raw data");
2575 MOZ_ASSERT(blobLength >= sizeof(COLRHeader), "COLR data too small");
2576 return colr->version;
2579 UniquePtr<nsTArray<sRGBColor>> COLRFonts::SetupColorPalette(
2580 hb_face_t* aFace, const FontPaletteValueSet* aPaletteValueSet,
2581 nsAtom* aFontPalette, const nsACString& aFamilyName) {
2582 // Find the base color palette to use, if there are multiple available;
2583 // default to first in the font, if nothing matches what is requested.
2584 unsigned int paletteIndex = 0;
2585 unsigned int count = hb_ot_color_palette_get_count(aFace);
2586 MOZ_ASSERT(count > 0, "No palettes? Font should have been rejected!");
2588 const FontPaletteValueSet::PaletteValues* fpv = nullptr;
2589 if (aFontPalette && aFontPalette != nsGkAtoms::normal &&
2590 (count > 1 || aPaletteValueSet)) {
2591 auto findPalette = [&](hb_ot_color_palette_flags_t flag) -> unsigned int {
2592 MOZ_ASSERT(flag != HB_OT_COLOR_PALETTE_FLAG_DEFAULT);
2593 for (unsigned int i = 0; i < count; ++i) {
2594 if (hb_ot_color_palette_get_flags(aFace, i) & flag) {
2595 return i;
2598 return 0;
2601 if (aFontPalette == nsGkAtoms::light) {
2602 paletteIndex =
2603 findPalette(HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_LIGHT_BACKGROUND);
2604 } else if (aFontPalette == nsGkAtoms::dark) {
2605 paletteIndex =
2606 findPalette(HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_DARK_BACKGROUND);
2607 } else {
2608 if (aPaletteValueSet) {
2609 if ((fpv = aPaletteValueSet->Lookup(aFontPalette, aFamilyName))) {
2610 if (fpv->mBasePalette >= 0 && fpv->mBasePalette < int32_t(count)) {
2611 paletteIndex = fpv->mBasePalette;
2612 } else if (fpv->mBasePalette ==
2613 FontPaletteValueSet::PaletteValues::kLight) {
2614 paletteIndex = findPalette(
2615 HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_LIGHT_BACKGROUND);
2616 } else if (fpv->mBasePalette ==
2617 FontPaletteValueSet::PaletteValues::kDark) {
2618 paletteIndex = findPalette(
2619 HB_OT_COLOR_PALETTE_FLAG_USABLE_WITH_DARK_BACKGROUND);
2626 // Collect the palette colors and convert them to sRGBColor values.
2627 count =
2628 hb_ot_color_palette_get_colors(aFace, paletteIndex, 0, nullptr, nullptr);
2629 nsTArray<hb_color_t> colors;
2630 colors.SetLength(count);
2631 hb_ot_color_palette_get_colors(aFace, paletteIndex, 0, &count,
2632 colors.Elements());
2634 auto palette = MakeUnique<nsTArray<sRGBColor>>();
2635 palette->SetCapacity(count);
2636 for (const auto c : colors) {
2637 palette->AppendElement(
2638 sRGBColor(hb_color_get_red(c) / 255.0, hb_color_get_green(c) / 255.0,
2639 hb_color_get_blue(c) / 255.0, hb_color_get_alpha(c) / 255.0));
2642 // Apply @font-palette-values overrides, if present.
2643 if (fpv) {
2644 for (const auto overrideColor : fpv->mOverrides) {
2645 if (overrideColor.mIndex < palette->Length()) {
2646 (*palette)[overrideColor.mIndex] = overrideColor.mColor;
2651 return palette;
2654 const FontPaletteValueSet::PaletteValues* FontPaletteValueSet::Lookup(
2655 nsAtom* aName, const nsACString& aFamily) const {
2656 nsAutoCString family(aFamily);
2657 ToLowerCase(family);
2658 if (const HashEntry* entry =
2659 mValues.GetEntry(PaletteHashKey(aName, family))) {
2660 return &entry->mValue;
2662 return nullptr;
2665 FontPaletteValueSet::PaletteValues* FontPaletteValueSet::Insert(
2666 nsAtom* aName, const nsACString& aFamily) {
2667 nsAutoCString family(aFamily);
2668 ToLowerCase(family);
2669 HashEntry* entry = mValues.PutEntry(PaletteHashKey(aName, family));
2670 return &entry->mValue;
2673 } // end namespace mozilla::gfx