1/** <title>NSAttributedStringAdditions</title> 2 3 <abstract>Categories which add capabilities to NSAttributedString</abstract> 4 5 Copyright (C) 1999 Free Software Foundation, Inc. 6 7 Author: Richard Frith-Macdonald <richard@brainstorm.co.uk> 8 Date: July 1999 9 Modifications: Fred Kiefer <FredKiefer@gmx.de> 10 Date: June 2000 11 12 This file is part of the GNUstep GUI Library. 13 14 This library is free software; you can redistribute it and/or 15 modify it under the terms of the GNU Lesser General Public 16 License as published by the Free Software Foundation; either 17 version 2 of the License, or (at your option) any later version. 18 19 This library is distributed in the hope that it will be useful, 20 but WITHOUT ANY WARRANTY; without even the implied warranty of 21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 22 Lesser General Public License for more details. 23 24 You should have received a copy of the GNU Lesser General Public 25 License along with this library; see the file COPYING.LIB. 26 If not, see <http://www.gnu.org/licenses/> or write to the 27 Free Software Foundation, 51 Franklin Street, Fifth Floor, 28 Boston, MA 02110-1301, USA. 29*/ 30 31#import <Foundation/NSArray.h> 32#import <Foundation/NSAutoreleasePool.h> 33#import <Foundation/NSBundle.h> 34#import <Foundation/NSCharacterSet.h> 35#import <Foundation/NSData.h> 36#import <Foundation/NSDebug.h> 37#import <Foundation/NSError.h> 38#import <Foundation/NSException.h> 39#import <Foundation/NSFileManager.h> 40#import <Foundation/NSPathUtilities.h> 41#import <Foundation/NSRange.h> 42#import <Foundation/NSSet.h> 43#import <Foundation/NSString.h> 44#import <Foundation/NSValue.h> 45 46#import "AppKit/NSAttributedString.h" 47#import "AppKit/NSDocumentController.h" 48#import "AppKit/NSParagraphStyle.h" 49#import "AppKit/NSPasteboard.h" 50#import "AppKit/NSTextAttachment.h" 51#import "AppKit/NSColor.h" 52#import "AppKit/NSFileWrapper.h" 53#import "AppKit/NSFont.h" 54#import "AppKit/NSFontDescriptor.h" 55#import "AppKit/NSFontManager.h" 56// For the colour name spaces 57#import "AppKit/NSGraphics.h" 58#import "AppKit/NSTextTable.h" 59 60#import "GNUstepGUI/GSTextConverter.h" 61#import "GSGuiPrivate.h" 62 63/* Cache class pointers to avoid the expensive lookup by string. */ 64static Class dictionaryClass = nil; 65static Class stringClass = nil; 66 67/* A character set containing characters that separate words. */ 68static NSCharacterSet *wordBreakCSet = nil; 69/* A character set containing characters that are legal within words. */ 70static NSCharacterSet *wordCSet = nil; 71/* Character sets containing characters that are white space and 72 not white space */ 73static NSCharacterSet *whiteCSet = nil; 74static NSCharacterSet *nonWhiteCSet = nil; 75/* A String containing the attachment character */ 76static NSString *attachmentString = nil; 77 78 79/* This function initializes all the previous cached values. */ 80static void cache_init_real(void) 81{ 82 NSMutableCharacterSet *m; 83 NSCharacterSet *cset; 84 unichar ch = NSAttachmentCharacter; 85 86 /* Initializes Class pointer cache */ 87 dictionaryClass = [NSDictionary class]; 88 stringClass = [NSString class]; 89 90 /* Initializes wordBreakCSet */ 91 m = [NSMutableCharacterSet new]; 92 cset = [NSCharacterSet whitespaceAndNewlineCharacterSet]; 93 [m formUnionWithCharacterSet: cset]; 94 cset = [NSCharacterSet punctuationCharacterSet]; 95 [m formUnionWithCharacterSet: cset]; 96 cset = [NSCharacterSet controlCharacterSet]; 97 [m formUnionWithCharacterSet: cset]; 98 cset = [NSCharacterSet illegalCharacterSet]; 99 [m formUnionWithCharacterSet: cset]; 100 [m addCharactersInString: @"<>"]; 101 [m removeCharactersInString: @"_"]; 102 wordBreakCSet = [m copy]; 103 RELEASE (m); 104 105 /* Initializes wordCSet */ 106 wordCSet = [[wordBreakCSet invertedSet] copy]; 107 108 /* Initializes white space and non-white space character sets */ 109 whiteCSet = [[NSCharacterSet whitespaceCharacterSet] copy]; 110 nonWhiteCSet = [[whiteCSet invertedSet] copy]; 111 112 /* Initializes attachmentString */ 113 attachmentString = [stringClass stringWithCharacters: &ch length: 1]; 114 RETAIN (attachmentString); 115} 116 117/* This inline function calls cache_init_real () the first time it is 118 invoked, and does nothing afterwards. Thus we get both speed 119 (cache_init is inlined and only compares a pointer to nil when the 120 cache has been initialized) and limit memory consumption (we are 121 not copying everywhere the real initialization code, which is in 122 cache_real_init (), which is not inlined.).*/ 123static inline void cache_init(void) 124{ 125 if (dictionaryClass == nil) 126 { 127 cache_init_real (); 128 } 129} 130 131/* Return the class that handles format from the first bundle it finds */ 132static 133Class converter_bundles(NSString *format, BOOL producer) 134{ 135 Class converter_class = Nil; 136 NSEnumerator *benum; 137 NSString *dpath; 138 139 /* Find the bundle paths */ 140 benum = [NSStandardLibraryPaths() objectEnumerator]; 141 while ((dpath = [benum nextObject])) 142 { 143 NSEnumerator *direnum; 144 NSString *path; 145 dpath = [dpath stringByAppendingPathComponent: @"Bundles"]; 146 dpath = [dpath stringByAppendingPathComponent: @"TextConverters"]; 147 if ([[NSFileManager defaultManager] fileExistsAtPath: dpath]) 148 direnum = [[NSFileManager defaultManager] enumeratorAtPath: dpath]; 149 else 150 direnum = nil; 151 while (direnum && (path = [direnum nextObject])) 152 { 153 Class bclass; 154 NSString *full_path; 155 NSBundle *aBundle; 156 if ([[path pathExtension] isEqual: @"bundle"] == NO) 157 continue; 158 full_path = [dpath stringByAppendingPathComponent: path]; 159 aBundle = [NSBundle bundleWithPath: full_path]; 160 if (aBundle && ((bclass = [aBundle principalClass]))) 161 { 162 if ([bclass respondsToSelector: 163 @selector(classForFormat: producer: )]) 164 { 165 converter_class = (Class)[bclass classForFormat: format 166 producer: producer]; 167 } 168 else 169 { 170 NSString *converter_name; 171 if (producer) 172 { 173 converter_name 174 = [format stringByAppendingString: @"Producer"]; 175 } 176 else 177 { 178 converter_name 179 = [format stringByAppendingString: @"Consumer"]; 180 } 181 converter_class = [aBundle classNamed: converter_name]; 182 } 183 } 184 if (converter_class) 185 break; 186 } 187 if (converter_class) 188 break; 189 } 190 return converter_class; 191} 192 193/* 194 Return a suitable converter for the text format supplied as argument. 195 If producer is YES a class capable of writing that format is returned, 196 otherwise a class able to read the format is returned. 197 */ 198static Class converter_class(NSString *format, BOOL producer) 199{ 200 static NSMutableDictionary *p_classes = nil; 201 static NSMutableDictionary *c_classes = nil; 202 Class found; 203 204 if (producer) 205 { 206 if (p_classes == nil) 207 p_classes = [NSMutableDictionary new]; 208 209 found = [p_classes objectForKey: format]; 210 if (found == Nil) 211 { 212 found = converter_bundles(format, producer); 213 if (found != Nil) 214 NSDebugLog(@"Found converter %@ for format %@", found, format); 215 if (found != Nil) 216 [p_classes setObject: found forKey: format]; 217 } 218 return found; 219 } 220 else 221 { 222 if (c_classes == nil) 223 c_classes = [NSMutableDictionary new]; 224 225 found = [c_classes objectForKey: format]; 226 if (found == Nil) 227 { 228 found = converter_bundles(format, producer); 229 if (found != Nil) 230 NSDebugLog(@"Found converter %@ for format %@", found, format); 231 if (found != Nil) 232 [c_classes setObject: found forKey: format]; 233 } 234 return found; 235 } 236 237 return Nil; 238} 239 240static inline NSError* 241create_error(int code, NSString* desc) 242{ 243 return [NSError errorWithDomain: @"NSAttributedString" 244 code: code 245 userInfo: [NSDictionary 246 dictionaryWithObjectsAndKeys: desc, 247 NSLocalizedDescriptionKey, nil]]; 248} 249 250@implementation NSAttributedString (AppKit) 251 252+ (NSArray *) textFileTypes 253{ 254 // FIXME: Apply service filters 255 return [self textUnfilteredFileTypes]; 256} 257 258+ (NSArray *) textPasteboardTypes 259{ 260 // FIXME: Apply service filters 261 return [self textUnfilteredPasteboardTypes]; 262} 263 264+ (NSArray *) textTypes 265{ 266 // FIXME: Apply service filters 267 return [self textUnfilteredTypes]; 268} 269 270+ (NSArray *) textUnfilteredFileTypes 271{ 272 return [NSArray arrayWithObjects: @"txt", @"rtf", @"rtfd", @"html", nil]; 273} 274 275+ (NSArray *) textUnfilteredPasteboardTypes 276{ 277 return [NSArray arrayWithObjects: NSStringPboardType, NSRTFPboardType, 278 NSRTFDPboardType, NSHTMLPboardType, nil]; 279} 280 281+ (NSArray *) textUnfilteredTypes 282{ 283 return [NSArray arrayWithObjects: @"public.plain-text", 284 @"public.rtf", 285 @"com.apple.rtfd", 286 @"public.html", 287 /* 288 @"public.xml", 289 @"com.apple.webarchive", 290 @"com.microsoft.word.doc", 291 @"com.microsoft.word.wordml", 292 @"org.openxmlformats.wordprocessingml.document", 293 @"org.oasis-open.opendocument.text", 294 @"com.apple.traditional-mac-plain-text", 295 */ 296 nil]; 297} 298 299+ (NSAttributedString *) attributedStringWithAttachment: 300 (NSTextAttachment *)attachment 301{ 302 NSDictionary *attributes; 303 304 cache_init (); 305 306 attributes = [dictionaryClass dictionaryWithObject: attachment 307 forKey: NSAttachmentAttributeName]; 308 309 return AUTORELEASE ([[self alloc] initWithString: attachmentString 310 attributes: attributes]); 311} 312 313- (BOOL) containsAttachments 314{ 315 NSRange aRange; 316 317 cache_init (); 318 319 aRange = [[self string] rangeOfString: attachmentString]; 320 321 if (aRange.length > 0) 322 { 323 return YES; 324 } 325 else 326 { 327 return NO; 328 } 329} 330 331- (NSDictionary *) fontAttributesInRange: (NSRange)range 332{ 333 NSDictionary *all; 334 static SEL sel = 0; 335 IMP objForKey; 336 id objects[8]; 337 id keys[8]; 338 int count = 0; 339 340 if (NSMaxRange(range) > [self length]) 341 { 342 [NSException raise: NSRangeException 343 format: @"RangeError in method -fontAttributesInRange: "]; 344 } 345 all = [self attributesAtIndex: range.location 346 effectiveRange: &range]; 347 348 if (sel == 0) 349 { 350 sel = @selector (objectForKey: ); 351 } 352 objForKey = [all methodForSelector: sel]; 353 354#define NSATT_GET_ATTRIBUTE(attribute) \ 355 keys[count] = attribute; \ 356 objects[count] = (*objForKey) (all, sel, keys[count]); \ 357 if (objects[count] != nil) count++; 358 359 NSATT_GET_ATTRIBUTE (NSFontAttributeName); 360 NSATT_GET_ATTRIBUTE (NSForegroundColorAttributeName); 361 NSATT_GET_ATTRIBUTE (NSBackgroundColorAttributeName); 362 NSATT_GET_ATTRIBUTE (NSUnderlineStyleAttributeName); 363 NSATT_GET_ATTRIBUTE (NSSuperscriptAttributeName); 364 NSATT_GET_ATTRIBUTE (NSBaselineOffsetAttributeName); 365 NSATT_GET_ATTRIBUTE (NSKernAttributeName); 366 NSATT_GET_ATTRIBUTE (NSLigatureAttributeName); 367 368#undef NSATT_GET_ATTRIBUTE 369 370 cache_init (); 371 372 return [dictionaryClass dictionaryWithObjects: objects 373 forKeys: keys 374 count: count]; 375} 376 377- (NSDictionary*) rulerAttributesInRange: (NSRange)range 378{ 379 id style; 380 381 cache_init (); 382 383 if (NSMaxRange (range) > [self length]) 384 { 385 [NSException raise: NSRangeException 386 format: @"RangeError in method -rulerAttributesInRange: "]; 387 } 388 389 style = [self attribute: NSParagraphStyleAttributeName 390 atIndex: range.location 391 effectiveRange: &range]; 392 393 if (style != nil) 394 { 395 return [dictionaryClass dictionaryWithObject: style 396 forKey: NSParagraphStyleAttributeName]; 397 } 398 399 return [dictionaryClass dictionary]; 400} 401 402- (NSUInteger) lineBreakByHyphenatingBeforeIndex: (NSUInteger)location 403 withinRange: (NSRange)aRange 404{ 405 // FIXME 406 return [self lineBreakBeforeIndex: location 407 withinRange: aRange]; 408} 409 410- (NSUInteger) lineBreakBeforeIndex: (NSUInteger)location 411 withinRange: (NSRange)aRange 412{ 413 NSString *str = [self string]; 414 NSUInteger length = [str length]; 415 NSRange scanRange; 416 NSRange startRange; 417 418 cache_init (); 419 420 if (NSMaxRange (aRange) > length || location > length) 421 { 422 [NSException raise: NSRangeException 423 format: @"RangeError in method -lineBreakBeforeIndex: withinRange: "]; 424 } 425 426 if (!NSLocationInRange (location, aRange)) 427 { 428 return NSNotFound; 429 } 430 431 scanRange = NSMakeRange (aRange.location, location - aRange.location); 432 startRange = [str rangeOfCharacterFromSet: wordBreakCSet 433 options: NSBackwardsSearch | NSLiteralSearch 434 range: scanRange]; 435 while (startRange.length > 0 && startRange.location > 0 436 && [str characterAtIndex: startRange.location] == '\'' 437 && [wordCSet characterIsMember: 438 [str characterAtIndex: startRange.location-1]]) 439 { 440 location = startRange.location - 1; 441 scanRange = NSMakeRange (0, location); 442 startRange = [str rangeOfCharacterFromSet: wordBreakCSet 443 options: NSBackwardsSearch|NSLiteralSearch range: scanRange]; 444 } 445 if (startRange.length == 0) 446 { 447 return NSNotFound; 448 } 449 else 450 { 451 return NSMaxRange (startRange); 452 } 453} 454 455- (NSRange) doubleClickAtIndex: (NSUInteger)location 456{ 457 NSString *str = [self string]; 458 NSUInteger length = [str length]; 459 NSRange scanRange; 460 NSRange startRange; 461 NSRange endRange; 462 NSCharacterSet *breakCSet; 463 464 cache_init (); 465 466 if (location > length) 467 { 468 [NSException raise: NSRangeException 469 format: @"RangeError in method -doubleClickAtIndex: "]; 470 } 471 472 /* 473 * Double clicking on a white space character selects all surrounding 474 * white space. Otherwise, if the location lies between words, a double 475 * click selects only the character actually clicked on. 476 */ 477 if ([whiteCSet characterIsMember: [str characterAtIndex: location]]) 478 { 479 breakCSet = nonWhiteCSet; 480 } 481 else if ([wordBreakCSet characterIsMember: [str characterAtIndex: location]]) 482 { 483 if (location == 0 || location == length - 1 484 || [str characterAtIndex: location] != '\'' 485 || ! [wordCSet characterIsMember: [str characterAtIndex: location - 1]] 486 || ! [wordCSet characterIsMember: [str characterAtIndex: location + 1]]) 487 { 488 return NSMakeRange(location, 1); 489 } 490 breakCSet = wordBreakCSet; 491 } 492 else 493 { 494 breakCSet = wordBreakCSet; 495 } 496 497 scanRange = NSMakeRange (0, location); 498 startRange = [str rangeOfCharacterFromSet: breakCSet 499 options: NSBackwardsSearch|NSLiteralSearch 500 range: scanRange]; 501 /* 502 * Don't treat single quotes embedded within a word as break characters. 503 * Note: The loop condition is always false when breakCSet==nonWhiteSetCSet. 504 */ 505 while (startRange.length > 0 506 && startRange.location > 0 && startRange.location < length - 1 507 && [str characterAtIndex: startRange.location] == '\'' 508 && [wordCSet characterIsMember: 509 [str characterAtIndex: startRange.location - 1]] 510 && [wordCSet characterIsMember: 511 [str characterAtIndex: startRange.location + 1]]) 512 { 513 location = startRange.location - 1; 514 scanRange = NSMakeRange (0, location); 515 startRange = [str rangeOfCharacterFromSet: wordBreakCSet 516 options: NSBackwardsSearch|NSLiteralSearch range: scanRange]; 517 } 518 519 scanRange = NSMakeRange (location, length - location); 520 endRange = [str rangeOfCharacterFromSet: breakCSet 521 options: NSLiteralSearch 522 range: scanRange]; 523 /* 524 * Don't treat single quotes embedded within a word as break characters. 525 * Note: The loop condition is always false when breakCSet==nonWhiteSetCSet. 526 */ 527 while (endRange.length > 0 528 && endRange.location > 0 && endRange.location < length - 1 529 && [str characterAtIndex: endRange.location] == '\'' 530 && [wordCSet characterIsMember: 531 [str characterAtIndex: endRange.location - 1]] 532 && [wordCSet characterIsMember: 533 [str characterAtIndex: endRange.location + 1]]) 534 { 535 location = endRange.location + 1; 536 scanRange = NSMakeRange (location, length - location); 537 endRange = [str rangeOfCharacterFromSet: wordBreakCSet 538 options: NSLiteralSearch range: scanRange]; 539 } 540 541 if (startRange.length == 0) 542 { 543 location = 0; 544 } 545 else 546 { 547 location = NSMaxRange (startRange); 548 } 549 550 if (endRange.length == 0) 551 { 552 length = length - location; 553 } 554 else 555 { 556 length = endRange.location - location; 557 } 558 return NSMakeRange (location, length); 559} 560 561- (NSUInteger) nextWordFromIndex: (NSUInteger)location 562 forward: (BOOL)isForward 563{ 564 NSString *str = [self string]; 565 NSUInteger length = [str length]; 566 NSRange range; 567 568 if (location > length) 569 { 570 [NSException raise: NSRangeException 571 format: @"RangeError in method -nextWordFromIndex: forward: "]; 572 } 573 574 /* Please note that we consider ' a valid word separator. This is 575 what Emacs does and is perfectly correct. If you want to change 576 the word separators, the right approach is to use a different 577 character set for word separators - the following code should be 578 unchanged whatever characters you use to separate words. */ 579 cache_init (); 580 581 if (isForward) 582 { 583 /* What we want to do is: move forward to the next chunk of 584 non-word separator characters, skip them all, and return the 585 location just after them. */ 586 587 if (location == length) 588 { 589 return length; 590 } 591 592 /* Move forward to the next non-word separator. */ 593 range = NSMakeRange (location, length - location); 594 range = [str rangeOfCharacterFromSet: wordCSet 595 options: NSLiteralSearch 596 range: range]; 597 if (range.location == NSNotFound) 598 { 599 return length; 600 } 601 /* rangeOfCharacterFromSet: options: range: only returns the range 602 of the first non-word-separator character ... we want to skip 603 them all! So we need to search again, this time for the 604 first word-separator character, and return the first such 605 character. */ 606 range = NSMakeRange (range.location, length - range.location); 607 range = [str rangeOfCharacterFromSet: wordBreakCSet 608 options: NSLiteralSearch 609 range: range]; 610 if (range.location == NSNotFound) 611 { 612 return length; 613 } 614 615 return range.location; 616 } 617 else 618 { 619 /* What we want to do is: move backward to the next chunk of 620 non-word separator characters, skip them all, and return the 621 location just at the beginning of the chunk. */ 622 623 if (location == 0) 624 { 625 return 0; 626 } 627 628 /* Move backward to the next non-word separator. */ 629 range = NSMakeRange (0, location); 630 range = [str rangeOfCharacterFromSet: wordCSet 631 options: NSBackwardsSearch | NSLiteralSearch 632 range: range]; 633 if (range.location == NSNotFound) 634 { 635 return 0; 636 } 637 638 /* rangeOfCharacterFromSet: options: range: only returns the range 639 of the first non-word-separator character ... we want to skip 640 them all! So we need to search again, this time for the 641 first word-separator character. */ 642 range = NSMakeRange (0, range.location); 643 range = [str rangeOfCharacterFromSet: wordBreakCSet 644 options: NSBackwardsSearch | NSLiteralSearch 645 range: range]; 646 if (range.location == NSNotFound) 647 { 648 return 0; 649 } 650 651 return NSMaxRange (range); 652 } 653} 654 655- (id) initWithRTFDFileWrapper: (NSFileWrapper *)wrapper 656 documentAttributes: (NSDictionary **)dict 657{ 658 return [self initWithRTFD: [wrapper serializedRepresentation] 659 documentAttributes: dict]; 660} 661 662- (id) initWithRTFD: (NSData*)data 663 documentAttributes: (NSDictionary**)dict 664{ 665 NSDictionary *options; 666 667 options = [NSDictionary dictionaryWithObject: NSRTFDTextDocumentType 668 forKey: NSDocumentTypeDocumentOption]; 669 return [self initWithData: data 670 options: options 671 documentAttributes: dict 672 error: NULL]; 673} 674 675- (id) initWithRTF: (NSData *)data 676 documentAttributes: (NSDictionary **)dict 677{ 678 NSDictionary *options; 679 680 options = [NSDictionary dictionaryWithObject: NSRTFTextDocumentType 681 forKey: NSDocumentTypeDocumentOption]; 682 return [self initWithData: data 683 options: options 684 documentAttributes: dict 685 error: NULL]; 686} 687 688- (id) initWithHTML: (NSData *)data 689 documentAttributes: (NSDictionary **)dict 690{ 691 return [self initWithHTML: data 692 baseURL: nil 693 documentAttributes: dict]; 694} 695 696- (id) initWithHTML: (NSData *)data 697 baseURL: (NSURL *)base 698 documentAttributes: (NSDictionary **)dict 699{ 700 NSDictionary *options = nil; 701 702 if (base != nil) 703 options = [NSDictionary dictionaryWithObject: base 704 forKey: NSBaseURLDocumentOption]; 705 706 return [self initWithHTML: data 707 options: options 708 documentAttributes: dict]; 709} 710 711- (id) initWithHTML: (NSData *)data 712 options: (NSDictionary *)options 713 documentAttributes: (NSDictionary **)dict 714{ 715 if (options == nil) 716 { 717 options = [NSDictionary dictionaryWithObject: NSHTMLTextDocumentType 718 forKey: NSDocumentTypeDocumentOption]; 719 } 720 else if ([options objectForKey: NSDocumentTypeDocumentOption] == nil) 721 { 722 options = AUTORELEASE([options mutableCopy]); 723 [(NSMutableDictionary*)options setObject: NSHTMLTextDocumentType 724 forKey: NSDocumentTypeDocumentOption]; 725 } 726 727 /* 728 The converter should support: 729 NSHTMLTextDocumentType 730 @"public.html" 731 @"html" 732 */ 733 return [self initWithData: data 734 options: options 735 documentAttributes: dict 736 error: NULL]; 737} 738 739- (id) initWithDocFormat: (NSData *)data 740 documentAttributes: (NSDictionary **)dict 741{ 742 NSDictionary *options; 743 744 options = [NSDictionary dictionaryWithObject: NSDocFormatTextDocumentType 745 forKey: NSDocumentTypeDocumentOption]; 746 return [self initWithData: data 747 options: options 748 documentAttributes: dict 749 error: NULL]; 750} 751 752- (id) initWithData: (NSData *)data 753 options: (NSDictionary *)options 754 documentAttributes: (NSDictionary **)dict 755 error: (NSError **)error 756{ 757 NSString *type = [options objectForKey: NSDocumentTypeDocumentOption]; 758 Class converter; 759 760 if (data == nil) 761 { 762 if (error) 763 *error = create_error(0, NSLocalizedString(@"No data specified for data loading.", 764 @"Error description")); 765 RELEASE(self); 766 return nil; 767 } 768 769 if (type == nil) 770 { 771 // Make sure this buffer is long enough 772 char prefix[14]; 773 NSUInteger l = [data length]; 774 if (l < sizeof(prefix)) 775 { 776 [data getBytes: prefix length: l]; 777 prefix[l] = 0; 778 } 779 else 780 { 781 [data getBytes: prefix length: sizeof(prefix)]; 782 } 783 784 // The list of file types below was derived from Apache's conf/magic file 785 // FIXME extend the list 786 if (strncmp(prefix, "{\\rtf", 5) == 0) 787 { 788 type = NSRTFTextDocumentType; 789 } 790 else if (strncasecmp(prefix, "<!doctype html", 14) == 0 || 791 strncasecmp(prefix, "<head", 5) == 0 || 792 strncasecmp(prefix, "<title", 6) == 0 || 793 strncasecmp(prefix, "<html", 5) == 0 || 794 strncmp(prefix, "<!--", 4) == 0 || 795 strncasecmp(prefix, "<h1", 3) == 0) 796 { 797 type = NSHTMLTextDocumentType; 798 } 799 } 800 if (type == nil) 801 { 802 type = NSPlainTextDocumentType; 803 } 804 805 converter = converter_class(type, NO); 806 if (converter != Nil) 807 { 808 NSAttributedString *new; 809 810 new = [converter 811 parseData: data 812 options: options 813 documentAttributes: dict 814 error: error 815 class: [self class]]; 816 // We do not return self but the newly created object 817 RELEASE(self); 818 return RETAIN(new); 819 } 820 else if ([type isEqualToString: NSPlainTextDocumentType] 821 || [type isEqualToString: @"public.plain-text"] 822 || [type isEqualToString: @"text"]) 823 { 824 // FIXME: Should we have a proper converter for this type? 825 NSStringEncoding encoding = [[options objectForKey: @"CharacterEncoding"] 826 intValue]; 827 NSDictionary *defaultAttrs = [options objectForKey: @"DefaultAttributes"]; 828 NSString *str; 829 830 if (encoding == GSUndefinedEncoding) 831 { 832 encoding = NSUTF8StringEncoding; 833 834 if ([data length] >= 2) 835 { 836 static const unichar byteOrderMark = 0xFEFF; 837 static const unichar byteOrderMarkSwapped = 0xFFFE; 838 const unichar firstChar = ((const unichar *)[data bytes])[0]; 839 if (firstChar == byteOrderMark 840 || firstChar == byteOrderMarkSwapped) 841 { 842 encoding = NSUnicodeStringEncoding; 843 } 844 } 845 } 846 847 if (dict != NULL) 848 { 849 *dict = [NSDictionary dictionaryWithObjectsAndKeys: 850 NSPlainTextDocumentType, NSDocumentTypeDocumentAttribute, 851 [NSNumber numberWithUnsignedInteger: encoding], NSCharacterEncodingDocumentAttribute, 852 nil]; 853 } 854 855 str = [[NSString alloc] initWithData: data 856 encoding: encoding]; 857 self = [self initWithString: str 858 attributes: defaultAttrs]; 859 RELEASE(str); 860 return self; 861 } 862 863 if (error) 864 *error = create_error(0, NSLocalizedString(@"Could not load data.", 865 @"Error description")); 866 RELEASE(self); 867 return nil; 868} 869 870- (id) initWithPath: (NSString *)path 871 documentAttributes: (NSDictionary **)dict 872{ 873 BOOL isDir = NO; 874 875 if (path == nil) 876 { 877 RELEASE (self); 878 return nil; 879 } 880 881 if ([[NSFileManager defaultManager] 882 fileExistsAtPath: path isDirectory: &isDir] && isDir) 883 { 884 // FIXME: This expects the file to be RTFD 885 NSFileWrapper *fw; 886 887 fw = [[NSFileWrapper alloc] initWithPath: path]; 888 AUTORELEASE (fw); 889 890 return [self initWithRTFDFileWrapper: fw documentAttributes: dict]; 891 } 892 else 893 { 894 return [self initWithURL: [NSURL fileURLWithPath: path] 895 documentAttributes: dict]; 896 } 897} 898 899- (id) initWithURL: (NSURL *)url 900documentAttributes: (NSDictionary **)dict 901{ 902 NSURL *baseURL = [url baseURL]; 903 NSDictionary *options = nil; 904 905 if (baseURL != nil) 906 { 907 [NSDictionary dictionaryWithObject: baseURL 908 forKey: NSBaseURLDocumentOption]; 909 } 910 911 return [self initWithURL: url 912 options: options 913 documentAttributes: dict 914 error: NULL]; 915} 916 917- (id) initWithURL: (NSURL *)url 918 options: (NSDictionary *)options 919documentAttributes: (NSDictionary **)dict 920 error: (NSError **)error 921{ 922 NSURL *baseURL; 923 NSData *data = [url resourceDataUsingCache: YES]; 924 925 if (data == nil) 926 { 927 if (error) 928 *error = create_error(0, NSLocalizedString(@"Could not load data from URL.", 929 @"Error description")); 930 RELEASE(self); 931 return nil; 932 } 933 934 // Pass on baseURL 935 baseURL = [url baseURL]; 936 if (baseURL != nil) 937 { 938 if (options == nil) 939 options = [NSDictionary dictionaryWithObject: baseURL 940 forKey: NSBaseURLDocumentOption]; 941 else if ([options objectForKey: NSBaseURLDocumentOption] == nil) 942 { 943 options = AUTORELEASE([options mutableCopy]); 944 [(NSMutableDictionary*)options setObject: baseURL 945 forKey: NSBaseURLDocumentOption]; 946 } 947 } 948 949 return [self initWithData: data 950 options: options 951 documentAttributes: dict 952 error: error]; 953} 954 955- (NSData *) RTFFromRange: (NSRange)range 956 documentAttributes: (NSDictionary *)dict 957{ 958 if (dict == nil) 959 { 960 dict = [NSDictionary dictionaryWithObject: NSRTFTextDocumentType 961 forKey: NSDocumentTypeDocumentOption]; 962 } 963 else if ([dict objectForKey: NSDocumentTypeDocumentOption] == nil) 964 { 965 dict = AUTORELEASE([dict mutableCopy]); 966 [(NSMutableDictionary*)dict setObject: NSRTFTextDocumentType 967 forKey: NSDocumentTypeDocumentOption]; 968 } 969 970 return [self dataFromRange: range 971 documentAttributes: dict 972 error: NULL]; 973} 974 975- (NSData *) RTFDFromRange: (NSRange)range 976 documentAttributes: (NSDictionary *)dict 977{ 978 if (dict == nil) 979 { 980 dict = [NSDictionary dictionaryWithObject: NSRTFDTextDocumentType 981 forKey: NSDocumentTypeDocumentOption]; 982 } 983 else if ([dict objectForKey: NSDocumentTypeDocumentOption] == nil) 984 { 985 dict = AUTORELEASE([dict mutableCopy]); 986 [(NSMutableDictionary*)dict setObject: NSRTFDTextDocumentType 987 forKey: NSDocumentTypeDocumentOption]; 988 } 989 990 return [self dataFromRange: range 991 documentAttributes: dict 992 error: NULL]; 993} 994 995- (NSFileWrapper *) RTFDFileWrapperFromRange: (NSRange)range 996 documentAttributes: (NSDictionary *)dict 997{ 998 return AUTORELEASE([[NSFileWrapper alloc] 999 initWithSerializedRepresentation: 1000 [self RTFDFromRange: range 1001 documentAttributes: dict]]); 1002} 1003 1004- (NSData *) docFormatFromRange: (NSRange)range 1005 documentAttributes: (NSDictionary *)dict 1006{ 1007 if (dict == nil) 1008 { 1009 dict = [NSDictionary dictionaryWithObject: NSDocFormatTextDocumentType 1010 forKey: NSDocumentTypeDocumentOption]; 1011 } 1012 else if ([dict objectForKey: NSDocumentTypeDocumentOption] == nil) 1013 { 1014 dict = AUTORELEASE([dict mutableCopy]); 1015 [(NSMutableDictionary*)dict setObject: NSDocFormatTextDocumentType 1016 forKey: NSDocumentTypeDocumentOption]; 1017 } 1018 1019 return [self dataFromRange: range 1020 documentAttributes: dict 1021 error: NULL]; 1022} 1023 1024- (NSData *) dataFromRange: (NSRange)range 1025 documentAttributes: (NSDictionary *)dict 1026 error: (NSError **)error 1027{ 1028 NSString *type = [dict objectForKey: NSDocumentTypeDocumentOption]; 1029 Class converter; 1030 1031 if (type == nil) 1032 { 1033 if (error) 1034 *error = create_error(0, NSLocalizedString(@"No type specified for data.", 1035 @"Error description")); 1036 return nil; 1037 } 1038 1039 converter = converter_class(type, YES); 1040 if (converter != Nil) 1041 { 1042 return [converter 1043 produceDataFrom: 1044 [self attributedSubstringFromRange: range] 1045 documentAttributes: dict 1046 error: error]; 1047 } 1048 else if ([type isEqualToString: NSPlainTextDocumentType] 1049 || [type isEqualToString: @"public.plain-text"] 1050 || [type isEqualToString: @"text"]) 1051 { 1052 NSStringEncoding encoding = [[dict objectForKey: @"CharacterEncoding"] 1053 intValue]; 1054 1055 if (!encoding) 1056 encoding = [NSString defaultCStringEncoding]; 1057 return [[self string] dataUsingEncoding: encoding]; 1058 } 1059 1060 if (error) 1061 *error = create_error(0, NSLocalizedString(@"Could not create data for type.", 1062 @"Error description")); 1063 return nil; 1064} 1065 1066- (NSFileWrapper *) fileWrapperFromRange: (NSRange)range 1067 documentAttributes: (NSDictionary *)dict 1068 error: (NSError **)error 1069{ 1070 NSFileWrapper *wrapper; 1071 NSData *data; 1072 1073 data = [self dataFromRange: range 1074 documentAttributes: dict 1075 error: error]; 1076 if (data != nil) 1077 { 1078 // FIXME: This wont work for directory bundles. 1079 wrapper = [[NSFileWrapper alloc] initRegularFileWithContents: data]; 1080 return AUTORELEASE(wrapper); 1081 } 1082 1083 if (error) 1084 *error = create_error(0, NSLocalizedString(@"Could not create data for type.", 1085 @"Error description")); 1086 1087 return nil; 1088} 1089 1090- (NSInteger) itemNumberInTextList: (NSTextList *)list 1091 atIndex: (NSUInteger)location 1092{ 1093 NSParagraphStyle *style = [self attribute: NSParagraphStyleAttributeName 1094 atIndex: location 1095 effectiveRange: NULL]; 1096 if (style != nil) 1097 { 1098 NSArray *textLists = [style textLists]; 1099 1100 if (textLists != nil) 1101 { 1102 return [textLists indexOfObject: list]; 1103 } 1104 } 1105 1106 return NSNotFound; 1107} 1108 1109- (NSRange) rangeOfTextBlock: (NSTextBlock *)block 1110 atIndex: (NSUInteger)location 1111{ 1112 NSRange effRange; 1113 NSParagraphStyle *style = [self attribute: NSParagraphStyleAttributeName 1114 atIndex: location 1115 effectiveRange: &effRange]; 1116 if (style != nil) 1117 { 1118 NSArray *textBlocks = [style textBlocks]; 1119 1120 if ((textBlocks != nil) && [textBlocks containsObject: block]) 1121 { 1122 NSRange newEffRange; 1123 NSUInteger len = [self length]; 1124 1125 while ((effRange.location > 0) && style && textBlocks) 1126 { 1127 style = [self attribute: NSParagraphStyleAttributeName 1128 atIndex: effRange.location - 1 1129 effectiveRange: &newEffRange]; 1130 if (style != nil) 1131 { 1132 textBlocks = [style textBlocks]; 1133 1134 if ((textBlocks != nil) && [textBlocks containsObject: block]) 1135 { 1136 effRange.location = newEffRange.location; 1137 effRange.length += newEffRange.length; 1138 } 1139 } 1140 } 1141 1142 while (NSMaxRange(effRange) < len && style && textBlocks) 1143 { 1144 style = [self attribute: NSParagraphStyleAttributeName 1145 atIndex: NSMaxRange(effRange) 1146 effectiveRange: &newEffRange]; 1147 if (style != nil) 1148 { 1149 textBlocks = [style textBlocks]; 1150 1151 if ((textBlocks != nil) && [textBlocks containsObject: block]) 1152 { 1153 effRange.length += newEffRange.length; 1154 } 1155 } 1156 } 1157 1158 return effRange; 1159 } 1160 } 1161 1162 return NSMakeRange(NSNotFound, 0); 1163} 1164 1165- (NSRange) rangeOfTextList: (NSTextList *)list 1166 atIndex: (NSUInteger)location 1167{ 1168 NSRange effRange; 1169 NSParagraphStyle *style = [self attribute: NSParagraphStyleAttributeName 1170 atIndex: location 1171 effectiveRange: &effRange]; 1172 if (style != nil) 1173 { 1174 NSArray *textLists = [style textLists]; 1175 1176 if ((textLists != nil) && [textLists containsObject: list]) 1177 { 1178 NSRange newEffRange; 1179 NSUInteger len = [self length]; 1180 1181 while ((effRange.location > 0) && style && textLists) 1182 { 1183 style = [self attribute: NSParagraphStyleAttributeName 1184 atIndex: effRange.location - 1 1185 effectiveRange: &newEffRange]; 1186 if (style != nil) 1187 { 1188 textLists = [style textLists]; 1189 1190 if ((textLists != nil) && [textLists containsObject: list]) 1191 { 1192 effRange.location = newEffRange.location; 1193 effRange.length += newEffRange.length; 1194 } 1195 } 1196 } 1197 1198 while (NSMaxRange(effRange) < len && style && textLists) 1199 { 1200 style = [self attribute: NSParagraphStyleAttributeName 1201 atIndex: NSMaxRange(effRange) 1202 effectiveRange: &newEffRange]; 1203 if (style != nil) 1204 { 1205 textLists = [style textLists]; 1206 1207 if ((textLists != nil) && [textLists containsObject: list]) 1208 { 1209 effRange.length += newEffRange.length; 1210 } 1211 } 1212 } 1213 1214 return effRange; 1215 } 1216 } 1217 1218 return NSMakeRange(NSNotFound, 0); 1219} 1220 1221static inline 1222BOOL containsTable(NSArray *textBlocks, NSTextTable *table) 1223{ 1224 NSEnumerator *benum = [textBlocks objectEnumerator]; 1225 NSTextTableBlock *block; 1226 1227 while ((block = [benum nextObject])) 1228 { 1229 if ([table isEqual: [block table]]) 1230 { 1231 return YES; 1232 } 1233 } 1234 return NO; 1235} 1236 1237- (NSRange) rangeOfTextTable: (NSTextTable *)table 1238 atIndex: (NSUInteger)location 1239{ 1240 NSRange effRange; 1241 NSParagraphStyle *style = [self attribute: NSParagraphStyleAttributeName 1242 atIndex: location 1243 effectiveRange: &effRange]; 1244 if (style != nil) 1245 { 1246 NSArray *textBlocks = [style textBlocks]; 1247 1248 if ((textBlocks != nil) && containsTable(textBlocks, table)) 1249 { 1250 NSRange newEffRange; 1251 NSUInteger len = [self length]; 1252 1253 while ((effRange.location > 0) && style && textBlocks) 1254 { 1255 style = [self attribute: NSParagraphStyleAttributeName 1256 atIndex: effRange.location - 1 1257 effectiveRange: &newEffRange]; 1258 if (style != nil) 1259 { 1260 textBlocks = [style textBlocks]; 1261 1262 if ((textBlocks != nil) && containsTable(textBlocks, table)) 1263 { 1264 effRange.location = newEffRange.location; 1265 effRange.length += newEffRange.length; 1266 } 1267 } 1268 } 1269 1270 while (NSMaxRange(effRange) < len && style && textBlocks) 1271 { 1272 style = [self attribute: NSParagraphStyleAttributeName 1273 atIndex: NSMaxRange(effRange) 1274 effectiveRange: &newEffRange]; 1275 if (style != nil) 1276 { 1277 textBlocks = [style textBlocks]; 1278 1279 if ((textBlocks != nil) && containsTable(textBlocks, table)) 1280 { 1281 effRange.length += newEffRange.length; 1282 } 1283 } 1284 } 1285 1286 return effRange; 1287 } 1288 } 1289 1290 return NSMakeRange(NSNotFound, 0); 1291} 1292 1293@end 1294 1295@implementation NSMutableAttributedString (AppKit) 1296- (void) superscriptRange: (NSRange)range 1297{ 1298 id value; 1299 int sValue; 1300 NSRange effRange; 1301 1302 if (NSMaxRange (range) > [self length]) 1303 { 1304 [NSException raise: NSRangeException 1305 format: @"RangeError in method -superscriptRange: "]; 1306 } 1307 1308 // We take the value from the first character and use it for the whole range 1309 value = [self attribute: NSSuperscriptAttributeName 1310 atIndex: range.location 1311 effectiveRange: &effRange]; 1312 1313 if (value != nil) 1314 { 1315 sValue = [value intValue] + 1; 1316 } 1317 else 1318 { 1319 sValue = 1; 1320 } 1321 1322 1323 [self addAttribute: NSSuperscriptAttributeName 1324 value: [NSNumber numberWithInt: sValue] 1325 range: range]; 1326} 1327 1328- (void) subscriptRange: (NSRange)range 1329{ 1330 id value; 1331 int sValue; 1332 NSRange effRange; 1333 1334 if (NSMaxRange (range) > [self length]) 1335 { 1336 [NSException raise: NSRangeException 1337 format: @"RangeError in method -subscriptRange: "]; 1338 } 1339 1340 // We take the value form the first character and use it for the whole range 1341 value = [self attribute: NSSuperscriptAttributeName 1342 atIndex: range.location 1343 effectiveRange: &effRange]; 1344 1345 if (value != nil) 1346 { 1347 sValue = [value intValue] - 1; 1348 } 1349 else 1350 { 1351 sValue = -1; 1352 } 1353 1354 [self addAttribute: NSSuperscriptAttributeName 1355 value: [NSNumber numberWithInt: sValue] 1356 range: range]; 1357} 1358 1359- (void) unscriptRange: (NSRange)range 1360{ 1361 if (NSMaxRange (range) > [self length]) 1362 { 1363 [NSException raise: NSRangeException 1364 format: @"RangeError in method -unscriptRange: "]; 1365 } 1366 1367 [self removeAttribute: NSSuperscriptAttributeName 1368 range: range]; 1369} 1370 1371- (void) applyFontTraits: (NSFontTraitMask)traitMask 1372 range: (NSRange)range 1373{ 1374 NSFont *font; 1375 NSUInteger loc = range.location; 1376 NSRange effRange; 1377 NSFontManager *fm = [NSFontManager sharedFontManager]; 1378 1379 if (NSMaxRange (range) > [self length]) 1380 { 1381 [NSException raise: NSRangeException 1382 format: @"RangeError in method -applyFontTraits: range: "]; 1383 } 1384 1385 while (loc < NSMaxRange (range)) 1386 { 1387 font = [self attribute: NSFontAttributeName 1388 atIndex: loc 1389 effectiveRange: &effRange]; 1390 1391 if (font != nil) 1392 { 1393 font = [fm convertFont: font 1394 toHaveTrait: traitMask]; 1395 1396 if (font != nil) 1397 { 1398 [self addAttribute: NSFontAttributeName 1399 value: font 1400 range: NSIntersectionRange (effRange, range)]; 1401 } 1402 } 1403 loc = NSMaxRange(effRange); 1404 } 1405} 1406 1407- (void) setAlignment: (NSTextAlignment)alignment 1408 range: (NSRange)range 1409{ 1410 id value; 1411 NSUInteger loc = range.location; 1412 1413 if (NSMaxRange(range) > [self length]) 1414 { 1415 [NSException raise: NSRangeException 1416 format: @"RangeError in method -setAlignment: range: "]; 1417 } 1418 1419 while (loc < NSMaxRange(range)) 1420 { 1421 BOOL copiedStyle = NO; 1422 NSRange effRange; 1423 NSRange newRange; 1424 1425 value = [self attribute: NSParagraphStyleAttributeName 1426 atIndex: loc 1427 effectiveRange: &effRange]; 1428 newRange = NSIntersectionRange (effRange, range); 1429 1430 if (value == nil) 1431 { 1432 value = [NSMutableParagraphStyle defaultParagraphStyle]; 1433 } 1434 else 1435 { 1436 value = [value mutableCopy]; 1437 copiedStyle = YES; 1438 } 1439 1440 [value setAlignment: alignment]; 1441 1442 [self addAttribute: NSParagraphStyleAttributeName 1443 value: value 1444 range: newRange]; 1445 if (copiedStyle == YES) 1446 { 1447 RELEASE(value); 1448 } 1449 loc = NSMaxRange (effRange); 1450 } 1451} 1452 1453- (void) fixAttributesInRange: (NSRange)range 1454{ 1455 [self fixFontAttributeInRange: range]; 1456 [self fixParagraphStyleAttributeInRange: range]; 1457 [self fixAttachmentAttributeInRange: range]; 1458} 1459 1460static NSString *lastFont = nil; 1461static NSCharacterSet *lastSet = nil; 1462static NSMutableDictionary *cachedCSets = nil; 1463 1464- (NSFont*)_substituteFontWithName: (NSString*)fontName 1465 font: (NSFont*)baseFont 1466{ 1467 return [[NSFontManager sharedFontManager] convertFont: baseFont 1468 toFace: fontName]; 1469} 1470 1471- (NSFont*)_substituteFontFor: (unichar)uchar 1472 font: (NSFont*)baseFont 1473 fromList: (NSArray *)fonts 1474{ 1475 NSUInteger count; 1476 NSUInteger i; 1477 1478 if (cachedCSets == nil) 1479 { 1480 cachedCSets = [NSMutableDictionary new]; 1481 } 1482 1483 count = [fonts count]; 1484 for (i = 0; i < count; i++) 1485 { 1486 NSFont *newFont; 1487 NSString *fName; 1488 NSCharacterSet *newSet; 1489 1490 fName = [fonts objectAtIndex: i]; 1491 newSet = [cachedCSets objectForKey: fName]; 1492 if (newSet == nil) 1493 { 1494 newFont = [self _substituteFontWithName: fName font: baseFont]; 1495 newSet = [newFont coveredCharacterSet]; 1496 if ((newSet != nil) && ([cachedCSets count] < 10)) 1497 { 1498 [cachedCSets setObject: newSet forKey: fName]; 1499 } 1500 } 1501 else 1502 { 1503 newFont = nil; 1504 } 1505 1506 if ([newSet characterIsMember: uchar]) 1507 { 1508 ASSIGN(lastFont, fName); 1509 ASSIGN(lastSet, newSet); 1510 if (newFont != nil) 1511 { 1512 return newFont; 1513 } 1514 else 1515 { 1516 return [self _substituteFontWithName: fName font: baseFont]; 1517 } 1518 } 1519 } 1520 1521 return nil; 1522} 1523 1524- (NSFontDescriptor*)_substituteFontDescriptorFor: (unichar)uchar 1525{ 1526 NSString *chars = [NSString stringWithCharacters: &uchar length: 1]; 1527 1528 // If we cannot get a string from a single unichar, it most likely is part of a surrogate pair 1529 if (nil != chars) 1530 { 1531 NSCharacterSet *requiredCharacterSet = [NSCharacterSet characterSetWithCharactersInString: chars]; 1532 NSDictionary *fontAttributes = [NSDictionary dictionaryWithObjectsAndKeys: requiredCharacterSet, NSFontCharacterSetAttribute, nil]; 1533 NSSet *mandatoryKeys = [NSSet setWithObjects: NSFontCharacterSetAttribute, nil]; 1534 NSFontDescriptor *fd = [NSFontDescriptor fontDescriptorWithFontAttributes: fontAttributes]; 1535 return [fd matchingFontDescriptorWithMandatoryKeys: mandatoryKeys]; 1536 } 1537 else 1538 { 1539 return nil; 1540 } 1541} 1542 1543- (NSFont*)_substituteFontFor: (unichar)uchar font: (NSFont*)baseFont 1544{ 1545 NSFont *subFont; 1546 NSFontDescriptor *descriptor; 1547 1548 // Caching one font may lead to the selected substitution font not being 1549 // from the prefered list, although there is one there with this character. 1550 if (lastSet && [lastSet characterIsMember: uchar]) 1551 { 1552 return [self _substituteFontWithName: lastFont font: baseFont]; 1553 } 1554 1555 subFont = [self _substituteFontFor: uchar 1556 font: baseFont 1557 fromList: [NSFont preferredFontNames]]; 1558 if (subFont != nil) 1559 { 1560 return subFont; 1561 } 1562 1563 // Fast way with font descriptors 1564 descriptor = [self _substituteFontDescriptorFor: uchar]; 1565 if (descriptor != nil) 1566 { 1567 NSCharacterSet *newSet = [descriptor objectForKey: NSFontCharacterSetAttribute]; 1568 if ([newSet characterIsMember: uchar]) 1569 { 1570 NSString *fName = [descriptor objectForKey: NSFontFamilyAttribute]; 1571 1572 ASSIGN(lastFont, fName); 1573 ASSIGN(lastSet, newSet); 1574 return [self _substituteFontWithName: fName font: baseFont]; 1575 } 1576 } 1577 1578 1579 subFont = [self _substituteFontFor: uchar font: baseFont fromList: 1580 [[NSFontManager sharedFontManager] availableFonts]]; 1581 if (subFont != nil) 1582 { 1583 return subFont; 1584 } 1585 1586 return nil; 1587} 1588 1589- (void) fixFontAttributeInRange: (NSRange)range 1590{ 1591 NSString *string; 1592 NSFont *font = nil; 1593 NSCharacterSet *charset = nil; 1594 NSRange fontRange = NSMakeRange(NSNotFound, 0); 1595 NSUInteger i; 1596 NSUInteger lastMax; 1597 NSUInteger start; 1598 unichar chars[64]; 1599 CREATE_AUTORELEASE_POOL(pool); 1600 NSCharacterSet *controlset = [NSCharacterSet controlCharacterSet]; 1601 1602 if (NSMaxRange (range) > [self length]) 1603 { 1604 [NSException raise: NSRangeException 1605 format: @"RangeError in method -fixFontAttributeInRange: "]; 1606 } 1607 // Check for each character if it is supported by the 1608 // assigned font 1609 1610 /* 1611 Note that this needs to be done on a script basis. Per-character checks 1612 are difficult to do at all, don't give reasonable results, and would have 1613 really poor performance. 1614 */ 1615 string = [self string]; 1616 lastMax = range.location; 1617 start = lastMax; 1618 for (i = range.location; i < NSMaxRange(range); i++) 1619 { 1620 unichar uchar; 1621 1622 if (i >= lastMax) 1623 { 1624 NSUInteger dist; 1625 1626 start = lastMax; 1627 dist = MIN(64, NSMaxRange(range) - start); 1628 lastMax = start + dist; 1629 [string getCharacters: chars range: NSMakeRange(start, dist)]; 1630 } 1631 uchar = chars[i - start]; 1632 if (uchar >= 0xd800 && uchar <= 0xdfff) 1633 { 1634 // Currently we don't handle surrogate pairs 1635 continue; 1636 } 1637 1638 if (!NSLocationInRange(i, fontRange)) 1639 { 1640 font = [self attribute: NSFontAttributeName 1641 atIndex: i 1642 effectiveRange: &fontRange]; 1643 1644 /* If we don't have an attribute for NSFontAttributeName, 1645 ** we take a default font so we can carry on with the 1646 ** substitution. 1647 */ 1648 if (nil == font) 1649 { 1650 font = [NSFont userFontOfSize: 0.0]; 1651 } 1652 1653 charset = [font coveredCharacterSet]; 1654 } 1655 1656 if (charset != nil && ![charset characterIsMember: uchar] 1657 && (uchar != NSAttachmentCharacter) 1658 && ![controlset characterIsMember: uchar]) 1659 { 1660 // Find a replacement font 1661 NSFont *subFont; 1662 1663 subFont = [self _substituteFontFor: uchar font: font]; 1664 if (subFont != nil) 1665 { 1666 // Set substitution font permanently 1667 [self addAttribute: NSFontAttributeName 1668 value: subFont 1669 range: NSMakeRange(i, 1)]; 1670 } 1671 } 1672 } 1673 1674 [pool drain]; 1675} 1676 1677- (void) fixParagraphStyleAttributeInRange: (NSRange)range 1678{ 1679 NSString *str = [self string]; 1680 NSUInteger loc = range.location; 1681 NSRange r; 1682 1683 if (NSMaxRange (range) > [self length]) 1684 { 1685 [NSException raise: NSRangeException 1686 format: @"RangeError in method -fixParagraphStyleAttributeInRange: "]; 1687 } 1688 1689 while (loc < NSMaxRange (range)) 1690 { 1691 NSParagraphStyle *style; 1692 NSRange found; 1693 NSUInteger end; 1694 1695 /* Extend loc to take in entire paragraph if necessary. */ 1696 r = [str lineRangeForRange: NSMakeRange (loc, 1)]; 1697 end = NSMaxRange (r); 1698 1699 /* Get the style in effect at the paragraph start. */ 1700 style = [self attribute: NSParagraphStyleAttributeName 1701 atIndex: r.location 1702 longestEffectiveRange: &found 1703 inRange: r]; 1704 if (style == nil) 1705 { 1706 /* No style found at the beginning of paragraph. found is 1707 the range without the style set. */ 1708 if ((NSMaxRange (found) + 1) < end) 1709 { 1710 /* There is a paragraph style for part of the paragraph. Set 1711 this style for the entire paragraph. 1712 1713 Since NSMaxRange(found) + 1 is outside the longest effective 1714 range for the nil style, it must be non-nil. 1715 */ 1716 style = [self attribute: NSParagraphStyleAttributeName 1717 atIndex: NSMaxRange(found) + 1 1718 effectiveRange: NULL]; 1719 [self addAttribute: NSParagraphStyleAttributeName 1720 value: style 1721 range: r]; 1722 } 1723 else 1724 { 1725 /* All the paragraph without a style ... too bad, fixup 1726 the whole paragraph using the default paragraph style. */ 1727 [self addAttribute: NSParagraphStyleAttributeName 1728 value: [NSParagraphStyle defaultParagraphStyle] 1729 range: r]; 1730 } 1731 } 1732 else 1733 { 1734 if (NSMaxRange (found) < end) 1735 { 1736 /* Not the whole paragraph has the same style ... add 1737 the style found at the beginning to the remainder of 1738 the paragraph. */ 1739 found.location = NSMaxRange (found); 1740 found.length = end - found.location; 1741 [self addAttribute: NSParagraphStyleAttributeName 1742 value: style 1743 range: found]; 1744 } 1745 } 1746 1747 /* Move on to the next paragraph. */ 1748 loc = end; 1749 } 1750} 1751 1752- (void) fixAttachmentAttributeInRange: (NSRange)range 1753{ 1754 NSString *string = [self string]; 1755 NSUInteger location = range.location; 1756 NSUInteger end = NSMaxRange (range); 1757 1758 cache_init (); 1759 1760 if (end > [self length]) 1761 { 1762 [NSException raise: NSRangeException 1763 format: @"RangeError in method -fixAttachmentAttributeInRange: "]; 1764 } 1765 1766 // Check for attachments with the wrong character 1767 while (location < end) 1768 { 1769 NSDictionary *attr; 1770 NSRange eRange; 1771 1772 attr = [self attributesAtIndex: location effectiveRange: &eRange]; 1773 if ([attr objectForKey: NSAttachmentAttributeName] != nil) 1774 { 1775 unichar buf[eRange.length]; 1776 NSUInteger pos = 0; 1777 NSUInteger start = eRange.location; 1778 1779 // Leave only one character with the attachment 1780 [string getCharacters: buf range: eRange]; 1781 while (pos < eRange.length && buf[pos] != NSAttachmentCharacter) 1782 pos++; 1783 if (pos) 1784 [self removeAttribute: NSAttachmentAttributeName 1785 range: NSMakeRange (start, pos)]; 1786 pos++; 1787 if (pos < eRange.length) 1788 [self removeAttribute: NSAttachmentAttributeName 1789 range: NSMakeRange (start + pos, eRange.length - pos)]; 1790 } 1791 location = NSMaxRange (eRange); 1792 } 1793 1794 // Check for attachment characters without attachments 1795 location = range.location; 1796 string = [self string]; 1797 while (location < end) 1798 { 1799 NSRange eRange = [string rangeOfString: attachmentString 1800 options: NSLiteralSearch 1801 range: NSMakeRange (location, end - location)]; 1802 NSTextAttachment *attachment; 1803 1804 if (!eRange.length) 1805 break; 1806 1807 attachment = [self attribute: NSAttachmentAttributeName 1808 atIndex: eRange.location 1809 effectiveRange: NULL]; 1810 1811 if (attachment == nil) 1812 { 1813 [self deleteCharactersInRange: NSMakeRange (eRange.location, 1)]; 1814 eRange.length--; 1815 end--; 1816 // Need to reset this after every character change 1817 string = [self string]; 1818 } 1819 1820 location = NSMaxRange (eRange); 1821 } 1822} 1823 1824- (void) updateAttachmentsFromPath: (NSString *)path 1825{ 1826 NSString *string = [self string]; 1827 NSUInteger location = 0; 1828 NSUInteger end = [string length]; 1829 1830 cache_init (); 1831 1832 while (location < end) 1833 { 1834 NSRange range = [string rangeOfString: attachmentString 1835 options: NSLiteralSearch 1836 range: NSMakeRange (location, end - location)]; 1837 NSTextAttachment *attachment; 1838 NSFileWrapper *fileWrapper; 1839 1840 if (!range.length) 1841 break; 1842 1843 attachment = [self attribute: NSAttachmentAttributeName 1844 atIndex: range.location 1845 effectiveRange: NULL]; 1846 fileWrapper = [attachment fileWrapper]; 1847 1848 // FIXME: Is this the correct thing to do? 1849 [fileWrapper updateFromPath: [path stringByAppendingPathComponent: 1850 [fileWrapper filename]]]; 1851 location = NSMaxRange (range); 1852 } 1853} 1854 1855- (BOOL) readFromURL: (NSURL *)url 1856 options: (NSDictionary *)options 1857 documentAttributes: (NSDictionary**)documentAttributes 1858{ 1859 return [self readFromURL: url 1860 options: options 1861 documentAttributes: documentAttributes 1862 error: NULL]; 1863} 1864 1865- (BOOL) readFromURL: (NSURL *)url 1866 options: (NSDictionary *)options 1867 documentAttributes: (NSDictionary **)documentAttributes 1868 error: (NSError **)error 1869{ 1870 NSAttributedString *attr; 1871 1872 attr = [[NSAttributedString alloc] 1873 initWithURL: url 1874 options: options 1875 documentAttributes: documentAttributes 1876 error: error]; 1877 if (attr != nil) 1878 { 1879 [self setAttributedString: attr]; 1880 RELEASE(attr); 1881 return YES; 1882 } 1883 1884 return NO; 1885} 1886 1887- (BOOL) readFromData: (NSData *)data 1888 options: (NSDictionary *)options 1889 documentAttributes: (NSDictionary **)documentAttributes 1890{ 1891 return [self readFromData: data 1892 options: options 1893 documentAttributes: documentAttributes 1894 error: NULL]; 1895} 1896 1897- (BOOL) readFromData: (NSData *)data 1898 options: (NSDictionary *)options 1899 documentAttributes: (NSDictionary **)documentAttributes 1900 error: (NSError **)error 1901{ 1902 NSAttributedString *attr; 1903 1904 attr = [[NSAttributedString alloc] 1905 initWithData: data 1906 options: options 1907 documentAttributes: documentAttributes 1908 error: error]; 1909 if (attr) 1910 { 1911 [self setAttributedString: attr]; 1912 RELEASE(attr); 1913 return YES; 1914 } 1915 1916 return NO; 1917} 1918 1919- (void) setBaseWritingDirection: (NSWritingDirection)writingDirection 1920 range: (NSRange)range 1921{ 1922 id value; 1923 NSUInteger loc = range.location; 1924 1925 if (NSMaxRange(range) > [self length]) 1926 { 1927 [NSException raise: NSRangeException 1928 format: @"RangeError in method -setBaseWritingDirection: range: "]; 1929 } 1930 1931 while (loc < NSMaxRange(range)) 1932 { 1933 BOOL copiedStyle = NO; 1934 NSRange effRange; 1935 NSRange newRange; 1936 1937 value = [self attribute: NSParagraphStyleAttributeName 1938 atIndex: loc 1939 effectiveRange: &effRange]; 1940 newRange = NSIntersectionRange(effRange, range); 1941 1942 if (value == nil) 1943 { 1944 value = [NSMutableParagraphStyle defaultParagraphStyle]; 1945 } 1946 else 1947 { 1948 value = [value mutableCopy]; 1949 copiedStyle = YES; 1950 } 1951 1952 [value setBaseWritingDirection: writingDirection]; 1953 1954 [self addAttribute: NSParagraphStyleAttributeName 1955 value: value 1956 range: newRange]; 1957 if (copiedStyle == YES) 1958 { 1959 RELEASE(value); 1960 } 1961 loc = NSMaxRange(effRange); 1962 } 1963} 1964 1965@end 1966