1 /*
   2  * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  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<EngineRecord> _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(e);
 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         if (font2D instanceof FontSubstitution) {
 412             font2D = ((FontSubstitution)font2D).getCompositeFont2D();
 413         }
 414 
 415         _textRecord.init(text, offset, lim, min, max);
 416         int start = offset;
 417         if (font2D instanceof CompositeFont) {
 418             _scriptRuns.init(text, offset, count); // ??? how to handle 'common' chars
 419             _fontRuns.init((CompositeFont)font2D, text, offset, lim);
 420             while (_scriptRuns.next()) {
 421                 int limit = _scriptRuns.getScriptLimit();
 422                 int script = _scriptRuns.getScriptCode();
 423                 while (_fontRuns.next(script, limit)) {
 424                     Font2D pfont = _fontRuns.getFont();
 425                     /* layout can't deal with NativeFont instances. The
 426                      * native font is assumed to know of a suitable non-native
 427                      * substitute font. This currently works because
 428                      * its consistent with the way NativeFonts delegate
 429                      * in other cases too.
 430                      */
 431                     if (pfont instanceof NativeFont) {
 432                         pfont = ((NativeFont)pfont).getDelegateFont();
 433                     }
 434                     int gmask = _fontRuns.getGlyphMask();
 435                     int pos = _fontRuns.getPos();
 436                     nextEngineRecord(start, pos, script, lang, pfont, gmask);
 437                     start = pos;
 438                 }
 439             }
 440         } else {
 441             _scriptRuns.init(text, offset, count); // ??? don't worry about 'common' chars
 442             while (_scriptRuns.next()) {
 443                 int limit = _scriptRuns.getScriptLimit();
 444                 int script = _scriptRuns.getScriptCode();
 445                 nextEngineRecord(start, limit, script, lang, font2D, 0);
 446                 start = limit;
 447             }
 448         }
 449 
 450         int ix = 0;
 451         int stop = _ercount;
 452         int dir = 1;
 453 
 454         if (_typo_flags < 0) { // RTL
 455             ix = stop - 1;
 456             stop = -1;
 457             dir = -1;
 458         }
 459 
 460         //        _sd.init(dtx, gtx, font.getStyle(), frc.isAntiAliased(), frc.usesFractionalMetrics());
 461         _sd = txinfo.sd;
 462         for (;ix != stop; ix += dir) {
 463             EngineRecord er = _erecords.get(ix);
 464             for (;;) {
 465                 try {
 466                     er.layout();
 467                     break;
 468                 }
 469                 catch (IndexOutOfBoundsException e) {
 470                     if (_gvdata._count >=0) {
 471                         _gvdata.grow();
 472                     }
 473                 }
 474             }
 475             // Break out of the outer for loop if layout fails.
 476             if (_gvdata._count < 0) {
 477                 break;
 478             }
 479         }
 480 
 481         //        if (txinfo.invdtx != null) {
 482         //            _gvdata.adjustPositions(txinfo.invdtx);
 483         //        }
 484 
 485         // If layout fails (negative glyph count) create an un-laid out GV instead.
 486         // ie default positions. This will be a lot better than the alternative of
 487         // a complete blank layout.
 488         StandardGlyphVector gv;
 489         if (_gvdata._count < 0) {
 490             gv = new StandardGlyphVector(font, text, offset, count, frc);
 491             if (FontUtilities.debugFonts()) {
 492                FontUtilities.getLogger().warning("OpenType layout failed on font: " +
 493                                                  font);
 494             }
 495         } else {
 496             gv = _gvdata.createGlyphVector(font, frc, result);
 497         }
 498         //        System.err.println("Layout returns: " + gv);
 499         return gv;
 500     }
 501 
 502     //
 503     // private methods
 504     //
 505 
 506     private GlyphLayout() {
 507         this._gvdata = new GVData();
 508         this._textRecord = new TextRecord();
 509         this._scriptRuns = new ScriptRun();
 510         this._fontRuns = new FontRunIterator();
 511         this._erecords = new ArrayList<>(10);
 512         this._pt = new Point2D.Float();
 513         this._sd = new FontStrikeDesc();
 514         this._mat = new float[4];
 515     }
 516 
 517     private void init(int capacity) {
 518         this._typo_flags = 0;
 519         this._ercount = 0;
 520         this._gvdata.init(capacity);
 521     }
 522 
 523     private void nextEngineRecord(int start, int limit, int script, int lang, Font2D font, int gmask) {
 524         EngineRecord er = null;
 525         if (_ercount == _erecords.size()) {
 526             er = new EngineRecord();
 527             _erecords.add(er);
 528         } else {
 529             er = _erecords.get(_ercount);
 530         }
 531         er.init(start, limit, font, script, lang, gmask);
 532         ++_ercount;
 533     }
 534 
 535     /**
 536      * Storage for layout to build glyph vector data, then generate a real GlyphVector
 537      */
 538     public static final class GVData {
 539         public int _count; // number of glyphs, >= number of chars
 540         public int _flags;
 541         public int[] _glyphs;
 542         public float[] _positions;
 543         public int[] _indices;
 544 
 545         private static final int UNINITIALIZED_FLAGS = -1;
 546 
 547         public void init(int size) {
 548             _count = 0;
 549             _flags = UNINITIALIZED_FLAGS;
 550 
 551             if (_glyphs == null || _glyphs.length < size) {
 552                 if (size < 20) {
 553                     size = 20;
 554                 }
 555                 _glyphs = new int[size];
 556                 _positions = new float[size * 2 + 2];
 557                 _indices = new int[size];
 558             }
 559         }
 560 
 561         public void grow() {
 562             grow(_glyphs.length / 4); // always grows because min length is 20
 563         }
 564 
 565         public void grow(int delta) {
 566             int size = _glyphs.length + delta;
 567             int[] nglyphs = new int[size];
 568             System.arraycopy(_glyphs, 0, nglyphs, 0, _count);
 569             _glyphs = nglyphs;
 570 
 571             float[] npositions = new float[size * 2 + 2];
 572             System.arraycopy(_positions, 0, npositions, 0, _count * 2 + 2);
 573             _positions = npositions;
 574 
 575             int[] nindices = new int[size];
 576             System.arraycopy(_indices, 0, nindices, 0, _count);
 577             _indices = nindices;
 578         }
 579 
 580         public void adjustPositions(AffineTransform invdtx) {
 581             invdtx.transform(_positions, 0, _positions, 0, _count);
 582         }
 583 
 584         public StandardGlyphVector createGlyphVector(Font font, FontRenderContext frc, StandardGlyphVector result) {
 585 
 586             // !!! default initialization until we let layout engines do it
 587             if (_flags == UNINITIALIZED_FLAGS) {
 588                 _flags = 0;
 589 
 590                 if (_count > 1) { // if only 1 glyph assume LTR
 591                     boolean ltr = true;
 592                     boolean rtl = true;
 593 
 594                     int rtlix = _count; // rtl index
 595                     for (int i = 0; i < _count && (ltr || rtl); ++i) {
 596                         int cx = _indices[i];
 597 
 598                         ltr = ltr && (cx == i);
 599                         rtl = rtl && (cx == --rtlix);
 600                     }
 601 
 602                     if (rtl) _flags |= GlyphVector.FLAG_RUN_RTL;
 603                     if (!rtl && !ltr) _flags |= GlyphVector.FLAG_COMPLEX_GLYPHS;
 604                 }
 605 
 606                 // !!! layout engines need to tell us whether they performed
 607                 // position adjustments. currently they don't tell us, so
 608                 // we must assume they did
 609                 _flags |= GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS;
 610             }
 611 
 612             int[] glyphs = new int[_count];
 613             System.arraycopy(_glyphs, 0, glyphs, 0, _count);
 614 
 615             float[] positions = null;
 616             if ((_flags & GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS) != 0) {
 617                 positions = new float[_count * 2 + 2];
 618                 System.arraycopy(_positions, 0, positions, 0, positions.length);
 619             }
 620 
 621             int[] indices = null;
 622             if ((_flags & GlyphVector.FLAG_COMPLEX_GLYPHS) != 0) {
 623                 indices = new int[_count];
 624                 System.arraycopy(_indices, 0, indices, 0, _count);
 625             }
 626 
 627             if (result == null) {
 628                 result = new StandardGlyphVector(font, frc, glyphs, positions, indices, _flags);
 629             } else {
 630                 result.initGlyphVector(font, frc, glyphs, positions, indices, _flags);
 631             }
 632 
 633             return result;
 634         }
 635     }
 636 
 637     /**
 638      * Utility class to keep track of script runs, which may have to be reordered rtl when we're
 639      * finished.
 640      */
 641     private final class EngineRecord {
 642         private int start;
 643         private int limit;
 644         private int gmask;
 645         private int eflags;
 646         private LayoutEngineKey key;
 647         private LayoutEngine engine;
 648 
 649         EngineRecord() {
 650             key = new LayoutEngineKey();
 651         }
 652 
 653         void init(int start, int limit, Font2D font, int script, int lang, int gmask) {
 654             this.start = start;
 655             this.limit = limit;
 656             this.gmask = gmask;
 657             this.key.init(font, script, lang);
 658             this.eflags = 0;
 659 
 660             // only request canonical substitution if we have combining marks
 661             for (int i = start; i < limit; ++i) {
 662                 int ch = _textRecord.text[i];
 663                 if (isHighSurrogate((char)ch) &&
 664                     i < limit - 1 &&
 665                     isLowSurrogate(_textRecord.text[i+1])) {
 666                     // rare case
 667                     ch = toCodePoint((char)ch,_textRecord.text[++i]); // inc
 668                 }
 669                 int gc = getType(ch);
 670                 if (gc == NON_SPACING_MARK ||
 671                     gc == ENCLOSING_MARK ||
 672                     gc == COMBINING_SPACING_MARK) { // could do range test also
 673 
 674                     this.eflags = 0x4;
 675                     break;
 676                 }
 677             }
 678 
 679             this.engine = _lef.getEngine(key); // flags?
 680         }
 681 
 682         void layout() {
 683             _textRecord.start = start;
 684             _textRecord.limit = limit;
 685             engine.layout(_sd, _mat, gmask, start - _offset, _textRecord,
 686                           _typo_flags | eflags, _pt, _gvdata);
 687         }
 688     }
 689 }
--- EOF ---