GestureTapHighlighter.cpp 9.26 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
/*
 * Copyright (C) 2011 Google Inc. All rights reserved.
 * Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "GestureTapHighlighter.h"

#include "Element.h"
#include "FrameView.h"
#include "GraphicsContext.h"
#include "GraphicsTypes.h"
darin@apple.com's avatar
darin@apple.com committed
37
#include "MainFrame.h"
38 39 40 41
#include "Node.h"
#include "Page.h"
#include "RenderBoxModelObject.h"
#include "RenderInline.h"
42
#include "RenderLayer.h"
43
#include "RenderObject.h"
44
#include "RenderView.h"
45 46 47 48 49

namespace WebCore {

namespace {

50
inline 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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
}

AffineTransform localToAbsoluteTransform(const RenderObject* o)
{
    AffineTransform transform;
    LayoutPoint referencePoint;

    while (o) {
        RenderObject* nextContainer = o->container();
        if (!nextContainer)
            break;

        LayoutSize containerOffset = o->offsetFromContainer(nextContainer, referencePoint);
        TransformationMatrix t;
        o->getTransformFromContainer(nextContainer, containerOffset, t);

        transform = t.toAffineTransform() * transform;
        referencePoint.move(containerOffset);
        o = nextContainer;
    }

    return transform;
}

83
inline bool contains(const LayoutRect& rect, int x)
84
{
85 86
    return !rect.isEmpty() && x >= rect.x() && x <= rect.maxX();
}
87

88 89 90 91 92 93
inline bool strikes(const LayoutRect& a, const LayoutRect& b)
{
    return !a.isEmpty() && !b.isEmpty()
        && a.x() <= b.maxX() && b.x() <= a.maxX()
        && a.y() <= b.maxY() && b.y() <= a.maxY();
}
94

95
inline void shiftXEdgesToContainIfStrikes(LayoutRect& rect, LayoutRect& other, bool isFirst)
96
{
97 98
    if (rect.isEmpty())
        return;
99

100 101 102 103 104
    if (other.isEmpty() || !strikes(rect, other))
        return;

    LayoutUnit leftSide = std::min(rect.x(), other.x());
    LayoutUnit rightSide = std::max(rect.maxX(), other.maxX());
105

106 107 108 109 110 111 112
    rect.shiftXEdgeTo(leftSide);
    rect.shiftMaxXEdgeTo(rightSide);

    if (isFirst)
        other.shiftMaxXEdgeTo(rightSide);
    else
        other.shiftXEdgeTo(leftSide);
113 114
}

115
inline void addHighlightRect(Path& path, const LayoutRect& rect, const LayoutRect& prev, const LayoutRect& next)
116
{
117 118 119 120 121 122 123 124
    // The rounding check depends on the rects not intersecting eachother,
    // or being contained for that matter.
    ASSERT(!rect.intersects(prev));
    ASSERT(!rect.intersects(next));

    if (rect.isEmpty())
        return;

125
    const int rounding = 4;
126

127 128 129
    FloatRect copy(rect);
    copy.inflateX(rounding);
    copy.inflateY(rounding / 2);
130

131 132
    FloatSize rounded(rounding * 1.8, rounding * 1.8);
    FloatSize squared(0, 0);
133

134
    path.addBeziersForRoundedRect(copy,
135 136 137 138
            contains(prev, rect.x()) ? squared : rounded,
            contains(prev, rect.maxX()) ? squared : rounded,
            contains(next, rect.x()) ? squared : rounded,
            contains(next, rect.maxX()) ? squared : rounded);
139 140
}

141
Path absolutePathForRenderer(RenderObject* const o)
142 143 144
{
    ASSERT(o);

145
    Vector<IntRect> rects;
146 147
    LayoutPoint frameOffset = ownerFrameToMainFrameOffset(o);
    o->addFocusRingRects(rects, frameOffset);
148

149
    if (rects.isEmpty())
150
        return Path();
151

152 153 154 155 156
    // The basic idea is to allow up to three different boxes in order to highlight
    // text with line breaks more nicer than using a bounding box.

    // Merge all center boxes (all but the first and the last).
    LayoutRect mid;
157 158 159 160 161

    // Set the end value to integer. It ensures that no unsigned int overflow occurs
    // in the test expression, in case of empty rects vector.
    int end = rects.size() - 1;
    for (int i = 1; i < end; ++i)
162 163
        mid.uniteIfNonZero(rects.at(i));

164 165
    LayoutRect first;
    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 170 171 172 173
        // If the mid box is empty at this point, unite it with the first box. This allows the first box to be
        // united with the last box if they intersect in the following check for last. Not uniting them would
        // trigger in assert in addHighlighRect due to the first and the last box intersecting, but being passed
        // as two separate boxes.
        if (mid.isEmpty() || mid.intersects(rects.first()))
174 175 176 177 178
            mid.unite(rects.first());
        else {
            first = rects.first();
            shiftXEdgesToContainIfStrikes(mid, first, /* isFirst */ true);
        }
179 180
    }

181 182
    // Add the last box, but merge it with the center boxes if it intersects.
    if (rects.size() > 1 && !rects.last().isEmpty()) {
183
        // Adjust center boxes to boundary of last
184
        if (mid.intersects(rects.last()))
185 186 187 188 189
            mid.unite(rects.last());
        else {
            last = rects.last();
            shiftXEdgesToContainIfStrikes(mid, last, /* isFirst */ false);
        }
190 191
    }

192 193 194 195 196 197 198 199
    Vector<LayoutRect> drawableRects;
    if (!first.isEmpty())
        drawableRects.append(first);
    if (!mid.isEmpty())
        drawableRects.append(mid);
    if (!last.isEmpty())
        drawableRects.append(last);

200
    // Clip the overflow rects if needed, before the ring path is formed to
201
    // ensure rounded highlight rects.
202 203 204 205 206 207 208 209 210 211
    for (int i = drawableRects.size() - 1; i >= 0; --i) {
        LayoutRect& ringRect = drawableRects.at(i);
        LayoutPoint ringRectLocation = ringRect.location();

        ringRect.moveBy(-frameOffset);

        RenderLayer* layer = o->enclosingLayer();
        RenderObject* currentRenderer = o;

        // 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 217 218 219 220
                bool containerSkipped = false;
                // Skip ancestor layers that are not containers for the current renderer.
                currentRenderer->container(layerRenderer, &containerSkipped);
                if (containerSkipped)
                    continue;
221 222 223 224 225 226
                FloatQuad ringQuad = currentRenderer->localToContainerQuad(FloatQuad(ringRect), layerRenderer);
                // Ignore quads that are not rectangular, since we can not currently highlight them nicely.
                if (ringQuad.isRectilinear())
                    ringRect = ringQuad.enclosingBoundingBox();
                else
                    ringRect = LayoutRect();
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
                currentRenderer = layerRenderer;

                ASSERT(layerRenderer->isBox());
                ringRect.intersect(toRenderBox(layerRenderer)->borderBoxRect());

                if (ringRect.isEmpty())
                    break;
            }
        }

        if (ringRect.isEmpty()) {
            drawableRects.remove(i);
            continue;
        }
        // After clipping, reset the original position so that parents' transforms apply correctly.
        ringRect.setLocation(ringRectLocation);
    }

    Path path;
246 247 248
    for (size_t i = 0; i < drawableRects.size(); ++i) {
        LayoutRect prev = i ? drawableRects.at(i - 1) : LayoutRect();
        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 254 255 256 257 258 259 260 261 262 263 264
    return path;
}

} // anonymous namespace

namespace GestureTapHighlighter {

Path pathForNodeHighlight(const Node* node)
{
    RenderObject* renderer = node->renderer();

    if (!renderer || (!renderer->isBox() && !renderer->isRenderInline()))
265
        return Path();
266

267
    return absolutePathForRenderer(renderer);
268 269 270 271 272
}

} // namespace GestureTapHighlighter

} // namespace WebCore