Chromium Code Reviews| 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 LayoutUnit FlipRtl(LayoutUnit value, TextDirection direction) { | |
| 54 return direction == TextDirection::kRtl ? -value : value; | |
| 55 } | |
| 56 | |
| 54 } // namespace | 57 } // namespace |
| 55 | 58 |
| 56 // Shapes a line of text by finding a valid and appropriate break opportunity | 59 // 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 | 60 // based on the shaping results for the entire paragraph. Re-shapes the start |
| 58 // and end of the line as needed. | 61 // and end of the line as needed. |
| 59 // | 62 // |
| 60 // Definitions: | 63 // Definitions: |
| 61 // Candidate break opportunity: Ideal point to break, disregarding line | 64 // Candidate break opportunity: Ideal point to break, disregarding line |
| 62 // breaking rules. May be in the middle of a word | 65 // breaking rules. May be in the middle of a word |
| 63 // or inside a ligature. | 66 // 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 | 82 // 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. | 83 // the break would happen at exactly 100px in that case. |
| 81 // The previous valid break opportunity though is at an offset of 5. | 84 // 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 | 85 // 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 | 86 // valid break opportunity reshaping is required as the combined width of the |
| 84 // two segments "Line " and "breaking" may be different from "Line breaking". | 87 // two segments "Line " and "breaking" may be different from "Line breaking". |
| 85 PassRefPtr<ShapeResult> ShapingLineBreaker::ShapeLine( | 88 PassRefPtr<ShapeResult> ShapingLineBreaker::ShapeLine( |
| 86 unsigned start, | 89 unsigned start, |
| 87 LayoutUnit available_space, | 90 LayoutUnit available_space, |
| 88 unsigned* break_offset) { | 91 unsigned* break_offset) { |
| 92 unsigned range_start = result_->CharacterStartIndex(); | |
| 93 unsigned range_end = range_start + result_->NumCharacters(); | |
| 94 DCHECK_GE(start, range_start); | |
| 95 DCHECK_LT(start, range_end); | |
| 96 | |
| 89 // The start position in the original shape results. | 97 // The start position in the original shape results. |
| 90 LayoutUnit start_position = result_->SnappedStartPositionForOffset(start); | 98 float start_position_float = result_->PositionForOffset(start - range_start); |
| 99 LayoutUnit start_position = LayoutUnit::FromFloatFloor(start_position_float); | |
| 91 TextDirection direction = result_->Direction(); | 100 TextDirection direction = result_->Direction(); |
| 92 | 101 |
| 102 // Find a candidate break opportunity by identifying the last offset before | |
| 103 // exceeding the available space and the determine the closest valid break | |
| 104 // preceding the candidate. | |
| 105 LayoutUnit end_position = LayoutUnit::FromFloatCeil(start_position_float) + | |
| 106 FlipRtl(available_space, direction); | |
| 107 unsigned candidate_break = | |
| 108 result_->OffsetForPosition(end_position, | |
| 109 ShapeResult::kOutsideAsStartOrEnd) + | |
| 110 range_start; | |
| 111 DCHECK_GE(candidate_break, start); | |
| 112 unsigned break_opportunity = | |
| 113 break_iterator_->PreviousBreakOpportunity(candidate_break, start); | |
| 114 if (break_opportunity <= start) { | |
| 115 break_opportunity = break_iterator_->NextBreakOpportunity( | |
| 116 std::max(candidate_break, start + 1)); | |
| 117 } | |
| 118 DCHECK_GT(break_opportunity, start); | |
| 119 | |
| 93 // If the start offset is not at a safe-to-break boundary the content between | 120 // 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 | 121 // 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. | 122 // available space adjusted to take the reshaping into account. |
| 96 RefPtr<ShapeResult> line_start_result; | 123 RefPtr<ShapeResult> line_start_result; |
| 97 unsigned first_safe = | 124 unsigned first_safe = |
| 98 NextSafeToBreakBefore(shaper_->GetText(), shaper_->TextLength(), start); | 125 NextSafeToBreakBefore(shaper_->GetText(), shaper_->TextLength(), start); |
| 99 if (first_safe != start) { | 126 DCHECK_GE(first_safe, start); |
| 127 // Reshape takes place only when first_safe is before the break opportunity. | |
|
eae
2017/05/12 18:45:49
I considered this case and wanted to fix it but fa
kojii
2017/05/12 19:08:13
Yes, the Han character in your ShapeLineArabicThai
| |
| 128 // Otherwise reshape will be part of line_end_result. | |
| 129 if (first_safe != start && first_safe < break_opportunity) { | |
| 100 LayoutUnit original_width = | 130 LayoutUnit original_width = |
| 101 result_->SnappedEndPositionForOffset(first_safe) - start_position; | 131 FlipRtl(result_->SnappedEndPositionForOffset(first_safe - range_start) - |
| 132 start_position, | |
| 133 direction); | |
| 102 line_start_result = shaper_->Shape(font_, direction, start, first_safe); | 134 line_start_result = shaper_->Shape(font_, direction, start, first_safe); |
| 103 available_space += line_start_result->SnappedWidth() - original_width; | 135 available_space += line_start_result->SnappedWidth() - original_width; |
| 104 } | 136 } |
| 105 | 137 |
| 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; | 138 RefPtr<ShapeResult> line_end_result; |
| 119 unsigned last_safe = break_opportunity; | 139 unsigned last_safe = break_opportunity; |
| 120 while (break_opportunity > start) { | 140 while (break_opportunity > start) { |
| 121 // If the previous valid break opportunity is not at a safe-to-break | 141 // 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 | 142 // boundary reshape between the safe-to-break offset and the valid break |
| 123 // offset. If the resulting width exceeds the available space the | 143 // offset. If the resulting width exceeds the available space the |
| 124 // preceding boundary is tried until the available space is sufficient. | 144 // preceding boundary is tried until the available space is sufficient. |
| 125 unsigned previous_safe = std::max( | 145 unsigned previous_safe = std::max( |
| 126 PreviousSafeToBreakAfter(shaper_->GetText(), start, break_opportunity), | 146 PreviousSafeToBreakAfter(shaper_->GetText(), start, break_opportunity), |
| 127 start); | 147 start); |
| 148 DCHECK_LE(previous_safe, break_opportunity); | |
| 128 if (previous_safe != break_opportunity) { | 149 if (previous_safe != break_opportunity) { |
| 129 LayoutUnit safe_position = | 150 LayoutUnit safe_position = |
| 130 result_->SnappedStartPositionForOffset(previous_safe); | 151 result_->SnappedStartPositionForOffset(previous_safe - range_start); |
| 131 while (break_opportunity > previous_safe && previous_safe > start) { | 152 while (break_opportunity > previous_safe && previous_safe >= start) { |
| 132 line_end_result = | 153 line_end_result = |
| 133 shaper_->Shape(font_, direction, previous_safe, break_opportunity); | 154 shaper_->Shape(font_, direction, previous_safe, |
| 134 if (safe_position + line_end_result->SnappedWidth() <= end_position) | 155 std::min(break_opportunity, range_end)); |
| 156 if (line_end_result->SnappedWidth() <= | |
| 157 FlipRtl(end_position - safe_position, direction)) | |
| 135 break; | 158 break; |
| 159 // Doesn't fit after the reshape. Try previous break opportunity, or | |
| 160 // overflow if there were none. | |
| 161 unsigned previous_break_opportunity = | |
| 162 break_iterator_->PreviousBreakOpportunity(break_opportunity - 1, | |
| 163 start); | |
| 164 if (previous_break_opportunity <= start) | |
| 165 break; | |
| 166 break_opportunity = previous_break_opportunity; | |
| 136 line_end_result = nullptr; | 167 line_end_result = nullptr; |
| 137 break_opportunity = break_iterator.PreviousBreakOpportunity( | |
| 138 break_opportunity - 1, start); | |
| 139 } | 168 } |
| 140 } | 169 } |
| 141 | 170 |
| 142 if (break_opportunity > start) { | 171 if (break_opportunity > start) { |
| 143 last_safe = previous_safe; | 172 last_safe = previous_safe; |
| 144 break; | 173 break; |
| 145 } | 174 } |
| 146 | 175 |
| 147 // No suitable break opportunity, not exceeding the available space, | 176 // No suitable break opportunity, not exceeding the available space, |
| 148 // found. Choose the next valid one even though it will overflow. | 177 // found. Choose the next valid one even though it will overflow. |
| 149 break_opportunity = break_iterator.NextBreakOpportunity(candidate_break); | 178 break_opportunity = break_iterator_->NextBreakOpportunity(candidate_break); |
| 150 } | 179 } |
| 151 | 180 |
| 152 // Create shape results for the line by copying from the re-shaped result (if | 181 // Create shape results for the line by copying from the re-shaped result (if |
| 153 // reshaping was needed) and the original shape results. | 182 // reshaping was needed) and the original shape results. |
| 154 RefPtr<ShapeResult> line_result = ShapeResult::Create(font_, 0, direction); | 183 RefPtr<ShapeResult> line_result = ShapeResult::Create(font_, 0, direction); |
| 155 unsigned max_length = std::numeric_limits<unsigned>::max(); | 184 unsigned max_length = std::numeric_limits<unsigned>::max(); |
| 156 if (line_start_result) | 185 if (line_start_result) |
| 157 line_start_result->CopyRange(0, max_length, line_result.Get()); | 186 line_start_result->CopyRange(0, max_length, line_result.Get()); |
| 158 if (last_safe > first_safe) | 187 if (last_safe > first_safe) |
| 159 result_->CopyRange(first_safe, last_safe, line_result.Get()); | 188 result_->CopyRange(first_safe, last_safe, line_result.Get()); |
| 160 if (line_end_result) | 189 if (line_end_result) |
| 161 line_end_result->CopyRange(last_safe, max_length, line_result.Get()); | 190 line_end_result->CopyRange(last_safe, max_length, line_result.Get()); |
| 162 | 191 |
| 192 DCHECK_GT(break_opportunity, start); | |
| 193 DCHECK_EQ(std::min(break_opportunity, range_end) - start, | |
| 194 line_result->NumCharacters()); | |
| 195 | |
| 163 *break_offset = break_opportunity; | 196 *break_offset = break_opportunity; |
| 164 return line_result.Release(); | 197 return line_result.Release(); |
| 165 } | 198 } |
| 166 | 199 |
| 167 } // namespace blink | 200 } // namespace blink |
| OLD | NEW |