#include "config.h"
#include "TextIndicator.h"
#include "Document.h"
#include "Frame.h"
#include "FrameSelection.h"
#include "FrameSnapshotting.h"
#include "FrameView.h"
#include "GeometryUtilities.h"
#include "GraphicsContext.h"
#include "ImageBuffer.h"
#include "IntRect.h"
#include "Page.h"
using namespace WebCore;
#if ENABLE(LEGACY_TEXT_INDICATOR_STYLE)
const float horizontalBorder = 3;
const float verticalBorder = 1;
const float dropShadowBlurRadius = 1.5;
#else
const float horizontalBorder = 2;
const float verticalBorder = 1;
const float dropShadowBlurRadius = 12;
#endif
namespace WebCore {
static FloatRect outsetIndicatorRectIncludingShadow(const FloatRect rect)
{
FloatRect outsetRect = rect;
outsetRect.inflateX(dropShadowBlurRadius + horizontalBorder);
outsetRect.inflateY(dropShadowBlurRadius + verticalBorder);
return outsetRect;
}
static bool textIndicatorsForTextRectsOverlap(const Vector<FloatRect>& textRects)
{
size_t count = textRects.size();
if (count <= 1)
return false;
Vector<FloatRect> indicatorRects;
indicatorRects.reserveInitialCapacity(count);
for (size_t i = 0; i < count; ++i) {
FloatRect indicatorRect = outsetIndicatorRectIncludingShadow(textRects[i]);
for (size_t j = indicatorRects.size(); j; ) {
--j;
if (indicatorRect.intersects(indicatorRects[j]))
return true;
}
indicatorRects.uncheckedAppend(indicatorRect);
}
return false;
}
PassRefPtr<TextIndicator> TextIndicator::create(const TextIndicatorData& data)
{
return adoptRef(new TextIndicator(data));
}
PassRefPtr<TextIndicator> TextIndicator::createWithRange(const Range& range, TextIndicatorPresentationTransition presentationTransition)
{
Frame* frame = range.startContainer()->document().frame();
if (!frame)
return nullptr;
VisibleSelection oldSelection = frame->selection().selection();
frame->selection().setSelection(&range);
RefPtr<TextIndicator> indicator = TextIndicator::createWithSelectionInFrame(*frame, presentationTransition);
frame->selection().setSelection(oldSelection);
return indicator.release();
}
static PassRefPtr<Image> snapshotSelectionWithHighlight(Frame& frame)
{
auto& selection = frame.selection();
if (!selection.isRange())
return nullptr;
FloatRect selectionBounds = selection.selectionBounds();
if (selectionBounds.isEmpty())
return nullptr;
std::unique_ptr<ImageBuffer> snapshot = snapshotFrameRect(frame, enclosingIntRect(selectionBounds), 0);
if (!snapshot)
return nullptr;
return snapshot->copyImage(CopyBackingStore, Unscaled);
}
PassRefPtr<TextIndicator> TextIndicator::createWithSelectionInFrame(Frame& frame, TextIndicatorPresentationTransition presentationTransition)
{
IntRect selectionRect = enclosingIntRect(frame.selection().selectionBounds());
std::unique_ptr<ImageBuffer> indicatorBuffer = snapshotSelection(frame, SnapshotOptionsForceBlackText);
if (!indicatorBuffer)
return nullptr;
RefPtr<Image> indicatorBitmap = indicatorBuffer->copyImage(CopyBackingStore, Unscaled);
if (!indicatorBitmap)
return nullptr;
RefPtr<Image> indicatorBitmapWithHighlight;
if (presentationTransition == TextIndicatorPresentationTransition::BounceAndCrossfade || presentationTransition == TextIndicatorPresentationTransition::Crossfade)
indicatorBitmapWithHighlight = snapshotSelectionWithHighlight(frame);
IntRect selectionRectInRootViewCoordinates = frame.view()->contentsToRootView(selectionRect);
Vector<FloatRect> textRects;
frame.selection().getClippedVisibleTextRectangles(textRects);
FloatRect textBoundingRectInRootViewCoordinates;
Vector<FloatRect> textRectsInRootViewCoordinates;
for (const FloatRect& textRect : textRects) {
FloatRect textRectInRootViewCoordinates = frame.view()->contentsToRootView(enclosingIntRect(textRect));
textRectsInRootViewCoordinates.append(textRectInRootViewCoordinates);
textBoundingRectInRootViewCoordinates.unite(textRectInRootViewCoordinates);
}
Vector<FloatRect> textRectsInBoundingRectCoordinates;
for (auto rect : textRectsInRootViewCoordinates) {
rect.moveBy(-textBoundingRectInRootViewCoordinates.location());
textRectsInBoundingRectCoordinates.append(rect);
}
TextIndicatorData data;
data.selectionRectInRootViewCoordinates = selectionRectInRootViewCoordinates;
data.textBoundingRectInRootViewCoordinates = textBoundingRectInRootViewCoordinates;
data.textRectsInBoundingRectCoordinates = textRectsInBoundingRectCoordinates;
data.contentImageScaleFactor = frame.page()->deviceScaleFactor();
data.contentImage = indicatorBitmap;
data.contentImageWithHighlight = indicatorBitmapWithHighlight;
data.presentationTransition = presentationTransition;
return TextIndicator::create(data);
}
TextIndicator::TextIndicator(const TextIndicatorData& data)
: m_data(data)
{
ASSERT(m_data.contentImageScaleFactor != 1 || m_data.contentImage->size() == enclosingIntRect(m_data.selectionRectInRootViewCoordinates).size());
if (textIndicatorsForTextRectsOverlap(m_data.textRectsInBoundingRectCoordinates)) {
m_data.textRectsInBoundingRectCoordinates[0] = unionRect(m_data.textRectsInBoundingRectCoordinates);
m_data.textRectsInBoundingRectCoordinates.shrink(1);
}
}
TextIndicator::~TextIndicator()
{
}
bool TextIndicator::wantsBounce() const
{
switch (m_data.presentationTransition) {
case TextIndicatorPresentationTransition::BounceAndCrossfade:
case TextIndicatorPresentationTransition::Bounce:
return true;
case TextIndicatorPresentationTransition::FadeIn:
case TextIndicatorPresentationTransition::Crossfade:
case TextIndicatorPresentationTransition::None:
return false;
}
ASSERT_NOT_REACHED();
return false;
}
bool TextIndicator::wantsContentCrossfade() const
{
if (!m_data.contentImageWithHighlight)
return false;
switch (m_data.presentationTransition) {
case TextIndicatorPresentationTransition::BounceAndCrossfade:
case TextIndicatorPresentationTransition::Crossfade:
return true;
case TextIndicatorPresentationTransition::Bounce:
case TextIndicatorPresentationTransition::FadeIn:
case TextIndicatorPresentationTransition::None:
return false;
}
ASSERT_NOT_REACHED();
return false;
}
bool TextIndicator::wantsFadeIn() const
{
switch (m_data.presentationTransition) {
case TextIndicatorPresentationTransition::FadeIn:
return true;
case TextIndicatorPresentationTransition::Bounce:
case TextIndicatorPresentationTransition::BounceAndCrossfade:
case TextIndicatorPresentationTransition::Crossfade:
case TextIndicatorPresentationTransition::None:
return false;
}
ASSERT_NOT_REACHED();
return false;
}
bool TextIndicator::wantsManualAnimation() const
{
switch (m_data.presentationTransition) {
case TextIndicatorPresentationTransition::FadeIn:
case TextIndicatorPresentationTransition::Crossfade:
return true;
case TextIndicatorPresentationTransition::Bounce:
case TextIndicatorPresentationTransition::BounceAndCrossfade:
case TextIndicatorPresentationTransition::None:
return false;
}
ASSERT_NOT_REACHED();
return false;
}
}