/* * 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 "Frame.h" #include "FrameView.h" #include "GraphicsContext.h" #include "GraphicsTypes.h" #include "Node.h" #include "Page.h" #include "RenderBoxModelObject.h" #include "RenderInline.h" #include "RenderLayer.h" #include "RenderObject.h" namespace WebCore { namespace { inline LayoutPoint ownerFrameToMainFrameOffset(const RenderObject* o) { ASSERT(o->node()); Frame* containingFrame = o->frame(); if (!containingFrame) return LayoutPoint(); Frame* mainFrame = containingFrame->page()->mainFrame(); LayoutPoint mainFramePoint = mainFrame->view()->windowToContents(containingFrame->view()->contentsToWindow(IntPoint())); return mainFramePoint; } 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; } inline bool contains(const LayoutRect& rect, int x) { return !rect.isEmpty() && x >= rect.x() && x <= rect.maxX(); } 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(); } inline void shiftXEdgesToContainIfStrikes(LayoutRect& rect, LayoutRect& other, bool isFirst) { if (rect.isEmpty()) return; if (other.isEmpty() || !strikes(rect, other)) return; LayoutUnit leftSide = std::min(rect.x(), other.x()); LayoutUnit rightSide = std::max(rect.maxX(), other.maxX()); rect.shiftXEdgeTo(leftSide); rect.shiftMaxXEdgeTo(rightSide); if (isFirst) other.shiftMaxXEdgeTo(rightSide); else other.shiftXEdgeTo(leftSide); } inline void addHighlightRect(Path& path, const LayoutRect& rect, const LayoutRect& prev, const LayoutRect& next) { // 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; const int rounding = 4; FloatRect copy(rect); copy.inflateX(rounding); copy.inflateY(rounding / 2); FloatSize rounded(rounding * 1.8, rounding * 1.8); FloatSize squared(0, 0); path.addBeziersForRoundedRect(copy, contains(prev, rect.x()) ? squared : rounded, contains(prev, rect.maxX()) ? squared : rounded, contains(next, rect.x()) ? squared : rounded, contains(next, rect.maxX()) ? squared : rounded); } Path absolutePathForRenderer(RenderObject* const o) { ASSERT(o); Vector rects; LayoutPoint frameOffset = ownerFrameToMainFrameOffset(o); o->addFocusRingRects(rects, frameOffset); if (rects.isEmpty()) return Path(); // 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; // 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) mid.uniteIfNonZero(rects.at(i)); LayoutRect first; LayoutRect last; // Add the first box, but merge it with the center boxes if it intersects. if (rects.size() && !rects.first().isEmpty()) { if (!mid.isEmpty() && mid.intersects(rects.first())) mid.unite(rects.first()); else { first = rects.first(); shiftXEdgesToContainIfStrikes(mid, first, /* isFirst */ true); } } // Add the last box, but merge it with the center boxes if it intersects. if (rects.size() > 1 && !rects.last().isEmpty()) { // Adjust center boxes to boundary of last if (!mid.isEmpty() && mid.intersects(rects.last())) mid.unite(rects.last()); else { last = rects.last(); shiftXEdgesToContainIfStrikes(mid, last, /* isFirst */ false); } } Vector drawableRects; if (!first.isEmpty()) drawableRects.append(first); if (!mid.isEmpty()) drawableRects.append(mid); if (!last.isEmpty()) drawableRects.append(last); // Clip the overflow rects if needed, before the ring path is formed to // ensure rounded highlight rects. This clipping has the problem with nested // divs with transforms, which could be resolved by proper Path::intersecting. 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. for (; layer; layer = layer->parent()) { RenderLayerModelObject* layerRenderer = layer->renderer(); if (layerRenderer->hasOverflowClip() && layerRenderer != currentRenderer) { bool containerSkipped = false; // Skip ancestor layers that are not containers for the current renderer. currentRenderer->container(layerRenderer, &containerSkipped); if (containerSkipped) continue; ringRect.move(currentRenderer->offsetFromAncestorContainer(layerRenderer)); 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; 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(); addHighlightRect(path, drawableRects.at(i), prev, next); } path.transform(localToAbsoluteTransform(o)); return path; } } // anonymous namespace namespace GestureTapHighlighter { Path pathForNodeHighlight(const Node* node) { RenderObject* renderer = node->renderer(); if (!renderer || (!renderer->isBox() && !renderer->isRenderInline())) return Path(); return absolutePathForRenderer(renderer); } } // namespace GestureTapHighlighter } // namespace WebCore