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 
 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 = _erecords.get(ix);
 461             for (;;) {
 462                 try {
 463                     er.layout();
 464                     break;
 465                 }
 466                 catch (IndexOutOfBoundsException e) {
 467                     if (_gvdata._count >=0) {
 468                         _gvdata.grow();
 469                     }
 470                 }
 471             }
 472             // Break out of the outer for loop if layout fails.
 473             if (_gvdata._count < 0) {
 474                 break;
 475             }
 476         }
 477 
 478         //        if (txinfo.invdtx != null) {
 479         //            _gvdata.adjustPositions(txinfo.invdtx);
 480         //        }
 481 
 482         // If layout fails (negative glyph count) create an un-laid out GV instead.
 483         // ie default positions. This will be a lot better than the alternative of
 484         // a complete blank layout.
 485         StandardGlyphVector gv;
 486         if (_gvdata._count < 0) {
 487             gv = new StandardGlyphVector(font, text, offset, count, frc);
 488             if (FontUtilities.debugFonts()) {
 489                FontUtilities.getLogger().warning("OpenType layout failed on font: " +
 490                                                  font);
 491             }
 492         } else {
 493             gv = _gvdata.createGlyphVector(font, frc, result);
 494         }
 495         //        System.err.println("Layout returns: " + gv);
 496         return gv;
 497     }
 498 
 499     //
 500     // private methods
 501     //
 502 
 503     private GlyphLayout() {
 504         this._gvdata = new GVData();
 505         this._textRecord = new TextRecord();
 506         this._scriptRuns = new ScriptRun();
 507         this._fontRuns = new FontRunIterator();
 508         this._erecords = new ArrayList<>(10);
 509         this._pt = new Point2D.Float();
 510         this._sd = new FontStrikeDesc();
 511         this._mat = new float[4];
 512     }
 513 
 514     private void init(int capacity) {
 515         this._typo_flags = 0;
 516         this._ercount = 0;
 517         this._gvdata.init(capacity);
 518     }
 519 
 520     private void nextEngineRecord(int start, int limit, int script, int lang, Font2D font, int gmask) {
 521         EngineRecord er = null;
 522         if (_ercount == _erecords.size()) {
 523             er = new EngineRecord();
 524             _erecords.add(er);
 525         } else {
 526             er = _erecords.get(_ercount);
 527         }
 528         er.init(start, limit, font, script, lang, gmask);
 529         ++_ercount;
 530     }
 531 
 532     /**
 533      * Storage for layout to build glyph vector data, then generate a real GlyphVector
 534      */
 535     public static final class GVData {
 536         public int _count; // number of glyphs, >= number of chars
 537         public int _flags;
 538         public int[] _glyphs;
 539         public float[] _positions;
 540         public int[] _indices;
 541 
 542         private static final int UNINITIALIZED_FLAGS = -1;
 543 
 544         public void init(int size) {
 545             _count = 0;
 546             _flags = UNINITIALIZED_FLAGS;
 547 
 548             if (_glyphs == null || _glyphs.length < size) {
 549                 if (size < 20) {
 550                     size = 20;
 551                 }
 552                 _glyphs = new int[size];
 553                 _positions = new float[size * 2 + 2];
 554                 _indices = new int[size];
 555             }
 556         }
 557 
 558         public void grow() {
 559             grow(_glyphs.length / 4); // always grows because min length is 20
 560         }
 561 
 562         public void grow(int delta) {
 563             int size = _glyphs.length + delta;
 564             int[] nglyphs = new int[size];
 565             System.arraycopy(_glyphs, 0, nglyphs, 0, _count);
 566             _glyphs = nglyphs;
 567 
 568             float[] npositions = new float[size * 2 + 2];
 569             System.arraycopy(_positions, 0, npositions, 0, _count * 2 + 2);
 570             _positions = npositions;
 571 
 572             int[] nindices = new int[size];
 573             System.arraycopy(_indices, 0, nindices, 0, _count);
 574             _indices = nindices;
 575         }
 576 
 577         public void adjustPositions(AffineTransform invdtx) {
 578             invdtx.transform(_positions, 0, _positions, 0, _count);
 579         }
 580 
 581         public StandardGlyphVector createGlyphVector(Font font, FontRenderContext frc, StandardGlyphVector result) {
 582 
 583             // !!! default initialization until we let layout engines do it
 584             if (_flags == UNINITIALIZED_FLAGS) {
 585                 _flags = 0;
 586 
 587                 if (_count > 1) { // if only 1 glyph assume LTR
 588                     boolean ltr = true;
 589                     boolean rtl = true;
 590 
 591                     int rtlix = _count; // rtl index
 592                     for (int i = 0; i < _count && (ltr || rtl); ++i) {
 593                         int cx = _indices[i];
 594 
 595                         ltr = ltr && (cx == i);
 596                         rtl = rtl && (cx == --rtlix);
 597                     }
 598 
 599                     if (rtl) _flags |= GlyphVector.FLAG_RUN_RTL;
 600                     if (!rtl && !ltr) _flags |= GlyphVector.FLAG_COMPLEX_GLYPHS;
 601                 }
 602 
 603                 // !!! layout engines need to tell us whether they performed
 604                 // position adjustments. currently they don't tell us, so
 605                 // we must assume they did
 606                 _flags |= GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS;
 607             }
 608 
 609             int[] glyphs = new int[_count];
 610             System.arraycopy(_glyphs, 0, glyphs, 0, _count);
 611 
 612             float[] positions = null;
 613             if ((_flags & GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS) != 0) {
 614                 positions = new float[_count * 2 + 2];
 615                 System.arraycopy(_positions, 0, positions, 0, positions.length);
 616             }
 617 
 618             int[] indices = null;
 619             if ((_flags & GlyphVector.FLAG_COMPLEX_GLYPHS) != 0) {
 620                 indices = new int[_count];
 621                 System.arraycopy(_indices, 0, indices, 0, _count);
 622             }
 623 
 624             if (result == null) {
 625                 result = new StandardGlyphVector(font, frc, glyphs, positions, indices, _flags);
 626             } else {
 627                 result.initGlyphVector(font, frc, glyphs, positions, indices, _flags);
 628             }
 629 
 630             return result;
 631         }
 632     }
 633 
 634     /**
 635      * Utility class to keep track of script runs, which may have to be reordered rtl when we're
 636      * finished.
 637      */
 638     private final class EngineRecord {
 639         private int start;
 640         private int limit;
 641         private int gmask;
 642         private int eflags;
 643         private LayoutEngineKey key;
 644         private LayoutEngine engine;
 645 
 646         EngineRecord() {
 647             key = new LayoutEngineKey();
 648         }
 649 
 650         void init(int start, int limit, Font2D font, int script, int lang, int gmask) {
 651             this.start = start;
 652             this.limit = limit;
 653             this.gmask = gmask;
 654             this.key.init(font, script, lang);
 655             this.eflags = 0;
 656 
 657             // only request canonical substitution if we have combining marks
 658             for (int i = start; i < limit; ++i) {
 659                 int ch = _textRecord.text[i];
 660                 if (isHighSurrogate((char)ch) &&
 661                     i < limit - 1 &&
 662                     isLowSurrogate(_textRecord.text[i+1])) {
 663                     // rare case
 664                     ch = toCodePoint((char)ch,_textRecord.text[++i]); // inc
 665                 }
 666                 int gc = getType(ch);
 667                 if (gc == NON_SPACING_MARK ||
 668                     gc == ENCLOSING_MARK ||
 669                     gc == COMBINING_SPACING_MARK) { // could do range test also
 670 
 671                     this.eflags = 0x4;
 672                     break;
 673                 }
 674             }
 675 
 676             this.engine = _lef.getEngine(key); // flags?
 677         }
 678 
 679         void layout() {
 680             _textRecord.start = start;
 681             _textRecord.limit = limit;
 682             engine.layout(_sd, _mat, gmask, start - _offset, _textRecord,
 683                           _typo_flags | eflags, _pt, _gvdata);
 684         }
 685     }
 686 }
--- EOF ---