1/*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
15 *     its contributors may be used to endorse or promote products derived
16 *     from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#include "config.h"
31#include "GestureTapHighlighter.h"
32
33#include "Element.h"
34#include "FrameView.h"
35#include "GraphicsContext.h"
36#include "GraphicsTypes.h"
37#include "MainFrame.h"
38#include "Node.h"
39#include "Page.h"
40#include "RenderBoxModelObject.h"
41#include "RenderInline.h"
42#include "RenderLayer.h"
43#include "RenderObject.h"
44#include "RenderView.h"
45
46namespace WebCore {
47
48namespace {
49
50inline LayoutPoint ownerFrameToMainFrameOffset(const RenderObject* o)
51{
52    ASSERT(o->node());
53    Frame& containingFrame = o->frame();
54
55    Frame& mainFrame = containingFrame.page()->mainFrame();
56
57    LayoutPoint mainFramePoint = mainFrame.view()->windowToContents(containingFrame.view()->contentsToWindow(IntPoint()));
58    return mainFramePoint;
59}
60
61AffineTransform localToAbsoluteTransform(const RenderObject* o)
62{
63    AffineTransform transform;
64    LayoutPoint referencePoint;
65
66    while (o) {
67        RenderObject* nextContainer = o->container();
68        if (!nextContainer)
69            break;
70
71        LayoutSize containerOffset = o->offsetFromContainer(nextContainer, referencePoint);
72        TransformationMatrix t;
73        o->getTransformFromContainer(nextContainer, containerOffset, t);
74
75        transform = t.toAffineTransform() * transform;
76        referencePoint.move(containerOffset);
77        o = nextContainer;
78    }
79
80    return transform;
81}
82
83inline bool contains(const LayoutRect& rect, int x)
84{
85    return !rect.isEmpty() && x >= rect.x() && x <= rect.maxX();
86}
87
88inline bool strikes(const LayoutRect& a, const LayoutRect& b)
89{
90    return !a.isEmpty() && !b.isEmpty()
91        && a.x() <= b.maxX() && b.x() <= a.maxX()
92        && a.y() <= b.maxY() && b.y() <= a.maxY();
93}
94
95inline void shiftXEdgesToContainIfStrikes(LayoutRect& rect, LayoutRect& other, bool isFirst)
96{
97    if (rect.isEmpty())
98        return;
99
100    if (other.isEmpty() || !strikes(rect, other))
101        return;
102
103    LayoutUnit leftSide = std::min(rect.x(), other.x());
104    LayoutUnit rightSide = std::max(rect.maxX(), other.maxX());
105
106    rect.shiftXEdgeTo(leftSide);
107    rect.shiftMaxXEdgeTo(rightSide);
108
109    if (isFirst)
110        other.shiftMaxXEdgeTo(rightSide);
111    else
112        other.shiftXEdgeTo(leftSide);
113}
114
115inline void addHighlightRect(Path& path, const LayoutRect& rect, const LayoutRect& prev, const LayoutRect& next)
116{
117    // The rounding check depends on the rects not intersecting eachother,
118    // or being contained for that matter.
119    ASSERT(!rect.intersects(prev));
120    ASSERT(!rect.intersects(next));
121
122    if (rect.isEmpty())
123        return;
124
125    const int rounding = 4;
126
127    FloatRect copy(rect);
128    copy.inflateX(rounding);
129    copy.inflateY(rounding / 2);
130
131    FloatSize rounded(rounding * 1.8, rounding * 1.8);
132    FloatSize squared(0, 0);
133
134    path.addBeziersForRoundedRect(copy,
135            contains(prev, rect.x()) ? squared : rounded,
136            contains(prev, rect.maxX()) ? squared : rounded,
137            contains(next, rect.x()) ? squared : rounded,
138            contains(next, rect.maxX()) ? squared : rounded);
139}
140
141Path absolutePathForRenderer(RenderObject* const o)
142{
143    ASSERT(o);
144
145    Vector<IntRect> rects;
146    LayoutPoint frameOffset = ownerFrameToMainFrameOffset(o);
147    o->addFocusRingRects(rects, frameOffset);
148
149    if (rects.isEmpty())
150        return Path();
151
152    // The basic idea is to allow up to three different boxes in order to highlight
153    // text with line breaks more nicer than using a bounding box.
154
155    // Merge all center boxes (all but the first and the last).
156    LayoutRect mid;
157
158    // Set the end value to integer. It ensures that no unsigned int overflow occurs
159    // in the test expression, in case of empty rects vector.
160    int end = rects.size() - 1;
161    for (int i = 1; i < end; ++i)
162        mid.uniteIfNonZero(rects.at(i));
163
164    LayoutRect first;
165    LayoutRect last;
166
167    // Add the first box, but merge it with the center boxes if it intersects or if the center box is empty.
168    if (rects.size() && !rects.first().isEmpty()) {
169        // If the mid box is empty at this point, unite it with the first box. This allows the first box to be
170        // united with the last box if they intersect in the following check for last. Not uniting them would
171        // trigger in assert in addHighlighRect due to the first and the last box intersecting, but being passed
172        // as two separate boxes.
173        if (mid.isEmpty() || mid.intersects(rects.first()))
174            mid.unite(rects.first());
175        else {
176            first = rects.first();
177            shiftXEdgesToContainIfStrikes(mid, first, /* isFirst */ true);
178        }
179    }
180
181    // Add the last box, but merge it with the center boxes if it intersects.
182    if (rects.size() > 1 && !rects.last().isEmpty()) {
183        // Adjust center boxes to boundary of last
184        if (mid.intersects(rects.last()))
185            mid.unite(rects.last());
186        else {
187            last = rects.last();
188            shiftXEdgesToContainIfStrikes(mid, last, /* isFirst */ false);
189        }
190    }
191
192    Vector<LayoutRect> drawableRects;
193    if (!first.isEmpty())
194        drawableRects.append(first);
195    if (!mid.isEmpty())
196        drawableRects.append(mid);
197    if (!last.isEmpty())
198        drawableRects.append(last);
199
200    // Clip the overflow rects if needed, before the ring path is formed to
201    // ensure rounded highlight rects.
202    for (int i = drawableRects.size() - 1; i >= 0; --i) {
203        LayoutRect& ringRect = drawableRects.at(i);
204        LayoutPoint ringRectLocation = ringRect.location();
205
206        ringRect.moveBy(-frameOffset);
207
208        RenderLayer* layer = o->enclosingLayer();
209        RenderObject* currentRenderer = o;
210
211        // Check ancestor layers for overflow clip and intersect them.
212        for (; layer; layer = layer->parent()) {
213            RenderLayerModelObject* layerRenderer = &layer->renderer();
214
215            if (layerRenderer->hasOverflowClip() && layerRenderer != currentRenderer) {
216                bool containerSkipped = false;
217                // Skip ancestor layers that are not containers for the current renderer.
218                currentRenderer->container(layerRenderer, &containerSkipped);
219                if (containerSkipped)
220                    continue;
221                FloatQuad ringQuad = currentRenderer->localToContainerQuad(FloatQuad(ringRect), layerRenderer);
222                // Ignore quads that are not rectangular, since we can not currently highlight them nicely.
223                if (ringQuad.isRectilinear())
224                    ringRect = ringQuad.enclosingBoundingBox();
225                else
226                    ringRect = LayoutRect();
227                currentRenderer = layerRenderer;
228
229                ASSERT(layerRenderer->isBox());
230                ringRect.intersect(toRenderBox(layerRenderer)->borderBoxRect());
231
232                if (ringRect.isEmpty())
233                    break;
234            }
235        }
236
237        if (ringRect.isEmpty()) {
238            drawableRects.remove(i);
239            continue;
240        }
241        // After clipping, reset the original position so that parents' transforms apply correctly.
242        ringRect.setLocation(ringRectLocation);
243    }
244
245    Path path;
246    for (size_t i = 0; i < drawableRects.size(); ++i) {
247        LayoutRect prev = i ? drawableRects.at(i - 1) : LayoutRect();
248        LayoutRect next = i < (drawableRects.size() - 1) ? drawableRects.at(i + 1) : LayoutRect();
249        addHighlightRect(path, drawableRects.at(i), prev, next);
250    }
251
252    path.transform(localToAbsoluteTransform(o));
253    return path;
254}
255
256} // anonymous namespace
257
258namespace GestureTapHighlighter {
259
260Path pathForNodeHighlight(const Node* node)
261{
262    RenderObject* renderer = node->renderer();
263
264    if (!renderer || (!renderer->isBox() && !renderer->isRenderInline()))
265        return Path();
266
267    return absolutePathForRenderer(renderer);
268}
269
270} // namespace GestureTapHighlighter
271
272} // namespace WebCore
273