blob: 4fb6b173c12cb5c9de04d14f2e5b7738f2b5fde5 [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.util;
import java.awt.Color;
import java.awt.geom.AffineTransform;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
/**
* A base class which holds information about the current painting state.
*/
public abstract class AbstractPaintingState implements Cloneable, Serializable {
private static final long serialVersionUID = 5998356138437094188L;
/** current state data */
private AbstractData data = null;
/** the state stack */
private StateStack/*<AbstractData>*/ stateStack = new StateStack/*<AbstractData>*/();
/**
* Instantiates a new state data object
*
* @return a new state data object
*/
protected abstract AbstractData instantiateData();
/**
* Instantiates a new state object
*
* @return a new state object
*/
protected abstract AbstractPaintingState instantiate();
/**
* Returns the currently valid state
*
* @return the currently valid state
*/
public AbstractData getData() {
if (data == null) {
data = instantiateData();
}
return data;
}
/**
* 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;
}
return false;
}
/**
* Get the color.
*
* @return the color
*/
public Color getColor() {
if (getData().color == null) {
getData().color = Color.black;
}
return getData().color;
}
/**
* Get the background color.
*
* @return the background color
*/
public Color getBackColor() {
if (getData().backColor == null) {
getData().backColor = Color.white;
}
return getData().backColor;
}
/**
* Set the current background color.
* Check if the new background color is a change and then set the current background color.
*
* @param col the background color to set
* @return true if the color has changed
*/
public boolean setBackColor(Color col) {
if (!col.equals(getData().backColor)) {
getData().backColor = col;
return true;
}
return false;
}
/**
* Set the current font name
*
* @param internalFontName the internal font name
* @return true if the font name has changed
*/
public boolean setFontName(String internalFontName) {
if (!internalFontName.equals(getData().fontName)) {
getData().fontName = internalFontName;
return true;
}
return false;
}
/**
* Gets the current font name
*
* @return the current font name
*/
public String getFontName() {
return getData().fontName;
}
/**
* Gets the current font size
*
* @return the current font size
*/
public int getFontSize() {
return getData().fontSize;
}
/**
* Set the current font size.
* Check if the font size is a change and then set the current font size.
*
* @param size the font size to set
* @return true if the font size has changed
*/
public boolean setFontSize(int size) {
if (size != getData().fontSize) {
getData().fontSize = size;
return true;
}
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;
}
return false;
}
/**
* Returns the current line width
*
* @return the current line width
*/
public float getLineWidth() {
return getData().lineWidth;
}
/**
* Sets the dash array (line type) for the current basic stroke
*
* @param dash the line dash array
* @return true if the dash array has changed
*/
public boolean setDashArray(float[] dash) {
if (!Arrays.equals(dash, getData().dashArray)) {
getData().dashArray = dash;
return true;
}
return false;
}
/**
* 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 at = new AffineTransform();
for (Iterator iter = stateStack.iterator(); iter.hasNext();) {
AbstractData data = (AbstractData)iter.next();
AffineTransform stackTrans = data.getTransform();
at.concatenate(stackTrans);
}
AffineTransform currentTrans = getData().getTransform();
at.concatenate(currentTrans);
return at;
}
/**
* Check the current transform.
* The transform for the current state is the combination of all
* transforms in the current state. The parameter is compared
* against this current transform.
*
* @param tf the transform the check against
* @return true if the new transform is different then the current transform
*/
public boolean checkTransform(AffineTransform tf) {
return !tf.equals(getData().getTransform());
}
/**
* Get a copy of the base transform for the page. Used to translate
* IPP/BPP values into X,Y positions when positioning is "fixed".
*
* @return the base transform, or null if the state stack is empty
*/
public AffineTransform getBaseTransform() {
if (stateStack.isEmpty()) {
return null;
} else {
AbstractData baseData = (AbstractData)stateStack.get(0);
return (AffineTransform) baseData.getTransform().clone();
}
}
/**
* Concatenates the given AffineTransform to the current one.
*
* @param at the transform to concatenate to the current level transform
*/
public void concatenate(AffineTransform at) {
getData().concatenate(at);
}
/**
* Resets the current AffineTransform to the Base AffineTransform.
*/
public void resetTransform() {
getData().setTransform(getBaseTransform());
}
/**
* Clears the current AffineTransform to the Identity AffineTransform
*/
public void clearTransform() {
getData().clearTransform();
}
/**
* Save the current painting state.
* This pushes the current painting 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 save() {
AbstractData copy = (AbstractData)getData().clone();
stateStack.push(copy);
}
/**
* Restore the current painting state.
* This pops the painting state from the stack and sets current values to popped state.
*
* @return the restored state, null if the stack is empty
*/
public AbstractData restore() {
if (!stateStack.isEmpty()) {
setData((AbstractData)stateStack.pop());
return this.data;
} else {
return null;
}
}
/**
* Save all painting state data.
* This pushes all painting state data in the given list to the stack
*
* @param dataList a state data list
*/
public void saveAll(List/*<AbstractData>*/ dataList) {
Iterator it = dataList.iterator();
while (it.hasNext()) {
// save current data on stack
save();
setData((AbstractData)it.next());
}
}
/**
* Restore all painting state data.
* This pops all painting state data from the stack
*
* @return a list of state data popped from the stack
*/
public List/*<AbstractData>*/ restoreAll() {
List/*<AbstractData>*/ dataList = new java.util.ArrayList/*<AbstractData>*/();
AbstractData data;
while (true) {
data = getData();
if (restore() == null) {
break;
}
// insert because of stack-popping
dataList.add(0, data);
}
return dataList;
}
/**
* Sets the current state data
*
* @param currentData state data
*/
protected void setData(AbstractData data) {
this.data = data;
}
/**
* Clears the state stack
*/
public void clear() {
stateStack.clear();
setData(null);
}
/**
* Return the state stack
*
* @return the state stack
*/
protected Stack/*<AbstractData>*/ getStateStack() {
return this.stateStack;
}
/** {@inheritDoc} */
public Object clone() {
AbstractPaintingState state = instantiate();
state.stateStack = new StateStack(this.stateStack);
state.data = (AbstractData)this.data.clone();
return state;
}
/** {@inheritDoc} */
public String toString() {
return ", stateStack=" + stateStack
+ ", currentData=" + data;
}
/**
* A stack implementation which holds state objects
*/
public class StateStack extends java.util.Stack {
private static final long serialVersionUID = 4897178211223823041L;
/**
* Default constructor
*/
public StateStack() {
super();
}
/**
* Copy constructor
*
* @param c initial contents of stack
*/
public StateStack(Collection c) {
elementCount = c.size();
// 10% for growth
elementData = new Object[
(int)Math.min((elementCount * 110L) / 100, Integer.MAX_VALUE)];
c.toArray(elementData);
}
}
/**
* A base painting state data holding object
*/
public abstract class AbstractData implements Cloneable, Serializable {
private static final long serialVersionUID = 5208418041189828624L;
/** The current color */
protected Color color = null;
/** The current background color */
protected Color backColor = null;
/** The current font name */
protected String fontName = null;
/** The current font size */
protected int fontSize = 0;
/** The current line width */
protected float lineWidth = 0;
/** The dash array for the current basic stroke (line type) */
protected float[] dashArray = null;
/** The current transform */
protected AffineTransform transform = null;
/**
* Returns a newly create data object
*
* @return a new data object
*/
protected abstract AbstractData instantiate();
/**
* Concatenate the given AffineTransform with the current thus creating
* a new viewport. Note that all concatenation operations are logged
* so they can be replayed if necessary (ex. for block-containers with
* "fixed" positioning.
*
* @param at Transformation to perform
*/
public void concatenate(AffineTransform at) {
getTransform().concatenate(at);
}
/**
* Get the current AffineTransform.
*
* @return the current transform
*/
public AffineTransform getTransform() {
if (transform == null) {
transform = new AffineTransform();
}
return transform;
}
/**
* Sets the current AffineTransform.
*/
public void setTransform(AffineTransform baseTransform) {
this.transform = baseTransform;
}
/**
* Resets the current AffineTransform.
*/
public void clearTransform() {
transform = new AffineTransform();
}
/**
* Returns the derived rotation from the current transform
*
* @return the derived rotation from the current transform
*/
public int getDerivedRotation() {
AffineTransform at = getTransform();
double sx = at.getScaleX();
double sy = at.getScaleY();
double shx = at.getShearX();
double shy = at.getShearY();
int rotation = 0;
if (sx == 0 && sy == 0 && shx > 0 && shy < 0) {
rotation = 270;
} else if (sx < 0 && sy < 0 && shx == 0 && shy == 0) {
rotation = 180;
} else if (sx == 0 && sy == 0 && shx < 0 && shy > 0) {
rotation = 90;
} else {
rotation = 0;
}
return rotation;
}
/** {@inheritDoc} */
public Object clone() {
AbstractData data = instantiate();
data.color = this.color;
data.backColor = this.backColor;
data.fontName = this.fontName;
data.fontSize = this.fontSize;
data.lineWidth = this.lineWidth;
data.dashArray = this.dashArray;
data.transform = new AffineTransform(this.transform);
return data;
}
/** {@inheritDoc} */
public String toString() {
return "color=" + color
+ ", backColor=" + backColor
+ ", fontName=" + fontName
+ ", fontSize=" + fontSize
+ ", lineWidth=" + lineWidth
+ ", dashArray=" + dashArray
+ ", transform=" + transform;
}
}
}