/*
 * Copyright (c) 1987, 1988, 1989, 1990, 1991 Stanford University
 * Copyright (c) 1991 Silicon Graphics, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and 
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Stanford and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Stanford and Silicon Graphics.
 * 
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 *
 * IN NO EVENT SHALL STANFORD OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
 * OF THIS SOFTWARE.
 */

/*
 * Printer - draw for PostScript printer
 */

#include <cstdio>
#include <InterViews/bitmap.h>
#include <InterViews/brush.h>
#include <InterViews/color.h>
#include <InterViews/font.h>
#include <InterViews/printer.h>
#include <InterViews/raster.h>
#include <InterViews/transformer.h>
#include <OS/list.h>
#include <OS/math.h>
#include <stream.h>
#include <string.h>
#include <stdio.h>

static const float PAGE_WIDTH = 8.5 * 72;
static const float PAGE_HEIGHT = 11 * 72;
static const float epsilon = 0.01;

static const char* ps_prolog = "\
save 20 dict begin\n\
\n\
/sf {   % scale /fontName => -  (set current font)\n\
    {findfont} stopped {pop /Courier findfont} if\n\
    exch scalefont setfont\n\
} def\n\
\n\
/ws {\n\
    4 index 6 4 roll moveto sub\n\
    2 index stringwidth pop sub\n\
    exch div 0 8#40 4 3 roll\n\
    widthshow\n\
} def\n\
\n\
/as {\n\
    4 index 6 4 roll moveto sub\n\
    2 index stringwidth pop sub\n\
    exch div 0 3 2 roll\n\
    ashow\n\
} def\n\
\n\
";

static const char* ps_epilog = "\
end restore\n\
";

class PrinterInfo {
public:
    const Color* color_;
    const Brush* brush_;
    const Font* font_;
};

class PrinterInfoList;

class PrinterRep {
public:
    ostream* out_;
    int page_;
    PrinterInfoList* info_;

    float x_;
    float y_;
    Coord text_curx_;
    Coord text_cury_;
    int text_chars_;
    int text_spaces_;
};

declareList(PrinterInfoList,PrinterInfo)
implementList(PrinterInfoList,PrinterInfo)

static void do_color(ostream& out, const Color* color) {
  // alpha supported added by cd1f 21-may-95
  float r, g, b, a;
  a = color->alpha();
  color->intensities(r, g, b);
  if(a == 1.0) {
    out << (double)r << " " << (double)g << " " << (double)b << " setrgbcolor\n";
  }
  else {
    float c = (r + g + b)/3.0;
    if(c < 0.5)  out  << (double)(1.0 - a) << " " << "setgray\n";
    else out  << (double)a << " " << "setgray\n";
  }
}

#include <InterViews/session.h>
#include <IV-X11/xbrush.h>

static void do_brush(ostream& out, const Brush* brush) {
    Coord linewidth = brush->width();
    out << (double)linewidth << " setlinewidth\n";

    int dcnt = brush->dash_count();
    if(dcnt == 0) 
      out << "[] 0 setdash\n";
    else {
      out << "[";
      for(int i=0; i < dcnt; i++) {
	out << " " << brush->dash_list(i);
      }
      out << "] 0 setdash\n";
    }
}

static void do_font(ostream& out, const Font* font) {
    out << (double)font->size() << " /";
    const char* p = font->name();
    while (*p != '\0') {
        out << char(*p == ' ' ? '-' : *p);
        ++p;
    }
    out << " sf\n";
}

Printer::Printer(ostream* out) {
    PrinterRep* p = new PrinterRep;
    rep_ = p;
    p->out_ = out;
    p->page_ = 1;
    p->x_ = 0;
    p->y_ = 0;
    p->text_curx_ = 0;
    p->text_cury_ = 0;
    p->text_chars_ = 0;
    p->text_spaces_ = 0;

    PrinterInfo info;
    info.color_ = nil;
    info.brush_ = nil;
    info.font_ = nil;

    p->info_ = new PrinterInfoList();
    p->info_->append(info);
}

Printer::~Printer() {
    flush();
    delete rep_->info_;
    delete rep_;
}

PixelCoord Printer::to_pixels(Coord p) const { return PixelCoord(p); }
Coord Printer::to_coord(PixelCoord p) const { return p; }
Coord Printer::to_pixels_coord(Coord p) const { return p; }

void Printer::resize(Coord left, Coord bottom, Coord right, Coord top) {
    PrinterRep* p = rep_;
    ostream& out = *p->out_;
    p->x_ = (float(left + right) - PAGE_WIDTH)/2;
    p->y_ = (float(top + bottom) - PAGE_HEIGHT)/2;
    flush();
    size(right - left, top - bottom);
    damage(left, bottom, right, top);
}

void Printer::prolog(const char* creator) {
    ostream& out = *rep_->out_;
    out << "%!PS-Adobe-2.0\n";
    out << "%%Creator: " << creator << "\n";
    out << "%%Pages: atend\n";
    out << "%%EndComments\n";
    out << ps_prolog;
    out << "%%EndProlog\n";
}

void Printer::epilog () {
    PrinterRep* p = rep_;
    ostream& out = *p->out_;
    flush();
    out << "showpage\n";
    out << "%%Trailer\n";
    out << ps_epilog;
    out << "%%Pages: " << p->page_ - 1 << "\n";
}

void Printer::comment(const char* text) {
    ostream& out = *rep_->out_;
    flush();
    out << "%% " << text << "\n";
}

void Printer::page(const char* label) {
    PrinterRep* p = rep_;
    flush();
    if (p->page_ > 1) {
        *p->out_ << "showpage\n";
    }
    *p->out_ << "%%Page: " << label << " " << p->page_ << "\n";
    *p->out_ << (double)-(p->x_) << " " << (double)-(p->y_) << " translate\n";
    PrinterInfo& info = p->info_->item_ref(p->info_->count() - 1);
    info.font_ = nil;
    info.color_ = nil;
    info.brush_ = nil;
    p->page_ += 1;
}

void Printer::push_transform() {
    Canvas::push_transform();
    PrinterRep* p = rep_;
    flush();
    long depth = p->info_->count();
    PrinterInfo info = p->info_->item_ref(depth - 1);
    p->info_->insert(depth, info);
    *p->out_ << "gsave\n";
}

void Printer::pop_transform() {
    PrinterRep* p = rep_;
    flush();
    long depth = p->info_->count();
    p->info_->remove(depth - 1);
    *p->out_ << "grestore\n";
    Canvas::pop_transform();
}

void Printer::transform(const Transformer& t) {
    Canvas::transform(t);
    PrinterRep* p = rep_;
    flush();
    float a00, a01, a10, a11, a20, a21;
    t.matrix(a00, a01, a10, a11, a20, a21);
    *p->out_ << "[" << (double)a00 << " " << (double)a01;
    *p->out_ << " " << (double)a10 << " " << (double)a11;
    *p->out_ << " " << (double)a20 << " " << (double)a21 << "] concat\n";
}

/*
 * The current transform push/pop just saves and restores state,
 * so we can use it for clipping as well.
 */

void Printer::push_clipping() {
    PrinterRep* p = rep_;
    flush();
    long depth = p->info_->count();
    PrinterInfo info = p->info_->item_ref(depth - 1);
    p->info_->insert(depth, info);
    *p->out_ << "gsave\n";
}

void Printer::pop_clipping() {
    PrinterRep* p = rep_;
    flush();
    long depth = p->info_->count();
    p->info_->remove(depth - 1);
    *p->out_ << "grestore\n";
}

void Printer::new_path() {
    ostream& out = *rep_->out_;
    flush();
    out << "newpath\n";
}

void Printer::move_to(Coord x, Coord y) {
    ostream& out = *rep_->out_;
    flush();
    out << (double)x << " " << (double)y << " moveto\n";
}

void Printer::line_to(Coord x, Coord y) {
    ostream& out = *rep_->out_;
    flush();
    out << (double)x << " " << (double)y << " lineto\n";
}

void Printer::curve_to(
    Coord x, Coord y, Coord x1, Coord y1, Coord x2, Coord y2
) {
    ostream& out = *rep_->out_;
    flush();
    out << (double)x1 << " " << (double)y1 << " " << (double)x2 << " " << (double)y2 << " ";
    out << (double)x << " " << (double)y << " curveto\n";
}

void Printer::close_path() {
    ostream& out = *rep_->out_;
    flush();
    out << "closepath\n";
}

void Printer::stroke(const Color* color, const Brush* brush) {
    PrinterRep* p = rep_;
    ostream& out = *p->out_;
    flush();
    PrinterInfo& info = p->info_->item_ref(p->info_->count() - 1);
    if (info.color_ != color) {
        do_color(out, color);
        info.color_ = color;
    }
    if (info.brush_ != brush) {
        do_brush(out, brush);
        info.brush_ = brush;
    }
    out << "gsave stroke grestore\n";
}

void Printer::fill(const Color* color) {
    PrinterRep* p = rep_;
    ostream& out = *p->out_;
    flush();
    PrinterInfo& info = p->info_->item_ref(p->info_->count() - 1);
    if (info.color_ != color) {
        do_color(out, color);
        info.color_ = color;
    }
    out << "gsave eofill grestore\n";
}

void Printer::clip() {
    ostream& out = *rep_->out_;
    flush();
    out << "eoclip\n";
}

void Printer::character(
    const Font* font, long c, Coord width, const Color* color, Coord x, Coord y
) {
    PrinterRep* p = rep_;
    ostream& out = *p->out_;
    PrinterInfo& info = p->info_->item_ref(p->info_->count() - 1);
    if (info.color_ != color) {
        flush();
        do_color(out, color);
        info.color_ = color;
    }
    if (!Math::equal(y, p->text_cury_, epsilon)) {
        flush();
    }
    if (!Math::equal(x, p->text_curx_, epsilon)) {
        flush();
    }
    if (info.font_ != font) {
        flush();
        do_font(out, font);
        info.font_ = font;
    }
    if (p->text_chars_ == 0) {
        out << (double)x << " " << (double)y << "(";
    }
    p->text_curx_ = x + width;
    p->text_cury_ = y;
    if (c == '\\' || c == ')' || c == '(') {
        out << "\\" << char(c);
    } else if (c > 127) {
#ifdef __GNUC__
	out_form(out, "\\%03lo", c);
#else
	out << "\\";
        int old_width = out.width(3);
        char old_fill = out.fill('0');
        out << oct << c << dec;
        out.width(old_width);
        out.fill(old_fill);
#endif
    } else {
        out << char(c);
    }
    p->text_chars_ += 1;
    if (c == ' ') {
        p->text_spaces_ += 1;
    }
}

void Printer::flush() {
    PrinterRep* p = rep_;
    ostream& out = *p->out_;
    if (p->text_chars_ > 0) {
        out << ") ";
        if (p->text_spaces_ > 0) {
            out << p->text_spaces_ << " " << (double)p->text_curx_ << " ws\n";
        } else {
            out << p->text_chars_ << " " << (double)p->text_curx_ << " as\n";
        }
        p->text_chars_ = 0;
        p->text_spaces_ = 0;
    }
}

void Printer::stencil(
    const Bitmap* mask, const Color* color, Coord x, Coord y
) {
    PrinterRep* p = rep_;
    ostream& out = *p->out_;
    flush();
    PrinterInfo& info = p->info_->item_ref(p->info_->count() - 1);
    if (info.color_ != color) {
        do_color(out, color);
        info.color_ = color;
    }
    unsigned long width = mask->pwidth();
    unsigned long height = mask->pheight();
    unsigned long bytes = (width-1)/8 + 1;
    Coord left = x - mask->left_bearing();
    Coord right = x + mask->right_bearing();
    Coord bottom = y - mask->descent();
    Coord top = y + mask->ascent();
    out << "gsave\n";
    out << "/picstr " << bytes << " string def\n";
    out << (double)left << " " << (double)bottom << "  translate\n";
    out << (double)right - left << " " << (double)top - bottom << " scale\n";
    out << (double)width << " " << (double)height << " true\n";
    out << "[" << (double)width << " 0 0 " << (double)height << " 0 0]\n";
    out << "{currentfile picstr readhexstring pop} imagemask\n";
#ifndef __GNUC__
    int old_width = out.width(1);
    out << hex;
#endif
    for (int iy = 0; iy < height; ++iy) {
        for (int ix = 0; ix < bytes; ++ix) {
            int byte = 0;
            for (int bit = 0; bit < 8; ++bit) {
                if (mask->peek(ix*8 + bit, iy)) {
                    byte |= 0x80 >> bit;
                }
            }
#ifdef __GNUC__
	    out_form(out, "%02x", byte);
#else
            out << ((byte>>4) & 0x0f) <<  (byte & 0x0f);
#endif
        }
        out << "\n";
    }
#ifndef __GNUC__
    out << dec;
    out.width(old_width);
#endif
    out << "grestore\n";
}

void Printer::image(const Raster* raster, Coord x, Coord y) {
    PrinterRep* p = rep_;
    ostream& out = *p->out_;
    flush();
    unsigned long width = raster->pwidth();
    unsigned long height = raster->pheight();
    float left = float(x) - raster->left_bearing();
    float right = float(x) + raster->right_bearing();
    float bottom = float(y) - raster->descent();
    float top = float(y) + raster->ascent();
    out << "gsave\n";
    out << "/picstr " << (double)width << " string def\n";
    out << (double)left << " " << (double)bottom << "  translate\n";
    out << (double)right - left << " " << (double)top - bottom << " scale\n";
    out << (double)width << " " << (double)height << " 8\n";
    out << "[" << (double)width << " 0 0 " << (double)height << " 0 0]\n";
    out << "{currentfile picstr readhexstring pop} image\n";
#ifndef __GNUC__
    int old_width = out.width(1);
    out << hex;
#endif
    for (int iy = 0; iy < height; ++iy) {
        for (int ix = 0; ix < width; ++ix) {
            float r, g, b, alpha;
            raster->peek(ix, iy, r, g, b, alpha);
            int byte = int(0xff * (r + g + b) / 3);
#ifdef __GNUC__
	    out_form(out, "%02x", byte);
#else
            out << ((byte>>4) & 0x0f) <<  (byte & 0x0f);
#endif
        }
        out << "\n";
    }
#ifndef __GNUC__
    out << dec;
    out.width(old_width);
#endif
    out << "grestore\n";
}
