1/* 2 * Copyright (C) 2005, 2007 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#import "WebKitNSStringExtras.h" 30 31#import <WebCore/Font.h> 32#import <WebCore/FontCache.h> 33#import <WebCore/GraphicsContext.h> 34#import <WebCore/TextRun.h> 35#import <WebCore/WebCoreNSStringExtras.h> 36#import <WebKitLegacy/WebNSFileManagerExtras.h> 37#import <WebKitLegacy/WebNSObjectExtras.h> 38#import <unicode/uchar.h> 39#import <sys/param.h> 40 41#if PLATFORM(IOS) 42#import <WebCore/WAKViewPrivate.h> 43#import <WebKitLegacy/DOM.h> 44#import <WebKitLegacy/WebFrame.h> 45#import <WebKitLegacy/WebFrameView.h> 46#import <WebKitLegacy/WebViewPrivate.h> 47#endif 48 49NSString *WebKitLocalCacheDefaultsKey = @"WebKitLocalCache"; 50 51#if !PLATFORM(IOS) 52static inline CGFloat webkit_CGCeiling(CGFloat value) 53{ 54 if (sizeof(value) == sizeof(float)) 55 return ceilf(value); 56 return ceil(value); 57} 58#endif 59 60using namespace WebCore; 61 62@implementation NSString (WebKitExtras) 63 64#if !PLATFORM(IOS) 65static BOOL canUseFastRenderer(const UniChar *buffer, unsigned length) 66{ 67 unsigned i; 68 for (i = 0; i < length; i++) { 69 UCharDirection direction = u_charDirection(buffer[i]); 70 if (direction == U_RIGHT_TO_LEFT || direction > U_OTHER_NEUTRAL) 71 return NO; 72 } 73 return YES; 74} 75 76- (void)_web_drawAtPoint:(NSPoint)point font:(NSFont *)font textColor:(NSColor *)textColor 77{ 78 [self _web_drawAtPoint:point font:font textColor:textColor allowingFontSmoothing:YES]; 79} 80 81- (void)_web_drawAtPoint:(NSPoint)point font:(NSFont *)font textColor:(NSColor *)textColor allowingFontSmoothing:(BOOL)fontSmoothingIsAllowed 82{ 83 unsigned length = [self length]; 84 Vector<UniChar, 2048> buffer(length); 85 86 [self getCharacters:buffer.data()]; 87 88 if (canUseFastRenderer(buffer.data(), length)) { 89 // The following is a half-assed attempt to match AppKit's rounding rules for drawAtPoint. 90 // It's probably incorrect for high DPI. 91 // If you change this, be sure to test all the text drawn this way in Safari, including 92 // the status bar, bookmarks bar, tab bar, and activity window. 93 point.y = webkit_CGCeiling(point.y); 94 95 NSGraphicsContext *nsContext = [NSGraphicsContext currentContext]; 96 CGContextRef cgContext = static_cast<CGContextRef>([nsContext graphicsPort]); 97 GraphicsContext graphicsContext(cgContext); 98 99 // Safari doesn't flip the NSGraphicsContext before calling WebKit, yet WebCore requires a flipped graphics context. 100 BOOL flipped = [nsContext isFlipped]; 101 if (!flipped) 102 CGContextScaleCTM(cgContext, 1, -1); 103 104 FontCachePurgePreventer fontCachePurgePreventer; 105 106 Font webCoreFont(FontPlatformData(font, [font pointSize]), ![nsContext isDrawingToScreen], fontSmoothingIsAllowed ? AutoSmoothing : Antialiased); 107 TextRun run(buffer.data(), length); 108 run.disableRoundingHacks(); 109 110 CGFloat red; 111 CGFloat green; 112 CGFloat blue; 113 CGFloat alpha; 114 [[textColor colorUsingColorSpaceName:NSDeviceRGBColorSpace] getRed:&red green:&green blue:&blue alpha:&alpha]; 115 graphicsContext.setFillColor(makeRGBA(red * 255, green * 255, blue * 255, alpha * 255), ColorSpaceDeviceRGB); 116 117 webCoreFont.drawText(&graphicsContext, run, FloatPoint(point.x, (flipped ? point.y : (-1 * point.y)))); 118 119 if (!flipped) 120 CGContextScaleCTM(cgContext, 1, -1); 121 } else { 122 // The given point is on the baseline. 123 if ([[NSView focusView] isFlipped]) 124 point.y -= [font ascender]; 125 else 126 point.y += [font descender]; 127 128 [self drawAtPoint:point withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, textColor, NSForegroundColorAttributeName, nil]]; 129 } 130} 131 132- (void)_web_drawDoubledAtPoint:(NSPoint)textPoint 133 withTopColor:(NSColor *)topColor 134 bottomColor:(NSColor *)bottomColor 135 font:(NSFont *)font 136{ 137 // turn off font smoothing so translucent text draws correctly (Radar 3118455) 138 [self _web_drawAtPoint:textPoint font:font textColor:bottomColor allowingFontSmoothing:NO]; 139 140 textPoint.y += 1; 141 [self _web_drawAtPoint:textPoint font:font textColor:topColor allowingFontSmoothing:NO]; 142} 143 144- (float)_web_widthWithFont:(NSFont *)font 145{ 146 unsigned length = [self length]; 147 Vector<UniChar, 2048> buffer(length); 148 149 [self getCharacters:buffer.data()]; 150 151 if (canUseFastRenderer(buffer.data(), length)) { 152 FontCachePurgePreventer fontCachePurgePreventer; 153 154 Font webCoreFont(FontPlatformData(font, [font pointSize]), ![[NSGraphicsContext currentContext] isDrawingToScreen]); 155 TextRun run(buffer.data(), length); 156 run.disableRoundingHacks(); 157 return webCoreFont.width(run); 158 } 159 160 return [self sizeWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil]].width; 161} 162#endif // !PLATFORM(IOS) 163 164- (NSString *)_web_stringByAbbreviatingWithTildeInPath 165{ 166 NSString *resolvedHomeDirectory = [NSHomeDirectory() stringByResolvingSymlinksInPath]; 167 NSString *path; 168 169 if ([self hasPrefix:resolvedHomeDirectory]) { 170 NSString *relativePath = [self substringFromIndex:[resolvedHomeDirectory length]]; 171 path = [NSHomeDirectory() stringByAppendingPathComponent:relativePath]; 172 } else { 173 path = self; 174 } 175 176 return [path stringByAbbreviatingWithTildeInPath]; 177} 178 179- (NSString *)_web_stringByStrippingReturnCharacters 180{ 181 NSMutableString *newString = [[self mutableCopy] autorelease]; 182 [newString replaceOccurrencesOfString:@"\r" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [newString length])]; 183 [newString replaceOccurrencesOfString:@"\n" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [newString length])]; 184 return newString; 185} 186 187#if !PLATFORM(IOS) 188+ (NSStringEncoding)_web_encodingForResource:(Handle)resource 189{ 190 return CFStringConvertEncodingToNSStringEncoding(stringEncodingForResource(resource)); 191} 192#endif 193 194- (BOOL)_webkit_isCaseInsensitiveEqualToString:(NSString *)string 195{ 196 return stringIsCaseInsensitiveEqualToString(self, string); 197} 198 199-(BOOL)_webkit_hasCaseInsensitivePrefix:(NSString *)prefix 200{ 201 return hasCaseInsensitivePrefix(self, prefix); 202} 203 204-(BOOL)_webkit_hasCaseInsensitiveSuffix:(NSString *)suffix 205{ 206 return hasCaseInsensitiveSuffix(self, suffix); 207} 208 209-(BOOL)_webkit_hasCaseInsensitiveSubstring:(NSString *)substring 210{ 211 return hasCaseInsensitiveSubstring(self, substring); 212} 213 214-(NSString *)_webkit_filenameByFixingIllegalCharacters 215{ 216 return filenameByFixingIllegalCharacters(self); 217} 218 219-(NSString *)_webkit_stringByTrimmingWhitespace 220{ 221 NSMutableString *trimmed = [[self mutableCopy] autorelease]; 222 CFStringTrimWhitespace((CFMutableStringRef)trimmed); 223 return trimmed; 224} 225 226- (NSString *)_webkit_stringByCollapsingNonPrintingCharacters 227{ 228 NSMutableString *result = [NSMutableString string]; 229 static NSCharacterSet *charactersToTurnIntoSpaces = nil; 230 static NSCharacterSet *charactersToNotTurnIntoSpaces = nil; 231 232 if (charactersToTurnIntoSpaces == nil) { 233 NSMutableCharacterSet *set = [[NSMutableCharacterSet alloc] init]; 234 [set addCharactersInRange:NSMakeRange(0x00, 0x21)]; 235 [set addCharactersInRange:NSMakeRange(0x7F, 0x01)]; 236 charactersToTurnIntoSpaces = [set copy]; 237 [set release]; 238 charactersToNotTurnIntoSpaces = [[charactersToTurnIntoSpaces invertedSet] retain]; 239 } 240 241 unsigned length = [self length]; 242 unsigned position = 0; 243 while (position != length) { 244 NSRange nonSpace = [self rangeOfCharacterFromSet:charactersToNotTurnIntoSpaces 245 options:0 range:NSMakeRange(position, length - position)]; 246 if (nonSpace.location == NSNotFound) { 247 break; 248 } 249 250 NSRange space = [self rangeOfCharacterFromSet:charactersToTurnIntoSpaces 251 options:0 range:NSMakeRange(nonSpace.location, length - nonSpace.location)]; 252 if (space.location == NSNotFound) { 253 space.location = length; 254 } 255 256 if (space.location > nonSpace.location) { 257 if (position != 0) { 258 [result appendString:@" "]; 259 } 260 [result appendString:[self substringWithRange: 261 NSMakeRange(nonSpace.location, space.location - nonSpace.location)]]; 262 } 263 264 position = space.location; 265 } 266 267 return result; 268} 269 270- (NSString *)_webkit_stringByCollapsingWhitespaceCharacters 271{ 272 NSMutableString *result = [[NSMutableString alloc] initWithCapacity:[self length]]; 273 NSCharacterSet *spaces = [NSCharacterSet whitespaceAndNewlineCharacterSet]; 274 static NSCharacterSet *notSpaces = nil; 275 276 if (notSpaces == nil) 277 notSpaces = [[spaces invertedSet] retain]; 278 279 unsigned length = [self length]; 280 unsigned position = 0; 281 while (position != length) { 282 NSRange nonSpace = [self rangeOfCharacterFromSet:notSpaces options:0 range:NSMakeRange(position, length - position)]; 283 if (nonSpace.location == NSNotFound) 284 break; 285 286 NSRange space = [self rangeOfCharacterFromSet:spaces options:0 range:NSMakeRange(nonSpace.location, length - nonSpace.location)]; 287 if (space.location == NSNotFound) 288 space.location = length; 289 290 if (space.location > nonSpace.location) { 291 if (position != 0) 292 [result appendString:@" "]; 293 [result appendString:[self substringWithRange:NSMakeRange(nonSpace.location, space.location - nonSpace.location)]]; 294 } 295 296 position = space.location; 297 } 298 299 return [result autorelease]; 300} 301 302#if !PLATFORM(IOS) 303-(NSString *)_webkit_fixedCarbonPOSIXPath 304{ 305 NSFileManager *fileManager = [NSFileManager defaultManager]; 306 if ([fileManager fileExistsAtPath:self]) { 307 // Files exists, no need to fix. 308 return self; 309 } 310 311 NSMutableArray *pathComponents = [[[self pathComponents] mutableCopy] autorelease]; 312 NSString *volumeName = [pathComponents objectAtIndex:1]; 313 if ([volumeName isEqualToString:@"Volumes"]) { 314 // Path starts with "/Volumes", so the volume name is the next path component. 315 volumeName = [pathComponents objectAtIndex:2]; 316 // Remove "Volumes" from the path because it may incorrectly be part of the path (3163647). 317 // We'll add it back if we have to. 318 [pathComponents removeObjectAtIndex:1]; 319 } 320 321 if (!volumeName) { 322 // Should only happen if self == "/", so this shouldn't happen because that always exists. 323 return self; 324 } 325 326 if ([[fileManager _webkit_startupVolumeName] isEqualToString:volumeName]) { 327 // Startup volume name is included in path, remove it. 328 [pathComponents removeObjectAtIndex:1]; 329 } else if ([[fileManager contentsOfDirectoryAtPath:@"/Volumes" error:NULL] containsObject:volumeName]) { 330 // Path starts with other volume name, prepend "/Volumes". 331 [pathComponents insertObject:@"Volumes" atIndex:1]; 332 } else 333 // It's valid. 334 return self; 335 336 NSString *path = [NSString pathWithComponents:pathComponents]; 337 338 if (![fileManager fileExistsAtPath:path]) 339 // File at canonicalized path doesn't exist, return original. 340 return self; 341 342 return path; 343} 344#endif // !PLATFORM(IOS) 345 346#if PLATFORM(IOS) 347+ (NSString *)_web_stringWithData:(NSData *)data textEncodingName:(NSString *)textEncodingName 348{ 349 return [WebFrame stringWithData:data textEncodingName:textEncodingName]; 350} 351#endif 352 353+ (NSString *)_webkit_localCacheDirectoryWithBundleIdentifier:(NSString*)bundleIdentifier 354{ 355 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 356 NSString *cacheDir = [defaults objectForKey:WebKitLocalCacheDefaultsKey]; 357 358 if (!cacheDir || ![cacheDir isKindOfClass:[NSString class]]) { 359#if PLATFORM(IOS) 360 cacheDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Caches"]; 361#else 362 char cacheDirectory[MAXPATHLEN]; 363 size_t cacheDirectoryLen = confstr(_CS_DARWIN_USER_CACHE_DIR, cacheDirectory, MAXPATHLEN); 364 365 if (cacheDirectoryLen) 366 cacheDir = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:cacheDirectory length:cacheDirectoryLen - 1]; 367#endif 368 } 369 370 return [cacheDir stringByAppendingPathComponent:bundleIdentifier]; 371} 372 373@end 374