7162125: [macosx] A font has different behaviour for ligatures depending on its creation mod

Reviewed-by: srl, jgodinez
This commit is contained in:
Phil Race 2015-11-16 16:07:46 -08:00
parent 7d7c956397
commit 8b00726f06
14 changed files with 540 additions and 13 deletions

View File

@ -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]);
}
}
}

View File

@ -31,12 +31,13 @@ import java.awt.geom.AffineTransform;
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 @@ public final class CFont extends PhysicalFont {
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 @@ public final class CFont extends PhysicalFont {
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);

View File

@ -31,7 +31,7 @@ import java.util.*;
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,

View File

@ -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

View File

@ -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];
}
@ -343,6 +374,95 @@ JNF_COCOA_EXIT(env);
#pragma mark --- sun.font.CFont JNI ---
/*
* 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
@ -460,3 +580,42 @@ Java_sun_awt_FontDescriptor_initIDs
{
}
#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);
}
}

View File

@ -149,6 +149,25 @@ public final class CompositeFont extends Font2D {
}
}
/*
* 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

View File

@ -42,7 +42,7 @@ package sun.font;
* 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;

View File

@ -461,10 +461,17 @@ public abstract class Font2D {
* 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;

View File

@ -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();
}

View File

@ -408,6 +408,9 @@ public final class GlyphLayout {
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;

View File

@ -1124,6 +1124,9 @@ public class StandardGlyphVector extends GlyphVector {
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 @@ public class StandardGlyphVector extends GlyphVector {
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);
}

View File

@ -155,10 +155,7 @@ public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory
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,

View File

@ -874,8 +874,8 @@ public class TrueTypeFont extends FileFont {
}
}
/* 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 @@ public class TrueTypeFont extends FileFont {
}
@Override
byte[] getTableBytes(int tag) {
protected byte[] getTableBytes(int tag) {
ByteBuffer buffer = getTableBuffer(tag);
if (buffer == null) {
return null;

View File

@ -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");
}
}
}