8318364: Add an FFM-based implementation of harfbuzz OpenType layout

Reviewed-by: jdv, psadhukhan
This commit is contained in:
Phil Race 2023-11-21 17:46:29 +00:00
parent 1c0bd81a10
commit f69e6653f8
7 changed files with 1376 additions and 3 deletions
src
java.base/share/classes
java.desktop/share
test/jdk/java/awt/font/GlyphVector

@ -148,6 +148,7 @@ module java.base {
// module declaration be annotated with jdk.internal.javac.ParticipatesInPreview
exports jdk.internal.javac to
java.compiler,
java.desktop, // for ScopedValue
jdk.compiler,
jdk.incubator.vector,
jdk.jshell;

@ -0,0 +1,659 @@
/*
* Copyright (c) 2023, 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;
import java.awt.geom.Point2D;
import sun.font.GlyphLayout.GVData;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import static java.lang.foreign.MemorySegment.NULL;
import java.lang.foreign.SequenceLayout;
import java.lang.foreign.StructLayout;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.UnionLayout;
import static java.lang.foreign.ValueLayout.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.util.Optional;
import java.util.WeakHashMap;
public class HBShaper {
/*
* union _hb_var_int_t {
* uint32_t u32;
* int32_t i32;
* uint16_t u16[2];
* int16_t i16[2];
* uint8_t u8[4];
* int8_t i8[4];
* };
*/
private static final UnionLayout VarIntLayout = MemoryLayout.unionLayout(
JAVA_INT.withName("u32"),
JAVA_INT.withName("i32"),
MemoryLayout.sequenceLayout(2, JAVA_SHORT).withName("u16"),
MemoryLayout.sequenceLayout(2, JAVA_SHORT).withName("i16"),
MemoryLayout.sequenceLayout(4, JAVA_BYTE).withName("u8"),
MemoryLayout.sequenceLayout(4, JAVA_BYTE).withName("i8")
).withName("_hb_var_int_t");
/*
* struct hb_glyph_position_t {
* hb_position_t x_advance;
* hb_position_t y_advance;
* hb_position_t x_offset;
* hb_position_t y_offset;
* hb_var_int_t var;
* };
*/
private static final StructLayout PositionLayout = MemoryLayout.structLayout(
JAVA_INT.withName("x_advance"),
JAVA_INT.withName("y_advance"),
JAVA_INT.withName("x_offset"),
JAVA_INT.withName("y_offset"),
VarIntLayout.withName("var")
).withName("hb_glyph_position_t");
/**
* struct hb_glyph_info_t {
* hb_codepoint_t codepoint;
* hb_mask_t mask;
* uint32_t cluster;
* hb_var_int_t var1;
* hb_var_int_t var2;
* };
*/
private static final StructLayout GlyphInfoLayout = MemoryLayout.structLayout(
JAVA_INT.withName("codepoint"),
JAVA_INT.withName("mask"),
JAVA_INT.withName("cluster"),
VarIntLayout.withName("var1"),
VarIntLayout.withName("var2")
).withName("hb_glyph_info_t");
private static VarHandle getVarHandle(StructLayout struct, String name) {
VarHandle h = struct.arrayElementVarHandle(PathElement.groupElement(name));
/* insert 0 offset so don't need to pass arg every time */
return MethodHandles.insertCoordinates(h, 1, 0L).withInvokeExactBehavior();
}
private static final VarHandle x_offsetHandle;
private static final VarHandle y_offsetHandle;
private static final VarHandle x_advanceHandle;
private static final VarHandle y_advanceHandle;
private static final VarHandle codePointHandle;
private static final VarHandle clusterHandle;
private static final MethodHandles.Lookup MH_LOOKUP;
private static final Linker LINKER;
private static final SymbolLookup SYM_LOOKUP;
private static final MethodHandle malloc_handle;
private static final MethodHandle create_face_handle;
private static final MethodHandle dispose_face_handle;
private static final MethodHandle jdk_hb_shape_handle;
/* hb_jdk_font_funcs_struct is a pointer to a harfbuzz font_funcs
* object which references the 5 following upcall stubs.
* The singleton shared font_funcs ptr is passed down in each
* call to shape() and installed on the hb_font.
*/
private static final MemorySegment hb_jdk_font_funcs_struct;
private static final MemorySegment get_var_glyph_stub;
private static final MemorySegment get_nominal_glyph_stub;
private static final MemorySegment get_h_advance_stub;
private static final MemorySegment get_v_advance_stub;
private static final MemorySegment get_contour_pt_stub;
private static final MemorySegment store_layout_results_stub;
private static FunctionDescriptor
getFunctionDescriptor(MemoryLayout retType,
MemoryLayout... argTypes) {
return (retType == null) ?
FunctionDescriptor.ofVoid(argTypes) :
FunctionDescriptor.of(retType, argTypes);
}
private static MethodHandle getMethodHandle
(String mName,
FunctionDescriptor fd) {
try {
MethodType mType = fd.toMethodType();
return MH_LOOKUP.findStatic(HBShaper.class, mName, mType);
} catch (IllegalAccessException | NoSuchMethodException e) {
return null;
}
}
static {
MH_LOOKUP = MethodHandles.lookup();
LINKER = Linker.nativeLinker();
SYM_LOOKUP = SymbolLookup.loaderLookup().or(LINKER.defaultLookup());
FunctionDescriptor mallocDescriptor =
FunctionDescriptor.of(ADDRESS, JAVA_LONG);
Optional<MemorySegment> malloc_symbol = SYM_LOOKUP.find("malloc");
@SuppressWarnings("restricted")
MethodHandle tmp1 = LINKER.downcallHandle(malloc_symbol.get(), mallocDescriptor);
malloc_handle = tmp1;
FunctionDescriptor createFaceDescriptor =
FunctionDescriptor.of(ADDRESS, ADDRESS);
Optional<MemorySegment> create_face_symbol = SYM_LOOKUP.find("HBCreateFace");
@SuppressWarnings("restricted")
MethodHandle tmp2 = LINKER.downcallHandle(create_face_symbol.get(), createFaceDescriptor);
create_face_handle = tmp2;
FunctionDescriptor disposeFaceDescriptor = FunctionDescriptor.ofVoid(ADDRESS);
Optional<MemorySegment> dispose_face_symbol = SYM_LOOKUP.find("HBDisposeFace");
@SuppressWarnings("restricted")
MethodHandle tmp3 = LINKER.downcallHandle(dispose_face_symbol.get(), disposeFaceDescriptor);
dispose_face_handle = tmp3;
FunctionDescriptor shapeDesc = FunctionDescriptor.ofVoid(
//JAVA_INT, // return type
JAVA_FLOAT, // ptSize
ADDRESS, // matrix
ADDRESS, // face
ADDRESS, // chars
JAVA_INT, // len
JAVA_INT, // script
JAVA_INT, // offset
JAVA_INT, // limit
JAVA_INT, // baseIndex
JAVA_FLOAT, // startX
JAVA_FLOAT, // startY
JAVA_INT, // flags,
JAVA_INT, // slot,
ADDRESS, // ptr to harfbuzz font_funcs object.
ADDRESS); // store_results_fn
Optional<MemorySegment> shape_sym = SYM_LOOKUP.find("jdk_hb_shape");
@SuppressWarnings("restricted")
MethodHandle tmp4 = LINKER.downcallHandle(shape_sym.get(), shapeDesc);
jdk_hb_shape_handle = tmp4;
Arena garena = Arena.global(); // creating stubs that exist until VM exit.
FunctionDescriptor get_var_glyph_fd = getFunctionDescriptor(JAVA_INT, // return type
ADDRESS, ADDRESS, JAVA_INT, JAVA_INT, ADDRESS, ADDRESS); // arg types
MethodHandle get_var_glyph_mh =
getMethodHandle("get_variation_glyph", get_var_glyph_fd);
@SuppressWarnings("restricted")
MemorySegment tmp5 = LINKER.upcallStub(get_var_glyph_mh, get_var_glyph_fd, garena);
get_var_glyph_stub = tmp5;
FunctionDescriptor get_nominal_glyph_fd = getFunctionDescriptor(JAVA_INT, // return type
ADDRESS, ADDRESS, JAVA_INT, ADDRESS, ADDRESS); // arg types
MethodHandle get_nominal_glyph_mh =
getMethodHandle("get_nominal_glyph", get_nominal_glyph_fd);
@SuppressWarnings("restricted")
MemorySegment tmp6 = LINKER.upcallStub(get_nominal_glyph_mh, get_nominal_glyph_fd, garena);
get_nominal_glyph_stub = tmp6;
FunctionDescriptor get_h_adv_fd = getFunctionDescriptor(JAVA_INT, // return type
ADDRESS, ADDRESS, JAVA_INT, ADDRESS); // arg types
MethodHandle get_h_adv_mh =
getMethodHandle("get_glyph_h_advance", get_h_adv_fd);
@SuppressWarnings("restricted")
MemorySegment tmp7 = LINKER.upcallStub(get_h_adv_mh, get_h_adv_fd, garena);
get_h_advance_stub = tmp7;
FunctionDescriptor get_v_adv_fd = getFunctionDescriptor(JAVA_INT, // return type
ADDRESS, ADDRESS, JAVA_INT, ADDRESS); // arg types
MethodHandle get_v_adv_mh =
getMethodHandle("get_glyph_v_advance", get_v_adv_fd);
@SuppressWarnings("restricted")
MemorySegment tmp8 = LINKER.upcallStub(get_v_adv_mh, get_v_adv_fd, garena);
get_v_advance_stub = tmp8;
FunctionDescriptor get_contour_pt_fd = getFunctionDescriptor(JAVA_INT, // return type
ADDRESS, ADDRESS, JAVA_INT, JAVA_INT, ADDRESS, ADDRESS, ADDRESS); // arg types
MethodHandle get_contour_pt_mh =
getMethodHandle("get_glyph_contour_point", get_contour_pt_fd);
@SuppressWarnings("restricted")
MemorySegment tmp9 = LINKER.upcallStub(get_contour_pt_mh, get_contour_pt_fd, garena);
get_contour_pt_stub = tmp9;
/* Having now created the font upcall stubs, we can call down to create
* the native harfbuzz object holding these.
*/
FunctionDescriptor createFontFuncsDescriptor = FunctionDescriptor.of(
ADDRESS, // hb_font_funcs* return type
ADDRESS, // glyph_fn upcall stub
ADDRESS, // variation_fn upcall stub
ADDRESS, // h_advance_fn upcall stub
ADDRESS, // v_advance_fn upcall stub
ADDRESS); // contour_pt_fn upcall stub
Optional<MemorySegment> create_font_funcs_symbol = SYM_LOOKUP.find("HBCreateFontFuncs");
@SuppressWarnings("restricted")
MethodHandle create_font_funcs_handle =
LINKER.downcallHandle(create_font_funcs_symbol.get(), createFontFuncsDescriptor);
MemorySegment s = null;
try {
s = (MemorySegment)create_font_funcs_handle.invokeExact(
get_nominal_glyph_stub,
get_var_glyph_stub,
get_h_advance_stub,
get_v_advance_stub,
get_contour_pt_stub);
} catch (Throwable t) {
t.printStackTrace();
}
hb_jdk_font_funcs_struct = s;
FunctionDescriptor store_layout_fd =
FunctionDescriptor.ofVoid(
JAVA_INT, // slot
JAVA_INT, // baseIndex
JAVA_INT, // offset
JAVA_FLOAT, // startX
JAVA_FLOAT, // startX
JAVA_FLOAT, // devScale
JAVA_INT, // charCount
JAVA_INT, // glyphCount
ADDRESS, // glyphInfo
ADDRESS); // glyphPos
MethodHandle store_layout_mh =
getMethodHandle("store_layout_results", store_layout_fd);
@SuppressWarnings("restricted")
MemorySegment tmp10 = LINKER.upcallStub(store_layout_mh, store_layout_fd, garena);
store_layout_results_stub = tmp10;
x_offsetHandle = getVarHandle(PositionLayout, "x_offset");
y_offsetHandle = getVarHandle(PositionLayout, "y_offset");
x_advanceHandle = getVarHandle(PositionLayout, "x_advance");
y_advanceHandle = getVarHandle(PositionLayout, "y_advance");
codePointHandle = getVarHandle(GlyphInfoLayout, "codepoint");
clusterHandle = getVarHandle(GlyphInfoLayout, "cluster");
}
/*
* This is expensive but it is done just once per font.
* The unbound stub could be cached but the savings would
* be very low in the only case it is used.
*/
@SuppressWarnings("restricted")
private static MemorySegment getBoundUpcallStub
(Arena arena, Class<?> clazz, Object bindArg, String mName,
MemoryLayout retType, MemoryLayout... argTypes) {
try {
FunctionDescriptor nativeDescriptor =
(retType == null) ?
FunctionDescriptor.ofVoid(argTypes) :
FunctionDescriptor.of(retType, argTypes);
MethodType mType = nativeDescriptor.toMethodType();
mType = mType.insertParameterTypes(0, clazz);
MethodHandle mh = MH_LOOKUP.findStatic(HBShaper.class, mName, mType);
MethodHandle bound_handle = mh.bindTo(bindArg);
return LINKER.upcallStub(bound_handle, nativeDescriptor, arena);
} catch (IllegalAccessException | NoSuchMethodException e) {
return null;
}
}
private static int get_nominal_glyph(
MemorySegment font_ptr, /* Not used */
MemorySegment font_data, /* Not used */
int unicode,
MemorySegment glyph, /* pointer to location to store glyphID */
MemorySegment user_data /* Not used */
) {
Font2D font2D = scopedVars.get().font();
int glyphID = font2D.charToGlyph(unicode);
@SuppressWarnings("restricted")
MemorySegment glyphIDPtr = glyph.reinterpret(4);
glyphIDPtr.setAtIndex(JAVA_INT, 0, glyphID);
return (glyphID != 0) ? 1 : 0;
}
private static int get_variation_glyph(
MemorySegment font_ptr, /* Not used */
MemorySegment font_data, /* Not used */
int unicode,
int variation_selector,
MemorySegment glyph, /* pointer to location to store glyphID */
MemorySegment user_data /* Not used */
) {
Font2D font2D = scopedVars.get().font();
int glyphID = font2D.charToVariationGlyph(unicode, variation_selector);
@SuppressWarnings("restricted")
MemorySegment glyphIDPtr = glyph.reinterpret(4);
glyphIDPtr.setAtIndex(JAVA_INT, 0, glyphID);
return (glyphID != 0) ? 1 : 0;
}
private static final float HBFloatToFixedScale = ((float)(1 << 16));
private static final int HBFloatToFixed(float f) {
return ((int)((f) * HBFloatToFixedScale));
}
private static int get_glyph_h_advance(
MemorySegment font_ptr, /* Not used */
MemorySegment font_data, /* Not used */
int glyph,
MemorySegment user_data /* Not used */
) {
FontStrike strike = scopedVars.get().fontStrike();
Point2D.Float pt = strike.getGlyphMetrics(glyph);
return (pt != null) ? HBFloatToFixed(pt.x) : 0;
}
private static int get_glyph_v_advance(
MemorySegment font_ptr, /* Not used */
MemorySegment font_data, /* Not used */
int glyph,
MemorySegment user_data /* Not used */
) {
FontStrike strike = scopedVars.get().fontStrike();
Point2D.Float pt = strike.getGlyphMetrics(glyph);
return (pt != null) ? HBFloatToFixed(pt.y) : 0;
}
/*
* This class exists to make the code that uses it less verbose
*/
private static class IntPtr {
MemorySegment seg;
IntPtr(MemorySegment seg) {
}
void set(int i) {
seg.setAtIndex(JAVA_INT, 0, i);
}
}
private static int get_glyph_contour_point(
MemorySegment font_ptr, /* Not used */
MemorySegment font_data, /* Not used */
int glyph,
int point_index,
MemorySegment x_ptr, /* ptr to return x */
MemorySegment y_ptr, /* ptr to return y */
MemorySegment user_data /* Not used */
) {
IntPtr x = new IntPtr(x_ptr);
IntPtr y = new IntPtr(y_ptr);
if ((glyph & 0xfffe) == 0xfffe) {
x.set(0);
y.set(0);
return 1;
}
FontStrike strike = scopedVars.get().fontStrike();
Point2D.Float pt = ((PhysicalStrike)strike).getGlyphPoint(glyph, point_index);
x.set(HBFloatToFixed(pt.x));
y.set(HBFloatToFixed(pt.y));
return 1;
}
record ScopedVars (
Font2D font,
FontStrike fontStrike,
GVData gvData,
Point2D.Float point) {}
static final ScopedValue<ScopedVars> scopedVars = ScopedValue.newInstance();
static void shape(
Font2D font2D,
FontStrike fontStrike,
float ptSize,
float[] mat,
MemorySegment hbface,
char[] text,
GVData gvData,
int script,
int offset,
int limit,
int baseIndex,
Point2D.Float startPt,
int flags,
int slot) {
/*
* ScopedValue is needed so that call backs into Java during
* shaping can locate the correct instances of these to query or update.
* The alternative of creating bound method handles is far too slow.
*/
ScopedVars vars = new ScopedVars(font2D, fontStrike, gvData, startPt);
ScopedValue.where(scopedVars, vars)
.run(() -> {
try (Arena arena = Arena.ofConfined()) {
float startX = (float)startPt.getX();
float startY = (float)startPt.getY();
MemorySegment matrix = arena.allocateFrom(JAVA_FLOAT, mat);
MemorySegment chars = arena.allocateFrom(JAVA_CHAR, text);
/*int ret =*/ jdk_hb_shape_handle.invokeExact(
ptSize, matrix, hbface, chars, text.length,
script, offset, limit,
baseIndex, startX, startY, flags, slot,
hb_jdk_font_funcs_struct,
store_layout_results_stub);
} catch (Throwable t) {
}
});
}
private static int getFontTableData(Font2D font2D,
int tag,
MemorySegment data_ptr_out) {
/*
* On return, the data_out_ptr will point to memory allocated by native malloc,
* so it will be freed by the caller using native free - when it is
* done with it.
*/
@SuppressWarnings("restricted")
MemorySegment data_ptr = data_ptr_out.reinterpret(ADDRESS.byteSize());
if (tag == 0) {
data_ptr.setAtIndex(ADDRESS, 0, NULL);
return 0;
}
byte[] data = font2D.getTableBytes(tag);
if (data == null) {
data_ptr.setAtIndex(ADDRESS, 0, NULL);
return 0;
}
int len = data.length;
MemorySegment zero_len = NULL;
try {
zero_len = (MemorySegment)malloc_handle.invokeExact((long)len);
} catch (Throwable t) {
}
if (zero_len.equals(NULL)) {
data_ptr.setAtIndex(ADDRESS, 0, NULL);
return 0;
}
@SuppressWarnings("restricted")
MemorySegment mem = zero_len.reinterpret(len);
MemorySegment.copy(data, 0, mem, JAVA_BYTE, 0, len);
data_ptr.setAtIndex(ADDRESS, 0, mem);
return len;
}
/* WeakHashMap is used so that we do not retain temporary fonts
*
* The value is a class that implements the 2D Disposer, so
* that the native resources for temp. fonts can be freed.
*
* Installed fonts should never be cleared from the map as
* they are permanently referenced.
*/
private static final WeakHashMap<Font2D, FaceRef>
faceMap = new WeakHashMap<>();
static MemorySegment getFace(Font2D font2D) {
FaceRef ref;
synchronized (faceMap) {
ref = faceMap.computeIfAbsent(font2D, FaceRef::new);
}
return ref.getFace();
}
private static class FaceRef implements DisposerRecord {
private Font2D font2D;
private MemorySegment face;
// get_table_data_fn uses an Arena managed by GC,
// so we need to keep a reference to it here until
// this FaceRef is collected.
private MemorySegment get_table_data_fn;
private FaceRef(Font2D font) {
this.font2D = font;
}
private synchronized MemorySegment getFace() {
if (face == null) {
createFace();
if (face != null) {
Disposer.addObjectRecord(font2D, this);
}
font2D = null;
}
return face;
}
private void createFace() {
try {
get_table_data_fn = getBoundUpcallStub(Arena.ofAuto(),
Font2D.class,
font2D, // bind arg
"getFontTableData", // method name
JAVA_INT, // return type
JAVA_INT, ADDRESS); // arg types
if (get_table_data_fn == null) {
return;
}
face = (MemorySegment)create_face_handle.invokeExact(get_table_data_fn);
} catch (Throwable t) {
}
}
@Override
public void dispose() {
try {
dispose_face_handle.invokeExact(face);
} catch (Throwable t) {
}
}
}
/* Upcall to receive results of layout */
private static void store_layout_results(
int slot,
int baseIndex,
int offset,
float startX,
float startY,
float devScale,
int charCount,
int glyphCount,
MemorySegment /* hb_glyph_info_t* */ glyphInfo,
MemorySegment /* hb_glyph_position_t* */ glyphPos
) {
GVData gvdata = scopedVars.get().gvData();
Point2D.Float startPt = scopedVars.get().point();
float x=0, y=0;
float advX, advY;
float scale = 1.0f / HBFloatToFixedScale / devScale;
int initialCount = gvdata._count;
int maxGlyphs = (charCount > glyphCount) ? charCount : glyphCount;
int maxStore = maxGlyphs + initialCount;
boolean needToGrow = (maxStore > gvdata._glyphs.length) ||
((maxStore * 2 + 2) > gvdata._positions.length);
if (needToGrow) {
gvdata.grow(maxStore-initialCount);
}
int glyphPosLen = glyphCount * 2 + 2;
long posSize = glyphPosLen * PositionLayout.byteSize();
@SuppressWarnings("restricted")
MemorySegment glyphPosArr = glyphPos.reinterpret(posSize);
long glyphInfoSize = glyphCount * GlyphInfoLayout.byteSize();
@SuppressWarnings("restricted")
MemorySegment glyphInfoArr = glyphInfo.reinterpret(glyphInfoSize);
for (int i = 0; i < glyphCount; i++) {
int storei = i + initialCount;
int cluster = (int)clusterHandle.get(glyphInfoArr, (long)i) - offset;
gvdata._indices[storei] = baseIndex + cluster;
int codePoint = (int)codePointHandle.get(glyphInfoArr, (long)i);
gvdata._glyphs[storei] = (slot | codePoint);
int x_offset = (int)x_offsetHandle.get(glyphPosArr, (long)i);
int y_offset = (int)y_offsetHandle.get(glyphPosArr, (long)i);
gvdata._positions[(storei*2)] = startX + x + (x_offset * scale);
gvdata._positions[(storei*2)+1] = startY + y - (y_offset * scale);
int x_advance = (int)x_advanceHandle.get(glyphPosArr, (long)i);
int y_advance = (int)y_advanceHandle.get(glyphPosArr, (long)i);
x += x_advance * scale;
y += y_advance * scale;
}
int storeadv = initialCount + glyphCount;
gvdata._count = storeadv;
// The final slot in the positions array is important
// because when the GlyphVector is created from this
// data it determines the overall advance of the glyphvector
// and this is used in positioning the next glyphvector
// during rendering where text is broken into runs.
// We also need to report it back into "pt", so layout can
// pass it back down for any next run.
advX = startX + x;
advY = startY + y;
gvdata._positions[(storeadv*2)] = advX;
gvdata._positions[(storeadv*2)+1] = advY;
startPt.x = advX;
startPt.y = advY;
}
}

@ -35,7 +35,10 @@ import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
import java.awt.geom.Point2D;
import java.lang.foreign.MemorySegment;
import java.lang.ref.SoftReference;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.ConcurrentHashMap;
import java.util.WeakHashMap;
@ -162,17 +165,38 @@ public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory
return ref.getNativePtr();
}
static boolean useFFM = true;
static {
@SuppressWarnings("removal")
String prop = AccessController.doPrivileged(
(PrivilegedAction<String>) () ->
System.getProperty("sun.font.layout.ffm", "true"));
useFFM = "true".equals(prop);
}
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 pFace = getFacePtr(font);
if (pFace != 0) {
shape(font, strike, ptSize, mat, pFace,
if (useFFM) {
MemorySegment face = HBShaper.getFace(font);
if (face != null) {
HBShaper.shape(font, strike, ptSize, mat, face,
tr.text, data, key.script(),
tr.start, tr.limit, baseIndex, pt,
typo_flags, gmask);
}
} else {
long pFace = getFacePtr(font);
if (pFace != 0) {
shape(font, strike, ptSize, mat, pFace,
tr.text, data, key.script(),
tr.start, tr.limit, baseIndex, pt,
typo_flags, gmask);
}
}
}

@ -0,0 +1,145 @@
/*
* Copyright (c) 2023, 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.
*/
#include <stdlib.h>
#include "hb.h"
#include "hb-jdk-p.h"
#include "hb-ot.h"
#include "scriptMapping.h"
static float euclidianDistance(float a, float b)
{
float root;
if (a < 0) {
a = -a;
}
if (b < 0) {
b = -b;
}
if (a == 0) {
return b;
}
if (b == 0) {
return a;
}
/* Do an initial approximation, in root */
root = a > b ? a + (b / 2) : b + (a / 2);
/* An unrolled Newton-Raphson iteration sequence */
root = (root + (a * (a / root)) + (b * (b / root)) + 1) / 2;
root = (root + (a * (a / root)) + (b * (b / root)) + 1) / 2;
root = (root + (a * (a / root)) + (b * (b / root)) + 1) / 2;
return root;
}
#define TYPO_KERN 0x00000001
#define TYPO_LIGA 0x00000002
#define TYPO_RTL 0x80000000
JDKEXPORT int jdk_hb_shape(
float ptSize,
float *matrix,
void* pFace,
unsigned short *chars,
int len,
int script,
int offset,
int limit,
int baseIndex,
float startX,
float startY,
int flags,
int slot,
hb_font_funcs_t* font_funcs,
store_layoutdata_func_t store_layout_results_fn
) {
hb_buffer_t *buffer;
hb_face_t* hbface;
hb_font_t* hbfont;
int glyphCount;
hb_glyph_info_t *glyphInfo;
hb_glyph_position_t *glyphPos;
hb_direction_t direction = HB_DIRECTION_LTR;
hb_feature_t *features = NULL;
int featureCount = 0;
char* kern = (flags & TYPO_KERN) ? "kern" : "-kern";
char* liga = (flags & TYPO_LIGA) ? "liga" : "-liga";
int ret;
unsigned int buflen;
float devScale = 1.0f;
if (getenv("HB_NODEVTX") != NULL) {
float xPtSize = euclidianDistance(matrix[0], matrix[1]);
float yPtSize = euclidianDistance(matrix[2], matrix[3]);
devScale = xPtSize / ptSize;
}
hbface = (hb_face_t*)pFace;
hbfont = jdk_font_create_hbp(hbface,
ptSize, devScale, NULL,
font_funcs);
buffer = hb_buffer_create();
hb_buffer_set_script(buffer, getHBScriptCode(script));
hb_buffer_set_language(buffer,
hb_ot_tag_to_language(HB_OT_TAG_DEFAULT_LANGUAGE));
if ((flags & TYPO_RTL) != 0) {
direction = HB_DIRECTION_RTL;
}
hb_buffer_set_direction(buffer, direction);
hb_buffer_set_cluster_level(buffer,
HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
int charCount = limit - offset;
hb_buffer_add_utf16(buffer, chars, len, offset, charCount);
features = calloc(2, sizeof(hb_feature_t));
if (features) {
hb_feature_from_string(kern, -1, &features[featureCount++]);
hb_feature_from_string(liga, -1, &features[featureCount++]);
}
hb_shape_full(hbfont, buffer, features, featureCount, 0);
glyphCount = hb_buffer_get_length(buffer);
glyphInfo = hb_buffer_get_glyph_infos(buffer, 0);
glyphPos = hb_buffer_get_glyph_positions(buffer, &buflen);
ret = (*store_layout_results_fn)
(slot, baseIndex, offset, startX, startY, devScale,
charCount, glyphCount, glyphInfo, glyphPos);
hb_buffer_destroy (buffer);
hb_font_destroy(hbfont);
if (features != NULL) {
free(features);
}
return ret;
}

@ -0,0 +1,241 @@
/*
* Copyright (c) 2023, 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.
*/
#include "hb.h"
#include "hb-jdk-p.h"
#include <stdlib.h>
#if defined(__GNUC__) && __GNUC__ >= 4
#define HB_UNUSED __attribute__((unused))
#else
#define HB_UNUSED
#endif
static hb_bool_t
hb_jdk_get_glyph_h_origin (hb_font_t *font HB_UNUSED,
void *font_data HB_UNUSED,
hb_codepoint_t glyph HB_UNUSED,
hb_position_t *x HB_UNUSED,
hb_position_t *y HB_UNUSED,
void *user_data HB_UNUSED)
{
/* We always work in the horizontal coordinates. */
return true;
}
static hb_bool_t
hb_jdk_get_glyph_v_origin (hb_font_t *font HB_UNUSED,
void *font_data,
hb_codepoint_t glyph,
hb_position_t *x,
hb_position_t *y,
void *user_data HB_UNUSED)
{
return false;
}
static hb_position_t
hb_jdk_get_glyph_h_kerning (hb_font_t *font,
void *font_data,
hb_codepoint_t lejdk_glyph,
hb_codepoint_t right_glyph,
void *user_data HB_UNUSED)
{
/* Not implemented. This seems to be in the HB API
* as a way to fall back to Freetype's kerning support
* which could be based on some on-the fly glyph analysis.
* But more likely it reads the kern table. That is easy
* enough code to add if we find a need to fall back
* to that instead of using gpos. It seems like if
* there is a gpos table at all, the practice is to
* use that and ignore kern, no matter that gpos does
* not implement the kern feature.
*/
return 0;
}
static hb_position_t
hb_jdk_get_glyph_v_kerning (hb_font_t *font HB_UNUSED,
void *font_data HB_UNUSED,
hb_codepoint_t top_glyph HB_UNUSED,
hb_codepoint_t bottom_glyph HB_UNUSED,
void *user_data HB_UNUSED)
{
/* OpenType doesn't have vertical-kerning other than GPOS. */
return 0;
}
static hb_bool_t
hb_jdk_get_glyph_extents (hb_font_t *font HB_UNUSED,
void *font_data,
hb_codepoint_t glyph,
hb_glyph_extents_t *extents,
void *user_data HB_UNUSED)
{
/* TODO */
return false;
}
static hb_bool_t
hb_jdk_get_glyph_name (hb_font_t *font HB_UNUSED,
void *font_data,
hb_codepoint_t glyph,
char *name, unsigned int size,
void *user_data HB_UNUSED)
{
return false;
}
static hb_bool_t
hb_jdk_get_glyph_from_name (hb_font_t *font HB_UNUSED,
void *font_data,
const char *name, int len,
hb_codepoint_t *glyph,
void *user_data HB_UNUSED)
{
return false;
}
extern "C" {
/*
* This is called exactly once, from Java code, and the result is
* used by all downcalls to do shaping(), installing the functions
* on the hb_font.
* The parameters are all FFM upcall stubs.
* I was surprised we can cache these native pointers to upcall
* stubs on the native side, but it seems to be fine using the global Arena.
* These stubs don't need to be bound to a particular font or strike
* since they use Scoped Locals to access the data they need to operate on.
* This is how we can cache them.
* Also caching the hb_font_funcs_t on the Java side means we can
* marshall fewer args to the calls to shape().
*/
JDKEXPORT hb_font_funcs_t *
HBCreateFontFuncs(hb_font_get_nominal_glyph_func_t nominal_fn,
hb_font_get_variation_glyph_func_t variation_fn,
hb_font_get_glyph_h_advance_func_t h_advance_fn,
hb_font_get_glyph_v_advance_func_t v_advance_fn,
hb_font_get_glyph_contour_point_func_t contour_pt_fn)
{
hb_font_funcs_t *ff = hb_font_funcs_create();
hb_font_funcs_set_nominal_glyph_func(ff, nominal_fn, NULL, NULL);
hb_font_funcs_set_variation_glyph_func(ff, variation_fn, NULL, NULL);
hb_font_funcs_set_glyph_h_advance_func(ff, h_advance_fn, NULL, NULL);
hb_font_funcs_set_glyph_v_advance_func(ff, v_advance_fn, NULL, NULL);
hb_font_funcs_set_glyph_contour_point_func(ff, contour_pt_fn, NULL, NULL);
/* These are all simple default implementations */
hb_font_funcs_set_glyph_h_origin_func(ff,
hb_jdk_get_glyph_h_origin, NULL, NULL);
hb_font_funcs_set_glyph_v_origin_func(ff,
hb_jdk_get_glyph_v_origin, NULL, NULL);
hb_font_funcs_set_glyph_h_kerning_func(ff,
hb_jdk_get_glyph_h_kerning, NULL, NULL);
hb_font_funcs_set_glyph_v_kerning_func(ff,
hb_jdk_get_glyph_v_kerning, NULL, NULL);
hb_font_funcs_set_glyph_extents_func(ff,
hb_jdk_get_glyph_extents, NULL, NULL);
hb_font_funcs_set_glyph_name_func(ff,
hb_jdk_get_glyph_name, NULL, NULL);
hb_font_funcs_set_glyph_from_name_func(ff,
hb_jdk_get_glyph_from_name, NULL, NULL);
hb_font_funcs_make_immutable(ff); // done setting functions.
return ff;
}
} /* extern "C" */
static void _do_nothing(void) {
}
typedef int (*GetTableDataFn) (int tag, char **dataPtr);
static hb_blob_t *
reference_table(hb_face_t *face HB_UNUSED, hb_tag_t tag, void *user_data) {
// HB_TAG_NONE is 0 and is used to get the whole font file.
// It is not expected to be needed for JDK.
if (tag == 0) {
return NULL;
}
// This has to be a method handle bound to the right Font2D
GetTableDataFn getDataFn = (GetTableDataFn)user_data;
char *tableData = NULL;
int length = (*getDataFn)(tag, &tableData);
if ((length == 0) || (tableData == NULL)) {
return NULL;
}
/* Can't call this non-exported hb fn from Java so can't have
* a Java version of the reference_table fn, which is why it
* has as a parameter the upcall stub that will be used.
* And the memory is freed by 'free' so the upcall needs to
* call back down to malloc to allocate it.
*/
return hb_blob_create((const char *)tableData, length,
HB_MEMORY_MODE_WRITABLE,
tableData, free);
}
extern "C" {
JDKEXPORT hb_face_t* HBCreateFace(GetTableDataFn *get_data_upcall_fn) {
hb_face_t *face = hb_face_create_for_tables(reference_table, get_data_upcall_fn, NULL);
return face;
}
JDKEXPORT void HBDisposeFace(hb_face_t* face) {
hb_face_destroy(face);
}
// Use 16.16 for better precision than 26.6
#define HBFloatToFixedScale ((float)(1 << 16))
#define HBFloatToFixed(f) ((unsigned int)((f) * HBFloatToFixedScale))
hb_font_t* jdk_font_create_hbp(
hb_face_t* face,
float ptSize, float devScale,
hb_destroy_func_t destroy,
hb_font_funcs_t *font_funcs) {
hb_font_t *font;
font = hb_font_create(face);
hb_font_set_funcs(font,
font_funcs,
NULL,
(hb_destroy_func_t)_do_nothing);
hb_font_set_scale(font,
HBFloatToFixed(ptSize*devScale),
HBFloatToFixed(ptSize*devScale));
return font;
}
} // extern "C"

@ -0,0 +1,89 @@
/*
* Copyright (c) 2023, 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.
*/
#ifndef HB_JDK_H
#define HB_JDK_H
#ifndef JDKEXPORT
#ifdef WIN32
#define JDKEXPORT __declspec(dllexport)
#else
#if (defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4) && (__GNUC_MINOR__ > 2))) || __has_attribute(visibility)
#ifdef ARM
#define JDKEXPORT __attribute__((externally_visible,visibility("default")))
#else
#define JDKEXPORT __attribute__((visibility("default")))
#endif
#else
#define JDKEXPORT
#endif
#endif
#endif
#include "hb.h"
# ifdef __cplusplus
extern "C" {
#endif
hb_font_t* jdk_font_create_hbp(
hb_face_t* face,
float ptSize, float devScale,
hb_destroy_func_t destroy,
hb_font_funcs_t* font_funcs);
typedef int (*store_layoutdata_func_t)
(int slot, int baseIndex, int offset,
float startX, float startY, float devScale,
int charCount, int glyphCount,
hb_glyph_info_t *glyphInfo, hb_glyph_position_t *glyphPos);
JDKEXPORT int jdk_hb_shape(
float ptSize,
float *matrix,
void* pFace,
unsigned short* chars,
int len,
int script,
int offset,
int limit,
int baseIndex, // used only to store results.
float startX, // used only to store results.
float startY, // used only to store results.
int flags,
int slot, // used only to store results
// Provide upcall Method handles that harfbuzz needs
hb_font_funcs_t* font_funcs,
store_layoutdata_func_t store_layout_data_upcall
);
# ifdef __cplusplus
}
#endif
#endif /* HB_JDK_H */

@ -0,0 +1,214 @@
/*
* Copyright (c) 2023, 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.
*
*/
/*
@test
@summary verify JNI and FFM harfbuzz OpenType layout implementations are equivalent.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.List;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
public class LayoutCompatTest {
static String jni = "jni.txt";
static String ffm = "ffm.txt";
static final AffineTransform tx = new AffineTransform();
static final FontRenderContext frc = new FontRenderContext(tx, false, false);
static final String englishText =
"OpenType font layout is a critical technology for proper rendering of many of the world's natural languages.";
static final String arabicText =
// " يعد تخطيط خطوط OpenType تقنية مهمة للعرض الصحيح للعديد من اللغات الطبيعية في العالم.יות";
"\u064a\u0639\u062f\u0020\u062a\u062e\u0637\u064a\u0637\u0020\u062e\u0637\u0648\u0637\u0020\u004f\u0070\u0065\u006e\u0054\u0079\u0070\u0065\u0020\u062a\u0642\u0646\u064a\u0629\u0020\u0645\u0647\u0645\u0629\u0020\u0644\u0644\u0639\u0631\u0636\u0020\u0627\u0644\u0635\u062d\u064a\u062d\u0020\u0644\u0644\u0639\u062f\u064a\u062f\u0020\u0645\u0646\u0020\u0627\u0644\u0644\u063a\u0627\u062a\u0020\u0627\u0644\u0637\u0628\u064a\u0639\u064a\u0629\u0020\u0641\u064a\u0020\u0627\u0644\u0639\u0627\u0644\u0645\u002e\u05d9\u05d5\u05ea";
static final String hebrewText =
// פריסת גופן OpenType היא טכנולוגיה קריטית לעיבוד נכון של רבות מהשפות הטבעיות בעולם.
"\u05e4\u05e8\u05d9\u05e1\u05ea\u0020\u05d2\u05d5\u05e4\u05df\u0020\u004f\u0070\u0065\u006e\u0054\u0079\u0070\u0065\u0020\u05d4\u05d9\u05d0\u0020\u05d8\u05db\u05e0\u05d5\u05dc\u05d5\u05d2\u05d9\u05d4\u0020\u05e7\u05e8\u05d9\u05d8\u05d9\u05ea\u0020\u05dc\u05e2\u05d9\u05d1\u05d5\u05d3\u0020\u05e0\u05db\u05d5\u05df\u0020\u05e9\u05dc\u0020\u05e8\u05d1\u05d5\u05ea\u0020\u05de\u05d4\u05e9\u05e4\u05d5\u05ea\u0020\u05d4\u05d8\u05d1\u05e2\u05d9\u05d5\u05ea\u0020\u05d1\u05e2\u05d5\u05dc\u05dd\u002e";
static final String thaiText =
// เค้าโครงแบบอักษร OpenType เป็นเทคโนโลยีที่สำคัญสำหรับการแสดงผลภาษาธรรมชาติจำนวนมากของโลกอย่างเหมาะสม
"\u0e40\u0e04\u0e49\u0e32\u0e42\u0e04\u0e23\u0e07\u0e41\u0e1a\u0e1a\u0e2d\u0e31\u0e01\u0e29\u0e23\u0020\u004f\u0070\u0065\u006e\u0054\u0079\u0070\u0065\u0020\u0e40\u0e1b\u0e47\u0e19\u0e40\u0e17\u0e04\u0e42\u0e19\u0e42\u0e25\u0e22\u0e35\u0e17\u0e35\u0e48\u0e2a\u0e33\u0e04\u0e31\u0e0d\u0e2a\u0e33\u0e2b\u0e23\u0e31\u0e1a\u0e01\u0e32\u0e23\u0e41\u0e2a\u0e14\u0e07\u0e1c\u0e25\u0e20\u0e32\u0e29\u0e32\u0e18\u0e23\u0e23\u0e21\u0e0a\u0e32\u0e15\u0e34\u0e08\u0e33\u0e19\u0e27\u0e19\u0e21\u0e32\u0e01\u0e02\u0e2d\u0e07\u0e42\u0e25\u0e01\u0e2d\u0e22\u0e48\u0e32\u0e07\u0e40\u0e2b\u0e21\u0e32\u0e30\u0e2a\u0e21";
static final String khmerText =
// ប្លង់ពុម្ពអក្សរ OpenType គឺជបច្ចកវិជ្ជសំខន់មួយសម្រប់ករបង្ហញត្រឹមត្រូវនធម្មជតិជច្រនរបស់ពិភពល
"\u1794\u17d2\u179b\u1784\u17cb\u1796\u17bb\u1798\u17d2\u1796\u17a2\u1780\u17d2\u179f\u179a\u0020\u004f\u0070\u0065\u006e\u0054\u0079\u0070\u0065\u0020\u1782\u17ba\u1787\u17b6\u1794\u1785\u17d2\u1785\u17c1\u1780\u179c\u17b7\u1787\u17d2\u1787\u17b6\u179f\u17c6\u1781\u17b6\u1793\u17cb\u1798\u17bd\u1799\u179f\u1798\u17d2\u179a\u17b6\u1794\u17cb\u1780\u17b6\u179a\u1794\u1784\u17d2\u17a0\u17b6\u1789\u178f\u17d2\u179a\u17b9\u1798\u178f\u17d2\u179a\u17bc\u179c\u1793\u17c3\u1797\u17b6\u179f\u17b6\u1792\u1798\u17d2\u1798\u1787\u17b6\u178f\u17b7\u1787\u17b6\u1785\u17d2\u179a\u17be\u1793\u179a\u1794\u179f\u17cb\u1796\u17b7\u1797\u1796\u179b\u17c4\u1780\u17d4";
static final String laoText =
// ຮູບແບບຕົວອັກສອນ OpenType ເປັນເທັກໂນໂລຍີສຳຄັນສຳລັບການສະແດງຜົນຂອງພາສາທຳມະຊາດຫຼາຍພາສາຂອງໂລກ.
"\u0eae\u0eb9\u0e9a\u0ec1\u0e9a\u0e9a\u0e95\u0ebb\u0ea7\u0ead\u0eb1\u0e81\u0eaa\u0ead\u0e99\u0020\u004f\u0070\u0065\u006e\u0054\u0079\u0070\u0065\u0020\u0ec0\u0e9b\u0eb1\u0e99\u0ec0\u0e97\u0eb1\u0e81\u0ec2\u0e99\u0ec2\u0ea5\u0e8d\u0eb5\u0eaa\u0eb3\u0e84\u0eb1\u0e99\u0eaa\u0eb3\u0ea5\u0eb1\u0e9a\u0e81\u0eb2\u0e99\u0eaa\u0eb0\u0ec1\u0e94\u0e87\u0e9c\u0ebb\u0e99\u0e82\u0ead\u0e87\u0e9e\u0eb2\u0eaa\u0eb2\u0e97\u0eb3\u0ea1\u0eb0\u0e8a\u0eb2\u0e94\u0eab\u0ebc\u0eb2\u0e8d\u0e9e\u0eb2\u0eaa\u0eb2\u0e82\u0ead\u0e87\u0ec2\u0ea5\u0e81\u002e";
static final String hindiText =
// ओपनटइप फ़न्ट लेआउट दुनि कई प्रकृति ओं के उचि प्रतिदन के ि एक महत्वपूर्ण तकन है
"\u0913\u092a\u0928\u091f\u093e\u0907\u092a\u0020\u092b\u093c\u0949\u0928\u094d\u091f\u0020\u0932\u0947\u0906\u0909\u091f\u0020\u0926\u0941\u0928\u093f\u092f\u093e\u0020\u0915\u0940\u0020\u0915\u0908\u0020\u092a\u094d\u0930\u093e\u0915\u0943\u0924\u093f\u0915\u0020\u092d\u093e\u0937\u093e\u0913\u0902\u0020\u0915\u0947\u0020\u0909\u091a\u093f\u0924\u0020\u092a\u094d\u0930\u0924\u093f\u092a\u093e\u0926\u0928\u0020\u0915\u0947\u0020\u0932\u093f\u090f\u0020\u090f\u0915\u0020\u092e\u0939\u0924\u094d\u0935\u092a\u0942\u0930\u094d\u0923\u0020\u0924\u0915\u0928\u0940\u0915\u0020\u0939\u0948\u0964";
static final String kannadaText =
// ಓಪನ್‌ಟಪ್ ಟ್ ವಿನ್ಯಸವ ಪ್ರಪಚದ ಅನ ಸರ್ಗಿಕ ಷೆಗಳ ಸರಿಯ ರೆಡರಿಗ್‌ಗೆ ನಿರ್ಣಯಕ ತ್ರಜ್ಞನವಗಿದೆ.
"\u0c93\u0caa\u0ca8\u0ccd\u200c\u0c9f\u0cc8\u0caa\u0ccd\u0020\u0cab\u0cbe\u0c82\u0c9f\u0ccd\u0020\u0cb5\u0cbf\u0ca8\u0ccd\u0caf\u0cbe\u0cb8\u0cb5\u0cc1\u0020\u0caa\u0ccd\u0cb0\u0caa\u0c82\u0c9a\u0ca6\u0020\u0c85\u0ca8\u0cc7\u0c95\u0020\u0ca8\u0cc8\u0cb8\u0cb0\u0ccd\u0c97\u0cbf\u0c95\u0020\u0cad\u0cbe\u0cb7\u0cc6\u0c97\u0cb3\u0020\u0cb8\u0cb0\u0cbf\u0caf\u0cbe\u0ca6\u0020\u0cb0\u0cc6\u0c82\u0ca1\u0cb0\u0cbf\u0c82\u0c97\u0ccd\u200c\u0c97\u0cc6\u0020\u0ca8\u0cbf\u0cb0\u0ccd\u0ca3\u0cbe\u0caf\u0c95\u0020\u0ca4\u0c82\u0ca4\u0ccd\u0cb0\u0c9c\u0ccd\u0c9e\u0cbe\u0ca8\u0cb5\u0cbe\u0c97\u0cbf\u0ca6\u0cc6\u002e";
static final String tamilText =
// ஓபன் ப் எழத்த அமப்ப என்பத உலகின் பல இயற்க ிகளச் சரி வழங்கவதற்க ஒர க்கியம ில்நட்பமம்.
"\u0b93\u0baa\u0ba9\u0bcd\u0020\u0b9f\u0bc8\u0baa\u0bcd\u0020\u0b8e\u0bb4\u0bc1\u0ba4\u0bcd\u0ba4\u0bc1\u0bb0\u0bc1\u0020\u0b85\u0bae\u0bc8\u0baa\u0bcd\u0baa\u0bc1\u0020\u0b8e\u0ba9\u0bcd\u0baa\u0ba4\u0bc1\u0020\u0b89\u0bb2\u0b95\u0bbf\u0ba9\u0bcd\u0020\u0baa\u0bb2\u0020\u0b87\u0baf\u0bb1\u0bcd\u0b95\u0bc8\u0020\u0bae\u0bca\u0bb4\u0bbf\u0b95\u0bb3\u0bc8\u0b9a\u0bcd\u0020\u0b9a\u0bb0\u0bbf\u0baf\u0bbe\u0b95\u0020\u0bb5\u0bb4\u0b99\u0bcd\u0b95\u0bc1\u0bb5\u0ba4\u0bb1\u0bcd\u0b95\u0bbe\u0ba9\u0020\u0b92\u0bb0\u0bc1\u0020\u0bae\u0bc1\u0b95\u0bcd\u0b95\u0bbf\u0baf\u0bae\u0bbe\u0ba9\u0020\u0ba4\u0bca\u0bb4\u0bbf\u0bb2\u0bcd\u0ba8\u0bc1\u0b9f\u0bcd\u0baa\u0bae\u0bbe\u0b95\u0bc1\u0bae\u0bcd\u002e";
static final String malayalamText =
// ഓപ്പൺടപ്പ് ണ്ട് ഔട്ട് കത്തി പല സ്വി ഷകളുടയു ശരി ൻഡറിിനുള്ള ഒരു ിർണയക ങ്കികവിദ്യയണ്.
"\u0d13\u0d2a\u0d4d\u0d2a\u0d7a\u0d1f\u0d48\u0d2a\u0d4d\u0d2a\u0d4d\u0020\u0d2b\u0d4b\u0d23\u0d4d\u0d1f\u0d4d\u0020\u0d32\u0d47\u0d14\u0d1f\u0d4d\u0d1f\u0d4d\u0020\u0d32\u0d4b\u0d15\u0d24\u0d4d\u0d24\u0d3f\u0d32\u0d46\u0020\u0d2a\u0d32\u0020\u0d38\u0d4d\u0d35\u0d3e\u0d2d\u0d3e\u0d35\u0d3f\u0d15\u0020\u0d2d\u0d3e\u0d37\u0d15\u0d33\u0d41\u0d1f\u0d46\u0d2f\u0d41\u0d02\u0020\u0d36\u0d30\u0d3f\u0d2f\u0d3e\u0d2f\u0020\u0d31\u0d46\u0d7b\u0d21\u0d31\u0d3f\u0d02\u0d17\u0d3f\u0d28\u0d41\u0d33\u0d4d\u0d33\u0020\u0d12\u0d30\u0d41\u0020\u0d28\u0d3f\u0d7c\u0d23\u0d3e\u0d2f\u0d15\u0020\u0d38\u0d3e\u0d19\u0d4d\u0d15\u0d47\u0d24\u0d3f\u0d15\u0d35\u0d3f\u0d26\u0d4d\u0d2f\u0d2f\u0d3e\u0d23\u0d4d\u002e";
static final String gujaratiText =
// ຮູບແບບຕົວອັກສອນ OpenType ເປັນເທັກໂນໂລຍີສຳຄັນສຳລັບການສະແດງຜົນຂອງພາສາທຳມະຊາດຫຼາຍພາສາຂອງໂລກ.
"\u0eae\u0eb9\u0e9a\u0ec1\u0e9a\u0e9a\u0e95\u0ebb\u0ea7\u0ead\u0eb1\u0e81\u0eaa\u0ead\u0e99\u0020\u004f\u0070\u0065\u006e\u0054\u0079\u0070\u0065\u0020\u0ec0\u0e9b\u0eb1\u0e99\u0ec0\u0e97\u0eb1\u0e81\u0ec2\u0e99\u0ec2\u0ea5\u0e8d\u0eb5\u0eaa\u0eb3\u0e84\u0eb1\u0e99\u0eaa\u0eb3\u0ea5\u0eb1\u0e9a\u0e81\u0eb2\u0e99\u0eaa\u0eb0\u0ec1\u0e94\u0e87\u0e9c\u0ebb\u0e99\u0e82\u0ead\u0e87\u0e9e\u0eb2\u0eaa\u0eb2\u0e97\u0eb3\u0ea1\u0eb0\u0e8a\u0eb2\u0e94\u0eab\u0ebc\u0eb2\u0e8d\u0e9e\u0eb2\u0eaa\u0eb2\u0e82\u0ead\u0e87\u0ec2\u0ea5\u0e81\u002e";
static final String teluguText =
// ఓపెన్‌టైప్ ఫాట్ లేఅవట్ అనేది ప్రపలోని అనేక సహజ భాషలన సరిగ్గా రెడరిగ్ చేయడానికి కీలకమైన సాకేతికత.
"\u0c13\u0c2a\u0c46\u0c28\u0c4d\u200c\u0c1f\u0c48\u0c2a\u0c4d\u0020\u0c2b\u0c3e\u0c02\u0c1f\u0c4d\u0020\u0c32\u0c47\u0c05\u0c35\u0c41\u0c1f\u0c4d\u0020\u0c05\u0c28\u0c47\u0c26\u0c3f\u0020\u0c2a\u0c4d\u0c30\u0c2a\u0c02\u0c1a\u0c02\u0c32\u0c4b\u0c28\u0c3f\u0020\u0c05\u0c28\u0c47\u0c15\u0020\u0c38\u0c39\u0c1c\u0020\u0c2d\u0c3e\u0c37\u0c32\u0c28\u0c41\u0020\u0c38\u0c30\u0c3f\u0c17\u0c4d\u0c17\u0c3e\u0020\u0c30\u0c46\u0c02\u0c21\u0c30\u0c3f\u0c02\u0c17\u0c4d\u0020\u0c1a\u0c47\u0c2f\u0c21\u0c3e\u0c28\u0c3f\u0c15\u0c3f\u0020\u0c15\u0c40\u0c32\u0c15\u0c2e\u0c48\u0c28\u0020\u0c38\u0c3e\u0c02\u0c15\u0c47\u0c24\u0c3f\u0c15\u0c24\u002e";
static Font[] allFonts;
public static void main(String args[]) throws Exception {
if (args.length > 0) {
writeLayouts(args[0]);
return;
}
String classesDir = System.getProperty("test.classes");
if (classesDir != null) {
String sep = System.getProperty("file.separator");
String fileDir = classesDir + sep;
jni = fileDir + jni;
ffm = fileDir + ffm;
}
forkAndWait(jni, false);
forkAndWait(ffm, true);
compareLayouts(jni, ffm);
}
static void compareLayouts(String file1, String file2) throws Exception {
FileInputStream i1 = new FileInputStream(file1);
FileInputStream i2 = new FileInputStream(file2);
byte[] ba1 = i1.readAllBytes();
byte[] ba2 = i2.readAllBytes();
for (int i = 0; i < ba1.length; i++) {
if (ba1[i] != ba2[i]) {
throw new RuntimeException("files differ byte offset=" + i);
}
}
}
static boolean isLogicalFont(Font f) {
String s = f.getFamily().toLowerCase();
if (s.startsWith(".") || // skip Apple System fonts - not supposed to be used
s.equals("serif") ||
s.equals("sansserif") ||
s.equals("dialog") ||
s.equals("dialoginput") ||
s.equals("monospaced")) {
return true;
}
return false;
}
static Font findFont(char c) {
for (Font f : allFonts) {
if (isLogicalFont(f)) continue;
if (f.canDisplay(c)) { // not for supplementary chars
return f.deriveFont(24.0f);
}
}
return new Font(Font.DIALOG, 24, Font.PLAIN);
}
static void writeGV(PrintStream out, String title, String text) {
char[] chars = text.toCharArray();
Font font = findFont(chars[0]);
GlyphVector gv = font.layoutGlyphVector(frc, chars, 0, chars.length, 0);
int ng = gv.getNumGlyphs();
int[] codes = gv.getGlyphCodes(0, ng, null);
float[] positions = gv.getGlyphPositions(0, ng, null);
out.println(title);
out.println(font);
out.println("num glyphs = " + ng);
out.print("Codes=");
for (int code : codes) out.print(" "+code); out.println();
out.print("Positions=");
for (float pos : positions) out.print(" "+pos); out.println();
out.println();
}
static void writeLayouts(String fileName) throws Exception {
allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
PrintStream out = new PrintStream(fileName);
out.println("java.home="+javaHome);
out.println("javaExe="+javaExe);
out.println("classpath="+classpath);
writeGV(out,"English:", englishText);
writeGV(out,"Arabic:", arabicText);
writeGV(out,"Hebrew:", hebrewText);
writeGV(out,"Thai:", thaiText);
writeGV(out,"Khmer:", khmerText);
writeGV(out,"Lao:", laoText);
writeGV(out,"Hindi:", hindiText);
writeGV(out,"Kannada:", kannadaText);
writeGV(out,"Tamil:", tamilText);
writeGV(out,"Malayalam:", malayalamText);
writeGV(out,"Gujarati:", gujaratiText);
writeGV(out,"Telugu:", teluguText);
out.close();
}
static final String javaHome = (System.getProperty("test.jdk") != null)
? System.getProperty("test.jdk")
: System.getProperty("java.home");
static final String javaExe =
javaHome + File.separator + "bin" + File.separator + "java";
static final String classpath =
System.getProperty("java.class.path");
static void forkAndWait(String fileName, boolean val) throws Exception {
List<String> args =
Arrays.asList(javaExe,
"-cp", classpath,
"-Dsun.font.layout.ffm="+Boolean.toString(val),
"-Dsun.font.layout.logtime=true",
"LayoutCompatTest",
fileName);
ProcessBuilder pb = new ProcessBuilder(args);
Process p = pb.start();
p.waitFor();
p.destroy();
}
}