/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
/*
*
* (C) Copyright IBM Corp. 2003 - All Rights Reserved
*/
package sun.font;
import sun.font.GlyphLayout.*;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
import java.awt.geom.Point2D;
import java.lang.ref.SoftReference;
import java.util.concurrent.ConcurrentHashMap;
import java.util.WeakHashMap;
/*
* different ways to do this
* 1) each physical font2d keeps a hashtable mapping scripts to layout
* engines, we query and fill this cache.
* 2) we keep a mapping independent of font using the key Most likely
* few fonts will be used, so option 2 seems better
*
* Once we know which engine to use for a font, we always know, so we
* shouldn't have to recheck each time we do layout. So the cache is
* ok.
*
* Should we reuse engines? We could instantiate an engine for each
* font/script pair. The engine would hold onto the table(s) from the
* font that it needs. If we have multiple threads using the same
* engine, we still need to keep the state separate, so the native
* engines would still need to be allocated for each call, since they
* keep their state in themselves. If they used the passed-in GVData
* arrays directly (with some checks for space) then since each GVData
* is different per thread, we could reuse the layout engines. This
* still requires a separate layout engine per font, because of the
* table state in the engine. If we pushed that out too and passed it
* in with the native call as well, we'd be ok if the layout engines
* keep all their process state on the stack, but I don't know if this
* is true. Then we'd basically just be down to an engine index which
* we pass into native and then invoke the engine code (now a
* procedure call, not an object invocation) based on a switch on the
* index. There would be only half a dozen engine objects then, not
* potentially half a dozen per font. But we'd have to stack-allocate
* some state that included the pointer to the required font tables.
*
* Seems for now that the way to do things is to come in with a
* selector and the font. The selector indicates which engine to use,
* the engine is stack allocated and initialized with the required
* font tables (the selector indicates which). Then layout is called,
* the contents are copied (or not), and the stack is destroyed on
* exit. So the association is between the font/script (layout engine
* desc) and one of a few permanent engine objects, which are
* handed the key when they need to process something. In the native
* case, the engine holds an index, and just passes it together with
* the key info down to native. Some default cases are the 'default
* layout' case that just runs the c2gmapper, this stays in java and
* just uses the mapper from the font/strike. Another default case
* might be the unicode arabic shaper, since this doesn't care about
* the font (or script or lang?) it wouldn't need to extract this
* data. It could be (yikes) ported back to java even to avoid
* upcalls to check if the font supports a particular unicode
* character.
*
* I'd expect that the majority of scripts use the default mapper for
* a particular font. Loading the hastable with 40 or so keys 30+ of
* which all map to the same object is unfortunate. It might be worth
* instead having a per-font list of 'scripts with non-default
* engines', e.g. the factory has a hashtable mapping fonts to 'script
* lists' (the factory has this since the design potentially has other
* factories, though I admit there's no client for this yet and no
* public api) and then the script list is queried for the script in
* question. it can be preloaded at creation time with all the
* scripts that don't have default engines-- either a list or a hash
* table, so a null return from the table means 'default' and not 'i
* don't know yet'.
*
* On the other hand, in most all cases the number of unique
* script/font combinations will be small, so a flat hashtable should
* suffice.
* */
public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory {
static {
FontManagerNativeLibrary.load();
}
private LayoutEngineKey key;
private static LayoutEngineFactory instance;
public static LayoutEngineFactory instance() {
if (instance == null) {
instance = new SunLayoutEngine();
}
return instance;
}
private SunLayoutEngine() {
// actually a factory, key is null so layout cannot be called on it
}
public LayoutEngine getEngine(Font2D font, int script, int lang) {
return getEngine(new LayoutEngineKey(font, script, lang));
}
// !!! don't need this unless we have more than one sun layout engine...
public LayoutEngine getEngine(LayoutEngineKey key) {
ConcurrentHashMap<LayoutEngineKey, LayoutEngine> cache = cacheref.get();
if (cache == null) {
cache = new ConcurrentHashMap<>();
cacheref = new SoftReference<>(cache);
}
LayoutEngine e = cache.get(key);
if (e == null) {
LayoutEngineKey copy = key.copy();
e = new SunLayoutEngine(copy);
cache.put(copy, e);
}
return e;
}
private SoftReference<ConcurrentHashMap<LayoutEngineKey, LayoutEngine>> cacheref =
new SoftReference<>(null);
private SunLayoutEngine(LayoutEngineKey key) {
this.key = key;
}
static WeakHashMap<Font2D, Boolean> aatInfo = new WeakHashMap<>();
private static final WeakHashMap<Font2D, FaceRef> facePtr =
new WeakHashMap<>();
private static boolean isAAT(Font2D font) {
Boolean aatObj;
synchronized (aatInfo) {
aatObj = aatInfo.get(font);
}
if (aatObj != null) {
return aatObj.booleanValue();
}
boolean aat = false;
if (font instanceof TrueTypeFont) {
TrueTypeFont ttf = (TrueTypeFont)font;
aat = ttf.getDirectoryEntry(TrueTypeFont.morxTag) != null ||
ttf.getDirectoryEntry(TrueTypeFont.mortTag) != null;
} else if (font instanceof PhysicalFont) {
PhysicalFont pf = (PhysicalFont)font;
aat = pf.getTableBytes(TrueTypeFont.morxTag) != null ||
pf.getTableBytes(TrueTypeFont.mortTag) != null;
}
synchronized (aatInfo) {
aatInfo.put(font, Boolean.valueOf(aat));
}
return aat;
}
private long getFacePtr(Font2D font2D) {
FaceRef ref;
synchronized (facePtr) {
ref = facePtr.computeIfAbsent(font2D, FaceRef::new);
}
return ref.getNativePtr();
}
public void layout(FontStrikeDesc desc, float[] mat, float ptSize, int gmask,
int baseIndex, TextRecord tr, int typo_flags,
Point2D.Float pt, GVData data) {
Font2D font = key.font();
FontStrike strike = font.getStrike(desc);
long pNativeFont = font.getPlatformNativeFontPtr(); // used on OSX
long pFace = getFacePtr(font);
if (pFace != 0) {
shape(font, strike, ptSize, mat, pNativeFont,
pFace, isAAT(font),
tr.text, data, key.script(),
tr.start, tr.limit, baseIndex, pt,
typo_flags, gmask);
}
}
/* Native method to invoke harfbuzz layout engine */
private static native boolean
shape(Font2D font, FontStrike strike, float ptSize, float[] mat,
long pNativeFont, long pFace, boolean aat,
char[] chars, GVData data,
int script, int offset, int limit,
int baseIndex, Point2D.Float pt, int typo_flags, int slot);
private static native long createFace(Font2D font,
boolean aat,
long platformNativeFontPtr);
private static native void disposeFace(long facePtr);
private static class FaceRef implements DisposerRecord {
private Font2D font;
private Long facePtr;
private FaceRef(Font2D font) {
this.font = font;
}
private synchronized long getNativePtr() {
if (facePtr == null) {
facePtr = createFace(font, isAAT(font),
font.getPlatformNativeFontPtr());
if (facePtr != 0) {
Disposer.addObjectRecord(font, this);
}
font = null;
}
return facePtr;
}
@Override
public void dispose() {
disposeFace(facePtr);
}
}
}