Showing the changes to java/pkg/sun/font/StandardGlyphVector.java (1 of 18352 files). Show all
Unified · Split
| @@ -0,0 +1,1935 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1998, 2005, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | |||
| 26 | package sun.font; | ||
| 27 | |||
| 28 | import java.awt.Font; | ||
| 29 | import java.awt.Graphics2D; | ||
| 30 | import java.awt.Point; | ||
| 31 | import java.awt.Rectangle; | ||
| 32 | import static java.awt.RenderingHints.*; | ||
| 33 | import java.awt.Shape; | ||
| 34 | import java.awt.font.FontRenderContext; | ||
| 35 | import java.awt.font.GlyphMetrics; | ||
| 36 | import java.awt.font.GlyphJustificationInfo; | ||
| 37 | import java.awt.font.GlyphVector; | ||
| 38 | import java.awt.font.LineMetrics; | ||
| 39 | import java.awt.font.TextAttribute; | ||
| 40 | import java.awt.geom.AffineTransform; | ||
| 41 | import java.awt.geom.GeneralPath; | ||
| 42 | import java.awt.geom.NoninvertibleTransformException; | ||
| 43 | import java.awt.geom.PathIterator; | ||
| 44 | import java.awt.geom.Point2D; | ||
| 45 | import java.awt.geom.Rectangle2D; | ||
| 46 | import java.lang.ref.SoftReference; | ||
| 47 | import java.text.CharacterIterator; | ||
| 48 | |||
| 49 | import sun.awt.SunHints; | ||
| 50 | import sun.java2d.loops.FontInfo; | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Standard implementation of GlyphVector used by Font, GlyphList, and | ||
| 54 | * SunGraphics2D. | ||
| 55 | * | ||
| 56 | * The main issues involve the semantics of the various transforms | ||
| 57 | * (font, glyph, device) and their effect on rendering and metrics. | ||
| 58 | * | ||
| 59 | * Very, very unfortunately, the translation component of the font | ||
| 60 | * transform affects where the text gets rendered. It offsets the | ||
| 61 | * rendering origin. None of the other metrics of the glyphvector | ||
| 62 | * are affected, making them inconsistent with the rendering behavior. | ||
| 63 | * I think the translation component of the font would be better | ||
| 64 | * interpreted as the translation component of a per-glyph transform, | ||
| 65 | * but I don't know if this is possible to change. | ||
| 66 | * | ||
| 67 | * After the font transform is applied, the glyph transform is | ||
| 68 | * applied. This makes glyph transforms relative to font transforms, | ||
| 69 | * if the font transform changes, the glyph transform will have the | ||
| 70 | * same (relative) effect on the outline of the glyph. The outline | ||
| 71 | * and logical bounds are passed through the glyph transform before | ||
| 72 | * being returned. The glyph metrics ignore the glyph transform, but | ||
| 73 | * provide the outline bounds and the advance vector of the glyph (the | ||
| 74 | * latter will be rotated if the font is rotated). The default layout | ||
| 75 | * places each glyph at the end of the advance vector of the previous | ||
| 76 | * glyph, and since the glyph transform translates the advance vector, | ||
| 77 | * this means a glyph transform affects the positions of all | ||
| 78 | * subsequent glyphs if defaultLayout is called after setting a glyph | ||
| 79 | * transform. In the glyph info array, the bounds are the outline | ||
| 80 | * bounds including the glyph transform, and the positions are as | ||
| 81 | * computed, and the advances are the deltas between the positions. | ||
| 82 | * | ||
| 83 | * (There's a bug in the logical bounds of a rotated glyph for | ||
| 84 | * composite fonts, it's not to spec (in 1.4.0, 1.4.1, 1.4.2). The | ||
| 85 | * problem is that the rotated composite doesn't handle the multiple | ||
| 86 | * ascents and descents properly in both x and y. You end up with | ||
| 87 | * a rotated advance vector but an unrotated ascent and descent.) | ||
| 88 | * | ||
| 89 | * Finally, the whole thing is transformed by the device transform to | ||
| 90 | * position it on the page. | ||
| 91 | * | ||
| 92 | * Another bug: The glyph outline seems to ignore fractional point | ||
| 93 | * size information, but the images (and advances) don't ignore it. | ||
| 94 | * | ||
| 95 | * Small fonts drawn at large magnification have odd advances when | ||
| 96 | * fractional metrics is off-- that's because the advances depend on | ||
| 97 | * the frc. When the frc is scaled appropriately, the advances are | ||
| 98 | * fine. FM or a large frc (high numbers) make the advances right. | ||
| 99 | * | ||
| 100 | * The buffer aa flag doesn't affect rendering, the glyph vector | ||
| 101 | * renders as AA if aa is set in its frc, and as non-aa if aa is not | ||
| 102 | * set in its frc. | ||
| 103 | * | ||
| 104 | * font rotation, baseline, vertical etc. | ||
| 105 | * | ||
| 106 | * Font rotation and baseline Line metrics should be measured along a | ||
| 107 | * unit vector pi/4 cc from the baseline vector. For 'horizontal' | ||
| 108 | * fonts the baseline vector is the x vector passed through the font | ||
| 109 | * transform (ignoring translation), for 'vertical' it is the y | ||
| 110 | * vector. This definition makes ascent, descent, etc independent of | ||
| 111 | * shear, so shearing can be used to simulate italic. This means no | ||
| 112 | * fonts have 'negative ascents' or 'zero ascents' etc. | ||
| 113 | * | ||
| 114 | * Having a coordinate system with orthogonal axes where one is | ||
| 115 | * parallel to the baseline means we could use rectangles and interpret | ||
| 116 | * them in terms of this coordinate system. Unfortunately there | ||
| 117 | * is support for rotated fonts in the jdk already so maintaining | ||
| 118 | * the semantics of existing code (getlogical bounds, etc) might | ||
| 119 | * be difficult. | ||
| 120 | * | ||
| 121 | * A font transform transforms both the baseline and all the glyphs | ||
| 122 | * in the font, so it does not rotate the glyph w.r.t the baseline. | ||
| 123 | * If you do want to rotate individual glyphs, you need to apply a | ||
| 124 | * glyph transform. If performDefaultLayout is called after this, | ||
| 125 | * the transformed glyph advances will affect the glyph positions. | ||
| 126 | * | ||
| 127 | * useful additions | ||
| 128 | * - select vertical metrics - glyphs are rotated pi/4 cc and vertical | ||
| 129 | * metrics are used to align them to the baseline. | ||
| 130 | * - define baseline for font (glyph rotation not linked to baseline) | ||
| 131 | * - define extra space (delta between each glyph along baseline) | ||
| 132 | * - define offset (delta from 'true' baseline, impacts ascent and | ||
| 133 | * descent as these are still computed from true basline and pinned | ||
| 134 | * to zero, used in superscript). | ||
| 135 | */ | ||
| 136 | public class StandardGlyphVector extends GlyphVector { | ||
| 137 | private Font font; | ||
| 138 | private FontRenderContext frc; | ||
| 139 | private int[] glyphs; // always | ||
| 140 | private int[] userGlyphs; // used to return glyphs to the client. | ||
| 141 | private float[] positions; // only if not default advances | ||
| 142 | private int[] charIndices; // only if interesting | ||
| 143 | private int flags; // indicates whether positions, charIndices is interesting | ||
| 144 | |||
| 145 | private static final int UNINITIALIZED_FLAGS = -1; | ||
| 146 | |||
| 147 | // transforms information | ||
| 148 | private GlyphTransformInfo gti; // information about per-glyph transforms | ||
| 149 | |||
| 150 | // !!! can we get rid of any of this extra stuff? | ||
| 151 | private AffineTransform ftx; // font transform without translation | ||
| 152 | private AffineTransform dtx; // device transform used for strike calculations, no translation | ||
| 153 | private AffineTransform invdtx; // inverse of dtx or null if dtx is identity | ||
| 154 | private AffineTransform frctx; // font render context transform, wish we could just share it | ||
| 155 | private Font2D font2D; // basic strike-independent stuff | ||
| 156 | private SoftReference fsref; // font strike reference for glyphs with no per-glyph transform | ||
| 157 | |||
| 158 | ///////////////////////////// | ||
| 159 | // Constructors and Factory methods | ||
| 160 | ///////////////////////////// | ||
| 161 | |||
| 162 | public StandardGlyphVector(Font font, String str, FontRenderContext frc) { | ||
| 163 | init(font, str.toCharArray(), 0, str.length(), frc, UNINITIALIZED_FLAGS); | ||
| 164 | } | ||
| 165 | |||
| 166 | public StandardGlyphVector(Font font, char[] text, FontRenderContext frc) { | ||
| 167 | init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS); | ||
| 168 | } | ||
| 169 | |||
| 170 | public StandardGlyphVector(Font font, char[] text, int start, int count, | ||
| 171 | FontRenderContext frc) { | ||
| 172 | init(font, text, start, count, frc, UNINITIALIZED_FLAGS); | ||
| 173 | } | ||
| 174 | |||
| 175 | private float getTracking(Font font) { | ||
| 176 | if (font.hasLayoutAttributes()) { | ||
| 177 | AttributeValues values = ((AttributeMap)font.getAttributes()).getValues(); | ||
| 178 | return values.getTracking(); | ||
| 179 | } | ||
| 180 | return 0; | ||
| 181 | } | ||
| 182 | |||
| 183 | // used by GlyphLayout to construct a glyphvector | ||
| 184 | public StandardGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions, | ||
| 185 | int[] indices, int flags) { | ||
| 186 | initGlyphVector(font, frc, glyphs, positions, indices, flags); | ||
| 187 | |||
| 188 | // this code should go into layout | ||
| 189 | float track = getTracking(font); | ||
| 190 | if (track != 0) { | ||
| 191 | track *= font.getSize2D(); | ||
| 192 | Point2D.Float trackPt = new Point2D.Float(track, 0); // advance delta | ||
| 193 | if (font.isTransformed()) { | ||
| 194 | AffineTransform at = font.getTransform(); | ||
| 195 | at.deltaTransform(trackPt, trackPt); | ||
| 196 | } | ||
| 197 | |||
| 198 | // how do we know its a base glyph | ||
| 199 | // for now, it is if the natural advance of the glyph is non-zero | ||
| 200 | Font2D f2d = FontUtilities.getFont2D(font); | ||
| 201 | FontStrike strike = f2d.getStrike(font, frc); | ||
| 202 | |||
| 203 | float[] deltas = { trackPt.x, trackPt.y }; | ||
| 204 | for (int j = 0; j < deltas.length; ++j) { | ||
| 205 | float inc = deltas[j]; | ||
| 206 | if (inc != 0) { | ||
| 207 | float delta = 0; | ||
| 208 | for (int i = j, n = 0; n < glyphs.length; i += 2) { | ||
| 209 | if (strike.getGlyphAdvance(glyphs[n++]) != 0) { // might be an inadequate test | ||
| 210 | positions[i] += delta; | ||
| 211 | delta += inc; | ||
| 212 | } | ||
| 213 | } | ||
| 214 | positions[positions.length-2+j] += delta; | ||
| 215 | } | ||
| 216 | } | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | public void initGlyphVector(Font font, FontRenderContext frc, int[] glyphs, float[] positions, | ||
| 221 | int[] indices, int flags) { | ||
| 222 | this.font = font; | ||
| 223 | this.frc = frc; | ||
| 224 | this.glyphs = glyphs; | ||
| 225 | this.userGlyphs = glyphs; // no need to check | ||
| 226 | this.positions = positions; | ||
| 227 | this.charIndices = indices; | ||
| 228 | this.flags = flags; | ||
| 229 | |||
| 230 | initFontData(); | ||
| 231 | } | ||
| 232 | |||
| 233 | public StandardGlyphVector(Font font, CharacterIterator iter, FontRenderContext frc) { | ||
| 234 | int offset = iter.getBeginIndex(); | ||
| 235 | char[] text = new char [iter.getEndIndex() - offset]; | ||
| 236 | for(char c = iter.first(); | ||
| 237 | c != CharacterIterator.DONE; | ||
| 238 | c = iter.next()) { | ||
| 239 | text[iter.getIndex() - offset] = c; | ||
| 240 | } | ||
| 241 | init(font, text, 0, text.length, frc, UNINITIALIZED_FLAGS); | ||
| 242 | } | ||
| 243 | |||
| 244 | public StandardGlyphVector(Font font, int[] glyphs, FontRenderContext frc) { | ||
| 245 | // !!! find callers of this | ||
| 246 | // should be able to fully init from raw data, e.g. charmap, flags too. | ||
| 247 | this.font = font; | ||
| 248 | this.frc = frc; | ||
| 249 | this.flags = UNINITIALIZED_FLAGS; | ||
| 250 | |||
| 251 | initFontData(); | ||
| 252 | this.userGlyphs = glyphs; | ||
| 253 | this.glyphs = getValidatedGlyphs(this.userGlyphs); | ||
| 254 | } | ||
| 255 | |||
| 256 | /* This is called from the rendering loop. FontInfo is supplied | ||
| 257 | * because a GV caches a strike and glyph images suitable for its FRC. | ||
| 258 | * LCD text isn't currently supported on all surfaces, in which case | ||
| 259 | * standard AA must be used. This is most likely to occur when LCD text | ||
| 260 | * is requested and the surface is some non-standard type or hardward | ||
| 261 | * surface for which there are no accelerated loops. | ||
| 262 | * We can detect this as being AA=="ON" in the FontInfo and AA!="ON" | ||
| 263 | * and AA!="GASP" in the FRC - since this only occurs for LCD text we don't | ||
| 264 | * need to check any more precisely what value is in the FRC. | ||
| 265 | */ | ||
| 266 | public static StandardGlyphVector getStandardGV(GlyphVector gv, | ||
| 267 | FontInfo info) { | ||
| 268 | if (info.aaHint == SunHints.INTVAL_TEXT_ANTIALIAS_ON) { | ||
| 269 | Object aaHint = gv.getFontRenderContext().getAntiAliasingHint(); | ||
| 270 | if (aaHint != VALUE_TEXT_ANTIALIAS_ON && | ||
| 271 | aaHint != VALUE_TEXT_ANTIALIAS_GASP) { | ||
| 272 | /* We need to create a new GV with AA==ON for rendering */ | ||
| 273 | FontRenderContext frc = gv.getFontRenderContext(); | ||
| 274 | frc = new FontRenderContext(frc.getTransform(), | ||
| 275 | VALUE_TEXT_ANTIALIAS_ON, | ||
| 276 | frc.getFractionalMetricsHint()); | ||
| 277 | return new StandardGlyphVector(gv, frc); | ||
| 278 | } | ||
| 279 | } | ||
| 280 | if (gv instanceof StandardGlyphVector) { | ||
| 281 | return (StandardGlyphVector)gv; | ||
| 282 | } | ||
| 283 | return new StandardGlyphVector(gv, gv.getFontRenderContext()); | ||
| 284 | } | ||
| 285 | |||
| 286 | ///////////////////////////// | ||
| 287 | // GlyphVector API | ||
| 288 | ///////////////////////////// | ||
| 289 | |||
| 290 | public Font getFont() { | ||
| 291 | return this.font; | ||
| 292 | } | ||
| 293 | |||
| 294 | public FontRenderContext getFontRenderContext() { | ||
| 295 | return this.frc; | ||
| 296 | } | ||
| 297 | |||
| 298 | public void performDefaultLayout() { | ||
| 299 | positions = null; | ||
| 300 | if (getTracking(font) == 0) { | ||
| 301 | clearFlags(FLAG_HAS_POSITION_ADJUSTMENTS); | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | public int getNumGlyphs() { | ||
| 306 | return glyphs.length; | ||
| 307 | } | ||
| 308 | |||
| 309 | public int getGlyphCode(int glyphIndex) { | ||
| 310 | return userGlyphs[glyphIndex]; | ||
| 311 | } | ||
| 312 | |||
| 313 | public int[] getGlyphCodes(int start, int count, int[] result) { | ||
| 314 | if (count < 0) { | ||
| 315 | throw new IllegalArgumentException("count = " + count); | ||
| 316 | } | ||
| 317 | if (start < 0) { | ||
| 318 | throw new IndexOutOfBoundsException("start = " + start); | ||
| 319 | } | ||
| 320 | if (start > glyphs.length - count) { // watch out for overflow if index + count overlarge | ||
| 321 | throw new IndexOutOfBoundsException("start + count = " + (start + count)); | ||
| 322 | } | ||
| 323 | |||
| 324 | if (result == null) { | ||
| 325 | result = new int[count]; | ||
| 326 | } | ||
| 327 | |||
| 328 | // if arraycopy were faster, we wouldn't code this | ||
| 329 | for (int i = 0; i < count; ++i) { | ||
| 330 | result[i] = userGlyphs[i + start]; | ||
| 331 | } | ||
| 332 | |||
| 333 | return result; | ||
| 334 | } | ||
| 335 | |||
| 336 | public int getGlyphCharIndex(int ix) { | ||
| 337 | if (ix < 0 && ix >= glyphs.length) { | ||
| 338 | throw new IndexOutOfBoundsException("" + ix); | ||
| 339 | } | ||
| 340 | if (charIndices == null) { | ||
| 341 | if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) { | ||
| 342 | return glyphs.length - 1 - ix; | ||
| 343 | } | ||
| 344 | return ix; | ||
| 345 | } | ||
| 346 | return charIndices[ix]; | ||
| 347 | } | ||
| 348 | |||
| 349 | public int[] getGlyphCharIndices(int start, int count, int[] result) { | ||
| 350 | if (start < 0 || count < 0 || (count > glyphs.length - start)) { | ||
| 351 | throw new IndexOutOfBoundsException("" + start + ", " + count); | ||
| 352 | } | ||
| 353 | if (result == null) { | ||
| 354 | result = new int[count]; | ||
| 355 | } | ||
| 356 | if (charIndices == null) { | ||
| 357 | if ((getLayoutFlags() & FLAG_RUN_RTL) != 0) { | ||
| 358 | for (int i = 0, n = glyphs.length - 1 - start; | ||
| 359 | i < count; ++i, --n) { | ||
| 360 | result[i] = n; | ||
| 361 | } | ||
| 362 | } else { | ||
| 363 | for (int i = 0, n = start; i < count; ++i, ++n) { | ||
| 364 | result[i] = n; | ||
| 365 | } | ||
| 366 | } | ||
| 367 | } else { | ||
| 368 | for (int i = 0; i < count; ++i) { | ||
| 369 | result[i] = charIndices[i + start]; | ||
| 370 | } | ||
| 371 | } | ||
| 372 | return result; | ||
| 373 | } | ||
| 374 | |||
| 375 | // !!! not cached, assume TextLayout will cache if necessary | ||
| 376 | // !!! reexamine for per-glyph-transforms | ||
| 377 | // !!! revisit for text-on-a-path, vertical | ||
| 378 | public Rectangle2D getLogicalBounds() { | ||
| 379 | setFRCTX(); | ||
| 380 | initPositions(); | ||
| 381 | |||
| 382 | LineMetrics lm = font.getLineMetrics("", frc); | ||
| 383 | |||
| 384 | float minX, minY, maxX, maxY; | ||
| 385 | // horiz only for now... | ||
| 386 | minX = 0; | ||
| 387 | minY = -lm.getAscent(); | ||
| 388 | maxX = 0; | ||
| 389 | maxY = lm.getDescent() + lm.getLeading(); | ||
| 390 | if (glyphs.length > 0) { | ||
| 391 | maxX = positions[positions.length - 2]; | ||
| 392 | } | ||
| 393 | |||
| 394 | return new Rectangle2D.Float(minX, minY, maxX - minX, maxY - minY); | ||
| 395 | } | ||
| 396 | |||
| 397 | // !!! not cached, assume TextLayout will cache if necessary | ||
| 398 | public Rectangle2D getVisualBounds() { | ||
| 399 | Rectangle2D result = null; | ||
| 400 | for (int i = 0; i < glyphs.length; ++i) { | ||
| 401 | Rectangle2D glyphVB = getGlyphVisualBounds(i).getBounds2D(); | ||
| 402 | if (!glyphVB.isEmpty()) { | ||
| 403 | if (result == null) { | ||
| 404 | result = glyphVB; | ||
| 405 | } else { | ||
| 406 | Rectangle2D.union(result, glyphVB, result); | ||
| 407 | } | ||
| 408 | } | ||
| 409 | } | ||
| 410 | if (result == null) { | ||
| 411 | result = new Rectangle2D.Float(0, 0, 0, 0); | ||
| 412 | } | ||
| 413 | return result; | ||
| 414 | } | ||
| 415 | |||
| 416 | // !!! not cached, assume TextLayout will cache if necessary | ||
| 417 | // !!! fontStrike needs a method for this | ||
| 418 | public Rectangle getPixelBounds(FontRenderContext renderFRC, float x, float y) { | ||
| 419 | return getGlyphsPixelBounds(renderFRC, x, y, 0, glyphs.length); | ||
| 420 | } | ||
| 421 | |||
| 422 | public Shape getOutline() { | ||
| 423 | return getGlyphsOutline(0, glyphs.length, 0, 0); | ||
| 424 | } | ||
| 425 | |||
| 426 | public Shape getOutline(float x, float y) { | ||
| 427 | return getGlyphsOutline(0, glyphs.length, x, y); | ||
| 428 | } | ||
| 429 | |||
| 430 | // relative to gv origin | ||
| 431 | public Shape getGlyphOutline(int ix) { | ||
| 432 | return getGlyphsOutline(ix, 1, 0, 0); | ||
| 433 | } | ||
| 434 | |||
| 435 | // relative to gv origin offset by x, y | ||
| 436 | public Shape getGlyphOutline(int ix, float x, float y) { | ||
| 437 | return getGlyphsOutline(ix, 1, x, y); | ||
| 438 | } | ||
| 439 | |||
| 440 | public Point2D getGlyphPosition(int ix) { | ||
| 441 | initPositions(); | ||
| 442 | |||
| 443 | ix *= 2; | ||
| 444 | return new Point2D.Float(positions[ix], positions[ix + 1]); | ||
| 445 | } | ||
| 446 | |||
| 447 | public void setGlyphPosition(int ix, Point2D pos) { | ||
| 448 | initPositions(); | ||
| 449 | |||
| 450 | int ix2 = ix << 1; | ||
| 451 | positions[ix2] = (float)pos.getX(); | ||
| 452 | positions[ix2 + 1] = (float)pos.getY(); | ||
| 453 | |||
| 454 | clearCaches(ix); | ||
| 455 | addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); | ||
| 456 | } | ||
| 457 | |||
| 458 | public AffineTransform getGlyphTransform(int ix) { | ||
| 459 | if (ix < 0 || ix >= glyphs.length) { | ||
| 460 | throw new IndexOutOfBoundsException("ix = " + ix); | ||
| 461 | } | ||
| 462 | if (gti != null) { | ||
| 463 | return gti.getGlyphTransform(ix); | ||
| 464 | } | ||
| 465 | return null; // spec'd as returning null | ||
| 466 | } | ||
| 467 | |||
| 468 | public void setGlyphTransform(int ix, AffineTransform newTX) { | ||
| 469 | if (ix < 0 || ix >= glyphs.length) { | ||
| 470 | throw new IndexOutOfBoundsException("ix = " + ix); | ||
| 471 | } | ||
| 472 | |||
| 473 | if (gti == null) { | ||
| 474 | if (newTX == null || newTX.isIdentity()) { | ||
| 475 | return; | ||
| 476 | } | ||
| 477 | gti = new GlyphTransformInfo(this); | ||
| 478 | } | ||
| 479 | gti.setGlyphTransform(ix, newTX); // sets flags | ||
| 480 | if (gti.transformCount() == 0) { | ||
| 481 | gti = null; | ||
| 482 | } | ||
| 483 | } | ||
| 484 | |||
| 485 | public int getLayoutFlags() { | ||
| 486 | if (flags == UNINITIALIZED_FLAGS) { | ||
| 487 | flags = 0; | ||
| 488 | |||
| 489 | if (charIndices != null && glyphs.length > 1) { | ||
| 490 | boolean ltr = true; | ||
| 491 | boolean rtl = true; | ||
| 492 | |||
| 493 | int rtlix = charIndices.length; // rtl index | ||
| 494 | for (int i = 0; i < charIndices.length && (ltr || rtl); ++i) { | ||
| 495 | int cx = charIndices[i]; | ||
| 496 | |||
| 497 | ltr = ltr && (cx == i); | ||
| 498 | rtl = rtl && (cx == --rtlix); | ||
| 499 | } | ||
| 500 | |||
| 501 | if (rtl) flags |= FLAG_RUN_RTL; | ||
| 502 | if (!rtl && !ltr) flags |= FLAG_COMPLEX_GLYPHS; | ||
| 503 | } | ||
| 504 | } | ||
| 505 | |||
| 506 | return flags; | ||
| 507 | } | ||
| 508 | |||
| 509 | public float[] getGlyphPositions(int start, int count, float[] result) { | ||
| 510 | if (count < 0) { | ||
| 511 | throw new IllegalArgumentException("count = " + count); | ||
| 512 | } | ||
| 513 | if (start < 0) { | ||
| 514 | throw new IndexOutOfBoundsException("start = " + start); | ||
| 515 | } | ||
| 516 | if (start > glyphs.length + 1 - count) { // watch for overflow | ||
| 517 | throw new IndexOutOfBoundsException("start + count = " + (start + count)); | ||
| 518 | } | ||
| 519 | |||
| 520 | return internalGetGlyphPositions(start, count, 0, result); | ||
| 521 | } | ||
| 522 | |||
| 523 | public Shape getGlyphLogicalBounds(int ix) { | ||
| 524 | if (ix < 0 || ix >= glyphs.length) { | ||
| 525 | throw new IndexOutOfBoundsException("ix = " + ix); | ||
| 526 | } | ||
| 527 | |||
| 528 | Shape[] lbcache; | ||
| 529 | if (lbcacheRef == null || (lbcache = (Shape[])lbcacheRef.get()) == null) { | ||
| 530 | lbcache = new Shape[glyphs.length]; | ||
| 531 | lbcacheRef = new SoftReference(lbcache); | ||
| 532 | } | ||
| 533 | |||
| 534 | Shape result = lbcache[ix]; | ||
| 535 | if (result == null) { | ||
| 536 | setFRCTX(); | ||
| 537 | initPositions(); | ||
| 538 | |||
| 539 | // !!! ought to return a rectangle2d for simple cases, though the following works for all | ||
| 540 | |||
| 541 | // get the position, the tx offset, and the x,y advance and x,y adl. The | ||
| 542 | // shape is the box formed by adv (width) and adl (height) offset by | ||
| 543 | // the position plus the tx offset minus the ascent. | ||
| 544 | |||
| 545 | ADL adl = new ADL(); | ||
| 546 | GlyphStrike gs = getGlyphStrike(ix); | ||
| 547 | gs.getADL(adl); | ||
| 548 | |||
| 549 | Point2D.Float adv = gs.strike.getGlyphMetrics(glyphs[ix]); | ||
| 550 | |||
| 551 | float wx = adv.x; | ||
| 552 | float wy = adv.y; | ||
| 553 | float hx = adl.descentX + adl.leadingX + adl.ascentX; | ||
| 554 | float hy = adl.descentY + adl.leadingY + adl.ascentY; | ||
| 555 | float x = positions[ix*2] + gs.dx - adl.ascentX; | ||
| 556 | float y = positions[ix*2+1] + gs.dy - adl.ascentY; | ||
| 557 | |||
| 558 | GeneralPath gp = new GeneralPath(); | ||
| 559 | gp.moveTo(x, y); | ||
| 560 | gp.lineTo(x + wx, y + wy); | ||
| 561 | gp.lineTo(x + wx + hx, y + wy + hy); | ||
| 562 | gp.lineTo(x + hx, y + hy); | ||
| 563 | gp.closePath(); | ||
| 564 | |||
| 565 | result = new DelegatingShape(gp); | ||
| 566 | lbcache[ix] = result; | ||
| 567 | } | ||
| 568 | |||
| 569 | return result; | ||
| 570 | } | ||
| 571 | private SoftReference lbcacheRef; | ||
| 572 | |||
| 573 | public Shape getGlyphVisualBounds(int ix) { | ||
| 574 | if (ix < 0 || ix >= glyphs.length) { | ||
| 575 | throw new IndexOutOfBoundsException("ix = " + ix); | ||
| 576 | } | ||
| 577 | |||
| 578 | Shape[] vbcache; | ||
| 579 | if (vbcacheRef == null || (vbcache = (Shape[])vbcacheRef.get()) == null) { | ||
| 580 | vbcache = new Shape[glyphs.length]; | ||
| 581 | vbcacheRef = new SoftReference(vbcache); | ||
| 582 | } | ||
| 583 | |||
| 584 | Shape result = vbcache[ix]; | ||
| 585 | if (result == null) { | ||
| 586 | result = new DelegatingShape(getGlyphOutlineBounds(ix)); | ||
| 587 | vbcache[ix] = result; | ||
| 588 | } | ||
| 589 | |||
| 590 | return result; | ||
| 591 | } | ||
| 592 | private SoftReference vbcacheRef; | ||
| 593 | |||
| 594 | public Rectangle getGlyphPixelBounds(int index, FontRenderContext renderFRC, float x, float y) { | ||
| 595 | return getGlyphsPixelBounds(renderFRC, x, y, index, 1); | ||
| 596 | } | ||
| 597 | |||
| 598 | public GlyphMetrics getGlyphMetrics(int ix) { | ||
| 599 | if (ix < 0 || ix >= glyphs.length) { | ||
| 600 | throw new IndexOutOfBoundsException("ix = " + ix); | ||
| 601 | } | ||
| 602 | |||
| 603 | Rectangle2D vb = getGlyphVisualBounds(ix).getBounds2D(); | ||
| 604 | Point2D pt = getGlyphPosition(ix); | ||
| 605 | vb.setRect(vb.getMinX() - pt.getX(), | ||
| 606 | vb.getMinY() - pt.getY(), | ||
| 607 | vb.getWidth(), | ||
| 608 | vb.getHeight()); | ||
| 609 | Point2D.Float adv = | ||
| 610 | getGlyphStrike(ix).strike.getGlyphMetrics(glyphs[ix]); | ||
| 611 | GlyphMetrics gm = new GlyphMetrics(true, adv.x, adv.y, | ||
| 612 | vb, | ||
| 613 | GlyphMetrics.STANDARD); | ||
| 614 | return gm; | ||
| 615 | } | ||
| 616 | |||
| 617 | public GlyphJustificationInfo getGlyphJustificationInfo(int ix) { | ||
| 618 | if (ix < 0 || ix >= glyphs.length) { | ||
| 619 | throw new IndexOutOfBoundsException("ix = " + ix); | ||
| 620 | } | ||
| 621 | |||
| 622 | // currently we don't have enough information to do this right. should | ||
| 623 | // get info from the font and use real OT/GX justification. Right now | ||
| 624 | // sun/font/ExtendedTextSourceLabel assigns one of three infos | ||
| 625 | // based on whether the char is kanji, space, or other. | ||
| 626 | |||
| 627 | return null; | ||
| 628 | } | ||
| 629 | |||
| 630 | public boolean equals(GlyphVector rhs) { | ||
| 631 | if (this == rhs) { | ||
| 632 | return true; | ||
| 633 | } | ||
| 634 | if (rhs == null) { | ||
| 635 | return false; | ||
| 636 | } | ||
| 637 | |||
| 638 | try { | ||
| 639 | StandardGlyphVector other = (StandardGlyphVector)rhs; | ||
| 640 | |||
| 641 | if (glyphs.length != other.glyphs.length) { | ||
| 642 | return false; | ||
| 643 | } | ||
| 644 | |||
| 645 | for (int i = 0; i < glyphs.length; ++i) { | ||
| 646 | if (glyphs[i] != other.glyphs[i]) { | ||
| 647 | return false; | ||
| 648 | } | ||
| 649 | } | ||
| 650 | |||
| 651 | if (!font.equals(other.font)) { | ||
| 652 | return false; | ||
| 653 | } | ||
| 654 | |||
| 655 | if (!frc.equals(other.frc)) { | ||
| 656 | return false; | ||
| 657 | } | ||
| 658 | |||
| 659 | if ((other.positions == null) != (positions == null)) { | ||
| 660 | if (positions == null) { | ||
| 661 | initPositions(); | ||
| 662 | } else { | ||
| 663 | other.initPositions(); | ||
| 664 | } | ||
| 665 | } | ||
| 666 | |||
| 667 | if (positions != null) { | ||
| 668 | for (int i = 0; i < positions.length; ++i) { | ||
| 669 | if (positions[i] != other.positions[i]) { | ||
| 670 | return false; | ||
| 671 | } | ||
| 672 | } | ||
| 673 | } | ||
| 674 | |||
| 675 | if (gti == null) { | ||
| 676 | return other.gti == null; | ||
| 677 | } else { | ||
| 678 | return gti.equals(other.gti); | ||
| 679 | } | ||
| 680 | } | ||
| 681 | catch (ClassCastException e) { | ||
| 682 | // assume they are different simply by virtue of the class difference | ||
| 683 | |||
| 684 | return false; | ||
| 685 | } | ||
| 686 | } | ||
| 687 | |||
| 688 | /** | ||
| 689 | * As a concrete subclass of Object that implements equality, this must | ||
| 690 | * implement hashCode. | ||
| 691 | */ | ||
| 692 | public int hashCode() { | ||
| 693 | return font.hashCode() ^ glyphs.length; | ||
| 694 | } | ||
| 695 | |||
| 696 | /** | ||
| 697 | * Since we implement equality comparisons for GlyphVector, we implement | ||
| 698 | * the inherited Object.equals(Object) as well. GlyphVector should do | ||
| 699 | * this, and define two glyphvectors as not equal if the classes differ. | ||
| 700 | */ | ||
| 701 | public boolean equals(Object rhs) { | ||
| 702 | try { | ||
| 703 | return equals((GlyphVector)rhs); | ||
| 704 | } | ||
| 705 | catch (ClassCastException e) { | ||
| 706 | return false; | ||
| 707 | } | ||
| 708 | } | ||
| 709 | |||
| 710 | /** | ||
| 711 | * Sometimes I wish java had covariant return types... | ||
| 712 | */ | ||
| 713 | public StandardGlyphVector copy() { | ||
| 714 | return (StandardGlyphVector)clone(); | ||
| 715 | } | ||
| 716 | |||
| 717 | /** | ||
| 718 | * As a concrete subclass of GlyphVector, this must implement clone. | ||
| 719 | */ | ||
| 720 | public Object clone() { | ||
| 721 | // positions, gti are mutable so we have to clone them | ||
| 722 | // font2d can be shared | ||
| 723 | // fsref is a cache and can be shared | ||
| 724 | try { | ||
| 725 | StandardGlyphVector result = (StandardGlyphVector)super.clone(); | ||
| 726 | |||
| 727 | result.clearCaches(); | ||
| 728 | |||
| 729 | if (positions != null) { | ||
| 730 | result.positions = (float[])positions.clone(); | ||
| 731 | } | ||
| 732 | |||
| 733 | if (gti != null) { | ||
| 734 | result.gti = new GlyphTransformInfo(result, gti); | ||
| 735 | } | ||
| 736 | |||
| 737 | return result; | ||
| 738 | } | ||
| 739 | catch (CloneNotSupportedException e) { | ||
| 740 | } | ||
| 741 | |||
| 742 | return this; | ||
| 743 | } | ||
| 744 | |||
| 745 | ////////////////////// | ||
| 746 | // StandardGlyphVector new public methods | ||
| 747 | ///////////////////// | ||
| 748 | |||
| 749 | /* | ||
| 750 | * Set a multiple glyph positions at one time. GlyphVector only | ||
| 751 | * provides API to set a single glyph at a time. | ||
| 752 | */ | ||
| 753 | public void setGlyphPositions(float[] srcPositions, int srcStart, | ||
| 754 | int start, int count) { | ||
| 755 | if (count < 0) { | ||
| 756 | throw new IllegalArgumentException("count = " + count); | ||
| 757 | } | ||
| 758 | |||
| 759 | initPositions(); | ||
| 760 | for (int i = start * 2, e = i + count * 2, p = srcStart; i < e; ++i, ++p) { | ||
| 761 | positions[i] = srcPositions[p]; | ||
| 762 | } | ||
| 763 | |||
| 764 | clearCaches(); | ||
| 765 | addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); | ||
| 766 | } | ||
| 767 | |||
| 768 | /** | ||
| 769 | * Set all the glyph positions, including the 'after last glyph' position. | ||
| 770 | * The srcPositions array must be of length (numGlyphs + 1) * 2. | ||
| 771 | */ | ||
| 772 | public void setGlyphPositions(float[] srcPositions) { | ||
| 773 | int requiredLength = glyphs.length * 2 + 2; | ||
| 774 | if (srcPositions.length != requiredLength) { | ||
| 775 | throw new IllegalArgumentException("srcPositions.length != " + requiredLength); | ||
| 776 | } | ||
| 777 | |||
| 778 | positions = (float[])srcPositions.clone(); | ||
| 779 | |||
| 780 | clearCaches(); | ||
| 781 | addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); | ||
| 782 | } | ||
| 783 | |||
| 784 | /** | ||
| 785 | * This is a convenience overload that gets all the glyph positions, which | ||
| 786 | * is what you usually want to do if you're getting more than one. | ||
| 787 | * !!! should I bother taking result parameter? | ||
| 788 | */ | ||
| 789 | public float[] getGlyphPositions(float[] result) { | ||
| 790 | return internalGetGlyphPositions(0, glyphs.length + 1, 0, result); | ||
| 791 | } | ||
| 792 | |||
| 793 | /** | ||
| 794 | * Get transform information for the requested range of glyphs. | ||
| 795 | * If no glyphs have a transform, return null. | ||
| 796 | * If a glyph has no transform (or is the identity transform) its entry in the result array will be null. | ||
| 797 | * If the passed-in result is null an array will be allocated for the caller. | ||
| 798 | * Each transform instance in the result array will unique, and independent of the GlyphVector's transform. | ||
| 799 | */ | ||
| 800 | public AffineTransform[] getGlyphTransforms(int start, int count, AffineTransform[] result) { | ||
| 801 | if (start < 0 || count < 0 || start + count > glyphs.length) { | ||
| 802 | throw new IllegalArgumentException("start: " + start + " count: " + count); | ||
| 803 | } | ||
| 804 | |||
| 805 | if (gti == null) { | ||
| 806 | return null; | ||
| 807 | } | ||
| 808 | |||
| 809 | if (result == null) { | ||
| 810 | result = new AffineTransform[count]; | ||
| 811 | } | ||
| 812 | |||
| 813 | for (int i = 0; i < count; ++i, ++start) { | ||
| 814 | result[i] = gti.getGlyphTransform(start); | ||
| 815 | } | ||
| 816 | |||
| 817 | return result; | ||
| 818 | } | ||
| 819 | |||
| 820 | /** | ||
| 821 | * Convenience overload for getGlyphTransforms(int, int, AffineTransform[], int); | ||
| 822 | */ | ||
| 823 | public AffineTransform[] getGlyphTransforms() { | ||
| 824 | return getGlyphTransforms(0, glyphs.length, null); | ||
| 825 | } | ||
| 826 | |||
| 827 | /** | ||
| 828 | * Set a number of glyph transforms. | ||
| 829 | * Original transforms are unchanged. The array may contain nulls, and also may | ||
| 830 | * contain multiple references to the same transform instance. | ||
| 831 | */ | ||
| 832 | public void setGlyphTransforms(AffineTransform[] srcTransforms, int srcStart, int start, int count) { | ||
| 833 | for (int i = start, e = start + count; i < e; ++i) { | ||
| 834 | setGlyphTransform(i, srcTransforms[srcStart + i]); | ||
| 835 | } | ||
| 836 | } | ||
| 837 | |||
| 838 | /** | ||
| 839 | * Convenience overload of setGlyphTransforms(AffineTransform[], int, int, int). | ||
| 840 | */ | ||
| 841 | public void setGlyphTransforms(AffineTransform[] srcTransforms) { | ||
| 842 | setGlyphTransforms(srcTransforms, 0, 0, glyphs.length); | ||
| 843 | } | ||
| 844 | |||
| 845 | /** | ||
| 846 | * For each glyph return posx, posy, advx, advy, visx, visy, visw, vish. | ||
| 847 | */ | ||
| 848 | public float[] getGlyphInfo() { | ||
| 849 | setFRCTX(); | ||
| 850 | initPositions(); | ||
| 851 | float[] result = new float[glyphs.length * 8]; | ||
| 852 | for (int i = 0, n = 0; i < glyphs.length; ++i, n += 8) { | ||
| 853 | float x = positions[i*2]; | ||
| 854 | float y = positions[i*2+1]; | ||
| 855 | result[n] = x; | ||
| 856 | result[n+1] = y; | ||
| 857 | |||
| 858 | int glyphID = glyphs[i]; | ||
| 859 | GlyphStrike s = getGlyphStrike(i); | ||
| 860 | Point2D.Float adv = s.strike.getGlyphMetrics(glyphID); | ||
| 861 | result[n+2] = adv.x; | ||
| 862 | result[n+3] = adv.y; | ||
| 863 | |||
| 864 | Rectangle2D vb = getGlyphVisualBounds(i).getBounds2D(); | ||
| 865 | result[n+4] = (float)(vb.getMinX()); | ||
| 866 | result[n+5] = (float)(vb.getMinY()); | ||
| 867 | result[n+6] = (float)(vb.getWidth()); | ||
| 868 | result[n+7] = (float)(vb.getHeight()); | ||
| 869 | } | ||
| 870 | return result; | ||
| 871 | } | ||
| 872 | |||
| 873 | /** | ||
| 874 | * !!! not used currently, but might be by getPixelbounds? | ||
| 875 | */ | ||
| 876 | public void pixellate(FontRenderContext renderFRC, Point2D loc, Point pxResult) { | ||
| 877 | if (renderFRC == null) { | ||
| 878 | renderFRC = frc; | ||
| 879 | } | ||
| 880 | |||
| 881 | // it is a total pain that you have to copy the transform. | ||
| 882 | |||
| 883 | AffineTransform at = renderFRC.getTransform(); | ||
| 884 | at.transform(loc, loc); | ||
| 885 | pxResult.x = (int)loc.getX(); // but must not behave oddly around zero | ||
| 886 | pxResult.y = (int)loc.getY(); | ||
| 887 | loc.setLocation(pxResult.x, pxResult.y); | ||
| 888 | try { | ||
| 889 | at.inverseTransform(loc, loc); | ||
| 890 | } | ||
| 891 | catch (NoninvertibleTransformException e) { | ||
| 892 | throw new IllegalArgumentException("must be able to invert frc transform"); | ||
| 893 | } | ||
| 894 | } | ||
| 895 | |||
| 896 | ////////////////////// | ||
| 897 | // StandardGlyphVector package private methods | ||
| 898 | ///////////////////// | ||
| 899 | |||
| 900 | // used by glyphlist to determine if it needs to allocate/size positions array | ||
| 901 | // gti always uses positions because the gtx might have translation. We also | ||
| 902 | // need positions if the rendering dtx is different from the frctx. | ||
| 903 | |||
| 904 | boolean needsPositions(double[] devTX) { | ||
| 905 | return gti != null || | ||
| 906 | (getLayoutFlags() & FLAG_HAS_POSITION_ADJUSTMENTS) != 0 || | ||
| 907 | !matchTX(devTX, frctx); | ||
| 908 | } | ||
| 909 | |||
| 910 | // used by glyphList to get strong refs to font strikes for duration of rendering call | ||
| 911 | // if devTX matches current devTX, we're ready to go | ||
| 912 | // if we don't have multiple transforms, we're already ok | ||
| 913 | |||
| 914 | // !!! I'm not sure fontInfo works so well for glyphvector, since we have to be able to handle | ||
| 915 | // the multiple-strikes case | ||
| 916 | |||
| 917 | /* | ||
| 918 | * GlyphList calls this to set up its images data. First it calls needsPositions, | ||
| 919 | * passing the devTX, to see if it should provide us a positions array to fill. | ||
| 920 | * It only doesn't need them if we're a simple glyph vector whose frctx matches the | ||
| 921 | * devtx. | ||
| 922 | * Then it calls setupGlyphImages. If we need positions, we make sure we have our | ||
| 923 | * default positions based on the frctx first. Then we set the devTX, and use | ||
| 924 | * strikes based on it to generate the images. Finally, we fill in the positions | ||
| 925 | * array. | ||
| 926 | * If we have transforms, we delegate to gti. It depends on our having first | ||
| 927 | * initialized the positions and devTX. | ||
| 928 | */ | ||
| 929 | Object setupGlyphImages(long[] images, float[] positions, double[] devTX) { | ||
| 930 | initPositions(); // FIRST ensure we have positions based on our frctx | ||
| 931 | setRenderTransform(devTX); // THEN make sure we are using the desired devTX | ||
| 932 | |||
| 933 | if (gti != null) { | ||
| 934 | return gti.setupGlyphImages(images, positions, dtx); | ||
| 935 | } | ||
| 936 | |||
| 937 | GlyphStrike gs = getDefaultStrike(); | ||
| 938 | gs.strike.getGlyphImagePtrs(glyphs, images, glyphs.length); | ||
| 939 | |||
| 940 | if (positions != null) { | ||
| 941 | if (dtx.isIdentity()) { | ||
| 942 | System.arraycopy(this.positions, 0, positions, 0, glyphs.length * 2); | ||
| 943 | } else { | ||
| 944 | dtx.transform(this.positions, 0, positions, 0, glyphs.length); | ||
| 945 | } | ||
| 946 | } | ||
| 947 | |||
| 948 | return gs; | ||
| 949 | } | ||
| 950 | |||
| 951 | ////////////////////// | ||
| 952 | // StandardGlyphVector private methods | ||
| 953 | ///////////////////// | ||
| 954 | |||
| 955 | // We keep translation in our frctx since getPixelBounds uses it. But | ||
| 956 | // GlyphList pulls out the translation and applies it separately, so | ||
| 957 | // we strip it out when we set the dtx. Basically nothing uses the | ||
| 958 | // translation except getPixelBounds. | ||
| 959 | |||
| 960 | // called by needsPositions, setRenderTransform | ||
| 961 | private static boolean matchTX(double[] lhs, AffineTransform rhs) { | ||
| 962 | return | ||
| 963 | lhs[0] == rhs.getScaleX() && | ||
| 964 | lhs[1] == rhs.getShearY() && | ||
| 965 | lhs[2] == rhs.getShearX() && | ||
| 966 | lhs[3] == rhs.getScaleY(); | ||
| 967 | } | ||
| 968 | |||
| 969 | // returns new tx if old one has translation, otherwise returns old one | ||
| 970 | private static AffineTransform getNonTranslateTX(AffineTransform tx) { | ||
| 971 | if (tx.getTranslateX() != 0 || tx.getTranslateY() != 0) { | ||
| 972 | tx = new AffineTransform(tx.getScaleX(), tx.getShearY(), | ||
| 973 | tx.getShearX(), tx.getScaleY(), | ||
| 974 | 0, 0); | ||
| 975 | } | ||
| 976 | return tx; | ||
| 977 | } | ||
| 978 | |||
| 979 | private static boolean equalNonTranslateTX(AffineTransform lhs, AffineTransform rhs) { | ||
| 980 | return lhs.getScaleX() == rhs.getScaleX() && | ||
| 981 | lhs.getShearY() == rhs.getShearY() && | ||
| 982 | lhs.getShearX() == rhs.getShearX() && | ||
| 983 | lhs.getScaleY() == rhs.getScaleY(); | ||
| 984 | } | ||
| 985 | |||
| 986 | // called by setupGlyphImages (after needsPositions, so redundant match check?) | ||
| 987 | private void setRenderTransform(double[] devTX) { | ||
| 988 | assert(devTX.length == 4); | ||
| 989 | if (!matchTX(devTX, dtx)) { | ||
| 990 | resetDTX(new AffineTransform(devTX)); // no translation since devTX len == 4. | ||
| 991 | } | ||
| 992 | } | ||
| 993 | |||
| 994 | // called by getGlyphsPixelBounds | ||
| 995 | private final void setDTX(AffineTransform tx) { | ||
| 996 | if (!equalNonTranslateTX(dtx, tx)) { | ||
| 997 | resetDTX(getNonTranslateTX(tx)); | ||
| 998 | } | ||
| 999 | } | ||
| 1000 | |||
| 1001 | // called by most functions | ||
| 1002 | private final void setFRCTX() { | ||
| 1003 | if (!equalNonTranslateTX(frctx, dtx)) { | ||
| 1004 | resetDTX(getNonTranslateTX(frctx)); | ||
| 1005 | } | ||
| 1006 | } | ||
| 1007 | |||
| 1008 | /** | ||
| 1009 | * Change the dtx for the strike refs we use. Keeps a reference to the at. At | ||
| 1010 | * must not contain translation. | ||
| 1011 | * Called by setRenderTransform, setDTX, initFontData. | ||
| 1012 | */ | ||
| 1013 | private final void resetDTX(AffineTransform at) { | ||
| 1014 | fsref = null; | ||
| 1015 | dtx = at; | ||
| 1016 | invdtx = null; | ||
| 1017 | if (!dtx.isIdentity()) { | ||
| 1018 | try { | ||
| 1019 | invdtx = dtx.createInverse(); | ||
| 1020 | } | ||
| 1021 | catch (NoninvertibleTransformException e) { | ||
| 1022 | // we needn't care for rendering | ||
| 1023 | } | ||
| 1024 | } | ||
| 1025 | if (gti != null) { | ||
| 1026 | gti.strikesRef = null; | ||
| 1027 | } | ||
| 1028 | } | ||
| 1029 | |||
| 1030 | /** | ||
| 1031 | * Utility used by getStandardGV. | ||
| 1032 | * Constructs a StandardGlyphVector from a generic glyph vector. | ||
| 1033 | * Do not call this from new contexts without considering the comment | ||
| 1034 | * about "userGlyphs". | ||
| 1035 | */ | ||
| 1036 | private StandardGlyphVector(GlyphVector gv, FontRenderContext frc) { | ||
| 1037 | this.font = gv.getFont(); | ||
| 1038 | this.frc = frc; | ||
| 1039 | initFontData(); | ||
| 1040 | |||
| 1041 | int nGlyphs = gv.getNumGlyphs(); | ||
| 1042 | this.userGlyphs = gv.getGlyphCodes(0, nGlyphs, null); | ||
| 1043 | if (gv instanceof StandardGlyphVector) { | ||
| 1044 | /* userGlyphs will be OK because this is a private constructor | ||
| 1045 | * and the returned instance is used only for rendering. | ||
| 1046 | * It's not constructable by user code, nor returned to the | ||
| 1047 | * application. So we know "userGlyphs" are valid as having | ||
| 1048 | * been either already validated or are the result of layout. | ||
| 1049 | */ | ||
| 1050 | this.glyphs = userGlyphs; | ||
| 1051 | } else { | ||
| 1052 | this.glyphs = getValidatedGlyphs(this.userGlyphs); | ||
| 1053 | } | ||
| 1054 | this.flags = gv.getLayoutFlags() & FLAG_MASK; | ||
| 1055 | |||
| 1056 | if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) { | ||
| 1057 | this.positions = gv.getGlyphPositions(0, nGlyphs + 1, null); | ||
| 1058 | } | ||
| 1059 | |||
| 1060 | if ((flags & FLAG_COMPLEX_GLYPHS) != 0) { | ||
| 1061 | this.charIndices = gv.getGlyphCharIndices(0, nGlyphs, null); | ||
| 1062 | } | ||
| 1063 | |||
| 1064 | if ((flags & FLAG_HAS_TRANSFORMS) != 0) { | ||
| 1065 | AffineTransform[] txs = new AffineTransform[nGlyphs]; // worst case | ||
| 1066 | for (int i = 0; i < nGlyphs; ++i) { | ||
| 1067 | txs[i] = gv.getGlyphTransform(i); // gv doesn't have getGlyphsTransforms | ||
| 1068 | } | ||
| 1069 | |||
| 1070 | setGlyphTransforms(txs); | ||
| 1071 | } | ||
| 1072 | } | ||
| 1073 | |||
| 1074 | /* Before asking the Font we see if the glyph code is | ||
| 1075 | * FFFE or FFFF which are special values that we should be internally | ||
| 1076 | * ready to handle as meaning invisible glyphs. The Font would report | ||
| 1077 | * those as the missing glyph. | ||
| 1078 | */ | ||
| 1079 | int[] getValidatedGlyphs(int[] oglyphs) { | ||
| 1080 | int len = oglyphs.length; | ||
| 1081 | int[] vglyphs = new int[len]; | ||
| 1082 | for (int i=0; i<len; i++) { | ||
| 1083 | if (oglyphs[i] == 0xFFFE || oglyphs[i] == 0xFFFF) { | ||
| 1084 | vglyphs[i] = oglyphs[i]; | ||
| 1085 | } else { | ||
| 1086 | vglyphs[i] = font2D.getValidatedGlyphCode(oglyphs[i]); | ||
| 1087 | } | ||
| 1088 | } | ||
| 1089 | return vglyphs; | ||
| 1090 | } | ||
| 1091 | |||
| 1092 | // utility used by constructors | ||
| 1093 | private void init(Font font, char[] text, int start, int count, | ||
| 1094 | FontRenderContext frc, int flags) { | ||
| 1095 | |||
| 1096 | if (start < 0 || count < 0 || start + count > text.length) { | ||
| 1097 | throw new ArrayIndexOutOfBoundsException("start or count out of bounds"); | ||
| 1098 | } | ||
| 1099 | |||
| 1100 | this.font = font; | ||
| 1101 | this.frc = frc; | ||
| 1102 | this.flags = flags; | ||
| 1103 | |||
| 1104 | if (getTracking(font) != 0) { | ||
| 1105 | addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); | ||
| 1106 | } | ||
| 1107 | |||
| 1108 | // !!! change mapper interface? | ||
| 1109 | if (start != 0) { | ||
| 1110 | char[] temp = new char[count]; | ||
| 1111 | System.arraycopy(text, start, temp, 0, count); | ||
| 1112 | text = temp; | ||
| 1113 | } | ||
| 1114 | |||
| 1115 | initFontData(); // sets up font2D | ||
| 1116 | |||
| 1117 | // !!! no layout for now, should add checks | ||
| 1118 | // !!! need to support creating a StandardGlyphVector from a TextMeasurer's info... | ||
| 1119 | glyphs = new int[count]; // hmmm | ||
| 1120 | /* Glyphs obtained here are already validated by the font */ | ||
| 1121 | userGlyphs = glyphs; | ||
| 1122 | font2D.getMapper().charsToGlyphs(count, text, glyphs); | ||
| 1123 | } | ||
| 1124 | |||
| 1125 | private void initFontData() { | ||
| 1126 | font2D = FontUtilities.getFont2D(font); | ||
| 1127 | float s = font.getSize2D(); | ||
| 1128 | if (font.isTransformed()) { | ||
| 1129 | ftx = font.getTransform(); | ||
| 1130 | if (ftx.getTranslateX() != 0 || ftx.getTranslateY() != 0) { | ||
| 1131 | addFlags(FLAG_HAS_POSITION_ADJUSTMENTS); | ||
| 1132 | } | ||
| 1133 | ftx.setTransform(ftx.getScaleX(), ftx.getShearY(), ftx.getShearX(), ftx.getScaleY(), 0, 0); | ||
| 1134 | ftx.scale(s, s); | ||
| 1135 | } else { | ||
| 1136 | ftx = AffineTransform.getScaleInstance(s, s); | ||
| 1137 | } | ||
| 1138 | |||
| 1139 | frctx = frc.getTransform(); | ||
| 1140 | resetDTX(getNonTranslateTX(frctx)); | ||
| 1141 | } | ||
| 1142 | |||
| 1143 | /** | ||
| 1144 | * Copy glyph position data into a result array starting at the indicated | ||
| 1145 | * offset in the array. If the passed-in result array is null, a new | ||
| 1146 | * array will be allocated and returned. | ||
| 1147 | * | ||
| 1148 | * This is an internal method and does no extra argument checking. | ||
| 1149 | * | ||
| 1150 | * @param start the index of the first glyph to get | ||
| 1151 | * @param count the number of glyphs to get | ||
| 1152 | * @param offset the offset into result at which to put the data | ||
| 1153 | * @param result an array to hold the x,y positions | ||
| 1154 | * @return the modified position array | ||
| 1155 | */ | ||
| 1156 | private float[] internalGetGlyphPositions(int start, int count, int offset, float[] result) { | ||
| 1157 | if (result == null) { | ||
| 1158 | result = new float[offset + count * 2]; | ||
| 1159 | } | ||
| 1160 | |||
| 1161 | initPositions(); | ||
| 1162 | |||
| 1163 | // System.arraycopy is slow for stuff like this | ||
| 1164 | for (int i = offset, e = offset + count * 2, p = start * 2; i < e; ++i, ++p) { | ||
| 1165 | result[i] = positions[p]; | ||
| 1166 | } | ||
| 1167 | |||
| 1168 | return result; | ||
| 1169 | } | ||
| 1170 | |||
| 1171 | private Rectangle2D getGlyphOutlineBounds(int ix) { | ||
| 1172 | setFRCTX(); | ||
| 1173 | initPositions(); | ||
| 1174 | return getGlyphStrike(ix).getGlyphOutlineBounds(glyphs[ix], positions[ix*2], positions[ix*2+1]); | ||
| 1175 | } | ||
| 1176 | |||
| 1177 | /** | ||
| 1178 | * Used by getOutline, getGlyphsOutline | ||
| 1179 | */ | ||
| 1180 | private Shape getGlyphsOutline(int start, int count, float x, float y) { | ||
| 1181 | setFRCTX(); | ||
| 1182 | initPositions(); | ||
| 1183 | |||
| 1184 | GeneralPath result = new GeneralPath(GeneralPath.WIND_NON_ZERO); | ||
| 1185 | for (int i = start, e = start + count, n = start * 2; i < e; ++i, n += 2) { | ||
| 1186 | float px = x + positions[n]; | ||
| 1187 | float py = y + positions[n+1]; | ||
| 1188 | |||
| 1189 | getGlyphStrike(i).appendGlyphOutline(glyphs[i], result, px, py); | ||
| 1190 | } | ||
| 1191 | |||
| 1192 | return result; | ||
| 1193 | } | ||
| 1194 | |||
| 1195 | private Rectangle getGlyphsPixelBounds(FontRenderContext frc, float x, float y, int start, int count) { | ||
| 1196 | initPositions(); // FIRST ensure we have positions based on our frctx | ||
| 1197 | |||
| 1198 | AffineTransform tx = null; | ||
| 1199 | if (frc == null || frc.equals(this.frc)) { | ||
| 1200 | tx = frctx; | ||
| 1201 | } else { | ||
| 1202 | tx = frc.getTransform(); | ||
| 1203 | } | ||
| 1204 | setDTX(tx); // need to get the right strikes, but we use tx itself to translate the points | ||
| 1205 | |||
| 1206 | if (gti != null) { | ||
| 1207 | return gti.getGlyphsPixelBounds(tx, x, y, start, count); | ||
| 1208 | } | ||
| 1209 | |||
| 1210 | FontStrike fs = getDefaultStrike().strike; | ||
| 1211 | Rectangle result = null; | ||
| 1212 | Rectangle r = new Rectangle(); | ||
| 1213 | Point2D.Float pt = new Point.Float(); | ||
| 1214 | int n = start * 2; | ||
| 1215 | while (--count >= 0) { | ||
| 1216 | pt.x = x + positions[n++]; | ||
| 1217 | pt.y = y + positions[n++]; | ||
| 1218 | tx.transform(pt, pt); | ||
| 1219 | fs.getGlyphImageBounds(glyphs[start++], pt, r); | ||
| 1220 | if (!r.isEmpty()) { | ||
| 1221 | if (result == null) { | ||
| 1222 | result = new Rectangle(r); | ||
| 1223 | } else { | ||
| 1224 | result.add(r); | ||
| 1225 | } | ||
| 1226 | } | ||
| 1227 | } | ||
| 1228 | return result != null ? result : r; | ||
| 1229 | } | ||
| 1230 | |||
| 1231 | private void clearCaches(int ix) { | ||
| 1232 | if (lbcacheRef != null) { | ||
| 1233 | Shape[] lbcache = (Shape[])lbcacheRef.get(); | ||
| 1234 | if (lbcache != null) { | ||
| 1235 | lbcache[ix] = null; | ||
| 1236 | } | ||
| 1237 | } | ||
| 1238 | |||
| 1239 | if (vbcacheRef != null) { | ||
| 1240 | Shape[] vbcache = (Shape[])vbcacheRef.get(); | ||
| 1241 | if (vbcache != null) { | ||
| 1242 | vbcache[ix] = null; | ||
| 1243 | } | ||
| 1244 | } | ||
| 1245 | } | ||
| 1246 | |||
| 1247 | private void clearCaches() { | ||
| 1248 | lbcacheRef = null; | ||
| 1249 | vbcacheRef = null; | ||
| 1250 | } | ||
| 1251 | |||
| 1252 | // internal use only for possible future extension | ||
| 1253 | |||
| 1254 | /** | ||
| 1255 | * A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses | ||
| 1256 | * a vertical baseline. | ||
| 1257 | */ | ||
| 1258 | public static final int FLAG_USES_VERTICAL_BASELINE = 128; | ||
| 1259 | |||
| 1260 | /** | ||
| 1261 | * A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses | ||
| 1262 | * vertical glyph metrics. A <code>GlyphVector</code> can use vertical metrics on a | ||
| 1263 | * horizontal line, or vice versa. | ||
| 1264 | */ | ||
| 1265 | public static final int FLAG_USES_VERTICAL_METRICS = 256; | ||
| 1266 | |||
| 1267 | /** | ||
| 1268 | * A flag used with getLayoutFlags that indicates whether this <code>GlyphVector</code> uses | ||
| 1269 | * the 'alternate orientation.' Glyphs have a default orientation given a | ||
| 1270 | * particular baseline and metrics orientation, this is the orientation appropriate | ||
| 1271 | * for left-to-right text. For example, the letter 'A' can have four orientations, | ||
| 1272 | * with the point at 12, 3, 6, or 9 'o clock. The following table shows where the | ||
| 1273 | * point displays for different values of vertical baseline (vb), vertical | ||
| 1274 | * metrics (vm) and alternate orientation (fo):<br> | ||
| 1275 | * <blockquote> | ||
| 1276 | * vb vm ao | ||
| 1277 | * -- -- -- -- | ||
| 1278 | * f f f 12 ^ horizontal metrics on horizontal lines | ||
| 1279 | * f f t 6 v | ||
| 1280 | * f t f 9 < vertical metrics on horizontal lines | ||
| 1281 | * f t t 3 > | ||
| 1282 | * t f f 3 > horizontal metrics on vertical lines | ||
| 1283 | * t f t 9 < | ||
| 1284 | * t t f 12 ^ vertical metrics on vertical lines | ||
| 1285 | * t t t 6 v | ||
| 1286 | * </blockquote> | ||
| 1287 | */ | ||
| 1288 | public static final int FLAG_USES_ALTERNATE_ORIENTATION = 512; | ||
| 1289 | |||
| 1290 | |||
| 1291 | /** | ||
| 1292 | * Ensure that the positions array exists and holds position data. | ||
| 1293 | * If the array is null, this allocates it and sets default positions. | ||
| 1294 | */ | ||
| 1295 | private void initPositions() { | ||
| 1296 | if (positions == null) { | ||
| 1297 | setFRCTX(); | ||
| 1298 | |||
| 1299 | positions = new float[glyphs.length * 2 + 2]; | ||
| 1300 | |||
| 1301 | Point2D.Float trackPt = null; | ||
| 1302 | float track = getTracking(font); | ||
| 1303 | if (track != 0) { | ||
| 1304 | track *= font.getSize2D(); | ||
| 1305 | trackPt = new Point2D.Float(track, 0); // advance delta | ||
| 1306 | } | ||
| 1307 | |||
| 1308 | Point2D.Float pt = new Point2D.Float(0, 0); | ||
| 1309 | if (font.isTransformed()) { | ||
| 1310 | AffineTransform at = font.getTransform(); | ||
| 1311 | at.transform(pt, pt); | ||
| 1312 | positions[0] = pt.x; | ||
| 1313 | positions[1] = pt.y; | ||
| 1314 | |||
| 1315 | if (trackPt != null) { | ||
| 1316 | at.deltaTransform(trackPt, trackPt); | ||
| 1317 | } | ||
| 1318 | } | ||
| 1319 | for (int i = 0, n = 2; i < glyphs.length; ++i, n += 2) { | ||
| 1320 | getGlyphStrike(i).addDefaultGlyphAdvance(glyphs[i], pt); | ||
| 1321 | if (trackPt != null) { | ||
| 1322 | pt.x += trackPt.x; | ||
| 1323 | pt.y += trackPt.y; | ||
| 1324 | } | ||
| 1325 | positions[n] = pt.x; | ||
| 1326 | positions[n+1] = pt.y; | ||
| 1327 | } | ||
| 1328 | } | ||
| 1329 | } | ||
| 1330 | |||
| 1331 | /** | ||
| 1332 | * OR newFlags with existing flags. First computes existing flags if needed. | ||
| 1333 | */ | ||
| 1334 | private void addFlags(int newflags) { | ||
| 1335 | flags = getLayoutFlags() | newflags; | ||
| 1336 | } | ||
| 1337 | |||
| 1338 | /** | ||
| 1339 | * AND the complement of clearedFlags with existing flags. First computes existing flags if needed. | ||
| 1340 | */ | ||
| 1341 | private void clearFlags(int clearedFlags) { | ||
| 1342 | flags = getLayoutFlags() & ~clearedFlags; | ||
| 1343 | } | ||
| 1344 | |||
| 1345 | // general utility methods | ||
| 1346 | |||
| 1347 | // encapsulate the test to check whether we have per-glyph transforms | ||
| 1348 | private GlyphStrike getGlyphStrike(int ix) { | ||
| 1349 | if (gti == null) { | ||
| 1350 | return getDefaultStrike(); | ||
| 1351 | } else { | ||
| 1352 | return gti.getStrike(ix); | ||
| 1353 | } | ||
| 1354 | } | ||
| 1355 | |||
| 1356 | // encapsulate access to cached default glyph strike | ||
| 1357 | private GlyphStrike getDefaultStrike() { | ||
| 1358 | GlyphStrike gs = null; | ||
| 1359 | if (fsref != null) { | ||
| 1360 | gs = (GlyphStrike)fsref.get(); | ||
| 1361 | } | ||
| 1362 | if (gs == null) { | ||
| 1363 | gs = GlyphStrike.create(this, dtx, null); | ||
| 1364 | fsref = new SoftReference(gs); | ||
| 1365 | } | ||
| 1366 | return gs; | ||
| 1367 | } | ||
| 1368 | |||
| 1369 | |||
| 1370 | ///////////////////// | ||
| 1371 | // Internal utility classes | ||
| 1372 | ///////////////////// | ||
| 1373 | |||
| 1374 | // !!! I have this as a separate class instead of just inside SGV, | ||
| 1375 | // but I previously didn't bother. Now I'm trying this again. | ||
| 1376 | // Probably still not worth it, but I'd like to keep sgv's small in the common case. | ||
| 1377 | |||
| 1378 | static final class GlyphTransformInfo { | ||
| 1379 | StandardGlyphVector sgv; // reference back to glyph vector - yuck | ||
| 1380 | int[] indices; // index into unique strikes | ||
| 1381 | double[] transforms; // six doubles per unique transform, because AT is a pain to manipulate | ||
| 1382 | SoftReference strikesRef; // ref to unique strikes, one per transform | ||
| 1383 | boolean haveAllStrikes; // true if the strike array has been filled by getStrikes(). | ||
| 1384 | |||
| 1385 | // used when first setting a transform | ||
| 1386 | GlyphTransformInfo(StandardGlyphVector sgv) { | ||
| 1387 | this.sgv = sgv; | ||
| 1388 | } | ||
| 1389 | |||
| 1390 | // used when cloning a glyph vector, need to set back link | ||
| 1391 | GlyphTransformInfo(StandardGlyphVector sgv, GlyphTransformInfo rhs) { | ||
| 1392 | this.sgv = sgv; | ||
| 1393 | |||
| 1394 | this.indices = rhs.indices == null ? null : (int[])rhs.indices.clone(); | ||
| 1395 | this.transforms = rhs.transforms == null ? null : (double[])rhs.transforms.clone(); | ||
| 1396 | this.strikesRef = null; // can't share cache, so rather than clone, we just null out | ||
| 1397 | } | ||
| 1398 | |||
| 1399 | // used in sgv equality | ||
| 1400 | public boolean equals(GlyphTransformInfo rhs) { | ||
| 1401 | if (rhs == null) { | ||
| 1402 | return false; | ||
| 1403 | } | ||
| 1404 | if (rhs == this) { | ||
| 1405 | return true; | ||
| 1406 | } | ||
| 1407 | if (this.indices.length != rhs.indices.length) { | ||
| 1408 | return false; | ||
| 1409 | } | ||
| 1410 | if (this.transforms.length != rhs.transforms.length) { | ||
| 1411 | return false; | ||
| 1412 | } | ||
| 1413 | |||
| 1414 | // slow since we end up processing the same transforms multiple | ||
| 1415 | // times, but since transforms can be in any order, we either do | ||
| 1416 | // this or create a mapping. Equality tests aren't common so | ||
| 1417 | // leave it like this. | ||
| 1418 | for (int i = 0; i < this.indices.length; ++i) { | ||
| 1419 | int tix = this.indices[i]; | ||
| 1420 | int rix = rhs.indices[i]; | ||
| 1421 | if ((tix == 0) != (rix == 0)) { | ||
| 1422 | return false; | ||
| 1423 | } | ||
| 1424 | if (tix != 0) { | ||
| 1425 | tix *= 6; | ||
| 1426 | rix *= 6; | ||
| 1427 | for (int j = 6; j > 0; --j) { | ||
| 1428 | if (this.indices[--tix] != rhs.indices[--rix]) { | ||
| 1429 | return false; | ||
| 1430 | } | ||
| 1431 | } | ||
| 1432 | } | ||
| 1433 | } | ||
| 1434 | return true; | ||
| 1435 | } | ||
| 1436 | |||
| 1437 | // implements sgv.setGlyphTransform | ||
| 1438 | void setGlyphTransform(int glyphIndex, AffineTransform newTX) { | ||
| 1439 | |||
| 1440 | // we store all the glyph transforms as a double array, and for each glyph there | ||
| 1441 | // is an entry in the txIndices array indicating which transform to use. 0 means | ||
| 1442 | // there's no transform, 1 means use the first transform (the 6 doubles at offset | ||
| 1443 | // 0), 2 means use the second transform (the 6 doubles at offset 6), etc. | ||
| 1444 | // | ||
| 1445 | // Since this can be called multiple times, and since the number of transforms | ||
| 1446 | // affects the time it takes to construct the glyphs, we try to keep the arrays as | ||
| 1447 | // compact as possible, by removing transforms that are no longer used, and reusing | ||
| 1448 | // transforms where we already have them. | ||
| 1449 | |||
| 1450 | double[] temp = new double[6]; | ||
| 1451 | boolean isIdentity = true; | ||
| 1452 | if (newTX == null || newTX.isIdentity()) { | ||
| 1453 | // Fill in temp | ||
| 1454 | temp[0] = temp[3] = 1.0; | ||
| 1455 | } | ||
| 1456 | else { | ||
| 1457 | isIdentity = false; | ||
| 1458 | newTX.getMatrix(temp); | ||
| 1459 | } | ||
| 1460 | |||
| 1461 | if (indices == null) { | ||
| 1462 | if (isIdentity) { // no change | ||
| 1463 | return; | ||
| 1464 | } | ||
| 1465 | |||
| 1466 | indices = new int[sgv.glyphs.length]; | ||
| 1467 | indices[glyphIndex] = 1; | ||
| 1468 | transforms = temp; | ||
| 1469 | } else { | ||
| 1470 | boolean addSlot = false; // assume we're not growing | ||
| 1471 | int newIndex = -1; | ||
| 1472 | if (isIdentity) { | ||
| 1473 | newIndex = 0; // might shrink | ||
| 1474 | } else { | ||
| 1475 | addSlot = true; // assume no match | ||
| 1476 | int i; | ||
| 1477 | loop: | ||
| 1478 | for (i = 0; i < transforms.length; i += 6) { | ||
| 1479 | for (int j = 0; j < 6; ++j) { | ||
| 1480 | if (transforms[i + j] != temp[j]) { | ||
| 1481 | continue loop; | ||
| 1482 | } | ||
| 1483 | } | ||
| 1484 | addSlot = false; | ||
| 1485 | break; | ||
| 1486 | } | ||
| 1487 | newIndex = i / 6 + 1; // if no match, end of list | ||
| 1488 | } | ||
| 1489 | |||
| 1490 | // if we're using the same transform, nothing to do | ||
| 1491 | int oldIndex = indices[glyphIndex]; | ||
| 1492 | if (newIndex != oldIndex) { | ||
| 1493 | // see if we are removing last use of the old slot | ||
| 1494 | boolean removeSlot = false; | ||
| 1495 | if (oldIndex != 0) { | ||
| 1496 | removeSlot = true; | ||
| 1497 | for (int i = 0; i < indices.length; ++i) { | ||
| 1498 | if (indices[i] == oldIndex && i != glyphIndex) { | ||
| 1499 | removeSlot = false; | ||
| 1500 | break; | ||
| 1501 | } | ||
| 1502 | } | ||
| 1503 | } | ||
| 1504 | |||
| 1505 | if (removeSlot && addSlot) { // reuse old slot with new transform | ||
| 1506 | newIndex = oldIndex; | ||
| 1507 | System.arraycopy(temp, 0, transforms, (newIndex - 1) * 6, 6); | ||
| 1508 | } else if (removeSlot) { | ||
| 1509 | if (transforms.length == 6) { // removing last one, so clear arrays | ||
| 1510 | indices = null; | ||
| 1511 | transforms = null; | ||
| 1512 | |||
| 1513 | sgv.clearCaches(glyphIndex); | ||
| 1514 | sgv.clearFlags(FLAG_HAS_TRANSFORMS); | ||
| 1515 | strikesRef = null; | ||
| 1516 | |||
| 1517 | return; | ||
| 1518 | } | ||
| 1519 | |||
| 1520 | double[] ttemp = new double[transforms.length - 6]; | ||
| 1521 | System.arraycopy(transforms, 0, ttemp, 0, (oldIndex - 1) * 6); | ||
| 1522 | System.arraycopy(transforms, oldIndex * 6, ttemp, (oldIndex - 1) * 6, | ||
| 1523 | transforms.length - oldIndex * 6); | ||
| 1524 | transforms = ttemp; | ||
| 1525 | |||
| 1526 | // clean up indices | ||
| 1527 | for (int i = 0; i < indices.length; ++i) { | ||
| 1528 | if (indices[i] > oldIndex) { // ignore == oldIndex, it's going away | ||
| 1529 | indices[i] -= 1; | ||
| 1530 | } | ||
| 1531 | } | ||
| 1532 | if (newIndex > oldIndex) { // don't forget to decrement this too if we need to | ||
| 1533 | --newIndex; | ||
| 1534 | } | ||
| 1535 | } else if (addSlot) { | ||
| 1536 | double[] ttemp = new double[transforms.length + 6]; | ||
| 1537 | System.arraycopy(transforms, 0, ttemp, 0, transforms.length); | ||
| 1538 | System.arraycopy(temp, 0, ttemp, transforms.length, 6); | ||
| 1539 | transforms = ttemp; | ||
| 1540 | } | ||
| 1541 | |||
| 1542 | indices[glyphIndex] = newIndex; | ||
| 1543 | } | ||
| 1544 | } | ||
| 1545 | |||
| 1546 | sgv.clearCaches(glyphIndex); | ||
| 1547 | sgv.addFlags(FLAG_HAS_TRANSFORMS); | ||
| 1548 | strikesRef = null; | ||
| 1549 | } | ||
| 1550 | |||
| 1551 | // implements sgv.getGlyphTransform | ||
| 1552 | AffineTransform getGlyphTransform(int ix) { | ||
| 1553 | int index = indices[ix]; | ||
| 1554 | if (index == 0) { | ||
| 1555 | return null; | ||
| 1556 | } | ||
| 1557 | |||
| 1558 | int x = (index - 1) * 6; | ||
| 1559 | return new AffineTransform(transforms[x + 0], | ||
| 1560 | transforms[x + 1], | ||
| 1561 | transforms[x + 2], | ||
| 1562 | transforms[x + 3], | ||
| 1563 | transforms[x + 4], | ||
| 1564 | transforms[x + 5]); | ||
| 1565 | } | ||
| 1566 | |||
| 1567 | int transformCount() { | ||
| 1568 | if (transforms == null) { | ||
| 1569 | return 0; | ||
| 1570 | } | ||
| 1571 | return transforms.length / 6; | ||
| 1572 | } | ||
| 1573 | |||
| 1574 | /** | ||
| 1575 | * The strike cache works like this. | ||
| 1576 | * | ||
| 1577 | * -Each glyph is thought of as having a transform, usually identity. | ||
| 1578 | * -Each request for a strike is based on a device transform, either the | ||
| 1579 | * one in the frc or the rendering transform. | ||
| 1580 | * -For general info, strikes are held with soft references. | ||
| 1581 | * -When rendering, strikes must be held with hard references for the | ||
| 1582 | * duration of the rendering call. GlyphList will have to hold this | ||
| 1583 | * info along with the image and position info, but toss the strike info | ||
| 1584 | * when done. | ||
| 1585 | * -Build the strike cache as needed. If the dev transform we want to use | ||
| 1586 | * has changed from the last time it is built, the cache is flushed by | ||
| 1587 | * the caller before these methods are called. | ||
| 1588 | * | ||
| 1589 | * Use a tx that doesn't include translation components of dst tx. | ||
| 1590 | */ | ||
| 1591 | Object setupGlyphImages(long[] images, float[] positions, AffineTransform tx) { | ||
| 1592 | int len = sgv.glyphs.length; | ||
| 1593 | |||
| 1594 | GlyphStrike[] sl = getAllStrikes(); | ||
| 1595 | for (int i = 0; i < len; ++i) { | ||
| 1596 | GlyphStrike gs = sl[indices[i]]; | ||
| 1597 | int glyphID = sgv.glyphs[i]; | ||
| 1598 | images[i] = gs.strike.getGlyphImagePtr(glyphID); | ||
| 1599 | |||
| 1600 | gs.getGlyphPosition(glyphID, i*2, sgv.positions, positions); | ||
| 1601 | } | ||
| 1602 | tx.transform(positions, 0, positions, 0, len); | ||
| 1603 | |||
| 1604 | return sl; | ||
| 1605 | } | ||
| 1606 | |||
| 1607 | Rectangle getGlyphsPixelBounds(AffineTransform tx, float x, float y, int start, int count) { | ||
| 1608 | Rectangle result = null; | ||
| 1609 | Rectangle r = new Rectangle(); | ||
| 1610 | Point2D.Float pt = new Point.Float(); | ||
| 1611 | int n = start * 2; | ||
| 1612 | while (--count >= 0) { | ||
| 1613 | GlyphStrike gs = getStrike(start); | ||
| 1614 | pt.x = x + sgv.positions[n++] + gs.dx; | ||
| 1615 | pt.y = y + sgv.positions[n++] + gs.dy; | ||
| 1616 | tx.transform(pt, pt); | ||
| 1617 | gs.strike.getGlyphImageBounds(sgv.glyphs[start++], pt, r); | ||
| 1618 | if (!r.isEmpty()) { | ||
| 1619 | if (result == null) { | ||
| 1620 | result = new Rectangle(r); | ||
| 1621 | } else { | ||
| 1622 | result.add(r); | ||
| 1623 | } | ||
| 1624 | } | ||
| 1625 | } | ||
| 1626 | return result != null ? result : r; | ||
| 1627 | } | ||
| 1628 | |||
| 1629 | GlyphStrike getStrike(int glyphIndex) { | ||
| 1630 | if (indices != null) { | ||
| 1631 | GlyphStrike[] strikes = getStrikeArray(); | ||
| 1632 | return getStrikeAtIndex(strikes, indices[glyphIndex]); | ||
| 1633 | } | ||
| 1634 | return sgv.getDefaultStrike(); | ||
| 1635 | } | ||
| 1636 | |||
| 1637 | private GlyphStrike[] getAllStrikes() { | ||
| 1638 | if (indices == null) { | ||
| 1639 | return null; | ||
| 1640 | } | ||
| 1641 | |||
| 1642 | GlyphStrike[] strikes = getStrikeArray(); | ||
| 1643 | if (!haveAllStrikes) { | ||
| 1644 | for (int i = 0; i < strikes.length; ++i) { | ||
| 1645 | getStrikeAtIndex(strikes, i); | ||
| 1646 | } | ||
| 1647 | haveAllStrikes = true; | ||
| 1648 | } | ||
| 1649 | |||
| 1650 | return strikes; | ||
| 1651 | } | ||
| 1652 | |||
| 1653 | private GlyphStrike[] getStrikeArray() { | ||
| 1654 | GlyphStrike[] strikes = null; | ||
| 1655 | if (strikesRef != null) { | ||
| 1656 | strikes = (GlyphStrike[])strikesRef.get(); | ||
| 1657 | } | ||
| 1658 | if (strikes == null) { | ||
| 1659 | haveAllStrikes = false; | ||
| 1660 | strikes = new GlyphStrike[transformCount() + 1]; | ||
| 1661 | strikesRef = new SoftReference(strikes); | ||
| 1662 | } | ||
| 1663 | |||
| 1664 | return strikes; | ||
| 1665 | } | ||
| 1666 | |||
| 1667 | private GlyphStrike getStrikeAtIndex(GlyphStrike[] strikes, int strikeIndex) { | ||
| 1668 | GlyphStrike strike = strikes[strikeIndex]; | ||
| 1669 | if (strike == null) { | ||
| 1670 | if (strikeIndex == 0) { | ||
| 1671 | strike = sgv.getDefaultStrike(); | ||
| 1672 | } else { | ||
| 1673 | int ix = (strikeIndex - 1) * 6; | ||
| 1674 | AffineTransform gtx = new AffineTransform(transforms[ix], | ||
| 1675 | transforms[ix+1], | ||
| 1676 | transforms[ix+2], | ||
| 1677 | transforms[ix+3], | ||
| 1678 | transforms[ix+4], | ||
| 1679 | transforms[ix+5]); | ||
| 1680 | |||
| 1681 | strike = GlyphStrike.create(sgv, sgv.dtx, gtx); | ||
| 1682 | } | ||
| 1683 | strikes[strikeIndex] = strike; | ||
| 1684 | } | ||
| 1685 | return strike; | ||
| 1686 | } | ||
| 1687 | } | ||
| 1688 | |||
| 1689 | // This adjusts the metrics by the translation components of the glyph | ||
| 1690 | // transform. It is done here since the translation is not known by the | ||
| 1691 | // strike. | ||
| 1692 | // It adjusts the position of the image and the advance. | ||
| 1693 | |||
| 1694 | public static final class GlyphStrike { | ||
| 1695 | StandardGlyphVector sgv; | ||
| 1696 | FontStrike strike; // hard reference | ||
| 1697 | float dx; | ||
| 1698 | float dy; | ||
| 1699 | |||
| 1700 | static GlyphStrike create(StandardGlyphVector sgv, AffineTransform dtx, AffineTransform gtx) { | ||
| 1701 | float dx = 0; | ||
| 1702 | float dy = 0; | ||
| 1703 | |||
| 1704 | AffineTransform tx = sgv.ftx; | ||
| 1705 | if (!dtx.isIdentity() || gtx != null) { | ||
| 1706 | tx = new AffineTransform(sgv.ftx); | ||
| 1707 | if (gtx != null) { | ||
| 1708 | tx.preConcatenate(gtx); | ||
| 1709 | dx = (float)tx.getTranslateX(); // uses ftx then gtx to get translation | ||
| 1710 | dy = (float)tx.getTranslateY(); | ||
| 1711 | } | ||
| 1712 | if (!dtx.isIdentity()) { | ||
| 1713 | tx.preConcatenate(dtx); | ||
| 1714 | } | ||
| 1715 | } | ||
| 1716 | |||
| 1717 | int ptSize = 1; // only matters for 'gasp' case. | ||
| 1718 | Object aaHint = sgv.frc.getAntiAliasingHint(); | ||
| 1719 | if (aaHint == VALUE_TEXT_ANTIALIAS_GASP) { | ||
| 1720 | /* Must pass in the calculated point size for rendering. | ||
| 1721 | * If the glyph tx is anything other than identity or a | ||
| 1722 | * simple translate, calculate the transformed point size. | ||
| 1723 | */ | ||
| 1724 | if (!tx.isIdentity() && | ||
| 1725 | (tx.getType() & ~AffineTransform.TYPE_TRANSLATION) != 0) { | ||
| 1726 | double shearx = tx.getShearX(); | ||
| 1727 | if (shearx != 0) { | ||
| 1728 | double scaley = tx.getScaleY(); | ||
| 1729 | ptSize = | ||
| 1730 | (int)Math.sqrt(shearx * shearx + scaley * scaley); | ||
| 1731 | } else { | ||
| 1732 | ptSize = (int)(Math.abs(tx.getScaleY())); | ||
| 1733 | } | ||
| 1734 | } | ||
| 1735 | } | ||
| 1736 | int aa = FontStrikeDesc.getAAHintIntVal(aaHint,sgv.font2D, ptSize); | ||
| 1737 | int fm = FontStrikeDesc.getFMHintIntVal | ||
| 1738 | (sgv.frc.getFractionalMetricsHint()); | ||
| 1739 | FontStrikeDesc desc = new FontStrikeDesc(dtx, | ||
| 1740 | tx, | ||
| 1741 | sgv.font.getStyle(), | ||
| 1742 | aa, fm); | ||
| 1743 | |||
| 1744 | FontStrike strike = sgv.font2D.getStrike(desc); // !!! getStrike(desc, false) | ||
| 1745 | |||
| 1746 | return new GlyphStrike(sgv, strike, dx, dy); | ||
| 1747 | } | ||
| 1748 | |||
| 1749 | private GlyphStrike(StandardGlyphVector sgv, FontStrike strike, float dx, float dy) { | ||
| 1750 | this.sgv = sgv; | ||
| 1751 | this.strike = strike; | ||
| 1752 | this.dx = dx; | ||
| 1753 | this.dy = dy; | ||
| 1754 | } | ||
| 1755 | |||
| 1756 | void getADL(ADL result) { | ||
| 1757 | StrikeMetrics sm = strike.getFontMetrics(); | ||
| 1758 | Point2D.Float delta = null; | ||
| 1759 | if (sgv.font.isTransformed()) { | ||
| 1760 | delta = new Point2D.Float(); | ||
| 1761 | delta.x = (float)sgv.font.getTransform().getTranslateX(); | ||
| 1762 | delta.y = (float)sgv.font.getTransform().getTranslateY(); | ||
| 1763 | } | ||
| 1764 | |||
| 1765 | result.ascentX = -sm.ascentX; | ||
| 1766 | result.ascentY = -sm.ascentY; | ||
| 1767 | result.descentX = sm.descentX; | ||
| 1768 | result.descentY = sm.descentY; | ||
| 1769 | result.leadingX = sm.leadingX; | ||
| 1770 | result.leadingY = sm.leadingY; | ||
| 1771 | } | ||
| 1772 | |||
| 1773 | void getGlyphPosition(int glyphID, int ix, float[] positions, float[] result) { | ||
| 1774 | result[ix] = positions[ix] + dx; | ||
| 1775 | ++ix; | ||
| 1776 | result[ix] = positions[ix] + dy; | ||
| 1777 | } | ||
| 1778 | |||
| 1779 | void addDefaultGlyphAdvance(int glyphID, Point2D.Float result) { | ||
| 1780 | // !!! change this API? Creates unnecessary garbage. Also the name doesn't quite fit. | ||
| 1781 | // strike.addGlyphAdvance(Point2D.Float adv); // hey, whaddya know, matches my api :-) | ||
| 1782 | Point2D.Float adv = strike.getGlyphMetrics(glyphID); | ||
| 1783 | result.x += adv.x + dx; | ||
| 1784 | result.y += adv.y + dy; | ||
| 1785 | } | ||
| 1786 | |||
| 1787 | Rectangle2D getGlyphOutlineBounds(int glyphID, float x, float y) { | ||
| 1788 | Rectangle2D result = null; | ||
| 1789 | if (sgv.invdtx == null) { | ||
| 1790 | result = new Rectangle2D.Float(); | ||
| 1791 | result.setRect(strike.getGlyphOutlineBounds(glyphID)); // don't mutate cached rect | ||
| 1792 | } else { | ||
| 1793 | GeneralPath gp = strike.getGlyphOutline(glyphID, 0, 0); | ||
| 1794 | gp.transform(sgv.invdtx); | ||
| 1795 | result = gp.getBounds2D(); | ||
| 1796 | } | ||
| 1797 | /* Since x is the logical advance of the glyph to this point. | ||
| 1798 | * Because of the way that Rectangle.union is specified, this | ||
| 1799 | * means that subsequent unioning of a rect including that | ||
| 1800 | * will be affected, even if the glyph is empty. So skip such | ||
| 1801 | * cases. This alone isn't a complete solution since x==0 | ||
| 1802 | * may also not be what is wanted. The code that does the | ||
| 1803 | * unioning also needs to be aware to ignore empty glyphs. | ||
| 1804 | */ | ||
| 1805 | if (!result.isEmpty()) { | ||
| 1806 | result.setRect(result.getMinX() + x + dx, | ||
| 1807 | result.getMinY() + y + dy, | ||
| 1808 | result.getWidth(), result.getHeight()); | ||
| 1809 | } | ||
| 1810 | return result; | ||
| 1811 | } | ||
| 1812 | |||
| 1813 | void appendGlyphOutline(int glyphID, GeneralPath result, float x, float y) { | ||
| 1814 | // !!! fontStrike needs a method for this. For that matter, GeneralPath does. | ||
| 1815 | GeneralPath gp = null; | ||
| 1816 | if (sgv.invdtx == null) { | ||
| 1817 | gp = strike.getGlyphOutline(glyphID, x + dx, y + dy); | ||
| 1818 | } else { | ||
| 1819 | gp = strike.getGlyphOutline(glyphID, 0, 0); | ||
| 1820 | gp.transform(sgv.invdtx); | ||
| 1821 | gp.transform(AffineTransform.getTranslateInstance(x + dx, y + dy)); | ||
| 1822 | } | ||
| 1823 | PathIterator iterator = gp.getPathIterator(null); | ||
| 1824 | result.append(iterator, false); | ||
| 1825 | } | ||
| 1826 | } | ||
| 1827 | |||
| 1828 | public String toString() { | ||
| 1829 | return appendString(null).toString(); | ||
| 1830 | } | ||
| 1831 | |||
| 1832 | StringBuffer appendString(StringBuffer buf) { | ||
| 1833 | if (buf == null) { | ||
| 1834 | buf = new StringBuffer(); | ||
| 1835 | } | ||
| 1836 | try { | ||
| 1837 | buf.append("SGV{font: "); | ||
| 1838 | buf.append(font.toString()); | ||
| 1839 | buf.append(", frc: "); | ||
| 1840 | buf.append(frc.toString()); | ||
| 1841 | buf.append(", glyphs: ("); | ||
| 1842 | buf.append(glyphs.length); | ||
| 1843 | buf.append(")["); | ||
| 1844 | for (int i = 0; i < glyphs.length; ++i) { | ||
| 1845 | if (i > 0) { | ||
| 1846 | buf.append(", "); | ||
| 1847 | } | ||
| 1848 | buf.append(Integer.toHexString(glyphs[i])); | ||
| 1849 | } | ||
| 1850 | buf.append("]"); | ||
| 1851 | if (positions != null) { | ||
| 1852 | buf.append(", positions: ("); | ||
| 1853 | buf.append(positions.length); | ||
| 1854 | buf.append(")["); | ||
| 1855 | for (int i = 0; i < positions.length; i += 2) { | ||
| 1856 | if (i > 0) { | ||
| 1857 | buf.append(", "); | ||
| 1858 | } | ||
| 1859 | buf.append(positions[i]); | ||
| 1860 | buf.append("@"); | ||
| 1861 | buf.append(positions[i+1]); | ||
| 1862 | } | ||
| 1863 | buf.append("]"); | ||
| 1864 | } | ||
| 1865 | if (charIndices != null) { | ||
| 1866 | buf.append(", indices: ("); | ||
| 1867 | buf.append(charIndices.length); | ||
| 1868 | buf.append(")["); | ||
| 1869 | for (int i = 0; i < charIndices.length; ++i) { | ||
| 1870 | if (i > 0) { | ||
| 1871 | buf.append(", "); | ||
| 1872 | } | ||
| 1873 | buf.append(charIndices[i]); | ||
| 1874 | } | ||
| 1875 | buf.append("]"); | ||
| 1876 | } | ||
| 1877 | buf.append(", flags:"); | ||
| 1878 | if (getLayoutFlags() == 0) { | ||
| 1879 | buf.append(" default"); | ||
| 1880 | } else { | ||
| 1881 | if ((flags & FLAG_HAS_TRANSFORMS) != 0) { | ||
| 1882 | buf.append(" tx"); | ||
| 1883 | } | ||
| 1884 | if ((flags & FLAG_HAS_POSITION_ADJUSTMENTS) != 0) { | ||
| 1885 | buf.append(" pos"); | ||
| 1886 | } | ||
| 1887 | if ((flags & FLAG_RUN_RTL) != 0) { | ||
| 1888 | buf.append(" rtl"); | ||
| 1889 | } | ||
| 1890 | if ((flags & FLAG_COMPLEX_GLYPHS) != 0) { | ||
| 1891 | buf.append(" complex"); | ||
| 1892 | } | ||
| 1893 | } | ||
| 1894 | } | ||
| 1895 | catch(Exception e) { | ||
| 1896 | buf.append(" " + e.getMessage()); | ||
| 1897 | } | ||
| 1898 | buf.append("}"); | ||
| 1899 | |||
| 1900 | return buf; | ||
| 1901 | } | ||
| 1902 | |||
| 1903 | static class ADL { | ||
| 1904 | public float ascentX; | ||
| 1905 | public float ascentY; | ||
| 1906 | public float descentX; | ||
| 1907 | public float descentY; | ||
| 1908 | public float leadingX; | ||
| 1909 | public float leadingY; | ||
| 1910 | |||
| 1911 | public String toString() { | ||
| 1912 | return toStringBuffer(null).toString(); | ||
| 1913 | } | ||
| 1914 | |||
| 1915 | protected StringBuffer toStringBuffer(StringBuffer result) { | ||
| 1916 | if (result == null) { | ||
| 1917 | result = new StringBuffer(); | ||
| 1918 | } | ||
| 1919 | result.append("ax: "); | ||
| 1920 | result.append(ascentX); | ||
| 1921 | result.append(" ay: "); | ||
| 1922 | result.append(ascentY); | ||
| 1923 | result.append(" dx: "); | ||
| 1924 | result.append(descentX); | ||
| 1925 | result.append(" dy: "); | ||
| 1926 | result.append(descentY); | ||
| 1927 | result.append(" lx: "); | ||
| 1928 | result.append(leadingX); | ||
| 1929 | result.append(" ly: "); | ||
| 1930 | result.append(leadingY); | ||
| 1931 | |||
| 1932 | return result; | ||
| 1933 | } | ||
| 1934 | } | ||
| 1935 | } | ||