8318364: Add an FFM-based implementation of harfbuzz OpenType layout
Reviewed-by: jdv, psadhukhan
This commit is contained in:
parent
1c0bd81a10
commit
f69e6653f8
src
java.base/share/classes
java.desktop/share
classes/sun/font
native/libfontmanager
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;
|
||||
|
659
src/java.desktop/share/classes/sun/font/HBShaper.java
Normal file
659
src/java.desktop/share/classes/sun/font/HBShaper.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
145
src/java.desktop/share/native/libfontmanager/HBShaper_Panama.c
Normal file
145
src/java.desktop/share/native/libfontmanager/HBShaper_Panama.c
Normal file
@ -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;
|
||||
}
|
241
src/java.desktop/share/native/libfontmanager/hb-jdk-font-p.cc
Normal file
241
src/java.desktop/share/native/libfontmanager/hb-jdk-font-p.cc
Normal file
@ -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"
|
89
src/java.desktop/share/native/libfontmanager/hb-jdk-p.h
Normal file
89
src/java.desktop/share/native/libfontmanager/hb-jdk-p.h
Normal file
@ -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 */
|
214
test/jdk/java/awt/font/GlyphVector/LayoutCompatTest.java
Normal file
214
test/jdk/java/awt/font/GlyphVector/LayoutCompatTest.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user