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