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 Computer, 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/GraphicsContext.h> 33#import <WebCore/WebCoreNSStringExtras.h> 34#import <WebKit/WebNSFileManagerExtras.h> 35#import <WebKit/WebNSObjectExtras.h> 36#import <unicode/uchar.h> 37#import <sys/param.h> 38 39NSString *WebKitLocalCacheDefaultsKey = @"WebKitLocalCache"; 40 41static inline CGFloat webkit_CGCeiling(CGFloat value) 42{ 43 if (sizeof(value) == sizeof(float)) 44 return ceilf(value); 45 return ceil(value); 46} 47 48using namespace WebCore; 49 50@implementation NSString (WebKitExtras) 51 52static BOOL canUseFastRenderer(const UniChar *buffer, unsigned length) 53{ 54 unsigned i; 55 for (i = 0; i < length; i++) { 56 UCharDirection direction = u_charDirection(buffer[i]); 57 if (direction == U_RIGHT_TO_LEFT || direction > U_OTHER_NEUTRAL) 58 return NO; 59 } 60 return YES; 61} 62 63- (void)_web_drawAtPoint:(NSPoint)point font:(NSFont *)font textColor:(NSColor *)textColor 64{ 65 // FIXME: Would be more efficient to change this to C++ and use Vector<UChar, 2048>. 66 unsigned length = [self length]; 67 Vector<UniChar, 2048> buffer(length); 68 69 [self getCharacters:buffer.data()]; 70 71 if (canUseFastRenderer(buffer.data(), length)) { 72 // The following is a half-assed attempt to match AppKit's rounding rules for drawAtPoint. 73 // It's probably incorrect for high DPI. 74 // If you change this, be sure to test all the text drawn this way in Safari, including 75 // the status bar, bookmarks bar, tab bar, and activity window. 76 point.y = webkit_CGCeiling(point.y); 77 78 NSGraphicsContext *nsContext = [NSGraphicsContext currentContext]; 79 CGContextRef cgContext = static_cast<CGContextRef>([nsContext graphicsPort]); 80 GraphicsContext graphicsContext(cgContext); 81 82 // Safari doesn't flip the NSGraphicsContext before calling WebKit, yet WebCore requires a flipped graphics context. 83 BOOL flipped = [nsContext isFlipped]; 84 if (!flipped) 85 CGContextScaleCTM(cgContext, 1, -1); 86 87 Font webCoreFont(FontPlatformData(font), ![nsContext isDrawingToScreen]); 88 TextRun run(buffer.data(), length); 89 run.disableRoundingHacks(); 90 91 CGFloat red; 92 CGFloat green; 93 CGFloat blue; 94 CGFloat alpha; 95 [[textColor colorUsingColorSpaceName:NSDeviceRGBColorSpace] getRed:&red green:&green blue:&blue alpha:&alpha]; 96 graphicsContext.setFillColor(makeRGBA(red * 255, green * 255, blue * 255, alpha * 255), DeviceColorSpace); 97 98 webCoreFont.drawText(&graphicsContext, run, FloatPoint(point.x, (flipped ? point.y : (-1 * point.y)))); 99 100 if (!flipped) 101 CGContextScaleCTM(cgContext, 1, -1); 102 } else { 103 // The given point is on the baseline. 104 if ([[NSView focusView] isFlipped]) 105 point.y -= [font ascender]; 106 else 107 point.y += [font descender]; 108 109 [self drawAtPoint:point withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, textColor, NSForegroundColorAttributeName, nil]]; 110 } 111} 112 113- (void)_web_drawDoubledAtPoint:(NSPoint)textPoint 114 withTopColor:(NSColor *)topColor 115 bottomColor:(NSColor *)bottomColor 116 font:(NSFont *)font 117{ 118 // turn off font smoothing so translucent text draws correctly (Radar 3118455) 119 [NSGraphicsContext saveGraphicsState]; 120 CGContextSetShouldSmoothFonts(static_cast<CGContextRef>([[NSGraphicsContext currentContext] graphicsPort]), false); 121 [self _web_drawAtPoint:textPoint 122 font:font 123 textColor:bottomColor]; 124 125 textPoint.y += 1; 126 [self _web_drawAtPoint:textPoint 127 font:font 128 textColor:topColor]; 129 [NSGraphicsContext restoreGraphicsState]; 130} 131 132- (float)_web_widthWithFont:(NSFont *)font 133{ 134 unsigned length = [self length]; 135 Vector<UniChar, 2048> buffer(length); 136 137 [self getCharacters:buffer.data()]; 138 139 if (canUseFastRenderer(buffer.data(), length)) { 140 Font webCoreFont(FontPlatformData(font), ![[NSGraphicsContext currentContext] isDrawingToScreen]); 141 TextRun run(buffer.data(), length); 142 run.disableRoundingHacks(); 143 return webCoreFont.floatWidth(run); 144 } 145 146 return [self sizeWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil]].width; 147} 148 149- (NSString *)_web_stringByAbbreviatingWithTildeInPath 150{ 151 NSString *resolvedHomeDirectory = [NSHomeDirectory() stringByResolvingSymlinksInPath]; 152 NSString *path; 153 154 if ([self hasPrefix:resolvedHomeDirectory]) { 155 NSString *relativePath = [self substringFromIndex:[resolvedHomeDirectory length]]; 156 path = [NSHomeDirectory() stringByAppendingPathComponent:relativePath]; 157 } else { 158 path = self; 159 } 160 161 return [path stringByAbbreviatingWithTildeInPath]; 162} 163 164- (NSString *)_web_stringByStrippingReturnCharacters 165{ 166 NSMutableString *newString = [[self mutableCopy] autorelease]; 167 [newString replaceOccurrencesOfString:@"\r" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [newString length])]; 168 [newString replaceOccurrencesOfString:@"\n" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [newString length])]; 169 return newString; 170} 171 172+ (NSStringEncoding)_web_encodingForResource:(Handle)resource 173{ 174 return CFStringConvertEncodingToNSStringEncoding(stringEncodingForResource(resource)); 175} 176 177- (BOOL)_webkit_isCaseInsensitiveEqualToString:(NSString *)string 178{ 179 return stringIsCaseInsensitiveEqualToString(self, string); 180} 181 182-(BOOL)_webkit_hasCaseInsensitivePrefix:(NSString *)prefix 183{ 184 return [self rangeOfString:prefix options:(NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound; 185} 186 187-(BOOL)_webkit_hasCaseInsensitiveSuffix:(NSString *)suffix 188{ 189 return hasCaseInsensitiveSuffix(self, suffix); 190} 191 192-(BOOL)_webkit_hasCaseInsensitiveSubstring:(NSString *)substring 193{ 194 return hasCaseInsensitiveSubstring(self, substring); 195} 196 197-(NSString *)_webkit_filenameByFixingIllegalCharacters 198{ 199 return filenameByFixingIllegalCharacters(self); 200} 201 202-(NSString *)_webkit_stringByTrimmingWhitespace 203{ 204 NSMutableString *trimmed = [[self mutableCopy] autorelease]; 205 CFStringTrimWhitespace((CFMutableStringRef)trimmed); 206 return trimmed; 207} 208 209- (NSString *)_webkit_stringByCollapsingNonPrintingCharacters 210{ 211 NSMutableString *result = [NSMutableString string]; 212 static NSCharacterSet *charactersToTurnIntoSpaces = nil; 213 static NSCharacterSet *charactersToNotTurnIntoSpaces = nil; 214 215 if (charactersToTurnIntoSpaces == nil) { 216 NSMutableCharacterSet *set = [[NSMutableCharacterSet alloc] init]; 217 [set addCharactersInRange:NSMakeRange(0x00, 0x21)]; 218 [set addCharactersInRange:NSMakeRange(0x7F, 0x01)]; 219 charactersToTurnIntoSpaces = [set copy]; 220 [set release]; 221 charactersToNotTurnIntoSpaces = [[charactersToTurnIntoSpaces invertedSet] retain]; 222 } 223 224 unsigned length = [self length]; 225 unsigned position = 0; 226 while (position != length) { 227 NSRange nonSpace = [self rangeOfCharacterFromSet:charactersToNotTurnIntoSpaces 228 options:0 range:NSMakeRange(position, length - position)]; 229 if (nonSpace.location == NSNotFound) { 230 break; 231 } 232 233 NSRange space = [self rangeOfCharacterFromSet:charactersToTurnIntoSpaces 234 options:0 range:NSMakeRange(nonSpace.location, length - nonSpace.location)]; 235 if (space.location == NSNotFound) { 236 space.location = length; 237 } 238 239 if (space.location > nonSpace.location) { 240 if (position != 0) { 241 [result appendString:@" "]; 242 } 243 [result appendString:[self substringWithRange: 244 NSMakeRange(nonSpace.location, space.location - nonSpace.location)]]; 245 } 246 247 position = space.location; 248 } 249 250 return result; 251} 252 253- (NSString *)_webkit_stringByCollapsingWhitespaceCharacters 254{ 255 NSMutableString *result = [[NSMutableString alloc] initWithCapacity:[self length]]; 256 NSCharacterSet *spaces = [NSCharacterSet whitespaceAndNewlineCharacterSet]; 257 static NSCharacterSet *notSpaces = nil; 258 259 if (notSpaces == nil) 260 notSpaces = [[spaces invertedSet] retain]; 261 262 unsigned length = [self length]; 263 unsigned position = 0; 264 while (position != length) { 265 NSRange nonSpace = [self rangeOfCharacterFromSet:notSpaces options:0 range:NSMakeRange(position, length - position)]; 266 if (nonSpace.location == NSNotFound) 267 break; 268 269 NSRange space = [self rangeOfCharacterFromSet:spaces options:0 range:NSMakeRange(nonSpace.location, length - nonSpace.location)]; 270 if (space.location == NSNotFound) 271 space.location = length; 272 273 if (space.location > nonSpace.location) { 274 if (position != 0) 275 [result appendString:@" "]; 276 [result appendString:[self substringWithRange:NSMakeRange(nonSpace.location, space.location - nonSpace.location)]]; 277 } 278 279 position = space.location; 280 } 281 282 return [result autorelease]; 283} 284 285-(NSString *)_webkit_fixedCarbonPOSIXPath 286{ 287 NSFileManager *fileManager = [NSFileManager defaultManager]; 288 if ([fileManager fileExistsAtPath:self]) { 289 // Files exists, no need to fix. 290 return self; 291 } 292 293 NSMutableArray *pathComponents = [[[self pathComponents] mutableCopy] autorelease]; 294 NSString *volumeName = [pathComponents objectAtIndex:1]; 295 if ([volumeName isEqualToString:@"Volumes"]) { 296 // Path starts with "/Volumes", so the volume name is the next path component. 297 volumeName = [pathComponents objectAtIndex:2]; 298 // Remove "Volumes" from the path because it may incorrectly be part of the path (3163647). 299 // We'll add it back if we have to. 300 [pathComponents removeObjectAtIndex:1]; 301 } 302 303 if (!volumeName) { 304 // Should only happen if self == "/", so this shouldn't happen because that always exists. 305 return self; 306 } 307 308 if ([[fileManager _webkit_startupVolumeName] isEqualToString:volumeName]) { 309 // Startup volume name is included in path, remove it. 310 [pathComponents removeObjectAtIndex:1]; 311 } else if ([[fileManager contentsOfDirectoryAtPath:@"/Volumes" error:NULL] containsObject:volumeName]) { 312 // Path starts with other volume name, prepend "/Volumes". 313 [pathComponents insertObject:@"Volumes" atIndex:1]; 314 } else 315 // It's valid. 316 return self; 317 318 NSString *path = [NSString pathWithComponents:pathComponents]; 319 320 if (![fileManager fileExistsAtPath:path]) 321 // File at canonicalized path doesn't exist, return original. 322 return self; 323 324 return path; 325} 326 327+ (NSString *)_webkit_localCacheDirectoryWithBundleIdentifier:(NSString*)bundleIdentifier 328{ 329 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 330 NSString *cacheDir = [defaults objectForKey:WebKitLocalCacheDefaultsKey]; 331 332 if (!cacheDir || ![cacheDir isKindOfClass:[NSString class]]) { 333#ifdef BUILDING_ON_TIGER 334 cacheDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Caches"]; 335#else 336 char cacheDirectory[MAXPATHLEN]; 337 size_t cacheDirectoryLen = confstr(_CS_DARWIN_USER_CACHE_DIR, cacheDirectory, MAXPATHLEN); 338 339 if (cacheDirectoryLen) 340 cacheDir = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:cacheDirectory length:cacheDirectoryLen - 1]; 341#endif 342 } 343 344 return [cacheDir stringByAppendingPathComponent:bundleIdentifier]; 345} 346 347@end 348