Home | History | Annotate | Download | only in Misc
      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