1 | /**************************************************************************** |
2 | * |
3 | * afshaper.c |
4 | * |
5 | * HarfBuzz interface for accessing OpenType features (body). |
6 | * |
7 | * Copyright (C) 2013-2023 by |
8 | * David Turner, Robert Wilhelm, and Werner Lemberg. |
9 | * |
10 | * This file is part of the FreeType project, and may only be used, |
11 | * modified, and distributed under the terms of the FreeType project |
12 | * license, LICENSE.TXT. By continuing to use, modify, or distribute |
13 | * this file you indicate that you have read the license and |
14 | * understand and accept it fully. |
15 | * |
16 | */ |
17 | |
18 | |
19 | #include <freetype/freetype.h> |
20 | #include <freetype/ftadvanc.h> |
21 | #include "afglobal.h" |
22 | #include "aftypes.h" |
23 | #include "afshaper.h" |
24 | |
25 | #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ |
26 | |
27 | |
28 | /************************************************************************** |
29 | * |
30 | * The macro FT_COMPONENT is used in trace mode. It is an implicit |
31 | * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log |
32 | * messages during execution. |
33 | */ |
34 | #undef FT_COMPONENT |
35 | #define FT_COMPONENT afshaper |
36 | |
37 | |
38 | /* |
39 | * We use `sets' (in the HarfBuzz sense, which comes quite near to the |
40 | * usual mathematical meaning) to manage both lookups and glyph indices. |
41 | * |
42 | * 1. For each coverage, collect lookup IDs in a set. Note that an |
43 | * auto-hinter `coverage' is represented by one `feature', and a |
44 | * feature consists of an arbitrary number of (font specific) `lookup's |
45 | * that actually do the mapping job. Please check the OpenType |
46 | * specification for more details on features and lookups. |
47 | * |
48 | * 2. Create glyph ID sets from the corresponding lookup sets. |
49 | * |
50 | * 3. The glyph set corresponding to AF_COVERAGE_DEFAULT is computed |
51 | * with all lookups specific to the OpenType script activated. It |
52 | * relies on the order of AF_DEFINE_STYLE_CLASS entries so that |
53 | * special coverages (like `oldstyle figures') don't get overwritten. |
54 | * |
55 | */ |
56 | |
57 | |
58 | /* load coverage tags */ |
59 | #undef COVERAGE |
60 | #define COVERAGE( name, NAME, description, \ |
61 | tag1, tag2, tag3, tag4 ) \ |
62 | static const hb_tag_t name ## _coverage[] = \ |
63 | { \ |
64 | HB_TAG( tag1, tag2, tag3, tag4 ), \ |
65 | HB_TAG_NONE \ |
66 | }; |
67 | |
68 | |
69 | #include "afcover.h" |
70 | |
71 | |
72 | /* define mapping between coverage tags and AF_Coverage */ |
73 | #undef COVERAGE |
74 | #define COVERAGE( name, NAME, description, \ |
75 | tag1, tag2, tag3, tag4 ) \ |
76 | name ## _coverage, |
77 | |
78 | |
79 | static const hb_tag_t* coverages[] = |
80 | { |
81 | #include "afcover.h" |
82 | |
83 | NULL /* AF_COVERAGE_DEFAULT */ |
84 | }; |
85 | |
86 | |
87 | /* load HarfBuzz script tags */ |
88 | #undef SCRIPT |
89 | #define SCRIPT( s, S, d, h, H, ss ) h, |
90 | |
91 | |
92 | static const hb_script_t scripts[] = |
93 | { |
94 | #include "afscript.h" |
95 | }; |
96 | |
97 | |
98 | FT_Error |
99 | af_shaper_get_coverage( AF_FaceGlobals globals, |
100 | AF_StyleClass style_class, |
101 | FT_UShort* gstyles, |
102 | FT_Bool default_script ) |
103 | { |
104 | hb_face_t* face; |
105 | |
106 | hb_set_t* gsub_lookups = NULL; /* GSUB lookups for a given script */ |
107 | hb_set_t* gsub_glyphs = NULL; /* glyphs covered by GSUB lookups */ |
108 | hb_set_t* gpos_lookups = NULL; /* GPOS lookups for a given script */ |
109 | hb_set_t* gpos_glyphs = NULL; /* glyphs covered by GPOS lookups */ |
110 | |
111 | hb_script_t script; |
112 | const hb_tag_t* coverage_tags; |
113 | hb_tag_t script_tags[] = { HB_TAG_NONE, |
114 | HB_TAG_NONE, |
115 | HB_TAG_NONE, |
116 | HB_TAG_NONE }; |
117 | |
118 | hb_codepoint_t idx; |
119 | #ifdef FT_DEBUG_LEVEL_TRACE |
120 | int count; |
121 | #endif |
122 | |
123 | |
124 | if ( !globals || !style_class || !gstyles ) |
125 | return FT_THROW( Invalid_Argument ); |
126 | |
127 | face = hb_font_get_face( globals->hb_font ); |
128 | |
129 | coverage_tags = coverages[style_class->coverage]; |
130 | script = scripts[style_class->script]; |
131 | |
132 | /* Convert a HarfBuzz script tag into the corresponding OpenType */ |
133 | /* tag or tags -- some Indic scripts like Devanagari have an old */ |
134 | /* and a new set of features. */ |
135 | { |
136 | unsigned int tags_count = 3; |
137 | hb_tag_t tags[3]; |
138 | |
139 | |
140 | hb_ot_tags_from_script_and_language( script, |
141 | HB_LANGUAGE_INVALID, |
142 | &tags_count, |
143 | tags, |
144 | NULL, |
145 | NULL ); |
146 | script_tags[0] = tags_count > 0 ? tags[0] : HB_TAG_NONE; |
147 | script_tags[1] = tags_count > 1 ? tags[1] : HB_TAG_NONE; |
148 | script_tags[2] = tags_count > 2 ? tags[2] : HB_TAG_NONE; |
149 | } |
150 | |
151 | /* If the second tag is HB_OT_TAG_DEFAULT_SCRIPT, change that to */ |
152 | /* HB_TAG_NONE except for the default script. */ |
153 | if ( default_script ) |
154 | { |
155 | if ( script_tags[0] == HB_TAG_NONE ) |
156 | script_tags[0] = HB_OT_TAG_DEFAULT_SCRIPT; |
157 | else |
158 | { |
159 | if ( script_tags[1] == HB_TAG_NONE ) |
160 | script_tags[1] = HB_OT_TAG_DEFAULT_SCRIPT; |
161 | else if ( script_tags[1] != HB_OT_TAG_DEFAULT_SCRIPT ) |
162 | script_tags[2] = HB_OT_TAG_DEFAULT_SCRIPT; |
163 | } |
164 | } |
165 | else |
166 | { |
167 | /* we use non-standard tags like `khms' for special purposes; */ |
168 | /* HarfBuzz maps them to `DFLT', which we don't want to handle here */ |
169 | if ( script_tags[0] == HB_OT_TAG_DEFAULT_SCRIPT ) |
170 | goto Exit; |
171 | } |
172 | |
173 | gsub_lookups = hb_set_create(); |
174 | hb_ot_layout_collect_lookups( face, |
175 | HB_OT_TAG_GSUB, |
176 | script_tags, |
177 | NULL, |
178 | coverage_tags, |
179 | gsub_lookups ); |
180 | |
181 | if ( hb_set_is_empty( gsub_lookups ) ) |
182 | goto Exit; /* nothing to do */ |
183 | |
184 | FT_TRACE4(( "GSUB lookups (style `%s'):\n" , |
185 | af_style_names[style_class->style] )); |
186 | FT_TRACE4(( " " )); |
187 | |
188 | #ifdef FT_DEBUG_LEVEL_TRACE |
189 | count = 0; |
190 | #endif |
191 | |
192 | gsub_glyphs = hb_set_create(); |
193 | for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_lookups, &idx ); ) |
194 | { |
195 | #ifdef FT_DEBUG_LEVEL_TRACE |
196 | FT_TRACE4(( " %d" , idx )); |
197 | count++; |
198 | #endif |
199 | |
200 | /* get output coverage of GSUB feature */ |
201 | hb_ot_layout_lookup_collect_glyphs( face, |
202 | HB_OT_TAG_GSUB, |
203 | idx, |
204 | NULL, |
205 | NULL, |
206 | NULL, |
207 | gsub_glyphs ); |
208 | } |
209 | |
210 | #ifdef FT_DEBUG_LEVEL_TRACE |
211 | if ( !count ) |
212 | FT_TRACE4(( " (none)" )); |
213 | FT_TRACE4(( "\n" )); |
214 | FT_TRACE4(( "\n" )); |
215 | #endif |
216 | |
217 | FT_TRACE4(( "GPOS lookups (style `%s'):\n" , |
218 | af_style_names[style_class->style] )); |
219 | FT_TRACE4(( " " )); |
220 | |
221 | gpos_lookups = hb_set_create(); |
222 | hb_ot_layout_collect_lookups( face, |
223 | HB_OT_TAG_GPOS, |
224 | script_tags, |
225 | NULL, |
226 | coverage_tags, |
227 | gpos_lookups ); |
228 | |
229 | #ifdef FT_DEBUG_LEVEL_TRACE |
230 | count = 0; |
231 | #endif |
232 | |
233 | gpos_glyphs = hb_set_create(); |
234 | for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gpos_lookups, &idx ); ) |
235 | { |
236 | #ifdef FT_DEBUG_LEVEL_TRACE |
237 | FT_TRACE4(( " %d" , idx )); |
238 | count++; |
239 | #endif |
240 | |
241 | /* get input coverage of GPOS feature */ |
242 | hb_ot_layout_lookup_collect_glyphs( face, |
243 | HB_OT_TAG_GPOS, |
244 | idx, |
245 | NULL, |
246 | gpos_glyphs, |
247 | NULL, |
248 | NULL ); |
249 | } |
250 | |
251 | #ifdef FT_DEBUG_LEVEL_TRACE |
252 | if ( !count ) |
253 | FT_TRACE4(( " (none)" )); |
254 | FT_TRACE4(( "\n" )); |
255 | FT_TRACE4(( "\n" )); |
256 | #endif |
257 | |
258 | /* |
259 | * We now check whether we can construct blue zones, using glyphs |
260 | * covered by the feature only. In case there is not a single zone |
261 | * (that is, not a single character is covered), we skip this coverage. |
262 | * |
263 | */ |
264 | if ( style_class->coverage != AF_COVERAGE_DEFAULT ) |
265 | { |
266 | AF_Blue_Stringset bss = style_class->blue_stringset; |
267 | const AF_Blue_StringRec* bs = &af_blue_stringsets[bss]; |
268 | |
269 | FT_Bool found = 0; |
270 | |
271 | |
272 | for ( ; bs->string != AF_BLUE_STRING_MAX; bs++ ) |
273 | { |
274 | const char* p = &af_blue_strings[bs->string]; |
275 | |
276 | |
277 | while ( *p ) |
278 | { |
279 | hb_codepoint_t ch; |
280 | |
281 | |
282 | GET_UTF8_CHAR( ch, p ); |
283 | |
284 | for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_lookups, |
285 | &idx ); ) |
286 | { |
287 | hb_codepoint_t gidx = FT_Get_Char_Index( globals->face, ch ); |
288 | |
289 | |
290 | if ( hb_ot_layout_lookup_would_substitute( face, idx, |
291 | &gidx, 1, 1 ) ) |
292 | { |
293 | found = 1; |
294 | break; |
295 | } |
296 | } |
297 | } |
298 | } |
299 | |
300 | if ( !found ) |
301 | { |
302 | FT_TRACE4(( " no blue characters found; style skipped\n" )); |
303 | goto Exit; |
304 | } |
305 | } |
306 | |
307 | /* |
308 | * Various OpenType features might use the same glyphs at different |
309 | * vertical positions; for example, superscript and subscript glyphs |
310 | * could be the same. However, the auto-hinter is completely |
311 | * agnostic of OpenType features after the feature analysis has been |
312 | * completed: The engine then simply receives a glyph index and returns a |
313 | * hinted and usually rendered glyph. |
314 | * |
315 | * Consider the superscript feature of font `pala.ttf': Some of the |
316 | * glyphs are `real', that is, they have a zero vertical offset, but |
317 | * most of them are small caps glyphs shifted up to the superscript |
318 | * position (that is, the `sups' feature is present in both the GSUB and |
319 | * GPOS tables). The code for blue zones computation actually uses a |
320 | * feature's y offset so that the `real' glyphs get correct hints. But |
321 | * later on it is impossible to decide whether a glyph index belongs to, |
322 | * say, the small caps or superscript feature. |
323 | * |
324 | * For this reason, we don't assign a style to a glyph if the current |
325 | * feature covers the glyph in both the GSUB and the GPOS tables. This |
326 | * is quite a broad condition, assuming that |
327 | * |
328 | * (a) glyphs that get used in multiple features are present in a |
329 | * feature without vertical shift, |
330 | * |
331 | * and |
332 | * |
333 | * (b) a feature's GPOS data really moves the glyph vertically. |
334 | * |
335 | * Not fulfilling condition (a) makes a font larger; it would also |
336 | * reduce the number of glyphs that could be addressed directly without |
337 | * using OpenType features, so this assumption is rather strong. |
338 | * |
339 | * Condition (b) is much weaker, and there might be glyphs which get |
340 | * missed. However, the OpenType features we are going to handle are |
341 | * primarily located in GSUB, and HarfBuzz doesn't provide an API to |
342 | * directly get the necessary information from the GPOS table. A |
343 | * possible solution might be to directly parse the GPOS table to find |
344 | * out whether a glyph gets shifted vertically, but this is something I |
345 | * would like to avoid if not really necessary. |
346 | * |
347 | * Note that we don't follow this logic for the default coverage. |
348 | * Complex scripts like Devanagari have mandatory GPOS features to |
349 | * position many glyph elements, using mark-to-base or mark-to-ligature |
350 | * tables; the number of glyphs missed due to condition (b) would be far |
351 | * too large. |
352 | * |
353 | */ |
354 | if ( style_class->coverage != AF_COVERAGE_DEFAULT ) |
355 | hb_set_subtract( gsub_glyphs, gpos_glyphs ); |
356 | |
357 | #ifdef FT_DEBUG_LEVEL_TRACE |
358 | FT_TRACE4(( " glyphs without GPOS data (`*' means already assigned)" )); |
359 | count = 0; |
360 | #endif |
361 | |
362 | for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_glyphs, &idx ); ) |
363 | { |
364 | #ifdef FT_DEBUG_LEVEL_TRACE |
365 | if ( !( count % 10 ) ) |
366 | { |
367 | FT_TRACE4(( "\n" )); |
368 | FT_TRACE4(( " " )); |
369 | } |
370 | |
371 | FT_TRACE4(( " %d" , idx )); |
372 | count++; |
373 | #endif |
374 | |
375 | /* glyph indices returned by `hb_ot_layout_lookup_collect_glyphs' */ |
376 | /* can be arbitrary: some fonts use fake indices for processing */ |
377 | /* internal to GSUB or GPOS, which is fully valid */ |
378 | if ( idx >= (hb_codepoint_t)globals->glyph_count ) |
379 | continue; |
380 | |
381 | if ( gstyles[idx] == AF_STYLE_UNASSIGNED ) |
382 | gstyles[idx] = (FT_UShort)style_class->style; |
383 | #ifdef FT_DEBUG_LEVEL_TRACE |
384 | else |
385 | FT_TRACE4(( "*" )); |
386 | #endif |
387 | } |
388 | |
389 | #ifdef FT_DEBUG_LEVEL_TRACE |
390 | if ( !count ) |
391 | { |
392 | FT_TRACE4(( "\n" )); |
393 | FT_TRACE4(( " (none)" )); |
394 | } |
395 | FT_TRACE4(( "\n" )); |
396 | FT_TRACE4(( "\n" )); |
397 | #endif |
398 | |
399 | Exit: |
400 | hb_set_destroy( gsub_lookups ); |
401 | hb_set_destroy( gsub_glyphs ); |
402 | hb_set_destroy( gpos_lookups ); |
403 | hb_set_destroy( gpos_glyphs ); |
404 | |
405 | return FT_Err_Ok; |
406 | } |
407 | |
408 | |
409 | /* construct HarfBuzz features */ |
410 | #undef COVERAGE |
411 | #define COVERAGE( name, NAME, description, \ |
412 | tag1, tag2, tag3, tag4 ) \ |
413 | static const hb_feature_t name ## _feature[] = \ |
414 | { \ |
415 | { \ |
416 | HB_TAG( tag1, tag2, tag3, tag4 ), \ |
417 | 1, 0, (unsigned int)-1 \ |
418 | } \ |
419 | }; |
420 | |
421 | |
422 | #include "afcover.h" |
423 | |
424 | |
425 | /* define mapping between HarfBuzz features and AF_Coverage */ |
426 | #undef COVERAGE |
427 | #define COVERAGE( name, NAME, description, \ |
428 | tag1, tag2, tag3, tag4 ) \ |
429 | name ## _feature, |
430 | |
431 | |
432 | static const hb_feature_t* features[] = |
433 | { |
434 | #include "afcover.h" |
435 | |
436 | NULL /* AF_COVERAGE_DEFAULT */ |
437 | }; |
438 | |
439 | |
440 | void* |
441 | af_shaper_buf_create( FT_Face face ) |
442 | { |
443 | FT_UNUSED( face ); |
444 | |
445 | return (void*)hb_buffer_create(); |
446 | } |
447 | |
448 | |
449 | void |
450 | af_shaper_buf_destroy( FT_Face face, |
451 | void* buf ) |
452 | { |
453 | FT_UNUSED( face ); |
454 | |
455 | hb_buffer_destroy( (hb_buffer_t*)buf ); |
456 | } |
457 | |
458 | |
459 | const char* |
460 | af_shaper_get_cluster( const char* p, |
461 | AF_StyleMetrics metrics, |
462 | void* buf_, |
463 | unsigned int* count ) |
464 | { |
465 | AF_StyleClass style_class; |
466 | const hb_feature_t* feature; |
467 | FT_Int upem; |
468 | const char* q; |
469 | int len; |
470 | |
471 | hb_buffer_t* buf = (hb_buffer_t*)buf_; |
472 | hb_font_t* font; |
473 | hb_codepoint_t dummy; |
474 | |
475 | |
476 | upem = (FT_Int)metrics->globals->face->units_per_EM; |
477 | style_class = metrics->style_class; |
478 | feature = features[style_class->coverage]; |
479 | |
480 | font = metrics->globals->hb_font; |
481 | |
482 | /* we shape at a size of units per EM; this means font units */ |
483 | hb_font_set_scale( font, upem, upem ); |
484 | |
485 | while ( *p == ' ' ) |
486 | p++; |
487 | |
488 | /* count bytes up to next space (or end of buffer) */ |
489 | q = p; |
490 | while ( !( *q == ' ' || *q == '\0' ) ) |
491 | GET_UTF8_CHAR( dummy, q ); |
492 | len = (int)( q - p ); |
493 | |
494 | /* feed character(s) to the HarfBuzz buffer */ |
495 | hb_buffer_clear_contents( buf ); |
496 | hb_buffer_add_utf8( buf, p, len, 0, len ); |
497 | |
498 | /* we let HarfBuzz guess the script and writing direction */ |
499 | hb_buffer_guess_segment_properties( buf ); |
500 | |
501 | /* shape buffer, which means conversion from character codes to */ |
502 | /* glyph indices, possibly applying a feature */ |
503 | hb_shape( font, buf, feature, feature ? 1 : 0 ); |
504 | |
505 | if ( feature ) |
506 | { |
507 | hb_buffer_t* hb_buf = metrics->globals->hb_buf; |
508 | |
509 | unsigned int gcount; |
510 | hb_glyph_info_t* ginfo; |
511 | |
512 | unsigned int hb_gcount; |
513 | hb_glyph_info_t* hb_ginfo; |
514 | |
515 | |
516 | /* we have to check whether applying a feature does actually change */ |
517 | /* glyph indices; otherwise the affected glyph or glyphs aren't */ |
518 | /* available at all in the feature */ |
519 | |
520 | hb_buffer_clear_contents( hb_buf ); |
521 | hb_buffer_add_utf8( hb_buf, p, len, 0, len ); |
522 | hb_buffer_guess_segment_properties( hb_buf ); |
523 | hb_shape( font, hb_buf, NULL, 0 ); |
524 | |
525 | ginfo = hb_buffer_get_glyph_infos( buf, &gcount ); |
526 | hb_ginfo = hb_buffer_get_glyph_infos( hb_buf, &hb_gcount ); |
527 | |
528 | if ( gcount == hb_gcount ) |
529 | { |
530 | unsigned int i; |
531 | |
532 | |
533 | for (i = 0; i < gcount; i++ ) |
534 | if ( ginfo[i].codepoint != hb_ginfo[i].codepoint ) |
535 | break; |
536 | |
537 | if ( i == gcount ) |
538 | { |
539 | /* both buffers have identical glyph indices */ |
540 | hb_buffer_clear_contents( buf ); |
541 | } |
542 | } |
543 | } |
544 | |
545 | *count = hb_buffer_get_length( buf ); |
546 | |
547 | #ifdef FT_DEBUG_LEVEL_TRACE |
548 | if ( feature && *count > 1 ) |
549 | FT_TRACE1(( "af_shaper_get_cluster:" |
550 | " input character mapped to multiple glyphs\n" )); |
551 | #endif |
552 | |
553 | return q; |
554 | } |
555 | |
556 | |
557 | FT_ULong |
558 | af_shaper_get_elem( AF_StyleMetrics metrics, |
559 | void* buf_, |
560 | unsigned int idx, |
561 | FT_Long* advance, |
562 | FT_Long* y_offset ) |
563 | { |
564 | hb_buffer_t* buf = (hb_buffer_t*)buf_; |
565 | hb_glyph_info_t* ginfo; |
566 | hb_glyph_position_t* gpos; |
567 | unsigned int gcount; |
568 | |
569 | FT_UNUSED( metrics ); |
570 | |
571 | |
572 | ginfo = hb_buffer_get_glyph_infos( buf, &gcount ); |
573 | gpos = hb_buffer_get_glyph_positions( buf, &gcount ); |
574 | |
575 | if ( idx >= gcount ) |
576 | return 0; |
577 | |
578 | if ( advance ) |
579 | *advance = gpos[idx].x_advance; |
580 | if ( y_offset ) |
581 | *y_offset = gpos[idx].y_offset; |
582 | |
583 | return ginfo[idx].codepoint; |
584 | } |
585 | |
586 | |
587 | #else /* !FT_CONFIG_OPTION_USE_HARFBUZZ */ |
588 | |
589 | |
590 | FT_Error |
591 | af_shaper_get_coverage( AF_FaceGlobals globals, |
592 | AF_StyleClass style_class, |
593 | FT_UShort* gstyles, |
594 | FT_Bool default_script ) |
595 | { |
596 | FT_UNUSED( globals ); |
597 | FT_UNUSED( style_class ); |
598 | FT_UNUSED( gstyles ); |
599 | FT_UNUSED( default_script ); |
600 | |
601 | return FT_Err_Ok; |
602 | } |
603 | |
604 | |
605 | void* |
606 | af_shaper_buf_create( FT_Face face ) |
607 | { |
608 | FT_UNUSED( face ); |
609 | |
610 | return NULL; |
611 | } |
612 | |
613 | |
614 | void |
615 | af_shaper_buf_destroy( FT_Face face, |
616 | void* buf ) |
617 | { |
618 | FT_UNUSED( face ); |
619 | FT_UNUSED( buf ); |
620 | } |
621 | |
622 | |
623 | const char* |
624 | af_shaper_get_cluster( const char* p, |
625 | AF_StyleMetrics metrics, |
626 | void* buf_, |
627 | unsigned int* count ) |
628 | { |
629 | FT_Face face = metrics->globals->face; |
630 | FT_ULong ch, dummy = 0; |
631 | FT_ULong* buf = (FT_ULong*)buf_; |
632 | |
633 | |
634 | while ( *p == ' ' ) |
635 | p++; |
636 | |
637 | GET_UTF8_CHAR( ch, p ); |
638 | |
639 | /* since we don't have an engine to handle clusters, */ |
640 | /* we scan the characters but return zero */ |
641 | while ( !( *p == ' ' || *p == '\0' ) ) |
642 | GET_UTF8_CHAR( dummy, p ); |
643 | |
644 | if ( dummy ) |
645 | { |
646 | *buf = 0; |
647 | *count = 0; |
648 | } |
649 | else |
650 | { |
651 | *buf = FT_Get_Char_Index( face, ch ); |
652 | *count = 1; |
653 | } |
654 | |
655 | return p; |
656 | } |
657 | |
658 | |
659 | FT_ULong |
660 | af_shaper_get_elem( AF_StyleMetrics metrics, |
661 | void* buf_, |
662 | unsigned int idx, |
663 | FT_Long* advance, |
664 | FT_Long* y_offset ) |
665 | { |
666 | FT_Face face = metrics->globals->face; |
667 | FT_ULong glyph_index = *(FT_ULong*)buf_; |
668 | |
669 | FT_UNUSED( idx ); |
670 | |
671 | |
672 | if ( advance ) |
673 | FT_Get_Advance( face, |
674 | glyph_index, |
675 | FT_LOAD_NO_SCALE | |
676 | FT_LOAD_NO_HINTING | |
677 | FT_LOAD_IGNORE_TRANSFORM, |
678 | advance ); |
679 | |
680 | if ( y_offset ) |
681 | *y_offset = 0; |
682 | |
683 | return glyph_index; |
684 | } |
685 | |
686 | |
687 | #endif /* !FT_CONFIG_OPTION_USE_HARFBUZZ */ |
688 | |
689 | |
690 | /* END */ |
691 | |