/*****************************************************************************
* Copyright (C) The Apache Software Foundation. All rights reserved. *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in *
* the LICENSE file. *
*****************************************************************************/
package org.apache.batik.svggen;
import java.util.Stack;
import org.apache.batik.ext.awt.g2d.GraphicContext;
import org.apache.batik.ext.awt.g2d.TransformStackElement;
import org.apache.batik.ext.awt.g2d.TransformType;
/**
* Utility class that converts a GraphicContext transform stack
* into an SVG transform attribute.
*
* @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
* @author <a href="mailto:paul_evenblij@compuware.com">Paul Evenblij</a>
* @version $Id: SVGTransform.java,v 1.7 2003/07/04 15:25:38 vhardy Exp $
*/
public class SVGTransform extends AbstractSVGConverter{
/**
* Ratio used to convert radians to degrees
*/
private static double radiansToDegrees = 180.0 / Math.PI;
/**
* @param generatorContext used by converter to handle precision
* or to create elements.
*/
public SVGTransform(SVGGeneratorContext generatorContext) {
super(generatorContext);
}
/**
* Converts part or all of the input GraphicContext into
* a set of attribute/value pairs and related definitions
*
* @param gc GraphicContext to be converted
* @return descriptor of the attributes required to represent
* some or all of the GraphicContext state, along
* with the related definitions
* @see org.apache.batik.svggen.SVGDescriptor
*/
public SVGDescriptor toSVG(GraphicContext gc){
return new SVGTransformDescriptor(toSVGTransform(gc));
}
/**
* @param gc GraphicContext whose transform stack should be converted
* to SVG.
* @return the value of an SVG attribute equivalent to the input
* GraphicContext's transform stack.
*/
public final String toSVGTransform(GraphicContext gc){
return toSVGTransform(gc.getTransformStack());
}
/**
* This method tries to collapse the transform stack into an SVG
* string as compact as possible while still conveying the semantic
* of the stack. Successive stack elements of the same kind (e.g., two
* successive transforms or scales) are collapsed into a single element.
*
* @param transformStack sequence of transform that should
* be converted to an SVG transform attribute equivalent
*/
public final String toSVGTransform(TransformStackElement transformStack[]){
StringBuffer transformStackBuffer = new StringBuffer();
int nTransforms = transformStack.length;
//
// Append transforms in the presentation stack
//
Stack presentation = new Stack() {
/**
* Adapted push implementation
*/
public Object push(Object o) {
Object element;
if(((TransformStackElement)o).isIdentity()) {
// identity transform: don't push,
// and try to return top of stack
element = pop();
} else {
// non-identity: push,
// and return null
super.push(o);
element = null;
}
return element;
}
/**
* Adapted pop implementation
*/
public Object pop() {
Object element = null;
if(!super.empty()) {
element = super.pop();
}
return element;
}
};
boolean canConcatenate = false;
int i = 0, j = 0, next = 0;
TransformStackElement element = null;
// We keep a separate 'presentation' stack, which contains
// all concatenated elements. The top of this stack is the
// element we try to concatenate onto. If this element
// becomes an identity transform, we discard it and look at
// the element underneath it instead.
// The presentation stack is guaranteed not to contain
// identity transforms.
while(i < nTransforms) {
// If we do not have an element to concatenate onto,
// we grab one here.
next = i;
if(element == null) {
element = (TransformStackElement) transformStack[i].clone();
next++;
}
// try to concatenate as much as possible
canConcatenate = true;
for(j = next; j < nTransforms; j++) {
canConcatenate = element.concatenate(transformStack[j]);
if(!canConcatenate)
break;
}
// loop variable assertion:
// If "i" does not increment during this iteration, it is guaranteed
// to do so in the next, since "i" can only keep the same value as a
// result of "element" having a non-null value on starting this
// iteration, which can only be the case if it was popped from the
// stack during the previous one. The stack does not contain
// identities, and since "i" has not grown, "element" has remained
// unchanged and will be pushed onto the stack again. "element" will
// then become null, so "i" will eventually increment.
i = j;
// Get rid of identity transforms within the stack.
// If an identity is pushed, it is immediately removed, and
// the current top of stack will be returned to concatenate onto.
// Otherwise, null will be returned.
element = (TransformStackElement) presentation.push(element);
}
// Push back teh last element popped, if not null
if (element != null){
presentation.push(element);
}
//
// Transform presentation stack to SVG
//
int nPresentations = presentation.size();
for(i = 0; i < nPresentations; i++) {
transformStackBuffer.append(convertTransform((TransformStackElement) presentation.elementAt(i)));
transformStackBuffer.append(SPACE);
}
String transformValue = transformStackBuffer.toString().trim();
return transformValue;
}
/**
* Converts an AffineTransform to an SVG transform string
*/
final String convertTransform(TransformStackElement transformElement){
StringBuffer transformString = new StringBuffer();
double transformParameters[] = transformElement.getTransformParameters();
switch(transformElement.getType().toInt()){
case TransformType.TRANSFORM_TRANSLATE:
if(!transformElement.isIdentity()) {
transformString.append(TRANSFORM_TRANSLATE);
transformString.append(OPEN_PARENTHESIS);
transformString.append(doubleString(transformParameters[0]));
transformString.append(COMMA);
transformString.append(doubleString(transformParameters[1]));
transformString.append(CLOSE_PARENTHESIS);
}
break;
case TransformType.TRANSFORM_ROTATE:
if(!transformElement.isIdentity()) {
transformString.append(TRANSFORM_ROTATE);
transformString.append(OPEN_PARENTHESIS);
transformString.append(doubleString(radiansToDegrees*transformParameters[0]));
transformString.append(CLOSE_PARENTHESIS);
}
break;
case TransformType.TRANSFORM_SCALE:
if(!transformElement.isIdentity()) {
transformString.append(TRANSFORM_SCALE);
transformString.append(OPEN_PARENTHESIS);
transformString.append(doubleString(transformParameters[0]));
transformString.append(COMMA);
transformString.append(doubleString(transformParameters[1]));
transformString.append(CLOSE_PARENTHESIS);
}
break;
case TransformType.TRANSFORM_SHEAR:
if(!transformElement.isIdentity()) {
transformString.append(TRANSFORM_MATRIX);
transformString.append(OPEN_PARENTHESIS);
transformString.append(1);
transformString.append(COMMA);
transformString.append(doubleString(transformParameters[1]));
transformString.append(COMMA);
transformString.append(doubleString(transformParameters[0]));
transformString.append(COMMA);
transformString.append(1);
transformString.append(COMMA);
transformString.append(0);
transformString.append(COMMA);
transformString.append(0);
transformString.append(CLOSE_PARENTHESIS);
}
break;
case TransformType.TRANSFORM_GENERAL:
if(!transformElement.isIdentity()) {
transformString.append(TRANSFORM_MATRIX);
transformString.append(OPEN_PARENTHESIS);
transformString.append(doubleString(transformParameters[0]));
transformString.append(COMMA);
transformString.append(doubleString(transformParameters[1]));
transformString.append(COMMA);
transformString.append(doubleString(transformParameters[2]));
transformString.append(COMMA);
transformString.append(doubleString(transformParameters[3]));
transformString.append(COMMA);
transformString.append(doubleString(transformParameters[4]));
transformString.append(COMMA);
transformString.append(doubleString(transformParameters[5]));
transformString.append(CLOSE_PARENTHESIS);
}
break;
default:
// This should never happen. If it does, there is a
// serious error.
throw new Error();
}
return transformString.toString();
}
}