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