/*
* 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[]";
}
}