minor improvements

git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@4017 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
richard 1999-04-02 08:04:05 +00:00
parent 2274bd4ecd
commit 80028023e4
2 changed files with 580 additions and 369 deletions

View file

@ -1,3 +1,9 @@
Thu Apr 1 20:45:00 1999 Richard Frith-Macdonald <richard@brainstorm.co.uk>
* Source/NSStringDrawing.m: improved paragraph style handling and
added slight optimisation to restore (barely) tolerable performance.
Further optimisation to wait until it all works properly.
1999-04-01 Adam Fedor <fedor@gnu.org> 1999-04-01 Adam Fedor <fedor@gnu.org>
* Model/test.m (main): Change GSContext to NSGraphicContext * Model/test.m (main): Change GSContext to NSGraphicContext

View file

@ -31,54 +31,17 @@
#include <AppKit/NSStringDrawing.h> #include <AppKit/NSStringDrawing.h>
#include <AppKit/AppKit.h> #include <AppKit/AppKit.h>
/*
* I know it's severely sub-optimal, but the NSString methods just
* use NSAttributes string to do the job.
*/
@implementation NSString (NSStringDrawing)
- (void) drawAtPoint: (NSPoint)point withAttributes: (NSDictionary *)attrs static NSCharacterSet *whitespace;
{ static NSCharacterSet *newlines;
NSAttributedString *a; static NSCharacterSet *separators;
a = [NSAttributedString allocWithZone: NSDefaultMallocZone()];
[a initWithString: self attributes: attrs];
[a drawAtPoint: point];
[a release];
}
- (void) drawInRect: (NSRect)rect withAttributes: (NSDictionary *)attrs
{
NSAttributedString *a;
a = [NSAttributedString allocWithZone: NSDefaultMallocZone()];
[a initWithString: self attributes: attrs];
[a drawInRect: rect];
[a release];
}
- (NSSize) sizeWithAttributes: (NSDictionary *)attrs
{
NSAttributedString *a;
NSSize s;
a = [NSAttributedString allocWithZone: NSDefaultMallocZone()];
[a initWithString: self attributes: attrs];
s = [a size];
[a release];
return s;
}
@end
@implementation NSAttributedString (NSStringDrawing)
static NSCharacterSet *nlset;
static NSFont *defFont; static NSFont *defFont;
static NSParagraphStyle *defStyle; static NSParagraphStyle *defStyle;
static NSColor *defFgCol; static NSColor *defFgCol;
static NSColor *defBgCol; static NSColor *defBgCol;
static SEL advSel = @selector(advancementForGlyph:);
static BOOL (*isSepImp)(NSCharacterSet*, SEL, unichar);
/* /*
* Thne 'checkInit()' function is called to ensure that any static * Thne 'checkInit()' function is called to ensure that any static
@ -91,17 +54,32 @@ checkInit()
if (beenHere == NO) if (beenHere == NO)
{ {
NSCharacterSet *not_ws; NSMutableCharacterSet *ms;
NSMutableCharacterSet *new_set;
whitespace = [[NSCharacterSet whitespaceCharacterSet] retain];
/* /*
* Build a character set containing only newline characters. * Build a character set containing only newline characters.
*/ */
not_ws = [[NSCharacterSet whitespaceCharacterSet] invertedSet]; ms = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy];
new_set = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy]; [ms formIntersectionWithCharacterSet: [whitespace invertedSet]];
[new_set formIntersectionWithCharacterSet: not_ws]; newlines = [ms copy];
nlset = [new_set copy]; [ms release];
[new_set release];
/*
* Build a character set containing only word separators.
*/
#if 0 /* FIXME */
ms = [[NSCharacterSet punctuationCharacterSet] mutableCopy];
[ms formUnionWithCharacterSet: whitespace];
separators = [ms copy];
[ms release];
#else
separators = whitespace;
#endif
isSepImp = (BOOL (*)(NSCharacterSet*, SEL, unichar))
[separators methodForSelector: @selector(characterIsMember:)];
defStyle = [NSParagraphStyle defaultParagraphStyle]; defStyle = [NSParagraphStyle defaultParagraphStyle];
[defStyle retain]; [defStyle retain];
@ -117,25 +95,72 @@ checkInit()
defFgCol = [NSColor textColor]; defFgCol = [NSColor textColor];
} }
static inline BOOL
isSeparator(unichar c)
{
return (*isSepImp)(separators, @selector(characterIsMember:), c);
}
#define ADVANCEMENT(X) (*advImp)(font, advSel, (X))
/* /*
* The 'sizeLine()' function is called to determine the size of a single * The 'sizeLine()' function is called to determine the size of a single
* line of text (specified by the 'range' argument) that may be part of * line of text (specified by the 'range' argument) that may be part of
* a larger attributed string. * a larger attributed string.
* If will also return the position of the baseline of the text within * If will also return the position of the baseline of the text within
* the bounding rectangle as an offset from the bottom of the rectangle. * the bounding rectangle as an offset from the bottom of the rectangle.
* The 'line' range is shortened to indicate any line wrapping.
*/ */
static NSSize static NSSize
sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr) sizeLine(NSAttributedString *str,
NSParagraphStyle *style,
NSRange *para,
BOOL first,
float *baseptr)
{ {
unsigned pos = line.location; unsigned pos = para->location;
unsigned end = NSMaxRange(line); unsigned end = pos + para->length;
unsigned lastSepIndex;
float lastSepOffset;
NSLineBreakMode lbm;
NSArray *tabStops = [style tabStops];
unsigned numTabs = [tabStops count];
unsigned nextTab = 0;
NSSize size = NSMakeSize(0, 0); NSSize size = NSMakeSize(0, 0);
float baseline = 0; float baseline = 0;
float maxx;
NSFont *oldFont = nil;
NSSize (*advImp)(NSFont*, SEL, NSGlyph);
if (pos >= end)
return size;
/*
* Perform initial horizontal positioning
*/
if (first)
size.width = [style firstLineHeadIndent];
else
size.width = [style headIndent];
/*
* Initialise possible linebreak points.
*/
lbm = [style lineBreakMode];
lastSepIndex = 0;
lastSepOffset = size.width;
/*
* Determine the end of a line - use a very large value if the style does
* not give us an eol relative to our starting point.
*/
maxx = [style tailIndent];
if (maxx <= 0.0)
maxx = 1.0E8;
while (pos < end) while (pos < end)
{ {
NSFont *font; NSFont *font;
NSParagraphStyle *style;
int superscript; int superscript;
int ligature; int ligature;
float base; float base;
@ -155,25 +180,13 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr)
effectiveRange: &range]; effectiveRange: &range];
if (font == nil) if (font == nil)
font = defFont; font = defFont;
maxRange = NSIntersectionRange(maxRange, range); if (font != oldFont)
// Get style and range over which it applies.
style = (NSParagraphStyle*)[str attribute: NSParagraphStyleAttributeName
atIndex: pos
effectiveRange: &range];
if (style == nil)
style = defStyle;
maxRange = NSIntersectionRange(maxRange, range);
/*
* Perform initial horizontal positioning
*/
if (pos == line.location)
{ {
if (first) oldFont = font;
size.width = [style firstLineHeadIndent]; advImp = (NSSize (*)(NSFont*, SEL, NSGlyph))
else [font methodForSelector: advSel];
size.width = [style headIndent];
} }
maxRange = NSIntersectionRange(maxRange, range);
// Get baseline offset and range over which it applies. // Get baseline offset and range over which it applies.
num = (NSNumber*)[str attribute: NSBaselineOffsetAttributeName num = (NSNumber*)[str attribute: NSBaselineOffsetAttributeName
@ -237,19 +250,25 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr)
*/ */
// FIXME - ligature should have some effect on width. // FIXME - ligature should have some effect on width.
range = maxRange; range = maxRange;
pos = NSMaxRange(range); // Next position in string.
if (range.length > 0) if (range.length > 0)
{ {
unichar chars[range.length]; unichar chars[range.length];
NSArray *tabStops = [style tabStops]; unsigned i = 0;
unsigned numTabs = [tabStops count]; float width = 0;
unsigned nextTab = 0;
unsigned i;
[[str string] getCharacters: chars range: range]; [[str string] getCharacters: chars range: range];
for (i = 0; i < range.length; i++) while (i < range.length && width < maxx)
{ {
if (chars[i] == '\t') unsigned tabIndex = i;
while (tabIndex < range.length)
{
if (chars[tabIndex] == '\t')
break;
tabIndex++;
}
if (tabIndex == i)
{ {
NSTextTab *tab; NSTextTab *tab;
@ -265,90 +284,218 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr)
nextTab++; nextTab++;
} }
if (nextTab < numTabs) if (nextTab < numTabs)
size.width = [tab location]; width = [tab location];
else else
size.width += [font advancementForGlyph: ' '].width; {
NSSize adv;
adv = ADVANCEMENT(' ');
width = size.width + adv.width;
}
if (width > maxx)
break;
/*
* In case we need to word-wrap, we must record this
* as a possible linebreak position.
*/
if (lbm == NSLineBreakByWordWrapping)
{
lastSepIndex = pos + i;
lastSepOffset = size.width;
}
size.width = width;
i++; // Point to next char.
} }
else else
{ {
size.width += [font advancementForGlyph: chars[i]].width; while (i < tabIndex)
size.width += kern; {
NSSize adv;
adv = ADVANCEMENT(chars[i]);
width = size.width + adv.width + kern;
if (width > maxx)
break;
if (lbm == NSLineBreakByWordWrapping
&& isSeparator(chars[i]))
{
lastSepIndex = pos + i;
lastSepOffset = size.width;
}
size.width = width;
i++;
} }
} }
} }
if (width > maxx)
{
if (lbm == NSLineBreakByWordWrapping)
{
unichar c;
/*
* Word wrap - if the words are separated by whitespace
* we discard the whitespace character - is this right?.
*/
pos = lastSepIndex;
size.width = lastSepOffset;
c = [[str string] characterAtIndex: pos];
if ([whitespace characterIsMember: c])
pos++;
} }
else if (lbm == NSLineBreakByCharWrapping)
{
/*
* Simply do a linebreak at the current character position.
*/
pos += i;
}
else
{
/*
* Truncate line.
*/
size.width = maxx;
pos = end;
}
break;
}
else
{
pos = NSMaxRange(range); // Next position in string.
}
}
}
/*
* Adjust the range 'para' to specify the characters in this line.
*/
para->length = (pos - para->location);
if (baseptr) if (baseptr)
*baseptr = baseline; *baseptr = baseline;
return size; return size;
} }
/* FIXME completely ignores paragraph style attachments and other layout info */
/*
* I know it's severely sub-optimal, but the NSString methods just
* use NSAttributes string to do the job.
*/
@implementation NSString (NSStringDrawing)
- (void) drawAtPoint: (NSPoint)point withAttributes: (NSDictionary *)attrs
{
NSAttributedString *a;
a = [NSAttributedString allocWithZone: NSDefaultMallocZone()];
[a initWithString: self attributes: attrs];
[a drawAtPoint: point];
[a release];
}
- (void) drawInRect: (NSRect)rect withAttributes: (NSDictionary *)attrs
{
NSAttributedString *a;
a = [NSAttributedString allocWithZone: NSDefaultMallocZone()];
[a initWithString: self attributes: attrs];
[a drawInRect: rect];
[a release];
}
- (NSSize) sizeWithAttributes: (NSDictionary *)attrs
{
NSAttributedString *a;
NSSize s;
a = [NSAttributedString allocWithZone: NSDefaultMallocZone()];
[a initWithString: self attributes: attrs];
s = [a size];
[a release];
return s;
}
@end
@implementation NSAttributedString (NSStringDrawing)
- (void) drawAtPoint: (NSPoint)point - (void) drawAtPoint: (NSPoint)point
{ {
NSGraphicsContext *ctxt = [NSGraphicsContext currentContext]; NSGraphicsContext *ctxt = [NSGraphicsContext currentContext];
NSString *allText = [self string]; NSString *allText = [self string];
unsigned length = [allText length]; unsigned length = [allText length];
unsigned linePos = 0; unsigned paraPos = 0;
BOOL isFlipped = [[ctxt focusView] isFlipped]; BOOL isFlipped = [[ctxt focusView] isFlipped];
NSParagraphStyle *style = nil; NSParagraphStyle *style = nil;
BOOL first = YES; BOOL firstLineOfFirstPara = YES;
NSFont *oldFont = nil;
NSSize (*advImp)(NSFont*, SEL, NSGlyph);
checkInit(); checkInit();
/* /*
* Now produce output on a per-line basis. * Now produce output on a per-line basis.
*/ */
while (linePos < length) while (paraPos < length)
{ {
NSRange para; // Range of current paragraph.
NSRange line; // Range of current line. NSRange line; // Range of current line.
NSRange eol; // Rnage of newline character. NSRange eol; // Range of newline character.
unsigned position; // Position in NSString. unsigned position; // Position in NSString.
NSSize lineSize; NSSize lineSize;
float baseline; float baseline;
float xpos = 0; float xpos = 0;
NSColor *bg = nil;
float leading;
BOOL firstLine = YES;
/* /*
* Determine the range of the next line of text (in 'line') and set * Determine the range of the next paragraph of text (in 'para') and set
* 'linePos' to point after the terminating newline character (if any). * 'paraPos' to point after the terminating newline character (if any).
*/ */
line = NSMakeRange(linePos, length - linePos); para = NSMakeRange(paraPos, length - paraPos);
eol = [allText rangeOfCharacterFromSet: nlset eol = [allText rangeOfCharacterFromSet: newlines
options: NSLiteralSearch options: NSLiteralSearch
range: line]; range: para];
if (eol.length == 0) if (eol.length == 0)
eol.location = length; eol.location = length;
else else
line.length = eol.location - line.location; para.length = eol.location - para.location;
linePos = NSMaxRange(eol); paraPos = NSMaxRange(eol);
position = line.location; position = para.location;
if (first == NO) do
{ {
NSParagraphStyle *newStyle; if (firstLine == YES)
NSColor *bg; {
float leading; leading = [style paragraphSpacing];
/* /*
* Check to see if the new line begins with the same paragraph style * Check to see if the new line begins with the same paragraph
* that the old ended in. This information is used to handle what * styl that the old ended in. This information is used to handle
* happens between lines and whether the new line is also a new * what happens between lines and whether the new line is also a
* paragraph. * new paragraph.
*/ */
newStyle = (NSParagraphStyle*)[self style = (NSParagraphStyle*)[self
attribute: NSParagraphStyleAttributeName attribute: NSParagraphStyleAttributeName
atIndex: position atIndex: position
effectiveRange: 0]; effectiveRange: 0];
if ([style isEqual: newStyle])
{
leading = [style lineSpacing];
} }
else else
{ {
leading = [style paragraphSpacing]; leading = [style lineSpacing];
first = YES;
} }
if (firstLineOfFirstPara == YES)
{
firstLineOfFirstPara = NO;
}
else
{
if (isFlipped) if (isFlipped)
point.y -= leading; point.y -= leading;
else else
@ -356,7 +503,7 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr)
/* /*
* Fill the inter-line/interparagraph space with the background * Fill the inter-line/interparagraph space with the background
* color in use at the end of the last paragraph. * color in use at the end of the last line.
*/ */
bg = (NSColor*)[self attribute: NSBackgroundColorAttributeName bg = (NSColor*)[self attribute: NSBackgroundColorAttributeName
atIndex: position - 1 atIndex: position - 1
@ -381,9 +528,11 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr)
/* /*
* Calculate sizing information for the entire line. * Calculate sizing information for the entire line.
*/ */
lineSize = sizeLine(self, line, first, &baseline); line = para;
lineSize = sizeLine(self, style, &line, firstLine, &baseline);
firstLine = NO;
while (position < eol.location) while (position < NSMaxRange(line))
{ {
NSAttributedString *subAttr; NSAttributedString *subAttr;
NSString *subString; NSString *subString;
@ -410,6 +559,12 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr)
effectiveRange: &range]; effectiveRange: &range];
if (font == nil) if (font == nil)
font = defFont; font = defFont;
if (font != oldFont)
{
oldFont = font;
advImp = (NSSize (*)(NSFont*, SEL, NSGlyph))
[font methodForSelector: advSel];
}
maxRange = NSIntersectionRange(maxRange, range); maxRange = NSIntersectionRange(maxRange, range);
// Get style and range over which it applies. // Get style and range over which it applies.
@ -497,7 +652,7 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr)
fillrect.origin = point; fillrect.origin = point;
fillrect.size = lineSize; fillrect.size = lineSize;
if (first) if (firstLine)
xpos = [style firstLineHeadIndent]; xpos = [style firstLineHeadIndent];
else else
xpos += [style headIndent]; xpos += [style headIndent];
@ -513,10 +668,10 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr)
} }
/* /*
* Now, at last we have all the required text drawing attributes and * Now, at last we have all the required text drawing attributes
* we have a range over which ALL of them apply. We update our * and we have a range over which ALL of them apply. We update
* position to point past this range, then we grab the substring from * our position to point past this range, then we grab the
* the range, draw it, and update our drawing position. * substring, draw it, and update our drawing position.
*/ */
range = maxRange; range = maxRange;
position = NSMaxRange(range); // Next position in string. position = NSMaxRange(range); // Next position in string.
@ -531,6 +686,7 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr)
NSArray *tabStops = [style tabStops]; NSArray *tabStops = [style tabStops];
unsigned numTabs = [tabStops count]; unsigned numTabs = [tabStops count];
unsigned nextTab = 0; unsigned nextTab = 0;
float width = xpos;
unsigned i; unsigned i;
[[self string] getCharacters: chars range: range]; [[self string] getCharacters: chars range: range];
@ -552,8 +708,8 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr)
NSTextTab *tab; NSTextTab *tab;
/* /*
* Either advance to next tabstop or by a space * Either advance to next tabstop or by
* if there are no more tabstops. * a space if there are no more tabstops.
*/ */
while (nextTab < numTabs) while (nextTab < numTabs)
{ {
@ -565,11 +721,19 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr)
if (nextTab < numTabs) if (nextTab < numTabs)
xpos = [tab location]; xpos = [tab location];
else else
xpos += [font advancementForGlyph: ' '].width; {
NSSize adv;
adv = ADVANCEMENT(' ');
xpos += adv.width;
}
} }
else else
{ {
xpos += [font advancementForGlyph: chars[i]].width; NSSize adv;
adv = ADVANCEMENT(chars[i]);
xpos += adv.width;
xpos += kern; xpos += kern;
} }
} }
@ -599,58 +763,99 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr)
else else
ypos = point.y - lineSize.height + baseline + base; ypos = point.y - lineSize.height + baseline + base;
for (i = 0; i < range.length; i++) i = 0;
while (i < range.length)
{ {
if (chars[i] == '\t') unsigned tabIndex = i;
while (tabIndex < range.length)
{
if (chars[tabIndex] == '\t')
break;
tabIndex++;
}
if (tabIndex == i)
{ {
NSTextTab *tab; NSTextTab *tab;
/* /*
* Either advance to next tabstop or by a space * Either advance to next tabstop or by a space if
* if there are no more tabstops. * there are no more tabstops.
*/ */
while (nextTab < numTabs) while (nextTab < numTabs)
{ {
tab = [tabStops objectAtIndex: nextTab]; tab = [tabStops objectAtIndex: nextTab];
if ([tab location] > xpos) if ([tab location] > width)
break; break;
nextTab++; nextTab++;
} }
if (nextTab < numTabs) if (nextTab < numTabs)
xpos = [tab location]; width = [tab location];
else else
xpos += [font advancementForGlyph: ' '].width; {
NSSize adv;
adv = ADVANCEMENT(' ');
width += adv.width;
}
i++; // Point to next char.
}
else if (kern == 0)
{
char buf[tabIndex - i + 1];
unsigned j;
for (j = i; j < tabIndex; j++)
buf[j-i] = chars[j];
buf[j-i] = '\0';
DPSmoveto(ctxt, point.x + xpos, ypos);
DPSshow(ctxt, buf);
while (i < tabIndex)
{
NSSize adv;
adv = ADVANCEMENT(chars[i]);
width += adv.width;
i++;
}
} }
else else
{ {
while (i < tabIndex)
{
NSSize adv;
char buf[2]; char buf[2];
xpos += kern; width += kern;
DPSmoveto(ctxt, point.x + xpos, ypos); DPSmoveto(ctxt, point.x + width, ypos);
/*
* FIXME Eugh - we simply assume that the unichar string
* actually contains ascii characters and render them
* using dpsshow
*/
buf[0] = chars[i]; buf[0] = chars[i];
buf[1] = '\0'; buf[1] = '\0';
DPSshow(ctxt, buf); DPSshow(ctxt, buf);
xpos += [font advancementForGlyph: chars[i]].width; adv = ADVANCEMENT(chars[i]);
width += adv.width;
i++;
} }
} }
xpos = width;
} }
if (underline == NSSingleUnderlineStyle) if (underline == NSSingleUnderlineStyle)
{ {
DPSmoveto(ctxt, point.x, ypos); DPSmoveto(ctxt, point.x + xpos, ypos);
DPSlineto(ctxt, point.x + xpos - 1, ypos); DPSlineto(ctxt, point.x + width - 1, ypos);
}
xpos = width;
} }
} }
if (isFlipped) if (isFlipped)
point.y += lineSize.height; point.y += lineSize.height;
else else
point.y -= lineSize.height; point.y -= lineSize.height;
first = NO; firstLine = NO;
para.length -= line.length;
para.location += line.length;
} while (para.location < eol.location);
} }
} }