/* * 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; 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.Stack; /** * A base class which holds information about the current rendering state. */ public abstract class AbstractState implements Cloneable, Serializable { /** current state data */ private AbstractData currentData = null; /** the state stack */ private StateStack stateStack = null; /** * 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 AbstractState instantiateState(); /** * Returns the currently valid state * * @return the currently valid state */ public AbstractData getData() { if (currentData == null) { currentData = instantiateData(); } return currentData; } /** * 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 = getStateStack().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 (getStateStack().isEmpty()) { return null; } else { AbstractData baseData = (AbstractData)getStateStack().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. */ public void resetTransform() { getData().resetTransform(); } /** * 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() { AbstractData copy = (AbstractData)getData().clone(); getStateStack().push(copy); } /** * 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 AbstractData pop() { if (!getStateStack().isEmpty()) { this.currentData = (AbstractData)getStateStack().pop(); return this.currentData; } else { return null; } } /** * Clears the state stack */ public void clear() { getStateStack().clear(); currentData = null; } /** * Return the state stack * * @return the state stack */ protected Stack/**/ getStateStack() { if (stateStack == null) { stateStack = new StateStack(); } return stateStack; } /** {@inheritDoc} */ public Object clone() { AbstractState state = instantiateState(); state.stateStack = new StateStack(this.stateStack); state.currentData = (AbstractData)this.currentData.clone(); return state; } /** {@inheritDoc} */ public String toString() { return ", stateStack=" + stateStack + ", currentData=" + currentData; } /** * A base state data holding object */ public abstract class AbstractData implements Cloneable, Serializable { private static final long serialVersionUID = 5208418041189828624L; /** The current color */ private Color color = null; /** The current background color */ private Color backColor = null; /** The current font name */ private String fontName = null; /** The current font size */ private int fontSize = 0; /** The current line width */ private float lineWidth = 0; /** The dash array for the current basic stroke (line type) */ private float[] dashArray = null; /** The current transform */ private AffineTransform transform = null; /** * 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; } /** * Resets the current AffineTransform. */ public void resetTransform() { transform = getBaseTransform(); // 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 = instantiateData(); 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; } } /** * No copy constructor for java.util.Stack so extended and implemented one. */ private 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); } } }