Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/font/GlyphLayout.java
38829 views
/*1* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26*27* (C) Copyright IBM Corp. 1999-2003 - All Rights Reserved28*29* The original version of this source code and documentation is30* copyrighted and owned by IBM. These materials are provided31* under terms of a License Agreement between IBM and Sun.32* This technology is protected by multiple US and International33* patents. This notice and attribution to IBM may not be removed.34*/3536/*37* GlyphLayout is used to process a run of text into a run of run of38* glyphs, optionally with position and char mapping info.39*40* The text has already been processed for numeric shaping and bidi.41* The run of text that layout works on has a single bidi level. It42* also has a single font/style. Some operations need context to work43* on (shaping, script resolution) so context for the text run text is44* provided. It is assumed that the text array contains sufficient45* context, and the offset and count delimit the portion of the text46* that needs to actually be processed.47*48* The font might be a composite font. Layout generally requires49* tables from a single physical font to operate, and so it must50* resolve the 'single' font run into runs of physical fonts.51*52* Some characters are supported by several fonts of a composite, and53* in order to properly emulate the glyph substitution behavior of a54* single physical font, these characters might need to be mapped to55* different physical fonts. The script code that is assigned56* characters normally considered 'common script' can be used to57* resolve which physical font to use for these characters. The input58* to the char to glyph mapper (which assigns physical fonts as it59* processes the glyphs) should include the script code, and the60* mapper should operate on runs of a single script.61*62* To perform layout, call get() to get a new (or reuse an old)63* GlyphLayout, call layout on it, then call done(GlyphLayout) when64* finished. There's no particular problem if you don't call done,65* but it assists in reuse of the GlyphLayout.66*/6768package sun.font;6970import java.lang.ref.SoftReference;71import java.awt.Font;72import java.awt.font.FontRenderContext;73import java.awt.font.GlyphVector;74import java.awt.geom.AffineTransform;75import java.awt.geom.NoninvertibleTransformException;76import java.awt.geom.Point2D;77import java.util.ArrayList;78import java.util.concurrent.ConcurrentHashMap;7980import static java.lang.Character.*;8182public final class GlyphLayout {83// data for glyph vector84private GVData _gvdata;8586// cached glyph layout data for reuse87private static volatile GlyphLayout cache; // reusable8889private LayoutEngineFactory _lef; // set when get is called, unset when done is called90private TextRecord _textRecord; // the text we're working on, used by iterators91private ScriptRun _scriptRuns; // iterator over script runs92private FontRunIterator _fontRuns; // iterator over physical fonts in a composite93private int _ercount;94private ArrayList _erecords;95private Point2D.Float _pt;96private FontStrikeDesc _sd;97private float[] _mat;98private int _typo_flags;99private int _offset;100101public static final class LayoutEngineKey {102private Font2D font;103private int script;104private int lang;105106LayoutEngineKey() {107}108109LayoutEngineKey(Font2D font, int script, int lang) {110init(font, script, lang);111}112113void init(Font2D font, int script, int lang) {114this.font = font;115this.script = script;116this.lang = lang;117}118119LayoutEngineKey copy() {120return new LayoutEngineKey(font, script, lang);121}122123Font2D font() {124return font;125}126127int script() {128return script;129}130131int lang() {132return lang;133}134135public boolean equals(Object rhs) {136if (this == rhs) return true;137if (rhs == null) return false;138try {139LayoutEngineKey that = (LayoutEngineKey)rhs;140return this.script == that.script &&141this.lang == that.lang &&142this.font.equals(that.font);143}144catch (ClassCastException e) {145return false;146}147}148149public int hashCode() {150return script ^ lang ^ font.hashCode();151}152}153154public static interface LayoutEngineFactory {155/**156* Given a font, script, and language, determine a layout engine to use.157*/158public LayoutEngine getEngine(Font2D font, int script, int lang);159160/**161* Given a key, determine a layout engine to use.162*/163public LayoutEngine getEngine(LayoutEngineKey key);164}165166public static interface LayoutEngine {167/**168* Given a strike descriptor, text, rtl flag, and starting point, append information about169* glyphs, positions, and character indices to the glyphvector data, and advance the point.170*171* If the GVData does not have room for the glyphs, throws an IndexOutOfBoundsException and172* leave pt and the gvdata unchanged.173*/174public void layout(FontStrikeDesc sd, float[] mat, int gmask,175int baseIndex, TextRecord text, int typo_flags, Point2D.Float pt, GVData data);176}177178/**179* Return a new instance of GlyphLayout, using the provided layout engine factory.180* If null, the system layout engine factory will be used.181*/182public static GlyphLayout get(LayoutEngineFactory lef) {183if (lef == null) {184lef = SunLayoutEngine.instance();185}186GlyphLayout result = null;187synchronized(GlyphLayout.class) {188if (cache != null) {189result = cache;190cache = null;191}192}193if (result == null) {194result = new GlyphLayout();195}196result._lef = lef;197return result;198}199200/**201* Return the old instance of GlyphLayout when you are done. This enables reuse202* of GlyphLayout objects.203*/204public static void done(GlyphLayout gl) {205gl._lef = null;206cache = gl; // object reference assignment is thread safe, it says here...207}208209private static final class SDCache {210public Font key_font;211public FontRenderContext key_frc;212213public AffineTransform dtx;214public AffineTransform invdtx;215public AffineTransform gtx;216public Point2D.Float delta;217public FontStrikeDesc sd;218219private SDCache(Font font, FontRenderContext frc) {220key_font = font;221key_frc = frc;222223// !!! add getVectorTransform and hasVectorTransform to frc? then224// we could just skip this work...225226dtx = frc.getTransform();227dtx.setTransform(dtx.getScaleX(), dtx.getShearY(),228dtx.getShearX(), dtx.getScaleY(),2290, 0);230if (!dtx.isIdentity()) {231try {232invdtx = dtx.createInverse();233}234catch (NoninvertibleTransformException e) {235throw new InternalError(e);236}237}238239float ptSize = font.getSize2D();240if (font.isTransformed()) {241gtx = font.getTransform();242gtx.scale(ptSize, ptSize);243delta = new Point2D.Float((float)gtx.getTranslateX(),244(float)gtx.getTranslateY());245gtx.setTransform(gtx.getScaleX(), gtx.getShearY(),246gtx.getShearX(), gtx.getScaleY(),2470, 0);248gtx.preConcatenate(dtx);249} else {250delta = ZERO_DELTA;251gtx = new AffineTransform(dtx);252gtx.scale(ptSize, ptSize);253}254255/* Similar logic to that used in SunGraphics2D.checkFontInfo().256* Whether a grey (AA) strike is needed is size dependent if257* AA mode is 'gasp'.258*/259int aa =260FontStrikeDesc.getAAHintIntVal(frc.getAntiAliasingHint(),261FontUtilities.getFont2D(font),262(int)Math.abs(ptSize));263int fm = FontStrikeDesc.getFMHintIntVal264(frc.getFractionalMetricsHint());265sd = new FontStrikeDesc(dtx, gtx, font.getStyle(), aa, fm);266}267268private static final Point2D.Float ZERO_DELTA = new Point2D.Float();269270private static271SoftReference<ConcurrentHashMap<SDKey, SDCache>> cacheRef;272273private static final class SDKey {274private final Font font;275private final FontRenderContext frc;276private final int hash;277278SDKey(Font font, FontRenderContext frc) {279this.font = font;280this.frc = frc;281this.hash = font.hashCode() ^ frc.hashCode();282}283284public int hashCode() {285return hash;286}287288public boolean equals(Object o) {289try {290SDKey rhs = (SDKey)o;291return292hash == rhs.hash &&293font.equals(rhs.font) &&294frc.equals(rhs.frc);295}296catch (ClassCastException e) {297}298return false;299}300}301302public static SDCache get(Font font, FontRenderContext frc) {303304// It is possible a translation component will be in the FRC.305// It doesn't affect us except adversely as we would consider306// FRC's which are really the same to be different. If we307// detect a translation component, then we need to exclude it308// by creating a new transform which excludes the translation.309if (frc.isTransformed()) {310AffineTransform transform = frc.getTransform();311if (transform.getTranslateX() != 0 ||312transform.getTranslateY() != 0) {313transform = new AffineTransform(transform.getScaleX(),314transform.getShearY(),315transform.getShearX(),316transform.getScaleY(),3170, 0);318frc = new FontRenderContext(transform,319frc.getAntiAliasingHint(),320frc.getFractionalMetricsHint()321);322}323}324325SDKey key = new SDKey(font, frc); // garbage, yuck...326ConcurrentHashMap<SDKey, SDCache> cache = null;327SDCache res = null;328if (cacheRef != null) {329cache = cacheRef.get();330if (cache != null) {331res = cache.get(key);332}333}334if (res == null) {335res = new SDCache(font, frc);336if (cache == null) {337cache = new ConcurrentHashMap<SDKey, SDCache>(10);338cacheRef = new339SoftReference<ConcurrentHashMap<SDKey, SDCache>>(cache);340} else if (cache.size() >= 512) {341cache.clear();342}343cache.put(key, res);344}345return res;346}347}348349/**350* Create a glyph vector.351* @param font the font to use352* @param frc the font render context353* @param text the text, including optional context before start and after start + count354* @param offset the start of the text to lay out355* @param count the length of the text to lay out356* @param flags bidi and context flags {@see #java.awt.Font}357* @param result a StandardGlyphVector to modify, can be null358* @return the layed out glyphvector, if result was passed in, it is returned359*/360public StandardGlyphVector layout(Font font, FontRenderContext frc,361char[] text, int offset, int count,362int flags, StandardGlyphVector result)363{364if (text == null || offset < 0 || count < 0 || (count > text.length - offset)) {365throw new IllegalArgumentException();366}367368init(count);369370// need to set after init371// go through the back door for this372if (font.hasLayoutAttributes()) {373AttributeValues values = ((AttributeMap)font.getAttributes()).getValues();374if (values.getKerning() != 0) _typo_flags |= 0x1;375if (values.getLigatures() != 0) _typo_flags |= 0x2;376}377378_offset = offset;379380// use cache now - can we use the strike cache for this?381382SDCache txinfo = SDCache.get(font, frc);383_mat[0] = (float)txinfo.gtx.getScaleX();384_mat[1] = (float)txinfo.gtx.getShearY();385_mat[2] = (float)txinfo.gtx.getShearX();386_mat[3] = (float)txinfo.gtx.getScaleY();387_pt.setLocation(txinfo.delta);388389int lim = offset + count;390391int min = 0;392int max = text.length;393if (flags != 0) {394if ((flags & Font.LAYOUT_RIGHT_TO_LEFT) != 0) {395_typo_flags |= 0x80000000; // RTL396}397398if ((flags & Font.LAYOUT_NO_START_CONTEXT) != 0) {399min = offset;400}401402if ((flags & Font.LAYOUT_NO_LIMIT_CONTEXT) != 0) {403max = lim;404}405}406407int lang = -1; // default for now408409Font2D font2D = FontUtilities.getFont2D(font);410if (font2D instanceof FontSubstitution) {411font2D = ((FontSubstitution)font2D).getCompositeFont2D();412}413414_textRecord.init(text, offset, lim, min, max);415int start = offset;416if (font2D instanceof CompositeFont) {417_scriptRuns.init(text, offset, count); // ??? how to handle 'common' chars418_fontRuns.init((CompositeFont)font2D, text, offset, lim);419while (_scriptRuns.next()) {420int limit = _scriptRuns.getScriptLimit();421int script = _scriptRuns.getScriptCode();422while (_fontRuns.next(script, limit)) {423Font2D pfont = _fontRuns.getFont();424/* layout can't deal with NativeFont instances. The425* native font is assumed to know of a suitable non-native426* substitute font. This currently works because427* its consistent with the way NativeFonts delegate428* in other cases too.429*/430if (pfont instanceof NativeFont) {431pfont = ((NativeFont)pfont).getDelegateFont();432}433int gmask = _fontRuns.getGlyphMask();434int pos = _fontRuns.getPos();435nextEngineRecord(start, pos, script, lang, pfont, gmask);436start = pos;437}438}439} else {440_scriptRuns.init(text, offset, count); // ??? don't worry about 'common' chars441while (_scriptRuns.next()) {442int limit = _scriptRuns.getScriptLimit();443int script = _scriptRuns.getScriptCode();444nextEngineRecord(start, limit, script, lang, font2D, 0);445start = limit;446}447}448449int ix = 0;450int stop = _ercount;451int dir = 1;452453if (_typo_flags < 0) { // RTL454ix = stop - 1;455stop = -1;456dir = -1;457}458459// _sd.init(dtx, gtx, font.getStyle(), frc.isAntiAliased(), frc.usesFractionalMetrics());460_sd = txinfo.sd;461for (;ix != stop; ix += dir) {462EngineRecord er = (EngineRecord)_erecords.get(ix);463for (;;) {464try {465er.layout();466break;467}468catch (IndexOutOfBoundsException e) {469if (_gvdata._count >=0) {470_gvdata.grow();471}472}473}474// Break out of the outer for loop if layout fails.475if (_gvdata._count < 0) {476break;477}478}479480// if (txinfo.invdtx != null) {481// _gvdata.adjustPositions(txinfo.invdtx);482// }483484// If layout fails (negative glyph count) create an un-laid out GV instead.485// ie default positions. This will be a lot better than the alternative of486// a complete blank layout.487StandardGlyphVector gv;488if (_gvdata._count < 0) {489gv = new StandardGlyphVector(font, text, offset, count, frc);490if (FontUtilities.debugFonts()) {491FontUtilities.getLogger().warning("OpenType layout failed on font: " +492font);493}494} else {495gv = _gvdata.createGlyphVector(font, frc, result);496}497// System.err.println("Layout returns: " + gv);498return gv;499}500501//502// private methods503//504505private GlyphLayout() {506this._gvdata = new GVData();507this._textRecord = new TextRecord();508this._scriptRuns = new ScriptRun();509this._fontRuns = new FontRunIterator();510this._erecords = new ArrayList(10);511this._pt = new Point2D.Float();512this._sd = new FontStrikeDesc();513this._mat = new float[4];514}515516private void init(int capacity) {517this._typo_flags = 0;518this._ercount = 0;519this._gvdata.init(capacity);520}521522private void nextEngineRecord(int start, int limit, int script, int lang, Font2D font, int gmask) {523EngineRecord er = null;524if (_ercount == _erecords.size()) {525er = new EngineRecord();526_erecords.add(er);527} else {528er = (EngineRecord)_erecords.get(_ercount);529}530er.init(start, limit, font, script, lang, gmask);531++_ercount;532}533534/**535* Storage for layout to build glyph vector data, then generate a real GlyphVector536*/537public static final class GVData {538public int _count; // number of glyphs, >= number of chars539public int _flags;540public int[] _glyphs;541public float[] _positions;542public int[] _indices;543544private static final int UNINITIALIZED_FLAGS = -1;545546public void init(int size) {547_count = 0;548_flags = UNINITIALIZED_FLAGS;549550if (_glyphs == null || _glyphs.length < size) {551if (size < 20) {552size = 20;553}554_glyphs = new int[size];555_positions = new float[size * 2 + 2];556_indices = new int[size];557}558}559560public void grow() {561grow(_glyphs.length / 4); // always grows because min length is 20562}563564public void grow(int delta) {565int size = _glyphs.length + delta;566int[] nglyphs = new int[size];567System.arraycopy(_glyphs, 0, nglyphs, 0, _count);568_glyphs = nglyphs;569570float[] npositions = new float[size * 2 + 2];571System.arraycopy(_positions, 0, npositions, 0, _count * 2 + 2);572_positions = npositions;573574int[] nindices = new int[size];575System.arraycopy(_indices, 0, nindices, 0, _count);576_indices = nindices;577}578579public void adjustPositions(AffineTransform invdtx) {580invdtx.transform(_positions, 0, _positions, 0, _count);581}582583public StandardGlyphVector createGlyphVector(Font font, FontRenderContext frc, StandardGlyphVector result) {584585// !!! default initialization until we let layout engines do it586if (_flags == UNINITIALIZED_FLAGS) {587_flags = 0;588589if (_count > 1) { // if only 1 glyph assume LTR590boolean ltr = true;591boolean rtl = true;592593int rtlix = _count; // rtl index594for (int i = 0; i < _count && (ltr || rtl); ++i) {595int cx = _indices[i];596597ltr = ltr && (cx == i);598rtl = rtl && (cx == --rtlix);599}600601if (rtl) _flags |= GlyphVector.FLAG_RUN_RTL;602if (!rtl && !ltr) _flags |= GlyphVector.FLAG_COMPLEX_GLYPHS;603}604605// !!! layout engines need to tell us whether they performed606// position adjustments. currently they don't tell us, so607// we must assume they did608_flags |= GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS;609}610611int[] glyphs = new int[_count];612System.arraycopy(_glyphs, 0, glyphs, 0, _count);613614float[] positions = null;615if ((_flags & GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS) != 0) {616positions = new float[_count * 2 + 2];617System.arraycopy(_positions, 0, positions, 0, positions.length);618}619620int[] indices = null;621if ((_flags & GlyphVector.FLAG_COMPLEX_GLYPHS) != 0) {622indices = new int[_count];623System.arraycopy(_indices, 0, indices, 0, _count);624}625626if (result == null) {627result = new StandardGlyphVector(font, frc, glyphs, positions, indices, _flags);628} else {629result.initGlyphVector(font, frc, glyphs, positions, indices, _flags);630}631632return result;633}634}635636/**637* Utility class to keep track of script runs, which may have to be reordered rtl when we're638* finished.639*/640private final class EngineRecord {641private int start;642private int limit;643private int gmask;644private int eflags;645private LayoutEngineKey key;646private LayoutEngine engine;647648EngineRecord() {649key = new LayoutEngineKey();650}651652void init(int start, int limit, Font2D font, int script, int lang, int gmask) {653this.start = start;654this.limit = limit;655this.gmask = gmask;656this.key.init(font, script, lang);657this.eflags = 0;658659// only request canonical substitution if we have combining marks660for (int i = start; i < limit; ++i) {661int ch = _textRecord.text[i];662if (isHighSurrogate((char)ch) &&663i < limit - 1 &&664isLowSurrogate(_textRecord.text[i+1])) {665// rare case666ch = toCodePoint((char)ch,_textRecord.text[++i]); // inc667}668int gc = getType(ch);669if (gc == NON_SPACING_MARK ||670gc == ENCLOSING_MARK ||671gc == COMBINING_SPACING_MARK) { // could do range test also672673this.eflags = 0x4;674break;675}676}677678this.engine = _lef.getEngine(key); // flags?679}680681void layout() {682_textRecord.start = start;683_textRecord.limit = limit;684engine.layout(_sd, _mat, gmask, start - _offset, _textRecord,685_typo_flags | eflags, _pt, _gvdata);686}687}688}689690691