Path: blob/master/src/java.desktop/share/classes/sun/font/GlyphLayout.java
41154 views
/*1* Copyright (c) 2003, 2020, 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<EngineRecord> _erecords;95private Point2D.Float _pt;96private FontStrikeDesc _sd;97private float[] _mat;98private float ptSize;99private int _typo_flags;100private int _offset;101102public static final class LayoutEngineKey {103private Font2D font;104private int script;105private int lang;106107LayoutEngineKey() {108}109110LayoutEngineKey(Font2D font, int script, int lang) {111init(font, script, lang);112}113114void init(Font2D font, int script, int lang) {115this.font = font;116this.script = script;117this.lang = lang;118}119120LayoutEngineKey copy() {121return new LayoutEngineKey(font, script, lang);122}123124Font2D font() {125return font;126}127128int script() {129return script;130}131132int lang() {133return lang;134}135136public boolean equals(Object rhs) {137if (this == rhs) return true;138if (rhs == null) return false;139try {140LayoutEngineKey that = (LayoutEngineKey)rhs;141return this.script == that.script &&142this.lang == that.lang &&143this.font.equals(that.font);144}145catch (ClassCastException e) {146return false;147}148}149150public int hashCode() {151return script ^ lang ^ font.hashCode();152}153}154155public static interface LayoutEngineFactory {156/**157* Given a font, script, and language, determine a layout engine to use.158*/159public LayoutEngine getEngine(Font2D font, int script, int lang);160161/**162* Given a key, determine a layout engine to use.163*/164public LayoutEngine getEngine(LayoutEngineKey key);165}166167public static interface LayoutEngine {168/**169* Given a strike descriptor, text, rtl flag, and starting point, append information about170* 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 and173* leave pt and the gvdata unchanged.174*/175public void layout(FontStrikeDesc sd, float[] mat, float ptSize, int gmask,176int baseIndex, TextRecord text, int typo_flags, Point2D.Float pt, GVData data);177}178179/**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*/183public static GlyphLayout get(LayoutEngineFactory lef) {184if (lef == null) {185lef = SunLayoutEngine.instance();186}187GlyphLayout result = null;188synchronized(GlyphLayout.class) {189if (cache != null) {190result = cache;191cache = null;192}193}194if (result == null) {195result = new GlyphLayout();196}197result._lef = lef;198return result;199}200201/**202* Return the old instance of GlyphLayout when you are done. This enables reuse203* of GlyphLayout objects.204*/205public static void done(GlyphLayout gl) {206gl._lef = null;207cache = gl; // object reference assignment is thread safe, it says here...208}209210private static final class SDCache {211public Font key_font;212public FontRenderContext key_frc;213214public AffineTransform dtx;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);230231float ptSize = font.getSize2D();232if (font.isTransformed()) {233gtx = font.getTransform();234gtx.scale(ptSize, ptSize);235delta = new Point2D.Float((float)gtx.getTranslateX(),236(float)gtx.getTranslateY());237gtx.setTransform(gtx.getScaleX(), gtx.getShearY(),238gtx.getShearX(), gtx.getScaleY(),2390, 0);240gtx.preConcatenate(dtx);241} else {242delta = ZERO_DELTA;243gtx = new AffineTransform(dtx);244gtx.scale(ptSize, ptSize);245}246247/* Similar logic to that used in SunGraphics2D.checkFontInfo().248* Whether a grey (AA) strike is needed is size dependent if249* AA mode is 'gasp'.250*/251int aa =252FontStrikeDesc.getAAHintIntVal(frc.getAntiAliasingHint(),253FontUtilities.getFont2D(font),254(int)Math.abs(ptSize));255int fm = FontStrikeDesc.getFMHintIntVal256(frc.getFractionalMetricsHint());257sd = new FontStrikeDesc(dtx, gtx, font.getStyle(), aa, fm);258}259260private static final Point2D.Float ZERO_DELTA = new Point2D.Float();261262private static263SoftReference<ConcurrentHashMap<SDKey, SDCache>> cacheRef;264265private static final class SDKey {266private final Font font;267private final FontRenderContext frc;268private final int hash;269270SDKey(Font font, FontRenderContext frc) {271this.font = font;272this.frc = frc;273this.hash = font.hashCode() ^ frc.hashCode();274}275276public int hashCode() {277return hash;278}279280public boolean equals(Object o) {281try {282SDKey rhs = (SDKey)o;283return284hash == rhs.hash &&285font.equals(rhs.font) &&286frc.equals(rhs.frc);287}288catch (ClassCastException e) {289}290return false;291}292}293294public static SDCache get(Font font, FontRenderContext frc) {295296// It is possible a translation component will be in the FRC.297// It doesn't affect us except adversely as we would consider298// FRC's which are really the same to be different. If we299// detect a translation component, then we need to exclude it300// by creating a new transform which excludes the translation.301if (frc.isTransformed()) {302AffineTransform transform = frc.getTransform();303if (transform.getTranslateX() != 0 ||304transform.getTranslateY() != 0) {305transform = new AffineTransform(transform.getScaleX(),306transform.getShearY(),307transform.getShearX(),308transform.getScaleY(),3090, 0);310frc = new FontRenderContext(transform,311frc.getAntiAliasingHint(),312frc.getFractionalMetricsHint()313);314}315}316317SDKey key = new SDKey(font, frc); // garbage, yuck...318ConcurrentHashMap<SDKey, SDCache> cache = null;319SDCache res = null;320if (cacheRef != null) {321cache = cacheRef.get();322if (cache != null) {323res = cache.get(key);324}325}326if (res == null) {327res = new SDCache(font, frc);328if (cache == null) {329cache = new ConcurrentHashMap<SDKey, SDCache>(10);330cacheRef = new331SoftReference<ConcurrentHashMap<SDKey, SDCache>>(cache);332} else if (cache.size() >= 512) {333cache.clear();334}335cache.put(key, res);336}337return res;338}339}340341/**342* Create a glyph vector.343* @param font the font to use344* @param frc the font render context345* @param text the text, including optional context before start and after start + count346* @param offset the start of the text to lay out347* @param count the length of the text to lay out348* @param flags bidi and context flags {@see #java.awt.Font}349* @param result a StandardGlyphVector to modify, can be null350* @return the layed out glyphvector, if result was passed in, it is returned351*/352public StandardGlyphVector layout(Font font, FontRenderContext frc,353char[] text, int offset, int count,354int flags, StandardGlyphVector result)355{356if (text == null || offset < 0 || count < 0 || (count > text.length - offset)) {357throw new IllegalArgumentException();358}359360init(count);361362// need to set after init363// go through the back door for this364if (font.hasLayoutAttributes()) {365AttributeValues values = ((AttributeMap)font.getAttributes()).getValues();366if (values.getKerning() != 0) _typo_flags |= 0x1;367if (values.getLigatures() != 0) _typo_flags |= 0x2;368}369370_offset = offset;371372// use cache now - can we use the strike cache for this?373374SDCache txinfo = SDCache.get(font, frc);375_mat[0] = (float)txinfo.gtx.getScaleX();376_mat[1] = (float)txinfo.gtx.getShearY();377_mat[2] = (float)txinfo.gtx.getShearX();378_mat[3] = (float)txinfo.gtx.getScaleY();379_pt.setLocation(txinfo.delta);380ptSize = font.getSize2D();381382int lim = offset + count;383384int min = 0;385int max = text.length;386if (flags != 0) {387if ((flags & Font.LAYOUT_RIGHT_TO_LEFT) != 0) {388_typo_flags |= 0x80000000; // RTL389}390391if ((flags & Font.LAYOUT_NO_START_CONTEXT) != 0) {392min = offset;393}394395if ((flags & Font.LAYOUT_NO_LIMIT_CONTEXT) != 0) {396max = lim;397}398}399400int lang = -1; // default for now401402Font2D font2D = FontUtilities.getFont2D(font);403if (font2D instanceof FontSubstitution) {404font2D = ((FontSubstitution)font2D).getCompositeFont2D();405}406407_textRecord.init(text, offset, lim, min, max);408int start = offset;409if (font2D instanceof CompositeFont) {410_scriptRuns.init(text, offset, count); // ??? how to handle 'common' chars411_fontRuns.init((CompositeFont)font2D, text, offset, lim);412while (_scriptRuns.next()) {413int limit = _scriptRuns.getScriptLimit();414int script = _scriptRuns.getScriptCode();415while (_fontRuns.next(script, limit)) {416Font2D pfont = _fontRuns.getFont();417/* layout can't deal with NativeFont instances. The418* native font is assumed to know of a suitable non-native419* substitute font. This currently works because420* its consistent with the way NativeFonts delegate421* in other cases too.422*/423if (pfont instanceof NativeFont) {424pfont = ((NativeFont)pfont).getDelegateFont();425}426int gmask = _fontRuns.getGlyphMask();427int pos = _fontRuns.getPos();428nextEngineRecord(start, pos, script, lang, pfont, gmask);429start = pos;430}431}432} else {433_scriptRuns.init(text, offset, count); // ??? don't worry about 'common' chars434while (_scriptRuns.next()) {435int limit = _scriptRuns.getScriptLimit();436int script = _scriptRuns.getScriptCode();437nextEngineRecord(start, limit, script, lang, font2D, 0);438start = limit;439}440}441442int ix = 0;443int stop = _ercount;444int dir = 1;445446if (_typo_flags < 0) { // RTL447ix = stop - 1;448stop = -1;449dir = -1;450}451452// _sd.init(dtx, gtx, font.getStyle(), frc.isAntiAliased(), frc.usesFractionalMetrics());453_sd = txinfo.sd;454for (;ix != stop; ix += dir) {455EngineRecord er = _erecords.get(ix);456for (;;) {457try {458er.layout();459break;460}461catch (IndexOutOfBoundsException e) {462if (_gvdata._count >=0) {463_gvdata.grow();464}465}466}467// Break out of the outer for loop if layout fails.468if (_gvdata._count < 0) {469break;470}471}472473// If layout fails (negative glyph count) create an un-laid out GV instead.474// ie default positions. This will be a lot better than the alternative of475// a complete blank layout.476StandardGlyphVector gv;477if (_gvdata._count < 0) {478gv = new StandardGlyphVector(font, text, offset, count, frc);479if (FontUtilities.debugFonts()) {480FontUtilities.logWarning("OpenType layout failed on font: " + font);481}482} else {483gv = _gvdata.createGlyphVector(font, frc, result);484}485// System.err.println("Layout returns: " + gv);486return gv;487}488489//490// private methods491//492493private GlyphLayout() {494this._gvdata = new GVData();495this._textRecord = new TextRecord();496this._scriptRuns = new ScriptRun();497this._fontRuns = new FontRunIterator();498this._erecords = new ArrayList<>(10);499this._pt = new Point2D.Float();500this._sd = new FontStrikeDesc();501this._mat = new float[4];502}503504private void init(int capacity) {505this._typo_flags = 0;506this._ercount = 0;507this._gvdata.init(capacity);508}509510private void nextEngineRecord(int start, int limit, int script, int lang, Font2D font, int gmask) {511EngineRecord er = null;512if (_ercount == _erecords.size()) {513er = new EngineRecord();514_erecords.add(er);515} else {516er = _erecords.get(_ercount);517}518er.init(start, limit, font, script, lang, gmask);519++_ercount;520}521522/**523* Storage for layout to build glyph vector data, then generate a real GlyphVector524*/525public static final class GVData {526public int _count; // number of glyphs, >= number of chars527public int _flags;528public int[] _glyphs;529public float[] _positions;530public int[] _indices;531532private static final int UNINITIALIZED_FLAGS = -1;533534public void init(int size) {535_count = 0;536_flags = UNINITIALIZED_FLAGS;537538if (_glyphs == null || _glyphs.length < size) {539if (size < 20) {540size = 20;541}542_glyphs = new int[size];543_positions = new float[size * 2 + 2];544_indices = new int[size];545}546}547548public void grow() {549grow(_glyphs.length / 4); // always grows because min length is 20550}551552public void grow(int delta) {553int size = _glyphs.length + delta;554int[] nglyphs = new int[size];555System.arraycopy(_glyphs, 0, nglyphs, 0, _count);556_glyphs = nglyphs;557558float[] npositions = new float[size * 2 + 2];559System.arraycopy(_positions, 0, npositions, 0, _count * 2 + 2);560_positions = npositions;561562int[] nindices = new int[size];563System.arraycopy(_indices, 0, nindices, 0, _count);564_indices = nindices;565}566567public StandardGlyphVector createGlyphVector(Font font, FontRenderContext frc, StandardGlyphVector result) {568569// !!! default initialization until we let layout engines do it570if (_flags == UNINITIALIZED_FLAGS) {571_flags = 0;572573if (_count > 1) { // if only 1 glyph assume LTR574boolean ltr = true;575boolean rtl = true;576577int rtlix = _count; // rtl index578for (int i = 0; i < _count && (ltr || rtl); ++i) {579int cx = _indices[i];580581ltr = ltr && (cx == i);582rtl = rtl && (cx == --rtlix);583}584585if (rtl) _flags |= GlyphVector.FLAG_RUN_RTL;586if (!rtl && !ltr) _flags |= GlyphVector.FLAG_COMPLEX_GLYPHS;587}588589// !!! layout engines need to tell us whether they performed590// position adjustments. currently they don't tell us, so591// we must assume they did592_flags |= GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS;593}594595int[] glyphs = new int[_count];596System.arraycopy(_glyphs, 0, glyphs, 0, _count);597598float[] positions = null;599if ((_flags & GlyphVector.FLAG_HAS_POSITION_ADJUSTMENTS) != 0) {600positions = new float[_count * 2 + 2];601System.arraycopy(_positions, 0, positions, 0, positions.length);602}603604int[] indices = null;605if ((_flags & GlyphVector.FLAG_COMPLEX_GLYPHS) != 0) {606indices = new int[_count];607System.arraycopy(_indices, 0, indices, 0, _count);608}609610if (result == null) {611result = new StandardGlyphVector(font, frc, glyphs, positions, indices, _flags);612} else {613result.initGlyphVector(font, frc, glyphs, positions, indices, _flags);614}615616return result;617}618}619620/**621* Utility class to keep track of script runs, which may have to be reordered rtl when we're622* finished.623*/624private final class EngineRecord {625private int start;626private int limit;627private int gmask;628private int eflags;629private LayoutEngineKey key;630private LayoutEngine engine;631632EngineRecord() {633key = new LayoutEngineKey();634}635636void init(int start, int limit, Font2D font, int script, int lang, int gmask) {637this.start = start;638this.limit = limit;639this.gmask = gmask;640this.key.init(font, script, lang);641this.eflags = 0;642643// only request canonical substitution if we have combining marks644for (int i = start; i < limit; ++i) {645int ch = _textRecord.text[i];646if (isHighSurrogate((char)ch) &&647i < limit - 1 &&648isLowSurrogate(_textRecord.text[i+1])) {649// rare case650ch = toCodePoint((char)ch,_textRecord.text[++i]); // inc651}652int gc = getType(ch);653if (gc == NON_SPACING_MARK ||654gc == ENCLOSING_MARK ||655gc == COMBINING_SPACING_MARK) { // could do range test also656657this.eflags = 0x4;658break;659}660}661662this.engine = _lef.getEngine(key); // flags?663}664665void layout() {666_textRecord.start = start;667_textRecord.limit = limit;668engine.layout(_sd, _mat, ptSize, gmask, start - _offset, _textRecord,669_typo_flags | eflags, _pt, _gvdata);670}671}672}673674675