| /* | |
| * Copyright (c) 2023, 2025, 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); | |
| MemorySegment malloc_symbol = SYM_LOOKUP.findOrThrow("malloc"); | |
| @SuppressWarnings("restricted") | |
| MethodHandle tmp1 = LINKER.downcallHandle(malloc_symbol, mallocDescriptor); | |
| malloc_handle = tmp1; | |
| FunctionDescriptor createFaceDescriptor = | |
| FunctionDescriptor.of(ADDRESS, ADDRESS); | |
| MemorySegment create_face_symbol = SYM_LOOKUP.findOrThrow("HBCreateFace"); | |
| @SuppressWarnings("restricted") | |
| MethodHandle tmp2 = LINKER.downcallHandle(create_face_symbol, createFaceDescriptor); | |
| create_face_handle = tmp2; | |
| FunctionDescriptor disposeFaceDescriptor = FunctionDescriptor.ofVoid(ADDRESS); | |
| MemorySegment dispose_face_symbol = SYM_LOOKUP.findOrThrow("HBDisposeFace"); | |
| @SuppressWarnings("restricted") | |
| MethodHandle tmp3 = LINKER.downcallHandle(dispose_face_symbol, disposeFaceDescriptor); | |
| dispose_face_handle = tmp3; | |
| FunctionDescriptor shapeDesc = FunctionDescriptor.ofVoid( | |
| 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 | |
| MemorySegment shape_sym = SYM_LOOKUP.findOrThrow("jdk_hb_shape"); | |
| @SuppressWarnings("restricted") | |
| MethodHandle tmp4 = LINKER.downcallHandle(shape_sym, 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 | |
| MemorySegment create_font_funcs_symbol = SYM_LOOKUP.findOrThrow("HBCreateFontFuncs"); | |
| @SuppressWarnings("restricted") | |
| MethodHandle create_font_funcs_handle = | |
| LINKER.downcallHandle(create_font_funcs_symbol, 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.charToGlyphRaw(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.charToVariationGlyphRaw(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); | |
| 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; | |
| } | |
| } |