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