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