/**************************************************************************** * * ftraster.c * * The FreeType glyph rasterizer (body). * * Copyright (C) 1996-2023 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 file can be compiled without the rest of the FreeType engine, by * defining the STANDALONE_ macro when compiling it. You also need to * put the files `ftimage.h' and `ftmisc.h' into the $(incdir) * directory. Typically, you should do something like * * - copy `src/raster/ftraster.c' (this file) to your current directory * * - copy `include/freetype/ftimage.h' and `src/raster/ftmisc.h' to your * current directory * * - compile `ftraster' with the STANDALONE_ macro defined, as in * * cc -c -DSTANDALONE_ ftraster.c * * The renderer can be initialized with a call to * `ft_standard_raster.raster_new'; a bitmap can be generated * with a call to `ft_standard_raster.raster_render'. * * See the comments and documentation in the file `ftimage.h' for more * details on how the raster works. * */ /************************************************************************** * * This is a rewrite of the FreeType 1.x scan-line converter * */ #ifdef STANDALONE_ /* The size in bytes of the render pool used by the scan-line converter */ /* to do all of its work. */ #define FT_RENDER_POOL_SIZE 16384L #define FT_CONFIG_STANDARD_LIBRARY_H #include /* for memset */ #include "ftmisc.h" #include "ftimage.h" #else /* !STANDALONE_ */ #include "ftraster.h" #include /* for FT_MulDiv and FT_MulDiv_No_Round */ #include /* for FT_Outline_Get_CBox */ #endif /* !STANDALONE_ */ /************************************************************************** * * A simple technical note on how the raster works * ----------------------------------------------- * * Converting an outline into a bitmap is achieved in several steps: * * 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 whether it was built going `up' or * `down', as this is very important for filling rules * * o its drop-out mode * * 2 - Sweeping the target map's scanlines in order to compute segment * `spans' which are then filled. Additionally, 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, for all generated profiles, the function End_Profile() * is used to record their 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 by bisecting the job and restarting. * */ /*************************************************************************/ /*************************************************************************/ /** **/ /** CONFIGURATION MACROS **/ /** **/ /*************************************************************************/ /*************************************************************************/ /*************************************************************************/ /*************************************************************************/ /** **/ /** OTHER MACROS (do not change) **/ /** **/ /*************************************************************************/ /*************************************************************************/ /************************************************************************** * * The macro FT_COMPONENT is used in trace mode. It is an implicit * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log * messages during execution. */ #undef FT_COMPONENT #define FT_COMPONENT raster #ifdef STANDALONE_ /* Auxiliary macros for token concatenation. */ #define FT_ERR_XCAT( x, y ) x ## y #define FT_ERR_CAT( x, y ) FT_ERR_XCAT( x, y ) /* This macro is used to indicate that a function parameter is unused. */ /* Its purpose is simply to reduce compiler warnings. Note also that */ /* simply defining it as `(void)x' doesn't avoid warnings with certain */ /* ANSI compilers (e.g. LCC). */ #define FT_UNUSED( x ) (x) = (x) /* Disable the tracing mechanism for simplicity -- developers can */ /* activate it easily by redefining these macros. */ #ifndef FT_ERROR #define FT_ERROR( x ) do { } while ( 0 ) /* nothing */ #endif #ifndef FT_TRACE #define FT_TRACE( x ) do { } while ( 0 ) /* nothing */ #define FT_TRACE1( x ) do { } while ( 0 ) /* nothing */ #define FT_TRACE6( x ) do { } while ( 0 ) /* nothing */ #define FT_TRACE7( x ) do { } while ( 0 ) /* nothing */ #endif #ifndef FT_THROW #define FT_THROW( e ) FT_ERR_CAT( Raster_Err_, e ) #endif #define Raster_Err_Ok 0 #define Raster_Err_Invalid_Outline -1 #define Raster_Err_Cannot_Render_Glyph -2 #define Raster_Err_Invalid_Argument -3 #define Raster_Err_Raster_Overflow -4 #define Raster_Err_Raster_Uninitialized -5 #define Raster_Err_Raster_Negative_Height -6 #define ft_memset memset #define FT_DEFINE_RASTER_FUNCS( class_, glyph_format_, raster_new_, \ raster_reset_, raster_set_mode_, \ raster_render_, raster_done_ ) \ const FT_Raster_Funcs class_ = \ { \ glyph_format_, \ raster_new_, \ raster_reset_, \ raster_set_mode_, \ raster_render_, \ raster_done_ \ }; #else /* !STANDALONE_ */ #include #include /* for FT_TRACE, FT_ERROR, and FT_THROW */ #include "rasterrs.h" #endif /* !STANDALONE_ */ #ifndef FT_MEM_SET #define FT_MEM_SET( d, s, c ) ft_memset( d, s, c ) #endif #ifndef FT_MEM_ZERO #define FT_MEM_ZERO( dest, count ) FT_MEM_SET( dest, 0, count ) #endif #ifndef FT_ZERO #define FT_ZERO( p ) FT_MEM_ZERO( p, sizeof ( *(p) ) ) #endif /* 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 into */ /* 32 bits. */ #define FMulDiv( a, b, c ) ( (a) * (b) / (c) ) /* On the other hand, SMulDiv means `Slow MulDiv', and is used typically */ /* for clipping computations. It simply uses the FT_MulDiv() function */ /* defined in `ftcalc.h'. */ #define SMulDiv FT_MulDiv #define SMulDiv_No_Round FT_MulDiv_No_Round /* 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; /* values for the `flags' bit field */ #define Flow_Up 0x08U #define Overshoot_Top 0x10U #define Overshoot_Bottom 0x20U /* States of each line, arc, and profile */ typedef enum TStates_ { Unknown_State, Ascending_State, Descending_State, Flat_State } TStates; typedef struct TProfile_ TProfile; typedef TProfile* PProfile; struct TProfile_ { PProfile link; /* link to next profile (various purposes) */ PProfile next; /* next profile in same contour, used */ /* during drop-out control */ Int offset; /* bottom or currently scanned array index */ Int height; /* profile's height in scanlines */ Int start; /* profile's starting scanline */ UShort flags; /* Bit 0-2: drop-out mode */ /* Bit 3: profile orientation (up/down) */ /* Bit 4: is top profile? */ /* Bit 5: is bottom profile? */ Int countL; /* number of lines to step before this */ /* profile becomes drawable */ FT_F26Dot6 X; /* current coordinate during sweep */ Long x[1]; /* actually variable array of scanline */ /* intersections with `height` elements */ }; typedef PProfile TProfileList; typedef PProfile* PProfileList; #undef RAS_ARG #undef RAS_ARGS #undef RAS_VAR #undef RAS_VARS #ifdef FT_STATIC_RASTER #define RAS_ARGS /* void */ #define RAS_ARG void #define RAS_VARS /* void */ #define RAS_VAR /* void */ #define FT_UNUSED_RASTER do { } while ( 0 ) #else /* !FT_STATIC_RASTER */ #define RAS_ARGS black_PWorker worker, #define RAS_ARG black_PWorker worker #define RAS_VARS worker, #define RAS_VAR worker #define FT_UNUSED_RASTER FT_UNUSED( worker ) #endif /* !FT_STATIC_RASTER */ typedef struct black_TWorker_ black_TWorker, *black_PWorker; /* prototypes used for sweep function dispatch */ typedef void Function_Sweep_Init( RAS_ARGS Int min, Int max ); typedef void Function_Sweep_Span( RAS_ARGS Int 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 */ #undef FLOOR #undef CEILING #undef TRUNC #undef SCALED #define FLOOR( x ) ( (x) & -ras.precision ) #define CEILING( x ) ( ( (x) + ras.precision - 1 ) & -ras.precision ) #define TRUNC( x ) ( (Long)(x) >> ras.precision_bits ) #define FRAC( x ) ( (x) & ( ras.precision - 1 ) ) /* scale and shift grid to pixel centers */ #define SCALED( x ) ( (x) * ras.precision_scale - ras.precision_half ) #define IS_BOTTOM_OVERSHOOT( x ) \ (Bool)( CEILING( x ) - x >= ras.precision_half ) #define IS_TOP_OVERSHOOT( x ) \ (Bool)( x - FLOOR( x ) >= ras.precision_half ) /* Smart dropout rounding to find which pixel is closer to span ends. */ /* To mimic Windows, symmetric cases do not depend on the precision. */ #define SMART( p, q ) FLOOR( ( (p) + (q) + ras.precision * 63 / 64 ) >> 1 ) #if FT_RENDER_POOL_SIZE > 2048 #define FT_MAX_BLACK_POOL ( FT_RENDER_POOL_SIZE / sizeof ( Long ) ) #else #define FT_MAX_BLACK_POOL ( 2048 / sizeof ( Long ) ) #endif /* The most used variables are positioned at the top of the structure. */ /* Thus, their offset can be coded with less opcodes, resulting in a */ /* smaller executable. */ struct black_TWorker_ { Int precision_bits; /* precision related variables */ Int precision; Int precision_half; Int precision_scale; Int precision_step; 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 */ Byte dropOutControl; /* current drop_out control method */ UShort bWidth; /* target bitmap width */ PByte bOrigin; /* target bitmap bottom-left origin */ PByte bLine; /* target bitmap current line */ Long lastX, lastY; Long 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; /* 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; }; typedef struct black_TRaster_ { void* memory; } black_TRaster, *black_PRaster; #ifdef FT_STATIC_RASTER static black_TWorker ras; #else /* !FT_STATIC_RASTER */ #define ras (*worker) #endif /* !FT_STATIC_RASTER */ /*************************************************************************/ /*************************************************************************/ /** **/ /** PROFILES COMPUTATION **/ /** **/ /*************************************************************************/ /*************************************************************************/ /************************************************************************** * * @Function: * Set_High_Precision * * @Description: * Set precision variables according to param flag. * * @Input: * High :: * Set to True for high precision (typically for ppem < 24), * false otherwise. */ static void Set_High_Precision( RAS_ARGS Int High ) { /* * `precision_step' is used in `Bezier_Up' to decide when to split a * given y-monotonous Bezier arc that crosses a scanline before * approximating it as a straight segment. The default value of 32 (for * low accuracy) corresponds to * * 32 / 64 == 0.5 pixels, * * while for the high accuracy case we have * * 256 / (1 << 12) = 0.0625 pixels. * */ if ( High ) { ras.precision_bits = 12; ras.precision_step = 256; } else { ras.precision_bits = 6; ras.precision_step = 32; } ras.precision = 1 << ras.precision_bits; ras.precision_half = ras.precision >> 1; ras.precision_scale = ras.precision >> Pixel_Bits; } /************************************************************************** * * @Function: * Insert_Y_Turn * * @Description: * Insert a salient into the sorted list placed on top of the render * pool. * * @Input: * New y scanline position. * * @Return: * SUCCESS on success. FAILURE in case of overflow. */ static Bool Insert_Y_Turn( RAS_ARGS Int y ) { Int n = ras.numTurns; PLong y_turns = ras.maxBuff; /* look for first y value that is <= */ while ( n-- && y < y_turns[n] ) ; /* if it is <, simply insert it, ignore if == */ if ( n < 0 || y > y_turns[n] ) { ras.maxBuff--; if ( ras.maxBuff <= ras.top ) { ras.error = FT_THROW( Raster_Overflow ); return FAILURE; } do { Int y2 = (Int)y_turns[n]; y_turns[n] = y; y = y2; } while ( n-- >= 0 ); ras.numTurns++; } return SUCCESS; } /************************************************************************** * * @Function: * New_Profile * * @Description: * Create a new profile in the render pool. * * @Input: * aState :: * The state/orientation of the new profile. * * overshoot :: * Whether the profile's unrounded start position * differs by at least a half pixel. * * @Return: * SUCCESS on success. FAILURE in case of overflow or of incoherent * profile. */ static Bool New_Profile( RAS_ARGS TStates aState, Bool overshoot ) { if ( !ras.cProfile || ras.cProfile->height ) { ras.cProfile = (PProfile)ras.top; ras.top = ras.cProfile->x; if ( ras.top >= ras.maxBuff ) { FT_TRACE1(( "overflow in New_Profile\n" )); ras.error = FT_THROW( Raster_Overflow ); return FAILURE; } ras.cProfile->height = 0; } ras.cProfile->flags = ras.dropOutControl; switch ( aState ) { case Ascending_State: ras.cProfile->flags |= Flow_Up; if ( overshoot ) ras.cProfile->flags |= Overshoot_Bottom; FT_TRACE7(( " new ascending profile = %p\n", (void *)ras.cProfile )); break; case Descending_State: if ( overshoot ) ras.cProfile->flags |= Overshoot_Top; FT_TRACE7(( " new descending profile = %p\n", (void *)ras.cProfile )); break; default: FT_ERROR(( "New_Profile: invalid profile direction\n" )); ras.error = FT_THROW( Invalid_Outline ); return FAILURE; } ras.state = aState; ras.fresh = TRUE; ras.joint = FALSE; return SUCCESS; } /************************************************************************** * * @Function: * End_Profile * * @Description: * Finalize the current profile and record y-turns. * * @Input: * overshoot :: * Whether the profile's unrounded end position differs * by at least a half pixel. * * @Return: * SUCCESS on success. FAILURE in case of overflow or incoherency. */ static Bool End_Profile( RAS_ARGS Bool overshoot ) { PProfile p = ras.cProfile; Int h = (Int)( ras.top - p->x ); Int bottom, top; if ( h < 0 ) { FT_ERROR(( "End_Profile: negative height encountered\n" )); ras.error = FT_THROW( Raster_Negative_Height ); return FAILURE; } if ( h > 0 ) { FT_TRACE7(( " ending profile %p, start = %2d, height = %+3d\n", (void *)p, p->start, p->flags & Flow_Up ? h : -h )); if ( overshoot ) { if ( p->flags & Flow_Up ) p->flags |= Overshoot_Top; else p->flags |= Overshoot_Bottom; } p->height = h; if ( p->flags & Flow_Up ) { bottom = p->start; top = bottom + h; p->offset = 0; } else { top = p->start + 1; bottom = top - h; p->start = bottom; p->offset = h - 1; } if ( Insert_Y_Turn( RAS_VARS bottom ) || Insert_Y_Turn( RAS_VARS top ) ) return FAILURE; if ( !ras.gProfile ) ras.gProfile = p; /* preliminary values to be finalized */ p->next = ras.gProfile; p->link = (PProfile)ras.top; ras.num_Profs++; } ras.joint = FALSE; return SUCCESS; } /************************************************************************** * * @Function: * Finalize_Profile_Table * * @Description: * Adjust all links in the profiles list. */ static void Finalize_Profile_Table( RAS_ARG ) { UShort n = ras.num_Profs; PProfile p = ras.fProfile; PProfile q; /* there should be at least two profiles, up and down */ while ( --n ) { q = p->link; /* fix the contour loop */ if ( q->next == p->next ) p->next = q; p = q; } /* null-terminate */ p->link = NULL; } /************************************************************************** * * @Function: * Split_Conic * * @Description: * Subdivide one conic Bezier into two joint sub-arcs in the Bezier * stack. * * @Input: * None (subdivided Bezier is taken from the top of the stack). * * @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; a = base[0].x + base[1].x; b = base[1].x + base[2].x; base[3].x = b >> 1; base[2].x = ( a + b ) >> 2; base[1].x = a >> 1; base[4].y = base[2].y; a = base[0].y + base[1].y; b = base[1].y + base[2].y; base[3].y = b >> 1; base[2].y = ( a + b ) >> 2; base[1].y = a >> 1; /* hand optimized. gcc doesn't seem to be too good at common */ /* expression substitution and instruction scheduling ;-) */ } /************************************************************************** * * @Function: * Split_Cubic * * @Description: * Subdivide 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; base[6].x = base[3].x; a = base[0].x + base[1].x; b = base[1].x + base[2].x; c = base[2].x + base[3].x; base[5].x = c >> 1; c += b; base[4].x = c >> 2; base[1].x = a >> 1; a += b; base[2].x = a >> 2; base[3].x = ( a + c ) >> 3; base[6].y = base[3].y; a = base[0].y + base[1].y; b = base[1].y + base[2].y; c = base[2].y + base[3].y; base[5].y = c >> 1; c += b; base[4].y = c >> 2; base[1].y = a >> 1; a += b; base[2].y = a >> 2; base[3].y = ( a + c ) >> 3; } /************************************************************************** * * @Function: * Line_Up * * @Description: * Compute the x-coordinates of an ascending line segment and store * them in the render pool. * * @Input: * x1 :: * The x-coordinate of the segment's start point. * * y1 :: * The y-coordinate of the segment's start point. * * x2 :: * The x-coordinate of the segment's end point. * * y2 :: * The y-coordinate of the segment's end point. * * miny :: * A lower vertical clipping bound value. * * maxy :: * An upper vertical clipping bound value. * * @Return: * 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; 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 = (Int)TRUNC( miny ); f1 = 0; } else { e1 = (Int)TRUNC( y1 ); f1 = (Int)FRAC( y1 ); } if ( y2 > maxy ) { /* x2 += FMulDiv( Dx, maxy - y2, Dy ); UNNECESSARY */ e2 = (Int)TRUNC( maxy ); f2 = 0; } else { e2 = (Int)TRUNC( y2 ); f2 = (Int)FRAC( y2 ); } if ( f1 > 0 ) { if ( e1 == e2 ) return SUCCESS; else { x1 += SMulDiv( Dx, ras.precision - f1, Dy ); e1 += 1; } } else if ( ras.joint ) { ras.top--; ras.joint = FALSE; } ras.joint = (char)( f2 == 0 ); if ( ras.fresh ) { ras.cProfile->start = e1; ras.fresh = FALSE; } size = e2 - e1 + 1; if ( ras.top + size >= ras.maxBuff ) { ras.error = FT_THROW( Raster_Overflow ); return FAILURE; } if ( Dx > 0 ) { Ix = SMulDiv_No_Round( ras.precision, Dx, Dy ); Rx = ( ras.precision * Dx ) % Dy; Dx = 1; } else { Ix = -SMulDiv_No_Round( 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; } /************************************************************************** * * @Function: * Line_Down * * @Description: * Compute the x-coordinates of an descending line segment and store * them in the render pool. * * @Input: * x1 :: * The x-coordinate of the segment's start point. * * y1 :: * The y-coordinate of the segment's start point. * * x2 :: * The x-coordinate of the segment's end point. * * y2 :: * The y-coordinate of the segment's end point. * * miny :: * A lower vertical clipping bound value. * * maxy :: * An upper vertical clipping bound value. * * @Return: * SUCCESS on success, FAILURE on render pool overflow. */ 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; } /* A function type describing the functions used to split Bezier arcs */ typedef void (*TSplitter)( TPoint* base ); /************************************************************************** * * @Function: * Bezier_Up * * @Description: * Compute the x-coordinates of an ascending Bezier arc and store * them in the render pool. * * @Input: * degree :: * The degree of the Bezier arc (either 2 or 3). * * splitter :: * The function to split Bezier arcs. * * miny :: * A lower vertical clipping bound value. * * maxy :: * An upper vertical clipping bound value. * * @Return: * SUCCESS on success, FAILURE on render pool overflow. */ static Bool Bezier_Up( RAS_ARGS Int degree, TPoint* arc, TSplitter splitter, Long miny, Long maxy ) { Long y1, y2, e, e2, e0, dy; Long dx, x2; TPoint* start_arc; PLong top; 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 ); e0 = e; if ( FRAC( y1 ) == 0 ) { if ( ras.joint ) { top--; ras.joint = FALSE; } *top++ = arc[degree].x; e += ras.precision; } } if ( ras.fresh ) { ras.cProfile->start = (Int)TRUNC( e0 ); ras.fresh = FALSE; } if ( e2 < e ) goto Fin; if ( ( top + TRUNC( e2 - e ) + 1 ) >= ras.maxBuff ) { ras.top = top; ras.error = FT_THROW( Raster_Overflow ); return FAILURE; } start_arc = arc; do { ras.joint = FALSE; y2 = arc[0].y; x2 = arc[0].x; if ( y2 > e ) { dy = y2 - arc[degree].y; dx = x2 - arc[degree].x; /* split condition should be invariant of direction */ if ( dy > ras.precision_step || dx > ras.precision_step || -dx > ras.precision_step ) { splitter( arc ); arc += degree; } else { *top++ = x2 - FMulDiv( y2 - e, dx, dy ); arc -= degree; e += ras.precision; } } else { if ( y2 == e ) { ras.joint = TRUE; *top++ = x2; e += ras.precision; } arc -= degree; } } while ( arc >= start_arc && e <= e2 ); Fin: ras.top = top; return SUCCESS; } /************************************************************************** * * @Function: * Bezier_Down * * @Description: * Compute the x-coordinates of an descending Bezier arc and store * them in the render pool. * * @Input: * degree :: * The degree of the Bezier arc (either 2 or 3). * * splitter :: * The function to split Bezier arcs. * * miny :: * A lower vertical clipping bound value. * * maxy :: * An upper vertical clipping bound value. * * @Return: * SUCCESS on success, FAILURE on render pool overflow. */ static Bool Bezier_Down( RAS_ARGS Int degree, TPoint* arc, TSplitter splitter, Long miny, Long maxy ) { 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, arc, 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: * Inject a new line segment and adjust the Profiles list. * * @Input: * x :: * The x-coordinate of the segment's end point (its start point * is stored in `lastX'). * * y :: * The y-coordinate of the segment's end point (its start point * is stored in `lastY'). * * @Return: * 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_State: if ( y > ras.lastY ) { 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, IS_TOP_OVERSHOOT( ras.lastY ) ) ) return FAILURE; } break; case Ascending_State: if ( y < ras.lastY ) { if ( End_Profile( RAS_VARS IS_TOP_OVERSHOOT( ras.lastY ) ) || New_Profile( RAS_VARS Descending_State, IS_TOP_OVERSHOOT( ras.lastY ) ) ) return FAILURE; } break; case Descending_State: if ( y > ras.lastY ) { if ( End_Profile( RAS_VARS IS_BOTTOM_OVERSHOOT( ras.lastY ) ) || New_Profile( RAS_VARS Ascending_State, IS_BOTTOM_OVERSHOOT( ras.lastY ) ) ) return FAILURE; } break; default: ; } /* Then compute the lines */ switch ( ras.state ) { case Ascending_State: if ( Line_Up( RAS_VARS ras.lastX, ras.lastY, 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 ) ) return FAILURE; break; default: ; } ras.lastX = x; ras.lastY = y; return SUCCESS; } /************************************************************************** * * @Function: * Conic_To * * @Description: * Inject a new conic arc and adjust the profile list. * * @Input: * cx :: * The x-coordinate of the arc's new control point. * * cy :: * The y-coordinate of the arc's new control point. * * x :: * The x-coordinate of the arc's end point (its start point is * stored in `lastX'). * * y :: * The y-coordinate of the arc's end point (its start point is * stored in `lastY'). * * @Return: * SUCCESS on success, FAILURE on render pool overflow or incorrect * profile. */ static Bool Conic_To( RAS_ARGS Long cx, Long cy, Long x, Long y ) { Long y1, y2, y3, x3, ymin, ymax; TStates state_bez; TPoint arcs[2 * MaxBezier + 1]; /* The Bezier stack */ TPoint* arc; /* current Bezier arc pointer */ arc = arcs; arc[2].x = ras.lastX; arc[2].y = ras.lastY; arc[1].x = cx; arc[1].y = cy; arc[0].x = x; arc[0].y = y; do { y1 = arc[2].y; y2 = arc[1].y; y3 = arc[0].y; x3 = 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( arc ); arc += 2; } else if ( y1 == y3 ) { /* this arc is flat, ignore it and pop it from the Bezier stack */ arc -= 2; } else { /* the arc is y-monotonous, either ascending or descending */ /* detect a change of direction */ 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_VARS o ) ) goto Fail; /* create a new profile */ if ( New_Profile( RAS_VARS state_bez, o ) ) goto Fail; } /* now call the appropriate routine */ if ( state_bez == Ascending_State ) { if ( Bezier_Up( RAS_VARS 2, arc, Split_Conic, ras.minY, ras.maxY ) ) goto Fail; } else if ( Bezier_Down( RAS_VARS 2, arc, Split_Conic, ras.minY, ras.maxY ) ) goto Fail; arc -= 2; } } while ( arc >= arcs ); ras.lastX = x3; ras.lastY = y3; return SUCCESS; Fail: return FAILURE; } /************************************************************************** * * @Function: * Cubic_To * * @Description: * Inject a new cubic arc and adjust the profile list. * * @Input: * cx1 :: * The x-coordinate of the arc's first new control point. * * cy1 :: * The y-coordinate of the arc's first new control point. * * cx2 :: * The x-coordinate of the arc's second new control point. * * cy2 :: * The y-coordinate of the arc's second new control point. * * x :: * The x-coordinate of the arc's end point (its start point is * stored in `lastX'). * * y :: * The y-coordinate of the arc's end point (its start point is * stored in `lastY'). * * @Return: * SUCCESS on success, FAILURE on render pool overflow or incorrect * profile. */ 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; TPoint arcs[3 * MaxBezier + 1]; /* The Bezier stack */ TPoint* arc; /* current Bezier arc pointer */ arc = arcs; arc[3].x = ras.lastX; arc[3].y = ras.lastY; arc[2].x = cx1; arc[2].y = cy1; arc[1].x = cx2; arc[1].y = cy2; arc[0].x = x; arc[0].y = y; do { y1 = arc[3].y; y2 = arc[2].y; y3 = arc[1].y; y4 = arc[0].y; x4 = 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( arc ); arc += 3; } else if ( y1 == y4 ) { /* this arc is flat, ignore it and pop it from the Bezier stack */ arc -= 3; } else { state_bez = ( y1 <= y4 ) ? Ascending_State : Descending_State; /* 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_VARS o ) ) goto Fail; if ( New_Profile( RAS_VARS state_bez, o ) ) goto Fail; } /* compute intersections */ if ( state_bez == Ascending_State ) { if ( Bezier_Up( RAS_VARS 3, arc, Split_Cubic, ras.minY, ras.maxY ) ) goto Fail; } else if ( Bezier_Down( RAS_VARS 3, arc, Split_Cubic, ras.minY, ras.maxY ) ) goto Fail; arc -= 3; } } while ( arc >= arcs ); ras.lastX = x4; ras.lastY = y4; return SUCCESS; Fail: return FAILURE; } #undef SWAP_ #define SWAP_( x, y ) do \ { \ Long swap = x; \ \ \ x = y; \ y = swap; \ } while ( 0 ) /************************************************************************** * * @Function: * Decompose_Curve * * @Description: * Scan the outline arrays 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 :: * The index of the first point in the contour. * * last :: * The index of the last point in the contour. * * flipped :: * If set, flip the direction of the curve. * * @Return: * SUCCESS on success, FAILURE on error. */ static Bool Decompose_Curve( RAS_ARGS Int first, Int 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; UInt 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; /* set scan mode if necessary */ if ( tags[0] & FT_CURVE_TAG_HAS_SCANMODE ) ras.dropOutControl = (Byte)tags[0] >> 5; 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 ); if ( flipped ) { SWAP_( x1, y1 ); SWAP_( x2, y2 ); } if ( point <= limit ) { x3 = SCALED( point[0].x ); y3 = SCALED( point[0].y ); if ( flipped ) SWAP_( x3, y3 ); 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 = FT_THROW( Invalid_Outline ); Fail: return FAILURE; } /************************************************************************** * * @Function: * Convert_Glyph * * @Description: * Convert a glyph into a series of segments and arcs and make a * profiles list with them. * * @Input: * flipped :: * If set, flip the direction of curve. * * @Return: * SUCCESS on success, FAILURE if any error was encountered during * rendering. */ static Bool Convert_Glyph( RAS_ARGS Int flipped ) { Int i; Int first, last; ras.fProfile = NULL; ras.cProfile = NULL; ras.joint = FALSE; ras.fresh = FALSE; ras.top = ras.buff; ras.maxBuff = ras.sizeBuff; ras.numTurns = 0; ras.num_Profs = 0; last = -1; for ( i = 0; i < ras.outline.n_contours; i++ ) { Bool o; ras.state = Unknown_State; ras.gProfile = NULL; first = last + 1; last = ras.outline.contours[i]; if ( Decompose_Curve( RAS_VARS first, last, flipped ) ) return FAILURE; /* Note that ras.gProfile can stay nil if the contour was */ /* too small to be drawn or degenerate. */ if ( !ras.gProfile ) continue; /* we must now check whether the extreme arcs join or not */ if ( FRAC( ras.lastY ) == 0 && ras.lastY >= ras.minY && ras.lastY <= ras.maxY ) if ( ( ras.gProfile->flags & Flow_Up ) == ( ras.cProfile->flags & Flow_Up ) ) ras.top--; o = ras.cProfile->flags & Flow_Up ? IS_TOP_OVERSHOOT( ras.lastY ) : IS_BOTTOM_OVERSHOOT( ras.lastY ); if ( End_Profile( RAS_VARS o ) ) return FAILURE; if ( !ras.fProfile ) ras.fProfile = ras.gProfile; } if ( ras.fProfile ) Finalize_Profile_Table( RAS_VAR ); return SUCCESS; } /*************************************************************************/ /*************************************************************************/ /** **/ /** SCAN-LINE SWEEPS AND DRAWING **/ /** **/ /*************************************************************************/ /*************************************************************************/ /************************************************************************** * * 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; } /************************************************************************** * * Increment * * Advances all profile in the list to the next scanline. It also * sorts the trace list in the unlikely case of profile crossing. * 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 Increment( PProfileList list ) { PProfile *old, current, next; /* First, set the new X coordinates and remove exhausted profiles */ old = list; while ( *old ) { current = *old; if ( --current->height ) { current->offset += ( current->flags & Flow_Up ) ? 1 : -1; current->X = current->x[current->offset]; old = ¤t->link; } else *old = current->link; /* remove */ } /* 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 four routines are used during the vertical black/white sweep * phase by the generic Draw_Sweep() function. * */ static void Vertical_Sweep_Init( RAS_ARGS Int min, Int max ) { FT_UNUSED( max ); ras.bLine = ras.bOrigin - min * ras.target.pitch; } static void Vertical_Sweep_Span( RAS_ARGS Int y, FT_F26Dot6 x1, FT_F26Dot6 x2, PProfile left, PProfile right ) { Int e1, e2; FT_UNUSED( y ); FT_UNUSED( left ); FT_UNUSED( right ); FT_TRACE7(( " y=%d x=[% .*f;% .*f]", y, ras.precision_bits, (double)x1 / (double)ras.precision, ras.precision_bits, (double)x2 / (double)ras.precision )); e1 = (Int)TRUNC( CEILING( x1 ) ); e2 = (Int)TRUNC( FLOOR( x2 ) ); if ( e2 >= 0 && e1 < ras.bWidth ) { Byte* target; Int c1, f1, c2, f2; if ( e1 < 0 ) e1 = 0; if ( e2 >= ras.bWidth ) e2 = ras.bWidth - 1; FT_TRACE7(( " -> x=[%d;%d]", e1, e2 )); c1 = e1 >> 3; c2 = e2 >> 3; f1 = 0xFF >> ( e1 & 7 ); f2 = ~0x7F >> ( e2 & 7 ); target = ras.bLine + 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. */ while ( --c2 > 0 ) *( ++target ) = 0xFF; target[1] |= f2; } else *target |= ( f1 & f2 ); } FT_TRACE7(( "\n" )); } static void Vertical_Sweep_Drop( RAS_ARGS Int y, FT_F26Dot6 x1, FT_F26Dot6 x2, PProfile left, PProfile right ) { Long e1, e2, pxl; Int c1, f1; FT_TRACE7(( " y=%d x=[% .*f;% .*f]", y, ras.precision_bits, (double)x1 / (double)ras.precision, ras.precision_bits, (double)x2 / (double)ras.precision )); /* Drop-out control */ /* e2 x2 x1 e1 */ /* */ /* ^ | */ /* | | */ /* +-------------+---------------------+------------+ */ /* | | */ /* | v */ /* */ /* pixel contour contour pixel */ /* center center */ /* drop-out mode scan conversion rules (as defined in OpenType) */ /* --------------------------------------------------------------- */ /* 0 1, 2, 3 */ /* 1 1, 2, 4 */ /* 2 1, 2 */ /* 3 same as mode 2 */ /* 4 1, 2, 5 */ /* 5 1, 2, 6 */ /* 6, 7 same as mode 2 */ e1 = CEILING( x1 ); e2 = FLOOR ( x2 ); pxl = e1; if ( e1 > e2 ) { Int dropOutControl = left->flags & 7; if ( e1 == e2 + ras.precision ) { switch ( dropOutControl ) { case 0: /* simple drop-outs including stubs */ pxl = e2; break; case 4: /* smart drop-outs including stubs */ pxl = SMART( x1, x2 ); break; case 1: /* simple drop-outs excluding stubs */ case 5: /* smart drop-outs excluding stubs */ /* Drop-out Control Rules #4 and #6 */ /* The specification neither provides an exact definition */ /* of a `stub' nor gives exact rules to exclude them. */ /* */ /* Here the constraints we use to recognize a stub. */ /* */ /* 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 */ /* */ /* 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 */ /* upper stub test */ if ( left->next == right && left->height <= 0 && !( left->flags & Overshoot_Top && x2 - x1 >= ras.precision_half ) ) goto Exit; /* lower stub test */ if ( right->next == left && left->start == y && !( left->flags & Overshoot_Bottom && x2 - x1 >= ras.precision_half ) ) goto Exit; if ( dropOutControl == 1 ) pxl = e2; else pxl = SMART( x1, x2 ); break; default: /* modes 2, 3, 6, 7 */ goto Exit; /* no drop-out control */ } /* undocumented but confirmed: If the drop-out would result in a */ /* pixel outside of the bounding box, use the pixel inside of the */ /* bounding box instead */ if ( pxl < 0 ) pxl = e1; else if ( TRUNC( pxl ) >= ras.bWidth ) pxl = e2; /* check that the other pixel isn't set */ e1 = ( pxl == e1 ) ? e2 : e1; e1 = TRUNC( e1 ); c1 = (Int)( e1 >> 3 ); f1 = (Int)( e1 & 7 ); if ( e1 >= 0 && e1 < ras.bWidth && ras.bLine[c1] & ( 0x80 >> f1 ) ) goto Exit; } else goto Exit; } e1 = TRUNC( pxl ); if ( e1 >= 0 && e1 < ras.bWidth ) { FT_TRACE7(( " -> x=%ld", e1 )); c1 = (Int)( e1 >> 3 ); f1 = (Int)( e1 & 7 ); ras.bLine[c1] |= 0x80 >> f1; } Exit: FT_TRACE7(( " dropout=%d\n", left->flags & 7 )); } static void Vertical_Sweep_Step( RAS_ARG ) { ras.bLine -= ras.target.pitch; } /************************************************************************ * * Horizontal Sweep Procedure Set * * These four routines are used during the horizontal black/white * sweep phase by the generic Draw_Sweep() function. * */ static void Horizontal_Sweep_Init( RAS_ARGS Int min, Int max ) { /* nothing, really */ FT_UNUSED_RASTER; FT_UNUSED( min ); FT_UNUSED( max ); } static void Horizontal_Sweep_Span( RAS_ARGS Int y, FT_F26Dot6 x1, FT_F26Dot6 x2, PProfile left, PProfile right ) { Long e1, e2; FT_UNUSED( left ); FT_UNUSED( right ); FT_TRACE7(( " x=%d y=[% .*f;% .*f]", y, ras.precision_bits, (double)x1 / (double)ras.precision, ras.precision_bits, (double)x2 / (double)ras.precision )); /* We should not need this procedure but the vertical sweep */ /* mishandles horizontal lines through pixel centers. So we */ /* have to check perfectly aligned span edges here. */ /* */ /* XXX: Can we handle horizontal lines better and drop this? */ e1 = CEILING( x1 ); if ( x1 == e1 ) { e1 = TRUNC( e1 ); if ( e1 >= 0 && (ULong)e1 < ras.target.rows ) { Int f1; PByte bits; bits = ras.bOrigin + ( y >> 3 ) - e1 * ras.target.pitch; f1 = 0x80 >> ( y & 7 ); FT_TRACE7(( bits[0] & f1 ? " redundant" : " -> y=%ld edge", e1 )); bits[0] |= f1; } } e2 = FLOOR ( x2 ); if ( x2 == e2 ) { e2 = TRUNC( e2 ); if ( e2 >= 0 && (ULong)e2 < ras.target.rows ) { Int f1; PByte bits; bits = ras.bOrigin + ( y >> 3 ) - e2 * ras.target.pitch; f1 = 0x80 >> ( y & 7 ); FT_TRACE7(( bits[0] & f1 ? " redundant" : " -> y=%ld edge", e2 )); bits[0] |= f1; } } FT_TRACE7(( "\n" )); } static void Horizontal_Sweep_Drop( RAS_ARGS Int y, FT_F26Dot6 x1, FT_F26Dot6 x2, PProfile left, PProfile right ) { Long e1, e2, pxl; PByte bits; Int f1; FT_TRACE7(( " x=%d y=[% .*f;% .*f]", y, ras.precision_bits, (double)x1 / (double)ras.precision, ras.precision_bits, (double)x2 / (double)ras.precision )); /* During the horizontal sweep, we only take care of drop-outs */ /* e1 + <-- pixel center */ /* | */ /* x1 ---+--> <-- contour */ /* | */ /* | */ /* x2 <--+--- <-- contour */ /* | */ /* | */ /* e2 + <-- pixel center */ e1 = CEILING( x1 ); e2 = FLOOR ( x2 ); pxl = e1; if ( e1 > e2 ) { Int dropOutControl = left->flags & 7; if ( e1 == e2 + ras.precision ) { switch ( dropOutControl ) { case 0: /* simple drop-outs including stubs */ pxl = e2; break; case 4: /* smart drop-outs including stubs */ pxl = SMART( x1, x2 ); break; case 1: /* simple drop-outs excluding stubs */ case 5: /* smart drop-outs excluding stubs */ /* see Vertical_Sweep_Drop for details */ /* rightmost stub test */ if ( left->next == right && left->height <= 0 && !( left->flags & Overshoot_Top && x2 - x1 >= ras.precision_half ) ) goto Exit; /* leftmost stub test */ if ( right->next == left && left->start == y && !( left->flags & Overshoot_Bottom && x2 - x1 >= ras.precision_half ) ) goto Exit; if ( dropOutControl == 1 ) pxl = e2; else pxl = SMART( x1, x2 ); break; default: /* modes 2, 3, 6, 7 */ goto Exit; /* no drop-out control */ } /* undocumented but confirmed: If the drop-out would result in a */ /* pixel outside of the bounding box, use the pixel inside of the */ /* bounding box instead */ if ( pxl < 0 ) pxl = e1; else if ( (ULong)( TRUNC( pxl ) ) >= ras.target.rows ) pxl = e2; /* check that the other pixel isn't set */ e1 = ( pxl == e1 ) ? e2 : e1; e1 = TRUNC( e1 ); bits = ras.bOrigin + ( y >> 3 ) - e1 * ras.target.pitch; f1 = 0x80 >> ( y & 7 ); if ( e1 >= 0 && (ULong)e1 < ras.target.rows && *bits & f1 ) goto Exit; } else goto Exit; } e1 = TRUNC( pxl ); if ( e1 >= 0 && (ULong)e1 < ras.target.rows ) { FT_TRACE7(( " -> y=%ld", e1 )); bits = ras.bOrigin + ( y >> 3 ) - e1 * ras.target.pitch; f1 = 0x80 >> ( y & 7 ); bits[0] |= f1; } Exit: FT_TRACE7(( " dropout=%d\n", left->flags & 7 )); } static void Horizontal_Sweep_Step( RAS_ARG ) { /* Nothing, really */ FT_UNUSED_RASTER; } /************************************************************************** * * Generic Sweep Drawing routine * * Note that this routine is executed with the pool containing at least * two valid profiles (up and down) and two y-turns (top and bottom). * */ static Bool Draw_Sweep( RAS_ARG ) { Int min_Y, max_Y, dropouts; Int y, y_change, y_height; PProfile *Q, P, P_Left, P_Right; TProfileList waiting = ras.fProfile; TProfileList draw_left = NULL; TProfileList draw_right = NULL; /* use y_turns to set the drawing range */ min_Y = (Int)ras.maxBuff[0]; max_Y = (Int)ras.sizeBuff[-1] - 1; /* now initialize the sweep */ ras.Proc_Sweep_Init( RAS_VARS min_Y, max_Y ); /* set the activation countdowns and the initial positions */ P = waiting; while ( P ) { P->countL = P->start - min_Y; P->X = P->x[P->offset]; P = P->link; } /* let's go, iterating through y_turns */ y = min_Y; y_height = 0; while ( ++ras.maxBuff < ras.sizeBuff ) { /* check waiting list for new profile activations */ Q = &waiting; while ( *Q ) { P = *Q; P->countL -= y_height; if ( P->countL == 0 ) { *Q = P->link; /* remove */ if ( P->flags & Flow_Up ) InsNew( &draw_left, P ); else InsNew( &draw_right, P ); } else Q = &P->link; } y_change = (Int)*ras.maxBuff; y_height = y_change - y; do { /* let's trace */ dropouts = 0; P_Left = draw_left; P_Right = draw_right; while ( P_Left && P_Right ) { Long x1 = P_Left ->X; Long x2 = P_Right->X; Long xs; if ( x1 > x2 ) { xs = x1; x1 = x2; x2 = xs; } /* if bottom ceiling exceeds top floor, it is a drop-out */ if ( CEILING( x1 ) > FLOOR( x2 ) ) { Int dropOutControl = P_Left->flags & 7; if ( dropOutControl != 2 ) { P_Left ->X = x1; P_Right->X = x2; /* mark profile for drop-out processing */ P_Left->countL = 1; dropouts++; } } else ras.Proc_Sweep_Span( RAS_VARS y, x1, x2, P_Left, P_Right ); P_Left = P_Left->link; P_Right = P_Right->link; } /* handle drop-outs _after_ the span drawing -- */ /* drop-out 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 ); Increment( &draw_left ); Increment( &draw_right ); } while ( ++y < y_change ); } return SUCCESS; Scan_DropOuts: P_Left = draw_left; P_Right = draw_right; while ( P_Left && P_Right ) { if ( P_Left->countL ) { P_Left->countL = 0; #if 0 dropouts--; /* -- this is useful when debugging only */ #endif 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; } #ifdef STANDALONE_ /************************************************************************** * * The following functions should only compile in stand-alone mode, * i.e., when building this component without the rest of FreeType. * */ /************************************************************************** * * @Function: * FT_Outline_Get_CBox * * @Description: * Return an outline's `control box'. The control box encloses all * the outline's points, including Bézier control points. Though it * coincides with the exact bounding box for most glyphs, it can be * slightly larger in some situations (like when rotating an outline * that contains Bézier outside arcs). * * Computing the control box is very fast, while getting the bounding * box can take much more time as it needs to walk over all segments * and arcs in the outline. To get the latter, you can use the * `ftbbox' component, which is dedicated to this single task. * * @Input: * outline :: * A pointer to the source outline descriptor. * * @Output: * acbox :: * The outline's control box. * * @Note: * See @FT_Glyph_Get_CBox for a discussion of tricky fonts. */ static void FT_Outline_Get_CBox( const FT_Outline* outline, FT_BBox *acbox ) { if ( outline && acbox ) { Long xMin, yMin, xMax, yMax; if ( outline->n_points == 0 ) { xMin = 0; yMin = 0; xMax = 0; yMax = 0; } else { FT_Vector* vec = outline->points; FT_Vector* limit = vec + outline->n_points; xMin = xMax = vec->x; yMin = yMax = vec->y; vec++; for ( ; vec < limit; vec++ ) { Long x, y; x = vec->x; if ( x < xMin ) xMin = x; if ( x > xMax ) xMax = x; y = vec->y; if ( y < yMin ) yMin = y; if ( y > yMax ) yMax = y; } } acbox->xMin = xMin; acbox->xMax = xMax; acbox->yMin = yMin; acbox->yMax = yMax; } } #endif /* STANDALONE_ */ /************************************************************************** * * @Function: * Render_Single_Pass * * @Description: * Perform one sweep with sub-banding. * * @Input: * flipped :: * If set, flip the direction of the outline. * * @Return: * Renderer error code. */ static int Render_Single_Pass( RAS_ARGS Bool flipped, Int y_min, Int y_max ) { Int y_mid; Int band_top = 0; Int band_stack[32]; /* enough to bisect 32-bit int bands */ FT_TRACE6(( "%s pass [%d..%d]\n", flipped ? "Horizontal" : "Vertical", y_min, y_max )); while ( 1 ) { ras.minY = (Long)y_min * ras.precision; ras.maxY = (Long)y_max * ras.precision; ras.error = Raster_Err_Ok; if ( Convert_Glyph( RAS_VARS flipped ) ) { if ( ras.error != Raster_Err_Raster_Overflow ) return ras.error; /* sub-banding */ if ( y_min == y_max ) return ras.error; /* still Raster_Overflow */ FT_TRACE6(( "band [%d..%d]: to be bisected\n", y_min, y_max )); y_mid = ( y_min + y_max ) >> 1; band_stack[band_top++] = y_min; y_min = y_mid + 1; } else { FT_TRACE6(( "band [%d..%d]: %hd profiles; %td bytes remaining\n", y_min, y_max, ras.num_Profs, (char*)ras.maxBuff - (char*)ras.top )); if ( ras.fProfile ) if ( Draw_Sweep( RAS_VAR ) ) return ras.error; if ( --band_top < 0 ) break; y_max = y_min - 1; y_min = band_stack[band_top]; } } return Raster_Err_Ok; } /************************************************************************** * * @Function: * Render_Glyph * * @Description: * Render a glyph in a bitmap. Sub-banding if needed. * * @Return: * FreeType error code. 0 means success. */ static FT_Error Render_Glyph( RAS_ARG ) { FT_Error error; Set_High_Precision( RAS_VARS ras.outline.flags & FT_OUTLINE_HIGH_PRECISION ); if ( ras.outline.flags & FT_OUTLINE_IGNORE_DROPOUTS ) ras.dropOutControl = 2; else { if ( ras.outline.flags & FT_OUTLINE_SMART_DROPOUTS ) ras.dropOutControl = 4; else ras.dropOutControl = 0; if ( !( ras.outline.flags & FT_OUTLINE_INCLUDE_STUBS ) ) ras.dropOutControl += 1; } FT_TRACE6(( "BW Raster: precision 1/%d, dropout mode %d\n", ras.precision, ras.dropOutControl )); /* 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.bWidth = (UShort)ras.target.width; ras.bOrigin = (Byte*)ras.target.buffer; if ( ras.target.pitch > 0 ) ras.bOrigin += (Long)( ras.target.rows - 1 ) * ras.target.pitch; error = Render_Single_Pass( RAS_VARS 0, 0, (Int)ras.target.rows - 1 ); if ( error ) return error; /* Horizontal Sweep */ if ( !( ras.outline.flags & FT_OUTLINE_SINGLE_PASS ) ) { 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; error = Render_Single_Pass( RAS_VARS 1, 0, (Int)ras.target.width - 1 ); if ( error ) return error; } return Raster_Err_Ok; } /**** 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 black_TRaster the_raster; FT_UNUSED( memory ); *araster = (FT_Raster)&the_raster; FT_ZERO( &the_raster ); return 0; } static void ft_black_done( FT_Raster raster ) { /* nothing */ FT_UNUSED( raster ); } #else /* !STANDALONE_ */ static int ft_black_new( void* memory_, /* FT_Memory */ FT_Raster *araster_ ) /* black_PRaster */ { FT_Memory memory = (FT_Memory)memory_; black_PRaster *araster = (black_PRaster*)araster_; FT_Error error; black_PRaster raster = NULL; if ( !FT_NEW( raster ) ) raster->memory = memory; *araster = raster; return error; } static void ft_black_done( FT_Raster raster_ ) /* black_PRaster */ { black_PRaster raster = (black_PRaster)raster_; FT_Memory memory = (FT_Memory)raster->memory; FT_FREE( raster ); } #endif /* !STANDALONE_ */ static void ft_black_reset( FT_Raster raster, PByte pool_base, ULong pool_size ) { FT_UNUSED( raster ); FT_UNUSED( pool_base ); FT_UNUSED( pool_size ); } static int ft_black_set_mode( FT_Raster raster, ULong mode, void* args ) { FT_UNUSED( raster ); FT_UNUSED( mode ); FT_UNUSED( args ); return 0; } static int ft_black_render( FT_Raster raster, const FT_Raster_Params* params ) { const FT_Outline* outline = (const FT_Outline*)params->source; const FT_Bitmap* target_map = params->target; #ifndef FT_STATIC_RASTER black_TWorker worker[1]; #endif Long buffer[FT_MAX_BLACK_POOL]; if ( !raster ) return FT_THROW( Raster_Uninitialized ); if ( !outline ) return FT_THROW( Invalid_Outline ); /* return immediately if the outline is empty */ if ( outline->n_points == 0 || outline->n_contours <= 0 ) return Raster_Err_Ok; if ( !outline->contours || !outline->points ) return FT_THROW( Invalid_Outline ); if ( outline->n_points != outline->contours[outline->n_contours - 1] + 1 ) return FT_THROW( Invalid_Outline ); /* this version of the raster does not support direct rendering, sorry */ if ( params->flags & FT_RASTER_FLAG_DIRECT || params->flags & FT_RASTER_FLAG_AA ) return FT_THROW( Cannot_Render_Glyph ); if ( !target_map ) return FT_THROW( Invalid_Argument ); /* nothing to do */ if ( !target_map->width || !target_map->rows ) return Raster_Err_Ok; if ( !target_map->buffer ) return FT_THROW( Invalid_Argument ); ras.outline = *outline; ras.target = *target_map; ras.buff = buffer; ras.sizeBuff = (&buffer)[1]; /* Points to right after buffer. */ return Render_Glyph( RAS_VAR ); } FT_DEFINE_RASTER_FUNCS( ft_standard_raster, FT_GLYPH_FORMAT_OUTLINE, ft_black_new, /* FT_Raster_New_Func raster_new */ ft_black_reset, /* FT_Raster_Reset_Func raster_reset */ ft_black_set_mode, /* FT_Raster_Set_Mode_Func raster_set_mode */ ft_black_render, /* FT_Raster_Render_Func raster_render */ ft_black_done /* FT_Raster_Done_Func raster_done */ ) /* END */