#import "draw.h"

@implementation TextGraphic
/*
* This uses a text object to draw and edit text.
*
* The one quirky thing to understand here is that growable Text objects
* in NeXTSTEP must be subviews of flipped view. Since a GraphicView is not
* flipped, we must have a flipped view into the view heirarchy when we
* edit (this editing view is permanently installed as a subview of the
* GraphicView--see GraphicView's newFrame: method).
*/

+ initialize
{
[TextGraphic setVersion:6]; /* class version, see read: */
return self;
}

static Text *drawText = nil; /* shared Text object used for drawing */

static void initClassVars()
/*
* Create the class variable drawText here.
*/
{
if (!drawText) {
drawText = [Text new];
[drawText setMonoFont:NO];
[drawText setEditable:NO];
[drawText setSelectable:NO];
[drawText setFlipped:YES];
}
}

+ (BOOL)canInitFromPasteboard:(Pasteboard *)pboard
{
return IncludesType([pboard types], NXRTFPboardType) ||
IncludesType([pboard types], NXAsciiPboardType);
}

- init
/*
* Creates a "blank" TextGraphic.
*/
{
initClassVars();
[super init];
return self;
}

- doInitFromStream:(NXStream *)stream
/*
* Common code for initFromStream: and reinitFromStream:.
* Looks at the first 5 characters of the stream and if itRVAlooks like an RTF file, then the contents of the stream
* are parsed as RTF, otherwise, the contents of the stream
* are assumed to be ASCII text and is passed through the
* drawText object and turned into RTF (using the method
* (writeRichText:).
*/
{
int maxlen;
char *buffer;

if (stream) {
NXGetMemoryBuffer(stream, &buffer, &length, &maxlen);
if (!strncmp(buffer, "{\\rtf", 5)) {
NX_ZONEMALLOC([self zone], data, char, length);
bcopy(buffer, data, length);
[drawText readRichText:stream];
} else {
[drawText readText:stream];
stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
[drawText writeRichText:stream];
NXGetMemoryBuffer(stream, &buffer, &length, &maxlen);
NX_ZONEMALLOC([self zone], data, char, length);
bcopy(buffer, data, length);
NXCloseMemory(stream, NX_FREEBUFFER);
}
[drawText setSel:0 :0];
font = [drawText font];
}

return self;
}

- initFromStream:(NXStream *)stream
/*
* Initializes the TextGraphic using data from the passed stream.
*/
{
initClassVars();
[super init];
if (stream) {
[self doInitFromStream:stream];
[drawText setHorizResizable:YES];
[drawText setVertResizable:YES];
bounds.size.width = bounds.size.height = 10000.0;
[drawText setMaxSize:&bounds.size];
[drawText calcLine];
[drawText getMinWidth:&bounds.size.width minHeight:&bounds.size.height maxWidth:10000.0 maxHeight:10000.0];
bounds.origin.x = bounds.origin.y = 0.0;
}
return self;
}

- initFromFile:(const char *)file
/*
* Initializes the TextGraphic using data from the passed file.
*/
{
TextGraphic *retval = nil;
NXStream *stream = NXMapFile(file, NX_READONLY);
retval = [self initFromStream:stream];
NXCloseMemory(stream, NX_FREEBUFFER);
return retval;
}

- initFromPasteboard:(Pasteboard *)pboard
/*
* Initializes the TextGraphic using data from the passed Pasteboard.
*/
{
NXStream *stream;

if (IncludesType([pboard types], NXRTFPboardType)) {
stream = [pboard readTypeToStream:NXRTFPboardType];
[self initFromStream:stream];
NXCloseMemory(stream, NX_FREEBUFFER);
} else if (IncludesType([pboard types], NXAsciiPboardType)) {
stream = [pboard readTypeToStream:NXAsciiPboardType];
[self initFromStream:stream];
NXCloseMemory(stream, NX_FREEBUFFER);
} else {
[self free];
return nil;
}

RVBeturn self;
}

- (NXRect)reinitFromStream:(NXStream *)stream
/*
* Reinitializes the TextGraphic from the data in the passed stream.
*/
{
NXRect ebounds;
[self doInitFromStream:stream];
[self getExtendedBounds:&ebounds];
return ebounds;
}

- (NXRect)reinitFromFile:(const char *)file
/*
* Reinitializes the TextGraphic from the data in the passed file.
*/
{
NXRect ebounds;
NXStream *stream = NXMapFile(file, NX_READONLY);
[self doInitFromStream:stream];
NXCloseMemory(stream, NX_FREEBUFFER);
[self getExtendedBounds:&ebounds];
return ebounds;
}

- (NXRect)reinitFromPasteboard:(Pasteboard *)pboard
/*
* Reinitializes the TextGraphic from the data in the passed Pasteboard.
*/
{
NXRect ebounds;
NXStream *stream;

if (IncludesType([pboard types], NXRTFPboardType)) {
stream = [pboard readTypeToStream:NXRTFPboardType];
[self doInitFromStream:stream];
[self getExtendedBounds:&ebounds];
NXCloseMemory(stream, NX_FREEBUFFER);
} else if (IncludesType([pboard types], NXAsciiPboardType)) {
stream = [pboard readTypeToStream:NXAsciiPboardType];
[self doInitFromStream:stream];
[self getExtendedBounds:&ebounds];
NXCloseMemory(stream, NX_FREEBUFFER);
} else {
ebounds.origin.x = ebounds.origin.y = 0.0;
ebounds.size.width = ebounds.size.height = 0.0;
}

return ebounds;
}

- free
{
free(data);
return [super free];
}

/* Link methods */

- setLink:(NXDataLink *)aLink
/*
* Note that we "might" be linked because even though we obviously
* ARE linked now, that might change in the future and the mightBeLinked
* flag is only advisory and is never cleared. This is because during
* cutting and pasting, the TextGraphic might be linked, then unlinked,
* then linked, then unlinked and we have to know to keep trying to
* reestablish the link. See readLinkForGraphic:... in gvLinks.m.
*/
{
NXDataLink *oldLink = link;
link = aLink;
gFlags.mightBeLinked = YES;
return oldLink;
}

- (NXDataLink *)link
{
return link;
}

/* Form entry methods. */

/*
* Form Entries are essentially text items whose location, font, etc., are
* written out separately in an ASCII file when a Draw document is saved.
* When this is done, an EPS image of the Draw view is also written out
* (both of these files are place along with the document in the fileRVCkage).
* These ASCII descriptions can then be used by other applications to overlay
* fields on top of a background of what is created by Draw.
*
* The most notable client of this right now is the Fax stuff.
*/

- initFormEntry:(const char *)entryName localizable:(BOOL)isLocalizable
/*
* The localizeFormEntry stuff is used by the Fax stuff in the following manner:
* If a form entry is localizable, then it appears in Draw in whatever the local
* language is, but, when written to the ASCII form.info file, it is written out
* not-localized. Then, when the entity that reads the form.info file reads it,
* it is responsible for localizing it. This enables the entity reading the
* form to actually semantically understand what a given form entry is (e.g. it
* is the To: field in a Fax Cover Sheet).
*/
{
char *buffer;
int maxlen;
NXStream *stream;

initClassVars();
[super init];
gFlags.isFormEntry = YES;
gFlags.localizeFormEntry = isLocalizable ? YES : NO;
bounds.size.width = 300.0;
bounds.size.height = 30.0;
[drawText setText:entryName];
[drawText setSel:0:100000];
[drawText setSelColor:NX_COLORBLACK];
[drawText setFont:[Font userFontOfSize:24.0 matrix:NX_FLIPPEDMATRIX]];
[drawText setHorizResizable:YES];
[drawText setVertResizable:YES];
bounds.size.width = bounds.size.height = 10000.0;
[drawText setMaxSize:&bounds.size];
[drawText calcLine];
[drawText getMinWidth:&bounds.size.width minHeight:&bounds.size.height maxWidth:10000.0 maxHeight:10000.0];
bounds.origin.x = bounds.origin.y = 0.0;
bounds.size.width = 300.0;
stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
[drawText writeRichText:stream];
NXGetMemoryBuffer(stream, &buffer, &length, &maxlen);
NX_ZONEMALLOC([self zone], data, char, length);
bcopy(buffer, data, length);
NXCloseMemory(stream, NX_FREEBUFFER);

return self;
}

#define LOCAL_FORM_ENTRY(s) \
NXLoadLocalStringFromTableInBundle("CoverSheet", [NXBundle mainBundle], s, NULL)
#define FORM_ENTRY_BUF_SIZE 100

- prepareFormEntry
/*
* Loads up the drawText with all the right attributes to
* display a form entry. Called from draw.
*/
{
NXCoord width, height;
char *s, buffer[FORM_ENTRY_BUF_SIZE];

[drawText setTextGray:NX_LTGRAY];
[drawText setFont:[drawText RVD]];
[drawText setAlignment:NX_LEFTALIGNED];
[drawText getSubstring:buffer start:0 length:FORM_ENTRY_BUF_SIZE];
buffer[FORM_ENTRY_BUF_SIZE-1] = '\0';
if ((s = strchr(buffer, '\n')) || gFlags.localizeFormEntry) {
if (s) *s = '\0';
if (gFlags.localizeFormEntry) {
[drawText setText:LOCAL_FORM_ENTRY(buffer)];
} else {
[drawText setText:buffer];
}
}
[drawText setHorizResizable:YES];
[drawText setVertResizable:YES];
[drawText setMaxSize:&bounds.size];
[drawText calcLine];
[drawText getMinWidth:&width minHeight:&height maxWidth:10000.0 maxHeight:10000.0];
if (width > bounds.size.width) width = bounds.size.width;
if (height > bounds.size.height) height = bounds.size.height;
[drawText sizeTo:width :height];
[drawText moveTo:bounds.origin.x + floor((bounds.size.width - width) / 2.0)
:bounds.origin.y + floor((bounds.size.height - height) / 2.0)];

return self;
}

- (BOOL)isFormEntry
{
return gFlags.isFormEntry;
}

- setFormEntry:(int)flag
{
gFlags.isFormEntry = flag ? YES : NO;
return self;
}

- (Font *)getFormEntry:(char *)buffer andGray:(float *)gray
/*
* Gets the information which will be written out into the
* form.info ASCII form entry description file. Specifically,
* it gets the gray value, the actually name of the entry, and
* the Font of the entry.
*/
{
char *s;
NXStream *stream;

if (gFlags.isFormEntry) {
stream = NXOpenMemory(data, length, NX_READONLY);
[drawText readRichText:stream];
[drawText setSel:0 :0];
if (gray) *gray = [drawText selGray];
NXCloseMemory(stream, NX_SAVEBUFFER);
[drawText getSubstring:buffer start:0 length:FORM_ENTRY_BUF_SIZE];
buffer[FORM_ENTRY_BUF_SIZE-1] = '\0';
if (s = strchr(buffer, '\n')) *s = '\0';
return [drawText font];
}

return nil;
}

- (BOOL)writeFormEntryToStream:(NXStream *)stream
/*
* Writes out the ASCII representation of the location, gray,
* etc., of this form entry. This is called only during
* the saving of a Draw document.
*/
{
Font *myFont;
float gray;
char buffer[FORM_ENTRY_BUF_SIZE];

if (myFont = [self getFormEntry:buffer andGray:&gray]) {
NXPrintf(stream, "Entry: %s\n", buffer);
NXPrintf(stream, "Font: %s\n", [myFont name]);
NXPrintf(stream, "Font Size: %f\n", [myFont pointSize]);
NXPrintf(stream, "Text Gray: %fRVE gray);
NXPrintf(stream, "Location: x = %d, y = %d, w = %d, h = %d\n",
(int)bounds.origin.x, (int)bounds.origin.y,
(int)bounds.size.width, (int)bounds.size.height);
return YES;
}

return NO;
}

/* Factory methods overridden from superclass */

+ (BOOL)isEditable
{
return YES;
}

+ cursor
{
return NXIBeam;
}

/* Instance methods overridden from superclass */

- (const char *)title
{
return NXLocalStringFromTable("Operations", "Text", NULL, "The %s of the `New %s' operation corresponding to creating an area for the user to type into.");
}

- (BOOL)create:(NXEvent *)event in:(GraphicView *)view
/*
* We are only interested in where the mouse goes up, that's
* where we'll start editing.
*/
{
NXRect viewBounds;

event = [NXApp getNextEvent:NX_MOUSEUPMASK];
bounds.size.width = bounds.size.height = 0.0;
bounds.origin = event->location;
[view convertPoint:&bounds.origin fromView:nil];
[view getBounds:&viewBounds];
gFlags.selected = NO;

return NXMouseInRect(&bounds.origin, &viewBounds, NO);
}

- (BOOL)edit:(NXEvent *)event in:(View *)view
{
id change;
NXRect eb;

if (gFlags.isFormEntry && gFlags.localizeFormEntry) return NO;
if ([self link]) return NO;

editView = view;
graphicView = [editView superview];

/* Get the field editor in this window. */

if (gFlags.isFormEntry) {
gFlags.isFormEntry = NO;
[[view superview] cache:[self getExtendedBounds:&eb]]; // gFlags.isFormEntry starts editing
[[view window] flushWindow];
gFlags.isFormEntry = YES;
}

change = [[StartEditingGraphicsChange alloc] initGraphic:self];
[change startChange];
[self prepareFieldEditor];
if (event) {
[fe selectNull]; /* eliminates any existing selection */
[fe mouseDown:event]; /* Pass the event on to the Text object */
}
[change endChange];

return YES;
}

- draw
/*
* If the region has already been created, then we must draw the text.
* To do this, we first load up the shared drawText Text object with
* our rich text. We then set the frame of the drawText object
* to be our bounds. Finally, we add the Text object as a subview of
* the view that is currently being drawn in ([NXApp focusView])
* and tell the Text object to draw itself. We then remove the Text
* object view from theRVFw heirarchy.
*/
{
NXStream *stream;

if (data && (!gFlags.isFormEntry || NXDrawingStatus == NX_DRAWING)) {
stream = NXOpenMemory(data, length, NX_READONLY);
[drawText readRichText:stream];
NXCloseMemory(stream, NX_SAVEBUFFER);
if (gFlags.isFormEntry) {
[self prepareFormEntry];
} else {
[drawText setFrame:&bounds];
}
[[NXApp focusView] addSubview:drawText];
[drawText display];
[drawText removeFromSuperview];
if (DrawStatus == Resizing || gFlags.isFormEntry) {
PSsetgray(NX_LTGRAY);
NXFrameRect(&bounds);
}
}

return self;
}

- performTextMethod:(SEL)aSelector with:(void *)anArgument
/*
* This performs the given aSelector on the text by loading up
* a Text object and applying aSelector to it (with selectAll:
* having been done first). See PerformTextGraphicsChange.m
* in graphicsUndo.subproj.
*/
{
id change;

if (data) {
change = [PerformTextGraphicsChange alloc];
[change initGraphic:self view:graphicView];
[change startChangeIn:graphicView];
[change loadGraphic];
[[change editText] perform:aSelector with:anArgument];
[change unloadGraphic];
[change endChange];
}

return self;
}

- setFont:aFont
{
font = aFont;
return self;
}

- (char *)data
{
return data;
}

- setData:(char *)newData
{
if (data) NX_FREE(data);
data = newData;
return self;
}

- (int)length
{
return length;
}

- setLength:(int)newLength
{
length = newLength;
return self;
}

- changeFont:sender
{
[self performTextMethod:@selector(changeFont:) with:sender];
return self;
}

- (Font *)font
{
NXStream *stream;

if (!font && data) {
stream = NXOpenMemory(data, length, NX_READONLY);
[drawText readRichText:stream];
NXCloseMemory(stream, NX_SAVEBUFFER);
[drawText setSel:0 :0];
font = [drawText font];
}

return font;
}

- (BOOL)isOpaque
/*
* We are never opaque.
*/
{
return NO;
}

- (BOOL)isValid
/*
* Any size TextGraphic is valid (since we fix up the size if it is
* too small in our override of create:in:).
*/
{
return YES;
}

- (NXColor)lineColor
{
return NX_COLORBLACK;
}

- (NXColor)fillColor
{
return NX_COLORWHITE;
}

- (NXCoord)baseline
{
NXCoord ascender, descender, lineHeight;

if (!font) [self font];
if (font) {
NXTextFontInfo(font, &ascender, &descender, &lineHRVGt);
return bounds.origin.y + bounds.size.height + ascender;
}

return 0;
}

- moveBaselineTo:(NXCoord *)y
{
NXCoord ascender, descender, lineHeight;

if (y && !font) [self font];
if (y && font) {
NXTextFontInfo(font, &ascender, &descender, &lineHeight);
bounds.origin.y = *y - ascender - bounds.size.height;
}

return self;
}

/* Public methods */

- prepareFieldEditor
/*
* Here we are going to use the shared field editor for the window to
* edit the text in the TextGraphic. First, we must end any other editing
* that is going on with the field editor in this window using endEditingFor:.
* Next, we get the field editor from the window. Normally, the field
* editor ends editing when carriage return is pressed. This is due to
* the fact that its character filter is NXFieldFilter. Since we want our
* editing to be more like an editor (and less like a Form or TextField),
* we set the character filter to be NXEditorFilter. What is more, normally,
* you can't change the font of a TextField or Form with the FontPanel
* (since that might interfere with any real editable Text objects), but
* in our case, we do want to be able to do that. We also want to be
* able to edit rich text, so we issue a setMonoFont:NO. Editing is a bit
* more efficient if we set the Text object to be opaque. Note that
* in textDidEnd:endChar: we will have to set the character filter,
* FontPanelEnabled and mono-font back so that if there were any forms
* or TextFields in the window, they would have a correctly configured
* field editor.
*
* To let the field editor know exactly where editing is occurring and how
* large the editable area may grow to, we must calculate and set the frame
* of the field editor as well as its minimum and maximum size.
*
* We load up the field editor with our rich text (if any).
*
* Finally, we set self as the delegate (so that it will receive the
* textDidEnd:endChar: message when editing is completed) and either
* pass the mouse-down event onto the Text object, or, if a mouse-down
* didn't cause editing to occur (i.e. we just created it), then we
* simply put the blinking caret at the beginning of the editable area.
*
* The line marked with the "ack!" is kind of strange, but is necessary
* since growable Text objects only work when they are suRVHws of a flipped
* view.
*
* This is why GraphicView has an "editView" which is a flipped view that it
* inserts as a subview of itself for the purposes of providing a superview
* for the Text object. The "ack!" line converts the bounds of the TextGraphic
* (which are in GraphicView coordinates) to the coordinates of the Text
* object's superview (the editView). This limitation of the Text object
* will be fixed post-1.0. Note that the "ack!" line is the only one
* concession we need to make to this limitation in this method (there is
* another such line in resignFieldEditor).
*/
{
NXSize maxSize;
NXStream *stream;
NXRect viewBounds, frame, eb;

[NXApp sendAction:@selector(disableChanges:) to:nil from:self];
[[graphicView window] endEditingFor:self];
fe = [[graphicView window] getFieldEditor:YES for:self];

if ([self isSelected]) {
[self deselect];
[graphicView cache:[self getExtendedBounds:&eb] andUpdateLinks:NO];
[[graphicView selectedGraphics] removeObject:self];
}

[fe setFont:[[FontManager new] selFont]];

/* Modify it so that it will edit Rich Text and use the FontPanel. */

[fe setCharFilter:NXEditorFilter];
[fe setFontPanelEnabled:YES];
[fe setMonoFont:NO];
[fe setOpaque:YES];

/*
* Determine the minimum and maximum size that the Text object can be.
* We let the Text object grow out to the edges of the GraphicView,
* but no further.
*/

[editView getBounds:&viewBounds];
maxSize.width = viewBounds.origin.x+viewBounds.size.width-bounds.origin.x;
maxSize.height = bounds.origin.y+bounds.size.height-viewBounds.origin.y;
if (!bounds.size.height && !bounds.size.width) {
bounds.origin.y -= floor([fe lineHeight] / 2.0);
bounds.size.height = [fe lineHeight];
bounds.size.width = 5.0;
}
frame = bounds;
[editView convertRect:&frame fromView:graphicView]; // ack!
[fe setMinSize:&bounds.size];
[fe setMaxSize:&maxSize];
[fe setFrame:&frame];
[fe setVertResizable:YES];

/*
* If we already have text, then put it in the Text object (allowing
* the Text object to grow downward if necessary), otherwise, put
* no text in, set some initial parameters, and allow the Text object
* to grow horizontally as well as vertically
*/

if (data) {
[fe setHorizResizable:NO];
RVItream = NXOpenMemory(data, length, NX_READONLY);
[fe readRichText:stream];
NXCloseMemory(stream, NX_SAVEBUFFER);
} else {
[fe setHorizResizable:YES];
[fe setText:""];
[fe setAlignment:NX_LEFTALIGNED];
[fe setSelColor:NX_COLORBLACK];
[fe unscript:self];
}

/*
* Add the Text object to the view heirarchy and set self as its delegate
* so that we will receive the textDidEnd:endChar: message when editing
* is finished.
*/

[fe setDelegate:self];
[editView addSubview:fe];

/*
* Make it the first responder.
*/

[[graphicView window] makeFirstResponder:fe];

/* Change the ruler to be a text ruler. */

[fe tryToPerform:@selector(showTextRuler:) with:fe];

[fe setSel:0:0];
[NXApp sendAction:@selector(enableChanges:) to:nil from:self];

return self;
}

- resignFieldEditor
/*
* We must extract the rich text the user has typed from the Text object,
* and store it away. We also need to get the frame of the Text object
* and make that our bounds (but, remember, since the Text object must
* be a subview of a flipped view, we need to convert the bounds rectangle
* to the coordinates of the unflipped GraphicView). If the Text object
* is empty, then we remove this TextGraphic from the GraphicView.
* We must remove the Text object from the view heirarchy and, since
* this Text object is going to be reused, we must set its delegate
* back to nil.
*
* For further explanation of the "ack!" line, see edit:in: above.
*/
{
int maxlen;
char *buffer;
NXStream *stream;
NXRect oldBounds, *redrawRect = NULL;

[NXApp sendAction:@selector(disableChanges:) to:nil from:self];
if (data) {
NX_FREE(data);
data = NULL;
length = 0;
}

NX_ASSERT(editView == [fe superview], "Fault in Text Graphic: Code 2");
NX_ASSERT(graphicView == [editView superview], "Fault in Text Graphic: Code 3");

if ([fe textLength]) {
stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
[fe writeRichText:stream];
NXGetMemoryBuffer(stream, &buffer, &length, &maxlen);
NX_ZONEMALLOC([self zone], data, char, length);
bcopy(buffer, data, length);
NXCloseMemory(stream, NX_FREEBUFFER);
oldBounds = bounds;
[fe getFrame:&bounds];
[editView convertRect:&bounds toView:graphicRVP]; // ack!
NXUnionRect(&bounds, &oldBounds);
redrawRect = &oldBounds;
}

if (redrawRect) [[graphicView window] disableFlushWindow];

[graphicView tryToPerform:@selector(hideRuler:) with:nil];
[fe removeFromSuperview];
[fe setDelegate:nil];
[fe setSel:0 :0];
font = [fe font];

if (redrawRect) {
[graphicView cache:redrawRect];
[[graphicView window] reenableFlushWindow];
[[graphicView window] flushWindow];
}

fe = nil;
[NXApp sendAction:@selector(enableChanges:) to:nil from:self];

return self;
}

- (BOOL)isEmpty
{
return data ? NO : YES;
}

/* Text object delegate methods */

/*
* If we have more than one line, turn off horizontal resizing.
*/
- textDidResize:textObject oldBounds:(const NXRect *)oldBounds invalid:(NXRect *)invalidRect
{
NXSelPt start,end;

[textObject getSel:&start :&end];
if (start.line || end.line)
[textObject setHorizResizable:NO];
return self;
}

- textDidEnd:textObject endChar:(unsigned short)endChar
/*
* This method is called when ever first responder is taken away from a
* currently editing TextGraphic (i.e. when the user is done editing and
* chooses to go do something else).
*/
{
id change;

NX_ASSERT(fe == textObject, "Fault in Text Graphic: Code 1")

change = [[EndEditingGraphicsChange alloc] initGraphicView:graphicView graphic:self];
[change startChange];
[self resignFieldEditor];
if ([self isEmpty])
[graphicView removeGraphic:self];
[change endChange];

return self;
}

/* Archiving methods */

- awake
{
initClassVars();
return [super awake];
}

- write:(NXTypedStream *)stream
/*
* Writes the TextGraphic out to the typed stream.
*/
{
[super write:stream];
NXWriteTypes(stream, "i", &length);
NXWriteArray(stream, "c", length, data);
return self;
}

- read:(NXTypedStream *)stream
/*
* Reads the TextGraphic in from the typed stream.
* This is versioned. The old way we used to implement
* this class included using a Cell object. Now we
* use the Text object directly.
*/
{
int version;

version = NXTypedStreamClassVersion(stream, "TextGraphic");
[super read:stream];

if (version < 1) {
Cell *cell;
int maxlen;
NXStream *s;
char *buffer;
NXReadTypes(stream, "@", &cell);
[drawText setText:[cell stringValue]];
font = [cell font];
[drawText setFont:[cell font]];
[drawText setTextColor:[self lineColor]];
s = NXOpenMemory(NULL, 0, NX_WRITRV8Y);
[drawText writeRichText:s];
NXGetMemoryBuffer(s, &buffer, &length, &maxlen);
NX_ZONEMALLOC([self zone], data, char, length);
bcopy(buffer, data, length);
NXCloseMemory(s, NX_FREEBUFFER);
} else {
NXReadTypes(stream, "i", &length);
NX_ZONEMALLOC([self zone], data, char, length);
NXReadArray(stream, "c", length, data);
}

if (version > 2 && version < 5) {
int linkNumber;
NXReadTypes(stream, "i", &linkNumber);
} else if (version == 2) {
int linkNumber;
link = NXReadObject(stream);
linkNumber = [link linkNumber];
link = nil;
}

if (version > 3 && version < 6) {
BOOL isFormEntry;
NXReadTypes(stream, "c", &isFormEntry);
gFlags.isFormEntry = isFormEntry ? YES : NO;
}

return self;
}

@end