| /* |
| |
| 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.gvt.font; |
| |
| import java.awt.Color; |
| import java.awt.Graphics2D; |
| import java.awt.Paint; |
| import java.awt.RenderingHints; |
| import java.awt.Shape; |
| import java.awt.Stroke; |
| import java.awt.font.FontRenderContext; |
| import java.awt.font.GlyphJustificationInfo; |
| 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 java.awt.geom.Rectangle2D; |
| import java.text.AttributedCharacterIterator; |
| import java.text.CharacterIterator; |
| |
| import org.apache.batik.gvt.text.ArabicTextHandler; |
| import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; |
| import org.apache.batik.gvt.text.TextPaintInfo; |
| import org.apache.batik.util.Platform; |
| |
| /** |
| * This is a wrapper class for a java.awt.font.GlyphVector instance. |
| * |
| * @author <a href="mailto:bella.robinson@cmis.csiro.au">Bella Robinson</a> |
| * @version $Id$ |
| */ |
| public class AWTGVTGlyphVector implements GVTGlyphVector { |
| |
| public static final AttributedCharacterIterator.Attribute PAINT_INFO |
| = GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO; |
| |
| private GlyphVector awtGlyphVector; |
| private AWTGVTFont gvtFont; |
| private CharacterIterator ci; |
| |
| // This contains the glyphPostions after doing a performDefaultLayout |
| private Point2D [] defaultGlyphPositions; |
| private Point2D.Float[] glyphPositions; |
| |
| // need to keep track of the glyphTransforms since GlyphVector doesn't |
| // seem to |
| private AffineTransform[] glyphTransforms; |
| |
| // these are for caching the glyph outlines |
| private Shape[] glyphOutlines; |
| private Shape[] glyphVisualBounds; |
| private Shape[] glyphLogicalBounds; |
| private boolean[] glyphVisible; |
| private GVTGlyphMetrics [] glyphMetrics; |
| private GeneralPath outline; |
| private Rectangle2D visualBounds; |
| private Rectangle2D logicalBounds; |
| private Rectangle2D bounds2D; |
| private double scaleFactor; |
| private float ascent; |
| private float descent; |
| private TextPaintInfo cacheTPI; |
| |
| /** |
| * Creates and new AWTGVTGlyphVector from the specified GlyphVector and |
| * AWTGVTFont objects. |
| * |
| * @param glyphVector The glyph vector that this one will be based upon. |
| * @param font The font that is creating this glyph vector. |
| * @param scaleFactor The scale factor to apply to the glyph vector. |
| * IMPORTANT: This is only required because the GlyphVector class doesn't |
| * handle font sizes less than 1 correctly. By using the scale factor we |
| * can use a GlyphVector created by a larger font and then scale it down to |
| * the correct size. |
| * @param ci The character string that this glyph vector represents. |
| */ |
| public AWTGVTGlyphVector(GlyphVector glyphVector, |
| AWTGVTFont font, |
| double scaleFactor, |
| CharacterIterator ci) { |
| |
| this.awtGlyphVector = glyphVector; |
| this.gvtFont = font; |
| this.scaleFactor = scaleFactor; |
| this.ci = ci; |
| |
| GVTLineMetrics lineMetrics = gvtFont.getLineMetrics |
| ("By", awtGlyphVector.getFontRenderContext()); |
| |
| ascent = lineMetrics.getAscent(); |
| descent = lineMetrics.getDescent(); |
| |
| outline = null; |
| visualBounds = null; |
| logicalBounds = null; |
| bounds2D = null; |
| int numGlyphs = glyphVector.getNumGlyphs(); |
| glyphPositions = new Point2D.Float [numGlyphs+1]; |
| glyphTransforms = new AffineTransform[numGlyphs]; |
| glyphOutlines = new Shape [numGlyphs]; |
| glyphVisualBounds = new Shape [numGlyphs]; |
| glyphLogicalBounds = new Shape [numGlyphs]; |
| glyphVisible = new boolean [numGlyphs]; |
| glyphMetrics = new GVTGlyphMetrics[numGlyphs]; |
| |
| for (int i = 0; i < numGlyphs; i++) { |
| glyphVisible[i] = true; |
| } |
| } |
| |
| /** |
| * Returns the GVTFont associated with this GVTGlyphVector. |
| */ |
| public GVTFont getFont() { |
| return gvtFont; |
| } |
| |
| /** |
| * Returns the FontRenderContext associated with this GlyphVector. |
| */ |
| public FontRenderContext getFontRenderContext() { |
| return awtGlyphVector.getFontRenderContext(); |
| } |
| |
| /** |
| * Returns the glyphcode of the specified glyph. |
| */ |
| public int getGlyphCode(int glyphIndex) { |
| return awtGlyphVector.getGlyphCode(glyphIndex); |
| } |
| |
| /** |
| * Returns an array of glyphcodes for the specified glyphs. |
| */ |
| public int[] getGlyphCodes(int beginGlyphIndex, int numEntries, |
| int[] codeReturn) { |
| return awtGlyphVector.getGlyphCodes(beginGlyphIndex, numEntries, |
| codeReturn); |
| } |
| |
| /** |
| * Returns the justification information for the glyph at the specified |
| * index into this GlyphVector. |
| */ |
| public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex) { |
| return awtGlyphVector.getGlyphJustificationInfo(glyphIndex); |
| } |
| |
| /** |
| * Returns a tight bounds on the GlyphVector including stroking. |
| */ |
| public Rectangle2D getBounds2D(AttributedCharacterIterator aci) { |
| aci.first(); |
| TextPaintInfo tpi = (TextPaintInfo)aci.getAttribute(PAINT_INFO); |
| if ((bounds2D != null) && |
| TextPaintInfo.equivilent(tpi, cacheTPI)) |
| return bounds2D; |
| |
| if (tpi == null) |
| return null; |
| if (!tpi.visible) |
| return null; |
| |
| cacheTPI = new TextPaintInfo(tpi); |
| Shape outline = null; |
| if (tpi.fillPaint != null) { |
| outline = getOutline(); |
| bounds2D = outline.getBounds2D(); |
| } |
| |
| // check if we need to include the |
| // outline of this glyph |
| Stroke stroke = tpi.strokeStroke; |
| Paint paint = tpi.strokePaint; |
| if ((stroke != null) && (paint != null)) { |
| if (outline == null) |
| outline = getOutline(); |
| Rectangle2D strokeBounds |
| = stroke.createStrokedShape(outline).getBounds2D(); |
| if (bounds2D == null) |
| bounds2D = strokeBounds; |
| else |
| // bounds2D = bounds2D.createUnion(strokeBounds); |
| bounds2D.add(strokeBounds); |
| } |
| if (bounds2D == null) |
| return null; |
| |
| if ((bounds2D.getWidth() == 0) || |
| (bounds2D.getHeight() == 0)) |
| bounds2D = null; |
| |
| return bounds2D; |
| } |
| |
| /** |
| * Returns the logical bounds of this GlyphVector. |
| * This is a bound useful for hit detection and highlighting. |
| */ |
| public Rectangle2D getLogicalBounds() { |
| if (logicalBounds == null) { |
| // This fills in logicalBounds... |
| computeGlyphLogicalBounds(); |
| } |
| return logicalBounds; |
| } |
| |
| /** |
| * Returns the logical bounds of the specified glyph within this |
| * GlyphVector. |
| */ |
| public Shape getGlyphLogicalBounds(int glyphIndex) { |
| if (glyphLogicalBounds[glyphIndex] == null && |
| glyphVisible[glyphIndex]) { |
| |
| computeGlyphLogicalBounds(); |
| } |
| return glyphLogicalBounds[glyphIndex]; |
| } |
| |
| /** |
| * Calculates the logical bounds for each glyph. The logical |
| * bounds are what is used for highlighting the glyphs when |
| * selected. |
| */ |
| private void computeGlyphLogicalBounds() { |
| |
| Shape[] tempLogicalBounds = new Shape[getNumGlyphs()]; |
| boolean[] rotated = new boolean[getNumGlyphs()]; |
| |
| double maxWidth = -1.0; |
| double maxHeight = -1.0; |
| for (int i = 0; i < getNumGlyphs(); i++) { |
| |
| if (!glyphVisible[i]) { |
| // the glyph is not drawn |
| tempLogicalBounds[i] = null; |
| continue; |
| } |
| |
| AffineTransform glyphTransform = getGlyphTransform(i); |
| GVTGlyphMetrics glyphMetrics = getGlyphMetrics(i); |
| |
| float glyphX = 0.0f; |
| float glyphY = (float)(-ascent/scaleFactor); |
| float glyphWidth = (float)(glyphMetrics.getHorizontalAdvance()/ |
| scaleFactor); |
| float glyphHeight = (float)(glyphMetrics.getVerticalAdvance()/ |
| scaleFactor); |
| Rectangle2D glyphBounds = new Rectangle2D.Double(glyphX, |
| glyphY, |
| glyphWidth, |
| glyphHeight); |
| |
| if (glyphBounds.isEmpty()) { |
| if (i > 0) { |
| // can't tell if rotated or not, make it the same as |
| // the previous glyph |
| rotated [i] = rotated [i-1]; |
| } else { |
| rotated [i] = true; |
| } |
| } else { |
| // get three corner points so we can determine |
| // whether the glyph is rotated |
| Point2D p1 = new Point2D.Double(glyphBounds.getMinX(), |
| glyphBounds.getMinY()); |
| Point2D p2 = new Point2D.Double(glyphBounds.getMaxX(), |
| glyphBounds.getMinY()); |
| Point2D p3 = new Point2D.Double(glyphBounds.getMinX(), |
| glyphBounds.getMaxY()); |
| |
| Point2D gpos = getGlyphPosition(i); |
| AffineTransform tr = AffineTransform.getTranslateInstance |
| (gpos.getX(), gpos.getY()); |
| |
| if (glyphTransform != null) |
| tr.concatenate(glyphTransform); |
| tr.scale(scaleFactor, scaleFactor); |
| |
| tempLogicalBounds[i] = tr.createTransformedShape(glyphBounds); |
| |
| Point2D tp1 = new Point2D.Double(); |
| Point2D tp2 = new Point2D.Double(); |
| Point2D tp3 = new Point2D.Double(); |
| tr.transform(p1, tp1); |
| tr.transform(p2, tp2); |
| tr.transform(p3, tp3); |
| double tdx12 = tp1.getX()-tp2.getX(); |
| double tdx13 = tp1.getX()-tp3.getX(); |
| double tdy12 = tp1.getY()-tp2.getY(); |
| double tdy13 = tp1.getY()-tp3.getY(); |
| |
| if (((Math.abs(tdx12) < 0.001) && (Math.abs(tdy13) < 0.001)) || |
| ((Math.abs(tdx13) < 0.001) && (Math.abs(tdy12) < 0.001))) { |
| // If either of these are zero then it is axially aligned |
| rotated[i] = false; |
| } else { |
| rotated [i] = true; |
| } |
| |
| Rectangle2D rectBounds; |
| rectBounds = tempLogicalBounds[i].getBounds2D(); |
| if (rectBounds.getWidth() > maxWidth) |
| maxWidth = rectBounds.getWidth(); |
| if (rectBounds.getHeight() > maxHeight) |
| maxHeight = rectBounds.getHeight(); |
| } |
| } |
| |
| // if appropriate, join adjacent glyph logical bounds |
| GeneralPath logicalBoundsPath = new GeneralPath(); |
| for (int i = 0; i < getNumGlyphs(); i++) { |
| if (tempLogicalBounds[i] != null) { |
| logicalBoundsPath.append(tempLogicalBounds[i], false); |
| } |
| } |
| |
| logicalBounds = logicalBoundsPath.getBounds2D(); |
| |
| if (logicalBounds.getHeight() < maxHeight*1.5) { |
| // make all glyphs tops and bottoms the same as the full bounds |
| for (int i = 0; i < getNumGlyphs(); i++) { |
| // first make sure that the glyph logical bounds are |
| // not rotated |
| if (rotated[i]) continue; |
| if (tempLogicalBounds[i] == null) continue; |
| |
| Rectangle2D glyphBounds = tempLogicalBounds[i].getBounds2D(); |
| |
| double x = glyphBounds.getMinX(); |
| double width = glyphBounds.getWidth(); |
| |
| if ((i < getNumGlyphs()-1) && |
| (tempLogicalBounds[i+1] != null)) { |
| // make this glyph extend to the start of the next one |
| Rectangle2D ngb = tempLogicalBounds[i+1].getBounds2D(); |
| |
| if (ngb.getX() > x) { |
| double nw = ngb.getX() - x; |
| if ((nw < width*1.15) && (nw > width*.85)) { |
| double delta = (nw-width)*.5; |
| width += delta; |
| ngb.setRect(ngb.getX()-delta, ngb.getY(), |
| ngb.getWidth()+delta, ngb.getHeight()); |
| } |
| } |
| } |
| tempLogicalBounds[i] = new Rectangle2D.Double |
| (x, logicalBounds.getMinY(), |
| width, logicalBounds.getHeight()); |
| } |
| } else if (logicalBounds.getWidth() < maxWidth*1.5) { |
| // make all glyphs left and right edges the same as the full bounds |
| for (int i = 0; i < getNumGlyphs(); i++) { |
| // first make sure that the glyph logical bounds are |
| // not rotated |
| if (rotated[i]) continue; |
| if (tempLogicalBounds[i] == null) continue; |
| |
| Rectangle2D glyphBounds = tempLogicalBounds[i].getBounds2D(); |
| double y = glyphBounds.getMinY(); |
| double height = glyphBounds.getHeight(); |
| |
| if ((i < getNumGlyphs()-1) && |
| (tempLogicalBounds[i+1] != null)) { |
| // make this glyph extend to the start of the next one |
| Rectangle2D ngb = tempLogicalBounds[i+1].getBounds2D(); |
| if (ngb.getY() > y) { // going top to bottom |
| double nh = ngb.getY() - y; |
| if ((nh < height*1.15) && (nh > height*.85)) { |
| double delta = (nh-height)*.5; |
| height += delta; |
| ngb.setRect(ngb.getX(), ngb.getY()-delta, |
| ngb.getWidth(), ngb.getHeight()+delta); |
| } |
| } |
| } |
| tempLogicalBounds[i] = new Rectangle2D.Double |
| (logicalBounds.getMinX(), y, |
| logicalBounds.getWidth(), height); |
| } |
| } |
| |
| System.arraycopy( tempLogicalBounds, 0, glyphLogicalBounds, 0, getNumGlyphs() ); |
| } |
| |
| /** |
| * Returns the metrics of the glyph at the specified index into this |
| * GVTGlyphVector. |
| */ |
| public GVTGlyphMetrics getGlyphMetrics(int glyphIndex) { |
| if (glyphMetrics[glyphIndex] != null) |
| return glyphMetrics[glyphIndex]; |
| |
| // -- start glyph cache code -- |
| Point2D glyphPos = defaultGlyphPositions[glyphIndex]; |
| char c = ci.setIndex(ci.getBeginIndex()+glyphIndex); |
| ci.setIndex(ci.getBeginIndex()); |
| AWTGlyphGeometryCache.Value v = AWTGVTFont.getGlyphGeometry |
| (gvtFont, c, awtGlyphVector, glyphIndex, glyphPos); |
| Rectangle2D gmB = v.getBounds2D(); |
| // -- end glyph cache code -- |
| |
| Rectangle2D bounds = new Rectangle2D.Double |
| (gmB.getX() * scaleFactor, gmB.getY() * scaleFactor, |
| gmB.getWidth() * scaleFactor, gmB.getHeight() * scaleFactor); |
| |
| // defaultGlyphPositions has one more entry than glyphs |
| // the last entry stores the total advance for the |
| // glyphVector. |
| float adv = (float)(defaultGlyphPositions[glyphIndex+1].getX()- |
| defaultGlyphPositions[glyphIndex] .getX()); |
| glyphMetrics[glyphIndex] = new GVTGlyphMetrics |
| ((float)(adv*scaleFactor), (ascent+descent), |
| bounds, GlyphMetrics.STANDARD); |
| |
| return glyphMetrics[glyphIndex]; |
| } |
| |
| /** |
| * Returns a Shape whose interior corresponds to the visual representation |
| * of the specified glyph within this GlyphVector. |
| */ |
| public Shape getGlyphOutline(int glyphIndex) { |
| if (glyphOutlines[glyphIndex] == null) { |
| /* |
| Shape glyphOutline = awtGlyphVector.getGlyphOutline(glyphIndex); |
| */ |
| // -- start glyph cache code -- |
| Point2D glyphPos = defaultGlyphPositions[glyphIndex]; |
| char c = ci.setIndex(ci.getBeginIndex()+glyphIndex); |
| ci.setIndex(ci.getBeginIndex()); |
| AWTGlyphGeometryCache.Value v = AWTGVTFont.getGlyphGeometry |
| (gvtFont, c, awtGlyphVector, glyphIndex, glyphPos); |
| Shape glyphOutline = v.getOutline(); |
| // -- end glyph cache code -- |
| |
| AffineTransform tr = AffineTransform.getTranslateInstance |
| (getGlyphPosition(glyphIndex).getX(), |
| getGlyphPosition(glyphIndex).getY()); |
| |
| AffineTransform glyphTransform = getGlyphTransform(glyphIndex); |
| |
| if (glyphTransform != null) { |
| tr.concatenate(glyphTransform); |
| } |
| // |
| // <!> HACK |
| // |
| // GlyphVector.getGlyphOutline behavior changes between 1.3 and 1.4 |
| // |
| // I've looked at this problem a bit more and the incorrect glyph |
| // positioning in Batik is definitely due to the change in |
| // behavior of GlyphVector.getGlyphOutline(glyphIndex). It used to |
| // return the outline of the glyph at position 0,0 which meant |
| // that we had to translate it to the actual glyph position before |
| // drawing it. Now, it returns the outline which has already been |
| // positioned. |
| // |
| // -- Bella |
| // |
| /* |
| if (outlinesPositioned()) { |
| Point2D glyphPos = defaultGlyphPositions[glyphIndex]; |
| tr.translate(-glyphPos.getX(), -glyphPos.getY()); |
| } |
| */ |
| tr.scale(scaleFactor, scaleFactor); |
| glyphOutlines[glyphIndex]=tr.createTransformedShape(glyphOutline); |
| } |
| |
| return glyphOutlines[glyphIndex]; |
| } |
| |
| // This is true if GlyphVector.getGlyphOutline returns glyph outlines |
| // that are positioned (if it is false the outlines are always at 0,0). |
| private static final boolean outlinesPositioned; |
| // This is true if Graphics2D.drawGlyphVector works for the |
| // current JDK/OS combination. |
| private static final boolean drawGlyphVectorWorks; |
| // This is true if Graphics2D.drawGlyphVector will correctly |
| // render Glyph Vectors with per glyph transforms. |
| private static final boolean glyphVectorTransformWorks; |
| |
| static { |
| String s = System.getProperty("java.specification.version"); |
| if ("1.6".compareTo(s) <= 0) { |
| outlinesPositioned = true; |
| drawGlyphVectorWorks = false; // [GA] not verified; needs further research |
| glyphVectorTransformWorks = true; |
| } else if ("1.4".compareTo(s) <= 0) { |
| // TODO Java 5 |
| outlinesPositioned = true; |
| drawGlyphVectorWorks = true; |
| glyphVectorTransformWorks = true; |
| } else if (Platform.isOSX) { |
| outlinesPositioned = true; |
| drawGlyphVectorWorks = false; |
| glyphVectorTransformWorks = false; |
| } else { |
| outlinesPositioned = false; |
| drawGlyphVectorWorks = true; |
| glyphVectorTransformWorks = false; |
| } |
| } |
| |
| // Returns true if GlyphVector.getGlyphOutlines returns glyph outlines |
| // that are positioned (otherwise they are always at 0,0). |
| static boolean outlinesPositioned() { |
| return outlinesPositioned; |
| } |
| |
| /** |
| * Returns the bounding box of the specified glyph, considering only the |
| * glyph's metrics (ascent, descent, advance) rather than the actual glyph |
| * shape. |
| */ |
| public Rectangle2D getGlyphCellBounds(int glyphIndex) { |
| return getGlyphLogicalBounds(glyphIndex).getBounds2D(); |
| } |
| |
| /** |
| * Returns the position of the specified glyph within this GlyphVector. |
| */ |
| public Point2D getGlyphPosition(int glyphIndex) { |
| return glyphPositions[glyphIndex]; |
| } |
| |
| /** |
| * Returns an array of glyph positions for the specified glyphs |
| */ |
| public float[] getGlyphPositions(int beginGlyphIndex, |
| int numEntries, |
| float[] positionReturn) { |
| |
| if (positionReturn == null) { |
| positionReturn = new float[numEntries*2]; |
| } |
| |
| for (int i = beginGlyphIndex; i < (beginGlyphIndex+numEntries); i++) { |
| Point2D glyphPos = getGlyphPosition(i); |
| positionReturn[(i-beginGlyphIndex)*2] = (float)glyphPos.getX(); |
| positionReturn[(i-beginGlyphIndex)*2 + 1] = (float)glyphPos.getY(); |
| } |
| |
| return positionReturn; |
| } |
| |
| /** |
| * Gets the transform of the specified glyph within this GlyphVector. |
| */ |
| public AffineTransform getGlyphTransform(int glyphIndex) { |
| return glyphTransforms[glyphIndex]; |
| } |
| |
| /** |
| * Returns the visual bounds of the specified glyph within the GlyphVector. |
| */ |
| public Shape getGlyphVisualBounds(int glyphIndex) { |
| if (glyphVisualBounds[glyphIndex] == null) { |
| /* |
| Shape glyphOutline = awtGlyphVector.getGlyphOutline(glyphIndex); |
| Rectangle2D glyphBounds = glyphOutline.getBounds2D(); |
| */ |
| // -- start glyph cache code -- |
| Point2D glyphPos = defaultGlyphPositions[glyphIndex]; |
| char c = ci.setIndex(ci.getBeginIndex()+glyphIndex); |
| ci.setIndex(ci.getBeginIndex()); |
| AWTGlyphGeometryCache.Value v = AWTGVTFont.getGlyphGeometry |
| (gvtFont, c, awtGlyphVector, glyphIndex, glyphPos); |
| Rectangle2D glyphBounds = v.getOutlineBounds2D(); |
| // -- end glyph cache code -- |
| |
| AffineTransform tr = AffineTransform.getTranslateInstance |
| (getGlyphPosition(glyphIndex).getX(), |
| getGlyphPosition(glyphIndex).getY()); |
| |
| AffineTransform glyphTransform = getGlyphTransform(glyphIndex); |
| if (glyphTransform != null) { |
| tr.concatenate(glyphTransform); |
| } |
| tr.scale(scaleFactor, scaleFactor); |
| glyphVisualBounds[glyphIndex] = |
| tr.createTransformedShape(glyphBounds); |
| } |
| |
| return glyphVisualBounds[glyphIndex]; |
| } |
| |
| /** |
| * Returns the number of glyphs in this GlyphVector. |
| */ |
| public int getNumGlyphs() { |
| return awtGlyphVector.getNumGlyphs(); |
| } |
| |
| /** |
| * Returns a Shape whose interior corresponds to the visual representation |
| * of this GlyphVector. |
| */ |
| public Shape getOutline() { |
| if (outline != null) |
| return outline; |
| |
| outline = new GeneralPath(); |
| for (int i = 0; i < getNumGlyphs(); i++) { |
| if (glyphVisible[i]) { |
| Shape glyphOutline = getGlyphOutline(i); |
| outline.append(glyphOutline, false); |
| } |
| } |
| return outline; |
| } |
| |
| /** |
| * Returns a Shape whose interior corresponds to the visual representation |
| * of this GlyphVector, offset to x, y. |
| */ |
| public Shape getOutline(float x, float y) { |
| Shape outline = getOutline(); |
| AffineTransform tr = AffineTransform.getTranslateInstance(x,y); |
| outline = tr.createTransformedShape(outline); |
| return outline; |
| } |
| |
| /** |
| * Returns the visual bounds of this GlyphVector The visual bounds is the |
| * tightest rectangle enclosing all non-background pixels in the rendered |
| * representation of this GlyphVector. |
| */ |
| public Rectangle2D getGeometricBounds() { |
| if (visualBounds == null) { |
| Shape outline = getOutline(); |
| visualBounds = outline.getBounds2D(); |
| } |
| return visualBounds; |
| } |
| |
| /** |
| * Assigns default positions to each glyph in this GlyphVector. |
| */ |
| public void performDefaultLayout() { |
| if (defaultGlyphPositions == null) { |
| awtGlyphVector.performDefaultLayout(); |
| defaultGlyphPositions = new Point2D.Float[getNumGlyphs()+1]; |
| for (int i = 0; i <= getNumGlyphs(); i++) |
| defaultGlyphPositions[i] = awtGlyphVector.getGlyphPosition(i); |
| } |
| |
| outline = null; |
| visualBounds = null; |
| logicalBounds = null; |
| bounds2D = null; |
| float shiftLeft = 0; |
| int i=0; |
| for (; i < getNumGlyphs(); i++) { |
| glyphTransforms [i] = null; |
| glyphVisualBounds [i] = null; |
| glyphLogicalBounds[i] = null; |
| glyphOutlines [i] = null; |
| glyphMetrics [i] = null; |
| Point2D glyphPos = defaultGlyphPositions[i]; |
| float x = (float)((glyphPos.getX() * scaleFactor)-shiftLeft); |
| float y = (float) (glyphPos.getY() * scaleFactor); |
| |
| // if c is a transparent arabic char then need to shift the |
| // following glyphs left so that the current glyph is overwritten |
| /*char c =*/ ci.setIndex(i + ci.getBeginIndex()); |
| /* |
| if (ArabicTextHandler.arabicCharTransparent(c)) { |
| int j; |
| shiftLeft += getGlyphMetrics(i).getHorizontalAdvance(); |
| for (j=i+1; j<getNumGlyphs(); j++) { |
| char c2 = ci.setIndex(j+ci.getBeginIndex()); |
| if (!ArabicTextHandler.arabicCharTransparent(c2)) break; |
| shiftLeft += getGlyphMetrics(j).getHorizontalAdvance(); |
| } |
| if (j != getNumGlyphs()) { |
| Point2D glyphPosBase = defaultGlyphPositions[j]; |
| double rEdge = glyphPosBase.getX()+getGlyphMetrics(j).getHorizontalAdvance(); |
| rEdge -= shiftLeft; |
| for (int k=i; k<j; k++) { |
| glyphTransforms [k] = null; |
| glyphVisualBounds [k] = null; |
| glyphLogicalBounds[k] = null; |
| glyphOutlines [k] = null; |
| glyphMetrics [k] = null; |
| x = (float)rEdge-getGlyphMetrics(k).getHorizontalAdvance(); |
| y = (float) (defaultGlyphPositions[k].getY() * scaleFactor); |
| if (glyphPositions[k] == null) { |
| glyphPositions[k] = new Point2D.Float(x,y); |
| } else { |
| glyphPositions[k].x = x; |
| glyphPositions[k].y = y; |
| } |
| } |
| i = j-1; |
| } |
| } else { |
| */ |
| if (glyphPositions[i] == null) { |
| glyphPositions[i] = new Point2D.Float(x,y); |
| } else { |
| glyphPositions[i].x = x; |
| glyphPositions[i].y = y; |
| } |
| // } |
| |
| } |
| |
| // Need glyph pos for point after last char... |
| Point2D glyphPos = defaultGlyphPositions[i]; |
| glyphPositions[i] = new Point2D.Float |
| ((float)((glyphPos.getX() * scaleFactor)-shiftLeft), |
| (float) (glyphPos.getY() * scaleFactor)); |
| } |
| |
| /** |
| * Sets the position of the specified glyph within this GlyphVector. |
| */ |
| public void setGlyphPosition(int glyphIndex, Point2D newPos) { |
| glyphPositions[glyphIndex].x = (float)newPos.getX(); |
| glyphPositions[glyphIndex].y = (float)newPos.getY(); |
| outline = null; |
| visualBounds = null; |
| logicalBounds = null; |
| bounds2D = null; |
| |
| if (glyphIndex != getNumGlyphs()) { |
| glyphVisualBounds [glyphIndex] = null; |
| glyphLogicalBounds[glyphIndex] = null; |
| glyphOutlines [glyphIndex] = null; |
| glyphMetrics [glyphIndex] = null; |
| } |
| } |
| |
| /** |
| * Sets the transform of the specified glyph within this GlyphVector. |
| */ |
| public void setGlyphTransform(int glyphIndex, AffineTransform newTX) { |
| glyphTransforms[glyphIndex] = newTX; |
| outline = null; |
| visualBounds = null; |
| logicalBounds = null; |
| bounds2D = null; |
| |
| glyphVisualBounds [glyphIndex] = null; |
| glyphLogicalBounds[glyphIndex] = null; |
| glyphOutlines [glyphIndex] = null; |
| glyphMetrics [glyphIndex] = null; |
| } |
| |
| /** |
| * Tells the glyph vector whether or not to draw the specified glyph. |
| */ |
| public void setGlyphVisible(int glyphIndex, boolean visible) { |
| if (visible == glyphVisible[glyphIndex]) |
| return; |
| glyphVisible[glyphIndex] = visible; |
| outline = null; |
| visualBounds = null; |
| logicalBounds = null; |
| bounds2D = null; |
| |
| glyphVisualBounds [glyphIndex] = null; |
| glyphLogicalBounds[glyphIndex] = null; |
| glyphOutlines [glyphIndex] = null; |
| glyphMetrics [glyphIndex] = null; |
| } |
| |
| /** |
| * Returns true if specified glyph will be rendered. |
| */ |
| public boolean isGlyphVisible(int glyphIndex) { |
| return glyphVisible[glyphIndex]; |
| } |
| |
| /** |
| * Returns the number of chars represented by the glyphs within the |
| * specified range. |
| * |
| * @param startGlyphIndex The index of the first glyph in the range. |
| * @param endGlyphIndex The index of the last glyph in the range. |
| * @return The number of chars. |
| */ |
| public int getCharacterCount(int startGlyphIndex, int endGlyphIndex) { |
| if (startGlyphIndex < 0) { |
| startGlyphIndex = 0; |
| } |
| if (endGlyphIndex >= getNumGlyphs()) { |
| endGlyphIndex = getNumGlyphs()-1; |
| } |
| int charCount = 0; |
| int start = startGlyphIndex+ci.getBeginIndex(); |
| int end = endGlyphIndex+ci.getBeginIndex(); |
| |
| for (char c = ci.setIndex(start); ci.getIndex() <= end; c=ci.next()) { |
| charCount += ArabicTextHandler.getNumChars(c); |
| } |
| |
| return charCount; |
| } |
| |
| @Override |
| public boolean isReversed() { |
| return false; |
| } |
| |
| @Override |
| public void maybeReverse(boolean mirror) { |
| } |
| |
| /** |
| * Draws this glyph vector. |
| */ |
| public void draw(Graphics2D graphics2D, |
| AttributedCharacterIterator aci) { |
| int numGlyphs = getNumGlyphs(); |
| |
| aci.first(); |
| TextPaintInfo tpi = (TextPaintInfo)aci.getAttribute |
| (GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO); |
| if (tpi == null) return; |
| if (!tpi.visible) return; |
| |
| Paint fillPaint = tpi.fillPaint; |
| Stroke stroke = tpi.strokeStroke; |
| Paint strokePaint = tpi.strokePaint; |
| |
| if ((fillPaint == null) && ((strokePaint == null) || |
| (stroke == null))) |
| return; |
| |
| boolean useHinting = drawGlyphVectorWorks; |
| if (useHinting && (stroke != null) && (strokePaint != null)) |
| // Can't stroke with drawGlyphVector. |
| useHinting = false; |
| |
| if (useHinting && |
| (fillPaint != null) && !(fillPaint instanceof Color)) |
| // The coordinate system is different for drawGlyphVector. |
| // So complex paints aren't positioned properly. |
| useHinting = false; |
| |
| if (useHinting) { |
| Object v1 = graphics2D.getRenderingHint |
| (RenderingHints.KEY_TEXT_ANTIALIASING); |
| Object v2 = graphics2D.getRenderingHint |
| (RenderingHints.KEY_STROKE_CONTROL); |
| // text-rendering = geometricPrecision so fill shapes. |
| if ((v1 == RenderingHints.VALUE_TEXT_ANTIALIAS_ON) && |
| (v2 == RenderingHints.VALUE_STROKE_PURE)) |
| useHinting = false; |
| } |
| |
| final int typeGRot = AffineTransform.TYPE_GENERAL_ROTATION; |
| final int typeGTrans = AffineTransform.TYPE_GENERAL_TRANSFORM; |
| |
| if (useHinting) { |
| // Check if usr->dev transform has general rotation, |
| // or shear.. |
| AffineTransform at = graphics2D.getTransform(); |
| int type = at.getType(); |
| if (((type & typeGTrans) != 0) || ((type & typeGRot) != 0)) |
| useHinting = false; |
| } |
| |
| if (useHinting) { |
| for (int i=0; i<numGlyphs; i++) { |
| if (!glyphVisible[i]) { |
| useHinting = false; |
| break; |
| } |
| AffineTransform at = glyphTransforms[i]; |
| if (at != null) { |
| int type = at.getType(); |
| if ((type & ~AffineTransform.TYPE_TRANSLATION) == 0) { |
| // Just translation |
| } else if (glyphVectorTransformWorks && |
| ((type & typeGTrans) == 0) && |
| ((type & typeGRot) == 0)) { |
| // It's a simple 90Deg rotate, and we can put |
| // it into the GlyphVector. |
| } else { |
| // we can't (or it doesn't make sense |
| // to use the GlyphVector. |
| useHinting = false; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (useHinting) { |
| double sf = scaleFactor; |
| double [] mat = new double[6]; |
| for (int i=0; i< numGlyphs; i++) { |
| Point2D pos = glyphPositions[i]; |
| double x = pos.getX(); |
| double y = pos.getY(); |
| AffineTransform at = glyphTransforms[i]; |
| if (at != null) { |
| // Scale the translate portion of matrix, |
| // and add it into the position. |
| at.getMatrix(mat); |
| x += mat[4]; |
| y += mat[5]; |
| if ((mat[0] != 1) || (mat[1] != 0) || |
| (mat[2] != 0) || (mat[3] != 1)) { |
| // More than just translation. |
| mat[4] = 0; mat[5] = 0; |
| at = new AffineTransform(mat); |
| } else { |
| at = null; |
| } |
| } |
| pos = new Point2D.Double(x/sf, y/sf); |
| awtGlyphVector.setGlyphPosition(i, pos); |
| awtGlyphVector.setGlyphTransform(i, at); |
| } |
| graphics2D.scale(sf, sf); |
| graphics2D.setPaint(fillPaint); |
| graphics2D.drawGlyphVector(awtGlyphVector, 0.0f, 0.0f); |
| graphics2D.scale(1.0/sf, 1.0/sf); |
| |
| for (int i=0; i< numGlyphs; i++) { |
| Point2D pos = defaultGlyphPositions[i]; |
| awtGlyphVector.setGlyphPosition(i, pos); |
| awtGlyphVector.setGlyphTransform(i, null); |
| } |
| |
| } else { |
| Shape outline = getOutline(); |
| |
| // check if we need to fill this glyph |
| if (fillPaint != null) { |
| graphics2D.setPaint(fillPaint); |
| graphics2D.fill(outline); |
| } |
| |
| // check if we need to draw the outline of this glyph |
| if (stroke != null && strokePaint != null) { |
| graphics2D.setStroke(stroke); |
| graphics2D.setPaint(strokePaint); |
| graphics2D.draw(outline); |
| } |
| } |
| } |
| } |