tizen 2.3.1 release
[framework/graphics/freetype.git] / src / autofit / afcjk.c
1 /***************************************************************************/
2 /*                                                                         */
3 /*  afcjk.c                                                                */
4 /*                                                                         */
5 /*    Auto-fitter hinting routines for CJK writing system (body).          */
6 /*                                                                         */
7 /*  Copyright 2006-2014 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    *  The algorithm is based on akito's autohint patch, available here:
20    *
21    *  http://www.kde.gr.jp/~akito/patch/freetype2/
22    *
23    */
24
25 #include <ft2build.h>
26 #include FT_ADVANCES_H
27 #include FT_INTERNAL_DEBUG_H
28
29 #include "afglobal.h"
30 #include "afpic.h"
31 #include "aflatin.h"
32
33
34 #ifdef AF_CONFIG_OPTION_CJK
35
36 #undef AF_CONFIG_OPTION_CJK_BLUE_HANI_VERT
37
38 #include "afcjk.h"
39 #include "aferrors.h"
40
41
42 #ifdef AF_CONFIG_OPTION_USE_WARPER
43 #include "afwarp.h"
44 #endif
45
46
47   /*************************************************************************/
48   /*                                                                       */
49   /* The macro FT_COMPONENT is used in trace mode.  It is an implicit      */
50   /* parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log  */
51   /* messages during execution.                                            */
52   /*                                                                       */
53 #undef  FT_COMPONENT
54 #define FT_COMPONENT  trace_afcjk
55
56
57   /*************************************************************************/
58   /*************************************************************************/
59   /*****                                                               *****/
60   /*****              C J K   G L O B A L   M E T R I C S              *****/
61   /*****                                                               *****/
62   /*************************************************************************/
63   /*************************************************************************/
64
65
66   /* Basically the Latin version with AF_CJKMetrics */
67   /* to replace AF_LatinMetrics.                    */
68
69   FT_LOCAL_DEF( void )
70   af_cjk_metrics_init_widths( AF_CJKMetrics  metrics,
71                               FT_Face        face )
72   {
73     /* scan the array of segments in each direction */
74     AF_GlyphHintsRec  hints[1];
75
76
77     FT_TRACE5(( "\n"
78                 "cjk standard widths computation (style `%s')\n"
79                 "===================================================\n"
80                 "\n",
81                 af_style_names[metrics->root.style_class->style] ));
82
83     af_glyph_hints_init( hints, face->memory );
84
85     metrics->axis[AF_DIMENSION_HORZ].width_count = 0;
86     metrics->axis[AF_DIMENSION_VERT].width_count = 0;
87
88     {
89       FT_Error          error;
90       FT_ULong          glyph_index;
91       FT_Long           y_offset;
92       int               dim;
93       AF_CJKMetricsRec  dummy[1];
94       AF_Scaler         scaler = &dummy->root.scaler;
95
96 #ifdef FT_CONFIG_OPTION_PIC
97       AF_FaceGlobals  globals = metrics->root.globals;
98 #endif
99
100       AF_StyleClass   style_class  = metrics->root.style_class;
101       AF_ScriptClass  script_class = AF_SCRIPT_CLASSES_GET
102                                        [style_class->script];
103
104       FT_UInt32  standard_char;
105
106
107       standard_char = script_class->standard_char1;
108       af_get_char_index( &metrics->root,
109                          standard_char,
110                          &glyph_index,
111                          &y_offset );
112       if ( !glyph_index )
113       {
114         if ( script_class->standard_char2 )
115         {
116           standard_char = script_class->standard_char2;
117           af_get_char_index( &metrics->root,
118                              standard_char,
119                              &glyph_index,
120                              &y_offset );
121           if ( !glyph_index )
122           {
123             if ( script_class->standard_char3 )
124             {
125               standard_char = script_class->standard_char3;
126               af_get_char_index( &metrics->root,
127                                  standard_char,
128                                  &glyph_index,
129                                  &y_offset );
130               if ( !glyph_index )
131                 goto Exit;
132             }
133             else
134               goto Exit;
135           }
136         }
137         else
138           goto Exit;
139       }
140
141       FT_TRACE5(( "standard character: U+%04lX (glyph index %d)\n",
142                   standard_char, glyph_index ));
143
144       error = FT_Load_Glyph( face, glyph_index, FT_LOAD_NO_SCALE );
145       if ( error || face->glyph->outline.n_points <= 0 )
146         goto Exit;
147
148       FT_ZERO( dummy );
149
150       dummy->units_per_em = metrics->units_per_em;
151
152       scaler->x_scale = 0x10000L;
153       scaler->y_scale = 0x10000L;
154       scaler->x_delta = 0;
155       scaler->y_delta = 0;
156
157       scaler->face        = face;
158       scaler->render_mode = FT_RENDER_MODE_NORMAL;
159       scaler->flags       = 0;
160
161       af_glyph_hints_rescale( hints, (AF_StyleMetrics)dummy );
162
163       error = af_glyph_hints_reload( hints, &face->glyph->outline );
164       if ( error )
165         goto Exit;
166
167       for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
168       {
169         AF_CJKAxis    axis    = &metrics->axis[dim];
170         AF_AxisHints  axhints = &hints->axis[dim];
171         AF_Segment    seg, limit, link;
172         FT_UInt       num_widths = 0;
173
174
175         error = af_latin_hints_compute_segments( hints,
176                                                  (AF_Dimension)dim );
177         if ( error )
178           goto Exit;
179
180         af_latin_hints_link_segments( hints,
181                                       0,
182                                       NULL,
183                                       (AF_Dimension)dim );
184
185         seg   = axhints->segments;
186         limit = seg + axhints->num_segments;
187
188         for ( ; seg < limit; seg++ )
189         {
190           link = seg->link;
191
192           /* we only consider stem segments there! */
193           if ( link && link->link == seg && link > seg )
194           {
195             FT_Pos  dist;
196
197
198             dist = seg->pos - link->pos;
199             if ( dist < 0 )
200               dist = -dist;
201
202             if ( num_widths < AF_CJK_MAX_WIDTHS )
203               axis->widths[num_widths++].org = dist;
204           }
205         }
206
207         /* this also replaces multiple almost identical stem widths */
208         /* with a single one (the value 100 is heuristic)           */
209         af_sort_and_quantize_widths( &num_widths, axis->widths,
210                                      dummy->units_per_em / 100 );
211         axis->width_count = num_widths;
212       }
213
214     Exit:
215       for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
216       {
217         AF_CJKAxis  axis = &metrics->axis[dim];
218         FT_Pos      stdw;
219
220
221         stdw = ( axis->width_count > 0 ) ? axis->widths[0].org
222                                          : AF_LATIN_CONSTANT( metrics, 50 );
223
224         /* let's try 20% of the smallest width */
225         axis->edge_distance_threshold = stdw / 5;
226         axis->standard_width          = stdw;
227         axis->extra_light             = 0;
228
229 #ifdef FT_DEBUG_LEVEL_TRACE
230         {
231           FT_UInt  i;
232
233
234           FT_TRACE5(( "%s widths:\n",
235                       dim == AF_DIMENSION_VERT ? "horizontal"
236                                                : "vertical" ));
237
238           FT_TRACE5(( "  %d (standard)", axis->standard_width ));
239           for ( i = 1; i < axis->width_count; i++ )
240             FT_TRACE5(( " %d", axis->widths[i].org ));
241
242           FT_TRACE5(( "\n" ));
243         }
244 #endif
245       }
246     }
247
248     FT_TRACE5(( "\n" ));
249
250     af_glyph_hints_done( hints );
251   }
252
253
254   /* Find all blue zones. */
255
256   static void
257   af_cjk_metrics_init_blues( AF_CJKMetrics  metrics,
258                              FT_Face        face )
259   {
260     FT_Pos      fills[AF_BLUE_STRING_MAX_LEN];
261     FT_Pos      flats[AF_BLUE_STRING_MAX_LEN];
262
263     FT_Int      num_fills;
264     FT_Int      num_flats;
265
266     FT_Bool     fill;
267
268     AF_CJKBlue  blue;
269     FT_Error    error;
270     AF_CJKAxis  axis;
271     FT_Outline  outline;
272
273     AF_StyleClass  sc = metrics->root.style_class;
274
275     AF_Blue_Stringset         bss = sc->blue_stringset;
276     const AF_Blue_StringRec*  bs  = &af_blue_stringsets[bss];
277
278
279     /* we walk over the blue character strings as specified in the   */
280     /* style's entry in the `af_blue_stringset' array, computing its */
281     /* extremum points (depending on the string properties)          */
282
283     FT_TRACE5(( "cjk blue zones computation\n"
284                 "==========================\n"
285                 "\n" ));
286
287     for ( ; bs->string != AF_BLUE_STRING_MAX; bs++ )
288     {
289       const char*  p = &af_blue_strings[bs->string];
290       FT_Pos*      blue_ref;
291       FT_Pos*      blue_shoot;
292
293
294       if ( AF_CJK_IS_HORIZ_BLUE( bs ) )
295         axis = &metrics->axis[AF_DIMENSION_HORZ];
296       else
297         axis = &metrics->axis[AF_DIMENSION_VERT];
298
299 #ifdef FT_DEBUG_LEVEL_TRACE
300       {
301         FT_String*  cjk_blue_name[4] =
302         {
303           (FT_String*)"bottom",    /* --   , --  */
304           (FT_String*)"top",       /* --   , TOP */
305           (FT_String*)"left",      /* HORIZ, --  */
306           (FT_String*)"right"      /* HORIZ, TOP */
307         };
308
309
310         FT_TRACE5(( "blue zone %d (%s):\n",
311                     axis->blue_count,
312                     cjk_blue_name[AF_CJK_IS_HORIZ_BLUE( bs ) |
313                                   AF_CJK_IS_TOP_BLUE( bs )   ] ));
314       }
315 #endif /* FT_DEBUG_LEVEL_TRACE */
316
317       num_fills = 0;
318       num_flats = 0;
319
320       fill = 1;  /* start with characters that define fill values */
321       FT_TRACE5(( "  [overshoot values]\n" ));
322
323       while ( *p )
324       {
325         FT_ULong    ch;
326         FT_ULong    glyph_index;
327         FT_Long     y_offset;
328         FT_Pos      best_pos;       /* same as points.y or points.x, resp. */
329         FT_Int      best_point;
330         FT_Vector*  points;
331
332
333         GET_UTF8_CHAR( ch, p );
334
335         /* switch to characters that define flat values */
336         if ( ch == '|' )
337         {
338           fill = 0;
339           FT_TRACE5(( "  [reference values]\n" ));
340           continue;
341         }
342
343         /* load the character in the face -- skip unknown or empty ones */
344         af_get_char_index( &metrics->root, ch, &glyph_index, &y_offset );
345         if ( glyph_index == 0 )
346         {
347           FT_TRACE5(( "  U+%04lX unavailable\n", ch ));
348           continue;
349         }
350
351         error   = FT_Load_Glyph( face, glyph_index, FT_LOAD_NO_SCALE );
352         outline = face->glyph->outline;
353         if ( error || outline.n_points <= 0 )
354         {
355           FT_TRACE5(( "  U+%04lX contains no outlines\n", ch ));
356           continue;
357         }
358
359         /* now compute min or max point indices and coordinates */
360         points     = outline.points;
361         best_point = -1;
362         best_pos   = 0;  /* make compiler happy */
363
364         {
365           FT_Int  nn;
366           FT_Int  first = 0;
367           FT_Int  last  = -1;
368
369
370           for ( nn = 0; nn < outline.n_contours; first = last + 1, nn++ )
371           {
372             FT_Int  pp;
373
374
375             last = outline.contours[nn];
376
377             /* Avoid single-point contours since they are never rasterized. */
378             /* In some fonts, they correspond to mark attachment points     */
379             /* which are way outside of the glyph's real outline.           */
380             if ( last <= first )
381               continue;
382
383             if ( AF_CJK_IS_HORIZ_BLUE( bs ) )
384             {
385               if ( AF_CJK_IS_RIGHT_BLUE( bs ) )
386               {
387                 for ( pp = first; pp <= last; pp++ )
388                   if ( best_point < 0 || points[pp].x > best_pos )
389                   {
390                     best_point = pp;
391                     best_pos   = points[pp].x;
392                   }
393               }
394               else
395               {
396                 for ( pp = first; pp <= last; pp++ )
397                   if ( best_point < 0 || points[pp].x < best_pos )
398                   {
399                     best_point = pp;
400                     best_pos   = points[pp].x;
401                   }
402               }
403             }
404             else
405             {
406               if ( AF_CJK_IS_TOP_BLUE( bs ) )
407               {
408                 for ( pp = first; pp <= last; pp++ )
409                   if ( best_point < 0 || points[pp].y > best_pos )
410                   {
411                     best_point = pp;
412                     best_pos   = points[pp].y;
413                   }
414               }
415               else
416               {
417                 for ( pp = first; pp <= last; pp++ )
418                   if ( best_point < 0 || points[pp].y < best_pos )
419                   {
420                     best_point = pp;
421                     best_pos   = points[pp].y;
422                   }
423               }
424             }
425           }
426
427           FT_TRACE5(( "  U+%04lX: best_pos = %5ld\n", ch, best_pos ));
428         }
429
430         if ( fill )
431           fills[num_fills++] = best_pos;
432         else
433           flats[num_flats++] = best_pos;
434       }
435
436       if ( num_flats == 0 && num_fills == 0 )
437       {
438         /*
439          *  we couldn't find a single glyph to compute this blue zone,
440          *  we will simply ignore it then
441          */
442         FT_TRACE5(( "  empty\n" ));
443         continue;
444       }
445
446       /* we have computed the contents of the `fill' and `flats' tables,   */
447       /* now determine the reference and overshoot position of the blue -- */
448       /* we simply take the median value after a simple sort               */
449       af_sort_pos( num_fills, fills );
450       af_sort_pos( num_flats, flats );
451
452       blue       = &axis->blues[axis->blue_count];
453       blue_ref   = &blue->ref.org;
454       blue_shoot = &blue->shoot.org;
455
456       axis->blue_count++;
457
458       if ( num_flats == 0 )
459       {
460         *blue_ref   =
461         *blue_shoot = fills[num_fills / 2];
462       }
463       else if ( num_fills == 0 )
464       {
465         *blue_ref   =
466         *blue_shoot = flats[num_flats / 2];
467       }
468       else
469       {
470         *blue_ref   = fills[num_fills / 2];
471         *blue_shoot = flats[num_flats / 2];
472       }
473
474       /* make sure blue_ref >= blue_shoot for top/right or */
475       /* vice versa for bottom/left                        */
476       if ( *blue_shoot != *blue_ref )
477       {
478         FT_Pos   ref       = *blue_ref;
479         FT_Pos   shoot     = *blue_shoot;
480         FT_Bool  under_ref = FT_BOOL( shoot < ref );
481
482
483         /* AF_CJK_IS_TOP_BLUE covers `right' and `top' */
484         if ( AF_CJK_IS_TOP_BLUE( bs ) ^ under_ref )
485         {
486           *blue_ref   =
487           *blue_shoot = ( shoot + ref ) / 2;
488
489           FT_TRACE5(( "  [reference smaller than overshoot,"
490                       " taking mean value]\n" ));
491         }
492       }
493
494       blue->flags = 0;
495       if ( AF_CJK_IS_TOP_BLUE( bs ) )
496         blue->flags |= AF_CJK_BLUE_TOP;
497
498       FT_TRACE5(( "    -> reference = %ld\n"
499                   "       overshoot = %ld\n",
500                   *blue_ref, *blue_shoot ));
501     }
502
503     FT_TRACE5(( "\n" ));
504
505     return;
506   }
507
508
509   /* Basically the Latin version with type AF_CJKMetrics for metrics. */
510
511   FT_LOCAL_DEF( void )
512   af_cjk_metrics_check_digits( AF_CJKMetrics  metrics,
513                                FT_Face        face )
514   {
515     FT_UInt   i;
516     FT_Bool   started = 0, same_width = 1;
517     FT_Fixed  advance, old_advance = 0;
518
519
520     /* digit `0' is 0x30 in all supported charmaps */
521     for ( i = 0x30; i <= 0x39; i++ )
522     {
523       FT_ULong  glyph_index;
524       FT_Long   y_offset;
525
526
527       af_get_char_index( &metrics->root, i, &glyph_index, &y_offset );
528       if ( glyph_index == 0 )
529         continue;
530
531       if ( FT_Get_Advance( face, glyph_index,
532                            FT_LOAD_NO_SCALE         |
533                            FT_LOAD_NO_HINTING       |
534                            FT_LOAD_IGNORE_TRANSFORM,
535                            &advance ) )
536         continue;
537
538       if ( started )
539       {
540         if ( advance != old_advance )
541         {
542           same_width = 0;
543           break;
544         }
545       }
546       else
547       {
548         old_advance = advance;
549         started     = 1;
550       }
551     }
552
553     metrics->root.digits_have_same_width = same_width;
554   }
555
556
557   /* Initialize global metrics. */
558
559   FT_LOCAL_DEF( FT_Error )
560   af_cjk_metrics_init( AF_CJKMetrics  metrics,
561                        FT_Face        face )
562   {
563     FT_CharMap  oldmap = face->charmap;
564
565
566     metrics->units_per_em = face->units_per_EM;
567
568     if ( !FT_Select_Charmap( face, FT_ENCODING_UNICODE ) )
569     {
570       af_cjk_metrics_init_widths( metrics, face );
571       af_cjk_metrics_init_blues( metrics, face );
572       af_cjk_metrics_check_digits( metrics, face );
573     }
574
575     FT_Set_Charmap( face, oldmap );
576     return FT_Err_Ok;
577   }
578
579
580   /* Adjust scaling value, then scale and shift widths   */
581   /* and blue zones (if applicable) for given dimension. */
582
583   static void
584   af_cjk_metrics_scale_dim( AF_CJKMetrics  metrics,
585                             AF_Scaler      scaler,
586                             AF_Dimension   dim )
587   {
588     FT_Fixed    scale;
589     FT_Pos      delta;
590     AF_CJKAxis  axis;
591     FT_UInt     nn;
592
593
594     if ( dim == AF_DIMENSION_HORZ )
595     {
596       scale = scaler->x_scale;
597       delta = scaler->x_delta;
598     }
599     else
600     {
601       scale = scaler->y_scale;
602       delta = scaler->y_delta;
603     }
604
605     axis = &metrics->axis[dim];
606
607     if ( axis->org_scale == scale && axis->org_delta == delta )
608       return;
609
610     axis->org_scale = scale;
611     axis->org_delta = delta;
612
613     axis->scale = scale;
614     axis->delta = delta;
615
616     /* scale the blue zones */
617     for ( nn = 0; nn < axis->blue_count; nn++ )
618     {
619       AF_CJKBlue  blue = &axis->blues[nn];
620       FT_Pos      dist;
621
622
623       blue->ref.cur   = FT_MulFix( blue->ref.org, scale ) + delta;
624       blue->ref.fit   = blue->ref.cur;
625       blue->shoot.cur = FT_MulFix( blue->shoot.org, scale ) + delta;
626       blue->shoot.fit = blue->shoot.cur;
627       blue->flags    &= ~AF_CJK_BLUE_ACTIVE;
628
629       /* a blue zone is only active if it is less than 3/4 pixels tall */
630       dist = FT_MulFix( blue->ref.org - blue->shoot.org, scale );
631       if ( dist <= 48 && dist >= -48 )
632       {
633         FT_Pos  delta1, delta2;
634
635
636         blue->ref.fit  = FT_PIX_ROUND( blue->ref.cur );
637
638         /* shoot is under shoot for cjk */
639         delta1 = FT_DivFix( blue->ref.fit, scale ) - blue->shoot.org;
640         delta2 = delta1;
641         if ( delta1 < 0 )
642           delta2 = -delta2;
643
644         delta2 = FT_MulFix( delta2, scale );
645
646         FT_TRACE5(( "delta: %d", delta1 ));
647         if ( delta2 < 32 )
648           delta2 = 0;
649 #if 0
650         else if ( delta2 < 64 )
651           delta2 = 32 + ( ( ( delta2 - 32 ) + 16 ) & ~31 );
652 #endif
653         else
654           delta2 = FT_PIX_ROUND( delta2 );
655         FT_TRACE5(( "/%d\n", delta2 ));
656
657         if ( delta1 < 0 )
658           delta2 = -delta2;
659
660         blue->shoot.fit = blue->ref.fit - delta2;
661
662         FT_TRACE5(( ">> active cjk blue zone %c%d[%ld/%ld]:\n"
663                     "     ref:   cur=%.2f fit=%.2f\n"
664                     "     shoot: cur=%.2f fit=%.2f\n",
665                     ( dim == AF_DIMENSION_HORZ ) ? 'H' : 'V',
666                     nn, blue->ref.org, blue->shoot.org,
667                     blue->ref.cur / 64.0, blue->ref.fit / 64.0,
668                     blue->shoot.cur / 64.0, blue->shoot.fit / 64.0 ));
669
670         blue->flags |= AF_CJK_BLUE_ACTIVE;
671       }
672     }
673   }
674
675
676   /* Scale global values in both directions. */
677
678   FT_LOCAL_DEF( void )
679   af_cjk_metrics_scale( AF_CJKMetrics  metrics,
680                         AF_Scaler      scaler )
681   {
682     /* we copy the whole structure since the x and y scaling values */
683     /* are not modified, contrary to e.g. the `latin' auto-hinter   */
684     metrics->root.scaler = *scaler;
685
686     af_cjk_metrics_scale_dim( metrics, scaler, AF_DIMENSION_HORZ );
687     af_cjk_metrics_scale_dim( metrics, scaler, AF_DIMENSION_VERT );
688   }
689
690
691   /*************************************************************************/
692   /*************************************************************************/
693   /*****                                                               *****/
694   /*****              C J K   G L Y P H   A N A L Y S I S              *****/
695   /*****                                                               *****/
696   /*************************************************************************/
697   /*************************************************************************/
698
699
700   /* Walk over all contours and compute its segments. */
701
702   static FT_Error
703   af_cjk_hints_compute_segments( AF_GlyphHints  hints,
704                                  AF_Dimension   dim )
705   {
706     AF_AxisHints  axis          = &hints->axis[dim];
707     AF_Segment    segments      = axis->segments;
708     AF_Segment    segment_limit = segments + axis->num_segments;
709     FT_Error      error;
710     AF_Segment    seg;
711
712
713     error = af_latin_hints_compute_segments( hints, dim );
714     if ( error )
715       return error;
716
717     /* a segment is round if it doesn't have successive */
718     /* on-curve points.                                 */
719     for ( seg = segments; seg < segment_limit; seg++ )
720     {
721       AF_Point  pt   = seg->first;
722       AF_Point  last = seg->last;
723       AF_Flags  f0   = (AF_Flags)( pt->flags & AF_FLAG_CONTROL );
724       AF_Flags  f1;
725
726
727       seg->flags &= ~AF_EDGE_ROUND;
728
729       for ( ; pt != last; f0 = f1 )
730       {
731         pt = pt->next;
732         f1 = (AF_Flags)( pt->flags & AF_FLAG_CONTROL );
733
734         if ( !f0 && !f1 )
735           break;
736
737         if ( pt == last )
738           seg->flags |= AF_EDGE_ROUND;
739       }
740     }
741
742     return FT_Err_Ok;
743   }
744
745
746   static void
747   af_cjk_hints_link_segments( AF_GlyphHints  hints,
748                               AF_Dimension   dim )
749   {
750     AF_AxisHints  axis          = &hints->axis[dim];
751     AF_Segment    segments      = axis->segments;
752     AF_Segment    segment_limit = segments + axis->num_segments;
753     AF_Direction  major_dir     = axis->major_dir;
754     AF_Segment    seg1, seg2;
755     FT_Pos        len_threshold;
756     FT_Pos        dist_threshold;
757
758
759     len_threshold = AF_LATIN_CONSTANT( hints->metrics, 8 );
760
761     dist_threshold = ( dim == AF_DIMENSION_HORZ ) ? hints->x_scale
762                                                   : hints->y_scale;
763     dist_threshold = FT_DivFix( 64 * 3, dist_threshold );
764
765     /* now compare each segment to the others */
766     for ( seg1 = segments; seg1 < segment_limit; seg1++ )
767     {
768       if ( seg1->dir != major_dir )
769         continue;
770
771       for ( seg2 = segments; seg2 < segment_limit; seg2++ )
772         if ( seg2 != seg1 && seg1->dir + seg2->dir == 0 )
773         {
774           FT_Pos  dist = seg2->pos - seg1->pos;
775
776
777           if ( dist < 0 )
778             continue;
779
780           {
781             FT_Pos  min = seg1->min_coord;
782             FT_Pos  max = seg1->max_coord;
783             FT_Pos  len;
784
785
786             if ( min < seg2->min_coord )
787               min = seg2->min_coord;
788
789             if ( max > seg2->max_coord )
790               max = seg2->max_coord;
791
792             len = max - min;
793             if ( len >= len_threshold )
794             {
795               if ( dist * 8 < seg1->score * 9                        &&
796                    ( dist * 8 < seg1->score * 7 || seg1->len < len ) )
797               {
798                 seg1->score = dist;
799                 seg1->len   = len;
800                 seg1->link  = seg2;
801               }
802
803               if ( dist * 8 < seg2->score * 9                        &&
804                    ( dist * 8 < seg2->score * 7 || seg2->len < len ) )
805               {
806                 seg2->score = dist;
807                 seg2->len   = len;
808                 seg2->link  = seg1;
809               }
810             }
811           }
812         }
813     }
814
815     /*
816      *  now compute the `serif' segments
817      *
818      *  In Hanzi, some strokes are wider on one or both of the ends.
819      *  We either identify the stems on the ends as serifs or remove
820      *  the linkage, depending on the length of the stems.
821      *
822      */
823
824     {
825       AF_Segment  link1, link2;
826
827
828       for ( seg1 = segments; seg1 < segment_limit; seg1++ )
829       {
830         link1 = seg1->link;
831         if ( !link1 || link1->link != seg1 || link1->pos <= seg1->pos )
832           continue;
833
834         if ( seg1->score >= dist_threshold )
835           continue;
836
837         for ( seg2 = segments; seg2 < segment_limit; seg2++ )
838         {
839           if ( seg2->pos > seg1->pos || seg1 == seg2 )
840             continue;
841
842           link2 = seg2->link;
843           if ( !link2 || link2->link != seg2 || link2->pos < link1->pos )
844             continue;
845
846           if ( seg1->pos == seg2->pos && link1->pos == link2->pos )
847             continue;
848
849           if ( seg2->score <= seg1->score || seg1->score * 4 <= seg2->score )
850             continue;
851
852           /* seg2 < seg1 < link1 < link2 */
853
854           if ( seg1->len >= seg2->len * 3 )
855           {
856             AF_Segment  seg;
857
858
859             for ( seg = segments; seg < segment_limit; seg++ )
860             {
861               AF_Segment  link = seg->link;
862
863
864               if ( link == seg2 )
865               {
866                 seg->link  = 0;
867                 seg->serif = link1;
868               }
869               else if ( link == link2 )
870               {
871                 seg->link  = 0;
872                 seg->serif = seg1;
873               }
874             }
875           }
876           else
877           {
878             seg1->link = link1->link = 0;
879
880             break;
881           }
882         }
883       }
884     }
885
886     for ( seg1 = segments; seg1 < segment_limit; seg1++ )
887     {
888       seg2 = seg1->link;
889
890       if ( seg2 )
891       {
892         seg2->num_linked++;
893         if ( seg2->link != seg1 )
894         {
895           seg1->link = 0;
896
897           if ( seg2->score < dist_threshold || seg1->score < seg2->score * 4 )
898             seg1->serif = seg2->link;
899           else
900             seg2->num_linked--;
901         }
902       }
903     }
904   }
905
906
907   static FT_Error
908   af_cjk_hints_compute_edges( AF_GlyphHints  hints,
909                               AF_Dimension   dim )
910   {
911     AF_AxisHints  axis   = &hints->axis[dim];
912     FT_Error      error  = FT_Err_Ok;
913     FT_Memory     memory = hints->memory;
914     AF_CJKAxis    laxis  = &((AF_CJKMetrics)hints->metrics)->axis[dim];
915
916     AF_Segment    segments      = axis->segments;
917     AF_Segment    segment_limit = segments + axis->num_segments;
918     AF_Segment    seg;
919
920     FT_Fixed      scale;
921     FT_Pos        edge_distance_threshold;
922
923
924     axis->num_edges = 0;
925
926     scale = ( dim == AF_DIMENSION_HORZ ) ? hints->x_scale
927                                          : hints->y_scale;
928
929     /*********************************************************************/
930     /*                                                                   */
931     /* We begin by generating a sorted table of edges for the current    */
932     /* direction.  To do so, we simply scan each segment and try to find */
933     /* an edge in our table that corresponds to its position.            */
934     /*                                                                   */
935     /* If no edge is found, we create and insert a new edge in the       */
936     /* sorted table.  Otherwise, we simply add the segment to the edge's */
937     /* list which is then processed in the second step to compute the    */
938     /* edge's properties.                                                */
939     /*                                                                   */
940     /* Note that the edges table is sorted along the segment/edge        */
941     /* position.                                                         */
942     /*                                                                   */
943     /*********************************************************************/
944
945     edge_distance_threshold = FT_MulFix( laxis->edge_distance_threshold,
946                                          scale );
947     if ( edge_distance_threshold > 64 / 4 )
948       edge_distance_threshold = FT_DivFix( 64 / 4, scale );
949     else
950       edge_distance_threshold = laxis->edge_distance_threshold;
951
952     for ( seg = segments; seg < segment_limit; seg++ )
953     {
954       AF_Edge  found = NULL;
955       FT_Pos   best  = 0xFFFFU;
956       FT_Int   ee;
957
958
959       /* look for an edge corresponding to the segment */
960       for ( ee = 0; ee < axis->num_edges; ee++ )
961       {
962         AF_Edge  edge = axis->edges + ee;
963         FT_Pos   dist;
964
965
966         if ( edge->dir != seg->dir )
967           continue;
968
969         dist = seg->pos - edge->fpos;
970         if ( dist < 0 )
971           dist = -dist;
972
973         if ( dist < edge_distance_threshold && dist < best )
974         {
975           AF_Segment  link = seg->link;
976
977
978           /* check whether all linked segments of the candidate edge */
979           /* can make a single edge.                                 */
980           if ( link )
981           {
982             AF_Segment  seg1  = edge->first;
983             FT_Pos      dist2 = 0;
984
985
986             do
987             {
988               AF_Segment  link1 = seg1->link;
989
990
991               if ( link1 )
992               {
993                 dist2 = AF_SEGMENT_DIST( link, link1 );
994                 if ( dist2 >= edge_distance_threshold )
995                   break;
996               }
997
998             } while ( ( seg1 = seg1->edge_next ) != edge->first );
999
1000             if ( dist2 >= edge_distance_threshold )
1001               continue;
1002           }
1003
1004           best  = dist;
1005           found = edge;
1006         }
1007       }
1008
1009       if ( !found )
1010       {
1011         AF_Edge  edge;
1012
1013
1014         /* insert a new edge in the list and */
1015         /* sort according to the position    */
1016         error = af_axis_hints_new_edge( axis, seg->pos,
1017                                         (AF_Direction)seg->dir,
1018                                         memory, &edge );
1019         if ( error )
1020           goto Exit;
1021
1022         /* add the segment to the new edge's list */
1023         FT_ZERO( edge );
1024
1025         edge->first    = seg;
1026         edge->last     = seg;
1027         edge->dir      = seg->dir;
1028         edge->fpos     = seg->pos;
1029         edge->opos     = FT_MulFix( seg->pos, scale );
1030         edge->pos      = edge->opos;
1031         seg->edge_next = seg;
1032       }
1033       else
1034       {
1035         /* if an edge was found, simply add the segment to the edge's */
1036         /* list                                                       */
1037         seg->edge_next         = found->first;
1038         found->last->edge_next = seg;
1039         found->last            = seg;
1040       }
1041     }
1042
1043     /******************************************************************/
1044     /*                                                                */
1045     /* Good, we now compute each edge's properties according to the   */
1046     /* segments found on its position.  Basically, these are          */
1047     /*                                                                */
1048     /*  - the edge's main direction                                   */
1049     /*  - stem edge, serif edge or both (which defaults to stem then) */
1050     /*  - rounded edge, straight or both (which defaults to straight) */
1051     /*  - link for edge                                               */
1052     /*                                                                */
1053     /******************************************************************/
1054
1055     /* first of all, set the `edge' field in each segment -- this is */
1056     /* required in order to compute edge links                       */
1057
1058     /*
1059      * Note that removing this loop and setting the `edge' field of each
1060      * segment directly in the code above slows down execution speed for
1061      * some reasons on platforms like the Sun.
1062      */
1063     {
1064       AF_Edge  edges      = axis->edges;
1065       AF_Edge  edge_limit = edges + axis->num_edges;
1066       AF_Edge  edge;
1067
1068
1069       for ( edge = edges; edge < edge_limit; edge++ )
1070       {
1071         seg = edge->first;
1072         if ( seg )
1073           do
1074           {
1075             seg->edge = edge;
1076             seg       = seg->edge_next;
1077
1078           } while ( seg != edge->first );
1079       }
1080
1081       /* now compute each edge properties */
1082       for ( edge = edges; edge < edge_limit; edge++ )
1083       {
1084         FT_Int  is_round    = 0;  /* does it contain round segments?    */
1085         FT_Int  is_straight = 0;  /* does it contain straight segments? */
1086
1087
1088         seg = edge->first;
1089
1090         do
1091         {
1092           FT_Bool  is_serif;
1093
1094
1095           /* check for roundness of segment */
1096           if ( seg->flags & AF_EDGE_ROUND )
1097             is_round++;
1098           else
1099             is_straight++;
1100
1101           /* check for links -- if seg->serif is set, then seg->link must */
1102           /* be ignored                                                   */
1103           is_serif = (FT_Bool)( seg->serif && seg->serif->edge != edge );
1104
1105           if ( seg->link || is_serif )
1106           {
1107             AF_Edge     edge2;
1108             AF_Segment  seg2;
1109
1110
1111             edge2 = edge->link;
1112             seg2  = seg->link;
1113
1114             if ( is_serif )
1115             {
1116               seg2  = seg->serif;
1117               edge2 = edge->serif;
1118             }
1119
1120             if ( edge2 )
1121             {
1122               FT_Pos  edge_delta;
1123               FT_Pos  seg_delta;
1124
1125
1126               edge_delta = edge->fpos - edge2->fpos;
1127               if ( edge_delta < 0 )
1128                 edge_delta = -edge_delta;
1129
1130               seg_delta = AF_SEGMENT_DIST( seg, seg2 );
1131
1132               if ( seg_delta < edge_delta )
1133                 edge2 = seg2->edge;
1134             }
1135             else
1136               edge2 = seg2->edge;
1137
1138             if ( is_serif )
1139             {
1140               edge->serif   = edge2;
1141               edge2->flags |= AF_EDGE_SERIF;
1142             }
1143             else
1144               edge->link  = edge2;
1145           }
1146
1147           seg = seg->edge_next;
1148
1149         } while ( seg != edge->first );
1150
1151         /* set the round/straight flags */
1152         edge->flags = AF_EDGE_NORMAL;
1153
1154         if ( is_round > 0 && is_round >= is_straight )
1155           edge->flags |= AF_EDGE_ROUND;
1156
1157         /* get rid of serifs if link is set                 */
1158         /* XXX: This gets rid of many unpleasant artefacts! */
1159         /*      Example: the `c' in cour.pfa at size 13     */
1160
1161         if ( edge->serif && edge->link )
1162           edge->serif = 0;
1163       }
1164     }
1165
1166   Exit:
1167     return error;
1168   }
1169
1170
1171   /* Detect segments and edges for given dimension. */
1172
1173   static FT_Error
1174   af_cjk_hints_detect_features( AF_GlyphHints  hints,
1175                                 AF_Dimension   dim )
1176   {
1177     FT_Error  error;
1178
1179
1180     error = af_cjk_hints_compute_segments( hints, dim );
1181     if ( !error )
1182     {
1183       af_cjk_hints_link_segments( hints, dim );
1184
1185       error = af_cjk_hints_compute_edges( hints, dim );
1186     }
1187     return error;
1188   }
1189
1190
1191   /* Compute all edges which lie within blue zones. */
1192
1193   FT_LOCAL_DEF( void )
1194   af_cjk_hints_compute_blue_edges( AF_GlyphHints  hints,
1195                                    AF_CJKMetrics  metrics,
1196                                    AF_Dimension   dim )
1197   {
1198     AF_AxisHints  axis       = &hints->axis[dim];
1199     AF_Edge       edge       = axis->edges;
1200     AF_Edge       edge_limit = edge + axis->num_edges;
1201     AF_CJKAxis    cjk        = &metrics->axis[dim];
1202     FT_Fixed      scale      = cjk->scale;
1203     FT_Pos        best_dist0;  /* initial threshold */
1204
1205
1206     /* compute the initial threshold as a fraction of the EM size */
1207     best_dist0 = FT_MulFix( metrics->units_per_em / 40, scale );
1208
1209     if ( best_dist0 > 64 / 2 ) /* maximum 1/2 pixel */
1210       best_dist0 = 64 / 2;
1211
1212     /* compute which blue zones are active, i.e. have their scaled */
1213     /* size < 3/4 pixels                                           */
1214
1215     /* If the distant between an edge and a blue zone is shorter than */
1216     /* best_dist0, set the blue zone for the edge.  Then search for   */
1217     /* the blue zone with the smallest best_dist to the edge.         */
1218
1219     for ( ; edge < edge_limit; edge++ )
1220     {
1221       FT_UInt   bb;
1222       AF_Width  best_blue = NULL;
1223       FT_Pos    best_dist = best_dist0;
1224
1225
1226       for ( bb = 0; bb < cjk->blue_count; bb++ )
1227       {
1228         AF_CJKBlue  blue = cjk->blues + bb;
1229         FT_Bool     is_top_right_blue, is_major_dir;
1230
1231
1232         /* skip inactive blue zones (i.e., those that are too small) */
1233         if ( !( blue->flags & AF_CJK_BLUE_ACTIVE ) )
1234           continue;
1235
1236         /* if it is a top zone, check for right edges -- if it is a bottom */
1237         /* zone, check for left edges                                      */
1238         /*                                                                 */
1239         /* of course, that's for TrueType                                  */
1240         is_top_right_blue =
1241           (FT_Byte)( ( blue->flags & AF_CJK_BLUE_TOP ) != 0 );
1242         is_major_dir =
1243           FT_BOOL( edge->dir == axis->major_dir );
1244
1245         /* if it is a top zone, the edge must be against the major    */
1246         /* direction; if it is a bottom zone, it must be in the major */
1247         /* direction                                                  */
1248         if ( is_top_right_blue ^ is_major_dir )
1249         {
1250           FT_Pos    dist;
1251           AF_Width  compare;
1252
1253
1254           /* Compare the edge to the closest blue zone type */
1255           if ( FT_ABS( edge->fpos - blue->ref.org ) >
1256                FT_ABS( edge->fpos - blue->shoot.org ) )
1257             compare = &blue->shoot;
1258           else
1259             compare = &blue->ref;
1260
1261           dist = edge->fpos - compare->org;
1262           if ( dist < 0 )
1263             dist = -dist;
1264
1265           dist = FT_MulFix( dist, scale );
1266           if ( dist < best_dist )
1267           {
1268             best_dist = dist;
1269             best_blue = compare;
1270           }
1271         }
1272       }
1273
1274       if ( best_blue )
1275         edge->blue_edge = best_blue;
1276     }
1277   }
1278
1279
1280   /* Initalize hinting engine. */
1281
1282   FT_LOCAL_DEF( FT_Error )
1283   af_cjk_hints_init( AF_GlyphHints  hints,
1284                      AF_CJKMetrics  metrics )
1285   {
1286     FT_Render_Mode  mode;
1287     FT_UInt32       scaler_flags, other_flags;
1288
1289
1290     af_glyph_hints_rescale( hints, (AF_StyleMetrics)metrics );
1291
1292     /*
1293      *  correct x_scale and y_scale when needed, since they may have
1294      *  been modified af_cjk_scale_dim above
1295      */
1296     hints->x_scale = metrics->axis[AF_DIMENSION_HORZ].scale;
1297     hints->x_delta = metrics->axis[AF_DIMENSION_HORZ].delta;
1298     hints->y_scale = metrics->axis[AF_DIMENSION_VERT].scale;
1299     hints->y_delta = metrics->axis[AF_DIMENSION_VERT].delta;
1300
1301     /* compute flags depending on render mode, etc. */
1302     mode = metrics->root.scaler.render_mode;
1303
1304 #ifdef AF_CONFIG_OPTION_USE_WARPER
1305     if ( mode == FT_RENDER_MODE_LCD || mode == FT_RENDER_MODE_LCD_V )
1306       metrics->root.scaler.render_mode = mode = FT_RENDER_MODE_NORMAL;
1307 #endif
1308
1309     scaler_flags = hints->scaler_flags;
1310     other_flags  = 0;
1311
1312     /*
1313      *  We snap the width of vertical stems for the monochrome and
1314      *  horizontal LCD rendering targets only.
1315      */
1316     if ( mode == FT_RENDER_MODE_MONO || mode == FT_RENDER_MODE_LCD )
1317       other_flags |= AF_LATIN_HINTS_HORZ_SNAP;
1318
1319     /*
1320      *  We snap the width of horizontal stems for the monochrome and
1321      *  vertical LCD rendering targets only.
1322      */
1323     if ( mode == FT_RENDER_MODE_MONO || mode == FT_RENDER_MODE_LCD_V )
1324       other_flags |= AF_LATIN_HINTS_VERT_SNAP;
1325
1326     /*
1327      *  We adjust stems to full pixels only if we don't use the `light' mode.
1328      */
1329     if ( mode != FT_RENDER_MODE_LIGHT )
1330       other_flags |= AF_LATIN_HINTS_STEM_ADJUST;
1331
1332     if ( mode == FT_RENDER_MODE_MONO )
1333       other_flags |= AF_LATIN_HINTS_MONO;
1334
1335     scaler_flags |= AF_SCALER_FLAG_NO_ADVANCE;
1336
1337     hints->scaler_flags = scaler_flags;
1338     hints->other_flags  = other_flags;
1339
1340     return FT_Err_Ok;
1341   }
1342
1343
1344   /*************************************************************************/
1345   /*************************************************************************/
1346   /*****                                                               *****/
1347   /*****          C J K   G L Y P H   G R I D - F I T T I N G          *****/
1348   /*****                                                               *****/
1349   /*************************************************************************/
1350   /*************************************************************************/
1351
1352   /* Snap a given width in scaled coordinates to one of the */
1353   /* current standard widths.                               */
1354
1355   static FT_Pos
1356   af_cjk_snap_width( AF_Width  widths,
1357                      FT_Int    count,
1358                      FT_Pos    width )
1359   {
1360     int     n;
1361     FT_Pos  best      = 64 + 32 + 2;
1362     FT_Pos  reference = width;
1363     FT_Pos  scaled;
1364
1365
1366     for ( n = 0; n < count; n++ )
1367     {
1368       FT_Pos  w;
1369       FT_Pos  dist;
1370
1371
1372       w = widths[n].cur;
1373       dist = width - w;
1374       if ( dist < 0 )
1375         dist = -dist;
1376       if ( dist < best )
1377       {
1378         best      = dist;
1379         reference = w;
1380       }
1381     }
1382
1383     scaled = FT_PIX_ROUND( reference );
1384
1385     if ( width >= reference )
1386     {
1387       if ( width < scaled + 48 )
1388         width = reference;
1389     }
1390     else
1391     {
1392       if ( width > scaled - 48 )
1393         width = reference;
1394     }
1395
1396     return width;
1397   }
1398
1399
1400   /* Compute the snapped width of a given stem.                          */
1401   /* There is a lot of voodoo in this function; changing the hard-coded  */
1402   /* parameters influence the whole hinting process.                     */
1403
1404   static FT_Pos
1405   af_cjk_compute_stem_width( AF_GlyphHints  hints,
1406                              AF_Dimension   dim,
1407                              FT_Pos         width,
1408                              AF_Edge_Flags  base_flags,
1409                              AF_Edge_Flags  stem_flags )
1410   {
1411     AF_CJKMetrics  metrics  = (AF_CJKMetrics)hints->metrics;
1412     AF_CJKAxis     axis     = &metrics->axis[dim];
1413     FT_Pos         dist     = width;
1414     FT_Int         sign     = 0;
1415     FT_Bool        vertical = FT_BOOL( dim == AF_DIMENSION_VERT );
1416
1417     FT_UNUSED( base_flags );
1418     FT_UNUSED( stem_flags );
1419
1420
1421     if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) )
1422       return width;
1423
1424     if ( dist < 0 )
1425     {
1426       dist = -width;
1427       sign = 1;
1428     }
1429
1430     if ( (  vertical && !AF_LATIN_HINTS_DO_VERT_SNAP( hints ) ) ||
1431          ( !vertical && !AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) ) )
1432     {
1433       /* smooth hinting process: very lightly quantize the stem width */
1434
1435       if ( axis->width_count > 0 )
1436       {
1437         if ( FT_ABS( dist - axis->widths[0].cur ) < 40 )
1438         {
1439           dist = axis->widths[0].cur;
1440           if ( dist < 48 )
1441             dist = 48;
1442
1443           goto Done_Width;
1444         }
1445       }
1446
1447       if ( dist < 54 )
1448         dist += ( 54 - dist ) / 2 ;
1449       else if ( dist < 3 * 64 )
1450       {
1451         FT_Pos  delta;
1452
1453
1454         delta  = dist & 63;
1455         dist  &= -64;
1456
1457         if ( delta < 10 )
1458           dist += delta;
1459         else if ( delta < 22 )
1460           dist += 10;
1461         else if ( delta < 42 )
1462           dist += delta;
1463         else if ( delta < 54 )
1464           dist += 54;
1465         else
1466           dist += delta;
1467       }
1468     }
1469     else
1470     {
1471       /* strong hinting process: snap the stem width to integer pixels */
1472
1473       dist = af_cjk_snap_width( axis->widths, axis->width_count, dist );
1474
1475       if ( vertical )
1476       {
1477         /* in the case of vertical hinting, always round */
1478         /* the stem heights to integer pixels            */
1479
1480         if ( dist >= 64 )
1481           dist = ( dist + 16 ) & ~63;
1482         else
1483           dist = 64;
1484       }
1485       else
1486       {
1487         if ( AF_LATIN_HINTS_DO_MONO( hints ) )
1488         {
1489           /* monochrome horizontal hinting: snap widths to integer pixels */
1490           /* with a different threshold                                   */
1491
1492           if ( dist < 64 )
1493             dist = 64;
1494           else
1495             dist = ( dist + 32 ) & ~63;
1496         }
1497         else
1498         {
1499           /* for horizontal anti-aliased hinting, we adopt a more subtle */
1500           /* approach: we strengthen small stems, round stems whose size */
1501           /* is between 1 and 2 pixels to an integer, otherwise nothing  */
1502
1503           if ( dist < 48 )
1504             dist = ( dist + 64 ) >> 1;
1505
1506           else if ( dist < 128 )
1507             dist = ( dist + 22 ) & ~63;
1508           else
1509             /* round otherwise to prevent color fringes in LCD mode */
1510             dist = ( dist + 32 ) & ~63;
1511         }
1512       }
1513     }
1514
1515   Done_Width:
1516     if ( sign )
1517       dist = -dist;
1518
1519     return dist;
1520   }
1521
1522
1523   /* Align one stem edge relative to the previous stem edge. */
1524
1525   static void
1526   af_cjk_align_linked_edge( AF_GlyphHints  hints,
1527                             AF_Dimension   dim,
1528                             AF_Edge        base_edge,
1529                             AF_Edge        stem_edge )
1530   {
1531     FT_Pos  dist = stem_edge->opos - base_edge->opos;
1532
1533     FT_Pos  fitted_width = af_cjk_compute_stem_width(
1534                              hints, dim, dist,
1535                              (AF_Edge_Flags)base_edge->flags,
1536                              (AF_Edge_Flags)stem_edge->flags );
1537
1538
1539     stem_edge->pos = base_edge->pos + fitted_width;
1540
1541     FT_TRACE5(( "  CJKLINK: edge %d @%d (opos=%.2f) linked to %.2f,"
1542                 " dist was %.2f, now %.2f\n",
1543                 stem_edge - hints->axis[dim].edges, stem_edge->fpos,
1544                 stem_edge->opos / 64.0, stem_edge->pos / 64.0,
1545                 dist / 64.0, fitted_width / 64.0 ));
1546   }
1547
1548
1549   /* Shift the coordinates of the `serif' edge by the same amount */
1550   /* as the corresponding `base' edge has been moved already.     */
1551
1552   static void
1553   af_cjk_align_serif_edge( AF_GlyphHints  hints,
1554                            AF_Edge        base,
1555                            AF_Edge        serif )
1556   {
1557     FT_UNUSED( hints );
1558
1559     serif->pos = base->pos + ( serif->opos - base->opos );
1560   }
1561
1562
1563   /*************************************************************************/
1564   /*************************************************************************/
1565   /*************************************************************************/
1566   /****                                                                 ****/
1567   /****                    E D G E   H I N T I N G                      ****/
1568   /****                                                                 ****/
1569   /*************************************************************************/
1570   /*************************************************************************/
1571   /*************************************************************************/
1572
1573
1574 #define AF_LIGHT_MODE_MAX_HORZ_GAP    9
1575 #define AF_LIGHT_MODE_MAX_VERT_GAP   15
1576 #define AF_LIGHT_MODE_MAX_DELTA_ABS  14
1577
1578
1579   static FT_Pos
1580   af_hint_normal_stem( AF_GlyphHints  hints,
1581                        AF_Edge        edge,
1582                        AF_Edge        edge2,
1583                        FT_Pos         anchor,
1584                        AF_Dimension   dim )
1585   {
1586     FT_Pos  org_len, cur_len, org_center;
1587     FT_Pos  cur_pos1, cur_pos2;
1588     FT_Pos  d_off1, u_off1, d_off2, u_off2, delta;
1589     FT_Pos  offset;
1590     FT_Pos  threshold = 64;
1591
1592
1593     if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) )
1594     {
1595       if ( ( edge->flags  & AF_EDGE_ROUND ) &&
1596            ( edge2->flags & AF_EDGE_ROUND ) )
1597       {
1598         if ( dim == AF_DIMENSION_VERT )
1599           threshold = 64 - AF_LIGHT_MODE_MAX_HORZ_GAP;
1600         else
1601           threshold = 64 - AF_LIGHT_MODE_MAX_VERT_GAP;
1602       }
1603       else
1604       {
1605         if ( dim == AF_DIMENSION_VERT )
1606           threshold = 64 - AF_LIGHT_MODE_MAX_HORZ_GAP / 3;
1607         else
1608           threshold = 64 - AF_LIGHT_MODE_MAX_VERT_GAP / 3;
1609       }
1610     }
1611
1612     org_len    = edge2->opos - edge->opos;
1613     cur_len    = af_cjk_compute_stem_width( hints, dim, org_len,
1614                                             (AF_Edge_Flags)edge->flags,
1615                                             (AF_Edge_Flags)edge2->flags );
1616
1617     org_center = ( edge->opos + edge2->opos ) / 2 + anchor;
1618     cur_pos1   = org_center - cur_len / 2;
1619     cur_pos2   = cur_pos1 + cur_len;
1620     d_off1     = cur_pos1 - FT_PIX_FLOOR( cur_pos1 );
1621     d_off2     = cur_pos2 - FT_PIX_FLOOR( cur_pos2 );
1622     u_off1     = 64 - d_off1;
1623     u_off2     = 64 - d_off2;
1624     delta      = 0;
1625
1626
1627     if ( d_off1 == 0 || d_off2 == 0 )
1628       goto Exit;
1629
1630     if ( cur_len <= threshold )
1631     {
1632       if ( d_off2 < cur_len )
1633       {
1634         if ( u_off1 <= d_off2 )
1635           delta =  u_off1;
1636         else
1637           delta = -d_off2;
1638       }
1639
1640       goto Exit;
1641     }
1642
1643     if ( threshold < 64 )
1644     {
1645       if ( d_off1 >= threshold || u_off1 >= threshold ||
1646            d_off2 >= threshold || u_off2 >= threshold )
1647         goto Exit;
1648     }
1649
1650     offset = cur_len & 63;
1651
1652     if ( offset < 32 )
1653     {
1654       if ( u_off1 <= offset || d_off2 <= offset )
1655         goto Exit;
1656     }
1657     else
1658       offset = 64 - threshold;
1659
1660     d_off1 = threshold - u_off1;
1661     u_off1 = u_off1    - offset;
1662     u_off2 = threshold - d_off2;
1663     d_off2 = d_off2    - offset;
1664
1665     if ( d_off1 <= u_off1 )
1666       u_off1 = -d_off1;
1667
1668     if ( d_off2 <= u_off2 )
1669       u_off2 = -d_off2;
1670
1671     if ( FT_ABS( u_off1 ) <= FT_ABS( u_off2 ) )
1672       delta = u_off1;
1673     else
1674       delta = u_off2;
1675
1676   Exit:
1677
1678 #if 1
1679     if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) )
1680     {
1681       if ( delta > AF_LIGHT_MODE_MAX_DELTA_ABS )
1682         delta = AF_LIGHT_MODE_MAX_DELTA_ABS;
1683       else if ( delta < -AF_LIGHT_MODE_MAX_DELTA_ABS )
1684         delta = -AF_LIGHT_MODE_MAX_DELTA_ABS;
1685     }
1686 #endif
1687
1688     cur_pos1 += delta;
1689
1690     if ( edge->opos < edge2->opos )
1691     {
1692       edge->pos  = cur_pos1;
1693       edge2->pos = cur_pos1 + cur_len;
1694     }
1695     else
1696     {
1697       edge->pos  = cur_pos1 + cur_len;
1698       edge2->pos = cur_pos1;
1699     }
1700
1701     return delta;
1702   }
1703
1704
1705   /* The main grid-fitting routine. */
1706
1707   static void
1708   af_cjk_hint_edges( AF_GlyphHints  hints,
1709                      AF_Dimension   dim )
1710   {
1711     AF_AxisHints  axis       = &hints->axis[dim];
1712     AF_Edge       edges      = axis->edges;
1713     AF_Edge       edge_limit = edges + axis->num_edges;
1714     FT_PtrDist    n_edges;
1715     AF_Edge       edge;
1716     AF_Edge       anchor   = 0;
1717     FT_Pos        delta    = 0;
1718     FT_Int        skipped  = 0;
1719     FT_Bool       has_last_stem = FALSE;
1720     FT_Pos        last_stem_pos = 0;
1721
1722 #ifdef FT_DEBUG_LEVEL_TRACE
1723     FT_UInt       num_actions = 0;
1724 #endif
1725
1726
1727     FT_TRACE5(( "cjk %s edge hinting (style `%s')\n",
1728                 dim == AF_DIMENSION_VERT ? "horizontal" : "vertical",
1729                 af_style_names[hints->metrics->style_class->style] ));
1730
1731     /* we begin by aligning all stems relative to the blue zone */
1732
1733     if ( AF_HINTS_DO_BLUES( hints ) )
1734     {
1735       for ( edge = edges; edge < edge_limit; edge++ )
1736       {
1737         AF_Width  blue;
1738         AF_Edge   edge1, edge2;
1739
1740
1741         if ( edge->flags & AF_EDGE_DONE )
1742           continue;
1743
1744         blue  = edge->blue_edge;
1745         edge1 = NULL;
1746         edge2 = edge->link;
1747
1748         if ( blue )
1749         {
1750           edge1 = edge;
1751         }
1752         else if ( edge2 && edge2->blue_edge )
1753         {
1754           blue  = edge2->blue_edge;
1755           edge1 = edge2;
1756           edge2 = edge;
1757         }
1758
1759         if ( !edge1 )
1760           continue;
1761
1762 #ifdef FT_DEBUG_LEVEL_TRACE
1763         FT_TRACE5(( "  CJKBLUE: edge %d @%d (opos=%.2f) snapped to %.2f,"
1764                     " was %.2f\n",
1765                     edge1 - edges, edge1->fpos, edge1->opos / 64.0,
1766                     blue->fit / 64.0, edge1->pos / 64.0 ));
1767
1768         num_actions++;
1769 #endif
1770
1771         edge1->pos    = blue->fit;
1772         edge1->flags |= AF_EDGE_DONE;
1773
1774         if ( edge2 && !edge2->blue_edge )
1775         {
1776           af_cjk_align_linked_edge( hints, dim, edge1, edge2 );
1777           edge2->flags |= AF_EDGE_DONE;
1778
1779 #ifdef FT_DEBUG_LEVEL_TRACE
1780           num_actions++;
1781 #endif
1782         }
1783
1784         if ( !anchor )
1785           anchor = edge;
1786       }
1787     }
1788
1789     /* now we align all stem edges. */
1790     for ( edge = edges; edge < edge_limit; edge++ )
1791     {
1792       AF_Edge  edge2;
1793
1794
1795       if ( edge->flags & AF_EDGE_DONE )
1796         continue;
1797
1798       /* skip all non-stem edges */
1799       edge2 = edge->link;
1800       if ( !edge2 )
1801       {
1802         skipped++;
1803         continue;
1804       }
1805
1806       /* Some CJK characters have so many stems that
1807        * the hinter is likely to merge two adjacent ones.
1808        * To solve this problem, if either edge of a stem
1809        * is too close to the previous one, we avoid
1810        * aligning the two edges, but rather interpolate
1811        * their locations at the end of this function in
1812        * order to preserve the space between the stems.
1813        */
1814       if ( has_last_stem                       &&
1815            ( edge->pos  < last_stem_pos + 64 ||
1816              edge2->pos < last_stem_pos + 64 ) )
1817       {
1818         skipped++;
1819         continue;
1820       }
1821
1822       /* now align the stem */
1823
1824       /* this should not happen, but it's better to be safe */
1825       if ( edge2->blue_edge )
1826       {
1827         FT_TRACE5(( "ASSERTION FAILED for edge %d\n", edge2-edges ));
1828
1829         af_cjk_align_linked_edge( hints, dim, edge2, edge );
1830         edge->flags |= AF_EDGE_DONE;
1831
1832 #ifdef FT_DEBUG_LEVEL_TRACE
1833         num_actions++;
1834 #endif
1835
1836         continue;
1837       }
1838
1839       if ( edge2 < edge )
1840       {
1841         af_cjk_align_linked_edge( hints, dim, edge2, edge );
1842         edge->flags |= AF_EDGE_DONE;
1843
1844 #ifdef FT_DEBUG_LEVEL_TRACE
1845         num_actions++;
1846 #endif
1847
1848         /* We rarely reaches here it seems;
1849          * usually the two edges belonging
1850          * to one stem are marked as DONE together
1851          */
1852         has_last_stem = TRUE;
1853         last_stem_pos = edge->pos;
1854         continue;
1855       }
1856
1857       if ( dim != AF_DIMENSION_VERT && !anchor )
1858       {
1859
1860 #if 0
1861         if ( fixedpitch )
1862         {
1863           AF_Edge     left  = edge;
1864           AF_Edge     right = edge_limit - 1;
1865           AF_EdgeRec  left1, left2, right1, right2;
1866           FT_Pos      target, center1, center2;
1867           FT_Pos      delta1, delta2, d1, d2;
1868
1869
1870           while ( right > left && !right->link )
1871             right--;
1872
1873           left1  = *left;
1874           left2  = *left->link;
1875           right1 = *right->link;
1876           right2 = *right;
1877
1878           delta  = ( ( ( hinter->pp2.x + 32 ) & -64 ) - hinter->pp2.x ) / 2;
1879           target = left->opos + ( right->opos - left->opos ) / 2 + delta - 16;
1880
1881           delta1  = delta;
1882           delta1 += af_hint_normal_stem( hints, left, left->link,
1883                                          delta1, 0 );
1884
1885           if ( left->link != right )
1886             af_hint_normal_stem( hints, right->link, right, delta1, 0 );
1887
1888           center1 = left->pos + ( right->pos - left->pos ) / 2;
1889
1890           if ( center1 >= target )
1891             delta2 = delta - 32;
1892           else
1893             delta2 = delta + 32;
1894
1895           delta2 += af_hint_normal_stem( hints, &left1, &left2, delta2, 0 );
1896
1897           if ( delta1 != delta2 )
1898           {
1899             if ( left->link != right )
1900               af_hint_normal_stem( hints, &right1, &right2, delta2, 0 );
1901
1902             center2 = left1.pos + ( right2.pos - left1.pos ) / 2;
1903
1904             d1 = center1 - target;
1905             d2 = center2 - target;
1906
1907             if ( FT_ABS( d2 ) < FT_ABS( d1 ) )
1908             {
1909               left->pos       = left1.pos;
1910               left->link->pos = left2.pos;
1911
1912               if ( left->link != right )
1913               {
1914                 right->link->pos = right1.pos;
1915                 right->pos       = right2.pos;
1916               }
1917
1918               delta1 = delta2;
1919             }
1920           }
1921
1922           delta               = delta1;
1923           right->link->flags |= AF_EDGE_DONE;
1924           right->flags       |= AF_EDGE_DONE;
1925         }
1926         else
1927
1928 #endif /* 0 */
1929
1930           delta = af_hint_normal_stem( hints, edge, edge2, 0,
1931                                        AF_DIMENSION_HORZ );
1932       }
1933       else
1934         af_hint_normal_stem( hints, edge, edge2, delta, dim );
1935
1936 #if 0
1937       printf( "stem (%d,%d) adjusted (%.1f,%.1f)\n",
1938                edge - edges, edge2 - edges,
1939                ( edge->pos - edge->opos ) / 64.0,
1940                ( edge2->pos - edge2->opos ) / 64.0 );
1941 #endif
1942
1943       anchor = edge;
1944       edge->flags  |= AF_EDGE_DONE;
1945       edge2->flags |= AF_EDGE_DONE;
1946       has_last_stem = TRUE;
1947       last_stem_pos = edge2->pos;
1948     }
1949
1950     /* make sure that lowercase m's maintain their symmetry */
1951
1952     /* In general, lowercase m's have six vertical edges if they are sans */
1953     /* serif, or twelve if they are with serifs.  This implementation is  */
1954     /* based on that assumption, and seems to work very well with most    */
1955     /* faces.  However, if for a certain face this assumption is not      */
1956     /* true, the m is just rendered like before.  In addition, any stem   */
1957     /* correction will only be applied to symmetrical glyphs (even if the */
1958     /* glyph is not an m), so the potential for unwanted distortion is    */
1959     /* relatively low.                                                    */
1960
1961     /* We don't handle horizontal edges since we can't easily assure that */
1962     /* the third (lowest) stem aligns with the base line; it might end up */
1963     /* one pixel higher or lower.                                         */
1964
1965     n_edges = edge_limit - edges;
1966     if ( dim == AF_DIMENSION_HORZ && ( n_edges == 6 || n_edges == 12 ) )
1967     {
1968       AF_Edge  edge1, edge2, edge3;
1969       FT_Pos   dist1, dist2, span;
1970
1971
1972       if ( n_edges == 6 )
1973       {
1974         edge1 = edges;
1975         edge2 = edges + 2;
1976         edge3 = edges + 4;
1977       }
1978       else
1979       {
1980         edge1 = edges + 1;
1981         edge2 = edges + 5;
1982         edge3 = edges + 9;
1983       }
1984
1985       dist1 = edge2->opos - edge1->opos;
1986       dist2 = edge3->opos - edge2->opos;
1987
1988       span = dist1 - dist2;
1989       if ( span < 0 )
1990         span = -span;
1991
1992       if ( edge1->link == edge1 + 1 &&
1993            edge2->link == edge2 + 1 &&
1994            edge3->link == edge3 + 1 && span < 8 )
1995       {
1996         delta = edge3->pos - ( 2 * edge2->pos - edge1->pos );
1997         edge3->pos -= delta;
1998         if ( edge3->link )
1999           edge3->link->pos -= delta;
2000
2001         /* move the serifs along with the stem */
2002         if ( n_edges == 12 )
2003         {
2004           ( edges + 8 )->pos -= delta;
2005           ( edges + 11 )->pos -= delta;
2006         }
2007
2008         edge3->flags |= AF_EDGE_DONE;
2009         if ( edge3->link )
2010           edge3->link->flags |= AF_EDGE_DONE;
2011       }
2012     }
2013
2014     if ( !skipped )
2015       goto Exit;
2016
2017     /*
2018      *  now hint the remaining edges (serifs and single) in order
2019      *  to complete our processing
2020      */
2021     for ( edge = edges; edge < edge_limit; edge++ )
2022     {
2023       if ( edge->flags & AF_EDGE_DONE )
2024         continue;
2025
2026       if ( edge->serif )
2027       {
2028         af_cjk_align_serif_edge( hints, edge->serif, edge );
2029         edge->flags |= AF_EDGE_DONE;
2030         skipped--;
2031       }
2032     }
2033
2034     if ( !skipped )
2035       goto Exit;
2036
2037     for ( edge = edges; edge < edge_limit; edge++ )
2038     {
2039       AF_Edge  before, after;
2040
2041
2042       if ( edge->flags & AF_EDGE_DONE )
2043         continue;
2044
2045       before = after = edge;
2046
2047       while ( --before >= edges )
2048         if ( before->flags & AF_EDGE_DONE )
2049           break;
2050
2051       while ( ++after < edge_limit )
2052         if ( after->flags & AF_EDGE_DONE )
2053           break;
2054
2055       if ( before >= edges || after < edge_limit )
2056       {
2057         if ( before < edges )
2058           af_cjk_align_serif_edge( hints, after, edge );
2059         else if ( after >= edge_limit )
2060           af_cjk_align_serif_edge( hints, before, edge );
2061         else
2062         {
2063           if ( after->fpos == before->fpos )
2064             edge->pos = before->pos;
2065           else
2066             edge->pos = before->pos +
2067                         FT_MulDiv( edge->fpos - before->fpos,
2068                                    after->pos - before->pos,
2069                                    after->fpos - before->fpos );
2070         }
2071       }
2072     }
2073
2074   Exit:
2075
2076 #ifdef FT_DEBUG_LEVEL_TRACE
2077     if ( !num_actions )
2078       FT_TRACE5(( "  (none)\n" ));
2079     FT_TRACE5(( "\n" ));
2080 #endif
2081
2082     return;
2083   }
2084
2085
2086   static void
2087   af_cjk_align_edge_points( AF_GlyphHints  hints,
2088                             AF_Dimension   dim )
2089   {
2090     AF_AxisHints  axis       = & hints->axis[dim];
2091     AF_Edge       edges      = axis->edges;
2092     AF_Edge       edge_limit = edges + axis->num_edges;
2093     AF_Edge       edge;
2094     FT_Bool       snapping;
2095
2096
2097     snapping = FT_BOOL( ( dim == AF_DIMENSION_HORZ             &&
2098                           AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) )  ||
2099                         ( dim == AF_DIMENSION_VERT             &&
2100                           AF_LATIN_HINTS_DO_VERT_SNAP( hints ) )  );
2101
2102     for ( edge = edges; edge < edge_limit; edge++ )
2103     {
2104       /* move the points of each segment     */
2105       /* in each edge to the edge's position */
2106       AF_Segment  seg = edge->first;
2107
2108
2109       if ( snapping )
2110       {
2111         do
2112         {
2113           AF_Point  point = seg->first;
2114
2115
2116           for (;;)
2117           {
2118             if ( dim == AF_DIMENSION_HORZ )
2119             {
2120               point->x      = edge->pos;
2121               point->flags |= AF_FLAG_TOUCH_X;
2122             }
2123             else
2124             {
2125               point->y      = edge->pos;
2126               point->flags |= AF_FLAG_TOUCH_Y;
2127             }
2128
2129             if ( point == seg->last )
2130               break;
2131
2132             point = point->next;
2133           }
2134
2135           seg = seg->edge_next;
2136
2137         } while ( seg != edge->first );
2138       }
2139       else
2140       {
2141         FT_Pos  delta = edge->pos - edge->opos;
2142
2143
2144         do
2145         {
2146           AF_Point  point = seg->first;
2147
2148
2149           for (;;)
2150           {
2151             if ( dim == AF_DIMENSION_HORZ )
2152             {
2153               point->x     += delta;
2154               point->flags |= AF_FLAG_TOUCH_X;
2155             }
2156             else
2157             {
2158               point->y     += delta;
2159               point->flags |= AF_FLAG_TOUCH_Y;
2160             }
2161
2162             if ( point == seg->last )
2163               break;
2164
2165             point = point->next;
2166           }
2167
2168           seg = seg->edge_next;
2169
2170         } while ( seg != edge->first );
2171       }
2172     }
2173   }
2174
2175
2176   /* Apply the complete hinting algorithm to a CJK glyph. */
2177
2178   FT_LOCAL_DEF( FT_Error )
2179   af_cjk_hints_apply( AF_GlyphHints  hints,
2180                       FT_Outline*    outline,
2181                       AF_CJKMetrics  metrics )
2182   {
2183     FT_Error  error;
2184     int       dim;
2185
2186     FT_UNUSED( metrics );
2187
2188
2189     error = af_glyph_hints_reload( hints, outline );
2190     if ( error )
2191       goto Exit;
2192
2193     /* analyze glyph outline */
2194     if ( AF_HINTS_DO_HORIZONTAL( hints ) )
2195     {
2196       error = af_cjk_hints_detect_features( hints, AF_DIMENSION_HORZ );
2197       if ( error )
2198         goto Exit;
2199
2200       af_cjk_hints_compute_blue_edges( hints, metrics, AF_DIMENSION_HORZ );
2201     }
2202
2203     if ( AF_HINTS_DO_VERTICAL( hints ) )
2204     {
2205       error = af_cjk_hints_detect_features( hints, AF_DIMENSION_VERT );
2206       if ( error )
2207         goto Exit;
2208
2209       af_cjk_hints_compute_blue_edges( hints, metrics, AF_DIMENSION_VERT );
2210     }
2211
2212     /* grid-fit the outline */
2213     for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
2214     {
2215       if ( ( dim == AF_DIMENSION_HORZ && AF_HINTS_DO_HORIZONTAL( hints ) ) ||
2216            ( dim == AF_DIMENSION_VERT && AF_HINTS_DO_VERTICAL( hints ) )   )
2217       {
2218
2219 #ifdef AF_CONFIG_OPTION_USE_WARPER
2220         if ( dim == AF_DIMENSION_HORZ                                  &&
2221              metrics->root.scaler.render_mode == FT_RENDER_MODE_NORMAL )
2222         {
2223           AF_WarperRec  warper;
2224           FT_Fixed      scale;
2225           FT_Pos        delta;
2226
2227
2228           af_warper_compute( &warper, hints, (AF_Dimension)dim,
2229                              &scale, &delta );
2230           af_glyph_hints_scale_dim( hints, (AF_Dimension)dim,
2231                                     scale, delta );
2232           continue;
2233         }
2234 #endif /* AF_CONFIG_OPTION_USE_WARPER */
2235
2236         af_cjk_hint_edges( hints, (AF_Dimension)dim );
2237         af_cjk_align_edge_points( hints, (AF_Dimension)dim );
2238         af_glyph_hints_align_strong_points( hints, (AF_Dimension)dim );
2239         af_glyph_hints_align_weak_points( hints, (AF_Dimension)dim );
2240       }
2241     }
2242
2243 #if 0
2244     af_glyph_hints_dump_points( hints );
2245     af_glyph_hints_dump_segments( hints );
2246     af_glyph_hints_dump_edges( hints );
2247 #endif
2248
2249     af_glyph_hints_save( hints, outline );
2250
2251   Exit:
2252     return error;
2253   }
2254
2255
2256   /*************************************************************************/
2257   /*************************************************************************/
2258   /*****                                                               *****/
2259   /*****                C J K   S C R I P T   C L A S S                *****/
2260   /*****                                                               *****/
2261   /*************************************************************************/
2262   /*************************************************************************/
2263
2264
2265   AF_DEFINE_WRITING_SYSTEM_CLASS(
2266     af_cjk_writing_system_class,
2267
2268     AF_WRITING_SYSTEM_CJK,
2269
2270     sizeof ( AF_CJKMetricsRec ),
2271
2272     (AF_WritingSystem_InitMetricsFunc) af_cjk_metrics_init,
2273     (AF_WritingSystem_ScaleMetricsFunc)af_cjk_metrics_scale,
2274     (AF_WritingSystem_DoneMetricsFunc) NULL,
2275
2276     (AF_WritingSystem_InitHintsFunc)   af_cjk_hints_init,
2277     (AF_WritingSystem_ApplyHintsFunc)  af_cjk_hints_apply
2278   )
2279
2280
2281 #else /* !AF_CONFIG_OPTION_CJK */
2282
2283
2284   AF_DEFINE_WRITING_SYSTEM_CLASS(
2285     af_cjk_writing_system_class,
2286
2287     AF_WRITING_SYSTEM_CJK,
2288
2289     sizeof ( AF_CJKMetricsRec ),
2290
2291     (AF_WritingSystem_InitMetricsFunc) NULL,
2292     (AF_WritingSystem_ScaleMetricsFunc)NULL,
2293     (AF_WritingSystem_DoneMetricsFunc) NULL,
2294
2295     (AF_WritingSystem_InitHintsFunc)   NULL,
2296     (AF_WritingSystem_ApplyHintsFunc)  NULL
2297   )
2298
2299
2300 #endif /* !AF_CONFIG_OPTION_CJK */
2301
2302
2303 /* END */