/* -*- 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/. */
namespace { // anonymous namespace for implementation internals
#pragmapack(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;
// 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();
}
// 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;
} constauto* 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;
} constauto* stop = colorStops(); if (reinterpret_cast<constchar*>(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));
}
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(); })
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);
} elseif (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));
}
};
// 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));
};
// 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;
} elseif (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));
}
};
bool Paint(const PaintState& aState, uint32_t aOffset, const Rect* aBounds) const {
MOZ_ASSERT(format == kFormat); if (!paintOffset) { returntrue;
}
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 returntrue;
}
RefPtr<Path> path = GetPathForGlyphs(aState, buffer);
aState.mDrawTarget->PushClip(path); bool ok = DispatchPaint(aState, aOffset + paintOffset, aBounds);
aState.mDrawTarget->PopClip(); return ok;
}
// Factored out as a helper because this is also used by the top-level // PaintGlyphGraph function. staticbool DoPaint(const PaintState& aState, const BaseGlyphPaintRecord* aBaseGlyphPaint,
uint32_t aGlyphId, const Rect* aBounds) {
AutoPopClips clips(aState.mDrawTarget);
Rect clipRect; if (constauto* clipList = aState.mHeader.v1->clipList()) { if (constauto* 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 (!backdropPaintOffset || !sourcePaintOffset) { returntrue;
} 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) { returntrue;
} 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()) { returntrue;
}
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.) returntrue;
}
// Draw the backdrop paint graph to a temporary surface.
RefPtr backdrop = aState.mDrawTarget->CreateSimilarDrawTarget(
intSize, SurfaceFormat::B8G8R8A8); if (!backdrop) { returntrue;
}
backdrop->SetTransform(Matrix::Translation(-bounds.TopLeft()));
state.mDrawTarget = backdrop; if (!DispatchPaint(state, aOffset + backdropPaintOffset, &bounds)) { returnfalse;
}
// Draw the source paint graph to another temp surface.
RefPtr source = aState.mDrawTarget->CreateSimilarDrawTarget(
intSize, SurfaceFormat::B8G8R8A8); if (!source) { returntrue;
}
source->SetTransform(Matrix::Translation(-bounds.TopLeft()));
state.mDrawTarget = source; if (!DispatchPaint(state, aOffset + sourcePaintOffset, &bounds)) { returnfalse;
}
// 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);
returntrue;
}
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));
}
};
#pragmapack()
const BaseGlyphPaintRecord* COLRv1Header::GetBaseGlyphPaint(
uint32_t aGlyphId) const { constauto* list = baseGlyphList(); if (!list) { return nullptr;
}
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.75 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.