/** @file
 * libLASi provides a C++ output stream interface for writing 
 * Postscript documents containing text strings in any of the world's
 * scripts supported by Unicode 4.0 and Pango.
 * Copyright (C) 2003, 2004, 2006 by Larry Siden.
 * See README file in project root directory for copyright and contact info.
 * See COPYING file in project root for terms of re-distribution.
 */

#include <ostream>
#include <stdexcept>
#include <pango/pango.h>
#include <ctype.h>
#include <algorithm>
#include <cmath>
#include <LASi.h>
#include "contextMgr.h"
#include "glyphMgr.h"
#include "util.h"
#include "memory.h"
#include "stringDimensions.h"
#include <iomanip>

#include <stdlib.h>

using namespace std;
using namespace LASi;

// Convert a UTF-8 sequence of characters pointed to by ptr to a UCS4
// value pointed to by unichar.  (This function has been adapted from
// LGPL'd PLplot software).

#define LASI_INVALID_UCS4 0xffffffff

// This function returns NULL, if an invalid UTF-8 sequence of chars
// is detected.  In addition for this case the UCS4 *unichar value
// gets updated to the invalid UCS4 value of LASI_INVALID_UCS4.  If a valid
// UTF-8 sequence is detected this function returns a pointer to the
// memory location just after that sequence.  In addition for this
// case the UCS4 *unichar value gets updated to the value
// corresponding to the detected valid sequence.


static const char *
utf8_to_ucs4( const char *ptr, uint32_t *unichar )
{
    char tmp;
    int  isFirst = 1;
    int  cnt     = 0;

    do
    {
        // Get next character in string
        tmp = *ptr++;
        if ( isFirst ) // First char in UTF8 sequence
        {
            isFirst = 0;
            // Determine length of sequence
            if ( (unsigned char) ( tmp & 0x80 ) == 0x00 ) // single char
            {
                *unichar = (unsigned int) tmp & 0x7F;
                cnt      = 0;
            }
            else if ( (unsigned char) ( tmp & 0xE0 ) == 0xC0 ) // 2 chars
            {
                *unichar = (unsigned int) tmp & 0x1F;
                cnt      = 1;
            }
            else if ( (unsigned char) ( tmp & 0xF0 ) == 0xE0 ) // 3 chars
            {
                *unichar = (unsigned char) tmp & 0x0F;
                cnt      = 2;
            }
            else if ( (unsigned char) ( tmp & 0xF8 ) == 0xF0 ) // 4 chars
            {
                *unichar = (unsigned char) tmp & 0x07;
                cnt      = 3;
            }
            else if ( (unsigned char) ( tmp & 0xFC ) == 0xF8 ) // 5 chars
            {
                *unichar = (unsigned char) tmp & 0x03;
                cnt      = 4;
            }
            else if ( (unsigned char) ( tmp & 0xFE ) == 0xFC ) // 6 chars
            {
                *unichar = (unsigned char) tmp & 0x01;
                cnt      = 5;
            }
            else  // Malformed
            {
                *unichar = LASI_INVALID_UCS4;
                return NULL;
            }
        }
        else   // Subsequent char in UTF8 sequence
        {
            if ( (unsigned char) ( tmp & 0xC0 ) == 0x80 )
            {
                *unichar = ( *unichar << 6 ) | ( (unsigned int) tmp & 0x3F );
                cnt--;
            }
            else  // Malformed
            {
                *unichar = LASI_INVALID_UCS4;
                return NULL;
            }
        }
    } while ( cnt > 0 );
    return ptr;
}

static string nameof(const FT_Face& face, const FT_UInt glyph_index, uint32_t unichar) 
{
  const int N = 256; // Length of buffer to hold glyph name
  char glyph_name[N];
  static unsigned int unique_int = 1;

  if (!FT_HAS_GLYPH_NAMES(face)){
    // Since the glyph has no name entry for the font that is broken this way,
    // must generate our own unique glyph name.
    if(unichar != LASI_INVALID_UCS4)
      // This version should always produce exactly the same unique glyph name
      // for the same glyph no matter how many times nameof is called
      // and regardless of the order of the glyphs that are identified.
      snprintf(glyph_name, N, "LASi_glyph_U+%04X", unichar);
    else
      // This version produces a unique glyph_name with the
      // drawbacks that (i) multiple names can become
      // associated with the same glyph (if nameof is called
      // twice for the same glyph which does happen, e.g., when
      // bounding-box calculations are made)), and (ii) the derived
      // name depends very much on the order of glyphs that are encountered
      // in strings.  N.B. for this case we zero pad to 10 digits
      // to assure the lexical order of glyph names is the same
      // as the unique_int order.
      snprintf(glyph_name, N, "LASi_glyph_%010u", unique_int++);
  }else{
    // Get the glyph name from the font when present:
    FT_Get_Glyph_Name(face, glyph_index, glyph_name, N);
  }
  return string(glyph_name);
}

PostscriptDocument::GlyphId::GlyphId(FT_Face face, const FT_UInt index, uint32_t unichar) 
{
  const std::string glyphName(nameof(face, index, unichar));
  const std::string faceName(face->family_name); 
  const std::string& variant(face->style_name);
  ostringstream os;
  os << glyphName << '-' << faceName << '-' << variant << '-' << index;
  _str = os.str();
  const int len = _str.size();

  //DEBUG:
  //cerr << "PostscriptDocument::GlyphId::GlyphId(...) before _str=" << _str << endl;
  // replace spaces with '-'
  for (int i=0 ; i < len ; ++i) {
    if (isspace(_str[i]))
      _str.replace(i, 1, 1, '-');
  }
  //DEBUG:
  //cerr << "PostscriptDocument::GlyphId::GlyphId(...) _str=" << _str << endl;
}

PostscriptDocument::PostscriptDocument()
  : _pContextMgr(new ContextMgr()), _fontSize(10), _osBody(*this),
_osFooter(*this)
{}

PostscriptDocument::~PostscriptDocument() 
{
  delete _pContextMgr;
}

inline PangoContext* PostscriptDocument::pangoContext() const
{
  return static_cast<PangoContext*>(*_pContextMgr);
}

void PostscriptDocument::setFont(
    const char* const       family,
    LASi::FontStyle   style,
    LASi::FontWeight  weight,
    LASi::FontVariant variant,
    LASi::FontStretch stretch)
{
    
    //
    // Style:
    // 
    PangoStyle   _style;
    switch(style){
    case NORMAL_STYLE:
       _style=PANGO_STYLE_NORMAL;
       break;
    case ITALIC:
       _style=PANGO_STYLE_ITALIC;
       break;
    case OBLIQUE:
       _style=PANGO_STYLE_OBLIQUE;
       break;
    default:
       _style=PANGO_STYLE_NORMAL;
       break;    
    }
    
    //
    // Weight:
    // 
    PangoWeight  _weight;
    switch(weight){
    case NORMAL_WEIGHT:
       _weight=PANGO_WEIGHT_NORMAL;
       break;
    case BOLD:
       _weight=PANGO_WEIGHT_BOLD;
       break;
    case ULTRALIGHT:
       _weight=PANGO_WEIGHT_ULTRALIGHT;
       break;
    case LIGHT:
       _weight=PANGO_WEIGHT_LIGHT;
       break;
    case ULTRABOLD:
       _weight=PANGO_WEIGHT_ULTRABOLD;
       break;
    case HEAVY:
       _weight=PANGO_WEIGHT_HEAVY;
       break;
    default:
       _weight=PANGO_WEIGHT_NORMAL;
       break;    
    }
    
    //
    // Variant:
    //
    PangoVariant _variant;
    switch(variant){ 
    case NORMAL_VARIANT:
      _variant=PANGO_VARIANT_NORMAL;
      break;
    case SMALLCAPS:
      _variant=PANGO_VARIANT_SMALL_CAPS;
      break;
    default:
       _variant=PANGO_VARIANT_NORMAL;
       break;
    }
    
    //
    // Stretch:
    // 
    PangoStretch _stretch;
    switch(stretch){
    case NORMAL_STRETCH:
      _stretch=PANGO_STRETCH_NORMAL;
      break;
    case ULTRACONDENSED:
      _stretch=PANGO_STRETCH_ULTRA_CONDENSED;
      break;
    case EXTRACONDENSED:
      _stretch=PANGO_STRETCH_EXTRA_CONDENSED;
      break;
    case CONDENSED:
      _stretch=PANGO_STRETCH_CONDENSED;
      break;
    case SEMICONDENSED:         
      _stretch=PANGO_STRETCH_SEMI_CONDENSED;
      break;
    case SEMIEXPANDED:
      _stretch=PANGO_STRETCH_SEMI_EXPANDED;
      break;
    case EXPANDED:
      _stretch=PANGO_STRETCH_EXPANDED;
      break;
    case EXTRAEXPANDED:
      _stretch=PANGO_STRETCH_EXTRA_EXPANDED;
      break;
    case ULTRAEXPANDED:
      _stretch=PANGO_STRETCH_ULTRA_EXPANDED;
      break;
    default:
       _stretch=PANGO_STRETCH_NORMAL;
       break;    
    }

    PangoFontDescription* font_description = pango_font_description_new();
    pango_font_description_set_family (font_description, family);
    pango_font_description_set_style (font_description, _style);
    pango_font_description_set_weight (font_description, _weight);
    pango_font_description_set_variant (font_description, _variant);
    pango_font_description_set_stretch (font_description, _stretch);
    pango_font_description_set_size(font_description, DRAWING_SCALE*PANGO_SCALE);
    pango_context_set_font_description (static_cast<PangoContext*>(*_pContextMgr), font_description);
  
}

// Assume a given string s has been divided by pango_itemize into
// PangoItems where the substring glyphs associated with that item can be
// rendered with a constant face (defined as a freetype2 font of fixed
// family, slant, weight, and width), and pItem is a pointer to one
// of the PangoItems generated by pango_itemize.  The purpose of
// PangoItem_do is to process that PangoItem.
//
// N.B. Cannot be declared as a simple static function because GLYPH_FUNC is
// a private typedef for the PostscriptDocument class.

FT_Error PostscriptDocument::PangoItem_do(const char *s, PangoItem* const pItem, const GLYPH_FUNC func, void* contextData, bool applyOffset)
{
    // next_glyph and unichar both used to help create a unique glyph id.
    const char *next_glyph = s;
    uint32_t unichar;
	
    PangoGlyphString* const pGlyphString = pango_glyph_string_new();
    pango_shape(s, pItem->length, &pItem->analysis, pGlyphString);

    const FT_Face face = pango_ft2_font_get_face(pItem->analysis.font);
    //DEBUG
    //std::cerr << "face->family_name = " <<  face->family_name << std::endl;
    if (!(face->face_flags & FT_FACE_FLAG_SCALABLE)){
      //DEBUG
      //std::cerr << "face->family_name = " <<  face->family_name << " does not contain outline glyphs" << std::endl;
      return FT_Err_Invalid_Outline;
    }
    PangoGlyphInfo* const pGlyphInfo = pGlyphString->glyphs;
    // For each glyph within a pango item.
    for (int i=0 ; i < pGlyphString->num_glyphs ; ++i) {
      const FT_UInt glyph_index = pGlyphInfo[i].glyph;    // get glyph index
            
      FT_Error error;
      // This loop is initialized with next_glyph = s.  But if this is a subsequent iteration
      // of this loop and utf8_to_ucs4 has failed previously in this loop, then the following
      // values have already been set: next_glyph = NULL and unichar = LASI_INVALID_UCS4
      // so there is no need to call utf8_to_ucs4 again.
      if(next_glyph !=NULL)
	next_glyph = utf8_to_ucs4(next_glyph, &unichar); 
      PostscriptDocument::GlyphId glyphId(face, glyph_index, unichar); // construct GlyphId
      FreetypeGlyphMgr& glyphMgr = _glyphMap[glyphId];  // access glyph from map

      if (0 == static_cast<FT_Glyph>(glyphMgr)) { // start of logic block if glyph is not in map
        //
        // access glyph from font face and put it in map
        //
        FT_Glyph glyph;
        //DEBUG:
        //std::cerr << "Glyph Index: " << std::hex << glyph_index << std::endl;
        
        error = FT_Load_Glyph(face,glyph_index,FT_LOAD_NO_BITMAP);
        if(error){
          //
	  //DEBUG:
	  //
	  //std::cerr << "PANGO is returning a glyph index of " << std::hex << glyph_index << std::endl;
	  //std::cerr << "but PANGO_GLYPH_UNKNOWN_FLAG is supposed to be: " << 0x10000000 << std::endl;
	  //std::cerr << "and PANGO_GLYPH_EMPTY        is supposed to be: " << 0x0FFFFFFF << std::endl;
           
	  //std::cerr << "FT_Load_Glyph is returning error = " << std::hex << error << " for a glyph index of " << std::hex << glyph_index << " associated with the substring " << s << std::endl;
	  //std::cerr << "Attempting the glyph_index = 0 workaround" << std::endl;
	  //
	  // Substitute replacement glyph that often works:
	  // glyph_index of 0 is normally a signal that the glyph
	  // doesn't exist so most fonts are supposed to respond to
	  // glyph_index 0 with their default replacement glyph:
	  error = FT_Load_Glyph(face,0,FT_LOAD_NO_BITMAP); 
	  // N.B. error would continue to be non zero for non-outline
	  // fonts, but because those are intercepted with the
	  // face->face_flags check above, error *should* always be
	  // zero for this case.  But check it anyhow "just in case"
	  // there are other reasons why glyph_index 0 does not work.
	  if(error){
	    //DEBUG:
	    //std::cerr << "The attempted glyph_index = 0 workaround did not work" << std::endl;
	    //std::cerr << "FT_Load_Glyph is returning error = " << std::hex << error << " for a glyph index of " << std::hex << glyph_index << std::endl;
	    // erase bad item from map
	    _glyphMap.erase(glyphId);
	    return error;
	  }
	   
        }
	error = FT_Get_Glyph(face->glyph, &glyph);
	if(error)
	  {
	    //DEBUG:
	    //std::cerr << " FT_Get_Glyph is returning error = " << std::hex << error << " for a glyph index of " << std::hex << glyph_index << std::endl;
	    // erase bad item from map
	    _glyphMap.erase(glyphId);
	    return error;
	  }
	glyphMgr.assign(glyph);
      } // end of logic block if glyph is not in map

      ostream* pos = 0;
      double   x_rmove = 0, y_rmove = 0;
      if (applyOffset) {
        const PangoGlyphGeometry& geo = pGlyphInfo[i].geometry;
        if (geo.x_offset != 0 || geo.y_offset != 0) {
          const double scale = _fontSize / (DRAWING_SCALE * PANGO_SCALE);
          pos = reinterpret_cast<ostream*>(contextData);
          x_rmove = scale * geo.x_offset;
          y_rmove = scale * geo.y_offset;
          (*pos) << x_rmove << ' ' << y_rmove << " rmoveto" << endl;
        }
      }
      //
      // glyph is guaranteed to be in map:
      // Call the function that operates on the glyph:
      //
      (this->*func)(*_glyphMap.find(glyphId), contextData);
      if (applyOffset && pos) { // undo previous rmoveto
         (*pos) << -x_rmove << ' ' << -y_rmove << " rmoveto" << endl;
      }
    }  // End of glyph loop
    pango_glyph_string_free(pGlyphString);
    return FT_Err_Ok;
}

void PostscriptDocument::for_each_glyph_do(const string& s, const GLYPH_FUNC func, void* contextData,
     bool applyOffset)
{

  // String containing a glyph to to be used for emergency
  // replacements of glyphs that cause unfixable freetype errors.  In
  // this case we choose a newline because we have evidence that also
  // generates a freetype error, but one that is easy to fix up
  // (internally in PangoItem_do with the glyph_index = 0 fixup) by
  // replacing it with the default replacement glyph which is normally
  // an empty box for most fonts.  This is convoluted logic depending
  // on side interactions with PangoItem_do internal logic, but
  // necessary because I otherwise don't know how to generate the
  // desired default replacement glyph.
  const std::string er_s("\n");
  //DEBUG
  //const std::string er_s("er string");

  // Number of Unicode characters (not bytes) in the PangoItem.
  int nr_num_chars;

  std::string saved_truncated_s;
  std::string truncated_s;
  std::string working_s;
  bool er_cycle = false;
  bool some_error_occurred = false;
  // Note use of this pattern is recommended to assure deep copy because
  // some libraries still use copy-on-write despite this being forbidden
  // for std::string copies by modern C++ standard.
  saved_truncated_s = s.c_str();

  while(er_cycle || saved_truncated_s.length() > 0)
  {
    if(er_cycle){
      // Determine working_s that duplicates er_s nr_num_chars times,
      // where nr_num_chars is the number of unicode glyphs to be replaced.
      working_s = "";
      for (int i = 0; i < nr_num_chars; i++)
	working_s.append(er_s);
    }
    else{
      working_s = saved_truncated_s.c_str();
    }

  PangoAttrList* const attrList = pango_attr_list_new(); // needed only for call to pango_itemize()

  GList* glItems = pango_itemize(
      pangoContext(), 
      working_s.c_str(), 
      0, working_s.length(),
      attrList,
      (PangoAttrIterator *) 0);
  pango_attr_list_unref(attrList);

  //DEBUG
  //std::cerr << "working_s.c_str() = " << working_s.c_str() << std::endl;
  for (; glItems ; glItems = g_list_next(glItems)) {
    // For each pango item (string of UTF-8 characters in working_s with constant font attributes):
    PangoItem* const pItem = reinterpret_cast<PangoItem*>(glItems->data);
    // Truncated_s starts at one glyph beyond the first in the PangoItem that produced an error.
    // Save it with deep copy before working_s gets trashed.
    truncated_s = (working_s.c_str() + pItem->offset + pItem->length);
    //DEBUG:
    //std::cerr << "truncated_s.c_str() = " << truncated_s.c_str() << std::endl;
    FT_Error error = PangoItem_do(working_s.c_str() + pItem->offset, pItem, func, contextData, applyOffset);
    if(error)
      {
	//std::cerr << "FT_Error = " << std::hex << error << " generated by one of the PangoItems with font family name \"" << pango_ft2_font_get_face(pItem->analysis.font)->family_name << "\" corresponding to the overall string \"" << s.c_str() << "\"" << std::endl;
	//std::cerr << "Substituting blanks for the glyphs in this string that cannot be rendered by libLASi" << std::endl;
	some_error_occurred = true;
	// If error on er_cycle, that means that not even er_s works which is bad news indeed!
	if(er_cycle)
	  evalReturnCode(error, "PangoItem_do");
	
	// Save truncated_s with deep copy for after the er_cycle which will clobber truncated_s.
	saved_truncated_s = truncated_s.c_str();
	// Run an er_cycle to replace one glyph before continuing with saved_truncated_s.
	er_cycle = true;
	nr_num_chars = pItem->num_chars;
	pango_item_free(pItem);
	break;
      }
    pango_item_free(pItem);
  }
  g_list_free(glItems);
  if(some_error_occurred)
    {
      some_error_occurred = false;
      // If some_error_occurred, then er_cycle is true and you should process that case.
      continue;
    }
  if(er_cycle)
    // Got through er_s without some_error_occurred.  Now proceed with processing saved_truncated_s.
    er_cycle = false;
  else
    // Got through saved_truncated_s without issues.  DONE!
    break;
  }
}

/** Add the next glyphs dimensions to the bounding box (contextData).
  * If the advance is in the x direction (the usual case),
  * the box grows in the x direction, yMax becomes the height
  * of the tallest character, and yMin the descent of the most
  * descending character.
  *
  * @param mapval std::pair<GlyphId, FreetypeGlyphMgr>
  * @param contextData std::pair<double, double>, the x and y dimensions
  */
void PostscriptDocument::accrue_dimensions(
    const PostscriptDocument::GlyphMap::value_type& mapval, void* contextData)
{
  // const GlyphId& gid = static_cast<GlyphId>(mapval.first);
  const FreetypeGlyphMgr& glyphMgr = static_cast<FreetypeGlyphMgr>(mapval.second);
  const FT_Glyph& glyph = static_cast<FT_Glyph>(glyphMgr);
  //
  //
  //
  //const double y_adv = std::abs(glyph->advance.y / (double) 0x10000);
  //
  const double x_adv = std::abs(glyph->advance.x / (double) 0x10000); // convert from 16.16 format

  //
  //
  //
  FT_BBox bbox; 
  FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_UNSCALED, &bbox);

  //
  // Get the mins and maxes, converting from 26.6 format:
  //
  // const double x_min = bbox.xMin/64.0;
  // const double x_max = bbox.xMax/64.0;
  //
  const double y_min = bbox.yMin/64.0;
  const double y_max = bbox.yMax/64.0;
   
  StringDimensions *SD = reinterpret_cast<StringDimensions *>(contextData);

  SD->accrueXAdvance(x_adv);
  SD->setYMin(y_min);
  SD->setYMax(y_max);

}

/** Insert a Postscript glyph_routine call into output stream (contextData).
  */
void PostscriptDocument::invoke_glyph_routine(
    const PostscriptDocument::GlyphMap::value_type& mapval, void* contextData)
{
  const GlyphId& gid = static_cast<GlyphId>(mapval.first);
  ostream* pos = reinterpret_cast<ostream*>(contextData);
  static_cast<ostream&>(*pos) << this->getFontSize() << " " << gid.str() << endl;
}

/** Returns the line spacing, x-advance, y-minimum and y-maximum
  * based on the current font face and font size.  A bounding box
  * around the text string, s, can be constructed from the xAdvance,
  * yMinimum, and yMaximum.  yMinimum tells you the descent from the
  * baseline. yMaximum tells you the ascent from the baseline.
  * The line spacing provides an inter-line spacing for multi-line 
  * text layout.
  *
  * This version accepts a const C-style character string.
  *
  */
void PostscriptDocument::get_dimensions(const char* s, double *lineSpacing, double *xAdvance, double *yMin, double *yMax)
{

  StringDimensions SD;
  for_each_glyph_do(s, &PostscriptDocument::accrue_dimensions,&SD);

  const double scale = _fontSize / DRAWING_SCALE;
  //
  // We always want to retrieve at least the lineSpacing:
  //
  *lineSpacing = SD.getLineSpacing() * scale;
  //
  // But xAdvance, yMin, and yMax are only necessary to retrieve
  // if we want to have the bounding box, so we allow default
  // parameters set to NULL:
  //
  if(xAdvance!=NULL)  *xAdvance = SD.getXAdvance() * scale;
  if(yMin    !=NULL)  *yMin     = SD.getYMin()     * scale;
  if(yMax    !=NULL)  *yMax     = SD.getYMax()     * scale;
  
}

/** Returns the line spacing, x-advance, y-minimum and y-maximum
  * based on the current font face and font size.  A bounding box
  * around the text string, s, can be constructed from the xAdvance,
  * yMinimum, and yMaximum.  yMinimum tells you the descent from the
  * baseline. yMaximum tells you the ascent from the baseline.
  * The line spacing provides an inter-line spacing for multi-line 
  * text layout.
  *
  * This version accepts an STL standard string class string.
  *
  */
void PostscriptDocument::get_dimensions(std::string s, double *lineSpacing, double *xAdvance, double *yMin, double *yMax){
	
	get_dimensions(s.c_str(),lineSpacing,xAdvance,yMin,yMax);
	
}


void show::apply(oPostscriptStream& os) const
{
  //DEBUG
  //cerr << "show::apply(os): _str = " << _str << endl;
  PostscriptDocument& doc = os.doc();
  doc.for_each_glyph_do(_str, &PostscriptDocument::invoke_glyph_routine, &os, true);
}

const unsigned int PostscriptDocument::DRAWING_SCALE = PANGO_SCALE;

/**
  * Writes out the document.
  *
  */
void PostscriptDocument::write(std::ostream& os, double llx, double lly, double urx, double ury)
{
  //
  // Output document header:
  //

  //
  // If any of the bounding box parameters are non-zero,
  // then write out an EPS document with a bounding box:
  //
  if(llx || lly || urx || ury){  
    //
    // Encapsulated PostScript Header:
    //
    os << "%!PS-Adobe-3.0 EPSF-3.0" << endl;
    os << "%%BoundingBox: " << int(llx) << " " << int(lly) << " " << int(ceil(urx)) << " " << int(ceil(ury)) << endl;
    os << "%%HiResBoundingBox: " << std::setprecision(9) << llx << " " << lly << " " << urx << " " << ury << endl;
  }else{
    //
    // Normal Postscript header for print media:
    //
    os << "%!PS-Adobe-3.0" << endl;  
  }
  //
  // Rest of header:
  //
  os << "%%Creator: libLASi C++ Stream Interface for Postscript. LASi is hosted on http://www.unifont.org." << endl;
  os << "%%Copyright: (c) 2003, 2004, 2006 by Larry Siden.  All Rights Reserved. Released under the LGPL." << endl;
  os << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%" << endl;
  os << "% START Document Header:" << endl;
  os << "%" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;

  //
  // If a "%!PS" is found at the beginning of the user's header,
  // warn them that LASi already provides that:
  //
  if( _osHeader.str().find("%!PS")!=string::npos) cerr << "WARNING: LASi automatically provides \"%!PS-Adobe-3.0\" at the start of the document!" << endl;

  //
  // Make sure there is a "%%BeginProlog" to complement the "%%EndProlog" that
  // LASi puts after the end of the glyph routines:
  // 
  if( _osHeader.str().find("%%BeginProlog")==string::npos) os << "%%BeginProlog" << endl;
  
  os << _osHeader.str() << endl;

  //
  // Write out the glyph routines:  
  //
    
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%" << endl;
  os << "% START LASi Glyph Routines:" << endl;
  os << "%" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%%BeginResource: GlyphRoutines" << endl;
  
  for_each(_glyphMap.begin(), _glyphMap.end(),
      write_glyph_routine_to_stream(os, static_cast<PangoContext*>(*_pContextMgr)));
 
  os << "%%EndResource" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%" << endl;
  os << "% END LASi Glyph Routines:"   << endl;
  os << "%" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%%EndProlog" << endl;
  
  os << endl;
  
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%" << endl;
  os << "% START Document Body:"       << endl;
  os << "%" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;

  os << _osBody.str() << endl;

  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%" << endl;
  os << "% END Document Body:"         << endl;
  os << "%" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%" << endl;
  os << "% START Document Footer:"     << endl;
  os << "%" << endl;
  os << "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" << endl;
  os << "%%Trailer" << endl;
    
  os << _osFooter.str() << endl;
  
  os << "%%EOF" << endl;
  
}
