// Copyright 2001 freehep
package org.freehep.graphicsio.pdf;
import java.awt.GradientPaint;
import java.awt.Paint;
import java.awt.TexturePaint;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
/**
* Delay Paint objects (gradient/texture, not color) for writing
* pattern/shading/function dictionaries to the pdf file when the pageStream is
* complete.
* TODO: - reuse pattern dictionaries if possible - cyclic function not working
* yet (ps calculation)
*
* @author Simon Fischer
* @version $Id: PDFPaintDelayQueue.java 10270 2007-01-09 18:18:57Z duns $
*/
public class PDFPaintDelayQueue {
private static int currentNumber = 0;
private class Entry {
private Paint paint;
private String name;
private AffineTransform trafo;
private String writeAs;
private boolean written;
private Entry(Paint paint, AffineTransform trafo, String writeAs) {
this.paint = paint;
this.trafo = trafo;
this.writeAs = writeAs;
this.name = "Paint" + (currentNumber++);
this.written = false;
}
}
private List paintList;
private PDFWriter pdf;
// private PDFImageDelayQueue imageDelayQueue;
private AffineTransform pageMatrix;
/** Don't forget to call setPageMatrix(). */
public PDFPaintDelayQueue(PDFWriter pdf, PDFImageDelayQueue imageDelayQueue) {
this.pdf = pdf;
this.paintList = new LinkedList();
// this.imageDelayQueue = imageDelayQueue;
this.pageMatrix = new AffineTransform();
}
/**
* Call this method in order to inform this class about the transformation
* that is necessary to map the pattern's coordinate space to the default
* coordinate system of the parent's content stream (in our case the
* flipping of the page).
*/
public void setPageMatrix(AffineTransform t) {
pageMatrix = new AffineTransform(t);
}
public PDFName delayPaint(Paint paint, AffineTransform transform,
String writeAs) {
Entry e = new Entry(paint, transform, writeAs);
paintList.add(e);
return pdf.name(e.name);
}
/** Creates a stream for every delayed image. */
public void processAll() throws IOException {
ListIterator i = paintList.listIterator();
while (i.hasNext()) {
Entry e = (Entry) i.next();
if (!e.written) {
e.written = true;
if (e.paint instanceof GradientPaint) {
addGradientPaint(e);
} else if (e.paint instanceof TexturePaint) {
addTexturePaint(e);
} else {
System.err.println("PDFWriter: Paint of class '"
+ e.paint.getClass() + "' not supported.");
// FIXME, we could write a color here, to keep the file
// valid.
}
}
}
}
/**
* Adds all names to the dictionary which should be the value of the
* resources dicionrary's /Pattern entry.
*/
public int addPatterns() throws IOException {
if (paintList.size() > 0) {
PDFDictionary patterns = pdf.openDictionary("Pattern");
ListIterator i = paintList.listIterator();
while (i.hasNext()) {
Entry e = (Entry) i.next();
patterns.entry(e.name, pdf.ref(e.name));
}
pdf.close(patterns);
}
return paintList.size();
}
private void addGradientPaint(Entry e) throws IOException {
GradientPaint gp = (GradientPaint) e.paint;
// open the pattern dictionary
PDFDictionary pattern = pdf.openDictionary(e.name);
pattern.entry("Type", pdf.name("Pattern"));
pattern.entry("PatternType", 2);
setMatrix(pattern, e, 0, 0);
// the shading subdictionary
PDFDictionary shading = pattern.openDictionary("Shading");
shading.entry("ShadingType", 2);
shading.entry("ColorSpace", pdf.name("DeviceRGB"));
Point2D p1 = gp.getPoint1();
Point2D p2 = gp.getPoint2();
shading.entry("Coords", new double[] { p1.getX(), p1.getY(), p2.getX(),
p2.getY() });
double[] domain = new double[] { 0, 1 };
shading.entry("Domain", domain);
// the function subdictionary (necessarily by reference, because
// the type 4 function is a stream)
String functionRef = e.name + "Function";
shading.entry("Function", pdf.ref(functionRef));
shading.entry("Extend", new boolean[] { true, true });
pattern.close(shading);
pdf.close(pattern);
// get color components and generate the functions
float[] col0 = new float[3];
gp.getColor1().getRGBColorComponents(col0);
double c0[] = new double[] { col0[0], col0[1], col0[2] };
float[] col1 = new float[3];
gp.getColor2().getRGBColorComponents(col1);
double c1[] = new double[] { col1[0], col1[1], col1[2] };
if (gp.isCyclic()) {
// FIXME: cyclic function only eveluated between 0 and 1 for short
// range, not repetitive.
// addCyclicFunction(functionRef, c0, c1, domain);
addLinearFunction(functionRef, c0, c1, domain);
} else {
addLinearFunction(functionRef, c0, c1, domain);
}
}
/** Writes a type 2 (exponential) function (exponent N=1) to the pdf file. */
private void addLinearFunction(String functionRef, double[] c0,
double[] c1, double[] dom) throws IOException {
PDFDictionary function = pdf.openDictionary(functionRef);
function.entry("FunctionType", 2);
function.entry("Domain", dom);
function.entry("Range", new double[] { 0., 1., 0., 1., 0., 1. });
function.entry("C0", c0);
function.entry("C1", c1);
function.entry("N", 1);
pdf.close(function);
}
/**
* Writes a type 4 (PostScript calculator) function that describes a
* triangle function to the pdf file.
*/
// NOT USED
protected void addCyclicFunction(String functionRef, double[] c0,
double[] c1, double[] dom) throws IOException {
// PDFStream function = pdf.openStream(functionRef, new String[] {
// "Flate", "ASCII85" });
PDFStream function = pdf.openStream(functionRef);
function.entry("FunctionType", 4);
// function.entry("Domain", new double[] {-1000.0, 1000.0} );
function.entry("Domain", dom);
function.entry("Range", new double[] { 0., 1., 0., 1., 0., 1. });
function.println("{");
/*
* function.println("2 mod"); function.println("1 sub");
* function.println("abs"); function.println("1 exch sub");
*/
// function.println("sin");
for (int i = 0; i < 3; i++) {
if (i < 2)
function.println("dup");
function.println((c1[i] - c0[i]) + " mul");
function.println(c0[i] + " add");
if (i < 2)
function.println("exch");
}
// function.println("pop 0.8 0.0 0.0");
function.println("}");
pdf.close(function);
}
private void addTexturePaint(Entry e) throws IOException {
TexturePaint tp = (TexturePaint) e.paint;
// open the pattern stream that also holds the inlined picture
PDFStream pattern = pdf.openStream(e.name, null); // image is encoded.
pattern.entry("Type", pdf.name("Pattern"));
pattern.entry("PatternType", 1);
pattern.entry("PaintType", 1);
BufferedImage image = tp.getImage();
pattern.entry("TilingType", 1);
double width = tp.getAnchorRect().getWidth();
double height = tp.getAnchorRect().getHeight();
double offsX = tp.getAnchorRect().getX();
double offsY = tp.getAnchorRect().getY();
pattern.entry("BBox", new double[] { 0, 0, width, height });
pattern.entry("XStep", width);
pattern.entry("YStep", height);
PDFDictionary resources = pattern.openDictionary("Resources");
resources.entry("ProcSet", new Object[] { pdf.name("PDF"),
pdf.name("ImageC") });
pattern.close(resources);
setMatrix(pattern, e, offsX, offsY);
// scale the tiling image to the correct size
pattern.matrix(width, 0, 0, -height, 0, height);
pattern.inlineImage(image, null, e.writeAs);
pdf.close(pattern);
}
/**
* Sets both the page (flip) matrix and the actual transformation matrix
* plus a translation offset (which may of course be be 0).
*/
private void setMatrix(PDFDictionary dict, Entry e, double translX,
double translY) throws IOException {
// concatenate the page matrix and the actual transformation matrix
AffineTransform trafo = new AffineTransform(pageMatrix);
trafo.concatenate(e.trafo);
trafo.translate(translX, translY);
double[] matrix = new double[6];
trafo.getMatrix(matrix);
dict.entry("Matrix", new double[] { matrix[0], matrix[1], matrix[2],
matrix[3], matrix[4], matrix[5] });
}
}