/*****************************************************************************
 * 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.ext.awt.font;

import java.awt.Shape;
import java.awt.font.GlyphMetrics;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;

import org.apache.batik.ext.awt.geom.PathLength;

/**
 * PathLayout can layout text along a Shape, usually a Path object.
 * <p>
 * There are a number of improvements that could be made to this class.
 * I'll try to list some of them:
 * <ul>
 * <li> The layout should really only modify the GlyphVector, rather
 *      than converting to a Shape.
 * <li> Maybe the functions should take a AttributedCharacterIterator
 *      or something? Should this class do the entire layout?
 * <li> The layout code works, but it's definitely not perfect.
 * </ul>
 * @author <a href="mailto:dean.jackson@cmis.csiro.au">Dean Jackson</a>
 * @version $Id: TextPathLayout.java,v 1.2 2003/04/11 13:57:15 vhardy Exp $
 */

public class TextPathLayout {

    /**
     * Align the text at the start of the path.
     */
    static public final int ALIGN_START = 0;
    /**
     * Align the text at the middle of the path.
     */
    static public final int ALIGN_MIDDLE = 1;
    /**
     * Align the text at the end of the path.
     */
    static public final int ALIGN_END = 2;

    /**
     * Use the spacing between the glyphs to adjust for textLength.
     */
    static public final int ADJUST_SPACING = 0;
    /**
     * Use the entire glyph to adjust for textLength.
     */
    static public final int ADJUST_GLYPHS = 1;

    /**
     * Wraps the GlyphVector around the given path. The results
     * are mostly quite nice but you need to be careful choosing
     * the size of the font that created the GlyphVector, as
     * well as the "curvyness" of the path (really dynamic curves
     * don't look so great, abrupt changes/vertices look worse).
     * 
     * @param glyphs The GlyphVector to layout.
     * @param path The path (or shape) to wrap around
     * @param align The text alignment to use. Should be one
     *              of ALIGN_START, ALIGN_MIDDLE or ALIGN_END.
     * @param startOffset The offset from the start of the path for the initial
     *              text position.
     * @param textLength The length that the text should fill.
     * @param lengthAdjustMode The method used to expand or contract
     *                         the text to meet the textLength.
     * @return A shape that is the outline of the glyph vector
     * wrapped along the path
     */


    static public Shape layoutGlyphVector(GlyphVector glyphs, 
					  Shape path, int align,
					  float startOffset,
					  float textLength,
					  int lengthAdjustMode) {

	GeneralPath newPath = new GeneralPath();
	PathLength pl = new PathLength(path);
	float pathLength = pl.lengthOfPath();
	float glyphsLength = (float) glyphs.getVisualBounds().getWidth();

	// return from the ugly cases
	if (path == null ||
	    glyphs == null ||
	    glyphs.getNumGlyphs() == 0 ||
	    pl.lengthOfPath() == 0f ||
	    glyphsLength == 0f) {
	    return newPath;
	}

	// work out the expansion/contraction per character
	float lengthRatio = textLength / glyphsLength;

	// the current start point of the character on the path
	float currentPosition = startOffset;

	// if align is START then a currentPosition of 0f
	// is correct. 
	// if align is END then the currentPosition should
	// be enough to place the last character on the end
	// of the path
	// if align is MIDDLE then the currentPosition should
	// be enough to center the glyphs on the path

	if (align == ALIGN_END) {
	    currentPosition += pathLength - textLength;
	} else if (align == ALIGN_MIDDLE) {
	    currentPosition += (pathLength - textLength) / 2;
	}

	// iterate through the GlyphVector placing each glyph

	for (int i = 0; i < glyphs.getNumGlyphs(); i++) {
	    
	    GlyphMetrics gm = glyphs.getGlyphMetrics(i);

	    float charAdvance = gm.getAdvance();

	    Shape glyph = glyphs.getGlyphOutline(i);

	    // if lengthAdjust was GLYPHS, then scale the glyph
	    // by the lengthRatio in the X direction
	    // FIXME: for vertical text this will be the Y direction
	    if (lengthAdjustMode == ADJUST_GLYPHS) {
		AffineTransform scale = AffineTransform.getScaleInstance(lengthRatio, 1f);
		glyph = scale.createTransformedShape(glyph);

		// charAdvance has to scale accordingly
		charAdvance *= lengthRatio;
	    }

	    float glyphWidth = (float) glyph.getBounds2D().getWidth();
	    
	    // Use either of these to calculate the mid point
	    // of the character along the path.
	    // If you change this, you must also change the 
	    // transform on the glyph down below
	    // In some case this gives better layout, but
	    // the way it is at the moment is a closer match
	    // to the textPath layout from the SVG spec

	    //float charMidPos = currentPosition + charAdvance / 2f;
	    float charMidPos = currentPosition + glyphWidth / 2f;

	    // Calculate the actual point to place the glyph around
	    Point2D charMidPoint = pl.pointAtLength(charMidPos);

	    // Check if the glyph is actually on the path

	    if (charMidPoint != null) {

		// Calculate the normal to the path (midline of glyph)
		float angle = pl.angleAtLength(charMidPos);

		// Define the transform of the glyph
		AffineTransform glyphTrans = new AffineTransform();

		// translate to the point on the path
		glyphTrans.translate(charMidPoint.getX(), charMidPoint.getY());

		// rotate midline of glyph to be normal to path
		glyphTrans.rotate(angle);

		// translate glyph backwards so we rotate about the 
		// center of the glyph
		// Choose one of these translations - see the comments
		// in the charMidPos calculation above
		glyphTrans.translate(charAdvance / -2f, 0f);
		//glyphTrans.translate(glyphWidth / -2f, 0f);

		glyph = glyphTrans.createTransformedShape(glyph);
		newPath.append(glyph, false);

	    }
	    
	    // move along by the advance value
	    // if the lengthAdjustMode was SPACING then
	    // we have to take this into account here
	    if (lengthAdjustMode == ADJUST_SPACING) {
		currentPosition += (charAdvance * lengthRatio);
	    } else {
		currentPosition += charAdvance;
	    }
	    
	}

	return newPath;
    }

    /**
     * Wraps the GlyphVector around the given path. 
     * 
     * @param glyphs The GlyphVector to layout.
     * @param path The path (or shape) to wrap around
     * @param align The text alignment to use. Should be one
     *              of ALIGN_START, ALIGN_MIDDLE or ALIGN_END.
     * @return A shape that is the outline of the glyph vector
     * wrapped along the path
     */

    static public Shape layoutGlyphVector(GlyphVector glyphs, 
					  Shape path, int align) {

	return layoutGlyphVector(glyphs, path, align, 0f,
				 (float) glyphs.getVisualBounds().getWidth(),
				 ADJUST_SPACING);
    }

    /**
     * Wraps the GlyphVector around the given path. 
     * 
     * @param glyphs The GlyphVector to layout.
     * @param path The path (or shape) to wrap around
     * @return A shape that is the outline of the glyph vector
     * wrapped along the path
     */

    static public Shape layoutGlyphVector(GlyphVector glyphs, 
					  Shape path) {

	return layoutGlyphVector(glyphs, path, ALIGN_START);
    }


} // TextPathLayout
