| /* | |
| * 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. | |
| */ | |
| /* | |
| * (C) Copyright IBM Corp. 1998-2003, All Rights Reserved | |
| * | |
| */ | |
| package sun.font; | |
| import java.awt.Font; | |
| import java.awt.Graphics2D; | |
| import java.awt.Rectangle; | |
| import java.awt.Shape; | |
| import java.awt.font.FontRenderContext; | |
| import java.awt.font.LineMetrics; | |
| import java.awt.font.GraphicAttribute; | |
| import java.awt.font.GlyphJustificationInfo; | |
| import java.awt.geom.AffineTransform; | |
| import java.awt.geom.GeneralPath; | |
| import java.awt.geom.Rectangle2D; | |
| import java.text.Bidi; | |
| import java.util.Map; | |
| public final class GraphicComponent implements TextLineComponent, | |
| Decoration.Label { | |
| public static final float GRAPHIC_LEADING = 2; | |
| private GraphicAttribute graphic; | |
| private int graphicCount; | |
| private int[] charsLtoV; // possibly null | |
| private byte[] levels; // possibly null | |
| // evaluated in computeVisualBounds | |
| private Rectangle2D visualBounds = null; | |
| // used everywhere so we'll cache it | |
| private float graphicAdvance; | |
| private AffineTransform baseTx; | |
| private CoreMetrics cm; | |
| private Decoration decorator; | |
| /** | |
| * Create a new GraphicComponent. start and limit are indices | |
| * into charLtoV and levels. charsLtoV and levels may be adopted. | |
| */ | |
| public GraphicComponent(GraphicAttribute graphic, | |
| Decoration decorator, | |
| int[] charsLtoV, | |
| byte[] levels, | |
| int start, | |
| int limit, | |
| AffineTransform baseTx) { | |
| if (limit <= start) { | |
| throw new IllegalArgumentException("0 or negative length in GraphicComponent"); | |
| } | |
| this.graphic = graphic; | |
| this.graphicAdvance = graphic.getAdvance(); | |
| this.decorator = decorator; | |
| this.cm = createCoreMetrics(graphic); | |
| this.baseTx = baseTx; | |
| initLocalOrdering(charsLtoV, levels, start, limit); | |
| } | |
| private GraphicComponent(GraphicComponent parent, int start, int limit, int dir) { | |
| this.graphic = parent.graphic; | |
| this.graphicAdvance = parent.graphicAdvance; | |
| this.decorator = parent.decorator; | |
| this.cm = parent.cm; | |
| this.baseTx = parent.baseTx; | |
| int[] charsLtoV = null; | |
| byte[] levels = null; | |
| if (dir == UNCHANGED) { | |
| charsLtoV = parent.charsLtoV; | |
| levels = parent.levels; | |
| } | |
| else if (dir == LEFT_TO_RIGHT || dir == RIGHT_TO_LEFT) { | |
| limit -= start; | |
| start = 0; | |
| if (dir == RIGHT_TO_LEFT) { | |
| charsLtoV = new int[limit]; | |
| levels = new byte[limit]; | |
| for (int i=0; i < limit; i++) { | |
| charsLtoV[i] = limit-i-1; | |
| levels[i] = (byte) 1; | |
| } | |
| } | |
| } | |
| else { | |
| throw new IllegalArgumentException("Invalid direction flag"); | |
| } | |
| initLocalOrdering(charsLtoV, levels, start, limit); | |
| } | |
| /** | |
| * Initialize graphicCount, also charsLtoV and levels arrays. | |
| */ | |
| private void initLocalOrdering(int[] charsLtoV, | |
| byte[] levels, | |
| int start, | |
| int limit) { | |
| this.graphicCount = limit - start; // todo: should be codepoints? | |
| if (charsLtoV == null || charsLtoV.length == graphicCount) { | |
| this.charsLtoV = charsLtoV; | |
| } | |
| else { | |
| this.charsLtoV = BidiUtils.createNormalizedMap(charsLtoV, levels, start, limit); | |
| } | |
| if (levels == null || levels.length == graphicCount) { | |
| this.levels = levels; | |
| } | |
| else { | |
| this.levels = new byte[graphicCount]; | |
| System.arraycopy(levels, start, this.levels, 0, graphicCount); | |
| } | |
| } | |
| public boolean isSimple() { | |
| return false; | |
| } | |
| public Rectangle getPixelBounds(FontRenderContext frc, float x, float y) { | |
| throw new InternalError("do not call if isSimple returns false"); | |
| } | |
| public Rectangle2D handleGetVisualBounds() { | |
| Rectangle2D bounds = graphic.getBounds(); | |
| float width = (float) bounds.getWidth() + | |
| graphicAdvance * (graphicCount-1); | |
| return new Rectangle2D.Float((float) bounds.getX(), | |
| (float) bounds.getY(), | |
| width, | |
| (float) bounds.getHeight()); | |
| } | |
| public CoreMetrics getCoreMetrics() { | |
| return cm; | |
| } | |
| public static CoreMetrics createCoreMetrics(GraphicAttribute graphic) { | |
| return new CoreMetrics(graphic.getAscent(), | |
| graphic.getDescent(), | |
| GRAPHIC_LEADING, | |
| graphic.getAscent() + graphic.getDescent() + GRAPHIC_LEADING, | |
| graphic.getAlignment(), | |
| new float[] { 0, -graphic.getAscent() / 2, -graphic.getAscent() }, | |
| -graphic.getAscent() / 2, | |
| graphic.getAscent() / 12, | |
| graphic.getDescent() / 3, | |
| graphic.getAscent() / 12, | |
| 0, // ss offset | |
| 0); // italic angle -- need api for this | |
| } | |
| public float getItalicAngle() { | |
| return 0; | |
| } | |
| public Rectangle2D getVisualBounds() { | |
| if (visualBounds == null) { | |
| visualBounds = decorator.getVisualBounds(this); | |
| } | |
| Rectangle2D.Float bounds = new Rectangle2D.Float(); | |
| bounds.setRect(visualBounds); | |
| return bounds; | |
| } | |
| public Shape handleGetOutline(float x, float y) { | |
| double[] matrix = { 1, 0, 0, 1, x, y }; | |
| if (graphicCount == 1) { | |
| AffineTransform tx = new AffineTransform(matrix); | |
| return graphic.getOutline(tx); | |
| } | |
| GeneralPath gp = new GeneralPath(); | |
| for (int i = 0; i < graphicCount; ++i) { | |
| AffineTransform tx = new AffineTransform(matrix); | |
| gp.append(graphic.getOutline(tx), false); | |
| matrix[4] += graphicAdvance; | |
| } | |
| return gp; | |
| } | |
| public AffineTransform getBaselineTransform() { | |
| return baseTx; | |
| } | |
| public Shape getOutline(float x, float y) { | |
| return decorator.getOutline(this, x, y); | |
| } | |
| public void handleDraw(Graphics2D g2d, float x, float y) { | |
| for (int i=0; i < graphicCount; i++) { | |
| graphic.draw(g2d, x, y); | |
| x += graphicAdvance; | |
| } | |
| } | |
| public void draw(Graphics2D g2d, float x, float y) { | |
| decorator.drawTextAndDecorations(this, g2d, x, y); | |
| } | |
| public Rectangle2D getCharVisualBounds(int index) { | |
| return decorator.getCharVisualBounds(this, index); | |
| } | |
| public int getNumCharacters() { | |
| return graphicCount; | |
| } | |
| public float getCharX(int index) { | |
| int visIndex = charsLtoV==null? index : charsLtoV[index]; | |
| return graphicAdvance * visIndex; | |
| } | |
| public float getCharY(int index) { | |
| return 0; | |
| } | |
| public float getCharAdvance(int index) { | |
| return graphicAdvance; | |
| } | |
| public boolean caretAtOffsetIsValid(int index) { | |
| return true; | |
| } | |
| public Rectangle2D handleGetCharVisualBounds(int index) { | |
| Rectangle2D bounds = graphic.getBounds(); | |
| // don't modify their rectangle, just in case they don't copy | |
| Rectangle2D.Float charBounds = new Rectangle2D.Float(); | |
| charBounds.setRect(bounds); | |
| charBounds.x += graphicAdvance * index; | |
| return charBounds; | |
| } | |
| // measures characters in context, in logical order | |
| public int getLineBreakIndex(int start, float width) { | |
| int index = (int) (width / graphicAdvance); | |
| if (index > graphicCount - start) { | |
| index = graphicCount - start; | |
| } | |
| return index; | |
| } | |
| // measures characters in context, in logical order | |
| public float getAdvanceBetween(int start, int limit) { | |
| return graphicAdvance * (limit - start); | |
| } | |
| public Rectangle2D getLogicalBounds() { | |
| float left = 0; | |
| float top = -cm.ascent; | |
| float width = graphicAdvance * graphicCount; | |
| float height = cm.descent - top; | |
| return new Rectangle2D.Float(left, top, width, height); | |
| } | |
| public float getAdvance() { | |
| return graphicAdvance * graphicCount; | |
| } | |
| public Rectangle2D getItalicBounds() { | |
| return getLogicalBounds(); | |
| } | |
| public TextLineComponent getSubset(int start, int limit, int dir) { | |
| if (start < 0 || limit > graphicCount || start >= limit) { | |
| throw new IllegalArgumentException("Invalid range. start=" | |
| +start+"; limit="+limit); | |
| } | |
| if (start == 0 && limit == graphicCount && dir == UNCHANGED) { | |
| return this; | |
| } | |
| return new GraphicComponent(this, start, limit, dir); | |
| } | |
| public String toString() { | |
| return "[graphic=" + graphic + ":count=" + getNumCharacters() + "]"; | |
| } | |
| /** | |
| * Return the number of justification records this uses. | |
| */ | |
| public int getNumJustificationInfos() { | |
| return 0; | |
| } | |
| /** | |
| * Return GlyphJustificationInfo objects for the characters between | |
| * charStart and charLimit, starting at offset infoStart. Infos | |
| * will be in visual order. All positions between infoStart and | |
| * getNumJustificationInfos will be set. If a position corresponds | |
| * to a character outside the provided range, it is set to null. | |
| */ | |
| public void getJustificationInfos(GlyphJustificationInfo[] infos, int infoStart, int charStart, int charLimit) { | |
| } | |
| /** | |
| * Apply deltas to the data in this component, starting at offset | |
| * deltaStart, and return the new component. There are two floats | |
| * for each justification info, for a total of 2 * getNumJustificationInfos. | |
| * The first delta is the left adjustment, the second is the right | |
| * adjustment. | |
| * <p> | |
| * If flags[0] is true on entry, rejustification is allowed. If | |
| * the new component requires rejustification (ligatures were | |
| * formed or split), flags[0] will be set on exit. | |
| */ | |
| public TextLineComponent applyJustificationDeltas(float[] deltas, int deltaStart, boolean[] flags) { | |
| return this; | |
| } | |
| } |