/* * 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.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; /** 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) { Color other = getData().color; if (!org.apache.xmlgraphics.java2d.color.ColorUtil.isSameColor(col, other)) { 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) { Color other = getData().backColor; if (!org.apache.xmlgraphics.java2d.color.ColorUtil.isSameColor(col, other)) { 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 (AbstractData data : stateStack) { 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 = 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(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) { for (AbstractData data : dataList) { // save current data on stack save(); setData(data); } } /** * 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 data the 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} */ @Override public Object clone() { AbstractPaintingState state = instantiate(); state.stateStack = new StateStack<AbstractData>(this.stateStack); if (this.data != null) { state.data = (AbstractData)this.data.clone(); } return state; } /** {@inheritDoc} */ @Override public String toString() { return ", stateStack=" + stateStack + ", currentData=" + data; } /** * A stack implementation which holds state objects */ // @SuppressFBWarnings("SE_INNER_CLASS") public class StateStack<E> extends java.util.Stack<E> { private static final long serialVersionUID = 4897178211223823041L; /** * Default constructor */ public StateStack() { } /** * 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; /** The current background color */ protected Color backColor; /** The current font name */ protected String fontName; /** The current font size */ protected int fontSize; /** The current line width */ protected float lineWidth; /** The dash array for the current basic stroke (line type) */ protected float[] dashArray; /** The current transform */ protected AffineTransform transform; /** The current (optional content group) layer. */ protected String layer; /** * 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. * @param baseTransform the transform */ public void setTransform(AffineTransform baseTransform) { this.transform = baseTransform; } /** * Resets the current AffineTransform. */ public void clearTransform() { transform = new AffineTransform(); } public void setLayer(String layer) { if (layer != null) { this.layer = layer; } else { throw new IllegalArgumentException(); } } public String getLayer() { return this.layer; } /** * 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} */ @Override 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; if (this.transform == null) { this.transform = new AffineTransform(); } data.transform = new AffineTransform(this.transform); data.layer = this.layer; return data; } /** {@inheritDoc} */ @Override public String toString() { return "color=" + color + ", backColor=" + backColor + ", fontName=" + fontName + ", fontSize=" + fontSize + ", lineWidth=" + lineWidth + ", dashArray=" + Arrays.toString(dashArray) + ", transform=" + transform + ", layer=" + layer; } } }