1/**
2 * Copyright (C) 2003, 2006 Apple Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21#include "EllipsisBox.h"
22
23#include "Document.h"
24#include "FontCascade.h"
25#include "GraphicsContext.h"
26#include "HitTestResult.h"
27#include "InlineTextBox.h"
28#include "PaintInfo.h"
29#include "RootInlineBox.h"
30#include "TextRun.h"
31#include <wtf/IsoMallocInlines.h>
32
33namespace WebCore {
34
35WTF_MAKE_ISO_ALLOCATED_IMPL(EllipsisBox);
36
37EllipsisBox::EllipsisBox(RenderBlockFlow& renderer, const AtomicString& ellipsisStr, InlineFlowBox* parent, int width, int height, int y, bool firstLine, bool isHorizontal, InlineBox* markupBox)
38 : InlineElementBox(renderer, FloatPoint(0, y), width, firstLine, true, false, false, isHorizontal, 0, 0, parent)
39 , m_shouldPaintMarkupBox(markupBox)
40 , m_height(height)
41 , m_str(ellipsisStr)
42{
43#if !ASSERT_WITH_SECURITY_IMPLICATION_DISABLED
44 m_isEverInChildList = false;
45#endif
46}
47
48void EllipsisBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit lineTop, LayoutUnit lineBottom)
49{
50 GraphicsContext& context = paintInfo.context();
51 const RenderStyle& lineStyle = this->lineStyle();
52 Color textColor = lineStyle.visitedDependentColorWithColorFilter(CSSPropertyWebkitTextFillColor);
53 if (textColor != context.fillColor())
54 context.setFillColor(textColor);
55 bool setShadow = false;
56 if (lineStyle.textShadow()) {
57 Color shadowColor = lineStyle.colorByApplyingColorFilter(lineStyle.textShadow()->color());
58 context.setShadow(LayoutSize(lineStyle.textShadow()->x(), lineStyle.textShadow()->y()), lineStyle.textShadow()->radius(), shadowColor);
59 setShadow = true;
60 }
61
62 const FontCascade& font = lineStyle.fontCascade();
63 if (selectionState() != RenderObject::SelectionNone) {
64 paintSelection(context, paintOffset, lineStyle, font);
65
66 // Select the correct color for painting the text.
67 Color foreground = paintInfo.forceTextColor() ? paintInfo.forcedTextColor() : blockFlow().selectionForegroundColor();
68 if (foreground.isValid() && foreground != textColor)
69 context.setFillColor(foreground);
70 }
71
72 // FIXME: Why is this always LTR? Fix by passing correct text run flags below.
73 context.drawText(font, RenderBlock::constructTextRun(m_str, lineStyle, AllowTrailingExpansion), LayoutPoint(x() + paintOffset.x(), y() + paintOffset.y() + lineStyle.fontMetrics().ascent()));
74
75 // Restore the regular fill color.
76 if (textColor != context.fillColor())
77 context.setFillColor(textColor);
78
79 if (setShadow)
80 context.clearShadow();
81
82 paintMarkupBox(paintInfo, paintOffset, lineTop, lineBottom, lineStyle);
83}
84
85InlineBox* EllipsisBox::markupBox() const
86{
87 if (!m_shouldPaintMarkupBox)
88 return 0;
89
90 RootInlineBox* lastLine = blockFlow().lineAtIndex(blockFlow().lineCount() - 1);
91 if (!lastLine)
92 return 0;
93
94 // If the last line-box on the last line of a block is a link, -webkit-line-clamp paints that box after the ellipsis.
95 // It does not actually move the link.
96 InlineBox* anchorBox = lastLine->lastChild();
97 if (!anchorBox || !anchorBox->renderer().style().isLink())
98 return 0;
99
100 return anchorBox;
101}
102
103void EllipsisBox::paintMarkupBox(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit lineTop, LayoutUnit lineBottom, const RenderStyle& style)
104{
105 InlineBox* markupBox = this->markupBox();
106 if (!markupBox)
107 return;
108
109 LayoutPoint adjustedPaintOffset = paintOffset;
110 adjustedPaintOffset.move(x() + logicalWidth() - markupBox->x(),
111 y() + style.fontMetrics().ascent() - (markupBox->y() + markupBox->lineStyle().fontMetrics().ascent()));
112 markupBox->paint(paintInfo, adjustedPaintOffset, lineTop, lineBottom);
113}
114
115IntRect EllipsisBox::selectionRect()
116{
117 const RenderStyle& lineStyle = this->lineStyle();
118 const FontCascade& font = lineStyle.fontCascade();
119 const RootInlineBox& rootBox = root();
120 // FIXME: Why is this always LTR? Fix by passing correct text run flags below.
121 LayoutRect selectionRect = LayoutRect(x(), y() + rootBox.selectionTopAdjustedForPrecedingBlock(), 0_lu, rootBox.selectionHeightAdjustedForPrecedingBlock());
122 font.adjustSelectionRectForText(RenderBlock::constructTextRun(m_str, lineStyle, AllowTrailingExpansion), selectionRect);
123 // FIXME: use directional pixel snapping instead.
124 return enclosingIntRect(selectionRect);
125}
126
127void EllipsisBox::paintSelection(GraphicsContext& context, const LayoutPoint& paintOffset, const RenderStyle& style, const FontCascade& font)
128{
129 Color textColor = style.visitedDependentColorWithColorFilter(CSSPropertyColor);
130 Color c = blockFlow().selectionBackgroundColor();
131 if (!c.isVisible())
132 return;
133
134 // If the text color ends up being the same as the selection background, invert the selection
135 // background.
136 if (textColor == c)
137 c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue());
138
139 const RootInlineBox& rootBox = root();
140 GraphicsContextStateSaver stateSaver(context);
141 // FIXME: Why is this always LTR? Fix by passing correct text run flags below.
142 LayoutRect selectionRect = LayoutRect(x() + paintOffset.x(), y() + paintOffset.y() + rootBox.selectionTop(), 0_lu, rootBox.selectionHeight());
143 TextRun run = RenderBlock::constructTextRun(m_str, style, AllowTrailingExpansion);
144 font.adjustSelectionRectForText(run, selectionRect);
145 context.fillRect(snapRectToDevicePixelsWithWritingDirection(selectionRect, renderer().document().deviceScaleFactor(), run.ltr()), c);
146}
147
148bool EllipsisBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit lineTop, LayoutUnit lineBottom, HitTestAction hitTestAction)
149{
150 LayoutPoint adjustedLocation = accumulatedOffset + LayoutPoint(topLeft());
151
152 // Hit test the markup box.
153 if (InlineBox* markupBox = this->markupBox()) {
154 const RenderStyle& lineStyle = this->lineStyle();
155 LayoutUnit mtx = adjustedLocation.x() + logicalWidth() - markupBox->x();
156 LayoutUnit mty = adjustedLocation.y() + lineStyle.fontMetrics().ascent() - (markupBox->y() + markupBox->lineStyle().fontMetrics().ascent());
157 if (markupBox->nodeAtPoint(request, result, locationInContainer, LayoutPoint(mtx, mty), lineTop, lineBottom, hitTestAction)) {
158 blockFlow().updateHitTestResult(result, locationInContainer.point() - LayoutSize(mtx, mty));
159 return true;
160 }
161 }
162
163 LayoutRect boundsRect(adjustedLocation, LayoutSize(logicalWidth(), m_height));
164 if (visibleToHitTesting() && boundsRect.intersects(HitTestLocation::rectForPoint(locationInContainer.point(), 0, 0, 0, 0))) {
165 blockFlow().updateHitTestResult(result, locationInContainer.point() - toLayoutSize(adjustedLocation));
166 if (result.addNodeToListBasedTestResult(blockFlow().element(), request, locationInContainer, boundsRect) == HitTestProgress::Stop)
167 return true;
168 }
169
170 return false;
171}
172
173} // namespace WebCore
174