• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
40NSString *WebKitLocalCacheDefaultsKey = @"WebKitLocalCache";
41
42static inline CGFloat webkit_CGCeiling(CGFloat value)
43{
44    if (sizeof(value) == sizeof(float))
45        return ceilf(value);
46    return ceil(value);
47}
48
49using namespace WebCore;
50
51@implementation NSString (WebKitExtras)
52
53static 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