1/*
2 * Copyright (C) 2006, 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 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#import "WebNodeHighlightView.h"
30#import "WebNodeHighlight.h"
31
32#import <WebCore/GraphicsContext.h>
33#import <WebCore/InspectorController.h>
34#import <wtf/Assertions.h>
35
36#if PLATFORM(IOS)
37#import <CoreGraphics/CoreGraphics.h>
38#import <WebCore/FloatQuad.h>
39#import <WebCore/GeometryUtilities.h>
40#import <WebCore/InspectorOverlay.h>
41#import <WebCore/WebCoreThread.h>
42#endif
43
44using namespace WebCore;
45
46@implementation WebNodeHighlightView
47
48#if PLATFORM(IOS)
49- (void)_removeAllLayers
50{
51    for (CAShapeLayer *layer in _layers)
52        [layer removeFromSuperlayer];
53    [_layers removeAllObjects];
54}
55#endif
56
57- (id)initWithWebNodeHighlight:(WebNodeHighlight *)webNodeHighlight
58{
59    self = [self initWithFrame:NSZeroRect];
60    if (!self)
61        return nil;
62
63    _webNodeHighlight = [webNodeHighlight retain];
64
65#if PLATFORM(IOS)
66    _layers = [[NSMutableArray alloc] init];
67#endif
68
69    return self;
70}
71
72- (void)dealloc
73{
74    [self detachFromWebNodeHighlight];
75#if PLATFORM(IOS)
76    [self _removeAllLayers];
77    [_layers release];
78#endif
79    [super dealloc];
80}
81
82- (void)detachFromWebNodeHighlight
83{
84    [_webNodeHighlight release];
85    _webNodeHighlight = nil;
86}
87
88- (BOOL)isFlipped
89{
90    return YES;
91}
92
93#if !PLATFORM(IOS)
94- (void)drawRect:(NSRect)rect
95{
96    if (_webNodeHighlight) {
97        [NSGraphicsContext saveGraphicsState];
98
99        ASSERT([[NSGraphicsContext currentContext] isFlipped]);
100
101        GraphicsContext context((PlatformGraphicsContext*)[[NSGraphicsContext currentContext] graphicsPort]);
102        [_webNodeHighlight inspectorController]->drawHighlight(context);
103        [NSGraphicsContext restoreGraphicsState];
104    }
105}
106#else
107- (void)_attach:(CALayer *)parent numLayers:(NSUInteger)numLayers
108{
109    ASSERT(numLayers);
110
111    // We have the right layers and they are all parented correctly.
112    if ([_layers count] == numLayers && [[_layers objectAtIndex:0] superlayer] == parent)
113        return;
114
115    // Remove and create new layers.
116    [self _removeAllLayers];
117    for (NSUInteger i = 0; i < numLayers; ++i) {
118        CAShapeLayer *layer = [[CAShapeLayer alloc] init];
119        [_layers addObject:layer];
120        [parent addSublayer:layer];
121        [layer release];
122    }
123}
124
125static bool findIntersectionOnLineBetweenPoints(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& d1, const FloatPoint& d2, FloatPoint& intersection)
126{
127    // Do the lines intersect?
128    FloatPoint temporaryIntersectionPoint;
129    if (!findIntersection(p1, p2, d1, d2, temporaryIntersectionPoint))
130        return false;
131
132    // Is the intersection between the two points on the line?
133    if (p1.x() >= p2.x()) {
134        if (temporaryIntersectionPoint.x() > p1.x() || temporaryIntersectionPoint.x() < p2.x())
135            return false;
136    } else {
137        if (temporaryIntersectionPoint.x() > p2.x() || temporaryIntersectionPoint.x() < p1.x())
138            return false;
139    }
140    if (p1.y() >= p2.y()) {
141        if (temporaryIntersectionPoint.y() > p1.y() || temporaryIntersectionPoint.y() < p2.y())
142            return false;
143    } else {
144        if (temporaryIntersectionPoint.y() > p2.y() || temporaryIntersectionPoint.y() < p1.y())
145            return false;
146    }
147
148    intersection = temporaryIntersectionPoint;
149    return true;
150}
151
152// This quad intersection works because the two quads are known to be at the same
153// rotation and clockwise-ness.
154static FloatQuad quadIntersection(FloatQuad bounds, FloatQuad toClamp)
155{
156    // Resulting points.
157    FloatPoint p1, p2, p3, p4;
158    bool containsPoint1 = false;
159    bool containsPoint2 = false;
160    bool containsPoint3 = false;
161    bool containsPoint4 = false;
162    bool intersectForPoint1 = false;
163    bool intersectForPoint2 = false;
164    bool intersectForPoint3 = false;
165    bool intersectForPoint4 = false;
166
167    // Top / bottom vertical clamping.
168    if (bounds.containsPoint(toClamp.p1())) {
169        containsPoint1 = true;
170        p1 = toClamp.p1();
171    } else if (!(intersectForPoint1 = findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p2(), toClamp.p1(), toClamp.p4(), p1)))
172        p1 = toClamp.p1();
173
174    if (bounds.containsPoint(toClamp.p2())) {
175        containsPoint2 = true;
176        p2 = toClamp.p2();
177    } else if (!(intersectForPoint2 = findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p2(), toClamp.p2(), toClamp.p3(), p2)))
178        p2 = toClamp.p2();
179
180    if (bounds.containsPoint(toClamp.p3())) {
181        containsPoint3 = true;
182        p3 = toClamp.p3();
183    } else if (!(intersectForPoint3 = findIntersectionOnLineBetweenPoints(bounds.p4(), bounds.p3(), toClamp.p2(), toClamp.p3(), p3)))
184        p3 = toClamp.p3();
185
186    if (bounds.containsPoint(toClamp.p4())) {
187        containsPoint4 = true;
188        p4 = toClamp.p4();
189    } else if (!(intersectForPoint4 = findIntersectionOnLineBetweenPoints(bounds.p4(), bounds.p3(), toClamp.p1(), toClamp.p4(), p4)))
190        p4 = toClamp.p4();
191
192    // If only one of the points intersected on either the top or bottom line then we
193    // can clamp the other point on that line to the corner of the bounds.
194    if (!containsPoint1 && intersectForPoint2 && !intersectForPoint1) {
195        containsPoint1 = true;
196        p1 = bounds.p1();
197    } else if (!containsPoint2 && intersectForPoint1 && !intersectForPoint2) {
198        containsPoint2 = true;
199        p2 = bounds.p2();
200    }
201    if (!containsPoint4 && intersectForPoint3 && !intersectForPoint4) {
202        containsPoint4 = true;
203        p4 = bounds.p4();
204    } else if (!containsPoint3 && intersectForPoint4 && !intersectForPoint3) {
205        containsPoint3 = true;
206        p3 = bounds.p3();
207    }
208
209    // Now we only need to perform horizontal clamping for unadjusted points.
210    if (!containsPoint2 && !intersectForPoint2)
211        findIntersectionOnLineBetweenPoints(bounds.p2(), bounds.p3(), p1, p2, p2);
212    if (!containsPoint3 && !intersectForPoint3)
213        findIntersectionOnLineBetweenPoints(bounds.p2(), bounds.p3(), p4, p3, p3);
214    if (!containsPoint1 && !intersectForPoint1)
215        findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p4(), p1, p2, p1);
216    if (!containsPoint4 && !intersectForPoint4)
217        findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p4(), p4, p3, p4);
218
219    return FloatQuad(p1, p2, p3, p4);
220}
221
222static void layerPathWithHole(CAShapeLayer *layer, const FloatQuad& outerQuad, const FloatQuad& holeQuad)
223{
224    // Nothing to show.
225    if (outerQuad == holeQuad || holeQuad.containsQuad(outerQuad)) {
226        layer.path = NULL;
227        return;
228    }
229
230    // If there is a negative margin / padding then the outer box might not
231    // fully contain the hole box. In such cases we recalculate the hole to
232    // be the intersection of the two quads.
233    FloatQuad innerHole;
234    if (outerQuad.containsQuad(holeQuad))
235        innerHole = holeQuad;
236    else
237        innerHole = quadIntersection(outerQuad, holeQuad);
238
239    // Clockwise inside rect (hole), Counter-Clockwise outside rect (fill).
240    CGMutablePathRef path = CGPathCreateMutable();
241    CGPathMoveToPoint(path, 0, innerHole.p1().x(), innerHole.p1().y());
242    CGPathAddLineToPoint(path, 0, innerHole.p2().x(), innerHole.p2().y());
243    CGPathAddLineToPoint(path, 0, innerHole.p3().x(), innerHole.p3().y());
244    CGPathAddLineToPoint(path, 0, innerHole.p4().x(), innerHole.p4().y());
245    CGPathMoveToPoint(path, 0, outerQuad.p1().x(), outerQuad.p1().y());
246    CGPathAddLineToPoint(path, 0, outerQuad.p4().x(), outerQuad.p4().y());
247    CGPathAddLineToPoint(path, 0, outerQuad.p3().x(), outerQuad.p3().y());
248    CGPathAddLineToPoint(path, 0, outerQuad.p2().x(), outerQuad.p2().y());
249    layer.path = path;
250    CGPathRelease(path);
251}
252
253static void layerPath(CAShapeLayer *layer, const FloatQuad& outerQuad)
254{
255    CGMutablePathRef path = CGPathCreateMutable();
256    CGPathMoveToPoint(path, 0, outerQuad.p1().x(), outerQuad.p1().y());
257    CGPathAddLineToPoint(path, 0, outerQuad.p4().x(), outerQuad.p4().y());
258    CGPathAddLineToPoint(path, 0, outerQuad.p3().x(), outerQuad.p3().y());
259    CGPathAddLineToPoint(path, 0, outerQuad.p2().x(), outerQuad.p2().y());
260    layer.path = path;
261    CGPathRelease(path);
262}
263
264- (void)_layoutForNodeHighlight:(Highlight*)h parent:(CALayer *)parentLayer
265{
266    [self _attach:parentLayer numLayers:4];
267
268    CAShapeLayer *marginLayer = [_layers objectAtIndex:0];
269    CAShapeLayer *borderLayer = [_layers objectAtIndex:1];
270    CAShapeLayer *paddingLayer = [_layers objectAtIndex:2];
271    CAShapeLayer *contentLayer = [_layers objectAtIndex:3];
272
273    FloatQuad marginQuad = h->quads[0];
274    FloatQuad borderQuad = h->quads[1];
275    FloatQuad paddingQuad = h->quads[2];
276    FloatQuad contentQuad = h->quads[3];
277
278    marginLayer.fillColor = cachedCGColor(h->marginColor, ColorSpaceDeviceRGB);
279    borderLayer.fillColor = cachedCGColor(h->borderColor, ColorSpaceDeviceRGB);
280    paddingLayer.fillColor = cachedCGColor(h->paddingColor, ColorSpaceDeviceRGB);
281    contentLayer.fillColor = cachedCGColor(h->contentColor, ColorSpaceDeviceRGB);
282
283    layerPathWithHole(marginLayer, marginQuad, borderQuad);
284    layerPathWithHole(borderLayer, borderQuad, paddingQuad);
285    layerPathWithHole(paddingLayer, paddingQuad, contentQuad);
286    layerPath(contentLayer, contentQuad);
287}
288
289- (void)_layoutForRectsHighlight:(Highlight*)h parent:(CALayer *)parentLayer
290{
291    NSUInteger numLayers = (NSUInteger)h->quads.size();
292    if (!numLayers) {
293        [self _removeAllLayers];
294        return;
295    }
296
297    [self _attach:parentLayer numLayers:numLayers];
298
299    CGColorRef contentColor = cachedCGColor(h->contentColor, ColorSpaceDeviceRGB);
300    for (NSUInteger i = 0; i < numLayers; ++i) {
301        CAShapeLayer *layer = [_layers objectAtIndex:i];
302        layer.fillColor = contentColor;
303        layerPath(layer, h->quads[i]);
304    }
305}
306
307- (void)layoutSublayers:(CALayer *)parentLayer
308{
309    if (!_webNodeHighlight)
310        return;
311
312    WebThreadLock();
313
314    if (![_webNodeHighlight inspectorController])
315        return;
316
317    Highlight h;
318    [_webNodeHighlight inspectorController]->getHighlight(&h, InspectorOverlay::CoordinateSystem::View);
319
320    if (h.type == HighlightTypeNode)
321        [self _layoutForNodeHighlight:&h parent:parentLayer];
322    else if (h.type == HighlightTypeRects)
323        [self _layoutForRectsHighlight:&h parent:parentLayer];
324}
325#endif
326
327- (WebNodeHighlight *)webNodeHighlight
328{
329    return _webNodeHighlight;
330}
331
332@end
333