Move parseFontFaceDescriptor to CSSPropertyParser.cpp
[chromium-blink-merge.git] / third_party / WebKit / Source / core / dom / FirstLetterPseudoElement.cpp
blob08d3a1c00b881de7a045f75bbd5140ccb52c246d
1 /*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2007 David Smith (catfish.man@gmail.com)
5 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved.
6 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
24 #include "config.h"
25 #include "core/dom/FirstLetterPseudoElement.h"
27 #include "core/dom/Element.h"
28 #include "core/layout/GeneratedChildren.h"
29 #include "core/layout/LayoutObject.h"
30 #include "core/layout/LayoutObjectInlines.h"
31 #include "core/layout/LayoutText.h"
32 #include "core/layout/LayoutTextFragment.h"
33 #include "wtf/TemporaryChange.h"
34 #include "wtf/text/WTFString.h"
35 #include "wtf/text/icu/UnicodeIcu.h"
37 namespace blink {
39 using namespace WTF;
40 using namespace Unicode;
42 // CSS 2.1 http://www.w3.org/TR/CSS21/selector.html#first-letter
43 // "Punctuation (i.e, characters defined in Unicode [UNICODE] in the "open" (Ps), "close" (Pe),
44 // "initial" (Pi). "final" (Pf) and "other" (Po) punctuation classes), that precedes or follows the first letter should be included"
45 static inline bool isPunctuationForFirstLetter(UChar c)
47 CharCategory charCategory = category(c);
48 return charCategory == Punctuation_Open
49 || charCategory == Punctuation_Close
50 || charCategory == Punctuation_InitialQuote
51 || charCategory == Punctuation_FinalQuote
52 || charCategory == Punctuation_Other;
55 static inline bool isSpaceForFirstLetter(UChar c)
57 return isSpaceOrNewline(c) || c == noBreakSpaceCharacter;
60 unsigned FirstLetterPseudoElement::firstLetterLength(const String& text)
62 unsigned length = 0;
63 unsigned textLength = text.length();
65 if (textLength == 0)
66 return length;
68 // Account for leading spaces first.
69 while (length < textLength && isSpaceForFirstLetter(text[length]))
70 length++;
71 // Now account for leading punctuation.
72 while (length < textLength && isPunctuationForFirstLetter(text[length]))
73 length++;
75 // Bail if we didn't find a letter before the end of the text or before a space.
76 if (isSpaceForFirstLetter(text[length]) || length == textLength)
77 return 0;
79 // Account the next character for first letter.
80 length++;
82 // Keep looking for allowed punctuation for the :first-letter.
83 for (; length < textLength; ++length) {
84 UChar c = text[length];
85 if (!isPunctuationForFirstLetter(c))
86 break;
88 return length;
91 // Once we see any of these layoutObjects we can stop looking for first-letter as
92 // they signal the end of the first line of text.
93 static bool isInvalidFirstLetterLayoutObject(const LayoutObject* obj)
95 return (obj->isBR() || (obj->isText() && toLayoutText(obj)->isWordBreak()));
98 LayoutObject* FirstLetterPseudoElement::firstLetterTextLayoutObject(const Element& element)
100 LayoutObject* parentLayoutObject = 0;
102 // If we are looking at a first letter element then we need to find the
103 // first letter text layoutObject from the parent node, and not ourselves.
104 if (element.isFirstLetterPseudoElement())
105 parentLayoutObject = element.parentOrShadowHostElement()->layoutObject();
106 else
107 parentLayoutObject = element.layoutObject();
109 if (!parentLayoutObject
110 || !parentLayoutObject->style()->hasPseudoStyle(FIRST_LETTER)
111 || !canHaveGeneratedChildren(*parentLayoutObject)
112 || !(parentLayoutObject->isLayoutBlockFlow() || parentLayoutObject->isLayoutButton()))
113 return nullptr;
115 // Drill down into our children and look for our first text child.
116 LayoutObject* firstLetterTextLayoutObject = parentLayoutObject->slowFirstChild();
117 while (firstLetterTextLayoutObject) {
118 // This can be called when the first letter layoutObject is already in the tree. We do not
119 // want to consider that layoutObject for our text layoutObject so we go to the sibling (which is
120 // the LayoutTextFragment for the remaining text).
121 if (firstLetterTextLayoutObject->style() && firstLetterTextLayoutObject->style()->styleType() == FIRST_LETTER) {
122 firstLetterTextLayoutObject = firstLetterTextLayoutObject->nextSibling();
123 } else if (firstLetterTextLayoutObject->isText()) {
124 // FIXME: If there is leading punctuation in a different LayoutText than
125 // the first letter, we'll not apply the correct style to it.
126 RefPtr<StringImpl> str = toLayoutText(firstLetterTextLayoutObject)->isTextFragment() ?
127 toLayoutTextFragment(firstLetterTextLayoutObject)->completeText() :
128 toLayoutText(firstLetterTextLayoutObject)->originalText();
129 if (firstLetterLength(str.get()) || isInvalidFirstLetterLayoutObject(firstLetterTextLayoutObject))
130 break;
131 firstLetterTextLayoutObject = firstLetterTextLayoutObject->nextSibling();
132 } else if (firstLetterTextLayoutObject->isListMarker()) {
133 firstLetterTextLayoutObject = firstLetterTextLayoutObject->nextSibling();
134 } else if (firstLetterTextLayoutObject->isFloatingOrOutOfFlowPositioned()) {
135 if (firstLetterTextLayoutObject->style()->styleType() == FIRST_LETTER) {
136 firstLetterTextLayoutObject = firstLetterTextLayoutObject->slowFirstChild();
137 break;
139 firstLetterTextLayoutObject = firstLetterTextLayoutObject->nextSibling();
140 } else if (firstLetterTextLayoutObject->isReplaced() || firstLetterTextLayoutObject->isLayoutButton()
141 || firstLetterTextLayoutObject->isMenuList()) {
142 return nullptr;
143 } else if (firstLetterTextLayoutObject->isFlexibleBoxIncludingDeprecated() || firstLetterTextLayoutObject->isLayoutGrid()) {
144 firstLetterTextLayoutObject = firstLetterTextLayoutObject->nextSibling();
145 } else if (!firstLetterTextLayoutObject->isInline()
146 && firstLetterTextLayoutObject->style()->hasPseudoStyle(FIRST_LETTER)
147 && canHaveGeneratedChildren(*firstLetterTextLayoutObject)) {
148 // There is a layoutObject further down the tree which has FIRST_LETTER set. When that node
149 // is attached we will handle setting up the first letter then.
150 return nullptr;
151 } else {
152 firstLetterTextLayoutObject = firstLetterTextLayoutObject->slowFirstChild();
156 // No first letter text to display, we're done.
157 // FIXME: This black-list of disallowed LayoutText subclasses is fragile. crbug.com/422336.
158 // Should counter be on this list? What about LayoutTextFragment?
159 if (!firstLetterTextLayoutObject || !firstLetterTextLayoutObject->isText() || isInvalidFirstLetterLayoutObject(firstLetterTextLayoutObject))
160 return nullptr;
162 return firstLetterTextLayoutObject;
165 FirstLetterPseudoElement::FirstLetterPseudoElement(Element* parent)
166 : PseudoElement(parent, FIRST_LETTER)
167 , m_remainingTextLayoutObject(nullptr)
171 FirstLetterPseudoElement::~FirstLetterPseudoElement()
175 void FirstLetterPseudoElement::updateTextFragments()
177 String oldText = m_remainingTextLayoutObject->completeText();
178 ASSERT(oldText.impl());
180 unsigned length = FirstLetterPseudoElement::firstLetterLength(oldText);
181 m_remainingTextLayoutObject->setTextFragment(oldText.impl()->substring(length, oldText.length()), length, oldText.length() - length);
182 m_remainingTextLayoutObject->dirtyLineBoxes();
184 for (auto child = layoutObject()->slowFirstChild(); child; child = child->nextSibling()) {
185 if (!child->isText() || !toLayoutText(child)->isTextFragment())
186 continue;
187 LayoutTextFragment* childFragment = toLayoutTextFragment(child);
188 if (childFragment->firstLetterPseudoElement() != this)
189 continue;
191 childFragment->setTextFragment(oldText.impl()->substring(0, length), 0, length);
192 childFragment->dirtyLineBoxes();
194 // Make sure the first-letter layoutObject is set to require a layout as it
195 // needs to re-create the line boxes. The remaining text layoutObject
196 // will be marked by the LayoutText::setText.
197 childFragment->setNeedsLayoutAndPrefWidthsRecalc(LayoutInvalidationReason::TextChanged);
198 break;
202 void FirstLetterPseudoElement::setRemainingTextLayoutObject(LayoutTextFragment* fragment)
204 // The text fragment we get our content from is being destroyed. We need
205 // to tell our parent element to recalcStyle so we can get cleaned up
206 // as well.
207 if (!fragment)
208 setNeedsStyleRecalc(LocalStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::PseudoClass));
210 m_remainingTextLayoutObject = fragment;
213 void FirstLetterPseudoElement::attach(const AttachContext& context)
215 PseudoElement::attach(context);
216 attachFirstLetterTextLayoutObjects();
219 void FirstLetterPseudoElement::detach(const AttachContext& context)
221 if (m_remainingTextLayoutObject) {
222 if (m_remainingTextLayoutObject->node() && document().isActive()) {
223 Text* textNode = toText(m_remainingTextLayoutObject->node());
224 m_remainingTextLayoutObject->setTextFragment(textNode->dataImpl(), 0, textNode->dataImpl()->length());
226 m_remainingTextLayoutObject->setFirstLetterPseudoElement(nullptr);
227 m_remainingTextLayoutObject->setIsRemainingTextLayoutObject(false);
229 m_remainingTextLayoutObject = nullptr;
231 PseudoElement::detach(context);
234 ComputedStyle* FirstLetterPseudoElement::styleForFirstLetter(LayoutObject* layoutObjectContainer)
236 ASSERT(layoutObjectContainer);
238 LayoutObject* styleContainer = parentOrShadowHostElement()->layoutObject();
239 ASSERT(styleContainer);
241 // We always force the pseudo style to recompute as the first-letter style
242 // computed by the style container may not have taken the layoutObjects styles
243 // into account.
244 styleContainer->mutableStyle()->removeCachedPseudoStyle(FIRST_LETTER);
246 ComputedStyle* pseudoStyle = styleContainer->getCachedPseudoStyle(FIRST_LETTER, layoutObjectContainer->firstLineStyle());
247 ASSERT(pseudoStyle);
249 return pseudoStyle;
252 void FirstLetterPseudoElement::attachFirstLetterTextLayoutObjects()
254 LayoutObject* nextLayoutObject = FirstLetterPseudoElement::firstLetterTextLayoutObject(*this);
255 ASSERT(nextLayoutObject);
256 ASSERT(nextLayoutObject->isText());
258 // The original string is going to be either a generated content string or a DOM node's
259 // string. We want the original string before it got transformed in case first-letter has
260 // no text-transform or a different text-transform applied to it.
261 String oldText = toLayoutText(nextLayoutObject)->isTextFragment() ? toLayoutTextFragment(nextLayoutObject)->completeText() : toLayoutText(nextLayoutObject)->originalText();
262 ASSERT(oldText.impl());
264 ComputedStyle* pseudoStyle = styleForFirstLetter(nextLayoutObject->parent());
265 layoutObject()->setStyle(pseudoStyle);
267 // FIXME: This would already have been calculated in firstLetterLayoutObject. Can we pass the length through?
268 unsigned length = FirstLetterPseudoElement::firstLetterLength(oldText);
270 // Construct a text fragment for the text after the first letter.
271 // This text fragment might be empty.
272 LayoutTextFragment* remainingText =
273 new LayoutTextFragment(nextLayoutObject->node() ? nextLayoutObject->node() : &nextLayoutObject->document(), oldText.impl(), length, oldText.length() - length);
274 remainingText->setFirstLetterPseudoElement(this);
275 remainingText->setIsRemainingTextLayoutObject(true);
276 remainingText->setStyle(nextLayoutObject->mutableStyle());
278 if (remainingText->node())
279 remainingText->node()->setLayoutObject(remainingText);
281 m_remainingTextLayoutObject = remainingText;
283 LayoutObject* nextSibling = layoutObject()->nextSibling();
284 layoutObject()->parent()->addChild(remainingText, nextSibling);
286 // Construct text fragment for the first letter.
287 LayoutTextFragment* letter = new LayoutTextFragment(&nextLayoutObject->document(), oldText.impl(), 0, length);
288 letter->setFirstLetterPseudoElement(this);
289 letter->setStyle(pseudoStyle);
290 layoutObject()->addChild(letter);
292 nextLayoutObject->destroy();
295 void FirstLetterPseudoElement::didRecalcStyle(StyleRecalcChange)
297 if (!layoutObject())
298 return;
300 // The layoutObjects inside pseudo elements are anonymous so they don't get notified of recalcStyle and must have
301 // the style propagated downward manually similar to LayoutObject::propagateStyleToAnonymousChildren.
302 LayoutObject* layoutObject = this->layoutObject();
303 for (LayoutObject* child = layoutObject->nextInPreOrder(layoutObject); child; child = child->nextInPreOrder(layoutObject)) {
304 // We need to re-calculate the correct style for the first letter element
305 // and then apply that to the container and the text fragment inside.
306 if (child->style()->styleType() == FIRST_LETTER && m_remainingTextLayoutObject) {
307 if (ComputedStyle* pseudoStyle = styleForFirstLetter(m_remainingTextLayoutObject->parent()))
308 child->setPseudoStyle(pseudoStyle);
309 continue;
312 // We only manage the style for the generated content items.
313 if (!child->isText() && !child->isQuote() && !child->isImage())
314 continue;
316 child->setPseudoStyle(layoutObject->mutableStyle());
320 } // namespace blink