/FileExtraction/Java_unseen/dlitz_resin/modules/quercus/src/com/caucho/quercus/lib/pdf/PDF.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source 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.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.quercus.lib.pdf;

import com.caucho.quercus.annotation.NotNull;
import com.caucho.quercus.annotation.Optional;
import com.caucho.quercus.env.BooleanValue;
import com.caucho.quercus.env.Env;
import com.caucho.quercus.env.Value;
import com.caucho.quercus.env.StringValue;
import com.caucho.util.L10N;
import com.caucho.vfs.Path;
import com.caucho.vfs.TempStream;
import com.caucho.vfs.TempBuffer;
import com.caucho.vfs.WriteStream;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Logger;

/**
 * pdf object oriented API facade
 */
public class PDF {
  private static final Logger log = Logger.getLogger(PDF.class.getName());
  private static final L10N L = new L10N(PDF.class);

  private static final double KAPPA = 0.5522847498;

  private static final int PAGE_GROUP = 8;

  private static HashMap<String,Font> _faceMap = new HashMap<String,Font>();

  private HashMap<PDFFont,PDFFont> _fontMap
    = new HashMap<PDFFont,PDFFont>();

  private HashMap<PDFProcSet,PDFProcSet> _procSetMap
    = new HashMap<PDFProcSet,PDFProcSet>();

  private TempStream _tempStream;
  private WriteStream _os;
  private PDFWriter _out;

  private ArrayList<PDFPage> _pageGroup = new ArrayList<PDFPage>();
  private ArrayList<Integer> _pagesGroupList = new ArrayList<Integer>();
  private int _pageCount;

  private int _catalogId;

  private int _pageParentId;

  private PDFPage _page;
  private PDFStream _stream;

  private Env _env;

  public PDF(Env env)
  {
    _out = new PDFWriter(env.getOut());
    _env = env;
  }

  public boolean begin_document(@Optional String fileName,
                                @Optional String optList)
    throws IOException
  {
    _tempStream = new TempStream();
    _tempStream.openWrite();
    _os = new WriteStream(_tempStream);

    _out = new PDFWriter(_os);
    _out.beginDocument();

    _catalogId = _out.allocateId(1);
    _pageParentId = _out.allocateId(1);

    return true;
  }

  public boolean begin_page(double width, double height)
    throws IOException
  {
    if (PAGE_GROUP <= _pageGroup.size()) {
      _out.writePageGroup(_pageParentId, _pageGroup);
      _pageGroup.clear();

      _pagesGroupList.add(_pageParentId);
      _pageParentId = _out.allocateId(1);
    }

    _page = new PDFPage(_out, _pageParentId, width, height);
    _stream = _page.getStream();

    _pageCount++;

    _pageGroup.add(_page);

    return true;
  }

  public boolean begin_page_ext(double width, double height, String opt)
    throws IOException
  {
    return begin_page(width, height);
  }

  public boolean set_info(String key, String value)
  {
    if ("Author".equals(key)) {
      _out.setAuthor(key);
      return true;
    }
    else if ("Title".equals(key)) {
      _out.setTitle(key);
      return true;
    }
    else if ("Creator".equals(key)) {
      _out.setCreator(key);
      return true;
    }
    else
      return false;
  }

  public boolean set_parameter(String key, String value)
  {
    return false;
  }

  public boolean set_value(String key, double value)
  {
    return false;
  }

  /**
   * Returns the result as a string.
   */
  public Value get_buffer(Env env)
  {
    TempStream ts = _tempStream;
    _tempStream = null;

    if (ts == null)
      return BooleanValue.FALSE;

    StringValue result = env.createBinaryBuilder();
    for (TempBuffer ptr = ts.getHead();
         ptr != null;
         ptr = ptr.getNext()) {
      result.append(ptr.getBuffer(), 0, ptr.getLength());
    }

    ts.destroy();

    return result;
  }

  /**
   * Returns the error message.
   */
  public String get_errmsg()
  {
    return "";
  }

  /**
   * Returns the error number.
   */
  public int get_errnum()
  {
    return 0;
  }

  /**
   * Returns the value for a parameter.
   */
  public String get_parameter(String name, @Optional double modifier)
  {
    if ("fontname".equals(name)) {
      PDFFont font = _stream.getFont();

      if (font != null)
        return font.getFontName();
      else
        return null;
    }
    else
      return null;
  }

  /**
   * Returns the value for a parameter.
   */
  public double get_value(String name, @Optional double modifier)
  {
    if ("ascender".equals(name)) {
      PDFFont font = _stream.getFont();

      if (font != null)
        return font.getAscender();
      else
        return 0;
    }
    else if ("capheight".equals(name)) {
      PDFFont font = _stream.getFont();

      if (font != null)
        return font.getCapHeight();
      else
        return 0;
    }
    else if ("descender".equals(name)) {
      PDFFont font = _stream.getFont();

      if (font != null)
        return font.getDescender();
      else
        return 0;
    }
    else if ("fontsize".equals(name)) {
      return _stream.getFontSize();
    }
    else
      return 0;
  }

  public boolean initgraphics(Env env)
  {
    env.stub("initgraphics");

    return false;
  }

  /**
   * Loads a font for later use.
   *
   * @param name the font name, e.g. Helvetica
   * @param encoding the font encoding, e.g. winansi
   * @param opt any options
   */
  public PDFFont load_font(String name, String encoding, String opt)
    throws IOException
  {
    Font face = loadFont(name);

    PDFFont font = new PDFFont(face, encoding, opt);

    PDFFont oldFont = _fontMap.get(font);

    if (oldFont != null)
      return oldFont;

    font.setId(_out.allocateId(1));

    _fontMap.put(font, font);

    _out.addPendingObject(font);

    return font;
  }

  private Font loadFont(String name)
    throws IOException
  {
    synchronized (_faceMap) {
      Font face = _faceMap.get(name);

      if (face == null) {
        Path p = _env.getQuercus().getPwd().lookup("WEB-INF/lib/");
        face = new AfmParser().parse(String.valueOf(p), name);

        _faceMap.put(name, face);
      }

      return face;
    }
  }

  /**
   * Sets the dashing
   *
   * @param b black length
   * @param w which length
   */
  public boolean setdash(double b, double w)
  {
    _stream.setDash(b, w);

    return true;
  }

  /**
   * Sets the dashing
   */
  public boolean setdashpattern(Env env, @Optional String optlist)
  {
    env.stub("setdashpattern");

    return false;
  }

  /**
   * Sets the flatness
   */
  public boolean setflat(Env env, double flatness)
  {
    env.stub("setflat");

    return false;
  }

  /**
   * Sets the linecap style
   */
  public boolean setlinecap(Env env,
                            int cap)
  {
    env.stub("setlinecap");

    return false;
  }

  /**
   * Sets the linejoin style
   */
  public boolean setlinejoin(Env env,
                             int linejoin)
  {
    env.stub("setlinejoin");

    return false;
  }

  /**
   * Sets the current font
   *
   * @param name the font name, e.g. Helvetica
   * @param encoding the font encoding, e.g. winansi
   * @param opt any options
   */
  public boolean setfont(@NotNull PDFFont font, double size)
    throws IOException
  {
    if (font == null)
      return false;

    _stream.setFont(font, size);

    _page.addResource(font.getResourceName(), font.getResource());

    return true;
  }

  /**
   * Sets the matrix style
   */
  public boolean setmatrix(Env env,
                           double a,
                           double b,
                           double c,
                           double d,
                           double e,
                           double f)
  {
    env.stub("setmatrix");

    return false;
  }

  /**
   * Sets the miter limit
   */
  public boolean setmiterlimit(Env env, double v)
  {
    env.stub("setmiterlimit");

    return false;
  }

  /**
   * Sets the shading pattern
   */
  public boolean shading_pattern(Env env,
                                 int shading,
                                 @Optional String optlist)
  {
    env.stub("shading_pattern");

    return false;
  }

  /**
   * Define a blend
   */
  public int shading(Env env,
                     String type,
                     double x1,
                     double y1,
                     double x2,
                     double y2,
                     double c1,
                     double c2,
                     double c3,
                     double c4,
                     @Optional String optlist)
  {
    env.stub("shading");

    return 0;
  }

  /**
   * Fill with a shading object.
   */
  public boolean shfill(Env env,
                        int shading)
  {
    env.stub("shfill");

    return false;
  }

  /**
   * Returns the length of a string for a font.
   */
  public double stringwidth(String string, @NotNull PDFFont font, double size)
  {
    if (font == null)
      return 0;

    return size * font.stringWidth(string) / 1000.0;
  }


  /**
   * Sets the text position.
   */
  public boolean set_text_pos(double x, double y)
  {
    _stream.setTextPos(x, y);

    return true;
  }

  /**
   * Fills
   */
  public boolean fill()
  {
    _stream.fill();

    return true;
  }

  /**
   * Closes the path
   */
  public boolean closepath()
  {
    _stream.closepath();

    return true;
  }

  /**
   * Appends the current path to the clipping path.
   */
  public boolean clip()
  {
    _stream.clip();

    return true;
  }

  /**
   * Closes the path strokes
   */
  public boolean closepath_stroke()
  {
    _stream.closepathStroke();

    return true;
  }

  /**
   * Closes the path strokes
   */
  public boolean closepath_fill_stroke()
  {
    _stream.closepathFillStroke();

    return true;
  }

  /**
   * Fills
   */
  public boolean fill_stroke()
  {
    _stream.fillStroke();

    return true;
  }

  /**
   * Ends the path
   */
  public boolean endpath()
  {
    _stream.endpath();

    return true;
  }

  /**
   * Draws a bezier curve
   */
  public boolean curveto(double x1, double y1,
                         double x2, double y2,
                         double x3, double y3)
  {
    _stream.curveTo(x1, y1, x2, y2, x3, y3);

    return true;
  }

  /**
   * Draws a bezier curve
   */
  public boolean curveto_b(double x1, double y1,
                           double x2, double y2)
  {
    _stream.curveTo(x1, y1, x1, y1, x2, y2);

    return true;
  }

  /**
   * Draws a bezier curve
   */
  public boolean curveto_e(double x1, double y1,
                           double x2, double y2)
  {
    _stream.curveTo(x1, y1, x2, y2, x2, y2);

    return true;
  }

  /**
   * Creates a counterclockwise arg
   */
  public boolean arc(double x1, double y1, double r, double a, double b)
  {
    a = a % 360;
    if (a < 0)
      a += 360;

    b = b % 360;
    if (b < 0)
      b += 360;

    if (b < a)
      b += 360;

    int aQuarter = (int) (a / 90);
    int bQuarter = (int) (b / 90);

    if (aQuarter == bQuarter) {
      clockwiseArc(x1, y1, r, a, b);
    }
    else {
      clockwiseArc(x1, y1, r, a, (aQuarter + 1) * 90);

      for (int q = aQuarter + 1; q < bQuarter; q++)
        clockwiseArc(x1, y1, r, q * 90, (q + 1) * 90);

      clockwiseArc(x1, y1, r, bQuarter * 90, b);
    }

    return true;
  }

  /**
   * Creates a clockwise arc
   */
  public boolean arcn(double x1, double y1, double r, double a, double b)
  {
    a = a % 360;
    if (a < 0)
      a += 360;

    b = b % 360;
    if (b < 0)
      b += 360;

    if (a < b)
      a += 360;

    int aQuarter = (int) (a / 90);
    int bQuarter = (int) (b / 90);

    if (aQuarter == bQuarter) {
      counterClockwiseArc(x1, y1, r, a, b);
    }
    else {
      counterClockwiseArc(x1, y1, r, a, aQuarter * 90);

      for (int q = aQuarter - 1; bQuarter < q; q--)
        counterClockwiseArc(x1, y1, r, (q + 1) * 90, q * 90);

      counterClockwiseArc(x1, y1, r, (bQuarter + 1) * 90, b);
    }

    return true;
  }

  /**
   * Creates an arc from 0 to pi/2
   */
  private boolean clockwiseArc(double x, double y, double r,
                               double aDeg, double bDeg)
  {
    double a = aDeg * Math.PI / 180.0;
    double b = bDeg * Math.PI / 180.0;

    double cos_a = Math.cos(a);
    double sin_a = Math.sin(a);

    double x1 = x + r * cos_a;
    double y1 = y + r * sin_a;

    double cos_b = Math.cos(b);
    double sin_b = Math.sin(b);

    double x2 = x + r * cos_b;
    double y2 = y + r * sin_b;

    double l = KAPPA * r * 2 * (b - a) / Math.PI;

    lineto(x1, y1);
    curveto(x1 - l * sin_a, y1 + l * cos_a,
            x2 + l * sin_b, y2 - l * cos_b,
            x2, y2);

    return true;
  }

  /**
   * Creates an arc from 0 to pi/2
   */
  private boolean counterClockwiseArc(double x, double y, double r,
                                      double aDeg, double bDeg)
  {
    double a = aDeg * Math.PI / 180.0;
    double b = bDeg * Math.PI / 180.0;

    double cos_a = Math.cos(a);
    double sin_a = Math.sin(a);

    double x1 = x + r * cos_a;
    double y1 = y + r * sin_a;

    double cos_b = Math.cos(b);
    double sin_b = Math.sin(b);

    double x2 = x + r * cos_b;
    double y2 = y + r * sin_b;

    double l = KAPPA * r * 2 * (a - b) / Math.PI;

    lineto(x1, y1);
    curveto(x1 + l * sin_a, y1 - l * cos_a,
            x2 - l * sin_b, y2 + l * cos_b,
            x2, y2);

    return true;
  }

  /**
   * Creates a circle
   */
  public boolean circle(double x1, double y1, double r)
  {
    double l = r * KAPPA;

    moveto(x1, y1 + r);

    curveto(x1 - l, y1 + r, x1 - r, y1 + l, x1 - r, y1);

    curveto(x1 - r, y1 - l, x1 - l, y1 - r, x1, y1 - r);

    curveto(x1 + l, y1 - r, x1 + r, y1 - l, x1 + r, y1);

    curveto(x1 + r, y1 + l, x1 + l, y1 + r, x1, y1 + r);

    return true;
  }

  /**
   * Sets the graphics position.
   */
  public boolean lineto(double x, double y)
  {
    _stream.lineTo(x, y);

    return true;
  }

  /**
   * Sets the graphics position.
   */
  public boolean moveto(double x, double y)
  {
    _stream.moveTo(x, y);

    return true;
  }

  /**
   * Creates a rectangle
   */
  public boolean rect(double x, double y, double width, double height)
  {
    _stream.rect(x, y, width, height);

    return true;
  }

  /**
   * Sets the color to a grayscale
   */
  public boolean setgray_stroke(double g)
  {
    return _stream.setcolor("stroke", "gray", g, 0, 0, 0);
  }

  /**
   * Sets the color to a grayscale
   */
  public boolean setgray_fill(double g)
  {
    return _stream.setcolor("fill", "gray", g, 0, 0, 0);
  }

  /**
   * Sets the color to a grayscale
   */
  public boolean setgray(double g)
  {
    return _stream.setcolor("both", "gray", g, 0, 0, 0);
  }

  /**
   * Sets the color to a rgb
   */
  public boolean setrgbcolor_stroke(double r, double g, double b)
  {
    return _stream.setcolor("stroke", "rgb", r, g, b, 0);
  }

  /**
   * Sets the fill color to a rgb
   */
  public boolean setrgbcolor_fill(double r, double g, double b)
  {
    return _stream.setcolor("fill", "rgb", r, g, b, 0);
  }

  /**
   * Sets the color to a rgb
   */
  public boolean setrgbcolor(double r, double g, double b)
  {
    return _stream.setcolor("both", "rgb", r, g, b, 0);
  }

  /**
   * Sets the color
   */
  public boolean setcolor(String fstype, String colorspace,
                          double c1,
                          @Optional double c2,
                          @Optional double c3,
                          @Optional double c4)
  {
    return _stream.setcolor(fstype, colorspace, c1, c2, c3, c4);
  }

  /**
   * Sets the line width
   */
  public boolean setlinewidth(double w)
  {
    return _stream.setlinewidth(w);
  }

  /**
   * Concatenates the matrix
   */
  public boolean concat(double a, double b, double c,
                        double d, double e, double f)
  {
    return _stream.concat(a, b, c, d, e, f);
  }

  /**
   * open image
   */
  public PDFImage open_image_file(String type, Path file,
                                  @Optional String stringParam,
                                  @Optional int intParam)
    throws IOException
  {
    PDFImage img = new PDFImage(file);

    img.setId(_out.allocateId(1));

    _out.addPendingObject(img);

    return img;
  }

  /**
   * open image
   */
  public PDFImage load_image(String type,
                             Path file,
                             @Optional String optlist)
    throws IOException
  {
    PDFImage img = new PDFImage(file);

    img.setId(_out.allocateId(1));

    _out.addPendingObject(img);

    return img;
  }

  public boolean fit_image(PDFImage img, double x, double y,
                           @Optional String opt)
  {
    _page.addResource(img.getResourceName(), img.getResource());

    _stream.save();

    concat(img.get_width(), 0, 0, img.get_height(), x, y);

    _stream.fit_image(img);

    _stream.restore();

    return true;
  }

  /**
   * open image
   */
  public PDFEmbeddedFile fit_embedded_file(Path path,
                                           double x, double y,
                                           double width, double height)
    throws IOException
  {
    PDFEmbeddedFile file = new PDFEmbeddedFile(path);

    file.setId(_out.allocateId(1));
    
    _out.addPendingObject(file);
    
    PDFFileImage img = new PDFFileImage(file.getId(), width, height);
    img.setId(_out.allocateId(1));

    _out.addPendingObject(img);
    
    _page.addResource(img.getResourceName(), img.getResource());

    _stream.save();

    // concat(img.get_width(), 0, 0, img.get_height(), x, y);
    concat(width, 0, 0, height, x, y);

    _stream.fit_file_image(img);

    _stream.restore();

    return file;
  }

  /**
   * Skews the coordinates
   *
   * @param a degrees to skew the x axis
   * @param b degrees to skew the y axis
   */
  public boolean skew(double aDeg, double bDeg)
  {
    double a = aDeg * Math.PI / 180;
    double b = bDeg * Math.PI / 180;

    return _stream.concat(1, Math.tan(a), Math.tan(b), 1, 0, 0);
  }

  /**
   * scales the coordinates
   *
   * @param sx amount to scale the x axis
   * @param sy amount to scale the y axis
   */
  public boolean scale(double sx, double sy)
  {
    return _stream.concat(sx, 0, 0, sy, 0, 0);
  }

  /**
   * translates the coordinates
   *
   * @param tx amount to translate the x axis
   * @param ty amount to translate the y axis
   */
  public boolean translate(double tx, double ty)
  {
    return _stream.concat(1, 0, 0, 1, tx, ty);
  }

  /**
   * rotates the coordinates
   *
   * @param p amount to rotate
   */
  public boolean rotate(double pDeg)
  {
    double p = pDeg * Math.PI / 180;

    return _stream.concat(Math.cos(p), Math.sin(p),
                          -Math.sin(p), Math.cos(p),
                          0, 0);
  }

  /**
   * Saves the graphics state.
   */
  public boolean save()
  {
    return _stream.save();
  }

  /**
   * Restores the graphics state.
   */
  public boolean restore()
  {
    return _stream.restore();
  }

  /**
   * Displays text
   */
  public boolean show(String text)
  {
    _stream.show(text);

    return true;
  }

  /**
   * Displays text
   */
  public boolean show_boxed(String text, double x, double y,
                            double width, double height,
                            String mode, @Optional String feature)
  {
    set_text_pos(x, y);
    _stream.show(text);

    return true;
  }

  /**
   * Displays text
   */
  public boolean show_xy(String text, double x, double y)
  {
    set_text_pos(x, y);
    _stream.show(text);

    return true;
  }

  /**
   * Draws the graph
   */
  public boolean stroke()
  {
    _stream.stroke();

    return true;
  }

  /**
   * Displays text
   */
  public boolean continue_text(String text)
  {
    _stream.continue_text(text);

    return true;
  }

  public boolean end_page()
  {
    _stream.flush();

    PDFProcSet procSet = _stream.getProcSet();

    _page.addResource(procSet.getResourceName(), procSet.getResource());

    _page = null;
    _stream = null;

    return true;
  }

  public boolean end_page_ext(String optlist)
  {
    return end_page();
  }

  public boolean end_document(@Optional String optList)
    throws IOException
  {
    if(null == _out)
    {
      // output stream already closed;
      return false;
    }
    if (_pageGroup.size() > 0) {
      _out.writePageGroup(_pageParentId, _pageGroup);
      _pageGroup.clear();

      if (_pagesGroupList.size() > 0)
        _pagesGroupList.add(_pageParentId);
    }

    _out.writeCatalog(_catalogId, _pageParentId, _pagesGroupList, _pageCount);

    _out.endDocument();

    _os.close();
    _out = null;

    return true;
  }

  public boolean close()
    throws IOException
  {
    return end_document("");
  }

  public boolean delete()
    throws IOException
  {
    return true;
  }

  public String toString()
  {
    return "PDF[]";
  }
}
