summaryrefslogtreecommitdiffstats
path: root/gfx/2d/CGTextDrawing.h
blob: ab8e9c2dad29c74bcdb38bd0e8689a8b4f91471f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/* -*- 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