| /* |
| * 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; |
| } |
| } |