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