lmthb.c /size: 28 Kb    last modification: 2024-01-16 10:22
1/*
2    See license.txt in the root of this project.
3*/
4
5# include "luametatex.h"
6# include "lmtoptional.h"
7
8/*tex
9
10    This is using similar c-lua-interfacing code as that Kai Eigner wrote for ffi. I cleaned it up
11    a bit but the principles remain because that way we are downward compatible. Don't expect
12    miracles here. We use function pointers as we delay binding to the module. We use the system
13    library as it comes, because after all, that is what is expected: shaping conform what the
14    system offers.
15
16    This interface is only for testing. We load the font in \LUA\ anyway, so we don't need to
17    collect information here. The code runs on top of the \CONTEXT\ font plugin interface that
18    itself was written for testing purposes. When we wanted to test uniscribe the hb command
19    line program could be used for that, but more direct support was also added. The tests that
20    were done at that time (irr it was when xetex switched to hb and folks used that as reference)
21    ended up in articles. Later we used this mechanism to check how Idris advanced Arabic font
22    behaves in different shapers (context, uniscribe, hb, ...) as we try to follow uniscribe when
23    possible and this gave the glue to that. It showed interesting differences and overlap in
24    interpretation (and made us wonder when bugs - in whatever program or standard - get turned
25    into features, but that's another  matter, and we can always adapt and provide variants and
26    overloads if needed).
27
28    The following code is not dependent on h files so we don't need to install a whole bunch of
29    dependencies. Also, because we delay loading, there is no default overhead in startup. The
30    loading of the library happens (as usual) at the \LUA\ end, but in order for it to work okay
31    the initializer needs to be called, so that the functions get resolved. So, it works a bit
32    like the ffi interface: delayed loading, but (maybe) with a bit less overhead. I should
33    probably look at the latest api to see if things can be done with less code but on the other
34    hand there is no real reason to change something that already worked okay some years ago.
35
36    I guess that the script enumeration is no longer right but it probably doesn't matter as
37    numbers are passed anyway. We can probably make that into an integer (I need to test that
38    some day) as these enumerations are just that: integers, and the less hard-coding we have
39    here the better.
40
41    When this module is used (which is triggered via loading an optional module and setting the
42    mode in a font definition) other features of the \CONTEXT\ font handler are lost for that
43    specific font instance, simply because these mechanism operate independently. But that is
44    probably what a user expects anyway: no interference from other code, just the results from
45    a library. It makes no sense to complicate the machinery even more. This is comparable with
46    basemode and nodemode that are also seperated code paths.
47
48    So, we could probably simplify the following typedefs, but this is what Kai started with so
49    I stick to it. From the enumerations only the direction constant is used.
50
51*/
52
53typedef struct hb_blob_t hb_blob_t;
54
55/* typedef int hb_memory_mode_t; */
56
57typedef enum hb_memory_mode_t {
58    HB_MEMORY_MODE_DUPLICATE,
59    HB_MEMORY_MODE_READONLY,
60    HB_MEMORY_MODE_WRITABLE,
61    HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE
62} hb_memory_mode_t;
63
64typedef void (*hb_destroy_func_t) (
65    void *user_data
66);
67
68typedef struct       hb_face_t           hb_face_t;
69typedef const struct hb_language_impl_t *hb_language_t;
70typedef struct       hb_buffer_t         hb_buffer_t;
71
72/* typedef int hb_script_t; */
73/* typedef int hb_direction_t; */
74
75/*
76    The content of this enum doesn't really matter here because we don't use it. Integers are
77    passed around. So, even if the following is not up to date we're okay.
78*/
79
80typedef enum hb_script_t {
81    HB_SCRIPT_COMMON, HB_SCRIPT_INHERITED, HB_SCRIPT_UNKNOWN,
82
83    HB_SCRIPT_ARABIC, HB_SCRIPT_ARMENIAN, HB_SCRIPT_BENGALI, HB_SCRIPT_CYRILLIC,
84    HB_SCRIPT_DEVANAGARI, HB_SCRIPT_GEORGIAN, HB_SCRIPT_GREEK,
85    HB_SCRIPT_GUJARATI, HB_SCRIPT_GURMUKHI, HB_SCRIPT_HANGUL, HB_SCRIPT_HAN,
86    HB_SCRIPT_HEBREW, HB_SCRIPT_HIRAGANA, HB_SCRIPT_KANNADA, HB_SCRIPT_KATAKANA,
87    HB_SCRIPT_LAO, HB_SCRIPT_LATIN, HB_SCRIPT_MALAYALAM, HB_SCRIPT_ORIYA,
88    HB_SCRIPT_TAMIL, HB_SCRIPT_TELUGU, HB_SCRIPT_THAI, HB_SCRIPT_TIBETAN,
89    HB_SCRIPT_BOPOMOFO, HB_SCRIPT_BRAILLE, HB_SCRIPT_CANADIAN_SYLLABICS,
90    HB_SCRIPT_CHEROKEE, HB_SCRIPT_ETHIOPIC, HB_SCRIPT_KHMER, HB_SCRIPT_MONGOLIAN,
91    HB_SCRIPT_MYANMAR, HB_SCRIPT_OGHAM, HB_SCRIPT_RUNIC, HB_SCRIPT_SINHALA,
92    HB_SCRIPT_SYRIAC, HB_SCRIPT_THAANA, HB_SCRIPT_YI, HB_SCRIPT_DESERET,
93    HB_SCRIPT_GOTHIC, HB_SCRIPT_OLD_ITALIC, HB_SCRIPT_BUHID, HB_SCRIPT_HANUNOO,
94    HB_SCRIPT_TAGALOG, HB_SCRIPT_TAGBANWA, HB_SCRIPT_CYPRIOT, HB_SCRIPT_LIMBU,
95    HB_SCRIPT_LINEAR_B, HB_SCRIPT_OSMANYA, HB_SCRIPT_SHAVIAN, HB_SCRIPT_TAI_LE,
96    HB_SCRIPT_UGARITIC, HB_SCRIPT_BUGINESE, HB_SCRIPT_COPTIC,
97    HB_SCRIPT_GLAGOLITIC, HB_SCRIPT_KHAROSHTHI, HB_SCRIPT_NEW_TAI_LUE,
98    HB_SCRIPT_OLD_PERSIAN, HB_SCRIPT_SYLOTI_NAGRI, HB_SCRIPT_TIFINAGH,
99    HB_SCRIPT_BALINESE, HB_SCRIPT_CUNEIFORM, HB_SCRIPT_NKO, HB_SCRIPT_PHAGS_PA,
100    HB_SCRIPT_PHOENICIAN, HB_SCRIPT_CARIAN, HB_SCRIPT_CHAM, HB_SCRIPT_KAYAH_LI,
101    HB_SCRIPT_LEPCHA, HB_SCRIPT_LYCIAN, HB_SCRIPT_LYDIAN, HB_SCRIPT_OL_CHIKI,
102    HB_SCRIPT_REJANG, HB_SCRIPT_SAURASHTRA, HB_SCRIPT_SUNDANESE, HB_SCRIPT_VAI,
103    HB_SCRIPT_AVESTAN, HB_SCRIPT_BAMUM, HB_SCRIPT_EGYPTIAN_HIEROGLYPHS,
104    HB_SCRIPT_IMPERIAL_ARAMAIC, HB_SCRIPT_INSCRIPTIONAL_PAHLAVI,
105    HB_SCRIPT_INSCRIPTIONAL_PARTHIAN, HB_SCRIPT_JAVANESE, HB_SCRIPT_KAITHI,
106    HB_SCRIPT_LISU, HB_SCRIPT_MEETEI_MAYEK, HB_SCRIPT_OLD_SOUTH_ARABIAN,
107    HB_SCRIPT_OLD_TURKIC, HB_SCRIPT_SAMARITAN, HB_SCRIPT_TAI_THAM,
108    HB_SCRIPT_TAI_VIET, HB_SCRIPT_BATAK, HB_SCRIPT_BRAHMI, HB_SCRIPT_MANDAIC,
109    HB_SCRIPT_CHAKMA, HB_SCRIPT_MEROITIC_CURSIVE, HB_SCRIPT_MEROITIC_HIEROGLYPHS,
110    HB_SCRIPT_MIAO, HB_SCRIPT_SHARADA, HB_SCRIPT_SORA_SOMPENG, HB_SCRIPT_TAKRI,
111    HB_SCRIPT_BASSA_VAH, HB_SCRIPT_CAUCASIAN_ALBANIAN, HB_SCRIPT_DUPLOYAN,
112    HB_SCRIPT_ELBASAN, HB_SCRIPT_GRANTHA, HB_SCRIPT_KHOJKI, HB_SCRIPT_KHUDAWADI,
113    HB_SCRIPT_LINEAR_A, HB_SCRIPT_MAHAJANI, HB_SCRIPT_MANICHAEAN,
114    HB_SCRIPT_MENDE_KIKAKUI, HB_SCRIPT_MODI, HB_SCRIPT_MRO, HB_SCRIPT_NABATAEAN,
115    HB_SCRIPT_OLD_NORTH_ARABIAN, HB_SCRIPT_OLD_PERMIC, HB_SCRIPT_PAHAWH_HMONG,
116    HB_SCRIPT_PALMYRENE, HB_SCRIPT_PAU_CIN_HAU, HB_SCRIPT_PSALTER_PAHLAVI,
117    HB_SCRIPT_SIDDHAM, HB_SCRIPT_TIRHUTA, HB_SCRIPT_WARANG_CITI, HB_SCRIPT_AHOM,
118    HB_SCRIPT_ANATOLIAN_HIEROGLYPHS, HB_SCRIPT_HATRAN, HB_SCRIPT_MULTANI,
119    HB_SCRIPT_OLD_HUNGARIAN, HB_SCRIPT_SIGNWRITING, HB_SCRIPT_ADLAM,
120    HB_SCRIPT_BHAIKSUKI, HB_SCRIPT_MARCHEN, HB_SCRIPT_OSAGE, HB_SCRIPT_TANGUT,
121    HB_SCRIPT_NEWA, HB_SCRIPT_MASARAM_GONDI, HB_SCRIPT_NUSHU, HB_SCRIPT_SOYOMBO,
122    HB_SCRIPT_ZANABAZAR_SQUARE, HB_SCRIPT_DOGRA, HB_SCRIPT_GUNJALA_GONDI,
123    HB_SCRIPT_HANIFI_ROHINGYA, HB_SCRIPT_MAKASAR, HB_SCRIPT_MEDEFAIDRIN,
124    HB_SCRIPT_OLD_SOGDIAN, HB_SCRIPT_SOGDIAN, HB_SCRIPT_ELYMAIC,
125    HB_SCRIPT_NANDINAGARI, HB_SCRIPT_NYIAKENG_PUACHUE_HMONG, HB_SCRIPT_WANCHO,
126
127    HB_SCRIPT_INVALID, _HB_SCRIPT_MAX_VALUE, _HB_SCRIPT_MAX_VALUE_SIGNED,
128} hb_script_t;
129
130typedef enum hb_direction_t {
131    HB_DIRECTION_INVALID,
132    HB_DIRECTION_LTR,
133    HB_DIRECTION_RTL,
134    HB_DIRECTION_TTB,
135    HB_DIRECTION_BTT
136} hb_direction_t;
137
138typedef int hb_bool_t;
139
140typedef uint32_t hb_tag_t;
141
142typedef struct hb_feature_t {
143    hb_tag_t      tag;
144    uint32_t      value;
145    unsigned int  start;
146    unsigned int  end;
147} hb_feature_t;
148
149typedef struct hb_font_t hb_font_t;
150
151typedef uint32_t hb_codepoint_t;
152typedef int32_t  hb_position_t;
153typedef uint32_t hb_mask_t;
154
155typedef union _hb_var_int_t {
156    uint32_t u32;
157    int32_t  i32;
158    uint16_t u16[2];
159    int16_t  i16[2];
160    uint8_t  u8[4];
161    int8_t   i8[4];
162} hb_var_int_t;
163
164typedef struct hb_glyph_info_t {
165    hb_codepoint_t codepoint;
166    hb_mask_t      mask;
167    uint32_t       cluster;
168    /* private */
169    hb_var_int_t   var1;
170    hb_var_int_t   var2;
171} hb_glyph_info_t;
172
173typedef struct hb_glyph_position_t {
174    hb_position_t  x_advance;
175    hb_position_t  y_advance;
176    hb_position_t  x_offset;
177    hb_position_t  y_offset;
178    /* private */
179    hb_var_int_t   var;
180} hb_glyph_position_t;
181
182/*tex
183
184    We only need to initialize the font and call a shaper. There is no need to interface more as we
185    won't use those features. I never compiled this library myself and just took it from the system
186    (e.g from inkscape). Keep in mind that names can be different on windows and linux.
187
188    If needed we can reuse buffers and cache a bit more but it probably doesn't make much difference
189    performance wise. Also, in a bit more complex document font handling is not the most time
190    critical and when you use specific scripts in \TEX\ that are not supported otherwise and
191    therefore demand a library run time is probably the least of your problems. So best is that we
192    keep it all abstract.
193
194*/
195
196# define HBLIB_METATABLE  "optional.hblib"
197
198typedef struct hblib_data {
199    hb_font_t *font;
200} hblib_data;
201
202typedef struct hblib_state_info {
203
204    int initialized;
205    int padding;
206
207    const char * (*hb_version_string) (
208        void
209    );
210
211    hb_blob_t * (*hb_blob_create) (
212        const char        *data,
213        unsigned int       length,
214        hb_memory_mode_t   mode, /* Could be int I guess. */
215        void              *user_data,
216        hb_destroy_func_t  destroy
217    );
218
219    void (*hb_blob_destroy) (
220        hb_blob_t *blob
221    );
222
223    hb_face_t * (*hb_face_create) (
224        hb_blob_t    *blob,
225        unsigned int  index
226    );
227
228    void (*hb_face_destroy) (
229        hb_face_t *face
230    );
231
232    hb_language_t (*hb_language_from_string) (
233        const char *str,
234        int         len
235    );
236
237    void (*hb_buffer_set_language) (
238        hb_buffer_t   *buffer,
239        hb_language_t  language
240    );
241
242    hb_script_t (*hb_script_from_string) (
243        const char *s,
244        int         len
245    );
246
247    void (*hb_buffer_set_script) (
248        hb_buffer_t *buffer,
249        hb_script_t  script
250    );
251
252    hb_direction_t (*hb_direction_from_string) (
253        const char *str,
254        int         len
255    );
256
257    void (*hb_buffer_set_direction) (
258        hb_buffer_t    *buffer,
259        hb_direction_t  direction
260    );
261
262    hb_bool_t (*hb_feature_from_string) (
263        const char   *str,
264        int           len,
265        hb_feature_t *feature
266    );
267
268    hb_bool_t (*hb_shape_full) (
269        hb_font_t          *font,
270        hb_buffer_t        *buffer,
271        const hb_feature_t *features,
272        unsigned int        num_features,
273        const char * const *shaper_list
274    );
275
276    hb_buffer_t * (*hb_buffer_create )(
277        void
278    );
279
280    void (*hb_buffer_destroy)(
281        hb_buffer_t *buffer
282    );
283
284    void (*hb_buffer_add_utf8) (
285        hb_buffer_t  *buffer,
286        const char   *text,
287        int           text_length,
288        unsigned int  item_offset,
289        int           item_length
290    );
291
292    void (*hb_buffer_add_utf32) (
293        hb_buffer_t  *buffer,
294        const char   *text,
295        int           text_length,
296        unsigned int  item_offset,
297        int           item_length
298    );
299
300    /* void (*hb_buffer_add) (
301        hb_buffer_t    *buffer,
302        hb_codepoint_t  codepoint,
303        unsigned int    cluster
304    ); */
305
306    unsigned int (*hb_buffer_get_length) (
307        hb_buffer_t *buffer
308    );
309
310    hb_glyph_info_t * (*hb_buffer_get_glyph_infos) (
311        hb_buffer_t  *buffer,
312        unsigned int *length
313    );
314
315    hb_glyph_position_t * (*hb_buffer_get_glyph_positions) (
316        hb_buffer_t  *buffer,
317        unsigned int *length
318    );
319
320    void (*hb_buffer_reverse) (
321        hb_buffer_t *buffer
322    );
323
324    void (*hb_buffer_reset) (
325        hb_buffer_t *buffer
326    );
327
328    void (*hb_buffer_guess_segment_properties) (
329        hb_buffer_t *buffer
330    );
331
332    hb_font_t * (*hb_font_create) (
333        hb_face_t *face
334    );
335
336    void (*hb_font_destroy) (
337        hb_font_t *font
338    );
339
340    void (*hb_font_set_scale) (
341        hb_font_t *font,
342        int        x_scale,
343        int        y_scale
344    );
345
346    void (*hb_ot_font_set_funcs) (
347        hb_font_t *font
348    );
349
350    unsigned int (*hb_face_get_upem) (
351        hb_face_t *face
352    );
353
354    const char ** (*hb_shape_list_shapers) (
355        void
356    );
357
358} hblib_state_info;
359
360static hblib_state_info hblib_state = {
361
362    .initialized                        = 0,
363    .padding                            = 0,
364
365    .hb_version_string                  = NULL,
366    .hb_blob_create                     = NULL,
367    .hb_blob_destroy                    = NULL,
368    .hb_face_create                     = NULL,
369    .hb_face_destroy                    = NULL,
370    .hb_language_from_string            = NULL,
371    .hb_buffer_set_language             = NULL,
372    .hb_script_from_string              = NULL,
373    .hb_buffer_set_script               = NULL,
374    .hb_direction_from_string           = NULL,
375    .hb_buffer_set_direction            = NULL,
376    .hb_feature_from_string             = NULL,
377    .hb_shape_full                      = NULL,
378    .hb_buffer_create                   = NULL,
379    .hb_buffer_destroy                  = NULL,
380    .hb_buffer_add_utf8                 = NULL,
381    .hb_buffer_add_utf32                = NULL,
382 /* .hb_buffer_add                      = NULL, */
383    .hb_buffer_get_length               = NULL,
384    .hb_buffer_get_glyph_infos          = NULL,
385    .hb_buffer_get_glyph_positions      = NULL,
386    .hb_buffer_reverse                  = NULL,
387    .hb_buffer_reset                    = NULL,
388    .hb_buffer_guess_segment_properties = NULL,
389    .hb_font_create                     = NULL,
390    .hb_font_destroy                    = NULL,
391    .hb_font_set_scale                  = NULL,
392    .hb_ot_font_set_funcs               = NULL,
393    .hb_face_get_upem                   = NULL,
394    .hb_shape_list_shapers              = NULL,
395
396};
397
398/* <boolean> = initialize(full_path_of_library) */
399
400static int hblib_initialize(lua_State * L)
401{
402    if (! hblib_state.initialized) {
403        const char *filename = lua_tostring(L, 1);
404        if (filename) {
405
406            lmt_library lib = lmt_library_load(filename);
407
408            hblib_state.hb_version_string                  = lmt_library_find(lib, "hb_version_string");
409            hblib_state.hb_language_from_string            = lmt_library_find(lib, "hb_language_from_string");
410            hblib_state.hb_script_from_string              = lmt_library_find(lib, "hb_script_from_string");
411            hblib_state.hb_direction_from_string           = lmt_library_find(lib, "hb_direction_from_string");
412            hblib_state.hb_feature_from_string             = lmt_library_find(lib, "hb_feature_from_string");
413
414            hblib_state.hb_buffer_set_language             = lmt_library_find(lib, "hb_buffer_set_language");
415            hblib_state.hb_buffer_set_script               = lmt_library_find(lib, "hb_buffer_set_script");
416            hblib_state.hb_buffer_set_direction            = lmt_library_find(lib, "hb_buffer_set_direction");
417
418            hblib_state.hb_buffer_create                   = lmt_library_find(lib, "hb_buffer_create");
419            hblib_state.hb_buffer_destroy                  = lmt_library_find(lib, "hb_buffer_destroy");
420            hblib_state.hb_buffer_reverse                  = lmt_library_find(lib, "hb_buffer_reverse");
421            hblib_state.hb_buffer_get_length               = lmt_library_find(lib, "hb_buffer_get_length");
422            hblib_state.hb_buffer_reset                    = lmt_library_find(lib, "hb_buffer_reset");
423            hblib_state.hb_buffer_add_utf8                 = lmt_library_find(lib, "hb_buffer_add_utf8");
424            hblib_state.hb_buffer_add_utf32                = lmt_library_find(lib, "hb_buffer_add_utf32");
425
426            hblib_state.hb_blob_create                     = lmt_library_find(lib, "hb_blob_create");
427            hblib_state.hb_blob_destroy                    = lmt_library_find(lib, "hb_blob_destroy");
428
429            hblib_state.hb_face_create                     = lmt_library_find(lib, "hb_face_create");
430            hblib_state.hb_face_destroy                    = lmt_library_find(lib, "hb_face_destroy");
431            hblib_state.hb_face_get_upem                   = lmt_library_find(lib, "hb_face_get_upem");
432
433            hblib_state.hb_font_create                     = lmt_library_find(lib, "hb_font_create");
434            hblib_state.hb_font_destroy                    = lmt_library_find(lib, "hb_font_destroy");
435            hblib_state.hb_font_set_scale                  = lmt_library_find(lib, "hb_font_set_scale");
436
437            hblib_state.hb_shape_list_shapers              = lmt_library_find(lib, "hb_shape_list_shapers");
438            hblib_state.hb_shape_full                      = lmt_library_find(lib, "hb_shape_full");
439
440            hblib_state.hb_ot_font_set_funcs               = lmt_library_find(lib, "hb_ot_font_set_funcs");
441
442            hblib_state.hb_buffer_guess_segment_properties = lmt_library_find(lib, "hb_buffer_guess_segment_properties");
443            hblib_state.hb_buffer_get_glyph_positions      = lmt_library_find(lib, "hb_buffer_get_glyph_positions");
444            hblib_state.hb_buffer_get_glyph_infos          = lmt_library_find(lib, "hb_buffer_get_glyph_infos");
445
446            hblib_state.initialized = lmt_library_okay(lib);
447        }
448    }
449    lua_pushboolean(L, hblib_state.initialized);
450    return 1;
451}
452
453/* <string> = getversion() */
454
455static int hblib_get_version(lua_State * L)
456{
457    if (hblib_state.initialized) {
458        lua_pushstring(L, hblib_state.hb_version_string());
459        return 1;
460    } else {
461        return 0;
462    }
463}
464
465/* <instance> = loadfont(identifier, fontdata) */
466
467static int hblib_load_font(lua_State * L)
468{
469    if (hblib_state.initialized) {
470        int id = (int) lua_tointeger(L, 1);
471        const char *str= lua_tostring(L, 2);
472        int size = (int) lua_rawlen(L, 2);
473        hb_blob_t *blob = hblib_state.hb_blob_create(str, size, 0, NULL, NULL);
474        hb_face_t *face = hblib_state.hb_face_create(blob, id);
475        unsigned int scale = hblib_state.hb_face_get_upem(face);
476        hb_font_t *font = hblib_state.hb_font_create(face);
477        hblib_data *data = lua_newuserdatauv(L, sizeof(data), 0);
478        hblib_state.hb_font_set_scale(font, scale, scale);
479        hblib_state.hb_ot_font_set_funcs(font);
480        data->font = font;
481        luaL_getmetatable(L, HBLIB_METATABLE);
482        lua_setmetatable(L, -2);
483        hblib_state.hb_blob_destroy(blob);
484        hblib_state.hb_face_destroy(face);
485        return 1;
486    } else {
487        return 0;
488    }
489}
490
491/* <table> = shapestring(instance, script, language, direction, { shapers }, { features }, text, reverse) */
492
493static int hblib_utf8len(const char *text, size_t size) /* todo: take from utilities */
494{
495    size_t ls = size;
496    int ind = 0;
497    int num = 0;
498    while (ind < (int) ls) {
499        unsigned char i = (unsigned char) *(text + ind);
500        if (i < 0x80) {
501            ind += 1;
502        } else if (i >= 0xF0) {
503            ind += 4;
504        } else if (i >= 0xE0) {
505            ind += 3;
506        } else if (i >= 0xC0) {
507            ind += 2;
508        } else {
509            ind += 1;
510        }
511        num += 1;
512    }
513    return num;
514}
515
516static int hblib_utf32len(const char *text, size_t size)
517{
518    /* not okay, hb doesn't stop at \0 */
519 /* (void) s; */
520 /* return (int) size / 4; */
521    /* so we do this instead */
522    size_t ls = size;
523    int ind = 0;
524    int num = 0;
525    while (ind < (int) ls) {
526        unsigned char i = (unsigned char) *(text + ind);
527        if (i) {
528            ind += 4;
529        } else {
530            break;
531        }
532        num += 1;
533    }
534    return num;
535}
536
537/*tex
538
539    Maybe with |utfbits == 0| take a table with code points, but then we might also need cluster
540    stuff, so there is no gain here.
541
542    I remember some issues with passing features (maybe because some defaults are always set) but
543    it's not really that important because one actually expects the library to handle them that way
544    (read: only enable additional ones). But I will look into it when needed.
545
546*/
547
548static int hblib_shape_string(lua_State * L)
549{
550    if (hblib_state.initialized) {
551        hblib_data *data = luaL_checkudata(L, 1, HBLIB_METATABLE);
552        if (data == NULL) {
553            lua_pushnil(L);
554        } else {
555            /* Maybe we can better take a table, so it's a yet undecided api. */
556            size_t          nofscript    = 0;
557            const char     *script       = lua_tolstring(L, 2, &nofscript);
558            size_t          noflanguage  = 0;
559            const char     *language     = lua_tolstring(L, 3, &noflanguage);
560            size_t          nofdirection = 0;
561            const char     *direction    = lua_tolstring(L, 4, &nofdirection);
562            int             nofshapers   = 0;
563            const char   * *shapers      = NULL; /* slot 5 */
564            int             noffeatures  = 0;
565            hb_feature_t   *features     = NULL; /* slot 6 */
566            size_t          noftext      = 0;
567            const char     *text         = lua_tolstring(L, 7, &noftext);
568            int             reverse      = lua_toboolean(L, 8);
569            int             utfbits      = (int) luaL_optinteger(L, 9, 8);
570            hb_buffer_t    *buffer       = NULL;
571            /*
572                Shapers are passed as a table; why not pass the length here too ... simpler in
573                ffi -) Maybe I'll make this more static: a general setshaper or so, which is
574                more natural than having it as argument to the shape function.
575
576                MSVC wants |char**| for the shapers and gcc wants |const char**| i.e.\ doesn't
577                like a cast so we just accept the less annoying MSVC warning.
578            */
579            if (lua_istable(L, 5)) {
580                lua_Unsigned n = lua_rawlen(L, 5);
581                if (n > 0) {
582                 // shapers = malloc((size_t) (n + 1) * sizeof(char *));
583                    shapers = calloc((size_t) (n + 1), sizeof(char *));
584                    if (shapers) {
585                        for (lua_Unsigned i = 0; i < n; i++) {
586                            lua_rawgeti(L, 5, i + 1);
587                            if (lua_isstring(L, -1)) {
588                                shapers[nofshapers] = lua_tostring(L, -1);
589                                nofshapers += 1;
590                            }
591                            lua_pop(L, 1);
592                        }
593                    } else {
594                        luaL_error(L, "optional hblib: unable to allocate shaper memory");
595                    }
596                    /* sentinal */
597                    shapers[nofshapers] = NULL;
598                }
599            }
600            /*
601                Features need to be converted to a table of features (manual work); simpler in
602                ffi -) Maybe I'll move this to the loadfont function.
603            */
604            if (lua_istable(L, 6)) {
605                lua_Unsigned n = lua_rawlen(L, 6);
606                if (n > 0) {
607                    features = malloc((size_t) n * sizeof(hb_feature_t));
608                    if (features) {
609                        for (lua_Unsigned i = 0; i < n; i++) {
610                            lua_rawgeti(L, 6, i + 1);
611                            if (lua_isstring(L, -1)) {
612                                size_t l = 0;
613                                const char *s = lua_tolstring(L, -1, &l);
614                                hblib_state.hb_feature_from_string(s, (int) l, &features[noffeatures]);
615                                noffeatures += 1;
616                            }
617                            lua_pop(L, 1);
618                        }
619                    } else {
620                        luaL_error(L, "optional hblib: unable to allocate feature memory");
621                    }
622                }
623            }
624            /* Some preparations (see original ffi variant). */
625            buffer = hblib_state.hb_buffer_create(); /* we could put this in the data blob */
626            /*
627                When using ffi we used to use utf32 plus some slack because utf8 crashed. It would
628                be more handy if we could pass an array of integers (maybe we can).
629            */
630            if (utfbits == 32) {
631                hblib_state.hb_buffer_add_utf32(buffer, text, (int) noftext, 0, hblib_utf32len(text, noftext));
632            } else { /* 8 */
633                hblib_state.hb_buffer_add_utf8(buffer, text, (int) noftext, 0, hblib_utf8len(text, noftext));
634            }
635            hblib_state.hb_buffer_set_language(buffer, hblib_state.hb_language_from_string(language, (int) noflanguage));
636            hblib_state.hb_buffer_set_script(buffer, hblib_state.hb_script_from_string(script, (int) nofscript));
637            hblib_state.hb_buffer_set_direction(buffer, hblib_state.hb_direction_from_string(direction, (int) nofdirection));
638            hblib_state.hb_buffer_guess_segment_properties(buffer);
639            /* Do it! */
640            hblib_state.hb_shape_full(data->font, buffer, features, noffeatures, shapers);
641            /* Fixup. */
642            if (reverse) {
643                hblib_state.hb_buffer_reverse(buffer);
644            }
645            /* Convert the result: plain and simple.*/
646            {
647                unsigned length = hblib_state.hb_buffer_get_length(buffer);
648                hb_glyph_info_t *infos = hblib_state.hb_buffer_get_glyph_infos(buffer, NULL);
649                hb_glyph_position_t *positions = hblib_state.hb_buffer_get_glyph_positions(buffer, NULL);
650                lua_createtable(L, length, 0);
651                for (unsigned i = 0; i < length; i++) {
652                    lua_createtable(L, 6, 0);
653                    lua_pushinteger(L, infos[i].codepoint);
654                    lua_rawseti(L, -2, 1);
655                    lua_pushinteger(L, infos[i].cluster);
656                    lua_rawseti(L, -2, 2);
657                    lua_pushinteger(L, positions[i].x_offset);
658                    lua_rawseti(L, -2, 3);
659                    lua_pushinteger(L, positions[i].y_offset);
660                    lua_rawseti(L, -2, 4);
661                    lua_pushinteger(L, positions[i].x_advance);
662                    lua_rawseti(L, -2, 5);
663                    lua_pushinteger(L, positions[i].y_advance);
664                    lua_rawseti(L, -2, 6);
665                    lua_rawseti(L, -2, (lua_Integer) i + 1);
666               }
667            }
668            /* 
669                We have a crash after many runs but why ... a printf("!") actually solves it. Do we need to free 
670                something more? SOmethign to so with utf32 (used to be utf8 issue).
671            */
672            hblib_state.hb_buffer_destroy(buffer);
673            free((void *) shapers); /* we didn't make copies of the lua strings, ms compiler gives warning */
674            free((void *) features);
675        }
676        return 1;
677    } else {
678        return 0;
679    }
680}
681
682/* <table> = getshapers() */
683
684static int hblib_get_shapers(lua_State * L)
685{
686    if (hblib_state.initialized) {
687        const char * *shapers = hblib_state.hb_shape_list_shapers();
688        if (shapers) {
689            int nofshapers = 0;
690            lua_createtable(L, 1, 0);
691            while (1) {
692                const char *s = shapers[nofshapers];
693                if (s) {
694                    nofshapers++;
695                    lua_pushstring(L, s);
696                    lua_rawseti(L, -2, nofshapers);
697                } else {
698                    break;
699                }
700            }
701            return 1;
702        }
703    }
704    return 0;
705}
706
707/* private */
708
709static int hblib_free(lua_State * L)
710{
711    if (hblib_state.initialized) {
712        hblib_data *data = luaL_checkudata(L, 1, HBLIB_METATABLE);
713        if (data) {
714            hblib_state.hb_font_destroy(data->font);
715        }
716    }
717    return 0;
718}
719
720/* <string> = tostring(instance) */
721
722static int hblib_tostring(lua_State * L)
723{
724    if (hblib_state.initialized) {
725        hblib_data *data = luaL_checkudata(L, 1, HBLIB_METATABLE);
726        if (data) {
727            lua_pushfstring(L, "<optional.hblib.instance %p>", data);
728        } else {
729            lua_pushnil(L);
730        }
731        return 1;
732    } else {
733        return 0;
734    }
735}
736
737/*tex We can do with a rather mimimal user data object. */
738
739static const struct luaL_Reg hblib_metatable[] = {
740    { "__tostring", hblib_tostring },
741    { "__gc",       hblib_free     },
742    { NULL,         NULL           },
743};
744
745/*tex
746
747    Idem, just the collected calls of the ffi variant. The less the better because that way there
748    is no tricky code needed at the \LUA\ end.
749
750*/
751
752static struct luaL_Reg hblib_function_list[] = {
753    { "initialize",  hblib_initialize   },
754    { "getversion",  hblib_get_version  },
755    { "getshapers",  hblib_get_shapers  },
756    { "loadfont",    hblib_load_font    },
757    { "shapestring", hblib_shape_string },
758    { NULL,          NULL               },
759};
760
761int luaopen_hb(lua_State *L)
762{
763    luaL_newmetatable(L, HBLIB_METATABLE);
764    luaL_setfuncs(L, hblib_metatable, 0);
765    lmt_library_register(L, "hb", hblib_function_list);
766    return 0;
767}
768