From 3469d0d038b01c7ddce0fb67da68d79ad2f8b8a6 Mon Sep 17 00:00:00 2001 From: David Turner Date: Wed, 19 Jul 2000 20:02:14 +0000 Subject: [PATCH] added auto-hinter module. Note that the code has been cleaned up, and it seems a bug was introduced ??? I'll start checking this under Linux, as debugging is a lot easier under this environment.. --- src/autohint/CatharonLicense.txt | 123 +++ src/autohint/ahangles.c | 125 +++ src/autohint/ahangles.h | 47 ++ src/autohint/ahglobal.c | 365 +++++++++ src/autohint/ahglobal.h | 38 + src/autohint/ahglyph.c | 1192 +++++++++++++++++++++++++++ src/autohint/ahglyph.h | 79 ++ src/autohint/ahhint.c | 1293 ++++++++++++++++++++++++++++++ src/autohint/ahhint.h | 65 ++ src/autohint/ahloader.h | 103 +++ src/autohint/ahmodule.c | 111 +++ src/autohint/ahmodule.h | 27 + src/autohint/ahoptim.c | 808 +++++++++++++++++++ src/autohint/ahoptim.h | 137 ++++ src/autohint/ahtypes.h | 440 ++++++++++ src/autohint/autohint.c | 40 + src/autohint/mather.py | 54 ++ src/autohint/module.mk | 7 + src/autohint/rules.mk | 86 ++ 19 files changed, 5140 insertions(+) create mode 100644 src/autohint/CatharonLicense.txt create mode 100644 src/autohint/ahangles.c create mode 100644 src/autohint/ahangles.h create mode 100644 src/autohint/ahglobal.c create mode 100644 src/autohint/ahglobal.h create mode 100644 src/autohint/ahglyph.c create mode 100644 src/autohint/ahglyph.h create mode 100644 src/autohint/ahhint.c create mode 100644 src/autohint/ahhint.h create mode 100644 src/autohint/ahloader.h create mode 100644 src/autohint/ahmodule.c create mode 100644 src/autohint/ahmodule.h create mode 100644 src/autohint/ahoptim.c create mode 100644 src/autohint/ahoptim.h create mode 100644 src/autohint/ahtypes.h create mode 100644 src/autohint/autohint.c create mode 100644 src/autohint/mather.py create mode 100644 src/autohint/module.mk create mode 100644 src/autohint/rules.mk diff --git a/src/autohint/CatharonLicense.txt b/src/autohint/CatharonLicense.txt new file mode 100644 index 000000000..d5063ddba --- /dev/null +++ b/src/autohint/CatharonLicense.txt @@ -0,0 +1,123 @@ + The Catharon Open Source LICENSE + ---------------------------- + + 2000-Jul-4 + + Copyright (C) 2000 by Catharon Productions, Inc. + + + +Introduction +============ + + This license applies to source files distributed by Catharon + Productions, Inc. in several archive packages. This license + applies to all files found in such packages which do not fall + under their own explicit license. + + This license was inspired by the BSD, Artistic, and IJG + (Independent JPEG Group) licenses, which all encourage inclusion + and use of free software in commercial and freeware products + alike. As a consequence, its main points are that: + + o We don't promise that this software works. However, we are + interested in any kind of bug reports. (`as is' distribution) + + o You can use this software for whatever you want, in parts or + full form, without having to pay us. (`royalty-free' usage) + + o You may not pretend that you wrote this software. If you use + it, or only parts of it, in a program, you must acknowledge + somewhere in your documentation that you have used the + Catharon Code. (`credits') + + We specifically permit and encourage the inclusion of this + software, with or without modifications, in commercial products. + We disclaim all warranties covering the packages distributed by + Catharon Productions, Inc. and assume no liability related to + their use. + + +Legal Terms +=========== + +0. Definitions +-------------- + + Throughout this license, the terms `Catharon Package', `package', + and `Catharon Code' refer to the set of files originally + distributed by Catharon Productions, Inc. + + `You' refers to the licensee, or person using the project, where + `using' is a generic term including compiling the project's source + code as well as linking it to form a `program' or `executable'. + This program is referred to as `a program using one of the + Catharon Packages'. + + This license applies to all files distributed in the original + Catharon Package(s), including all source code, binaries and + documentation, unless otherwise stated in the file in its + original, unmodified form as distributed in the original archive. + If you are unsure whether or not a particular file is covered by + this license, you must contact us to verify this. + + The Catharon Packages are copyright (C) 2000 by Catharon + Productions, Inc. All rights reserved except as specified below. + +1. No Warranty +-------------- + + THE CATHARON PACKAGES ARE PROVIDED `AS IS' WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OF OR THE INABILITY TO + USE THE CATHARON PACKAGE. + +2. Redistribution +----------------- + + This license grants a worldwide, royalty-free, perpetual and + irrevocable right and license to use, execute, perform, compile, + display, copy, create derivative works of, distribute and + sublicense the Catharon Packages (in both source and object code + forms) and derivative works thereof for any purpose; and to + authorize others to exercise some or all of the rights granted + herein, subject to the following conditions: + + o Redistribution of source code must retain this license file + (`license.txt') unaltered; any additions, deletions or changes + to the original files must be clearly indicated in + accompanying documentation. The copyright notices of the + unaltered, original files must be preserved in all copies of + source files. + + o Redistribution in binary form must provide a disclaimer that + states that the software is based in part on the work of + Catharon Productions, Inc. in the distribution documentation. + + These conditions apply to any software derived from or based on + the Catharon Packages, not just the unmodified files. If you use + our work, you must acknowledge us. However, no fee need be paid + to us. + +3. Advertising +-------------- + + Neither Catharon Productions, Inc. and contributors nor you shall + use the name of the other for commercial, advertising, or + promotional purposes without specific prior written permission. + + We suggest, but do not require, that you use the following phrase + to refer to this software in your documentation: 'this software is + based in part on the Catharon Typography Project'. + + As you have not signed this license, you are not required to + accept it. However, as the Catharon Packages are copyrighted + material, only this license, or another one contracted with the + authors, grants you the right to use, distribute, and modify it. + Therefore, by using, distributing, or modifying the Catharon + Packages, you indicate that you understand and accept all the + terms of this license. + +--- end of license.txt --- diff --git a/src/autohint/ahangles.c b/src/autohint/ahangles.c new file mode 100644 index 000000000..2fb9401ef --- /dev/null +++ b/src/autohint/ahangles.c @@ -0,0 +1,125 @@ +/***************************************************************************/ +/* */ +/* ahangles.h */ +/* */ +/* a routine used to compute vector angles with limited accuracy */ +/* and very high speed. */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ +#ifdef FT_FLAT_COMPILE +#include "ahangles.h" +#else +#include +#endif + +/* the following two tables are automatically generated with */ +/* the "mather.py" Python script.. */ + +static const AH_Angle ah_arctan[ 1L << AH_ATAN_BITS ] = +{ + 0, 0, 1, 1, 1, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 5, + 5, 5, 6, 6, 6, 7, 7, 7, + 8, 8, 8, 9, 9, 9, 10, 10, + 10, 10, 11, 11, 11, 12, 12, 12, + 13, 13, 13, 14, 14, 14, 14, 15, + 15, 15, 16, 16, 16, 17, 17, 17, + 18, 18, 18, 18, 19, 19, 19, 20, + 20, 20, 21, 21, 21, 21, 22, 22, + 22, 23, 23, 23, 24, 24, 24, 24, + 25, 25, 25, 26, 26, 26, 26, 27, + 27, 27, 28, 28, 28, 28, 29, 29, + 29, 30, 30, 30, 30, 31, 31, 31, + 31, 32, 32, 32, 33, 33, 33, 33, + 34, 34, 34, 34, 35, 35, 35, 35, + 36, 36, 36, 36, 37, 37, 37, 38, + 38, 38, 38, 39, 39, 39, 39, 40, + 40, 40, 40, 41, 41, 41, 41, 42, + 42, 42, 42, 42, 43, 43, 43, 43, + 44, 44, 44, 44, 45, 45, 45, 45, + 46, 46, 46, 46, 46, 47, 47, 47, + 47, 48, 48, 48, 48, 48, 49, 49, + 49, 49, 50, 50, 50, 50, 50, 51, + 51, 51, 51, 51, 52, 52, 52, 52, + 52, 53, 53, 53, 53, 53, 54, 54, + 54, 54, 54, 55, 55, 55, 55, 55, + 56, 56, 56, 56, 56, 57, 57, 57, + 57, 57, 57, 58, 58, 58, 58, 58, + 59, 59, 59, 59, 59, 59, 60, 60, + 60, 60, 60, 61, 61, 61, 61, 61, + 61, 62, 62, 62, 62, 62, 62, 63, + 63, 63, 63, 63, 63, 64, 64, 64 +}; + + + LOCAL_FUNC + AH_Angle ah_angle( FT_Vector* v ) + { + FT_Pos dx, dy; + AH_Angle angle; + + dx = v->x; + dy = v->y; + + /* check trivial cases */ + if (dy == 0) + { + angle = 0; + if (dx < 0) + angle = AH_PI; + return angle; + } + else if (dx == 0) + { + angle = AH_HALF_PI; + if (dy < 0) + angle = -AH_HALF_PI; + return angle; + } + + angle = 0; + if ( dx < 0 ) + { + dx = -v->x; + dy = -v->y; + angle = AH_PI; + } + + if ( dy < 0 ) + { + FT_Pos tmp; + tmp = dx; + dx = -dy; + dy = tmp; + angle -= AH_HALF_PI; + } + + if (dx == 0 && dy == 0) + return 0; + + if (dx == dy) + angle += AH_PI/4; + else if (dx > dy) + angle += ah_arctan[ FT_DivFix( dy, dx ) >> (16-AH_ATAN_BITS) ]; + else + angle += AH_HALF_PI - ah_arctan[ FT_DivFix( dx, dy ) >> (16-AH_ATAN_BITS) ]; + + if (angle > AH_PI) + angle -= AH_2PI; + + return angle; + } + + diff --git a/src/autohint/ahangles.h b/src/autohint/ahangles.h new file mode 100644 index 000000000..e24ff4b91 --- /dev/null +++ b/src/autohint/ahangles.h @@ -0,0 +1,47 @@ +/***************************************************************************/ +/* */ +/* ahangles.h */ +/* */ +/* a routine used to compute vector angles with limited accuracy */ +/* and very high speed. */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ +#ifndef AGANGLES_H +#define AGANGLES_H + +#ifdef FT_FLAT_COMPILE +#include "ahtypes.h" +#else +#include +#endif + +#include + +/* PI expressed in ah_angles - we don't really need an important */ +/* precision, so 256 should be enough.. */ +#define AH_PI 256 +#define AH_2PI (AH_PI*2) +#define AH_HALF_PI (AH_PI/2) +#define AH_2PIMASK (AH_2PI-1) + +/* the number of bits to use to express an arc tangent */ +/* see the structure of the lookup table.. */ +#define AH_ATAN_BITS 8 + +LOCAL_DEF const AH_Angle ah_arctan[ 1L << AH_ATAN_BITS ]; + +LOCAL_DEF AH_Angle ah_angle( FT_Vector* v ); + +#endif /* AGANGLES_H */ diff --git a/src/autohint/ahglobal.c b/src/autohint/ahglobal.c new file mode 100644 index 000000000..43bd6ff3c --- /dev/null +++ b/src/autohint/ahglobal.c @@ -0,0 +1,365 @@ +/***************************************************************************/ +/* */ +/* ahglobal.c */ +/* */ +/* routines used to compute global metrics automatically */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ +#ifdef FT_FLAT_COMPILE +#include "ahglobal.h" +#include "ahglyph.h" +#else +#include +#include +#endif + +#define MAX_TEST_CHARACTERS 12 + + static const char* blue_chars[ ah_blue_max ] = + { + "THEZOCQS", + "HEZLOCUS", + "xzroesc", + "xzroesc", + "pqgjy" + }; + + /* simple insertion sort */ + static + void sort_values( FT_Int count, FT_Pos* table ) + { + FT_Int i, j, swap; + + for ( i = 1; i < count; i++ ) + { + for ( j = i; j > 1; j-- ) + { + if ( table[j] > table[j-1] ) + break; + + swap = table[j]; + table[j] = table[j-1]; + table[j-1] = swap; + } + } + } + + + static + FT_Error ah_hinter_compute_blues( AH_Hinter* hinter ) + { + AH_Blue blue; + AH_Globals* globals = &hinter->globals->design; + FT_Pos flats [ MAX_TEST_CHARACTERS ]; + FT_Pos rounds[ MAX_TEST_CHARACTERS ]; + FT_Int num_flats; + FT_Int num_rounds; + + FT_Face face; + FT_GlyphSlot glyph; + FT_Error error; + FT_CharMap charmap; + + face = hinter->face; + glyph = face->glyph; + + /* save current charmap */ + charmap = face->charmap; + + /* do we have a Unicode charmap in there ?? */ + error = FT_Select_Charmap( face, ft_encoding_unicode ); + if (error) goto Exit; + + /* we compute the blues simply by loading each character from the */ + /* 'blue_chars[blues]' string, then compute its top-most and bottom-most */ + /* points */ + + AH_LOG(( "blue zones computation\n" )); + AH_LOG(( "------------------------------------------------\n" )); + + for ( blue = (AH_Blue)0; blue < ah_blue_max; blue++ ) + { + const char* p = blue_chars[blue]; + const char* limit = p + MAX_TEST_CHARACTERS; + FT_Pos *blue_ref, *blue_shoot; + + AH_LOG(( "blue %3d : ", (int)blue )); + + num_flats = 0; + num_rounds = 0; + for ( ; p < limit; p++ ) + { + FT_UInt glyph_index; + FT_Vector* extremum; + FT_Vector* points; + FT_Vector* point_limit; + FT_Vector* point; + FT_Bool round; + + /* exit if we reach the end of the string */ + if (!*p) break; + + AH_LOG(( "'%c'", *p )); + + /* load the character in the face - skip unknown or empty ones */ + glyph_index = FT_Get_Char_Index( face, (FT_UInt)*p ); + if (glyph_index == 0) continue; + + error = FT_Load_Glyph( face, glyph_index, FT_LOAD_NO_SCALE ); + if (error || glyph->outline.n_points <= 0) continue; + + + /* now compute min or max point indices and coordinates */ + points = glyph->outline.points; + point_limit = points + glyph->outline.n_points; + point = points; + extremum = point; + point++; + + if ( AH_IS_TOP_BLUE(blue) ) + { + for ( ; point < point_limit; point++ ) + if ( point->y > extremum->y ) + extremum = point; + } + else + { + for ( ; point < point_limit; point++ ) + if ( point->y < extremum->y ) + extremum = point; + } + + AH_LOG(( "%5d", (int)extremum->y )); + + /* now, see if the point belongs to a straight or round segment */ + /* we first need to find in which contour the extremum lies then */ + /* see its previous and next points.. */ + { + FT_Int index = extremum - points; + FT_Int n; + FT_Int first = 0; + FT_Int last, prev, next, end; + FT_Pos dist; + + last = -1; + first = 0; + for ( n = 0; n < glyph->outline.n_contours; n++ ) + { + end = glyph->outline.contours[n]; + if ( end >= index ) + { + last = end; + break; + } + first = end+1; + } + + /* XXX : should never happen !!! */ + if ( last < 0 ) + continue; + + /* now look for the previous and next points that are not on the */ + /* same Y coordinate. Threshold the "closeness" .. */ + + prev = index; + next = prev; + + do + { + if (prev > first) prev--; + else prev = last; + + dist = points[prev].y - extremum->y; + if ( dist < -5 || dist > 5 ) + break; + + } while (prev != index); + + do + { + if (next < last) next++; + else next = first; + + dist = points[next].y - extremum->y; + if ( dist < -5 || dist > 5 ) + break; + + } while (next != index); + + /* now, set the "round" flag depending on the segment's kind */ + round = FT_CURVE_TAG(glyph->outline.tags[prev]) != FT_Curve_Tag_On || + FT_CURVE_TAG(glyph->outline.tags[next]) != FT_Curve_Tag_On ; + + AH_LOG(( "%c ", round ? 'r' : 'f' )); + } + + if (round) + rounds[ num_rounds++ ] = extremum->y; + else + flats[ num_flats++ ] = extremum->y; + } + + AH_LOG(( "\n" )); + /* we have computed the contents of the 'rounds' and 'flats' tables */ + /* now determine the reference and overshoot position of the blue */ + /* we simply take the median value after a simple short.. */ + sort_values( num_rounds, rounds ); + sort_values( num_flats, flats ); + + blue_ref = globals->blue_refs + blue; + blue_shoot = globals->blue_shoots + blue; + if ( num_flats == 0 && num_rounds == 0 ) + { + *blue_ref = -10000; + *blue_shoot = -10000; + } + else if ( num_flats == 0 ) + { + *blue_ref = + *blue_shoot = rounds[ num_rounds/2 ]; + } + else if ( num_rounds == 0 ) + { + *blue_ref = + *blue_shoot = flats[ num_flats/2 ]; + } + else + { + *blue_ref = flats[ num_flats/2 ]; + *blue_shoot = rounds[ num_rounds/2 ]; + } + + /* there are sometimes problems, when the overshoot position of top */ + /* zones is under its reference position, or the opposite for bottom */ + /* zones. We must thus check everything there.. and correct the errors */ + if ( *blue_shoot != *blue_ref ) + { + FT_Pos ref = *blue_ref; + FT_Pos shoot = *blue_shoot; + FT_Bool over_ref = ( shoot > ref ); + + if ( AH_IS_TOP_BLUE(blue) ^ over_ref ) + *blue_shoot = *blue_ref = (shoot+ref)/2; + } + AH_LOG(( "-- ref = %ld, shoot = %ld\n", *blue_ref, *blue_shoot )); + } + + /* reset original face charmap */ + FT_Set_Charmap( face, charmap ); + error = 0; + + Exit: + return error; + } + + + static + FT_Error ah_hinter_compute_widths( AH_Hinter* hinter ) + { + /* scan the array of segments in each direction */ + AH_Outline* outline = hinter->glyph; + AH_Segment* segments; + AH_Segment* limit; + AH_Globals* globals = &hinter->globals->design; + FT_Pos* widths; + FT_Int dimension; + FT_Int* p_num_widths; + FT_Error error = 0; + FT_Pos edge_distance_threshold = 32000; + + globals->num_widths = 0; + globals->num_heights = 0; + + /* for now, compute the standard width and height from the "o" character */ + /* I started computing the stem width of the "i" and the stem height of */ + /* the "-", but it wasn't too good.. Moreover, we now have a single */ + /* character that gives us standard width and height */ + { + FT_UInt glyph_index; + + glyph_index = FT_Get_Char_Index( hinter->face, 'o' ); + if (glyph_index == 0) return 0; + + error = FT_Load_Glyph( hinter->face, glyph_index, FT_LOAD_NO_SCALE ); + if (error) goto Exit; + + error = ah_outline_load( hinter->glyph, hinter->face ); + if (error) goto Exit; + + ah_outline_compute_segments( hinter->glyph ); + ah_outline_link_segments( hinter->glyph ); + } + + segments = outline->horz_segments; + limit = segments + outline->num_hsegments; + widths = globals->heights; + p_num_widths = &globals->num_heights; + + for ( dimension = 1; dimension >= 0; dimension-- ) + { + AH_Segment* seg = segments; + AH_Segment* link; + FT_Int num_widths = 0; + + for ( ; seg < limit; seg++ ) + { + link = seg->link; + /* we only consider the stem segments there ! */ + if (link && link->link == seg && link > seg) + { + FT_Int dist; + + dist = seg->pos - link->pos; + if (dist < 0) dist = -dist; + + if ( num_widths < 12 ) + widths[ num_widths++ ] = dist; + } + } + + sort_values( num_widths, widths ); + *p_num_widths = num_widths; + + /* we will now try to find the smallest width */ + if (num_widths > 0 && widths[0] < edge_distance_threshold ) + edge_distance_threshold = widths[0]; + + segments = outline->vert_segments; + limit = segments + outline->num_vsegments; + widths = globals->widths; + p_num_widths = &globals->num_widths; + + } + + /* now, compute the edge distance threshold as a fraction of the */ + /* smallest width in the font.. Set it in "hinter.glyph" too !! */ + if ( edge_distance_threshold == 32000) + edge_distance_threshold = 50; + + /* let's try 20% */ + hinter->glyph->edge_distance_threshold = edge_distance_threshold/5; + + Exit: + return error; + } + + + LOCAL_FUNC + FT_Error ah_hinter_compute_globals( AH_Hinter* hinter ) + { + return ah_hinter_compute_widths( hinter ) || + ah_hinter_compute_blues ( hinter ); + } + diff --git a/src/autohint/ahglobal.h b/src/autohint/ahglobal.h new file mode 100644 index 000000000..418663469 --- /dev/null +++ b/src/autohint/ahglobal.h @@ -0,0 +1,38 @@ +/***************************************************************************/ +/* */ +/* ahglobal.h */ +/* */ +/* routines used to compute global metrics automatically */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ +#ifndef AGGLOBAL_H +#define AGGLOBAL_H + +#ifdef FT_FLAT_COMPILE +#include "ahtypes.h" +#else +#include +#endif + +#include /* for LOCAL_DEF/LOCAL_FUNC */ + +#define AH_IS_TOP_BLUE(b) ( (b) == ah_blue_capital_top || \ + (b) == ah_blue_small_top ) + + /* compute global metrics automatically */ + LOCAL_DEF + FT_Error ah_hinter_compute_globals( AH_Hinter* hinter ); + +#endif /* AGGLOBAL_H */ diff --git a/src/autohint/ahglyph.c b/src/autohint/ahglyph.c new file mode 100644 index 000000000..428b6076a --- /dev/null +++ b/src/autohint/ahglyph.c @@ -0,0 +1,1192 @@ +/***************************************************************************/ +/* */ +/* ahglyph.c */ +/* */ +/* routines used to load and analyze a given glyph before hinting */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ + +#ifdef FT_FLAT_COMPILE +#include "ahglyph.h" +#include "ahangles.h" +#include "ahglobal.h" +#else +#include +#include +#include +#endif + +#include + +#define xxxAH_DEBUG_GLYPH + + /* compute the direction value of a given vector.. */ + static + AH_Direction ah_compute_direction( FT_Pos dx, FT_Pos dy ) + { + AH_Direction dir; + FT_Pos ax = ABS(dx); + FT_Pos ay = ABS(dy); + + dir = ah_dir_none; + + /* test for vertical direction */ + if ( ax*12 < ay ) + { + dir = ( dy > 0 ? ah_dir_up : ah_dir_down ); + } + /* test for horizontal direction */ + else if ( ay*12 < ax ) + { + dir = ( dx > 0 ? ah_dir_right : ah_dir_left ); + } + + return dir; + } + + /************************************************************************ + * + * + * ah_outline_new + * + * + * Create a new empty AH_Outline + * + ************************************************************************/ + + LOCAL_FUNC + FT_Error ah_outline_new( FT_Memory memory, + AH_Outline* *aoutline ) + { + FT_Error error; + AH_Outline* outline; + + if ( !ALLOC( outline, sizeof(*outline) ) ) + { + outline->memory = memory; + *aoutline = outline; + } + return error; + } + + + /************************************************************************ + * + * + * ah_outline_done + * + * + * Destroys a given AH_Outline + * + ************************************************************************/ + + LOCAL_FUNC + void ah_outline_done( AH_Outline* outline ) + { + FT_Memory memory = outline->memory; + + FREE( outline->horz_edges ); + FREE( outline->horz_segments ); + FREE( outline->contours ); + FREE( outline->points ); + outline->vert_edges = 0; + outline->vert_segments = 0; + + outline->num_points = 0; + outline->max_points = 0; + outline->num_contours = 0; + outline->max_contours = 0; + FREE( outline ); + } + + + + /************************************************************************ + * + * + * ah_outline_save + * + * + * Save the content of a given AH_Outline into a face's glyph slot + * + ************************************************************************/ + + LOCAL_FUNC + void ah_outline_save( AH_Outline* outline, + AH_Loader* gloader ) + { + AH_Point* point = outline->points; + AH_Point* limit = point + outline->num_points; + FT_Vector* vec = gloader->current.outline.points; + char* tag = gloader->current.outline.tags; + + /* we assume that the glyph loader has already been checked for storage */ + for ( ; point < limit; point++, vec++, tag++ ) + { + vec->x = point->x; + vec->y = point->y; + + if (point->flags & ah_flah_conic) + tag[0] = FT_Curve_Tag_Conic; + else if (point->flags & ah_flah_cubic) + tag[0] = FT_Curve_Tag_Cubic; + else + tag[0] = FT_Curve_Tag_On; + } + } + + + /************************************************************************ + * + * + * ah_outline_load + * + * + * Loads an unscaled outline from a glyph slot into an AH_Outline + * + ************************************************************************/ + + LOCAL_FUNC + FT_Error ah_outline_load( AH_Outline* outline, + FT_Face face ) + { + FT_Memory memory = outline->memory; + FT_Error error = FT_Err_Ok; + FT_Outline* source = &face->glyph->outline; + FT_Int num_points = source->n_points; + FT_Int num_contours = source->n_contours; + AH_Point* points; + + /* check arguments */ + if (!face || !face->size || face->glyph->format != ft_glyph_format_outline) + return FT_Err_Invalid_Argument; + + + /* first of all, realloc the contours array if necessary */ + if ( num_contours > outline->max_contours ) + { + FT_Int new_contours = (num_contours+3) & -4; + + if ( REALLOC_ARRAY( outline->contours, outline->max_contours, + new_contours, AH_Point* ) ) + goto Exit; + + outline->max_contours = new_contours; + } + + /* then, realloc the points, segments & edges arrays if needed */ + if ( num_points > outline->max_points ) + { + FT_Int news = (num_points+7) & -8; + FT_Int max = outline->max_points; + + if ( REALLOC_ARRAY( outline->points, max, news, AH_Point ) || + REALLOC_ARRAY( outline->horz_edges, max, news, AH_Edge ) || + REALLOC_ARRAY( outline->horz_segments, max, news, AH_Segment ) ) + goto Exit; + + /* readjust some pointers */ + outline->vert_edges = outline->horz_edges + (news >> 1); + outline->vert_segments = outline->horz_segments + (news >> 1); + outline->max_points = news; + } + + outline->num_points = num_points; + outline->num_contours = num_contours; + + outline->num_hedges = 0; + outline->num_vedges = 0; + outline->num_hsegments = 0; + outline->num_vsegments = 0; + + /* compute the vertical and horizontal major directions, this is */ + /* currently done by inspecting the 'ft_outline_reverse_fill' flag */ + /* However, some fonts have improper glyphs, and it'd be a good idea */ + /* to be able to re-compute these values on the fly.. */ + { + outline->vert_major_dir = ah_dir_up; + outline->horz_major_dir = ah_dir_left; + + if (source->flags & ft_outline_reverse_fill) + { + outline->vert_major_dir = ah_dir_down; + outline->horz_major_dir = ah_dir_right; + } + } + + outline->x_scale = face->size->metrics.x_scale; + outline->y_scale = face->size->metrics.y_scale; + + points = outline->points; + + { + /* do one thing at a time - it is easier to understand, and */ + /* the code is clearer.. */ + AH_Point* point = points; + AH_Point* limit = point + outline->num_points; + + /* compute coordinates */ + { + FT_Vector* vec = source->points; + FT_Fixed x_scale = outline->x_scale; + FT_Fixed y_scale = outline->y_scale; + + for (; point < limit; vec++, point++ ) + { + point->fx = vec->x; + point->fy = vec->y; + point->ox = point->x = FT_MulFix( vec->x, x_scale ); + point->oy = point->y = FT_MulFix( vec->y, y_scale ); + point->flags = 0; + } + } + + /* compute bezier flags */ + { + char* tag = source->tags; + for ( point = points; point < limit; point++, tag++ ) + { + switch ( FT_CURVE_TAG( *tag ) ) + { + case FT_Curve_Tag_Conic: point->flags = ah_flah_conic; break; + case FT_Curve_Tag_Cubic: point->flags = ah_flah_cubic; break; + default: + ; + } + } + } + + /* compute "next" and "prev" */ + { + FT_Int contour_index; + AH_Point* prev; + AH_Point* first; + AH_Point* end; + + contour_index = 0; + + first = points; + end = points + source->contours[0]; + prev = end; + + for ( point = points; point < limit; point++ ) + { + point->prev = prev; + if (point < end) + { + point->next = point+1; + prev = point; + } + else + { + point->next = first; + contour_index++; + if (point+1 < limit) + { + end = points + source->contours[contour_index]; + first = point+1; + prev = end; + } + } + } + } + + /* set-up the contours array */ + { + AH_Point** contour = outline->contours; + AH_Point** limit = contour + outline->num_contours; + short* end = source->contours; + short index = 0; + + for (; contour < limit; contour++, end++ ) + { + contour[0] = points + index; + index = end[0] + 1; + } + } + + /* compute directions of in & out vectors */ + { + for ( point = points; point < limit; point++ ) + { + AH_Point* prev; + AH_Point* next; + FT_Vector vec; + + prev = point->prev; + vec.x = point->fx - prev->fx; + vec.y = point->fy - prev->fy; + point->in_dir = ah_compute_direction( vec.x, vec.y ); + +#ifndef AH_OPTION_NO_WEAK_INTERPOLATION + point->in_angle = ah_angle( &vec ); +#endif + next = point->next; + vec.x = next->fx - point->fx; + vec.y = next->fy - point->fy; + point->out_dir = ah_compute_direction( vec.x, vec.y ); + +#ifndef AH_OPTION_NO_WEAK_INTERPOLATION + point->out_angle = ah_angle( &vec ); + + { + AH_Angle delta = point->in_angle - point->out_angle; + if (delta < 0) delta = -delta; + + if (delta < 2) + point->flags |= ah_flah_weak_interpolation; + } + + /* + if (point->flags & (ah_flah_conic|ah_flah_cubic)) + point->flags |= ah_flah_weak_interpolation; + */ +#endif + +#ifdef AH_OPTION_NO_STRONG_INTERPOLATION + point->flags |= ah_flah_weak_interpolation; +#endif + } + } + } + Exit: + return error; + } + + + + LOCAL_FUNC + void ah_setup_uv( AH_Outline* outline, + AH_UV source ) + { + AH_Point* point = outline->points; + AH_Point* limit = point + outline->num_points; + + for ( ; point < limit; point++ ) + { + FT_Pos u, v; + + switch (source) + { + case ah_uv_fxy: u = point->fx; v = point->fy; break; + case ah_uv_fyx: u = point->fy; v = point->fx; break; + case ah_uv_oxy: u = point->ox; v = point->oy; break; + case ah_uv_oyx: u = point->oy; v = point->ox; break; + case ah_uv_yx: u = point->y; v = point->x; break; + case ah_uv_ox: u = point->x; v = point->ox; break; + case ah_uv_oy: u = point->y; v = point->oy; break; + default: u = point->x; v = point->y; break; + } + point->u = u; + point->v = v; + } + } + + + LOCAL_FUNC + void ah_outline_compute_segments( AH_Outline* outline ) + { + int dimension; + AH_Segment* segments; + FT_Int* p_num_segments; + AH_Direction segment_dir; + AH_Direction major_dir; + + segments = outline->horz_segments; + p_num_segments = &outline->num_hsegments; + major_dir = ah_dir_right; /* !!! This value must be positive */ + segment_dir = major_dir; + + /* set up (u,v) in each point */ + ah_setup_uv( outline, ah_uv_fyx ); + + for ( dimension = 1; dimension >= 0; dimension-- ) + { + AH_Point** contour = outline->contours; + AH_Point** contour_limit = contour + outline->num_contours; + AH_Segment* segment = segments; + FT_Int num_segments = 0; + +#ifdef AH_HINT_METRICS + AH_Point* min_point = 0; + AH_Point* max_point = 0; + FT_Pos min_coord = 32000; + FT_Pos max_coord = -32000; +#endif + /* do each contour separately */ + for ( ; contour < contour_limit; contour++ ) + { + AH_Point* point = contour[0]; + AH_Point* last = point->prev; + int on_edge = 0; + FT_Pos min_pos = +32000; /* minimum segment pos != min_coord */ + FT_Pos max_pos = -32000; /* maximum segment pos != max_coord */ + FT_Bool passed; + +#ifdef AH_HINT_METRICS + if (point->u < min_coord) + { + min_coord = point->u; + min_point = point; + } + if (point->u > max_coord) + { + max_coord = point->u; + max_point = point; + } +#endif + + if (point == last) /* skip singletons - just in case ?? */ + continue; + + if ( ABS(last->out_dir) == major_dir && + ABS(point->out_dir) == major_dir) + { + /* we are already on an edge, try to locate its start */ + last = point; + for (;;) + { + point = point->prev; + if ( ABS(point->out_dir) != major_dir ) + { + point = point->next; + break; + } + if ( point == last ) + break; + } + + } + + last = point; + passed = 0; + for (;;) + { + FT_Pos u, v; + + if (on_edge) + { + u = point->u; + if ( u < min_pos ) min_pos = u; + if ( u > max_pos ) max_pos = u; + + if ( point->out_dir != segment_dir || point == last) + { + /* we're just leaving an edge, record a new segment !! */ + segment->last = point; + segment->pos = (min_pos + max_pos) >> 1; + + /* a segment is round if either its first or last point */ + /* is a control point.. */ + if ( (segment->first->flags | point->flags) & ah_flah_control ) + segment->flags |= ah_edge_round; + + /* compute segment size */ + min_pos = max_pos = point->v; + v = segment->first->v; + if (v < min_pos) min_pos = v; + if (v > max_pos) max_pos = v; + segment->min_coord = min_pos; + segment->max_coord = max_pos; + + on_edge = 0; + num_segments++; + segment++; + /* fallthrough */ + } + } + + /* now exit if we're at the start/end point */ + if (point == last) + { + if (passed) + break; + passed = 1; + } + + if ( !on_edge && ABS(point->out_dir) == major_dir ) + { + /* this is the start of a new segment ! */ + segment_dir = point->out_dir; + + /* clear all segment fields */ + memset( segment, 0, sizeof(*segment) ); + + segment->dir = segment_dir; + segment->flags = ah_edge_normal; + min_pos = max_pos = point->u; + segment->first = point; + segment->last = point; + segment->contour = contour; + on_edge = 1; + + if (point == max_point) + max_point = 0; + + if (point == min_point) + min_point = 0; + } + + point = point->next; + } + + } /* contours */ + + +#ifdef AH_HINT_METRICS + /* we need to ensure that there are edges on the left-most and */ + /* right-most points of the glyph in order to hint the metrics */ + /* we do this by inserting fake segments when needed.. */ + if (dimension == 0) + { + AH_Point* point = outline->points; + AH_Point* limit = point + outline->num_points; + + AH_Point* min_point = 0; + AH_Point* max_point = 0; + FT_Pos min_pos = 32000; + FT_Pos max_pos = -32000; + + /* compute minimum and maximum points */ + for ( ; point < limit; point++ ) + { + FT_Pos x = point->fx; + + if ( x < min_pos ) + { + min_pos = x; + min_point = point; + } + if ( x > max_pos ) + { + max_pos = x; + max_point = point; + } + } + + /* insert minimum segment */ + if (min_point) + { + /* clear all segment fields */ + memset( segment, 0, sizeof(*segment) ); + + segment->dir = segment_dir; + segment->flags = ah_edge_normal; + segment->first = min_point; + segment->last = min_point; + segment->pos = min_pos; + + num_segments++; + segment++; + } + + /* insert maximum segment */ + if (max_point) + { + /* clear all segment fields */ + memset( segment, 0, sizeof(*segment) ); + + segment->dir = segment_dir; + segment->flags = ah_edge_normal; + segment->first = max_point; + segment->last = max_point; + segment->pos = max_pos; + + num_segments++; + segment++; + } + } +#endif + + + *p_num_segments = num_segments; + + segments = outline->vert_segments; + major_dir = ah_dir_up; + p_num_segments = &outline->num_vsegments; + ah_setup_uv( outline, ah_uv_fxy ); + } + } + + + + LOCAL_FUNC + void ah_outline_link_segments( AH_Outline* outline ) + { + AH_Segment* segments; + AH_Segment* limit; + int dimension; + + ah_setup_uv( outline, ah_uv_fyx ); + + segments = outline->horz_segments; + limit = segments + outline->num_hsegments; + for ( dimension = 1; dimension >= 0; dimension-- ) + { + AH_Segment* seg1; + AH_Segment* seg2; + + /* now compare each segment to the others */ + for ( seg1 = segments; seg1 < limit; seg1++ ) + { + FT_Pos best_score = 32000; + AH_Segment* best_segment = 0; + + /* the fake segments are introduced to hint the metrics */ + /* we must never link them to anything.. */ + if (seg1->first == seg1->last) + continue; + + for ( seg2 = segments; seg2 < limit; seg2++ ) + if ( seg1 != seg2 && seg1->dir + seg2->dir == 0 ) + { + FT_Pos pos1 = seg1->pos; + FT_Pos pos2 = seg2->pos; + FT_Bool is_dir; + FT_Bool is_pos; + + /* check that the segments are correctly oriented and positioned */ + /* to form a black distance.. */ + + is_dir = ( seg1->dir == outline->horz_major_dir || + seg1->dir == outline->vert_major_dir ); + is_pos = pos1 > pos2; + + if ( pos1 == pos2 || !(is_dir ^ is_pos) ) + continue; + + /* check the two segments, we now have a better algorithm */ + /* that doesn't rely on the segment points themselves but */ + /* on their relative position. This gets rids of many */ + /* unpleasant artefacts and incorrect stem/serifs */ + /* computations.. */ + + /* first of all, compute the size of the "common" height */ + { + FT_Pos min = seg1->min_coord; + FT_Pos max = seg1->max_coord; + FT_Pos len, score; + FT_Pos size1, size2; + + size1 = max - min; + size2 = seg2->max_coord - seg2->min_coord; + + if ( min < seg2->min_coord ) + min = seg2->min_coord; + + if ( max < seg2->max_coord ) + max = seg2->max_coord; + + len = max - min; + score = seg2->pos - seg1->pos; + if (score < 0) + score = -score; + + /* before comparing the scores, take care that the segments */ + /* are really facing each other (often not for italics..) */ + if ( 4*len >= size1 && 4*len >= size2 ) + if (score < best_score) + { + best_score = score; + best_segment = seg2; + } + } + } + + if (best_segment) + { + seg1->link = best_segment; + seg1->score = best_score; + best_segment->num_linked++; + } + + + } /* edges 1 */ + + /* now, compute the "serif" segments */ + for ( seg1 = segments; seg1 < limit; seg1++ ) + { + seg2 = seg1->link; + if (seg2 && seg2->link != seg1) + { + seg1->link = 0; + seg1->serif = seg2->link; + } + } + + ah_setup_uv( outline, ah_uv_fxy ); + + segments = outline->vert_segments; + limit = segments + outline->num_vsegments; + } + } + + +#ifdef AH_DEBUG_GLYPH + /* A function used to dump the array of linked segments */ + extern + void ah_dump_segments( AH_Outline* outline ) + { + AH_Segment* segments; + AH_Segment* limit; + AH_Point* points; + FT_Int dimension; + + points = outline->points; + segments = outline->horz_segments; + limit = segments + outline->num_hsegments; + + for (dimension = 1; dimension >= 0; dimension-- ) + { + AH_Segment* seg; + + printf ( "Table of %s segments:\n", !dimension ? "vertical" : "horizontal" ); + printf ( " [ index | pos | dir | link | serif | numl | first | start ]\n" ); + + for ( seg = segments; seg < limit; seg++ ) + { + printf ( " [ %5d | %4d | %5s | %4d | %5d | %4d | %5d | %5d ]\n", + seg - segments, + (int)seg->pos, + seg->dir == ah_dir_up ? "up" : + (seg->dir == ah_dir_down ? "down" : + (seg->dir == ah_dir_left ? "left" : + (seg->dir == ah_dir_right ? "right" : "none") + ) + ), + seg->link ? (seg->link-segments) : -1, + seg->serif ? (seg->serif-segments) : -1, + (int)seg->num_linked, + seg->first - points, + seg->last - points ); + } + segments = outline->vert_segments; + limit = segments + outline->num_vsegments; + } + } +#endif + + + + static + void ah_outline_compute_edges( AH_Outline* outline ) + { + AH_Edge* edges; + AH_Segment* segments; + AH_Segment* segment_limit; + AH_Direction up_dir; + FT_Int* p_num_edges; + FT_Int dimension; + FT_Fixed scale; + FT_Pos edge_distance_threshold; + + edges = outline->horz_edges; + segments = outline->horz_segments; + segment_limit = segments + outline->num_hsegments; + p_num_edges = &outline->num_hedges; + up_dir = ah_dir_right; + scale = outline->y_scale; + + for ( dimension = 1; dimension >= 0; dimension-- ) + { + AH_Edge* edge; + AH_Edge* edge_limit; /* really == edge + num_edges */ + AH_Segment* seg; + + /**********************************************************************/ + /* */ + /* We will begin by generating a sorted table of edges for the */ + /* current direction. To do so, we simply scan each segment and */ + /* try to find an edge in our table that corresponds to its position */ + /* */ + /* If no edge is found, we create and insert a new edge in the */ + /* sorted table. Otherwise, we simply add the segment to the */ + /* edge's list which will be processed in the second step to */ + /* compute the edge's properties.. */ + /* */ + /* Note that the edges table is sorted along the segment/edge */ + /* position. */ + /* */ + + edge_distance_threshold = FT_MulFix( outline->edge_distance_threshold, + scale ); + if (edge_distance_threshold > 64/4) + edge_distance_threshold = 64/4; + + edge_limit = edges; + for ( seg = segments; seg < segment_limit; seg++ ) + { + AH_Edge* found = 0; + + /* look for an edge corresponding to the segment */ + for ( edge = edges; edge < edge_limit; edge++ ) + { + FT_Pos dist; + + dist = seg->pos - edge->fpos; + if (dist < 0) dist = -dist; + + dist = FT_MulFix( dist, scale ); + if ( dist < edge_distance_threshold ) + { + found = edge; + break; + } + } + + if (!found) + { + /* insert a new edge in the list. Sort according to the position */ + while ( edge > edges && edge[-1].fpos > seg->pos ) + { + edge[0] = edge[-1]; + edge--; + } + edge_limit++; + + /* clear all edge fields */ + memset( edge, 0, sizeof(*edge) ); + + /* add the segment to the new edge's list */ + edge->first = seg; + edge->last = seg; + edge->fpos = seg->pos; + edge->opos = edge->pos = FT_MulFix( seg->pos, scale ); + seg->edge_next = seg; + } + else + { + /* if an edge was found, simply add the segment to the edge's */ + /* list */ + seg->edge_next = edge->first; + edge->last->edge_next = seg; + edge->last = seg; + } + } + + *p_num_edges = edge_limit - edges; + + + /**********************************************************************/ + /* */ + /* Good, we will now compute each edge's properties according to */ + /* segments found on its position. Basically, these are: */ + /* */ + /* - edge's main direction */ + /* - stem edge, serif edge or both (which defaults to stem then) */ + /* - rounded edge, straigth or both (which defaults to straight) */ + /* - link for edge.. */ + /* */ + + /* first of all, set the "edge" field in each segment - this is */ + /* required in order to compute edge links.. */ + for ( edge = edges; edge < edge_limit; edge++ ) + { + seg = edge->first; + if (seg) + do + { + seg->edge = edge; + seg = seg->edge_next; + } + while ( seg != edge->first ); + } + + /* now, compute each edge properties */ + for ( edge = edges; edge < edge_limit; edge++ ) + { + int is_round = 0; /* does it contain round segments ? */ + int is_straight = 0; /* does it contain straight segments ? */ + int ups = 0; /* number of upwards segments */ + int downs = 0; /* number of downwards segments */ + + seg = edge->first; + do + { + FT_Bool is_serif; + + /* check for roundness of segment */ + if ( seg->flags & ah_edge_round ) is_round++; + else is_straight++; + + /* check for segment direction */ + if ( seg->dir == up_dir ) ups += (seg->max_coord-seg->min_coord); + else downs += (seg->max_coord-seg->min_coord); + + /* check for links - if seg->serif is set, then seg->link must be */ + /* ignored.. */ + is_serif = seg->serif && seg->serif->edge != edge; + + if ( seg->link || is_serif ) + { + AH_Edge* edge2; + AH_Segment* seg2; + + edge2 = edge->link; + seg2 = seg->link; + + if (is_serif) + { + seg2 = seg->serif; + edge2 = edge->serif; + } + + if (edge2) + { + FT_Pos edge_delta; + FT_Pos seg_delta; + + edge_delta = edge->fpos - edge2->fpos; + if (edge_delta < 0) edge_delta = -edge_delta; + + seg_delta = seg->pos - seg2->pos; + if (seg_delta < 0) seg_delta = -seg_delta; + + if (seg_delta < edge_delta) + edge2 = seg2->edge; + } + else + edge2 = seg2->edge; + + if (is_serif) + edge->serif = edge2; + else + edge->link = edge2; + } + + seg = seg->edge_next; + + } while ( seg != edge->first ); + + /* set the round/straight flags */ + edge->flags = ah_edge_normal; + + if ( is_straight == 0 && is_round ) + edge->flags |= ah_edge_round; + + /* set the edge's main direction */ + edge->dir = ah_dir_none; + + if ( ups > downs ) + edge->dir = up_dir; + + else if ( ups < downs ) + edge->dir = - up_dir; + + else if ( ups == downs ) + edge->dir = 0; /* both up and down !! */ + + /* gets rid of serifs if link is set */ + /* ZZZZ: this gets rid of many unpleasant artefacts !! */ + /* example : the "c" in cour.pfa at size 13 */ + + if (edge->serif && edge->link) + edge->serif = 0; + } + + edges = outline->vert_edges; + segments = outline->vert_segments; + segment_limit = segments + outline->num_vsegments; + p_num_edges = &outline->num_vedges; + up_dir = ah_dir_up; + scale = outline->x_scale; + } + } + + + /************************************************************************ + * + * + * ah_outline_detect_features + * + * + * Performs feature detection on a given AH_Outline + * + ************************************************************************/ + + LOCAL_FUNC + void ah_outline_detect_features( AH_Outline* outline ) + { + ah_outline_compute_segments( outline ); + ah_outline_link_segments ( outline ); + ah_outline_compute_edges ( outline ); + } + + + + /************************************************************************ + * + * + * ah_outline_compute_blue_edges + * + * + * Computes the "blue edges" in a given outline (i.e. those that + * must be snapped to a blue zone edge (top or bottom) + * + ************************************************************************/ + + LOCAL_FUNC + void ah_outline_compute_blue_edges( AH_Outline* outline, + AH_Face_Globals* face_globals ) + { + AH_Edge* edge = outline->horz_edges; + AH_Edge* limit = edge + outline->num_hedges; + AH_Globals* globals = &face_globals->design; + FT_Fixed y_scale = outline->y_scale; + + /* compute for each horizontal edge, which blue zone is closer */ + for ( ; edge < limit; edge++ ) + { + AH_Blue blue; + FT_Pos* best_blue = 0; + FT_Pos best_dist; /* initial threshold */ + + /* compute the initial threshold as a fraction of the EM size */ + best_dist = FT_MulFix( face_globals->face->units_per_EM / 40, y_scale ); + if (best_dist > 64/4) + best_dist = 64/4; + + for ( blue = (AH_Blue)0; blue < ah_blue_max; blue++ ) + { + /* if it is a top zone, check for right edges - if it is a bottom */ + /* zone, check for left edges.. */ + /* of course, that's for TrueType .. XXXX */ + FT_Bool is_top_blue = AH_IS_TOP_BLUE(blue); + FT_Bool is_major_dir = edge->dir == outline->horz_major_dir; + + /* if it's a top zone, the edge must be against the major direction */ + /* if it's a bottom zone, it must be in the major direction */ + if ( is_top_blue ^ is_major_dir ) + { + FT_Pos dist; + FT_Pos* blue_pos = globals->blue_refs + blue; + + /* first of all, compare it to the reference position */ + dist = edge->fpos - *blue_pos; + if (dist < 0) dist = -dist; + + dist = FT_MulFix( dist, y_scale ); + if (dist < best_dist) + { + best_dist = dist; + best_blue = blue_pos; + } + + /* now, compare it to the overshoot position if the edge is rounded */ + /* and when the edge is over the reference position of a top zone, */ + /* or under the reference position of a bottom zone */ + if ( edge->flags & ah_edge_round && dist != 0 ) + { + FT_Bool is_under_ref = edge->fpos < *blue_pos; + + if ( is_top_blue ^ is_under_ref ) + { + blue_pos = globals->blue_shoots + blue; + dist = edge->fpos - *blue_pos; + if (dist < 0) dist = -dist; + + dist = FT_MulFix( dist, y_scale ); + if (dist < best_dist) + { + best_dist = dist; + best_blue = blue_pos; + } + } + } + } + } + + if (best_blue) + edge->blue_edge = best_blue; + } + } + + + /************************************************************************ + * + * + * ah_outline_scale_blue_edges + * + * + * This functions must be called before hinting in order to re-adjust + * the content of the detected edges (basically change the "blue edge" + * pointer from 'design units' to 'scaled ones' + * + ************************************************************************/ + + LOCAL_FUNC + void ah_outline_scale_blue_edges( AH_Outline* outline, + AH_Face_Globals* globals ) + { + AH_Edge* edge = outline->horz_edges; + AH_Edge* limit = edge + outline->num_hedges; + FT_Int delta; + + delta = globals->scaled.blue_refs - globals->design.blue_refs; + for ( ; edge < limit; edge++ ) + { + if (edge->blue_edge) + edge->blue_edge += delta; + } + } + + + + + +#ifdef AH_DEBUG_GLYPH + extern + void ah_dump_edges( AH_Outline* outline ) + { + AH_Edge* edges; + AH_Edge* limit; + AH_Segment* segments; + FT_Int dimension; + + edges = outline->horz_edges; + limit = edges + outline->num_hedges; + segments = outline->horz_segments; + for ( dimension = 1; dimension >= 0; dimension-- ) + { + AH_Edge* edge; + + printf ( "Table of %s edges:\n", !dimension ? "vertical" : "horizontal" ); + printf ( " [ index | pos | dir | link | serif | blue | opos | pos ]\n" ); + + for ( edge = edges; edge < limit; edge++ ) + { + printf ( " [ %5d | %4d | %5s | %4d | %5d | %c | %5.2f | %5.2f ]\n", + edge - edges, + (int)edge->fpos, + edge->dir == ah_dir_up ? "up" : + (edge->dir == ah_dir_down ? "down" : + (edge->dir == ah_dir_left ? "left" : + (edge->dir == ah_dir_right ? "right" : "none") + ) + ), + edge->link ? (edge->link-edges) : -1, + edge->serif ? (edge->serif-edges) : -1, + edge->blue_edge ? 'y' : 'n', + edge->opos/64.0, + edge->pos/64.0 ); + } + + + edges = outline->vert_edges; + limit = edges + outline->num_vedges; + segments = outline->vert_segments; + } + } +#endif diff --git a/src/autohint/ahglyph.h b/src/autohint/ahglyph.h new file mode 100644 index 000000000..801d9b637 --- /dev/null +++ b/src/autohint/ahglyph.h @@ -0,0 +1,79 @@ +/***************************************************************************/ +/* */ +/* ahglyph.h */ +/* */ +/* routines used to load and analyze a given glyph before hinting */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ +#ifndef AGGLYPH_H +#define AGGLYPH_H + +#ifdef FT_FLAT_COMPILE +#include "ahtypes.h" +#else +#include +#endif + + typedef enum AH_UV_ + { + ah_uv_fxy, + ah_uv_fyx, + ah_uv_oxy, + ah_uv_oyx, + ah_uv_ox, + ah_uv_oy, + ah_uv_yx, + ah_uv_xy /* should always be last !! */ + + } AH_UV; + + LOCAL_DEF + void ah_setup_uv( AH_Outline* outline, + AH_UV source ); + + + /* AH_Outline functions - they should be typically called in this order */ + + LOCAL_DEF + FT_Error ah_outline_new( FT_Memory memory, AH_Outline* *aoutline ); + + LOCAL_DEF + FT_Error ah_outline_load( AH_Outline* outline, FT_Face face ); + + LOCAL_DEF + void ah_outline_compute_segments( AH_Outline* outline ); + + LOCAL_DEF + void ah_outline_link_segments( AH_Outline* outline ); + + LOCAL_DEF + void ah_outline_detect_features( AH_Outline* outline ); + + LOCAL_DEF + void ah_outline_compute_blue_edges( AH_Outline* outline, + AH_Face_Globals* globals ); + + LOCAL_DEF + void ah_outline_scale_blue_edges( AH_Outline* outline, + AH_Face_Globals* globals ); + + LOCAL_DEF + void ah_outline_save( AH_Outline* outline, AH_Loader* loader ); + + LOCAL_DEF + void ah_outline_done( AH_Outline* outline ); + + +#endif /* AGGLYPH_H */ diff --git a/src/autohint/ahhint.c b/src/autohint/ahhint.c new file mode 100644 index 000000000..10621a4ea --- /dev/null +++ b/src/autohint/ahhint.c @@ -0,0 +1,1293 @@ +/***************************************************************************/ +/* */ +/* ahhint.c */ +/* */ +/* Glyph hinter */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ + +#ifdef FT_FLAT_COMPILE +#include "ahhint.h" +#include "ahglyph.h" +#include "ahangles.h" +#else +#include +#include +#include +#endif + +#include + +#define FACE_GLOBALS(face) ((AH_Face_Globals*)(face)->autohint.data) + +#define AH_USE_IUP + + + /*******************************************************************/ + /*******************************************************************/ + /**** ****/ + /**** Hinting routines ****/ + /**** ****/ + /*******************************************************************/ + /*******************************************************************/ + + static int disable_horz_edges = 0; + static int disable_vert_edges = 0; + + /* snap a given width in scaled coordinates to one of the */ + /* current standard widths.. */ + static + FT_Pos ah_snap_width( FT_Pos* widths, + FT_Int count, + FT_Pos width ) + { + int n; + FT_Pos best = 64+32+2; + FT_Pos reference = width; + + for ( n = 0; n < count; n++ ) + { + FT_Pos w; + FT_Pos dist; + + w = widths[n]; + dist = width - w; + if (dist < 0) dist = -dist; + if (dist < best) + { + best = dist; + reference = w; + } + } + + if ( width >= reference ) + { + width -= 0x21; + if ( width < reference ) + width = reference; + } + else + { + width += 0x21; + if ( width > reference ) + width = reference; + } + + return width; + } + + + + /* Align one stem edge relative to the previous stem edge */ + static + void ah_align_linked_edge( AH_Hinter* hinter, + AH_Edge* base_edge, + AH_Edge* stem_edge, + int vertical ) + { + FT_Pos dist = stem_edge->opos - base_edge->opos; + AH_Globals* globals = &hinter->globals->scaled; + FT_Pos sign = 1; + + if (dist < 0) + { + dist = -dist; + sign = -1; + } + + if (vertical) + { + dist = ah_snap_width( globals->heights, globals->num_heights, dist ); + + /* in the case of vertical hinting, always round */ + /* the stem heights to integer pixels.. */ + if (dist >= 64) + dist = (dist+16) & -64; + else + dist = 64; + } + else + { + dist = ah_snap_width( globals->widths, globals->num_widths, dist ); + + if (hinter->flags & ah_hinter_monochrome) + { + /* monochrome horizontal hinting: snap widths to integer pixels */ + /* with a different threshold.. */ + if (dist < 64) + dist = 64; + else + dist = (dist+32) & -64; + } + else + { + /* for horizontal anti-aliased hinting, we adopt a more subtle */ + /* approach, we strengthen small stems, round stems whose size */ + /* is between 1 and 2 pixels to an integer, otherwise nothing */ + if (dist < 48) + dist = (dist+64) >> 1; + + else if (dist < 128) + dist = (dist+42) & -64; + + } + } + stem_edge->pos = base_edge->pos + sign*dist; + } + + + + static + void ah_align_serif_edge( AH_Hinter* hinter, + AH_Edge* base, + AH_Edge* serif ) + { + FT_Pos dist; + FT_Pos sign = 1; + + UNUSED(hinter); + dist = serif->opos - base->opos; + if (dist < 0) + { + dist = -dist; + sign = -1; + } + + if (base->flags & ah_edge_done) + /* do not strengthen serifs */ + { + if (dist > 64) + dist = (dist+16) & -64; + + else if (dist <= 32) + dist = (dist+33) >> 1; + } + serif->pos = base->pos + sign*dist; + } + + + /**************************************************************************/ + /**************************************************************************/ + /**************************************************************************/ + /**** ****/ + /**** E D G E H I N T I N G ****/ + /**** ****/ + /**** ****/ + /**************************************************************************/ + /**************************************************************************/ + /**************************************************************************/ + + /* Another alternative edge hinting algorithm */ + static + void ah_hint_edges_3( AH_Hinter* hinter ) + { + AH_Edge* edges; + AH_Edge* edge_limit; + AH_Outline* outline = hinter->glyph; + FT_Int dimension; + + edges = outline->horz_edges; + edge_limit = edges + outline->num_hedges; + + for ( dimension = 1; dimension >= 0; dimension-- ) + { + AH_Edge* edge; + AH_Edge* before = 0; + AH_Edge* after = 0; + AH_Edge* anchor = 0; + int has_serifs = 0; + + if (disable_vert_edges && !dimension) + goto Next_Dimension; + + if (disable_horz_edges && dimension) + goto Next_Dimension; + + /* we begin by aligning all stems relative to the blue zone */ + /* if needed.. that's only for horizontal edges.. */ + if (dimension) + { + for ( edge = edges; edge < edge_limit; edge++ ) + { + FT_Pos* blue; + AH_Edge *edge1, *edge2; + + if (edge->flags & ah_edge_done) + continue; + + blue = edge->blue_edge; + edge1 = 0; + edge2 = edge->link; + + if (blue) + { + edge1 = edge; + } + else if (edge2 && edge2->blue_edge) + { + blue = edge2->blue_edge; + edge1 = edge2; + edge2 = edge; + } + + if (!edge1) + continue; + + edge1->pos = blue[0]; + edge1->flags |= ah_edge_done; + + if (edge2 && !edge2->blue_edge) + { + ah_align_linked_edge( hinter, edge1, edge2, dimension ); + edge2->flags |= ah_edge_done; + } + + if (!anchor) + anchor = edge; + } + } + + /* now, we will align all stem edges, trying to maintain the */ + /* relative order of stems in the glyph.. */ + before = 0; + after = 0; + for ( edge = edges; edge < edge_limit; edge++ ) + { + AH_Edge *edge2; + + if (edge->flags & ah_edge_done) + continue; + + /* skip all non-stem edges */ + edge2 = edge->link; + if (!edge2) + { + has_serifs++; + continue; + } + + /* now, align the stem */ + + /* this should not happen, but it's better to be safe.. */ + if (edge2->blue_edge || edge2 < edge) + { +#if 0 + printf( "strange blue alignement, edge %d to %d\n", + edge - edges, edge2 - edges ); +#endif + ah_align_linked_edge( hinter, edge2, edge, dimension ); + edge->flags |= ah_edge_done; + continue; + } + + { + FT_Bool min = 0; + FT_Pos delta; + + if (!anchor) + { + edge->pos = (edge->opos+32) & -64; + anchor = edge; + } + else + edge->pos = anchor->pos + ((edge->opos - anchor->opos + 32) & -64); + + edge->flags |= ah_edge_done; + + if (edge > edges && edge->pos < edge[-1].pos) + { + edge->pos = edge[-1].pos; + min = 1; + } + + ah_align_linked_edge( hinter, edge, edge2, dimension ); + delta = 0; + if ( edge2+1 < edge_limit && + edge2[1].flags & ah_edge_done ) + delta = edge2[1].pos - edge2->pos; + + if (delta < 0) + { + edge2->pos += delta; + if (!min) + edge->pos += delta; + } + edge2->flags |= ah_edge_done; + } + } + + if (!has_serifs) + goto Next_Dimension; + + /* now, hint the remaining edges (serifs and single) in order */ + /* to complete our processing.. */ + for ( edge = edges; edge < edge_limit; edge++ ) + { + if (edge->flags & ah_edge_done) + continue; + + if (edge->serif) + { + ah_align_serif_edge( hinter, edge->serif, edge ); + } + else if (!anchor) + { + edge->pos = (edge->opos+32) & -64; + anchor = edge; + } + else + edge->pos = anchor->pos + ((edge->opos-anchor->opos+32) & -64); + + edge->flags |= ah_edge_done; + + if (edge > edges && edge->pos < edge[-1].pos) + edge->pos = edge[-1].pos; + + if ( edge+1 < edge_limit && edge[1].flags & ah_edge_done && + edge->pos > edge[1].pos) + edge->pos = edge[1].pos; + } + + Next_Dimension: + edges = outline->vert_edges; + edge_limit = edges + outline->num_vedges; + } + + } + + + + + + + + LOCAL_FUNC + void ah_hinter_hint_edges( AH_Hinter* hinter, + int no_horz_edges, + int no_vert_edges ) + { + disable_horz_edges = no_horz_edges; + disable_vert_edges = no_vert_edges; + + /* AH_Interpolate_Blue_Edges( hinter ); -- doesn't seem to help */ + /* reduce the problem of the disappearing eye in the "e" of Times */ + /* also, creates some artifacts near the blue zones ?? */ + { + ah_hint_edges_3( hinter ); + + /* outline optimiser removed temporarily */ +#if 0 + if (hinter->flags & ah_hinter_optimize) + { + AH_Optimizer opt; + + if (!AH_Optimizer_Init( &opt, hinter->glyph, hinter->memory )) + { + AH_Optimizer_Compute( &opt ); + AH_Optimizer_Done( &opt ); + } + } +#endif + } + } + + + + /**************************************************************************/ + /**************************************************************************/ + /**************************************************************************/ + /**** ****/ + /**** P O I N T H I N T I N G ****/ + /**** ****/ + /**** ****/ + /**************************************************************************/ + /**************************************************************************/ + /**************************************************************************/ + + static + void ah_hinter_align_edge_points( AH_Hinter* hinter ) + { + AH_Outline* outline = hinter->glyph; + AH_Edge* edges; + AH_Edge* edge_limit; + FT_Int dimension; + + edges = outline->horz_edges; + edge_limit = edges + outline->num_hedges; + + for ( dimension = 1; dimension >= 0; dimension-- ) + { + AH_Edge* edge; + AH_Edge* before; + AH_Edge* after; + + before = 0; + after = 0; + + edge = edges; + for ( ; edge < edge_limit; edge++ ) + { + /* move the points of each segment in each edge to the edge's position */ + AH_Segment* seg = edge->first; + do + { + AH_Point* point = seg->first; + for (;;) + { + if (dimension) + { + point->y = edge->pos; + point->flags |= ah_flah_touch_y; + } + else + { + point->x = edge->pos; + point->flags |= ah_flah_touch_x; + } + if (point == seg->last) + break; + + point = point->next; + } + + seg = seg->edge_next; + } + while (seg != edge->first); + } + edges = outline->vert_edges; + edge_limit = edges + outline->num_vedges; + } + } + + + /* hint the strong points - this is equivalent to the TrueType "IP" */ + static + void ah_hinter_align_strong_points( AH_Hinter* hinter ) + { + AH_Outline* outline = hinter->glyph; + FT_Int dimension; + AH_Edge* edges; + AH_Edge* edge_limit; + AH_Point* points; + AH_Point* point_limit; + AH_Flags touch_flag; + + points = outline->points; + point_limit = points + outline->num_points; + + edges = outline->horz_edges; + edge_limit = edges + outline->num_hedges; + touch_flag = ah_flah_touch_y; + + for ( dimension = 1; dimension >= 0; dimension-- ) + { + AH_Point* point; + AH_Edge* edge; + AH_Edge* before; + AH_Edge* after; + + before = 0; + after = 0; + + if ( edges < edge_limit ) + for ( point = points; point < point_limit; point++ ) + { + FT_Pos u, ou, fu; /* point position */ + FT_Pos delta; + + if ( point->flags & touch_flag ) + continue; + +#ifndef AH_OPTION_NO_WEAK_INTERPOLATION + /* if this point is candidate to weak interpolation, we'll */ + /* interpolate it after all strong points have been processed */ + if ( point->flags & ah_flah_weak_interpolation ) + continue; +#endif + + if (dimension) { u = point->fy; ou = point->oy; } + else { u = point->fx; ou = point->ox; } + + fu = u; + + /* is the point before the first edge ? */ + edge = edges; + delta = edge->fpos - u; + if (delta >= 0) + { + u = edge->pos - (edge->opos - ou); + goto Store_Point; + } + + /* is the point after the last edge ? */ + edge = edge_limit-1; + delta = u - edge->fpos; + if ( delta >= 0 ) + { + u = edge->pos + (ou - edge->opos); + goto Store_Point; + } + + /* otherwise, interpolate the point in between */ + { + AH_Edge* before = 0; + AH_Edge* after = 0; + + for ( edge = edges; edge < edge_limit; edge++ ) + { + if ( u == edge->fpos ) + { + u = edge->pos; + goto Store_Point; + } + if ( u < edge->fpos ) + break; + before = edge; + } + + for ( edge = edge_limit-1; edge >= edges; edge-- ) + { + if ( u == edge->fpos ) + { + u = edge->pos; + goto Store_Point; + } + if ( u > edge->fpos ) + break; + after = edge; + } + + /* assert( before && after && before != after ) */ + u = before->pos + FT_MulDiv( fu - before->fpos, + after->pos - before->pos, + after->fpos - before->fpos ); + } + Store_Point: + + /* save the point position */ + if (dimension) point->y = u; + else point->x = u; + + point->flags |= touch_flag; + } + + edges = outline->vert_edges; + edge_limit = edges + outline->num_vedges; + touch_flag = ah_flah_touch_x; + } + } + + +#ifndef AH_OPTION_NO_WEAK_INTERPOLATION + static + void ah_iup_shift( AH_Point* p1, + AH_Point* p2, + AH_Point* ref ) + { + AH_Point* p; + FT_Pos delta = ref->u - ref->v; + + for ( p = p1; p < ref; p++ ) + p->u = p->v + delta; + + for ( p = ref+1; p <= p2; p++ ) + p->u = p->v + delta; + } + + + static + void ah_iup_interp( AH_Point* p1, + AH_Point* p2, + AH_Point* ref1, + AH_Point* ref2 ) + { + AH_Point* p; + FT_Pos u; + FT_Pos v1 = ref1->v; + FT_Pos v2 = ref2->v; + FT_Pos d1 = ref1->u - v1; + FT_Pos d2 = ref2->u - v2; + + if (p1 > p2) return; + + if (v1 == v2) + { + for ( p = p1; p <= p2; p++ ) + { + FT_Pos u = p->v; + + if (u <= v1) u += d1; + else u += d2; + p->u = u; + } + return; + } + + if ( v1 < v2 ) + { + for ( p = p1; p <= p2; p++ ) + { + u = p->v; + + if (u <= v1) u += d1; + else if (u >= v2) u += d2; + else u = ref1->u+FT_MulDiv( u-v1, ref2->u-ref1->u, v2-v1 ); + + p->u = u; + } + } + else + { + for ( p = p1; p <= p2; p++ ) + { + u = p->v; + if (u <= v2) u += d2; + else if (u >= v1) u += d1; + else u = ref1->u+FT_MulDiv( u-v1, ref2->u-ref1->u, v2-v1 ); + + p->u = u; + } + } + } + + /* interpolate weak points - this is equivalent to the TrueType "IUP" */ + static + void ah_hinter_align_weak_points( AH_Hinter* hinter ) + { + AH_Outline* outline = hinter->glyph; + FT_Int dimension; + AH_Edge* edges; + AH_Edge* edge_limit; + AH_Point* points; + AH_Point* point_limit; + AH_Point** contour_limit; + AH_Flags touch_flag; + + points = outline->points; + point_limit = points + outline->num_points; + + /* PASS 1 : Move segment points to edge positions */ + + edges = outline->horz_edges; + edge_limit = edges + outline->num_hedges; + touch_flag = ah_flah_touch_y; + + contour_limit = outline->contours + outline->num_contours; + + ah_setup_uv( outline, ah_uv_oy ); + + for ( dimension = 1; dimension >= 0; dimension-- ) + { + AH_Point* point; + AH_Point* end_point; + AH_Point* first_point; + AH_Point** contour; + + point = points; + contour = outline->contours; + + for ( ; contour < contour_limit; contour++ ) + { + point = *contour; + end_point = point->prev; + first_point = point; + + while (point <= end_point && !(point->flags & touch_flag)) + point++; + + if (point <= end_point) + { + AH_Point* first_touched = point; + AH_Point* cur_touched = point; + + point++; + while ( point <= end_point ) + { + if (point->flags & touch_flag) + { + /* we found two succesive touched points, we interpolate */ + /* all contour points between them.. */ + ah_iup_interp( cur_touched+1, point-1, + cur_touched, point ); + cur_touched = point; + } + point++; + } + + if (cur_touched == first_touched) + { + /* this is a special case: only one point was touched in the */ + /* contour.. we thus simply shift the whole contour.. */ + ah_iup_shift( first_point, end_point, cur_touched ); + } + else + { + /* now interpolate after the last touched point to the end */ + /* of the contour.. */ + ah_iup_interp( cur_touched+1, end_point, + cur_touched, first_touched ); + + /* if the first contour point isn't touched, interpolate */ + /* from the contour start to the first touched point */ + if (first_touched > points) + ah_iup_interp( first_point, first_touched-1, + cur_touched, first_touched ); + } + } + } + + /* now save the interpolated values back to x/y */ + if (dimension) + { + for ( point = points; point < point_limit; point++ ) + point->y = point->u; + + touch_flag = ah_flah_touch_x; + ah_setup_uv( outline, ah_uv_ox ); + } + else + { + for ( point = points; point < point_limit; point++ ) + point->x = point->u; + + break; /* exit loop */ + } + } + } +#endif + + LOCAL_FUNC + void ah_hinter_align_points( AH_Hinter* hinter ) + { + ah_hinter_align_edge_points( hinter ); + +#ifndef AH_OPTION_NO_STRONG_INTERPOLATION + ah_hinter_align_strong_points( hinter ); +#endif + +#ifndef AH_OPTION_NO_WEAK_INTERPOLATION + ah_hinter_align_weak_points( hinter ); +#endif + } + + + /**************************************************************************/ + /**************************************************************************/ + /**************************************************************************/ + /**** ****/ + /**** H I N T E R O B J E C T M E T H O D S ****/ + /**** ****/ + /**** ****/ + /**************************************************************************/ + /**************************************************************************/ + /**************************************************************************/ + + /* scale and fit the global metrics */ + static + void ah_hinter_scale_globals( AH_Hinter* hinter, + FT_Fixed x_scale, + FT_Fixed y_scale ) + { + FT_Int n; + AH_Face_Globals* globals = hinter->globals; + AH_Globals* design = &globals->design; + AH_Globals* scaled = &globals->scaled; + + /* copy content */ + *scaled = *design; + + /* scale the standard widths & heights */ + for ( n = 0; n < design->num_widths; n++ ) + scaled->widths[n] = FT_MulFix( design->widths[n], x_scale ); + + for ( n = 0; n < design->num_heights; n++ ) + scaled->heights[n] = FT_MulFix( design->heights[n], y_scale ); + + /* scale the blue zones */ + for ( n = 0; n < ah_blue_max; n++ ) + { + FT_Pos delta, delta2; + + delta = design->blue_shoots[n] - design->blue_refs[n]; + delta2 = delta; if (delta < 0) delta2 = -delta2; + delta2 = FT_MulFix( delta2, y_scale ); + + if (delta2 < 32) + delta2 = 0; + else if (delta2 < 64) + delta2 = 32 + (((delta2-32)+16) & -32); + else + delta2 = (delta2+32) & -64; + + if (delta < 0) delta2 = -delta2; + + scaled->blue_refs [n] = (FT_MulFix(design->blue_refs[n],y_scale)+32) & -64; + scaled->blue_shoots[n] = scaled->blue_refs[n] + delta2; + } + } + + + static + void ah_hinter_align( AH_Hinter* hinter ) + { + ah_hinter_align_edge_points( hinter ); + ah_hinter_align_points( hinter ); + } + + + /* finalise a hinter object */ + void ah_hinter_done( AH_Hinter* hinter ) + { + if (hinter) + { + FT_Memory memory = hinter->memory; + + ah_loader_done( hinter->loader ); + ah_outline_done( hinter->glyph ); + + /* note: the globals pointer is _not_ owned by the hinter */ + /* but by the current face object, we don't need to */ + /* release it.. */ + hinter->globals = 0; + hinter->face = 0; + + FREE(hinter); + } + } + + + /* create a new empty hinter object */ + FT_Error ah_hinter_new( FT_Library library, AH_Hinter* *ahinter ) + { + AH_Hinter* hinter = 0; + FT_Memory memory = library->memory; + FT_Error error; + + *ahinter = 0; + + /* allocate object */ + if (ALLOC( hinter, sizeof(*hinter) )) goto Exit; + + hinter->memory = memory; + hinter->flags = 0; + + /* allocate outline and loader */ + error = ah_outline_new( memory, &hinter->glyph ) || + ah_loader_new ( memory, &hinter->loader ) || + ah_loader_create_extra( hinter->loader ); + if (error) goto Exit; + + *ahinter = hinter; + + Exit: + if (error) + ah_hinter_done( hinter ); + + return error; + } + + + /* create a face's autohint globals */ + FT_Error ah_hinter_new_face_globals( AH_Hinter* hinter, + FT_Face face, + AH_Globals* globals ) + { + FT_Error error; + FT_Memory memory = hinter->memory; + AH_Face_Globals* face_globals; + + if ( ALLOC( face_globals, sizeof(*face_globals) ) ) + goto Exit; + + hinter->face = face; + hinter->globals = face_globals; + if (globals) + face_globals->design = *globals; + else + ah_hinter_compute_globals( hinter ); + + face->autohint.data = face_globals; + face->autohint.finalizer = (FT_Generic_Finalizer)ah_hinter_done_face_globals; + face_globals->face = face; + + Exit: + return error; + } + + + + /* discard a face's autohint globals */ + void ah_hinter_done_face_globals( AH_Face_Globals* globals ) + { + FT_Face face = globals->face; + FT_Memory memory = face->memory; + + FREE( globals ); + } + + + + static + FT_Error ah_hinter_load( AH_Hinter* hinter, + FT_UInt glyph_index, + FT_UInt load_flags, + FT_UInt depth ) + { + FT_Face face = hinter->face; + FT_GlyphSlot slot = face->glyph; + FT_Fixed x_scale = face->size->metrics.x_scale; + FT_Fixed y_scale = face->size->metrics.y_scale; + FT_Glyph_Metrics metrics; /* temporary metrics */ + FT_Error error; + AH_Outline* outline = hinter->glyph; + AH_Loader* gloader = hinter->loader; + FT_Bool no_horz_hints = (load_flags & AH_HINT_NO_HORZ_EDGES) != 0; + FT_Bool no_vert_hints = (load_flags & AH_HINT_NO_VERT_EDGES) != 0; + + /* load the glyph */ + error = FT_Load_Glyph( face, glyph_index, load_flags ); + if (error) goto Exit; + + /* save current glyph metrics */ + metrics = slot->metrics; + + switch (slot->format) + { + case ft_glyph_format_outline: + { + /* first of all, copy the outline points in the loader's current */ + /* extra points, which is used to keep original glyph coordinates */ + error = ah_loader_check_points( gloader, slot->outline.n_points+2, + slot->outline.n_contours ); + if (error) goto Exit; + + MEM_Copy( gloader->current.extra_points, slot->outline.points, + slot->outline.n_points * sizeof(FT_Vector) ); + + MEM_Copy( gloader->current.outline.contours, slot->outline.contours, + slot->outline.n_contours*sizeof(short) ); + + MEM_Copy( gloader->current.outline.tags, slot->outline.tags, + slot->outline.n_points * sizeof(char) ); + + gloader->current.outline.n_points = slot->outline.n_points; + gloader->current.outline.n_contours = slot->outline.n_contours; + + /* compute original phantom points */ + hinter->pp1.x = 0; + hinter->pp1.y = 0; + hinter->pp2.x = FT_MulFix( slot->metrics.horiAdvance, x_scale ); + hinter->pp2.y = 0; + + /* be sure to check for spacing glyphs */ + if (slot->outline.n_points == 0) + goto Hint_Metrics; + + /* now, load the slot image into the auto-outline, and run the */ + /* automatic hinting process.. */ + error = ah_outline_load( outline, face ); /* XXXX: change to slot */ + if (error) goto Exit; + + /* perform feature detection */ + ah_outline_detect_features( outline ); + + if ( !no_horz_hints ) + { + ah_outline_compute_blue_edges( outline, hinter->globals ); + ah_outline_scale_blue_edges( outline, hinter->globals ); + } + + /* perform alignment control */ + ah_hinter_hint_edges( hinter, no_horz_hints, no_vert_hints ); + ah_hinter_align( hinter ); + + /* now save the current outline into the loader's current table */ + ah_outline_save( outline, gloader ); + + /* we now need to hint the metrics according to the change in */ + /* width/positioning that occured during the hinting process */ + { + FT_Pos old_width, new_width; + FT_Pos old_advance, new_advance; + FT_Pos old_lsb, new_lsb; + AH_Edge* edge1 = hinter->glyph->horz_edges; /* left-most edge */ + AH_Edge* edge2 = edge1 + hinter->glyph->num_hedges-1; /* right-mode edge */ + + old_width = edge2->opos - edge1->opos; + new_width = edge2->pos - edge1->pos; + + old_advance = hinter->pp2.x; + old_lsb = edge1->opos; + new_lsb = edge1->pos; + + new_advance = old_advance + (new_width+new_lsb-old_width-old_lsb); + + hinter->pp1.x = ((new_lsb - old_lsb)+32) & -64; + hinter->pp2.x = ((edge2->pos + (old_advance - edge2->opos))+32) & -64; + } + + /* good, we simply add the glyph to our loader's base */ + ah_loader_add( gloader ); + } + break; + + case ft_glyph_format_composite: + { + FT_UInt nn, num_subglyphs = slot->num_subglyphs; + FT_UInt num_base_subgs, start_point, start_contour; + FT_SubGlyph* subglyph; + + start_point = gloader->base.outline.n_points; + start_contour = gloader->base.outline.n_contours; + + /* first of all, copy the subglyph descriptors in the glyph loader */ + error = ah_loader_check_subglyphs( gloader, num_subglyphs ); + if (error) goto Exit; + + MEM_Copy( gloader->current.subglyphs, slot->subglyphs, + num_subglyphs*sizeof(FT_SubGlyph) ); + + gloader->current.num_subglyphs = num_subglyphs; + num_base_subgs = gloader->base.num_subglyphs; + + /* now, read each subglyph independently */ + for ( nn = 0; nn < num_subglyphs; nn++ ) + { + FT_Vector pp1, pp2; + FT_Pos x, y; + FT_UInt num_points, num_new_points, num_base_points; + + /* gloader.current.subglyphs can change during glyph loading due */ + /* to re-allocation. We must recompute the current subglyph on */ + /* each iteration.. */ + subglyph = gloader->base.subglyphs + num_base_subgs + nn; + + pp1 = hinter->pp1; + pp2 = hinter->pp2; + + num_base_points = gloader->base.outline.n_points; + + error = ah_hinter_load( hinter, subglyph->index, load_flags, depth+1 ); + if ( error ) goto Exit; + + /* recompute subglyph pointer */ + subglyph = gloader->base.subglyphs + num_base_subgs + nn; + + if ( subglyph->flags & FT_SUBGLYPH_FLAG_USE_MY_METRICS ) + { + pp1 = hinter->pp1; + pp2 = hinter->pp2; + } + else + { + hinter->pp1 = pp1; + hinter->pp2 = pp2; + } + + num_points = gloader->base.outline.n_points; + num_new_points = num_points - num_base_points; + + /* now perform the transform required for this subglyph */ + + if ( subglyph->flags & ( FT_SUBGLYPH_FLAG_SCALE | + FT_SUBGLYPH_FLAG_XY_SCALE | + FT_SUBGLYPH_FLAG_2X2 ) ) + { + FT_Vector* cur = gloader->base.outline.points + num_base_points; + FT_Vector* org = gloader->base.extra_points + num_base_points; + FT_Vector* limit = cur + num_new_points; + + for ( ; cur < limit; cur++, org++ ) + { + FT_Vector_Transform( cur, &subglyph->transform ); + FT_Vector_Transform( org, &subglyph->transform ); + } + } + + /* apply offset */ + + if ( !( subglyph->flags & FT_SUBGLYPH_FLAG_ARGS_ARE_XY_VALUES ) ) + { + FT_Int k = subglyph->arg1; + FT_UInt l = subglyph->arg2; + FT_Vector* p1; + FT_Vector* p2; + + if ( start_point + k >= num_base_points || + l >= (FT_UInt)num_new_points ) + { + error = FT_Err_Invalid_Composite; + goto Exit; + } + + l += num_base_points; + + /* for now, only use the current point coordinates */ + /* we may consider another approach in the near future */ + p1 = gloader->base.outline.points + start_point + k; + p2 = gloader->base.outline.points + start_point + l; + + x = p1->x - p2->x; + y = p1->y - p2->y; + } + else + { + x = FT_MulFix( subglyph->arg1, x_scale ); + y = FT_MulFix( subglyph->arg2, y_scale ); + + x = ( x + 32 ) & -64; + y = ( y + 32 ) & -64; + } + + { + FT_Outline dummy = gloader->base.outline; + dummy.points += num_base_points; + dummy.n_points = num_new_points; + + FT_Outline_Translate( &dummy, x, y ); + } + } + } + break; + + default: + /* we don't support other formats (yet ?) */ + error = FT_Err_Unimplemented_Feature; + } + + Hint_Metrics: + if (depth == 0) + { + FT_BBox bbox; + + /* we must translate our final outline by -pp1.x, and compute */ + /* the new metrics.. */ + if (hinter->pp1.x) + FT_Outline_Translate( &gloader->base.outline, -hinter->pp1.x, 0 ); + + FT_Outline_Get_CBox( &gloader->base.outline, &bbox ); + bbox.xMin &= -64; + bbox.yMin &= -64; + bbox.xMax = (bbox.xMax+63) & -64; + bbox.yMax = (bbox.yMax+63) & -64; + + slot->metrics.width = (bbox.xMax - bbox.xMin); + slot->metrics.height = (bbox.yMax - bbox.yMin); + slot->metrics.horiBearingX = bbox.xMin; + slot->metrics.horiBearingY = bbox.yMax; + slot->metrics.horiAdvance = hinter->pp2.x - hinter->pp1.x; + /* XXXX: TO DO - slot->linearHoriAdvance */ + + /* now copy outline into glyph slot */ + ah_loader_rewind( slot->loader ); + error = ah_loader_copy_points( slot->loader, gloader ); + if (error) goto Exit; + + slot->outline = slot->loader->base.outline; + slot->format = ft_glyph_format_outline; + } + + Exit: + return error; + } + + + /* load and hint a given glyph */ + FT_Error ah_hinter_load_glyph( AH_Hinter* hinter, + FT_GlyphSlot slot, + FT_Size size, + FT_UInt glyph_index, + FT_Int load_flags ) + { + FT_Face face = slot->face; + FT_Error error; + FT_Fixed x_scale = size->metrics.x_scale; + FT_Fixed y_scale = size->metrics.y_scale; + AH_Face_Globals* face_globals = FACE_GLOBALS(face); + + /* first of all, we need to check that we're using the correct face and */ + /* global hints to load the glyph */ + if ( hinter->face != face || hinter->globals != face_globals ) + { + hinter->face = face; + if (!face_globals) + { + error = ah_hinter_new_face_globals( hinter, face, 0 ); + if (error) goto Exit; + } + hinter->globals = FACE_GLOBALS(face); + face_globals = FACE_GLOBALS(face); + } + + /* now, we must check the current character pixel size to see if we need */ + /* to rescale the global metrics.. */ + if ( face_globals->x_scale != x_scale || + face_globals->y_scale != y_scale ) + { + ah_hinter_scale_globals( hinter, x_scale, y_scale ); + } + + load_flags |= FT_LOAD_NO_SCALE | FT_LOAD_NO_RECURSE; + + ah_loader_rewind( hinter->loader ); + + error = ah_hinter_load( hinter, glyph_index, load_flags, 0 ); + + Exit: + return error; + } + + + /* retrieve a face's autohint globals for client applications */ + void ah_hinter_get_global_hints( AH_Hinter* hinter, + FT_Face face, + void* *global_hints, + long *global_len ) + { + AH_Globals* globals = 0; + FT_Memory memory = hinter->memory; + FT_Error error; + + /* allocate new master globals */ + if (ALLOC( globals, sizeof(*globals))) + goto Fail; + + /* compute face globals if needed */ + if (!FACE_GLOBALS(face)) + { + error = ah_hinter_new_face_globals( hinter, face, 0 ); + if (error) goto Fail; + } + + *globals = FACE_GLOBALS(face)->design; + *global_hints = globals; + *global_len = sizeof(*globals); + return; + + Fail: + FREE( globals ); + *global_hints = 0; + *global_len = 0; + } + + + void ah_hinter_done_global_hints( AH_Hinter* hinter, + void* global_hints ) + { + FT_Memory memory = hinter->memory; + FREE( global_hints ); + } + + diff --git a/src/autohint/ahhint.h b/src/autohint/ahhint.h new file mode 100644 index 000000000..212743392 --- /dev/null +++ b/src/autohint/ahhint.h @@ -0,0 +1,65 @@ +/***************************************************************************/ +/* */ +/* ahhint.h */ +/* */ +/* Glyph hinter declarations */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ +#ifndef AGHINT_H +#define AGHINT_H + +#ifdef FT_FLAT_COMPILE +#include "ahglobal.h" +#else +#include +#endif + +#define AH_HINT_DEFAULT 0 +#define AH_HINT_NO_ALIGNMENT 1 +#define AH_HINT_NO_HORZ_EDGES 0x20000 +#define AH_HINT_NO_VERT_EDGES 0x40000 + + + + /* create a new empty hinter object */ + extern + FT_Error ah_hinter_new( FT_Library library, AH_Hinter* *ahinter ); + + /* Load a hinted glyph in the hinter */ + extern + FT_Error ah_hinter_load_glyph( AH_Hinter* hinter, + FT_GlyphSlot slot, + FT_Size size, + FT_UInt glyph_index, + FT_Int load_flags ); + + /* finalise a hinter object */ + extern + void ah_hinter_done( AH_Hinter* hinter ); + + LOCAL_DEF + void ah_hinter_done_face_globals( AH_Face_Globals* globals ); + + extern + void ah_hinter_get_global_hints( AH_Hinter* hinter, + FT_Face face, + void* *global_hints, + long *global_len ); + + extern + void ah_hinter_done_global_hints( AH_Hinter* hinter, + void* global_hints ); + +#endif /* AGHINT_H */ diff --git a/src/autohint/ahloader.h b/src/autohint/ahloader.h new file mode 100644 index 000000000..68f3e22b2 --- /dev/null +++ b/src/autohint/ahloader.h @@ -0,0 +1,103 @@ +/***************************************************************************/ +/* */ +/* ahloader.h */ +/* */ +/* Glyph loader implementation for the auto-hinting module */ +/* This defines the AG_GlyphLoader type in two different ways: */ +/* */ +/* - when the module is compiled within FreeType 2, the type */ +/* is simply a typedef to FT_GlyphLoader */ +/* */ +/* - when the module is compiled as a standalone object, */ +/* AG_GlyphLoader has its own implementation.. */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ +#ifndef AGLOADER_H +#define AGLOADER_H + +#ifdef _STANDALONE_ + + typedef struct AH_GlyphLoad_ + { + FT_Outline outline; /* outline */ + FT_UInt num_subglyphs; /* number of subglyphs */ + FT_SubGlyph* subglyphs; /* subglyphs */ + FT_Vector* extra_points; /* extra points table.. */ + + } AH_GlyphLoad; + + + struct AH_GlyphLoader_ + { + FT_Memory memory; + FT_UInt max_points; + FT_UInt max_contours; + FT_UInt max_subglyphs; + FT_Bool use_extra; + + AH_GlyphLoad base; + AH_GlyphLoad current; + + void* other; /* for possible future extension ? */ + + }; + + + LOCAL_DEF FT_Error AH_GlyphLoader_New( FT_Memory memory, + AH_GlyphLoader* *aloader ); + + LOCAL_DEF FT_Error AH_GlyphLoader_Create_Extra( AH_GlyphLoader* loader ); + + LOCAL_DEF void AH_GlyphLoader_Done( AH_GlyphLoader* loader ); + + LOCAL_DEF void AH_GlyphLoader_Reset( AH_GlyphLoader* loader ); + + LOCAL_DEF void AH_GlyphLoader_Rewind( AH_GlyphLoader* loader ); + + LOCAL_DEF FT_Error AH_GlyphLoader_Check_Points( AH_GlyphLoader* loader, + FT_UInt n_points, + FT_UInt n_contours ); + + LOCAL_DEF FT_Error AH_GlyphLoader_Check_Subglyphs( AH_GlyphLoader* loader, + FT_UInt n_subs ); + + LOCAL_DEF void AH_GlyphLoader_Prepare( AH_GlyphLoader* loader ); + + + LOCAL_DEF void AH_GlyphLoader_Add( AH_GlyphLoader* loader ); + + LOCAL_DEF FT_Error AH_GlyphLoader_Copy_Points( AH_GlyphLoader* target, + FT_GlyphLoader* source ); + +#else +#include + + #define AH_Load FT_GlyphLoad + #define AH_Loader FT_GlyphLoader + + #define ah_loader_new FT_GlyphLoader_New + #define ah_loader_done FT_GlyphLoader_Done + #define ah_loader_reset FT_GlyphLoader_Reset + #define ah_loader_rewind FT_GlyphLoader_Rewind + #define ah_loader_create_extra FT_GlyphLoader_Create_Extra + #define ah_loader_check_points FT_GlyphLoader_Check_Points + #define ah_loader_check_subglyphs FT_GlyphLoader_Check_Subglyphs + #define ah_loader_prepare FT_GlyphLoader_Prepare + #define ah_loader_add FT_GlyphLoader_Add + #define ah_loader_copy_points FT_GlyphLoader_Copy_Points + +#endif + +#endif /* AGLOADER_H */ diff --git a/src/autohint/ahmodule.c b/src/autohint/ahmodule.c new file mode 100644 index 000000000..887d03d08 --- /dev/null +++ b/src/autohint/ahmodule.c @@ -0,0 +1,111 @@ +/***************************************************************************/ +/* */ +/* ahmodule.c */ +/* */ +/* Auto-hinting module implementation */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ +#include + +#ifdef FT_FLAT_COMPILE +#include "ahhint.h" +#else +#include +#endif + + + typedef struct FT_AutoHinterRec_ + { + FT_ModuleRec root; + AH_Hinter* hinter; + + } FT_AutoHinterRec; + + + static + FT_Error ft_autohinter_init( FT_AutoHinter module ) + { + return ah_hinter_new( module->root.library, &module->hinter ); + } + + static + void ft_autohinter_done( FT_AutoHinter module ) + { + ah_hinter_done( module->hinter ); + } + + static + FT_Error ft_autohinter_load( FT_AutoHinter module, + FT_GlyphSlot slot, + FT_Size size, + FT_UInt glyph_index, + FT_ULong load_flags ) + { + return ah_hinter_load_glyph( module->hinter, + slot, size, glyph_index, load_flags ); + } + + static + void ft_autohinter_reset( FT_AutoHinter module, + FT_Face face ) + { + UNUSED(module); + if (face->autohint.data) + ah_hinter_done_face_globals( face->autohint.data ); + } + + static + void ft_autohinter_get_globals( FT_AutoHinter module, + FT_Face face, + void* *global_hints, + long *global_len ) + { + ah_hinter_get_global_hints( module->hinter, face, + global_hints, global_len ); + } + + + static + void ft_autohinter_done_globals( FT_AutoHinter module, + void* global_hints ) + { + ah_hinter_done_global_hints( module->hinter, global_hints ); + } + + + static + const FT_AutoHinter_Interface autohinter_interface = + { + ft_autohinter_reset, + ft_autohinter_load, + ft_autohinter_get_globals, + ft_autohinter_done_globals + }; + + const FT_Module_Class autohint_module_class = + { + ft_module_hinter, + sizeof( FT_AutoHinterRec ), + + "autohinter", + 0x10000, /* version 1.0 of the autohinter */ + 0x20000, /* requires FreeType 2.0 or above */ + + (const void*)&autohinter_interface, + + (FT_Module_Constructor) ft_autohinter_init, + (FT_Module_Destructor) ft_autohinter_done, + (FT_Module_Requester) 0 + }; diff --git a/src/autohint/ahmodule.h b/src/autohint/ahmodule.h new file mode 100644 index 000000000..7a5789619 --- /dev/null +++ b/src/autohint/ahmodule.h @@ -0,0 +1,27 @@ +/***************************************************************************/ +/* */ +/* ahmodule.h */ +/* */ +/* Auto-hinting module declaration */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ +#ifndef AHMODULE_H +#define AHMODULE_H + +#include + + FT_EXPORT_VAR(const FT_Module_Class) autohint_module_class; + +#endif /* AHMODULE_H */ diff --git a/src/autohint/ahoptim.c b/src/autohint/ahoptim.c new file mode 100644 index 000000000..daf49f6a9 --- /dev/null +++ b/src/autohint/ahoptim.c @@ -0,0 +1,808 @@ +/***************************************************************************/ +/* */ +/* FreeType Auto-Gridder Outline Optimisation */ +/* */ +/* This module is in charge of optimising the outlines produced by the */ +/* auto-hinter in direct mode. This is required at small pixel sizes in */ +/* order to ensure coherent spacing, among other things.. */ +/* */ +/* The technique used in this module is a simplified simulated annealing. */ +/* */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ + +#include /* for ALLOC_ARRAY and FREE */ + +#ifdef FT_FLAT_COMPILE +#include "ahoptim.h" +#else +#include +#endif + +/* define this macro to use brute force optimisation, this is slow, but */ +/* a good way to perfect the distortion function "by hand" through */ +/* tweaking.. */ +#define BRUTE_FORCE + +#define xxxDEBUG_OPTIM + +#undef LOG +#ifdef DEBUG_OPTIM +#define LOG(x) optim_log##x +#else +#define LOG(x) +#endif + +#ifdef DEBUG_OPTIM +#include +#include +#include + +#define FLOAT(x) ((float)((x)/64.0)) + +static +void optim_log( const char* fmt, ... ) +{ + va_list ap; + + + va_start( ap, fmt ); + vprintf( fmt, ap ); + va_end( ap ); +} +#endif + + +#ifdef DEBUG_OPTIM + static + void AH_Dump_Stems( AH_Optimizer* optimizer ) + { + int n; + AH_Stem* stem; + + stem = optimizer->stems; + for ( n = 0; n < optimizer->num_stems; n++, stem++ ) + { + LOG(( " %c%2d [%.1f:%.1f]={%.1f:%.1f}=<%1.f..%1.f> force=%.1f speed=%.1f\n", + optimizer->vertical ? 'V' : 'H', n, + FLOAT(stem->edge1->opos), FLOAT(stem->edge2->opos), + FLOAT(stem->edge1->pos), FLOAT(stem->edge2->pos), + FLOAT(stem->min_pos), FLOAT(stem->max_pos), + FLOAT(stem->force), FLOAT(stem->velocity) )); + } + } + + static + void AH_Dump_Stems2( AH_Optimizer* optimizer ) + { + int n; + AH_Stem* stem; + + stem = optimizer->stems; + for ( n = 0; n < optimizer->num_stems; n++, stem++ ) + { + LOG(( " %c%2d [%.1f]=<%1.f..%1.f> force=%.1f speed=%.1f\n", + optimizer->vertical ? 'V' : 'H', n, + FLOAT(stem->pos), + FLOAT(stem->min_pos), FLOAT(stem->max_pos), + FLOAT(stem->force), FLOAT(stem->velocity) )); + } + } + + static + void AH_Dump_Springs( AH_Optimizer* optimizer ) + { + int n; + AH_Spring* spring; + AH_Stem* stems; + + spring = optimizer->springs; + stems = optimizer->stems; + LOG(( "%cSprings ", optimizer->vertical ? 'V' : 'H' )); + for ( n = 0; n < optimizer->num_springs; n++, spring++ ) + { + LOG(( " [%d-%d:%.1f:%1.f:%.1f]", spring->stem1 - stems, spring->stem2 - stems, + FLOAT(spring->owidth), + FLOAT(spring->stem2->pos-(spring->stem1->pos+spring->stem1->width)), + FLOAT(spring->tension) )); + } + + LOG(( "\n" )); + } +#endif + + + /*************************************************************************/ + /*************************************************************************/ + /*************************************************************************/ + /**** ****/ + /**** COMPUTE STEMS AND SPRINGS IN AN OUTLINE ****/ + /**** ****/ + /**** ****/ + /**** ****/ + /*************************************************************************/ + /*************************************************************************/ + /*************************************************************************/ + + static + int valid_stem_segments( AH_Segment* seg1, AH_Segment* seg2 ) + { + return seg1->serif == 0 && seg2 && seg2->link == seg1 && seg1->pos < seg2->pos && + seg1->min_coord <= seg2->max_coord && + seg2->min_coord <= seg1->max_coord; + } + + /* compute all stems in an outline */ + static + int optim_compute_stems( AH_Optimizer* optimizer ) + { + AH_Outline* outline = optimizer->outline; + FT_Fixed scale; + FT_Memory memory = optimizer->memory; + FT_Error error = 0; + FT_Int dimension; + AH_Edge* edges; + AH_Edge* edge_limit; + AH_Stem** p_stems; + FT_Int* p_num_stems; + + edges = outline->horz_edges; + edge_limit = edges + outline->num_hedges; + scale = outline->y_scale; + + p_stems = &optimizer->horz_stems; + p_num_stems = &optimizer->num_hstems; + + for ( dimension = 1; dimension >= 0; dimension-- ) + { + AH_Stem* stems = 0; + FT_Int num_stems = 0; + AH_Edge* edge; + + /* first of all, count the number of stems in this direction */ + for ( edge = edges; edge < edge_limit; edge++ ) + { + AH_Segment* seg = edge->first; + do + { + if (valid_stem_segments( seg, seg->link )) + num_stems++; + + seg = seg->edge_next; + + } while (seg != edge->first); + } + + /* now allocate the stems and build their table */ + if (num_stems > 0) + { + AH_Stem* stem; + + if ( ALLOC_ARRAY( stems, num_stems, AH_Stem ) ) + goto Exit; + + stem = stems; + for ( edge = edges; edge < edge_limit; edge++ ) + { + AH_Segment* seg = edge->first; + AH_Segment* seg2; + do + { + seg2 = seg->link; + if (valid_stem_segments(seg,seg2)) + { + AH_Edge* edge1 = seg->edge; + AH_Edge* edge2 = seg2->edge; + + stem->edge1 = edge1; + stem->edge2 = edge2; + stem->opos = edge1->opos; + stem->pos = edge1->pos; + stem->owidth = edge2->opos - edge1->opos; + stem->width = edge2->pos - edge1->pos; + + /* compute min_coord and max_coord */ + { + FT_Pos min_coord = seg->min_coord; + FT_Pos max_coord = seg->max_coord; + + if (seg2->min_coord > min_coord) + min_coord = seg2->min_coord; + + if (seg2->max_coord < max_coord) + max_coord = seg2->max_coord; + + stem->min_coord = min_coord; + stem->max_coord = max_coord; + } + + /* compute minimum and maximum positions for stem */ + /* note that the left-most/bottom-most stem has always */ + /* a fixed position.. */ + if (stem == stems || edge1->blue_edge || edge2->blue_edge) + { + /* this stem cannot move, it is snapped to a blue edge */ + stem->min_pos = stem->pos; + stem->max_pos = stem->pos; + } + else + { + /* this edge can move, compute its min and max positions */ + FT_Pos pos1 = stem->opos; + FT_Pos pos2 = pos1 + stem->owidth - stem->width; + FT_Pos min1 = (pos1 & -64); + FT_Pos min2 = (pos2 & -64); + + stem->min_pos = min1; + stem->max_pos = min1+64; + if (min2 < min1) + stem->min_pos = min2; + else + stem->max_pos = min2+64; + + /* XXX : just to see what it does */ + stem->max_pos += 64; + + /* just for the case where direct hinting did some incredible */ + /* things (e.g. blue edge shifts..) */ + if (stem->min_pos > stem->pos) + stem->min_pos = stem->pos; + + if (stem->max_pos < stem->pos) + stem->max_pos = stem->pos; + } + + stem->velocity = 0; + stem->force = 0; + + stem++; + } + seg = seg->edge_next; + } + while (seg != edge->first); + } + } + + *p_stems = stems; + *p_num_stems = num_stems; + + edges = outline->vert_edges; + edge_limit = edges + outline->num_vedges; + scale = outline->x_scale; + + p_stems = &optimizer->vert_stems; + p_num_stems = &optimizer->num_vstems; + } + Exit: + #ifdef DEBUG_OPTIM + AH_Dump_Stems(optimizer); + #endif + return error; + } + + + /* returns the spring area between two stems, 0 if none */ + static + FT_Pos stem_spring_area( AH_Stem* stem1, AH_Stem* stem2 ) + { + FT_Pos area1 = stem1->max_coord - stem1->min_coord; + FT_Pos area2 = stem2->max_coord - stem2->min_coord; + FT_Pos min = stem1->min_coord; + FT_Pos max = stem1->max_coord; + FT_Pos area; + + /* order stems */ + if (stem2->opos <= stem1->opos + stem1->owidth) + return 0; + + if (min < stem2->min_coord) + min = stem2->min_coord; + + if (max < stem2->max_coord) + max = stem2->max_coord; + + area = (max-min); + if ( 2*area < area1 && 2*area < area2 ) + area = 0; + + return area; + } + + + /* compute all springs in an outline */ + static + int optim_compute_springs( AH_Optimizer* optimizer ) + { + /* basically, a spring exists between two stems if most of their */ + /* surface is aligned.. */ + FT_Memory memory = optimizer->memory; + + AH_Stem* stems; + AH_Stem* stem_limit; + AH_Stem* stem; + int dimension; + int error = 0; + + FT_Int* p_num_springs; + AH_Spring** p_springs; + + stems = optimizer->horz_stems; + stem_limit = stems + optimizer->num_hstems; + + p_springs = &optimizer->horz_springs; + p_num_springs = &optimizer->num_hsprings; + + for ( dimension = 1; dimension >= 0; dimension-- ) + { + FT_Int num_springs = 0; + AH_Spring* springs = 0; + + /* first of all, count stem springs */ + for ( stem = stems; stem+1 < stem_limit; stem++ ) + { + AH_Stem* stem2; + for ( stem2 = stem+1; stem2 < stem_limit; stem2++ ) + if (stem_spring_area(stem,stem2)) + num_springs++; + } + + /* then allocate and build the springs table */ + if (num_springs > 0) + { + AH_Spring* spring; + + /* allocate table of springs */ + if ( ALLOC_ARRAY( springs, num_springs, AH_Spring ) ) + goto Exit; + + /* fill the springs table */ + spring = springs; + for ( stem = stems; stem+1 < stem_limit; stem++ ) + { + AH_Stem* stem2; + FT_Pos area; + + for ( stem2 = stem+1; stem2 < stem_limit; stem2++ ) + { + area = stem_spring_area(stem,stem2); + if (area) + { + /* add a new spring here */ + spring->stem1 = stem; + spring->stem2 = stem2; + spring->owidth = stem2->opos - (stem->opos + stem->owidth); + spring->tension = 0; + + spring++; + } + } + } + } + *p_num_springs = num_springs; + *p_springs = springs; + + stems = optimizer->vert_stems; + stem_limit = stems + optimizer->num_vstems; + + p_springs = &optimizer->vert_springs; + p_num_springs = &optimizer->num_vsprings; + } + + Exit: + #ifdef DEBUG_OPTIM + AH_Dump_Springs(optimizer); + #endif + return error; + } + + /*************************************************************************/ + /*************************************************************************/ + /*************************************************************************/ + /**** ****/ + /**** OPTIMISE THROUGH MY STRANGE SIMULATED ANNEALING ALGO ;-) ****/ + /**** ****/ + /**** ****/ + /**** ****/ + /*************************************************************************/ + /*************************************************************************/ + /*************************************************************************/ + +#ifndef BRUTE_FORCE + /* compute all spring tensions */ + static + void optim_compute_tensions( AH_Optimizer* optimizer ) + { + AH_Spring* spring = optimizer->springs; + AH_Spring* limit = spring + optimizer->num_springs; + for ( ; spring < limit; spring++ ) + { + AH_Stem* stem1 = spring->stem1; + AH_Stem* stem2 = spring->stem2; + FT_Int status; + + FT_Pos width; + FT_Pos tension; + FT_Pos sign; + + /* compute the tension, it simply is -K*(new_width-old_width) */ + width = stem2->pos - (stem1->pos + stem1->width); + tension = width - spring->owidth; + + sign = 1; + if (tension < 0) + { + sign = -1; + tension = -tension; + } + + if (width <= 0) + tension = 32000; + else + tension = (tension << 10)/width; + + tension = -sign*FT_MulFix( tension, optimizer->tension_scale ); + spring->tension = tension; + + /* now, distribute tension among the englobing stems, if they */ + /* are able to move.. */ + status = 0; + if (stem1->pos <= stem1->min_pos) + status |= 1; + if (stem2->pos >= stem2->max_pos) + status |= 2; + + if (!status) + tension /= 2; + + if ((status & 1)== 0) + stem1->force -= tension; + + if ((status & 2)== 0) + stem2->force += tension; + } + } + + + + /* compute all stem movements - returns 0 if nothing moved */ + static + int optim_compute_stem_movements( AH_Optimizer* optimizer ) + { + AH_Stem* stems = optimizer->stems; + AH_Stem* limit = stems + optimizer->num_stems; + AH_Stem* stem = stems; + int moved = 0; + + /* set initial forces to velocity */ + for ( stem = stems; stem < limit; stem++ ) + { + stem->force = stem->velocity; + stem->velocity /= 2; /* XXXX: Heuristics */ + } + + /* compute the sum of forces applied on each stem */ + optim_compute_tensions( optimizer ); + #ifdef DEBUG_OPTIM + AH_Dump_Springs( optimizer ); + AH_Dump_Stems2( optimizer ); + #endif + + /* now, see if something can move ? */ + for ( stem = stems; stem < limit; stem++ ) + { + if (stem->force > optimizer->tension_threshold) + { + /* there is enough tension to move the stem to the right */ + if (stem->pos < stem->max_pos) + { + stem->pos += 64; + stem->velocity = stem->force/2; + moved = 1; + } + else + stem->velocity = 0; + } + else if (stem->force < optimizer->tension_threshold) + { + /* there is enough tension to move the stem to the left */ + if (stem->pos > stem->min_pos) + { + stem->pos -= 64; + stem->velocity = stem->force/2; + moved = 1; + } + else + stem->velocity = 0; + } + } + /* return 0 if nothing moved */ + return moved; + } + +#endif /* BRUTE_FORCE */ + + + /* compute current global distortion from springs */ + static + FT_Pos optim_compute_distorsion( AH_Optimizer* optimizer ) + { + AH_Spring* spring = optimizer->springs; + AH_Spring* limit = spring + optimizer->num_springs; + FT_Pos distorsion = 0; + + for ( ; spring < limit; spring++ ) + { + AH_Stem* stem1 = spring->stem1; + AH_Stem* stem2 = spring->stem2; + FT_Pos width; + + width = stem2->pos - (stem1->pos + stem1->width); + width -= spring->owidth; + if (width < 0) + width = -width; + + distorsion += width; + } + return distorsion; + } + + + /* record stems configuration in "best of" history */ + static + void optim_record_configuration( AH_Optimizer* optimizer ) + { + FT_Pos distorsion; + AH_Configuration* configs = optimizer->configs; + AH_Configuration* limit = configs + optimizer->num_configs; + AH_Configuration* config; + + distorsion = optim_compute_distorsion( optimizer ); + LOG(( "config distorsion = %.1f ", FLOAT(distorsion*64) )); + + /* check that we really need to add this configuration to our */ + /* sorted history.. */ + if ( limit > configs && limit[-1].distorsion < distorsion ) + { + LOG(( "ejected\n" )); + return; + } + + /* add new configuration at the end of the table */ + { + int n; + + config = limit; + if (optimizer->num_configs < AH_MAX_CONFIGS) + optimizer->num_configs++; + else + config--; + + config->distorsion = distorsion; + + for ( n = 0; n < optimizer->num_stems; n++ ) + config->positions[n] = optimizer->stems[n].pos; + } + + /* move the current configuration towards the front of the list */ + /* when necessary, yes this is slow bubble sort ;-) */ + while ( config > configs && config[0].distorsion < config[-1].distorsion ) + { + AH_Configuration temp; + config--; + temp = config[0]; + config[0] = config[1]; + config[1] = temp; + } + LOG(( "recorded !!\n" )); + } + + +#ifdef BRUTE_FORCE + /* optimize outline in a single direction */ + static + void optim_compute( AH_Optimizer* optimizer ) + { + int n; + FT_Bool moved; + + + AH_Stem* stem = optimizer->stems; + AH_Stem* limit = stem + optimizer->num_stems; + + /* empty, exit */ + if (stem >= limit) + return; + + optimizer->num_configs = 0; + + stem = optimizer->stems; + for ( ; stem < limit; stem++ ) + stem->pos = stem->min_pos; + + do + { + /* record current configuration */ + optim_record_configuration(optimizer); + + /* now change configuration */ + moved = 0; + for ( stem = optimizer->stems; stem < limit; stem++ ) + { + if (stem->pos < stem->max_pos) + { + stem->pos += 64; + moved = 1; + break; + } + + stem->pos = stem->min_pos; + } + } + while (moved); + + /* now, set the best stem positions */ + for ( n = 0; n < optimizer->num_stems; n++ ) + { + AH_Stem* stem = optimizer->stems + n; + FT_Pos pos = optimizer->configs[0].positions[n]; + + stem->edge1->pos = pos; + stem->edge2->pos = pos + stem->width; + + stem->edge1->flags |= ah_edge_done; + stem->edge2->flags |= ah_edge_done; + } + } +#else + /* optimize outline in a single direction */ + static + void optim_compute( AH_Optimizer* optimizer ) + { + int n, counter, counter2; + + optimizer->num_configs = 0; + optimizer->tension_scale = 0x80000L; + optimizer->tension_threshold = 64; + + /* record initial configuration threshold */ + optim_record_configuration(optimizer); + counter = 0; + for ( counter2 = optimizer->num_stems*8; counter2 >= 0; counter2-- ) + { + if (counter == 0) + counter = 2*optimizer->num_stems; + + if (!optim_compute_stem_movements( optimizer )) + break; + + optim_record_configuration(optimizer); + counter--; + if (counter == 0) + optimizer->tension_scale /= 2; + } + + /* now, set the best stem positions */ + for ( n = 0; n < optimizer->num_stems; n++ ) + { + AH_Stem* stem = optimizer->stems + n; + FT_Pos pos = optimizer->configs[0].positions[n]; + + stem->edge1->pos = pos; + stem->edge2->pos = pos + stem->width; + + stem->edge1->flags |= ah_edge_done; + stem->edge2->flags |= ah_edge_done; + } + } +#endif + + /*************************************************************************/ + /*************************************************************************/ + /*************************************************************************/ + /**** ****/ + /**** HIGH-LEVEL OPTIMIZER API ****/ + /**** ****/ + /**** ****/ + /**** ****/ + /*************************************************************************/ + /*************************************************************************/ + /*************************************************************************/ + + + /* releases the optimisation data */ + void AH_Optimizer_Done( AH_Optimizer* optimizer ) + { + if (optimizer) + { + FT_Memory memory = optimizer->memory; + FREE( optimizer->horz_stems ); + FREE( optimizer->vert_stems ); + FREE( optimizer->horz_springs ); + FREE( optimizer->vert_springs ); + FREE( optimizer->positions ); + } + } + + /* loads the outline into the optimizer */ + int AH_Optimizer_Init( AH_Optimizer* optimizer, + AH_Outline* outline, + FT_Memory memory ) + { + FT_Error error; + + MEM_Set( optimizer, 0, sizeof(*optimizer)); + optimizer->outline = outline; + optimizer->memory = memory; + + LOG(( "initializing new optimizer\n" )); + /* compute stems and springs */ + error = optim_compute_stems ( optimizer ) || + optim_compute_springs( optimizer ); + if (error) goto Fail; + + /* allocate stem positions history and configurations */ + { + int n, max_stems; + + max_stems = optimizer->num_hstems; + if (max_stems < optimizer->num_vstems) + max_stems = optimizer->num_vstems; + + if ( ALLOC_ARRAY( optimizer->positions, max_stems*AH_MAX_CONFIGS, FT_Pos ) ) + goto Fail; + + optimizer->num_configs = 0; + for ( n = 0; n < AH_MAX_CONFIGS; n++ ) + optimizer->configs[n].positions = optimizer->positions + n*max_stems; + } + + return error; + + Fail: + AH_Optimizer_Done( optimizer ); + return error; + } + + + /* compute optimal outline */ + void AH_Optimizer_Compute( AH_Optimizer* optimizer ) + { + optimizer->num_stems = optimizer->num_hstems; + optimizer->stems = optimizer->horz_stems; + optimizer->num_springs = optimizer->num_hsprings; + optimizer->springs = optimizer->horz_springs; + + if (optimizer->num_springs > 0) + { + LOG(( "horizontal optimisation ------------------------\n" )); + optim_compute( optimizer ); + } + + optimizer->num_stems = optimizer->num_vstems; + optimizer->stems = optimizer->vert_stems; + optimizer->num_springs = optimizer->num_vsprings; + optimizer->springs = optimizer->vert_springs; + + if (optimizer->num_springs) + { + LOG(( "vertical optimisation --------------------------\n" )); + optim_compute( optimizer ); + } + } + + + + + diff --git a/src/autohint/ahoptim.h b/src/autohint/ahoptim.h new file mode 100644 index 000000000..3ff82337a --- /dev/null +++ b/src/autohint/ahoptim.h @@ -0,0 +1,137 @@ +/***************************************************************************/ +/* */ +/* FreeType Auto-Gridder Outline Optimisation */ +/* */ +/* This module is in charge of optimising the outlines produced by the */ +/* auto-hinter in direct mode. This is required at small pixel sizes in */ +/* order to ensure coherent spacing, among other things.. */ +/* */ +/* The technique used in this module is a simplified simulated annealing. */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ + +#ifndef AGOPTIM_H +#define AGOPTIM_H + +#ifdef FT_FLAT_COMPILE +#include "ahtypes.h" +#else +#include +#endif + +/* the maximal number of stem configurations to record during optimisation */ +#define AH_MAX_CONFIGS 8 + + + typedef struct AH_Stem_ + { + FT_Pos pos; /* current position */ + FT_Pos velocity; /* current velocity */ + FT_Pos force; /* sum of current forces */ + FT_Pos width; /* normalized width */ + + FT_Pos min_pos; /* minimum grid position */ + FT_Pos max_pos; /* maximum grid position */ + + AH_Edge* edge1; /* left/bottom edge */ + AH_Edge* edge2; /* right/top edge */ + + FT_Pos opos; /* original position */ + FT_Pos owidth; /* original width */ + + FT_Pos min_coord; /* minimum coordinate */ + FT_Pos max_coord; /* maximum coordinate */ + + } AH_Stem; + + + /* A spring between two stems */ + typedef struct AH_Spring_ + { + AH_Stem* stem1; + AH_Stem* stem2; + FT_Pos owidth; /* original width */ + FT_Pos tension; /* current tension */ + + } AH_Spring; + + + /* A configuration records the position of each stem at a given time */ + /* as well as the associated distortion.. */ + typedef struct AH_Configuration_ + { + FT_Pos* positions; + FT_Long distorsion; + + } AH_Configuration; + + + + + typedef struct AH_Optimizer_ + { + FT_Memory memory; + AH_Outline* outline; + + FT_Int num_hstems; + AH_Stem* horz_stems; + + FT_Int num_vstems; + AH_Stem* vert_stems; + + FT_Int num_hsprings; + FT_Int num_vsprings; + AH_Spring* horz_springs; + AH_Spring* vert_springs; + + FT_Int num_configs; + AH_Configuration configs[ AH_MAX_CONFIGS ]; + FT_Pos* positions; + + /* during each pass, use these instead */ + FT_Int num_stems; + AH_Stem* stems; + + FT_Int num_springs; + AH_Spring* springs; + FT_Bool vertical; + + FT_Fixed tension_scale; + FT_Pos tension_threshold; + + } AH_Optimizer; + + + /* loads the outline into the optimizer */ + extern + int AH_Optimizer_Init( AH_Optimizer* optimizer, + AH_Outline* outline, + FT_Memory memory ); + + + + /* compute optimal outline */ + extern + void AH_Optimizer_Compute( AH_Optimizer* optimizer ); + + + + + /* releases the optimisation data */ + extern + void AH_Optimizer_Done( AH_Optimizer* optimizer ); + + +#endif /* AGOPTIM_H */ diff --git a/src/autohint/ahtypes.h b/src/autohint/ahtypes.h new file mode 100644 index 000000000..6fe6ba81e --- /dev/null +++ b/src/autohint/ahtypes.h @@ -0,0 +1,440 @@ +/***************************************************************************/ +/* */ +/* ahtypes.h */ +/* */ +/* General types and definitions for the auto-hint module */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ + +#ifndef AGTYPES_H +#define AGTYPES_H + +#include /* for freetype.h + LOCAL_DEF etc.. */ + +#ifdef FT_FLAT_COMPILE +#include "ahloader.h" /* glyph loader types & declarations */ +#else +#include /* glyph loader types & declarations */ +#endif + +#define xxDEBUG_AG + +#ifdef DEBUG_AG +#include +#define AH_LOG(x) printf##x +#else +#define AH_LOG(x) /* nothing */ +#endif + + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ +/**** ****/ +/**** COMPILE-TIME BUILD OPTIONS ****/ +/**** ****/ +/**** Toggle these configuration macros to experiment with ****/ +/**** "features" of the auto-hinter.. ****/ +/**** ****/ +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +/* if this option is defined, only strong interpolation will be used to */ +/* place the points between edges. Otherwise, "smooth" points are detected */ +/* and later hinted through weak interpolation to correct some unpleasant */ +/* artefacts.. */ +/* */ +#undef AH_OPTION_NO_WEAK_INTERPOLATION +#undef AH_OPTION_NO_STRONG_INTERPOLATION + +/* undefine this macro if you don't want to hint the metrics */ +/* there is no reason to do this, except for experimentation */ +#define AH_HINT_METRICS + +/* define this macro if you do not want to insert extra edges at a glyph's */ +/* x and y extrema (when there isn't one already available). This help */ +/* reduce a number of artefacts and allow hinting of metrics.. */ +/* */ +#undef AH_OPTION_NO_EXTREMUM_EDGES + +/* don't touch for now.. */ +#define AH_MAX_WIDTHS 12 +#define AH_MAX_HEIGHTS 12 + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ +/**** ****/ +/**** TYPES DEFINITIONS ****/ +/**** ****/ +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + + /* see agangles.h */ + typedef FT_Int AH_Angle; + + + /* hint flags */ + typedef enum AH_Flags_ + { + ah_flah_none = 0, + + /* bezier control points flags */ + ah_flah_conic = 1, + ah_flah_cubic = 2, + ah_flah_control = ah_flah_conic | ah_flah_cubic, + + /* extrema flags */ + ah_flah_extrema_x = 4, + ah_flah_extrema_y = 8, + + /* roundness */ + ah_flah_round_x = 16, + ah_flah_round_y = 32, + + /* touched */ + ah_flah_touch_x = 64, + ah_flah_touch_y = 128, + + /* weak interpolation */ + ah_flah_weak_interpolation = 256, + + /* never remove this one !! */ + ah_flah_max + + } AH_Flags; + + + /* edge hint flags */ + typedef enum AH_Edge_Flags_ + { + ah_edge_normal = 0, + ah_edge_round = 1, + ah_edge_serif = 2, + ah_edge_done = 4 + + } AH_Edge_Flags; + + + /* hint directions - the values are computed so that two vectors are */ + /* in opposite directions iff "dir1+dir2 == 0" */ + typedef enum AH_Direction_ + { + ah_dir_none = 4, + ah_dir_right = 1, + ah_dir_left = -1, + ah_dir_up = 2, + ah_dir_down = -2 + + } AH_Direction; + + + typedef struct AH_Point AH_Point; + typedef struct AH_Segment AH_Segment; + typedef struct AH_Edge AH_Edge; + + /*************************************************************************** + * + * + * AH_Point + * + * + * A structure used to model an outline point to the AH_Outline type + * + * + * flags :: current point hint flags + * ox, oy :: current original scaled coordinates + * fx, fy :: current coordinates in font units + * x, y :: current hinter coordinates + * u, v :: point coordinates - meaning varies with context + * + * in_dir :: direction of inwards vector (prev->point) + * out_dir :: direction of outwards vector (point->next) + * + * in_angle :: angle of inwards vector + * out_angle :: angle of outwards vector + * + * next :: next point in same contour + * prev :: previous point in same contour + * + */ + struct AH_Point + { + AH_Flags flags; /* point flags used by hinter */ + FT_Pos ox, oy; + FT_Pos fx, fy; + FT_Pos x, y; + FT_Pos u, v; + + AH_Direction in_dir; /* direction of inwards vector */ + AH_Direction out_dir; /* direction of outwards vector */ + + AH_Angle in_angle; + AH_Angle out_angle; + + AH_Point* next; /* next point in contour */ + AH_Point* prev; /* previous point in contour */ + }; + + + /*************************************************************************** + * + * + * AH_Segment + * + * + * a structure used to describe an edge segment to the auto-hinter. A + * segment is simply a sequence of successive points located on the same + * horizontal or vertical "position", in a given direction. + * + * + * flags :: segment edge flags ( straight, rounded.. ) + * dir :: segment direction + * + * first :: first point in segment + * last :: last point in segment + * contour :: ptr to first point of segment's contour + * + * pos :: segment position in font units + * size :: segment size + * + * edge :: edge of current segment + * edge_next :: next segment on same edge + * + * link :: the pairing segment for this edge + * serif :: the primary segment for serifs + * num_linked :: the number of other segments that link to this one + * + * score :: used to score the segment when selecting them.. + * + */ + struct AH_Segment + { + AH_Edge_Flags flags; + AH_Direction dir; + + AH_Point* first; /* first point in edge segment */ + AH_Point* last; /* last point in edge segment */ + AH_Point** contour; /* ptr to first point of segment's contour */ + + FT_Pos pos; /* position of segment */ + FT_Pos min_coord; /* minimum coordinate of segment */ + FT_Pos max_coord; /* maximum coordinate of segment */ + + AH_Edge* edge; + AH_Segment* edge_next; + + AH_Segment* link; /* link segment */ + AH_Segment* serif; /* primary segment for serifs */ + FT_Pos num_linked; /* number of linked segments */ + FT_Int score; + }; + + + /*************************************************************************** + * + * + * AH_Edge + * + * + * a structure used to describe an edge, which really is a horizontal + * or vertical coordinate which will be hinted depending on the segments + * located on it.. + * + * + * flags :: segment edge flags ( straight, rounded.. ) + * dir :: main segment direction on this edge + * + * first :: first edge segment + * last :: last edge segment + * + * fpos :: original edge position in font units + * opos :: original scaled edge position + * pos :: hinted edge position + * + * link :: the linked edge + * serif :: the serif edge + * num_paired :: the number of other edges that pair to this one + * + * score :: used to score the edge when selecting them.. + * + * blue_edge :: indicate the blue zone edge this edge is related to + * only set for some of the horizontal edges in a Latin + * font.. + * + ***************************************************************************/ + struct AH_Edge + { + AH_Edge_Flags flags; + AH_Direction dir; + + AH_Segment* first; + AH_Segment* last; + + FT_Pos fpos; + FT_Pos opos; + FT_Pos pos; + + AH_Edge* link; + AH_Edge* serif; + FT_Int num_linked; + + FT_Int score; + FT_Pos* blue_edge; + }; + + + /* an outline as seen by the hinter */ + typedef struct AH_Outline_ + { + FT_Memory memory; + + AH_Direction vert_major_dir; /* vertical major direction */ + AH_Direction horz_major_dir; /* horizontal major direction */ + + FT_Fixed x_scale; + FT_Fixed y_scale; + FT_Pos edge_distance_threshold; + + FT_Int max_points; + FT_Int num_points; + AH_Point* points; + + FT_Int max_contours; + FT_Int num_contours; + AH_Point** contours; + + FT_Int num_hedges; + AH_Edge* horz_edges; + + FT_Int num_vedges; + AH_Edge* vert_edges; + + FT_Int num_hsegments; + AH_Segment* horz_segments; + + FT_Int num_vsegments; + AH_Segment* vert_segments; + + } AH_Outline; + + + + typedef enum AH_Blue_ + { + ah_blue_capital_top, /* THEZOCQS */ + ah_blue_capital_bottom, /* HEZLOCUS */ + ah_blue_small_top, /* xzroesc */ + ah_blue_small_bottom, /* xzroesc */ + ah_blue_small_minor, /* pqgjy */ + + ah_blue_max + + } AH_Blue; + + typedef enum + { + ah_hinter_monochrome = 1, + ah_hinter_optimize = 2 + + } AH_Hinter_Flags; + + + /************************************************************************ + * + * + * AH_Globals + * + * + * Holds the global metrics for a given font face (be it in design + * units, or scaled pixel values).. + * + * + * num_widths :: number of widths + * num_heights :: number of heights + * widths :: snap widths, including standard one + * heights :: snap height, including standard one + * blue_refs :: reference position of blue zones + * blue_shoots :: overshoot position of blue zones + * + ************************************************************************/ + + typedef struct AH_Globals_ + { + FT_Int num_widths; + FT_Int num_heights; + + FT_Pos widths [ AH_MAX_WIDTHS ]; + FT_Pos heights[ AH_MAX_HEIGHTS ]; + + FT_Pos blue_refs [ ah_blue_max ]; + FT_Pos blue_shoots[ ah_blue_max ]; + + } AH_Globals; + + + /************************************************************************ + * + * + * AH_Face_Globals + * + * + * Holds the complete global metrics for a given font face (i.e. the + * design units version + a scaled version + the current scales used) + * + * + * face :: handle to source face object + * design :: globals in font design units + * scaled :: scaled globals in sub-pixel values + * x_scale :: current horizontal scale + * y_scale :: current vertical scale + * + ************************************************************************/ + + typedef struct AH_Face_Globals_ + { + FT_Face face; + AH_Globals design; + AH_Globals scaled; + FT_Fixed x_scale; + FT_Fixed y_scale; + FT_Bool control_overshoot; + + } AH_Face_Globals; + + + + + typedef struct AH_Hinter + { + FT_Memory memory; + FT_Long flags; + + FT_Int algorithm; + FT_Face face; + + AH_Face_Globals* globals; + + AH_Outline* glyph; + + AH_Loader* loader; + FT_Vector pp1; + FT_Vector pp2; + + } AH_Hinter; + +#endif /* AGTYPES_H */ diff --git a/src/autohint/autohint.c b/src/autohint/autohint.c new file mode 100644 index 000000000..fb5ee1db3 --- /dev/null +++ b/src/autohint/autohint.c @@ -0,0 +1,40 @@ +/***************************************************************************/ +/* */ +/* autohint.c */ +/* */ +/* Automatic Hinting wrapper. */ +/* */ +/* Copyright 2000: Catharon Productions Inc. */ +/* Author: David Turner */ +/* */ +/* This file is part of the Catharon Typography Project and shall only */ +/* be used, modified, and distributed under the terms of the Catharon */ +/* Open Source License that should come with this file under the name */ +/* "CatharonLicense.txt". By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/* Note that this license is compatible with the FreeType license */ +/* */ +/***************************************************************************/ + +#define FT_MAKE_OPTION_SINGLE_OBJECT + +#ifdef FT_FLAT_COMPILE + +#include "ahangles.c" +#include "ahglyph.c" +#include "ahglobal.c" +#include "ahhint.c" +#include "ahmodule.c" + +#else + +#include +#include +#include +#include +#include + +#endif + diff --git a/src/autohint/mather.py b/src/autohint/mather.py new file mode 100644 index 000000000..9f0b34439 --- /dev/null +++ b/src/autohint/mather.py @@ -0,0 +1,54 @@ +import math + +ag_pi = 256 + +def print_arctan( atan_bits ): + atan_base = 1 << atan_bits + + print "static AH_Angle ag_arctan[ 1L << AG_ATAN_BITS ] =" + print "{" + + count = 0 + line = "" + + for n in range(atan_base): + comma = "," + if (n == atan_base-1): + comma = "" + + angle = math.atan(n*1.0/atan_base)/math.pi*ag_pi + line = line + " " + repr(int(angle+0.5)) + comma + count = count+1; + if (count == 8): + count = 0 + print line + line = "" + + if (count >0): + print line + print "};" + + +def print_sines(): + print "static FT_Fixed ah_sines[ AG_HALF_PI+1 ] =" + print "{" + count = 0 + line = "" + + for n in range(ag_pi/2): + sinus = math.sin(n*math.pi/ag_pi) + line = line + " " + repr(int(65536.0*sinus)) + "," + count = count+1 + if (count == 8): + count = 0 + print line + line = "" + + if (count > 0): + print line + print " 65536" + print "};" + +print_arctan(8) +print + diff --git a/src/autohint/module.mk b/src/autohint/module.mk new file mode 100644 index 000000000..71e5ee352 --- /dev/null +++ b/src/autohint/module.mk @@ -0,0 +1,7 @@ +make_module_list: add_autohint_module + +add_autohint_module: + $(OPEN_DRIVER)autohint_module_class$(CLOSE_DRIVER) + $(ECHO_DRIVER)autohint $(ECHO_DRIVER_DESC)automatic hinting module$(ECHO_DRIVER_DONE) + +# EOF diff --git a/src/autohint/rules.mk b/src/autohint/rules.mk new file mode 100644 index 000000000..94cb023b3 --- /dev/null +++ b/src/autohint/rules.mk @@ -0,0 +1,86 @@ +# +# FreeType 2 auto-hinter module configuration rules +# + + +# +# Copyright 2000: Catharon Productions Inc. +# Author: David Turner +# +# This file is part of the Catharon Typography Project and shall only +# be used, modified, and distributed under the terms of the Catharon +# Open Source License that should come with this file under the name +# "CatharonLicense.txt". By continuing to use, modify, or distribute +# this file you indicate that you have read the license and +# understand and accept it fully. +# +# Note that this license is compatible with the FreeType license +# +# +# Copyright 1996-2000 by +# David Turner, Robert Wilhelm, and Werner Lemberg. +# +# This file is part of the FreeType project, and may only be used, modified, +# and distributed under the terms of the FreeType project license, +# LICENSE.TXT. By continuing to use, modify, or distribute this file you +# indicate that you have read the license and understand and accept it +# fully. + + +# AUTO driver directory +# +AUTO_DIR := $(SRC_)autohint +AUTO_DIR_ := $(AUTO_DIR)$(SEP) + + +# compilation flags for the driver +# +AUTO_COMPILE := $(FT_COMPILE) + + +# AUTO driver sources (i.e., C files) +# +AUTO_DRV_SRC := $(AUTO_DIR_)ahangles.c \ + $(AUTO_DIR_)ahglobal.c \ + $(AUTO_DIR_)ahglyph.c \ + $(AUTO_DIR_)ahhint.c \ + $(AUTO_DIR_)ahmodule.c + +# AUTO driver headers +# +AUTO_DRV_H := $(AUTO_DRV_SRC:%c=%h) + + +# AUTO driver object(s) +# +# AUTO_DRV_OBJ_M is used during `multi' builds. +# AUTO_DRV_OBJ_S is used during `single' builds. +# +AUTO_DRV_OBJ_M := $(AUTO_DRV_SRC:$(AUTO_DIR_)%.c=$(OBJ_)%.$O) +AUTO_DRV_OBJ_S := $(OBJ_)autohint.$O + +# AUTO driver source file for single build +# +AUTO_DRV_SRC_S := $(AUTO_DIR_)autohint.c + + +# AUTO driver - single object +# +$(AUTO_DRV_OBJ_S): $(AUTO_DRV_SRC_S) $(AUTO_DRV_SRC) \ + $(FREETYPE_H) $(AUTO_DRV_H) + $(AUTO_COMPILE) $T$@ $(AUTO_DRV_SRC_S) + + +# AUTO driver - multiple objects +# +$(OBJ_)%.$O: $(AUTO_DIR_)%.c $(FREETYPE_H) $(AUTO_DRV_H) + $(AUTO_COMPILE) $T$@ $< + + +# update main driver object lists +# +DRV_OBJS_S += $(AUTO_DRV_OBJ_S) +DRV_OBJS_M += $(AUTO_DRV_OBJ_M) + + +# EOF