author | prr |
Mon, 30 Nov 2009 14:39:35 -0800 | |
changeset 4358 | 0549f5b9abd1 |
parent 3928 | be186a33df9b |
child 5506 | 202f599c92aa |
permissions | -rw-r--r-- |
2 | 1 |
/* |
2 |
* Copyright 1998-2005 Sun Microsystems, Inc. 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. Sun designates this |
|
8 |
* particular file as subject to the "Classpath" exception as provided |
|
9 |
* by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
|
22 |
* CA 95054 USA or visit www.sun.com if you need additional information or |
|
23 |
* have any questions. |
|
24 |
*/ |
|
25 |
||
26 |
package sun.font; |
|
27 |
||
28 |
import java.awt.Font; |
|
29 |
import java.awt.Graphics2D; |
|
30 |
import java.awt.Point; |
|
31 |
import java.awt.Rectangle; |
|
32 |
import static java.awt.RenderingHints.*; |
|
33 |
import java.awt.Shape; |
|
34 |
import java.awt.font.FontRenderContext; |
|
35 |
import java.awt.font.GlyphMetrics; |
|
36 |
import java.awt.font.GlyphJustificationInfo; |
|
37 |
import java.awt.font.GlyphVector; |
|
38 |
import java.awt.font.LineMetrics; |
|
39 |
import java.awt.font.TextAttribute; |
|
40 |
import java.awt.geom.AffineTransform; |
|
41 |
import java.awt.geom.GeneralPath; |
|
42 |
import java.awt.geom.NoninvertibleTransformException; |
|
43 |
import java.awt.geom.PathIterator; |
|
44 |
import java.awt.geom.Point2D; |
|
45 |
import java.awt.geom.Rectangle2D; |
|
46 |
import java.lang.ref.SoftReference; |
|
47 |
import java.text.CharacterIterator; |
|
48 |
||
49 |
import sun.awt.SunHints; |
|
50 |
import sun.java2d.loops.FontInfo; |
|
51 |
||
52 |
/** |
|
53 |
* Standard implementation of GlyphVector used by Font, GlyphList, and |
|
54 |
* SunGraphics2D. |
|
55 |
* |
|
56 |
* The main issues involve the semantics of the various transforms |
|
57 |
* (font, glyph, device) and their effect on rendering and metrics. |
|
58 |
* |
|
59 |
* Very, very unfortunately, the translation component of the font |
|
60 |
* transform affects where the text gets rendered. It offsets the |
|
61 |
* rendering origin. None of the other metrics of the glyphvector |
|
62 |
* are affected, making them inconsistent with the rendering behavior. |
|
63 |
* I think the translation component of the font would be better |
|
64 |
* interpreted as the translation component of a per-glyph transform, |
|
65 |
* but I don't know if this is possible to change. |
|
66 |
* |
|
67 |
* After the font transform is applied, the glyph transform is |
|
68 |
* applied. This makes glyph transforms relative to font transforms, |
|
69 |
* if the font transform changes, the glyph transform will have the |
|
70 |
* same (relative) effect on the outline of the glyph. The outline |
|
71 |
* and logical bounds are passed through the glyph transform before |
|
72 |
* being returned. The glyph metrics ignore the glyph transform, but |
|
73 |
* provide the outline bounds and the advance vector of the glyph (the |
|
74 |
* latter will be rotated if the font is rotated). The default layout |
|
75 |
* places each glyph at the end of the advance vector of the previous |
|
76 |
* glyph, and since the glyph transform translates the advance vector, |
|
77 |
* this means a glyph transform affects the positions of all |
|
78 |
* subsequent glyphs if defaultLayout is called after setting a glyph |
|
79 |
* transform. In the glyph info array, the bounds are the outline |
|
80 |
* bounds including the glyph transform, and the positions are as |
|
81 |
* computed, and the advances are the deltas between the positions. |
|
82 |
* |
|
83 |
* (There's a bug in the logical bounds of a rotated glyph for |
|
84 |
* composite fonts, it's not to spec (in 1.4.0, 1.4.1, 1.4.2). The |
|
85 |
* problem is that the rotated composite doesn't handle the multiple |
|
86 |
* ascents and descents properly in both x and y. You end up with |
|
87 |
* a rotated advance vector but an unrotated ascent and descent.) |
|
88 |
* |
|
89 |
* Finally, the whole thing is transformed by the device transform to |
|
90 |
* position it on the page. |
|
91 |
* |
|
92 |
* Another bug: The glyph outline seems to ignore fractional point |
|
93 |
* size information, but the images (and advances) don't ignore it. |
|
94 |
* |
|
95 |
* Small fonts drawn at large magnification have odd advances when |
|
96 |
* fractional metrics is off-- that's because the advances depend on |
|
97 |
* the frc. When the frc is scaled appropriately, the advances are |
|
98 |
* fine. FM or a large frc (high numbers) make the advances right. |
|
99 |
* |
|
100 |
* The buffer aa flag doesn't affect rendering, the glyph vector |
|
101 |
* renders as AA if aa is set in its frc, and as non-aa if aa is not |
|
102 |
* set in its frc. |
|
103 |
* |
|
104 |
* font rotation, baseline, vertical etc. |
|
105 |
* |
|
106 |
* Font rotation and baseline Line metrics should be measured along a |
|
107 |
* unit vector pi/4 cc from the baseline vector. For 'horizontal' |
|
108 |
* fonts the baseline vector is the x vector passed through the font |
|
109 |
* transform (ignoring translation), for 'vertical' it is the y |
|
110 |
* vector. This definition makes ascent, descent, etc independent of |
|
111 |
* shear, so shearing can be used to simulate italic. This means no |
|
112 |
* fonts have 'negative ascents' or 'zero ascents' etc. |
|
113 |
* |
|
114 |
* Having a coordinate system with orthogonal axes where one is |
|
115 |
* parallel to the baseline means we could use rectangles and interpret |
|
116 |
* them in terms of this coordinate system. Unfortunately there |
|
117 |
* is support for rotated fonts in the jdk already so maintaining |
|
118 |
* the semantics of existing code (getlogical bounds, etc) might |
|
119 |
* be difficult. |
|
120 |
* |
|
121 |
* A font transform transforms both the baseline and all the glyphs |
|
122 |
* in the font, so it does not rotate the glyph w.r.t the baseline. |
|
123 |
* If you do want to rotate individual glyphs, you need to apply a |
|
124 |
* glyph transform. If performDefaultLayout is called after this, |
|
125 |
* the transformed glyph advances will affect the glyph positions. |
|
126 |
* |
|
127 |
* useful additions |
|
128 |
* - select vertical metrics - glyphs are rotated pi/4 cc and vertical |
|
129 |
* metrics are used to align them to the baseline. |
|
130 |
* - define baseline for font (glyph rotation not linked to baseline) |
|
131 |
* - define extra space (delta between each glyph along baseline) |
|
132 |
* - define offset (delta from 'true' baseline, impacts ascent and |
|
133 |
* descent as these are still computed from true basline and pinned |
|
134 |
* to zero, used in superscript). |
|
135 |
*/ |
|
136 |
public class StandardGlyphVector extends GlyphVector { |
|
137 |
private Font font; |
|
138 |
private FontRenderContext frc; |
|
139 |
private int[] glyphs; // always |
|
140 |
private int[] userGlyphs; // used to return glyphs to the client. |
|
141 |
private float[] positions; // only if not default advances |
|
142 |
private int[] charIndices; // only if interesting |
|
143 |
private int flags; // indicates whether positions, charIndices is interesting |
|
144 |
||
145 |
private static final int UNINITIALIZED_FLAGS = -1; |
|
146 |
||
147 |
// transforms information |
|
148 |
private GlyphTransformInfo gti; // information about per-glyph transforms |
|
149 |
||
150 |
// !!! can we get rid of any of this extra stuff? |
|
151 |
private AffineTransform ftx; // font transform without translation |
|
152 |
private AffineTransform dtx; // device transform used for strike calculations, no translation |
|
153 |
private AffineTransform invdtx; // inverse of dtx or null if dtx is identity |
|
154 |
private AffineTransform frctx; // font render context transform, wish we could just share it |
|
155 |
private Font2D font2D; // basic strike-independent stuff |
|
156 |
private SoftReference fsref; // font strike reference for glyphs with no per-glyph transform |
|
157 |
||
158 |
///////////////////////////// |
|
159 |
// Constructors and Factory methods |
|
160 |
///////////////////////////// |
|
161 |
||
162 |
public StandardGlyphVector(Font font, String str, FontRenderContext frc) { |
|
163 |
init(font, str.toCharArray(), 0, str.length(), frc, UNINITIALIZED_FLAGS); |
|
164 |
} |
|
165 |
||
166 |
public StandardGlyphVector(Font font, char[] text, FontRenderContext frc) { |
|
167 |
init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS); |
|
168 |
} |
|
169 |
||
170 |
public StandardGlyphVector(Font font, char[] text, int start, int count, |
|
171 |
FontRenderContext frc) { |
|
172 |
init(font, text, start, count, frc, UNINITIALIZED_FLAGS); |
|
173 |
} |
|
174 |
||
175 |
private float getTracking(Font font) { |
|
176 |
if (font.hasLayoutAttributes()) { |
|
177 |
AttributeValues values = ((AttributeMap)font.getAttributes()).getValues(); |
|
178 |
return values.getTracking(); |
|
179 |
} |
|
180 |
return 0; |
|
181 |
} |
|
182 |
||
183 |
// used by GlyphLayout to construct a glyphvector |
|
184 |
public StandardGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions, |
|
185 |
int[] indices, int flags) { |
|
186 |
initGlyphVector(font, frc, glyphs, positions, indices, flags); |
|
187 |
||
188 |
// this code should go into layout |
|
189 |
float track = getTracking(font); |
|
190 |
if (track != 0) { |
|
191 |
track *= font.getSize2D(); |
|
192 |
Point2D.Float trackPt = new Point2D.Float(track, 0); // advance delta |
|
193 |
if (font.isTransformed()) { |
|
194 |
AffineTransform at = font.getTransform(); |
|
195 |
at.deltaTransform(trackPt, trackPt); |
|
196 |
} |
|
197 |
||
198 |
// how do we know its a base glyph |
|
199 |
// for now, it is if the natural advance of the glyph is non-zero |
|
3928 | 200 |
Font2D f2d = FontUtilities.getFont2D(font); |
2 | 201 |
FontStrike strike = f2d.getStrike(font, frc); |
202 |
||
203 |
float[] deltas = { trackPt.x, trackPt.y }; |
|
204 |
for (int j = 0; j < deltas.length; ++j) { |
|
205 |
float inc = deltas[j]; |
|
206 |
if (inc != 0) { |
|
207 |
float delta = 0; |
|
208 |
for (int i = j, n = 0; n < glyphs.length; i += 2) { |
|
209 |
if (strike.getGlyphAdvance(glyphs[n++]) != 0) { // might be an inadequate test |
|
210 |
positions[i] += delta; |
|
211 |
delta += inc; |
|
212 |
} |
|
213 |
} |
|
214 |
positions[positions.length-2+j] += delta; |
|
215 |
} |
|
216 |
} |
|
217 |
} |
|
218 |
} |
|
219 |
||
220 |
public void initGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions, |
|
221 |
int[] indices, int flags) { |
|
222 |
this.font = font; |
|
223 |
this.frc = frc; |
|
224 |
this.glyphs = glyphs; |
|
225 |
this.userGlyphs = glyphs; // no need to check |
|
226 |
this.positions = positions; |
|
227 |
this.charIndices = indices; |
|
228 |
this.flags = flags; |
|
229 |
||
230 |
initFontData(); |
|
231 |
} |
|
232 |
||
233 |
public StandardGlyphVector(Font font, CharacterIterator iter, FontRenderContext frc) { |
|
234 |
int offset = iter.getBeginIndex(); |
|
235 |
char[] text = new char [iter.getEndIndex() - offset]; |
|
236 |
for(char c = iter.first(); |
|
237 |
c != CharacterIterator.DONE; |
|
238 |
c = iter.next()) { |
|
239 |
text[iter.getIndex() - offset] = c; |
|
240 |
} |
|
241 |
init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS); |
|
242 |
} |
|
243 |
||
244 |
public StandardGlyphVector(Font font, int[] glyphs, FontRenderContext frc) { |
|
245 |
// !!! find callers of this |
|
246 |
// should be able to fully init from raw data, e.g. charmap, flags too. |
|
247 |
this.font = font; |
|
248 |
this.frc = frc; |
|
249 |
this.flags = UNINITIALIZED_FLAGS; |
|
250 |
||
251 |
initFontData(); |
|
252 |
this.userGlyphs = glyphs; |
|
253 |
this.glyphs = getValidatedGlyphs(this.userGlyphs); |
|
254 |
} |
|
255 |
||
256 |
/* This is called from the rendering loop. FontInfo is supplied |
|
257 |
* because a GV caches a strike and glyph images suitable for its FRC. |
|
258 |
* LCD text isn't currently supported on all surfaces, in which case |
|
259 |
* standard AA must be used. This is most likely to occur when LCD text |
|
260 |
* is requested and the surface is some non-standard type or hardward |
|
261 |
* surface for which there are no accelerated loops. |
|
262 |
* We can detect this as being AA=="ON" in the FontInfo and AA!="ON" |
|
263 |
* and AA!="GASP" in the FRC - since this only occurs for LCD text we don't |
|
264 |
* need to check any more precisely what value is in the FRC. |
|
265 |
*/ |
|
266 |
public static StandardGlyphVector getStandardGV(GlyphVector gv, |
|
267 |
FontInfo info) { |
|
268 |
if (info.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) { |
|
269 |
Object aaHint = gv.getFontRenderContext().getAntiAliasingHint(); |
|
270 |
if (aaHint != VALUE_TEXT_ANTIALIAS_ON && |
|
271 |
aaHint != VALUE_TEXT_ANTIALIAS_GASP) { |
|
272 |
/* We need to create a new GV with AA==ON for rendering */ |
|
273 |
FontRenderContext frc = gv.getFontRenderContext(); |
|
274 |
frc = new FontRenderContext(frc.getTransform(), |
|
275 |
VALUE_TEXT_ANTIALIAS_ON, |
|
276 |
frc.getFractionalMetricsHint()); |
|
277 |
return new StandardGlyphVector(gv, frc); |
|
278 |
} |
|
279 |
} |
|
280 |
if (gv instanceof StandardGlyphVector) { |
|
281 |
return (StandardGlyphVector)gv; |
|
282 |
} |
|
283 |
return new StandardGlyphVector(gv, gv.getFontRenderContext()); |
|
284 |
} |
|
285 |
||
286 |
///////////////////////////// |
|
287 |
// GlyphVector API |
|
288 |
///////////////////////////// |
|
289 |
||
290 |
public Font getFont() { |
|
291 |
return this.font; |
|
292 |
} |
|
293 |
||
294 |
public FontRenderContext getFontRenderContext() { |
|
295 |
return this.frc; |
|
296 |
} |
|
297 |
||
298 |
public void performDefaultLayout() { |
|
299 |
positions = null; |
|
300 |
if (getTracking(font) == 0) { |
|
301 |
clearFlags(FLAG_HAS_POSITION_ADJUSTMENTS); |
|
302 |
} |
|
303 |
} |
|
304 |
||
305 |
public int getNumGlyphs() { |
|
306 |
return glyphs.length; |
|
307 |
} |
|
308 |
||
309 |
public int getGlyphCode(int glyphIndex) { |
|
310 |
return userGlyphs[glyphIndex]; |
|
311 |
} |
|
312 |
||
313 |
public int[] getGlyphCodes(int start, int count, int[] result) { |
|
314 |
if (count < 0) { |
|
315 |
throw new IllegalArgumentException("count = " + count); |
|
316 |
} |
|
317 |
if (start < 0) { |
|
318 |
throw new IndexOutOfBoundsException("start = " + start); |
|
319 |
} |
|
320 |
if (start > glyphs.length - count) { // watch out for overflow if index + count overlarge |
|
321 |
throw new IndexOutOfBoundsException("start + count = " + (start + count)); |
|
322 |
} |
|
323 |
||
324 |
if (result == null) { |
|
325 |
result = new int[count]; |
|
326 |
} |
|
327 |
||
328 |
// if arraycopy were faster, we wouldn't code this |
|
329 |
for (int i = 0; i < count; ++i) { |
|
330 |
result[i] = userGlyphs[i + start]; |
|
331 |
} |
|
332 |
||
333 |
return result; |
|
334 |
} |
|
335 |
||
336 |
public int getGlyphCharIndex(int ix) { |
|
337 |
if (ix < 0 && ix >= glyphs.length) { |
|
338 |
throw new IndexOutOfBoundsException("" + ix); |
|
339 |
} |
|
340 |
if (charIndices == null) { |
|
341 |
if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) { |
|
342 |
return glyphs.length - 1 - ix; |
|
343 |
} |
|
344 |
return ix; |
|
345 |
} |
|
346 |
return charIndices[ix]; |
|
347 |
} |
|
348 |
||
349 |
public int[] getGlyphCharIndices(int start, int count, int[] result) { |
|
350 |
if (start < 0 || count < 0 || (count > glyphs.length - start)) { |
|
351 |
throw new IndexOutOfBoundsException("" + start + ", " + count); |
|
352 |
} |
|
353 |
if (result == null) { |
|
354 |
result = new int[count]; |
|
355 |
} |
|
356 |
if (charIndices == null) { |
|
357 |
if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) { |
|
358 |
for (int i = 0, n = glyphs.length - 1 - start; |
|
359 |
i < count; ++i, --n) { |
|
360 |
result[i] = n; |
|
361 |
} |
|
362 |
} else { |
|
363 |
for (int i = 0, n = start; i < count; ++i, ++n) { |
|
364 |
result[i] = n; |
|
365 |
} |
|
366 |
} |
|
367 |
} else { |
|
368 |
for (int i = 0; i < count; ++i) { |
|
369 |
result[i] = charIndices[i + start]; |
|
370 |
} |
|
371 |
} |
|
372 |
return result; |
|
373 |
} |
|
374 |
||
375 |
// !!! not cached, assume TextLayout will cache if necessary |
|
376 |
// !!! reexamine for per-glyph-transforms |
|
377 |
// !!! revisit for text-on-a-path, vertical |
|
378 |
public Rectangle2D getLogicalBounds() { |
|
379 |
setFRCTX(); |
|
380 |
initPositions(); |
|
381 |
||
382 |
LineMetrics lm = font.getLineMetrics("", frc); |
|
383 |
||
384 |
float minX, minY, maxX, maxY; |
|
385 |
// horiz only for now... |
|
386 |
minX = 0; |
|
387 |
minY = -lm.getAscent(); |
|
388 |
maxX = 0; |
|
389 |
maxY = lm.getDescent() + lm.getLeading(); |
|
390 |
if (glyphs.length > 0) { |
|
391 |
maxX = positions[positions.length - 2]; |
|
392 |
} |
|
393 |
||
394 |
return new Rectangle2D.Float(minX, minY, maxX - minX, maxY - minY); |
|
395 |
} |
|
396 |
||
397 |
// !!! not cached, assume TextLayout will cache if necessary |
|
398 |
public Rectangle2D getVisualBounds() { |
|
4358
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
399 |
Rectangle2D result = null; |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
400 |
for (int i = 0; i < glyphs.length; ++i) { |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
401 |
Rectangle2D glyphVB = getGlyphVisualBounds(i).getBounds2D(); |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
402 |
if (!glyphVB.isEmpty()) { |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
403 |
if (result == null) { |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
404 |
result = glyphVB; |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
405 |
} else { |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
406 |
Rectangle2D.union(result, glyphVB, result); |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
407 |
} |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
408 |
} |
2 | 409 |
} |
4358
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
410 |
if (result == null) { |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
411 |
result = new Rectangle2D.Float(0, 0, 0, 0); |
2 | 412 |
} |
413 |
return result; |
|
414 |
} |
|
415 |
||
416 |
// !!! not cached, assume TextLayout will cache if necessary |
|
417 |
// !!! fontStrike needs a method for this |
|
418 |
public Rectangle getPixelBounds(FontRenderContext renderFRC, float x, float y) { |
|
419 |
return getGlyphsPixelBounds(renderFRC, x, y, 0, glyphs.length); |
|
420 |
} |
|
421 |
||
422 |
public Shape getOutline() { |
|
423 |
return getGlyphsOutline(0, glyphs.length, 0, 0); |
|
424 |
} |
|
425 |
||
426 |
public Shape getOutline(float x, float y) { |
|
427 |
return getGlyphsOutline(0, glyphs.length, x, y); |
|
428 |
} |
|
429 |
||
430 |
// relative to gv origin |
|
431 |
public Shape getGlyphOutline(int ix) { |
|
432 |
return getGlyphsOutline(ix, 1, 0, 0); |
|
433 |
} |
|
434 |
||
435 |
// relative to gv origin offset by x, y |
|
436 |
public Shape getGlyphOutline(int ix, float x, float y) { |
|
437 |
return getGlyphsOutline(ix, 1, x, y); |
|
438 |
} |
|
439 |
||
440 |
public Point2D getGlyphPosition(int ix) { |
|
441 |
initPositions(); |
|
442 |
||
443 |
ix *= 2; |
|
444 |
return new Point2D.Float(positions[ix], positions[ix + 1]); |
|
445 |
} |
|
446 |
||
447 |
public void setGlyphPosition(int ix, Point2D pos) { |
|
448 |
initPositions(); |
|
449 |
||
450 |
int ix2 = ix << 1; |
|
451 |
positions[ix2] = (float)pos.getX(); |
|
452 |
positions[ix2 + 1] = (float)pos.getY(); |
|
453 |
||
454 |
clearCaches(ix); |
|
455 |
addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); |
|
456 |
} |
|
457 |
||
458 |
public AffineTransform getGlyphTransform(int ix) { |
|
459 |
if (ix < 0 || ix >= glyphs.length) { |
|
460 |
throw new IndexOutOfBoundsException("ix = " + ix); |
|
461 |
} |
|
462 |
if (gti != null) { |
|
463 |
return gti.getGlyphTransform(ix); |
|
464 |
} |
|
465 |
return null; // spec'd as returning null |
|
466 |
} |
|
467 |
||
468 |
public void setGlyphTransform(int ix, AffineTransform newTX) { |
|
469 |
if (ix < 0 || ix >= glyphs.length) { |
|
470 |
throw new IndexOutOfBoundsException("ix = " + ix); |
|
471 |
} |
|
472 |
||
473 |
if (gti == null) { |
|
474 |
if (newTX == null || newTX.isIdentity()) { |
|
475 |
return; |
|
476 |
} |
|
477 |
gti = new GlyphTransformInfo(this); |
|
478 |
} |
|
479 |
gti.setGlyphTransform(ix, newTX); // sets flags |
|
480 |
if (gti.transformCount() == 0) { |
|
481 |
gti = null; |
|
482 |
} |
|
483 |
} |
|
484 |
||
485 |
public int getLayoutFlags() { |
|
486 |
if (flags == UNINITIALIZED_FLAGS) { |
|
487 |
flags = 0; |
|
488 |
||
489 |
if (charIndices != null && glyphs.length > 1) { |
|
490 |
boolean ltr = true; |
|
491 |
boolean rtl = true; |
|
492 |
||
493 |
int rtlix = charIndices.length; // rtl index |
|
494 |
for (int i = 0; i < charIndices.length && (ltr || rtl); ++i) { |
|
495 |
int cx = charIndices[i]; |
|
496 |
||
497 |
ltr = ltr && (cx == i); |
|
498 |
rtl = rtl && (cx == --rtlix); |
|
499 |
} |
|
500 |
||
501 |
if (rtl) flags |= FLAG_RUN_RTL; |
|
502 |
if (!rtl && !ltr) flags |= FLAG_COMPLEX_GLYPHS; |
|
503 |
} |
|
504 |
} |
|
505 |
||
506 |
return flags; |
|
507 |
} |
|
508 |
||
509 |
public float[] getGlyphPositions(int start, int count, float[] result) { |
|
510 |
if (count < 0) { |
|
511 |
throw new IllegalArgumentException("count = " + count); |
|
512 |
} |
|
513 |
if (start < 0) { |
|
514 |
throw new IndexOutOfBoundsException("start = " + start); |
|
515 |
} |
|
516 |
if (start > glyphs.length + 1 - count) { // watch for overflow |
|
517 |
throw new IndexOutOfBoundsException("start + count = " + (start + count)); |
|
518 |
} |
|
519 |
||
520 |
return internalGetGlyphPositions(start, count, 0, result); |
|
521 |
} |
|
522 |
||
523 |
public Shape getGlyphLogicalBounds(int ix) { |
|
524 |
if (ix < 0 || ix >= glyphs.length) { |
|
525 |
throw new IndexOutOfBoundsException("ix = " + ix); |
|
526 |
} |
|
527 |
||
528 |
Shape[] lbcache; |
|
529 |
if (lbcacheRef == null || (lbcache = (Shape[])lbcacheRef.get()) == null) { |
|
530 |
lbcache = new Shape[glyphs.length]; |
|
531 |
lbcacheRef = new SoftReference(lbcache); |
|
532 |
} |
|
533 |
||
534 |
Shape result = lbcache[ix]; |
|
535 |
if (result == null) { |
|
536 |
setFRCTX(); |
|
537 |
initPositions(); |
|
538 |
||
539 |
// !!! ought to return a rectangle2d for simple cases, though the following works for all |
|
540 |
||
541 |
// get the position, the tx offset, and the x,y advance and x,y adl. The |
|
542 |
// shape is the box formed by adv (width) and adl (height) offset by |
|
543 |
// the position plus the tx offset minus the ascent. |
|
544 |
||
545 |
ADL adl = new ADL(); |
|
546 |
GlyphStrike gs = getGlyphStrike(ix); |
|
547 |
gs.getADL(adl); |
|
548 |
||
549 |
Point2D.Float adv = gs.strike.getGlyphMetrics(glyphs[ix]); |
|
550 |
||
551 |
float wx = adv.x; |
|
552 |
float wy = adv.y; |
|
553 |
float hx = adl.descentX + adl.leadingX + adl.ascentX; |
|
554 |
float hy = adl.descentY + adl.leadingY + adl.ascentY; |
|
555 |
float x = positions[ix*2] + gs.dx - adl.ascentX; |
|
556 |
float y = positions[ix*2+1] + gs.dy - adl.ascentY; |
|
557 |
||
558 |
GeneralPath gp = new GeneralPath(); |
|
559 |
gp.moveTo(x, y); |
|
560 |
gp.lineTo(x + wx, y + wy); |
|
561 |
gp.lineTo(x + wx + hx, y + wy + hy); |
|
562 |
gp.lineTo(x + hx, y + hy); |
|
563 |
gp.closePath(); |
|
564 |
||
565 |
result = new DelegatingShape(gp); |
|
566 |
lbcache[ix] = result; |
|
567 |
} |
|
568 |
||
569 |
return result; |
|
570 |
} |
|
571 |
private SoftReference lbcacheRef; |
|
572 |
||
573 |
public Shape getGlyphVisualBounds(int ix) { |
|
574 |
if (ix < 0 || ix >= glyphs.length) { |
|
575 |
throw new IndexOutOfBoundsException("ix = " + ix); |
|
576 |
} |
|
577 |
||
578 |
Shape[] vbcache; |
|
579 |
if (vbcacheRef == null || (vbcache = (Shape[])vbcacheRef.get()) == null) { |
|
580 |
vbcache = new Shape[glyphs.length]; |
|
581 |
vbcacheRef = new SoftReference(vbcache); |
|
582 |
} |
|
583 |
||
584 |
Shape result = vbcache[ix]; |
|
585 |
if (result == null) { |
|
586 |
result = new DelegatingShape(getGlyphOutlineBounds(ix)); |
|
587 |
vbcache[ix] = result; |
|
588 |
} |
|
589 |
||
590 |
return result; |
|
591 |
} |
|
592 |
private SoftReference vbcacheRef; |
|
593 |
||
594 |
public Rectangle getGlyphPixelBounds(int index, FontRenderContext renderFRC, float x, float y) { |
|
595 |
return getGlyphsPixelBounds(renderFRC, x, y, index, 1); |
|
596 |
} |
|
597 |
||
598 |
public GlyphMetrics getGlyphMetrics(int ix) { |
|
599 |
if (ix < 0 || ix >= glyphs.length) { |
|
600 |
throw new IndexOutOfBoundsException("ix = " + ix); |
|
601 |
} |
|
602 |
||
603 |
Rectangle2D vb = getGlyphVisualBounds(ix).getBounds2D(); |
|
604 |
Point2D pt = getGlyphPosition(ix); |
|
605 |
vb.setRect(vb.getMinX() - pt.getX(), |
|
606 |
vb.getMinY() - pt.getY(), |
|
607 |
vb.getWidth(), |
|
608 |
vb.getHeight()); |
|
609 |
Point2D.Float adv = |
|
610 |
getGlyphStrike(ix).strike.getGlyphMetrics(glyphs[ix]); |
|
611 |
GlyphMetrics gm = new GlyphMetrics(true, adv.x, adv.y, |
|
612 |
vb, |
|
613 |
GlyphMetrics.STANDARD); |
|
614 |
return gm; |
|
615 |
} |
|
616 |
||
617 |
public GlyphJustificationInfo getGlyphJustificationInfo(int ix) { |
|
618 |
if (ix < 0 || ix >= glyphs.length) { |
|
619 |
throw new IndexOutOfBoundsException("ix = " + ix); |
|
620 |
} |
|
621 |
||
622 |
// currently we don't have enough information to do this right. should |
|
623 |
// get info from the font and use real OT/GX justification. Right now |
|
624 |
// sun/font/ExtendedTextSourceLabel assigns one of three infos |
|
625 |
// based on whether the char is kanji, space, or other. |
|
626 |
||
627 |
return null; |
|
628 |
} |
|
629 |
||
630 |
public boolean equals(GlyphVector rhs) { |
|
631 |
if (this == rhs) { |
|
632 |
return true; |
|
633 |
} |
|
634 |
if (rhs == null) { |
|
635 |
return false; |
|
636 |
} |
|
637 |
||
638 |
try { |
|
639 |
StandardGlyphVector other = (StandardGlyphVector)rhs; |
|
640 |
||
641 |
if (glyphs.length != other.glyphs.length) { |
|
642 |
return false; |
|
643 |
} |
|
644 |
||
645 |
for (int i = 0; i < glyphs.length; ++i) { |
|
646 |
if (glyphs[i] != other.glyphs[i]) { |
|
647 |
return false; |
|
648 |
} |
|
649 |
} |
|
650 |
||
651 |
if (!font.equals(other.font)) { |
|
652 |
return false; |
|
653 |
} |
|
654 |
||
655 |
if (!frc.equals(other.frc)) { |
|
656 |
return false; |
|
657 |
} |
|
658 |
||
659 |
if ((other.positions == null) != (positions == null)) { |
|
660 |
if (positions == null) { |
|
661 |
initPositions(); |
|
662 |
} else { |
|
663 |
other.initPositions(); |
|
664 |
} |
|
665 |
} |
|
666 |
||
667 |
if (positions != null) { |
|
668 |
for (int i = 0; i < positions.length; ++i) { |
|
669 |
if (positions[i] != other.positions[i]) { |
|
670 |
return false; |
|
671 |
} |
|
672 |
} |
|
673 |
} |
|
674 |
||
675 |
if (gti == null) { |
|
676 |
return other.gti == null; |
|
677 |
} else { |
|
678 |
return gti.equals(other.gti); |
|
679 |
} |
|
680 |
} |
|
681 |
catch (ClassCastException e) { |
|
682 |
// assume they are different simply by virtue of the class difference |
|
683 |
||
684 |
return false; |
|
685 |
} |
|
686 |
} |
|
687 |
||
688 |
/** |
|
689 |
* As a concrete subclass of Object that implements equality, this must |
|
690 |
* implement hashCode. |
|
691 |
*/ |
|
692 |
public int hashCode() { |
|
693 |
return font.hashCode() ^ glyphs.length; |
|
694 |
} |
|
695 |
||
696 |
/** |
|
697 |
* Since we implement equality comparisons for GlyphVector, we implement |
|
698 |
* the inherited Object.equals(Object) as well. GlyphVector should do |
|
699 |
* this, and define two glyphvectors as not equal if the classes differ. |
|
700 |
*/ |
|
701 |
public boolean equals(Object rhs) { |
|
702 |
try { |
|
703 |
return equals((GlyphVector)rhs); |
|
704 |
} |
|
705 |
catch (ClassCastException e) { |
|
706 |
return false; |
|
707 |
} |
|
708 |
} |
|
709 |
||
710 |
/** |
|
711 |
* Sometimes I wish java had covariant return types... |
|
712 |
*/ |
|
713 |
public StandardGlyphVector copy() { |
|
714 |
return (StandardGlyphVector)clone(); |
|
715 |
} |
|
716 |
||
717 |
/** |
|
718 |
* As a concrete subclass of GlyphVector, this must implement clone. |
|
719 |
*/ |
|
720 |
public Object clone() { |
|
721 |
// positions, gti are mutable so we have to clone them |
|
722 |
// font2d can be shared |
|
723 |
// fsref is a cache and can be shared |
|
724 |
try { |
|
725 |
StandardGlyphVector result = (StandardGlyphVector)super.clone(); |
|
726 |
||
727 |
result.clearCaches(); |
|
728 |
||
729 |
if (positions != null) { |
|
730 |
result.positions = (float[])positions.clone(); |
|
731 |
} |
|
732 |
||
733 |
if (gti != null) { |
|
734 |
result.gti = new GlyphTransformInfo(result, gti); |
|
735 |
} |
|
736 |
||
737 |
return result; |
|
738 |
} |
|
739 |
catch (CloneNotSupportedException e) { |
|
740 |
} |
|
741 |
||
742 |
return this; |
|
743 |
} |
|
744 |
||
745 |
////////////////////// |
|
746 |
// StandardGlyphVector new public methods |
|
747 |
///////////////////// |
|
748 |
||
749 |
/* |
|
750 |
* Set a multiple glyph positions at one time. GlyphVector only |
|
751 |
* provides API to set a single glyph at a time. |
|
752 |
*/ |
|
753 |
public void setGlyphPositions(float[] srcPositions, int srcStart, |
|
754 |
int start, int count) { |
|
755 |
if (count < 0) { |
|
756 |
throw new IllegalArgumentException("count = " + count); |
|
757 |
} |
|
758 |
||
759 |
initPositions(); |
|
760 |
for (int i = start * 2, e = i + count * 2, p = srcStart; i < e; ++i, ++p) { |
|
761 |
positions[i] = srcPositions[p]; |
|
762 |
} |
|
763 |
||
764 |
clearCaches(); |
|
765 |
addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); |
|
766 |
} |
|
767 |
||
768 |
/** |
|
769 |
* Set all the glyph positions, including the 'after last glyph' position. |
|
770 |
* The srcPositions array must be of length (numGlyphs + 1) * 2. |
|
771 |
*/ |
|
772 |
public void setGlyphPositions(float[] srcPositions) { |
|
773 |
int requiredLength = glyphs.length * 2 + 2; |
|
774 |
if (srcPositions.length != requiredLength) { |
|
775 |
throw new IllegalArgumentException("srcPositions.length != " + requiredLength); |
|
776 |
} |
|
777 |
||
778 |
positions = (float[])srcPositions.clone(); |
|
779 |
||
780 |
clearCaches(); |
|
781 |
addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); |
|
782 |
} |
|
783 |
||
784 |
/** |
|
785 |
* This is a convenience overload that gets all the glyph positions, which |
|
786 |
* is what you usually want to do if you're getting more than one. |
|
787 |
* !!! should I bother taking result parameter? |
|
788 |
*/ |
|
789 |
public float[] getGlyphPositions(float[] result) { |
|
790 |
return internalGetGlyphPositions(0, glyphs.length + 1, 0, result); |
|
791 |
} |
|
792 |
||
793 |
/** |
|
794 |
* Get transform information for the requested range of glyphs. |
|
795 |
* If no glyphs have a transform, return null. |
|
796 |
* If a glyph has no transform (or is the identity transform) its entry in the result array will be null. |
|
797 |
* If the passed-in result is null an array will be allocated for the caller. |
|
798 |
* Each transform instance in the result array will unique, and independent of the GlyphVector's transform. |
|
799 |
*/ |
|
800 |
public AffineTransform[] getGlyphTransforms(int start, int count, AffineTransform[] result) { |
|
801 |
if (start < 0 || count < 0 || start + count > glyphs.length) { |
|
802 |
throw new IllegalArgumentException("start: " + start + " count: " + count); |
|
803 |
} |
|
804 |
||
805 |
if (gti == null) { |
|
806 |
return null; |
|
807 |
} |
|
808 |
||
809 |
if (result == null) { |
|
810 |
result = new AffineTransform[count]; |
|
811 |
} |
|
812 |
||
813 |
for (int i = 0; i < count; ++i, ++start) { |
|
814 |
result[i] = gti.getGlyphTransform(start); |
|
815 |
} |
|
816 |
||
817 |
return result; |
|
818 |
} |
|
819 |
||
820 |
/** |
|
821 |
* Convenience overload for getGlyphTransforms(int, int, AffineTransform[], int); |
|
822 |
*/ |
|
823 |
public AffineTransform[] getGlyphTransforms() { |
|
824 |
return getGlyphTransforms(0, glyphs.length, null); |
|
825 |
} |
|
826 |
||
827 |
/** |
|
828 |
* Set a number of glyph transforms. |
|
829 |
* Original transforms are unchanged. The array may contain nulls, and also may |
|
830 |
* contain multiple references to the same transform instance. |
|
831 |
*/ |
|
832 |
public void setGlyphTransforms(AffineTransform[] srcTransforms, int srcStart, int start, int count) { |
|
833 |
for (int i = start, e = start + count; i < e; ++i) { |
|
834 |
setGlyphTransform(i, srcTransforms[srcStart + i]); |
|
835 |
} |
|
836 |
} |
|
837 |
||
838 |
/** |
|
839 |
* Convenience overload of setGlyphTransforms(AffineTransform[], int, int, int). |
|
840 |
*/ |
|
841 |
public void setGlyphTransforms(AffineTransform[] srcTransforms) { |
|
842 |
setGlyphTransforms(srcTransforms, 0, 0, glyphs.length); |
|
843 |
} |
|
844 |
||
845 |
/** |
|
846 |
* For each glyph return posx, posy, advx, advy, visx, visy, visw, vish. |
|
847 |
*/ |
|
848 |
public float[] getGlyphInfo() { |
|
849 |
setFRCTX(); |
|
850 |
initPositions(); |
|
851 |
float[] result = new float[glyphs.length * 8]; |
|
852 |
for (int i = 0, n = 0; i < glyphs.length; ++i, n += 8) { |
|
853 |
float x = positions[i*2]; |
|
854 |
float y = positions[i*2+1]; |
|
855 |
result[n] = x; |
|
856 |
result[n+1] = y; |
|
857 |
||
858 |
int glyphID = glyphs[i]; |
|
859 |
GlyphStrike s = getGlyphStrike(i); |
|
860 |
Point2D.Float adv = s.strike.getGlyphMetrics(glyphID); |
|
861 |
result[n+2] = adv.x; |
|
862 |
result[n+3] = adv.y; |
|
863 |
||
864 |
Rectangle2D vb = getGlyphVisualBounds(i).getBounds2D(); |
|
865 |
result[n+4] = (float)(vb.getMinX()); |
|
866 |
result[n+5] = (float)(vb.getMinY()); |
|
867 |
result[n+6] = (float)(vb.getWidth()); |
|
868 |
result[n+7] = (float)(vb.getHeight()); |
|
869 |
} |
|
870 |
return result; |
|
871 |
} |
|
872 |
||
873 |
/** |
|
874 |
* !!! not used currently, but might be by getPixelbounds? |
|
875 |
*/ |
|
876 |
public void pixellate(FontRenderContext renderFRC, Point2D loc, Point pxResult) { |
|
877 |
if (renderFRC == null) { |
|
878 |
renderFRC = frc; |
|
879 |
} |
|
880 |
||
881 |
// it is a total pain that you have to copy the transform. |
|
882 |
||
883 |
AffineTransform at = renderFRC.getTransform(); |
|
884 |
at.transform(loc, loc); |
|
885 |
pxResult.x = (int)loc.getX(); // but must not behave oddly around zero |
|
886 |
pxResult.y = (int)loc.getY(); |
|
887 |
loc.setLocation(pxResult.x, pxResult.y); |
|
888 |
try { |
|
889 |
at.inverseTransform(loc, loc); |
|
890 |
} |
|
891 |
catch (NoninvertibleTransformException e) { |
|
892 |
throw new IllegalArgumentException("must be able to invert frc transform"); |
|
893 |
} |
|
894 |
} |
|
895 |
||
896 |
////////////////////// |
|
897 |
// StandardGlyphVector package private methods |
|
898 |
///////////////////// |
|
899 |
||
900 |
// used by glyphlist to determine if it needs to allocate/size positions array |
|
901 |
// gti always uses positions because the gtx might have translation. We also |
|
902 |
// need positions if the rendering dtx is different from the frctx. |
|
903 |
||
904 |
boolean needsPositions(double[] devTX) { |
|
905 |
return gti != null || |
|
906 |
(getLayoutFlags() & FLAG_HAS_POSITION_ADJUSTMENTS) != 0 || |
|
907 |
!matchTX(devTX, frctx); |
|
908 |
} |
|
909 |
||
910 |
// used by glyphList to get strong refs to font strikes for duration of rendering call |
|
911 |
// if devTX matches current devTX, we're ready to go |
|
912 |
// if we don't have multiple transforms, we're already ok |
|
913 |
||
914 |
// !!! I'm not sure fontInfo works so well for glyphvector, since we have to be able to handle |
|
915 |
// the multiple-strikes case |
|
916 |
||
917 |
/* |
|
918 |
* GlyphList calls this to set up its images data. First it calls needsPositions, |
|
919 |
* passing the devTX, to see if it should provide us a positions array to fill. |
|
920 |
* It only doesn't need them if we're a simple glyph vector whose frctx matches the |
|
921 |
* devtx. |
|
922 |
* Then it calls setupGlyphImages. If we need positions, we make sure we have our |
|
923 |
* default positions based on the frctx first. Then we set the devTX, and use |
|
924 |
* strikes based on it to generate the images. Finally, we fill in the positions |
|
925 |
* array. |
|
926 |
* If we have transforms, we delegate to gti. It depends on our having first |
|
927 |
* initialized the positions and devTX. |
|
928 |
*/ |
|
929 |
Object setupGlyphImages(long[] images, float[] positions, double[] devTX) { |
|
930 |
initPositions(); // FIRST ensure we have positions based on our frctx |
|
931 |
setRenderTransform(devTX); // THEN make sure we are using the desired devTX |
|
932 |
||
933 |
if (gti != null) { |
|
934 |
return gti.setupGlyphImages(images, positions, dtx); |
|
935 |
} |
|
936 |
||
937 |
GlyphStrike gs = getDefaultStrike(); |
|
938 |
gs.strike.getGlyphImagePtrs(glyphs, images, glyphs.length); |
|
939 |
||
940 |
if (positions != null) { |
|
941 |
if (dtx.isIdentity()) { |
|
942 |
System.arraycopy(this.positions, 0, positions, 0, glyphs.length * 2); |
|
943 |
} else { |
|
944 |
dtx.transform(this.positions, 0, positions, 0, glyphs.length); |
|
945 |
} |
|
946 |
} |
|
947 |
||
948 |
return gs; |
|
949 |
} |
|
950 |
||
951 |
////////////////////// |
|
952 |
// StandardGlyphVector private methods |
|
953 |
///////////////////// |
|
954 |
||
955 |
// We keep translation in our frctx since getPixelBounds uses it. But |
|
956 |
// GlyphList pulls out the translation and applies it separately, so |
|
957 |
// we strip it out when we set the dtx. Basically nothing uses the |
|
958 |
// translation except getPixelBounds. |
|
959 |
||
960 |
// called by needsPositions, setRenderTransform |
|
961 |
private static boolean matchTX(double[] lhs, AffineTransform rhs) { |
|
962 |
return |
|
963 |
lhs[0] == rhs.getScaleX() && |
|
964 |
lhs[1] == rhs.getShearY() && |
|
965 |
lhs[2] == rhs.getShearX() && |
|
966 |
lhs[3] == rhs.getScaleY(); |
|
967 |
} |
|
968 |
||
969 |
// returns new tx if old one has translation, otherwise returns old one |
|
970 |
private static AffineTransform getNonTranslateTX(AffineTransform tx) { |
|
971 |
if (tx.getTranslateX() != 0 || tx.getTranslateY() != 0) { |
|
972 |
tx = new AffineTransform(tx.getScaleX(), tx.getShearY(), |
|
973 |
tx.getShearX(), tx.getScaleY(), |
|
974 |
0, 0); |
|
975 |
} |
|
976 |
return tx; |
|
977 |
} |
|
978 |
||
979 |
private static boolean equalNonTranslateTX(AffineTransform lhs, AffineTransform rhs) { |
|
980 |
return lhs.getScaleX() == rhs.getScaleX() && |
|
981 |
lhs.getShearY() == rhs.getShearY() && |
|
982 |
lhs.getShearX() == rhs.getShearX() && |
|
983 |
lhs.getScaleY() == rhs.getScaleY(); |
|
984 |
} |
|
985 |
||
986 |
// called by setupGlyphImages (after needsPositions, so redundant match check?) |
|
987 |
private void setRenderTransform(double[] devTX) { |
|
988 |
assert(devTX.length == 4); |
|
989 |
if (!matchTX(devTX, dtx)) { |
|
990 |
resetDTX(new AffineTransform(devTX)); // no translation since devTX len == 4. |
|
991 |
} |
|
992 |
} |
|
993 |
||
994 |
// called by getGlyphsPixelBounds |
|
995 |
private final void setDTX(AffineTransform tx) { |
|
996 |
if (!equalNonTranslateTX(dtx, tx)) { |
|
997 |
resetDTX(getNonTranslateTX(tx)); |
|
998 |
} |
|
999 |
} |
|
1000 |
||
1001 |
// called by most functions |
|
1002 |
private final void setFRCTX() { |
|
1003 |
if (!equalNonTranslateTX(frctx, dtx)) { |
|
1004 |
resetDTX(getNonTranslateTX(frctx)); |
|
1005 |
} |
|
1006 |
} |
|
1007 |
||
1008 |
/** |
|
1009 |
* Change the dtx for the strike refs we use. Keeps a reference to the at. At |
|
1010 |
* must not contain translation. |
|
1011 |
* Called by setRenderTransform, setDTX, initFontData. |
|
1012 |
*/ |
|
1013 |
private final void resetDTX(AffineTransform at) { |
|
1014 |
fsref = null; |
|
1015 |
dtx = at; |
|
1016 |
invdtx = null; |
|
1017 |
if (!dtx.isIdentity()) { |
|
1018 |
try { |
|
1019 |
invdtx = dtx.createInverse(); |
|
1020 |
} |
|
1021 |
catch (NoninvertibleTransformException e) { |
|
1022 |
// we needn't care for rendering |
|
1023 |
} |
|
1024 |
} |
|
1025 |
if (gti != null) { |
|
1026 |
gti.strikesRef = null; |
|
1027 |
} |
|
1028 |
} |
|
1029 |
||
1030 |
/** |
|
1031 |
* Utility used by getStandardGV. |
|
1032 |
* Constructs a StandardGlyphVector from a generic glyph vector. |
|
1033 |
* Do not call this from new contexts without considering the comment |
|
1034 |
* about "userGlyphs". |
|
1035 |
*/ |
|
1036 |
private StandardGlyphVector(GlyphVector gv, FontRenderContext frc) { |
|
1037 |
this.font = gv.getFont(); |
|
1038 |
this.frc = frc; |
|
1039 |
initFontData(); |
|
1040 |
||
1041 |
int nGlyphs = gv.getNumGlyphs(); |
|
1042 |
this.userGlyphs = gv.getGlyphCodes(0, nGlyphs, null); |
|
1043 |
if (gv instanceof StandardGlyphVector) { |
|
1044 |
/* userGlyphs will be OK because this is a private constructor |
|
1045 |
* and the returned instance is used only for rendering. |
|
1046 |
* It's not constructable by user code, nor returned to the |
|
1047 |
* application. So we know "userGlyphs" are valid as having |
|
1048 |
* been either already validated or are the result of layout. |
|
1049 |
*/ |
|
1050 |
this.glyphs = userGlyphs; |
|
1051 |
} else { |
|
1052 |
this.glyphs = getValidatedGlyphs(this.userGlyphs); |
|
1053 |
} |
|
1054 |
this.flags = gv.getLayoutFlags() & FLAG_MASK; |
|
1055 |
||
1056 |
if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) { |
|
1057 |
this.positions = gv.getGlyphPositions(0, nGlyphs + 1, null); |
|
1058 |
} |
|
1059 |
||
1060 |
if ((flags & FLAG_COMPLEX_GLYPHS) != 0) { |
|
1061 |
this.charIndices = gv.getGlyphCharIndices(0, nGlyphs, null); |
|
1062 |
} |
|
1063 |
||
1064 |
if ((flags & FLAG_HAS_TRANSFORMS) != 0) { |
|
1065 |
AffineTransform[] txs = new AffineTransform[nGlyphs]; // worst case |
|
1066 |
for (int i = 0; i < nGlyphs; ++i) { |
|
1067 |
txs[i] = gv.getGlyphTransform(i); // gv doesn't have getGlyphsTransforms |
|
1068 |
} |
|
1069 |
||
1070 |
setGlyphTransforms(txs); |
|
1071 |
} |
|
1072 |
} |
|
1073 |
||
1074 |
/* Before asking the Font we see if the glyph code is |
|
1075 |
* FFFE or FFFF which are special values that we should be internally |
|
1076 |
* ready to handle as meaning invisible glyphs. The Font would report |
|
1077 |
* those as the missing glyph. |
|
1078 |
*/ |
|
1079 |
int[] getValidatedGlyphs(int[] oglyphs) { |
|
1080 |
int len = oglyphs.length; |
|
1081 |
int[] vglyphs = new int[len]; |
|
1082 |
for (int i=0; i<len; i++) { |
|
1083 |
if (oglyphs[i] == 0xFFFE || oglyphs[i] == 0xFFFF) { |
|
1084 |
vglyphs[i] = oglyphs[i]; |
|
1085 |
} else { |
|
1086 |
vglyphs[i] = font2D.getValidatedGlyphCode(oglyphs[i]); |
|
1087 |
} |
|
1088 |
} |
|
1089 |
return vglyphs; |
|
1090 |
} |
|
1091 |
||
1092 |
// utility used by constructors |
|
1093 |
private void init(Font font, char[] text, int start, int count, |
|
1094 |
FontRenderContext frc, int flags) { |
|
1095 |
||
1096 |
if (start < 0 || count < 0 || start + count > text.length) { |
|
1097 |
throw new ArrayIndexOutOfBoundsException("start or count out of bounds"); |
|
1098 |
} |
|
1099 |
||
1100 |
this.font = font; |
|
1101 |
this.frc = frc; |
|
1102 |
this.flags = flags; |
|
1103 |
||
1104 |
if (getTracking(font) != 0) { |
|
1105 |
addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); |
|
1106 |
} |
|
1107 |
||
1108 |
// !!! change mapper interface? |
|
1109 |
if (start != 0) { |
|
1110 |
char[] temp = new char[count]; |
|
1111 |
System.arraycopy(text, start, temp, 0, count); |
|
1112 |
text = temp; |
|
1113 |
} |
|
1114 |
||
1115 |
initFontData(); // sets up font2D |
|
1116 |
||
1117 |
// !!! no layout for now, should add checks |
|
1118 |
// !!! need to support creating a StandardGlyphVector from a TextMeasurer's info... |
|
1119 |
glyphs = new int[count]; // hmmm |
|
1120 |
/* Glyphs obtained here are already validated by the font */ |
|
1121 |
userGlyphs = glyphs; |
|
1122 |
font2D.getMapper().charsToGlyphs(count, text, glyphs); |
|
1123 |
} |
|
1124 |
||
1125 |
private void initFontData() { |
|
3928 | 1126 |
font2D = FontUtilities.getFont2D(font); |
2 | 1127 |
float s = font.getSize2D(); |
1128 |
if (font.isTransformed()) { |
|
1129 |
ftx = font.getTransform(); |
|
1130 |
if (ftx.getTranslateX() != 0 || ftx.getTranslateY() != 0) { |
|
1131 |
addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); |
|
1132 |
} |
|
1133 |
ftx.setTransform(ftx.getScaleX(), ftx.getShearY(), ftx.getShearX(), ftx.getScaleY(), 0, 0); |
|
1134 |
ftx.scale(s, s); |
|
1135 |
} else { |
|
1136 |
ftx = AffineTransform.getScaleInstance(s, s); |
|
1137 |
} |
|
1138 |
||
1139 |
frctx = frc.getTransform(); |
|
1140 |
resetDTX(getNonTranslateTX(frctx)); |
|
1141 |
} |
|
1142 |
||
1143 |
/** |
|
1144 |
* Copy glyph position data into a result array starting at the indicated |
|
1145 |
* offset in the array. If the passed-in result array is null, a new |
|
1146 |
* array will be allocated and returned. |
|
1147 |
* |
|
1148 |
* This is an internal method and does no extra argument checking. |
|
1149 |
* |
|
1150 |
* @param start the index of the first glyph to get |
|
1151 |
* @param count the number of glyphs to get |
|
1152 |
* @param offset the offset into result at which to put the data |
|
1153 |
* @param result an array to hold the x,y positions |
|
1154 |
* @return the modified position array |
|
1155 |
*/ |
|
1156 |
private float[] internalGetGlyphPositions(int start, int count, int offset, float[] result) { |
|
1157 |
if (result == null) { |
|
1158 |
result = new float[offset + count * 2]; |
|
1159 |
} |
|
1160 |
||
1161 |
initPositions(); |
|
1162 |
||
1163 |
// System.arraycopy is slow for stuff like this |
|
1164 |
for (int i = offset, e = offset + count * 2, p = start * 2; i < e; ++i, ++p) { |
|
1165 |
result[i] = positions[p]; |
|
1166 |
} |
|
1167 |
||
1168 |
return result; |
|
1169 |
} |
|
1170 |
||
1171 |
private Rectangle2D getGlyphOutlineBounds(int ix) { |
|
1172 |
setFRCTX(); |
|
1173 |
initPositions(); |
|
1174 |
return getGlyphStrike(ix).getGlyphOutlineBounds(glyphs[ix], positions[ix*2], positions[ix*2+1]); |
|
1175 |
} |
|
1176 |
||
1177 |
/** |
|
1178 |
* Used by getOutline, getGlyphsOutline |
|
1179 |
*/ |
|
1180 |
private Shape getGlyphsOutline(int start, int count, float x, float y) { |
|
1181 |
setFRCTX(); |
|
1182 |
initPositions(); |
|
1183 |
||
1184 |
GeneralPath result = new GeneralPath(GeneralPath.WIND_NON_ZERO); |
|
1185 |
for (int i = start, e = start + count, n = start * 2; i < e; ++i, n += 2) { |
|
1186 |
float px = x + positions[n]; |
|
1187 |
float py = y + positions[n+1]; |
|
1188 |
||
1189 |
getGlyphStrike(i).appendGlyphOutline(glyphs[i], result, px, py); |
|
1190 |
} |
|
1191 |
||
1192 |
return result; |
|
1193 |
} |
|
1194 |
||
1195 |
private Rectangle getGlyphsPixelBounds(FontRenderContext frc, float x, float y, int start, int count) { |
|
1196 |
initPositions(); // FIRST ensure we have positions based on our frctx |
|
1197 |
||
1198 |
AffineTransform tx = null; |
|
1199 |
if (frc == null || frc.equals(this.frc)) { |
|
1200 |
tx = frctx; |
|
1201 |
} else { |
|
1202 |
tx = frc.getTransform(); |
|
1203 |
} |
|
1204 |
setDTX(tx); // need to get the right strikes, but we use tx itself to translate the points |
|
1205 |
||
1206 |
if (gti != null) { |
|
1207 |
return gti.getGlyphsPixelBounds(tx, x, y, start, count); |
|
1208 |
} |
|
1209 |
||
1210 |
FontStrike fs = getDefaultStrike().strike; |
|
1211 |
Rectangle result = null; |
|
1212 |
Rectangle r = new Rectangle(); |
|
1213 |
Point2D.Float pt = new Point.Float(); |
|
1214 |
int n = start * 2; |
|
1215 |
while (--count >= 0) { |
|
1216 |
pt.x = x + positions[n++]; |
|
1217 |
pt.y = y + positions[n++]; |
|
1218 |
tx.transform(pt, pt); |
|
1219 |
fs.getGlyphImageBounds(glyphs[start++], pt, r); |
|
1220 |
if (!r.isEmpty()) { |
|
1221 |
if (result == null) { |
|
1222 |
result = new Rectangle(r); |
|
1223 |
} else { |
|
1224 |
result.add(r); |
|
1225 |
} |
|
1226 |
} |
|
1227 |
} |
|
1228 |
return result != null ? result : r; |
|
1229 |
} |
|
1230 |
||
1231 |
private void clearCaches(int ix) { |
|
1232 |
if (lbcacheRef != null) { |
|
1233 |
Shape[] lbcache = (Shape[])lbcacheRef.get(); |
|
1234 |
if (lbcache != null) { |
|
1235 |
lbcache[ix] = null; |
|
1236 |
} |
|
1237 |
} |
|
1238 |
||
1239 |
if (vbcacheRef != null) { |
|
1240 |
Shape[] vbcache = (Shape[])vbcacheRef.get(); |
|
1241 |
if (vbcache != null) { |
|
1242 |
vbcache[ix] = null; |
|
1243 |
} |
|
1244 |
} |
|
1245 |
} |
|
1246 |
||
1247 |
private void clearCaches() { |
|
1248 |
lbcacheRef = null; |
|
1249 |
vbcacheRef = null; |
|
1250 |
} |
|
1251 |
||
1252 |
// internal use only for possible future extension |
|
1253 |
||
1254 |
/** |
|
1255 |
* A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses |
|
1256 |
* a vertical baseline. |
|
1257 |
*/ |
|
1258 |
public static final int FLAG_USES_VERTICAL_BASELINE = 128; |
|
1259 |
||
1260 |
/** |
|
1261 |
* A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses |
|
1262 |
* vertical glyph metrics. A <code>GlyphVector</code> can use vertical metrics on a |
|
1263 |
* horizontal line, or vice versa. |
|
1264 |
*/ |
|
1265 |
public static final int FLAG_USES_VERTICAL_METRICS = 256; |
|
1266 |
||
1267 |
/** |
|
1268 |
* A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses |
|
1269 |
* the 'alternate orientation.' Glyphs have a default orientation given a |
|
1270 |
* particular baseline and metrics orientation, this is the orientation appropriate |
|
1271 |
* for left-to-right text. For example, the letter 'A' can have four orientations, |
|
1272 |
* with the point at 12, 3, 6, or 9 'o clock. The following table shows where the |
|
1273 |
* point displays for different values of vertical baseline (vb), vertical |
|
1274 |
* metrics (vm) and alternate orientation (fo):<br> |
|
1275 |
* <blockquote> |
|
1276 |
* vb vm ao |
|
1277 |
* -- -- -- -- |
|
1278 |
* f f f 12 ^ horizontal metrics on horizontal lines |
|
1279 |
* f f t 6 v |
|
1280 |
* f t f 9 < vertical metrics on horizontal lines |
|
1281 |
* f t t 3 > |
|
1282 |
* t f f 3 > horizontal metrics on vertical lines |
|
1283 |
* t f t 9 < |
|
1284 |
* t t f 12 ^ vertical metrics on vertical lines |
|
1285 |
* t t t 6 v |
|
1286 |
* </blockquote> |
|
1287 |
*/ |
|
1288 |
public static final int FLAG_USES_ALTERNATE_ORIENTATION = 512; |
|
1289 |
||
1290 |
||
1291 |
/** |
|
1292 |
* Ensure that the positions array exists and holds position data. |
|
1293 |
* If the array is null, this allocates it and sets default positions. |
|
1294 |
*/ |
|
1295 |
private void initPositions() { |
|
1296 |
if (positions == null) { |
|
1297 |
setFRCTX(); |
|
1298 |
||
1299 |
positions = new float[glyphs.length * 2 + 2]; |
|
1300 |
||
1301 |
Point2D.Float trackPt = null; |
|
1302 |
float track = getTracking(font); |
|
1303 |
if (track != 0) { |
|
1304 |
track *= font.getSize2D(); |
|
1305 |
trackPt = new Point2D.Float(track, 0); // advance delta |
|
1306 |
} |
|
1307 |
||
1308 |
Point2D.Float pt = new Point2D.Float(0, 0); |
|
1309 |
if (font.isTransformed()) { |
|
1310 |
AffineTransform at = font.getTransform(); |
|
1311 |
at.transform(pt, pt); |
|
1312 |
positions[0] = pt.x; |
|
1313 |
positions[1] = pt.y; |
|
1314 |
||
1315 |
if (trackPt != null) { |
|
1316 |
at.deltaTransform(trackPt, trackPt); |
|
1317 |
} |
|
1318 |
} |
|
1319 |
for (int i = 0, n = 2; i < glyphs.length; ++i, n += 2) { |
|
1320 |
getGlyphStrike(i).addDefaultGlyphAdvance(glyphs[i], pt); |
|
1321 |
if (trackPt != null) { |
|
1322 |
pt.x += trackPt.x; |
|
1323 |
pt.y += trackPt.y; |
|
1324 |
} |
|
1325 |
positions[n] = pt.x; |
|
1326 |
positions[n+1] = pt.y; |
|
1327 |
} |
|
1328 |
} |
|
1329 |
} |
|
1330 |
||
1331 |
/** |
|
1332 |
* OR newFlags with existing flags. First computes existing flags if needed. |
|
1333 |
*/ |
|
1334 |
private void addFlags(int newflags) { |
|
1335 |
flags = getLayoutFlags() | newflags; |
|
1336 |
} |
|
1337 |
||
1338 |
/** |
|
1339 |
* AND the complement of clearedFlags with existing flags. First computes existing flags if needed. |
|
1340 |
*/ |
|
1341 |
private void clearFlags(int clearedFlags) { |
|
1342 |
flags = getLayoutFlags() & ~clearedFlags; |
|
1343 |
} |
|
1344 |
||
1345 |
// general utility methods |
|
1346 |
||
1347 |
// encapsulate the test to check whether we have per-glyph transforms |
|
1348 |
private GlyphStrike getGlyphStrike(int ix) { |
|
1349 |
if (gti == null) { |
|
1350 |
return getDefaultStrike(); |
|
1351 |
} else { |
|
1352 |
return gti.getStrike(ix); |
|
1353 |
} |
|
1354 |
} |
|
1355 |
||
1356 |
// encapsulate access to cached default glyph strike |
|
1357 |
private GlyphStrike getDefaultStrike() { |
|
1358 |
GlyphStrike gs = null; |
|
1359 |
if (fsref != null) { |
|
1360 |
gs = (GlyphStrike)fsref.get(); |
|
1361 |
} |
|
1362 |
if (gs == null) { |
|
1363 |
gs = GlyphStrike.create(this, dtx, null); |
|
1364 |
fsref = new SoftReference(gs); |
|
1365 |
} |
|
1366 |
return gs; |
|
1367 |
} |
|
1368 |
||
1369 |
||
1370 |
///////////////////// |
|
1371 |
// Internal utility classes |
|
1372 |
///////////////////// |
|
1373 |
||
1374 |
// !!! I have this as a separate class instead of just inside SGV, |
|
1375 |
// but I previously didn't bother. Now I'm trying this again. |
|
1376 |
// Probably still not worth it, but I'd like to keep sgv's small in the common case. |
|
1377 |
||
1378 |
static final class GlyphTransformInfo { |
|
1379 |
StandardGlyphVector sgv; // reference back to glyph vector - yuck |
|
1380 |
int[] indices; // index into unique strikes |
|
1381 |
double[] transforms; // six doubles per unique transform, because AT is a pain to manipulate |
|
1382 |
SoftReference strikesRef; // ref to unique strikes, one per transform |
|
1383 |
boolean haveAllStrikes; // true if the strike array has been filled by getStrikes(). |
|
1384 |
||
1385 |
// used when first setting a transform |
|
1386 |
GlyphTransformInfo(StandardGlyphVector sgv) { |
|
1387 |
this.sgv = sgv; |
|
1388 |
} |
|
1389 |
||
1390 |
// used when cloning a glyph vector, need to set back link |
|
1391 |
GlyphTransformInfo(StandardGlyphVector sgv, GlyphTransformInfo rhs) { |
|
1392 |
this.sgv = sgv; |
|
1393 |
||
1394 |
this.indices = rhs.indices == null ? null : (int[])rhs.indices.clone(); |
|
1395 |
this.transforms = rhs.transforms == null ? null : (double[])rhs.transforms.clone(); |
|
1396 |
this.strikesRef = null; // can't share cache, so rather than clone, we just null out |
|
1397 |
} |
|
1398 |
||
1399 |
// used in sgv equality |
|
1400 |
public boolean equals(GlyphTransformInfo rhs) { |
|
1401 |
if (rhs == null) { |
|
1402 |
return false; |
|
1403 |
} |
|
1404 |
if (rhs == this) { |
|
1405 |
return true; |
|
1406 |
} |
|
1407 |
if (this.indices.length != rhs.indices.length) { |
|
1408 |
return false; |
|
1409 |
} |
|
1410 |
if (this.transforms.length != rhs.transforms.length) { |
|
1411 |
return false; |
|
1412 |
} |
|
1413 |
||
1414 |
// slow since we end up processing the same transforms multiple |
|
1415 |
// times, but since transforms can be in any order, we either do |
|
1416 |
// this or create a mapping. Equality tests aren't common so |
|
1417 |
// leave it like this. |
|
1418 |
for (int i = 0; i < this.indices.length; ++i) { |
|
1419 |
int tix = this.indices[i]; |
|
1420 |
int rix = rhs.indices[i]; |
|
1421 |
if ((tix == 0) != (rix == 0)) { |
|
1422 |
return false; |
|
1423 |
} |
|
1424 |
if (tix != 0) { |
|
1425 |
tix *= 6; |
|
1426 |
rix *= 6; |
|
1427 |
for (int j = 6; j > 0; --j) { |
|
1428 |
if (this.indices[--tix] != rhs.indices[--rix]) { |
|
1429 |
return false; |
|
1430 |
} |
|
1431 |
} |
|
1432 |
} |
|
1433 |
} |
|
1434 |
return true; |
|
1435 |
} |
|
1436 |
||
1437 |
// implements sgv.setGlyphTransform |
|
1438 |
void setGlyphTransform(int glyphIndex, AffineTransform newTX) { |
|
1439 |
||
1440 |
// we store all the glyph transforms as a double array, and for each glyph there |
|
1441 |
// is an entry in the txIndices array indicating which transform to use. 0 means |
|
1442 |
// there's no transform, 1 means use the first transform (the 6 doubles at offset |
|
1443 |
// 0), 2 means use the second transform (the 6 doubles at offset 6), etc. |
|
1444 |
// |
|
1445 |
// Since this can be called multiple times, and since the number of transforms |
|
1446 |
// affects the time it takes to construct the glyphs, we try to keep the arrays as |
|
1447 |
// compact as possible, by removing transforms that are no longer used, and reusing |
|
1448 |
// transforms where we already have them. |
|
1449 |
||
1450 |
double[] temp = new double[6]; |
|
1451 |
boolean isIdentity = true; |
|
1452 |
if (newTX == null || newTX.isIdentity()) { |
|
1453 |
// Fill in temp |
|
1454 |
temp[0] = temp[3] = 1.0; |
|
1455 |
} |
|
1456 |
else { |
|
1457 |
isIdentity = false; |
|
1458 |
newTX.getMatrix(temp); |
|
1459 |
} |
|
1460 |
||
1461 |
if (indices == null) { |
|
1462 |
if (isIdentity) { // no change |
|
1463 |
return; |
|
1464 |
} |
|
1465 |
||
1466 |
indices = new int[sgv.glyphs.length]; |
|
1467 |
indices[glyphIndex] = 1; |
|
1468 |
transforms = temp; |
|
1469 |
} else { |
|
1470 |
boolean addSlot = false; // assume we're not growing |
|
1471 |
int newIndex = -1; |
|
1472 |
if (isIdentity) { |
|
1473 |
newIndex = 0; // might shrink |
|
1474 |
} else { |
|
1475 |
addSlot = true; // assume no match |
|
1476 |
int i; |
|
1477 |
loop: |
|
1478 |
for (i = 0; i < transforms.length; i += 6) { |
|
1479 |
for (int j = 0; j < 6; ++j) { |
|
1480 |
if (transforms[i + j] != temp[j]) { |
|
1481 |
continue loop; |
|
1482 |
} |
|
1483 |
} |
|
1484 |
addSlot = false; |
|
1485 |
break; |
|
1486 |
} |
|
1487 |
newIndex = i / 6 + 1; // if no match, end of list |
|
1488 |
} |
|
1489 |
||
1490 |
// if we're using the same transform, nothing to do |
|
1491 |
int oldIndex = indices[glyphIndex]; |
|
1492 |
if (newIndex != oldIndex) { |
|
1493 |
// see if we are removing last use of the old slot |
|
1494 |
boolean removeSlot = false; |
|
1495 |
if (oldIndex != 0) { |
|
1496 |
removeSlot = true; |
|
1497 |
for (int i = 0; i < indices.length; ++i) { |
|
1498 |
if (indices[i] == oldIndex && i != glyphIndex) { |
|
1499 |
removeSlot = false; |
|
1500 |
break; |
|
1501 |
} |
|
1502 |
} |
|
1503 |
} |
|
1504 |
||
1505 |
if (removeSlot && addSlot) { // reuse old slot with new transform |
|
1506 |
newIndex = oldIndex; |
|
1507 |
System.arraycopy(temp, 0, transforms, (newIndex - 1) * 6, 6); |
|
1508 |
} else if (removeSlot) { |
|
1509 |
if (transforms.length == 6) { // removing last one, so clear arrays |
|
1510 |
indices = null; |
|
1511 |
transforms = null; |
|
1512 |
||
1513 |
sgv.clearCaches(glyphIndex); |
|
1514 |
sgv.clearFlags(FLAG_HAS_TRANSFORMS); |
|
1515 |
strikesRef = null; |
|
1516 |
||
1517 |
return; |
|
1518 |
} |
|
1519 |
||
1520 |
double[] ttemp = new double[transforms.length - 6]; |
|
1521 |
System.arraycopy(transforms, 0, ttemp, 0, (oldIndex - 1) * 6); |
|
1522 |
System.arraycopy(transforms, oldIndex * 6, ttemp, (oldIndex - 1) * 6, |
|
1523 |
transforms.length - oldIndex * 6); |
|
1524 |
transforms = ttemp; |
|
1525 |
||
1526 |
// clean up indices |
|
1527 |
for (int i = 0; i < indices.length; ++i) { |
|
1528 |
if (indices[i] > oldIndex) { // ignore == oldIndex, it's going away |
|
1529 |
indices[i] -= 1; |
|
1530 |
} |
|
1531 |
} |
|
1532 |
if (newIndex > oldIndex) { // don't forget to decrement this too if we need to |
|
1533 |
--newIndex; |
|
1534 |
} |
|
1535 |
} else if (addSlot) { |
|
1536 |
double[] ttemp = new double[transforms.length + 6]; |
|
1537 |
System.arraycopy(transforms, 0, ttemp, 0, transforms.length); |
|
1538 |
System.arraycopy(temp, 0, ttemp, transforms.length, 6); |
|
1539 |
transforms = ttemp; |
|
1540 |
} |
|
1541 |
||
1542 |
indices[glyphIndex] = newIndex; |
|
1543 |
} |
|
1544 |
} |
|
1545 |
||
1546 |
sgv.clearCaches(glyphIndex); |
|
1547 |
sgv.addFlags(FLAG_HAS_TRANSFORMS); |
|
1548 |
strikesRef = null; |
|
1549 |
} |
|
1550 |
||
1551 |
// implements sgv.getGlyphTransform |
|
1552 |
AffineTransform getGlyphTransform(int ix) { |
|
1553 |
int index = indices[ix]; |
|
1554 |
if (index == 0) { |
|
1555 |
return null; |
|
1556 |
} |
|
1557 |
||
1558 |
int x = (index - 1) * 6; |
|
1559 |
return new AffineTransform(transforms[x + 0], |
|
1560 |
transforms[x + 1], |
|
1561 |
transforms[x + 2], |
|
1562 |
transforms[x + 3], |
|
1563 |
transforms[x + 4], |
|
1564 |
transforms[x + 5]); |
|
1565 |
} |
|
1566 |
||
1567 |
int transformCount() { |
|
1568 |
if (transforms == null) { |
|
1569 |
return 0; |
|
1570 |
} |
|
1571 |
return transforms.length / 6; |
|
1572 |
} |
|
1573 |
||
1574 |
/** |
|
1575 |
* The strike cache works like this. |
|
1576 |
* |
|
1577 |
* -Each glyph is thought of as having a transform, usually identity. |
|
1578 |
* -Each request for a strike is based on a device transform, either the |
|
1579 |
* one in the frc or the rendering transform. |
|
1580 |
* -For general info, strikes are held with soft references. |
|
1581 |
* -When rendering, strikes must be held with hard references for the |
|
1582 |
* duration of the rendering call. GlyphList will have to hold this |
|
1583 |
* info along with the image and position info, but toss the strike info |
|
1584 |
* when done. |
|
1585 |
* -Build the strike cache as needed. If the dev transform we want to use |
|
1586 |
* has changed from the last time it is built, the cache is flushed by |
|
1587 |
* the caller before these methods are called. |
|
1588 |
* |
|
1589 |
* Use a tx that doesn't include translation components of dst tx. |
|
1590 |
*/ |
|
1591 |
Object setupGlyphImages(long[] images, float[] positions, AffineTransform tx) { |
|
1592 |
int len = sgv.glyphs.length; |
|
1593 |
||
1594 |
GlyphStrike[] sl = getAllStrikes(); |
|
1595 |
for (int i = 0; i < len; ++i) { |
|
1596 |
GlyphStrike gs = sl[indices[i]]; |
|
1597 |
int glyphID = sgv.glyphs[i]; |
|
1598 |
images[i] = gs.strike.getGlyphImagePtr(glyphID); |
|
1599 |
||
1600 |
gs.getGlyphPosition(glyphID, i*2, sgv.positions, positions); |
|
1601 |
} |
|
1602 |
tx.transform(positions, 0, positions, 0, len); |
|
1603 |
||
1604 |
return sl; |
|
1605 |
} |
|
1606 |
||
1607 |
Rectangle getGlyphsPixelBounds(AffineTransform tx, float x, float y, int start, int count) { |
|
1608 |
Rectangle result = null; |
|
1609 |
Rectangle r = new Rectangle(); |
|
1610 |
Point2D.Float pt = new Point.Float(); |
|
1611 |
int n = start * 2; |
|
1612 |
while (--count >= 0) { |
|
1613 |
GlyphStrike gs = getStrike(start); |
|
1614 |
pt.x = x + sgv.positions[n++] + gs.dx; |
|
1615 |
pt.y = y + sgv.positions[n++] + gs.dy; |
|
1616 |
tx.transform(pt, pt); |
|
1617 |
gs.strike.getGlyphImageBounds(sgv.glyphs[start++], pt, r); |
|
1618 |
if (!r.isEmpty()) { |
|
1619 |
if (result == null) { |
|
1620 |
result = new Rectangle(r); |
|
1621 |
} else { |
|
1622 |
result.add(r); |
|
1623 |
} |
|
1624 |
} |
|
1625 |
} |
|
1626 |
return result != null ? result : r; |
|
1627 |
} |
|
1628 |
||
1629 |
GlyphStrike getStrike(int glyphIndex) { |
|
1630 |
if (indices != null) { |
|
1631 |
GlyphStrike[] strikes = getStrikeArray(); |
|
1632 |
return getStrikeAtIndex(strikes, indices[glyphIndex]); |
|
1633 |
} |
|
1634 |
return sgv.getDefaultStrike(); |
|
1635 |
} |
|
1636 |
||
1637 |
private GlyphStrike[] getAllStrikes() { |
|
1638 |
if (indices == null) { |
|
1639 |
return null; |
|
1640 |
} |
|
1641 |
||
1642 |
GlyphStrike[] strikes = getStrikeArray(); |
|
1643 |
if (!haveAllStrikes) { |
|
1644 |
for (int i = 0; i < strikes.length; ++i) { |
|
1645 |
getStrikeAtIndex(strikes, i); |
|
1646 |
} |
|
1647 |
haveAllStrikes = true; |
|
1648 |
} |
|
1649 |
||
1650 |
return strikes; |
|
1651 |
} |
|
1652 |
||
1653 |
private GlyphStrike[] getStrikeArray() { |
|
1654 |
GlyphStrike[] strikes = null; |
|
1655 |
if (strikesRef != null) { |
|
1656 |
strikes = (GlyphStrike[])strikesRef.get(); |
|
1657 |
} |
|
1658 |
if (strikes == null) { |
|
1659 |
haveAllStrikes = false; |
|
1660 |
strikes = new GlyphStrike[transformCount() + 1]; |
|
1661 |
strikesRef = new SoftReference(strikes); |
|
1662 |
} |
|
1663 |
||
1664 |
return strikes; |
|
1665 |
} |
|
1666 |
||
1667 |
private GlyphStrike getStrikeAtIndex(GlyphStrike[] strikes, int strikeIndex) { |
|
1668 |
GlyphStrike strike = strikes[strikeIndex]; |
|
1669 |
if (strike == null) { |
|
1670 |
if (strikeIndex == 0) { |
|
1671 |
strike = sgv.getDefaultStrike(); |
|
1672 |
} else { |
|
1673 |
int ix = (strikeIndex - 1) * 6; |
|
1674 |
AffineTransform gtx = new AffineTransform(transforms[ix], |
|
1675 |
transforms[ix+1], |
|
1676 |
transforms[ix+2], |
|
1677 |
transforms[ix+3], |
|
1678 |
transforms[ix+4], |
|
1679 |
transforms[ix+5]); |
|
1680 |
||
1681 |
strike = GlyphStrike.create(sgv, sgv.dtx, gtx); |
|
1682 |
} |
|
1683 |
strikes[strikeIndex] = strike; |
|
1684 |
} |
|
1685 |
return strike; |
|
1686 |
} |
|
1687 |
} |
|
1688 |
||
1689 |
// This adjusts the metrics by the translation components of the glyph |
|
1690 |
// transform. It is done here since the translation is not known by the |
|
1691 |
// strike. |
|
1692 |
// It adjusts the position of the image and the advance. |
|
1693 |
||
1694 |
public static final class GlyphStrike { |
|
1695 |
StandardGlyphVector sgv; |
|
1696 |
FontStrike strike; // hard reference |
|
1697 |
float dx; |
|
1698 |
float dy; |
|
1699 |
||
1700 |
static GlyphStrike create(StandardGlyphVector sgv, AffineTransform dtx, AffineTransform gtx) { |
|
1701 |
float dx = 0; |
|
1702 |
float dy = 0; |
|
1703 |
||
1704 |
AffineTransform tx = sgv.ftx; |
|
1705 |
if (!dtx.isIdentity() || gtx != null) { |
|
1706 |
tx = new AffineTransform(sgv.ftx); |
|
1707 |
if (gtx != null) { |
|
1708 |
tx.preConcatenate(gtx); |
|
1709 |
dx = (float)tx.getTranslateX(); // uses ftx then gtx to get translation |
|
1710 |
dy = (float)tx.getTranslateY(); |
|
1711 |
} |
|
1712 |
if (!dtx.isIdentity()) { |
|
1713 |
tx.preConcatenate(dtx); |
|
1714 |
} |
|
1715 |
} |
|
1716 |
||
1717 |
int ptSize = 1; // only matters for 'gasp' case. |
|
1718 |
Object aaHint = sgv.frc.getAntiAliasingHint(); |
|
1719 |
if (aaHint == VALUE_TEXT_ANTIALIAS_GASP) { |
|
1720 |
/* Must pass in the calculated point size for rendering. |
|
1721 |
* If the glyph tx is anything other than identity or a |
|
1722 |
* simple translate, calculate the transformed point size. |
|
1723 |
*/ |
|
1724 |
if (!tx.isIdentity() && |
|
1725 |
(tx.getType() & ~AffineTransform.TYPE_TRANSLATION) != 0) { |
|
1726 |
double shearx = tx.getShearX(); |
|
1727 |
if (shearx != 0) { |
|
1728 |
double scaley = tx.getScaleY(); |
|
1729 |
ptSize = |
|
1730 |
(int)Math.sqrt(shearx * shearx + scaley * scaley); |
|
1731 |
} else { |
|
1732 |
ptSize = (int)(Math.abs(tx.getScaleY())); |
|
1733 |
} |
|
1734 |
} |
|
1735 |
} |
|
1736 |
int aa = FontStrikeDesc.getAAHintIntVal(aaHint,sgv.font2D, ptSize); |
|
1737 |
int fm = FontStrikeDesc.getFMHintIntVal |
|
1738 |
(sgv.frc.getFractionalMetricsHint()); |
|
1739 |
FontStrikeDesc desc = new FontStrikeDesc(dtx, |
|
1740 |
tx, |
|
1741 |
sgv.font.getStyle(), |
|
1742 |
aa, fm); |
|
1743 |
||
1744 |
FontStrike strike = sgv.font2D.getStrike(desc); // !!! getStrike(desc, false) |
|
1745 |
||
1746 |
return new GlyphStrike(sgv, strike, dx, dy); |
|
1747 |
} |
|
1748 |
||
1749 |
private GlyphStrike(StandardGlyphVector sgv, FontStrike strike, float dx, float dy) { |
|
1750 |
this.sgv = sgv; |
|
1751 |
this.strike = strike; |
|
1752 |
this.dx = dx; |
|
1753 |
this.dy = dy; |
|
1754 |
} |
|
1755 |
||
1756 |
void getADL(ADL result) { |
|
1757 |
StrikeMetrics sm = strike.getFontMetrics(); |
|
1758 |
Point2D.Float delta = null; |
|
1759 |
if (sgv.font.isTransformed()) { |
|
1760 |
delta = new Point2D.Float(); |
|
1761 |
delta.x = (float)sgv.font.getTransform().getTranslateX(); |
|
1762 |
delta.y = (float)sgv.font.getTransform().getTranslateY(); |
|
1763 |
} |
|
1764 |
||
1765 |
result.ascentX = -sm.ascentX; |
|
1766 |
result.ascentY = -sm.ascentY; |
|
1767 |
result.descentX = sm.descentX; |
|
1768 |
result.descentY = sm.descentY; |
|
1769 |
result.leadingX = sm.leadingX; |
|
1770 |
result.leadingY = sm.leadingY; |
|
1771 |
} |
|
1772 |
||
1773 |
void getGlyphPosition(int glyphID, int ix, float[] positions, float[] result) { |
|
1774 |
result[ix] = positions[ix] + dx; |
|
1775 |
++ix; |
|
1776 |
result[ix] = positions[ix] + dy; |
|
1777 |
} |
|
1778 |
||
1779 |
void addDefaultGlyphAdvance(int glyphID, Point2D.Float result) { |
|
1780 |
// !!! change this API? Creates unnecessary garbage. Also the name doesn't quite fit. |
|
1781 |
// strike.addGlyphAdvance(Point2D.Float adv); // hey, whaddya know, matches my api :-) |
|
1782 |
Point2D.Float adv = strike.getGlyphMetrics(glyphID); |
|
1783 |
result.x += adv.x + dx; |
|
1784 |
result.y += adv.y + dy; |
|
1785 |
} |
|
1786 |
||
1787 |
Rectangle2D getGlyphOutlineBounds(int glyphID, float x, float y) { |
|
1788 |
Rectangle2D result = null; |
|
1789 |
if (sgv.invdtx == null) { |
|
1790 |
result = new Rectangle2D.Float(); |
|
1791 |
result.setRect(strike.getGlyphOutlineBounds(glyphID)); // don't mutate cached rect |
|
1792 |
} else { |
|
1793 |
GeneralPath gp = strike.getGlyphOutline(glyphID, 0, 0); |
|
1794 |
gp.transform(sgv.invdtx); |
|
1795 |
result = gp.getBounds2D(); |
|
1796 |
} |
|
4358
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
1797 |
/* Since x is the logical advance of the glyph to this point. |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
1798 |
* Because of the way that Rectangle.union is specified, this |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
1799 |
* means that subsequent unioning of a rect including that |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
1800 |
* will be affected, even if the glyph is empty. So skip such |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
1801 |
* cases. This alone isn't a complete solution since x==0 |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
1802 |
* may also not be what is wanted. The code that does the |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
1803 |
* unioning also needs to be aware to ignore empty glyphs. |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
1804 |
*/ |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
1805 |
if (!result.isEmpty()) { |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
1806 |
result.setRect(result.getMinX() + x + dx, |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
1807 |
result.getMinY() + y + dy, |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
1808 |
result.getWidth(), result.getHeight()); |
0549f5b9abd1
6904962: GlyphVector.getVisualBounds should not be affected by leading or trailing white space.
prr
parents:
3928
diff
changeset
|
1809 |
} |
2 | 1810 |
return result; |
1811 |
} |
|
1812 |
||
1813 |
void appendGlyphOutline(int glyphID, GeneralPath result, float x, float y) { |
|
1814 |
// !!! fontStrike needs a method for this. For that matter, GeneralPath does. |
|
1815 |
GeneralPath gp = null; |
|
1816 |
if (sgv.invdtx == null) { |
|
1817 |
gp = strike.getGlyphOutline(glyphID, x + dx, y + dy); |
|
1818 |
} else { |
|
1819 |
gp = strike.getGlyphOutline(glyphID, 0, 0); |
|
1820 |
gp.transform(sgv.invdtx); |
|
1821 |
gp.transform(AffineTransform.getTranslateInstance(x + dx, y + dy)); |
|
1822 |
} |
|
1823 |
PathIterator iterator = gp.getPathIterator(null); |
|
1824 |
result.append(iterator, false); |
|
1825 |
} |
|
1826 |
} |
|
1827 |
||
1828 |
public String toString() { |
|
1829 |
return appendString(null).toString(); |
|
1830 |
} |
|
1831 |
||
1832 |
StringBuffer appendString(StringBuffer buf) { |
|
1833 |
if (buf == null) { |
|
1834 |
buf = new StringBuffer(); |
|
1835 |
} |
|
1836 |
try { |
|
1837 |
buf.append("SGV{font: "); |
|
1838 |
buf.append(font.toString()); |
|
1839 |
buf.append(", frc: "); |
|
1840 |
buf.append(frc.toString()); |
|
1841 |
buf.append(", glyphs: ("); |
|
1842 |
buf.append(glyphs.length); |
|
1843 |
buf.append(")["); |
|
1844 |
for (int i = 0; i < glyphs.length; ++i) { |
|
1845 |
if (i > 0) { |
|
1846 |
buf.append(", "); |
|
1847 |
} |
|
1848 |
buf.append(Integer.toHexString(glyphs[i])); |
|
1849 |
} |
|
1850 |
buf.append("]"); |
|
1851 |
if (positions != null) { |
|
1852 |
buf.append(", positions: ("); |
|
1853 |
buf.append(positions.length); |
|
1854 |
buf.append(")["); |
|
1855 |
for (int i = 0; i < positions.length; i += 2) { |
|
1856 |
if (i > 0) { |
|
1857 |
buf.append(", "); |
|
1858 |
} |
|
1859 |
buf.append(positions[i]); |
|
1860 |
buf.append("@"); |
|
1861 |
buf.append(positions[i+1]); |
|
1862 |
} |
|
1863 |
buf.append("]"); |
|
1864 |
} |
|
1865 |
if (charIndices != null) { |
|
1866 |
buf.append(", indices: ("); |
|
1867 |
buf.append(charIndices.length); |
|
1868 |
buf.append(")["); |
|
1869 |
for (int i = 0; i < charIndices.length; ++i) { |
|
1870 |
if (i > 0) { |
|
1871 |
buf.append(", "); |
|
1872 |
} |
|
1873 |
buf.append(charIndices[i]); |
|
1874 |
} |
|
1875 |
buf.append("]"); |
|
1876 |
} |
|
1877 |
buf.append(", flags:"); |
|
1878 |
if (getLayoutFlags() == 0) { |
|
1879 |
buf.append(" default"); |
|
1880 |
} else { |
|
1881 |
if ((flags & FLAG_HAS_TRANSFORMS) != 0) { |
|
1882 |
buf.append(" tx"); |
|
1883 |
} |
|
1884 |
if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) { |
|
1885 |
buf.append(" pos"); |
|
1886 |
} |
|
1887 |
if ((flags & FLAG_RUN_RTL) != 0) { |
|
1888 |
buf.append(" rtl"); |
|
1889 |
} |
|
1890 |
if ((flags & FLAG_COMPLEX_GLYPHS) != 0) { |
|
1891 |
buf.append(" complex"); |
|
1892 |
} |
|
1893 |
} |
|
1894 |
} |
|
1895 |
catch(Exception e) { |
|
1896 |
buf.append(" " + e.getMessage()); |
|
1897 |
} |
|
1898 |
buf.append("}"); |
|
1899 |
||
1900 |
return buf; |
|
1901 |
} |
|
1902 |
||
1903 |
static class ADL { |
|
1904 |
public float ascentX; |
|
1905 |
public float ascentY; |
|
1906 |
public float descentX; |
|
1907 |
public float descentY; |
|
1908 |
public float leadingX; |
|
1909 |
public float leadingY; |
|
1910 |
||
1911 |
public String toString() { |
|
1912 |
return toStringBuffer(null).toString(); |
|
1913 |
} |
|
1914 |
||
1915 |
protected StringBuffer toStringBuffer(StringBuffer result) { |
|
1916 |
if (result == null) { |
|
1917 |
result = new StringBuffer(); |
|
1918 |
} |
|
1919 |
result.append("ax: "); |
|
1920 |
result.append(ascentX); |
|
1921 |
result.append(" ay: "); |
|
1922 |
result.append(ascentY); |
|
1923 |
result.append(" dx: "); |
|
1924 |
result.append(descentX); |
|
1925 |
result.append(" dy: "); |
|
1926 |
result.append(descentY); |
|
1927 |
result.append(" lx: "); |
|
1928 |
result.append(leadingX); |
|
1929 |
result.append(" ly: "); |
|
1930 |
result.append(leadingY); |
|
1931 |
||
1932 |
return result; |
|
1933 |
} |
|
1934 |
} |
|
1935 |
} |