/* This file handles text_block records in a Paige struct. */

#include "Paige.h"

#ifdef MAC_PLATFORM
#pragma segment pgbasic2
#endif

#include "machine.h"
#include "defprocs.h"
#include "pgDefStl.h"
#include "pgUtils.h"
#include "pgText.h"
#include "pgTxtWid.h"
#include "pgSubref.h"


static void measure_hidden_text (long PG_FAR *positions, short PG_FAR *chartypes,
		long measure_size);
static pg_short_t maximum_measure_size (style_info_ptr measure_style);

#ifdef PG_SUBSETS
static long next_subref_offset (pg_char_ptr text, long remaining_length);
static long measure_subref_char (paige_rec_ptr pg, text_block_ptr block, short subref_index);
#endif


/* pgLineParse is the default line parser function, which merely returns the
current length-to-next-line-partition. When it is used for other fancy purposes
the idea is to return a lesser length than remaining_length if the line should
parse on something sooner. The text param is the text beginning at this portion
of the line.  */

PG_PASCAL (long) pgLineParse (paige_rec_ptr pg, pg_measure_ptr measure_info,
		pg_char_ptr text, point_start_ptr line_start, long global_offset, long remaining_length)
{
	return	remaining_length;
}


/* pgInitLineProc is the default init/uninit for line measuring. This is where
the measure_info is initialized (if init_verb = init_measure_verb), or starting
a new line (new_line_verb) or terminating a text block measure
(done_measure_verb).  */

PG_PASCAL (void) pgInitLineProc (paige_rec_ptr pg, pg_measure_ptr measure_info, short init_verb)
{

}



/* pgPageModify is the default modify-page function. This gets called for each repeating page.
This default function does not do anything. */

PG_PASCAL (void) pgPageModify  (paige_rec_ptr pg, long page_num, rectangle_ptr margins)
{
#ifdef MAC_PLATFORM
#pragma unused (pg, page_num, margins)
#endif
}


/* pgHyphenateProc is the default hyphenation function. Actually, "hyphenation"
might be slight of a misnomer since this function must determine where to break
a word at the end of a line. Upon entry, *offset is the relative offset of a
byte in text that is ONE TOO MANY characters that can fit in the available space.
This function must decide how far to back up (normally, it backs up to the beginning
of the current word minus any leading blanks). In the case of hyphenation you
can determine something else.  In any case, *offset should get updated to a
lower number and the line will break there.  If you are actually hyphenating,
return TRUE for the function result.   */

PG_PASCAL (pg_boolean) pgHyphenateProc (paige_rec_ptr pg, text_block_ptr block,
		style_walk_ptr styles, pg_char_ptr block_text, long line_begin,
		long PG_FAR *line_end, long PG_FAR *positions, short PG_FAR *char_types,
		long PG_FAR *line_width_extra, pg_boolean zero_length_ok)
{
	register long			offset_begin, offset_end, global_offset;
	register style_walk_ptr	walker;
	long					soft_hyphen_flag, original_end, multibyte_adjust;
	long					max_end, c_info, previous_info, previous_previous_info, previous_pos;
	long					class_bits, first_class_bits;
	pg_boolean				hyphenated;

#ifdef MAC_PLATFORM
#pragma unused (block, positions, char_types)
#endif

	if (pg->flags & NO_SOFT_HYPHEN_BIT)
		soft_hyphen_flag = 0;
	else
		soft_hyphen_flag = SOFT_HYPHEN_BIT;

	offset_begin = line_begin;
	offset_end = original_end = *line_end;
	walker = styles;
	global_offset = block->begin;
	max_end = block->end - block->begin;

	hyphenated = FALSE;
	
	if (offset_end > offset_begin) {

		pgWalkStyle(walker, -1);
		first_class_bits = walker->cur_style->class_bits;
		pgWalkStyle(walker, 1);
	}

	previous_info = previous_previous_info = previous_pos = 0;
	
	offset_end = *line_end;

	while (offset_end > offset_begin) {
		
		pgWalkStyle(walker, -1);
		--offset_end;

		class_bits = walker->cur_style->class_bits;
		
		if (class_bits & GROUP_CHARS_BIT) {

			if (!(class_bits & CANNOT_BREAK)) {

				if (walker->current_offset == walker->prev_style_run->offset)
					break;

				if (!(first_class_bits & GROUP_CHARS_BIT)) {

					++offset_end;
					pgWalkStyle(walker, 1);

					break;
				}
			}
		}
		else
		if (!(class_bits & CANNOT_BREAK)) {

			c_info = walker->cur_style->procs.char_info(pg, walker, block_text,
					block->begin, offset_begin, max_end, offset_end, 
					WORD_BREAK_BIT | INCLUDE_BREAK_BIT | LAST_HALF_BIT | MIDDLE_CHAR_BIT
					| NON_BREAKAFTER_BIT | NON_BREAKBEFORE_BIT | SOFT_HYPHEN_BIT | BLANK_BIT);
		
			while (c_info & (LAST_HALF_BIT | MIDDLE_CHAR_BIT) && offset_end > offset_begin)
				c_info = walker->cur_style->procs.char_info(pg, walker, block_text,
							block->begin, offset_begin, max_end, --offset_end, 
							WORD_BREAK_BIT | INCLUDE_BREAK_BIT | LAST_HALF_BIT | MIDDLE_CHAR_BIT
							| NON_BREAKAFTER_BIT | NON_BREAKBEFORE_BIT | SOFT_HYPHEN_BIT | BLANK_BIT); 


			if (previous_info & NON_BREAKAFTER_BIT) {
				if (c_info & NON_BREAKAFTER_BIT)
					goto resume;
				else {
					offset_end = previous_pos;
					break;	
				}
			}
			if (previous_info & NON_BREAKBEFORE_BIT) {
				if (c_info & (INCLUDE_BREAK_BIT | NON_BREAKBEFORE_BIT | NON_BREAKAFTER_BIT))
					goto resume;
				else if (c_info & BLANK_BIT) {
					offset_end = previous_pos;
					break;	
				} else if (c_info & WORD_BREAK_BIT)
					break;
			}
			// Break when Double Byte Character is preceded by roman text
			if ((previous_info & WORD_BREAK_BIT && !(previous_info & INCLUDE_BREAK_BIT))  // character breaks on its own i.e. kanji
				&& (c_info & INCLUDE_BREAK_BIT)) {		
				offset_end = previous_pos;
				break;
			} 
			
	   		if (!(c_info & (LAST_HALF_BIT | MIDDLE_CHAR_BIT)))
				if (((c_info & WORD_BREAK_BIT) && !(c_info & INCLUDE_BREAK_BIT)) || (c_info & soft_hyphen_flag)) {
				
				/* Scan backwards until a non-blank exists */
				
				if (c_info & (BLANK_BIT))
					while (offset_end > offset_begin) {
					
						--offset_end;
						pgWalkStyle(walker, -1);
					
						c_info = walker->cur_style->procs.char_info(pg, walker,
								block_text, block->begin, offset_begin, max_end, offset_end,
								WORD_BREAK_BIT | SOFT_HYPHEN_BIT | BLANK_BIT | INCLUDE_BREAK_BIT
								| LAST_HALF_BIT | MIDDLE_CHAR_BIT | NON_BREAKAFTER_BIT | NON_BREAKBEFORE_BIT);

						if (!(c_info & (LAST_HALF_BIT | MIDDLE_CHAR_BIT | NON_BREAKAFTER_BIT | NON_BREAKBEFORE_BIT)))
							if (!(previous_info & NON_BREAKBEFORE_BIT))
								if (!(c_info & BLANK_BIT)) {
						
							++offset_end;
							previous_info = previous_previous_info;

							pgWalkStyle(walker, 1);
						
							while (offset_end < original_end) {
						
								c_info = walker->cur_style->procs.char_info(pg, walker,
									block_text, block->begin, offset_begin, max_end, offset_end,
									WORD_BREAK_BIT | INCLUDE_BREAK_BIT | SOFT_HYPHEN_BIT | BLANK_BIT
									| LAST_HALF_BIT | MIDDLE_CHAR_BIT);
							
								if (!(c_info & (LAST_HALF_BIT | MIDDLE_CHAR_BIT)))
									break;

								++offset_end;
								pgWalkStyle(walker, 1);
							}
						
							break;
						}
					
						if (!(c_info & LAST_HALF_BIT)) {
							previous_info = previous_previous_info;
							previous_info = c_info;
						}
				} else
					if ((c_info & WORD_BREAK_BIT) && !(c_info & INCLUDE_BREAK_BIT)) {
						if (c_info & NON_BREAKAFTER_BIT)
							goto resume;
						else if (previous_info & INCLUDE_BREAK_BIT) {
							offset_end = previous_pos;
							break;
						} else if (previous_info & WORD_BREAK_BIT) {
							offset_end = previous_pos;
							break;
						} else
							goto resume;				
					} 

				if ((c_info & INCLUDE_BREAK_BIT) || (c_info & soft_hyphen_flag))
					++offset_end;
				
				if (offset_end >= original_end)
					multibyte_adjust = -1;
				else
					multibyte_adjust = 1;

				pgSetWalkStyle(walker, global_offset + offset_end);
				
				while (walker->cur_style->procs.char_info(pg, walker,
							block_text, block->begin, offset_begin, max_end, offset_end, LAST_HALF_BIT | MIDDLE_CHAR_BIT)) {
							
					offset_end += multibyte_adjust;
					pgWalkStyle(walker, multibyte_adjust);
				}

				hyphenated = ((c_info & soft_hyphen_flag) != 0);
				break;
			}
		}
	
	resume:
			
		if (!(c_info & LAST_HALF_BIT)) {
			previous_previous_info = previous_info;
			previous_info = c_info;
			previous_pos = offset_end;
		}
	}

	if ((!zero_length_ok) && (offset_end == offset_begin)) {

		hyphenated = FALSE;		

		offset_end = *line_end;
		pgSetWalkStyle(walker, global_offset + offset_end);
		
		c_info = 0;

		while (offset_end > offset_begin) {

			pgWalkStyle(walker, -1);
			--offset_end;
			
			if (!walker->cur_style->procs.char_info(pg, walker, block_text,
					global_offset, offset_begin, max_end, offset_end, LAST_HALF_BIT | MIDDLE_CHAR_BIT))
				break;
		}
	
		if (offset_end == offset_begin) {  /* Zero length line! */
			long			byte_advance, initial_begin;

			pgSetWalkStyle(walker, global_offset + offset_begin);
			offset_end = *line_end;
			initial_begin = offset_begin;

			while (offset_begin < offset_end) {
				
				byte_advance = walker->cur_style->char_bytes + 1;

				pgWalkStyle(walker, byte_advance);
				offset_begin += byte_advance;
				
				if (!walker->cur_style->procs.char_info(pg, walker, block_text,
						block->begin, initial_begin, max_end, offset_begin, LAST_HALF_BIT | MIDDLE_CHAR_BIT))
					break;
			}
			
			offset_end = offset_begin;
		}
	}

	*line_end = offset_end;

	if (hyphenated)
		if (offset_end)
			*line_width_extra = positions[offset_end] - positions[offset_end - 1];
	
	return	hyphenated;
}


/* Given a relative point_start in a block, pgLineBoundary returns the first
text offset of the line in *line_begin_offset and the ending text offset in
*line_end_offset (either parameter can also be NULL if you don't need the result).
The function returns the total number of point_starts for the line. Also,
from_start returns with the actual line's first start. */

PG_PASCAL (pg_short_t) pgLineBoundary (paige_rec_ptr pg, text_block_ptr block, pg_short_t PG_FAR *from_start,
			pg_short_t PG_FAR *line_begin_offset, pg_short_t *line_end_offset)
{
	register point_start_ptr		starts;
	register pg_short_t				ctr, back_ctr;
	long							abs_offset;
	pg_short_t						first_offset, last_offset;

	starts = UseMemory(block->lines);
	back_ctr = *from_start;
	starts += back_ctr;
	abs_offset = block->begin;
	abs_offset += (long) starts->offset;
	
	if (abs_offset < pg->t_length) {
	
		while (starts->offset) {
			
			--starts;
	
			if (starts->flags & LINE_BREAK_BIT) {
				++starts;
				break;
			}
			--back_ctr;
		}
		
		for (ctr = 1, first_offset = starts->offset; !(starts->flags & LINE_BREAK_BIT);
					++ctr, ++starts) ;
		
		last_offset = starts[1].offset;
	}
	else {
		
		ctr = 1;
		first_offset = last_offset = starts->offset;
	}
	
	if (line_begin_offset)
		*line_begin_offset = first_offset;
	if (line_end_offset)
		*line_end_offset = last_offset;
	
	*from_start = back_ctr;

	UnuseMemory(block->lines);
	
	return	ctr;
}



/* pgGetCharLocs returns the character positions pointer for a text_block. Note
the positions array might already exist (in the pg_rec) and, if so, they are
returned as a pointer in use.  If they do not exist, they are created, placed
in the pg_rec and returned as a pointer in use. In either case, the memory ref
is returned in *memory_ref and you must do UnuseMemory with it when finished.

If types_ref is non-NULL it gets set to the character-types list memory_ref.  */

PG_PASCAL (long PG_FAR*) pgGetCharLocs (paige_rec_ptr pg_rec, text_block_ptr block,
		memory_ref PG_FAR *used_ref, memory_ref PG_FAR *types_ref)
{
	register long PG_FAR		*positions;
	register short PG_FAR		*char_types;
	long PG_FAR					*positions_result;
	memory_ref					long_pos_ref, types;
	pg_char_ptr					text;
	style_walk					walker;
	long						text_length, max_chars, partial_length, next_style;
	short						call_order;
#ifdef PG_SUBSETS
	long						next_subref_pos;
	short						subref_ctr = 0;
#endif

	if (long_pos_ref = pgFindCharLocs(pg_rec, block, NULL, &types, TRUE, FALSE)) {
		
		*used_ref = long_pos_ref;

		if (types_ref)
			*types_ref = types;

		return	UseMemory(long_pos_ref);
	}
	
	*used_ref = long_pos_ref = pgFindCharLocs(pg_rec, block, NULL, &types, FALSE, TRUE);

	if (types_ref)
		*types_ref = types;
	
	pg_rec->procs.load_proc(pg_rec, block);

	text_length = block->end - block->begin;
	SetMemorySize(long_pos_ref, text_length + 1);
	SetMemorySize(types, text_length + 1);

	positions_result = positions = UseMemory(long_pos_ref);
	*positions = 0;
	char_types = UseMemory(types);

	pgPrepareStyleWalk(pg_rec, block->begin, &walker, FALSE);
	text = UseMemory(block->text);

	pgSetMeasureDevice(pg_rec);
	call_order = 0;
	
	while (text_length) {
		
		walker.cur_style->procs.install(pg_rec, walker.cur_style, walker.cur_font, &walker.superimpose, FALSE);
		
		max_chars = (long)maximum_measure_size(&walker.superimpose);
		partial_length = text_length;
		next_style = walker.next_style_run->offset - walker.current_offset;

		if (next_style < partial_length)
			partial_length = next_style;

		if (partial_length > max_chars)
			partial_length = max_chars;

#ifdef PG_SUBSETS
		next_subref_pos = next_subref_offset(text, partial_length);
		
		if (partial_length > next_subref_pos)
			partial_length = next_subref_pos;
#endif
		if (partial_length == text_length)
			call_order = -1;

		if ((pg_rec->flags & NO_HIDDEN_TEXT_BIT) && walker.cur_style->styles[hidden_text_var])
			measure_hidden_text (positions, char_types, partial_length);
		else {
			
#ifdef PG_SUBSETS
			if (*text == SUBREF_CHAR) {

				positions[1] = *positions + measure_subref_char(pg_rec, block, subref_ctr);
				partial_length = 1;
				*char_types = CTL_BIT | INVIS_ACTION_BIT;
				subref_ctr += 1;
			}
			else
#endif
			walker.cur_style->procs.measure(pg_rec, &walker, text, partial_length,
				0, positions, char_types, measure_width_locs, walker.current_offset,
				FALSE, call_order);
		}

		positions += partial_length;
		char_types += partial_length;
		++call_order;

		if ((text_length -= partial_length) > 0) {

			pgWalkStyle(&walker, partial_length);			
			text += partial_length;
		}
	}
	
	UnuseMemory(block->text);
	UnuseMemory(types);

	pgPrepareStyleWalk(pg_rec, 0, NULL, FALSE);
	pgUnsetMeasureDevice(pg_rec);

	return	positions_result;
}


/* pgGetSpecialLocs returns a (new) memory_ref of a smaller block of text measured
with a full-justify slop. It gets called by mouse-selection routines when the
actual screen positions (including space-extra settings, etc.) are required. The
block of text to be measured is guaranteed to be a single style run.
If scaled_widths is non-zero it gets passed to the measure proc. */

PG_PASCAL (memory_ref) pgGetSpecialLocs (paige_rec_ptr pg_rec, text_block_ptr block,
		point_start_ptr related_start, pg_short_t text_size, long slop_extra,
		short scale_widths)
{
	register long PG_FAR			*positions;
	register pg_char_ptr			text;
	memory_ref						result_ref;
	style_walk						walker;
	long							char_size, starting_offset, ending_offset;
	short							scale_verb, measure_verb;
	pg_short_t						non_blank_size;
	pg_short_t						text_offset, unresolved_chars;

	result_ref = MemoryAlloc(pg_rec->globals->mem_globals, sizeof(long),
			text_size + 1, 0);
	positions = UseMemory(result_ref);
	*positions = 0;

	starting_offset = block->begin + (long) related_start->offset;
	ending_offset = block->end - block->begin;

	pg_rec->procs.load_proc(pg_rec, block);
	text = UseMemory(block->text);
	text_offset = related_start->offset;

#ifdef PG_SUBSETS
	
	if (text[text_offset] == SUBREF_CHAR) {
		long			subref_index;
		
		subref_index = pgFindSubrefPosition(block, text_offset);
		positions[1] = measure_subref_char(pg_rec, block, (short)subref_index);
		UnuseMemory(block->text);
		UnuseMemory(result_ref);
		
		return	result_ref;
	}

#endif
	text += related_start->offset;

	pgPrepareStyleWalk(pg_rec, starting_offset, &walker, TRUE);

	pgSetMeasureDevice(pg_rec);

	walker.cur_style->procs.install(pg_rec, walker.cur_style, walker.cur_font, &walker.superimpose, FALSE);

	non_blank_size = text_size;
	
/* Check to see if we omit blanks from the measurement's end: */

/* (Fix BaseView reported: ) */

	if (!(related_start->flags & PAR_BREAK_BIT) && (related_start[1].flags != TERMINATOR_BITS)
		&& (related_start->flags & LINE_BREAK_BIT) && ( (related_start->flags & RIGHT_DIRECTION_BIT)
			|| (walker.cur_par_style->justification != justify_left)) )  {

		while (non_blank_size) {
	
			char_size = walker.cur_style->char_bytes + 1;
			if (!walker.cur_style->procs.char_info(pg_rec, &walker, text,
					NO_BLOCK_OFFSET, 0, ending_offset, non_blank_size - char_size, BLANK_BIT))
				break;
	
			non_blank_size -= (pg_short_t)char_size;
			pgWalkStyle(&walker, -char_size);
		}
	}

	if (pg_rec->scale_factor.scale)
		scale_verb = scale_widths;
	else
		scale_verb = FALSE;

	if (non_blank_size) {

		pgSetWalkStyle(&walker, starting_offset);

		if ((pg_rec->flags & NO_HIDDEN_TEXT_BIT) && walker.cur_style->styles[hidden_text_var])
			measure_hidden_text (positions, NULL, non_blank_size);
		else {

			measure_verb = (short)measure_draw_locs;
			
			if (!(related_start->flags & HAS_WORDS_BIT))
				measure_verb = -measure_verb;

			walker.cur_style->procs.measure(pg_rec, &walker, text, (long)non_blank_size,
				(pg_short_t)slop_extra, positions, (short PG_FAR *)NULL, measure_verb,
				walker.current_offset, (pg_boolean)scale_verb, (short)-1);
		}
	}

	if (unresolved_chars = text_size - non_blank_size) {
		
		for (positions += non_blank_size; unresolved_chars; ++positions, --unresolved_chars)
			positions[1] = *positions;

	}

	UnuseMemory(block->text);
	UnuseMemory(result_ref);
	
	pgPrepareStyleWalk(pg_rec, 0, NULL, TRUE);
	pgUnsetMeasureDevice(pg_rec);

	return	result_ref;
}


/* pgPartialTextMeasure inserts new character positions in the current positions
record if it exists and if it matches up with the given block. This does nothing
if current positions (in globals->width_tables) do not exist. This gets called by
functions that insert new text. The insertion took place at measure_from
(local offset to block) for new_measure_length bytes. */

PG_PASCAL (void) pgPartialTextMeasure (paige_rec_ptr pg_rec, text_block_ptr block,
			pg_short_t measure_from, pg_short_t new_measure_length)
{
	long PG_FAR				*original_positions;
	register long PG_FAR	*positions;
	register short PG_FAR	*char_types;
	register pg_char_ptr	text;
	memory_ref				long_pos_ref, types;
	style_walk				walker;
	long					end_base, next_style, global_offset;
	pg_short_t				partial_length, text_length, this_index, text_position, max_chars;
	short					call_order;

	if (!(long_pos_ref = pgFindCharLocs(pg_rec, block, &this_index, &types, TRUE, TRUE)))
		return;

	pg_rec->procs.load_proc(pg_rec, block);

	pgPrepareStyleWalk(pg_rec, block->begin + measure_from, &walker, FALSE);
	text = UseMemory(block->text);
	global_offset = block->begin;

	if (walker.cur_style->procs.char_info(pg_rec, &walker, text, global_offset, 0, block->end - block->begin,
			measure_from, LAST_HALF_BIT | MIDDLE_CHAR_BIT)) {
		
		UnuseMemory(block->text);
		pgPrepareStyleWalk(pg_rec, 0, NULL, FALSE);
		pgFreeCharLocs(pg_rec, this_index);
		
		return;
	}

	positions = InsertMemory(long_pos_ref, measure_from, new_measure_length);
	pg_rec->globals->width_tables[this_index].end += new_measure_length;
	char_types = InsertMemory(types, measure_from, new_measure_length);

	original_positions = positions;
	end_base = positions[new_measure_length];

	text_length = new_measure_length;
	text += measure_from;
	pgSetMeasureDevice(pg_rec);
	call_order = 0;
	text_position = measure_from;

	while (text_length) {
		
		walker.cur_style->procs.install(pg_rec, walker.cur_style, walker.cur_font, &walker.superimpose, FALSE);
		
		partial_length = text_length;
		next_style = walker.next_style_run->offset - walker.current_offset;
		
		max_chars = maximum_measure_size(&walker.superimpose);

		if (next_style < (long)partial_length)
			partial_length = (pg_short_t)next_style;
		
		if (partial_length > max_chars)
			partial_length = max_chars;
		if (partial_length == text_length)
			call_order = -1;

		if ((pg_rec->flags & NO_HIDDEN_TEXT_BIT) && walker.cur_style->styles[hidden_text_var])
			measure_hidden_text(positions, char_types, partial_length);
		else
#ifdef PG_SUBSETS
		if (*text == SUBREF_CHAR) {
			long			subref_index;
			
			subref_index = pgFindSubrefPosition(block, text_position);
			positions[1] = *positions + measure_subref_char(pg_rec, block, (short)subref_index);
			*char_types = CTL_BIT | INVIS_ACTION_BIT;
			partial_length = 1;
		}
		else
#endif
			walker.cur_style->procs.measure(pg_rec, &walker, text,
				partial_length, 0, positions, char_types, measure_width_locs,
				walker.current_offset, FALSE, call_order);

		positions += partial_length;
		char_types += partial_length;
		++call_order;
		
		text_length -= partial_length;
		text_position += partial_length;

		pgWalkStyle(&walker, partial_length);
		text += partial_length;
	}

	pgPrepareStyleWalk(pg_rec, 0, NULL, FALSE);
	UnuseMemory(block->text);
	UnuseMemory(types);

	text_length = (pg_short_t)(block->end - block->begin);
	partial_length = measure_from + new_measure_length;
	
	end_base = *positions - end_base;

	while (partial_length < text_length) {
		
		++positions;
		*positions += end_base;
		++partial_length;
	}

	UnuseMemory(long_pos_ref);
	
	pgReleaseCharLocs(pg_rec, long_pos_ref);
	pgUnsetMeasureDevice(pg_rec);
}


#ifdef PG_SUBSETS

/* pgTextReMeasure re-measures the embedded character located at measure_from. This is called
only by subref code (reserved for DSI). */


PG_PASCAL (void) pgTextReMeasure (paige_rec_ptr pg_rec, text_block_ptr block, long measure_from)
{
	register long PG_FAR	*positions;
	register pg_char_ptr	text;
	memory_ref				long_pos_ref, types;
	long					end_base, subref_index, remaining;
	pg_char					measure_byte;
	pg_short_t				this_index;

	if (!(long_pos_ref = pgFindCharLocs(pg_rec, block, &this_index, &types, TRUE, FALSE)))
		return;
	
	pg_rec->procs.load_proc(pg_rec, block);
	text = UseMemory(block->text);
	measure_byte = text[measure_from];
	UnuseMemory(block->text);
	
	if (measure_byte != SUBREF_CHAR) {
		
		pgReleaseCharLocs(pg_rec, long_pos_ref);
		return;
	}
	
	subref_index = pgFindSubrefPosition(block, (pg_short_t)measure_from);
	positions = UseMemory(long_pos_ref);
	positions += measure_from;
	end_base = positions[1];
	positions[1] = *positions + measure_subref_char(pg_rec, block, (short)subref_index);

	positions += 1;
	end_base = *positions - end_base;
	
	remaining = (block->end - block->begin - measure_from) - 1;
	
	while (remaining) {
	
		++positions;
		*positions += end_base;
		--remaining;
	}
	
	UnuseMemory(long_pos_ref);
	pgReleaseCharLocs(pg_rec, long_pos_ref);
}


#endif


/* pgDeleteTextMeasure deletes character positions from the current positions
record if it exists and if it matches up with the given block. This does nothing
if current positions (in globals->width_tables) do not exist. This gets called by
functions that delete text. The deletion took place at delete_from
(local offset to block) for delete_length bytes. */

PG_PASCAL (void) pgDeleteTextMeasure (paige_rec_ptr pg_rec, text_block_ptr block,
			pg_short_t delete_from, pg_short_t delete_length)
{
	long PG_FAR					*positions;
	memory_ref					long_pos_ref, types;
	long						base_position;
	pg_short_t					remaining_length, this_index;

	if (!(long_pos_ref = pgFindCharLocs(pg_rec, block, &this_index, &types, (pg_boolean)TRUE, TRUE)))
		return;

	remaining_length = (pg_short_t)GetMemorySize(long_pos_ref);

	GetMemoryRecord(long_pos_ref, delete_from, &base_position);
	DeleteMemory(long_pos_ref, delete_from, delete_length);
	DeleteMemory(types, delete_from, delete_length);

	pg_rec->globals->width_tables[this_index].end -= delete_length;

	remaining_length -= (delete_from + delete_length);

	positions = UseMemoryRecord(long_pos_ref, delete_from, USE_ALL_RECS, TRUE);
	base_position = *positions - base_position;
		
	while (remaining_length) {
		
		*positions -= base_position;
		++positions;
		--remaining_length;
	}
	
	UnuseMemory(long_pos_ref);
	
	pgReleaseCharLocs(pg_rec, long_pos_ref);
}



/* pgFindCharLocs takes a look at three possible char loc records in pg to
see if any of them match text_offset.  If so, the appopriate memory ref is
returned. However, if none match up and must_be_exact is TRUE, the function
return NULL;  if none match and must_be_exact is FALSE, the function initializes
its best choice in the three char loc entries in pg and returns than memory_ref. */

PG_PASCAL (memory_ref) pgFindCharLocs (paige_rec_ptr pg, text_block_ptr block,
		pg_short_t PG_FAR *found_index, memory_ref PG_FAR *types, pg_boolean must_be_exact,
		pg_boolean must_be_unlocked)
{
	register pg_globals_ptr		globals;
	register long				offset;
	register short				index;
	long						memory_id;

	memory_id = pg->mem_id;

	for (globals = pg->globals, offset = block->begin, index = 0; index < WIDTH_QTY; ++index)
		if ((globals->width_tables[index].mem_id == memory_id) && (globals->width_tables[index].offset == offset)) {
			
			if (!must_be_unlocked || (globals->width_tables[index].used_ctr == 0)) {
			
				if (found_index)
					*found_index = index;
	
				if (types)
					*types = globals->width_tables[index].types;
				
				globals->width_tables[index].used_ctr += 1;

				return	globals->width_tables[index].positions;
			}
		}

	if (must_be_exact)
		return	MEM_NULL;

	for (index = 0; index < ACCEPTABLE_WIDTH_QTY; ++index) {
		
		if (globals->width_tables[index].offset == -1)
			break;
	}
	
	if (index == ACCEPTABLE_WIDTH_QTY) {  // Still have not located anything
		
		for (index = 0; index < ACCEPTABLE_WIDTH_QTY; ++index)
			if (globals->width_tables[index].used_ctr == 0)
				break;
	}

	if (index == ACCEPTABLE_WIDTH_QTY) {  // Still have not located anything
		
		for (index = 0; index < WIDTH_QTY; ++index)
			if (globals->width_tables[index].used_ctr == 0)
				break;
	}

#ifdef PG_DEBUG
	if (index == WIDTH_QTY)
		globals->mem_globals->debug_proc(ACCESS_ERR, globals->width_tables[0].positions);
#endif

	globals->width_tables[index].offset = offset;
	globals->width_tables[index].end = block->end;
	globals->width_tables[index].mem_id = memory_id;
	globals->width_tables[index].used_ctr += 1;

	if (found_index)
		*found_index = (pg_short_t)index;
	if (types)
		*types = globals->width_tables[index].types;

	return	globals->width_tables[index].positions;
}


/* pgFreeCharLocs forces the width_table charloc_index to be rendered invalid. */

PG_PASCAL (void) pgFreeCharLocs(paige_rec_ptr pg, pg_short_t charloc_index)
{
	pg_globals_ptr		globals = pg->globals;

	globals->width_tables[charloc_index].offset = -1;
	globals->width_tables[charloc_index].mem_id = 0;
	globals->width_tables[charloc_index].used_ctr = 0;
	
	if (charloc_index > ACCEPTABLE_WIDTH_QTY) {
		
		SetMemorySize(globals->width_tables[charloc_index].positions, 0);
		SetMemorySize(globals->width_tables[charloc_index].types, 0);
		ForceMinimumMemorySize(globals->width_tables[charloc_index].positions);
		ForceMinimumMemorySize(globals->width_tables[charloc_index].types);
	}
}


/* pgReleaseCharLocs locates locs_ref and decrements its usage counter. */

PG_PASCAL (void) pgReleaseCharLocs (paige_rec_ptr pg, memory_ref locs_ref)
{
	pg_globals_ptr		globals;
	short				index;
	
	globals = pg->globals;

	for (index = 0; index < WIDTH_QTY; ++index)
		if (globals->width_tables[index].positions == locs_ref) {
			
			if ((globals->width_tables[index].used_ctr -= 1) == 0)
				if (index > ACCEPTABLE_WIDTH_QTY)
					pgFreeCharLocs(pg, index);
			break;
		}
}


/* pgInvalTextBlock tries to tag individual line records as "dirty" so I won't
necessarily need to paginate the whole block. Upon entry, block is the textblock
in question, insert_position is the (absolute) insertion position and ins_amount
the amount that has been inserted (if postitive) or deleted (if negative).
The function result is the lowest offset that got tagged. */

PG_PASCAL (long) pgInvalTextBlock (paige_rec_ptr pg, text_block_ptr block,
			long insert_position, long ins_amount)
{
	register point_start_ptr		starts;
	point_start_ptr					first_start;
	long							begin_offset, ending_offset;
	pg_short_t						wanted_offset, lowest_offset;

#ifdef MAC_PLATFORM
#pragma unused (pg)
#endif

	if (!(block->flags & SOME_LINES_GOOD) || (block->flags & LINES_PURGED))
		return	block->begin;

	begin_offset = insert_position - block->begin;
	ending_offset = begin_offset + ins_amount;
	
	if (ins_amount < 0) {
		long		swap;
		
		swap = begin_offset;
		begin_offset = ending_offset;
		ending_offset = swap;
	}

	wanted_offset = (pg_short_t)begin_offset;

	starts = first_start = UseMemory(block->lines);
	block->flags |= (NEEDS_CALC | NEEDS_PARNUMS);
	
	while ((starts[1].offset < wanted_offset) && (starts[1].flags != TERMINATOR_BITS))
		++starts;
	
	if (starts[1].offset == wanted_offset)
		if (starts[1].flags != TERMINATOR_BITS)
			++starts;

	while (!(starts->flags & NEW_LINE_BIT) && starts->offset)
		--starts;

	wanted_offset = (pg_short_t)ending_offset;
	
	while ((starts[1].offset < (pg_short_t)ending_offset) && (starts[1].flags != TERMINATOR_BITS)) {
		
		starts->flags &= (~LINE_GOOD_BIT);
		++starts;
	}

	if ((ins_amount < 0) && (starts[1].offset == (pg_short_t)ending_offset)
		&& (starts->flags & (PAR_BREAK_BIT | SOFT_PAR_BIT))) {
		
		while (starts[1].flags != TERMINATOR_BITS) {
			
			starts->flags &= (~LINE_GOOD_BIT);   /* No lines good through end */
			++starts;
		}
	}
	else {
	
		for (;;) {
			
			starts->flags &= (~LINE_GOOD_BIT);
			
			if ((starts->flags == TERMINATOR_BITS) || (starts->flags & LINE_BREAK_BIT))
				break;
			
			++starts;
		}
	}

	ending_offset = starts->offset;
	ending_offset += block->begin;

	for (starts = first_start; (starts->flags & LINE_GOOD_BIT); ++starts)
		if (starts->flags == TERMINATOR_BITS) {
			
			--starts;
			break;
		}
	
	lowest_offset = starts->offset;

	if (!lowest_offset && ending_offset >= block->end)
		block->flags &= (~SOME_LINES_GOOD);

	UnuseMemory(block->lines);

	return	block->begin + lowest_offset;
}


/* pgInvalCharLocs forces all current char widths to be invalid. */

PG_PASCAL (void) pgInvalCharLocs (paige_rec_ptr pg)
{
	pg_globals_ptr		globals;
	pg_short_t			index;

	globals = pg->globals;

	for (index = 0; index < WIDTH_QTY; ++index)
		if (globals->width_tables[index].mem_id == pg->mem_id)
			pgFreeCharLocs(pg, index);
}


/* pgGetLineRealDescent returns the actual line descent of the line containing arbitrary_start.
This can be different than the point_start baseline if, for instance, extra leading or line
spacing has been applied. */

PG_PASCAL (long) pgGetLineRealDescent (text_block_ptr block,
		point_start_ptr arbitrary_start, style_walk_ptr walker)
{
	register point_start_ptr	starts;
	long						global_offset, original_walker;
	long						result, total_descent;
	
	starts = arbitrary_start;
	
	while (starts->offset) {
		
		if (starts->flags & NEW_LINE_BIT)
			break;
		
		--starts;
	}
	
	global_offset = starts->offset;
	global_offset += block->begin;
	original_walker = walker->current_offset;
	pgSetWalkStyle(walker, global_offset);
	
	result = 0;
	
	while (starts->flags != TERMINATOR_BITS) {
		
		total_descent = walker->cur_style->descent + walker->cur_style->leading;

		if (total_descent > result)
			result = total_descent;

		if (starts->flags & LINE_BREAK_BIT)
			break;
		
		pgWalkStyle(walker, (long)(starts[1].offset - starts->offset));
		++starts;
	}
	
	pgSetWalkStyle(walker, original_walker);

	return	result;
}



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


/* measure_hidden_text is called when "invisible" char widths are to be measured.
This function fills *positions with all the same value, and if chartypes is
non-null it fills in with zeros. */

static void measure_hidden_text (long PG_FAR *positions, short PG_FAR *chartypes,
		long measure_size)
{
	register long PG_FAR		*measure_ptr;
	register long				measure_val;
	register pg_short_t			measure_qty;
	
	measure_ptr = positions;
	measure_val = *measure_ptr;

	for (measure_qty = (pg_short_t)(measure_size + 1); measure_qty; --measure_qty)
		*measure_ptr++ = measure_val;
	
	if (chartypes)
		pgFillBlock(chartypes, measure_size * sizeof(short), 0);
}


/* maximum_measure_size returns the maximum safe quantity of chars to measure at once. */

static pg_short_t maximum_measure_size (style_info_ptr measure_style)
{
	long			point_size, suggested_max;
	
	point_size = measure_style->point + measure_style->char_extra;
	
	if (((point_size >>= 16) - 2) < 1)
		point_size = 10;

	suggested_max = MAX_ACCUMULATED_WIDTHS;
	suggested_max /= point_size;
	
	if (suggested_max > TEXT_PARTITION_SIZE)
		return	(pg_short_t)TEXT_PARTITION_SIZE;
	
	return	(pg_short_t)suggested_max;
}


#ifdef PG_SUBSETS

/* next_subref_offset returns the next subref embedded char, if any. */

static long next_subref_offset (pg_char_ptr text, long remaining_length)
{
	register pg_char_ptr			text_ptr;
	register long					ctr;
	
	ctr = 0;
	text_ptr = text;
	
	if (*text_ptr == SUBREF_CHAR)
		return	remaining_length;

	while (ctr < remaining_length) {
		
		if (*text_ptr++ == SUBREF_CHAR)
			break;
		
		++ctr;
	}
	
	return	ctr;
}


/* measure_subref_char obtains the width measurement for the subref located at subref_index
in the block. */

static long measure_subref_char (paige_rec_ptr pg, text_block_ptr block, short subref_index)
{
	pg_subref_ptr				subref_list;
	paige_sub_ptr				sub_ptr;
	text_block_ptr				subref_block;
	long						width;

	subref_list = UseMemoryRecord(block->subref_list, subref_index, 0, TRUE);
	sub_ptr = UseMemory(*subref_list);
	sub_ptr += sub_ptr->alt_index;
	subref_block = UseMemory(sub_ptr->t_blocks);
	
	if (subref_block->flags & NEEDS_CALC) {
		
		pgUseSubRef(pg, *subref_list, 0, NULL, NULL);
		pgPaginateBlock(pg, UseMemory(pg->t_blocks), NULL, FALSE);
		UnuseMemory(pg->t_blocks);
		pgUnuseSubRef(pg);
	}
	
	width = sub_ptr->subset_bounds.bot_right.h - sub_ptr->subset_bounds.top_left.h;
	
	if (width < sub_ptr->minimum_width)
		width = sub_ptr->minimum_width;

	UnuseMemory(sub_ptr->t_blocks);
	UnuseMemory(*subref_list);
	UnuseMemory(block->subref_list);
	
	return		width;
}

#endif
// End above is for PG_SUBSETS

