1 /* 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 it 5 * under the terms of the GNU General Public License version 2 only, as 6 * published by the Free Software Foundation. Oracle designates this 7 * particular file as subject to the "Classpath" exception as provided 8 * 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 WITHOUT 11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 * version 2 for more details (a copy is included in the LICENSE file that 14 * accompanied this code). 15 * 16 * You should have received a copy of the GNU General Public License version 17 * 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 USA 21 * or visit www.oracle.com if you need additional information or have any 22 * questions. 23 * 24 */ 25 26 /* 27 * 28 * (C) Copyright IBM Corp. 2003 - All Rights Reserved 29 */ 30 31 package sun.font; 32 33 import sun.font.GlyphLayout.*; 34 import sun.java2d.Disposer; 35 import sun.java2d.DisposerRecord; 36 37 import java.awt.geom.Point2D; 38 import java.lang.ref.SoftReference; 39 import java.util.concurrent.ConcurrentHashMap; 40 import java.util.WeakHashMap; 41 42 /* 43 * different ways to do this 44 * 1) each physical font2d keeps a hashtable mapping scripts to layout 45 * engines, we query and fill this cache. 46 * 2) we keep a mapping independent of font using the key Most likely 47 * few fonts will be used, so option 2 seems better 48 * 49 * Once we know which engine to use for a font, we always know, so we 50 * shouldn't have to recheck each time we do layout. So the cache is 51 * ok. 52 * 53 * Should we reuse engines? We could instantiate an engine for each 54 * font/script pair. The engine would hold onto the table(s) from the 55 * font that it needs. If we have multiple threads using the same 56 * engine, we still need to keep the state separate, so the native 57 * engines would still need to be allocated for each call, since they 58 * keep their state in themselves. If they used the passed-in GVData 59 * arrays directly (with some checks for space) then since each GVData 60 * is different per thread, we could reuse the layout engines. This 61 * still requires a separate layout engine per font, because of the 62 * table state in the engine. If we pushed that out too and passed it 63 * in with the native call as well, we'd be ok if the layout engines 64 * keep all their process state on the stack, but I don't know if this 65 * is true. Then we'd basically just be down to an engine index which 66 * we pass into native and then invoke the engine code (now a 67 * procedure call, not an object invocation) based on a switch on the 68 * index. There would be only half a dozen engine objects then, not 69 * potentially half a dozen per font. But we'd have to stack-allocate 70 * some state that included the pointer to the required font tables. 71 * 72 * Seems for now that the way to do things is to come in with a 73 * selector and the font. The selector indicates which engine to use, 74 * the engine is stack allocated and initialized with the required 75 * font tables (the selector indicates which). Then layout is called, 76 * the contents are copied (or not), and the stack is destroyed on 77 * exit. So the association is between the font/script (layout engine 78 * desc) and one of a few permanent engine objects, which are 79 * handed the key when they need to process something. In the native 80 * case, the engine holds an index, and just passes it together with 81 * the key info down to native. Some default cases are the 'default 82 * layout' case that just runs the c2gmapper, this stays in java and 83 * just uses the mapper from the font/strike. Another default case 84 * might be the unicode arabic shaper, since this doesn't care about 85 * the font (or script or lang?) it wouldn't need to extract this 86 * data. It could be (yikes) ported back to java even to avoid 87 * upcalls to check if the font supports a particular unicode 88 * character. 89 * 90 * I'd expect that the majority of scripts use the default mapper for 91 * a particular font. Loading the hastable with 40 or so keys 30+ of 92 * which all map to the same object is unfortunate. It might be worth 93 * instead having a per-font list of 'scripts with non-default 94 * engines', e.g. the factory has a hashtable mapping fonts to 'script 95 * lists' (the factory has this since the design potentially has other 96 * factories, though I admit there's no client for this yet and no 97 * public api) and then the script list is queried for the script in 98 * question. it can be preloaded at creation time with all the 99 * scripts that don't have default engines-- either a list or a hash 100 * table, so a null return from the table means 'default' and not 'i 101 * don't know yet'. 102 * 103 * On the other hand, in most all cases the number of unique 104 * script/font combinations will be small, so a flat hashtable should 105 * suffice. 106 * */ 107 public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory { 108 static { 109 FontManagerNativeLibrary.load(); 110 } 111 112 private LayoutEngineKey key; 113 114 private static LayoutEngineFactory instance; 115 116 public static LayoutEngineFactory instance() { 117 if (instance == null) { 118 instance = new SunLayoutEngine(); 119 } 120 return instance; 121 } 122 123 private SunLayoutEngine() { 124 // actually a factory, key is null so layout cannot be called on it 125 } 126 127 public LayoutEngine getEngine(Font2D font, int script, int lang) { 128 return getEngine(new LayoutEngineKey(font, script, lang)); 129 } 130 131 // !!! don't need this unless we have more than one sun layout engine... 132 public LayoutEngine getEngine(LayoutEngineKey key) { 133 ConcurrentHashMap<LayoutEngineKey, LayoutEngine> cache = cacheref.get(); 134 if (cache == null) { 135 cache = new ConcurrentHashMap<>(); 136 cacheref = new SoftReference<>(cache); 137 } 138 139 LayoutEngine e = cache.get(key); 140 if (e == null) { 141 LayoutEngineKey copy = key.copy(); 142 e = new SunLayoutEngine(copy); 143 cache.put(copy, e); 144 } 145 return e; 146 } 147 private SoftReference<ConcurrentHashMap<LayoutEngineKey, LayoutEngine>> cacheref = 148 new SoftReference<>(null); 149 150 private SunLayoutEngine(LayoutEngineKey key) { 151 this.key = key; 152 } 153 154 static WeakHashMap<Font2D, Boolean> aatInfo = new WeakHashMap<>(); 155 private static final WeakHashMap<Font2D, FaceRef> facePtr = 156 new WeakHashMap<>(); 157 158 private static boolean isAAT(Font2D font) { 159 Boolean aatObj; 160 synchronized (aatInfo) { 161 aatObj = aatInfo.get(font); 162 } 163 if (aatObj != null) { 164 return aatObj.booleanValue(); 165 } 166 boolean aat = false; 167 if (font instanceof TrueTypeFont) { 168 TrueTypeFont ttf = (TrueTypeFont)font; 169 aat = ttf.getDirectoryEntry(TrueTypeFont.morxTag) != null || 170 ttf.getDirectoryEntry(TrueTypeFont.mortTag) != null; 171 } else if (font instanceof PhysicalFont) { 172 PhysicalFont pf = (PhysicalFont)font; 173 aat = pf.getTableBytes(TrueTypeFont.morxTag) != null || 174 pf.getTableBytes(TrueTypeFont.mortTag) != null; 175 } 176 synchronized (aatInfo) { 177 aatInfo.put(font, Boolean.valueOf(aat)); 178 } 179 return aat; 180 } 181 182 private long getFacePtr(Font2D font2D) { 183 FaceRef ref; 184 synchronized (facePtr) { 185 ref = facePtr.computeIfAbsent(font2D, FaceRef::new); 186 } 187 return ref.getNativePtr(); 188 } 189 190 public void layout(FontStrikeDesc desc, float[] mat, float ptSize, int gmask, 191 int baseIndex, TextRecord tr, int typo_flags, 192 Point2D.Float pt, GVData data) { 193 Font2D font = key.font(); 194 FontStrike strike = font.getStrike(desc); 195 long pNativeFont = font.getPlatformNativeFontPtr(); // used on OSX 196 // pScaler probably not needed long term. 197 long pScaler = 0L; 198 if (font instanceof FileFont) { 199 pScaler = ((FileFont)font).getScaler().nativeScaler; 200 } 201 long pFace = getFacePtr(font); 202 if (pFace != 0) { 203 shape(font, strike, ptSize, mat, pScaler, pNativeFont, 204 pFace, isAAT(font), 205 tr.text, data, key.script(), 206 tr.start, tr.limit, baseIndex, pt, 207 typo_flags, gmask); 208 } 209 } 210 211 /* Native method to invoke harfbuzz layout engine */ 212 private static native boolean 213 shape(Font2D font, FontStrike strike, float ptSize, float[] mat, 214 long pscaler, long pNativeFont, long pFace, boolean aat, 215 char[] chars, GVData data, 216 int script, int offset, int limit, 217 int baseIndex, Point2D.Float pt, int typo_flags, int slot); 218 219 private static native long createFace(Font2D font, 220 boolean aat, 221 long platformNativeFontPtr, 222 long layoutTables); 223 224 private static native void disposeFace(long facePtr); 225 226 private static class FaceRef implements DisposerRecord { 227 private Font2D font; 228 private Long facePtr; 229 230 private FaceRef(Font2D font) { 231 this.font = font; 232 } 233 234 private synchronized long getNativePtr() { 235 if (facePtr == null) { 236 facePtr = createFace(font, isAAT(font), 237 font.getPlatformNativeFontPtr(), 238 font.getLayoutTableCache()); 239 if (facePtr != 0) { 240 Disposer.addObjectRecord(font, this); 241 } 242 font = null; 243 } 244 return facePtr; 245 } 246 247 @Override 248 public void dispose() { 249 disposeFace(facePtr); 250 } 251 } 252 }