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