changeset 13225:81619853311c

7162125: [macosx] A font has different behaviour for ligatures depending on its creation mod Reviewed-by: srl, jgodinez
author prr
date Mon, 16 Nov 2015 16:07:46 -0800
parents 0aee778df141
children 7e7126dd06bb
files src/java.desktop/macosx/classes/sun/font/CCompositeGlyphMapper.java src/java.desktop/macosx/classes/sun/font/CFont.java src/java.desktop/macosx/classes/sun/font/CStrike.java src/java.desktop/macosx/native/libawt_lwawt/font/AWTFont.h src/java.desktop/macosx/native/libawt_lwawt/font/AWTFont.m src/java.desktop/share/classes/sun/font/CompositeFont.java src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java src/java.desktop/share/classes/sun/font/Font2D.java src/java.desktop/share/classes/sun/font/FontSubstitution.java src/java.desktop/share/classes/sun/font/GlyphLayout.java src/java.desktop/share/classes/sun/font/StandardGlyphVector.java src/java.desktop/share/classes/sun/font/SunLayoutEngine.java src/java.desktop/share/classes/sun/font/TrueTypeFont.java test/java/awt/font/TextLayout/OSXLigatureTest.java
diffstat 14 files changed, 540 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.desktop/macosx/classes/sun/font/CCompositeGlyphMapper.java	Mon Nov 16 16:07:46 2015 -0800
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * 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.
+ */
+
+package sun.font;
+
+public final class CCompositeGlyphMapper extends CompositeGlyphMapper {
+
+    private CompositeFont font;
+    private CharToGlyphMapper slotMappers[];
+
+    public CCompositeGlyphMapper(CompositeFont compFont) {
+        super(compFont);
+        font = compFont;
+        slotMappers = new CharToGlyphMapper[font.numSlots];
+        missingGlyph = 0;
+    }
+
+    private CharToGlyphMapper getSlotMapper(int slot) {
+        CharToGlyphMapper mapper = slotMappers[slot];
+        if (mapper == null) {
+            mapper = font.getSlotFont(slot).getMapper();
+            slotMappers[slot] = mapper;
+        }
+        return mapper;
+    }
+
+    public boolean canDisplay(char ch) {
+        int glyph = charToGlyph(ch);
+        return glyph != missingGlyph;
+    }
+
+    private int convertToGlyph(int unicode) {
+        for (int slot = 0; slot < font.numSlots; slot++) {
+            CharToGlyphMapper mapper = getSlotMapper(slot);
+            int glyphCode = mapper.charToGlyph(unicode);
+            // The CFont Mappers will return a negative code
+            // for fonts that will fill the glyph from fallbacks
+            // - cascading font in OSX-speak. But we need to be
+            //  know here that only the codes > 0 are really present.
+            if (glyphCode > 0) {
+                glyphCode = compositeGlyphCode(slot, glyphCode);
+                return glyphCode;
+            }
+        }
+        return missingGlyph;
+    }
+
+    public int getNumGlyphs() {
+        int numGlyphs = 0;
+        for (int slot=0; slot<1 /*font.numSlots*/; slot++) {
+           CharToGlyphMapper mapper = slotMappers[slot];
+           if (mapper == null) {
+               mapper = font.getSlotFont(slot).getMapper();
+               slotMappers[slot] = mapper;
+           }
+           numGlyphs += mapper.getNumGlyphs();
+        }
+        return numGlyphs;
+    }
+
+    public int charToGlyph(int unicode) {
+        return convertToGlyph(unicode);
+    }
+
+    public int charToGlyph(char unicode) {
+        return convertToGlyph(unicode);
+    }
+
+    public boolean charsToGlyphsNS(int count, char[] unicodes, int[] glyphs) {
+
+        for (int i=0; i<count; i++) {
+            int code = unicodes[i]; // char is unsigned.
+
+            if (code >= HI_SURROGATE_START &&
+                code <= HI_SURROGATE_END && i < count - 1) {
+                char low = unicodes[i + 1];
+
+                if (low >= LO_SURROGATE_START &&
+                    low <= LO_SURROGATE_END) {
+                    code = (code - HI_SURROGATE_START) *
+                        0x400 + low - LO_SURROGATE_START + 0x10000;
+                    glyphs[i + 1] = INVISIBLE_GLYPH_ID;
+                }
+            }
+
+            glyphs[i] = convertToGlyph(code);
+
+            if (code < FontUtilities.MIN_LAYOUT_CHARCODE) {
+                continue;
+            }
+            else if (FontUtilities.isComplexCharCode(code)) {
+                return true;
+            }
+            else if (code >= 0x10000) {
+                i += 1; // Empty glyph slot after surrogate
+                continue;
+            }
+        }
+
+        return false;
+    }
+
+    public void charsToGlyphs(int count, char[] unicodes, int[] glyphs) {
+        for (int i=0; i<count; i++) {
+            int code = unicodes[i]; // char is unsigned.
+
+            if (code >= HI_SURROGATE_START &&
+                code <= HI_SURROGATE_END && i < count - 1) {
+                char low = unicodes[i + 1];
+
+                if (low >= LO_SURROGATE_START &&
+                    low <= LO_SURROGATE_END) {
+                    code = (code - HI_SURROGATE_START) *
+                        0x400 + low - LO_SURROGATE_START + 0x10000;
+
+                    glyphs[i] = convertToGlyph(code);
+                    i += 1; // Empty glyph slot after surrogate
+                    glyphs[i] = INVISIBLE_GLYPH_ID;
+                    continue;
+                }
+            }
+
+            glyphs[i] = convertToGlyph(code);
+        }
+    }
+
+    public void charsToGlyphs(int count, int[] unicodes, int[] glyphs) {
+        for (int i=0; i<count; i++) {
+             glyphs[i] = convertToGlyph(unicodes[i]);
+        }
+    }
+
+}
--- a/src/java.desktop/macosx/classes/sun/font/CFont.java	Mon Nov 16 15:03:17 2015 +0400
+++ b/src/java.desktop/macosx/classes/sun/font/CFont.java	Mon Nov 16 16:07:46 2015 -0800
@@ -31,12 +31,13 @@
 import java.awt.geom.GeneralPath;;
 import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
 
 // Right now this class is final to avoid a problem with native code.
 // For some reason the JNI IsInstanceOf was not working correctly
 // so we are checking the class specifically. If we subclass this
 // we need to modify the native code in CFontWrapper.m
-public final class CFont extends PhysicalFont {
+public final class CFont extends PhysicalFont implements FontSubstitution {
 
     /* CFontStrike doesn't call these methods so they are unimplemented.
      * They are here to meet the requirements of PhysicalFont, needed
@@ -76,6 +77,20 @@
        throw new InternalError("Not implemented");
     }
 
+    @Override
+    protected long getLayoutTableCache() {
+        return getLayoutTableCacheNative(getNativeFontPtr());
+    }
+
+    @Override
+    protected byte[] getTableBytes(int tag) {
+        return getTableBytesNative(getNativeFontPtr(), tag);
+    }
+
+    private native synchronized long getLayoutTableCacheNative(long nativeFontPtr);
+
+    private native byte[] getTableBytesNative(long nativeFontPtr, int tag);
+
     private static native long createNativeFont(final String nativeFontName,
                                                 final int style);
     private static native void disposeNativeFont(final long nativeFontPtr);
@@ -179,10 +194,51 @@
     protected synchronized long getNativeFontPtr() {
         if (nativeFontPtr == 0L) {
             nativeFontPtr = createNativeFont(nativeFontName, style);
-}
+        }
         return nativeFontPtr;
     }
 
+    static native void getCascadeList(long nativeFontPtr, ArrayList<String> listOfString);
+
+    private CompositeFont createCompositeFont() {
+        ArrayList<String> listOfString = new ArrayList<String>();
+        getCascadeList(nativeFontPtr, listOfString);
+
+        FontManager fm = FontManagerFactory.getInstance();
+        int numFonts = 1 + listOfString.size();
+        PhysicalFont[] fonts = new PhysicalFont[numFonts];
+        fonts[0] = this;
+        int idx = 1;
+        for (String s : listOfString) {
+            if (s.equals(".AppleSymbolsFB"))  {
+                // Don't know why we get the weird name above .. replace.
+                s = "AppleSymbols";
+            }
+            Font2D f2d = fm.findFont2D(s, Font.PLAIN, FontManager.NO_FALLBACK);
+            if (f2d == null || f2d == this) {
+                continue;
+            }
+            fonts[idx++] = (PhysicalFont)f2d;
+        }
+        if (idx < fonts.length) {
+            PhysicalFont[] orig = fonts;
+            fonts = new PhysicalFont[idx];
+            System.arraycopy(orig, 0, fonts, 0, idx);
+        }
+        CompositeFont compFont = new CompositeFont(fonts);
+        compFont.mapper = new CCompositeGlyphMapper(compFont);
+        return compFont;
+    }
+
+    private CompositeFont compFont;
+
+    public CompositeFont getCompositeFont2D() {
+        if (compFont == null) {
+           compFont = createCompositeFont();
+        }
+        return compFont;
+    }
+
     protected synchronized void finalize() {
         if (nativeFontPtr != 0) {
             disposeNativeFont(nativeFontPtr);
--- a/src/java.desktop/macosx/classes/sun/font/CStrike.java	Mon Nov 16 15:03:17 2015 +0400
+++ b/src/java.desktop/macosx/classes/sun/font/CStrike.java	Mon Nov 16 16:07:46 2015 -0800
@@ -31,7 +31,7 @@
 
 import sun.awt.SunHints;
 
-public final class CStrike extends FontStrike {
+public final class CStrike extends PhysicalStrike {
 
     // Creates the native strike
     private static native long createNativeStrikePtr(long nativeFontPtr,
--- a/src/java.desktop/macosx/native/libawt_lwawt/font/AWTFont.h	Mon Nov 16 15:03:17 2015 +0400
+++ b/src/java.desktop/macosx/native/libawt_lwawt/font/AWTFont.h	Mon Nov 16 16:07:46 2015 -0800
@@ -26,6 +26,8 @@
 #import <Cocoa/Cocoa.h>
 #import <JavaRuntimeSupport/JavaRuntimeSupport.h>
 
+#import "fontscalerdefs.h"
+
 #define MAX_STACK_ALLOC_GLYPH_BUFFER_SIZE 256
 
 @interface AWTFont : NSObject {
@@ -33,6 +35,7 @@
     NSFont    *fFont;
     CGFontRef  fNativeCGFont;
     BOOL       fIsFakeItalic;
+    TTLayoutTableCache* layoutTableCache;
 }
 
 + (AWTFont *) awtFontForName:(NSString *)name
--- a/src/java.desktop/macosx/native/libawt_lwawt/font/AWTFont.m	Mon Nov 16 15:03:17 2015 +0400
+++ b/src/java.desktop/macosx/native/libawt_lwawt/font/AWTFont.m	Mon Nov 16 16:07:46 2015 -0800
@@ -42,10 +42,33 @@
     if (self) {
         fFont = [font retain];
         fNativeCGFont = CTFontCopyGraphicsFont((CTFontRef)font, NULL);
+        layoutTableCache = NULL;
     }
     return self;
 }
 
+static TTLayoutTableCache* newCFontLayoutTableCache() {
+  TTLayoutTableCache* ltc = calloc(1, sizeof(TTLayoutTableCache));
+  if (ltc) {
+    int i;
+    for(i=0;i<LAYOUTCACHE_ENTRIES;i++) {
+      ltc->entries[i].len = -1;
+    }
+  }
+  return ltc;
+}
+
+static void freeCFontLayoutTableCache(TTLayoutTableCache* ltc) {
+  if (ltc) {
+    int i;
+    for(i=0;i<LAYOUTCACHE_ENTRIES;i++) {
+      if(ltc->entries[i].ptr) free (ltc->entries[i].ptr);
+    }
+    if (ltc->kernPairs) free(ltc->kernPairs);
+    free(ltc);
+  }
+}
+
 - (void) dealloc {
     [fFont release];
     fFont = nil;
@@ -53,6 +76,10 @@
     if (fNativeCGFont) {
         CGFontRelease(fNativeCGFont);
     fNativeCGFont = NULL;
+    if (layoutTableCache != NULL) {
+        freeCFontLayoutTableCache(layoutTableCache);
+        layoutTableCache = NULL;
+    }
     }
 
     [super dealloc];
@@ -63,6 +90,10 @@
         CGFontRelease(fNativeCGFont);
     fNativeCGFont = NULL;
     }
+    if (layoutTableCache != NULL) {
+        freeCFontLayoutTableCache(layoutTableCache);
+        layoutTableCache = NULL;
+    }
     [super finalize];
 }
 
@@ -345,6 +376,95 @@
 
 /*
  * Class:     sun_font_CFont
+ * Method:    getPlatformFontPtrNative
+ * Signature: (JI)[B
+ */
+JNIEXPORT jlong JNICALL
+Java_sun_font_CFont_getCGFontPtrNative
+    (JNIEnv *env, jclass clazz,
+     jlong awtFontPtr)
+{
+    AWTFont *awtFont = (AWTFont *)jlong_to_ptr(awtFontPtr);
+    return (jlong)(awtFont->fNativeCGFont);
+}
+
+/*
+ * Class:     sun_font_CFont
+ * Method:    getLayoutTableCacheNative
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL
+Java_sun_font_CFont_getLayoutTableCacheNative
+    (JNIEnv *env, jclass clazz,
+     jlong awtFontPtr)
+{
+    AWTFont *awtFont = (AWTFont *)jlong_to_ptr(awtFontPtr);
+    if (awtFont->layoutTableCache == NULL) {
+        awtFont->layoutTableCache = newCFontLayoutTableCache();
+    }
+    return (jlong)(awtFont->layoutTableCache);
+}
+
+/*
+ * Class:     sun_font_CFont
+ * Method:    getTableBytesNative
+ * Signature: (JI)[B
+ */
+JNIEXPORT jbyteArray JNICALL
+Java_sun_font_CFont_getTableBytesNative
+    (JNIEnv *env, jclass clazz,
+     jlong awtFontPtr, jint jtag)
+{
+    jbyteArray jbytes = NULL;
+JNF_COCOA_ENTER(env);
+
+    CTFontTableTag tag = (CTFontTableTag)jtag;
+    int i, found = 0;
+    AWTFont *awtFont = (AWTFont *)jlong_to_ptr(awtFontPtr);
+    NSFont* nsFont = awtFont->fFont;
+    CTFontRef ctfont = (CTFontRef)nsFont;
+    CFArrayRef tagsArray =
+        CTFontCopyAvailableTables(ctfont, kCTFontTableOptionNoOptions);
+    CFIndex numTags = CFArrayGetCount(tagsArray);
+    for (i=0; i<numTags; i++) {
+        if (tag ==
+            (CTFontTableTag)(uintptr_t)CFArrayGetValueAtIndex(tagsArray, i)) {
+            found = 1;
+            break;
+        }
+    }
+    CFRelease(tagsArray);
+    if (!found) {
+        return NULL;
+    }
+    CFDataRef table = CTFontCopyTable(ctfont, tag, kCTFontTableOptionNoOptions);
+    if (table == NULL) {
+        return NULL;
+    }
+
+    char *tableBytes = (char*)(CFDataGetBytePtr(table));
+    size_t tableLength = CFDataGetLength(table);
+    if (tableBytes == NULL || tableLength == 0) {
+        CFRelease(table);
+        return NULL;
+    }
+
+    jbytes = (*env)->NewByteArray(env, (jsize)tableLength);
+    if (jbytes == NULL) {
+        return NULL;
+    }
+    (*env)->SetByteArrayRegion(env, jbytes, 0,
+                               (jsize)tableLength,
+                               (jbyte*)tableBytes);
+    CFRelease(table);
+
+JNF_COCOA_EXIT(env);
+
+    return jbytes;
+}
+
+/*
+ * Class:     sun_font_CFont
  * Method:    initNativeFont
  * Signature: (Ljava/lang/String;I)J
  */
@@ -460,3 +580,42 @@
 {
 }
 #endif
+
+/*
+ * Class:     sun_awt_FontDescriptor
+ * Method:    initIDs
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+Java_sun_font_CFont_getCascadeList
+    (JNIEnv *env, jclass cls, jlong awtFontPtr, jobject arrayListOfString)
+{
+    jclass alc = (*env)->FindClass(env, "java/util/ArrayList");
+    if (alc == NULL) return;
+    jmethodID addMID = (*env)->GetMethodID(env, alc, "add", "(Ljava/lang/Object;)Z");
+    if (addMID == NULL) return;
+
+    CFIndex i;
+    AWTFont *awtFont = (AWTFont *)jlong_to_ptr(awtFontPtr);
+    NSFont* nsFont = awtFont->fFont;
+    CTFontRef font = (CTFontRef)nsFont;
+    CFStringRef base = CTFontCopyFullName(font);
+    CFArrayRef codes = CFLocaleCopyISOLanguageCodes();
+
+#ifdef DEBUG
+    NSLog(@"BaseFont is : %@", (NSString*)base);
+#endif
+    CFArrayRef fds = CTFontCopyDefaultCascadeListForLanguages(font, codes);
+    CFIndex cnt = CFArrayGetCount(fds);
+    for (i=0; i<cnt; i++) {
+        CTFontDescriptorRef ref = CFArrayGetValueAtIndex(fds, i);
+        CFStringRef fontname =
+            CTFontDescriptorCopyAttribute(ref, kCTFontNameAttribute);
+#ifdef DEBUG
+        NSLog(@"Font is : %@", (NSString*)fontname);
+#endif
+        jstring jFontName = (jstring)JNFNSToJavaString(env, fontname);
+        (*env)->CallBooleanMethod(env, arrayListOfString, addMID, jFontName); 
+        (*env)->DeleteLocalRef(env, jFontName);
+    }
+}
--- a/src/java.desktop/share/classes/sun/font/CompositeFont.java	Mon Nov 16 15:03:17 2015 +0400
+++ b/src/java.desktop/share/classes/sun/font/CompositeFont.java	Mon Nov 16 16:07:46 2015 -0800
@@ -149,6 +149,25 @@
         }
     }
 
+    /*
+     * Build a composite from a set of individual slot fonts.
+     */
+    CompositeFont(PhysicalFont[] slotFonts) {
+
+        isStdComposite = false;
+        handle = new Font2DHandle(this);
+        fullName = slotFonts[0].fullName;
+        familyName = slotFonts[0].familyName;
+        style = slotFonts[0].style;
+
+        numMetricsSlots = 1; /* Only the physical Font */
+        numSlots = slotFonts.length;
+
+        components = new PhysicalFont[numSlots];
+        System.arraycopy(slotFonts, 0, components, 0, numSlots);
+        deferredInitialisation = new boolean[numSlots]; // all false.
+    }
+
     /* This method is currently intended to be called only from
      * FontManager.getCompositeFontUIResource(Font)
      * It creates a new CompositeFont with the contents of the Physical
--- a/src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java	Mon Nov 16 15:03:17 2015 +0400
+++ b/src/java.desktop/share/classes/sun/font/CompositeGlyphMapper.java	Mon Nov 16 16:07:46 2015 -0800
@@ -42,7 +42,7 @@
  * this appears to cause problems.
  */
 
-public final class CompositeGlyphMapper extends CharToGlyphMapper {
+public class CompositeGlyphMapper extends CharToGlyphMapper {
 
     public static final int SLOTMASK =  0xff000000;
     public static final int GLYPHMASK = 0x00ffffff;
--- a/src/java.desktop/share/classes/sun/font/Font2D.java	Mon Nov 16 15:03:17 2015 +0400
+++ b/src/java.desktop/share/classes/sun/font/Font2D.java	Mon Nov 16 16:07:46 2015 -0800
@@ -461,10 +461,17 @@
      * to check the font class before attempting to run, rather than needing
      * to promote this method up from TrueTypeFont
      */
-    byte[] getTableBytes(int tag) {
+    protected byte[] getTableBytes(int tag) {
         return null;
     }
 
+    /* implemented for fonts backed by an sfnt that has
+     * OpenType or AAT layout tables.
+     */
+    protected long getLayoutTableCache() {
+        return 0L;
+    }
+
     /* for layout code */
     protected long getUnitsPerEm() {
         return 2048;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.desktop/share/classes/sun/font/FontSubstitution.java	Mon Nov 16 16:07:46 2015 -0800
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * 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.
+ */
+package sun.font;
+
+
+
+/**
+ * Interface that indicates a Font2D that is not a Composite but has the
+ * property that it internally behaves like one, substituting glyphs
+ * from another font at render time.
+ * In this case the Font must provide a way to behave like a regular
+ * composite when that behaviour is not wanted.
+ */
+public interface FontSubstitution {
+    public CompositeFont getCompositeFont2D();
+}
--- a/src/java.desktop/share/classes/sun/font/GlyphLayout.java	Mon Nov 16 15:03:17 2015 +0400
+++ b/src/java.desktop/share/classes/sun/font/GlyphLayout.java	Mon Nov 16 16:07:46 2015 -0800
@@ -408,6 +408,9 @@
         int lang = -1; // default for now
 
         Font2D font2D = FontUtilities.getFont2D(font);
+        if (font2D instanceof FontSubstitution) {
+            font2D = ((FontSubstitution)font2D).getCompositeFont2D();
+        }
 
         _textRecord.init(text, offset, lim, min, max);
         int start = offset;
--- a/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java	Mon Nov 16 15:03:17 2015 +0400
+++ b/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java	Mon Nov 16 16:07:46 2015 -0800
@@ -1124,6 +1124,9 @@
 
     private void initFontData() {
         font2D = FontUtilities.getFont2D(font);
+        if (font2D instanceof FontSubstitution) {
+           font2D = ((FontSubstitution)font2D).getCompositeFont2D();
+        }
         float s = font.getSize2D();
         if (font.isTransformed()) {
             ftx = font.getTransform();
@@ -1742,7 +1745,12 @@
                                                      aa, fm);
             // Get the strike via the handle. Shouldn't matter
             // if we've invalidated the font but its an extra precaution.
-            FontStrike strike = sgv.font2D.handle.font2D.getStrike(desc);  // !!! getStrike(desc, false)
+        // do we want the CompFont from CFont here ?
+        Font2D f2d = sgv.font2D;
+        if (f2d instanceof FontSubstitution) {
+           f2d = ((FontSubstitution)f2d).getCompositeFont2D();
+        }
+            FontStrike strike = f2d.handle.font2D.getStrike(desc);  // !!! getStrike(desc, false)
 
             return new GlyphStrike(sgv, strike, dx, dy);
         }
--- a/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java	Mon Nov 16 15:03:17 2015 +0400
+++ b/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java	Mon Nov 16 16:07:46 2015 -0800
@@ -155,10 +155,7 @@
                        Point2D.Float pt, GVData data) {
         Font2D font = key.font();
         FontStrike strike = font.getStrike(desc);
-        long layoutTables = 0;
-        if (font instanceof TrueTypeFont) {
-            layoutTables = ((TrueTypeFont) font).getLayoutTableCache();
-        }
+        long layoutTables = font.getLayoutTableCache();
         nativeLayout(font, strike, mat, gmask, baseIndex,
              tr.text, tr.start, tr.limit, tr.min, tr.max,
              key.script(), key.lang(), typo_flags, pt, data,
--- a/src/java.desktop/share/classes/sun/font/TrueTypeFont.java	Mon Nov 16 15:03:17 2015 +0400
+++ b/src/java.desktop/share/classes/sun/font/TrueTypeFont.java	Mon Nov 16 16:07:46 2015 -0800
@@ -874,8 +874,8 @@
         }
     }
 
-    /* NB: is it better to move declaration to Font2D? */
-    long getLayoutTableCache() {
+    @Override
+    protected long getLayoutTableCache() {
         try {
           return getScaler().getLayoutTableCache();
         } catch(FontScalerException fe) {
@@ -884,7 +884,7 @@
     }
 
     @Override
-    byte[] getTableBytes(int tag) {
+    protected byte[] getTableBytes(int tag) {
         ByteBuffer buffer = getTableBuffer(tag);
         if (buffer == null) {
             return null;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/awt/font/TextLayout/OSXLigatureTest.java	Mon Nov 16 16:07:46 2015 -0800
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 7162125
+ * @summary Test ligatures form on OS X.
+ */
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.awt.font.TextAttribute;
+import java.util.HashMap;
+import java.util.Map;
+
+public class OSXLigatureTest {
+
+    public static void main(String[] args) {
+        if (!System.getProperty("os.name").startsWith("Mac")) {
+            return;
+        }
+        String ligStr = "ffi";
+        int w = 50, h = 50;
+
+        BufferedImage bi1 = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
+        Graphics2D bi1Graphics = bi1.createGraphics();
+        bi1Graphics.setColor(Color.white);
+        bi1Graphics.fillRect(0, 0, w, h);
+        bi1Graphics.setColor(Color.black);
+        Font noLigFont = new Font("Gill Sans", Font.PLAIN, 30);
+        bi1Graphics.setFont(noLigFont);
+        bi1Graphics.drawString(ligStr, 10, 40);
+
+        BufferedImage bi2 = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
+        Graphics2D bi2Graphics = bi2.createGraphics();
+        bi2Graphics.setColor(Color.white);
+        bi2Graphics.fillRect(0, 0, w, h);
+        bi2Graphics.setColor(Color.black);
+        Map<TextAttribute, Object> attributes = new HashMap<>();
+        attributes.put(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON);
+        Font ligFont = noLigFont.deriveFont(attributes);
+        bi2Graphics.setFont(ligFont);
+        bi2Graphics.drawString(ligStr, 10, 40);
+
+        boolean same = true;
+        for (int x = 0; x < w; x++) {
+            for (int y = 0; y < h; y++) {
+                int c1 = bi1.getRGB(x, y);
+                int c2 = bi2.getRGB(x, y);
+                same &= (c1 == c2);
+            }
+            if (!same) {
+               break;
+            }
+        }
+        if (same) {
+            throw new RuntimeException("Images do not differ - no ligature");
+        }
+    }
+}