//
// "$Id: Fl_Cocoa_Printer_Driver.mm 12690 2018-02-25 16:54:21Z manolo $"
//
// Mac OS X-specific printing support (objective-c++) for the Fast Light Tool Kit (FLTK).
//
// Copyright 2010-2018 by Bill Spitzak and others.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file.  If this
// file is missing or damaged, see the license at:
//
//     http://www.fltk.org/COPYING.php
//
// Please report all bugs and problems to:
//
//     http://www.fltk.org/str.php
//

#include <FL/Fl_Printer.H>
#include <FL/Fl_Shared_Image.H>
#include <FL/Fl_Window_Driver.H>
#include <FL/Fl_Screen_Driver.H>
#include "../Quartz/Fl_Quartz_Graphics_Driver.H"
#include "../Darwin/Fl_Darwin_System_Driver.H"
#include "Fl_Cocoa_Window_Driver.H"

#include <FL/Fl.H>
#include <FL/platform.H>
#include <FL/fl_ask.H>
#include <FL/fl_draw.H>
#import <Cocoa/Cocoa.h>

typedef OSStatus (*PMSessionSetDocumentFormatGeneration_type)(
                                     PMPrintSession   printSession,
                                     CFStringRef      docFormat,
                                     CFArrayRef       graphicsContextTypes,
                                     CFTypeRef        options);
typedef OSStatus (*PMSessionBeginDocumentNoDialog_type)(
                               PMPrintSession    printSession,
                               PMPrintSettings   printSettings,
                               PMPageFormat      pageFormat);
typedef OSStatus
(*PMSessionGetGraphicsContext_type)(
                            PMPrintSession   printSession,
                            CFStringRef      graphicsContextType,
                            void **          graphicsContext);


/** Support for printing on the Apple OS X platform */
class Fl_Cocoa_Printer_Driver : public Fl_Paged_Device {
  friend class Fl_Paged_Device;
private:
  float scale_x;
  float scale_y;
  float angle; // rotation angle in radians
  PMPrintSession  printSession;
  PMPageFormat    pageFormat;
  PMPrintSettings printSettings;
  Fl_Cocoa_Printer_Driver(void);
  int start_job(int pagecount, int *frompage = NULL, int *topage = NULL);
  int start_page (void);
  int printable_rect(int *w, int *h);
  void margins(int *left, int *top, int *right, int *bottom);
  void origin(int *x, int *y);
  void origin(int x, int y);
  void scale (float scale_x, float scale_y = 0.);
  void rotate(float angle);
  void translate(int x, int y);
  void untranslate(void);
  int end_page (void);
  void end_job (void);
  void draw_decorated_window(Fl_Window *win, int x_offset, int y_offset);
  void print_window_part(Fl_Window *win, int x, int y, int w, int h, int delta_x, int delta_y);
  ~Fl_Cocoa_Printer_Driver(void);
};


Fl_Cocoa_Printer_Driver::Fl_Cocoa_Printer_Driver(void)
{
  x_offset = 0;
  y_offset = 0;
  scale_x = scale_y = 1.;
  driver(new Fl_Quartz_Printer_Graphics_Driver);
}

Fl_Paged_Device* Fl_Paged_Device::newPrinterDriver(void)
{
  return new Fl_Cocoa_Printer_Driver();
}

Fl_Cocoa_Printer_Driver::~Fl_Cocoa_Printer_Driver(void) {
  delete driver();
}


int Fl_Cocoa_Printer_Driver::start_job (int pagecount, int *frompage, int *topage)
//printing using a Quartz graphics context
//returns 0 iff OK
{
  OSStatus status = 0;
  fl_open_display();
  Fl_Cocoa_Window_Driver::q_release_context();
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
  if (fl_mac_os_version >= 100500) {
    NSPrintInfo *info = [NSPrintInfo sharedPrintInfo];
    NSPrintPanel *panel = [NSPrintPanel printPanel];
    //from 10.5
    [panel setOptions:NSPrintPanelShowsCopies | NSPrintPanelShowsPageRange | NSPrintPanelShowsPageSetupAccessory];
    NSInteger retval = [panel runModalWithPrintInfo:info];//from 10.5
    if(retval != NSOKButton) {
      Fl_Window *w = Fl::first_window();
      if (w) w->show();
      return 1;
    }
    printSession = (PMPrintSession)[info PMPrintSession];//from 10.5
    pageFormat = (PMPageFormat)[info PMPageFormat];//from 10.5
    printSettings = (PMPrintSettings)[info PMPrintSettings];//from 10.5
    UInt32 from32, to32;
    PMGetFirstPage(printSettings, &from32); 
    if (frompage) *frompage = (int)from32;
    PMGetLastPage(printSettings, &to32); 
    if (topage) {
      *topage = (int)to32;
      if (*topage > pagecount && pagecount > 0) *topage = pagecount;
    }
    status = PMSessionBeginCGDocumentNoDialog(printSession, printSettings, pageFormat);//from 10.4
  }
  else
#endif
  {
#if !defined(__LP64__) || !__LP64__
    Boolean accepted;
    status = PMCreateSession(&printSession);
    if (status != noErr) return 1;
    status = PMCreatePageFormat(&pageFormat);
    status = PMSessionDefaultPageFormat(printSession, pageFormat);
    if (status != noErr) return 1;
    // get pointer to the PMSessionPageSetupDialog Carbon function
    typedef OSStatus (*dialog_f)(PMPrintSession, PMPageFormat, Boolean *);
    static dialog_f f = NULL;
    if (!f) f = (dialog_f)Fl_Darwin_System_Driver::get_carbon_function("PMSessionPageSetupDialog");
    status = (*f)(printSession, pageFormat, &accepted);
    if (status != noErr || !accepted) {
      Fl::first_window()->show();
      return 1;
    }
    status = PMCreatePrintSettings(&printSettings);
    if (status != noErr || printSettings == kPMNoPrintSettings) return 1;
    status = PMSessionDefaultPrintSettings (printSession, printSettings);
    if (status != noErr) return 1;
    PMSetPageRange(printSettings, 1, (UInt32)kPMPrintAllPages);
    // get pointer to the PMSessionPrintDialog Carbon function
    typedef OSStatus (*dialog_f2)(PMPrintSession, PMPrintSettings, PMPageFormat, Boolean *);
    static dialog_f2 f2 = NULL;
    if (!f2) f2 = (dialog_f2)Fl_Darwin_System_Driver::get_carbon_function("PMSessionPrintDialog");
    status = (*f2)(printSession, printSettings, pageFormat, &accepted);
    if (!accepted) status = kPMCancel;
    if (status != noErr) {
      Fl::first_window()->show();
      return 1;
    }
    UInt32 from32, to32;
    PMGetFirstPage(printSettings, &from32); 
    if (frompage) *frompage = (int)from32;
    PMGetLastPage(printSettings, &to32); 
    if (topage) *topage = (int)to32;
    if(topage && *topage > pagecount) *topage = pagecount;
    CFStringRef mystring[1];
    mystring[0] = kPMGraphicsContextCoreGraphics;
    CFArrayRef array = CFArrayCreate(NULL, (const void **)mystring, 1, &kCFTypeArrayCallBacks);
    PMSessionSetDocumentFormatGeneration_type PMSessionSetDocumentFormatGeneration =
      (PMSessionSetDocumentFormatGeneration_type)Fl_Darwin_System_Driver::get_carbon_function("PMSessionSetDocumentFormatGeneration");
    status = PMSessionSetDocumentFormatGeneration(printSession, kPMDocumentFormatDefault, array, NULL);
    CFRelease(array);
    PMSessionBeginDocumentNoDialog_type PMSessionBeginDocumentNoDialog =
      (PMSessionBeginDocumentNoDialog_type)Fl_Darwin_System_Driver::get_carbon_function("PMSessionBeginDocumentNoDialog");
    status = PMSessionBeginDocumentNoDialog(printSession, printSettings, pageFormat);
#endif //__LP64__
  }

  if (status != noErr) return 1;
  y_offset = x_offset = 0;
  return 0;
}

void Fl_Cocoa_Printer_Driver::margins(int *left, int *top, int *right, int *bottom)
{
  PMPaper paper;
  PMGetPageFormatPaper(pageFormat, &paper);
  PMOrientation orientation;
  PMGetOrientation(pageFormat, &orientation);
  PMPaperMargins margins;
  PMPaperGetMargins(paper, &margins);
  if(orientation == kPMPortrait) {
    if (left) *left = (int)(margins.left / scale_x + 0.5);
    if (top) *top = (int)(margins.top / scale_y + 0.5);
    if (right) *right = (int)(margins.right / scale_x + 0.5);
    if (bottom) *bottom = (int)(margins.bottom / scale_y + 0.5);
    }
  else {
    if (left) *left = (int)(margins.top / scale_x + 0.5);
    if (top) *top = (int)(margins.left / scale_y + 0.5);
    if (right) *right = (int)(margins.bottom / scale_x + 0.5);
    if (bottom) *bottom = (int)(margins.right / scale_y + 0.5);
  }
}

int Fl_Cocoa_Printer_Driver::printable_rect(int *w, int *h)
//returns 0 iff OK
{
  OSStatus status;
  PMRect pmRect;
  int x, y;
  
  status = PMGetAdjustedPageRect(pageFormat, &pmRect);
  if (status != noErr) return 1;
  
  x = (int)pmRect.left;
  y = (int)pmRect.top;
  *w = int((int)(pmRect.right - x) / scale_x + 1);
  *h = int((int)(pmRect.bottom - y) / scale_y + 1);
  return 0;
}

void Fl_Cocoa_Printer_Driver::origin(int x, int y)
{
  x_offset = x;
  y_offset = y;
  CGContextRef gc = (CGContextRef)driver()->gc();
  CGContextRestoreGState(gc);
  CGContextRestoreGState(gc);
  CGContextSaveGState(gc);
  CGContextScaleCTM(gc, scale_x, scale_y);
  CGContextTranslateCTM(gc, x, y);
  CGContextRotateCTM(gc, angle);
  CGContextSaveGState(gc);
}

void Fl_Cocoa_Printer_Driver::scale (float s_x, float s_y)
{
  if (s_y == 0.) s_y = s_x;
  scale_x = s_x;
  scale_y = s_y;
  CGContextRef gc = (CGContextRef)driver()->gc();
  CGContextRestoreGState(gc);
  CGContextRestoreGState(gc);
  CGContextSaveGState(gc);
  CGContextScaleCTM(gc, scale_x, scale_y);
  CGContextRotateCTM(gc, angle);
  x_offset = y_offset = 0;
  CGContextSaveGState(gc);
}

void Fl_Cocoa_Printer_Driver::rotate (float rot_angle)
{
  angle = - rot_angle * M_PI / 180.;
  CGContextRef gc = (CGContextRef)driver()->gc();
  CGContextRestoreGState(gc);
  CGContextRestoreGState(gc);
  CGContextSaveGState(gc);
  CGContextScaleCTM(gc, scale_x, scale_y);
  CGContextTranslateCTM(gc, x_offset, y_offset);
  CGContextRotateCTM(gc, angle);
  CGContextSaveGState(gc);
}

void Fl_Cocoa_Printer_Driver::translate(int x, int y)
{
  CGContextRef gc = (CGContextRef)driver()->gc();
  CGContextSaveGState(gc);
  CGContextTranslateCTM(gc, x, y );
  CGContextSaveGState(gc);
}

void Fl_Cocoa_Printer_Driver::untranslate(void)
{
  CGContextRef gc = (CGContextRef)driver()->gc();
  CGContextRestoreGState(gc);
  CGContextRestoreGState(gc);
}

int Fl_Cocoa_Printer_Driver::start_page (void)
{	
  OSStatus status = PMSessionBeginPageNoDialog(printSession, pageFormat, NULL);
  CGContextRef gc;
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
  if ( &PMSessionGetCGGraphicsContext != NULL ) {
    status = PMSessionGetCGGraphicsContext(printSession, &gc);
  }
  else
#endif
  {
#if !defined(__LP64__) || !__LP64__
    PMSessionGetGraphicsContext_type PMSessionGetGraphicsContext =
      (PMSessionGetGraphicsContext_type)Fl_Darwin_System_Driver::get_carbon_function("PMSessionGetGraphicsContext");
    status = PMSessionGetGraphicsContext(printSession, NULL, (void **)&gc);
#endif
  }
  driver()->gc(gc);
  Fl_Surface_Device::push_current(this);
  PMRect pmRect;
  float win_scale_x, win_scale_y;

  PMPaper paper;
  PMGetPageFormatPaper(pageFormat, &paper);
  PMPaperMargins margins;
  PMPaperGetMargins(paper, &margins);
  PMOrientation orientation;
  PMGetOrientation(pageFormat, &orientation);
  
  status = PMGetAdjustedPageRect(pageFormat, &pmRect);
  double h = pmRect.bottom - pmRect.top;
  x_offset = 0;
  y_offset = 0; 
  angle = 0;
  scale_x = scale_y = 1;
  win_scale_x = win_scale_y = 1;
  if(orientation == kPMPortrait)
    CGContextTranslateCTM(gc, margins.left, margins.bottom + h);
  else
    CGContextTranslateCTM(gc, margins.top, margins.right + h);
  CGContextScaleCTM(gc, win_scale_x, - win_scale_y);
  ((Fl_Quartz_Graphics_Driver*)driver())->quartz_restore_line_style();
  CGContextSetShouldAntialias(gc, false);
  CGContextSaveGState(gc);
  CGContextSaveGState(gc);
  fl_line_style(FL_SOLID);
  fl_window = (Window)1; // TODO: something better
  fl_clip_region(0);
  return status != noErr;
}

int Fl_Cocoa_Printer_Driver::end_page (void)
{	
  CGContextRef gc = (CGContextRef)driver()->gc();
  CGContextFlush(gc);
  CGContextRestoreGState(gc);
  CGContextRestoreGState(gc);
  OSStatus status = PMSessionEndPageNoDialog(printSession);
  gc = NULL;
  Fl_Surface_Device::pop_current();
  return status != noErr;
}

void Fl_Cocoa_Printer_Driver::end_job (void)
{
  OSStatus status;
  
  status = PMSessionError(printSession);
  if (status != noErr) {
    fl_alert ("PM Session error %d", (int)status);
  }
  PMSessionEndDocumentNoDialog(printSession);
#if !defined(__LP64__) || !__LP64__
  if (fl_mac_os_version < 100500) {
    PMRelease(printSettings);
    PMRelease(pageFormat);
    PMRelease(printSession);
    }
#endif
  Fl_Window *w = Fl::first_window();
  if (w) w->show();
}

// version that prints at high res if using a retina display
void Fl_Cocoa_Printer_Driver::print_window_part(Fl_Window *win, int x, int y, int w, int h, int delta_x, int delta_y)
{
 Fl_Surface_Device::push_current( Fl_Display_Device::display_device() );
  Fl_Window *save_front = Fl::first_window();
  win->show();
  Fl::check();
  CGImageRef img = Fl_Cocoa_Window_Driver::driver(win)->CGImage_from_window_rect(x, y, w, h);
  if (save_front != win) save_front->show();
  Fl_Surface_Device::pop_current();
  ((Fl_Quartz_Graphics_Driver*)driver())->draw_CGImage(img,delta_x, delta_y, w, h, 0,0,w,h);
  CFRelease(img);
}

void Fl_Cocoa_Printer_Driver::origin(int *x, int *y)
{
  Fl_Paged_Device::origin(x, y);
}

void Fl_Cocoa_Printer_Driver::draw_decorated_window(Fl_Window *win, int x_offset, int y_offset)
{
  if (!win->shown() || win->parent() || !win->border() || !win->visible()) {
    this->print_widget(win, x_offset, y_offset);
    return;
  }
  int bt = win->decorated_h() - win->h();
  BOOL to_quartz =  (this->driver()->has_feature(Fl_Graphics_Driver::NATIVE));
  CALayer *layer = nil;
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
  if (fl_mac_os_version >= 101000) layer = [[[fl_xid(win) standardWindowButton:NSWindowCloseButton] superview] layer];
#endif
  
  if (layer) { // if title bar uses a layer
    if (to_quartz) { // to Quartz printer
      CGContextRef gc = (CGContextRef)driver()->gc();
      CGContextSaveGState(gc);
      CGContextTranslateCTM(gc, x_offset - 0.5, y_offset + bt - 0.5);
      float s = Fl::screen_driver()->scale(win->driver()->screen_num());
      CGContextScaleCTM(gc, 1/s, -1/s);
      Fl_Cocoa_Window_Driver::draw_layer_to_context(layer, gc, win->w() * s, bt);
      CGContextRestoreGState(gc);
    }
    else {
      CGColorSpaceRef cspace = CGColorSpaceCreateDeviceRGB ();
      CGContextRef gc = CGBitmapContextCreate(NULL, 2*win->w(), 2*bt, 8, 0, cspace, kCGImageAlphaPremultipliedLast);
      CGColorSpaceRelease(cspace);
      CGContextScaleCTM(gc, 2, 2);
      Fl_Cocoa_Window_Driver::draw_layer_to_context(layer, gc, win->w(), bt);
      Fl_RGB_Image *image = new Fl_RGB_Image((const uchar*)CGBitmapContextGetData(gc), 2*win->w(), 2*bt, 4,
                                             CGBitmapContextGetBytesPerRow(gc)); // 10.2
      int ori_x, ori_y;
      origin(&ori_x, &ori_y);
      scale(0.5);
      origin(2*ori_x, 2*ori_y);
      image->draw(2*x_offset, 2*y_offset); // draw title bar as double resolution image
      scale(1);
      origin(ori_x, ori_y);
      delete image;
      CGContextRelease(gc);
    }
    bool clip_corners = fl_mac_os_version >= 100600 && to_quartz && !win->parent();
    if (clip_corners) {
      CGContextRef gc = (CGContextRef)driver()->gc();
      CGContextSaveGState(gc);
      CGContextTranslateCTM(gc, x_offset, y_offset + bt );
      Fl_Cocoa_Window_Driver::clip_to_rounded_corners(gc, win->w(), win->h());
      CGContextTranslateCTM(gc, -x_offset, -y_offset - bt);
    }
    this->print_widget(win, x_offset, y_offset + bt);
    if (clip_corners) {
      CGContextRestoreGState((CGContextRef)driver()->gc());
    }
    return;
  }
  Fl_Display_Device::display_device()->set_current(); // send win to front and make it current
  NSString *title = [(NSWindow*)fl_xid(win) title];
  [title retain];
  [(NSWindow*)fl_xid(win) setTitle:@""]; // temporarily set a void window title
  win->show();
  Fl::check();
  // capture the window title bar with no title
  Fl_Shared_Image *top, *left, *bottom, *right;
  win->driver()->capture_titlebar_and_borders(top, left, bottom, right);
  [(NSWindow*)fl_xid(win) setTitle:title]; // put back the window title
  this->set_current(); // back to the Fl_Paged_Device
  top->draw(x_offset, y_offset); // print the title bar
  top->release();
  if (win->label()) { // print the window title
    const int skip = 65; // approx width of the zone of the 3 window control buttons
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
    if (fl_mac_os_version >= 100400 && to_quartz) { // use Cocoa string drawing with exact title bar font
      // the exact font is LucidaGrande 13 pts (and HelveticaNeueDeskInterface-Regular with 10.10)
      NSGraphicsContext *current = [NSGraphicsContext currentContext];
      [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithGraphicsPort:driver()->gc() flipped:YES]];//10.4
      NSDictionary *attr = [NSDictionary dictionaryWithObject:[NSFont titleBarFontOfSize:0]
                                                       forKey:NSFontAttributeName];
      NSSize size = [title sizeWithAttributes:attr];
      int x = x_offset + win->w()/2 - size.width/2;
      if (x < x_offset+skip) x = x_offset+skip;
      NSRect r = NSMakeRect(x, y_offset+bt/2+4, win->w() - skip, bt);
      [[NSGraphicsContext currentContext] setShouldAntialias:YES];
      [title drawWithRect:r options:(NSStringDrawingOptions)0 attributes:attr]; // 10.4
      [[NSGraphicsContext currentContext] setShouldAntialias:NO];
      [NSGraphicsContext setCurrentContext:current];
    }
    else
#endif
    {
      fl_font(FL_HELVETICA, 14);
      fl_color(FL_BLACK);
      int x = x_offset + win->w()/2 - fl_width(win->label())/2;
      if (x < x_offset+skip) x = x_offset+skip;
      fl_push_clip(x_offset, y_offset, win->w(), bt);
      fl_draw(win->label(), x, y_offset+bt/2+4);
      fl_pop_clip();
    }
  }
  [title release];
  this->print_widget(win, x_offset, y_offset + bt); // print the window inner part
}

//
// End of "$Id: Fl_Cocoa_Printer_Driver.mm 12690 2018-02-25 16:54:21Z manolo $".
//
