blob: a735bc5ac139f45687f59b7e6e273a95a3a3aa44 [file] [log] [blame]
/*
* Copyright 2006-2012 The Android Open Source Project
* Copyright 2012 Mozilla Foundation
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkDrawable.h"
#include "include/core/SkGraphics.h"
#include "include/core/SkOpenTypeSVGDecoder.h"
#include "include/core/SkPath.h"
#include "include/effects/SkGradientShader.h"
#include "include/pathops/SkPathOps.h"
#include "include/private/SkColorData.h"
#include "include/private/SkTo.h"
#include "src/core/SkFDot6.h"
#include "src/ports/SkFontHost_FreeType_common.h"
#include <algorithm>
#include <utility>
#include <ft2build.h>
#include <freetype/freetype.h>
#include <freetype/ftbitmap.h>
#ifdef FT_COLOR_H
# include <freetype/ftcolor.h>
#endif
#include <freetype/ftimage.h>
#include <freetype/ftoutln.h>
#include <freetype/ftsizes.h>
// In the past, FT_GlyphSlot_Own_Bitmap was defined in this header file.
#include <freetype/ftsynth.h>
namespace {
[[maybe_unused]] static inline const constexpr bool kSkShowTextBlitCoverage = false;
}
#if defined(FT_CONFIG_OPTION_SVG)
# include <freetype/otsvg.h>
#endif
#ifdef TT_SUPPORT_COLRV1
// FT_ClipBox and FT_Get_Color_Glyph_ClipBox introduced VER-2-11-0-18-g47cf8ebf4
// FT_COLR_COMPOSITE_PLUS and renumbering introduced VER-2-11-0-21-ge40ae7569
// FT_SIZEOF_LONG_LONG introduced VER-2-11-0-31-gffdac8d67
// FT_PaintRadialGradient changed size and layout at VER-2-11-0-147-gd3d3ff76d
// FT_STATIC_CAST introduced VER-2-11-0-172-g9079c5d91
// So undefine TT_SUPPORT_COLRV1 before 2.11.1 but not if FT_STATIC_CAST is defined.
#if (((FREETYPE_MAJOR) < 2) || \
((FREETYPE_MAJOR) == 2 && (FREETYPE_MINOR) < 11) || \
((FREETYPE_MAJOR) == 2 && (FREETYPE_MINOR) == 11 && (FREETYPE_PATCH) < 1)) && \
!defined(FT_STATIC_CAST)
# undef TT_SUPPORT_COLRV1
#else
# include "src/core/SkScopeExit.h"
#endif
#endif
// FT_OUTLINE_OVERLAP was added in FreeType 2.10.3
#ifndef FT_OUTLINE_OVERLAP
# define FT_OUTLINE_OVERLAP 0x40
#endif
// FT_LOAD_COLOR and the corresponding FT_Pixel_Mode::FT_PIXEL_MODE_BGRA
// were introduced in FreeType 2.5.0.
// The following may be removed once FreeType 2.5.0 is required to build.
#ifndef FT_LOAD_COLOR
# define FT_LOAD_COLOR ( 1L << 20 )
# define FT_PIXEL_MODE_BGRA 7
#endif
#ifdef SK_DEBUG
const char* SkTraceFtrGetError(int e) {
switch ((FT_Error)e) {
#undef FTERRORS_H_
#define FT_ERRORDEF( e, v, s ) case v: return s;
#define FT_ERROR_START_LIST
#define FT_ERROR_END_LIST
#include FT_ERRORS_H
#undef FT_ERRORDEF
#undef FT_ERROR_START_LIST
#undef FT_ERROR_END_LIST
default: return "";
}
}
#endif // SK_DEBUG
#ifdef TT_SUPPORT_COLRV1
bool operator==(const FT_OpaquePaint& a, const FT_OpaquePaint& b) {
return a.p == b.p && a.insert_root_transform == b.insert_root_transform;
}
// The stop_offset field is being upgraded to a larger representation in FreeType, and changed from
// 2.14 to 16.16. Adjust the shift factor depending on size type.
static_assert(sizeof(FT_Fixed) != sizeof(FT_F2Dot14));
constexpr float kColorStopShift =
sizeof(FT_ColorStop::stop_offset) == sizeof(FT_F2Dot14) ? 1 << 14 : 1 << 16;
#endif
namespace {
FT_Pixel_Mode compute_pixel_mode(SkMask::Format format) {
switch (format) {
case SkMask::kBW_Format:
return FT_PIXEL_MODE_MONO;
case SkMask::kA8_Format:
default:
return FT_PIXEL_MODE_GRAY;
}
}
///////////////////////////////////////////////////////////////////////////////
uint16_t packTriple(U8CPU r, U8CPU g, U8CPU b) {
if constexpr (kSkShowTextBlitCoverage) {
r = std::max(r, (U8CPU)0x40);
g = std::max(g, (U8CPU)0x40);
b = std::max(b, (U8CPU)0x40);
}
return SkPack888ToRGB16(r, g, b);
}
uint16_t grayToRGB16(U8CPU gray) {
if constexpr (kSkShowTextBlitCoverage) {
gray = std::max(gray, (U8CPU)0x40);
}
return SkPack888ToRGB16(gray, gray, gray);
}
int bittst(const uint8_t data[], int bitOffset) {
SkASSERT(bitOffset >= 0);
int lowBit = data[bitOffset >> 3] >> (~bitOffset & 7);
return lowBit & 1;
}
/**
* Copies a FT_Bitmap into an SkMask with the same dimensions.
*
* FT_PIXEL_MODE_MONO
* FT_PIXEL_MODE_GRAY
* FT_PIXEL_MODE_LCD
* FT_PIXEL_MODE_LCD_V
*/
template<bool APPLY_PREBLEND>
void copyFT2LCD16(const FT_Bitmap& bitmap, const SkMask& mask, int lcdIsBGR,
const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB)
{
SkASSERT(SkMask::kLCD16_Format == mask.fFormat);
if (FT_PIXEL_MODE_LCD != bitmap.pixel_mode) {
SkASSERT(mask.fBounds.width() == static_cast<int>(bitmap.width));
}
if (FT_PIXEL_MODE_LCD_V != bitmap.pixel_mode) {
SkASSERT(mask.fBounds.height() == static_cast<int>(bitmap.rows));
}
const uint8_t* src = bitmap.buffer;
uint16_t* dst = reinterpret_cast<uint16_t*>(mask.fImage);
const size_t dstRB = mask.fRowBytes;
const int width = mask.fBounds.width();
const int height = mask.fBounds.height();
switch (bitmap.pixel_mode) {
case FT_PIXEL_MODE_MONO:
for (int y = height; y --> 0;) {
for (int x = 0; x < width; ++x) {
dst[x] = -bittst(src, x);
}
dst = (uint16_t*)((char*)dst + dstRB);
src += bitmap.pitch;
}
break;
case FT_PIXEL_MODE_GRAY:
for (int y = height; y --> 0;) {
for (int x = 0; x < width; ++x) {
dst[x] = grayToRGB16(src[x]);
}
dst = (uint16_t*)((char*)dst + dstRB);
src += bitmap.pitch;
}
break;
case FT_PIXEL_MODE_LCD:
SkASSERT(3 * mask.fBounds.width() == static_cast<int>(bitmap.width));
for (int y = height; y --> 0;) {
const uint8_t* triple = src;
if (lcdIsBGR) {
for (int x = 0; x < width; x++) {
dst[x] = packTriple(sk_apply_lut_if<APPLY_PREBLEND>(triple[2], tableR),
sk_apply_lut_if<APPLY_PREBLEND>(triple[1], tableG),
sk_apply_lut_if<APPLY_PREBLEND>(triple[0], tableB));
triple += 3;
}
} else {
for (int x = 0; x < width; x++) {
dst[x] = packTriple(sk_apply_lut_if<APPLY_PREBLEND>(triple[0], tableR),
sk_apply_lut_if<APPLY_PREBLEND>(triple[1], tableG),
sk_apply_lut_if<APPLY_PREBLEND>(triple[2], tableB));
triple += 3;
}
}
src += bitmap.pitch;
dst = (uint16_t*)((char*)dst + dstRB);
}
break;
case FT_PIXEL_MODE_LCD_V:
SkASSERT(3 * mask.fBounds.height() == static_cast<int>(bitmap.rows));
for (int y = height; y --> 0;) {
const uint8_t* srcR = src;
const uint8_t* srcG = srcR + bitmap.pitch;
const uint8_t* srcB = srcG + bitmap.pitch;
if (lcdIsBGR) {
using std::swap;
swap(srcR, srcB);
}
for (int x = 0; x < width; x++) {
dst[x] = packTriple(sk_apply_lut_if<APPLY_PREBLEND>(*srcR++, tableR),
sk_apply_lut_if<APPLY_PREBLEND>(*srcG++, tableG),
sk_apply_lut_if<APPLY_PREBLEND>(*srcB++, tableB));
}
src += 3 * bitmap.pitch;
dst = (uint16_t*)((char*)dst + dstRB);
}
break;
default:
SkDEBUGF("FT_Pixel_Mode %d", bitmap.pixel_mode);
SkDEBUGFAIL("unsupported FT_Pixel_Mode for LCD16");
break;
}
}
/**
* Copies a FT_Bitmap into an SkMask with the same dimensions.
*
* Yes, No, Never Requested, Never Produced
*
* kBW kA8 k3D kARGB32 kLCD16
* FT_PIXEL_MODE_MONO Y Y NR N Y
* FT_PIXEL_MODE_GRAY N Y NR N Y
* FT_PIXEL_MODE_GRAY2 NP NP NR NP NP
* FT_PIXEL_MODE_GRAY4 NP NP NR NP NP
* FT_PIXEL_MODE_LCD NP NP NR NP NP
* FT_PIXEL_MODE_LCD_V NP NP NR NP NP
* FT_PIXEL_MODE_BGRA N N NR Y N
*
* TODO: All of these N need to be Y or otherwise ruled out.
*/
void copyFTBitmap(const FT_Bitmap& srcFTBitmap, SkMask& dstMask) {
SkASSERTF(dstMask.fBounds.width() == static_cast<int>(srcFTBitmap.width),
"dstMask.fBounds.width() = %d\n"
"static_cast<int>(srcFTBitmap.width) = %d",
dstMask.fBounds.width(),
static_cast<int>(srcFTBitmap.width)
);
SkASSERTF(dstMask.fBounds.height() == static_cast<int>(srcFTBitmap.rows),
"dstMask.fBounds.height() = %d\n"
"static_cast<int>(srcFTBitmap.rows) = %d",
dstMask.fBounds.height(),
static_cast<int>(srcFTBitmap.rows)
);
const uint8_t* src = reinterpret_cast<const uint8_t*>(srcFTBitmap.buffer);
const FT_Pixel_Mode srcFormat = static_cast<FT_Pixel_Mode>(srcFTBitmap.pixel_mode);
// FT_Bitmap::pitch is an int and allowed to be negative.
const int srcPitch = srcFTBitmap.pitch;
const size_t srcRowBytes = SkTAbs(srcPitch);
uint8_t* dst = dstMask.fImage;
const SkMask::Format dstFormat = static_cast<SkMask::Format>(dstMask.fFormat);
const size_t dstRowBytes = dstMask.fRowBytes;
const size_t width = srcFTBitmap.width;
const size_t height = srcFTBitmap.rows;
if (SkMask::kLCD16_Format == dstFormat) {
copyFT2LCD16<false>(srcFTBitmap, dstMask, false, nullptr, nullptr, nullptr);
return;
}
if ((FT_PIXEL_MODE_MONO == srcFormat && SkMask::kBW_Format == dstFormat) ||
(FT_PIXEL_MODE_GRAY == srcFormat && SkMask::kA8_Format == dstFormat))
{
size_t commonRowBytes = std::min(srcRowBytes, dstRowBytes);
for (size_t y = height; y --> 0;) {
memcpy(dst, src, commonRowBytes);
src += srcPitch;
dst += dstRowBytes;
}
} else if (FT_PIXEL_MODE_MONO == srcFormat && SkMask::kA8_Format == dstFormat) {
for (size_t y = height; y --> 0;) {
uint8_t byte = 0;
int bits = 0;
const uint8_t* src_row = src;
uint8_t* dst_row = dst;
for (size_t x = width; x --> 0;) {
if (0 == bits) {
byte = *src_row++;
bits = 8;
}
*dst_row++ = byte & 0x80 ? 0xff : 0x00;
bits--;
byte <<= 1;
}
src += srcPitch;
dst += dstRowBytes;
}
} else if (FT_PIXEL_MODE_BGRA == srcFormat && SkMask::kARGB32_Format == dstFormat) {
// FT_PIXEL_MODE_BGRA is pre-multiplied.
for (size_t y = height; y --> 0;) {
const uint8_t* src_row = src;
SkPMColor* dst_row = reinterpret_cast<SkPMColor*>(dst);
for (size_t x = 0; x < width; ++x) {
uint8_t b = *src_row++;
uint8_t g = *src_row++;
uint8_t r = *src_row++;
uint8_t a = *src_row++;
*dst_row++ = SkPackARGB32(a, r, g, b);
if constexpr (kSkShowTextBlitCoverage) {
*(dst_row-1) = SkFourByteInterp256(*(dst_row-1), SK_ColorWHITE, 0x40);
}
}
src += srcPitch;
dst += dstRowBytes;
}
} else {
SkDEBUGF("FT_Pixel_Mode %d, SkMask::Format %d\n", srcFormat, dstFormat);
SkDEBUGFAIL("unsupported combination of FT_Pixel_Mode and SkMask::Format");
}
}
inline int convert_8_to_1(unsigned byte) {
SkASSERT(byte <= 0xFF);
// Arbitrary decision that making the cutoff at 1/4 instead of 1/2 in general looks better.
return (byte >> 6) != 0;
}
uint8_t pack_8_to_1(const uint8_t alpha[8]) {
unsigned bits = 0;
for (int i = 0; i < 8; ++i) {
bits <<= 1;
bits |= convert_8_to_1(alpha[i]);
}
return SkToU8(bits);
}
void packA8ToA1(const SkMask& mask, const uint8_t* src, size_t srcRB) {
const int height = mask.fBounds.height();
const int width = mask.fBounds.width();
const int octs = width >> 3;
const int leftOverBits = width & 7;
uint8_t* dst = mask.fImage;
const int dstPad = mask.fRowBytes - SkAlign8(width)/8;
SkASSERT(dstPad >= 0);
const int srcPad = srcRB - width;
SkASSERT(srcPad >= 0);
for (int y = 0; y < height; ++y) {
for (int i = 0; i < octs; ++i) {
*dst++ = pack_8_to_1(src);
src += 8;
}
if (leftOverBits > 0) {
unsigned bits = 0;
int shift = 7;
for (int i = 0; i < leftOverBits; ++i, --shift) {
bits |= convert_8_to_1(*src++) << shift;
}
*dst++ = bits;
}
src += srcPad;
dst += dstPad;
}
}
inline SkMask::Format SkMaskFormat_for_SkColorType(SkColorType colorType) {
switch (colorType) {
case kAlpha_8_SkColorType:
return SkMask::kA8_Format;
case kN32_SkColorType:
return SkMask::kARGB32_Format;
default:
SkDEBUGFAIL("unsupported SkBitmap::Config");
return SkMask::kA8_Format;
}
}
inline SkColorType SkColorType_for_FTPixelMode(FT_Pixel_Mode pixel_mode) {
switch (pixel_mode) {
case FT_PIXEL_MODE_MONO:
case FT_PIXEL_MODE_GRAY:
return kAlpha_8_SkColorType;
case FT_PIXEL_MODE_BGRA:
return kN32_SkColorType;
default:
SkDEBUGFAIL("unsupported FT_PIXEL_MODE");
return kAlpha_8_SkColorType;
}
}
inline SkColorType SkColorType_for_SkMaskFormat(SkMask::Format format) {
switch (format) {
case SkMask::kBW_Format:
case SkMask::kA8_Format:
case SkMask::kLCD16_Format:
return kAlpha_8_SkColorType;
case SkMask::kARGB32_Format:
return kN32_SkColorType;
default:
SkDEBUGFAIL("unsupported destination SkBitmap::Config");
return kAlpha_8_SkColorType;
}
}
// Only build COLRv1 rendering code if FreeType is new enough to have COLRv1
// additions. FreeType defines a macro in the ftoption header to tell us whether
// it does support these features.
#ifdef TT_SUPPORT_COLRV1
const uint16_t kForegroundColorPaletteIndex = 0xFFFF;
// 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.
SkColor lerpSkColor(SkColor c0, SkColor 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;
}
const auto c0_4f = Sk4f_fromL32(c0), c1_4f = Sk4f_fromL32(c1),
c_4f = c0_4f + (c1_4f - c0_4f) * t;
return Sk4f_toL32(c_4f);
}
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<SkColor>& colors,
std::vector<SkScalar>& stops,
TruncateStops truncateStops) {
if (stops.size() <= 1u ||
zeroRadiusStop < *stops.begin() || *(stops.end() - 1) < zeroRadiusStop)
{
return;
}
size_t afterIndex = (truncateStops == TruncateStart)
? std::lower_bound(stops.begin(), stops.end(), zeroRadiusStop) - stops.begin()
: std::upper_bound(stops.begin(), stops.end(), zeroRadiusStop) - stops.begin();
const float t = (zeroRadiusStop - stops[afterIndex - 1]) /
(stops[afterIndex] - stops[afterIndex - 1]);
SkColor lerpColor = lerpSkColor(colors[afterIndex - 1], colors[afterIndex], t);
if (truncateStops == TruncateStart) {
stops.erase(stops.begin(), stops.begin() + afterIndex);
colors.erase(colors.begin(), colors.begin() + afterIndex);
stops.insert(stops.begin(), 0);
colors.insert(colors.begin(), lerpColor);
} else {
stops.erase(stops.begin() + afterIndex, stops.end());
colors.erase(colors.begin() + afterIndex, colors.end());
stops.insert(stops.end(), 1);
colors.insert(colors.end(), lerpColor);
}
}
struct OpaquePaintHasher {
size_t operator()(const FT_OpaquePaint& opaquePaint) {
return SkGoodHash()(opaquePaint.p) ^
SkGoodHash()(opaquePaint.insert_root_transform);
}
};
using VisitedSet = SkTHashSet<FT_OpaquePaint, OpaquePaintHasher>;
bool generateFacePathCOLRv1(FT_Face face, SkGlyphID glyphID, SkPath* path);
inline float SkColrV1AlphaToFloat(uint16_t alpha) { return (alpha / float(1 << 14)); }
inline SkTileMode ToSkTileMode(FT_PaintExtend extendMode) {
switch (extendMode) {
case FT_COLR_PAINT_EXTEND_REPEAT:
return SkTileMode::kRepeat;
case FT_COLR_PAINT_EXTEND_REFLECT:
return SkTileMode::kMirror;
default:
return SkTileMode::kClamp;
}
}
inline SkBlendMode ToSkBlendMode(FT_Composite_Mode compositeMode) {
switch (compositeMode) {
case FT_COLR_COMPOSITE_CLEAR:
return SkBlendMode::kClear;
case FT_COLR_COMPOSITE_SRC:
return SkBlendMode::kSrc;
case FT_COLR_COMPOSITE_DEST:
return SkBlendMode::kDst;
case FT_COLR_COMPOSITE_SRC_OVER:
return SkBlendMode::kSrcOver;
case FT_COLR_COMPOSITE_DEST_OVER:
return SkBlendMode::kDstOver;
case FT_COLR_COMPOSITE_SRC_IN:
return SkBlendMode::kSrcIn;
case FT_COLR_COMPOSITE_DEST_IN:
return SkBlendMode::kDstIn;
case FT_COLR_COMPOSITE_SRC_OUT:
return SkBlendMode::kSrcOut;
case FT_COLR_COMPOSITE_DEST_OUT:
return SkBlendMode::kDstOut;
case FT_COLR_COMPOSITE_SRC_ATOP:
return SkBlendMode::kSrcATop;
case FT_COLR_COMPOSITE_DEST_ATOP:
return SkBlendMode::kDstATop;
case FT_COLR_COMPOSITE_XOR:
return SkBlendMode::kXor;
case FT_COLR_COMPOSITE_PLUS:
return SkBlendMode::kPlus;
case FT_COLR_COMPOSITE_SCREEN:
return SkBlendMode::kScreen;
case FT_COLR_COMPOSITE_OVERLAY:
return SkBlendMode::kOverlay;
case FT_COLR_COMPOSITE_DARKEN:
return SkBlendMode::kDarken;
case FT_COLR_COMPOSITE_LIGHTEN:
return SkBlendMode::kLighten;
case FT_COLR_COMPOSITE_COLOR_DODGE:
return SkBlendMode::kColorDodge;
case FT_COLR_COMPOSITE_COLOR_BURN:
return SkBlendMode::kColorBurn;
case FT_COLR_COMPOSITE_HARD_LIGHT:
return SkBlendMode::kHardLight;
case FT_COLR_COMPOSITE_SOFT_LIGHT:
return SkBlendMode::kSoftLight;
case FT_COLR_COMPOSITE_DIFFERENCE:
return SkBlendMode::kDifference;
case FT_COLR_COMPOSITE_EXCLUSION:
return SkBlendMode::kExclusion;
case FT_COLR_COMPOSITE_MULTIPLY:
return SkBlendMode::kMultiply;
case FT_COLR_COMPOSITE_HSL_HUE:
return SkBlendMode::kHue;
case FT_COLR_COMPOSITE_HSL_SATURATION:
return SkBlendMode::kSaturation;
case FT_COLR_COMPOSITE_HSL_COLOR:
return SkBlendMode::kColor;
case FT_COLR_COMPOSITE_HSL_LUMINOSITY:
return SkBlendMode::kLuminosity;
default:
return SkBlendMode::kDst;
}
}
inline SkMatrix ToSkMatrix(FT_Affine23 affine23) {
// Convert from FreeType's FT_Affine23 column major order to SkMatrix row-major order.
return SkMatrix::MakeAll(
SkFixedToScalar(affine23.xx), -SkFixedToScalar(affine23.xy), SkFixedToScalar(affine23.dx),
-SkFixedToScalar(affine23.yx), SkFixedToScalar(affine23.yy), -SkFixedToScalar(affine23.dy),
0, 0, 1);
}
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;
}
bool colrv1_configure_skpaint(FT_Face face,
const SkSpan<SkColor>& palette,
const SkColor foregroundColor,
const FT_COLR_Paint& colrPaint,
SkPaint* paint) {
auto fetchColorStops = [&face, &palette, &foregroundColor](
const FT_ColorStopIterator& colorStopIterator,
std::vector<SkScalar>& stops,
std::vector<SkColor>& colors) -> bool {
const FT_UInt colorStopCount = colorStopIterator.num_color_stops;
if (colorStopCount == 0) {
return false;
}
// 5.7.11.2.4 ColorIndex, ColorStop and ColorLine
// "Applications shall apply the colorStops in increasing stopOffset order."
struct ColorStop {
SkScalar pos;
SkColor color;
};
std::vector<ColorStop> colorStopsSorted;
colorStopsSorted.resize(colorStopCount);
FT_ColorStop color_stop;
FT_ColorStopIterator mutable_color_stop_iterator = colorStopIterator;
while (FT_Get_Colorline_Stops(face, &color_stop, &mutable_color_stop_iterator)) {
FT_UInt index = mutable_color_stop_iterator.current_color_stop - 1;
colorStopsSorted[index].pos = color_stop.stop_offset / kColorStopShift;
FT_UInt16& palette_index = color_stop.color.palette_index;
if (palette_index == kForegroundColorPaletteIndex) {
U8CPU newAlpha = SkColorGetA(foregroundColor) *
SkColrV1AlphaToFloat(color_stop.color.alpha);
colorStopsSorted[index].color = SkColorSetA(foregroundColor, newAlpha);
} else if (palette_index >= palette.size()) {
return false;
} else {
U8CPU newAlpha = SkColorGetA(palette[palette_index]) *
SkColrV1AlphaToFloat(color_stop.color.alpha);
colorStopsSorted[index].color = SkColorSetA(palette[palette_index], newAlpha);
}
}
std::stable_sort(colorStopsSorted.begin(), colorStopsSorted.end(),
[](const ColorStop& a, const ColorStop& b) { return a.pos < b.pos; });
stops.resize(colorStopCount);
colors.resize(colorStopCount);
for (size_t i = 0; i < colorStopCount; ++i) {
stops[i] = colorStopsSorted[i].pos;
colors[i] = colorStopsSorted[i].color;
}
return true;
};
switch (colrPaint.format) {
case FT_COLR_PAINTFORMAT_SOLID: {
FT_PaintSolid solid = colrPaint.u.solid;
// Dont' draw anything with this color if the palette index is out of bounds.
SkColor color = SK_ColorTRANSPARENT;
if (solid.color.palette_index == kForegroundColorPaletteIndex) {
U8CPU newAlpha = SkColorGetA(foregroundColor) *
SkColrV1AlphaToFloat(solid.color.alpha);
color = SkColorSetA(foregroundColor, newAlpha);
} else if (solid.color.palette_index >= palette.size()) {
return false;
} else {
U8CPU newAlpha = SkColorGetA(palette[solid.color.palette_index]) *
SkColrV1AlphaToFloat(solid.color.alpha);
color = SkColorSetA(palette[solid.color.palette_index], newAlpha);
}
paint->setShader(nullptr);
paint->setColor(color);
return true;
}
case FT_COLR_PAINTFORMAT_LINEAR_GRADIENT: {
const FT_PaintLinearGradient& linearGradient = colrPaint.u.linear_gradient;
std::vector<SkScalar> stops;
std::vector<SkColor> colors;
if (!fetchColorStops(linearGradient.colorline.color_stop_iterator, stops, colors)) {
return false;
}
if (stops.size() == 1) {
paint->setColor(colors[0]);
return true;
}
SkPoint linePositions[2] = {SkPoint::Make( SkFixedToScalar(linearGradient.p0.x),
-SkFixedToScalar(linearGradient.p0.y)),
SkPoint::Make( SkFixedToScalar(linearGradient.p1.x),
-SkFixedToScalar(linearGradient.p1.y))};
SkPoint p0 = linePositions[0];
SkPoint p1 = linePositions[1];
SkPoint p2 = SkPoint::Make( SkFixedToScalar(linearGradient.p2.x),
-SkFixedToScalar(linearGradient.p2.y));
// 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)) {
paint->setColor(colors[0]);
return true;
}
// 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 = ToSkTileMode(linearGradient.colorline.extend);
SkScalar colorStopRange = stops.back() - stops.front();
// 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) {
paint->setColor(SK_ColorTRANSPARENT);
return true;
} 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.end() - 1) + 1.0f);
colors.push_back(*(colors.end()-1));
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() != 0.f)) {
SkVector p0p3 = p3 - p0;
SkVector p0Offset = p0p3;
p0Offset.scale(stops.front());
SkVector p1Offset = p0p3;
p1Offset.scale(stops.back());
linePositions[0] = p0 + p0Offset;
linePositions[1] = p0 + p1Offset;
SkScalar scaleFactor = 1 / colorStopRange;
SkScalar startOffset = stops.front();
for (SkScalar& stop : stops) {
stop = (stop - startOffset) * scaleFactor;
}
}
sk_sp<SkShader> shader(SkGradientShader::MakeLinear(
linePositions,
colors.data(), stops.data(), stops.size(),
tileMode));
SkASSERT(shader);
// An opaque color is needed to ensure the gradient is not modulated by alpha.
paint->setColor(SK_ColorBLACK);
paint->setShader(shader);
return true;
}
case FT_COLR_PAINTFORMAT_RADIAL_GRADIENT: {
const FT_PaintRadialGradient& radialGradient = colrPaint.u.radial_gradient;
SkPoint start = SkPoint::Make( SkFixedToScalar(radialGradient.c0.x),
-SkFixedToScalar(radialGradient.c0.y));
SkScalar startRadius = SkFixedToScalar(radialGradient.r0);
SkPoint end = SkPoint::Make( SkFixedToScalar(radialGradient.c1.x),
-SkFixedToScalar(radialGradient.c1.y));
SkScalar endRadius = SkFixedToScalar(radialGradient.r1);
std::vector<SkScalar> stops;
std::vector<SkColor> colors;
if (!fetchColorStops(radialGradient.colorline.color_stop_iterator, stops, colors)) {
return false;
}
if (stops.size() == 1) {
paint->setColor(colors[0]);
return true;
}
SkScalar colorStopRange = stops.back() - stops.front();
SkTileMode tileMode = ToSkTileMode(radialGradient.colorline.extend);
if (colorStopRange == 0.f) {
if (tileMode != SkTileMode::kClamp) {
paint->setColor(SK_ColorTRANSPARENT);
return true;
} 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.end() - 1) + 1.0f);
colors.push_back(*(colors.end()-1));
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() != 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();
SkVector startOffset = startToEnd;
startOffset.scale(stops.front());
SkVector endOffset = startToEnd;
endOffset.scale(stops.back());
// 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();
startRadius = startRadius + radiusDiff * stops.front();
for (auto& stop : stops) {
stop = (stop - stopsStartOffset) * scaleFactor;
}
}
// For negative radii, interpolation is needed to prepare parameters suitable
// for invoking the shader. Implementation below as resolution discussed in
// https://github.com/googlefonts/colr-gradients-spec/issues/367.
// Truncate to manually interpolated color for tile mode clamp, otherwise
// calculate positive projected circles.
if (startRadius < 0 || endRadius < 0) {
if (SkGraphics::GetVariableColrV1Enabled()) {
if (startRadius == endRadius && startRadius < 0) {
paint->setColor(SK_ColorTRANSPARENT);
return true;
}
if (tileMode == SkTileMode::kClamp) {
SkVector startToEnd = end - start;
SkScalar radiusDiff = endRadius - startRadius;
SkScalar zeroRadiusStop = 0.f;
TruncateStops truncateSide = TruncateStart;
if (startRadius < 0) {
truncateSide = TruncateStart;
// 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, colors, 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;
colors.erase(colors.begin(), colors.end() - 1);
stops.erase(stops.begin(), stops.end() - 1);
} else {
start -= startToEnd;
startRadius = -radiusDiff;
colors.erase(colors.begin() + 1, colors.end());
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;
};
SkVector startToEnd = end - start;
SkScalar radiusDiff = endRadius - startRadius;
SkScalar factorZeroCrossing = (startRadius / (startRadius - endRadius));
bool inRange = 0.f <= factorZeroCrossing && factorZeroCrossing <= 1.0f;
SkScalar direction = inRange && radiusDiff < 0 ? -1.0f : 1.0f;
SkScalar circleProjectionFactor =
roundIntegerMultiple(factorZeroCrossing * direction, tileMode);
startToEnd.scale(circleProjectionFactor);
startRadius += circleProjectionFactor * radiusDiff;
endRadius += circleProjectionFactor * radiusDiff;
start += startToEnd;
end += startToEnd;
}
}
} else {
// TODO(drott): Remove else part when variable COLRv1 becomes the default.
paint->setColor(SK_ColorTRANSPARENT);
return true;
}
}
// An opaque color is needed to ensure the gradient is not modulated by alpha.
paint->setColor(SK_ColorBLACK);
paint->setShader(SkGradientShader::MakeTwoPointConical(
start, startRadius, end, endRadius, colors.data(), stops.data(), stops.size(),
tileMode));
return true;
}
case FT_COLR_PAINTFORMAT_SWEEP_GRADIENT: {
const FT_PaintSweepGradient& sweepGradient = colrPaint.u.sweep_gradient;
SkPoint center = SkPoint::Make( SkFixedToScalar(sweepGradient.center.x),
-SkFixedToScalar(sweepGradient.center.y));
SkScalar startAngle = SkFixedToScalar(sweepGradient.start_angle * 180.0f);
SkScalar endAngle = SkFixedToScalar(sweepGradient.end_angle * 180.0f);
if (SkGraphics::GetVariableColrV1Enabled()) {
// OpenType 1.9.1 adds a shift to the angle to ease specification of a 0 to 360
// degree sweep. We want to release that in sync with releasing variable COLRv1.
startAngle += 180.0f;
endAngle += 180.0f;
}
std::vector<SkScalar> stops;
std::vector<SkColor> colors;
if (!fetchColorStops(sweepGradient.colorline.color_stop_iterator, stops, colors)) {
return false;
}
if (stops.size() == 1) {
paint->setColor(colors[0]);
return true;
}
// An opaque color is needed to ensure the gradient is not modulated by alpha.
paint->setColor(SK_ColorBLACK);
if (SkGraphics::GetVariableColrV1Enabled()) {
// New (Var)SweepGradient implementation compliant with OpenType 1.9.1 from here.
// The plan is to release this in sync with variable COLRv1 support.
// 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 = ToSkTileMode(sweepGradient.colorline.extend);
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.".
paint->setColor(SK_ColorTRANSPARENT);
return true;
}
SkScalar startAngleScaled = startAngle + sectorAngle * stops.front();
SkScalar endAngleScaled = startAngle + sectorAngle * stops.back();
// 2) Scale stops accordingly to 0 to 1 range.
SkScalar scaleFactor = 1 / (stops.back() - stops.front());
SkScalar startOffset = stops.front();
for (SkScalar& stop : stops) {
stop = (stop - startOffset) * scaleFactor;
}
/* 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());
std::reverse(colors.begin(), colors.end());
for (auto& stop : stops) {
stop = 1.0f - stop;
}
}
paint->setShader(SkGradientShader::MakeSweep(center.x(), center.y(),
colors.data(),
stops.data(), stops.size(),
tileMode,
startAngleScaled,
endAngleScaled,
0, nullptr));
} else {
// Old (Var)SweepGradient implementation, which is aiming to be compliant with
// OpenType 1.9. but ambiguities and issues were found in the spec resolved in
// 1.9.1. see above. TODO(drott): This else block is to be removed when the new
// implementation has shipped in Chrome.
// Prepare angles to be within range for the shader.
auto clampAngleToRange = [](SkScalar angle) {
SkScalar clampedAngle = SkScalarMod(angle, 360.f);
if (clampedAngle < 0) {
return clampedAngle + 360.f;
}
return clampedAngle;
};
startAngle = clampAngleToRange(startAngle);
endAngle = clampAngleToRange(endAngle);
SkScalar sectorAngle = endAngle > startAngle ? endAngle - startAngle
: endAngle + 360.0f - startAngle;
/* 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;"
*/
SkMatrix localMatrix;
localMatrix.postRotate(startAngle, center.x(), center.y());
/* Mirror along x-axis to change angle direction. */
localMatrix.postScale(1, -1, center.x(), center.y());
SkTileMode tileMode = ToSkTileMode(sweepGradient.colorline.extend);
paint->setShader(SkGradientShader::MakeSweep(center.x(), center.y(),
colors.data(),
stops.data(), stops.size(),
tileMode,
0,
sectorAngle,
0, &localMatrix));
}
return true;
}
default: {
SkASSERT(false);
return false;
}
}
SkUNREACHABLE;
}
bool colrv1_draw_paint(SkCanvas* canvas,
const SkSpan<SkColor>& palette,
const SkColor foregroundColor,
FT_Face face,
const FT_COLR_Paint& colrPaint) {
switch (colrPaint.format) {
case FT_COLR_PAINTFORMAT_GLYPH: {
FT_UInt glyphID = colrPaint.u.glyph.glyphID;
SkPath path;
/* TODO: Currently this call retrieves the path at units_per_em size. If we want to get
* correct hinting for the scaled size under the transforms at this point in the color
* glyph graph, we need to extract at least the requested glyph width and height and
* pass that to the path generation. */
if (!generateFacePathCOLRv1(face, glyphID, &path)) {
return false;
}
if constexpr (kSkShowTextBlitCoverage) {
SkPaint highlight_paint;
highlight_paint.setColor(0x33FF0000);
canvas->drawRect(path.getBounds(), highlight_paint);
}
canvas->clipPath(path, true /* doAntiAlias */);
return true;
}
case FT_COLR_PAINTFORMAT_SOLID:
case FT_COLR_PAINTFORMAT_LINEAR_GRADIENT:
case FT_COLR_PAINTFORMAT_RADIAL_GRADIENT:
case FT_COLR_PAINTFORMAT_SWEEP_GRADIENT: {
SkPaint skPaint;
if (!colrv1_configure_skpaint(face, palette, foregroundColor, colrPaint, &skPaint)) {
return false;
}
canvas->drawPaint(skPaint);
return true;
}
case FT_COLR_PAINTFORMAT_TRANSFORM:
case FT_COLR_PAINTFORMAT_TRANSLATE:
case FT_COLR_PAINTFORMAT_SCALE:
case FT_COLR_PAINTFORMAT_ROTATE:
case FT_COLR_PAINTFORMAT_SKEW:
[[fallthrough]]; // Transforms handled in colrv1_transform.
default:
SkASSERT(false);
return false;
}
SkUNREACHABLE;
}
bool colrv1_draw_glyph_with_path(SkCanvas* canvas,
const SkSpan<SkColor>& palette, SkColor foregroundColor,
FT_Face face,
const FT_COLR_Paint& glyphPaint, const FT_COLR_Paint& fillPaint) {
SkASSERT(glyphPaint.format == FT_COLR_PAINTFORMAT_GLYPH);
SkASSERT(fillPaint.format == FT_COLR_PAINTFORMAT_SOLID ||
fillPaint.format == FT_COLR_PAINTFORMAT_LINEAR_GRADIENT ||
fillPaint.format == FT_COLR_PAINTFORMAT_RADIAL_GRADIENT ||
fillPaint.format == FT_COLR_PAINTFORMAT_SWEEP_GRADIENT);
SkPaint skiaFillPaint;
skiaFillPaint.setAntiAlias(true);
if (!colrv1_configure_skpaint(face, palette, foregroundColor, fillPaint, &skiaFillPaint)) {
return false;
}
FT_UInt glyphID = glyphPaint.u.glyph.glyphID;
SkPath path;
/* TODO: Currently this call retrieves the path at units_per_em size. If we want to get
* correct hinting for the scaled size under the transforms at this point in the color
* glyph graph, we need to extract at least the requested glyph width and height and
* pass that to the path generation. */
if (!generateFacePathCOLRv1(face, glyphID, &path)) {
return false;
}
if constexpr (kSkShowTextBlitCoverage) {
SkPaint highlightPaint;
highlightPaint.setColor(0x33FF0000);
canvas->drawRect(path.getBounds(), highlightPaint);
}
canvas->drawPath(path, skiaFillPaint);
return true;
}
/* In drawing mode, concatenates the transforms directly on SkCanvas. In
* bounding box calculation mode, no SkCanvas is specified, but we only want to
* retrieve the transform from the FreeType paint object. */
void colrv1_transform(FT_Face face,
const FT_COLR_Paint& colrPaint,
SkCanvas* canvas,
SkMatrix* outTransform = nullptr) {
SkMatrix transform;
SkASSERT(canvas || outTransform);
switch (colrPaint.format) {
case FT_COLR_PAINTFORMAT_TRANSFORM: {
transform = ToSkMatrix(colrPaint.u.transform.affine);
break;
}
case FT_COLR_PAINTFORMAT_TRANSLATE: {
transform = SkMatrix::Translate( SkFixedToScalar(colrPaint.u.translate.dx),
-SkFixedToScalar(colrPaint.u.translate.dy));
break;
}
case FT_COLR_PAINTFORMAT_SCALE: {
transform.setScale( SkFixedToScalar(colrPaint.u.scale.scale_x),
SkFixedToScalar(colrPaint.u.scale.scale_y),
SkFixedToScalar(colrPaint.u.scale.center_x),
-SkFixedToScalar(colrPaint.u.scale.center_y));
break;
}
case FT_COLR_PAINTFORMAT_ROTATE: {
// COLRv1 angles are counter-clockwise, compare
// https://docs.microsoft.com/en-us/typography/opentype/spec/colr#formats-24-to-27-paintrotate-paintvarrotate-paintrotatearoundcenter-paintvarrotatearoundcenter
transform = SkMatrix::RotateDeg(
-SkFixedToScalar(colrPaint.u.rotate.angle) * 180.0f,
SkPoint::Make( SkFixedToScalar(colrPaint.u.rotate.center_x),
-SkFixedToScalar(colrPaint.u.rotate.center_y)));
break;
}
case FT_COLR_PAINTFORMAT_SKEW: {
// In the PAINTFORMAT_ROTATE implementation, SkMatrix setRotate
// snaps to 0 for values very close to 0. Do the same here.
SkScalar xDeg = SkFixedToScalar(colrPaint.u.skew.x_skew_angle) * 180.0f;
SkScalar xRad = SkDegreesToRadians(xDeg);
SkScalar xTan = SkScalarTan(xRad);
xTan = SkScalarNearlyZero(xTan) ? 0.0f : xTan;
SkScalar yDeg = SkFixedToScalar(colrPaint.u.skew.y_skew_angle) * 180.0f;
// Negate y_skew_angle due to Skia's y-down coordinate system to achieve
// counter-clockwise skew along the y-axis.
SkScalar yRad = SkDegreesToRadians(-yDeg);
SkScalar yTan = SkScalarTan(yRad);
yTan = SkScalarNearlyZero(yTan) ? 0.0f : yTan;
transform.setSkew(xTan, yTan,
SkFixedToScalar(colrPaint.u.skew.center_x),
-SkFixedToScalar(colrPaint.u.skew.center_y));
break;
}
default: {
SkASSERT(false); // Only transforms are handled in this function.
}
}
if (canvas) {
canvas->concat(transform);
}
if (outTransform) {
*outTransform = transform;
}
}
bool colrv1_start_glyph(SkCanvas* canvas,
const SkSpan<SkColor>& palette,
const SkColor foregroundColor,
FT_Face face,
uint16_t glyphId,
FT_Color_Root_Transform rootTransform,
VisitedSet* activePaints);
bool colrv1_traverse_paint(SkCanvas* canvas,
const SkSpan<SkColor>& palette,
const SkColor foregroundColor,
FT_Face face,
FT_OpaquePaint opaquePaint,
VisitedSet* activePaints) {
// Cycle detection, see section "5.7.11.1.9 Color glyphs as a directed acyclic graph".
if (activePaints->contains(opaquePaint)) {
return false;
}
activePaints->add(opaquePaint);
SK_AT_SCOPE_EXIT(activePaints->remove(opaquePaint));
FT_COLR_Paint paint;
if (!FT_Get_Paint(face, opaquePaint, &paint)) {
return false;
}
SkAutoCanvasRestore autoRestore(canvas, true /* doSave */);
switch (paint.format) {
case FT_COLR_PAINTFORMAT_COLR_LAYERS: {
FT_LayerIterator& layerIterator = paint.u.colr_layers.layer_iterator;
FT_OpaquePaint layerPaint{nullptr, 1};
while (FT_Get_Paint_Layers(face, &layerIterator, &layerPaint)) {
if (!colrv1_traverse_paint(canvas, palette, foregroundColor, face,
layerPaint, activePaints)) {
return false;
}
}
return true;
}
case FT_COLR_PAINTFORMAT_GLYPH:
// Special case paint graph leaf situations to improve
// performance. These are situations in the graph where a GlyphPaint
// is followed by either a solid or a gradient fill. Here we can use
// drawPath() + SkPaint directly which is faster than setting a
// clipPath() followed by a drawPaint().
FT_COLR_Paint fillPaint;
if (!FT_Get_Paint(face, paint.u.glyph.paint, &fillPaint)) {
return false;
}
if (fillPaint.format == FT_COLR_PAINTFORMAT_SOLID ||
fillPaint.format == FT_COLR_PAINTFORMAT_LINEAR_GRADIENT ||
fillPaint.format == FT_COLR_PAINTFORMAT_RADIAL_GRADIENT ||
fillPaint.format == FT_COLR_PAINTFORMAT_SWEEP_GRADIENT)
{
return colrv1_draw_glyph_with_path(canvas, palette, foregroundColor,
face, paint, fillPaint);
}
if (!colrv1_draw_paint(canvas, palette, foregroundColor, face, paint)) {
return false;
}
return colrv1_traverse_paint(canvas, palette, foregroundColor,
face, paint.u.glyph.paint, activePaints);
case FT_COLR_PAINTFORMAT_COLR_GLYPH:
return colrv1_start_glyph(canvas, palette, foregroundColor,
face, paint.u.colr_glyph.glyphID, FT_COLOR_NO_ROOT_TRANSFORM,
activePaints);
case FT_COLR_PAINTFORMAT_TRANSFORM:
colrv1_transform(face, paint, canvas);
return colrv1_traverse_paint(canvas, palette, foregroundColor,
face, paint.u.transform.paint, activePaints);
case FT_COLR_PAINTFORMAT_TRANSLATE:
colrv1_transform(face, paint, canvas);
return colrv1_traverse_paint(canvas, palette, foregroundColor,
face, paint.u.translate.paint, activePaints);
case FT_COLR_PAINTFORMAT_SCALE:
colrv1_transform(face, paint, canvas);
return colrv1_traverse_paint(canvas, palette, foregroundColor,
face, paint.u.scale.paint, activePaints);
case FT_COLR_PAINTFORMAT_ROTATE:
colrv1_transform(face, paint, canvas);
return colrv1_traverse_paint(canvas, palette, foregroundColor,
face, paint.u.rotate.paint, activePaints);
case FT_COLR_PAINTFORMAT_SKEW:
colrv1_transform(face, paint, canvas);
return colrv1_traverse_paint(canvas, palette, foregroundColor,
face, paint.u.skew.paint, activePaints);
case FT_COLR_PAINTFORMAT_COMPOSITE: {
SkAutoCanvasRestore acr(canvas, false);
canvas->saveLayer(nullptr, nullptr);
if (!colrv1_traverse_paint(canvas, palette, foregroundColor,
face, paint.u.composite.backdrop_paint, activePaints)) {
return false;
}
SkPaint blendModePaint;
blendModePaint.setBlendMode(ToSkBlendMode(paint.u.composite.composite_mode));
canvas->saveLayer(nullptr, &blendModePaint);
return colrv1_traverse_paint(canvas, palette, foregroundColor,
face, paint.u.composite.source_paint, activePaints);
}
case FT_COLR_PAINTFORMAT_SOLID:
case FT_COLR_PAINTFORMAT_LINEAR_GRADIENT:
case FT_COLR_PAINTFORMAT_RADIAL_GRADIENT:
case FT_COLR_PAINTFORMAT_SWEEP_GRADIENT: {
return colrv1_draw_paint(canvas, palette, foregroundColor, face, paint);
}
default:
SkASSERT(false);
return false;
}
SkUNREACHABLE;
}
SkPath GetClipBoxPath(FT_Face face, uint16_t glyphId, bool untransformed) {
SkPath resultPath;
using DoneFTSize = SkFunctionWrapper<decltype(FT_Done_Size), FT_Done_Size>;
std::unique_ptr<std::remove_pointer_t<FT_Size>, DoneFTSize> unscaledFtSize = nullptr;
FT_Size oldSize = face->size;
FT_Matrix oldTransform;
FT_Vector oldDelta;
FT_Error err = 0;
if (untransformed) {
unscaledFtSize.reset(
[face]() -> FT_Size {
FT_Size size;
FT_Error err = FT_New_Size(face, &size);
if (err != 0) {
SK_TRACEFTR(err,
"FT_New_Size(%s) failed in generateFacePathStaticCOLRv1.",
face->family_name);
return nullptr;
}
return size;
}());
if (!unscaledFtSize) {
return resultPath;
}
err = FT_Activate_Size(unscaledFtSize.get());
if (err != 0) {
return resultPath;
}
err = FT_Set_Char_Size(face, SkIntToFDot6(face->units_per_EM), 0, 0, 0);
if (err != 0) {
return resultPath;
}
FT_Get_Transform(face, &oldTransform, &oldDelta);
FT_Set_Transform(face, nullptr, nullptr);
}
FT_ClipBox colrGlyphClipBox;
if (FT_Get_Color_Glyph_ClipBox(face, glyphId, &colrGlyphClipBox)) {
resultPath = SkPath::Polygon({{ SkFDot6ToScalar(colrGlyphClipBox.bottom_left.x),
-SkFDot6ToScalar(colrGlyphClipBox.bottom_left.y)},
{ SkFDot6ToScalar(colrGlyphClipBox.top_left.x),
-SkFDot6ToScalar(colrGlyphClipBox.top_left.y)},
{ SkFDot6ToScalar(colrGlyphClipBox.top_right.x),
-SkFDot6ToScalar(colrGlyphClipBox.top_right.y)},
{ SkFDot6ToScalar(colrGlyphClipBox.bottom_right.x),
-SkFDot6ToScalar(colrGlyphClipBox.bottom_right.y)}},
true);
}
if (untransformed) {
err = FT_Activate_Size(oldSize);
if (err != 0) {
return resultPath;
}
FT_Set_Transform(face, &oldTransform, &oldDelta);
}
return resultPath;
}
bool colrv1_start_glyph(SkCanvas* canvas,
const SkSpan<SkColor>& palette,
const SkColor foregroundColor,
FT_Face face,
uint16_t glyphId,
FT_Color_Root_Transform rootTransform,
VisitedSet* activePaints) {
FT_OpaquePaint opaquePaint{nullptr, 1};
if (!FT_Get_Color_Glyph_Paint(face, glyphId, rootTransform, &opaquePaint)) {
return false;
}
bool untransformed = rootTransform == FT_COLOR_NO_ROOT_TRANSFORM;
SkPath clipBoxPath = GetClipBoxPath(face, glyphId, untransformed);
if (!clipBoxPath.isEmpty()) {
canvas->clipPath(clipBoxPath, true);
}
if (!colrv1_traverse_paint(canvas, palette, foregroundColor,
face, opaquePaint, activePaints)) {
return false;
}
return true;
}
bool colrv1_start_glyph_bounds(SkMatrix *ctm,
SkRect* bounds,
FT_Face face,
uint16_t glyphId,
FT_Color_Root_Transform rootTransform,
VisitedSet* activePaints);
bool colrv1_traverse_paint_bounds(SkMatrix* ctm,
SkRect* bounds,
FT_Face face,
FT_OpaquePaint opaquePaint,
VisitedSet* activePaints) {
// Cycle detection, see section "5.7.11.1.9 Color glyphs as a directed acyclic graph".
if (activePaints->contains(opaquePaint)) {
return false;
}
activePaints->add(opaquePaint);
SK_AT_SCOPE_EXIT(activePaints->remove(opaquePaint));
FT_COLR_Paint paint;
if (!FT_Get_Paint(face, opaquePaint, &paint)) {
return false;
}
SkMatrix restoreMatrix = *ctm;
SK_AT_SCOPE_EXIT(*ctm = restoreMatrix);
switch (paint.format) {
case FT_COLR_PAINTFORMAT_COLR_LAYERS: {
FT_LayerIterator& layerIterator = paint.u.colr_layers.layer_iterator;
FT_OpaquePaint layerPaint{nullptr, 1};
while (FT_Get_Paint_Layers(face, &layerIterator, &layerPaint)) {
if (!colrv1_traverse_paint_bounds(ctm, bounds, face, layerPaint, activePaints)) {
return false;
}
}
return true;
}
case FT_COLR_PAINTFORMAT_GLYPH: {
FT_UInt glyphID = paint.u.glyph.glyphID;
SkPath path;
if (!generateFacePathCOLRv1(face, glyphID, &path)) {
return false;
}
path.transform(*ctm);
bounds->join(path.getBounds());
return true;
}
case FT_COLR_PAINTFORMAT_COLR_GLYPH: {
FT_UInt glyphID = paint.u.colr_glyph.glyphID;
return colrv1_start_glyph_bounds(ctm, bounds, face, glyphID, FT_COLOR_NO_ROOT_TRANSFORM,
activePaints);
}
case FT_COLR_PAINTFORMAT_TRANSFORM: {
SkMatrix transformMatrix;
colrv1_transform(face, paint, nullptr, &transformMatrix);
ctm->preConcat(transformMatrix);
FT_OpaquePaint& transformPaint = paint.u.transform.paint;
return colrv1_traverse_paint_bounds(ctm, bounds, face, transformPaint, activePaints);
}
case FT_COLR_PAINTFORMAT_TRANSLATE: {
SkMatrix transformMatrix;
colrv1_transform(face, paint, nullptr, &transformMatrix);
ctm->preConcat(transformMatrix);
FT_OpaquePaint& translatePaint = paint.u.translate.paint;
return colrv1_traverse_paint_bounds(ctm, bounds, face, translatePaint, activePaints);
}
case FT_COLR_PAINTFORMAT_SCALE: {
SkMatrix transformMatrix;
colrv1_transform(face, paint, nullptr, &transformMatrix);
ctm->preConcat(transformMatrix);
FT_OpaquePaint& scalePaint = paint.u.scale.paint;
return colrv1_traverse_paint_bounds(ctm, bounds, face, scalePaint, activePaints);
}
case FT_COLR_PAINTFORMAT_ROTATE: {
SkMatrix transformMatrix;
colrv1_transform(face, paint, nullptr, &transformMatrix);
ctm->preConcat(transformMatrix);
FT_OpaquePaint& rotatePaint = paint.u.rotate.paint;
return colrv1_traverse_paint_bounds(ctm, bounds, face, rotatePaint, activePaints);
}
case FT_COLR_PAINTFORMAT_SKEW: {
SkMatrix transformMatrix;
colrv1_transform(face, paint, nullptr, &transformMatrix);
ctm->preConcat(transformMatrix);
FT_OpaquePaint& skewPaint = paint.u.skew.paint;
return colrv1_traverse_paint_bounds(ctm, bounds, face, skewPaint, activePaints);
}
case FT_COLR_PAINTFORMAT_COMPOSITE: {
FT_OpaquePaint& backdropPaint = paint.u.composite.backdrop_paint;
FT_OpaquePaint& sourcePaint = paint.u.composite. source_paint;
return colrv1_traverse_paint_bounds(ctm, bounds, face, backdropPaint, activePaints) &&
colrv1_traverse_paint_bounds(ctm, bounds, face, sourcePaint, activePaints);
}
case FT_COLR_PAINTFORMAT_SOLID:
case FT_COLR_PAINTFORMAT_LINEAR_GRADIENT:
case FT_COLR_PAINTFORMAT_RADIAL_GRADIENT:
case FT_COLR_PAINTFORMAT_SWEEP_GRADIENT: {
return true;
}
default:
SkASSERT(false);
return false;
}
SkUNREACHABLE;
}
bool colrv1_start_glyph_bounds(SkMatrix *ctm,
SkRect* bounds,
FT_Face face,
uint16_t glyphId,
FT_Color_Root_Transform rootTransform,
VisitedSet* activePaints) {
FT_OpaquePaint opaquePaint{nullptr, 1};
return FT_Get_Color_Glyph_Paint(face, glyphId, rootTransform, &opaquePaint) &&
colrv1_traverse_paint_bounds(ctm, bounds, face, opaquePaint, activePaints);
}
#endif // TT_SUPPORT_COLRV1
} // namespace
#ifdef TT_SUPPORT_COLRV1
bool SkScalerContext_FreeType_Base::drawCOLRv1Glyph(FT_Face face,
const SkGlyph& glyph,
uint32_t loadGlyphFlags,
SkSpan<SkColor> palette,
SkCanvas* canvas) {
if (this->isSubpixel()) {
canvas->translate(SkFixedToScalar(glyph.getSubXFixed()),
SkFixedToScalar(glyph.getSubYFixed()));
}
VisitedSet activePaints;
bool haveLayers = colrv1_start_glyph(canvas, palette,
fRec.fForegroundColor,
face, glyph.getGlyphID(),
FT_COLOR_INCLUDE_ROOT_TRANSFORM,
&activePaints);
SkASSERTF(haveLayers, "Could not get COLRv1 layers from '%s'.", face->family_name);
return haveLayers;
}
#endif // TT_SUPPORT_COLRV1
#ifdef FT_COLOR_H
bool SkScalerContext_FreeType_Base::drawCOLRv0Glyph(FT_Face face,
const SkGlyph& glyph,
uint32_t loadGlyphFlags,
SkSpan<SkColor> palette,
SkCanvas* canvas) {
if (this->isSubpixel()) {
canvas->translate(SkFixedToScalar(glyph.getSubXFixed()),
SkFixedToScalar(glyph.getSubYFixed()));
}
bool haveLayers = false;
FT_LayerIterator layerIterator;
layerIterator.p = nullptr;
FT_UInt layerGlyphIndex = 0;
FT_UInt layerColorIndex = 0;
SkPaint paint;
paint.setAntiAlias(!(loadGlyphFlags & FT_LOAD_TARGET_MONO));
while (FT_Get_Color_Glyph_Layer(face, glyph.getGlyphID(), &layerGlyphIndex,
&layerColorIndex, &layerIterator)) {
haveLayers = true;
if (layerColorIndex == 0xFFFF) {
paint.setColor(fRec.fForegroundColor);
} else {
paint.setColor(palette[layerColorIndex]);
}
SkPath path;
if (this->generateFacePath(face, layerGlyphIndex, loadGlyphFlags, &path)) {
canvas->drawPath(path, paint);
}
}
SkASSERTF(haveLayers, "Could not get COLRv0 layers from '%s'.", face->family_name);
return haveLayers;
}
#endif // FT_COLOR_H
#if defined(FT_CONFIG_OPTION_SVG)
bool SkScalerContext_FreeType_Base::drawSVGGlyph(FT_Face face,
const SkGlyph& glyph,
uint32_t loadGlyphFlags,
SkSpan<SkColor> palette,
SkCanvas* canvas) {
SkASSERT(face->glyph->format == FT_GLYPH_FORMAT_SVG);
FT_SVG_Document ftSvg = (FT_SVG_Document)face->glyph->other;
SkMatrix m;
FT_Matrix ftMatrix = ftSvg->transform;
FT_Vector ftOffset = ftSvg->delta;
m.setAll(
SkFixedToFloat(ftMatrix.xx), -SkFixedToFloat(ftMatrix.xy), SkFixedToFloat(ftOffset.x),
-SkFixedToFloat(ftMatrix.yx), SkFixedToFloat(ftMatrix.yy), -SkFixedToFloat(ftOffset.y),
0 , 0 , 1 );
m.postScale(SkFixedToFloat(ftSvg->metrics.x_scale) / 64.0f,
SkFixedToFloat(ftSvg->metrics.y_scale) / 64.0f);
if (this->isSubpixel()) {
m.postTranslate(SkFixedToScalar(glyph.getSubXFixed()),
SkFixedToScalar(glyph.getSubYFixed()));
}
canvas->concat(m);
SkGraphics::OpenTypeSVGDecoderFactory svgFactory = SkGraphics::GetOpenTypeSVGDecoderFactory();
if (!svgFactory) {
return false;
}
auto svgDecoder = svgFactory(ftSvg->svg_document, ftSvg->svg_document_length);
if (!svgDecoder) {
return false;
}
return svgDecoder->render(*canvas, ftSvg->units_per_EM, glyph.getGlyphID(),
fRec.fForegroundColor, palette);
}
#endif // FT_CONFIG_OPTION_SVG
void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face,
const SkGlyph& glyph,
const SkMatrix& bitmapTransform)
{
switch ( face->glyph->format ) {
case FT_GLYPH_FORMAT_OUTLINE: {
FT_Outline* outline = &face->glyph->outline;
int dx = 0, dy = 0;
if (this->isSubpixel()) {
dx = SkFixedToFDot6(glyph.getSubXFixed());
dy = SkFixedToFDot6(glyph.getSubYFixed());
// negate dy since freetype-y-goes-up and skia-y-goes-down
dy = -dy;
}
memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight);
if (SkMask::kLCD16_Format == glyph.fMaskFormat) {
const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag);
const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag);
FT_Outline_Translate(outline, dx, dy);
FT_Error err = FT_Render_Glyph(face->glyph, doVert ? FT_RENDER_MODE_LCD_V :
FT_RENDER_MODE_LCD);
if (err) {
SK_TRACEFTR(err, "Could not render glyph %p.", face->glyph);
return;
}
SkMask mask = glyph.mask();
if constexpr (kSkShowTextBlitCoverage) {
memset(mask.fImage, 0x80, mask.fBounds.height() * mask.fRowBytes);
}
FT_GlyphSlotRec& ftGlyph = *face->glyph;
if (!SkIRect::Intersects(mask.fBounds,
SkIRect::MakeXYWH( ftGlyph.bitmap_left,
-ftGlyph.bitmap_top,
ftGlyph.bitmap.width,
ftGlyph.bitmap.rows)))
{
return;
}
// If the FT_Bitmap extent is larger, discard bits of the bitmap outside the mask.
// If the SkMask extent is larger, shrink mask to fit bitmap (clearing discarded).
unsigned char* origBuffer = ftGlyph.bitmap.buffer;
// First align the top left (origin).
if (-ftGlyph.bitmap_top < mask.fBounds.fTop) {
int32_t topDiff = mask.fBounds.fTop - (-ftGlyph.bitmap_top);
ftGlyph.bitmap.buffer += ftGlyph.bitmap.pitch * topDiff;
ftGlyph.bitmap.rows -= topDiff;
ftGlyph.bitmap_top = -mask.fBounds.fTop;
}
if (ftGlyph.bitmap_left < mask.fBounds.fLeft) {
int32_t leftDiff = mask.fBounds.fLeft - ftGlyph.bitmap_left;
ftGlyph.bitmap.buffer += leftDiff;
ftGlyph.bitmap.width -= leftDiff;
ftGlyph.bitmap_left = mask.fBounds.fLeft;
}
if (mask.fBounds.fTop < -ftGlyph.bitmap_top) {
mask.fImage += mask.fRowBytes * (-ftGlyph.bitmap_top - mask.fBounds.fTop);
mask.fBounds.fTop = -ftGlyph.bitmap_top;
}
if (mask.fBounds.fLeft < ftGlyph.bitmap_left) {
mask.fImage += sizeof(uint16_t) * (ftGlyph.bitmap_left - mask.fBounds.fLeft);
mask.fBounds.fLeft = ftGlyph.bitmap_left;
}
// Origins aligned, clean up the width and height.
int ftVertScale = (doVert ? 3 : 1);
int ftHoriScale = (doVert ? 1 : 3);
if (mask.fBounds.height() * ftVertScale < SkToInt(ftGlyph.bitmap.rows)) {
ftGlyph.bitmap.rows = mask.fBounds.height() * ftVertScale;
}
if (mask.fBounds.width() * ftHoriScale < SkToInt(ftGlyph.bitmap.width)) {
ftGlyph.bitmap.width = mask.fBounds.width() * ftHoriScale;
}
if (SkToInt(ftGlyph.bitmap.rows) < mask.fBounds.height() * ftVertScale) {
mask.fBounds.fBottom = mask.fBounds.fTop + ftGlyph.bitmap.rows / ftVertScale;
}
if (SkToInt(ftGlyph.bitmap.width) < mask.fBounds.width() * ftHoriScale) {
mask.fBounds.fRight = mask.fBounds.fLeft + ftGlyph.bitmap.width / ftHoriScale;
}
if (fPreBlend.isApplicable()) {
copyFT2LCD16<true>(ftGlyph.bitmap, mask, doBGR,
fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
} else {
copyFT2LCD16<false>(ftGlyph.bitmap, mask, doBGR,
fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
}
// Restore the buffer pointer so FreeType can properly free it.
ftGlyph.bitmap.buffer = origBuffer;
} else {
FT_BBox bbox;
FT_Bitmap target;
FT_Outline_Get_CBox(outline, &bbox);
/*
what we really want to do for subpixel is
offset(dx, dy)
compute_bounds
offset(bbox & !63)
but that is two calls to offset, so we do the following, which
achieves the same thing with only one offset call.
*/
FT_Outline_Translate(outline, dx - ((bbox.xMin + dx) & ~63),
dy - ((bbox.yMin + dy) & ~63));
target.width = glyph.fWidth;
target.rows = glyph.fHeight;
target.pitch = glyph.rowBytes();
target.buffer = reinterpret_cast<uint8_t*>(glyph.fImage);
target.pixel_mode = compute_pixel_mode(glyph.fMaskFormat);
target.num_grays = 256;
FT_Outline_Get_Bitmap(face->glyph->library, outline, &target);
if constexpr (kSkShowTextBlitCoverage) {
if (glyph.fMaskFormat == SkMask::kBW_Format) {
for (unsigned y = 0; y < target.rows; y += 2) {
for (unsigned x = (y & 0x2); x < target.width; x+=4) {
uint8_t& b = target.buffer[(target.pitch * y) + (x >> 3)];
b = b ^ (1 << (0x7 - (x & 0x7)));
}
}
} else {
for (unsigned y = 0; y < target.rows; ++y) {
for (unsigned x = 0; x < target.width; ++x) {
uint8_t& a = target.buffer[(target.pitch * y) + x];
a = std::max<uint8_t>(a, 0x20);
}
}
}
}
}
} break;
case FT_GLYPH_FORMAT_BITMAP: {
FT_Pixel_Mode pixel_mode = static_cast<FT_Pixel_Mode>(face->glyph->bitmap.pixel_mode);
SkMask::Format maskFormat = static_cast<SkMask::Format>(glyph.fMaskFormat);
// Assume that the other formats do not exist.
SkASSERT(FT_PIXEL_MODE_MONO == pixel_mode ||
FT_PIXEL_MODE_GRAY == pixel_mode ||
FT_PIXEL_MODE_BGRA == pixel_mode);
// These are the only formats this ScalerContext should request.
SkASSERT(SkMask::kBW_Format == maskFormat ||
SkMask::kA8_Format == maskFormat ||
SkMask::kARGB32_Format == maskFormat ||
SkMask::kLCD16_Format == maskFormat);
// If no scaling needed, directly copy glyph bitmap.
if (bitmapTransform.isIdentity()) {
SkMask dstMask = glyph.mask();
copyFTBitmap(face->glyph->bitmap, dstMask);
break;
}
// Otherwise, scale the bitmap.
// Copy the FT_Bitmap into an SkBitmap (either A8 or ARGB)
SkBitmap unscaledBitmap;
// TODO: mark this as sRGB when the blits will be sRGB.
unscaledBitmap.setInfo(SkImageInfo::Make(face->glyph->bitmap.width,
face->glyph->bitmap.rows,
SkColorType_for_FTPixelMode(pixel_mode),
kPremul_SkAlphaType));
if (!unscaledBitmap.tryAllocPixels()) {
// TODO: set the fImage to indicate "missing"
memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight);
return;
}
SkMask unscaledBitmapAlias;
unscaledBitmapAlias.fImage = reinterpret_cast<uint8_t*>(unscaledBitmap.getPixels());
unscaledBitmapAlias.fBounds.setWH(unscaledBitmap.width(), unscaledBitmap.height());
unscaledBitmapAlias.fRowBytes = unscaledBitmap.rowBytes();
unscaledBitmapAlias.fFormat = SkMaskFormat_for_SkColorType(unscaledBitmap.colorType());
copyFTBitmap(face->glyph->bitmap, unscaledBitmapAlias);
// Wrap the glyph's mask in a bitmap, unless the glyph's mask is BW or LCD.
// BW requires an A8 target for resizing, which can then be down sampled.
// LCD should use a 4x A8 target, which will then be down sampled.
// For simplicity, LCD uses A8 and is replicated.
int bitmapRowBytes = 0;
if (SkMask::kBW_Format != maskFormat && SkMask::kLCD16_Format != maskFormat) {
bitmapRowBytes = glyph.rowBytes();
}
SkBitmap dstBitmap;
// TODO: mark this as sRGB when the blits will be sRGB.
dstBitmap.setInfo(SkImageInfo::Make(glyph.fWidth, glyph.fHeight,
SkColorType_for_SkMaskFormat(maskFormat),
kPremul_SkAlphaType),
bitmapRowBytes);
if (SkMask::kBW_Format == maskFormat || SkMask::kLCD16_Format == maskFormat) {
if (!dstBitmap.tryAllocPixels()) {
// TODO: set the fImage to indicate "missing"
memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight);
return;
}
} else {
dstBitmap.setPixels(glyph.fImage);
}
// Scale unscaledBitmap into dstBitmap.
SkCanvas canvas(dstBitmap);
if constexpr (kSkShowTextBlitCoverage) {
canvas.clear(0x33FF0000);
} else {
canvas.clear(SK_ColorTRANSPARENT);
}
canvas.translate(-glyph.fLeft, -glyph.fTop);
canvas.concat(bitmapTransform);
canvas.translate(face->glyph->bitmap_left, -face->glyph->bitmap_top);
SkSamplingOptions sampling(SkFilterMode::kLinear, SkMipmapMode::kNearest);
canvas.drawImage(unscaledBitmap.asImage().get(), 0, 0, sampling, nullptr);
// If the destination is BW or LCD, convert from A8.
if (SkMask::kBW_Format == maskFormat) {
// Copy the A8 dstBitmap into the A1 glyph.fImage.
SkMask dstMask = glyph.mask();
packA8ToA1(dstMask, dstBitmap.getAddr8(0, 0), dstBitmap.rowBytes());
} else if (SkMask::kLCD16_Format == maskFormat) {
// Copy the A8 dstBitmap into the LCD16 glyph.fImage.
uint8_t* src = dstBitmap.getAddr8(0, 0);
uint16_t* dst = reinterpret_cast<uint16_t*>(glyph.fImage);
for (int y = dstBitmap.height(); y --> 0;) {
for (int x = 0; x < dstBitmap.width(); ++x) {
dst[x] = grayToRGB16(src[x]);
}
dst = (uint16_t*)((char*)dst + glyph.rowBytes());
src += dstBitmap.rowBytes();
}
}
} break;
default:
SkDEBUGFAIL("unknown glyph format");
memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight);
return;
}
// We used to always do this pre-USE_COLOR_LUMINANCE, but with colorlum,
// it is optional
#if defined(SK_GAMMA_APPLY_TO_A8)
if (SkMask::kA8_Format == glyph.fMaskFormat && fPreBlend.isApplicable()) {
uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage;
unsigned rowBytes = glyph.rowBytes();
for (int y = glyph.fHeight - 1; y >= 0; --y) {
for (int x = glyph.fWidth - 1; x >= 0; --x) {
dst[x] = fPreBlend.fG[dst[x]];
}
dst += rowBytes;
}
}
#endif
}
///////////////////////////////////////////////////////////////////////////////
namespace {
class SkFTGeometrySink {
SkPath* fPath;
bool fStarted;
FT_Vector fCurrent;
void goingTo(const FT_Vector* pt) {
if (!fStarted) {
fStarted = true;
fPath->moveTo(SkFDot6ToScalar(fCurrent.x), -SkFDot6ToScalar(fCurrent.y));
}
fCurrent = *pt;
}
bool currentIsNot(const FT_Vector* pt) {
return fCurrent.x != pt->x || fCurrent.y != pt->y;
}
static int Move(const FT_Vector* pt, void* ctx) {
SkFTGeometrySink& self = *(SkFTGeometrySink*)ctx;
if (self.fStarted) {
self.fPath->close();
self.fStarted = false;
}
self.fCurrent = *pt;
return 0;
}
static int Line(const FT_Vector* pt, void* ctx) {
SkFTGeometrySink& self = *(SkFTGeometrySink*)ctx;
if (self.currentIsNot(pt)) {
self.goingTo(pt);
self.fPath->lineTo(SkFDot6ToScalar(pt->x), -SkFDot6ToScalar(pt->y));
}
return 0;
}
static int Quad(const FT_Vector* pt0, const FT_Vector* pt1, void* ctx) {
SkFTGeometrySink& self = *(SkFTGeometrySink*)ctx;
if (self.currentIsNot(pt0) || self.currentIsNot(pt1)) {
self.goingTo(pt1);
self.fPath->quadTo(SkFDot6ToScalar(pt0->x), -SkFDot6ToScalar(pt0->y),
SkFDot6ToScalar(pt1->x), -SkFDot6ToScalar(pt1->y));
}
return 0;
}
static int Cubic(const FT_Vector* pt0, const FT_Vector* pt1, const FT_Vector* pt2, void* ctx) {
SkFTGeometrySink& self = *(SkFTGeometrySink*)ctx;
if (self.currentIsNot(pt0) || self.currentIsNot(pt1) || self.currentIsNot(pt2)) {
self.goingTo(pt2);
self.fPath->cubicTo(SkFDot6ToScalar(pt0->x), -SkFDot6ToScalar(pt0->y),
SkFDot6ToScalar(pt1->x), -SkFDot6ToScalar(pt1->y),
SkFDot6ToScalar(pt2->x), -SkFDot6ToScalar(pt2->y));
}
return 0;
}
public:
SkFTGeometrySink(SkPath* path) : fPath{path}, fStarted{false}, fCurrent{0,0} {}
inline static constexpr const FT_Outline_Funcs Funcs{
/*move_to =*/ SkFTGeometrySink::Move,
/*line_to =*/ SkFTGeometrySink::Line,
/*conic_to =*/ SkFTGeometrySink::Quad,
/*cubic_to =*/ SkFTGeometrySink::Cubic,
/*shift = */ 0,
/*delta =*/ 0,
};
};
bool generateGlyphPathStatic(FT_Face face, SkPath* path) {
SkFTGeometrySink sink{path};
if (face->glyph->format != FT_GLYPH_FORMAT_OUTLINE ||
FT_Outline_Decompose(&face->glyph->outline, &SkFTGeometrySink::Funcs, &sink))
{
path->reset();
return false;
}
path->close();
return true;
}
bool generateFacePathStatic(FT_Face face, SkGlyphID glyphID, uint32_t loadGlyphFlags, SkPath* path){
loadGlyphFlags |= FT_LOAD_BITMAP_METRICS_ONLY; // Don't decode any bitmaps.
loadGlyphFlags |= FT_LOAD_NO_BITMAP; // Ignore embedded bitmaps.
loadGlyphFlags &= ~FT_LOAD_RENDER; // Don't scan convert.
loadGlyphFlags &= ~FT_LOAD_COLOR; // Ignore SVG.
if (FT_Load_Glyph(face, glyphID, loadGlyphFlags)) {
path->reset();
return false;
}
return generateGlyphPathStatic(face, path);
}
#ifdef TT_SUPPORT_COLRV1
bool generateFacePathCOLRv1(FT_Face face, SkGlyphID glyphID, SkPath* path) {
uint32_t flags = 0;
flags |= FT_LOAD_BITMAP_METRICS_ONLY; // Don't decode any bitmaps.
flags |= FT_LOAD_NO_BITMAP; // Ignore embedded bitmaps.
flags &= ~FT_LOAD_RENDER; // Don't scan convert.
flags &= ~FT_LOAD_COLOR; // Ignore SVG.
flags |= FT_LOAD_NO_HINTING;
flags |= FT_LOAD_NO_AUTOHINT;
flags |= FT_LOAD_IGNORE_TRANSFORM;
using DoneFTSize = SkFunctionWrapper<decltype(FT_Done_Size), FT_Done_Size>;
std::unique_ptr<std::remove_pointer_t<FT_Size>, DoneFTSize> unscaledFtSize([face]() -> FT_Size {
FT_Size size;
FT_Error err = FT_New_Size(face, &size);
if (err != 0) {
SK_TRACEFTR(err, "FT_New_Size(%s) failed in generateFacePathStaticCOLRv1.",
face->family_name);
return nullptr;
}
return size;
}());
if (!unscaledFtSize) {
return false;
}
FT_Size oldSize = face->size;
auto tryGeneratePath = [face, &unscaledFtSize, glyphID, flags, path]() {
FT_Error err = 0;
err = FT_Activate_Size(unscaledFtSize.get());
if (err != 0) {
return false;
}
err = FT_Set_Char_Size(face, SkIntToFDot6(face->units_per_EM),
SkIntToFDot6(face->units_per_EM), 72, 72);
if (err != 0) {
return false;
}
err = FT_Load_Glyph(face, glyphID, flags);
if (err != 0) {
path->reset();
return false;
}
if (!generateGlyphPathStatic(face, path)) {
path->reset();
return false;
}
return true;
};
bool pathGenerationResult = tryGeneratePath();
FT_Activate_Size(oldSize);
return pathGenerationResult;
}
#endif
} // namespace
bool SkScalerContext_FreeType_Base::generateGlyphPath(FT_Face face, SkPath* path) {
if (!generateGlyphPathStatic(face, path)) {
return false;
}
if (face->glyph->outline.flags & FT_OUTLINE_OVERLAP) {
Simplify(*path, path);
// Simplify will return an even-odd path.
// A stroke+fill (for fake bold) may be incorrect for even-odd.
// https://github.com/flutter/flutter/issues/112546
AsWinding(*path, path);
}
return true;
}
bool SkScalerContext_FreeType_Base::generateFacePath(FT_Face face,
SkGlyphID glyphID,
uint32_t loadGlyphFlags,
SkPath* path) {
return generateFacePathStatic(face, glyphID, loadGlyphFlags, path);
}
#ifdef TT_SUPPORT_COLRV1
bool SkScalerContext_FreeType_Base::computeColrV1GlyphBoundingBox(FT_Face face,
SkGlyphID glyphID,
SkRect* bounds) {
SkMatrix ctm;
*bounds = SkRect::MakeEmpty();
VisitedSet activePaints;
return colrv1_start_glyph_bounds(&ctm, bounds, face, glyphID,
FT_COLOR_INCLUDE_ROOT_TRANSFORM, &activePaints);
}
#endif