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