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