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