string_util.mm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. // Copyright 2013 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. #include "ios/chrome/common/string_util.h"
  5. #import <UIKit/UIKit.h>
  6. #include "base/check.h"
  7. #include "base/strings/sys_string_conversions.h"
  8. #if !defined(__has_feature) || !__has_feature(objc_arc)
  9. #error "This file requires ARC support."
  10. #endif
  11. namespace {
  12. typedef BOOL (^ArrayFilterProcedure)(id object, NSUInteger index, BOOL* stop);
  13. typedef NSString* (^SubstringExtractionProcedure)(NSUInteger);
  14. NSString* const kBeginLinkTag = @"BEGIN_LINK[ \t]*";
  15. NSString* const kEndLinkTag = @"[ \t]*END_LINK";
  16. NSString* const kBeginBoldTag = @"BEGIN_BOLD[ \t]*";
  17. NSString* const kEndBoldTag = @"[ \t]*END_BOLD";
  18. }
  19. StringWithTags::StringWithTags() = default;
  20. StringWithTags::StringWithTags(NSString* string, std::vector<NSRange> ranges)
  21. : string([string copy]), ranges(ranges) {}
  22. StringWithTags::StringWithTags(const StringWithTags& other) = default;
  23. StringWithTags& StringWithTags::operator=(const StringWithTags& other) =
  24. default;
  25. StringWithTags::StringWithTags(StringWithTags&& other) = default;
  26. StringWithTags& StringWithTags::operator=(StringWithTags&& other) = default;
  27. StringWithTags::~StringWithTags() = default;
  28. StringWithTags ParseStringWithLinks(NSString* text) {
  29. return ParseStringWithTags(text, kBeginLinkTag, kEndLinkTag);
  30. }
  31. NSAttributedString* AttributedStringFromStringWithLink(
  32. NSString* text,
  33. NSDictionary* text_attributes,
  34. NSDictionary* link_attributes) {
  35. StringWithTag parsed_string =
  36. ParseStringWithTag(text, kBeginLinkTag, kEndLinkTag);
  37. NSMutableAttributedString* attributed_string =
  38. [[NSMutableAttributedString alloc] initWithString:parsed_string.string
  39. attributes:text_attributes];
  40. DCHECK(parsed_string.range.location != NSNotFound);
  41. if (link_attributes != nil) {
  42. [attributed_string addAttributes:link_attributes range:parsed_string.range];
  43. }
  44. return attributed_string;
  45. }
  46. StringWithTag ParseStringWithTag(NSString* text,
  47. NSString* begin_tag,
  48. NSString* end_tag) {
  49. const StringWithTags parsed_string =
  50. ParseStringWithTags(text, begin_tag, end_tag);
  51. DCHECK_LE(parsed_string.ranges.size(), 1u);
  52. return StringWithTag{parsed_string.string, parsed_string.ranges.empty()
  53. ? NSRange{NSNotFound, 0}
  54. : parsed_string.ranges[0]};
  55. }
  56. StringWithTags ParseStringWithTags(NSString* text,
  57. NSString* begin_tag,
  58. NSString* end_tag) {
  59. NSMutableString* out_text = nil;
  60. std::vector<NSRange> tag_ranges;
  61. NSRange text_range{0, text.length};
  62. do {
  63. // Find the next |begin_tag| in |text_range|.
  64. const NSRange begin_range = [text rangeOfString:begin_tag
  65. options:NSRegularExpressionSearch
  66. range:text_range];
  67. // If no |begin_tag| is found, then there is no substitutions remainining.
  68. if (begin_range.length == 0)
  69. break;
  70. // Find the next |end_tag| after the recently found |begin_tag|.
  71. const NSUInteger after_begin_pos = NSMaxRange(begin_range);
  72. const NSRange after_begin_range{
  73. after_begin_pos,
  74. text_range.length - (after_begin_pos - text_range.location)};
  75. const NSRange end_range = [text rangeOfString:end_tag
  76. options:NSRegularExpressionSearch
  77. range:after_begin_range];
  78. // If no |end_tag| is found, then there is no substitutions remaining.
  79. if (end_range.length == 0)
  80. break;
  81. if (!out_text)
  82. out_text = [[NSMutableString alloc] initWithCapacity:text.length];
  83. const NSUInteger after_end_pos = NSMaxRange(end_range);
  84. [out_text
  85. appendString:[text
  86. substringWithRange:NSRange{text_range.location,
  87. begin_range.location -
  88. text_range.location}]];
  89. [out_text
  90. appendString:[text substringWithRange:NSRange{after_begin_pos,
  91. end_range.location -
  92. after_begin_pos}]];
  93. const NSUInteger tag_length = end_range.location - after_begin_pos;
  94. tag_ranges.push_back(NSRange{out_text.length - tag_length, tag_length});
  95. text_range =
  96. NSRange{after_end_pos,
  97. text_range.length - (after_end_pos - text_range.location)};
  98. } while (text_range.length != 0);
  99. if (!out_text) {
  100. DCHECK(tag_ranges.empty());
  101. return StringWithTags(text, {});
  102. }
  103. // Append any remaining text without tags.
  104. if (text_range.length != 0)
  105. [out_text appendString:[text substringWithRange:text_range]];
  106. return StringWithTags(out_text, tag_ranges);
  107. }
  108. // Ranges of unicode codepage containing drawing characters.
  109. // 2190—21FF Arrows
  110. // 2200—22FF Mathematical Operators
  111. // 2300—23FF Miscellaneous Technical
  112. // 2400—243F Control Pictures
  113. // 2440—245F Optical Character Recognition
  114. // 2460—24FF Enclosed Alphanumerics
  115. // 2500—257F Box Drawing
  116. // 2580—259F Block Elements
  117. // 25A0—25FF Geometric Shapes
  118. // 2600—26FF Miscellaneous Symbols
  119. // 2700—27BF Dingbats
  120. // 27C0—27EF Miscellaneous Mathematical Symbols-A
  121. // 27F0—27FF Supplemental Arrows-A
  122. // 2900—297F Supplemental Arrows-B
  123. // 2980—29FF Miscellaneous Mathematical Symbols-B
  124. // 2A00—2AFF Supplemental Mathematical Operators
  125. // 2B00—2BFF Miscellaneous Symbols and Arrows
  126. // The section 2800—28FF Braille Patterns must be preserved.
  127. // The list of characters that must be deleted from the selection.
  128. NSCharacterSet* GraphicCharactersSet() {
  129. static NSMutableCharacterSet* graphicalCharsSet;
  130. static dispatch_once_t dispatch_once_token;
  131. dispatch_once(&dispatch_once_token, ^{
  132. graphicalCharsSet = [[NSMutableCharacterSet alloc] init];
  133. NSRange graphicalCharsFirstRange = NSMakeRange(0x2190, 0x2800 - 0x2190);
  134. NSRange graphicalCharsSecondRange = NSMakeRange(0x2900, 0x2c00 - 0x2900);
  135. [graphicalCharsSet addCharactersInRange:graphicalCharsFirstRange];
  136. [graphicalCharsSet addCharactersInRange:graphicalCharsSecondRange];
  137. });
  138. return graphicalCharsSet;
  139. }
  140. NSString* CleanNSStringForDisplay(NSString* dirty, BOOL removeGraphicChars) {
  141. NSCharacterSet* wspace = [NSCharacterSet whitespaceAndNewlineCharacterSet];
  142. NSString* cleanString = dirty;
  143. if (removeGraphicChars) {
  144. cleanString = [[cleanString
  145. componentsSeparatedByCharactersInSet:GraphicCharactersSet()]
  146. componentsJoinedByString:@" "];
  147. }
  148. NSMutableArray* spaceSeparatedComponents =
  149. [[cleanString componentsSeparatedByCharactersInSet:wspace] mutableCopy];
  150. ArrayFilterProcedure filter = ^(id object, NSUInteger index, BOOL* stop) {
  151. return [object isEqualToString:@""];
  152. };
  153. [spaceSeparatedComponents
  154. removeObjectsAtIndexes:[spaceSeparatedComponents
  155. indexesOfObjectsPassingTest:filter]];
  156. cleanString = [spaceSeparatedComponents componentsJoinedByString:@" "];
  157. return cleanString;
  158. }
  159. std::string CleanStringForDisplay(const std::string& dirty,
  160. BOOL removeGraphicChars) {
  161. return base::SysNSStringToUTF8(CleanNSStringForDisplay(
  162. base::SysUTF8ToNSString(dirty), removeGraphicChars));
  163. }
  164. NSString* SubstringOfWidth(NSString* string,
  165. NSDictionary* attributes,
  166. CGFloat targetWidth,
  167. BOOL trailing) {
  168. if (![string length])
  169. return nil;
  170. UIFont* font = [attributes objectForKey:NSFontAttributeName];
  171. DCHECK(font);
  172. // Function to get the correct substring while insulating against
  173. // length overrun/underrun.
  174. SubstringExtractionProcedure getSubstring;
  175. if (trailing) {
  176. getSubstring = [^NSString*(NSUInteger chars) {
  177. NSUInteger length = [string length];
  178. return [string substringFromIndex:length - MIN(length, chars)];
  179. } copy];
  180. } else {
  181. getSubstring = [^NSString*(NSUInteger chars) {
  182. return [string substringToIndex:MIN(chars, [string length])];
  183. } copy];
  184. }
  185. // Guess at the number of characters that will fit, assuming
  186. // the font's x-height is about 25% wider than an average character (25%
  187. // value was determined experimentally).
  188. NSUInteger characters =
  189. MIN(targetWidth / (font.xHeight * 0.8), [string length]);
  190. NSInteger increment = 1;
  191. NSString* substring = getSubstring(characters);
  192. CGFloat prevWidth = [substring sizeWithAttributes:attributes].width;
  193. do {
  194. characters += increment;
  195. substring = getSubstring(characters);
  196. CGFloat thisWidth = [substring sizeWithAttributes:attributes].width;
  197. if (prevWidth > targetWidth) {
  198. if (thisWidth <= targetWidth)
  199. break; // Shrinking the string, found the right size.
  200. else
  201. increment = -1; // Shrink the string
  202. } else if (prevWidth < targetWidth) {
  203. if (thisWidth < targetWidth)
  204. increment = 1; // Grow the string
  205. else {
  206. substring = getSubstring(characters - increment);
  207. break; // Growing the string, found the right size.
  208. }
  209. }
  210. prevWidth = thisWidth;
  211. } while (characters > 0 && characters < [string length]);
  212. return substring;
  213. }
  214. CGRect TextViewLinkBound(UITextView* text_view, NSRange character_range) {
  215. // Calculate UITextRange with NSRange.
  216. UITextPosition* beginning = text_view.beginningOfDocument;
  217. UITextPosition* start =
  218. [text_view positionFromPosition:beginning
  219. offset:character_range.location];
  220. UITextPosition* end = [text_view positionFromPosition:start
  221. offset:character_range.length];
  222. CGRect rect = CGRectNull;
  223. // Returns CGRectNull if there is a nil text position.
  224. if (start && end) {
  225. UITextRange* text_range = [text_view textRangeFromPosition:start
  226. toPosition:end];
  227. NSArray* selection_rects = [text_view selectionRectsForRange:text_range];
  228. for (UITextSelectionRect* selection_rect in selection_rects) {
  229. rect = CGRectUnion(rect, selection_rect.rect);
  230. }
  231. }
  232. return rect;
  233. }
  234. NSAttributedString* PutBoldPartInString(NSString* string,
  235. UIFontTextStyle font_style) {
  236. UIFontDescriptor* default_descriptor =
  237. [UIFontDescriptor preferredFontDescriptorWithTextStyle:font_style];
  238. UIFontDescriptor* bold_descriptor =
  239. [[UIFontDescriptor preferredFontDescriptorWithTextStyle:font_style]
  240. fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold];
  241. StringWithTag parsed_string =
  242. ParseStringWithTag(string, kBeginBoldTag, kEndBoldTag);
  243. NSMutableAttributedString* attributed_string =
  244. [[NSMutableAttributedString alloc] initWithString:parsed_string.string];
  245. [attributed_string addAttribute:NSFontAttributeName
  246. value:[UIFont fontWithDescriptor:default_descriptor
  247. size:0.0]
  248. range:NSMakeRange(0, parsed_string.string.length)];
  249. [attributed_string addAttribute:NSFontAttributeName
  250. value:[UIFont fontWithDescriptor:bold_descriptor
  251. size:0.0]
  252. range:parsed_string.range];
  253. return attributed_string;
  254. }