RenderTreeUpdaterFirstLetter.cpp [plain text]
#include "config.h"
#include "RenderTreeUpdaterFirstLetter.h"
#include "FontCascade.h"
#include "RenderBlock.h"
#include "RenderButton.h"
#include "RenderInline.h"
#include "RenderRubyRun.h"
#include "RenderSVGText.h"
#include "RenderStyle.h"
#include "RenderTable.h"
#include "RenderTextFragment.h"
namespace WebCore {
static RenderStyle styleForFirstLetter(const RenderBlock& firstLetterBlock, const RenderObject& firstLetterContainer)
{
auto* containerFirstLetterStyle = firstLetterBlock.getCachedPseudoStyle(FIRST_LETTER, &firstLetterContainer.firstLineStyle());
ASSERT(containerFirstLetterStyle);
auto firstLetterStyle = RenderStyle::clone(containerFirstLetterStyle ? *containerFirstLetterStyle : firstLetterContainer.firstLineStyle());
if (firstLetterStyle.initialLetterDrop() >= 1 && !firstLetterStyle.isFloating())
firstLetterStyle.setFloating(firstLetterStyle.isLeftToRightDirection() ? LeftFloat : RightFloat);
auto* paragraph = firstLetterContainer.isRenderBlockFlow() ? &firstLetterContainer : firstLetterContainer.containingBlock();
if (firstLetterStyle.initialLetterHeight() >= 1 && firstLetterStyle.fontMetrics().hasCapHeight() && paragraph->style().fontMetrics().hasCapHeight()) {
firstLetterStyle.setLineBoxContain(LineBoxContainInitialLetter);
int lineHeight = paragraph->style().computedLineHeight();
auto newFontDescription = firstLetterStyle.fontDescription();
float capRatio = firstLetterStyle.fontMetrics().floatCapHeight() / firstLetterStyle.computedFontPixelSize();
float startingFontSize = ((firstLetterStyle.initialLetterHeight() - 1) * lineHeight + paragraph->style().fontMetrics().capHeight()) / capRatio;
newFontDescription.setSpecifiedSize(startingFontSize);
newFontDescription.setComputedSize(startingFontSize);
firstLetterStyle.setFontDescription(newFontDescription);
firstLetterStyle.fontCascade().update(firstLetterStyle.fontCascade().fontSelector());
int desiredCapHeight = (firstLetterStyle.initialLetterHeight() - 1) * lineHeight + paragraph->style().fontMetrics().capHeight();
int actualCapHeight = firstLetterStyle.fontMetrics().capHeight();
while (actualCapHeight > desiredCapHeight) {
auto newFontDescription = firstLetterStyle.fontDescription();
newFontDescription.setSpecifiedSize(newFontDescription.specifiedSize() - 1);
newFontDescription.setComputedSize(newFontDescription.computedSize() -1);
firstLetterStyle.setFontDescription(newFontDescription);
firstLetterStyle.fontCascade().update(firstLetterStyle.fontCascade().fontSelector());
actualCapHeight = firstLetterStyle.fontMetrics().capHeight();
}
}
firstLetterStyle.setDisplay(firstLetterStyle.isFloating() ? BLOCK : INLINE);
firstLetterStyle.setPosition(StaticPosition);
return firstLetterStyle;
}
static inline bool isPunctuationForFirstLetter(UChar c)
{
return U_GET_GC_MASK(c) & (U_GC_PS_MASK | U_GC_PE_MASK | U_GC_PI_MASK | U_GC_PF_MASK | U_GC_PO_MASK);
}
static inline bool shouldSkipForFirstLetter(UChar c)
{
return isSpaceOrNewline(c) || c == noBreakSpace || isPunctuationForFirstLetter(c);
}
static void updateFirstLetterStyle(RenderBlock& firstLetterBlock, RenderObject& currentChild)
{
RenderElement* firstLetter = currentChild.parent();
ASSERT(firstLetter->isFirstLetter());
RenderElement* firstLetterContainer = firstLetter->parent();
auto pseudoStyle = styleForFirstLetter(firstLetterBlock, *firstLetterContainer);
ASSERT(firstLetter->isFloating() || firstLetter->isInline());
if (Style::determineChange(firstLetter->style(), pseudoStyle) == Style::Detach) {
RenderPtr<RenderBoxModelObject> newFirstLetter;
if (pseudoStyle.display() == INLINE)
newFirstLetter = createRenderer<RenderInline>(firstLetterBlock.document(), WTFMove(pseudoStyle));
else
newFirstLetter = createRenderer<RenderBlockFlow>(firstLetterBlock.document(), WTFMove(pseudoStyle));
newFirstLetter->initializeStyle();
newFirstLetter->setIsFirstLetter();
while (RenderObject* child = firstLetter->firstChild()) {
if (is<RenderText>(*child))
downcast<RenderText>(*child).removeAndDestroyTextBoxes();
auto toMove = firstLetter->takeChild(*child);
newFirstLetter->addChild(WTFMove(toMove));
}
RenderObject* nextSibling = firstLetter->nextSibling();
if (RenderTextFragment* remainingText = downcast<RenderBoxModelObject>(*firstLetter).firstLetterRemainingText()) {
ASSERT(remainingText->isAnonymous() || remainingText->textNode()->renderer() == remainingText);
remainingText->setFirstLetter(*newFirstLetter);
newFirstLetter->setFirstLetterRemainingText(*remainingText);
}
firstLetterContainer->removeAndDestroyChild(*firstLetter);
firstLetterContainer->addChild(WTFMove(newFirstLetter), nextSibling);
} else
firstLetter->setStyle(WTFMove(pseudoStyle));
}
static void createFirstLetterRenderer(RenderBlock& firstLetterBlock, RenderText& currentTextChild)
{
RenderElement* firstLetterContainer = currentTextChild.parent();
auto pseudoStyle = styleForFirstLetter(firstLetterBlock, *firstLetterContainer);
RenderPtr<RenderBoxModelObject> newFirstLetter;
if (pseudoStyle.display() == INLINE)
newFirstLetter = createRenderer<RenderInline>(firstLetterBlock.document(), WTFMove(pseudoStyle));
else
newFirstLetter = createRenderer<RenderBlockFlow>(firstLetterBlock.document(), WTFMove(pseudoStyle));
newFirstLetter->initializeStyle();
newFirstLetter->setIsFirstLetter();
auto& firstLetter = *newFirstLetter;
firstLetterContainer->addChild(WTFMove(newFirstLetter), ¤tTextChild);
String oldText = currentTextChild.originalText();
ASSERT(!oldText.isNull());
if (!oldText.isEmpty()) {
unsigned length = 0;
while (length < oldText.length() && shouldSkipForFirstLetter(oldText[length]))
length++;
length += numCharactersInGraphemeClusters(StringView(oldText).substring(length), 1);
for (unsigned scanLength = length; scanLength < oldText.length(); ++scanLength) {
UChar c = oldText[scanLength];
if (!shouldSkipForFirstLetter(c))
break;
if (isPunctuationForFirstLetter(c))
length = scanLength + 1;
}
auto* textNode = currentTextChild.textNode();
auto* beforeChild = currentTextChild.nextSibling();
firstLetterContainer->removeAndDestroyChild(currentTextChild);
RenderPtr<RenderTextFragment> newRemainingText;
if (textNode) {
newRemainingText = createRenderer<RenderTextFragment>(*textNode, oldText, length, oldText.length() - length);
textNode->setRenderer(newRemainingText.get());
} else
newRemainingText = createRenderer<RenderTextFragment>(firstLetterBlock.document(), oldText, length, oldText.length() - length);
RenderTextFragment& remainingText = *newRemainingText;
firstLetterContainer->addChild(WTFMove(newRemainingText), beforeChild);
remainingText.setFirstLetter(firstLetter);
firstLetter.setFirstLetterRemainingText(remainingText);
auto letter = createRenderer<RenderTextFragment>(firstLetterBlock.document(), oldText, 0, length);
firstLetter.addChild(WTFMove(letter));
}
}
static bool supportsFirstLetter(RenderBlock& block)
{
if (is<RenderButton>(block))
return true;
if (!is<RenderBlockFlow>(block))
return false;
if (is<RenderSVGText>(block))
return false;
if (is<RenderRubyRun>(block))
return false;
return block.canHaveGeneratedChildren();
}
void RenderTreeUpdater::FirstLetter::update(RenderBlock& block)
{
if (!block.style().hasPseudoStyle(FIRST_LETTER))
return;
if (!supportsFirstLetter(block))
return;
RenderObject* firstLetterRenderer;
RenderElement* firstLetterContainer;
block.getFirstLetter(firstLetterRenderer, firstLetterContainer);
if (!firstLetterRenderer)
return;
if (&block != firstLetterContainer)
return;
if (firstLetterRenderer->parent()->style().styleType() == FIRST_LETTER) {
updateFirstLetterStyle(block, *firstLetterRenderer);
return;
}
if (!is<RenderText>(firstLetterRenderer))
return;
createFirstLetterRenderer(block, downcast<RenderText>(*firstLetterRenderer));
}
};