gecko-dev/gfx/2d/CGTextDrawing.h

136 lines
5.5 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef _MOZILLA_GFX_SKIACGPOPUPDRAWER_H
#define _MOZILLA_GFX_SKIACGPOPUPDRAWER_H
#include <ApplicationServices/ApplicationServices.h>
#include "nsDebug.h"
#include "mozilla/Vector.h"
#include "ScaledFontMac.h"
#include <dlfcn.h>
// This is used when we explicitly need CG to draw text to support things such
// as vibrancy and subpixel AA on transparent backgrounds. The current use cases
// are really only to enable Skia to support drawing text in those situations.
namespace mozilla {
namespace gfx {
typedef void (*CGContextSetFontSmoothingBackgroundColorFunc)(CGContextRef cgContext,
CGColorRef color);
static CGContextSetFontSmoothingBackgroundColorFunc
GetCGContextSetFontSmoothingBackgroundColorFunc() {
static CGContextSetFontSmoothingBackgroundColorFunc func = nullptr;
static bool lookedUpFunc = false;
if (!lookedUpFunc) {
func = (CGContextSetFontSmoothingBackgroundColorFunc)dlsym(
RTLD_DEFAULT, "CGContextSetFontSmoothingBackgroundColor");
lookedUpFunc = true;
}
return func;
}
static CGColorRef ColorToCGColor(CGColorSpaceRef aColorSpace, const DeviceColor& aColor) {
CGFloat components[4] = {aColor.r, aColor.g, aColor.b, aColor.a};
return CGColorCreate(aColorSpace, components);
}
static bool SetFontSmoothingBackgroundColor(CGContextRef aCGContext, CGColorSpaceRef aColorSpace,
const DeviceColor& aFontSmoothingBackgroundColor) {
if (aFontSmoothingBackgroundColor.a > 0) {
CGContextSetFontSmoothingBackgroundColorFunc setFontSmoothingBGColorFunc =
GetCGContextSetFontSmoothingBackgroundColorFunc();
if (setFontSmoothingBGColorFunc) {
CGColorRef color = ColorToCGColor(aColorSpace, aFontSmoothingBackgroundColor);
setFontSmoothingBGColorFunc(aCGContext, color);
CGColorRelease(color);
return true;
}
}
return false;
}
// Font rendering with a non-transparent font smoothing background color
// can leave pixels in our buffer where the rgb components exceed the alpha
// component. When this happens we need to clean up the data afterwards.
// The purpose of this is probably the following: Correct compositing of
// subpixel anti-aliased fonts on transparent backgrounds requires
// different alpha values per RGB component. Usually, premultiplied color
// values are derived by multiplying all components with the same per-pixel
// alpha value. However, if you multiply each component with a *different*
// alpha, and set the alpha component of the pixel to, say, the average
// of the alpha values that you used during the premultiplication of the
// RGB components, you can trick OVER compositing into doing a simplified
// form of component alpha compositing. (You just need to make sure to
// clamp the components of the result pixel to [0,255] afterwards.)
static void EnsureValidPremultipliedData(CGContextRef aContext,
CGRect aTextBounds = CGRectInfinite) {
if (CGBitmapContextGetBitsPerPixel(aContext) != 32 ||
CGBitmapContextGetAlphaInfo(aContext) != kCGImageAlphaPremultipliedFirst) {
return;
}
uint8_t* bitmapData = (uint8_t*)CGBitmapContextGetData(aContext);
CGRect bitmapBounds =
CGRectMake(0, 0, CGBitmapContextGetWidth(aContext), CGBitmapContextGetHeight(aContext));
int stride = CGBitmapContextGetBytesPerRow(aContext);
CGRect bounds = CGRectIntersection(bitmapBounds, aTextBounds);
if (CGRectIsEmpty(bounds)) {
return;
}
int startX = bounds.origin.x;
int endX = startX + bounds.size.width;
MOZ_ASSERT(endX <= bitmapBounds.size.width);
// CGRect assume that our origin is the bottom left.
// The data assumes that the origin is the top left.
// Have to switch the Y axis so that our coordinates are correct
int startY = bitmapBounds.size.height - (bounds.origin.y + bounds.size.height);
int endY = startY + bounds.size.height;
MOZ_ASSERT(endY <= (int)CGBitmapContextGetHeight(aContext));
for (int y = startY; y < endY; y++) {
for (int x = startX; x < endX; x++) {
int i = y * stride + x * 4;
uint8_t a = bitmapData[i + 3];
bitmapData[i + 0] = std::min(a, bitmapData[i + 0]);
bitmapData[i + 1] = std::min(a, bitmapData[i + 1]);
bitmapData[i + 2] = std::min(a, bitmapData[i + 2]);
}
}
}
static CGRect ComputeGlyphsExtents(CGRect* bboxes, CGPoint* positions, CFIndex count, float scale) {
CGFloat x1, x2, y1, y2;
if (count < 1) return CGRectZero;
x1 = bboxes[0].origin.x + positions[0].x;
x2 = bboxes[0].origin.x + positions[0].x + scale * bboxes[0].size.width;
y1 = bboxes[0].origin.y + positions[0].y;
y2 = bboxes[0].origin.y + positions[0].y + scale * bboxes[0].size.height;
// accumulate max and minimum coordinates
for (int i = 1; i < count; i++) {
x1 = std::min(x1, bboxes[i].origin.x + positions[i].x);
y1 = std::min(y1, bboxes[i].origin.y + positions[i].y);
x2 = std::max(x2, bboxes[i].origin.x + positions[i].x + scale * bboxes[i].size.width);
y2 = std::max(y2, bboxes[i].origin.y + positions[i].y + scale * bboxes[i].size.height);
}
CGRect extents = {{x1, y1}, {x2 - x1, y2 - y1}};
return extents;
}
} // namespace gfx
} // namespace mozilla
#endif