Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(152)

Side by Side Diff: Source/core/rendering/RenderBlock.cpp

Issue 207553007: Apply the correct style to first-letter pseudo elements composed of different renderers (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Address comments from the latest review Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « LayoutTests/fast/css/first-letter-punctuation-expected.html ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 /* 1 /*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org) 3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2007 David Smith (catfish.man@gmail.com) 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. 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. 6 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
7 * Copyright (C) 2014 Samsung Electronics. All rights reserved.
7 * 8 *
8 * This library is free software; you can redistribute it and/or 9 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public 10 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either 11 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version. 12 * version 2 of the License, or (at your option) any later version.
12 * 13 *
13 * This library is distributed in the hope that it will be useful, 14 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details. 17 * Library General Public License for more details.
(...skipping 3991 matching lines...) Expand 10 before | Expand all | Expand 10 after
4008 static RenderStyle* styleForFirstLetter(RenderObject* firstLetterBlock, RenderOb ject* firstLetterContainer) 4009 static RenderStyle* styleForFirstLetter(RenderObject* firstLetterBlock, RenderOb ject* firstLetterContainer)
4009 { 4010 {
4010 RenderStyle* pseudoStyle = firstLetterBlock->getCachedPseudoStyle(FIRST_LETT ER, firstLetterContainer->firstLineStyle()); 4011 RenderStyle* pseudoStyle = firstLetterBlock->getCachedPseudoStyle(FIRST_LETT ER, firstLetterContainer->firstLineStyle());
4011 // Force inline display (except for floating first-letters). 4012 // Force inline display (except for floating first-letters).
4012 pseudoStyle->setDisplay(pseudoStyle->isFloating() ? BLOCK : INLINE); 4013 pseudoStyle->setDisplay(pseudoStyle->isFloating() ? BLOCK : INLINE);
4013 // CSS2 says first-letter can't be positioned. 4014 // CSS2 says first-letter can't be positioned.
4014 pseudoStyle->setPosition(StaticPosition); 4015 pseudoStyle->setPosition(StaticPosition);
4015 return pseudoStyle; 4016 return pseudoStyle;
4016 } 4017 }
4017 4018
4018 // CSS 2.1 http://www.w3.org/TR/CSS21/selector.html#first-letter
4019 // "Punctuation (i.e, characters defined in Unicode [UNICODE] in the "open" (Ps) , "close" (Pe),
4020 // "initial" (Pi). "final" (Pf) and "other" (Po) punctuation classes), that prec edes or follows the first letter should be included"
4021 static inline bool isPunctuationForFirstLetter(UChar c)
4022 {
4023 CharCategory charCategory = category(c);
4024 return charCategory == Punctuation_Open
4025 || charCategory == Punctuation_Close
4026 || charCategory == Punctuation_InitialQuote
4027 || charCategory == Punctuation_FinalQuote
4028 || charCategory == Punctuation_Other;
4029 }
4030
4031 static inline bool isSpaceForFirstLetter(UChar c)
4032 {
4033 return isSpaceOrNewline(c) || c == noBreakSpace;
4034 }
4035
4036 static inline RenderObject* findFirstLetterBlock(RenderBlock* start) 4019 static inline RenderObject* findFirstLetterBlock(RenderBlock* start)
4037 { 4020 {
4038 RenderObject* firstLetterBlock = start; 4021 RenderObject* firstLetterBlock = start;
4039 while (true) { 4022 while (true) {
4040 // We include isRenderButton in these two checks because buttons are 4023 // We include isRenderButton in these two checks because buttons are
4041 // implemented using flex box but should still support first-letter. 4024 // implemented using flex box but should still support first-letter.
4042 // The flex box spec requires that flex box does not support 4025 // The flex box spec requires that flex box does not support
4043 // first-letter, though. 4026 // first-letter, though.
4044 // FIXME: Remove when buttons are implemented with align-items instead 4027 // FIXME: Remove when buttons are implemented with align-items instead
4045 // of flexbox. 4028 // of flexbox.
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
4099 firstLetterContainer->addChild(firstLetter, nextSibling); 4082 firstLetterContainer->addChild(firstLetter, nextSibling);
4100 } else 4083 } else
4101 firstLetter->setStyle(pseudoStyle); 4084 firstLetter->setStyle(pseudoStyle);
4102 4085
4103 for (RenderObject* genChild = firstLetter->firstChild(); genChild; genChild = genChild->nextSibling()) { 4086 for (RenderObject* genChild = firstLetter->firstChild(); genChild; genChild = genChild->nextSibling()) {
4104 if (genChild->isText()) 4087 if (genChild->isText())
4105 genChild->setStyle(pseudoStyle); 4088 genChild->setStyle(pseudoStyle);
4106 } 4089 }
4107 } 4090 }
4108 4091
4109 static inline unsigned firstLetterLength(const String& text)
4110 {
4111 unsigned length = 0;
4112 unsigned textLength = text.length();
4113
4114 // Account for leading spaces first.
4115 while (length < textLength && isSpaceForFirstLetter(text[length]))
4116 length++;
4117
4118 // Now account for leading punctuation.
4119 while (length < textLength && isPunctuationForFirstLetter(text[length]))
4120 length++;
4121
4122 // Bail if we didn't find a letter before the end of the text or before a sp ace.
4123 if (isSpaceForFirstLetter(text[length]) || (textLength && length == textLeng th))
4124 return 0;
4125
4126 // Account the next character for first letter.
4127 length++;
4128
4129 // Keep looking allowed punctuation for the :first-letter.
4130 for (unsigned scanLength = length; scanLength < textLength; ++scanLength) {
4131 UChar c = text[scanLength];
4132
4133 if (!isPunctuationForFirstLetter(c))
4134 break;
4135
4136 length = scanLength + 1;
4137 }
4138
4139 // FIXME: If textLength is 0, length may still be 1!
4140 return length;
4141 }
4142
4143 void RenderBlock::createFirstLetterRenderer(RenderObject* firstLetterBlock, Rend erObject* currentChild, unsigned length) 4092 void RenderBlock::createFirstLetterRenderer(RenderObject* firstLetterBlock, Rend erObject* currentChild, unsigned length)
4144 { 4093 {
4145 ASSERT(length && currentChild->isText()); 4094 ASSERT(length && currentChild->isText());
4146 4095
4147 RenderObject* firstLetterContainer = currentChild->parent(); 4096 RenderObject* firstLetterContainer = currentChild->parent();
4148 RenderStyle* pseudoStyle = styleForFirstLetter(firstLetterBlock, firstLetter Container); 4097 RenderStyle* pseudoStyle = styleForFirstLetter(firstLetterBlock, firstLetter Container);
4149 RenderObject* firstLetter = 0; 4098 RenderObject* firstLetter = 0;
4150 if (pseudoStyle->display() == INLINE) 4099 if (pseudoStyle->display() == INLINE)
4151 firstLetter = RenderInline::createAnonymous(&document()); 4100 firstLetter = RenderInline::createAnonymous(&document());
4152 else 4101 else
(...skipping 24 matching lines...) Expand all
4177 4126
4178 // construct text fragment for the first letter 4127 // construct text fragment for the first letter
4179 RenderTextFragment* letter = 4128 RenderTextFragment* letter =
4180 new RenderTextFragment(remainingText->node() ? remainingText->node() : & remainingText->document(), oldText.impl(), 0, length); 4129 new RenderTextFragment(remainingText->node() ? remainingText->node() : & remainingText->document(), oldText.impl(), 0, length);
4181 letter->setStyle(pseudoStyle); 4130 letter->setStyle(pseudoStyle);
4182 firstLetter->addChild(letter); 4131 firstLetter->addChild(letter);
4183 4132
4184 textObj->destroy(); 4133 textObj->destroy();
4185 } 4134 }
4186 4135
4136 static bool isRendererAllowedForFirstLetter(RenderObject* renderer)
4137 {
4138 // FIXME: This black-list of disallowed RenderText subclasses is fragile.
4139 // Should counter be on this list? What about RenderTextFragment?
4140 return renderer->isText() && !renderer->isBR() && !toRenderText(renderer)->i sWordBreak();
4141 }
4142
4143 typedef Vector<std::pair<RenderObject*, unsigned> > FirstLetterRenderersList;
4144
4145 enum FirstLetterSearchState {
4146 SearchLeadingSpaces,
4147 SearchLeadingPunctuation,
4148 SearchFirstLetterCharacter,
4149 SearchTrailingPunctuation
4150 };
4151
4152 class FirstLetterFinder {
4153 WTF_MAKE_NONCOPYABLE(FirstLetterFinder);
4154 public:
4155 FirstLetterFinder(RenderObject* block)
4156 : m_containerBlock(block)
4157 , m_renderers()
4158 , m_searchState(SearchLeadingSpaces)
4159 , m_firstLetterFound(false)
4160 {
4161 // The purpose of this class is to find the renderers that would be part
4162 // of the first-letter pseudo element, so go find them right now.
4163 findTextRenderers();
4164 }
4165
4166 FirstLetterRenderersList& renderers() { return m_renderers; }
4167 RenderObject* containerBlock() { return m_containerBlock; }
4168
4169 private:
4170 void findTextRenderers()
4171 {
4172 // Drill into inlines looking for the render objects to be transformed i nto first-letter pseudoelements.
4173 RenderObject* currentChild = m_containerBlock->firstChild();
4174 unsigned currentLength = 0;
4175
4176 while (currentChild) {
4177 if (currentChild->isText()) {
4178 // Process the renderer and store it in the list if valid text w as found.
4179 currentLength = processTextRenderer(currentChild);
4180 if (currentLength)
4181 m_renderers.append(std::make_pair(currentChild, currentLengt h));
4182
4183 // No need to keep looking if we haven't found anything with the
4184 // current renderer or if we already made a decision.
4185 String text = rendererTextForFirstLetter(currentChild);
4186 if (!currentLength || currentLength < text.length())
4187 break;
4188
4189 // We need to look the next object traversing the tree in preord er as if the current renderer
4190 // was a leaf node (which probably is anyway) but without leavin g the scope of the parent block.
4191 currentChild = currentChild->nextInPreOrderAfterChildren(m_conta inerBlock);
4192 } else if (currentChild->isListMarker()) {
4193 currentChild = currentChild->nextSibling();
4194 } else if (currentChild->isFloatingOrOutOfFlowPositioned()) {
4195 if (currentChild->style()->styleType() == FIRST_LETTER) {
4196 currentChild = currentChild->firstChild();
4197 if (currentChild) {
4198 // If found a floating/out-of-flow element with the firs t-letter
4199 // style already applied, it means it has been previousl y identified
4200 // and so we should discard whatever we found so far and use that.
4201 m_firstLetterFound = true;
4202 m_renderers.append(std::make_pair(currentChild, renderer TextForFirstLetter(currentChild).length()));
4203 }
4204 break;
4205 }
4206 currentChild = currentChild->nextSibling();
4207 } else if (currentChild->isReplaced() || currentChild->isRenderButto n() || currentChild->isMenuList()) {
4208 break;
4209 } else if (currentChild->style()->hasPseudoStyle(FIRST_LETTER) && cu rrentChild->canHaveGeneratedChildren()) {
4210 // We found a lower-level node with first-letter, which supersed es the higher-level style.
4211 m_containerBlock = currentChild;
4212 currentChild = currentChild->firstChild();
4213 } else {
4214 currentChild = currentChild->firstChild();
4215 }
4216 }
4217
4218 if (!m_firstLetterFound) {
4219 // Empty the list of renderers if we did not find a correct set of t hem
4220 // to further generate new elements to apply the first-letter style over.
4221 m_renderers.clear();
4222 }
4223 }
4224
4225 String rendererTextForFirstLetter(RenderObject* renderer) const
4226 {
4227 ASSERT(renderer->isText());
4228 RenderText* textRenderer = toRenderText(renderer);
4229
4230 String result = textRenderer->originalText();
4231 if (!result.isNull())
4232 return result;
4233
4234 if (isRendererAllowedForFirstLetter(renderer))
4235 return textRenderer->text();
4236
4237 return String();
4238 }
4239
4240 unsigned processTextRenderer(RenderObject* renderer)
4241 {
4242 ASSERT(renderer->isText());
4243 String text = rendererTextForFirstLetter(renderer);
4244
4245 // Early return in case we encounter the wrong characters at the beginni ng.
4246 if (text.isEmpty()
4247 || (m_searchState == SearchLeadingPunctuation && isSpaceForFirstLett er(text[0]))
4248 || (m_firstLetterFound && !isPunctuationForFirstLetter(text[0])))
4249 return 0;
4250
4251 // Now start looking for valid characters for the first-letter pseudo el ement.
4252 bool doneSearching = false;
4253 unsigned textLength = text.length();
4254 unsigned length = 0;
4255
4256 while (!doneSearching && length < textLength) {
4257 switch (m_searchState) {
4258 case SearchLeadingSpaces:
4259 advancePositionWhile<isSpaceForFirstLetter>(text, length);
4260 if (length < textLength)
4261 m_searchState = SearchLeadingPunctuation;
4262 break;
4263
4264 case SearchLeadingPunctuation:
4265 advancePositionWhile<isPunctuationForFirstLetter>(text, length);
4266 if (length < textLength)
4267 m_searchState = SearchFirstLetterCharacter;
4268 break;
4269
4270 case SearchFirstLetterCharacter:
4271 // Now spaces are allowed between leading punctuation and the le tter.
4272 if (isSpaceForFirstLetter(text[length]))
4273 return 0;
4274
4275 m_firstLetterFound = true;
4276 m_searchState = SearchTrailingPunctuation;
4277 length++;
4278 break;
4279
4280 case SearchTrailingPunctuation:
4281 for (unsigned scanLength = length; scanLength < textLength; ++sc anLength) {
4282 UChar c = text[scanLength];
4283 if (!isPunctuationForFirstLetter(c)) {
4284 doneSearching = true;
4285 break;
4286 }
4287 length = scanLength + 1;
4288 }
4289 break;
4290 }
4291 }
4292
4293 ASSERT(length <= textLength);
4294 return length;
4295 }
4296
4297 template<bool characterPredicate(UChar)>
4298 void advancePositionWhile(const String& text, unsigned& position)
4299 {
4300 unsigned textLength = text.length();
4301 while (position < textLength && characterPredicate(text[position]))
4302 position++;
4303 }
4304
4305 // CSS 2.1 http://www.w3.org/TR/CSS21/selector.html#first-letter
4306 // "Punctuation (i.e, characters defined in Unicode [UNICODE] in the "open" (Ps), "close" (Pe),
4307 // "initial" (Pi). "final" (Pf) and "other" (Po) punctuation classes), that precedes or follows the first letter should be included"
4308 static inline bool isPunctuationForFirstLetter(UChar c)
4309 {
4310 CharCategory charCategory = category(c);
4311 return charCategory == Punctuation_Open
4312 || charCategory == Punctuation_Close
4313 || charCategory == Punctuation_InitialQuote
4314 || charCategory == Punctuation_FinalQuote
4315 || charCategory == Punctuation_Other;
4316 }
4317
4318 static inline bool isSpaceForFirstLetter(UChar c)
4319 {
4320 return isSpaceOrNewline(c) || c == noBreakSpace;
4321 }
4322
4323 RenderObject* m_containerBlock;
4324 FirstLetterRenderersList m_renderers;
4325 FirstLetterSearchState m_searchState;
4326 bool m_firstLetterFound;
4327 };
4328
4187 void RenderBlock::updateFirstLetter() 4329 void RenderBlock::updateFirstLetter()
4188 { 4330 {
4189 if (!document().styleEngine()->usesFirstLetterRules()) 4331 if (!document().styleEngine()->usesFirstLetterRules())
4190 return; 4332 return;
4191 // Don't recur 4333
4334 // Early return if the renderer is already known to be part of a first-lette r pseudo element.
4192 if (style()->styleType() == FIRST_LETTER) 4335 if (style()->styleType() == FIRST_LETTER)
4193 return; 4336 return;
4194 4337
4195 // FIXME: We need to destroy the first-letter object if it is no longer the first child. Need to find 4338 // FIXME: We need to destroy the first-letter object if it is no longer the first child. Need to find
4196 // an efficient way to check for that situation though before implementing a nything. 4339 // an efficient way to check for that situation though before implementing a nything.
4197 RenderObject* firstLetterBlock = findFirstLetterBlock(this); 4340 RenderObject* firstLetterBlock = findFirstLetterBlock(this);
4198 if (!firstLetterBlock) 4341 if (!firstLetterBlock)
4199 return; 4342 return;
4200 4343
4201 // Drill into inlines looking for our first text child. 4344 // Find the renderers to apply the first-letter style over.
4202 RenderObject* currChild = firstLetterBlock->firstChild(); 4345 FirstLetterFinder firstLetterFinder(firstLetterBlock);
4203 unsigned length = 0; 4346 FirstLetterRenderersList& renderers = firstLetterFinder.renderers();
4204 while (currChild) { 4347 if (renderers.isEmpty())
4205 if (currChild->isText()) {
4206 // FIXME: If there is leading punctuation in a different RenderText than
4207 // the first letter, we'll not apply the correct style to it.
4208 length = firstLetterLength(toRenderText(currChild)->originalText());
4209 if (length)
4210 break;
4211 currChild = currChild->nextSibling();
4212 } else if (currChild->isListMarker()) {
4213 currChild = currChild->nextSibling();
4214 } else if (currChild->isFloatingOrOutOfFlowPositioned()) {
4215 if (currChild->style()->styleType() == FIRST_LETTER) {
4216 currChild = currChild->firstChild();
4217 break;
4218 }
4219 currChild = currChild->nextSibling();
4220 } else if (currChild->isReplaced() || currChild->isRenderButton() || cur rChild->isMenuList())
4221 break;
4222 else if (currChild->style()->hasPseudoStyle(FIRST_LETTER) && currChild-> canHaveGeneratedChildren()) {
4223 // We found a lower-level node with first-letter, which supersedes t he higher-level style
4224 firstLetterBlock = currChild;
4225 currChild = currChild->firstChild();
4226 } else
4227 currChild = currChild->firstChild();
4228 }
4229
4230 if (!currChild)
4231 return; 4348 return;
4232 4349
4233 // If the child already has style, then it has already been created, so we j ust want 4350 // The FindLetterFinder might change what considers to be the container bloc k
4234 // to update it. 4351 // for the render objects that form the first-letter pseudo element from the one
4235 if (currChild->parent()->style()->styleType() == FIRST_LETTER) { 4352 // originally passed to the constructor so we need to update the pointer now .
4236 updateFirstLetterStyle(firstLetterBlock, currChild); 4353 firstLetterBlock = firstLetterFinder.containerBlock();
4237 return; 4354
4355 // Create the new renderers for the first-letter pseudo elements.
4356 for (FirstLetterRenderersList::const_iterator it = renderers.begin(); it != renderers.end(); ++it) {
4357 RenderObject* currentRenderer = it->first;
4358 ASSERT(currentRenderer->isText());
4359
4360 // If the child already has style, then it has already been created, so we just want
4361 // to update it.
4362 if (currentRenderer->parent()->style()->styleType() == FIRST_LETTER) {
4363 updateFirstLetterStyle(firstLetterBlock, currentRenderer);
4364 continue;
4365 }
4366
4367 if (!isRendererAllowedForFirstLetter(currentRenderer))
4368 continue;
4369
4370 // Our layout state is not valid for the repaints we are going to trigge r by
4371 // adding and removing children of firstLetterContainer.
4372 LayoutStateDisabler layoutStateDisabler(*this);
4373
4374 unsigned lengthForRenderer = it->second;
4375 if (lengthForRenderer)
4376 createFirstLetterRenderer(firstLetterBlock, currentRenderer, lengthF orRenderer);
4238 } 4377 }
4239
4240 // FIXME: This black-list of disallowed RenderText subclasses is fragile.
4241 // Should counter be on this list? What about RenderTextFragment?
4242 if (!currChild->isText() || currChild->isBR() || toRenderText(currChild)->is WordBreak())
4243 return;
4244
4245 // Our layout state is not valid for the repaints we are going to trigger by
4246 // adding and removing children of firstLetterContainer.
4247 LayoutStateDisabler layoutStateDisabler(*this);
4248
4249 createFirstLetterRenderer(firstLetterBlock, currChild, length);
4250 } 4378 }
4251 4379
4252 // Helper methods for obtaining the last line, computing line counts and heights for line counts 4380 // Helper methods for obtaining the last line, computing line counts and heights for line counts
4253 // (crawling into blocks). 4381 // (crawling into blocks).
4254 static bool shouldCheckLines(RenderObject* obj) 4382 static bool shouldCheckLines(RenderObject* obj)
4255 { 4383 {
4256 return !obj->isFloatingOrOutOfFlowPositioned() 4384 return !obj->isFloatingOrOutOfFlowPositioned()
4257 && obj->isRenderBlock() && obj->style()->height().isAuto() 4385 && obj->isRenderBlock() && obj->style()->height().isAuto()
4258 && (!obj->isDeprecatedFlexibleBox() || obj->style()->boxOrient() == VERT ICAL); 4386 && (!obj->isDeprecatedFlexibleBox() || obj->style()->boxOrient() == VERT ICAL);
4259 } 4387 }
(...skipping 765 matching lines...) Expand 10 before | Expand all | Expand 10 after
5025 void RenderBlock::showLineTreeAndMark(const InlineBox* markedBox1, const char* m arkedLabel1, const InlineBox* markedBox2, const char* markedLabel2, const Render Object* obj) const 5153 void RenderBlock::showLineTreeAndMark(const InlineBox* markedBox1, const char* m arkedLabel1, const InlineBox* markedBox2, const char* markedLabel2, const Render Object* obj) const
5026 { 5154 {
5027 showRenderObject(); 5155 showRenderObject();
5028 for (const RootInlineBox* root = firstRootBox(); root; root = root->nextRoot Box()) 5156 for (const RootInlineBox* root = firstRootBox(); root; root = root->nextRoot Box())
5029 root->showLineTreeAndMark(markedBox1, markedLabel1, markedBox2, markedLa bel2, obj, 1); 5157 root->showLineTreeAndMark(markedBox1, markedLabel1, markedBox2, markedLa bel2, obj, 1);
5030 } 5158 }
5031 5159
5032 #endif 5160 #endif
5033 5161
5034 } // namespace WebCore 5162 } // namespace WebCore
OLDNEW
« no previous file with comments | « LayoutTests/fast/css/first-letter-punctuation-expected.html ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698