Improve heuristics for recognizing flat segments.
[ttfautohint.git] / lib / talatin.c
blob7986a1c0d128fff1c402365cca6d5e4a326cc105
1 /* talatin.c */
3 /*
4 * Copyright (C) 2011-2012 by Werner Lemberg.
6 * This file is part of the ttfautohint library, and may only be used,
7 * modified, and distributed under the terms given in `COPYING'. By
8 * continuing to use, modify, or distribute this file you indicate that you
9 * have read `COPYING' and understand and accept it fully.
11 * The file `COPYING' mentioned in the previous paragraph is distributed
12 * with the ttfautohint library.
16 /* originally file `aflatin.c' (2011-Mar-28) from FreeType */
18 /* heavily modified 2011 by Werner Lemberg <wl@gnu.org> */
20 #include <string.h>
22 #include <ft2build.h>
23 #include FT_ADVANCES_H
25 #include "talatin.h"
26 #include "tasort.h"
29 #ifdef TA_CONFIG_OPTION_USE_WARPER
30 #include "tawarp.h"
31 #endif
34 /* find segments and links, compute all stem widths, and initialize */
35 /* standard width and height for the glyph with given charcode */
37 void
38 ta_latin_metrics_init_widths(TA_LatinMetrics metrics,
39 FT_Face face,
40 FT_ULong charcode)
42 /* scan the array of segments in each direction */
43 TA_GlyphHintsRec hints[1];
46 ta_glyph_hints_init(hints);
48 metrics->axis[TA_DIMENSION_HORZ].width_count = 0;
49 metrics->axis[TA_DIMENSION_VERT].width_count = 0;
52 FT_Error error;
53 FT_UInt glyph_index;
54 int dim;
55 TA_LatinMetricsRec dummy[1];
56 TA_Scaler scaler = &dummy->root.scaler;
59 glyph_index = FT_Get_Char_Index(face, charcode);
60 if (glyph_index == 0)
61 goto Exit;
63 error = FT_Load_Glyph(face, glyph_index, FT_LOAD_NO_SCALE);
64 if (error || face->glyph->outline.n_points <= 0)
65 goto Exit;
67 memset(dummy, 0, sizeof (TA_LatinMetricsRec));
69 dummy->units_per_em = metrics->units_per_em;
71 scaler->x_scale = 0x10000L;
72 scaler->y_scale = 0x10000L;
73 scaler->x_delta = 0;
74 scaler->y_delta = 0;
76 scaler->face = face;
77 scaler->render_mode = FT_RENDER_MODE_NORMAL;
78 scaler->flags = 0;
80 ta_glyph_hints_rescale(hints, (TA_ScriptMetrics)dummy);
82 error = ta_glyph_hints_reload(hints, &face->glyph->outline);
83 if (error)
84 goto Exit;
86 for (dim = 0; dim < TA_DIMENSION_MAX; dim++)
88 TA_LatinAxis axis = &metrics->axis[dim];
89 TA_AxisHints axhints = &hints->axis[dim];
91 TA_Segment seg, limit, link;
92 FT_UInt num_widths = 0;
95 error = ta_latin_hints_compute_segments(hints, (TA_Dimension)dim);
96 if (error)
97 goto Exit;
99 ta_latin_hints_link_segments(hints, (TA_Dimension)dim);
101 seg = axhints->segments;
102 limit = seg + axhints->num_segments;
104 for (; seg < limit; seg++)
106 link = seg->link;
108 /* we only consider stem segments there! */
109 if (link
110 && link->link == seg
111 && link > seg)
113 FT_Pos dist;
116 dist = seg->pos - link->pos;
117 if (dist < 0)
118 dist = -dist;
120 if (num_widths < TA_LATIN_MAX_WIDTHS)
121 axis->widths[num_widths++].org = dist;
125 /* this also replaces multiple almost identical stem widths */
126 /* with a single one (the value 100 is heuristic) */
127 ta_sort_and_quantize_widths(&num_widths, axis->widths,
128 dummy->units_per_em / 100);
129 axis->width_count = num_widths;
132 Exit:
133 for (dim = 0; dim < TA_DIMENSION_MAX; dim++)
135 TA_LatinAxis axis = &metrics->axis[dim];
136 FT_Pos stdw;
139 stdw = (axis->width_count > 0) ? axis->widths[0].org
140 : TA_LATIN_CONSTANT(metrics, 50);
142 /* let's try 20% of the smallest width */
143 axis->edge_distance_threshold = stdw / 5;
144 axis->standard_width = stdw;
145 axis->extra_light = 0;
149 ta_glyph_hints_done(hints);
153 #define TA_LATIN_MAX_TEST_CHARACTERS 12
156 static const char ta_latin_blue_chars[TA_LATIN_MAX_BLUES]
157 [TA_LATIN_MAX_TEST_CHARACTERS + 1] =
159 "THEZOCQS",
160 "HEZLOCUS",
161 "fijkdbh",
162 "xzroesc",
163 "xzroesc",
164 "pqgjy"
168 /* find all blue zones; flat segments give the reference points, */
169 /* round segments the overshoot positions */
171 static void
172 ta_latin_metrics_init_blues(TA_LatinMetrics metrics,
173 FT_Face face)
175 FT_Pos flats[TA_LATIN_MAX_TEST_CHARACTERS];
176 FT_Pos rounds[TA_LATIN_MAX_TEST_CHARACTERS];
177 FT_Int num_flats;
178 FT_Int num_rounds;
180 FT_Int bb;
181 TA_LatinBlue blue;
182 FT_Error error;
183 TA_LatinAxis axis = &metrics->axis[TA_DIMENSION_VERT];
184 FT_Outline outline;
187 /* we compute the blues simply by loading each character from the */
188 /* `ta_latin_blue_chars[blues]' string, then finding its top-most or */
189 /* bottom-most points (depending on `TA_IS_TOP_BLUE') */
191 TA_LOG(("blue zones computation\n"
192 "======================\n\n"));
194 for (bb = 0; bb < TA_LATIN_BLUE_MAX; bb++)
196 const char* p = ta_latin_blue_chars[bb];
197 const char* limit = p + TA_LATIN_MAX_TEST_CHARACTERS;
198 FT_Pos* blue_ref;
199 FT_Pos* blue_shoot;
202 TA_LOG(("blue zone %d:\n", bb));
204 num_flats = 0;
205 num_rounds = 0;
207 for (; p < limit && *p; p++)
209 FT_UInt glyph_index;
210 FT_Pos best_y; /* same as points.y */
211 FT_Int best_point, best_contour_first, best_contour_last;
212 FT_Vector* points;
213 FT_Bool round = 0;
216 /* load the character in the face -- skip unknown or empty ones */
217 glyph_index = FT_Get_Char_Index(face, (FT_UInt)*p);
218 if (glyph_index == 0)
219 continue;
221 error = FT_Load_Glyph(face, glyph_index, FT_LOAD_NO_SCALE);
222 outline = face->glyph->outline;
223 if (error || outline.n_points <= 0)
224 continue;
226 /* now compute min or max point indices and coordinates */
227 points = outline.points;
228 best_point = -1;
229 best_y = 0; /* make compiler happy */
230 best_contour_first = 0; /* ditto */
231 best_contour_last = 0; /* ditto */
234 FT_Int nn;
235 FT_Int first = 0;
236 FT_Int last = -1;
239 for (nn = 0; nn < outline.n_contours; first = last + 1, nn++)
241 FT_Int old_best_point = best_point;
242 FT_Int pp;
245 last = outline.contours[nn];
247 /* avoid single-point contours since they are never rasterized; */
248 /* in some fonts, they correspond to mark attachment points */
249 /* which are way outside of the glyph's real outline */
250 if (last <= first)
251 continue;
253 if (TA_LATIN_IS_TOP_BLUE(bb))
255 for (pp = first; pp <= last; pp++)
256 if (best_point < 0
257 || points[pp].y > best_y)
259 best_point = pp;
260 best_y = points[pp].y;
263 else
265 for (pp = first; pp <= last; pp++)
266 if (best_point < 0
267 || points[pp].y < best_y)
269 best_point = pp;
270 best_y = points[pp].y;
274 if (best_point != old_best_point)
276 best_contour_first = first;
277 best_contour_last = last;
280 TA_LOG((" %c %ld", *p, best_y));
283 /* now check whether the point belongs to a straight or round */
284 /* segment; we first need to find in which contour the extremum */
285 /* lies, then inspect its previous and next points */
286 if (best_point >= 0)
288 FT_Pos best_x = points[best_point].x;
289 FT_Int prev, next;
290 FT_Int best_on_point_first, best_on_point_last;
291 FT_Pos dist;
294 if (FT_CURVE_TAG(outline.tags[best_point]) == FT_CURVE_TAG_ON)
296 best_on_point_first = best_point;
297 best_on_point_last = best_point;
299 else
301 best_on_point_first = -1;
302 best_on_point_last = -1;
305 /* look for the previous and next points that are not on the */
306 /* same Y coordinate, then threshold the `closeness'... */
307 prev = best_point;
308 next = prev;
312 if (prev > best_contour_first)
313 prev--;
314 else
315 prev = best_contour_last;
317 dist = TA_ABS(points[prev].y - best_y);
318 /* accept a small distance or a small angle (both values are */
319 /* heuristic; value 20 corresponds to approx. 2.9 degrees) */
320 if (dist > 5)
321 if (TA_ABS(points[prev].x - best_x) <= 20 * dist)
322 break;
324 if (FT_CURVE_TAG(outline.tags[prev]) == FT_CURVE_TAG_ON)
326 best_on_point_first = prev;
327 if (best_on_point_last < 0)
328 best_on_point_last = prev;
331 } while (prev != best_point);
335 if (next < best_contour_last)
336 next++;
337 else
338 next = best_contour_first;
340 dist = TA_ABS(points[next].y - best_y);
341 if (dist > 5)
342 if (TA_ABS(points[next].x - best_x) <= 20 * dist)
343 break;
345 if (FT_CURVE_TAG(outline.tags[next]) == FT_CURVE_TAG_ON)
347 best_on_point_last = next;
348 if (best_on_point_first < 0)
349 best_on_point_first = next;
352 } while (next != best_point);
354 /* now set the `round' flag depending on the segment's kind */
355 /* (value 8 is heuristic) */
356 if (best_on_point_first >= 0
357 && best_on_point_last >= 0
358 && (FT_UInt)(TA_ABS(points[best_on_point_last].x
359 - points[best_on_point_first].x))
360 > metrics->units_per_em / 8)
361 round = 0;
362 else
363 round = FT_BOOL(
364 FT_CURVE_TAG(outline.tags[prev]) != FT_CURVE_TAG_ON
365 || FT_CURVE_TAG(outline.tags[next]) != FT_CURVE_TAG_ON);
367 TA_LOG((" (%s)\n", round ? "round" : "flat"));
370 if (round)
371 rounds[num_rounds++] = best_y;
372 else
373 flats[num_flats++] = best_y;
376 if (num_flats == 0 && num_rounds == 0)
378 /* we couldn't find a single glyph to compute this blue zone, */
379 /* we will simply ignore it then */
380 TA_LOG((" empty\n"));
381 continue;
384 /* we have computed the contents of the `rounds' and `flats' tables, */
385 /* now determine the reference and overshoot position of the blue -- */
386 /* we simply take the median value after a simple sort */
387 ta_sort_pos(num_rounds, rounds);
388 ta_sort_pos(num_flats, flats);
390 blue = &axis->blues[axis->blue_count];
391 blue_ref = &blue->ref.org;
392 blue_shoot = &blue->shoot.org;
394 axis->blue_count++;
396 if (num_flats == 0)
398 *blue_ref =
399 *blue_shoot = rounds[num_rounds / 2];
401 else if (num_rounds == 0)
403 *blue_ref =
404 *blue_shoot = flats[num_flats / 2];
406 else
408 *blue_ref = flats[num_flats / 2];
409 *blue_shoot = rounds[num_rounds / 2];
412 /* there are sometimes problems if the overshoot position of top */
413 /* zones is under its reference position, or the opposite for bottom */
414 /* zones; we must thus check everything there and correct the errors */
415 if (*blue_shoot != *blue_ref)
417 FT_Pos ref = *blue_ref;
418 FT_Pos shoot = *blue_shoot;
419 FT_Bool over_ref = FT_BOOL(shoot > ref);
422 if (TA_LATIN_IS_TOP_BLUE(bb) ^ over_ref)
424 *blue_ref =
425 *blue_shoot = (shoot + ref) / 2;
427 TA_LOG((" [overshoot smaller than reference,"
428 " taking mean value]\n"));
432 blue->flags = 0;
433 if (TA_LATIN_IS_TOP_BLUE(bb))
434 blue->flags |= TA_LATIN_BLUE_TOP;
436 /* the following flag is used later to adjust the y and x scales */
437 /* in order to optimize the pixel grid alignment */
438 /* of the top of small letters */
439 if (bb == TA_LATIN_BLUE_SMALL_TOP)
440 blue->flags |= TA_LATIN_BLUE_ADJUSTMENT;
442 TA_LOG((" -> reference = %ld\n"
443 " overshoot = %ld\n",
444 *blue_ref, *blue_shoot));
447 TA_LOG(("\n"));
449 return;
453 /* check whether all ASCII digits have the same advance width */
455 void
456 ta_latin_metrics_check_digits(TA_LatinMetrics metrics,
457 FT_Face face)
459 FT_UInt i;
460 FT_Bool started = 0, same_width = 1;
461 FT_Fixed advance, old_advance = 0;
464 /* digit `0' is 0x30 in all supported charmaps */
465 for (i = 0x30; i <= 0x39; i++)
467 FT_UInt glyph_index;
470 glyph_index = FT_Get_Char_Index(face, i);
471 if (glyph_index == 0)
472 continue;
474 if (FT_Get_Advance(face, glyph_index,
475 FT_LOAD_NO_SCALE
476 | FT_LOAD_NO_HINTING
477 | FT_LOAD_IGNORE_TRANSFORM,
478 &advance))
479 continue;
481 if (started)
483 if (advance != old_advance)
485 same_width = 0;
486 break;
489 else
491 old_advance = advance;
492 started = 1;
496 metrics->root.digits_have_same_width = same_width;
500 /* initialize global metrics */
502 FT_Error
503 ta_latin_metrics_init(TA_LatinMetrics metrics,
504 FT_Face face)
506 FT_Error error = FT_Err_Ok;
507 FT_CharMap oldmap = face->charmap;
508 FT_UInt ee;
510 static const FT_Encoding latin_encodings[] =
512 FT_ENCODING_UNICODE,
513 FT_ENCODING_APPLE_ROMAN,
514 FT_ENCODING_ADOBE_STANDARD,
515 FT_ENCODING_ADOBE_LATIN_1,
517 FT_ENCODING_NONE /* end of list */
521 metrics->units_per_em = face->units_per_EM;
523 /* do we have a latin charmap in there? */
524 for (ee = 0; latin_encodings[ee] != FT_ENCODING_NONE; ee++)
526 error = FT_Select_Charmap(face, latin_encodings[ee]);
527 if (!error)
528 break;
531 if (!error)
533 /* for now, compute the standard width and height from the `o' */
534 ta_latin_metrics_init_widths(metrics, face, 'o');
535 ta_latin_metrics_init_blues(metrics, face);
536 ta_latin_metrics_check_digits(metrics, face);
539 FT_Set_Charmap(face, oldmap);
540 return FT_Err_Ok;
544 /* adjust scaling value, then scale and shift widths */
545 /* and blue zones (if applicable) for given dimension */
547 static void
548 ta_latin_metrics_scale_dim(TA_LatinMetrics metrics,
549 TA_Scaler scaler,
550 TA_Dimension dim)
552 FT_Fixed scale;
553 FT_Pos delta;
554 TA_LatinAxis axis;
555 FT_UInt nn;
558 if (dim == TA_DIMENSION_HORZ)
560 scale = scaler->x_scale;
561 delta = scaler->x_delta;
563 else
565 scale = scaler->y_scale;
566 delta = scaler->y_delta;
569 axis = &metrics->axis[dim];
571 if (axis->org_scale == scale && axis->org_delta == delta)
572 return;
574 axis->org_scale = scale;
575 axis->org_delta = delta;
577 /* correct X and Y scale to optimize the alignment of the top of */
578 /* small letters to the pixel grid */
580 TA_LatinAxis Axis = &metrics->axis[TA_DIMENSION_VERT];
581 TA_LatinBlue blue = NULL;
584 for (nn = 0; nn < Axis->blue_count; nn++)
586 if (Axis->blues[nn].flags & TA_LATIN_BLUE_ADJUSTMENT)
588 blue = &Axis->blues[nn];
589 break;
593 if (blue)
595 FT_Pos scaled;
596 FT_Pos threshold;
597 FT_Pos fitted;
598 FT_UInt limit;
601 scaled = FT_MulFix(blue->shoot.org, scaler->y_scale);
603 threshold = 40;
604 /* scaler flag bits 3-6 hold the x height increase limit; */
605 /* if zero, the feature is switched off, */
606 /* otherwise the limit is the bits value + 5 */
607 limit = (scaler->flags >> 3) & 15;
608 if (limit
609 && metrics->root.scaler.face->size->metrics.x_ppem <= limit + 5
610 && metrics->root.scaler.face->size->metrics.x_ppem >= 6)
611 threshold = 52;
613 fitted = (scaled + threshold) & ~63;
615 if (scaled != fitted)
617 if (dim == TA_DIMENSION_VERT)
618 scale = FT_MulDiv(scale, fitted, scaled);
623 axis->scale = scale;
624 axis->delta = delta;
626 if (dim == TA_DIMENSION_HORZ)
628 metrics->root.scaler.x_scale = scale;
629 metrics->root.scaler.x_delta = delta;
631 else
633 metrics->root.scaler.y_scale = scale;
634 metrics->root.scaler.y_delta = delta;
637 /* scale the widths */
638 for (nn = 0; nn < axis->width_count; nn++)
640 TA_Width width = axis->widths + nn;
643 width->cur = FT_MulFix(width->org, scale);
644 width->fit = width->cur;
647 /* an extra-light axis corresponds to a standard width that is */
648 /* smaller than 5/8 pixels */
649 axis->extra_light =
650 (FT_Bool)(FT_MulFix(axis->standard_width, scale) < 32 + 8);
652 if (dim == TA_DIMENSION_VERT)
654 /* scale the blue zones */
655 for (nn = 0; nn < axis->blue_count; nn++)
657 TA_LatinBlue blue = &axis->blues[nn];
658 FT_Pos dist;
661 blue->ref.cur = FT_MulFix(blue->ref.org, scale) + delta;
662 blue->ref.fit = blue->ref.cur;
663 blue->shoot.cur = FT_MulFix(blue->shoot.org, scale) + delta;
664 blue->shoot.fit = blue->shoot.cur;
665 blue->flags &= ~TA_LATIN_BLUE_ACTIVE;
667 /* a blue zone is only active if it is less than 3/4 pixels tall */
668 dist = FT_MulFix(blue->ref.org - blue->shoot.org, scale);
669 if (dist <= 48 && dist >= -48)
671 #if 0
672 FT_Pos delta1;
673 #endif
674 FT_Pos delta2;
677 /* use discrete values for blue zone widths */
679 #if 0
680 /* generic, original code */
681 delta1 = blue->shoot.org - blue->ref.org;
682 delta2 = delta1;
683 if (delta1 < 0)
684 delta2 = -delta2;
686 delta2 = FT_MulFix(delta2, scale);
688 if (delta2 < 32)
689 delta2 = 0;
690 else if (delta2 < 64)
691 delta2 = 32 + (((delta2 - 32) + 16) & ~31);
692 else
693 delta2 = TA_PIX_ROUND(delta2);
695 if (delta1 < 0)
696 delta2 = -delta2;
698 blue->ref.fit = TA_PIX_ROUND(blue->ref.cur);
699 blue->shoot.fit = blue->ref.fit + delta2;
700 #else
701 /* simplified version due to abs(dist) <= 48 */
702 delta2 = dist;
703 if (dist < 0)
704 delta2 = -delta2;
706 if (delta2 < 32)
707 delta2 = 0;
708 else if (delta < 48)
709 delta2 = 32;
710 else
711 delta2 = 64;
713 if (dist < 0)
714 delta2 = -delta2;
716 blue->ref.fit = TA_PIX_ROUND(blue->ref.cur);
717 blue->shoot.fit = blue->ref.fit - delta2;
718 #endif
720 blue->flags |= TA_LATIN_BLUE_ACTIVE;
727 /* scale global values in both directions */
729 void
730 ta_latin_metrics_scale(TA_LatinMetrics metrics,
731 TA_Scaler scaler)
733 metrics->root.scaler.render_mode = scaler->render_mode;
734 metrics->root.scaler.face = scaler->face;
735 metrics->root.scaler.flags = scaler->flags;
737 ta_latin_metrics_scale_dim(metrics, scaler, TA_DIMENSION_HORZ);
738 ta_latin_metrics_scale_dim(metrics, scaler, TA_DIMENSION_VERT);
742 /* walk over all contours and compute its segments */
744 FT_Error
745 ta_latin_hints_compute_segments(TA_GlyphHints hints,
746 TA_Dimension dim)
748 TA_AxisHints axis = &hints->axis[dim];
749 FT_Error error = FT_Err_Ok;
751 TA_Segment segment = NULL;
752 TA_SegmentRec seg0;
754 TA_Point* contour = hints->contours;
755 TA_Point* contour_limit = contour + hints->num_contours;
756 TA_Direction major_dir, segment_dir;
759 memset(&seg0, 0, sizeof (TA_SegmentRec));
760 seg0.score = 32000;
761 seg0.flags = TA_EDGE_NORMAL;
763 major_dir = (TA_Direction)TA_ABS(axis->major_dir);
764 segment_dir = major_dir;
766 axis->num_segments = 0;
768 /* set up (u,v) in each point */
769 if (dim == TA_DIMENSION_HORZ)
771 TA_Point point = hints->points;
772 TA_Point limit = point + hints->num_points;
775 for (; point < limit; point++)
777 point->u = point->fx;
778 point->v = point->fy;
781 else
783 TA_Point point = hints->points;
784 TA_Point limit = point + hints->num_points;
787 for (; point < limit; point++)
789 point->u = point->fy;
790 point->v = point->fx;
794 /* do each contour separately */
795 for (; contour < contour_limit; contour++)
797 TA_Point point = contour[0];
798 TA_Point last = point->prev;
800 int on_edge = 0;
802 FT_Pos min_pos = 32000; /* minimum segment pos != min_coord */
803 FT_Pos max_pos = -32000; /* maximum segment pos != max_coord */
804 FT_Bool passed;
807 if (point == last) /* skip singletons -- just in case */
808 continue;
810 if (TA_ABS(last->out_dir) == major_dir
811 && TA_ABS(point->out_dir) == major_dir)
813 /* we are already on an edge, try to locate its start */
814 last = point;
816 for (;;)
818 point = point->prev;
819 if (TA_ABS(point->out_dir) != major_dir)
821 point = point->next;
822 break;
824 if (point == last)
825 break;
829 last = point;
830 passed = 0;
832 for (;;)
834 FT_Pos u, v;
837 if (on_edge)
839 u = point->u;
840 if (u < min_pos)
841 min_pos = u;
842 if (u > max_pos)
843 max_pos = u;
845 if (point->out_dir != segment_dir
846 || point == last)
848 /* we are just leaving an edge; record a new segment! */
849 segment->last = point;
850 segment->pos = (FT_Short)((min_pos + max_pos) >> 1);
852 /* a segment is round if either its first or last point */
853 /* is a control point */
854 if ((segment->first->flags | point->flags) & TA_FLAG_CONTROL)
855 segment->flags |= TA_EDGE_ROUND;
857 /* compute segment size */
858 min_pos = max_pos = point->v;
860 v = segment->first->v;
861 if (v < min_pos)
862 min_pos = v;
863 if (v > max_pos)
864 max_pos = v;
866 segment->min_coord = (FT_Short)min_pos;
867 segment->max_coord = (FT_Short)max_pos;
868 segment->height = (FT_Short)(segment->max_coord -
869 segment->min_coord);
871 on_edge = 0;
872 segment = NULL;
873 /* fall through */
877 /* now exit if we are at the start/end point */
878 if (point == last)
880 if (passed)
881 break;
882 passed = 1;
885 if (!on_edge
886 && TA_ABS(point->out_dir) == major_dir)
888 /* this is the start of a new segment! */
889 segment_dir = (TA_Direction)point->out_dir;
891 /* clear all segment fields */
892 error = ta_axis_hints_new_segment(axis, &segment);
893 if (error)
894 goto Exit;
896 segment[0] = seg0;
897 segment->dir = (FT_Char)segment_dir;
898 min_pos = max_pos = point->u;
899 segment->first = point;
900 segment->last = point;
901 on_edge = 1;
904 point = point->next;
906 } /* contours */
909 /* now slightly increase the height of segments if this makes sense -- */
910 /* this is used to better detect and ignore serifs */
912 TA_Segment segments = axis->segments;
913 TA_Segment segments_end = segments + axis->num_segments;
916 for (segment = segments; segment < segments_end; segment++)
918 TA_Point first = segment->first;
919 TA_Point last = segment->last;
921 FT_Pos first_v = first->v;
922 FT_Pos last_v = last->v;
925 if (first == last)
926 continue;
928 if (first_v < last_v)
930 TA_Point p;
933 p = first->prev;
934 if (p->v < first_v)
935 segment->height = (FT_Short)(segment->height +
936 ((first_v - p->v) >> 1));
938 p = last->next;
939 if (p->v > last_v)
940 segment->height = (FT_Short)(segment->height +
941 ((p->v - last_v) >> 1));
943 else
945 TA_Point p;
948 p = first->prev;
949 if (p->v > first_v)
950 segment->height = (FT_Short)(segment->height +
951 ((p->v - first_v) >> 1));
953 p = last->next;
954 if (p->v < last_v)
955 segment->height = (FT_Short)(segment->height +
956 ((last_v - p->v) >> 1));
961 Exit:
962 return error;
966 /* link segments to form stems and serifs */
968 void
969 ta_latin_hints_link_segments(TA_GlyphHints hints,
970 TA_Dimension dim)
972 TA_AxisHints axis = &hints->axis[dim];
974 TA_Segment segments = axis->segments;
975 TA_Segment segment_limit = segments + axis->num_segments;
977 FT_Pos len_threshold, len_score;
978 TA_Segment seg1, seg2;
981 len_threshold = TA_LATIN_CONSTANT(hints->metrics, 8);
982 if (len_threshold == 0)
983 len_threshold = 1;
985 len_score = TA_LATIN_CONSTANT(hints->metrics, 6000);
987 /* now compare each segment to the others */
988 for (seg1 = segments; seg1 < segment_limit; seg1++)
990 /* the fake segments are introduced to hint the metrics -- */
991 /* we must never link them to anything */
992 if (seg1->dir != axis->major_dir
993 || seg1->first == seg1->last)
994 continue;
996 /* search for stems having opposite directions, */
997 /* with seg1 to the `left' of seg2 */
998 for (seg2 = segments; seg2 < segment_limit; seg2++)
1000 FT_Pos pos1 = seg1->pos;
1001 FT_Pos pos2 = seg2->pos;
1004 if (seg1->dir + seg2->dir == 0
1005 && pos2 > pos1)
1007 /* compute distance between the two segments */
1008 FT_Pos dist = pos2 - pos1;
1009 FT_Pos min = seg1->min_coord;
1010 FT_Pos max = seg1->max_coord;
1011 FT_Pos len, score;
1014 if (min < seg2->min_coord)
1015 min = seg2->min_coord;
1016 if (max > seg2->max_coord)
1017 max = seg2->max_coord;
1019 /* compute maximum coordinate difference of the two segments */
1020 len = max - min;
1021 if (len >= len_threshold)
1023 /* small coordinate differences cause a higher score, and */
1024 /* segments with a greater distance cause a higher score also */
1025 score = dist + len_score / len;
1027 /* and we search for the smallest score */
1028 /* of the sum of the two values */
1029 if (score < seg1->score)
1031 seg1->score = score;
1032 seg1->link = seg2;
1035 if (score < seg2->score)
1037 seg2->score = score;
1038 seg2->link = seg1;
1045 /* now compute the `serif' segments, cf. explanations in `tahints.h' */
1046 for (seg1 = segments; seg1 < segment_limit; seg1++)
1048 seg2 = seg1->link;
1050 if (seg2)
1052 if (seg2->link != seg1)
1054 seg1->link = 0;
1055 seg1->serif = seg2->link;
1062 /* link segments to edges, using feature analysis for selection */
1064 FT_Error
1065 ta_latin_hints_compute_edges(TA_GlyphHints hints,
1066 TA_Dimension dim)
1068 TA_AxisHints axis = &hints->axis[dim];
1069 FT_Error error = FT_Err_Ok;
1070 TA_LatinAxis laxis = &((TA_LatinMetrics)hints->metrics)->axis[dim];
1072 TA_Segment segments = axis->segments;
1073 TA_Segment segment_limit = segments + axis->num_segments;
1074 TA_Segment seg;
1076 #if 0
1077 TA_Direction up_dir;
1078 #endif
1079 FT_Fixed scale;
1080 FT_Pos edge_distance_threshold;
1081 FT_Pos segment_length_threshold;
1084 axis->num_edges = 0;
1086 scale = (dim == TA_DIMENSION_HORZ) ? hints->x_scale
1087 : hints->y_scale;
1089 #if 0
1090 up_dir = (dim == TA_DIMENSION_HORZ) ? TA_DIR_UP
1091 : TA_DIR_RIGHT;
1092 #endif
1094 /* we ignore all segments that are less than 1 pixel in length */
1095 /* to avoid many problems with serif fonts */
1096 /* (the corresponding threshold is computed in font units) */
1097 if (dim == TA_DIMENSION_HORZ)
1098 segment_length_threshold = FT_DivFix(64, hints->y_scale);
1099 else
1100 segment_length_threshold = 0;
1102 /********************************************************************/
1103 /* */
1104 /* We begin by generating a sorted table of edges for the current */
1105 /* direction. To do so, we simply scan each segment and try to find */
1106 /* an edge in our table that corresponds to its position. */
1107 /* */
1108 /* If no edge is found, we create and insert a new edge in the */
1109 /* sorted table. Otherwise, we simply add the segment to the edge's */
1110 /* list which gets processed in the second step to compute the */
1111 /* edge's properties. */
1112 /* */
1113 /* Note that the table of edges is sorted along the segment/edge */
1114 /* position. */
1115 /* */
1116 /********************************************************************/
1118 /* assure that edge distance threshold is at most 0.25px */
1119 edge_distance_threshold = FT_MulFix(laxis->edge_distance_threshold,
1120 scale);
1121 if (edge_distance_threshold > 64 / 4)
1122 edge_distance_threshold = 64 / 4;
1124 edge_distance_threshold = FT_DivFix(edge_distance_threshold,
1125 scale);
1127 for (seg = segments; seg < segment_limit; seg++)
1129 TA_Edge found = NULL;
1130 FT_Int ee;
1133 if (seg->height < segment_length_threshold)
1134 continue;
1136 /* a special case for serif edges: */
1137 /* if they are smaller than 1.5 pixels we ignore them */
1138 if (seg->serif
1139 && 2 * seg->height < 3 * segment_length_threshold)
1140 continue;
1142 /* look for an edge corresponding to the segment */
1143 for (ee = 0; ee < axis->num_edges; ee++)
1145 TA_Edge edge = axis->edges + ee;
1146 FT_Pos dist;
1149 dist = seg->pos - edge->fpos;
1150 if (dist < 0)
1151 dist = -dist;
1153 if (dist < edge_distance_threshold && edge->dir == seg->dir)
1155 found = edge;
1156 break;
1160 if (!found)
1162 TA_Edge edge;
1165 /* insert a new edge in the list and sort according to the position */
1166 error = ta_axis_hints_new_edge(axis, seg->pos,
1167 (TA_Direction)seg->dir,
1168 &edge);
1169 if (error)
1170 goto Exit;
1172 /* add the segment to the new edge's list */
1173 memset(edge, 0, sizeof (TA_EdgeRec));
1174 edge->first = seg;
1175 edge->last = seg;
1176 edge->dir = seg->dir;
1177 edge->fpos = seg->pos;
1178 edge->opos = FT_MulFix(seg->pos, scale);
1179 edge->pos = edge->opos;
1180 seg->edge_next = seg;
1182 else
1184 /* if an edge was found, simply add the segment to the edge's list */
1185 seg->edge_next = found->first;
1186 found->last->edge_next = seg;
1187 found->last = seg;
1191 /*****************************************************************/
1192 /* */
1193 /* Good, we now compute each edge's properties according to */
1194 /* the segments found on its position. Basically, these are */
1195 /* */
1196 /* - the edge's main direction */
1197 /* - stem edge, serif edge or both (which defaults to stem then) */
1198 /* - rounded edge, straight or both (which defaults to straight) */
1199 /* - link for edge */
1200 /* */
1201 /*****************************************************************/
1203 /* first of all, set the `edge' field in each segment -- this is */
1204 /* required in order to compute edge links */
1206 /* note that removing this loop and setting the `edge' field of each */
1207 /* segment directly in the code above slows down execution speed for */
1208 /* some reasons on platforms like the Sun */
1210 TA_Edge edges = axis->edges;
1211 TA_Edge edge_limit = edges + axis->num_edges;
1212 TA_Edge edge;
1215 for (edge = edges; edge < edge_limit; edge++)
1217 seg = edge->first;
1218 if (seg)
1221 seg->edge = edge;
1222 seg = seg->edge_next;
1223 } while (seg != edge->first);
1226 /* now compute each edge properties */
1227 for (edge = edges; edge < edge_limit; edge++)
1229 FT_Int is_round = 0; /* does it contain round segments? */
1230 FT_Int is_straight = 0; /* does it contain straight segments? */
1231 #if 0
1232 FT_Pos ups = 0; /* number of upwards segments */
1233 FT_Pos downs = 0; /* number of downwards segments */
1234 #endif
1237 seg = edge->first;
1241 FT_Bool is_serif;
1244 /* check for roundness of segment */
1245 if (seg->flags & TA_EDGE_ROUND)
1246 is_round++;
1247 else
1248 is_straight++;
1250 #if 0
1251 /* check for segment direction */
1252 if (seg->dir == up_dir)
1253 ups += seg->max_coord - seg->min_coord;
1254 else
1255 downs += seg->max_coord - seg->min_coord;
1256 #endif
1258 /* check for links -- */
1259 /* if seg->serif is set, then seg->link must be ignored */
1260 is_serif = (FT_Bool)(seg->serif
1261 && seg->serif->edge
1262 && seg->serif->edge != edge);
1264 if ((seg->link && seg->link->edge != NULL)
1265 || is_serif)
1267 TA_Edge edge2;
1268 TA_Segment seg2;
1271 edge2 = edge->link;
1272 seg2 = seg->link;
1274 if (is_serif)
1276 seg2 = seg->serif;
1277 edge2 = edge->serif;
1280 if (edge2)
1282 FT_Pos edge_delta;
1283 FT_Pos seg_delta;
1286 edge_delta = edge->fpos - edge2->fpos;
1287 if (edge_delta < 0)
1288 edge_delta = -edge_delta;
1290 seg_delta = seg->pos - seg2->pos;
1291 if (seg_delta < 0)
1292 seg_delta = -seg_delta;
1294 if (seg_delta < edge_delta)
1295 edge2 = seg2->edge;
1297 else
1298 edge2 = seg2->edge;
1300 if (is_serif)
1302 edge->serif = edge2;
1303 edge2->flags |= TA_EDGE_SERIF;
1305 else
1306 edge->link = edge2;
1309 seg = seg->edge_next;
1310 } while (seg != edge->first);
1312 /* set the round/straight flags */
1313 edge->flags = TA_EDGE_NORMAL;
1315 if (is_round > 0
1316 && is_round >= is_straight)
1317 edge->flags |= TA_EDGE_ROUND;
1319 #if 0
1320 /* set the edge's main direction */
1321 edge->dir = TA_DIR_NONE;
1323 if (ups > downs)
1324 edge->dir = (FT_Char)up_dir;
1326 else if (ups < downs)
1327 edge->dir = (FT_Char)-up_dir;
1329 else if (ups == downs)
1330 edge->dir = 0; /* both up and down! */
1331 #endif
1333 /* get rid of serifs if link is set */
1334 /* XXX: this gets rid of many unpleasant artefacts! */
1335 /* example: the `c' in cour.pfa at size 13 */
1337 if (edge->serif && edge->link)
1338 edge->serif = 0;
1342 Exit:
1343 return error;
1347 /* detect segments and edges for given dimension */
1349 FT_Error
1350 ta_latin_hints_detect_features(TA_GlyphHints hints,
1351 TA_Dimension dim)
1353 FT_Error error;
1356 error = ta_latin_hints_compute_segments(hints, dim);
1357 if (!error)
1359 ta_latin_hints_link_segments(hints, dim);
1361 error = ta_latin_hints_compute_edges(hints, dim);
1364 return error;
1368 /* compute all edges which lie within blue zones */
1370 void
1371 ta_latin_hints_compute_blue_edges(TA_GlyphHints hints,
1372 TA_LatinMetrics metrics)
1374 TA_AxisHints axis = &hints->axis[TA_DIMENSION_VERT];
1376 TA_Edge edge = axis->edges;
1377 TA_Edge edge_limit = edge + axis->num_edges;
1379 TA_LatinAxis latin = &metrics->axis[TA_DIMENSION_VERT];
1380 FT_Fixed scale = latin->scale;
1383 /* compute which blue zones are active, */
1384 /* i.e. have their scaled size < 3/4 pixels */
1386 /* for each horizontal edge search the blue zone which is closest */
1387 for (; edge < edge_limit; edge++)
1389 FT_Int bb;
1390 TA_Width best_blue = NULL;
1391 FT_Pos best_dist; /* initial threshold */
1393 FT_UInt best_blue_idx = 0;
1394 FT_Bool best_blue_is_shoot = 0;
1397 /* compute the initial threshold as a fraction of the EM size */
1398 /* (the value 40 is heuristic) */
1399 best_dist = FT_MulFix(metrics->units_per_em / 40, scale);
1401 /* assure a minimum distance of 0.5px */
1402 if (best_dist > 64 / 2)
1403 best_dist = 64 / 2;
1405 for (bb = 0; bb < TA_LATIN_BLUE_MAX; bb++)
1407 TA_LatinBlue blue = latin->blues + bb;
1408 FT_Bool is_top_blue, is_major_dir;
1411 /* skip inactive blue zones (i.e., those that are too large) */
1412 if (!(blue->flags & TA_LATIN_BLUE_ACTIVE))
1413 continue;
1415 /* if it is a top zone, check for right edges -- */
1416 /* if it is a bottom zone, check for left edges */
1417 is_top_blue = (FT_Byte)((blue->flags & TA_LATIN_BLUE_TOP) != 0);
1418 is_major_dir = FT_BOOL(edge->dir == axis->major_dir);
1420 /* if it is a top zone, the edge must be against the major */
1421 /* direction; if it is a bottom zone, it must be in the major */
1422 /* direction */
1423 if (is_top_blue ^ is_major_dir)
1425 FT_Pos dist;
1428 /* first of all, compare it to the reference position */
1429 dist = edge->fpos - blue->ref.org;
1430 if (dist < 0)
1431 dist = -dist;
1433 dist = FT_MulFix(dist, scale);
1434 if (dist < best_dist)
1436 best_dist = dist;
1437 best_blue = &blue->ref;
1439 best_blue_idx = bb;
1440 best_blue_is_shoot = 0;
1443 /* now compare it to the overshoot position and check whether */
1444 /* the edge is rounded, and whether the edge is over the */
1445 /* reference position of a top zone, or under the reference */
1446 /* position of a bottom zone */
1447 if (edge->flags & TA_EDGE_ROUND
1448 && dist != 0)
1450 FT_Bool is_under_ref = FT_BOOL(edge->fpos < blue->ref.org);
1453 if (is_top_blue ^ is_under_ref)
1455 dist = edge->fpos - blue->shoot.org;
1456 if (dist < 0)
1457 dist = -dist;
1459 dist = FT_MulFix(dist, scale);
1460 if (dist < best_dist)
1462 best_dist = dist;
1463 best_blue = &blue->shoot;
1465 best_blue_idx = bb;
1466 best_blue_is_shoot = 1;
1473 if (best_blue)
1475 edge->blue_edge = best_blue;
1476 edge->best_blue_idx = best_blue_idx;
1477 edge->best_blue_is_shoot = best_blue_is_shoot;
1483 /* initalize hinting engine */
1485 static FT_Error
1486 ta_latin_hints_init(TA_GlyphHints hints,
1487 TA_LatinMetrics metrics)
1489 FT_Render_Mode mode;
1490 FT_UInt32 scaler_flags, other_flags;
1491 FT_Face face = metrics->root.scaler.face;
1494 ta_glyph_hints_rescale(hints, (TA_ScriptMetrics)metrics);
1496 /* correct x_scale and y_scale if needed, since they may have */
1497 /* been modified by `ta_latin_metrics_scale_dim' above */
1498 hints->x_scale = metrics->axis[TA_DIMENSION_HORZ].scale;
1499 hints->x_delta = metrics->axis[TA_DIMENSION_HORZ].delta;
1500 hints->y_scale = metrics->axis[TA_DIMENSION_VERT].scale;
1501 hints->y_delta = metrics->axis[TA_DIMENSION_VERT].delta;
1503 /* compute flags depending on render mode, etc. */
1504 mode = metrics->root.scaler.render_mode;
1506 #if 0 /* #ifdef TA_CONFIG_OPTION_USE_WARPER */
1507 if (mode == FT_RENDER_MODE_LCD
1508 || mode == FT_RENDER_MODE_LCD_V)
1509 metrics->root.scaler.render_mode =
1510 mode = FT_RENDER_MODE_NORMAL;
1511 #endif
1513 scaler_flags = hints->scaler_flags;
1514 other_flags = 0;
1516 /* we snap the width of vertical stems for the monochrome */
1517 /* and horizontal LCD rendering targets only */
1518 if (mode == FT_RENDER_MODE_MONO
1519 || mode == FT_RENDER_MODE_LCD)
1520 other_flags |= TA_LATIN_HINTS_HORZ_SNAP;
1522 /* we snap the width of horizontal stems for the monochrome */
1523 /* and vertical LCD rendering targets only */
1524 if (mode == FT_RENDER_MODE_MONO
1525 || mode == FT_RENDER_MODE_LCD_V)
1526 other_flags |= TA_LATIN_HINTS_VERT_SNAP;
1528 /* we adjust stems to full pixels only if we don't use the `light' mode */
1529 if (mode != FT_RENDER_MODE_LIGHT)
1530 other_flags |= TA_LATIN_HINTS_STEM_ADJUST;
1532 if (mode == FT_RENDER_MODE_MONO)
1533 other_flags |= TA_LATIN_HINTS_MONO;
1535 /* in `light' hinting mode we disable horizontal hinting completely; */
1536 /* we also do it if the face is italic */
1537 if (mode == FT_RENDER_MODE_LIGHT
1538 || (face->style_flags & FT_STYLE_FLAG_ITALIC) != 0)
1539 scaler_flags |= TA_SCALER_FLAG_NO_HORIZONTAL;
1541 hints->scaler_flags = scaler_flags;
1542 hints->other_flags = other_flags;
1544 return FT_Err_Ok;
1548 /* snap a given width in scaled coordinates to */
1549 /* one of the current standard widths */
1551 static FT_Pos
1552 ta_latin_snap_width(TA_Width widths,
1553 FT_Int count,
1554 FT_Pos width)
1556 int n;
1557 FT_Pos best = 64 + 32 + 2;
1558 FT_Pos reference = width;
1559 FT_Pos scaled;
1562 for (n = 0; n < count; n++)
1564 FT_Pos w;
1565 FT_Pos dist;
1568 w = widths[n].cur;
1569 dist = width - w;
1570 if (dist < 0)
1571 dist = -dist;
1572 if (dist < best)
1574 best = dist;
1575 reference = w;
1579 scaled = TA_PIX_ROUND(reference);
1581 if (width >= reference)
1583 if (width < scaled + 48)
1584 width = reference;
1586 else
1588 if (width > scaled - 48)
1589 width = reference;
1592 return width;
1596 /* compute the snapped width of a given stem, ignoring very thin ones */
1598 /* there is a lot of voodoo in this function; changing the hard-coded */
1599 /* parameters influence the whole hinting process */
1601 static FT_Pos
1602 ta_latin_compute_stem_width(TA_GlyphHints hints,
1603 TA_Dimension dim,
1604 FT_Pos width,
1605 FT_Byte base_flags,
1606 FT_Byte stem_flags)
1608 TA_LatinMetrics metrics = (TA_LatinMetrics) hints->metrics;
1609 TA_LatinAxis axis = &metrics->axis[dim];
1611 FT_Pos dist = width;
1612 FT_Int sign = 0;
1613 FT_Int vertical = (dim == TA_DIMENSION_VERT);
1616 if (!TA_LATIN_HINTS_DO_STEM_ADJUST(hints)
1617 || axis->extra_light)
1618 return width;
1620 if (dist < 0)
1622 dist = -width;
1623 sign = 1;
1626 if ((vertical && !TA_LATIN_HINTS_DO_VERT_SNAP(hints))
1627 || (!vertical && !TA_LATIN_HINTS_DO_HORZ_SNAP(hints)))
1629 /* smooth hinting process: very lightly quantize the stem width */
1631 /* leave the widths of serifs alone */
1632 if ((stem_flags & TA_EDGE_SERIF)
1633 && vertical
1634 && (dist < 3 * 64))
1635 goto Done_Width;
1636 else if (base_flags & TA_EDGE_ROUND)
1638 if (dist < 80)
1639 dist = 64;
1641 else if (dist < 56)
1642 dist = 56;
1644 if (axis->width_count > 0)
1646 FT_Pos delta;
1649 /* compare to standard width */
1650 delta = dist - axis->widths[0].cur;
1652 if (delta < 0)
1653 delta = -delta;
1655 if (delta < 40)
1657 dist = axis->widths[0].cur;
1658 if (dist < 48)
1659 dist = 48;
1661 goto Done_Width;
1664 if (dist < 3 * 64)
1666 delta = dist & 63;
1667 dist &= -64;
1669 if (delta < 10)
1670 dist += delta;
1671 else if (delta < 32)
1672 dist += 10;
1673 else if (delta < 54)
1674 dist += 54;
1675 else
1676 dist += delta;
1678 else
1679 dist = (dist + 32) & ~63;
1682 else
1684 /* strong hinting process: snap the stem width to integer pixels */
1686 FT_Pos org_dist = dist;
1689 dist = ta_latin_snap_width(axis->widths, axis->width_count, dist);
1691 if (vertical)
1693 /* in the case of vertical hinting, */
1694 /* always round the stem heights to integer pixels */
1696 if (dist >= 64)
1697 dist = (dist + 16) & ~63;
1698 else
1699 dist = 64;
1701 else
1703 if (TA_LATIN_HINTS_DO_MONO(hints))
1705 /* monochrome horizontal hinting: */
1706 /* snap widths to integer pixels with a different threshold */
1708 if (dist < 64)
1709 dist = 64;
1710 else
1711 dist = (dist + 32) & ~63;
1713 else
1715 /* for horizontal anti-aliased hinting, we adopt a more subtle */
1716 /* approach: we strengthen small stems, round stems whose size */
1717 /* is between 1 and 2 pixels to an integer, otherwise nothing */
1719 if (dist < 48)
1720 dist = (dist + 64) >> 1;
1722 else if (dist < 128)
1724 /* we only round to an integer width if the corresponding */
1725 /* distortion is less than 1/4 pixel -- otherwise, this */
1726 /* makes everything worse since the diagonals, which are */
1727 /* not hinted, appear a lot bolder or thinner than the */
1728 /* vertical stems */
1730 FT_Pos delta;
1733 dist = (dist + 22) & ~63;
1734 delta = dist - org_dist;
1735 if (delta < 0)
1736 delta = -delta;
1738 if (delta >= 16)
1740 dist = org_dist;
1741 if (dist < 48)
1742 dist = (dist + 64) >> 1;
1745 else
1746 /* round otherwise to prevent color fringes in LCD mode */
1747 dist = (dist + 32) & ~63;
1752 Done_Width:
1753 if (sign)
1754 dist = -dist;
1756 return dist;
1760 /* align one stem edge relative to the previous stem edge */
1762 static void
1763 ta_latin_align_linked_edge(TA_GlyphHints hints,
1764 TA_Dimension dim,
1765 TA_Edge base_edge,
1766 TA_Edge stem_edge)
1768 FT_Pos dist = stem_edge->opos - base_edge->opos;
1770 FT_Pos fitted_width = ta_latin_compute_stem_width(
1771 hints, dim, dist,
1772 base_edge->flags,
1773 stem_edge->flags);
1776 stem_edge->pos = base_edge->pos + fitted_width;
1778 TA_LOG((" LINK: edge %d (opos=%.2f) linked to %.2f,"
1779 " dist was %.2f, now %.2f\n",
1780 stem_edge - hints->axis[dim].edges, stem_edge->opos / 64.0,
1781 stem_edge->pos / 64.0, dist / 64.0, fitted_width / 64.0));
1783 if (hints->recorder)
1784 hints->recorder(ta_link, hints, dim,
1785 base_edge, stem_edge, NULL, NULL, NULL);
1789 /* shift the coordinates of the `serif' edge by the same amount */
1790 /* as the corresponding `base' edge has been moved already */
1792 static void
1793 ta_latin_align_serif_edge(TA_GlyphHints hints,
1794 TA_Edge base,
1795 TA_Edge serif)
1797 FT_UNUSED(hints);
1799 serif->pos = base->pos + (serif->opos - base->opos);
1803 /* the main grid-fitting routine */
1805 void
1806 ta_latin_hint_edges(TA_GlyphHints hints,
1807 TA_Dimension dim)
1809 TA_AxisHints axis = &hints->axis[dim];
1811 TA_Edge edges = axis->edges;
1812 TA_Edge edge_limit = edges + axis->num_edges;
1813 FT_PtrDist n_edges;
1814 TA_Edge edge;
1816 TA_Edge anchor = NULL;
1817 FT_Int has_serifs = 0;
1820 TA_LOG(("%s edge hinting\n", dim == TA_DIMENSION_VERT ? "horizontal"
1821 : "vertical"));
1823 /* we begin by aligning all stems relative to the blue zone if needed -- */
1824 /* that's only for horizontal edges */
1826 if (dim == TA_DIMENSION_VERT
1827 && TA_HINTS_DO_BLUES(hints))
1829 for (edge = edges; edge < edge_limit; edge++)
1831 TA_Width blue;
1832 TA_Edge edge1, edge2; /* these edges form the stem to check */
1835 if (edge->flags & TA_EDGE_DONE)
1836 continue;
1838 blue = edge->blue_edge;
1839 edge1 = NULL;
1840 edge2 = edge->link;
1842 if (blue)
1843 edge1 = edge;
1845 /* flip edges if the other stem is aligned to a blue zone */
1846 else if (edge2 && edge2->blue_edge)
1848 blue = edge2->blue_edge;
1849 edge1 = edge2;
1850 edge2 = edge;
1853 if (!edge1)
1854 continue;
1856 if (!anchor)
1857 TA_LOG((" BLUE_ANCHOR: edge %d (opos=%.2f) snapped to %.2f,"
1858 " was %.2f (anchor=edge %d)\n",
1859 edge1 - edges, edge1->opos / 64.0, blue->fit / 64.0,
1860 edge1->pos / 64.0, edge - edges));
1861 else
1862 TA_LOG((" BLUE: edge %d (opos=%.2f) snapped to %.2f, was %.2f\n",
1863 edge1 - edges, edge1->opos / 64.0, blue->fit / 64.0,
1864 edge1->pos / 64.0));
1866 edge1->pos = blue->fit;
1867 edge1->flags |= TA_EDGE_DONE;
1869 if (hints->recorder)
1871 if (!anchor)
1872 hints->recorder(ta_blue_anchor, hints, dim,
1873 edge1, edge, NULL, NULL, NULL);
1874 else
1875 hints->recorder(ta_blue, hints, dim,
1876 edge1, NULL, NULL, NULL, NULL);
1879 if (edge2 && !edge2->blue_edge)
1881 ta_latin_align_linked_edge(hints, dim, edge1, edge2);
1882 edge2->flags |= TA_EDGE_DONE;
1885 if (!anchor)
1886 anchor = edge;
1890 /* now we align all other stem edges, */
1891 /* trying to maintain the relative order of stems in the glyph */
1892 for (edge = edges; edge < edge_limit; edge++)
1894 TA_Edge edge2;
1897 if (edge->flags & TA_EDGE_DONE)
1898 continue;
1900 /* skip all non-stem edges */
1901 edge2 = edge->link;
1902 if (!edge2)
1904 has_serifs++;
1905 continue;
1908 /* now align the stem */
1910 /* this should not happen, but it's better to be safe */
1911 if (edge2->blue_edge)
1913 TA_LOG((" ASSERTION FAILED for edge %d\n", edge2-edges));
1915 ta_latin_align_linked_edge(hints, dim, edge2, edge);
1916 edge->flags |= TA_EDGE_DONE;
1917 continue;
1920 if (!anchor)
1922 /* if we reach this if clause, no stem has been aligned yet */
1924 FT_Pos org_len, org_center, cur_len;
1925 FT_Pos cur_pos1, error1, error2, u_off, d_off;
1928 org_len = edge2->opos - edge->opos;
1929 cur_len = ta_latin_compute_stem_width(hints, dim, org_len,
1930 edge->flags, edge2->flags);
1932 /* some voodoo to specially round edges for small stem widths; */
1933 /* the idea is to align the center of a stem, */
1934 /* then shifting the stem edges to suitable positions */
1935 if (cur_len <= 64)
1937 /* width <= 1px */
1938 u_off = 32;
1939 d_off = 32;
1941 else
1943 /* 1px < width < 1.5px */
1944 u_off = 38;
1945 d_off = 26;
1948 if (cur_len < 96)
1950 org_center = edge->opos + (org_len >> 1);
1951 cur_pos1 = TA_PIX_ROUND(org_center);
1953 error1 = org_center - (cur_pos1 - u_off);
1954 if (error1 < 0)
1955 error1 = -error1;
1957 error2 = org_center - (cur_pos1 + d_off);
1958 if (error2 < 0)
1959 error2 = -error2;
1961 if (error1 < error2)
1962 cur_pos1 -= u_off;
1963 else
1964 cur_pos1 += d_off;
1966 edge->pos = cur_pos1 - cur_len / 2;
1967 edge2->pos = edge->pos + cur_len;
1969 else
1970 edge->pos = TA_PIX_ROUND(edge->opos);
1972 anchor = edge;
1973 edge->flags |= TA_EDGE_DONE;
1975 TA_LOG((" ANCHOR: edge %d (opos=%.2f) and %d (opos=%.2f)"
1976 " snapped to %.2f and %.2f\n",
1977 edge - edges, edge->opos / 64.0,
1978 edge2 - edges, edge2->opos / 64.0,
1979 edge->pos / 64.0, edge2->pos / 64.0));
1981 if (hints->recorder)
1982 hints->recorder(ta_anchor, hints, dim,
1983 edge, edge2, NULL, NULL, NULL);
1985 ta_latin_align_linked_edge(hints, dim, edge, edge2);
1987 else
1989 FT_Pos org_pos, org_len, org_center, cur_len;
1990 FT_Pos cur_pos1, cur_pos2, delta1, delta2;
1993 org_pos = anchor->pos + (edge->opos - anchor->opos);
1994 org_len = edge2->opos - edge->opos;
1995 org_center = org_pos + (org_len >> 1);
1997 cur_len = ta_latin_compute_stem_width(hints, dim, org_len,
1998 edge->flags, edge2->flags);
2000 if (edge2->flags & TA_EDGE_DONE)
2002 TA_LOG((" ADJUST: edge %d (pos=%.2f) moved to %.2f\n",
2003 edge - edges, edge->pos / 64.0,
2004 (edge2->pos - cur_len) / 64.0));
2006 edge->pos = edge2->pos - cur_len;
2008 if (hints->recorder)
2010 TA_Edge bound = NULL;
2013 if (edge > edges)
2014 bound = &edge[-1];
2016 hints->recorder(ta_adjust, hints, dim,
2017 edge, edge2, NULL, bound, NULL);
2021 else if (cur_len < 96)
2023 FT_Pos u_off, d_off;
2026 cur_pos1 = TA_PIX_ROUND(org_center);
2028 if (cur_len <= 64)
2030 u_off = 32;
2031 d_off = 32;
2033 else
2035 u_off = 38;
2036 d_off = 26;
2039 delta1 = org_center - (cur_pos1 - u_off);
2040 if (delta1 < 0)
2041 delta1 = -delta1;
2043 delta2 = org_center - (cur_pos1 + d_off);
2044 if (delta2 < 0)
2045 delta2 = -delta2;
2047 if (delta1 < delta2)
2048 cur_pos1 -= u_off;
2049 else
2050 cur_pos1 += d_off;
2052 edge->pos = cur_pos1 - cur_len / 2;
2053 edge2->pos = cur_pos1 + cur_len / 2;
2055 TA_LOG((" STEM: edge %d (opos=%.2f) linked to %d (opos=%.2f)"
2056 " snapped to %.2f and %.2f\n",
2057 edge - edges, edge->opos / 64.0,
2058 edge2 - edges, edge2->opos / 64.0,
2059 edge->pos / 64.0, edge2->pos / 64.0));
2061 if (hints->recorder)
2063 TA_Edge bound = NULL;
2066 if (edge > edges)
2067 bound = &edge[-1];
2069 hints->recorder(ta_stem, hints, dim,
2070 edge, edge2, NULL, bound, NULL);
2074 else
2076 org_pos = anchor->pos + (edge->opos - anchor->opos);
2077 org_len = edge2->opos - edge->opos;
2078 org_center = org_pos + (org_len >> 1);
2080 cur_len = ta_latin_compute_stem_width(hints, dim, org_len,
2081 edge->flags, edge2->flags);
2083 cur_pos1 = TA_PIX_ROUND(org_pos);
2084 delta1 = cur_pos1 + (cur_len >> 1) - org_center;
2085 if (delta1 < 0)
2086 delta1 = -delta1;
2088 cur_pos2 = TA_PIX_ROUND(org_pos + org_len) - cur_len;
2089 delta2 = cur_pos2 + (cur_len >> 1) - org_center;
2090 if (delta2 < 0)
2091 delta2 = -delta2;
2093 edge->pos = (delta1 < delta2) ? cur_pos1 : cur_pos2;
2094 edge2->pos = edge->pos + cur_len;
2096 TA_LOG((" STEM: edge %d (opos=%.2f) linked to %d (opos=%.2f)"
2097 " snapped to %.2f and %.2f\n",
2098 edge - edges, edge->opos / 64.0,
2099 edge2 - edges, edge2->opos / 64.0,
2100 edge->pos / 64.0, edge2->pos / 64.0));
2102 if (hints->recorder)
2104 TA_Edge bound = NULL;
2107 if (edge > edges)
2108 bound = &edge[-1];
2110 hints->recorder(ta_stem, hints, dim,
2111 edge, edge2, NULL, bound, NULL);
2115 edge->flags |= TA_EDGE_DONE;
2116 edge2->flags |= TA_EDGE_DONE;
2118 if (edge > edges
2119 && edge->pos < edge[-1].pos)
2121 TA_LOG((" BOUND: edge %d (pos=%.2f) moved to %.2f\n",
2122 edge - edges, edge->pos / 64.0, edge[-1].pos / 64.0));
2124 edge->pos = edge[-1].pos;
2126 if (hints->recorder)
2127 hints->recorder(ta_bound, hints, dim,
2128 edge, &edge[-1], NULL, NULL, NULL);
2133 /* make sure that lowercase m's maintain their symmetry */
2135 /* In general, lowercase m's have six vertical edges if they are sans */
2136 /* serif, or twelve if they are with serifs. This implementation is */
2137 /* based on that assumption, and seems to work very well with most */
2138 /* faces. However, if for a certain face this assumption is not */
2139 /* true, the m is just rendered like before. In addition, any stem */
2140 /* correction will only be applied to symmetrical glyphs (even if the */
2141 /* glyph is not an m), so the potential for unwanted distortion is */
2142 /* relatively low. */
2144 /* we don't handle horizontal edges since we can't easily assure that */
2145 /* the third (lowest) stem aligns with the base line; it might end up */
2146 /* one pixel higher or lower */
2148 n_edges = edge_limit - edges;
2149 if (dim == TA_DIMENSION_HORZ
2150 && (n_edges == 6 || n_edges == 12))
2152 TA_Edge edge1, edge2, edge3;
2153 FT_Pos dist1, dist2, span, delta;
2156 if (n_edges == 6)
2158 edge1 = edges;
2159 edge2 = edges + 2;
2160 edge3 = edges + 4;
2162 else
2164 edge1 = edges + 1;
2165 edge2 = edges + 5;
2166 edge3 = edges + 9;
2169 dist1 = edge2->opos - edge1->opos;
2170 dist2 = edge3->opos - edge2->opos;
2172 span = dist1 - dist2;
2173 if (span < 0)
2174 span = -span;
2176 if (span < 8)
2178 delta = edge3->pos - (2 * edge2->pos - edge1->pos);
2179 edge3->pos -= delta;
2180 if (edge3->link)
2181 edge3->link->pos -= delta;
2183 /* move the serifs along with the stem */
2184 if (n_edges == 12)
2186 (edges + 8)->pos -= delta;
2187 (edges + 11)->pos -= delta;
2190 edge3->flags |= TA_EDGE_DONE;
2191 if (edge3->link)
2192 edge3->link->flags |= TA_EDGE_DONE;
2196 if (has_serifs || !anchor)
2198 /* now hint the remaining edges (serifs and single) */
2199 /* in order to complete our processing */
2200 for (edge = edges; edge < edge_limit; edge++)
2202 TA_Edge lower_bound = NULL;
2203 TA_Edge upper_bound = NULL;
2205 FT_Pos delta;
2208 if (edge->flags & TA_EDGE_DONE)
2209 continue;
2211 delta = 1000;
2213 if (edge->serif)
2215 delta = edge->serif->opos - edge->opos;
2216 if (delta < 0)
2217 delta = -delta;
2220 if (edge > edges)
2221 lower_bound = &edge[-1];
2223 if (edge + 1 < edge_limit
2224 && edge[1].flags & TA_EDGE_DONE)
2225 upper_bound = &edge[1];
2228 if (delta < 64 + 16)
2230 ta_latin_align_serif_edge(hints, edge->serif, edge);
2232 TA_LOG((" SERIF: edge %d (opos=%.2f) serif to %d (opos=%.2f)"
2233 " aligned to %.2f\n",
2234 edge - edges, edge->opos / 64.0,
2235 edge->serif - edges, edge->serif->opos / 64.0,
2236 edge->pos / 64.0));
2238 if (hints->recorder)
2239 hints->recorder(ta_serif, hints, dim,
2240 edge, NULL, NULL, lower_bound, upper_bound);
2242 else if (!anchor)
2244 edge->pos = TA_PIX_ROUND(edge->opos);
2245 anchor = edge;
2247 TA_LOG((" SERIF_ANCHOR: edge %d (opos=%.2f) snapped to %.2f\n",
2248 edge - edges, edge->opos / 64.0, edge->pos / 64.0));
2250 if (hints->recorder)
2251 hints->recorder(ta_serif_anchor, hints, dim,
2252 edge, NULL, NULL, lower_bound, upper_bound);
2254 else
2256 TA_Edge before, after;
2259 for (before = edge - 1; before >= edges; before--)
2260 if (before->flags & TA_EDGE_DONE)
2261 break;
2263 for (after = edge + 1; after < edge_limit; after++)
2264 if (after->flags & TA_EDGE_DONE)
2265 break;
2267 if (before >= edges && before < edge
2268 && after < edge_limit && after > edge)
2270 if (after->opos == before->opos)
2271 edge->pos = before->pos;
2272 else
2273 edge->pos = before->pos + FT_MulDiv(edge->opos - before->opos,
2274 after->pos - before->pos,
2275 after->opos - before->opos);
2277 TA_LOG((" SERIF_LINK1: edge %d (opos=%.2f) snapped to %.2f"
2278 " from %d (opos=%.2f)\n",
2279 edge - edges, edge->opos / 64.0,
2280 edge->pos / 64.0,
2281 before - edges, before->opos / 64.0));
2283 if (hints->recorder)
2284 hints->recorder(ta_serif_link1, hints, dim,
2285 edge, before, after, lower_bound, upper_bound);
2287 else
2289 edge->pos = anchor->pos + ((edge->opos - anchor->opos + 16) & ~31);
2291 TA_LOG((" SERIF_LINK2: edge %d (opos=%.2f) snapped to %.2f\n",
2292 edge - edges, edge->opos / 64.0, edge->pos / 64.0));
2294 if (hints->recorder)
2295 hints->recorder(ta_serif_link2, hints, dim,
2296 edge, NULL, NULL, lower_bound, upper_bound);
2300 edge->flags |= TA_EDGE_DONE;
2302 if (edge > edges
2303 && edge->pos < edge[-1].pos)
2305 TA_LOG((" BOUND: edge %d (pos=%.2f) moved to %.2f\n",
2306 edge - edges, edge->pos / 64.0, edge[-1].pos / 64.0));
2308 edge->pos = edge[-1].pos;
2310 if (hints->recorder)
2311 hints->recorder(ta_bound, hints, dim,
2312 edge, &edge[-1], NULL, NULL, NULL);
2315 if (edge + 1 < edge_limit
2316 && edge[1].flags & TA_EDGE_DONE
2317 && edge->pos > edge[1].pos)
2319 TA_LOG((" BOUND: edge %d (pos=%.2f) moved to %.2f\n",
2320 edge - edges, edge->pos / 64.0, edge[1].pos / 64.0));
2322 edge->pos = edge[1].pos;
2324 if (hints->recorder)
2325 hints->recorder(ta_bound, hints, dim,
2326 edge, &edge[1], NULL, NULL, NULL);
2331 TA_LOG(("\n"));
2335 /* apply the complete hinting algorithm to a latin glyph */
2337 static FT_Error
2338 ta_latin_hints_apply(TA_GlyphHints hints,
2339 FT_Outline* outline,
2340 TA_LatinMetrics metrics)
2342 FT_Error error;
2343 int dim;
2346 error = ta_glyph_hints_reload(hints, outline);
2347 if (error)
2348 goto Exit;
2350 /* analyze glyph outline */
2351 #ifdef TA_CONFIG_OPTION_USE_WARPER
2352 if (metrics->root.scaler.render_mode == FT_RENDER_MODE_LIGHT
2353 || TA_HINTS_DO_HORIZONTAL(hints))
2354 #else
2355 if (TA_HINTS_DO_HORIZONTAL(hints))
2356 #endif
2358 error = ta_latin_hints_detect_features(hints, TA_DIMENSION_HORZ);
2359 if (error)
2360 goto Exit;
2363 if (TA_HINTS_DO_VERTICAL(hints))
2365 error = ta_latin_hints_detect_features(hints, TA_DIMENSION_VERT);
2366 if (error)
2367 goto Exit;
2369 ta_latin_hints_compute_blue_edges(hints, metrics);
2372 /* grid-fit the outline */
2373 for (dim = 0; dim < TA_DIMENSION_MAX; dim++)
2375 #ifdef TA_CONFIG_OPTION_USE_WARPER
2376 if (dim == TA_DIMENSION_HORZ
2377 && metrics->root.scaler.render_mode == FT_RENDER_MODE_LIGHT)
2379 TA_WarperRec warper;
2380 FT_Fixed scale;
2381 FT_Pos delta;
2384 ta_warper_compute(&warper, hints, (TA_Dimension)dim, &scale, &delta);
2385 ta_glyph_hints_scale_dim(hints, (TA_Dimension)dim, scale, delta);
2387 continue;
2389 #endif
2391 if ((dim == TA_DIMENSION_HORZ && TA_HINTS_DO_HORIZONTAL(hints))
2392 || (dim == TA_DIMENSION_VERT && TA_HINTS_DO_VERTICAL(hints)))
2394 ta_latin_hint_edges(hints, (TA_Dimension)dim);
2395 ta_glyph_hints_align_edge_points(hints, (TA_Dimension)dim);
2396 ta_glyph_hints_align_strong_points(hints, (TA_Dimension)dim);
2397 ta_glyph_hints_align_weak_points(hints, (TA_Dimension)dim);
2400 ta_glyph_hints_save(hints, outline);
2402 Exit:
2403 return error;
2407 /* XXX: this should probably fine tuned to differentiate better between */
2408 /* scripts... */
2410 static const TA_Script_UniRangeRec ta_latin_uniranges[] =
2412 TA_UNIRANGE_REC(0x0020UL, 0x007FUL), /* Basic Latin (no control chars) */
2413 TA_UNIRANGE_REC(0x00A0UL, 0x00FFUL), /* Latin-1 Supplement (no control chars) */
2414 TA_UNIRANGE_REC(0x0100UL, 0x017FUL), /* Latin Extended-A */
2415 TA_UNIRANGE_REC(0x0180UL, 0x024FUL), /* Latin Extended-B */
2416 TA_UNIRANGE_REC(0x0250UL, 0x02AFUL), /* IPA Extensions */
2417 TA_UNIRANGE_REC(0x02B0UL, 0x02FFUL), /* Spacing Modifier Letters */
2418 TA_UNIRANGE_REC(0x0300UL, 0x036FUL), /* Combining Diacritical Marks */
2419 TA_UNIRANGE_REC(0x0370UL, 0x03FFUL), /* Greek and Coptic */
2420 TA_UNIRANGE_REC(0x0400UL, 0x04FFUL), /* Cyrillic */
2421 TA_UNIRANGE_REC(0x0500UL, 0x052FUL), /* Cyrillic Supplement */
2422 TA_UNIRANGE_REC(0x1D00UL, 0x1D7FUL), /* Phonetic Extensions */
2423 TA_UNIRANGE_REC(0x1D80UL, 0x1DBFUL), /* Phonetic Extensions Supplement */
2424 TA_UNIRANGE_REC(0x1DC0UL, 0x1DFFUL), /* Combining Diacritical Marks Supplement */
2425 TA_UNIRANGE_REC(0x1E00UL, 0x1EFFUL), /* Latin Extended Additional */
2426 TA_UNIRANGE_REC(0x1F00UL, 0x1FFFUL), /* Greek Extended */
2427 TA_UNIRANGE_REC(0x2000UL, 0x206FUL), /* General Punctuation */
2428 TA_UNIRANGE_REC(0x2070UL, 0x209FUL), /* Superscripts and Subscripts */
2429 TA_UNIRANGE_REC(0x20A0UL, 0x20CFUL), /* Currency Symbols */
2430 TA_UNIRANGE_REC(0x2150UL, 0x218FUL), /* Number Forms */
2431 TA_UNIRANGE_REC(0x2460UL, 0x24FFUL), /* Enclosed Alphanumerics */
2432 TA_UNIRANGE_REC(0x2C60UL, 0x2C7FUL), /* Latin Extended-C */
2433 TA_UNIRANGE_REC(0x2DE0UL, 0x2DFFUL), /* Cyrillic Extended-A */
2434 TA_UNIRANGE_REC(0xA640UL, 0xA69FUL), /* Cyrillic Extended-B */
2435 TA_UNIRANGE_REC(0xA720UL, 0xA7FFUL), /* Latin Extended-D */
2436 TA_UNIRANGE_REC(0xFB00UL, 0xFB06UL), /* Alphab. Present. Forms (Latin Ligs) */
2437 TA_UNIRANGE_REC(0x1D400UL, 0x1D7FFUL), /* Mathematical Alphanumeric Symbols */
2438 TA_UNIRANGE_REC(0UL, 0UL)
2442 const TA_ScriptClassRec ta_latin_script_class =
2444 TA_SCRIPT_LATIN,
2445 ta_latin_uniranges,
2447 sizeof (TA_LatinMetricsRec),
2449 (TA_Script_InitMetricsFunc)ta_latin_metrics_init,
2450 (TA_Script_ScaleMetricsFunc)ta_latin_metrics_scale,
2451 (TA_Script_DoneMetricsFunc)NULL,
2453 (TA_Script_InitHintsFunc)ta_latin_hints_init,
2454 (TA_Script_ApplyHintsFunc)ta_latin_hints_apply
2457 /* end of talatin.c */