/* 
   NSStringDrawing.m

   Draw and Measure categories of NSString and NSAttributedString 

   Copyright (C) 1997 Free Software Foundation, Inc.

   Author:  Felipe A. Rodriguez <far@ix.netcom.com>
   Date:    Aug 1998
   
   This file is part of the mySTEP Library and is provided
   under the terms of the GNU Library General Public License.
*/ 

#import <AppKit/NSStringDrawing.h>
#import <AppKit/AppKit.h>
#import "NSBackendPrivate.h"

@implementation NSString (NSStringDrawingAdditions)

- (NSRect) boundingRectWithSize:(NSSize)size
						options:(NSStringDrawingOptions)options
					 attributes:(NSDictionary *)attributes;
{
	NSAttributedString *a=[[NSAttributedString alloc] initWithString:self attributes:attributes];
	NSRect r=[a boundingRectWithSize:size options:options];		// get rect of attributed string
	[a release];			// no longer needed
	return r;
}

- (void) drawAtPoint:(NSPoint)point
	  withAttributes:(NSDictionary *)attrs;
{
	NSRect r;
	r.origin=point; // calculate a "virtual" bounding box
	r.size=NSMakeSize(16000.0, 16.0);	// height of characters, i.e. get NSFont
	[self drawWithRect:r options:0 attributes:attrs];
}

- (void) drawInRect:(NSRect)rect
	 withAttributes:(NSDictionary *)attrs;
{
	[self drawWithRect:rect options:0 attributes:attrs];
}

- (void) drawWithRect:(NSRect)rect
			  options:(NSStringDrawingOptions)options
		   attributes:(NSDictionary *)attributes;
{
#if 0
	NSAutoreleasePool *arp=[NSAutoreleasePool new];
	NSLog(@"drawWithRect");
	{
#endif
	NSAttributedString *a=[[NSAttributedString alloc] initWithString:self attributes:attributes];
	[a drawWithRect:rect options:options];	// draw attributed string
	[a release];							// no longer needed
#if 0
	}
	[arp release];
	NSLog(@"drawWithRect arp released");
#endif
}

// FIXME: this should use NSFont to get info on client side

- (NSSize) sizeWithAttributes:(NSDictionary *)attrs;
{
#if OTHER
	NSAttributedString *a=[[NSAttributedString alloc] initWithString:self attributes:attrs];
	NSSize size=[a size];	// get size of attributed string
	[a release];			// no longer needed
	return size;
#endif
	NSGraphicsContext *ctxt=[NSGraphicsContext currentContext];
	if([self length] == 0)
		return NSZeroSize;	// empty string has no size (or should we return font height?)
	if(!ctxt)
		{
		NSLog(@"-sizeWithAttributes:%@ has no active graphics context! (%@)", attrs, self);	// no chance to query the backend...
		return NSZeroSize;
		}
	// should do the same as drawing, i.e. collect fragments and determine total size
	// how to get/specify line spacing?
	return [ctxt _string:self withAttributes:attrs draw:NO];
}

@end


@implementation NSAttributedString (NSAttributedStringDrawingAdditions)

- (NSRect) boundingRectWithSize:(NSSize) size
						options:(NSStringDrawingOptions) options;
{
	NSRect r;
	r.origin=NSZeroPoint;	// should be position of first glyph
	r.size=[self size];
	return r;
}

- (void) drawAtPoint:(NSPoint) point;
{
	NSRect r;
	r.origin=point; // calculate a "virtual" bounding box
	r.size=NSMakeSize(16000.0, 16.0);	// should apply font-dependent height of characters, i.e. get NSFont
	[self drawWithRect:r options:0];
}

- (void) drawInRect:(NSRect) rect;
{
	[self drawWithRect:rect options:0];
}

- (void) drawWithRect:(NSRect) rect options:(NSStringDrawingOptions) options;
{ // draw with line breaks within box defined by rect - might clip
	/* potential options
	NSStringDrawingUsesLineFragmentOrigin
	NSStringDrawingUsesFontLeading
	NSStringDrawingDisableScreenFontSubstitution
	NSStringDrawingUsesDeviceMetrics
	NSStringDrawingOneShot	// don't cache
	*/
	NSString *str;
	NSRange rangeLimit;
	NSGraphicsContext *ctxt;
	NSPoint pos;
#define hpos pos.x
	if([self length] == 0)
		return;	// empty string
	str=[self string];						// raw characters
	rangeLimit=NSMakeRange(0, [str length]);	// initial limit
	ctxt=[NSGraphicsContext currentContext];
	/* FIXME:
	- save context
	- translate context to rect origin
	*/
	[ctxt _beginText];	// starts at (0,0)
	pos=NSZeroPoint;	// tracks current drawing position
	if(![ctxt isFlipped])
		rect.origin.y+=rect.size.height;	// start at top of rect
	// FIXME: not needed if backend handles complete TM and CTM
	[ctxt _setTextPosition:rect.origin];
	hpos=0.0;	// initial horizontal position
	while(rangeLimit.length > 0)
		{ // parse and process white-space separated words resp. fragments with same attributes
		NSDictionary *attr;
		NSRange attribRange;
		NSRange wordRange;
		NSSize size;
		switch([str characterAtIndex:rangeLimit.location])
			{
			case '\n':
				[ctxt _newLine];	// start new line
				rangeLimit.location++;
				rangeLimit.length--;
				hpos=0.0;
				continue;
			case '\t':
				// go to next tab position
				// increment hpos to be multiple of tab width
				// tab width = 8*width of "x" character in current font - or 28 points
				// [ctxt _gotohpos:hpos];
				rangeLimit.location++;
				rangeLimit.length--;
				continue;
			case '\r':
				// start over at beginning of line
				hpos=0.0;
				// [ctxt _gotohpos:hpos];
				rangeLimit.location++;
				rangeLimit.length--;
				continue;
			}
		attr=[self attributesAtIndex:rangeLimit.location longestEffectiveRange:&attribRange inRange:rangeLimit];
		wordRange=[str rangeOfCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] options:0 range:attribRange];	// embedded space in this range?
		if(wordRange.length != 0)
			{ // any whitespace found within attribute range
			if(wordRange.location > attribRange.location)	
				attribRange.length=wordRange.location-attribRange.location;	// cut attribute range at next white-space character
			else
				attribRange.length=1;	// limit to whitespace character itself
			}
		// FIXME: this algorithm does not really word-wrap (only) if attributes change within a word
		size=[ctxt _string:[str substringWithRange:attribRange] withAttributes:attr draw:NO];
		if(hpos + size.width > rect.size.width)
			{ // fragment does not fit into remaining line
			if(hpos > 0.0)
				{ // we didn't just do a newline...
				[ctxt _newLine];	// start over on new line
				hpos=0.0;
				if(wordRange.location > attribRange.location)
					{ // eat whitespace (might be blank, tab or newline)
					// FIXME: this does not eat multiple spaces or tabs
					rangeLimit.location=NSMaxRange(attribRange);
					rangeLimit.length-=attribRange.length;
					continue;
					}
				}
			while(size.width > rect.size.width && attribRange.length > 1)
				{ // does not fit into box at all - truncate
				attribRange.length--;	// try one character less
				size=[ctxt _string:[str substringWithRange:attribRange] withAttributes:attr draw:NO];
				}
			// FIXME: handle special case of a space character at beginning of line
			}
		size=[ctxt _string:[str substringWithRange:attribRange] withAttributes:attr draw:YES];
		rangeLimit.location=NSMaxRange(attribRange);	// handle next fragment
		rangeLimit.length-=attribRange.length;
		hpos+=size.width;
		}
	[ctxt _endText];
}

// FIXME: this should use NSFont to get info on client side

- (NSSize) size;
{
	// FIXME: should do the same as drawing, i.e. collect fragments and determine total size
	return [[self string] sizeWithAttributes:[self attributesAtIndex:0 effectiveRange:NULL]];
}

@end
