# $Id: tolatex.pl,v 1.1.1.2 1998/03/22 09:39:20 valerio Exp valerio $
$VERSION{__FILE__} = '$Revision: 1.1.1.2 $';
#
# >>Title::     LaTeX Format Driver
#
# >>Copyright::
# Copyright (c) 1997, Ian Clatworthy (ianc@mincom.com).
# You may distribute under the terms specified in the LICENSE file.
#
# >>History::
# -----------------------------------------------------------------------
# Date      Who     Change
# 30-Oct-98 valerio First Beta release.
# 02-Apr-98 valerio Fixed List closing before Tables.
# 14-Mar-98 valerio Wrote List code.
# 20-Nov-97 valerio Initial rewriting for full use of LaTeX. 
# 12-Aug-97 ianc    Initial writing for Apache Documentation Project
# 14-May-96 ianc    SDF 2.000
# -----------------------------------------------------------------------
#
# >>Purpose::
# This library provides an [[SDF_DRIVER]] which generates
# LaTeX files.
#
# >>Description::
#
# >>Limitations::
#
# >>Resources::
#
# >>Implementation::
#

##### Constants #####

# Default right margin
$_LATEX_DEFAULT_MARGIN = 70;

# Mapping of characters
%_LATEX_CHAR = (
    'backslash'.    '\\',
    'bullet',       '$\bullet$',
    'c',            '\copyright ',
    'cent',         'c',
    'dagger',       '\dagger',
    'doubledagger', '\ddagger',
    'emdash',       '--',
    'endash',       '---',
    'emspace',      '\ \ ',
    'enspace',      '\ ',
    'lbrace',       '{',
    'lbracket',     '[',
    'nbdash',       '-',
    'nbspace',      ' ',
    'nl',           "\n",
    'pound',        '\pounds ',
    'r',            '(r)',
    'rbrace',       '}',
    'rbracket',     ']',
    'tab',          "\t",
    'tm',           '$^{TM}$',
    'yen',          'y',

    # From pod2latex ...

    'amp'	=>	'\\&',	#   ampersand
    'lt'	=>	'$<$',	#   left chevron, less-than
    'gt'	=>	'$>$',	#   right chevron, greater-than
    'quot'	=>	'"',	#   double quote

    "Aacute"	=>	"\\'{A}",	#   capital A, acute accent
    "aacute"	=>	"\\'{a}",	#   small a, acute accent
    "Acirc"	=>	"\\^{A}",	#   capital A, circumflex accent
    "acirc"	=>	"\\^{a}",	#   small a, circumflex accent
    "AElig"	=>	'\\AE',		#   capital AE diphthong (ligature)
    "aelig"	=>	'\\ae',		#   small ae diphthong (ligature)
    "Agrave"	=>	"\\`{A}",	#   capital A, grave accent
    "agrave"	=>	"\\`{a}",	#   small a, grave accent
    "Aring"	=>	'\\u{A}',	#   capital A, ring
    "aring"	=>	'\\u{a}',	#   small a, ring
    "Atilde"	=>	'\\~{A}',	#   capital A, tilde
    "atilde"	=>	'\\~{a}',	#   small a, tilde
    "Auml"	=>	'\\"{A}',	#   capital A, dieresis or umlaut mark
    "auml"	=>	'\\"{a}',	#   small a, dieresis or umlaut mark
    "Ccedil"	=>	'\\c{C}',	#   capital C, cedilla
    "ccedil"	=>	'\\c{c}',	#   small c, cedilla
    "Eacute"	=>	"\\'{E}",	#   capital E, acute accent
    "eacute"	=>	"\\'{e}",	#   small e, acute accent
    "Ecirc"	=>	"\\^{E}",	#   capital E, circumflex accent
    "ecirc"	=>	"\\^{e}",	#   small e, circumflex accent
    "Egrave"	=>	"\\`{E}",	#   capital E, grave accent
    "egrave"	=>	"\\`{e}",	#   small e, grave accent
    "ETH"	=>	'\\OE',		#   capital Eth, Icelandic
    "eth"	=>	'\\oe',		#   small eth, Icelandic
    "Euml"	=>	'\\"{E}',	#   capital E, dieresis or umlaut mark
    "euml"	=>	'\\"{e}',	#   small e, dieresis or umlaut mark
    "Iacute"	=>	"\\'{I}",	#   capital I, acute accent
    "iacute"	=>	"\\'{i}",	#   small i, acute accent
    "Icirc"	=>	"\\^{I}",	#   capital I, circumflex accent
    "icirc"	=>	"\\^{i}",	#   small i, circumflex accent
    "Igrave"	=>	"\\`{I}",	#   capital I, grave accent
    "igrave"	=>	"\\`{i}",	#   small i, grave accent
    "Iuml"	=>	'\\"{I}',	#   capital I, dieresis or umlaut mark
    "iuml"	=>	'\\"{i}',	#   small i, dieresis or umlaut mark
    "Ntilde"	=>	'\\~{N}',	#   capital N, tilde
    "ntilde"	=>	'\\~{n}',	#   small n, tilde
    "Oacute"	=>	"\\'{O}",	#   capital O, acute accent
    "oacute"	=>	"\\'{o}",	#   small o, acute accent
    "Ocirc"	=>	"\\^{O}",	#   capital O, circumflex accent
    "ocirc"	=>	"\\^{o}",	#   small o, circumflex accent
    "Ograve"	=>	"\\`{O}",	#   capital O, grave accent
    "ograve"	=>	"\\`{o}",	#   small o, grave accent
    "Oslash"	=>	"\\O",		#   capital O, slash
    "oslash"	=>	"\\o",		#   small o, slash
    "Otilde"	=>	"\\~{O}",	#   capital O, tilde
    "otilde"	=>	"\\~{o}",	#   small o, tilde
    "Ouml"	=>	'\\"{O}',	#   capital O, dieresis or umlaut mark
    "ouml"	=>	'\\"{o}',	#   small o, dieresis or umlaut mark
    "szlig"	=>	'\\ss',		#   small sharp s, German (sz ligature)
    "THORN"	=>	'\\L',		#   capital THORN, Icelandic
    "thorn"	=>	'\\l',,		#   small thorn, Icelandic
    "Uacute"	=>	"\\'{U}",	#   capital U, acute accent
    "uacute"	=>	"\\'{u}",	#   small u, acute accent
    "Ucirc"	=>	"\\^{U}",	#   capital U, circumflex accent
    "ucirc"	=>	"\\^{u}",	#   small u, circumflex accent
    "Ugrave"	=>	"\\`{U}",	#   capital U, grave accent
    "ugrave"	=>	"\\`{u}",	#   small u, grave accent
    "Uuml"	=>	'\\"{U}',	#   capital U, dieresis or umlaut mark
    "uuml"	=>	'\\"{u}',	#   small u, dieresis or umlaut mark
    "Yacute"	=>	"\\'{Y}",	#   capital Y, acute accent
    "yacute"	=>	"\\'{y}",	#   small y, acute accent
    "yuml"	=>	'\\"{y}',	#   small y, dieresis or umlaut mark
);

# Directive mapping table
%_LATEX_HANDLER = (
    'tuning',           '_LatexHandlerTuning',
    'endtuning',        '_LatexHandlerEndTuning',
    'table',            '_LatexHandlerTable',
    'row',              '_LatexHandlerRow',
    'cell',             '_LatexHandlerCell',
    'endtable',         '_LatexHandlerEndTable',
    'import',           '_LatexHandlerImport',
    'inline',           '_LatexHandlerInline',
    'output',           '_LatexHandlerOutput',
    'object',           '_LatexHandlerObject',
);

# Phrase directive mapping table
%_LATEX_PHRASE_HANDLER = (
    'char',             '_LatexPhraseHandlerChar',
    'import',           '_LatexPhraseHandlerImport',
    'inline',           '_LatexPhraseHandlerInline',
    'variable',         '_LatexPhraseHandlerVariable',
);

# List states

$_LATEX_LISTLEVEL  = 0;
@_LATEX_LISTTYPES = ();

# Table states
$_LATEX_INTABLE = 1;
$_LATEX_INROW   = 2;
$_LATEX_INCELL  = 3;



##### Variables #####

# Right margin position
$_latex_margin = $SDF_USER'var{'LATEX_MARGIN'} || $_LATEX_DEFAULT_MARGIN;

# Counters for ordered lists - index is the level
@_latex_list_num = 0;

# Table states and row types
@_latex_tbl_state = ();
@_latex_row_type = ();

# Column number & starting positions for current table
$_latex_col_num = 0;
@_latex_col_posn = ();

# Current column number
$_latex_current_col = 1;

# Number of Table columns
$_latex_tab_cols = 0;

# Table column alignment
@_latex_colaligns = ();

# Location of the first line of the current row
$_latex_first_row = 0;

# The current cell text
$_latex_cell_current = '';

# The current verbatim escape char
$_latex_verbatim_open  = '';
$_latex_verbatim_close = '';

# Global in example;
$_latex_in_example = 0;

##### Routines #####

#
# >>Description::
# {{Y:LatexFormat}} is an SDF driver which outputs plain text files.
#
sub LatexFormat {
    local(*data) = @_;
    local(@result);
    local(@contents);

    # Initialise defaults
    $_latex_margin = $SDF_USER'var{'LATEX_MARGIN'} || $_LATEX_DEFAULT_MARGIN;

    # Format the paragraphs
    @contents = ();
    @result = &_LatexFormatSection(*data, *contents);

    # Turn into final form and return
    return &_LatexFinalise(*result, *contents);
}

#
# >>_Description::
# {{Y:_LatexFormatSection}} formats a set of SDF paragraphs into text.
# If a parameter is passed to contents, then that array is populated
# with a generated Table of Contents.
#
sub _LatexFormatSection {
    local(*data, *contents) = @_;
    local(@result);
    local($prev_tag, $prev_indent);
    local($para_tag, $para_text, %para_attrs);
    local($directive);


    # Process the paragraphs
    @result = ();
    $prev_tag = '';
    $prev_indent = '';
    while (($para_text, $para_tag, %para_attrs) = &SdfNextPara(*data)) {

        # handle directives
        if ($para_tag =~ /^__(\w+)$/) {
            $directive = $_LATEX_HANDLER{$1};
            if (defined &$directive) {
                &$directive(*result, $para_text, %para_attrs);
            }
            else {
                &AppMsg("warning", "ignoring internal directive '$1' in LATEX driver");
            }
            next;
        }

        # Add the paragraph
        &_LatexParaAdd(*result, $para_tag, $para_text, *para_attrs, $prev_tag,
          $prev_indent, *contents);
    }

    # Do this stuff before starting next loop iteration
    continue {
        unless ($para_tag eq 'PB') {
            $prev_tag = $para_tag;
            $prev_indent = $para_attrs{'in'};
        }
    }

    # Return result
    return @result;
}
       
#
# >>_Description::
# {{Y:_LatexParaAdd}} adds a paragraph.
#
sub _LatexParaAdd {
    local(*result, $para_tag, $para_text, *para_attrs, $prev_tag, $prev_indent, *contents) = @_;
#   local();
    local($in_example);
    local($para_fmt);
    local($para_override);
    local($para);
    local($hdg_level);
    local($toc_jump);
    local($label);

    # Set the example flag
    $in_example = $SDF_USER'parastyles_category{$para_tag} eq 'example';

    if ($in_example) {
       if ($_latex_in_example eq 0) {
          $_latex_in_example = 1;
          $para_text = "\\begin\{verbatim\}\n" . $para_text;
       }
    }
    else {
       if ($_latex_in_example eq 1) {
        $_latex_in_example = 0;
        &_LatexParaAppend(*result, "\n\\end\{verbatim\}\n");
       }
    }

    # Enumerated lists are the same as list paragraphs at the previous level
    if ($para_tag =~ /^LI(\d)$/) {
        $para_tag = $1 > 1 ? "L" . ($1 - 1) : 'N';
    }

    # Get the target format name
    $para_fmt = $SDF_USER'parastyles_to{$para_tag};
    $para_fmt = $para_tag if $para_fmt eq '';

    # Map the attributes
    &SdfAttrMap(*para_attrs, 'latex', *SDF_USER'paraattrs_to,
      *SDF_USER'paraattrs_map, *SDF_USER'paraattrs_attrs,
      $SDF_USER'parastyles_attrs{$para_tag});

    # Build the Table of Contents as we go

    if ($para_tag =~ /^[HAP](\d)$/) {
	$para_fmt = $para_tag;
    }

    # Handle lists (is this needed for text format?)
    elsif ($para_tag =~ /^(L[FUN]?)(\d)$/) {
        $para_attrs{'in'} = $2;
    }

    # Prepend the label, if any (replacing tabs with spaces)
    $label = $para_attrs{'label'};
    $label = 'Note: ' if ($para_tag eq 'Note' || $para_tag eq 'NB') &&
             $label eq '';
    $label =~ s/\\t/ /g;
    $para_text = "{{2:$label}}$para_text" if $label ne '';

    # Indent examples, if necessary
    if ($in_example) {
# do nothing
    }

    # Format the paragraph body
    if ($para_attrs{'verbatim'} || $in_example) {
        $para = $para_text;
        delete $para_attrs{'verbatim'};
    }
    else {
        $para = &_LatexParaText($para_text);
    }

    # If we're in a table, prepend the paragraph onto the current cell
    if (@_latex_tbl_state) {
        if ($para_fmt eq "Line") {
            $_latex_cell_current .= "-" x $_latex_cell_width;
            return;
        }
        $_latex_cell_current .= $para;
        return;
    }

    # Build result
    if ($para_tag eq 'PB') {
        $para = &_LatexElement($para_fmt, $para, %para_attrs);
        &_LatexParaAppend(*result, $para);
    }
    elsif ($in_example && $para_tag eq $prev_tag && !%para_attrs) {
        &_LatexParaAppend(*result, $para);
    }
    else {
        $para = &_LatexElement($para_fmt, $para, %para_attrs);
        push(@result, $para);
    }
}

#
# >>_Description::
# {{Y:_LatexParaText}} converts SDF paragraph text into LATEX.
# 
sub _LatexParaText {
    local($para_text) = @_;
    local($para);
    local($state);
    local($sect_type, $char_tag, $text, %sect_attrs);
    local($added_anchors);
    local(@char_fonts);
    local($char_font);
    local($directive);

    # Process the text
    $para = '';
    $state = 0;
    while (($sect_type, $text, $char_tag, %sect_attrs) =
      &SdfNextSection(*para_text, *state)) {

        # Build the paragraph
        if ($sect_type eq 'special') {
            $directive = $_LATEX_PHRASE_HANDLER{$char_tag};
            if (defined &$directive) {
                &$directive(*para, $text, %sect_attrs);
            }
            else {
                &AppMsg("warning", "ignoring special phrase '$1' in LATEX driver");
            }
	}
        elsif ($sect_type eq 'phrase') {
   

	    if ($char_tag eq 'L') {
                ($text, $url) = &SDF_USER'ExpandLink($text);
                $sect_attrs{'jump'} = $url;
            }

            if ($char_tag ne 'E' 
                && $SDF_USER'phrasestyles_to{$char_tag} ne 'SDF_VERBATIM') {
	      # Escape any special characters
	      $text = &_LatexEscape($text);
	    }

	  if ( $SDF_USER'phrasestyles_to{$char_tag} eq 'SDF_VERBATIM') {
	    _LatexCheckVerbatim($text);
	      $text = $_latex_verbatim_open . $text;
	  }

            # Expand non-breaking spaces, if necessary
	  if ($char_tag eq 'S') {
	    $text =~ s/ /~/g;
	  }
	  
	  
	  # Process formatting attributes
            &SdfAttrMap(*sect_attrs, 'latex', *SDF_USER'phraseattrs_to,
	       *SDF_USER'phraseattrs_map, *SDF_USER'phraseattrs_attrs,
	       $SDF_USER'phrasestyles_attrs{$char_tag});


            # Map the font
            $char_font = $SDF_USER'phrasestyles_to{$char_tag};


            # Add the text for this phrase
            push(@char_fonts, $char_font);
            if ($char_font ne '' && $char_font !~ /^SDF/) {
                $para .= "$char_font$text";
            }
            else {
                $para .= $text;
            }
        }

	  elsif ($sect_type eq 'phrase_end') {
	      $char_font = pop(@char_fonts);
	      $para .= "}}" if $char_font ne '' && $char_font !~ /^SDF/;
              if ($char_font eq 'SDF_VERBATIM') { $para .= $_latex_verbatim_close; }
	  }


        elsif ($sect_type eq 'string') {
            $text = &_LatexEscape($text);
            $para .= $text;
        }

        elsif ($sect_type eq 'phrase') {
        }

        elsif ($sect_type eq 'phrase_end') {
            # do nothing
        }

        else {
            &AppMsg("warning", "unknown section type '$sect_type' in LATEX driver");
        }
    }

    # Return result
    return $para;
}


#
# >>_Description::
# {{Y:_LatexElement}} formats a LATEX element from a
# tag, text and set of attributes.
#
sub _LatexElement {
    local($tag, $text, %attr) = @_;
    local($latex);
    local($prefix, $label);
    local($list_level, $close_list);
    local($cnt);

    # Handle page breaks
    if ($tag eq 'PB') {
        return "\\newpage";
    }

    # For examples, don't word wrap the lines
    if ($tag eq 'E') {
        $latex =  "$text";
    }

    # For lines, output a 'line'
    elsif ($tag eq 'Line') {
        $latex =  ("_" x $_latex_margin) . "\n";
    }

    # For headings, underline the text
    elsif ($tag =~ /^[HAP](\d)/) {
      while ($_LATEX_LISTLEVEL gt 0) {
	$close_list .= "\\end\{$_LATEX_LISTTYPES[$_LATEX_LISTLEVEL]\}\n";
	$_LATEX_LISTLEVEL--;
      }
        $latex = $close_list . "\n" . "$SDF_USER'parastyles_to{$tag}\{$text\}\n"; 
#	    . ($char x length("$SDF_USER'parastyles_to{$tag}\{$text\}\n")) . "\n";
    }


    # For list items, add the necessary "label"
    elsif ($tag =~ /^(L[FUN]?)(\d)$/) {
      
      $ list_level = $2;
      #>        $prefix = " " x ($2 * 5);
      #>        $label  = " " x (($2 - 1) * 5);
      
      if ($1 eq 'LU') {
	if ($_LATEX_LISTLEVEL lt $list_level) {
	  $label = "\\begin\{itemize\} \% Level $list_level\n";
	  $_LATEX_LISTLEVEL = $list_level;
	  @_LATEX_LISTTYPES[$list_level] = "itemize";
	}
	elsif ($_LATEX_LISTLEVEL gt $list_level) {
	  $label = "\\end\{$_LATEX_LISTTYPES[$_LATEX_LISTLEVEL]\}\n";
	  $_LATEX_LISTTYPES[$_LATEX_LISTLEVEL] = "";
	  $_LATEX_LISTLEVEL = $list_level;
	}
      $label .= "\\item ";
      }
        elsif ($1 eq 'L') {
	if ($_LATEX_LISTLEVEL lt $list_level) {
	  $label = "\\begin\{list\}\{\ \}\{\} \% Level $list_level\n";
	  $_LATEX_LISTLEVEL = $list_level;
	  @_LATEX_LISTTYPES[$list_level] = "list";
	}
	elsif ($_LATEX_LISTLEVEL gt $list_level) {
	  $label = "\\end\{$_LATEX_LISTTYPES[$_LATEX_LISTLEVEL]\}\n";
	  $_LATEX_LISTTYPES[$_LATEX_LISTLEVEL] = "";
	  $_LATEX_LISTLEVEL = $list_level;
	}

            $label .= "\\item ";
        }
        elsif ($1 eq 'LF') {
	if ($_LATEX_LISTLEVEL lt $list_level) {
	  $label = "\\begin\{enumerate\} \% Level $list_level\n";
	  $_LATEX_LISTLEVEL = $list_level;
	  @_LATEX_LISTTYPES[$list_level] = "enumerate";
	}
	elsif ($_LATEX_LISTLEVEL gt $list_level) {
	  $label = "\\end\{$_LATEX_LISTTYPES[$_LATEX_LISTLEVEL]\}\n";
	  $_LATEX_LISTTYPES[$_LATEX_LISTLEVEL] = "";
	  $_LATEX_LISTLEVEL = $list_level;
	}

            $label .= "\\item ";
        }
        else {
	if ($_LATEX_LISTLEVEL lt $list_level) {
	  $label = "\\begin\{enumerate\} \% Level $list_level\n";
	  $_LATEX_LISTLEVEL = $list_level;
	  @_LATEX_LISTTYPES[$list_level] = "enumerate";
	}
	elsif ($_LATEX_LISTLEVEL gt $list_level) {
	  $label = "\\end\{$_LATEX_LISTTYPES[$_LATEX_LISTLEVEL]\}\n";
	  $_LATEX_LISTTYPES[$_LATEX_LISTLEVEL] = "";
	  $_LATEX_LISTLEVEL = $list_level;
	}

            $label .= "\\item ";
        }
        $latex = &MiscTextWrap($label . $text, $_latex_margin, $prefix,
          '', 1) . "\n";
    }

    # Otherwise, format as a plain paragraph
    else {
      while ($_LATEX_LISTLEVEL gt 0) {
	$close_list .= "\\end\{$_LATEX_LISTTYPES[$_LATEX_LISTLEVEL]\}\n";
	$_LATEX_LISTLEVEL--;
      }
        $latex = &MiscTextWrap($close_list . "\n" . $text, $_latex_margin, '', '', 1) . "\n";
    }

    # Handle the top attribute
    return $para{'top'} ? "\f\n$latex" : $latex;
}

#
# >>_Description::
# {{Y:_LatexParaAppend}} merges {{para}} into the last paragraph
# in {{@result}}. Both paragraphs are assumed to be examples.
#
sub _LatexParaAppend {
    local(*result, $para) = @_;
#   local();

    $result[$#result] .= "$para\n";
}

#
# >>_Description::
# {{Y:_LatexHandlerTuning}} handles the 'tuning' directive.
#
sub _LatexHandlerTuning {
    local(*outbuffer, $style, %attr) = @_;
#   local();

    # do nothing
}

#
# >>_Description::
# {{Y:_LatexHandlerEndTuning}} handles the 'endtuning' directive.
#
sub _LatexHandlerEndTuning {
    local(*outbuffer, $style, %attr) = @_;
#   local();

    # do nothing
}

#
# >>_Description::
# {{Y:_LatexHandlerTable}} handles the 'table' directive.
#
sub _LatexHandlerTable {
    local(*outbuffer, $columns, %attr) = @_;
    local($tmp, $close_list, $coldim);
    local($tbl_title);

    $_latex_tab_cols = $columns; 
    $coldim = (1 / $columns) - 0.02;

    # Close all yet open lists
    $close_list = "";
    while ($_LATEX_LISTLEVEL gt 0) {
      $close_list .= "\\end\{$_LATEX_LISTTYPES[$_LATEX_LISTLEVEL]\}\n";
      $_LATEX_LISTLEVEL--;
    }
    push(@outbuffer, $close_list . "\n\n");
    

    # Update the state
    push(@_latex_tbl_state, $_LATEX_INTABLE);
    push(@_latex_row_type, '');

    # Calculate the column positions (rounded)

    @_latex_col_posn = &SdfColPositions($columns, $attr{'format'}, $_latex_margin);
    @_latex_colaligns = split(/,/, $attr{'colaligns'});

    # Add the title, if any
    $tbl_title = $attr{'title'};
    if ($tbl_title ne '') {
        push(@outbuffer, "\\begin\{table\}\[H\]\n\\caption\[$tbl_title\]\{$tbl_title\}\n");
    }
    else {
        push(@outbuffer, "\\begin\{table\}\[H\]\n");
      }
    push(@outbuffer, "\\begin\{center\}\n\\begin\{tabular\}");

    $outbuffer[$#outbuffer] .= "\{";
    if ($attr{'style'} ne 'plain' &&  $attr{'style'} ne 'rows') {
      $outbuffer[$#outbuffer] .= "|";
    }
    foreach $tmp (1..$_latex_tab_cols) {
      $outbuffer[$#outbuffer] .= 
	"c";
#	"\@\{\\hspace\{.01\\linewidth\}\}p\{$coldim\\linewidth\}\@\{\\hspace\{.01\\linewidth\}\}";
      if ($attr{'style'} ne 'plain' &&  $attr{'style'} ne 'rows') {
	$outbuffer[$#outbuffer] .= "|";
      }
    }
    $outbuffer[$#outbuffer] .= "\}";

    if ($attr{'style'} ne 'plain') {
      push(@outbuffer, "\\hline");
    }
    push(@outbuffer,"%% -> attribute format = $attr{'format'}");
    push(@outbuffer,"%% -> attribute colaligns = $attr{'colaligns'}");
}

#
# >>_Description::
# {{Y:_LatexHandlerRow}} handles the 'row' directive.
#
sub _LatexHandlerRow {
    local(*outbuffer, $text, %attr) = @_;
#   local();
    local($current_state);

    # Finalise the previous row, if any
    $current_state = $_latex_tbl_state[$#_latex_tbl_state];
    unless ($current_state eq $_LATEX_INTABLE) {
        &_LatexFinalisePrevRow(*outbuffer, $_latex_row_type[$#_latex_row_type]);
    }

    # Start the new row
    push(@outbuffer, "");
    $_latex_col_num = 0;
    $_latex_first_row = $#outbuffer;

    # Update the state
    $_latex_tbl_state[$#_latex_tbl_state] = $_LATEX_INROW;
    $_latex_row_type[$#_latex_row_type] = $text;
#    push(@outbuffer,"%% -> Starting row type $text\n");
}

#
# >>_Description::
# {{Y:_LatexHandlerCell}} handles the 'cell' directive.
#
sub _LatexHandlerCell {
    local(*outbuffer, $text, %attr) = @_;
#   local();
    local($state);
    local($padding);
    local($tmp);

    # Finalise the old cell, if any
    $state = $_latex_tbl_state[$#_latex_tbl_state];
    if ($state eq $_LATEX_INCELL) {
        &_LatexFinishCell(*outbuffer);
#        if ($_latex_col_num > 0) {
#            foreach $tmp ($_latex_first_row .. $#outbuffer) {
#                $padding = $_latex_col_posn[$_latex_col_num - 1] -
#                  length($outbuffer[$tmp]);
#                $padding = 1 if ($padding <= 0);
#                $outbuffer[$tmp] .= " " x $padding;
#            }
#        }
      }

    # Update the state
    $_latex_tbl_state[$#_latex_tbl_state] = $_LATEX_INCELL;
    $_latex_cell_margin = ($_latex_col_num>0?$_latex_col_posn[$_latex_col_num-1]:0);
    $_latex_col_num+=$attr{cols};
    if ($_latex_col_num > $#_latex_col_posn + 1) {
        $_latex_cell_width = $_latex_margin - $_latex_cell_margin - 1;
    } else {
        $_latex_cell_width = $_latex_col_posn[$_latex_col_num-1] -
             $_latex_cell_margin - 1;
    }
    %_latex_cell_attrs = %attr;
    $_latex_cell_current = "";
}

#
# >>_Description::
# {{Y:_LatexFinishCell}} adds the cell text to the output
#
sub _LatexFinishCell {
    local(*outbuffer) = @_;
    $_latex_cell_current =~ s/\s+/ /g;
    local @lines = split(/\n/,
        &MiscTextWrap($_latex_cell_current, $_latex_cell_width,"",'',1));
    local $tmp;
#    foreach $tmp ($#outbuffer+1..$_latex_first_row+$#lines) {
#        push(@outbuffer, " " x $_latex_cell_margin);
#    }

    if ($#lines == 0) {
	if ($_latex_cell_attrs{'align'} eq "Center") {
	  $outbuffer[$#outbuffer] .=
	    "\{\\centering $lines[$#lines]\}";
	} elsif ($_latex_cell_attrs{'align'} eq "Right") {
	  $outbuffer[$#outbuffer] .=
	    "\{\\raggedright  $lines[$#lines]\}";
	} else {
	  $outbuffer[$#outbuffer] .=
	    "\{\\raggedleft  $lines[$#lines]\}";
	}
	if ($_latex_col_num < $_latex_tab_cols) {
	  $outbuffer[$#outbuffer] .= " & ";
	}
    } else {
      if ($_latex_cell_attrs{'align'} eq "Center") {
	$outbuffer[$#outbuffer] .=
	  "\{\\centering ";
      } elsif ($_latex_cell_attrs{'align'} eq "Right") {
	$outbuffer[$#outbuffer] .=
	  "\{\\raggedright ";
      } else {
	  $outbuffer[$#outbuffer] .=
	    "\{\\raggedleft ";
	}
      foreach $tmp (0..$#lines) {
	$outbuffer[$#outbuffer] .= " $lines[$tmp] ";
      }
      $outbuffer[$#outbuffer] .= "\}";
      if ( $_latex_col_num < $_latex_tab_cols ) {
	$outbuffer[$#outbuffer] .= " \& ";
      }
    }
    $_latex_cell_current = "";
    $_latex_current_col++;
}

#
# >>_Description::
# {{Y:_LatexHandlerEndTable}} handles the 'endtable' directive.
#
sub _LatexHandlerEndTable {
    local(*outbuffer, $text, %attr) = @_;
#   local();
    local($state);
    local($row_type);

    # Finalise the table
    $state = pop(@_latex_tbl_state);
    $row_type = pop(@_latex_row_type);
    if ($state eq $_LATEX_INCELL) {
        &_LatexFinalisePrevRow(*outbuffer, $row_type);
        $outbuffer[$#outbuffer] .= "\n";
    }
    elsif ($state eq $_LATEX_INROW) {
        &_LatexFinalisePrevRow(*outbuffer, $row_type);
        $outbuffer[$#outbuffer] .= "\n";
    }
    if ($attr{'style'} ne 'plain') {
      push(@outbuffer,"\\hline\n");
    }
    push(@outbuffer, "\\end\{tabular\}\n\\end\{center\}\n\\end\{table\}\n");
    
}

#
# >>_Description::
# {{Y:_LatexFinalisePrevRow}} finalises the previous row, if any.
#
sub _LatexFinalisePrevRow {
    local(*outbuffer, $row_type) = @_;
#   local();
    local($line_row);
    local($prefix);
    local($tmp);
    local($_);

    &_LatexFinishCell(*outbuffer);
    foreach $tmp ($_latex_first_row..$#outbuffer) {
        $outbuffer[$tmp] =~ s/ *(\n*)$/$1/;
    }

    # If the last row was a heading, underline it
    $outbuffer[$#outbuffer] .= "\\\\";
    if ($row_type ne 'Heading') {
#      while (< $_latex_col_num) {
#	     $outbuffer[$#outbuffer] .= " \& \{\}";
#	     $_latex_col_num++;
#	   }   
    } else {
      $line_row = "";
      if ($attr{'style'} ne 'plain') {
	$line_row = "\\hline";
	if ($attr{'style'} eq 'grid') {
	  $line_row .= "\\hline";
	}
      }
      push(@outbuffer, $line_row);
    }
}

#
# >>_Description::
# {{Y:_LatexHandlerImport}} handles the import directive.
#
sub _LatexHandlerImport {
    local(*outbuffer, $filepath, %attr) = @_;
#   local();
    local($para);

    # Build the result
    &_LatexPhraseHandlerImport(*para, $filepath, %attr);
    push(@outbuffer, "$para\n");
}

#
# >>_Description::
# {{Y:_LatexHandlerInline}} handles the inline directive.
#
sub _LatexHandlerInline {
    local(*outbuffer, $text, %attr) = @_;
#   local();

    # Check we can handle this format
    my $target = $attr{'target'};
    return unless $target eq 'latex' || $target eq 'text';

    # Build the result
    push(@outbuffer, $text);
}

#
# >>_Description::
# {{Y:_LatexHandlerOutput}} handles the 'output' directive.
#
sub _LatexHandlerOutput {
    local(*outbuffer, $text, %attrs) = @_;
#   local();

    # do nothing
}

#
# >>_Description::
# {{Y:_LatexHandlerObject}} handles the 'object' directive.
#
sub _LatexHandlerObject {
    local(*outbuffer, $text, %attrs) = @_;
#   local();

    # Save the margin, if necessary
    if ($text eq 'Variable' && $attrs{'Name'} eq 'LATEX_MARGIN') {
        $_latex_margin = $attrs{'value'};
    }
}

#
# >>_Description::
# {{Y:_LatexPhraseHandlerChar}} handles the 'char' phrase directive.
#
sub _LatexPhraseHandlerChar {
    local(*para, $text, %attr) = @_;
#   local();

    # Map those we know about it
    if (defined($_LATEX_CHAR{$text})) {
        $para .= $_LATEX_CHAR{$text};
    }
    else {
        $para .= $text;
    }
}

#
# >>_Description::
# {{Y:_LatexPhraseHandlerImport}} handles the 'import' phrase directive.
#
sub _LatexPhraseHandlerImport {
    local(*para, $filepath, %attr) = @_;
#   local();
    local($name, $value);

    $para .= "** Unable to import figure $filepath **";
}

#
# >>_Description::
# {{Y:_LatexPhraseHandlerInline}} handles the 'inline' phrase directive.
#
sub _LatexPhraseHandlerInline {
    local(*para, $text, %attr) = @_;
#   local();

    # Build the result
    $para .= $text;
}

#
# >>_Description::
# {{Y:_LatexPhraseHandlerVariable}} handles the 'variable' phrase directive.
#
sub _LatexPhraseHandlerVariable {
    local(*para, $text, %attr) = @_;
#   local();

    # do nothing
}

#
# >>_Description::
# {{Y:_LatexFinalise}} generates the final LaTeX file.
#
sub _LatexFinalise {
    local(*body, *contents) = @_;
#   local(@result);
    local($title, @sdf_title, @title);
    local($version, @head);
    local($body);
    local($macro, @header, @footer);
    local(@dummy);
    local($rec, @html_contents, $toc_posn);

    # Convert the title
    $title = $SDF_USER'var{'DOC_NAME'};
    $author = $SDF_USER'var{'DOC_AUTHOR'};
    $date   = $SDF_USER'var{'DOC_DATE'};

    # Build the HEAD element (and append BODY opening)
    $version = $SDF_USER'var{'SDF_VERSION'};
    @head = (
        '%%% This file was generated using SDF $version by',
        '%%% Ian Clatworthy (ianc@mincom.com) and the 2latex_ driver', 
        '%%% $Id: tolatex.pl,v 1.1.1.2 1998/03/22 09:39:20 valerio Exp valerio $',
        '%%% written by Valerio Aimale <valerio@svpop.com.dist.unige.it>.',
        '%%% SDF is freely available from http://www.mincom.com/mtr/sdf',
        ''
    );
    push(@head, '\author{'. $author . '}') if $author;
    push(@head, '\date{'. $date . '}') if $date;
    push(@head, '\title{'. $title . '}') if $title;
    push(@head, '','');
    push(@head, '\begin{document}', '');
    push(@head, '\maketitle') if $title;




    # If requested, provide a Table of Contents
    if (@contents) {
    push(@head, '\tableofcontents');
    }

    # Close eventually open lists
      while ($_LATEX_LISTLEVEL gt 0) {
	push(@body, '',  "\\end\{$_LATEX_LISTTYPES[$_LATEX_LISTLEVEL]\}\n");
	$_LATEX_LISTLEVEL--;
      }
    # Return result
    push(@body, '', '\end{document}');
    return (@head, @body);
}

#
# >>_Description::
# {{Y:_LatexEscape}} escapes special symbols in LaTeX text.
# 
sub _LatexEscape {
    local($text) = @_;
#   local($result);
    local($old_match_flag);

    # Enable multi-line matching
    $old_match_flag = $*;
    $* = 1;

    # Escape the special symbols. Note that it isn't exactly clear
    # from the SGML-Tools and/or QWERTZ DTD documentation as to
    # whether all of these are mandatory, but they shouldn't cause
    # any harm (I hope!)
    $text =~ s/\\/\\verb+\\+/g;
    $text =~ s/\&/\\\&/g;
    $text =~ s/\</\$<\$/g;
    $text =~ s/\>/\$>\$/g;
    $text =~ s/\"/"/g;
    $text =~ s/\$/\\\$/g;
    $text =~ s/\~/\\verb+~+/g;
    $text =~ s/\^/\\verb+^+/g;
    $text =~ s/\#/\\\#/g;
    $text =~ s/\%/\\\%/g;
    $text =~ s/_/\\\_/g;
    $text =~ s/\|/\|/g;
    $text =~ s/\[/\$[\$/g;
    $text =~ s/\]/\$]\$/g;
    $text =~ s/\{/\\\{/g;
    $text =~ s/\}/\\\}/g;

    # Reset multi-line matching flag
    $* = $old_match_flag;

    # Return result
    $text;
}

#
# >>_Description::
# {{Y:_LatexCheckVerbatim}} checks for un unsed che in the phrase
#
sub _LatexCheckVerbatim {
  local($text) = @_;
  local($match);
  #   local($_i);
  for (33..127) {
    $match = chr;
    if ($text =~ /$match/) {
      ;
    }
    else {
      $_latex_verbatim_open  = '\verb' . $match;
      $_latex_verbatim_close = $match;
      return;
    }
  }
      $_latex_verbatim_open  = '\begin{verbatim}';
      $_latex_verbatim_close = '\end{verbatim}';
      return;
}


# package return value
1;
