1 /*
  2  * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  *
 25  */
 26 
 27 package sun.font;
 28 
 29 import java.awt.geom.Point2D;
 30 import sun.font.GlyphLayout.GVData;
 31 import sun.java2d.Disposer;
 32 import sun.java2d.DisposerRecord;
 33 
 34 import java.lang.foreign.Arena;
 35 import java.lang.foreign.FunctionDescriptor;
 36 import java.lang.foreign.Linker;
 37 import java.lang.foreign.MemoryLayout;
 38 import java.lang.foreign.MemorySegment;
 39 import static java.lang.foreign.MemorySegment.NULL;
 40 import java.lang.foreign.SequenceLayout;
 41 import java.lang.foreign.StructLayout;
 42 import java.lang.foreign.SymbolLookup;
 43 import java.lang.foreign.UnionLayout;
 44 import static java.lang.foreign.ValueLayout.*;
 45 
 46 import java.lang.invoke.MethodHandle;
 47 import java.lang.invoke.MethodHandles;
 48 import java.lang.invoke.MethodType;
 49 import java.lang.invoke.VarHandle;
 50 
 51 import java.util.Optional;
 52 import java.util.WeakHashMap;
 53 
 54 public class HBShaper {
 55 
 56     /*
 57      * union _hb_var_int_t {
 58      *     uint32_t u32;
 59      *     int32_t i32;
 60      *     uint16_t u16[2];
 61      *     int16_t i16[2];
 62      *     uint8_t u8[4];
 63      *     int8_t i8[4];
 64      * };
 65      */
 66     private static final UnionLayout VarIntLayout = MemoryLayout.unionLayout(
 67         JAVA_INT.withName("u32"),
 68         JAVA_INT.withName("i32"),
 69         MemoryLayout.sequenceLayout(2, JAVA_SHORT).withName("u16"),
 70         MemoryLayout.sequenceLayout(2, JAVA_SHORT).withName("i16"),
 71         MemoryLayout.sequenceLayout(4, JAVA_BYTE).withName("u8"),
 72         MemoryLayout.sequenceLayout(4, JAVA_BYTE).withName("i8")
 73     ).withName("_hb_var_int_t");
 74 
 75     /*
 76      * struct hb_glyph_position_t {
 77      *     hb_position_t x_advance;
 78      *     hb_position_t y_advance;
 79      *     hb_position_t x_offset;
 80      *     hb_position_t y_offset;
 81      *     hb_var_int_t var;
 82      * };
 83      */
 84     private static final StructLayout PositionLayout = MemoryLayout.structLayout(
 85         JAVA_INT.withName("x_advance"),
 86         JAVA_INT.withName("y_advance"),
 87         JAVA_INT.withName("x_offset"),
 88         JAVA_INT.withName("y_offset"),
 89         VarIntLayout.withName("var")
 90      ).withName("hb_glyph_position_t");
 91 
 92     /**
 93      * struct hb_glyph_info_t {
 94      *     hb_codepoint_t codepoint;
 95      *     hb_mask_t mask;
 96      *     uint32_t cluster;
 97      *     hb_var_int_t var1;
 98      *     hb_var_int_t var2;
 99      * };
100      */
101     private static final StructLayout GlyphInfoLayout = MemoryLayout.structLayout(
102         JAVA_INT.withName("codepoint"),
103         JAVA_INT.withName("mask"),
104         JAVA_INT.withName("cluster"),
105         VarIntLayout.withName("var1"),
106         VarIntLayout.withName("var2")
107     ).withName("hb_glyph_info_t");
108 
109     private static VarHandle getVarHandle(StructLayout struct, String name) {
110         VarHandle h = struct.arrayElementVarHandle(PathElement.groupElement(name));
111         /* insert 0 offset so don't need to pass arg every time */
112         return MethodHandles.insertCoordinates(h, 1, 0L).withInvokeExactBehavior();
113     }
114 
115     private static final VarHandle x_offsetHandle;
116     private static final VarHandle y_offsetHandle;
117     private static final VarHandle x_advanceHandle;
118     private static final VarHandle y_advanceHandle;
119     private static final VarHandle codePointHandle;
120     private static final VarHandle clusterHandle;
121 
122     private static final MethodHandles.Lookup MH_LOOKUP;
123     private static final Linker LINKER;
124     private static final SymbolLookup SYM_LOOKUP;
125     private static final MethodHandle malloc_handle;
126     private static final MethodHandle create_face_handle;
127     private static final MethodHandle dispose_face_handle;
128     private static final MethodHandle jdk_hb_shape_handle;
129 
130     /* hb_jdk_font_funcs_struct is a pointer to a harfbuzz font_funcs
131      * object which references the 5 following upcall stubs.
132      * The singleton shared font_funcs ptr is passed down in each
133      * call to shape() and installed on the hb_font.
134      */
135     private static final MemorySegment hb_jdk_font_funcs_struct;
136     private static final MemorySegment get_var_glyph_stub;
137     private static final MemorySegment get_nominal_glyph_stub;
138     private static final MemorySegment get_h_advance_stub;
139     private static final MemorySegment get_v_advance_stub;
140     private static final MemorySegment get_contour_pt_stub;
141 
142     private static final MemorySegment store_layout_results_stub;
143 
144     private static FunctionDescriptor
145        getFunctionDescriptor(MemoryLayout retType,
146                              MemoryLayout... argTypes) {
147 
148        return (retType == null) ?
149                FunctionDescriptor.ofVoid(argTypes) :
150                FunctionDescriptor.of(retType, argTypes);
151     }
152 
153     private static MethodHandle getMethodHandle
154          (String mName,
155           FunctionDescriptor fd) {
156 
157         try {
158             MethodType mType = fd.toMethodType();
159             return MH_LOOKUP.findStatic(HBShaper.class, mName, mType);
160         } catch (IllegalAccessException | NoSuchMethodException e) {
161            return null;
162        }
163    }
164 
165     static {
166         MH_LOOKUP = MethodHandles.lookup();
167         LINKER = Linker.nativeLinker();
168         SYM_LOOKUP = SymbolLookup.loaderLookup().or(LINKER.defaultLookup());
169         FunctionDescriptor mallocDescriptor =
170             FunctionDescriptor.of(ADDRESS, JAVA_LONG);
171         MemorySegment malloc_symbol = SYM_LOOKUP.findOrThrow("malloc");
172         @SuppressWarnings("restricted")
173         MethodHandle tmp1 = LINKER.downcallHandle(malloc_symbol, mallocDescriptor);
174         malloc_handle = tmp1;
175 
176         FunctionDescriptor createFaceDescriptor =
177             FunctionDescriptor.of(ADDRESS, ADDRESS);
178         MemorySegment create_face_symbol = SYM_LOOKUP.findOrThrow("HBCreateFace");
179         @SuppressWarnings("restricted")
180         MethodHandle tmp2 = LINKER.downcallHandle(create_face_symbol, createFaceDescriptor);
181         create_face_handle = tmp2;
182 
183         FunctionDescriptor disposeFaceDescriptor = FunctionDescriptor.ofVoid(ADDRESS);
184         MemorySegment dispose_face_symbol = SYM_LOOKUP.findOrThrow("HBDisposeFace");
185         @SuppressWarnings("restricted")
186         MethodHandle tmp3 = LINKER.downcallHandle(dispose_face_symbol, disposeFaceDescriptor);
187         dispose_face_handle = tmp3;
188 
189         FunctionDescriptor shapeDesc = FunctionDescriptor.ofVoid(
190             //JAVA_INT,    // return type
191             JAVA_FLOAT,  // ptSize
192             ADDRESS,     // matrix
193             ADDRESS,     // face
194             ADDRESS,     // chars
195             JAVA_INT,    // len
196             JAVA_INT,    // script
197             JAVA_INT,    // offset
198             JAVA_INT,    // limit
199             JAVA_INT,    // baseIndex
200             JAVA_FLOAT,  // startX
201             JAVA_FLOAT,  // startY
202             JAVA_INT,    // flags,
203             JAVA_INT,    // slot,
204             ADDRESS,     // ptr to harfbuzz font_funcs object.
205             ADDRESS);    // store_results_fn
206 
207         MemorySegment shape_sym = SYM_LOOKUP.findOrThrow("jdk_hb_shape");
208         @SuppressWarnings("restricted")
209         MethodHandle tmp4 = LINKER.downcallHandle(shape_sym, shapeDesc);
210         jdk_hb_shape_handle = tmp4;
211 
212         Arena garena = Arena.global(); // creating stubs that exist until VM exit.
213         FunctionDescriptor get_var_glyph_fd = getFunctionDescriptor(JAVA_INT,  // return type
214               ADDRESS, ADDRESS, JAVA_INT, JAVA_INT, ADDRESS, ADDRESS); // arg types
215         MethodHandle get_var_glyph_mh =
216             getMethodHandle("get_variation_glyph", get_var_glyph_fd);
217         @SuppressWarnings("restricted")
218         MemorySegment tmp5 = LINKER.upcallStub(get_var_glyph_mh, get_var_glyph_fd, garena);
219         get_var_glyph_stub = tmp5;
220 
221         FunctionDescriptor get_nominal_glyph_fd = getFunctionDescriptor(JAVA_INT, // return type
222                    ADDRESS, ADDRESS, JAVA_INT, ADDRESS, ADDRESS); // arg types
223         MethodHandle get_nominal_glyph_mh =
224             getMethodHandle("get_nominal_glyph", get_nominal_glyph_fd);
225         @SuppressWarnings("restricted")
226         MemorySegment tmp6 = LINKER.upcallStub(get_nominal_glyph_mh, get_nominal_glyph_fd, garena);
227         get_nominal_glyph_stub = tmp6;
228 
229         FunctionDescriptor get_h_adv_fd = getFunctionDescriptor(JAVA_INT,  // return type
230                    ADDRESS, ADDRESS, JAVA_INT, ADDRESS); // arg types
231         MethodHandle get_h_adv_mh =
232             getMethodHandle("get_glyph_h_advance", get_h_adv_fd);
233         @SuppressWarnings("restricted")
234         MemorySegment tmp7 = LINKER.upcallStub(get_h_adv_mh, get_h_adv_fd, garena);
235         get_h_advance_stub = tmp7;
236 
237         FunctionDescriptor get_v_adv_fd = getFunctionDescriptor(JAVA_INT,  // return type
238                    ADDRESS, ADDRESS, JAVA_INT, ADDRESS); // arg types
239         MethodHandle get_v_adv_mh =
240             getMethodHandle("get_glyph_v_advance", get_v_adv_fd);
241         @SuppressWarnings("restricted")
242         MemorySegment tmp8 = LINKER.upcallStub(get_v_adv_mh, get_v_adv_fd, garena);
243         get_v_advance_stub = tmp8;
244 
245         FunctionDescriptor get_contour_pt_fd = getFunctionDescriptor(JAVA_INT,  // return type
246             ADDRESS, ADDRESS, JAVA_INT, JAVA_INT, ADDRESS, ADDRESS, ADDRESS); // arg types
247         MethodHandle get_contour_pt_mh =
248             getMethodHandle("get_glyph_contour_point", get_contour_pt_fd);
249         @SuppressWarnings("restricted")
250         MemorySegment tmp9 = LINKER.upcallStub(get_contour_pt_mh, get_contour_pt_fd, garena);
251         get_contour_pt_stub = tmp9;
252 
253        /* Having now created the font upcall stubs, we can call down to create
254         * the native harfbuzz object holding these.
255         */
256         FunctionDescriptor createFontFuncsDescriptor = FunctionDescriptor.of(
257             ADDRESS,     // hb_font_funcs* return type
258             ADDRESS,     // glyph_fn upcall stub
259             ADDRESS,     // variation_fn upcall stub
260             ADDRESS,     // h_advance_fn upcall stub
261             ADDRESS,     // v_advance_fn upcall stub
262             ADDRESS);     // contour_pt_fn upcall stub
263         MemorySegment create_font_funcs_symbol = SYM_LOOKUP.findOrThrow("HBCreateFontFuncs");
264         @SuppressWarnings("restricted")
265         MethodHandle create_font_funcs_handle =
266             LINKER.downcallHandle(create_font_funcs_symbol, createFontFuncsDescriptor);
267 
268         MemorySegment s = null;
269         try {
270             s = (MemorySegment)create_font_funcs_handle.invokeExact(
271                 get_nominal_glyph_stub,
272                 get_var_glyph_stub,
273                 get_h_advance_stub,
274                 get_v_advance_stub,
275                 get_contour_pt_stub);
276         } catch (Throwable t) {
277             t.printStackTrace();
278         }
279         hb_jdk_font_funcs_struct = s;
280 
281         FunctionDescriptor store_layout_fd =
282            FunctionDescriptor.ofVoid(
283                    JAVA_INT,               // slot
284                    JAVA_INT,               // baseIndex
285                    JAVA_INT,               // offset
286                    JAVA_FLOAT,             // startX
287                    JAVA_FLOAT,             // startX
288                    JAVA_FLOAT,             // devScale
289                    JAVA_INT,               // charCount
290                    JAVA_INT,               // glyphCount
291                    ADDRESS,                // glyphInfo
292                    ADDRESS);               // glyphPos
293         MethodHandle store_layout_mh =
294             getMethodHandle("store_layout_results", store_layout_fd);
295         @SuppressWarnings("restricted")
296         MemorySegment tmp10 = LINKER.upcallStub(store_layout_mh, store_layout_fd, garena);
297         store_layout_results_stub = tmp10;
298 
299         x_offsetHandle = getVarHandle(PositionLayout, "x_offset");
300         y_offsetHandle = getVarHandle(PositionLayout, "y_offset");
301         x_advanceHandle = getVarHandle(PositionLayout, "x_advance");
302         y_advanceHandle = getVarHandle(PositionLayout, "y_advance");
303         codePointHandle = getVarHandle(GlyphInfoLayout, "codepoint");
304         clusterHandle = getVarHandle(GlyphInfoLayout, "cluster");
305     }
306 
307 
308     /*
309      * This is expensive but it is done just once per font.
310      * The unbound stub could be cached but the savings would
311      * be very low in the only case it is used.
312      */
313     @SuppressWarnings("restricted")
314     private static MemorySegment getBoundUpcallStub
315          (Arena arena, Class<?> clazz, Object bindArg, String mName,
316           MemoryLayout retType, MemoryLayout... argTypes) {
317 
318        try {
319             FunctionDescriptor nativeDescriptor =
320                (retType == null) ?
321                    FunctionDescriptor.ofVoid(argTypes) :
322                    FunctionDescriptor.of(retType, argTypes);
323            MethodType mType = nativeDescriptor.toMethodType();
324            mType = mType.insertParameterTypes(0, clazz);
325            MethodHandle mh = MH_LOOKUP.findStatic(HBShaper.class, mName, mType);
326            MethodHandle bound_handle = mh.bindTo(bindArg);
327            return LINKER.upcallStub(bound_handle, nativeDescriptor, arena);
328        } catch (IllegalAccessException | NoSuchMethodException e) {
329           return null;
330        }
331    }
332 
333     private static int get_nominal_glyph(
334         MemorySegment font_ptr,   /* Not used */
335         MemorySegment font_data,  /* Not used */
336         int unicode,
337         MemorySegment glyph,      /* pointer to location to store glyphID */
338         MemorySegment user_data   /* Not used */
339     ) {
340 
341         Font2D font2D = scopedVars.get().font();
342         int glyphID = font2D.charToGlyph(unicode);
343         @SuppressWarnings("restricted")
344         MemorySegment glyphIDPtr = glyph.reinterpret(4);
345         glyphIDPtr.setAtIndex(JAVA_INT, 0, glyphID);
346         return (glyphID != 0) ? 1 : 0;
347     }
348 
349     private static int get_variation_glyph(
350         MemorySegment font_ptr,   /* Not used */
351         MemorySegment font_data,  /* Not used */
352         int unicode,
353         int variation_selector,
354         MemorySegment glyph,      /* pointer to location to store glyphID */
355         MemorySegment user_data   /* Not used */
356     ) {
357         Font2D font2D = scopedVars.get().font();
358         int glyphID = font2D.charToVariationGlyph(unicode, variation_selector);
359         @SuppressWarnings("restricted")
360         MemorySegment glyphIDPtr = glyph.reinterpret(4);
361         glyphIDPtr.setAtIndex(JAVA_INT, 0, glyphID);
362         return (glyphID != 0) ? 1 : 0;
363     }
364 
365     private static final float HBFloatToFixedScale = ((float)(1 << 16));
366     private static final int HBFloatToFixed(float f) {
367         return ((int)((f) * HBFloatToFixedScale));
368     }
369 
370     private static int get_glyph_h_advance(
371         MemorySegment font_ptr,   /* Not used */
372         MemorySegment font_data,  /* Not used */
373         int glyph,
374         MemorySegment user_data  /* Not used */
375     ) {
376         FontStrike strike = scopedVars.get().fontStrike();
377         Point2D.Float pt = strike.getGlyphMetrics(glyph);
378         return (pt != null) ? HBFloatToFixed(pt.x) : 0;
379     }
380 
381     private static int get_glyph_v_advance(
382         MemorySegment font_ptr,   /* Not used */
383         MemorySegment font_data,  /* Not used */
384         int glyph,
385         MemorySegment user_data  /* Not used */
386     ) {
387 
388         FontStrike strike = scopedVars.get().fontStrike();
389         Point2D.Float pt = strike.getGlyphMetrics(glyph);
390         return (pt != null) ? HBFloatToFixed(pt.y) : 0;
391     }
392 
393     /*
394      * This class exists to make the code that uses it less verbose
395      */
396     private static class IntPtr {
397         MemorySegment seg;
398         IntPtr(MemorySegment seg) {
399         }
400 
401         void set(int i) {
402             seg.setAtIndex(JAVA_INT, 0, i);
403         }
404     }
405 
406     private static int get_glyph_contour_point(
407         MemorySegment font_ptr,   /* Not used */
408         MemorySegment font_data,  /* Not used */
409         int glyph,
410         int point_index,
411         MemorySegment x_ptr,     /* ptr to return x */
412         MemorySegment y_ptr,     /* ptr to return y */
413         MemorySegment user_data  /* Not used */
414     ) {
415         IntPtr x = new IntPtr(x_ptr);
416         IntPtr y = new IntPtr(y_ptr);
417 
418         if ((glyph & 0xfffe) == 0xfffe) {
419             x.set(0);
420             y.set(0);
421             return 1;
422         }
423 
424         FontStrike strike = scopedVars.get().fontStrike();
425         Point2D.Float pt = ((PhysicalStrike)strike).getGlyphPoint(glyph, point_index);
426         x.set(HBFloatToFixed(pt.x));
427         y.set(HBFloatToFixed(pt.y));
428 
429        return 1;
430     }
431 
432     record ScopedVars (
433         Font2D font,
434         FontStrike fontStrike,
435         GVData gvData,
436         Point2D.Float point) {}
437 
438     static final ScopedValue<ScopedVars> scopedVars = ScopedValue.newInstance();
439 
440     static void shape(
441         Font2D font2D,
442         FontStrike fontStrike,
443         float ptSize,
444         float[] mat,
445         MemorySegment hbface,
446         char[] text,
447         GVData gvData,
448         int script,
449         int offset,
450         int limit,
451         int baseIndex,
452         Point2D.Float startPt,
453         int flags,
454         int slot) {
455 
456         /*
457          * ScopedValue is needed so that call backs into Java during
458          * shaping can locate the correct instances of these to query or update.
459          * The alternative of creating bound method handles is far too slow.
460          */
461         ScopedVars vars = new ScopedVars(font2D, fontStrike, gvData, startPt);
462         ScopedValue.runWhere(scopedVars, vars, () -> {
463             try (Arena arena = Arena.ofConfined()) {
464 
465                 float startX = (float)startPt.getX();
466                 float startY = (float)startPt.getY();
467 
468                 MemorySegment matrix = arena.allocateFrom(JAVA_FLOAT, mat);
469                 MemorySegment chars = arena.allocateFrom(JAVA_CHAR, text);
470 
471                 /*int ret =*/ jdk_hb_shape_handle.invokeExact(
472                      ptSize, matrix, hbface, chars, text.length,
473                      script, offset, limit,
474                      baseIndex, startX, startY, flags, slot,
475                      hb_jdk_font_funcs_struct,
476                      store_layout_results_stub);
477             } catch (Throwable t) {
478             }
479         });
480     }
481 
482     private static int getFontTableData(Font2D font2D,
483                                 int tag,
484                                 MemorySegment data_ptr_out) {
485 
486         /*
487          * On return, the data_out_ptr will point to memory allocated by native malloc,
488          * so it will be freed by the caller using native free - when it is
489          * done with it.
490          */
491         @SuppressWarnings("restricted")
492         MemorySegment data_ptr = data_ptr_out.reinterpret(ADDRESS.byteSize());
493         if (tag == 0) {
494             data_ptr.setAtIndex(ADDRESS, 0, NULL);
495             return 0;
496         }
497         byte[] data = font2D.getTableBytes(tag);
498         if (data == null) {
499             data_ptr.setAtIndex(ADDRESS, 0, NULL);
500             return 0;
501         }
502         int len = data.length;
503         MemorySegment zero_len = NULL;
504         try {
505             zero_len = (MemorySegment)malloc_handle.invokeExact((long)len);
506         } catch (Throwable t) {
507         }
508         if (zero_len.equals(NULL)) {
509             data_ptr.setAtIndex(ADDRESS, 0, NULL);
510             return 0;
511         }
512         @SuppressWarnings("restricted")
513         MemorySegment mem = zero_len.reinterpret(len);
514         MemorySegment.copy(data, 0, mem, JAVA_BYTE, 0, len);
515         data_ptr.setAtIndex(ADDRESS, 0, mem);
516         return len;
517     }
518 
519     /* WeakHashMap is used so that we do not retain temporary fonts
520      *
521      * The value is a class that implements the 2D Disposer, so
522      * that the native resources for temp. fonts can be freed.
523      *
524      * Installed fonts should never be cleared from the map as
525      * they are permanently referenced.
526      */
527     private static final WeakHashMap<Font2D, FaceRef>
528        faceMap = new WeakHashMap<>();
529 
530     static MemorySegment getFace(Font2D font2D) {
531         FaceRef ref;
532         synchronized (faceMap) {
533             ref = faceMap.computeIfAbsent(font2D, FaceRef::new);
534         }
535         return ref.getFace();
536     }
537 
538     private static class FaceRef implements DisposerRecord {
539         private Font2D font2D;
540         private MemorySegment face;
541         // get_table_data_fn uses an Arena managed by GC,
542         // so we need to keep a reference to it here until
543         // this FaceRef is collected.
544         private MemorySegment get_table_data_fn;
545 
546         private FaceRef(Font2D font) {
547             this.font2D = font;
548         }
549 
550         private synchronized MemorySegment getFace() {
551             if (face == null) {
552                 createFace();
553                 if (face != null) {
554                     Disposer.addObjectRecord(font2D, this);
555                 }
556                 font2D = null;
557             }
558             return face;
559         }
560 
561         private void createFace() {
562             try {
563                 get_table_data_fn = getBoundUpcallStub(Arena.ofAuto(),
564                         Font2D.class,
565                         font2D,                      // bind arg
566                         "getFontTableData",          // method name
567                         JAVA_INT,                   // return type
568                         JAVA_INT, ADDRESS); // arg types
569                 if (get_table_data_fn == null) {
570                     return;
571                 }
572                 face = (MemorySegment)create_face_handle.invokeExact(get_table_data_fn);
573             } catch (Throwable t) {
574             }
575         }
576 
577         @Override
578         public void dispose() {
579             try {
580                 dispose_face_handle.invokeExact(face);
581             } catch (Throwable t) {
582             }
583         }
584     }
585 
586 
587     /* Upcall to receive results of layout */
588     private static void store_layout_results(
589         int slot,
590         int baseIndex,
591         int offset,
592         float startX,
593         float startY,
594         float devScale,
595         int charCount,
596         int glyphCount,
597         MemorySegment /* hb_glyph_info_t* */ glyphInfo,
598         MemorySegment /* hb_glyph_position_t* */ glyphPos
599         ) {
600 
601         GVData gvdata = scopedVars.get().gvData();
602         Point2D.Float startPt = scopedVars.get().point();
603         float x=0, y=0;
604         float advX, advY;
605         float scale = 1.0f / HBFloatToFixedScale / devScale;
606 
607         int initialCount = gvdata._count;
608 
609         int maxGlyphs = (charCount > glyphCount) ? charCount : glyphCount;
610         int maxStore = maxGlyphs + initialCount;
611         boolean needToGrow = (maxStore > gvdata._glyphs.length) ||
612                              ((maxStore * 2 + 2) > gvdata._positions.length);
613         if (needToGrow) {
614             gvdata.grow(maxStore-initialCount);
615         }
616 
617         int glyphPosLen = glyphCount * 2 + 2;
618         long posSize = glyphPosLen * PositionLayout.byteSize();
619         @SuppressWarnings("restricted")
620         MemorySegment glyphPosArr = glyphPos.reinterpret(posSize);
621 
622         long glyphInfoSize = glyphCount * GlyphInfoLayout.byteSize();
623         @SuppressWarnings("restricted")
624         MemorySegment glyphInfoArr = glyphInfo.reinterpret(glyphInfoSize);
625 
626          for (int i = 0; i < glyphCount; i++) {
627              int storei = i + initialCount;
628              int cluster = (int)clusterHandle.get(glyphInfoArr, (long)i) - offset;
629              gvdata._indices[storei] = baseIndex + cluster;
630              int codePoint = (int)codePointHandle.get(glyphInfoArr, (long)i);
631              gvdata._glyphs[storei] = (slot | codePoint);
632              int x_offset = (int)x_offsetHandle.get(glyphPosArr, (long)i);
633              int y_offset = (int)y_offsetHandle.get(glyphPosArr, (long)i);
634              gvdata._positions[(storei*2)]   = startX + x + (x_offset * scale);
635              gvdata._positions[(storei*2)+1] = startY + y - (y_offset * scale);
636              int x_advance = (int)x_advanceHandle.get(glyphPosArr, (long)i);
637              int y_advance = (int)y_advanceHandle.get(glyphPosArr, (long)i);
638              x += x_advance * scale;
639              y += y_advance * scale;
640         }
641         int storeadv = initialCount + glyphCount;
642         gvdata._count = storeadv;
643         // The final slot in the positions array is important
644         // because when the GlyphVector is created from this
645         // data it determines the overall advance of the glyphvector
646         // and this is used in positioning the next glyphvector
647         // during rendering where text is broken into runs.
648         // We also need to report it back into "pt", so layout can
649         // pass it back down for any next run.
650         advX = startX + x;
651         advY = startY + y;
652         gvdata._positions[(storeadv*2)] = advX;
653         gvdata._positions[(storeadv*2)+1] = advY;
654         startPt.x = advX;
655         startPt.y = advY;
656     }
657 }