# jtagconvert.tcl - procedures for converting multi-font text
#
###############################################################################
#  Copyright 1992-2001 by Jay Sekora.  This file may be freely distributed,   #
#  modified or unmodified, for any purpose, provided that this copyright      #
#  notice is retained verbatim in all copies and no attempt is made to        #
#  obscure the authorship of this file.  If you distribute any modified       #
#  versions, I ask, but do not require, that you clearly mark any changes     #
#  you make as such and that you provide your users with instructions for     #
#  getting the original sources.                                              #
###############################################################################

# TO DO:
#   html conversion needs to handle single newline (?<br>?)
#   bold italic for html
#   ISO entities for html
#   extend to underlining and colours
#   generic "get tag information for chunk" routine, eg
#     "::aq::tc::get_status var" might set var(font) to bolditalic,
#     var(anchor) to "Table of Contents" and var(link) to
#     "foo/bar#intro".
#   rewrite conversion to use above routine, and switch on its
#     results.
#   rewrite TeX to use {...} sensibly.
#   handle returns better, esp if they're tagged.


package provide aq 0.1

# following has side-effect of making sure ::aq namespace is defined
namespace eval ::aq {
  namespace export {[a-z]*}
}

# following also makes sure ::aq::tc namespace is defined
namespace eval ::aq::tc {
  namespace export {[a-z]*}
}



######################################################################
######################################################################
### FOLLOWING SHOULD HAVE A PREFERENCES PANEL:
######################################################################
######################################################################

catch {
  global J_TAGCONVERT
  ::aq::default J_TAGCONVERT(lib) [file join $aq_library jtagconvert]
  ::aq::default J_TAGCONVERT(tex,prologue) prologue.tex
  ::aq::default J_TAGCONVERT(tex,epilogue) epilogue.tex
  ::aq::default J_TAGCONVERT(ps,prologue) prologue.ps
  ::aq::default J_TAGCONVERT(ps,epilogue) epilogue.ps
  ::aq::default J_TAGCONVERT(html,prologue) prologue.html
  ::aq::default J_TAGCONVERT(html,epilogue) epilogue.html
  ::aq::default J_TAGCONVERT(html,jdoc_prefix) ""
  ::aq::default J_TAGCONVERT(tclrt,prologue) prologue.tclrt
  ::aq::default J_TAGCONVERT(tclrt,epilogue) epilogue.tclrt
  
  # following should be in J_TAGCONVERT
  global J_PREFS
  ::aq::default J_PREFS(ps_roman_font) /Times-Roman
  ::aq::default J_PREFS(ps_italic_font) /Times-Italic
  ::aq::default J_PREFS(ps_bold_font) /Times-Bold
  ::aq::default J_PREFS(ps_bolditalic_font) /Times-BoldItalic
  ::aq::default J_PREFS(ps_monospace_font) /Courier
  ::aq::default J_PREFS(ps_heading_font) /Helvetica
  ::aq::default J_PREFS(ps_monospace_size) 11
  ::aq::default J_PREFS(ps_normal_size) 12
  ::aq::default J_PREFS(ps_linespacing) 14
}

######################################################################
# ::aq::tc::saveas t -
#   let the user choose a file format and a file to save the
#   richtext in $t into
######################################################################

proc ::aq::tc::saveas { t } {
  set save_as_prompt [::jldb::long_text "Save as:"]
  set format_prompt [::jldb::long_text "Format:"]
  
  set filename [::aq::fs \
    -prompt $save_as_prompt \
    -types {PostScript HTML TeX {Tcl Rich Text}} \
    -typevariable user_type \
    -typeprompt $format_prompt]
  
  if {"x$filename" == "x"} {
    return
  }
  
  switch -exact -- $user_type {
    {PostScript} {set type ps}
    {Tcl Rich Text} {set type tclrt}
    {HTML} {set type html}
    default {set type tex}
  }
  
  ::aq::write_file $filename [::aq::tc::${type}_convert_text $t]
}

######################################################################
# clear all convert:place:* tags (done when you start)
######################################################################

proc ::aq::tc::clear_marks { t } {
  foreach mark [$t mark names] {
    if [string match convert:place:* $mark] {
      $t mark unset $mark
    }
  }
}

######################################################################
# mark all places in the text where the font changes
######################################################################

proc ::aq::tc::mark_transitions { t } {
  # mark every spot where there's a font tag transition.  because each
  # mark is named after its position, a given location will only end up
  # with one mark, even if it has multiple font transitions  
  foreach tag [$t tag names] {
    # we only care about particular kinds of tags at this point:
    if {[string match richtext:font:* $tag] || \
        [string match jdoc:link:* $tag] || \
        [string match list:level:* $tag] || \
        [string match special:* $tag]} {
      foreach place [$t tag ranges $tag] {
        $t mark set convert:place:$place $place
      }
    }
  }
  $t mark set convert:place:1.0 1.0
  set end [$t index end]
  $t mark set convert:place:$end $end
}

######################################################################
# add marks to break the text up into pieces of a manageable size
######################################################################

proc ::aq::tc::make_small_chunks { t } {
  # add a mark about every sixty characters.  because each
  # mark is named after its position, a given location will only end up
  # with one mark, even if it has multiple font transitions  
  $t mark set chunk_pointer 1.0
  
  while 1 {
    $t mark set chunk_pointer {chunk_pointer + 50 chars wordend}
    while {[string match "\[ \n\t\f\]" [$t get chunk_pointer]] &&
           [$t compare chunk_pointer < end]} {
      $t mark set chunk_pointer {chunk_pointer + 1 char}
    }
    if [$t compare chunk_pointer >= end] {
      return 0
    }
    set index [$t index chunk_pointer]
    $t mark set convert:place:$index $index
  }
}

######################################################################
# compare marks by index
######################################################################

proc ::aq::tc::compare_marks { t one two } {
  if [$t compare $one < $two] {
    return -1
  }
  if [$t compare $one == $two] {
    return 0
  }
  return 1
}

######################################################################
# get all (relevant) marks, in sorted order
######################################################################

proc ::aq::tc::get_marks { t } {
  set marks {}
  
  foreach mark [$t mark names] {
    if [string match convert:place:* $mark] {
      lappend marks $mark
    }
  }
  return [lsort -command "::aq::tc::compare_marks $t" $marks]
}

######################################################################
# convert text to mode, processing each chunk in order
#   this is a "generic" procedure that many output formats should
#   be able to share.  it prepends a mode-specific prologue.  then
#   it breaks the text up into chunks, where each chunk is the same
#   font and a reasonable length, and calls a mode-specific procedure
#   to convert each chunk.  it appends a mode-specific epilogue
#   and returns the result.
# if "-break 1" is given, call ::aq::tc::make_small_chunks before
#   processing each chunk.  (a mode can also break chunks in 
#   ::aq::tc::_mode_::convert_chunk
######################################################################

proc ::aq::tc::generic_convert_text { t {mode tclrt} args } {
  ::aq::parse_args {
    {break 0}
  }
  
  set result {}
  set title [::aq::tag::get_metainfo $t title]
  if [string match "" $title] {
    set title Untitled
    catch {
      set title \
        [$t get richtext:font:heading0.first richtext:font:heading0.last]
      set title [lindex [split $title "\n"] 0]
      set title [string trim $title]
    }
  }
  set stylesheet [::aq::tag::get_metainfo $t stylesheet]
  
  catch {
    set result \
      [::aq::tc::${mode}_prologue $t -title $title -stylesheet $stylesheet]
  }
  
  ::aq::tc::clear_marks $t
  if $break {
    ::aq::tc::make_small_chunks $t
  }
  ::aq::tc::mark_transitions $t
  
  set marks [::aq::tc::get_marks $t]
  if {[llength $marks] == 1} {
    # text is empty
    return -1
  }
  
  set lastmark [expr [llength $marks] - 1]
  
  for {set i 0} {$i < $lastmark} {incr i} {
    set start [lindex $marks $i]
    set end [lindex $marks [expr $i + 1]]
    set text [$t get $start $end]
    set tags [$t tag names $start]
    set nextchar [$t get $end $end]
    set nexttags [$t tag names $end]
    append result \
      [::aq::tc::${mode}_convert_chunk $text $tags $nextchar $nexttags]
  }
  catch {
    append result [::aq::tc::${mode}_epilogue $t]
  }
  return $result
}

######################################################################
# ::aq::tc::find_tag pattern tags -
#   given a list of tags, return the first tag which matches pattern
#   if there is no match, returns null string
######################################################################

proc ::aq::tc::find_tag { pattern tags } {
  foreach tag $tags {
    if [string match $pattern $tag] {
      return $tag
    }
  }
  return ""
}

######################################################################
# ::aq::tc::find_font_tag tags -
#   given a list of tags, return the (hopefully only) font tag
#   defaults to "typewriter" font.
######################################################################

proc ::aq::tc::find_font_tag { tags } {
  foreach tag $tags {
    if [string match richtext:font:* $tag] {
      return $tag
    }
  }
  return "richtext:font:typewriter"
}

######################################################################
# ::aq::tc::break_string string -
#   return string with spaces replaced with returns every sixty characters
#   or so
#   HORRIBLY HORRIBLY KLUDGY!
######################################################################

proc ::aq::tc::break_string { string } {
  if {[string length $string] < 60} {
    return $string
  }
  
  set old_lines [split $string "\n"]
  set new_lines ""
  
  foreach old_line $old_lines {
    if {[string length $old_line] < 60} {
      lappend new_lines $old_line
    } else {
      set new_words ""
      foreach word [split $old_line " "] {
        lappend new_words $word
        if { [string length $new_words] > 60} {
          lappend new_lines [join $new_words " "]
          set new_words ""
        } ;# if >60
      } ;# foreach word
      if [llength $new_words] {
        lappend new_lines [join $new_words " "]
      }
    } ;# else (wasn't <60)
  } ;# foreach line
  
  return [join $new_lines "\n"]
}

######################################################################
# ::aq::tc::get_file_contents filename -
#   get contents of file if it exists, else empty string
######################################################################

proc ::aq::tc::get_file_contents { filename } {
  if [file readable $filename] {
    set file [open $filename]
    set contents [read -nonewline $file]
    close $file
    
    return $contents
  } else {
    return {}
  }
}

######################################################################
######################################################################
### tclrt mode
######################################################################
######################################################################

proc ::aq::tc::tclrt_convert_text { t } {
  return [::aq::tc::generic_convert_text $t tclrt -break 1]
}

proc ::aq::tc::tclrt_prologue { t args } {
  global J_TAGCONVERT
  
  return [::aq::tc::get_file_contents \
    [file join $J_TAGCONVERT(lib) $J_TAGCONVERT(tclrt,prologue)]]
}

proc ::aq::tc::tclrt_epilogue { t args } {
  global J_TAGCONVERT
  
  return [::aq::tc::get_file_contents \
    [file join $J_TAGCONVERT(lib) $J_TAGCONVERT(tclrt,epilogue)]]
}

proc ::aq::tc::tclrt_convert_string { text } {
  # prepend a backslash to some characters
  return [list $text]
}

proc ::aq::tc::tclrt_tag_to_command { tag } {
  switch -glob $tag {
    *:roman		{return j:rt:rm}
    *:italic		{return j:rt:it}
    *:bold		{return j:rt:bf}
    *:bolditalic	{return j:rt:it}
    *:typewriter	{return j:rt:tt}
    *:heading0		{return j:rt:h0}
    *:heading1		{return j:rt:h1}
    *:heading2		{return j:rt:h2}
    *:heading3		{return j:rt:h3}
    *:heading4		{return j:rt:h4}
    *:heading5		{return j:rt:h5}
    *:l_em		{return j:rt:it}
    *:l_cite		{return j:rt:it}
    *:l_var		{return j:rt:it}
    *:l_dfn		{return j:rt:it}
    *:l_strong		{return j:rt:bf}
    *:l_kbd		{return j:rt:bf}
    *:l_code		{return j:rt:tt}
    *:l_samp		{return j:rt:tt}
    *			{return j:rt:tt}
  }
}

proc ::aq::tc::tclrt_convert_chunk { text tags nextchar nexttags } {
  set text [::aq::tc::tclrt_convert_string $text]
  set tag [::aq::tc::find_font_tag $tags]
  set command [::aq::tc::tclrt_tag_to_command $tag]
  
  return "$command $text\n"
}

######################################################################
######################################################################
### tex mode
######################################################################
######################################################################

proc ::aq::tc::tex_convert_text { t args } {
  return [::aq::tc::generic_convert_text $t tex -break 1]
}

proc ::aq::tc::tex_prologue { t args } {
  global J_TAGCONVERT
  
  return [::aq::tc::get_file_contents \
    [file join $J_TAGCONVERT(lib) $J_TAGCONVERT(tex,prologue)]]
}

proc ::aq::tc::tex_epilogue { t args } {
  global J_TAGCONVERT
  
  return [::aq::tc::get_file_contents \
    [file join $J_TAGCONVERT(lib) $J_TAGCONVERT(tex,epilogue)]]
}

### This really should be split - rules are different in \tt mode from
### in normal fonts.
### note that it requires a lot of help from the TeX prologue.

proc ::aq::tc::tex_convert_string { text } {
  # prepend a backslash to some characters
  regsub -all -- {[${\\|}<>\\\\&#%_]} $text {\\\0} text
  regsub -all -- {[~^]} $text {\\\0{}} text
  # carriage returns become paragraph markers or line breaks:
  regsub -all -- "\n\n\n*" $text {\par } text
  regsub -all -- "\n" $text {\jtcnewline } text
  # tabs become whitespace (best we can do easily):
  regsub -all -- "\t" $text {\jtctab } text
  
  return $text
}

proc ::aq::tc::tex_tag_to_command { tag } {
  switch -glob $tag {
    *:roman		{return \\jtcroman}
    *:italic		{return \\jtcitalic}
    *:bold		{return \\jtcbold}
    *:bolditalic	{return \\jtcbolditalic}
    *:typewriter	{return \\jtctypewriter}
    *:heading0		{return \\jtcheadingzero}
    *:heading1		{return \\jtcheadingone}
    *:heading2		{return \\jtcheadingtwo}
    *:heading3		{return \\jtcheadingthree}
    *:heading4		{return \\jtcheadingfour}
    *:heading5		{return \\jtcheadingfive}
    *:l_em		{return \\jtcitalic}
    *:l_cite		{return \\jtcitalic}
    *:l_var		{return \\jtcitalic}
    *:l_dfn		{return \\jtcitalic}
    *:l_strong		{return \\jtcbold}
    *:l_kbd		{return \\jtcbold}
    *:l_code		{return \\jtctypewriter}
    *:l_samp		{return \\jtctypewriter}
    *			{return \\jtctypewriter}
  }
}

proc ::aq::tc::tex_convert_chunk { text tags nextchar nexttags } {
  set text [::aq::tc::tex_convert_string $text]
  set tag [::aq::tc::find_font_tag $tags]
  set command [::aq::tc::tex_tag_to_command $tag]
  
  if [string match " *" $text] {
    return "$command \\$text%\n"
  } else {
    return "$command $text%\n"
  }
}

######################################################################
######################################################################
### html mode
######################################################################
######################################################################

proc ::aq::tc::html_convert_text { t args } {
  return [::aq::tc::generic_convert_text $t html -break 0]
}

# -title sets the title of the document
# -preamble lets you add additional tags within the head
proc ::aq::tc::html_prologue { t args } {
  ::aq::parse_args {
    {title Untitled}
    {stylesheet {}}
  }
  
  global J_TAGCONVERT
  
  set prologue [::aq::tc::get_file_contents \
    [file join $J_TAGCONVERT(lib) $J_TAGCONVERT(html,prologue)]]
  if {[string length $stylesheet]} {
    regsub -all @@STYLESHEET@@ $prologue \
      "<link rel=\"stylesheet\" href=\"$stylesheet\" type=\"text/css\">\n" \
      prologue
  } else {
    regsub -all @@STYLESHEET@@ $prologue "" prologue
      
  }
  regsub -all @@TITLE@@ $prologue $title prologue
  
  set bodyparams {}
  foreach attribute {background bgcolor text link vlink alink} {
    set value {}
    catch {set value [::aq::tag::get_metainfo $t body_$attribute]}
    if ![string match {} $value] {
      append bodyparams " $attribute=\"$value\""
    }
  }
  
  regsub -all -nocase "<body>" $prologue "<body$bodyparams>" prologue
  return $prologue
}

proc ::aq::tc::html_epilogue { t args } {
  global J_TAGCONVERT
  
  return [::aq::tc::get_file_contents \
    [file join $J_TAGCONVERT(lib) $J_TAGCONVERT(html,epilogue)]]
}

proc ::aq::tc::html_convert_string { text } {
  # need to translate <>& into "&entity;" sequences
  if [string match {*[<>&]*} $text] {
    set chars [split $text ""]
    set text ""
    foreach char $chars {
      if { ! [string match {[<>&]} $char] } {
        append text $char
      } else {
        switch -exact $char {
          {<} {
            append text "&lt;"
          }
          {>} {
            append text "&gt;"
          }
          {&} {
            append text "&amp;"
          }
        } ;# switch
      } ;# if char is <>&
    } ;# foreach
  } ;# if any <>&
  
  # change newlines to breaks:
  regsub -all -- "\n" $text "\n<br>\n" text
  # change _multiple_ breaks to paragraphs:
  regsub -all -- "\n<br>\n\n<br>\n(\n<br>\n)*" $text "\n<p>\n" text
  return [::aq::tc::break_string $text]
}

proc ::aq::tc::html:tag_to_font { tag } {
  switch -glob $tag {
    *:roman		{return {}}
    *:italic		{return i}
    *:bold		{return b}
    *:bolditalic	{return em}
    *:typewriter	{return tt}
    *:heading0		{return h1}
    *:heading1		{return h2}
    *:heading2		{return h3}
    *:heading3		{return h4}
    *:heading4		{return h5}
    *:heading5		{return h5}
    *:l_em		{return em}
    *:l_cite		{return cite}
    *:l_var		{return var}
    *:l_dfn		{return dfn}
    *:l_strong		{return strong}
    *:l_kbd		{return kbd}
    *:l_code		{return code}
    *:l_samp		{return samp}
    *			{return tt}
  }
}

proc ::aq::tc::html_convert_chunk { text tags nextchar nexttags } {
  global J_TAGCONVERT
  
  # hack for <ul>...</ul> - I need a more general way to deal with this.
  global J_TAGCONVERT_HTML
  ::aq::default J_TAGCONVERT_HTML(list_level) 0
  
  # hack for vertical whitespace - again, this needs to be more general
  ::aq::default J_TAGCONVERT_HTML(last_whitespace) none
  
  set before ""
  set after ""
  set link ""
  set anchor ""
  
  # eliminate (at least some) superfluous <br> and <p>:
  if {[string length [::aq::tc::find_tag {*:heading*} $nexttags]] ||
      [string length [::aq::tc::find_tag {special:*} $nexttags]] ||
      [string length [::aq::tc::find_tag {*:heading*} $tags]]} {
    set text [string trimright $text "\n \t"]
    set after "\n"			;# make HTML easier to read
  }
  
  set specialtag [::aq::tc::find_tag "special:*" $tags]
  if ![llength $specialtag] {
    set text [::aq::tc::html_convert_string $text]
  } else {
    switch -glob $specialtag {
      "special:list_item:*" {
        set text "\n<li>"
      }
      "special:hr:*" {
        set text "\n<hr>\n"
        set J_TAGCONVERT_HTML(last_whitespace) hr
      }
      "special:html:literal" {
        set text $text
      }
      "special:ps:literal" {
        return "\n<!-- literal PostScript text omitted here -->\n"
      }
    }
  }
  
  # check for transition to or from a list:
  set listtag [::aq::tc::find_tag "list:level:*" $tags]
  if [string match "" $listtag] {
    set current_list_level 0
  } else {
    set current_list_level [lindex [split $listtag :] 2]
  }
  set old_list_level $J_TAGCONVERT_HTML(list_level)
  if {$current_list_level > $old_list_level} {
    set count [expr $current_list_level - $old_list_level]
    for {set i 0} {$i < $count} {incr i} {
      set before "$before\n<ul>\n"		;# spit out <ul>
    }
  }
  if {$current_list_level < $old_list_level} {
    set count [expr $old_list_level - $current_list_level]
    for {set i 0} {$i < $count} {incr i} {
      set before "\n</ul>\n$before"		;# spit out </ul>
    }
  }
  set J_TAGCONVERT_HTML(list_level) $current_list_level
  
  set linktag [::aq::tc::find_tag "jdoc:link:*" $tags]
  if [string length $linktag] {
    set link [string range $linktag 10 end]	;# strip off "jdoc:link:"
    if [string match *.jdoc $link] {
      regsub -- {\.jdoc$} $link .html link	;# change final .jdoc to .html
      						;# and add prefix...
      set link $J_TAGCONVERT(html,jdoc_prefix)$link
    }
    if [string match *.jdoc#* $link] {
      regsub -- {\.jdoc#} $link .html# link	;# change final .jdoc to .html
      						;# and add prefix...
      set link $J_TAGCONVERT(html,jdoc_prefix)$link
    }
###     if [string match #* $link] {		;# doesn't handle anchors yet
###       set link ""
###     }
  }
  set anchortag [::aq::tc::find_tag "jdoc:anchorname:*" $tags]
  if [string length $anchortag] {
    set anchor [string range $anchortag 16 end]	;# strip off "jdoc:link:"
  }
  set tag [::aq::tc::find_font_tag $tags]
  set font [::aq::tc::html:tag_to_font $tag]
  if [string length $font] {
    set before "$before<$font>"
    set after "</$font>$after"
  }
  if [string length $anchor] {
    set before "$before<a name=\"$anchor\">"
    set after "</a>$after"
  }
  if [string length $link] {
    set before "$before<a href=\"$link\">"
    set after "</a>$after"
  }
  if [string match "* " $text] {
    return "$before$text$after\n"
  } else {
    return "$before$text$after"
  }
}

######################################################################
######################################################################
### postscript mode
######################################################################
######################################################################

proc ::aq::tc::ps_convert_text { t args } {
  return [::aq::tc::generic_convert_text $t ps -break 0]
}

proc ::aq::tc::ps_prologue { t args } {
  global J_TAGCONVERT J_PREFS
  
  set prologue "%!"
  append prologue "% FONTS:\n"
  append prologue "/romanfont {$J_PREFS(ps_roman_font)} def\n"
  append prologue "/italicfont {$J_PREFS(ps_italic_font)} def\n"
  append prologue "/boldfont {$J_PREFS(ps_bold_font)} def\n"
  append prologue "/bolditalicfont {$J_PREFS(ps_bolditalic_font)} def\n"
  append prologue "/typewriterfont {$J_PREFS(ps_monospace_font)} def\n"
  append prologue "/normalsize $J_PREFS(ps_normal_size) def\n"
  append prologue {
    /romansize normalsize def
    /italicsize normalsize def
    /boldsize normalsize def
    /bolditalicsize normalsize def
  }
  append prologue "/typewritersize $J_PREFS(ps_monospace_size) def\n"
  append prologue "/headingfont {$J_PREFS(ps_heading_font)} def\n"
  append prologue {
    /heading0font headingfont def
    /heading1font headingfont def
    /heading2font headingfont def
    /heading3font headingfont def
    /heading4font headingfont def
    /heading5font headingfont def
  }
  append prologue "/linespacing $J_PREFS(ps_linespacing) def\n"
  append prologue [::aq::tc::get_file_contents \
    [file join $J_TAGCONVERT(lib) $J_TAGCONVERT(ps,prologue)]]
  return $prologue
}

proc ::aq::tc::ps_epilogue { t args } {
  global J_TAGCONVERT
  
  return [::aq::tc::get_file_contents \
    [file join $J_TAGCONVERT(lib) $J_TAGCONVERT(ps,epilogue)]]
}

proc ::aq::tc::ps_convert_string { text } {
  # prepend a backslash to some characters
  regsub -all -- {[()\\\\]} $text {\\\0} text
  # tabs become whitespace (best we can do easily):
  regsub -all -- "\t" $text {        } text
  
  return "($text)"
}

proc ::aq::tc::ps_tag_to_command { tag } {
  switch -glob $tag {
    *:roman		{return roman}
    *:italic		{return italic}
    *:bold		{return bold}
    *:bolditalic	{return bolditalic}
    *:typewriter	{return typewriter}
    *:heading0		{return heading0}
    *:heading1		{return heading1}
    *:heading2		{return heading2}
    *:heading3		{return heading3}
    *:heading4		{return heading4}
    *:heading5		{return heading5}
    *:l_em		{return italic}
    *:l_cite		{return italic}
    *:l_var		{return italic}
    *:l_dfn		{return italic}
    *:l_strong		{return bold}
    *:l_kbd		{return bold}
    *:l_code		{return typewriter}
    *:l_samp		{return typewriter}
    *			{return typewriter}
  }
}

proc ::aq::tc::ps_convert_chunk { text tags nextchar nexttags } {
  global J_TAGCONVERT
  
  # hack for <ul>...</ul> - I need a more general way to deal with this.
  global J_TAGCONVERT_PS
  ::aq::default J_TAGCONVERT_PS(list_level) 0
  
  
  set before ""
  
  # check for transition to or from a list:
  set listtag [::aq::tc::find_tag "list:level:*" $tags]
  if [string match "" $listtag] {
    set current_list_level 0
  } else {
    set current_list_level [lindex [split $listtag :] 2]
  }
  set old_list_level $J_TAGCONVERT_PS(list_level)
  if {$current_list_level > $old_list_level} {
    set count [expr $current_list_level - $old_list_level]
    for {set i 0} {$i < $count} {incr i} {
      append before "indent\n"		;# spit out "indent"
    }
  }
  if {$current_list_level < $old_list_level} {
    set count [expr $old_list_level - $current_list_level]
    for {set i 0} {$i < $count} {incr i} {
      append before "unindent\n"	;# spit out "unindent"
    }
  }
  set J_TAGCONVERT_PS(list_level) $current_list_level
  
  
  set specialtag [::aq::tc::find_tag "special:*" $tags]
  if ![llength $specialtag] {
    set text [::aq::tc::ps_convert_string $text]
  } else {
    switch -glob $specialtag {
      "special:list_item:*" {
        return "${before}item\n"
      }
      "special:hr:*" {
        return "${before}hrule\n"
        #set J_TAGCONVERT_HTML(last_whitespace) hr
      }
      "special:html:literal" {
        return "${before}% there was some HTML literal text omitted here\n"
      }
      "special:ps:literal" {
        return "$text "
      }
    }
  }
  
  
  set tag [::aq::tc::find_font_tag $tags]
  set command [::aq::tc::ps_tag_to_command $tag]
  
  return "$before$command $text breakshow\n"
}
