[iOS] Upstream PLATFORM(IOS) changes to Source/WebKit/
[WebKit-https.git] / Source / WebKit / mac / Misc / WebKitNSStringExtras.mm
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
41 #if PLATFORM(IOS)
42 #import <WebCore/WAKViewPrivate.h>
43 #import <WebKit/DOM.h>
44 #import <WebKit/WebFrame.h>
45 #import <WebKit/WebFrameView.h>
46 #import <WebKit/WebViewPrivate.h>
47 #endif
48
49 NSString *WebKitLocalCacheDefaultsKey = @"WebKitLocalCache";
50
51 #if !PLATFORM(IOS)
52 static 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
60 using namespace WebCore;
61
62 @implementation NSString (WebKitExtras)
63
64 #if !PLATFORM(IOS)
65 static 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