summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/layout/ng/inline/ng_line_breaker.h
blob: 36f60e4f5bfde588ebd1995a7a5e4b30152ec044 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_BREAKER_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_BREAKER_H_

#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/layout/ng/exclusions/ng_line_layout_opportunity.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h"
#include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h"
#include "third_party/blink/renderer/platform/text/text_break_iterator.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"

namespace blink {

class Hyphenation;
class NGBlockBreakToken;
class NGInlineBreakToken;
class NGInlineItem;
class NGLineInfo;

// The line breaker needs to know which mode its in to properly handle floats.
enum class NGLineBreakerMode { kContent, kMinContent, kMaxContent };

// Represents a line breaker.
//
// This class measures each NGInlineItem and determines items to form a line,
// so that NGInlineLayoutAlgorithm can build a line box from the output.
class CORE_EXPORT NGLineBreaker {
  STACK_ALLOCATED();

 public:
  NGLineBreaker(NGInlineNode,
                NGLineBreakerMode,
                const NGConstraintSpace&,
                const NGLineLayoutOpportunity&,
                const NGPositionedFloatVector& leading_floats,
                unsigned handled_leading_floats_index,
                const NGInlineBreakToken*,
                NGExclusionSpace*);
  ~NGLineBreaker();

  const NGInlineItemsData& ItemsData() const { return items_data_; }

  // True if the last line has `box-decoration-break: clone`, which affected the
  // size.
  bool HasClonedBoxDecorations() const { return has_cloned_box_decorations_; }

  // Compute the next line break point and produces NGInlineItemResults for
  // the line.
  void NextLine(NGLineInfo*);

  bool IsFinished() const { return item_index_ >= Items().size(); }

  // Create an NGInlineBreakToken for the last line returned by NextLine().
  scoped_refptr<NGInlineBreakToken> CreateBreakToken(const NGLineInfo&) const;

  void PropagateBreakToken(scoped_refptr<const NGBlockBreakToken>);
  Vector<scoped_refptr<const NGBlockBreakToken>>& PropagatedBreakTokens() {
    return propagated_break_tokens_;
  }

  // Computing |NGLineBreakerMode::kMinContent| with |MaxSizeCache| caches
  // information that can help computing |kMaxContent|. It is recommended to set
  // this when computing both |kMinContent| and |kMaxContent|.
  using MaxSizeCache = Vector<LayoutUnit, 64>;
  void SetIntrinsicSizeOutputs(MaxSizeCache* max_size_cache,
                               bool* depends_on_block_constraints_out);

  // Compute NGInlineItemResult for an open tag item.
  // Returns true if this item has edge and may have non-zero inline size.
  static bool ComputeOpenTagResult(const NGInlineItem&,
                                   const NGConstraintSpace&,
                                   NGInlineItemResult*);

  // This enum is private, except for |WhitespaceStateForTesting()|. See
  // |whitespace_| member.
  enum class WhitespaceState {
    kLeading,
    kNone,
    kUnknown,
    kCollapsible,
    kCollapsed,
    kPreserved,
  };
  WhitespaceState TrailingWhitespaceForTesting() const {
    return trailing_whitespace_;
  }

 private:
  const String& Text() const { return text_content_; }
  const Vector<NGInlineItem>& Items() const { return items_data_.items; }

  String TextContentForLineBreak() const;

  NGInlineItemResult* AddItem(const NGInlineItem&,
                              unsigned end_offset,
                              NGLineInfo*);
  NGInlineItemResult* AddItem(const NGInlineItem&, NGLineInfo*);

  void BreakLine(NGLineInfo*);
  void PrepareNextLine(NGLineInfo*);

  void ComputeLineLocation(NGLineInfo*) const;

  enum class LineBreakState {
    // The line breaking is complete.
    kDone,

    // Overflow is detected without any earlier break opportunities. This line
    // should break at the earliest break opportunity.
    kOverflow,

    // Should complete the line at the earliest possible point.
    // Trailing spaces, <br>, or close tags should be included to the line even
    // when it is overflowing.
    kTrailing,

    // Looking for more items to fit into the current line.
    kContinue,
  };

  void HandleText(const NGInlineItem& item, const ShapeResult&, NGLineInfo*);
  // Split |item| by glyphs, and add them to |line_info|.
  // This is for SVG <text>.
  void SplitTextByGlyphs(const NGInlineItem& item, NGLineInfo* line_info);
  enum BreakResult { kSuccess, kOverflow };
  BreakResult BreakText(NGInlineItemResult*,
                        const NGInlineItem&,
                        const ShapeResult&,
                        LayoutUnit available_width,
                        LayoutUnit available_width_with_hyphens,
                        NGLineInfo*);
  bool BreakTextAtPreviousBreakOpportunity(NGInlineItemResult* item_result);
  bool HandleTextForFastMinContent(NGInlineItemResult*,
                                   const NGInlineItem&,
                                   const ShapeResult&,
                                   NGLineInfo*);
  void HandleEmptyText(const NGInlineItem& item, NGLineInfo*);

  scoped_refptr<ShapeResultView> TruncateLineEndResult(
      const NGLineInfo&,
      const NGInlineItemResult&,
      unsigned end_offset);
  void UpdateShapeResult(const NGLineInfo&, NGInlineItemResult*);
  scoped_refptr<ShapeResult> ShapeText(const NGInlineItem&,
                                       unsigned start,
                                       unsigned end);

  void HandleTrailingSpaces(const NGInlineItem&, NGLineInfo*);
  void HandleTrailingSpaces(const NGInlineItem&,
                            const ShapeResult*,
                            NGLineInfo*);
  void RemoveTrailingCollapsibleSpace(NGLineInfo*);
  LayoutUnit TrailingCollapsibleSpaceWidth(NGLineInfo*);
  void ComputeTrailingCollapsibleSpace(NGLineInfo*);

  void HandleControlItem(const NGInlineItem&, NGLineInfo*);
  void HandleBidiControlItem(const NGInlineItem&, NGLineInfo*);
  void HandleAtomicInline(
      const NGInlineItem&,
      NGLineInfo*);
  bool ShouldForceCanBreakAfter(const NGInlineItemResult& item_result) const;
  void HandleFloat(const NGInlineItem&,
                   NGLineInfo*);
  void HandleOutOfFlowPositioned(const NGInlineItem&, NGLineInfo*);

  void HandleOpenTag(const NGInlineItem&, NGLineInfo*);
  void HandleCloseTag(const NGInlineItem&, NGLineInfo*);

  bool HandleOverflowIfNeeded(NGLineInfo*);
  void HandleOverflow(NGLineInfo*);
  void RewindOverflow(unsigned new_end, NGLineInfo*);
  void Rewind(unsigned new_end, NGLineInfo*);
  void ResetRewindLoopDetector() { last_rewind_.reset(); }

  const ComputedStyle& ComputeCurrentStyle(unsigned item_result_index,
                                           NGLineInfo*) const;
  void SetCurrentStyle(const ComputedStyle&);

  bool IsPreviousItemOfType(NGInlineItem::NGInlineItemType);
  void MoveToNextOf(const NGInlineItem&);
  void MoveToNextOf(const NGInlineItemResult&);

  void ComputeBaseDirection();
  void RecalcClonedBoxDecorations();

  LayoutUnit AvailableWidth() const {
    DCHECK_EQ(available_width_, ComputeAvailableWidth());
    return available_width_;
  }
  LayoutUnit AvailableWidthToFit() const {
    return AvailableWidth().AddEpsilon();
  }
  LayoutUnit RemainingAvailableWidth() const {
    return AvailableWidthToFit() - position_;
  }
  bool CanFitOnLine() const { return position_ <= AvailableWidthToFit(); }
  LayoutUnit ComputeAvailableWidth() const;

  void ClearNeedsLayout(const NGInlineItem& item);

  // True if the current line is hyphenated.
  bool HasHyphen() const { return hyphen_index_.has_value(); }
  LayoutUnit AddHyphen(NGInlineItemResults* item_results,
                       wtf_size_t index,
                       NGInlineItemResult* item_result,
                       const NGInlineItem& item);
  LayoutUnit AddHyphen(NGInlineItemResults* item_results, wtf_size_t index);
  LayoutUnit AddHyphen(NGInlineItemResults* item_results,
                       NGInlineItemResult* item_result,
                       const NGInlineItem& item);
  LayoutUnit RemoveHyphen(NGInlineItemResults* item_results);
  void RestoreLastHyphen(NGInlineItemResults* item_results);
  void FinalizeHyphen(NGInlineItemResults* item_results);

  // Represents the current offset of the input.
  LineBreakState state_;
  unsigned item_index_ = 0;
  unsigned offset_ = 0;

  // |WhitespaceState| of the current end. When a line is broken, this indicates
  // the state of trailing whitespaces.
  WhitespaceState trailing_whitespace_;

  // The current position from inline_start. Unlike NGInlineLayoutAlgorithm
  // that computes position in visual order, this position in logical order.
  LayoutUnit position_;
  LayoutUnit available_width_;
  NGLineLayoutOpportunity line_opportunity_;

  NGInlineNode node_;

  NGLineBreakerMode mode_;

  // True if node_ is an SVG <text>.
  const bool is_svg_text_;

  // True if this line is the "first formatted line".
  // https://www.w3.org/TR/CSS22/selector.html#first-formatted-line
  bool is_first_formatted_line_ = false;

  bool use_first_line_style_ = false;

  // True when current box allows line wrapping.
  bool auto_wrap_ = false;

  // True when current box has 'word-break/word-wrap: break-word'.
  bool break_anywhere_if_overflow_ = false;

  // Force LineBreakType::kBreakCharacter by ignoring the current style if
  // |break_anywhere_if_overflow_| is set. Set to find grapheme cluster
  // boundaries for 'break-word' after overflow.
  bool override_break_anywhere_ = false;

  // True when breaking at soft hyphens (U+00AD) is allowed.
  bool enable_soft_hyphen_ = true;

  // True when the line we are breaking has a list marker.
  bool has_list_marker_ = false;

  // Set when the line ended with a forced break. Used to setup the states for
  // the next line.
  bool is_after_forced_break_ = false;

  // Set in quirks mode when we're not supposed to break inside table cells
  // between images, and between text and images.
  bool sticky_images_quirk_ = false;

  // True if the resultant line contains a RubyRun with inline-end overhang.
  bool maybe_have_end_overhang_ = false;

  const NGInlineItemsData& items_data_;

  // The text content of this node. This is same as |items_data_.text_content|
  // except when sticky images quirk is needed. See
  // |NGInlineNode::TextContentForContentSize|.
  String text_content_;

  const NGConstraintSpace& constraint_space_;
  NGExclusionSpace* exclusion_space_;
  scoped_refptr<const NGInlineBreakToken> break_token_;
  scoped_refptr<const ComputedStyle> current_style_;

  LazyLineBreakIterator break_iterator_;
  HarfBuzzShaper shaper_;
  ShapeResultSpacing<String> spacing_;
  bool previous_line_had_forced_break_ = false;
  const Hyphenation* hyphenation_ = nullptr;

  absl::optional<wtf_size_t> hyphen_index_;
  bool has_any_hyphens_ = false;

  // Cache the result of |ComputeTrailingCollapsibleSpace| to avoid shaping
  // multiple times.
  struct TrailingCollapsibleSpace {
    NGInlineItemResult* item_result;
    scoped_refptr<const ShapeResultView> collapsed_shape_result;
  };
  absl::optional<TrailingCollapsibleSpace> trailing_collapsible_space_;

  // Keep track of handled float items. See HandleFloat().
  const NGPositionedFloatVector& leading_floats_;
  unsigned leading_floats_index_ = 0u;
  unsigned handled_leading_floats_index_;

  // Cache for computing |MinMaxSize|. See |MaxSizeCache|.
  MaxSizeCache* max_size_cache_ = nullptr;

  bool* depends_on_block_constraints_out_ = nullptr;

  // Keep the last item |HandleTextForFastMinContent()| has handled. This is
  // used to fallback the last word to |HandleText()|.
  const NGInlineItem* fast_min_content_item_ = nullptr;

  // The current base direction for the bidi algorithm.
  // This is copied from NGInlineNode, then updated after each forced line break
  // if 'unicode-bidi: plaintext'.
  TextDirection base_direction_;

  Vector<scoped_refptr<const NGBlockBreakToken>> propagated_break_tokens_;

  // Fields for `box-decoration-break: clone`.
  unsigned cloned_box_decorations_count_ = 0;
  LayoutUnit cloned_box_decorations_initial_size_;
  LayoutUnit cloned_box_decorations_end_size_;
  bool has_cloned_box_decorations_ = false;

  // These fields are to detect rewind-loop.
  struct RewindIndex {
    wtf_size_t from_item_index;
    wtf_size_t to_index;
  };
  absl::optional<RewindIndex> last_rewind_;
};

}  // namespace blink

#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_BREAKER_H_