//==================================================================================================
// T e x 2 p n g                                                                     Implementation
//                                                                                By Bruno Bachelet
//==================================================================================================
// Copyright (c) 1999-2016
// Bruno Bachelet - bruno@nawouak.net - http://www.nawouak.net
//
// This program is free software; you can redistribute it and/or modify it under the terms of the
// GNU General Public License as published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
// the GNU General Public License for more details (http://www.gnu.org).

// This programs allows to convert a LaTeX formula into a PNG image.

// You will need LaTeX, GhostScript and Linux facilities to use this program. The optional
// graphical display requires Java classes from the B++ Library.

// Headers //---------------------------------------------------------------------------------------
#include <algorithm>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <string>

// Types //-----------------------------------------------------------------------------------------
typedef std::ifstream InputFile;
typedef std::ofstream OutputFile;
typedef std::string   String;

typedef bool          boolean_t;
typedef unsigned char byte_t;
typedef unsigned int  cardinal_t;
typedef char          character_t;
typedef const char *  cstring_t;
typedef long          integer_t;
typedef double        real_t;
typedef char *        string_t;

// Portability //-----------------------------------------------------------------------------------
// #define or  ||
// #define and &&
// #define not !

// Global Variables //------------------------------------------------------------------------------
String    bpp_java       = "";
boolean_t display        = false;
boolean_t lowQuality     = false;
boolean_t percentReplace = false;
boolean_t quiet          = true;
real_t    scale          = 20;
boolean_t secureProgram  = false;
boolean_t starReplace    = false;
String    temporary      = "tex2png";

String auxFile;
String dviFile;
String epsFile;
String logFile;
String pngFile;
String pnmFile;
String psFile;
String outFile;
String texFile;

// Functions Implementation //----------------------------------------------------------------------

//------------------------------------------------------------------------------------------CutImage
void cutImage(cstring_t _inputFile,cstring_t _outputFile) {
 character_t  character;
 InputFile    fin;
 OutputFile   fout;
 boolean_t ** image;
 String       word;

 cardinal_t n1;
 cardinal_t x1;
 cardinal_t y1;

 cardinal_t height;
 cardinal_t left;
 cardinal_t top;
 cardinal_t width;

 cardinal_t border = 4;
 cardinal_t bottom = 0;
 cardinal_t right  = 0;
 cardinal_t x2     = 0;
 cardinal_t y2     = 0;

 // Image Matrix Reading //
 fin.open(_inputFile,std::ios::in|std::ios::binary);
 fin >> word;

 while (not fin.eof() and word!="(device=pnm)") fin >> word;
 if (fin.eof()) return;

 fin >> x1 >> y1;
 image=new boolean_t *[y1];
 image[0]=new boolean_t[x1];

 left=x1-1;
 top=y1-1;
 n1=x1*y1;

 while (n1>0) {
  --n1;

  // Pixel Reading //
  do fin.read(&character,1);
  while ((byte_t)character<=32);

  image[y2][x2]=(character=='1');

  // Bounding Box Computation //
  if (image[y2][x2]) {
   left=std::min(x2,left);
   right=std::max(x2,right);
   top=std::min(y2,top);
   bottom=std::max(y2,bottom);
  }

  // Next Line //
  if (++x2 == x1) {
   x2=0;
   if (++y2 < y1) image[y2]=new boolean_t[x1];
  }
 }

 fin.close();

 // PNM Image Writing //
 fout.open(_outputFile);
 fout << "P1" << std::endl;
 fout << "# Image generated by TeX2PNG" << std::endl;

 height=(bottom-top+1)+2*border;
 width=(right-left+1)+2*border;
 fout << width << " " << height << std::endl;

 // Top Border //
 y2=0;

 while (y2<border) {
  x2=0;
  while (x2++<width) fout << '0';
  fout << std::endl;
  ++y2;
 }

 // Bounded Image //
 y2=0;

 while (y2<y1) {
  if (y2>=top and y2<=bottom) {
   // Left Border //
   x2=0;
   while (x2++ < border) fout << '0';

   // Line //
   x2=left;
   while (x2<=right) fout << (image[y2][x2++] ? '1' : '0');

   // Right Border //
   x2=0;
   while (x2++ < border) fout << '0';

   // End Of Line //
   fout << std::endl;
  }

  delete [] image[y2++];
 }

 delete [] image;

 // Bottom Border //
 y2=0;

 while (y2<border) {
  x2=0;
  while (x2++<width) fout << '0';
  fout << std::endl;
  ++y2;
 }

 // File Closing //
 fout << std::endl;
 fout.close();
}

//------------------------------------------------------------------------------GenerateFormulaImage
void generateFormulaImage(cstring_t _outputFile,cstring_t _formula) {
 String      command;
 InputFile   fin;
 OutputFile  fout;
 cardinal_t  position;
 character_t resolution[32];
 character_t size[32];
 String      word;

 integer_t x1;
 integer_t x2;
 integer_t y1;
 integer_t y2;

 String formula;
 String outputFile;

 // File Name Normalizing //
 while (*_outputFile) {
  if (*_outputFile=='\\') outputFile+='/';
  else outputFile+=*_outputFile;

  ++_outputFile;
 }

 // Formula Construction //
 while (*_formula != 0) {
  if (starReplace and *_formula=='*') formula+="{\\times}";
  else if (secureProgram and *_formula=='"') formula+="";
  else formula+=*_formula;

  ++_formula;
 }

 if (percentReplace) {
  character_t ascii[] = { 0,0 };
  character_t hexa[]  = { '%','0','0',0 };

  while ((byte_t)ascii[0]<255) {
   ++(ascii[0]);

   if (hexa[2]=='9') hexa[2]='A';
   else if (hexa[2]=='F') {
    if (hexa[1]=='9') hexa[1]='A';
    else ++hexa[1];

    hexa[2]='0';
   }
   else ++hexa[2];

   while ((position=formula.find(hexa)) != String::npos) formula.replace(position,3,ascii);
  }
 }

 // LaTeX Generation //
 std::cout << "[>] LaTeX File Generation..." << std::endl;
 fout.open(texFile.c_str(),std::ios::in|std::ios::trunc);

 fout << "\\documentclass[a4paper,11pt]{article}" << std::endl
      << "\\usepackage{amssymb}" << std::endl
      << "\\usepackage{amsmath}" << std::endl
      << "\\usepackage{epsfig}" << std::endl
      << "\\pagestyle{empty}" << std::endl
      << "\\textwidth 15cm" << std::endl
      << "\\setlength{\\parindent}{0mm}" << std::endl
      << "\\begin{document}" << std::endl
      << "$\\displaystyle " << formula << "$" << std::endl
      << "\\end{document}" << std::endl;

 fout.close();

 if (fout.fail()) {
  std::cerr << "[!] Can't create the LaTeX file." << std::endl;
  exit(1);
 }

 // LaTeX Compilation //
 std::cout << "[>] LaTeX File Compilation..." << std::endl;
 command="latex -interaction=nonstopmode "+texFile;
 if (quiet) command+=" > "+outFile+" 2> "+outFile;
 system(command.c_str());
 command="touch "+dviFile;
 system(command.c_str());
 remove(texFile.c_str());
 remove(auxFile.c_str());
 remove(logFile.c_str());

 // EPS Generation //
 std::cout << "[>] PostScript File Generation..." << std::endl;
 command="dvips -q -D 600 -E -n 1 -p 1 -o "+epsFile+" "+dviFile;
 if (quiet) command+=" > "+outFile+" 2> "+outFile;
 system(command.c_str());
 command="touch "+epsFile;
 system(command.c_str());
 remove(dviFile.c_str());

 // Bounding Box Computation //
 fin.open(epsFile.c_str(),std::ios::in);

 do fin >> word;
 while (not fin.eof() and word!="%%BoundingBox:");

 if (word=="%%BoundingBox:") fin >> x1 >> y1 >> x2 >> y2;
 fin.close();

 if (fin.fail()) {
  std::cerr << "[!] Can't find the bounding box." << std::endl;
  exit(1);
 }

 if (secureProgram) {
  if ((x2-x1)*scale*0.25 > 1600 or (y2-y1)*scale*0.25 > 1200) {
   std::cerr << "[!] Sorry, image too big." << std::endl;
   exit(1);
  }
 }

 // PS Generation //
 fout.clear();
 fout.open(psFile.c_str(),std::ios::in|std::ios::trunc);

 fout << "1 1 1 setrgbcolor" << std::endl
      << "newpath" << std::endl
      << "-1 -1 moveto" << std::endl
      << (x2-x1+2) << " -1 lineto" << std::endl
      << (x2-x1+2) << " " << (y2-y1+2) << " lineto" << std::endl
      << "-1 " << (y2-y1+2) << " lineto" << std::endl
      << "closepath" << std::endl
      << "fill" << std::endl
      << -x1 << " " << -y1 << " translate" << std::endl
      << "0 0 0 setrgbcolor" << std::endl
      << "("+epsFile+") run" << std::endl;

 fout.close();

 if (fout.fail()) {
  std::cerr << "[!] Can't generate the PS file." << std::endl;
  exit(1);
 }

 // PNG Generation //
 std::cout << "[>] PNG Image Generation..." << std::endl;

 if (lowQuality) {
  scale*=0.25;
  sprintf(size,"%dx%d",(int)((x2-x1)*scale),(int)((y2-y1)*scale));
  sprintf(resolution,"%dx%d",(int)(scale*72),(int)(scale*72));
  command=String("gs -q -g")+size+" -r"+resolution;
  command+=" -sDEVICE=pnggray -sOutputFile=";
  command+=outputFile;
  command+=" -dNOPAUSE -dBATCH -- "+psFile+";";

  if (quiet) command+=" > "+outFile+" 2> "+outFile;
  system(command.c_str());

  remove(epsFile.c_str());
  remove(psFile.c_str());
 }
 else {
  sprintf(size,"%dx%d",(int)((x2-x1)*scale),(int)((y2-y1)*scale));
  sprintf(resolution,"%dx%d",(int)(scale*72),(int)(scale*72));
  command=String("gs -q -g")+size+" -r"+resolution;
  command+=" -sDEVICE=pnm -sOutputFile="+pnmFile+" -dNOPAUSE -dBATCH -- "+psFile+";";

  if (quiet) command+=" > "+outFile+" 2> "+outFile;
  system(command.c_str());

  remove(epsFile.c_str());
  remove(psFile.c_str());

  cutImage(pnmFile.c_str(),pnmFile.c_str());

  command="pnmscale 0.25 "+pnmFile+" 2> "+outFile+" | pnmtopng > ";
  command+=outputFile;
  if (quiet) command+=" 2> "+outFile;
  system(command.c_str());
  remove(pnmFile.c_str());
 }

 remove(outFile.c_str());

 // Image Display //
 if (display) {
  if (bpp_java=="") {
   if (std::getenv("BPP_JAVA")) bpp_java=String(std::getenv("BPP_JAVA"));
   else bpp_java=".";
  }

  std::cout << "[>] Image Display..." << std::endl;
  command="java -cp \""+bpp_java+"\" bpp.graphic.PictureFrame ";
  command+=outputFile;
  system(command.c_str());
  remove(pngFile.c_str());
 }
}

//----------------------------------------------------------------------------------------------Main
int main(int argc,const char * argv[]) {
 String formula;
 String filename;

 int i = 1;

 while (i<argc) {
  if (argv[i][0]=='-') {
   switch (argv[i][1]) {
    case 'b': bpp_java=(argv[i]+2); break;
    case 'r':
     if (argv[i][2]=='*') starReplace=true;
     if (argv[i][2]=='%') percentReplace=true;
    break;
    case 's': scale=atof(argv[i]+2); break;
    case 't': temporary=(argv[i]+2); break;
    case 'v': quiet=false; break;
    case 'd': display=true; break;
    case 'l': if (argv[i][2]=='q') lowQuality=true; break;
    default: std::cout << "[!] Unknown option '" << argv[i] << "'" << std::endl;
   }
  }
  else {
   if (formula=="") formula=argv[i];
   else filename=argv[i];
  }

  ++i;
 }

 if (secureProgram) {
  display=false;
  if (scale>20) scale=20;
 }

 texFile=temporary+".tex";
 outFile=temporary+".out";
 dviFile=temporary+".dvi";
 auxFile=temporary+".aux";
 logFile=temporary+".log";
 epsFile=temporary+".eps";
 psFile=temporary+".ps";
 pnmFile=temporary+".pnm";
 pngFile=temporary+".png";

 if (display and filename=="") filename=pngFile;

 if (formula=="" or (not display and filename=="")) {
  std::cout << "[!] Syntax: " << argv[0] << " [options] <formula> [output file]" << std::endl
            << std::endl
            << "    Options:" << std::endl
            << "      -bx = sets the path of the B++ Library Java classes to x." << std::endl
            << "      -d  = displays the image, the output file is optional." << std::endl
            << "      -lq = low quality, faster." << std::endl
            << "      -r* = replaces the '*' symbol by the '\\times' command." << std::endl
            << "      -r% = replaces the '%XX' hexa sequences by the corresponding ASCII symbol." << std::endl
            << "      -sx = sets the scale of the image to x (real value)." << std::endl
            << "      -tx = sets the prefix of the temporary files." << std::endl
            << "      -v  = verbose mode." << std::endl;

  exit(1);
 }

 generateFormulaImage(filename.c_str(),formula.c_str());
 return 0;
}

// End //-------------------------------------------------------------------------------------------