/* GSLayoutManager.m Copyright (C) 2002, 2003 Free Software Foundation, Inc. Author: Alexander Malmberg Date: November 2002 - February 2003 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 #include #include "AppKit/NSTextStorage.h" #include "AppKit/NSTextContainer.h" /* just for NSAttachmentCharacter */ #include "AppKit/NSTextAttachment.h" #include "GNUstepGUI/GSTypesetter.h" #include "GNUstepGUI/GSLayoutManager_internal.h" /* TODO: is using rand() here ok? */ static int random_level(void) { int i; for (i = 0; i < SKIP_LIST_DEPTH - 2; i++) if ((rand() % SKIP_LIST_LEVEL_PROBABILITY) != 0) break; return i; } /* Private method used internally by GSLayoutManager for sanity checking. */ @interface NSTextStorage (GSLayoutManager_sanity_checking) -(unsigned int) _editCount; @end @implementation NSTextStorage (GSLayoutManager_sanity_checking) -(unsigned int) _editCount; { return _editCount; } @end /***** Glyph handling *****/ @implementation GSLayoutManager (glyphs_helpers) -(void) _run_cache_attributes: (glyph_run_t *)r : (NSDictionary *)attributes { /* set up attributes for this run */ NSNumber *n; r->explicit_kern = !![attributes objectForKey: NSKernAttributeName]; n = [attributes objectForKey: NSLigatureAttributeName]; if (n) r->ligature = [n intValue]; else r->ligature = 1; r->font = [typesetter fontForCharactersWithAttributes: attributes]; /* TODO: it might be useful to change this slightly: Returning a nil font from -fontForCharactersWithAttributes: causes those characters to not be displayed (ie. no glyphs are generated). How would glyph<->char mapping be handled? Map the entire run to one NSNullGlyph? */ if (!r->font) r->font = [NSFont userFontOfSize: 0]; r->font = [self substituteFontForFont: r->font]; r->font = [r->font retain]; } -(void ) _run_free_attributes: (glyph_run_t *)r { [r->font release]; } -(void) _run_copy_attributes: (glyph_run_t *)dst : (const glyph_run_t *)src { dst->font = [src->font retain]; dst->ligature = src->ligature; dst->explicit_kern = src->explicit_kern; } -(void) _freeGlyphs { glyph_run_t *cur, *next; glyph_run_head_t *h; if (!glyphs) return; h = glyphs; h += SKIP_LIST_DEPTH - 1; for (cur = (glyph_run_t *)h->next; cur; cur = next) { next = (glyph_run_t *)cur->head.next; if (cur->glyphs) free(cur->glyphs); [self _run_free_attributes: cur]; h = &cur->head; h -= cur->level; free(h); } free(glyphs); glyphs = NULL; } -(void) _initGlyphs { int i, size; glyph_run_head_t *h; size = sizeof(glyph_run_head_t) * (SKIP_LIST_DEPTH - 1) + sizeof(glyph_run_t); glyphs = malloc(size); memset(glyphs, 0, size); for (h = glyphs, i = SKIP_LIST_DEPTH; i; i--, h++) h->complete = 1; } -(void) _glyphDumpRuns { printf("--- dumping runs\n"); { glyph_run_t *h; unsigned int cpos = 0; h = (glyph_run_t *)(glyphs + SKIP_LIST_DEPTH - 1)->next; for (; h; h = (glyph_run_t *)h->head.next) { printf("%08x %i chars, %i glyphs, %i complete, prev %08x next %08x\n", (int)h, h->head.char_length, h->head.glyph_length, h->head.complete, (int)h->prev, (int)h->head.next); printf(" level %i, continued %i\n", h->level, h->continued); if (h->head.complete) { unsigned int i; printf("glyphs:\n"); for (i = 0;i < h->head.glyph_length;i++) printf("%5i %04x u%04x ", h->glyphs[i].char_offset,h->glyphs[i].g, [[_textStorage string] characterAtIndex: cpos+h->glyphs[i].char_offset]); printf("\n"); } cpos += h->head.char_length; } } printf("- structure\n"); { glyph_run_head_t *h, *g; int i; printf(" head: "); for (i = 0, h = glyphs + SKIP_LIST_DEPTH - 1; i < SKIP_LIST_DEPTH; i++, h--) printf("%8x %i %3i %3i|", (int)h->next, h->complete, h->char_length, h->glyph_length); printf("\n"); h = (glyphs + SKIP_LIST_DEPTH - 1)->next; for (; h; h = h->next) { printf("%8x: ", (int)h); for (g = h, i = ((glyph_run_t *)h)->level; i >= 0; i--, g--) printf("%8x %i %3i %3i|", (int)g->next, g->complete, g->char_length, g->glyph_length); printf("\n"); } } printf("--- done\n"); fflush(stdout); } -(void) _sanityChecks { glyph_run_t *g; g = (glyph_run_t *)&glyphs[SKIP_LIST_DEPTH - 1]; while (g->head.next) { NSAssert((glyph_run_t *)((glyph_run_t *)g->head.next)->prev == g, @"glyph structure corrupted: g->next->prev!=g"); g = (glyph_run_t *)g->head.next; } } -(glyph_run_t *)run_for_glyph_index: (unsigned int)glyphIndex : (unsigned int *)glyph_pos : (unsigned int *)char_pos { int level; glyph_run_head_t *h; int pos, cpos; if (glyphs->glyph_length <= glyphIndex) return NULL; if (cached_run) { if (glyphIndex >= cached_pos && glyphIndex < cached_pos + cached_run->head.glyph_length) { if (glyph_pos) *glyph_pos = cached_pos; if (char_pos) *char_pos = cached_cpos; return cached_run; } } pos = cpos = 0; level = SKIP_LIST_DEPTH; h = glyphs; while (1) { if (!h->complete) { h++; level--; if (!level) return NULL; continue; } if (glyphIndex >= pos + h->glyph_length) { pos += h->glyph_length; cpos += h->char_length; h = h->next; if (!h) return NULL; continue; } if (level > 1) { h++; level--; continue; } *glyph_pos = pos; if (char_pos) *char_pos = cpos; cached_run = (glyph_run_t *)h; cached_pos = pos; cached_cpos = cpos; return (glyph_run_t *)h; } } static glyph_run_t *run_for_character_index(unsigned int charIndex, glyph_run_head_t *glyphs, unsigned int *glyph_pos, unsigned int *char_pos) { int level; glyph_run_head_t *h; int pos, cpos; if (glyphs->char_length <= charIndex) return NULL; pos = cpos = 0; level = SKIP_LIST_DEPTH; h = glyphs; while (1) { if (!h->complete) { h++; level--; if (!level) return NULL; continue; } if (charIndex >= cpos + h->char_length) { pos += h->glyph_length; cpos += h->char_length; h = h->next; if (!h) return NULL; continue; } if (level > 1) { h++; level--; continue; } *glyph_pos = pos; if (char_pos) *char_pos = cpos; return (glyph_run_t *)h; } } /* Recalculates char_length, glyph_length, and complete for a glyph_run_head_t. All "children" of this head must have valid values. */ static void run_fix_head(glyph_run_head_t *h) { glyph_run_head_t *h2, *next; next = h->next; if (next) next++; h2 = h + 1; h->complete = 1; h->glyph_length = 0; h->char_length = 0; while (h2 != next) { if (h->complete) h->glyph_length += h2->glyph_length; h->char_length += h2->char_length; if (!h2->complete) h->complete = 0; h2 = h2->next; } } static glyph_run_t *run_insert(glyph_run_head_t **context) { glyph_run_head_t *h; glyph_run_t *r; int level; int i; level = random_level(); h = malloc(sizeof(glyph_run_head_t) * level + sizeof(glyph_run_t)); memset(h, 0, sizeof(glyph_run_head_t) * level + sizeof(glyph_run_t)); for (i = level; i >= 0; i--) { h->next = context[i]->next; context[i]->next = h; h++; } h--; r = (glyph_run_t *)h; r->level = level; r->prev = context[0]; return r; } -(void) _generateRunsToCharacter: (unsigned int)last { glyph_run_head_t *context[SKIP_LIST_DEPTH]; int positions[SKIP_LIST_DEPTH]; glyph_run_head_t *h; unsigned int pos; unsigned int length; int level; length = [_textStorage length]; if (last >= length) last = length - 1; h = glyphs; pos = 0; if (h->char_length > last) return; /* We haven't created any run for that character. Find the last run. */ for (level = SKIP_LIST_DEPTH; level; level--) { while (h->next) pos += h->char_length, h = h->next; context[level - 1] = h; positions[level - 1] = pos; h++; } h--; pos += h->char_length; /* Create runs and add them to the skip list until we're past our target. */ while (pos <= last) { NSRange maxRange; NSRange curRange; NSDictionary *attributes; glyph_run_head_t *new_head; glyph_run_t *new; int new_level; int i; maxRange = NSMakeRange(pos, length - pos); if (pos > 0) { maxRange.location--; maxRange.length++; } attributes = [_textStorage attributesAtIndex: pos longestEffectiveRange: &curRange inRange: maxRange]; /* Optimize run structure by merging with the previous run under some circumstances. See the comments in -invalidateGlyphsForCharacterRange:changeInLength:actualCharacterRange: for more information. */ if (curRange.location < pos && context[0]->char_length && context[0]->char_length < 16) { curRange.length -= pos - curRange.location; curRange.location = pos; new = (glyph_run_t *)context[0]; if (new->head.complete) { free(new->glyphs); new->glyphs = NULL; new->head.glyph_length = 0; new->head.complete = 0; } new->head.char_length += curRange.length; for (i = 1; i < SKIP_LIST_DEPTH; i++) { run_fix_head(context[i]); } pos = NSMaxRange(curRange); continue; } if (curRange.location < pos) { curRange.length -= pos - curRange.location; curRange.location = pos; } /* TODO: this shouldn't really be necessary if all searches inside runs were binary. but for now, it helps performance, and it keeps things more balanced when there are long runs of text. */ if (curRange.length > MAX_RUN_LENGTH) { unsigned int ch = curRange.location + MAX_RUN_LENGTH; ch = [self _findSafeBreakMovingForwardFrom: ch]; if (ch < NSMaxRange(curRange)) curRange.length = ch - curRange.location; } new_level = random_level(); /* Since we'll be creating these in order, we can be smart about picking new levels. */ { int i; glyph_num_end_runs++; for (i=0; i < SKIP_LIST_DEPTH - 2; i++) if (glyph_num_end_runs & (1 << i)) break; new_level = i; } new_head = malloc(sizeof(glyph_run_t) + sizeof(glyph_run_head_t) * new_level); memset(new_head, 0, sizeof(glyph_run_t) + sizeof(glyph_run_head_t) * new_level); new = (glyph_run_t *)(new_head + new_level); new->level = new_level; new->head.char_length = curRange.length; new->prev = context[0]; [self _run_cache_attributes: new : attributes]; h = &new->head; for (i = 0; i <= new_level; i++, h--) { h->char_length = new->head.char_length; context[i]->next = h; context[i] = h; } for (; i < SKIP_LIST_DEPTH; i++) { context[i]->char_length += new->head.char_length; context[i]->complete = 0; } pos += new->head.char_length; } [self _sanityChecks]; } /* Returns number of valid glyphs under h after generating up to last (sortof, not completely accurate). */ -(unsigned int) _generateGlyphs_char_r: (unsigned int)last : (unsigned int)pos : (int)level : (glyph_run_head_t *)h : (glyph_run_head_t *)stop : (BOOL *)all_complete { int total = 0, sub_total; BOOL c; *all_complete = YES; while (h != stop && (pos <= last || *all_complete)) { if (h->complete) { total += h->glyph_length; pos += h->char_length; h = h->next; continue; } if (pos > last) break; if (level) { if (h->next) sub_total = [self _generateGlyphs_char_r: last : pos : level - 1: h + 1: h->next + 1: &c]; else sub_total = [self _generateGlyphs_char_r: last : pos : level - 1: h + 1: NULL : &c]; if (!c) *all_complete = NO; else h->complete = 1; h->glyph_length = sub_total; total += sub_total; } else { [self _generateGlyphsForRun: (glyph_run_t *)h at: pos]; h->complete = 1; total += h->glyph_length; } pos += h->char_length; h = h->next; } if (h != stop) *all_complete = NO; return total; } -(void) _generateGlyphsUpToCharacter: (unsigned int)last { unsigned int length; BOOL dummy; /* Trying to do anything here while the text storage has unprocessed edits (ie. an edit count>0) breaks things badly, and in strange ways. Thus, we detect this and raise an exception. */ if ([_textStorage _editCount]) { [NSException raise: NSGenericException format: @"Glyph generation was triggered for a layout manager " @"while the text storage it was attached to had " @"unprocessed editing. This is not allowed. Glyph " @"generation may be triggered only at points where " @"calls to -beginEditing and -endEditing are " @"balanced."]; } if (!_textStorage) return; length = [_textStorage length]; if (!length) return; if (last >= length) last = length - 1; if (glyphs->char_length <= last) [self _generateRunsToCharacter: last]; // [self _glyphDumpRuns]; if ([self _generateGlyphs_char_r: last : 0 : SKIP_LIST_DEPTH - 1: glyphs : NULL : &dummy]) /*[self _glyphDumpRuns]*/; } -(void) _generateGlyphsUpToGlyph: (unsigned int)last { unsigned int length; if (!_textStorage) return; length = [_textStorage length]; /* OPT: this can be done __much__ more efficiently */ while (glyphs->glyph_length <= last && (glyphs->char_length < length || !glyphs->complete)) { [self _generateGlyphsUpToCharacter: glyphs->char_length]; } } -(glyph_run_t *) _glyphForCharacter: (unsigned int)target index: (unsigned int *)rindex positions: (unsigned int *)rpos : (unsigned int *)rcpos { glyph_run_t *r; unsigned int pos, cpos; int lo, hi, mid, i; r = run_for_character_index(target, glyphs, &pos, &cpos); if (!r) return NULL; target -= cpos; lo = 0; hi = r->head.glyph_length - 1; while (lo < hi) { mid = (lo + hi) / 2; if (r->glyphs[mid].char_offset > target) hi = mid - 1; else if (r->glyphs[mid].char_offset < target) lo = mid + 1; else hi = lo = mid; } i = lo; while (r->glyphs[i].char_offset > target) i--; while (i > 0 && r->glyphs[i - 1].char_offset == r->glyphs[i].char_offset) i--; *rindex = i; *rpos = pos; *rcpos = cpos; return r; } @end @implementation GSLayoutManager (glyphs) - (unsigned int) numberOfGlyphs { [self _generateGlyphsUpToCharacter: -1]; return glyphs->glyph_length; } - (NSGlyph) glyphAtIndex: (unsigned int)glyphIndex { BOOL valid; NSGlyph g; g = [self glyphAtIndex: glyphIndex isValidIndex: &valid]; if (valid) return g; [NSException raise: NSRangeException format: @"%s glyph index out of range", __PRETTY_FUNCTION__]; return 0; } - (NSGlyph) glyphAtIndex: (unsigned int)glyphIndex isValidIndex: (BOOL *)isValidIndex { glyph_run_t *r; int pos; *isValidIndex = NO; /* glyph '-1' is returned in other places as an "invalid" marker; this way, we can say that it isn't valid without building all glyphs */ /* TODO: check if this is really safe or smart. if it isn't, some other methods will need to be changed so they can return "no glyph index" in some other way. */ if (glyphIndex == (unsigned int)-1) return 0; if (glyphs->glyph_length <= glyphIndex) { [self _generateGlyphsUpToGlyph: glyphIndex]; if (glyphs->glyph_length <= glyphIndex) return 0; } r = run_for_glyph_index(glyphIndex, glyphs, &pos, NULL); if (!r) /* shouldn't happen */ return 0; glyphIndex -= pos; *isValidIndex = YES; return r->glyphs[glyphIndex].g; } - (unsigned int) getGlyphs: (NSGlyph *)glyphArray range: (NSRange)glyphRange { glyph_run_t *r; NSGlyph *g; unsigned int pos; unsigned int num; unsigned int i, j, k; if (glyphRange.length == 0) { return 0; } pos = NSMaxRange(glyphRange) - 1; if (glyphs->glyph_length <= pos) { [self _generateGlyphsUpToGlyph: pos]; if (glyphs->glyph_length <= pos) { [NSException raise: NSRangeException format: @"%s glyph range out of range", __PRETTY_FUNCTION__]; return 0; } } r = run_for_glyph_index(glyphRange.location, glyphs, &pos, NULL); if (!r) { /* shouldn't happen */ [NSException raise: NSRangeException format: @"%s glyph range out of range", __PRETTY_FUNCTION__]; return 0; } g = glyphArray; num = 0; while (1) { if (pos < glyphRange.location) j = glyphRange.location - pos; else j = 0; k = NSMaxRange(glyphRange) - pos; if (k > r->head.glyph_length) k = r->head.glyph_length; if (k <= j) break; /* TODO? only "displayed" glyphs */ for (i = j; i < k; i++) { *g++=r->glyphs[i].g; num++; } pos += r->head.glyph_length; r = (glyph_run_t *)r->head.next; if (!r) break; } return num; } - (unsigned int) characterIndexForGlyphAtIndex: (unsigned int)glyphIndex { glyph_run_t *r; int pos, cpos; if (glyphs->glyph_length <= glyphIndex) { [self _generateGlyphsUpToGlyph: glyphIndex]; if (glyphs->glyph_length <= glyphIndex) { [NSException raise: NSRangeException format: @"%s glyph index out of range", __PRETTY_FUNCTION__]; return 0; } } r = run_for_glyph_index(glyphIndex, glyphs, &pos, &cpos); if (!r) { [NSException raise: NSRangeException format: @"%s glyph index out of range", __PRETTY_FUNCTION__]; return 0; } return cpos + r->glyphs[glyphIndex - pos].char_offset; } - (NSRange) characterRangeForGlyphRange: (NSRange)glyphRange actualGlyphRange: (NSRange *)actualGlyphRange { glyph_run_t *r; NSRange real_range, char_range; unsigned int cpos, pos; unsigned j; if (NSMaxRange(glyphRange) == 0) { if (actualGlyphRange) *actualGlyphRange = glyphRange; return NSMakeRange(0, 0); } pos = NSMaxRange(glyphRange) - 1; if (glyphs->glyph_length <= pos) { [self _generateGlyphsUpToGlyph: pos]; if (glyphs->glyph_length <= pos) { [NSException raise: NSRangeException format: @"%s glyph range out of range", __PRETTY_FUNCTION__]; return NSMakeRange(0, 0); } } r = run_for_glyph_index(glyphRange.location, glyphs, &pos, &cpos); if (!r) { [NSException raise: NSRangeException format: @"%s glyph range out of range", __PRETTY_FUNCTION__]; return NSMakeRange(0, 0); } j = cpos + r->glyphs[glyphRange.location - pos].char_offset; char_range.location = j; /* scan backwards to find the real first glyph */ { glyph_run_t *r2; unsigned int adj, cadj; int i; i = glyphRange.location - pos; r2 = r; adj = pos; cadj = cpos; while (r2->glyphs[i].char_offset + cadj == j) { i--; while (i < 0) { if (!r2->prev) break; r2 = (glyph_run_t *)r2->prev; i = r2->head.glyph_length - 1; adj -= r2->head.glyph_length; cadj -= r2->head.char_length; } if (i < 0) break; } real_range.location = i + 1 + adj; } /* the range is likely short, so we can do better then a completely new search */ r = run_for_glyph_index(glyphRange.location + glyphRange.length - 1, glyphs, &pos, &cpos); if (!r) { [NSException raise: NSRangeException format: @"%s glyph range out of range", __PRETTY_FUNCTION__]; return NSMakeRange(0, 0); } j = cpos + r->glyphs[glyphRange.location + glyphRange.length - 1 - pos].char_offset; /* scan forwards to find the real last glyph */ { glyph_run_t *r2; unsigned int adj, cadj; unsigned int last = 0; unsigned int i; i = glyphRange.location + glyphRange.length - 1 - pos; r2 = r; adj = pos; cadj = cpos; while (r2->glyphs[i].char_offset + cadj == j) { GLYPH_STEP_FORWARD(r2,i,adj,cadj) if (i==r2->head.glyph_length) { last = cadj + r2->head.char_length; goto found; } } last = r2->glyphs[i].char_offset + cadj; found: real_range.length = i + adj - real_range.location; char_range.length = last - char_range.location; } if (actualGlyphRange) *actualGlyphRange = real_range; return char_range; } - (NSRange) glyphRangeForCharacterRange: (NSRange)charRange actualCharacterRange: (NSRange *)actualCharRange { NSRange char_range, glyph_range; glyph_run_t *r; unsigned int cpos, pos; unsigned int i, target; /* TODO: should this really be valid? This is causing all kinds of problems when border glyph ranges are passed to other functions. Better to keep the layout manager clean of all this and let NSTextView deal with it. */ #if 1 if (charRange.length == 0 && charRange.location == [[_textStorage string] length]) { if (actualCharRange) *actualCharRange = NSMakeRange([[_textStorage string] length], 0); return NSMakeRange([self numberOfGlyphs], 0); } #endif /* TODO: this case is also dubious, but it makes sense to return like this, so it's mostly the caller's fault */ if (charRange.length == 0) { NSLog(@"Warning: %s called with zero-length range", __PRETTY_FUNCTION__); if (actualCharRange) *actualCharRange = NSMakeRange(0, 0); return NSMakeRange(0, 0); } pos = NSMaxRange(charRange) - 1; [self _generateGlyphsUpToCharacter: pos]; if (glyphs->char_length <= pos) { [NSException raise: NSRangeException format: @"%s character range out of range", __PRETTY_FUNCTION__]; return NSMakeRange(0, 0); } target = charRange.location; r = [self _glyphForCharacter: target index: &i positions: &pos : &cpos]; glyph_range.location = i + pos; char_range.location = r->glyphs[i].char_offset + cpos; target = NSMaxRange(charRange) - 1; r = [self _glyphForCharacter: target index: &i positions: &pos : &cpos]; GLYPH_SCAN_FORWARD(r, i, pos, cpos, r->glyphs[i].char_offset + cpos <= target) glyph_range.length = i + pos - glyph_range.location; if (i == r->head.glyph_length) char_range.length = glyphs->char_length - char_range.location; else char_range.length = r->glyphs[i].char_offset + cpos - char_range.location; if (actualCharRange) *actualCharRange = char_range; return glyph_range; } /* TODO? this might currently lead to continued runs not being marked as continued runs. this will only happen at safe break spots, though, so it should still be safe. might lose opportunities to merge runs, though. */ /* This is hairy. The ranges passed in and out of this method are ranges _after_ the change. Internally, we switch between before- and after-indices. Comments mark the places where we switch. */ - (void) invalidateGlyphsForCharacterRange: (NSRange)range changeInLength: (int)lengthChange actualCharacterRange: (NSRange *)actualRange { BOOL trailing; glyph_run_head_t *context[SKIP_LIST_DEPTH]; glyph_run_head_t *h; glyph_run_t *r; NSRange rng; int position[SKIP_LIST_DEPTH]; unsigned int cpos; unsigned int ts_length; int gap; int level; unsigned int ch; /* We always clear out the cached run information to be safe. */ cached_run = NULL; /* Set it now for early returns. */ if (actualRange) *actualRange = range; // printf("\n +++ range=(%i+%i) lengthChange=%i\n", range.location, range.length, lengthChange); [self _sanityChecks]; // [self _glyphDumpRuns]; /* Switch to before-indices. */ range.length -= lengthChange; // printf("invalidate %i+%i=%i\n", range.location, range.length, range.location+range.length); ts_length = [_textStorage length]; /* Find out what range we actually need to invalidate. This depends on how context affects glyph generation. */ ch = range.location; if (ch > 0) { ch = [self _findSafeBreakMovingBackwardFrom: ch]; range.length += range.location - ch; range.location = ch; } ch = ch + range.length + lengthChange; if (ch < ts_length) { ch = [self _findSafeBreakMovingForwardFrom: ch]; ch -= lengthChange; range.length = ch - range.location; } /* We now have the range to invalidate in 'range' (indices before the change). */ // printf("adjusted to %i+%i\n", range.location, range.length); ch = range.location; /* Find the first run (and context) for the range. */ h = glyphs; cpos = 0; for (level = SKIP_LIST_DEPTH - 1; level >= 0; level--) { while (cpos + h->char_length <= ch) { cpos += h->char_length; h = h->next; if (!h) { /* No runs have been created for the range, so there's nothing to invalidate. */ // printf("no runs created yet\n"); return; } } context[level] = h; position[level] = cpos; h++; } h--; r = (glyph_run_t *)h; /* Now we have the first run that intersects the range we're invalidating in 'r' (and context in 'context' and 'position'). */ // printf("split if %i+%i > %i+%i\n", cpos, r->head.char_length, ch, range.length); /* If 'r' extends beyond the invalidated range, split off the trailing, valid part to a new run. The reason we need to do this is that we must have runs for the first glyph not invalidated or the deletion loop below will fail. */ if (cpos + r->head.char_length > ch + range.length && ch != cpos) { glyph_run_t *new; glyph_run_head_t *hn; int i; new = run_insert(context); new->head.char_length = cpos + r->head.char_length - (ch + range.length); [self _run_copy_attributes: new : r]; /* OPT: keep valid glyphs this seems to be a fairly rare case */ hn = &new->head; hn--; for (i = 1; i <= new->level; i++, hn--) run_fix_head(hn); if (new->head.next) ((glyph_run_t *)new->head.next)->prev = (glyph_run_head_t *)new; r->head.char_length -= new->head.char_length; } /* Set things up. We want 'r' to be the last run we want to keep. */ if (ch == cpos) { /* This run begins exactly at the beginning of the invalidated range. Since we want 'r' to be the last run to keep, we actually want it to be the previous run. Thus, we step backwards in the skip list to get the previous run. */ glyph_run_head_t *h2; h2 = h - r->level; h = context[r->level + 1]; cpos = position[r->level + 1]; h++; for (level = r->level; level >= 0; level--) { while (h->next != h2) { cpos += h->char_length; h = h->next; } position[level] = cpos; context[level] = h; h++; h2++; } h--; r = (glyph_run_t *)h; gap = 0; } else { /* This run begins before the invalidated range. Resize it so it ends just before it. */ gap = r->head.char_length + cpos - ch; r->head.char_length = ch - cpos; /* OPT!!! keep valid glyphs */ if (r->head.complete) { r->head.glyph_length = 0; r->head.complete = 0; free(r->glyphs); r->glyphs = NULL; } } /* 'r' is the last run we should keep, 'context' and 'position' are set up for it, 'gap' is the number of characters already deleted. Now we delete all runs completely invalidated. */ { glyph_run_t *next; unsigned int max = range.location + range.length; int i; cpos += gap + r->head.char_length; while (1) { next = (glyph_run_t *)r->head.next; /* We reached the end of all created runs. */ if (!next) break; NSAssert(max >= cpos, @"no run for first glyph beyond invalidated range"); /* Clean cut, just stop. */ if (max == cpos) break; /* Part of this run extends beyond the invalidated range. Resize it so it's completely beyond the invalidated range and stop. */ if (max < cpos + next->head.char_length) { glyph_run_head_t *hn; /* adjust final run */ /* OPT!!! keep valid glyphs */ if (next->head.complete) { next->head.complete = 0; next->head.glyph_length = 0; free(next->glyphs); next->glyphs = NULL; } next->head.char_length -= max - cpos; hn = &next->head; hn--; for (i = 1; i <= next->level; i++, hn--) run_fix_head(hn); break; } cpos += next->head.char_length; /* This run is completely inside the invalidated range. Remove it. The context run heads will be adjusted later. */ if (next->head.next) ((glyph_run_t *)next->head.next)->prev = &r->head; for (i = 0; i <= next->level; i++) context[i]->next = context[i]->next->next; h = &next->head; if (h->complete) free(next->glyphs); [self _run_free_attributes: next]; h -= next->level; free(h); h = NULL; } trailing = !next; } /* printf("deleted\n"); [self _glyphDumpRuns];*/ /* Switch back to after-indices. */ range.length += lengthChange; /* 'r' is the last run we want to keep, and the next run is the next uninvalidated run. We need to insert new runs for invalidated range after 'r'. As we create new runs, we move the context forward. When we do this, we adjust their heads with updated information. When we're done, we update all the remaining heads. */ // printf("create runs for %i+%i\n", range.location, range.length); { /* OPT: this is creating more runs than it needs to */ NSDictionary *attributes; glyph_run_t *new; unsigned int max = range.location + range.length; int i; ch = range.location; while (ch < max) { attributes = [_textStorage attributesAtIndex: ch longestEffectiveRange: &rng inRange: NSMakeRange(0, [_textStorage length])]; /* printf("at %i, max=%i, effective range (%i+%i)\n", ch, max, rng.location, rng.length);*/ /* Catch a common case. If the new run would be a continuation of the previous run, and the previous run is short, we resize the previous run instead of creating a new run. (Note that we must make sure that we don't merge with the dummy runs at the very front.) This happens a lot with repeated single-character insertions, aka. typing in a text view. */ if (rng.location < ch && context[0]->char_length && context[0]->char_length < 16) { rng.length -= ch - rng.location; rng.location = ch; if (ch + rng.length > max) { rng.length = max - ch; } new = (glyph_run_t *)context[0]; if (new->head.complete) { free(new->glyphs); new->glyphs = NULL; new->head.glyph_length = 0; new->head.complete = 0; } new->head.char_length += rng.length; ch = NSMaxRange(rng); continue; } new = run_insert(context); /* We have the longest range the attributes allow us to create a run for. Since this might overlap the previous and next runs, we might need to adjust the location and length of the range we create a run for. OPT: If the overlapped run is short, we might want to clear out its glyphs and extend it to cover our range. This should result in fewer runs being created for large sequences of single character adds. */ if (rng.location < ch) { /* The new run has the same attributes as the previous run, so we mark it is as a continued run. */ new->continued = 1; rng.length -= ch - rng.location; rng.location = ch; } if (ch + rng.length > max) { /* The new run has the same attributes as the next run, so we mark the next run as a continued run. */ if (new->head.next) ((glyph_run_t *)new->head.next)->continued = 1; rng.length = max - ch; } /* See comment in -_generateRunsToCharacter:. */ if (rng.length > MAX_RUN_LENGTH) { unsigned int safe_break = rng.location + MAX_RUN_LENGTH; safe_break = [self _findSafeBreakMovingForwardFrom: safe_break]; if (safe_break < NSMaxRange(rng)) rng.length = safe_break - rng.location; } // printf("adjusted length: %i\n", rng.length); new->head.char_length = rng.length; [self _run_cache_attributes: new : attributes]; h = &new->head; for (i = 0; i <= new->level; i++, h--) { if (i) run_fix_head(context[i]); context[i] = h; } ch += new->head.char_length; } if (context[0]->next) ((glyph_run_t *)context[0]->next)->prev = context[0]; } /* Fix up the remaining context run heads. */ { int i; for (i = 1; i < SKIP_LIST_DEPTH; i++) { run_fix_head(context[i]); } } if (actualRange) *actualRange = range; // [self _glyphDumpRuns]; [self _sanityChecks]; } #define GET_GLYPH \ glyph_run_t *r; \ int pos, cpos; \ \ if (glyphs->glyph_length <= idx) \ { \ [self _generateGlyphsUpToGlyph: idx]; \ if (glyphs->glyph_length <= idx) \ { \ [NSException raise: NSRangeException \ format: @"%s glyph range out of range", __PRETTY_FUNCTION__]; \ } \ } \ \ r = run_for_glyph_index(idx, glyphs, &pos, &cpos); \ if (!r) \ { \ [NSException raise: NSRangeException \ format: @"%s glyph range out of range", __PRETTY_FUNCTION__]; \ } \ idx -= pos; - (void) setDrawsOutsideLineFragment: (BOOL)flag forGlyphAtIndex: (unsigned int)idx { GET_GLYPH r->glyphs[idx].drawsOutsideLineFragment = !!flag; } - (BOOL) drawsOutsideLineFragmentForGlyphAtIndex: (unsigned int)idx { GET_GLYPH return r->glyphs[idx].drawsOutsideLineFragment; } - (void) setNotShownAttribute: (BOOL)flag forGlyphAtIndex: (unsigned int)idx { GET_GLYPH r->glyphs[idx].isNotShown = !!flag; } - (BOOL) notShownAttributeForGlyphAtIndex: (unsigned int)idx { GET_GLYPH return r->glyphs[idx].isNotShown; } - (NSFont *) effectiveFontForGlyphAtIndex: (unsigned int)idx range: (NSRange *)range { GET_GLYPH if (range) *range = NSMakeRange(pos, r->head.glyph_length); return r->font; } - (void) insertGlyph: (NSGlyph)aGlyph atGlyphIndex: (unsigned int)glyphIndex characterIndex: (unsigned int)charIndex { NSLog(@"Internal method %s called", __PRETTY_FUNCTION__); } - (void) replaceGlyphAtIndex: (unsigned int)glyphIndex withGlyph: (NSGlyph)newGlyph { NSLog(@"Internal method %s called", __PRETTY_FUNCTION__); } - (void) deleteGlyphsInRange: (NSRange)aRange { NSLog(@"Internal method %s called", __PRETTY_FUNCTION__); } - (void) setCharacterIndex: (unsigned int)charIndex forGlyphAtIndex: (unsigned int)glyphIndex { NSLog(@"Internal method %s called", __PRETTY_FUNCTION__); } - (void) setIntAttribute: (int)attributeTag value: (int)anInt forGlyphAtIndex: (unsigned int)glyphIndex { [self subclassResponsibility: _cmd]; } - (int) intAttribute: (int)attributeTag forGlyphAtIndex: (unsigned int)glyphIndex { [self subclassResponsibility: _cmd]; return 0; } @end /***** Layout handling *****/ @implementation GSLayoutManager (layout_helpers) -(void) _invalidateLayoutFromContainer: (int)idx { int i, j; textcontainer_t *tc; linefrag_t *lf; extra_textcontainer = nil; for (i = idx, tc = textcontainers + idx; i < num_textcontainers; i++, tc++) { tc->complete = NO; if (tc->linefrags) { for (j = 0, lf = tc->linefrags; j < tc->num_linefrags + tc->num_soft; j++, lf++) { if (lf->points) free(lf->points); if (lf->attachments) free(lf->attachments); } free(tc->linefrags); } tc->linefrags = NULL; tc->num_linefrags = tc->num_soft = 0; tc->size_linefrags = 0; tc->pos = tc->length = 0; tc->was_invalidated = YES; } for (i = idx - 1, tc = textcontainers + idx - 1; i >= 0; i--, tc--) { if (tc->num_linefrags) { layout_glyph = tc->pos + tc->length; if (layout_glyph == glyphs->glyph_length) layout_char = glyphs->char_length; else layout_char = [self characterIndexForGlyphAtIndex: layout_glyph]; /* TODO? */ return; } } layout_glyph = layout_char = 0; } -(void) _freeLayout { [self _invalidateLayoutFromContainer: 0]; } -(void) _doLayout { int i, j; textcontainer_t *tc; unsigned int next; NSRect prev; BOOL delegate_responds; delegate_responds = [_delegate respondsToSelector: @selector(layoutManager:didCompleteLayoutForTextContainer:atEnd:)]; next = layout_glyph; for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) { if (tc->complete) continue; while (1) { if (tc->num_linefrags) prev = tc->linefrags[tc->num_linefrags - 1].rect; else prev = NSZeroRect; j = [typesetter layoutGlyphsInLayoutManager: self inTextContainer: tc->textContainer startingAtGlyphIndex: next previousLineFragmentRect: prev nextGlyphIndex: &next numberOfLineFragments: 0]; if (j) break; } tc->complete = YES; if (tc->num_soft) { /* If there is any soft invalidated layout information left, remove it. */ int k; linefrag_t *lf; for (k = tc->num_linefrags, lf = tc->linefrags + k; k < tc->num_linefrags + tc->num_soft; k++, lf++) { if (lf->points) { free(lf->points); lf->points = NULL; } if (lf->attachments) { free(lf->attachments); lf->attachments = NULL; } } tc->num_soft = 0; } if (delegate_responds) { [_delegate layoutManager: self didCompleteLayoutForTextContainer: tc->textContainer atEnd: j == 2]; /* The call might have resulted in more text containers being added, so 'textcontainers' might have moved. */ tc = textcontainers + i; } if (j == 2) { break; } if (i == num_textcontainers && delegate_responds) { [_delegate layoutManager: self didCompleteLayoutForTextContainer: nil atEnd: NO]; } } } -(void) _doLayoutToGlyph: (unsigned int)glyphIndex { [self _doLayout]; } -(void) _doLayoutToContainer: (int)cindex { [self _doLayout]; } -(void) _didInvalidateLayout { int i; textcontainer_t *tc; for (tc = textcontainers, i = 0; i < num_textcontainers; i++, tc++) { tc->was_invalidated = NO; } } @end @implementation GSLayoutManager (layout) /* In the general case, we can't make any assumptions about how layout might interact between line frag rects. To be safe in all cases, we must invalidate all layout information. TODO: We could handle this by assuming that whoever calls this knows exactly what needs to be invalidated. We won't be using it internally, anyway, so it doesn't matter much to us, and it would make more advanced things possible for external callers. On the other hand, it would be easy to break things by calling this incorrectly. */ - (void) invalidateLayoutForCharacterRange: (NSRange)aRange isSoft: (BOOL)flag actualCharacterRange: (NSRange *)actualRange { [self _invalidateLayoutFromContainer: 0]; } #define SETUP_STUFF \ unsigned int max = glyphRange.location + glyphRange.length; \ \ [self _generateGlyphsUpToGlyph: max - 1]; \ if (glyphs->glyph_length < max) \ { \ [NSException raise: NSRangeException \ format: @"%s: glyph range out of range", __PRETTY_FUNCTION__]; \ return; \ } - (void) setTextContainer: (NSTextContainer *)aTextContainer forGlyphRange: (NSRange)glyphRange { textcontainer_t *tc; int i; SETUP_STUFF for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) if (tc->textContainer == aTextContainer) break; if (i == num_textcontainers) { NSLog(@"%s: doesn't own text container", __PRETTY_FUNCTION__); return; } /* Assume that no line frags means that layout hasn't started yet. */ if (tc->num_linefrags) { if (glyphRange.location != tc->pos + tc->length) { [NSException raise: NSRangeException format: @"%s: glyph range not consistent with existing layout", __PRETTY_FUNCTION__]; return; } tc->length += glyphRange.length; } else if (!i) { if (glyphRange.location) { [NSException raise: NSRangeException format: @"%s: glyph range not consistent with existing layout", __PRETTY_FUNCTION__]; return; } tc->pos = 0; tc->length = glyphRange.length; } else { if (tc[-1].pos + tc[-1].length != glyphRange.location) { [NSException raise: NSRangeException format: @"%s: glyph range not consistent with existing layout", __PRETTY_FUNCTION__]; return; } tc->pos = glyphRange.location; tc->length = glyphRange.length; } { unsigned int gpos; unsigned int g; glyph_t *glyph; glyph_run_t *run = run_for_glyph_index(glyphRange.location, glyphs, &gpos, NULL); g = glyphRange.location; glyph = &run->glyphs[g - gpos]; while (g < glyphRange.location + glyphRange.length) { if (g == gpos + run->head.glyph_length) { gpos += run->head.glyph_length; run = (glyph_run_t *)run->head.next; glyph = run->glyphs; } glyph->isNotShown = NO; glyph->drawsOutsideLineFragment = NO; g++; glyph++; } } layout_glyph = tc->pos + tc->length; if (layout_glyph == glyphs->glyph_length) layout_char = glyphs->char_length; else layout_char = [self characterIndexForGlyphAtIndex: layout_glyph]; } - (void) setLineFragmentRect: (NSRect)fragmentRect forGlyphRange: (NSRange)glyphRange usedRect: (NSRect)usedRect { textcontainer_t *tc; int i; linefrag_t *lf; SETUP_STUFF for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) { if (tc->pos <= glyphRange.location && tc->pos + tc->length >= glyphRange.location + glyphRange.length) break; } if (i == num_textcontainers) { [NSException raise: NSRangeException format: @"%s: glyph range not consistent with existing layout", __PRETTY_FUNCTION__]; return; } /* Make sure the given glyph range matches earlier layout. */ if (!tc->num_linefrags) { if (glyphRange.location != tc->pos) { [NSException raise: NSRangeException format: @"%s: glyph range not consistent with existing layout", __PRETTY_FUNCTION__]; return; } } else { lf = &tc->linefrags[tc->num_linefrags - 1]; if (lf->pos + lf->length != glyphRange.location) { [NSException raise: NSRangeException format: @"%s: glyph range not consistent with existing layout", __PRETTY_FUNCTION__]; return; } } if (!(tc->num_linefrags + tc->num_soft)) { if (!tc->size_linefrags) { tc->size_linefrags = 16; tc->linefrags = malloc(sizeof(linefrag_t) * tc->size_linefrags); } tc->num_linefrags = 1; lf = tc->linefrags; } else if (!tc->num_soft) { if (tc->size_linefrags <= tc->num_linefrags) { tc->size_linefrags += tc->size_linefrags / 2; tc->linefrags = realloc(tc->linefrags, sizeof(linefrag_t) * tc->size_linefrags); } tc->num_linefrags++; lf = &tc->linefrags[tc->num_linefrags - 1]; } else { int i; for (i = tc->num_linefrags, lf = tc->linefrags + i; i < tc->num_linefrags + tc->num_soft; i++, lf++) { if (lf->pos >= NSMaxRange(glyphRange)) break; if (lf->points) { free(lf->points); lf->points = NULL; } if (lf->attachments) { free(lf->attachments); lf->attachments = NULL; } } if (i == tc->num_linefrags) { /* If we should keep all soft frags, we need to enlarge the array to fit the new line frag. */ if (tc->size_linefrags <= tc->num_linefrags + tc->num_soft) { tc->size_linefrags += tc->size_linefrags / 2; tc->linefrags = realloc(tc->linefrags, sizeof(linefrag_t) * tc->size_linefrags); } memmove(&tc->linefrags[tc->num_linefrags + 1], &tc->linefrags[tc->num_linefrags], tc->num_soft * sizeof(linefrag_t)); } else if (i > tc->num_linefrags + 1) { tc->num_soft -= i - tc->num_linefrags; memmove(&tc->linefrags[tc->num_linefrags + 1], &tc->linefrags[i], tc->num_soft * sizeof(linefrag_t)); } else { /* If i == tc->num_linefrags + 1, we're lucky and everything already lines up, so no moving is necessary. */ tc->num_soft--; } tc->num_linefrags++; lf = &tc->linefrags[tc->num_linefrags - 1]; } memset(lf, 0, sizeof(linefrag_t)); lf->rect = fragmentRect; lf->used_rect = usedRect; lf->pos = glyphRange.location; lf->length = glyphRange.length; } - (void) setLocation: (NSPoint)location forStartOfGlyphRange: (NSRange)glyphRange { textcontainer_t *tc; int i; linefrag_t *lf; linefrag_point_t *lp; SETUP_STUFF for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) { if (tc->pos <= glyphRange.location && tc->pos + tc->length >= glyphRange.location + glyphRange.length) break; } if (i == num_textcontainers) { [NSException raise: NSRangeException format: @"%s: glyph range not consistent with existing layout", __PRETTY_FUNCTION__]; return; } for (i = tc->num_linefrags - 1, lf = tc->linefrags + i; i >= 0; i--, lf--) { if (lf->pos <= glyphRange.location && lf->pos + lf->length >= glyphRange.location + glyphRange.length) break; } if (i < 0) { [NSException raise: NSRangeException format: @"%s: glyph range not consistent with existing layout", __PRETTY_FUNCTION__]; return; } if (!lf->num_points) { if (glyphRange.location != lf->pos) { [NSException raise: NSRangeException format: @"%s: glyph range not consistent with existing layout", __PRETTY_FUNCTION__]; return; } lp = lf->points = malloc(sizeof(linefrag_point_t)); lf->num_points++; } else { lp = &lf->points[lf->num_points - 1]; if (lp->pos + lp->length != glyphRange.location) { [NSException raise: NSRangeException format: @"%s: glyph range not consistent with existing layout", __PRETTY_FUNCTION__]; return; } lf->num_points++; lf->points = realloc(lf->points, sizeof(linefrag_point_t) * lf->num_points); lp = &lf->points[lf->num_points - 1]; } lp->pos = glyphRange.location; lp->length = glyphRange.length; lp->p = location; } -(void) setAttachmentSize: (NSSize)size forGlyphRange: (NSRange)glyphRange { textcontainer_t *tc; int i; linefrag_t *lf; linefrag_attachment_t *la; SETUP_STUFF for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) { if (tc->pos <= glyphRange.location && tc->pos + tc->length >= glyphRange.location + glyphRange.length) break; } if (i == num_textcontainers) { [NSException raise: NSRangeException format: @"%s: glyph range not consistent with existing layout", __PRETTY_FUNCTION__]; return; } for (i = 0, lf = tc->linefrags; i < tc->num_linefrags; i++, lf++) { if (lf->pos <= glyphRange.location && lf->pos + lf->length >= glyphRange.location + glyphRange.length) break; } if (i == tc->num_linefrags) { [NSException raise: NSRangeException format: @"%s: glyph range not consistent with existing layout", __PRETTY_FUNCTION__]; return; } /* TODO: we do no sanity checking of attachment size ranges. might want to consider doing it */ lf->attachments = realloc(lf->attachments, sizeof(linefrag_attachment_t) * (lf->num_attachments + 1)); la = &lf->attachments[lf->num_attachments++]; memset(la, 0, sizeof(linefrag_attachment_t)); la->pos = glyphRange.location; la->length = glyphRange.length; la->size = size; } #undef SETUP_STUFF - (NSTextContainer *) textContainerForGlyphAtIndex: (unsigned int)glyphIndex effectiveRange: (NSRange *)effectiveRange { textcontainer_t *tc; int i; [self _doLayoutToGlyph: glyphIndex]; for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) if (tc->pos + tc->length > glyphIndex) break; if (i == num_textcontainers) { NSLog(@"%s: can't find text container for glyph (internal error)", __PRETTY_FUNCTION__); return nil; } if (effectiveRange) { [self _doLayoutToContainer: i]; tc = textcontainers + i; *effectiveRange = NSMakeRange(tc->pos, tc->length); } return tc->textContainer; } - (NSRect) lineFragmentRectForGlyphAtIndex: (unsigned int)glyphIndex effectiveRange: (NSRange *)effectiveGlyphRange { int i; textcontainer_t *tc; linefrag_t *lf; [self _doLayoutToGlyph: glyphIndex]; for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) if (tc->pos + tc->length > glyphIndex) break; if (i == num_textcontainers) { NSLog(@"%s: can't find text container for glyph (internal error)", __PRETTY_FUNCTION__); return NSZeroRect; } for (i = 0, lf = tc->linefrags; i < tc->num_linefrags; i++, lf++) if (lf->pos + lf->length > glyphIndex) break; if (i == tc->num_linefrags) { NSLog(@"%s: can't find line frag rect for glyph (internal error)", __PRETTY_FUNCTION__); return NSZeroRect; } if (effectiveGlyphRange) { *effectiveGlyphRange = NSMakeRange(lf->pos, lf->length); } return lf->rect; } - (NSRect) lineFragmentUsedRectForGlyphAtIndex: (unsigned int)glyphIndex effectiveRange: (NSRange *)effectiveGlyphRange { int i; textcontainer_t *tc; linefrag_t *lf; [self _doLayoutToGlyph: glyphIndex]; for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) if (tc->pos + tc->length > glyphIndex) break; if (i == num_textcontainers) { NSLog(@"%s: can't find text container for glyph (internal error)", __PRETTY_FUNCTION__); return NSMakeRect(0, 0, 0, 0); } for (i = 0, lf = tc->linefrags; i < tc->num_linefrags; i++, lf++) if (lf->pos + lf->length > glyphIndex) break; if (i == tc->num_linefrags) { NSLog(@"%s: can't find line frag rect for glyph (internal error)", __PRETTY_FUNCTION__); return NSMakeRect(0, 0, 0, 0); } if (effectiveGlyphRange) { *effectiveGlyphRange = NSMakeRange(lf->pos, lf->length); } return lf->used_rect; } - (NSRange) rangeOfNominallySpacedGlyphsContainingIndex: (unsigned int)glyphIndex startLocation: (NSPoint *)p { int i; textcontainer_t *tc; linefrag_t *lf; linefrag_point_t *lp; [self _doLayoutToGlyph: glyphIndex]; for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) if (tc->pos + tc->length > glyphIndex) break; if (i == num_textcontainers) { NSLog(@"%s: can't find text container for glyph (internal error)", __PRETTY_FUNCTION__); return NSMakeRange(NSNotFound, 0); } for (i = 0, lf = tc->linefrags; i < tc->num_linefrags; i++, lf++) if (lf->pos + lf->length > glyphIndex) break; if (i == tc->num_linefrags) { NSLog(@"%s: can't find line frag rect for glyph (internal error)", __PRETTY_FUNCTION__); return NSMakeRange(NSNotFound, 0); } for (i = 0, lp = lf->points; i < lf->num_points; i++, lp++) if (lp->pos + lp->length > glyphIndex) break; if (i == lf->num_points) { NSLog(@"%s: can't find location for glyph (internal error)", __PRETTY_FUNCTION__); return NSMakeRange(NSNotFound, 0); } if (p) *p = lp->p; return NSMakeRange(lp->pos, lp->length); } - (NSRange) rangeOfNominallySpacedGlyphsContainingIndex:(unsigned int)glyphIndex { return [self rangeOfNominallySpacedGlyphsContainingIndex: glyphIndex startLocation: NULL]; } /* The union of all line frag rects' used rects. */ - (NSRect) usedRectForTextContainer: (NSTextContainer *)container { textcontainer_t *tc; linefrag_t *lf; int i; NSRect used; for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) if (tc->textContainer == container) break; if (i == num_textcontainers) { NSLog(@"%s: doesn't own text container", __PRETTY_FUNCTION__); return NSMakeRect(0, 0, 0, 0); } [self _doLayoutToContainer: i]; tc = textcontainers + i; used = NSZeroRect; for (i = 0, lf = tc->linefrags; i < tc->num_linefrags; i++, lf++) used = NSUnionRect(used, lf->used_rect); return used; } - (NSRange) glyphRangeForTextContainer: (NSTextContainer *)container { textcontainer_t *tc; int i; for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) if (tc->textContainer == container) break; if (i == num_textcontainers) { NSLog(@"%s: doesn't own text container", __PRETTY_FUNCTION__); return NSMakeRange(NSNotFound, 0); } [self _doLayoutToContainer: i]; tc = textcontainers + i; return NSMakeRange(tc->pos, tc->length); } /* TODO: make more efficient */ - (NSArray *) textContainers { NSMutableArray *ma; int i; ma = [[NSMutableArray alloc] initWithCapacity: num_textcontainers]; for (i = 0; i < num_textcontainers; i++) [ma addObject: textcontainers[i].textContainer]; return [ma autorelease]; } - (void) addTextContainer: (NSTextContainer *)container { [self insertTextContainer: container atIndex: num_textcontainers]; } - (void) insertTextContainer: (NSTextContainer *)aTextContainer atIndex: (unsigned int)index { unsigned int i; if (index < num_textcontainers) [self _invalidateLayoutFromContainer: index]; num_textcontainers++; textcontainers = realloc(textcontainers, sizeof(textcontainer_t) * num_textcontainers); for (i = num_textcontainers - 1; i > index; i--) textcontainers[i] = textcontainers[i - 1]; memset(&textcontainers[i], 0, sizeof(textcontainer_t)); textcontainers[i].textContainer = [aTextContainer retain]; [aTextContainer setLayoutManager: self]; [self _didInvalidateLayout]; } - (void) removeTextContainerAtIndex: (unsigned int)index { int i; textcontainer_t *tc = &textcontainers[index]; [self _invalidateLayoutFromContainer: index]; [tc->textContainer setLayoutManager: nil]; [tc->textContainer release]; num_textcontainers--; for (i = index; i < num_textcontainers; i++) textcontainers[i] = textcontainers[i + 1]; if (num_textcontainers) textcontainers = realloc(textcontainers, sizeof(textcontainer_t) * num_textcontainers); else { free(textcontainers); textcontainers = NULL; } [self _didInvalidateLayout]; } - (void) textContainerChangedGeometry: (NSTextContainer *)aContainer { int i; for (i = 0; i < num_textcontainers; i++) if (textcontainers[i].textContainer == aContainer) break; if (i == num_textcontainers) { NSLog(@"%s: does not own text container", __PRETTY_FUNCTION__); return; } [self _invalidateLayoutFromContainer: i]; [self _didInvalidateLayout]; } - (unsigned int) firstUnlaidCharacterIndex { return layout_char; } - (unsigned int) firstUnlaidGlyphIndex { return layout_glyph; } -(void) getFirstUnlaidCharacterIndex: (unsigned int *)cindex glyphIndex: (unsigned int *)gindex { if (cindex) *cindex = [self firstUnlaidCharacterIndex]; if (gindex) *gindex = [self firstUnlaidGlyphIndex]; } -(void) setExtraLineFragmentRect: (NSRect)linefrag usedRect: (NSRect)used textContainer: (NSTextContainer *)tc { extra_rect = linefrag; extra_used_rect = used; extra_textcontainer = tc; } -(NSRect) extraLineFragmentRect { return extra_rect; } -(NSRect) extraLineFragmentUsedRect { return extra_used_rect; } -(NSTextContainer *) extraLineFragmentTextContainer { return extra_textcontainer; } -(void) _softInvalidateUseLineFrags: (int)num withShift: (NSSize)shift inTextContainer: (NSTextContainer *)textContainer { int i; textcontainer_t *tc; linefrag_t *lf; for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) if (tc->textContainer == textContainer) break; if (i == num_textcontainers) { NSLog(@"(%s): does not own text container", __PRETTY_FUNCTION__); return; } if (shift.width || shift.height) { for (i = 0, lf = &tc->linefrags[tc->num_linefrags]; i < num; i++, lf++) { lf->rect.origin.x += shift.width; lf->rect.origin.y += shift.height; lf->used_rect.origin.x += shift.width; lf->used_rect.origin.y += shift.height; } } tc->num_soft -= num; tc->num_linefrags += num; lf = &tc->linefrags[tc->num_linefrags - 1]; tc->length = lf->pos + lf->length - tc->pos; layout_glyph = tc->pos + tc->length; /* We must have glyphs beyond all the soft-invalidated line frags, so comparing with glyphs->glyph_length is ok. */ if (layout_glyph == glyphs->glyph_length) layout_char = glyphs->char_length; else layout_char = [self characterIndexForGlyphAtIndex: layout_glyph]; /* TODO? */ } -(NSRect) _softInvalidateLineFragRect: (int)index firstGlyph: (unsigned int *)first_glyph nextGlyph: (unsigned int *)next_glyph inTextContainer: (NSTextContainer *)textContainer { int i; textcontainer_t *tc; linefrag_t *lf; for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) if (tc->textContainer == textContainer) break; if (i == num_textcontainers) { NSLog(@"(%s): does not own text container", __PRETTY_FUNCTION__); return NSZeroRect; } if (index >= tc->num_soft) return NSZeroRect; lf = &tc->linefrags[tc->num_linefrags + index]; *first_glyph = lf->pos; *next_glyph = lf->pos + lf->length; return lf->rect; } -(unsigned int) _softInvalidateFirstGlyphInTextContainer: (NSTextContainer *)textContainer { int i; textcontainer_t *tc; for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) if (tc->textContainer == textContainer) break; if (i == num_textcontainers) { NSLog(@"(%s): does not own text container", __PRETTY_FUNCTION__); return (unsigned int)-1; } if (tc->num_soft) return tc->linefrags[tc->num_linefrags].pos; else return (unsigned int)-1; } -(unsigned int) _softInvalidateNumberOfLineFragsInTextContainer: (NSTextContainer *)textContainer { int i; textcontainer_t *tc; for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) if (tc->textContainer == textContainer) break; if (i == num_textcontainers) { NSLog(@"(%s): does not own text container", __PRETTY_FUNCTION__); return (unsigned int)-1; } return tc->num_soft; } @end /***** The rest *****/ @implementation GSLayoutManager - init { if (!(self = [super init])) return nil; [self _initGlyphs]; typesetter = [[GSTypesetter sharedSystemTypesetter] retain]; usesScreenFonts = YES; return self; } -(void) dealloc { int i; textcontainer_t *tc; free(rect_array); rect_array_size = 0; rect_array = NULL; [self _freeLayout]; for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) { [tc->textContainer release]; } free(textcontainers); textcontainers = NULL; [self _freeGlyphs]; DESTROY(typesetter); [super dealloc]; } -(void) _invalidateEverything { [self _freeLayout]; [self _freeGlyphs]; [self _initGlyphs]; } /** * Sets the text storage for the layout manager. * Use -replaceTextStorage: instead as a rule. - this method is really * more for internal use by the text system. * Invalidates the entire layout (should it??) */ /* See [NSTextView -setTextContainer:] for more information about these calls. */ - (void) setTextStorage: (NSTextStorage *)aTextStorage { int i; textcontainer_t *tc; [self _invalidateEverything]; /* * 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; /* We send this message to all text containers so they can respond to the change (most importantly to let them tell their text views). */ for (i = 0, tc = textcontainers; i < num_textcontainers; i++, tc++) { [tc->textContainer setLayoutManager: self]; } [self _didInvalidateLayout]; } /** * Returns the text storage for this layout manager. */ - (NSTextStorage *) textStorage { return _textStorage; } /** * Replaces the text storage with a new one.
* Takes care (since layout managers are owned by text storages) * not to get self deallocated. */ - (void) replaceTextStorage: (NSTextStorage *)newTextStorage { NSArray *layoutManagers = [_textStorage layoutManagers]; NSEnumerator *enumerator = [layoutManagers objectEnumerator]; GSLayoutManager *object; /* Remove layout managers from old NSTextStorage object and add them to the new one. NSTextStorage's addLayoutManager invokes GSLayoutManager's setTextStorage method automatically, and that includes self. */ while ((object = (GSLayoutManager*)[enumerator nextObject]) != nil) { RETAIN(object); [_textStorage removeLayoutManager: object]; [newTextStorage addLayoutManager: object]; RELEASE(object); } } - (id) delegate { return _delegate; } - (void) setDelegate: (id)aDelegate { _delegate = aDelegate; } -(GSTypesetter *) typesetter { return typesetter; } -(void) setTypesetter: (GSTypesetter *)a_typesetter { ASSIGN(typesetter, a_typesetter); } - (BOOL) usesScreenFonts { return usesScreenFonts; } - (void) setUsesScreenFonts: (BOOL)flag { flag = !!flag; if (flag == usesScreenFonts) return; usesScreenFonts = flag; [self _invalidateEverything]; [self _didInvalidateLayout]; } - (NSFont *) substituteFontForFont: (NSFont *)originalFont { NSFont *f; if (usesScreenFonts) { f = [originalFont screenFont]; if (f) return f; } return originalFont; } - (void) setBackgroundLayoutEnabled: (BOOL)flag { flag = !!flag; if (flag == backgroundLayoutEnabled) return; backgroundLayoutEnabled = flag; /* TODO */ } - (BOOL) backgroundLayoutEnabled { return backgroundLayoutEnabled; } - (void) setShowsInvisibleCharacters: (BOOL)flag { flag = !!flag; if (flag == showsInvisibleCharacters) return; showsInvisibleCharacters = flag; [self _invalidateEverything]; [self _didInvalidateLayout]; } - (BOOL) showsInvisibleCharacters { return showsInvisibleCharacters; } - (void) setShowsControlCharacters: (BOOL)flag { flag = !!flag; if (flag == showsControlCharacters) return; showsControlCharacters = flag; [self _invalidateEverything]; [self _didInvalidateLayout]; } - (BOOL) showsControlCharacters { return showsControlCharacters; } /* Note that NSLayoutManager completely overrides this (to perform more intelligent invalidation of layout using the constraints on layout it has). */ - (void) textStorage: (NSTextStorage *)aTextStorage edited: (unsigned int)mask range: (NSRange)range changeInLength: (int)lengthChange invalidatedRange: (NSRange)invalidatedRange { NSRange r; if (!(mask & NSTextStorageEditedCharacters)) lengthChange = 0; [self invalidateGlyphsForCharacterRange: invalidatedRange changeInLength: lengthChange actualCharacterRange: &r]; /* See the comments above -invalidateLayoutForCharacterRange:isSoft: actualCharacterRange: for information on why we invalidate everything here. */ [self _invalidateLayoutFromContainer: 0]; [self _didInvalidateLayout]; } /* These must be in the main implementation so backends can override them in a category safely. */ /* These three methods should be implemented in the backend, but there's a dummy here. It maps each character to a glyph with the glyph id==unicode index, except control characters, which are mapped to NSControlGlyph. */ -(unsigned int) _findSafeBreakMovingBackwardFrom: (unsigned int)ch { return ch; } -(unsigned int) _findSafeBreakMovingForwardFrom: (unsigned int)ch { return ch; } /* TODO2: put good control code handling here in a way that makes it easy for the backends to use it */ -(void) _generateGlyphsForRun: (glyph_run_t *)run at: (unsigned int)pos { int i, c = run->head.char_length; unsigned int ch; unichar buf[c]; glyph_t *g; NSCharacterSet *cs = [NSCharacterSet controlCharacterSet]; IMP characterIsMember = [cs methodForSelector: @selector(characterIsMember:)]; run->head.glyph_length = c; run->glyphs = malloc(sizeof(glyph_t) * c); memset(run->glyphs, 0, sizeof(glyph_t) * c); [[_textStorage string] getCharacters: buf range: NSMakeRange(pos, c)]; g = run->glyphs; for (i = 0; i < c; i++) { ch = buf[i]; g->char_offset = i; if (characterIsMember(cs, @selector(characterIsMember:), ch)) g->g = NSControlGlyph; else if (ch == NSAttachmentCharacter) g->g = GSAttachmentGlyph; else g->g = ch; g++; } } @end