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