// Copyright 2001-2006, FreeHEP.
package org.freehep.postscript;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.color.ColorSpace;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.awt.image.RenderedImage;
/**
* Graphics State Object for PostScript Processor,
* as defined in 4.2 Graphics State.
*
* Some of the state is kept in Graphics2D, some of it in BasicStroke
*
* @author Mark Donszelmann
* @version $Id: PSGState.java 10178 2006-12-08 09:03:07Z duns $
*/
public class PSGState extends PSComposite {
private PSDevice device;
private AffineTransform ctm;
private GeneralPath path;
private GeneralPath clipPath;
private float lineWidth;
private int cap;
private int join;
private float miterLimit;
private float[] dash;
private float dashPhase;
private PSDictionary font;
private double flat;
private PSPackedArray transfer;
private ColorSpace colorSpace;
private String colorSpaceName;
private PSPackedArray blackGeneration;
private PSPackedArray underColorRemoval;
private Rectangle2D boundingBox;
private PSGState() {
super("gstate", true);
}
public PSGState(PSDevice device) {
super("gstate", true);
this.device = device;
font = new PSFontDictionary(device.getGraphics().getFont(), "STDLatin");
flat = 0.5; // FIXME: is device dependent
transfer = new PSPackedArray(new PSObject[0]);
transfer.setExecutable();
blackGeneration = new PSPackedArray(0);
blackGeneration.setExecutable();
underColorRemoval = new PSPackedArray(0);
underColorRemoval.setExecutable();
boundingBox = null;
initGraphics();
erasePage();
}
public void initGraphics() {
ctm = new AffineTransform();
newPath();
initClip();
setColorSpace("DeviceGray");
setColor(new float[] { 0.0f } );
lineWidth = 1.0f;
cap = BasicStroke.CAP_BUTT;
join = BasicStroke.JOIN_MITER;
miterLimit = 10.0f;
dash = null;
dashPhase = 0.0f;
setStroke();
}
public BufferedImage convertToImage(int width, int height) {
return device.convertToImage(width, height);
}
public void erasePage() {
String oldSpace = colorSpace();
float[] oldColor = color();
setColorSpace("DeviceGray");
setColor(new float[] { 1.0f } );
device.erasePage();
setColorSpace(oldSpace);
setColor(oldColor);
}
public void copyInto(PSGState copy) {
copy.device = device;
copy.ctm = (AffineTransform)ctm.clone();
copy.path = (GeneralPath)path.clone();
copy.clipPath = (clipPath == null) ? null : (GeneralPath)clipPath.clone();
copy.lineWidth = lineWidth;
copy.cap = cap;
copy.join = join;
copy.miterLimit = miterLimit;
if (dash == null) {
copy.dash = null;
} else {
copy.dash = new float[dash.length];
System.arraycopy(dash, 0, copy.dash, 0, dash.length);
}
copy.dashPhase = dashPhase;
copy.font = new PSDictionary();
font.copyInto(copy.font);
copy.flat = flat;
copy.transfer = (PSPackedArray)transfer.copy();
copy.colorSpace = colorSpace;
copy.colorSpaceName = colorSpaceName;
copy.blackGeneration = (PSPackedArray)blackGeneration.copy();
copy.underColorRemoval = (PSPackedArray)underColorRemoval.copy();
copy.boundingBox = boundingBox;
}
public PSObject copy() {
PSGState copy = new PSGState();
copyInto(copy);
return copy;
}
private void setStroke() {
BasicStroke stroke = new BasicStroke(lineWidth,
cap, join, miterLimit,
dash, dashPhase);
device.getGraphics().setStroke(stroke);
}
public void fill() {
fill(path);
}
public void stroke() {
stroke(path);
}
public void strokePath() {
path = new GeneralPath(device.getGraphics().getStroke().createStrokedShape(path));
}
public Shape charPath(int cc, float x, float y, boolean strokePath) {
Shape path;
// FIXME: works only for Java type 1 fonts
switch (font.getInteger("FontType")) {
case 1:
PSName name = font.getPackedArray("Encoding").getName(cc);
PSObject obj = font.getDictionary("CharStrings").get(name);
// FIXME: obj may be PSPackedArray
GlyphVector glyph = ((PSJavaGlyph)obj).getGlyph();
path = glyph.getOutline(x, y);
break;
default:
System.out.println(getClass()+": CharPath failed for fonttype: "+font.getInteger("FontType"));
return null;
// FontRenderContext fontRenderContext = new FontRenderContext(null, false, true);
// GlyphVector boxGlyph = font.createGlyphVector(fontRenderContext, '\u25a1');
// return boxGlyph.getOutline(x, y);
}
if (strokePath) {
path = new GeneralPath(device.getGraphics().getStroke().createStrokedShape(path));
}
return path;
}
public void fill(Shape s) {
Graphics2D g = (Graphics2D)device.getGraphics().create();
g.transform(ctm);
g.fill(s);
g.dispose();
}
public void stroke(Shape s) {
stroke(s, null);
}
public void stroke(Shape s, AffineTransform m) {
AffineTransform at = (AffineTransform)ctm.clone();
GeneralPath p = new GeneralPath(s);
if (m != null) {
// apply AT-1 to s
try {
AffineTransform pt = m.createInverse();
p.transform(pt);
} catch (NoninvertibleTransformException e) {
System.err.println("Internal GState Error");
}
// add new transform
at.concatenate(m);
}
Graphics2D g = (Graphics2D)device.getGraphics().create();
g.transform(at);
g.draw(p);
g.dispose();
}
public void show(GlyphVector gv, float x, float y) {
Graphics2D g = (Graphics2D)device.getGraphics().create();
g.transform(ctm);
g.drawGlyphVector(gv, x, y);
g.dispose();
}
public void image(RenderedImage image, AffineTransform at) {
Graphics2D g = (Graphics2D)device.getGraphics().create();
g.transform(ctm);
// map to unit space!
try {
at = at.createInverse();
} catch (NoninvertibleTransformException e) {
System.err.println("Internal GState Error");
}
g.drawRenderedImage(image, at);
g.dispose();
}
public Point2D position() {
return (path == null) ? null : path.getCurrentPoint();
}
public void translate(double tx, double ty) {
AffineTransform at = AffineTransform.getTranslateInstance(-tx, -ty);
path.transform(at);
ctm.translate(tx, ty);
}
public void rotate(double angle) {
AffineTransform at = AffineTransform.getRotateInstance(-angle);
path.transform(at);
ctm.rotate(angle);
}
public void scale(double sx, double sy) {
AffineTransform at = AffineTransform.getScaleInstance(1/sx, 1/sy);
path.transform(at);
ctm.scale(sx, sy);
}
public void setTransform(AffineTransform at) {
// apply AT-1 * CTM to path
try {
AffineTransform pt = at.createInverse();
pt.concatenate(ctm);
path.transform(pt);
} catch (NoninvertibleTransformException e) {
System.err.println("Internal GState Error");
}
// set new transform
ctm.setTransform(at);
}
public void transform(AffineTransform at) {
// apply AT-1 to path
try {
AffineTransform pt = at.createInverse();
path.transform(pt);
} catch (NoninvertibleTransformException e) {
System.err.println("Internal GState Error");
}
// add new transform
ctm.concatenate(at);
}
public AffineTransform getTransform() {
return (AffineTransform)ctm.clone();
}
public GeneralPath path() {
return path;
}
public GeneralPath newPath() {
path = new GeneralPath();
path.transform(ctm);
return path;
}
public void initClip() {
device.getGraphics().setClip(null);
try {
AffineTransform inverse = ctm.createInverse();
clipPath = new GeneralPath(new Rectangle(0, 0, (int)device.getWidth(), (int)device.getHeight()));
clipPath.transform(inverse);
} catch (NoninvertibleTransformException e) {
System.out.println("Internal error in GState");
}
}
public void clip(Shape p) {
clipPath = new GeneralPath(p);
Shape clip = clipPath.createTransformedShape(ctm);
device.getGraphics().setClip(clip);
}
public void clipPath() {
path = (GeneralPath)clipPath.clone();
path.transform(ctm);
}
public float lineWidth() {
return lineWidth;
}
public void setLineWidth(double width) {
lineWidth = (float)width;
setStroke();
}
public float dashPhase() {
return dashPhase;
}
public float[] dash() {
return dash;
}
public void setDash(float[] dash, float dashPhase) {
if (dash.length == 0) {
this.dash = null;
this.dashPhase = 0;
} else {
this.dash = dash;
this.dashPhase = dashPhase;
}
setStroke();
}
public void setLineCap(int cap) {
this.cap = cap;
setStroke();
}
public int lineCap() {
return cap;
}
public void setLineJoin(int join) {
this.join = join;
setStroke();
}
public int lineJoin() {
return join;
}
public void setMiterLimit(float miterLimit) {
this.miterLimit = miterLimit;
setStroke();
}
public float miterLimit() {
return miterLimit;
}
public void setStrokeAdjust(boolean adjust) {
device.getGraphics().setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
}
public boolean strokeAdjust() {
Object value = device.getGraphics().getRenderingHint(RenderingHints.KEY_STROKE_CONTROL);
return (value == RenderingHints.VALUE_STROKE_PURE);
}
public boolean execute(OperandStack os) {
os.push(this);
return true;
}
public void setFont(PSDictionary font) {
this.font = font;
}
public PSDictionary font() {
return font;
}
public void setFlat(double f) {
flat = f;
}
public double flat() {
return flat;
}
public void flattenPath() {
PathIterator iterator = path.getPathIterator(new AffineTransform(), flat);
newPath();
path.append(iterator, true);
}
public void setTransfer(PSPackedArray proc) {
// FREEHEP-164: transfer is ignored
transfer = proc;
}
public PSPackedArray transfer() {
return transfer;
}
private static ColorSpace toColorSpace(String name) {
if (name.equals("DeviceGray")) {
return ColorSpace.getInstance(ColorSpace.CS_GRAY);
} else if (name.equals("DeviceRGB")) {
return ColorSpace.getInstance(ColorSpace.CS_sRGB);
} else if (name.equals("DeviceCMYK")) {
return ColorSpace.getInstance(ColorSpace.CS_sRGB);
}
return null;
}
public boolean setColorSpace(String name) {
return setColorSpace(name, null);
}
public boolean setColorSpace(String name, Object[] params) {
ColorSpace space = toColorSpace(name);
if (space == null) {
if (name.equals("Pattern")) {
if (params == null) {
space = new Pattern(toColorSpace("DeviceRGB"));
} else {
space = new Pattern(toColorSpace(((PSName)params[1]).cvs()));
}
}
}
if (space != null) {
colorSpaceName = name;
colorSpace = space;
return true;
}
return false;
}
public int getNumberOfColorSpaceComponents() {
if (colorSpaceName.equals("DeviceGray")) {
return 1;
} else if (colorSpaceName.equals("DeviceRGB")) {
return 3;
} else if (colorSpaceName.equals("DeviceCMYK")) {
return 4;
} else if (colorSpaceName.equals("Pattern")) {
return colorSpace.getNumComponents();
}
return 0;
}
public String colorSpace() {
return colorSpaceName;
}
private static String toColorSpaceName(ColorSpace space) {
switch (space.getType()) {
// FIXME: does not work for CMYK
case ColorSpace.CS_GRAY: return "DeviceGray";
case ColorSpace.CS_sRGB: return "DeviceRGB";
default: return "Unknown";
}
}
public void setColor(Paint paint) {
setColorSpace("Pattern");
setColor(paint, null);
}
public void setColor(Paint paint, Object[] params) {
if (params != null) {
FixedTexturePaint ftp = (FixedTexturePaint)paint;
BufferedImage image = ftp.getImage();
ColorConvertOp convert = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null);
// SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel)image.getSampleModel();
// sm = sm.createCompatibleSampleModel(sm.getWidth(), sm.getHeight());
// ColorModel cm = new ComponentColorModel(colorSpace, );
// BufferedImage dstImage = convert.createCompatibleDestImage(image, null);
System.out.println(image.getSampleModel().getNumBands());
BufferedImage filteredImage = convert.filter(image, null);
// FIXME: copy & set this into paint
}
device.getGraphics().setPaint(paint);
}
public void setColor(float[] color) {
float[] rgb = toRGB(color, colorSpaceName);
if (rgb != null) {
device.getGraphics().setPaint(new Color(rgb[0], rgb[1], rgb[2]));
} else {
System.err.println("Unknown colorspace: "+colorSpaceName);
}
}
public static float[] toRGB(float[] color, String space) {
if (space.equals("DeviceGray")) {
float c = Math.max(0.0f, Math.min(1.0f, color[0]));
return new float[] { c, c, c };
} else if (space.equals("DeviceRGB")) {
float r = Math.max(0.0f, Math.min(1.0f, color[0]));
float g = Math.max(0.0f, Math.min(1.0f, color[1]));
float b = Math.max(0.0f, Math.min(1.0f, color[2]));
return new float[] { r, g, b };
} else if (space.equals("DeviceCMYK")) {
float r = Math.max(0.0f, 1.0f - Math.min(1.0f, color[0] + color[3] ));
float g = Math.max(0.0f, 1.0f - Math.min(1.0f, color[1] + color[3] ));
float b = Math.max(0.0f, 1.0f - Math.min(1.0f, color[2] + color[3] ));
return new float[] { r, g, b };
}
return null;
}
public float[] color() {
return color(colorSpaceName);
}
public float[] color(String space) {
float[] rgb;
rgb = device.getGraphics().getColor().getColorComponents(null);
if (space.equals("Pattern")) {
rgb = colorSpace.toRGB(rgb);
space = toColorSpaceName(colorSpace);
}
rgb = toColor(space, rgb);
if (rgb == null) {
System.err.println("Unknown colorspace: "+colorSpaceName);
}
return rgb;
}
public static float[] toColor(String space, float[] rgb) {
float[] color;
if (space.equals("DeviceGray")) {
color = new float[1];
color[0] = (float)(0.30*rgb[0] + 0.59*rgb[1] + 0.11*rgb[2]);
return color;
} else if (space.equals("DeviceRGB")) {
return rgb;
} else if (space.equals("DeviceCMYK")) {
float c = 1.0f - rgb[0];
float m = 1.0f - rgb[1];
float y = 1.0f - rgb[2];
float k = Math.min(c, Math.min(m, y));
color = new float[4];
color[0] = Math.min(1.0f, Math.max(0.0f, c));
color[1] = Math.min(1.0f, Math.max(0.0f, m));
color[2] = Math.min(1.0f, Math.max(0.0f, y));
color[3] = Math.min(1.0f, Math.max(0.0f, k));
return color;
} else {
return null;
}
}
public void setBlackGeneration(PSPackedArray p) {
blackGeneration = p;
}
public PSPackedArray blackGeneration() {
return blackGeneration;
}
public void setUnderColorRemoval(PSPackedArray p) {
underColorRemoval = p;
}
public PSPackedArray underColorRemoval() {
return underColorRemoval;
}
public void setBoundingBox(Rectangle2D bb) {
if (boundingBox != null) {
boundingBox.add(bb);
} else {
boundingBox = bb;
}
}
public Rectangle2D boundingBox() {
return boundingBox;
}
public String getType() {
return "gstatetype";
}
// FIXME: maybe not correct
public int hashCode() {
return device.hashCode();
}
// FIXME: there may be no equal
public boolean equals(Object o) {
if (o instanceof PSGState) {
return (device == ((PSGState)o).device);
}
return false;
}
// FIXME: not implemented
public Object clone() {
return null;
}
public String cvs() {
return toString();
}
public String toString() {
return "--"+name+"--";
}
}