`number_set_parse': Improve diagnostics.
[ttfautohint.git] / lib / talatin.c
bloba8b515c64e67c43a42c9a115a615feee6a72c7f5
1 /* talatin.c */
3 /*
4 * Copyright (C) 2011-2012 by Werner Lemberg.
6 * This file is part of the ttfautohint library, and may only be used,
7 * modified, and distributed under the terms given in `COPYING'. By
8 * continuing to use, modify, or distribute this file you indicate that you
9 * have read `COPYING' and understand and accept it fully.
11 * The file `COPYING' mentioned in the previous paragraph is distributed
12 * with the ttfautohint library.
16 /* originally file `aflatin.c' (2011-Mar-28) from FreeType */
18 /* heavily modified 2011 by Werner Lemberg <wl@gnu.org> */
20 #include <string.h>
22 #include <ft2build.h>
23 #include FT_ADVANCES_H
24 #include FT_TRUETYPE_TABLES_H
26 #include "taglobal.h"
27 #include "talatin.h"
28 #include "tasort.h"
31 #ifdef TA_CONFIG_OPTION_USE_WARPER
32 #include "tawarp.h"
33 #endif
36 /* find segments and links, compute all stem widths, and initialize */
37 /* standard width and height for the glyph with given charcode */
39 void
40 ta_latin_metrics_init_widths(TA_LatinMetrics metrics,
41 FT_Face face)
43 /* scan the array of segments in each direction */
44 TA_GlyphHintsRec hints[1];
47 TA_LOG(("standard widths computation\n"
48 "===========================\n\n"));
50 ta_glyph_hints_init(hints);
52 metrics->axis[TA_DIMENSION_HORZ].width_count = 0;
53 metrics->axis[TA_DIMENSION_VERT].width_count = 0;
56 FT_Error error;
57 FT_UInt glyph_index;
58 int dim;
59 TA_LatinMetricsRec dummy[1];
60 TA_Scaler scaler = &dummy->root.scaler;
63 glyph_index = FT_Get_Char_Index(face,
64 metrics->root.clazz->standard_char);
65 if (glyph_index == 0)
66 goto Exit;
68 TA_LOG(("standard character: 0x%X (glyph index %d)\n",
69 metrics->root.clazz->standard_char, glyph_index));
71 error = FT_Load_Glyph(face, glyph_index, FT_LOAD_NO_SCALE);
72 if (error || face->glyph->outline.n_points <= 0)
73 goto Exit;
75 memset(dummy, 0, sizeof (TA_LatinMetricsRec));
77 dummy->units_per_em = metrics->units_per_em;
79 scaler->x_scale = 0x10000L;
80 scaler->y_scale = 0x10000L;
81 scaler->x_delta = 0;
82 scaler->y_delta = 0;
84 scaler->face = face;
85 scaler->render_mode = FT_RENDER_MODE_NORMAL;
86 scaler->flags = 0;
88 ta_glyph_hints_rescale(hints, (TA_ScriptMetrics)dummy);
90 error = ta_glyph_hints_reload(hints, &face->glyph->outline);
91 if (error)
92 goto Exit;
94 for (dim = 0; dim < TA_DIMENSION_MAX; dim++)
96 TA_LatinAxis axis = &metrics->axis[dim];
97 TA_AxisHints axhints = &hints->axis[dim];
99 TA_Segment seg, limit, link;
100 FT_UInt num_widths = 0;
103 error = ta_latin_hints_compute_segments(hints, (TA_Dimension)dim);
104 if (error)
105 goto Exit;
107 ta_latin_hints_link_segments(hints, (TA_Dimension)dim);
109 seg = axhints->segments;
110 limit = seg + axhints->num_segments;
112 for (; seg < limit; seg++)
114 link = seg->link;
116 /* we only consider stem segments there! */
117 if (link
118 && link->link == seg
119 && link > seg)
121 FT_Pos dist;
124 dist = seg->pos - link->pos;
125 if (dist < 0)
126 dist = -dist;
128 if (num_widths < TA_LATIN_MAX_WIDTHS)
129 axis->widths[num_widths++].org = dist;
133 /* this also replaces multiple almost identical stem widths */
134 /* with a single one (the value 100 is heuristic) */
135 ta_sort_and_quantize_widths(&num_widths, axis->widths,
136 dummy->units_per_em / 100);
137 axis->width_count = num_widths;
140 Exit:
141 for (dim = 0; dim < TA_DIMENSION_MAX; dim++)
143 TA_LatinAxis axis = &metrics->axis[dim];
144 FT_Pos stdw;
147 stdw = (axis->width_count > 0) ? axis->widths[0].org
148 : TA_LATIN_CONSTANT(metrics, 50);
150 /* let's try 20% of the smallest width */
151 axis->edge_distance_threshold = stdw / 5;
152 axis->standard_width = stdw;
153 axis->extra_light = 0;
155 #ifdef TA_DEBUG
157 FT_UInt i;
160 TA_LOG(("%s widths:\n",
161 dim == TA_DIMENSION_VERT ? "horizontal"
162 : "vertical"));
164 TA_LOG((" %d (standard)", axis->standard_width));
165 for (i = 1; i < axis->width_count; i++)
166 TA_LOG((" %d", axis->widths[i].org));
168 TA_LOG(("\n"));
170 #endif
174 TA_LOG(("\n"));
176 ta_glyph_hints_done(hints);
180 #define TA_LATIN_MAX_TEST_CHARACTERS 12
183 static const char ta_latin_blue_chars[TA_LATIN_MAX_BLUES]
184 [TA_LATIN_MAX_TEST_CHARACTERS + 1] =
186 "THEZOCQS",
187 "HEZLOCUS",
188 "fijkdbh",
189 "xzroesc",
190 "xzroesc",
191 "pqgjy"
195 /* find all blue zones; flat segments give the reference points, */
196 /* round segments the overshoot positions */
198 static void
199 ta_latin_metrics_init_blues(TA_LatinMetrics metrics,
200 FT_Face face)
202 FT_Pos flats[TA_LATIN_MAX_TEST_CHARACTERS];
203 FT_Pos rounds[TA_LATIN_MAX_TEST_CHARACTERS];
204 FT_Int num_flats;
205 FT_Int num_rounds;
207 FT_Int bb;
208 TA_LatinBlue blue;
209 FT_Error error;
210 TA_LatinAxis axis = &metrics->axis[TA_DIMENSION_VERT];
211 FT_Outline outline;
214 /* we compute the blues simply by loading each character from the */
215 /* `ta_latin_blue_chars[blues]' string, then finding its top-most or */
216 /* bottom-most points (depending on `TA_IS_TOP_BLUE') */
218 TA_LOG(("blue zones computation\n"
219 "======================\n\n"));
221 for (bb = 0; bb < TA_LATIN_BLUE_MAX; bb++)
223 const char* p = ta_latin_blue_chars[bb];
224 const char* limit = p + TA_LATIN_MAX_TEST_CHARACTERS;
225 FT_Pos* blue_ref;
226 FT_Pos* blue_shoot;
229 TA_LOG(("blue zone %d:\n", bb));
231 num_flats = 0;
232 num_rounds = 0;
234 for (; p < limit && *p; p++)
236 FT_UInt glyph_index;
237 FT_Pos best_y; /* same as points.y */
238 FT_Int best_point, best_contour_first, best_contour_last;
239 FT_Vector* points;
240 FT_Bool round = 0;
243 /* load the character in the face -- skip unknown or empty ones */
244 glyph_index = FT_Get_Char_Index(face, (FT_UInt)*p);
245 if (glyph_index == 0)
246 continue;
248 error = FT_Load_Glyph(face, glyph_index, FT_LOAD_NO_SCALE);
249 outline = face->glyph->outline;
250 if (error || outline.n_points <= 0)
251 continue;
253 /* now compute min or max point indices and coordinates */
254 points = outline.points;
255 best_point = -1;
256 best_y = 0; /* make compiler happy */
257 best_contour_first = 0; /* ditto */
258 best_contour_last = 0; /* ditto */
261 FT_Int nn;
262 FT_Int first = 0;
263 FT_Int last = -1;
266 for (nn = 0; nn < outline.n_contours; first = last + 1, nn++)
268 FT_Int old_best_point = best_point;
269 FT_Int pp;
272 last = outline.contours[nn];
274 /* avoid single-point contours since they are never rasterized; */
275 /* in some fonts, they correspond to mark attachment points */
276 /* which are way outside of the glyph's real outline */
277 if (last <= first)
278 continue;
280 if (TA_LATIN_IS_TOP_BLUE(bb))
282 for (pp = first; pp <= last; pp++)
283 if (best_point < 0
284 || points[pp].y > best_y)
286 best_point = pp;
287 best_y = points[pp].y;
290 else
292 for (pp = first; pp <= last; pp++)
293 if (best_point < 0
294 || points[pp].y < best_y)
296 best_point = pp;
297 best_y = points[pp].y;
301 if (best_point != old_best_point)
303 best_contour_first = first;
304 best_contour_last = last;
307 TA_LOG((" %c %ld", *p, best_y));
310 /* now check whether the point belongs to a straight or round */
311 /* segment; we first need to find in which contour the extremum */
312 /* lies, then inspect its previous and next points */
313 if (best_point >= 0)
315 FT_Pos best_x = points[best_point].x;
316 FT_Int prev, next;
317 FT_Int best_on_point_first, best_on_point_last;
318 FT_Pos dist;
321 if (FT_CURVE_TAG(outline.tags[best_point]) == FT_CURVE_TAG_ON)
323 best_on_point_first = best_point;
324 best_on_point_last = best_point;
326 else
328 best_on_point_first = -1;
329 best_on_point_last = -1;
332 /* look for the previous and next points that are not on the */
333 /* same Y coordinate, then threshold the `closeness'... */
334 prev = best_point;
335 next = prev;
339 if (prev > best_contour_first)
340 prev--;
341 else
342 prev = best_contour_last;
344 dist = TA_ABS(points[prev].y - best_y);
345 /* accept a small distance or a small angle (both values are */
346 /* heuristic; value 20 corresponds to approx. 2.9 degrees) */
347 if (dist > 5)
348 if (TA_ABS(points[prev].x - best_x) <= 20 * dist)
349 break;
351 if (FT_CURVE_TAG(outline.tags[prev]) == FT_CURVE_TAG_ON)
353 best_on_point_first = prev;
354 if (best_on_point_last < 0)
355 best_on_point_last = prev;
358 } while (prev != best_point);
362 if (next < best_contour_last)
363 next++;
364 else
365 next = best_contour_first;
367 dist = TA_ABS(points[next].y - best_y);
368 if (dist > 5)
369 if (TA_ABS(points[next].x - best_x) <= 20 * dist)
370 break;
372 if (FT_CURVE_TAG(outline.tags[next]) == FT_CURVE_TAG_ON)
374 best_on_point_last = next;
375 if (best_on_point_first < 0)
376 best_on_point_first = next;
379 } while (next != best_point);
381 /* now set the `round' flag depending on the segment's kind */
382 /* (value 8 is heuristic) */
383 if (best_on_point_first >= 0
384 && best_on_point_last >= 0
385 && (FT_UInt)(TA_ABS(points[best_on_point_last].x
386 - points[best_on_point_first].x))
387 > metrics->units_per_em / 8)
388 round = 0;
389 else
390 round = FT_BOOL(
391 FT_CURVE_TAG(outline.tags[prev]) != FT_CURVE_TAG_ON
392 || FT_CURVE_TAG(outline.tags[next]) != FT_CURVE_TAG_ON);
394 TA_LOG((" (%s)\n", round ? "round" : "flat"));
397 if (round)
398 rounds[num_rounds++] = best_y;
399 else
400 flats[num_flats++] = best_y;
403 if (num_flats == 0 && num_rounds == 0)
405 /* we couldn't find a single glyph to compute this blue zone, */
406 /* we will simply ignore it then */
407 TA_LOG((" empty\n"));
408 continue;
411 /* we have computed the contents of the `rounds' and `flats' tables, */
412 /* now determine the reference and overshoot position of the blue -- */
413 /* we simply take the median value after a simple sort */
414 ta_sort_pos(num_rounds, rounds);
415 ta_sort_pos(num_flats, flats);
417 blue = &axis->blues[axis->blue_count];
418 blue_ref = &blue->ref.org;
419 blue_shoot = &blue->shoot.org;
421 axis->blue_count++;
423 if (num_flats == 0)
425 *blue_ref =
426 *blue_shoot = rounds[num_rounds / 2];
428 else if (num_rounds == 0)
430 *blue_ref =
431 *blue_shoot = flats[num_flats / 2];
433 else
435 *blue_ref = flats[num_flats / 2];
436 *blue_shoot = rounds[num_rounds / 2];
439 /* there are sometimes problems if the overshoot position of top */
440 /* zones is under its reference position, or the opposite for bottom */
441 /* zones; we must thus check everything there and correct the errors */
442 if (*blue_shoot != *blue_ref)
444 FT_Pos ref = *blue_ref;
445 FT_Pos shoot = *blue_shoot;
446 FT_Bool over_ref = FT_BOOL(shoot > ref);
449 if (TA_LATIN_IS_TOP_BLUE(bb) ^ over_ref)
451 *blue_ref =
452 *blue_shoot = (shoot + ref) / 2;
454 TA_LOG((" [overshoot smaller than reference,"
455 " taking mean value]\n"));
459 blue->flags = 0;
460 if (TA_LATIN_IS_TOP_BLUE(bb))
461 blue->flags |= TA_LATIN_BLUE_TOP;
463 /* the following flag is used later to adjust the y and x scales */
464 /* in order to optimize the pixel grid alignment */
465 /* of the top of small letters */
466 if (bb == TA_LATIN_BLUE_SMALL_TOP)
467 blue->flags |= TA_LATIN_BLUE_ADJUSTMENT;
469 TA_LOG((" -> reference = %ld\n"
470 " overshoot = %ld\n",
471 *blue_ref, *blue_shoot));
474 /* add two blue zones for usWinAscent and usWinDescent */
475 /* just in case the above algorithm has missed them -- */
476 /* Windows cuts off everything outside of those two values */
478 TT_OS2* os2;
481 os2 = (TT_OS2*)FT_Get_Sfnt_Table(face, ft_sfnt_os2);
483 if (os2)
485 blue = &axis->blues[axis->blue_count];
486 blue->flags = TA_LATIN_BLUE_TOP | TA_LATIN_BLUE_ACTIVE;
487 blue->ref.org =
488 blue->shoot.org = os2->usWinAscent;
490 TA_LOG(("artificial blue zone for usWinAscent:\n"
491 " -> reference = %ld\n"
492 " overshoot = %ld\n",
493 blue->ref.org, blue->shoot.org));
495 blue = &axis->blues[axis->blue_count + 1];
496 blue->flags = TA_LATIN_BLUE_ACTIVE;
497 blue->ref.org =
498 blue->shoot.org = -os2->usWinDescent;
500 TA_LOG(("artificial blue zone for usWinDescent:\n"
501 " -> reference = %ld\n"
502 " overshoot = %ld\n",
503 blue->ref.org, blue->shoot.org));
505 else
507 blue = &axis->blues[axis->blue_count];
508 blue->flags =
509 blue->ref.org =
510 blue->shoot.org = 0;
512 blue = &axis->blues[axis->blue_count + 1];
513 blue->flags =
514 blue->ref.org =
515 blue->shoot.org = 0;
519 TA_LOG(("\n"));
521 return;
525 /* check whether all ASCII digits have the same advance width */
527 void
528 ta_latin_metrics_check_digits(TA_LatinMetrics metrics,
529 FT_Face face)
531 FT_UInt i;
532 FT_Bool started = 0, same_width = 1;
533 FT_Fixed advance, old_advance = 0;
536 /* digit `0' is 0x30 in all supported charmaps */
537 for (i = 0x30; i <= 0x39; i++)
539 FT_UInt glyph_index;
542 glyph_index = FT_Get_Char_Index(face, i);
543 if (glyph_index == 0)
544 continue;
546 if (FT_Get_Advance(face, glyph_index,
547 FT_LOAD_NO_SCALE
548 | FT_LOAD_NO_HINTING
549 | FT_LOAD_IGNORE_TRANSFORM,
550 &advance))
551 continue;
553 if (started)
555 if (advance != old_advance)
557 same_width = 0;
558 break;
561 else
563 old_advance = advance;
564 started = 1;
568 metrics->root.digits_have_same_width = same_width;
572 /* initialize global metrics */
574 FT_Error
575 ta_latin_metrics_init(TA_LatinMetrics metrics,
576 FT_Face face)
578 FT_CharMap oldmap = face->charmap;
581 metrics->units_per_em = face->units_per_EM;
583 if (!FT_Select_Charmap(face, FT_ENCODING_UNICODE))
585 ta_latin_metrics_init_widths(metrics, face);
586 ta_latin_metrics_init_blues(metrics, face);
587 ta_latin_metrics_check_digits(metrics, face);
590 FT_Set_Charmap(face, oldmap);
591 return FT_Err_Ok;
595 /* adjust scaling value, then scale and shift widths */
596 /* and blue zones (if applicable) for given dimension */
598 static void
599 ta_latin_metrics_scale_dim(TA_LatinMetrics metrics,
600 TA_Scaler scaler,
601 TA_Dimension dim)
603 FT_Fixed scale;
604 FT_Pos delta;
605 TA_LatinAxis axis;
606 FT_UInt nn;
609 if (dim == TA_DIMENSION_HORZ)
611 scale = scaler->x_scale;
612 delta = scaler->x_delta;
614 else
616 scale = scaler->y_scale;
617 delta = scaler->y_delta;
620 axis = &metrics->axis[dim];
622 if (axis->org_scale == scale && axis->org_delta == delta)
623 return;
625 axis->org_scale = scale;
626 axis->org_delta = delta;
628 /* correct X and Y scale to optimize the alignment of the top of */
629 /* small letters to the pixel grid */
631 TA_LatinAxis Axis = &metrics->axis[TA_DIMENSION_VERT];
632 TA_LatinBlue blue = NULL;
635 for (nn = 0; nn < Axis->blue_count; nn++)
637 if (Axis->blues[nn].flags & TA_LATIN_BLUE_ADJUSTMENT)
639 blue = &Axis->blues[nn];
640 break;
644 if (blue)
646 FT_Pos scaled;
647 FT_Pos threshold;
648 FT_Pos fitted;
649 FT_UInt limit;
650 FT_UInt ppem;
653 scaled = FT_MulFix(blue->shoot.org, scaler->y_scale);
654 ppem = metrics->root.scaler.face->size->metrics.x_ppem;
655 limit = metrics->root.globals->increase_x_height;
656 threshold = 40;
658 /* if the `increase-x-height' property is active, */
659 /* we round up much more often */
660 if (limit
661 && ppem <= limit
662 && ppem >= TA_PROP_INCREASE_X_HEIGHT_MIN)
663 threshold = 52;
665 fitted = (scaled + threshold) & ~63;
667 if (scaled != fitted)
669 if (dim == TA_DIMENSION_VERT)
670 scale = FT_MulDiv(scale, fitted, scaled);
675 axis->scale = scale;
676 axis->delta = delta;
678 if (dim == TA_DIMENSION_HORZ)
680 metrics->root.scaler.x_scale = scale;
681 metrics->root.scaler.x_delta = delta;
683 else
685 metrics->root.scaler.y_scale = scale;
686 metrics->root.scaler.y_delta = delta;
689 /* scale the widths */
690 for (nn = 0; nn < axis->width_count; nn++)
692 TA_Width width = axis->widths + nn;
695 width->cur = FT_MulFix(width->org, scale);
696 width->fit = width->cur;
699 /* an extra-light axis corresponds to a standard width that is */
700 /* smaller than 5/8 pixels */
701 axis->extra_light =
702 (FT_Bool)(FT_MulFix(axis->standard_width, scale) < 32 + 8);
704 if (dim == TA_DIMENSION_VERT)
706 /* scale the blue zones */
707 for (nn = 0; nn < axis->blue_count; nn++)
709 TA_LatinBlue blue = &axis->blues[nn];
710 FT_Pos dist;
713 blue->ref.cur = FT_MulFix(blue->ref.org, scale) + delta;
714 blue->ref.fit = blue->ref.cur;
715 blue->shoot.cur = FT_MulFix(blue->shoot.org, scale) + delta;
716 blue->shoot.fit = blue->shoot.cur;
717 blue->flags &= ~TA_LATIN_BLUE_ACTIVE;
719 /* a blue zone is only active if it is less than 3/4 pixels tall */
720 dist = FT_MulFix(blue->ref.org - blue->shoot.org, scale);
721 if (dist <= 48 && dist >= -48)
723 #if 0
724 FT_Pos delta1;
725 #endif
726 FT_Pos delta2;
729 /* use discrete values for blue zone widths */
731 #if 0
732 /* generic, original code */
733 delta1 = blue->shoot.org - blue->ref.org;
734 delta2 = delta1;
735 if (delta1 < 0)
736 delta2 = -delta2;
738 delta2 = FT_MulFix(delta2, scale);
740 if (delta2 < 32)
741 delta2 = 0;
742 else if (delta2 < 64)
743 delta2 = 32 + (((delta2 - 32) + 16) & ~31);
744 else
745 delta2 = TA_PIX_ROUND(delta2);
747 if (delta1 < 0)
748 delta2 = -delta2;
750 blue->ref.fit = TA_PIX_ROUND(blue->ref.cur);
751 blue->shoot.fit = blue->ref.fit + delta2;
752 #else
753 /* simplified version due to abs(dist) <= 48 */
754 delta2 = dist;
755 if (dist < 0)
756 delta2 = -delta2;
758 if (delta2 < 32)
759 delta2 = 0;
760 else if (delta < 48)
761 delta2 = 32;
762 else
763 delta2 = 64;
765 if (dist < 0)
766 delta2 = -delta2;
768 blue->ref.fit = TA_PIX_ROUND(blue->ref.cur);
769 blue->shoot.fit = blue->ref.fit - delta2;
770 #endif
772 blue->flags |= TA_LATIN_BLUE_ACTIVE;
776 /* the last two artificial blue zones are to be scaled */
777 /* with uncorrected scaling values */
779 TA_LatinAxis a = &metrics->axis[TA_DIMENSION_VERT];
780 TA_LatinBlue b;
783 b = &a->blues[a->blue_count];
784 b->ref.cur =
785 b->ref.fit =
786 b->shoot.cur =
787 b->shoot.fit = FT_MulFix(b->ref.org, a->org_scale) + delta;
789 b = &a->blues[a->blue_count + 1];
790 b->ref.cur =
791 b->ref.fit =
792 b->shoot.cur =
793 b->shoot.fit = FT_MulFix(b->ref.org, a->org_scale) + delta;
799 /* scale global values in both directions */
801 void
802 ta_latin_metrics_scale(TA_LatinMetrics metrics,
803 TA_Scaler scaler)
805 metrics->root.scaler.render_mode = scaler->render_mode;
806 metrics->root.scaler.face = scaler->face;
807 metrics->root.scaler.flags = scaler->flags;
809 ta_latin_metrics_scale_dim(metrics, scaler, TA_DIMENSION_HORZ);
810 ta_latin_metrics_scale_dim(metrics, scaler, TA_DIMENSION_VERT);
814 /* walk over all contours and compute its segments */
816 FT_Error
817 ta_latin_hints_compute_segments(TA_GlyphHints hints,
818 TA_Dimension dim)
820 TA_AxisHints axis = &hints->axis[dim];
821 FT_Error error = FT_Err_Ok;
823 TA_Segment segment = NULL;
824 TA_SegmentRec seg0;
826 TA_Point* contour = hints->contours;
827 TA_Point* contour_limit = contour + hints->num_contours;
828 TA_Direction major_dir, segment_dir;
831 memset(&seg0, 0, sizeof (TA_SegmentRec));
832 seg0.score = 32000;
833 seg0.flags = TA_EDGE_NORMAL;
835 major_dir = (TA_Direction)TA_ABS(axis->major_dir);
836 segment_dir = major_dir;
838 axis->num_segments = 0;
840 /* set up (u,v) in each point */
841 if (dim == TA_DIMENSION_HORZ)
843 TA_Point point = hints->points;
844 TA_Point limit = point + hints->num_points;
847 for (; point < limit; point++)
849 point->u = point->fx;
850 point->v = point->fy;
853 else
855 TA_Point point = hints->points;
856 TA_Point limit = point + hints->num_points;
859 for (; point < limit; point++)
861 point->u = point->fy;
862 point->v = point->fx;
866 /* do each contour separately */
867 for (; contour < contour_limit; contour++)
869 TA_Point point = contour[0];
870 TA_Point last = point->prev;
872 int on_edge = 0;
874 FT_Pos min_pos = 32000; /* minimum segment pos != min_coord */
875 FT_Pos max_pos = -32000; /* maximum segment pos != max_coord */
876 FT_Bool passed;
879 if (point == last) /* skip singletons -- just in case */
880 continue;
882 if (TA_ABS(last->out_dir) == major_dir
883 && TA_ABS(point->out_dir) == major_dir)
885 /* we are already on an edge, try to locate its start */
886 last = point;
888 for (;;)
890 point = point->prev;
891 if (TA_ABS(point->out_dir) != major_dir)
893 point = point->next;
894 break;
896 if (point == last)
897 break;
901 last = point;
902 passed = 0;
904 for (;;)
906 FT_Pos u, v;
909 if (on_edge)
911 u = point->u;
912 if (u < min_pos)
913 min_pos = u;
914 if (u > max_pos)
915 max_pos = u;
917 if (point->out_dir != segment_dir
918 || point == last)
920 /* we are just leaving an edge; record a new segment! */
921 segment->last = point;
922 segment->pos = (FT_Short)((min_pos + max_pos) >> 1);
924 /* a segment is round if either its first or last point */
925 /* is a control point */
926 if ((segment->first->flags | point->flags) & TA_FLAG_CONTROL)
927 segment->flags |= TA_EDGE_ROUND;
929 /* compute segment size */
930 min_pos = max_pos = point->v;
932 v = segment->first->v;
933 if (v < min_pos)
934 min_pos = v;
935 if (v > max_pos)
936 max_pos = v;
938 segment->min_coord = (FT_Short)min_pos;
939 segment->max_coord = (FT_Short)max_pos;
940 segment->height = (FT_Short)(segment->max_coord -
941 segment->min_coord);
943 on_edge = 0;
944 segment = NULL;
945 /* fall through */
949 /* now exit if we are at the start/end point */
950 if (point == last)
952 if (passed)
953 break;
954 passed = 1;
957 if (!on_edge
958 && TA_ABS(point->out_dir) == major_dir)
960 /* this is the start of a new segment! */
961 segment_dir = (TA_Direction)point->out_dir;
963 /* clear all segment fields */
964 error = ta_axis_hints_new_segment(axis, &segment);
965 if (error)
966 goto Exit;
968 segment[0] = seg0;
969 segment->dir = (FT_Char)segment_dir;
970 min_pos = max_pos = point->u;
971 segment->first = point;
972 segment->last = point;
973 on_edge = 1;
976 point = point->next;
978 } /* contours */
981 /* now slightly increase the height of segments if this makes sense -- */
982 /* this is used to better detect and ignore serifs */
984 TA_Segment segments = axis->segments;
985 TA_Segment segments_end = segments + axis->num_segments;
988 for (segment = segments; segment < segments_end; segment++)
990 TA_Point first = segment->first;
991 TA_Point last = segment->last;
993 FT_Pos first_v = first->v;
994 FT_Pos last_v = last->v;
997 if (first == last)
998 continue;
1000 if (first_v < last_v)
1002 TA_Point p;
1005 p = first->prev;
1006 if (p->v < first_v)
1007 segment->height = (FT_Short)(segment->height +
1008 ((first_v - p->v) >> 1));
1010 p = last->next;
1011 if (p->v > last_v)
1012 segment->height = (FT_Short)(segment->height +
1013 ((p->v - last_v) >> 1));
1015 else
1017 TA_Point p;
1020 p = first->prev;
1021 if (p->v > first_v)
1022 segment->height = (FT_Short)(segment->height +
1023 ((p->v - first_v) >> 1));
1025 p = last->next;
1026 if (p->v < last_v)
1027 segment->height = (FT_Short)(segment->height +
1028 ((last_v - p->v) >> 1));
1033 Exit:
1034 return error;
1038 /* link segments to form stems and serifs */
1040 void
1041 ta_latin_hints_link_segments(TA_GlyphHints hints,
1042 TA_Dimension dim)
1044 TA_AxisHints axis = &hints->axis[dim];
1046 TA_Segment segments = axis->segments;
1047 TA_Segment segment_limit = segments + axis->num_segments;
1049 FT_Pos len_threshold, len_score;
1050 TA_Segment seg1, seg2;
1053 len_threshold = TA_LATIN_CONSTANT(hints->metrics, 8);
1054 if (len_threshold == 0)
1055 len_threshold = 1;
1057 len_score = TA_LATIN_CONSTANT(hints->metrics, 6000);
1059 /* now compare each segment to the others */
1060 for (seg1 = segments; seg1 < segment_limit; seg1++)
1062 /* the fake segments are introduced to hint the metrics -- */
1063 /* we must never link them to anything */
1064 if (seg1->dir != axis->major_dir
1065 || seg1->first == seg1->last)
1066 continue;
1068 /* search for stems having opposite directions, */
1069 /* with seg1 to the `left' of seg2 */
1070 for (seg2 = segments; seg2 < segment_limit; seg2++)
1072 FT_Pos pos1 = seg1->pos;
1073 FT_Pos pos2 = seg2->pos;
1076 if (seg1->dir + seg2->dir == 0
1077 && pos2 > pos1)
1079 /* compute distance between the two segments */
1080 FT_Pos dist = pos2 - pos1;
1081 FT_Pos min = seg1->min_coord;
1082 FT_Pos max = seg1->max_coord;
1083 FT_Pos len, score;
1086 if (min < seg2->min_coord)
1087 min = seg2->min_coord;
1088 if (max > seg2->max_coord)
1089 max = seg2->max_coord;
1091 /* compute maximum coordinate difference of the two segments */
1092 len = max - min;
1093 if (len >= len_threshold)
1095 /* small coordinate differences cause a higher score, and */
1096 /* segments with a greater distance cause a higher score also */
1097 score = dist + len_score / len;
1099 /* and we search for the smallest score */
1100 /* of the sum of the two values */
1101 if (score < seg1->score)
1103 seg1->score = score;
1104 seg1->link = seg2;
1107 if (score < seg2->score)
1109 seg2->score = score;
1110 seg2->link = seg1;
1117 /* now compute the `serif' segments, cf. explanations in `tahints.h' */
1118 for (seg1 = segments; seg1 < segment_limit; seg1++)
1120 seg2 = seg1->link;
1122 if (seg2)
1124 if (seg2->link != seg1)
1126 seg1->link = 0;
1127 seg1->serif = seg2->link;
1134 /* link segments to edges, using feature analysis for selection */
1136 FT_Error
1137 ta_latin_hints_compute_edges(TA_GlyphHints hints,
1138 TA_Dimension dim)
1140 TA_AxisHints axis = &hints->axis[dim];
1141 FT_Error error = FT_Err_Ok;
1142 TA_LatinAxis laxis = &((TA_LatinMetrics)hints->metrics)->axis[dim];
1144 TA_Segment segments = axis->segments;
1145 TA_Segment segment_limit = segments + axis->num_segments;
1146 TA_Segment seg;
1148 #if 0
1149 TA_Direction up_dir;
1150 #endif
1151 FT_Fixed scale;
1152 FT_Pos edge_distance_threshold;
1153 FT_Pos segment_length_threshold;
1156 axis->num_edges = 0;
1158 scale = (dim == TA_DIMENSION_HORZ) ? hints->x_scale
1159 : hints->y_scale;
1161 #if 0
1162 up_dir = (dim == TA_DIMENSION_HORZ) ? TA_DIR_UP
1163 : TA_DIR_RIGHT;
1164 #endif
1166 /* we ignore all segments that are less than 1 pixel in length */
1167 /* to avoid many problems with serif fonts */
1168 /* (the corresponding threshold is computed in font units) */
1169 if (dim == TA_DIMENSION_HORZ)
1170 segment_length_threshold = FT_DivFix(64, hints->y_scale);
1171 else
1172 segment_length_threshold = 0;
1174 /********************************************************************/
1175 /* */
1176 /* We begin by generating a sorted table of edges for the current */
1177 /* direction. To do so, we simply scan each segment and try to find */
1178 /* an edge in our table that corresponds to its position. */
1179 /* */
1180 /* If no edge is found, we create and insert a new edge in the */
1181 /* sorted table. Otherwise, we simply add the segment to the edge's */
1182 /* list which gets processed in the second step to compute the */
1183 /* edge's properties. */
1184 /* */
1185 /* Note that the table of edges is sorted along the segment/edge */
1186 /* position. */
1187 /* */
1188 /********************************************************************/
1190 /* assure that edge distance threshold is at most 0.25px */
1191 edge_distance_threshold = FT_MulFix(laxis->edge_distance_threshold,
1192 scale);
1193 if (edge_distance_threshold > 64 / 4)
1194 edge_distance_threshold = 64 / 4;
1196 edge_distance_threshold = FT_DivFix(edge_distance_threshold,
1197 scale);
1199 for (seg = segments; seg < segment_limit; seg++)
1201 TA_Edge found = NULL;
1202 FT_Int ee;
1205 if (seg->height < segment_length_threshold)
1206 continue;
1208 /* a special case for serif edges: */
1209 /* if they are smaller than 1.5 pixels we ignore them */
1210 if (seg->serif
1211 && 2 * seg->height < 3 * segment_length_threshold)
1212 continue;
1214 /* look for an edge corresponding to the segment */
1215 for (ee = 0; ee < axis->num_edges; ee++)
1217 TA_Edge edge = axis->edges + ee;
1218 FT_Pos dist;
1221 dist = seg->pos - edge->fpos;
1222 if (dist < 0)
1223 dist = -dist;
1225 if (dist < edge_distance_threshold && edge->dir == seg->dir)
1227 found = edge;
1228 break;
1232 if (!found)
1234 TA_Edge edge;
1237 /* insert a new edge in the list and sort according to the position */
1238 error = ta_axis_hints_new_edge(axis, seg->pos,
1239 (TA_Direction)seg->dir,
1240 &edge);
1241 if (error)
1242 goto Exit;
1244 /* add the segment to the new edge's list */
1245 memset(edge, 0, sizeof (TA_EdgeRec));
1246 edge->first = seg;
1247 edge->last = seg;
1248 edge->dir = seg->dir;
1249 edge->fpos = seg->pos;
1250 edge->opos = FT_MulFix(seg->pos, scale);
1251 edge->pos = edge->opos;
1252 seg->edge_next = seg;
1254 else
1256 /* if an edge was found, simply add the segment to the edge's list */
1257 seg->edge_next = found->first;
1258 found->last->edge_next = seg;
1259 found->last = seg;
1263 /*****************************************************************/
1264 /* */
1265 /* Good, we now compute each edge's properties according to */
1266 /* the segments found on its position. Basically, these are */
1267 /* */
1268 /* - the edge's main direction */
1269 /* - stem edge, serif edge or both (which defaults to stem then) */
1270 /* - rounded edge, straight or both (which defaults to straight) */
1271 /* - link for edge */
1272 /* */
1273 /*****************************************************************/
1275 /* first of all, set the `edge' field in each segment -- this is */
1276 /* required in order to compute edge links */
1278 /* note that removing this loop and setting the `edge' field of each */
1279 /* segment directly in the code above slows down execution speed for */
1280 /* some reasons on platforms like the Sun */
1282 TA_Edge edges = axis->edges;
1283 TA_Edge edge_limit = edges + axis->num_edges;
1284 TA_Edge edge;
1287 for (edge = edges; edge < edge_limit; edge++)
1289 seg = edge->first;
1290 if (seg)
1293 seg->edge = edge;
1294 seg = seg->edge_next;
1295 } while (seg != edge->first);
1298 /* now compute each edge properties */
1299 for (edge = edges; edge < edge_limit; edge++)
1301 FT_Int is_round = 0; /* does it contain round segments? */
1302 FT_Int is_straight = 0; /* does it contain straight segments? */
1303 #if 0
1304 FT_Pos ups = 0; /* number of upwards segments */
1305 FT_Pos downs = 0; /* number of downwards segments */
1306 #endif
1309 seg = edge->first;
1313 FT_Bool is_serif;
1316 /* check for roundness of segment */
1317 if (seg->flags & TA_EDGE_ROUND)
1318 is_round++;
1319 else
1320 is_straight++;
1322 #if 0
1323 /* check for segment direction */
1324 if (seg->dir == up_dir)
1325 ups += seg->max_coord - seg->min_coord;
1326 else
1327 downs += seg->max_coord - seg->min_coord;
1328 #endif
1330 /* check for links -- */
1331 /* if seg->serif is set, then seg->link must be ignored */
1332 is_serif = (FT_Bool)(seg->serif
1333 && seg->serif->edge
1334 && seg->serif->edge != edge);
1336 if ((seg->link && seg->link->edge != NULL)
1337 || is_serif)
1339 TA_Edge edge2;
1340 TA_Segment seg2;
1343 edge2 = edge->link;
1344 seg2 = seg->link;
1346 if (is_serif)
1348 seg2 = seg->serif;
1349 edge2 = edge->serif;
1352 if (edge2)
1354 FT_Pos edge_delta;
1355 FT_Pos seg_delta;
1358 edge_delta = edge->fpos - edge2->fpos;
1359 if (edge_delta < 0)
1360 edge_delta = -edge_delta;
1362 seg_delta = seg->pos - seg2->pos;
1363 if (seg_delta < 0)
1364 seg_delta = -seg_delta;
1366 if (seg_delta < edge_delta)
1367 edge2 = seg2->edge;
1369 else
1370 edge2 = seg2->edge;
1372 if (is_serif)
1374 edge->serif = edge2;
1375 edge2->flags |= TA_EDGE_SERIF;
1377 else
1378 edge->link = edge2;
1381 seg = seg->edge_next;
1382 } while (seg != edge->first);
1384 /* set the round/straight flags */
1385 edge->flags = TA_EDGE_NORMAL;
1387 if (is_round > 0
1388 && is_round >= is_straight)
1389 edge->flags |= TA_EDGE_ROUND;
1391 #if 0
1392 /* set the edge's main direction */
1393 edge->dir = TA_DIR_NONE;
1395 if (ups > downs)
1396 edge->dir = (FT_Char)up_dir;
1398 else if (ups < downs)
1399 edge->dir = (FT_Char)-up_dir;
1401 else if (ups == downs)
1402 edge->dir = 0; /* both up and down! */
1403 #endif
1405 /* get rid of serifs if link is set */
1406 /* XXX: this gets rid of many unpleasant artefacts! */
1407 /* example: the `c' in cour.pfa at size 13 */
1409 if (edge->serif && edge->link)
1410 edge->serif = 0;
1414 Exit:
1415 return error;
1419 /* detect segments and edges for given dimension */
1421 FT_Error
1422 ta_latin_hints_detect_features(TA_GlyphHints hints,
1423 TA_Dimension dim)
1425 FT_Error error;
1428 error = ta_latin_hints_compute_segments(hints, dim);
1429 if (!error)
1431 ta_latin_hints_link_segments(hints, dim);
1433 error = ta_latin_hints_compute_edges(hints, dim);
1436 return error;
1440 /* compute all edges which lie within blue zones */
1442 void
1443 ta_latin_hints_compute_blue_edges(TA_GlyphHints hints,
1444 TA_LatinMetrics metrics)
1446 TA_AxisHints axis = &hints->axis[TA_DIMENSION_VERT];
1448 TA_Edge edge = axis->edges;
1449 TA_Edge edge_limit = edge + axis->num_edges;
1451 TA_LatinAxis latin = &metrics->axis[TA_DIMENSION_VERT];
1452 FT_Fixed scale = latin->scale;
1455 /* compute which blue zones are active, */
1456 /* i.e. have their scaled size < 3/4 pixels */
1458 /* for each horizontal edge search the blue zone which is closest */
1459 for (; edge < edge_limit; edge++)
1461 FT_UInt bb;
1462 TA_Width best_blue = NULL;
1463 FT_Pos best_dist; /* initial threshold */
1465 FT_UInt best_blue_idx = 0;
1466 FT_Bool best_blue_is_shoot = 0;
1469 /* compute the initial threshold as a fraction of the EM size */
1470 /* (the value 40 is heuristic) */
1471 best_dist = FT_MulFix(metrics->units_per_em / 40, scale);
1473 /* assure a minimum distance of 0.5px */
1474 if (best_dist > 64 / 2)
1475 best_dist = 64 / 2;
1477 /* this loop also handles the two extra blue zones */
1478 /* for usWinAscent and usWinDescent */
1479 /* if option `windows-compatibility' is set */
1480 for (bb = 0;
1481 bb < latin->blue_count
1482 + (metrics->root.globals->font->windows_compatibility ? 2 : 0);
1483 bb++)
1485 TA_LatinBlue blue = latin->blues + bb;
1486 FT_Bool is_top_blue, is_major_dir;
1489 /* skip inactive blue zones (i.e., those that are too large) */
1490 if (!(blue->flags & TA_LATIN_BLUE_ACTIVE))
1491 continue;
1493 /* if it is a top zone, check for right edges -- */
1494 /* if it is a bottom zone, check for left edges */
1495 is_top_blue = (FT_Byte)((blue->flags & TA_LATIN_BLUE_TOP) != 0);
1496 is_major_dir = FT_BOOL(edge->dir == axis->major_dir);
1498 /* if it is a top zone, the edge must be against the major */
1499 /* direction; if it is a bottom zone, it must be in the major */
1500 /* direction */
1501 if (is_top_blue ^ is_major_dir)
1503 FT_Pos dist;
1506 /* first of all, compare it to the reference position */
1507 dist = edge->fpos - blue->ref.org;
1508 if (dist < 0)
1509 dist = -dist;
1511 dist = FT_MulFix(dist, scale);
1512 if (dist < best_dist)
1514 best_dist = dist;
1515 best_blue = &blue->ref;
1517 best_blue_idx = bb;
1518 best_blue_is_shoot = 0;
1521 /* now compare it to the overshoot position and check whether */
1522 /* the edge is rounded, and whether the edge is over the */
1523 /* reference position of a top zone, or under the reference */
1524 /* position of a bottom zone */
1525 if (edge->flags & TA_EDGE_ROUND
1526 && dist != 0)
1528 FT_Bool is_under_ref = FT_BOOL(edge->fpos < blue->ref.org);
1531 if (is_top_blue ^ is_under_ref)
1533 dist = edge->fpos - blue->shoot.org;
1534 if (dist < 0)
1535 dist = -dist;
1537 dist = FT_MulFix(dist, scale);
1538 if (dist < best_dist)
1540 best_dist = dist;
1541 best_blue = &blue->shoot;
1543 best_blue_idx = bb;
1544 best_blue_is_shoot = 1;
1551 if (best_blue)
1553 edge->blue_edge = best_blue;
1554 edge->best_blue_idx = best_blue_idx;
1555 edge->best_blue_is_shoot = best_blue_is_shoot;
1561 /* initalize hinting engine */
1563 static FT_Error
1564 ta_latin_hints_init(TA_GlyphHints hints,
1565 TA_LatinMetrics metrics)
1567 FT_Render_Mode mode;
1568 FT_UInt32 scaler_flags, other_flags;
1569 FT_Face face = metrics->root.scaler.face;
1572 ta_glyph_hints_rescale(hints, (TA_ScriptMetrics)metrics);
1574 /* correct x_scale and y_scale if needed, since they may have */
1575 /* been modified by `ta_latin_metrics_scale_dim' above */
1576 hints->x_scale = metrics->axis[TA_DIMENSION_HORZ].scale;
1577 hints->x_delta = metrics->axis[TA_DIMENSION_HORZ].delta;
1578 hints->y_scale = metrics->axis[TA_DIMENSION_VERT].scale;
1579 hints->y_delta = metrics->axis[TA_DIMENSION_VERT].delta;
1581 /* compute flags depending on render mode, etc. */
1582 mode = metrics->root.scaler.render_mode;
1584 #if 0 /* #ifdef TA_CONFIG_OPTION_USE_WARPER */
1585 if (mode == FT_RENDER_MODE_LCD
1586 || mode == FT_RENDER_MODE_LCD_V)
1587 metrics->root.scaler.render_mode =
1588 mode = FT_RENDER_MODE_NORMAL;
1589 #endif
1591 scaler_flags = hints->scaler_flags;
1592 other_flags = 0;
1594 /* we snap the width of vertical stems for the monochrome */
1595 /* and horizontal LCD rendering targets only */
1596 if (mode == FT_RENDER_MODE_MONO
1597 || mode == FT_RENDER_MODE_LCD)
1598 other_flags |= TA_LATIN_HINTS_HORZ_SNAP;
1600 /* we snap the width of horizontal stems for the monochrome */
1601 /* and vertical LCD rendering targets only */
1602 if (mode == FT_RENDER_MODE_MONO
1603 || mode == FT_RENDER_MODE_LCD_V)
1604 other_flags |= TA_LATIN_HINTS_VERT_SNAP;
1606 /* we adjust stems to full pixels only if we don't use the `light' mode */
1607 if (mode != FT_RENDER_MODE_LIGHT)
1608 other_flags |= TA_LATIN_HINTS_STEM_ADJUST;
1610 if (mode == FT_RENDER_MODE_MONO)
1611 other_flags |= TA_LATIN_HINTS_MONO;
1613 /* in `light' hinting mode we disable horizontal hinting completely; */
1614 /* we also do it if the face is italic */
1615 if (mode == FT_RENDER_MODE_LIGHT
1616 || (face->style_flags & FT_STYLE_FLAG_ITALIC) != 0)
1617 scaler_flags |= TA_SCALER_FLAG_NO_HORIZONTAL;
1619 hints->scaler_flags = scaler_flags;
1620 hints->other_flags = other_flags;
1622 return FT_Err_Ok;
1626 /* snap a given width in scaled coordinates to */
1627 /* one of the current standard widths */
1629 static FT_Pos
1630 ta_latin_snap_width(TA_Width widths,
1631 FT_Int count,
1632 FT_Pos width)
1634 int n;
1635 FT_Pos best = 64 + 32 + 2;
1636 FT_Pos reference = width;
1637 FT_Pos scaled;
1640 for (n = 0; n < count; n++)
1642 FT_Pos w;
1643 FT_Pos dist;
1646 w = widths[n].cur;
1647 dist = width - w;
1648 if (dist < 0)
1649 dist = -dist;
1650 if (dist < best)
1652 best = dist;
1653 reference = w;
1657 scaled = TA_PIX_ROUND(reference);
1659 if (width >= reference)
1661 if (width < scaled + 48)
1662 width = reference;
1664 else
1666 if (width > scaled - 48)
1667 width = reference;
1670 return width;
1674 /* compute the snapped width of a given stem, ignoring very thin ones */
1676 /* there is a lot of voodoo in this function; changing the hard-coded */
1677 /* parameters influence the whole hinting process */
1679 static FT_Pos
1680 ta_latin_compute_stem_width(TA_GlyphHints hints,
1681 TA_Dimension dim,
1682 FT_Pos width,
1683 FT_Byte base_flags,
1684 FT_Byte stem_flags)
1686 TA_LatinMetrics metrics = (TA_LatinMetrics) hints->metrics;
1687 TA_LatinAxis axis = &metrics->axis[dim];
1689 FT_Pos dist = width;
1690 FT_Int sign = 0;
1691 FT_Int vertical = (dim == TA_DIMENSION_VERT);
1694 if (!TA_LATIN_HINTS_DO_STEM_ADJUST(hints)
1695 || axis->extra_light)
1696 return width;
1698 if (dist < 0)
1700 dist = -width;
1701 sign = 1;
1704 if ((vertical && !TA_LATIN_HINTS_DO_VERT_SNAP(hints))
1705 || (!vertical && !TA_LATIN_HINTS_DO_HORZ_SNAP(hints)))
1707 /* smooth hinting process: very lightly quantize the stem width */
1709 /* leave the widths of serifs alone */
1710 if ((stem_flags & TA_EDGE_SERIF)
1711 && vertical
1712 && (dist < 3 * 64))
1713 goto Done_Width;
1714 else if (base_flags & TA_EDGE_ROUND)
1716 if (dist < 80)
1717 dist = 64;
1719 else if (dist < 56)
1720 dist = 56;
1722 if (axis->width_count > 0)
1724 FT_Pos delta;
1727 /* compare to standard width */
1728 delta = dist - axis->widths[0].cur;
1730 if (delta < 0)
1731 delta = -delta;
1733 if (delta < 40)
1735 dist = axis->widths[0].cur;
1736 if (dist < 48)
1737 dist = 48;
1739 goto Done_Width;
1742 if (dist < 3 * 64)
1744 delta = dist & 63;
1745 dist &= -64;
1747 if (delta < 10)
1748 dist += delta;
1749 else if (delta < 32)
1750 dist += 10;
1751 else if (delta < 54)
1752 dist += 54;
1753 else
1754 dist += delta;
1756 else
1757 dist = (dist + 32) & ~63;
1760 else
1762 /* strong hinting process: snap the stem width to integer pixels */
1764 FT_Pos org_dist = dist;
1767 dist = ta_latin_snap_width(axis->widths, axis->width_count, dist);
1769 if (vertical)
1771 /* in the case of vertical hinting, */
1772 /* always round the stem heights to integer pixels */
1774 if (dist >= 64)
1775 dist = (dist + 16) & ~63;
1776 else
1777 dist = 64;
1779 else
1781 if (TA_LATIN_HINTS_DO_MONO(hints))
1783 /* monochrome horizontal hinting: */
1784 /* snap widths to integer pixels with a different threshold */
1786 if (dist < 64)
1787 dist = 64;
1788 else
1789 dist = (dist + 32) & ~63;
1791 else
1793 /* for horizontal anti-aliased hinting, we adopt a more subtle */
1794 /* approach: we strengthen small stems, round stems whose size */
1795 /* is between 1 and 2 pixels to an integer, otherwise nothing */
1797 if (dist < 48)
1798 dist = (dist + 64) >> 1;
1800 else if (dist < 128)
1802 /* we only round to an integer width if the corresponding */
1803 /* distortion is less than 1/4 pixel -- otherwise, this */
1804 /* makes everything worse since the diagonals, which are */
1805 /* not hinted, appear a lot bolder or thinner than the */
1806 /* vertical stems */
1808 FT_Pos delta;
1811 dist = (dist + 22) & ~63;
1812 delta = dist - org_dist;
1813 if (delta < 0)
1814 delta = -delta;
1816 if (delta >= 16)
1818 dist = org_dist;
1819 if (dist < 48)
1820 dist = (dist + 64) >> 1;
1823 else
1824 /* round otherwise to prevent color fringes in LCD mode */
1825 dist = (dist + 32) & ~63;
1830 Done_Width:
1831 if (sign)
1832 dist = -dist;
1834 return dist;
1838 /* align one stem edge relative to the previous stem edge */
1840 static void
1841 ta_latin_align_linked_edge(TA_GlyphHints hints,
1842 TA_Dimension dim,
1843 TA_Edge base_edge,
1844 TA_Edge stem_edge)
1846 FT_Pos dist = stem_edge->opos - base_edge->opos;
1848 FT_Pos fitted_width = ta_latin_compute_stem_width(
1849 hints, dim, dist,
1850 base_edge->flags,
1851 stem_edge->flags);
1854 stem_edge->pos = base_edge->pos + fitted_width;
1856 TA_LOG((" LINK: edge %d (opos=%.2f) linked to %.2f,"
1857 " dist was %.2f, now %.2f\n",
1858 stem_edge - hints->axis[dim].edges, stem_edge->opos / 64.0,
1859 stem_edge->pos / 64.0, dist / 64.0, fitted_width / 64.0));
1861 if (hints->recorder)
1862 hints->recorder(ta_link, hints, dim,
1863 base_edge, stem_edge, NULL, NULL, NULL);
1867 /* shift the coordinates of the `serif' edge by the same amount */
1868 /* as the corresponding `base' edge has been moved already */
1870 static void
1871 ta_latin_align_serif_edge(TA_GlyphHints hints,
1872 TA_Edge base,
1873 TA_Edge serif)
1875 FT_UNUSED(hints);
1877 serif->pos = base->pos + (serif->opos - base->opos);
1881 /* the main grid-fitting routine */
1883 void
1884 ta_latin_hint_edges(TA_GlyphHints hints,
1885 TA_Dimension dim)
1887 TA_AxisHints axis = &hints->axis[dim];
1889 TA_Edge edges = axis->edges;
1890 TA_Edge edge_limit = edges + axis->num_edges;
1891 FT_PtrDist n_edges;
1892 TA_Edge edge;
1894 TA_Edge anchor = NULL;
1895 FT_Int has_serifs = 0;
1897 #ifdef TA_DEBUG
1898 FT_UInt num_actions = 0;
1899 #endif
1901 TA_LOG(("%s edge hinting\n", dim == TA_DIMENSION_VERT ? "horizontal"
1902 : "vertical"));
1904 /* we begin by aligning all stems relative to the blue zone if needed -- */
1905 /* that's only for horizontal edges */
1907 if (dim == TA_DIMENSION_VERT
1908 && TA_HINTS_DO_BLUES(hints))
1910 for (edge = edges; edge < edge_limit; edge++)
1912 TA_Width blue;
1913 TA_Edge edge1, edge2; /* these edges form the stem to check */
1916 if (edge->flags & TA_EDGE_DONE)
1917 continue;
1919 blue = edge->blue_edge;
1920 edge1 = NULL;
1921 edge2 = edge->link;
1923 if (blue)
1924 edge1 = edge;
1926 /* flip edges if the other stem is aligned to a blue zone */
1927 else if (edge2 && edge2->blue_edge)
1929 blue = edge2->blue_edge;
1930 edge1 = edge2;
1931 edge2 = edge;
1934 if (!edge1)
1935 continue;
1937 #ifdef TA_DEBUG
1938 if (!anchor)
1939 TA_LOG((" BLUE_ANCHOR: edge %d (opos=%.2f) snapped to %.2f,"
1940 " was %.2f (anchor=edge %d)\n",
1941 edge1 - edges, edge1->opos / 64.0, blue->fit / 64.0,
1942 edge1->pos / 64.0, edge - edges));
1943 else
1944 TA_LOG((" BLUE: edge %d (opos=%.2f) snapped to %.2f, was %.2f\n",
1945 edge1 - edges, edge1->opos / 64.0, blue->fit / 64.0,
1946 edge1->pos / 64.0));
1948 num_actions++;
1949 #endif
1951 edge1->pos = blue->fit;
1952 edge1->flags |= TA_EDGE_DONE;
1954 if (hints->recorder)
1956 if (!anchor)
1957 hints->recorder(ta_blue_anchor, hints, dim,
1958 edge1, edge, NULL, NULL, NULL);
1959 else
1960 hints->recorder(ta_blue, hints, dim,
1961 edge1, NULL, NULL, NULL, NULL);
1964 if (edge2 && !edge2->blue_edge)
1966 ta_latin_align_linked_edge(hints, dim, edge1, edge2);
1967 edge2->flags |= TA_EDGE_DONE;
1969 #ifdef TA_DEBUG
1970 num_actions++;
1971 #endif
1974 if (!anchor)
1975 anchor = edge;
1979 /* now we align all other stem edges, */
1980 /* trying to maintain the relative order of stems in the glyph */
1981 for (edge = edges; edge < edge_limit; edge++)
1983 TA_Edge edge2;
1986 if (edge->flags & TA_EDGE_DONE)
1987 continue;
1989 /* skip all non-stem edges */
1990 edge2 = edge->link;
1991 if (!edge2)
1993 has_serifs++;
1994 continue;
1997 /* now align the stem */
1999 /* this should not happen, but it's better to be safe */
2000 if (edge2->blue_edge)
2002 TA_LOG((" ASSERTION FAILED for edge %d\n", edge2-edges));
2004 ta_latin_align_linked_edge(hints, dim, edge2, edge);
2005 edge->flags |= TA_EDGE_DONE;
2007 #ifdef TA_DEBUG
2008 num_actions++;
2009 #endif
2010 continue;
2013 if (!anchor)
2015 /* if we reach this if clause, no stem has been aligned yet */
2017 FT_Pos org_len, org_center, cur_len;
2018 FT_Pos cur_pos1, error1, error2, u_off, d_off;
2021 org_len = edge2->opos - edge->opos;
2022 cur_len = ta_latin_compute_stem_width(hints, dim, org_len,
2023 edge->flags, edge2->flags);
2025 /* some voodoo to specially round edges for small stem widths; */
2026 /* the idea is to align the center of a stem, */
2027 /* then shifting the stem edges to suitable positions */
2028 if (cur_len <= 64)
2030 /* width <= 1px */
2031 u_off = 32;
2032 d_off = 32;
2034 else
2036 /* 1px < width < 1.5px */
2037 u_off = 38;
2038 d_off = 26;
2041 if (cur_len < 96)
2043 org_center = edge->opos + (org_len >> 1);
2044 cur_pos1 = TA_PIX_ROUND(org_center);
2046 error1 = org_center - (cur_pos1 - u_off);
2047 if (error1 < 0)
2048 error1 = -error1;
2050 error2 = org_center - (cur_pos1 + d_off);
2051 if (error2 < 0)
2052 error2 = -error2;
2054 if (error1 < error2)
2055 cur_pos1 -= u_off;
2056 else
2057 cur_pos1 += d_off;
2059 edge->pos = cur_pos1 - cur_len / 2;
2060 edge2->pos = edge->pos + cur_len;
2062 else
2063 edge->pos = TA_PIX_ROUND(edge->opos);
2065 anchor = edge;
2066 edge->flags |= TA_EDGE_DONE;
2068 TA_LOG((" ANCHOR: edge %d (opos=%.2f) and %d (opos=%.2f)"
2069 " snapped to %.2f and %.2f\n",
2070 edge - edges, edge->opos / 64.0,
2071 edge2 - edges, edge2->opos / 64.0,
2072 edge->pos / 64.0, edge2->pos / 64.0));
2074 if (hints->recorder)
2075 hints->recorder(ta_anchor, hints, dim,
2076 edge, edge2, NULL, NULL, NULL);
2078 ta_latin_align_linked_edge(hints, dim, edge, edge2);
2080 #ifdef TA_DEBUG
2081 num_actions += 2;
2082 #endif
2084 else
2086 FT_Pos org_pos, org_len, org_center, cur_len;
2087 FT_Pos cur_pos1, cur_pos2, delta1, delta2;
2090 org_pos = anchor->pos + (edge->opos - anchor->opos);
2091 org_len = edge2->opos - edge->opos;
2092 org_center = org_pos + (org_len >> 1);
2094 cur_len = ta_latin_compute_stem_width(hints, dim, org_len,
2095 edge->flags, edge2->flags);
2097 if (edge2->flags & TA_EDGE_DONE)
2099 TA_LOG((" ADJUST: edge %d (pos=%.2f) moved to %.2f\n",
2100 edge - edges, edge->pos / 64.0,
2101 (edge2->pos - cur_len) / 64.0));
2103 edge->pos = edge2->pos - cur_len;
2105 if (hints->recorder)
2107 TA_Edge bound = NULL;
2110 if (edge > edges)
2111 bound = &edge[-1];
2113 hints->recorder(ta_adjust, hints, dim,
2114 edge, edge2, NULL, bound, NULL);
2118 else if (cur_len < 96)
2120 FT_Pos u_off, d_off;
2123 cur_pos1 = TA_PIX_ROUND(org_center);
2125 if (cur_len <= 64)
2127 u_off = 32;
2128 d_off = 32;
2130 else
2132 u_off = 38;
2133 d_off = 26;
2136 delta1 = org_center - (cur_pos1 - u_off);
2137 if (delta1 < 0)
2138 delta1 = -delta1;
2140 delta2 = org_center - (cur_pos1 + d_off);
2141 if (delta2 < 0)
2142 delta2 = -delta2;
2144 if (delta1 < delta2)
2145 cur_pos1 -= u_off;
2146 else
2147 cur_pos1 += d_off;
2149 edge->pos = cur_pos1 - cur_len / 2;
2150 edge2->pos = cur_pos1 + cur_len / 2;
2152 TA_LOG((" STEM: edge %d (opos=%.2f) linked to %d (opos=%.2f)"
2153 " snapped to %.2f and %.2f\n",
2154 edge - edges, edge->opos / 64.0,
2155 edge2 - edges, edge2->opos / 64.0,
2156 edge->pos / 64.0, edge2->pos / 64.0));
2158 if (hints->recorder)
2160 TA_Edge bound = NULL;
2163 if (edge > edges)
2164 bound = &edge[-1];
2166 hints->recorder(ta_stem, hints, dim,
2167 edge, edge2, NULL, bound, NULL);
2171 else
2173 org_pos = anchor->pos + (edge->opos - anchor->opos);
2174 org_len = edge2->opos - edge->opos;
2175 org_center = org_pos + (org_len >> 1);
2177 cur_len = ta_latin_compute_stem_width(hints, dim, org_len,
2178 edge->flags, edge2->flags);
2180 cur_pos1 = TA_PIX_ROUND(org_pos);
2181 delta1 = cur_pos1 + (cur_len >> 1) - org_center;
2182 if (delta1 < 0)
2183 delta1 = -delta1;
2185 cur_pos2 = TA_PIX_ROUND(org_pos + org_len) - cur_len;
2186 delta2 = cur_pos2 + (cur_len >> 1) - org_center;
2187 if (delta2 < 0)
2188 delta2 = -delta2;
2190 edge->pos = (delta1 < delta2) ? cur_pos1 : cur_pos2;
2191 edge2->pos = edge->pos + cur_len;
2193 TA_LOG((" STEM: edge %d (opos=%.2f) linked to %d (opos=%.2f)"
2194 " snapped to %.2f and %.2f\n",
2195 edge - edges, edge->opos / 64.0,
2196 edge2 - edges, edge2->opos / 64.0,
2197 edge->pos / 64.0, edge2->pos / 64.0));
2199 if (hints->recorder)
2201 TA_Edge bound = NULL;
2204 if (edge > edges)
2205 bound = &edge[-1];
2207 hints->recorder(ta_stem, hints, dim,
2208 edge, edge2, NULL, bound, NULL);
2212 #ifdef TA_DEBUG
2213 num_actions++;
2214 #endif
2216 edge->flags |= TA_EDGE_DONE;
2217 edge2->flags |= TA_EDGE_DONE;
2219 if (edge > edges
2220 && edge->pos < edge[-1].pos)
2222 #ifdef TA_DEBUG
2223 TA_LOG((" BOUND: edge %d (pos=%.2f) moved to %.2f\n",
2224 edge - edges, edge->pos / 64.0, edge[-1].pos / 64.0));
2226 num_actions++;
2227 #endif
2229 edge->pos = edge[-1].pos;
2231 if (hints->recorder)
2232 hints->recorder(ta_bound, hints, dim,
2233 edge, &edge[-1], NULL, NULL, NULL);
2238 /* make sure that lowercase m's maintain their symmetry */
2240 /* In general, lowercase m's have six vertical edges if they are sans */
2241 /* serif, or twelve if they are with serifs. This implementation is */
2242 /* based on that assumption, and seems to work very well with most */
2243 /* faces. However, if for a certain face this assumption is not */
2244 /* true, the m is just rendered like before. In addition, any stem */
2245 /* correction will only be applied to symmetrical glyphs (even if the */
2246 /* glyph is not an m), so the potential for unwanted distortion is */
2247 /* relatively low. */
2249 /* we don't handle horizontal edges since we can't easily assure that */
2250 /* the third (lowest) stem aligns with the base line; it might end up */
2251 /* one pixel higher or lower */
2253 n_edges = edge_limit - edges;
2254 if (dim == TA_DIMENSION_HORZ
2255 && (n_edges == 6 || n_edges == 12))
2257 TA_Edge edge1, edge2, edge3;
2258 FT_Pos dist1, dist2, span, delta;
2261 if (n_edges == 6)
2263 edge1 = edges;
2264 edge2 = edges + 2;
2265 edge3 = edges + 4;
2267 else
2269 edge1 = edges + 1;
2270 edge2 = edges + 5;
2271 edge3 = edges + 9;
2274 dist1 = edge2->opos - edge1->opos;
2275 dist2 = edge3->opos - edge2->opos;
2277 span = dist1 - dist2;
2278 if (span < 0)
2279 span = -span;
2281 if (span < 8)
2283 delta = edge3->pos - (2 * edge2->pos - edge1->pos);
2284 edge3->pos -= delta;
2285 if (edge3->link)
2286 edge3->link->pos -= delta;
2288 /* move the serifs along with the stem */
2289 if (n_edges == 12)
2291 (edges + 8)->pos -= delta;
2292 (edges + 11)->pos -= delta;
2295 edge3->flags |= TA_EDGE_DONE;
2296 if (edge3->link)
2297 edge3->link->flags |= TA_EDGE_DONE;
2301 if (has_serifs || !anchor)
2303 /* now hint the remaining edges (serifs and single) */
2304 /* in order to complete our processing */
2305 for (edge = edges; edge < edge_limit; edge++)
2307 TA_Edge lower_bound = NULL;
2308 TA_Edge upper_bound = NULL;
2310 FT_Pos delta;
2313 if (edge->flags & TA_EDGE_DONE)
2314 continue;
2316 delta = 1000;
2318 if (edge->serif)
2320 delta = edge->serif->opos - edge->opos;
2321 if (delta < 0)
2322 delta = -delta;
2325 if (edge > edges)
2326 lower_bound = &edge[-1];
2328 if (edge + 1 < edge_limit
2329 && edge[1].flags & TA_EDGE_DONE)
2330 upper_bound = &edge[1];
2333 if (delta < 64 + 16)
2335 ta_latin_align_serif_edge(hints, edge->serif, edge);
2337 TA_LOG((" SERIF: edge %d (opos=%.2f) serif to %d (opos=%.2f)"
2338 " aligned to %.2f\n",
2339 edge - edges, edge->opos / 64.0,
2340 edge->serif - edges, edge->serif->opos / 64.0,
2341 edge->pos / 64.0));
2343 if (hints->recorder)
2344 hints->recorder(ta_serif, hints, dim,
2345 edge, NULL, NULL, lower_bound, upper_bound);
2347 else if (!anchor)
2349 edge->pos = TA_PIX_ROUND(edge->opos);
2350 anchor = edge;
2352 TA_LOG((" SERIF_ANCHOR: edge %d (opos=%.2f) snapped to %.2f\n",
2353 edge - edges, edge->opos / 64.0, edge->pos / 64.0));
2355 if (hints->recorder)
2356 hints->recorder(ta_serif_anchor, hints, dim,
2357 edge, NULL, NULL, lower_bound, upper_bound);
2359 else
2361 TA_Edge before, after;
2364 for (before = edge - 1; before >= edges; before--)
2365 if (before->flags & TA_EDGE_DONE)
2366 break;
2368 for (after = edge + 1; after < edge_limit; after++)
2369 if (after->flags & TA_EDGE_DONE)
2370 break;
2372 if (before >= edges && before < edge
2373 && after < edge_limit && after > edge)
2375 if (after->opos == before->opos)
2376 edge->pos = before->pos;
2377 else
2378 edge->pos = before->pos + FT_MulDiv(edge->opos - before->opos,
2379 after->pos - before->pos,
2380 after->opos - before->opos);
2382 TA_LOG((" SERIF_LINK1: edge %d (opos=%.2f) snapped to %.2f"
2383 " from %d (opos=%.2f)\n",
2384 edge - edges, edge->opos / 64.0,
2385 edge->pos / 64.0,
2386 before - edges, before->opos / 64.0));
2388 if (hints->recorder)
2389 hints->recorder(ta_serif_link1, hints, dim,
2390 edge, before, after, lower_bound, upper_bound);
2392 else
2394 edge->pos = anchor->pos + ((edge->opos - anchor->opos + 16) & ~31);
2395 TA_LOG((" SERIF_LINK2: edge %d (opos=%.2f) snapped to %.2f\n",
2396 edge - edges, edge->opos / 64.0, edge->pos / 64.0));
2398 if (hints->recorder)
2399 hints->recorder(ta_serif_link2, hints, dim,
2400 edge, NULL, NULL, lower_bound, upper_bound);
2404 #ifdef TA_DEBUG
2405 num_actions++;
2406 #endif
2407 edge->flags |= TA_EDGE_DONE;
2409 if (edge > edges
2410 && edge->pos < edge[-1].pos)
2412 #ifdef TA_DEBUG
2413 TA_LOG((" BOUND: edge %d (pos=%.2f) moved to %.2f\n",
2414 edge - edges, edge->pos / 64.0, edge[-1].pos / 64.0));
2415 num_actions++;
2416 #endif
2418 edge->pos = edge[-1].pos;
2420 if (hints->recorder)
2421 hints->recorder(ta_bound, hints, dim,
2422 edge, &edge[-1], NULL, NULL, NULL);
2425 if (edge + 1 < edge_limit
2426 && edge[1].flags & TA_EDGE_DONE
2427 && edge->pos > edge[1].pos)
2429 #ifdef TA_DEBUG
2430 TA_LOG((" BOUND: edge %d (pos=%.2f) moved to %.2f\n",
2431 edge - edges, edge->pos / 64.0, edge[1].pos / 64.0));
2433 num_actions++;
2434 #endif
2436 edge->pos = edge[1].pos;
2438 if (hints->recorder)
2439 hints->recorder(ta_bound, hints, dim,
2440 edge, &edge[1], NULL, NULL, NULL);
2445 #ifdef TA_DEBUG
2446 if (!num_actions)
2447 TA_LOG((" (none)\n"));
2448 TA_LOG(("\n"));
2449 #endif
2453 /* apply the complete hinting algorithm to a latin glyph */
2455 static FT_Error
2456 ta_latin_hints_apply(TA_GlyphHints hints,
2457 FT_Outline* outline,
2458 TA_LatinMetrics metrics)
2460 FT_Error error;
2461 int dim;
2464 error = ta_glyph_hints_reload(hints, outline);
2465 if (error)
2466 goto Exit;
2468 /* analyze glyph outline */
2469 #ifdef TA_CONFIG_OPTION_USE_WARPER
2470 if (metrics->root.scaler.render_mode == FT_RENDER_MODE_LIGHT
2471 || TA_HINTS_DO_HORIZONTAL(hints))
2472 #else
2473 if (TA_HINTS_DO_HORIZONTAL(hints))
2474 #endif
2476 error = ta_latin_hints_detect_features(hints, TA_DIMENSION_HORZ);
2477 if (error)
2478 goto Exit;
2481 if (TA_HINTS_DO_VERTICAL(hints))
2483 error = ta_latin_hints_detect_features(hints, TA_DIMENSION_VERT);
2484 if (error)
2485 goto Exit;
2487 ta_latin_hints_compute_blue_edges(hints, metrics);
2490 /* grid-fit the outline */
2491 for (dim = 0; dim < TA_DIMENSION_MAX; dim++)
2493 #ifdef TA_CONFIG_OPTION_USE_WARPER
2494 if (dim == TA_DIMENSION_HORZ
2495 && metrics->root.scaler.render_mode == FT_RENDER_MODE_LIGHT)
2497 TA_WarperRec warper;
2498 FT_Fixed scale;
2499 FT_Pos delta;
2502 ta_warper_compute(&warper, hints, (TA_Dimension)dim, &scale, &delta);
2503 ta_glyph_hints_scale_dim(hints, (TA_Dimension)dim, scale, delta);
2505 continue;
2507 #endif
2509 if ((dim == TA_DIMENSION_HORZ && TA_HINTS_DO_HORIZONTAL(hints))
2510 || (dim == TA_DIMENSION_VERT && TA_HINTS_DO_VERTICAL(hints)))
2512 ta_latin_hint_edges(hints, (TA_Dimension)dim);
2513 ta_glyph_hints_align_edge_points(hints, (TA_Dimension)dim);
2514 ta_glyph_hints_align_strong_points(hints, (TA_Dimension)dim);
2515 ta_glyph_hints_align_weak_points(hints, (TA_Dimension)dim);
2518 ta_glyph_hints_save(hints, outline);
2520 Exit:
2521 return error;
2525 /* XXX: this should probably fine tuned to differentiate better between */
2526 /* scripts... */
2528 static const TA_Script_UniRangeRec ta_latin_uniranges[] =
2530 TA_UNIRANGE_REC(0x0020UL, 0x007FUL), /* Basic Latin (no control chars) */
2531 TA_UNIRANGE_REC(0x00A0UL, 0x00FFUL), /* Latin-1 Supplement (no control chars) */
2532 TA_UNIRANGE_REC(0x0100UL, 0x017FUL), /* Latin Extended-A */
2533 TA_UNIRANGE_REC(0x0180UL, 0x024FUL), /* Latin Extended-B */
2534 TA_UNIRANGE_REC(0x0250UL, 0x02AFUL), /* IPA Extensions */
2535 TA_UNIRANGE_REC(0x02B0UL, 0x02FFUL), /* Spacing Modifier Letters */
2536 TA_UNIRANGE_REC(0x0300UL, 0x036FUL), /* Combining Diacritical Marks */
2537 TA_UNIRANGE_REC(0x0370UL, 0x03FFUL), /* Greek and Coptic */
2538 TA_UNIRANGE_REC(0x0400UL, 0x04FFUL), /* Cyrillic */
2539 TA_UNIRANGE_REC(0x0500UL, 0x052FUL), /* Cyrillic Supplement */
2540 TA_UNIRANGE_REC(0x1D00UL, 0x1D7FUL), /* Phonetic Extensions */
2541 TA_UNIRANGE_REC(0x1D80UL, 0x1DBFUL), /* Phonetic Extensions Supplement */
2542 TA_UNIRANGE_REC(0x1DC0UL, 0x1DFFUL), /* Combining Diacritical Marks Supplement */
2543 TA_UNIRANGE_REC(0x1E00UL, 0x1EFFUL), /* Latin Extended Additional */
2544 TA_UNIRANGE_REC(0x1F00UL, 0x1FFFUL), /* Greek Extended */
2545 TA_UNIRANGE_REC(0x2000UL, 0x206FUL), /* General Punctuation */
2546 TA_UNIRANGE_REC(0x2070UL, 0x209FUL), /* Superscripts and Subscripts */
2547 TA_UNIRANGE_REC(0x20A0UL, 0x20CFUL), /* Currency Symbols */
2548 TA_UNIRANGE_REC(0x2150UL, 0x218FUL), /* Number Forms */
2549 TA_UNIRANGE_REC(0x2460UL, 0x24FFUL), /* Enclosed Alphanumerics */
2550 TA_UNIRANGE_REC(0x2C60UL, 0x2C7FUL), /* Latin Extended-C */
2551 TA_UNIRANGE_REC(0x2DE0UL, 0x2DFFUL), /* Cyrillic Extended-A */
2552 TA_UNIRANGE_REC(0x2E00UL, 0x2E7FUL), /* Supplemental Punctuation */
2553 TA_UNIRANGE_REC(0xA640UL, 0xA69FUL), /* Cyrillic Extended-B */
2554 TA_UNIRANGE_REC(0xA720UL, 0xA7FFUL), /* Latin Extended-D */
2555 TA_UNIRANGE_REC(0xFB00UL, 0xFB06UL), /* Alphab. Present. Forms (Latin Ligs) */
2556 TA_UNIRANGE_REC(0x1D400UL, 0x1D7FFUL), /* Mathematical Alphanumeric Symbols */
2557 TA_UNIRANGE_REC(0x1F100UL, 0x1F1FFUL), /* Enclosed Alphanumeric Supplement */
2558 TA_UNIRANGE_REC(0UL, 0UL)
2562 const TA_ScriptClassRec ta_latin_script_class =
2564 TA_SCRIPT_LATIN,
2565 ta_latin_uniranges,
2566 'o',
2568 sizeof (TA_LatinMetricsRec),
2570 (TA_Script_InitMetricsFunc)ta_latin_metrics_init,
2571 (TA_Script_ScaleMetricsFunc)ta_latin_metrics_scale,
2572 (TA_Script_DoneMetricsFunc)NULL,
2574 (TA_Script_InitHintsFunc)ta_latin_hints_init,
2575 (TA_Script_ApplyHintsFunc)ta_latin_hints_apply
2578 /* end of talatin.c */