OLD | NEW |
1 // Copyright 2017 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "platform/fonts/shaping/ShapingLineBreaker.h" | 5 #include "platform/fonts/shaping/ShapingLineBreaker.h" |
6 | 6 |
7 #include "platform/fonts/Font.h" | 7 #include "platform/fonts/Font.h" |
8 #include "platform/fonts/shaping/HarfBuzzShaper.h" | 8 #include "platform/fonts/shaping/HarfBuzzShaper.h" |
9 #include "platform/fonts/shaping/ShapeResult.h" | 9 #include "platform/fonts/shaping/ShapeResult.h" |
10 #include "platform/fonts/shaping/ShapeResultInlineHeaders.h" | 10 #include "platform/fonts/shaping/ShapeResultInlineHeaders.h" |
11 #include "platform/text/TextBreakIterator.h" | 11 #include "platform/text/TextBreakIterator.h" |
12 | 12 |
13 namespace blink { | 13 namespace blink { |
14 | 14 |
15 ShapingLineBreaker::ShapingLineBreaker(const HarfBuzzShaper* shaper, | 15 ShapingLineBreaker::ShapingLineBreaker( |
16 const Font* font, | 16 const HarfBuzzShaper* shaper, |
17 const ShapeResult* result, | 17 const Font* font, |
18 const AtomicString locale, | 18 const ShapeResult* result, |
19 LineBreakType break_type) | 19 const LazyLineBreakIterator* break_iterator) |
20 : shaper_(shaper), | 20 : shaper_(shaper), |
21 font_(font), | 21 font_(font), |
22 result_(result), | 22 result_(result), |
23 locale_(locale), | 23 break_iterator_(break_iterator) { |
24 break_type_(break_type) { | |
25 text_ = String(shaper->GetText(), shaper->TextLength()); | 24 text_ = String(shaper->GetText(), shaper->TextLength()); |
26 } | 25 } |
27 | 26 |
28 namespace { | 27 namespace { |
29 | 28 |
30 unsigned PreviousSafeToBreakAfter(const UChar* text, | 29 unsigned PreviousSafeToBreakAfter(const UChar* text, |
31 unsigned start, | 30 unsigned start, |
32 unsigned offset) { | 31 unsigned offset) { |
33 // TODO(eae): This is quite incorrect. It should be changed to use the | 32 // TODO(eae): This is quite incorrect. It should be changed to use the |
34 // HarfBuzzHarfBuzz safe to break info when available. | 33 // HarfBuzzHarfBuzz safe to break info when available. |
35 for (; offset > start; offset--) { | 34 for (; offset > start; offset--) { |
36 if (text[offset - 1] == kSpaceCharacter) | 35 if (text[offset - 1] == kSpaceCharacter) |
37 break; | 36 break; |
38 } | 37 } |
39 return offset; | 38 return offset; |
40 } | 39 } |
41 | 40 |
42 unsigned NextSafeToBreakBefore(const UChar* text, | 41 unsigned NextSafeToBreakBefore(const UChar* text, |
43 unsigned end, | 42 unsigned end, |
44 unsigned offset) { | 43 unsigned offset) { |
45 // TODO(eae): This is quite incorrect. It should be changed to use the | 44 // TODO(eae): This is quite incorrect. It should be changed to use the |
46 // HarfBuzzHarfBuzz safe to break info when available. | 45 // HarfBuzzHarfBuzz safe to break info when available. |
47 for (; offset < end; offset++) { | 46 for (; offset < end; offset++) { |
48 if (text[offset] == kSpaceCharacter) | 47 if (text[offset] == kSpaceCharacter) |
49 break; | 48 break; |
50 } | 49 } |
51 return offset; | 50 return offset; |
52 } | 51 } |
53 | 52 |
| 53 // ShapingLineBreaker computes using visual positions. This function flips |
| 54 // logical advance to visual, or vice versa. |
| 55 LayoutUnit FlipRtl(LayoutUnit value, TextDirection direction) { |
| 56 return direction != TextDirection::kRtl ? value : -value; |
| 57 } |
| 58 |
| 59 // Snaps a visual position to the line start direction. |
| 60 LayoutUnit SnapStart(float value, TextDirection direction) { |
| 61 return direction != TextDirection::kRtl ? LayoutUnit::FromFloatFloor(value) |
| 62 : LayoutUnit::FromFloatCeil(value); |
| 63 } |
| 64 |
| 65 // Snaps a visual position to the line end direction. |
| 66 LayoutUnit SnapEnd(float value, TextDirection direction) { |
| 67 return direction != TextDirection::kRtl ? LayoutUnit::FromFloatCeil(value) |
| 68 : LayoutUnit::FromFloatFloor(value); |
| 69 } |
| 70 |
54 } // namespace | 71 } // namespace |
55 | 72 |
56 // Shapes a line of text by finding a valid and appropriate break opportunity | 73 // Shapes a line of text by finding a valid and appropriate break opportunity |
57 // based on the shaping results for the entire paragraph. Re-shapes the start | 74 // based on the shaping results for the entire paragraph. Re-shapes the start |
58 // and end of the line as needed. | 75 // and end of the line as needed. |
59 // | 76 // |
60 // Definitions: | 77 // Definitions: |
61 // Candidate break opportunity: Ideal point to break, disregarding line | 78 // Candidate break opportunity: Ideal point to break, disregarding line |
62 // breaking rules. May be in the middle of a word | 79 // breaking rules. May be in the middle of a word |
63 // or inside a ligature. | 80 // or inside a ligature. |
(...skipping 15 matching lines...) Expand all Loading... |
79 // The candidate (or ideal) break opportunity would be at an offset of 10 as | 96 // The candidate (or ideal) break opportunity would be at an offset of 10 as |
80 // the break would happen at exactly 100px in that case. | 97 // the break would happen at exactly 100px in that case. |
81 // The previous valid break opportunity though is at an offset of 5. | 98 // The previous valid break opportunity though is at an offset of 5. |
82 // If we further assume that the font kerns with space then even though it's a | 99 // If we further assume that the font kerns with space then even though it's a |
83 // valid break opportunity reshaping is required as the combined width of the | 100 // valid break opportunity reshaping is required as the combined width of the |
84 // two segments "Line " and "breaking" may be different from "Line breaking". | 101 // two segments "Line " and "breaking" may be different from "Line breaking". |
85 PassRefPtr<ShapeResult> ShapingLineBreaker::ShapeLine( | 102 PassRefPtr<ShapeResult> ShapingLineBreaker::ShapeLine( |
86 unsigned start, | 103 unsigned start, |
87 LayoutUnit available_space, | 104 LayoutUnit available_space, |
88 unsigned* break_offset) { | 105 unsigned* break_offset) { |
| 106 DCHECK_GE(available_space, LayoutUnit(0)); |
| 107 unsigned range_start = result_->StartIndexForResult(); |
| 108 unsigned range_end = result_->EndIndexForResult(); |
| 109 DCHECK_GE(start, range_start); |
| 110 DCHECK_LT(start, range_end); |
| 111 |
89 // The start position in the original shape results. | 112 // The start position in the original shape results. |
90 LayoutUnit start_position = result_->SnappedStartPositionForOffset(start); | 113 float start_position_float = result_->PositionForOffset(start - range_start); |
91 TextDirection direction = result_->Direction(); | 114 TextDirection direction = result_->Direction(); |
| 115 LayoutUnit start_position = SnapStart(start_position_float, direction); |
| 116 |
| 117 // Find a candidate break opportunity by identifying the last offset before |
| 118 // exceeding the available space and the determine the closest valid break |
| 119 // preceding the candidate. |
| 120 LayoutUnit end_position = SnapEnd(start_position_float, direction) + |
| 121 FlipRtl(available_space, direction); |
| 122 DCHECK_GE(FlipRtl(end_position - start_position, direction), LayoutUnit(0)); |
| 123 unsigned candidate_break = |
| 124 result_->OffsetForPosition(end_position, false) + range_start; |
| 125 // candidate_break should be >= start, but rounding errors can chime in when |
| 126 // comparing floats. See ShapeLineZeroAvailableWidth on Linux/Mac. |
| 127 candidate_break = std::max(candidate_break, start); |
| 128 unsigned break_opportunity = |
| 129 break_iterator_->PreviousBreakOpportunity(candidate_break, start); |
| 130 if (break_opportunity <= start) { |
| 131 break_opportunity = break_iterator_->NextBreakOpportunity( |
| 132 std::max(candidate_break, start + 1)); |
| 133 } |
| 134 DCHECK_GT(break_opportunity, start); |
92 | 135 |
93 // If the start offset is not at a safe-to-break boundary the content between | 136 // If the start offset is not at a safe-to-break boundary the content between |
94 // the start and the next safe-to-break boundary needs to be reshaped and the | 137 // the start and the next safe-to-break boundary needs to be reshaped and the |
95 // available space adjusted to take the reshaping into account. | 138 // available space adjusted to take the reshaping into account. |
96 RefPtr<ShapeResult> line_start_result; | 139 RefPtr<ShapeResult> line_start_result; |
97 unsigned first_safe = | 140 unsigned first_safe = |
98 NextSafeToBreakBefore(shaper_->GetText(), shaper_->TextLength(), start); | 141 NextSafeToBreakBefore(shaper_->GetText(), shaper_->TextLength(), start); |
99 if (first_safe != start) { | 142 DCHECK_GE(first_safe, start); |
| 143 // Reshape takes place only when first_safe is before the break opportunity. |
| 144 // Otherwise reshape will be part of line_end_result. |
| 145 if (first_safe != start && first_safe < break_opportunity) { |
100 LayoutUnit original_width = | 146 LayoutUnit original_width = |
101 result_->SnappedEndPositionForOffset(first_safe) - start_position; | 147 FlipRtl(SnapEnd(result_->PositionForOffset(first_safe - range_start), |
| 148 direction) - |
| 149 start_position, |
| 150 direction); |
102 line_start_result = shaper_->Shape(font_, direction, start, first_safe); | 151 line_start_result = shaper_->Shape(font_, direction, start, first_safe); |
103 available_space += line_start_result->SnappedWidth() - original_width; | 152 available_space += line_start_result->SnappedWidth() - original_width; |
104 } | 153 } |
105 | 154 |
106 // Find a candidate break opportunity by identifying the last offset before | |
107 // exceeding the available space and the determine the closest valid break | |
108 // preceding the candidate. | |
109 LazyLineBreakIterator break_iterator(text_, locale_, break_type_); | |
110 LayoutUnit end_position = start_position + available_space; | |
111 unsigned candidate_break = result_->OffsetForPosition(end_position, false); | |
112 unsigned break_opportunity = | |
113 break_iterator.PreviousBreakOpportunity(candidate_break, start); | |
114 if (break_opportunity <= start) { | |
115 break_opportunity = break_iterator.NextBreakOpportunity(candidate_break); | |
116 } | |
117 | |
118 RefPtr<ShapeResult> line_end_result; | 155 RefPtr<ShapeResult> line_end_result; |
119 unsigned last_safe = break_opportunity; | 156 unsigned last_safe = break_opportunity; |
120 while (break_opportunity > start) { | 157 while (break_opportunity > start) { |
121 // If the previous valid break opportunity is not at a safe-to-break | 158 // If the previous valid break opportunity is not at a safe-to-break |
122 // boundary reshape between the safe-to-break offset and the valid break | 159 // boundary reshape between the safe-to-break offset and the valid break |
123 // offset. If the resulting width exceeds the available space the | 160 // offset. If the resulting width exceeds the available space the |
124 // preceding boundary is tried until the available space is sufficient. | 161 // preceding boundary is tried until the available space is sufficient. |
125 unsigned previous_safe = std::max( | 162 unsigned previous_safe = std::max( |
126 PreviousSafeToBreakAfter(shaper_->GetText(), start, break_opportunity), | 163 PreviousSafeToBreakAfter(shaper_->GetText(), start, break_opportunity), |
127 start); | 164 start); |
| 165 DCHECK_LE(previous_safe, break_opportunity); |
128 if (previous_safe != break_opportunity) { | 166 if (previous_safe != break_opportunity) { |
129 LayoutUnit safe_position = | 167 LayoutUnit safe_position = SnapStart( |
130 result_->SnappedStartPositionForOffset(previous_safe); | 168 result_->PositionForOffset(previous_safe - range_start), direction); |
131 while (break_opportunity > previous_safe && previous_safe > start) { | 169 while (break_opportunity > previous_safe && previous_safe >= start) { |
132 line_end_result = | 170 line_end_result = |
133 shaper_->Shape(font_, direction, previous_safe, break_opportunity); | 171 shaper_->Shape(font_, direction, previous_safe, |
134 if (safe_position + line_end_result->SnappedWidth() <= end_position) | 172 std::min(break_opportunity, range_end)); |
| 173 if (line_end_result->SnappedWidth() <= |
| 174 FlipRtl(end_position - safe_position, direction)) |
135 break; | 175 break; |
| 176 // Doesn't fit after the reshape. Try previous break opportunity, or |
| 177 // overflow if there were none. |
| 178 unsigned previous_break_opportunity = |
| 179 break_iterator_->PreviousBreakOpportunity(break_opportunity - 1, |
| 180 start); |
| 181 if (previous_break_opportunity <= start) |
| 182 break; |
| 183 break_opportunity = previous_break_opportunity; |
136 line_end_result = nullptr; | 184 line_end_result = nullptr; |
137 break_opportunity = break_iterator.PreviousBreakOpportunity( | |
138 break_opportunity - 1, start); | |
139 } | 185 } |
140 } | 186 } |
141 | 187 |
142 if (break_opportunity > start) { | 188 if (break_opportunity > start) { |
143 last_safe = previous_safe; | 189 last_safe = previous_safe; |
144 break; | 190 break; |
145 } | 191 } |
146 | 192 |
147 // No suitable break opportunity, not exceeding the available space, | 193 // No suitable break opportunity, not exceeding the available space, |
148 // found. Choose the next valid one even though it will overflow. | 194 // found. Choose the next valid one even though it will overflow. |
149 break_opportunity = break_iterator.NextBreakOpportunity(candidate_break); | 195 break_opportunity = break_iterator_->NextBreakOpportunity(candidate_break); |
150 } | 196 } |
151 | 197 |
152 // Create shape results for the line by copying from the re-shaped result (if | 198 // Create shape results for the line by copying from the re-shaped result (if |
153 // reshaping was needed) and the original shape results. | 199 // reshaping was needed) and the original shape results. |
154 RefPtr<ShapeResult> line_result = ShapeResult::Create(font_, 0, direction); | 200 RefPtr<ShapeResult> line_result = ShapeResult::Create(font_, 0, direction); |
155 unsigned max_length = std::numeric_limits<unsigned>::max(); | 201 unsigned max_length = std::numeric_limits<unsigned>::max(); |
156 if (line_start_result) | 202 if (line_start_result) |
157 line_start_result->CopyRange(0, max_length, line_result.Get()); | 203 line_start_result->CopyRange(0, max_length, line_result.Get()); |
158 if (last_safe > first_safe) | 204 if (last_safe > first_safe) |
159 result_->CopyRange(first_safe, last_safe, line_result.Get()); | 205 result_->CopyRange(first_safe, last_safe, line_result.Get()); |
160 if (line_end_result) | 206 if (line_end_result) |
161 line_end_result->CopyRange(last_safe, max_length, line_result.Get()); | 207 line_end_result->CopyRange(last_safe, max_length, line_result.Get()); |
162 | 208 |
| 209 DCHECK_GT(break_opportunity, start); |
| 210 DCHECK_EQ(std::min(break_opportunity, range_end) - start, |
| 211 line_result->NumCharacters()); |
| 212 |
163 *break_offset = break_opportunity; | 213 *break_offset = break_opportunity; |
164 return line_result.Release(); | 214 return line_result.Release(); |
165 } | 215 } |
166 | 216 |
167 } // namespace blink | 217 } // namespace blink |
OLD | NEW |