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/WebCoreNSStringExtras.h>
     34 #import <WebKit/WebNSFileManagerExtras.h>
     35 #import <WebKit/WebNSObjectExtras.h>
     36 #import <unicode/uchar.h>
     37 #import <sys/param.h>
     38 
     39 NSString *WebKitLocalCacheDefaultsKey = @"WebKitLocalCache";
     40 
     41 static inline CGFloat webkit_CGCeiling(CGFloat value)
     42 {
     43     if (sizeof(value) == sizeof(float))
     44         return ceilf(value);
     45     return ceil(value);
     46 }
     47 
     48 using namespace WebCore;
     49 
     50 @implementation NSString (WebKitExtras)
     51 
     52 static 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