View Javadoc
1   /*
2    * Copyright (c) 2003, 2013, 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(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 = (EngineRecord)_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 = (EngineRecord)_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 }