Fortran Wiki
asa2pdf

asa2pdf(1)

convert text files using ASA carriage control to an Adobe PDF file

The asa2pdf(1)program takes a file with (or without) ASA carriage control and converts it to an Adobe PDF file.

The program provides extensive user documentation via the –help switch, including a basic definition of ASA vertical carriage control for those unfamiliar with it.

Tested with GNU Fortran (GCC) 5.4.0 ; 20170313

Support of ASA carriage control by Operating Systems and printers was once nearly ubiquitious, but is now rarely supported. As a result newer codes rarely produce ASA files; so this program is primarily of interest to people supporting codes that already contain ASA carriage control.

Various versions of asa2pdf(1) have been used for a long time to provide output support for a number of Fortran and COBOL programs that made extensive use of ASA carriage control to support overstrikes. Overstrikes were used to provide characters with accents, underlining, special characters such as “not equal” (“=”+“backspace”+“/”)…. . As an extension to ASA box characters and extended ANSI characters were also used and supported.

The programs the asa2pdf(1) filter was developed for have now been converted to generate HTML output and use Unicode characters, but if anyone else still needs to support ASA characters or wants to correctly display old ASCII Art files that used ASA carriage control, or are looking for an example program that converts flat text files to Adobe PDF files (which are hard to come by) this program is being made freely available, as asa2pdf(1) will (probably) no longer be supported as a public Open Source application by the original author.

To make the asa2pdf(1) source self-contained several string utilties and a large command-line parsing module are included, which makes the source rather larger than expected.

!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
subroutine help_usage(l_help)
implicit none
character(len=*),parameter     :: ident="@(#)help_usage(3f): prints help information"
logical,intent(in)             :: l_help
character(len=:),allocatable :: help_text(:)
integer                        :: i
logical                        :: stopit=.false.
stopit=.false.
if(l_help)then
help_text=[ CHARACTER(LEN=128) :: &
'NAME                                                                            ',&
'    asa2pdf(1f) - Convert text files with/without ASA carriage control          ',&
'                  to an Adobe PDF file.                                         ',&
'                                                                                ',&
'SYNOPSIS                                                                        ',&
'   asa2pdf -o output_filename -i input_filename                                 ',&
'           -g gray_scale_shade -b lines_alternately_shaded -d dashcode          ',&
'           -s top_middle_page_label -t top_left_page_label                      ',&
'           -P # add page numbers                                                ',&
'           -l lines_per_page -f font_name -S columns_to_shift_data              ',&
'           -N # add line numbers                                                ',&
'           -H page_height -W page_width -u points_per_unit                      ',&
'           -L left_margin -R right_margin -B bottom_margin -T top_margin        ',&
'           -help -version -show                                                 ',&
'                                                                                ',&
'DESCRIPTION                                                                     ',&
'                                                                                ',&
'   Basically, asa2pdf(1) emulates a line printer that recognizes ASA            ',&
'   carriage control. That is, it lets you convert ASCII text files using        ',&
'   ASA carriage control into Adobe "clear text" PDF files instead of a          ',&
'   printed page.                                                                ',&
'                                                                                ',&
'   The PDF is clear-text ASCII so that it is easy to still use other            ',&
'   Unix/Linux utilities such as spell(1), diff(1), grep(1), .... on the         ',&
'   output files.                                                                ',&
'                                                                                ',&
'   To properly view the output requires a PDF processor (such                   ',&
'   as xpdf(1),acroread(1)/AcroRd32, gv(1) or ghostview(1), ...).                ',&
'   Most modern systems can view, mail and print PDF files.                      ',&
'                                                                                ',&
'   The default layout generates a landscape 132-column 60-line format with      ',&
'   every other two lines shaded. A variety of switches are available to         ',&
'   let you easily print files with no vertical carriage control, and in         ',&
'   portrait mode too. There are options to use dashed lines instead of          ',&
'   shading, to set different margins, and so on.                                ',&
'                                                                                ',&
'   WHAT IS ASA CARRIAGE CONTROL?                                                ',&
'                                                                                ',&
'   The ASA carriage control standard was the first important formatting         ',&
'   standard for printing and viewing text files. The standard was almost        ',&
'   universally adapted by printer manufacturers of the time (and printers       ',&
'   were a much more common output device than interactive displays).            ',&
'                                                                                ',&
'   Most commercial high-level programs at the time the standard was             ',&
'   created were either FORTRAN or COBOL; so nearly all early FORTRAN            ',&
'   output used ASA carriage control                                             ',&
'   (ASA was the American Standards Association -- now ANSI).                    ',&
'   This FORTRAN/ASA association became so strong that the standard is           ',&
'   sometimes referred to as the "Fortran carriage control standard" (FCC).      ',&
'   Indeed, even though ASA is no longer commonly directly supported on          ',&
'   desktop printers, it was part of the Fortran 90 standard (this was           ',&
'   dropped in Fortran 2003 -- how a printer processes files is really           ',&
'   not directly part of any programming language).                              ',&
'                                                                                ',&
'   Times have changed, and the once nearly ubiquitous ASA standard              ',&
'   is poorly supported on Unix and MSWindows machines in particular             ',&
'   (Direct operating-system support of ASA files was once common, but           ',&
'   is now rare).                                                                ',&
'                                                                                ',&
'   But no alternative as simple has emerged for output files                    ',&
'   that truly replaces the ASA standard (although machine control               ',&
'   characters (ctrl-H, ctrl-L, ...) have come close they have their             ',&
'   own issues).                                                                 ',&
'                                                                                ',&
'   So many programs using ASA-based formatting have not been changed,           ',&
'   and use commands like asa(1)/nasa(1), and fpr(1) to allow the files to       ',&
'   be printed as desired but NOT to generally be viewed properly on-line,       ',&
'   and printing itself is becoming less common.                                 ',&
'                                                                                ',&
'   So the problem isn''t so much with ASA files, but that today''s              ',&
'   infrastructure does not support the format well. The asa2pdf(1)              ',&
'   program bridges the gap by allowing you to still make and manipulate         ',&
'   ASA files until you want to print or email them, at which time you           ',&
'   can quickly convert them to an Adobe PDF file.                               ',&
'                                                                                ',&
'USAGE                                                                           ',&
'                                                                                ',&
'   asa2pdf(1) reads input from standard input. By default the first             ',&
'   character of each line is interpreted as a control character. Lines          ',&
'   beginning with any character other than those listed in the ASA              ',&
'   carriage-control characters table or in the list of extensions below         ',&
'   are interpreted as if they began with a blank, and an appropriate            ',&
'   diagnostic appears on standard error. The first character of each            ',&
'   line is not printed.                                                         ',&
'                                                                                ',&
'   ASA Carriage Control Characters                                              ',&
'                                                                                ',&
'        +------------+-----------------------------------------------+          ',&
'        | Character  |                                               |          ',&
'        +------------+-----------------------------------------------+          ',&
'        | +          | Do not advance; overstrike previous line.     |          ',&
'        | blank      | Advance one line.                             |          ',&
'        | null lines | Treated as if they started with a blank       |          ',&
'        | 0          | Advance two lines.                            |          ',&
'        | -          | Advance three lines (IBM extension).          |          ',&
'        | 1          | Advance to top of next page.                  |          ',&
'        | all others | Discarded (except for extensions listed below)|          ',&
'        +------------+-----------------------------------------------+          ',&
'   Extensions                                                                   ',&
'                                                                                ',&
'       H   Advance one-half line.                                               ',&
'       R   Do not advance; overstrike previous line. Use red text color         ',&
'       G   Do not advance; overstrike previous line. Use green text color       ',&
'       B   Do not advance; overstrike previous line. Use blue text color        ',&
'       r   Advance one line. Use red text color                                 ',&
'       g   Advance one line. Use green text color                               ',&
'       b   Advance one line. Use blue text color                                ',&
'       ^   Overprint but add 127 to the ADE value of the character              ',&
'           (ie., use ASCII extended character set)                              ',&
'                                                                                ',&
'OPTIONS                                                                         ',&
'       -o outputfile  Name of Adobe PDF output file to create                   ',&
'       -i inputfile   Name of text file to read. Defaults to stdin.             ',&
'                                                                                ',&
'    PRINTABLE PAGE AREA                                                         ',&
'                                                                                ',&
'      The page size may be specified using -H for height, -W for width, and -u  ',&
'      to indicate the points per unit (72 makes H and W in inches,              ',&
'      1 is used when units are in font points). For example:                    ',&
'                                                                                ',&
'       -u 72 -H 8.5 -W 11   # page Height and Width in inches                   ',&
'       -T 0.5 -B 0.5 -L 0.5 -R 0.5   # margins (Top, Bottom, Left, Right)       ',&
'                                                                                ',&
'      common media sizes with -u 1:                                             ',&
'                                                                                ',&
'       +-------------------+------+------------+                                ',&
'       | name              |  W   |        H   |                                ',&
'       +-------------------+------+------------+                                ',&
'       | Letterdj (11x8.5) | 792  |       612  | (LandScape)                    ',&
'       | A4dj              | 842  |       595  |                                ',&
'       | Letter (8.5x11)   | 612  |       792  | (Portrait)                     ',&
'       | Legal             | 612  |       1008 |                                ',&
'       | A5                | 420  |       595  |                                ',&
'       | A4                | 595  |       842  |                                ',&
'       | A3                | 842  |       1190 |                                ',&
'       +-------------------+------+------------+                                ',&
'                                                                                ',&
'    SHADING                                                                     ',&
'        -g 0.800781      gray-scale value for shaded bars ( 0 < g 1 )           ',&
'                         0 is black, 1 is white.                                ',&
'        -b 2             repeat shade pattern every N lines                     ',&
'        -d '' ''           dashcode pattern                                     ',&
'                         The pattern is a series of integers defining an        ',&
'                         on-off sequence in user units used to create a         ',&
'                         dash pattern. A single digit "N" implies a pattern     ',&
'                         of "N N". (seems buggy)                                ',&
'                                                                                ',&
'    MARGIN LABELS                                                               ',&
'       -s ''''             top middle page label.                               ',&
'       -t ''''             top left page label.                                 ',&
'       -P                add page numbers to right corners                      ',&
'                                                                                ',&
'    TEXT OPTIONS                                                                ',&
'       -l 60             lines per page                                         ',&
'       -f Courier        font names: Courier, Courier-Bold,Courier-Oblique      ',&
'                         Helvetica, Symbol, Times-Bold, Helvetica-Bold,         ',&
'                         ZapfDingbats, Times-Italic, Helvetica-Oblique,         ',&
'                         Times-BoldItalic, Helvetica-BoldOblique,               ',&
'                         Times-Roman, Courier-BoldOblique                       ',&
'                                                                                ',&
'       -S 0              right shift 1 for non-ASA files                        ',&
'       -N                add line numbers                                       ',&
'    INFORMATION                                                                 ',&
'       -version          display version number                                 ',&
'       -help             display this help                                      ',&
'                                                                                ',&
'ENVIRONMENT VARIABLES                                                           ',&
'     o $IMPACT_TOP Will be printed in large red letters across the page top.    ',&
'     o $IMPACT_GRAY sets the default gray-scale value, same as the -g switch.   ',&
'                                                                                ',&
'EXAMPLES                                                                        ',&
' Sample input:                                                                  ',&
'                                                                                ',&
'  > The numbers are plain underlined double-struck over-struck                  ',&
'  >+                      __________ double-struck ///////////                  ',&
'  >R                                               ///////////                  ',&
'  > abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-=_+()*&^%$#@!\|[]{};'':",.<>/?`~',&
'  >                                                                             ',&
'  >r red                                                                        ',&
'  >g     green                                                                  ',&
'  >b           blue                                                             ',&
'  > PRIMARY:                                                                    ',&
'  >R         red                                                                ',&
'  >G             green                                                          ',&
'  >B                   blue                                                     ',&
'  > 1/2 line advance                                                            ',&
'  >H                1                                                           ',&
'  >H                 2         a-1                                              ',&
'  >H                  3       Z                                                 ',&
'  >H                   4       b                                                ',&
'  > back to a normal line                                                       ',&
'                                                                                ',&
' Sample commands:                                                               ',&
'                                                                                ',&
'     # process  non-ASA file in portrait mode with a dashed line under every line ',&
'     asa2pdf -S 1 -W 8.5 -H 11 -b 1 -d ''2 4 1'' -T 1 -B .75 -o paper.pdf < INFILE',&
'                                                                                ',&
'     # banner on top                                                            ',&
'     env IMPACT_GRAY=1 IMPACT_TOP=CONFIDENTIAL asa2pdf -o paper.pdf < test.txt  ',&
'                                                                                ',&
'     # 132 landscape                                                            ',&
'      asa2pdf -s LANDSCAPE -o paper.pdf <asa2pdf.c                              ',&
'                                                                                ',&
'     # 132 landscape with line numbers with dashed lines                        ',&
'      asa2pdf -s ''LANDSCAPE LINE NUMBERS'' -d ''3 1 2''\                       ',&
'      -N -T .9 -o paper.pdf <asa2pdf.c                                          ',&
'                                                                                ',&
'     # portrait 80 column non-ASA file with dashed lines                        ',&
'      asa2pdf -s PORTRAIT -S 1 -W 8.5 -H 11 -b 1 -d ''2 4 1'' \                 ',&
'      -T 1 -B .75 -o paper.pdf < asa2pdf.c                                      ',&
'                                                                                ',&
'     # portrait 80 column with line numbers , non-ASA                           ',&
'      asa2pdf -s ''PORTRAIT LINE NUMBERS'' -l 66 -S 1 -W 8.5 -H 11 \            ',&
'      -b 1 -T 1 -B .75 -N -o paper.pdf < asa2pdf.c                              ',&
'                                                                                ',&
'     # titling                                                                  ',&
'      asa2pdf -d ''1 0 1'' -t "$USER" -b 1 -P -N -T 1 \                         ',&
'      -s "asa2pdf.c" -o paper.pdf <asa2pdf.c                                    ',&
'                                                                                ',&
'SEE ALSO                                                                        ',&
'                                                                                ',&
'   ALTERNATIVES TO ASA2PDF                                                      ',&
'                                                                                ',&
'   About the only standard ASA support on Unix variants is that some            ',&
'   contain the asa(1)/fpr(1) and nasa(1) commands for converting ASA text       ',&
'   files into and from text files with machine control (MC) characters          ',&
'   such as form-feed, backspace, carriage-return, .... Most personal            ',&
'   printers will no longer properly print ASA files directly, but they          ',&
'   will often correctly print files with simple MC characters                   ',&
'   (Note that the asa(1) command is referenced in the POSIX.2 standard).        ',&
'                                                                                ',&
'   Furthermore, if a printer does not directly support MC characters,           ',&
'   text conversion utilities such as enscript(1) and a2ps(1) can                ',&
'   often be used to print the files (usually by converting the files            ',&
'   to PostScript or PCL). Such utilities support features such as               ',&
'   titling, page numbering, and other useful options.                           ',&
'                                                                                ',&
'   Programs like "Adobe Distiller" can convert text to a PDF; as well as        ',&
'   editors such as OpenOffice. In fact, most modern document-formatting         ',&
'   editors can read in an ASCII text file and save it as an Adobe               ',&
'   PDF file.                                                                    ',&
'                                                                                ',&
'   HTML and PostScript/PDF and PCL files are the alternatives often             ',&
'   incorporated to satisfy simple formatting criteria --                        ',&
'   yet HTML is not printer-oriented;                                            ',&
'   and PDF files are complex to write from a simple program, and PCL is         ',&
'   vendor-specific and has few on-line viewers available for it.                ',&
'                                                                                ',&
'                                                                                ',&
'   Assuming converting the Fortran program to just write a plain ASCII          ',&
'   file instead of an ASA file is not acceptable, More extensive flat-text      ',&
'   formatting is available using                                                ',&
'                                                                                ',&
'   o HTML, *roff and LaTex-related file formats                                 ',&
'   o libraries for writing more sophisticated PostScript, PDF, and HTML/CSS files',&
'   o XML files formatted using Cascading Style Sheet (CSS) files                ',&
'   o RTF (Rich Text Format) files                                               ',&
'                                                                                ',&
'   Other Unix commands that can be useful in working with plain text and        ',&
'   MC character files are                                                       ',&
'                                                                                ',&
'      pr(1)           can be used to add page numbers and titles.               ',&
'      expand(1)       can remove tab characters                                 ',&
'      fold(1),fmt(1)  can be used to wrap the text                              ',&
'      cut(1)          can let you trim or select columns                        ',&
'      cat -n          can be used to add number lines                           ',&
'      paste(1)        can be used to put files side-by-side.                    ',&
'                                                                                ',&
'asa(1)/nasa(1), fpr(1), enscript(1), a2ps(1), and ps2pdf(1).                    ',&
'                                                                                ',&
'']
   WRITE(*,'(a)')(trim(help_text(i)),i=1,size(help_text))
   stop ! if -help was specified, stop
endif
end subroutine help_usage
subroutine help_version(l_version)
implicit none
character(len=*),parameter     :: ident="@(#)help_version(3f): prints version information"
logical,intent(in)             :: l_version
character(len=:),allocatable   :: help_text(:)
integer                        :: i
logical                        :: stopit=.false.
stopit=.false.
if(l_version)then
help_text=[ CHARACTER(LEN=128) :: &
'@(#)PRODUCT:        CLI library utilities and examples>',&
!>PRODUCT:        CLI library utilities and examples
'@(#)PROGRAM:        asa2pdf(1f)>',&
!>PROGRAM:        asa2pdf(1f)
'@(#)DESCRIPTION:    convert text files with ASA carriage return to Adobe PDF files>',&
!>DESCRIPTION:    convert text files with ASA carriage return to Adobe PDF files
'@(#)VERSION:        2.0, 20170210>',&
!>VERSION:        2.0, 20170210
'@(#)AUTHOR:         John S. Urban>',&
!>AUTHOR:         John S. Urban
'@(#)COMPILED:       Sun, Mar 5th, 2017 11:22:47 PM>',&
'']
   WRITE(*,'(a)')(trim(help_text(i)(5:len_trim(help_text(i))-1)),i=1,size(help_text))
   stop ! if -version was specified, stop
endif
end subroutine help_version
!-----------------------------------------------------------------------------------------------------------------------------------
!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
module M_kracken
implicit none
! "@(#)M_kracken(3f,module):parse command line options of Fortran programs using Unix-like syntax"
!===================================================================================================================================
   private
!-----------------------------------------------------------------------------------------------------------------------------------
   public :: kracken                ! define command and default parameter values from command arguments
   public :: setprompts             ! define prompts for commands in interactive mode
!-----------------------------------------------------------------------------------------------------------------------------------
   public :: rget                   ! fetch real    value of name VERB_NAME from the language dictionary
   public :: dget                   ! fetch double  value of name VERB_NAME from the language dictionary
   public :: iget                   ! fetch integer value of name VERB_NAME from the language dictionary
   public :: lget                   ! fetch logical value of name VERB_NAME from the language dictionary
   public :: sget                   ! fetch string  value of name VERB_NAME from the language dictionary.
   public :: sgetl                  ! fetch string  value of name VERB_NAME from the language dictionary.
   public :: retrev                 ! retrieve token value as string from Language Dictionary when given NAME
!-----------------------------------------------------------------------------------------------------------------------------------
   public :: delim                  ! parse a string and store tokens into an array
   public :: string_to_real         ! returns real value from numeric character string NOT USING CALCULATOR
   public :: string_to_dble         ! returns double precision value from numeric character string NOT USING CALCULATOR
!-----------------------------------------------------------------------------------------------------------------------------------
   private :: dissect               ! for user-defined commands: define defaults, then process user input
   private :: parse                 ! parse user command and store tokens into Language Dictionary
   private :: store                 ! replace dictionary name's value (if allow=add add name if necessary)
   private :: bounce                ! find location (index) in Language Dictionary where VARNAM can be found
   private :: add_string            ! Add new string name to Language Library dictionary
   private :: send_message
   private :: get_command_arguments ! get_command_arguments: return all command arguments as a string
   private :: igets                 ! return the subscript value of a string when given it's name
   private :: uppers                ! return copy of string converted to uppercase
   private :: menu                  ! generate an interactive menu when -? option is used
!-----------------------------------------------------------------------------------------------------------------------------------
! length of verbs and entries in Language dictionary
! NOTE:   many parameters were reduced in size so as to just accommodate being used as a command line parser.
!         In particular, some might want to change:
   integer, parameter,public :: IPic=400                           ! number of entries in language dictionary
   integer, parameter,public :: IPvalue=4096                       ! length of keyword value
   integer, parameter,public :: IPcmd=32768                        ! length of command
   integer, parameter,public :: IPverb=20                          ! length of verb
!-----------------------------------------------------------------------------------------------------------------------------------
   integer, parameter        :: dp = kind(0.d0)
   integer, parameter        :: k_int = SELECTED_INT_KIND(9)       ! integer*4
   integer, parameter        :: k_dbl = SELECTED_REAL_KIND(15,300) ! real*8
!-----------------------------------------------------------------------------------------------------------------------------------
   ! dictionary for Language routines
   character(len=IPvalue),dimension(IPic)     :: values=" "        ! contains the values of string variables
   character(len=IPverb),dimension(IPic)      :: dict_verbs=" "    ! string variable names
   integer(kind=k_int),dimension(IPic)        :: ivalue=0          ! significant lengths of string variable values
!-----------------------------------------------------------------------------------------------------------------------------------
contains
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine retrev(name,val,len,ier)
! "@(#)retrev(3f): retrieve token value from Language Dictionary when given NAME"
!-----------------------------------------------------------------------------------------------------------------------------------
      character(len=*),intent(in)  :: name        ! name of variable to retrieve value for in form VERB_NAME
      character(len=*),intent(out) :: val         ! value for requested variable
      integer,intent(out)          :: len         ! position of last non-blank character in requested variable
      integer,intent(out)          :: ier         ! error flag 0=found requested variable; -1=entry not found
!-----------------------------------------------------------------------------------------------------------------------------------
      integer                      :: isub        ! subscript in dictionary where requested entry and corresponding value are found
!-----------------------------------------------------------------------------------------------------------------------------------
      isub=igets(name)                            ! get index entry is stored at
!-----------------------------------------------------------------------------------------------------------------------------------
      if(isub > 0)then                            ! entry was in dictionary
         val=values(isub)                         ! retrieve corresponding value for requested entry
         len=ivalue(isub)                         ! get significant length of value
         ier=0                                    ! indicate requested entry name was successfully found
      else                                        ! entry was not in dictionary
         val=" "                                  ! set value to blank
         len=0                                    ! set length to zero
         ier=-1                                   ! set error flag to indicate requested entry was not found
      endif
!-----------------------------------------------------------------------------------------------------------------------------------
end subroutine retrev
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine string_to_dble(chars,value8,ierr)
! "@(#)string_to_dble(3f): returns double precision value from numeric character string"
                                                  !      works with any g-format input, including integer, real, and exponential.
      character(len=*),intent(in)  :: chars       ! string assumed to represent a numeric value
      real(kind=k_dbl),intent(out) :: value8      ! double precision value to return; set to zero on error.
      integer,intent(out)          :: ierr        ! if an error occurs in the read, a non-zero value is returned.
!-----------------------------------------------------------------------------------------------------------------------------------
      character(len=13)            :: frmt        ! FORMAT to use to read the value from the string
      integer                      :: ios         ! error flag returned from internal READ
!-----------------------------------------------------------------------------------------------------------------------------------
      ierr=0                                                     ! initialize the error flag
!-----------------------------------------------------------------------------------------------------------------------------------
      write(unit=frmt,fmt="( ""(bn,g"",i5,"".0)"" )")len(chars)  ! build FORMAT to read the value based on length of input string
      read(unit=chars,fmt=frmt,iostat=ios)value8                 ! read the value from the string using an internal read
!-----------------------------------------------------------------------------------------------------------------------------------
      if(ios /= 0 )then                                          ! if an error occurred in reading from the string report it
         value8=0.0_k_dbl                                        ! set the returned value to zero on error
         call send_message("*string_to_dble* - cannot produce number from this string["//trim(chars)//"]")
         ierr=ios
      endif
!-----------------------------------------------------------------------------------------------------------------------------------
end subroutine string_to_dble
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine string_to_real(chars,valu,ierr)
! "@(#)string_to_real(3f): returns real value from numeric character string"
      character(len=*),intent(in)  :: chars
      real,intent(out)             :: valu
      integer,intent(out)          :: ierr
      real(kind=k_dbl)             :: valu8
!-----------------------------------------------------------------------------------------------------------------------------------
      call string_to_dble(chars,valu8,ierr)         ! get value as double precision and stuff into a real variable
      valu=real(valu8)
!-----------------------------------------------------------------------------------------------------------------------------------
end subroutine string_to_real
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
function dget(keyword)
! "@(#)dget(3f): given keyword fetch value from Language Dictionary as a dble (zero on error)"
   real(kind=dp)               :: dget              ! function type
!-----------------------------------------------------------------------------------------------------------------------------------
   character(len=*),intent(in) :: keyword           ! keyword to retrieve value for from dictionary
!-----------------------------------------------------------------------------------------------------------------------------------
   character(len=IPvalue)      :: value             ! value returned
   integer                     :: len               ! length of value found
   integer                     :: ier               ! error flag on call to retrieve value
   real(kind=dp)               :: a8                ! number to return
!-----------------------------------------------------------------------------------------------------------------------------------
   value=" "                                        ! initialize value found for keyword in case an error occurs
   call retrev(keyword, value, len, ier)            ! find value associated with keyword
   call string_to_dble(value(:len), a8, ier)        ! convert the string to a numeric value
   dget = a8
!-----------------------------------------------------------------------------------------------------------------------------------
end function dget
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
function rget(keyword)
! "@(#)rget(3f): given keyword, fetch single real value from language dictionary (zero on error)"
!-----------------------------------------------------------------------------------------------------------------------------------
   real                        :: rget             ! function type
   character(len=*),intent(in) :: keyword          ! keyword to retrieve value for from dictionary
!-----------------------------------------------------------------------------------------------------------------------------------
   rget=real(dget(keyword))                        ! just call DGET(3f) but change returned value to type REAL
!-----------------------------------------------------------------------------------------------------------------------------------
end function rget
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
function iget(keyword)
! "@(#)iget(3f): given keyword, fetch integer value from language dictionary (zero on error)"
!-----------------------------------------------------------------------------------------------------------------------------------
   integer                      :: iget            ! function type
   character(len=*),intent(in)  :: keyword         ! keyword to retrieve value for from dictionary
!-----------------------------------------------------------------------------------------------------------------------------------
   iget = int(dget(keyword))                       ! just call DGET(3f) but change returned value to type INTEGER
!-----------------------------------------------------------------------------------------------------------------------------------
end function iget
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
function lget(keyword)
! "@(#)lget(3f): given keyword, fetch logical value from language dictionary (zero on error)"
!-----------------------------------------------------------------------------------------------------------------------------------
   logical                      :: lget            ! procedure type
   character(len=*),intent(in)  :: keyword         ! the dictionary keyword (in form VERB_KEYWORD) to retrieve
!-----------------------------------------------------------------------------------------------------------------------------------
   character(len=IPvalue)       :: value           ! value corresponding to the requested keyword
   integer                      :: len             ! length of VALUE returned by RETREV(3f)
   integer                      :: ier             ! flag returned by RETREV(3f) indicating if an error occurred in retrieving value
!-----------------------------------------------------------------------------------------------------------------------------------
   lget=.false.                                    ! initialize return value to .false.
   call retrev(keyword, value, len, ier)           ! get value for corresponding keyword from language dictionary
                                                   ! report on error ????
   value=adjustl(uppers(value,len))                ! convert value to uppercase, left spaces trimmed
!-----------------------------------------------------------------------------------------------------------------------------------
   if(value(:len).ne."#N#")then
      select case(value(1:1))                      ! check first letter
      case('T','Y',' ')                            ! anything starting with "T" or "Y" or a blank is TRUE (true,t,yes,y,...)
         lget=.true.
      case('F','N')                                ! assume this is false or no
         lget=.false.
      case('.')                                    ! looking for fortran logical syntax .STRING.
         select case(value(2:2))
         case('T')                                 ! assume this is .t. or .true.
            lget=.true.
         case('F')                                 ! assume this is .f. or .false.
            lget=.false.
         case default
            call send_message("*lget* bad logical expression for "//keyword(:len_trim(keyword))//'='//value(:len))
         end select
      case default
            call send_message("*lget* bad logical expression for "//keyword(:len_trim(keyword))//'='//value(:len))
      end select
   else                                            ! special value "#N#" is assumed FALSE
      lget=.false.
   endif
!-----------------------------------------------------------------------------------------------------------------------------------
end function lget
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
character(len=IPvalue) function sget(name,ilen) result(string)
! "@(#)sget(3f): Fetch string value and length of specified NAME the language dictionary"
!     This routine trusts that the desired name exists. A blank is returned if the name is not in the dictionary
      character(len=*),intent(in)   :: name     !  name to look up in dictionary
      integer,intent(out),optional  :: ilen     !  length of returned output string
!-----------------------------------------------------------------------------------------------------------------------------------
      integer                       :: isub     ! index where verb_oo is stored or -1 if this is an unknown name
!-----------------------------------------------------------------------------------------------------------------------------------
      isub=igets(name)                          ! given name return index name is stored at
!-----------------------------------------------------------------------------------------------------------------------------------
      if(isub > 0)then                          ! if index is valid return string
         string=values(isub)
      else                                      ! if index is not valid return blank string
         string(:)=" "
      endif
!-----------------------------------------------------------------------------------------------------------------------------------
      if(present(ilen))then                     ! if ILEN is present on call, return the value
         ilen=ivalue(isub)
      endif
!-----------------------------------------------------------------------------------------------------------------------------------
end function sget
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
function sgetl(name,ilen) result(string)
! "@(#)sgetl(3f): Fetch string value for NAME from language dictionary up to length ILEN"
!     This routine trusts that the desired name exists. A blank is returned if the name is not in the dictionary
      character(len=*),intent(in)  :: name        ! name to look up in dictionary
      integer,intent(in)           :: ilen        ! length of returned output string
!-----------------------------------------------------------------------------------------------------------------------------------
      character(len=ilen)          :: string
      integer                      :: isub
!-----------------------------------------------------------------------------------------------------------------------------------
      isub=igets(name)                            ! given name return index name is stored at
!-----------------------------------------------------------------------------------------------------------------------------------
      if(isub > 0)then                            ! if index is valid return string
         string=values(isub)
      else                                        ! if index is not valid return blank string
         string(:)=" "
      endif
!-----------------------------------------------------------------------------------------------------------------------------------
end function sgetl
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine kracken(verb,string,error_return)
! "@(#)kracken(3f): define and parse command line options"
!     get the entire command line argument list and pass it and the
!     prototype to dissect()
!-----------------------------------------------------------------------------------------------------------------------------------
      character(len=*),intent(in)    :: string
      character(len=*),intent(in)    :: verb
      integer,intent(out),optional   :: error_return
!-----------------------------------------------------------------------------------------------------------------------------------
      character(len=IPcmd)           :: command
      integer                        :: ilen
      integer                        :: ier
!-----------------------------------------------------------------------------------------------------------------------------------
      if(present(error_return))error_return=0
!-----------------------------------------------------------------------------------------------------------------------------------
      call get_command_arguments(command,ilen,ier)
      if(ier.ne.0)then
         call send_message("*kracken* could not get command line arguments")
         if(present(error_return))error_return=ier
      else
         call dissect(verb,string,command(:ilen),ilen,ier)
         ! if calling procedure is not testing error flag stop program on error
         if(.not.present(error_return).and.ier.ne.0)then
            call send_message("*kracken* (V 20151212) STOPPING: error parsing arguments")
            stop
         endif
      endif
!-----------------------------------------------------------------------------------------------------------------------------------
end subroutine kracken
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine setprompts(verb,init)
! "@(#)setprompts(3f): set explicit prompts for keywords in interactive mode"
      character(len=*),intent(in)  :: verb   ! verb name to define prompts for
      character(len=*),intent(in)  :: init   ! string to define prompts instead of values
      call parse('?'//trim(verb),init,"add") ! initialize command, prefixing verb with question mark character to designate prompts
end subroutine setprompts
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine dissect(verb,init,pars,ipars,error_return)
! "@(#)dissect(3f): convenient call to parse() -- define defaults, then process"
!
      character(len=*),intent(in)  :: verb            ! the name of the command to be reset/defined  and then set
      character(len=*),intent(in)  :: init            ! used to define or reset command options; usually hard-set in the program.
      character(len=*),intent(in)  :: pars            ! defines the command options to be set, usually from a user input file
      integer,intent(in)           :: ipars           ! length of the user-input string pars.
      integer,intent(out),optional :: error_return
!-----------------------------------------------------------------------------------------------------------------------------------
      integer                      :: ier
      character(len=IPvalue)       :: varvalue        ! value of environment variable
      integer                      :: ipars2
!-----------------------------------------------------------------------------------------------------------------------------------
      call store(trim(verb)//'_?','.false.',"add",ier)  ! all commands have the option -? to invoke prompt mode
      call parse(trim(verb),init,"add") ! initialize command
!-----------------------------------------------------------------------------------------------------------------------------------
      ! if environment variable DEFAULT_verbname is set apply it as defaults to define _verb values
      ! for programs that want to determine the values set by the command definition and the variable
      ! before user selections are applied
      call parse('_'//trim(verb),init,"add") ! initialize _command
      call get_environment_variable('DEFAULT_'//trim(verb),varvalue)
      call parse('_'//trim(verb),trim(varvalue),"no_add") ! process and store as _CMD_VERB for appending
!-----------------------------------------------------------------------------------------------------------------------------------
      if(varvalue.ne.' ')then
         call parse(verb,trim(varvalue),"no_add")            ! process environment variable
      endif
!-----------------------------------------------------------------------------------------------------------------------------------
      if(ipars <= 0)then
         ipars2=len(pars(:ipars))
      else
         ipars2=ipars
      endif
!-----------------------------------------------------------------------------------------------------------------------------------
      call parse(verb,pars(:ipars2),"no_add",ier) ! process user command options
      if(lget(trim(verb)//'_?'))then    ! if -? option was present prompt for values
         call menu(verb)
      endif
      if(present(error_return))error_return=ier
!-----------------------------------------------------------------------------------------------------------------------------------
end subroutine dissect
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine parse(verb,string,allow,error_return)
! "@(#)parse(3f,private): parse user command and store tokens into Language Dictionary"
!!!   set up odd for future expansion
!!!   need to handle a minus followed by a blank character
!-----------------------------------------------------------------------------------------------------------------------------------
!     given a string of form
!         verb  -keyword1 value1 -keyword2 value2 ...
!     define three arrays of the form
!     verb_keyword(i) : value(i)  : len_trim(value(i))
!     -keyword(i) will become verb__keyword(i)
!
!     values may be in double quotes.
!     if tokens contain alphameric characters an unquoted # signifies the rest of the line is a comment.
!     adjacent double quotes put one double quote into value
!     processing ends when an end of string is encountered
!     the variable name for the first value is verb_oo
!     call it once to give defaults
!     leading and trailing blanks are removed from values
!
!-----------------------------------------------------------------------------------------------------------------------------------
! @(#)parse+ for left-over command string for Language routines
!     optionally needed if you are going to allow multiple commands on a line
      ! number of characters left over,
      ! number of non-blank characters in actual parameter list
!-----------------------------------------------------------------------------------------------------------------------------------
      character(len=*),intent(in)          :: verb
      character(len=*),intent(in)          :: string
      character(len=*),intent(in)          :: allow
      character(len=IPvalue+2)             :: dummy
      character(len=IPvalue),dimension(2)  :: var
      character(len=3)                     :: delmt
      character(len=2)                     :: init
      character(len=1)                     :: currnt
      character(len=1)                     :: prev
      character(len=1)                     :: forwrd
      character(len=IPvalue)               :: val
      character(len=IPverb)                :: name
      integer,dimension(2)                 :: ipnt
      integer                              :: ilist
      integer                              :: ier
      integer,optional,intent(out)         :: error_return
      integer                              :: islen
      integer                              :: ipln
      integer                              :: ipoint
      integer                              :: itype
      integer                              :: ifwd
      integer                              :: ibegin
      integer                              :: iend
!-----------------------------------------------------------------------------------------------------------------------------------
      ilist=1
      init="oo"
      ier=0
      if(present(error_return)) error_return=0
      islen=len_trim(string)   ! find number of characters in input string
      ! if input string is blank, even default variable will not be changed
      if(islen  ==  0)then
         return
      endif
      dummy=string             ! working mutable copy of STRING
      ipln=len_trim(verb)      ! find number of characters in verb prefix string
!-----------------------------------------------------------------------------------------------------------------------------------
      var(2)=init         ! initial variable name
      var(1)=" "          ! initial value of a string
      ipoint=0            ! ipoint is the current character pointer for (dummy)
      ipnt(2)=2           ! pointer to position in parameter name
      ipnt(1)=1           ! pointer to position in parameter value
      itype=1             ! itype=1 for value, itype=2 for variable
!-----------------------------------------------------------------------------------------------------------------------------------
      delmt="off"
      prev=" "
!-----------------------------------------------------------------------------------------------------------------------------------
      do
      ipoint=ipoint+1               ! move current character pointer forward
      currnt=dummy(ipoint:ipoint)   ! store current character into currnt
      ifwd=min(ipoint+1,islen)
      forwrd=dummy(ifwd:ifwd)       ! next character (or duplicate if last)
!-----------------------------------------------------------------------------------------------------------------------------------
      if((currnt=="-".and.prev==" ".and.delmt == "off".and.index("0123456789.",forwrd) == 0).or.ipoint > islen)then
      ! beginning of a parameter name
         if(forwrd.eq.'-')then         ! change --var to -var so "long" syntax is supported
            dummy(ifwd:ifwd)='_'
            ipoint=ipoint+1            ! ignore second - instead
         endif
         if(ipnt(1)-1 >= 1)then
            ibegin=1
            iend=len_trim(var(1)(:ipnt(1)-1))
            do
               if(iend  ==  0)then   !len_trim returned 0, parameter value is blank
                  iend=ibegin
                  exit
               else if(var(1)(ibegin:ibegin) == " ")then
                  ibegin=ibegin+1
               else
                  exit
               endif
            enddo
            name=verb(:ipln)//"_"//var(2)(:ipnt(2))
            val=var(1)(ibegin:iend)
            if(var(2)(:ipnt(2)).eq.'oo'.and.allow.ne.'add'.and.val.eq.'')then
               ! do not allow a blank value to override initial value so can have default
            else
               call store(name,val,allow,ier)       ! store name and it's value
            endif
            if(present(error_return).and.ier.ne.0)error_return=ier
         else
            name=verb(:ipln)//"_"//var(2)(:ipnt(2))
            val=" "                                 ! store name and null value
            call store(name,val,allow,ier)
            if(present(error_return).and.ier.ne.0)error_return=ier
         endif
         ilist=ilist+ipln+1+ipnt(2)
         ilist=ilist+1
         itype=2                          ! change to filling a variable name
         var(1)=" "                       ! clear value for this variable
         var(2)=" "                       ! clear variable name
         ipnt(1)=1                        ! restart variable value
         ipnt(2)=1                        ! restart variable name
!-----------------------------------------------------------------------------------------------------------------------------------
      elseif(currnt == "#".and.delmt == "off")then   ! rest of line is comment
         islen=ipoint
         dummy=" "
         prev=" "
         cycle
!-----------------------------------------------------------------------------------------------------------------------------------
      ! rest of line is another command(s)
         islen=ipoint
         dummy=" "
         prev=" "
         cycle
!-----------------------------------------------------------------------------------------------------------------------------------
      else       ! currnt is not one of the special characters
         ! the space after a keyword before the value
         if(currnt == " ".and.itype  ==  2)then
            ! switch from building a keyword string to building a value string
            itype=1
         ! beginning of a delimited parameter value
         elseif(currnt  ==  """".and.itype  ==  1)then
            ! second of a double quote, put quote in
            if(prev  ==  """")then
                var(itype)(ipnt(itype):ipnt(itype))=currnt
                ipnt(itype)=ipnt(itype)+1
                delmt="on"
            elseif(delmt  ==  "on")then     ! first quote of a delimited string
                delmt="off"
            else
                delmt="on"
            endif
         else     ! add character to current parameter name or parameter value
            var(itype)(ipnt(itype):ipnt(itype))=currnt
            ipnt(itype)=ipnt(itype)+1
            if(currnt /= " ")then
            endif
         endif
!-----------------------------------------------------------------------------------------------------------------------------------
      endif
!-----------------------------------------------------------------------------------------------------------------------------------
      prev=currnt
      if(ipoint <= islen)then
         cycle
      endif
      exit
      enddo
end subroutine parse
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine store(name1,value1,allow1,ier)
! "@(#)store(3f,private): replace dictionary name's value (if allow='add' add name if necessary)"
!-----------------------------------------------------------------------------------------------------------------------------------
      character(len=*),intent(in)        :: name1       ! name in dictionary of from VERB_KEYWORD
      character(len=*),intent(in)        :: value1      ! value to be associated to NAME1
      character(len=*),intent(in)        :: allow1      ! flag to allow new VERB_KEYWORD name being added
      integer,intent(out)                :: ier         ! flag if error occurs in adding or setting value
!-----------------------------------------------------------------------------------------------------------------------------------
      character(len=IPverb)              :: name
      integer                            :: indx
      character(len=10)                  :: allow
      character(len=IPvalue)             :: value
      character(len=IPvalue)             :: mssge       !  the  message/error/string  value
      integer                            :: nlen
      integer                            :: new
      integer                            :: ii
      integer                            :: i10
!-----------------------------------------------------------------------------------------------------------------------------------
      value=" "
      name=" "
      allow=" "
      name=name1                                        ! store into a standard size variable for this type
      value=value1                                      ! store into a standard size variable for this type
      allow=allow1                                      ! store into a standard size variable for this type
      nlen=len(name1)
!-----------------------------------------------------------------------------------------------------------------------------------
      call bounce(name,indx,dict_verbs,ier,mssge)       ! determine storage placement of the variable and whether it is new
      if(ier  ==  -1)then                               ! an error occurred in determining the storage location
         call send_message("error occurred in *store*")
         call send_message(mssge)
         return
      endif
!-----------------------------------------------------------------------------------------------------------------------------------
      if(indx > 0)then                                  ! found the variable name
         new=1
      else if(indx <= 0.and.allow  ==  "add")then       ! check if the name needs added
         call add_string(name,nlen,indx,ier)            ! adding the new variable name in the variable name array
         if(ier  ==  -1)then
            call send_message("*store* could not add "//name(:nlen))
            call send_message(mssge)
            return
         endif
         new=0
!-----------------------------------------------------------------------------------------------------------------------------------
      else                                              ! did not find variable name but not allowed to add it
         ii=index(name,"_")
         call send_message("########################################################")
         call send_message("error: UNKNOWN OPTION -"//name(ii+1:))
         if(ii > 0)then
            call send_message(name(:ii-1)//" parameters are")
            do i10=1,IPic
               if(name(:ii)  ==  dict_verbs(i10)(:ii))then
                  if(dict_verbs(i10)(ii:ii+1).eq.'__')then
                     call send_message(" --"//dict_verbs(i10)(ii+2:len_trim(dict_verbs(i10)))//" "//values(i10)(:ivalue(i10)))
                  else
                     call send_message(" -"//dict_verbs(i10)(ii+1:len_trim(dict_verbs(i10)))//" "//values(i10)(:ivalue(i10)))
                  endif
               endif
            enddo
         endif
         call send_message("########################################################")
         ier=-10
         return
      endif
!-----------------------------------------------------------------------------------------------------------------------------------
      values(iabs(indx))=value               ! store a defined variable's value
      ivalue(iabs(indx))=len_trim(value)     ! store length of string
!-----------------------------------------------------------------------------------------------------------------------------------
end subroutine store
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine bounce(varnam,index,dictionary,ier,mssge)
! "@(#)bounce(3f,private): find index in Language Dictionary where VARNAM can be found"
!
!     If VARNAM is not found report where it should be placed as a NEGATIVE index number.
!     Assuming DICTIONARY is an alphabetized array
!     Assuming all variable names are lexically greater than a blank string.
!-----------------------------------------------------------------------------------------------------------------------------------
      character(len=*),intent(in)                :: varnam      ! variable name to look up in dictionary
      integer,intent(out)                        :: index       ! location where variable is or should be
      character(len=*),dimension(:),intent(in)   :: dictionary  ! sorted dictionary array to find varnam in
      integer,intent(out)                        :: ier
      character(len=*),intent(out)               :: mssge
!-----------------------------------------------------------------------------------------------------------------------------------
      integer                                    :: maxtry      ! maximum number of tries that should be required
      integer                                    :: imin
      integer                                    :: imax
      integer                                    :: i10
!-----------------------------------------------------------------------------------------------------------------------------------
      maxtry=int(log(float(IPic))/log(2.0)+1.0)                 ! calculate max number of tries required to find a conforming name
      index=(IPic+1)/2
      imin=1
      imax=IPic
!-----------------------------------------------------------------------------------------------------------------------------------
      do i10=1,maxtry
         if(varnam  ==  dictionary(index))then
            return
         else if(varnam > dictionary(index))then
            imax=index-1
         else
            imin=index+1
         endif
         if(imin > imax)then
            index=-imin
            if(iabs(index) > IPic)then
               mssge="error 03 in bounce"
               ier=-1
               return
            endif
            return
         endif
         index=(imax+imin)/2
         if(index > IPic.or.index <= 0)then
            mssge="error 01 in bounce"
            ier=-1
            return
         endif
      enddo
!-----------------------------------------------------------------------------------------------------------------------------------
      mssge="error 02 in bounce"
!-----------------------------------------------------------------------------------------------------------------------------------
end subroutine bounce
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine add_string(newnam,nchars,index,ier)
! "@(#)add_string(3f,private): Add new string name to Language Library dictionary"
!=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      character(len=*),intent(in)       :: newnam     ! new variable name to add to dictionary
      integer,intent(in)                :: nchars     ! number of characters in NEWNAM
      integer,intent(in)                :: index
      integer,intent(out)               :: ier
!=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      integer                           :: istart
      integer                           :: i10
!=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      istart=iabs(index)
!=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
!     if last position in the name array has already been used, then report no room is left and set error flag and error message.
      if(dict_verbs(IPic) /= " ")then                 ! check if dictionary full
         call send_message("*add_string* no room left to add more string variable names")
         ier=-1
!=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      elseif(istart.gt.IPic)then
         call send_message("*add_string* dictionary size exceeded")
         ier=-1
!=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      else
         do i10=IPic-1,istart,-1
!           pull down the array to make room for new value
            values(i10+1)=values(i10)
            ivalue(i10+1)=ivalue(i10)
            dict_verbs(i10+1)=dict_verbs(i10)
         enddo
         values(istart)=" "
         ivalue(istart)= 0
         dict_verbs(istart)=newnam(1:nchars)
!=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
      endif
!=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
end subroutine add_string
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
function igets(chars0)
! "@(#)igets(3f,private): return the subscript value of a string when given it's name"
!     WARNING: only request value of names known to exist
!-----------------------------------------------------------------------------------------------------------------------------------
      character(len=*),intent(in)        :: chars0
!-----------------------------------------------------------------------------------------------------------------------------------
      character(len=IPverb)              :: chars
      character(len=IPvalue)             :: mssge
      integer                            :: ierr
      integer                            :: index
      integer                            :: igets
!-----------------------------------------------------------------------------------------------------------------------------------
      chars=chars0
      ierr=0
      index=0
      call bounce(chars,index,dict_verbs,ierr,mssge)             ! look up position
!-----------------------------------------------------------------------------------------------------------------------------------
      if((ierr  ==  -1).or.(index <= 0))then
         call send_message("*igets* variable "//trim(chars)//" undefined")
         igets=-1                                                ! very unfriendly subscript value
      else
         igets=index
      endif
!-----------------------------------------------------------------------------------------------------------------------------------
end function igets
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine delim(line0,array,n,iicount,ibegin,iterm,ilen,dlim)
! "@(#)delim(3f): parse a string and store tokens into an array"
!
!     given a line of structure " par1 par2 par3 ... parn "
!     store each par(n) into a separate variable in array.
!
!     IF ARRAY(1) = '#N#' do not store into string array  (KLUDGE))
!
!     also icount number of elements of array initialized, and
!     return beginning and ending positions for each element.
!     also return position of last non-blank character (even if more
!     than n elements were found).
!
!     no quoting of delimiter is allowed
!     no checking for more than n parameters, if any more they are ignored
!
      character(len=*),intent(in)                 :: line0
      integer,intent(in)                          :: n
      !character(len=*),dimension(n),intent(out)  :: array
      character(len=*),dimension(:),intent(out)   :: array
      integer,intent(out)                         :: iicount
      !integer,dimension(n),intent(out)           :: ibegin
      integer,dimension(:),intent(out)            :: ibegin
      !integer,dimension(n),intent(out)           :: iterm
      integer,dimension(:),intent(out)            :: iterm
      integer,intent(out)                         :: ilen
      character(len=*),intent(in)                 :: dlim
      character(len=IPcmd)                        :: line
      logical                                     :: lstore
      integer                                     :: idlim
      integer                                     :: icol
      integer                                     :: iarray
      integer                                     :: istart
      integer                                     :: iend
      integer                                     :: i10
      integer                                     :: ifound
      iicount=0
      ilen=len_trim(line0)
      if(ilen > IPcmd)then
         call send_message("*delim* input line too long")
      endif
      line=line0
      idlim=len(dlim)
      if(idlim > 5)then
         idlim=len_trim(dlim)                                ! dlim a lot of blanks on some machines if dlim is a big string
         if(idlim  ==  0)then
            idlim=1  ! blank string
         endif
      endif
!     command was totally blank
      if(ilen  ==  0)then
         return
      endif
!     there is at least one non-blank character in the command
!     ilen is the column position of the last non-blank character
!     find next non-delimiter
      icol=1
      if(array(1)  ==  "#N#")then                            ! special flag to not store into character array
         lstore=.false.
      else
         lstore=.true.
      endif
      do iarray=1,n,1                                        ! store into each array element until done or too many words
         if(index(dlim(1:idlim),line(icol:icol))  ==  0)then ! if current character is not a delimiter
           istart=icol                                       ! start new token on the non-delimiter character
           ibegin(iarray)=icol
           iend=ilen-istart+1+1                              ! assume no delimiters so put past end of line
           do i10=1,idlim
              ifound=index(line(istart:ilen),dlim(i10:i10))
              if(ifound > 0)then
                iend=min(iend,ifound)
              endif
           enddo
            if(iend <= 0)then                              ! no remaining delimiters
              iterm(iarray)=ilen
              if(lstore)then
                 array(iarray)=line(istart:ilen)
              endif
              iicount=iarray
              return
            else
              iend=iend+istart-2
              iterm(iarray)=iend
              if(lstore)then
                 array(iarray)=line(istart:iend)
              endif
            endif
           icol=iend+2
         else
           icol=icol+1
           cycle
         endif
   !     last character in line was a delimiter, so no text left
   !     (should not happen where blank=delimiter)
         if(icol > ilen)then
           iicount=iarray
           return
         endif
      enddo
!     more than n elements
      iicount=n
end subroutine delim
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine send_message(msg)
! "@(#)send_message(3f,private): general message routine"
      character(len=*),intent(in) :: msg                      ! message to display
      print "(""# "",a)", msg(:len_trim(msg))               ! write message
end subroutine send_message
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine get_command_arguments(string,istring_len,istatus)
! "@(#)get_command_arguments(3f,private): return all command arguments as a string"
!-----------------------------------------------------------------------------------------------------------------------------------
   character(len=*),intent(out) :: string                     ! string of all arguments to create
   integer,intent(out)          :: istring_len                ! last character position set in output string
   integer,intent(out)          :: istatus                    ! status (non-zero means error)
!-----------------------------------------------------------------------------------------------------------------------------------
   integer                      :: istring_len_new            ! length of output if append next argument
   integer                      :: string_len                 ! allowed length of output string
   integer                      :: ilength                    ! length of individual arguments
   integer                      :: i                          ! loop count
   character(len=IPvalue)       :: value                      ! store individual arguments one at a time
   integer                      :: ifoundspace
!-----------------------------------------------------------------------------------------------------------------------------------
   string=""                                                  ! initialize returned output string
   string_len=len(string)                                     ! find out how big the output string can be
   istring_len=0                                              ! initialize returned output string length
   istatus=0                                                  ! initialize returned error code
!-----------------------------------------------------------------------------------------------------------------------------------
   do i=1,command_argument_count()                            ! append any arguments together
      call get_command_argument(i,value,ilength,istatus)      ! get next argument
      istring_len_new=istring_len+ilength+1                   ! calculate total string length plus one for a separator
      !---------------------
      ! BEGIN GUESS AT RE-QUOTING STRING
      !---------------------
      ! if argument contains a space and does not contain a double-quote and is short enough to have double quotes added
      ! assume this argument was quoted but that the shell stripped the quotes and add double quotes. This is an optional
      ! behavior and assumes an operating system that strips the quotes from quoted strings on the command line. If the
      ! operating system is smarter than that remove this section
      if(ilength.gt.0)then
         ifoundspace=index(value(:ilength),' ')
         if(index(value(:ilength),' ').ne.0.and.index(value(:ilength),'"').eq.0)then
            ilength=ilength+2
            if(ilength.le.len(value))then
               value='"'//value(:ilength)//'"'
            endif
         endif
      endif
      !---------------------
      ! END GUESS AT RE-QUOTING STRING
      !---------------------
      if(ilength.gt.len(value))then
         call send_message('*get_command_arguments* argument too big')
         stop
      elseif(istatus /= 0) then                               ! stop appending on error
         call send_message('*get_command_arguments* error obtaining argument')
         stop
      elseif(istring_len_new.gt.string_len)then               ! not enough room to store argument
         call send_message('*get_command_arguments* output too long, command trimmed')
         stop
      endif
      string=string(:istring_len)//value(:ilength)            ! append strings together
      istring_len=istring_len_new
   enddo
   istring_len=len_trim(string)                               ! keep track of length and so do not need to use len_trim
!-----------------------------------------------------------------------------------------------------------------------------------
end subroutine get_command_arguments
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
function uppers(input_string,output_size) result (output_string)
! "@(#)uppers(3f,private): return copy of input string converted to uppercase"
!-----------------------------------------------------------------------------------------------------------------------------------
      character(len=*),intent(in) :: input_string         ! input string to convert to uppercase
      integer,intent(in)          :: output_size          ! size of output string
      character(len=output_size)  :: output_string        ! output string converted to uppercase
!-----------------------------------------------------------------------------------------------------------------------------------
      character(len=1)            :: letter               ! current letter
      integer                     :: ilet                 ! ADE (ASCII Decimal Equivalent) of current letter
      integer                     :: icount               ! counter used to increment thru the input string
!-----------------------------------------------------------------------------------------------------------------------------------
      if(len_trim(input_string).gt.output_size)then       ! warn that length of input longer than length of output
         call send_message("*uppers* - input string longer than output string")
      endif
!-----------------------------------------------------------------------------------------------------------------------------------
      output_string=" "                                   ! initialize output string to all blanks
!-----------------------------------------------------------------------------------------------------------------------------------
      do icount=1,min(output_size,len_trim(input_string)) ! loop thru input one character at a time up to length of output string
         letter=input_string(icount:icount)               ! extract next letter
         ilet=ichar(letter)                               ! get integer ADE (ASCII Decimal Equivalent) of letter
                                                          ! NOTE: lowercase a-z in ASCII is an ADE of 97 to 122
                                                          !       uppercase A-Z in ASCII is an ADE of 65 to 90
         if((ilet >= 97) .and.(ilet <= 122))then          ! find if current letter is a lowercase letter
            output_string(icount:icount)=char(ilet-32)    ! convert lowercase a-z to uppercase A-Z and store into output string
         else                                             ! character is not a lowercase letter, just put it in output
            output_string(icount:icount)=letter           ! store character as-is
         endif
      enddo
!-----------------------------------------------------------------------------------------------------------------------------------
end function uppers
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
subroutine menu(verb)
! "@(#)menu(3f,private): prompt for values using a menu interface"
!-----------------------------------------------------------------------------------------------------------------------------------
   character(len=*),intent(in)    :: verb
!-----------------------------------------------------------------------------------------------------------------------------------
   character(len=IPvalue)         :: reply
   character(len=IPvalue)         :: prompt
   integer                        :: ii
   integer                        :: icount
   integer                        :: ios
   integer                        :: i10
   integer                        :: i20
   integer                        :: istart
   integer                        :: iend
   integer                        :: ifound
   integer                        :: ireply
   real                           :: valu
   integer                        :: ierr
   integer                        :: index
   character(len=IPvalue)         :: mssge   !  the message/error/string  value returned by BOUNCE(3f)
!-----------------------------------------------------------------------------------------------------------------------------------
   ii=len_trim(verb)
   write(*,*)verb(:ii)//" parameters are"
   istart=1
!-----------------------------------------------------------------------------------------------------------------------------------
   INFINITE: do
      icount=0                                                ! how many entries in the dictionary belong to this command
      iend=IPic                                               ! last dictionary entry to search for current command
      MAKEMENU: do i10=istart,iend                            ! search dictionary for keywords for current command
         if(verb(:ii)//'_'  ==  dict_verbs(i10)(:ii+1))then   ! found part of the desired command
            if(istart.eq.0)istart=i10                         ! store index to the beginning of this command
            icount=icount+1                                   ! count keywords that start with VERB_
            if(dict_verbs(i10).eq.verb(:ii)//'_?')then        ! do not show the keyword VERB_?
               cycle MAKEMENU
            endif
            call bounce('?'//dict_verbs(i10),index,dict_verbs,ierr,mssge) ! if ?VERB is defined assume it is a prompt
            if(index.gt.0)then
               prompt=values(index)
            else
               prompt=' '
            endif
            if(prompt.eq.'')then
               write(*,'(i4,")",a,a)') i10,dict_verbs(i10)(ii+2:),trim(values(i10)(:ivalue(i10)))
            elseif(prompt.eq.'#N#')then                       ! special prompt value which means to skip prompting
            else
               write(*,'(i4,")",a,":[",a,"]")') i10,trim(prompt),trim(values(i10))
            endif
         endif
      enddo MAKEMENU
      iend=icount+istart-1                                 ! no need to go thru entire dictionary on subsequent passes
!-----------------------------------------------------------------------------------------------------------------------------------
      write(*,'(a)',advance='no')'Enter number of parameter to change(0 to finish):'
      read(*,'(a)',iostat=ios)reply
      reply=adjustl(reply)
      valu=-1
!-----------------------------------------------------------------------------------------------------------------------------------
      if(reply(1:1).eq.'-')then            ! assume this is the beginning of a respecification of options using -keyword value ...
         call parse(verb,trim(reply)//' -? .false.',"no_add")
         cycle INFINITE
      endif
!-----------------------------------------------------------------------------------------------------------------------------------
      select case(REPLY)
!-----------------------------------------------------------------------------------------------------------------------------------
      case('@DUMP')                                   ! debug option to dump dictionary
         do i20=1,IPic
            if(dict_verbs(i20).ne.' ')then
                 write(*,'(a,a,a)')i20,dict_verbs(i20),':',trim(values(i20)(:ivalue(i20)))
            endif
         enddo
         cycle INFINITE
!-----------------------------------------------------------------------------------------------------------------------------------
      case('.','q')
         stop
         !exit INFINITE
!-----------------------------------------------------------------------------------------------------------------------------------
      case('?')
         write(*,*)'--------------------------------------------------------------------------------'
         write(*,*)' Enter '
         write(*,*)'   o  NNN                   the number of the option to change the value for'
         write(*,*)'   o  "-keyword value ..."  to respecify values'
         write(*,*)'   o  ?                     display this help'
         write(*,*)'   o  .                     stop the program'
         write(*,*)''
         cycle INFINITE
!-----------------------------------------------------------------------------------------------------------------------------------
      case default
         call string_to_real(reply,valu,ierr)              ! try to convert to a number
      end select
!-----------------------------------------------------------------------------------------------------------------------------------
      ireply=int(valu)
!-----------------------------------------------------------------------------------------------------------------------------------
      if(ireply.eq.0)then
         exit INFINITE
      elseif((valu.lt.istart).or.(valu.gt.iend))then
         write(*,*)'illegal menu choice ',istart,'<=',valu,'<=',iend
!-----------------------------------------------------------------------------------------------------------------------------------
      else
         ifound=ireply                                     ! index into dictionary for requested keyword and value
         if(dict_verbs(ifound).eq.verb(:ii)//'_?')then     ! replaced this with FINISHED so exit
            exit INFINITE
         endif
         call bounce('?'//dict_verbs(ifound),index,dict_verbs,ierr,mssge) ! if ?VERB is defined assume it is a prompt
         if(index.gt.0)then
            prompt=values(index)
         else
            prompt=' '
         endif
         if(prompt.eq.'')then
            write(*,'("Enter value for ",a,":")',advance='no') trim(dict_verbs(ifound)(ii+2:))
         elseif(prompt.eq.'#N#')then                       ! special prompt value
         else
            write(*,'(a,":")',advance='no') trim(prompt)
         endif
!-----------------------------------------------------------------------------------------------------------------------------------
         read(*,'(a)',iostat=ios)reply
         call store(dict_verbs(ifound),reply,"no_add",ierr)
!-----------------------------------------------------------------------------------------------------------------------------------
      endif
   enddo INFINITE
!-----------------------------------------------------------------------------------------------------------------------------------
end subroutine menu
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
end module M_kracken
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
module M_utilities
use iso_fortran_env, only : ERROR_UNIT        ! access computing environment
implicit none
interface v2s
   module procedure d2s, r2s, i2s
end interface
contains
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
!! 
!! NAME
!!    stderr - [M_debug]write message to stderr
!! 
!! SYNOPSIS
!!    subroutine stderr(message)
!! 
!!     character(len=*) :: message
!! 
!! DESCRIPTION
!!    STDERR(3f) writes a message to standard error using a standard f2003 method.
!! 
!! EXAMPLES
!!   Sample program:
!! 
!!    program demo_stderr
!!    use M_utilities, only: stderr
!!    implicit none
!!    call stderr('error: program will now stop')
!!    stop 1
!!    end program demo_stderr
subroutine stderr(message)
character(len=*),parameter :: ident="@(#)M_debug::stderr(3f): writes a message to standard error using a standard f2003 method"
character(len=*) :: message
   write(error_unit,'(a)')trim(message)    ! write message to standard error
end subroutine stderr
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
!! NAME
!!      s2v - [M_utilities]function returns doubleprecision numeric value from a string
!! 
!! SYNOPSIS
!!    function s2v(string,[ierr])
!! 
!!     character(len=*)             :: string
!!     doubleprecision              :: s2v
!!     integer,intent(out),optional :: ierr
!! 
!! DESCRIPTION
!!       This function converts a string to a DOUBLEPRECISION numeric value.
!!       A value of zero (0) is returned on error.
!! 
!!       If an error occurs the program is stopped if the optional parameter
!!       IERR is not present. If IERR is non-zero an error occurred.
!! 
!! EXAMPLE
!! 
!!    program demo_s2v
!! 
!!     use M_utilities, only: s2v
!!     implicit none
!!     character(len=8)              :: s=' 10.345 '
!!     integer                       :: i
!!     character(len=14),allocatable :: strings(:)
!!     doubleprecision               :: dv
!!     integer                       :: errnum
!! 
!!     ! different strings representing INTEGER, REAL, and DOUBLEPRECISION
!!     strings=[&
!!     &' 10.345       ',&
!!     &'+10           ',&
!!     &'    -3        ',&
!!     &'    -4.94e-2  ',&
!!     &'0.1           ',&
!!     &'12345.678910d0',&
!!     &'              ',& ! Note: will return zero without an error message
!!     &'1 2 1 2 1 . 0 ',& ! Note: spaces will be ignored
!!     &'WHAT?         ']  ! Note: error messages will appear, zero returned
!! 
!!     ! a numeric value is returned, so it can be used in numeric expression
!!     write(*,*) '1/2 value of string is ',s2v(s)/2.0d0
!!     write(*,*)
!!     write(*,*)' STRING            VALUE                    ERROR_NUMBER'
!!     do i=1,size(strings)
!!        ! Note: not a good idea to use s2v(3f) in a WRITE(3f) statement,
!!        ! as it does I/O when errors occur, so called on a separate line
!!        dv=s2v(strings(i),errnum)
!!        write(*,*) strings(i)//'=',dv,errnum
!!     enddo
!!     write(*,*)"That's all folks!"
!! 
!!     end program demo_s2v
!! 
!!    Expected output
!! 
!!     >1/2 value of string is    5.1725000000000003
!!     >
!!     > STRING            VALUE                    ERROR_NUMBER
!!     > 10.345       =   10.345000000000001                0
!!     >+10           =   10.000000000000000                0
!!     >    -3        =  -3.0000000000000000                0
!!     >    -4.94e-2  =  -4.9399999999999999E-002           0
!!     >0.1           =  0.10000000000000001                0
!!     >12345.678910d0=   12345.678910000001                0
!!     >              =   0.0000000000000000                0
!!     >1 2 1 2 1 . 0 =   12121.000000000000                0
!!     >*a2d* - cannot produce number from string [WHAT?]
!!     >*a2d* - [Bad value during floating point read]
!!     >WHAT?         =   0.0000000000000000             5010
!!     >That's all folks!
!! PROCEDURE:
!! DESCRIPTION: s2v(3f): function returns doubleprecision number from string;zero if error occurs"
!! VERSION:     2.0, 20160704
!! AUTHOR:      John S. Urban
doubleprecision function s2v(chars,ierr)
!  1989 John S. Urban
character(len=*),parameter::ident="@(#)M_utilities::s2v(3f): returns doubleprecision number from string"

character(len=*),intent(in) :: chars
integer,optional            :: ierr
doubleprecision             :: valu
   integer                  :: ierr_local

   ierr_local=0
   call a2d(chars,valu,ierr_local)
   s2v=valu
   if(present(ierr))then ! if error is not returned stop program on error
      ierr=ierr_local
   elseif(ierr_local.ne.0)then
      write(*,*)'*s2v* stopped while reading '//trim(chars)
      stop 1
   endif
end function s2v
!----------------------------------------------------------------------------------------------------------------------------------
subroutine a2d(chars,valu,ierr)
character(len=*),parameter::ident="@(#)M_utilities::a2d(3fp): subroutine returns double value from string"
!     1989,2016 John S. Urban.
!
!  o  works with any g-format input, including integer, real, and exponential.
!  o  if an error occurs in the read, iostat is returned in ierr and value is set to zero.  if no error occurs, ierr=0.
!  o  if the string happens to be 'eod' no error message is produced so this string may be used to act as an end-of-data.
!     IERR will still be non-zero in this case.
!----------------------------------------------------------------------------------------------------------------------------------
   character(len=*),intent(in)  :: chars                        ! input string
   character(len=:),allocatable :: local_chars
   doubleprecision,intent(out)  :: valu                         ! value read from input string
   integer,intent(out)          :: ierr                         ! error flag (0 == no error)
!----------------------------------------------------------------------------------------------------------------------------------
   character(len=*),parameter   :: fmt="('(bn,g',i5,'.0)')"     ! format used to build frmt
   character(len=15)            :: frmt                         ! holds format built to read input string
   character(len=256)           :: msg                          ! hold message from I/O errors
   integer                      :: intg
!----------------------------------------------------------------------------------------------------------------------------------
   ierr=0                                                       ! initialize error flag to zero
   local_chars=chars
   if(len(local_chars).eq.0)local_chars=' '
   call substitute(local_chars,',','')                          ! remove any comma characters
   select case(local_chars(1:1))
   case('z','Z','h','H')                                        ! assume hexadecimal
      frmt='(Z'//v2s(len(local_chars))//')'
      read(local_chars(2:),frmt,iostat=ierr,iomsg=msg)intg
      valu=dble(intg)
   case('b','B')                                                ! assume binary (base 2)
      frmt='(B'//v2s(len(local_chars))//')'
      read(local_chars(2:),frmt,iostat=ierr,iomsg=msg)intg
      valu=dble(intg)
   case('O','o')                                                ! assume octal
      frmt='(O'//v2s(len(local_chars))//')'
      read(local_chars(2:),frmt,iostat=ierr,iomsg=msg)intg
      valu=dble(intg)
   case default
      write(frmt,fmt)len(local_chars)                           ! build format of form '(BN,Gn.0)'
      read(local_chars,fmt=frmt,iostat=ierr,iomsg=msg)valu      ! try to read value from string
   end select
   if(ierr.ne.0)then                                            ! if an error occurred ierr will be non-zero.
      valu=0.0                                                  ! set returned value to zero on error
         if(local_chars.ne.'eod')then                                 ! print warning message
            call stderr('*a2d* - cannot produce number from string ['//trim(chars)//']')
            call stderr('*a2d* - ['//trim(msg)//']')
         endif
      endif
end subroutine a2d
!===================================================================================================================================
function d2s(dvalue) result(outstr)
character(len=*),parameter::ident="@(#)M_utilities::d2s(3fp): private function returns string given doubleprecision value"
doubleprecision,intent(in)   :: dvalue                         ! input value to convert to a string
character(len=:),allocatable :: outstr                         ! output string to generate
character(len=80)            :: string
   call value_to_string(dvalue,string)
   outstr=trim(string)
end function d2s
!===================================================================================================================================
function r2s(rvalue) result(outstr)
character(len=*),parameter::ident="@(#)M_utilities::r2s(3fp): private function returns string given real value"
real,intent(in )             :: rvalue                         ! input value to convert to a string
character(len=:),allocatable :: outstr                         ! output string to generate
character(len=80)            :: string
   call value_to_string(rvalue,string)
   outstr=trim(string)
end function r2s
!===================================================================================================================================
function i2s(ivalue) result(outstr)
character(len=*),parameter::ident="@(#)M_utilities::i2s(3fp): private function returns string given integer value"
integer,intent(in )          :: ivalue                         ! input value to convert to a string
character(len=:),allocatable :: outstr                         ! output string to generate
character(len=80)            :: string
   call value_to_string(ivalue,string)
   outstr=trim(string)
end function i2s
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
!! NAME
!!    substitute - [M_strings]Globally substitute one substring for another in string
!! 
!! SYNOPSIS
!!    subroutine substitute(targetline,old,new,ierr,start,end)
!! 
!!     character(len=*)              :: targetline
!!     character(len=*),intent(in)   :: old
!!     character(len=*),intent(in)   :: new
!!     integer,intent(out),optional  :: ierr
!!     integer,intent(in),optional   :: start
!!     integer,intent(in),optional   :: end
!! 
!! DESCRIPTION
!!    Globally substitute one substring for another in string.
!! 
!! OPTIONS
!!     targetline  input line to be changed
!!     old         old substring to replace
!!     new         new substring
!!     ierr        error code. iF ier = -1 bad directive, &gt;= 0 then
!!                 count of changes made
!!     start       start sets the left  margin
!!     end         end sets the right  margin
!! 
!! EXAMPLES
!!   Sample Program:
!! 
!!    program test_substitute
!!    use M_strings, only : substitute
!!    implicit none
!!    ! must be long enough to hold changed line
!!    character(len=80) :: targetline
!! 
!!    targetline='this is the input string'
!!    write(*,*)'ORIGINAL    : '//trim(targetline)
!! 
!!    ! changes the input to 'THis is THe input string'
!!    call substitute(targetline,'th','TH')
!!    write(*,*)'th => TH    : '//trim(targetline)
!! 
!!    ! a null old substring means "at beginning of line"
!!    ! changes the input to 'BEFORE:this is the input string'
!!    call substitute(targetline,'','BEFORE:')
!!    write(*,*)'"" => BEFORE: '//trim(targetline)
!! 
!!    ! a null new string deletes occurrences of the old substring
!!    ! changes the input to 'ths s the nput strng'
!!    call substitute(targetline,'i','')
!!    write(*,*)'i => ""     : '//trim(targetline)
!! 
!!    end program test_substitute
!! 
!!   Expected output
!! 
!!     ORIGINAL    : this is the input string
!!     th => TH    : THis is THe input string
!!     "" => BEFORE: BEFORE:THis is THe input string
!!     i => ""     : BEFORE:THs s THe nput strng
subroutine substitute(targetline,old,new,ierr,start,end)
character(len=*),parameter::ident="@(#)M_strings::substitute(3f): Globally substitute one substring for another in string"
!-----------------------------------------------------------------------------------------------------------------------------------
   character(len=*)              :: targetline         ! input line to be changed
   character(len=*),intent(in)   :: old                ! old substring to replace
   character(len=*),intent(in)   :: new                ! new substring
   character(len=len(targetline)):: dum1               ! scratch string buffers
   integer,intent(out),optional  :: ierr               ! error code. if ierr = -1 bad directive, >=0 then ierr changes made
   integer,intent(in),optional   :: start              ! start sets the left  margin
   integer,intent(in),optional   :: end                ! end sets the right  margin
!-----------------------------------------------------------------------------------------------------------------------------------
   integer                       :: ml, mr, ier1
   integer                       :: maxlengthout       ! MAXIMUM LENGTH ALLOWED FOR NEW STRING
   integer                       :: original_input_length
   integer                       :: len_old, len_new
   integer                       :: ladd
   integer                       :: ir
   integer                       :: ind
   integer                       :: il
   integer                       :: id
   integer                       :: ic
   integer                       :: ichar
!-----------------------------------------------------------------------------------------------------------------------------------
   if (present(start)) then                            ! optional starting column
      ml=start
   else
      ml=1
   endif
   if (present(end)) then                              ! optional ending column
      mr=end
   else
      mr=len(targetline)
   endif
!-----------------------------------------------------------------------------------------------------------------------------------
   ier1=0                                              ! initialize error flag/change count
   maxlengthout=len(targetline)                        ! max length of output string
   original_input_length=len_trim(targetline)          ! get non-blank length of input line
   dum1(:)=' '                                         ! initialize string to build output in
   id=mr-ml                                            ! check for window option !! change to optional parameter(s)
!-----------------------------------------------------------------------------------------------------------------------------------
   len_old=len(old)                                    ! length of old substring to be replaced
   len_new=len(new)                                    ! length of new substring to replace old substring
   if(id.le.0)then                                     ! no window so change entire input string
      il=1                                             ! il is left margin of window to change
      ir=maxlengthout                                  ! ir is right margin  of window to change
      dum1(:)=' '                                      ! begin with a blank line
   else                                                ! if window is set
      il=ml                                            ! use left margin
      ir=min0(mr,maxlengthout)                         ! use right margin or rightmost
      dum1=targetline(:il-1)                           ! begin with what's below margin
   endif                                               ! end of window settings
!-----------------------------------------------------------------------------------------------------------------------------------
   if(len_old.eq.0)then                                ! c//new/ means insert new at beginning of line (or left margin)
      ichar=len_new + original_input_length
      if(ichar.gt.maxlengthout)then
         call stderr('*substitute* new line will be too long')
         ier1=-1
         if (present(ierr))ierr=ier1
         return
      endif
      if(len_new.gt.0)then
         dum1(il:)=new(:len_new)//targetline(il:original_input_length)
      else
         dum1(il:)=targetline(il:original_input_length)
      endif
      targetline(1:maxlengthout)=dum1(:maxlengthout)
      ier1=1                                           ! made one change. actually, c/// should maybe return 0
      if(present(ierr))ierr=ier1
      return
   endif
!-----------------------------------------------------------------------------------------------------------------------------------
   ichar=il                                            ! place to put characters into output string
   ic=il                                               ! place looking at in input string
   loop: do
      ind=index(targetline(ic:),old(:len_old))+ic-1    ! try to find start of old string in remaining part of input in change window
      if(ind.eq.ic-1.or.ind.gt.ir)then                 ! did not find old string or found old string past edit window
         exit loop                                     ! no more changes left to make
      endif
      ier1=ier1+1                                      ! found an old string to change, so increment count of changes
      if(ind.gt.ic)then                                ! if found old string past at current position in input string copy unchanged
         ladd=ind-ic                                   ! find length of character range to copy as-is from input to output
         if(ichar-1+ladd.gt.maxlengthout)then
            ier1=-1
            exit loop
         endif
         dum1(ichar:)=targetline(ic:ind-1)
         ichar=ichar+ladd
      endif
      if(ichar-1+len_new.gt.maxlengthout)then
         ier1=-2
         exit loop
      endif
      if(len_new.ne.0)then
         dum1(ichar:)=new(:len_new)
         ichar=ichar+len_new
      endif
      ic=ind+len_old
   enddo loop
!-----------------------------------------------------------------------------------------------------------------------------------
   select case (ier1)
   case (:-1)
      call stderr('*substitute* new line will be too long')
   case (0)                                                ! there were no changes made to the window
   case default
      ladd=original_input_length-ic
      if(ichar+ladd.gt.maxlengthout)then
         call stderr('*substitute* new line will be too long')
         ier1=-1
         if(present(ierr))ierr=ier1
         return
      endif
      if(ic.lt.len(targetline))then
         dum1(ichar:)=targetline(ic:max(ic,original_input_length))
      endif
      targetline=dum1(:maxlengthout)
   end select
   if(present(ierr))ierr=ier1
!-----------------------------------------------------------------------------------------------------------------------------------
end subroutine substitute
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
!! NAME
!!      value_to_string - [M_strings]return numeric string from a numeric value
!! 
!! SYNOPSIS
!! 
!!    subroutine value_to_string(value,chars[,ilen,ierr,fmt])
!! 
!!     character(len=*) :: chars  ! minimum of 23 characters required
!!     !--------
!!     ! VALUE may be any <em>one</em> of the following types:
!!     doubleprecision,intent(in)               :: value
!!     real,intent(in)                          :: value
!!     integer,intent(in)                       :: value
!!     !--------
!!     character(len=*),intent(out)             :: chars
!!     integer,intent(out),optional             :: ilen
!!     integer,optional                         :: ierr
!!     character(len=*),intent(in),optional     :: fmt
!! 
!! DESCRIPTION
!! 
!!    value_to_string(3f)
!!    that returns a numeric representation in a string given a numeric value of type
!!    REAL, DOUBLEPRECISION, or INTEGER. It creates the strings using internal writes.
!!    It then removes trailing zeros from non-zero values, and left-justifies the string.
!! 
!! OPTIONS
!!       o  VALUE - input value to be converted to a string
!! RETURNS
!!       o  CHARS - returned string representing input value, must be at least 23 characters long;
!!                  or what is required by optional FMT if longer.
!!       o  ILEN - position of last non-blank character in returned string; optional.
!!       o  IERR - If not zero, error occurred.; optional.
!!       o  FMT - You may specify a specific format that produces a string up to the length of CHARS; optional.
!! 
!! EXAMPLE
!! 
!!    Sample program
!! 
!!      program demo_value_to_string
!!      use m_strings, only: value_to_string
!!      implicit none
!!      character(len=80) :: string
!!      integer           :: ilen
!!         call value_to_string(3.0/4.0,string,ilen)
!!         write(*,*) 'The value is [',string(:ilen),']'
!! 
!!         call value_to_string(3.0/4.0,string,ilen,fmt='')
!!         write(*,*) 'The value is [',string(:ilen),']'
!! 
!!         call value_to_string(3.0/4.0,string,ilen,fmt='("THE VALUE IS ",g0)')
!!         write(*,*) 'The value is [',string(:ilen),']'
!! 
!!         call value_to_string(1234,string,ilen)
!!         write(*,*) 'The value is [',string(:ilen),']'
!! 
!!         call value_to_string(1.0d0/3.0d0,string,ilen)
!!         write(*,*) 'The value is [',string(:ilen),']'
!! 
!!      end program demo_value_to_string
!! 
!!    Expected output
!! 
!!     The value is [0.75]
!!     The value is [      0.7500000000]
!!     The value is [THE VALUE IS .750000000]
!!     The value is [1234]
!!     The value is [0.33333333333333331]
!! 
subroutine value_to_string(gval,chars,length,err,fmt)
character(len=*),parameter::ident="@(#)M_strings::value_to_string(3fp): subroutine returns a string from a value"
class(*),intent(in)                      :: gval
character(len=*),intent(out)             :: chars
integer,intent(out),optional             :: length
integer,optional                         :: err
integer                                  :: err_local
character(len=*),optional,intent(in)     :: fmt         ! format to write value with
character(len=:),allocatable             :: fmt_local

!  Notice that the value GVAL can be any of several types ( INTEGER,REAL,DOUBLEPRECISION)

   if (present(fmt)) then
      select type(gval)
      type is (integer)
         fmt_local='(i0)'
         if(fmt.ne.'') fmt_local=fmt
         write(chars,fmt_local,iostat=err_local)gval
      type is (real)
         fmt_local='(bz,g23.10e3)'
         if(fmt.ne.'') fmt_local=fmt
         write(chars,fmt_local,iostat=err_local)gval
      type is (doubleprecision)
         fmt_local='(bz,g0)'
         if(fmt.ne.'') fmt_local=fmt
         write(chars,fmt_local,iostat=err_local)gval
      end select
      if(fmt.eq.'') then
         chars=adjustl(chars)
         call trimzeros(chars)
      endif
   else                                                  ! no explicit format option present
      select type(gval)
      type is (integer)
         write(chars,*,iostat=err_local)gval
      type is (real)
         write(chars,*,iostat=err_local)gval
      type is (doubleprecision)
         write(chars,*,iostat=err_local)gval
      end select
      chars=adjustl(chars)
      if(index(chars,'.').ne.0) call trimzeros(chars)
   endif

   if(present(length)) then ; length=len_trim(chars) ; endif

   if(present(err)) then ; err=err_local ; endif

end subroutine value_to_string
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()!
!===================================================================================================================================
!! NAME
!!    trimzeros(3fp) - [M_strings]Delete trailing zeros from numeric decimal string
!! SYNOPSIS
!!    subroutine trimzeros(str)
!! 
!!     character(len=*)  :: str
!! DESCRIPTION
!!    TRIMZEROS(3f) deletes trailing zeros from a string representing a
!!    number. If the resulting string would end in a decimal point, one
!!    trailing zero is added.
!! EXAMPLES
!!    Sample program:
!! 
!!       program demo_trimzeros
!!       use M_strings, only : trimzeros
!!       character(len=:),allocatable :: string
!!       write(*,*)trimzeros('123.450000000000')
!!       write(*,*)trimzeros('12345')
!!       write(*,*)trimzeros('12345.')
!!       write(*,*)trimzeros('12345.00e3')
!!       end program demo_trimzeros
subroutine trimzeros(string)
character(len=*),parameter::ident="@(#)M_strings::trimzeros(3fp): Delete trailing zeros from numeric decimal string"
! if zero needs added at end assumes input string has room
character(len=*)             :: string
character(len=len(string)+2) :: str
character(len=len(string))   :: exp          ! the exponent string if present
integer                      :: ipos         ! where exponent letter appears if present
integer                      :: i, ii
   str=string                                ! working copy of string
   ipos=scan(str,'eEdD')                     ! find end of real number if string uses exponent notation
   if(ipos>0) then                           ! letter was found
      exp=str(ipos:)                         ! keep exponent string so it can be added back as a suffix
      str=str(1:ipos-1)                      ! just the real part, exponent removed will not have trailing zeros removed
   endif
   if(index(str,'.').eq.0)then               ! if no decimal character in original string add one to end of string
      ii=len_trim(str)
      str(ii+1:ii+1)='.'                     ! add decimal to end of string
   endif
   do i=len_trim(str),1,-1                   ! scanning from end find a non-zero character
      select case(str(i:i))
      case('0')                              ! found a trailing zero so keep trimming
         cycle
      case('.')                              ! found a decimal character at end of remaining string
         if(i.le.1)then
            str='0'
         else
            str=str(1:i-1)
         endif
         exit
      case default
         str=str(1:i)                        ! found a non-zero character so trim string and exit
         exit
      end select
   end do
   if(ipos>0)then                            ! if originally had an exponent place it back on
      string=trim(str)//trim(exp)
   else
      string=str
   endif
end subroutine trimzeros
end module M_utilities
!-----------------------------------------------------------------------------------------------------------------------------------
!>NAME
!!    asa2pdf(1f) - Convert text files with/without ASA carriage control
!!                  to an Adobe PDF file.
!!
!!SYNOPSIS
!!   asa2pdf -o output_filename -i input_filename
!!           -g gray_scale_shade -b lines_alternately_shaded -d dashcode
!!           -s top_middle_page_label -t top_left_page_label
!!           -P # add page numbers
!!           -l lines_per_page -f font_name -S columns_to_shift_data
!!           -N # add line numbers
!!           -H page_height -W page_width -u points_per_unit
!!           -L left_margin -R right_margin -B bottom_margin -T top_margin
!!           -help -version -show
!!
!!DESCRIPTION
!!
!!   Basically, asa2pdf(1) emulates a line printer that recognizes ASA
!!   carriage control. That is, it lets you convert ASCII text files using
!!   ASA carriage control into Adobe "clear text" PDF files instead of a
!!   printed page.
!!
!!   The PDF is clear-text ASCII so that it is easy to still use other
!!   Unix/Linux utilities such as spell(1), diff(1), grep(1), .... on the
!!   output files.
!!
!!   To properly view the output requires a PDF processor (such
!!   as xpdf(1),acroread(1)/AcroRd32, gv(1) or ghostview(1), ...).
!!   Most modern systems can view, mail and print PDF files.
!!
!!   The default layout generates a landscape 132-column 60-line format with
!!   every other two lines shaded. A variety of switches are available to
!!   let you easily print files with no vertical carriage control, and in
!!   portrait mode too. There are options to use dashed lines instead of
!!   shading, to set different margins, and so on.
!!
!!   WHAT IS ASA CARRIAGE CONTROL?
!!
!!   The ASA carriage control standard was the first important formatting
!!   standard for printing and viewing text files. The standard was almost
!!   universally adapted by printer manufacturers of the time (and printers
!!   were a much more common output device than interactive displays).
!!
!!   Most commercial high-level programs at the time the standard was
!!   created were either FORTRAN or COBOL; so nearly all early FORTRAN
!!   output used ASA carriage control
!!   (ASA was the American Standards Association -- now ANSI).
!!   This FORTRAN/ASA association became so strong that the standard is
!!   sometimes referred to as the "Fortran carriage control standard" (FCC).
!!   Indeed, even though ASA is no longer commonly directly supported on
!!   desktop printers, it was part of the Fortran 90 standard (this was
!!   dropped in Fortran 2003 -- how a printer processes files is really
!!   not directly part of any programming language).
!!
!!   Times have changed, and the once nearly ubiquitous ASA standard
!!   is poorly supported on Unix and MSWindows machines in particular
!!   (Direct operating-system support of ASA files was once common, but
!!   is now rare).
!!
!!   But no alternative as simple has emerged for output files
!!   that truly replaces the ASA standard (although machine control
!!   characters (ctrl-H, ctrl-L, ...) have come close they have their
!!   own issues).
!!
!!   So many programs using ASA-based formatting have not been changed,
!!   and use commands like asa(1)/nasa(1), and fpr(1) to allow the files to
!!   be printed as desired but NOT to generally be viewed properly on-line,
!!   and printing itself is becoming less common.
!!
!!   So the problem isn't so much with ASA files, but that today's
!!   infrastructure does not support the format well. The asa2pdf(1)
!!   program bridges the gap by allowing you to still make and manipulate
!!   ASA files until you want to print or email them, at which time you
!!   can quickly convert them to an Adobe PDF file.
!!
!!USAGE
!!
!!   asa2pdf(1) reads input from standard input. By default the first
!!   character of each line is interpreted as a control character. Lines
!!   beginning with any character other than those listed in the ASA
!!   carriage-control characters table or in the list of extensions below
!!   are interpreted as if they began with a blank, and an appropriate
!!   diagnostic appears on standard error. The first character of each
!!   line is not printed.
!!
!!   ASA Carriage Control Characters
!!
!!        +------------+-----------------------------------------------+
!!        | Character  |                                               |
!!        +------------+-----------------------------------------------+
!!        | +          | Do not advance; overstrike previous line.     |
!!        | blank      | Advance one line.                             |
!!        | null lines | Treated as if they started with a blank       |
!!        | 0          | Advance two lines.                            |
!!        | -          | Advance three lines (IBM extension).          |
!!        | 1          | Advance to top of next page.                  |
!!        | all others | Discarded (except for extensions listed below)|
!!        +------------+-----------------------------------------------+
!!   Extensions
!!
!!       H   Advance one-half line.
!!       R   Do not advance; overstrike previous line. Use red text color
!!       G   Do not advance; overstrike previous line. Use green text color
!!       B   Do not advance; overstrike previous line. Use blue text color
!!       r   Advance one line. Use red text color
!!       g   Advance one line. Use green text color
!!       b   Advance one line. Use blue text color
!!       ^   Overprint but add 127 to the ADE value of the character
!!           (ie., use ASCII extended character set)
!!
!!OPTIONS
!!       -o outputfile  Name of Adobe PDF output file to create
!!       -i inputfile   Name of text file to read. Defaults to stdin.
!!
!!    PRINTABLE PAGE AREA
!!
!!      The page size may be specified using -H for height, -W for width, and -u
!!      to indicate the points per unit (72 makes H and W in inches,
!!      1 is used when units are in font points). For example:
!!
!!       -u 72 -H 8.5 -W 11   # page Height and Width in inches
!!       -T 0.5 -B 0.5 -L 0.5 -R 0.5   # margins (Top, Bottom, Left, Right)
!!
!!      common media sizes with -u 1:
!!
!!       +-------------------+------+------------+
!!       | name              |  W   |        H   |
!!       +-------------------+------+------------+
!!       | Letterdj (11x8.5) | 792  |       612  | (LandScape)
!!       | A4dj              | 842  |       595  |
!!       | Letter (8.5x11)   | 612  |       792  | (Portrait)
!!       | Legal             | 612  |       1008 |
!!       | A5                | 420  |       595  |
!!       | A4                | 595  |       842  |
!!       | A3                | 842  |       1190 |
!!       +-------------------+------+------------+
!!
!!    SHADING
!!        -g 0.800781      gray-scale value for shaded bars ( 0 < g 1 )
!!                         0 is black, 1 is white.
!!        -b 2             repeat shade pattern every N lines
!!        -d ' '           dashcode pattern
!!                         The pattern is a series of integers defining an
!!                         on-off sequence in user units used to create a
!!                         dash pattern. A single digit "N" implies a pattern
!!                         of "N N". (seems buggy)
!!
!!    MARGIN LABELS
!!       -s ''             top middle page label.
!!       -t ''             top left page label.
!!       -P                add page numbers to right corners
!!
!!    TEXT OPTIONS
!!       -l 60             lines per page
!!       -f Courier        font names: Courier, Courier-Bold,Courier-Oblique
!!                         Helvetica, Symbol, Times-Bold, Helvetica-Bold,
!!                         ZapfDingbats, Times-Italic, Helvetica-Oblique,
!!                         Times-BoldItalic, Helvetica-BoldOblique,
!!                         Times-Roman, Courier-BoldOblique
!!
!!       -S 0              right shift 1 for non-ASA files
!!       -N                add line numbers
!!    INFORMATION
!!       -version          display version number
!!       -help             display this help
!!
!!ENVIRONMENT VARIABLES
!!     o $IMPACT_TOP Will be printed in large red letters across the page top.
!!     o $IMPACT_GRAY sets the default gray-scale value, same as the -g switch.
!!
!!EXAMPLES
!! Sample input:
!!
!!  > The numbers are plain underlined double-struck over-struck
!!  >+                      __________ double-struck ///////////
!!  >R                                               ///////////
!!  > abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-=_+()*&^%$#@!\|[]{};':",.<>/?`~
!!  >
!!  >r red
!!  >g     green
!!  >b           blue
!!  > PRIMARY:
!!  >R         red
!!  >G             green
!!  >B                   blue
!!  > 1/2 line advance
!!  >H                1
!!  >H                 2         a-1
!!  >H                  3       Z
!!  >H                   4       b
!!  > back to a normal line
!!
!! Sample commands:
!!
!!     # create non-ASA file in portrait mode with a dashed line under every line
!!     asa2pdf -S 1 -W 8.5 -H 11 -b 1 -d '2 4 1' -T 1 -B .75 -o paper.pdf < INFILE
!!
!!     # banner on top
!!     env IMPACT_GRAY=1 IMPACT_TOP=CONFIDENTIAL asa2pdf -o paper.pdf < test.txt
!!
!!     # 132 landscape
!!      asa2pdf -s LANDSCAPE -o paper.pdf <asa2pdf.c
!!
!!     # 132 landscape with line numbers with dashed lines
!!      asa2pdf -s 'LANDSCAPE LINE NUMBERS' -d '3 1 2' \
!!      -N -T .9 -o paper.pdf <asa2pdf.c
!!
!!     # portrait 80 non-ASA file with dashed lines
!!      asa2pdf -s PORTRAIT -S 1 -W 8.5 -H 11 -b 1 -d '2 4 1' \
!!      -T 1 -B .75 -o paper.pdf < asa2pdf.c
!!
!!     # portrait 80 with line numbers , non-ASA
!!      asa2pdf -s 'PORTRAIT LINE NUMBERS' -l 66 -S 1 -W 8.5 -H 11 \
!!      -b 1 -T 1 -B .75 -N -o paper.pdf < asa2pdf.c
!!
!!     # titling
!!      asa2pdf -d '1 0 1' -t "$USER" -b 1 -P -N -T 1 \
!!      -s "asa2pdf.c" -o paper.pdf <asa2pdf.c
!!
!!SEE ALSO
!!
!!   ALTERNATIVES TO ASA2PDF
!!
!!   About the only standard ASA support on Unix variants is that some
!!   contain the asa(1)/fpr(1) and nasa(1) commands for converting ASA text
!!   files into and from text files with machine control (MC) characters
!!   such as form-feed, backspace, carriage-return, .... Most personal
!!   printers will no longer properly print ASA files directly, but they
!!   will often correctly print files with simple MC characters
!!   (Note that the asa(1) command is referenced in the POSIX.2 standard).
!!
!!   Furthermore, if a printer does not directly support MC characters,
!!   text conversion utilities such as enscript(1) and a2ps(1) can
!!   often be used to print the files (usually by converting the files
!!   to PostScript or PCL). Such utilities support features such as
!!   titling, page numbering, and other useful options.
!!
!!   Programs like "Adobe Distiller" can convert text to a PDF; as well as
!!   editors such as OpenOffice. In fact, most modern document-formatting
!!   editors can read in an ASCII text file and save it as an Adobe
!!   PDF file.
!!
!!   HTML and PostScript/PDF and PCL files are the alternatives often
!!   incorporated to satisfy simple formatting criteria --
!!   yet HTML is not printer-oriented;
!!   and PDF files are complex to write from a simple program, and PCL is
!!   vendor-specific and has few on-line viewers available for it.
!!
!!
!!   Assuming converting the Fortran program to just write a plain ASCII
!!   file instead of an ASA file is not acceptable, More extensive flat-text
!!   formatting is available using
!!
!!   o HTML, *roff and LaTex-related file formats
!!   o libraries for writing more sophisticated PostScript, PDF, and HTML/CSS files
!!   o XML files formatted using Cascading Style Sheet (CSS) files
!!   o RTF (Rich Text Format) files
!!
!!   Other Unix commands that can be useful in working with plain text and
!!   MC character files are
!!
!!      pr(1)           can be used to add page numbers and titles.
!!      expand(1)       can remove tab characters
!!      fold(1),fmt(1)  can be used to wrap the text
!!      cut(1)          can let you trim or select columns
!!      cat -n          can be used to add number lines
!!      paste(1)        can be used to put files side-by-side.
!!
!!asa(1)/nasa(1), fpr(1), enscript(1), a2ps(1), and ps2pdf(1).
!!
!@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
program asa2pdf
use M_utilities, only   : stderr
use M_kracken, only : kracken, sget, rget, iget, lget
use M_utilities, only : s2v, v2s
implicit none

! size of printable area
! Default unit is 72 points per inch

 character(len=256),save      :: GLOBAL_CENTER_TITLE       = ' '
 character(len=256),save      :: GLOBAL_DASHCODE           = ' '
 character(len=256),save      :: GLOBAL_FONT               = 'Courier'
 character(len=256),save      :: GLOBAL_LEFT_TITLE         = ' '
 character(len=:),allocatable :: GLOBAL_PAGE_LIST
 logical                      :: GLOBAL_LINENUMBERS        = .false.
 logical                      :: GLOBAL_PAGES              = .false.
 integer                      :: GLOBAL_ADD                = 0
 integer                      :: GLOBAL_LINECOUNT          = 0
 integer                      :: GLOBAL_NUM_PAGES          = 0
 integer                      :: GLOBAL_OBJECT_ID          = 1
 integer                      :: GLOBAL_PAGECOUNT          = 0
 integer                      :: GLOBAL_PAGE_TREE_ID
 integer                      :: GLOBAL_SHADE_STEP         = 2
 integer                      :: GLOBAL_SHIFT              = 0
 integer                      :: GLOBAL_STREAM_ID, GLOBAL_STREAM_LEN_ID
 integer                      :: GLOBAL_STREAM_START
 integer,parameter            :: GLOBAL_DIRECT             = 12
 integer,parameter            :: GLOBAL_OUTFILE            = 11
 integer                      :: GLOBAL_INFILE             = 10
 real                         :: GLOBAL_FONT_SIZE
 real                         :: GLOBAL_GRAY_SCALE         =   0.800781 ! gray-scale value
 real                         :: GLOBAL_LEAD_SIZE
 real                         :: GLOBAL_LINES_PER_PAGE     =  60.0
 real                         :: GLOBAL_PAGE_DEPTH         = 612.0
 real                         :: GLOBAL_PAGE_MARGIN_BOTTOM =  36.0
 real                         :: GLOBAL_PAGE_MARGIN_LEFT   =  40.0
 real                         :: GLOBAL_PAGE_MARGIN_RIGHT  =  39.0
 real                         :: GLOBAL_PAGE_MARGIN_TOP    =  36.0
 real                         :: GLOBAL_PAGE_WIDTH         = 792.0 ! Default is 72 points per inch
 real                         :: GLOBAL_TITLE_SIZE         =  20.0
 real                         :: GLOBAL_UNIT_MULTIPLIER    =   1.0
 real                         :: GLOBAL_YPOS

   character(len=100):: varname
   integer :: ios

   GLOBAL_PAGE_LIST=''

   call get_environment_variable("IMPACT_GRAY",varname)
   if(varname.eq.'') varname='0.800781' ! gray-scale value
   GLOBAL_GRAY_SCALE=s2v(varname)
   if(GLOBAL_GRAY_SCALE.lt.0) GLOBAL_GRAY_SCALE=0.800781

   call kracken('asa2pdf',' &
   & -i &
   & -o asa.pdf &
   ! SHADING
   ! gray-scale value  for shaded bars ( 0 < g < 1 ); 0 is black, 1 is white
   & -g 0.800781 &
   ! repeat shade pattern every N lines
   & -b 2 &
   ! dashcode pattern (seems buggy)
   & -d &
   ! MARGIN LABELS
   ! top middle page label.
   & -s &
   ! top left page label.
   & -t &
   ! add page numbers to right corners
   & -P       .F. &
   ! TEXT OPTIONS
   ! lines per page
   & -l 60 &
   ! font names
   & -f Courier &
   ! right shift N characters for non-ASA files
   & -S 0 &
   ! add line numbers
   & -N       .F. &
   ! PRINTABLE PAGE AREA
   !   The page size may be specified using -H for height, -W for width, and -u
   !   to indicate the points per unit (72 makes H and W in inches,
   !   1 is used when units are in font points). For example:
   ! page height
   & -H 612.0  &
   ! page width
   & -W 792.0 &
   ! units per inch
   & -u 1    &
   ! MARGINS
   ! left margin
   & -L 40.0 &
   ! right margin
   & -R 39.0 &
   ! bottom margin
   & -B 36.0 &
   ! top margin
   & -T 36.0 &

   & -show    .F. &
   & -help    .F. &
   & -version .F. &
   &')

   call help_usage(lget('asa2pdf_help'))              ! display help information and stop if true
   call help_version(lget('asa2pdf_version'))         ! display version information and stop if true

   OPEN(UNIT=GLOBAL_OUTFILE, FILE=trim(sget('asa2pdf_o')), ACCESS="STREAM", iostat=ios,form='formatted')
   if(ios.ne.0)then
      call stderr("E-R-R-O-R: asa2pdf(1) cannot open output file "//trim(sget('asa2pdf_o')))
      stop 2
   endif

   if(sget('asa2pdf_i').ne.'')then
      OPEN(UNIT=GLOBAL_INFILE, FILE=trim(sget('asa2pdf_i')), iostat=ios,form='formatted')
      if(ios.ne.0)then
         call stderr("E-R-R-O-R: asa2pdf(1) cannot open input file "//trim(sget('asa2pdf_i')))
         stop 2
      endif
   else
      GLOBAL_INFILE=5
   endif

   GLOBAL_UNIT_MULTIPLIER =     rget('asa2pdf_u')                          ! unit_divisor
   GLOBAL_PAGE_MARGIN_LEFT =    rget('asa2pdf_L')*GLOBAL_UNIT_MULTIPLIER;  ! Left margin
   GLOBAL_PAGE_MARGIN_RIGHT =   rget('asa2pdf_R')*GLOBAL_UNIT_MULTIPLIER;  ! Right margin
   GLOBAL_PAGE_MARGIN_BOTTOM =  rget('asa2pdf_B')*GLOBAL_UNIT_MULTIPLIER;  ! Bottom margin
   GLOBAL_PAGE_MARGIN_TOP =     rget('asa2pdf_T')*GLOBAL_UNIT_MULTIPLIER;  ! Top margin
   GLOBAL_PAGE_DEPTH =          rget('asa2pdf_H')*GLOBAL_UNIT_MULTIPLIER;  ! Height
   GLOBAL_PAGE_WIDTH =          rget('asa2pdf_W')*GLOBAL_UNIT_MULTIPLIER;  ! Width

   GLOBAL_GRAY_SCALE =          rget('asa2pdf_g')                          ! grayscale value for bars
   GLOBAL_LINES_PER_PAGE=       rget('asa2pdf_l')                          ! lines per page
   GLOBAL_SHADE_STEP =          iget('asa2pdf_b')                          ! increment for bars
   GLOBAL_SHIFT =               MAX(0,iget('asa2pdf_S'))                   ! right shift

   GLOBAL_CENTER_TITLE=sget('asa2pdf_s')                                   ! special label
   GLOBAL_LEFT_TITLE=sget('asa2pdf_t')                                     ! margin left label

   GLOBAL_DASHCODE=sget('asa2pdf_d')                                       ! dash code
   GLOBAL_FONT=sget('asa2pdf_f')                                           ! font

   GLOBAL_LINENUMBERS=lget('asa2pdf_N')                                    ! number lines
   GLOBAL_PAGES= lget('asa2pdf_P')                                         ! number pages

   if(GLOBAL_SHADE_STEP < 1 )then
      call stderr("W-A-R-N-I-N-G: asa2pdf(1) resetting -b "//v2s(GLOBAL_SHADE_STEP))
      GLOBAL_SHADE_STEP=1;
   endif

   if(GLOBAL_LINES_PER_PAGE < 1 )then
      call stderr("W-A-R-N-I-N-G: asa2pdf(1) resetting -l "//v2s(GLOBAL_LINES_PER_PAGE))
      GLOBAL_LINES_PER_PAGE=60;
   endif

   if(lget('asa2pdf_show'))then
      call showhelp()
      stop 3
   endif

   open(unit=GLOBAL_DIRECT,iostat=ios,access='direct',form='formatted',recl=34,status='scratch')
   if(ios.ne.0)then
      call stderr("E-R-R-O-R: asa2pdf(1) cannot open scratch file ")
      stop 3
   endif

   call dopages()

   CLOSE(UNIT=GLOBAL_OUTFILE,iostat=ios)
   CLOSE(UNIT=GLOBAL_DIRECT,iostat=ios)
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
contains
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
subroutine showhelp()
   write(*,'("-u ",g0," # unit multiplier")')          GLOBAL_UNIT_MULTIPLIER
   write(*,'("-T ",g0," # Top margin")')               GLOBAL_PAGE_MARGIN_TOP/GLOBAL_UNIT_MULTIPLIER
   write(*,'("-B ",g0," # Bottom margin")')            GLOBAL_PAGE_MARGIN_BOTTOM/GLOBAL_UNIT_MULTIPLIER
   write(*,'("-L ",g0," # Left margin")')              GLOBAL_PAGE_MARGIN_LEFT/GLOBAL_UNIT_MULTIPLIER
   write(*,'("-R ",g0," # Right margin")')             GLOBAL_PAGE_MARGIN_RIGHT/GLOBAL_UNIT_MULTIPLIER

   write(*,'("-W ",g0," # page Width")')               GLOBAL_PAGE_WIDTH/GLOBAL_UNIT_MULTIPLIER
   write(*,'("-H ",g0," # page Height")')              GLOBAL_PAGE_DEPTH/GLOBAL_UNIT_MULTIPLIER

   write(*,'("-g ",g0,    " # shading gray scale value ([black]0 <= g <= 1[white]")') GLOBAL_GRAY_SCALE
   write(*,'("-b ",i0,t14," # shading line increment")')    GLOBAL_SHADE_STEP
   write(*,'("-d ",a,     " # shading line dashcode")')     trim(GLOBAL_DASHCODE)

   write(*,'("-l ",g0,t14," # lines per page")')            GLOBAL_LINES_PER_PAGE
   write(*,'("-f ",a,t14, " # font name")')                 trim(GLOBAL_FONT)

   write(*,'("-s ",a,     " # margin label")')              trim(GLOBAL_CENTER_TITLE)
   write(*,'("-t ",a,     " # margin left label")')         trim(GLOBAL_LEFT_TITLE)
   write(*,'("-S ",i0,t14," # right shift")')               GLOBAL_SHIFT

   write(*,'("-N [flag=",g0,"] # add line numbers ")')      GLOBAL_LINENUMBERS
   write(*,'("-P [flag=",L1,"] # add page numbers")')       GLOBAL_PAGES
end subroutine showhelp
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
!>##REFERENCE
!!    8.4.3.6       Line Dash Pattern
!!
!!    The line dash pattern shall control the pattern of dashes and gaps used to stroke paths. It shall be specified by
!!    a dash array and a dash phase. The dash array's elements shall be numbers that specify the lengths of
!!    alternating dashes and gaps; the numbers shall be nonnegative and not all zero. The dash phase shall specify
!!    the distance into the dash pattern at which to start the dash. The elements of both the dash array and the dash
!!    phase shall be expressed in user space units.
!!
!!    Before beginning to stroke a path, the dash array shall be cycled through, adding up the lengths of dashes and
!!    gaps. When the accumulated length equals the value specified by the dash phase, stroking of the path shall
!!    begin, and the dash array shall be used cyclically from that point onward. Table 56 shows examples of line
!!    dash patterns. As can be seen from the table, an empty dash array and zero phase can be used to restore the
!!    dash pattern to a solid line.
!!
!!                       Table 56 ­ Examples of Line Dash Patterns
!!
!!      Dash Array       Appearance                   Description
!!      and Phase
!!
!!      [] 0                                          No dash; solid, unbroken lines
!!
!!      [3] 0                                         3 units on, 3 units off, ...
!!
!!      [2] 1                                         1 on, 2 off, 2 on, 2 off, ...
!!
!!      [2 1] 0                                       2 on, 1 off, 2 on, 1 off, ...
!!
!!      [3 5] 6                                       2 off, 3 on, 5 off, 3 on, 5 off, ...
!!
!!      [ 2 3 ] 11                                    1 on, 3 off, 2 on, 3 off, 2 on, ...
!!
!!    Dashed lines shall wrap around curves and corners just as solid stroked lines do. The ends of each dash shall
!!    be treated with the current line cap style, and corners within dashes shall be treated with the current line join
!!    style. A stroking operation shall take no measures to coordinate the dash pattern with features of the path; it
!!    simply shall dispense dashes and gaps along the path in the pattern defined by the dash array.
!!
!!    When a path consisting of several subpaths is stroked, each subpath shall be treated independently--that is,
!!    the dash pattern shall be restarted and the dash phase shall be reapplied to it at the beginning of each subpath.
subroutine print_bars()
real :: x1
real :: y1
real :: height
real :: width
real :: step

   write(GLOBAL_OUTFILE,'(f0.6," g")')GLOBAL_GRAY_SCALE ! gray-scale value
   ! If you want to add color,
   ! R G B rg where R G B are red, green, blue components
   ! in range 0.0 to 1.0 sets fill color, "RG" sets line
   ! color instead of fill color.
   !
   ! 0.60 0.82 0.60 rg
   !

   write(GLOBAL_OUTFILE,'(i0," i")')1

   x1=GLOBAL_PAGE_MARGIN_LEFT-0.1*GLOBAL_FONT_SIZE
   height=GLOBAL_SHADE_STEP*GLOBAL_LEAD_SIZE
   y1 = GLOBAL_PAGE_DEPTH - GLOBAL_PAGE_MARGIN_TOP - height- 0.22*GLOBAL_FONT_SIZE
   width=GLOBAL_PAGE_WIDTH-GLOBAL_PAGE_MARGIN_LEFT-GLOBAL_PAGE_MARGIN_RIGHT
   step=1.0

   if(GLOBAL_DASHCODE.ne.'')then
      write(GLOBAL_OUTFILE, '("0 w [",a,"] 0 d")')GLOBAL_DASHCODE      ! dash code array plus offset
   endif

   do while ( y1 >= (GLOBAL_PAGE_MARGIN_BOTTOM-height) )
      if(GLOBAL_DASHCODE .eq.'')then
            ! a shaded bar
            write(GLOBAL_OUTFILE,'(4(f0.6,1x),"re f")')x1,y1,width,height
            step=2.0

            !! x1 y1 m x2 y2 l S
            !! xxx w  # line width
            !write(GLOBAL_OUTFILE,'("0.6 0.8 0.6 RG",/,1x,f0.6,1x, f0.6," m ",%f %f," l S")')x1,y1,x1+width,y1

      else
             write(GLOBAL_OUTFILE,'(f0.6,1x,f0.6," m ")',advance='no') x1 ,y1
             write(GLOBAL_OUTFILE,'(f0.6,1x,f0.6," l s")')x1+width,y1
      endif
      y1=y1-step*height
   enddo

   if(GLOBAL_DASHCODE .ne. '')then
      write(GLOBAL_OUTFILE, '("[] 0 d")') ! set dash pattern to solid line
   endif

   write(GLOBAL_OUTFILE,'(i0," G")') 0
   write(GLOBAL_OUTFILE,'(i0," g")') 0 ! gray-scale value

 end subroutine print_bars
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
subroutine end_page()
integer :: stream_len
integer :: page_id

   page_id = GLOBAL_OBJECT_ID
   GLOBAL_OBJECT_ID=GLOBAL_OBJECT_ID+1

   call store_page(page_id)
   write(GLOBAL_OUTFILE,'("ET")')

   stream_len = tell_position(GLOBAL_OUTFILE) - GLOBAL_STREAM_START
   write(GLOBAL_OUTFILE,'("endstream",/,"endobj")')

   call start_object(GLOBAL_STREAM_LEN_ID)
   write(GLOBAL_OUTFILE,'(i0,/,"endobj")')stream_len

   call start_object(page_id);
   write(GLOBAL_OUTFILE,'("<</Type/Page/Parent ",i0," 0 R/Contents ",i0," 0 R>>",/,"endobj")')GLOBAL_PAGE_TREE_ID, GLOBAL_STREAM_ID

end subroutine end_page
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
subroutine printstring(buffer)
character(len=*) :: buffer
! Print string as (escaped_string) where ()\ characters have a preceding \ character added
character(len=1) :: c
integer          :: i

   write(GLOBAL_OUTFILE,'(a)',advance='no')'('

   if(GLOBAL_LINENUMBERS )then
      write(GLOBAL_OUTFILE,'(i6.6,1x)')GLOBAL_LINECOUNT
   endif

   do i=1,len(buffer)

      c=char(ichar(buffer(i:i))+GLOBAL_ADD)

      select case(c)
      case ('(',')','\')
         write(GLOBAL_OUTFILE,'("\")',advance='no')
      end select

      write(GLOBAL_OUTFILE,'(a)',advance='no')c

   enddo

   write(GLOBAL_OUTFILE,'(")")',advance='no')

end subroutine printstring
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
function tell_position(lun) result (position)
integer,intent(in) :: lun
integer :: position
integer :: ios
  INQUIRE(UNIT=lun, POS=position,iostat=ios)
  if(ios.ne.0)then
     call stderr('*asa2pdf* cannot determine position of output file')
  endif
  position=position-1
end function tell_position
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
subroutine printme(xvalue,yvalue,string)
real,intent(in)             :: xvalue
real,intent(in)             :: yvalue
character(len=*),intent(in) :: string
   write(GLOBAL_OUTFILE,'("BT /F2 ",f0.6," Tf ",f0.6," ",f0.6," Td")')GLOBAL_TITLE_SIZE,xvalue,yvalue
   call printstring(string)
   write(GLOBAL_OUTFILE,'(" Tj ET")')
end subroutine printme
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
subroutine dopages()
integer :: catalog_id
integer :: font_id0
integer :: font_id1
integer :: start_xref
character(len=34) :: string
integer :: i

write(GLOBAL_OUTFILE,'("%PDF-1.0")')

!   Note: If a PDF file contains binary data, as most do , it is
!   recommended that the header line be immediately followed by a
!   comment line containing at least four binary characters--that is,
!   characters whose codes are 128 or greater. This will ensure proper behavior of file
!   transfer applications that inspect data near the beginning of a
!   file to determine whether to treat the file's contents as text or as binary.

   write(GLOBAL_OUTFILE,'("%",*(a))')char(128),char(129),char(130),char(131)
   write(GLOBAL_OUTFILE,'("% PDF: Adobe Portable Document Format")')

   GLOBAL_LEAD_SIZE=(GLOBAL_PAGE_DEPTH-GLOBAL_PAGE_MARGIN_TOP-GLOBAL_PAGE_MARGIN_BOTTOM)/GLOBAL_LINES_PER_PAGE
   GLOBAL_FONT_SIZE=GLOBAL_LEAD_SIZE

   GLOBAL_OBJECT_ID = 1;
   GLOBAL_PAGE_TREE_ID = GLOBAL_OBJECT_ID
   GLOBAL_OBJECT_ID= GLOBAL_OBJECT_ID +1

   call do_text()

   font_id0 = GLOBAL_OBJECT_ID
   call start_object(font_id0)
   GLOBAL_OBJECT_ID= GLOBAL_OBJECT_ID +1

   write(GLOBAL_OUTFILE,'("<</Type/Font/Subtype/Type1/BaseFont/",a,"/Encoding/WinAnsiEncoding>>")')trim(GLOBAL_FONT)
   write(GLOBAL_OUTFILE,'("endobj")')

   font_id1 = GLOBAL_OBJECT_ID
   call start_object(font_id1)
   GLOBAL_OBJECT_ID= GLOBAL_OBJECT_ID +1

   write(GLOBAL_OUTFILE,'("<</Type/Font/Subtype/Type1/BaseFont/",a,"/Encoding/WinAnsiEncoding>>")')trim(GLOBAL_FONT)
   write(GLOBAL_OUTFILE,'("endobj")')

   call start_object(GLOBAL_PAGE_TREE_ID)

   write(GLOBAL_OUTFILE,'("<</Type /Pages /Count ",i0)') GLOBAL_NUM_PAGES

   write(GLOBAL_OUTFILE,'("/Kids[")')
   write(GLOBAL_OUTFILE,'(a)') GLOBAL_PAGE_LIST ! '(i0," 0 R",new_line("A"))'
   write(GLOBAL_OUTFILE,'("]")')

   write(GLOBAL_OUTFILE,'("/Resources<</ProcSet[/PDF/Text]/Font<</F0 ",i0," 0 R")') font_id0
   write(GLOBAL_OUTFILE,'("/F1 ",i0," 0 R")') font_id1
   write(GLOBAL_OUTFILE,'(" /F2<</Type/Font/Subtype/Type1/BaseFont/Courier-Bold/Encoding/WinAnsiEncoding >> >>")')

   !write(GLOBAL_OUTFILE,'(">>/MediaBox [ 0 0 ",f0.6,1x,f0.6," ]")') GLOBAL_PAGE_WIDTH, GLOBAL_PAGE_DEPTH
   write(GLOBAL_OUTFILE,'(">>/MediaBox [ 0 0 ",a,1x,a," ]")') v2s(GLOBAL_PAGE_WIDTH), v2s(GLOBAL_PAGE_DEPTH)

   write(GLOBAL_OUTFILE,'(">>")')
   write(GLOBAL_OUTFILE,'("endobj")')

   catalog_id = GLOBAL_OBJECT_ID
   GLOBAL_OBJECT_ID= GLOBAL_OBJECT_ID +1
   call start_object(catalog_id)
   write(GLOBAL_OUTFILE,'("<</Type/Catalog/Pages ",i0," 0 R>>")') GLOBAL_PAGE_TREE_ID
   write(GLOBAL_OUTFILE,'("endobj")')
   start_xref = tell_position(GLOBAL_OUTFILE)
   write(GLOBAL_OUTFILE,'("xref")')
   write(GLOBAL_OUTFILE,'("0 ",i0)') GLOBAL_OBJECT_ID
   write(GLOBAL_OUTFILE,'("0000000000 65535 f ")')

   do i=1,GLOBAL_OBJECT_ID-1
      read(GLOBAL_DIRECT,'(a)',REC=i) string
      write(GLOBAL_OUTFILE,'(a)',advance='no') trim(string)
   enddo

   write(GLOBAL_OUTFILE,'("trailer")')
   write(GLOBAL_OUTFILE,'("<<")')
   write(GLOBAL_OUTFILE,'("/Size ",i0)') GLOBAL_OBJECT_ID
   write(GLOBAL_OUTFILE,'("/Root ",i0," 0 R")') catalog_id
   write(GLOBAL_OUTFILE,'(">>")')
   write(GLOBAL_OUTFILE,'("startxref")')
   write(GLOBAL_OUTFILE,'(i0)') start_xref
   write(GLOBAL_OUTFILE,'("%%EOF")')
end subroutine dopages
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
 subroutine start_page()
   GLOBAL_STREAM_ID = GLOBAL_OBJECT_ID
   GLOBAL_OBJECT_ID= GLOBAL_OBJECT_ID +1
   GLOBAL_STREAM_LEN_ID = GLOBAL_OBJECT_ID
   GLOBAL_OBJECT_ID= GLOBAL_OBJECT_ID +1
   GLOBAL_PAGECOUNT= GLOBAL_PAGECOUNT+1
   call start_object(GLOBAL_STREAM_ID)

   write(GLOBAL_OUTFILE,'("<< /Length ",i0," 0 R >>")') GLOBAL_STREAM_LEN_ID
   write(GLOBAL_OUTFILE,'("stream")')

   GLOBAL_STREAM_START = tell_position(GLOBAL_OUTFILE)
   call print_bars()
   call print_margin_label()

   write(GLOBAL_OUTFILE,'("BT")')
   write(GLOBAL_OUTFILE,'("/F0 ",f0.6," Tf")') GLOBAL_FONT_SIZE

   GLOBAL_YPOS = GLOBAL_PAGE_DEPTH - GLOBAL_PAGE_MARGIN_TOP

   write(GLOBAL_OUTFILE,'(f0.6,1x,f0.6," Td")') GLOBAL_PAGE_MARGIN_LEFT, GLOBAL_YPOS
   write(GLOBAL_OUTFILE,'(f0.6," TL")') GLOBAL_LEAD_SIZE

end subroutine start_page
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
subroutine print_margin_label()
character(len=80) :: line
real              :: charwidth
real              :: start
logical           :: hold

hold=GLOBAL_LINENUMBERS
GLOBAL_LINENUMBERS=.false.

call printme_top()

if(GLOBAL_CENTER_TITLE .ne.  '' )then

   ! assuming fixed-space font Courier-Bold
   charwidth=GLOBAL_TITLE_SIZE*0.60
   start=GLOBAL_PAGE_MARGIN_LEFT &
      &    +((GLOBAL_PAGE_WIDTH-GLOBAL_PAGE_MARGIN_LEFT-GLOBAL_PAGE_MARGIN_RIGHT)/2.0) &
      &    -(len_trim(GLOBAL_CENTER_TITLE)*charwidth/2.0)

   call printme(start,GLOBAL_PAGE_DEPTH-GLOBAL_PAGE_MARGIN_TOP+0.12*GLOBAL_TITLE_SIZE,GLOBAL_CENTER_TITLE)
   call printme(start,GLOBAL_PAGE_MARGIN_BOTTOM-GLOBAL_TITLE_SIZE,GLOBAL_CENTER_TITLE)
endif

if(GLOBAL_PAGES)then                                 ! print page numbers on page
   charwidth=GLOBAL_TITLE_SIZE*0.60
   write(line,'("Page ",i0.4)')GLOBAL_PAGECOUNT
   start=GLOBAL_PAGE_WIDTH-GLOBAL_PAGE_MARGIN_RIGHT-(len_trim(line)*charwidth) ! Right Justified
   call printme(start,GLOBAL_PAGE_DEPTH-GLOBAL_PAGE_MARGIN_TOP+0.12*GLOBAL_TITLE_SIZE,line)
   call printme(start,GLOBAL_PAGE_MARGIN_BOTTOM-GLOBAL_TITLE_SIZE,line)
endif

if(GLOBAL_LEFT_TITLE .ne. "" )then
   start=GLOBAL_PAGE_MARGIN_LEFT     ! Left justified
   call printme(start,GLOBAL_PAGE_DEPTH-GLOBAL_PAGE_MARGIN_TOP+0.12*GLOBAL_TITLE_SIZE,GLOBAL_LEFT_TITLE)
   call printme(start,GLOBAL_PAGE_MARGIN_BOTTOM-GLOBAL_TITLE_SIZE,GLOBAL_LEFT_TITLE)
endif

GLOBAL_LINENUMBERS=hold

end subroutine print_margin_label
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
subroutine printme_top()
character(len=256) :: IMPACT_TOP
real :: charwidth
real :: xvalue
real :: yvalue
real :: text_size=20.0
call get_environment_variable('IMPACT_TOP',impact_top)
if( impact_top .ne. '' )then
   charwidth=text_size*0.60                              ! assuming fixed-space font Courier-Bold
   write(GLOBAL_OUTFILE,'("1.0 0.0 0.0 rg")')            ! gray-scale value
   yvalue=GLOBAL_PAGE_DEPTH-text_size
   xvalue=GLOBAL_PAGE_MARGIN_LEFT &
      & +((GLOBAL_PAGE_WIDTH-GLOBAL_PAGE_MARGIN_LEFT-GLOBAL_PAGE_MARGIN_RIGHT)/2.0) &
      & -(len_trim(IMPACT_TOP)*charwidth/2.0)

   write(GLOBAL_OUTFILE,'("BT /F2 ",f0.6," Tf ",f0.6,1x,f0.6," Td")')text_size,xvalue,yvalue
   call printstring(IMPACT_TOP)
   write(GLOBAL_OUTFILE,'(" Tj ET")')

   write(GLOBAL_OUTFILE,'("0.0 0.0 0.0 rg")')            ! gray-scale value
endif
end subroutine printme_top
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
subroutine increment_ypos(mult)
real,intent(in) :: mult
   if (GLOBAL_YPOS < GLOBAL_PAGE_DEPTH - GLOBAL_PAGE_MARGIN_TOP ) then  ! if not at top of page
      GLOBAL_YPOS = GLOBAL_YPOS + GLOBAL_LEAD_SIZE*mult
   endif
end subroutine increment_ypos
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
subroutine store_page(id)
integer,intent(in) :: id
character(len=80) :: string
   write(string,'(i0," 0 R")')id
   GLOBAL_PAGE_LIST = GLOBAL_PAGE_LIST // trim(string) //new_line('A')
   GLOBAL_NUM_PAGES= GLOBAL_NUM_PAGES + 1
end subroutine store_page
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
subroutine start_object(id)
integer,intent(in) :: id
character(len=34)  :: string

   ! record position of start of object in file for writing the reference table at the end
   write(string,'(i10.10," 00000 n ",a)') tell_position(GLOBAL_OUTFILE) ,new_line('a')
   write(GLOBAL_DIRECT,'(a)',rec=id)string

   ! write the beginning of the object definition
   write(GLOBAL_OUTFILE,'(i0," 0 obj")') id

end subroutine start_object
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
subroutine do_text()
character(len=8192) :: buffer
character(len=1)    :: ASA
integer             :: black
integer             :: ios

call start_page()
buffer=' '

INFINITE: do
   read(GLOBAL_INFILE,'(a)',iostat=ios) buffer(GLOBAL_SHIFT+1:)
   if(ios.ne.0)exit INFINITE
   GLOBAL_LINECOUNT= GLOBAL_LINECOUNT+1
   black=0
   GLOBAL_ADD=0

   ! +1 for roundoff , using floating point point units
   if(GLOBAL_YPOS <= (GLOBAL_PAGE_MARGIN_BOTTOM+1) .and. len_trim(buffer) .ne. 0 .and. buffer(1:1) .ne. '+' ) then
      call end_page()
      call start_page()
   endif

   if(len_trim(buffer) .eq. 0)then ! blank line
      write(GLOBAL_OUTFILE,'("T*")')
   else
      ASA=buffer(1:1);

      select case(ASA)

         case ('1')     ! start a new page before processing data on line
            if (GLOBAL_YPOS < GLOBAL_PAGE_DEPTH - GLOBAL_PAGE_MARGIN_TOP )then
               call end_page()
               call start_page()
            endif

         case ('0')        ! put out a blank line before processing data on line
               write(GLOBAL_OUTFILE,'("T*")')
               GLOBAL_YPOS = GLOBAL_YPOS - GLOBAL_LEAD_SIZE

         case ('-')        ! put out two blank lines before processing data on line
            write(GLOBAL_OUTFILE,'("T*")')
            GLOBAL_YPOS = GLOBAL_YPOS - GLOBAL_LEAD_SIZE;
            GLOBAL_YPOS = GLOBAL_YPOS - GLOBAL_LEAD_SIZE;

         case ('+')        ! print at same y-position as previous line
            write(GLOBAL_OUTFILE,'("0 ",f0.6," Td")')GLOBAL_LEAD_SIZE
            call increment_ypos(1.0)

         case ('R','G','B')  ! RED/GREEN/BLUE print at same y-position as previous line
            if(ASA .eq. 'R') write(GLOBAL_OUTFILE,'("1.0 0.0 0.0 rg")') ! red text
            if(ASA .eq. 'G') write(GLOBAL_OUTFILE,'("0.0 1.0 0.0 rg")') ! green text
            if(ASA .eq. 'B') write(GLOBAL_OUTFILE,'("0.0 0.0 1.0 rg")') ! blue text
            black=1
            write(GLOBAL_OUTFILE,'("0 ",f6.0," Td")')GLOBAL_LEAD_SIZE
            call increment_ypos(1.0)

         case ('H')        ! 1/2 line advance
            write(GLOBAL_OUTFILE,'("0 ",f0.6," Td")')GLOBAL_LEAD_SIZE/2.0
            call increment_ypos(0.5)

         case ('r','g','b') ! RED, GREEN, BLUE  print
            if(ASA .eq. 'r') write(GLOBAL_OUTFILE,'("1.0 0.0 0.0 rg")') ! red text
            if(ASA .eq. 'g') write(GLOBAL_OUTFILE,'("0.0 1.0 0.0 rg")') ! green text
            if(ASA .eq. 'b') write(GLOBAL_OUTFILE,'("0.0 0.0 1.0 rg")') ! blue text
            black=1

         case ('^')        ! print at same y-position as previous line like + but add 127 to character
            write(GLOBAL_OUTFILE,'("0 ",f0.6," Td")')GLOBAL_LEAD_SIZE
            call increment_ypos(1.0)
            GLOBAL_ADD=127

         case (char(12))       ! ctrl-L is a common form-feed character on Unix, but NOT ASA
            call end_page()
            call start_page()

         case (' ')
         case default
         call stderr("unknown ASA carriage control character "//ASA)

      end select

      call printstring(trim(buffer(2:)))
      write(GLOBAL_OUTFILE,'("''")')

   endif
   GLOBAL_YPOS = GLOBAL_YPOS - GLOBAL_LEAD_SIZE
   if(black .ne. 0)then
      write(GLOBAL_OUTFILE,'("0.0 0.0 0.0 rg")') ! black text
   endif

   enddo INFINITE
   call end_page()
end subroutine do_text
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================
end program asa2pdf
!===================================================================================================================================
!()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()=
!===================================================================================================================================

category: code