1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "config.h"
6#include "core/paint/ListMarkerPainter.h"
7
8#include "core/paint/BlockPainter.h"
9#include "core/rendering/GraphicsContextAnnotator.h"
10#include "core/rendering/PaintInfo.h"
11#include "core/rendering/RenderListItem.h"
12#include "core/rendering/RenderListMarker.h"
13#include "core/rendering/TextRunConstructor.h"
14#include "platform/geometry/LayoutPoint.h"
15#include "platform/graphics/GraphicsContextStateSaver.h"
16#include "wtf/unicode/CharacterNames.h"
17
18namespace blink {
19
20void ListMarkerPainter::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
21{
22    ANNOTATE_GRAPHICS_CONTEXT(paintInfo, &m_renderListMarker);
23
24    if (paintInfo.phase != PaintPhaseForeground)
25        return;
26
27    if (m_renderListMarker.style()->visibility() != VISIBLE)
28        return;
29
30    LayoutPoint boxOrigin(paintOffset + m_renderListMarker.location());
31    LayoutRect overflowRect(m_renderListMarker.visualOverflowRect());
32    overflowRect.moveBy(boxOrigin);
33
34    if (!paintInfo.rect.intersects(pixelSnappedIntRect(overflowRect)))
35        return;
36
37    LayoutRect box(boxOrigin, m_renderListMarker.size());
38
39    IntRect marker = m_renderListMarker.getRelativeMarkerRect();
40    marker.moveBy(roundedIntPoint(boxOrigin));
41
42    GraphicsContext* context = paintInfo.context;
43
44    if (m_renderListMarker.isImage()) {
45        context->drawImage(m_renderListMarker.image()->image(&m_renderListMarker, marker.size()).get(), marker);
46        if (m_renderListMarker.selectionState() != RenderObject::SelectionNone) {
47            LayoutRect selRect = m_renderListMarker.localSelectionRect();
48            selRect.moveBy(boxOrigin);
49            context->fillRect(pixelSnappedIntRect(selRect), m_renderListMarker.selectionBackgroundColor());
50        }
51        return;
52    }
53
54    if (m_renderListMarker.selectionState() != RenderObject::SelectionNone) {
55        LayoutRect selRect = m_renderListMarker.localSelectionRect();
56        selRect.moveBy(boxOrigin);
57        context->fillRect(pixelSnappedIntRect(selRect), m_renderListMarker.selectionBackgroundColor());
58    }
59
60    const Color color(m_renderListMarker.resolveColor(CSSPropertyColor));
61    context->setStrokeColor(color);
62    context->setStrokeStyle(SolidStroke);
63    context->setStrokeThickness(1.0f);
64    context->setFillColor(color);
65
66    EListStyleType type = m_renderListMarker.style()->listStyleType();
67    switch (type) {
68    case Disc:
69        context->fillEllipse(marker);
70        return;
71    case Circle:
72        context->strokeEllipse(marker);
73        return;
74    case Square:
75        context->fillRect(marker);
76        return;
77    case NoneListStyle:
78        return;
79    case Afar:
80    case Amharic:
81    case AmharicAbegede:
82    case ArabicIndic:
83    case Armenian:
84    case BinaryListStyle:
85    case Bengali:
86    case Cambodian:
87    case CJKIdeographic:
88    case CjkEarthlyBranch:
89    case CjkHeavenlyStem:
90    case DecimalLeadingZero:
91    case DecimalListStyle:
92    case Devanagari:
93    case Ethiopic:
94    case EthiopicAbegede:
95    case EthiopicAbegedeAmEt:
96    case EthiopicAbegedeGez:
97    case EthiopicAbegedeTiEr:
98    case EthiopicAbegedeTiEt:
99    case EthiopicHalehameAaEr:
100    case EthiopicHalehameAaEt:
101    case EthiopicHalehameAmEt:
102    case EthiopicHalehameGez:
103    case EthiopicHalehameOmEt:
104    case EthiopicHalehameSidEt:
105    case EthiopicHalehameSoEt:
106    case EthiopicHalehameTiEr:
107    case EthiopicHalehameTiEt:
108    case EthiopicHalehameTig:
109    case Georgian:
110    case Gujarati:
111    case Gurmukhi:
112    case Hangul:
113    case HangulConsonant:
114    case Hebrew:
115    case Hiragana:
116    case HiraganaIroha:
117    case Kannada:
118    case Katakana:
119    case KatakanaIroha:
120    case Khmer:
121    case Lao:
122    case LowerAlpha:
123    case LowerArmenian:
124    case LowerGreek:
125    case LowerHexadecimal:
126    case LowerLatin:
127    case LowerNorwegian:
128    case LowerRoman:
129    case Malayalam:
130    case Mongolian:
131    case Myanmar:
132    case Octal:
133    case Oriya:
134    case Oromo:
135    case Persian:
136    case Sidama:
137    case Somali:
138    case Telugu:
139    case Thai:
140    case Tibetan:
141    case Tigre:
142    case TigrinyaEr:
143    case TigrinyaErAbegede:
144    case TigrinyaEt:
145    case TigrinyaEtAbegede:
146    case UpperAlpha:
147    case UpperArmenian:
148    case UpperGreek:
149    case UpperHexadecimal:
150    case UpperLatin:
151    case UpperNorwegian:
152    case UpperRoman:
153    case Urdu:
154    case Asterisks:
155    case Footnotes:
156        break;
157    }
158    if (m_renderListMarker.text().isEmpty())
159        return;
160
161    const Font& font = m_renderListMarker.style()->font();
162    TextRun textRun = constructTextRun(&m_renderListMarker, font, m_renderListMarker.text(), m_renderListMarker.style());
163
164    GraphicsContextStateSaver stateSaver(*context, false);
165    if (!m_renderListMarker.style()->isHorizontalWritingMode()) {
166        marker.moveBy(roundedIntPoint(-boxOrigin));
167        marker = marker.transposedRect();
168        marker.moveBy(IntPoint(roundToInt(box.x()), roundToInt(box.y() - m_renderListMarker.logicalHeight())));
169        stateSaver.save();
170        context->translate(marker.x(), marker.maxY());
171        context->rotate(static_cast<float>(deg2rad(90.)));
172        context->translate(-marker.x(), -marker.maxY());
173    }
174
175    TextRunPaintInfo textRunPaintInfo(textRun);
176    textRunPaintInfo.bounds = marker;
177    IntPoint textOrigin = IntPoint(marker.x(), marker.y() + m_renderListMarker.style()->fontMetrics().ascent());
178
179    if (type == Asterisks || type == Footnotes) {
180        context->drawText(font, textRunPaintInfo, textOrigin);
181    } else {
182        // Text is not arbitrary. We can judge whether it's RTL from the first character,
183        // and we only need to handle the direction RightToLeft for now.
184        bool textNeedsReversing = WTF::Unicode::direction(m_renderListMarker.text()[0]) == WTF::Unicode::RightToLeft;
185        StringBuilder reversedText;
186        if (textNeedsReversing) {
187            int length = m_renderListMarker.text().length();
188            reversedText.reserveCapacity(length);
189            for (int i = length - 1; i >= 0; --i)
190                reversedText.append(m_renderListMarker.text()[i]);
191            ASSERT(reversedText.length() == reversedText.capacity());
192            textRun.setText(reversedText.toString());
193        }
194
195        const UChar suffix = m_renderListMarker.listMarkerSuffix(type, m_renderListMarker.listItem()->value());
196        UChar suffixStr[2] = {
197            m_renderListMarker.style()->isLeftToRightDirection() ? suffix : ' ',
198            m_renderListMarker.style()->isLeftToRightDirection() ? ' ' : suffix
199        };
200        TextRun suffixRun = constructTextRun(&m_renderListMarker, font, suffixStr, 2, m_renderListMarker.style(), m_renderListMarker.style()->direction());
201        TextRunPaintInfo suffixRunInfo(suffixRun);
202        suffixRunInfo.bounds = marker;
203
204        if (m_renderListMarker.style()->isLeftToRightDirection()) {
205            context->drawText(font, textRunPaintInfo, textOrigin);
206            context->drawText(font, suffixRunInfo, textOrigin + IntSize(font.width(textRun), 0));
207        } else {
208            context->drawText(font, suffixRunInfo, textOrigin);
209            context->drawText(font, textRunPaintInfo, textOrigin + IntSize(font.width(suffixRun), 0));
210        }
211    }
212}
213
214} // namespace blink
215