1 /*
2  * Copyright (c) 1998, 2005, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 /*
27  * (C) Copyright IBM Corp. 1998-2003, All Rights Reserved
28  *
29  */
30 
31 package sun.font;
32 
33 import java.awt.Font;
34 import java.awt.Graphics2D;
35 import java.awt.Rectangle;
36 import java.awt.Shape;
37 import java.awt.font.FontRenderContext;
38 import java.awt.font.LineMetrics;
39 import java.awt.font.GraphicAttribute;
40 import java.awt.font.GlyphJustificationInfo;
41 import java.awt.geom.AffineTransform;
42 import java.awt.geom.GeneralPath;
43 import java.awt.geom.Rectangle2D;
44 import java.text.Bidi;
45 import java.util.Map;
46 
47 public final class GraphicComponent implements TextLineComponent,
48                                                Decoration.Label {
49 
50     public static final float GRAPHIC_LEADING = 2;
51 
52     private GraphicAttribute graphic;
53     private int graphicCount;
54     private int[] charsLtoV;  // possibly null
55     private byte[] levels; // possibly null
56 
57     // evaluated in computeVisualBounds
58     private Rectangle2D visualBounds = null;
59 
60     // used everywhere so we'll cache it
61     private float graphicAdvance;
62 
63     private AffineTransform baseTx;
64 
65     private CoreMetrics cm;
66     private Decoration decorator;
67 
68 
69     /**
70      * Create a new GraphicComponent.  start and limit are indices
71      * into charLtoV and levels.  charsLtoV and levels may be adopted.
72      */
GraphicComponent(GraphicAttribute graphic, Decoration decorator, int[] charsLtoV, byte[] levels, int start, int limit, AffineTransform baseTx)73     public GraphicComponent(GraphicAttribute graphic,
74                             Decoration decorator,
75                             int[] charsLtoV,
76                             byte[] levels,
77                             int start,
78                             int limit,
79                             AffineTransform baseTx) {
80 
81         if (limit <= start) {
82             throw new IllegalArgumentException("0 or negative length in GraphicComponent");
83         }
84         this.graphic = graphic;
85         this.graphicAdvance = graphic.getAdvance();
86         this.decorator = decorator;
87         this.cm = createCoreMetrics(graphic);
88         this.baseTx = baseTx;
89 
90         initLocalOrdering(charsLtoV, levels, start, limit);
91     }
92 
GraphicComponent(GraphicComponent parent, int start, int limit, int dir)93     private GraphicComponent(GraphicComponent parent, int start, int limit, int dir) {
94 
95         this.graphic = parent.graphic;
96         this.graphicAdvance = parent.graphicAdvance;
97         this.decorator = parent.decorator;
98         this.cm = parent.cm;
99         this.baseTx = parent.baseTx;
100 
101         int[] charsLtoV = null;
102         byte[] levels = null;
103 
104         if (dir == UNCHANGED) {
105             charsLtoV = parent.charsLtoV;
106             levels = parent.levels;
107         }
108         else if (dir == LEFT_TO_RIGHT || dir == RIGHT_TO_LEFT) {
109             limit -= start;
110             start = 0;
111             if (dir == RIGHT_TO_LEFT) {
112                 charsLtoV = new int[limit];
113                 levels = new byte[limit];
114                 for (int i=0; i < limit; i++) {
115                     charsLtoV[i] = limit-i-1;
116                     levels[i] = (byte) 1;
117                 }
118             }
119         }
120         else {
121             throw new IllegalArgumentException("Invalid direction flag");
122         }
123 
124         initLocalOrdering(charsLtoV, levels, start, limit);
125     }
126 
127     /**
128      * Initialize graphicCount, also charsLtoV and levels arrays.
129      */
initLocalOrdering(int[] charsLtoV, byte[] levels, int start, int limit)130     private void initLocalOrdering(int[] charsLtoV,
131                                    byte[] levels,
132                                    int start,
133                                    int limit) {
134 
135         this.graphicCount = limit - start; // todo: should be codepoints?
136 
137         if (charsLtoV == null || charsLtoV.length == graphicCount) {
138             this.charsLtoV = charsLtoV;
139         }
140         else {
141             this.charsLtoV = BidiUtils.createNormalizedMap(charsLtoV, levels, start, limit);
142         }
143 
144         if (levels == null || levels.length == graphicCount) {
145             this.levels = levels;
146         }
147         else {
148             this.levels = new byte[graphicCount];
149             System.arraycopy(levels, start, this.levels, 0, graphicCount);
150         }
151     }
152 
isSimple()153     public boolean isSimple() {
154         return false;
155     }
156 
getPixelBounds(FontRenderContext frc, float x, float y)157     public Rectangle getPixelBounds(FontRenderContext frc, float x, float y) {
158         throw new InternalError("do not call if isSimple returns false");
159     }
160 
handleGetVisualBounds()161     public Rectangle2D handleGetVisualBounds() {
162 
163         Rectangle2D bounds = graphic.getBounds();
164 
165         float width = (float) bounds.getWidth() +
166                                  graphicAdvance * (graphicCount-1);
167 
168         return new Rectangle2D.Float((float) bounds.getX(),
169                                      (float) bounds.getY(),
170                                      width,
171                                      (float) bounds.getHeight());
172     }
173 
getCoreMetrics()174     public CoreMetrics getCoreMetrics() {
175         return cm;
176     }
177 
createCoreMetrics(GraphicAttribute graphic)178     public static CoreMetrics createCoreMetrics(GraphicAttribute graphic) {
179         return new CoreMetrics(graphic.getAscent(),
180                                graphic.getDescent(),
181                                GRAPHIC_LEADING,
182                                graphic.getAscent() + graphic.getDescent() + GRAPHIC_LEADING,
183                                graphic.getAlignment(),
184                                new float[] { 0, -graphic.getAscent() / 2, -graphic.getAscent() },
185                                -graphic.getAscent() / 2,
186                                graphic.getAscent() / 12,
187                                graphic.getDescent() / 3,
188                                graphic.getAscent() / 12,
189                                0, // ss offset
190                                0); // italic angle -- need api for this
191     }
192 
getItalicAngle()193     public float getItalicAngle() {
194 
195         return 0;
196     }
197 
getVisualBounds()198     public Rectangle2D getVisualBounds() {
199 
200         if (visualBounds == null) {
201             visualBounds = decorator.getVisualBounds(this);
202         }
203         Rectangle2D.Float bounds = new Rectangle2D.Float();
204         bounds.setRect(visualBounds);
205         return bounds;
206     }
207 
handleGetOutline(float x, float y)208     public Shape handleGetOutline(float x, float y) {
209         double[] matrix = { 1, 0, 0, 1, x, y };
210 
211         if (graphicCount == 1) {
212             AffineTransform tx = new AffineTransform(matrix);
213             return graphic.getOutline(tx);
214         }
215 
216         GeneralPath gp = new GeneralPath();
217         for (int i = 0; i < graphicCount; ++i) {
218             AffineTransform tx = new AffineTransform(matrix);
219             gp.append(graphic.getOutline(tx), false);
220             matrix[4] += graphicAdvance;
221         }
222 
223         return gp;
224     }
225 
getBaselineTransform()226     public AffineTransform getBaselineTransform() {
227         return baseTx;
228     }
229 
getOutline(float x, float y)230     public Shape getOutline(float x, float y) {
231 
232         return decorator.getOutline(this, x, y);
233     }
234 
handleDraw(Graphics2D g2d, float x, float y)235     public void handleDraw(Graphics2D g2d, float x, float y) {
236 
237         for (int i=0; i < graphicCount; i++) {
238 
239             graphic.draw(g2d, x, y);
240             x += graphicAdvance;
241         }
242     }
243 
draw(Graphics2D g2d, float x, float y)244     public void draw(Graphics2D g2d, float x, float y) {
245 
246         decorator.drawTextAndDecorations(this, g2d, x, y);
247     }
248 
getCharVisualBounds(int index)249     public Rectangle2D getCharVisualBounds(int index) {
250 
251         return decorator.getCharVisualBounds(this, index);
252     }
253 
getNumCharacters()254     public int getNumCharacters() {
255 
256         return graphicCount;
257     }
258 
getCharX(int index)259     public float getCharX(int index) {
260 
261         int visIndex = charsLtoV==null? index : charsLtoV[index];
262         return graphicAdvance * visIndex;
263     }
264 
getCharY(int index)265     public float getCharY(int index) {
266 
267         return 0;
268     }
269 
getCharAdvance(int index)270     public float getCharAdvance(int index) {
271 
272         return graphicAdvance;
273     }
274 
caretAtOffsetIsValid(int index)275     public boolean caretAtOffsetIsValid(int index) {
276 
277         return true;
278     }
279 
handleGetCharVisualBounds(int index)280     public Rectangle2D handleGetCharVisualBounds(int index) {
281 
282         Rectangle2D bounds = graphic.getBounds();
283         // don't modify their rectangle, just in case they don't copy
284 
285         Rectangle2D.Float charBounds = new Rectangle2D.Float();
286         charBounds.setRect(bounds);
287         charBounds.x += graphicAdvance * index;
288 
289         return charBounds;
290     }
291 
292     // measures characters in context, in logical order
getLineBreakIndex(int start, float width)293     public int getLineBreakIndex(int start, float width) {
294 
295         int index = (int) (width / graphicAdvance);
296         if (index > graphicCount - start) {
297             index = graphicCount - start;
298         }
299         return index;
300     }
301 
302     // measures characters in context, in logical order
getAdvanceBetween(int start, int limit)303     public float getAdvanceBetween(int start, int limit) {
304 
305         return graphicAdvance * (limit - start);
306     }
307 
getLogicalBounds()308     public Rectangle2D getLogicalBounds() {
309 
310         float left = 0;
311         float top = -cm.ascent;
312         float width = graphicAdvance * graphicCount;
313         float height = cm.descent - top;
314 
315         return new Rectangle2D.Float(left, top, width, height);
316     }
317 
getAdvance()318     public float getAdvance() {
319         return graphicAdvance * graphicCount;
320     }
321 
getItalicBounds()322     public Rectangle2D getItalicBounds() {
323         return getLogicalBounds();
324     }
325 
getSubset(int start, int limit, int dir)326     public TextLineComponent getSubset(int start, int limit, int dir) {
327 
328         if (start < 0 || limit > graphicCount || start >= limit) {
329             throw new IllegalArgumentException("Invalid range.  start="
330                                                +start+"; limit="+limit);
331         }
332 
333         if (start == 0 && limit == graphicCount && dir == UNCHANGED) {
334             return this;
335         }
336 
337         return new GraphicComponent(this, start, limit, dir);
338     }
339 
toString()340     public String toString() {
341 
342         return "[graphic=" + graphic + ":count=" + getNumCharacters() + "]";
343     }
344 
345   /**
346    * Return the number of justification records this uses.
347    */
getNumJustificationInfos()348   public int getNumJustificationInfos() {
349     return 0;
350   }
351 
352   /**
353    * Return GlyphJustificationInfo objects for the characters between
354    * charStart and charLimit, starting at offset infoStart.  Infos
355    * will be in visual order.  All positions between infoStart and
356    * getNumJustificationInfos will be set.  If a position corresponds
357    * to a character outside the provided range, it is set to null.
358    */
getJustificationInfos(GlyphJustificationInfo[] infos, int infoStart, int charStart, int charLimit)359   public void getJustificationInfos(GlyphJustificationInfo[] infos, int infoStart, int charStart, int charLimit) {
360   }
361 
362   /**
363    * Apply deltas to the data in this component, starting at offset
364    * deltaStart, and return the new component.  There are two floats
365    * for each justification info, for a total of 2 * getNumJustificationInfos.
366    * The first delta is the left adjustment, the second is the right
367    * adjustment.
368    * <p>
369    * If flags[0] is true on entry, rejustification is allowed.  If
370    * the new component requires rejustification (ligatures were
371    * formed or split), flags[0] will be set on exit.
372    */
applyJustificationDeltas(float[] deltas, int deltaStart, boolean[] flags)373   public TextLineComponent applyJustificationDeltas(float[] deltas, int deltaStart, boolean[] flags) {
374     return this;
375   }
376 }
377