blob: 5e77da0d2145c149a6e21f648d3b1d7f1deb64e9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.pdf;
import java.util.Iterator;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
/**
* This keeps information about the current state when writing to pdf.
* It allows for creating new graphics states with the q operator.
* This class is only used to store the information about the state
* the caller needs to handle the actual pdf operators.
*
* When setting the state for pdf there are three possible ways of
* handling the situation.
* The values can be set to override previous or default values.
* A new state can be added and then the values set.
* The current state can be popped and values will return to a
* previous state then the necessary values can be overridden.
* The current transform behaves differently to other values as the
* matrix is combined with the current resolved value.
* It is impossible to optimise the result without analysing the all
* the possible combinations after completing.
*/
public class PDFState extends org.apache.fop.render.AbstractState {
/**
* PDF State for storing graphics state.
*/
public PDFState() {
}
// /**
// * Push the current state onto the stack.
// * This call should be used when the q operator is used
// * so that the state is known when popped.
// */
// public void push() {
// PDFData copy;
// try {
// copy = (PDFData)getData().clone();
// getData().resetTransform();
// } catch (CloneNotSupportedException e) {
// throw new RuntimeException(e.getMessage());
// }
// stateStack.add(copy);
// }
//
// /**
// * @return the currently valid state
// */
// public PDFData getData() {
// return data;
// }
//
// /**
// * Pop the state from the stack and set current values to popped state.
// * This should be called when a Q operator is used so
// * the state is restored to the correct values.
// * @return the restored state, null if the stack is empty
// */
// public PDFData pop() {
// if (getStackLevel() > 0) {
// PDFData popped = (PDFData)stateStack.remove(stateStack.size() - 1);
//
// data = popped;
// return popped;
// } else {
// return null;
// }
// }
//
// /**
// * Get the current stack level.
// *
// * @return the current stack level
// */
// public int getStackLevel() {
// return stateStack.size();
// }
/**
* Restore the state to a particular level.
* this can be used to restore to a known level without making
* multiple pop calls.
*
* @param stack the level to restore to
*/
/*
public void restoreLevel(int stack) {
int pos = stack;
while (stateStack.size() > pos + 1) {
stateStack.remove(stateStack.size() - 1);
}
if (stateStack.size() > pos) {
pop();
}
}*/
/**
* Set the current line dash.
* Check if setting the line dash to the given values
* will make a change and then set the state to the new values.
*
* @param array the line dash array
* @param offset the line dash start offset
* @return true if the line dash has changed
*/
/*
public boolean setLineDash(int[] array, int offset) {
return false;
}*/
// /**
// * Set the current line width.
// * @param width the line width in points
// * @return true if the line width has changed
// */
// public boolean setLineWidth(float width) {
// if (getData().lineWidth != width) {
// getData().lineWidth = width;
// return true;
// } else {
// return false;
// }
// }
//
// /**
// * Set the current color.
// * Check if the new color is a change and then set the current color.
// *
// * @param col the color to set
// * @return true if the color has changed
// */
// public boolean setColor(Color col) {
// if (!col.equals(getData().color)) {
// getData().color = col;
// return true;
// } else {
// return false;
// }
// }
//
// /**
// * Set the current background color.
// * Check if the background color will change and then set the new color.
// *
// * @param col the new background color
// * @return true if the background color has changed
// */
// public boolean setBackColor(Color col) {
// if (!col.equals(getData().backcolor)) {
// getData().backcolor = col;
// return true;
// } else {
// return false;
// }
// }
/**
* Set the current paint.
* This checks if the paint will change and then sets the current paint.
*
* @param p the new paint
* @return true if the new paint changes the current paint
*/
public boolean setPaint(Paint p) {
Paint paint = ((PDFData)getData()).paint;
if (paint == null) {
if (p != null) {
((PDFData)getData()).paint = p;
return true;
}
} else if (!paint.equals(p)) {
((PDFData)getData()).paint = p;
return true;
}
return false;
}
/**
* Check if the clip will change the current state.
* A clip is assumed to be used in a situation where it will add
* to any clip in the current or parent states.
* A clip cannot be cleared, this can only be achieved by going to
* a parent level with the correct clip.
* If the clip is different then it may start a new state so that
* it can return to the previous clip.
*
* @param cl the clip shape to check
* @return true if the clip will change the current clip.
*/
public boolean checkClip(Shape cl) {
Shape clip = ((PDFData)getData()).clip;
if (clip == null) {
if (cl != null) {
return true;
}
} else if (!new Area(clip).equals(new Area(cl))) {
return true;
}
//TODO check for clips that are larger than the current
return false;
}
/**
* Set the current clip.
* This either sets a new clip or sets the clip to the intersect of
* the old clip and the new clip.
*
* @param cl the new clip in the current state
*/
public void setClip(Shape cl) {
Shape clip = ((PDFData)getData()).clip;
if (clip != null) {
Area newClip = new Area(clip);
newClip.intersect(new Area(cl));
((PDFData)getData()).clip = new GeneralPath(newClip);
} else {
((PDFData)getData()).clip = cl;
}
}
// /**
// * Set a new transform.
// * This transform is appended to the transform of
// * the current graphic state.
// *
// * @param tf the transform to concatonate to the current level transform
// * @deprecated This method name is misleading. Use concatenate(AffineTransform) instead!
// */
// public void setTransform(AffineTransform tf) {
// concatenate(tf);
// }
//
// /**
// * Concatenates the given AffineTransform to the current one.
// * @param tf the transform to concatenate to the current level transform
// */
// public void concatenate(AffineTransform tf) {
// getData().concatenate(tf);
// }
// /**
// * Get the current transform.
// * This gets the combination of all transforms in the
// * current state.
// *
// * @return the calculate combined transform for the current state
// */
// public AffineTransform getTransform() {
// AffineTransform tf;
// AffineTransform at = new AffineTransform();
// for (Iterator iter = stateStack.iterator(); iter.hasNext();) {
// PDFData d = (PDFData)iter.next();
// tf = d.getTransform();
// at.concatenate(tf);
// }
// at.concatenate(getData().getTransform());
// return at;
// }
/**
* Get the graphics state.
* This gets the combination of all graphic states for
* the current context.
* This is the graphic state set with the gs operator not
* the other graphic state changes.
*
* @return the calculated ExtGState in the current context
*/
public PDFGState getGState() {
PDFGState defaultState = PDFGState.DEFAULT;
PDFGState state;
PDFGState newState = new PDFGState();
newState.addValues(defaultState);
for (Iterator iter = getStateStack().iterator(); iter.hasNext();) {
PDFData d = (PDFData)iter.next();
state = d.gstate;
if (state != null) {
newState.addValues(state);
}
}
if (((PDFData)getData()).gstate != null) {
newState.addValues(((PDFData)getData()).gstate);
}
return newState;
}
private class PDFData extends org.apache.fop.render.AbstractState.AbstractData {
private static final long serialVersionUID = 3527950647293177764L;
private Paint paint = null;
private Paint backPaint = null;
private int lineCap = 0;
private int lineJoin = 0;
private float miterLimit = 0;
private boolean text = false;
private int dashOffset = 0;
private Shape clip = null;
private PDFGState gstate = null;
/** {@inheritDoc} */
public Object clone() throws CloneNotSupportedException {
PDFData obj = (PDFData)super.clone();
obj.paint = this.paint;
obj.backPaint = this.paint;
obj.lineCap = this.lineCap;
obj.lineJoin = this.lineJoin;
obj.miterLimit = this.miterLimit;
obj.text = this.text;
obj.dashOffset = this.dashOffset;
obj.clip = this.clip;
obj.gstate = this.gstate;
return obj;
}
}
/** {@inheritDoc} */
protected AbstractData instantiateData() {
return new PDFData();
}
}