1/*
2 * Copyright (C) 2014 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. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "WKInspectorHighlightView.h"
28
29#if PLATFORM(IOS)
30
31#import <WebCore/FloatQuad.h>
32#import <WebCore/GeometryUtilities.h>
33#import <WebCore/InspectorOverlay.h>
34
35using namespace WebCore;
36
37@implementation WKInspectorHighlightView
38
39- (instancetype)initWithFrame:(CGRect)frame
40{
41    if (!(self = [super initWithFrame:frame]))
42        return nil;
43    _layers = [[NSMutableArray alloc] init];
44    return self;
45}
46
47- (void)dealloc
48{
49    [self _removeAllLayers];
50    [_layers release];
51    [super dealloc];
52}
53
54- (void)_removeAllLayers
55{
56    for (CAShapeLayer *layer in _layers)
57        [layer removeFromSuperlayer];
58    [_layers removeAllObjects];
59}
60
61- (void)_createLayers:(NSUInteger)numLayers
62{
63    if ([_layers count] == numLayers)
64        return;
65
66    [self _removeAllLayers];
67
68    for (NSUInteger i = 0; i < numLayers; ++i) {
69        CAShapeLayer *layer = [[CAShapeLayer alloc] init];
70        [_layers addObject:layer];
71        [self.layer addSublayer:layer];
72        [layer release];
73    }
74}
75
76static bool findIntersectionOnLineBetweenPoints(const FloatPoint& p1, const FloatPoint& p2, const FloatPoint& d1, const FloatPoint& d2, FloatPoint& intersection)
77{
78    // Do the lines intersect?
79    FloatPoint temporaryIntersectionPoint;
80    if (!findIntersection(p1, p2, d1, d2, temporaryIntersectionPoint))
81        return false;
82
83    // Is the intersection between the two points on the line?
84    if (p1.x() >= p2.x()) {
85        if (temporaryIntersectionPoint.x() > p1.x() || temporaryIntersectionPoint.x() < p2.x())
86            return false;
87    } else {
88        if (temporaryIntersectionPoint.x() > p2.x() || temporaryIntersectionPoint.x() < p1.x())
89            return false;
90    }
91    if (p1.y() >= p2.y()) {
92        if (temporaryIntersectionPoint.y() > p1.y() || temporaryIntersectionPoint.y() < p2.y())
93            return false;
94    } else {
95        if (temporaryIntersectionPoint.y() > p2.y() || temporaryIntersectionPoint.y() < p1.y())
96            return false;
97    }
98
99    intersection = temporaryIntersectionPoint;
100    return true;
101}
102
103// This quad intersection works because the two quads are known to be at the same
104// rotation and clockwise-ness.
105static FloatQuad quadIntersection(FloatQuad bounds, FloatQuad toClamp)
106{
107    // Resulting points.
108    FloatPoint p1, p2, p3, p4;
109    bool containsPoint1 = false;
110    bool containsPoint2 = false;
111    bool containsPoint3 = false;
112    bool containsPoint4 = false;
113    bool intersectForPoint1 = false;
114    bool intersectForPoint2 = false;
115    bool intersectForPoint3 = false;
116    bool intersectForPoint4 = false;
117
118    // Top / bottom vertical clamping.
119    if (bounds.containsPoint(toClamp.p1())) {
120        containsPoint1 = true;
121        p1 = toClamp.p1();
122    } else if (!(intersectForPoint1 = findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p2(), toClamp.p1(), toClamp.p4(), p1)))
123        p1 = toClamp.p1();
124
125    if (bounds.containsPoint(toClamp.p2())) {
126        containsPoint2 = true;
127        p2 = toClamp.p2();
128    } else if (!(intersectForPoint2 = findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p2(), toClamp.p2(), toClamp.p3(), p2)))
129        p2 = toClamp.p2();
130
131    if (bounds.containsPoint(toClamp.p3())) {
132        containsPoint3 = true;
133        p3 = toClamp.p3();
134    } else if (!(intersectForPoint3 = findIntersectionOnLineBetweenPoints(bounds.p4(), bounds.p3(), toClamp.p2(), toClamp.p3(), p3)))
135        p3 = toClamp.p3();
136
137    if (bounds.containsPoint(toClamp.p4())) {
138        containsPoint4 = true;
139        p4 = toClamp.p4();
140    } else if (!(intersectForPoint4 = findIntersectionOnLineBetweenPoints(bounds.p4(), bounds.p3(), toClamp.p1(), toClamp.p4(), p4)))
141        p4 = toClamp.p4();
142
143    // If only one of the points intersected on either the top or bottom line then we
144    // can clamp the other point on that line to the corner of the bounds.
145    if (!containsPoint1 && intersectForPoint2 && !intersectForPoint1) {
146        containsPoint1 = true;
147        p1 = bounds.p1();
148    } else if (!containsPoint2 && intersectForPoint1 && !intersectForPoint2) {
149        containsPoint2 = true;
150        p2 = bounds.p2();
151    }
152    if (!containsPoint4 && intersectForPoint3 && !intersectForPoint4) {
153        containsPoint4 = true;
154        p4 = bounds.p4();
155    } else if (!containsPoint3 && intersectForPoint4 && !intersectForPoint3) {
156        containsPoint3 = true;
157        p3 = bounds.p3();
158    }
159
160    // Now we only need to perform horizontal clamping for unadjusted points.
161    if (!containsPoint2 && !intersectForPoint2)
162        findIntersectionOnLineBetweenPoints(bounds.p2(), bounds.p3(), p1, p2, p2);
163    if (!containsPoint3 && !intersectForPoint3)
164        findIntersectionOnLineBetweenPoints(bounds.p2(), bounds.p3(), p4, p3, p3);
165    if (!containsPoint1 && !intersectForPoint1)
166        findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p4(), p1, p2, p1);
167    if (!containsPoint4 && !intersectForPoint4)
168        findIntersectionOnLineBetweenPoints(bounds.p1(), bounds.p4(), p4, p3, p4);
169
170    return FloatQuad(p1, p2, p3, p4);
171}
172
173static void layerPathWithHole(CAShapeLayer *layer, const FloatQuad& outerQuad, const FloatQuad& holeQuad)
174{
175    // Nothing to show.
176    if (outerQuad == holeQuad || holeQuad.containsQuad(outerQuad)) {
177        layer.path = NULL;
178        return;
179    }
180
181    // If there is a negative margin / padding then the outer box might not
182    // fully contain the hole box. In such cases we recalculate the hole to
183    // be the intersection of the two quads.
184    FloatQuad innerHole;
185    if (outerQuad.containsQuad(holeQuad))
186        innerHole = holeQuad;
187    else
188        innerHole = quadIntersection(outerQuad, holeQuad);
189
190    // Clockwise inside rect (hole), Counter-Clockwise outside rect (fill).
191    CGMutablePathRef path = CGPathCreateMutable();
192    CGPathMoveToPoint(path, 0, innerHole.p1().x(), innerHole.p1().y());
193    CGPathAddLineToPoint(path, 0, innerHole.p2().x(), innerHole.p2().y());
194    CGPathAddLineToPoint(path, 0, innerHole.p3().x(), innerHole.p3().y());
195    CGPathAddLineToPoint(path, 0, innerHole.p4().x(), innerHole.p4().y());
196    CGPathMoveToPoint(path, 0, outerQuad.p1().x(), outerQuad.p1().y());
197    CGPathAddLineToPoint(path, 0, outerQuad.p4().x(), outerQuad.p4().y());
198    CGPathAddLineToPoint(path, 0, outerQuad.p3().x(), outerQuad.p3().y());
199    CGPathAddLineToPoint(path, 0, outerQuad.p2().x(), outerQuad.p2().y());
200    layer.path = path;
201    CGPathRelease(path);
202}
203
204static void layerPath(CAShapeLayer *layer, const FloatQuad& outerQuad)
205{
206    CGMutablePathRef path = CGPathCreateMutable();
207    CGPathMoveToPoint(path, 0, outerQuad.p1().x(), outerQuad.p1().y());
208    CGPathAddLineToPoint(path, 0, outerQuad.p4().x(), outerQuad.p4().y());
209    CGPathAddLineToPoint(path, 0, outerQuad.p3().x(), outerQuad.p3().y());
210    CGPathAddLineToPoint(path, 0, outerQuad.p2().x(), outerQuad.p2().y());
211    layer.path = path;
212    CGPathRelease(path);
213}
214
215- (void)_layoutForNodeHighlight:(const Highlight&)highlight
216{
217    [self _createLayers:4];
218
219    CAShapeLayer *marginLayer = [_layers objectAtIndex:0];
220    CAShapeLayer *borderLayer = [_layers objectAtIndex:1];
221    CAShapeLayer *paddingLayer = [_layers objectAtIndex:2];
222    CAShapeLayer *contentLayer = [_layers objectAtIndex:3];
223
224    FloatQuad marginQuad = highlight.quads[0];
225    FloatQuad borderQuad = highlight.quads[1];
226    FloatQuad paddingQuad = highlight.quads[2];
227    FloatQuad contentQuad = highlight.quads[3];
228
229    marginLayer.fillColor = cachedCGColor(highlight.marginColor, ColorSpaceDeviceRGB);
230    borderLayer.fillColor = cachedCGColor(highlight.borderColor, ColorSpaceDeviceRGB);
231    paddingLayer.fillColor = cachedCGColor(highlight.paddingColor, ColorSpaceDeviceRGB);
232    contentLayer.fillColor = cachedCGColor(highlight.contentColor, ColorSpaceDeviceRGB);
233
234    layerPathWithHole(marginLayer, marginQuad, borderQuad);
235    layerPathWithHole(borderLayer, borderQuad, paddingQuad);
236    layerPathWithHole(paddingLayer, paddingQuad, contentQuad);
237    layerPath(contentLayer, contentQuad);
238}
239
240- (void)_layoutForRectsHighlight:(const Highlight&)highlight
241{
242    NSUInteger numLayers = (NSUInteger)highlight.quads.size();
243    if (!numLayers) {
244        [self _removeAllLayers];
245        return;
246    }
247
248    [self _createLayers:numLayers];
249
250    CGColorRef contentColor = cachedCGColor(highlight.contentColor, ColorSpaceDeviceRGB);
251    for (NSUInteger i = 0; i < numLayers; ++i) {
252        CAShapeLayer *layer = [_layers objectAtIndex:i];
253        layer.fillColor = contentColor;
254        layerPath(layer, highlight.quads[i]);
255    }
256}
257
258- (void)update:(const Highlight&)highlight
259{
260    if (highlight.type == HighlightTypeNode)
261        [self _layoutForNodeHighlight:highlight];
262    else if (highlight.type == HighlightTypeRects)
263        [self _layoutForRectsHighlight:highlight];
264}
265
266@end
267
268#endif
269