/*
**  ps-anc5, 19 Feb 1996, enhanced by Phil Stringer (P.Stringer@mcc.ac.uk)
**           - all comments/bugs should now go to Phil Stringer
**  ps-anc, 9 September 1994, by Fred Wheeler (wheeler@ipl.rpi.edu)
**
**  BRIEF DESCRIPTION
**
**  This LifeLines report program generates Postscript ancestral and
**  descendant charts.  The ancestral charts can include the siblings
**  of all direct ancestors (aunts, uncles, great-aunts, great-uncles,
**  etc.).  A multi-page poster chart can also be generated.  The
**  chart format is based on the program GedChart, by Tom Blumer.
**
**  The Postscript file created can be sent to any size printer; it
**  will automatically adapt the size of the chart.  I send the same
**  file to A-size (8.5 by 11) and B-size (11 by 17) printers.
**
**  After you use this program a few times, you should edit the
**  function interrogate_user().  This is the first function after
**  these comments and the global variable declarations.  This
**  function is set up to make it easy for you to configure what
**  questions this program should ask you each time and what default
**  values it should use for questions not asked.
**
**  Please contact me if you like this program, find any bugs, have
**  any bug fixes, or want to suggest improvements.  I am also always
**  on the lookout for better ancestral/descendant chart generating
**  programs.  If you know of a program that generates charts which
**  you like better than those generated by this program, please drop
**  me a line.
**
**  This report program works with the LifeLines Genealogical database
**  program only.  (see ftp://ftp.cac.psu.edu/pub/genealogy/lines/*)
**
**  CHANGE LOG
**
**  Changes since version 1:
**    Completely new descendant chart in addition to ancestral chart
**    Multi-page poster option
**    Multi-page charts scaled correctly (thanks to broman@Np.nosc.mil)
**    Maximum name length configurable by user (fixes long squashed names)
**    Option to supress siblings of later generations in ancestral charts
**    Checks that user selects a valid person (bug fix)
**    Can make a guess at whether a title is a prefix of suffix type
**    Use of titles is configurable (prefix, suffix, guess, none)
**    Birth/death/marriage date styles are configurable (may include place)
**    Corner message is slightly smaller, and chart will not overlap it
**    Marriage date is printed before death date
**
**  CREDITS
**
**  Code improvements received from:
**    Vincent Broman (broman@Np.nosc.mil)
**
**  Helpful comments received from:
**    Vincent Broman (broman@Np.nosc.mil)
**    Frank H. Flaesland (phranx@imr.no)
**    Linda Wilson (lwilson@mcc.com)
**    Stacy Johnson (sjohnson@oucsace.cs.ohiou.edu)
**    John F. Chandler (jchbn@cuvmb.cc.columbia.edu)
**    Susan Radel
**
**  Changes since version 2:
**    Birth/death/marriage date style addition (full date with short place).
**    Examples for including other fonts.
**    Option for bold lines/text for direct line of ascent.
**    Option to start on right or left of page.
**    Option for landscape or portrait format.
**    Small additional space between border and text to improve appearance.
**    Now fills the page if max generations > actual generations.
**    With multi-page output generations are multiple of x-pages to prevent
**      text split over sheets.
**    Option to show aunts/uncles from parents multiple marriages.
**
**  Changes since version 3:
**    Border enhanced at the corners.
**    Chart title font changed.
**    Lines now used to join families rather than being used as a framework.
**    Names now adjacent to line or halfway between if in 2 families.
**    Descendant chart has reduced lines and is more tree like
**
**  Changes since version 4:
**    Enhanced descendant chart
**    Automatic choice of chart type if no children or no ancestors
**    Multi page landscape bug fixed
**    Enhancements to user option specification
**    Character set enhanced to iso-8859-1
**    Additional titles
**
**  CREDITS
**
**  Code improvements received from:
**    Phil Stringer (p.stringer@mcc.ac.uk)
**
**  ABOUT GEDCHART (a different program)
**
**  This program includes postscript code written by Tom Blumer
**  (blumer@ptltd.com).  It is used here with his permission.  This
**  postscript code is from Tom Blumer's GedChart package.  The report
**  is very much like that generated by GedChart using the -Sa or -Sd
**  option.
**
**  GedChart is DOS program that generates ancestral and descendant
**  charts like this report program, and also fan charts.  GedChart
**  works directly from a GEDCOM file and is completely independent of
**  LifeLines.  It is currently up to version 1.6, which is a beta
**  version that may lead to a commercial product.  You can find
**  GedChart at ftp:oak.oakland.edu/pub/msdos/genealgy/gedcht16.zip
**
*/

global (high_pos_gen)         /* array, highest so far in each generation */
global (high_pos_all)         /* highest position so far for any generation */
global (high_depth)           /* highest depth so far */

global (name_height)          /* height of name text on chart */
global (date_height)          /* height of birth/death/marriage date text */

global (no_parent_extra)      /* constant, extra vert. line when no parent */

/* variables prompted from or configured by the user */

global (chart_type)           /* int, 0: ancestral, 1: descendant */
global (root_person)          /* indi, person for whom to generate the chart */
global (font_name)            /* string, name of font */
global (max_depth)            /* int, maximum number of generations */
global (chart_label)          /* string, label for corner of chart */
global (color_chart)          /* boolean, is chart in color */
global (multi_page)           /* boolean, is chart many page poster type */
global (x_pages)              /* int, number of horizontal pages */
global (y_pages)              /* int, number of vertical pages */
global (name_letters)         /* int, maximum number of letters in a name */
global (title_method)         /* int, code for how to insert titles */
global (depth_siblings)       /* int, number of generations to show siblings */
global (dateplace_birth)      /* int, date style for birth/death/marriage */
global (dateplace_death)
global (dateplace_marriage)
global (bold_chart)           /* int, direct line in bold 0: no, 1: yes */
global (mirror_chart)         /* int, root person on right 0: no, 1: yes */
global (half_sib)             /* int, show half-siblings 0: no, 1: yes */
global (portrait)             /* int, 0: landscape, 1: portrait */

/* variables to return values from procedures to make them functions */
global (do_anc_stack)         /* stack, function do_anc is recursive */
global (person_height_return)
global (is_prefix_title_return)
global (dateplace_return)

/* these three constants define how close branches of the tree can get */
global (branch_dist_prev)     /* minimum distance from previous generation */
global (branch_dist_same)     /* minimum distance from same generation */
global (branch_dist_next)     /* minimum distance from next generation */

/* stacks for storing the information for each person on the chart */
/* see proc's enqueue_person and dequeue_all_persons */

global (plist_person)  /* the person (to extract name, birth, death) */
global (plist_depth)   /* generation depth */
global (plist_pos)     /* vertical position */
global (plist_line)    /* 0,1 boolean, is direct ancestor? */
global (plist_mdate)   /* marriage date */
global (plist_anc)    /* 0,1 boolean, person has ancestor? */
global (plist_des)    /* 0,1 boolean, person has descendant? */

/* stacks for storing the information for each vertical line on the chart */
/* see proc's enqueue_vertical and dequeue_all_verticals */

global (llist_depth)   /* generation depth */
global (llist_low)     /* starting point */
global (llist_high)    /* finishing point */
global (llist_dash)    /* dashed lines */

/*
**  function: interrogate_user
**
**  This function is designed to be modified by the user.  It asks
**  many questions about how to configure the charts.  If your answer
**  to one of the questions is always the same, you can easily
**  hardwire your answer here so that you are never asked again.
**
**  An 'if' statement is wrapped around each question.  The 'if (1)'
**  can be changed to an 'if (0)' to make the program use the default
**  value defined in the 'else' clause instead of asking every time.
**
*/

func interrogate_user ()
{

/*
**  QUESTION: Who is the root person?
**
**  This question should always be asked, unless you always use the same
**  person, which is not likely.  If you do set a default, it is a string
**  representation of that persons number.
**
*/

  if (1)  {
    set (root_person, 0)
    while ( not (root_person) )  {
      getindimsg (root_person, "Identify root person for chart")
    }
  }  else  {
    set (root_person, indi ("1"))
  }

/*
**  QUESTION: What type of chart?
**
**  This should always be asked, unless you never use one of the two
**  types of charts. If there is only one type of chart possible e.g.
**  the person has no children, or no ancestors then the question
**  isn't asked.
**
*/
  if (1)  {
    indiset(pset)
    addtoset(pset,root_person,1)
    if (eq( lengthset(childset(pset)), 0) ) {
      print ("Printing ancestor chart as ", name(root_person), " has no known children.", nl())
      set (chart_type, 0)
    } elsif (eq( lengthset(parentset(pset)), 0) ) {
        print ("Printing descendant chart as ", name(root_person), " no known ancestors.", nl())
        set (chart_type, 1)
    } else {
        getintmsg (chart_type, "Enter 0 for ancestral, 1 for descendant chart")
    }
  } else {
     set (chart_type, 1)
  }


/*
**  QUESTION: How many generations should be shown?
**
**  If there are less than this, then the page is filled anyway,
**  so you only need to ask if you want a restricted number.
**
*/

  if (0)  {
    getintmsg (max_depth, "Maximum number of generations")
  }  else  {
    set (max_depth, 99)
  }

/*
**  QUESTION: How many generations should show siblings?
**
**  If you want to show siblings in all generations, set this default to 999.
**  This question is only asked for ancestral charts.
**
*/

  if (eq (chart_type, 0))  {

    if (0)  {
      getintmsg (depth_siblings, "How many generations to show siblings")
    }  else  {
      set (depth_siblings, 999)
    }

  }

/*
**  QUESTION: What message should be shown in the corner of the chart?
**
**  I suggest not asking this question, and setting a default credit with
**  your name.  The advantage of this is that you can have the date
**  automatically inserted.
**
*/

  if (0)  {
    getstrmsg (chart_label, "Label for corner of chart (your name, date)")
    set (chart_label, save (chart_label))
  }  else  {
    dayformat (2)
    monthformat (4)
    dateformat (0)
    set (chart_label,
      concat (save (stddate (gettoday ())),
        "    produced by Phil Stringer, 40 Broomfields, Denton, Manchester M34 3TH.    Tel: 0161 320 6530"))
  }

/*
**  QUESTION: What font should be used?
**
**  Because it is such a pain to enter a font name, and a spelling mistake
**  will get you an ugly default font, this should be set to a default.  I
**  suggest one of: Times-Roman, NewCenturySchlbk-Roman, or ZapfChancery.
**  Search the Postscript code at bottom of this file for a longer list.
**
*/

  if (0)  {
    getstrmsg (font_name,
      "Font (Times-Roman, NewCenturySchlbk-Roman, ZapfChancery, etc.")
    set (font_name, save (font_name))
  }  else  {
    set (font_name, "ZapfChancery-MediumItalic")
   /* set (font_name, "Times-Roman")*/
  }

/*
**  QUESTION: Should color be used?
**
**  If you don't have access to a color printer, you should probably turn
**  off this question.
**
*/

  if (0)  {
    getintmsg (color_chart, "Enter 0 for black/white, 1 for color")
  }  else  {
    set (color_chart, 1)
  }

/*
**  QUESTION: Do you want multi-page poster output, and select orientation.
**
**  So that I am not hassled with this question everytime I run this
**  program, I turn this question off, but change the default on the
**  special occasion that I want a poster chart.
**
*/

  if (1)  {
    list(options)
    setel(options,1,"Single page, in portrait")
    setel(options,2,"Single page, in landscape")
    setel(options,3,"Multi page, using portrait sheets of paper")
    setel(options,4,"Multi page, using landscape sheets of paper")
    set(mc, menuchoose(options, "Select chart type:"))
    if (eq(0,mc)) {
      return(0)
    } elsif (eq(1,mc)) {
      set (multi_page, 0)
      set (portrait, 1)
      print("Single page - portrait", nl())
    } elsif (eq(2,mc)) {
      set (multi_page, 0)
      set (portrait, 0)
    } elsif (eq(3,mc)) {
      set (multi_page, 1)
      set (portrait, 1)
    } else {
      set (multi_page, 1)
      set (portrait, 0)
    }
}

/*
**  QUESTION: How many pages make up the poster?
**
**  You will probably want to always ask this question.  This question is
**  asked if a poster chart is requested.
**
*/

  if (multi_page)  {
    if (1)  {
        getintmsg (x_pages, "Number of horizontal pages on finished chart")
        getintmsg (y_pages, "Number of vertical pages on finished chart")
    } else {
      set (x_pages, 3)
      set (y_pages, 3)
    }
  }  else  {
    set (x_pages, 1)
    set (y_pages, 1)
  }

/*
**  QUESTION: How should titles be used?
**
**  I would leave this default set to 'guess' (3), or 'none' (0), if you
**  don't want the titles.  If find a title that is guessed incorrectly,
**  please send an e-mail to wheeler@ipl.rpi.edu.
**
*/

  if (0)  {
    getintmsg (title_method, "Title method (0:none,1:prefix,2:suffix,3:guess)")
  }  else  {
    set (title_method, 3)
  }

/*
**  QUESTION: What is the maximum length for names?
**
**  It is best to just set a default maximum name length.  If you want
**  to always show the complete name, just set the default to 999.
**
*/

  if (0)  {
    getintmsg (name_letters, "Maximum name length")
  }  else  {
    set (name_letters, 40)
  }

/*
**  QUESTION: How should dates/places of birth/death/marriage be shown?
**
**  This is actually three questions, or the same question for birth
**  death and marriage dates.  The codes cause the dates to be printed
**  as follows.
**
**      0: do not show date
**      1: full date only
**         [ LifeLines date() function ]
**      2: date and place, just year and State/Country
**         [ LifeLines short() function ]
**      3: full date and full place, can get very long and thus smushed
**         [ LifeLines long() function ]
**
*/

  if (0)  {
    set (dateplace_birth, 99)
    while (or (lt (dateplace_birth, 0), ge (dateplace_birth, 4)))  {
      getintmsg (dateplace_birth,
                 "Birth date style (0:no,1:date,2:short,3:long)")
    }
    set (dateplace_death, 99)
    while (or (lt (dateplace_death, 0), ge (dateplace_death, 4)))  {
      getintmsg (dateplace_death,
                 "Death date style (0:no,1:date,2:short,3:long)")
    }
    set (dateplace_marriage, 99)
    while (or (lt (dateplace_marriage, 0), ge (dateplace_marriage, 4)))  {
      getintmsg (dateplace_marriage,
                 "Marriage date style (0:no,1:date,2:short,3:long)")
    }
  }  else  {
    set (dateplace_birth, 4)
    set (dateplace_death, 4)
    set (dateplace_marriage, 4)
  }

/*
**  QUESTION: Should the direct line of descent be put in bold?
**
**  Puts the text and lines for the direct line in bold.
**
*/

  if (0)  {
    getintmsg (bold_chart, "Enter 1 for bold direct line, 0 for all the same")
  }  else  {
    set (bold_chart, 1)
  }

/*
**  QUESTION: Should the selected person be on the left or right of the page?
**
*/

  if (0)  {
    getintmsg (mirror_chart, "Enter 0 to start on left of paper, 1 to start on right")
  } else {
    if (chart_type) {
      /* Descendant chart */
      set (mirror_chart, 0)
    } else {
      set (mirror_chart, 1)
    }
  }

/*
**  QUESTION: Should half siblings be shown?
**
**  In the ancestral report, if a parent has had multiple marriages
**  this determines whether the children of these marriages are shown
**  in the aunts/uncles. They ar placed above the father or below the
**  mother with a thin vertical line in the aunt/uncle colour.
**
*/

  if (0)  {
    getintmsg (half_sib, "Enter 1 to show half brothers/sisters, 0 to omit them")
  } else {
     set (half_sib, 1)
  }

/*
**  END OF QUESTIONS
**
*/

  return(1)
}

/*
**  procedure: main
**
**  The main procedure.
**
*/

proc main ()
{

  /* set constants */

  set (name_height, 1250)          /* height to allow for name text */
  set (date_height, 750)           /* height to allow for date text */

  set (branch_dist_prev, 1000)     /* previous generation */
  set (branch_dist_same, 1250)     /* same generation */
  set (branch_dist_next, 1000)     /* next generation */

  set (no_parent_extra, 500)       /* a little extra line when no parent */

  /* initialize other global variables and declare global stacks */

  set (high_pos_all, 0)

  list (high_pos_gen)
  list (do_anc_stack)

  list (plist_person)
  list (plist_depth)
  list (plist_pos)
  list (plist_line)
  list (plist_mdate)
  list (plist_anc)
  list (plist_des)

  list (llist_depth)
  list (llist_low)
  list (llist_high)
  list (llist_dash)

  if (interrogate_user()) {

  /* convert the numerical response for color to string, "true" or "false" */

  if (eq (color_chart, 0))  {
    set (color_true_false, "false")
  }  else  {
    set (color_true_false, "true")
  }

  /* start iteration that creates the chart */

  if (eq (chart_type, 0))  {
    call do_anc (root_person, 1, 0, 0, 0)
  }  else  {
    call do_des (root_person, 1, 0, 0, 1)
  }

  /* Ensure that the vertical columns do not cross a page edge, as its hard to fit
   * the text into a perfect line! */

  set (hd,mul(div(high_depth,x_pages),x_pages))
  if (gt(high_depth,hd)) {
        set(high_depth,add(hd,x_pages))
  }

  /* put the pieces together to make the output file */

  set (xi, 1)
  while ( le (xi, x_pages))  {
    set (yi, 1)
    while ( le (yi, y_pages))  {

      call print_header (font_name, high_depth, high_pos_all, color_true_false,
                         chart_label, xi, x_pages, yi, y_pages)
      call dequeue_all_persons ()
      call dequeue_all_verticals ()
      "showpage" nl()

      set (yi, add (yi, 1))
    }
    set (xi, add (xi, 1))
  }
  }

}

/*
**  procedure: do_anc
**
**  A recursive function to position persons on an ancestral chart.
**  First, a recursive call is made to put the father on the chart.
**  Where he is put on the chart determines the minimum position for
**  the mother.  Once the father and mother are put on the chart, the
**  siblings are put on the chart.
**
**  The position of the person is returned via the global stack
**  do_anc_stack.  A stack is necessary since this procedure is
**  reentrant.
**
*/

proc do_anc (person, depth, min_pos_arg, marriage_date, des)
{
  set(anc, 0)
  /* don't want to modify procedure argument variable, so copy it */

  set (min_pos, min_pos_arg)

  /* make sure minimum position is greater than zero */

  if (lt (min_pos, 0))  {
    set (min_pos, 0)
  }

  /* make we will not overlap the another branch at the younger generation */

  if (gt (depth, 1))  {
    if (high, getel (high_pos_gen, sub (depth, 1)))  {
      if (lt (min_pos, add (high, branch_dist_prev)))  {
        set (min_pos, add (high, branch_dist_prev))
      }
    }
  }

  /* make we will not overlap the another branch at the same generation */

  if (high, getel (high_pos_gen, depth))  {
    if (lt (min_pos, add (high, branch_dist_same)))  {
      set (min_pos, add (high, branch_dist_same))
    }
  }

  /* make we will not overlap the another branch at the older generation */

  if (lt (depth, max_depth))  {
    if (high, getel (high_pos_gen, add (depth, 1)))  {
      if (lt (min_pos, add (high, branch_dist_next)))  {
        set (min_pos, add (high, branch_dist_next))
      }
    }
  }

  /* See if father had any other children by a different mother
   * and add up the space to show them */

  set (fam, parents (person))
  set (famkey,key(fam))
  set(fhsize, 0)
  set(dhs,0)
  if (and(half_sib,father(person))) {
    set(anc, 1)
    if ( gt (nfamilies(father(person)),1)) {
      families(father(person),fv,sv,nf) {
        if (ne(famkey,key(fv))) {
          children (fv, child, un) {

            set(dhs,1)

            /* increment position by height of person plus the spacer */

            call person_height (child)
            set (fhsize, add (fhsize, person_height_return))
          }
        }
      }
    }
  }

  /* do father if he exists and is not too deep */

  /* update the highest position array, or set it for the first time */
  /* to save space for these other children */

  if (high, getel (high_pos_gen, depth))  {
    if (lt (high, add(high,fhsize)))  {
      setel (high_pos_gen, depth, add(high,fhsize))
    }
  }  else  {
    setel (high_pos_gen, depth, fhsize)
  }

  set (dad_min_pos, sub (min_pos, name_height))
  set (dad_pos, dad_min_pos)
  set (did_dad, 0)                     /* boolean, is dad on the chart */

  if (lt (depth, max_depth))  {
    if (par, father (person))  {
      set(anc, 1)
      call dateplace (marriage (parents (person)), dateplace_marriage)
      if (dateplace_return)  {
        call do_anc (par, add (depth, 1), dad_min_pos, dateplace_return, 1)
      }  else  {
        call do_anc (par, add (depth, 1), dad_min_pos, 0, 1)
      }
      set (dad_pos, pop (do_anc_stack))
      set (did_dad, 1)
    }
  }

  if (lt (min_pos, add (dad_pos, name_height)))  {
    set (min_pos, add (dad_pos, name_height))
  }

  /* If father had any other kids by a different mother print them*/

  set(pos,sub(dad_pos, fhsize))
  set(sdhs, pos)
  if (dhs) {
    families(father(person),fv,sv,nf) {
      if (ne(famkey,key(fv))) {
        children (fv, child, un) {
          call enqueue_person (child, depth, pos, 0, 0, 1, 0)

          /* increment position by height of person plus the spacer */

          call person_height (child)
          set (pos, add (pos, person_height_return))
        }
      }
    }
    call enqueue_vertical (depth, sdhs, pos, 1) /* Draw th line */
  }

  /* figure out number of siblings and total sibling height */
  /* done differently, depending on whether the parents family exists */

  if ( and ( fam, le (depth, depth_siblings) ) )  {

    set (sibling_height, 0)
    children (fam, child, unused_number)  {
      call person_height (child)
      set (sibling_height, add (sibling_height, person_height_return))
    }
    set (num_siblings, nchildren (fam))

  }  else  {

    call person_height (child)
    set (sibling_height, person_height_return)
    set (num_siblings, 1)

  }

  /* add extra width for marriage date of male ancestor, if it is known */

  if (marriage_date)  {
    set (sibling_height, add (sibling_height, date_height))
  }

  /* do mother if she exists and is not too deep */

  set (mom_min_pos, add (add (dad_pos, name_height), sibling_height))
  set (mom_min_pos,sub(mom_min_pos,250))
  set (mom_pos, mom_min_pos)
  set (did_mom, 0)                     /* boolean, is mom on the chart */

  if (lt (depth, max_depth))  {
    if (par, mother (person))  {
      set(anc, 1)
      call do_anc (par, add (depth, 1), mom_min_pos, 0, 1)
      set (mom_pos, pop (do_anc_stack))
      set (did_mom, 1)
    }
  }

  /* find the spacer needed to line up siblings between mother and father */

  set (delta, sub (mom_pos, add (dad_pos, name_height)))
  set (extra, sub (delta, sibling_height))
  set (spacer, div (extra, add (num_siblings, 1)))

  set (pos, add (dad_pos, name_height))
  set (pos, add (pos, spacer))

  /* position siblings, differently depending on whether parents exist */

  if (fam, parents (person))  {

    if ( le (depth, depth_siblings))  {

      children (fam, child, number)  {

        /* if this is the ancestor, return the position and use marriage */

        if (eq (child, person))  {
          call enqueue_person (child, depth, pos, 1, marriage_date, 1, des)
          push (do_anc_stack, pos)
        }  else  {
          call enqueue_person (child, depth, pos, 0, 0, 1, 0)
        }

        /* store the positions of the first and last children */

        if (eq (number, 1))  {
          set (first_pos, pos)
        }
        if (eq (number, nchildren (fam)))  {
          set (last_pos, pos)
        }

        /* increment position by height of person plus the spacer */

        call person_height (child)
        set (pos, add (pos, person_height_return))
        if (and (eq (child, person), marriage_date))  {
          set (pos, add (pos, date_height))
        }
        set (pos, add (pos, spacer))
      }

    }  else  {

      call enqueue_person (person, depth, pos, 1, marriage_date, anc, des)
      push (do_anc_stack, pos)

      /* this may cause a line of zero length to be drawn */
      set (first_pos, pos)
      set (last_pos, pos)

      /* increment position by height of person plus the spacer */

      call person_height (person)
      set (pos, add (pos, person_height_return))
      if (marriage_date)  {
        set (pos, add (pos, date_height))
      }
      set (pos, add (pos, spacer))
    }

    /* if father is on the chart, he determines the vertical line start */
    /* otherwise, the oldest sibling does */

    if (eq (did_dad, 1))  {
      set (line_start, dad_pos)
    }  else  {
      set (line_start, sub (first_pos, no_parent_extra))
    }

    /* note: line_start may be < 0, that is OK */

    /* if mother is on the chart, she determines the vertical line end */
    /* otherwise, the youngest sibling does */

    if (eq (did_mom, 1))  {
      set (line_end, mom_pos)
    }  else  {
      set (line_end, add (last_pos, no_parent_extra))
    }
    /* set(line_end, sub(line_end,250)) */

    /* print vert. line if mother, father or any siblings are on the chart */

    if (or (or (did_mom, did_dad), gt (nchildren (fam), 1)))  {
      call enqueue_vertical (depth, line_start, line_end, 0)
      /* update highest overall position */
      if (lt (high_pos_all, add (line_end, name_height)))  {
        set (high_pos_all, add (line_end, name_height))
      }
    }

  }  else  {

    /* else, if the person has no visible siblings */

    call enqueue_person (person, depth, pos, 1, marriage_date, anc, des)
    push (do_anc_stack, pos)

    /* increment position by height of person plus the spacer */

    call person_height (person)
    set (pos, add (pos, person_height_return))
    if (marriage_date)  {
      set (pos, add (pos, date_height))
    }
    set (pos, add (pos, spacer))
  }

  /* See if mother had any other children by a different father */

  set(smhs, pos)
  set(dhs,0)
  set(pos, add (pos,branch_dist_next))
  if (and(half_sib,did_mom)) {
    if ( gt (nfamilies(mother(person)),1)) {
      families(mother(person),fv,sv,nf) {
        if (ne(famkey,key(fv))) {
          children (fv, child, un) {

            set(dhs,1)
            call enqueue_person (child, depth, pos, 0, 0, 1, 0)

            /* increment position by height of person plus the spacer */

            call person_height (child)
            set (pos, add (pos, person_height_return))
          }
        }
      }
    }
  }

  /* If there were any more children on the mothers side draw the vert line */

  if (dhs) {
    call enqueue_vertical (depth, smhs, sub(pos,name_height), 1)
    set(pos,add(pos,no_parent_extra))
  }

  /* update the highest position array, or set it for the first time */

  if (high, getel (high_pos_gen, depth))  {
    if (lt (high, pos))  {
      setel (high_pos_gen, depth, pos)
    }
  }  else  {
    setel (high_pos_gen, depth, pos)
  }

  /* update the overall highest position */

  if (lt (high_pos_all, pos))  {
    set (high_pos_all, pos)
  }

  /* update the overall highest depth */

  if (lt (high_depth, depth))  {
    set (high_depth, depth)
  }
}

/*
**  procedure: do_des
**
**  A recursive function to position persons on a descendant chart.
**
**  Descendant charts are harder to make look neat, as ancestor charts
**  branch from the ends of a family group, whilst descendant charts can
**  branch from any child. A person's family can either branch up to the
**  top of the page or down to the bottom. So to get a spreading chart the
**  first half of a family branch up, and the rest branch down.
**
**  As a child's own family affects the position of his/her siblings, then
**  those higher on the page may need to move down. At the moment this is done
**  for the parent or spouse (branch down or up) but not for siblings without
**  children. An improvement could therfore be made by stacking such individuals
**  then printing as required. However it gets more complicated with spouses and
**  multiple families.
**
**  A simpler enhancement required is to position a person with no spouse halfway
**  between the children when branching up as is already done when branching
**  down.
*/

proc do_des (person, depth, min_pos_arg, anc, branch_up)
{
  if (branch_up) {
    set (branch_down,0)
  } else {
    set (branch_down,1)
  }
  set(des, 0)
  if( ne(nfamilies(person),0) ) {
    set(des, 1)
  }
  /* don't want to modify procedure argument variable, so copy it */

  set (min_pos, min_pos_arg)

  /* make sure we will not overlap the another branch at the same generation */

  if (high, getel (high_pos_gen, depth))  {
    if (lt (min_pos, high))  {
      set (min_pos, high)
    }
  }
  if (lt (min_pos, branch_dist_same))  {
    set (min_pos, branch_dist_same)
  }

  set (make_line, 0)
  set (pos, min_pos)
  set (had_kids, 0)
  set (known_spouse, 0)

  set (start_pos,pos)
  if (and(nspouses(person),branch_down)) {
    call person_height (person)
    set (pos, add (pos, person_height_return))
  }
  set (ffcp, pos)


  families (person, fam, spouse, fn)  {
    set (make_line, 1)
/*    if (eq (fn, 1))  {
      set (line_top, pos)
    }*/
    if (branch_up)  {
      call dateplace (marriage (fam), dateplace_marriage)
      set (mdate, dateplace_return)
      set (start_fam, pos)
      if (spouse) {
        set (pos, add(pos,name_height))
      }
    }
    set (change_point, div (add(1,nchildren(fam)), 2))
    if (lt (depth, max_depth))  {
      children (fam, child, cn)  {
        set (had_kids, 1)
        if ( and(and(and(eq(1,cn),eq(1,fn)),eq(1,nfamilies(person))),eq(1,nchildren(fam))) ) {
          call do_des (child, add (depth, 1), pos, 1, branch_up)
        } else {
          if ( gt (cn, change_point)) {
            call do_des (child, add (depth, 1), pos, 1, 0)
          } else {
            call do_des (child, add (depth, 1), pos, 1, 1)
          }
        }
        set (pos, pop (do_anc_stack))
        if (eq (1,cn)) {
          if (branch_down) {
            set (start_fam, pos)
          } else {
            if (spouse) {
              set (start_fam, sub(pos,name_height))
            } else {
              set (start_fam, pos)
            }
          }
          if (eq (1, fn)) {
            set (ffcp, pos)
            set (line_top, start_fam)
          }
        }
      }
      if (nchildren(fam)) {
        set(nd, add(depth,1))
        set(ov, getel(high_pos_gen,nd))
        setel(high_pos_gen, nd, add(ov,branch_dist_same))
      }
    }
    if (branch_up) {
      if (spouse)  {
        set (known_spouse, 1)
/*      if (had_kids) {
          set (nms, sub (start_fam, name_height))
        } else {*/
          set (nms, start_fam)
/*      }*/
        if (eq (1,fn)) {
          set (line_top, nms)
        }
        call enqueue_person (spouse, depth, nms, 0, mdate, 0, 1)
        call person_height (person)
        set (nms, add (nms, person_height_return))
        if (mdate)  {
          set (nms, add (nms, date_height))
        }
        if (gt (nms, pos)) {
          set(pos, nms)
        }
      }
      set (line_bot, pos)
    } else {
      call dateplace (marriage (fam), dateplace_marriage)
      set (mdate, dateplace_return)
      if (spouse)  {
        set (known_spouse, 1)
        if (had_kids) {
          set (pos, add (pos, name_height))
        }
        call enqueue_person (spouse, depth, pos, 0, mdate, 0, 1)
        set (line_bot, pos)
        call person_height (spouse)
        set (pos, add (pos, person_height_return))
        if (mdate)  {
          set (pos, add (pos, date_height))
        }
      } else {
        set (line_bot, pos)
      }
    }
  }

  if (branch_up)  {
    if (had_kids) {
      set (pos, add (pos, name_height))
    }
    if (and (had_kids, not (known_spouse))) {
      set (nmp, add (line_top, div (sub(line_bot, line_top), 2)))
      call enqueue_person (person, depth, nmp, 1, 0, anc, des)
      call enqueue_person (person, depth, nmp, 1, 0, anc, des)
      push (do_anc_stack, nmp)
      set (nmp, add (nmp, person_height_return))
      if (gt (nmp, pos)) {
        set(pos, nmp)
      }
    } else {
      call enqueue_person (person, depth, pos, 1, 0, anc, des)
      push (do_anc_stack, pos)
      set (line_bot, pos)
      call person_height (person)
      set (pos, add (pos, person_height_return))
    }
  } else {
    call person_height (person)
    if (and (had_kids, known_spouse)) {
      set (nmp, sub (ffcp, person_height_return))
    } else {
      set (nmp, start_pos)
    }
    set (line_top, nmp)
    if (and (had_kids, not (known_spouse))) {
      set (nmp, add (line_top, div (sub(line_bot, line_top), 2)))
    }
    call enqueue_person (person, depth, nmp, 1, 0, anc, des)
    push (do_anc_stack, nmp)
    set (nmp, add (nmp, person_height_return))
    if (gt (nmp, pos)) {
      set(pos, nmp)
    }
  }

  /* update the highest position array, or set it for the first time */

  if (high, getel (high_pos_gen, depth))  {
    if (lt (high, pos))  {
      setel (high_pos_gen, depth, pos)
    }
  }  else  {
    setel (high_pos_gen, depth, pos)
  }

  /* update the overall highest position */

  if (lt (high_pos_all, pos))  {
    set (high_pos_all, pos)
  }

   /* update the overall highest depth */

  if (lt (high_depth, depth))  {
    set (high_depth, depth)
  }

  if (make_line)  {
    call enqueue_vertical (depth, line_top, line_bot, 0)
  }
}

/*
**  procedure: dateplace
**
**  Get the date of an event in the appropriate style (which may include
**  the place.  Return via global variable.
**
*/

proc dateplace (ev, style)
{
  if (eq (style, 0))  {
    set (dateplace_return, 0)
  }
  if (eq (style, 1))  {
    set (dateplace_return, save (date (ev)))
  }
  if (eq (style, 2))  {
    set (dateplace_return, save (short (ev)))
  }
  if (eq (style, 3))  {
    set (dateplace_return, save (long (ev)))
  }
  if (eq (style, 4))  {
        if (long(ev)) {
                if (place(ev)) {
                        list(pl)
                        extractplaces(ev,pl,np)
                        set(where,concat(", ",dequeue(pl)))
                        set (dateplace_return, save (concat(date (ev), where)))
                } else {
                        set (dateplace_return, save (date (ev)))
                }
        } else {
                set (dateplace_return, 0)
        }
  }
  if (ge (style, 5))  {
    print ("error: invalid date style code")
  }
}

/*
**  procedure: person_height
**
**  Return the height of a single persons entry.  Only the name, and
**  birth and death dates are considered.  The name is assumed to be in
**  the database, the dates are checked for.  The marriage date is not
**  checked for here.  It is more tricky since it is only put below the
**  father's name and you have to make sure you have the date from the
**  right marriage.
**
**  The height of the person is returned via the global variable
**  person_height_return.  This global variable is used since LifeLines
**  does not yet provide user-defined functions.
**
*/

proc person_height (person)
{
  /* determine height of person and put in global var person_height_return */

  set (person_height_return, name_height)

  call dateplace (birth (person), dateplace_birth)
  if (dateplace_return)  {
    set (person_height_return, add (person_height_return, date_height))
  }

  call dateplace (death (person), dateplace_death)
  if (dateplace_return)  {
    set (person_height_return, add (person_height_return, date_height))
  }
}

/*
**  procedure: is_prefix_title
**
**  Decide if the given title is a prefix type title.  Returns boolean
**  response in global variable is_prefix_title_return.
**
*/

proc is_prefix_title (t)
{
  set (is_prefix_title_return, 0)

  if (index (t, "Arch", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Baron", 1))   { set (is_prefix_title_return, 1) }
  if (index (t, "Bish", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Brot", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Card", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Canon", 1))  { set (is_prefix_title_return, 1) }
  if (index (t, "Cong", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Deacon", 1))  { set (is_prefix_title_return, 1) }
  if (index (t, "Dr", 1))      { set (is_prefix_title_return, 1) }
  if (index (t, "Duke", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Father", 1))  { set (is_prefix_title_return, 1) }
  if (index (t, "Fr", 1))      { set (is_prefix_title_return, 1) }
  if (index (t, "Hon", 1))     { set (is_prefix_title_return, 1) }
  if (index (t, "Judge", 1))   { set (is_prefix_title_return, 1) }
  if (index (t, "King", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Lady", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Lord", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Miss", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Mons", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Mr", 1))      { set (is_prefix_title_return, 1) }
  if (index (t, "Mrs", 1))     { set (is_prefix_title_return, 1) }
  if (index (t, "Ms", 1))      { set (is_prefix_title_return, 1) }
  if (index (t, "Msgr", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Pope", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Pres", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Princ", 1))   { set (is_prefix_title_return, 1) }
  if (index (t, "Prof", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Queen", 1))   { set (is_prefix_title_return, 1) }
  if (index (t, "Rabbi", 1))     { set (is_prefix_title_return, 1) }
  if (index (t, "Rav", 1))     { set (is_prefix_title_return, 1) }
  if (index (t, "Rep", 1))     { set (is_prefix_title_return, 1) }
  if (index (t, "Sen", 1))     { set (is_prefix_title_return, 1) }
  if (index (t, "Sir", 1))    { set (is_prefix_title_return, 1) }
  if (index (t, "Sis", 1))     { set (is_prefix_title_return, 1) }
}

/*
**  procedure: enqueue_person
**
**  Store the data for a person in the global lists.  It will be
**  printed later.
**
*/

proc enqueue_person (person, depth, pos, line, mdate, anc, des)
{
  enqueue (plist_person, person)
  enqueue (plist_depth,  depth)
  enqueue (plist_pos,    pos)
  enqueue (plist_line,   line)
  enqueue (plist_mdate,  mdate)
  enqueue (plist_anc,    anc)
  enqueue (plist_des,    des)
}

/*
**  procedure: dequeue_all_persons
**
**  Dequeue and print all persons stored in the global lists.  The
**  lines are stored in a second queue as they are printed and then
**  placed back in the original, global, queue.
**
*/

proc dequeue_all_persons ()
{
  list (tlist_person)
  list (tlist_depth)
  list (tlist_pos)
  list (tlist_line)
  list (tlist_mdate)
  list (tlist_anc)
  list (tlist_des)

  while (person, dequeue (plist_person))  {
    set (depth,  dequeue (plist_depth))
    set (pos,    dequeue (plist_pos))
    set (line,   dequeue (plist_line))
    set (mdate,  dequeue (plist_mdate))
    set (anc,    dequeue (plist_anc))
    set (des,    dequeue (plist_des))

    call print_person (person, depth, pos, line, mdate, anc, des)

    enqueue (tlist_person, person)
    enqueue (tlist_depth,  depth)
    enqueue (tlist_pos,    pos)
    enqueue (tlist_line,   line)
    enqueue (tlist_mdate,  mdate)
    enqueue (tlist_anc,    anc)
    enqueue (tlist_des,    des)
  }

  while (person, dequeue (tlist_person))  {
    set (depth,  dequeue (tlist_depth))
    set (pos,    dequeue (tlist_pos))
    set (line,   dequeue (tlist_line))
    set (mdate,  dequeue (tlist_mdate))
    set (anc,    dequeue (tlist_anc))
    set (des,    dequeue (tlist_des))

    enqueue (plist_person, person)
    enqueue (plist_depth,  depth)
    enqueue (plist_pos,    pos)
    enqueue (plist_line,   line)
    enqueue (plist_mdate,  mdate)
    enqueue (plist_anc,    anc)
    enqueue (plist_des,    des)
  }
}

/*
**  procedure: print_person
**
**  Print a line of data for a person in postscript format.  Each line
**  printed is essentially a call to a postscript function defined in the
**  header.
**
*/

proc print_person (person, depth, pos, line, mdate, anc, des)
{

  if (eq (title_method, 0))  {
    set (prefix_title, 0)
    set (suffix_title, 0)
  }
  if (eq (title_method, 1))  {
    set (prefix_title, title (person))
    set (suffix_title, 0)
  }
  if (eq (title_method, 2))  {
    set (prefix_title, 0)
    set (suffix_title, title (person))
  }
  if (eq (title_method, 3))  {
    set (prefix_title, 0)
    set (suffix_title, 0)
    if (t, title (person))  {
      call is_prefix_title (t)
      if (is_prefix_title_return)  {
        set (prefix_title, t)
      }  else  {
        set (suffix_title, t)
      }
    }
  }

  set (nlet, name_letters)
  if (prefix_title)  {
    set (nlet, sub (nlet, strlen (prefix_title)))
  }
  if (suffix_title)  {
    set (nlet, sub (nlet, strlen (suffix_title)))
  }

  /* print name and title, if it exists */
  "("
  if (prefix_title)  {
    prefix_title " "
  }
  fullname (person, 0, 1, nlet)
  if (suffix_title)  {
    " " suffix_title
  }
  ")"

  /* print birth date, if it exists */
  call dateplace (birth (person), dateplace_birth)
  if (dateplace_return)  {
    " (b. " dateplace_return ")"
  }  else  {
    " ()"
  }

  /* print marriage date, if it exists */
  if (mdate)  {
    " (m. " mdate ")"
  }  else  {
    " ()"
  }

  /* optional special tagged note, not used yet */
  " ()"

  /* print death date, if it exists */
  call dateplace (death (person), dateplace_death)
  if (dateplace_return)  {
    " (d. " dateplace_return ")"
  }  else  {
    " ()"
  }

  /* print generation, 0=youngest */
  " " d (sub (depth, 1))

  /* print vertical position */
  " " call print_thousandths (pos)

  /* extra height, not used or understood */
  " 1"

  /* 1=direct ancestor, 0=sibling */
  " " d (line)

  /* duplicate individual, not used */
  " 0"

  /* person has ancestors */
  " " d(anc)

  /* person has descendants*/
  " " d(des)

  /* call postscript function to process and print this data */
  " i"

  nl()
}

/*
**  procedure: enqueue_vertical
**
**  Enqueue the data for a single vertical line onto the global lists.
**
*/

proc enqueue_vertical (depth, low, high, dash)
{
  enqueue (llist_depth,  depth)
  enqueue (llist_low,    sub(low,500))
  enqueue (llist_high,   sub(high,500))
  enqueue (llist_dash,   dash)
}

/*
**  procedure: dequeue_all_verticals
**
**  Dequeue and print all vertical lines.  The lines are stored in a
**  second queue as they are printed and then placed back in the
**  original, global, queue.
**
*/

proc dequeue_all_verticals ()
{
  list (tlist_depth)
  list (tlist_low)
  list (tlist_high)
  list (tlist_dash)

  while (depth,  dequeue (llist_depth))  {
    set (low,    dequeue (llist_low))
    set (high,   dequeue (llist_high))
    set (dash,   dequeue (llist_dash))

    call print_vertical (depth, low, high, dash)

    enqueue (tlist_depth, depth)
    enqueue (tlist_low,   low)
    enqueue (tlist_high,  high)
    enqueue (tlist_dash,  dash)
  }

  while (depth,  dequeue (tlist_depth))  {
    set (low,    dequeue (tlist_low))
    set (high,   dequeue (tlist_high))
    set (dash,   dequeue (tlist_dash))

    enqueue (llist_depth, depth)
    enqueue (llist_low,   low)
    enqueue (llist_high,  high)
    enqueue (llist_dash,  dash)
  }
}

/*
**  procedure: print_vertical
**
**  Print a single vertical line to link a married couple or siblings.
**
*/

proc print_vertical (depth, low, high, dash)
{
  d (sub (depth, 1))
  " " call print_thousandths (low)
  " " call print_thousandths (high)
  " " d(dash)
  " l" nl()
}

/*
**  procedure: print_thousandths
**
**  Since LifeLines does not offer a floating point type, decimal
**  computation is done using integers that represent thousands.  This
**  procedure converts a number in thousandths to decimal notation and
**  prints it.  The length of the decimal part is checked to make sure
**  it is padded with zeros correctly.
**
*/

proc print_thousandths (n_arg)
{

  /* don't want to modify proc argument, so copy it */
  set (n, n_arg)

  if (lt (n, 0))  {
    "-"
    set (n, neg (n))
  }

  d (div (n, 1000)) "."

  set (t, d (mod (n, 1000)))
  if (eq (strlen (t), 1))  {
    "00" t
  }
  if (eq (strlen (t), 2))  {
    "0" t
  }
  if (eq (strlen (t), 3))  {
    t
  }

}

/*
**  procedure: print_header
**
**  Arguments:
**    fn:  font name
**    md:  maximum level, integer
**    mp:  maximum position, integer in thousandths
**    ctf: color true/false, string "true" or "false"
**    cl:  chart label, string
**    xi:  which horizontal page
**    xn:  number of horizontal pages
**    yi:  which vertical page
**    yn:  number of vertical pages
**
**  Print the initial postscript code.  This code will likely be the
**  bulk of the output file.  It prints the border, defines postscript
**  functions for printing peoples names, dates and the lines on the
**  chart, and more.  It will be followed by the data.
**
**  This postscript code was written by Thomas P. Blumer (blumer@ptltd.com).
**  The only modification is where data from the arguments is inserted.
**
*/

proc print_header (fn, ml, mp, ctf, cl, xi, xn, yi, yn)
{
  "%!PS-Adobe-2.0 EPSF-1.2" nl()
  "%%BoundingBox:0 0 612 792" nl()
  "/#copies 1 def" nl()
  "/xpages " d (xn) " def" nl()
  "/ypages " d (yn) " def" nl()
  "/xpage " d (xi) " def" nl()
  "/ypage " d (yi) " def" nl()
  "/maxlevel " d (ml) " def" nl()
  "/mirror " if (mirror_chart) { "true" } else { "false" } " def" nl()
  "/maxpos " call print_thousandths (mp)  nl()
  "0.02 ypages div 1.0 add mul def" nl()
  "/color " ctf " def" nl()
  "/bold " if (bold_chart) { "true" } else { "false" } " def" nl()
  "/portrait " if (portrait) { "true" } else { "false" } " def" nl()
  "/indent 3.00 def" nl()
  "/linwidf 1.000 def" nl()
  "/font_adjust 1.000 def" nl()
  "/offset_name 0.000 def" nl()
  "% Put PostScript code here to print a label on the chart" nl()
  "/inch {72 mul} def" nl()
  "/chart_label {" nl()
  "     .30 inch .15 inch moveto" nl()
  "     /Helvetica-Narrow findfont 7 scalefont setfont" nl()
  "     (" cl ") show" nl()
  "} def" nl()
  "/lr 0 def /lg 1 def /lb 1 def" nl()
  "/Lr 0 def /Lg 0 def /Lb 1 def" nl()
  "/tr 0 def /tg 0 def /tb 0 def" nl()
  "/Tr 0 def /Tg 0 def /Tb 0 def" nl()
  "/lmr 0 def /lmg 1 def /lmb 1 def" nl()
  "/Lmr 0 def /Lmg 0 def /Lmb 1 def" nl()
  "/tmr 0 def /tmg 0 def /tmb 0 def" nl()
  "/Tmr 0 def /Tmg 0 def /Tmb 0 def" nl()
  "/fontname /" fn " def" nl()
  "/encvec [" nl()
  "16#80 /Ccedilla" nl()
  "16#81 /udieresis" nl()
  "16#82 /eacute" nl()
  "16#83 /acircumflex" nl()
  "16#84 /adieresis" nl()
  "16#85 /agrave" nl()
  "16#86 /aring" nl()
  "16#87 /ccedilla" nl()
  "16#88 /ecircumflex" nl()
  "16#89 /edieresis" nl()
  "16#8a /egrave" nl()
  "16#8b /idieresis" nl()
  "16#8c /icircumflex" nl()
  "16#8d /igrave" nl()
  "16#8e /Adieresis" nl()
  "16#8f /Aring" nl()
/*---  "16#90 /Eacute" nl()
  "16#91 /ae" nl()
  "16#92 /AE" nl()
  "16#93 /ocircumflex" nl()
  "16#94 /odieresis" nl()
  "16#95 /ograve" nl()
  "16#96 /ucircumflex" nl()
  "16#97 /ugrave" nl()
  "16#98 /ydieresis" nl()
  "16#99 /Odieresis" nl()
  "16#9a /Udieresis" nl()
  "16#9b /cent" nl()
  "16#9c /sterling" nl()
  "16#9d /yen" nl()
  "16#9f /florin" nl() ---*/
  "16#90 /dotlessi" nl()
  "16#91 /grave" nl()
  "16#92 /acute" nl()
  "16#93 /circumflex" nl()
  "16#94 /tilde" nl()
  "16#95 /macron" nl()
  "16#96 /breve" nl()
  "16#97 /dotaccent" nl()
  "16#98 /dieresis" nl()
  "16#99 /.notdef" nl()
  "16#9a /ring" nl()
  "16#9b /cedilla" nl()
  "16#9c /.notdef" nl()
  "16#9d /hungarumlaut" nl()
  "16#9e /ogonek" nl()
  "16#9f /caron" nl()
  "16#a0 /space" nl()
/*----  "16#a0 /aacute" nl()
  "16#a1 /iacute" nl()
  "16#a2 /oacute" nl()
  "16#a3 /uacute" nl()
  "16#a4 /ntilde" nl()
  "16#a5 /Ntilde" nl()
  "16#a6 /ordfeminine" nl()
  "16#a7 /ordmasculine" nl()
  "16#a8 /questiondown" nl()
  "16#aa /logicalnot" nl()
  "16#ab /onehalf" nl()
  "16#ac /onequarter" nl()
  "16#ad /exclamdown" nl()
  "16#ae /guillemotleft" nl()
  "16#af /guillemotright" nl() -----*/
  "16#a1 /exclamdown" nl()
  "16#a2 /cent" nl()
  "16#a3 /sterling" nl()
  "16#a4 /currency" nl()
  "16#a5 /yen" nl()
  "16#a6 /brokenbar" nl()
  "16#a7 /section" nl()
  "16#a8 /dieresis" nl()
  "16#a9 /copyright" nl()
  "16#aa /ordfeminine" nl()
  "16#ab /guillemotleft" nl()
  "16#ac /logicalnot" nl()
  "16#ad /hyphen" nl()
  "16#ae /registered" nl()
  "16#af /macron" nl()
  "16#b0 /degree" nl()
  "16#b1 /plusminus" nl()
  "16#b2 /twosuperior" nl()
  "16#b3 /threesuperior" nl()
  "16#b4 /acute" nl()
  "16#b5 /mu" nl()
  "16#b6 /paragraph" nl()
  "16#b7 /periodcentered" nl()
  "16#b8 /cedilla" nl()
  "16#b9 /onesuperior" nl()
  "16#ba /ordmasculine" nl()
  "16#bb /guillemotright" nl()
  "16#bc /onequarter" nl()
  "16#bd /onehalf" nl()
  "16#be /threequarters" nl()
  "16#bf /questiondown" nl()
  "16#c0 /Agrave" nl()
  "16#c1 /Aacute" nl()
  "16#c2 /Acircumflex" nl()
  "16#c3 /Atilde" nl()
  "16#c4 /Adieresis" nl()
  "16#c5 /Aring" nl()
  "16#c6 /AE" nl()
  "16#c7 /Ccedilla" nl()
  "16#c8 /Egrave" nl()
  "16#c9 /Eacute" nl()
  "16#ca /Ecircumflex" nl()
  "16#cb /Edieresis" nl()
  "16#cc /Igrave" nl()
  "16#cd /Iacute" nl()
  "16#ce /Icircumflex" nl()
  "16#cf /Idieresis" nl()
  "16#d0 /Eth" nl()
  "16#d1 /Ntilde" nl()
  "16#d2 /Ograve" nl()
  "16#d3 /Oacute" nl()
  "16#d4 /Ocircumflex" nl()
  "16#d5 /Otilde" nl()
  "16#d6 /Odieresis" nl()
  "16#d7 /multiply" nl()
  "16#d8 /Oslash" nl()
  "16#d9 /Ugrave" nl()
  "16#da /Uacute" nl()
  "16#db /Ucircumflex" nl()
  "16#dc /Udieresis" nl()
  "16#dd /Yacute" nl()
  "16#de /Thorn" nl()
  "16#df /germandbls" nl()
  "16#e0 /agrave" nl()
  "16#e1 /aacute" nl()
  "16#e2 /acircumflex" nl()
  "16#e3 /atilde" nl()
  "16#e4 /adieresis" nl()
  "16#e5 /aring" nl()
  "16#e6 /ae" nl()
  "16#e7 /ccedilla" nl()
  "16#e8 /egrave" nl()
  "16#e9 /eacute" nl()
  "16#ea /ecircumflex" nl()
  "16#eb /edieresis" nl()
  "16#ec /igrave" nl()
  "16#ed /iacute" nl()
  "16#ee /icircumflex" nl()
  "16#ef /idieresis" nl()
  "16#f0 /eth" nl()
  "16#f1 /ntilde" nl()
  "16#f2 /ograve" nl()
  "16#f3 /oacute" nl()
  "16#f4 /ocircumflex" nl()
  "16#f5 /otilde" nl()
  "16#f6 /odieresis" nl()
  "16#f7 /divide" nl()
  "16#f8 /oslash" nl()
  "16#f9 /ugrave" nl()
  "16#fa /uacute" nl()
  "16#fb /ucircumflex" nl()
  "16#fc /udieresis" nl()
  "16#fd /yacute" nl()
  "16#fe /thorn" nl()
  "16#ff /ydieresis" nl()
/*  "16#f8 /degree" nl()
  "16#f9 /bullet" nl()
  "16#fa /periodcentered" nl()*/
  "] def" nl()
  "% Copyright (c) 1991-1993 Thomas P. Blumer.  All Rights Reserved." nl()
  "% Permission granted to use in LifeLines report generation." nl()
  "/border true def" nl()
  nl()
  "color {" nl()
  "     /setcmykcolor where { pop" nl()
  "             Tr Tg Tb add add 0 eq {" nl()
  "                     /Tk 1 def" nl()
  "             } {" nl()
  "                     /Tk 0 def" nl()
  "                     /Tr 1 Tr sub def /Tg 1 Tg sub def /Tb 1 Tb sub def" nl()
  "             } ifelse" nl()
  nl()
  "             tr tg tb add add 0 eq {" nl()
  "                     /tk 1 def" nl()
  "             } {" nl()
  "                     /tk 0 def" nl()
  "                     /tr 1 tr sub def /tg 1 tg sub def /tb 1 tb sub def" nl()
  "             } ifelse" nl()
  nl()
  "             Lr Lg Lb add add 0 eq {" nl()
  "                     /Lk 1 def" nl()
  "             } {" nl()
  "                     /Lk 0 def" nl()
  "                     /Lr 1 Lr sub def /Lg 1 Lg sub def /Lb 1 Lb sub def" nl()
  "             } ifelse" nl()
  nl()
  "             lr lg lb add add 0 eq {" nl()
  "                     /lk 1 def" nl()
  "             } {" nl()
  "                     /lk 0 def" nl()
  "                     /lr 1 lr sub def /lg 1 lg sub def /lb 1 lb sub def" nl()
  "             } ifelse" nl()
  nl()
  "             /textcolr0 {Tr Tg Tb Tk setcmykcolor} bind def % direct ancestor name" nl()
  "             /textcolr1 {tr tg tb tk setcmykcolor} bind def % indirect names" nl()
  "             /lincolr0 {Lr Lg Lb Lk setcmykcolor} bind def  % direct ancestor lines" nl()
  "             /lincolr1 {lr lg lb lk setcmykcolor} bind def  % indirect lines" nl()
  "     } {" nl()
  "             /textcolr0 {Tr Tg Tb setrgbcolor} bind def % direct ancestor name" nl()
  "             /textcolr1 {tr tg tb setrgbcolor} bind def % indirect names" nl()
  "             /lincolr0 {Lr Lg Lb setrgbcolor} bind def  % direct ancestor lines" nl()
  "             /lincolr1 {lr lg lb setrgbcolor} bind def  % indirect lines" nl()
  "     } ifelse" nl()
  "} {" nl()
  "     /textcolr0 {} bind def" nl()
  "     /textcolr1 {} bind def" nl()
  "     /lincolr0 {} bind def" nl()
  "     /lincolr1 {} bind def" nl()
  "} ifelse" nl()
  nl()
  "% table of how to get bold fonts" nl()
  "/bolddict 25 dict def" nl()
  "bolddict begin" nl()
  nl()
  "% default table entry is that boldfontname = fontname" nl()
  "fontname fontname def" nl()
  nl()
  "/Courier /Courier-Bold def" nl()
  "/Courier-Oblique /Courier-BoldOblique def" nl()
  "/Times-Roman /Times-Bold def" nl()
  "/Times-Italic /Times-BoldItalic def" nl()
  "/Helvetica /Helvetica-Bold def" nl()
  "/Helvetica-Oblique /Helvetica-BoldOblique def" nl()
  "/Bookman-Light /Bookman-Demi def" nl()
  "/Bookman-LightItalic /Bookman-DemiItalic def" nl()
  "/Palatino-Roman /Palatino-Bold def" nl()
  "/Palatino-Italic /Palatino-BoldItalic def" nl()
  "/AvantGarde-Book /AvantGarde-Demi def" nl()
  "/AvantGarde-BookOblique /AvantGarde-DemiOblique def" nl()
  "/Helvetica-Narrow /Helvetica-Narrow-Bold def" nl()
  "/Helvetica-Narrow-Oblique /Helvetica-Narrow-BoldOblique def" nl()
  "/Helvetica-Condensed /Helvetica-Condensed-Bold def" nl()
  "/Helvetica-Condensed-Oblique /Helvetica-Condensed-BoldObl def" nl()
  "/NewCenturySchlbk-Roman /NewCenturySchlbk-Bold def" nl()
  "/NewCenturySchlbk-Italic /NewCenturySchlbk-BoldItalic def" nl()
  "/ZapfChancery /ZapfChancery-Bold def" nl()
  "end" nl()
  nl()
  "/boldfontname fontname def" nl()
  "/boldfontname bolddict fontname get def" nl()
  nl()
  "% get printable area" nl()
  "clippath pathbbox newpath" nl()
  "/ury exch def /urx exch def" nl()
  "/lly exch def /llx exch def" nl()
  nl()
  "% adjust for PacificPage cartridge" nl()
  "statusdict /product known {" nl()
  "     statusdict begin product end (PacificPage) eq" nl()
  "     version (4.06) eq and {" nl()
  "             /lly lly 5 add def" nl()
  "             /ury ury 10 sub def" nl()
  "     } if" nl()
  "} if" nl()
  nl()
  "% set portrait mode, get width and height" nl()
  "/wp urx llx sub def" nl()
  "/hp ury lly sub def" nl()
  "/w wp xpages mul def" nl()
  "/h hp ypages mul def" nl()
  "portrait {" nl()
  "       % portrait mode" nl()
  "       llx lly translate" nl()
  "  } {" nl()
  "       % landscape mode" nl()
  "       /tmp hp def" nl()
  "       /hp wp def" nl()
  "       /wp tmp def" nl()
  "       /tmp h def" nl()
  "       /h w def" nl()
  "       /w tmp def" nl()
  "       urx lly translate 90 rotate" nl()
  "} ifelse" nl()
  nl()
  "% multi page output" nl()
  "xpages 1 ne ypages 1 ne or {" nl()
  "     portrait {" nl()
  "       1 xpage sub wp mul  1 ypage sub hp mul  translate" nl()
  "     } {" nl()
  "       1 ypage sub wp mul  1 xpage sub hp mul  translate" nl()
  "     } ifelse" nl()
  "} if" nl()
  nl()
  "% decorative border" nl()
  "border {" nl()
  "     /bwid1 2.5 def" nl()
  "     /gapwid 1.5 def" nl()
  "     /bwid2 0.7 def" nl()
  "     /bgap 10 def" nl()
  "     /tw 7.2 def" nl()
  "     /rect {" nl()
  "             /rh exch def" nl()
  "             /rw exch def" nl()
  "             moveto" nl()
  "             rw 0 rlineto" nl()
  "             0 rh rlineto" nl()
  "             rw neg 0 rlineto" nl()
  "             closepath stroke" nl()
  "     } def" nl()
  "     /rectt {" nl()
  "             /rh exch def" nl()
  "             /rw exch def" nl()
  "             /rhs rh tw sub tw sub def" nl()
  "             /rws rw tw sub tw sub def" nl()
  "             moveto" nl()
  "             0 tw rmoveto" nl()
  "             tw 0 rlineto" nl()
  "             0 tw neg rlineto" nl()
  "             rws 0 rlineto" nl()
  "             0 tw rlineto" nl()
  "             tw 0 rlineto" nl()
  "             0 rhs rlineto" nl()
  "             tw neg 0 rlineto" nl()
  "             0 tw rlineto" nl()
  "             rws neg 0 rlineto" nl()
  "             0 tw neg rlineto" nl()
  "             tw neg 0 rlineto" nl()
  "             closepath stroke" nl()
  "     } def" nl()
  nl()
  "     bwid1 setlinewidth" nl()
  "     lincolr0" nl()
  "     bwid1 2 div  dup  w bwid1 sub  h bwid1 sub  rectt" nl()
  nl()
  "     bwid2 setlinewidth" nl()
  "     bwid1 gapwid bwid2 2 div add add  dup" nl()
  "     w bwid1 2 mul sub gapwid 2 mul sub bwid2 sub " nl()
  "     h bwid1 2 mul sub gapwid 2 mul sub bwid2 sub rect" nl()
  nl()
  "     % cut the border out of the imageable area" nl()
  "     /tmp bwid1 gapwid bwid2 bgap add add add def" nl()
  "     tmp tmp translate" nl()
  "     /w w tmp 2 mul sub def" nl()
  "     /h h tmp 2 mul sub def" nl()
  "} if" nl()
  nl()
  "% for multi page: only label bottom left page" nl()
  "/chart_label where xpage 1 eq ypage 1 eq and and {" nl()
  "     pop" nl()
  "     gsave" nl()
  "     % set up coordinate system for custom chart label" nl()
  "     clippath pathbbox newpath pop pop translate" nl()
  "     chart_label" nl()
  "     grestore" nl()
  "} if" nl()
  nl()
  "% Reencode the font so that we can use the IBMPC set of international chars" nl()
  "/encdict 12 dict def" nl()
  "/reenc {" nl()
  "     encdict begin" nl()
  "     /newenc exch def" nl()
  "     /nfont exch def" nl()
  "     /ofont exch def" nl()
  "     /ofontdict ofont findfont def" nl()
  "     /newfont ofontdict maxlength 1 add dict def" nl()
  "     ofontdict {" nl()
  "             exch dup /FID ne {" nl()
  "                     dup /Encoding eq" nl()
  "                      {exch dup length array copy newfont 3 1 roll put}" nl()
  "                      {exch newfont 3 1 roll put} ifelse" nl()
  "             }" nl()
  "             {pop pop}" nl()
  "             ifelse" nl()
  "     } forall" nl()
  "     newfont /Fontname nfont put" nl()
  "     newenc aload pop" nl()
  "     newenc length 2 idiv" nl()
  "     { newfont /Encoding get 3 1 roll put}" nl()
  "     repeat" nl()
  "     nfont newfont definefont pop" nl()
  "     end" nl()
  "} def" nl()
  nl()
  "fontname /gedfont encvec reenc" nl()
  "/fontname /gedfont def" nl()
  "boldfontname /boldgedfont encvec reenc" nl()
  "/boldfontname /boldgedfont def" nl()
  "% end font reencoding" nl()
  nl()
  "/rl w maxlevel div def" nl()
  "/posunit h maxpos div def" nl()
  nl()
  "/posname 1.0 def" nl()
  "/posdate 0.75 def" nl()
  "/posmarg 0.3 def" nl()
  "/posbase posname posmarg 2 div add def" nl()
  nl()
  "/top h posunit posbase mul sub def" nl()
  nl()
  "% calculate base font size from segment length" nl()
  "/fntsize rl 9.0 div def" nl()
  nl()
  "% space for one individual" nl()
  "fntsize posunit gt {" nl()
  "     /fntsize posunit def" nl()
  "} if" nl()
  nl()
  "% font adjustment" nl()
  "/fntsize fntsize font_adjust mul def" nl()
  nl()
  "% font for birth/death dates" nl()
  "/fntsize2 fntsize posdate mul def" nl()
  nl()
  "fontname findfont fntsize scalefont setfont" nl()
  "/space ( ) stringwidth pop def" nl()
  nl()
  "% calc line width from segment length - .24 pts = 1 pixel" nl()
  "/linwid fntsize .1 mul .6 mul def" nl()
  "/linwid linwid linwidf mul def" nl()
  "linwid setlinewidth" nl()
  "/namey0 linwid fntsize 16.0 div add offset_name add def" nl()
  nl()
  "/dashwid rl 72 div def" nl()
  nl()
  "2 setlinecap" nl()
  nl()
  "% name string length for all generations" nl()
  "/len1 rl space indent 1 add mul sub def" nl()
  nl()
  "% show string given as argument" nl()
  "% select font size so that string fits in available length" nl()
  "/wshow {" nl()
  "     /s exch def" nl()
  "     /len exch def" nl()
  "     /fntsiz exch def" nl()
  "     bold direct and {" nl()
  "             boldfontname findfont fntsiz scalefont setfont" nl()
  "     } {" nl()
  "             fontname findfont fntsiz scalefont setfont" nl()
  "     } ifelse" nl()
  "     s stringwidth pop dup len lt {" nl()
  "             pop" nl()
  "     } {" nl()
  "             % compute new font size for exact fit" nl()
  "             len exch div fntsiz mul /fsize exch def" nl()
  "             bold direct and {" nl()
  "                     boldfontname findfont fsize scalefont setfont" nl()
  "             } {" nl()
  "                     fontname findfont fsize scalefont setfont" nl()
  "             } ifelse" nl()
  "     } ifelse" nl()
  "     direct {textcolr0} {textcolr1} ifelse" nl()
  "     s show" nl()
  "} bind def" nl()
  nl()
  "% give length of string given as argument" nl()
  "% select font size so that string fits in available length" nl()
  "/wlen {" nl()
  "     /s exch def" nl()
  "     /len exch def" nl()
  "     /fntsiz exch def" nl()
  "     bold direct and {" nl()
  "             boldfontname findfont fntsiz scalefont setfont" nl()
  "     } {" nl()
  "             fontname findfont fntsiz scalefont setfont" nl()
  "     } ifelse" nl()
  "     s stringwidth pop dup len lt {" nl()
  "             pop" nl()
  "     } {" nl()
  "             % compute new font size for exact fit" nl()
  "             len exch div fntsiz mul /fsize exch def" nl()
  "             bold direct and {" nl()
  "                     boldfontname findfont fsize scalefont setfont" nl()
  "             } {" nl()
  "                     fontname findfont fsize scalefont setfont" nl()
  "             } ifelse" nl()
  "     } ifelse" nl()
  "     s stringwidth pop dup" nl()
  "     pop" nl()
  "} bind def" nl()
  nl()
  "% called once for each individual on chart" nl()
  "/i {" nl()
  "     /des exch 1 eq def      % true if person has descendants" nl()
  "     /anc exch 1 eq def      % true if person has ancestors" nl()
  "     /duplic exch 1 eq def   % true for duplicate individual" nl()
  "     /direct exch 1 eq def   % true for direct ancestor, false for indirect" nl()
  "     /xhgt exch def          % extra height - not used here" nl()
  "     /pos exch def           % vertical position" nl()
  "     /level exch def         % generation level, 0 = youngest" nl()
  "     /marriage exch def      % marriage date" nl()
  "     /tagnote exch def       % tagged note from PAF" nl()
  "     /death exch def         % death date" nl()
  "     /birth exch def         % birth date" nl()
  "     /name exch def          % name" nl()
  nl()
  "     % x1 = left edge, x2 = right edge" nl()
  "     mirror {" nl()
  "             /x1 maxlevel level sub 1 sub rl mul def" nl()
  "             /x x1 space indent mul add def" nl()
  "     } {" nl()
  "             /x1 level rl mul def" nl()
  "             /x x1 space indent mul add def" nl()
  "     } ifelse" nl()
  "     /x2 x1 rl add def" nl()
  nl()
  "     % Column positions" nl()
  "     % x1=left x=start of text x2=right" nl()
  nl()
  "     /y top pos posunit mul sub def" nl()
  "     /y2 top pos 0.5 sub posunit mul sub def" nl()
  nl()
  "     lincolr0" nl()
  nl()
  "     /namey namey0 def" nl()
  nl()
  "     % Calcuate the printed length of the name" nl()
  "     fntsize len1 name wlen" nl()
  "     /lname exch def" nl()
  nl()
  "     % Find the size of the longest text string" nl()
  "     /ls lname def" nl()
  "     birth length 0 gt {" nl()
  "       fntsize2 len1 birth wlen" nl()
  "       /lb exch def" nl()
  "       lb ls gt {/ls lb def} if" nl()
  "     } if" nl()
  "     death length 0 gt {" nl()
  "       fntsize2 len1 death wlen" nl()
  "       /ld exch def" nl()
  "       ld ls gt {/ls ld def} if" nl()
  "     } if" nl()
  "     marriage length 0 gt {" nl()
  "       fntsize2 len1 marriage wlen" nl()
  "       /lm exch def" nl()
  "       lm ls gt {/ls lm def} if" nl()
  "     } if" nl()
  nl()
  "     anc not {" nl()
  "       /x x2 ls sub rl 0.05 mul sub space sub def" nl()
  "     } if" nl()
  "     des not {" nl()
  "       /x x1 rl 0.05 mul add space add def" nl()
  "     } if" nl()
  "     anc des and {" nl()
  "       /sr x2 x1 sub ls sub def" nl()
  "       /x sr 2 div space sub x1 add def" nl()
  "     } if" nl()
  nl()
  "     direct {" nl()
  "             bold {linwid 2.0 mul setlinewidth /namey namey linwid add def} if" nl()
  "             duplic {[dashwid dup currentlinewidth add] 0 setdash} if" nl()
  "             anc {x1 y2 moveto x space sub y2 lineto stroke} if" nl()
  "             des {x lname add space add y2 moveto x2 y2 lineto stroke} if" nl()
  "             duplic {[] 0 setdash} if" nl()
  "             bold {linwid setlinewidth} if" nl()
  "             % print name" nl()
  "             x y namey add moveto" nl()
  "             fntsize len1 name wshow" nl()
  "     } {" nl()
  "             duplic {[dashwid dup currentlinewidth add] 0 setdash} if" nl()
  "             mirror {" nl()
  "                     x1 y2 moveto x y2 lineto stroke" nl()
  "                     % print name" nl()
  "                     x y namey add moveto" nl()
  "                     fntsize len1 name wshow" nl()
  "             } {" nl()
  "                     x lname add space add y2 moveto x2 y2 lineto stroke" nl()
  "                     % print name" nl()
  "                     x y namey add moveto" nl()
  "                     fntsize len1 name wshow" nl()
  "             } ifelse" nl()
  "             duplic {[] 0 setdash} if" nl()
  "     } ifelse" nl()
  nl()
  "     % print birth/death" nl()
  "     birth length 0 gt {" nl()
  "             /y y fntsize2 sub def" nl()
  "             x y moveto" nl()
  "             fntsize2 len1 birth wshow" nl()
  "     } if" nl()
  "     death length 0 gt {" nl()
  "             /y y fntsize2 sub def" nl()
  "             x y moveto" nl()
  "             fntsize2 len1 death wshow" nl()
  "     } if" nl()
  "     marriage length 0 gt {" nl()
  "             /y y fntsize2 sub def" nl()
  "             x y moveto" nl()
  "             fntsize2 len1 marriage wshow" nl()
  "     } if" nl()
  "} bind def" nl()
  nl()
  "/l {" nl()
  "     /indirect exch 1 eq def" nl()
  "     /parent exch def" nl()
  "     /pos exch def" nl()
  "     /level exch def" nl()
  nl()
  "     mirror {" nl()
  "             /x maxlevel level sub 1 sub rl mul def" nl()
  "     } {" nl()
  "             /x level 1 add rl mul def" nl()
  "     } ifelse" nl()
  "     /y1 top pos posunit mul sub def" nl()
  "     /y2 top parent posunit mul sub def" nl()
  "     indirect {lincolr1} {lincolr0} ifelse" nl()
  "     bold indirect not and {linwid 2.0 mul setlinewidth} if" nl()
  "     x y1 moveto x y2 lineto stroke" nl()
  "     bold indirect not and {linwid setlinewidth} if" nl()
  "} bind def" nl()
}
