blob: 5ef8f14f438c8e1e523819837f2553b6053abcfe [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "config.h"
#include "core/paint/ListMarkerPainter.h"
#include "core/layout/LayoutListItem.h"
#include "core/layout/LayoutListMarker.h"
#include "core/layout/ListMarkerText.h"
#include "core/layout/TextRunConstructor.h"
#include "core/layout/api/SelectionState.h"
#include "core/paint/BlockPainter.h"
#include "core/paint/LayoutObjectDrawingRecorder.h"
#include "core/paint/PaintInfo.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/geometry/LayoutPoint.h"
#include "platform/graphics/GraphicsContextStateSaver.h"
#include "wtf/text/CharacterNames.h"
namespace blink {
static inline void paintSymbol(GraphicsContext* context, const Color& color,
const IntRect& marker, EListStyleType listStyle)
{
context->setStrokeColor(color);
context->setStrokeStyle(SolidStroke);
context->setStrokeThickness(1.0f);
switch (listStyle) {
case Disc:
context->fillEllipse(marker);
break;
case Circle:
context->strokeEllipse(marker);
break;
case Square:
context->fillRect(marker);
break;
default:
ASSERT_NOT_REACHED();
break;
}
}
void ListMarkerPainter::paint(const PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
if (paintInfo.phase != PaintPhaseForeground)
return;
if (m_layoutListMarker.style()->visibility() != VISIBLE)
return;
if (LayoutObjectDrawingRecorder::useCachedDrawingIfPossible(*paintInfo.context, m_layoutListMarker, paintInfo.phase, paintOffset))
return;
LayoutPoint boxOrigin(paintOffset + m_layoutListMarker.location());
LayoutRect overflowRect(m_layoutListMarker.visualOverflowRect());
if (!RuntimeEnabledFeatures::selectionPaintingWithoutSelectionGapsEnabled()
&& m_layoutListMarker.selectionState() != SelectionNone)
overflowRect.unite(m_layoutListMarker.localSelectionRect());
overflowRect.moveBy(boxOrigin);
IntRect pixelSnappedOverflowRect = pixelSnappedIntRect(overflowRect);
if (!paintInfo.cullRect().intersectsCullRect(overflowRect))
return;
LayoutObjectDrawingRecorder recorder(*paintInfo.context, m_layoutListMarker, paintInfo.phase, pixelSnappedOverflowRect, paintOffset);
LayoutRect box(boxOrigin, m_layoutListMarker.size());
IntRect marker = m_layoutListMarker.getRelativeMarkerRect();
marker.moveBy(roundedIntPoint(boxOrigin));
GraphicsContext* context = paintInfo.context;
if (m_layoutListMarker.isImage()) {
context->drawImage(m_layoutListMarker.image()->image(
&m_layoutListMarker, marker.size(), m_layoutListMarker.styleRef().effectiveZoom()).get(), marker);
if (m_layoutListMarker.selectionState() != SelectionNone) {
LayoutRect selRect = m_layoutListMarker.localSelectionRect();
selRect.moveBy(boxOrigin);
context->fillRect(pixelSnappedIntRect(selRect), m_layoutListMarker.listItem()->selectionBackgroundColor());
}
return;
}
if (!RuntimeEnabledFeatures::selectionPaintingWithoutSelectionGapsEnabled()
&& m_layoutListMarker.selectionState() != SelectionNone) {
LayoutRect selRect = m_layoutListMarker.localSelectionRect();
selRect.moveBy(boxOrigin);
context->fillRect(pixelSnappedIntRect(selRect), m_layoutListMarker.listItem()->selectionBackgroundColor());
}
LayoutListMarker::ListStyleCategory styleCategory = m_layoutListMarker.listStyleCategory();
if (styleCategory == LayoutListMarker::ListStyleCategory::None)
return;
const Color color(m_layoutListMarker.resolveColor(CSSPropertyColor));
// Apply the color to the list marker text.
context->setFillColor(color);
const EListStyleType listStyle = m_layoutListMarker.style()->listStyleType();
if (styleCategory == LayoutListMarker::ListStyleCategory::Symbol) {
paintSymbol(context, color, marker, listStyle);
return;
}
if (m_layoutListMarker.text().isEmpty())
return;
const Font& font = m_layoutListMarker.style()->font();
TextRun textRun = constructTextRun(font, m_layoutListMarker.text(), m_layoutListMarker.styleRef());
GraphicsContextStateSaver stateSaver(*context, false);
if (!m_layoutListMarker.style()->isHorizontalWritingMode()) {
marker.moveBy(roundedIntPoint(-boxOrigin));
marker = marker.transposedRect();
marker.moveBy(IntPoint(roundToInt(box.x()), roundToInt(box.y() - m_layoutListMarker.logicalHeight())));
stateSaver.save();
context->translate(marker.x(), marker.maxY());
context->rotate(static_cast<float>(deg2rad(90.)));
context->translate(-marker.x(), -marker.maxY());
}
TextRunPaintInfo textRunPaintInfo(textRun);
textRunPaintInfo.bounds = marker;
IntPoint textOrigin = IntPoint(marker.x(), marker.y() + m_layoutListMarker.style()->fontMetrics().ascent());
// Text is not arbitrary. We can judge whether it's RTL from the first character,
// and we only need to handle the direction RightToLeft for now.
bool textNeedsReversing = WTF::Unicode::direction(m_layoutListMarker.text()[0]) == WTF::Unicode::RightToLeft;
StringBuilder reversedText;
if (textNeedsReversing) {
unsigned length = m_layoutListMarker.text().length();
reversedText.reserveCapacity(length);
for (int i = length - 1; i >= 0; --i)
reversedText.append(m_layoutListMarker.text()[i]);
ASSERT(reversedText.length() == length);
textRun.setText(reversedText.toString());
}
const UChar suffix = ListMarkerText::suffix(listStyle, m_layoutListMarker.listItem()->value());
UChar suffixStr[2] = { suffix, static_cast<UChar>(' ') };
TextRun suffixRun = constructTextRun(font, suffixStr, 2, m_layoutListMarker.styleRef(), m_layoutListMarker.style()->direction());
TextRunPaintInfo suffixRunInfo(suffixRun);
suffixRunInfo.bounds = marker;
if (m_layoutListMarker.style()->isLeftToRightDirection()) {
context->drawText(font, textRunPaintInfo, textOrigin);
context->drawText(font, suffixRunInfo, textOrigin + IntSize(font.width(textRun), 0));
} else {
context->drawText(font, suffixRunInfo, textOrigin);
context->drawText(font, textRunPaintInfo, textOrigin + IntSize(font.width(suffixRun), 0));
}
}
} // namespace blink