1/*
2 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 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 COMPUTER, 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 COMPUTER, 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 "GraphicsContext.h"
28
29#include "AffineTransform.h"
30#include "Path.h"
31
32#include <CoreGraphics/CGBitmapContext.h>
33#include <WebKitSystemInterface/WebKitSystemInterface.h>
34#include "GraphicsContextPlatformPrivateCG.h"
35
36using namespace std;
37
38namespace WebCore {
39
40static CGContextRef CGContextWithHDC(HDC hdc, bool hasAlpha)
41{
42    HBITMAP bitmap = static_cast<HBITMAP>(GetCurrentObject(hdc, OBJ_BITMAP));
43    CGColorSpaceRef deviceRGB = CGColorSpaceCreateDeviceRGB();
44    BITMAP info;
45
46    GetObject(bitmap, sizeof(info), &info);
47    ASSERT(info.bmBitsPixel == 32);
48
49    CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Little | (hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst);
50    CGContextRef context = CGBitmapContextCreate(info.bmBits, info.bmWidth, info.bmHeight, 8,
51                                                 info.bmWidthBytes, deviceRGB, bitmapInfo);
52    CGColorSpaceRelease(deviceRGB);
53
54    // Flip coords
55    CGContextTranslateCTM(context, 0, info.bmHeight);
56    CGContextScaleCTM(context, 1, -1);
57
58    // Put the HDC In advanced mode so it will honor affine transforms.
59    SetGraphicsMode(hdc, GM_ADVANCED);
60
61    return context;
62}
63
64GraphicsContext::GraphicsContext(HDC hdc, bool hasAlpha)
65    : m_common(createGraphicsContextPrivate())
66    , m_data(new GraphicsContextPlatformPrivate(CGContextWithHDC(hdc, hasAlpha)))
67{
68    CGContextRelease(m_data->m_cgContext.get());
69    m_data->m_hdc = hdc;
70    setPaintingDisabled(!m_data->m_cgContext);
71    if (m_data->m_cgContext) {
72        // Make sure the context starts in sync with our state.
73        setPlatformFillColor(fillColor(), DeviceColorSpace);
74        setPlatformStrokeColor(strokeColor(), DeviceColorSpace);
75    }
76}
77
78// FIXME: Is it possible to merge getWindowsContext and createWindowsBitmap into a single API
79// suitable for all clients?
80void GraphicsContext::releaseWindowsContext(HDC hdc, const IntRect& dstRect, bool supportAlphaBlend, bool mayCreateBitmap)
81{
82    if (mayCreateBitmap && hdc && inTransparencyLayer()) {
83        if (dstRect.isEmpty())
84            return;
85
86        HBITMAP bitmap = static_cast<HBITMAP>(GetCurrentObject(hdc, OBJ_BITMAP));
87
88        // Need to make a CGImage out of the bitmap's pixel buffer and then draw
89        // it into our context.
90        BITMAP info;
91        GetObject(bitmap, sizeof(info), &info);
92        ASSERT(info.bmBitsPixel == 32);
93
94        CGColorSpaceRef deviceRGB = CGColorSpaceCreateDeviceRGB();
95        CGContextRef bitmapContext = CGBitmapContextCreate(info.bmBits, info.bmWidth, info.bmHeight, 8,
96                                                           info.bmWidthBytes, deviceRGB, kCGBitmapByteOrder32Little |
97                                                           (supportAlphaBlend ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst));
98        CGColorSpaceRelease(deviceRGB);
99
100        CGImageRef image = CGBitmapContextCreateImage(bitmapContext);
101        CGContextDrawImage(m_data->m_cgContext.get(), dstRect, image);
102
103        // Delete all our junk.
104        CGImageRelease(image);
105        CGContextRelease(bitmapContext);
106        ::DeleteDC(hdc);
107        ::DeleteObject(bitmap);
108
109        return;
110    }
111
112    m_data->restore();
113}
114
115void GraphicsContext::drawWindowsBitmap(WindowsBitmap* image, const IntPoint& point)
116{
117    RetainPtr<CGColorSpaceRef> deviceRGB(AdoptCF, CGColorSpaceCreateDeviceRGB());
118    // FIXME: Creating CFData is non-optimal, but needed to avoid crashing when printing.  Ideally we should
119    // make a custom CGDataProvider that controls the WindowsBitmap lifetime.  see <rdar://6394455>
120    RetainPtr<CFDataRef> imageData(AdoptCF, CFDataCreate(kCFAllocatorDefault, image->buffer(), image->bufferLength()));
121    RetainPtr<CGDataProviderRef> dataProvider(AdoptCF, CGDataProviderCreateWithCFData(imageData.get()));
122    RetainPtr<CGImageRef> cgImage(AdoptCF, CGImageCreate(image->size().width(), image->size().height(), 8, 32, image->bytesPerRow(), deviceRGB.get(),
123                                                         kCGBitmapByteOrder32Little | kCGImageAlphaFirst, dataProvider.get(), 0, true, kCGRenderingIntentDefault));
124    CGContextDrawImage(m_data->m_cgContext.get(), CGRectMake(point.x(), point.y(), image->size().width(), image->size().height()), cgImage.get());
125}
126
127void GraphicsContext::drawFocusRing(const Vector<Path>& paths, int width, int offset, const Color& color)
128{
129    // FIXME: implement
130}
131
132void GraphicsContext::drawFocusRing(const Vector<IntRect>& rects, int width, int offset, const Color& color)
133{
134    if (paintingDisabled())
135        return;
136
137    float radius = (width - 1) / 2.0f;
138    offset += radius;
139    CGColorRef colorRef = color.isValid() ? createCGColor(color) : 0;
140
141    CGMutablePathRef focusRingPath = CGPathCreateMutable();
142    unsigned rectCount = rects.size();
143    for (unsigned i = 0; i < rectCount; i++)
144        CGPathAddRect(focusRingPath, 0, CGRectInset(rects[i], -offset, -offset));
145
146    CGContextRef context = platformContext();
147    CGContextSaveGState(context);
148
149    CGContextBeginPath(context);
150    CGContextAddPath(context, focusRingPath);
151
152    wkDrawFocusRing(context, colorRef, radius);
153
154    CGColorRelease(colorRef);
155
156    CGPathRelease(focusRingPath);
157
158    CGContextRestoreGState(context);
159}
160
161// Pulled from GraphicsContextCG
162static void setCGStrokeColor(CGContextRef context, const Color& color)
163{
164    CGFloat red, green, blue, alpha;
165    color.getRGBA(red, green, blue, alpha);
166    CGContextSetRGBStrokeColor(context, red, green, blue, alpha);
167}
168
169static const Color& spellingPatternColor() {
170    static const Color spellingColor(255, 0, 0);
171    return spellingColor;
172}
173
174static const Color& grammarPatternColor() {
175    static const Color grammarColor(0, 128, 0);
176    return grammarColor;
177}
178
179void GraphicsContext::drawLineForMisspellingOrBadGrammar(const IntPoint& point, int width, bool grammar)
180{
181    if (paintingDisabled())
182        return;
183
184    // These are the same for misspelling or bad grammar
185    const int patternHeight = 3; // 3 rows
186    ASSERT(cMisspellingLineThickness == patternHeight);
187    const int patternWidth = 4; // 4 pixels
188    ASSERT(patternWidth == cMisspellingLinePatternWidth);
189
190    // Make sure to draw only complete dots.
191    // NOTE: Code here used to shift the underline to the left and increase the width
192    // to make sure everything gets underlined, but that results in drawing out of
193    // bounds (e.g. when at the edge of a view) and could make it appear that the
194    // space between adjacent misspelled words was underlined.
195    // allow slightly more considering that the pattern ends with a transparent pixel
196    int widthMod = width % patternWidth;
197    if (patternWidth - widthMod > cMisspellingLinePatternGapWidth)
198        width -= widthMod;
199
200    // Draw the underline
201    CGContextRef context = platformContext();
202    CGContextSaveGState(context);
203
204    const Color& patternColor = grammar ? grammarPatternColor() : spellingPatternColor();
205    setCGStrokeColor(context, patternColor);
206
207    wkSetPatternPhaseInUserSpace(context, point);
208    CGContextSetBlendMode(context, kCGBlendModeNormal);
209
210    // 3 rows, each offset by half a pixel for blending purposes
211    const CGPoint upperPoints [] = {{point.x(), point.y() + patternHeight - 2.5 }, {point.x() + width, point.y() + patternHeight - 2.5}};
212    const CGPoint middlePoints [] = {{point.x(), point.y() + patternHeight - 1.5 }, {point.x() + width, point.y() + patternHeight - 1.5}};
213    const CGPoint lowerPoints [] = {{point.x(), point.y() + patternHeight - 0.5 }, {point.x() + width, point.y() + patternHeight - 0.5 }};
214
215    // Dash lengths for the top and bottom of the error underline are the same.
216    // These are magic.
217    static const float edge_dash_lengths[] = {2.0f, 2.0f};
218    static const float middle_dash_lengths[] = {2.76f, 1.24f};
219    static const float edge_offset = -(edge_dash_lengths[1] - 1.0f) / 2.0f;
220    static const float middle_offset = -(middle_dash_lengths[1] - 1.0f) / 2.0f;
221
222    // Line opacities.  Once again, these are magic.
223    const float upperOpacity = 0.33f;
224    const float middleOpacity = 0.75f;
225    const float lowerOpacity = 0.88f;
226
227    //Top line
228    CGContextSetLineDash(context, edge_offset, edge_dash_lengths,
229                         sizeof(edge_dash_lengths) / sizeof(edge_dash_lengths[0]));
230    CGContextSetAlpha(context, upperOpacity);
231    CGContextStrokeLineSegments(context, upperPoints, 2);
232
233    // Middle line
234    CGContextSetLineDash(context, middle_offset, middle_dash_lengths,
235                         sizeof(middle_dash_lengths) / sizeof(middle_dash_lengths[0]));
236    CGContextSetAlpha(context, middleOpacity);
237    CGContextStrokeLineSegments(context, middlePoints, 2);
238
239    // Bottom line
240    CGContextSetLineDash(context, edge_offset, edge_dash_lengths,
241                         sizeof(edge_dash_lengths) / sizeof(edge_dash_lengths[0]));
242    CGContextSetAlpha(context, lowerOpacity);
243    CGContextStrokeLineSegments(context, lowerPoints, 2);
244
245    CGContextRestoreGState(context);
246}
247
248void GraphicsContextPlatformPrivate::flush()
249{
250    CGContextFlush(m_cgContext.get());
251}
252
253}
254