/* Import-Export module. Copyright 1995-96 by DataPak Software, Inc.  This software is a
part of the total PAIGE library.

This source file contains all the member functions for the RTF import class (derived
from the PaigeImportFilter class. */


#include "Paige.h"
#include "pgTraps.h"
#include "pgUtils.h"
#include "pgTxrCPP.h"
#include "defprocs.h"
#include "pgErrors.h"
#include "pgDefStl.h"
#include "pgRTFDef.h"
#include "pgEmbed.h"
#include "pgdeftbl.h"

static short rtf_command (pg_char_ptr table, pg_char_ptr argument, pg_short_t arg_size,
		short PG_FAR *param);
static pg_boolean regular_letter (pg_char the_char);
static pg_boolean is_hex_char (pg_char the_char);
static long string_to_num (pg_char_ptr string, short string_size);
static pg_short_t translate_hex (pg_char hex_char);
static font_info_ptr locate_font_in_table (memory_ref table, short family_id);
static style_table_ptr locate_stylesheet (memory_ref table, short style_id);
static short convert_twips (paige_rec_ptr pg, short twipsvalue);


/* PaigeImportFilter constructor. This sets up the various members common to all filters.
No error checking or file verification occurs just yet. */


PaigeRTFImportFilter::PaigeRTFImportFilter ()
{
	file_type = pg_rtf_type;	// Default type for this class
	feature_bits = IMPORT_TEXT_FEATURE | IMPORT_TEXT_FORMATS_FEATURE
					| IMPORT_PAR_FORMATS_FEATURE | IMPORT_PAGE_INFO_FEATURE
					| IMPORT_EMBEDDED_OBJECTS_FEATURE
					| IMPORT_SERIAL_SETUP;
}


/* PaigeImportFilter destructor. This removes common items. */

PaigeRTFImportFilter::~PaigeRTFImportFilter ()
{
 
}


/* pgPrepareImport sets up the necessary extra buffers to begin importing. */

pg_error PaigeRTFImportFilter::pgPrepareImport (void)
{
	rectangle		print_rect;
	long			topbottom_margin, leftright_margin;
	
	if (import_pg_rec->version < MINIMUM_RTF_VERSION)
		return		BAD_PAIGE_VERSION_ERR;

	style_stack = ::MemoryAlloc(paige_globals->mem_globals, sizeof(format_stack), 0, 2);
	font_table = ::MemoryAlloc(paige_globals->mem_globals, sizeof(font_info), 0, 4);
	color_table = ::MemoryAlloc(paige_globals->mem_globals, sizeof(color_value), 0, 4);

	current_style = translator.format;
	current_font = translator.font;
	current_par = translator.par_format;

// Establish the default font:

#ifdef MAC_PLATFORM
	default_font = current_font.family_id;
#else
	default_font = current_style.font_index;
#endif

	def_font_set = default_font;

	picture_ref = MEM_NULL;
	current_stylesheet = NULL;
	
	pgGetDocInfo(import_pg, &translator.doc_info);
	pgAreaBounds(import_pg, &print_rect, NULL);
	print_rect.top_left.h -= translator.doc_info.margins.top_left.h;
	print_rect.top_left.v -= translator.doc_info.margins.top_left.v;
	print_rect.bot_right.h += translator.doc_info.margins.bot_right.h;
	print_rect.bot_right.v += translator.doc_info.margins.bot_right.v;
	pgOffsetRect(&print_rect, -print_rect.top_left.h, -print_rect.top_left.v);
	translator.doc_info.print_target = print_rect;

	topbottom_margin = pgConvertResolution(import_pg_rec, MSWORD_TOPBOTTOM_MARGIN);
	leftright_margin = pgConvertResolution(import_pg_rec, MSWORD_LEFTRIGHT_MARGIN);

	translator.doc_info_changed
			= (pg_boolean)(translator.doc_info.margins.top_left.v != topbottom_margin
			|| translator.doc_info.margins.bot_right.v != topbottom_margin
			|| translator.doc_info.margins.top_left.h != leftright_margin
			|| translator.doc_info.margins.bot_right.h != leftright_margin);
	
	translator.doc_info.margins.top_left.v = topbottom_margin;
	translator.doc_info.margins.bot_right.v = topbottom_margin;
	translator.doc_info.margins.top_left.h = leftright_margin;
	translator.doc_info.margins.bot_right.h = leftright_margin;
	
	return		NO_ERROR;
}


/* pgImportDone disposes all structs created for pgPrepareImport(). */

pg_error PaigeRTFImportFilter::pgImportDone (void)
{
	::DisposeNonNilMemory(style_stack);
	::DisposeNonNilMemory(font_table);
	::DisposeNonNilMemory(color_table);

	return		NO_ERROR;
}


/* pgVerifySignature returns NO_ERROR if this is an RTF file. */

pg_error PaigeRTFImportFilter::pgVerifySignature ()
{
	return	::pgVerifyRTF((pg_file_unit)filemap, io_proc, file_begin);
}


/* pgReadNextBlock is the major entry point that reads the next block of text, sets up paragraph
and text formats and returns something for the base class to insert into the pg_ref. */

pg_boolean PaigeRTFImportFilter::pgReadNextBlock (void)
{
	pg_char				next_char;
	pg_boolean			transfer_text = FALSE;
	pg_error			result = NO_ERROR;

	translator.bytes_transferred = 0;
	translator.flags = 0;

	if (current_style.embed_object) {	
	
// Zero-out embed_ref stuff from possible previous embedded insertion.

		current_style.procs = paige_globals->def_style.procs;
		current_style.embed_entry = current_style.embed_style_refcon
				= current_style.embed_refcon = current_style.embed_id = 0;
		current_style.embed_object = MEM_NULL;
		current_style.char_bytes = 0;
		current_style.class_bits = 0;
	}
	
	translator.format_changed = !pgEqualStruct(&translator.format, &current_style, SIGNIFICANT_STYLE_SIZE);
	translator.font_changed = !pgEqualStruct(&translator.font, &current_font, SIGNIFICANT_FONT_SIZE);

	translator.format = current_style;
	translator.font = current_font;

	if (picture_ref)
		if (ReadRTFPicture())
			return		TRUE;

	while ( (result = pgGetImportByte(&next_char)) == NO_ERROR ) {
		
		if (next_char == RTF_COMMAND_CHAR) {
			short			parameter, command, table_id;
			long			command_result;
			
			if ((command_result = ReadCommand(&parameter, &next_char)) == 0) {
				
				if (parameter)
					if (OutputCharacter((pg_char)parameter))
						break;
			}
			else
			if (command_result != DO_NOTHING_COMMAND) {
				
				table_id = pgHiWord(command_result);
				command = pgLoWord(command_result);
				
				switch (table_id) {

					case SPECIAL_CHAR_COMMAND:
						transfer_text = DoSpecialCharCommand(command, parameter);
						break;
						
					case GROUP_AVOID_COMMAND:
						SkipCurrentLevel();
						break;

					case DESTINATION_COMMAND:
						transfer_text = DoDestinationCommand(command, parameter);
						break;

					case DOCUMENT_COMMAND:
						transfer_text = DoDocumentCommand(command, parameter);
						break;

					case PARAGRAPH_COMMAND:
						transfer_text = DoParagraphCommand(command, parameter);
						break;

					case STYLE_COMMAND:
						transfer_text = DoStyleCommand(command, parameter);
						break;
					
					case INFO_COMMAND:
						ProcessInfoCommand(command, parameter);
						break;

				}
			}
		}
		else
		if (next_char == RTF_GROUPBEGIN_CHAR) {
		
				PushStyleStack();
				
				if (current_stylesheet)
					++stylesheet_level;
		}
		else
		if (next_char == RTF_GROUPEND_CHAR) {

			PopStyleStack();
			
			if (current_stylesheet) {
				
				if ((--stylesheet_level) == 0) {
					long			stylesheet_qty;
					
					stylesheet_qty = ::GetMemorySize(translator.stylesheet_table);
					
					if (!stylesheet.name[0] && stylesheet_qty)
						stylesheet_qty -= 1;

					::UnuseMemory(translator.stylesheet_table);
					::SetMemorySize(translator.stylesheet_table, stylesheet_qty);

					current_stylesheet = NULL;
				}
			}
			else
			if (!translator.bytes_transferred) {

				translator.format_changed |= !pgEqualStruct(&translator.format, &current_style, SIGNIFICANT_STYLE_SIZE);
				translator.font_changed |= !pgEqualStruct(&translator.font, &current_font, SIGNIFICANT_FONT_SIZE);

				translator.format = current_style;
				translator.font = current_font;

			}
			else
			if (translator.font_changed || translator.format_changed)
				break;
		}
		else {
			// Default is regular text character.
			
			if (current_stylesheet) {
			
				if (next_char == TABLE_TERMINATOR)
					ProcessStylesheet();
				else
				if ((next_char >= ' ') && (stylesheet_index < (FONT_SIZE - 1))) {
					
					stylesheet.name[stylesheet_index] = next_char;
					++stylesheet_index;
				}
			}
			else
			if (next_char >= ' ')
				if (OutputCharacter(next_char))
					break;
		}
		
		if (transfer_text)
			break;
	}
	
	translator.par_format_changed |= !pgEqualStruct(&translator.par_format, &current_par, SIGNIFICANT_PAR_STYLE_SIZE);

	if (translator.par_format_changed)
		translator.par_format = current_par;
	
	bytes_read = (filepos - file_begin) - buffer_size + buffer_index;
	io_result = result;

	if (result == NO_ERROR || translator.bytes_transferred)
		return	TRUE;
	
	return	FALSE;
}


/* ReadCommand gets called after an RTF_COMMAND_CHAR byte has been read. The next byte to read
is known to be the first byte following RTF_COMMAND_CHAR. If this function returns -1, end-of-file
has been reached.  If this function returns zero, the command is not recognized in any table. Note,
however, that an unrecognized command could be a special character, in which case *param will
contain that char, otherwise *param will be set to zero.
If the command is recognized, the function result's highword is the table number and its lowword
is the enumeration for that table. The *terminator char will be the character the command terminated
with;  the next byte position in the file will be the first byte following the terminator UNLESS
the terminator is another RTF_COMMAND_CHAR, RTF_GROUPBEGIN or RTF_GROUPEND char. */

 
long PaigeRTFImportFilter::ReadCommand (short PG_FAR *param, pg_char_ptr terminator)
{
	pg_char			command_buffer[MAX_COMMAND_BYTES]; // RTF token keyword just read
	pg_char_ptr		command_buffer_ptr;
	pg_char			next_byte;
	pg_boolean		negative_param;
	long			result;
	short			byte_ctr, param_ctr, parameter;

	*terminator = 0;
	*param = 0;
	negative_param = FALSE;
	command_buffer_ptr = command_buffer;
	
	for (byte_ctr = 0; byte_ctr < (MAX_COMMAND_BYTES - 1); ++byte_ctr, ++command_buffer_ptr) {

		if (pgGetImportByte(command_buffer_ptr) != NO_ERROR)
			break;
		
	// Check for special characters:
	
		if (byte_ctr == 0) {
			
			if (*command_buffer_ptr == RTF_HEX_CHAR) {
				
				pgGetImportByte(&next_byte);
				*param = (pg_short_t)translate_hex(next_byte);
				*param <<= 4;
				pgGetImportByte(&next_byte);
				*param |= (pg_short_t)translate_hex(next_byte);
				
				return	0;
			}
			else
			if (*command_buffer_ptr == RTF_STAR_CHAR)
				return	((long)GROUP_AVOID_COMMAND << 16);
			else
			if (*command_buffer_ptr == RTF_COMMAND_CHAR
				|| *command_buffer_ptr == RTF_GROUPBEGIN_CHAR
				|| *command_buffer_ptr == RTF_GROUPEND_CHAR
				|| *command_buffer_ptr == RTF_COLON) {
				
				*param = *command_buffer_ptr;
				return	0;
			}
			else
			if (!regular_letter(*command_buffer_ptr))
				return	DO_NOTHING_COMMAND;
		}

		if ((*command_buffer_ptr < '0') && (*command_buffer_ptr != NEGATIVE_SIGN_CHAR))
			break;
	
	// Examine the NEXT byte to see if it is a special command character:
		
		if (buffer_index < buffer_size) {
		
			next_byte = io_buffer[buffer_index];
			
			if (next_byte == RTF_COMMAND_CHAR || next_byte == RTF_GROUPBEGIN_CHAR
					|| next_byte == RTF_GROUPEND_CHAR || next_byte == TABLE_TERMINATOR) {
				
				++command_buffer_ptr;
				*command_buffer_ptr = ' ';
				++byte_ctr;
				break;
			}
		}
	}

	*terminator = *command_buffer_ptr;

	if (!byte_ctr)
		return	0;

	if (result = rtf_command(special_char_commands, command_buffer, byte_ctr, param))
		return		(result | ((long)SPECIAL_CHAR_COMMAND << 16));

	if (result = rtf_command(group_avoid_commands, command_buffer, byte_ctr, param)) {

		UnsupportedCommand(command_buffer, *param);
		return		(result | ((long)GROUP_AVOID_COMMAND << 16));
	}

	if (result = rtf_command(destination_commands, command_buffer, byte_ctr, param))
		return		(result | ((long)DESTINATION_COMMAND << 16));

	if (result = rtf_command(document_commands, command_buffer, byte_ctr, param))
		return		(result | ((long)DOCUMENT_COMMAND << 16));

	if (result = rtf_command(paragraph_commands, command_buffer, byte_ctr, param))
		return		(result | ((long)PARAGRAPH_COMMAND << 16));

	if (result = rtf_command(style_commands, command_buffer, byte_ctr, param))
		return		(result | ((long)STYLE_COMMAND << 16));

	if (result = rtf_command(info_commands, command_buffer, byte_ctr, param))
		return		(result | ((long)INFO_COMMAND << 16));
	
	parameter = 0;

	for (param_ctr = 0; param_ctr < byte_ctr; ++param_ctr) {
		
		if (command_buffer[param_ctr] >= '0' && command_buffer[param_ctr] <= '9') {
			
			parameter = (short)string_to_num(&command_buffer[param_ctr], byte_ctr - param_ctr);
			break;
		}
	}
	
	command_buffer[param_ctr] = 0;

	UnsupportedCommand(command_buffer, parameter);

	*param = 0;
	return		0;
}


/* DoSpecialCharCommand handles special-character commands. If TRUE is returned, the
current block of text should be transferred. */

pg_boolean PaigeRTFImportFilter::DoSpecialCharCommand (short command, short parameter)
{
	pg_boolean			result = FALSE;
	pg_boolean			cross_platform;

	cross_platform = (pg_boolean)(file_os != CURRENT_OS);

	switch (command) {
	
		case bullet_command:
			if (cross_platform)
				result = OutputCharacter((pg_char)BULLET_CROSS_CHAR);
			else
	#ifdef GLOBALS_ARE_PSTRINGS
				result = OutputString(&paige_globals->bullet_char[1]);
	#else
				result = OutputString(paige_globals->bullet_char);
	#endif
			break;
		
		case left_doublequote_command:
			if (cross_platform)
				result = OutputCharacter((pg_char)LEFTDBLQUOTE_CROSS_CHAR);
			else
	#ifdef GLOBALS_ARE_PSTRINGS
				result = OutputString(&paige_globals->left_double_quote[1]);
	#else
				result = OutputString(paige_globals->left_double_quote);
	#endif
			break;

		case line_feed_command:
			break;
		
		case left_singlequote_command:
			if (cross_platform)
				result = OutputCharacter((pg_char)LEFTQUOTE_CROSS_CHAR);
			else
				result = OutputString(paige_globals->left_single_quote);
			break;
			
		case page_break_command:
			result = OutputCharacter((pg_char)paige_globals->ff_char);
			break;

		case cr_command:
			OutputCharacter((pg_char)paige_globals->line_wrap_char);
			result = TRUE;
			break;

		case right_doublequote_command:
			if (cross_platform)
				result = OutputCharacter((pg_char)RIGHTDBLQUOTE_CROSS_CHAR);
			else
	#ifdef GLOBALS_ARE_PSTRINGS
				result = OutputString(&paige_globals->right_double_quote[1]);
	#else
				result = OutputString(paige_globals->right_double_quote);
	#endif
			break;
			
		case right_singlequote_command:
			if (cross_platform)
				result = OutputCharacter((pg_char)RIGHTQUOTE_CROSS_CHAR);
			else
	#ifdef GLOBALS_ARE_PSTRINGS
				result = OutputString(&paige_globals->right_single_quote[1]);
	#else
				result = OutputString(paige_globals->right_single_quote);
	#endif
			break;

		case end_section_command:
			break;
			
		case tab_char_command:
			result = OutputCharacter((pg_char)paige_globals->tab_char);
			break;
	}

	return		result;
}


/* DoDestinationCommand handles all the commands in the "destination type" table. */

pg_boolean PaigeRTFImportFilter::DoDestinationCommand (short command, short parameter)
{
	font_info			font;
	color_value			color;
	long				command_result;
	pg_char_ptr			font_name;
	pg_char				next_char;
	pg_boolean			result = FALSE;
	short				param, bracket_level, table_id, command_id;
	short				fontname_level;

	switch (command) {
	
		case non_mac_os_command:
			file_os = WINDOWS_OS;
			break;
		
		case binary_data_command:
			embed_binary = (long)parameter;
			break;
		
		case def_font_command:
			default_font = parameter;
			break;

		case color_table_command:
		case font_table_command:
			bracket_level = 1;
			fontname_level = 0;

			pgFillBlock(&font, sizeof(font_info), 0);
			font_name = &font.name[1];
			pgFillBlock(&color, sizeof(color_value), 0);

			while (pgGetImportByte(&next_char) == NO_ERROR) {
				
				if (next_char == RTF_COMMAND_CHAR) {
					
					if ((command_result = ReadCommand(&param, &next_char)) != 0) {

						table_id = pgHiWord(command_result);
						
						if (table_id == STYLE_COMMAND) {
						
							command_id = pgLoWord(command_result);
							
							if (command == font_table_command) {

								if (command_id == font_command)
									font.family_id = (short)param;
								else
								if (command_id == charset_command || command_id == font_charset_command)
									font.machine_var[PG_CHARSET] = param;
							}
							else
							if (command == color_table_command) {
								pg_short_t			colorfield;
								
								colorfield = (pg_short_t)param;
								colorfield <<= 8;

								switch (command_id) {
									
									case redcolor_command:
										color.red = colorfield;
										break;
										
									case greencolor_command:
										color.green = colorfield;
										break;

									case bluecolor_command:
										color.blue = colorfield;
										break;
								}
							}
						}
					}
				}
				else
				if (next_char == RTF_GROUPBEGIN_CHAR)
					++bracket_level;
				else
				if (next_char == RTF_GROUPEND_CHAR) {
					
					--bracket_level;
					
					if (bracket_level == 0)
						break;
				}
				else
				if (next_char == TABLE_TERMINATOR) {
					font_info_ptr		font_to_add;
					color_value_ptr		color_to_add;

					if (command == font_table_command) {

						font_to_add = (font_info_ptr)AppendMemory(font_table, 1, FALSE);
						pgBlockMove(&font, font_to_add, sizeof(font_info));
						UnuseMemory(font_table);
						pgFillBlock(&font, sizeof(font_info), 0);
						font_name = &font.name[1];
					}
					else
					if (command == color_table_command) {
						
						color_to_add = (color_value_ptr)AppendMemory(color_table, 1, FALSE);
						pgBlockMove(&color, color_to_add, sizeof(color_value));
						UnuseMemory(color_table);
						pgFillBlock(&color, sizeof(color_value), 0);
					}
				}
				else
				if (command == font_table_command) {
					
					if (fontname_level == 0)
						fontname_level = bracket_level;

					if (fontname_level == bracket_level && (font_name[0] < (FONT_SIZE - 2))) {
	
						*font_name++ = next_char;
						++font.name[0];
					}
				}
			}

			if (command == font_table_command && def_font_set != default_font) {
				font_info_ptr		first_font;

				DoStyleCommand(font_command, default_font);
				first_font = (font_info_ptr)UseMemory(import_pg_rec->fonts);
				pgBlockMove(&current_font, first_font, sizeof(font_info));
				this->pgMapFont(first_font, file_os, CURRENT_OS);
				import_pg_rec->procs.font_proc(import_pg_rec, first_font);

				UnuseMemory(import_pg_rec->fonts);
				def_font_set = default_font;
			}

			break;

		case stylesheet_command:
		// Set up the variables so we know stylesheet(s) are being processed:

			current_stylesheet = (style_table_ptr) ::AppendMemory(translator.stylesheet_table, 1, TRUE);
			stylesheet_level = 1;
			stylesheet_index = 0;
			pgFillBlock(&stylesheet, sizeof(style_table_entry), 0);
			stylesheet.par = paige_globals->def_par;

			break;

		case mac_platform_command:
			file_os = MACINTOSH_OS;
			break;

		case mac_pict_command:
			embed_type = embed_mac_pict;
			break;

		case object_command:
			embed_type = embed_user_data;
			// no break
		case pict_command:
			result = PrepareRTFPicture();
			break;

		case picframe_height_command:
			if (embed_type == embed_meta_file) {
				
				// This is the desired height, in twips
				
				twips_gheight = parameter;
				embed_height = convert_twips(import_pg_rec, parameter);
				break;
			}
			// no break;

		case pict_height_command:
		case object_height_command:
			if (embed_type == embed_meta_file || embed_type == embed_ole) {
				
				metafile.y_ext = convert_twips(import_pg_rec, parameter);
				
				if (!embed_height)
					embed_height = metafile.y_ext;

				twips_height = parameter;
			}
			else
				embed_height = (short)parameter;

			break;

		case picframe_width_command:
			if (embed_type == embed_meta_file) {
				
				twips_gwidth = parameter;
				embed_width = convert_twips(import_pg_rec, parameter);
				break;
			}
			// no break;

		case pict_width_command:
		case object_width_command:
			if (embed_type == embed_meta_file || embed_type == embed_ole) {
				
				metafile.x_ext = convert_twips(import_pg_rec, parameter);
				twips_width = parameter;
				
				if (!embed_height)
					embed_height = metafile.x_ext;
			}
			else
				embed_width = (short)parameter;
			break;

		case rtf_signature_command:
			break;
			
		case windows_metafile_command:
			embed_type = embed_meta_file;
			metafile.mapping_mode = parameter;

			break;
	}
	
	return		result;
}


/* DoDocumentCommand handles commands that set document sizes, etc. */

pg_boolean PaigeRTFImportFilter::DoDocumentCommand (short command, short parameter)
{
	long			PG_FAR 	*value_ptr;
	long					real_parameter;
	
	real_parameter = (long)convert_twips(import_pg_rec, parameter);
	value_ptr = NULL;

	switch (command) {
	
		case bottom_margin_command:
			value_ptr = &translator.doc_info.margins.bot_right.v;
			break;

		case left_margin_command:
			value_ptr = &translator.doc_info.margins.top_left.h;
			break;
			
		case right_margin_command:
			value_ptr = &translator.doc_info.margins.bot_right.h;
			break;

		case top_margin_command:
			value_ptr = &translator.doc_info.margins.top_left.v;
			break;

		case paper_height_command:
			value_ptr = &translator.doc_info.print_target.bot_right.v;
			break;

		case paper_width_command:
			value_ptr = &translator.doc_info.print_target.bot_right.h;
			break;
	}
	
	if (value_ptr) {
		
		translator.doc_info_changed |= (pg_boolean)(*value_ptr != real_parameter);
		*value_ptr = real_parameter;
	}

	return	FALSE;
}


/* DoParagraphCommand handles paragraph formatting commands. */

pg_boolean PaigeRTFImportFilter::DoParagraphCommand (short command, short parameter)
{
	pg_boolean		result = FALSE;
	pg_short_t		tab_index;
	long			real_parameter;
	
	real_parameter = (long)convert_twips(import_pg_rec, parameter);

	tab_index = current_par.num_tabs;

	switch (command) {
	
		case first_indent_command:
			current_par.indents.first_indent = real_parameter;
			break;

		case keep_together_command:
			break;

		case left_indent_command:
			current_par.indents.left_indent = real_parameter;
			break;

		case page_break_before_command:
			break;
			
		case par_default_command:
			pgGetParInfoRec(import_pg, 0, &current_par);
			break;

		case center_justify_command:
			current_par.justification = justify_center;
			break;

		case full_justify_command:
			current_par.justification = justify_full;
			break;

		case left_justify_command:
			current_par.justification = justify_left;
			break;

		case right_justify_command:
			current_par.justification = justify_right;
			break;
			
		case right_indent_command:
			current_par.indents.right_indent = real_parameter;
			break;

		case space_after_command:
			current_par.bot_extra = pgAbsoluteValue(real_parameter);
			break;

		case space_before_command:
			current_par.top_extra = pgAbsoluteValue(real_parameter);
			break;

		case line_spacing_command:
			current_par.leading_fixed = pgAbsoluteValue(real_parameter);
			break;

		case dot_leader_command:
			if (tab_index < TAB_ARRAY_SIZE)
				current_par.tabs[tab_index].leader = (pg_short_t)'.';
			break;
			
		case dash_leader_command:
			if (tab_index < TAB_ARRAY_SIZE)
				current_par.tabs[tab_index].leader = (pg_short_t)'-';
			break;

		case center_tab_command:
			if (tab_index < TAB_ARRAY_SIZE)
				current_par.tabs[tab_index].tab_type = center_tab;
			break;

		case decimal_tab_command:
			if (tab_index < TAB_ARRAY_SIZE)
				current_par.tabs[tab_index].tab_type = decimal_tab;
			break;

		case right_tab_command:
			if (tab_index < TAB_ARRAY_SIZE)
				current_par.tabs[tab_index].tab_type = right_tab;
			break;

		case set_tab_command:
			if (tab_index < TAB_ARRAY_SIZE) {
					rectangle		wrap_bounds, vis_bounds;

				if (current_par.tabs[tab_index].tab_type == no_tab)
					current_par.tabs[tab_index].tab_type = left_tab;
				
				pgAreaBounds(import_pg, &wrap_bounds, &vis_bounds);
				real_parameter += vis_bounds.top_left.h;

				if (import_pg_rec->tab_base != TAB_BOUNDS_RELATIVE)
					real_parameter -= wrap_bounds.top_left.h;
				
				if (real_parameter < 0)
					real_parameter = 0;

				current_par.tabs[tab_index].position = real_parameter;
				current_par.num_tabs += 1;
			}

			break;
	}

	if (current_stylesheet)
		stylesheet.has_par_styles = TRUE;
	else
	if (command != par_default_command)
		if (current_par.style_sheet_id > 0)
			current_par.style_sheet_id = -current_par.style_sheet_id;

	return	FALSE;
}


/* DoStyleCommand handles text formatting commands. */

pg_boolean PaigeRTFImportFilter::DoStyleCommand (short command, short parameter)
{
	pg_boolean			result = FALSE;
	style_table_ptr		stylesheet_ptr;
	font_info_ptr		font_ptr;
	long				kerning_register;

	switch (command) {
	
		case bold_command:
			current_style.styles[bold_var] = -1;
			break;

		case all_caps_command:
			current_style.styles[all_caps_var] = -1;
			break;
		
		case fgcolor_command:
			if (GetMemorySize(color_table) > (long)parameter)
				GetMemoryRecord(color_table, parameter, &current_style.fg_color);
			break;

		case subscript_command:
			current_style.styles[subscript_var] = (short)(parameter / 2);
			break;
		
		case expand_condense_command:
			kerning_register = parameter;
			
			if (kerning_register >= 57 && kerning_register <= 63)
				kerning_register -= 64;
			
			current_style.char_extra = kerning_register << 14;
			current_style.space_extra = kerning_register << 14;
			break;

		case charset_command:
		case font_charset_command:
			current_font.machine_var[PG_CHARSET] = parameter;
			
			if (!translator.bytes_transferred)
				translator.font.machine_var[PG_CHARSET] = parameter;

			break;

		case font_command:
			if (font_ptr = locate_font_in_table(font_table, parameter)) {

				pgBlockMove(font_ptr->name, current_font.name, FONT_SIZE * sizeof(pg_char));
				UnuseMemory(font_table);

				translator.font_changed |= !pgEqualStruct(&translator.font, &current_font, SIGNIFICANT_FONT_SIZE);

				if (!translator.bytes_transferred)
					translator.font = current_font;
				else 
					result = translator.font_changed;
				
				if (current_stylesheet)
					stylesheet.has_font = TRUE;
			}
			break;

		case pointsize_command:
			current_style.point = (pg_fixed)(parameter / 2);
			current_style.point <<= 16;
			break;

		case italic_command:
			current_style.styles[italic_var] = -1;
			break;

		case outline_command:
			current_style.styles[outline_var] = -1;
			break;

		case plain_command:
			pgFillBlock(current_style.styles, MAX_STYLES * sizeof(short), 0);
			current_style.char_extra = 0;
			current_style.space_extra = 0;
			break;

		case style_command:
			if (current_stylesheet)
				stylesheet.style_id = current_stylesheet->style_id = parameter;
			else
			if ((stylesheet_ptr = locate_stylesheet(translator.stylesheet_table, parameter)) != NULL) {
				
				pgGetNamedStyleInfo(import_pg, stylesheet_ptr->index, &current_style, &current_font, &current_par);
				::UnuseMemory(translator.stylesheet_table);
			}

			break;

		case small_caps_command:
			current_style.styles[small_caps_var] = -1;
			break;

		case strikeout_command:
			current_style.styles[strikeout_var] = -1;
			break;

		case shadow_command:
			current_style.styles[shadow_var] = -1;
			break;

		case underline_command:
			current_style.styles[underline_var] = -1;
			break;

		case dotted_underline_command:
			current_style.styles[dotted_underline_var] = -1;
			break;

		case double_underline_command:
			current_style.styles[dbl_underline_var] = -1;
			break;

		case word_underline_command:
			current_style.styles[word_underline_var] = -1;
			break;

		case superscript_command:
			current_style.styles[superscript_var] = (short)(parameter / 2);
			break;
			
		case invis_text_command:
			current_style.styles[hidden_text_var] = -1;
			break;
	}

	if (current_stylesheet) {
		
		if (command != style_command)
			stylesheet.has_styles = TRUE;
	}
	else {
		
		translator.format_changed |= !pgEqualStruct(&translator.format, &current_style, SIGNIFICANT_STYLE_SIZE);
		
		if (translator.format_changed)
			if (current_style.style_sheet_id > 0)
				current_style.style_sheet_id = -current_style.style_sheet_id;

		if (!translator.bytes_transferred)
			translator.format = current_style;
		else
			result |= translator.format_changed;
	}

	return		result;
}

/* ProcessInfoCommand gets called for "\info" type commands. The default member
function here does nothing but you can override. */

void PaigeRTFImportFilter::ProcessInfoCommand (short command, short parameter)
{
	SkipCurrentLevel();
}

/* UnsupportedCommand gets called when an RTF command is not recognized. The
token is in command + any parameters */

void PaigeRTFImportFilter::UnsupportedCommand (pg_char_ptr command, short parameter)
{

}

/* PrepareRTFPicture prepares for reading a picture. It is necessary to read the picture
after the current text, if any, is transferred. If result == TRUE then current text should
be sent to the host. */

pg_boolean PaigeRTFImportFilter::PrepareRTFPicture (void)
{
	long				command_result;
	short				parameter, table_id, command;
	pg_char				next_char;

	pgFillBlock(&metafile, sizeof(metafile_struct), 0);
	twips_width = twips_gwidth = twips_height = twips_gheight = 0;

	embed_width = embed_height = 0;
	embed_type = 0;
	embed_binary = 0;

	while (!is_hex_char(io_buffer[buffer_index]) && !embed_binary) {
		
		if (pgGetImportByte(&next_char) != NO_ERROR)
			return	FALSE;

		if (next_char == RTF_COMMAND_CHAR) {

			if ((command_result = ReadCommand(&parameter, &next_char)) != 0) {
				
				table_id = pgHiWord(command_result);
				command = pgLoWord(command_result);
				
				if (table_id == DESTINATION_COMMAND)
					DoDestinationCommand(command, parameter);
			}
		}
	}

	picture_ref = MemoryAlloc(paige_globals->mem_globals, 1, 0, PICT_APPEND_SIZE);
	
	if (translator.bytes_transferred)
		return	TRUE;
	
	return	ReadRTFPicture();
}


/* ReadRTFPicture imports an RTF graphic. This only gets called after a picture has been
prepped for import. */

pg_boolean PaigeRTFImportFilter::ReadRTFPicture (void)
{
	register pg_bits8_ptr		pict_output;
	short						output_ctr, byte_toggle;
	long						real_output;
	pg_char						next_char, hex_char;

	if (!(import_bits & IMPORT_EMBEDDED_OBJECTS_FLAG)) {
		
		// Pass through binary data, if any

		while (embed_binary) {
			
			if (pgGetImportByte(&next_char) != NO_ERROR)
				break;
			
			--embed_binary;

		}
		
		while (pgGetImportByte(&next_char) == NO_ERROR) {

			if (next_char == RTF_GROUPEND_CHAR) {
				
				PopStyleStack();
				break;
			}
		}

		return	FALSE;
	}

	::SetMemorySize(picture_ref, PICT_APPEND_SIZE);
	pict_output = (pg_bits8_ptr)UseMemory(picture_ref);
	output_ctr = PICT_APPEND_SIZE;
	real_output = 0;
	byte_toggle = 0;

	if (embed_binary) {

		while (embed_binary) {
			
			if (pgGetImportByte(&next_char) != NO_ERROR)
				break;
					
			*pict_output++ |= (pg_bits8)next_char;
			++real_output;
			
			if ((output_ctr -= 1) == 0) {
				
				pict_output = (pg_bits8_ptr)AppendMemory(picture_ref, PICT_APPEND_SIZE, FALSE);
				output_ctr = PICT_APPEND_SIZE;
			}
			
			--embed_binary;
		}

		while (pgGetImportByte(&next_char) == NO_ERROR) {
			
			if (next_char == RTF_GROUPEND_CHAR) {
				
				PopStyleStack();
				break;
			}
		}
	}
	else {
	
		while (pgGetImportByte(&next_char) == NO_ERROR) {
			
			if (next_char == RTF_GROUPEND_CHAR) {
				
				PopStyleStack();
				break;
			}
			else
			if (is_hex_char(next_char)) {
			
				hex_char = (pg_char)translate_hex(next_char);
				++byte_toggle;
		
				if (byte_toggle & 1)
					*pict_output = (hex_char << 4);
				else {
					
					*pict_output++ |= hex_char;
					++real_output;
					
					if ((output_ctr -= 1) == 0) {

						pict_output = (pg_bits8_ptr)AppendMemory(picture_ref, PICT_APPEND_SIZE, FALSE);
						output_ctr = PICT_APPEND_SIZE;
					}
				}
			}
		}
	}

	UnuseMemory(picture_ref);
	SetMemorySize(picture_ref, real_output);
	
	if (!real_output || !embed_type)
		DisposeMemory(picture_ref);
	else {
		embed_ref			embed;
		pg_embed_ptr		embed_ptr;
		void PG_FAR			*embed_data;
		
		if ((embed_data = pgProcessEmbedData(picture_ref, embed_type)) == NULL)
			embed_data = (void PG_FAR *) picture_ref;
		
		if (embed_type == embed_meta_file) {
			
			metafile.metafile = (long)embed_data;
			
			if ((metafile.bounds.bot_right.h = embed_width) == 0)
				metafile.bounds.bot_right.h = metafile.x_ext;
			if ((metafile.bounds.bot_right.v = embed_height) == 0)
				metafile.bounds.bot_right.v = metafile.y_ext;
			
			embed_data = (void PG_FAR *)&metafile;
		}

		embed = pgNewEmbedRef(paige_globals->mem_globals, embed_type,
				embed_data, 0, 0, 0, 0, FALSE);
		
		embed_ptr = (pg_embed_ptr)UseMemory(embed);
		
		if (embed_ptr->width == 0 || embed_type == embed_mac_pict)
			embed_ptr->width = embed_width;
		if (embed_ptr->height == 0 || embed_type == embed_mac_pict)
			embed_ptr->height = embed_height;
		
		if (embed_type == embed_meta_file) {

			embed_ptr->uu.pict_data.twips_width = twips_width;
			embed_ptr->uu.pict_data.twips_height = twips_height;
			embed_ptr->uu.pict_data.twips_gwidth = twips_gwidth;
			embed_ptr->uu.pict_data.twips_gheight = twips_gheight;
		}

		UnuseMemory(embed);

		pgInitEmbedStyleInfo(import_pg_rec, 0, embed, 0, NULL, 0, &current_style, NULL, NULL, NULL, FALSE);

		OutputCharacter(DUMMY_LEFT_EMBED);
		OutputCharacter(DUMMY_RIGHT_EMBED);
		
		translator.format = current_style;
		translator.format_changed = TRUE;
	}

	picture_ref = MEM_NULL;

	return	(pg_boolean)(real_output > 0);
}


/* PushStyleStack pushes the current style and font into a "stack" for {levels} */

void PaigeRTFImportFilter::PushStyleStack (void)
{
	format_stack_ptr	style_stack_ptr;

	style_stack_ptr = (format_stack_ptr)AppendMemory(style_stack, 1, FALSE);
	pgBlockMove(&current_style, &style_stack_ptr->style, sizeof(style_info));
	pgBlockMove(&current_font, &style_stack_ptr->font, sizeof(font_info));
	UnuseMemory(style_stack);
}


/* PopStyleStack pops the current style and font into a "stack" for {levels} */

void PaigeRTFImportFilter::PopStyleStack (void)
{
	format_stack_ptr	style_stack_ptr;
	long				style_stack_size;

	if ((style_stack_size = GetMemorySize(style_stack)) > 0) {

		--style_stack_size;
		
		style_stack_ptr = (format_stack_ptr)UseMemory(style_stack);
		style_stack_ptr += style_stack_size;
		pgBlockMove(&style_stack_ptr->style, &current_style, sizeof(style_info));
		pgBlockMove(&style_stack_ptr->font, &current_font, sizeof(font_info));
		UnuseMemory(style_stack);

		SetMemorySize(style_stack, style_stack_size);
	}
}


/* SkipCurrentLevel just scans the byte stream until the current level is reduced by one. */

void PaigeRTFImportFilter::SkipCurrentLevel (void)
{
	pg_char			next_char;
	long			stack_level, current_level;
	
	if (stack_level = GetMemorySize(style_stack)) {
		
		current_level = stack_level;

		while (pgGetImportByte(&next_char) == NO_ERROR) {
			
			if (next_char == RTF_GROUPBEGIN_CHAR) {

				if (current_stylesheet)
					++stylesheet_level;

				++current_level;
			}
			else
			if (next_char == RTF_GROUPEND_CHAR) {
				
				--current_level;

				if (current_stylesheet)
					--stylesheet_level;

				if (current_level < stack_level) {
					
					PopStyleStack();
					break;
				}
			}
		}
	}
}


/* ProcessStylesheet gets called after a name terminator in the text stream. Here we
complete the stylesheet record and begin a new one. */

void PaigeRTFImportFilter::ProcessStylesheet (void)
{
	named_stylesheet	named_style;
	style_info_ptr		style_ptr;
	par_info_ptr		par_ptr;
	font_info_ptr		font_ptr;

	if (stylesheet.name[0]) {
	
		stylesheet.style = current_style;
		stylesheet.font = current_font;
		stylesheet.par = current_par;
		
		if (import_bits & IMPORT_STYLESHEETS_FLAG) {
			
			if (stylesheet.has_styles)
				style_ptr = &stylesheet.style;
			else
				style_ptr = NULL;
			if (stylesheet.has_par_styles)
				par_ptr = &stylesheet.par;
			else
				par_ptr = NULL;
			if (stylesheet.has_font) {
				
				if (!FindFontInList(&stylesheet.font))
					pgMapFont(&stylesheet.font, file_os, CURRENT_OS);

				font_ptr = &stylesheet.font;
			}
			else
				font_ptr = NULL;
		}

		current_stylesheet->index = pgNewNamedStyle(import_pg, stylesheet.name, style_ptr, font_ptr, par_ptr);
		pgGetNamedStyle(import_pg, current_stylesheet->index, &named_style);

		if (stylesheet.style_id == 0) {
		
			par_ptr = (par_info_ptr)UseMemory(import_pg_rec->par_formats);
			*par_ptr = stylesheet.par;
			par_ptr->used_ctr += 1;
			UnuseMemory(import_pg_rec->par_formats);
			
			import_pg_rec->def_named_index = current_stylesheet->index - 1;
		}

		current_stylesheet = (style_table_ptr)::AppendMemory(translator.stylesheet_table, 1, TRUE);
	}

	pgFillBlock(&stylesheet, sizeof(style_table_entry), 0);
	stylesheet_index = 0;
	
	current_style = paige_globals->def_style;
	current_par = paige_globals->def_par;
	stylesheet.par = current_par;
	current_font = paige_globals->def_font;
}


/********************************* Local Functions ********************************/


/* locate_font_in_table walks through the stored font table looking for family_id */

static font_info_ptr locate_font_in_table (memory_ref table, short family_id)
{
	font_info_ptr			fonts;
	long					num_fonts;
	
	if ((num_fonts = GetMemorySize(table)) > 0) {
		
		fonts = (font_info_ptr)UseMemory(table);
		
		while (num_fonts) {
			
			if (fonts->family_id == family_id)
				return		fonts;
			
			++fonts;
			--num_fonts;
		}
	}
	
	UnuseMemory(table);
	
	return	(font_info_ptr)NULL;
}



/* This functions scans a table of terms and returns the argument number if found,
or zero if not found. The arg_size is the length of the argument, however the argument might
be space-terminated or control-terminated.  If there is a numeric command or parameter in
the argument, it is returned in *param (if param is non-null). */

static short rtf_command (pg_char_ptr table, pg_char_ptr argument, pg_short_t arg_size,
		short PG_FAR *param)
{
	register pg_char_ptr		table_ptr;
	register pg_char_ptr		arg_ptr;
	register pg_short_t			arg_ctr;
	short						table_item;

	if (!arg_size)
		return	FALSE;

	table_ptr = table;
	table_item = 1;

	while (*table_ptr) {
		
		arg_ptr = argument;

		if (*table_ptr > *arg_ptr)
			return	FALSE;
		
		if (*table_ptr == *argument) {
		
			arg_ctr = arg_size;
	
			for (arg_ctr = arg_size; arg_ctr; --arg_ctr) {

				if (*table_ptr++ != *arg_ptr++)
					break;

				if (*table_ptr == ' ') {
					
					if (arg_ctr == 1)
						return	table_item;
					
					if (!regular_letter(*arg_ptr)) {
						
						if (param) {
							
							if ( (*arg_ptr >= '0' && *arg_ptr <= '9') || (*arg_ptr == NEGATIVE_SIGN_CHAR))
								*param = (short)string_to_num(arg_ptr, arg_ctr - 1);
							else
								*param = (short)(*arg_ptr & 0x00FF);
								
							*arg_ptr = 0;
						}

						return	table_item;
					}
					
					break;
				}
			}
		}

		for (;;)
			if (*table_ptr++ == ' ')
				break;

		table_item += 1;
	}

	return		0;
}


/* regular_letter returns TRUE if the_char is a - z or A-Z. */

static pg_boolean regular_letter (pg_char the_char)
{
	if (the_char < 'A' || the_char > 'z')
		return	FALSE;
	
	if (the_char > 'Z' && the_char < 'a')
		return	FALSE;

	return 	TRUE;
}


/* is_hex_char returns TRUE if the_char is hexidecimal ascii. */

static pg_boolean is_hex_char (pg_char the_char)
{
	if (the_char >= '0' && the_char <= '9')
		return	TRUE;
	if (the_char >= 'a' && the_char <= 'f')
		return	TRUE;
	if (the_char >= 'A' && the_char <= 'F')
		return	TRUE;

	return		FALSE;
}


/* string_to_num returns the numeric value of a string. */

static long string_to_num (pg_char_ptr string, short string_size)
{
	register pg_char_ptr	string_ptr;
	register short			string_ctr;
	pg_boolean				negative_sign;
	long					result = 0;
	
	string_ptr = string;
	string_ctr = string_size;
	
	if (*string_ptr == NEGATIVE_SIGN_CHAR) {
		
		negative_sign = TRUE;
		++string_ptr;
		--string_ctr;
	}
	else
		negative_sign = FALSE;

	while (string_ctr) {
		
		if (*string_ptr < '0' || *string_ptr > '9')
			break;
		
		result *= 10;
		result += (long)((*string_ptr++) & 0x0F);
		
		--string_ctr;
	}
	
	if (negative_sign)
		result = -result;

	return		result;
}

/* translate_hex translates a hax character to its value. */

static pg_short_t translate_hex (pg_char hex_char)
{
	pg_short_t			result;
	
	result = (pg_short_t)hex_char;
	
	if (result > 'F')
		result -= 0x20;
	if (result > '9')
		result -= 7;
	
	return		(result - 0x30);
}


/* locate_stylesheet returns a pointer to the stylesheet in table.  If found the pointer is returned. */

static style_table_ptr locate_stylesheet (memory_ref table, short style_id)
{
	style_table_ptr		table_ptr;
	long				num_entries, index_ctr;
	
	num_entries = GetMemorySize(table);
	table_ptr = (style_table_ptr)UseMemory(table);
	
	for (index_ctr = 1; index_ctr <= num_entries; ++index_ctr, ++table_ptr) {
		
		if (table_ptr->style_id == style_id)
			return		table_ptr;
	}
	
	UnuseMemory(table);
	
	return	(style_table_ptr)NULL;
}


/* convert_twips converts twipsvalue to the appropriate pixel size. */

static short convert_twips (paige_rec_ptr pg, short twipsvalue)
{
	long		result;		
	
	result = pgConvertResolution(pg, twipsvalue);
	result /= 20;
	
	return	(short)result;
}

