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