ftp.nice.ch/pub/next/text/apps/ConvertRTF.NIHS.bs.tar.gz#/Convert_RTF/Source/rtfConverter.m

This is rtfConverter.m in view mode; [Download] [Up]

/***********************************************************************\
Converter class for Convert RTF which converts between Mac and NeXT rtf formats.
Copyright (C) 1993 David John Burrowes

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

The author, David John Burrowes, can be reached at:
	davidjohn@kira.net.netcom.com
	David John Burrowes
	1926 Ivy #10
	San Mateo, CA 94403-1367
\***********************************************************************/
#import "rtfConverter.h"
#import "rtfController.h"			// <--- Ugly hack!
#import "rtfFile.h"
#import "rtfToken.h"
#import <stdio.h>
#import <stdlib.h>
#import <string.h>
#import <objc/HashTable.h>
#import "MacToNeXTRTFText.h"
#import "NeXTToMacRTFText.h"
#import "FontEntry.h"
#import <appkit/publicWraps.h> 
#import <appkit/Listener.h>		// for the drag-and-drop of files facility
#import <appkit/Speaker.h>		// ibid
#import <appkit/Application.h>		// To get NXApp (I thought that is what defaults was...)
#import	"djbflag.h"

@implementation rtfConverter


/*######################################################################*\

HISTORY:
	93.04.03	djb	Added code for a bugfix for character conversions.
	Here's the scoop.  Given a document that was of the form:
		{...\f23 blahblah {\f45 foo foo} blah blah...}
	If \f23 was, say, Symbol, and \f45 was Times, then what used to happen is that
	the blahblah would not have characters converted, then, the \f45 would change
	the conversion flag, and 'foo foo' would be, and then 'blah blah' would also be converted
	because I was ignoring group boundraries.  oops.  This will now do that conversion
	properly if the user has chosen to have only standard fonts converted.  This involves
	maintaining a stack of conversion values that we push and pop for each group, so
	we can revert to the previous conversion values easily.  Note that examineFile still
	does not do essentially any processing of whether individual text should be converted,
	and so it wasn't affected by this bug, or the fix.

\*######################################################################*/


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		init
//	Parameters:	none
//	Returns: 	none
//	Stores:		none
//	Description:
//		Initalizes an instance... namely by defining th manager instance as null
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- init
{
	[super   init];
	MacTextConverter = [[MacToNeXTRTFText   alloc]  init];
	NeXTTextConverter = [[NeXTToMacRTFText   alloc] init];
	colorsUsed = NO;
	SymbolNumber = -1;
	addedSymbol = NO;
	numPicts = 0;	// Number of pictures found in the file
	pictNum = 0;		// Number of the last picture found.
	//
	//	These will presumably be set by the caller, we just define them just in case
	//
	RemoveUnderline = NO;
	AlterSingleQuotes = NO;
	ConvertAllText = ConvertAll;	// Modified for change from boolean to 3 optins
	ExtractPicts = NO;
	ConvertPictures =YES;
	//
	//	KeepTokensForMe is intended only for the original author.  No one else
	//	probably could care less.  It stores the various \pich, etc tokens from
	//	a pict group in another file.  These are usually useless details though.
	//
	if (ForDavidJohnBurrowesOnly == 1)
		KeepTokensForMe = YES;
	else
		KeepTokensForMe = NO;
	PictConverterIsOpen = NO;
	//
	//	93.04.03	djb	Added as part of char convert bugfix
	//
	CharConvertStack = (ConvertTypes*) malloc(50*sizeof(ConvertTypes));
	StackTop = 49;
	StackLocation = 0;
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		free
//	Parameters:	none
//	Returns: 	none
//	Stores:		none
//	Description:
//		Frees the object.  This involves killing the pict converter if we opened it.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- free
{
	int			result, flag;
	Instance		ourSpeaker;
	port_t		thePort;

	if (PictConverterIsOpen == YES)
	{
		thePort = NXPortFromName("Convert_PICT", NULL);
		if (thePort != PORT_NULL)
		{
			ourSpeaker = [NXApp appSpeaker];
			[ourSpeaker   setSendPort: thePort ];
			result = [ourSpeaker   msgQuit: &flag];
		}
	}
	[super free];
	return self;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		isThisAGoodFile:
//	Parameters:	A File instance
//	Returns: 	YES if it is, NO if it isn't.
//	Stores:		none
//	Description:
//		This method attempts to determine if the given file (being accessed as an rtfFile
//		instance) is indeed an RTF one.  A file is judged to be an RTF one iff it's first
//		two tokens are {\rtf#.  Note that it restores the file's position when done.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (Boolean) isThisAGoodFile: fileInstance
{
	Instance		thetoken;
	Boolean		 isRTF 			= NO;
	Integer		storedLocation	= [fileInstance   GetCurrentPosition];
	CString		tempName;

	[fileInstance   MoveTo: 0];   
	thetoken = [fileInstance   GetToken];
	if ([thetoken   GetType] == tokenBeginGroup)
		isRTF = YES;
	[thetoken   free];
	
	thetoken = [fileInstance   GetToken];
	tempName = [thetoken  GetName];
	if ((strcmp(tempName, "rtf") == 0) && (isRTF == YES))
		isRTF = YES;
	else
	{
		[self  PutCString: "This does not appear to be an rtf file (because the first two tokens were not `{\\rtf').  Proceed with caution!" Into: SECOND_RESULT];
		isRTF = NO;
	}
	FreeCString(tempName);
	[thetoken   free];
	[fileInstance   MoveTo: storedLocation];   

	return isRTF;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		SetConversionDirection:
//	Parameters:	A boolean value. YES if to assume files are Mac RTF files.  No if not.
//	Returns: 	self
//	Stores:		none
//	Description:
//		If the user has asked that files, by default, be assumed to be Mac rtf files,
//		this will be alled with YES.  If the user prefers to assume RTF files are
//		going to be processed, NO is passed.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- SetConversionDirection: (Boolean) convertAsMacRTF
{
	if (convertAsMacRTF == YES)
		ConvertSource = MacRTF;
	else
		ConvertSource = NeXTRTF;

	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		RemoveFirstNeXTUnderline:
//	Parameters:	A boolean value. YES if we should remove the first \ul0 in an rtf file.
//	Returns: 	self
//	Stores:		none
//	Description:
//		If the source file is a NeXT rtf file (ConvertSource is NeXTRTF), then if our
//		parameter is YES, then we will remove the first \ul0 in the file 
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- RemoveFirstNeXTUnderline: (Boolean) StripFirstUL0
{
	RemoveUnderline = StripFirstUL0;
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		ConvertTheSingleQuotes:
//	Parameters:	A boolean value. YES if we should always change the ascii single quotes
//				to equivalent looking quotes on the destination platform.  Otherwise, let
//				them appear on thedestination as thye would there. (e.g. next single quotes
//				look curley.  mac ones are straight and an accent.  if this is yes, the next
//				quotes will be mapped to curley quotes on the mac, not the default ones)
//	Returns: 	self
//	Stores:		none
//	Description:
//		Stores the specified parameter until later processing.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ConvertTheSingleQuotes: (Boolean) changeQuotes
{
	AlterSingleQuotes = changeQuotes;
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		SetTextConversion:
//	Parameters:	A boolean value. YES if we should convert the special characters for
//				all fonts, NO if we should only do it for 'standard' fonts.
//	Returns: 	self
//	Stores:		none
//	Description:
//		Stores the specified parameter until later processing.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- SetTextConversion: (GuiConvertChoices) convertText
{
	ConvertAllText = convertText;
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Method:		SetPictConversion:AndDataRemoval:
//	Parameters:	YES if we are to convert Mac PICT data
//				YES if we are to remove all traces of pict data
//	Returns: 	self
//	Stores:		none
//	Description:
//		This sets two conversion options in this class.  The first specifies whether
//		we should try to use the Convert_PICT program to convert any PICT data
//		we encounter.  The second is a bit more complex.  This converter always removes
//		the {\pict\macpict...} data from the rtf file.  The question is: do we discard it
//		completely (after perhaps doing the above conversion), or do we store it in an
//		another file (a .pict file).  This latter is only of interest if the user doesn't want
//		to loose the original pict data for one reason or another.  I'm such a person, and
//		so I'd prefer to keep it around.
//	Bugs:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- SetPictConversion: (Boolean) ConvertPictData AndDataRemoval: (Boolean) DeletePictData;
{
	if (DeletePictData == YES)
		ExtractPicts = NO;
	else
		ExtractPicts = YES;
	
	ConvertPictures = ConvertPictData;
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		examineFile: 
//	Parameters:	The file to be examined
//	Returns:		self
//	Stores:		error code
//	Description:
//		This walks through the rtf file from start to end, and watches for certain
//		things:
//			1) Whether any color tokens are used (\cf and \cb)
//			2) it counts how many times each type face is used (using \f)
//				(allowing us to judge what to write out in the font table)
//			3) It stores whether Symbol may be needed for some character conversions (in \')
//			4) Counts occurrences of PICT images (\pict)
//		As part of the work for counting font use, we read in the whole font table, and
//		store it as a fontTable variable.  We then later increment usage counts in the
//		individual FontEntry instances.  (this table is later used to write out the font table)
//	Bugs
//		When computing whether symbol was used while processing \', we ignore
//		the 'current' font at that point.  This may have the effect of leaving the count
//		at least partially inaccurate, since those characters may not actually be converted
//		later on.
//	History:
//		93.01.24	Added storing of the lasterrorcode for the while, because in circumstances
//				where the end of file occurrs at a point where we want to update the
//				progress indicator, we'd wipe out the sourceFile's error code, and produce
//				a crash.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- examineFile: sourceFile
{
	Instance		thetoken, temptoken;
	TokenType	theType;
	CString		theName;
	Real			modifier		= 30.0 / [sourceFile   FileSize];
	Integer		groupDepth	= 0;
	Character	aCharacter;
	Instance		myEntry;
	Integer		tokenCount	= 0;
	CString		tempName;
	Integer		lastErrorCode;
	Character	fontName[31];	// To hold UnnamedFont32767  (for instance)
	Integer		fontNumber;

	[sourceFile   MoveTo: 0];
	thetoken = [sourceFile   GetNextControlToken];
	lastErrorCode = [sourceFile  GetErrorCode];
	theName = [thetoken   GetName];

	while ( lastErrorCode == ERR_OK )
	{
		//
		//	If we just found the \fonttbl token.  Consume the font table and turn it into our
		//	hashed fontTable structure.
		//
		if (strcmp(theName, "fonttbl") == 0)
		{
			groupDepth = 1;
			while (groupDepth > 0)
			{
				//
				//	Watch for the end of the table (when we have
				//	found a matching number of {'s and }'s  (measured by groupDepth).
				//	Store the font information
				//
				[thetoken   free];
				FreeCString(theName);
				thetoken = [sourceFile   GetToken];
				theType = [thetoken   GetType];
				theName = [thetoken   GetName];
				if (theType ==  tokenBeginGroup)
					groupDepth ++;
				else if (theType == tokenEndGroup)
					groupDepth --;
				else if (theType == tokenControlWord)
				{
					if (strcmp(theName, "f") == 0)
					{
						myEntry = [[FontEntry alloc] init];
						[myEntry  SetNumber: [thetoken GetValue]];
						[thetoken   free];
						//
						//	Assume the next token is our type token, and that is followed
						//	by the font name token.
						//
						[myEntry  SetFontType: [sourceFile   GetToken]];
						thetoken = [sourceFile   GetFamilyNameToken];
						tempName = [thetoken   GetName];
						[myEntry   SetName: tempName];
						[myEntry   PrepName];
						FreeCString(tempName);
						//
						//	Add the entry to the font table
						//
						[fontTable   insertKey:  (const void *) [myEntry   GetNumber]
							value: (void *) myEntry];
						//
						//	If this entry described symbol, get the number for reference
						//
						tempName = [myEntry   GetName];
						if (strcmp(tempName, "Symbol") == 0)
							SymbolNumber = [myEntry  GetNumber];
						FreeCString(tempName);
					}
				}	/*	End of if-then-else on a token type */
			}
		}
		else if (strcmp(theName, "f") == 0)
		{
			//
			//	Count how often each font is used. (assumes all \fxx are in the font table) 
			//
			fontNumber =  [thetoken   GetValue];
			myEntry = [fontTable valueForKey:  (const void *) fontNumber];
			if (myEntry== nil)
			{
				//
				//	93.02.10	djb	Added when I realized I really did want a clear record
				//	of all fonts being used in the document, even if I didn't know their
				//	names (this is useful, because you might know what \f201 refers to,
				//	and thus be able to manually fix the font table.  This chunk of code has
				//	the effect of creating a new font entry in the table which will then
				//	have it's count incremented and be written out in the final thing
				//
				myEntry = [[FontEntry alloc] init];
				[myEntry   SetNumber:  fontNumber];
				sprintf(fontName, "UnnamedFont%d", (int) fontNumber);
				[myEntry   SetName: fontName];
				temptoken=[[rtfToken alloc] initTokenOfType: tokenControlWord];
				[temptoken   SetTokenName:   "fnil"];
				[myEntry   SetFontType: temptoken];
				[fontTable   insertKey:  (const void *) fontNumber
					value: (void *) myEntry];
			}
			[myEntry   IncrementCount];
		}
		//
		//	Check whether color values are used.
		//
		else  if ( (strcmp(theName, "cf") == 0) || (strcmp(theName, "cb") == 0) )
				colorsUsed = YES;
		else if  (ConvertSource == MacRTF)
		{
			//
			//	Do some Mac specific conversions.
			//
			if (strcmp(theName, "\'") == 0)
			{
				//
				//	We found a \' token.  Check whether this will be converted into
				//	Symbol (BUG: We ignore the actual font in effect at this piont)
				//	If it will be, the increment the Symbol count.  (we determine if the
				//	char will be converted to symbol by actually converting it and
				//	checking the result code.  ICK!
				//
				aCharacter = (Character)  [thetoken   GetValue];
				[CharConverter   ConvertCharacter: aCharacter];
				if  ([CharConverter   GetBooleanFrom: SECOND_RESULT] == YES)
				{
					//
					//	If symbol wasn't in the font entry table, pick it a number,
					//	by claiming the first unused number
					//
					if (SymbolNumber == -1)
					{
						SymbolNumber = 1;
						while ([fontTable   isKey: (const void *) SymbolNumber] == YES)
							SymbolNumber++;
						myEntry = [[FontEntry alloc] init];
						[myEntry   SetNumber: SymbolNumber];
						[myEntry   SetName: "Symbol"];
						temptoken=[[rtfToken alloc] initTokenOfType: tokenControlWord];
						[temptoken   SetTokenName:   "ftech"];
						[myEntry   SetFontType: temptoken];
						[fontTable   insertKey:  (const void *) SymbolNumber
							value: (void *) myEntry];
						addedSymbol = YES;
					}
					//
					//	Now that we're garantueed that symbol is in here,
					//	increment it's count
					//
					myEntry = [fontTable valueForKey:  (const void *) SymbolNumber] ;
					[ myEntry  IncrementCount ];
				}
			}
			else if (strcmp(theName, "pict") == 0)
			{
				//
				//	If we find a \pict instruction, check to see if it is a Mac picture,
				//	and increment a count if so.
				//
				[thetoken   free];
				FreeCString(theName);
				thetoken = [sourceFile   GetToken];
				theName = [thetoken  GetName];
				if (strcmp(theName, "macpict") == 0)
					numPicts++;
				//
				//	Force the us to ping the mamager, since sometimes
				//	pictures are big enough to cause the update on the screen not to work
				//	right.
				[myManager   SetPercentageDone: modifier *
								[sourceFile  GetCurrentPosition]];
			}
		}
		//
		//	BUG: We should be walking through each word token, and if one would convert
		//	to symbol, then setting symbol as above.
		//
		[thetoken   free];
		FreeCString(theName);
		thetoken = [sourceFile   GetNextControlToken];
		lastErrorCode = [sourceFile   GetErrorCode];
		if (thetoken != NullInstance)
			theName = [thetoken   GetName];
		else
			theName = NullCString;

		if ((tokenCount++ % 100) == 0)
			[myManager   SetPercentageDone: modifier * [sourceFile  GetCurrentPosition]];
 	}

	[thetoken   free];
	FreeCString(theName);

	[self   StoreErrorCode:  ERR_OK AndText: "OK"];
	return self;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		PrepareFontTable
//	Parameters:	none
//	Returns:		self
//	Stores:		none directly
//	Description:
//		Given the instance's fontTable, we walk through each entry, and convert the
//		font names based on which way we are converting.  e.g. the Mac's
//		Avant Garde is called AvantGarde-Book on the NeXT.  Thus, for all the standard
//		Mac fonts, we make sure they will come up right on the NeXT.  And vice versa.
//		Some fonts should not have their characters converted at all (e.g. Zaph Dingbats)
//		because they have the same layout on both platforms.  Otherwise, we use
//		whatever conversion option the user has chosen.
//	Bugs
//		A NeXT font can be specified as, say, AvantGarde-Italic.  Because NeXt utilities
//		don't always record just the family name, it becomes very difficult to retain
//		this information.  So.  We do not.
//	History:
//		93.02.15	djb	Added hack to the end: If the user has chosen to convert NO fonts,
//					then override	whatever we've already put in and set it to none.
//					This is a bit of a kludge, but it suffices for now.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- PrepareFontTable
{
	NXHashState	state			= [fontTable initState]; 
	Integer			fontNumber;
	Instance			fontEntry;
	CString			fontName;
	//
	//	For each fontTable entry, massage the name and the conversion options
	//
	while  ([fontTable   nextState: &state
				key: (void **)&fontNumber
				value: (const void **)&fontEntry] == YES) 
	{
		//
		//	Set the default type of conversion.
		//
		[fontEntry   SetConversion: FullConversion];
		fontName = [fontEntry   GetName];

		if (ConvertSource == MacRTF)
		{
			//
			//	Convert type family names
			//
			if (strcmp(fontName, "Times") == 0)
				[fontEntry   SetName: "Times-Roman"];
			else if (strcmp(fontName, "Avant Garde") == 0)
				[fontEntry   SetName: "AvantGarde-Book"];
			else if (strcmp(fontName, "Bookman") == 0)
				[fontEntry   SetName: "Bookman-Light"];
			else if (strcmp(fontName, "Helvetica Narrow") == 0)
				[fontEntry   SetName: "Helvetica-Narrow"];
			else if (strcmp(fontName, "New Century Schlbk") == 0)
				[fontEntry   SetName: "NewCenturySchlbk-Roman"];
			else if (strcmp(fontName, "Palatino") == 0)
				[fontEntry   SetName: "Palatino-Roman"];
			else if (strcmp(fontName, "Zapf Chancery") == 0)
				[fontEntry   SetName: "ZapfChancery-MediumItalic"];
			else if (strcmp(fontName, "New York") == 0)
				[fontEntry   SetName: "NewYork"];
			else if (strcmp(fontName, "Los Angeles") == 0)
				[fontEntry   SetName: "LosAngeles"];
			else if (strcmp(fontName, "San Francisco") == 0)
				[fontEntry   SetName: "SanFrancisco"];
			//
			//	Also, mark certain type families as requiring no character conversions
			//
			else 	if (strcmp(fontName, "Symbol") == 0)
				[fontEntry   SetConversion: NoConversion];  // Identical on both platforms
			else if (strcmp(fontName, "Zapf Dingbats") == 0)
			{
				[fontEntry   SetName: "ZapfDingbats"];
				[fontEntry   SetConversion: NoConversion]; // Identical on both platforms.
			}
			else if (strcmp(fontName, "Cairo") == 0)
				[fontEntry   SetConversion: NoConversion];
			else if (strcmp(fontName, "Mobile") == 0)
				[fontEntry   SetConversion: NoConversion];
			else if (strcmp(fontName, "Taliesin") == 0) 
				[fontEntry   SetConversion: NoConversion];
			//
			//	The following families require no name changes, and take the FullConversion
			//	If a name isn't in the above or below, we set it's conversion options to
			//	whatever the user specified
			//
			else if ( (strcmp(fontName, "Geneva") != 0)  &&
				(strcmp(fontName, "Courier") != 0)  && 
				(strcmp(fontName, "Helvetica") != 0)  && 
				(strcmp(fontName, "Monaco") != 0)  && 
				(strcmp(fontName, "Athens") != 0)  && 
				(strcmp(fontName, "Toronto") != 0)  && 
				(strcmp(fontName, "Venice") != 0)  && 
				(strcmp(fontName, "London") != 0)  && 
				(strcmp(fontName, "Chicago") != 0)  && 
				(strcmp(fontName, "Seattle") != 0) )
			{
				if (ConvertAllText == ConvertAll)
					[fontEntry   SetConversion: FullConversion];
				else
					[fontEntry   SetConversion: NoConversion];
			}
		}
		else	// NeXTRTF
		{
			//
			//	Convert names of type faces
			//
			if (strcmp(fontName, "Times-Roman") == 0)
				[fontEntry   SetName: "Times"];
			else if (strncmp(fontName, "AvantGarde", 10) == 0)
				[fontEntry   SetName: "Avant Garde"];
			else if (strncmp(fontName, "Bookman",7) == 0)
				[fontEntry   SetName: "Bookman"];
			else if (strcmp(fontName, "Helvetica-Narrow") == 0)
				[fontEntry   SetName: "Helvetica Narrow"];
			else if (strncmp(fontName, "NewCenturySchlbk", 16) == 0)
				[fontEntry   SetName: "New Century Schlbk"];
			else if (strncmp(fontName, "Palatino", 8) == 0)
				[fontEntry   SetName: "Palatino"];
			else if (strncmp(fontName, "ZapfChancery", 12) == 0)
				[fontEntry   SetName: "Zapf Chancery"];
			//
			//	Declare these as being ones that require no conversion
			//
			else 	if (strcmp(fontName, "Symbol") == 0)
				[fontEntry   SetConversion: NoConversion];  // Identical on both platforms
			else if (strncmp(fontName, "ZapfDingbats",12) == 0)
			{
				[fontEntry   SetName: "Zapf Dingbats"];
				[fontEntry   SetConversion: NoConversion]; // Identical on both platforms.
			}
			//
			//	The following families require no name changes, and take the FullConversion
			//	If a name isn't in the above or below, we set it's conversion options to 
			//	whatever the user specified
			//
			else if ( (strcmp(fontName, "Courier") != 0)  && 
				(strcmp(fontName, "Helvetica") != 0)  && 
				(strcmp(fontName, "Ohlfs") != 0)  )
			{
				if (ConvertAllText == ConvertAll)
					[fontEntry   SetConversion: FullConversion];
				else
					[fontEntry   SetConversion: NoConversion];
			}
		}
		//
		//	93.02.15	djb	Added this to force no conversion if the user has chosen.
		//				Note that by now we've polarized the 3 options of ConvertAllText
		//				to the two that a FontEntry understands.
		//	
		if (ConvertAllText == ConvertNone)
			[fontEntry   SetConversion: NoConversion];

		FreeCString(fontName);
	}


	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		WriteFontTableEntriesTo:
//	Parameters:	the file object to write to
//	Returns:		self
//	Stores:		none directly
//	Description:
//		This takes the instance fontTable object, and iterates through it, writing each
//		font entry out to the destination file.  Each entry will have the form
//			{\fxx \yyy fontname ; }
//		Naturally, this assumes that the fontTable was created and filled elsewhere.
//		This does *not* destroy the fontTable's entries.
//	History
//		93.07.07	djb	Bugfix for 1.1 to convert the characters in the name of the font.
//	Bugs
//		By virtue of the way these are read in, this writes out fonts in the reverse order.
//		Thus \f23 is written before \f1.  This is probably not a big deal, just unaesthetically
//		pleasing.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- WriteFontTableEntriesTo: destinationFile
{
	NXHashState	state		= [fontTable initState]; 
	Integer			fontNumber;
	Instance			fontEntry;
	Instance			temptoken;
	CString			tempName;
	//
	//	Step to the next entry in the fontTable.  If there is a next entry, we get
	//	YES and continue
	//
	while  ([fontTable   nextState: &state
				key: (void **)&fontNumber
				value: (const void **)&fontEntry] == YES) 
	{
		//
		//	If this font was used more than 0 times, we will write it out.
		//	(Thus, we avoid writing out the unused entries that Word so happily writes)
		//
		if ([fontEntry   GetCount] > 0)
		{
			//
			//	Write the {\fxx
			//
			temptoken=[[rtfToken alloc] initTokenOfType: tokenBeginGroup];
			[destinationFile   WriteToken: temptoken];
			
			temptoken=[[rtfToken alloc] initTokenOfType: tokenControlWord];
			[temptoken   SetTokenName:   "f"];
			[temptoken   SetTokenValue:   (Integer) [fontEntry   GetNumber]];
			[destinationFile   WriteToken: temptoken];
			//
			//	Write the \yyy (the type of the font (e.g. modern)
			//
			[destinationFile   WriteToken: [fontEntry   GetFontType]];
			//
			//	Write:  font-name ; }
			//
			temptoken = [[rtfToken alloc] initTokenOfType: tokenWord];
			tempName = [fontEntry   GetName];
			[temptoken   SetTokenName:   tempName];
			FreeCString(tempName);
			//
			//	93.07.07	djb	A bug reported (by moi) revealed that if there was an 8 bit char
			//				in a font name, it was not being converted when the rtf file was converted.
			//				So, I've changed the straight 'write out' to the method to convert and
			//				then write out.  Note that we always do a full conversion.  Note also
			//				that this is affected by the current quote conversion preference
			//
			[self   ConvertWordIn: temptoken To: destinationFile WithConversion: FullConversion];

			temptoken = [[rtfToken alloc] initTokenOfType: tokenWord];
			[temptoken   SetTokenName:   ";"];
			[destinationFile   WriteToken: temptoken];

			temptoken=[[rtfToken alloc] initTokenOfType: tokenEndGroup];
			[destinationFile   WriteToken: temptoken];
		}
	}

	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ConvertFrom:To: 
//	Parameters:	The file to be converted from, and the file to be converted to
//	Returns:		self
//	Stores:		error code
//	Description:
//		this long routine is the main converter of rtf tokens.
//		After doing lots of initalizations, walk through the file to get a sense of what is there,
//		and to gather all the information about the font table.
//		If necessary, change to an .rtfd file, and start up a pict converter.
//		After this, walk through the source file one token at a time.  In certain cases,
//		such as in the font and color table, we switch into special modes to consume their
//		data properly.  Each time, we keep a count of how deep we are (counting braces)
//		to know when to quit (the inital brace count comes futher down in the file from
//		where we do this consumption, (where we find the initial token in the group).
//		Otherwise, write out the token in a straight forward manner... more or less.
//		Do some character processing on \' tokens.  And, for word tokens, call another
//		method (this one is already plenty big, thank you very much) to do any
//		conversions to be done there.
//	Bugs
//		If the source has foo\'xxbar and this would become fooñbar, this may cause the
//		single 'word' to be split across lines.
//	Reminder:
//		WriteToken takes posession of the token instance.  We should not free it.
//	History:
//		92.12.13	djb	Beginning to clean up from 'proof of concept' form.
//		93.04.03	djb	Modified so used the char conversion stack rather than a local
//					variable called ConvertChars
//		93.07.07	djb	Enhancement for 1.1.  Added code to convert \bullet to ·, \emdash to
//					Ð, etc.  when converting a Mac RTF file.
//		93.07.10	djb	Added a bit to the code added on 07.07 so it calls a routine to write the token out
//					as it is if we haven't found it in any of the special cases.  This addition is a bit of a hack,
//					since the code is duplicated a line later.  But, this way the multiple if's are avoided if we are
//					doing a conversion from NeXT.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ConvertFrom: sourceFile To: destinationFile
{
	Real				modifier			=  70.0 / [sourceFile   FileSize];
	CString			theName;
	CString			tempString;
	Integer			GroupDepth		= 0;
	Integer			colorStartDepth	= 0;
	Integer			fontStartDepth	= 0;
	WritingState		state			= normal;
	Boolean			LookingForUL	= NO;
	Integer			tokenCount		= 0;
	Instance			myEntry;
	Instance			thetoken;
	Instance			tempToken;
	TokenType		theType;
	NXHashState	tableState;
	Integer			fontNumber;
	Instance			temptoken;
	[self   ResetResults];
	//
	//	Initalize the instance variables for this file conversion, and set up the
	//	proper character converter.
	//
	pictNum = 0;
	colorsUsed = NO;
	SymbolNumber = -1;
	addedSymbol = NO;
	numPicts = 0;
	[self   ResetStack];			//	93.04.03	djb Added for char conversion bugfix
	if (ConvertSource == MacRTF)
		 CharConverter = MacTextConverter;
	else
	{
		CharConverter = NeXTTextConverter;
		//
		//	Set flag to look for underline if user so wishes.
		//
		if ( RemoveUnderline == YES)
			LookingForUL = YES;
	}
	//
	//	Set up the object to hold type family information.
	//
	fontTable = [[HashTable alloc] initKeyDesc:"i" valueDesc: "@"];
	//
	//	Do a first pass through the file, counting various elements for our use here.
	//	Then, convert the new contents of the font table.
	//
	[self   examineFile: sourceFile];
	[self   PrepareFontTable];
	//
	//	If we found PICTs in the file, we must change ourselves to an rtfd file.  Nasty.
	//	(we only do this, though, if we're going to be writing something about them out)
	//
	if  ( (numPicts > 0) &&( (ExtractPicts == YES) || (ConvertPictures == YES)))
		destinationFile = [myManager  MakeDestAnRTFD];
	//
	//	If there's a destination file to convert to, then begin!!!
	//		(the rtfd conversion above might have failed and left us nothing)
	//
	if (destinationFile != NullInstance)
	{
		[sourceFile   MoveTo: 0];
		//
		//	ASSUMPTION:
		//		the first three tokens in the file will be { \rtf and \mac   (or \ansi)
		//		}So, we read these three tokens in, and process accordingly. 
		//		If we fail to find the \rtf,  then we drop into our main converter below.
		//
		thetoken = [sourceFile   GetToken];
		[destinationFile   WriteToken: thetoken];
		GroupDepth ++;
		thetoken = [sourceFile   GetToken];
		theName = [thetoken   GetName];
		if (strcmp(theName, "rtf") == 0)
		{
			[destinationFile   WriteToken: thetoken];
			thetoken = [sourceFile   GetToken];
			if (ConvertSource == MacRTF)	// Ignore whatever it came in with...
				[thetoken   SetTokenName: "ansi"];
			else
				[thetoken   SetTokenName: "mac"];
			[destinationFile   WriteToken: thetoken];
			thetoken = [sourceFile   GetToken];
		}
		FreeCString(theName);
		//
		//	Commence main conversion.
		//
		while ( [sourceFile   GetErrorCode] == ERR_OK )
		{
			//
			//	If we're processing the font table:
			//		1) watch begin and end tokens so we know when we're out
			//		2) When we fall out, dump the fonts processed earlier, and change
			//			state back to the normal processing state.
			//
			if (state == inFontTable)
			{
				theType =[thetoken   GetType];
				if (theType == tokenBeginGroup)
				{
					GroupDepth ++;
					[thetoken   free];
				}
				else if (theType == tokenEndGroup)
				{
					GroupDepth --;
					if (GroupDepth != fontStartDepth)
						[thetoken   free];
					else
					{
						[self   WriteFontTableEntriesTo: destinationFile];
						[destinationFile   WriteToken: thetoken]; // need close brace
						state = normal;
					}
				}
				else
					[thetoken   free];
			}
			//
			//	If we're processing the color table, and colors were not used in the document
			//	then discard the whole table.  Reset our state when we're done.
			//
			else if ( (state == inColorTable) && (colorsUsed == NO))
			{
				theType =[thetoken   GetType];
				if (theType ==  tokenBeginGroup)
					GroupDepth ++;
				else if (theType == tokenEndGroup)
				{
					GroupDepth --;
					if (GroupDepth == colorStartDepth)
						state = normal;
				}
				[thetoken   free];
			}
			else
			{
				theName = [thetoken   GetName];
				//
				//	Switch indented strangely so more room on lines.  =(
				//
				switch([thetoken   GetType])
				{
				case tokenBeginGroup:
					[destinationFile   WriteToken: thetoken];
					GroupDepth ++;
					[self StartNewGroup];	// 93.04.03  added for char convert bugfix
					break;
				case tokenEndGroup:
					GroupDepth --;
					[destinationFile   WriteToken: thetoken];
					[self FinishGroup];	// 93.04.03  added for char convert bugfix
					break;
				case tokenControlWord:
					//
					//	Process a control word, watching for special cases:
					//		\f
					//		\colortbl
					//		\fonttbl
					//		\pict
					//		\ul
					//		\bullet
					//		\emdash
					//		\endash
					//		\lquote
					//		\rquote
					//		\ldblquote
					//		\rdblquote
					//
					if (strcmp(theName, "f") == 0)
					{
						//
						//	Determine whether text in this typeface should be
						//	converted or not.  store the value on the stack.
						//
						myEntry = [fontTable valueForKey:
							(const void *) [thetoken   GetValue]];
						if (myEntry == nil)
							[self SetCurrentConverstionTo: FullConversion];
						else
						{
							if ([myEntry   GetConversion] == FullConversion)
								[self SetCurrentConverstionTo: FullConversion];
							else
								[self SetCurrentConverstionTo: NoConversion];
						}
						[destinationFile   WriteToken: thetoken];
					}
					else if (strncmp(theName, "colortbl", 8) == 0)
					{
						state = inColorTable;
						colorStartDepth = GroupDepth -1;
						if (colorsUsed == YES)
							[destinationFile   WriteToken: thetoken];
						else
						{
							[destinationFile   ClearThroughLastBegin];
							[thetoken   free];
						}
					}
					else	if (strcmp(theName, "fonttbl") == 0)
					{
						state = inFontTable;
						fontStartDepth = GroupDepth -1;
						[destinationFile   WriteToken: thetoken];
					}
					else if (strcmp(theName, "pict") == 0)
					{
						//
						//	If we've found a Mac image, then call the convert routine
						//
						[destinationFile   WriteToken: thetoken];
						thetoken = [sourceFile   GetToken];
						FreeCString(theName);
						theName = [thetoken  GetName];
						[destinationFile   WriteToken: thetoken];
						if (strcmp(theName, "macpict") == 0)
							[self   ConvertMacPictImageIn: sourceFile  To: destinationFile];
						[myManager   SetPercentageDone:
							(modifier * [sourceFile  GetCurrentPosition]) + 30];
					}
					else if ((LookingForUL == YES) && (ConvertSource == NeXTRTF) &&
							(strcmp(theName, "ul") == 0) && ([thetoken  GetValue] == 0) )
					{
						//
						//	We we're still looking for \ul0, and we found it, zap it.
						//
						[thetoken	free];
						LookingForUL = NO;
					}
					//
					//	93.07.07	djb	Added bullet, lquote, rquote, ldblquote, and rdblquote
					//				conversions.  Note that they are only done when converting
					//				a Mac file, since NeXT stuff should not be generating these.
					//				As always, these reveal some problems with the kinda kludgey
					//				parser I have: Minorly, maybe I should be converting them
					//				when going out.  More significantly, this writing out of single
					//				characters as word tokens runsthe risk of these being broken up
					//				from their associated text, which we may not want.  Neither
					//				issue is very large, but.... it bugs the perfectionist in me.
					//
					else if (ConvertSource == MacRTF)
					{
						if (strcmp(theName, "bullet") == 0)
						{
							temptoken=[[rtfToken alloc] initTokenOfType: tokenWord];
							[temptoken   SetTokenName:   "·"];
							[destinationFile   WriteToken: temptoken];
						}
						else if (strcmp(theName, "emdash") == 0)
						{
							temptoken=[[rtfToken alloc] initTokenOfType: tokenWord];
							[temptoken   SetTokenName:   "Ð"];
							[destinationFile   WriteToken: temptoken];
						}
						else if (strcmp(theName, "endash") == 0)  // 93.10.15 djb fixed from emdash
						{
							temptoken=[[rtfToken alloc] initTokenOfType: tokenWord];
							[temptoken   SetTokenName:   "±"];
							[destinationFile   WriteToken: temptoken];
						}
						else if (strcmp(theName, "lquote") == 0)
						{
							temptoken=[[rtfToken alloc] initTokenOfType: tokenWord];
							[temptoken   SetTokenName:   "`"];
							[destinationFile   WriteToken: temptoken];
						}
						else if (strcmp(theName, "rquote") == 0)
						{
							temptoken=[[rtfToken alloc] initTokenOfType: tokenWord];
							[temptoken   SetTokenName:   "'"];
							[destinationFile   WriteToken: temptoken];
						}
						else if (strcmp(theName, "ldblquote") == 0)
						{
							temptoken=[[rtfToken alloc] initTokenOfType: tokenWord];
							[temptoken   SetTokenName:   "ª"];
							[destinationFile   WriteToken: temptoken];
						}
						else if (strcmp(theName, "rdblquote") == 0)
						{
							temptoken=[[rtfToken alloc] initTokenOfType: tokenWord];
							[temptoken   SetTokenName:   "º"];
							[destinationFile   WriteToken: temptoken];
						}
						else	// Added this so that we don't loose the token if it is none of the above.
							[destinationFile   WriteToken: thetoken];
					}
					else
						[destinationFile   WriteToken: thetoken];
					break;
				case tokenControlSymbol:
					//
					//	If the token isn't a \',  or a \<newline>, write it out. 
					//	Otherwise, process it some.
					//
					if ((strcmp(theName, "\n")== 0) || (strcmp(theName, "\r") == 0))
					{
						//
						//	Token is escaped end of line character.  Convert to a \par.
						//	Note: Generally \EOL seems to be treated the same
						//	as \par.  But \EOL isn't explicitly defined in the spec I have,
						//	while \par is.  Thus, my preference for it.
						//
						[thetoken   free];
						thetoken=[[rtfToken alloc] initTokenOfType: tokenControlWord];
						[thetoken   SetTokenName:   "par"];
						[destinationFile   WriteToken: thetoken];
					}
					else if (strcmp(theName, "\'") == 0)
					{
						//
						//	If the current type font is to be converted, convert the chars.
						//
						if ([self  GetCurrentConversion] == NoConversion)
						{
							if (ConvertSource == NeXTRTF)
								[destinationFile   WriteToken: thetoken];
							else
							{
								tempToken = [[rtfToken alloc]
										initTokenOfType: tokenWord];
								tempString = NewCString(1);
								tempString[0] = (Character) [thetoken   GetValue];
								tempString[1] = EndOfCString;
								[tempToken   SetTokenName:   tempString];
								[destinationFile   WriteToken: tempToken];
								FreeCString(tempString);
								[thetoken   free];
							}
						}
						else
						{
							[self   ConvertThisChar: (Character)  [thetoken   GetValue]
								Into: destinationFile];
							[thetoken  free];
						}
					}
					else
						[destinationFile   WriteToken: thetoken];
					break;
				case tokenWord:
					//
					//	If we've found a word, stop looking for \ul0
					//	Then, convert the word we found.
					//
					LookingForUL = NO;
					[self   ConvertWordIn: thetoken To: destinationFile
						WithConversion: [self  GetCurrentConversion] ];
					[thetoken free];
					break;
				case tokenNoToken:
					//
					//	This is here mainly to make the compiler stop telling me that
					//	tokenNoToken isn't in the switch.  Free the token while we're at it.
					//
					[thetoken   free];
					break;
				}
				FreeCString(theName);
			}
			//
			//	Tell our manager how we're doing, and get a new token.
			//
			tokenCount++;
			if ((tokenCount %100) == 0)
				[myManager   SetPercentageDone:
					(modifier * [sourceFile  GetCurrentPosition]) + 30];
			thetoken = [sourceFile   GetToken];
		}
		[thetoken   free];	// Free the token that was last retrieved
	}
	tableState = [fontTable initState]; 
	while  ([fontTable   nextState: &tableState
			key: (void **)&fontNumber
			value: (const void **)&myEntry] == YES) 
		{[myEntry  free];}

	[fontTable   free];
	return self;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ConvertWordIn:To:WithConversion:
//	Parameters:	The word token we should be writing out, and the file to write it into. 
//	Returns:		self
//	Stores:		none
//	Description:
//		This does the generally simple (?) task of writing a word token out.
//		If w are converting the current font, then we go through the string a character
//		at a time, and convert the letters.  If we are altering the quotes, we also watch for
//		quote chars and map to the others as appropriate.
//	Bugs
//		Shouldn't we watching for non-printable ascii chars if we aren't
//		converting anyway?
//	History:
//		93.01.02	djb	Broke off from ConvertFrom:To:, and commented!
//		93.04.03	djb	Modified so ConvertChars is of ConvertTypes
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ConvertWordIn: thetoken  To: destinationFile WithConversion: (ConvertTypes) ConvertChars
{
	CString			nameStart;
	CString			namePtr		= [thetoken   GetName];
	Character		aCharacter;
	PositiveInteger	index		= 0;
	Instance			myToken;
	Instance			quoteToken;
	Character		theQuote;
	CString			subPart;
	PositiveInteger	subloc;
	
	nameStart = namePtr;
	//
	//	Write the word out, modifying any characters we need to.
	//
	if (AlterSingleQuotes == NO)
	{
		//
		//	If we are not converting the current font, just write the word.
		//	Otherwise, step through each character.  If you find a character that is
		//	not in the printable range, write out everything up to it, and then
		//	have the char converted and printed out, and then continue.
		//
		if (ConvertChars == NoConversion)
		{
			myToken = [[rtfToken alloc] initTokenOfType: tokenWord];
			[myToken   SetTokenName: namePtr];
			[destinationFile   WriteToken: myToken];
		}
		else
		{
			while (namePtr[index] != EndOfCString)
			{
				aCharacter = namePtr[index];
				if ((aCharacter < 32) || (aCharacter > 126))
				{
					namePtr[index] = EndOfCString;
					myToken = [[rtfToken alloc] initTokenOfType: tokenWord];
					[myToken   SetTokenName: namePtr];
					[destinationFile   WriteToken: myToken];
					namePtr = &namePtr[++index];
					index = 0;
					[self   ConvertThisChar: aCharacter  Into: destinationFile];
				}
				else
					index++;
			}
			//
			//	If there is still a chunk waiting to be written.
			//
			if (index != 0)
			{
				myToken = [[rtfToken alloc] initTokenOfType: tokenWord];
				[myToken   SetTokenName: namePtr];
				[destinationFile   WriteToken: myToken];
			}
		}
	}
	else
	{
		//
		//	Since we are converting quotes, we must examine every character in
		//	the string.
		//
		while (namePtr[index] != EndOfCString)
		{
			//
			//	Look for either start or end quote in namePtr.  Store the character
			//	in theQuote (If it failed to find a quote, it returns the index to the null
			//	terminator)
			//
			index = strcspn(namePtr, "`\'");
			theQuote = namePtr[index];
			//
			//	Don't do this work if the quote was the first character found.
			//
			if (index != 0)
			{
				//
				//	We don't need the quote character any longer, so make it a
				//	null terminator so we can easily process the portion of the string
				//	that came before the quote.
				//
				namePtr[index] = EndOfCString;
				if (ConvertChars == NoConversion)
				{
					myToken = [[rtfToken alloc] initTokenOfType: tokenWord];
					[myToken   SetTokenName: namePtr];
					[destinationFile   WriteToken: myToken];
				}
				else
				{
					//
					//	We are converting the string, so walk through the part up to
					//	the quote, and convert any special characters we find.
					//
					subPart = namePtr;
					subloc = 0;
					while (subPart[subloc] != EndOfCString)
					{
						aCharacter = subPart[subloc];
						if ((aCharacter < 32) || (aCharacter > 126))
						{
							//
							//	we found a character that needs converting.  Soooo.
							//	make IT a null, write out that which went before, and
							//	then convert it and continue.
							//
							subPart[subloc] = EndOfCString;
							myToken = [[rtfToken alloc] initTokenOfType: tokenWord];
							[myToken   SetTokenName: subPart];
							[destinationFile   WriteToken: myToken];
							[self   ConvertThisChar: aCharacter  Into: destinationFile];
							subPart = &subPart[++subloc];
							subloc = 0;
						}
						else
							subloc++;
					}
					if (subloc != 0)
					{
						//
						//	If there are still characters waiting to be written out, do so.
						//
						myToken = [[rtfToken alloc] initTokenOfType: tokenWord];
						[myToken   SetTokenName: subPart];
						[destinationFile   WriteToken: myToken];
					}
				}
			}
			//
			//	Having written out the text that comes before the quote, now
			//	write out the quote itself.
			//
			switch (theQuote)
			{
				case EndOfCString :
					index--;
					break;
				case '\'' :
					quoteToken = [[rtfToken alloc] initTokenOfType: tokenControlSymbol];
					[quoteToken SetTokenName: "\'"];
					if (ConvertSource == MacRTF)
						[quoteToken   SetTokenValue: (Character) '©'];
					else
						[quoteToken   SetTokenValue: (Character) 0xD5];  //mac curley quote
					[destinationFile   WriteToken: quoteToken];
					break;
				case '`' :
					quoteToken = [[rtfToken alloc] initTokenOfType: tokenControlSymbol];
					[quoteToken SetTokenName: "\'"];
					if (ConvertSource == MacRTF)
						[quoteToken   SetTokenValue: (Character) 'Á'];
					else
						[quoteToken   SetTokenValue: (Character) 0xD4];  //mac curley quote
					[destinationFile   WriteToken: quoteToken];
					break;
			}
			//
			//	Set namePtr to point just beyond the quote.
			//
			namePtr = &namePtr[index+1];
			//	93.01.09	djb	Wasn't resetting index.  DUH.  (final pre-beta 1.0 bug)
			index = 0;
		}
	}
	FreeCString(nameStart);
	return self;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ConvertThisChar:Into: 
//	Parameters:	A character we are reading from, and a file to write into.
//	Returns:		self
//	Stores:		none
//	Description:
//		Given a character, this does any conversions needed, and writes it out to
//		the destination file.
//		Conversions that might be needed are: Convert between Mac and NeXT
//		character sets.  Change some ctrl characters to rtf things (e.g. tab char to \tab).
//		Conversion to a \' notation if appropriate and going to the Mac.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ConvertThisChar: (Character) aCharacter Into: destinationFile
{
	Character	newCharacter;
	Boolean		usedSymbol;
	Instance		thetoken;
	CString		tempString;
	CString		newString;
	
	if (aCharacter == '\t')
	{
		thetoken=[[rtfToken alloc] initTokenOfType: tokenControlWord];
		[thetoken   SetTokenName:   "tab"];
		[destinationFile   WriteToken: thetoken];
	}
	else
	{
		//
		//	Try to map the character to the other character set.
		//		If we sould not map the char to a single dest character (e.g. NeXt's
		//			thorn character must be converted to [thorn])
		//		If we could, then check if the result required Symbol.  If so, then
		//			write a proper font change out.
		//		Otherwise, just write out the charaction.
		//
		newCharacter = [CharConverter   ConvertCharacter: aCharacter];
		if ([CharConverter   GetErrorCode] == errCANTMAPTOONE)
		{
			//
			//	If we could not map our character to a single destination character,
			//	Then, resort to this hack.  Store the character in a tiny string.  Ask the
			//	converter to convert it with it's string converter, which will give us a longer
			//	version of the character (e.g. theNeXT thorn character becomes [thorn]),
			//	turn this into a token, and write it out.  
			//
			tempString = NewCString(1);
			tempString[0] = newCharacter;
			tempString[1] = EndOfCString;
			newString = [CharConverter   ConvertString: tempString WithLength: 1];
			thetoken = [[rtfToken alloc] initTokenOfType: tokenWord];
			[thetoken   SetTokenName:   newString];
			[destinationFile   WriteToken: thetoken];
			FreeCString(tempString);
			FreeCString(newString);
		}
		else
		{
			usedSymbol = [CharConverter   GetBooleanFrom: SECOND_RESULT];
			//
			//	If we converted a mac source character, and it will only map to a symbol
			//	character, then write out the introduction to the symbol sequence: 
			//	{\f23  (or whatever number symbol is)
			//
			if  (usedSymbol == YES)
			{
				thetoken=[[rtfToken alloc] initTokenOfType: tokenBeginGroup];
				[destinationFile   WriteToken: thetoken];
				thetoken=[[rtfToken alloc] initTokenOfType: tokenControlWord];
				[thetoken   SetTokenName:   "f"];
				[thetoken   SetTokenValue:   (Integer) SymbolNumber];
				[destinationFile   WriteToken: thetoken];
			}
			//
			//	In all cases, build the proper token type for the converted character
			//	(a new \' or a word token,  depending on whether the character is > or < 126),
			//	and write it out. (I'm taking the liberty of putting ctrl characters out as
			//	control symbols as well)  Also, note that we do not convert to \', but always
			//	to a litteral character when translating TO the NeXT.  This is because
			//	I've not seen Edit write out these commands, and edit mis-displayed some
			//	chars (0xDF-0xFF converted to no visible character in Times-Roman,
			//	when they very well should have) that were in this notation.
			//
			if ( ((newCharacter > 126) || (newCharacter < 32))
				&& (ConvertSource == NeXTRTF))
			{
				thetoken = [[rtfToken alloc] initTokenOfType: tokenControlSymbol];
				[thetoken   SetTokenName:   "\'"];
				[thetoken   SetTokenValue:   (Integer) newCharacter];
			}
			else
			{
				thetoken = [[rtfToken alloc] initTokenOfType: tokenWord];
				tempString = NewCString(1);
				tempString[0] = newCharacter;
				tempString[1] = EndOfCString;
				[thetoken   SetTokenName:   tempString];
				FreeCString(tempString);
			}
			[destinationFile   WriteToken: thetoken];
			//
			//	If we are, building the symbol sequence, add the closing group token.
			//
			if (usedSymbol == YES)
			{
				thetoken=[[rtfToken alloc] initTokenOfType: tokenEndGroup];
				[destinationFile   WriteToken: thetoken];
			}
		}
	}
	return self;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ConvertMacPictImageIn:To: 
//	Parameters:	The rtf file containing the picture info, and the file to write any stuff to.
//	Returns:		self
//	Stores:		none
//	Description:
//		Note 'To:', above, referes to the rtffile that is being converted into, not where we should
//		convert the pict to.
//
//		In brief, consuming picture data from the source file, and treat in some way according
//		to user preferences.
//		When called, several things are assumed:
//			- The destination file has had {\pict\macpict written out to it
//			- The instance variable ExtractPicts will store whether we should write pict
//				data out as files, or whether we should just discard it
//			- The instance variable ConvertPicts will have a YES value if we are to try to
//				get the pict data converted into an eps file
//			- The variable KeepTokensForMe  will have a YES value if all extra tokens in
//				the picture group should be kept (this is probably only of interest to me,
//				hence this  setting is only changable by recompilation)
//		This will then do several things.  It will discard the items mentiod above in the
//		destinationFile.   It will then give the rtf file the WordsArePictures hint. 
//		Following this, tokens are read in one after another.  Ultimately, these are discarded
//		(unles KeepTokensForMe is YES, in which case they are written to a tiny rtf file).
//		If we are converting the image, however, we watch each, and figure out what the
//		width and height of the image should be, and proceed to write proper information
//		to the destinationFile.  The picture data itself is then encountered.  This is either then
//		discarded, or stored in a file.  If we have been asked to, we will then pass the
//		name of this file to the pict converter to be converted.  We then discard the final }.
//	Bugs
//		We deal with binary data inadequately.  We should be getting the parameter to
//		\bin, and reading exactly that number of bytes in.
//	History:
//		92.12.24	djb	created initial version
//		93.01.03	djb	enhanced so it worked completely.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ConvertMacPictImageIn: sourceFile To: destinationFile
{
	Instance		thetoken;
	Instance		tokenFile		= NullInstance; // initialized to shut the compiler up
	Instance		pictFile;
	TokenType	thetokentype;
	Integer		groupDepth		= 0;
	CString		theName;
	Boolean		BinaryData		= NO;
	Integer		ImageWidth		= 0;
	Integer		ImageHeight		= 0;
	CString		newFilename;		// extensionless name of file inside the .rtfd wrapper
	CString		baseFilename;
	CString		charLocation;
	CString		theDirectory;
	CString		epsFilename;
	CString		tempFilename;
	CString		justFilename;
	CString		tempName;
	Boolean		weHaveError			= NO;
	ByteString		buffer;
	PositiveInteger	nameLength;
	PositiveInteger	tempLength;
	PositiveInteger	index;
	PositiveInteger	destIndex;
	port_t			thePort;
	Instance			ourSpeaker = [NXApp appSpeaker];
	int				storedTimeout;
	//
	//	Increment the count, to show which picture this is (1st, 2nd, etc)
	//
	pictNum ++;
	//
	//	Compute base name for the file(s) we create here.Get the path to
	//	the rtfd folder:	/x/y/...z/foo.rtfd
	//	extract			'foo'				(the tempFilename) 
	//	build			/x/y/...z/foo.rtfd/foo  (the baseFilename)
	//	build			foo(picture#0000)	(justFilename)
	//
	theDirectory = [destinationFile   GetDirectory];
	baseFilename = NewCString(strlen(theDirectory)+1);
	strcpy(baseFilename, theDirectory);
	strcat(baseFilename, "/");

	tempFilename = NewCString(strlen(theDirectory));
	charLocation = strrchr(theDirectory, '/');
	if (charLocation == NullCString)
		charLocation = theDirectory; // No /.  Weird.  Use whole dir name.
	else
		charLocation++;	// skip over the / that we just found
	strcpy(tempFilename, charLocation);
	charLocation = strchr(tempFilename, '.'); // Remove everything after first period
	if (charLocation != NullCString)
		charLocation[0] = EndOfCString;	// amputate the string at the period

	//
	//	93.02.20	djb	Added this loop to remove any spaces, {'s, }'s, /'s, \'s or .'s
	//				found in the filename. Removing the \ and }'s is pretty necessary.
	//				Removing the others is for the sake of sanity.  We do this by walking
	//				through tempFilename one char at a time.  If the char is an acceptable
	//				one, we move it to the next spot down in the filename string
	//				(in the worst case, this will move it onto itself).
	//
	tempLength = strlen(tempFilename);
	destIndex = 0;
	for (index = 0; index < tempLength; index++)
	{
		//
		//	If the current source character was not one of the ones we are looking for,
		//	(the default case), we copy this next character to the next available
		//	destination position in the same string and then increment the destination
		//	position.
		//
		switch (tempFilename[index])
		{
			case '\\' :
			case '}' :
			case '{' :
			case '.' :
			case ' ':
			case '/' :
			case '\'' :
			case '\"' :
			case '`':
				// Do nothing
				break;
			default :				
				tempFilename[destIndex] = tempFilename[index];
				destIndex++;
				break;
		}
	}
	tempFilename[destIndex] = EndOfCString;


	justFilename = NewCString(strlen(tempFilename)+15);
	sprintf(justFilename, "%s(picture#%.4ld)", tempFilename, pictNum); // only 999 images...
	FreeCString(tempFilename);
	FreeCString(theDirectory);
	//
	//	Now, build an extensionless filename of the form
	//		/x/y/...z/foo.rtfd/foo(picture#0000)
	//
	newFilename = NewCString(strlen(baseFilename)+strlen(justFilename));
	sprintf(newFilename, "%s%s", baseFilename, justFilename); 
	FreeCString(baseFilename);
	//
	//	Create the temporary file.  Note that we totally ignore any error codes. 
	//
	if ((KeepTokensForMe == YES) && (ExtractPicts == YES))
	{
		tempFilename = NewCString(strlen(newFilename)+7);
		strcpy(tempFilename, newFilename);
		strcat(tempFilename, ".tokens");
		tokenFile = [[rtfFile  alloc] initAndUse: tempFilename];
		[tokenFile   CreateAndOpenFor: FILE_WRITE];
		FreeCString(tempFilename);
	}
	//
	//	Discard the pending {\pict\macpict
	//	And, tell the source file that the next word token it finds may be big
	//		(and that it can do a hex convert)
	//
	(void) [destinationFile   ClearThroughLastBegin];
	[sourceFile   Hint: WordsArePictures];
	//
	//	Read in any and all control tokens before the bitmap.  For some of them, we'll
	//	want to record particular information for later use.  Most we just ignore.
	//	(if we have the KeepTokensForMe set to YS, we also store these in our temp file)
	//
	thetoken = [sourceFile   GetToken];
	thetokentype = [thetoken   GetType];
	while (thetokentype != tokenWord)
	{
		switch (thetokentype)
		{
			case tokenBeginGroup:
				groupDepth++;
				break;
			case tokenEndGroup:
				groupDepth--;
				break;
			case tokenControlWord:
				theName = [thetoken   GetName];
				if (strcmp(theName, "picw") == 0)
					ImageWidth = [thetoken  GetValue];
				else if (strcmp(theName, "pich") == 0)
					ImageHeight = [thetoken  GetValue];
				else if (strcmp(theName, "bin") == 0)
					BinaryData = YES;
				FreeCString(theName);
				break;	
			case tokenControlSymbol:
			case tokenNoToken:
			case tokenWord:
				//
				//	Should be setting some kinda error
				//
				break;
		}
		if ((KeepTokensForMe == YES) && (ExtractPicts == YES))
			[tokenFile  WriteToken: thetoken];
		else
			[thetoken   free];
		thetoken = [sourceFile   GetToken];
		thetokentype = [thetoken   GetType];
	}
	//
	//	Close the notes file. 
	//
	if ((KeepTokensForMe == YES) && (ExtractPicts == YES))
		[tokenFile CloseAndSave];
	//
	//	Now, write out the proper info into the destination file, if we are converting.
	//
	if (ConvertPictures == YES)
	{
		//
		//	93.01.31	djb	We used to be writing the NeXTGraphic out as a series of
		//				rtf tokens.  This was a bit precarious, and when it broke again
		//				this time because the line had necessarily grown too long for the
		//				rtf file, I dediced the rtffile needed a specialized method for writing
		//				this construct out.  So, all the convoluted stuff that was here has
		//				been replaced by this one method call.
		//
		tempName = NewCString(strlen(justFilename)+4);
		sprintf(tempName, "%s.eps", justFilename);
		[destinationFile   WriteNeXTGraphicAt: 0
			WithName: tempName
			Width: ImageWidth
			Height: ImageHeight];
		FreeCString(tempName);
	}
	//
	//	We have just found a word token, (just before the if statement immediately above,
	//	that is).  This should be the picture token.  If it isn't, then we're probably
	//	in deep trouble.  Sigh.  Why doesn the spec say so little about what MUST be
	//	where?  If we are extracting or converting pictures, we must store this ascii
	//	data as a binary pict file.
	//
	if  ((ConvertPictures == YES) || (ExtractPicts == YES))
	{
		//
		//	If we are not extracting the pictures (storing them as part of the
		//	.rtfd wrapper), then we will store them as temporary files to be cleaned
		//	up by the OS later.  Otherwise, we create the .pict file inside the wrapper.
		//
		if (ExtractPicts == NO)
			pictFile = [[File  alloc] initAndUseTemporary];
		else
		{
			tempFilename = NewCString(strlen(newFilename)+5);
			strcpy(tempFilename, newFilename);
			strcat(tempFilename, ".pict");
			pictFile = [[File  alloc] initAndUse: tempFilename];
			FreeCString(tempFilename);
		}
		//
		//	Open the file, and write a vanity pict header.
		//
		[pictFile   CreateAndOpenFor: FILE_WRITE];
		buffer = NewByteString(512);
		for (index = 0; index < 512; index+=16)
			strcpy((CString)&buffer[index], "Convert_RTF V1.1");
		[pictFile   Write: 512 BytesFrom: buffer];
		FreeByteString(buffer);
		//
		//	Now, write out the picture data.
		//		If the picture data is binary, then we've got trouble because we read it
		//		in wrong because we ignored the argument to \bin above.  
		//
		if (BinaryData == YES)
		{
			// uh oh....
		}
		else
		{
			//
			//	Get a copy of the hex version of the picture data.  
			//
			theName = [thetoken   GetName];
			nameLength = strlen(theName);
			buffer = [self  Convert: nameLength BytesToBinFrom: (ByteString) theName];
			[pictFile   Write: nameLength / 2 BytesFrom: buffer];
			FreeByteString(buffer);
			FreeCString(theName);
		}
		[pictFile   CloseAndSave];
		//
		//	Finally, invoke the convert pict app, and ask it to convert the stuff.
		//
		if (ConvertPictures == YES)
		{
			thePort = NXPortFromName("Convert_PICT", NULL);
			if (thePort != PORT_NULL)
			{
				PictConverterIsOpen = YES;
				//
				//	Set up the filenames, and call the IAC method....
				//
				epsFilename = NewCString(strlen(newFilename)+5);
				strcpy(epsFilename, newFilename);
				strcat(epsFilename, ".eps");
				tempFilename = [pictFile   GetPathname];
				ourSpeaker = [NXApp appSpeaker];
				storedTimeout = [ourSpeaker  sendTimeout];
				[ourSpeaker   setSendTimeout: 120000]; //60,000ms.  2 mintues.
				[ourSpeaker   setSendPort: thePort ];
				[ourSpeaker   msgConvert: tempFilename To: epsFilename];
				[ourSpeaker   setSendTimeout: storedTimeout]; //restore it.
				FreeCString(tempFilename);
				FreeCString(epsFilename);
			}
		}
	}
	//
	//	Discard the picture data.
	//
	[thetoken   free];
	//
	//	Finally, read in the last token, which had better be the } token.
	//
	thetoken = [sourceFile   GetToken];
	if ( ([thetoken   GetType] != tokenEndGroup)  || (groupDepth != 0) )
		weHaveError = YES;
	[thetoken   free];
	//
	//	Undo the setting stored above, and free strings.
	//
	[destinationFile   Hint: WordsAreNotPictures];
	FreeCString(newFilename);
	FreeCString(justFilename);
	return self;
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		Convert:BytesToBinFrom: 
//	Parameters:	A source buffer and it's length
//	Returns:		A pointer to a buffer of the converted data
//	Stores:		none
//	Description:
//		This takes a buffer of the form "0A0053FD..." (that is, a hex representation of
//		binary data), and returns a buffer with 0x0A0053FD...  in it (binary rep).
//		It does this with an algorithm that probably is not optimal, that converts the
//		individual nibbles (each represented by a char of ascii) into a binary form.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (ByteString) Convert: (Integer) sourceSize BytesToBinFrom: (ByteString) source
{
	Integer			nibblecnt;
	Byte			result;
	Character		thenibble;
	ByteString		buffer;
	PositiveInteger	bufferSize;
	PositiveInteger	bufferIndex	= 0;
	PositiveInteger	index;

	bufferSize = sourceSize / 2;	// The source had BETTER be an even bytes length. 
	buffer = NewByteString(bufferSize);
	for (index = 0; index < sourceSize; index+=2)
	{
		thenibble = source[index];
		result = 0;
		//
		//	GROSS algorithm.  Loop through switch twice.  First time using nibble1,
		//	second time using nibble2.  This allows us to use this switch for each nibble.
		//
		for (nibblecnt = 0; nibblecnt < 2; nibblecnt++)
		{
			result = result << 4;   // 1st, shift nothing, 2nd time shift 1st nible to high nibble. 
			switch (thenibble)
			{
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					// subtracting '0' (0x30) from a character gives binary #
					result += (Byte) (thenibble - '0');  				break;
				case 'A':
				case 'B':
				case 'C':
				case 'D':
				case 'E':
				case 'F':
					result += (Byte)  ((thenibble - 'A')  + 10); 
					break;
				case 'a':
				case 'b':
				case 'c':
				case 'd':
				case 'e':
				case 'f':
					result += (Byte)  ((thenibble - 'a')  + 10); 
					break;
				default:
					result += 0;	// This is really an error condition.
					break;
			}
			thenibble =  source[index+1];
		}
		buffer[bufferIndex] = result;
		bufferIndex++;
	}
    return buffer;
}


//
//	93.04.04	djb	These methods were added today to fix a final bug in the
//	converter.  Ideally, they should actually be implemented by another object.
//	However, it's not that important, and it will be easier to put them here
//	I just want to get this done.  If I ever fix this converter so it works as
//	it should (a real parser, for instance) then these should move into their
//	own 'stack object'.  Anyway.  These routines will manipulate a stack of
//	flags indicating whether text should be converted or not.  See above for
//	more details.
//


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ResetStack
//	Parameters:	none
//	Returns:		self
//	Stores:		none
//	Description:
//		This will set the current stack location back to 0.  It also sets the first value on
//		the stack to use the current default conversion value the user has specified using
//		the preferences. It could legitimately free
//		the current stack and remalloc a small one, in the case that the last time the
//		stack got much too large.  But, this is unlikely, so this won't.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ResetStack
{
	StackLocation = 0;

	if (ConvertAllText == ConvertAll)
		CharConvertStack[StackLocation] = FullConversion;
	else
		CharConvertStack[StackLocation] = NoConversion;

	return self;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		StartNewGroup
//	Parameters:	none
//	Returns:		self
//	Stores:		none
//	Description:
//		This causes a new conversion value to be pushed on the stack.  The value is
//		duplicated from what was previously on the top of the stack.  Thus, if ConvertAll
//		was on the top of the stack, this pushes a new value of ConvertAll on the stack.
//		This is because a new group always will start out with the same font that was
//		used in the surrounding group.  This value can be changed later.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- StartNewGroup
{
	ConvertTypes	*tempStack;

	StackLocation++;
	if (StackLocation > StackTop)
	{
		tempStack = malloc((1+StackTop)*sizeof(ConvertTypes) * 2);
		memcpy(tempStack, CharConvertStack, (1+StackTop)*sizeof(ConvertTypes));
		StackTop  = ((StackTop+1)*2) -1;
		free(CharConvertStack);
		CharConvertStack = tempStack;
	}
	CharConvertStack[StackLocation] = CharConvertStack[StackLocation-1];

	return self;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		FinishGroup
//	Parameters:	none
//	Returns:		self
//	Stores:		none
//	Description:
//		When we finish a group, we simply pop the top element off the stack so we revert
//		back to the conversion value for the surrounding group
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- FinishGroup
{
	StackLocation--;
	if (StackLocation < 0)
		StackLocation = 0;

	return self;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		ChangeCurrentConverstionTo:
//	Parameters:	The value to be replace the value at the top of the stack.
//	Returns:		self
//	Stores:		none
//	Description:
//		In violation of a pristine stack paradigm, this will replace the value on the top
//		of the stack with the specified value.  This allows the top value to reflect whatever
//		the conversion is for the current group properly.  This could actually be modeled
//		as a pop and a push.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- SetCurrentConverstionTo: (ConvertTypes) newValue
{
	CharConvertStack[StackLocation] = newValue;
	return self;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//	Routine:		GetCurrentConverstion:
//	Parameters:	none
//	Returns:		the ConvertTypes that is on the top of the stack
//	Stores:		none
//	Description:
//		In violation of a pristine stack paradigm, this will replace the value on the top
//		of the stack with the specified value.  This allows the top value to reflect whatever
//		the conversion is for the current group properly.  This could actually be modeled
//		as a pop and a push.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (ConvertTypes) GetCurrentConversion
{
	return CharConvertStack[StackLocation];
}


@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.