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