0001: /*
0002:
0003: Licensed to the Apache Software Foundation (ASF) under one or more
0004: contributor license agreements. See the NOTICE file distributed with
0005: this work for additional information regarding copyright ownership.
0006: The ASF licenses this file to You under the Apache License, Version 2.0
0007: (the "License"); you may not use this file except in compliance with
0008: the License. You may obtain a copy of the License at
0009:
0010: http://www.apache.org/licenses/LICENSE-2.0
0011:
0012: Unless required by applicable law or agreed to in writing, software
0013: distributed under the License is distributed on an "AS IS" BASIS,
0014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0015: See the License for the specific language governing permissions and
0016: limitations under the License.
0017:
0018: */
0019: package org.apache.batik.gvt.text;
0020:
0021: import java.awt.BasicStroke;
0022: import java.awt.Graphics2D;
0023: import java.awt.Shape;
0024: import java.awt.Stroke;
0025: import java.awt.font.FontRenderContext;
0026: import java.awt.font.TextAttribute;
0027: import java.awt.geom.AffineTransform;
0028: import java.awt.geom.Area;
0029: import java.awt.geom.GeneralPath;
0030: import java.awt.geom.PathIterator;
0031: import java.awt.geom.Point2D;
0032: import java.awt.geom.Rectangle2D;
0033: import java.awt.geom.Line2D;
0034: import java.text.AttributedCharacterIterator;
0035: import java.text.CharacterIterator;
0036: import java.util.HashSet;
0037: import java.util.Set;
0038:
0039: import org.apache.batik.gvt.font.AWTGVTFont;
0040: import org.apache.batik.gvt.font.AltGlyphHandler;
0041: import org.apache.batik.gvt.font.GVTFont;
0042: import org.apache.batik.gvt.font.GVTGlyphMetrics;
0043: import org.apache.batik.gvt.font.GVTGlyphVector;
0044: import org.apache.batik.gvt.font.GVTLineMetrics;
0045:
0046: /**
0047: * Implementation of TextSpanLayout which uses java.awt.font.GlyphVector.
0048: * @see org.apache.batik.gvt.text.TextSpanLayout
0049: *
0050: * @author <a href="mailto:bill.haneman@ireland.sun.com">Bill Haneman</a>
0051: * @version $Id: GlyphLayout.java 501922 2007-01-31 17:47:47Z dvholten $
0052: */
0053: public class GlyphLayout implements TextSpanLayout {
0054:
0055: private GVTGlyphVector gv;
0056: private GVTFont font;
0057: private GVTLineMetrics metrics;
0058: private AttributedCharacterIterator aci;
0059: private Point2D advance;
0060: private Point2D offset;
0061: private float xScale = 1;
0062: private float yScale = 1;
0063: private TextPath textPath;
0064: private Point2D textPathAdvance;
0065: private int[] charMap;
0066: private boolean vertical, adjSpacing = true;
0067: private float[] glyphAdvances;
0068: private boolean isAltGlyph; //false
0069:
0070: // When layoutApplied is false it means that the glyph positions
0071: // are different from where they would be if you did
0072: // doExplicitGlyphLayout().
0073: private boolean layoutApplied = false;
0074: // When spacingApplied is false it means that xScale, yScale and
0075: // kerning/wordspacing stuff haven't been applied. This can
0076: // be rectified by calling adjustTextSpacing(). Note that when
0077: // spacing is actually used layoutApplied will be cleared it
0078: // is not garunteed that applying text spacing will cause it to
0079: // be cleared (it will only be cleared if the glyphs move).
0080: private boolean spacingApplied = false;
0081: // When pathApplied is false it means that the text has not been
0082: // layed out on the associated text path (if any). If there is an
0083: // associated text path then this will clear both layoutApplied
0084: // and spacing applied but neither will be touched if no text path
0085: // is present.
0086: private boolean pathApplied = false;
0087:
0088: public static final AttributedCharacterIterator.Attribute FLOW_LINE_BREAK = GVTAttributedCharacterIterator.TextAttribute.FLOW_LINE_BREAK;
0089:
0090: public static final AttributedCharacterIterator.Attribute FLOW_PARAGRAPH = GVTAttributedCharacterIterator.TextAttribute.FLOW_PARAGRAPH;
0091:
0092: public static final AttributedCharacterIterator.Attribute FLOW_EMPTY_PARAGRAPH = GVTAttributedCharacterIterator.TextAttribute.FLOW_EMPTY_PARAGRAPH;
0093:
0094: public static final AttributedCharacterIterator.Attribute LINE_HEIGHT = GVTAttributedCharacterIterator.TextAttribute.LINE_HEIGHT;
0095:
0096: public static final AttributedCharacterIterator.Attribute VERTICAL_ORIENTATION = GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION;
0097:
0098: public static final AttributedCharacterIterator.Attribute VERTICAL_ORIENTATION_ANGLE = GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION_ANGLE;
0099:
0100: public static final AttributedCharacterIterator.Attribute HORIZONTAL_ORIENTATION_ANGLE = GVTAttributedCharacterIterator.TextAttribute.HORIZONTAL_ORIENTATION_ANGLE;
0101:
0102: private static final AttributedCharacterIterator.Attribute X = GVTAttributedCharacterIterator.TextAttribute.X;
0103:
0104: private static final AttributedCharacterIterator.Attribute Y = GVTAttributedCharacterIterator.TextAttribute.Y;
0105:
0106: private static final AttributedCharacterIterator.Attribute DX = GVTAttributedCharacterIterator.TextAttribute.DX;
0107:
0108: private static final AttributedCharacterIterator.Attribute DY = GVTAttributedCharacterIterator.TextAttribute.DY;
0109:
0110: private static final AttributedCharacterIterator.Attribute ROTATION = GVTAttributedCharacterIterator.TextAttribute.ROTATION;
0111:
0112: private static final AttributedCharacterIterator.Attribute BASELINE_SHIFT = GVTAttributedCharacterIterator.TextAttribute.BASELINE_SHIFT;
0113:
0114: private static final AttributedCharacterIterator.Attribute WRITING_MODE = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE;
0115:
0116: private static final Integer WRITING_MODE_TTB = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_TTB;
0117:
0118: private static final Integer ORIENTATION_AUTO = GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_AUTO;
0119:
0120: public static final AttributedCharacterIterator.Attribute GVT_FONT = GVTAttributedCharacterIterator.TextAttribute.GVT_FONT;
0121:
0122: protected static Set runAtts = new HashSet();
0123:
0124: static {
0125: runAtts.add(X);
0126: runAtts.add(Y);
0127: runAtts.add(DX);
0128: runAtts.add(DY);
0129: runAtts.add(ROTATION);
0130: runAtts.add(BASELINE_SHIFT);
0131: }
0132:
0133: protected static Set szAtts = new HashSet();
0134:
0135: static {
0136: szAtts.add(TextAttribute.SIZE);
0137: szAtts.add(GVT_FONT);
0138: szAtts.add(LINE_HEIGHT);
0139: }
0140:
0141: /**
0142: * Creates the specified text layout using the
0143: * specified AttributedCharacterIterator and rendering context.
0144: *
0145: * @param aci the AttributedCharacterIterator whose text is to
0146: * be laid out
0147: * @param charMap Indicates how chars in aci map to original
0148: * text char array.
0149: * @param offset The offset position of this text layout
0150: * @param frc the FontRenderContext to use for generating glyphs.
0151: */
0152: public GlyphLayout(AttributedCharacterIterator aci, int[] charMap,
0153: Point2D offset, FontRenderContext frc) {
0154:
0155: this .aci = aci;
0156: this .offset = offset;
0157: this .font = getFont();
0158: this .charMap = charMap;
0159:
0160: this .metrics = font.getLineMetrics(aci, aci.getBeginIndex(),
0161: aci.getEndIndex(), frc);
0162:
0163: // create the glyph vector
0164: this .gv = null;
0165: this .aci.first();
0166: this .vertical = (aci.getAttribute(WRITING_MODE) == WRITING_MODE_TTB);
0167: this .textPath = (TextPath) aci
0168: .getAttribute(GVTAttributedCharacterIterator.TextAttribute.TEXTPATH);
0169:
0170: AltGlyphHandler altGlyphHandler = (AltGlyphHandler) this .aci
0171: .getAttribute(GVTAttributedCharacterIterator.TextAttribute.ALT_GLYPH_HANDLER);
0172: if (altGlyphHandler != null) {
0173: // this must be an altGlyph text element, try and create
0174: // the alternate glyphs
0175: this .gv = altGlyphHandler.createGlyphVector(frc, this .font
0176: .getSize(), this .aci);
0177: if (this .gv != null) {
0178: this .isAltGlyph = true;
0179: }
0180: }
0181: if (this .gv == null) {
0182: // either not an altGlyph or the altGlyphHandler failed to
0183: // create a glyph vector
0184: this .gv = font.createGlyphVector(frc, this .aci);
0185: }
0186: }
0187:
0188: public GVTGlyphVector getGlyphVector() {
0189: return this .gv;
0190: }
0191:
0192: /**
0193: * Returns the current text position at the beginning
0194: * of glyph layout, before the application of explicit
0195: * glyph positioning attributes.
0196: */
0197: public Point2D getOffset() {
0198: return offset;
0199: }
0200:
0201: /**
0202: * Sets the scaling factor to use for string. if ajdSpacing is
0203: * true then only the spacing between glyphs will be adjusted
0204: * otherwise the glyphs and the spaces between them will be
0205: * adjusted. Only the scale factor in the progression direction
0206: * is used (x for horizontal text, y for vertical text
0207: * ).
0208: * @param xScale Scale factor to apply in X direction.
0209: * @param yScale Scale factor to apply in Y direction.
0210: * @param adjSpacing True if only spaces should be adjusted.
0211: */
0212: public void setScale(float xScale, float yScale, boolean adjSpacing) {
0213: // Fix the off axis scale factor.
0214: if (vertical)
0215: xScale = 1;
0216: else
0217: yScale = 1;
0218:
0219: if ((xScale != this .xScale) || (yScale != this .yScale)
0220: || (adjSpacing != this .adjSpacing)) {
0221: this .xScale = xScale;
0222: this .yScale = yScale;
0223: this .adjSpacing = adjSpacing;
0224:
0225: // We don't affect layoutApplied directly...
0226: // System.out.println("layoutApplied: " + layoutApplied);
0227:
0228: // However if we did path layout or spacing it's all junk now...
0229: spacingApplied = false;
0230: glyphAdvances = null;
0231: pathApplied = false;
0232: }
0233: }
0234:
0235: /**
0236: * Sets the text position used for the implicit origin
0237: * of glyph layout. Ignored if multiple explicit glyph
0238: * positioning attributes are present in ACI
0239: * (e.g. if the aci has multiple X or Y values).
0240: */
0241: public void setOffset(Point2D offset) {
0242: // System.err.println("SetOffset: " + offset + " - " + this.offset);
0243: if ((offset.getX() != this .offset.getX())
0244: || (offset.getY() != this .offset.getY())) {
0245: if ((layoutApplied) || (spacingApplied)) {
0246: // Already layed out need to shift glyph positions to
0247: // account for new offset.
0248: float dx = (float) (offset.getX() - this .offset.getX());
0249: float dy = (float) (offset.getY() - this .offset.getY());
0250: int numGlyphs = gv.getNumGlyphs();
0251:
0252: // System.out.println("DXY: [" + dx +","+dy+"]");
0253: float[] gp = gv.getGlyphPositions(0, numGlyphs + 1,
0254: null);
0255: Point2D.Float pos = new Point2D.Float();
0256: for (int i = 0; i <= numGlyphs; i++) {
0257: pos.x = gp[2 * i] + dx;
0258: pos.y = gp[2 * i + 1] + dy;
0259: gv.setGlyphPosition(i, pos);
0260: }
0261: }
0262:
0263: // When not layed out (or after updating) just set the new
0264: // offset this will be factored in for any future layout
0265: // operations.
0266: this .offset = offset;
0267:
0268: // We don't affect layoutApplied or spacingApplied since
0269: // they both work off the offset value.
0270:
0271: // However if we did path layout it's all junk now...
0272: pathApplied = false;
0273: }
0274: }
0275:
0276: public GVTGlyphMetrics getGlyphMetrics(int glyphIndex) {
0277: return gv.getGlyphMetrics(glyphIndex);
0278: }
0279:
0280: public GVTLineMetrics getLineMetrics() {
0281: return metrics;
0282: }
0283:
0284: /**
0285: * Returns true if the advance direction of this text is vertical.
0286: */
0287: public boolean isVertical() {
0288: return vertical;
0289: }
0290:
0291: /**
0292: * Returns true if this layout in on a text path.
0293: */
0294: public boolean isOnATextPath() {
0295: return (textPath != null);
0296: }
0297:
0298: /**
0299: * Returns the number of glyphs in this layout.
0300: */
0301: public int getGlyphCount() {
0302: return gv.getNumGlyphs();
0303: }
0304:
0305: /**
0306: * Returns the number of chars represented by the glyphs within the
0307: * specified range.
0308: *
0309: * @param startGlyphIndex The index of the first glyph in the range.
0310: * @param endGlyphIndex The index of the last glyph in the range.
0311: *
0312: * @return The number of chars.
0313: */
0314: public int getCharacterCount(int startGlyphIndex, int endGlyphIndex) {
0315: return gv.getCharacterCount(startGlyphIndex, endGlyphIndex);
0316: }
0317:
0318: /**
0319: * Returns true if the text direction in this layout is from left to right.
0320: */
0321: public boolean isLeftToRight() {
0322: aci.first();
0323: int bidiLevel = ((Integer) aci
0324: .getAttribute(GVTAttributedCharacterIterator.TextAttribute.BIDI_LEVEL))
0325: .intValue();
0326:
0327: // Check if low bit is set if not then we are left to right
0328: // (even bidi level).
0329: return ((bidiLevel & 0x01) == 0);
0330: }
0331:
0332: /**
0333: * This method makes certain that the layout has been
0334: * completed at this point (much of the layout is done lazily).
0335: */
0336: private final void syncLayout() {
0337: if (!pathApplied) {
0338: // System.out.println("Doing Path Layout: " + this);
0339: doPathLayout();
0340: }
0341: }
0342:
0343: /**
0344: * Paints the text layout using the
0345: * specified Graphics2D and rendering context.
0346: * @param g2d the Graphics2D to use
0347: */
0348: public void draw(Graphics2D g2d) {
0349: syncLayout();
0350: gv.draw(g2d, aci);
0351: }
0352:
0353: /**
0354: * Returns the current text position at the completion
0355: * of glyph layout.
0356: */
0357: public Point2D getAdvance2D() {
0358: adjustTextSpacing();
0359: return advance;
0360: }
0361:
0362: /**
0363: * Returns the outline of the completed glyph layout.
0364: */
0365: public Shape getOutline() {
0366: syncLayout();
0367:
0368: return gv.getOutline();
0369: }
0370:
0371: public float[] getGlyphAdvances() {
0372: if (glyphAdvances != null)
0373: return glyphAdvances;
0374:
0375: if (!spacingApplied)
0376: // This will layout the text if needed.
0377: adjustTextSpacing();
0378:
0379: int numGlyphs = gv.getNumGlyphs();
0380: float[] glyphPos = gv.getGlyphPositions(0, numGlyphs + 1, null);
0381: glyphAdvances = new float[numGlyphs + 1];
0382: int off = 0;
0383: if (isVertical())
0384: off = 1;
0385:
0386: float start = glyphPos[off];
0387: for (int i = 0; i < numGlyphs + 1; i++) {
0388: glyphAdvances[i] = glyphPos[2 * i + off] - start;
0389: }
0390: return glyphAdvances;
0391: }
0392:
0393: /**
0394: * Returns the outline of the specified decorations on the glyphs,
0395: * @param decorationType an integer indicating the type(s) of decorations
0396: * included in this shape. May be the result of "OR-ing" several
0397: * values together:
0398: * e.g. <tt>DECORATION_UNDERLINE | DECORATION_STRIKETHROUGH</tt>
0399: */
0400: public Shape getDecorationOutline(int decorationType) {
0401: syncLayout();
0402:
0403: Shape g = new GeneralPath();
0404: if ((decorationType & DECORATION_UNDERLINE) != 0) {
0405: ((GeneralPath) g).append(getUnderlineShape(), false);
0406: }
0407: if ((decorationType & DECORATION_STRIKETHROUGH) != 0) {
0408: ((GeneralPath) g).append(getStrikethroughShape(), false);
0409: }
0410: if ((decorationType & DECORATION_OVERLINE) != 0) {
0411: ((GeneralPath) g).append(getOverlineShape(), false);
0412: }
0413: return g;
0414: }
0415:
0416: /**
0417: * Returns the rectangular bounds of the completed glyph layout.
0418: */
0419: public Rectangle2D getBounds2D() {
0420: syncLayout();
0421: return gv.getBounds2D(aci);
0422: }
0423:
0424: /**
0425: * Returns the rectangular bounds of the completed glyph layout,
0426: * inclusive of "decoration" (underline, overline, etc.)
0427: */
0428: public Rectangle2D getGeometricBounds() {
0429: syncLayout();
0430: Rectangle2D gvB, decB;
0431: gvB = gv.getGeometricBounds();
0432: decB = getDecorationOutline(DECORATION_ALL).getBounds2D();
0433: return gvB.createUnion(decB);
0434: }
0435:
0436: /**
0437: * Returns the position to used when drawing a text run after this one.
0438: * It takes into account the text path layout if there is one.
0439: */
0440: public Point2D getTextPathAdvance() {
0441: syncLayout();
0442: if (textPath != null) {
0443: return textPathAdvance;
0444: } else {
0445: return getAdvance2D();
0446: }
0447: }
0448:
0449: /**
0450: * Returns the index of the first glyph that has the specified char index.
0451: *
0452: * @param charIndex The original index of the character in the text node's
0453: * text string.
0454: * @return The index of the matching glyph in this layout's glyph vector,
0455: * or -1 if a matching glyph could not be found.
0456: */
0457: public int getGlyphIndex(int charIndex) {
0458: int numGlyphs = getGlyphCount();
0459: int j = 0;
0460: for (int i = 0; i < numGlyphs; i++) {
0461: int count = getCharacterCount(i, i);
0462: for (int n = 0; n < count; n++) {
0463: int glyphCharIndex = charMap[j++];
0464: if (charIndex == glyphCharIndex)
0465: return i;
0466: if (j >= charMap.length)
0467: return -1;
0468: }
0469: }
0470: return -1;
0471: }
0472:
0473: /**
0474: * Returns the index of the last glyph that has the specified char index.
0475: *
0476: * @param charIndex The original index of the character in the text node's
0477: * text string.
0478: * @return The index of the matching glyph in this layout's glyph vector,
0479: * or -1 if a matching glyph could not be found.
0480: */
0481: public int getLastGlyphIndex(int charIndex) {
0482: int numGlyphs = getGlyphCount();
0483: int j = charMap.length - 1;
0484: for (int i = numGlyphs - 1; i >= 0; --i) {
0485: int count = getCharacterCount(i, i);
0486: for (int n = 0; n < count; n++) {
0487: int glyphCharIndex = charMap[j--];
0488: if (charIndex == glyphCharIndex)
0489: return i;
0490: if (j < 0)
0491: return -1;
0492: }
0493: }
0494: return -1;
0495: }
0496:
0497: /**
0498: * Return the angle value according to the orientation
0499: * of the character.
0500: */
0501: public double getComputedOrientationAngle(int index) {
0502:
0503: if (isGlyphOrientationAuto()) {
0504: if (isVertical()) {
0505: char ch = aci.setIndex(index);
0506: if (isLatinChar(ch))
0507: return 90.0;
0508: else
0509: return 0.0;
0510: }
0511: return 0.0;
0512: } else {
0513: return getGlyphOrientationAngle();
0514: }
0515: }
0516:
0517: /**
0518: * Returns a Shape which encloses the currently selected glyphs
0519: * as specified by the character indices.
0520: *
0521: * @param beginCharIndex the index of the first char in the
0522: * contiguous selection.
0523: * @param endCharIndex the index of the last char in the
0524: * contiguous selection.
0525: * @return The highlight shape or null if the spacified char range
0526: * does not overlap with the chars in this layout. */
0527: public Shape getHighlightShape(int beginCharIndex, int endCharIndex) {
0528: syncLayout();
0529:
0530: if (beginCharIndex > endCharIndex) {
0531: int temp = beginCharIndex;
0532: beginCharIndex = endCharIndex;
0533: endCharIndex = temp;
0534: }
0535: GeneralPath shape = null;
0536: int numGlyphs = getGlyphCount();
0537:
0538: Point2D.Float[] topPts = new Point2D.Float[2 * numGlyphs];
0539: Point2D.Float[] botPts = new Point2D.Float[2 * numGlyphs];
0540:
0541: int ptIdx = 0;
0542:
0543: int currentChar = 0;
0544: for (int i = 0; i < numGlyphs; i++) {
0545: int glyphCharIndex = charMap[currentChar];
0546: if ((glyphCharIndex >= beginCharIndex)
0547: && (glyphCharIndex <= endCharIndex)
0548: && gv.isGlyphVisible(i)) {
0549:
0550: Shape gbounds = gv.getGlyphLogicalBounds(i);
0551: if (gbounds != null) {
0552: // We got something...
0553: if (shape == null)
0554: shape = new GeneralPath();
0555:
0556: // We are pretty dumb here we assume that we always
0557: // get back polygons with four sides to them if
0558: // isn't met we are SOL.
0559: float[] pts = new float[6];
0560: int count = 0;
0561: int type = -1;
0562:
0563: PathIterator pi = gbounds.getPathIterator(null);
0564: Point2D.Float firstPt = null;
0565:
0566: while (!pi.isDone()) {
0567: type = pi.currentSegment(pts);
0568: if ((type == PathIterator.SEG_MOVETO)
0569: || (type == PathIterator.SEG_LINETO)) {
0570: // LINETO or MOVETO
0571: if (count > 4)
0572: break; // too many lines...
0573: if (count == 4) {
0574: // make sure we are just closing it..
0575: if ((firstPt == null)
0576: || (firstPt.x != pts[0])
0577: || (firstPt.y != pts[1]))
0578: break;
0579: } else {
0580: Point2D.Float pt;
0581: pt = new Point2D.Float(pts[0], pts[1]);
0582: if (count == 0)
0583: firstPt = pt;
0584: // Use sides of rectangle...
0585: switch (count) {
0586: case 0:
0587: botPts[ptIdx] = pt;
0588: break;
0589: case 1:
0590: topPts[ptIdx] = pt;
0591: break;
0592: case 2:
0593: topPts[ptIdx + 1] = pt;
0594: break;
0595: case 3:
0596: botPts[ptIdx + 1] = pt;
0597: break;
0598: }
0599: }
0600: } else if (type == PathIterator.SEG_CLOSE) {
0601: // Close in the wrong spot?
0602: if ((count < 4) || (count > 5))
0603: break;
0604: } else {
0605: // QUADTO or CUBETO
0606: break;
0607: }
0608:
0609: count++;
0610: pi.next();
0611: }
0612: if (pi.isDone()) {
0613: // Sucessfully Expressed as a quadralateral...
0614: if ((botPts[ptIdx] != null)
0615: && ((topPts[ptIdx].x != topPts[ptIdx + 1].x) || (topPts[ptIdx].y != topPts[ptIdx + 1].y)))
0616: // box isn't empty so use it's points...
0617: ptIdx += 2;
0618: } else {
0619: // System.out.println("Type: " + type +
0620: // " count: " + count);
0621: // Wasn't a quadralateral so just add it don't try
0622: // and merge it...
0623: addPtsToPath(shape, topPts, botPts, ptIdx);
0624: ptIdx = 0;
0625: shape.append(gbounds, false);
0626: }
0627: }
0628: }
0629: currentChar += getCharacterCount(i, i);
0630: if (currentChar >= charMap.length)
0631: currentChar = charMap.length - 1;
0632: }
0633: addPtsToPath(shape, topPts, botPts, ptIdx);
0634:
0635: return shape;
0636: }
0637:
0638: public static final double eps = 0.00001;
0639:
0640: public static boolean epsEQ(double a, double b) {
0641: return ((a + eps > b) && (a - eps < b));
0642: }
0643:
0644: public static int makeConvexHull(Point2D.Float[] pts, int numPts) {
0645: // Sort the Pts in X...
0646: Point2D.Float tmp;
0647: // System.out.print("Sorting...");
0648: for (int i = 1; i < numPts; i++) {
0649: // Simple bubble sort (numPts should be small so shouldn't
0650: // be too bad.).
0651: if ((pts[i].x < pts[i - 1].x)
0652: || ((pts[i].x == pts[i - 1].x) && (pts[i].y < pts[i - 1].y))) {
0653: tmp = pts[i];
0654: pts[i] = pts[i - 1];
0655: pts[i - 1] = tmp;
0656: i = 0;
0657: continue;
0658: }
0659: }
0660:
0661: // System.out.println("Sorted");
0662:
0663: Point2D.Float pt0 = pts[0];
0664: Point2D.Float pt1 = pts[numPts - 1];
0665: Point2D.Float dxdy = new Point2D.Float(pt1.x - pt0.x, pt1.y
0666: - pt0.y);
0667: float soln, c = dxdy.y * pt0.x - dxdy.x * pt0.y;
0668:
0669: Point2D.Float[] topList = new Point2D.Float[numPts];
0670: Point2D.Float[] botList = new Point2D.Float[numPts];
0671: botList[0] = topList[0] = pts[0];
0672: int nTopPts = 1;
0673: int nBotPts = 1;
0674: for (int i = 1; i < numPts - 1; i++) {
0675: Point2D.Float pt = pts[i];
0676: soln = dxdy.x * pt.y - dxdy.y * pt.x + c;
0677: if (soln < 0) {
0678: // Below line goes into bot pt list...
0679: while (nBotPts >= 2) {
0680: pt0 = botList[nBotPts - 2];
0681: pt1 = botList[nBotPts - 1];
0682: float dx = pt1.x - pt0.x;
0683: float dy = pt1.y - pt0.y;
0684: float c0 = dy * pt0.x - dx * pt0.y;
0685: soln = dx * pt.y - dy * pt.x + c0;
0686: if (soln > eps) // Left turn add and we are done..
0687: break;
0688: if (soln > -eps) {
0689: // On line take lowest Y of two and keep going
0690: if (pt1.y < pt.y)
0691: pt = pt1;
0692: nBotPts--;
0693: break;
0694: }
0695: // right turn drop prev pt;
0696: nBotPts--;
0697: }
0698: botList[nBotPts++] = pt;
0699: } else {
0700: // Above line goes into top pt list...
0701: while (nTopPts >= 2) {
0702: pt0 = topList[nTopPts - 2];
0703: pt1 = topList[nTopPts - 1];
0704: float dx = pt1.x - pt0.x;
0705: float dy = pt1.y - pt0.y;
0706: float c0 = dy * pt0.x - dx * pt0.y;
0707: soln = dx * pt.y - dy * pt.x + c0;
0708: if (soln < -eps) // Right turn add and check next point.
0709: break;
0710: if (soln < eps) {
0711: // On line take greatest Y of two and keep going
0712: if (pt1.y > pt.y)
0713: pt = pt1;
0714: nTopPts--;
0715: break;
0716: }
0717: // left turn drop prev pt;
0718: nTopPts--;
0719: }
0720: topList[nTopPts++] = pt;
0721: }
0722: }
0723:
0724: // Check last point in both sets...
0725: Point2D.Float pt = pts[numPts - 1];
0726: while (nBotPts >= 2) {
0727: pt0 = botList[nBotPts - 2];
0728: pt1 = botList[nBotPts - 1];
0729: float dx = pt1.x - pt0.x;
0730: float dy = pt1.y - pt0.y;
0731: float c0 = dy * pt0.x - dx * pt0.y;
0732: soln = dx * pt.y - dy * pt.x + c0;
0733: if (soln > eps)
0734: // Left turn add and we are done..
0735: break;
0736: if (soln > -eps) {
0737: // On line take lowest Y of two and keep going
0738: if (pt1.y >= pt.y)
0739: nBotPts--;
0740: break;
0741: }
0742: // right turn drop prev pt;
0743: nBotPts--;
0744: }
0745:
0746: while (nTopPts >= 2) {
0747: pt0 = topList[nTopPts - 2];
0748: pt1 = topList[nTopPts - 1];
0749: float dx = pt1.x - pt0.x;
0750: float dy = pt1.y - pt0.y;
0751: float c0 = dy * pt0.x - dx * pt0.y;
0752: soln = dx * pt.y - dy * pt.x + c0;
0753: if (soln < -eps)
0754: // Right turn done...
0755: break;
0756: if (soln < eps) {
0757: // On line take lowest Y of two and keep going
0758: if (pt1.y <= pt.y)
0759: nTopPts--;
0760: break;
0761: }
0762: // left turn drop prev pt;
0763: nTopPts--;
0764: }
0765:
0766: System.arraycopy(topList, 0, pts, 0, nTopPts);
0767: int i = nTopPts;
0768:
0769: // We always include the 'last' point as it is always on convex hull.
0770: pts[i++] = pts[numPts - 1];
0771:
0772: // don't include botList[0] since it is the same as topList[0].
0773: for (int n = nBotPts - 1; n > 0; n--, i++)
0774: pts[i] = botList[n];
0775:
0776: // System.out.println("CHull has " + i + " pts");
0777: return i;
0778: }
0779:
0780: public static void addPtsToPath(GeneralPath shape,
0781: Point2D.Float[] topPts, Point2D.Float[] botPts, int numPts) {
0782: if (numPts < 2)
0783: return;
0784: if (numPts == 2) {
0785: shape.moveTo(topPts[0].x, topPts[0].y);
0786: shape.lineTo(topPts[1].x, topPts[1].y);
0787: shape.lineTo(botPts[1].x, botPts[1].y);
0788: shape.lineTo(botPts[0].x, botPts[0].y);
0789: shape.lineTo(topPts[0].x, topPts[0].y);
0790: return;
0791: }
0792:
0793: // Here we 'connect the dots' the best way we know how...
0794: // What I do is construct a convex hull between adjacent
0795: // character boxes, then I union that into the shape. this
0796: // does a good job of bridging between adjacent characters,
0797: // but still closely tracking to text boxes. The use of the
0798: // Area class is fairly heavy weight but it seems to keep up
0799: // in this instanace (probably because all the shapes are very
0800: // simple polygons).
0801: Point2D.Float[] boxes = new Point2D.Float[8];
0802: Point2D.Float[] chull = new Point2D.Float[8];
0803: boxes[4] = topPts[0];
0804: boxes[5] = topPts[1];
0805: boxes[6] = botPts[1];
0806: boxes[7] = botPts[0];
0807: Area[] areas = new Area[numPts / 2];
0808: int nAreas = 0;
0809: for (int i = 2; i < numPts; i += 2) {
0810: boxes[0] = boxes[4];
0811: boxes[1] = boxes[5];
0812: boxes[2] = boxes[6];
0813: boxes[3] = boxes[7];
0814: boxes[4] = topPts[i];
0815: boxes[5] = topPts[i + 1];
0816: boxes[6] = botPts[i + 1];
0817: boxes[7] = botPts[i];
0818:
0819: float delta, sz, dist;
0820: delta = boxes[2].x - boxes[0].x;
0821: dist = delta * delta;
0822: delta = boxes[2].y - boxes[0].y;
0823: dist += delta * delta;
0824: sz = (float) Math.sqrt(dist);
0825:
0826: delta = boxes[6].x - boxes[4].x;
0827: dist = delta * delta;
0828: delta = boxes[6].y - boxes[4].y;
0829: dist += delta * delta;
0830: sz += (float) Math.sqrt(dist);
0831:
0832: delta = ((boxes[0].x + boxes[1].x + boxes[2].x + boxes[3].x) - (boxes[4].x
0833: + boxes[5].x + boxes[6].x + boxes[7].x)) / 4;
0834: dist = delta * delta;
0835: delta = ((boxes[0].y + boxes[1].y + boxes[2].y + boxes[3].y) - (boxes[4].y
0836: + boxes[5].y + boxes[6].y + boxes[7].y)) / 4;
0837: dist += delta * delta;
0838: dist = (float) Math.sqrt(dist);
0839: // Note here that dist is the distance between center
0840: // points, and sz is the sum of the length of the
0841: // diagonals of the letter boxes. In normal cases one
0842: // would expect dist to be approximately equal to sz/2.
0843: // So here we merge if the two characters are within four
0844: // character widths of each other. If they are farther
0845: // apart than that chances are it's a 'line break' or
0846: // something similar where we will get better results
0847: // merging seperately, and anyways with this much space
0848: // between them the extra outline shouldn't hurt..
0849: GeneralPath gp = new GeneralPath();
0850: if (dist < sz) {
0851: // Close enough to merge with previous char...
0852: System.arraycopy(boxes, 0, chull, 0, 8);
0853: int npts = makeConvexHull(chull, 8);
0854: gp.moveTo(chull[0].x, chull[0].y);
0855: for (int n = 1; n < npts; n++)
0856: gp.lineTo(chull[n].x, chull[n].y);
0857: gp.closePath();
0858: } else {
0859: // Merge all previous areas
0860: mergeAreas(shape, areas, nAreas);
0861: nAreas = 0; // Start fresh...
0862:
0863: // Then just add box (add the previous char box if first pts)
0864: if (i == 2) {
0865: gp.moveTo(boxes[0].x, boxes[0].y);
0866: gp.lineTo(boxes[1].x, boxes[1].y);
0867: gp.lineTo(boxes[2].x, boxes[2].y);
0868: gp.lineTo(boxes[3].x, boxes[3].y);
0869: gp.closePath();
0870: shape.append(gp, false);
0871: gp.reset();
0872: }
0873: gp.moveTo(boxes[4].x, boxes[4].y);
0874: gp.lineTo(boxes[5].x, boxes[5].y);
0875: gp.lineTo(boxes[6].x, boxes[6].y);
0876: gp.lineTo(boxes[7].x, boxes[7].y);
0877: gp.closePath();
0878: }
0879: areas[nAreas++] = new Area(gp);
0880: }
0881:
0882: mergeAreas(shape, areas, nAreas);
0883: }
0884:
0885: public static void mergeAreas(GeneralPath shape, Area[] shapes,
0886: int nShapes) {
0887: // Merge areas hierarchically, this means that while there are
0888: // the same number of Area.add calls (n-1) the great majority
0889: // of them are very simple combinations. This helps to speed
0890: // things up a tad...
0891: while (nShapes > 1) {
0892: int n = 0;
0893: for (int i = 1; i < nShapes; i += 2) {
0894: shapes[i - 1].add(shapes[i]);
0895: shapes[n++] = shapes[i - 1];
0896: shapes[i] = null;
0897: }
0898:
0899: // make sure we include the last one if odd.
0900: if ((nShapes & 0x1) == 1)
0901: shapes[n - 1].add(shapes[nShapes - 1]);
0902: nShapes = nShapes / 2;
0903: }
0904: if (nShapes == 1)
0905: shape.append(shapes[0], false);
0906: }
0907:
0908: /**
0909: * Perform hit testing for coordinate at x, y.
0910: *
0911: * @param x the x coordinate of the point to be tested.
0912: * @param y the y coordinate of the point to be tested.
0913: *
0914: * @return a TextHit object encapsulating the character index for
0915: * successful hits and whether the hit is on the character
0916: * leading edge.
0917: */
0918: public TextHit hitTestChar(float x, float y) {
0919: syncLayout();
0920:
0921: TextHit textHit = null;
0922:
0923: int currentChar = 0;
0924: for (int i = 0; i < gv.getNumGlyphs(); i++) {
0925: Shape gbounds = gv.getGlyphLogicalBounds(i);
0926: if (gbounds != null) {
0927: Rectangle2D gbounds2d = gbounds.getBounds2D();
0928: // System.out.println("Hit Test: [" + x + ", " + y + "] - " +
0929: // gbounds2d);
0930: if (gbounds.contains(x, y)) {
0931: boolean isRightHalf = (x > (gbounds2d.getX() + (gbounds2d
0932: .getWidth() / 2d)));
0933: boolean isLeadingEdge = !isRightHalf;
0934: int charIndex = charMap[currentChar];
0935: textHit = new TextHit(charIndex, isLeadingEdge);
0936: return textHit;
0937: }
0938: }
0939: currentChar += getCharacterCount(i, i);
0940: if (currentChar >= charMap.length)
0941: currentChar = charMap.length - 1;
0942: }
0943: return textHit;
0944: }
0945:
0946: //protected
0947:
0948: /**
0949: * Returns the GVTFont to use when rendering the specified
0950: * character iterator. This should already be set as an attribute
0951: * on the aci.
0952: *
0953: * @return The GVTFont to use.
0954: */
0955: protected GVTFont getFont() {
0956: aci.first();
0957: GVTFont gvtFont = (GVTFont) aci.getAttribute(GVT_FONT);
0958:
0959: if (gvtFont != null)
0960: return gvtFont;
0961:
0962: // shouldn't get here
0963: return new AWTGVTFont(aci.getAttributes());
0964: }
0965:
0966: /**
0967: * Returns a shape describing the overline decoration for a given ACI.
0968: */
0969: protected Shape getOverlineShape() {
0970: double y = metrics.getOverlineOffset();
0971: float overlineThickness = metrics.getOverlineThickness();
0972:
0973: // need to move the overline a bit lower,
0974: // not sure if this is correct behaviour or not
0975: y += overlineThickness;
0976:
0977: // Not certain what should be done here...
0978: aci.first();
0979: Float dy = (Float) aci.getAttribute(DY);
0980: if (dy != null)
0981: y += dy.floatValue();
0982:
0983: Stroke overlineStroke = new BasicStroke(overlineThickness);
0984: Rectangle2D logicalBounds = gv.getLogicalBounds();
0985:
0986: return overlineStroke.createStrokedShape(new Line2D.Double(
0987: logicalBounds.getMinX() + overlineThickness / 2.0,
0988: offset.getY() + y, logicalBounds.getMaxX()
0989: - overlineThickness / 2.0, offset.getY() + y));
0990: }
0991:
0992: /**
0993: * Returns a shape describing the undeline decoration for a given ACI.
0994: */
0995: protected Shape getUnderlineShape() {
0996:
0997: double y = metrics.getUnderlineOffset();
0998: float underlineThickness = metrics.getUnderlineThickness();
0999:
1000: // need to move the underline a bit lower,
1001: // not sure if this is correct behaviour or not
1002: y += underlineThickness * 1.5;
1003:
1004: BasicStroke underlineStroke = new BasicStroke(
1005: underlineThickness);
1006:
1007: // Not certain what should be done here...
1008: aci.first();
1009: Float dy = (Float) aci.getAttribute(DY);
1010: if (dy != null)
1011: y += dy.floatValue();
1012:
1013: Rectangle2D logicalBounds = gv.getLogicalBounds();
1014:
1015: return underlineStroke.createStrokedShape(new Line2D.Double(
1016: logicalBounds.getMinX() + underlineThickness / 2.0,
1017: offset.getY() + y, logicalBounds.getMaxX()
1018: - underlineThickness / 2.0, offset.getY() + y));
1019: }
1020:
1021: /**
1022: * Returns a shape describing the strikethrough line for a given ACI.
1023: */
1024: protected Shape getStrikethroughShape() {
1025: double y = metrics.getStrikethroughOffset();
1026: float strikethroughThickness = metrics
1027: .getStrikethroughThickness();
1028:
1029: Stroke strikethroughStroke = new BasicStroke(
1030: strikethroughThickness);
1031:
1032: // Not certain what should be done here...
1033: aci.first();
1034: Float dy = (Float) aci.getAttribute(DY);
1035: if (dy != null)
1036: y += dy.floatValue();
1037:
1038: Rectangle2D logicalBounds = gv.getLogicalBounds();
1039: return strikethroughStroke
1040: .createStrokedShape(new Line2D.Double(logicalBounds
1041: .getMinX()
1042: + strikethroughThickness / 2.0, offset.getY()
1043: + y, logicalBounds.getMaxX()
1044: - strikethroughThickness / 2.0, offset.getY()
1045: + y));
1046: }
1047:
1048: /**
1049: * Explicitly lays out each of the glyphs in the glyph
1050: * vector. This will handle any glyph position adjustments such as
1051: * dx, dy and baseline offsets. It will also handle vertical
1052: * layouts.
1053: */
1054: protected void doExplicitGlyphLayout() {
1055:
1056: this .gv.performDefaultLayout();
1057:
1058: float baselineAscent = vertical ? (float) gv.getLogicalBounds()
1059: .getWidth() : (metrics.getAscent() + Math.abs(metrics
1060: .getDescent()));
1061:
1062: int numGlyphs = gv.getNumGlyphs();
1063: // System.out.println("NumGlyphs: " + numGlyphs);
1064:
1065: float[] gp = gv.getGlyphPositions(0, numGlyphs + 1, null);
1066: float verticalFirstOffset = 0f;
1067: float horizontalFirstOffset = 0f;
1068:
1069: boolean glyphOrientationAuto = isGlyphOrientationAuto();
1070: int glyphOrientationAngle = 0;
1071: if (!glyphOrientationAuto) {
1072: glyphOrientationAngle = getGlyphOrientationAngle();
1073: }
1074: int i = 0;
1075: int aciStart = aci.getBeginIndex();
1076: int aciIndex = 0;
1077: char ch = aci.first();
1078: int runLimit = aciIndex + aciStart;
1079:
1080: Float x = null, y = null, dx = null, dy = null, rotation = null;
1081: Object baseline = null;
1082:
1083: float shift_x_pos = 0;
1084: float shift_y_pos = 0;
1085: float curr_x_pos = (float) offset.getX();
1086: float curr_y_pos = (float) offset.getY();
1087:
1088: Point2D.Float pos = new Point2D.Float();
1089: boolean hasArabicTransparent = false;
1090:
1091: while (i < numGlyphs) {
1092: //System.out.println("limit: " + runLimit + ", " + aciIndex);
1093: if (aciIndex + aciStart >= runLimit) {
1094: runLimit = aci.getRunLimit(runAtts);
1095: x = (Float) aci.getAttribute(X);
1096: y = (Float) aci.getAttribute(Y);
1097: dx = (Float) aci.getAttribute(DX);
1098: dy = (Float) aci.getAttribute(DY);
1099: rotation = (Float) aci.getAttribute(ROTATION);
1100: baseline = aci.getAttribute(BASELINE_SHIFT);
1101: }
1102:
1103: GVTGlyphMetrics gm = gv.getGlyphMetrics(i);
1104:
1105: if (i == 0) {
1106: if (isVertical()) {
1107: if (glyphOrientationAuto) {
1108: if (isLatinChar(ch)) {
1109: // it will be rotated 90
1110: verticalFirstOffset = 0f;
1111: } else {
1112: // it won't be rotated
1113: float advY = gm.getVerticalAdvance();
1114: float asc = metrics.getAscent();
1115: float dsc = metrics.getDescent();
1116: verticalFirstOffset = asc
1117: + (advY - (asc + dsc)) / 2;
1118: }
1119: } else {
1120: if (glyphOrientationAngle == 0) {
1121: float advY = gm.getVerticalAdvance();
1122: float asc = metrics.getAscent();
1123: float dsc = metrics.getDescent();
1124: verticalFirstOffset = asc
1125: + (advY - (asc + dsc)) / 2;
1126: } else {
1127: // 90, 180, 270
1128: verticalFirstOffset = 0f;
1129: }
1130: }
1131: } else { // not vertical
1132: if ((glyphOrientationAngle == 270)) {
1133: horizontalFirstOffset = (float) gm
1134: .getBounds2D().getHeight();
1135: } else {
1136: // 0, 90, 180
1137: horizontalFirstOffset = 0;
1138: }
1139: }
1140: } else { // not the first char
1141: if (glyphOrientationAuto && (verticalFirstOffset == 0f)
1142: && !isLatinChar(ch)) {
1143: float advY = gm.getVerticalAdvance();
1144: float asc = metrics.getAscent();
1145: float dsc = metrics.getDescent();
1146: verticalFirstOffset = asc + (advY - (asc + dsc))
1147: / 2;
1148: }
1149: }
1150:
1151: // ox and oy are origin adjustments for each glyph,
1152: // computed on the basis of baseline-shifts, etc.
1153: float ox = 0f;
1154: float oy = 0f;
1155: float glyphOrientationRotation = 0f;
1156: float glyphRotation = 0f;
1157:
1158: if (ch != CharacterIterator.DONE) {
1159: if (vertical) {
1160: if (glyphOrientationAuto) {
1161: if (isLatinChar(ch)) {
1162: // If character is Latin, then rotate by
1163: // 90 degrees
1164: glyphOrientationRotation = (float) (Math.PI / 2f);
1165: } else {
1166: glyphOrientationRotation = 0f;
1167: }
1168: } else {
1169: glyphOrientationRotation = (float) Math
1170: .toRadians(glyphOrientationAngle);
1171: }
1172: if (textPath != null) {
1173: // if vertical and on a path, any x's are ignored
1174: x = null;
1175: }
1176: } else {
1177: glyphOrientationRotation = (float) Math
1178: .toRadians(glyphOrientationAngle);
1179: if (textPath != null) {
1180: // if horizontal and on a path, any y's are ignored
1181: y = null;
1182: }
1183: }
1184:
1185: // calculate the total rotation for this glyph
1186: if (rotation == null || rotation.isNaN()) {
1187: glyphRotation = glyphOrientationRotation;
1188: } else {
1189: glyphRotation = (rotation.floatValue() + glyphOrientationRotation);
1190: }
1191:
1192: if ((x != null) && !x.isNaN()) {
1193: if (i == 0)
1194: shift_x_pos = (float) (x.floatValue() - offset
1195: .getX());
1196: curr_x_pos = x.floatValue() - shift_x_pos;
1197: }
1198: if (dx != null && !dx.isNaN()) {
1199: curr_x_pos += dx.floatValue();
1200: }
1201:
1202: if ((y != null) && !y.isNaN()) {
1203: if (i == 0)
1204: shift_y_pos = (float) (y.floatValue() - offset
1205: .getY());
1206: curr_y_pos = y.floatValue() - shift_y_pos;
1207: }
1208: if (dy != null && !dy.isNaN()) {
1209: curr_y_pos += dy.floatValue();
1210: } else if (i > 0) {
1211: curr_y_pos += gp[i * 2 + 1] - gp[i * 2 - 1];
1212: }
1213:
1214: float baselineAdjust = 0f;
1215: if (baseline != null) {
1216: if (baseline instanceof Integer) {
1217: if (baseline == TextAttribute.SUPERSCRIPT_SUPER) {
1218: baselineAdjust = baselineAscent * 0.5f;
1219: } else if (baseline == TextAttribute.SUPERSCRIPT_SUB) {
1220: baselineAdjust = -baselineAscent * 0.5f;
1221: }
1222: } else if (baseline instanceof Float) {
1223: baselineAdjust = ((Float) baseline)
1224: .floatValue();
1225: }
1226: if (vertical) {
1227: ox = baselineAdjust;
1228: } else {
1229: oy = -baselineAdjust;
1230: }
1231: }
1232:
1233: if (vertical) {
1234: // offset due to rotation of first character
1235: oy += verticalFirstOffset;
1236:
1237: if (glyphOrientationAuto) {
1238: if (isLatinChar(ch)) {
1239: ox += metrics.getStrikethroughOffset();
1240: } else {
1241: Rectangle2D glyphBounds = gv
1242: .getGlyphVisualBounds(i)
1243: .getBounds2D();
1244: ox -= (float) ((glyphBounds.getMaxX() - gp[2 * i]) - glyphBounds
1245: .getWidth() / 2);
1246: }
1247: } else {
1248: // center the character if it's not auto orient
1249: Rectangle2D glyphBounds = gv
1250: .getGlyphVisualBounds(i).getBounds2D();
1251: if (glyphOrientationAngle == 0) {
1252: ox -= (float) ((glyphBounds.getMaxX() - gp[2 * i]) - glyphBounds
1253: .getWidth() / 2);
1254: } else if (glyphOrientationAngle == 180) {
1255: ox += (float) ((glyphBounds.getMaxX() - gp[2 * i]) - glyphBounds
1256: .getWidth() / 2);
1257: } else if (glyphOrientationAngle == 90) {
1258: ox += metrics.getStrikethroughOffset();
1259: } else { // 270
1260: ox -= metrics.getStrikethroughOffset();
1261: }
1262: }
1263: } else {
1264: ox += horizontalFirstOffset;
1265: if (glyphOrientationAngle == 90) {
1266: oy -= gm.getHorizontalAdvance();
1267: } else if (glyphOrientationAngle == 180) {
1268: oy -= metrics.getAscent();
1269: }
1270: }
1271: }
1272:
1273: // set the new glyph position
1274: pos.x = curr_x_pos + ox;
1275: pos.y = curr_y_pos + oy;
1276: gv.setGlyphPosition(i, pos);
1277:
1278: // calculate the position of the next glyph
1279: if (ArabicTextHandler.arabicCharTransparent(ch)) {
1280: hasArabicTransparent = true;
1281: } else {
1282: // Apply the advance if the current char is not transparent
1283: if (vertical) {
1284: float advanceY = 0;
1285: if (glyphOrientationAuto) {
1286: if (isLatinChar(ch)) {
1287: advanceY = gm.getHorizontalAdvance();
1288: } else {
1289: advanceY = gm.getVerticalAdvance();
1290: }
1291: } else {
1292: if ((glyphOrientationAngle == 0)
1293: || (glyphOrientationAngle == 180)) {
1294: advanceY = gm.getVerticalAdvance();
1295: } else if (glyphOrientationAngle == 90) {
1296: advanceY = gm.getHorizontalAdvance();
1297: } else { // 270
1298: advanceY = gm.getHorizontalAdvance();
1299: // need to translate so that the spacing
1300: // between chars is correct
1301: gv.setGlyphTransform(i, AffineTransform
1302: .getTranslateInstance(0, advanceY));
1303: }
1304: }
1305: curr_y_pos += advanceY;
1306: } else {
1307: float advanceX = 0;
1308: if (glyphOrientationAngle == 0) {
1309: advanceX = gm.getHorizontalAdvance();
1310: } else if (glyphOrientationAngle == 180) {
1311: advanceX = gm.getHorizontalAdvance();
1312: // need to translate so that the spacing
1313: // between chars is correct
1314: gv.setGlyphTransform(i, AffineTransform
1315: .getTranslateInstance(advanceX, 0));
1316: } else {
1317: // 90, 270
1318: advanceX = gm.getVerticalAdvance();
1319: }
1320: curr_x_pos += advanceX;
1321: }
1322: }
1323:
1324: // rotate the glyph
1325: if (!epsEQ(glyphRotation, 0)) {
1326: AffineTransform glyphTransform = gv
1327: .getGlyphTransform(i);
1328: if (glyphTransform == null) {
1329: glyphTransform = new AffineTransform();
1330: }
1331: AffineTransform rotAt;
1332: // Make the 90Deg rotations slightly 'snap to'.
1333: // Also use explicit matrix to avoid round-off.
1334: if (epsEQ(glyphRotation, Math.PI / 2)) {
1335: rotAt = new AffineTransform(0, 1, -1, 0, 0, 0);
1336: } else if (epsEQ(glyphRotation, Math.PI)) {
1337: rotAt = new AffineTransform(-1, 0, 0, -1, 0, 0);
1338: } else if (epsEQ(glyphRotation, 3 * Math.PI / 2)) {
1339: rotAt = new AffineTransform(0, -1, 1, 0, 0, 0);
1340: } else {
1341: rotAt = AffineTransform
1342: .getRotateInstance(glyphRotation);
1343: }
1344: glyphTransform.concatenate(rotAt);
1345: gv.setGlyphTransform(i, glyphTransform);
1346: }
1347:
1348: aciIndex += gv.getCharacterCount(i, i);
1349: if (aciIndex >= charMap.length)
1350: aciIndex = charMap.length - 1;
1351: ch = aci.setIndex(aciIndex + aciStart);
1352: i++;
1353: }
1354: // Update last glyph pos
1355: pos.x = curr_x_pos;
1356: pos.y = curr_y_pos;
1357: gv.setGlyphPosition(i, pos);
1358:
1359: advance = new Point2D.Float(
1360: (float) (curr_x_pos - offset.getX()),
1361: (float) (curr_y_pos - offset.getY()));
1362:
1363: // Do a last pass positioning the transparent/mark glyphs on the
1364: // base glyphs.
1365: if (hasArabicTransparent) {
1366: ch = aci.first();
1367: aciIndex = 0;
1368: i = 0;
1369: int transparentStart = -1;
1370: while (i < numGlyphs) {
1371: if (ArabicTextHandler.arabicCharTransparent(ch)) {
1372: if (transparentStart == -1)
1373: transparentStart = i;
1374: } else {
1375: if (transparentStart != -1) {
1376: Point2D loc = gv.getGlyphPosition(i);
1377: GVTGlyphMetrics gm = gv.getGlyphMetrics(i);
1378: int tyS = 0, txS = 0;
1379: float advX = 0, advY = 0;
1380: if (vertical) {
1381: if (glyphOrientationAuto
1382: || (glyphOrientationAngle == 90))
1383: advY = gm.getHorizontalAdvance();
1384: else if (glyphOrientationAngle == 270)
1385: advY = 0;
1386: else if (glyphOrientationAngle == 0)
1387: advX = gm.getHorizontalAdvance();
1388: else
1389: // 180
1390: advX = -gm.getHorizontalAdvance();
1391: } else {
1392: if (glyphOrientationAngle == 0)
1393: advX = gm.getHorizontalAdvance();
1394: else if (glyphOrientationAngle == 90)
1395: advY = gm.getHorizontalAdvance();
1396: else if (glyphOrientationAngle == 180)
1397: advX = 0;
1398: else
1399: // 270
1400: advY = -gm.getHorizontalAdvance();
1401: }
1402: float baseX = (float) (loc.getX() + advX);
1403: float baseY = (float) (loc.getY() + advY);
1404: for (int j = transparentStart; j < i; j++) {
1405: Point2D locT = gv.getGlyphPosition(j);
1406: GVTGlyphMetrics gmT = gv.getGlyphMetrics(j);
1407: float locX = (float) locT.getX();
1408: float locY = (float) locT.getY();
1409: float tx = 0, ty = 0;
1410: float advT = gmT.getHorizontalAdvance();
1411: if (vertical) {
1412: if (glyphOrientationAuto
1413: || (glyphOrientationAngle == 90))
1414: locY = baseY - advT;
1415: else if (glyphOrientationAngle == 270)
1416: locY = baseY + advT;
1417: else if (glyphOrientationAngle == 0)
1418: locX = baseX - advT;
1419: else
1420: // 180deg
1421: locX = baseX + advT;
1422: } else {
1423: if (glyphOrientationAngle == 0)
1424: locX = baseX - advT;
1425: else if (glyphOrientationAngle == 90)
1426: locY = baseY - advT;
1427: else if (glyphOrientationAngle == 180)
1428: locX = baseX + advT;
1429: else
1430: // 270
1431: locY = baseY + advT;
1432: }
1433:
1434: locT = new Point2D.Double(locX, locY);
1435: gv.setGlyphPosition(j, locT);
1436: if ((txS != 0) || (tyS != 0)) {
1437: AffineTransform at;
1438: at = AffineTransform
1439: .getTranslateInstance(tx, ty);
1440: at.concatenate(gv.getGlyphTransform(i));
1441: gv.setGlyphTransform(i, at);
1442: }
1443: }
1444: transparentStart = -1;
1445: }
1446: }
1447: aciIndex += gv.getCharacterCount(i, i);
1448: if (aciIndex >= charMap.length)
1449: aciIndex = charMap.length - 1;
1450: ch = aci.setIndex(aciIndex + aciStart);
1451: i++;
1452: }
1453:
1454: }
1455:
1456: layoutApplied = true;
1457: spacingApplied = false;
1458: glyphAdvances = null;
1459: pathApplied = false;
1460: }
1461:
1462: /**
1463: * Does any spacing adjustments that may have been specified.
1464: */
1465: protected void adjustTextSpacing() {
1466:
1467: if (spacingApplied)
1468: // Nothing to do...
1469: return;
1470:
1471: if (!layoutApplied)
1472: // Must have clean layout to do spacing...
1473: doExplicitGlyphLayout();
1474:
1475: aci.first();
1476: Boolean customSpacing = (Boolean) aci
1477: .getAttribute(GVTAttributedCharacterIterator.TextAttribute.CUSTOM_SPACING);
1478: if ((customSpacing != null) && customSpacing.booleanValue()) {
1479: advance = doSpacing(
1480: (Float) aci
1481: .getAttribute(GVTAttributedCharacterIterator.TextAttribute.KERNING),
1482: (Float) aci
1483: .getAttribute(GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING),
1484: (Float) aci
1485: .getAttribute(GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING));
1486: // Basic layout is now messed up...
1487: layoutApplied = false;
1488: }
1489:
1490: // This will clear layoutApplied if it mucks with the current
1491: // character positions.
1492: applyStretchTransform(!adjSpacing);
1493:
1494: spacingApplied = true;
1495: pathApplied = false;
1496: }
1497:
1498: /**
1499: * Performs any spacing adjustments required and returns the new advance
1500: * value.
1501: *
1502: * @param kern The kerning adjustment to apply to the space
1503: * between each char.
1504: * @param letterSpacing The amount of spacing required between each char.
1505: * @param wordSpacing The amount of spacing required between each word. */
1506: protected Point2D doSpacing(Float kern, Float letterSpacing,
1507: Float wordSpacing) {
1508: boolean autoKern = true;
1509: boolean doWordSpacing = false;
1510: boolean doLetterSpacing = false;
1511: float kernVal = 0f;
1512: float letterSpacingVal = 0f;
1513:
1514: if ((kern != null) && (!kern.isNaN())) {
1515: kernVal = kern.floatValue();
1516: autoKern = false;
1517: //System.out.println("KERNING: "+kernVal);
1518: }
1519: if ((letterSpacing != null) && (!letterSpacing.isNaN())) {
1520: letterSpacingVal = letterSpacing.floatValue();
1521: doLetterSpacing = true;
1522: //System.out.println("LETTER-SPACING: "+letterSpacingVal);
1523: }
1524: if ((wordSpacing != null) && (!wordSpacing.isNaN())) {
1525: doWordSpacing = true;
1526: }
1527:
1528: int numGlyphs = gv.getNumGlyphs();
1529:
1530: float dx = 0f;
1531: float dy = 0f;
1532: Point2D[] newPositions = new Point2D[numGlyphs + 1];
1533: Point2D prevPos = gv.getGlyphPosition(0);
1534: int prevCode = gv.getGlyphCode(0);
1535: float x = (float) prevPos.getX();
1536: float y = (float) prevPos.getY();
1537:
1538: Point2D lastCharAdvance = new Point2D.Double(
1539: advance.getX()
1540: - (gv.getGlyphPosition(numGlyphs - 1).getX() - x),
1541: advance.getY()
1542: - (gv.getGlyphPosition(numGlyphs - 1).getY() - y));
1543:
1544: try {
1545: GVTFont font = gv.getFont();
1546: // do letter spacing first
1547: if ((numGlyphs > 1) && (doLetterSpacing || !autoKern)) {
1548: for (int i = 1; i <= numGlyphs; ++i) {
1549: Point2D gpos = gv.getGlyphPosition(i);
1550: int currCode;
1551: currCode = (i == numGlyphs) ? -1 : gv
1552: .getGlyphCode(i);
1553: dx = (float) gpos.getX() - (float) prevPos.getX();
1554: dy = (float) gpos.getY() - (float) prevPos.getY();
1555: if (autoKern) {
1556: if (vertical)
1557: dy += letterSpacingVal;
1558: else
1559: dx += letterSpacingVal;
1560: } else {
1561: // apply explicit kerning adjustments,
1562: // removing any auto-kern values
1563: if (vertical) {
1564: float vKern = 0;
1565: if (currCode != -1)
1566: vKern = font.getVKern(prevCode,
1567: currCode);
1568: dy += kernVal - vKern + letterSpacingVal;
1569: } else {
1570: float hKern = 0;
1571: if (currCode != -1)
1572: hKern = font.getHKern(prevCode,
1573: currCode);
1574: dx += kernVal - hKern + letterSpacingVal;
1575: }
1576: }
1577: x += dx;
1578: y += dy;
1579: newPositions[i] = new Point2D.Float(x, y);
1580: prevPos = gpos;
1581: prevCode = currCode;
1582: }
1583:
1584: for (int i = 1; i <= numGlyphs; ++i) { // assign the new positions
1585: if (newPositions[i] != null) {
1586: gv.setGlyphPosition(i, newPositions[i]);
1587: }
1588: }
1589: }
1590:
1591: // adjust the advance of the last character
1592: if (vertical) {
1593: lastCharAdvance.setLocation(lastCharAdvance.getX(),
1594: lastCharAdvance.getY() + kernVal
1595: + letterSpacingVal);
1596: } else {
1597: lastCharAdvance.setLocation(lastCharAdvance.getX()
1598: + kernVal + letterSpacingVal, lastCharAdvance
1599: .getY());
1600: }
1601:
1602: // now do word spacing
1603: dx = 0f;
1604: dy = 0f;
1605: prevPos = gv.getGlyphPosition(0);
1606: x = (float) prevPos.getX();
1607: y = (float) prevPos.getY();
1608:
1609: if ((numGlyphs > 1) && (doWordSpacing)) {
1610: for (int i = 1; i < numGlyphs; i++) {
1611: Point2D gpos = gv.getGlyphPosition(i);
1612: dx = (float) gpos.getX() - (float) prevPos.getX();
1613: dy = (float) gpos.getY() - (float) prevPos.getY();
1614: boolean inWS = false;
1615: // while this is whitespace, increment
1616: int beginWS = i;
1617: int endWS = i;
1618: GVTGlyphMetrics gm = gv.getGlyphMetrics(i);
1619:
1620: // BUG: gm.isWhitespace() fails for latin SPACE glyph!
1621: while ((gm.getBounds2D().getWidth() < 0.01d)
1622: || gm.isWhitespace()) {
1623: if (!inWS)
1624: inWS = true;
1625: if (i == numGlyphs - 1) {
1626: // white space at the end
1627: break;
1628: }
1629: ++i;
1630: ++endWS;
1631: gpos = gv.getGlyphPosition(i);
1632: gm = gv.getGlyphMetrics(i);
1633: }
1634:
1635: if (inWS) { // apply wordSpacing
1636: int nWS = endWS - beginWS;
1637: float px = (float) prevPos.getX();
1638: float py = (float) prevPos.getY();
1639: dx = (float) (gpos.getX() - px) / (nWS + 1);
1640: dy = (float) (gpos.getY() - py) / (nWS + 1);
1641: if (vertical) {
1642: dy += wordSpacing.floatValue() / (nWS + 1);
1643: } else {
1644: dx += wordSpacing.floatValue() / (nWS + 1);
1645: }
1646: for (int j = beginWS; j <= endWS; ++j) {
1647: x += dx;
1648: y += dy;
1649: newPositions[j] = new Point2D.Float(x, y);
1650: }
1651: } else {
1652: dx = (float) (gpos.getX() - prevPos.getX());
1653: dy = (float) (gpos.getY() - prevPos.getY());
1654: x += dx;
1655: y += dy;
1656: newPositions[i] = new Point2D.Float(x, y);
1657: }
1658: prevPos = gpos;
1659: }
1660: Point2D gPos = gv.getGlyphPosition(numGlyphs);
1661: x += (float) (gPos.getX() - prevPos.getX());
1662: y += (float) (gPos.getY() - prevPos.getY());
1663: newPositions[numGlyphs] = new Point2D.Float(x, y);
1664:
1665: for (int i = 1; i <= numGlyphs; ++i) { // assign the new positions
1666: if (newPositions[i] != null) {
1667: gv.setGlyphPosition(i, newPositions[i]);
1668: }
1669: }
1670: }
1671:
1672: } catch (Exception e) {
1673: e.printStackTrace();
1674: }
1675:
1676: // calculate the new advance
1677: double advX = gv.getGlyphPosition(numGlyphs - 1).getX()
1678: - gv.getGlyphPosition(0).getX();
1679: double advY = gv.getGlyphPosition(numGlyphs - 1).getY()
1680: - gv.getGlyphPosition(0).getY();
1681: Point2D newAdvance = new Point2D.Double(advX
1682: + lastCharAdvance.getX(), advY + lastCharAdvance.getY());
1683: return newAdvance;
1684: }
1685:
1686: /**
1687: * Stretches the text so that it becomes the specified length.
1688: *
1689: * @param stretchGlyphs if true xScale, yScale will be applied to
1690: * each glyphs transform.
1691: */
1692: protected void applyStretchTransform(boolean stretchGlyphs) {
1693: if ((xScale == 1) && (yScale == 1))
1694: return;
1695:
1696: AffineTransform scaleAT = AffineTransform.getScaleInstance(
1697: xScale, yScale);
1698:
1699: int numGlyphs = gv.getNumGlyphs();
1700: float[] gp = gv.getGlyphPositions(0, numGlyphs + 1, null);
1701:
1702: float initX = gp[0];
1703: float initY = gp[1];
1704: Point2D.Float pos = new Point2D.Float();
1705: for (int i = 0; i <= numGlyphs; i++) {
1706: float dx = gp[2 * i] - initX;
1707: float dy = gp[2 * i + 1] - initY;
1708: pos.x = initX + dx * xScale;
1709: pos.y = initY + dy * yScale;
1710: gv.setGlyphPosition(i, pos);
1711:
1712: if ((stretchGlyphs) && (i != numGlyphs)) {
1713: // stretch the glyph
1714: AffineTransform glyphTransform = gv
1715: .getGlyphTransform(i);
1716: if (glyphTransform != null) {
1717: glyphTransform.preConcatenate(scaleAT);
1718: gv.setGlyphTransform(i, glyphTransform);
1719: } else {
1720: gv.setGlyphTransform(i, scaleAT);
1721: }
1722: }
1723: }
1724:
1725: advance = new Point2D.Float((float) (advance.getX() * xScale),
1726: (float) (advance.getY() * yScale));
1727: // Basic layout is now messed up...
1728: layoutApplied = false;
1729: }
1730:
1731: /**
1732: * If this layout is on a text path, positions the characters
1733: * along the path.
1734: */
1735: protected void doPathLayout() {
1736: if (pathApplied)
1737: return;
1738:
1739: if (!spacingApplied)
1740: // This will layout the text if needed.
1741: adjustTextSpacing();
1742:
1743: getGlyphAdvances();
1744:
1745: // if doesn't have an attached text path, just return
1746: if (textPath == null) {
1747: // We applied the empty path (i.e. do nothing).
1748: pathApplied = true;
1749: return;
1750: }
1751:
1752: boolean horizontal = !isVertical();
1753:
1754: boolean glyphOrientationAuto = isGlyphOrientationAuto();
1755: int glyphOrientationAngle = 0;
1756: if (!glyphOrientationAuto) {
1757: glyphOrientationAngle = getGlyphOrientationAngle();
1758: }
1759:
1760: float pathLength = textPath.lengthOfPath();
1761: float startOffset = textPath.getStartOffset();
1762: int numGlyphs = gv.getNumGlyphs();
1763:
1764: // make sure all glyphs visible again, this maybe just a change in
1765: // offset so they may have been made invisible in a previous
1766: // pathLayout call
1767: for (int i = 0; i < numGlyphs; i++) {
1768: gv.setGlyphVisible(i, true);
1769: }
1770:
1771: // calculate the total length of the glyphs, this will become be
1772: // the length along the path that is used by the text
1773: float glyphsLength;
1774: if (horizontal) {
1775: glyphsLength = (float) gv.getLogicalBounds().getWidth();
1776: } else {
1777: glyphsLength = (float) gv.getLogicalBounds().getHeight();
1778: }
1779:
1780: // check that pathLength and glyphsLength are not 0
1781: if (pathLength == 0f || glyphsLength == 0f) {
1782: // We applied the empty path.
1783: pathApplied = true;
1784: textPathAdvance = advance;
1785: return;
1786: }
1787:
1788: // the current start point of the character on the path
1789: // calculate the offset of the first glyph the offset will be
1790: // 0 if the glyph is on the path (ie. not adjusted by a dy or
1791: // dx)
1792: Point2D firstGlyphPosition = gv.getGlyphPosition(0);
1793: float glyphOffset = 0; // offset perpendicular to path
1794: float currentPosition;
1795: if (horizontal) {
1796: glyphOffset = (float) (firstGlyphPosition.getY());
1797: currentPosition = (float) (firstGlyphPosition.getX() + startOffset);
1798: } else {
1799: glyphOffset = (float) (firstGlyphPosition.getX());
1800: currentPosition = (float) (firstGlyphPosition.getY() + startOffset);
1801: }
1802:
1803: char ch = aci.first();
1804: int start = aci.getBeginIndex();
1805: int currentChar = 0;
1806: int lastGlyphDrawn = -1;
1807: float lastGlyphAdvance = 0;
1808: // iterate through the GlyphVector placing each glyph
1809: for (int i = 0; i < numGlyphs; i++) {
1810:
1811: Point2D currentGlyphPos = gv.getGlyphPosition(i);
1812:
1813: // calculate the advance and offset for the next glyph, do it
1814: // now before we modify the current glyph position
1815:
1816: float glyphAdvance = 0; // along path
1817: float nextGlyphOffset = 0; // perpendicular to path eg dy or dx
1818: Point2D nextGlyphPosition = gv.getGlyphPosition(i + 1);
1819: if (horizontal) {
1820: glyphAdvance = (float) (nextGlyphPosition.getX() - currentGlyphPos
1821: .getX());
1822: nextGlyphOffset = (float) (nextGlyphPosition.getY() - currentGlyphPos
1823: .getY());
1824: } else {
1825: glyphAdvance = (float) (nextGlyphPosition.getY() - currentGlyphPos
1826: .getY());
1827: nextGlyphOffset = (float) (nextGlyphPosition.getX() - currentGlyphPos
1828: .getX());
1829: }
1830:
1831: // calculate the center line position for the glyph
1832: Rectangle2D glyphBounds = gv.getGlyphOutline(i)
1833: .getBounds2D();
1834: float glyphWidth = (float) glyphBounds.getWidth();
1835: float glyphHeight = (float) glyphBounds.getHeight();
1836: float glyphMidX = 0;
1837: if (glyphWidth > 0) {
1838: glyphMidX = (float) (glyphBounds.getX() + glyphWidth / 2f);
1839: glyphMidX -= (float) currentGlyphPos.getX();
1840: }
1841:
1842: float glyphMidY = 0;
1843: if (glyphHeight > 0) {
1844: glyphMidY = (float) (glyphBounds.getY() + glyphHeight / 2f);
1845: glyphMidY -= (float) currentGlyphPos.getY();
1846: }
1847:
1848: // System.err.println("GMX: " + glyphMidX +
1849: // " W2: " + (glyphWidth/2) +
1850: // " PosX: " + currentGlyphPos.getX() +
1851: // " BX: " + glyphBounds.getX());
1852: //
1853: // System.err.println("GMY: " + glyphMidY +
1854: // " H2: " + (glyphHeight/2) +
1855: // " PosY: " + currentGlyphPos.getY() +
1856: // " BY: " + glyphBounds.getY());
1857:
1858: float charMidPos;
1859: if (horizontal) {
1860: charMidPos = currentPosition + glyphMidX;
1861: } else {
1862: charMidPos = currentPosition + glyphMidY;
1863: }
1864:
1865: // Calculate the actual point to place the glyph around
1866: Point2D charMidPoint = textPath.pointAtLength(charMidPos);
1867:
1868: // Check if the glyph is actually on the path
1869: if (charMidPoint != null) {
1870:
1871: // Calculate the normal to the path (midline of glyph)
1872: float angle = textPath.angleAtLength(charMidPos);
1873:
1874: // Define the transform of the glyph
1875: AffineTransform glyphPathTransform = new AffineTransform();
1876:
1877: // rotate midline of glyph to be normal to path
1878: if (horizontal) {
1879: glyphPathTransform.rotate(angle);
1880: } else {
1881: glyphPathTransform.rotate(angle - (Math.PI / 2));
1882: }
1883:
1884: // re-apply any offset eg from tspan, or spacing adjust
1885: if (horizontal) {
1886: glyphPathTransform.translate(0, glyphOffset);
1887: } else {
1888: glyphPathTransform.translate(glyphOffset, 0);
1889: }
1890:
1891: // translate glyph backwards so we rotate about the
1892: // center of the glyph
1893: if (horizontal) {
1894: glyphPathTransform.translate(-glyphMidX, 0f);
1895: } else {
1896: glyphPathTransform.translate(0f, -glyphMidY);
1897: }
1898:
1899: // set the new glyph position and transform
1900: AffineTransform glyphTransform = gv
1901: .getGlyphTransform(i);
1902: if (glyphTransform != null) {
1903: glyphPathTransform.concatenate(glyphTransform);
1904: }
1905:
1906: gv.setGlyphTransform(i, glyphPathTransform);
1907: gv.setGlyphPosition(i, charMidPoint);
1908: // keep track of the last glyph drawn to make calculating the
1909: // textPathAdvance value easier later
1910: lastGlyphDrawn = i;
1911: lastGlyphAdvance = glyphAdvance;
1912:
1913: } else {
1914: // not on path so don't render
1915: gv.setGlyphVisible(i, false);
1916: }
1917: currentPosition += glyphAdvance;
1918: glyphOffset += nextGlyphOffset;
1919: currentChar += gv.getCharacterCount(i, i);
1920: if (currentChar >= charMap.length)
1921: currentChar = charMap.length - 1;
1922: ch = aci.setIndex(currentChar + start);
1923: }
1924:
1925: // store the position where a following glyph should be drawn,
1926: // note: this will only be used if the following text layout is not
1927: // on a text path
1928: if (lastGlyphDrawn > -1) {
1929: Point2D lastGlyphPos = gv.getGlyphPosition(lastGlyphDrawn);
1930: if (horizontal) {
1931: textPathAdvance = new Point2D.Double(lastGlyphPos
1932: .getX()
1933: + lastGlyphAdvance, lastGlyphPos.getY());
1934: } else {
1935: textPathAdvance = new Point2D.Double(lastGlyphPos
1936: .getX(), lastGlyphPos.getY() + lastGlyphAdvance);
1937: }
1938: } else {
1939: textPathAdvance = new Point2D.Double(0, 0);
1940: }
1941:
1942: // The default layout is junk now...
1943: layoutApplied = false;
1944: // The spacing stuff is junk now.
1945: spacingApplied = false;
1946: pathApplied = true;
1947: }
1948:
1949: /**
1950: * Returns true if the specified character is within one of the Latin
1951: * unicode character blocks.
1952: *
1953: * @param c The char to test.
1954: *
1955: * @return True if c is latin.
1956: */
1957: protected boolean isLatinChar(char c) {
1958:
1959: if (c < 255 && Character.isLetterOrDigit(c)) {
1960: // cheap quick check, should catch most lation-chars
1961: return true;
1962: }
1963:
1964: Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
1965:
1966: if (block == Character.UnicodeBlock.BASIC_LATIN
1967: || block == Character.UnicodeBlock.LATIN_1_SUPPLEMENT
1968: || block == Character.UnicodeBlock.LATIN_EXTENDED_ADDITIONAL
1969: || block == Character.UnicodeBlock.LATIN_EXTENDED_A
1970: || block == Character.UnicodeBlock.LATIN_EXTENDED_B
1971: || block == Character.UnicodeBlock.ARABIC
1972: || block == Character.UnicodeBlock.ARABIC_PRESENTATION_FORMS_A
1973: || block == Character.UnicodeBlock.ARABIC_PRESENTATION_FORMS_B) {
1974: return true;
1975: }
1976: return false;
1977: }
1978:
1979: /**
1980: * Returns whether or not the vertical glyph orientation value is "auto".
1981: */
1982: protected boolean isGlyphOrientationAuto() {
1983: if (!isVertical())
1984: return false;
1985: aci.first();
1986: Integer vOrient = (Integer) aci
1987: .getAttribute(VERTICAL_ORIENTATION);
1988: if (vOrient != null) {
1989: return (vOrient == ORIENTATION_AUTO);
1990: }
1991: return true;
1992: }
1993:
1994: /**
1995: * Returns the value of the vertical glyph orientation angle. This will be
1996: * one of 0, 90, 180 or 270.
1997: */
1998: protected int getGlyphOrientationAngle() {
1999:
2000: int glyphOrientationAngle = 0;
2001:
2002: aci.first();
2003: Float angle;
2004:
2005: if (isVertical()) {
2006: angle = (Float) aci
2007: .getAttribute(VERTICAL_ORIENTATION_ANGLE);
2008: } else {
2009: angle = (Float) aci
2010: .getAttribute(HORIZONTAL_ORIENTATION_ANGLE);
2011: }
2012:
2013: if (angle != null) {
2014: glyphOrientationAngle = (int) angle.floatValue();
2015: }
2016:
2017: // if not one of 0, 90, 180 or 270, round to nearest value
2018: if ((glyphOrientationAngle != 0)
2019: || (glyphOrientationAngle != 90)
2020: || (glyphOrientationAngle != 180)
2021: || (glyphOrientationAngle != 270)) {
2022:
2023: while (glyphOrientationAngle < 0) {
2024: glyphOrientationAngle += 360;
2025: }
2026:
2027: while (glyphOrientationAngle >= 360) {
2028: glyphOrientationAngle -= 360;
2029: }
2030:
2031: if ((glyphOrientationAngle <= 45)
2032: || (glyphOrientationAngle > 315)) {
2033: glyphOrientationAngle = 0;
2034: } else if ((glyphOrientationAngle > 45)
2035: && (glyphOrientationAngle <= 135)) {
2036: glyphOrientationAngle = 90;
2037: } else if ((glyphOrientationAngle > 135)
2038: && (glyphOrientationAngle <= 225)) {
2039: glyphOrientationAngle = 180;
2040: } else {
2041: glyphOrientationAngle = 270;
2042: }
2043: }
2044: return glyphOrientationAngle;
2045: }
2046:
2047: /**
2048: * Return true is the character index is represented by glyphs
2049: * in this layout.
2050: *
2051: * @param index index of the character in the ACI.
2052: * @return true if the layout represents that character.
2053: */
2054: public boolean hasCharacterIndex(int index) {
2055:
2056: for (int n = 0; n < charMap.length; n++) {
2057: if (index == charMap[n])
2058: return true;
2059: }
2060: return false;
2061: }
2062:
2063: /**
2064: * Return true if this text run represents
2065: * an alt glyph.
2066: */
2067: public boolean isAltGlyph() {
2068: return this.isAltGlyph;
2069: }
2070: }
|