
/******************************************************************************
* MODULE     : cg_renderer.cpp
* DESCRIPTION: CoreGraphics drawing interface class
* COPYRIGHT  : (C) 2008 Massimiliano Gubinelli
*******************************************************************************
* This software falls under the GNU general public license and comes WITHOUT
* ANY WARRANTY WHATSOEVER. See the file $TEXMACS_PATH/LICENSE for more details.
* If you don't have this file, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
******************************************************************************/

#include "cg_renderer.hpp"
#include "analyze.hpp"
#include "image_files.hpp"
#include "file.hpp"
#include "iterator.hpp"
#include "gui.hpp" // for INTERRUPT_EVENT, INTERRUPTED_EVENT
#include "font.hpp" // for the definition of font

#include "Freetype/tt_file.hpp" // tt_font_find

#include "mac_images.h"

/******************************************************************************
* CG images
******************************************************************************/

struct cg_image_rep: concrete_struct {
  CGImageRef img;
  SI xo,yo;
  int w,h;
  cg_image_rep (CGImageRef img2, SI xo2, SI yo2, int w2, int h2) :
    img (img2), xo (xo2), yo (yo2), w (w2), h (h2) { CGImageRetain(img); };
  ~cg_image_rep()  {  CGImageRelease(img); };
  friend class cg_image;
};

class cg_image {
CONCRETE_NULL(cg_image);
  cg_image (CGImageRef img2, SI xo2, SI yo2, int w2, int h2):
    rep (tm_new <cg_image_rep> (img2, xo2, yo2, w2, h2)) {}	
};

CONCRETE_NULL_CODE(cg_image);

/******************************************************************************
* Global support variables for all cg_renderers
******************************************************************************/

static hashmap<basic_character,cg_image> character_image;  // bitmaps of all characters
static hashmap<string,cg_image> images; 

/******************************************************************************
* cg_renderer
******************************************************************************/

void 
cg_set_color (CGContextRef cxt, color col) {
  int r, g, b, a;
  get_rgb_color (col, r, g, b, a);
  CGContextSetRGBFillColor(cxt, r/255.0, g/255.0, b/255.0, a/255.0);
  CGContextSetRGBStrokeColor(cxt, r/255.0, g/255.0, b/255.0, a/255.0);
}

cg_renderer_rep::cg_renderer_rep (int w2, int h2):
  basic_renderer_rep (true, w2, h2), context (NULL) {
}

cg_renderer_rep::~cg_renderer_rep () {
  if (context) end();
}

void 
cg_renderer_rep::begin (void * c) { 
  context = (CGContextRef)c; 
  CGContextRetain(context);
  CGContextBeginPage(context, NULL);
}

void 
cg_renderer_rep::end () { 
  CGContextEndPage(context);
  CGContextRelease(context); 
  context = NULL;  
}

void 
cg_renderer_rep::next_page () { 
  CGContextEndPage(context);
  CGContextBeginPage(context, NULL);
}

void
cg_renderer_rep::set_pencil (pencil p) {
  basic_renderer_rep::set_pencil (p);
  cg_set_color (context, pen->c);
  CGContextSetLineCap(context, pen->cap == cap_round?
                               kCGLineCapRound : kCGLineCapSquare);
  CGContextSetLineJoin(context, kCGLineJoinRound);
  CGContextSetLineWidth(context, pen->w <= pixel ?
                                 1 : ((lw+thicken) / (1.0*pixel)));
}

void
cg_renderer_rep::line (SI x1, SI y1, SI x2, SI y2) {
  decode (x1, y1);
  decode (x2, y2);
  // y1--; y2--; // top-left origin to bottom-left origin conversion
  CGContextSetShouldAntialias(context, true);
  CGPoint points[2]= { CGPointMake(x1,y1), CGPointMake(x2,y2) };
  CGContextStrokeLineSegments(context, points, 2);
}

void
cg_renderer_rep::lines (array<SI> x, array<SI> y) {
  int i, n= N(x);
  if ((N(y) != n) || (n<1)) return;
  STACK_NEW_ARRAY (pnt, CGPoint, n);
  CGContextSetShouldAntialias(context, true);
  for (i=0; i<n; i++) {
    SI xx= x[i], yy= y[i];
    decode (xx, yy);
    pnt[i] = CGPointMake(xx,yy);
    if (i>0) {
      CGContextStrokeLineSegments(context, pnt + (i - 1), 2); // FIX: hack
    }
  }
  STACK_DELETE_ARRAY (pnt);
}

void
cg_renderer_rep::clear (SI x1, SI y1, SI x2, SI y2) {
  x1= max (x1, cx1-ox); y1= max (y1, cy1-oy);
  x2= min (x2, cx2-ox); y2= min (y2, cy2-oy);
  // outer_round (x1, y1, x2, y2); might still be needed somewhere
  decode (x1, y1);
  decode (x2, y2);
  if ((x1>=x2) || (y1<=y2)) return;
  cg_set_color (context, bg_brush->get_color ());
  CGContextSetShouldAntialias(context, false);
  CGContextFillRect(context, CGRectMake(x1, y2, x2-x1, y1-y2) );
  cg_set_color (context, pen->get_color ());
}

void
cg_renderer_rep::fill (SI x1, SI y1, SI x2, SI y2) {
  if ((x2>x1) && ((x2-x1)<pixel)) {
    SI d= pixel-(x2-x1);
    x1 -= (d>>1);
    x2 += ((d+1)>>1);
  }
  if ((y2>y1) && ((y2-y1)<pixel)) {
    SI d= pixel-(y2-y1);
    y1 -= (d>>1);
    y2 += ((d+1)>>1);
  }
  
  x1= max (x1, cx1-ox); y1= max (y1, cy1-oy);
  x2= min (x2, cx2-ox); y2= min (y2, cy2-oy);
  // outer_round (x1, y1, x2, y2); might still be needed somewhere
  if ((x1>=x2) || (y1>=y2)) return;
  
  decode (x1, y1);
  decode (x2, y2);

  // cg_set_color (context, pen->get_color ());
  CGContextSetShouldAntialias (context, false);
  CGContextFillRect (context, CGRectMake(x1, y2, x2-x1, y1-y2) );
}

void
cg_renderer_rep::arc (SI x1, SI y1, SI x2, SI y2, int alpha, int delta) {
  (void) alpha; (void) delta;
  if ((x1>=x2) || (y1>=y2)) return;
  decode (x1, y1);
  decode (x2, y2);
  //FIXME: XDrawArc (dpy, win, gc, x1, y2, x2-x1, y1-y2, alpha, delta);
}

void
cg_renderer_rep::fill_arc (SI x1, SI y1, SI x2, SI y2, int alpha, int delta) {
  (void) alpha; (void) delta;
  if ((x1>=x2) || (y1>=y2)) return;
  decode (x1, y1);
  decode (x2, y2);
  //FIXME: XFillArc (dpy, win, gc, x1, y2, x2-x1, y1-y2, alpha, delta);
}

void
cg_renderer_rep::polygon (array<SI> x, array<SI> y, bool convex) {  
  int i, n= N(x);
  if ((N(y) != n) || (n<1)) return;
	
  CGContextBeginPath(context);
  for (i=0; i<n; i++) {
    SI xx= x[i], yy= y[i];
    decode (xx, yy);
    if (i==0) CGContextMoveToPoint (context, xx, yy);
    else  CGContextAddLineToPoint(context, xx ,yy);
  }
  CGContextClosePath (context);
  // cg_set_color (context, pen->get_color ());
  CGContextSetShouldAntialias (context, true);
  if (convex)    CGContextEOFillPath (context);	
  else CGContextFillPath (context);	
}

/******************************************************************************
* Image rendering
******************************************************************************/

void
cg_renderer_rep::image (url u, SI w, SI h, SI x, SI y, int alpha) {
  // Given an image of original size (W, H),
  // we display it at position (x, y) in a rectangle of size (w, h)

  // if (DEBUG_EVENTS) debug_events << "cg_renderer_rep::image " << as_string(u) << LF;
  (void) alpha;

  w= w/pixel; h= h/pixel;
  decode (x, y);
  
  //painter.setRenderHints (0);
  //painter.drawRect (QRect (x, y-h, w, h));
  
  CGImageRef pm = NULL;
  if (suffix (u) == "png") {
    // rendering
    string suu = as_string (u);
    c_string buf (suu);
    // cout << suu << LF;
    CFURLRef uu =  CFURLCreateFromFileSystemRepresentation(NULL, (UInt8*)buf, N(suu),  false);
    CGImageSourceRef source =  CGImageSourceCreateWithURL ( uu, NULL );
    pm =  CGImageSourceCreateImageAtIndex(source, 0, NULL);
    CFRelease(source);
    CFRelease(uu);
  }
  else if (suffix (u) == "ps" ||
           suffix (u) == "eps" ||
           suffix (u) == "pdf") {
    url temp= url_temp (".png");
    // system ("convert", u, temp);
    mac_image_to_png (u, temp); 
    string suu = as_string (temp);
    c_string buf (suu);
    //cout << suu << LF;
    CFURLRef uu =  CFURLCreateFromFileSystemRepresentation(NULL, (UInt8*)buf, N(suu),  false);
    CGImageSourceRef source =  CGImageSourceCreateWithURL ( uu, NULL );
    pm =  CGImageSourceCreateImageAtIndex(source, 0, NULL);
    CFRelease(source);
    CFRelease(uu);
    remove (temp);
  }

  if (pm == NULL ) {
    cout << "TeXmacs] warning: cannot render " << as_string (u) << "\n";
    return;
  }
  
  CGContextSetShouldAntialias(context, false);
  CGContextSaveGState(context);
  CGContextTranslateCTM(context, x,y);
  CGContextScaleCTM(context,1.0,-1.0);
  CGContextDrawImage(context, CGRectMake(0, 0, w, h), pm); 
  CGContextRestoreGState(context);
}

void
cg_renderer_rep::draw_clipped (CGImageRef im, int w, int h, SI x, SI y) {
  decode (x , y );
  y--; // top-left origin to bottom-left origin conversion
       // clear(x1,y1,x2,y2);
  CGContextSetShouldAntialias(context, true);
  // CGContextSetBlendMode(context,kCGBlendModeSourceAtop);
  CGContextDrawImage(context, CGRectMake(x,y,w,h), im); 
}  




static hashmap<string,pointer> native_fonts;
static hashset<string> native_loaded;

int 
posixStringToFSSpec(FSSpec *fss, CFStringRef posixPath, bool isDirectory)  {
  FSRef fsRef;
  FSSpec fileSpec;
  // create a URL from the posix path:
  CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault,posixPath,kCFURLPOSIXPathStyle,isDirectory);
  // check to be sure the URL was created properly:
  if (url == 0) {
    //fprintf(stderr,"Can't get URL");
    return(1);
  }
  // use the CF function to extract an FSRef from the URL:
  if (CFURLGetFSRef(url, &fsRef) == 0){
    //fprintf(stderr,"Can't get FSRef.\n");
    CFRelease(url);
    return(1);
  }
  // use Carbon call to get the FSSpec from the FSRef
  if (FSGetCatalogInfo (&fsRef, kFSCatInfoNone, 0, 0, &fileSpec, 0) != noErr) {		
    //fprintf(stderr,"Can't get FSSpec.\n");
    CFRelease(url);
    return(1);
  }
  // We have a valid FSSpec! Clean up and return it:
  CFRelease(url);
  *fss = fileSpec;
  return 0;
}

bool 
cg_renderer_rep::native_draw (int ch, font_glyphs fn, SI x, SI y) {
  string name= fn->res_name;
  unsigned char c= ch;
  if (ch >= 256) {
    name= name * "-" * as_string (ch / 256);
    c= (unsigned char) (ch & 255);
  }
  
  //	cout << name << LF;
  int size;
  {
    // find size (weird)
    int pos1= search_forwards (".", name);
    int pos2= search_backwards (":", name);
    string sz = name(pos2+1,pos1);
    size = as_int(sz);
  }
  CGFontRef f = (CGFontRef)native_fonts(name);
	
  if ((f == NULL)&&(! native_loaded->contains(name))) {
    native_loaded->insert(name);
    string ttf;
    int    pos  = search_forwards (".", name);
    string root = (pos==-1? name: name (0, pos));
    if ((pos!=-1) && ends (name, "tt")) {
      int pos2= search_backwards (":", name);
      root= name (0, pos2);
      url u= tt_font_find (root);
      if (suffix (u) == "pfb") {
        // cout << u << LF;
        url v= url_temp (".otf");
        string vs = concretize(v);
        system ("/Users/mgubi/t/t1wrap/T1Wrap " * concretize(u) * " > " * vs);
        FSSpec fss;
        ATSFontRef atsFont;
        ATSFontContainerRef container;
        c_string p (vs);
        CFStringRef font_filename = CFStringCreateWithCString(NULL,p,kCFStringEncodingASCII);
					
        if (posixStringToFSSpec(&fss,font_filename,false)) {
          cout << "Cannot load font" << vs << LF;
        } else {
          int status =  ATSFontActivateFromFileSpecification(&fss,kATSFontContextLocal,kATSFontFormatUnspecified,NULL,NULL,&container);
          cout << "Font " << vs << " loaded" << LF;
          ItemCount count;
          status = ATSFontFindFromContainer(container, 0, 1, &atsFont, &count);
          
          f = CGFontCreateWithPlatformFont((void*)&atsFont);
          native_fonts(name) = f;
        }
        CFRelease(font_filename);
        remove (v);
      }
    }
  } // end caching
  
  if (f) {
    decode (x , y );
    y--; // top-left origin to bottom-left origin conversion
    CGContextRef cgc = context;
    CGContextSetFont(cgc,f);
    CGContextSetFontSize(cgc,size);
    CGAffineTransform	kHorizontalMatrix = { PIXEL*600.0/(pixel*72.0),  0.0,  0.0,  -PIXEL*600.0/(pixel*72.0),  0.0,  0.0 };
    CGContextSetTextMatrix(cgc, kHorizontalMatrix);
    CGContextSetTextDrawingMode(cgc,  kCGTextFill);
    CGContextSetShouldAntialias(cgc,true);
    CGContextSetShouldSmoothFonts(cgc,true);
    // CGContextSetBlendMode(context,kCGBlendModeSourceAtop);
    // cg_set_color (context, pen->get_color ());
    CGGlyph buf[1] = {c};
    CGContextShowGlyphsAtPoint(cgc,x,y,(CGGlyph*)buf,1);
  } 
  return true;
}


CGContextRef 
MyCreateBitmapContext (int pixelsWide, int pixelsHigh) {
  int bitmapBytesPerRow   = (pixelsWide * 4);
  int bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);	
  CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
  void *bitmapData = malloc( bitmapByteCount );
  if (bitmapData == NULL) {
    //fprintf (stderr, "Memory not allocated!");
    return NULL;
  }
  CGContextRef context = CGBitmapContextCreate (bitmapData, pixelsWide,	pixelsHigh,	8,
                                                bitmapBytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast);
  if (context == NULL) {
    free (bitmapData);
    // fprintf (stderr, "Context not created!");
    return NULL;
  }
  CGColorSpaceRelease (colorSpace);
  return context;
}

void
cg_renderer_rep::draw (int c, font_glyphs fng, SI x, SI y) {
  // get the pixmap
  basic_character xc (c, fng, std_shrinkf, 0, 0);
  cg_image mi = character_image [xc];
  if (is_nil(mi)) {
    SI xo, yo;
    glyph pre_gl= fng->get (c); if (is_nil (pre_gl)) return;
    glyph gl= shrink (pre_gl, std_shrinkf, std_shrinkf, xo, yo);
    int i, j, w= gl->width, h= gl->height;
    CGImageRef im = NULL;
    {
      CGContextRef ic = MyCreateBitmapContext(w,h);
      int nr_cols= std_shrinkf*std_shrinkf;
      if (nr_cols >= 64) nr_cols= 64;
      //CGContextSetShouldAntialias(ic,true);
      CGContextSetBlendMode(ic,kCGBlendModeCopy);
      //CGContextSetRGBFillColor(ic,1.0,1.0,1.0,0.0);
      //CGContextFillRect(ic,CGRectMake(0,0,w,h));
      
      for (j=0; j<h; j++)
        for (i=0; i<w; i++) {
          int col = gl->get_x (i, j);
          CGContextSetRGBFillColor(ic, 0.0,0.0,0.0,  ((255*col)/(nr_cols+1))/255.0);
          CGContextFillRect(ic,CGRectMake(i,j,1,1));
        }
      im = CGBitmapContextCreateImage (ic);
      CGContextRelease (ic);
    }
    cg_image mi2 (im, xo, yo, w, h);
    mi = mi2;
    CGImageRelease(im); // cg_image retains im
    character_image (xc)= mi;
  }
  
  // draw the character
  {
    (void) w; (void) h;
    int x1= x- mi->xo*std_shrinkf;
    int y1=  y+ mi->yo*std_shrinkf;
    decode (x1, y1);
    y1--; // top-left origin to bottom-left origin conversion
    CGRect r = CGRectMake(x1,y1,mi->w,mi->h);
    CGContextSetShouldAntialias (context, true);
    CGContextSaveGState (context);
    //  cg_set_color (context, pen->get_color ());
    CGContextClipToMask (context, r, mi->img); 
    CGContextFillRect (context, r);
    CGContextRestoreGState (context);
  }  
}

/******************************************************************************
* Setting up and displaying xpm pixmaps
******************************************************************************/

static CGImageRef
xpm_init (url file_name) {
  tree t= xpm_load (file_name);
  
  // get main info
  int ok, i=0, j, k, w, h, c, b, x, y;
  string s= as_string (t[0]);
  skip_spaces (s, i);
  ok= read_int (s, i, w);
  skip_spaces (s, i);
  ok= read_int (s, i, h) && ok;
  skip_spaces (s, i);
  ok= read_int (s, i, c) && ok;
  skip_spaces (s, i);
  ok= read_int (s, i, b) && ok;
  if ((!ok) || (N(t)<(c+1)) || (c<=0)) {
    failed_error << "File name= " << file_name << "\n";
    FAILED ("invalid xpm");
  }
	
  // setup colors
  string first_name;
  hashmap<string,color> pmcs;
  for (k=0; k<c; k++) {
    string s   = as_string (t[k+1]);
    string name= "";
    string def = "none";
    if (N(s)<b) i=N(s);
    else { name= s(0,b); i=b; }
    if (k==0) first_name= name;
    
    skip_spaces (s, i);
    if ((i<N(s)) && (s[i]=='s')) {
      i++;
      skip_spaces (s, i);
      while ((i<N(s)) && (s[i]!=' ') && (s[i]!='\t')) i++;
      skip_spaces (s, i);
    }
    if ((i<N(s)) && (s[i]=='c')) {
      i++;
      skip_spaces (s, i);
      j=i;
      while ((i<N(s)) && (s[i]!=' ') && (s[i]!='\t')) i++;
      def= locase_all (s (j, i));
    }
    
    pmcs(name)= xpm_color(def);
  }
  CGContextRef ic = MyCreateBitmapContext(w,h);
  CGContextSetBlendMode(ic,kCGBlendModeCopy);
  // setup pixmap
  for (y=0; y<h; y++) {
    if (N(t)< (y+c+1)) s= "";
    else s= as_string (t[y+c+1]);
    for (x=0; x<w; x++) {
      string name;
      if (N(s)<(b*(x+1))) name= first_name;
      else name= s (b*x, b*(x+1));
      color col = pmcs[(pmcs->contains (name) ? name : first_name)];
      cg_set_color (ic, col);
      CGContextFillRect (ic,CGRectMake(x,y,1,1));
    }
  }
  CGImageRef im = CGBitmapContextCreateImage (ic);
  CGContextRelease (ic);
  return im;
}

CGImageRef 
cg_renderer_rep::xpm_image (url file_name) { 
  CGImageRef pxm= NULL;
  cg_image mi= images [as_string (file_name)];
  if (is_nil (mi)) {    
    pxm = xpm_init(file_name);
    cg_image mi2 (pxm, 0, 0, CGImageGetWidth (pxm), CGImageGetHeight (pxm));
    mi= mi2;
    images (as_string (file_name))= mi2;
    CGImageRelease(pxm);
  }
  else pxm= mi->img;
  return pxm;
}

/******************************************************************************
 * main coregraphics renderer
 ******************************************************************************/

static cg_renderer_rep* the_renderer= NULL;

cg_renderer_rep*
the_cg_renderer () {
  if (!the_renderer) the_renderer= tm_new <cg_renderer_rep> ();
  return the_renderer;
}
