1 /*
   2  * Portions Copyright 2003-2008 Sun Microsystems, Inc.  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.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any 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 }