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