blob: c573cb806d8dfa4320cad013392c726be3956662 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
* Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net)
* Copyright (C) 2010 Daniel Bates (dbates@intudata.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "core/layout/LayoutListMarker.h"
#include "core/fetch/ImageResource.h"
#include "core/layout/LayoutAnalyzer.h"
#include "core/layout/LayoutListItem.h"
#include "core/layout/ListMarkerText.h"
#include "core/layout/api/LineLayoutBlockFlow.h"
#include "core/paint/ListMarkerPainter.h"
#include "platform/fonts/Font.h"
namespace blink {
const int cMarkerPaddingPx = 7;
// TODO(glebl): Move to WebKit/Source/core/css/html.css after
// Blink starts to support ::marker crbug.com/457718
// Recommended UA margin for list markers.
const int cUAMarkerMarginEm = 1;
LayoutListMarker::LayoutListMarker(LayoutListItem* item)
: LayoutBox(nullptr), m_listItem(item) {
setInline(true);
setIsAtomicInlineLevel(true);
}
LayoutListMarker::~LayoutListMarker() {}
void LayoutListMarker::willBeDestroyed() {
if (m_image)
m_image->removeClient(this);
LayoutBox::willBeDestroyed();
}
LayoutListMarker* LayoutListMarker::createAnonymous(LayoutListItem* item) {
Document& document = item->document();
LayoutListMarker* layoutObject = new LayoutListMarker(item);
layoutObject->setDocumentForAnonymous(&document);
return layoutObject;
}
LayoutSize LayoutListMarker::imageBulletSize() const {
ASSERT(isImage());
// FIXME: This is a somewhat arbitrary default width. Generated images for markers really won't
// become particularly useful until we support the CSS3 marker pseudoclass to allow control over
// the width and height of the marker box.
LayoutUnit bulletWidth = style()->getFontMetrics().ascent() / LayoutUnit(2);
return m_image->imageSize(*this, style()->effectiveZoom(),
LayoutSize(bulletWidth, bulletWidth));
}
void LayoutListMarker::styleWillChange(StyleDifference diff,
const ComputedStyle& newStyle) {
if (style() &&
(newStyle.listStylePosition() != style()->listStylePosition() ||
newStyle.listStyleType() != style()->listStyleType()))
setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(
LayoutInvalidationReason::StyleChange);
LayoutBox::styleWillChange(diff, newStyle);
}
void LayoutListMarker::styleDidChange(StyleDifference diff,
const ComputedStyle* oldStyle) {
LayoutBox::styleDidChange(diff, oldStyle);
if (m_image != style()->listStyleImage()) {
if (m_image)
m_image->removeClient(this);
m_image = style()->listStyleImage();
if (m_image)
m_image->addClient(this);
}
}
InlineBox* LayoutListMarker::createInlineBox() {
InlineBox* result = LayoutBox::createInlineBox();
result->setIsText(isText());
return result;
}
bool LayoutListMarker::isImage() const {
return m_image && !m_image->errorOccurred();
}
LayoutRect LayoutListMarker::localSelectionRect() const {
InlineBox* box = inlineBoxWrapper();
if (!box)
return LayoutRect(LayoutPoint(), size());
RootInlineBox& root = inlineBoxWrapper()->root();
const ComputedStyle* blockStyle = root.block().style();
LayoutUnit newLogicalTop =
blockStyle->isFlippedBlocksWritingMode()
? inlineBoxWrapper()->logicalBottom() - root.selectionBottom()
: root.selectionTop() - inlineBoxWrapper()->logicalTop();
return blockStyle->isHorizontalWritingMode()
? LayoutRect(LayoutUnit(), newLogicalTop, size().width(),
root.selectionHeight())
: LayoutRect(newLogicalTop, LayoutUnit(), root.selectionHeight(),
size().height());
}
void LayoutListMarker::paint(const PaintInfo& paintInfo,
const LayoutPoint& paintOffset) const {
ListMarkerPainter(*this).paint(paintInfo, paintOffset);
}
void LayoutListMarker::layout() {
ASSERT(needsLayout());
LayoutAnalyzer::Scope analyzer(*this);
if (isImage()) {
updateMarginsAndContent();
LayoutSize imageSize(imageBulletSize());
setWidth(imageSize.width());
setHeight(imageSize.height());
} else {
setLogicalWidth(minPreferredLogicalWidth());
setLogicalHeight(LayoutUnit(style()->getFontMetrics().height()));
}
setMarginStart(LayoutUnit());
setMarginEnd(LayoutUnit());
Length startMargin = style()->marginStart();
Length endMargin = style()->marginEnd();
if (startMargin.isFixed())
setMarginStart(LayoutUnit(startMargin.value()));
if (endMargin.isFixed())
setMarginEnd(LayoutUnit(endMargin.value()));
clearNeedsLayout();
}
void LayoutListMarker::imageChanged(WrappedImagePtr o, const IntRect*) {
// A list marker can't have a background or border image, so no need to call the base class method.
if (!m_image || o != m_image->data())
return;
LayoutSize imageSize = isImage() ? imageBulletSize() : LayoutSize();
if (size() != imageSize || m_image->errorOccurred())
setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(
LayoutInvalidationReason::ImageChanged);
else
setShouldDoFullPaintInvalidation();
}
void LayoutListMarker::updateMarginsAndContent() {
updateContent();
updateMargins();
}
void LayoutListMarker::updateContent() {
// FIXME: This if-statement is just a performance optimization, but it's messy to use the preferredLogicalWidths dirty bit for this.
// It's unclear if this is a premature optimization.
if (!preferredLogicalWidthsDirty())
return;
m_text = "";
if (isImage())
return;
switch (getListStyleCategory()) {
case ListStyleCategory::None:
break;
case ListStyleCategory::Symbol:
m_text = ListMarkerText::text(style()->listStyleType(),
0); // value is ignored for these types
break;
case ListStyleCategory::Language:
m_text =
ListMarkerText::text(style()->listStyleType(), m_listItem->value());
break;
}
}
LayoutUnit LayoutListMarker::getWidthOfTextWithSuffix() const {
if (m_text.isEmpty())
return LayoutUnit();
const Font& font = style()->font();
LayoutUnit itemWidth = LayoutUnit(font.width(TextRun(m_text)));
// TODO(wkorman): Look into constructing a text run for both text and suffix
// and painting them together.
UChar suffix[2] = {
ListMarkerText::suffix(style()->listStyleType(), m_listItem->value()),
' '};
TextRun run =
constructTextRun(font, suffix, 2, styleRef(), style()->direction());
LayoutUnit suffixSpaceWidth = LayoutUnit(font.width(run));
return itemWidth + suffixSpaceWidth;
}
void LayoutListMarker::computePreferredLogicalWidths() {
ASSERT(preferredLogicalWidthsDirty());
updateContent();
if (isImage()) {
LayoutSize imageSize(imageBulletSize());
m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth =
style()->isHorizontalWritingMode() ? imageSize.width()
: imageSize.height();
clearPreferredLogicalWidthsDirty();
updateMargins();
return;
}
const Font& font = style()->font();
LayoutUnit logicalWidth;
switch (getListStyleCategory()) {
case ListStyleCategory::None:
break;
case ListStyleCategory::Symbol:
logicalWidth =
LayoutUnit((font.getFontMetrics().ascent() * 2 / 3 + 1) / 2 + 2);
break;
case ListStyleCategory::Language:
logicalWidth = getWidthOfTextWithSuffix();
break;
}
m_minPreferredLogicalWidth = logicalWidth;
m_maxPreferredLogicalWidth = logicalWidth;
clearPreferredLogicalWidthsDirty();
updateMargins();
}
void LayoutListMarker::updateMargins() {
const FontMetrics& fontMetrics = style()->getFontMetrics();
LayoutUnit marginStart;
LayoutUnit marginEnd;
if (isInside()) {
if (isImage()) {
marginEnd = LayoutUnit(cMarkerPaddingPx);
} else {
switch (getListStyleCategory()) {
case ListStyleCategory::Symbol:
marginStart = LayoutUnit(-1);
marginEnd =
fontMetrics.ascent() - minPreferredLogicalWidth() + 1 +
LayoutUnit(cUAMarkerMarginEm * style()->computedFontSize());
break;
default:
break;
}
}
} else {
if (style()->isLeftToRightDirection()) {
if (isImage()) {
marginStart = -minPreferredLogicalWidth() - cMarkerPaddingPx;
} else {
int offset = fontMetrics.ascent() * 2 / 3;
switch (getListStyleCategory()) {
case ListStyleCategory::None:
break;
case ListStyleCategory::Symbol:
marginStart = LayoutUnit(-offset - cMarkerPaddingPx - 1);
break;
default:
marginStart =
m_text.isEmpty() ? LayoutUnit() : -minPreferredLogicalWidth();
}
}
marginEnd = -marginStart - minPreferredLogicalWidth();
} else {
if (isImage()) {
marginEnd = LayoutUnit(cMarkerPaddingPx);
} else {
int offset = fontMetrics.ascent() * 2 / 3;
switch (getListStyleCategory()) {
case ListStyleCategory::None:
break;
case ListStyleCategory::Symbol:
marginEnd =
offset + cMarkerPaddingPx + 1 - minPreferredLogicalWidth();
break;
default:
marginEnd = LayoutUnit();
}
}
marginStart = -marginEnd - minPreferredLogicalWidth();
}
}
mutableStyleRef().setMarginStart(Length(marginStart, Fixed));
mutableStyleRef().setMarginEnd(Length(marginEnd, Fixed));
}
LayoutUnit LayoutListMarker::lineHeight(
bool firstLine,
LineDirectionMode direction,
LinePositionMode linePositionMode) const {
if (!isImage())
return m_listItem->lineHeight(firstLine, direction,
PositionOfInteriorLineBoxes);
return LayoutBox::lineHeight(firstLine, direction, linePositionMode);
}
int LayoutListMarker::baselinePosition(
FontBaseline baselineType,
bool firstLine,
LineDirectionMode direction,
LinePositionMode linePositionMode) const {
ASSERT(linePositionMode == PositionOnContainingLine);
if (!isImage())
return m_listItem->baselinePosition(baselineType, firstLine, direction,
PositionOfInteriorLineBoxes);
return LayoutBox::baselinePosition(baselineType, firstLine, direction,
linePositionMode);
}
LayoutListMarker::ListStyleCategory LayoutListMarker::getListStyleCategory()
const {
switch (style()->listStyleType()) {
case NoneListStyle:
return ListStyleCategory::None;
case Disc:
case Circle:
case Square:
return ListStyleCategory::Symbol;
case ArabicIndic:
case Armenian:
case Bengali:
case Cambodian:
case CJKIdeographic:
case CjkEarthlyBranch:
case CjkHeavenlyStem:
case DecimalLeadingZero:
case DecimalListStyle:
case Devanagari:
case EthiopicHalehame:
case EthiopicHalehameAm:
case EthiopicHalehameTiEr:
case EthiopicHalehameTiEt:
case Georgian:
case Gujarati:
case Gurmukhi:
case Hangul:
case HangulConsonant:
case Hebrew:
case Hiragana:
case HiraganaIroha:
case Kannada:
case Katakana:
case KatakanaIroha:
case Khmer:
case KoreanHangulFormal:
case KoreanHanjaFormal:
case KoreanHanjaInformal:
case Lao:
case LowerAlpha:
case LowerArmenian:
case LowerGreek:
case LowerLatin:
case LowerRoman:
case Malayalam:
case Mongolian:
case Myanmar:
case Oriya:
case Persian:
case SimpChineseFormal:
case SimpChineseInformal:
case Telugu:
case Thai:
case Tibetan:
case TradChineseFormal:
case TradChineseInformal:
case UpperAlpha:
case UpperArmenian:
case UpperLatin:
case UpperRoman:
case Urdu:
return ListStyleCategory::Language;
default:
ASSERT_NOT_REACHED();
return ListStyleCategory::Language;
}
}
bool LayoutListMarker::isInside() const {
return m_listItem->notInList() ||
style()->listStylePosition() == EListStylePosition::Inside;
}
IntRect LayoutListMarker::getRelativeMarkerRect() const {
if (isImage()) {
IntSize imageSize = flooredIntSize(imageBulletSize());
return IntRect(0, 0, imageSize.width(), imageSize.height());
}
IntRect relativeRect;
switch (getListStyleCategory()) {
case ListStyleCategory::None:
return IntRect();
case ListStyleCategory::Symbol: {
// TODO(wkorman): Review and clean up/document the calculations below.
// http://crbug.com/543193
const FontMetrics& fontMetrics = style()->getFontMetrics();
int ascent = fontMetrics.ascent();
int bulletWidth = (ascent * 2 / 3 + 1) / 2;
relativeRect = IntRect(1, 3 * (ascent - ascent * 2 / 3) / 2, bulletWidth,
bulletWidth);
} break;
case ListStyleCategory::Language:
relativeRect = IntRect(0, 0, getWidthOfTextWithSuffix().toInt(),
style()->font().getFontMetrics().height());
break;
}
if (!style()->isHorizontalWritingMode()) {
relativeRect = relativeRect.transposedRect();
relativeRect.setX(
(size().width() - relativeRect.x() - relativeRect.width()).toInt());
}
return relativeRect;
}
void LayoutListMarker::setSelectionState(SelectionState state) {
// The selection state for our containing block hierarchy is updated by the base class call.
LayoutBox::setSelectionState(state);
if (inlineBoxWrapper() && canUpdateSelectionOnRootLineBoxes())
inlineBoxWrapper()->root().setHasSelectedChildren(state != SelectionNone);
}
void LayoutListMarker::listItemStyleDidChange() {
RefPtr<ComputedStyle> newStyle = ComputedStyle::create();
// The marker always inherits from the list item, regardless of where it might end
// up (e.g., in some deeply nested line box). See CSS3 spec.
newStyle->inheritFrom(m_listItem->styleRef());
if (style()) {
// Reuse the current margins. Otherwise resetting the margins to initial values
// would trigger unnecessary layout.
newStyle->setMarginStart(style()->marginStart());
newStyle->setMarginEnd(style()->marginRight());
}
setStyle(newStyle.release());
}
} // namespace blink