From 0c6bd080e8454eaa50f32c210d969ca61af7dc94 Mon Sep 17 00:00:00 2001 From: Werner Lemberg Date: Tue, 30 Sep 2014 11:34:04 +0200 Subject: [PATCH] Implement forced point directions. The change extends the control file syntax. This allows creation of one-point segments to make e.g. isolated points behave as if they were on a horizontal segment. --- doc/ttfautohint-1.pandoc | 6 +- lib/ta.h | 4 ++ lib/tabytecode.c | 11 +++- lib/tacontrol.bison | 150 ++++++++++++++++++++++++++++++++++++++++++++++- lib/tacontrol.c | 132 +++++++++++++++++++++++++++++++++++++++-- lib/tacontrol.flex | 2 +- lib/tacontrol.h | 26 +++++++- lib/tahints.c | 32 ++++++++++ lib/talatin.c | 16 +++++ lib/ttfautohint.h | 32 ++++++++-- 10 files changed, 391 insertions(+), 20 deletions(-) diff --git a/doc/ttfautohint-1.pandoc b/doc/ttfautohint-1.pandoc index daa6dbe..90bfaff 100644 --- a/doc/ttfautohint-1.pandoc +++ b/doc/ttfautohint-1.pandoc @@ -527,9 +527,9 @@ bytecode larger. Both *points* and *ppems* are number ranges, see ['x Height Snapping Exceptions'](#x-height-snapping-exceptions) for the syntax. - *x-shift* and *y-shift* represent floating point numbers that get - rounded to multiples of 1/8 pixels. The entries for `x` and `y` are - optional; if missing, the corresponding value is set to zero. + *x-shift* and *y-shift* represent real numbers that get rounded to + multiples of 1/8 pixels. The entries for `x` and `y` are optional; if + missing, the corresponding value is set to zero. Values for *x-shift* and *y-shift* must be in the range [-1.0;1.0]. Values for *ppems* must be in the range [6;53]. Values for *points* are diff --git a/lib/ta.h b/lib/ta.h index acb7896..6bd80db 100644 --- a/lib/ta.h +++ b/lib/ta.h @@ -236,6 +236,10 @@ struct FONT_ void* control_data_head; void* control_data_cur; + /* two fields for handling forced point directions */ + number_range* control_point_dirs; + number_set_iter control_point_dir_iter; + TA_LoaderRec loader[1]; /* the interface to the autohinter */ /* configuration options */ diff --git a/lib/tabytecode.c b/lib/tabytecode.c index 33c14c9..22eb667 100644 --- a/lib/tabytecode.c +++ b/lib/tabytecode.c @@ -692,15 +692,14 @@ TA_sfnt_build_delta_exceptions(SFNT* sfnt, FT_Bool need_word_counts = 0; FT_Bool allocated = 0; - const Ctrl* ctrl; - num_points = font->loader->gloader->base.outline.n_points; /* loop over all fitting control instructions */ for (;;) { - ctrl = TA_control_get_ctrl(font); + const Ctrl* ctrl = TA_control_get_ctrl(font); + if (!ctrl) break; @@ -2267,6 +2266,12 @@ TA_sfnt_build_glyph_instructions(SFNT* sfnt, if (error) return error; + /* this data is needed for `ta_glyph_hints_reload' (in file `tahints.c') */ + /* to modify `out' directions of points at the user's request */ + error = TA_control_point_dir_collect(font, face->face_index, idx); + if (error) + return error; + #ifdef TA_DEBUG /* temporarily disable some debugging output */ /* to avoid getting the information twice */ diff --git a/lib/tacontrol.bison b/lib/tacontrol.bison index 45cae05..ff35c16 100644 --- a/lib/tacontrol.bison +++ b/lib/tacontrol.bison @@ -93,6 +93,9 @@ store_error_data(const YYLTYPE *locp, %type left_limited %type number_set %type point_set +%type left_dir_set +%type right_dir_set +%type none_dir_set %type ppem_set %type range %type range_elem @@ -167,6 +170,51 @@ entry: YYABORT; } } +| font_idx glyph_idx left_dir_set EOE + { + $entry = TA_control_new(Control_Point_Dir, + $font_idx, + $glyph_idx, + $left_dir_set, + TA_DIR_LEFT, + 0, + NULL); + if (!$entry) + { + store_error_data(&@$, context, TA_Err_Control_Allocation_Error); + YYABORT; + } + } +| font_idx glyph_idx right_dir_set EOE + { + $entry = TA_control_new(Control_Point_Dir, + $font_idx, + $glyph_idx, + $right_dir_set, + TA_DIR_RIGHT, + 0, + NULL); + if (!$entry) + { + store_error_data(&@$, context, TA_Err_Control_Allocation_Error); + YYABORT; + } + } +| font_idx glyph_idx none_dir_set EOE + { + $entry = TA_control_new(Control_Point_Dir, + $font_idx, + $glyph_idx, + $none_dir_set, + TA_DIR_NONE, + 0, + NULL); + if (!$entry) + { + store_error_data(&@$, context, TA_Err_Control_Allocation_Error); + YYABORT; + } + } ; font_idx: @@ -230,7 +278,25 @@ glyph_idx: ; glyph_name: - 'p' + 'l' + { + $glyph_name = strdup("l"); + if ($glyph_name) + { + store_error_data(&@$, context, TA_Err_Control_Allocation_Error); + YYABORT; + } + } +| 'n' + { + $glyph_name = strdup("n"); + if ($glyph_name) + { + store_error_data(&@$, context, TA_Err_Control_Allocation_Error); + YYABORT; + } + } +| 'p' { $glyph_name = strdup("p"); if ($glyph_name) @@ -239,6 +305,15 @@ glyph_name: YYABORT; } } +| 'r' + { + $glyph_name = strdup("r"); + if ($glyph_name) + { + store_error_data(&@$, context, TA_Err_Control_Allocation_Error); + YYABORT; + } + } | 'x' { $glyph_name = strdup("x"); @@ -295,6 +370,79 @@ point_set: { $point_set = $number_set; } ; +left_dir_set: + 'l' + { + FT_Error error; + FT_Face face = context->font->sfnts[context->font_idx].face; + int num_points; + + + error = FT_Load_Glyph(face, context->glyph_idx, FT_LOAD_NO_SCALE); + if (error) + { + store_error_data(&@$, context, TA_Err_Control_Invalid_Glyph); + YYABORT; + } + + num_points = face->glyph->outline.n_points; + + context->number_set_min = 0; + context->number_set_max = num_points - 1; + } + number_set + { $left_dir_set = $number_set; } +; + +right_dir_set: + 'r' + { + FT_Error error; + FT_Face face = context->font->sfnts[context->font_idx].face; + int num_points; + + + error = FT_Load_Glyph(face, context->glyph_idx, FT_LOAD_NO_SCALE); + if (error) + { + store_error_data(&@$, context, TA_Err_Control_Invalid_Glyph); + YYABORT; + } + + num_points = face->glyph->outline.n_points; + + context->number_set_min = 0; + context->number_set_max = num_points - 1; + } + number_set + { $right_dir_set = $number_set; } +; + +none_dir_set: + 'n' + { + FT_Error error; + FT_Face face = context->font->sfnts[context->font_idx].face; + int num_points; + + + error = FT_Load_Glyph(face, context->glyph_idx, FT_LOAD_NO_SCALE); + if (error) + { + store_error_data(&@$, context, TA_Err_Control_Invalid_Glyph); + YYABORT; + } + + num_points = face->glyph->outline.n_points; + + context->number_set_min = 0; + context->number_set_max = num_points - 1; + } + number_set + { $none_dir_set = $number_set; } +; + + x_shift: /* empty */ { $x_shift = 0; } diff --git a/lib/tacontrol.c b/lib/tacontrol.c index 5f955f8..ac51fb3 100644 --- a/lib/tacontrol.c +++ b/lib/tacontrol.c @@ -56,9 +56,9 @@ TA_control_new(Control_Type type, + (y_shift > 0 ? 0.5 : -0.5)); break; - case Control_One_Point_Segment: - /* not implemented yet */ - control->x_shift = 0; + case Control_Point_Dir: + /* values -1, 1, and 4 mean `left', `right', and `none', respectively */ + control->x_shift = x_shift; control->y_shift = 0; break; } @@ -184,8 +184,24 @@ control_show_line(FONT* font, ppems_buf); break; - case Control_One_Point_Segment: - /* not implemented yet */ + case Control_Point_Dir: + /* display glyph index if we don't have a glyph name */ + if (*glyph_name_buf) + s = sdscatprintf(s, "%ld %s %c %s", + control->font_idx, + glyph_name_buf, + control->x_shift == TA_DIR_LEFT ? 'l' + : control->x_shift == TA_DIR_RIGHT ? 'r' + : 'n', + points_buf); + else + s = sdscatprintf(s, "%ld %ld %c %s", + control->font_idx, + control->glyph_idx, + control->x_shift == TA_DIR_LEFT ? 'l' + : control->x_shift == TA_DIR_RIGHT ? 'r' + : 'n', + points_buf); break; } @@ -425,6 +441,7 @@ void TA_control_free_tree(FONT* font) { control_data* control_data_head = (control_data*)font->control_data_head; + number_range* control_point_dirs = font->control_point_dirs; Node* node; Node* next_node; @@ -443,6 +460,7 @@ TA_control_free_tree(FONT* font) } free(control_data_head); + number_set_free(control_point_dirs); } @@ -454,10 +472,14 @@ TA_control_build_tree(FONT* font) int emit_newline = 0; + font->control_point_dirs = NULL; + font->control_point_dir_iter.range = NULL; + /* nothing to do if no data */ if (!control) { font->control_data_head = NULL; + font->control_data_cur = NULL; return TA_Err_Ok; } @@ -482,12 +504,16 @@ TA_control_build_tree(FONT* font) ppems_iter.range = control->ppems; ppem = number_set_get_first(&ppems_iter); + if (type == Control_Point_Dir) + goto Points_Loop; + while (ppems_iter.range) { number_set_iter points_iter; int point_idx; + Points_Loop: points_iter.range = control->points; point_idx = number_set_get_first(&points_iter); @@ -596,4 +622,100 @@ TA_control_get_ctrl(FONT* font) return node ? &node->ctrl : NULL; } + +TA_Error +TA_control_point_dir_collect(FONT* font, + long font_idx, + long glyph_idx) +{ + number_range* control_point_dirs = font->control_point_dirs; + + + /* nothing to do if no data */ + if (!font->control_data_head) + return TA_Err_Ok; + + if (control_point_dirs) + { + number_set_free(control_point_dirs); + control_point_dirs = NULL; + } + + /* + * The PPEM value for one-point segments is always zero; such control + * instructions are thus sorted before other control instructions for the + * same glyph index -- this fits nicely with the call to + * `TA_control_get_next' in the loop of `TA_sfnt_build_delta_exceptions', + * assuring proper sequential access to the red-black tree. + */ + for (;;) + { + const Ctrl* ctrl = TA_control_get_ctrl(font); + number_range* elem; + int point_idx; + + + if (!ctrl) + break; + + /* check type */ + if (ctrl->type != Control_Point_Dir) + break; + + /* too large values of font and glyph indices in `ctrl' */ + /* are handled by later calls of this function */ + if (font_idx < ctrl->font_idx + || glyph_idx < ctrl->glyph_idx) + break; + + /* we store point index and direction together */ + point_idx = (ctrl->point_idx << 2) + + (ctrl->x_shift == TA_DIR_LEFT ? 0 + : ctrl->x_shift == TA_DIR_RIGHT ? 1 + : 2); + + /* don't care about checking valid point indices */ + elem = number_set_new(point_idx, point_idx, point_idx, point_idx); + if (elem == NUMBERSET_ALLOCATION_ERROR) + { + number_set_free(control_point_dirs); + return TA_Err_Control_Allocation_Error; + } + control_point_dirs = number_set_prepend(control_point_dirs, elem); + + TA_control_get_next(font); + } + + font->control_point_dirs = number_set_reverse(control_point_dirs); + + return TA_Err_Ok; +} + + +int +TA_control_point_dir_get_next(FONT* font, + int* point_idx, + TA_Direction* dir) +{ + number_range* control_point_dirs = font->control_point_dirs; + number_set_iter* control_point_dir_iter = &font->control_point_dir_iter; + int pd_idx; + + + if (!control_point_dir_iter->range) + { + control_point_dir_iter->range = control_point_dirs; + pd_idx = number_set_get_first(control_point_dir_iter); + } + else + pd_idx = number_set_get_next(control_point_dir_iter); + + *point_idx = pd_idx >> 2; + *dir = pd_idx % 4 == 0 ? TA_DIR_LEFT + : pd_idx % 4 == 1 ? TA_DIR_RIGHT + : TA_DIR_NONE; + + return control_point_dir_iter->range != NULL; +} + /* end of tacontrol.c */ diff --git a/lib/tacontrol.flex b/lib/tacontrol.flex index 5166cf1..a60f3bd 100644 --- a/lib/tacontrol.flex +++ b/lib/tacontrol.flex @@ -164,7 +164,7 @@ TA_control_scanner_fatal_error(const char* msg, } -(?x: [pxy@,] +(?x: [lnprxy@,] ) { /* delimiters */ return yytext[0]; diff --git a/lib/tacontrol.h b/lib/tacontrol.h index 61cb0f2..cdf19b4 100644 --- a/lib/tacontrol.h +++ b/lib/tacontrol.h @@ -59,7 +59,7 @@ typedef enum Control_Type_ { Control_Delta_before_IUP, Control_Delta_after_IUP, - Control_One_Point_Segment + Control_Point_Dir } Control_Type; @@ -308,9 +308,33 @@ TA_control_get_next(FONT* font); /* * Access control instruction. Return NULL if there is no more data. */ + const Ctrl* TA_control_get_ctrl(FONT* font); + +/* + * Collect forced point directions and store them in + * `font->control_point_dirs'. + */ + +TA_Error +TA_control_point_dir_collect(FONT* font, + long font_idx, + long glyph_idx); + +/* + * Access next forced point direction. Returns 1 on success or 0 if no more + * data. In the latter case, it resets the internal iterator so that + * calling this function another time starts at the beginning again. + */ + +int +TA_control_point_dir_get_next(FONT* font, + int* point_idx, + TA_Direction* dir); + + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/lib/tahints.c b/lib/tahints.c index 324021f..f58f4b3 100644 --- a/lib/tahints.c +++ b/lib/tahints.c @@ -17,6 +17,8 @@ /* heavily modified 2011 by Werner Lemberg */ +#include "ta.h" + #include #include #include "tahints.h" @@ -879,6 +881,36 @@ ta_glyph_hints_reload(TA_GlyphHints hints, } } + /* change some directions at the user's request */ + /* to make ttfautohint insert one-point segments */ + /* or remove points from segments */ + { + FONT* font; + FT_Int idx; + TA_Direction dir; + + + /* `globals' is not set up while initializing metrics, */ + /* so exit early in this case */ + if (!hints->metrics->globals) + goto Exit; + + font = hints->metrics->globals->font; + + /* start conditions are set with `TA_control_point_dir_collect' */ + while (TA_control_point_dir_get_next(font, &idx, &dir)) + { + TA_Point point = &points[idx]; + + + point->out_dir = dir; + if (dir == TA_DIR_NONE) + point->flags |= TA_FLAG_WEAK_INTERPOLATION; + else + point->flags &= ~TA_FLAG_WEAK_INTERPOLATION; + } + } + Exit: return error; } diff --git a/lib/talatin.c b/lib/talatin.c index 42f4028..359dd61 100644 --- a/lib/talatin.c +++ b/lib/talatin.c @@ -1375,6 +1375,22 @@ ta_latin_hints_compute_segments(TA_GlyphHints hints, segment->last = point; on_edge = 1; + + if (point->out_dir != point->next->in_dir) + { + /* we have a one-point segment */ + segment->pos = (FT_Short)min_pos; + + if (point->flags & TA_FLAG_CONTROL) + segment->flags |= TA_EDGE_ROUND; + + segment->min_coord = (FT_Short)min_pos; + segment->max_coord = (FT_Short)max_pos; + segment->height = 0; + + on_edge = 0; + segment = NULL; + } } point = point->next; diff --git a/lib/ttfautohint.h b/lib/ttfautohint.h index 1c2c9d4..34435d3 100644 --- a/lib/ttfautohint.h +++ b/lib/ttfautohint.h @@ -253,10 +253,11 @@ typedef int * : A pointer of type `FILE*` to the data stream of control instructions. * Mutually exclusive with `control-buffer`. * - * An entry in a control instructions file or buffer has the following - * syntax: + * An entry in a control instructions file or buffer has one of the + * following syntax forms: * - * > *\[* font-idx *\]* glyph-id *`p`* points *\[* *`x`* x-shift *\]* *\[* *`y`* y-shift *\]* *`@`* ppems + * > *\[* font-idx *\]*\ \ glyph-id\ \ *`l`|`r`|`n`* points\ + * > *\[* font-idx *\]*\ \ glyph-id\ \ *`p`* points\ \ *\[* *`x`* x-shift *\]*\ \ *\[* *`y`* y-shift *\]*\ \ *`@`* ppems * * *font-idx* gives the index of the font in a TrueType Collection. If * missing, it is set to zero. For normal TrueType fonts, only value @@ -271,12 +272,31 @@ typedef int * can be specified in decimal, octal, or hexadecimal format, the latter * two indicated by the prefixes `0` and `0x`, respectively. * + * The mutually exclusive parameters `l`, `r`, or\ `n` indicate that the + * following points have left, right, or no direction, respectively, + * overriding ttfautohint's algorithm for setting point directions. The + * 'direction of a point' is the direction of the outline controlled by + * this point. By changing a point's direction from 'no direction' to + * either left or right, you can create a one-point segment with the + * given direction so that ttfautohint handles the point similar to + * other segments. Setting a point's direction to 'no direction', + * ttfautohint no longer considers it as part of a segment, thus + * treating it as a 'weak' point. Changed point directions don't + * directly modify the outlines; they only influence the hinting + * process. + * + * Parameter `p` makes ttfautohint apply delta exceptions for the given + * points, shifting the points by the given values. Note that those + * delta exceptions are applied *after* the final `IP` instructions in + * the bytecode; as a consequence, they are (partially) ignored by + * rasterizers if in ClearType mode. + * * Both *points* and *ppems* are number ranges, similar to the * `x-height-snapping-exceptions` syntax. * - * *x-shift* and *y-shift* represent floating point numbers that get - * rounded to multiples of 1/8 pixels. The entries for `x` and `y` are - * optional; if missing, the corresponding value is set to zero. + * *x-shift* and *y-shift* represent real numbers that get rounded to + * multiples of 1/8 pixels. The entries for `x` and `y` are optional; + * if missing, the corresponding value is set to zero. * * Values for *x-shift* and *y-shift* must be in the range [-1.0;1.0]. * Values for *ppems* must be in the range [6;53]. Values for *points* -- 2.11.4.GIT