diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 161cbe380cf..2a4c5aa81ed 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -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; diff --git a/src/java.desktop/share/classes/sun/font/HBShaper.java b/src/java.desktop/share/classes/sun/font/HBShaper.java new file mode 100644 index 00000000000..90877623c2b --- /dev/null +++ b/src/java.desktop/share/classes/sun/font/HBShaper.java @@ -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 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 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 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 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 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 = 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 + 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; + } +} diff --git a/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java b/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java index bc3e6f2dad7..c291e45b558 100644 --- a/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java +++ b/src/java.desktop/share/classes/sun/font/SunLayoutEngine.java @@ -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) () -> + 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); + } } } diff --git a/src/java.desktop/share/native/libfontmanager/HBShaper_Panama.c b/src/java.desktop/share/native/libfontmanager/HBShaper_Panama.c new file mode 100644 index 00000000000..94289db7046 --- /dev/null +++ b/src/java.desktop/share/native/libfontmanager/HBShaper_Panama.c @@ -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 +#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; +} diff --git a/src/java.desktop/share/native/libfontmanager/hb-jdk-font-p.cc b/src/java.desktop/share/native/libfontmanager/hb-jdk-font-p.cc new file mode 100644 index 00000000000..590c273d151 --- /dev/null +++ b/src/java.desktop/share/native/libfontmanager/hb-jdk-font-p.cc @@ -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 + +#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" diff --git a/src/java.desktop/share/native/libfontmanager/hb-jdk-p.h b/src/java.desktop/share/native/libfontmanager/hb-jdk-p.h new file mode 100644 index 00000000000..58d39816b75 --- /dev/null +++ b/src/java.desktop/share/native/libfontmanager/hb-jdk-p.h @@ -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 */ diff --git a/test/jdk/java/awt/font/GlyphVector/LayoutCompatTest.java b/test/jdk/java/awt/font/GlyphVector/LayoutCompatTest.java new file mode 100644 index 00000000000..35aa90374d5 --- /dev/null +++ b/test/jdk/java/awt/font/GlyphVector/LayoutCompatTest.java @@ -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 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(); + } +}