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 200 Font2D f2d = FontManager.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 if (glyphs.length == 0) { 400 return new Rectangle2D.Float(0, 0, 0, 0); 401 } 402 Rectangle2D result = getGlyphVisualBounds(0).getBounds2D(); 403 for (int i = 1; i < glyphs.length; ++i) { 404 Rectangle2D.union(result, getGlyphVisualBounds(i).getBounds2D(), result); 405 } 406 return result; 407 } 408 409 // !!! not cached, assume TextLayout will cache if necessary 410 // !!! fontStrike needs a method for this 411 public Rectangle getPixelBounds(FontRenderContext renderFRC, float x, float y) { 412 return getGlyphsPixelBounds(renderFRC, x, y, 0, glyphs.length); 413 } 414 415 public Shape getOutline() { 416 return getGlyphsOutline(0, glyphs.length, 0, 0); 417 } 418 419 public Shape getOutline(float x, float y) { 420 return getGlyphsOutline(0, glyphs.length, x, y); 421 } 422 423 // relative to gv origin 424 public Shape getGlyphOutline(int ix) { 425 return getGlyphsOutline(ix, 1, 0, 0); 426 } 427 428 // relative to gv origin offset by x, y 429 public Shape getGlyphOutline(int ix, float x, float y) { 430 return getGlyphsOutline(ix, 1, x, y); 431 } 432 433 public Point2D getGlyphPosition(int ix) { 434 initPositions(); 435 436 ix *= 2; 437 return new Point2D.Float(positions[ix], positions[ix + 1]); 438 } 439 440 public void setGlyphPosition(int ix, Point2D pos) { 441 initPositions(); 442 443 int ix2 = ix << 1; 444 positions[ix2] = (float)pos.getX(); 445 positions[ix2 + 1] = (float)pos.getY(); 446 447 clearCaches(ix); 448 addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); 449 } 450 451 public AffineTransform getGlyphTransform(int ix) { 452 if (ix < 0 || ix >= glyphs.length) { 453 throw new IndexOutOfBoundsException("ix = " + ix); 454 } 455 if (gti != null) { 456 return gti.getGlyphTransform(ix); 457 } 458 return null; // spec'd as returning null 459 } 460 461 public void setGlyphTransform(int ix, AffineTransform newTX) { 462 if (ix < 0 || ix >= glyphs.length) { 463 throw new IndexOutOfBoundsException("ix = " + ix); 464 } 465 466 if (gti == null) { 467 if (newTX == null || newTX.isIdentity()) { 468 return; 469 } 470 gti = new GlyphTransformInfo(this); 471 } 472 gti.setGlyphTransform(ix, newTX); // sets flags 473 if (gti.transformCount() == 0) { 474 gti = null; 475 } 476 } 477 478 public int getLayoutFlags() { 479 if (flags == UNINITIALIZED_FLAGS) { 480 flags = 0; 481 482 if (charIndices != null && glyphs.length > 1) { 483 boolean ltr = true; 484 boolean rtl = true; 485 486 int rtlix = charIndices.length; // rtl index 487 for (int i = 0; i < charIndices.length && (ltr || rtl); ++i) { 488 int cx = charIndices[i]; 489 490 ltr = ltr && (cx == i); 491 rtl = rtl && (cx == --rtlix); 492 } 493 494 if (rtl) flags |= FLAG_RUN_RTL; 495 if (!rtl && !ltr) flags |= FLAG_COMPLEX_GLYPHS; 496 } 497 } 498 499 return flags; 500 } 501 502 public float[] getGlyphPositions(int start, int count, float[] result) { 503 if (count < 0) { 504 throw new IllegalArgumentException("count = " + count); 505 } 506 if (start < 0) { 507 throw new IndexOutOfBoundsException("start = " + start); 508 } 509 if (start > glyphs.length + 1 - count) { // watch for overflow 510 throw new IndexOutOfBoundsException("start + count = " + (start + count)); 511 } 512 513 return internalGetGlyphPositions(start, count, 0, result); 514 } 515 516 public Shape getGlyphLogicalBounds(int ix) { 517 if (ix < 0 || ix >= glyphs.length) { 518 throw new IndexOutOfBoundsException("ix = " + ix); 519 } 520 521 Shape[] lbcache; 522 if (lbcacheRef == null || (lbcache = (Shape[])lbcacheRef.get()) == null) { 523 lbcache = new Shape[glyphs.length]; 524 lbcacheRef = new SoftReference(lbcache); 525 } 526 527 Shape result = lbcache[ix]; 528 if (result == null) { 529 setFRCTX(); 530 initPositions(); 531 532 // !!! ought to return a rectangle2d for simple cases, though the following works for all 533 534 // get the position, the tx offset, and the x,y advance and x,y adl. The 535 // shape is the box formed by adv (width) and adl (height) offset by 536 // the position plus the tx offset minus the ascent. 537 538 ADL adl = new ADL(); 539 GlyphStrike gs = getGlyphStrike(ix); 540 gs.getADL(adl); 541 542 Point2D.Float adv = gs.strike.getGlyphMetrics(glyphs[ix]); 543 544 float wx = adv.x; 545 float wy = adv.y; 546 float hx = adl.descentX + adl.leadingX + adl.ascentX; 547 float hy = adl.descentY + adl.leadingY + adl.ascentY; 548 float x = positions[ix*2] + gs.dx - adl.ascentX; 549 float y = positions[ix*2+1] + gs.dy - adl.ascentY; 550 551 GeneralPath gp = new GeneralPath(); 552 gp.moveTo(x, y); 553 gp.lineTo(x + wx, y + wy); 554 gp.lineTo(x + wx + hx, y + wy + hy); 555 gp.lineTo(x + hx, y + hy); 556 gp.closePath(); 557 558 result = new DelegatingShape(gp); 559 lbcache[ix] = result; 560 } 561 562 return result; 563 } 564 private SoftReference lbcacheRef; 565 566 public Shape getGlyphVisualBounds(int ix) { 567 if (ix < 0 || ix >= glyphs.length) { 568 throw new IndexOutOfBoundsException("ix = " + ix); 569 } 570 571 Shape[] vbcache; 572 if (vbcacheRef == null || (vbcache = (Shape[])vbcacheRef.get()) == null) { 573 vbcache = new Shape[glyphs.length]; 574 vbcacheRef = new SoftReference(vbcache); 575 } 576 577 Shape result = vbcache[ix]; 578 if (result == null) { 579 result = new DelegatingShape(getGlyphOutlineBounds(ix)); 580 vbcache[ix] = result; 581 } 582 583 return result; 584 } 585 private SoftReference vbcacheRef; 586 587 public Rectangle getGlyphPixelBounds(int index, FontRenderContext renderFRC, float x, float y) { 588 return getGlyphsPixelBounds(renderFRC, x, y, index, 1); 589 } 590 591 public GlyphMetrics getGlyphMetrics(int ix) { 592 if (ix < 0 || ix >= glyphs.length) { 593 throw new IndexOutOfBoundsException("ix = " + ix); 594 } 595 596 Rectangle2D vb = getGlyphVisualBounds(ix).getBounds2D(); 597 Point2D pt = getGlyphPosition(ix); 598 vb.setRect(vb.getMinX() - pt.getX(), 599 vb.getMinY() - pt.getY(), 600 vb.getWidth(), 601 vb.getHeight()); 602 Point2D.Float adv = 603 getGlyphStrike(ix).strike.getGlyphMetrics(glyphs[ix]); 604 GlyphMetrics gm = new GlyphMetrics(true, adv.x, adv.y, 605 vb, 606 GlyphMetrics.STANDARD); 607 return gm; 608 } 609 610 public GlyphJustificationInfo getGlyphJustificationInfo(int ix) { 611 if (ix < 0 || ix >= glyphs.length) { 612 throw new IndexOutOfBoundsException("ix = " + ix); 613 } 614 615 // currently we don't have enough information to do this right. should 616 // get info from the font and use real OT/GX justification. Right now 617 // sun/font/ExtendedTextSourceLabel assigns one of three infos 618 // based on whether the char is kanji, space, or other. 619 620 return null; 621 } 622 623 public boolean equals(GlyphVector rhs) { 624 if (this == rhs) { 625 return true; 626 } 627 if (rhs == null) { 628 return false; 629 } 630 631 try { 632 StandardGlyphVector other = (StandardGlyphVector)rhs; 633 634 if (glyphs.length != other.glyphs.length) { 635 return false; 636 } 637 638 for (int i = 0; i < glyphs.length; ++i) { 639 if (glyphs[i] != other.glyphs[i]) { 640 return false; 641 } 642 } 643 644 if (!font.equals(other.font)) { 645 return false; 646 } 647 648 if (!frc.equals(other.frc)) { 649 return false; 650 } 651 652 if ((other.positions == null) != (positions == null)) { 653 if (positions == null) { 654 initPositions(); 655 } else { 656 other.initPositions(); 657 } 658 } 659 660 if (positions != null) { 661 for (int i = 0; i < positions.length; ++i) { 662 if (positions[i] != other.positions[i]) { 663 return false; 664 } 665 } 666 } 667 668 if (gti == null) { 669 return other.gti == null; 670 } else { 671 return gti.equals(other.gti); 672 } 673 } 674 catch (ClassCastException e) { 675 // assume they are different simply by virtue of the class difference 676 677 return false; 678 } 679 } 680 681 /** 682 * As a concrete subclass of Object that implements equality, this must 683 * implement hashCode. 684 */ 685 public int hashCode() { 686 return font.hashCode() ^ glyphs.length; 687 } 688 689 /** 690 * Since we implement equality comparisons for GlyphVector, we implement 691 * the inherited Object.equals(Object) as well. GlyphVector should do 692 * this, and define two glyphvectors as not equal if the classes differ. 693 */ 694 public boolean equals(Object rhs) { 695 try { 696 return equals((GlyphVector)rhs); 697 } 698 catch (ClassCastException e) { 699 return false; 700 } 701 } 702 703 /** 704 * Sometimes I wish java had covariant return types... 705 */ 706 public StandardGlyphVector copy() { 707 return (StandardGlyphVector)clone(); 708 } 709 710 /** 711 * As a concrete subclass of GlyphVector, this must implement clone. 712 */ 713 public Object clone() { 714 // positions, gti are mutable so we have to clone them 715 // font2d can be shared 716 // fsref is a cache and can be shared 717 try { 718 StandardGlyphVector result = (StandardGlyphVector)super.clone(); 719 720 result.clearCaches(); 721 722 if (positions != null) { 723 result.positions = (float[])positions.clone(); 724 } 725 726 if (gti != null) { 727 result.gti = new GlyphTransformInfo(result, gti); 728 } 729 730 return result; 731 } 732 catch (CloneNotSupportedException e) { 733 } 734 735 return this; 736 } 737 738 ////////////////////// 739 // StandardGlyphVector new public methods 740 ///////////////////// 741 742 /* 743 * Set a multiple glyph positions at one time. GlyphVector only 744 * provides API to set a single glyph at a time. 745 */ 746 public void setGlyphPositions(float[] srcPositions, int srcStart, 747 int start, int count) { 748 if (count < 0) { 749 throw new IllegalArgumentException("count = " + count); 750 } 751 752 initPositions(); 753 for (int i = start * 2, e = i + count * 2, p = srcStart; i < e; ++i, ++p) { 754 positions[i] = srcPositions[p]; 755 } 756 757 clearCaches(); 758 addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); 759 } 760 761 /** 762 * Set all the glyph positions, including the 'after last glyph' position. 763 * The srcPositions array must be of length (numGlyphs + 1) * 2. 764 */ 765 public void setGlyphPositions(float[] srcPositions) { 766 int requiredLength = glyphs.length * 2 + 2; 767 if (srcPositions.length != requiredLength) { 768 throw new IllegalArgumentException("srcPositions.length != " + requiredLength); 769 } 770 771 positions = (float[])srcPositions.clone(); 772 773 clearCaches(); 774 addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); 775 } 776 777 /** 778 * This is a convenience overload that gets all the glyph positions, which 779 * is what you usually want to do if you're getting more than one. 780 * !!! should I bother taking result parameter? 781 */ 782 public float[] getGlyphPositions(float[] result) { 783 return internalGetGlyphPositions(0, glyphs.length + 1, 0, result); 784 } 785 786 /** 787 * Get transform information for the requested range of glyphs. 788 * If no glyphs have a transform, return null. 789 * If a glyph has no transform (or is the identity transform) its entry in the result array will be null. 790 * If the passed-in result is null an array will be allocated for the caller. 791 * Each transform instance in the result array will unique, and independent of the GlyphVector's transform. 792 */ 793 public AffineTransform[] getGlyphTransforms(int start, int count, AffineTransform[] result) { 794 if (start < 0 || count < 0 || start + count > glyphs.length) { 795 throw new IllegalArgumentException("start: " + start + " count: " + count); 796 } 797 798 if (gti == null) { 799 return null; 800 } 801 802 if (result == null) { 803 result = new AffineTransform[count]; 804 } 805 806 for (int i = 0; i < count; ++i, ++start) { 807 result[i] = gti.getGlyphTransform(start); 808 } 809 810 return result; 811 } 812 813 /** 814 * Convenience overload for getGlyphTransforms(int, int, AffineTransform[], int); 815 */ 816 public AffineTransform[] getGlyphTransforms() { 817 return getGlyphTransforms(0, glyphs.length, null); 818 } 819 820 /** 821 * Set a number of glyph transforms. 822 * Original transforms are unchanged. The array may contain nulls, and also may 823 * contain multiple references to the same transform instance. 824 */ 825 public void setGlyphTransforms(AffineTransform[] srcTransforms, int srcStart, int start, int count) { 826 for (int i = start, e = start + count; i < e; ++i) { 827 setGlyphTransform(i, srcTransforms[srcStart + i]); 828 } 829 } 830 831 /** 832 * Convenience overload of setGlyphTransforms(AffineTransform[], int, int, int). 833 */ 834 public void setGlyphTransforms(AffineTransform[] srcTransforms) { 835 setGlyphTransforms(srcTransforms, 0, 0, glyphs.length); 836 } 837 838 /** 839 * For each glyph return posx, posy, advx, advy, visx, visy, visw, vish. 840 */ 841 public float[] getGlyphInfo() { 842 setFRCTX(); 843 initPositions(); 844 float[] result = new float[glyphs.length * 8]; 845 for (int i = 0, n = 0; i < glyphs.length; ++i, n += 8) { 846 float x = positions[i*2]; 847 float y = positions[i*2+1]; 848 result[n] = x; 849 result[n+1] = y; 850 851 int glyphID = glyphs[i]; 852 GlyphStrike s = getGlyphStrike(i); 853 Point2D.Float adv = s.strike.getGlyphMetrics(glyphID); 854 result[n+2] = adv.x; 855 result[n+3] = adv.y; 856 857 Rectangle2D vb = getGlyphVisualBounds(i).getBounds2D(); 858 result[n+4] = (float)(vb.getMinX()); 859 result[n+5] = (float)(vb.getMinY()); 860 result[n+6] = (float)(vb.getWidth()); 861 result[n+7] = (float)(vb.getHeight()); 862 } 863 return result; 864 } 865 866 /** 867 * !!! not used currently, but might be by getPixelbounds? 868 */ 869 public void pixellate(FontRenderContext renderFRC, Point2D loc, Point pxResult) { 870 if (renderFRC == null) { 871 renderFRC = frc; 872 } 873 874 // it is a total pain that you have to copy the transform. 875 876 AffineTransform at = renderFRC.getTransform(); 877 at.transform(loc, loc); 878 pxResult.x = (int)loc.getX(); // but must not behave oddly around zero 879 pxResult.y = (int)loc.getY(); 880 loc.setLocation(pxResult.x, pxResult.y); 881 try { 882 at.inverseTransform(loc, loc); 883 } 884 catch (NoninvertibleTransformException e) { 885 throw new IllegalArgumentException("must be able to invert frc transform"); 886 } 887 } 888 889 ////////////////////// 890 // StandardGlyphVector package private methods 891 ///////////////////// 892 893 // used by glyphlist to determine if it needs to allocate/size positions array 894 // gti always uses positions because the gtx might have translation. We also 895 // need positions if the rendering dtx is different from the frctx. 896 897 boolean needsPositions(double[] devTX) { 898 return gti != null || 899 (getLayoutFlags() & FLAG_HAS_POSITION_ADJUSTMENTS) != 0 || 900 !matchTX(devTX, frctx); 901 } 902 903 // used by glyphList to get strong refs to font strikes for duration of rendering call 904 // if devTX matches current devTX, we're ready to go 905 // if we don't have multiple transforms, we're already ok 906 907 // !!! I'm not sure fontInfo works so well for glyphvector, since we have to be able to handle 908 // the multiple-strikes case 909 910 /* 911 * GlyphList calls this to set up its images data. First it calls needsPositions, 912 * passing the devTX, to see if it should provide us a positions array to fill. 913 * It only doesn't need them if we're a simple glyph vector whose frctx matches the 914 * devtx. 915 * Then it calls setupGlyphImages. If we need positions, we make sure we have our 916 * default positions based on the frctx first. Then we set the devTX, and use 917 * strikes based on it to generate the images. Finally, we fill in the positions 918 * array. 919 * If we have transforms, we delegate to gti. It depends on our having first 920 * initialized the positions and devTX. 921 */ 922 Object setupGlyphImages(long[] images, float[] positions, double[] devTX) { 923 initPositions(); // FIRST ensure we have positions based on our frctx 924 setRenderTransform(devTX); // THEN make sure we are using the desired devTX 925 926 if (gti != null) { 927 return gti.setupGlyphImages(images, positions, dtx); 928 } 929 930 GlyphStrike gs = getDefaultStrike(); 931 gs.strike.getGlyphImagePtrs(glyphs, images, glyphs.length); 932 933 if (positions != null) { 934 if (dtx.isIdentity()) { 935 System.arraycopy(this.positions, 0, positions, 0, glyphs.length * 2); 936 } else { 937 dtx.transform(this.positions, 0, positions, 0, glyphs.length); 938 } 939 } 940 941 return gs; 942 } 943 944 ////////////////////// 945 // StandardGlyphVector private methods 946 ///////////////////// 947 948 // We keep translation in our frctx since getPixelBounds uses it. But 949 // GlyphList pulls out the translation and applies it separately, so 950 // we strip it out when we set the dtx. Basically nothing uses the 951 // translation except getPixelBounds. 952 953 // called by needsPositions, setRenderTransform 954 private static boolean matchTX(double[] lhs, AffineTransform rhs) { 955 return 956 lhs[0] == rhs.getScaleX() && 957 lhs[1] == rhs.getShearY() && 958 lhs[2] == rhs.getShearX() && 959 lhs[3] == rhs.getScaleY(); 960 } 961 962 // returns new tx if old one has translation, otherwise returns old one 963 private static AffineTransform getNonTranslateTX(AffineTransform tx) { 964 if (tx.getTranslateX() != 0 || tx.getTranslateY() != 0) { 965 tx = new AffineTransform(tx.getScaleX(), tx.getShearY(), 966 tx.getShearX(), tx.getScaleY(), 967 0, 0); 968 } 969 return tx; 970 } 971 972 private static boolean equalNonTranslateTX(AffineTransform lhs, AffineTransform rhs) { 973 return lhs.getScaleX() == rhs.getScaleX() && 974 lhs.getShearY() == rhs.getShearY() && 975 lhs.getShearX() == rhs.getShearX() && 976 lhs.getScaleY() == rhs.getScaleY(); 977 } 978 979 // called by setupGlyphImages (after needsPositions, so redundant match check?) 980 private void setRenderTransform(double[] devTX) { 981 assert(devTX.length == 4); 982 if (!matchTX(devTX, dtx)) { 983 resetDTX(new AffineTransform(devTX)); // no translation since devTX len == 4. 984 } 985 } 986 987 // called by getGlyphsPixelBounds 988 private final void setDTX(AffineTransform tx) { 989 if (!equalNonTranslateTX(dtx, tx)) { 990 resetDTX(getNonTranslateTX(tx)); 991 } 992 } 993 994 // called by most functions 995 private final void setFRCTX() { 996 if (!equalNonTranslateTX(frctx, dtx)) { 997 resetDTX(getNonTranslateTX(frctx)); 998 } 999 } 1000 1001 /** 1002 * Change the dtx for the strike refs we use. Keeps a reference to the at. At 1003 * must not contain translation. 1004 * Called by setRenderTransform, setDTX, initFontData. 1005 */ 1006 private final void resetDTX(AffineTransform at) { 1007 fsref = null; 1008 dtx = at; 1009 invdtx = null; 1010 if (!dtx.isIdentity()) { 1011 try { 1012 invdtx = dtx.createInverse(); 1013 } 1014 catch (NoninvertibleTransformException e) { 1015 // we needn't care for rendering 1016 } 1017 } 1018 if (gti != null) { 1019 gti.strikesRef = null; 1020 } 1021 } 1022 1023 /** 1024 * Utility used by getStandardGV. 1025 * Constructs a StandardGlyphVector from a generic glyph vector. 1026 * Do not call this from new contexts without considering the comment 1027 * about "userGlyphs". 1028 */ 1029 private StandardGlyphVector(GlyphVector gv, FontRenderContext frc) { 1030 this.font = gv.getFont(); 1031 this.frc = frc; 1032 initFontData(); 1033 1034 int nGlyphs = gv.getNumGlyphs(); 1035 this.userGlyphs = gv.getGlyphCodes(0, nGlyphs, null); 1036 if (gv instanceof StandardGlyphVector) { 1037 /* userGlyphs will be OK because this is a private constructor 1038 * and the returned instance is used only for rendering. 1039 * It's not constructable by user code, nor returned to the 1040 * application. So we know "userGlyphs" are valid as having 1041 * been either already validated or are the result of layout. 1042 */ 1043 this.glyphs = userGlyphs; 1044 } else { 1045 this.glyphs = getValidatedGlyphs(this.userGlyphs); 1046 } 1047 this.flags = gv.getLayoutFlags() & FLAG_MASK; 1048 1049 if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) { 1050 this.positions = gv.getGlyphPositions(0, nGlyphs + 1, null); 1051 } 1052 1053 if ((flags & FLAG_COMPLEX_GLYPHS) != 0) { 1054 this.charIndices = gv.getGlyphCharIndices(0, nGlyphs, null); 1055 } 1056 1057 if ((flags & FLAG_HAS_TRANSFORMS) != 0) { 1058 AffineTransform[] txs = new AffineTransform[nGlyphs]; // worst case 1059 for (int i = 0; i < nGlyphs; ++i) { 1060 txs[i] = gv.getGlyphTransform(i); // gv doesn't have getGlyphsTransforms 1061 } 1062 1063 setGlyphTransforms(txs); 1064 } 1065 } 1066 1067 /* Before asking the Font we see if the glyph code is 1068 * FFFE or FFFF which are special values that we should be internally 1069 * ready to handle as meaning invisible glyphs. The Font would report 1070 * those as the missing glyph. 1071 */ 1072 int[] getValidatedGlyphs(int[] oglyphs) { 1073 int len = oglyphs.length; 1074 int[] vglyphs = new int[len]; 1075 for (int i=0; i<len; i++) { 1076 if (oglyphs[i] == 0xFFFE || oglyphs[i] == 0xFFFF) { 1077 vglyphs[i] = oglyphs[i]; 1078 } else { 1079 vglyphs[i] = font2D.getValidatedGlyphCode(oglyphs[i]); 1080 } 1081 } 1082 return vglyphs; 1083 } 1084 1085 // utility used by constructors 1086 private void init(Font font, char[] text, int start, int count, 1087 FontRenderContext frc, int flags) { 1088 1089 if (start < 0 || count < 0 || start + count > text.length) { 1090 throw new ArrayIndexOutOfBoundsException("start or count out of bounds"); 1091 } 1092 1093 this.font = font; 1094 this.frc = frc; 1095 this.flags = flags; 1096 1097 if (getTracking(font) != 0) { 1098 addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); 1099 } 1100 1101 // !!! change mapper interface? 1102 if (start != 0) { 1103 char[] temp = new char[count]; 1104 System.arraycopy(text, start, temp, 0, count); 1105 text = temp; 1106 } 1107 1108 initFontData(); // sets up font2D 1109 1110 // !!! no layout for now, should add checks 1111 // !!! need to support creating a StandardGlyphVector from a TextMeasurer's info... 1112 glyphs = new int[count]; // hmmm 1113 /* Glyphs obtained here are already validated by the font */ 1114 userGlyphs = glyphs; 1115 font2D.getMapper().charsToGlyphs(count, text, glyphs); 1116 } 1117 1118 private void initFontData() { 1119 font2D = FontManager.getFont2D(font); 1120 float s = font.getSize2D(); 1121 if (font.isTransformed()) { 1122 ftx = font.getTransform(); 1123 if (ftx.getTranslateX() != 0 || ftx.getTranslateY() != 0) { 1124 addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); 1125 } 1126 ftx.setTransform(ftx.getScaleX(), ftx.getShearY(), ftx.getShearX(), ftx.getScaleY(), 0, 0); 1127 ftx.scale(s, s); 1128 } else { 1129 ftx = AffineTransform.getScaleInstance(s, s); 1130 } 1131 1132 frctx = frc.getTransform(); 1133 resetDTX(getNonTranslateTX(frctx)); 1134 } 1135 1136 /** 1137 * Copy glyph position data into a result array starting at the indicated 1138 * offset in the array. If the passed-in result array is null, a new 1139 * array will be allocated and returned. 1140 * 1141 * This is an internal method and does no extra argument checking. 1142 * 1143 * @param start the index of the first glyph to get 1144 * @param count the number of glyphs to get 1145 * @param offset the offset into result at which to put the data 1146 * @param result an array to hold the x,y positions 1147 * @return the modified position array 1148 */ 1149 private float[] internalGetGlyphPositions(int start, int count, int offset, float[] result) { 1150 if (result == null) { 1151 result = new float[offset + count * 2]; 1152 } 1153 1154 initPositions(); 1155 1156 // System.arraycopy is slow for stuff like this 1157 for (int i = offset, e = offset + count * 2, p = start * 2; i < e; ++i, ++p) { 1158 result[i] = positions[p]; 1159 } 1160 1161 return result; 1162 } 1163 1164 private Rectangle2D getGlyphOutlineBounds(int ix) { 1165 setFRCTX(); 1166 initPositions(); 1167 return getGlyphStrike(ix).getGlyphOutlineBounds(glyphs[ix], positions[ix*2], positions[ix*2+1]); 1168 } 1169 1170 /** 1171 * Used by getOutline, getGlyphsOutline 1172 */ 1173 private Shape getGlyphsOutline(int start, int count, float x, float y) { 1174 setFRCTX(); 1175 initPositions(); 1176 1177 GeneralPath result = new GeneralPath(GeneralPath.WIND_NON_ZERO); 1178 for (int i = start, e = start + count, n = start * 2; i < e; ++i, n += 2) { 1179 float px = x + positions[n]; 1180 float py = y + positions[n+1]; 1181 1182 getGlyphStrike(i).appendGlyphOutline(glyphs[i], result, px, py); 1183 } 1184 1185 return result; 1186 } 1187 1188 private Rectangle getGlyphsPixelBounds(FontRenderContext frc, float x, float y, int start, int count) { 1189 initPositions(); // FIRST ensure we have positions based on our frctx 1190 1191 AffineTransform tx = null; 1192 if (frc == null || frc.equals(this.frc)) { 1193 tx = frctx; 1194 } else { 1195 tx = frc.getTransform(); 1196 } 1197 setDTX(tx); // need to get the right strikes, but we use tx itself to translate the points 1198 1199 if (gti != null) { 1200 return gti.getGlyphsPixelBounds(tx, x, y, start, count); 1201 } 1202 1203 FontStrike fs = getDefaultStrike().strike; 1204 Rectangle result = null; 1205 Rectangle r = new Rectangle(); 1206 Point2D.Float pt = new Point.Float(); 1207 int n = start * 2; 1208 while (--count >= 0) { 1209 pt.x = x + positions[n++]; 1210 pt.y = y + positions[n++]; 1211 tx.transform(pt, pt); 1212 fs.getGlyphImageBounds(glyphs[start++], pt, r); 1213 if (!r.isEmpty()) { 1214 if (result == null) { 1215 result = new Rectangle(r); 1216 } else { 1217 result.add(r); 1218 } 1219 } 1220 } 1221 return result != null ? result : r; 1222 } 1223 1224 private void clearCaches(int ix) { 1225 if (lbcacheRef != null) { 1226 Shape[] lbcache = (Shape[])lbcacheRef.get(); 1227 if (lbcache != null) { 1228 lbcache[ix] = null; 1229 } 1230 } 1231 1232 if (vbcacheRef != null) { 1233 Shape[] vbcache = (Shape[])vbcacheRef.get(); 1234 if (vbcache != null) { 1235 vbcache[ix] = null; 1236 } 1237 } 1238 } 1239 1240 private void clearCaches() { 1241 lbcacheRef = null; 1242 vbcacheRef = null; 1243 } 1244 1245 // internal use only for possible future extension 1246 1247 /** 1248 * A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses 1249 * a vertical baseline. 1250 */ 1251 public static final int FLAG_USES_VERTICAL_BASELINE = 128; 1252 1253 /** 1254 * A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses 1255 * vertical glyph metrics. A <code>GlyphVector</code> can use vertical metrics on a 1256 * horizontal line, or vice versa. 1257 */ 1258 public static final int FLAG_USES_VERTICAL_METRICS = 256; 1259 1260 /** 1261 * A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses 1262 * the 'alternate orientation.' Glyphs have a default orientation given a 1263 * particular baseline and metrics orientation, this is the orientation appropriate 1264 * for left-to-right text. For example, the letter 'A' can have four orientations, 1265 * with the point at 12, 3, 6, or 9 'o clock. The following table shows where the 1266 * point displays for different values of vertical baseline (vb), vertical 1267 * metrics (vm) and alternate orientation (fo):<br> 1268 * <blockquote> 1269 * vb vm ao 1270 * -- -- -- -- 1271 * f f f 12 ^ horizontal metrics on horizontal lines 1272 * f f t 6 v 1273 * f t f 9 < vertical metrics on horizontal lines 1274 * f t t 3 > 1275 * t f f 3 > horizontal metrics on vertical lines 1276 * t f t 9 < 1277 * t t f 12 ^ vertical metrics on vertical lines 1278 * t t t 6 v 1279 * </blockquote> 1280 */ 1281 public static final int FLAG_USES_ALTERNATE_ORIENTATION = 512; 1282 1283 1284 /** 1285 * Ensure that the positions array exists and holds position data. 1286 * If the array is null, this allocates it and sets default positions. 1287 */ 1288 private void initPositions() { 1289 if (positions == null) { 1290 setFRCTX(); 1291 1292 positions = new float[glyphs.length * 2 + 2]; 1293 1294 Point2D.Float trackPt = null; 1295 float track = getTracking(font); 1296 if (track != 0) { 1297 track *= font.getSize2D(); 1298 trackPt = new Point2D.Float(track, 0); // advance delta 1299 } 1300 1301 Point2D.Float pt = new Point2D.Float(0, 0); 1302 if (font.isTransformed()) { 1303 AffineTransform at = font.getTransform(); 1304 at.transform(pt, pt); 1305 positions[0] = pt.x; 1306 positions[1] = pt.y; 1307 1308 if (trackPt != null) { 1309 at.deltaTransform(trackPt, trackPt); 1310 } 1311 } 1312 for (int i = 0, n = 2; i < glyphs.length; ++i, n += 2) { 1313 getGlyphStrike(i).addDefaultGlyphAdvance(glyphs[i], pt); 1314 if (trackPt != null) { 1315 pt.x += trackPt.x; 1316 pt.y += trackPt.y; 1317 } 1318 positions[n] = pt.x; 1319 positions[n+1] = pt.y; 1320 } 1321 } 1322 } 1323 1324 /** 1325 * OR newFlags with existing flags. First computes existing flags if needed. 1326 */ 1327 private void addFlags(int newflags) { 1328 flags = getLayoutFlags() | newflags; 1329 } 1330 1331 /** 1332 * AND the complement of clearedFlags with existing flags. First computes existing flags if needed. 1333 */ 1334 private void clearFlags(int clearedFlags) { 1335 flags = getLayoutFlags() & ~clearedFlags; 1336 } 1337 1338 // general utility methods 1339 1340 // encapsulate the test to check whether we have per-glyph transforms 1341 private GlyphStrike getGlyphStrike(int ix) { 1342 if (gti == null) { 1343 return getDefaultStrike(); 1344 } else { 1345 return gti.getStrike(ix); 1346 } 1347 } 1348 1349 // encapsulate access to cached default glyph strike 1350 private GlyphStrike getDefaultStrike() { 1351 GlyphStrike gs = null; 1352 if (fsref != null) { 1353 gs = (GlyphStrike)fsref.get(); 1354 } 1355 if (gs == null) { 1356 gs = GlyphStrike.create(this, dtx, null); 1357 fsref = new SoftReference(gs); 1358 } 1359 return gs; 1360 } 1361 1362 1363 ///////////////////// 1364 // Internal utility classes 1365 ///////////////////// 1366 1367 // !!! I have this as a separate class instead of just inside SGV, 1368 // but I previously didn't bother. Now I'm trying this again. 1369 // Probably still not worth it, but I'd like to keep sgv's small in the common case. 1370 1371 static final class GlyphTransformInfo { 1372 StandardGlyphVector sgv; // reference back to glyph vector - yuck 1373 int[] indices; // index into unique strikes 1374 double[] transforms; // six doubles per unique transform, because AT is a pain to manipulate 1375 SoftReference strikesRef; // ref to unique strikes, one per transform 1376 boolean haveAllStrikes; // true if the strike array has been filled by getStrikes(). 1377 1378 // used when first setting a transform 1379 GlyphTransformInfo(StandardGlyphVector sgv) { 1380 this.sgv = sgv; 1381 } 1382 1383 // used when cloning a glyph vector, need to set back link 1384 GlyphTransformInfo(StandardGlyphVector sgv, GlyphTransformInfo rhs) { 1385 this.sgv = sgv; 1386 1387 this.indices = rhs.indices == null ? null : (int[])rhs.indices.clone(); 1388 this.transforms = rhs.transforms == null ? null : (double[])rhs.transforms.clone(); 1389 this.strikesRef = null; // can't share cache, so rather than clone, we just null out 1390 } 1391 1392 // used in sgv equality 1393 public boolean equals(GlyphTransformInfo rhs) { 1394 if (rhs == null) { 1395 return false; 1396 } 1397 if (rhs == this) { 1398 return true; 1399 } 1400 if (this.indices.length != rhs.indices.length) { 1401 return false; 1402 } 1403 if (this.transforms.length != rhs.transforms.length) { 1404 return false; 1405 } 1406 1407 // slow since we end up processing the same transforms multiple 1408 // times, but since transforms can be in any order, we either do 1409 // this or create a mapping. Equality tests aren't common so 1410 // leave it like this. 1411 for (int i = 0; i < this.indices.length; ++i) { 1412 int tix = this.indices[i]; 1413 int rix = rhs.indices[i]; 1414 if ((tix == 0) != (rix == 0)) { 1415 return false; 1416 } 1417 if (tix != 0) { 1418 tix *= 6; 1419 rix *= 6; 1420 for (int j = 6; j > 0; --j) { 1421 if (this.indices[--tix] != rhs.indices[--rix]) { 1422 return false; 1423 } 1424 } 1425 } 1426 } 1427 return true; 1428 } 1429 1430 // implements sgv.setGlyphTransform 1431 void setGlyphTransform(int glyphIndex, AffineTransform newTX) { 1432 1433 // we store all the glyph transforms as a double array, and for each glyph there 1434 // is an entry in the txIndices array indicating which transform to use. 0 means 1435 // there's no transform, 1 means use the first transform (the 6 doubles at offset 1436 // 0), 2 means use the second transform (the 6 doubles at offset 6), etc. 1437 // 1438 // Since this can be called multiple times, and since the number of transforms 1439 // affects the time it takes to construct the glyphs, we try to keep the arrays as 1440 // compact as possible, by removing transforms that are no longer used, and reusing 1441 // transforms where we already have them. 1442 1443 double[] temp = new double[6]; 1444 boolean isIdentity = true; 1445 if (newTX == null || newTX.isIdentity()) { 1446 // Fill in temp 1447 temp[0] = temp[3] = 1.0; 1448 } 1449 else { 1450 isIdentity = false; 1451 newTX.getMatrix(temp); 1452 } 1453 1454 if (indices == null) { 1455 if (isIdentity) { // no change 1456 return; 1457 } 1458 1459 indices = new int[sgv.glyphs.length]; 1460 indices[glyphIndex] = 1; 1461 transforms = temp; 1462 } else { 1463 boolean addSlot = false; // assume we're not growing 1464 int newIndex = -1; 1465 if (isIdentity) { 1466 newIndex = 0; // might shrink 1467 } else { 1468 addSlot = true; // assume no match 1469 int i; 1470 loop: 1471 for (i = 0; i < transforms.length; i += 6) { 1472 for (int j = 0; j < 6; ++j) { 1473 if (transforms[i + j] != temp[j]) { 1474 continue loop; 1475 } 1476 } 1477 addSlot = false; 1478 break; 1479 } 1480 newIndex = i / 6 + 1; // if no match, end of list 1481 } 1482 1483 // if we're using the same transform, nothing to do 1484 int oldIndex = indices[glyphIndex]; 1485 if (newIndex != oldIndex) { 1486 // see if we are removing last use of the old slot 1487 boolean removeSlot = false; 1488 if (oldIndex != 0) { 1489 removeSlot = true; 1490 for (int i = 0; i < indices.length; ++i) { 1491 if (indices[i] == oldIndex && i != glyphIndex) { 1492 removeSlot = false; 1493 break; 1494 } 1495 } 1496 } 1497 1498 if (removeSlot && addSlot) { // reuse old slot with new transform 1499 newIndex = oldIndex; 1500 System.arraycopy(temp, 0, transforms, (newIndex - 1) * 6, 6); 1501 } else if (removeSlot) { 1502 if (transforms.length == 6) { // removing last one, so clear arrays 1503 indices = null; 1504 transforms = null; 1505 1506 sgv.clearCaches(glyphIndex); 1507 sgv.clearFlags(FLAG_HAS_TRANSFORMS); 1508 strikesRef = null; 1509 1510 return; 1511 } 1512 1513 double[] ttemp = new double[transforms.length - 6]; 1514 System.arraycopy(transforms, 0, ttemp, 0, (oldIndex - 1) * 6); 1515 System.arraycopy(transforms, oldIndex * 6, ttemp, (oldIndex - 1) * 6, 1516 transforms.length - oldIndex * 6); 1517 transforms = ttemp; 1518 1519 // clean up indices 1520 for (int i = 0; i < indices.length; ++i) { 1521 if (indices[i] > oldIndex) { // ignore == oldIndex, it's going away 1522 indices[i] -= 1; 1523 } 1524 } 1525 if (newIndex > oldIndex) { // don't forget to decrement this too if we need to 1526 --newIndex; 1527 } 1528 } else if (addSlot) { 1529 double[] ttemp = new double[transforms.length + 6]; 1530 System.arraycopy(transforms, 0, ttemp, 0, transforms.length); 1531 System.arraycopy(temp, 0, ttemp, transforms.length, 6); 1532 transforms = ttemp; 1533 } 1534 1535 indices[glyphIndex] = newIndex; 1536 } 1537 } 1538 1539 sgv.clearCaches(glyphIndex); 1540 sgv.addFlags(FLAG_HAS_TRANSFORMS); 1541 strikesRef = null; 1542 } 1543 1544 // implements sgv.getGlyphTransform 1545 AffineTransform getGlyphTransform(int ix) { 1546 int index = indices[ix]; 1547 if (index == 0) { 1548 return null; 1549 } 1550 1551 int x = (index - 1) * 6; 1552 return new AffineTransform(transforms[x + 0], 1553 transforms[x + 1], 1554 transforms[x + 2], 1555 transforms[x + 3], 1556 transforms[x + 4], 1557 transforms[x + 5]); 1558 } 1559 1560 int transformCount() { 1561 if (transforms == null) { 1562 return 0; 1563 } 1564 return transforms.length / 6; 1565 } 1566 1567 /** 1568 * The strike cache works like this. 1569 * 1570 * -Each glyph is thought of as having a transform, usually identity. 1571 * -Each request for a strike is based on a device transform, either the 1572 * one in the frc or the rendering transform. 1573 * -For general info, strikes are held with soft references. 1574 * -When rendering, strikes must be held with hard references for the 1575 * duration of the rendering call. GlyphList will have to hold this 1576 * info along with the image and position info, but toss the strike info 1577 * when done. 1578 * -Build the strike cache as needed. If the dev transform we want to use 1579 * has changed from the last time it is built, the cache is flushed by 1580 * the caller before these methods are called. 1581 * 1582 * Use a tx that doesn't include translation components of dst tx. 1583 */ 1584 Object setupGlyphImages(long[] images, float[] positions, AffineTransform tx) { 1585 int len = sgv.glyphs.length; 1586 1587 GlyphStrike[] sl = getAllStrikes(); 1588 for (int i = 0; i < len; ++i) { 1589 GlyphStrike gs = sl[indices[i]]; 1590 int glyphID = sgv.glyphs[i]; 1591 images[i] = gs.strike.getGlyphImagePtr(glyphID); 1592 1593 gs.getGlyphPosition(glyphID, i*2, sgv.positions, positions); 1594 } 1595 tx.transform(positions, 0, positions, 0, len); 1596 1597 return sl; 1598 } 1599 1600 Rectangle getGlyphsPixelBounds(AffineTransform tx, float x, float y, int start, int count) { 1601 Rectangle result = null; 1602 Rectangle r = new Rectangle(); 1603 Point2D.Float pt = new Point.Float(); 1604 int n = start * 2; 1605 while (--count >= 0) { 1606 GlyphStrike gs = getStrike(start); 1607 pt.x = x + sgv.positions[n++] + gs.dx; 1608 pt.y = y + sgv.positions[n++] + gs.dy; 1609 tx.transform(pt, pt); 1610 gs.strike.getGlyphImageBounds(sgv.glyphs[start++], pt, r); 1611 if (!r.isEmpty()) { 1612 if (result == null) { 1613 result = new Rectangle(r); 1614 } else { 1615 result.add(r); 1616 } 1617 } 1618 } 1619 return result != null ? result : r; 1620 } 1621 1622 GlyphStrike getStrike(int glyphIndex) { 1623 if (indices != null) { 1624 GlyphStrike[] strikes = getStrikeArray(); 1625 return getStrikeAtIndex(strikes, indices[glyphIndex]); 1626 } 1627 return sgv.getDefaultStrike(); 1628 } 1629 1630 private GlyphStrike[] getAllStrikes() { 1631 if (indices == null) { 1632 return null; 1633 } 1634 1635 GlyphStrike[] strikes = getStrikeArray(); 1636 if (!haveAllStrikes) { 1637 for (int i = 0; i < strikes.length; ++i) { 1638 getStrikeAtIndex(strikes, i); 1639 } 1640 haveAllStrikes = true; 1641 } 1642 1643 return strikes; 1644 } 1645 1646 private GlyphStrike[] getStrikeArray() { 1647 GlyphStrike[] strikes = null; 1648 if (strikesRef != null) { 1649 strikes = (GlyphStrike[])strikesRef.get(); 1650 } 1651 if (strikes == null) { 1652 haveAllStrikes = false; 1653 strikes = new GlyphStrike[transformCount() + 1]; 1654 strikesRef = new SoftReference(strikes); 1655 } 1656 1657 return strikes; 1658 } 1659 1660 private GlyphStrike getStrikeAtIndex(GlyphStrike[] strikes, int strikeIndex) { 1661 GlyphStrike strike = strikes[strikeIndex]; 1662 if (strike == null) { 1663 if (strikeIndex == 0) { 1664 strike = sgv.getDefaultStrike(); 1665 } else { 1666 int ix = (strikeIndex - 1) * 6; 1667 AffineTransform gtx = new AffineTransform(transforms[ix], 1668 transforms[ix+1], 1669 transforms[ix+2], 1670 transforms[ix+3], 1671 transforms[ix+4], 1672 transforms[ix+5]); 1673 1674 strike = GlyphStrike.create(sgv, sgv.dtx, gtx); 1675 } 1676 strikes[strikeIndex] = strike; 1677 } 1678 return strike; 1679 } 1680 } 1681 1682 // This adjusts the metrics by the translation components of the glyph 1683 // transform. It is done here since the translation is not known by the 1684 // strike. 1685 // It adjusts the position of the image and the advance. 1686 1687 public static final class GlyphStrike { 1688 StandardGlyphVector sgv; 1689 FontStrike strike; // hard reference 1690 float dx; 1691 float dy; 1692 1693 static GlyphStrike create(StandardGlyphVector sgv, AffineTransform dtx, AffineTransform gtx) { 1694 float dx = 0; 1695 float dy = 0; 1696 1697 AffineTransform tx = sgv.ftx; 1698 if (!dtx.isIdentity() || gtx != null) { 1699 tx = new AffineTransform(sgv.ftx); 1700 if (gtx != null) { 1701 tx.preConcatenate(gtx); 1702 dx = (float)tx.getTranslateX(); // uses ftx then gtx to get translation 1703 dy = (float)tx.getTranslateY(); 1704 } 1705 if (!dtx.isIdentity()) { 1706 tx.preConcatenate(dtx); 1707 } 1708 } 1709 1710 int ptSize = 1; // only matters for 'gasp' case. 1711 Object aaHint = sgv.frc.getAntiAliasingHint(); 1712 if (aaHint == VALUE_TEXT_ANTIALIAS_GASP) { 1713 /* Must pass in the calculated point size for rendering. 1714 * If the glyph tx is anything other than identity or a 1715 * simple translate, calculate the transformed point size. 1716 */ 1717 if (!tx.isIdentity() && 1718 (tx.getType() & ~AffineTransform.TYPE_TRANSLATION) != 0) { 1719 double shearx = tx.getShearX(); 1720 if (shearx != 0) { 1721 double scaley = tx.getScaleY(); 1722 ptSize = 1723 (int)Math.sqrt(shearx * shearx + scaley * scaley); 1724 } else { 1725 ptSize = (int)(Math.abs(tx.getScaleY())); 1726 } 1727 } 1728 } 1729 int aa = FontStrikeDesc.getAAHintIntVal(aaHint,sgv.font2D, ptSize); 1730 int fm = FontStrikeDesc.getFMHintIntVal 1731 (sgv.frc.getFractionalMetricsHint()); 1732 FontStrikeDesc desc = new FontStrikeDesc(dtx, 1733 tx, 1734 sgv.font.getStyle(), 1735 aa, fm); 1736 1737 FontStrike strike = sgv.font2D.getStrike(desc); // !!! getStrike(desc, false) 1738 1739 return new GlyphStrike(sgv, strike, dx, dy); 1740 } 1741 1742 private GlyphStrike(StandardGlyphVector sgv, FontStrike strike, float dx, float dy) { 1743 this.sgv = sgv; 1744 this.strike = strike; 1745 this.dx = dx; 1746 this.dy = dy; 1747 } 1748 1749 void getADL(ADL result) { 1750 StrikeMetrics sm = strike.getFontMetrics(); 1751 Point2D.Float delta = null; 1752 if (sgv.font.isTransformed()) { 1753 delta = new Point2D.Float(); 1754 delta.x = (float)sgv.font.getTransform().getTranslateX(); 1755 delta.y = (float)sgv.font.getTransform().getTranslateY(); 1756 } 1757 1758 result.ascentX = -sm.ascentX; 1759 result.ascentY = -sm.ascentY; 1760 result.descentX = sm.descentX; 1761 result.descentY = sm.descentY; 1762 result.leadingX = sm.leadingX; 1763 result.leadingY = sm.leadingY; 1764 } 1765 1766 void getGlyphPosition(int glyphID, int ix, float[] positions, float[] result) { 1767 result[ix] = positions[ix] + dx; 1768 ++ix; 1769 result[ix] = positions[ix] + dy; 1770 } 1771 1772 void addDefaultGlyphAdvance(int glyphID, Point2D.Float result) { 1773 // !!! change this API? Creates unnecessary garbage. Also the name doesn't quite fit. 1774 // strike.addGlyphAdvance(Point2D.Float adv); // hey, whaddya know, matches my api :-) 1775 Point2D.Float adv = strike.getGlyphMetrics(glyphID); 1776 result.x += adv.x + dx; 1777 result.y += adv.y + dy; 1778 } 1779 1780 Rectangle2D getGlyphOutlineBounds(int glyphID, float x, float y) { 1781 Rectangle2D result = null; 1782 if (sgv.invdtx == null) { 1783 result = new Rectangle2D.Float(); 1784 result.setRect(strike.getGlyphOutlineBounds(glyphID)); // don't mutate cached rect 1785 } else { 1786 GeneralPath gp = strike.getGlyphOutline(glyphID, 0, 0); 1787 gp.transform(sgv.invdtx); 1788 result = gp.getBounds2D(); 1789 } 1790 result.setRect(result.getMinX() + x + dx, result.getMinY() + y + dy, 1791 result.getWidth(), result.getHeight()); 1792 return result; 1793 } 1794 1795 void appendGlyphOutline(int glyphID, GeneralPath result, float x, float y) { 1796 // !!! fontStrike needs a method for this. For that matter, GeneralPath does. 1797 GeneralPath gp = null; 1798 if (sgv.invdtx == null) { 1799 gp = strike.getGlyphOutline(glyphID, x + dx, y + dy); 1800 } else { 1801 gp = strike.getGlyphOutline(glyphID, 0, 0); 1802 gp.transform(sgv.invdtx); 1803 gp.transform(AffineTransform.getTranslateInstance(x + dx, y + dy)); 1804 } 1805 PathIterator iterator = gp.getPathIterator(null); 1806 result.append(iterator, false); 1807 } 1808 } 1809 1810 public String toString() { 1811 return appendString(null).toString(); 1812 } 1813 1814 StringBuffer appendString(StringBuffer buf) { 1815 if (buf == null) { 1816 buf = new StringBuffer(); 1817 } 1818 try { 1819 buf.append("SGV{font: "); 1820 buf.append(font.toString()); 1821 buf.append(", frc: "); 1822 buf.append(frc.toString()); 1823 buf.append(", glyphs: ("); 1824 buf.append(glyphs.length); 1825 buf.append(")["); 1826 for (int i = 0; i < glyphs.length; ++i) { 1827 if (i > 0) { 1828 buf.append(", "); 1829 } 1830 buf.append(Integer.toHexString(glyphs[i])); 1831 } 1832 buf.append("]"); 1833 if (positions != null) { 1834 buf.append(", positions: ("); 1835 buf.append(positions.length); 1836 buf.append(")["); 1837 for (int i = 0; i < positions.length; i += 2) { 1838 if (i > 0) { 1839 buf.append(", "); 1840 } 1841 buf.append(positions[i]); 1842 buf.append("@"); 1843 buf.append(positions[i+1]); 1844 } 1845 buf.append("]"); 1846 } 1847 if (charIndices != null) { 1848 buf.append(", indices: ("); 1849 buf.append(charIndices.length); 1850 buf.append(")["); 1851 for (int i = 0; i < charIndices.length; ++i) { 1852 if (i > 0) { 1853 buf.append(", "); 1854 } 1855 buf.append(charIndices[i]); 1856 } 1857 buf.append("]"); 1858 } 1859 buf.append(", flags:"); 1860 if (getLayoutFlags() == 0) { 1861 buf.append(" default"); 1862 } else { 1863 if ((flags & FLAG_HAS_TRANSFORMS) != 0) { 1864 buf.append(" tx"); 1865 } 1866 if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) { 1867 buf.append(" pos"); 1868 } 1869 if ((flags & FLAG_RUN_RTL) != 0) { 1870 buf.append(" rtl"); 1871 } 1872 if ((flags & FLAG_COMPLEX_GLYPHS) != 0) { 1873 buf.append(" complex"); 1874 } 1875 } 1876 } 1877 catch(Exception e) { 1878 buf.append(" " + e.getMessage()); 1879 } 1880 buf.append("}"); 1881 1882 return buf; 1883 } 1884 1885 static class ADL { 1886 public float ascentX; 1887 public float ascentY; 1888 public float descentX; 1889 public float descentY; 1890 public float leadingX; 1891 public float leadingY; 1892 1893 public String toString() { 1894 return toStringBuffer(null).toString(); 1895 } 1896 1897 protected StringBuffer toStringBuffer(StringBuffer result) { 1898 if (result == null) { 1899 result = new StringBuffer(); 1900 } 1901 result.append("ax: "); 1902 result.append(ascentX); 1903 result.append(" ay: "); 1904 result.append(ascentY); 1905 result.append(" dx: "); 1906 result.append(descentX); 1907 result.append(" dy: "); 1908 result.append(descentY); 1909 result.append(" lx: "); 1910 result.append(leadingX); 1911 result.append(" ly: "); 1912 result.append(leadingY); 1913 1914 return result; 1915 } 1916 } 1917 }