From 80028023e4f923c24f0684435627b14f558f3d8d Mon Sep 17 00:00:00 2001 From: richard Date: Fri, 2 Apr 1999 08:04:05 +0000 Subject: [PATCH] minor improvements git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@4017 72102866-910b-0410-8b05-ffd578937521 --- ChangeLog | 6 + Source/NSStringDrawing.m | 943 ++++++++++++++++++++++++--------------- 2 files changed, 580 insertions(+), 369 deletions(-) diff --git a/ChangeLog b/ChangeLog index 641a8c2b8..b0ee63db1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +Thu Apr 1 20:45:00 1999 Richard Frith-Macdonald + + * 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 * Model/test.m (main): Change GSContext to NSGraphicContext diff --git a/Source/NSStringDrawing.m b/Source/NSStringDrawing.m index 09a4c4967..ad87562b6 100644 --- a/Source/NSStringDrawing.m +++ b/Source/NSStringDrawing.m @@ -31,54 +31,17 @@ #include #include -/* - * 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) - -static NSCharacterSet *nlset; +static NSCharacterSet *whitespace; +static NSCharacterSet *newlines; +static NSCharacterSet *separators; static NSFont *defFont; static NSParagraphStyle *defStyle; static NSColor *defFgCol; static NSColor *defBgCol; +static SEL advSel = @selector(advancementForGlyph:); + +static BOOL (*isSepImp)(NSCharacterSet*, SEL, unichar); /* * Thne 'checkInit()' function is called to ensure that any static @@ -91,17 +54,32 @@ checkInit() if (beenHere == NO) { - NSCharacterSet *not_ws; - NSMutableCharacterSet *new_set; + NSMutableCharacterSet *ms; + + whitespace = [[NSCharacterSet whitespaceCharacterSet] retain]; /* * Build a character set containing only newline characters. */ - not_ws = [[NSCharacterSet whitespaceCharacterSet] invertedSet]; - new_set = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy]; - [new_set formIntersectionWithCharacterSet: not_ws]; - nlset = [new_set copy]; - [new_set release]; + ms = [[NSCharacterSet whitespaceAndNewlineCharacterSet] mutableCopy]; + [ms formIntersectionWithCharacterSet: [whitespace invertedSet]]; + newlines = [ms copy]; + [ms 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 retain]; @@ -117,25 +95,72 @@ checkInit() 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 * line of text (specified by the 'range' argument) that may be part of * a larger attributed string. * 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 'line' range is shortened to indicate any line wrapping. */ 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 end = NSMaxRange(line); + unsigned pos = para->location; + 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); 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) { NSFont *font; - NSParagraphStyle *style; int superscript; int ligature; float base; @@ -155,25 +180,13 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr) effectiveRange: &range]; if (font == nil) font = defFont; - maxRange = NSIntersectionRange(maxRange, range); - - // 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 (font != oldFont) { - if (first) - size.width = [style firstLineHeadIndent]; - else - size.width = [style headIndent]; + oldFont = font; + advImp = (NSSize (*)(NSFont*, SEL, NSGlyph)) + [font methodForSelector: advSel]; } + maxRange = NSIntersectionRange(maxRange, range); // Get baseline offset and range over which it applies. 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. range = maxRange; - pos = NSMaxRange(range); // Next position in string. if (range.length > 0) { unichar chars[range.length]; - NSArray *tabStops = [style tabStops]; - unsigned numTabs = [tabStops count]; - unsigned nextTab = 0; - unsigned i; + unsigned i = 0; + float width = 0; [[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; @@ -265,246 +284,240 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr) nextTab++; } if (nextTab < numTabs) - size.width = [tab location]; + width = [tab location]; 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 { - size.width += [font advancementForGlyph: chars[i]].width; - size.width += kern; + while (i < tabIndex) + { + 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) *baseptr = baseline; 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 { NSGraphicsContext *ctxt = [NSGraphicsContext currentContext]; NSString *allText = [self string]; unsigned length = [allText length]; - unsigned linePos = 0; + unsigned paraPos = 0; BOOL isFlipped = [[ctxt focusView] isFlipped]; NSParagraphStyle *style = nil; - BOOL first = YES; + BOOL firstLineOfFirstPara = YES; + NSFont *oldFont = nil; + NSSize (*advImp)(NSFont*, SEL, NSGlyph); checkInit(); /* * 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 eol; // Rnage of newline character. + NSRange eol; // Range of newline character. unsigned position; // Position in NSString. NSSize lineSize; float baseline; float xpos = 0; + NSColor *bg = nil; + float leading; + BOOL firstLine = YES; /* - * Determine the range of the next line of text (in 'line') and set - * 'linePos' to point after the terminating newline character (if any). + * Determine the range of the next paragraph of text (in 'para') and set + * 'paraPos' to point after the terminating newline character (if any). */ - line = NSMakeRange(linePos, length - linePos); - eol = [allText rangeOfCharacterFromSet: nlset + para = NSMakeRange(paraPos, length - paraPos); + eol = [allText rangeOfCharacterFromSet: newlines options: NSLiteralSearch - range: line]; + range: para]; if (eol.length == 0) eol.location = length; else - line.length = eol.location - line.location; - linePos = NSMaxRange(eol); - position = line.location; + para.length = eol.location - para.location; + paraPos = NSMaxRange(eol); + position = para.location; - if (first == NO) + do { - NSParagraphStyle *newStyle; - NSColor *bg; - float leading; - - /* - * Check to see if the new line begins with the same paragraph style - * that the old ended in. This information is used to handle what - * happens between lines and whether the new line is also a new - * paragraph. - */ - newStyle = (NSParagraphStyle*)[self - attribute: NSParagraphStyleAttributeName - atIndex: position - effectiveRange: 0]; - if ([style isEqual: newStyle]) + if (firstLine == YES) + { + leading = [style paragraphSpacing]; + /* + * Check to see if the new line begins with the same paragraph + * styl that the old ended in. This information is used to handle + * what happens between lines and whether the new line is also a + * new paragraph. + */ + style = (NSParagraphStyle*)[self + attribute: NSParagraphStyleAttributeName + atIndex: position + effectiveRange: 0]; + } + else { leading = [style lineSpacing]; } - else + + if (firstLineOfFirstPara == YES) { - leading = [style paragraphSpacing]; - first = YES; + firstLineOfFirstPara = NO; } - - if (isFlipped) - point.y -= leading; else - point.y += leading; - - /* - * Fill the inter-line/interparagraph space with the background - * color in use at the end of the last paragraph. - */ - bg = (NSColor*)[self attribute: NSBackgroundColorAttributeName - atIndex: position - 1 - effectiveRange: 0]; - if (bg == nil) - bg = defBgCol; - - if (bg != nil) { - NSRect fillrect; - - fillrect.origin = point; - fillrect.size.width = lineSize.width; - fillrect.size.height = leading; - [bg set]; - if (isFlipped == NO) - fillrect.origin.y -= fillrect.size.height; - NSRectFill(fillrect); - } - } - - /* - * Calculate sizing information for the entire line. - */ - lineSize = sizeLine(self, line, first, &baseline); - - while (position < eol.location) - { - NSAttributedString *subAttr; - NSString *subString; - NSSize size; - NSFont *font; - NSColor *bg; - NSColor *fg; - int underline; - int superscript; - float base; - float kern; - float ypos; - int ligature; - NSNumber *num; - NSRange maxRange; - NSRange range; - - // Maximum range is up to end of line. - maxRange = NSMakeRange(position, eol.location - position); - - // Get font and range over which it applies. - font = (NSFont*)[self attribute: NSFontAttributeName - atIndex: position - effectiveRange: &range]; - if (font == nil) - font = defFont; - maxRange = NSIntersectionRange(maxRange, range); - - // Get style and range over which it applies. - style = (NSParagraphStyle*)[self - attribute: NSParagraphStyleAttributeName - atIndex: position - effectiveRange: &range]; - if (style == nil) - style = defStyle; - maxRange = NSIntersectionRange(maxRange, range); - - // Get background color and range over which it applies. - bg = (NSColor*)[self attribute: NSBackgroundColorAttributeName - atIndex: position - effectiveRange: &range]; - if (bg == nil) - bg = defBgCol; - maxRange = NSIntersectionRange(maxRange, range); - - // Get foreground color and range over which it applies. - fg = (NSColor*)[self attribute: NSForegroundColorAttributeName - atIndex: position - effectiveRange: &range]; - if (fg == nil) - fg = defFgCol; - maxRange = NSIntersectionRange(maxRange, range); - - // Get underline style and range over which it applies. - num = (NSNumber*)[self attribute: NSUnderlineStyleAttributeName - atIndex: position - effectiveRange: &range]; - if (num == nil) - underline = GSNoUnderlineStyle; // No underline - else - underline = [num intValue]; - maxRange = NSIntersectionRange(maxRange, range); - - // Get superscript and range over which it applies. - num = (NSNumber*)[self attribute: NSSuperscriptAttributeName - atIndex: position - effectiveRange: &range]; - if (num == nil) - superscript = 0; - else - superscript = [num intValue]; - maxRange = NSIntersectionRange(maxRange, range); - - // Get baseline offset and range over which it applies. - num = (NSNumber*)[self attribute: NSBaselineOffsetAttributeName - atIndex: position - effectiveRange: &range]; - if (num == nil) - base = 0.0; - else - base = [num floatValue]; - maxRange = NSIntersectionRange(maxRange, range); - - // Get kern attribute and range over which it applies. - num = (NSNumber*)[self attribute: NSKernAttributeName - atIndex: position - effectiveRange: &range]; - if (num == nil) - kern = 0.0; - else - kern = [num floatValue]; - maxRange = NSIntersectionRange(maxRange, range); - - // Get ligature attribute and range over which it applies. - num = (NSNumber*)[self attribute: NSLigatureAttributeName - atIndex: position - effectiveRange: &range]; - if (num == nil) - ligature = 1; - else - ligature = [num intValue]; - maxRange = NSIntersectionRange(maxRange, range); - - /* - * If this is a new line - adjust for indentation. - */ - if (position == line.location) - { - NSRect fillrect; - - fillrect.origin = point; - fillrect.size = lineSize; - - if (first) - xpos = [style firstLineHeadIndent]; + if (isFlipped) + point.y -= leading; else - xpos += [style headIndent]; + point.y += leading; - fillrect.size.width = xpos; - if (bg != nil && fillrect.size.width > 0) + /* + * Fill the inter-line/interparagraph space with the background + * color in use at the end of the last line. + */ + bg = (NSColor*)[self attribute: NSBackgroundColorAttributeName + atIndex: position - 1 + effectiveRange: 0]; + if (bg == nil) + bg = defBgCol; + + if (bg != nil) { + NSRect fillrect; + + fillrect.origin = point; + fillrect.size.width = lineSize.width; + fillrect.size.height = leading; [bg set]; if (isFlipped == NO) fillrect.origin.y -= fillrect.size.height; @@ -513,144 +526,336 @@ sizeLine(NSAttributedString *str, NSRange line, BOOL first, float *baseptr) } /* - * Now, at last we have all the required text drawing attributes and - * we have a range over which ALL of them apply. We update our - * position to point past this range, then we grab the substring from - * the range, draw it, and update our drawing position. + * Calculate sizing information for the entire line. */ - range = maxRange; - position = NSMaxRange(range); // Next position in string. - subAttr = [self attributedSubstringFromRange: range]; - subString = [subAttr string]; - size.width = [font widthOfString: subString]; - size.height = [font pointSize]; + line = para; + lineSize = sizeLine(self, style, &line, firstLine, &baseline); + firstLine = NO; - if (range.length > 0) + while (position < NSMaxRange(line)) { - unichar chars[range.length]; - NSArray *tabStops = [style tabStops]; - unsigned numTabs = [tabStops count]; - unsigned nextTab = 0; - unsigned i; + NSAttributedString *subAttr; + NSString *subString; + NSSize size; + NSFont *font; + NSColor *bg; + NSColor *fg; + int underline; + int superscript; + float base; + float kern; + float ypos; + int ligature; + NSNumber *num; + NSRange maxRange; + NSRange range; - [[self string] getCharacters: chars range: range]; + // Maximum range is up to end of line. + maxRange = NSMakeRange(position, eol.location - position); + + // Get font and range over which it applies. + font = (NSFont*)[self attribute: NSFontAttributeName + atIndex: position + effectiveRange: &range]; + if (font == nil) + font = defFont; + if (font != oldFont) + { + oldFont = font; + advImp = (NSSize (*)(NSFont*, SEL, NSGlyph)) + [font methodForSelector: advSel]; + } + maxRange = NSIntersectionRange(maxRange, range); + + // Get style and range over which it applies. + style = (NSParagraphStyle*)[self + attribute: NSParagraphStyleAttributeName + atIndex: position + effectiveRange: &range]; + if (style == nil) + style = defStyle; + maxRange = NSIntersectionRange(maxRange, range); + + // Get background color and range over which it applies. + bg = (NSColor*)[self attribute: NSBackgroundColorAttributeName + atIndex: position + effectiveRange: &range]; + if (bg == nil) + bg = defBgCol; + maxRange = NSIntersectionRange(maxRange, range); + + // Get foreground color and range over which it applies. + fg = (NSColor*)[self attribute: NSForegroundColorAttributeName + atIndex: position + effectiveRange: &range]; + if (fg == nil) + fg = defFgCol; + maxRange = NSIntersectionRange(maxRange, range); + + // Get underline style and range over which it applies. + num = (NSNumber*)[self attribute: NSUnderlineStyleAttributeName + atIndex: position + effectiveRange: &range]; + if (num == nil) + underline = GSNoUnderlineStyle; // No underline + else + underline = [num intValue]; + maxRange = NSIntersectionRange(maxRange, range); + + // Get superscript and range over which it applies. + num = (NSNumber*)[self attribute: NSSuperscriptAttributeName + atIndex: position + effectiveRange: &range]; + if (num == nil) + superscript = 0; + else + superscript = [num intValue]; + maxRange = NSIntersectionRange(maxRange, range); + + // Get baseline offset and range over which it applies. + num = (NSNumber*)[self attribute: NSBaselineOffsetAttributeName + atIndex: position + effectiveRange: &range]; + if (num == nil) + base = 0.0; + else + base = [num floatValue]; + maxRange = NSIntersectionRange(maxRange, range); + + // Get kern attribute and range over which it applies. + num = (NSNumber*)[self attribute: NSKernAttributeName + atIndex: position + effectiveRange: &range]; + if (num == nil) + kern = 0.0; + else + kern = [num floatValue]; + maxRange = NSIntersectionRange(maxRange, range); + + // Get ligature attribute and range over which it applies. + num = (NSNumber*)[self attribute: NSLigatureAttributeName + atIndex: position + effectiveRange: &range]; + if (num == nil) + ligature = 1; + else + ligature = [num intValue]; + maxRange = NSIntersectionRange(maxRange, range); /* - * If we have a background color set - we fill in the - * region occupied by this substring. + * If this is a new line - adjust for indentation. */ - if (bg) + if (position == line.location) { - float oldx = xpos; - NSRect rect; + NSRect fillrect; + fillrect.origin = point; + fillrect.size = lineSize; - for (i = 0; i < range.length; i++) + if (firstLine) + xpos = [style firstLineHeadIndent]; + else + xpos += [style headIndent]; + + fillrect.size.width = xpos; + if (bg != nil && fillrect.size.width > 0) { - if (chars[i] == '\t') + [bg set]; + if (isFlipped == NO) + fillrect.origin.y -= fillrect.size.height; + NSRectFill(fillrect); + } + } + + /* + * Now, at last we have all the required text drawing attributes + * and we have a range over which ALL of them apply. We update + * our position to point past this range, then we grab the + * substring, draw it, and update our drawing position. + */ + range = maxRange; + position = NSMaxRange(range); // Next position in string. + subAttr = [self attributedSubstringFromRange: range]; + subString = [subAttr string]; + size.width = [font widthOfString: subString]; + size.height = [font pointSize]; + + if (range.length > 0) + { + unichar chars[range.length]; + NSArray *tabStops = [style tabStops]; + unsigned numTabs = [tabStops count]; + unsigned nextTab = 0; + float width = xpos; + unsigned i; + + [[self string] getCharacters: chars range: range]; + + /* + * If we have a background color set - we fill in the + * region occupied by this substring. + */ + if (bg) + { + float oldx = xpos; + NSRect rect; + + + for (i = 0; i < range.length; i++) + { + if (chars[i] == '\t') + { + NSTextTab *tab; + + /* + * Either advance to next tabstop or by + * a space if there are no more tabstops. + */ + while (nextTab < numTabs) + { + tab = [tabStops objectAtIndex: nextTab]; + if ([tab location] > xpos) + break; + nextTab++; + } + if (nextTab < numTabs) + xpos = [tab location]; + else + { + NSSize adv; + + adv = ADVANCEMENT(' '); + xpos += adv.width; + } + } + else + { + NSSize adv; + + adv = ADVANCEMENT(chars[i]); + xpos += adv.width; + xpos += kern; + } + } + + rect.origin.x = point.x + oldx; + rect.origin.y = point.y; + rect.size.height = lineSize.height; + rect.size.width = xpos - oldx; + xpos = oldx; + if (isFlipped == NO) + rect.origin.y -= lineSize.height; + + [bg set]; + NSRectFill(rect); + } + + /* + * Set font and color, then draw the substring. + * NB. Our origin is top-left of the string so we need to + * calculate a vertical coordinate for the baseline of the + * text produced by psshow. + */ + [fg set]; + [font set]; + if (isFlipped) + ypos = point.y + lineSize.height - baseline - base; + else + ypos = point.y - lineSize.height + baseline + base; + + i = 0; + while (i < range.length) + { + unsigned tabIndex = i; + + while (tabIndex < range.length) + { + if (chars[tabIndex] == '\t') + break; + tabIndex++; + } + + if (tabIndex == i) { NSTextTab *tab; /* - * Either advance to next tabstop or by a space - * if there are no more tabstops. + * Either advance to next tabstop or by a space if + * there are no more tabstops. */ while (nextTab < numTabs) { tab = [tabStops objectAtIndex: nextTab]; - if ([tab location] > xpos) + if ([tab location] > width) break; nextTab++; } if (nextTab < numTabs) - xpos = [tab location]; + width = [tab location]; 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 { - xpos += [font advancementForGlyph: chars[i]].width; - xpos += kern; + while (i < tabIndex) + { + NSSize adv; + char buf[2]; + + width += kern; + DPSmoveto(ctxt, point.x + width, ypos); + buf[0] = chars[i]; + buf[1] = '\0'; + DPSshow(ctxt, buf); + adv = ADVANCEMENT(chars[i]); + width += adv.width; + i++; + } } + xpos = width; } - - rect.origin.x = point.x + oldx; - rect.origin.y = point.y; - rect.size.height = lineSize.height; - rect.size.width = xpos - oldx; - xpos = oldx; - if (isFlipped == NO) - rect.origin.y -= lineSize.height; - [bg set]; - NSRectFill(rect); - } - - /* - * Set font and color, then draw the substring. - * NB. Our origin is top-left of the string so we need to - * calculate a vertical coordinate for the baseline of the - * text produced by psshow. - */ - [fg set]; - [font set]; - if (isFlipped) - ypos = point.y + lineSize.height - baseline - base; - else - ypos = point.y - lineSize.height + baseline + base; - - for (i = 0; i < range.length; i++) - { - if (chars[i] == '\t') + if (underline == NSSingleUnderlineStyle) { - NSTextTab *tab; - - /* - * Either advance to next tabstop or by a space - * if there are no more tabstops. - */ - while (nextTab < numTabs) - { - tab = [tabStops objectAtIndex: nextTab]; - if ([tab location] > xpos) - break; - nextTab++; - } - if (nextTab < numTabs) - xpos = [tab location]; - else - xpos += [font advancementForGlyph: ' '].width; - } - else - { - char buf[2]; - - xpos += kern; DPSmoveto(ctxt, point.x + xpos, ypos); - /* - * FIXME Eugh - we simply assume that the unichar string - * actually contains ascii characters and render them - * using dpsshow - */ - buf[0] = chars[i]; - buf[1] = '\0'; - DPSshow(ctxt, buf); - xpos += [font advancementForGlyph: chars[i]].width; + DPSlineto(ctxt, point.x + width - 1, ypos); } + xpos = width; } } - - if (underline == NSSingleUnderlineStyle) - { - DPSmoveto(ctxt, point.x, ypos); - DPSlineto(ctxt, point.x + xpos - 1, ypos); - } - } - if (isFlipped) - point.y += lineSize.height; - else - point.y -= lineSize.height; - first = NO; + if (isFlipped) + point.y += lineSize.height; + else + point.y -= lineSize.height; + firstLine = NO; + para.length -= line.length; + para.location += line.length; + } while (para.location < eol.location); } }