History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/sun/font/StandardGlyphVector.java (1 of 18352 files). Show all

Unified · Split

java/pkg/sun/font/StandardGlyphVector.java+1935−0
@@ -0,0 +1,1935 @@
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
26package sun.font;
27
28import java.awt.Font;
29import java.awt.Graphics2D;
30import java.awt.Point;
31import java.awt.Rectangle;
32import static java.awt.RenderingHints.*;
33import java.awt.Shape;
34import java.awt.font.FontRenderContext;
35import java.awt.font.GlyphMetrics;
36import java.awt.font.GlyphJustificationInfo;
37import java.awt.font.GlyphVector;
38import java.awt.font.LineMetrics;
39import java.awt.font.TextAttribute;
40import java.awt.geom.AffineTransform;
41import java.awt.geom.GeneralPath;
42import java.awt.geom.NoninvertibleTransformException;
43import java.awt.geom.PathIterator;
44import java.awt.geom.Point2D;
45import java.awt.geom.Rectangle2D;
46import java.lang.ref.SoftReference;
47import java.text.CharacterIterator;
48
49import sun.awt.SunHints;
50import 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 */
136public 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
200 Font2D f2d = FontUtilities.getFont2D(font);
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() {
399 Rectangle2D result = null;
400 for (int i = 0; i < glyphs.length; ++i) {
401 Rectangle2D glyphVB = getGlyphVisualBounds(i).getBounds2D();
402 if (!glyphVB.isEmpty()) {
403 if (result == null) {
404 result = glyphVB;
405 } else {
406 Rectangle2D.union(result, glyphVB, result);
407 }
408 }
409 }
410 if (result == null) {
411 result = new Rectangle2D.Float(0, 0, 0, 0);
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() {
1126 font2D = FontUtilities.getFont2D(font);
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 }
1797 /* Since x is the logical advance of the glyph to this point.
1798 * Because of the way that Rectangle.union is specified, this
1799 * means that subsequent unioning of a rect including that
1800 * will be affected, even if the glyph is empty. So skip such
1801 * cases. This alone isn't a complete solution since x==0
1802 * may also not be what is wanted. The code that does the
1803 * unioning also needs to be aware to ignore empty glyphs.
1804 */
1805 if (!result.isEmpty()) {
1806 result.setRect(result.getMinX() + x + dx,
1807 result.getMinY() + y + dy,
1808 result.getWidth(), result.getHeight());
1809 }
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}