|
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 <WebKit/WebNSObjectExtras.h> |
|
32 #import <WebKit/WebNSFileManagerExtras.h> |
|
33 |
|
34 #import <WebCore/WebCoreNSStringExtras.h> |
|
35 #import <WebCore/WebCoreTextRenderer.h> |
|
36 |
|
37 #import <unicode/uchar.h> |
|
38 |
|
39 @implementation NSString (WebKitExtras) |
|
40 |
|
41 static BOOL canUseFastRenderer(const UniChar *buffer, unsigned length) |
|
42 { |
|
43 unsigned i; |
|
44 for (i = 0; i < length; i++) { |
|
45 UCharDirection direction = u_charDirection(buffer[i]); |
|
46 if (direction == U_RIGHT_TO_LEFT || direction > U_OTHER_NEUTRAL) |
|
47 return NO; |
|
48 } |
|
49 return YES; |
|
50 } |
|
51 |
|
52 - (void)_web_drawAtPoint:(NSPoint)point font:(NSFont *)font textColor:(NSColor *)textColor; |
|
53 { |
|
54 // FIXME: Would be more efficient to change this to C++ and use Vector<UChar, 2048>. |
|
55 unsigned length = [self length]; |
|
56 UniChar *buffer = malloc(sizeof(UniChar) * length); |
|
57 |
|
58 [self getCharacters:buffer]; |
|
59 |
|
60 if (canUseFastRenderer(buffer, length)) { |
|
61 // The following is a half-assed attempt to match AppKit's rounding rules for drawAtPoint. |
|
62 // It's probably incorrect for high DPI. |
|
63 // If you change this, be sure to test all the text drawn this way in Safari, including |
|
64 // the status bar, bookmarks bar, tab bar, and activity window. |
|
65 point.y = ceilf(point.y); |
|
66 WebCoreDrawTextAtPoint(buffer, length, point, font, textColor); |
|
67 } else { |
|
68 // WebTextRenderer assumes drawing from baseline. |
|
69 if ([[NSView focusView] isFlipped]) |
|
70 point.y -= [font ascender]; |
|
71 else { |
|
72 point.y += [font descender]; |
|
73 } |
|
74 [self drawAtPoint:point withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, textColor, NSForegroundColorAttributeName, nil]]; |
|
75 } |
|
76 |
|
77 free(buffer); |
|
78 } |
|
79 |
|
80 - (void)_web_drawDoubledAtPoint:(NSPoint)textPoint |
|
81 withTopColor:(NSColor *)topColor |
|
82 bottomColor:(NSColor *)bottomColor |
|
83 font:(NSFont *)font |
|
84 { |
|
85 // turn off font smoothing so translucent text draws correctly (Radar 3118455) |
|
86 [NSGraphicsContext saveGraphicsState]; |
|
87 CGContextSetShouldSmoothFonts([[NSGraphicsContext currentContext] graphicsPort], false); |
|
88 [self _web_drawAtPoint:textPoint |
|
89 font:font |
|
90 textColor:bottomColor]; |
|
91 |
|
92 textPoint.y += 1; |
|
93 [self _web_drawAtPoint:textPoint |
|
94 font:font |
|
95 textColor:topColor]; |
|
96 [NSGraphicsContext restoreGraphicsState]; |
|
97 } |
|
98 |
|
99 - (float)_web_widthWithFont:(NSFont *)font |
|
100 { |
|
101 unsigned length = [self length]; |
|
102 float width; |
|
103 UniChar *buffer = (UniChar *)malloc(sizeof(UniChar) * length); |
|
104 |
|
105 [self getCharacters:buffer]; |
|
106 |
|
107 if (canUseFastRenderer(buffer, length)) |
|
108 width = WebCoreTextFloatWidth(buffer, length, font); |
|
109 else |
|
110 width = [self sizeWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil]].width; |
|
111 |
|
112 free(buffer); |
|
113 |
|
114 return width; |
|
115 } |
|
116 |
|
117 - (NSString *)_web_stringByAbbreviatingWithTildeInPath |
|
118 { |
|
119 NSString *resolvedHomeDirectory = [NSHomeDirectory() stringByResolvingSymlinksInPath]; |
|
120 NSString *path; |
|
121 |
|
122 if ([self hasPrefix:resolvedHomeDirectory]) { |
|
123 NSString *relativePath = [self substringFromIndex:[resolvedHomeDirectory length]]; |
|
124 path = [NSHomeDirectory() stringByAppendingPathComponent:relativePath]; |
|
125 } else { |
|
126 path = self; |
|
127 } |
|
128 |
|
129 return [path stringByAbbreviatingWithTildeInPath]; |
|
130 } |
|
131 |
|
132 - (NSString *)_web_stringByStrippingReturnCharacters |
|
133 { |
|
134 NSMutableString *newString = [[self mutableCopy] autorelease]; |
|
135 [newString replaceOccurrencesOfString:@"\r" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [newString length])]; |
|
136 [newString replaceOccurrencesOfString:@"\n" withString:@"" options:NSLiteralSearch range:NSMakeRange(0, [newString length])]; |
|
137 return newString; |
|
138 } |
|
139 |
|
140 + (NSStringEncoding)_web_encodingForResource:(Handle)resource |
|
141 { |
|
142 short resRef = HomeResFile(resource); |
|
143 if (ResError() != noErr) { |
|
144 return NSMacOSRomanStringEncoding; |
|
145 } |
|
146 |
|
147 // Get the FSRef for the current resource file |
|
148 FSRef fref; |
|
149 OSStatus error = FSGetForkCBInfo(resRef, 0, NULL, NULL, NULL, &fref, NULL); |
|
150 if (error != noErr) { |
|
151 return NSMacOSRomanStringEncoding; |
|
152 } |
|
153 |
|
154 CFURLRef URL = CFURLCreateFromFSRef(NULL, &fref); |
|
155 if (URL == NULL) { |
|
156 return NSMacOSRomanStringEncoding; |
|
157 } |
|
158 |
|
159 NSString *path = [(NSURL *)URL path]; |
|
160 CFRelease(URL); |
|
161 |
|
162 // Get the lproj directory name |
|
163 path = [path stringByDeletingLastPathComponent]; |
|
164 if (![[path pathExtension] _webkit_isCaseInsensitiveEqualToString:@"lproj"]) { |
|
165 return NSMacOSRomanStringEncoding; |
|
166 } |
|
167 |
|
168 NSString *directoryName = [[path stringByDeletingPathExtension] lastPathComponent]; |
|
169 CFStringRef locale = CFLocaleCreateCanonicalLocaleIdentifierFromString(NULL, (CFStringRef)directoryName); |
|
170 if (locale == NULL) { |
|
171 return NSMacOSRomanStringEncoding; |
|
172 } |
|
173 |
|
174 LangCode lang; |
|
175 RegionCode region; |
|
176 error = LocaleStringToLangAndRegionCodes([(NSString *)locale UTF8String], &lang, ®ion); |
|
177 CFRelease(locale); |
|
178 if (error != noErr) { |
|
179 return NSMacOSRomanStringEncoding; |
|
180 } |
|
181 |
|
182 TextEncoding encoding; |
|
183 error = UpgradeScriptInfoToTextEncoding(kTextScriptDontCare, lang, region, NULL, &encoding); |
|
184 if (error != noErr) { |
|
185 return NSMacOSRomanStringEncoding; |
|
186 } |
|
187 |
|
188 return CFStringConvertEncodingToNSStringEncoding(encoding); |
|
189 } |
|
190 |
|
191 - (BOOL)_webkit_isCaseInsensitiveEqualToString:(NSString *)string |
|
192 { |
|
193 return [self compare:string options:(NSCaseInsensitiveSearch|NSLiteralSearch)] == NSOrderedSame; |
|
194 } |
|
195 |
|
196 -(BOOL)_webkit_hasCaseInsensitivePrefix:(NSString *)prefix |
|
197 { |
|
198 return [self rangeOfString:prefix options:(NSCaseInsensitiveSearch | NSAnchoredSearch)].location != NSNotFound; |
|
199 } |
|
200 |
|
201 -(BOOL)_webkit_hasCaseInsensitiveSuffix:(NSString *)suffix |
|
202 { |
|
203 return hasCaseInsensitiveSuffix(self, suffix); |
|
204 } |
|
205 |
|
206 -(BOOL)_webkit_hasCaseInsensitiveSubstring:(NSString *)substring |
|
207 { |
|
208 return hasCaseInsensitiveSubstring(self, substring); |
|
209 } |
|
210 |
|
211 -(NSString *)_webkit_filenameByFixingIllegalCharacters |
|
212 { |
|
213 return filenameByFixingIllegalCharacters(self); |
|
214 } |
|
215 |
|
216 -(NSString *)_webkit_stringByTrimmingWhitespace |
|
217 { |
|
218 NSMutableString *trimmed = [[self mutableCopy] autorelease]; |
|
219 CFStringTrimWhitespace((CFMutableStringRef)trimmed); |
|
220 return trimmed; |
|
221 } |
|
222 |
|
223 - (NSString *)_webkit_stringByCollapsingNonPrintingCharacters |
|
224 { |
|
225 NSMutableString *result = [NSMutableString string]; |
|
226 static NSCharacterSet *charactersToTurnIntoSpaces = nil; |
|
227 static NSCharacterSet *charactersToNotTurnIntoSpaces = nil; |
|
228 |
|
229 if (charactersToTurnIntoSpaces == nil) { |
|
230 NSMutableCharacterSet *set = [[NSMutableCharacterSet alloc] init]; |
|
231 [set addCharactersInRange:NSMakeRange(0x00, 0x21)]; |
|
232 [set addCharactersInRange:NSMakeRange(0x7F, 0x01)]; |
|
233 charactersToTurnIntoSpaces = [set copy]; |
|
234 [set release]; |
|
235 charactersToNotTurnIntoSpaces = [[charactersToTurnIntoSpaces invertedSet] retain]; |
|
236 } |
|
237 |
|
238 unsigned length = [self length]; |
|
239 unsigned position = 0; |
|
240 while (position != length) { |
|
241 NSRange nonSpace = [self rangeOfCharacterFromSet:charactersToNotTurnIntoSpaces |
|
242 options:0 range:NSMakeRange(position, length - position)]; |
|
243 if (nonSpace.location == NSNotFound) { |
|
244 break; |
|
245 } |
|
246 |
|
247 NSRange space = [self rangeOfCharacterFromSet:charactersToTurnIntoSpaces |
|
248 options:0 range:NSMakeRange(nonSpace.location, length - nonSpace.location)]; |
|
249 if (space.location == NSNotFound) { |
|
250 space.location = length; |
|
251 } |
|
252 |
|
253 if (space.location > nonSpace.location) { |
|
254 if (position != 0) { |
|
255 [result appendString:@" "]; |
|
256 } |
|
257 [result appendString:[self substringWithRange: |
|
258 NSMakeRange(nonSpace.location, space.location - nonSpace.location)]]; |
|
259 } |
|
260 |
|
261 position = space.location; |
|
262 } |
|
263 |
|
264 return result; |
|
265 } |
|
266 |
|
267 - (NSString *)_webkit_stringByCollapsingWhitespaceCharacters |
|
268 { |
|
269 NSMutableString *result = [[NSMutableString alloc] initWithCapacity:[self length]]; |
|
270 NSCharacterSet *spaces = [NSCharacterSet whitespaceAndNewlineCharacterSet]; |
|
271 static NSCharacterSet *notSpaces = nil; |
|
272 |
|
273 if (notSpaces == nil) |
|
274 notSpaces = [[spaces invertedSet] retain]; |
|
275 |
|
276 unsigned length = [self length]; |
|
277 unsigned position = 0; |
|
278 while (position != length) { |
|
279 NSRange nonSpace = [self rangeOfCharacterFromSet:notSpaces options:0 range:NSMakeRange(position, length - position)]; |
|
280 if (nonSpace.location == NSNotFound) |
|
281 break; |
|
282 |
|
283 NSRange space = [self rangeOfCharacterFromSet:spaces options:0 range:NSMakeRange(nonSpace.location, length - nonSpace.location)]; |
|
284 if (space.location == NSNotFound) |
|
285 space.location = length; |
|
286 |
|
287 if (space.location > nonSpace.location) { |
|
288 if (position != 0) |
|
289 [result appendString:@" "]; |
|
290 [result appendString:[self substringWithRange:NSMakeRange(nonSpace.location, space.location - nonSpace.location)]]; |
|
291 } |
|
292 |
|
293 position = space.location; |
|
294 } |
|
295 |
|
296 return [result autorelease]; |
|
297 } |
|
298 |
|
299 -(NSString *)_webkit_fixedCarbonPOSIXPath |
|
300 { |
|
301 NSFileManager *fileManager = [NSFileManager defaultManager]; |
|
302 if ([fileManager fileExistsAtPath:self]) { |
|
303 // Files exists, no need to fix. |
|
304 return self; |
|
305 } |
|
306 |
|
307 NSMutableArray *pathComponents = [[[self pathComponents] mutableCopy] autorelease]; |
|
308 NSString *volumeName = [pathComponents objectAtIndex:1]; |
|
309 if ([volumeName isEqualToString:@"Volumes"]) { |
|
310 // Path starts with "/Volumes", so the volume name is the next path component. |
|
311 volumeName = [pathComponents objectAtIndex:2]; |
|
312 // Remove "Volumes" from the path because it may incorrectly be part of the path (3163647). |
|
313 // We'll add it back if we have to. |
|
314 [pathComponents removeObjectAtIndex:1]; |
|
315 } |
|
316 |
|
317 if (!volumeName) { |
|
318 // Should only happen if self == "/", so this shouldn't happen because that always exists. |
|
319 return self; |
|
320 } |
|
321 |
|
322 if ([[fileManager _webkit_startupVolumeName] isEqualToString:volumeName]) { |
|
323 // Startup volume name is included in path, remove it. |
|
324 [pathComponents removeObjectAtIndex:1]; |
|
325 } else if ([[fileManager directoryContentsAtPath:@"/Volumes"] containsObject:volumeName]) { |
|
326 // Path starts with other volume name, prepend "/Volumes". |
|
327 [pathComponents insertObject:@"Volumes" atIndex:1]; |
|
328 } else { |
|
329 // It's valid. |
|
330 return self; |
|
331 } |
|
332 |
|
333 NSString *path = [NSString pathWithComponents:pathComponents]; |
|
334 |
|
335 if (![fileManager fileExistsAtPath:path]) { |
|
336 // File at canonicalized path doesn't exist, return original. |
|
337 return self; |
|
338 } |
|
339 |
|
340 return path; |
|
341 } |
|
342 |
|
343 @end |