Index: Source/core/rendering/RenderBlock.cpp |
diff --git a/Source/core/rendering/RenderBlock.cpp b/Source/core/rendering/RenderBlock.cpp |
index a23bfbb8af6da719c0329019d101c1b0f6ab86d1..23e86cc6a186b77c8906542fee1ac5a41b17cd88 100644 |
--- a/Source/core/rendering/RenderBlock.cpp |
+++ b/Source/core/rendering/RenderBlock.cpp |
@@ -4,6 +4,7 @@ |
* (C) 2007 David Smith (catfish.man@gmail.com) |
* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. |
* Copyright (C) Research In Motion Limited 2010. All rights reserved. |
+ * Copyright (C) 2014 Samsung Electronics. All rights reserved. |
* |
* This library is free software; you can redistribute it and/or |
* modify it under the terms of the GNU Library General Public |
@@ -4015,24 +4016,6 @@ static RenderStyle* styleForFirstLetter(RenderObject* firstLetterBlock, RenderOb |
return pseudoStyle; |
} |
-// CSS 2.1 http://www.w3.org/TR/CSS21/selector.html#first-letter |
-// "Punctuation (i.e, characters defined in Unicode [UNICODE] in the "open" (Ps), "close" (Pe), |
-// "initial" (Pi). "final" (Pf) and "other" (Po) punctuation classes), that precedes or follows the first letter should be included" |
-static inline bool isPunctuationForFirstLetter(UChar c) |
-{ |
- CharCategory charCategory = category(c); |
- return charCategory == Punctuation_Open |
- || charCategory == Punctuation_Close |
- || charCategory == Punctuation_InitialQuote |
- || charCategory == Punctuation_FinalQuote |
- || charCategory == Punctuation_Other; |
-} |
- |
-static inline bool isSpaceForFirstLetter(UChar c) |
-{ |
- return isSpaceOrNewline(c) || c == noBreakSpace; |
-} |
- |
static inline RenderObject* findFirstLetterBlock(RenderBlock* start) |
{ |
RenderObject* firstLetterBlock = start; |
@@ -4106,40 +4089,6 @@ void RenderBlock::updateFirstLetterStyle(RenderObject* firstLetterBlock, RenderO |
} |
} |
-static inline unsigned firstLetterLength(const String& text) |
-{ |
- unsigned length = 0; |
- unsigned textLength = text.length(); |
- |
- // Account for leading spaces first. |
- while (length < textLength && isSpaceForFirstLetter(text[length])) |
- length++; |
- |
- // Now account for leading punctuation. |
- while (length < textLength && isPunctuationForFirstLetter(text[length])) |
- length++; |
- |
- // Bail if we didn't find a letter before the end of the text or before a space. |
- if (isSpaceForFirstLetter(text[length]) || (textLength && length == textLength)) |
- return 0; |
- |
- // Account the next character for first letter. |
- length++; |
- |
- // Keep looking allowed punctuation for the :first-letter. |
- for (unsigned scanLength = length; scanLength < textLength; ++scanLength) { |
- UChar c = text[scanLength]; |
- |
- if (!isPunctuationForFirstLetter(c)) |
- break; |
- |
- length = scanLength + 1; |
- } |
- |
- // FIXME: If textLength is 0, length may still be 1! |
- return length; |
-} |
- |
void RenderBlock::createFirstLetterRenderer(RenderObject* firstLetterBlock, RenderObject* currentChild, unsigned length) |
{ |
ASSERT(length && currentChild->isText()); |
@@ -4184,11 +4133,205 @@ void RenderBlock::createFirstLetterRenderer(RenderObject* firstLetterBlock, Rend |
textObj->destroy(); |
} |
+static bool isRendererAllowedForFirstLetter(RenderObject* renderer) |
+{ |
+ // FIXME: This black-list of disallowed RenderText subclasses is fragile. |
+ // Should counter be on this list? What about RenderTextFragment? |
+ return renderer->isText() && !renderer->isBR() && !toRenderText(renderer)->isWordBreak(); |
+} |
+ |
+typedef Vector<std::pair<RenderObject*, unsigned> > FirstLetterRenderersList; |
+ |
+enum FirstLetterSearchState { |
+ SearchLeadingSpaces, |
+ SearchLeadingPunctuation, |
+ SearchFirstLetterCharacter, |
+ SearchTrailingPunctuation |
+}; |
+ |
+class FirstLetterFinder { |
+ WTF_MAKE_NONCOPYABLE(FirstLetterFinder); |
+public: |
+ FirstLetterFinder(RenderObject* block) |
+ : m_containerBlock(block) |
+ , m_renderers() |
+ , m_searchState(SearchLeadingSpaces) |
+ , m_firstLetterFound(false) |
+ { |
+ // The purpose of this class is to find the renderers that would be part |
+ // of the first-letter pseudo element, so go find them right now. |
+ findTextRenderers(); |
+ } |
+ |
+ FirstLetterRenderersList& renderers() { return m_renderers; } |
+ RenderObject* containerBlock() { return m_containerBlock; } |
+ |
+private: |
+ void findTextRenderers() |
+ { |
+ // Drill into inlines looking for the render objects to be transformed into first-letter pseudoelements. |
+ RenderObject* currentChild = m_containerBlock->firstChild(); |
+ unsigned currentLength = 0; |
+ |
+ while (currentChild) { |
+ if (currentChild->isText()) { |
+ // Process the renderer and store it in the list if valid text was found. |
+ currentLength = processTextRenderer(currentChild); |
+ if (currentLength) |
+ m_renderers.append(std::make_pair(currentChild, currentLength)); |
+ |
+ // No need to keep looking if we haven't found anything with the |
+ // current renderer or if we already made a decision. |
+ String text = rendererTextForFirstLetter(currentChild); |
+ if (!currentLength || currentLength < text.length()) |
+ break; |
+ |
+ // We need to look the next object traversing the tree in preorder as if the current renderer |
+ // was a leaf node (which probably is anyway) but without leaving the scope of the parent block. |
+ currentChild = currentChild->nextInPreOrderAfterChildren(m_containerBlock); |
+ } else if (currentChild->isListMarker()) { |
+ currentChild = currentChild->nextSibling(); |
+ } else if (currentChild->isFloatingOrOutOfFlowPositioned()) { |
+ if (currentChild->style()->styleType() == FIRST_LETTER) { |
+ currentChild = currentChild->firstChild(); |
+ if (currentChild) { |
+ // If found a floating/out-of-flow element with the first-letter |
+ // style already applied, it means it has been previously identified |
+ // and so we should discard whatever we found so far and use that. |
+ m_firstLetterFound = true; |
+ m_renderers.append(std::make_pair(currentChild, rendererTextForFirstLetter(currentChild).length())); |
+ } |
+ break; |
+ } |
+ currentChild = currentChild->nextSibling(); |
+ } else if (currentChild->isReplaced() || currentChild->isRenderButton() || currentChild->isMenuList()) { |
+ break; |
+ } else if (currentChild->style()->hasPseudoStyle(FIRST_LETTER) && currentChild->canHaveGeneratedChildren()) { |
+ // We found a lower-level node with first-letter, which supersedes the higher-level style. |
+ m_containerBlock = currentChild; |
+ currentChild = currentChild->firstChild(); |
+ } else { |
+ currentChild = currentChild->firstChild(); |
+ } |
+ } |
+ |
+ if (!m_firstLetterFound) { |
+ // Empty the list of renderers if we did not find a correct set of them |
+ // to further generate new elements to apply the first-letter style over. |
+ m_renderers.clear(); |
+ } |
+ } |
+ |
+ String rendererTextForFirstLetter(RenderObject* renderer) const |
+ { |
+ ASSERT(renderer->isText()); |
+ RenderText* textRenderer = toRenderText(renderer); |
+ |
+ String result = textRenderer->originalText(); |
+ if (!result.isNull()) |
+ return result; |
+ |
+ if (isRendererAllowedForFirstLetter(renderer)) |
+ return textRenderer->text(); |
+ |
+ return String(); |
+ } |
+ |
+ unsigned processTextRenderer(RenderObject* renderer) |
+ { |
+ ASSERT(renderer->isText()); |
+ String text = rendererTextForFirstLetter(renderer); |
+ |
+ // Early return in case we encounter the wrong characters at the beginning. |
+ if (text.isEmpty() |
+ || (m_searchState == SearchLeadingPunctuation && isSpaceForFirstLetter(text[0])) |
+ || (m_firstLetterFound && !isPunctuationForFirstLetter(text[0]))) |
+ return 0; |
+ |
+ // Now start looking for valid characters for the first-letter pseudo element. |
+ bool doneSearching = false; |
+ unsigned textLength = text.length(); |
+ unsigned length = 0; |
+ |
+ while (!doneSearching && length < textLength) { |
+ switch (m_searchState) { |
+ case SearchLeadingSpaces: |
+ advancePositionWhile<isSpaceForFirstLetter>(text, length); |
+ if (length < textLength) |
+ m_searchState = SearchLeadingPunctuation; |
+ break; |
+ |
+ case SearchLeadingPunctuation: |
+ advancePositionWhile<isPunctuationForFirstLetter>(text, length); |
+ if (length < textLength) |
+ m_searchState = SearchFirstLetterCharacter; |
+ break; |
+ |
+ case SearchFirstLetterCharacter: |
+ // Now spaces are allowed between leading punctuation and the letter. |
+ if (isSpaceForFirstLetter(text[length])) |
+ return 0; |
+ |
+ m_firstLetterFound = true; |
+ m_searchState = SearchTrailingPunctuation; |
+ length++; |
+ break; |
+ |
+ case SearchTrailingPunctuation: |
+ for (unsigned scanLength = length; scanLength < textLength; ++scanLength) { |
+ UChar c = text[scanLength]; |
+ if (!isPunctuationForFirstLetter(c)) { |
+ doneSearching = true; |
+ break; |
+ } |
+ length = scanLength + 1; |
+ } |
+ break; |
+ } |
+ } |
+ |
+ ASSERT(length <= textLength); |
+ return length; |
+ } |
+ |
+ template<bool characterPredicate(UChar)> |
+ void advancePositionWhile(const String& text, unsigned& position) |
+ { |
+ unsigned textLength = text.length(); |
+ while (position < textLength && characterPredicate(text[position])) |
+ position++; |
+ } |
+ |
+ // CSS 2.1 http://www.w3.org/TR/CSS21/selector.html#first-letter |
+ // "Punctuation (i.e, characters defined in Unicode [UNICODE] in the "open" (Ps), "close" (Pe), |
+ // "initial" (Pi). "final" (Pf) and "other" (Po) punctuation classes), that precedes or follows the first letter should be included" |
+ static inline bool isPunctuationForFirstLetter(UChar c) |
+ { |
+ CharCategory charCategory = category(c); |
+ return charCategory == Punctuation_Open |
+ || charCategory == Punctuation_Close |
+ || charCategory == Punctuation_InitialQuote |
+ || charCategory == Punctuation_FinalQuote |
+ || charCategory == Punctuation_Other; |
+ } |
+ |
+ static inline bool isSpaceForFirstLetter(UChar c) |
+ { |
+ return isSpaceOrNewline(c) || c == noBreakSpace; |
+ } |
+ |
+ RenderObject* m_containerBlock; |
+ FirstLetterRenderersList m_renderers; |
+ FirstLetterSearchState m_searchState; |
+ bool m_firstLetterFound; |
+}; |
+ |
void RenderBlock::updateFirstLetter() |
{ |
if (!document().styleEngine()->usesFirstLetterRules()) |
return; |
- // Don't recur |
+ |
+ // Early return if the renderer is already known to be part of a first-letter pseudo element. |
if (style()->styleType() == FIRST_LETTER) |
return; |
@@ -4198,55 +4341,40 @@ void RenderBlock::updateFirstLetter() |
if (!firstLetterBlock) |
return; |
- // Drill into inlines looking for our first text child. |
- RenderObject* currChild = firstLetterBlock->firstChild(); |
- unsigned length = 0; |
- while (currChild) { |
- if (currChild->isText()) { |
- // FIXME: If there is leading punctuation in a different RenderText than |
- // the first letter, we'll not apply the correct style to it. |
- length = firstLetterLength(toRenderText(currChild)->originalText()); |
- if (length) |
- break; |
- currChild = currChild->nextSibling(); |
- } else if (currChild->isListMarker()) { |
- currChild = currChild->nextSibling(); |
- } else if (currChild->isFloatingOrOutOfFlowPositioned()) { |
- if (currChild->style()->styleType() == FIRST_LETTER) { |
- currChild = currChild->firstChild(); |
- break; |
- } |
- currChild = currChild->nextSibling(); |
- } else if (currChild->isReplaced() || currChild->isRenderButton() || currChild->isMenuList()) |
- break; |
- else if (currChild->style()->hasPseudoStyle(FIRST_LETTER) && currChild->canHaveGeneratedChildren()) { |
- // We found a lower-level node with first-letter, which supersedes the higher-level style |
- firstLetterBlock = currChild; |
- currChild = currChild->firstChild(); |
- } else |
- currChild = currChild->firstChild(); |
- } |
- |
- if (!currChild) |
+ // Find the renderers to apply the first-letter style over. |
+ FirstLetterFinder firstLetterFinder(firstLetterBlock); |
+ FirstLetterRenderersList& renderers = firstLetterFinder.renderers(); |
+ if (renderers.isEmpty()) |
return; |
- // If the child already has style, then it has already been created, so we just want |
- // to update it. |
- if (currChild->parent()->style()->styleType() == FIRST_LETTER) { |
- updateFirstLetterStyle(firstLetterBlock, currChild); |
- return; |
- } |
+ // The FindLetterFinder might change what considers to be the container block |
+ // for the render objects that form the first-letter pseudo element from the one |
+ // originally passed to the constructor so we need to update the pointer now. |
+ firstLetterBlock = firstLetterFinder.containerBlock(); |
- // FIXME: This black-list of disallowed RenderText subclasses is fragile. |
- // Should counter be on this list? What about RenderTextFragment? |
- if (!currChild->isText() || currChild->isBR() || toRenderText(currChild)->isWordBreak()) |
- return; |
+ // Create the new renderers for the first-letter pseudo elements. |
+ for (FirstLetterRenderersList::const_iterator it = renderers.begin(); it != renderers.end(); ++it) { |
+ RenderObject* currentRenderer = it->first; |
+ ASSERT(currentRenderer->isText()); |
- // Our layout state is not valid for the repaints we are going to trigger by |
- // adding and removing children of firstLetterContainer. |
- LayoutStateDisabler layoutStateDisabler(*this); |
+ // If the child already has style, then it has already been created, so we just want |
+ // to update it. |
+ if (currentRenderer->parent()->style()->styleType() == FIRST_LETTER) { |
+ updateFirstLetterStyle(firstLetterBlock, currentRenderer); |
+ continue; |
+ } |
+ |
+ if (!isRendererAllowedForFirstLetter(currentRenderer)) |
+ continue; |
- createFirstLetterRenderer(firstLetterBlock, currChild, length); |
+ // Our layout state is not valid for the repaints we are going to trigger by |
+ // adding and removing children of firstLetterContainer. |
+ LayoutStateDisabler layoutStateDisabler(*this); |
+ |
+ unsigned lengthForRenderer = it->second; |
+ if (lengthForRenderer) |
+ createFirstLetterRenderer(firstLetterBlock, currentRenderer, lengthForRenderer); |
+ } |
} |
// Helper methods for obtaining the last line, computing line counts and heights for line counts |