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 Computer, 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 "Frame.h" 35#include "FrameView.h" 36#include "GraphicsContext.h" 37#include "GraphicsTypes.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 45namespace WebCore { 46 47namespace { 48 49inline LayoutPoint ownerFrameToMainFrameOffset(const RenderObject* o) 50{ 51 ASSERT(o->node()); 52 Frame* containingFrame = o->frame(); 53 if (!containingFrame) 54 return LayoutPoint(); 55 56 Frame* mainFrame = containingFrame->page()->mainFrame(); 57 58 LayoutPoint mainFramePoint = mainFrame->view()->windowToContents(containingFrame->view()->contentsToWindow(IntPoint())); 59 return mainFramePoint; 60} 61 62AffineTransform localToAbsoluteTransform(const RenderObject* o) 63{ 64 AffineTransform transform; 65 LayoutPoint referencePoint; 66 67 while (o) { 68 RenderObject* nextContainer = o->container(); 69 if (!nextContainer) 70 break; 71 72 LayoutSize containerOffset = o->offsetFromContainer(nextContainer, referencePoint); 73 TransformationMatrix t; 74 o->getTransformFromContainer(nextContainer, containerOffset, t); 75 76 transform = t.toAffineTransform() * transform; 77 referencePoint.move(containerOffset); 78 o = nextContainer; 79 } 80 81 return transform; 82} 83 84inline bool contains(const LayoutRect& rect, int x) 85{ 86 return !rect.isEmpty() && x >= rect.x() && x <= rect.maxX(); 87} 88 89inline bool strikes(const LayoutRect& a, const LayoutRect& b) 90{ 91 return !a.isEmpty() && !b.isEmpty() 92 && a.x() <= b.maxX() && b.x() <= a.maxX() 93 && a.y() <= b.maxY() && b.y() <= a.maxY(); 94} 95 96inline void shiftXEdgesToContainIfStrikes(LayoutRect& rect, LayoutRect& other, bool isFirst) 97{ 98 if (rect.isEmpty()) 99 return; 100 101 if (other.isEmpty() || !strikes(rect, other)) 102 return; 103 104 LayoutUnit leftSide = std::min(rect.x(), other.x()); 105 LayoutUnit rightSide = std::max(rect.maxX(), other.maxX()); 106 107 rect.shiftXEdgeTo(leftSide); 108 rect.shiftMaxXEdgeTo(rightSide); 109 110 if (isFirst) 111 other.shiftMaxXEdgeTo(rightSide); 112 else 113 other.shiftXEdgeTo(leftSide); 114} 115 116inline void addHighlightRect(Path& path, const LayoutRect& rect, const LayoutRect& prev, const LayoutRect& next) 117{ 118 // The rounding check depends on the rects not intersecting eachother, 119 // or being contained for that matter. 120 ASSERT(!rect.intersects(prev)); 121 ASSERT(!rect.intersects(next)); 122 123 if (rect.isEmpty()) 124 return; 125 126 const int rounding = 4; 127 128 FloatRect copy(rect); 129 copy.inflateX(rounding); 130 copy.inflateY(rounding / 2); 131 132 FloatSize rounded(rounding * 1.8, rounding * 1.8); 133 FloatSize squared(0, 0); 134 135 path.addBeziersForRoundedRect(copy, 136 contains(prev, rect.x()) ? squared : rounded, 137 contains(prev, rect.maxX()) ? squared : rounded, 138 contains(next, rect.x()) ? squared : rounded, 139 contains(next, rect.maxX()) ? squared : rounded); 140} 141 142Path absolutePathForRenderer(RenderObject* const o) 143{ 144 ASSERT(o); 145 146 Vector<IntRect> rects; 147 LayoutPoint frameOffset = ownerFrameToMainFrameOffset(o); 148 o->addFocusRingRects(rects, frameOffset); 149 150 if (rects.isEmpty()) 151 return Path(); 152 153 // The basic idea is to allow up to three different boxes in order to highlight 154 // text with line breaks more nicer than using a bounding box. 155 156 // Merge all center boxes (all but the first and the last). 157 LayoutRect mid; 158 159 // Set the end value to integer. It ensures that no unsigned int overflow occurs 160 // in the test expression, in case of empty rects vector. 161 int end = rects.size() - 1; 162 for (int i = 1; i < end; ++i) 163 mid.uniteIfNonZero(rects.at(i)); 164 165 LayoutRect first; 166 LayoutRect last; 167 168 // Add the first box, but merge it with the center boxes if it intersects or if the center box is empty. 169 if (rects.size() && !rects.first().isEmpty()) { 170 // If the mid box is empty at this point, unite it with the first box. This allows the first box to be 171 // united with the last box if they intersect in the following check for last. Not uniting them would 172 // trigger in assert in addHighlighRect due to the first and the last box intersecting, but being passed 173 // as two separate boxes. 174 if (mid.isEmpty() || mid.intersects(rects.first())) 175 mid.unite(rects.first()); 176 else { 177 first = rects.first(); 178 shiftXEdgesToContainIfStrikes(mid, first, /* isFirst */ true); 179 } 180 } 181 182 // Add the last box, but merge it with the center boxes if it intersects. 183 if (rects.size() > 1 && !rects.last().isEmpty()) { 184 // Adjust center boxes to boundary of last 185 if (mid.intersects(rects.last())) 186 mid.unite(rects.last()); 187 else { 188 last = rects.last(); 189 shiftXEdgesToContainIfStrikes(mid, last, /* isFirst */ false); 190 } 191 } 192 193 Vector<LayoutRect> drawableRects; 194 if (!first.isEmpty()) 195 drawableRects.append(first); 196 if (!mid.isEmpty()) 197 drawableRects.append(mid); 198 if (!last.isEmpty()) 199 drawableRects.append(last); 200 201 // Clip the overflow rects if needed, before the ring path is formed to 202 // ensure rounded highlight rects. This clipping has the problem with nested 203 // divs with transforms, which could be resolved by proper Path::intersecting. 204 for (int i = drawableRects.size() - 1; i >= 0; --i) { 205 LayoutRect& ringRect = drawableRects.at(i); 206 LayoutPoint ringRectLocation = ringRect.location(); 207 208 ringRect.moveBy(-frameOffset); 209 210 RenderLayer* layer = o->enclosingLayer(); 211 RenderObject* currentRenderer = o; 212 213 // Check ancestor layers for overflow clip and intersect them. 214 for (; layer; layer = layer->parent()) { 215 RenderLayerModelObject* layerRenderer = layer->renderer(); 216 217 if (layerRenderer->hasOverflowClip() && layerRenderer != currentRenderer) { 218 bool containerSkipped = false; 219 // Skip ancestor layers that are not containers for the current renderer. 220 currentRenderer->container(layerRenderer, &containerSkipped); 221 if (containerSkipped) 222 continue; 223 ringRect.move(currentRenderer->offsetFromAncestorContainer(layerRenderer)); 224 currentRenderer = layerRenderer; 225 226 ASSERT(layerRenderer->isBox()); 227 ringRect.intersect(toRenderBox(layerRenderer)->borderBoxRect()); 228 229 if (ringRect.isEmpty()) 230 break; 231 } 232 } 233 234 if (ringRect.isEmpty()) { 235 drawableRects.remove(i); 236 continue; 237 } 238 // After clipping, reset the original position so that parents' transforms apply correctly. 239 ringRect.setLocation(ringRectLocation); 240 } 241 242 Path path; 243 for (size_t i = 0; i < drawableRects.size(); ++i) { 244 LayoutRect prev = i ? drawableRects.at(i - 1) : LayoutRect(); 245 LayoutRect next = i < (drawableRects.size() - 1) ? drawableRects.at(i + 1) : LayoutRect(); 246 addHighlightRect(path, drawableRects.at(i), prev, next); 247 } 248 249 path.transform(localToAbsoluteTransform(o)); 250 return path; 251} 252 253} // anonymous namespace 254 255namespace GestureTapHighlighter { 256 257Path pathForNodeHighlight(const Node* node) 258{ 259 RenderObject* renderer = node->renderer(); 260 261 if (!renderer || (!renderer->isBox() && !renderer->isRenderInline())) 262 return Path(); 263 264 return absolutePathForRenderer(renderer); 265} 266 267} // namespace GestureTapHighlighter 268 269} // namespace WebCore 270