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