/* * Copyright 2011 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file.
*/ #include"src/utils/win/SkDWriteNTDDI_VERSION.h"
/* Note: * In versions 8 and 8.1 of Windows, some calls in DWrite are not thread safe. * The mutex returned from maybe_dw_mutex protects the calls that are * problematic.
*/ static SkSharedMutex* maybe_dw_mutex(DWriteFontTypeface& typeface) { static SkSharedMutex mutex; return typeface.fDWriteFontFace4 ? nullptr : &mutex;
}
class SK_SCOPED_CAPABILITY Exclusive { public: explicit Exclusive(SkSharedMutex* maybe_lock) SK_ACQUIRE(*maybe_lock)
: fLock(maybe_lock) { if (fLock) {
fLock->acquire();
}
}
~Exclusive() SK_RELEASE_CAPABILITY() { if (fLock) {
fLock->release();
}
}
// You would think this should be SK_RELEASE_SHARED_CAPABILITY, but SK_SCOPED_CAPABILITY // doesn't fully understand the difference between shared and exclusive. // Please review https://reviews.llvm.org/D52578 for more information.
~Shared() SK_RELEASE_CAPABILITY() { if (fLock) {
fLock->releaseShared();
}
}
/** A GaspRange is inclusive, [min, max]. */ struct GaspRange { using Behavior = SkOTTableGridAndScanProcedure::GaspRange::behavior;
GaspRange(int min, int max, int version, Behavior flags)
: fMin(min), fMax(max), fVersion(version), fFlags(flags) { } int fMin; int fMax; int fVersion;
Behavior fFlags;
};
bool get_gasp_range(DWriteFontTypeface* typeface, int size, GaspRange* range) {
AutoTDWriteTable<SkOTTableGridAndScanProcedure> gasp(typeface->fDWriteFontFace.get()); if (!gasp.fExists) { returnfalse;
} if (gasp.fSize < sizeof(SkOTTableGridAndScanProcedure)) { returnfalse;
} if (gasp->version != SkOTTableGridAndScanProcedure::version0 &&
gasp->version != SkOTTableGridAndScanProcedure::version1)
{ returnfalse;
}
const SkOTTableEmbeddedBitmapLocation::BitmapSizeTable* sizeTable =
SkTAfter<const SkOTTableEmbeddedBitmapLocation::BitmapSizeTable>(eblc.get()); for (uint32_t i = 0; i < numSizes; ++i, ++sizeTable) { if (sizeTable->ppemX == sizeTable->ppemY &&
range.fMin <= sizeTable->ppemX && sizeTable->ppemX <= range.fMax)
{ // TODO: determine if we should dig through IndexSubTableArray/IndexSubTable // to determine the actual number of glyphs with bitmaps.
// TODO: Ensure that the bitmaps actually cover a significant portion of the strike.
// TODO: Ensure that the bitmaps are bi-level? if (sizeTable->endGlyphIndex >= sizeTable->startGlyphIndex + 3) { returntrue;
}
}
}
}
{
AutoTDWriteTable<SkOTTableEmbeddedBitmapScaling> ebsc(typeface->fDWriteFontFace.get()); if (!ebsc.fExists) { returnfalse;
} if (ebsc.fSize < sizeof(SkOTTableEmbeddedBitmapScaling)) { returnfalse;
} if (ebsc->version != SkOTTableEmbeddedBitmapScaling::version_initial) { returnfalse;
}
const SkOTTableEmbeddedBitmapScaling::BitmapScaleTable* scaleTable =
SkTAfter<const SkOTTableEmbeddedBitmapScaling::BitmapScaleTable>(ebsc.get()); for (uint32_t i = 0; i < numSizes; ++i, ++scaleTable) { if (scaleTable->ppemX == scaleTable->ppemY &&
range.fMin <= scaleTable->ppemX && scaleTable->ppemX <= range.fMax) { // EBSC tables are normally only found in bitmap only fonts. returntrue;
}
}
}
returnfalse;
}
staticbool both_zero(SkScalar a, SkScalar b) { return 0 == a && 0 == b;
}
// returns false if there is any non-90-rotation or skew staticbool is_axis_aligned(const SkScalerContextRec& rec) { return 0 == rec.fPreSkewX &&
(both_zero(rec.fPost2x2[0][1], rec.fPost2x2[1][0]) ||
both_zero(rec.fPost2x2[0][0], rec.fPost2x2[1][1]));
}
// In general, all glyphs should use DWriteFontFace::GetRecommendedRenderingMode // except when bi-level rendering is requested or there are embedded // bi-level bitmaps (and the embedded bitmap flag is set and no rotation). // // DirectWrite's IDWriteFontFace::GetRecommendedRenderingMode does not do // this. As a result, determine the actual size of the text and then see if // there are any embedded bi-level bitmaps of that size. If there are, then // force bitmaps by requesting bi-level rendering. // // FreeType allows for separate ppemX and ppemY, but DirectWrite assumes // square pixels and only uses ppemY. Therefore the transform must track any // non-uniform x-scale. // // Also, rotated glyphs should have the same absolute advance widths as // horizontal glyphs and the subpixel flag should not affect glyph shapes.
// realTextSize is the actual device size we want (as opposed to the size the user requested). // gdiTextSize is the size we request when GDI compatible. // If the scale is negative, this means the matrix will do the flip anyway. const SkScalar realTextSize = scale.fY; // Due to floating point math, the lower bits are suspect. Round carefully.
SkScalar gdiTextSize = SkScalarRoundToScalar(realTextSize * 64.0f) / 64.0f; if (gdiTextSize == 0) {
gdiTextSize = SK_Scalar1;
}
bool bitmapRequested = SkToBool(fRec.fFlags & SkScalerContext::kEmbeddedBitmapText_Flag); bool treatLikeBitmap = false; bool axisAlignedBitmap = false; if (bitmapRequested) { // When embedded bitmaps are requested, treat the entire range like // a bitmap strike if the range is gridfit only and contains a bitmap. int bitmapPPEM = SkScalarTruncToInt(gdiTextSize);
GaspRange range(bitmapPPEM, bitmapPPEM, 0, GaspRange::Behavior()); if (get_gasp_range(typeface, bitmapPPEM, &range)) { if (!is_gridfit_only(range.fFlags)) {
range = GaspRange(bitmapPPEM, bitmapPPEM, 0, GaspRange::Behavior());
}
}
treatLikeBitmap = has_bitmap_strike(typeface, range);
// If the user requested aliased, do so with aliased compatible metrics. if (SkMask::kBW_Format == fRec.fMaskFormat) {
fTextSizeRender = gdiTextSize;
fRenderingMode = DWRITE_RENDERING_MODE_ALIASED;
fTextureType = DWRITE_TEXTURE_ALIASED_1x1;
fTextSizeMeasure = gdiTextSize;
fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC;
// If we can use a bitmap, use gdi classic rendering and measurement. // This will not always provide a bitmap, but matches expected behavior.
} elseif ((treatLikeBitmap && axisAlignedBitmap) || typeface->ForceGDI()) {
fTextSizeRender = gdiTextSize;
fRenderingMode = DWRITE_RENDERING_MODE_GDI_CLASSIC;
fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
fTextSizeMeasure = gdiTextSize;
fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC;
// If rotated but the horizontal text could have used a bitmap, // render high quality rotated glyphs but measure using bitmap metrics.
} elseif (treatLikeBitmap) {
fTextSizeRender = gdiTextSize;
fRenderingMode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC;
fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
fTextSizeMeasure = gdiTextSize;
fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC;
// Force symmetric if the font is above the threshold or there is an explicit mode. // Here we check if the size exceeds 20 before checking the GASP table to match the // results of calling GetRecommendedRenderingMode/Direct2D, which skip looking at // the GASP table if the text is too large.
} elseif (realTextSize > SkIntToScalar(20) ||
typeface->GetRenderingMode() == DWRITE_RENDERING_MODE_NATURAL ||
typeface->GetRenderingMode() == DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC) {
fTextSizeRender = realTextSize;
fRenderingMode = typeface->GetRenderingMode() == DWRITE_RENDERING_MODE_NATURAL ?
DWRITE_RENDERING_MODE_NATURAL : DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC;
fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
fTextSizeMeasure = realTextSize;
fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL; // If the font has a gasp table version 1, use it to determine symmetric rendering.
} elseif (get_gasp_range(typeface, SkScalarRoundToInt(gdiTextSize), &range) &&
range.fVersion >= 1) {
fTextSizeRender = realTextSize;
fRenderingMode = !range.fFlags.field.SymmetricSmoothing ?
DWRITE_RENDERING_MODE_NATURAL : DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC;
fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
fTextSizeMeasure = realTextSize;
fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL; // Fonts with hints, no gasp or gasp version 0, and below 20px get non-symmetric rendering. // Often such fonts have hints which were only tested with GDI ClearType classic. // Some of these fonts rely on drop out control in the y direction in order to be legible. // Tenor Sans // https://fonts.google.com/specimen/Tenor+Sans // Gill Sans W04 // https://cdn.leagueoflegends.com/lolkit/1.1.9/resources/fonts/gill-sans-w04-book.woff // https://na.leagueoflegends.com/en/news/game-updates/patch/patch-410-notes // See https://crbug.com/385897
} else { if (is_hinted(typeface)) {
fTextSizeRender = gdiTextSize;
fRenderingMode = DWRITE_RENDERING_MODE_NATURAL;
} else { // Unhinted but with no gasp and below 20px defaults to symmetric for // GetRecommendedRenderingMode.
fTextSizeRender = realTextSize;
fRenderingMode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC;
}
fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
fTextSizeMeasure = realTextSize;
fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL;
}
// DirectWrite2 allows for grayscale hinting.
fAntiAliasMode = DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE; if (typeface->fFactory2 && typeface->fDWriteFontFace2 &&
SkMask::kA8_Format == fRec.fMaskFormat &&
!(fRec.fFlags & SkScalerContext::kGenA8FromLCD_Flag))
{ // DWRITE_TEXTURE_ALIASED_1x1 is now misnamed, it must also be used with grayscale.
fTextureType = DWRITE_TEXTURE_ALIASED_1x1;
fAntiAliasMode = DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE;
}
// DirectWrite2 allows hinting to be disabled.
fGridFitMode = DWRITE_GRID_FIT_MODE_ENABLED; if (fRec.getHinting() == SkFontHinting::kNone) {
fGridFitMode = DWRITE_GRID_FIT_MODE_DISABLED; if (fRenderingMode != DWRITE_RENDERING_MODE_ALIASED) {
fRenderingMode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC;
}
}
if (this->isLinearMetrics()) {
fTextSizeMeasure = realTextSize;
fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL;
}
// The GDI measuring modes don't seem to work well with CBDT fonts (DWrite.dll 10.0.18362.836). if (fMeasuringMode != DWRITE_MEASURING_MODE_NATURAL) {
constexpr UINT32 CBDTTag = DWRITE_MAKE_OPENTYPE_TAG('C','B','D','T');
AutoDWriteTable CBDT(typeface->fDWriteFontFace.get(), CBDTTag); if (CBDT.fExists) {
fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL;
}
}
}
namespace {
SkColor4f sk_color_from(DWRITE_COLOR_F const& color) { // DWRITE_COLOR_F and SkColor4f are laid out the same and this should be a no-op. return SkColor4f{ color.r, color.g, color.b, color.a };
}
DWRITE_COLOR_F dw_color_from(SkColor4f const& color) { // DWRITE_COLOR_F and SkColor4f are laid out the same and this should be a no-op. // Avoid brace initialization as DWRITE_COLOR_F can be defined as four floats (dxgitype.h, // d3d9types.h) or four unions of two floats (dwrite_2.h, d3dtypes.h). The type changed in // Direct3D 10, but the change does not appear to be documented.
DWRITE_COLOR_F dwColor;
dwColor.r = color.fR;
dwColor.g = color.fG;
dwColor.b = color.fB;
dwColor.a = color.fA; return dwColor;
}
SkRect sk_rect_from(D2D_RECT_F const& rect) { // D2D_RECT_F and SkRect are both y-down and laid the same so this should be a no-op. return SkRect{ rect.left, rect.top, rect.right, rect.bottom };
}
constexpr bool D2D_RECT_F_is_empty(const D2D_RECT_F& r) { return r.right <= r.left || r.bottom <= r.top;
}
SkMatrix sk_matrix_from(DWRITE_MATRIX const& m) { // DWRITE_MATRIX and SkMatrix are y-down. However DWRITE_MATRIX is affine only. return SkMatrix::MakeAll(
m.m11, m.m21, m.dx,
m.m12, m.m22, m.dy,
0, 0, 1);
}
SkTileMode sk_tile_mode_from(D2D1_EXTEND_MODE extendMode) { switch (extendMode) { case D2D1_EXTEND_MODE_CLAMP: return SkTileMode::kClamp; case D2D1_EXTEND_MODE_WRAP: return SkTileMode::kRepeat; case D2D1_EXTEND_MODE_MIRROR: return SkTileMode::kMirror; default: return SkTileMode::kClamp;
}
}
SkBlendMode sk_blend_mode_from(DWRITE_COLOR_COMPOSITE_MODE compositeMode) { switch (compositeMode) { case DWRITE_COLOR_COMPOSITE_CLEAR: return SkBlendMode::kClear; case DWRITE_COLOR_COMPOSITE_SRC: return SkBlendMode::kSrc; case DWRITE_COLOR_COMPOSITE_DEST: return SkBlendMode::kDst; case DWRITE_COLOR_COMPOSITE_SRC_OVER: return SkBlendMode::kSrcOver; case DWRITE_COLOR_COMPOSITE_DEST_OVER: return SkBlendMode::kDstOver; case DWRITE_COLOR_COMPOSITE_SRC_IN: return SkBlendMode::kSrcIn; case DWRITE_COLOR_COMPOSITE_DEST_IN: return SkBlendMode::kDstIn; case DWRITE_COLOR_COMPOSITE_SRC_OUT: return SkBlendMode::kSrcOut; case DWRITE_COLOR_COMPOSITE_DEST_OUT: return SkBlendMode::kDstOut; case DWRITE_COLOR_COMPOSITE_SRC_ATOP: return SkBlendMode::kSrcATop; case DWRITE_COLOR_COMPOSITE_DEST_ATOP: return SkBlendMode::kDstATop; case DWRITE_COLOR_COMPOSITE_XOR: return SkBlendMode::kXor; case DWRITE_COLOR_COMPOSITE_PLUS: return SkBlendMode::kPlus;
case DWRITE_COLOR_COMPOSITE_SCREEN: return SkBlendMode::kScreen; case DWRITE_COLOR_COMPOSITE_OVERLAY: return SkBlendMode::kOverlay; case DWRITE_COLOR_COMPOSITE_DARKEN: return SkBlendMode::kDarken; case DWRITE_COLOR_COMPOSITE_LIGHTEN: return SkBlendMode::kLighten; case DWRITE_COLOR_COMPOSITE_COLOR_DODGE: return SkBlendMode::kColorDodge; case DWRITE_COLOR_COMPOSITE_COLOR_BURN: return SkBlendMode::kColorBurn; case DWRITE_COLOR_COMPOSITE_HARD_LIGHT: return SkBlendMode::kHardLight; case DWRITE_COLOR_COMPOSITE_SOFT_LIGHT: return SkBlendMode::kSoftLight; case DWRITE_COLOR_COMPOSITE_DIFFERENCE: return SkBlendMode::kDifference; case DWRITE_COLOR_COMPOSITE_EXCLUSION: return SkBlendMode::kExclusion; case DWRITE_COLOR_COMPOSITE_MULTIPLY: return SkBlendMode::kMultiply;
case DWRITE_COLOR_COMPOSITE_HSL_HUE: return SkBlendMode::kHue; case DWRITE_COLOR_COMPOSITE_HSL_SATURATION: return SkBlendMode::kSaturation; case DWRITE_COLOR_COMPOSITE_HSL_COLOR: return SkBlendMode::kColor; case DWRITE_COLOR_COMPOSITE_HSL_LUMINOSITY: return SkBlendMode::kLuminosity; default: return SkBlendMode::kDst;
}
}
inline SkPoint SkVectorProjection(SkPoint a, SkPoint b) {
SkScalar length = b.length(); if (!length) { return SkPoint();
}
SkPoint bNormalized = b;
bNormalized.normalize();
bNormalized.scale(SkPoint::DotProduct(a, b) / length); return bNormalized;
}
// This linear interpolation is used for calculating a truncated color line in special edge cases. // This interpolation needs to be kept in sync with what the gradient shader would normally do when // truncating and drawing color lines. When drawing into N32 surfaces, this is expected to be true. // If that changes, or if we support other color spaces in CPAL tables at some point, this needs to // be looked at.
D2D1_COLOR_F lerpSkColor(D2D1_COLOR_F c0, D2D1_COLOR_F c1, float t) { // Due to the floating point calculation in the caller, when interpolating between very narrow // stops, we may get values outside the interpolation range, guard against these. if (t < 0) { return c0;
} if (t > 1) { return c1;
} constauto c0_4f = skvx::float4(c0.r, c0.g, c0.b, c0.a),
c1_4f = skvx::float4(c1.r, c1.g, c1.b, c1.a),
c_4f = c0_4f + (c1_4f - c0_4f) * t;
D2D1_COLOR_F r;
c_4f.store(&r); return r;
}
enum TruncateStops {
TruncateStart,
TruncateEnd,
}; // Truncate a vector of color stops at a previously computed stop position and insert at that // position the color interpolated between the surrounding stops. void truncateToStopInterpolating(SkScalar zeroRadiusStop,
std::vector<D2D1_GRADIENT_STOP>& stops,
TruncateStops truncateStops) { if (stops.size() <= 1u ||
zeroRadiusStop < stops.front().position || stops.back().position < zeroRadiusStop) { return;
}
bool SkScalerContext_DW::drawColorV1Paint(SkCanvas& canvas,
IDWritePaintReader& reader,
DWRITE_PAINT_ELEMENT const & element)
{ // Helper to draw the specified number of children. auto drawChildren = [&](uint32_t childCount) -> bool { if (childCount != 0) {
DWRITE_PAINT_ELEMENT childElement;
HRB(reader.MoveToFirstChild(&childElement));
this->drawColorV1Paint(canvas, reader, childElement);
for (uint32_t i = 1; i < childCount; i++) {
HRB(reader.MoveToNextSibling(&childElement));
this->drawColorV1Paint(canvas, reader, childElement);
}
HRB(reader.MoveToParent());
} returntrue;
};
SkAutoCanvasRestore restoreCanvas(&canvas, true); switch (element.paintType) { case DWRITE_PAINT_TYPE_NONE: returntrue;
case DWRITE_PAINT_TYPE_LAYERS: { // A layers paint element has a variable number of children. return drawChildren(element.paint.layers.childCount);
}
case DWRITE_PAINT_TYPE_SOLID_GLYPH: { // A solid glyph paint element has no children. // glyphIndex, color.value, color.paletteEntryIndex, color.alpha, color.colorAttributes autoconst& solidGlyph = element.paint.solidGlyph;
case DWRITE_PAINT_TYPE_SOLID: { // A solid paint element has no children. // value, paletteEntryIndex, alphaMultiplier, colorAttributes
SkPaint skPaint;
skPaint.setColor4f(sk_color_from(element.paint.solid.value));
canvas.drawPaint(skPaint); returntrue;
}
case DWRITE_PAINT_TYPE_LINEAR_GRADIENT: { autoconst& linearGradient = element.paint.linearGradient; // A linear gradient paint element has no children. // x0, y0, x1, y1, x2, y2, extendMode, gradientStopCount, [colorStops]
if (linearGradient.gradientStopCount == 0) { returntrue;
}
std::vector<D2D1_GRADIENT_STOP> stops;
stops.resize(linearGradient.gradientStopCount);
// If success stops will be ordered.
HRBM(reader.GetGradientStops(0, stops.size(), stops.data()), "Could not get linear gradient stops.");
SkPaint skPaint; if (stops.size() == 1) {
skPaint.setColor4f(sk_color_from(stops[0].color));
canvas.drawPaint(skPaint); returntrue;
}
SkPoint linePositions[2] = { {linearGradient.x0, linearGradient.y0},
{linearGradient.x1, linearGradient.y1} };
SkPoint p0 = linePositions[0];
SkPoint p1 = linePositions[1];
SkPoint p2 = SkPoint::Make(linearGradient.x2, linearGradient.y2);
// If p0p1 or p0p2 are degenerate probably nothing should be drawn. // If p0p1 and p0p2 are parallel then one side is the first color and the other side is // the last color, depending on the direction. // For now, just use the first color. if (p1 == p0 || p2 == p0 || !SkPoint::CrossProduct(p1 - p0, p2 - p0)) {
skPaint.setColor4f(sk_color_from(stops[0].color));
canvas.drawPaint(skPaint); returntrue;
}
// Follow implementation note in nanoemoji: // https://github.com/googlefonts/nanoemoji/blob/0ac6e7bb4d8202db692574d8530a9b643f1b3b3c/src/nanoemoji/svg.py#L188 // to compute a new gradient end point P3 as the orthogonal // projection of the vector from p0 to p1 onto a line perpendicular // to line p0p2 and passing through p0.
SkVector perpendicularToP2P0 = (p2 - p0);
perpendicularToP2P0 = SkPoint::Make( perpendicularToP2P0.y(),
-perpendicularToP2P0.x());
SkVector p3 = p0 + SkVectorProjection((p1 - p0), perpendicularToP2P0);
linePositions[1] = p3;
// Project/scale points according to stop extrema along p0p3 line, // p3 being the result of the projection above, then scale stops to // to [0, 1] range so that repeat modes work. The Skia linear // gradient shader performs the repeat modes over the 0 to 1 range, // that's why we need to scale the stops to within that range.
SkTileMode tileMode = sk_tile_mode_from(SkTo<D2D1_EXTEND_MODE>(linearGradient.extendMode));
SkScalar colorStopRange = stops.back().position - stops.front().position; // If the color stops are all at the same offset position, repeat and reflect modes // become meaningless. if (colorStopRange == 0.f) { if (tileMode != SkTileMode::kClamp) { //skPaint.setColor(SK_ColorTRANSPARENT); returntrue;
} else { // Insert duplicated fake color stop in pad case at +1.0f to enable the projection // of circles for an originally 0-length color stop range. Adding this stop will // paint the equivalent gradient, because: All font specified color stops are in the // same spot, mode is pad, so everything before this spot is painted with the first // color, everything after this spot is painted with the last color. Not adding this // stop will skip the projection and result in specifying non-normalized color stops // to the shader.
stops.push_back({ stops.back().position + 1.0f, stops.back().color });
colorStopRange = 1.0f;
}
}
SkASSERT(colorStopRange != 0.f);
// If the colorStopRange is 0 at this point, the default behavior of the shader is to // clamp to 1 color stops that are above 1, clamp to 0 for color stops that are below 0, // and repeat the outer color stops at 0 and 1 if the color stops are inside the // range. That will result in the correct rendering. if ((colorStopRange != 1 || stops.front().position != 0.f)) {
SkVector p0p3 = p3 - p0;
SkVector p0Offset = p0p3;
p0Offset.scale(stops.front().position);
SkVector p1Offset = p0p3;
p1Offset.scale(stops.back().position);
SkASSERT(shader); // An opaque color is needed to ensure the gradient is not modulated by alpha.
skPaint.setColor(SK_ColorBLACK);
skPaint.setShader(shader);
canvas.drawPaint(skPaint); returntrue;
}
case DWRITE_PAINT_TYPE_RADIAL_GRADIENT: { autoconst& radialGradient = element.paint.radialGradient; // A radial gradient paint element has no children. // x0, y0, radius0, x1, y1, radius1, extendMode, gradientStopCount, [colorsStops]
if (radialGradient.gradientStopCount == 0) { returntrue;
}
std::vector<D2D1_GRADIENT_STOP> stops;
stops.resize(radialGradient.gradientStopCount);
// If success stops will be ordered.
HRBM(reader.GetGradientStops(0, stops.size(), stops.data()), "Could not get radial gradient stops.");
SkPaint skPaint; if (stops.size() == 1) {
skPaint.setColor4f(sk_color_from(stops[0].color));
canvas.drawPaint(skPaint); returntrue;
}
if (colorStopRange == 0.f) { if (tileMode != SkTileMode::kClamp) { //skPaint.setColor(SK_ColorTRANSPARENT); returntrue;
} else { // Insert duplicated fake color stop in pad case at +1.0f to enable the projection // of circles for an originally 0-length color stop range. Adding this stop will // paint the equivalent gradient, because: All font specified color stops are in the // same spot, mode is pad, so everything before this spot is painted with the first // color, everything after this spot is painted with the last color. Not adding this // stop will skip the projection and result in specifying non-normalized color stops // to the shader.
stops.push_back({ stops.back().position + 1.0f, stops.back().color });
colorStopRange = 1.0f;
}
}
SkASSERT(colorStopRange != 0.f);
// If the colorStopRange is 0 at this point, the default behavior of the shader is to // clamp to 1 color stops that are above 1, clamp to 0 for color stops that are below 0, // and repeat the outer color stops at 0 and 1 if the color stops are inside the // range. That will result in the correct rendering. if (colorStopRange != 1 || stops.front().position != 0.f) { // For the Skia two-point caonical shader to understand the // COLRv1 color stops we need to scale stops to 0 to 1 range and // interpolate new centers and radii. Otherwise the shader // clamps stops outside the range to 0 and 1 (larger interval) // or repeats the outer stops at 0 and 1 if the (smaller // interval).
SkVector startToEnd = end - start;
SkScalar radiusDiff = endRadius - startRadius;
SkScalar scaleFactor = 1 / colorStopRange;
SkScalar stopsStartOffset = stops.front().position;
// The order of the following computations is important in order to avoid // overwriting start or startRadius before the second reassignment.
end = start + endOffset;
start = start + startOffset;
endRadius = startRadius + radiusDiff * stops.back().position;
startRadius = startRadius + radiusDiff * stops.front().position;
// Compute color stop position where radius is = 0. After the scaling // of stop positions to the normal 0,1 range that we have done above, // the size of the radius as a function of the color stops is: r(x) = r0 // + x*(r1-r0) Solving this function for r(x) = 0, we get: x = -r0 / // (r1-r0)
zeroRadiusStop = -startRadius / (endRadius - startRadius);
startRadius = 0.f;
SkVector startEndDiff = end - start;
startEndDiff.scale(zeroRadiusStop);
start = start + startEndDiff;
}
if (endRadius < 0) {
truncateSide = TruncateEnd;
zeroRadiusStop = -startRadius / (endRadius - startRadius);
endRadius = 0.f;
SkVector startEndDiff = end - start;
startEndDiff.scale(1 - zeroRadiusStop);
end = end - startEndDiff;
}
if (!(startRadius == 0 && endRadius == 0)) {
truncateToStopInterpolating(zeroRadiusStop, stops, truncateSide);
} else { // If both radii have become negative and where clamped to 0, we need to // produce a single color cone, otherwise the shader colors the whole // plane in a single color when two radii are specified as 0. if (radiusDiff > 0) {
end = start + startToEnd;
endRadius = radiusDiff;
stops.erase(stops.begin(), stops.end() - 1);
} else {
start -= startToEnd;
startRadius = -radiusDiff;
stops.erase(stops.begin() + 1, stops.end());
}
}
} else { if (startRadius < 0 || endRadius < 0) { auto roundIntegerMultiple = [](SkScalar factorZeroCrossing,
SkTileMode tileMode) { int roundedMultiple = factorZeroCrossing > 0
? ceilf(factorZeroCrossing)
: floorf(factorZeroCrossing) - 1; if (tileMode == SkTileMode::kMirror && roundedMultiple % 2 != 0) {
roundedMultiple += roundedMultiple < 0 ? -1 : 1;
} return roundedMultiple;
};
std::unique_ptr<SkColor4f[]> skColors(new SkColor4f[stops.size()]);
std::unique_ptr<SkScalar[]> skStops(new SkScalar[stops.size()]); for (size_t i = 0; i < stops.size(); ++i) {
skColors[i] = sk_color_from(stops[i].color);
skStops[i] = stops[i].position;
}
// An opaque color is needed to ensure the gradient is not modulated by alpha.
skPaint.setColor(SK_ColorBLACK);
skPaint.setShader(SkGradientShader::MakeTwoPointConical(
start, startRadius, end, endRadius,
skColors.get(), SkColorSpace::MakeSRGB(), skStops.get(), stops.size(),
tileMode,
SkGradientShader::Interpolation{
SkGradientShader::Interpolation::InPremul::kNo,
SkGradientShader::Interpolation::ColorSpace::kSRGB,
SkGradientShader::Interpolation::HueMethod::kShorter
},
nullptr));
canvas.drawPaint(skPaint); returntrue;
}
case DWRITE_PAINT_TYPE_SWEEP_GRADIENT: { autoconst& sweepGradient = element.paint.sweepGradient; // A sweep gradient paint element has no children. // centerX, centerY, startAngle, endAngle, extendMode, gradientStopCount, [colorStops]
if (sweepGradient.gradientStopCount == 0) { returntrue;
}
std::vector<D2D1_GRADIENT_STOP> stops;
stops.resize(sweepGradient.gradientStopCount);
// If success stops will be ordered.
HRBM(reader.GetGradientStops(0, stops.size(), stops.data()), "Could not get sweep gradient stops");
SkPaint skPaint; if (stops.size() == 1) {
skPaint.setColor4f(sk_color_from(stops[0].color));
canvas.drawPaint(skPaint); returntrue;
}
SkPoint center = SkPoint::Make(sweepGradient.centerX, sweepGradient.centerY);
SkScalar startAngle = sweepGradient.startAngle;
SkScalar endAngle = sweepGradient.endAngle; // OpenType 1.9.1 adds a shift to the angle to ease specification of a 0 to 360 // degree sweep. This appears to already be applied by DW. //startAngle += 180.0f; //endAngle += 180.0f;
// An opaque color is needed to ensure the gradient is not modulated by alpha.
skPaint.setColor(SK_ColorBLACK);
// New (Var)SweepGradient implementation compliant with OpenType 1.9.1 from here.
// The shader expects stops from 0 to 1, so we need to account for // minimum and maximum stop positions being different from 0 and // 1. We do that by scaling minimum and maximum stop positions to // the 0 to 1 interval and scaling the angles inverse proportionally.
// 1) Scale angles to their equivalent positions if stops were from 0 to 1.
SkScalar sectorAngle = endAngle - startAngle;
SkTileMode tileMode = sk_tile_mode_from(SkTo<D2D1_EXTEND_MODE>(sweepGradient.extendMode)); if (sectorAngle == 0 && tileMode != SkTileMode::kClamp) { // "If the ColorLine's extend mode is reflect or repeat and start and end angle // are equal, nothing is drawn.". //skPaint.setColor(SK_ColorTRANSPARENT); returntrue;
}
float colorStopRange = stops.back().position - stops.front().position; if (colorStopRange == 0.f) { if (tileMode != SkTileMode::kClamp) { //skPaint.setColor(SK_ColorTRANSPARENT); returntrue;
} else { // Insert duplicated fake color stop in pad case at +1.0f to feed the shader correct // values and enable painting a pad sweep gradient with two colors. Adding this stop // will paint the equivalent gradient, because: All font specified color stops are // in the same spot, mode is pad, so everything before this spot is painted with the // first color, everything after this spot is painted with the last color. Not // adding this stop will skip the projection and result in specifying non-normalized // color stops to the shader.
stops.push_back({ stops.back().position + 1.0f, stops.back().color });
colorStopRange = 1.0f;
}
}
/* https://docs.microsoft.com/en-us/typography/opentype/spec/colr#sweep-gradients * "The angles are expressed in counter-clockwise degrees from * the direction of the positive x-axis on the design * grid. [...] The color line progresses from the start angle * to the end angle in the counter-clockwise direction;" - * Convert angles and stops from counter-clockwise to clockwise * for the shader if the gradient is not already reversed due to
* start angle being larger than end angle. */
startAngleScaled = 360.f - startAngleScaled;
endAngleScaled = 360.f - endAngleScaled; if (startAngleScaled >= endAngleScaled) {
std::swap(startAngleScaled, endAngleScaled);
std::reverse(stops.begin(), stops.end()); for (auto& stop : stops) {
stop.position = 1.0f - stop.position;
}
}
std::unique_ptr<SkColor4f[]> skColors(new SkColor4f[stops.size()]);
std::unique_ptr<SkScalar[]> skStops(new SkScalar[stops.size()]); for (size_t i = 0; i < stops.size(); ++i) {
skColors[i] = sk_color_from(stops[i].color);
skStops[i] = stops[i].position;
}
case DWRITE_PAINT_TYPE_GLYPH: { // A glyph paint element has one child, which is the fill for the glyph shape glyphIndex.
SkPath path;
SkTScopedComPtr<IDWriteGeometrySink> geometryToPath;
HRBM(SkDWriteGeometrySink::Create(&path, &geometryToPath), "Could not create geometry to path converter.");
UINT16 glyphId = SkTo<UINT16>(element.paint.glyph.glyphIndex);
{
Exclusive l(maybe_dw_mutex(*this->getDWriteTypeface()));
HRBM(this->getDWriteTypeface()->fDWriteFontFace->GetGlyphRunOutline(
SkScalarToFloat(fTextSizeRender),
&glyphId,
nullptr, //advances
nullptr, //offsets
1, //num glyphs FALSE, //sideways FALSE, //rtl
geometryToPath.get()), "Could not create glyph outline.");
}
case DWRITE_PAINT_TYPE_COLOR_GLYPH: { autoconst& colorGlyph = element.paint.colorGlyph; // A color glyph paint element has one child, the root of the paint tree for glyphIndex. // glyphIndex, clipBox if (D2D_RECT_F_is_empty(colorGlyph.clipBox)) { // Does not have a clip box
} else {
SkRect r = sk_rect_from(colorGlyph.clipBox);
canvas.clipRect(r, fRenderingMode != DWRITE_RENDERING_MODE_ALIASED);
}
drawChildren(1); returntrue;
}
case DWRITE_PAINT_TYPE_TRANSFORM: { // A transform paint element always has one child, the transformed content.
canvas.concat(sk_matrix_from(element.paint.transform));
drawChildren(1); returntrue;
}
case DWRITE_PAINT_TYPE_COMPOSITE: { // A composite paint element has two children, the source and destination of the operation.
// Need to visit the second child first and do savelayers, so manually handle children.
DWRITE_PAINT_ELEMENT sourceElement;
DWRITE_PAINT_ELEMENT backdropElement;
HRBM(reader.MoveToFirstChild(&sourceElement), "Could not move to child.");
HRBM(reader.MoveToNextSibling(&backdropElement), "Could not move to sibiling.");
canvas.saveLayer(nullptr, nullptr);
this->drawColorV1Paint(canvas, reader, backdropElement);
HRBM(reader.MoveToParent(), "Could not move to parent.");
HRBM(reader.MoveToFirstChild(&sourceElement), "Could not move to child.");
canvas.saveLayer(nullptr, &blendModePaint);
this->drawColorV1Paint(canvas, reader, sourceElement);
HRBM(reader.MoveToParent(), "Could not move to parent.");
SkTScopedComPtr<IDWritePaintReader> paintReader;
HRBM(fontFace->CreatePaintReader(DWRITE_GLYPH_IMAGE_FORMATS_COLR_PAINT_TREE,
DWRITE_PAINT_FEATURE_LEVEL_COLR_V1,
&paintReader), "Could not create paint reader.");
DWRITE_PAINT_ELEMENT paintElement;
D2D_RECT_F clipBox;
DWRITE_PAINT_ATTRIBUTES attributes;
HRBM(paintReader->SetCurrentGlyph(glyphIndex, &paintElement, &clipBox, &attributes), "Could not set current glyph.");
if (paintElement.paintType == DWRITE_PAINT_TYPE_NONE) { // Does not have paint layers, try another format. returnfalse;
}
// All coordinates (including top level clip) are reported in "em"s (1 == em). // Size up all em units to the current size and transform. // Get glyph paths at render size, divide out the render size to get em units.
if (D2D_RECT_F_is_empty(clipBox)) { // Does not have a clip box
} else {
canvas.clipRect(sk_rect_from(clipBox));
}
// The DirectWrite interface returns resolved colors if these are provided. // Indexes and alphas are reported but there is no reason to duplicate the color calculation.
paintReader->SetTextColor(dw_color_from(SkColor4f::FromColor(fRec.fForegroundColor)));
paintReader->SetCustomColorPalette(typeface->fDWPalette.get(), typeface->fPaletteEntryCount);
SkBitmap dstBitmap; // TODO: mark this as sRGB when the blits will be sRGB.
dstBitmap.setInfo(SkImageInfo::Make(glyph.width(), glyph.height(),
kN32_SkColorType, kPremul_SkAlphaType),
glyph.rowBytes());
dstBitmap.setPixels(imageBuffer);
switch (element.paintType) { case DWRITE_PAINT_TYPE_NONE: returnfalse;
case DWRITE_PAINT_TYPE_LAYERS: { // A layers paint element has a variable number of children. return boundChildren(element.paint.layers.childCount);
}
case DWRITE_PAINT_TYPE_SOLID_GLYPH: { // A solid glyph paint element has no children. // glyphIndex, color.value, color.paletteEntryIndex, color.alpha, color.colorAttributes
case DWRITE_PAINT_TYPE_LINEAR_GRADIENT: { returntrue;
}
case DWRITE_PAINT_TYPE_RADIAL_GRADIENT: { returntrue;
}
case DWRITE_PAINT_TYPE_SWEEP_GRADIENT: { returntrue;
}
case DWRITE_PAINT_TYPE_GLYPH: { // A glyph paint element has one child, which is the fill for the glyph shape glyphIndex.
SkPath path;
SkTScopedComPtr<IDWriteGeometrySink> geometryToPath;
HRBM(SkDWriteGeometrySink::Create(&path, &geometryToPath), "Could not create geometry to path converter.");
UINT16 glyphId = SkTo<UINT16>(element.paint.glyph.glyphIndex);
{
Exclusive l(maybe_dw_mutex(*this->getDWriteTypeface()));
HRBM(this->getDWriteTypeface()->fDWriteFontFace->GetGlyphRunOutline(
SkScalarToFloat(fTextSizeRender),
&glyphId,
nullptr, //advances
nullptr, //offsets
1, //num glyphs FALSE, //sideways FALSE, //rtl
geometryToPath.get()), "Could not create glyph outline.");
}
case DWRITE_PAINT_TYPE_COLOR_GLYPH: { // A color glyph paint element has one child, which is the root // of the paint tree for the glyph specified by glyphIndex. autoconst& colorGlyph = element.paint.colorGlyph; if (D2D_RECT_F_is_empty(colorGlyph.clipBox)) { // Does not have a clip box return boundChildren(1);
}
SkRect r = sk_rect_from(colorGlyph.clipBox);
ctm->mapRect(r);
bounds->join(r); returntrue;
}
case DWRITE_PAINT_TYPE_TRANSFORM: { // A transform paint element always has one child, which is the transformed content.
ctm->preConcat(sk_matrix_from(element.paint.transform)); return boundChildren(1);
}
case DWRITE_PAINT_TYPE_COMPOSITE: { // A composite paint element has two children, the source and destination of the operation. return boundChildren(2);
}
SkTScopedComPtr<IDWritePaintReader> paintReader;
HRESULT hr; // No message on failure here, since this will fail if the font has no color glyphs.
hr = fontFace->CreatePaintReader(DWRITE_GLYPH_IMAGE_FORMATS_COLR_PAINT_TREE,
DWRITE_PAINT_FEATURE_LEVEL_COLR_V1,
&paintReader); if (FAILED(hr)) { returnfalse;
}
DWRITE_PAINT_ELEMENT paintElement;
D2D_RECT_F clipBox;
DWRITE_PAINT_ATTRIBUTES attributes; // If the glyph is not color this will succeed but return paintType NONE.
HRBM(paintReader->SetCurrentGlyph(glyphIndex, &paintElement, &clipBox, &attributes), "Could not set the current glyph.");
if (paintElement.paintType == DWRITE_PAINT_TYPE_NONE) { // Does not have paint layers, try another format. returnfalse;
}
// All coordinates (including top level clip) are reported in "em"s (1 == em). // Size up all em units to the current size and transform.
// DirectWrite treats all out of bounds glyph ids as having the same data as glyph 0. // For consistency with all other backends, treat out of range glyph ids as an error. if (fGlyphCount <= glyphId) { returnfalse;
}
DWRITE_GLYPH_METRICS gm;
if (DWRITE_MEASURING_MODE_GDI_CLASSIC == fMeasuringMode ||
DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode)
{
Exclusive l(maybe_dw_mutex(*typeface));
HRBM(typeface->fDWriteFontFace->GetGdiCompatibleGlyphMetrics(
fTextSizeMeasure,
1.0f, // pixelsPerDip // This parameter does not act like the lpmat2 parameter to GetGlyphOutlineW. // If it did then GsA here and G_inv below to mapVectors.
nullptr,
DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode,
&glyphId, 1,
&gm), "Could not get gdi compatible glyph metrics.");
} else {
Exclusive l(maybe_dw_mutex(*typeface));
HRBM(typeface->fDWriteFontFace->GetDesignGlyphMetrics(&glyphId, 1, &gm), "Could not get design metrics.");
}
*advance = { advanceX, 0 }; if (DWRITE_MEASURING_MODE_GDI_CLASSIC == fMeasuringMode ||
DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode)
{ // DirectWrite produced 'compatible' metrics, but while close, // the end result is not always an integer as it would be with GDI.
advance->fX = SkScalarRoundToScalar(advance->fX);
}
fSkXform.mapVectors(advance, 1); returntrue;
}
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 und die Messung sind noch experimentell.