1/*
2 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2010, 2013 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "GraphicsContextCG.h"
28
29#include "AffineTransform.h"
30#include "GraphicsContextPlatformPrivateCG.h"
31#include "Path.h"
32
33#include <CoreGraphics/CGBitmapContext.h>
34#include <WebKitSystemInterface/WebKitSystemInterface.h>
35#include <wtf/win/GDIObject.h>
36
37using namespace std;
38
39namespace WebCore {
40
41static CGContextRef CGContextWithHDC(HDC hdc, bool hasAlpha)
42{
43    HBITMAP bitmap = static_cast<HBITMAP>(GetCurrentObject(hdc, OBJ_BITMAP));
44
45    DIBPixelData pixelData(bitmap);
46
47    // FIXME: We can get here because we asked for a bitmap that is too big
48    // when we have a tiled layer and we're compositing. In that case
49    // bmBitsPixel will be 0. This seems to be benign, so for now we will
50    // exit gracefully and look at it later:
51    //  https://bugs.webkit.org/show_bug.cgi?id=52041
52    // ASSERT(bitmapBits.bitsPerPixel() == 32);
53    if (pixelData.bitsPerPixel() != 32)
54        return 0;
55
56    CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Little | (hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst);
57    CGContextRef context = CGBitmapContextCreate(pixelData.buffer(), pixelData.size().width(), pixelData.size().height(), 8,
58                                                 pixelData.bytesPerRow(), deviceRGBColorSpaceRef(), bitmapInfo);
59
60    // Flip coords
61    CGContextTranslateCTM(context, 0, pixelData.size().height());
62    CGContextScaleCTM(context, 1, -1);
63
64    // Put the HDC In advanced mode so it will honor affine transforms.
65    SetGraphicsMode(hdc, GM_ADVANCED);
66
67    return context;
68}
69
70GraphicsContext::GraphicsContext(HDC hdc, bool hasAlpha)
71    : m_updatingControlTints(false),
72      m_transparencyCount(0)
73{
74    platformInit(hdc, hasAlpha);
75}
76
77void GraphicsContext::platformInit(HDC hdc, bool hasAlpha)
78{
79    m_data = new GraphicsContextPlatformPrivate(CGContextWithHDC(hdc, hasAlpha));
80    CGContextRelease(m_data->m_cgContext.get());
81    m_data->m_hdc = hdc;
82    setPaintingDisabled(!m_data->m_cgContext);
83    if (m_data->m_cgContext) {
84        // Make sure the context starts in sync with our state.
85        setPlatformFillColor(fillColor(), fillColorSpace());
86        setPlatformStrokeColor(strokeColor(), strokeColorSpace());
87    }
88}
89
90// FIXME: Is it possible to merge getWindowsContext and createWindowsBitmap into a single API
91// suitable for all clients?
92void GraphicsContext::releaseWindowsContext(HDC hdc, const IntRect& dstRect, bool supportAlphaBlend, bool mayCreateBitmap)
93{
94    bool createdBitmap = mayCreateBitmap && (!m_data->m_hdc || isInTransparencyLayer());
95    if (!createdBitmap) {
96        m_data->restore();
97        return;
98    }
99
100    if (dstRect.isEmpty())
101        return;
102
103    auto bitmap = adoptGDIObject(static_cast<HBITMAP>(::GetCurrentObject(hdc, OBJ_BITMAP)));
104
105    DIBPixelData pixelData(bitmap.get());
106
107    ASSERT(pixelData.bitsPerPixel() == 32);
108
109    CGContextRef bitmapContext = CGBitmapContextCreate(pixelData.buffer(), pixelData.size().width(), pixelData.size().height(), 8,
110                                                       pixelData.bytesPerRow(), deviceRGBColorSpaceRef(), kCGBitmapByteOrder32Little |
111                                                       (supportAlphaBlend ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst));
112
113    CGImageRef image = CGBitmapContextCreateImage(bitmapContext);
114    CGContextDrawImage(m_data->m_cgContext.get(), dstRect, image);
115
116    // Delete all our junk.
117    CGImageRelease(image);
118    CGContextRelease(bitmapContext);
119    ::DeleteDC(hdc);
120}
121
122void GraphicsContext::drawWindowsBitmap(WindowsBitmap* image, const IntPoint& point)
123{
124    // FIXME: Creating CFData is non-optimal, but needed to avoid crashing when printing.  Ideally we should
125    // make a custom CGDataProvider that controls the WindowsBitmap lifetime.  see <rdar://6394455>
126    RetainPtr<CFDataRef> imageData = adoptCF(CFDataCreate(kCFAllocatorDefault, image->buffer(), image->bufferLength()));
127    RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateWithCFData(imageData.get()));
128    RetainPtr<CGImageRef> cgImage = adoptCF(CGImageCreate(image->size().width(), image->size().height(), 8, 32, image->bytesPerRow(), deviceRGBColorSpaceRef(),
129                                                         kCGBitmapByteOrder32Little | kCGImageAlphaFirst, dataProvider.get(), 0, true, kCGRenderingIntentDefault));
130    CGContextDrawImage(m_data->m_cgContext.get(), CGRectMake(point.x(), point.y(), image->size().width(), image->size().height()), cgImage.get());
131}
132
133void GraphicsContext::drawFocusRing(const Path& path, int width, int offset, const Color& color)
134{
135    // FIXME: implement
136}
137
138// FIXME: This is nearly identical to the GraphicsContext::drawFocusRing function in GraphicsContextMac.mm.
139// The code could move to GraphicsContextCG.cpp and be shared.
140void GraphicsContext::drawFocusRing(const Vector<IntRect>& rects, int width, int offset, const Color& color)
141{
142    if (paintingDisabled())
143        return;
144
145    float radius = (width - 1) / 2.0f;
146    offset += radius;
147    CGColorRef colorRef = color.isValid() ? cachedCGColor(color, ColorSpaceDeviceRGB) : 0;
148
149    CGMutablePathRef focusRingPath = CGPathCreateMutable();
150    unsigned rectCount = rects.size();
151    for (unsigned i = 0; i < rectCount; i++)
152        CGPathAddRect(focusRingPath, 0, CGRectInset(rects[i], -offset, -offset));
153
154    CGContextRef context = platformContext();
155    CGContextSaveGState(context);
156
157    CGContextBeginPath(context);
158    CGContextAddPath(context, focusRingPath);
159
160    wkDrawFocusRing(context, colorRef, radius);
161
162    CGPathRelease(focusRingPath);
163
164    CGContextRestoreGState(context);
165}
166
167// Pulled from GraphicsContextCG
168static void setCGStrokeColor(CGContextRef context, const Color& color)
169{
170    CGFloat red, green, blue, alpha;
171    color.getRGBA(red, green, blue, alpha);
172    CGContextSetRGBStrokeColor(context, red, green, blue, alpha);
173}
174
175static const Color& spellingPatternColor() {
176    static const Color spellingColor(255, 0, 0);
177    return spellingColor;
178}
179
180static const Color& grammarPatternColor() {
181    static const Color grammarColor(0, 128, 0);
182    return grammarColor;
183}
184
185void GraphicsContext::updateDocumentMarkerResources()
186{
187    // Unnecessary, since our document markers don't use resources.
188}
189
190void GraphicsContext::drawLineForDocumentMarker(const FloatPoint& point, float width, DocumentMarkerLineStyle style)
191{
192    if (paintingDisabled())
193        return;
194
195    if (style != DocumentMarkerSpellingLineStyle && style != DocumentMarkerGrammarLineStyle)
196        return;
197
198    // These are the same for misspelling or bad grammar
199    const int patternHeight = 3; // 3 rows
200    ASSERT(cMisspellingLineThickness == patternHeight);
201    const int patternWidth = 4; // 4 pixels
202    ASSERT(patternWidth == cMisspellingLinePatternWidth);
203
204    // Make sure to draw only complete dots.
205    // NOTE: Code here used to shift the underline to the left and increase the width
206    // to make sure everything gets underlined, but that results in drawing out of
207    // bounds (e.g. when at the edge of a view) and could make it appear that the
208    // space between adjacent misspelled words was underlined.
209    // allow slightly more considering that the pattern ends with a transparent pixel
210    float widthMod = fmodf(width, patternWidth);
211    if (patternWidth - widthMod > cMisspellingLinePatternGapWidth)
212        width -= widthMod;
213
214    // Draw the underline
215    CGContextRef context = platformContext();
216    CGContextSaveGState(context);
217
218    const Color& patternColor = style == DocumentMarkerGrammarLineStyle ? grammarPatternColor() : spellingPatternColor();
219    setCGStrokeColor(context, patternColor);
220
221    wkSetPatternPhaseInUserSpace(context, point);
222    CGContextSetBlendMode(context, kCGBlendModeNormal);
223
224    // 3 rows, each offset by half a pixel for blending purposes
225    const CGPoint upperPoints [] = {{point.x(), point.y() + patternHeight - 2.5 }, {point.x() + width, point.y() + patternHeight - 2.5}};
226    const CGPoint middlePoints [] = {{point.x(), point.y() + patternHeight - 1.5 }, {point.x() + width, point.y() + patternHeight - 1.5}};
227    const CGPoint lowerPoints [] = {{point.x(), point.y() + patternHeight - 0.5 }, {point.x() + width, point.y() + patternHeight - 0.5 }};
228
229    // Dash lengths for the top and bottom of the error underline are the same.
230    // These are magic.
231    static const CGFloat edge_dash_lengths[] = {2.0f, 2.0f};
232    static const CGFloat middle_dash_lengths[] = { 2.76f, 1.24f };
233    static const CGFloat edge_offset = -(edge_dash_lengths[1] - 1.0f) / 2.0f;
234    static const CGFloat middle_offset = -(middle_dash_lengths[1] - 1.0f) / 2.0f;
235
236    // Line opacities.  Once again, these are magic.
237    const float upperOpacity = 0.33f;
238    const float middleOpacity = 0.75f;
239    const float lowerOpacity = 0.88f;
240
241    //Top line
242    CGContextSetLineDash(context, edge_offset, edge_dash_lengths, WTF_ARRAY_LENGTH(edge_dash_lengths));
243    CGContextSetAlpha(context, upperOpacity);
244    CGContextStrokeLineSegments(context, upperPoints, 2);
245
246    // Middle line
247    CGContextSetLineDash(context, middle_offset, middle_dash_lengths, WTF_ARRAY_LENGTH(middle_dash_lengths));
248    CGContextSetAlpha(context, middleOpacity);
249    CGContextStrokeLineSegments(context, middlePoints, 2);
250
251    // Bottom line
252    CGContextSetLineDash(context, edge_offset, edge_dash_lengths, WTF_ARRAY_LENGTH(edge_dash_lengths));
253    CGContextSetAlpha(context, lowerOpacity);
254    CGContextStrokeLineSegments(context, lowerPoints, 2);
255
256    CGContextRestoreGState(context);
257}
258
259void GraphicsContextPlatformPrivate::flush()
260{
261    CGContextFlush(m_cgContext.get());
262}
263
264}
265