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