// Copyright 2013 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/common/string_util.h" #import <UIKit/UIKit.h> #include "base/logging.h" #include "base/mac/scoped_block.h" #include "base/mac/scoped_nsobject.h" #include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" namespace { typedef BOOL (^ArrayFilterProcedure)(id object, NSUInteger index, BOOL* stop); typedef NSString* (^SubstringExtractionProcedure)(NSUInteger); } NSString* ParseStringWithLink(NSString* text, NSRange* out_link_range) { // Find the range within |text| and create a substring without the link tags. NSRange begin_range = [text rangeOfString:@"BEGIN_LINK[ \t]*" options:NSRegularExpressionSearch]; NSRange link_text_range = NSMakeRange(NSNotFound, 0); if (begin_range.length == 0) { if (out_link_range) *out_link_range = link_text_range; return text; } NSUInteger after_begin_link = NSMaxRange(begin_range); NSRange range_to_search_for_end_link = NSMakeRange(after_begin_link, text.length - after_begin_link); NSRange end_range = [text rangeOfString:@"[ \t]*END_LINK" options:NSRegularExpressionSearch range:range_to_search_for_end_link]; if (end_range.length == 0) { if (out_link_range) *out_link_range = link_text_range; return text; } link_text_range.location = after_begin_link; link_text_range.length = end_range.location - link_text_range.location; base::scoped_nsobject<NSMutableString> out_text( [[NSMutableString alloc] init]); // First part - before the link. if (begin_range.location > 0) [out_text appendString:[text substringToIndex:begin_range.location]]; // Link part. [out_text appendString:[text substringWithRange:link_text_range]]; // Last part - after the link. NSUInteger after_end_link = NSMaxRange(end_range); if (after_end_link < [text length]) { [out_text appendString:[text substringFromIndex:after_end_link]]; } link_text_range.location = begin_range.location; if (out_link_range) *out_link_range = link_text_range; return [NSString stringWithString:out_text]; } // Ranges of unicode codepage containing drawing characters. // 2190—21FF Arrows // 2200—22FF Mathematical Operators // 2300—23FF Miscellaneous Technical // 2400—243F Control Pictures // 2440—245F Optical Character Recognition // 2460—24FF Enclosed Alphanumerics // 2500—257F Box Drawing // 2580—259F Block Elements // 25A0—25FF Geometric Shapes // 2600—26FF Miscellaneous Symbols // 2700—27BF Dingbats // 27C0—27EF Miscellaneous Mathematical Symbols-A // 27F0—27FF Supplemental Arrows-A // 2900—297F Supplemental Arrows-B // 2980—29FF Miscellaneous Mathematical Symbols-B // 2A00—2AFF Supplemental Mathematical Operators // 2B00—2BFF Miscellaneous Symbols and Arrows // The section 2800—28FF Braille Patterns must be preserved. // The list of characters that must be deleted from the selection. NSCharacterSet* GraphicCharactersSet() { static NSMutableCharacterSet* graphicalCharsSet; static dispatch_once_t dispatch_once_token; dispatch_once(&dispatch_once_token, ^{ graphicalCharsSet = [[NSMutableCharacterSet alloc] init]; NSRange graphicalCharsFirstRange = NSMakeRange(0x2190, 0x2800 - 0x2190); NSRange graphicalCharsSecondRange = NSMakeRange(0x2900, 0x2c00 - 0x2900); [graphicalCharsSet addCharactersInRange:graphicalCharsFirstRange]; [graphicalCharsSet addCharactersInRange:graphicalCharsSecondRange]; }); return graphicalCharsSet; } NSString* CleanNSStringForDisplay(NSString* dirty, BOOL removeGraphicChars) { NSCharacterSet* wspace = [NSCharacterSet whitespaceAndNewlineCharacterSet]; NSString* cleanString = dirty; if (removeGraphicChars) { cleanString = [[cleanString componentsSeparatedByCharactersInSet:GraphicCharactersSet()] componentsJoinedByString:@" "]; } base::scoped_nsobject<NSMutableArray> spaceSeparatedCompoments( [[cleanString componentsSeparatedByCharactersInSet:wspace] mutableCopy]); ArrayFilterProcedure filter = ^(id object, NSUInteger index, BOOL* stop) { return [object isEqualToString:@""]; }; [spaceSeparatedCompoments removeObjectsAtIndexes:[spaceSeparatedCompoments indexesOfObjectsPassingTest:filter]]; cleanString = [spaceSeparatedCompoments componentsJoinedByString:@" "]; return cleanString; } std::string CleanStringForDisplay(const std::string& dirty, BOOL removeGraphicChars) { return base::SysNSStringToUTF8(CleanNSStringForDisplay( base::SysUTF8ToNSString(dirty), removeGraphicChars)); } NSString* SubstringOfWidth(NSString* string, NSDictionary* attributes, CGFloat targetWidth, BOOL trailing) { if (![string length]) return nil; UIFont* font = [attributes objectForKey:NSFontAttributeName]; DCHECK(font); // Function to get the correct substring while insulating against // length overrun/underrun. base::mac::ScopedBlock<SubstringExtractionProcedure> getSubstring; if (trailing) { getSubstring.reset([^NSString*(NSUInteger chars) { NSUInteger length = [string length]; return [string substringFromIndex:length - MIN(length, chars)]; } copy]); } else { getSubstring.reset([^NSString*(NSUInteger chars) { return [string substringToIndex:MIN(chars, [string length])]; } copy]); } // Guess at the number of characters that will fit, assuming // the font's x-height is about 25% wider than an average character (25% // value was determined experimentally). NSUInteger characters = MIN(targetWidth / (font.xHeight * 0.8), [string length]); NSInteger increment = 1; NSString* substring = getSubstring.get()(characters); CGFloat prevWidth = [substring sizeWithAttributes:attributes].width; do { characters += increment; substring = getSubstring.get()(characters); CGFloat thisWidth = [substring sizeWithAttributes:attributes].width; if (prevWidth > targetWidth) { if (thisWidth <= targetWidth) break; // Shrinking the string, found the right size. else increment = -1; // Shrink the string } else if (prevWidth < targetWidth) { if (thisWidth < targetWidth) increment = 1; // Grow the string else { substring = getSubstring.get()(characters - increment); break; // Growing the string, found the right size. } } prevWidth = thisWidth; } while (characters > 0 && characters < [string length]); return substring; }