1935 lines · 69.8 KB · Java
RawDownload
/* * Copyright (c) 1998, 2005, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package sun.font; import java.awt.Font;import java.awt.Graphics2D;import java.awt.Point;import java.awt.Rectangle;import static java.awt.RenderingHints.*;import java.awt.Shape;import java.awt.font.FontRenderContext;import java.awt.font.GlyphMetrics;import java.awt.font.GlyphJustificationInfo;import java.awt.font.GlyphVector;import java.awt.font.LineMetrics;import java.awt.font.TextAttribute;import java.awt.geom.AffineTransform;import java.awt.geom.GeneralPath;import java.awt.geom.NoninvertibleTransformException;import java.awt.geom.PathIterator;import java.awt.geom.Point2D;import java.awt.geom.Rectangle2D;import java.lang.ref.SoftReference;import java.text.CharacterIterator; import sun.awt.SunHints;import sun.java2d.loops.FontInfo; /** * Standard implementation of GlyphVector used by Font, GlyphList, and * SunGraphics2D. * * The main issues involve the semantics of the various transforms * (font, glyph, device) and their effect on rendering and metrics. * * Very, very unfortunately, the translation component of the font * transform affects where the text gets rendered.  It offsets the * rendering origin.  None of the other metrics of the glyphvector * are affected, making them inconsistent with the rendering behavior. * I think the translation component of the font would be better * interpreted as the translation component of a per-glyph transform, * but I don't know if this is possible to change. * * After the font transform is applied, the glyph transform is * applied.  This makes glyph transforms relative to font transforms, * if the font transform changes, the glyph transform will have the * same (relative) effect on the outline of the glyph.  The outline * and logical bounds are passed through the glyph transform before * being returned.  The glyph metrics ignore the glyph transform, but * provide the outline bounds and the advance vector of the glyph (the * latter will be rotated if the font is rotated).  The default layout * places each glyph at the end of the advance vector of the previous * glyph, and since the glyph transform translates the advance vector, * this means a glyph transform affects the positions of all * subsequent glyphs if defaultLayout is called after setting a glyph * transform.  In the glyph info array, the bounds are the outline * bounds including the glyph transform, and the positions are as * computed, and the advances are the deltas between the positions. * * (There's a bug in the logical bounds of a rotated glyph for * composite fonts, it's not to spec (in 1.4.0, 1.4.1, 1.4.2).  The * problem is that the rotated composite doesn't handle the multiple * ascents and descents properly in both x and y.  You end up with * a rotated advance vector but an unrotated ascent and descent.) * * Finally, the whole thing is transformed by the device transform to * position it on the page. * * Another bug: The glyph outline seems to ignore fractional point * size information, but the images (and advances) don't ignore it. * * Small fonts drawn at large magnification have odd advances when * fractional metrics is off-- that's because the advances depend on * the frc.  When the frc is scaled appropriately, the advances are * fine.  FM or a large frc (high numbers) make the advances right. * * The buffer aa flag doesn't affect rendering, the glyph vector * renders as AA if aa is set in its frc, and as non-aa if aa is not * set in its frc. * * font rotation, baseline, vertical etc. * * Font rotation and baseline Line metrics should be measured along a * unit vector pi/4 cc from the baseline vector.  For 'horizontal' * fonts the baseline vector is the x vector passed through the font * transform (ignoring translation), for 'vertical' it is the y * vector.  This definition makes ascent, descent, etc independent of * shear, so shearing can be used to simulate italic. This means no * fonts have 'negative ascents' or 'zero ascents' etc. * * Having a coordinate system with orthogonal axes where one is * parallel to the baseline means we could use rectangles and interpret * them in terms of this coordinate system.  Unfortunately there * is support for rotated fonts in the jdk already so maintaining * the semantics of existing code (getlogical bounds, etc) might * be difficult. * * A font transform transforms both the baseline and all the glyphs * in the font, so it does not rotate the glyph w.r.t the baseline. * If you do want to rotate individual glyphs, you need to apply a * glyph transform.  If performDefaultLayout is called after this, * the transformed glyph advances will affect the glyph positions. * * useful additions * - select vertical metrics - glyphs are rotated pi/4 cc and vertical * metrics are used to align them to the baseline. * - define baseline for font (glyph rotation not linked to baseline) * - define extra space (delta between each glyph along baseline) * - define offset (delta from 'true' baseline, impacts ascent and * descent as these are still computed from true basline and pinned * to zero, used in superscript). */public class StandardGlyphVector extends GlyphVector {    private Font font;    private FontRenderContext frc;    private int[] glyphs; // always    private int[] userGlyphs; // used to return glyphs to the client.    private float[] positions; // only if not default advances    private int[] charIndices;  // only if interesting    private int flags; // indicates whether positions, charIndices is interesting     private static final int UNINITIALIZED_FLAGS = -1;     // transforms information    private GlyphTransformInfo gti; // information about per-glyph transforms     // !!! can we get rid of any of this extra stuff?    private AffineTransform ftx;   // font transform without translation    private AffineTransform dtx;   // device transform used for strike calculations, no translation    private AffineTransform invdtx; // inverse of dtx or null if dtx is identity    private AffineTransform frctx; // font render context transform, wish we could just share it    private Font2D font2D;         // basic strike-independent stuff    private SoftReference fsref;   // font strike reference for glyphs with no per-glyph transform     /////////////////////////////    // Constructors and Factory methods    /////////////////////////////     public StandardGlyphVector(Font font, String str, FontRenderContext frc) {        init(font, str.toCharArray(), 0, str.length(), frc, UNINITIALIZED_FLAGS);    }     public StandardGlyphVector(Font font, char[] text, FontRenderContext frc) {        init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS);    }     public StandardGlyphVector(Font font, char[] text, int start, int count,                               FontRenderContext frc) {        init(font, text, start, count, frc, UNINITIALIZED_FLAGS);    }     private float getTracking(Font font) {        if (font.hasLayoutAttributes()) {            AttributeValues values = ((AttributeMap)font.getAttributes()).getValues();            return values.getTracking();        }        return 0;    }      // used by GlyphLayout to construct a glyphvector    public StandardGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions,                               int[] indices, int flags) {        initGlyphVector(font, frc, glyphs, positions, indices, flags);         // this code should go into layout        float track = getTracking(font);        if (track != 0) {            track *= font.getSize2D();            Point2D.Float trackPt = new Point2D.Float(track, 0); // advance delta            if (font.isTransformed()) {                AffineTransform at = font.getTransform();                at.deltaTransform(trackPt, trackPt);            }             // how do we know its a base glyph            // for now, it is if the natural advance of the glyph is non-zero            Font2D f2d = FontUtilities.getFont2D(font);            FontStrike strike = f2d.getStrike(font, frc);             float[] deltas = { trackPt.x, trackPt.y };            for (int j = 0; j < deltas.length; ++j) {                float inc = deltas[j];                if (inc != 0) {                    float delta = 0;                    for (int i = j, n = 0; n < glyphs.length; i += 2) {                        if (strike.getGlyphAdvance(glyphs[n++]) != 0) { // might be an inadequate test                            positions[i] += delta;                            delta += inc;                        }                    }                    positions[positions.length-2+j] += delta;                }            }        }    }     public void initGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions,                                int[] indices, int flags) {        this.font = font;        this.frc = frc;        this.glyphs = glyphs;        this.userGlyphs = glyphs; // no need to check        this.positions = positions;        this.charIndices = indices;        this.flags = flags;         initFontData();    }     public StandardGlyphVector(Font font, CharacterIterator iter, FontRenderContext frc) {        int offset = iter.getBeginIndex();        char[] text = new char [iter.getEndIndex() - offset];        for(char c = iter.first();            c != CharacterIterator.DONE;            c = iter.next()) {            text[iter.getIndex() - offset] = c;        }        init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS);    }     public StandardGlyphVector(Font font, int[] glyphs, FontRenderContext frc) {        // !!! find callers of this        // should be able to fully init from raw data, e.g. charmap, flags too.        this.font = font;        this.frc = frc;        this.flags = UNINITIALIZED_FLAGS;         initFontData();        this.userGlyphs = glyphs;        this.glyphs = getValidatedGlyphs(this.userGlyphs);    }     /* This is called from the rendering loop. FontInfo is supplied     * because a GV caches a strike and glyph images suitable for its FRC.     * LCD text isn't currently supported on all surfaces, in which case     * standard AA must be used. This is most likely to occur when LCD text     * is requested and the surface is some non-standard type or hardward     * surface for which there are no accelerated loops.     * We can detect this as being AA=="ON" in the FontInfo and AA!="ON"     * and AA!="GASP" in the FRC - since this only occurs for LCD text we don't     * need to check any more precisely what value is in the FRC.     */    public static StandardGlyphVector getStandardGV(GlyphVector gv,                                                    FontInfo info) {        if (info.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) {            Object aaHint = gv.getFontRenderContext().getAntiAliasingHint();            if (aaHint != VALUE_TEXT_ANTIALIAS_ON &&                aaHint != VALUE_TEXT_ANTIALIAS_GASP) {                /* We need to create a new GV with AA==ON for rendering */                FontRenderContext frc = gv.getFontRenderContext();                frc = new FontRenderContext(frc.getTransform(),                                            VALUE_TEXT_ANTIALIAS_ON,                                            frc.getFractionalMetricsHint());                return new StandardGlyphVector(gv, frc);            }        }        if (gv instanceof StandardGlyphVector) {            return (StandardGlyphVector)gv;        }        return new StandardGlyphVector(gv, gv.getFontRenderContext());    }     /////////////////////////////    // GlyphVector API    /////////////////////////////     public Font getFont() {        return this.font;    }     public FontRenderContext getFontRenderContext() {        return this.frc;    }     public void performDefaultLayout() {        positions = null;        if (getTracking(font) == 0) {            clearFlags(FLAG_HAS_POSITION_ADJUSTMENTS);        }    }     public int getNumGlyphs() {        return glyphs.length;    }     public int getGlyphCode(int glyphIndex) {        return userGlyphs[glyphIndex];    }     public int[] getGlyphCodes(int start, int count, int[] result) {        if (count < 0) {            throw new IllegalArgumentException("count = " + count);        }        if (start < 0) {            throw new IndexOutOfBoundsException("start = " + start);        }        if (start > glyphs.length - count) { // watch out for overflow if index + count overlarge            throw new IndexOutOfBoundsException("start + count = " + (start + count));        }         if (result == null) {            result = new int[count];        }         // if arraycopy were faster, we wouldn't code this        for (int i = 0; i < count; ++i) {            result[i] = userGlyphs[i + start];        }         return result;    }     public int getGlyphCharIndex(int ix) {        if (ix < 0 && ix >= glyphs.length) {            throw new IndexOutOfBoundsException("" + ix);        }        if (charIndices == null) {            if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) {                return glyphs.length - 1 - ix;            }            return ix;        }        return charIndices[ix];    }     public int[] getGlyphCharIndices(int start, int count, int[] result) {        if (start < 0 || count < 0 || (count > glyphs.length - start)) {            throw new IndexOutOfBoundsException("" + start + ", " + count);        }        if (result == null) {            result = new int[count];        }        if (charIndices == null) {            if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) {                for (int i = 0, n = glyphs.length - 1 - start;                     i < count; ++i, --n) {                         result[i] = n;                     }            } else {                for (int i = 0, n = start; i < count; ++i, ++n) {                    result[i] = n;                }            }        } else {            for (int i = 0; i < count; ++i) {                result[i] = charIndices[i + start];            }        }        return result;    }     // !!! not cached, assume TextLayout will cache if necessary    // !!! reexamine for per-glyph-transforms    // !!! revisit for text-on-a-path, vertical    public Rectangle2D getLogicalBounds() {        setFRCTX();        initPositions();         LineMetrics lm = font.getLineMetrics("", frc);         float minX, minY, maxX, maxY;        // horiz only for now...        minX = 0;        minY = -lm.getAscent();        maxX = 0;        maxY = lm.getDescent() + lm.getLeading();        if (glyphs.length > 0) {            maxX = positions[positions.length - 2];        }         return new Rectangle2D.Float(minX, minY, maxX - minX, maxY - minY);    }     // !!! not cached, assume TextLayout will cache if necessary    public Rectangle2D getVisualBounds() {        Rectangle2D result = null;        for (int i = 0; i < glyphs.length; ++i) {            Rectangle2D glyphVB = getGlyphVisualBounds(i).getBounds2D();            if (!glyphVB.isEmpty()) {                if (result == null) {                    result = glyphVB;                } else {                    Rectangle2D.union(result, glyphVB, result);                }            }        }        if (result == null) {            result = new Rectangle2D.Float(0, 0, 0, 0);        }        return result;    }     // !!! not cached, assume TextLayout will cache if necessary    // !!! fontStrike needs a method for this    public Rectangle getPixelBounds(FontRenderContext renderFRC, float x, float y) {      return getGlyphsPixelBounds(renderFRC, x, y, 0, glyphs.length);    }     public Shape getOutline() {        return getGlyphsOutline(0, glyphs.length, 0, 0);    }     public Shape getOutline(float x, float y) {        return getGlyphsOutline(0, glyphs.length, x, y);    }     // relative to gv origin    public Shape getGlyphOutline(int ix) {        return getGlyphsOutline(ix, 1, 0, 0);    }     // relative to gv origin offset by x, y    public Shape getGlyphOutline(int ix, float x, float y) {        return getGlyphsOutline(ix, 1, x, y);    }     public Point2D getGlyphPosition(int ix) {        initPositions();         ix *= 2;        return new Point2D.Float(positions[ix], positions[ix + 1]);    }     public void setGlyphPosition(int ix, Point2D pos) {        initPositions();         int ix2 = ix << 1;        positions[ix2] = (float)pos.getX();        positions[ix2 + 1] = (float)pos.getY();         clearCaches(ix);        addFlags(FLAG_HAS_POSITION_ADJUSTMENTS);    }     public AffineTransform getGlyphTransform(int ix) {        if (ix < 0 || ix >= glyphs.length) {            throw new IndexOutOfBoundsException("ix = " + ix);        }        if (gti != null) {            return gti.getGlyphTransform(ix);        }        return null; // spec'd as returning null    }     public void setGlyphTransform(int ix, AffineTransform newTX) {        if (ix < 0 || ix >= glyphs.length) {            throw new IndexOutOfBoundsException("ix = " + ix);        }         if (gti == null) {            if (newTX == null || newTX.isIdentity()) {                return;            }            gti = new GlyphTransformInfo(this);        }        gti.setGlyphTransform(ix, newTX); // sets flags        if (gti.transformCount() == 0) {            gti = null;        }    }     public int getLayoutFlags() {        if (flags == UNINITIALIZED_FLAGS) {            flags = 0;             if (charIndices != null && glyphs.length > 1) {                boolean ltr = true;                boolean rtl = true;                 int rtlix = charIndices.length; // rtl index                for (int i = 0; i < charIndices.length && (ltr || rtl); ++i) {                    int cx = charIndices[i];                     ltr = ltr && (cx == i);                    rtl = rtl && (cx == --rtlix);                } 

Lines 1–500 of 1935.