001: /*
002:
003: Licensed to the Apache Software Foundation (ASF) under one or more
004: contributor license agreements. See the NOTICE file distributed with
005: this work for additional information regarding copyright ownership.
006: The ASF licenses this file to You under the Apache License, Version 2.0
007: (the "License"); you may not use this file except in compliance with
008: the License. You may obtain a copy of the License at
009:
010: http://www.apache.org/licenses/LICENSE-2.0
011:
012: Unless required by applicable law or agreed to in writing, software
013: distributed under the License is distributed on an "AS IS" BASIS,
014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: See the License for the specific language governing permissions and
016: limitations under the License.
017:
018: */
019:
020: package org.apache.batik.ext.awt.font;
021:
022: import java.awt.Shape;
023: import java.awt.font.GlyphMetrics;
024: import java.awt.font.GlyphVector;
025: import java.awt.geom.AffineTransform;
026: import java.awt.geom.GeneralPath;
027: import java.awt.geom.Point2D;
028:
029: import org.apache.batik.ext.awt.geom.PathLength;
030:
031: /**
032: * PathLayout can layout text along a Shape, usually a Path object.
033: * <p>
034: * There are a number of improvements that could be made to this class.
035: * I'll try to list some of them:
036: * <ul>
037: * <li> The layout should really only modify the GlyphVector, rather
038: * than converting to a Shape.
039: * <li> Maybe the functions should take a AttributedCharacterIterator
040: * or something? Should this class do the entire layout?
041: * <li> The layout code works, but it's definitely not perfect.
042: * </ul>
043: * @author <a href="mailto:dean.jackson@cmis.csiro.au">Dean Jackson</a>
044: * @version $Id: TextPathLayout.java 522271 2007-03-25 14:42:45Z dvholten $
045: */
046:
047: public class TextPathLayout {
048:
049: /**
050: * Align the text at the start of the path.
051: */
052: public static final int ALIGN_START = 0;
053: /**
054: * Align the text at the middle of the path.
055: */
056: public static final int ALIGN_MIDDLE = 1;
057: /**
058: * Align the text at the end of the path.
059: */
060: public static final int ALIGN_END = 2;
061:
062: /**
063: * Use the spacing between the glyphs to adjust for textLength.
064: */
065: public static final int ADJUST_SPACING = 0;
066: /**
067: * Use the entire glyph to adjust for textLength.
068: */
069: public static final int ADJUST_GLYPHS = 1;
070:
071: /**
072: * Wraps the GlyphVector around the given path. The results
073: * are mostly quite nice but you need to be careful choosing
074: * the size of the font that created the GlyphVector, as
075: * well as the "curvyness" of the path (really dynamic curves
076: * don't look so great, abrupt changes/vertices look worse).
077: *
078: * @param glyphs The GlyphVector to layout.
079: * @param path The path (or shape) to wrap around
080: * @param align The text alignment to use. Should be one
081: * of ALIGN_START, ALIGN_MIDDLE or ALIGN_END.
082: * @param startOffset The offset from the start of the path for the initial
083: * text position.
084: * @param textLength The length that the text should fill.
085: * @param lengthAdjustMode The method used to expand or contract
086: * the text to meet the textLength.
087: * @return A shape that is the outline of the glyph vector
088: * wrapped along the path
089: */
090:
091: public static Shape layoutGlyphVector(GlyphVector glyphs,
092: Shape path, int align, float startOffset, float textLength,
093: int lengthAdjustMode) {
094:
095: GeneralPath newPath = new GeneralPath();
096: PathLength pl = new PathLength(path);
097: float pathLength = pl.lengthOfPath();
098:
099: if (glyphs == null) {
100: return newPath;
101: }
102: float glyphsLength = (float) glyphs.getVisualBounds()
103: .getWidth();
104:
105: // return from the ugly cases
106: if (path == null || glyphs.getNumGlyphs() == 0
107: || pl.lengthOfPath() == 0f || glyphsLength == 0f) {
108: return newPath;
109: }
110:
111: // work out the expansion/contraction per character
112: float lengthRatio = textLength / glyphsLength;
113:
114: // the current start point of the character on the path
115: float currentPosition = startOffset;
116:
117: // if align is START then a currentPosition of 0f
118: // is correct.
119: // if align is END then the currentPosition should
120: // be enough to place the last character on the end
121: // of the path
122: // if align is MIDDLE then the currentPosition should
123: // be enough to center the glyphs on the path
124:
125: if (align == ALIGN_END) {
126: currentPosition += pathLength - textLength;
127: } else if (align == ALIGN_MIDDLE) {
128: currentPosition += (pathLength - textLength) / 2;
129: }
130:
131: // iterate through the GlyphVector placing each glyph
132:
133: for (int i = 0; i < glyphs.getNumGlyphs(); i++) {
134:
135: GlyphMetrics gm = glyphs.getGlyphMetrics(i);
136:
137: float charAdvance = gm.getAdvance();
138:
139: Shape glyph = glyphs.getGlyphOutline(i);
140:
141: // if lengthAdjust was GLYPHS, then scale the glyph
142: // by the lengthRatio in the X direction
143: // FIXME: for vertical text this will be the Y direction
144: if (lengthAdjustMode == ADJUST_GLYPHS) {
145: AffineTransform scale = AffineTransform
146: .getScaleInstance(lengthRatio, 1.0f);
147: glyph = scale.createTransformedShape(glyph);
148:
149: // charAdvance has to scale accordingly
150: charAdvance *= lengthRatio;
151: }
152:
153: float glyphWidth = (float) glyph.getBounds2D().getWidth();
154:
155: // Use either of these to calculate the mid point
156: // of the character along the path.
157: // If you change this, you must also change the
158: // transform on the glyph down below
159: // In some case this gives better layout, but
160: // the way it is at the moment is a closer match
161: // to the textPath layout from the SVG spec
162:
163: //float charMidPos = currentPosition + charAdvance / 2f;
164: float charMidPos = currentPosition + glyphWidth / 2f;
165:
166: // Calculate the actual point to place the glyph around
167: Point2D charMidPoint = pl.pointAtLength(charMidPos);
168:
169: // Check if the glyph is actually on the path
170:
171: if (charMidPoint != null) {
172:
173: // Calculate the normal to the path (midline of glyph)
174: float angle = pl.angleAtLength(charMidPos);
175:
176: // Define the transform of the glyph
177: AffineTransform glyphTrans = new AffineTransform();
178:
179: // translate to the point on the path
180: glyphTrans.translate(charMidPoint.getX(), charMidPoint
181: .getY());
182:
183: // rotate midline of glyph to be normal to path
184: glyphTrans.rotate(angle);
185:
186: // translate glyph backwards so we rotate about the
187: // center of the glyph
188: // Choose one of these translations - see the comments
189: // in the charMidPos calculation above
190: glyphTrans.translate(charAdvance / -2f, 0f);
191: //glyphTrans.translate(glyphWidth / -2f, 0f);
192:
193: glyph = glyphTrans.createTransformedShape(glyph);
194: newPath.append(glyph, false);
195:
196: }
197:
198: // move along by the advance value
199: // if the lengthAdjustMode was SPACING then
200: // we have to take this into account here
201: if (lengthAdjustMode == ADJUST_SPACING) {
202: currentPosition += (charAdvance * lengthRatio);
203: } else {
204: currentPosition += charAdvance;
205: }
206:
207: }
208:
209: return newPath;
210: }
211:
212: /**
213: * Wraps the GlyphVector around the given path.
214: *
215: * @param glyphs The GlyphVector to layout.
216: * @param path The path (or shape) to wrap around
217: * @param align The text alignment to use. Should be one
218: * of ALIGN_START, ALIGN_MIDDLE or ALIGN_END.
219: * @return A shape that is the outline of the glyph vector
220: * wrapped along the path
221: */
222:
223: public static Shape layoutGlyphVector(GlyphVector glyphs,
224: Shape path, int align) {
225:
226: return layoutGlyphVector(glyphs, path, align, 0f,
227: (float) glyphs.getVisualBounds().getWidth(),
228: ADJUST_SPACING);
229: }
230:
231: /**
232: * Wraps the GlyphVector around the given path.
233: *
234: * @param glyphs The GlyphVector to layout.
235: * @param path The path (or shape) to wrap around
236: * @return A shape that is the outline of the glyph vector
237: * wrapped along the path
238: */
239:
240: public static Shape layoutGlyphVector(GlyphVector glyphs, Shape path) {
241:
242: return layoutGlyphVector(glyphs, path, ALIGN_START);
243: }
244:
245: } // TextPathLayout
|