ref: ea44d5ec3d7460e56c4cc681e8a36637a39a71e3
dir: /src/base/ftraster.c/
/******************************************************************* * * ftraster.c 1.5 * * The FreeType glyph rasterizer (body). * * 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. * * * This is a rewrite of the FreeType 1.x scan-line converter * * * ******************************************************************/ #include <freetype/ftraster.h> #include <freetype/internal/ftcalc.h> /* for FT_MulDiv only */ /*************************************************************************/ /* */ /* A simple technical note on how the raster works: */ /* */ /* Converting an outline into a bitmap is achieved in several steps */ /* which are: */ /* */ /* 1 - Decomposing the outline into successive `profiles'. Each */ /* profile is simply an array of scanline intersections on a given */ /* dimension. A profile's main attributes are */ /* */ /* o its scanline position boundaries, i.e. `Ymin' and `Ymax'. */ /* */ /* o an array of intersection coordinates for each scanline */ /* between `Ymin' and `Ymax'. */ /* */ /* o a direction, indicating wether is was built going `up' or */ /* `down', as this is very important for filling rules. */ /* */ /* 2 - Sweeping the target map's scanlines in order to compute segment */ /* `spans' which are then filled. Additionaly, this pass performs */ /* drop-out control. */ /* */ /* The outline data is parsed during step 1 only. The profiles are */ /* built from the bottom of the render pool, used as a stack. The */ /* following graphics shows the profile list under construction: */ /* */ /* ____________________________________________________________ _ _ */ /* | | | | | */ /* | profile | coordinates for | profile | coordinates for |--> */ /* | 1 | profile 1 | 2 | profile 2 |--> */ /* |_________|___________________|_________|_________________|__ _ _ */ /* */ /* ^ ^ */ /* | | */ /* start of render pool top */ /* */ /* The top of the profile stack is kept in the `top' variable. */ /* */ /* As you can see, a profile record is pushed on top of the render */ /* pool, which is then followed by its coordinates/intersections. If */ /* a change of direction is detected in the outline, a new profile is */ /* generated until the end of the outline. */ /* */ /* Note that when all profiles have been generated, the function */ /* Finalize_Profile_Table() is used to record, for each profile, its */ /* bottom-most scanline as well as the scanline above its upmost */ /* boundary. These positions are called `y-turns' because they (sort */ /* of) correspond to local extrema. They are stored in a sorted list */ /* built from the top of the render pool as a downwards stack: */ /* */ /* _ _ _______________________________________ */ /* | | */ /* <--| sorted list of | */ /* <--| extrema scanlines | */ /* _ _ __________________|____________________| */ /* */ /* ^ ^ */ /* | | */ /* maxBuff sizeBuff = end of pool */ /* */ /* This list is later used during the sweep phase in order to */ /* optimize performance (see technical note on the sweep below). */ /* */ /* Of course, the raster detects whether the two stacks collide and */ /* handles the situation propertly. */ /* */ /*************************************************************************/ /****************************************************************/ /****************************************************************/ /** **/ /** CONFIGURATION MACROS **/ /** **/ /****************************************************************/ /****************************************************************/ /* define DEBUG_RASTER if you want to compile a debugging version */ #define xxxDEBUG_RASTER /* The default render pool size in bytes */ #define RASTER_RENDER_POOL 8192 /* undefine FT_RASTER_OPTION_ANTI_ALIASING if you do not want to support */ /* 5-levels anti-aliasing */ #ifdef FT_CONFIG_OPTION_5_GRAY_LEVELS #define FT_RASTER_OPTION_ANTI_ALIASING #endif /* The size of the two-lines intermediate bitmap used */ /* for anti-aliasing, in bytes.. */ #define RASTER_GRAY_LINES 2048 /****************************************************************/ /****************************************************************/ /** **/ /** OTHER MACROS (do not change) **/ /** **/ /****************************************************************/ /****************************************************************/ /* required by the tracing mode */ #undef FT_COMPONENT #define FT_COMPONENT trace_raster #include <freetype/internal/ftdebug.h> #define Raster_Err_None 0 #define Raster_Err_Not_Ini -1 #define Raster_Err_Overflow -2 #define Raster_Err_Neg_Height -3 #define Raster_Err_Invalid -4 #define Raster_Err_Gray_Unsupported -5 #define Raster_Err_Unsupported -6 /* FMulDiv means "Fast MulDiv", it is used in case where 'b' is typically */ /* a small value and the result of (a*b) is known to fit in 32 bits. */ #define FMulDiv( a, b, c ) ( (a) * (b) / (c) ) /* On the other hand, SMulDiv is for "Slow MulDiv", and is used typically */ /* for clipping computations. It simply uses the FT_MulDiv() function */ /* defined in "ftcalc.h" */ /* */ #define SMulDiv FT_MulDiv /* The rasterizer is a very general purpose component, please leave */ /* the following redefinitions there (you never know your target */ /* environment). */ #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #ifndef NULL #define NULL (void*)0 #endif #ifndef SUCCESS #define SUCCESS 0 #endif #ifndef FAILURE #define FAILURE 1 #endif #define MaxBezier 32 /* The maximum number of stacked Bezier curves. */ /* Setting this constant to more than 32 is a */ /* pure waste of space. */ #define Pixel_Bits 6 /* fractional bits of *input* coordinates */ /****************************************************************/ /****************************************************************/ /** **/ /** SIMPLE TYPE DECLARATIONS **/ /** **/ /****************************************************************/ /****************************************************************/ typedef int Int; typedef unsigned int UInt; typedef short Short; typedef unsigned short UShort, *PUShort; typedef long Long, *PLong; typedef unsigned long ULong; typedef unsigned char Byte, *PByte; typedef char Bool; typedef struct TPoint_ { Long x; Long y; } TPoint; typedef enum TFlow_ { Flow_None = 0, Flow_Up = 1, Flow_Down = -1 } TFlow; /* States of each line, arc and profile */ typedef enum TStates_ { Unknown, Ascending, Descending, Flat } TStates; typedef struct TProfile_ TProfile; typedef TProfile* PProfile; struct TProfile_ { FT_F26Dot6 X; /* current coordinate during sweep */ PProfile link; /* link to next profile - various purpose */ PLong offset; /* start of profile's data in render pool */ Int flow; /* Profile orientation: Asc/Descending */ Long height; /* profile's height in scanlines */ Long start; /* profile's starting scanline */ UShort countL; /* number of lines to step before this */ /* profile becomes drawable */ PProfile next; /* next profile in same contour, used */ /* during drop-out control */ }; typedef PProfile TProfileList; typedef PProfile* PProfileList; /* Simple record used to implement a stack of bands, required */ /* by the sub-banding mechanism */ typedef struct TBand_ { Short y_min; /* band's minimum */ Short y_max; /* band's maximum */ } TBand; #define AlignProfileSize \ (( sizeof(TProfile)+sizeof(long)-1 ) / sizeof(long)) #ifdef TT_STATIC_RASTER #define RAS_ARGS /* void */ #define RAS_ARG /* void */ #define RAS_VARS /* void */ #define RAS_VAR /* void */ #else #define RAS_ARGS TRaster_Instance* raster, #define RAS_ARG TRaster_Instance* raster #define RAS_VARS raster, #define RAS_VAR raster #endif typedef struct TRaster_Instance_ TRaster_Instance; /* prototypes used for sweep function dispatch */ typedef void Function_Sweep_Init( RAS_ARGS Short* min, Short* max ); typedef void Function_Sweep_Span( RAS_ARGS Short y, FT_F26Dot6 x1, FT_F26Dot6 x2, PProfile left, PProfile right ); typedef void Function_Sweep_Step( RAS_ARG ); /* NOTE: These operations are only valid on 2's complement processors */ #define FLOOR( x ) ( (x) & -ras.precision ) #define CEILING( x ) ( ((x) + ras.precision - 1) & -ras.precision ) #define TRUNC( x ) ( (signed long)(x) >> ras.precision_bits ) #define FRAC( x ) ( (x) & (ras.precision - 1) ) #define SCALED( x ) ( ((x) << ras.scale_shift) - 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 */ /* opcodes, and it results in a smaller executable. */ struct TRaster_Instance_ { Int precision_bits; /* precision related variables */ Int precision; Int precision_half; Long precision_mask; Int precision_shift; Int precision_step; Int precision_jitter; Int scale_shift; /* == precision_shift for bitmaps */ /* == precision_shift+1 for pixmaps */ PLong buff; /* The profiles buffer */ PLong sizeBuff; /* Render pool size */ PLong maxBuff; /* Profiles buffer size */ PLong top; /* Current cursor in buffer */ FT_Error error; Int numTurns; /* number of Y-turns in outline */ TPoint* arc; /* current Bezier arc pointer */ UShort bWidth; /* target bitmap width */ PByte bTarget; /* target bitmap buffer */ PByte gTarget; /* target pixmap buffer */ Long lastX, lastY, minY, maxY; UShort num_Profs; /* current number of profiles */ Bool fresh; /* signals a fresh new profile which */ /* 'start' field must be completed */ Bool joint; /* signals that the last arc ended */ /* exactly on a scanline. Allows */ /* removal of doublets */ PProfile cProfile; /* current profile */ PProfile fProfile; /* head of linked list of profiles */ PProfile gProfile; /* contour's first profile in case */ /* of impact */ TStates state; /* rendering state */ FT_Bitmap target; /* description of target bit/pixmap */ FT_Outline outline; Long traceOfs; /* current offset in target bitmap */ Long traceG; /* current offset in target pixmap */ Short traceIncr; /* sweep's increment in target bitmap */ Short gray_min_x; /* current min x during gray rendering */ Short gray_max_x; /* current max x during gray rendering */ /* dispatch variables */ Function_Sweep_Init* Proc_Sweep_Init; Function_Sweep_Span* Proc_Sweep_Span; Function_Sweep_Span* Proc_Sweep_Drop; Function_Sweep_Step* Proc_Sweep_Step; Byte dropOutControl; /* current drop_out control method */ Bool second_pass; /* indicates wether a horizontal pass */ /* should be performed to control drop-out */ /* accurately when calling Render_Glyph. */ /* Note that there is no horizontal pass */ /* during gray rendering. */ TPoint arcs[2 * MaxBezier + 1]; /* The Bezier stack */ TBand band_stack[16]; /* band stack used for sub-banding */ Int band_top; /* band stack top */ Int count_table[256]; /* Look-up table used to quickly count */ /* set bits in a gray 2x2 cell */ void* memory; #ifdef FT_RASTER_OPTION_ANTI_ALIASING Byte grays[5]; /* Palette of gray levels used for render */ Byte gray_lines[RASTER_GRAY_LINES]; /* Intermediate table used to render the */ /* graylevels pixmaps. */ /* gray_lines is a buffer holding two */ /* monochrome scanlines */ Short gray_width; /* width in bytes of one monochrome */ /* intermediate scanline of gray_lines. */ /* Each gray pixel takes 2 bits long there */ /* The gray_lines must hold 2 lines, thus with size */ /* in bytes of at least 'gray_width*2' */ #endif /* FT_RASTER_ANTI_ALIASING */ #if 0 PByte flags; /* current flags table */ PUShort outs; /* current outlines table */ FT_Vector* coords; UShort nPoints; /* number of points in current glyph */ Short nContours; /* number of contours in current glyph */ #endif }; #ifdef FT_CONFIG_OPTION_STATIC_RASTER static TRaster_Instance cur_ras; #define ras cur_ras #else #define ras (*raster) #endif /* FT_STATIC_RASTER */ /****************************************************************/ /****************************************************************/ /** **/ /** PROFILES COMPUTATION **/ /** **/ /****************************************************************/ /****************************************************************/ /************************************************************************/ /* */ /* Function: Set_High_Precision */ /* */ /* Description: Sets precision variables according to param flag. */ /* */ /* Input: High set to True for high precision (typically for */ /* ppem < 18), false otherwise. */ /* */ /************************************************************************/ static void Set_High_Precision( RAS_ARGS Int High ) { if ( High ) { ras.precision_bits = 10; ras.precision_step = 128; ras.precision_jitter = 24; } else { ras.precision_bits = 6; ras.precision_step = 32; ras.precision_jitter = 2; } FT_TRACE7(( "Set_High_Precision(%s)\n", High ? "true" : "false" )); ras.precision = 1L << ras.precision_bits; ras.precision_half = ras.precision / 2; ras.precision_shift = ras.precision_bits - Pixel_Bits; ras.precision_mask = -ras.precision; } /****************************************************************************/ /* */ /* Function: New_Profile */ /* */ /* Description: Creates a new Profile in the render pool. */ /* */ /* Input: aState state/orientation of the new Profile */ /* */ /* Returns: SUCCESS on success. */ /* FAILURE in case of overflow or of incoherent Profile. */ /* */ /****************************************************************************/ static Bool New_Profile( RAS_ARGS TStates aState ) { if ( !ras.fProfile ) { ras.cProfile = (PProfile)ras.top; ras.fProfile = ras.cProfile; ras.top += AlignProfileSize; } if ( ras.top >= ras.maxBuff ) { ras.error = Raster_Err_Overflow; return FAILURE; } switch ( aState ) { case Ascending: ras.cProfile->flow = Flow_Up; FT_TRACE7(( "New ascending profile = %lx\n", (long)ras.cProfile )); break; case Descending: ras.cProfile->flow = Flow_Down; FT_TRACE7(( "New descending profile = %lx\n", (long)ras.cProfile )); break; default: FT_ERROR(( "Invalid profile direction in Raster:New_Profile !!\n" )); ras.error = Raster_Err_Invalid; 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; ras.state = aState; ras.fresh = TRUE; ras.joint = FALSE; return SUCCESS; } /****************************************************************************/ /* */ /* Function: End_Profile */ /* */ /* Description: Finalizes the current Profile. */ /* */ /* Input: None */ /* */ /* Returns: SUCCESS on success. */ /* FAILURE in case of overflow or incoherency. */ /* */ /****************************************************************************/ static Bool End_Profile( RAS_ARG ) { Long h; PProfile oldProfile; h = ras.top - ras.cProfile->offset; if ( h < 0 ) { FT_ERROR(( "Negative height encountered in End_Profile!\n" )); ras.error = Raster_Err_Neg_Height; return FAILURE; } if ( h > 0 ) { FT_TRACE1(( "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; ras.top += AlignProfileSize; ras.cProfile->height = 0; ras.cProfile->offset = ras.top; oldProfile->next = ras.cProfile; ras.num_Profs++; } if ( ras.top >= ras.maxBuff ) { FT_TRACE1(( "overflow in End_Profile\n" )); ras.error = Raster_Err_Overflow; return FAILURE; } ras.joint = FALSE; return SUCCESS; } /****************************************************************************/ /* */ /* Function: Insert_Y_Turn */ /* */ /* Description: Insert a salient into the sorted list placed on top */ /* of the render pool */ /* */ /* Input: New y scanline position */ /* */ /****************************************************************************/ static Bool Insert_Y_Turn( RAS_ARGS Int y ) { PLong y_turns; Int y2, n; n = ras.numTurns-1; y_turns = ras.sizeBuff - ras.numTurns; /* look for first y value that is <= */ while ( n >= 0 && y < y_turns[n] ) n--; /* if it is <, simply insert it, ignore if == */ if ( n >= 0 && y > y_turns[n] ) while ( n >= 0 ) { y2 = y_turns[n]; y_turns[n] = y; y = y2; n--; } if ( n < 0 ) { if (ras.maxBuff <= ras.top) { ras.error = Raster_Err_Overflow; return FAILURE; } ras.maxBuff--; ras.numTurns++; ras.sizeBuff[-ras.numTurns] = y; } return SUCCESS; } /****************************************************************************/ /* */ /* Function: Finalize_Profile_Table */ /* */ /* Description: Adjusts all links in the Profiles list. */ /* */ /* Input: None */ /* */ /* Returns: None. */ /* */ /****************************************************************************/ static Bool Finalize_Profile_Table( RAS_ARG ) { Int bottom, top; UShort n; PProfile p; n = ras.num_Profs; if ( n > 1 ) { p = ras.fProfile; while ( n > 0 ) { if ( n > 1 ) p->link = (PProfile)( p->offset + p->height ); else p->link = NULL; switch ( p->flow ) { case Flow_Down: bottom = p->start - p->height+1; top = p->start; p->start = bottom; p->offset += p->height-1; break; case Flow_Up: default: bottom = p->start; top = p->start + p->height-1; } if ( Insert_Y_Turn( RAS_VARS bottom ) || Insert_Y_Turn( RAS_VARS top+1 ) ) return FAILURE; p = p->link; n--; } } else ras.fProfile = NULL; return SUCCESS; } /****************************************************************************/ /* */ /* Function: Split_Conic */ /* */ /* Description: Subdivides one conic bezier into two joint */ /* sub-arcs in the Bezier stack. */ /* */ /* Input: None (subdivided bezier is taken from the top of the */ /* stack). */ /* */ /* Returns: None. */ /* */ /* */ /* Note: This routine is the 'beef' of this component. It is _the_ */ /* inner loop that should be optimized to hell to get the */ /* best performance. */ /* */ /****************************************************************************/ static void Split_Conic( TPoint* base ) { Long a, b; base[4].x = base[2].x; b = base[1].x; a = base[3].x = ( base[2].x + b ) / 2; b = base[1].x = ( base[0].x + b ) / 2; base[2].x = ( a + b ) / 2; base[4].y = base[2].y; b = base[1].y; a = base[3].y = ( base[2].y + b ) / 2; b = base[1].y = ( base[0].y + b ) / 2; base[2].y = ( a + b ) / 2; /* hand optimized. gcc doesn't seem too good at common expression */ /* substitution and instruction scheduling ;-) */ } /*************************************************************************/ /* */ /* <Function> Split_Cubic */ /* */ /* <Description> */ /* Subdivides a third-order Bezier arc into two joint sub-arcs in */ /* the Bezier stack. */ /* */ /* <Note> */ /* This routine is the `beef' of the component. It is one of _the_ */ /* inner loops that should be optimized like hell to get the best */ /* performance. */ /* */ static void Split_Cubic( TPoint* base ) { Long a, b, c, d; base[6].x = base[3].x; c = base[1].x; d = base[2].x; base[1].x = a = ( base[0].x + c + 1 ) >> 1; base[5].x = b = ( base[3].x + d + 1 ) >> 1; c = ( c + d + 1 ) >> 1; base[2].x = a = ( a + c + 1 ) >> 1; base[4].x = b = ( b + c + 1 ) >> 1; base[3].x = ( a + b + 1 ) >> 1; base[6].y = base[3].y; c = base[1].y; d = base[2].y; base[1].y = a = ( base[0].y + c + 1 ) >> 1; base[5].y = b = ( base[3].y + d + 1 ) >> 1; c = ( c + d + 1 ) >> 1; base[2].y = a = ( a + c + 1 ) >> 1; base[4].y = b = ( b + c + 1 ) >> 1; base[3].y = ( a + b + 1 ) >> 1; } /****************************************************************************/ /* */ /* Function: Line_Up */ /* */ /* Description: Computes the x-coordinates of an ascending line segment */ /* and stores them in the render pool. */ /* */ /* Input: x1,y1,x2,y2 Segment start (x1,y1) and end (x2,y2) points */ /* */ /* Returns: SUCCESS on success. */ /* FAILURE on Render Pool overflow. */ /* */ /****************************************************************************/ static Bool Line_Up( RAS_ARGS Long x1, Long y1, Long x2, Long y2, Long miny, Long maxy ) { Long Dx, Dy; Int e1, e2, f1, f2, size; /* XXX: is `Short' sufficient? */ Long Ix, Rx, Ax; PLong top; Dx = x2 - x1; Dy = y2 - y1; if ( Dy <= 0 || y2 < miny || y1 > maxy ) return SUCCESS; if ( y1 < miny ) { /* Take care : miny-y1 can be a very large value, we use */ /* a slow MulDiv function to avoid clipping bugs */ x1 += SMulDiv( Dx, miny - y1, Dy ); e1 = TRUNC( miny ); f1 = 0; } else { e1 = TRUNC( y1 ); f1 = FRAC( y1 ); } if ( y2 > maxy ) { /* x2 += FMulDiv( Dx, maxy - y2, Dy ); UNNECESSARY */ e2 = TRUNC( maxy ); f2 = 0; } else { e2 = TRUNC( y2 ); f2 = FRAC( y2 ); } if ( f1 > 0 ) { if ( e1 == e2 ) return SUCCESS; else { x1 += FMulDiv( Dx, ras.precision - f1, Dy ); e1 += 1; } } else if ( ras.joint ) { ras.top--; ras.joint = FALSE; } ras.joint = ( f2 == 0 ); if ( ras.fresh ) { ras.cProfile->start = e1; ras.fresh = FALSE; } size = e2 - e1 + 1; if ( ras.top + size >= ras.maxBuff ) { ras.error = Raster_Err_Overflow; return FAILURE; } if ( Dx > 0 ) { Ix = (ras.precision*Dx) / Dy; Rx = (ras.precision*Dx) % Dy; Dx = 1; } else { Ix = -( (ras.precision*-Dx) / Dy ); Rx = (ras.precision*-Dx) % Dy; Dx = -1; } Ax = -Dy; top = ras.top; while ( size > 0 ) { *top++ = x1; x1 += Ix; Ax += Rx; if ( Ax >= 0 ) { Ax -= Dy; x1 += Dx; } size--; } ras.top = top; return SUCCESS; } static Bool Line_Down( RAS_ARGS Long x1, Long y1, Long x2, Long y2, Long miny, Long maxy ) { Bool result, fresh; fresh = ras.fresh; result = Line_Up( RAS_VARS x1, -y1, x2, -y2, -maxy, -miny ); if ( fresh && !ras.fresh ) ras.cProfile->start = -ras.cProfile->start; return result; } /****************************************************************************/ /* */ /* Function: Bezier_Up */ /* */ /* Description: Computes thes x-coordinates of an ascending bezier arc */ /* and stores them in the render pool. */ /* */ /* A function type describing the functions used to split bezier arcs */ typedef void (*TSplitter)( TPoint* base ); static Bool Bezier_Up( RAS_ARGS Int degree, TSplitter splitter, Long miny, Long maxy ) { Long y1, y2, e, e2, e0; Short f1; TPoint* arc; TPoint* start_arc; PLong top; arc = ras.arc; y1 = arc[degree].y; y2 = arc[0].y; top = ras.top; if ( y2 < miny || y1 > maxy ) goto Fin; e2 = FLOOR( y2 ); if ( e2 > maxy ) e2 = maxy; e0 = miny; if ( y1 < miny ) e = miny; else { e = CEILING( y1 ); f1 = FRAC( y1 ); e0 = e; if ( f1 == 0 ) { if ( ras.joint ) { top--; ras.joint = FALSE; } *top++ = arc[degree].x; e += ras.precision; } } if ( ras.fresh ) { ras.cProfile->start = TRUNC( e0 ); ras.fresh = FALSE; } if ( e2 < e ) goto Fin; if ( ( top + TRUNC( e2 - e ) + 1 ) >= ras.maxBuff ) { ras.top = top; ras.error = Raster_Err_Overflow; return FAILURE; } start_arc = arc; while ( arc >= start_arc && e <= e2 ) { ras.joint = FALSE; y2 = arc[0].y; if ( y2 > e ) { y1 = arc[degree].y; if ( y2 - y1 >= ras.precision_step ) { splitter( arc ); arc += degree; } else { *top++ = arc[degree].x + FMulDiv( arc[0].x-arc[degree].x, e - y1, y2 - y1 ); arc -= degree; e += ras.precision; } } else { if ( y2 == e ) { ras.joint = TRUE; *top++ = arc[0].x; e += ras.precision; } arc -= degree; } } Fin: ras.top = top; ras.arc -= degree; return SUCCESS; } /****************************************************************************/ /* */ /* Function: Bezier_Down */ /* */ /* Description: Computes the x-coordinates of a descending bezier arc */ /* and stores them in the render pool. */ /* */ static Bool Bezier_Down( RAS_ARGS Int degree, TSplitter splitter, Long miny, Long maxy ) { TPoint* arc = ras.arc; Bool result, fresh; arc[0].y = -arc[0].y; arc[1].y = -arc[1].y; arc[2].y = -arc[2].y; if (degree > 2) arc[3].y = -arc[3].y; fresh = ras.fresh; result = Bezier_Up( RAS_VARS degree, splitter, -maxy, -miny ); if ( fresh && !ras.fresh ) ras.cProfile->start = -ras.cProfile->start; arc[0].y = -arc[0].y; return result; } /****************************************************************************/ /* */ /* Function: Line_To */ /* */ /* Description: Injects a new line segment and adjusts Profiles list. */ /* */ /* Input: x, y : segment endpoint (start point in LastX,LastY) */ /* */ /* Returns: SUCCESS on success. */ /* FAILURE on Render Pool overflow or Incorrect Profile. */ /* */ /****************************************************************************/ static Bool Line_To( RAS_ARGS Long x, Long y ) { /* First, detect a change of direction */ switch ( ras.state ) { case Unknown: if ( y > ras.lastY ) { if ( New_Profile( RAS_VARS Ascending ) ) return FAILURE; } else { if ( y < ras.lastY ) if ( New_Profile( RAS_VARS Descending ) ) return FAILURE; } break; case Ascending: if ( y < ras.lastY ) { if ( End_Profile( RAS_VAR ) || New_Profile( RAS_VARS Descending ) ) return FAILURE; } break; case Descending: if ( y > ras.lastY ) { if ( End_Profile( RAS_VAR ) || New_Profile( RAS_VARS Ascending ) ) return FAILURE; } break; default: ; } /* Then compute the lines */ switch ( ras.state ) { case Ascending: if ( Line_Up ( RAS_VARS ras.lastX, ras.lastY, x, y, ras.minY, ras.maxY ) ) return FAILURE; break; case Descending: if ( Line_Down( RAS_VARS ras.lastX, ras.lastY, x, y, ras.minY, ras.maxY ) ) return FAILURE; break; default: ; } ras.lastX = x; ras.lastY = y; return SUCCESS; } /****************************************************************************/ /* */ /* Function: Conic_To */ /* */ /* Description: Injects a new conic arc and adjusts the profile list. */ /* */ static Bool Conic_To( RAS_ARGS Long cx, Long cy, Long x, Long y ) { Long y1, y2, y3, x3, ymin, ymax; TStates state_bez; ras.arc = ras.arcs; ras.arc[2].x = ras.lastX; ras.arc[2].y = ras.lastY; ras.arc[1].x = cx; ras.arc[1].y = cy; ras.arc[0].x = x; ras.arc[0].y = y; do { y1 = ras.arc[2].y; y2 = ras.arc[1].y; y3 = ras.arc[0].y; x3 = ras.arc[0].x; /* first, categorize the Bezier arc */ if ( y1 <= y3 ) { ymin = y1; ymax = y3; } else { ymin = y3; ymax = y1; } if ( y2 < ymin || y2 > ymax ) { /* this arc has no given direction, split it !! */ Split_Conic( ras.arc ); ras.arc += 2; } else if ( y1 == y3 ) { /* this arc is flat, ignore it and pop it from the bezier stack */ ras.arc -= 2; } else { /* the arc is y-monotonous, either ascending or descending */ /* detect a change of direction */ state_bez = y1 < y3 ? Ascending : Descending; if ( ras.state != state_bez ) { /* finalize current profile if any */ if ( ras.state != Unknown && End_Profile( RAS_VAR ) ) goto Fail; /* create a new profile */ if ( New_Profile( RAS_VARS state_bez ) ) goto Fail; } /* now call the appropriate routine */ if ( state_bez == Ascending ) { if ( Bezier_Up( RAS_VARS 2, Split_Conic, ras.minY, ras.maxY ) ) goto Fail; } else if ( Bezier_Down( RAS_VARS 2, Split_Conic, ras.minY, ras.maxY ) ) goto Fail; } } while ( ras.arc >= ras.arcs ); ras.lastX = x3; ras.lastY = y3; return SUCCESS; Fail: return FAILURE; } /****************************************************************************/ /* */ /* Function: Cubic_To */ /* */ /* Description: Injects a new cubic arc and adjusts the profile list. */ /* */ static Bool Cubic_To( RAS_ARGS Long cx1, Long cy1, Long cx2, Long cy2, Long x, Long y ) { Long y1, y2, y3, y4, x4, ymin1, ymax1, ymin2, ymax2; TStates state_bez; ras.arc = ras.arcs; ras.arc[3].x = ras.lastX; ras.arc[3].y = ras.lastY; ras.arc[2].x = cx1; ras.arc[2].y = cy1; ras.arc[1].x = cx2; ras.arc[1].y = cy2; ras.arc[0].x = x; ras.arc[0].y = y; do { y1 = ras.arc[3].y; y2 = ras.arc[2].y; y3 = ras.arc[1].y; y4 = ras.arc[0].y; x4 = ras.arc[0].x; /* first, categorize the Bezier arc */ if ( y1 <= y4 ) { ymin1 = y1; ymax1 = y4; } else { ymin1 = y4; ymax1 = y1; } if ( y2 <= y3 ) { ymin2 = y2; ymax2 = y3; } else { ymin2 = y3; ymax2 = y2; } if ( ymin2 < ymin1 || ymax2 > ymax1 ) { /* this arc has no given direction, split it! */ Split_Cubic( ras.arc ); ras.arc += 3; } else if ( y1 == y4 ) { /* this arc is flat, ignore it and pop it from the bezier stack */ ras.arc -= 3; } else { state_bez = ( y1 <= y4 ) ? Ascending : Descending; /* detect a change of direction */ if ( ras.state != state_bez ) { if ( ras.state != Unknown && End_Profile( RAS_VAR ) ) goto Fail; if ( New_Profile( RAS_VARS state_bez ) ) goto Fail; } /* compute intersections */ if ( state_bez == Ascending ) { if ( Bezier_Up ( RAS_VARS 3, Split_Cubic, ras.minY, ras.maxY ) ) goto Fail; } else if ( Bezier_Down ( RAS_VARS 3, Split_Cubic, ras.minY, ras.maxY ) ) goto Fail; } } while ( ras.arc >= ras.arcs ); ras.lastX = x4; ras.lastY = y4; return SUCCESS; Fail: return FAILURE; } /****************************************************************************/ /* */ /* Function: Decompose_Curve */ /* */ /* Description: Scans the outline arays in order to emit individual */ /* segments and beziers by calling Line_To() and Bezier_To(). */ /* It handles all weird cases, like when the first point */ /* is off the curve, or when there are simply no 'on' */ /* points in the contour! */ /* */ /* Input: first, last : indexes of first and last point in */ /* contour. */ /* */ /* Returns: SUCCESS on success. */ /* FAILURE on error. */ /* */ /****************************************************************************/ #undef SWAP_ #define SWAP_(x,y) { Long swap = x; x = y; y = swap; } static Bool Decompose_Curve( RAS_ARGS UShort first, UShort last, int flipped ) { FT_Vector v_last; FT_Vector v_control; FT_Vector v_start; FT_Vector* points; FT_Vector* point; FT_Vector* limit; char* tags; char tag; /* current point's state */ points = ras.outline.points; limit = points + last; v_start.x = SCALED(points[first].x); v_start.y = SCALED(points[first].y); v_last.x = SCALED(points[last].x); v_last.y = SCALED(points[last].y); if (flipped) { SWAP_(v_start.x,v_start.y); SWAP_(v_last.x,v_last.y); } v_control = v_start; point = points + first; tags = ras.outline.tags + first; tag = FT_CURVE_TAG( tags[0] ); /* A contour cannot start with a cubic control point! */ if ( tag == FT_Curve_Tag_Cubic ) goto Invalid_Outline; /* check first point to determine origin */ if ( tag == FT_Curve_Tag_Conic ) { /* first point is conic control. Yes, this happens. */ if ( FT_CURVE_TAG( ras.outline.tags[last] ) == FT_Curve_Tag_On ) { /* start at last point if it is on the curve */ v_start = v_last; limit--; } else { /* if both first and last points are conic, */ /* start at their middle and record its position */ /* for closure */ v_start.x = ( v_start.x + v_last.x ) / 2; v_start.y = ( v_start.y + v_last.y ) / 2; v_last = v_start; } point--; tags--; } ras.lastX = v_start.x; ras.lastY = v_start.y; while (point < limit) { point++; tags++; tag = FT_CURVE_TAG( tags[0] ); switch (tag) { case FT_Curve_Tag_On: /* emit a single line_to */ { Long x, y; x = SCALED(point->x); y = SCALED(point->y); if (flipped) SWAP_(x,y); if (Line_To( RAS_VARS x, y )) goto Fail; continue; } case FT_Curve_Tag_Conic: /* consume conic arcs */ { v_control.x = SCALED(point[0].x); v_control.y = SCALED(point[0].y); if (flipped) SWAP_(v_control.x,v_control.y); Do_Conic: if (point < limit) { FT_Vector v_middle; Long x, y; point++; tags++; tag = FT_CURVE_TAG( tags[0] ); x = SCALED(point[0].x); y = SCALED(point[0].y); if (flipped) SWAP_(x,y); if (tag == FT_Curve_Tag_On) { if (Conic_To( RAS_VARS v_control.x, v_control.y, x, y )) goto Fail; continue; } if (tag != FT_Curve_Tag_Conic) goto Invalid_Outline; v_middle.x = (v_control.x + x)/2; v_middle.y = (v_control.y + y)/2; if (Conic_To( RAS_VARS v_control.x, v_control.y, v_middle.x, v_middle.y )) goto Fail; v_control.x = x; v_control.y = y; goto Do_Conic; } if (Conic_To( RAS_VARS v_control.x, v_control.y, v_start.x, v_start.y )) goto Fail; goto Close; } default: /* FT_Curve_Tag_Cubic */ { Long x1, y1, x2, y2, x3, y3; if ( point+1 > limit || FT_CURVE_TAG( tags[1] ) != FT_Curve_Tag_Cubic ) goto Invalid_Outline; point += 2; tags += 2; x1 = SCALED(point[-2].x); y1 = SCALED(point[-2].y); x2 = SCALED(point[-1].x); y2 = SCALED(point[-1].y); x3 = SCALED(point[ 0].x); y3 = SCALED(point[ 0].y); if (flipped) { SWAP_(x1,y1); SWAP_(x2,y2); SWAP_(x3,y3); } if (point <= limit) { if (Cubic_To( RAS_VARS x1, y1, x2, y2, x3, y3 )) goto Fail; continue; } if (Cubic_To( RAS_VARS x1, y1, x2, y2, v_start.x, v_start.y )) goto Fail; goto Close; } } } /* close the contour with a line segment */ if (Line_To( RAS_VARS v_start.x, v_start.y )) goto Fail; Close: return SUCCESS; Invalid_Outline: ras.error = Raster_Err_Invalid; Fail: return FAILURE; } /****************************************************************************/ /* */ /* Function: Convert_Glyph */ /* */ /* Description: Converts a glyph into a series of segments and arcs */ /* and makes a Profiles list with them. */ /* */ /* Input: _xCoord, _yCoord : coordinates tables. */ /* */ /* Uses the 'Flag' table too. */ /* */ /* Returns: SUCCESS on success. */ /* FAILURE if any error was encountered during rendering. */ /* */ /****************************************************************************/ static Bool Convert_Glyph( RAS_ARGS int flipped ) { Short i; UShort start; PProfile lastProfile; ras.fProfile = NULL; ras.joint = FALSE; ras.fresh = FALSE; ras.maxBuff = ras.sizeBuff - AlignProfileSize; ras.numTurns = 0; ras.cProfile = (PProfile)ras.top; ras.cProfile->offset = ras.top; ras.num_Profs = 0; start = 0; for ( i = 0; i < ras.outline.n_contours; i++ ) { ras.state = Unknown; ras.gProfile = NULL; if ( Decompose_Curve( RAS_VARS start, ras.outline.contours[i], flipped ) ) return FAILURE; start = ras.outline.contours[i] + 1; /* We must now see if the extreme arcs join or not */ if ( ( FRAC( ras.lastY ) == 0 && ras.lastY >= ras.minY && ras.lastY <= ras.maxY ) ) if ( ras.gProfile && ras.gProfile->flow == ras.cProfile->flow ) 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 ) ) return FAILURE; /* close the 'next profile in contour' linked list */ if ( ras.gProfile ) lastProfile->next = ras.gProfile; } if (Finalize_Profile_Table( RAS_VAR )) return FAILURE; return (ras.top < ras.maxBuff ? SUCCESS : FAILURE ); } /****************************************************************/ /****************************************************************/ /** **/ /** SCAN-LINE SWEEPS AND DRAWING **/ /** **/ /****************************************************************/ /****************************************************************/ /************************************************/ /* */ /* Init_Linked */ /* */ /* Inits an empty linked list. */ /* */ /************************************************/ static void Init_Linked( TProfileList* l ) { *l = NULL; } /************************************************/ /* */ /* InsNew : */ /* */ /* Inserts a new Profile in a linked list. */ /* */ /************************************************/ static void InsNew( PProfileList list, PProfile profile ) { PProfile *old, current; Long x; old = list; current = *old; x = profile->X; while ( current ) { if ( x < current->X ) break; old = ¤t->link; current = *old; } profile->link = current; *old = profile; } /*************************************************/ /* */ /* DelOld : */ /* */ /* Removes an old Profile from a linked list. */ /* */ /*************************************************/ static void DelOld( PProfileList list, PProfile profile ) { PProfile *old, current; old = list; current = *old; while ( current ) { if ( current == profile ) { *old = current->link; return; } old = ¤t->link; current = *old; } /* we should never get there, unless the Profile was not part of */ /* the list. */ } /************************************************/ /* */ /* Update : */ /* */ /* Update all X offsets of a drawing list */ /* */ /************************************************/ static void Update( PProfile first ) { PProfile current = first; while ( current ) { current->X = *current->offset; current->offset += current->flow; current->height--; current = current->link; } } /************************************************/ /* */ /* Sort : */ /* */ /* Sorts a trace list. In 95%, the list */ /* is already sorted. We need an algorithm */ /* which is fast in this case. Bubble sort */ /* is enough and simple. */ /* */ /************************************************/ static void Sort( PProfileList list ) { PProfile *old, current, next; /* First, set the new X coordinate of each profile */ Update( *list ); /* Then sort them */ old = list; current = *old; if ( !current ) return; next = current->link; while ( next ) { if ( current->X <= next->X ) { old = ¤t->link; current = *old; if ( !current ) return; } else { *old = next; current->link = next->link; next->link = current; old = list; current = *old; } next = current->link; } } /***********************************************************************/ /* */ /* Vertical Sweep Procedure Set : */ /* */ /* These three routines are used during the vertical black/white */ /* sweep phase by the generic Draw_Sweep() function. */ /* */ /***********************************************************************/ static void Vertical_Sweep_Init( RAS_ARGS Short* min, Short* max ) { Long pitch = ras.target.pitch; UNUSED(max); ras.traceIncr = (Short)- pitch; ras.traceOfs = - *min * pitch; if (pitch > 0) ras.traceOfs += (ras.target.rows-1)*pitch; ras.gray_min_x = 0; ras.gray_max_x = 0; } static void Vertical_Sweep_Span( RAS_ARGS Short y, FT_F26Dot6 x1, FT_F26Dot6 x2, PProfile left, PProfile right ) { Long e1, e2; Short c1, c2; Byte f1, f2; Byte* target; UNUSED(y); UNUSED(left); UNUSED(right); /* Drop-out control */ e1 = TRUNC( CEILING( x1 ) ); if ( x2-x1-ras.precision <= ras.precision_jitter ) e2 = e1; else e2 = TRUNC( FLOOR( x2 ) ); if ( e2 >= 0 && e1 < ras.bWidth ) { if ( e1 < 0 ) e1 = 0; if ( e2 >= ras.bWidth ) e2 = ras.bWidth-1; c1 = (Short)(e1 >> 3); c2 = (Short)(e2 >> 3); f1 = ((unsigned char)0xFF >> (e1 & 7)); f2 = ~((unsigned char)0x7F >> (e2 & 7)); if ( ras.gray_min_x > c1 ) ras.gray_min_x = c1; if ( ras.gray_max_x < c2 ) ras.gray_max_x = c2; target = ras.bTarget + ras.traceOfs + c1; c2 -= c1; if ( c2 > 0 ) { target[0] |= f1; /* memset() is slower than the following code on many platforms. */ /* This is due to the fact that, in the vast majority of cases, */ /* the span length in bytes is relatively small. */ c2--; while ( c2 > 0 ) { *(++target) = 0xFF; c2--; } target[1] |= f2; } else *target |= ( f1 & f2 ); } } static void Vertical_Sweep_Drop( RAS_ARGS Short y, FT_F26Dot6 x1, FT_F26Dot6 x2, PProfile left, PProfile right ) { Long e1, e2; Short c1, f1; /* Drop-out control */ e1 = CEILING( x1 ); e2 = FLOOR ( x2 ); if ( e1 > e2 ) { if ( e1 == e2 + ras.precision ) { switch ( ras.dropOutControl ) { case 1: e1 = e2; break; case 4: e1 = CEILING( (x1 + x2 + 1) / 2 ); break; case 2: case 5: /* Drop-out Control Rule #4 */ /* The spec is not very clear regarding rule #4. It */ /* presents a method that is way too costly to implement */ /* while the general idea seems to get rid of 'stubs'. */ /* */ /* Here, we only get rid of stubs recognized when: */ /* */ /* upper stub: */ /* */ /* - P_Left and P_Right are in the same contour */ /* - P_Right is the successor of P_Left in that contour */ /* - y is the top of P_Left and P_Right */ /* */ /* lower stub: */ /* */ /* - P_Left and P_Right are in the same contour */ /* - P_Left is the successor of P_Right in that contour */ /* - y is the bottom of P_Left */ /* */ /* 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 ( x2-x1 < ras.precision_half ) */ { /* upper stub test */ if ( left->next == right && left->height <= 0 ) return; /* lower stub test */ if ( right->next == left && left->start == y ) return; } /* check that the rightmost pixel isn't set */ e1 = TRUNC( e1 ); c1 = (Short)(e1 >> 3); f1 = e1 & 7; if ( e1 >= 0 && e1 < ras.bWidth && ras.bTarget[ras.traceOfs + c1] & (0x80 >> f1) ) return; if ( ras.dropOutControl == 2 ) e1 = e2; else e1 = CEILING( (x1 + x2 + 1) / 2 ); break; default: return; /* unsupported mode */ } } else return; } e1 = TRUNC( e1 ); if ( e1 >= 0 && e1 < ras.bWidth ) { c1 = (Short)(e1 >> 3); f1 = e1 & 7; if ( ras.gray_min_x > c1 ) ras.gray_min_x = c1; if ( ras.gray_max_x < c1 ) ras.gray_max_x = c1; ras.bTarget[ras.traceOfs + c1] |= (char)(0x80 >> f1); } } static void Vertical_Sweep_Step( RAS_ARG ) { ras.traceOfs += ras.traceIncr; } /***********************************************************************/ /* */ /* Horizontal Sweep Procedure Set : */ /* */ /* These three routines are used during the horizontal black/white */ /* sweep phase by the generic Draw_Sweep() function. */ /* */ /***********************************************************************/ static void Horizontal_Sweep_Init( RAS_ARGS Short* min, Short* max ) { /* nothing, really */ UNUSED(raster); UNUSED(min); UNUSED(max); } static void Horizontal_Sweep_Span( RAS_ARGS Short y, FT_F26Dot6 x1, FT_F26Dot6 x2, PProfile left, PProfile right ) { Long e1, e2; PByte bits; Byte f1; UNUSED(left); UNUSED(right); if ( x2-x1 < ras.precision ) { e1 = CEILING( x1 ); e2 = FLOOR ( x2 ); if ( e1 == e2 ) { bits = ras.bTarget + (y >> 3); f1 = (Byte)(0x80 >> (y & 7)); e1 = TRUNC( e1 ); if ( e1 >= 0 && e1 < ras.target.rows ) { PByte p; p = bits - e1*ras.target.pitch; if (ras.target.pitch > 0) p += (ras.target.rows-1)*ras.target.pitch; p[0] |= f1; } } } } static void Horizontal_Sweep_Drop( RAS_ARGS Short y, FT_F26Dot6 x1, FT_F26Dot6 x2, PProfile left, PProfile right ) { Long e1, e2; PByte bits; Byte f1; /* During the horizontal sweep, we only take care of drop-outs */ e1 = CEILING( x1 ); e2 = FLOOR ( x2 ); if ( e1 > e2 ) { if ( e1 == e2 + ras.precision ) { switch ( ras.dropOutControl ) { case 1: e1 = e2; break; case 4: e1 = CEILING( (x1 + x2 + 1) / 2 ); break; case 2: case 5: /* Drop-out Control Rule #4 */ /* The spec is not very clear regarding rule #4. It */ /* presents a method that is way too costly to implement */ /* while the general idea seems to get rid of 'stubs'. */ /* */ /* rightmost stub test */ if ( left->next == right && left->height <= 0 ) return; /* leftmost stub test */ if ( right->next == left && left->start == y ) return; /* check that the rightmost pixel isn't set */ e1 = TRUNC( e1 ); bits = ras.bTarget + (y >> 3); f1 = (Byte)(0x80 >> (y & 7)); bits -= e1*ras.target.pitch; if (ras.target.pitch > 0) bits += (ras.target.rows-1)*ras.target.pitch; if ( e1 >= 0 && e1 < ras.target.rows && *bits & f1 ) return; if ( ras.dropOutControl == 2 ) e1 = e2; else e1 = CEILING( (x1 + x2 + 1) / 2 ); break; default: return; /* unsupported mode */ } } else return; } bits = ras.bTarget + (y >> 3); f1 = (Byte)(0x80 >> (y & 7)); e1 = TRUNC( e1 ); if ( e1 >= 0 && e1 < ras.target.rows ) { bits -= e1*ras.target.pitch; if (ras.target.pitch > 0) bits += (ras.target.rows-1)*ras.target.pitch; bits[0] |= f1; } } static void Horizontal_Sweep_Step( RAS_ARG ) { /* Nothing, really */ UNUSED(raster); } #ifdef FT_RASTER_OPTION_ANTI_ALIASING /***********************************************************************/ /* */ /* Vertical Gray Sweep Procedure Set: */ /* */ /* These two routines are used during the vertical gray-levels */ /* sweep phase by the generic Draw_Sweep() function. */ /* */ /* */ /* NOTES: */ /* */ /* - The target pixmap's width *must* be a multiple of 4. */ /* */ /* - you have to use the function Vertical_Sweep_Span() for */ /* the gray span call. */ /* */ /***********************************************************************/ static void Vertical_Gray_Sweep_Init( RAS_ARGS Short* min, Short* max ) { Long pitch, byte_len; *min = *min & -2; *max = ( *max + 3 ) & -2; ras.traceOfs = 0; pitch = ras.target.pitch; byte_len = -pitch; ras.traceIncr = (Short)byte_len; ras.traceG = (*min/2)*byte_len; if (pitch > 0) { ras.traceG += (ras.target.rows-1)*pitch; byte_len = -byte_len; } ras.gray_min_x = (Short)byte_len; ras.gray_max_x = -(Short)byte_len; } static void Vertical_Gray_Sweep_Step( RAS_ARG ) { Int c1, c2; PByte pix, bit, bit2; Int* count = ras.count_table; Byte* grays; ras.traceOfs += ras.gray_width; if ( ras.traceOfs > ras.gray_width ) { pix = ras.gTarget + ras.traceG + ras.gray_min_x * 4; grays = ras.grays; if ( ras.gray_max_x >= 0 ) { Long last_pixel = ras.target.width-1; Int last_cell = last_pixel >> 2; Int last_bit = last_pixel & 3; Bool over = 0; if (ras.gray_max_x >= last_cell && last_bit != 3) { ras.gray_max_x = last_cell-1; over = 1; } if ( ras.gray_min_x < 0 ) ras.gray_min_x = 0; bit = ras.bTarget + ras.gray_min_x; bit2 = bit + ras.gray_width; c1 = ras.gray_max_x - ras.gray_min_x; while ( c1 >= 0 ) { c2 = count[*bit] + count[*bit2]; if ( c2 ) { pix[0] = grays[(c2 >> 12) & 0x000F]; pix[1] = grays[(c2 >> 8 ) & 0x000F]; pix[2] = grays[(c2 >> 4 ) & 0x000F]; pix[3] = grays[ c2 & 0x000F]; *bit = 0; *bit2 = 0; } bit ++; bit2++; pix += 4; c1 --; } if (over) { c2 = count[*bit] + count[*bit2]; if (c2) { switch (last_bit) { case 2: pix[2] = grays[(c2 >> 4 ) & 0x000F]; case 1: pix[1] = grays[(c2 >> 8 ) & 0x000F]; default: pix[0] = grays[(c2 >> 12) & 0x000F]; } *bit = 0; *bit2 = 0; } } } ras.traceOfs = 0; ras.traceG += ras.traceIncr; ras.gray_min_x = 32000; ras.gray_max_x = -32000; } } static void Horizontal_Gray_Sweep_Span( RAS_ARGS Short y, FT_F26Dot6 x1, FT_F26Dot6 x2, PProfile left, PProfile right ) { /* nothing, really */ UNUSED(raster); UNUSED(y); UNUSED(x1); UNUSED(x2); UNUSED(left); UNUSED(right); } static void Horizontal_Gray_Sweep_Drop( RAS_ARGS Short y, FT_F26Dot6 x1, FT_F26Dot6 x2, PProfile left, PProfile right ) { Long e1, e2; PByte pixel; Byte color; /* During the horizontal sweep, we only take care of drop-outs */ e1 = CEILING( x1 ); e2 = FLOOR ( x2 ); if ( e1 > e2 ) { if ( e1 == e2 + ras.precision ) { switch ( ras.dropOutControl ) { case 1: e1 = e2; break; case 4: e1 = CEILING( (x1 + x2 + 1) / 2 ); break; case 2: case 5: /* Drop-out Control Rule #4 */ /* The spec is not very clear regarding rule #4. It */ /* presents a method that is way too costly to implement */ /* while the general idea seems to get rid of 'stubs'. */ /* */ /* rightmost stub test */ if ( left->next == right && left->height <= 0 ) return; /* leftmost stub test */ if ( right->next == left && left->start == y ) return; if ( ras.dropOutControl == 2 ) e1 = e2; else e1 = CEILING( (x1 + x2 + 1) / 2 ); break; default: return; /* unsupported mode */ } } else return; } if ( e1 >= 0 ) { if ( x2 - x1 >= ras.precision_half ) color = ras.grays[2]; else color = ras.grays[1]; e1 = TRUNC( e1 ) / 2; if ( e1 < ras.target.rows ) { pixel = ras.gTarget - e1*ras.target.pitch + y/2; if (ras.target.pitch > 0) pixel += (ras.target.rows-1)*ras.target.pitch; if (pixel[0] == ras.grays[0]) pixel[0] = color; } } } #endif /* FT_RASTER_OPTION_ANTI_ALIASING */ /********************************************************************/ /* */ /* Generic Sweep Drawing routine */ /* */ /********************************************************************/ static Bool Draw_Sweep( RAS_ARG ) { Short y, y_change, y_height; PProfile P, Q, P_Left, P_Right; Short min_Y, max_Y, top, bottom, dropouts; Long x1, x2, xs, e1, e2; TProfileList wait; TProfileList draw_left, draw_right; /* Init empty linked lists */ Init_Linked( &wait ); Init_Linked( &draw_left ); Init_Linked( &draw_right ); /* first, compute min and max Y */ P = ras.fProfile; max_Y = (short)TRUNC( ras.minY ); min_Y = (short)TRUNC( ras.maxY ); while ( P ) { Q = P->link; bottom = (Short)P->start; top = (Short)P->start + P->height-1; if ( min_Y > bottom ) min_Y = bottom; if ( max_Y < top ) max_Y = top; P->X = 0; InsNew( &wait, P ); P = Q; } /* Check the Y-turns */ if ( ras.numTurns == 0 ) { ras.error = Raster_Err_Invalid; return FAILURE; } /* Now inits the sweep */ ras.Proc_Sweep_Init( RAS_VARS &min_Y, &max_Y ); /* Then compute the distance of each profile from min_Y */ P = wait; while ( P ) { P->countL = P->start - min_Y; P = P->link; } /* Let's go */ y = min_Y; y_height = 0; if ( ras.numTurns > 0 && ras.sizeBuff[-ras.numTurns] == min_Y ) ras.numTurns--; while ( ras.numTurns > 0 ) { /* look in the wait list for new activations */ P = wait; while ( P ) { Q = P->link; P->countL -= y_height; if ( P->countL == 0 ) { DelOld( &wait, P ); switch ( P->flow ) { case Flow_Up: InsNew( &draw_left, P ); break; case Flow_Down: InsNew( &draw_right, P ); break; } } P = Q; } /* Sort the drawing lists */ Sort( &draw_left ); Sort( &draw_right ); y_change = (Short)ras.sizeBuff[-ras.numTurns--]; y_height = y_change - y; while ( y < y_change ) { /* Let's trace */ dropouts = 0; P_Left = draw_left; P_Right = draw_right; while ( P_Left ) { x1 = P_Left ->X; x2 = P_Right->X; if ( x1 > x2 ) { xs = x1; x1 = x2; x2 = xs; } if ( x2-x1 <= ras.precision ) { e1 = FLOOR( x1 ); e2 = CEILING( x2 ); if ( ras.dropOutControl != 0 && (e1 > e2 || e2 == e1 + ras.precision) ) { /* a drop out was detected */ P_Left ->X = x1; P_Right->X = x2; /* mark profile for drop-out processing */ P_Left->countL = 1; dropouts++; goto Skip_To_Next; } } ras.Proc_Sweep_Span( RAS_VARS y, x1, x2, P_Left, P_Right ); Skip_To_Next: P_Left = P_Left->link; P_Right = P_Right->link; } /* now perform the dropouts _after_ the span drawing */ /* drop-outs processing has been moved out of the loop */ /* for performance tuning */ if (dropouts > 0) goto Scan_DropOuts; Next_Line: ras.Proc_Sweep_Step( RAS_VAR ); y++; if ( y < y_change ) { Sort( &draw_left ); Sort( &draw_right ); } } /* Now finalize the profiles that needs it */ { PProfile Q, P; P = draw_left; while ( P ) { Q = P->link; if ( P->height == 0 ) DelOld( &draw_left, P ); P = Q; } } { PProfile Q, P = draw_right; while ( P ) { Q = P->link; if ( P->height == 0 ) DelOld( &draw_right, P ); P = Q; } } } /* for gray-scaling, flushes the bitmap scanline cache */ while ( y <= max_Y ) { ras.Proc_Sweep_Step( RAS_VAR ); y++; } return SUCCESS; Scan_DropOuts : P_Left = draw_left; P_Right = draw_right; while ( P_Left ) { if ( P_Left->countL ) { P_Left->countL = 0; /* dropouts--; -- this is useful when debugging only */ ras.Proc_Sweep_Drop( RAS_VARS y, P_Left->X, P_Right->X, P_Left, P_Right ); } P_Left = P_Left->link; P_Right = P_Right->link; } goto Next_Line; } /****************************************************************************/ /* */ /* Function: Render_Single_Pass */ /* */ /* Description: Performs one sweep with sub-banding. */ /* */ /* Input: _XCoord, _YCoord : x and y coordinates arrays */ /* */ /* Returns: SUCCESS on success */ /* FAILURE if any error was encountered during render. */ /* */ /****************************************************************************/ static FT_Error Render_Single_Pass( RAS_ARGS Bool flipped ) { Short i, j, k; while ( ras.band_top >= 0 ) { ras.maxY = (Long)ras.band_stack[ras.band_top].y_max * ras.precision; ras.minY = (Long)ras.band_stack[ras.band_top].y_min * ras.precision; ras.top = ras.buff; ras.error = Raster_Err_None; if ( Convert_Glyph( RAS_VARS flipped ) ) { if ( ras.error != Raster_Err_Overflow ) return FAILURE; ras.error = Raster_Err_None; /* sub-banding */ #ifdef DEBUG_RASTER ClearBand( RAS_VARS TRUNC( ras.minY ), TRUNC( ras.maxY ) ); #endif i = ras.band_stack[ras.band_top].y_min; j = ras.band_stack[ras.band_top].y_max; k = ( i + j ) / 2; if ( ras.band_top >= 7 || k < i ) { ras.band_top = 0; ras.error = Raster_Err_Invalid; return ras.error; } ras.band_stack[ras.band_top+1].y_min = k; ras.band_stack[ras.band_top+1].y_max = j; ras.band_stack[ras.band_top].y_max = k - 1; ras.band_top++; } else { if ( ras.fProfile ) if ( Draw_Sweep( RAS_VAR ) ) return ras.error; ras.band_top--; } } return FT_Err_Ok; } /****************************************************************************/ /* */ /* Function: Render_Glyph */ /* */ /* Description: Renders a glyph in a bitmap. Sub-banding if needed. */ /* */ /* Input: AGlyph Glyph record */ /* */ /* Returns: SUCCESS on success. */ /* FAILURE if any error was encountered during rendering. */ /* */ /****************************************************************************/ LOCAL_FUNC FT_Error Render_Glyph( RAS_ARG ) { FT_Error error; Set_High_Precision( RAS_VARS ras.outline.flags & ft_outline_high_precision ); ras.scale_shift = ras.precision_shift; ras.dropOutControl = 2; ras.second_pass = !(ras.outline.flags & ft_outline_single_pass); /* Vertical Sweep */ ras.Proc_Sweep_Init = Vertical_Sweep_Init; ras.Proc_Sweep_Span = Vertical_Sweep_Span; ras.Proc_Sweep_Drop = Vertical_Sweep_Drop; ras.Proc_Sweep_Step = Vertical_Sweep_Step; ras.band_top = 0; ras.band_stack[0].y_min = 0; ras.band_stack[0].y_max = ras.target.rows - 1; ras.bWidth = ras.target.width; ras.bTarget = (Byte*)ras.target.buffer; if ( (error = Render_Single_Pass( RAS_VARS 0 )) != 0 ) return error; /* Horizontal Sweep */ if ( ras.second_pass && ras.dropOutControl != 0 ) { ras.Proc_Sweep_Init = Horizontal_Sweep_Init; ras.Proc_Sweep_Span = Horizontal_Sweep_Span; ras.Proc_Sweep_Drop = Horizontal_Sweep_Drop; ras.Proc_Sweep_Step = Horizontal_Sweep_Step; ras.band_top = 0; ras.band_stack[0].y_min = 0; ras.band_stack[0].y_max = ras.target.width - 1; if ( (error = Render_Single_Pass( RAS_VARS 1 )) != 0 ) return error; } return FT_Err_Ok; } /****************************************************************************/ /* */ /* Function: Render_Gray_Glyph */ /* */ /* Description: Renders a glyph with grayscaling. Sub-banding if needed. */ /* */ /* Input: AGlyph Glyph record */ /* */ /* Returns: SUCCESS on success */ /* FAILURE if any error was encountered during rendering. */ /* */ /****************************************************************************/ #ifdef FT_RASTER_OPTION_ANTI_ALIASING LOCAL_FUNC FT_Error Render_Gray_Glyph( RAS_ARG ) { Long pixel_width; FT_Error error; Set_High_Precision( RAS_VARS ras.outline.flags & ft_outline_high_precision ); ras.scale_shift = ras.precision_shift+1; ras.dropOutControl = 2; ras.second_pass = !(ras.outline.flags & ft_outline_single_pass); /* Vertical Sweep */ ras.band_top = 0; ras.band_stack[0].y_min = 0; ras.band_stack[0].y_max = 2 * ras.target.rows - 1; ras.bWidth = ras.gray_width; pixel_width = 2*((ras.target.width + 3) >> 2); if ( ras.bWidth > pixel_width ) ras.bWidth = pixel_width; ras.bWidth = ras.bWidth * 8; ras.bTarget = (Byte*)ras.gray_lines; ras.gTarget = (Byte*)ras.target.buffer; ras.Proc_Sweep_Init = Vertical_Gray_Sweep_Init; ras.Proc_Sweep_Span = Vertical_Sweep_Span; ras.Proc_Sweep_Drop = Vertical_Sweep_Drop; ras.Proc_Sweep_Step = Vertical_Gray_Sweep_Step; error = Render_Single_Pass( RAS_VARS 0 ); if (error) return error; /* Horizontal Sweep */ if ( ras.second_pass && ras.dropOutControl != 0 ) { ras.Proc_Sweep_Init = Horizontal_Sweep_Init; ras.Proc_Sweep_Span = Horizontal_Gray_Sweep_Span; ras.Proc_Sweep_Drop = Horizontal_Gray_Sweep_Drop; ras.Proc_Sweep_Step = Horizontal_Sweep_Step; ras.band_top = 0; ras.band_stack[0].y_min = 0; ras.band_stack[0].y_max = ras.target.width * 2 - 1; error = Render_Single_Pass( RAS_VARS 1 ); if (error) return error; } return FT_Err_Ok; } #else LOCAL_FUNC FT_Error Render_Gray_Glyph( RAS_ARG ) { UNUSED_RASTER return Raster_Err_Unsupported; } #endif static void ft_black_init( TRaster_Instance* raster ) { FT_UInt n; FT_ULong c; /* setup count table */ for ( n = 0; n < 256; n++ ) { c = (n & 0x55) + ((n & 0xAA) >> 1); c = ((c << 6) & 0x3000) | ((c << 4) & 0x0300) | ((c << 2) & 0x0030) | (c & 0x0003); raster->count_table[n] = c; } /* set default 5-levels gray palette */ for ( n = 0; n < 5; n++ ) raster->grays[n] = (n*127/4); raster->gray_width = RASTER_GRAY_LINES/2; } /**** RASTER OBJECT CREATION : in standalone mode, we simply use *****/ /**** a static object .. *****/ #ifdef _STANDALONE_ static int ft_black_new( void* memory, FT_Raster *araster ) { static FT_RasterRec_ the_raster; *araster = &the_raster; memset( &the_raster, sizeof(the_raster), 0 ); ft_black_init( &the_raster ); return 0; } static void ft_black_done( FT_Raster raster ) { /* nothing */ raster->init = 0; } #else #include <freetype/internal/ftobjs.h> static int ft_black_new( FT_Memory memory, TRaster_Instance* *araster ) { FT_Error error; TRaster_Instance* raster; *araster = 0; if ( !ALLOC( raster, sizeof(*raster) )) { raster->memory = memory; ft_black_init( raster ); *araster = raster; } return error; } static void ft_black_done( TRaster_Instance* raster ) { FT_Memory memory = (FT_Memory)raster->memory; FREE( raster ); } #endif static void ft_black_reset( TRaster_Instance* raster, const char* pool_base, long pool_size ) { if ( raster && pool_base && pool_size >= 4096 ) { /* save the pool */ raster->buff = (PLong)pool_base; raster->sizeBuff = raster->buff + pool_size / sizeof (Long); } } static void ft_black_set_mode( TRaster_Instance* raster, unsigned long mode, const char* palette ) { if (mode==FT_MAKE_TAG('p','a','l','5')) { /* set 5-levels gray palette */ raster->grays[0] = palette[0]; raster->grays[1] = palette[1]; raster->grays[2] = palette[2]; raster->grays[3] = palette[3]; raster->grays[4] = palette[4]; } } static int ft_black_render( TRaster_Instance* raster, FT_Raster_Params* params ) { FT_Outline* outline = (FT_Outline*)params->source; FT_Bitmap* target_map = params->target; if ( !raster || !raster->buff || !raster->sizeBuff ) return Raster_Err_Not_Ini; if ( !outline || !outline->contours || !outline->points ) return Raster_Err_Invalid; /* return immediately if the outline is empty */ if ( outline->n_points == 0 || outline->n_contours <= 0 ) return Raster_Err_None; if ( outline->n_points != outline->contours[outline->n_contours - 1] + 1 ) return Raster_Err_Invalid; if ( !target_map || !target_map->buffer ) return Raster_Err_Invalid; /* this version of the raster does not support direct rendering, sorry */ if ( params->flags & ft_raster_flag_direct ) return Raster_Err_Unsupported; ras.outline = *outline; ras.target = *target_map; return ( params->flags & ft_raster_flag_aa ? Render_Gray_Glyph( raster ) : Render_Glyph( raster ) ); } FT_Raster_Funcs ft_default_raster = { ft_glyph_format_outline, (FT_Raster_New_Func) ft_black_new, (FT_Raster_Reset_Func) ft_black_reset, (FT_Raster_Set_Mode_Func) ft_black_set_mode, (FT_Raster_Render_Func) ft_black_render, (FT_Raster_Done_Func) ft_black_done }; /* END */