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