| /* |
| * 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" |
| #include "MainFrame.h" |
| #include "Node.h" |
| #include "Page.h" |
| #include "RenderBoxModelObject.h" |
| #include "RenderInline.h" |
| #include "RenderLayer.h" |
| #include "RenderObject.h" |
| #include "RenderView.h" |
| |
| namespace WebCore { |
| |
| namespace { |
| |
| inline LayoutPoint ownerFrameToMainFrameOffset(const RenderObject* o) |
| { |
| ASSERT(o->node()); |
| Frame& containingFrame = o->frame(); |
| |
| 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<IntRect> 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 or if the center box is empty. |
| if (rects.size() && !rects.first().isEmpty()) { |
| // 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())) |
| 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.intersects(rects.last())) |
| mid.unite(rects.last()); |
| else { |
| last = rects.last(); |
| shiftXEdgesToContainIfStrikes(mid, last, /* isFirst */ false); |
| } |
| } |
| |
| Vector<LayoutRect> 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. |
| 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; |
| 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(); |
| 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 |