libs-gui/Source/GSLayoutManager.m
Alexander Malmberg ad2419e484 2005-01-21 21:43 Alexander Malmberg <alexander@malmberg.org>
Various whitespace cleanups, comment type fixes, and changes
	to avoid warnings from recent versions of gcc.

	* Headers/Additions/GNUstepGUI/GSToolbar.h (-_toolbars): Declare.
	* Source/NSWindow+Toolbar.m: Remove conflicting declaration of
	[NSToolbar -_toolbars].

	* Headers/Additions/GNUstepGUI/GSServicesManager.h,
 	Source/GSServicesMananger.m (-item2title:, -validateMenuItem:):
	Adjust argument types.
	* Headers/AppKit/NSMenu.h (-validateMenuItem:): Adjust argument
	type.

	* Source/NSTextView.m (-sizeToFit): Don't use size uninitialized
	if neither resizable flags is set.
	(-insertText:): Adjust argument type.
	* Headers/AppKit/NSResponder.h, Source/NSResponder.m (-insertText:):
	Adjust argument type. Document.

	* Headers/AppKit/NSView.h: Change type of ivar _window to NSWindow *.

	* Source/GSTitleView.m (-mouseDown:): Always initialize
	startWindowOrigin.
	* Source/NSApplication.m (-setApplicationIconImage:): Add casts
	to avoid warnings.
	* Source/NSCell.m (-cellSize): Add default: case.
	* Source/NSPasteboard.m
	([GSFiltered -pasteboard:provideDataForType:]): Detect and warn if we
	can't find a filter that will get us the desired type.
	* Source/NSProgressIndicator.m: Comment out unused variable 'images'.
	* Source/NSBezierPath.m: Declare GSBezierPath fully before using it.
	(-bezierPathByFlatteningPath, -bezierPathByReversingPath): Make sure
	variables are always initialized.

	* Source/NSMenuView.m,
	* Source/NSPrintOperation.m,
	* Source/NSSplitView.m,
	* Source/NSTableHeaderView.m: Make sure variables are always
	initialized.

	* Source/NSBox.m,
	* Source/NSImageview.m,
	* Source/NSText.m,
	* Source/NSTextStorage.m: Add missing includes.

	* Source/GSKeyBindingTable.m,
	* Source/GSLayoutManager.m,
	* Source/NSBitmapImageRep+PNM.m,
	* Source/NSBundleAdditions.m,
	* Source/NSLayoutManager.m,
	* Source/nsimage-tiff.h,
	* Source/tiff.m,
	* Headers/Additions/GNUstepGUI/GSDisplayServer.h,
	* Source/GSDisplayServer.m: Change signedness of various variables.

	* Source/NSPanel.m (-sendEvent:): Remove.
	* Source/NSWindow.m (-becomesKeyOnlyIfNeeded): New method.
	(-_sendEvent:becomesKeyOnlyIfNeeded:): Remove. Move code ...
	(-sendEvent:): ... here. Use -becomesKeyOnlyIfNeeded instead
	of the argument.


git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@20590 72102866-910b-0410-8b05-ffd578937521
2005-01-21 20:39:18 +00:00

2780 lines
65 KiB
Objective-C

/*
GSLayoutManager.m
Copyright (C) 2002, 2003 Free Software Foundation, Inc.
Author: Alexander Malmberg <alexander@malmberg.org>
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 <Foundation/NSCharacterSet.h>
#include <Foundation/NSDebug.h>
#include <Foundation/NSException.h>
#include <Foundation/NSValue.h>
#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;
unsigned 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;
}
- (BOOL) isValidGlyphIndex: (unsigned int)glyphIndex
{
if (glyphIndex == (unsigned int)-1)
return NO;
if (glyphs->glyph_length <= glyphIndex)
{
return NO;
}
else
{
return YES;
}
}
- (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;
unsigned 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; \
unsigned 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;
tc->usedRectValid = NO;
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;
if (tc->usedRectValid)
return tc->usedRect;
if (tc->num_linefrags)
{
double x0, y0, x1, y1;
i = 0;
lf = tc->linefrags;
x0 = NSMinX(lf->used_rect);
y0 = NSMinY(lf->used_rect);
x1 = NSMaxX(lf->used_rect);
y1 = NSMaxY(lf->used_rect);
for (i++, lf++; i < tc->num_linefrags; i++, lf++)
{
if (NSMinX(lf->used_rect) < x0)
x0 = NSMinX(lf->used_rect);
if (NSMinY(lf->used_rect) < y0)
y0 = NSMinY(lf->used_rect);
if (NSMaxX(lf->used_rect) > x1)
x1 = NSMaxX(lf->used_rect);
if (NSMaxY(lf->used_rect) > y1)
y1 = NSMaxY(lf->used_rect);
}
used = NSMakeRect(x0, y0, x1 - x0, y1 - y0);
}
else
used = NSZeroRect;
tc->usedRect = used;
tc->usedRectValid = YES;
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.<br />
* 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];
BOOL (*characterIsMember)(id, SEL, unichar)
= (BOOL(*)(id, SEL, unichar)) [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