1 /* 2 * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 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<GlyphStrike> 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 = 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<Shape[]> 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 = 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<Shape[]> 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 = 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 = 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 = lbcacheRef.get(); 1234 if (lbcache != null) { 1235 lbcache[ix] = null; 1236 } 1237 } 1238 1239 if (vbcacheRef != null) { 1240 Shape[] vbcache = 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 = 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<GlyphStrike[]> 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 : rhs.indices.clone(); 1395 this.transforms = rhs.transforms == null ? null : 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 = 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 // Get the strike via the handle. Shouldn't matter 1744 // if we've invalidated the font but its an extra precaution. 1745 FontStrike strike = sgv.font2D.handle.font2D.getStrike(desc); // !!! getStrike(desc, false) 1746 1747 return new GlyphStrike(sgv, strike, dx, dy); 1748 } 1749 1750 private GlyphStrike(StandardGlyphVector sgv, FontStrike strike, float dx, float dy) { 1751 this.sgv = sgv; 1752 this.strike = strike; 1753 this.dx = dx; 1754 this.dy = dy; 1755 } 1756 1757 void getADL(ADL result) { 1758 StrikeMetrics sm = strike.getFontMetrics(); 1759 Point2D.Float delta = null; 1760 if (sgv.font.isTransformed()) { 1761 delta = new Point2D.Float(); 1762 delta.x = (float)sgv.font.getTransform().getTranslateX(); 1763 delta.y = (float)sgv.font.getTransform().getTranslateY(); 1764 } 1765 1766 result.ascentX = -sm.ascentX; 1767 result.ascentY = -sm.ascentY; 1768 result.descentX = sm.descentX; 1769 result.descentY = sm.descentY; 1770 result.leadingX = sm.leadingX; 1771 result.leadingY = sm.leadingY; 1772 } 1773 1774 void getGlyphPosition(int glyphID, int ix, float[] positions, float[] result) { 1775 result[ix] = positions[ix] + dx; 1776 ++ix; 1777 result[ix] = positions[ix] + dy; 1778 } 1779 1780 void addDefaultGlyphAdvance(int glyphID, Point2D.Float result) { 1781 // !!! change this API? Creates unnecessary garbage. Also the name doesn't quite fit. 1782 // strike.addGlyphAdvance(Point2D.Float adv); // hey, whaddya know, matches my api :-) 1783 Point2D.Float adv = strike.getGlyphMetrics(glyphID); 1784 result.x += adv.x + dx; 1785 result.y += adv.y + dy; 1786 } 1787 1788 Rectangle2D getGlyphOutlineBounds(int glyphID, float x, float y) { 1789 Rectangle2D result = null; 1790 if (sgv.invdtx == null) { 1791 result = new Rectangle2D.Float(); 1792 result.setRect(strike.getGlyphOutlineBounds(glyphID)); // don't mutate cached rect 1793 } else { 1794 GeneralPath gp = strike.getGlyphOutline(glyphID, 0, 0); 1795 gp.transform(sgv.invdtx); 1796 result = gp.getBounds2D(); 1797 } 1798 /* Since x is the logical advance of the glyph to this point. 1799 * Because of the way that Rectangle.union is specified, this 1800 * means that subsequent unioning of a rect including that 1801 * will be affected, even if the glyph is empty. So skip such 1802 * cases. This alone isn't a complete solution since x==0 1803 * may also not be what is wanted. The code that does the 1804 * unioning also needs to be aware to ignore empty glyphs. 1805 */ 1806 if (!result.isEmpty()) { 1807 result.setRect(result.getMinX() + x + dx, 1808 result.getMinY() + y + dy, 1809 result.getWidth(), result.getHeight()); 1810 } 1811 return result; 1812 } 1813 1814 void appendGlyphOutline(int glyphID, GeneralPath result, float x, float y) { 1815 // !!! fontStrike needs a method for this. For that matter, GeneralPath does. 1816 GeneralPath gp = null; 1817 if (sgv.invdtx == null) { 1818 gp = strike.getGlyphOutline(glyphID, x + dx, y + dy); 1819 } else { 1820 gp = strike.getGlyphOutline(glyphID, 0, 0); 1821 gp.transform(sgv.invdtx); 1822 gp.transform(AffineTransform.getTranslateInstance(x + dx, y + dy)); 1823 } 1824 PathIterator iterator = gp.getPathIterator(null); 1825 result.append(iterator, false); 1826 } 1827 } 1828 1829 public String toString() { 1830 return appendString(null).toString(); 1831 } 1832 1833 StringBuffer appendString(StringBuffer buf) { 1834 if (buf == null) { 1835 buf = new StringBuffer(); 1836 } 1837 try { 1838 buf.append("SGV{font: "); 1839 buf.append(font.toString()); 1840 buf.append(", frc: "); 1841 buf.append(frc.toString()); 1842 buf.append(", glyphs: ("); 1843 buf.append(glyphs.length); 1844 buf.append(")["); 1845 for (int i = 0; i < glyphs.length; ++i) { 1846 if (i > 0) { 1847 buf.append(", "); 1848 } 1849 buf.append(Integer.toHexString(glyphs[i])); 1850 } 1851 buf.append("]"); 1852 if (positions != null) { 1853 buf.append(", positions: ("); 1854 buf.append(positions.length); 1855 buf.append(")["); 1856 for (int i = 0; i < positions.length; i += 2) { 1857 if (i > 0) { 1858 buf.append(", "); 1859 } 1860 buf.append(positions[i]); 1861 buf.append("@"); 1862 buf.append(positions[i+1]); 1863 } 1864 buf.append("]"); 1865 } 1866 if (charIndices != null) { 1867 buf.append(", indices: ("); 1868 buf.append(charIndices.length); 1869 buf.append(")["); 1870 for (int i = 0; i < charIndices.length; ++i) { 1871 if (i > 0) { 1872 buf.append(", "); 1873 } 1874 buf.append(charIndices[i]); 1875 } 1876 buf.append("]"); 1877 } 1878 buf.append(", flags:"); 1879 if (getLayoutFlags() == 0) { 1880 buf.append(" default"); 1881 } else { 1882 if ((flags & FLAG_HAS_TRANSFORMS) != 0) { 1883 buf.append(" tx"); 1884 } 1885 if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) { 1886 buf.append(" pos"); 1887 } 1888 if ((flags & FLAG_RUN_RTL) != 0) { 1889 buf.append(" rtl"); 1890 } 1891 if ((flags & FLAG_COMPLEX_GLYPHS) != 0) { 1892 buf.append(" complex"); 1893 } 1894 } 1895 } 1896 catch(Exception e) { 1897 buf.append(" " + e.getMessage()); 1898 } 1899 buf.append("}"); 1900 1901 return buf; 1902 } 1903 1904 static class ADL { 1905 public float ascentX; 1906 public float ascentY; 1907 public float descentX; 1908 public float descentY; 1909 public float leadingX; 1910 public float leadingY; 1911 1912 public String toString() { 1913 return toStringBuffer(null).toString(); 1914 } 1915 1916 protected StringBuffer toStringBuffer(StringBuffer result) { 1917 if (result == null) { 1918 result = new StringBuffer(); 1919 } 1920 result.append("ax: "); 1921 result.append(ascentX); 1922 result.append(" ay: "); 1923 result.append(ascentY); 1924 result.append(" dx: "); 1925 result.append(descentX); 1926 result.append(" dy: "); 1927 result.append(descentY); 1928 result.append(" lx: "); 1929 result.append(leadingX); 1930 result.append(" ly: "); 1931 result.append(leadingY); 1932 1933 return result; 1934 } 1935 } 1936 }