| /* |
| |
| 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. |
| |
| */ |
| |
| 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$ |
| */ |
| |
| public class TextPathLayout { |
| |
| /** |
| * Align the text at the start of the path. |
| */ |
| public static final int ALIGN_START = 0; |
| /** |
| * Align the text at the middle of the path. |
| */ |
| public static final int ALIGN_MIDDLE = 1; |
| /** |
| * Align the text at the end of the path. |
| */ |
| public static final int ALIGN_END = 2; |
| |
| /** |
| * Use the spacing between the glyphs to adjust for textLength. |
| */ |
| public static final int ADJUST_SPACING = 0; |
| /** |
| * Use the entire glyph to adjust for textLength. |
| */ |
| public static 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 |
| */ |
| |
| |
| public static 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(); |
| |
| if ( glyphs == null ){ |
| return newPath; |
| } |
| float glyphsLength = (float) glyphs.getVisualBounds().getWidth(); |
| |
| // return from the ugly cases |
| if (path == 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, 1.0f); |
| 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 |
| */ |
| |
| public static 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 |
| */ |
| |
| public static Shape layoutGlyphVector(GlyphVector glyphs, |
| Shape path) { |
| |
| return layoutGlyphVector(glyphs, path, ALIGN_START); |
| } |
| |
| |
| } // TextPathLayout |