/** NSLayoutManager The text layout manager class Copyright (C) 1999 Free Software Foundation, Inc. Author: Jonathan Gapen Date: July 1999 Author: Michael Hanni Date: August 1999 Author: Richard Frith-Macdonald Date: January 2001 This file is part of the GNUstep GUI Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "GSSimpleLayoutManager.h" #include #include #define USE_GLYPHS 0 #define ALL_CHECKS 0 #define BOTH (NSTextStorageEditedCharacters | NSTextStorageEditedAttributes) #if ALL_CHECKS static void missmatch(SEL s) { NSLog(@"Missmatch in %@", NSStringFromSelector(s)); } #endif /* * Glyph attributes known to the layout manager. */ typedef enum { GSGlyphDrawsOutsideLineFragment, GSGlyphIsNotShown, GSGlyphGeneration, GSGlyphInscription, } GSGlyphAttributes; /* * A structure to hold information about a glyph in the glyph stream * NB. This structure should be no more than 32 bits, so it can fit * in as a GSIArray element. */ typedef struct { unsigned offset:24; // characters in from start of chunk unsigned drawsOutsideLineFragment:1; // glyph bigger than fragment? unsigned isNotShown:1; // glyph invisible (space, tab etc) unsigned inscription:3; // NSGlyphInscription info unsigned generation:3; // Other attributes } GSGlyphAttrs; /* * We need a fast array that can store - * pointers, objects, glyphs (long) and attributes. */ #define GSI_ARRAY_TYPES GSUNION_PTR|GSUNION_OBJ|GSUNION_INT|GSUNION_LONG #define GSI_ARRAY_TYPE GSGlyphAttrs /* * We handle retain/release explicitly, so we can use GSIArrays to hold * non-object values. */ #define GSI_ARRAY_NO_RELEASE 1 #define GSI_ARRAY_NO_RETAIN 1 #ifdef GSIArray #undef GSIArray #endif #include /* * The glyph attributes within a chunk must be ordered by their offset fields, * so we can use a binary search to find the item for a particular offset. */ static NSComparisonResult offsetSort(GSIArrayItem i0, GSIArrayItem i1) { if ((i0.ext).offset < (i1.ext).offset) return NSOrderedAscending; else if ((i0.ext).offset > (i1.ext).offset) return NSOrderedDescending; else return NSOrderedSame; } /* * Glyph management implementation notes (January 2001) * Author - Richard Frith-Macdonald * * An NSLayoutManager object maintains a 'glyph stream' which contains the * actual symbols to be displayed in text. This glyph stream is conceptually * an array of glyphs and certain attributes. * * Each glyph has an associated index of the corresponding character in the * text storage object. Since more than one character may map to a single * glyphs, the character index for a glyph is the index of the first * character corresponing to the glyph. Since more than one glyph may map * on to the same character, adjacent glyphs in the glyph stream may have * the same character index. * * Other attributes of a glyph include flags to say whether the glyph is to * be drawn or not, and how it should be layed out with respect to the * preceeding glyph in the stream (allowing for overstrike etc). * * Since the state of the text storage object may change, glyphs may be * deleted from the stream from time to time, leaving a situation where * not all characters in the text storage have corresponding glyphs in * the glyph stream. This state is called a 'gap'. We maintain an array * of the locations of the gaps in the glyph stream. When we attempt to * access a glyph, we must generate new glyphs from the text storage to * fill any gaps in the glyphs stream and insert them into the stream. * * * The glyph stream is actually implemented as an array of 'chunks' - * where a chunk contains an array of glyphs, and array of the glyph * attributes, and the glyph and character indices of the first glyph * in the chunk. The remaining character/glyph indices are calculated * as offsets from the first glyph in the chunk. * * This implementation is used for speed of manipulation of very large * documents - modifications to a single chunk may have their effects * to some degree localised to that chunk. * * Invariants ... * 1. The glyph stream is a continuous array of glyphs ranging from 0 up. * 2. The character index of a glyph in the glyph stream is greater than * or equal to that of the glyph that preceeds it. * 3. The gap array contains glyph indices in numeric order and where no * index exceeds the length of the glyph stream. * 4. The glyph stream consists of at least one chunk whose glyph index * is zero. */ /* * Structure to handle the storage of the glyph stream. * This is done as an array of chunks. * Each chunk contains an array of glyphs and corresponding attributes. */ typedef struct { unsigned charIndex; // Index of character at start of chunk unsigned glyphIndex; // Index of glyph at start of chunk GSIArray_t glyphs; // Array of glyphs. GSIArray_t attrs; // Array of attributes. } GSGlyphChunk; /* * The glyph chunks must be ordered by their charIndex offset fields, * so we can use a binary search to find the item for a particular * character index. */ static NSComparisonResult charIndexSort(GSIArrayItem i0, GSIArrayItem i1) { if (((GSGlyphChunk*)(i0.ptr))->charIndex < (((GSGlyphChunk*)(i1.ptr))->charIndex)) return NSOrderedAscending; else if (((GSGlyphChunk*)(i0.ptr))->charIndex > (((GSGlyphChunk*)(i1.ptr))->charIndex)) return NSOrderedDescending; else return NSOrderedSame; } /* * The glyph chunks must be ordered by their glyphIndex offset fields, * so we can use a binary search to find the item for a particular * glyph index. */ static NSComparisonResult glyphIndexSort(GSIArrayItem i0, GSIArrayItem i1) { if (((GSGlyphChunk*)(i0.ptr))->glyphIndex < (((GSGlyphChunk*)(i1.ptr))->glyphIndex)) return NSOrderedAscending; else if (((GSGlyphChunk*)(i0.ptr))->glyphIndex > (((GSGlyphChunk*)(i1.ptr))->glyphIndex)) return NSOrderedDescending; else return NSOrderedSame; } /* * Glyph management functions. */ static GSGlyphChunk* GSCreateGlyphChunk(unsigned glyphIndex, unsigned charIndex) { GSGlyphChunk *chunk; chunk = NSZoneMalloc(NSDefaultMallocZone(), sizeof(GSGlyphChunk)); chunk->charIndex = charIndex; chunk->glyphIndex = glyphIndex; GSIArrayInitWithZoneAndCapacity(&chunk->glyphs, NSDefaultMallocZone(), 8); GSIArrayInitWithZoneAndCapacity(&chunk->attrs, NSDefaultMallocZone(), 8); return chunk; } static void GSDestroyGlyphChunk(GSGlyphChunk *chunk) { GSIArrayClear(&chunk->glyphs); GSIArrayClear(&chunk->attrs); NSZoneFree(NSDefaultMallocZone(), chunk); } static unsigned GSChunkForCharIndex(GSIArray chunks, unsigned charIndex) { unsigned pos; GSGlyphChunk tmp; tmp.charIndex = charIndex; pos = GSIArrayInsertionPosition(chunks, (GSIArrayItem)(void*)&tmp, charIndexSort); /* * pos is the index of the next chunk *after* the one we want, * unless we want something in the very first chunk. */ if (pos > 0) { pos--; } return pos; } static unsigned GSChunkForGlyphIndex(GSIArray chunks, unsigned glyphIndex) { unsigned pos; GSGlyphChunk tmp; tmp.glyphIndex = glyphIndex; pos = GSIArrayInsertionPosition(chunks, (GSIArrayItem)(void*)&tmp, glyphIndexSort); /* * pos is the index of the next chunk *after* the one we want, * unless we want something in the very first chunk. */ NSCAssert(pos > 0, @"No glyph chunks present"); if (pos > 0) { pos--; } return pos; } /* * Medium level functions for accessing and manipulating glyphs. */ typedef struct { @defs(NSLayoutManager) } *lmDefs; #define glyphChunks ((GSIArray)_glyphData) #define _chunks ((GSIArray)(((lmDefs)lm)->_glyphData)) #define _chunk ((GSGlyphChunk*)(((lmDefs)lm)->_currentGlyphs)) #define _gindex (((lmDefs)lm)->_glyphIndex) #define _cindex (((lmDefs)lm)->_chunkIndex) #define _offset (((lmDefs)lm)->_glyphOffset) #define _gaps ((GSIArray)(((lmDefs)lm)->_glyphGaps)) static GSGlyphAttrs _Attrs(NSLayoutManager *lm); static BOOL _Back(NSLayoutManager *lm); static unsigned _CharEnd(NSLayoutManager *lm); static unsigned _CharIndex(NSLayoutManager *lm); static NSGlyph _Glyph(NSLayoutManager *lm); static unsigned _GlyphEnd(NSLayoutManager *lm); static unsigned _GlyphIndex(NSLayoutManager *lm); static BOOL _JumpToChar(NSLayoutManager *lm, unsigned charIndex); static BOOL _JumpToGlyph(NSLayoutManager *lm, unsigned glyphIndex); static void _SetAttrs(NSLayoutManager *lm, GSGlyphAttrs a); static void _SetGlyph(NSLayoutManager *lm, NSGlyph g); static BOOL _Step(NSLayoutManager *lm); /* * Move 'current' glyph index back one place in glyph stream. * return NO on failure (start of stream). */ static inline BOOL _Back(NSLayoutManager *lm) { if (_offset > 0) { _offset--; _gindex--; return YES; } else if (_cindex > 0) { _cindex--; _chunk = (GSGlyphChunk*)GSIArrayItemAtIndex(_chunks, _cindex).ptr; _offset = GSIArrayCount(&_chunk->glyphs) - 1; _gindex--; return YES; } else { return NO; } } /* * Move 'current' glyph index forward one place in glyph stream. * return NO on failure (end of stream). */ static inline BOOL _Step(NSLayoutManager *lm) { if (_offset < GSIArrayCount(&_chunk->glyphs) - 1) { _offset++; _gindex++; return YES; } else { if (_cindex < GSIArrayCount(_chunks) - 1) { _cindex++; _chunk = (GSGlyphChunk*)GSIArrayItemAtIndex(_chunks, _cindex).ptr; _offset = 0; _gindex++; return YES; } else { return NO; } } } /* * Adjust the character indices for all the glyphs from the specified * location onwards. Leave the current glyphs set to the 'from' location. */ static void _Adjust(NSLayoutManager *lm, unsigned from, int lengthChange) { if (_JumpToGlyph(lm, from) == YES) { GSGlyphChunk *chunk = _chunk; unsigned index = _cindex; unsigned offset = _offset; /* * Adjust character offsets for all glyphs in this chunk. */ if (offset > 0) { while (offset < GSIArrayCount(&chunk->glyphs)) { GSGlyphAttrs a; a = GSIArrayItemAtIndex(&chunk->attrs, offset).ext; a.offset += lengthChange; GSIArraySetItemAtIndex(&chunk->attrs, (GSIArrayItem)a, offset); offset--; } index++; } /* * Now adjust character offsets for remaining chunks. */ while (index < GSIArrayCount(_chunks)) { chunk = (GSGlyphChunk*)GSIArrayItemAtIndex(_chunks, index).ptr; index++; chunk->charIndex += lengthChange; } } } /* * Return the current glyph attributes. */ static inline GSGlyphAttrs _Attrs(NSLayoutManager *lm) { return GSIArrayItemAtIndex(&_chunk->attrs, _offset).ext; } /* * Return the index of the character immediately beyond the last * generated glyph. */ static inline unsigned _CharEnd(NSLayoutManager *lm) { unsigned i; i = GSIArrayCount(_chunks); while (i-- > 0) { GSGlyphChunk *c; unsigned j; c = (GSGlyphChunk*)GSIArrayItemAtIndex(_chunks, i).ptr; j = GSIArrayCount(&c->attrs); if (j-- > 0) { return c->charIndex + (GSIArrayItemAtIndex(&c->attrs, j).ext).offset + 1; } } return 0; } /* * Return the glyph index immediately beyond the last generated glyph. */ static inline unsigned _GlyphEnd(NSLayoutManager *lm) { unsigned i; i = GSIArrayCount(_chunks); while (i-- > 0) { GSGlyphChunk *c; unsigned j; c = (GSGlyphChunk*)GSIArrayItemAtIndex(_chunks, i).ptr; j = GSIArrayCount(&c->glyphs); if (j > 0) { return c->glyphIndex + j; } } return 0; } /* * return the character index of the current glyph. */ static inline unsigned _CharIndex(NSLayoutManager *lm) { return _chunk->charIndex + (GSIArrayItemAtIndex(&_chunk->attrs, _offset).ext).offset; } /* * Return the value of the current glyph. */ static inline NSGlyph _Glyph(NSLayoutManager *lm) { return (NSGlyph)GSIArrayItemAtIndex(&_chunk->glyphs, _offset).ulng; } /* * return the index of the current glyph. */ static inline unsigned _GlyphIndex(NSLayoutManager *lm) { return _chunk->glyphIndex + _offset; } /* * Locate the first glyph corresponding to the specified character index * and make it the current glyph. */ static BOOL _JumpToChar(NSLayoutManager *lm, unsigned charIndex) { GSGlyphAttrs tmp; GSGlyphChunk *c; unsigned i; unsigned o; unsigned co; i = GSChunkForCharIndex(_chunks, charIndex); c = (GSGlyphChunk*)GSIArrayItemAtIndex(_chunks, i).ptr; tmp.offset = charIndex - c->charIndex; o = GSIArrayInsertionPosition(&c->attrs, (GSIArrayItem)tmp, offsetSort); if (o == 0) { return NO; // Insertion position not found. } o--; /* * Check the character index of this glyph to see if it matches the * character index we were asked for. If it doesn't we have probably * failed to find a glyph matching the character. */ co = (GSIArrayItemAtIndex(&c->attrs, o).ext).offset; if (co + c->charIndex != charIndex) { if ([((lmDefs)lm)->_textStorage length] > charIndex) { NSRange r; r = [[((lmDefs)lm)->_textStorage string] rangeOfComposedCharacterSequenceAtIndex: charIndex]; if (r.length > 0 && r.location == co + c->charIndex) { /* * The requested character is part of a composed character * sequence whose first character maps on to the glyph we found. */ _chunk = c; _cindex = i; _offset = o; _gindex = c->glyphIndex + o; return YES; } } return NO; } /* * Locate the *first* glyph for this character index... */ while (o > 0 && (GSIArrayItemAtIndex(&c->attrs, o-1).ext).offset == co) { o--; } _chunk = c; _cindex = i; _offset = o; _gindex = c->glyphIndex + o; return YES; } /* * Make the specified glyph index the current glyph */ static BOOL _JumpToGlyph(NSLayoutManager *lm, unsigned glyphIndex) { GSGlyphChunk *c; unsigned i; unsigned o; /* * Optimise for glyph index zero ... easy to find. */ if (glyphIndex == 0) { c = (GSGlyphChunk*)GSIArrayItemAtIndex(_chunks, 0).ptr; if (GSIArrayCount(&c->glyphs) > 0) { _chunk = c; _cindex = 0; _offset = 0; _gindex = 0; return YES; } return NO; } i = GSChunkForGlyphIndex(_chunks, glyphIndex); c = (GSGlyphChunk*)GSIArrayItemAtIndex(_chunks, i).ptr; o = glyphIndex - c->glyphIndex; if (o < GSIArrayCount(&c->glyphs)) { _chunk = c; _cindex = i; _offset = o; _gindex = glyphIndex; return YES; } else { return NO; } } /* * Set the attributes of the current glyph */ static inline void _SetAttrs(NSLayoutManager *lm, GSGlyphAttrs a) { GSIArraySetItemAtIndex(&_chunk->attrs, (GSIArrayItem)a, _offset); } /* * Set the value of the current glyph */ static inline void _SetGlyph(NSLayoutManager *lm, NSGlyph g) { GSIArraySetItemAtIndex(&_chunk->glyphs, (GSIArrayItem)g, _offset); } #if USE_GLYPHS static void _Sane(NSLayoutManager *lm) { unsigned lastGlyph = 0; unsigned lastChar = 0; unsigned pos; /* * Check gaps. */ for (pos = 0; pos < GSIArrayCount(_gaps); pos++) { unsigned val = GSIArrayItemAtIndex(_gaps, pos).ulng; NSCAssert(val > lastGlyph || (val == 0 && pos == 0), NSInternalInconsistencyException); lastGlyph = val; } NSCAssert(GSIArrayCount(_chunks) > 0, NSInternalInconsistencyException); lastGlyph = 0; for (pos = 0; pos < GSIArrayCount(_chunks); pos++) { GSGlyphChunk *chunk; unsigned count; chunk = (GSGlyphChunk*)GSIArrayItemAtIndex(_chunks, pos).ptr; NSCAssert(chunk->glyphIndex == (pos == 0 ? 0 : lastGlyph+1), NSInternalInconsistencyException); NSCAssert(chunk->charIndex >= lastChar, NSInternalInconsistencyException); count = GSIArrayCount(&chunk->glyphs); if (count > 0) { GSGlyphAttrs a; unsigned i; for (i = 0; i < count; i++) { a = GSIArrayItemAtIndex(&chunk->attrs, i).ext; NSCAssert(chunk->charIndex + a.offset >= lastChar, NSInternalInconsistencyException); lastChar = chunk->charIndex + a.offset; } lastGlyph = chunk->glyphIndex + count - 1; } } } static void _GLog(NSLayoutManager *lm, SEL _cmd) { #if ALL_CHECKS unsigned pos; /* * Check gaps. */ fprintf(stderr, "%s, %x\ngaps (%u) - ", _cmd ? sel_get_name(_cmd) : "", (unsigned)lm, GSIArrayCount(_gaps)); for (pos = 0; pos < GSIArrayCount(_gaps); pos++) { unsigned val = GSIArrayItemAtIndex(_gaps, pos).ulng; fprintf(stderr, " %u", val); } fprintf(stderr, "\n"); fprintf(stderr, "chunks (%u) -\n", GSIArrayCount(_chunks)); for (pos = 0; pos < GSIArrayCount(_chunks); pos++) { GSGlyphChunk *chunk; unsigned count; chunk = (GSGlyphChunk*)GSIArrayItemAtIndex(_chunks, pos).ptr; count = GSIArrayCount(&chunk->glyphs); fprintf(stderr, " glyphs (%u) - gi %d, ci %d\n", count, chunk->glyphIndex, chunk->charIndex); if (count > 0) { GSGlyphAttrs a; unsigned i; for (i = 0; i < count; i++) { a = GSIArrayItemAtIndex(&chunk->attrs, i).ext; fprintf(stderr, " %4d %4d %c", chunk->glyphIndex + i, chunk->charIndex + a.offset, (char)GSIArrayItemAtIndex(&chunk->glyphs, i).ulng); } fprintf(stderr, "\n"); } } #endif } #else static inline void _Sane(NSLayoutManager *lm) { } static inline void _GLog(NSLayoutManager *lm, SEL _cmd) { } #endif @interface NSLayoutManager (Private) - (void) _doLayout; - (int) _rebuildLayoutForTextContainer: (NSTextContainer*)aContainer startingAtGlyphIndex: (int)glyphIndex; @end @implementation NSLayoutManager + (id) allocWithZone: (NSZone*)z { // Return a simple layout manager as this is the only working subclass if (self == [NSLayoutManager class]) { return [GSSimpleLayoutManager allocWithZone: z]; } else { return NSAllocateObject (self, 0, z); } } /** * Sets up this instance. Finds the shared * NSGlyphGenerator and the shared default NSTypesetter. * The NSLayoutManager starts off without a NSTextStorage */ - (id) init { self = [super init]; if (self != nil) { GSIArray a; _backgroundLayout = YES; _delegate = nil; _textContainers = [[NSMutableArray alloc] initWithCapacity: 2]; /* * Initialise glyph storage and ivars to contain 'current' glyph * location information. */ a = NSZoneMalloc(NSDefaultMallocZone(), sizeof(GSIArray_t)); GSIArrayInitWithZoneAndCapacity(a, NSDefaultMallocZone(), 8); _glyphData = a; _currentGlyphs = GSCreateGlyphChunk(0, 0); GSIArrayInsertItem(glyphChunks, (GSIArrayItem)_currentGlyphs, 0); _chunkIndex = 0; _glyphOffset = 0; /* * Initialise storage of gaps in the glyph stream. * Initially there are no gaps in the stream. */ a = NSZoneMalloc(NSDefaultMallocZone(), sizeof(GSIArray_t)); GSIArrayInitWithZoneAndCapacity(a, NSDefaultMallocZone(), 8); _glyphGaps = a; } return self; } - (void) dealloc { unsigned i; /* We check that the _glyphData and _glyphGaps are not NULL so that * we can dealloc an object which has not been -init (some * regression tests need it). */ /* * Release all glyph chunk information. */ if (_glyphData != NULL) { i = GSIArrayCount(glyphChunks); while (i-- > 0) { GSGlyphChunk *chunk; chunk = (GSGlyphChunk*)(GSIArrayItemAtIndex(glyphChunks, i).ptr); GSDestroyGlyphChunk(chunk); } GSIArrayEmpty(glyphChunks); NSZoneFree(NSDefaultMallocZone(), _glyphData); } if (_glyphGaps != NULL) { GSIArrayEmpty((GSIArray)_glyphGaps); NSZoneFree(NSDefaultMallocZone(), _glyphGaps); } RELEASE (_textContainers); [super dealloc]; } /** * The set method generally should not be called directly, but you may * want to override it. Used to get and set the text storage. The * set method is called by the NSTextStorage's * addTextStorageObserver/removeTextStorageObserver methods. */ - (void) setTextStorage: (NSTextStorage*)aTextStorage { unsigned int length; NSRange aRange; /* * Mark the entire existing text storage as invalid. */ length = [_textStorage length]; aRange = NSMakeRange(0, length); [self textStorage: _textStorage edited: BOTH range: aRange changeInLength: -length invalidatedRange: aRange]; /* * Make a note of the new text storage object, but don't retain it. * The text storage is owning us - it retains us. */ _textStorage = aTextStorage; length = [aTextStorage length]; aRange = NSMakeRange (0, length); // force complete re - layout [self textStorage: aTextStorage edited: BOTH range: aRange changeInLength: length invalidatedRange: aRange]; } - (NSTextStorage*) textStorage { return _textStorage; } /** * This method should be used instead of the primitive * -setTextStorage: if you need to replace a NSLayoutManager's * NSTextStorage with a new one leaving the rest of the web intact. * This method deals with all the work of making sure the * NSLayoutManager doesn't get deallocated and transferring all the * NSLayoutManager s on the old NSTextStorage to the new one. */ - (void) replaceTextStorage: (NSTextStorage*)newTextStorage { NSArray *layoutManagers = [_textStorage layoutManagers]; NSEnumerator *enumerator = [layoutManagers objectEnumerator]; NSLayoutManager *object; /* Remove layout managers from old NSTextStorage object and add them to the new one. NSTextStorage's addLayoutManager invokes NSLayoutManager's setTextStorage method automatically, and that includes self. */ while ((object = (NSLayoutManager*)[enumerator nextObject]) != nil) { [_textStorage removeLayoutManager: object]; [newTextStorage addLayoutManager: object]; } } /* * Setting text containers */ - (NSArray*) textContainers { return _textContainers; } /** * Add a container to the end of the array. Must invalidate layout of * all glyphs after the previous last container (ie glyphs that were * not previously laid out because they would not fit anywhere). */ - (void) addTextContainer: (NSTextContainer*)obj { if ([_textContainers indexOfObjectIdenticalTo: obj] == NSNotFound) { int i; [_textContainers addObject: obj]; [obj setLayoutManager: self]; // TODO: Invalidate layout _textContainersCount++; /* NB: We do not retain this here ! It's already retained in the array. */ _firstTextView = [(NSTextContainer *)[_textContainers objectAtIndex: 0] textView]; for (i = 0; i < _textContainersCount; i++) { NSTextView *tv = [[_textContainers objectAtIndex: i] textView]; [tv _updateMultipleTextViews]; } } } /** * Insert a container into the array before the container at index. * Must invalidate layout of all glyphs in the containers from the one * previously at index to the last container. */ - (void) insertTextContainer: (NSTextContainer*)aTextContainer atIndex: (unsigned)index { int i; [_textContainers insertObject: aTextContainer atIndex: index]; _textContainersCount++; _firstTextView = [(NSTextContainer *)[_textContainers objectAtIndex: 0] textView]; for (i = 0; i < _textContainersCount; i++) { NSTextView *tv = [[_textContainers objectAtIndex: i] textView]; [tv _updateMultipleTextViews]; } // TODO: Invalidate layout } /** * Removes the container at index from the array. Must invalidate * layout of all glyphs in the container being removed and any * containers which come after it. */ - (void) removeTextContainerAtIndex: (unsigned)index { int i; [_textContainers removeObjectAtIndex: index]; _textContainersCount--; if (_textContainersCount > 0) { _firstTextView = [(NSTextContainer *)[_textContainers objectAtIndex: 0] textView]; } else { _firstTextView = nil; } for (i = 0; i < _textContainersCount; i++) { NSTextView *tv = [[_textContainers objectAtIndex: i] textView]; [tv _updateMultipleTextViews]; } } /** * This removes all glyphs for the old character range, adjusts the * character indices of all the subsequent glyphs by the change in * length, and invalidates the new character range. If * actualCharRange is non-NULL it will be set to the actual range * invalidated after any necessary expansion. */ - (void) invalidateGlyphsForCharacterRange: (NSRange)aRange changeInLength: (int)lengthChange actualCharacterRange: (NSRange*)actualRange { NSRange cRange; NSRange gRange; _GLog(self,_cmd); if (actualRange != 0) { *actualRange = cRange; } if (aRange.length == 0) { return; // Empty ... nothing to do. } if (aRange.location >= _CharEnd(self)) { return; // No glyphs generated for that character index. } gRange = [self glyphRangeForCharacterRange: aRange actualCharacterRange: &cRange]; if (actualRange != 0) { *actualRange = cRange; } if (gRange.length == 0) { return; // Nothing to do. } [self deleteGlyphsInRange: gRange]; /* * Now adjust character locations for glyphs if necessary. */ _Adjust(self, gRange.location, lengthChange); /* * Unless the 'lengthChange' accounts for the entire character range * or the deleted glyphs were at the end of the glyph stream, we must * note the presence of a gap. */ if (cRange.length + lengthChange > -1 && gRange.location < _GlyphEnd(self)) { unsigned count = GSIArrayCount((GSIArray)_glyphGaps); if (count == 0) { GSIArrayInsertItem((GSIArray)_glyphGaps, (GSIArrayItem)gRange.location, 0); } else { unsigned pos; for (pos = 0; pos < count; pos++) { unsigned val; val = GSIArrayItemAtIndex((GSIArray)_glyphGaps, pos).ulng; if (val == gRange.location) { break; // Gap already marked here. } if (val > gRange.location) { GSIArrayInsertItem((GSIArray)_glyphGaps, (GSIArrayItem)gRange.location, pos); break; } } } } // FIXME - should invalidate the character range ... but what does that mean? _GLog(self,_cmd); _Sane(self); } /** * This invalidates the layout information (glyph location and * rotation) for the given range of characters. If flag is YES then * this range is marked as a hard layout invalidation. If NO, then * the invalidation is soft. A hard invalid layout range indicates * that layout information must be completely recalculated no matter * what. A soft invalid layout range means that there is already old * layout info for the range in question, and if the NSLayoutManager * is smart enough to figure out how to avoid doing the complete * relayout, it may perform any optimization available. If * actualCharRange is non-NULL it will be set to the actual range * invalidated after any necessary expansion. */ - (void) invalidateLayoutForCharacterRange: (NSRange)aRange isSoft: (BOOL)flag actualCharacterRange: (NSRange*)actualRange { [self _doLayout]; } /** * Invalidates display for the character range given. * Unlaid parts of the range are remembered and will definitely be * redisplayed at some point later when the layout is available. * Does not cause layout. */ - (void) invalidateDisplayForCharacterRange: (NSRange)aRange { /* FIXME */ } /** * Invalidates display for the glyph range given. * Any part of the range that does not yet * have glyphs generated is ignored. * Does not cause layout. */ - (void) invalidateDisplayForGlyphRange: (NSRange)aRange { /* FIXME */ } /** * Invalidates layout of all glyphs in container and all subsequent * containers. */ - (void) textContainerChangedGeometry: (NSTextContainer*)aContainer { // find the first character in that text container NSRange aRange = [self glyphRangeForTextContainer: aContainer]; unsigned first = aRange.location; // invalidate the layout from here on [self invalidateLayoutForCharacterRange: NSMakeRange(first, [_textStorage length] - first) isSoft: NO actualCharacterRange: NULL]; } /** * Called by NSTextContainer whenever its textView changes. Used to * keep notifications in synch. */ - (void) textContainerChangedTextView: (NSTextContainer*)aContainer { unsigned index; index = [_textContainers indexOfObjectIdenticalTo: aContainer]; if (index != NSNotFound) { if (index == 0) { _firstTextView = [aContainer textView]; /* It only makes sense to update the other text views if we have more than one text container */ if (_textContainersCount > 1) { /* It's the first text view. Need to update everything. */ int i; for (i = 0; i < _textContainersCount; i++) { NSTextView *tv; tv = [[_textContainers objectAtIndex: i] textView]; [tv _updateMultipleTextViews]; } } } } } /** * Sent from processEditing in NSTextStorage. newCharRange is the * range in the final string which was explicitly * edited. invalidatedRange includes stuff which was changed as a * result of attribute fixing. invalidatedRange is either equal to * newCharRange or larger. Layout managers should not change the * contents of the text storage during the execution of this message. */ - (void) textStorage: (NSTextStorage*)aTextStorage edited: (unsigned)mask range: (NSRange)newCharRange changeInLength: (int)lengthChange invalidatedRange: (NSRange)invalidatedRange { /* NSLog(@"NSLayoutManager was just notified that a change in the text storage occured."); NSLog(@"range: (%d, %d) changeInLength: %d invalidatedRange (%d, %d)", newCharRange.location, newCharRange.length, lengthChange, invalidatedRange.location, invalidatedRange.length); */ int delta = 0; unsigned int last; _GLog(self,_cmd); if (mask & NSTextStorageEditedCharacters) { delta = lengthChange; } else if (mask == 0) { return; // No changes to make. } last = NSMaxRange (invalidatedRange); // hard invalidation occures here. [self invalidateGlyphsForCharacterRange: newCharRange changeInLength: delta actualCharacterRange: NULL]; [self invalidateLayoutForCharacterRange: invalidatedRange isSoft: NO actualCharacterRange: NULL]; // the following range is soft invalidated newCharRange = NSMakeRange (last, [_textStorage length] - last); [self invalidateLayoutForCharacterRange: newCharRange isSoft: YES actualCharacterRange: NULL]; _GLog(self,_cmd); } /** * This method allows you to set whether text gets laid out in * the background when there's nothing else to do. */ - (void) setBackgroundLayoutEnabled: (BOOL)flag { _backgroundLayout = flag; } /** * This method allows you to query whether text gets laid out in * the background when there's nothing else to do. */ - (BOOL) backgroundLayoutEnabled { return _backgroundLayout; } // // Accessing glyphs // // These methods are primitive. They do not cause the bookkeeping of // filling holes to happen. They do not cause invalidation of other // stuff. /** * Inserts a single glyph into the glyph stream at glyphIndex. * The character index which this glyph corresponds to is given * by charIndex. * Invariants ... * a) Glyph chunks are ordered sequentially from zero by character index. * b) Glyph chunks are ordered sequentially from zero by glyph index. * c) Adjacent glyphs may share a character index. */ - (void) insertGlyph: (NSGlyph)aGlyph atGlyphIndex: (unsigned)glyphIndex characterIndex: (unsigned)charIndex { unsigned chunkCount = GSIArrayCount(glyphChunks); GSGlyphAttrs attrs = { 0 }; GSGlyphChunk *chunk; unsigned pos; _GLog(self,_cmd); if (glyphIndex == 0 && chunkCount == 0) { /* * Special case - if there are no chunks, this is the * very first glyph and can simply be added to a new chunk. */ chunk = GSCreateGlyphChunk(glyphIndex, charIndex); GSIArrayAddItem(&chunk->glyphs, (GSIArrayItem)aGlyph); GSIArrayAddItem(&chunk->attrs, (GSIArrayItem)attrs); GSIArrayAddItem(glyphChunks, (GSIArrayItem)(void*)chunk); } else { unsigned gCount; unsigned gOffset; unsigned chunkIndex; /* * Locate the chunk that we should insert into - the last one with * a glyphIndex less than or equal to the index we were given. */ chunkIndex = GSChunkForGlyphIndex(glyphChunks, glyphIndex); chunk = (GSGlyphChunk*)GSIArrayItemAtIndex(glyphChunks, chunkIndex).ptr; /* * Check for the case where we have been given an index that's * beyond the end of the last chunk. */ gCount = GSIArrayCount(&chunk->glyphs); gOffset = glyphIndex - chunk->glyphIndex; if (gOffset > gCount) { [NSException raise: NSRangeException format: @"insertGlyph:glyphIndex:characterIndex: " @"glyph index out of range"]; } if (gOffset == 0) // Before first glyph in chunk { if (chunk->charIndex < charIndex) { [NSException raise: NSRangeException format: @"insertGlyph:glyphIndex:characterIndex: " @"character index greater than that of next glyph"]; } if (chunkIndex > 0) { GSGlyphChunk *previous; unsigned c; previous = (GSGlyphChunk*)GSIArrayItemAtIndex(glyphChunks, chunkIndex-1).ptr; c = GSIArrayCount(&previous->attrs); c = previous->charIndex + (GSIArrayItemAtIndex(&previous->attrs, c).ext).offset; if (c > charIndex) { [NSException raise: NSRangeException format: @"insertGlyph:glyphIndex:characterIndex: " @"character index less than that of previous glyph"]; } else if (c == charIndex) { /* * Inserting with the same character index as the last glyph * in the previous chunk - so we should append to that chunk * rather than prepending to this one. */ chunkIndex--; chunk = previous; gCount = GSIArrayCount(&chunk->glyphs); gOffset = glyphIndex - chunk->glyphIndex; } } } else if (gOffset == gCount) // After last glyph in chunk { unsigned c = chunk->charIndex; if (gOffset > 0) { c += (GSIArrayItemAtIndex(&chunk->attrs, gOffset-1).ext).offset; } if (charIndex < c) { [NSException raise: NSRangeException format: @"insertGlyph:glyphIndex:characterIndex: " @"character index less than that of previous glyph"]; } if (chunkIndex < chunkCount - 1) { GSGlyphChunk *next; next = (GSGlyphChunk*)GSIArrayItemAtIndex(glyphChunks, chunkIndex+1).ptr; if (next->charIndex < charIndex) { [NSException raise: NSRangeException format: @"insertGlyph:glyphIndex:characterIndex: " @"character index greater than that of next glyph"]; } else if (next->charIndex == charIndex) { /* * Inserting with the same character index as the first glyph * in the next chunk - so we should insert in that chunk * rather than appending to this one. */ chunkIndex++; chunk = next; gCount = GSIArrayCount(&chunk->glyphs); gOffset = glyphIndex - chunk->glyphIndex; } } } else // In middle of chunk somewhere. { unsigned n; unsigned p; p = chunk->charIndex + (GSIArrayItemAtIndex(&chunk->attrs, gOffset-1).ext).offset; if (p > charIndex) { [NSException raise: NSRangeException format: @"insertGlyph:glyphIndex:characterIndex: " @"character index less than that of previous glyph"]; } n = chunk->charIndex + (GSIArrayItemAtIndex(&chunk->attrs, gOffset).ext).offset; if (n < charIndex) { [NSException raise: NSRangeException format: @"insertGlyph:glyphIndex:characterIndex: " @"character index greater than that of next glyph"]; } } /* * Shall we add to the chunk or is it big enough already? */ if (gCount > 4 && gCount == GSIArrayCapacity(&chunk->glyphs)) { GSGlyphChunk *newChunk = 0; unsigned from; unsigned pos; unsigned splitAt = gCount/2; unsigned splitChar; splitChar = (GSIArrayItemAtIndex(&chunk->attrs, splitAt).ext).offset; while (splitAt > 0 && splitChar == (GSIArrayItemAtIndex(&chunk->attrs, splitAt-1).ext).offset) { splitAt--; } /* * Arbitrary check that we could make a sane splitup of the * chunk. Conceivably we could have every glyph in the * chunk set to the same character - which would force us to * break our invariant that all glyphs for a particular * character lie in the same chunk. */ if (splitAt <= gCount/4) { [NSException raise: NSInternalInconsistencyException format: @"unable to split glyph chunk"]; } /* * Ok - split the chunk into two (roughly) equal parts. */ splitChar = (GSIArrayItemAtIndex(&chunk->attrs, splitAt).ext).offset; newChunk = GSCreateGlyphChunk(chunk->glyphIndex + splitAt, chunk->charIndex + splitChar); GSIArrayInsertItem(glyphChunks, (GSIArrayItem)(void*)newChunk, chunkIndex+1); pos = 0; from = splitAt; while (from < GSIArrayCount(&chunk->glyphs)) { GSGlyphAttrs attrs; NSGlyph glyph; /* * Remove attributes from old chunk and add to new. * Adjust offset for character index of new chunk. */ attrs = GSIArrayItemAtIndex(&chunk->attrs, from).ext; attrs.offset -= splitChar; GSIArrayInsertItem(&newChunk->attrs, (GSIArrayItem)attrs, pos); /* * Remove glyph from old chunk and add to new. */ glyph = GSIArrayItemAtIndex(&chunk->glyphs, from).ulng; GSIArrayInsertItem(&newChunk->glyphs, (GSIArrayItem)glyph, pos); from++; pos++; } GSIArrayRemoveItemsFromIndex(&chunk->glyphs, splitAt); GSIArrayRemoveItemsFromIndex(&chunk->attrs, splitAt); /* * And set up so we point at the correct half of the split chunk. */ if (glyphIndex >= newChunk->glyphIndex) { chunkIndex++; chunk = newChunk; gOffset = glyphIndex - chunk->glyphIndex; } gCount = GSIArrayCount(&chunk->glyphs); } /* * Special handling for insertion at the start of a chunk - we * need to update the index values for the chunk, and (possibly) * the character offsets of every glyph in the chunk. */ if (gOffset == 0) { chunk->glyphIndex = glyphIndex; if (chunk->charIndex != charIndex) { int diff = charIndex - chunk->charIndex; /* * Changing character index of entire chunk. */ for (pos = 0; pos < gCount; pos++) { GSGlyphAttrs tmp; tmp = GSIArrayItemAtIndex(&chunk->attrs, pos).ext; tmp.offset += diff; GSIArraySetItemAtIndex(&chunk->attrs, (GSIArrayItem)tmp, pos); } chunk->charIndex = charIndex; } } /* * At last we insert the glyph and its attributes into the chunk. */ attrs.offset = charIndex - chunk->charIndex; GSIArrayInsertItem(&chunk->glyphs, (GSIArrayItem)aGlyph, gOffset); GSIArrayInsertItem(&chunk->attrs, (GSIArrayItem)attrs, gOffset); /* * Now adjust the glyph index for all following chunks so we will * still know the index of the first glyph in each chunk. */ for (pos = chunkIndex+1; pos < chunkCount; pos++) { chunk = (GSGlyphChunk*)GSIArrayItemAtIndex(glyphChunks, pos).ptr; chunk->glyphIndex++; } } /* * Now adjust gaps to handle glyph insertion. */ pos = 0; while (pos < GSIArrayCount((GSIArray)_glyphGaps)) { unsigned long val; val = GSIArrayItemAtIndex((GSIArray)_glyphGaps, pos).ulng; if (val >= glyphIndex) { GSIArraySetItemAtIndex((GSIArray)_glyphGaps, (GSIArrayItem)(val+1), pos); } pos++; } _GLog(self,_cmd); _Sane(self); } /** * If there are any holes in the glyph stream this will cause glyph * generation for all holes sequentially encountered until the desired * index is available. The first variant raises a NSRangeError if the * requested index is out of bounds, the second does not, but instead * optionally returns a flag indicating whether the requested index * exists. */ - (NSGlyph) glyphAtIndex: (unsigned)index { BOOL flag; NSGlyph glyph; glyph = [self glyphAtIndex: index isValidIndex: &flag]; if (flag == NO) { [NSException raise: NSRangeException format: @"glyph index out of range"]; } return glyph; } - (NSGlyph) glyphAtIndex: (unsigned)index isValidIndex: (BOOL*)flag { #if USE_GLYPHS NSGlyph glyph; NSString *string = nil; unsigned textLength = [_textStorage length]; _GLog(self,_cmd); if (GSIArrayCount((GSIArray)_glyphGaps) > 0 && (GSIArrayItemAtIndex((GSIArray)_glyphGaps, 0).ulng) <= index) { unsigned long gap; string = [_textStorage string]; while (GSIArrayCount((GSIArray)_glyphGaps) > 0 && (gap = GSIArrayItemAtIndex((GSIArray)_glyphGaps, 0).ulng) <= index) { unsigned endChar; unsigned startChar; if (gap == 0) { startChar = 0; } else { /* * Locate the glyph that preceeds the gap, and start with the * a character one beyond the one that generated that glyph. * This guarantees that we won't try to re-generate the * preceeding glyph. * FIXME ... probably too simplistic an algorithm if we have * decomposed unicode characters to deal with 0 we should * probably skip forward to the next character sequence. */ _JumpToGlyph(self, gap - 1); startChar = _CharIndex(self) + 1; } if (gap == _GlyphEnd(self)) { endChar = textLength; } else { _JumpToGlyph(self, gap); endChar = _CharIndex(self); } /* * FIXME * Here we put some simple-minded code to generate glyphs from * characters assuming that a glyph is the same as a character. */ while (startChar < endChar) { unichar c = [string characterAtIndex: startChar]; [self insertGlyph: (NSGlyph)c atGlyphIndex: gap++ characterIndex: startChar++]; } /* * We have generated glyphs upto or beyond the gap, so we * can remove this gap and any others we have gone past. */ while (GSIArrayCount((GSIArray)_glyphGaps) > 0 && GSIArrayItemAtIndex((GSIArray)_glyphGaps, 0).ulng < gap) { GSIArrayRemoveItemAtIndex((GSIArray)_glyphGaps, 0); } } } if (index >= _GlyphEnd(self) && _CharEnd(self) < textLength) { unsigned endChar = textLength; unsigned startChar = _CharEnd(self); unsigned glyphIndex = _GlyphEnd(self); if (string == nil) { string = [_textStorage string]; } /* FIXME ... should generate glyphs properly here */ while (startChar < endChar && glyphIndex <= index) { unichar c = [string characterAtIndex: startChar]; [self insertGlyph: (NSGlyph)c atGlyphIndex: glyphIndex++ characterIndex: startChar++]; } } _GLog(self,_cmd); _Sane(self); if (_JumpToGlyph(self, index) == YES) { *flag = YES; glyph = _Glyph(self); } else { *flag = NO; glyph = NSNullGlyph; } #if ALL_CHECKS if (index >= [_textStorage length]) { if (glyph != NSNullGlyph) { missmatch(_cmd); *flag = NO; glyph = NSNullGlyph; } } else if (glyph != (NSGlyph)[[_textStorage string] characterAtIndex: index]) { missmatch(_cmd); *flag = YES; glyph = (NSGlyph)[[_textStorage string] characterAtIndex: index]; } #endif return glyph; #else return (NSGlyph)[[_textStorage string] characterAtIndex: index]; #endif } /** * Replaces the glyph currently at glyphIndex with newGlyph. The * character index of the glyph is assumed to remain the same * (although it can, of course, be set explicitly if needed). */ - (void) replaceGlyphAtIndex: (unsigned)index withGlyph: (NSGlyph)newGlyph { _GLog(self,_cmd); if (_JumpToGlyph(self, index) == NO) { [NSException raise: NSRangeException format: @"glyph index out of range"]; } _SetGlyph(self, newGlyph); _GLog(self,_cmd); } /** * This causes glyph generation similarly to asking for a single * glyph. It formats a sequence of NSGlyphs (unsigned long ints). It * does not include glyphs that aren't shown in the result but does * zero-terminate the array. The memory passed in to the function * should be large enough for at least glyphRange.length+1 elements. * The actual number of glyphs stuck into the array is returned (not * counting the null-termination). RM!!! check out the private method * "_packedGlyphs:range:length:" if you need to send glyphs to the * window server. It returns a (conceptually) autoreleased array of * big-endian packed glyphs. Don't use this method to do that. */ - (unsigned) getGlyphs: (NSGlyph*)glyphArray range: (NSRange)glyphRange { unsigned packed = 0; unsigned toFetch = glyphRange.length; _GLog(self,_cmd); if (toFetch > 0) { /* * Force generation of glyphs to fill range. */ [self glyphAtIndex: NSMaxRange(glyphRange)-1]; _JumpToGlyph(self, glyphRange.location); /* * Now return glyphs, excluding those 'not shown' */ while (toFetch-- > 0) { if (_Attrs(self).isNotShown == 0) { glyphArray[packed++] = _Glyph(self); } _Step(self); // Move to next glyph. } } glyphArray[packed] = 0; _GLog(self,_cmd); return packed; } /** * Removes all glyphs in aRange from the storage, possibly leaving a gap * in the glyph stream. */ - (void) deleteGlyphsInRange: (NSRange)aRange { unsigned chunkStart; unsigned chunkEnd; unsigned offset; unsigned from; unsigned pos; GSGlyphChunk *chunk; _GLog(self,_cmd); if (aRange.length == 0) { return; // Nothing to delete. } pos = GSIArrayCount(glyphChunks) - 1; chunk = (GSGlyphChunk*)GSIArrayItemAtIndex(glyphChunks, pos).ptr; pos = chunk->glyphIndex + GSIArrayCount(&chunk->glyphs); if (aRange.location >= pos) { return; // Range is beyond glyphs. } if (NSMaxRange(aRange) > pos) { aRange.length = pos - aRange.location; // Truncate range to glyphs. } chunkStart = GSChunkForGlyphIndex(glyphChunks, aRange.location); chunkEnd = GSChunkForGlyphIndex(glyphChunks, NSMaxRange(aRange)-1); /* * Remove all chunks wholy contained in the range. */ while (chunkEnd - chunkStart > 1) { chunkEnd--; chunk = (GSGlyphChunk*)GSIArrayItemAtIndex(glyphChunks, chunkEnd).ptr; GSIArrayRemoveItemAtIndex(glyphChunks, chunkEnd); GSDestroyGlyphChunk(chunk); } /* * Get start chunk and remove any glyphs in specified range. */ chunk = (GSGlyphChunk*)GSIArrayItemAtIndex(glyphChunks, chunkStart).ptr; if (chunkStart == chunkEnd) { pos = chunk->glyphIndex; } else { offset = aRange.location - chunk->glyphIndex; if (offset == 0) { /* * Start chunk is fully enclosed in range - remove it. */ pos = chunk->glyphIndex; GSIArrayRemoveItemAtIndex(glyphChunks, chunkStart); GSDestroyGlyphChunk(chunk); chunkEnd--; } else { pos = chunk->glyphIndex + offset; GSIArrayRemoveItemsFromIndex(&chunk->glyphs, offset); GSIArrayRemoveItemsFromIndex(&chunk->attrs, offset); } chunk = (GSGlyphChunk*)GSIArrayItemAtIndex(glyphChunks, chunkEnd).ptr; } offset = NSMaxRange(aRange) - chunk->glyphIndex; if (chunk->glyphIndex < aRange.location) { from = aRange.location - chunk->glyphIndex; } else { from = 0; } chunk->glyphIndex = pos; while (offset-- > from) { GSIArrayRemoveItemAtIndex(&chunk->glyphs, from); GSIArrayRemoveItemAtIndex(&chunk->attrs, from); } while (++chunkEnd < GSIArrayCount(glyphChunks)) { chunk = (GSGlyphChunk*)GSIArrayItemAtIndex(glyphChunks, chunkEnd).ptr; chunk->glyphIndex -= aRange.length; } /* * Remove any gaps that were in the deleted range and adjust the * indices of any remaining gaps to allow for the deletion. */ pos = 0; while (pos < GSIArrayCount((GSIArray)_glyphGaps)) { unsigned val = GSIArrayItemAtIndex((GSIArray)_glyphGaps, pos).ulng; if (val < aRange.location) { pos++; // Not modified by deletion } else if (val <= NSMaxRange(aRange)) { /* * Gap is within (or immediately after) the deleted area ... * we set it to the end of the deleted area, or remove it if * there is already a gap at that location. */ if (pos > 0 && GSIArrayItemAtIndex((GSIArray)_glyphGaps, pos-1).ulng == aRange.location) { GSIArrayRemoveItemAtIndex((GSIArray)_glyphGaps, pos); } else { GSIArraySetItemAtIndex((GSIArray)_glyphGaps, (GSIArrayItem)aRange.location, pos); pos++; } } else { /* * Gap is beyond deleted area ... simply adjust downwards. */ val -= aRange.length; GSIArraySetItemAtIndex((GSIArray)_glyphGaps, (GSIArrayItem)val, pos); pos++; } } _GLog(self,_cmd); _Sane(self); } /** * If there are any gaps in the glyph stream, this will cause all * invalid character ranges to have glyphs generated for them. */ - (unsigned) numberOfGlyphs { unsigned result; #if USE_GLYPHS BOOL valid; /* * Force generation of all glyphs. */ [self glyphAtIndex: 0x7fffffff isValidIndex: &valid]; result = _GlyphEnd(self); #if ALL_CHECKS if (result != [_textStorage length]) { missmatch(_cmd); result = [_textStorage length]; } #endif #else result = [_textStorage length]; #endif return result; } /** * Sets the index of the corresponding character for the glyph at the * given glyphIndex to be charIndex. */ - (void) setCharacterIndex: (unsigned)charIndex forGlyphAtIndex: (unsigned)glyphIndex { GSGlyphAttrs attrs; int diff; _GLog(self,_cmd); if (_JumpToGlyph(self, glyphIndex) == NO) { [self glyphAtIndex: glyphIndex]; _JumpToGlyph(self, glyphIndex); } diff = charIndex - _CharIndex(self); if (diff == 0) { return; // Already set - nothing to do. } if (_Back(self) == NO) { if (charIndex != 0) { [NSException raise: NSRangeException format: @"set non-zero index for initial glyph"]; } return; } if (_CharIndex(self) > charIndex) { [NSException raise: NSRangeException format: @"set index lower than preceeding glyph"]; } _Step(self); if (_Step(self) == YES && charIndex > _CharIndex(self)) { [NSException raise: NSRangeException format: @"set index higher than following glyph"]; } _Back(self); /* * If this is the start of a chunk, we adjust the character position * for the chunk as a whole, then fix each glyph in turn. Otherwise * we simply adjust the glyph concerned. */ if (_glyphOffset == 0) { GSGlyphChunk *chunk = (GSGlyphChunk*)_currentGlyphs; diff = charIndex - _CharIndex(self); chunk->charIndex += diff; while (_Step(self) == YES && (GSGlyphChunk*)_currentGlyphs == chunk) { attrs = _Attrs(self); attrs.offset += diff; _SetAttrs(self, attrs); } } else { attrs = _Attrs(self); attrs.offset += diff; _SetAttrs(self, attrs); } _GLog(self,_cmd); _Sane(self); } /** * If there are any holes in the glyph stream this will cause glyph * generation for all holes sequentially encountered until the desired * index is available. */ - (unsigned) characterIndexForGlyphAtIndex: (unsigned)glyphIndex { unsigned result; #if USE_GLYPHS _GLog(self,_cmd); if (_JumpToGlyph(self, glyphIndex) == NO) { BOOL exists; [self glyphAtIndex: glyphIndex isValidIndex: &exists]; if (exists == YES) { _JumpToGlyph(self, glyphIndex); result = _CharIndex(self); } else { /* * As a special case, the glyph index just beyond the end of * the glyph stream is known to map to the character index just * beyond the end of the text. */ if (glyphIndex == _GlyphEnd(self)) { result = [_textStorage length]; } else { [NSException raise: NSRangeException format: @"glyph index out of range"]; } } } else { result = _CharIndex(self); } #if ALL_CHECKS if (result != glyphIndex) { missmatch(_cmd); result = glyphIndex; } #endif _GLog(self,_cmd); #else result = glyphIndex; #endif return result; } /** * This method can cause glyph generation. Returns the range of * characters that generated the glyphs in the given glyphRange. * actualGlyphRange, if not NULL, will be set to the full range of * glyphs that the character range returned generated. This range may * be identical or slightly larger than the requested glyphRange. For * instance, if the text storage contains the unichar (o-umlaut) and * the glyph store contains the two atomic glyphs "o" and (umlaut), * and if the glyphRange given encloses only the first or second * glyph, the actualGlyphRange will be set to enclose both glyphs. */ - (NSRange) characterRangeForGlyphRange: (NSRange)glyphRange actualGlyphRange: (NSRange*)actualGlyphRange { NSRange cRange; NSRange gRange = glyphRange; #if USE_GLYPHS unsigned cEnd; BOOL exists; _GLog(self,_cmd); /* * Force generation of glyphs to fill gaps. */ [self glyphAtIndex: NSMaxRange(glyphRange) isValidIndex: &exists]; /* * Locate character index of location immediately beyond last glyph in range. */ if (exists == NO) { if (NSMaxRange(glyphRange) > _GlyphEnd(self)) { [NSException raise: NSRangeException format: @"glyph range too large"]; } cEnd = [_textStorage length]; } else { _JumpToGlyph(self, NSMaxRange(glyphRange)); cEnd = _CharIndex(self); } /* * Locate the first glyph and step backwards to the earliest glyph with * the same character index. */ _JumpToGlyph(self, glyphRange.location); cRange.location = _CharIndex(self); cRange.length = cEnd - cRange.location; while (_Back(self) == YES && _CharIndex(self) == cRange.location) { gRange.location--; gRange.length++; } #if ALL_CHECKS if (NSEqualRanges(cRange, glyphRange) == NO) { missmatch(_cmd); cRange = glyphRange; } if (NSEqualRanges(gRange, glyphRange) == NO) { missmatch(_cmd); gRange = glyphRange; } #endif #else // Currently gyphIndex is the same as character index gRange = glyphRange; cRange = glyphRange; #endif if (actualGlyphRange != 0) { *actualGlyphRange = gRange; } return cRange; } /** * This method can cause glyph generation. * Returns the range of glyphs that are generated from the unichars in * the given charRange. actualCharRange, if not NULL, will be set to * the actual range of characters that fully define the glyph range * returned. This range may be identical or slightly larger than the * requested characterRange. For instance, if the text storage * contains the unichars "o" and (umlaut) and the glyph store contains * the single precomposed glyph (o-umlaut), and if the characterRange * given encloses only the first or second unichar, the * actualCharRange will be set to enclose both unichars. */ - (NSRange) glyphRangeForCharacterRange: (NSRange)charRange actualCharacterRange: (NSRange*)actualCharRange { NSRange gRange; #if USE_GLYPHS unsigned pos; NSRange cRange = charRange; unsigned numGlyphs; BOOL valid; /* * If the range we have been given begins or ends with a composed * character sequence, we must extend it to encompass the entire * sequence. We store the actual range in cRange. */ if (charRange.length > 0) { NSString *s = [_textStorage string]; NSRange r; r = [s rangeOfComposedCharacterSequenceAtIndex: cRange.location]; if (r.length > 0) { cRange.length += (cRange.location - r.location); cRange.location = r.location; } if (NSMaxRange(charRange) > NSMaxRange(r)) { pos = NSMaxRange(charrange) - 1; r = [s rangeOfComposedCharacterSequenceAtIndex: pos]; if (r.length > 0) { cRange.length += r.length - 1; } } } _GLog(self,_cmd); // Force generation of glyphs. [self glyphAtIndex: NSMaxRange(cRange) - 1 isValidIndex: &valid]; /* * Locate the first glyph corresponding to the start character. * If it doesn't exist, we either have a zero length range at the end. * or we must return a not found marker. */ if (_JumpToChar(self, charRange.location) == NO) { if (charRange.location == _CharEnd(self)) { cRange = NSMakeRange(charRange.location, 0); gRange = NSMakeRange(numGlyphs, 0); } else { cRange = NSMakeRange(NSNotFound, 0); gRange = NSMakeRange(NSNotFound, 0); } } else { gRange.location = _GlyphIndex(self); /* * Adjust start character if necessary. The glyph may have a lower * index if the start char is part of a composed character sequence. */ pos = _CharIndex(self); if (pos < cRange.location) { cRange.length += (cRange.location - pos); cRange.location = pos; } if (charRange.length == 0) { /* * For a zero length range, we don't need to locate an end character. */ cRange.length = 0; // May have been lengthened above. gRange.length = 0; } else if (NSMaxRange(charRange) == [_textStorage length]) { /* * Special case - range extends to end of text storage. */ gRange.length = numGlyphs - gRange.location; } else { /* * Locate the glyph immediately beyond the range, * and calculate the length of the range from that. */ _JumpToChar(self, NSMaxRange(charRange)); pos = _GlyphIndex(self); gRange.length = pos - gRange.location; pos = _CharIndex(self); cRange.length = pos - cRange.location; } } #if ALL_CHECKS if (NSEqualRanges(gRange, charRange) == NO) { missmatch(_cmd); gRange = charRange; } if (NSEqualRanges(cRange, charRange) == NO) { missmatch(_cmd); cRange = charRange; } #endif if (actualCharRange != 0) { *actualCharRange = cRange; } _GLog(self,_cmd); #else // Currently gyphIndex is the same as character index if (actualCharRange != NULL) { *actualCharRange = charRange; } gRange = charRange; #endif return gRange; } /** * Each NSGlyph has an attribute field, yes? This method is * primitive. It does not cause any invalidation of other stuff. * This method also will not cause glyph generation. The glyph being * set must already be there. This method is used by the * NSGlyphGenerator to set attributes. It is not usually necessary * for anyone but the glyph generator (and perhaps the typesetter) to * call it. It is provided as a public method so subclassers can * extend it to accept other glyph attributes. To add new glyph * attributes to the text system you basically need to do two things. * You need to write a rulebook which will generate the attributes (in * rulebooks attributes are identified by integer tags). Then you * need to subclass NSLayoutManager to provide someplace to store the * new attribute and to override this method and * -attribute:forGlyphAtIndex: to understand the integer tag which * your new rulebook is generating. NSLayoutManager's implementation * understands the glyph attributes which it is prepared to remember. * Your override should pass any glyph attributes it does not * understand up to the superclass's implementation. */ - (void) setIntAttribute: (int)attribute value: (int)anInt forGlyphAtIndex: (unsigned)glyphIndex { GSGlyphAttrs attrs; _GLog(self,_cmd); if (_JumpToGlyph(self, glyphIndex) == NO) { [NSException raise: NSRangeException format: @"glyph index out of range"]; } attrs = _Attrs(self); if (attribute == GSGlyphDrawsOutsideLineFragment) { if (anInt == 0) { attrs.drawsOutsideLineFragment = 0; } else { attrs.drawsOutsideLineFragment = 1; } } else if (attribute == GSGlyphIsNotShown) { if (anInt == 0) { attrs.isNotShown = 0; } else { attrs.isNotShown = 1; } } else if (attribute == GSGlyphGeneration) { attrs.generation = anInt; } else if (attribute == GSGlyphInscription) { attrs.inscription = anInt; } _SetAttrs(self, attrs); _GLog(self,_cmd); } /** * This returns the value for the given glyph attribute at the glyph * index specified. Most apps will not have much use for this info * but the typesetter and glyph generator might need to know about * certain attributes. You can override this method to know how to * return any custom glyph attributes you want to support. */ - (int) intAttribute: (int)attribute forGlyphAtIndex: (unsigned)glyphIndex { GSGlyphAttrs attrs; _GLog(self,_cmd); if (_JumpToGlyph(self, glyphIndex) == NO) { [NSException raise: NSRangeException format: @"glyph index out of range"]; } attrs = _Attrs(self); if (attribute == GSGlyphDrawsOutsideLineFragment) { if (attrs.drawsOutsideLineFragment == 0) { return 0; } else { return 1; } } else if (attribute == GSGlyphIsNotShown) { if (attrs.isNotShown == 0) { return 0; } else { return 1; } } else if (attribute == GSGlyphGeneration) { return attrs.generation; } else if (attribute == GSGlyphInscription) { return attrs.inscription; } return 0; } // // Handling layout for text containers // // These methods are fairly primitive. They do not cause any kind of // invalidation to happen. The glyphs being set must already exist. // This is not a hardship since the NSTypesetter will have had to ask // for the actual glyphs already by the time it goes to set this, and // asking for the glyphs causes the glyph to be generated if // necessary. Associates the given container with the given range of // glyphs. This method should be called by the typesetter first (ie // before setting line fragment rect or any of the layout bits) for // each range of glyphs it lays out. This method will set several key // layout atttributes (like not shown and draws outside line fragment) // to their default values. - (void) setTextContainer: (NSTextContainer*)aTextContainer forGlyphRange: (NSRange)glyphRange { /* TODO */ } // All of these methods can cause glyph generation AND layout. // Returns the range of characters which have been laid into the given // container. This is a less efficient method than the similar // -textContainerForGlyphAtIndex:effectiveRange:. - (NSRange) glyphRangeForTextContainer: (NSTextContainer*)aTextContainer { /* TODO */ return NSMakeRange(NSNotFound, 0); } /** * Returns the container in which the given glyph is laid and * (optionally) by reference the whole range of glyphs that are in * that container. This will cause glyph generation AND layout as * needed. */ - (NSTextContainer*) textContainerForGlyphAtIndex: (unsigned)glyphIndex effectiveRange: (NSRange*)effectiveRange { /* FIXME ... needs to be properly implemented */ if (effectiveRange != 0) { *effectiveRange = NSMakeRange(0, [self numberOfGlyphs]); } if ([_textContainers count] == 0) { return nil; } else { return [_textContainers objectAtIndex: 0]; } } // // Handling line fragment rectangles // // Associates the given line fragment bounds with the given range of // glyphs. - (void) setLineFragmentRect: (NSRect)fragmentRect forGlyphRange: (NSRange)glyphRange usedRect: (NSRect)usedRect { /* TODO */ } // Returns the rect for the line fragment in which the given glyph is // laid and (optionally) by reference the whole range of glyphs that // are in that fragment. This will cause glyph generation AND layout // as needed. - (NSRect) lineFragmentRectForGlyphAtIndex: (unsigned)glyphIndex effectiveRange: (NSRange*)lineFragmentRange { /* TODO */ return NSZeroRect; } // Returns the usage rect for the line fragment in which the given // glyph is laid and (optionally) by reference the whole range of // glyphs that are in that fragment. This will cause glyph generation // AND layout as needed. - (NSRect) lineFragmentUsedRectForGlyphAtIndex: (unsigned)glyphIndex effectiveRange: (NSRange*)lineFragmentRange { /* TODO */ return NSZeroRect; } // Sets the bounds and container for the extra line fragment. The // extra line fragment is used when the text backing ends with a hard // line break or when the text backing is totally empty to define the // extra line which needs to be displayed. If the text backing does // not end with a hard line break this should be set to NSZeroRect and // nil. - (void) setExtraLineFragmentRect: (NSRect)aRect usedRect: (NSRect)usedRect textContainer: (NSTextContainer*)aTextContainer { /* TODO */ } // Return info about the extra line fragment. - (NSRect) extraLineFragmentRect { /* TODO */ return NSZeroRect; } - (NSRect) extraLineFragmentUsedRect { /* TODO */ return NSZeroRect; } - (NSTextContainer*) extraLineFragmentTextContainer { /* TODO */ return nil; } // Returns the container's currently used area. This is the size that // the view would need to be in order to display all the stuff that is // currently laid into the container. This causes no generation. - (NSRect)usedRectForTextContainer:(NSTextContainer *)container { /* TODO */ return NSZeroRect; } - (void)setAttachmentSize:(NSSize)attachmentSize forGlyphRange:(NSRange)glyphRange { /* TODO */ } // Used to indicate that a particular glyph for some reason marks // outside its line fragment bounding rect. This can commonly happen // if a fixed line height is used (consider a 12 point line height and // a 24 point glyph). - (void) setDrawsOutsideLineFragment: (BOOL)flag forGlyphAtIndex: (unsigned)glyphIndex { [self setIntAttribute: GSGlyphDrawsOutsideLineFragment value: 1 forGlyphAtIndex: glyphIndex]; } // Returns whether the glyph will make marks outside its line // fragment's bounds. - (BOOL) drawsOutsideLineFragmentForGlyphAtIndex: (unsigned)glyphIndex { if ([self intAttribute: GSGlyphDrawsOutsideLineFragment forGlyphAtIndex: glyphIndex] == 1) { return YES; } return NO; } // // Layout of glyphs // // Sets the location to draw the first glyph of the given range at. // Setting the location for a glyph range implies that its first glyph // is NOT nominally spaced with respect to the previous glyph. When // all is said and done all glyphs in the layoutManager should have // been included in a range passed to this method. But only glyphs // which start a new nominal rtange should be at the start of such // ranges. Glyph locations are given relative the their line fragment // bounding rect's origin. - (void) setLocation: (NSPoint)aPoint forStartOfGlyphRange: (NSRange)glyphRange { /* TODO */ } // Returns the location that the given glyph will draw at. If this // glyph doesn't have an explicit location set for it (ie it is part // of (but not first in) a sequence of nominally spaced characters), // the location is calculated from the location of the last glyph with // a location set. Glyph locations are relative the their line // fragment bounding rect's origin (see // -lineFragmentForGlyphAtIndex:effectiveRange: below for finding line // fragment bounding rects). This will cause glyph generation AND // layout as needed. - (NSPoint) locationForGlyphAtIndex: (unsigned)glyphIndex { /* TODO */ return NSZeroPoint; } // Returns the range including the first glyph from glyphIndex on back // that has a location set and up to, but not including the next glyph // that has a location set. This is a range of glyphs that can be // shown with a single postscript show operation. - (NSRange) rangeOfNominallySpacedGlyphsContainingIndex: (unsigned)glyphIndex { /* TODO */ return NSMakeRange(NSNotFound, 0); } // Returns an array of NSRects and the number of rects by reference // which define the region in container that encloses the given range. // If a selected range is given in the second argument, the rectangles // returned will be correct for drawing the selection. Selection // rectangles are generally more complicated than enclosing rectangles // and supplying a selected range is the clue these methods use to // determine whether to go to the trouble of doing this special work. // If the caller is interested in this more from an enclosing point of // view rather than a selection point of view pass {NSNotFound, 0} as // the selected range. This method works hard to do the minimum // amount of work required to answer the question. The resulting // array is owned by the layoutManager and will be reused when either // of these two methods OR -boundingRectForGlyphRange:inTextContainer: // is called. Note that one of these methods may be called // indirectly. The upshot is that if you aren't going to use the // rects right away, you should copy them to another location. - (NSRect*) rectArrayForCharacterRange: (NSRange)charRange withinSelectedCharacterRange: (NSRange)selChareRange inTextContainer: (NSTextContainer*)aTextContainer rectCount: (unsigned*)rectCount { /* TODO */ return NULL; } - (NSRect*) rectArrayForGlyphRange: (NSRange)glyphRange withinSelectedGlyphRange: (NSRange)selectedGlyphRange inTextContainer: (NSTextContainer*)aTextContainer rectCount: (unsigned*)rectCount { /* TODO */ return _cachedRectArray; } // Returns the smallest bounding rect (in container coordinates) which // completely encloses the glyphs in the given glyphRange that are in // the given container. If no container is given, then the container // of the first glyph is assumed. Basically, the range is intersected // with the container's range before computing the bounding rect. // This method can be used to translate glyph ranges into display // rectangles for invalidation. - (NSRect) boundingRectForGlyphRange: (NSRange)glyphRange inTextContainer: (NSTextContainer*)aTextContainer { /* TODO */ return NSZeroRect; } // Returns the minimum contiguous glyph range that would need to be // displayed in order to draw all glyphs that fall (even partially) // within the bounding rect given. This range might include glyphs // which do not fall into the rect at all. At most this will return // the glyph range for the whole container. The "WithoutFillingHoles" // variant will not generate glyphs or perform layout in attempting to // answer, and, thus, will potentially not be totally correct. - (NSRange) glyphRangeForBoundingRect: (NSRect)aRect inTextContainer: (NSTextContainer*)aTextContainer { /* TODO */ return NSMakeRange(0, 0); } - (NSRange) glyphRangeForBoundingRectWithoutAdditionalLayout: (NSRect)bounds inTextContainer: (NSTextContainer*)aTextContainer { /* TODO */ return NSMakeRange(0, 0); } // Returns the index of the glyph which under the given point which is // expressed in the given container's coordinate system. If no glyph // is under the point the "nearest" glyph is returned where "nearest" // is defined in such a way that selection works like it should. See // the implementation for details. partialFraction, if provided, is // set to the fraction of the distance between the location of the // glyph returned and the location of the next glyph that the point is // at. - (unsigned) glyphIndexForPoint: (NSPoint)aPoint inTextContainer: (NSTextContainer*)aTextContainer fractionOfDistanceThroughGlyph: (float*)partialFraction { /* TODO */ return 0; } - (unsigned) glyphIndexForPoint: (NSPoint)aPoint inTextContainer: (NSTextContainer *)aTextContainer { /* TODO */ return [self glyphIndexForPoint: aPoint inTextContainer: aTextContainer fractionOfDistanceThroughGlyph: NULL]; } // // Display of special glyphs // // Some glyphs are not shown. The typesetter decides which ones and // sets this attribute in layoutManager where the view can find it. - (void) setNotShownAttribute: (BOOL)flag forGlyphAtIndex: (unsigned)glyphIndex { [self setIntAttribute: GSGlyphIsNotShown value: 1 forGlyphAtIndex: glyphIndex]; } // Some glyphs are not shown. This will cause glyph generation and // layout as needed.. - (BOOL) notShownAttributeForGlyphAtIndex: (unsigned)glyphIndex { if ([self intAttribute: GSGlyphIsNotShown forGlyphAtIndex: glyphIndex] == 1) { return YES; } return NO; } // If YES, and the rulebooks and fonts in use support it, whitespace // and other "invisible" unicodes will be shown with special glyphs // (ie "." for space, the little CR icon for new lines, etc...) - (void) setShowsInvisibleCharacters: (BOOL)flag { _showsInvisibleChars = flag; } - (BOOL) showsInvisibleCharacters { return _showsInvisibleChars; } // If YES, and the rulebooks and fonts in use support it, control // characters will be rendered visibly (usually like "^M", but // possibly with special glyphs if the the font and rulebook supports // it). - (void) setShowsControlCharacters: (BOOL)flag { _showsControlChars = flag; } - (BOOL) showsControlCharacters { return _showsControlChars; } // // Controlling hyphenation // - (void) setHyphenationFactor: (float)factor { _hyphenationFactor = factor; } - (float) hyphenationFactor { return _hyphenationFactor; } // // Finding unlaid characters/glyphs // // Returns (by reference) the character index or glyph index or both // of the first unlaid character/glyph in the layout manager at this // time. - (void) getFirstUnlaidCharacterIndex: (unsigned*)charIndex glyphIndex: (unsigned*)glyphIndex { if (charIndex) { *charIndex = [self firstUnlaidCharacterIndex]; } if (glyphIndex) { *glyphIndex = [self firstUnlaidGlyphIndex]; } } - (unsigned int) firstUnlaidCharacterIndex { return _firstUnlaidCharIndex; } - (unsigned int) firstUnlaidGlyphIndex { return _firstUnlaidGlyphIndex; } // // Using screen fonts // // Sets whether this layoutManager will use screen fonts when it is // possible to do so. - (void) setUsesScreenFonts: (BOOL)flag { _usesScreenFonts = flag; } - (BOOL) usesScreenFonts { return _usesScreenFonts; } // Returns a font to use in place of originalFont. This method is // used to substitute screen fonts for regular fonts. If screen fonts // are allowed AND no NSTextView managed by this layoutManager is // scaled or rotated AND a screen font is available for originalFont, // it is returned, otherwise originalFont is returned. MF:??? This // method will eventually need to know or be told whether use of // screen fonts is appropriate in a given situation (ie screen font // used might be enabled or disabled, we might be printing, etc...). // This method causes no generation. - (NSFont*) substituteFontForFont: (NSFont*)originalFont { NSFont *replaceFont; if (! _usesScreenFonts) { return originalFont; } // FIXME: Should check if any NSTextView is scaled or rotated replaceFont = [originalFont screenFont]; if (replaceFont != nil) { return replaceFont; } else { return originalFont; } } // // Handling rulers // // These return, respectively, an array of text ruler objects for the // current selection and the accessory view that the text system uses // for ruler. If you have turned off automatic ruler updating through // the use of setUsesRulers: so that you can do more complex things, // but you still want to display the appropriate text ruler objects // and/or accessory view, you can use these methods. - (NSView*) rulerAccessoryViewForTextView: (NSTextView*)aTextView paragraphStyle: (NSParagraphStyle*)paragraphStyle ruler: (NSRulerView*)aRulerView enabled: (BOOL)flag { /* TODO */ return NULL; } - (NSArray*) rulerMarkersForTextView: (NSTextView*)aTextView paragraphStyle: (NSParagraphStyle*)paragraphStyle ruler: (NSRulerView*)aRulerView { NSRulerMarker *marker; NSTextTab *tab; NSImage *image; NSArray *tabs = [paragraphStyle tabStops]; NSEnumerator *enumerator = [tabs objectEnumerator]; NSMutableArray *markers = [NSMutableArray arrayWithCapacity: [tabs count]]; while ((tab = [enumerator nextObject]) != nil) { switch ([tab tabStopType]) { case NSLeftTabStopType: image = [NSImage imageNamed: @"common_LeftTabStop"]; break; case NSRightTabStopType: image = [NSImage imageNamed: @"common_RightTabStop"]; break; case NSCenterTabStopType: image = [NSImage imageNamed: @"common_CenterTabStop"]; break; case NSDecimalTabStopType: image = [NSImage imageNamed: @"common_DecimalTabStop"]; break; default: image = nil; break; } marker = [[NSRulerMarker alloc] initWithRulerView: aRulerView markerLocation: [tab location] image: image imageOrigin: NSMakePoint(0, 0)]; [marker setRepresentedObject: tab]; [markers addObject: marker]; } return markers; } /* * Managing the responder chain */ - (BOOL) layoutManagerOwnsFirstResponderInWindow: (NSWindow*)aWindow { id firstResponder = [aWindow firstResponder]; if (_textContainersCount == 1) { if (_firstTextView == firstResponder) { return YES; } } else { int i; for (i = 0; i < _textContainersCount; i++) { id tv = [[_textContainers objectAtIndex: i] textView]; if (tv == firstResponder) { return YES; } } } return NO; } - (NSTextView*) firstTextView { return _firstTextView; } // This method is special in that it won't cause layout if the // beginning of the selected range is not yet laid out. Other than // that this method could be done through other API. - (NSTextView*) textViewForBeginningOfSelection { return nil; } // // Drawing // - (void) drawBackgroundForGlyphRange: (NSRange)glyphRange atPoint: (NSPoint)containerOrigin { NSTextContainer *aTextContainer; aTextContainer = [self textContainerForGlyphAtIndex: glyphRange.location effectiveRange: NULL]; [[[aTextContainer textView] backgroundColor] set]; NSRectFill ([self boundingRectForGlyphRange: glyphRange inTextContainer: aTextContainer]); } // These methods are called by NSTextView to do drawing. You can // override these if you think you can draw the stuff any better // (but not to change layout). You can call them if you want, but // focus must already be locked on the destination view or MF:???image?. // -drawBackgroundGorGlyphRange:atPoint: should draw the background // color and selection and marked range aspects of the text display. // -drawGlyphsForGlyphRange:atPoint: should draw the actual glyphs. // The point in either method is the container origin in the currently // focused view's coordinates for the container the glyphs lie in. - (void) drawGlyphsForGlyphRange: (NSRange)glyphRange atPoint: (NSPoint)containerOrigin { /* TODO */ } - (void) drawUnderlineForGlyphRange: (NSRange)glyphRange underlineType: (int)underlineType baselineOffset: (float)baselineOffset lineFragmentRect: (NSRect)lineRect lineFragmentGlyphRange: (NSRange)lineGlyphRange containerOrigin: (NSPoint)containerOrigin { /* TODO */ } // The first of these methods actually draws an appropriate underline // for the glyph range given. The second method potentailly breaks // the range it is given up into subranges and calls drawUnderline... // for ranges that should actually have the underline drawn. As // examples of why there are two methods, consider two situations. // First, in all cases you don't want to underline the leading and // trailing whitespace on a line. The -underlineGlyphRange... method // is passed glyph ranges that have underlining turned on, but it will // then look for this leading and trailing white space and only pass // the ranges that should actually be underlined to -drawUnderline... // Second, if the underlineType: indicates that only words, (ie no // whitespace), should be underlined, then -underlineGlyphRange... // will carve the range it is passed up into words and only pass word // ranges to -drawUnderline. - (void) underlineGlyphRange: (NSRange)glyphRange underlineType: (int)underlineType lineFragmentRect: (NSRect)lineRect lineFragmentGlyphRange: (NSRange)lineGlyphRange containerOrigin: (NSPoint)containerOrigin { /* TODO */ } // // Setting the delegate // - (void) setDelegate: (id)aDelegate { _delegate = aDelegate; } - (id) delegate { return _delegate; } - (unsigned) _charIndexForInsertionPointMovingFromY: (float)position bestX: (float)wanted up: (BOOL)upFlag textContainer: (NSTextContainer *)tc { [self subclassResponsibility: _cmd]; return 0; } @end /* NSLayoutManager */ /* The methods laid out here are not correct, however the code they contain for the most part is. Therefore, my country and a handsome gift of Ghiradelli chocolate to he who puts all the pieces together :) */ /* * A little utility function to determine the range of characters in a * scanner that are present in a specified character set. */ static inline NSRange scanRange (NSScanner *scanner, NSCharacterSet* aSet) { unsigned start = [scanner scanLocation]; unsigned end = start; if ([scanner scanCharactersFromSet: aSet intoString: 0] == YES) { end = [scanner scanLocation]; } return NSMakeRange (start, end - start); } @implementation NSLayoutManager (Private) - (int) _rebuildLayoutForTextContainer: (NSTextContainer*)aContainer startingAtGlyphIndex: (int)glyphIndex { NSSize cSize = [aContainer containerSize]; float i = 0.0; NSMutableArray *lineStarts = [NSMutableArray new]; NSMutableArray *lineEnds = [NSMutableArray new]; int indexToAdd; NSScanner *lineScanner; NSScanner *paragraphScanner; BOOL lastLineForContainerReached = NO; int previousScanLocation; int previousParagraphLocation; int endScanLocation; int startIndex; NSRect firstProposedRect; NSRect secondProposedRect; NSCharacterSet *selectionParagraphGranularitySet = [NSCharacterSet characterSetWithCharactersInString: @"\n"]; NSCharacterSet *selectionWordGranularitySet = [NSCharacterSet characterSetWithCharactersInString: @" "]; NSCharacterSet *invSelectionWordGranularitySet = [selectionWordGranularitySet invertedSet]; NSCharacterSet *invSelectionParagraphGranularitySet = [selectionParagraphGranularitySet invertedSet]; NSRange paragraphRange; NSRange leadingSpacesRange; NSRange currentStringRange; NSRange trailingSpacesRange; NSRange leadingNlRange; NSRange trailingNlRange; NSSize lSize; float lineWidth = 0.0; float ourLines = 0.0; int beginLineIndex = 0; NSLog(@"rebuilding Layout at index: %d.\n", glyphIndex); // 1.) figure out how many glyphs we can fit in our container by // breaking up glyphs from the first unlaid out glyph and breaking it // into lines. // // 2.) // a.) set the range for the container // b.) for each line in step 1 we need to set a lineFragmentRect and // an origin point. // Here we go at part 1. startIndex = glyphIndex; paragraphScanner = [NSScanner scannerWithString: [_textStorage string]]; [paragraphScanner setCharactersToBeSkipped: nil]; [paragraphScanner setScanLocation: startIndex]; NSLog(@"length of textStorage: %d", [[_textStorage string] length]); // NSLog(@"buffer: %@", [_textStorage string]); /* * This scanner eats one word at a time, we should have it imbeded in * another scanner that snacks on paragraphs (i.e. lines that end with * \n). Look in NSText. */ while (![paragraphScanner isAtEnd]) { previousParagraphLocation = [paragraphScanner scanLocation]; beginLineIndex = previousParagraphLocation; lineWidth = 0.0; leadingNlRange = scanRange(paragraphScanner, selectionParagraphGranularitySet); paragraphRange = scanRange(paragraphScanner, invSelectionParagraphGranularitySet); trailingNlRange = scanRange(paragraphScanner, selectionParagraphGranularitySet); // NSLog(@"leadingNlRange: (%d, %d)", leadingNlRange.location, leadingNlRange.length); // if (leadingNlRange.length) // paragraphRange = NSUnionRange (leadingNlRange,paragraphRange); // if (trailingNlRange.length) // paragraphRange = NSUnionRange (trailingNlRange,paragraphRange); NSLog(@"paragraphRange: (%d, %d)", paragraphRange.location, paragraphRange.length); lineScanner = [NSScanner scannerWithString: [[_textStorage string] substringWithRange: paragraphRange]]; [lineScanner setCharactersToBeSkipped: nil]; while (![lineScanner isAtEnd]) { previousScanLocation = [lineScanner scanLocation]; // snack next word leadingSpacesRange = scanRange(lineScanner, selectionWordGranularitySet); currentStringRange = scanRange(lineScanner, invSelectionWordGranularitySet); trailingSpacesRange = scanRange(lineScanner, selectionWordGranularitySet); if (leadingSpacesRange.length) currentStringRange = NSUnionRange(leadingSpacesRange,currentStringRange); if (trailingSpacesRange.length) currentStringRange = NSUnionRange(trailingSpacesRange,currentStringRange); lSize = [_textStorage sizeRange: currentStringRange]; // lSize = [_textStorage sizeRange: //NSMakeRange(currentStringRange.location+paragraphRange.location+startIndex, //currentStringRange.length)]; if ((lineWidth + lSize.width) < cSize.width) { if ([lineScanner isAtEnd]) { NSLog(@"we are at end before finishing a line: %d.\n", [lineScanner scanLocation]); NSLog(@"scanLocation = %d, previousParagraphLocation = %d, beginLineIndex = %d", [lineScanner scanLocation], previousParagraphLocation, beginLineIndex); [lineStarts addObject: [NSNumber numberWithInt: beginLineIndex]]; [lineEnds addObject: [NSNumber numberWithInt: (int)[lineScanner scanLocation] + previousParagraphLocation - (beginLineIndex)]]; lineWidth = 0.0; } lineWidth += lSize.width; //NSLog(@"lineWidth: %f", lineWidth); } else { if (ourLines > cSize.height) { lastLineForContainerReached = YES; break; } [lineScanner setScanLocation: previousScanLocation]; indexToAdd = previousScanLocation + previousParagraphLocation - (beginLineIndex); NSLog(@"previousScanLocation = %d, previousParagraphLocation = %d, beginLineIndex = %d indexToAdd = %d", previousScanLocation, previousParagraphLocation, beginLineIndex, indexToAdd); ourLines += 20.0; // 14 lineWidth = 0.0; [lineStarts addObject: [NSNumber numberWithInt: beginLineIndex]]; [lineEnds addObject: [NSNumber numberWithInt: indexToAdd]]; beginLineIndex = previousScanLocation + previousParagraphLocation; } } if (lastLineForContainerReached) break; } endScanLocation = [paragraphScanner scanLocation]; NSLog(@"endScanLocation: %d", endScanLocation); // set this container for that glyphrange [self setTextContainer: aContainer forGlyphRange: NSMakeRange(startIndex, endScanLocation - startIndex)]; NSLog(@"ok, move on to step 2."); // step 2. break the lines up and assign rects to them. for (i = 0; i < [lineStarts count]; i++) { NSRect aRect, bRect; float padding = [aContainer lineFragmentPadding]; NSRange ourRange; // NSLog(@"\t\t===> %d", [[lines objectAtIndex: i] intValue]); ourRange = NSMakeRange ([[lineStarts objectAtIndex: i] intValue], [[lineEnds objectAtIndex: i] intValue]); /* if (i == 0) { ourRange = NSMakeRange (startIndex, [[lines objectAtIndex: i] intValue] - startIndex); } else { ourRange = NSMakeRange ([[lines objectAtIndex: i-1] intValue], [[lines objectAtIndex: i] intValue] - [[lines objectAtIndex: i-1] intValue]); } */ NSLog(@"line: %@|", [[_textStorage string] substringWithRange: ourRange]); firstProposedRect = NSMakeRect (0, i * 14, cSize.width, 14); // ask our textContainer to fix our lineFragment. secondProposedRect = [aContainer lineFragmentRectForProposedRect: firstProposedRect sweepDirection: NSLineSweepLeft movementDirection: NSLineMoveLeft remainingRect: &bRect]; // set the line fragmentRect for this range. [self setLineFragmentRect: secondProposedRect forGlyphRange: ourRange usedRect: aRect]; // set the location for this string to be 'show'ed. [self setLocation: NSMakePoint(secondProposedRect.origin.x + padding, secondProposedRect.origin.y + padding) forStartOfGlyphRange: ourRange]; } // bloody hack. // if (moreText) // [delegate layoutManager: self // didCompleteLayoutForTextContainer: [textContainers objectAtIndex: i] // atEnd: NO]; // else // [delegate layoutManager: self // didCompleteLayoutForTextContainer: [textContainers objectAtIndex: i] // atEnd: YES]; [lineStarts release]; [lineEnds release]; return endScanLocation; } - (void) _doLayout { NSEnumerator *enumerator; NSTextContainer *container; int gIndex = 0; NSLog(@"doLayout called.\n"); enumerator = [_textContainers objectEnumerator]; while ((container = [enumerator nextObject]) != nil) { gIndex = [self _rebuildLayoutForTextContainer: container startingAtGlyphIndex: gIndex]; } } @end