Index: ios/chrome/browser/ui/util/manual_text_framer_unittest.mm |
diff --git a/ios/chrome/browser/ui/util/manual_text_framer_unittest.mm b/ios/chrome/browser/ui/util/manual_text_framer_unittest.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..532b351d670b7c2d766e5d604873b3b2df055a5d |
--- /dev/null |
+++ b/ios/chrome/browser/ui/util/manual_text_framer_unittest.mm |
@@ -0,0 +1,301 @@ |
+// Copyright 2015 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. |
+ |
+#include "ios/chrome/browser/ui/util/manual_text_framer.h" |
+ |
+#include "base/mac/foundation_util.h" |
+#import "base/mac/scoped_nsobject.h" |
+#include "base/time/time.h" |
+#import "ios/chrome/browser/ui/util/core_text_util.h" |
+#import "ios/chrome/browser/ui/util/text_frame.h" |
+#import "ios/third_party/material_roboto_font_loader_ios/src/src/MaterialRobotoFontLoader.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "testing/gtest_mac.h" |
+#include "testing/platform_test.h" |
+#include "url/gurl.h" |
+ |
+namespace { |
+// Copy of ManualTextFramer's alignment function. |
+enum class AlignmentFunction : short { CEIL = 0, FLOOR }; |
+CGFloat AlignValueToPixel(CGFloat value, AlignmentFunction function) { |
+ static CGFloat scale = [[UIScreen mainScreen] scale]; |
+ return function == AlignmentFunction::CEIL ? ceil(value * scale) / scale |
+ : floor(value * scale) / scale; |
+} |
+ |
+class ManualTextFramerTest : public PlatformTest { |
+ protected: |
+ void SetUp() override { |
+ attributes_.reset([[NSMutableDictionary alloc] init]); |
+ string_.reset([[NSMutableAttributedString alloc] init]); |
+ } |
+ |
+ NSString* text() { return [string_ string]; } |
+ NSRange text_range() { return NSMakeRange(0, [string_ length]); } |
+ id<TextFrame> text_frame() { |
+ return static_cast<id<TextFrame>>(text_frame_.get()); |
+ } |
+ |
+ void SetText(NSString* text) { |
+ DCHECK(text.length); |
+ [[string_ mutableString] setString:text]; |
+ } |
+ |
+ void FrameTextInBounds(CGRect bounds) { |
+ base::scoped_nsobject<ManualTextFramer> manual_framer( |
+ [[ManualTextFramer alloc] initWithString:string_ inBounds:bounds]); |
+ [manual_framer frameText]; |
+ id frame = [manual_framer textFrame]; |
+ text_frame_.reset([frame retain]); |
+ } |
+ |
+ UIFont* RobotoFontWithSize(CGFloat size) { |
+ return [[MDFRobotoFontLoader sharedInstance] regularFontOfSize:size]; |
+ } |
+ |
+ NSParagraphStyle* CreateParagraphStyle(CGFloat line_height, |
+ NSTextAlignment alignment, |
+ NSLineBreakMode line_break_mode) { |
+ NSMutableParagraphStyle* style = |
+ [[[NSMutableParagraphStyle alloc] init] autorelease]; |
+ style.alignment = alignment; |
+ style.lineBreakMode = line_break_mode; |
+ style.minimumLineHeight = line_height; |
+ style.maximumLineHeight = line_height; |
+ return style; |
+ } |
+ |
+ NSMutableDictionary* attributes() { return attributes_; } |
+ |
+ void ApplyAttributesForRange(NSRange range) { |
+ [string_ setAttributes:attributes_ range:range]; |
+ } |
+ |
+ void CheckForLineCountAndFramedRange(NSUInteger line_count, |
+ NSRange framed_range) { |
+ EXPECT_EQ(line_count, text_frame().lines.count); |
+ EXPECT_EQ(framed_range.location, text_frame().framedRange.location); |
+ EXPECT_EQ(framed_range.length, text_frame().framedRange.length); |
+ } |
+ |
+ base::scoped_nsobject<NSMutableDictionary> attributes_; |
+ base::scoped_nsobject<NSMutableAttributedString> string_; |
+ base::scoped_nsprotocol<id<TextFrame>> text_frame_; |
+}; |
+ |
+// Tests that newline characters cause an attributed string to be laid out on |
+// multiple lines. |
+TEST_F(ManualTextFramerTest, NewlineTest) { |
+ SetText(@"line one\nline two\nline three"); |
+ attributes()[NSFontAttributeName] = RobotoFontWithSize(14.0); |
+ attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
+ 20.0, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
+ ApplyAttributesForRange(text_range()); |
+ CGRect bounds = CGRectMake(0, 0, 500, 500); |
+ FrameTextInBounds(bounds); |
+ CheckForLineCountAndFramedRange(3, text_range()); |
+} |
+ |
+// Tests that strings with no spaces fail correctly. |
+TEST_F(ManualTextFramerTest, NoSpacesText) { |
+ // "St. Mary's church in the hollow of the white hazel near the the rapid |
+ // whirlpool of Llantysilio of the red cave." |
+ SetText( |
+ @"Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch"); |
+ attributes()[NSFontAttributeName] = RobotoFontWithSize(16.0); |
+ attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
+ 20.0, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
+ ApplyAttributesForRange(text_range()); |
+ CGRect bounds = CGRectMake(0, 0, 200, 60); |
+ FrameTextInBounds(bounds); |
+ CheckForLineCountAndFramedRange(0, NSMakeRange(0, 0)); |
+} |
+ |
+// Tests that multiple newlines are accounted for. Only the first three |
+// newlines should be added to |lines_|. |
+TEST_F(ManualTextFramerTest, MultipleNewlineTest) { |
+ SetText(@"\n\n\ntext"); |
+ attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
+ 20.0, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
+ ApplyAttributesForRange(text_range()); |
+ CGRect bounds = CGRectMake(0, 0, 500, 60); |
+ FrameTextInBounds(bounds); |
+ CheckForLineCountAndFramedRange(3, NSMakeRange(0, 3)); |
+} |
+ |
+// Tests that the framed range for text that will be rendered with ligatures is |
+// corresponds with the actual range of the text. |
+TEST_F(ManualTextFramerTest, LigatureTest) { |
+ SetText(@"fffi"); |
+ attributes()[NSFontAttributeName] = RobotoFontWithSize(14.0); |
+ attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
+ 20.0, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
+ ApplyAttributesForRange(text_range()); |
+ CGRect bounds = CGRectMake(0, 0, 500, 20); |
+ FrameTextInBounds(bounds); |
+ CheckForLineCountAndFramedRange(1, text_range()); |
+} |
+ |
+// Tests that ManualTextFramer correctly frames Å |
+TEST_F(ManualTextFramerTest, DiacriticTest) { |
+ SetText(@"A\u030A"); |
+ attributes()[NSFontAttributeName] = RobotoFontWithSize(14.0); |
+ attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
+ 20.0, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
+ ApplyAttributesForRange(text_range()); |
+ CGRect bounds = CGRectMake(0, 0, 500, 20); |
+ FrameTextInBounds(bounds); |
+ CheckForLineCountAndFramedRange(1, text_range()); |
+} |
+ |
+// String text, attributes, and bounds are chosen to match the "Terms of |
+// Service" text in WelcomeToChromeView, as the text is not properly framed by |
+// CTFrameSetter. http://crbug.com/537212 |
+TEST_F(ManualTextFramerTest, TOSTextTest) { |
+ CGRect bounds = CGRectMake(0, 0, 300.0, 40.0); |
+ NSString* const kTOSLinkText = @"Terms of Service"; |
+ NSString* const kTOSText = |
+ @"By using this application, you agree to Chrome’s Terms of Service."; |
+ SetText(kTOSText); |
+ attributes()[NSFontAttributeName] = RobotoFontWithSize(14.0); |
+ attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
+ 20.0, NSTextAlignmentCenter, NSLineBreakByTruncatingTail); |
+ attributes()[NSForegroundColorAttributeName] = [UIColor blackColor]; |
+ ApplyAttributesForRange(text_range()); |
+ attributes()[NSForegroundColorAttributeName] = [UIColor blueColor]; |
+ ApplyAttributesForRange([kTOSText rangeOfString:kTOSLinkText]); |
+ FrameTextInBounds(bounds); |
+ CheckForLineCountAndFramedRange(2, text_range()); |
+} |
+ |
+// Tests that the origin of a left-aligned single line is correct. |
+TEST_F(ManualTextFramerTest, SimpleOriginTest) { |
+ SetText(@"test"); |
+ UIFont* font = RobotoFontWithSize(14.0); |
+ attributes()[NSFontAttributeName] = font; |
+ CGFloat line_height = 20.0; |
+ attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
+ line_height, NSTextAlignmentLeft, NSLineBreakByWordWrapping); |
+ ApplyAttributesForRange(text_range()); |
+ CGRect bounds = CGRectMake(0, 0, 500, 21); |
+ FrameTextInBounds(bounds); |
+ CheckForLineCountAndFramedRange(1, text_range()); |
+ FramedLine* line = [text_frame().lines firstObject]; |
+ EXPECT_EQ(0, line.origin.x); |
+ EXPECT_EQ( |
+ AlignValueToPixel(CGRectGetHeight(bounds) - line_height - font.descender, |
+ AlignmentFunction::FLOOR), |
+ line.origin.y); |
+} |
+ |
+// Tests that lines that are laid out in RTL are right aligned. |
+TEST_F(ManualTextFramerTest, OriginRTLTest) { |
+ SetText(@"\u0641\u064e\u0628\u064e\u0642\u064e\u064a\u0652\u062a\u064f\u0020" |
+ @"\u0645\u064f\u062a\u064e\u0627\u0628\u0650\u0639\u064e\u0627\u064b" |
+ @"\u0020\u0028\u0634\u064f\u063a\u0652\u0644\u0650\u064a\u0029\u0020" |
+ @"\u0644\u064e\u0639\u064e\u0644\u064e\u0643\u0650\u0020\u062a\u064e" |
+ @"\u062a\u064e\u0639\u064e\u0644\u0651\u064e\u0645\u064e\u0020\u0627" |
+ @"\u0644\u062d\u0650\u0631\u0652\u0635\u064e\u0020\u0639\u064e\u0644" |
+ @"\u064e\u0649\u0020\u0627\u0644\u0648\u064e\u0642\u0652\u062a\u0650" |
+ @"\u0020\u002e\u0020\u0641\u064e\u0627\u0644\u062d\u064e\u064a\u064e" |
+ @"\u0627\u0629\u064f"); |
+ attributes()[NSFontAttributeName] = RobotoFontWithSize(14.0); |
+ attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
+ 20.0, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
+ ApplyAttributesForRange(text_range()); |
+ CGRect bounds = CGRectMake(0, 0, 100.0, 60.0); |
+ FrameTextInBounds(bounds); |
+ CheckForLineCountAndFramedRange(3, text_range()); |
+ for (FramedLine* line in text_frame().lines) { |
+ CGFloat line_width = |
+ AlignValueToPixel(core_text_util::GetTrimmedLineWidth(line.line), |
+ AlignmentFunction::CEIL); |
+ EXPECT_EQ(CGRectGetMaxX(bounds), line.origin.x + line_width); |
+ } |
+} |
+ |
+TEST_F(ManualTextFramerTest, CJKLineBreakTest) { |
+ // Example from our strings. Framer will put “触摸搜索” on one line, and then |
+ // fail to frame the second. |
+ // clang-format off |
+ SetText(@"“触摸搜索”会将所选字词和当前页面(作为上下文)一起发送给 Google 搜索。" |
+ @"您可以在设置中停用此功能。"); |
+ // clang-format on |
+ attributes()[NSFontAttributeName] = RobotoFontWithSize(16.0); |
+ attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
+ 16 * 1.15, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
+ ApplyAttributesForRange(text_range()); |
+ CGRect bounds = CGRectMake(0, 0, 300.0, 65.0); |
+ FrameTextInBounds(bounds); |
+ CheckForLineCountAndFramedRange(3, NSMakeRange(0, 53)); |
+ |
+ // Example without any space-ish characters: |
+ // clang-format off |
+ SetText(@"会将所选字词和当前页面(作为上下文)一起发送给Google搜索。" |
+ @"您可以在设置中停用此功能。"); |
+ // clang-format on |
+ attributes()[NSFontAttributeName] = RobotoFontWithSize(16.0); |
+ attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
+ 16 * 1.15, NSTextAlignmentNatural, NSLineBreakByWordWrapping); |
+ ApplyAttributesForRange(text_range()); |
+ FrameTextInBounds(bounds); |
+ CheckForLineCountAndFramedRange(2, NSMakeRange(0, 45)); |
+} |
+ |
+// Tests that paragraphs with NSTextAlignmentCenter are actually centered. |
+TEST_F(ManualTextFramerTest, CenterAlignedTest) { |
+ SetText(@"xxxx\nyyy\nwww"); |
+ attributes()[NSFontAttributeName] = RobotoFontWithSize(14.0); |
+ attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
+ 20.0, NSTextAlignmentCenter, NSLineBreakByWordWrapping); |
+ ApplyAttributesForRange(text_range()); |
+ CGRect bounds = CGRectMake(0, 0, 200.0, 60.0); |
+ FrameTextInBounds(bounds); |
+ CheckForLineCountAndFramedRange(3, text_range()); |
+ for (FramedLine* line in text_frame().lines) { |
+ CGFloat line_width = |
+ AlignValueToPixel(core_text_util::GetTrimmedLineWidth(line.line), |
+ AlignmentFunction::CEIL); |
+ EXPECT_EQ(AlignValueToPixel(CGRectGetMidX(bounds) - 0.5 * line_width, |
+ AlignmentFunction::FLOOR), |
+ line.origin.x); |
+ } |
+} |
+ |
+// Tests that words with a large line height will not be framed if they don't |
+// fit in the bounding height. |
+TEST_F(ManualTextFramerTest, LargeLineHeightTest) { |
+ SetText(@"the last word is very LARGE"); |
+ attributes()[NSFontAttributeName] = RobotoFontWithSize(14.0); |
+ attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
+ 20.0, NSTextAlignmentCenter, NSLineBreakByWordWrapping); |
+ ApplyAttributesForRange(text_range()); |
+ attributes()[NSParagraphStyleAttributeName] = CreateParagraphStyle( |
+ 500.0, NSTextAlignmentCenter, NSLineBreakByWordWrapping); |
+ ApplyAttributesForRange(NSMakeRange(22, 5)); // "LARGE" |
+ CGRect bounds = CGRectMake(0, 0, 500, 20.0); |
+ FrameTextInBounds(bounds); |
+ CheckForLineCountAndFramedRange(1, NSMakeRange(0, 22)); |
+} |
+ |
+// Tests a preexisting error condition in which a BiDi string containing Arabic |
+// is correctly laid out (crbug.com/584549). |
+TEST_F(ManualTextFramerTest, RTLTest) { |
+ SetText(@"\u0642\u062F\u0020\u064A\u0633\u062A\u062E\u062F\u0645\u0020\u0047" |
+ "\u006F\u006F\u0067\u006C\u0065\u0020\u0043\u0068\u0072\u006F\u006D" |
+ "\u0065\u0020\u062E\u062F\u0645\u0627\u062A\u0020\u0627\u0644\u0648" |
+ "\u064A\u0628\u0020\u0644\u062A\u062D\u0633\u064A\u0646\u0020\u062A" |
+ "\u062C\u0631\u0628\u0629\u0020\u0627\u0644\u062A\u0635\u0641\u062D" |
+ "\u002E\u0020\u0648\u064A\u0645\u0643\u0646\u0643\u0020\u0628\u0634" |
+ "\u0643\u0644\u0020\u0627\u062E\u062A\u064A\u0627\u0631\u064A\u0020" |
+ "\u062A\u0639\u0637\u064A\u0644\u0020\u0647\u0630\u0647\u0020\u0627" |
+ "\u0644\u062E\u062F\u0645\u0627\u062A\u002E"); |
+ attributes()[NSFontAttributeName] = [UIFont systemFontOfSize:20.0]; |
+ ApplyAttributesForRange(text_range()); |
+ CGRect bounds = CGRectMake(0, 0, 500, 100); |
+ FrameTextInBounds(bounds); |
+ CheckForLineCountAndFramedRange(2, text_range()); |
+} |
+ |
+} // namespace |