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_text.h"
32 #include "third_party/blink/renderer/core/paint/list_marker_painter.h"
33 #include "third_party/blink/renderer/platform/fonts/font.h"
34 
35 namespace blink {
36 
37 const int kCMarkerPaddingPx = 7;
38 
39 // TODO(glebl): Move to core/html/resources/html.css after
40 // Blink starts to support ::marker crbug.com/457718
41 // Recommended UA margin for list markers.
42 const int kCUAMarkerMarginEm = 1;
43 
LayoutListMarker(Element * element)44 LayoutListMarker::LayoutListMarker(Element* element) : LayoutBox(element) {
45   DCHECK(ListItem());
46   SetInline(true);
47   SetIsAtomicInlineLevel(true);
48 }
49 
50 LayoutListMarker::~LayoutListMarker() = default;
51 
WillBeDestroyed()52 void LayoutListMarker::WillBeDestroyed() {
53   if (image_)
54     image_->RemoveClient(this);
55   LayoutBox::WillBeDestroyed();
56 }
57 
ListItem() const58 const LayoutListItem* LayoutListMarker::ListItem() const {
59   LayoutObject* list_item = GetNode()->parentNode()->GetLayoutObject();
60   DCHECK(list_item);
61   DCHECK(list_item->IsListItem());
62   return ToLayoutListItem(list_item);
63 }
64 
ImageBulletSize() const65 LayoutSize LayoutListMarker::ImageBulletSize() const {
66   DCHECK(IsImage());
67   const SimpleFontData* font_data = StyleRef().GetFont().PrimaryFont();
68   DCHECK(font_data);
69   if (!font_data)
70     return LayoutSize();
71 
72   // FIXME: This is a somewhat arbitrary default width. Generated images for
73   // markers really won't become particularly useful until we support the CSS3
74   // marker pseudoclass to allow control over the width and height of the
75   // marker box.
76   LayoutUnit bullet_width =
77       font_data->GetFontMetrics().Ascent() / LayoutUnit(2);
78   return RoundedLayoutSize(
79       image_->ImageSize(GetDocument(), StyleRef().EffectiveZoom(),
80                         LayoutSize(bullet_width, bullet_width),
81                         LayoutObject::ShouldRespectImageOrientation(this)));
82 }
83 
StyleWillChange(StyleDifference diff,const ComputedStyle & new_style)84 void LayoutListMarker::StyleWillChange(StyleDifference diff,
85                                        const ComputedStyle& new_style) {
86   if (Style() &&
87       (new_style.ListStylePosition() != StyleRef().ListStylePosition() ||
88        new_style.ListStyleType() != StyleRef().ListStyleType() ||
89        (new_style.ListStyleType() == EListStyleType::kString &&
90         new_style.ListStyleStringValue() !=
91             StyleRef().ListStyleStringValue()))) {
92     SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation(
93         layout_invalidation_reason::kStyleChange);
94   }
95 
96   LayoutBox::StyleWillChange(diff, new_style);
97 }
98 
StyleDidChange(StyleDifference diff,const ComputedStyle * old_style)99 void LayoutListMarker::StyleDidChange(StyleDifference diff,
100                                       const ComputedStyle* old_style) {
101   LayoutBox::StyleDidChange(diff, old_style);
102 
103   if (image_ != StyleRef().ListStyleImage()) {
104     if (image_)
105       image_->RemoveClient(this);
106     image_ = StyleRef().ListStyleImage();
107     if (image_)
108       image_->AddClient(this);
109   }
110 }
111 
CreateInlineBox()112 InlineBox* LayoutListMarker::CreateInlineBox() {
113   InlineBox* result = LayoutBox::CreateInlineBox();
114   result->SetIsText(IsText());
115   return result;
116 }
117 
IsImage() const118 bool LayoutListMarker::IsImage() const {
119   return image_ && !image_->ErrorOccurred();
120 }
121 
Paint(const PaintInfo & paint_info) const122 void LayoutListMarker::Paint(const PaintInfo& paint_info) const {
123   ListMarkerPainter(*this).Paint(paint_info);
124 }
125 
UpdateLayout()126 void LayoutListMarker::UpdateLayout() {
127   DCHECK(NeedsLayout());
128   LayoutAnalyzer::Scope analyzer(*this);
129 
130   LayoutUnit block_offset = LogicalTop();
131   const LayoutListItem* list_item = ListItem();
132   for (LayoutBox* o = ParentBox(); o && o != list_item; o = o->ParentBox()) {
133     block_offset += o->LogicalTop();
134   }
135   if (list_item->StyleRef().IsLeftToRightDirection()) {
136     line_offset_ = list_item->LogicalLeftOffsetForLine(
137         block_offset, kDoNotIndentText, LayoutUnit());
138   } else {
139     line_offset_ = list_item->LogicalRightOffsetForLine(
140         block_offset, kDoNotIndentText, LayoutUnit());
141   }
142   if (IsImage()) {
143     UpdateMarginsAndContent();
144     LayoutSize image_size(ImageBulletSize());
145     SetWidth(image_size.Width());
146     SetHeight(image_size.Height());
147   } else {
148     const SimpleFontData* font_data = StyleRef().GetFont().PrimaryFont();
149     DCHECK(font_data);
150     SetLogicalWidth(PreferredLogicalWidths().min_size);
151     SetLogicalHeight(
152         LayoutUnit(font_data ? font_data->GetFontMetrics().Height() : 0));
153   }
154 
155   ClearNeedsLayout();
156 }
157 
ImageChanged(WrappedImagePtr o,CanDeferInvalidation)158 void LayoutListMarker::ImageChanged(WrappedImagePtr o, CanDeferInvalidation) {
159   // A list marker can't have a background or border image, so no need to call
160   // the base class method.
161   if (!image_ || o != image_->Data())
162     return;
163 
164   LayoutSize image_size = IsImage() ? ImageBulletSize() : LayoutSize();
165   if (Size() != image_size || image_->ErrorOccurred()) {
166     SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation(
167         layout_invalidation_reason::kImageChanged);
168   } else {
169     SetShouldDoFullPaintInvalidation();
170   }
171 }
172 
UpdateMarginsAndContent()173 void LayoutListMarker::UpdateMarginsAndContent() {
174   UpdateMargins(PreferredLogicalWidths().min_size);
175 }
176 
UpdateContent()177 void LayoutListMarker::UpdateContent() {
178   DCHECK(IntrinsicLogicalWidthsDirty());
179 
180   text_ = "";
181 
182   if (IsImage())
183     return;
184 
185   switch (GetListStyleCategory()) {
186     case ListStyleCategory::kNone:
187       break;
188     case ListStyleCategory::kSymbol:
189       text_ = list_marker_text::GetText(StyleRef().ListStyleType(),
190                                         0);  // value is ignored for these types
191       break;
192     case ListStyleCategory::kLanguage:
193       text_ = list_marker_text::GetText(StyleRef().ListStyleType(),
194                                         ListItem()->Value());
195       break;
196     case ListStyleCategory::kStaticString:
197       text_ = StyleRef().ListStyleStringValue();
198       break;
199   }
200 }
201 
TextAlternative() const202 String LayoutListMarker::TextAlternative() const {
203   if (GetListStyleCategory() == ListStyleCategory::kStaticString)
204     return text_;
205   UChar suffix =
206       list_marker_text::Suffix(StyleRef().ListStyleType(), ListItem()->Value());
207   // Return suffix after the marker text, even in RTL, reflecting speech order.
208   return text_ + suffix + ' ';
209 }
210 
GetWidthOfText(ListStyleCategory category) const211 LayoutUnit LayoutListMarker::GetWidthOfText(ListStyleCategory category) const {
212   // TODO(crbug.com/1012289): this code doesn't support bidi algorithm.
213   if (text_.IsEmpty())
214     return LayoutUnit();
215   const Font& font = StyleRef().GetFont();
216   LayoutUnit item_width = LayoutUnit(font.Width(TextRun(text_)));
217   if (category == ListStyleCategory::kStaticString) {
218     // Don't add a suffix.
219     return item_width;
220   }
221   // TODO(wkorman): Look into constructing a text run for both text and suffix
222   // and painting them together.
223   UChar suffix[2] = {
224       list_marker_text::Suffix(StyleRef().ListStyleType(), ListItem()->Value()),
225       ' '};
226   TextRun run =
227       ConstructTextRun(font, suffix, 2, StyleRef(), StyleRef().Direction());
228   LayoutUnit suffix_space_width = LayoutUnit(font.Width(run));
229   return item_width + suffix_space_width;
230 }
231 
ComputeIntrinsicLogicalWidths() const232 MinMaxSizes LayoutListMarker::ComputeIntrinsicLogicalWidths() const {
233   DCHECK(IntrinsicLogicalWidthsDirty());
234   const_cast<LayoutListMarker*>(this)->UpdateContent();
235 
236   MinMaxSizes sizes;
237   if (IsImage()) {
238     LayoutSize image_size(ImageBulletSize());
239     sizes = StyleRef().IsHorizontalWritingMode() ? image_size.Width()
240                                                  : image_size.Height();
241   } else {
242     ListStyleCategory category = GetListStyleCategory();
243     switch (category) {
244       case ListStyleCategory::kNone:
245         break;
246       case ListStyleCategory::kSymbol:
247         sizes = WidthOfSymbol(StyleRef());
248         break;
249       case ListStyleCategory::kLanguage:
250       case ListStyleCategory::kStaticString:
251         sizes = GetWidthOfText(category);
252         break;
253     }
254   }
255 
256   const_cast<LayoutListMarker*>(this)->UpdateMargins(sizes.min_size);
257   return sizes;
258 }
259 
PreferredLogicalWidths() const260 MinMaxSizes LayoutListMarker::PreferredLogicalWidths() const {
261   return IntrinsicLogicalWidths();
262 }
263 
WidthOfSymbol(const ComputedStyle & style)264 LayoutUnit LayoutListMarker::WidthOfSymbol(const ComputedStyle& style) {
265   const Font& font = style.GetFont();
266   const SimpleFontData* font_data = font.PrimaryFont();
267   DCHECK(font_data);
268   if (!font_data)
269     return LayoutUnit();
270   return LayoutUnit((font_data->GetFontMetrics().Ascent() * 2 / 3 + 1) / 2 + 2);
271 }
272 
UpdateMargins(LayoutUnit marker_inline_size)273 void LayoutListMarker::UpdateMargins(LayoutUnit marker_inline_size) {
274   LayoutUnit margin_start;
275   LayoutUnit margin_end;
276   const ComputedStyle& style = StyleRef();
277   if (IsInsideListMarker()) {
278     std::tie(margin_start, margin_end) =
279         InlineMarginsForInside(style, IsImage());
280   } else {
281     std::tie(margin_start, margin_end) =
282         InlineMarginsForOutside(style, IsImage(), marker_inline_size);
283   }
284 
285   SetMarginStart(margin_start);
286   SetMarginEnd(margin_end);
287 }
288 
InlineMarginsForInside(const ComputedStyle & style,bool is_image)289 std::pair<LayoutUnit, LayoutUnit> LayoutListMarker::InlineMarginsForInside(
290     const ComputedStyle& style,
291     bool is_image) {
292   if (!style.ContentBehavesAsNormal())
293     return {};
294   if (is_image)
295     return {LayoutUnit(), LayoutUnit(kCMarkerPaddingPx)};
296   switch (GetListStyleCategory(style.ListStyleType())) {
297     case ListStyleCategory::kSymbol:
298       return {LayoutUnit(-1),
299               LayoutUnit(kCUAMarkerMarginEm * style.ComputedFontSize())};
300     default:
301       break;
302   }
303   return {};
304 }
305 
InlineMarginsForOutside(const ComputedStyle & style,bool is_image,LayoutUnit marker_inline_size)306 std::pair<LayoutUnit, LayoutUnit> LayoutListMarker::InlineMarginsForOutside(
307     const ComputedStyle& style,
308     bool is_image,
309     LayoutUnit marker_inline_size) {
310   LayoutUnit margin_start;
311   LayoutUnit margin_end;
312   if (!style.ContentBehavesAsNormal()) {
313     margin_start = -marker_inline_size;
314   } else if (is_image) {
315     margin_start = -marker_inline_size - kCMarkerPaddingPx;
316     margin_end = LayoutUnit(kCMarkerPaddingPx);
317   } else {
318     switch (GetListStyleCategory(style.ListStyleType())) {
319       case ListStyleCategory::kNone:
320         break;
321       case ListStyleCategory::kSymbol: {
322         const SimpleFontData* font_data = style.GetFont().PrimaryFont();
323         DCHECK(font_data);
324         if (!font_data)
325           return {};
326         const FontMetrics& font_metrics = font_data->GetFontMetrics();
327         int offset = font_metrics.Ascent() * 2 / 3;
328         margin_start = LayoutUnit(-offset - kCMarkerPaddingPx - 1);
329         margin_end = offset + kCMarkerPaddingPx + 1 - marker_inline_size;
330         break;
331       }
332       default:
333         margin_start = -marker_inline_size;
334     }
335   }
336   DCHECK_EQ(margin_start + margin_end, -marker_inline_size);
337   return {margin_start, margin_end};
338 }
339 
LineHeight(bool first_line,LineDirectionMode direction,LinePositionMode line_position_mode) const340 LayoutUnit LayoutListMarker::LineHeight(
341     bool first_line,
342     LineDirectionMode direction,
343     LinePositionMode line_position_mode) const {
344   if (!IsImage())
345     return ListItem()->LineHeight(first_line, direction,
346                                   kPositionOfInteriorLineBoxes);
347   return LayoutBox::LineHeight(first_line, direction, line_position_mode);
348 }
349 
BaselinePosition(FontBaseline baseline_type,bool first_line,LineDirectionMode direction,LinePositionMode line_position_mode) const350 LayoutUnit LayoutListMarker::BaselinePosition(
351     FontBaseline baseline_type,
352     bool first_line,
353     LineDirectionMode direction,
354     LinePositionMode line_position_mode) const {
355   DCHECK_EQ(line_position_mode, kPositionOnContainingLine);
356   if (!IsImage())
357     return ListItem()->BaselinePosition(baseline_type, first_line, direction,
358                                         kPositionOfInteriorLineBoxes);
359   return LayoutBox::BaselinePosition(baseline_type, first_line, direction,
360                                      line_position_mode);
361 }
362 
GetListStyleCategory() const363 LayoutListMarker::ListStyleCategory LayoutListMarker::GetListStyleCategory()
364     const {
365   return GetListStyleCategory(StyleRef().ListStyleType());
366 }
367 
GetListStyleCategory(EListStyleType type)368 LayoutListMarker::ListStyleCategory LayoutListMarker::GetListStyleCategory(
369     EListStyleType type) {
370   switch (type) {
371     case EListStyleType::kNone:
372       return ListStyleCategory::kNone;
373     case EListStyleType::kString:
374       return ListStyleCategory::kStaticString;
375     case EListStyleType::kDisc:
376     case EListStyleType::kCircle:
377     case EListStyleType::kSquare:
378       return ListStyleCategory::kSymbol;
379     case EListStyleType::kArabicIndic:
380     case EListStyleType::kArmenian:
381     case EListStyleType::kBengali:
382     case EListStyleType::kCambodian:
383     case EListStyleType::kCjkIdeographic:
384     case EListStyleType::kCjkEarthlyBranch:
385     case EListStyleType::kCjkHeavenlyStem:
386     case EListStyleType::kDecimalLeadingZero:
387     case EListStyleType::kDecimal:
388     case EListStyleType::kDevanagari:
389     case EListStyleType::kEthiopicHalehame:
390     case EListStyleType::kEthiopicHalehameAm:
391     case EListStyleType::kEthiopicHalehameTiEr:
392     case EListStyleType::kEthiopicHalehameTiEt:
393     case EListStyleType::kGeorgian:
394     case EListStyleType::kGujarati:
395     case EListStyleType::kGurmukhi:
396     case EListStyleType::kHangul:
397     case EListStyleType::kHangulConsonant:
398     case EListStyleType::kHebrew:
399     case EListStyleType::kHiragana:
400     case EListStyleType::kHiraganaIroha:
401     case EListStyleType::kKannada:
402     case EListStyleType::kKatakana:
403     case EListStyleType::kKatakanaIroha:
404     case EListStyleType::kKhmer:
405     case EListStyleType::kKoreanHangulFormal:
406     case EListStyleType::kKoreanHanjaFormal:
407     case EListStyleType::kKoreanHanjaInformal:
408     case EListStyleType::kLao:
409     case EListStyleType::kLowerAlpha:
410     case EListStyleType::kLowerArmenian:
411     case EListStyleType::kLowerGreek:
412     case EListStyleType::kLowerLatin:
413     case EListStyleType::kLowerRoman:
414     case EListStyleType::kMalayalam:
415     case EListStyleType::kMongolian:
416     case EListStyleType::kMyanmar:
417     case EListStyleType::kOriya:
418     case EListStyleType::kPersian:
419     case EListStyleType::kSimpChineseFormal:
420     case EListStyleType::kSimpChineseInformal:
421     case EListStyleType::kTelugu:
422     case EListStyleType::kThai:
423     case EListStyleType::kTibetan:
424     case EListStyleType::kTradChineseFormal:
425     case EListStyleType::kTradChineseInformal:
426     case EListStyleType::kUpperAlpha:
427     case EListStyleType::kUpperArmenian:
428     case EListStyleType::kUpperLatin:
429     case EListStyleType::kUpperRoman:
430     case EListStyleType::kUrdu:
431       return ListStyleCategory::kLanguage;
432     default:
433       NOTREACHED();
434       return ListStyleCategory::kLanguage;
435   }
436 }
437 
GetRelativeMarkerRect() const438 LayoutRect LayoutListMarker::GetRelativeMarkerRect() const {
439   if (IsImage())
440     return LayoutRect(LayoutPoint(), ImageBulletSize());
441 
442   LayoutRect relative_rect;
443   ListStyleCategory category = GetListStyleCategory();
444   switch (category) {
445     case ListStyleCategory::kNone:
446       return LayoutRect();
447     case ListStyleCategory::kSymbol:
448       return RelativeSymbolMarkerRect(StyleRef(), Size().Width());
449     case ListStyleCategory::kLanguage:
450     case ListStyleCategory::kStaticString: {
451       const SimpleFontData* font_data = StyleRef().GetFont().PrimaryFont();
452       DCHECK(font_data);
453       if (!font_data)
454         return relative_rect;
455       relative_rect =
456           LayoutRect(LayoutUnit(), LayoutUnit(), GetWidthOfText(category),
457                      LayoutUnit(font_data->GetFontMetrics().Height()));
458       break;
459     }
460   }
461 
462   if (!StyleRef().IsHorizontalWritingMode()) {
463     relative_rect = relative_rect.TransposedRect();
464     relative_rect.SetX(Size().Width() - relative_rect.X() -
465                        relative_rect.Width());
466   }
467   return relative_rect;
468 }
469 
RelativeSymbolMarkerRect(const ComputedStyle & style,LayoutUnit width)470 LayoutRect LayoutListMarker::RelativeSymbolMarkerRect(
471     const ComputedStyle& style,
472     LayoutUnit width) {
473   LayoutRect relative_rect;
474   const SimpleFontData* font_data = style.GetFont().PrimaryFont();
475   DCHECK(font_data);
476   if (!font_data)
477     return LayoutRect();
478 
479   // TODO(wkorman): Review and clean up/document the calculations below.
480   // http://crbug.com/543193
481   const FontMetrics& font_metrics = font_data->GetFontMetrics();
482   int ascent = font_metrics.Ascent();
483   int bullet_width = (ascent * 2 / 3 + 1) / 2;
484   relative_rect = LayoutRect(1, 3 * (ascent - ascent * 2 / 3) / 2, bullet_width,
485                              bullet_width);
486   if (!style.IsHorizontalWritingMode()) {
487     relative_rect = relative_rect.TransposedRect();
488     relative_rect.SetX(width - relative_rect.X() - relative_rect.Width());
489   }
490   return relative_rect;
491 }
492 
493 }  // namespace blink
494