OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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 #include "ios/chrome/browser/ui/util/manual_text_framer.h" |
| 6 |
| 7 #include "base/mac/foundation_util.h" |
| 8 #import "base/mac/scoped_nsobject.h" |
| 9 #include "base/time/time.h" |
| 10 #import "ios/chrome/browser/ui/util/core_text_util.h" |
| 11 #import "ios/chrome/browser/ui/util/text_frame.h" |
| 12 #import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoF
ontLoader.h" |
| 13 #include "testing/gtest/include/gtest/gtest.h" |
| 14 #include "testing/gtest_mac.h" |
| 15 #include "testing/platform_test.h" |
| 16 #include "url/gurl.h" |
| 17 |
| 18 namespace { |
| 19 // Copy of ManualTextFramer's alignment function. |
| 20 enum class AlignmentFunction : short { CEIL = 0, FLOOR }; |
| 21 CGFloat AlignValueToPixel(CGFloat value, AlignmentFunction function) { |
| 22 static CGFloat scale = [[UIScreen mainScreen] scale]; |
| 23 return function == AlignmentFunction::CEIL ? ceil(value * scale) / scale |
| 24 : floor(value * scale) / scale; |
| 25 } |
| 26 |
| 27 class ManualTextFramerTest : public PlatformTest { |
| 28 protected: |
| 29 void SetUp() override { |
| 30 attributes_.reset([[NSMutableDictionary alloc] init]); |
| 31 string_.reset([[NSMutableAttributedString alloc] init]); |
| 32 } |
| 33 |
| 34 NSString* text() { return [string_ string]; } |
| 35 NSRange text_range() { return NSMakeRange(0, [string_ length]); } |
| 36 id<TextFrame> text_frame() { |
| 37 return static_cast<id<TextFrame>>(text_frame_.get()); |
| 38 } |
| 39 |
| 40 void SetText(NSString* text) { |
| 41 DCHECK(text.length); |
| 42 [[string_ mutableString] setString:text]; |
| 43 } |
| 44 |
| 45 void FrameTextInBounds(CGRect bounds) { |
| 46 base::scoped_nsobject<ManualTextFramer> manual_framer( |
| 47 [[ManualTextFramer alloc] initWithString:string_ inBounds:bounds]); |
| 48 [manual_framer frameText]; |
| 49 id frame = [manual_framer textFrame]; |
| 50 text_frame_.reset([frame retain]); |
| 51 } |
| 52 |
| 53 UIFont* RobotoFontWithSize(CGFloat size) { |
| 54 return [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:size]; |
| 55 } |
| 56 |
| 57 NSParagraphStyle* CreateParagraphStyle(CGFloat line_height, |
| 58 NSTextAlignment alignment, |
| 59 NSLineBreakMode line_break_mode) { |
| 60 NSMutableParagraphStyle* style = |
| 61 [[[NSMutableParagraphStyle alloc] init] autorelease]; |
| 62 style.alignment = alignment; |
| 63 style.lineBreakMode = line_break_mode; |
| 64 style.minimumLineHeight = line_height; |
| 65 style.maximumLineHeight = line_height; |
| 66 return style; |
| 67 } |
| 68 |
| 69 NSMutableDictionary* attributes() { return attributes_; } |
| 70 |
| 71 void ApplyAttributesForRange(NSRange range) { |
| 72 [string_ setAttributes:attributes_ range:range]; |
| 73 } |
| 74 |
| 75 void CheckForLineCountAndFramedRange(NSUInteger line_count, |
| 76 NSRange framed_range) { |
| 77 EXPECT_EQ(line_count, text_frame().lines.count); |
| 78 EXPECT_EQ(framed_range.location, text_frame().framedRange.location); |
| 79 EXPECT_EQ(framed_range.length, text_frame().framedRange.length); |
| 80 } |
| 81 |
| 82 base::scoped_nsobject<NSMutableDictionary> attributes_; |
| 83 base::scoped_nsobject<NSMutableAttributedString> string_; |
| 84 base::scoped_nsprotocol<id<TextFrame>> text_frame_; |
| 85 }; |
| 86 |
| 87 // Tests that newline characters cause an attributed string to be laid out on |
| 88 // multiple lines. |
| 89 TEST_F(ManualTextFramerTest, NewlineTest) { |
| 90 SetText(@"line one\nline two\nline three"); |
| 91 attributes()[NSFontAttributeName] = RobotoFontWithSize(14.0); |
| 92 attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
| 93 20.0, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
| 94 ApplyAttributesForRange(text_range()); |
| 95 CGRect bounds = CGRectMake(0, 0, 500, 500); |
| 96 FrameTextInBounds(bounds); |
| 97 CheckForLineCountAndFramedRange(3, text_range()); |
| 98 } |
| 99 |
| 100 // Tests that strings with no spaces fail correctly. |
| 101 TEST_F(ManualTextFramerTest, NoSpacesText) { |
| 102 // "St. Mary's church in the hollow of the white hazel near the the rapid |
| 103 // whirlpool of Llantysilio of the red cave." |
| 104 SetText( |
| 105 @"Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch"); |
| 106 attributes()[NSFontAttributeName] = RobotoFontWithSize(16.0); |
| 107 attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
| 108 20.0, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
| 109 ApplyAttributesForRange(text_range()); |
| 110 CGRect bounds = CGRectMake(0, 0, 200, 60); |
| 111 FrameTextInBounds(bounds); |
| 112 CheckForLineCountAndFramedRange(0, NSMakeRange(0, 0)); |
| 113 } |
| 114 |
| 115 // Tests that multiple newlines are accounted for. Only the first three |
| 116 // newlines should be added to |lines_|. |
| 117 TEST_F(ManualTextFramerTest, MultipleNewlineTest) { |
| 118 SetText(@"\n\n\ntext"); |
| 119 attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
| 120 20.0, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
| 121 ApplyAttributesForRange(text_range()); |
| 122 CGRect bounds = CGRectMake(0, 0, 500, 60); |
| 123 FrameTextInBounds(bounds); |
| 124 CheckForLineCountAndFramedRange(3, NSMakeRange(0, 3)); |
| 125 } |
| 126 |
| 127 // Tests that the framed range for text that will be rendered with ligatures is |
| 128 // corresponds with the actual range of the text. |
| 129 TEST_F(ManualTextFramerTest, LigatureTest) { |
| 130 SetText(@"fffi"); |
| 131 attributes()[NSFontAttributeName] = RobotoFontWithSize(14.0); |
| 132 attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
| 133 20.0, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
| 134 ApplyAttributesForRange(text_range()); |
| 135 CGRect bounds = CGRectMake(0, 0, 500, 20); |
| 136 FrameTextInBounds(bounds); |
| 137 CheckForLineCountAndFramedRange(1, text_range()); |
| 138 } |
| 139 |
| 140 // Tests that ManualTextFramer correctly frames Å |
| 141 TEST_F(ManualTextFramerTest, DiacriticTest) { |
| 142 SetText(@"A\u030A"); |
| 143 attributes()[NSFontAttributeName] = RobotoFontWithSize(14.0); |
| 144 attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
| 145 20.0, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
| 146 ApplyAttributesForRange(text_range()); |
| 147 CGRect bounds = CGRectMake(0, 0, 500, 20); |
| 148 FrameTextInBounds(bounds); |
| 149 CheckForLineCountAndFramedRange(1, text_range()); |
| 150 } |
| 151 |
| 152 // String text, attributes, and bounds are chosen to match the "Terms of |
| 153 // Service" text in WelcomeToChromeView, as the text is not properly framed by |
| 154 // CTFrameSetter. http://crbug.com/537212 |
| 155 TEST_F(ManualTextFramerTest, TOSTextTest) { |
| 156 CGRect bounds = CGRectMake(0, 0, 300.0, 40.0); |
| 157 NSString* const kTOSLinkText = @"Terms of Service"; |
| 158 NSString* const kTOSText = |
| 159 @"By using this application, you agree to Chrome’s Terms of Service."; |
| 160 SetText(kTOSText); |
| 161 attributes()[NSFontAttributeName] = RobotoFontWithSize(14.0); |
| 162 attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
| 163 20.0, NSTextAlignmentCenter, NSLineBreakByTruncatingTail); |
| 164 attributes()[NSForegroundColorAttributeName] = [UIColor blackColor]; |
| 165 ApplyAttributesForRange(text_range()); |
| 166 attributes()[NSForegroundColorAttributeName] = [UIColor blueColor]; |
| 167 ApplyAttributesForRange([kTOSText rangeOfString:kTOSLinkText]); |
| 168 FrameTextInBounds(bounds); |
| 169 CheckForLineCountAndFramedRange(2, text_range()); |
| 170 } |
| 171 |
| 172 // Tests that the origin of a left-aligned single line is correct. |
| 173 TEST_F(ManualTextFramerTest, SimpleOriginTest) { |
| 174 SetText(@"test"); |
| 175 UIFont* font = RobotoFontWithSize(14.0); |
| 176 attributes()[NSFontAttributeName] = font; |
| 177 CGFloat line_height = 20.0; |
| 178 attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
| 179 line_height, NSTextAlignmentLeft, NSLineBreakByWordWrapping); |
| 180 ApplyAttributesForRange(text_range()); |
| 181 CGRect bounds = CGRectMake(0, 0, 500, 21); |
| 182 FrameTextInBounds(bounds); |
| 183 CheckForLineCountAndFramedRange(1, text_range()); |
| 184 FramedLine* line = [text_frame().lines firstObject]; |
| 185 EXPECT_EQ(0, line.origin.x); |
| 186 EXPECT_EQ( |
| 187 AlignValueToPixel(CGRectGetHeight(bounds) - line_height - font.descender, |
| 188 AlignmentFunction::FLOOR), |
| 189 line.origin.y); |
| 190 } |
| 191 |
| 192 // Tests that lines that are laid out in RTL are right aligned. |
| 193 TEST_F(ManualTextFramerTest, OriginRTLTest) { |
| 194 SetText(@"\u0641\u064e\u0628\u064e\u0642\u064e\u064a\u0652\u062a\u064f\u0020" |
| 195 @"\u0645\u064f\u062a\u064e\u0627\u0628\u0650\u0639\u064e\u0627\u064b" |
| 196 @"\u0020\u0028\u0634\u064f\u063a\u0652\u0644\u0650\u064a\u0029\u0020" |
| 197 @"\u0644\u064e\u0639\u064e\u0644\u064e\u0643\u0650\u0020\u062a\u064e" |
| 198 @"\u062a\u064e\u0639\u064e\u0644\u0651\u064e\u0645\u064e\u0020\u0627" |
| 199 @"\u0644\u062d\u0650\u0631\u0652\u0635\u064e\u0020\u0639\u064e\u0644" |
| 200 @"\u064e\u0649\u0020\u0627\u0644\u0648\u064e\u0642\u0652\u062a\u0650" |
| 201 @"\u0020\u002e\u0020\u0641\u064e\u0627\u0644\u062d\u064e\u064a\u064e" |
| 202 @"\u0627\u0629\u064f"); |
| 203 attributes()[NSFontAttributeName] = RobotoFontWithSize(14.0); |
| 204 attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
| 205 20.0, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
| 206 ApplyAttributesForRange(text_range()); |
| 207 CGRect bounds = CGRectMake(0, 0, 100.0, 60.0); |
| 208 FrameTextInBounds(bounds); |
| 209 CheckForLineCountAndFramedRange(3, text_range()); |
| 210 for (FramedLine* line in text_frame().lines) { |
| 211 CGFloat line_width = |
| 212 AlignValueToPixel(core_text_util::GetTrimmedLineWidth(line.line), |
| 213 AlignmentFunction::CEIL); |
| 214 EXPECT_EQ(CGRectGetMaxX(bounds), line.origin.x + line_width); |
| 215 } |
| 216 } |
| 217 |
| 218 TEST_F(ManualTextFramerTest, CJKLineBreakTest) { |
| 219 // Example from our strings. Framer will put “触摸搜索” on one line, and then |
| 220 // fail to frame the second. |
| 221 // clang-format off |
| 222 SetText(@"“触摸搜索”会将所选字词和当前页面(作为上下文)一起发送给 Google 搜索。" |
| 223 @"您可以在设置中停用此功能。"); |
| 224 // clang-format on |
| 225 attributes()[NSFontAttributeName] = RobotoFontWithSize(16.0); |
| 226 attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
| 227 16 * 1.15, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
| 228 ApplyAttributesForRange(text_range()); |
| 229 CGRect bounds = CGRectMake(0, 0, 300.0, 65.0); |
| 230 FrameTextInBounds(bounds); |
| 231 CheckForLineCountAndFramedRange(3, NSMakeRange(0, 53)); |
| 232 |
| 233 // Example without any space-ish characters: |
| 234 // clang-format off |
| 235 SetText(@"会将所选字词和当前页面(作为上下文)一起发送给Google搜索。" |
| 236 @"您可以在设置中停用此功能。"); |
| 237 // clang-format on |
| 238 attributes()[NSFontAttributeName] = RobotoFontWithSize(16.0); |
| 239 attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
| 240 16 * 1.15, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
| 241 ApplyAttributesForRange(text_range()); |
| 242 FrameTextInBounds(bounds); |
| 243 CheckForLineCountAndFramedRange(2, NSMakeRange(0, 45)); |
| 244 } |
| 245 |
| 246 // Tests that paragraphs with NSTextAlignmentCenter are actually centered. |
| 247 TEST_F(ManualTextFramerTest, CenterAlignedTest) { |
| 248 SetText(@"xxxx\nyyy\nwww"); |
| 249 attributes()[NSFontAttributeName] = RobotoFontWithSize(14.0); |
| 250 attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
| 251 20.0, NSTextAlignmentCenter, NSLineBreakByWordWrapping); |
| 252 ApplyAttributesForRange(text_range()); |
| 253 CGRect bounds = CGRectMake(0, 0, 200.0, 60.0); |
| 254 FrameTextInBounds(bounds); |
| 255 CheckForLineCountAndFramedRange(3, text_range()); |
| 256 for (FramedLine* line in text_frame().lines) { |
| 257 CGFloat line_width = |
| 258 AlignValueToPixel(core_text_util::GetTrimmedLineWidth(line.line), |
| 259 AlignmentFunction::CEIL); |
| 260 EXPECT_EQ(AlignValueToPixel(CGRectGetMidX(bounds) - 0.5 * line_width, |
| 261 AlignmentFunction::FLOOR), |
| 262 line.origin.x); |
| 263 } |
| 264 } |
| 265 |
| 266 // Tests that words with a large line height will not be framed if they don't |
| 267 // fit in the bounding height. |
| 268 TEST_F(ManualTextFramerTest, LargeLineHeightTest) { |
| 269 SetText(@"the last word is very LARGE"); |
| 270 attributes()[NSFontAttributeName] = RobotoFontWithSize(14.0); |
| 271 attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
| 272 20.0, NSTextAlignmentCenter, NSLineBreakByWordWrapping); |
| 273 ApplyAttributesForRange(text_range()); |
| 274 attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
| 275 500.0, NSTextAlignmentCenter, NSLineBreakByWordWrapping); |
| 276 ApplyAttributesForRange(NSMakeRange(22, 5)); // "LARGE" |
| 277 CGRect bounds = CGRectMake(0, 0, 500, 20.0); |
| 278 FrameTextInBounds(bounds); |
| 279 CheckForLineCountAndFramedRange(1, NSMakeRange(0, 22)); |
| 280 } |
| 281 |
| 282 // Tests a preexisting error condition in which a BiDi string containing Arabic |
| 283 // is correctly laid out (crbug.com/584549). |
| 284 TEST_F(ManualTextFramerTest, RTLTest) { |
| 285 SetText(@"\u0642\u062F\u0020\u064A\u0633\u062A\u062E\u062F\u0645\u0020\u0047" |
| 286 "\u006F\u006F\u0067\u006C\u0065\u0020\u0043\u0068\u0072\u006F\u006D" |
| 287 "\u0065\u0020\u062E\u062F\u0645\u0627\u062A\u0020\u0627\u0644\u0648" |
| 288 "\u064A\u0628\u0020\u0644\u062A\u062D\u0633\u064A\u0646\u0020\u062A" |
| 289 "\u062C\u0631\u0628\u0629\u0020\u0627\u0644\u062A\u0635\u0641\u062D" |
| 290 "\u002E\u0020\u0648\u064A\u0645\u0643\u0646\u0643\u0020\u0628\u0634" |
| 291 "\u0643\u0644\u0020\u0627\u062E\u062A\u064A\u0627\u0631\u064A\u0020" |
| 292 "\u062A\u0639\u0637\u064A\u0644\u0020\u0647\u0630\u0647\u0020\u0627" |
| 293 "\u0644\u062E\u062F\u0645\u0627\u062A\u002E"); |
| 294 attributes()[NSFontAttributeName] = [UIFont systemFontOfSize:20.0]; |
| 295 ApplyAttributesForRange(text_range()); |
| 296 CGRect bounds = CGRectMake(0, 0, 500, 100); |
| 297 FrameTextInBounds(bounds); |
| 298 CheckForLineCountAndFramedRange(2, text_range()); |
| 299 } |
| 300 |
| 301 } // namespace |
OLD | NEW |