#include <iostream>
#include <fstream>
#include <stdexcept>
#include <cmath>
#include <LASi.h>

#ifndef MAX
#define MAX( a, b )                      ( ( ( a ) > ( b ) ) ? ( a ) : ( b ) )
#endif
#ifndef MIN
#define MIN( a, b )                      ( ( ( a ) < ( b ) ) ? ( a ) : ( b ) )
#endif

#define LASI_POINTS_PER_CM (72./2.54)

// Useful macro for showing strings and updating bounding box


// Macro to show string and retrieve the dimensions of the
// corresponding text box.
#define LASI_SHOW_AND_RETRIEVE_BOX(string)                               \
  doc.osBody() << show(string);                                          \
  doc.get_dimensions(string,&lineSpacing,&xAdvance,&yMinimum,&yMaximum);

// Macro to show string and update bounding box on the assumption that
// the text is not rotated.
// x, y, llx, urx, lly, and ury must be previously defined.
#define LASI_SHOW_AND_UPDATE_BB(string) \
  LASI_SHOW_AND_RETRIEVE_BOX(string);   \
  llx = MIN(llx, x);                    \
  urx = MAX(urx, x+xAdvance);           \
  lly = MIN(lly, y+yMinimum);           \
  ury = MAX(ury, y+yMaximum);           \
  x+=xAdvance;

// Macro to show string and update bounding box on the assumption that
// the text is scaled but not rotated.
// overall_xscale, overall_yscale, x, y, llx, urx, lly, and ury must be previously defined.
#define LASI_SCALED_SHOW_AND_UPDATE_BB(xscale, yscale, string) \
  overall_xscale *= xscale; \
  overall_yscale *= yscale; \
  doc.osBody() << xscale << " " << yscale << " scale" << endl; \
  LASI_SHOW_AND_RETRIEVE_BOX(string);                          \
  llx = MIN(llx, x);                                           \
  urx = MAX(urx, x+(overall_xscale*xAdvance));                 \
  lly = MIN(lly, y+(overall_yscale*yMinimum));                 \
  ury = MAX(ury, y+(overall_yscale*yMaximum));                 \
  x+=overall_xscale*xAdvance;

#define LASI_PI 3.1415926535897932384
#define LASI_RAD_PER_DEGREE (LASI_PI/180.)

// Macro to show string and update bounding box on the assumption that
// the text is rotated by an angle (in degrees) around the x, y, point.
// overall_theta, x, y, llx, urx, lly, and ury must be previously defined.
// N.B. to transform from text box coordinates dx and dy (relative to
// the rotation point x, y) to PostScript coordinates, the formulas are
// dx' = dx cos theta - dy sin theta
// dy' = dx sin theta + dy cos theta

#define LASI_ROTATED_SHOW_AND_UPDATE_BB(angle, string)   \
  doc.osBody() << angle << " rotate" << endl;            \
  theta = overall_theta + angle*LASI_RAD_PER_DEGREE;         \
  overall_theta = theta;                                     \
  cosine = cos(theta);                                   \
  sine = sin(theta);                                     \
  LASI_SHOW_AND_RETRIEVE_BOX(string);                    \
  /* for the ll corner, dx = 0, dy = yMinimum */         \
  dxprime = -yMinimum*sine;                              \
  dyprime = yMinimum*cosine;                             \
  llx = MIN(llx, x+dxprime);                             \
  urx = MAX(urx, x+dxprime);                             \
  lly = MIN(lly, y+dyprime);                             \
  ury = MAX(ury, y+dyprime);                             \
  /* for the ul corner, dx = 0, dy = yMaximum */         \
  dxprime = -yMaximum*sine;                              \
  dyprime = yMaximum*cosine;                             \
  llx = MIN(llx, x+dxprime);                             \
  urx = MAX(urx, x+dxprime);                             \
  lly = MIN(lly, y+dyprime);                             \
  ury = MAX(ury, y+dyprime);                             \
  /* for the ur corner, dx = xAdvance, dy = yMaximum */  \
  dxprime = xAdvance*cosine - yMaximum*sine;             \
  dyprime = xAdvance*sine + yMaximum*cosine;             \
  llx = MIN(llx, x+dxprime);                             \
  urx = MAX(urx, x+dxprime);                             \
  lly = MIN(lly, y+dyprime);                             \
  ury = MAX(ury, y+dyprime);                             \
  /* for the lr corner, dx = xAdvance, dy = yMinimum */  \
  dxprime = xAdvance*cosine - yMinimum*sine;             \
  dyprime = xAdvance*sine + yMinimum*cosine;             \
  llx = MIN(llx, x+dxprime);                             \
  urx = MAX(urx, x+dxprime);                             \
  lly = MIN(lly, y+dyprime);                             \
  ury = MAX(ury, y+dyprime);                             \
  /* for the next origin point, dx = xAdvance, dy = 0 */ \
  dxprime = xAdvance*cosine;                             \
  dyprime = xAdvance*sine;                               \
  x+=dxprime;                                            \
  y+=dyprime;

using namespace LASi;
using namespace std;

int main(const int argc, char* const argv[]) 
{

  ofstream strm;

  try {

    //
    // This is the Postscript document object:
    //
    PostscriptDocument doc;

    double xAdvance,yMinimum,yMaximum,lineSpacing, yDelta, leftMargin, verticalPosition, verticalIncrement;
    double x,y,llx,urx,lly,ury;
    double overall_theta, theta, cosine, sine, dxprime, dyprime;
    double overall_xscale, overall_yscale;

    /////////////////////////////////////////////////
    //
    // DOCUMENT HEADER:
    //
    // Here is where you put user-defined Postscript
    // procedures.  LASi's glyph procedures also end up
    // in the header of the final Postscript document:
    //
    /////////////////////////////////////////////////
    doc.osHeader() << "%%%%%%%%%%%%%%%             " << endl;
    doc.osHeader() << "%                           " << endl;
    doc.osHeader() << "% Unit metrics:             " << endl;
    doc.osHeader() << "%                           " << endl;
    doc.osHeader() << "%%%%%%%%%%%%%%%             " << endl;
    doc.osHeader() << "/inch {72. mul}      bind def" << endl;
    doc.osHeader() << "/cm {inch 2.54 div} bind def" << endl;
    doc.osHeader() << "/mm {cm 10. div}     bind def" << endl;

    /////////////////////////////////////////////////
    //
    // DOCUMENT BODY:
    //
    /////////////////////////////////////////////////
    
    //
    // The Vera font family is available from www.gnome.org
    // See http://www.unifont.org/fontguide/ for download site
    //
    doc.osBody() << setFont("Vera Sans",LASi::NORMAL_STYLE,LASi::BOLD) << setFontSize(30);
    doc.osBody() << "gsave" << endl;
    doc.osBody() << "2 cm 26 cm moveto" << endl;

    // Position of start of string (in units of points).
    x = 2.*LASI_POINTS_PER_CM;
    y = 26.*LASI_POINTS_PER_CM;
    // Corresponding initial total bounding box values.
    llx = x;
    urx = x;
    lly = y;
    ury = y;
    doc.osBody() << "0.61 0.22 0.10 setrgbcolor" << endl;
    LASI_SHOW_AND_UPDATE_BB("L");

    doc.osBody() << "0.93 0.85 0.65 setrgbcolor" << endl;
    LASI_SHOW_AND_UPDATE_BB("A");

    doc.osBody() << "0.83 0.58 0.32 setrgbcolor" << endl;
    LASI_SHOW_AND_UPDATE_BB("S");

    doc.osBody() << "0.16 0.07 0.07 setrgbcolor" << endl;
    LASI_SHOW_AND_UPDATE_BB("i");

    doc.osBody() << "grestore" << endl;
    
    //
    // Set the standard font that we are going to use for comments in the document:
    //    
    doc.osBody() << setFont("Vera Sans",LASi::ITALIC) << setFontSize(12);
    //
    // Determine a line spacing for the Vera Sans 12 point strings:
    // Be sure to pass a string to get_dimensions() that will have the
    // maximum ascenders and descenders that you will need for your text.
    // For example, for Latin text, at least one among "A", "X", "b", "d", and "k" 
    // should be maximally ascendant, while at least one among "g", "p", "q",
    // and "y" should be maximally descendant.
    //
    // For other languages and scripts, you would choose different strings
    // for fixing line spacing.  For a CTL script like Thai or Hindi, you
    // would definitely want to include vowels and other diacritic marks that
    // sit above or below consonants and would use a string like "ดูที่นี้" (Thai) or
    // "कि कु कू कै" (Devanagari).  For Arabic, you might have to decide
    // whether you are going to have vocalized or unvocalized text, since
    // vocalization will increase the required line spacing.
    //
    doc.get_dimensions("AXbdk gpqy",&verticalIncrement);
    //
    // Define a Postscript procedure called "newLine" which will use "verticalIncrement" for the increment:
    //
    leftMargin = 2.;
    verticalPosition = 25.;
    doc.osHeader() << "/leftMargin " << leftMargin << " cm def" << endl;
    doc.osHeader() << "/verticalPosition " << verticalPosition << " cm def" << endl;
    doc.osHeader() << "/verticalIncrement " << verticalIncrement << " def" << endl;
    doc.osHeader() << "/newLine {" << endl;
    doc.osHeader() << " /verticalPosition verticalPosition verticalIncrement sub def" << endl;  
    doc.osHeader() << " leftMargin verticalPosition moveto" << endl;
    doc.osHeader() << "} bind def" << endl;
    doc.osBody() << "leftMargin verticalPosition moveto" << endl;
    
    // Position of start of string (in units of points).
    x = leftMargin*LASI_POINTS_PER_CM;
    y = verticalPosition*LASI_POINTS_PER_CM;
    LASI_SHOW_AND_UPDATE_BB("LASi uses Pango's layout services to make laying out left-to-right");
    doc.osBody() << "newLine" << endl;
    x = leftMargin*LASI_POINTS_PER_CM;
    y-=verticalIncrement;
    LASI_SHOW_AND_UPDATE_BB("and right-to-left text trivially easy:");

    //
    // You can always use the virtual "serif" or "sans" font
    // if you don't want to specify a specific font:
    //    
    doc.osBody() << setFont("serif") << setFontSize(22) << endl;
    //
    // Here we pass the full set of parameters to get dimensions:
    //
    doc.get_dimensions("Hello World! / שלום",&verticalIncrement,&xAdvance,&yMinimum,&yMaximum);
    //
    // Draw a rectangle showing the bounding box of the string:
    //
    yDelta=yMaximum-yMinimum;
    doc.osBody() << "gsave" << endl;
    doc.osBody() << "newpath" << endl;
    doc.osBody() << "1 0 0 setrgbcolor" << endl;
    doc.osBody() << "2.5 cm 23.2 cm moveto" << endl;
    doc.osBody() << 0 << " " << yMinimum << " rmoveto " << endl;
    doc.osBody() << 0 << " " << yDelta << " rlineto "  << endl;
    doc.osBody() << xAdvance << " " << 0 << " rlineto "  << endl;
    doc.osBody() << 0 << " " << -yDelta << " rlineto " << endl;
    doc.osBody() << "closepath" << endl;
    doc.osBody() << "stroke" << endl;
    doc.osBody() << "grestore" << endl;

    // Overall bounding box corresponding to above box:

    x = 2.5*LASI_POINTS_PER_CM;
    y = 23.2*LASI_POINTS_PER_CM;
    llx = MIN(llx, x);
    urx = MAX(urx, x+xAdvance);
    lly = MIN(lly, y+yMinimum);
    ury = MAX(ury, y+yMaximum);
    //
    // Now print the string:
    //
    doc.osBody() << "2.5 cm 23.2 cm moveto" << endl;
    x = 2.5*LASI_POINTS_PER_CM;
    y = 23.2*LASI_POINTS_PER_CM;
    LASI_SHOW_AND_UPDATE_BB("Hello World! / ");
    LASI_SHOW_AND_UPDATE_BB("שלום");//shalom

    doc.osBody() << setFont("Vera Sans",LASi::ITALIC) << setFontSize(12);
    doc.osBody() << "leftMargin 22 cm moveto" << endl;
    x = leftMargin*LASI_POINTS_PER_CM;
    y = 22.*LASI_POINTS_PER_CM;
    LASI_SHOW_AND_UPDATE_BB("Scripts with complex layout requirements, such as Arabic and Thai, are supported:");

    /////////////////////////////////////////////////
    //
    // "ALEXANDRIA" IN ARABIC: الإسكندرية
    //
    /////////////////////////////////////////////////
    doc.osBody() << "gsave" << endl;
    doc.osBody() << "2.5 cm 20.5 cm moveto" << endl;
    x = 2.5*LASI_POINTS_PER_CM;
    y = 20.5*LASI_POINTS_PER_CM;
    //
    // The Ae_Cortoba font is available from arabeyes.org
    // See http://www.unifont.org/fontguide/ for download site
    //
    doc.osBody() << setFont("Ae_Cortoba") << setFontSize(22) << endl;
    doc.osBody() << "0.6 0.5 0.1 setrgbcolor" << endl;
    LASI_SHOW_AND_UPDATE_BB("الإسكندرية");
    doc.osBody() << "grestore" << endl;

    /////////////////////////////////////////////////
    //
    // "UTHAITHANI" IN THAI: อุทัยธานี
    //
    /////////////////////////////////////////////////    
    doc.osBody() << "gsave" << endl;
    doc.osBody() << "2.5 cm 19 cm moveto" << endl;
    x = 2.5*LASI_POINTS_PER_CM;
    y = 19.*LASI_POINTS_PER_CM;
    //
    // Garuda is part of the Thai national font set from NECTEC.
    // See http://www.unifont.org/fontguide/ for download site
    //    
    doc.osBody() << setFont("Garuda") << endl;
    doc.osBody() << setFontSize(25) << endl;
    doc.osBody() << "0.55 0.32 0.21 setrgbcolor" << endl;
    LASI_SHOW_AND_UPDATE_BB("อุทัยธานี");
    doc.osBody() << "grestore" << endl;

    //
    // Move the vertical position down to 17.5 cm:
    //
    verticalPosition = 17.5;
    doc.osBody() << "/verticalPosition " << verticalPosition << " cm def" << endl;
    doc.osBody() << setFont("Vera Sans",LASi::ITALIC) << setFontSize(12);
    doc.osBody() << "leftMargin verticalPosition moveto" << endl;
    x = leftMargin*LASI_POINTS_PER_CM;
    y = verticalPosition*LASI_POINTS_PER_CM;
    LASI_SHOW_AND_UPDATE_BB("LASi is based on Unicode, so you can produce documents containing virtually any");
    doc.osBody() << "newLine" << endl;
    x = leftMargin*LASI_POINTS_PER_CM;
    y-=verticalIncrement;
    LASI_SHOW_AND_UPDATE_BB("of the world's scripts while still using familiar Postscript operators to manipulate");
    doc.osBody() << "newLine" << endl;
    x = leftMargin*LASI_POINTS_PER_CM;
    y-=verticalIncrement;
    LASI_SHOW_AND_UPDATE_BB("text or graphic elements:");

    /////////////////////////////////////////////////
    //
    // "NAGANO PREFECTURE" IN JAPANESE: 長野県
    //
    // Here we demonstrate using the Postscript "rotate" command
    // to achieve a nice graphic layout effect and show how easy
    // it is to mix LASi's show() method with Postscript:
    //
    /////////////////////////////////////////////////
    doc.osBody() << "gsave" << endl;
    doc.osBody() << "4.0 cm 14.0 cm moveto" << endl;
    x = 4.0*LASI_POINTS_PER_CM;
    y = 14.0*LASI_POINTS_PER_CM;
    //
    // See http://www.unifont.org/fontguide/ for
    // the Bitstream Cyberbit ftp download site:
    //
    doc.osBody() << setFont("Bitstream Cyberbit") << setFontSize(22) << endl;
    doc.osBody() << "0.4 0.4 0.75 setrgbcolor" << endl;
    overall_theta = 0.;
    LASI_ROTATED_SHOW_AND_UPDATE_BB(45., "長");
    LASI_ROTATED_SHOW_AND_UPDATE_BB(-15, "野");
    LASI_ROTATED_SHOW_AND_UPDATE_BB(-15, "県");
    doc.osBody() << "grestore" << endl;

    /////////////////////////////////////////////////
    //
    // "SAINT PETERSBURG" in Cyrillic: Санкт-Петербург
    //
    // Notice the anamorphic scaling just to demonstrate another
    // simple effect in Postscript:
    //
    /////////////////////////////////////////////////
    doc.osBody() << "gsave" << endl;
    doc.osBody() << setFont("Bitstream Cyberbit");
    doc.osBody() << "7.0 cm 14.2 cm moveto" << endl;
    x = 7.0*LASI_POINTS_PER_CM;
    y = 14.2*LASI_POINTS_PER_CM;
    doc.osBody() << "0.6 0.74 0.2 setrgbcolor" << endl;
    overall_xscale = 1.0;
    overall_yscale = 1.0;
    LASI_SCALED_SHOW_AND_UPDATE_BB(0.65, 1.75, "Санкт-Петербург");
    doc.osBody() << "grestore" << endl;
    
    /////////////////////////////////////////////////
    //
    // Bengali: First line from UN declaration des droits des hommes (en Bengali):
    //
    /////////////////////////////////////////////////
    doc.osBody() << "gsave" << endl;
    //
    // The Akaash Bengali font is 
    // available from http://savannah.nongnu.org/download/freebangfont/
    // See http://www.unifont.org/fontguide/ for
    // other Bengali font options:
    //
    doc.osBody() << setFont("Akaash");
    doc.osBody() << setFontSize(16);
    doc.osBody() << "6.0 cm 15.7 cm moveto" << endl;
    x = 6.0*LASI_POINTS_PER_CM;
    y = 15.7*LASI_POINTS_PER_CM;
    doc.osBody() << "0.6 0.44 0.42 setrgbcolor" << endl;
    LASI_SHOW_AND_UPDATE_BB("সমস্ত মানুস স্বাধীনভাবে সমান মর্যাদা এবং অধিকার নিয়ে জন্মগ্রহন করে । ");
    doc.osBody() << "grestore" << endl;

    //
    // Move the vertical position down to 13.0 cm:
    //
    verticalPosition = 13.0;
    doc.osBody() << "/verticalPosition " << verticalPosition << " cm def" << endl;
    doc.osBody() << setFont("Vera Sans",LASi::ITALIC) << setFontSize(12);
    doc.osBody() << "leftMargin verticalPosition moveto" << endl;
    x = leftMargin*LASI_POINTS_PER_CM;
    y = verticalPosition*LASI_POINTS_PER_CM;
    LASI_SHOW_AND_UPDATE_BB("Equally important, the complete repetoire of scientific and mathematical symbols");
    doc.osBody() << "newLine" << endl;
    x = leftMargin*LASI_POINTS_PER_CM;
    y-=verticalIncrement;
    LASI_SHOW_AND_UPDATE_BB("in Unicode are also available to you:");

    /////////////////////////////////////////////////
    //
    // SOMETHING FROM MAXIMUM LIKELIHOOD STATISTICS: 
    //
    // To demonstrate a layout with scientific symbols:
    //
    /////////////////////////////////////////////////
    doc.osBody() << "gsave" << endl;
    doc.osBody() << "2.5 cm 10.9 cm moveto" << endl;
    x = 2.5*LASI_POINTS_PER_CM;
    y = 10.9*LASI_POINTS_PER_CM;
    doc.osBody() << setFontSize(22) << endl;
    doc.osBody() << "0.5 setgray" << endl;
    doc.osBody() << setFont("Bitstream Cyberbit") << endl;
    LASI_SHOW_AND_UPDATE_BB("∴ ∂SS(δ) ∕ ∂δ ≡ 2Z′WX + 2(Z′WZδ)");
    doc.osBody() << "grestore" << endl;

    doc.osBody() << "showpage" << endl;

    /////////////////////////////////////////////////
    //
    // DOCUMENT FOOTER:
    //
    // Additional Postscript Document Structuring 
    // Conventions may go here:
    //
    /////////////////////////////////////////////////    
    doc.osFooter() << "%%Pages: 1" << endl;
    //cerr << "doc.write(cout);\n";
    
    //
    // Write it all out:
    // Is this easy or what?
    //
    //
    // If you need to write out an Encapsulated Postscript
    // (EPS) document, just include the BoundingBox parameters
    // llx, lly, urx, ury as additional parameters to the write()
    // method:
    //
    // doc.write(cout, llx, lly, urx, ury);
    // 
    if (argc == 1) {
      doc.write(cout, llx, lly, urx, ury);
    }
    else {
      strm.open(argv[1]);
      doc.write(strm, llx, lly, urx, ury);
      strm.close();
    }
    
  } catch (runtime_error& e) {
    cerr << e.what() << endl;
    return 1;
  }

  return 0;
}
