1 /* 2 * Copyright (c) 1998, 2017, 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 StandardGlyphVector(Font font, String str, FontRenderContext frc)162 public StandardGlyphVector(Font font, String str, FontRenderContext frc) { 163 init(font, str.toCharArray(), 0, str.length(), frc, UNINITIALIZED_FLAGS); 164 } 165 StandardGlyphVector(Font font, char[] text, FontRenderContext frc)166 public StandardGlyphVector(Font font, char[] text, FontRenderContext frc) { 167 init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS); 168 } 169 StandardGlyphVector(Font font, char[] text, int start, int count, FontRenderContext frc)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 getTracking(Font font)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 StandardGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions, int[] indices, int flags)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 initGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions, int[] indices, int flags)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 StandardGlyphVector(Font font, CharacterIterator iter, FontRenderContext frc)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 StandardGlyphVector(Font font, int[] glyphs, FontRenderContext frc)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 */ getStandardGV(GlyphVector gv, FontInfo info)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 getFont()290 public Font getFont() { 291 return this.font; 292 } 293 getFontRenderContext()294 public FontRenderContext getFontRenderContext() { 295 return this.frc; 296 } 297 performDefaultLayout()298 public void performDefaultLayout() { 299 positions = null; 300 if (getTracking(font) == 0) { 301 clearFlags(FLAG_HAS_POSITION_ADJUSTMENTS); 302 } 303 } 304 getNumGlyphs()305 public int getNumGlyphs() { 306 return glyphs.length; 307 } 308 getGlyphCode(int glyphIndex)309 public int getGlyphCode(int glyphIndex) { 310 return userGlyphs[glyphIndex]; 311 } 312 getGlyphCodes(int start, int count, int[] result)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 getGlyphCharIndex(int ix)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 getGlyphCharIndices(int start, int count, int[] result)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 getLogicalBounds()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 getVisualBounds()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 getPixelBounds(FontRenderContext renderFRC, float x, float y)418 public Rectangle getPixelBounds(FontRenderContext renderFRC, float x, float y) { 419 return getGlyphsPixelBounds(renderFRC, x, y, 0, glyphs.length); 420 } 421 getOutline()422 public Shape getOutline() { 423 return getGlyphsOutline(0, glyphs.length, 0, 0); 424 } 425 getOutline(float x, float y)426 public Shape getOutline(float x, float y) { 427 return getGlyphsOutline(0, glyphs.length, x, y); 428 } 429 430 // relative to gv origin getGlyphOutline(int ix)431 public Shape getGlyphOutline(int ix) { 432 return getGlyphsOutline(ix, 1, 0, 0); 433 } 434 435 // relative to gv origin offset by x, y getGlyphOutline(int ix, float x, float y)436 public Shape getGlyphOutline(int ix, float x, float y) { 437 return getGlyphsOutline(ix, 1, x, y); 438 } 439 getGlyphPosition(int ix)440 public Point2D getGlyphPosition(int ix) { 441 initPositions(); 442 443 ix *= 2; 444 return new Point2D.Float(positions[ix], positions[ix + 1]); 445 } 446 setGlyphPosition(int ix, Point2D pos)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 getGlyphTransform(int ix)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 setGlyphTransform(int ix, AffineTransform newTX)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 getLayoutFlags()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 getGlyphPositions(int start, int count, float[] result)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 getGlyphLogicalBounds(int ix)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 getGlyphVisualBounds(int ix)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 getGlyphPixelBounds(int index, FontRenderContext renderFRC, float x, float y)600 public Rectangle getGlyphPixelBounds(int index, FontRenderContext renderFRC, float x, float y) { 601 return getGlyphsPixelBounds(renderFRC, x, y, index, 1); 602 } 603 getGlyphMetrics(int ix)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 getGlyphJustificationInfo(int ix)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 equals(GlyphVector rhs)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 */ hashCode()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 */ equals(Object rhs)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 */ copy()719 public StandardGlyphVector copy() { 720 return (StandardGlyphVector)clone(); 721 } 722 723 /** 724 * As a concrete subclass of GlyphVector, this must implement clone. 725 */ clone()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 */ setGlyphPositions(float[] srcPositions, int srcStart, int start, int count)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 */ setGlyphPositions(float[] srcPositions)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 */ getGlyphPositions(float[] result)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 */ getGlyphTransforms(int start, int count, AffineTransform[] result)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 */ getGlyphTransforms()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 */ setGlyphTransforms(AffineTransform[] srcTransforms, int srcStart, int start, int count)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 */ setGlyphTransforms(AffineTransform[] srcTransforms)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 */ getGlyphInfo()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 // StandardGlyphVector package private methods 881 ///////////////////// 882 883 // used by glyphlist to determine if it needs to allocate/size positions array 884 // gti always uses positions because the gtx might have translation. We also 885 // need positions if the rendering dtx is different from the frctx. 886 needsPositions(double[] devTX)887 boolean needsPositions(double[] devTX) { 888 return gti != null || 889 (getLayoutFlags() & FLAG_HAS_POSITION_ADJUSTMENTS) != 0 || 890 !matchTX(devTX, frctx); 891 } 892 893 // used by glyphList to get strong refs to font strikes for duration of rendering call 894 // if devTX matches current devTX, we're ready to go 895 // if we don't have multiple transforms, we're already ok 896 897 // !!! I'm not sure fontInfo works so well for glyphvector, since we have to be able to handle 898 // the multiple-strikes case 899 900 /* 901 * GlyphList calls this to set up its images data. First it calls needsPositions, 902 * passing the devTX, to see if it should provide us a positions array to fill. 903 * It only doesn't need them if we're a simple glyph vector whose frctx matches the 904 * devtx. 905 * Then it calls setupGlyphImages. If we need positions, we make sure we have our 906 * default positions based on the frctx first. Then we set the devTX, and use 907 * strikes based on it to generate the images. Finally, we fill in the positions 908 * array. 909 * If we have transforms, we delegate to gti. It depends on our having first 910 * initialized the positions and devTX. 911 */ setupGlyphImages(long[] images, float[] positions, double[] devTX)912 Object setupGlyphImages(long[] images, float[] positions, double[] devTX) { 913 initPositions(); // FIRST ensure we have positions based on our frctx 914 setRenderTransform(devTX); // THEN make sure we are using the desired devTX 915 916 if (gti != null) { 917 return gti.setupGlyphImages(images, positions, dtx); 918 } 919 920 GlyphStrike gs = getDefaultStrike(); 921 gs.strike.getGlyphImagePtrs(glyphs, images, glyphs.length); 922 923 if (positions != null) { 924 if (dtx.isIdentity()) { 925 System.arraycopy(this.positions, 0, positions, 0, glyphs.length * 2); 926 } else { 927 dtx.transform(this.positions, 0, positions, 0, glyphs.length); 928 } 929 } 930 931 return gs; 932 } 933 934 ////////////////////// 935 // StandardGlyphVector private methods 936 ///////////////////// 937 938 // We keep translation in our frctx since getPixelBounds uses it. But 939 // GlyphList pulls out the translation and applies it separately, so 940 // we strip it out when we set the dtx. Basically nothing uses the 941 // translation except getPixelBounds. 942 943 // called by needsPositions, setRenderTransform matchTX(double[] lhs, AffineTransform rhs)944 private static boolean matchTX(double[] lhs, AffineTransform rhs) { 945 return 946 lhs[0] == rhs.getScaleX() && 947 lhs[1] == rhs.getShearY() && 948 lhs[2] == rhs.getShearX() && 949 lhs[3] == rhs.getScaleY(); 950 } 951 952 // returns new tx if old one has translation, otherwise returns old one getNonTranslateTX(AffineTransform tx)953 private static AffineTransform getNonTranslateTX(AffineTransform tx) { 954 if (tx.getTranslateX() != 0 || tx.getTranslateY() != 0) { 955 tx = new AffineTransform(tx.getScaleX(), tx.getShearY(), 956 tx.getShearX(), tx.getScaleY(), 957 0, 0); 958 } 959 return tx; 960 } 961 equalNonTranslateTX(AffineTransform lhs, AffineTransform rhs)962 private static boolean equalNonTranslateTX(AffineTransform lhs, AffineTransform rhs) { 963 return lhs.getScaleX() == rhs.getScaleX() && 964 lhs.getShearY() == rhs.getShearY() && 965 lhs.getShearX() == rhs.getShearX() && 966 lhs.getScaleY() == rhs.getScaleY(); 967 } 968 969 // called by setupGlyphImages (after needsPositions, so redundant match check?) setRenderTransform(double[] devTX)970 private void setRenderTransform(double[] devTX) { 971 assert(devTX.length == 4); 972 if (!matchTX(devTX, dtx)) { 973 resetDTX(new AffineTransform(devTX)); // no translation since devTX len == 4. 974 } 975 } 976 977 // called by getGlyphsPixelBounds setDTX(AffineTransform tx)978 private void setDTX(AffineTransform tx) { 979 if (!equalNonTranslateTX(dtx, tx)) { 980 resetDTX(getNonTranslateTX(tx)); 981 } 982 } 983 984 // called by most functions setFRCTX()985 private void setFRCTX() { 986 if (!equalNonTranslateTX(frctx, dtx)) { 987 resetDTX(getNonTranslateTX(frctx)); 988 } 989 } 990 991 /** 992 * Change the dtx for the strike refs we use. Keeps a reference to the at. At 993 * must not contain translation. 994 * Called by setRenderTransform, setDTX, initFontData. 995 */ resetDTX(AffineTransform at)996 private void resetDTX(AffineTransform at) { 997 fsref = null; 998 dtx = at; 999 invdtx = null; 1000 if (!dtx.isIdentity()) { 1001 try { 1002 invdtx = dtx.createInverse(); 1003 } 1004 catch (NoninvertibleTransformException e) { 1005 // we needn't care for rendering 1006 } 1007 } 1008 if (gti != null) { 1009 gti.strikesRef = null; 1010 } 1011 } 1012 1013 /** 1014 * Utility used by getStandardGV. 1015 * Constructs a StandardGlyphVector from a generic glyph vector. 1016 * Do not call this from new contexts without considering the comment 1017 * about "userGlyphs". 1018 */ StandardGlyphVector(GlyphVector gv, FontRenderContext frc)1019 private StandardGlyphVector(GlyphVector gv, FontRenderContext frc) { 1020 this.font = gv.getFont(); 1021 this.frc = frc; 1022 initFontData(); 1023 1024 int nGlyphs = gv.getNumGlyphs(); 1025 this.userGlyphs = gv.getGlyphCodes(0, nGlyphs, null); 1026 if (gv instanceof StandardGlyphVector) { 1027 /* userGlyphs will be OK because this is a private constructor 1028 * and the returned instance is used only for rendering. 1029 * It's not constructable by user code, nor returned to the 1030 * application. So we know "userGlyphs" are valid as having 1031 * been either already validated or are the result of layout. 1032 */ 1033 this.glyphs = userGlyphs; 1034 } else { 1035 this.glyphs = getValidatedGlyphs(this.userGlyphs); 1036 } 1037 this.flags = gv.getLayoutFlags() & FLAG_MASK; 1038 1039 if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) { 1040 this.positions = gv.getGlyphPositions(0, nGlyphs + 1, null); 1041 } 1042 1043 if ((flags & FLAG_COMPLEX_GLYPHS) != 0) { 1044 this.charIndices = gv.getGlyphCharIndices(0, nGlyphs, null); 1045 } 1046 1047 if ((flags & FLAG_HAS_TRANSFORMS) != 0) { 1048 AffineTransform[] txs = new AffineTransform[nGlyphs]; // worst case 1049 for (int i = 0; i < nGlyphs; ++i) { 1050 txs[i] = gv.getGlyphTransform(i); // gv doesn't have getGlyphsTransforms 1051 } 1052 1053 setGlyphTransforms(txs); 1054 } 1055 } 1056 1057 /* Before asking the Font we see if the glyph code is 1058 * FFFE or FFFF which are special values that we should be internally 1059 * ready to handle as meaning invisible glyphs. The Font would report 1060 * those as the missing glyph. 1061 */ getValidatedGlyphs(int[] oglyphs)1062 int[] getValidatedGlyphs(int[] oglyphs) { 1063 int len = oglyphs.length; 1064 int[] vglyphs = new int[len]; 1065 for (int i=0; i<len; i++) { 1066 if (oglyphs[i] == 0xFFFE || oglyphs[i] == 0xFFFF) { 1067 vglyphs[i] = oglyphs[i]; 1068 } else { 1069 vglyphs[i] = font2D.getValidatedGlyphCode(oglyphs[i]); 1070 } 1071 } 1072 return vglyphs; 1073 } 1074 1075 // utility used by constructors init(Font font, char[] text, int start, int count, FontRenderContext frc, int flags)1076 private void init(Font font, char[] text, int start, int count, 1077 FontRenderContext frc, int flags) { 1078 1079 if (start < 0 || count < 0 || start + count > text.length) { 1080 throw new ArrayIndexOutOfBoundsException("start or count out of bounds"); 1081 } 1082 1083 this.font = font; 1084 this.frc = frc; 1085 this.flags = flags; 1086 1087 if (getTracking(font) != 0) { 1088 addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); 1089 } 1090 1091 // !!! change mapper interface? 1092 if (start != 0) { 1093 char[] temp = new char[count]; 1094 System.arraycopy(text, start, temp, 0, count); 1095 text = temp; 1096 } 1097 1098 initFontData(); // sets up font2D 1099 1100 // !!! no layout for now, should add checks 1101 // !!! need to support creating a StandardGlyphVector from a TextMeasurer's info... 1102 glyphs = new int[count]; // hmmm 1103 /* Glyphs obtained here are already validated by the font */ 1104 userGlyphs = glyphs; 1105 font2D.getMapper().charsToGlyphs(count, text, glyphs); 1106 } 1107 initFontData()1108 private void initFontData() { 1109 font2D = FontUtilities.getFont2D(font); 1110 if (font2D instanceof FontSubstitution) { 1111 font2D = ((FontSubstitution)font2D).getCompositeFont2D(); 1112 } 1113 float s = font.getSize2D(); 1114 if (font.isTransformed()) { 1115 ftx = font.getTransform(); 1116 if (ftx.getTranslateX() != 0 || ftx.getTranslateY() != 0) { 1117 addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); 1118 } 1119 ftx.setTransform(ftx.getScaleX(), ftx.getShearY(), ftx.getShearX(), ftx.getScaleY(), 0, 0); 1120 ftx.scale(s, s); 1121 } else { 1122 ftx = AffineTransform.getScaleInstance(s, s); 1123 } 1124 1125 frctx = frc.getTransform(); 1126 resetDTX(getNonTranslateTX(frctx)); 1127 } 1128 1129 /** 1130 * Copy glyph position data into a result array starting at the indicated 1131 * offset in the array. If the passed-in result array is null, a new 1132 * array will be allocated and returned. 1133 * 1134 * This is an internal method and does no extra argument checking. 1135 * 1136 * @param start the index of the first glyph to get 1137 * @param count the number of glyphs to get 1138 * @param offset the offset into result at which to put the data 1139 * @param result an array to hold the x,y positions 1140 * @return the modified position array 1141 */ internalGetGlyphPositions(int start, int count, int offset, float[] result)1142 private float[] internalGetGlyphPositions(int start, int count, int offset, float[] result) { 1143 if (result == null) { 1144 result = new float[offset + count * 2]; 1145 } 1146 1147 initPositions(); 1148 1149 // System.arraycopy is slow for stuff like this 1150 for (int i = offset, e = offset + count * 2, p = start * 2; i < e; ++i, ++p) { 1151 result[i] = positions[p]; 1152 } 1153 1154 return result; 1155 } 1156 getGlyphOutlineBounds(int ix)1157 private Rectangle2D getGlyphOutlineBounds(int ix) { 1158 setFRCTX(); 1159 initPositions(); 1160 return getGlyphStrike(ix).getGlyphOutlineBounds(glyphs[ix], positions[ix*2], positions[ix*2+1]); 1161 } 1162 1163 /** 1164 * Used by getOutline, getGlyphsOutline 1165 */ getGlyphsOutline(int start, int count, float x, float y)1166 private Shape getGlyphsOutline(int start, int count, float x, float y) { 1167 setFRCTX(); 1168 initPositions(); 1169 1170 GeneralPath result = new GeneralPath(GeneralPath.WIND_NON_ZERO); 1171 for (int i = start, e = start + count, n = start * 2; i < e; ++i, n += 2) { 1172 float px = x + positions[n]; 1173 float py = y + positions[n+1]; 1174 1175 getGlyphStrike(i).appendGlyphOutline(glyphs[i], result, px, py); 1176 } 1177 1178 return result; 1179 } 1180 getGlyphsPixelBounds(FontRenderContext frc, float x, float y, int start, int count)1181 private Rectangle getGlyphsPixelBounds(FontRenderContext frc, float x, float y, int start, int count) { 1182 initPositions(); // FIRST ensure we have positions based on our frctx 1183 1184 AffineTransform tx = null; 1185 if (frc == null || frc.equals(this.frc)) { 1186 tx = frctx; 1187 } else { 1188 tx = frc.getTransform(); 1189 } 1190 setDTX(tx); // need to get the right strikes, but we use tx itself to translate the points 1191 1192 if (gti != null) { 1193 return gti.getGlyphsPixelBounds(tx, x, y, start, count); 1194 } 1195 1196 FontStrike fs = getDefaultStrike().strike; 1197 Rectangle result = null; 1198 Rectangle r = new Rectangle(); 1199 Point2D.Float pt = new Point.Float(); 1200 int n = start * 2; 1201 while (--count >= 0) { 1202 pt.x = x + positions[n++]; 1203 pt.y = y + positions[n++]; 1204 tx.transform(pt, pt); 1205 fs.getGlyphImageBounds(glyphs[start++], pt, r); 1206 if (!r.isEmpty()) { 1207 if (result == null) { 1208 result = new Rectangle(r); 1209 } else { 1210 result.add(r); 1211 } 1212 } 1213 } 1214 return result != null ? result : r; 1215 } 1216 clearCaches(int ix)1217 private void clearCaches(int ix) { 1218 if (lbcacheRef != null) { 1219 Shape[] lbcache = lbcacheRef.get(); 1220 if (lbcache != null) { 1221 lbcache[ix] = null; 1222 } 1223 } 1224 1225 if (vbcacheRef != null) { 1226 Shape[] vbcache = vbcacheRef.get(); 1227 if (vbcache != null) { 1228 vbcache[ix] = null; 1229 } 1230 } 1231 } 1232 clearCaches()1233 private void clearCaches() { 1234 lbcacheRef = null; 1235 vbcacheRef = null; 1236 } 1237 1238 // internal use only for possible future extension 1239 1240 /** 1241 * A flag used with getLayoutFlags that indicates whether this {@code GlyphVector} uses 1242 * a vertical baseline. 1243 */ 1244 public static final int FLAG_USES_VERTICAL_BASELINE = 128; 1245 1246 /** 1247 * A flag used with getLayoutFlags that indicates whether this {@code GlyphVector} uses 1248 * vertical glyph metrics. A {@code GlyphVector} can use vertical metrics on a 1249 * horizontal line, or vice versa. 1250 */ 1251 public static final int FLAG_USES_VERTICAL_METRICS = 256; 1252 1253 /** 1254 * A flag used with getLayoutFlags that indicates whether this {@code GlyphVector} uses 1255 * the 'alternate orientation.' Glyphs have a default orientation given a 1256 * particular baseline and metrics orientation, this is the orientation appropriate 1257 * for left-to-right text. For example, the letter 'A' can have four orientations, 1258 * with the point at 12, 3, 6, or 9 'o clock. The following table shows where the 1259 * point displays for different values of vertical baseline (vb), vertical 1260 * metrics (vm) and alternate orientation (fo):<br> 1261 * <blockquote> 1262 * vb vm ao 1263 * -- -- -- -- 1264 * f f f 12 ^ horizontal metrics on horizontal lines 1265 * f f t 6 v 1266 * f t f 9 < vertical metrics on horizontal lines 1267 * f t t 3 > 1268 * t f f 3 > horizontal metrics on vertical lines 1269 * t f t 9 < 1270 * t t f 12 ^ vertical metrics on vertical lines 1271 * t t t 6 v 1272 * </blockquote> 1273 */ 1274 public static final int FLAG_USES_ALTERNATE_ORIENTATION = 512; 1275 1276 1277 /** 1278 * Ensure that the positions array exists and holds position data. 1279 * If the array is null, this allocates it and sets default positions. 1280 */ initPositions()1281 private void initPositions() { 1282 if (positions == null) { 1283 setFRCTX(); 1284 1285 positions = new float[glyphs.length * 2 + 2]; 1286 1287 Point2D.Float trackPt = null; 1288 float track = getTracking(font); 1289 if (track != 0) { 1290 track *= font.getSize2D(); 1291 trackPt = new Point2D.Float(track, 0); // advance delta 1292 } 1293 1294 Point2D.Float pt = new Point2D.Float(0, 0); 1295 if (font.isTransformed()) { 1296 AffineTransform at = font.getTransform(); 1297 at.transform(pt, pt); 1298 positions[0] = pt.x; 1299 positions[1] = pt.y; 1300 1301 if (trackPt != null) { 1302 at.deltaTransform(trackPt, trackPt); 1303 } 1304 } 1305 for (int i = 0, n = 2; i < glyphs.length; ++i, n += 2) { 1306 getGlyphStrike(i).addDefaultGlyphAdvance(glyphs[i], pt); 1307 if (trackPt != null) { 1308 pt.x += trackPt.x; 1309 pt.y += trackPt.y; 1310 } 1311 positions[n] = pt.x; 1312 positions[n+1] = pt.y; 1313 } 1314 } 1315 } 1316 1317 /** 1318 * OR newFlags with existing flags. First computes existing flags if needed. 1319 */ addFlags(int newflags)1320 private void addFlags(int newflags) { 1321 flags = getLayoutFlags() | newflags; 1322 } 1323 1324 /** 1325 * AND the complement of clearedFlags with existing flags. First computes existing flags if needed. 1326 */ clearFlags(int clearedFlags)1327 private void clearFlags(int clearedFlags) { 1328 flags = getLayoutFlags() & ~clearedFlags; 1329 } 1330 1331 // general utility methods 1332 1333 // encapsulate the test to check whether we have per-glyph transforms getGlyphStrike(int ix)1334 private GlyphStrike getGlyphStrike(int ix) { 1335 if (gti == null) { 1336 return getDefaultStrike(); 1337 } else { 1338 return gti.getStrike(ix); 1339 } 1340 } 1341 1342 // encapsulate access to cached default glyph strike getDefaultStrike()1343 private GlyphStrike getDefaultStrike() { 1344 GlyphStrike gs = null; 1345 if (fsref != null) { 1346 gs = fsref.get(); 1347 } 1348 if (gs == null) { 1349 gs = GlyphStrike.create(this, dtx, null); 1350 fsref = new SoftReference<>(gs); 1351 } 1352 return gs; 1353 } 1354 1355 1356 ///////////////////// 1357 // Internal utility classes 1358 ///////////////////// 1359 1360 // !!! I have this as a separate class instead of just inside SGV, 1361 // but I previously didn't bother. Now I'm trying this again. 1362 // Probably still not worth it, but I'd like to keep sgv's small in the common case. 1363 1364 static final class GlyphTransformInfo { 1365 StandardGlyphVector sgv; // reference back to glyph vector - yuck 1366 int[] indices; // index into unique strikes 1367 double[] transforms; // six doubles per unique transform, because AT is a pain to manipulate 1368 SoftReference<GlyphStrike[]> strikesRef; // ref to unique strikes, one per transform 1369 boolean haveAllStrikes; // true if the strike array has been filled by getStrikes(). 1370 1371 // used when first setting a transform GlyphTransformInfo(StandardGlyphVector sgv)1372 GlyphTransformInfo(StandardGlyphVector sgv) { 1373 this.sgv = sgv; 1374 } 1375 1376 // used when cloning a glyph vector, need to set back link GlyphTransformInfo(StandardGlyphVector sgv, GlyphTransformInfo rhs)1377 GlyphTransformInfo(StandardGlyphVector sgv, GlyphTransformInfo rhs) { 1378 this.sgv = sgv; 1379 1380 this.indices = rhs.indices == null ? null : rhs.indices.clone(); 1381 this.transforms = rhs.transforms == null ? null : rhs.transforms.clone(); 1382 this.strikesRef = null; // can't share cache, so rather than clone, we just null out 1383 } 1384 1385 // used in sgv equality equals(GlyphTransformInfo rhs)1386 public boolean equals(GlyphTransformInfo rhs) { 1387 if (rhs == null) { 1388 return false; 1389 } 1390 if (rhs == this) { 1391 return true; 1392 } 1393 if (this.indices.length != rhs.indices.length) { 1394 return false; 1395 } 1396 if (this.transforms.length != rhs.transforms.length) { 1397 return false; 1398 } 1399 1400 // slow since we end up processing the same transforms multiple 1401 // times, but since transforms can be in any order, we either do 1402 // this or create a mapping. Equality tests aren't common so 1403 // leave it like this. 1404 for (int i = 0; i < this.indices.length; ++i) { 1405 int tix = this.indices[i]; 1406 int rix = rhs.indices[i]; 1407 if ((tix == 0) != (rix == 0)) { 1408 return false; 1409 } 1410 if (tix != 0) { 1411 tix *= 6; 1412 rix *= 6; 1413 for (int j = 6; j > 0; --j) { 1414 if (this.indices[--tix] != rhs.indices[--rix]) { 1415 return false; 1416 } 1417 } 1418 } 1419 } 1420 return true; 1421 } 1422 1423 // implements sgv.setGlyphTransform setGlyphTransform(int glyphIndex, AffineTransform newTX)1424 void setGlyphTransform(int glyphIndex, AffineTransform newTX) { 1425 1426 // we store all the glyph transforms as a double array, and for each glyph there 1427 // is an entry in the txIndices array indicating which transform to use. 0 means 1428 // there's no transform, 1 means use the first transform (the 6 doubles at offset 1429 // 0), 2 means use the second transform (the 6 doubles at offset 6), etc. 1430 // 1431 // Since this can be called multiple times, and since the number of transforms 1432 // affects the time it takes to construct the glyphs, we try to keep the arrays as 1433 // compact as possible, by removing transforms that are no longer used, and reusing 1434 // transforms where we already have them. 1435 1436 double[] temp = new double[6]; 1437 boolean isIdentity = true; 1438 if (newTX == null || newTX.isIdentity()) { 1439 // Fill in temp 1440 temp[0] = temp[3] = 1.0; 1441 } 1442 else { 1443 isIdentity = false; 1444 newTX.getMatrix(temp); 1445 } 1446 1447 if (indices == null) { 1448 if (isIdentity) { // no change 1449 return; 1450 } 1451 1452 indices = new int[sgv.glyphs.length]; 1453 indices[glyphIndex] = 1; 1454 transforms = temp; 1455 } else { 1456 boolean addSlot = false; // assume we're not growing 1457 int newIndex = -1; 1458 if (isIdentity) { 1459 newIndex = 0; // might shrink 1460 } else { 1461 addSlot = true; // assume no match 1462 int i; 1463 loop: 1464 for (i = 0; i < transforms.length; i += 6) { 1465 for (int j = 0; j < 6; ++j) { 1466 if (transforms[i + j] != temp[j]) { 1467 continue loop; 1468 } 1469 } 1470 addSlot = false; 1471 break; 1472 } 1473 newIndex = i / 6 + 1; // if no match, end of list 1474 } 1475 1476 // if we're using the same transform, nothing to do 1477 int oldIndex = indices[glyphIndex]; 1478 if (newIndex != oldIndex) { 1479 // see if we are removing last use of the old slot 1480 boolean removeSlot = false; 1481 if (oldIndex != 0) { 1482 removeSlot = true; 1483 for (int i = 0; i < indices.length; ++i) { 1484 if (indices[i] == oldIndex && i != glyphIndex) { 1485 removeSlot = false; 1486 break; 1487 } 1488 } 1489 } 1490 1491 if (removeSlot && addSlot) { // reuse old slot with new transform 1492 newIndex = oldIndex; 1493 System.arraycopy(temp, 0, transforms, (newIndex - 1) * 6, 6); 1494 } else if (removeSlot) { 1495 if (transforms.length == 6) { // removing last one, so clear arrays 1496 indices = null; 1497 transforms = null; 1498 1499 sgv.clearCaches(glyphIndex); 1500 sgv.clearFlags(FLAG_HAS_TRANSFORMS); 1501 strikesRef = null; 1502 1503 return; 1504 } 1505 1506 double[] ttemp = new double[transforms.length - 6]; 1507 System.arraycopy(transforms, 0, ttemp, 0, (oldIndex - 1) * 6); 1508 System.arraycopy(transforms, oldIndex * 6, ttemp, (oldIndex - 1) * 6, 1509 transforms.length - oldIndex * 6); 1510 transforms = ttemp; 1511 1512 // clean up indices 1513 for (int i = 0; i < indices.length; ++i) { 1514 if (indices[i] > oldIndex) { // ignore == oldIndex, it's going away 1515 indices[i] -= 1; 1516 } 1517 } 1518 if (newIndex > oldIndex) { // don't forget to decrement this too if we need to 1519 --newIndex; 1520 } 1521 } else if (addSlot) { 1522 double[] ttemp = new double[transforms.length + 6]; 1523 System.arraycopy(transforms, 0, ttemp, 0, transforms.length); 1524 System.arraycopy(temp, 0, ttemp, transforms.length, 6); 1525 transforms = ttemp; 1526 } 1527 1528 indices[glyphIndex] = newIndex; 1529 } 1530 } 1531 1532 sgv.clearCaches(glyphIndex); 1533 sgv.addFlags(FLAG_HAS_TRANSFORMS); 1534 strikesRef = null; 1535 } 1536 1537 // implements sgv.getGlyphTransform getGlyphTransform(int ix)1538 AffineTransform getGlyphTransform(int ix) { 1539 int index = indices[ix]; 1540 if (index == 0) { 1541 return null; 1542 } 1543 1544 int x = (index - 1) * 6; 1545 return new AffineTransform(transforms[x + 0], 1546 transforms[x + 1], 1547 transforms[x + 2], 1548 transforms[x + 3], 1549 transforms[x + 4], 1550 transforms[x + 5]); 1551 } 1552 transformCount()1553 int transformCount() { 1554 if (transforms == null) { 1555 return 0; 1556 } 1557 return transforms.length / 6; 1558 } 1559 1560 /** 1561 * The strike cache works like this. 1562 * 1563 * -Each glyph is thought of as having a transform, usually identity. 1564 * -Each request for a strike is based on a device transform, either the 1565 * one in the frc or the rendering transform. 1566 * -For general info, strikes are held with soft references. 1567 * -When rendering, strikes must be held with hard references for the 1568 * duration of the rendering call. GlyphList will have to hold this 1569 * info along with the image and position info, but toss the strike info 1570 * when done. 1571 * -Build the strike cache as needed. If the dev transform we want to use 1572 * has changed from the last time it is built, the cache is flushed by 1573 * the caller before these methods are called. 1574 * 1575 * Use a tx that doesn't include translation components of dst tx. 1576 */ setupGlyphImages(long[] images, float[] positions, AffineTransform tx)1577 Object setupGlyphImages(long[] images, float[] positions, AffineTransform tx) { 1578 int len = sgv.glyphs.length; 1579 1580 GlyphStrike[] sl = getAllStrikes(); 1581 for (int i = 0; i < len; ++i) { 1582 GlyphStrike gs = sl[indices[i]]; 1583 int glyphID = sgv.glyphs[i]; 1584 images[i] = gs.strike.getGlyphImagePtr(glyphID); 1585 1586 gs.getGlyphPosition(glyphID, i*2, sgv.positions, positions); 1587 } 1588 tx.transform(positions, 0, positions, 0, len); 1589 1590 return sl; 1591 } 1592 getGlyphsPixelBounds(AffineTransform tx, float x, float y, int start, int count)1593 Rectangle getGlyphsPixelBounds(AffineTransform tx, float x, float y, int start, int count) { 1594 Rectangle result = null; 1595 Rectangle r = new Rectangle(); 1596 Point2D.Float pt = new Point.Float(); 1597 int n = start * 2; 1598 while (--count >= 0) { 1599 GlyphStrike gs = getStrike(start); 1600 pt.x = x + sgv.positions[n++] + gs.dx; 1601 pt.y = y + sgv.positions[n++] + gs.dy; 1602 tx.transform(pt, pt); 1603 gs.strike.getGlyphImageBounds(sgv.glyphs[start++], pt, r); 1604 if (!r.isEmpty()) { 1605 if (result == null) { 1606 result = new Rectangle(r); 1607 } else { 1608 result.add(r); 1609 } 1610 } 1611 } 1612 return result != null ? result : r; 1613 } 1614 getStrike(int glyphIndex)1615 GlyphStrike getStrike(int glyphIndex) { 1616 if (indices != null) { 1617 GlyphStrike[] strikes = getStrikeArray(); 1618 return getStrikeAtIndex(strikes, indices[glyphIndex]); 1619 } 1620 return sgv.getDefaultStrike(); 1621 } 1622 getAllStrikes()1623 private GlyphStrike[] getAllStrikes() { 1624 if (indices == null) { 1625 return null; 1626 } 1627 1628 GlyphStrike[] strikes = getStrikeArray(); 1629 if (!haveAllStrikes) { 1630 for (int i = 0; i < strikes.length; ++i) { 1631 getStrikeAtIndex(strikes, i); 1632 } 1633 haveAllStrikes = true; 1634 } 1635 1636 return strikes; 1637 } 1638 getStrikeArray()1639 private GlyphStrike[] getStrikeArray() { 1640 GlyphStrike[] strikes = null; 1641 if (strikesRef != null) { 1642 strikes = strikesRef.get(); 1643 } 1644 if (strikes == null) { 1645 haveAllStrikes = false; 1646 strikes = new GlyphStrike[transformCount() + 1]; 1647 strikesRef = new SoftReference<>(strikes); 1648 } 1649 1650 return strikes; 1651 } 1652 getStrikeAtIndex(GlyphStrike[] strikes, int strikeIndex)1653 private GlyphStrike getStrikeAtIndex(GlyphStrike[] strikes, int strikeIndex) { 1654 GlyphStrike strike = strikes[strikeIndex]; 1655 if (strike == null) { 1656 if (strikeIndex == 0) { 1657 strike = sgv.getDefaultStrike(); 1658 } else { 1659 int ix = (strikeIndex - 1) * 6; 1660 AffineTransform gtx = new AffineTransform(transforms[ix], 1661 transforms[ix+1], 1662 transforms[ix+2], 1663 transforms[ix+3], 1664 transforms[ix+4], 1665 transforms[ix+5]); 1666 1667 strike = GlyphStrike.create(sgv, sgv.dtx, gtx); 1668 } 1669 strikes[strikeIndex] = strike; 1670 } 1671 return strike; 1672 } 1673 } 1674 1675 // This adjusts the metrics by the translation components of the glyph 1676 // transform. It is done here since the translation is not known by the 1677 // strike. 1678 // It adjusts the position of the image and the advance. 1679 1680 public static final class GlyphStrike { 1681 StandardGlyphVector sgv; 1682 FontStrike strike; // hard reference 1683 float dx; 1684 float dy; 1685 create(StandardGlyphVector sgv, AffineTransform dtx, AffineTransform gtx)1686 static GlyphStrike create(StandardGlyphVector sgv, AffineTransform dtx, AffineTransform gtx) { 1687 float dx = 0; 1688 float dy = 0; 1689 1690 AffineTransform tx = sgv.ftx; 1691 if (!dtx.isIdentity() || gtx != null) { 1692 tx = new AffineTransform(sgv.ftx); 1693 if (gtx != null) { 1694 tx.preConcatenate(gtx); 1695 dx = (float)tx.getTranslateX(); // uses ftx then gtx to get translation 1696 dy = (float)tx.getTranslateY(); 1697 } 1698 if (!dtx.isIdentity()) { 1699 tx.preConcatenate(dtx); 1700 } 1701 } 1702 1703 int ptSize = 1; // only matters for 'gasp' case. 1704 Object aaHint = sgv.frc.getAntiAliasingHint(); 1705 if (aaHint == VALUE_TEXT_ANTIALIAS_GASP) { 1706 /* Must pass in the calculated point size for rendering. 1707 * If the glyph tx is anything other than identity or a 1708 * simple translate, calculate the transformed point size. 1709 */ 1710 if (!tx.isIdentity() && 1711 (tx.getType() & ~AffineTransform.TYPE_TRANSLATION) != 0) { 1712 double shearx = tx.getShearX(); 1713 if (shearx != 0) { 1714 double scaley = tx.getScaleY(); 1715 ptSize = 1716 (int)Math.sqrt(shearx * shearx + scaley * scaley); 1717 } else { 1718 ptSize = (int)(Math.abs(tx.getScaleY())); 1719 } 1720 } 1721 } 1722 int aa = FontStrikeDesc.getAAHintIntVal(aaHint,sgv.font2D, ptSize); 1723 int fm = FontStrikeDesc.getFMHintIntVal 1724 (sgv.frc.getFractionalMetricsHint()); 1725 FontStrikeDesc desc = new FontStrikeDesc(dtx, 1726 tx, 1727 sgv.font.getStyle(), 1728 aa, fm); 1729 // Get the strike via the handle. Shouldn't matter 1730 // if we've invalidated the font but its an extra precaution. 1731 // do we want the CompFont from CFont here ? 1732 Font2D f2d = sgv.font2D; 1733 if (f2d instanceof FontSubstitution) { 1734 f2d = ((FontSubstitution)f2d).getCompositeFont2D(); 1735 } 1736 FontStrike strike = f2d.handle.font2D.getStrike(desc); // !!! getStrike(desc, false) 1737 1738 return new GlyphStrike(sgv, strike, dx, dy); 1739 } 1740 GlyphStrike(StandardGlyphVector sgv, FontStrike strike, float dx, float dy)1741 private GlyphStrike(StandardGlyphVector sgv, FontStrike strike, float dx, float dy) { 1742 this.sgv = sgv; 1743 this.strike = strike; 1744 this.dx = dx; 1745 this.dy = dy; 1746 } 1747 getADL(ADL result)1748 void getADL(ADL result) { 1749 StrikeMetrics sm = strike.getFontMetrics(); 1750 Point2D.Float delta = null; 1751 if (sgv.font.isTransformed()) { 1752 delta = new Point2D.Float(); 1753 delta.x = (float)sgv.font.getTransform().getTranslateX(); 1754 delta.y = (float)sgv.font.getTransform().getTranslateY(); 1755 } 1756 1757 result.ascentX = -sm.ascentX; 1758 result.ascentY = -sm.ascentY; 1759 result.descentX = sm.descentX; 1760 result.descentY = sm.descentY; 1761 result.leadingX = sm.leadingX; 1762 result.leadingY = sm.leadingY; 1763 } 1764 getGlyphPosition(int glyphID, int ix, float[] positions, float[] result)1765 void getGlyphPosition(int glyphID, int ix, float[] positions, float[] result) { 1766 result[ix] = positions[ix] + dx; 1767 ++ix; 1768 result[ix] = positions[ix] + dy; 1769 } 1770 addDefaultGlyphAdvance(int glyphID, Point2D.Float result)1771 void addDefaultGlyphAdvance(int glyphID, Point2D.Float result) { 1772 // !!! change this API? Creates unnecessary garbage. Also the name doesn't quite fit. 1773 // strike.addGlyphAdvance(Point2D.Float adv); // hey, whaddya know, matches my api :-) 1774 Point2D.Float adv = strike.getGlyphMetrics(glyphID); 1775 result.x += adv.x + dx; 1776 result.y += adv.y + dy; 1777 } 1778 getGlyphOutlineBounds(int glyphID, float x, float y)1779 Rectangle2D getGlyphOutlineBounds(int glyphID, float x, float y) { 1780 Rectangle2D result = null; 1781 if (sgv.invdtx == null) { 1782 result = new Rectangle2D.Float(); 1783 result.setRect(strike.getGlyphOutlineBounds(glyphID)); // don't mutate cached rect 1784 } else { 1785 GeneralPath gp = strike.getGlyphOutline(glyphID, 0, 0); 1786 gp.transform(sgv.invdtx); 1787 result = gp.getBounds2D(); 1788 } 1789 /* Since x is the logical advance of the glyph to this point. 1790 * Because of the way that Rectangle.union is specified, this 1791 * means that subsequent unioning of a rect including that 1792 * will be affected, even if the glyph is empty. So skip such 1793 * cases. This alone isn't a complete solution since x==0 1794 * may also not be what is wanted. The code that does the 1795 * unioning also needs to be aware to ignore empty glyphs. 1796 */ 1797 if (!result.isEmpty()) { 1798 result.setRect(result.getMinX() + x + dx, 1799 result.getMinY() + y + dy, 1800 result.getWidth(), result.getHeight()); 1801 } 1802 return result; 1803 } 1804 appendGlyphOutline(int glyphID, GeneralPath result, float x, float y)1805 void appendGlyphOutline(int glyphID, GeneralPath result, float x, float y) { 1806 // !!! fontStrike needs a method for this. For that matter, GeneralPath does. 1807 GeneralPath gp = null; 1808 if (sgv.invdtx == null) { 1809 gp = strike.getGlyphOutline(glyphID, x + dx, y + dy); 1810 } else { 1811 gp = strike.getGlyphOutline(glyphID, 0, 0); 1812 gp.transform(sgv.invdtx); 1813 gp.transform(AffineTransform.getTranslateInstance(x + dx, y + dy)); 1814 } 1815 PathIterator iterator = gp.getPathIterator(null); 1816 result.append(iterator, false); 1817 } 1818 } 1819 toString()1820 public String toString() { 1821 return appendString(null).toString(); 1822 } 1823 appendString(StringBuffer buf)1824 StringBuffer appendString(StringBuffer buf) { 1825 if (buf == null) { 1826 buf = new StringBuffer(); 1827 } 1828 try { 1829 buf.append("SGV{font: "); 1830 buf.append(font.toString()); 1831 buf.append(", frc: "); 1832 buf.append(frc.toString()); 1833 buf.append(", glyphs: ("); 1834 buf.append(glyphs.length); 1835 buf.append(")["); 1836 for (int i = 0; i < glyphs.length; ++i) { 1837 if (i > 0) { 1838 buf.append(", "); 1839 } 1840 buf.append(Integer.toHexString(glyphs[i])); 1841 } 1842 buf.append("]"); 1843 if (positions != null) { 1844 buf.append(", positions: ("); 1845 buf.append(positions.length); 1846 buf.append(")["); 1847 for (int i = 0; i < positions.length; i += 2) { 1848 if (i > 0) { 1849 buf.append(", "); 1850 } 1851 buf.append(positions[i]); 1852 buf.append("@"); 1853 buf.append(positions[i+1]); 1854 } 1855 buf.append("]"); 1856 } 1857 if (charIndices != null) { 1858 buf.append(", indices: ("); 1859 buf.append(charIndices.length); 1860 buf.append(")["); 1861 for (int i = 0; i < charIndices.length; ++i) { 1862 if (i > 0) { 1863 buf.append(", "); 1864 } 1865 buf.append(charIndices[i]); 1866 } 1867 buf.append("]"); 1868 } 1869 buf.append(", flags:"); 1870 if (getLayoutFlags() == 0) { 1871 buf.append(" default"); 1872 } else { 1873 if ((flags & FLAG_HAS_TRANSFORMS) != 0) { 1874 buf.append(" tx"); 1875 } 1876 if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) { 1877 buf.append(" pos"); 1878 } 1879 if ((flags & FLAG_RUN_RTL) != 0) { 1880 buf.append(" rtl"); 1881 } 1882 if ((flags & FLAG_COMPLEX_GLYPHS) != 0) { 1883 buf.append(" complex"); 1884 } 1885 } 1886 } 1887 catch(Exception e) { 1888 buf.append(' ').append(e.getMessage()); 1889 } 1890 buf.append('}'); 1891 1892 return buf; 1893 } 1894 1895 static class ADL { 1896 public float ascentX; 1897 public float ascentY; 1898 public float descentX; 1899 public float descentY; 1900 public float leadingX; 1901 public float leadingY; 1902 toString()1903 public String toString() { 1904 return toStringBuffer(null).toString(); 1905 } 1906 toStringBuffer(StringBuffer result)1907 protected StringBuffer toStringBuffer(StringBuffer result) { 1908 if (result == null) { 1909 result = new StringBuffer(); 1910 } 1911 result.append("ax: "); 1912 result.append(ascentX); 1913 result.append(" ay: "); 1914 result.append(ascentY); 1915 result.append(" dx: "); 1916 result.append(descentX); 1917 result.append(" dy: "); 1918 result.append(descentY); 1919 result.append(" lx: "); 1920 result.append(leadingX); 1921 result.append(" ly: "); 1922 result.append(leadingY); 1923 1924 return result; 1925 } 1926 } 1927 } 1928