1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_BREAKER_H_
6 #define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_BREAKER_H_
7 
8 #include "base/optional.h"
9 #include "third_party/blink/renderer/core/core_export.h"
10 #include "third_party/blink/renderer/core/layout/ng/exclusions/ng_line_layout_opportunity.h"
11 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_item_result.h"
12 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h"
13 #include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h"
14 #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h"
15 #include "third_party/blink/renderer/platform/text/text_break_iterator.h"
16 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
17 
18 namespace blink {
19 
20 class Hyphenation;
21 class NGInlineBreakToken;
22 class NGInlineItem;
23 
24 // The line breaker needs to know which mode its in to properly handle floats.
25 enum class NGLineBreakerMode { kContent, kMinContent, kMaxContent };
26 
27 // Represents a line breaker.
28 //
29 // This class measures each NGInlineItem and determines items to form a line,
30 // so that NGInlineLayoutAlgorithm can build a line box from the output.
31 class CORE_EXPORT NGLineBreaker {
32   STACK_ALLOCATED();
33 
34  public:
35   NGLineBreaker(NGInlineNode,
36                 NGLineBreakerMode,
37                 const NGConstraintSpace&,
38                 const NGLineLayoutOpportunity&,
39                 const NGPositionedFloatVector& leading_floats,
40                 unsigned handled_leading_floats_index,
41                 const NGInlineBreakToken*,
42                 NGExclusionSpace*);
43   ~NGLineBreaker();
44 
ItemsData()45   const NGInlineItemsData& ItemsData() const { return items_data_; }
46 
47   // Compute the next line break point and produces NGInlineItemResults for
48   // the line.
NextLine(NGLineInfo * line_info)49   inline void NextLine(NGLineInfo* line_info) {
50     NextLine(kIndefiniteSize, line_info);
51   }
52 
53   // During the min/max size calculation we need a special percentage
54   // resolution block-size to pass to children/pass to children.
55   // TODO(layout-dev): Split into two methods (NextLine/NextLineForMinMax) or,
56   // better yet, subclass or templetize the line-breaker for Min/Max computation
57   // if we can do that without incurring a performance penalty
58   void NextLine(LayoutUnit percentage_resolution_block_size_for_min_max,
59                 NGLineInfo*);
60 
IsFinished()61   bool IsFinished() const { return item_index_ >= Items().size(); }
62 
63   // Create an NGInlineBreakToken for the last line returned by NextLine().
64   scoped_refptr<NGInlineBreakToken> CreateBreakToken(const NGLineInfo&) const;
65 
66   // Computing |NGLineBreakerMode::kMinContent| with |MaxSizeCache| caches
67   // information that can help computing |kMaxContent|. It is recommended to set
68   // this when computing both |kMinContent| and |kMaxContent|.
69   using MaxSizeCache = Vector<LayoutUnit, 64>;
70   void SetMaxSizeCache(MaxSizeCache* max_size_cache);
71 
72   // Compute NGInlineItemResult for an open tag item.
73   // Returns true if this item has edge and may have non-zero inline size.
74   static bool ComputeOpenTagResult(const NGInlineItem&,
75                                    const NGConstraintSpace&,
76                                    NGInlineItemResult*);
77 
78   // This enum is private, except for |WhitespaceStateForTesting()|. See
79   // |whitespace_| member.
80   enum class WhitespaceState {
81     kLeading,
82     kNone,
83     kUnknown,
84     kCollapsible,
85     kCollapsed,
86     kPreserved,
87   };
TrailingWhitespaceForTesting()88   WhitespaceState TrailingWhitespaceForTesting() const {
89     return trailing_whitespace_;
90   }
91 
92  private:
Text()93   const String& Text() const { return text_content_; }
Items()94   const Vector<NGInlineItem>& Items() const { return items_data_.items; }
95 
96   String TextContentForLineBreak() const;
97 
98   NGInlineItemResult* AddItem(const NGInlineItem&,
99                               unsigned end_offset,
100                               NGLineInfo*);
101   NGInlineItemResult* AddItem(const NGInlineItem&, NGLineInfo*);
102 
103   void BreakLine(LayoutUnit percentage_resolution_block_size_for_min_max,
104                  NGLineInfo*);
105   void PrepareNextLine(NGLineInfo*);
106 
107   void ComputeLineLocation(NGLineInfo*) const;
108 
109   enum class LineBreakState {
110     // The line breaking is complete.
111     kDone,
112 
113     // Overflow is detected without any earlier break opportunities. This line
114     // should break at the earliest break opportunity.
115     kOverflow,
116 
117     // Should complete the line at the earliest possible point.
118     // Trailing spaces, <br>, or close tags should be included to the line even
119     // when it is overflowing.
120     kTrailing,
121 
122     // Looking for more items to fit into the current line.
123     kContinue,
124   };
125 
HandleText(const NGInlineItem & item,NGLineInfo * line_info)126   inline void HandleText(const NGInlineItem& item, NGLineInfo* line_info) {
127     DCHECK(item.TextShapeResult());
128     HandleText(item, *item.TextShapeResult(), line_info);
129   }
130   void HandleText(const NGInlineItem& item, const ShapeResult&, NGLineInfo*);
131   enum BreakResult { kSuccess, kOverflow };
132   BreakResult BreakText(NGInlineItemResult*,
133                         const NGInlineItem&,
134                         const ShapeResult&,
135                         LayoutUnit available_width,
136                         LayoutUnit available_width_with_hyphens,
137                         NGLineInfo*);
138   bool BreakTextAtPreviousBreakOpportunity(NGInlineItemResult* item_result);
139   bool HandleTextForFastMinContent(NGInlineItemResult*,
140                                    const NGInlineItem&,
141                                    const ShapeResult&,
142                                    NGLineInfo*);
143   void HandleEmptyText(const NGInlineItem& item, NGLineInfo*);
144 
145   scoped_refptr<ShapeResultView> TruncateLineEndResult(
146       const NGLineInfo&,
147       const NGInlineItemResult&,
148       unsigned end_offset);
149   void UpdateShapeResult(const NGLineInfo&, NGInlineItemResult*);
150   scoped_refptr<ShapeResult> ShapeText(const NGInlineItem&,
151                                        unsigned start,
152                                        unsigned end);
153 
154   void HandleTrailingSpaces(const NGInlineItem&, NGLineInfo*);
155   void HandleTrailingSpaces(const NGInlineItem&,
156                             const ShapeResult&,
157                             NGLineInfo*);
158   void RemoveTrailingCollapsibleSpace(NGLineInfo*);
159   LayoutUnit TrailingCollapsibleSpaceWidth(NGLineInfo*);
160   void ComputeTrailingCollapsibleSpace(NGLineInfo*);
161 
162   void HandleControlItem(const NGInlineItem&, NGLineInfo*);
163   void HandleBidiControlItem(const NGInlineItem&, NGLineInfo*);
164   void HandleAtomicInline(
165       const NGInlineItem&,
166       LayoutUnit percentage_resolution_block_size_for_min_max,
167       NGLineInfo*);
168   bool ShouldForceCanBreakAfter(const NGInlineItemResult& item_result) const;
169   void HandleFloat(const NGInlineItem&,
170                    NGLineInfo*);
171   void HandleOutOfFlowPositioned(const NGInlineItem&, NGLineInfo*);
172 
173   void HandleOpenTag(const NGInlineItem&, NGLineInfo*);
174   void HandleCloseTag(const NGInlineItem&, NGLineInfo*);
175 
176   bool HandleOverflowIfNeeded(NGLineInfo*);
177   void HandleOverflow(NGLineInfo*);
178   void RewindOverflow(unsigned new_end, NGLineInfo*);
179   void Rewind(unsigned new_end, NGLineInfo*);
ResetRewindLoopDetector()180   void ResetRewindLoopDetector() {
181 #if DCHECK_IS_ON()
182     last_rewind_from_item_index_ = last_rewind_to_item_index_ = 0;
183 #endif
184   }
185 
186   const ComputedStyle& ComputeCurrentStyle(unsigned item_result_index,
187                                            NGLineInfo*) const;
188   void SetCurrentStyle(const ComputedStyle&);
189 
190   void MoveToNextOf(const NGInlineItem&);
191   void MoveToNextOf(const NGInlineItemResult&);
192 
193   void ComputeBaseDirection();
194 
AvailableWidth()195   LayoutUnit AvailableWidth() const {
196     DCHECK_EQ(available_width_, ComputeAvailableWidth());
197     return available_width_;
198   }
AvailableWidthToFit()199   LayoutUnit AvailableWidthToFit() const {
200     return AvailableWidth().AddEpsilon();
201   }
RemainingAvailableWidth()202   LayoutUnit RemainingAvailableWidth() const {
203     return AvailableWidthToFit() - position_;
204   }
CanFitOnLine()205   bool CanFitOnLine() const { return position_ <= AvailableWidthToFit(); }
206   LayoutUnit ComputeAvailableWidth() const;
207 
208   // Represents the current offset of the input.
209   LineBreakState state_;
210   unsigned item_index_ = 0;
211   unsigned offset_ = 0;
212 
213   // |WhitespaceState| of the current end. When a line is broken, this indicates
214   // the state of trailing whitespaces.
215   WhitespaceState trailing_whitespace_;
216 
217   // The current position from inline_start. Unlike NGInlineLayoutAlgorithm
218   // that computes position in visual order, this position in logical order.
219   LayoutUnit position_;
220   LayoutUnit available_width_;
221   NGLineLayoutOpportunity line_opportunity_;
222 
223   NGInlineNode node_;
224 
225   NGLineBreakerMode mode_;
226 
227   // True if this line is the "first formatted line".
228   // https://www.w3.org/TR/CSS22/selector.html#first-formatted-line
229   bool is_first_formatted_line_ = false;
230 
231   bool use_first_line_style_ = false;
232 
233   // True when current box allows line wrapping.
234   bool auto_wrap_ = false;
235 
236   // True when current box has 'word-break/word-wrap: break-word'.
237   bool break_anywhere_if_overflow_ = false;
238 
239   // Force LineBreakType::kBreakCharacter by ignoring the current style if
240   // |break_anywhere_if_overflow_| is set. Set to find grapheme cluster
241   // boundaries for 'break-word' after overflow.
242   bool override_break_anywhere_ = false;
243 
244   // True when breaking at soft hyphens (U+00AD) is allowed.
245   bool enable_soft_hyphen_ = true;
246 
247   // True in quirks mode or limited-quirks mode, which require line-height
248   // quirks.
249   // https://quirks.spec.whatwg.org/#the-line-height-calculation-quirk
250   bool in_line_height_quirks_mode_ = false;
251 
252   // True when the line we are breaking has a list marker.
253   bool has_list_marker_ = false;
254 
255   // Set when the line ended with a forced break. Used to setup the states for
256   // the next line.
257   bool is_after_forced_break_ = false;
258 
259   // Set in quirks mode when we're not supposed to break inside table cells
260   // between images, and between text and images.
261   bool sticky_images_quirk_ = false;
262 
263   const NGInlineItemsData& items_data_;
264 
265   // The text content of this node. This is same as |items_data_.text_content|
266   // except when sticky images quirk is needed. See
267   // |NGInlineNode::TextContentForContentSize|.
268   String text_content_;
269 
270   const NGConstraintSpace& constraint_space_;
271   NGExclusionSpace* exclusion_space_;
272   scoped_refptr<const NGInlineBreakToken> break_token_;
273   scoped_refptr<const ComputedStyle> current_style_;
274 
275   LazyLineBreakIterator break_iterator_;
276   HarfBuzzShaper shaper_;
277   ShapeResultSpacing<String> spacing_;
278   bool previous_line_had_forced_break_ = false;
279   const Hyphenation* hyphenation_ = nullptr;
280 
281   // Cache the result of |ComputeTrailingCollapsibleSpace| to avoid shaping
282   // multiple times.
283   struct TrailingCollapsibleSpace {
284     NGInlineItemResult* item_result;
285     scoped_refptr<const ShapeResultView> collapsed_shape_result;
286   };
287   base::Optional<TrailingCollapsibleSpace> trailing_collapsible_space_;
288 
289   // Keep track of handled float items. See HandleFloat().
290   const NGPositionedFloatVector& leading_floats_;
291   unsigned leading_floats_index_ = 0u;
292   unsigned handled_leading_floats_index_;
293 
294   // Cache for computing |MinMaxSize|. See |MaxSizeCache|.
295   MaxSizeCache* max_size_cache_ = nullptr;
296 
297   // Keep the last item |HandleTextForFastMinContent()| has handled. This is
298   // used to fallback the last word to |HandleText()|.
299   const NGInlineItem* fast_min_content_item_ = nullptr;
300 
301   // The current base direction for the bidi algorithm.
302   // This is copied from NGInlineNode, then updated after each forced line break
303   // if 'unicode-bidi: plaintext'.
304   TextDirection base_direction_;
305 
306 #if DCHECK_IS_ON()
307   // These fields are to detect rewind-loop.
308   unsigned last_rewind_from_item_index_ = 0;
309   unsigned last_rewind_to_item_index_ = 0;
310 #endif
311 };
312 
313 }  // namespace blink
314 
315 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_INLINE_NG_LINE_BREAKER_H_
316