Showing the changes to java/pkg/sun/font/GlyphLayout.java (1 of 18352 files). Show all
Unified · Split
| @@ -0,0 +1,668 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2003, 2008, 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 | /* | ||
| 27 | * | ||
| 28 | * (C) Copyright IBM Corp. 1999-2003 - All Rights Reserved | ||
| 29 | * | ||
| 30 | * The original version of this source code and documentation is | ||
| 31 | * copyrighted and owned by IBM. These materials are provided | ||
| 32 | * under terms of a License Agreement between IBM and Sun. | ||
| 33 | * This technology is protected by multiple US and International | ||
| 34 | * patents. This notice and attribution to IBM may not be removed. | ||
| 35 | */ | ||
| 36 | |||
| 37 | /* | ||
| 38 | * GlyphLayout is used to process a run of text into a run of run of | ||
| 39 | * glyphs, optionally with position and char mapping info. | ||
| 40 | * | ||
| 41 | * The text has already been processed for numeric shaping and bidi. | ||
| 42 | * The run of text that layout works on has a single bidi level. It | ||
| 43 | * also has a single font/style. Some operations need context to work | ||
| 44 | * on (shaping, script resolution) so context for the text run text is | ||
| 45 | * provided. It is assumed that the text array contains sufficient | ||
| 46 | * context, and the offset and count delimit the portion of the text | ||
| 47 | * that needs to actually be processed. | ||
| 48 | * | ||
| 49 | * The font might be a composite font. Layout generally requires | ||
| 50 | * tables from a single physical font to operate, and so it must | ||
| 51 | * resolve the 'single' font run into runs of physical fonts. | ||
| 52 | * | ||
| 53 | * Some characters are supported by several fonts of a composite, and | ||
| 54 | * in order to properly emulate the glyph substitution behavior of a | ||
| 55 | * single physical font, these characters might need to be mapped to | ||
| 56 | * different physical fonts. The script code that is assigned | ||
| 57 | * characters normally considered 'common script' can be used to | ||
| 58 | * resolve which physical font to use for these characters. The input | ||
| 59 | * to the char to glyph mapper (which assigns physical fonts as it | ||
| 60 | * processes the glyphs) should include the script code, and the | ||
| 61 | * mapper should operate on runs of a single script. | ||
| 62 | * | ||
| 63 | * To perform layout, call get() to get a new (or reuse an old) | ||
| 64 | * GlyphLayout, call layout on it, then call done(GlyphLayout) when | ||
| 65 | * finished. There's no particular problem if you don't call done, | ||
| 66 | * but it assists in reuse of the GlyphLayout. | ||
| 67 | */ | ||
| 68 | |||
| 69 | package sun.font; | ||
| 70 | |||
| 71 | import java.lang.ref.SoftReference; | ||
| 72 | import java.awt.Font; | ||
| 73 | import java.awt.font.FontRenderContext; | ||
| 74 | import java.awt.font.GlyphVector; | ||
| 75 | import java.awt.geom.AffineTransform; | ||
| 76 | import java.awt.geom.NoninvertibleTransformException; | ||
| 77 | import java.awt.geom.Point2D; | ||
| 78 | import java.util.ArrayList; | ||
| 79 | import java.util.concurrent.ConcurrentHashMap; | ||
| 80 | |||
| 81 | import static java.lang.Character.*; | ||
| 82 | |||
| 83 | public final class GlyphLayout { | ||
| 84 | // data for glyph vector | ||
| 85 | private GVData _gvdata; | ||
| 86 | |||
| 87 | // cached glyph layout data for reuse | ||
| 88 | private static volatile GlyphLayout cache; // reusable | ||
| 89 | |||
| 90 | private LayoutEngineFactory _lef; // set when get is called, unset when done is called | ||
| 91 | private TextRecord _textRecord; // the text we're working on, used by iterators | ||
| 92 | private ScriptRun _scriptRuns; // iterator over script runs | ||
| 93 | private FontRunIterator _fontRuns; // iterator over physical fonts in a composite | ||
| 94 | private int _ercount; | ||
| 95 | private ArrayList _erecords; | ||
| 96 | private Point2D.Float _pt; | ||
| 97 | private FontStrikeDesc _sd; | ||
| 98 | private float[] _mat; | ||
| 99 | private int _typo_flags; | ||
| 100 | private int _offset; | ||
| 101 | |||
| 102 | public static final class LayoutEngineKey { | ||
| 103 | private Font2D font; | ||
| 104 | private int script; | ||
| 105 | private int lang; | ||
| 106 | |||
| 107 | LayoutEngineKey() { | ||
| 108 | } | ||
| 109 | |||
| 110 | LayoutEngineKey(Font2D font, int script, int lang) { | ||
| 111 | init(font, script, lang); | ||
| 112 | } | ||
| 113 | |||
| 114 | void init(Font2D font, int script, int lang) { | ||
| 115 | this.font = font; | ||
| 116 | this.script = script; | ||
| 117 | this.lang = lang; | ||
| 118 | } | ||
| 119 | |||
| 120 | LayoutEngineKey copy() { | ||
| 121 | return new LayoutEngineKey(font, script, lang); | ||
| 122 | } | ||
| 123 | |||
| 124 | Font2D font() { | ||
| 125 | return font; | ||
| 126 | } | ||
| 127 | |||
| 128 | int script() { | ||
| 129 | return script; | ||
| 130 | } | ||
| 131 | |||
| 132 | int lang() { | ||
| 133 | return lang; | ||
| 134 | } | ||
| 135 | |||
| 136 | public boolean equals(Object rhs) { | ||
| 137 | if (this == rhs) return true; | ||
| 138 | if (rhs == null) return false; | ||
| 139 | try { | ||
| 140 | LayoutEngineKey that = (LayoutEngineKey)rhs; | ||
| 141 | return this.script == that.script && | ||
| 142 | this.lang == that.lang && | ||
| 143 | this.font.equals(that.font); | ||
| 144 | } | ||
| 145 | catch (ClassCastException e) { | ||
| 146 | return false; | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | public int hashCode() { | ||
| 151 | return script ^ lang ^ font.hashCode(); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | public static interface LayoutEngineFactory { | ||
| 156 | /** | ||
| 157 | * Given a font, script, and language, determine a layout engine to use. | ||
| 158 | */ | ||
| 159 | public LayoutEngine getEngine(Font2D font, int script, int lang); | ||
| 160 | |||
| 161 | /** | ||
| 162 | * Given a key, determine a layout engine to use. | ||
| 163 | */ | ||
| 164 | public LayoutEngine getEngine(LayoutEngineKey key); | ||
| 165 | } | ||
| 166 | |||
| 167 | public static interface LayoutEngine { | ||
| 168 | /** | ||
| 169 | * Given a strike descriptor, text, rtl flag, and starting point, append information about | ||
| 170 | * glyphs, positions, and character indices to the glyphvector data, and advance the point. | ||
| 171 | * | ||
| 172 | * If the GVData does not have room for the glyphs, throws an IndexOutOfBoundsException and | ||
| 173 | * leave pt and the gvdata unchanged. | ||
| 174 | */ | ||
| 175 | public void layout(FontStrikeDesc sd, float[] mat, int gmask, | ||
| 176 | int baseIndex, TextRecord text, int typo_flags, Point2D.Float pt, GVData data); | ||
| 177 | } | ||
| 178 | |||
| 179 | /** | ||
| 180 | * Return a new instance of GlyphLayout, using the provided layout engine factory. | ||
| 181 | * If null, the system layout engine factory will be used. | ||
| 182 | */ | ||
| 183 | public static GlyphLayout get(LayoutEngineFactory lef) { | ||
| 184 | if (lef == null) { | ||
| 185 | lef = SunLayoutEngine.instance(); | ||
| 186 | } | ||
| 187 | GlyphLayout result = null; | ||
| 188 | synchronized(GlyphLayout.class) { | ||
| 189 | if (cache != null) { | ||
| 190 | result = cache; | ||
| 191 | cache = null; | ||
| 192 | } | ||
| 193 | } | ||
| 194 | if (result == null) { | ||
| 195 | result = new GlyphLayout(); | ||
| 196 | } | ||
| 197 | result._lef = lef; | ||
| 198 | return result; | ||
| 199 | } | ||
| 200 | |||
| 201 | /** | ||
| 202 | * Return the old instance of GlyphLayout when you are done. This enables reuse | ||
| 203 | * of GlyphLayout objects. | ||
| 204 | */ | ||
| 205 | public static void done(GlyphLayout gl) { | ||
| 206 | gl._lef = null; | ||
| 207 | cache = gl; // object reference assignment is thread safe, it says here... | ||
| 208 | } | ||
| 209 | |||
| 210 | private static final class SDCache { | ||
| 211 | public Font key_font; | ||
| 212 | public FontRenderContext key_frc; | ||
| 213 | |||
| 214 | public AffineTransform dtx; | ||
| 215 | public AffineTransform invdtx; | ||
| 216 | public AffineTransform gtx; | ||
| 217 | public Point2D.Float delta; | ||
| 218 | public FontStrikeDesc sd; | ||
| 219 | |||
| 220 | private SDCache(Font font, FontRenderContext frc) { | ||
| 221 | key_font = font; | ||
| 222 | key_frc = frc; | ||
| 223 | |||
| 224 | // !!! add getVectorTransform and hasVectorTransform to frc? then | ||
| 225 | // we could just skip this work... | ||
| 226 | |||
| 227 | dtx = frc.getTransform(); | ||
| 228 | dtx.setTransform(dtx.getScaleX(), dtx.getShearY(), | ||
| 229 | dtx.getShearX(), dtx.getScaleY(), | ||
| 230 | 0, 0); | ||
| 231 | if (!dtx.isIdentity()) { | ||
| 232 | try { | ||
| 233 | invdtx = dtx.createInverse(); | ||
| 234 | } | ||
| 235 | catch (NoninvertibleTransformException e) { | ||
| 236 | throw new InternalError(); | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | float ptSize = font.getSize2D(); | ||
| 241 | if (font.isTransformed()) { | ||
| 242 | gtx = font.getTransform(); | ||
| 243 | gtx.scale(ptSize, ptSize); | ||
| 244 | delta = new Point2D.Float((float)gtx.getTranslateX(), | ||
| 245 | (float)gtx.getTranslateY()); | ||
| 246 | gtx.setTransform(gtx.getScaleX(), gtx.getShearY(), | ||
| 247 | gtx.getShearX(), gtx.getScaleY(), | ||
| 248 | 0, 0); | ||
| 249 | gtx.preConcatenate(dtx); | ||
| 250 | } else { | ||
| 251 | delta = ZERO_DELTA; | ||
| 252 | gtx = new AffineTransform(dtx); | ||
| 253 | gtx.scale(ptSize, ptSize); | ||
| 254 | } | ||
| 255 | |||
| 256 | /* Similar logic to that used in SunGraphics2D.checkFontInfo(). | ||
| 257 | * Whether a grey (AA) strike is needed is size dependent if | ||
| 258 | * AA mode is 'gasp'. | ||
| 259 | */ | ||
| 260 | int aa = | ||
| 261 | FontStrikeDesc.getAAHintIntVal(frc.getAntiAliasingHint(), | ||
| 262 | FontUtilities.getFont2D(font), | ||
| 263 | (int)Math.abs(ptSize)); | ||
| 264 | int fm = FontStrikeDesc.getFMHintIntVal | ||
| 265 | (frc.getFractionalMetricsHint()); | ||
| 266 | sd = new FontStrikeDesc(dtx, gtx, font.getStyle(), aa, fm); | ||
| 267 | } | ||
| 268 | |||
| 269 | private static final Point2D.Float ZERO_DELTA = new Point2D.Float(); | ||
| 270 | |||
| 271 | private static | ||
| 272 | SoftReference<ConcurrentHashMap<SDKey, SDCache>> cacheRef; | ||
| 273 | |||
| 274 | private static final class SDKey { | ||
| 275 | private final Font font; | ||
| 276 | private final FontRenderContext frc; | ||
| 277 | private final int hash; | ||
| 278 | |||
| 279 | SDKey(Font font, FontRenderContext frc) { | ||
| 280 | this.font = font; | ||
| 281 | this.frc = frc; | ||
| 282 | this.hash = font.hashCode() ^ frc.hashCode(); | ||
| 283 | } | ||
| 284 | |||
| 285 | public int hashCode() { | ||
| 286 | return hash; | ||
| 287 | } | ||
| 288 | |||
| 289 | public boolean equals(Object o) { | ||
| 290 | try { | ||
| 291 | SDKey rhs = (SDKey)o; | ||
| 292 | return | ||
| 293 | hash == rhs.hash && | ||
| 294 | font.equals(rhs.font) && | ||
| 295 | frc.equals(rhs.frc); | ||
| 296 | } | ||
| 297 | catch (ClassCastException e) { | ||
| 298 | } | ||
| 299 | return false; | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | public static SDCache get(Font font, FontRenderContext frc) { | ||
| 304 | |||
| 305 | // It is possible a translation component will be in the FRC. | ||
| 306 | // It doesn't affect us except adversely as we would consider | ||
| 307 | // FRC's which are really the same to be different. If we | ||
| 308 | // detect a translation component, then we need to exclude it | ||
| 309 | // by creating a new transform which excludes the translation. | ||
| 310 | if (frc.isTransformed()) { | ||
| 311 | AffineTransform transform = frc.getTransform(); | ||
| 312 | if (transform.getTranslateX() != 0 || | ||
| 313 | transform.getTranslateY() != 0) { | ||
| 314 | transform = new AffineTransform(transform.getScaleX(), | ||
| 315 | transform.getShearY(), | ||
| 316 | transform.getShearX(), | ||
| 317 | transform.getScaleY(), | ||
| 318 | 0, 0); | ||
| 319 | frc = new FontRenderContext(transform, | ||
| 320 | frc.getAntiAliasingHint(), | ||
| 321 | frc.getFractionalMetricsHint() | ||
| 322 | ); | ||
| 323 | } | ||
| 324 | } | ||
| 325 | |||
| 326 | SDKey key = new SDKey(font, frc); // garbage, yuck... | ||
| 327 | ConcurrentHashMap<SDKey, SDCache> cache = null; | ||
| 328 | SDCache res = null; | ||
| 329 | if (cacheRef != null) { | ||
| 330 | cache = cacheRef.get(); | ||
| 331 | if (cache != null) { | ||
| 332 | res = cache.get(key); | ||
| 333 | } | ||
| 334 | } | ||
| 335 | if (res == null) { | ||
| 336 | res = new SDCache(font, frc); | ||
| 337 | if (cache == null) { | ||
| 338 | cache = new ConcurrentHashMap<SDKey, SDCache>(10); | ||
| 339 | cacheRef = new | ||
| 340 | SoftReference<ConcurrentHashMap<SDKey, SDCache>>(cache); | ||
| 341 | } else if (cache.size() >= 512) { | ||
| 342 | cache.clear(); | ||
| 343 | } | ||
| 344 | cache.put(key, res); | ||
| 345 | } | ||
| 346 | return res; | ||
| 347 | } | ||
| 348 | } | ||
| 349 | |||
| 350 | /** | ||
| 351 | * Create a glyph vector. | ||
| 352 | * @param font the font to use | ||
| 353 | * @param frc the font render context | ||
| 354 | * @param text the text, including optional context before start and after start + count | ||
| 355 | * @param offset the start of the text to lay out | ||
| 356 | * @param count the length of the text to lay out | ||
| 357 | * @param flags bidi and context flags {@see #java.awt.Font} | ||
| 358 | * @param result a StandardGlyphVector to modify, can be null | ||
| 359 | * @return the layed out glyphvector, if result was passed in, it is returned | ||
| 360 | */ | ||
| 361 | public StandardGlyphVector layout(Font font, FontRenderContext frc, | ||
| 362 | char[] text, int offset, int count, | ||
| 363 | int flags, StandardGlyphVector result) | ||
| 364 | { | ||
| 365 | if (text == null || offset < 0 || count < 0 || (count > text.length - offset)) { | ||
| 366 | throw new IllegalArgumentException(); | ||
| 367 | } | ||
| 368 | |||
| 369 | init(count); | ||
| 370 | |||
| 371 | // need to set after init | ||
| 372 | // go through the back door for this | ||
| 373 | if (font.hasLayoutAttributes()) { | ||
| 374 | AttributeValues values = ((AttributeMap)font.getAttributes()).getValues(); | ||
| 375 | if (values.getKerning() != 0) _typo_flags |= 0x1; | ||
| 376 | if (values.getLigatures() != 0) _typo_flags |= 0x2; | ||
| 377 | } | ||
| 378 | |||
| 379 | _offset = offset; | ||
| 380 | |||
| 381 | // use cache now - can we use the strike cache for this? | ||
| 382 | |||
| 383 | SDCache txinfo = SDCache.get(font, frc); | ||
| 384 | _mat[0] = (float)txinfo.gtx.getScaleX(); | ||
| 385 | _mat[1] = (float)txinfo.gtx.getShearY(); | ||
| 386 | _mat[2] = (float)txinfo.gtx.getShearX(); | ||
| 387 | _mat[3] = (float)txinfo.gtx.getScaleY(); | ||
| 388 | _pt.setLocation(txinfo.delta); | ||
| 389 | |||
| 390 | int lim = offset + count; | ||
| 391 | |||
| 392 | int min = 0; | ||
| 393 | int max = text.length; | ||
| 394 | if (flags != 0) { | ||
| 395 | if ((flags & Font.LAYOUT_RIGHT_TO_LEFT) != 0) { | ||
| 396 | _typo_flags |= 0x80000000; // RTL | ||
| 397 | } | ||
| 398 | |||
| 399 | if ((flags & Font.LAYOUT_NO_START_CONTEXT) != 0) { | ||
| 400 | min = offset; | ||
| 401 | } | ||
| 402 | |||
| 403 | if ((flags & Font.LAYOUT_NO_LIMIT_CONTEXT) != 0) { | ||
| 404 | max = lim; | ||
| 405 | } | ||
| 406 | } | ||
| 407 | |||
| 408 | int lang = -1; // default for now | ||
| 409 | |||
| 410 | Font2D font2D = FontUtilities.getFont2D(font); | ||
| 411 | |||
| 412 | _textRecord.init(text, offset, lim, min, max); | ||
| 413 | int start = offset; | ||
| 414 | if (font2D instanceof CompositeFont) { | ||
| 415 | _scriptRuns.init(text, offset, count); // ??? how to handle 'common' chars | ||
| 416 | _fontRuns.init((CompositeFont)font2D, text, offset, lim); | ||
| 417 | while (_scriptRuns.next()) { | ||
| 418 | int limit = _scriptRuns.getScriptLimit(); | ||
| 419 | int script = _scriptRuns.getScriptCode(); | ||
| 420 | while (_fontRuns.next(script, limit)) { | ||
| 421 | Font2D pfont = _fontRuns.getFont(); | ||
| 422 | /* layout can't deal with NativeFont instances. The | ||
| 423 | * native font is assumed to know of a suitable non-native | ||
| 424 | * substitute font. This currently works because | ||
| 425 | * its consistent with the way NativeFonts delegate | ||
| 426 | * in other cases too. | ||
| 427 | */ | ||
| 428 | if (pfont instanceof NativeFont) { | ||
| 429 | pfont = ((NativeFont)pfont).getDelegateFont(); | ||
| 430 | } | ||
| 431 | int gmask = _fontRuns.getGlyphMask(); | ||
| 432 | int pos = _fontRuns.getPos(); | ||
| 433 | nextEngineRecord(start, pos, script, lang, pfont, gmask); | ||
| 434 | start = pos; | ||
| 435 | } | ||
| 436 | } | ||
| 437 | } else { | ||
| 438 | _scriptRuns.init(text, offset, count); // ??? don't worry about 'common' chars | ||
| 439 | while (_scriptRuns.next()) { | ||
| 440 | int limit = _scriptRuns.getScriptLimit(); | ||
| 441 | int script = _scriptRuns.getScriptCode(); | ||
| 442 | nextEngineRecord(start, limit, script, lang, font2D, 0); | ||
| 443 | start = limit; | ||
| 444 | } | ||
| 445 | } | ||
| 446 | |||
| 447 | int ix = 0; | ||
| 448 | int stop = _ercount; | ||
| 449 | int dir = 1; | ||
| 450 | |||
| 451 | if (_typo_flags < 0) { // RTL | ||
| 452 | ix = stop - 1; | ||
| 453 | stop = -1; | ||
| 454 | dir = -1; | ||
| 455 | } | ||
| 456 | |||
| 457 | // _sd.init(dtx, gtx, font.getStyle(), frc.isAntiAliased(), frc.usesFractionalMetrics()); | ||
| 458 | _sd = txinfo.sd; | ||
| 459 | for (;ix != stop; ix += dir) { | ||
| 460 | EngineRecord er = (EngineRecord)_erecords.get(ix); | ||
| 461 | for (;;) { | ||
| 462 | try { | ||
| 463 | er.layout(); | ||
| 464 | break; | ||
| 465 | } | ||
| 466 | catch (IndexOutOfBoundsException e) { | ||
| 467 | _gvdata.grow(); | ||
| 468 | } | ||
| 469 | } | ||
| 470 | } | ||
| 471 | |||
| 472 | // if (txinfo.invdtx != null) { | ||
| 473 | // _gvdata.adjustPositions(txinfo.invdtx); | ||
| 474 | // } | ||
| 475 | |||
| 476 | StandardGlyphVector gv = _gvdata.createGlyphVector(font, frc, result); | ||
| 477 | // System.err.println("Layout returns: " + gv); | ||
| 478 | return gv; | ||
| 479 | } | ||
| 480 | |||
| 481 | // | ||
| 482 | // private methods | ||
| 483 | // | ||
| 484 | |||
| 485 | private GlyphLayout() { | ||
| 486 | this._gvdata = new GVData(); | ||
| 487 | this._textRecord = new TextRecord(); | ||
| 488 | this._scriptRuns = new ScriptRun(); | ||
| 489 | this._fontRuns = new FontRunIterator(); | ||
| 490 | this._erecords = new ArrayList(10); | ||
| 491 | this._pt = new Point2D.Float(); | ||
| 492 | this._sd = new FontStrikeDesc(); | ||
| 493 | this._mat = new float[4]; | ||
| 494 | } | ||
| 495 | |||
| 496 | private void init(int capacity) { | ||
| 497 | this._typo_flags = 0; | ||
| 498 | this._ercount = 0; | ||
| 499 | this._gvdata.init(capacity); | ||
| 500 | } | ||
| 501 | |||
| 502 | private void nextEngineRecord(int start, int limit, int script, int lang, Font2D font, int gmask) { | ||
| 503 | EngineRecord er = null; | ||
| 504 | if (_ercount == _erecords.size()) { | ||
| 505 | er = new EngineRecord(); | ||
| 506 | _erecords.add(er); | ||
| 507 | } else { | ||
| 508 | er = (EngineRecord)_erecords.get(_ercount); | ||
| 509 | } | ||
| 510 | er.init(start, limit, font, script, lang, gmask); | ||
| 511 | ++_ercount; | ||
| 512 | } | ||
| 513 | |||
| 514 | /** | ||
| 515 | * Storage for layout to build glyph vector data, then generate a real GlyphVector | ||
| 516 | */ | ||
| 517 | public static final class GVData { | ||
| 518 | public int _count; // number of glyphs, >= number of chars | ||
| 519 | public int _flags; | ||
| 520 | public int[] _glyphs; | ||
| 521 | public float[] _positions; | ||
| 522 | public int[] _indices; | ||
| 523 | |||
| 524 | private static final int UNINITIALIZED_FLAGS = -1; | ||
| 525 | |||
| 526 | public void init(int size) { | ||
| 527 | _count = 0; | ||
| 528 | _flags = UNINITIALIZED_FLAGS; | ||
| 529 | |||
| 530 | if (_glyphs == null || _glyphs.length < size) { | ||
| 531 | if (size < 20) { | ||
| 532 | size = 20; | ||
| 533 | } | ||
| 534 | _glyphs = new int[size]; | ||
| 535 | _positions = new float[size * 2 + 2]; | ||
| 536 | _indices = new int[size]; | ||
| 537 | } | ||
| 538 | } | ||
| 539 | |||
| 540 | public void grow() { | ||
| 541 | grow(_glyphs.length / 4); // always grows because min length is 20 | ||
| 542 | } | ||
| 543 | |||
| 544 | public void grow(int delta) { | ||
| 545 | int size = _glyphs.length + delta; | ||
| 546 | int[] nglyphs = new int[size]; | ||
| 547 | System.arraycopy(_glyphs, 0, nglyphs, 0, _count); | ||
| 548 | _glyphs = nglyphs; | ||
| 549 | |||
| 550 | float[] npositions = new float[size * 2 + 2]; | ||
| 551 | System.arraycopy(_positions, 0, npositions, 0, _count * 2 + 2); | ||
| 552 | _positions = npositions; | ||
| 553 | |||
| 554 | int[] nindices = new int[size]; | ||
| 555 | System.arraycopy(_indices, 0, nindices, 0, _count); | ||
| 556 | _indices = nindices; | ||
| 557 | } | ||
| 558 | |||
| 559 | public void adjustPositions(AffineTransform invdtx) { | ||
| 560 | invdtx.transform(_positions, 0, _positions, 0, _count); | ||
| 561 | } | ||
| 562 | |||
| 563 | public StandardGlyphVector createGlyphVector(Font font, FontRenderContext frc, StandardGlyphVector result) { | ||
| 564 | |||
| 565 | // !!! default initialization until we let layout engines do it | ||
| 566 | if (_flags == UNINITIALIZED_FLAGS) { | ||
| 567 | _flags = 0; | ||
| 568 | |||
| 569 | if (_count > 1) { // if only 1 glyph assume LTR | ||
| 570 | boolean ltr = true; | ||
| 571 | boolean rtl = true; | ||
| 572 | |||
| 573 | int rtlix = _count; // rtl index | ||
| 574 | for (int i = 0; i < _count && (ltr || rtl); ++i) { | ||
| 575 | int cx = _indices[i]; | ||
| 576 | |||
| 577 | ltr = ltr && (cx == i); | ||
| 578 | rtl = rtl && (cx == --rtlix); | ||
| 579 | } | ||
| 580 | |||
| 581 | if (rtl) _flags |= GlyphVector.FLAG_RUN_RTL; | ||
| 582 | if (!rtl && !ltr) _flags |= GlyphVector.FLAG_COMPLEX_GLYPHS; | ||
| 583 | } | ||
| 584 | |||
| 585 | // !!! layout engines need to tell us whether they performed | ||
| 586 | // position adjustments. currently they don't tell us, so | ||
| 587 | // we must assume they did | ||
| 588 | _flags |= GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS; | ||
| 589 | } | ||
| 590 | |||
| 591 | int[] glyphs = new int[_count]; | ||
| 592 | System.arraycopy(_glyphs, 0, glyphs, 0, _count); | ||
| 593 | |||
| 594 | float[] positions = null; | ||
| 595 | if ((_flags & GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS) != 0) { | ||
| 596 | positions = new float[_count * 2 + 2]; | ||
| 597 | System.arraycopy(_positions, 0, positions, 0, positions.length); | ||
| 598 | } | ||
| 599 | |||
| 600 | int[] indices = null; | ||
| 601 | if ((_flags & GlyphVector.FLAG_COMPLEX_GLYPHS) != 0) { | ||
| 602 | indices = new int[_count]; | ||
| 603 | System.arraycopy(_indices, 0, indices, 0, _count); | ||
| 604 | } | ||
| 605 | |||
| 606 | if (result == null) { | ||
| 607 | result = new StandardGlyphVector(font, frc, glyphs, positions, indices, _flags); | ||
| 608 | } else { | ||
| 609 | result.initGlyphVector(font, frc, glyphs, positions, indices, _flags); | ||
| 610 | } | ||
| 611 | |||
| 612 | return result; | ||
| 613 | } | ||
| 614 | } | ||
| 615 | |||
| 616 | /** | ||
| 617 | * Utility class to keep track of script runs, which may have to be reordered rtl when we're | ||
| 618 | * finished. | ||
| 619 | */ | ||
| 620 | private final class EngineRecord { | ||
| 621 | private int start; | ||
| 622 | private int limit; | ||
| 623 | private int gmask; | ||
| 624 | private int eflags; | ||
| 625 | private LayoutEngineKey key; | ||
| 626 | private LayoutEngine engine; | ||
| 627 | |||
| 628 | EngineRecord() { | ||
| 629 | key = new LayoutEngineKey(); | ||
| 630 | } | ||
| 631 | |||
| 632 | void init(int start, int limit, Font2D font, int script, int lang, int gmask) { | ||
| 633 | this.start = start; | ||
| 634 | this.limit = limit; | ||
| 635 | this.gmask = gmask; | ||
| 636 | this.key.init(font, script, lang); | ||
| 637 | this.eflags = 0; | ||
| 638 | |||
| 639 | // only request canonical substitution if we have combining marks | ||
| 640 | for (int i = start; i < limit; ++i) { | ||
| 641 | int ch = _textRecord.text[i]; | ||
| 642 | if (isHighSurrogate((char)ch) && | ||
| 643 | i < limit - 1 && | ||
| 644 | isLowSurrogate(_textRecord.text[i+1])) { | ||
| 645 | // rare case | ||
| 646 | ch = toCodePoint((char)ch,_textRecord.text[++i]); // inc | ||
| 647 | } | ||
| 648 | int gc = getType(ch); | ||
| 649 | if (gc == NON_SPACING_MARK || | ||
| 650 | gc == ENCLOSING_MARK || | ||
| 651 | gc == COMBINING_SPACING_MARK) { // could do range test also | ||
| 652 | |||
| 653 | this.eflags = 0x4; | ||
| 654 | break; | ||
| 655 | } | ||
| 656 | } | ||
| 657 | |||
| 658 | this.engine = _lef.getEngine(key); // flags? | ||
| 659 | } | ||
| 660 | |||
| 661 | void layout() { | ||
| 662 | _textRecord.start = start; | ||
| 663 | _textRecord.limit = limit; | ||
| 664 | engine.layout(_sd, _mat, gmask, start - _offset, _textRecord, | ||
| 665 | _typo_flags | eflags, _pt, _gvdata); | ||
| 666 | } | ||
| 667 | } | ||
| 668 | } | ||