Synchronize with FreeType.
[ttfautohint.git] / lib / talatin.c
blob2e2b00f43529cdcd02b16126d8e732c6f65ac309
1 /* talatin.c */
3 /*
4 * Copyright (C) 2011-2016 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 /* needed for computation of round vs. flat segments */
39 #define FLAT_THRESHOLD(x) (x / 14)
42 /* find segments and links, compute all stem widths, and initialize */
43 /* standard width and height for the glyph with given charcode */
45 void
46 ta_latin_metrics_init_widths(TA_LatinMetrics metrics,
47 FT_Face face,
48 FT_Bool use_cmap)
50 /* scan the array of segments in each direction */
51 TA_GlyphHintsRec hints[1];
54 TA_LOG_GLOBAL(("\n"
55 "latin standard widths computation (style `%s')\n"
56 "=====================================================\n"
57 "\n",
58 ta_style_names[metrics->root.style_class->style]));
60 ta_glyph_hints_init(hints);
62 metrics->axis[TA_DIMENSION_HORZ].width_count = 0;
63 metrics->axis[TA_DIMENSION_VERT].width_count = 0;
66 FT_Error error;
67 FT_ULong glyph_index;
68 int dim;
69 TA_LatinMetricsRec dummy[1];
70 TA_Scaler scaler = &dummy->root.scaler;
72 TA_StyleClass style_class = metrics->root.style_class;
73 TA_ScriptClass script_class = ta_script_classes[style_class->script];
75 void* shaper_buf;
76 const char* p;
78 #ifdef TA_DEBUG
79 FT_ULong ch = 0;
80 #endif
83 if (!use_cmap)
84 goto Exit;
86 p = script_class->standard_charstring;
87 shaper_buf = ta_shaper_buf_create(face);
90 * We check a list of standard characters to catch features like
91 * `c2sc' (small caps from caps) that don't contain lowercase letters
92 * by definition, or other features that mainly operate on numerals.
93 * The first match wins.
96 glyph_index = 0;
97 while (*p)
99 unsigned int num_idx;
101 #ifdef TA_DEBUG
102 const char* p_old;
103 #endif
106 while (*p == ' ')
107 p++;
109 #ifdef TA_DEBUG
110 p_old = p;
111 GET_UTF8_CHAR(ch, p_old);
112 #endif
114 /* reject input that maps to more than a single glyph */
115 p = ta_shaper_get_cluster(p, &metrics->root, shaper_buf, &num_idx);
116 if (num_idx > 1)
117 continue;
119 /* otherwise exit loop if we have a result */
120 glyph_index = ta_shaper_get_elem(&metrics->root,
121 shaper_buf,
123 NULL,
124 NULL);
125 if (glyph_index)
126 break;
129 ta_shaper_buf_destroy(face, shaper_buf);
131 if (!glyph_index)
132 goto Exit;
134 TA_LOG_GLOBAL(("standard character: U+%04lX (glyph index %d)\n",
135 ch, glyph_index));
137 error = FT_Load_Glyph(face, glyph_index, FT_LOAD_NO_SCALE);
138 if (error || face->glyph->outline.n_points <= 0)
139 goto Exit;
141 memset(dummy, 0, sizeof (TA_LatinMetricsRec));
143 dummy->units_per_em = metrics->units_per_em;
145 scaler->x_scale = 0x10000L;
146 scaler->y_scale = 0x10000L;
147 scaler->x_delta = 0;
148 scaler->y_delta = 0;
150 scaler->face = face;
151 scaler->render_mode = FT_RENDER_MODE_NORMAL;
152 scaler->flags = 0;
154 ta_glyph_hints_rescale(hints, (TA_StyleMetrics)dummy);
156 error = ta_glyph_hints_reload(hints, &face->glyph->outline);
157 if (error)
158 goto Exit;
160 for (dim = 0; dim < TA_DIMENSION_MAX; dim++)
162 TA_LatinAxis axis = &metrics->axis[dim];
163 TA_AxisHints axhints = &hints->axis[dim];
165 TA_Segment seg, limit, link;
166 FT_UInt num_widths = 0;
169 error = ta_latin_hints_compute_segments(hints, (TA_Dimension)dim);
170 if (error)
171 goto Exit;
174 * We assume that the glyphs selected for the stem width
175 * computation are `featureless' enough so that the linking
176 * algorithm works fine without adjustments of its scoring
177 * function.
179 ta_latin_hints_link_segments(hints, 0, NULL, (TA_Dimension)dim);
181 seg = axhints->segments;
182 limit = seg + axhints->num_segments;
184 for (; seg < limit; seg++)
186 link = seg->link;
188 /* we only consider stem segments there! */
189 if (link
190 && link->link == seg
191 && link > seg)
193 FT_Pos dist;
196 dist = seg->pos - link->pos;
197 if (dist < 0)
198 dist = -dist;
200 if (num_widths < TA_LATIN_MAX_WIDTHS)
201 axis->widths[num_widths++].org = dist;
205 /* this also replaces multiple almost identical stem widths */
206 /* with a single one (the value 100 is heuristic) */
207 ta_sort_and_quantize_widths(&num_widths, axis->widths,
208 dummy->units_per_em / 100);
209 axis->width_count = num_widths;
212 Exit:
213 for (dim = 0; dim < TA_DIMENSION_MAX; dim++)
215 FONT* font = metrics->root.globals->font;
216 TA_LatinAxis axis = &metrics->axis[dim];
217 FT_Pos stdw;
220 if (!axis->width_count)
222 /* if we have no standard characters, */
223 /* use `fallback-stem-width', if available, */
224 /* or a default width (value 50 is heuristic) */
225 stdw = (dim == TA_DIMENSION_VERT && font->fallback_stem_width)
226 ? (FT_Pos)font->fallback_stem_width
227 : TA_LATIN_CONSTANT(metrics, 50);
229 axis->width_count++;
230 axis->widths[0].org = stdw;
233 stdw = axis->widths[0].org;
235 /* let's try 20% of the smallest width */
236 axis->edge_distance_threshold = stdw / 5;
237 axis->standard_width = stdw;
238 axis->extra_light = 0;
240 #ifdef TA_DEBUG
242 FT_UInt i;
245 TA_LOG_GLOBAL(("%s widths:\n",
246 dim == TA_DIMENSION_VERT ? "horizontal"
247 : "vertical"));
249 TA_LOG_GLOBAL((" %d (standard)", axis->standard_width));
250 for (i = 1; i < axis->width_count; i++)
251 TA_LOG_GLOBAL((" %d", axis->widths[i].org));
253 TA_LOG_GLOBAL(("\n"));
255 #endif
259 TA_LOG_GLOBAL(("\n"));
261 ta_glyph_hints_done(hints);
265 /* find all blue zones; flat segments give the reference points, */
266 /* round segments the overshoot positions */
268 static void
269 ta_latin_metrics_init_blues(TA_LatinMetrics metrics,
270 FT_Face face)
272 FT_Pos flats[TA_BLUE_STRING_MAX_LEN];
273 FT_Pos rounds[TA_BLUE_STRING_MAX_LEN];
274 FT_UInt num_flats;
275 FT_UInt num_rounds;
277 TA_LatinBlue blue;
278 FT_Error error;
279 TA_LatinAxis axis = &metrics->axis[TA_DIMENSION_VERT];
280 FT_Outline outline;
282 TA_StyleClass sc = metrics->root.style_class;
284 TA_Blue_Stringset bss = sc->blue_stringset;
285 const TA_Blue_StringRec* bs = &ta_blue_stringsets[bss];
287 FT_Pos flat_threshold = FLAT_THRESHOLD(metrics->units_per_em);
289 void* shaper_buf;
292 /* we walk over the blue character strings as specified in the */
293 /* style's entry in the `ta_blue_stringset' array */
295 TA_LOG_GLOBAL(("latin blue zones computation\n"
296 "============================\n"
297 "\n"));
299 shaper_buf = ta_shaper_buf_create(face);
301 for (; bs->string != TA_BLUE_STRING_MAX; bs++)
303 const char* p = &ta_blue_strings[bs->string];
304 FT_Pos* blue_ref;
305 FT_Pos* blue_shoot;
306 FT_Pos ascender;
307 FT_Pos descender;
310 #ifdef TA_DEBUG
312 FT_Bool have_flag = 0;
315 TA_LOG_GLOBAL(("blue zone %d", axis->blue_count));
317 if (bs->properties)
319 TA_LOG_GLOBAL((" ("));
321 if (TA_LATIN_IS_TOP_BLUE(bs))
323 TA_LOG_GLOBAL(("top"));
324 have_flag = 1;
326 else if (TA_LATIN_IS_SUB_TOP_BLUE(bs))
328 TA_LOG_GLOBAL(("sub top"));
329 have_flag = 1;
332 if (TA_LATIN_IS_NEUTRAL_BLUE(bs))
334 if (have_flag)
335 TA_LOG_GLOBAL((", "));
336 TA_LOG_GLOBAL(("neutral"));
337 have_flag = 1;
340 if (TA_LATIN_IS_X_HEIGHT_BLUE(bs))
342 if (have_flag)
343 TA_LOG_GLOBAL((", "));
344 TA_LOG_GLOBAL(("small top"));
345 have_flag = 1;
348 if (TA_LATIN_IS_LONG_BLUE(bs))
350 if (have_flag)
351 TA_LOG_GLOBAL((", "));
352 TA_LOG_GLOBAL(("long"));
355 TA_LOG_GLOBAL((")"));
358 TA_LOG_GLOBAL((":\n"));
360 #endif /* TA_DEBUG */
362 num_flats = 0;
363 num_rounds = 0;
364 ascender = 0;
365 descender = 0;
367 while (*p)
369 FT_ULong glyph_index;
370 FT_Long y_offset;
371 FT_Int best_point, best_contour_first, best_contour_last;
372 FT_Vector* points;
374 FT_Pos best_y_extremum; /* same as points.y */
375 FT_Bool best_round = 0;
377 unsigned int i, num_idx;
379 #ifdef TA_DEBUG
380 const char* p_old;
381 FT_ULong ch;
382 #endif
385 while (*p == ' ')
386 p++;
388 #ifdef TA_DEBUG
389 p_old = p;
390 GET_UTF8_CHAR(ch, p_old);
391 #endif
393 p = ta_shaper_get_cluster(p, &metrics->root, shaper_buf, &num_idx);
395 if (!num_idx)
397 TA_LOG_GLOBAL((" U+%04lX unavailable\n", ch));
398 continue;
401 if (TA_LATIN_IS_TOP_BLUE(bs))
402 best_y_extremum = FT_INT_MIN;
403 else
404 best_y_extremum = FT_INT_MAX;
406 /* iterate over all glyph elements of the character cluster */
407 /* and get the data of the `biggest' one */
408 for (i = 0; i < num_idx; i++)
410 FT_Pos best_y;
411 FT_Bool round = 0;
414 /* load the character in the face -- skip unknown or empty ones */
415 glyph_index = ta_shaper_get_elem(&metrics->root,
416 shaper_buf,
418 NULL,
419 &y_offset);
420 if (glyph_index == 0)
422 TA_LOG_GLOBAL((" U+%04lX unavailable\n", ch));
423 continue;
426 error = FT_Load_Glyph(face, glyph_index, FT_LOAD_NO_SCALE);
427 outline = face->glyph->outline;
428 /* reject glyphs that don't produce any rendering */
429 if (error || outline.n_points <= 2)
431 #ifdef TA_DEBUG
432 if (num_idx == 1)
433 TA_LOG_GLOBAL((" U+%04lX contains no (usable) outlines\n", ch));
434 else
435 TA_LOG_GLOBAL((" component %d of cluster starting with U+%04lX"
436 " contains no (usable) outlines\n", i, ch));
437 #endif
438 continue;
441 /* now compute min or max point indices and coordinates */
442 points = outline.points;
443 best_point = -1;
444 best_y = 0; /* make compiler happy */
445 best_contour_first = 0; /* ditto */
446 best_contour_last = 0; /* ditto */
449 FT_Int nn;
450 FT_Int first = 0;
451 FT_Int last = -1;
454 for (nn = 0; nn < outline.n_contours; first = last + 1, nn++)
456 FT_Int old_best_point = best_point;
457 FT_Int pp;
460 last = outline.contours[nn];
462 /* avoid single-point contours since they are never */
463 /* rasterized; in some fonts, they correspond to mark */
464 /* attachment points that are way outside of the glyph's */
465 /* real outline */
466 if (last <= first)
467 continue;
469 if (TA_LATIN_IS_TOP_BLUE(bs)
470 || TA_LATIN_IS_SUB_TOP_BLUE(bs))
472 for (pp = first; pp <= last; pp++)
474 if (best_point < 0
475 || points[pp].y > best_y)
477 best_point = pp;
478 best_y = points[pp].y;
479 ascender = TA_MAX(ascender, best_y + y_offset);
481 else
482 descender = TA_MIN(descender, points[pp].y + y_offset);
485 else
487 for (pp = first; pp <= last; pp++)
489 if (best_point < 0
490 || points[pp].y < best_y)
492 best_point = pp;
493 best_y = points[pp].y;
494 descender = TA_MIN(descender, best_y + y_offset);
496 else
497 ascender = TA_MAX(ascender, points[pp].y + y_offset);
501 if (best_point != old_best_point)
503 best_contour_first = first;
504 best_contour_last = last;
509 /* now check whether the point belongs to a straight or round */
510 /* segment; we first need to find in which contour the extremum */
511 /* lies, then inspect its previous and next points */
512 if (best_point >= 0)
514 FT_Pos best_x = points[best_point].x;
515 FT_Int prev, next;
516 FT_Int best_segment_first, best_segment_last;
517 FT_Int best_on_point_first, best_on_point_last;
518 FT_Pos dist;
521 best_segment_first = best_point;
522 best_segment_last = best_point;
524 if (FT_CURVE_TAG(outline.tags[best_point]) == FT_CURVE_TAG_ON)
526 best_on_point_first = best_point;
527 best_on_point_last = best_point;
529 else
531 best_on_point_first = -1;
532 best_on_point_last = -1;
535 /* look for the previous and next points on the contour */
536 /* that are not on the same Y coordinate, then threshold */
537 /* the `closeness'... */
538 prev = best_point;
539 next = prev;
543 if (prev > best_contour_first)
544 prev--;
545 else
546 prev = best_contour_last;
548 dist = TA_ABS(points[prev].y - best_y);
549 /* accept a small distance or a small angle (both values are */
550 /* heuristic; value 20 corresponds to approx. 2.9 degrees) */
551 if (dist > 5)
552 if (TA_ABS(points[prev].x - best_x) <= 20 * dist)
553 break;
555 best_segment_first = prev;
557 if (FT_CURVE_TAG(outline.tags[prev]) == FT_CURVE_TAG_ON)
559 best_on_point_first = prev;
560 if (best_on_point_last < 0)
561 best_on_point_last = prev;
564 } while (prev != best_point);
568 if (next < best_contour_last)
569 next++;
570 else
571 next = best_contour_first;
573 dist = TA_ABS(points[next].y - best_y);
574 if (dist > 5)
575 if (TA_ABS(points[next].x - best_x) <= 20 * dist)
576 break;
578 best_segment_last = next;
580 if (FT_CURVE_TAG(outline.tags[next]) == FT_CURVE_TAG_ON)
582 best_on_point_last = next;
583 if (best_on_point_first < 0)
584 best_on_point_first = next;
587 } while (next != best_point);
589 if (TA_LATIN_IS_LONG_BLUE(bs))
591 /* If this flag is set, we have an additional constraint to */
592 /* get the blue zone distance: Find a segment of the topmost */
593 /* (or bottommost) contour that is longer than a heuristic */
594 /* threshold. This ensures that small bumps in the outline */
595 /* are ignored (for example, the `vertical serifs' found in */
596 /* many Hebrew glyph designs). */
598 /* If this segment is long enough, we are done. Otherwise, */
599 /* search the segment next to the extremum that is long */
600 /* enough, has the same direction, and a not too large */
601 /* vertical distance from the extremum. Note that the */
602 /* algorithm doesn't check whether the found segment is */
603 /* actually the one (vertically) nearest to the extremum. */
605 /* heuristic threshold value */
606 FT_Pos length_threshold = metrics->units_per_em / 25;
609 dist = TA_ABS(points[best_segment_last].x -
610 points[best_segment_first].x);
612 if (dist < length_threshold
613 && best_segment_last - best_segment_first + 2 <=
614 best_contour_last - best_contour_first)
616 /* heuristic threshold value */
617 FT_Pos height_threshold = metrics->units_per_em / 4;
619 FT_Int first;
620 FT_Int last;
621 FT_Bool hit;
623 /* we intentionally declare these two variables */
624 /* outside of the loop since various compilers emit */
625 /* incorrect warning messages otherwise, talking about */
626 /* `possibly uninitialized variables' */
627 FT_Int p_first = 0; /* make compiler happy */
628 FT_Int p_last = 0;
630 FT_Bool left2right;
633 /* compute direction */
634 prev = best_point;
638 if (prev > best_contour_first)
639 prev--;
640 else
641 prev = best_contour_last;
643 if (points[prev].x != best_x)
644 break;
645 } while (prev != best_point);
647 /* skip glyph for the degenerate case */
648 if (prev == best_point)
649 continue;
651 left2right = FT_BOOL(points[prev].x < points[best_point].x);
653 first = best_segment_last;
654 last = first;
655 hit = 0;
659 FT_Bool l2r;
660 FT_Pos d;
663 if (!hit)
665 /* no hit; adjust first point */
666 first = last;
668 /* also adjust first and last on point */
669 if (FT_CURVE_TAG(outline.tags[first]) == FT_CURVE_TAG_ON)
671 p_first = first;
672 p_last = first;
674 else
676 p_first = -1;
677 p_last = -1;
680 hit = 1;
683 if (last < best_contour_last)
684 last++;
685 else
686 last = best_contour_first;
688 if (TA_ABS(best_y - points[first].y) > height_threshold)
690 /* vertical distance too large */
691 hit = 0;
692 continue;
695 /* same test as above */
696 dist = TA_ABS(points[last].y - points[first].y);
697 if (dist > 5)
698 if (TA_ABS(points[last].x - points[first].x) <= 20 * dist)
700 hit = 0;
701 continue;
704 if (FT_CURVE_TAG(outline.tags[last]) == FT_CURVE_TAG_ON)
706 p_last = last;
707 if (p_first < 0)
708 p_first = last;
711 l2r = FT_BOOL(points[first].x < points[last].x);
712 d = TA_ABS(points[last].x - points[first].x);
714 if (l2r == left2right
715 && d >= length_threshold)
717 /* all constraints are met; update segment after */
718 /* finding its end */
721 if (last < best_contour_last)
722 last++;
723 else
724 last = best_contour_first;
726 d = TA_ABS(points[last].y - points[first].y);
727 if (d > 5)
728 if (TA_ABS(points[next].x - points[first].x) <=
729 20 * dist)
731 if (last > best_contour_first)
732 last--;
733 else
734 last = best_contour_last;
735 break;
738 p_last = last;
740 if (FT_CURVE_TAG(outline.tags[last]) == FT_CURVE_TAG_ON)
742 p_last = last;
743 if (p_first < 0)
744 p_first = last;
746 } while (last != best_segment_first);
748 best_y = points[first].y;
750 best_segment_first = first;
751 best_segment_last = last;
753 best_on_point_first = p_first;
754 best_on_point_last = p_last;
756 break;
758 } while (last != best_segment_first);
763 * for computing blue zones, we add the y offset as returned
764 * by the currently used OpenType feature --
765 * for example, superscript glyphs might be identical
766 * to subscript glyphs with a vertical shift
768 best_y += y_offset;
770 #ifdef TA_DEBUG
771 if (num_idx == 1)
772 TA_LOG_GLOBAL((" U+%04lX: best_y = %5ld", ch, best_y));
773 else
774 TA_LOG_GLOBAL((" component %d of cluster starting with U+%04lX:"
775 " best_y = %5ld", i, ch, best_y));
776 #endif
779 * now set the `round' flag depending on the segment's kind:
781 * - if the horizontal distance between the first and last
782 * `on' point is larger than a heuristic threshold
783 * we have a flat segment
784 * - if either the first or the last point of the segment is
785 * an `off' point, the segment is round, otherwise it is
786 * flat
788 if (best_on_point_first >= 0
789 && best_on_point_last >= 0
790 && (TA_ABS(points[best_on_point_last].x
791 - points[best_on_point_first].x))
792 > flat_threshold)
793 round = 0;
794 else
795 round = FT_BOOL(FT_CURVE_TAG(outline.tags[best_segment_first])
796 != FT_CURVE_TAG_ON
797 || FT_CURVE_TAG(outline.tags[best_segment_last])
798 != FT_CURVE_TAG_ON);
800 if (round && TA_LATIN_IS_NEUTRAL_BLUE(bs))
802 /* only use flat segments for a neutral blue zone */
803 TA_LOG_GLOBAL((" (round, skipped)\n"));
804 continue;
807 TA_LOG_GLOBAL((" (%s)\n", round ? "round" : "flat"));
810 if (TA_LATIN_IS_TOP_BLUE(bs))
812 if (best_y > best_y_extremum)
814 best_y_extremum = best_y;
815 best_round = round;
818 else
820 if (best_y < best_y_extremum)
822 best_y_extremum = best_y;
823 best_round = round;
827 } /* end for loop */
829 if (!(best_y_extremum == FT_INT_MIN
830 || best_y_extremum == FT_INT_MAX))
832 if (best_round)
833 rounds[num_rounds++] = best_y_extremum;
834 else
835 flats[num_flats++] = best_y_extremum;
838 } /* end while loop */
840 if (num_flats == 0 && num_rounds == 0)
842 /* we couldn't find a single glyph to compute this blue zone, */
843 /* we will simply ignore it then */
844 TA_LOG_GLOBAL((" empty\n"));
845 continue;
848 /* we have computed the contents of the `rounds' and `flats' tables, */
849 /* now determine the reference and overshoot position of the blue -- */
850 /* we simply take the median value after a simple sort */
851 ta_sort_pos(num_rounds, rounds);
852 ta_sort_pos(num_flats, flats);
854 blue = &axis->blues[axis->blue_count];
855 blue_ref = &blue->ref.org;
856 blue_shoot = &blue->shoot.org;
858 axis->blue_count++;
860 if (num_flats == 0)
862 *blue_ref =
863 *blue_shoot = rounds[num_rounds / 2];
865 else if (num_rounds == 0)
867 *blue_ref =
868 *blue_shoot = flats[num_flats / 2];
870 else
872 *blue_ref = flats[num_flats / 2];
873 *blue_shoot = rounds[num_rounds / 2];
876 /* there are sometimes problems if the overshoot position of top */
877 /* zones is under its reference position, or the opposite for bottom */
878 /* zones; we must thus check everything there and correct the errors */
879 if (*blue_shoot != *blue_ref)
881 FT_Pos ref = *blue_ref;
882 FT_Pos shoot = *blue_shoot;
883 FT_Bool over_ref = FT_BOOL(shoot > ref);
886 if ((TA_LATIN_IS_TOP_BLUE(bs)
887 || TA_LATIN_IS_SUB_TOP_BLUE(bs)) ^ over_ref)
889 *blue_ref =
890 *blue_shoot = (shoot + ref) / 2;
892 TA_LOG_GLOBAL((" [overshoot smaller than reference,"
893 " taking mean value]\n"));
897 blue->ascender = ascender;
898 blue->descender = descender;
900 blue->flags = 0;
901 if (TA_LATIN_IS_TOP_BLUE(bs))
902 blue->flags |= TA_LATIN_BLUE_TOP;
903 if (TA_LATIN_IS_SUB_TOP_BLUE(bs))
904 blue->flags |= TA_LATIN_BLUE_SUB_TOP;
905 if (TA_LATIN_IS_NEUTRAL_BLUE(bs))
906 blue->flags |= TA_LATIN_BLUE_NEUTRAL;
908 /* the following flag is used later to adjust the y and x scales */
909 /* in order to optimize the pixel grid alignment */
910 /* of the top of small letters */
911 if (TA_LATIN_IS_X_HEIGHT_BLUE(bs))
912 blue->flags |= TA_LATIN_BLUE_ADJUSTMENT;
914 TA_LOG_GLOBAL((" -> reference = %ld\n"
915 " overshoot = %ld\n",
916 *blue_ref, *blue_shoot));
918 } /* end for loop */
920 ta_shaper_buf_destroy(face, shaper_buf);
922 /* if windows compatibility mode is activated, */
923 /* add two artificial blue zones for usWinAscent and usWinDescent */
924 /* just in case the above algorithm has missed them -- */
925 /* Windows cuts off everything outside of those two values */
926 if (metrics->root.globals->font->windows_compatibility)
928 TT_OS2* os2;
931 os2 = (TT_OS2*)FT_Get_Sfnt_Table(face, ft_sfnt_os2);
933 if (os2)
935 blue = &axis->blues[axis->blue_count];
936 blue->flags = TA_LATIN_BLUE_TOP | TA_LATIN_BLUE_ACTIVE;
937 blue->ref.org =
938 blue->shoot.org = os2->usWinAscent;
940 TA_LOG_GLOBAL(("artificial blue zone for usWinAscent:\n"
941 " -> reference = %ld\n"
942 " overshoot = %ld\n",
943 blue->ref.org, blue->shoot.org));
945 blue = &axis->blues[axis->blue_count + 1];
946 blue->flags = TA_LATIN_BLUE_ACTIVE;
947 blue->ref.org =
948 blue->shoot.org = -os2->usWinDescent;
950 TA_LOG_GLOBAL(("artificial blue zone for usWinDescent:\n"
951 " -> reference = %ld\n"
952 " overshoot = %ld\n",
953 blue->ref.org, blue->shoot.org));
955 else
957 blue = &axis->blues[axis->blue_count];
958 blue->flags =
959 blue->ref.org =
960 blue->shoot.org = 0;
962 blue = &axis->blues[axis->blue_count + 1];
963 blue->flags =
964 blue->ref.org =
965 blue->shoot.org = 0;
969 TA_LOG_GLOBAL(("\n"));
971 return;
975 /* check whether all ASCII digits have the same advance width */
977 void
978 ta_latin_metrics_check_digits(TA_LatinMetrics metrics,
979 FT_Face face)
981 FT_Bool started = 0, same_width = 1;
982 FT_Fixed advance, old_advance = 0;
984 void* shaper_buf;
986 /* in all supported charmaps, digits have character codes 0x30-0x39 */
987 const char digits[] = "0 1 2 3 4 5 6 7 8 9";
988 const char* p;
991 p = digits;
992 shaper_buf = ta_shaper_buf_create(face);
994 while (*p)
996 FT_ULong glyph_index;
997 unsigned int num_idx;
1000 /* reject input that maps to more than a single glyph */
1001 p = ta_shaper_get_cluster(p, &metrics->root, shaper_buf, &num_idx);
1002 if (num_idx > 1)
1003 continue;
1005 glyph_index = ta_shaper_get_elem(&metrics->root,
1006 shaper_buf,
1008 &advance,
1009 NULL);
1010 if (!glyph_index)
1011 continue;
1013 if (started)
1015 if (advance != old_advance)
1017 same_width = 0;
1018 break;
1021 else
1023 old_advance = advance;
1024 started = 1;
1028 ta_shaper_buf_destroy(face, shaper_buf);
1030 metrics->root.digits_have_same_width = same_width;
1034 /* initialize global metrics */
1036 FT_Error
1037 ta_latin_metrics_init(TA_LatinMetrics metrics,
1038 FT_Face face)
1040 FT_CharMap oldmap = face->charmap;
1043 metrics->units_per_em = face->units_per_EM;
1045 if (!FT_Select_Charmap(face, FT_ENCODING_UNICODE))
1047 ta_latin_metrics_init_widths(metrics, face, 1);
1048 ta_latin_metrics_init_blues(metrics, face);
1049 ta_latin_metrics_check_digits(metrics, face);
1051 else
1053 /* we only have a symbol font encoding */
1054 ta_latin_metrics_init_widths(metrics, face, 0);
1057 FT_Set_Charmap(face, oldmap);
1058 return FT_Err_Ok;
1062 /* adjust scaling value, then scale and shift widths */
1063 /* and blue zones (if applicable) for given dimension */
1065 static void
1066 ta_latin_metrics_scale_dim(TA_LatinMetrics metrics,
1067 TA_Scaler scaler,
1068 TA_Dimension dim)
1070 FT_Fixed scale;
1071 FT_Pos delta;
1072 TA_LatinAxis axis;
1073 FT_UInt ppem;
1074 FT_UInt nn;
1077 ppem = metrics->root.scaler.face->size->metrics.x_ppem;
1079 if (dim == TA_DIMENSION_HORZ)
1081 scale = scaler->x_scale;
1082 delta = scaler->x_delta;
1084 else
1086 scale = scaler->y_scale;
1087 delta = scaler->y_delta;
1090 axis = &metrics->axis[dim];
1092 if (axis->org_scale == scale && axis->org_delta == delta)
1093 return;
1095 axis->org_scale = scale;
1096 axis->org_delta = delta;
1098 /* correct Y scale to optimize the alignment of the top of */
1099 /* small letters to the pixel grid */
1100 /* (if we do x-height snapping for this ppem value) */
1101 if (!number_set_is_element(
1102 metrics->root.globals->font->x_height_snapping_exceptions,
1103 (int)ppem))
1105 TA_LatinAxis Axis = &metrics->axis[TA_DIMENSION_VERT];
1106 TA_LatinBlue blue = NULL;
1109 for (nn = 0; nn < Axis->blue_count; nn++)
1111 if (Axis->blues[nn].flags & TA_LATIN_BLUE_ADJUSTMENT)
1113 blue = &Axis->blues[nn];
1114 break;
1118 if (blue)
1120 FT_Pos scaled;
1121 FT_Pos threshold;
1122 FT_Pos fitted;
1123 FT_UInt limit;
1126 scaled = FT_MulFix(blue->shoot.org, scaler->y_scale);
1127 limit = metrics->root.globals->increase_x_height;
1128 threshold = 40;
1130 /* if the `increase-x-height' property is active, */
1131 /* we round up much more often */
1132 if (limit
1133 && ppem <= limit
1134 && ppem >= TA_PROP_INCREASE_X_HEIGHT_MIN)
1135 threshold = 52;
1137 fitted = (scaled + threshold) & ~63;
1139 if (scaled != fitted)
1141 if (dim == TA_DIMENSION_VERT)
1143 FT_Pos max_height;
1144 FT_Pos dist;
1145 FT_Fixed new_scale;
1148 new_scale = FT_MulDiv(scale, fitted, scaled);
1150 /* the scaling should not change the result by more than two pixels */
1151 max_height = metrics->units_per_em;
1153 for (nn = 0; nn < Axis->blue_count; nn++)
1155 max_height = TA_MAX(max_height, Axis->blues[nn].ascender);
1156 max_height = TA_MAX(max_height, -Axis->blues[nn].descender);
1159 dist = TA_ABS(FT_MulFix(max_height, new_scale - scale));
1160 dist &= ~127;
1162 if (dist == 0)
1164 scale = new_scale;
1166 TA_LOG_GLOBAL((
1167 "ta_latin_metrics_scale_dim:"
1168 " x height alignment (style `%s'):\n"
1170 " vertical scaling changed from %.4f to %.4f (by %d%%)\n"
1171 "\n",
1172 ta_style_names[metrics->root.style_class->style],
1173 axis->org_scale / 65536.0,
1174 scale / 65536.0,
1175 (fitted - scaled) * 100 / scaled));
1177 #ifdef TA_DEBUG
1178 else
1180 TA_LOG_GLOBAL((
1181 "ta_latin_metrics_scale_dim:"
1182 " x height alignment (style `%s'):\n"
1184 " excessive vertical scaling abandoned\n"
1185 "\n",
1186 ta_style_names[metrics->root.style_class->style]));
1188 #endif
1194 axis->scale = scale;
1195 axis->delta = delta;
1197 if (dim == TA_DIMENSION_HORZ)
1199 metrics->root.scaler.x_scale = scale;
1200 metrics->root.scaler.x_delta = delta;
1202 else
1204 metrics->root.scaler.y_scale = scale;
1205 metrics->root.scaler.y_delta = delta;
1208 TA_LOG_GLOBAL(("%s widths (style `%s')\n",
1209 dim == TA_DIMENSION_HORZ ? "horizontal" : "vertical",
1210 ta_style_names[metrics->root.style_class->style]));
1212 /* scale the widths */
1213 for (nn = 0; nn < axis->width_count; nn++)
1215 TA_Width width = axis->widths + nn;
1218 width->cur = FT_MulFix(width->org, scale);
1219 width->fit = width->cur;
1221 TA_LOG_GLOBAL((" %d scaled to %.2f\n",
1222 width->org,
1223 width->cur / 64.0));
1226 TA_LOG_GLOBAL(("\n"));
1228 /* an extra-light axis corresponds to a standard width that is */
1229 /* smaller than 5/8 pixels */
1230 axis->extra_light =
1231 (FT_Bool)(FT_MulFix(axis->standard_width, scale) < 32 + 8);
1233 #ifdef TA_DEBUG
1234 if (axis->extra_light)
1235 TA_LOG_GLOBAL(("`%s' style is extra light (at current resolution)\n"
1236 "\n",
1237 ta_style_names[metrics->root.style_class->style]));
1238 #endif
1240 if (dim == TA_DIMENSION_VERT)
1242 #ifdef TA_DEBUG
1243 if (axis->blue_count)
1244 TA_LOG_GLOBAL(("blue zones (style `%s')\n",
1245 ta_style_names[metrics->root.style_class->style]));
1246 #endif
1248 /* scale the blue zones */
1249 for (nn = 0; nn < axis->blue_count; nn++)
1251 TA_LatinBlue blue = &axis->blues[nn];
1252 FT_Pos dist;
1255 blue->ref.cur = FT_MulFix(blue->ref.org, scale) + delta;
1256 blue->ref.fit = blue->ref.cur;
1257 blue->shoot.cur = FT_MulFix(blue->shoot.org, scale) + delta;
1258 blue->shoot.fit = blue->shoot.cur;
1259 blue->flags &= ~TA_LATIN_BLUE_ACTIVE;
1261 /* a blue zone is only active if it is less than 3/4 pixels tall */
1262 dist = FT_MulFix(blue->ref.org - blue->shoot.org, scale);
1263 if (dist <= 48 && dist >= -48)
1265 #if 0
1266 FT_Pos delta1;
1267 #endif
1268 FT_Pos delta2;
1271 /* use discrete values for blue zone widths */
1273 #if 0
1274 /* generic, original code */
1275 delta1 = blue->shoot.org - blue->ref.org;
1276 delta2 = delta1;
1277 if (delta1 < 0)
1278 delta2 = -delta2;
1280 delta2 = FT_MulFix(delta2, scale);
1282 if (delta2 < 32)
1283 delta2 = 0;
1284 else if (delta2 < 64)
1285 delta2 = 32 + (((delta2 - 32) + 16) & ~31);
1286 else
1287 delta2 = TA_PIX_ROUND(delta2);
1289 if (delta1 < 0)
1290 delta2 = -delta2;
1292 blue->ref.fit = TA_PIX_ROUND(blue->ref.cur);
1293 blue->shoot.fit = blue->ref.fit + delta2;
1294 #else
1295 /* simplified version due to abs(dist) <= 48 */
1296 delta2 = dist;
1297 if (dist < 0)
1298 delta2 = -delta2;
1300 if (delta2 < 32)
1301 delta2 = 0;
1302 else if (delta2 < 48)
1303 delta2 = 32;
1304 else
1305 delta2 = 64;
1307 if (dist < 0)
1308 delta2 = -delta2;
1310 blue->ref.fit = TA_PIX_ROUND(blue->ref.cur);
1311 blue->shoot.fit = blue->ref.fit - delta2;
1312 #endif
1314 blue->flags |= TA_LATIN_BLUE_ACTIVE;
1318 /* use sub-top blue zone only if it doesn't overlap with */
1319 /* another (non-sup-top) blue zone; otherwise, the */
1320 /* effect would be similar to a neutral blue zone, which */
1321 /* is not desired here */
1322 for (nn = 0; nn < axis->blue_count; nn++)
1324 TA_LatinBlue blue = &axis->blues[nn];
1325 FT_UInt i;
1328 if (!(blue->flags & TA_LATIN_BLUE_SUB_TOP))
1329 continue;
1330 if (!(blue->flags & TA_LATIN_BLUE_ACTIVE))
1331 continue;
1333 for (i = 0; i < axis->blue_count; i++)
1335 TA_LatinBlue b = &axis->blues[i];
1338 if (b->flags & TA_LATIN_BLUE_SUB_TOP)
1339 continue;
1340 if (!(b->flags & TA_LATIN_BLUE_ACTIVE))
1341 continue;
1343 if (b->ref.fit <= blue->shoot.fit
1344 && b->shoot.fit >= blue->ref.fit)
1346 blue->flags &= ~TA_LATIN_BLUE_ACTIVE;
1347 break;
1352 #ifdef TA_DEBUG
1353 for (nn = 0; nn < axis->blue_count; nn++)
1355 TA_LatinBlue blue = &axis->blues[nn];
1358 TA_LOG_GLOBAL((" reference %d: %d scaled to %.2f%s\n"
1359 " overshoot %d: %d scaled to %.2f%s\n",
1361 blue->ref.org,
1362 blue->ref.fit / 64.0,
1363 blue->flags & TA_LATIN_BLUE_ACTIVE ? ""
1364 : " (inactive)",
1366 blue->shoot.org,
1367 blue->shoot.fit / 64.0,
1368 blue->flags & TA_LATIN_BLUE_ACTIVE ? ""
1369 : " (inactive)"));
1371 #endif
1373 /* the last two artificial blue zones are to be scaled */
1374 /* with uncorrected scaling values */
1375 if (metrics->root.globals->font->windows_compatibility)
1377 TA_LatinAxis a = &metrics->axis[TA_DIMENSION_VERT];
1378 TA_LatinBlue b;
1381 b = &a->blues[a->blue_count];
1382 b->ref.cur =
1383 b->ref.fit =
1384 b->shoot.cur =
1385 b->shoot.fit = FT_MulFix(b->ref.org, a->org_scale) + delta;
1387 TA_LOG_GLOBAL((" reference %d: %d scaled to %.2f (artificial)\n"
1388 " overshoot %d: %d scaled to %.2f (artificial)\n",
1389 a->blue_count,
1390 b->ref.org,
1391 b->ref.fit / 64.0,
1392 a->blue_count,
1393 b->shoot.org,
1394 b->shoot.fit / 64.0));
1396 b = &a->blues[a->blue_count + 1];
1397 b->ref.cur =
1398 b->ref.fit =
1399 b->shoot.cur =
1400 b->shoot.fit = FT_MulFix(b->ref.org, a->org_scale) + delta;
1402 TA_LOG_GLOBAL((" reference %d: %d scaled to %.2f (artificial)\n"
1403 " overshoot %d: %d scaled to %.2f (artificial)\n",
1404 a->blue_count + 1,
1405 b->ref.org,
1406 b->ref.fit / 64.0,
1407 a->blue_count + 1,
1408 b->shoot.org,
1409 b->shoot.fit / 64.0));
1412 TA_LOG_GLOBAL(("\n"));
1417 /* scale global values in both directions */
1419 void
1420 ta_latin_metrics_scale(TA_LatinMetrics metrics,
1421 TA_Scaler scaler)
1423 metrics->root.scaler.render_mode = scaler->render_mode;
1424 metrics->root.scaler.face = scaler->face;
1425 metrics->root.scaler.flags = scaler->flags;
1427 ta_latin_metrics_scale_dim(metrics, scaler, TA_DIMENSION_HORZ);
1428 ta_latin_metrics_scale_dim(metrics, scaler, TA_DIMENSION_VERT);
1432 /* walk over all contours and compute its segments */
1434 FT_Error
1435 ta_latin_hints_compute_segments(TA_GlyphHints hints,
1436 TA_Dimension dim)
1438 TA_LatinMetrics metrics = (TA_LatinMetrics)hints->metrics;
1439 TA_AxisHints axis = &hints->axis[dim];
1440 FT_Error error = FT_Err_Ok;
1442 TA_Segment segment = NULL;
1443 TA_SegmentRec seg0;
1445 TA_Point* contour = hints->contours;
1446 TA_Point* contour_limit = contour + hints->num_contours;
1447 TA_Direction major_dir, segment_dir;
1449 FT_Pos flat_threshold = FLAT_THRESHOLD(metrics->units_per_em);
1452 memset(&seg0, 0, sizeof (TA_SegmentRec));
1453 seg0.score = 32000;
1454 seg0.flags = TA_EDGE_NORMAL;
1456 major_dir = (TA_Direction)TA_ABS(axis->major_dir);
1457 segment_dir = major_dir;
1459 axis->num_segments = 0;
1461 /* set up (u,v) in each point */
1462 if (dim == TA_DIMENSION_HORZ)
1464 TA_Point point = hints->points;
1465 TA_Point limit = point + hints->num_points;
1468 for (; point < limit; point++)
1470 point->u = point->fx;
1471 point->v = point->fy;
1474 else
1476 TA_Point point = hints->points;
1477 TA_Point limit = point + hints->num_points;
1480 for (; point < limit; point++)
1482 point->u = point->fy;
1483 point->v = point->fx;
1487 /* do each contour separately */
1488 for (; contour < contour_limit; contour++)
1490 TA_Point point = contour[0];
1491 TA_Point last = point->prev;
1493 int on_edge = 0;
1495 /* we call values measured along a segment (point->v) */
1496 /* `coordinates', and values orthogonal to it (point->u) */
1497 /* `positions' */
1498 FT_Pos min_pos = 32000;
1499 FT_Pos max_pos = -32000;
1500 FT_Pos min_coord = 32000;
1501 FT_Pos max_coord = -32000;
1502 FT_UShort min_flags = TA_FLAG_NONE;
1503 FT_UShort max_flags = TA_FLAG_NONE;
1504 FT_Pos min_on_coord = 32000;
1505 FT_Pos max_on_coord = -32000;
1507 FT_Bool passed;
1509 TA_Segment prev_segment = NULL;
1511 FT_Pos prev_min_pos = min_pos;
1512 FT_Pos prev_max_pos = max_pos;
1513 FT_Pos prev_min_coord = min_coord;
1514 FT_Pos prev_max_coord = max_coord;
1515 FT_UShort prev_min_flags = min_flags;
1516 FT_UShort prev_max_flags = max_flags;
1517 FT_Pos prev_min_on_coord = min_on_coord;
1518 FT_Pos prev_max_on_coord = max_on_coord;
1521 if (TA_ABS(last->out_dir) == major_dir
1522 && TA_ABS(point->out_dir) == major_dir)
1524 /* we are already on an edge, try to locate its start */
1525 last = point;
1527 for (;;)
1529 point = point->prev;
1530 if (TA_ABS(point->out_dir) != major_dir)
1532 point = point->next;
1533 break;
1535 if (point == last)
1536 break;
1540 last = point;
1541 passed = 0;
1543 for (;;)
1545 FT_Pos u, v;
1548 if (on_edge)
1550 /* get minimum and maximum position */
1551 u = point->u;
1552 if (u < min_pos)
1553 min_pos = u;
1554 if (u > max_pos)
1555 max_pos = u;
1557 /* get minimum and maximum coordinate together with flags */
1558 v = point->v;
1559 if (v < min_coord)
1561 min_coord = v;
1562 min_flags = point->flags;
1564 if (v > max_coord)
1566 max_coord = v;
1567 max_flags = point->flags;
1570 /* get minimum and maximum coordinate of `on' points */
1571 if (!(point->flags & TA_FLAG_CONTROL))
1573 v = point->v;
1574 if (v < min_on_coord)
1575 min_on_coord = v;
1576 if (v > max_on_coord)
1577 max_on_coord = v;
1580 if (point->out_dir != segment_dir
1581 || point == last)
1583 /* check whether the new segment's start point is identical to */
1584 /* the previous segment's end point; for example, this might */
1585 /* happen for spikes */
1587 if (!prev_segment
1588 || segment->first != prev_segment->last)
1590 /* points are different: we are just leaving an edge, thus */
1591 /* record a new segment */
1593 segment->last = point;
1594 segment->pos = (FT_Short)((min_pos + max_pos) >> 1);
1595 segment->delta = (FT_Short)((max_pos - min_pos) >> 1);
1597 /* a segment is round if either its first or last point */
1598 /* is a control point, and the length of the on points */
1599 /* inbetween doesn't exceed a heuristic limit */
1600 if ((min_flags | max_flags) & TA_FLAG_CONTROL
1601 && (max_on_coord - min_on_coord) < flat_threshold)
1602 segment->flags |= TA_EDGE_ROUND;
1604 segment->min_coord = (FT_Short)min_coord;
1605 segment->max_coord = (FT_Short)max_coord;
1606 segment->height = segment->max_coord - segment->min_coord;
1608 prev_segment = segment;
1609 prev_min_pos = min_pos;
1610 prev_max_pos = max_pos;
1611 prev_min_coord = min_coord;
1612 prev_max_coord = max_coord;
1613 prev_min_flags = min_flags;
1614 prev_max_flags = max_flags;
1615 prev_min_on_coord = min_on_coord;
1616 prev_max_on_coord = max_on_coord;
1618 else
1620 /* points are the same: we don't create a new segment but */
1621 /* merge the current segment with the previous one */
1623 if (prev_segment->last->in_dir == point->in_dir)
1625 /* we have identical directions (this can happen for */
1626 /* degenerate outlines that move zig-zag along the main */
1627 /* axis without changing the coordinate value of the other */
1628 /* axis, and where the segments have just been merged): */
1629 /* unify segments */
1631 /* update constraints */
1633 if (prev_min_pos < min_pos)
1634 min_pos = prev_min_pos;
1635 if (prev_max_pos > max_pos)
1636 max_pos = prev_max_pos;
1638 if (prev_min_coord < min_coord)
1640 min_coord = prev_min_coord;
1641 min_flags = prev_min_flags;
1643 if (prev_max_coord > max_coord)
1645 max_coord = prev_max_coord;
1646 max_flags = prev_max_flags;
1649 if (prev_min_on_coord < min_on_coord)
1650 min_on_coord = prev_min_on_coord;
1651 if (prev_max_on_coord > max_on_coord)
1652 max_on_coord = prev_max_on_coord;
1654 prev_segment->last = point;
1655 prev_segment->pos = (FT_Short)((min_pos + max_pos) >> 1);
1657 if ((min_flags | max_flags) & TA_FLAG_CONTROL
1658 && (max_on_coord - min_on_coord) < flat_threshold)
1659 prev_segment->flags |= TA_EDGE_ROUND;
1660 else
1661 prev_segment->flags &= ~TA_EDGE_ROUND;
1663 prev_segment->min_coord = (FT_Short)min_coord;
1664 prev_segment->max_coord = (FT_Short)max_coord;
1665 prev_segment->height = prev_segment->max_coord
1666 - prev_segment->min_coord;
1668 else
1670 /* we have different directions; use the properties of the */
1671 /* longer segment and discard the other one */
1673 if (TA_ABS(prev_max_coord - prev_min_coord)
1674 > TA_ABS(max_coord - min_coord))
1676 /* discard current segment */
1678 if (min_pos < prev_min_pos)
1679 prev_min_pos = min_pos;
1680 if (max_pos > prev_max_pos)
1681 prev_max_pos = max_pos;
1683 prev_segment->last = point;
1684 prev_segment->pos = (FT_Short)((prev_min_pos
1685 + prev_max_pos) >> 1);
1687 else
1689 /* discard previous segment */
1691 if (prev_min_pos < min_pos)
1692 min_pos = prev_min_pos;
1693 if (prev_max_pos > max_pos)
1694 max_pos = prev_max_pos;
1696 segment->last = point;
1697 segment->pos = (FT_Short)((min_pos + max_pos) >> 1);
1699 if ((min_flags | max_flags) & TA_FLAG_CONTROL
1700 && (max_on_coord - min_on_coord) < flat_threshold)
1701 segment->flags |= TA_EDGE_ROUND;
1703 segment->min_coord = (FT_Short)min_coord;
1704 segment->max_coord = (FT_Short)max_coord;
1705 segment->height = segment->max_coord - segment->min_coord;
1707 *prev_segment = *segment;
1709 prev_min_pos = min_pos;
1710 prev_max_pos = max_pos;
1711 prev_min_coord = min_coord;
1712 prev_max_coord = max_coord;
1713 prev_min_flags = min_flags;
1714 prev_max_flags = max_flags;
1715 prev_min_on_coord = min_on_coord;
1716 prev_max_on_coord = max_on_coord;
1720 axis->num_segments--;
1723 on_edge = 0;
1724 segment = NULL;
1726 /* fall through */
1730 /* now exit if we are at the start/end point */
1731 if (point == last)
1733 if (passed)
1734 break;
1735 passed = 1;
1738 /* if we are not on an edge, check whether the major direction */
1739 /* coincides with the current point's `out' direction, or */
1740 /* whether we have a single-point contour */
1741 if (!on_edge
1742 && (TA_ABS(point->out_dir) == major_dir
1743 || point == point->prev))
1745 /* this is the start of a new segment! */
1746 segment_dir = (TA_Direction)point->out_dir;
1748 error = ta_axis_hints_new_segment(axis, &segment);
1749 if (error)
1750 goto Exit;
1752 /* clear all segment fields */
1753 segment[0] = seg0;
1755 segment->dir = (FT_Char)segment_dir;
1756 segment->first = point;
1757 segment->last = point;
1759 /* `ta_axis_hints_new_segment' reallocates memory, */
1760 /* thus we have to refresh the `prev_segment' pointer */
1761 if (prev_segment)
1762 prev_segment = segment - 1;
1764 min_pos = max_pos = point->u;
1765 min_coord = max_coord = point->v;
1766 min_flags = max_flags = point->flags;
1768 if (point->flags & TA_FLAG_CONTROL)
1770 min_on_coord = 32000;
1771 max_on_coord = -32000;
1773 else
1774 min_on_coord = max_on_coord = point->v;
1776 on_edge = 1;
1778 if (point->out_dir != point->next->in_dir
1779 || point == point->prev)
1782 * We have a one-point segment. This is either
1784 * . a one-point contour (with `in' and `out' direction
1785 * set to TA_DIR_NONE by default), or
1787 * . an artificial one-point segment (with a forced
1788 * `out' direction).
1790 segment->pos = (FT_Short)min_pos;
1792 if (point->flags & TA_FLAG_CONTROL)
1793 segment->flags |= TA_EDGE_ROUND;
1795 /* artificially extend the horizontal size if requested */
1796 segment->min_coord = (FT_Short)point->v + point->left_offset;
1797 segment->max_coord = (FT_Short)point->v + point->right_offset;
1798 segment->height = 0;
1800 on_edge = 0;
1801 segment = NULL;
1805 point = point->next;
1807 } /* contours */
1810 /* now slightly increase the height of segments if this makes sense -- */
1811 /* this is used to better detect and ignore serifs */
1813 TA_Segment segments = axis->segments;
1814 TA_Segment segments_end = segments + axis->num_segments;
1817 for (segment = segments; segment < segments_end; segment++)
1819 TA_Point first = segment->first;
1820 TA_Point last = segment->last;
1822 FT_Pos first_v = first->v;
1823 FT_Pos last_v = last->v;
1826 if (first_v < last_v)
1828 TA_Point p;
1831 p = first->prev;
1832 if (p->v < first_v)
1833 segment->height = (FT_Short)(segment->height +
1834 ((first_v - p->v) >> 1));
1836 p = last->next;
1837 if (p->v > last_v)
1838 segment->height = (FT_Short)(segment->height +
1839 ((p->v - last_v) >> 1));
1841 else
1843 TA_Point p;
1846 p = first->prev;
1847 if (p->v > first_v)
1848 segment->height = (FT_Short)(segment->height +
1849 ((p->v - first_v) >> 1));
1851 p = last->next;
1852 if (p->v < last_v)
1853 segment->height = (FT_Short)(segment->height +
1854 ((last_v - p->v) >> 1));
1859 Exit:
1860 return error;
1864 /* link segments to form stems and serifs; if `width_count' and */
1865 /* `widths' are non-zero, use them to fine-tune the scoring function */
1867 void
1868 ta_latin_hints_link_segments(TA_GlyphHints hints,
1869 FT_UInt width_count,
1870 TA_WidthRec* widths,
1871 TA_Dimension dim)
1873 TA_AxisHints axis = &hints->axis[dim];
1875 TA_Segment segments = axis->segments;
1876 TA_Segment segment_limit = segments + axis->num_segments;
1878 FT_Pos len_threshold, len_score, dist_score, max_width;
1879 TA_Segment seg1, seg2;
1882 if (width_count)
1883 max_width = widths[width_count - 1].org;
1884 else
1885 max_width = 0;
1887 /* a heuristic value to set up a minimum value for overlapping */
1888 len_threshold = TA_LATIN_CONSTANT(hints->metrics, 8);
1889 if (len_threshold == 0)
1890 len_threshold = 1;
1892 /* a heuristic value to weight lengths */
1893 len_score = TA_LATIN_CONSTANT(hints->metrics, 6000);
1895 /* a heuristic value to weight distances (no call to */
1896 /* TA_LATIN_CONSTANT needed, since we work on multiples */
1897 /* of the stem width) */
1898 dist_score = 3000;
1900 /* now compare each segment to the others */
1901 for (seg1 = segments; seg1 < segment_limit; seg1++)
1903 if (seg1->dir != axis->major_dir)
1904 continue;
1906 /* search for stems having opposite directions, */
1907 /* with seg1 to the `left' of seg2 */
1908 for (seg2 = segments; seg2 < segment_limit; seg2++)
1910 FT_Pos pos1 = seg1->pos;
1911 FT_Pos pos2 = seg2->pos;
1914 if (seg1->dir + seg2->dir == 0
1915 && pos2 > pos1)
1917 /* compute distance between the two segments */
1918 FT_Pos min = seg1->min_coord;
1919 FT_Pos max = seg1->max_coord;
1920 FT_Pos len;
1923 if (min < seg2->min_coord)
1924 min = seg2->min_coord;
1925 if (max > seg2->max_coord)
1926 max = seg2->max_coord;
1928 /* compute maximum coordinate difference of the two segments */
1929 /* (this is, how much they overlap) */
1930 len = max - min;
1932 /* for one-point segments, `len' is zero if there is an overlap */
1933 /* (and negative otherwise); we have to correct this */
1934 if (len == 0
1935 && (seg1->min_coord == seg1->max_coord
1936 || seg2->min_coord == seg2->max_coord))
1937 len = len_threshold;
1939 if (len >= len_threshold)
1942 * The score is the sum of two demerits indicating the
1943 * `badness' of a fit, measured along the segments' main axis
1944 * and orthogonal to it, respectively.
1946 * o The less overlapping along the main axis, the worse it
1947 * is, causing a larger demerit.
1949 * o The nearer the orthogonal distance to a stem width, the
1950 * better it is, causing a smaller demerit. For simplicity,
1951 * however, we only increase the demerit for values that
1952 * exceed the largest stem width.
1955 FT_Pos dist = pos2 - pos1;
1957 FT_Pos dist_demerit, score;
1960 if (max_width)
1962 /* distance demerits are based on multiples of `max_width'; */
1963 /* we scale by 1024 for getting more precision */
1964 FT_Pos delta = (dist << 10) / max_width - (1 << 10);
1967 if (delta > 10000)
1968 dist_demerit = 32000;
1969 else if (delta > 0)
1970 dist_demerit = delta * delta / dist_score;
1971 else
1972 dist_demerit = 0;
1974 else
1975 dist_demerit = dist; /* default if no widths available */
1977 score = dist_demerit + len_score / len;
1979 /* and we search for the smallest score */
1980 if (score < seg1->score)
1982 seg1->score = score;
1983 seg1->link = seg2;
1986 if (score < seg2->score)
1988 seg2->score = score;
1989 seg2->link = seg1;
1996 /* now compute the `serif' segments, cf. explanations in `tahints.h' */
1997 for (seg1 = segments; seg1 < segment_limit; seg1++)
1999 seg2 = seg1->link;
2001 if (seg2)
2003 if (seg2->link != seg1)
2005 seg1->link = 0;
2006 seg1->serif = seg2->link;
2013 /* link segments to edges, using feature analysis for selection */
2015 FT_Error
2016 ta_latin_hints_compute_edges(TA_GlyphHints hints,
2017 TA_Dimension dim)
2019 TA_AxisHints axis = &hints->axis[dim];
2020 FT_Error error = FT_Err_Ok;
2021 TA_LatinAxis laxis = &((TA_LatinMetrics)hints->metrics)->axis[dim];
2023 TA_StyleClass style_class = hints->metrics->style_class;
2024 TA_ScriptClass script_class = ta_script_classes[style_class->script];
2026 FT_Bool top_to_bottom_hinting = 0;
2028 TA_Segment segments = axis->segments;
2029 TA_Segment segment_limit = segments + axis->num_segments;
2030 TA_Segment seg;
2032 #if 0
2033 TA_Direction up_dir;
2034 #endif
2035 FT_Fixed scale;
2036 FT_Pos edge_distance_threshold;
2037 FT_Pos segment_length_threshold;
2038 FT_Pos segment_width_threshold;
2041 axis->num_edges = 0;
2043 scale = (dim == TA_DIMENSION_HORZ) ? hints->x_scale
2044 : hints->y_scale;
2046 #if 0
2047 up_dir = (dim == TA_DIMENSION_HORZ) ? TA_DIR_UP
2048 : TA_DIR_RIGHT;
2049 #endif
2051 if (dim == TA_DIMENSION_VERT)
2052 top_to_bottom_hinting = script_class->top_to_bottom_hinting;
2054 /* we ignore all segments that are less than 1 pixel in length */
2055 /* to avoid many problems with serif fonts */
2056 /* (the corresponding threshold is computed in font units) */
2057 if (dim == TA_DIMENSION_HORZ)
2058 segment_length_threshold = FT_DivFix(64, hints->y_scale);
2059 else
2060 segment_length_threshold = 0;
2063 * Similarly, we ignore segments that have a width delta
2064 * larger than 0.5px (i.e., a width larger than 1px).
2066 segment_width_threshold = FT_DivFix(32, scale);
2068 /********************************************************************/
2069 /* */
2070 /* We begin by generating a sorted table of edges for the current */
2071 /* direction. To do so, we simply scan each segment and try to find */
2072 /* an edge in our table that corresponds to its position. */
2073 /* */
2074 /* If no edge is found, we create and insert a new edge in the */
2075 /* sorted table. Otherwise, we simply add the segment to the edge's */
2076 /* list which gets processed in the second step to compute the */
2077 /* edge's properties. */
2078 /* */
2079 /* Note that the table of edges is sorted along the segment/edge */
2080 /* position. */
2081 /* */
2082 /********************************************************************/
2084 /* assure that edge distance threshold is at most 0.25px */
2085 edge_distance_threshold = FT_MulFix(laxis->edge_distance_threshold,
2086 scale);
2087 if (edge_distance_threshold > 64 / 4)
2088 edge_distance_threshold = 64 / 4;
2090 edge_distance_threshold = FT_DivFix(edge_distance_threshold,
2091 scale);
2093 for (seg = segments; seg < segment_limit; seg++)
2095 TA_Edge found = NULL;
2096 FT_Int ee;
2099 /* ignore too short segments, too wide ones, and, in this loop, */
2100 /* one-point segments without a direction */
2101 if (seg->height < segment_length_threshold
2102 || seg->delta > segment_width_threshold
2103 || seg->dir == TA_DIR_NONE)
2104 continue;
2106 /* a special case for serif edges: */
2107 /* if they are smaller than 1.5 pixels we ignore them */
2108 if (seg->serif
2109 && 2 * seg->height < 3 * segment_length_threshold)
2110 continue;
2112 /* look for an edge corresponding to the segment */
2113 for (ee = 0; ee < axis->num_edges; ee++)
2115 TA_Edge edge = axis->edges + ee;
2116 FT_Pos dist;
2119 dist = seg->pos - edge->fpos;
2120 if (dist < 0)
2121 dist = -dist;
2123 if (dist < edge_distance_threshold && edge->dir == seg->dir)
2125 found = edge;
2126 break;
2130 if (!found)
2132 TA_Edge edge;
2135 /* insert a new edge in the list and sort according to the position */
2136 error = ta_axis_hints_new_edge(axis, seg->pos,
2137 (TA_Direction)seg->dir,
2138 top_to_bottom_hinting,
2139 &edge);
2140 if (error)
2141 goto Exit;
2143 /* add the segment to the new edge's list */
2144 memset(edge, 0, sizeof (TA_EdgeRec));
2145 edge->first = seg;
2146 edge->last = seg;
2147 edge->dir = seg->dir;
2148 edge->fpos = seg->pos;
2149 edge->opos = FT_MulFix(seg->pos, scale);
2150 edge->pos = edge->opos;
2151 seg->edge_next = seg;
2153 else
2155 /* if an edge was found, simply add the segment to the edge's list */
2156 seg->edge_next = found->first;
2157 found->last->edge_next = seg;
2158 found->last = seg;
2162 /* we loop again over all segments to catch one-point segments */
2163 /* without a direction: if possible, link them to existing edges */
2164 for (seg = segments; seg < segment_limit; seg++)
2166 TA_Edge found = NULL;
2167 FT_Int ee;
2170 if (seg->dir != TA_DIR_NONE)
2171 continue;
2173 /* look for an edge corresponding to the segment */
2174 for (ee = 0; ee < axis->num_edges; ee++)
2176 TA_Edge edge = axis->edges + ee;
2177 FT_Pos dist;
2180 dist = seg->pos - edge->fpos;
2181 if (dist < 0)
2182 dist = -dist;
2184 if (dist < edge_distance_threshold)
2186 found = edge;
2187 break;
2191 /* one-point segments without a match are ignored */
2192 if (found)
2194 seg->edge_next = found->first;
2195 found->last->edge_next = seg;
2196 found->last = seg;
2200 /*****************************************************************/
2201 /* */
2202 /* Good, we now compute each edge's properties according to */
2203 /* the segments found on its position. Basically, these are */
2204 /* */
2205 /* - the edge's main direction */
2206 /* - stem edge, serif edge or both (which defaults to stem then) */
2207 /* - rounded edge, straight or both (which defaults to straight) */
2208 /* - link for edge */
2209 /* */
2210 /*****************************************************************/
2212 /* first of all, set the `edge' field in each segment -- this is */
2213 /* required in order to compute edge links */
2215 /* note that removing this loop and setting the `edge' field of each */
2216 /* segment directly in the code above slows down execution speed for */
2217 /* some reasons on platforms like the Sun */
2219 TA_Edge edges = axis->edges;
2220 TA_Edge edge_limit = edges + axis->num_edges;
2221 TA_Edge edge;
2224 for (edge = edges; edge < edge_limit; edge++)
2226 seg = edge->first;
2227 if (seg)
2230 seg->edge = edge;
2231 seg = seg->edge_next;
2232 } while (seg != edge->first);
2235 /* now compute each edge properties */
2236 for (edge = edges; edge < edge_limit; edge++)
2238 FT_Int is_round = 0; /* does it contain round segments? */
2239 FT_Int is_straight = 0; /* does it contain straight segments? */
2240 #if 0
2241 FT_Pos ups = 0; /* number of upwards segments */
2242 FT_Pos downs = 0; /* number of downwards segments */
2243 #endif
2246 seg = edge->first;
2250 FT_Bool is_serif;
2253 /* check for roundness of segment */
2254 if (seg->flags & TA_EDGE_ROUND)
2255 is_round++;
2256 else
2257 is_straight++;
2259 #if 0
2260 /* check for segment direction */
2261 if (seg->dir == up_dir)
2262 ups += seg->max_coord - seg->min_coord;
2263 else
2264 downs += seg->max_coord - seg->min_coord;
2265 #endif
2267 /* check for links -- */
2268 /* if seg->serif is set, then seg->link must be ignored */
2269 is_serif = (FT_Bool)(seg->serif
2270 && seg->serif->edge
2271 && seg->serif->edge != edge);
2273 if ((seg->link && seg->link->edge != NULL)
2274 || is_serif)
2276 TA_Edge edge2;
2277 TA_Segment seg2;
2280 edge2 = edge->link;
2281 seg2 = seg->link;
2283 if (is_serif)
2285 seg2 = seg->serif;
2286 edge2 = edge->serif;
2289 if (edge2)
2291 FT_Pos edge_delta;
2292 FT_Pos seg_delta;
2295 edge_delta = edge->fpos - edge2->fpos;
2296 if (edge_delta < 0)
2297 edge_delta = -edge_delta;
2299 seg_delta = seg->pos - seg2->pos;
2300 if (seg_delta < 0)
2301 seg_delta = -seg_delta;
2303 if (seg_delta < edge_delta)
2304 edge2 = seg2->edge;
2306 else
2307 edge2 = seg2->edge;
2309 if (is_serif)
2311 edge->serif = edge2;
2312 edge2->flags |= TA_EDGE_SERIF;
2314 else
2315 edge->link = edge2;
2318 seg = seg->edge_next;
2319 } while (seg != edge->first);
2321 /* set the round/straight flags */
2322 edge->flags = TA_EDGE_NORMAL;
2324 if (is_round > 0
2325 && is_round >= is_straight)
2326 edge->flags |= TA_EDGE_ROUND;
2328 #if 0
2329 /* set the edge's main direction */
2330 edge->dir = TA_DIR_NONE;
2332 if (ups > downs)
2333 edge->dir = (FT_Char)up_dir;
2335 else if (ups < downs)
2336 edge->dir = (FT_Char)-up_dir;
2338 else if (ups == downs)
2339 edge->dir = 0; /* both up and down! */
2340 #endif
2342 /* get rid of serifs if link is set */
2343 /* XXX: this gets rid of many unpleasant artefacts! */
2344 /* example: the `c' in cour.pfa at size 13 */
2346 if (edge->serif && edge->link)
2347 edge->serif = NULL;
2351 Exit:
2352 return error;
2356 /* detect segments and edges for given dimension */
2358 FT_Error
2359 ta_latin_hints_detect_features(TA_GlyphHints hints,
2360 FT_UInt width_count,
2361 TA_WidthRec* widths,
2362 TA_Dimension dim)
2364 FT_Error error;
2367 error = ta_latin_hints_compute_segments(hints, dim);
2368 if (!error)
2370 ta_latin_hints_link_segments(hints, width_count, widths, dim);
2372 error = ta_latin_hints_compute_edges(hints, dim);
2375 return error;
2379 /* compute all edges which lie within blue zones */
2381 static void
2382 ta_latin_hints_compute_blue_edges(TA_GlyphHints hints,
2383 TA_LatinMetrics metrics)
2385 TA_AxisHints axis = &hints->axis[TA_DIMENSION_VERT];
2387 TA_Edge edge = axis->edges;
2388 TA_Edge edge_limit = edge + axis->num_edges;
2390 TA_LatinAxis latin = &metrics->axis[TA_DIMENSION_VERT];
2391 FT_Fixed scale = latin->scale;
2394 /* compute which blue zones are active, */
2395 /* i.e. have their scaled size < 3/4 pixels */
2397 /* for each horizontal edge search the blue zone which is closest */
2398 for (; edge < edge_limit; edge++)
2400 FT_UInt bb;
2401 TA_Width best_blue = NULL;
2402 FT_Bool best_blue_is_neutral = 0;
2403 FT_Pos best_dist; /* initial threshold */
2405 FT_UInt best_blue_idx = 0;
2406 FT_Bool best_blue_is_shoot = 0;
2409 /* compute the initial threshold as a fraction of the EM size */
2410 /* (the value 40 is heuristic) */
2411 best_dist = FT_MulFix(metrics->units_per_em / 40, scale);
2413 /* assure a minimum distance of 0.5px */
2414 if (best_dist > 64 / 2)
2415 best_dist = 64 / 2;
2417 /* this loop also handles the two extra blue zones */
2418 /* for usWinAscent and usWinDescent */
2419 /* if option `windows-compatibility' is set */
2420 for (bb = 0;
2421 bb < latin->blue_count
2422 + (metrics->root.globals->font->windows_compatibility ? 2 : 0);
2423 bb++)
2425 TA_LatinBlue blue = latin->blues + bb;
2426 FT_Bool is_top_blue, is_neutral_blue, is_major_dir;
2429 /* skip inactive blue zones (i.e., those that are too large) */
2430 if (!(blue->flags & TA_LATIN_BLUE_ACTIVE))
2431 continue;
2433 /* if it is a top zone, check for right edges (against the major */
2434 /* direction); if it is a bottom zone, check for left edges (in */
2435 /* the major direction) */
2436 is_top_blue = (FT_Byte)((blue->flags & (TA_LATIN_BLUE_TOP
2437 | TA_LATIN_BLUE_SUB_TOP)) != 0);
2438 is_neutral_blue = (FT_Byte)((blue->flags & TA_LATIN_BLUE_NEUTRAL) != 0);
2439 is_major_dir = FT_BOOL(edge->dir == axis->major_dir);
2441 /* neutral blue zones are handled for both directions */
2442 if (is_top_blue ^ is_major_dir || is_neutral_blue)
2444 FT_Pos dist;
2447 /* first of all, compare it to the reference position */
2448 dist = edge->fpos - blue->ref.org;
2449 if (dist < 0)
2450 dist = -dist;
2452 dist = FT_MulFix(dist, scale);
2453 if (dist < best_dist)
2455 best_dist = dist;
2456 best_blue = &blue->ref;
2457 best_blue_is_neutral = is_neutral_blue;
2459 best_blue_idx = bb;
2460 best_blue_is_shoot = 0;
2463 /* now compare it to the overshoot position and check whether */
2464 /* the edge is rounded, and whether the edge is over the */
2465 /* reference position of a top zone, or under the reference */
2466 /* position of a bottom zone (provided we don't have a */
2467 /* neutral blue zone) */
2468 if (edge->flags & TA_EDGE_ROUND
2469 && dist != 0
2470 && !is_neutral_blue)
2472 FT_Bool is_under_ref = FT_BOOL(edge->fpos < blue->ref.org);
2475 if (is_top_blue ^ is_under_ref)
2477 dist = edge->fpos - blue->shoot.org;
2478 if (dist < 0)
2479 dist = -dist;
2481 dist = FT_MulFix(dist, scale);
2482 if (dist < best_dist)
2484 best_dist = dist;
2485 best_blue = &blue->shoot;
2486 best_blue_is_neutral = is_neutral_blue;
2488 best_blue_idx = bb;
2489 best_blue_is_shoot = 1;
2496 if (best_blue)
2498 edge->blue_edge = best_blue;
2499 edge->best_blue_idx = best_blue_idx;
2500 edge->best_blue_is_shoot = best_blue_is_shoot;
2501 if (best_blue_is_neutral)
2502 edge->flags |= TA_EDGE_NEUTRAL;
2508 /* initalize hinting engine */
2510 static FT_Error
2511 ta_latin_hints_init(TA_GlyphHints hints,
2512 TA_LatinMetrics metrics)
2514 FT_Render_Mode mode;
2515 FT_UInt32 scaler_flags, other_flags;
2516 FT_Face face = metrics->root.scaler.face;
2519 ta_glyph_hints_rescale(hints, (TA_StyleMetrics)metrics);
2521 /* correct x_scale and y_scale if needed, since they may have */
2522 /* been modified by `ta_latin_metrics_scale_dim' above */
2523 hints->x_scale = metrics->axis[TA_DIMENSION_HORZ].scale;
2524 hints->x_delta = metrics->axis[TA_DIMENSION_HORZ].delta;
2525 hints->y_scale = metrics->axis[TA_DIMENSION_VERT].scale;
2526 hints->y_delta = metrics->axis[TA_DIMENSION_VERT].delta;
2528 /* compute flags depending on render mode, etc. */
2529 mode = metrics->root.scaler.render_mode;
2531 #if 0 /* #ifdef TA_CONFIG_OPTION_USE_WARPER */
2532 if (mode == FT_RENDER_MODE_LCD
2533 || mode == FT_RENDER_MODE_LCD_V)
2534 metrics->root.scaler.render_mode =
2535 mode = FT_RENDER_MODE_NORMAL;
2536 #endif
2538 scaler_flags = hints->scaler_flags;
2539 other_flags = 0;
2541 /* we snap the width of vertical stems for the monochrome */
2542 /* and horizontal LCD rendering targets only */
2543 if (mode == FT_RENDER_MODE_MONO
2544 || mode == FT_RENDER_MODE_LCD)
2545 other_flags |= TA_LATIN_HINTS_HORZ_SNAP;
2547 /* we snap the width of horizontal stems for the monochrome */
2548 /* and vertical LCD rendering targets only */
2549 if (mode == FT_RENDER_MODE_MONO
2550 || mode == FT_RENDER_MODE_LCD_V)
2551 other_flags |= TA_LATIN_HINTS_VERT_SNAP;
2553 /* we adjust stems to full pixels only if we don't use the `light' mode */
2554 if (mode != FT_RENDER_MODE_LIGHT)
2555 other_flags |= TA_LATIN_HINTS_STEM_ADJUST;
2557 if (mode == FT_RENDER_MODE_MONO)
2558 other_flags |= TA_LATIN_HINTS_MONO;
2560 /* in `light' hinting mode we disable horizontal hinting completely; */
2561 /* we also do it if the face is italic -- */
2562 /* however, if warping is enabled (which only works in `light' hinting */
2563 /* mode), advance widths get adjusted, too */
2564 if (mode == FT_RENDER_MODE_LIGHT
2565 || (face->style_flags & FT_STYLE_FLAG_ITALIC) != 0)
2566 scaler_flags |= TA_SCALER_FLAG_NO_HORIZONTAL;
2568 #ifdef TA_CONFIG_OPTION_USE_WARPER
2569 /* get (global) warper flag */
2570 if (!metrics->root.globals->module->warping)
2571 scaler_flags |= TA_SCALER_FLAG_NO_WARPER;
2572 #endif
2574 hints->scaler_flags = scaler_flags;
2575 hints->other_flags = other_flags;
2577 return FT_Err_Ok;
2581 /* snap a given width in scaled coordinates to */
2582 /* one of the current standard widths */
2584 static FT_Pos
2585 ta_latin_snap_width(TA_Width widths,
2586 FT_UInt count,
2587 FT_Pos width)
2589 FT_UInt n;
2590 FT_Pos best = 64 + 32 + 2;
2591 FT_Pos reference = width;
2592 FT_Pos scaled;
2595 for (n = 0; n < count; n++)
2597 FT_Pos w;
2598 FT_Pos dist;
2601 w = widths[n].cur;
2602 dist = width - w;
2603 if (dist < 0)
2604 dist = -dist;
2605 if (dist < best)
2607 best = dist;
2608 reference = w;
2612 scaled = TA_PIX_ROUND(reference);
2614 if (width >= reference)
2616 if (width < scaled + 48)
2617 width = reference;
2619 else
2621 if (width > scaled - 48)
2622 width = reference;
2625 return width;
2629 /* compute the snapped width of a given stem, ignoring very thin ones */
2631 /* there is a lot of voodoo in this function; changing the hard-coded */
2632 /* parameters influences the whole hinting process */
2634 static FT_Pos
2635 ta_latin_compute_stem_width(TA_GlyphHints hints,
2636 TA_Dimension dim,
2637 FT_Pos width,
2638 FT_Pos base_delta,
2639 FT_Byte base_flags,
2640 FT_Byte stem_flags)
2642 TA_LatinMetrics metrics = (TA_LatinMetrics) hints->metrics;
2643 TA_LatinAxis axis = &metrics->axis[dim];
2645 FT_Pos dist = width;
2646 FT_Int sign = 0;
2647 FT_Int vertical = (dim == TA_DIMENSION_VERT);
2650 if (!TA_LATIN_HINTS_DO_STEM_ADJUST(hints)
2651 || axis->extra_light)
2652 return width;
2654 if (dist < 0)
2656 dist = -width;
2657 sign = 1;
2660 if ((vertical && !TA_LATIN_HINTS_DO_VERT_SNAP(hints))
2661 || (!vertical && !TA_LATIN_HINTS_DO_HORZ_SNAP(hints)))
2663 /* smooth hinting process: very lightly quantize the stem width */
2665 /* leave the widths of serifs alone */
2666 if ((stem_flags & TA_EDGE_SERIF)
2667 && vertical
2668 && (dist < 3 * 64))
2669 goto Done_Width;
2670 else if (base_flags & TA_EDGE_ROUND)
2672 if (dist < 80)
2673 dist = 64;
2675 else if (dist < 56)
2676 dist = 56;
2678 if (axis->width_count > 0)
2680 FT_Pos delta;
2683 /* compare to standard width */
2684 delta = dist - axis->widths[0].cur;
2686 if (delta < 0)
2687 delta = -delta;
2689 if (delta < 40)
2691 dist = axis->widths[0].cur;
2692 if (dist < 48)
2693 dist = 48;
2695 goto Done_Width;
2698 if (dist < 3 * 64)
2700 delta = dist & 63;
2701 dist &= -64;
2703 if (delta < 10)
2704 dist += delta;
2705 else if (delta < 32)
2706 dist += 10;
2707 else if (delta < 54)
2708 dist += 54;
2709 else
2710 dist += delta;
2712 else
2714 /* A stem's end position depends on two values: the start */
2715 /* position and the stem length. The former gets usually */
2716 /* rounded to the grid, while the latter gets rounded also if it */
2717 /* exceeds a certain length (see below in this function). This */
2718 /* `double rounding' can lead to a great difference to the */
2719 /* original, unhinted position; this normally doesn't matter for */
2720 /* large PPEM values, but for small sizes it can easily make */
2721 /* outlines collide. For this reason, we adjust the stem length */
2722 /* by a small amount depending on the PPEM value in case the */
2723 /* former and latter rounding both point into the same */
2724 /* direction. */
2726 FT_Pos bdelta = 0;
2729 if (((width > 0) && (base_delta > 0))
2730 || ((width < 0) && (base_delta < 0)))
2732 FT_UInt ppem = metrics->root.scaler.face->size->metrics.x_ppem;
2735 if (ppem < 10)
2736 bdelta = base_delta;
2737 else if (ppem < 30)
2738 bdelta = (base_delta * (FT_Pos)(30 - ppem)) / 20;
2740 if (bdelta < 0)
2741 bdelta = -bdelta;
2744 dist = (dist - bdelta + 32) & ~63;
2748 else
2750 /* strong hinting process: snap the stem width to integer pixels */
2752 FT_Pos org_dist = dist;
2755 dist = ta_latin_snap_width(axis->widths, axis->width_count, dist);
2757 if (vertical)
2759 /* in the case of vertical hinting, */
2760 /* always round the stem heights to integer pixels */
2762 if (dist >= 64)
2763 dist = (dist + 16) & ~63;
2764 else
2765 dist = 64;
2767 else
2769 if (TA_LATIN_HINTS_DO_MONO(hints))
2771 /* monochrome horizontal hinting: */
2772 /* snap widths to integer pixels with a different threshold */
2774 if (dist < 64)
2775 dist = 64;
2776 else
2777 dist = (dist + 32) & ~63;
2779 else
2781 /* for horizontal anti-aliased hinting, we adopt a more subtle */
2782 /* approach: we strengthen small stems, round stems whose size */
2783 /* is between 1 and 2 pixels to an integer, otherwise nothing */
2785 if (dist < 48)
2786 dist = (dist + 64) >> 1;
2788 else if (dist < 128)
2790 /* we only round to an integer width if the corresponding */
2791 /* distortion is less than 1/4 pixel -- otherwise, this */
2792 /* makes everything worse since the diagonals, which are */
2793 /* not hinted, appear a lot bolder or thinner than the */
2794 /* vertical stems */
2796 FT_Pos delta;
2799 dist = (dist + 22) & ~63;
2800 delta = dist - org_dist;
2801 if (delta < 0)
2802 delta = -delta;
2804 if (delta >= 16)
2806 dist = org_dist;
2807 if (dist < 48)
2808 dist = (dist + 64) >> 1;
2811 else
2812 /* round otherwise to prevent color fringes in LCD mode */
2813 dist = (dist + 32) & ~63;
2818 Done_Width:
2819 if (sign)
2820 dist = -dist;
2822 return dist;
2826 /* align one stem edge relative to the previous stem edge */
2828 static void
2829 ta_latin_align_linked_edge(TA_GlyphHints hints,
2830 TA_Dimension dim,
2831 TA_Edge base_edge,
2832 TA_Edge stem_edge)
2834 FT_Pos dist, base_delta;
2835 FT_Pos fitted_width;
2838 dist = stem_edge->opos - base_edge->opos;
2839 base_delta = base_edge->pos - base_edge->opos;
2842 fitted_width = ta_latin_compute_stem_width(hints, dim,
2843 dist, base_delta,
2844 base_edge->flags,
2845 stem_edge->flags);
2847 stem_edge->pos = base_edge->pos + fitted_width;
2849 TA_LOG((" LINK: edge %d (opos=%.2f) linked to %.2f,"
2850 " dist was %.2f, now %.2f\n",
2851 stem_edge - hints->axis[dim].edges, stem_edge->opos / 64.0,
2852 stem_edge->pos / 64.0, dist / 64.0, fitted_width / 64.0));
2854 if (hints->recorder)
2855 hints->recorder(ta_link, hints, dim,
2856 base_edge, stem_edge, NULL, NULL, NULL);
2860 /* shift the coordinates of the `serif' edge by the same amount */
2861 /* as the corresponding `base' edge has been moved already */
2863 static void
2864 ta_latin_align_serif_edge(TA_GlyphHints hints,
2865 TA_Edge base,
2866 TA_Edge serif)
2868 FT_UNUSED(hints);
2870 serif->pos = base->pos + (serif->opos - base->opos);
2874 /* the main grid-fitting routine */
2876 static void
2877 ta_latin_hint_edges(TA_GlyphHints hints,
2878 TA_Dimension dim)
2880 TA_AxisHints axis = &hints->axis[dim];
2882 TA_Edge edges = axis->edges;
2883 TA_Edge edge_limit = edges + axis->num_edges;
2884 FT_PtrDist n_edges;
2885 TA_Edge edge;
2887 TA_Edge anchor = NULL;
2888 FT_Int has_serifs = 0;
2890 TA_StyleClass style_class = hints->metrics->style_class;
2891 TA_ScriptClass script_class = ta_script_classes[style_class->script];
2893 FT_Bool top_to_bottom_hinting = 0;
2895 #ifdef TA_DEBUG
2896 FT_UInt num_actions = 0;
2897 #endif
2899 TA_LOG(("latin %s edge hinting (style `%s')\n",
2900 dim == TA_DIMENSION_VERT ? "horizontal" : "vertical",
2901 ta_style_names[hints->metrics->style_class->style]));
2903 if (dim == TA_DIMENSION_VERT)
2904 top_to_bottom_hinting = script_class->top_to_bottom_hinting;
2906 /* we begin by aligning all stems relative to the blue zone if needed -- */
2907 /* that's only for horizontal edges */
2909 if (dim == TA_DIMENSION_VERT
2910 && TA_HINTS_DO_BLUES(hints))
2912 for (edge = edges; edge < edge_limit; edge++)
2914 TA_Width blue;
2915 TA_Edge edge1, edge2; /* these edges form the stem to check */
2918 if (edge->flags & TA_EDGE_DONE)
2919 continue;
2921 edge1 = NULL;
2922 edge2 = edge->link;
2925 * If a stem contains both a neutral and a non-neutral blue zone,
2926 * skip the neutral one. Otherwise, outlines with different
2927 * directions might be incorrectly aligned at the same vertical
2928 * position.
2930 * If we have two neutral blue zones, skip one of them.
2932 if (edge->blue_edge && edge2 && edge2->blue_edge)
2934 FT_Byte neutral = edge->flags & TA_EDGE_NEUTRAL;
2935 FT_Byte neutral2 = edge2->flags & TA_EDGE_NEUTRAL;
2938 if (neutral2)
2940 edge2->blue_edge = NULL;
2941 edge2->flags &= ~TA_EDGE_NEUTRAL;
2943 else if (neutral)
2945 edge->blue_edge = NULL;
2946 edge->flags &= ~TA_EDGE_NEUTRAL;
2950 blue = edge->blue_edge;
2951 if (blue)
2952 edge1 = edge;
2954 /* flip edges if the other edge is aligned to a blue zone */
2955 else if (edge2 && edge2->blue_edge)
2957 blue = edge2->blue_edge;
2958 edge1 = edge2;
2959 edge2 = edge;
2962 if (!edge1)
2963 continue;
2965 #ifdef TA_DEBUG
2966 if (!anchor)
2967 TA_LOG((" BLUE_ANCHOR: edge %d (opos=%.2f) snapped to %.2f,"
2968 " was %.2f (anchor=edge %d)\n",
2969 edge1 - edges, edge1->opos / 64.0, blue->fit / 64.0,
2970 edge1->pos / 64.0, edge - edges));
2971 else
2972 TA_LOG((" BLUE: edge %d (opos=%.2f) snapped to %.2f, was %.2f\n",
2973 edge1 - edges, edge1->opos / 64.0, blue->fit / 64.0,
2974 edge1->pos / 64.0));
2976 num_actions++;
2977 #endif
2979 edge1->pos = blue->fit;
2980 edge1->flags |= TA_EDGE_DONE;
2982 if (hints->recorder)
2984 if (!anchor)
2985 hints->recorder(ta_blue_anchor, hints, dim,
2986 edge1, edge, NULL, NULL, NULL);
2987 else
2988 hints->recorder(ta_blue, hints, dim,
2989 edge1, NULL, NULL, NULL, NULL);
2992 if (edge2 && !edge2->blue_edge)
2994 ta_latin_align_linked_edge(hints, dim, edge1, edge2);
2995 edge2->flags |= TA_EDGE_DONE;
2997 #ifdef TA_DEBUG
2998 num_actions++;
2999 #endif
3002 if (!anchor)
3003 anchor = edge;
3007 /* now we align all other stem edges, */
3008 /* trying to maintain the relative order of stems in the glyph */
3009 for (edge = edges; edge < edge_limit; edge++)
3011 TA_Edge edge2;
3014 if (edge->flags & TA_EDGE_DONE)
3015 continue;
3017 /* skip all non-stem edges */
3018 edge2 = edge->link;
3019 if (!edge2)
3021 has_serifs++;
3022 continue;
3025 /* now align the stem */
3027 /* this should not happen, but it's better to be safe */
3028 if (edge2->blue_edge)
3030 TA_LOG((" ASSERTION FAILED for edge %d\n", edge2 - edges));
3032 ta_latin_align_linked_edge(hints, dim, edge2, edge);
3033 edge->flags |= TA_EDGE_DONE;
3035 #ifdef TA_DEBUG
3036 num_actions++;
3037 #endif
3038 continue;
3041 if (!anchor)
3043 /* if we reach this if clause, no stem has been aligned yet */
3045 FT_Pos org_len, org_center, cur_len;
3046 FT_Pos cur_pos1, error1, error2, u_off, d_off;
3049 org_len = edge2->opos - edge->opos;
3050 cur_len = ta_latin_compute_stem_width(hints, dim,
3051 org_len, 0,
3052 edge->flags, edge2->flags);
3054 /* some voodoo to specially round edges for small stem widths; */
3055 /* the idea is to align the center of a stem, */
3056 /* then shifting the stem edges to suitable positions */
3057 if (cur_len <= 64)
3059 /* width <= 1px */
3060 u_off = 32;
3061 d_off = 32;
3063 else
3065 /* 1px < width < 1.5px */
3066 u_off = 38;
3067 d_off = 26;
3070 if (cur_len < 96)
3072 org_center = edge->opos + (org_len >> 1);
3073 cur_pos1 = TA_PIX_ROUND(org_center);
3075 error1 = org_center - (cur_pos1 - u_off);
3076 if (error1 < 0)
3077 error1 = -error1;
3079 error2 = org_center - (cur_pos1 + d_off);
3080 if (error2 < 0)
3081 error2 = -error2;
3083 if (error1 < error2)
3084 cur_pos1 -= u_off;
3085 else
3086 cur_pos1 += d_off;
3088 edge->pos = cur_pos1 - cur_len / 2;
3089 edge2->pos = edge->pos + cur_len;
3091 else
3092 edge->pos = TA_PIX_ROUND(edge->opos);
3094 anchor = edge;
3095 edge->flags |= TA_EDGE_DONE;
3097 TA_LOG((" ANCHOR: edge %d (opos=%.2f) and %d (opos=%.2f)"
3098 " snapped to %.2f and %.2f\n",
3099 edge - edges, edge->opos / 64.0,
3100 edge2 - edges, edge2->opos / 64.0,
3101 edge->pos / 64.0, edge2->pos / 64.0));
3103 if (hints->recorder)
3104 hints->recorder(ta_anchor, hints, dim,
3105 edge, edge2, NULL, NULL, NULL);
3107 ta_latin_align_linked_edge(hints, dim, edge, edge2);
3109 #ifdef TA_DEBUG
3110 num_actions += 2;
3111 #endif
3113 else
3115 FT_Pos org_pos, org_len, org_center, cur_len;
3116 FT_Pos cur_pos1, cur_pos2, delta1, delta2;
3119 org_pos = anchor->pos + (edge->opos - anchor->opos);
3120 org_len = edge2->opos - edge->opos;
3121 org_center = org_pos + (org_len >> 1);
3123 cur_len = ta_latin_compute_stem_width(hints, dim,
3124 org_len, 0,
3125 edge->flags, edge2->flags);
3127 if (edge2->flags & TA_EDGE_DONE)
3129 TA_LOG((" ADJUST: edge %d (pos=%.2f) moved to %.2f\n",
3130 edge - edges, edge->pos / 64.0,
3131 (edge2->pos - cur_len) / 64.0));
3133 edge->pos = edge2->pos - cur_len;
3135 if (hints->recorder)
3137 TA_Edge bound = NULL;
3140 if (edge > edges)
3141 bound = &edge[-1];
3143 hints->recorder(ta_adjust, hints, dim,
3144 edge, edge2, NULL, bound, NULL);
3148 else if (cur_len < 96)
3150 FT_Pos u_off, d_off;
3153 cur_pos1 = TA_PIX_ROUND(org_center);
3155 if (cur_len <= 64)
3157 u_off = 32;
3158 d_off = 32;
3160 else
3162 u_off = 38;
3163 d_off = 26;
3166 delta1 = org_center - (cur_pos1 - u_off);
3167 if (delta1 < 0)
3168 delta1 = -delta1;
3170 delta2 = org_center - (cur_pos1 + d_off);
3171 if (delta2 < 0)
3172 delta2 = -delta2;
3174 if (delta1 < delta2)
3175 cur_pos1 -= u_off;
3176 else
3177 cur_pos1 += d_off;
3179 edge->pos = cur_pos1 - cur_len / 2;
3180 edge2->pos = cur_pos1 + cur_len / 2;
3182 TA_LOG((" STEM: edge %d (opos=%.2f) linked to %d (opos=%.2f)"
3183 " snapped to %.2f and %.2f\n",
3184 edge - edges, edge->opos / 64.0,
3185 edge2 - edges, edge2->opos / 64.0,
3186 edge->pos / 64.0, edge2->pos / 64.0));
3188 if (hints->recorder)
3190 TA_Edge bound = NULL;
3193 if (edge > edges)
3194 bound = &edge[-1];
3196 hints->recorder(ta_stem, hints, dim,
3197 edge, edge2, NULL, bound, NULL);
3201 else
3203 org_pos = anchor->pos + (edge->opos - anchor->opos);
3204 org_len = edge2->opos - edge->opos;
3205 org_center = org_pos + (org_len >> 1);
3207 cur_len = ta_latin_compute_stem_width(hints, dim,
3208 org_len, 0,
3209 edge->flags, edge2->flags);
3211 cur_pos1 = TA_PIX_ROUND(org_pos);
3212 delta1 = cur_pos1 + (cur_len >> 1) - org_center;
3213 if (delta1 < 0)
3214 delta1 = -delta1;
3216 cur_pos2 = TA_PIX_ROUND(org_pos + org_len) - cur_len;
3217 delta2 = cur_pos2 + (cur_len >> 1) - org_center;
3218 if (delta2 < 0)
3219 delta2 = -delta2;
3221 edge->pos = (delta1 < delta2) ? cur_pos1 : cur_pos2;
3222 edge2->pos = edge->pos + cur_len;
3224 TA_LOG((" STEM: edge %d (opos=%.2f) linked to %d (opos=%.2f)"
3225 " snapped to %.2f and %.2f\n",
3226 edge - edges, edge->opos / 64.0,
3227 edge2 - edges, edge2->opos / 64.0,
3228 edge->pos / 64.0, edge2->pos / 64.0));
3230 if (hints->recorder)
3232 TA_Edge bound = NULL;
3235 if (edge > edges)
3236 bound = &edge[-1];
3238 hints->recorder(ta_stem, hints, dim,
3239 edge, edge2, NULL, bound, NULL);
3243 #ifdef TA_DEBUG
3244 num_actions++;
3245 #endif
3247 edge->flags |= TA_EDGE_DONE;
3248 edge2->flags |= TA_EDGE_DONE;
3250 if (edge > edges
3251 && (top_to_bottom_hinting ? (edge->pos > edge[-1].pos)
3252 : (edge->pos < edge[-1].pos)))
3254 /* don't move if stem would (almost) disappear otherwise; */
3255 /* the ad-hoc value 16 corresponds to 1/4px */
3256 if (edge->link
3257 && TA_ABS(edge->link->pos - edge[-1].pos) > 16)
3259 #ifdef TA_DEBUG
3260 TA_LOG((" BOUND: edge %d (pos=%.2f) moved to %.2f\n",
3261 edge - edges, edge->pos / 64.0, edge[-1].pos / 64.0));
3263 num_actions++;
3264 #endif
3266 edge->pos = edge[-1].pos;
3268 if (hints->recorder)
3269 hints->recorder(ta_bound, hints, dim,
3270 edge, &edge[-1], NULL, NULL, NULL);
3276 /* make sure that lowercase m's maintain their symmetry */
3278 /* In general, lowercase m's have six vertical edges if they are sans */
3279 /* serif, or twelve if they are with serifs. This implementation is */
3280 /* based on that assumption, and seems to work very well with most */
3281 /* faces. However, if for a certain face this assumption is not */
3282 /* true, the m is just rendered like before. In addition, any stem */
3283 /* correction will only be applied to symmetrical glyphs (even if the */
3284 /* glyph is not an m), so the potential for unwanted distortion is */
3285 /* relatively low. */
3287 /* we don't handle horizontal edges since we can't easily assure that */
3288 /* the third (lowest) stem aligns with the base line; it might end up */
3289 /* one pixel higher or lower */
3291 n_edges = edge_limit - edges;
3292 if (dim == TA_DIMENSION_HORZ
3293 && (n_edges == 6 || n_edges == 12))
3295 TA_Edge edge1, edge2, edge3;
3296 FT_Pos dist1, dist2, span, delta;
3299 if (n_edges == 6)
3301 edge1 = edges;
3302 edge2 = edges + 2;
3303 edge3 = edges + 4;
3305 else
3307 edge1 = edges + 1;
3308 edge2 = edges + 5;
3309 edge3 = edges + 9;
3312 dist1 = edge2->opos - edge1->opos;
3313 dist2 = edge3->opos - edge2->opos;
3315 span = dist1 - dist2;
3316 if (span < 0)
3317 span = -span;
3319 if (span < 8)
3321 delta = edge3->pos - (2 * edge2->pos - edge1->pos);
3322 edge3->pos -= delta;
3323 if (edge3->link)
3324 edge3->link->pos -= delta;
3326 /* move the serifs along with the stem */
3327 if (n_edges == 12)
3329 (edges + 8)->pos -= delta;
3330 (edges + 11)->pos -= delta;
3333 edge3->flags |= TA_EDGE_DONE;
3334 if (edge3->link)
3335 edge3->link->flags |= TA_EDGE_DONE;
3339 if (has_serifs || !anchor)
3341 /* now hint the remaining edges (serifs and single) */
3342 /* in order to complete our processing */
3343 for (edge = edges; edge < edge_limit; edge++)
3345 TA_Edge lower_bound = NULL;
3346 TA_Edge upper_bound = NULL;
3348 FT_Pos delta;
3351 if (edge->flags & TA_EDGE_DONE)
3352 continue;
3354 delta = 1000;
3356 if (edge->serif)
3358 delta = edge->serif->opos - edge->opos;
3359 if (delta < 0)
3360 delta = -delta;
3363 if (edge > edges)
3364 lower_bound = &edge[-1];
3366 if (edge + 1 < edge_limit
3367 && edge[1].flags & TA_EDGE_DONE)
3368 upper_bound = &edge[1];
3371 if (delta < 64 + 16)
3373 ta_latin_align_serif_edge(hints, edge->serif, edge);
3375 TA_LOG((" SERIF: edge %d (opos=%.2f) serif to %d (opos=%.2f)"
3376 " aligned to %.2f\n",
3377 edge - edges, edge->opos / 64.0,
3378 edge->serif - edges, edge->serif->opos / 64.0,
3379 edge->pos / 64.0));
3381 if (hints->recorder)
3382 hints->recorder(ta_serif, hints, dim,
3383 edge, NULL, NULL, lower_bound, upper_bound);
3385 else if (!anchor)
3387 edge->pos = TA_PIX_ROUND(edge->opos);
3388 anchor = edge;
3390 TA_LOG((" SERIF_ANCHOR: edge %d (opos=%.2f) snapped to %.2f\n",
3391 edge - edges, edge->opos / 64.0, edge->pos / 64.0));
3393 if (hints->recorder)
3394 hints->recorder(ta_serif_anchor, hints, dim,
3395 edge, NULL, NULL, lower_bound, upper_bound);
3397 else
3399 TA_Edge before, after;
3402 for (before = edge - 1; before >= edges; before--)
3403 if (before->flags & TA_EDGE_DONE)
3404 break;
3406 for (after = edge + 1; after < edge_limit; after++)
3407 if (after->flags & TA_EDGE_DONE)
3408 break;
3410 if (before >= edges && before < edge
3411 && after < edge_limit && after > edge)
3413 if (after->opos == before->opos)
3414 edge->pos = before->pos;
3415 else
3416 edge->pos = before->pos + FT_MulDiv(edge->opos - before->opos,
3417 after->pos - before->pos,
3418 after->opos - before->opos);
3420 TA_LOG((" SERIF_LINK1: edge %d (opos=%.2f) snapped to %.2f"
3421 " from %d (opos=%.2f)\n",
3422 edge - edges, edge->opos / 64.0,
3423 edge->pos / 64.0,
3424 before - edges, before->opos / 64.0));
3426 if (hints->recorder)
3427 hints->recorder(ta_serif_link1, hints, dim,
3428 edge, before, after, lower_bound, upper_bound);
3430 else
3432 edge->pos = anchor->pos + ((edge->opos - anchor->opos + 16) & ~31);
3433 TA_LOG((" SERIF_LINK2: edge %d (opos=%.2f) snapped to %.2f\n",
3434 edge - edges, edge->opos / 64.0, edge->pos / 64.0));
3436 if (hints->recorder)
3437 hints->recorder(ta_serif_link2, hints, dim,
3438 edge, NULL, NULL, lower_bound, upper_bound);
3442 #ifdef TA_DEBUG
3443 num_actions++;
3444 #endif
3445 edge->flags |= TA_EDGE_DONE;
3447 if (edge > edges
3448 && (top_to_bottom_hinting ? (edge->pos > edge[-1].pos)
3449 : (edge->pos < edge[-1].pos)))
3451 /* don't move if stem would (almost) disappear otherwise; */
3452 /* the ad-hoc value 16 corresponds to 1/4px */
3453 if (edge->link
3454 && TA_ABS(edge->link->pos - edge[-1].pos) > 16)
3456 #ifdef TA_DEBUG
3457 TA_LOG((" BOUND: edge %d (pos=%.2f) moved to %.2f\n",
3458 edge - edges, edge->pos / 64.0, edge[-1].pos / 64.0));
3459 num_actions++;
3460 #endif
3462 edge->pos = edge[-1].pos;
3464 if (hints->recorder)
3465 hints->recorder(ta_bound, hints, dim,
3466 edge, &edge[-1], NULL, NULL, NULL);
3470 if (edge + 1 < edge_limit
3471 && edge[1].flags & TA_EDGE_DONE
3472 && (top_to_bottom_hinting ? (edge->pos < edge[1].pos)
3473 : (edge->pos > edge[1].pos)))
3476 /* don't move if stem would (almost) disappear otherwise; */
3477 /* the ad-hoc value 16 corresponds to 1/4px */
3478 if (edge->link
3479 && TA_ABS(edge->link->pos - edge[-1].pos) > 16)
3481 #ifdef TA_DEBUG
3482 TA_LOG((" BOUND: edge %d (pos=%.2f) moved to %.2f\n",
3483 edge - edges, edge->pos / 64.0, edge[1].pos / 64.0));
3485 num_actions++;
3486 #endif
3488 edge->pos = edge[1].pos;
3490 if (hints->recorder)
3491 hints->recorder(ta_bound, hints, dim,
3492 edge, &edge[1], NULL, NULL, NULL);
3498 #ifdef TA_DEBUG
3499 if (!num_actions)
3500 TA_LOG((" (none)\n"));
3501 TA_LOG(("\n"));
3502 #endif
3506 /* apply the complete hinting algorithm to a latin glyph */
3508 static FT_Error
3509 ta_latin_hints_apply(FT_UInt glyph_index,
3510 TA_GlyphHints hints,
3511 FT_Outline* outline,
3512 TA_LatinMetrics metrics)
3514 FT_Error error;
3515 int dim;
3517 TA_LatinAxis axis;
3520 error = ta_glyph_hints_reload(hints, outline);
3521 if (error)
3522 goto Exit;
3524 /* analyze glyph outline */
3525 #ifdef TA_CONFIG_OPTION_USE_WARPER
3526 if ((metrics->root.scaler.render_mode == FT_RENDER_MODE_LIGHT
3527 && TA_HINTS_DO_WARP(hints))
3528 || TA_HINTS_DO_HORIZONTAL(hints))
3529 #else
3530 if (TA_HINTS_DO_HORIZONTAL(hints))
3531 #endif
3533 axis = &metrics->axis[TA_DIMENSION_HORZ];
3534 error = ta_latin_hints_detect_features(hints,
3535 axis->width_count,
3536 axis->widths,
3537 TA_DIMENSION_HORZ);
3538 if (error)
3539 goto Exit;
3542 if (TA_HINTS_DO_VERTICAL(hints))
3544 axis = &metrics->axis[TA_DIMENSION_VERT];
3545 error = ta_latin_hints_detect_features(hints,
3546 axis->width_count,
3547 axis->widths,
3548 TA_DIMENSION_VERT);
3549 if (error)
3550 goto Exit;
3552 /* apply blue zones to base characters only */
3553 if (!(metrics->root.globals->glyph_styles[glyph_index] & TA_NONBASE))
3554 ta_latin_hints_compute_blue_edges(hints, metrics);
3557 /* grid-fit the outline */
3558 for (dim = 0; dim < TA_DIMENSION_MAX; dim++)
3560 #ifdef TA_CONFIG_OPTION_USE_WARPER
3561 if (dim == TA_DIMENSION_HORZ
3562 && metrics->root.scaler.render_mode == FT_RENDER_MODE_LIGHT
3563 && TA_HINTS_DO_WARP(hints))
3565 TA_WarperRec warper;
3566 FT_Fixed scale;
3567 FT_Pos delta;
3570 ta_warper_compute(&warper, hints, (TA_Dimension)dim, &scale, &delta);
3571 ta_glyph_hints_scale_dim(hints, (TA_Dimension)dim, scale, delta);
3573 continue;
3575 #endif /* TA_CONFIG_OPTION_USE_WARPER */
3577 if ((dim == TA_DIMENSION_HORZ && TA_HINTS_DO_HORIZONTAL(hints))
3578 || (dim == TA_DIMENSION_VERT && TA_HINTS_DO_VERTICAL(hints)))
3580 ta_latin_hint_edges(hints, (TA_Dimension)dim);
3581 ta_glyph_hints_align_edge_points(hints, (TA_Dimension)dim);
3582 ta_glyph_hints_align_strong_points(hints, (TA_Dimension)dim);
3583 ta_glyph_hints_align_weak_points(hints, (TA_Dimension)dim);
3587 ta_glyph_hints_save(hints, outline);
3589 Exit:
3590 return error;
3594 const TA_WritingSystemClassRec ta_latin_writing_system_class =
3596 TA_WRITING_SYSTEM_LATIN,
3598 sizeof (TA_LatinMetricsRec),
3600 (TA_WritingSystem_InitMetricsFunc)ta_latin_metrics_init,
3601 (TA_WritingSystem_ScaleMetricsFunc)ta_latin_metrics_scale,
3602 (TA_WritingSystem_DoneMetricsFunc)NULL,
3604 (TA_WritingSystem_InitHintsFunc)ta_latin_hints_init,
3605 (TA_WritingSystem_ApplyHintsFunc)ta_latin_hints_apply
3608 /* end of talatin.c */