# Output module
# =============

# Exported global constants:
#
#	$OUMaxLineLength	-- Desired output line length
#	$OUIndentIncrement	-- spaces per indentation level
#
#
# Exported global variable:
#
#	$OUIndentLevel		-- current indentation level
#
#
# Exported Routines:
#
#	&OUInit		-- Initializes the module
#
#	&OUAppendString	-- Normally appends a string to the output;
#			   splitting and printing part of the output
#			   line if its length exceeds the target length.
#			   When &OUStartPre has been called, the
#			   argument string is output directly.
#	&OUAppendSpace	-- Normally appends one space to the output,
#			   unless the output line is empty or
#			   already has a space as the last character.
#			   When &OUStartPre has been called, the
#			   argument string (space characters) is
#			   output directly.
#	&OUFlushOutput	-- Forces the current line to be
#			   printed, even if not full.
#	&OUStartPre	-- Causes &OUAppendString and &OUAppendSpace
#			   to change their behaviour while a <PRE>
#			   tag is in effect.
#	&OUEndPre	-- Undoes the effect of &OUStartPre.



# Module variable:
#	$ou_buffer	-- output buffer.
#	$ou_inPre	-- flag to indicate if <PRE>
#			   processing is in effect.



# OUInit initializes the constants and variables owned
# by this module.
sub OUInit {
    $OUMaxLineLength = 72;
    $OUIndentIncrement = 3;
    $OUIndentLevel = 0;
    $ou_buffer = '';
    $ou_inPre = 0;
}


# ou_SpecialChars maps text containing special characters.
# Input:	argument is text to be output, but possibly including
#		HTML escape codes such as &lt;
# Result:	the text with the escape codes translated back to the
#		characters they represent
sub ou_SpecialChars {
    local($x) = @_;
    $x =~ s/&amp;/&/g;		# ampersand
    $x =~ s/&lt;/</g;		# left angle bracket
    $x =~ s/&gt;/>/g;		# right angle bracket
    $x =~ s/&nbsp;/ /g;		# non-breaking space
    $x =~ s/&[a-zA-Z]+;/?/g;	# all others become `?'
    $x;
}


# Computes the number of spaces to indent an output line
# based on the current indentation level.
#
# The result is computed as
#	max($OUIndentLevel*$OUIndentIncrement, $OUMaxLineLength/2)
sub ou_IndentAmount {
    local($i);
    $i = $OUIndentLevel * $OUIndentIncrement;
    if (($i+$i) > $OUMaxLineLength) {
	$i = $OUMaxLineLength / 2;
    }
    $i;
}


# OUAppendString normally appends a character string to an output
# line that is being built in a buffer.  After appending the string,
# OUAppendString tests to see if the buffer should be printed.
#
# OUAppendString operates as follows.
#
# If <PRE> is not in effect then...
#    If the string in the buffer exceeds the desired maximum length
#    of output lines, the initial portion of the string is printed
#    and the remainder left in the buffer (but prefixed by spaces
#    to give the current indentation).
#    The line is split, if possible, at the rightmost space that
#    occurs before the desired line length.  If the buffer does not
#    contain such a space character, the line is simply split at the
#    maximum line length position.
#
# Exceptionally, if a <PRE> construct is bein processed (as indicated
# by a call to OUStartPre), then the argument string is printed
# immediately.
#
# In either case, any special characters represented by the HTML
# escape codes (e.g. &lt;) are translated to the characters that
# they represent.

sub OUAppendString {
    local($chunk) = @_;
    local($i);

    if ($DEBUG & 2) {
	print "* Appending string '", $chunk, "'\n";
    }
    $chunk = &ou_SpecialChars($chunk);
    if ($ou_inPre != 0) {
	print $chunk;
    } else {
	$ou_buffer .= $chunk;
    }
    while(length($ou_buffer) > $OUMaxLineLength) {
	# We output as much of the line as possible
	# Begin by finding the rightmost gap between words ...
	for( $i = $OUMaxLineLength-1;  $i > 0;  $i-- ) {
	    if (substr($ou_buffer,$i,1) eq ' ') {
	        last;
	    }
	}
#print "** long line = '",$ou_buffer,"'\n";
#print "** line length =",length($ou_buffer),", i = ",$i,"\n";
	if ($i <= &ou_IndentAmount()) {
	    # No suitable gap was found, just split the line
	    # in the middle of a word.
	    print substr($ou_buffer,0,$OUMaxLineLength),"\n";
	    $i = $OUMaxLineLength - 1;
	} else {
	    # Output line up to the word gap
	    print substr($ou_buffer,0,$i),"\n";
	}
	# And leave unprinted text for next line of output
	$ou_buffer = (' ' x &ou_IndentAmount()) .
				substr($ou_buffer,$i+1);
    }
}


# OUAppendSpace appends space to the output line.
#
# Input arg: a string of whitespace characters.
#
# If a <PRE> construct is *not* being processed, a single space
# is appended to the output buffer (and that action is suppressed
# if the buffer is empty or if the last character in the buffer
# is already a space).
# If a <PRE> construct is being processed, the argument string is
# output unchanged.
sub OUAppendSpace {
    local($string) = @_;

    if ($ou_inPre != 0) {
	OUAppendString($string);
    } elsif ($ou_buffer ne '' &&
	    substr($ou_buffer,length($ou_buffer)-1,1) ne ' ') {
	OUAppendString(' ');
    }
}


# OUFlushOutput forces immediate printing of the output buffer,
# even if the buffer is empty.
# Subsequently, the buffer is re-initialized to hold spaces
# needed for the current indentation of output.
sub OUFlushOutput {
    print $ou_buffer, "\n";
    $ou_buffer = ' ' x &ou_IndentAmount();
}


# &OUStartPre changes the mode of &OUAppendString and
# &OUAppendSpace so that lines are not re-formatted.
sub OUStartPre {
    $ou_inPre = 1;
}


# &OUEndPre reverses the effect of &OUStartPre
sub OUEndPre {
    $ou_inPre = 0;
}


1;
