diff --git a/ChangeLog b/ChangeLog index 6edf1050c..22b2a34c1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,31 @@ +2009-06-16 Werner Lemberg + + Improve scan conversion rules 4 and 6. + + Two new constraints are introduced to better identify a `stub' -- a + concept which is only vaguely described in the OpenType + specification. The old code was too rigorous and suppressed more + pixel than it should. + + . The intersection of the two profiles with the scanline is less + than a half pixel. Code related to this was already present in + the sources but has been commented out. + + . The endpoint of the original contour forming a profile has a + distance (`overshoot') less than half a pixel to the scanline. + + Note that the two additional conditions fix almost all differences + to the Windows rasterizer, but some problematic cases remain. + + * src/raster/ftraster.c (Overshoot_Top, Overshoot_Bottom): New + macros for the `flags' field in the `TProfile' structure. + (IS_BOTTOM_OVERSHOOT, IS_TOP_OVERSHOOT): New macros. + (New_Profile, End_Profile): Pass overshoot flag as an argument and + set it accordingly. + Update callers. + (Vertical_Sweep_Drop, Horizontal_Sweep_Drop): Implement the two new + constraints. + 2009-06-11 Werner Lemberg Increase precision for B/W rasterizer. diff --git a/src/raster/ftraster.c b/src/raster/ftraster.c index 5ade13bef..c8c13f22e 100644 --- a/src/raster/ftraster.c +++ b/src/raster/ftraster.c @@ -305,7 +305,10 @@ } TPoint; -#define Flow_Up 0x1 + /* values for the `flags' bit field */ +#define Flow_Up 0x1 +#define Overshoot_Top 0x2 +#define Overshoot_Bottom 0x4 /* States of each line, arc, and profile */ @@ -324,18 +327,19 @@ struct TProfile_ { - FT_F26Dot6 X; /* current coordinate during sweep */ - PProfile link; /* link to next profile (various purposes) */ - PLong offset; /* start of profile's data in render pool */ - unsigned flags; /* Bit 0: profile orientation: up/down */ - long height; /* profile's height in scanlines */ - long start; /* profile's starting scanline */ + FT_F26Dot6 X; /* current coordinate during sweep */ + PProfile link; /* link to next profile (various purposes) */ + PLong offset; /* start of profile's data in render pool */ + unsigned flags; /* Bit 0: profile orientation (up/down) */ + /* Bit 1, 2: profile overshoot (top/bottom) */ + long height; /* profile's height in scanlines */ + long start; /* profile's starting scanline */ - unsigned countL; /* number of lines to step before this */ - /* profile becomes drawable */ + unsigned countL; /* number of lines to step before this */ + /* profile becomes drawable */ - PProfile next; /* next profile in same contour, used */ - /* during drop-out control */ + PProfile next; /* next profile in same contour, used */ + /* during drop-out control */ }; typedef PProfile TProfileList; @@ -410,6 +414,9 @@ #define FRAC( x ) ( (x) & ( ras.precision - 1 ) ) #define SCALED( x ) ( ( (x) << ras.scale_shift ) - ras.precision_half ) +#define IS_BOTTOM_OVERSHOOT( x ) ( CEILING( x ) - x >= ras.precision_half ) +#define IS_TOP_OVERSHOOT( x ) ( x - FLOOR( x ) >= ras.precision_half ) + /* Note that I have moved the location of some fields in the */ /* structure to ensure that the most used variables are used */ /* at the top. Thus, their offset can be coded with less */ @@ -617,14 +624,18 @@ /* Create a new profile in the render pool. */ /* */ /* */ - /* aState :: The state/orientation of the new profile. */ + /* aState :: The state/orientation of the new profile. */ + /* */ + /* overshoot :: Whether the profile's unrounded start position */ + /* differs by at least a half pixel. */ /* */ /* */ /* SUCCESS on success. FAILURE in case of overflow or of incoherent */ /* profile. */ /* */ static Bool - New_Profile( RAS_ARGS TStates aState ) + New_Profile( RAS_ARGS TStates aState, + Bool overshoot ) { if ( !ras.fProfile ) { @@ -639,15 +650,26 @@ return FAILURE; } + ras.cProfile->flags = 0; + ras.cProfile->start = 0; + ras.cProfile->height = 0; + ras.cProfile->offset = ras.top; + ras.cProfile->link = (PProfile)0; + ras.cProfile->next = (PProfile)0; + switch ( aState ) { case Ascending_State: ras.cProfile->flags |= Flow_Up; + if ( overshoot ) + ras.cProfile->flags |= Overshoot_Bottom; + FT_TRACE6(( "New ascending profile = %lx\n", (long)ras.cProfile )); break; case Descending_State: - ras.cProfile->flags &= ~Flow_Up; + if ( overshoot ) + ras.cProfile->flags |= Overshoot_Top; FT_TRACE6(( "New descending profile = %lx\n", (long)ras.cProfile )); break; @@ -657,12 +679,6 @@ return FAILURE; } - ras.cProfile->start = 0; - ras.cProfile->height = 0; - ras.cProfile->offset = ras.top; - ras.cProfile->link = (PProfile)0; - ras.cProfile->next = (PProfile)0; - if ( !ras.gProfile ) ras.gProfile = ras.cProfile; @@ -682,11 +698,15 @@ /* */ /* Finalize the current profile. */ /* */ + /* */ + /* overshoot :: Whether the profile's unrounded end position differs */ + /* by at least a half pixel. */ + /* */ /* */ /* SUCCESS on success. FAILURE in case of overflow or incoherency. */ /* */ static Bool - End_Profile( RAS_ARG ) + End_Profile( RAS_ARGS Bool overshoot ) { Long h; PProfile oldProfile; @@ -706,15 +726,24 @@ FT_TRACE6(( "Ending profile %lx, start = %ld, height = %ld\n", (long)ras.cProfile, ras.cProfile->start, h )); - oldProfile = ras.cProfile; ras.cProfile->height = h; - ras.cProfile = (PProfile)ras.top; + if ( overshoot ) + { + if ( ras.cProfile->flags & Flow_Up ) + ras.cProfile->flags |= Overshoot_Top; + else + ras.cProfile->flags |= Overshoot_Bottom; + } - ras.top += AlignProfileSize; + oldProfile = ras.cProfile; + ras.cProfile = (PProfile)ras.top; + + ras.top += AlignProfileSize; ras.cProfile->height = 0; ras.cProfile->offset = ras.top; - oldProfile->next = ras.cProfile; + + oldProfile->next = ras.cProfile; ras.num_Profs++; } @@ -1328,13 +1357,15 @@ case Unknown_State: if ( y > ras.lastY ) { - if ( New_Profile( RAS_VARS Ascending_State ) ) + if ( New_Profile( RAS_VARS Ascending_State, + IS_BOTTOM_OVERSHOOT( ras.lastY ) ) ) return FAILURE; } else { if ( y < ras.lastY ) - if ( New_Profile( RAS_VARS Descending_State ) ) + if ( New_Profile( RAS_VARS Descending_State, + IS_TOP_OVERSHOOT( ras.lastY ) ) ) return FAILURE; } break; @@ -1342,8 +1373,9 @@ case Ascending_State: if ( y < ras.lastY ) { - if ( End_Profile( RAS_VAR ) || - New_Profile( RAS_VARS Descending_State ) ) + if ( End_Profile( RAS_VARS IS_TOP_OVERSHOOT( ras.lastY ) ) || + New_Profile( RAS_VARS Descending_State, + IS_TOP_OVERSHOOT( ras.lastY ) ) ) return FAILURE; } break; @@ -1351,8 +1383,9 @@ case Descending_State: if ( y > ras.lastY ) { - if ( End_Profile( RAS_VAR ) || - New_Profile( RAS_VARS Ascending_State ) ) + if ( End_Profile( RAS_VARS IS_BOTTOM_OVERSHOOT( ras.lastY ) ) || + New_Profile( RAS_VARS Ascending_State, + IS_BOTTOM_OVERSHOOT( ras.lastY ) ) ) return FAILURE; } break; @@ -1367,13 +1400,13 @@ { case Ascending_State: if ( Line_Up( RAS_VARS ras.lastX, ras.lastY, - x, y, ras.minY, ras.maxY ) ) + x, y, ras.minY, ras.maxY ) ) return FAILURE; break; case Descending_State: if ( Line_Down( RAS_VARS ras.lastX, ras.lastY, - x, y, ras.minY, ras.maxY ) ) + x, y, ras.minY, ras.maxY ) ) return FAILURE; break; @@ -1467,13 +1500,17 @@ state_bez = y1 < y3 ? Ascending_State : Descending_State; if ( ras.state != state_bez ) { + Bool o = state_bez == Ascending_State ? IS_BOTTOM_OVERSHOOT( y1 ) + : IS_TOP_OVERSHOOT( y1 ); + + /* finalize current profile if any */ if ( ras.state != Unknown_State && - End_Profile( RAS_VAR ) ) + End_Profile( RAS_VARS o ) ) goto Fail; /* create a new profile */ - if ( New_Profile( RAS_VARS state_bez ) ) + if ( New_Profile( RAS_VARS state_bez, o ) ) goto Fail; } @@ -1599,11 +1636,16 @@ /* detect a change of direction */ if ( ras.state != state_bez ) { + Bool o = state_bez == Ascending_State ? IS_BOTTOM_OVERSHOOT( y1 ) + : IS_TOP_OVERSHOOT( y1 ); + + + /* finalize current profile if any */ if ( ras.state != Unknown_State && - End_Profile( RAS_VAR ) ) + End_Profile( RAS_VARS o ) ) goto Fail; - if ( New_Profile( RAS_VARS state_bez ) ) + if ( New_Profile( RAS_VARS state_bez, o ) ) goto Fail; } @@ -1903,12 +1945,15 @@ for ( i = 0; i < ras.outline.n_contours; i++ ) { + Bool o; + + ras.state = Unknown_State; ras.gProfile = NULL; if ( Decompose_Curve( RAS_VARS (unsigned short)start, - ras.outline.contours[i], - flipped ) ) + ras.outline.contours[i], + flipped ) ) return FAILURE; start = ras.outline.contours[i] + 1; @@ -1917,15 +1962,19 @@ if ( FRAC( ras.lastY ) == 0 && ras.lastY >= ras.minY && ras.lastY <= ras.maxY ) - if ( ras.gProfile && - ( ras.gProfile->flags & Flow_Up ) == - ( ras.cProfile->flags & Flow_Up ) ) + if ( ras.gProfile && + ( ras.gProfile->flags & Flow_Up ) == + ( ras.cProfile->flags & Flow_Up ) ) ras.top--; /* Note that ras.gProfile can be nil if the contour was too small */ /* to be drawn. */ lastProfile = ras.cProfile; - if ( End_Profile( RAS_VAR ) ) + if ( ras.cProfile->flags & Flow_Up ) + o = IS_TOP_OVERSHOOT( ras.lastY ); + else + o = IS_BOTTOM_OVERSHOOT( ras.lastY ); + if ( End_Profile( RAS_VARS o ) ) return FAILURE; /* close the `next profile in contour' linked list */ @@ -2237,11 +2286,10 @@ /* Drop-out Control Rules #4 and #6 */ - /* The spec is not very clear regarding those rules. It */ - /* presents a method that is way too costly to implement */ - /* while the general idea seems to get rid of `stubs'. */ + /* The specification neither provides an exact definition */ + /* of a `stub' nor gives exact rules to exclude them. */ /* */ - /* Here, we only get rid of stubs recognized if: */ + /* Here the constraints we use to recognize a stub. */ /* */ /* upper stub: */ /* */ @@ -2255,22 +2303,26 @@ /* - P_Left is the successor of P_Right in that contour */ /* - y is the bottom of P_Left */ /* */ + /* We draw a stub if the following constraints are met. */ + /* */ + /* - for an upper or lower stub, there is top or bottom */ + /* overshoot, respectively */ + /* - the covered interval is greater or equal to a half */ + /* pixel */ - /* FIXXXME: uncommenting this line solves the disappearing */ - /* bit problem in the `7' of verdana 10pts, but */ - /* makes a new one in the `C' of arial 14pts */ -#if 0 - if ( x2 - x1 < ras.precision_half ) -#endif - { - /* upper stub test */ - if ( left->next == right && left->height <= 0 ) - return; + /* upper stub test */ + if ( left->next == right && + left->height <= 0 && + !( left->flags & Overshoot_Top && + x2 - x1 >= ras.precision_half ) ) + return; - /* lower stub test */ - if ( right->next == left && left->start == y ) - return; - } + /* lower stub test */ + if ( right->next == left && + left->start == y && + !( left->flags & Overshoot_Bottom && + x2 - x1 >= ras.precision_half ) ) + return; if ( ras.dropOutControl == 1 ) pxl = e2; @@ -2432,11 +2484,17 @@ /* see Vertical_Sweep_Drop for details */ /* rightmost stub test */ - if ( left->next == right && left->height <= 0 ) + if ( left->next == right && + left->height <= 0 && + !( left->flags & Overshoot_Top && + x2 - x1 >= ras.precision_half ) ) return; /* leftmost stub test */ - if ( right->next == left && left->start == y ) + if ( right->next == left && + left->start == y && + !( left->flags & Overshoot_Bottom && + x2 - x1 >= ras.precision_half ) ) return; if ( ras.dropOutControl == 1 ) @@ -3064,7 +3122,7 @@ Set_High_Precision( RAS_VARS ras.outline.flags & - FT_OUTLINE_HIGH_PRECISION ); + FT_OUTLINE_HIGH_PRECISION ); ras.scale_shift = ras.precision_shift; if ( ras.outline.flags & FT_OUTLINE_IGNORE_DROPOUTS ) @@ -3140,7 +3198,7 @@ Set_High_Precision( RAS_VARS ras.outline.flags & - FT_OUTLINE_HIGH_PRECISION ); + FT_OUTLINE_HIGH_PRECISION ); ras.scale_shift = ras.precision_shift + 1; if ( ras.outline.flags & FT_OUTLINE_IGNORE_DROPOUTS )