Reviewed by Kevin.
[WebKit-https.git] / WebKit / Misc.subproj / WebKitNSStringExtras.m
1 /*
2     WebKitNSStringExtras.m
3     Copyright 2002, Apple, Inc. All rights reserved.
4 */
5
6 #import "WebKitNSStringExtras.h"
7
8 #import <WebKit/WebNSObjectExtras.h>
9 #import <WebKit/WebTextRenderer.h>
10 #import <WebKit/WebTextRendererFactory.h>
11 #import <WebKit/WebNSFileManagerExtras.h>
12
13 #import <unicode/uchar.h>
14
15 @implementation NSString (WebKitExtras)
16
17 static BOOL canUseFastRenderer(const UniChar *buffer, unsigned length)
18 {
19     unsigned i;
20     for (i = 0; i < length; i++) {
21         UCharDirection direction = u_charDirection(buffer[i]);
22         if (direction == U_RIGHT_TO_LEFT || direction > U_WHITE_SPACE_NEUTRAL) {
23             return NO;
24         }
25     }
26     return YES;
27 }
28
29 - (void)_web_drawAtPoint:(NSPoint)point font:(NSFont *)font textColor:(NSColor *)textColor;
30 {
31     unsigned length = [self length];
32     UniChar *buffer = (UniChar *)malloc(sizeof(UniChar) * length);
33
34     [self getCharacters:buffer];
35     
36     if (canUseFastRenderer(buffer, length)){
37         WebTextRenderer *renderer = [[WebTextRendererFactory sharedFactory] rendererWithFont:font usingPrinterFont:NO];
38
39         WebCoreTextRun run;
40         WebCoreInitializeTextRun (&run, buffer, length, 0, length);
41         WebCoreTextStyle style;
42         WebCoreInitializeEmptyTextStyle(&style);
43         style.applyRunRounding = NO;
44         style.applyWordRounding = NO;
45         style.textColor = textColor;
46         WebCoreTextGeometry geometry;
47         WebCoreInitializeEmptyTextGeometry(&geometry);
48         geometry.point = point;
49         [renderer drawRun:&run style:&style geometry:&geometry];
50     }
51     else {
52         // WebTextRenderer assumes drawing from baseline.
53         if ([[NSView focusView] isFlipped])
54             point.y -= [font ascender];
55         else {
56             point.y += [font descender];
57         }
58         [self drawAtPoint:point withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, textColor, NSForegroundColorAttributeName, nil]];
59     }
60
61     free(buffer);
62 }
63
64 - (void)_web_drawDoubledAtPoint:(NSPoint)textPoint
65              withTopColor:(NSColor *)topColor
66               bottomColor:(NSColor *)bottomColor
67                      font:(NSFont *)font
68 {
69     // turn off font smoothing so translucent text draws correctly (Radar 3118455)
70     [NSGraphicsContext saveGraphicsState];
71     CGContextSetShouldSmoothFonts([[NSGraphicsContext currentContext] graphicsPort], false);
72     [self _web_drawAtPoint:textPoint
73                       font:font
74                  textColor:bottomColor];
75
76     textPoint.y += 1;
77     [self _web_drawAtPoint:textPoint
78                       font:font
79                  textColor:topColor];
80     [NSGraphicsContext restoreGraphicsState];
81 }
82
83 - (float)_web_widthWithFont:(NSFont *)font
84 {
85     unsigned length = [self length];
86     float width;
87     UniChar *buffer = (UniChar *)malloc(sizeof(UniChar) * length);
88
89     [self getCharacters:buffer];
90
91     if (canUseFastRenderer(buffer, length)){
92         WebTextRenderer *renderer = [[WebTextRendererFactory sharedFactory] rendererWithFont:font usingPrinterFont:NO];
93
94         WebCoreTextRun run;
95         WebCoreInitializeTextRun (&run, buffer, length, 0, length);
96         WebCoreTextStyle style;
97         WebCoreInitializeEmptyTextStyle(&style);
98         style.applyRunRounding = NO;
99         style.applyWordRounding = NO;
100         width = [renderer floatWidthForRun:&run style:&style widths: 0];
101     }
102     else {
103         width = [self sizeWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil]].width;
104     }
105     
106     free(buffer);
107     
108     return width;
109 }
110
111 - (NSString *)_web_stringByAbbreviatingWithTildeInPath
112 {
113     NSString *resolvedHomeDirectory = [NSHomeDirectory() stringByResolvingSymlinksInPath];
114     NSString *path;
115     
116     if ([self hasPrefix:resolvedHomeDirectory]) {
117         NSString *relativePath = [self substringFromIndex:[resolvedHomeDirectory length]];
118         path = [NSHomeDirectory() stringByAppendingPathComponent:relativePath];
119     } else {
120         path = self;
121     }
122         
123     return [path stringByAbbreviatingWithTildeInPath];
124 }
125
126 - (NSString *)_web_stringByStrippingReturnCharacters
127 {
128     NSMutableString *newString = [[self mutableCopy] autorelease];
129     [newString replaceOccurrencesOfString:@"\r" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [newString length])];
130     [newString replaceOccurrencesOfString:@"\n" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [newString length])];
131     return newString;
132 }
133
134 + (NSStringEncoding)_web_encodingForResource:(Handle)resource
135 {
136     short resRef = HomeResFile(resource);
137     if (ResError() != noErr) {
138         return NSMacOSRomanStringEncoding;
139     }
140     
141     // Get the FSRef for the current resource file
142     FSRef fref;
143     OSStatus error = FSGetForkCBInfo(resRef, 0, NULL, NULL, NULL, &fref, NULL);
144     if (error != noErr) {
145         return NSMacOSRomanStringEncoding;
146     }
147     
148     CFURLRef URL = CFURLCreateFromFSRef(NULL, &fref);
149     if (URL == NULL) {
150         return NSMacOSRomanStringEncoding;
151     }
152     
153     NSString *path = [(NSURL *)URL path];
154     CFRelease(URL);
155     
156     // Get the lproj directory name
157     path = [path stringByDeletingLastPathComponent];
158     if (![[path pathExtension] _webkit_isCaseInsensitiveEqualToString:@"lproj"]) {
159         return NSMacOSRomanStringEncoding;
160     }
161     
162     NSString *directoryName = [[path stringByDeletingPathExtension] lastPathComponent];
163     CFStringRef locale = CFLocaleCreateCanonicalLocaleIdentifierFromString(NULL, (CFStringRef)directoryName);
164     if (locale == NULL) {
165         return NSMacOSRomanStringEncoding;
166     }
167             
168     LangCode lang;
169     RegionCode region;
170     error = LocaleStringToLangAndRegionCodes([(NSString *)locale UTF8String], &lang, &region);
171     CFRelease(locale);
172     if (error != noErr) {
173         return NSMacOSRomanStringEncoding;
174     }
175     
176     TextEncoding encoding;
177     error = UpgradeScriptInfoToTextEncoding(kTextScriptDontCare, lang, region, NULL, &encoding);
178     if (error != noErr) {
179         return NSMacOSRomanStringEncoding;
180     }
181     
182     return CFStringConvertEncodingToNSStringEncoding(encoding);
183 }
184
185 - (BOOL)_webkit_isCaseInsensitiveEqualToString:(NSString *)string
186 {
187   return [self compare:string options:(NSCaseInsensitiveSearch|NSLiteralSearch)] == NSOrderedSame;
188 }
189
190 -(BOOL)_webkit_hasCaseInsensitivePrefix:(NSString *)prefix
191 {
192     return [self rangeOfString:prefix options:(NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound;
193 }
194
195 -(BOOL)_webkit_hasCaseInsensitiveSuffix:(NSString *)suffix
196 {
197     return [self rangeOfString:suffix options:(NSCaseInsensitiveSearch | NSBackwardsSearch | NSAnchoredSearch)].location != NSNotFound;
198 }
199
200 -(BOOL)_webkit_hasCaseInsensitiveSubstring:(NSString *)substring
201 {
202     return [self rangeOfString:substring options:NSCaseInsensitiveSearch].location != NSNotFound;
203 }
204
205 -(NSString *)_webkit_filenameByFixingIllegalCharacters
206 {
207     NSMutableString *filename = [[self mutableCopy] autorelease];
208
209     // Strip null characters.
210         unichar nullChar = 0;
211     [filename replaceOccurrencesOfString:[NSString stringWithCharacters:&nullChar length:0] withString:@"" options:0 range:NSMakeRange(0, [filename length])];
212
213     // Replace "/" with "-".
214     [filename replaceOccurrencesOfString:@"/" withString:@"-" options:0 range:NSMakeRange(0, [filename length])];
215
216     // Replace ":" with "-".
217     [filename replaceOccurrencesOfString:@":" withString:@"-" options:0 range:NSMakeRange(0, [filename length])];
218     
219     // Strip leading dots.
220     while ([filename hasPrefix:@"."]) {
221         [filename deleteCharactersInRange:NSMakeRange(0,1)];
222     }
223     
224     return filename;
225 }
226
227 -(NSString *)_webkit_stringByTrimmingWhitespace
228 {
229     NSMutableString *trimmed = [[self mutableCopy] autorelease];
230     CFStringTrimWhitespace((CFMutableStringRef)trimmed);
231     return trimmed;
232 }
233
234 - (NSString *)_webkit_stringByCollapsingNonPrintingCharacters
235 {
236     NSMutableString *result = [NSMutableString string];
237     static NSCharacterSet *charactersToTurnIntoSpaces = nil;
238     static NSCharacterSet *charactersToNotTurnIntoSpaces = nil;
239     
240     if (charactersToTurnIntoSpaces == nil) {
241         NSMutableCharacterSet *set = [[NSMutableCharacterSet alloc] init];
242         [set addCharactersInRange:NSMakeRange(0x00, 0x21)];
243         [set addCharactersInRange:NSMakeRange(0x7F, 0x01)];
244         charactersToTurnIntoSpaces = [set copy];
245         [set release];
246         charactersToNotTurnIntoSpaces = [[charactersToTurnIntoSpaces invertedSet] retain];
247     }
248     
249     unsigned length = [self length];
250     unsigned position = 0;
251     while (position != length) {
252         NSRange nonSpace = [self rangeOfCharacterFromSet:charactersToNotTurnIntoSpaces
253             options:0 range:NSMakeRange(position, length - position)];
254         if (nonSpace.location == NSNotFound) {
255             break;
256         }
257
258         NSRange space = [self rangeOfCharacterFromSet:charactersToTurnIntoSpaces
259             options:0 range:NSMakeRange(nonSpace.location, length - nonSpace.location)];
260         if (space.location == NSNotFound) {
261             space.location = length;
262         }
263
264         if (space.location > nonSpace.location) {
265             if (position != 0) {
266                 [result appendString:@" "];
267             }
268             [result appendString:[self substringWithRange:
269                 NSMakeRange(nonSpace.location, space.location - nonSpace.location)]];
270         }
271
272         position = space.location;
273     }
274     
275     return result;
276 }
277
278 -(NSString *)_webkit_fixedCarbonPOSIXPath
279 {
280     NSFileManager *fileManager = [NSFileManager defaultManager];
281     if ([fileManager fileExistsAtPath:self]) {
282         // Files exists, no need to fix.
283         return self;
284     }
285
286     NSMutableArray *pathComponents = [[[self pathComponents] mutableCopy] autorelease];
287     NSString *volumeName = [pathComponents objectAtIndex:1];
288     if ([volumeName isEqualToString:@"Volumes"]) {
289         // Path starts with "/Volumes", so the volume name is the next path component.
290         volumeName = [pathComponents objectAtIndex:2];
291         // Remove "Volumes" from the path because it may incorrectly be part of the path (3163647).
292         // We'll add it back if we have to.
293         [pathComponents removeObjectAtIndex:1];
294     }
295
296     if (!volumeName) {
297         // Should only happen if self == "/", so this shouldn't happen because that always exists.
298         return self;
299     }
300
301     if ([[fileManager _webkit_startupVolumeName] isEqualToString:volumeName]) {
302         // Startup volume name is included in path, remove it.
303         [pathComponents removeObjectAtIndex:1];
304     } else if ([[fileManager directoryContentsAtPath:@"/Volumes"] containsObject:volumeName]) {
305         // Path starts with other volume name, prepend "/Volumes".
306         [pathComponents insertObject:@"Volumes" atIndex:1];
307     } else {
308         // It's valid.
309         return self;
310     }
311
312     NSString *path = [NSString pathWithComponents:pathComponents];
313
314     if (![fileManager fileExistsAtPath:path]) {
315         // File at canonicalized path doesn't exist, return original.
316         return self;
317     }
318
319     return path;
320 }
321
322 @end