/* * Copyright 2006 The Android Open Source Project * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file.
*/
// CT on (at least) 10.9 will size color glyphs down from the requested size, but not up. // As a result, it is necessary to know the actual device size and request that.
SkVector scale;
SkMatrix skTransform; bool invertible = fRec.computeMatrices(SkScalerContextRec::PreMatrixScale::kVertical,
&scale, &skTransform, nullptr, nullptr, nullptr);
fTransform = MatrixToCGAffineTransform(skTransform); // CGAffineTransformInvert documents that if the transform is non-invertible it will return the // passed transform unchanged. It does so, but then also prints a message to stdout. Avoid this. if (invertible) {
fInvTransform = CGAffineTransformInvert(fTransform);
} else {
fInvTransform = fTransform;
}
// The transform contains everything except the requested text size. // Some properties, like 'trak', are based on the optical text size.
CGFloat textSize = SkScalarToCGFloat(scale.y());
fCTFont = SkCTFontCreateExactCopy(ctFont, textSize,
((SkTypeface_Mac*)this->getTypeface())->fOpszVariation);
fCGFont.reset(CTFontCopyGraphicsFont(fCTFont.get(), nullptr));
}
static CGColorRef CGColorForSkColor(CGColorSpaceRef rgbcs, SkColor bgra) {
CGFloat components[4];
components[0] = (CGFloat)SkColorGetR(bgra) * (1/255.0f);
components[1] = (CGFloat)SkColorGetG(bgra) * (1/255.0f);
components[2] = (CGFloat)SkColorGetB(bgra) * (1/255.0f); // CoreText applies the CGContext fill color as the COLR foreground color. // However, the alpha is applied to the whole glyph drawing (and Skia will do that as well). // For now, cannot really support COLR foreground color alpha.
components[3] = 1.0f; return CGColorCreate(rgbcs, components);
}
CGRGBPixel* SkScalerContext_Mac::Offscreen::getCG(const SkScalerContext_Mac& context, const SkGlyph& glyph, CGGlyph glyphID,
size_t* rowBytesPtr, bool generateA8FromLCD) { if (!fRGBSpace) { //It doesn't appear to matter what color space is specified. //Regular blends and antialiased text are always (s*a + d*(1-a)) //and subpixel antialiased text is always g=2.0.
fRGBSpace.reset(CGColorSpaceCreateDeviceRGB());
fCGForegroundColor.reset(CGColorForSkColor(fRGBSpace.get(), fSKForegroundColor));
}
// If this font might have color glyphs, disable LCD as there's no way to support it. // CoreText doesn't tell us which format it ended up using, so we can't detect it. // A8 will end up black on transparent, but TODO: we can detect gray and set to A8. if (SkMask::kARGB32_Format == glyph.maskFormat()) {
doLCD = false;
}
// Skia handles quantization and subpixel positioning, // so disable quantization and enable subpixel positioning in CG.
CGContextSetAllowsFontSubpixelQuantization(fCG.get(), false);
CGContextSetShouldSubpixelQuantizeFonts(fCG.get(), false);
// Because CG always draws from the horizontal baseline, // if there is a non-integral translation from the horizontal origin to the vertical origin, // then CG cannot draw the glyph in the correct location without subpixel positioning.
CGContextSetAllowsFontSubpixelPositioning(fCG.get(), true);
CGContextSetShouldSubpixelPositionFonts(fCG.get(), true);
if (SkMask::kARGB32_Format != glyph.maskFormat()) { // Draw black on white to create mask. (Special path exists to speed this up in CG.)
CGContextSetGrayFillColor(fCG.get(), 0.0f, 1.0f);
} else {
CGContextSetFillColorWithColor(fCG.get(), fCGForegroundColor.get());
}
// force our checks below to happen
fDoAA = !doAA;
fDoLCD = !doLCD;
CGRGBPixel* image = (CGRGBPixel*)fImageStorage.get(); // skip rows based on the glyph's height
image += (fSize.fHeight - glyph.height()) * fSize.fWidth;
// Erase to white (or transparent black if it's a color glyph, to not composite against white). // For light-on-dark, instead erase to black.
uint32_t bgColor = (!glyph.isColor()) ? 0xFFFFFFFF : 0x00000000;
sk_memset_rect32(image, bgColor, glyph.width(), glyph.height(), rowBytes);
CGPoint point = CGPointMake(-glyph.left() + subX, glyph.top() + glyph.height() - subY); // Prior to 10.10, CTFontDrawGlyphs acted like CGContextShowGlyphsAtPositions and took // 'positions' which are in text space. The glyph location (in device space) must be // mapped into text space, so that CG can convert it back into device space. // In 10.10.1, this is handled directly in CTFontDrawGlyphs. // // However, in 10.10.2 color glyphs no longer rotate based on the font transform. // So always make the font transform identity and place the transform on the context.
point = CGPointApplyAffineTransform(point, context.fInvTransform);
// The following block produces cgAdvance in CG units (pixels, y up).
CGSize cgAdvance;
CTFontGetAdvancesForGlyphs(fCTFont.get(), kCTFontOrientationHorizontal,
&cgGlyph, &cgAdvance, 1);
cgAdvance = CGSizeApplyAffineTransform(cgAdvance, fTransform);
mx.advance.fX = SkFloatFromCGFloat(cgAdvance.width);
mx.advance.fY = -SkFloatFromCGFloat(cgAdvance.height);
// The following produces skBounds in SkGlyph units (pixels, y down), // or returns early if skBounds would be empty.
SkRect skBounds;
// Glyphs are always drawn from the horizontal origin. The caller must manually use the result // of CTFontGetVerticalTranslationsForGlyphs to calculate where to draw the glyph for vertical // glyphs. As a result, always get the horizontal bounds of a glyph and translate it if the // glyph is vertical. This avoids any diagreement between the various means of retrieving // vertical metrics.
{ // CTFontGetBoundingRectsForGlyphs produces cgBounds in CG units (pixels, y up).
CGRect cgBounds;
CTFontGetBoundingRectsForGlyphs(fCTFont.get(), kCTFontOrientationHorizontal,
&cgGlyph, &cgBounds, 1);
cgBounds = CGRectApplyAffineTransform(cgBounds, fTransform);
// BUG? // 0x200B (zero-advance space) seems to return a huge (garbage) bounds, when // it should be empty. So, if we see a zero-advance, we check if it has an // empty path or not, and if so, we jam the bounds to 0. Hopefully a zero-advance // is rare, so we won't incur a big performance cost for this extra check. // Avoid trying to create a path from a color font due to crashing on 10.9. if (0 == cgAdvance.width && 0 == cgAdvance.height &&
SkMask::kARGB32_Format != glyph.maskFormat()) {
SkUniqueCFRef<CGPathRef> path(CTFontCreatePathForGlyph(fCTFont.get(), cgGlyph,nullptr)); if (!path || CGPathIsEmpty(path.get())) { return mx;
}
}
if (SkCGRectIsEmpty(cgBounds)) { return mx;
}
// Convert cgBounds to SkGlyph units (pixels, y down).
skBounds = SkRect::MakeXYWH(cgBounds.origin.x, -cgBounds.origin.y - cgBounds.size.height,
cgBounds.size.width, cgBounds.size.height);
}
// Currently the bounds are based on being rendered at (0,0). // The top left must not move, since that is the base from which subpixel positioning is offset. if (fDoSubPosition) {
skBounds.fRight += SkFixedToFloat(glyph.getSubXFixed());
skBounds.fBottom += SkFixedToFloat(glyph.getSubYFixed());
}
skBounds.roundOut(&mx.bounds); // Expand the bounds by 1 pixel, to give CG room for anti-aliasing. // Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset // is not currently known, as CG dilates the outlines by some percentage. // Note that if this context is A8 and not back-forming from LCD, there is no need to outset.
mx.bounds.outset(1, 1); return mx;
}
/** * This will invert the gamma applied by CoreGraphics, so we can get linear * values. * * CoreGraphics obscurely defaults to 2.0 as the subpixel coverage gamma value. * The color space used does not appear to affect this choice.
*/ static constexpr auto gLinearCoverageFromCGLCDValue = SkMakeArray<256>(sk_pow2_table);
staticvoid cgpixels_to_bits(uint8_t dst[], const CGRGBPixel src[], int count) { while (count > 0) {
uint8_t mask = 0; for (int i = 7; i >= 0; --i) {
mask |= ((CGRGBPixel_getAlpha(*src++) >> 7) ^ 0x1) << i; if (0 == --count) { break;
}
}
*dst++ = mask;
}
}
// Fix the glyph if ((glyph.maskFormat() == SkMask::kLCD16_Format) ||
(glyph.maskFormat() == SkMask::kA8_Format
&& requestSmooth
&& SkCTFontGetSmoothBehavior() != SkCTFontSmoothBehavior::none))
{ const uint8_t* linear = gLinearCoverageFromCGLCDValue.data();
//Note that the following cannot really be integrated into the //pre-blend, since we may not be applying the pre-blend; when we aren't //applying the pre-blend it means that a filter wants linear anyway. //Other code may also be applying the pre-blend, so we'd need another //one with this and one without.
CGRGBPixel* addr = cgPixels; for (int y = 0; y < glyph.height(); ++y) { for (int x = 0; x < glyph.width(); ++x) { int r = (addr[x] >> 16) & 0xFF; int g = (addr[x] >> 8) & 0xFF; int b = (addr[x] >> 0) & 0xFF;
addr[x] = (linear[r] << 16) | (linear[g] << 8) | linear[b];
}
addr = SkTAddOffset<CGRGBPixel>(addr, cgRowBytes);
}
}
// Convert glyph to mask switch (glyph.maskFormat()) { case SkMask::kLCD16_Format: { if (fPreBlend.isApplicable()) {
RGBToLcd16<true>(cgPixels, cgRowBytes, glyph, imageBuffer,
fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
} else {
RGBToLcd16<false>(cgPixels, cgRowBytes, glyph, imageBuffer,
fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
}
} break; case SkMask::kA8_Format: { if (fPreBlend.isApplicable()) {
RGBToA8<true>(cgPixels, cgRowBytes, glyph, imageBuffer, fPreBlend.fG);
} else {
RGBToA8<false>(cgPixels, cgRowBytes, glyph, imageBuffer, fPreBlend.fG);
}
} break; case SkMask::kBW_Format: { constint width = glyph.width();
size_t dstRB = glyph.rowBytes();
uint8_t* dst = (uint8_t*)imageBuffer; for (int y = 0; y < glyph.height(); y++) {
cgpixels_to_bits(dst, cgPixels, width);
cgPixels = SkTAddOffset<CGRGBPixel>(cgPixels, cgRowBytes);
dst = SkTAddOffset<uint8_t>(dst, dstRB);
}
} break; case SkMask::kARGB32_Format: { constint width = glyph.width();
size_t dstRB = glyph.rowBytes();
SkPMColor* dst = (SkPMColor*)imageBuffer; for (int y = 0; y < glyph.height(); y++) { for (int x = 0; x < width; ++x) {
dst[x] = cgpixels_to_pmcolor(cgPixels[x]);
}
cgPixels = SkTAddOffset<CGRGBPixel>(cgPixels, cgRowBytes);
dst = SkTAddOffset<SkPMColor>(dst, dstRB);
}
} break; default:
SkDEBUGFAIL("unexpected mask format"); break;
}
}
case kCGPathElementAddLineToPoint: if (self.currentIsNot(points[0])) {
self.goingTo(points[0]);
self.fBuilder.lineTo(points[0].x, -points[0].y);
} break;
case kCGPathElementAddQuadCurveToPoint: if (self.currentIsNot(points[0]) || self.currentIsNot(points[1])) {
self.goingTo(points[1]);
self.fBuilder.quadTo(points[0].x, -points[0].y,
points[1].x, -points[1].y);
} break;
case kCGPathElementAddCurveToPoint: if (self.currentIsNot(points[0]) ||
self.currentIsNot(points[1]) ||
self.currentIsNot(points[2]))
{
self.goingTo(points[2]);
self.fBuilder.cubicTo(points[0].x, -points[0].y,
points[1].x, -points[1].y,
points[2].x, -points[2].y);
} break;
case kCGPathElementCloseSubpath: if (self.fStarted) {
self.fBuilder.close();
} break;
/* * Our subpixel resolution is only 2 bits in each direction, so a scale of 4 * seems sufficient, and possibly even correct, to allow the hinted outline * to be subpixel positioned.
*/ #define kScaleForSubPixelPositionHinting (4.0f)
CGAffineTransform xform = fTransform; /* * For subpixel positioning, we want to return an unhinted outline, so it * can be positioned nicely at fractional offsets. However, we special-case * if the baseline of the (horizontal) text is axis-aligned. In those cases * we want to retain hinting in the direction orthogonal to the baseline. * e.g. for horizontal baseline, we want to retain hinting in Y. * The way we remove hinting is to scale the font by some value (4) in that * direction, ask for the path, and then scale the path back down.
*/ if (fDoSubPosition) { // start out by assuming that we want no hining in X and Y
scaleX = scaleY = kScaleForSubPixelPositionHinting; // now see if we need to restore hinting for axis-aligned baselines switch (this->computeAxisAlignmentForHText()) { case SkAxisAlignment::kX:
scaleY = SK_Scalar1; // want hinting in the Y direction break; case SkAxisAlignment::kY:
scaleX = SK_Scalar1; // want hinting in the X direction break; default: break;
}
CFArrayRef ctAxes = ((SkTypeface_Mac*)this->getTypeface())->getVariationAxes(); if ((ctAxes && CFArrayGetCount(ctAxes) > 0) ||
((SkTypeface_Mac*)this->getTypeface())->fHasColorGlyphs)
{ // The bounds are only valid for the default outline variation. // In particular `sbix` and `SVG ` data may draw outside these bounds.
metrics->fFlags |= SkFontMetrics::kBoundsInvalid_Flag;
}
sk_sp<SkData> os2 = this->getTypeface()->copyTableData(SkTEndian_SwapBE32(SkOTTableOS2::TAG)); if (os2) { // 'fontSize' is correct because the entire resolved size is set by the constructor. const CGFloat fontSize = CTFontGetSize(fCTFont.get()); constunsignedint upem = CTFontGetUnitsPerEm(fCTFont.get()); constunsignedint maxSaneHeight = upem * 2;
// See https://bugs.chromium.org/p/skia/issues/detail?id=6203 // At least on 10.12.3 with memory based fonts the x-height is always 0.6666 of the ascent // and the cap-height is always 0.8888 of the ascent. It appears that the values from the // 'OS/2' table are read, but then overwritten if the font is not a system font. As a // result, if there is a valid 'OS/2' table available use the values from the table if they // aren't too strange. if (sizeof(SkOTTableOS2_V2) <= os2->size()) { const SkOTTableOS2_V2* os2v2 = static_cast<const SkOTTableOS2_V2*>(os2->data());
uint16_t xHeight = SkEndian_SwapBE16(os2v2->sxHeight); if (xHeight && xHeight < maxSaneHeight) {
metrics->fXHeight = SkScalarFromCGFloat(xHeight * fontSize / upem);
}
uint16_t capHeight = SkEndian_SwapBE16(os2v2->sCapHeight); if (capHeight && capHeight < maxSaneHeight) {
metrics->fCapHeight = SkScalarFromCGFloat(capHeight * fontSize / upem);
}
}
// CoreText does not provide the strikeout metrics, which are available in OS/2 version 0. if (sizeof(SkOTTableOS2_V0) <= os2->size()) { const SkOTTableOS2_V0* os2v0 = static_cast<const SkOTTableOS2_V0*>(os2->data());
uint16_t strikeoutSize = SkEndian_SwapBE16(os2v0->yStrikeoutSize); if (strikeoutSize && strikeoutSize < maxSaneHeight) {
metrics->fStrikeoutThickness = SkScalarFromCGFloat(strikeoutSize * fontSize / upem);
metrics->fFlags |= SkFontMetrics::kStrikeoutThicknessIsValid_Flag;
}
uint16_t strikeoutPos = SkEndian_SwapBE16(os2v0->yStrikeoutPosition); if (strikeoutPos && strikeoutPos < maxSaneHeight) {
metrics->fStrikeoutPosition = -SkScalarFromCGFloat(strikeoutPos * fontSize / upem);
metrics->fFlags |= SkFontMetrics::kStrikeoutPositionIsValid_Flag;
}
}
}
}
#endif
Messung V0.5
¤ Dauer der Verarbeitung: 0.2 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.