1 /***************************************************************************/
2 /*                                                                         */
3 /*  afcjk.c                                                                */
4 /*                                                                         */
5 /*    Auto-fitter hinting routines for CJK writing system (body).          */
6 /*                                                                         */
7 /*  Copyright 2006-2015 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 )
af_cjk_metrics_init_widths(AF_CJKMetrics metrics,FT_Face face)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
af_cjk_metrics_init_blues(AF_CJKMetrics metrics,FT_Face face)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_UInt     num_fills;
264     FT_UInt     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 )
af_cjk_metrics_check_digits(AF_CJKMetrics metrics,FT_Face face)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 )
af_cjk_metrics_init(AF_CJKMetrics metrics,FT_Face face)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
af_cjk_metrics_scale_dim(AF_CJKMetrics metrics,AF_Scaler scaler,AF_Dimension dim)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 )
af_cjk_metrics_scale(AF_CJKMetrics metrics,AF_Scaler scaler)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
af_cjk_hints_compute_segments(AF_GlyphHints hints,AF_Dimension dim)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       FT_UInt   f0   = pt->flags & AF_FLAG_CONTROL;
724       FT_UInt   f1;
725 
726 
727       seg->flags &= ~AF_EDGE_ROUND;
728 
729       for ( ; pt != last; f0 = f1 )
730       {
731         pt = pt->next;
732         f1 = 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
af_cjk_hints_link_segments(AF_GlyphHints hints,AF_Dimension dim)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  = NULL;
867                 seg->serif = link1;
868               }
869               else if ( link == link2 )
870               {
871                 seg->link  = NULL;
872                 seg->serif = seg1;
873               }
874             }
875           }
876           else
877           {
878             seg1->link = link1->link = NULL;
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 = NULL;
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
af_cjk_hints_compute_edges(AF_GlyphHints hints,AF_Dimension dim)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 = NULL;
1163       }
1164     }
1165 
1166   Exit:
1167     return error;
1168   }
1169 
1170 
1171   /* Detect segments and edges for given dimension. */
1172 
1173   static FT_Error
af_cjk_hints_detect_features(AF_GlyphHints hints,AF_Dimension dim)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   static void
af_cjk_hints_compute_blue_edges(AF_GlyphHints hints,AF_CJKMetrics metrics,AF_Dimension dim)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 )
af_cjk_hints_init(AF_GlyphHints hints,AF_CJKMetrics metrics)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 #if 0 /* 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 #ifdef AF_CONFIG_OPTION_USE_WARPER
1338     /* get (global) warper flag */
1339     if ( !metrics->root.globals->module->warping )
1340       scaler_flags |= AF_SCALER_FLAG_NO_WARPER;
1341 #endif
1342 
1343     hints->scaler_flags = scaler_flags;
1344     hints->other_flags  = other_flags;
1345 
1346     return FT_Err_Ok;
1347   }
1348 
1349 
1350   /*************************************************************************/
1351   /*************************************************************************/
1352   /*****                                                               *****/
1353   /*****          C J K   G L Y P H   G R I D - F I T T I N G          *****/
1354   /*****                                                               *****/
1355   /*************************************************************************/
1356   /*************************************************************************/
1357 
1358   /* Snap a given width in scaled coordinates to one of the */
1359   /* current standard widths.                               */
1360 
1361   static FT_Pos
af_cjk_snap_width(AF_Width widths,FT_UInt count,FT_Pos width)1362   af_cjk_snap_width( AF_Width  widths,
1363                      FT_UInt   count,
1364                      FT_Pos    width )
1365   {
1366     FT_UInt  n;
1367     FT_Pos   best      = 64 + 32 + 2;
1368     FT_Pos   reference = width;
1369     FT_Pos   scaled;
1370 
1371 
1372     for ( n = 0; n < count; n++ )
1373     {
1374       FT_Pos  w;
1375       FT_Pos  dist;
1376 
1377 
1378       w = widths[n].cur;
1379       dist = width - w;
1380       if ( dist < 0 )
1381         dist = -dist;
1382       if ( dist < best )
1383       {
1384         best      = dist;
1385         reference = w;
1386       }
1387     }
1388 
1389     scaled = FT_PIX_ROUND( reference );
1390 
1391     if ( width >= reference )
1392     {
1393       if ( width < scaled + 48 )
1394         width = reference;
1395     }
1396     else
1397     {
1398       if ( width > scaled - 48 )
1399         width = reference;
1400     }
1401 
1402     return width;
1403   }
1404 
1405 
1406   /* Compute the snapped width of a given stem.                          */
1407   /* There is a lot of voodoo in this function; changing the hard-coded  */
1408   /* parameters influence the whole hinting process.                     */
1409 
1410   static FT_Pos
af_cjk_compute_stem_width(AF_GlyphHints hints,AF_Dimension dim,FT_Pos width,FT_UInt base_flags,FT_UInt stem_flags)1411   af_cjk_compute_stem_width( AF_GlyphHints  hints,
1412                              AF_Dimension   dim,
1413                              FT_Pos         width,
1414                              FT_UInt        base_flags,
1415                              FT_UInt        stem_flags )
1416   {
1417     AF_CJKMetrics  metrics  = (AF_CJKMetrics)hints->metrics;
1418     AF_CJKAxis     axis     = &metrics->axis[dim];
1419     FT_Pos         dist     = width;
1420     FT_Int         sign     = 0;
1421     FT_Bool        vertical = FT_BOOL( dim == AF_DIMENSION_VERT );
1422 
1423     FT_UNUSED( base_flags );
1424     FT_UNUSED( stem_flags );
1425 
1426 
1427     if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) )
1428       return width;
1429 
1430     if ( dist < 0 )
1431     {
1432       dist = -width;
1433       sign = 1;
1434     }
1435 
1436     if ( (  vertical && !AF_LATIN_HINTS_DO_VERT_SNAP( hints ) ) ||
1437          ( !vertical && !AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) ) )
1438     {
1439       /* smooth hinting process: very lightly quantize the stem width */
1440 
1441       if ( axis->width_count > 0 )
1442       {
1443         if ( FT_ABS( dist - axis->widths[0].cur ) < 40 )
1444         {
1445           dist = axis->widths[0].cur;
1446           if ( dist < 48 )
1447             dist = 48;
1448 
1449           goto Done_Width;
1450         }
1451       }
1452 
1453       if ( dist < 54 )
1454         dist += ( 54 - dist ) / 2 ;
1455       else if ( dist < 3 * 64 )
1456       {
1457         FT_Pos  delta;
1458 
1459 
1460         delta  = dist & 63;
1461         dist  &= -64;
1462 
1463         if ( delta < 10 )
1464           dist += delta;
1465         else if ( delta < 22 )
1466           dist += 10;
1467         else if ( delta < 42 )
1468           dist += delta;
1469         else if ( delta < 54 )
1470           dist += 54;
1471         else
1472           dist += delta;
1473       }
1474     }
1475     else
1476     {
1477       /* strong hinting process: snap the stem width to integer pixels */
1478 
1479       dist = af_cjk_snap_width( axis->widths, axis->width_count, dist );
1480 
1481       if ( vertical )
1482       {
1483         /* in the case of vertical hinting, always round */
1484         /* the stem heights to integer pixels            */
1485 
1486         if ( dist >= 64 )
1487           dist = ( dist + 16 ) & ~63;
1488         else
1489           dist = 64;
1490       }
1491       else
1492       {
1493         if ( AF_LATIN_HINTS_DO_MONO( hints ) )
1494         {
1495           /* monochrome horizontal hinting: snap widths to integer pixels */
1496           /* with a different threshold                                   */
1497 
1498           if ( dist < 64 )
1499             dist = 64;
1500           else
1501             dist = ( dist + 32 ) & ~63;
1502         }
1503         else
1504         {
1505           /* for horizontal anti-aliased hinting, we adopt a more subtle */
1506           /* approach: we strengthen small stems, round stems whose size */
1507           /* is between 1 and 2 pixels to an integer, otherwise nothing  */
1508 
1509           if ( dist < 48 )
1510             dist = ( dist + 64 ) >> 1;
1511 
1512           else if ( dist < 128 )
1513             dist = ( dist + 22 ) & ~63;
1514           else
1515             /* round otherwise to prevent color fringes in LCD mode */
1516             dist = ( dist + 32 ) & ~63;
1517         }
1518       }
1519     }
1520 
1521   Done_Width:
1522     if ( sign )
1523       dist = -dist;
1524 
1525     return dist;
1526   }
1527 
1528 
1529   /* Align one stem edge relative to the previous stem edge. */
1530 
1531   static void
af_cjk_align_linked_edge(AF_GlyphHints hints,AF_Dimension dim,AF_Edge base_edge,AF_Edge stem_edge)1532   af_cjk_align_linked_edge( AF_GlyphHints  hints,
1533                             AF_Dimension   dim,
1534                             AF_Edge        base_edge,
1535                             AF_Edge        stem_edge )
1536   {
1537     FT_Pos  dist = stem_edge->opos - base_edge->opos;
1538 
1539     FT_Pos  fitted_width = af_cjk_compute_stem_width( hints, dim, dist,
1540                                                       base_edge->flags,
1541                                                       stem_edge->flags );
1542 
1543 
1544     stem_edge->pos = base_edge->pos + fitted_width;
1545 
1546     FT_TRACE5(( "  CJKLINK: edge %d @%d (opos=%.2f) linked to %.2f,"
1547                 " dist was %.2f, now %.2f\n",
1548                 stem_edge - hints->axis[dim].edges, stem_edge->fpos,
1549                 stem_edge->opos / 64.0, stem_edge->pos / 64.0,
1550                 dist / 64.0, fitted_width / 64.0 ));
1551   }
1552 
1553 
1554   /* Shift the coordinates of the `serif' edge by the same amount */
1555   /* as the corresponding `base' edge has been moved already.     */
1556 
1557   static void
af_cjk_align_serif_edge(AF_GlyphHints hints,AF_Edge base,AF_Edge serif)1558   af_cjk_align_serif_edge( AF_GlyphHints  hints,
1559                            AF_Edge        base,
1560                            AF_Edge        serif )
1561   {
1562     FT_UNUSED( hints );
1563 
1564     serif->pos = base->pos + ( serif->opos - base->opos );
1565   }
1566 
1567 
1568   /*************************************************************************/
1569   /*************************************************************************/
1570   /*************************************************************************/
1571   /****                                                                 ****/
1572   /****                    E D G E   H I N T I N G                      ****/
1573   /****                                                                 ****/
1574   /*************************************************************************/
1575   /*************************************************************************/
1576   /*************************************************************************/
1577 
1578 
1579 #define AF_LIGHT_MODE_MAX_HORZ_GAP    9
1580 #define AF_LIGHT_MODE_MAX_VERT_GAP   15
1581 #define AF_LIGHT_MODE_MAX_DELTA_ABS  14
1582 
1583 
1584   static FT_Pos
af_hint_normal_stem(AF_GlyphHints hints,AF_Edge edge,AF_Edge edge2,FT_Pos anchor,AF_Dimension dim)1585   af_hint_normal_stem( AF_GlyphHints  hints,
1586                        AF_Edge        edge,
1587                        AF_Edge        edge2,
1588                        FT_Pos         anchor,
1589                        AF_Dimension   dim )
1590   {
1591     FT_Pos  org_len, cur_len, org_center;
1592     FT_Pos  cur_pos1, cur_pos2;
1593     FT_Pos  d_off1, u_off1, d_off2, u_off2, delta;
1594     FT_Pos  offset;
1595     FT_Pos  threshold = 64;
1596 
1597 
1598     if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) )
1599     {
1600       if ( ( edge->flags  & AF_EDGE_ROUND ) &&
1601            ( edge2->flags & AF_EDGE_ROUND ) )
1602       {
1603         if ( dim == AF_DIMENSION_VERT )
1604           threshold = 64 - AF_LIGHT_MODE_MAX_HORZ_GAP;
1605         else
1606           threshold = 64 - AF_LIGHT_MODE_MAX_VERT_GAP;
1607       }
1608       else
1609       {
1610         if ( dim == AF_DIMENSION_VERT )
1611           threshold = 64 - AF_LIGHT_MODE_MAX_HORZ_GAP / 3;
1612         else
1613           threshold = 64 - AF_LIGHT_MODE_MAX_VERT_GAP / 3;
1614       }
1615     }
1616 
1617     org_len    = edge2->opos - edge->opos;
1618     cur_len    = af_cjk_compute_stem_width( hints, dim, org_len,
1619                                             edge->flags,
1620                                             edge2->flags );
1621 
1622     org_center = ( edge->opos + edge2->opos ) / 2 + anchor;
1623     cur_pos1   = org_center - cur_len / 2;
1624     cur_pos2   = cur_pos1 + cur_len;
1625     d_off1     = cur_pos1 - FT_PIX_FLOOR( cur_pos1 );
1626     d_off2     = cur_pos2 - FT_PIX_FLOOR( cur_pos2 );
1627     u_off1     = 64 - d_off1;
1628     u_off2     = 64 - d_off2;
1629     delta      = 0;
1630 
1631 
1632     if ( d_off1 == 0 || d_off2 == 0 )
1633       goto Exit;
1634 
1635     if ( cur_len <= threshold )
1636     {
1637       if ( d_off2 < cur_len )
1638       {
1639         if ( u_off1 <= d_off2 )
1640           delta =  u_off1;
1641         else
1642           delta = -d_off2;
1643       }
1644 
1645       goto Exit;
1646     }
1647 
1648     if ( threshold < 64 )
1649     {
1650       if ( d_off1 >= threshold || u_off1 >= threshold ||
1651            d_off2 >= threshold || u_off2 >= threshold )
1652         goto Exit;
1653     }
1654 
1655     offset = cur_len & 63;
1656 
1657     if ( offset < 32 )
1658     {
1659       if ( u_off1 <= offset || d_off2 <= offset )
1660         goto Exit;
1661     }
1662     else
1663       offset = 64 - threshold;
1664 
1665     d_off1 = threshold - u_off1;
1666     u_off1 = u_off1    - offset;
1667     u_off2 = threshold - d_off2;
1668     d_off2 = d_off2    - offset;
1669 
1670     if ( d_off1 <= u_off1 )
1671       u_off1 = -d_off1;
1672 
1673     if ( d_off2 <= u_off2 )
1674       u_off2 = -d_off2;
1675 
1676     if ( FT_ABS( u_off1 ) <= FT_ABS( u_off2 ) )
1677       delta = u_off1;
1678     else
1679       delta = u_off2;
1680 
1681   Exit:
1682 
1683 #if 1
1684     if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) )
1685     {
1686       if ( delta > AF_LIGHT_MODE_MAX_DELTA_ABS )
1687         delta = AF_LIGHT_MODE_MAX_DELTA_ABS;
1688       else if ( delta < -AF_LIGHT_MODE_MAX_DELTA_ABS )
1689         delta = -AF_LIGHT_MODE_MAX_DELTA_ABS;
1690     }
1691 #endif
1692 
1693     cur_pos1 += delta;
1694 
1695     if ( edge->opos < edge2->opos )
1696     {
1697       edge->pos  = cur_pos1;
1698       edge2->pos = cur_pos1 + cur_len;
1699     }
1700     else
1701     {
1702       edge->pos  = cur_pos1 + cur_len;
1703       edge2->pos = cur_pos1;
1704     }
1705 
1706     return delta;
1707   }
1708 
1709 
1710   /* The main grid-fitting routine. */
1711 
1712   static void
af_cjk_hint_edges(AF_GlyphHints hints,AF_Dimension dim)1713   af_cjk_hint_edges( AF_GlyphHints  hints,
1714                      AF_Dimension   dim )
1715   {
1716     AF_AxisHints  axis       = &hints->axis[dim];
1717     AF_Edge       edges      = axis->edges;
1718     AF_Edge       edge_limit = edges + axis->num_edges;
1719     FT_PtrDist    n_edges;
1720     AF_Edge       edge;
1721     AF_Edge       anchor   = NULL;
1722     FT_Pos        delta    = 0;
1723     FT_Int        skipped  = 0;
1724     FT_Bool       has_last_stem = FALSE;
1725     FT_Pos        last_stem_pos = 0;
1726 
1727 #ifdef FT_DEBUG_LEVEL_TRACE
1728     FT_UInt       num_actions = 0;
1729 #endif
1730 
1731 
1732     FT_TRACE5(( "cjk %s edge hinting (style `%s')\n",
1733                 dim == AF_DIMENSION_VERT ? "horizontal" : "vertical",
1734                 af_style_names[hints->metrics->style_class->style] ));
1735 
1736     /* we begin by aligning all stems relative to the blue zone */
1737 
1738     if ( AF_HINTS_DO_BLUES( hints ) )
1739     {
1740       for ( edge = edges; edge < edge_limit; edge++ )
1741       {
1742         AF_Width  blue;
1743         AF_Edge   edge1, edge2;
1744 
1745 
1746         if ( edge->flags & AF_EDGE_DONE )
1747           continue;
1748 
1749         blue  = edge->blue_edge;
1750         edge1 = NULL;
1751         edge2 = edge->link;
1752 
1753         if ( blue )
1754         {
1755           edge1 = edge;
1756         }
1757         else if ( edge2 && edge2->blue_edge )
1758         {
1759           blue  = edge2->blue_edge;
1760           edge1 = edge2;
1761           edge2 = edge;
1762         }
1763 
1764         if ( !edge1 )
1765           continue;
1766 
1767 #ifdef FT_DEBUG_LEVEL_TRACE
1768         FT_TRACE5(( "  CJKBLUE: edge %d @%d (opos=%.2f) snapped to %.2f,"
1769                     " was %.2f\n",
1770                     edge1 - edges, edge1->fpos, edge1->opos / 64.0,
1771                     blue->fit / 64.0, edge1->pos / 64.0 ));
1772 
1773         num_actions++;
1774 #endif
1775 
1776         edge1->pos    = blue->fit;
1777         edge1->flags |= AF_EDGE_DONE;
1778 
1779         if ( edge2 && !edge2->blue_edge )
1780         {
1781           af_cjk_align_linked_edge( hints, dim, edge1, edge2 );
1782           edge2->flags |= AF_EDGE_DONE;
1783 
1784 #ifdef FT_DEBUG_LEVEL_TRACE
1785           num_actions++;
1786 #endif
1787         }
1788 
1789         if ( !anchor )
1790           anchor = edge;
1791       }
1792     }
1793 
1794     /* now we align all stem edges. */
1795     for ( edge = edges; edge < edge_limit; edge++ )
1796     {
1797       AF_Edge  edge2;
1798 
1799 
1800       if ( edge->flags & AF_EDGE_DONE )
1801         continue;
1802 
1803       /* skip all non-stem edges */
1804       edge2 = edge->link;
1805       if ( !edge2 )
1806       {
1807         skipped++;
1808         continue;
1809       }
1810 
1811       /* Some CJK characters have so many stems that
1812        * the hinter is likely to merge two adjacent ones.
1813        * To solve this problem, if either edge of a stem
1814        * is too close to the previous one, we avoid
1815        * aligning the two edges, but rather interpolate
1816        * their locations at the end of this function in
1817        * order to preserve the space between the stems.
1818        */
1819       if ( has_last_stem                       &&
1820            ( edge->pos  < last_stem_pos + 64 ||
1821              edge2->pos < last_stem_pos + 64 ) )
1822       {
1823         skipped++;
1824         continue;
1825       }
1826 
1827       /* now align the stem */
1828 
1829       /* this should not happen, but it's better to be safe */
1830       if ( edge2->blue_edge )
1831       {
1832         FT_TRACE5(( "ASSERTION FAILED for edge %d\n", edge2-edges ));
1833 
1834         af_cjk_align_linked_edge( hints, dim, edge2, edge );
1835         edge->flags |= AF_EDGE_DONE;
1836 
1837 #ifdef FT_DEBUG_LEVEL_TRACE
1838         num_actions++;
1839 #endif
1840 
1841         continue;
1842       }
1843 
1844       if ( edge2 < edge )
1845       {
1846         af_cjk_align_linked_edge( hints, dim, edge2, edge );
1847         edge->flags |= AF_EDGE_DONE;
1848 
1849 #ifdef FT_DEBUG_LEVEL_TRACE
1850         num_actions++;
1851 #endif
1852 
1853         /* We rarely reaches here it seems;
1854          * usually the two edges belonging
1855          * to one stem are marked as DONE together
1856          */
1857         has_last_stem = TRUE;
1858         last_stem_pos = edge->pos;
1859         continue;
1860       }
1861 
1862       if ( dim != AF_DIMENSION_VERT && !anchor )
1863       {
1864 
1865 #if 0
1866         if ( fixedpitch )
1867         {
1868           AF_Edge     left  = edge;
1869           AF_Edge     right = edge_limit - 1;
1870           AF_EdgeRec  left1, left2, right1, right2;
1871           FT_Pos      target, center1, center2;
1872           FT_Pos      delta1, delta2, d1, d2;
1873 
1874 
1875           while ( right > left && !right->link )
1876             right--;
1877 
1878           left1  = *left;
1879           left2  = *left->link;
1880           right1 = *right->link;
1881           right2 = *right;
1882 
1883           delta  = ( ( ( hinter->pp2.x + 32 ) & -64 ) - hinter->pp2.x ) / 2;
1884           target = left->opos + ( right->opos - left->opos ) / 2 + delta - 16;
1885 
1886           delta1  = delta;
1887           delta1 += af_hint_normal_stem( hints, left, left->link,
1888                                          delta1, 0 );
1889 
1890           if ( left->link != right )
1891             af_hint_normal_stem( hints, right->link, right, delta1, 0 );
1892 
1893           center1 = left->pos + ( right->pos - left->pos ) / 2;
1894 
1895           if ( center1 >= target )
1896             delta2 = delta - 32;
1897           else
1898             delta2 = delta + 32;
1899 
1900           delta2 += af_hint_normal_stem( hints, &left1, &left2, delta2, 0 );
1901 
1902           if ( delta1 != delta2 )
1903           {
1904             if ( left->link != right )
1905               af_hint_normal_stem( hints, &right1, &right2, delta2, 0 );
1906 
1907             center2 = left1.pos + ( right2.pos - left1.pos ) / 2;
1908 
1909             d1 = center1 - target;
1910             d2 = center2 - target;
1911 
1912             if ( FT_ABS( d2 ) < FT_ABS( d1 ) )
1913             {
1914               left->pos       = left1.pos;
1915               left->link->pos = left2.pos;
1916 
1917               if ( left->link != right )
1918               {
1919                 right->link->pos = right1.pos;
1920                 right->pos       = right2.pos;
1921               }
1922 
1923               delta1 = delta2;
1924             }
1925           }
1926 
1927           delta               = delta1;
1928           right->link->flags |= AF_EDGE_DONE;
1929           right->flags       |= AF_EDGE_DONE;
1930         }
1931         else
1932 
1933 #endif /* 0 */
1934 
1935           delta = af_hint_normal_stem( hints, edge, edge2, 0,
1936                                        AF_DIMENSION_HORZ );
1937       }
1938       else
1939         af_hint_normal_stem( hints, edge, edge2, delta, dim );
1940 
1941 #if 0
1942       printf( "stem (%d,%d) adjusted (%.1f,%.1f)\n",
1943                edge - edges, edge2 - edges,
1944                ( edge->pos - edge->opos ) / 64.0,
1945                ( edge2->pos - edge2->opos ) / 64.0 );
1946 #endif
1947 
1948       anchor = edge;
1949       edge->flags  |= AF_EDGE_DONE;
1950       edge2->flags |= AF_EDGE_DONE;
1951       has_last_stem = TRUE;
1952       last_stem_pos = edge2->pos;
1953     }
1954 
1955     /* make sure that lowercase m's maintain their symmetry */
1956 
1957     /* In general, lowercase m's have six vertical edges if they are sans */
1958     /* serif, or twelve if they are with serifs.  This implementation is  */
1959     /* based on that assumption, and seems to work very well with most    */
1960     /* faces.  However, if for a certain face this assumption is not      */
1961     /* true, the m is just rendered like before.  In addition, any stem   */
1962     /* correction will only be applied to symmetrical glyphs (even if the */
1963     /* glyph is not an m), so the potential for unwanted distortion is    */
1964     /* relatively low.                                                    */
1965 
1966     /* We don't handle horizontal edges since we can't easily assure that */
1967     /* the third (lowest) stem aligns with the base line; it might end up */
1968     /* one pixel higher or lower.                                         */
1969 
1970     n_edges = edge_limit - edges;
1971     if ( dim == AF_DIMENSION_HORZ && ( n_edges == 6 || n_edges == 12 ) )
1972     {
1973       AF_Edge  edge1, edge2, edge3;
1974       FT_Pos   dist1, dist2, span;
1975 
1976 
1977       if ( n_edges == 6 )
1978       {
1979         edge1 = edges;
1980         edge2 = edges + 2;
1981         edge3 = edges + 4;
1982       }
1983       else
1984       {
1985         edge1 = edges + 1;
1986         edge2 = edges + 5;
1987         edge3 = edges + 9;
1988       }
1989 
1990       dist1 = edge2->opos - edge1->opos;
1991       dist2 = edge3->opos - edge2->opos;
1992 
1993       span = dist1 - dist2;
1994       if ( span < 0 )
1995         span = -span;
1996 
1997       if ( edge1->link == edge1 + 1 &&
1998            edge2->link == edge2 + 1 &&
1999            edge3->link == edge3 + 1 && span < 8 )
2000       {
2001         delta = edge3->pos - ( 2 * edge2->pos - edge1->pos );
2002         edge3->pos -= delta;
2003         if ( edge3->link )
2004           edge3->link->pos -= delta;
2005 
2006         /* move the serifs along with the stem */
2007         if ( n_edges == 12 )
2008         {
2009           ( edges + 8 )->pos -= delta;
2010           ( edges + 11 )->pos -= delta;
2011         }
2012 
2013         edge3->flags |= AF_EDGE_DONE;
2014         if ( edge3->link )
2015           edge3->link->flags |= AF_EDGE_DONE;
2016       }
2017     }
2018 
2019     if ( !skipped )
2020       goto Exit;
2021 
2022     /*
2023      *  now hint the remaining edges (serifs and single) in order
2024      *  to complete our processing
2025      */
2026     for ( edge = edges; edge < edge_limit; edge++ )
2027     {
2028       if ( edge->flags & AF_EDGE_DONE )
2029         continue;
2030 
2031       if ( edge->serif )
2032       {
2033         af_cjk_align_serif_edge( hints, edge->serif, edge );
2034         edge->flags |= AF_EDGE_DONE;
2035         skipped--;
2036       }
2037     }
2038 
2039     if ( !skipped )
2040       goto Exit;
2041 
2042     for ( edge = edges; edge < edge_limit; edge++ )
2043     {
2044       AF_Edge  before, after;
2045 
2046 
2047       if ( edge->flags & AF_EDGE_DONE )
2048         continue;
2049 
2050       before = after = edge;
2051 
2052       while ( --before >= edges )
2053         if ( before->flags & AF_EDGE_DONE )
2054           break;
2055 
2056       while ( ++after < edge_limit )
2057         if ( after->flags & AF_EDGE_DONE )
2058           break;
2059 
2060       if ( before >= edges || after < edge_limit )
2061       {
2062         if ( before < edges )
2063           af_cjk_align_serif_edge( hints, after, edge );
2064         else if ( after >= edge_limit )
2065           af_cjk_align_serif_edge( hints, before, edge );
2066         else
2067         {
2068           if ( after->fpos == before->fpos )
2069             edge->pos = before->pos;
2070           else
2071             edge->pos = before->pos +
2072                         FT_MulDiv( edge->fpos - before->fpos,
2073                                    after->pos - before->pos,
2074                                    after->fpos - before->fpos );
2075         }
2076       }
2077     }
2078 
2079   Exit:
2080 
2081 #ifdef FT_DEBUG_LEVEL_TRACE
2082     if ( !num_actions )
2083       FT_TRACE5(( "  (none)\n" ));
2084     FT_TRACE5(( "\n" ));
2085 #endif
2086 
2087     return;
2088   }
2089 
2090 
2091   static void
af_cjk_align_edge_points(AF_GlyphHints hints,AF_Dimension dim)2092   af_cjk_align_edge_points( AF_GlyphHints  hints,
2093                             AF_Dimension   dim )
2094   {
2095     AF_AxisHints  axis       = & hints->axis[dim];
2096     AF_Edge       edges      = axis->edges;
2097     AF_Edge       edge_limit = edges + axis->num_edges;
2098     AF_Edge       edge;
2099     FT_Bool       snapping;
2100 
2101 
2102     snapping = FT_BOOL( ( dim == AF_DIMENSION_HORZ             &&
2103                           AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) )  ||
2104                         ( dim == AF_DIMENSION_VERT             &&
2105                           AF_LATIN_HINTS_DO_VERT_SNAP( hints ) )  );
2106 
2107     for ( edge = edges; edge < edge_limit; edge++ )
2108     {
2109       /* move the points of each segment     */
2110       /* in each edge to the edge's position */
2111       AF_Segment  seg = edge->first;
2112 
2113 
2114       if ( snapping )
2115       {
2116         do
2117         {
2118           AF_Point  point = seg->first;
2119 
2120 
2121           for (;;)
2122           {
2123             if ( dim == AF_DIMENSION_HORZ )
2124             {
2125               point->x      = edge->pos;
2126               point->flags |= AF_FLAG_TOUCH_X;
2127             }
2128             else
2129             {
2130               point->y      = edge->pos;
2131               point->flags |= AF_FLAG_TOUCH_Y;
2132             }
2133 
2134             if ( point == seg->last )
2135               break;
2136 
2137             point = point->next;
2138           }
2139 
2140           seg = seg->edge_next;
2141 
2142         } while ( seg != edge->first );
2143       }
2144       else
2145       {
2146         FT_Pos  delta = edge->pos - edge->opos;
2147 
2148 
2149         do
2150         {
2151           AF_Point  point = seg->first;
2152 
2153 
2154           for (;;)
2155           {
2156             if ( dim == AF_DIMENSION_HORZ )
2157             {
2158               point->x     += delta;
2159               point->flags |= AF_FLAG_TOUCH_X;
2160             }
2161             else
2162             {
2163               point->y     += delta;
2164               point->flags |= AF_FLAG_TOUCH_Y;
2165             }
2166 
2167             if ( point == seg->last )
2168               break;
2169 
2170             point = point->next;
2171           }
2172 
2173           seg = seg->edge_next;
2174 
2175         } while ( seg != edge->first );
2176       }
2177     }
2178   }
2179 
2180 
2181   /* Apply the complete hinting algorithm to a CJK glyph. */
2182 
2183   FT_LOCAL_DEF( FT_Error )
af_cjk_hints_apply(AF_GlyphHints hints,FT_Outline * outline,AF_CJKMetrics metrics)2184   af_cjk_hints_apply( AF_GlyphHints  hints,
2185                       FT_Outline*    outline,
2186                       AF_CJKMetrics  metrics )
2187   {
2188     FT_Error  error;
2189     int       dim;
2190 
2191     FT_UNUSED( metrics );
2192 
2193 
2194     error = af_glyph_hints_reload( hints, outline );
2195     if ( error )
2196       goto Exit;
2197 
2198     /* analyze glyph outline */
2199 #ifdef AF_CONFIG_OPTION_USE_WARPER
2200     if ( ( metrics->root.scaler.render_mode == FT_RENDER_MODE_LIGHT &&
2201            AF_HINTS_DO_WARP( hints )                                ) ||
2202          AF_HINTS_DO_HORIZONTAL( hints )                              )
2203 #else
2204     if ( AF_HINTS_DO_HORIZONTAL( hints ) )
2205 #endif
2206     {
2207       error = af_cjk_hints_detect_features( hints, AF_DIMENSION_HORZ );
2208       if ( error )
2209         goto Exit;
2210 
2211       af_cjk_hints_compute_blue_edges( hints, metrics, AF_DIMENSION_HORZ );
2212     }
2213 
2214     if ( AF_HINTS_DO_VERTICAL( hints ) )
2215     {
2216       error = af_cjk_hints_detect_features( hints, AF_DIMENSION_VERT );
2217       if ( error )
2218         goto Exit;
2219 
2220       af_cjk_hints_compute_blue_edges( hints, metrics, AF_DIMENSION_VERT );
2221     }
2222 
2223     /* grid-fit the outline */
2224     for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
2225     {
2226       if ( ( dim == AF_DIMENSION_HORZ && AF_HINTS_DO_HORIZONTAL( hints ) ) ||
2227            ( dim == AF_DIMENSION_VERT && AF_HINTS_DO_VERTICAL( hints ) )   )
2228       {
2229 
2230 #ifdef AF_CONFIG_OPTION_USE_WARPER
2231         if ( dim == AF_DIMENSION_HORZ                                 &&
2232              metrics->root.scaler.render_mode == FT_RENDER_MODE_LIGHT &&
2233              AF_HINTS_DO_WARP( hints )                                )
2234         {
2235           AF_WarperRec  warper;
2236           FT_Fixed      scale;
2237           FT_Pos        delta;
2238 
2239 
2240           af_warper_compute( &warper, hints, (AF_Dimension)dim,
2241                              &scale, &delta );
2242           af_glyph_hints_scale_dim( hints, (AF_Dimension)dim,
2243                                     scale, delta );
2244           continue;
2245         }
2246 #endif /* AF_CONFIG_OPTION_USE_WARPER */
2247 
2248         af_cjk_hint_edges( hints, (AF_Dimension)dim );
2249         af_cjk_align_edge_points( hints, (AF_Dimension)dim );
2250         af_glyph_hints_align_strong_points( hints, (AF_Dimension)dim );
2251         af_glyph_hints_align_weak_points( hints, (AF_Dimension)dim );
2252       }
2253     }
2254 
2255     af_glyph_hints_save( hints, outline );
2256 
2257   Exit:
2258     return error;
2259   }
2260 
2261 
2262   /*************************************************************************/
2263   /*************************************************************************/
2264   /*****                                                               *****/
2265   /*****                C J K   S C R I P T   C L A S S                *****/
2266   /*****                                                               *****/
2267   /*************************************************************************/
2268   /*************************************************************************/
2269 
2270 
2271   AF_DEFINE_WRITING_SYSTEM_CLASS(
2272     af_cjk_writing_system_class,
2273 
2274     AF_WRITING_SYSTEM_CJK,
2275 
2276     sizeof ( AF_CJKMetricsRec ),
2277 
2278     (AF_WritingSystem_InitMetricsFunc) af_cjk_metrics_init,
2279     (AF_WritingSystem_ScaleMetricsFunc)af_cjk_metrics_scale,
2280     (AF_WritingSystem_DoneMetricsFunc) NULL,
2281 
2282     (AF_WritingSystem_InitHintsFunc)   af_cjk_hints_init,
2283     (AF_WritingSystem_ApplyHintsFunc)  af_cjk_hints_apply
2284   )
2285 
2286 
2287 #else /* !AF_CONFIG_OPTION_CJK */
2288 
2289 
2290   AF_DEFINE_WRITING_SYSTEM_CLASS(
2291     af_cjk_writing_system_class,
2292 
2293     AF_WRITING_SYSTEM_CJK,
2294 
2295     sizeof ( AF_CJKMetricsRec ),
2296 
2297     (AF_WritingSystem_InitMetricsFunc) NULL,
2298     (AF_WritingSystem_ScaleMetricsFunc)NULL,
2299     (AF_WritingSystem_DoneMetricsFunc) NULL,
2300 
2301     (AF_WritingSystem_InitHintsFunc)   NULL,
2302     (AF_WritingSystem_ApplyHintsFunc)  NULL
2303   )
2304 
2305 
2306 #endif /* !AF_CONFIG_OPTION_CJK */
2307 
2308 
2309 /* END */
2310