1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc.
5  *               All rights reserved.
6  * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net)
7  * Copyright (C) 2010 Daniel Bates (dbates@intudata.com)
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public License
20  * along with this library; see the file COPYING.LIB.  If not, write to
21  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  *
24  */
25 
26 #include "third_party/blink/renderer/core/layout/layout_list_marker.h"
27 
28 #include "third_party/blink/renderer/core/layout/api/line_layout_block_flow.h"
29 #include "third_party/blink/renderer/core/layout/layout_analyzer.h"
30 #include "third_party/blink/renderer/core/layout/layout_list_item.h"
31 #include "third_party/blink/renderer/core/layout/list_marker.h"
32 #include "third_party/blink/renderer/core/layout/list_marker_text.h"
33 #include "third_party/blink/renderer/core/paint/list_marker_painter.h"
34 #include "third_party/blink/renderer/platform/fonts/font.h"
35 
36 namespace blink {
37 
LayoutListMarker(Element * element)38 LayoutListMarker::LayoutListMarker(Element* element) : LayoutBox(element) {
39   DCHECK(ListItem());
40   SetInline(true);
41   SetIsAtomicInlineLevel(true);
42 }
43 
44 LayoutListMarker::~LayoutListMarker() = default;
45 
WillBeDestroyed()46 void LayoutListMarker::WillBeDestroyed() {
47   NOT_DESTROYED();
48   if (image_)
49     image_->RemoveClient(this);
50   LayoutBox::WillBeDestroyed();
51 }
52 
ListItem() const53 const LayoutListItem* LayoutListMarker::ListItem() const {
54   NOT_DESTROYED();
55   LayoutObject* list_item = GetNode()->parentNode()->GetLayoutObject();
56   DCHECK(list_item);
57   return To<LayoutListItem>(list_item);
58 }
59 
ImageBulletSize() const60 LayoutSize LayoutListMarker::ImageBulletSize() const {
61   NOT_DESTROYED();
62   DCHECK(IsImage());
63   const SimpleFontData* font_data = StyleRef().GetFont().PrimaryFont();
64   DCHECK(font_data);
65   if (!font_data)
66     return LayoutSize();
67 
68   // FIXME: This is a somewhat arbitrary default width. Generated images for
69   // markers really won't become particularly useful until we support the CSS3
70   // marker pseudoclass to allow control over the width and height of the
71   // marker box.
72   float bullet_width = font_data->GetFontMetrics().Ascent() / 2.0f;
73   return RoundedLayoutSize(
74       image_->ImageSize(GetDocument(), StyleRef().EffectiveZoom(),
75                         FloatSize(bullet_width, bullet_width),
76                         LayoutObject::ShouldRespectImageOrientation(this)));
77 }
78 
ListStyleTypeChanged()79 void LayoutListMarker::ListStyleTypeChanged() {
80   NOT_DESTROYED();
81   if (IsImage())
82     return;
83   SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation(
84       layout_invalidation_reason::kListStyleTypeChange);
85 }
86 
UpdateMarkerImageIfNeeded(StyleImage * image)87 void LayoutListMarker::UpdateMarkerImageIfNeeded(StyleImage* image) {
88   NOT_DESTROYED();
89   if (image_ != image) {
90     if (image_)
91       image_->RemoveClient(this);
92     image_ = image;
93     if (image_)
94       image_->AddClient(this);
95   }
96 }
97 
CreateInlineBox()98 InlineBox* LayoutListMarker::CreateInlineBox() {
99   NOT_DESTROYED();
100   InlineBox* result = LayoutBox::CreateInlineBox();
101   result->SetIsText(IsText());
102   return result;
103 }
104 
IsImage() const105 bool LayoutListMarker::IsImage() const {
106   NOT_DESTROYED();
107   return image_ && !image_->ErrorOccurred();
108 }
109 
Paint(const PaintInfo & paint_info) const110 void LayoutListMarker::Paint(const PaintInfo& paint_info) const {
111   NOT_DESTROYED();
112   ListMarkerPainter(*this).Paint(paint_info);
113 }
114 
UpdateLayout()115 void LayoutListMarker::UpdateLayout() {
116   NOT_DESTROYED();
117   DCHECK(NeedsLayout());
118   LayoutAnalyzer::Scope analyzer(*this);
119 
120   LayoutUnit block_offset = LogicalTop();
121   const LayoutListItem* list_item = ListItem();
122   for (LayoutBox* o = ParentBox(); o && o != list_item; o = o->ParentBox()) {
123     block_offset += o->LogicalTop();
124   }
125   if (list_item->StyleRef().IsLeftToRightDirection()) {
126     list_item_inline_start_offset_ = list_item->LogicalLeftOffsetForLine(
127         block_offset, kDoNotIndentText, LayoutUnit());
128   } else {
129     list_item_inline_start_offset_ = list_item->LogicalRightOffsetForLine(
130         block_offset, kDoNotIndentText, LayoutUnit());
131   }
132   if (IsImage()) {
133     UpdateMargins();
134     LayoutSize image_size(ImageBulletSize());
135     SetWidth(image_size.Width());
136     SetHeight(image_size.Height());
137   } else {
138     const SimpleFontData* font_data = StyleRef().GetFont().PrimaryFont();
139     DCHECK(font_data);
140     SetLogicalWidth(PreferredLogicalWidths().min_size);
141     SetLogicalHeight(
142         LayoutUnit(font_data ? font_data->GetFontMetrics().Height() : 0));
143   }
144 
145   ClearNeedsLayout();
146 }
147 
ImageChanged(WrappedImagePtr o,CanDeferInvalidation)148 void LayoutListMarker::ImageChanged(WrappedImagePtr o, CanDeferInvalidation) {
149   NOT_DESTROYED();
150   // A list marker can't have a background or border image, so no need to call
151   // the base class method.
152   if (!image_ || o != image_->Data())
153     return;
154 
155   LayoutSize image_size = IsImage() ? ImageBulletSize() : LayoutSize();
156   if (Size() != image_size || image_->ErrorOccurred()) {
157     SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation(
158         layout_invalidation_reason::kImageChanged);
159   } else {
160     SetShouldDoFullPaintInvalidation();
161   }
162 }
163 
UpdateContent()164 void LayoutListMarker::UpdateContent() {
165   NOT_DESTROYED();
166   DCHECK(IntrinsicLogicalWidthsDirty());
167 
168   text_ = "";
169 
170   if (IsImage())
171     return;
172 
173   switch (GetListStyleCategory()) {
174     case ListMarker::ListStyleCategory::kNone:
175       break;
176     case ListMarker::ListStyleCategory::kSymbol:
177       text_ = list_marker_text::GetText(StyleRef().ListStyleType(),
178                                         0);  // value is ignored for these types
179       break;
180     case ListMarker::ListStyleCategory::kLanguage:
181       text_ = list_marker_text::GetText(StyleRef().ListStyleType(),
182                                         ListItem()->Value());
183       break;
184     case ListMarker::ListStyleCategory::kStaticString:
185       text_ = StyleRef().ListStyleStringValue();
186       break;
187   }
188 }
189 
TextAlternative() const190 String LayoutListMarker::TextAlternative() const {
191   NOT_DESTROYED();
192   if (GetListStyleCategory() == ListMarker::ListStyleCategory::kStaticString)
193     return text_;
194   UChar suffix =
195       list_marker_text::Suffix(StyleRef().ListStyleType(), ListItem()->Value());
196   // Return suffix after the marker text, even in RTL, reflecting speech order.
197   return text_ + suffix + ' ';
198 }
199 
GetWidthOfText(ListMarker::ListStyleCategory category) const200 LayoutUnit LayoutListMarker::GetWidthOfText(
201     ListMarker::ListStyleCategory category) const {
202   NOT_DESTROYED();
203   // TODO(crbug.com/1012289): this code doesn't support bidi algorithm.
204   if (text_.IsEmpty())
205     return LayoutUnit();
206   const Font& font = StyleRef().GetFont();
207   LayoutUnit item_width = LayoutUnit(font.Width(TextRun(text_)));
208   if (category == ListMarker::ListStyleCategory::kStaticString) {
209     // Don't add a suffix.
210     return item_width;
211   }
212   // TODO(wkorman): Look into constructing a text run for both text and suffix
213   // and painting them together.
214   UChar suffix[2] = {
215       list_marker_text::Suffix(StyleRef().ListStyleType(), ListItem()->Value()),
216       ' '};
217   TextRun run =
218       ConstructTextRun(font, suffix, 2, StyleRef(), StyleRef().Direction());
219   LayoutUnit suffix_space_width = LayoutUnit(font.Width(run));
220   return item_width + suffix_space_width;
221 }
222 
ComputeIntrinsicLogicalWidths() const223 MinMaxSizes LayoutListMarker::ComputeIntrinsicLogicalWidths() const {
224   NOT_DESTROYED();
225   DCHECK(IntrinsicLogicalWidthsDirty());
226   const_cast<LayoutListMarker*>(this)->UpdateContent();
227 
228   MinMaxSizes sizes;
229   if (IsImage()) {
230     LayoutSize image_size(ImageBulletSize());
231     sizes = StyleRef().IsHorizontalWritingMode() ? image_size.Width()
232                                                  : image_size.Height();
233   } else {
234     ListMarker::ListStyleCategory category = GetListStyleCategory();
235     switch (category) {
236       case ListMarker::ListStyleCategory::kNone:
237         break;
238       case ListMarker::ListStyleCategory::kSymbol:
239         sizes = ListMarker::WidthOfSymbol(StyleRef());
240         break;
241       case ListMarker::ListStyleCategory::kLanguage:
242       case ListMarker::ListStyleCategory::kStaticString:
243         sizes = GetWidthOfText(category);
244         break;
245     }
246   }
247 
248   const_cast<LayoutListMarker*>(this)->UpdateMargins(sizes.min_size);
249   return sizes;
250 }
251 
PreferredLogicalWidths() const252 MinMaxSizes LayoutListMarker::PreferredLogicalWidths() const {
253   NOT_DESTROYED();
254   return IntrinsicLogicalWidths();
255 }
256 
UpdateMargins(LayoutUnit marker_inline_size)257 void LayoutListMarker::UpdateMargins(LayoutUnit marker_inline_size) {
258   NOT_DESTROYED();
259   LayoutUnit margin_start;
260   LayoutUnit margin_end;
261   const ComputedStyle& style = StyleRef();
262   const ComputedStyle& list_item_style = ListItem()->StyleRef();
263   if (IsInside()) {
264     std::tie(margin_start, margin_end) =
265         ListMarker::InlineMarginsForInside(style, list_item_style);
266   } else {
267     std::tie(margin_start, margin_end) = ListMarker::InlineMarginsForOutside(
268         style, list_item_style, marker_inline_size);
269   }
270 
271   SetMarginStart(margin_start);
272   SetMarginEnd(margin_end);
273 }
274 
UpdateMargins()275 void LayoutListMarker::UpdateMargins() {
276   NOT_DESTROYED();
277   UpdateMargins(PreferredLogicalWidths().min_size);
278 }
279 
LineHeight(bool first_line,LineDirectionMode direction,LinePositionMode line_position_mode) const280 LayoutUnit LayoutListMarker::LineHeight(
281     bool first_line,
282     LineDirectionMode direction,
283     LinePositionMode line_position_mode) const {
284   NOT_DESTROYED();
285   if (!IsImage())
286     return ListItem()->LineHeight(first_line, direction,
287                                   kPositionOfInteriorLineBoxes);
288   return LayoutBox::LineHeight(first_line, direction, line_position_mode);
289 }
290 
BaselinePosition(FontBaseline baseline_type,bool first_line,LineDirectionMode direction,LinePositionMode line_position_mode) const291 LayoutUnit LayoutListMarker::BaselinePosition(
292     FontBaseline baseline_type,
293     bool first_line,
294     LineDirectionMode direction,
295     LinePositionMode line_position_mode) const {
296   NOT_DESTROYED();
297   DCHECK_EQ(line_position_mode, kPositionOnContainingLine);
298   if (!IsImage())
299     return ListItem()->BaselinePosition(baseline_type, first_line, direction,
300                                         kPositionOfInteriorLineBoxes);
301   return LayoutBox::BaselinePosition(baseline_type, first_line, direction,
302                                      line_position_mode);
303 }
304 
GetListStyleCategory() const305 ListMarker::ListStyleCategory LayoutListMarker::GetListStyleCategory() const {
306   NOT_DESTROYED();
307   return ListMarker::GetListStyleCategory(StyleRef().ListStyleType());
308 }
309 
IsInside() const310 bool LayoutListMarker::IsInside() const {
311   NOT_DESTROYED();
312   const LayoutListItem* list_item = ListItem();
313   const ComputedStyle& parent_style = list_item->StyleRef();
314   return parent_style.ListStylePosition() == EListStylePosition::kInside ||
315          (IsA<HTMLLIElement>(list_item->GetNode()) &&
316           !parent_style.IsInsideListElement());
317 }
318 
GetRelativeMarkerRect() const319 LayoutRect LayoutListMarker::GetRelativeMarkerRect() const {
320   NOT_DESTROYED();
321   if (IsImage())
322     return LayoutRect(LayoutPoint(), ImageBulletSize());
323 
324   LayoutRect relative_rect;
325   ListMarker::ListStyleCategory category = GetListStyleCategory();
326   switch (category) {
327     case ListMarker::ListStyleCategory::kNone:
328       return LayoutRect();
329     case ListMarker::ListStyleCategory::kSymbol:
330       return ListMarker::RelativeSymbolMarkerRect(StyleRef(), Size().Width());
331     case ListMarker::ListStyleCategory::kLanguage:
332     case ListMarker::ListStyleCategory::kStaticString: {
333       const SimpleFontData* font_data = StyleRef().GetFont().PrimaryFont();
334       DCHECK(font_data);
335       if (!font_data)
336         return relative_rect;
337       relative_rect =
338           LayoutRect(LayoutUnit(), LayoutUnit(), GetWidthOfText(category),
339                      LayoutUnit(font_data->GetFontMetrics().Height()));
340       break;
341     }
342   }
343 
344   if (!StyleRef().IsHorizontalWritingMode()) {
345     relative_rect = relative_rect.TransposedRect();
346     relative_rect.SetX(Size().Width() - relative_rect.X() -
347                        relative_rect.Width());
348   }
349   return relative_rect;
350 }
351 
352 }  // namespace blink
353