#ifndef LASI_H
#define LASI_H

/** @file
 *  Defines oPostscriptStream and PostscriptDocument and various 
 *  stream manipulators.
 */

#include <string>
#include <ostream>
#include <sstream>
#include <map>
#include <pango/pango.h>
#include <freetype/ftglyph.h>

class FreetypeGlyphMgr;
class ContextMgr;

// These macros are needed for Visual C++ and other Windows compilers.
// All functions/classes marked with LASIDLLIMPEXP can be imported form/exported to the dll.
// Has no impact on other platforms
#if defined(WIN32)
  /* Visual C/C++, Borland, MinGW and Watcom */
  #if defined(__VISUALC__) || defined(_MSC_VER) || defined(__BORLANDC__) || defined(__GNUC__) || defined(__WATCOMC__) || defined(__CYGWIN__)
    #define LASIDLLEXPORT __declspec(dllexport)
    #define LASIDLLIMPORT __declspec(dllimport)
  #else
    #define LASIDLLEXPORT
    #define LASIDLLIMPORT
  #endif
#else
  #define LASIDLLEXPORT
  #define LASIDLLIMPORT
#endif

#if defined(LASi_EXPORTS)
  #define LASIDLLIMPEXP LASIDLLEXPORT
  #define LASIDLLIMPEXP_DATA(type) LASIDLLEXPORT type
#elif defined(LASi_DLL)
  #define LASIDLLIMPEXP LASIDLLIMPORT
  #define LASIDLLIMPEXP_DATA(type) LASIDLLIMPORT type
#else
  #define LASIDLLIMPEXP
  #define LASIDLLIMPEXP_DATA(type) type
#endif

namespace LASi {

  enum FontStyle{
    NORMAL_STYLE,
    OBLIQUE,
    ITALIC
  };

  enum FontWeight{
    ULTRALIGHT,
    LIGHT,
    NORMAL_WEIGHT,
    BOLD,
    ULTRABOLD,
    HEAVY
  };

  enum FontVariant{
    NORMAL_VARIANT,
    SMALLCAPS
  };

  enum FontStretch{
    ULTRACONDENSED,
    EXTRACONDENSED,
    CONDENSED,
    SEMICONDENSED,
    NORMAL_STRETCH,
    SEMIEXPANDED,
    EXPANDED,
    EXTRAEXPANDED,
    ULTRAEXPANDED
  };
  
  class PostscriptDocument;
  class write_glyph_routine_to_stream;

  /** Just like any ordinary ostringstream,
   * but maintains a reference to the PostscriptDocument.
   */
  class LASIDLLIMPEXP oPostscriptStream : public std::ostringstream {
    public:
      friend class PostscriptDocument;
      friend class show;
      friend class setFont;
      friend class setFontSize;

      oPostscriptStream(PostscriptDocument& psDoc) : _psDoc(psDoc) {}

    protected:
      PostscriptDocument& doc() {return _psDoc;}

    private:
      PostscriptDocument& _psDoc;
  };

  template<class T>
    inline oPostscriptStream& operator<<(oPostscriptStream& os, T t) {
      static_cast<std::ostream&>(os) << t;
      return os;
    }

  /** Composes Postscript document as three separate and independant streams
   * for header, body and footer.  Body and footer streams respond to 
   * LASi::show applicator which generates Postscript commands to display a 
   * string by using glyph routines instead of a Postscript font.
   */
  class LASIDLLIMPEXP PostscriptDocument {
    public:
      friend class write_glyph_routine_to_stream; // helper class
      friend class show;

      PostscriptDocument();
      ~PostscriptDocument();

      /** Sets the font that all subsequent text written
       * to bodyStream() or footerStream() will be rendered with.
       */
      void setFont(
          const char* const family = "sans",
          LASi::FontStyle   = LASi::NORMAL_STYLE,
          LASi::FontWeight  = LASi::NORMAL_WEIGHT,
          LASi::FontVariant = LASi::NORMAL_VARIANT,
          LASi::FontStretch = LASi::NORMAL_STRETCH
      );

      /** Sets the font size, in points, that all subsequent text written
       * to bodyStream() or footerStream() will be rendered with.
       */
      void setFontSize(const double size) {_fontSize = size;}

      /** Returns stream for Postscript header.
       */
      std::ostringstream& osHeader() {return _osHeader;}

      /** Returns stream for Postscript body.
       */
      oPostscriptStream& osBody() {return _osBody;}

      /** Returns stream for Postscript footer.
       */
      oPostscriptStream& osFooter() {return _osFooter;}

      /** Closes all streams and writes completed Postscript document to os.
       * Header will include glyph routines for all text glyphs in body and footer.
       * 
       * 2006.05.01.ET Addendum: To create an EPS document, just include the
       * the four BoundingBox coordinates llx, lly, urx, ury (dimensions in points).
       * These are optional parameters -- When not included, you'll get a regular PS
       * document.  When included, you'll get an EPS document.
       * 
       */
      void write(std::ostream& os, double llx=0, double lly=0, double urx=0, double ury=0);

      /** Return string dimensions:
        * lineSpacing: inter-line spacing 
        * xAdvance:    width of the string
        * yMin:        y-coordinate bounding the lowest descender, Indic under-consonantal vowel, etc.
        * yMax:        y-coordinate bounding the highest ascender, diacritic, Indic over-letter vowel, etc.
        */
      void get_dimensions(const char* s, double *lineSpacing, double *xAdvance=NULL, double *yMin=NULL, double *yMax=NULL);
      void get_dimensions(std::string s, double *lineSpacing, double *xAdvance=NULL, double *yMin=NULL, double *yMax=NULL);

    protected:
      /** For internal use only.
        * @internal
        * A unique id for a glyph that is composed of its font face and a unique integer.
        */
      class GlyphId {
        public:
          friend bool operator==(const GlyphId id1, const GlyphId id2) {
            return id1._str == id2._str;
          }

          friend bool operator<(const GlyphId id1, const GlyphId id2) {
            return id1._str < id2._str;
          }

          GlyphId() {}
          GlyphId(FT_Face, const FT_UInt);

          /** @return string representation of glyph id */
          std::string str() const {return _str;}

        private:
          /** @return string representation of glyph id */
          std::string _str;
      };

      /** Maps glyph routine name to FT_Glyph instance.
       */
      typedef std::map<GlyphId, FreetypeGlyphMgr> GlyphMap;

      /** Pointer to a function that takes a reference to a glyph
       * and to x and y coordinates.
       * May return new x and y coordinates.
       */
      typedef void (PostscriptDocument::*GLYPH_FUNC)(
          const GlyphMap::value_type&, void* contextData);

      void invoke_glyph_routine(const GlyphMap::value_type&, void* contextData);

      void accrue_dimensions( const GlyphMap::value_type&, void* contextData1);

      /** Decomposes string into glyphs and applies GLYPH_FUNC to each glyph.
       */
      void for_each_glyph_do(const std::string&, const GLYPH_FUNC, void* contextData);

      PangoContext* pangoContext() const;

      /** @returns name of Postscript glyph routine
       */
      std::string glyphProcName() const;

      /** @return font size in points (1/72 in.)
       */
      double getFontSize() {return _fontSize;}

      /** For internal use only.
        */
      class write_glyph_routine_to_stream {
        private:
          std::ostream& os;
          PangoContext* pangoCtx;

        public:
          write_glyph_routine_to_stream(std::ostream& os, PangoContext* pangoCtx) 
            : os(os), pangoCtx(pangoCtx) {}
          void operator()(PostscriptDocument::GlyphMap::value_type v);
      };

    private:
      GlyphMap _glyphMap;

      static const unsigned int DRAWING_SCALE;

      // Use pointers instead of objects in order to minimize namespace pollution of .h file user
      // Requires fwd declarations above.
      ContextMgr* _pContextMgr;     // manage PangoContext*
      double _fontSize;             // font size to be used when rendering next show()
      std::ostringstream _osHeader; // Postscript header
      oPostscriptStream _osBody;    // Postscript body
      oPostscriptStream _osFooter;  // Postscript footer
  };

  /** stream manipulator applied to oPostscriptStream.
   */
  class LASIDLLIMPEXP setFont {
    public:
      /** Stream inserter for 'setFont' stream manipulator
       */
      friend inline oPostscriptStream& operator<<(oPostscriptStream& os, const setFont& x) {
        x.apply(os);
        return os;
      }

      /** Usage:
       *   os << setFont("Vera Sans",LASi::ITALIC,LASi::BOLD) << ...
       */
      setFont(
          const char* const family = "sans",
          const LASi::FontStyle   style   = LASi::NORMAL_STYLE,
          const LASi::FontWeight  weight  = LASi::NORMAL_WEIGHT,
          const LASi::FontVariant variant = LASi::NORMAL_VARIANT,
          const LASi::FontStretch stretch = LASi::NORMAL_STRETCH )
          : _family(family), _style(style), _weight(weight), _variant(variant), _stretch(stretch)
      {}
      
    protected:
      void apply(oPostscriptStream& os) const {
        os.doc().setFont(_family, _style,_weight, _variant,  _stretch);
      }

    private:
      const char* const  _family;
      const LASi::FontStyle   _style;
      const LASi::FontWeight  _weight;
      const LASi::FontVariant _variant;
      const LASi::FontStretch _stretch;
      
  };

  /** stream manipulator applied to oPostscriptStream.
   */
  class LASIDLLIMPEXP setFontSize {
    public:
      /** Stream inserter for 'setFontSize' stream manipulator
       */
      friend inline oPostscriptStream& operator<<(oPostscriptStream& os, const setFontSize& x) {
        x.apply(os);
        return os;
      }

      /** Usage:
       *   os << setFontSize(12) << ...
       * @param size size in points
       */
      setFontSize(double size) : _size(size) {}

    protected:
      void apply(oPostscriptStream& os) const {
        os.doc().setFontSize(_size);
      }

    private:
      double _size;
  };

  /** stream applicator applied to oPostscriptStream.
   */
  class LASIDLLIMPEXP show{
    public:
      /** stream inserter for 'show' stream applicator
       */
      friend inline oPostscriptStream& operator<<(oPostscriptStream& os, const show& x) {
        x.apply(os);
        return os;
      }

      /** Usage:
       *   os << show("some UTF-8 text") << ...
       */
      show(const char* c_str   ) : _str(c_str  ) {}
      show(std::string stl_str ) : _str(stl_str) {}

    protected:
      void apply(oPostscriptStream& os) const;

    private:
      std::string _str;
  };
}
#endif

/** @mainpage
 * @section purpose Purpose
 * 
 * LASi is Copyright (C) 2003, 2004, 2006 by Larry Siden.
 * 
 * LASi is hosted on http://www.unifont.org
 * 
 * LASi provides a C++ output stream interface for writing Postscript
 * documents containing any of the world's scripts supported by Unicode 
 * 4.0 and by Pango. Right-to-left scripts like Hebrew and Arabic are 
 * accomodated as easily as left-to-right scripts.  Complex text layout (CTL) 
 * scripts such as Devanagari, Thai, Lao and Tibetan are supported to the 
 * extent provided by Pango and by the OpenType fonts installed on your system.
 * 
 * Postscript's <b>show</b> operator and font dictionaries can
 * only provide glyphs for up to 255 code points and thus are hopelessly
 * inadequate for many of the world's scripts, much less any kind of 
 * multilingual or scientific text encoded in Unicode.
 * 
 * LASi works around the limitations of Postscript's built-in font technology
 * by generating a Postscript routine for each glyph in a string.
 * A LASi user writes a Postscript program much as he or she may have done in the
 * past, but now uses LASi's <b>show()</b> method instead of the Postscript 
 * <b>show</b> operator.  The user simply passes UTF-8 text strings
 * directly to LASi's show() method. 
 * 
 * To draw the text string, LASi generates a sequence of calls
 * to LASi-generated Postscript glyph routines.  Glyph routines are only
 * generated for glyphs that are actually used in the text.  In this respect,
 * LASi is efficient even for Chinese, Japanese, or Korean (CJK).
 * 
 * In addition to being efficient, the Postscript code and routines 
 * created by LASi are intelligible to human readers which facilitates 
 * debugging of Postscript programs.
 * 
 * @section usage Usage
 *
 * Please view the examples provided in the <b>examples</b> directory to see how LASi is used.  
 * The only header your program need include is <b>LASi.h</b>.
 * View the examples' <b>Makefiles</b> to see how a program may link with libLASi.
 * 
 */
