Run `update-copyright'.
[ttfautohint.git] / lib / talatin.c
blob2d338480234fb333e7795a019e9ca66edbb16055
1 /* talatin.c */
3 /*
4 * Copyright (C) 2011-2013 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
35 #include <numberset.h>
38 /* find segments and links, compute all stem widths, and initialize */
39 /* standard width and height for the glyph with given charcode */
41 void
42 ta_latin_metrics_init_widths(TA_LatinMetrics metrics,
43 FT_Face face)
45 /* scan the array of segments in each direction */
46 TA_GlyphHintsRec hints[1];
49 TA_LOG(("standard widths computation\n"
50 "===========================\n\n"));
52 ta_glyph_hints_init(hints);
54 metrics->axis[TA_DIMENSION_HORZ].width_count = 0;
55 metrics->axis[TA_DIMENSION_VERT].width_count = 0;
58 FT_Error error;
59 FT_UInt glyph_index;
60 int dim;
61 TA_LatinMetricsRec dummy[1];
62 TA_Scaler scaler = &dummy->root.scaler;
65 glyph_index = FT_Get_Char_Index(face,
66 metrics->root.clazz->standard_char);
67 if (glyph_index == 0)
68 goto Exit;
70 TA_LOG(("standard character: 0x%X (glyph index %d)\n",
71 metrics->root.clazz->standard_char, glyph_index));
73 error = FT_Load_Glyph(face, glyph_index, FT_LOAD_NO_SCALE);
74 if (error || face->glyph->outline.n_points <= 0)
75 goto Exit;
77 memset(dummy, 0, sizeof (TA_LatinMetricsRec));
79 dummy->units_per_em = metrics->units_per_em;
81 scaler->x_scale = 0x10000L;
82 scaler->y_scale = 0x10000L;
83 scaler->x_delta = 0;
84 scaler->y_delta = 0;
86 scaler->face = face;
87 scaler->render_mode = FT_RENDER_MODE_NORMAL;
88 scaler->flags = 0;
90 ta_glyph_hints_rescale(hints, (TA_ScriptMetrics)dummy);
92 error = ta_glyph_hints_reload(hints, &face->glyph->outline);
93 if (error)
94 goto Exit;
96 for (dim = 0; dim < TA_DIMENSION_MAX; dim++)
98 TA_LatinAxis axis = &metrics->axis[dim];
99 TA_AxisHints axhints = &hints->axis[dim];
101 TA_Segment seg, limit, link;
102 FT_UInt num_widths = 0;
105 error = ta_latin_hints_compute_segments(hints, (TA_Dimension)dim);
106 if (error)
107 goto Exit;
109 ta_latin_hints_link_segments(hints, (TA_Dimension)dim);
111 seg = axhints->segments;
112 limit = seg + axhints->num_segments;
114 for (; seg < limit; seg++)
116 link = seg->link;
118 /* we only consider stem segments there! */
119 if (link
120 && link->link == seg
121 && link > seg)
123 FT_Pos dist;
126 dist = seg->pos - link->pos;
127 if (dist < 0)
128 dist = -dist;
130 if (num_widths < TA_LATIN_MAX_WIDTHS)
131 axis->widths[num_widths++].org = dist;
135 /* this also replaces multiple almost identical stem widths */
136 /* with a single one (the value 100 is heuristic) */
137 ta_sort_and_quantize_widths(&num_widths, axis->widths,
138 dummy->units_per_em / 100);
139 axis->width_count = num_widths;
142 Exit:
143 for (dim = 0; dim < TA_DIMENSION_MAX; dim++)
145 TA_LatinAxis axis = &metrics->axis[dim];
146 FT_Pos stdw;
149 stdw = (axis->width_count > 0) ? axis->widths[0].org
150 : TA_LATIN_CONSTANT(metrics, 50);
152 /* let's try 20% of the smallest width */
153 axis->edge_distance_threshold = stdw / 5;
154 axis->standard_width = stdw;
155 axis->extra_light = 0;
157 #ifdef TA_DEBUG
159 FT_UInt i;
162 TA_LOG(("%s widths:\n",
163 dim == TA_DIMENSION_VERT ? "horizontal"
164 : "vertical"));
166 TA_LOG((" %d (standard)", axis->standard_width));
167 for (i = 1; i < axis->width_count; i++)
168 TA_LOG((" %d", axis->widths[i].org));
170 TA_LOG(("\n"));
172 #endif
176 TA_LOG(("\n"));
178 ta_glyph_hints_done(hints);
182 #define TA_LATIN_MAX_TEST_CHARACTERS 12
185 static const char ta_latin_blue_chars[TA_LATIN_MAX_BLUES]
186 [TA_LATIN_MAX_TEST_CHARACTERS + 1] =
188 "THEZOCQS",
189 "HEZLOCUS",
190 "fijkdbh",
191 "xzroesc",
192 "xzroesc",
193 "pqgjy"
197 /* find all blue zones; flat segments give the reference points, */
198 /* round segments the overshoot positions */
200 static void
201 ta_latin_metrics_init_blues(TA_LatinMetrics metrics,
202 FT_Face face)
204 FT_Pos flats[TA_LATIN_MAX_TEST_CHARACTERS];
205 FT_Pos rounds[TA_LATIN_MAX_TEST_CHARACTERS];
206 FT_Int num_flats;
207 FT_Int num_rounds;
209 FT_Int bb;
210 TA_LatinBlue blue;
211 FT_Error error;
212 TA_LatinAxis axis = &metrics->axis[TA_DIMENSION_VERT];
213 FT_Outline outline;
216 /* we compute the blues simply by loading each character from the */
217 /* `ta_latin_blue_chars[blues]' string, then finding its top-most or */
218 /* bottom-most points (depending on `TA_IS_TOP_BLUE') */
220 TA_LOG(("blue zones computation\n"
221 "======================\n\n"));
223 for (bb = 0; bb < TA_LATIN_BLUE_MAX; bb++)
225 const char* p = ta_latin_blue_chars[bb];
226 const char* limit = p + TA_LATIN_MAX_TEST_CHARACTERS;
227 FT_Pos* blue_ref;
228 FT_Pos* blue_shoot;
231 TA_LOG(("blue zone %d:\n", bb));
233 num_flats = 0;
234 num_rounds = 0;
236 for (; p < limit && *p; p++)
238 FT_UInt glyph_index;
239 FT_Pos best_y; /* same as points.y */
240 FT_Int best_point, best_contour_first, best_contour_last;
241 FT_Vector* points;
242 FT_Bool round = 0;
245 /* load the character in the face -- skip unknown or empty ones */
246 glyph_index = FT_Get_Char_Index(face, (FT_UInt)*p);
247 if (glyph_index == 0)
248 continue;
250 error = FT_Load_Glyph(face, glyph_index, FT_LOAD_NO_SCALE);
251 outline = face->glyph->outline;
252 if (error || outline.n_points <= 0)
253 continue;
255 /* now compute min or max point indices and coordinates */
256 points = outline.points;
257 best_point = -1;
258 best_y = 0; /* make compiler happy */
259 best_contour_first = 0; /* ditto */
260 best_contour_last = 0; /* ditto */
263 FT_Int nn;
264 FT_Int first = 0;
265 FT_Int last = -1;
268 for (nn = 0; nn < outline.n_contours; first = last + 1, nn++)
270 FT_Int old_best_point = best_point;
271 FT_Int pp;
274 last = outline.contours[nn];
276 /* avoid single-point contours since they are never rasterized; */
277 /* in some fonts, they correspond to mark attachment points */
278 /* which are way outside of the glyph's real outline */
279 if (last <= first)
280 continue;
282 if (TA_LATIN_IS_TOP_BLUE(bb))
284 for (pp = first; pp <= last; pp++)
285 if (best_point < 0
286 || points[pp].y > best_y)
288 best_point = pp;
289 best_y = points[pp].y;
292 else
294 for (pp = first; pp <= last; pp++)
295 if (best_point < 0
296 || points[pp].y < best_y)
298 best_point = pp;
299 best_y = points[pp].y;
303 if (best_point != old_best_point)
305 best_contour_first = first;
306 best_contour_last = last;
309 TA_LOG((" %c %ld", *p, best_y));
312 /* now check whether the point belongs to a straight or round */
313 /* segment; we first need to find in which contour the extremum */
314 /* lies, then inspect its previous and next points */
315 if (best_point >= 0)
317 FT_Pos best_x = points[best_point].x;
318 FT_Int prev, next;
319 FT_Int best_on_point_first, best_on_point_last;
320 FT_Pos dist;
323 if (FT_CURVE_TAG(outline.tags[best_point]) == FT_CURVE_TAG_ON)
325 best_on_point_first = best_point;
326 best_on_point_last = best_point;
328 else
330 best_on_point_first = -1;
331 best_on_point_last = -1;
334 /* look for the previous and next points that are not on the */
335 /* same Y coordinate, then threshold the `closeness'... */
336 prev = best_point;
337 next = prev;
341 if (prev > best_contour_first)
342 prev--;
343 else
344 prev = best_contour_last;
346 dist = TA_ABS(points[prev].y - best_y);
347 /* accept a small distance or a small angle (both values are */
348 /* heuristic; value 20 corresponds to approx. 2.9 degrees) */
349 if (dist > 5)
350 if (TA_ABS(points[prev].x - best_x) <= 20 * dist)
351 break;
353 if (FT_CURVE_TAG(outline.tags[prev]) == FT_CURVE_TAG_ON)
355 best_on_point_first = prev;
356 if (best_on_point_last < 0)
357 best_on_point_last = prev;
360 } while (prev != best_point);
364 if (next < best_contour_last)
365 next++;
366 else
367 next = best_contour_first;
369 dist = TA_ABS(points[next].y - best_y);
370 if (dist > 5)
371 if (TA_ABS(points[next].x - best_x) <= 20 * dist)
372 break;
374 if (FT_CURVE_TAG(outline.tags[next]) == FT_CURVE_TAG_ON)
376 best_on_point_last = next;
377 if (best_on_point_first < 0)
378 best_on_point_first = next;
381 } while (next != best_point);
383 /* now set the `round' flag depending on the segment's kind */
384 /* (value 8 is heuristic) */
385 if (best_on_point_first >= 0
386 && best_on_point_last >= 0
387 && (FT_UInt)(TA_ABS(points[best_on_point_last].x
388 - points[best_on_point_first].x))
389 > metrics->units_per_em / 8)
390 round = 0;
391 else
392 round = FT_BOOL(
393 FT_CURVE_TAG(outline.tags[prev]) != FT_CURVE_TAG_ON
394 || FT_CURVE_TAG(outline.tags[next]) != FT_CURVE_TAG_ON);
396 TA_LOG((" (%s)\n", round ? "round" : "flat"));
399 if (round)
400 rounds[num_rounds++] = best_y;
401 else
402 flats[num_flats++] = best_y;
405 if (num_flats == 0 && num_rounds == 0)
407 /* we couldn't find a single glyph to compute this blue zone, */
408 /* we will simply ignore it then */
409 TA_LOG((" empty\n"));
410 continue;
413 /* we have computed the contents of the `rounds' and `flats' tables, */
414 /* now determine the reference and overshoot position of the blue -- */
415 /* we simply take the median value after a simple sort */
416 ta_sort_pos(num_rounds, rounds);
417 ta_sort_pos(num_flats, flats);
419 blue = &axis->blues[axis->blue_count];
420 blue_ref = &blue->ref.org;
421 blue_shoot = &blue->shoot.org;
423 axis->blue_count++;
425 if (num_flats == 0)
427 *blue_ref =
428 *blue_shoot = rounds[num_rounds / 2];
430 else if (num_rounds == 0)
432 *blue_ref =
433 *blue_shoot = flats[num_flats / 2];
435 else
437 *blue_ref = flats[num_flats / 2];
438 *blue_shoot = rounds[num_rounds / 2];
441 /* there are sometimes problems if the overshoot position of top */
442 /* zones is under its reference position, or the opposite for bottom */
443 /* zones; we must thus check everything there and correct the errors */
444 if (*blue_shoot != *blue_ref)
446 FT_Pos ref = *blue_ref;
447 FT_Pos shoot = *blue_shoot;
448 FT_Bool over_ref = FT_BOOL(shoot > ref);
451 if (TA_LATIN_IS_TOP_BLUE(bb) ^ over_ref)
453 *blue_ref =
454 *blue_shoot = (shoot + ref) / 2;
456 TA_LOG((" [overshoot smaller than reference,"
457 " taking mean value]\n"));
461 blue->flags = 0;
462 if (TA_LATIN_IS_TOP_BLUE(bb))
463 blue->flags |= TA_LATIN_BLUE_TOP;
465 /* the following flag is used later to adjust the y and x scales */
466 /* in order to optimize the pixel grid alignment */
467 /* of the top of small letters */
468 if (bb == TA_LATIN_BLUE_SMALL_TOP)
469 blue->flags |= TA_LATIN_BLUE_ADJUSTMENT;
471 TA_LOG((" -> reference = %ld\n"
472 " overshoot = %ld\n",
473 *blue_ref, *blue_shoot));
476 /* add two blue zones for usWinAscent and usWinDescent */
477 /* just in case the above algorithm has missed them -- */
478 /* Windows cuts off everything outside of those two values */
480 TT_OS2* os2;
483 os2 = (TT_OS2*)FT_Get_Sfnt_Table(face, ft_sfnt_os2);
485 if (os2)
487 blue = &axis->blues[axis->blue_count];
488 blue->flags = TA_LATIN_BLUE_TOP | TA_LATIN_BLUE_ACTIVE;
489 blue->ref.org =
490 blue->shoot.org = os2->usWinAscent;
492 TA_LOG(("artificial blue zone for usWinAscent:\n"
493 " -> reference = %ld\n"
494 " overshoot = %ld\n",
495 blue->ref.org, blue->shoot.org));
497 blue = &axis->blues[axis->blue_count + 1];
498 blue->flags = TA_LATIN_BLUE_ACTIVE;
499 blue->ref.org =
500 blue->shoot.org = -os2->usWinDescent;
502 TA_LOG(("artificial blue zone for usWinDescent:\n"
503 " -> reference = %ld\n"
504 " overshoot = %ld\n",
505 blue->ref.org, blue->shoot.org));
507 else
509 blue = &axis->blues[axis->blue_count];
510 blue->flags =
511 blue->ref.org =
512 blue->shoot.org = 0;
514 blue = &axis->blues[axis->blue_count + 1];
515 blue->flags =
516 blue->ref.org =
517 blue->shoot.org = 0;
521 TA_LOG(("\n"));
523 return;
527 /* check whether all ASCII digits have the same advance width */
529 void
530 ta_latin_metrics_check_digits(TA_LatinMetrics metrics,
531 FT_Face face)
533 FT_UInt i;
534 FT_Bool started = 0, same_width = 1;
535 FT_Fixed advance, old_advance = 0;
538 /* digit `0' is 0x30 in all supported charmaps */
539 for (i = 0x30; i <= 0x39; i++)
541 FT_UInt glyph_index;
544 glyph_index = FT_Get_Char_Index(face, i);
545 if (glyph_index == 0)
546 continue;
548 if (FT_Get_Advance(face, glyph_index,
549 FT_LOAD_NO_SCALE
550 | FT_LOAD_NO_HINTING
551 | FT_LOAD_IGNORE_TRANSFORM,
552 &advance))
553 continue;
555 if (started)
557 if (advance != old_advance)
559 same_width = 0;
560 break;
563 else
565 old_advance = advance;
566 started = 1;
570 metrics->root.digits_have_same_width = same_width;
574 /* initialize global metrics */
576 FT_Error
577 ta_latin_metrics_init(TA_LatinMetrics metrics,
578 FT_Face face)
580 FT_CharMap oldmap = face->charmap;
583 metrics->units_per_em = face->units_per_EM;
585 if (!FT_Select_Charmap(face, FT_ENCODING_UNICODE))
587 ta_latin_metrics_init_widths(metrics, face);
588 ta_latin_metrics_init_blues(metrics, face);
589 ta_latin_metrics_check_digits(metrics, face);
592 FT_Set_Charmap(face, oldmap);
593 return FT_Err_Ok;
597 /* adjust scaling value, then scale and shift widths */
598 /* and blue zones (if applicable) for given dimension */
600 static void
601 ta_latin_metrics_scale_dim(TA_LatinMetrics metrics,
602 TA_Scaler scaler,
603 TA_Dimension dim)
605 FT_Fixed scale;
606 FT_Pos delta;
607 TA_LatinAxis axis;
608 FT_UInt ppem;
609 FT_UInt nn;
612 ppem = metrics->root.scaler.face->size->metrics.x_ppem;
614 if (dim == TA_DIMENSION_HORZ)
616 scale = scaler->x_scale;
617 delta = scaler->x_delta;
619 else
621 scale = scaler->y_scale;
622 delta = scaler->y_delta;
625 axis = &metrics->axis[dim];
627 if (axis->org_scale == scale && axis->org_delta == delta)
628 return;
630 axis->org_scale = scale;
631 axis->org_delta = delta;
633 /* correct X and Y scale to optimize the alignment of the top of */
634 /* small letters to the pixel grid */
635 /* (if we do x-height snapping for this ppem value) */
636 if (!number_set_is_element(
637 metrics->root.globals->font->x_height_snapping_exceptions,
638 ppem))
640 TA_LatinAxis Axis = &metrics->axis[TA_DIMENSION_VERT];
641 TA_LatinBlue blue = NULL;
644 for (nn = 0; nn < Axis->blue_count; nn++)
646 if (Axis->blues[nn].flags & TA_LATIN_BLUE_ADJUSTMENT)
648 blue = &Axis->blues[nn];
649 break;
653 if (blue)
655 FT_Pos scaled;
656 FT_Pos threshold;
657 FT_Pos fitted;
658 FT_UInt limit;
661 scaled = FT_MulFix(blue->shoot.org, scaler->y_scale);
662 limit = metrics->root.globals->increase_x_height;
663 threshold = 40;
665 /* if the `increase-x-height' property is active, */
666 /* we round up much more often */
667 if (limit
668 && ppem <= limit
669 && ppem >= TA_PROP_INCREASE_X_HEIGHT_MIN)
670 threshold = 52;
672 fitted = (scaled + threshold) & ~63;
674 if (scaled != fitted)
676 if (dim == TA_DIMENSION_VERT)
677 scale = FT_MulDiv(scale, fitted, scaled);
682 axis->scale = scale;
683 axis->delta = delta;
685 if (dim == TA_DIMENSION_HORZ)
687 metrics->root.scaler.x_scale = scale;
688 metrics->root.scaler.x_delta = delta;
690 else
692 metrics->root.scaler.y_scale = scale;
693 metrics->root.scaler.y_delta = delta;
696 /* scale the widths */
697 for (nn = 0; nn < axis->width_count; nn++)
699 TA_Width width = axis->widths + nn;
702 width->cur = FT_MulFix(width->org, scale);
703 width->fit = width->cur;
706 /* an extra-light axis corresponds to a standard width that is */
707 /* smaller than 5/8 pixels */
708 axis->extra_light =
709 (FT_Bool)(FT_MulFix(axis->standard_width, scale) < 32 + 8);
711 if (dim == TA_DIMENSION_VERT)
713 /* scale the blue zones */
714 for (nn = 0; nn < axis->blue_count; nn++)
716 TA_LatinBlue blue = &axis->blues[nn];
717 FT_Pos dist;
720 blue->ref.cur = FT_MulFix(blue->ref.org, scale) + delta;
721 blue->ref.fit = blue->ref.cur;
722 blue->shoot.cur = FT_MulFix(blue->shoot.org, scale) + delta;
723 blue->shoot.fit = blue->shoot.cur;
724 blue->flags &= ~TA_LATIN_BLUE_ACTIVE;
726 /* a blue zone is only active if it is less than 3/4 pixels tall */
727 dist = FT_MulFix(blue->ref.org - blue->shoot.org, scale);
728 if (dist <= 48 && dist >= -48)
730 #if 0
731 FT_Pos delta1;
732 #endif
733 FT_Pos delta2;
736 /* use discrete values for blue zone widths */
738 #if 0
739 /* generic, original code */
740 delta1 = blue->shoot.org - blue->ref.org;
741 delta2 = delta1;
742 if (delta1 < 0)
743 delta2 = -delta2;
745 delta2 = FT_MulFix(delta2, scale);
747 if (delta2 < 32)
748 delta2 = 0;
749 else if (delta2 < 64)
750 delta2 = 32 + (((delta2 - 32) + 16) & ~31);
751 else
752 delta2 = TA_PIX_ROUND(delta2);
754 if (delta1 < 0)
755 delta2 = -delta2;
757 blue->ref.fit = TA_PIX_ROUND(blue->ref.cur);
758 blue->shoot.fit = blue->ref.fit + delta2;
759 #else
760 /* simplified version due to abs(dist) <= 48 */
761 delta2 = dist;
762 if (dist < 0)
763 delta2 = -delta2;
765 if (delta2 < 32)
766 delta2 = 0;
767 else if (delta < 48)
768 delta2 = 32;
769 else
770 delta2 = 64;
772 if (dist < 0)
773 delta2 = -delta2;
775 blue->ref.fit = TA_PIX_ROUND(blue->ref.cur);
776 blue->shoot.fit = blue->ref.fit - delta2;
777 #endif
779 blue->flags |= TA_LATIN_BLUE_ACTIVE;
783 /* the last two artificial blue zones are to be scaled */
784 /* with uncorrected scaling values */
786 TA_LatinAxis a = &metrics->axis[TA_DIMENSION_VERT];
787 TA_LatinBlue b;
790 b = &a->blues[a->blue_count];
791 b->ref.cur =
792 b->ref.fit =
793 b->shoot.cur =
794 b->shoot.fit = FT_MulFix(b->ref.org, a->org_scale) + delta;
796 b = &a->blues[a->blue_count + 1];
797 b->ref.cur =
798 b->ref.fit =
799 b->shoot.cur =
800 b->shoot.fit = FT_MulFix(b->ref.org, a->org_scale) + delta;
806 /* scale global values in both directions */
808 void
809 ta_latin_metrics_scale(TA_LatinMetrics metrics,
810 TA_Scaler scaler)
812 metrics->root.scaler.render_mode = scaler->render_mode;
813 metrics->root.scaler.face = scaler->face;
814 metrics->root.scaler.flags = scaler->flags;
816 ta_latin_metrics_scale_dim(metrics, scaler, TA_DIMENSION_HORZ);
817 ta_latin_metrics_scale_dim(metrics, scaler, TA_DIMENSION_VERT);
821 /* walk over all contours and compute its segments */
823 FT_Error
824 ta_latin_hints_compute_segments(TA_GlyphHints hints,
825 TA_Dimension dim)
827 TA_AxisHints axis = &hints->axis[dim];
828 FT_Error error = FT_Err_Ok;
830 TA_Segment segment = NULL;
831 TA_SegmentRec seg0;
833 TA_Point* contour = hints->contours;
834 TA_Point* contour_limit = contour + hints->num_contours;
835 TA_Direction major_dir, segment_dir;
838 memset(&seg0, 0, sizeof (TA_SegmentRec));
839 seg0.score = 32000;
840 seg0.flags = TA_EDGE_NORMAL;
842 major_dir = (TA_Direction)TA_ABS(axis->major_dir);
843 segment_dir = major_dir;
845 axis->num_segments = 0;
847 /* set up (u,v) in each point */
848 if (dim == TA_DIMENSION_HORZ)
850 TA_Point point = hints->points;
851 TA_Point limit = point + hints->num_points;
854 for (; point < limit; point++)
856 point->u = point->fx;
857 point->v = point->fy;
860 else
862 TA_Point point = hints->points;
863 TA_Point limit = point + hints->num_points;
866 for (; point < limit; point++)
868 point->u = point->fy;
869 point->v = point->fx;
873 /* do each contour separately */
874 for (; contour < contour_limit; contour++)
876 TA_Point point = contour[0];
877 TA_Point last = point->prev;
879 int on_edge = 0;
881 FT_Pos min_pos = 32000; /* minimum segment pos != min_coord */
882 FT_Pos max_pos = -32000; /* maximum segment pos != max_coord */
883 FT_Bool passed;
886 if (point == last) /* skip singletons -- just in case */
887 continue;
889 if (TA_ABS(last->out_dir) == major_dir
890 && TA_ABS(point->out_dir) == major_dir)
892 /* we are already on an edge, try to locate its start */
893 last = point;
895 for (;;)
897 point = point->prev;
898 if (TA_ABS(point->out_dir) != major_dir)
900 point = point->next;
901 break;
903 if (point == last)
904 break;
908 last = point;
909 passed = 0;
911 for (;;)
913 FT_Pos u, v;
916 if (on_edge)
918 u = point->u;
919 if (u < min_pos)
920 min_pos = u;
921 if (u > max_pos)
922 max_pos = u;
924 if (point->out_dir != segment_dir
925 || point == last)
927 /* we are just leaving an edge; record a new segment! */
928 segment->last = point;
929 segment->pos = (FT_Short)((min_pos + max_pos) >> 1);
931 /* a segment is round if either its first or last point */
932 /* is a control point */
933 if ((segment->first->flags | point->flags) & TA_FLAG_CONTROL)
934 segment->flags |= TA_EDGE_ROUND;
936 /* compute segment size */
937 min_pos = max_pos = point->v;
939 v = segment->first->v;
940 if (v < min_pos)
941 min_pos = v;
942 if (v > max_pos)
943 max_pos = v;
945 segment->min_coord = (FT_Short)min_pos;
946 segment->max_coord = (FT_Short)max_pos;
947 segment->height = (FT_Short)(segment->max_coord -
948 segment->min_coord);
950 on_edge = 0;
951 segment = NULL;
952 /* fall through */
956 /* now exit if we are at the start/end point */
957 if (point == last)
959 if (passed)
960 break;
961 passed = 1;
964 if (!on_edge
965 && TA_ABS(point->out_dir) == major_dir)
967 /* this is the start of a new segment! */
968 segment_dir = (TA_Direction)point->out_dir;
970 /* clear all segment fields */
971 error = ta_axis_hints_new_segment(axis, &segment);
972 if (error)
973 goto Exit;
975 segment[0] = seg0;
976 segment->dir = (FT_Char)segment_dir;
977 min_pos = max_pos = point->u;
978 segment->first = point;
979 segment->last = point;
980 on_edge = 1;
983 point = point->next;
985 } /* contours */
988 /* now slightly increase the height of segments if this makes sense -- */
989 /* this is used to better detect and ignore serifs */
991 TA_Segment segments = axis->segments;
992 TA_Segment segments_end = segments + axis->num_segments;
995 for (segment = segments; segment < segments_end; segment++)
997 TA_Point first = segment->first;
998 TA_Point last = segment->last;
1000 FT_Pos first_v = first->v;
1001 FT_Pos last_v = last->v;
1004 if (first == last)
1005 continue;
1007 if (first_v < last_v)
1009 TA_Point p;
1012 p = first->prev;
1013 if (p->v < first_v)
1014 segment->height = (FT_Short)(segment->height +
1015 ((first_v - p->v) >> 1));
1017 p = last->next;
1018 if (p->v > last_v)
1019 segment->height = (FT_Short)(segment->height +
1020 ((p->v - last_v) >> 1));
1022 else
1024 TA_Point p;
1027 p = first->prev;
1028 if (p->v > first_v)
1029 segment->height = (FT_Short)(segment->height +
1030 ((p->v - first_v) >> 1));
1032 p = last->next;
1033 if (p->v < last_v)
1034 segment->height = (FT_Short)(segment->height +
1035 ((last_v - p->v) >> 1));
1040 Exit:
1041 return error;
1045 /* link segments to form stems and serifs */
1047 void
1048 ta_latin_hints_link_segments(TA_GlyphHints hints,
1049 TA_Dimension dim)
1051 TA_AxisHints axis = &hints->axis[dim];
1053 TA_Segment segments = axis->segments;
1054 TA_Segment segment_limit = segments + axis->num_segments;
1056 FT_Pos len_threshold, len_score;
1057 TA_Segment seg1, seg2;
1060 len_threshold = TA_LATIN_CONSTANT(hints->metrics, 8);
1061 if (len_threshold == 0)
1062 len_threshold = 1;
1064 len_score = TA_LATIN_CONSTANT(hints->metrics, 6000);
1066 /* now compare each segment to the others */
1067 for (seg1 = segments; seg1 < segment_limit; seg1++)
1069 /* the fake segments are introduced to hint the metrics -- */
1070 /* we must never link them to anything */
1071 if (seg1->dir != axis->major_dir
1072 || seg1->first == seg1->last)
1073 continue;
1075 /* search for stems having opposite directions, */
1076 /* with seg1 to the `left' of seg2 */
1077 for (seg2 = segments; seg2 < segment_limit; seg2++)
1079 FT_Pos pos1 = seg1->pos;
1080 FT_Pos pos2 = seg2->pos;
1083 if (seg1->dir + seg2->dir == 0
1084 && pos2 > pos1)
1086 /* compute distance between the two segments */
1087 FT_Pos dist = pos2 - pos1;
1088 FT_Pos min = seg1->min_coord;
1089 FT_Pos max = seg1->max_coord;
1090 FT_Pos len, score;
1093 if (min < seg2->min_coord)
1094 min = seg2->min_coord;
1095 if (max > seg2->max_coord)
1096 max = seg2->max_coord;
1098 /* compute maximum coordinate difference of the two segments */
1099 len = max - min;
1100 if (len >= len_threshold)
1102 /* small coordinate differences cause a higher score, and */
1103 /* segments with a greater distance cause a higher score also */
1104 score = dist + len_score / len;
1106 /* and we search for the smallest score */
1107 /* of the sum of the two values */
1108 if (score < seg1->score)
1110 seg1->score = score;
1111 seg1->link = seg2;
1114 if (score < seg2->score)
1116 seg2->score = score;
1117 seg2->link = seg1;
1124 /* now compute the `serif' segments, cf. explanations in `tahints.h' */
1125 for (seg1 = segments; seg1 < segment_limit; seg1++)
1127 seg2 = seg1->link;
1129 if (seg2)
1131 if (seg2->link != seg1)
1133 seg1->link = 0;
1134 seg1->serif = seg2->link;
1141 /* link segments to edges, using feature analysis for selection */
1143 FT_Error
1144 ta_latin_hints_compute_edges(TA_GlyphHints hints,
1145 TA_Dimension dim)
1147 TA_AxisHints axis = &hints->axis[dim];
1148 FT_Error error = FT_Err_Ok;
1149 TA_LatinAxis laxis = &((TA_LatinMetrics)hints->metrics)->axis[dim];
1151 TA_Segment segments = axis->segments;
1152 TA_Segment segment_limit = segments + axis->num_segments;
1153 TA_Segment seg;
1155 #if 0
1156 TA_Direction up_dir;
1157 #endif
1158 FT_Fixed scale;
1159 FT_Pos edge_distance_threshold;
1160 FT_Pos segment_length_threshold;
1163 axis->num_edges = 0;
1165 scale = (dim == TA_DIMENSION_HORZ) ? hints->x_scale
1166 : hints->y_scale;
1168 #if 0
1169 up_dir = (dim == TA_DIMENSION_HORZ) ? TA_DIR_UP
1170 : TA_DIR_RIGHT;
1171 #endif
1173 /* we ignore all segments that are less than 1 pixel in length */
1174 /* to avoid many problems with serif fonts */
1175 /* (the corresponding threshold is computed in font units) */
1176 if (dim == TA_DIMENSION_HORZ)
1177 segment_length_threshold = FT_DivFix(64, hints->y_scale);
1178 else
1179 segment_length_threshold = 0;
1181 /********************************************************************/
1182 /* */
1183 /* We begin by generating a sorted table of edges for the current */
1184 /* direction. To do so, we simply scan each segment and try to find */
1185 /* an edge in our table that corresponds to its position. */
1186 /* */
1187 /* If no edge is found, we create and insert a new edge in the */
1188 /* sorted table. Otherwise, we simply add the segment to the edge's */
1189 /* list which gets processed in the second step to compute the */
1190 /* edge's properties. */
1191 /* */
1192 /* Note that the table of edges is sorted along the segment/edge */
1193 /* position. */
1194 /* */
1195 /********************************************************************/
1197 /* assure that edge distance threshold is at most 0.25px */
1198 edge_distance_threshold = FT_MulFix(laxis->edge_distance_threshold,
1199 scale);
1200 if (edge_distance_threshold > 64 / 4)
1201 edge_distance_threshold = 64 / 4;
1203 edge_distance_threshold = FT_DivFix(edge_distance_threshold,
1204 scale);
1206 for (seg = segments; seg < segment_limit; seg++)
1208 TA_Edge found = NULL;
1209 FT_Int ee;
1212 if (seg->height < segment_length_threshold)
1213 continue;
1215 /* a special case for serif edges: */
1216 /* if they are smaller than 1.5 pixels we ignore them */
1217 if (seg->serif
1218 && 2 * seg->height < 3 * segment_length_threshold)
1219 continue;
1221 /* look for an edge corresponding to the segment */
1222 for (ee = 0; ee < axis->num_edges; ee++)
1224 TA_Edge edge = axis->edges + ee;
1225 FT_Pos dist;
1228 dist = seg->pos - edge->fpos;
1229 if (dist < 0)
1230 dist = -dist;
1232 if (dist < edge_distance_threshold && edge->dir == seg->dir)
1234 found = edge;
1235 break;
1239 if (!found)
1241 TA_Edge edge;
1244 /* insert a new edge in the list and sort according to the position */
1245 error = ta_axis_hints_new_edge(axis, seg->pos,
1246 (TA_Direction)seg->dir,
1247 &edge);
1248 if (error)
1249 goto Exit;
1251 /* add the segment to the new edge's list */
1252 memset(edge, 0, sizeof (TA_EdgeRec));
1253 edge->first = seg;
1254 edge->last = seg;
1255 edge->dir = seg->dir;
1256 edge->fpos = seg->pos;
1257 edge->opos = FT_MulFix(seg->pos, scale);
1258 edge->pos = edge->opos;
1259 seg->edge_next = seg;
1261 else
1263 /* if an edge was found, simply add the segment to the edge's list */
1264 seg->edge_next = found->first;
1265 found->last->edge_next = seg;
1266 found->last = seg;
1270 /*****************************************************************/
1271 /* */
1272 /* Good, we now compute each edge's properties according to */
1273 /* the segments found on its position. Basically, these are */
1274 /* */
1275 /* - the edge's main direction */
1276 /* - stem edge, serif edge or both (which defaults to stem then) */
1277 /* - rounded edge, straight or both (which defaults to straight) */
1278 /* - link for edge */
1279 /* */
1280 /*****************************************************************/
1282 /* first of all, set the `edge' field in each segment -- this is */
1283 /* required in order to compute edge links */
1285 /* note that removing this loop and setting the `edge' field of each */
1286 /* segment directly in the code above slows down execution speed for */
1287 /* some reasons on platforms like the Sun */
1289 TA_Edge edges = axis->edges;
1290 TA_Edge edge_limit = edges + axis->num_edges;
1291 TA_Edge edge;
1294 for (edge = edges; edge < edge_limit; edge++)
1296 seg = edge->first;
1297 if (seg)
1300 seg->edge = edge;
1301 seg = seg->edge_next;
1302 } while (seg != edge->first);
1305 /* now compute each edge properties */
1306 for (edge = edges; edge < edge_limit; edge++)
1308 FT_Int is_round = 0; /* does it contain round segments? */
1309 FT_Int is_straight = 0; /* does it contain straight segments? */
1310 #if 0
1311 FT_Pos ups = 0; /* number of upwards segments */
1312 FT_Pos downs = 0; /* number of downwards segments */
1313 #endif
1316 seg = edge->first;
1320 FT_Bool is_serif;
1323 /* check for roundness of segment */
1324 if (seg->flags & TA_EDGE_ROUND)
1325 is_round++;
1326 else
1327 is_straight++;
1329 #if 0
1330 /* check for segment direction */
1331 if (seg->dir == up_dir)
1332 ups += seg->max_coord - seg->min_coord;
1333 else
1334 downs += seg->max_coord - seg->min_coord;
1335 #endif
1337 /* check for links -- */
1338 /* if seg->serif is set, then seg->link must be ignored */
1339 is_serif = (FT_Bool)(seg->serif
1340 && seg->serif->edge
1341 && seg->serif->edge != edge);
1343 if ((seg->link && seg->link->edge != NULL)
1344 || is_serif)
1346 TA_Edge edge2;
1347 TA_Segment seg2;
1350 edge2 = edge->link;
1351 seg2 = seg->link;
1353 if (is_serif)
1355 seg2 = seg->serif;
1356 edge2 = edge->serif;
1359 if (edge2)
1361 FT_Pos edge_delta;
1362 FT_Pos seg_delta;
1365 edge_delta = edge->fpos - edge2->fpos;
1366 if (edge_delta < 0)
1367 edge_delta = -edge_delta;
1369 seg_delta = seg->pos - seg2->pos;
1370 if (seg_delta < 0)
1371 seg_delta = -seg_delta;
1373 if (seg_delta < edge_delta)
1374 edge2 = seg2->edge;
1376 else
1377 edge2 = seg2->edge;
1379 if (is_serif)
1381 edge->serif = edge2;
1382 edge2->flags |= TA_EDGE_SERIF;
1384 else
1385 edge->link = edge2;
1388 seg = seg->edge_next;
1389 } while (seg != edge->first);
1391 /* set the round/straight flags */
1392 edge->flags = TA_EDGE_NORMAL;
1394 if (is_round > 0
1395 && is_round >= is_straight)
1396 edge->flags |= TA_EDGE_ROUND;
1398 #if 0
1399 /* set the edge's main direction */
1400 edge->dir = TA_DIR_NONE;
1402 if (ups > downs)
1403 edge->dir = (FT_Char)up_dir;
1405 else if (ups < downs)
1406 edge->dir = (FT_Char)-up_dir;
1408 else if (ups == downs)
1409 edge->dir = 0; /* both up and down! */
1410 #endif
1412 /* get rid of serifs if link is set */
1413 /* XXX: this gets rid of many unpleasant artefacts! */
1414 /* example: the `c' in cour.pfa at size 13 */
1416 if (edge->serif && edge->link)
1417 edge->serif = 0;
1421 Exit:
1422 return error;
1426 /* detect segments and edges for given dimension */
1428 FT_Error
1429 ta_latin_hints_detect_features(TA_GlyphHints hints,
1430 TA_Dimension dim)
1432 FT_Error error;
1435 error = ta_latin_hints_compute_segments(hints, dim);
1436 if (!error)
1438 ta_latin_hints_link_segments(hints, dim);
1440 error = ta_latin_hints_compute_edges(hints, dim);
1443 return error;
1447 /* compute all edges which lie within blue zones */
1449 void
1450 ta_latin_hints_compute_blue_edges(TA_GlyphHints hints,
1451 TA_LatinMetrics metrics)
1453 TA_AxisHints axis = &hints->axis[TA_DIMENSION_VERT];
1455 TA_Edge edge = axis->edges;
1456 TA_Edge edge_limit = edge + axis->num_edges;
1458 TA_LatinAxis latin = &metrics->axis[TA_DIMENSION_VERT];
1459 FT_Fixed scale = latin->scale;
1462 /* compute which blue zones are active, */
1463 /* i.e. have their scaled size < 3/4 pixels */
1465 /* for each horizontal edge search the blue zone which is closest */
1466 for (; edge < edge_limit; edge++)
1468 FT_UInt bb;
1469 TA_Width best_blue = NULL;
1470 FT_Pos best_dist; /* initial threshold */
1472 FT_UInt best_blue_idx = 0;
1473 FT_Bool best_blue_is_shoot = 0;
1476 /* compute the initial threshold as a fraction of the EM size */
1477 /* (the value 40 is heuristic) */
1478 best_dist = FT_MulFix(metrics->units_per_em / 40, scale);
1480 /* assure a minimum distance of 0.5px */
1481 if (best_dist > 64 / 2)
1482 best_dist = 64 / 2;
1484 /* this loop also handles the two extra blue zones */
1485 /* for usWinAscent and usWinDescent */
1486 /* if option `windows-compatibility' is set */
1487 for (bb = 0;
1488 bb < latin->blue_count
1489 + (metrics->root.globals->font->windows_compatibility ? 2 : 0);
1490 bb++)
1492 TA_LatinBlue blue = latin->blues + bb;
1493 FT_Bool is_top_blue, is_major_dir;
1496 /* skip inactive blue zones (i.e., those that are too large) */
1497 if (!(blue->flags & TA_LATIN_BLUE_ACTIVE))
1498 continue;
1500 /* if it is a top zone, check for right edges -- */
1501 /* if it is a bottom zone, check for left edges */
1502 is_top_blue = (FT_Byte)((blue->flags & TA_LATIN_BLUE_TOP) != 0);
1503 is_major_dir = FT_BOOL(edge->dir == axis->major_dir);
1505 /* if it is a top zone, the edge must be against the major */
1506 /* direction; if it is a bottom zone, it must be in the major */
1507 /* direction */
1508 if (is_top_blue ^ is_major_dir)
1510 FT_Pos dist;
1513 /* first of all, compare it to the reference position */
1514 dist = edge->fpos - blue->ref.org;
1515 if (dist < 0)
1516 dist = -dist;
1518 dist = FT_MulFix(dist, scale);
1519 if (dist < best_dist)
1521 best_dist = dist;
1522 best_blue = &blue->ref;
1524 best_blue_idx = bb;
1525 best_blue_is_shoot = 0;
1528 /* now compare it to the overshoot position and check whether */
1529 /* the edge is rounded, and whether the edge is over the */
1530 /* reference position of a top zone, or under the reference */
1531 /* position of a bottom zone */
1532 if (edge->flags & TA_EDGE_ROUND
1533 && dist != 0)
1535 FT_Bool is_under_ref = FT_BOOL(edge->fpos < blue->ref.org);
1538 if (is_top_blue ^ is_under_ref)
1540 dist = edge->fpos - blue->shoot.org;
1541 if (dist < 0)
1542 dist = -dist;
1544 dist = FT_MulFix(dist, scale);
1545 if (dist < best_dist)
1547 best_dist = dist;
1548 best_blue = &blue->shoot;
1550 best_blue_idx = bb;
1551 best_blue_is_shoot = 1;
1558 if (best_blue)
1560 edge->blue_edge = best_blue;
1561 edge->best_blue_idx = best_blue_idx;
1562 edge->best_blue_is_shoot = best_blue_is_shoot;
1568 /* initalize hinting engine */
1570 static FT_Error
1571 ta_latin_hints_init(TA_GlyphHints hints,
1572 TA_LatinMetrics metrics)
1574 FT_Render_Mode mode;
1575 FT_UInt32 scaler_flags, other_flags;
1576 FT_Face face = metrics->root.scaler.face;
1579 ta_glyph_hints_rescale(hints, (TA_ScriptMetrics)metrics);
1581 /* correct x_scale and y_scale if needed, since they may have */
1582 /* been modified by `ta_latin_metrics_scale_dim' above */
1583 hints->x_scale = metrics->axis[TA_DIMENSION_HORZ].scale;
1584 hints->x_delta = metrics->axis[TA_DIMENSION_HORZ].delta;
1585 hints->y_scale = metrics->axis[TA_DIMENSION_VERT].scale;
1586 hints->y_delta = metrics->axis[TA_DIMENSION_VERT].delta;
1588 /* compute flags depending on render mode, etc. */
1589 mode = metrics->root.scaler.render_mode;
1591 #if 0 /* #ifdef TA_CONFIG_OPTION_USE_WARPER */
1592 if (mode == FT_RENDER_MODE_LCD
1593 || mode == FT_RENDER_MODE_LCD_V)
1594 metrics->root.scaler.render_mode =
1595 mode = FT_RENDER_MODE_NORMAL;
1596 #endif
1598 scaler_flags = hints->scaler_flags;
1599 other_flags = 0;
1601 /* we snap the width of vertical stems for the monochrome */
1602 /* and horizontal LCD rendering targets only */
1603 if (mode == FT_RENDER_MODE_MONO
1604 || mode == FT_RENDER_MODE_LCD)
1605 other_flags |= TA_LATIN_HINTS_HORZ_SNAP;
1607 /* we snap the width of horizontal stems for the monochrome */
1608 /* and vertical LCD rendering targets only */
1609 if (mode == FT_RENDER_MODE_MONO
1610 || mode == FT_RENDER_MODE_LCD_V)
1611 other_flags |= TA_LATIN_HINTS_VERT_SNAP;
1613 /* we adjust stems to full pixels only if we don't use the `light' mode */
1614 if (mode != FT_RENDER_MODE_LIGHT)
1615 other_flags |= TA_LATIN_HINTS_STEM_ADJUST;
1617 if (mode == FT_RENDER_MODE_MONO)
1618 other_flags |= TA_LATIN_HINTS_MONO;
1620 /* in `light' hinting mode we disable horizontal hinting completely; */
1621 /* we also do it if the face is italic */
1622 if (mode == FT_RENDER_MODE_LIGHT
1623 || (face->style_flags & FT_STYLE_FLAG_ITALIC) != 0)
1624 scaler_flags |= TA_SCALER_FLAG_NO_HORIZONTAL;
1626 hints->scaler_flags = scaler_flags;
1627 hints->other_flags = other_flags;
1629 return FT_Err_Ok;
1633 /* snap a given width in scaled coordinates to */
1634 /* one of the current standard widths */
1636 static FT_Pos
1637 ta_latin_snap_width(TA_Width widths,
1638 FT_Int count,
1639 FT_Pos width)
1641 int n;
1642 FT_Pos best = 64 + 32 + 2;
1643 FT_Pos reference = width;
1644 FT_Pos scaled;
1647 for (n = 0; n < count; n++)
1649 FT_Pos w;
1650 FT_Pos dist;
1653 w = widths[n].cur;
1654 dist = width - w;
1655 if (dist < 0)
1656 dist = -dist;
1657 if (dist < best)
1659 best = dist;
1660 reference = w;
1664 scaled = TA_PIX_ROUND(reference);
1666 if (width >= reference)
1668 if (width < scaled + 48)
1669 width = reference;
1671 else
1673 if (width > scaled - 48)
1674 width = reference;
1677 return width;
1681 /* compute the snapped width of a given stem, ignoring very thin ones */
1683 /* there is a lot of voodoo in this function; changing the hard-coded */
1684 /* parameters influence the whole hinting process */
1686 static FT_Pos
1687 ta_latin_compute_stem_width(TA_GlyphHints hints,
1688 TA_Dimension dim,
1689 FT_Pos width,
1690 FT_Byte base_flags,
1691 FT_Byte stem_flags)
1693 TA_LatinMetrics metrics = (TA_LatinMetrics) hints->metrics;
1694 TA_LatinAxis axis = &metrics->axis[dim];
1696 FT_Pos dist = width;
1697 FT_Int sign = 0;
1698 FT_Int vertical = (dim == TA_DIMENSION_VERT);
1701 if (!TA_LATIN_HINTS_DO_STEM_ADJUST(hints)
1702 || axis->extra_light)
1703 return width;
1705 if (dist < 0)
1707 dist = -width;
1708 sign = 1;
1711 if ((vertical && !TA_LATIN_HINTS_DO_VERT_SNAP(hints))
1712 || (!vertical && !TA_LATIN_HINTS_DO_HORZ_SNAP(hints)))
1714 /* smooth hinting process: very lightly quantize the stem width */
1716 /* leave the widths of serifs alone */
1717 if ((stem_flags & TA_EDGE_SERIF)
1718 && vertical
1719 && (dist < 3 * 64))
1720 goto Done_Width;
1721 else if (base_flags & TA_EDGE_ROUND)
1723 if (dist < 80)
1724 dist = 64;
1726 else if (dist < 56)
1727 dist = 56;
1729 if (axis->width_count > 0)
1731 FT_Pos delta;
1734 /* compare to standard width */
1735 delta = dist - axis->widths[0].cur;
1737 if (delta < 0)
1738 delta = -delta;
1740 if (delta < 40)
1742 dist = axis->widths[0].cur;
1743 if (dist < 48)
1744 dist = 48;
1746 goto Done_Width;
1749 if (dist < 3 * 64)
1751 delta = dist & 63;
1752 dist &= -64;
1754 if (delta < 10)
1755 dist += delta;
1756 else if (delta < 32)
1757 dist += 10;
1758 else if (delta < 54)
1759 dist += 54;
1760 else
1761 dist += delta;
1763 else
1764 dist = (dist + 32) & ~63;
1767 else
1769 /* strong hinting process: snap the stem width to integer pixels */
1771 FT_Pos org_dist = dist;
1774 dist = ta_latin_snap_width(axis->widths, axis->width_count, dist);
1776 if (vertical)
1778 /* in the case of vertical hinting, */
1779 /* always round the stem heights to integer pixels */
1781 if (dist >= 64)
1782 dist = (dist + 16) & ~63;
1783 else
1784 dist = 64;
1786 else
1788 if (TA_LATIN_HINTS_DO_MONO(hints))
1790 /* monochrome horizontal hinting: */
1791 /* snap widths to integer pixels with a different threshold */
1793 if (dist < 64)
1794 dist = 64;
1795 else
1796 dist = (dist + 32) & ~63;
1798 else
1800 /* for horizontal anti-aliased hinting, we adopt a more subtle */
1801 /* approach: we strengthen small stems, round stems whose size */
1802 /* is between 1 and 2 pixels to an integer, otherwise nothing */
1804 if (dist < 48)
1805 dist = (dist + 64) >> 1;
1807 else if (dist < 128)
1809 /* we only round to an integer width if the corresponding */
1810 /* distortion is less than 1/4 pixel -- otherwise, this */
1811 /* makes everything worse since the diagonals, which are */
1812 /* not hinted, appear a lot bolder or thinner than the */
1813 /* vertical stems */
1815 FT_Pos delta;
1818 dist = (dist + 22) & ~63;
1819 delta = dist - org_dist;
1820 if (delta < 0)
1821 delta = -delta;
1823 if (delta >= 16)
1825 dist = org_dist;
1826 if (dist < 48)
1827 dist = (dist + 64) >> 1;
1830 else
1831 /* round otherwise to prevent color fringes in LCD mode */
1832 dist = (dist + 32) & ~63;
1837 Done_Width:
1838 if (sign)
1839 dist = -dist;
1841 return dist;
1845 /* align one stem edge relative to the previous stem edge */
1847 static void
1848 ta_latin_align_linked_edge(TA_GlyphHints hints,
1849 TA_Dimension dim,
1850 TA_Edge base_edge,
1851 TA_Edge stem_edge)
1853 FT_Pos dist = stem_edge->opos - base_edge->opos;
1855 FT_Pos fitted_width = ta_latin_compute_stem_width(
1856 hints, dim, dist,
1857 base_edge->flags,
1858 stem_edge->flags);
1861 stem_edge->pos = base_edge->pos + fitted_width;
1863 TA_LOG((" LINK: edge %d (opos=%.2f) linked to %.2f,"
1864 " dist was %.2f, now %.2f\n",
1865 stem_edge - hints->axis[dim].edges, stem_edge->opos / 64.0,
1866 stem_edge->pos / 64.0, dist / 64.0, fitted_width / 64.0));
1868 if (hints->recorder)
1869 hints->recorder(ta_link, hints, dim,
1870 base_edge, stem_edge, NULL, NULL, NULL);
1874 /* shift the coordinates of the `serif' edge by the same amount */
1875 /* as the corresponding `base' edge has been moved already */
1877 static void
1878 ta_latin_align_serif_edge(TA_GlyphHints hints,
1879 TA_Edge base,
1880 TA_Edge serif)
1882 FT_UNUSED(hints);
1884 serif->pos = base->pos + (serif->opos - base->opos);
1888 /* the main grid-fitting routine */
1890 void
1891 ta_latin_hint_edges(TA_GlyphHints hints,
1892 TA_Dimension dim)
1894 TA_AxisHints axis = &hints->axis[dim];
1896 TA_Edge edges = axis->edges;
1897 TA_Edge edge_limit = edges + axis->num_edges;
1898 FT_PtrDist n_edges;
1899 TA_Edge edge;
1901 TA_Edge anchor = NULL;
1902 FT_Int has_serifs = 0;
1904 #ifdef TA_DEBUG
1905 FT_UInt num_actions = 0;
1906 #endif
1908 TA_LOG(("%s edge hinting\n", dim == TA_DIMENSION_VERT ? "horizontal"
1909 : "vertical"));
1911 /* we begin by aligning all stems relative to the blue zone if needed -- */
1912 /* that's only for horizontal edges */
1914 if (dim == TA_DIMENSION_VERT
1915 && TA_HINTS_DO_BLUES(hints))
1917 for (edge = edges; edge < edge_limit; edge++)
1919 TA_Width blue;
1920 TA_Edge edge1, edge2; /* these edges form the stem to check */
1923 if (edge->flags & TA_EDGE_DONE)
1924 continue;
1926 blue = edge->blue_edge;
1927 edge1 = NULL;
1928 edge2 = edge->link;
1930 if (blue)
1931 edge1 = edge;
1933 /* flip edges if the other stem is aligned to a blue zone */
1934 else if (edge2 && edge2->blue_edge)
1936 blue = edge2->blue_edge;
1937 edge1 = edge2;
1938 edge2 = edge;
1941 if (!edge1)
1942 continue;
1944 #ifdef TA_DEBUG
1945 if (!anchor)
1946 TA_LOG((" BLUE_ANCHOR: edge %d (opos=%.2f) snapped to %.2f,"
1947 " was %.2f (anchor=edge %d)\n",
1948 edge1 - edges, edge1->opos / 64.0, blue->fit / 64.0,
1949 edge1->pos / 64.0, edge - edges));
1950 else
1951 TA_LOG((" BLUE: edge %d (opos=%.2f) snapped to %.2f, was %.2f\n",
1952 edge1 - edges, edge1->opos / 64.0, blue->fit / 64.0,
1953 edge1->pos / 64.0));
1955 num_actions++;
1956 #endif
1958 edge1->pos = blue->fit;
1959 edge1->flags |= TA_EDGE_DONE;
1961 if (hints->recorder)
1963 if (!anchor)
1964 hints->recorder(ta_blue_anchor, hints, dim,
1965 edge1, edge, NULL, NULL, NULL);
1966 else
1967 hints->recorder(ta_blue, hints, dim,
1968 edge1, NULL, NULL, NULL, NULL);
1971 if (edge2 && !edge2->blue_edge)
1973 ta_latin_align_linked_edge(hints, dim, edge1, edge2);
1974 edge2->flags |= TA_EDGE_DONE;
1976 #ifdef TA_DEBUG
1977 num_actions++;
1978 #endif
1981 if (!anchor)
1982 anchor = edge;
1986 /* now we align all other stem edges, */
1987 /* trying to maintain the relative order of stems in the glyph */
1988 for (edge = edges; edge < edge_limit; edge++)
1990 TA_Edge edge2;
1993 if (edge->flags & TA_EDGE_DONE)
1994 continue;
1996 /* skip all non-stem edges */
1997 edge2 = edge->link;
1998 if (!edge2)
2000 has_serifs++;
2001 continue;
2004 /* now align the stem */
2006 /* this should not happen, but it's better to be safe */
2007 if (edge2->blue_edge)
2009 TA_LOG((" ASSERTION FAILED for edge %d\n", edge2-edges));
2011 ta_latin_align_linked_edge(hints, dim, edge2, edge);
2012 edge->flags |= TA_EDGE_DONE;
2014 #ifdef TA_DEBUG
2015 num_actions++;
2016 #endif
2017 continue;
2020 if (!anchor)
2022 /* if we reach this if clause, no stem has been aligned yet */
2024 FT_Pos org_len, org_center, cur_len;
2025 FT_Pos cur_pos1, error1, error2, u_off, d_off;
2028 org_len = edge2->opos - edge->opos;
2029 cur_len = ta_latin_compute_stem_width(hints, dim, org_len,
2030 edge->flags, edge2->flags);
2032 /* some voodoo to specially round edges for small stem widths; */
2033 /* the idea is to align the center of a stem, */
2034 /* then shifting the stem edges to suitable positions */
2035 if (cur_len <= 64)
2037 /* width <= 1px */
2038 u_off = 32;
2039 d_off = 32;
2041 else
2043 /* 1px < width < 1.5px */
2044 u_off = 38;
2045 d_off = 26;
2048 if (cur_len < 96)
2050 org_center = edge->opos + (org_len >> 1);
2051 cur_pos1 = TA_PIX_ROUND(org_center);
2053 error1 = org_center - (cur_pos1 - u_off);
2054 if (error1 < 0)
2055 error1 = -error1;
2057 error2 = org_center - (cur_pos1 + d_off);
2058 if (error2 < 0)
2059 error2 = -error2;
2061 if (error1 < error2)
2062 cur_pos1 -= u_off;
2063 else
2064 cur_pos1 += d_off;
2066 edge->pos = cur_pos1 - cur_len / 2;
2067 edge2->pos = edge->pos + cur_len;
2069 else
2070 edge->pos = TA_PIX_ROUND(edge->opos);
2072 anchor = edge;
2073 edge->flags |= TA_EDGE_DONE;
2075 TA_LOG((" ANCHOR: edge %d (opos=%.2f) and %d (opos=%.2f)"
2076 " snapped to %.2f and %.2f\n",
2077 edge - edges, edge->opos / 64.0,
2078 edge2 - edges, edge2->opos / 64.0,
2079 edge->pos / 64.0, edge2->pos / 64.0));
2081 if (hints->recorder)
2082 hints->recorder(ta_anchor, hints, dim,
2083 edge, edge2, NULL, NULL, NULL);
2085 ta_latin_align_linked_edge(hints, dim, edge, edge2);
2087 #ifdef TA_DEBUG
2088 num_actions += 2;
2089 #endif
2091 else
2093 FT_Pos org_pos, org_len, org_center, cur_len;
2094 FT_Pos cur_pos1, cur_pos2, delta1, delta2;
2097 org_pos = anchor->pos + (edge->opos - anchor->opos);
2098 org_len = edge2->opos - edge->opos;
2099 org_center = org_pos + (org_len >> 1);
2101 cur_len = ta_latin_compute_stem_width(hints, dim, org_len,
2102 edge->flags, edge2->flags);
2104 if (edge2->flags & TA_EDGE_DONE)
2106 TA_LOG((" ADJUST: edge %d (pos=%.2f) moved to %.2f\n",
2107 edge - edges, edge->pos / 64.0,
2108 (edge2->pos - cur_len) / 64.0));
2110 edge->pos = edge2->pos - cur_len;
2112 if (hints->recorder)
2114 TA_Edge bound = NULL;
2117 if (edge > edges)
2118 bound = &edge[-1];
2120 hints->recorder(ta_adjust, hints, dim,
2121 edge, edge2, NULL, bound, NULL);
2125 else if (cur_len < 96)
2127 FT_Pos u_off, d_off;
2130 cur_pos1 = TA_PIX_ROUND(org_center);
2132 if (cur_len <= 64)
2134 u_off = 32;
2135 d_off = 32;
2137 else
2139 u_off = 38;
2140 d_off = 26;
2143 delta1 = org_center - (cur_pos1 - u_off);
2144 if (delta1 < 0)
2145 delta1 = -delta1;
2147 delta2 = org_center - (cur_pos1 + d_off);
2148 if (delta2 < 0)
2149 delta2 = -delta2;
2151 if (delta1 < delta2)
2152 cur_pos1 -= u_off;
2153 else
2154 cur_pos1 += d_off;
2156 edge->pos = cur_pos1 - cur_len / 2;
2157 edge2->pos = cur_pos1 + cur_len / 2;
2159 TA_LOG((" STEM: edge %d (opos=%.2f) linked to %d (opos=%.2f)"
2160 " snapped to %.2f and %.2f\n",
2161 edge - edges, edge->opos / 64.0,
2162 edge2 - edges, edge2->opos / 64.0,
2163 edge->pos / 64.0, edge2->pos / 64.0));
2165 if (hints->recorder)
2167 TA_Edge bound = NULL;
2170 if (edge > edges)
2171 bound = &edge[-1];
2173 hints->recorder(ta_stem, hints, dim,
2174 edge, edge2, NULL, bound, NULL);
2178 else
2180 org_pos = anchor->pos + (edge->opos - anchor->opos);
2181 org_len = edge2->opos - edge->opos;
2182 org_center = org_pos + (org_len >> 1);
2184 cur_len = ta_latin_compute_stem_width(hints, dim, org_len,
2185 edge->flags, edge2->flags);
2187 cur_pos1 = TA_PIX_ROUND(org_pos);
2188 delta1 = cur_pos1 + (cur_len >> 1) - org_center;
2189 if (delta1 < 0)
2190 delta1 = -delta1;
2192 cur_pos2 = TA_PIX_ROUND(org_pos + org_len) - cur_len;
2193 delta2 = cur_pos2 + (cur_len >> 1) - org_center;
2194 if (delta2 < 0)
2195 delta2 = -delta2;
2197 edge->pos = (delta1 < delta2) ? cur_pos1 : cur_pos2;
2198 edge2->pos = edge->pos + cur_len;
2200 TA_LOG((" STEM: edge %d (opos=%.2f) linked to %d (opos=%.2f)"
2201 " snapped to %.2f and %.2f\n",
2202 edge - edges, edge->opos / 64.0,
2203 edge2 - edges, edge2->opos / 64.0,
2204 edge->pos / 64.0, edge2->pos / 64.0));
2206 if (hints->recorder)
2208 TA_Edge bound = NULL;
2211 if (edge > edges)
2212 bound = &edge[-1];
2214 hints->recorder(ta_stem, hints, dim,
2215 edge, edge2, NULL, bound, NULL);
2219 #ifdef TA_DEBUG
2220 num_actions++;
2221 #endif
2223 edge->flags |= TA_EDGE_DONE;
2224 edge2->flags |= TA_EDGE_DONE;
2226 if (edge > edges
2227 && edge->pos < edge[-1].pos)
2229 #ifdef TA_DEBUG
2230 TA_LOG((" BOUND: edge %d (pos=%.2f) moved to %.2f\n",
2231 edge - edges, edge->pos / 64.0, edge[-1].pos / 64.0));
2233 num_actions++;
2234 #endif
2236 edge->pos = edge[-1].pos;
2238 if (hints->recorder)
2239 hints->recorder(ta_bound, hints, dim,
2240 edge, &edge[-1], NULL, NULL, NULL);
2245 /* make sure that lowercase m's maintain their symmetry */
2247 /* In general, lowercase m's have six vertical edges if they are sans */
2248 /* serif, or twelve if they are with serifs. This implementation is */
2249 /* based on that assumption, and seems to work very well with most */
2250 /* faces. However, if for a certain face this assumption is not */
2251 /* true, the m is just rendered like before. In addition, any stem */
2252 /* correction will only be applied to symmetrical glyphs (even if the */
2253 /* glyph is not an m), so the potential for unwanted distortion is */
2254 /* relatively low. */
2256 /* we don't handle horizontal edges since we can't easily assure that */
2257 /* the third (lowest) stem aligns with the base line; it might end up */
2258 /* one pixel higher or lower */
2260 n_edges = edge_limit - edges;
2261 if (dim == TA_DIMENSION_HORZ
2262 && (n_edges == 6 || n_edges == 12))
2264 TA_Edge edge1, edge2, edge3;
2265 FT_Pos dist1, dist2, span, delta;
2268 if (n_edges == 6)
2270 edge1 = edges;
2271 edge2 = edges + 2;
2272 edge3 = edges + 4;
2274 else
2276 edge1 = edges + 1;
2277 edge2 = edges + 5;
2278 edge3 = edges + 9;
2281 dist1 = edge2->opos - edge1->opos;
2282 dist2 = edge3->opos - edge2->opos;
2284 span = dist1 - dist2;
2285 if (span < 0)
2286 span = -span;
2288 if (span < 8)
2290 delta = edge3->pos - (2 * edge2->pos - edge1->pos);
2291 edge3->pos -= delta;
2292 if (edge3->link)
2293 edge3->link->pos -= delta;
2295 /* move the serifs along with the stem */
2296 if (n_edges == 12)
2298 (edges + 8)->pos -= delta;
2299 (edges + 11)->pos -= delta;
2302 edge3->flags |= TA_EDGE_DONE;
2303 if (edge3->link)
2304 edge3->link->flags |= TA_EDGE_DONE;
2308 if (has_serifs || !anchor)
2310 /* now hint the remaining edges (serifs and single) */
2311 /* in order to complete our processing */
2312 for (edge = edges; edge < edge_limit; edge++)
2314 TA_Edge lower_bound = NULL;
2315 TA_Edge upper_bound = NULL;
2317 FT_Pos delta;
2320 if (edge->flags & TA_EDGE_DONE)
2321 continue;
2323 delta = 1000;
2325 if (edge->serif)
2327 delta = edge->serif->opos - edge->opos;
2328 if (delta < 0)
2329 delta = -delta;
2332 if (edge > edges)
2333 lower_bound = &edge[-1];
2335 if (edge + 1 < edge_limit
2336 && edge[1].flags & TA_EDGE_DONE)
2337 upper_bound = &edge[1];
2340 if (delta < 64 + 16)
2342 ta_latin_align_serif_edge(hints, edge->serif, edge);
2344 TA_LOG((" SERIF: edge %d (opos=%.2f) serif to %d (opos=%.2f)"
2345 " aligned to %.2f\n",
2346 edge - edges, edge->opos / 64.0,
2347 edge->serif - edges, edge->serif->opos / 64.0,
2348 edge->pos / 64.0));
2350 if (hints->recorder)
2351 hints->recorder(ta_serif, hints, dim,
2352 edge, NULL, NULL, lower_bound, upper_bound);
2354 else if (!anchor)
2356 edge->pos = TA_PIX_ROUND(edge->opos);
2357 anchor = edge;
2359 TA_LOG((" SERIF_ANCHOR: edge %d (opos=%.2f) snapped to %.2f\n",
2360 edge - edges, edge->opos / 64.0, edge->pos / 64.0));
2362 if (hints->recorder)
2363 hints->recorder(ta_serif_anchor, hints, dim,
2364 edge, NULL, NULL, lower_bound, upper_bound);
2366 else
2368 TA_Edge before, after;
2371 for (before = edge - 1; before >= edges; before--)
2372 if (before->flags & TA_EDGE_DONE)
2373 break;
2375 for (after = edge + 1; after < edge_limit; after++)
2376 if (after->flags & TA_EDGE_DONE)
2377 break;
2379 if (before >= edges && before < edge
2380 && after < edge_limit && after > edge)
2382 if (after->opos == before->opos)
2383 edge->pos = before->pos;
2384 else
2385 edge->pos = before->pos + FT_MulDiv(edge->opos - before->opos,
2386 after->pos - before->pos,
2387 after->opos - before->opos);
2389 TA_LOG((" SERIF_LINK1: edge %d (opos=%.2f) snapped to %.2f"
2390 " from %d (opos=%.2f)\n",
2391 edge - edges, edge->opos / 64.0,
2392 edge->pos / 64.0,
2393 before - edges, before->opos / 64.0));
2395 if (hints->recorder)
2396 hints->recorder(ta_serif_link1, hints, dim,
2397 edge, before, after, lower_bound, upper_bound);
2399 else
2401 edge->pos = anchor->pos + ((edge->opos - anchor->opos + 16) & ~31);
2402 TA_LOG((" SERIF_LINK2: edge %d (opos=%.2f) snapped to %.2f\n",
2403 edge - edges, edge->opos / 64.0, edge->pos / 64.0));
2405 if (hints->recorder)
2406 hints->recorder(ta_serif_link2, hints, dim,
2407 edge, NULL, NULL, lower_bound, upper_bound);
2411 #ifdef TA_DEBUG
2412 num_actions++;
2413 #endif
2414 edge->flags |= TA_EDGE_DONE;
2416 if (edge > edges
2417 && edge->pos < edge[-1].pos)
2419 #ifdef TA_DEBUG
2420 TA_LOG((" BOUND: edge %d (pos=%.2f) moved to %.2f\n",
2421 edge - edges, edge->pos / 64.0, edge[-1].pos / 64.0));
2422 num_actions++;
2423 #endif
2425 edge->pos = edge[-1].pos;
2427 if (hints->recorder)
2428 hints->recorder(ta_bound, hints, dim,
2429 edge, &edge[-1], NULL, NULL, NULL);
2432 if (edge + 1 < edge_limit
2433 && edge[1].flags & TA_EDGE_DONE
2434 && edge->pos > edge[1].pos)
2436 #ifdef TA_DEBUG
2437 TA_LOG((" BOUND: edge %d (pos=%.2f) moved to %.2f\n",
2438 edge - edges, edge->pos / 64.0, edge[1].pos / 64.0));
2440 num_actions++;
2441 #endif
2443 edge->pos = edge[1].pos;
2445 if (hints->recorder)
2446 hints->recorder(ta_bound, hints, dim,
2447 edge, &edge[1], NULL, NULL, NULL);
2452 #ifdef TA_DEBUG
2453 if (!num_actions)
2454 TA_LOG((" (none)\n"));
2455 TA_LOG(("\n"));
2456 #endif
2460 /* apply the complete hinting algorithm to a latin glyph */
2462 static FT_Error
2463 ta_latin_hints_apply(TA_GlyphHints hints,
2464 FT_Outline* outline,
2465 TA_LatinMetrics metrics)
2467 FT_Error error;
2468 int dim;
2471 error = ta_glyph_hints_reload(hints, outline);
2472 if (error)
2473 goto Exit;
2475 /* analyze glyph outline */
2476 #ifdef TA_CONFIG_OPTION_USE_WARPER
2477 if (metrics->root.scaler.render_mode == FT_RENDER_MODE_LIGHT
2478 || TA_HINTS_DO_HORIZONTAL(hints))
2479 #else
2480 if (TA_HINTS_DO_HORIZONTAL(hints))
2481 #endif
2483 error = ta_latin_hints_detect_features(hints, TA_DIMENSION_HORZ);
2484 if (error)
2485 goto Exit;
2488 if (TA_HINTS_DO_VERTICAL(hints))
2490 error = ta_latin_hints_detect_features(hints, TA_DIMENSION_VERT);
2491 if (error)
2492 goto Exit;
2494 ta_latin_hints_compute_blue_edges(hints, metrics);
2497 /* grid-fit the outline */
2498 for (dim = 0; dim < TA_DIMENSION_MAX; dim++)
2500 #ifdef TA_CONFIG_OPTION_USE_WARPER
2501 if (dim == TA_DIMENSION_HORZ
2502 && metrics->root.scaler.render_mode == FT_RENDER_MODE_LIGHT)
2504 TA_WarperRec warper;
2505 FT_Fixed scale;
2506 FT_Pos delta;
2509 ta_warper_compute(&warper, hints, (TA_Dimension)dim, &scale, &delta);
2510 ta_glyph_hints_scale_dim(hints, (TA_Dimension)dim, scale, delta);
2512 continue;
2514 #endif
2516 if ((dim == TA_DIMENSION_HORZ && TA_HINTS_DO_HORIZONTAL(hints))
2517 || (dim == TA_DIMENSION_VERT && TA_HINTS_DO_VERTICAL(hints)))
2519 ta_latin_hint_edges(hints, (TA_Dimension)dim);
2520 ta_glyph_hints_align_edge_points(hints, (TA_Dimension)dim);
2521 ta_glyph_hints_align_strong_points(hints, (TA_Dimension)dim);
2522 ta_glyph_hints_align_weak_points(hints, (TA_Dimension)dim);
2525 ta_glyph_hints_save(hints, outline);
2527 Exit:
2528 return error;
2532 /* XXX: this should probably fine tuned to differentiate better between */
2533 /* scripts... */
2535 static const TA_Script_UniRangeRec ta_latin_uniranges[] =
2537 TA_UNIRANGE_REC(0x0020UL, 0x007FUL), /* Basic Latin (no control chars) */
2538 TA_UNIRANGE_REC(0x00A0UL, 0x00FFUL), /* Latin-1 Supplement (no control chars) */
2539 TA_UNIRANGE_REC(0x0100UL, 0x017FUL), /* Latin Extended-A */
2540 TA_UNIRANGE_REC(0x0180UL, 0x024FUL), /* Latin Extended-B */
2541 TA_UNIRANGE_REC(0x0250UL, 0x02AFUL), /* IPA Extensions */
2542 TA_UNIRANGE_REC(0x02B0UL, 0x02FFUL), /* Spacing Modifier Letters */
2543 TA_UNIRANGE_REC(0x0300UL, 0x036FUL), /* Combining Diacritical Marks */
2544 TA_UNIRANGE_REC(0x0370UL, 0x03FFUL), /* Greek and Coptic */
2545 TA_UNIRANGE_REC(0x0400UL, 0x04FFUL), /* Cyrillic */
2546 TA_UNIRANGE_REC(0x0500UL, 0x052FUL), /* Cyrillic Supplement */
2547 TA_UNIRANGE_REC(0x1D00UL, 0x1D7FUL), /* Phonetic Extensions */
2548 TA_UNIRANGE_REC(0x1D80UL, 0x1DBFUL), /* Phonetic Extensions Supplement */
2549 TA_UNIRANGE_REC(0x1DC0UL, 0x1DFFUL), /* Combining Diacritical Marks Supplement */
2550 TA_UNIRANGE_REC(0x1E00UL, 0x1EFFUL), /* Latin Extended Additional */
2551 TA_UNIRANGE_REC(0x1F00UL, 0x1FFFUL), /* Greek Extended */
2552 TA_UNIRANGE_REC(0x2000UL, 0x206FUL), /* General Punctuation */
2553 TA_UNIRANGE_REC(0x2070UL, 0x209FUL), /* Superscripts and Subscripts */
2554 TA_UNIRANGE_REC(0x20A0UL, 0x20CFUL), /* Currency Symbols */
2555 TA_UNIRANGE_REC(0x2150UL, 0x218FUL), /* Number Forms */
2556 TA_UNIRANGE_REC(0x2460UL, 0x24FFUL), /* Enclosed Alphanumerics */
2557 TA_UNIRANGE_REC(0x2C60UL, 0x2C7FUL), /* Latin Extended-C */
2558 TA_UNIRANGE_REC(0x2DE0UL, 0x2DFFUL), /* Cyrillic Extended-A */
2559 TA_UNIRANGE_REC(0x2E00UL, 0x2E7FUL), /* Supplemental Punctuation */
2560 TA_UNIRANGE_REC(0xA640UL, 0xA69FUL), /* Cyrillic Extended-B */
2561 TA_UNIRANGE_REC(0xA720UL, 0xA7FFUL), /* Latin Extended-D */
2562 TA_UNIRANGE_REC(0xFB00UL, 0xFB06UL), /* Alphab. Present. Forms (Latin Ligs) */
2563 TA_UNIRANGE_REC(0x1D400UL, 0x1D7FFUL), /* Mathematical Alphanumeric Symbols */
2564 TA_UNIRANGE_REC(0x1F100UL, 0x1F1FFUL), /* Enclosed Alphanumeric Supplement */
2565 TA_UNIRANGE_REC(0UL, 0UL)
2569 const TA_ScriptClassRec ta_latin_script_class =
2571 TA_SCRIPT_LATIN,
2572 ta_latin_uniranges,
2573 'o',
2575 sizeof (TA_LatinMetricsRec),
2577 (TA_Script_InitMetricsFunc)ta_latin_metrics_init,
2578 (TA_Script_ScaleMetricsFunc)ta_latin_metrics_scale,
2579 (TA_Script_DoneMetricsFunc)NULL,
2581 (TA_Script_InitHintsFunc)ta_latin_hints_init,
2582 (TA_Script_ApplyHintsFunc)ta_latin_hints_apply
2585 /* end of talatin.c */