freetype/src/smooth/ftgrays.c
Alexei Podtelezhnikov 4a2f8f1faf [smooth] Sub-banding protocol revision.
Rasterization sub-banding is utilized at large sizes while using
rather small fixed memory pool. Indeed it is possible to make an
educated guess how much memory is necessary at a given size for a
given glyph. It turns out that, for large majority of European glyphs,
you should store about 8 times more boundary pixels than their height.
Or, vice versa, if your memory pool can hold 800 pixels the band
height should be 100 and you should sub-band anything larger than
that. Should you still run out of memory, FreeType bisects the band
but you have wasted some time. This is what has been implemented in
FreeType since the beginning.

It was overlooked, however, that the top band could grow to twice the
default band size leading to unnecessary memory overflows there. This
commit fixes that. Now the bands are distributed more evenly and
cannot exceed the default size.

Now the magic number 8 is really suitable for rather simple European
scripts. For complex Chinese logograms the magic number should be 13
but that is subject for another day.

* src/smooth/ftgrays.c (gray_convert_glyph): Revise sub-banding
protocol.
2016-07-08 00:16:07 -04:00

2205 lines
64 KiB
C

/***************************************************************************/
/* */
/* ftgrays.c */
/* */
/* A new `perfect' anti-aliasing renderer (body). */
/* */
/* Copyright 2000-2016 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 `ftgrays.h' and `ftimage.h' into the current */
/* compilation directory. Typically, you could do something like */
/* */
/* - copy `src/smooth/ftgrays.c' (this file) to your current directory */
/* */
/* - copy `include/freetype/ftimage.h' and `src/smooth/ftgrays.h' to the */
/* same directory */
/* */
/* - compile `ftgrays' with the STANDALONE_ macro defined, as in */
/* */
/* cc -c -DSTANDALONE_ ftgrays.c */
/* */
/* The renderer can be initialized with a call to */
/* `ft_gray_raster.raster_new'; an anti-aliased bitmap can be generated */
/* with a call to `ft_gray_raster.raster_render'. */
/* */
/* See the comments and documentation in the file `ftimage.h' for more */
/* details on how the raster works. */
/* */
/*************************************************************************/
/*************************************************************************/
/* */
/* This is a new anti-aliasing scan-converter for FreeType 2. The */
/* algorithm used here is _very_ different from the one in the standard */
/* `ftraster' module. Actually, `ftgrays' computes the _exact_ */
/* coverage of the outline on each pixel cell. */
/* */
/* It is based on ideas that I initially found in Raph Levien's */
/* excellent LibArt graphics library (see http://www.levien.com/libart */
/* for more information, though the web pages do not tell anything */
/* about the renderer; you'll have to dive into the source code to */
/* understand how it works). */
/* */
/* Note, however, that this is a _very_ different implementation */
/* compared to Raph's. Coverage information is stored in a very */
/* different way, and I don't use sorted vector paths. Also, it doesn't */
/* use floating point values. */
/* */
/* This renderer has the following advantages: */
/* */
/* - It doesn't need an intermediate bitmap. Instead, one can supply a */
/* callback function that will be called by the renderer to draw gray */
/* spans on any target surface. You can thus do direct composition on */
/* any kind of bitmap, provided that you give the renderer the right */
/* callback. */
/* */
/* - A perfect anti-aliaser, i.e., it computes the _exact_ coverage on */
/* each pixel cell. */
/* */
/* - It performs a single pass on the outline (the `standard' FT2 */
/* renderer makes two passes). */
/* */
/* - It can easily be modified to render to _any_ number of gray levels */
/* cheaply. */
/* */
/* - For small (< 20) pixel sizes, it is faster than the standard */
/* renderer. */
/* */
/*************************************************************************/
/*************************************************************************/
/* */
/* 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 trace_smooth
#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
/* Auxiliary macros for token concatenation. */
#define FT_ERR_XCAT( x, y ) x ## y
#define FT_ERR_CAT( x, y ) FT_ERR_XCAT( x, y )
#define FT_BEGIN_STMNT do {
#define FT_END_STMNT } while ( 0 )
#define FT_MIN( a, b ) ( (a) < (b) ? (a) : (b) )
#define FT_MAX( a, b ) ( (a) > (b) ? (a) : (b) )
#define FT_ABS( a ) ( (a) < 0 ? -(a) : (a) )
/*
* Approximate sqrt(x*x+y*y) using the `alpha max plus beta min'
* algorithm. We use alpha = 1, beta = 3/8, giving us results with a
* largest error less than 7% compared to the exact value.
*/
#define FT_HYPOT( x, y ) \
( x = FT_ABS( x ), \
y = FT_ABS( y ), \
x > y ? x + ( 3 * y >> 3 ) \
: y + ( 3 * x >> 3 ) )
/* define this to dump debugging information */
/* #define FT_DEBUG_LEVEL_TRACE */
#ifdef FT_DEBUG_LEVEL_TRACE
#include <stdio.h>
#include <stdarg.h>
#endif
#include <stddef.h>
#include <string.h>
#include <setjmp.h>
#include <limits.h>
#define FT_CHAR_BIT CHAR_BIT
#define FT_UINT_MAX UINT_MAX
#define FT_INT_MAX INT_MAX
#define FT_ULONG_MAX ULONG_MAX
#define ft_memset memset
#define ft_setjmp setjmp
#define ft_longjmp longjmp
#define ft_jmp_buf jmp_buf
typedef ptrdiff_t FT_PtrDist;
#define ErrRaster_Invalid_Mode -2
#define ErrRaster_Invalid_Outline -1
#define ErrRaster_Invalid_Argument -3
#define ErrRaster_Memory_Overflow -4
#define FT_BEGIN_HEADER
#define FT_END_HEADER
#include "ftimage.h"
#include "ftgrays.h"
/* 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)
/* we only use level 5 & 7 tracing messages; cf. ftdebug.h */
#ifdef FT_DEBUG_LEVEL_TRACE
void
FT_Message( const char* fmt,
... )
{
va_list ap;
va_start( ap, fmt );
vfprintf( stderr, fmt, ap );
va_end( ap );
}
/* empty function useful for setting a breakpoint to catch errors */
int
FT_Throw( int error,
int line,
const char* file )
{
FT_UNUSED( error );
FT_UNUSED( line );
FT_UNUSED( file );
return 0;
}
/* we don't handle tracing levels in stand-alone mode; */
#ifndef FT_TRACE5
#define FT_TRACE5( varformat ) FT_Message varformat
#endif
#ifndef FT_TRACE7
#define FT_TRACE7( varformat ) FT_Message varformat
#endif
#ifndef FT_ERROR
#define FT_ERROR( varformat ) FT_Message varformat
#endif
#define FT_THROW( e ) \
( FT_Throw( FT_ERR_CAT( ErrRaster, e ), \
__LINE__, \
__FILE__ ) | \
FT_ERR_CAT( ErrRaster, e ) )
#else /* !FT_DEBUG_LEVEL_TRACE */
#define FT_TRACE5( x ) do { } while ( 0 ) /* nothing */
#define FT_TRACE7( x ) do { } while ( 0 ) /* nothing */
#define FT_ERROR( x ) do { } while ( 0 ) /* nothing */
#define FT_THROW( e ) FT_ERR_CAT( ErrRaster_, e )
#endif /* !FT_DEBUG_LEVEL_TRACE */
#define FT_DEFINE_OUTLINE_FUNCS( class_, \
move_to_, line_to_, \
conic_to_, cubic_to_, \
shift_, delta_ ) \
static const FT_Outline_Funcs class_ = \
{ \
move_to_, \
line_to_, \
conic_to_, \
cubic_to_, \
shift_, \
delta_ \
};
#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 <ft2build.h>
#include "ftgrays.h"
#include FT_INTERNAL_OBJECTS_H
#include FT_INTERNAL_DEBUG_H
#include FT_OUTLINE_H
#include "ftsmerrs.h"
#include "ftspic.h"
#define Smooth_Err_Invalid_Mode Smooth_Err_Cannot_Render_Glyph
#define Smooth_Err_Memory_Overflow Smooth_Err_Out_Of_Memory
#define ErrRaster_Memory_Overflow Smooth_Err_Out_Of_Memory
#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
/* as usual, for the speed hungry :-) */
#undef RAS_ARG
#undef RAS_ARG_
#undef RAS_VAR
#undef RAS_VAR_
#ifndef FT_STATIC_RASTER
#define RAS_ARG gray_PWorker worker
#define RAS_ARG_ gray_PWorker worker,
#define RAS_VAR worker
#define RAS_VAR_ worker,
#else /* FT_STATIC_RASTER */
#define RAS_ARG void
#define RAS_ARG_ /* empty */
#define RAS_VAR /* empty */
#define RAS_VAR_ /* empty */
#endif /* FT_STATIC_RASTER */
/* must be at least 6 bits! */
#define PIXEL_BITS 8
#undef FLOOR
#undef CEILING
#undef TRUNC
#undef SCALED
#define ONE_PIXEL ( 1 << PIXEL_BITS )
#define TRUNC( x ) ( (TCoord)( (x) >> PIXEL_BITS ) )
#define SUBPIXELS( x ) ( (TPos)(x) * ONE_PIXEL )
#define FLOOR( x ) ( (x) & -ONE_PIXEL )
#define CEILING( x ) ( ( (x) + ONE_PIXEL - 1 ) & -ONE_PIXEL )
#define ROUND( x ) ( ( (x) + ONE_PIXEL / 2 ) & -ONE_PIXEL )
#if PIXEL_BITS >= 6
#define UPSCALE( x ) ( (x) * ( ONE_PIXEL >> 6 ) )
#define DOWNSCALE( x ) ( (x) >> ( PIXEL_BITS - 6 ) )
#else
#define UPSCALE( x ) ( (x) >> ( 6 - PIXEL_BITS ) )
#define DOWNSCALE( x ) ( (x) * ( 64 >> PIXEL_BITS ) )
#endif
/* Compute `dividend / divisor' and return both its quotient and */
/* remainder, cast to a specific type. This macro also ensures that */
/* the remainder is always positive. */
#define FT_DIV_MOD( type, dividend, divisor, quotient, remainder ) \
FT_BEGIN_STMNT \
(quotient) = (type)( (dividend) / (divisor) ); \
(remainder) = (type)( (dividend) % (divisor) ); \
if ( (remainder) < 0 ) \
{ \
(quotient)--; \
(remainder) += (type)(divisor); \
} \
FT_END_STMNT
#ifdef __arm__
/* Work around a bug specific to GCC which make the compiler fail to */
/* optimize a division and modulo operation on the same parameters */
/* into a single call to `__aeabi_idivmod'. See */
/* */
/* http://gcc.gnu.org/bugzilla/show_bug.cgi?id=43721 */
#undef FT_DIV_MOD
#define FT_DIV_MOD( type, dividend, divisor, quotient, remainder ) \
FT_BEGIN_STMNT \
(quotient) = (type)( (dividend) / (divisor) ); \
(remainder) = (type)( (dividend) - (quotient) * (divisor) ); \
if ( (remainder) < 0 ) \
{ \
(quotient)--; \
(remainder) += (type)(divisor); \
} \
FT_END_STMNT
#endif /* __arm__ */
/* These macros speed up repetitive divisions by replacing them */
/* with multiplications and right shifts. */
#define FT_UDIVPREP( b ) \
long b ## _r = (long)( FT_ULONG_MAX >> PIXEL_BITS ) / ( b )
#define FT_UDIV( a, b ) \
( ( (unsigned long)( a ) * (unsigned long)( b ## _r ) ) >> \
( sizeof( long ) * FT_CHAR_BIT - PIXEL_BITS ) )
/*************************************************************************/
/* */
/* TYPE DEFINITIONS */
/* */
/* don't change the following types to FT_Int or FT_Pos, since we might */
/* need to define them to "float" or "double" when experimenting with */
/* new algorithms */
typedef long TPos; /* sub-pixel coordinate */
typedef int TCoord; /* integer scanline/pixel coordinate */
typedef int TArea; /* cell areas, coordinate products */
typedef struct TCell_* PCell;
typedef struct TCell_
{
TCoord x; /* same with gray_TWorker.ex */
TCoord cover; /* same with gray_TWorker.cover */
TArea area;
PCell next;
} TCell;
/* maximum number of gray spans in a call to the span callback */
#define FT_MAX_GRAY_SPANS 32
/* maximum number of gray cells in the buffer */
#if FT_RENDER_POOL_SIZE > 2048
#define FT_MAX_GRAY_POOL ( FT_RENDER_POOL_SIZE / sizeof ( TCell ) )
#else
#define FT_MAX_GRAY_POOL ( 2048 / sizeof ( TCell ) )
#endif
#if defined( _MSC_VER ) /* Visual C++ (and Intel C++) */
/* We disable the warning `structure was padded due to */
/* __declspec(align())' in order to compile cleanly with */
/* the maximum level of warnings. */
#pragma warning( push )
#pragma warning( disable : 4324 )
#endif /* _MSC_VER */
typedef struct gray_TWorker_
{
ft_jmp_buf jump_buffer;
TCoord ex, ey;
TCoord min_ex, max_ex;
TCoord min_ey, max_ey;
TCoord count_ex, count_ey;
TArea area;
TCoord cover;
int invalid;
PCell cells;
FT_PtrDist max_cells;
FT_PtrDist num_cells;
TPos x, y;
FT_Outline outline;
FT_Bitmap target;
FT_Span gray_spans[FT_MAX_GRAY_SPANS];
int num_gray_spans;
FT_Raster_Span_Func render_span;
void* render_span_data;
int span_y;
PCell* ycells;
} gray_TWorker, *gray_PWorker;
#if defined( _MSC_VER )
#pragma warning( pop )
#endif
#ifndef FT_STATIC_RASTER
#define ras (*worker)
#else
static gray_TWorker ras;
#endif
typedef struct gray_TRaster_
{
void* memory;
} gray_TRaster, *gray_PRaster;
#ifdef FT_DEBUG_LEVEL_TRACE
/* to be called while in the debugger -- */
/* this function causes a compiler warning since it is unused otherwise */
static void
gray_dump_cells( RAS_ARG )
{
int yindex;
for ( yindex = 0; yindex < ras.count_ey; yindex++ )
{
PCell cell;
printf( "%3d:", yindex );
for ( cell = ras.ycells[yindex]; cell != NULL; cell = cell->next )
printf( " (%3d, c:%4d, a:%6d)",
cell->x, cell->cover, cell->area );
printf( "\n" );
}
}
#endif /* FT_DEBUG_LEVEL_TRACE */
/*************************************************************************/
/* */
/* Record the current cell in the table. */
/* */
static PCell
gray_find_cell( RAS_ARG )
{
PCell *pcell, cell;
TCoord x = ras.ex;
if ( x > ras.count_ex )
x = ras.count_ex;
pcell = &ras.ycells[ras.ey];
for (;;)
{
cell = *pcell;
if ( cell == NULL || cell->x > x )
break;
if ( cell->x == x )
goto Exit;
pcell = &cell->next;
}
if ( ras.num_cells >= ras.max_cells )
ft_longjmp( ras.jump_buffer, 1 );
cell = ras.cells + ras.num_cells++;
cell->x = x;
cell->area = 0;
cell->cover = 0;
cell->next = *pcell;
*pcell = cell;
Exit:
return cell;
}
static void
gray_record_cell( RAS_ARG )
{
if ( ras.area | ras.cover )
{
PCell cell = gray_find_cell( RAS_VAR );
cell->area += ras.area;
cell->cover += ras.cover;
}
}
/*************************************************************************/
/* */
/* Set the current cell to a new position. */
/* */
static void
gray_set_cell( RAS_ARG_ TCoord ex,
TCoord ey )
{
/* Move the cell pointer to a new position. We set the `invalid' */
/* flag to indicate that the cell isn't part of those we're interested */
/* in during the render phase. This means that: */
/* */
/* . the new vertical position must be within min_ey..max_ey-1. */
/* . the new horizontal position must be strictly less than max_ex */
/* */
/* Note that if a cell is to the left of the clipping region, it is */
/* actually set to the (min_ex-1) horizontal position. */
/* All cells that are on the left of the clipping region go to the */
/* min_ex - 1 horizontal position. */
ey -= ras.min_ey;
if ( ex > ras.max_ex )
ex = ras.max_ex;
ex -= ras.min_ex;
if ( ex < 0 )
ex = -1;
/* are we moving to a different cell ? */
if ( ex != ras.ex || ey != ras.ey )
{
/* record the current one if it is valid */
if ( !ras.invalid )
gray_record_cell( RAS_VAR );
ras.area = 0;
ras.cover = 0;
ras.ex = ex;
ras.ey = ey;
}
ras.invalid = ( (unsigned int)ey >= (unsigned int)ras.count_ey ||
ex >= ras.count_ex );
}
/*************************************************************************/
/* */
/* Start a new contour at a given cell. */
/* */
static void
gray_start_cell( RAS_ARG_ TCoord ex,
TCoord ey )
{
if ( ex > ras.max_ex )
ex = ras.max_ex;
if ( ex < ras.min_ex )
ex = ras.min_ex - 1;
ras.area = 0;
ras.cover = 0;
ras.ex = ex - ras.min_ex;
ras.ey = ey - ras.min_ey;
ras.invalid = 0;
gray_set_cell( RAS_VAR_ ex, ey );
}
#ifndef FT_LONG64
/*************************************************************************/
/* */
/* Render a scanline as one or more cells. */
/* */
static void
gray_render_scanline( RAS_ARG_ TCoord ey,
TPos x1,
TCoord y1,
TPos x2,
TCoord y2 )
{
TCoord ex1, ex2, fx1, fx2, first, delta, mod;
TPos p, dx;
int incr;
ex1 = TRUNC( x1 );
ex2 = TRUNC( x2 );
/* trivial case. Happens often */
if ( y1 == y2 )
{
gray_set_cell( RAS_VAR_ ex2, ey );
return;
}
fx1 = (TCoord)( x1 - SUBPIXELS( ex1 ) );
fx2 = (TCoord)( x2 - SUBPIXELS( ex2 ) );
/* everything is located in a single cell. That is easy! */
/* */
if ( ex1 == ex2 )
{
delta = y2 - y1;
ras.area += (TArea)(( fx1 + fx2 ) * delta);
ras.cover += delta;
return;
}
/* ok, we'll have to render a run of adjacent cells on the same */
/* scanline... */
/* */
p = ( ONE_PIXEL - fx1 ) * ( y2 - y1 );
first = ONE_PIXEL;
incr = 1;
dx = x2 - x1;
if ( dx < 0 )
{
p = fx1 * ( y2 - y1 );
first = 0;
incr = -1;
dx = -dx;
}
FT_DIV_MOD( TCoord, p, dx, delta, mod );
ras.area += (TArea)(( fx1 + first ) * delta);
ras.cover += delta;
ex1 += incr;
gray_set_cell( RAS_VAR_ ex1, ey );
y1 += delta;
if ( ex1 != ex2 )
{
TCoord lift, rem;
p = ONE_PIXEL * ( y2 - y1 + delta );
FT_DIV_MOD( TCoord, p, dx, lift, rem );
mod -= (int)dx;
do
{
delta = lift;
mod += rem;
if ( mod >= 0 )
{
mod -= (TCoord)dx;
delta++;
}
ras.area += (TArea)(ONE_PIXEL * delta);
ras.cover += delta;
y1 += delta;
ex1 += incr;
gray_set_cell( RAS_VAR_ ex1, ey );
} while ( ex1 != ex2 );
}
delta = y2 - y1;
ras.area += (TArea)(( fx2 + ONE_PIXEL - first ) * delta);
ras.cover += delta;
}
/*************************************************************************/
/* */
/* Render a given line as a series of scanlines. */
/* */
static void
gray_render_line( RAS_ARG_ TPos to_x,
TPos to_y )
{
TCoord ey1, ey2, fy1, fy2, first, delta, mod;
TPos p, dx, dy, x, x2;
int incr;
ey1 = TRUNC( ras.y );
ey2 = TRUNC( to_y ); /* if (ey2 >= ras.max_ey) ey2 = ras.max_ey-1; */
/* perform vertical clipping */
if ( ( ey1 >= ras.max_ey && ey2 >= ras.max_ey ) ||
( ey1 < ras.min_ey && ey2 < ras.min_ey ) )
goto End;
fy1 = (TCoord)( ras.y - SUBPIXELS( ey1 ) );
fy2 = (TCoord)( to_y - SUBPIXELS( ey2 ) );
/* everything is on a single scanline */
if ( ey1 == ey2 )
{
gray_render_scanline( RAS_VAR_ ey1, ras.x, fy1, to_x, fy2 );
goto End;
}
dx = to_x - ras.x;
dy = to_y - ras.y;
/* vertical line - avoid calling gray_render_scanline */
incr = 1;
if ( dx == 0 )
{
TCoord ex = TRUNC( ras.x );
TCoord two_fx = (TCoord)( ( ras.x - SUBPIXELS( ex ) ) << 1 );
TArea area;
first = ONE_PIXEL;
if ( dy < 0 )
{
first = 0;
incr = -1;
}
delta = first - fy1;
ras.area += (TArea)two_fx * delta;
ras.cover += delta;
ey1 += incr;
gray_set_cell( RAS_VAR_ ex, ey1 );
delta = first + first - ONE_PIXEL;
area = (TArea)two_fx * delta;
while ( ey1 != ey2 )
{
ras.area += area;
ras.cover += delta;
ey1 += incr;
gray_set_cell( RAS_VAR_ ex, ey1 );
}
delta = fy2 - ONE_PIXEL + first;
ras.area += (TArea)two_fx * delta;
ras.cover += delta;
goto End;
}
/* ok, we have to render several scanlines */
p = ( ONE_PIXEL - fy1 ) * dx;
first = ONE_PIXEL;
incr = 1;
if ( dy < 0 )
{
p = fy1 * dx;
first = 0;
incr = -1;
dy = -dy;
}
FT_DIV_MOD( TCoord, p, dy, delta, mod );
x = ras.x + delta;
gray_render_scanline( RAS_VAR_ ey1, ras.x, fy1, x, first );
ey1 += incr;
gray_set_cell( RAS_VAR_ TRUNC( x ), ey1 );
if ( ey1 != ey2 )
{
TCoord lift, rem;
p = ONE_PIXEL * dx;
FT_DIV_MOD( TCoord, p, dy, lift, rem );
mod -= (TCoord)dy;
do
{
delta = lift;
mod += rem;
if ( mod >= 0 )
{
mod -= (TCoord)dy;
delta++;
}
x2 = x + delta;
gray_render_scanline( RAS_VAR_ ey1,
x, ONE_PIXEL - first,
x2, first );
x = x2;
ey1 += incr;
gray_set_cell( RAS_VAR_ TRUNC( x ), ey1 );
} while ( ey1 != ey2 );
}
gray_render_scanline( RAS_VAR_ ey1,
x, ONE_PIXEL - first,
to_x, fy2 );
End:
ras.x = to_x;
ras.y = to_y;
}
#else
/*************************************************************************/
/* */
/* Render a straight line across multiple cells in any direction. */
/* */
static void
gray_render_line( RAS_ARG_ TPos to_x,
TPos to_y )
{
TPos dx, dy, fx1, fy1, fx2, fy2;
TCoord ex1, ex2, ey1, ey2;
ey1 = TRUNC( ras.y );
ey2 = TRUNC( to_y );
/* perform vertical clipping */
if ( ( ey1 >= ras.max_ey && ey2 >= ras.max_ey ) ||
( ey1 < ras.min_ey && ey2 < ras.min_ey ) )
goto End;
ex1 = TRUNC( ras.x );
ex2 = TRUNC( to_x );
fx1 = ras.x - SUBPIXELS( ex1 );
fy1 = ras.y - SUBPIXELS( ey1 );
dx = to_x - ras.x;
dy = to_y - ras.y;
if ( ex1 == ex2 && ey1 == ey2 ) /* inside one cell */
;
else if ( dy == 0 ) /* ex1 != ex2 */ /* any horizontal line */
{
ex1 = ex2;
gray_set_cell( RAS_VAR_ ex1, ey1 );
}
else if ( dx == 0 )
{
if ( dy > 0 ) /* vertical line up */
do
{
fy2 = ONE_PIXEL;
ras.cover += ( fy2 - fy1 );
ras.area += ( fy2 - fy1 ) * fx1 * 2;
fy1 = 0;
ey1++;
gray_set_cell( RAS_VAR_ ex1, ey1 );
} while ( ey1 != ey2 );
else /* vertical line down */
do
{
fy2 = 0;
ras.cover += ( fy2 - fy1 );
ras.area += ( fy2 - fy1 ) * fx1 * 2;
fy1 = ONE_PIXEL;
ey1--;
gray_set_cell( RAS_VAR_ ex1, ey1 );
} while ( ey1 != ey2 );
}
else /* any other line */
{
TPos prod = dx * fy1 - dy * fx1;
FT_UDIVPREP( dx );
FT_UDIVPREP( dy );
/* The fundamental value `prod' determines which side and the */
/* exact coordinate where the line exits current cell. It is */
/* also easily updated when moving from one cell to the next. */
do
{
if ( prod <= 0 &&
prod - dx * ONE_PIXEL > 0 ) /* left */
{
fx2 = 0;
fy2 = (TPos)FT_UDIV( -prod, -dx );
prod -= dy * ONE_PIXEL;
ras.cover += ( fy2 - fy1 );
ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 );
fx1 = ONE_PIXEL;
fy1 = fy2;
ex1--;
}
else if ( prod - dx * ONE_PIXEL <= 0 &&
prod - dx * ONE_PIXEL + dy * ONE_PIXEL > 0 ) /* up */
{
prod -= dx * ONE_PIXEL;
fx2 = (TPos)FT_UDIV( -prod, dy );
fy2 = ONE_PIXEL;
ras.cover += ( fy2 - fy1 );
ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 );
fx1 = fx2;
fy1 = 0;
ey1++;
}
else if ( prod - dx * ONE_PIXEL + dy * ONE_PIXEL <= 0 &&
prod + dy * ONE_PIXEL >= 0 ) /* right */
{
prod += dy * ONE_PIXEL;
fx2 = ONE_PIXEL;
fy2 = (TPos)FT_UDIV( prod, dx );
ras.cover += ( fy2 - fy1 );
ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 );
fx1 = 0;
fy1 = fy2;
ex1++;
}
else /* ( prod + dy * ONE_PIXEL < 0 &&
prod > 0 ) down */
{
fx2 = (TPos)FT_UDIV( prod, -dy );
fy2 = 0;
prod += dx * ONE_PIXEL;
ras.cover += ( fy2 - fy1 );
ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 );
fx1 = fx2;
fy1 = ONE_PIXEL;
ey1--;
}
gray_set_cell( RAS_VAR_ ex1, ey1 );
} while ( ex1 != ex2 || ey1 != ey2 );
}
fx2 = to_x - SUBPIXELS( ex2 );
fy2 = to_y - SUBPIXELS( ey2 );
ras.cover += ( fy2 - fy1 );
ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 );
End:
ras.x = to_x;
ras.y = to_y;
}
#endif
static void
gray_split_conic( FT_Vector* base )
{
TPos 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;
}
static void
gray_render_conic( RAS_ARG_ const FT_Vector* control,
const FT_Vector* to )
{
FT_Vector bez_stack[16 * 2 + 1]; /* enough to accommodate bisections */
FT_Vector* arc = bez_stack;
TPos dx, dy;
int draw, split;
arc[0].x = UPSCALE( to->x );
arc[0].y = UPSCALE( to->y );
arc[1].x = UPSCALE( control->x );
arc[1].y = UPSCALE( control->y );
arc[2].x = ras.x;
arc[2].y = ras.y;
/* short-cut the arc that crosses the current band */
if ( ( TRUNC( arc[0].y ) >= ras.max_ey &&
TRUNC( arc[1].y ) >= ras.max_ey &&
TRUNC( arc[2].y ) >= ras.max_ey ) ||
( TRUNC( arc[0].y ) < ras.min_ey &&
TRUNC( arc[1].y ) < ras.min_ey &&
TRUNC( arc[2].y ) < ras.min_ey ) )
{
ras.x = arc[0].x;
ras.y = arc[0].y;
return;
}
dx = FT_ABS( arc[2].x + arc[0].x - 2 * arc[1].x );
dy = FT_ABS( arc[2].y + arc[0].y - 2 * arc[1].y );
if ( dx < dy )
dx = dy;
/* We can calculate the number of necessary bisections because */
/* each bisection predictably reduces deviation exactly 4-fold. */
/* Even 32-bit deviation would vanish after 16 bisections. */
draw = 1;
while ( dx > ONE_PIXEL / 4 )
{
dx >>= 2;
draw <<= 1;
}
/* We use decrement counter to count the total number of segments */
/* to draw starting from 2^level. Before each draw we split as */
/* many times as there are trailing zeros in the counter. */
do
{
split = 1;
while ( ( draw & split ) == 0 )
{
gray_split_conic( arc );
arc += 2;
split <<= 1;
}
gray_render_line( RAS_VAR_ arc[0].x, arc[0].y );
arc -= 2;
} while ( --draw );
}
static void
gray_split_cubic( FT_Vector* base )
{
TPos 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 ) / 2;
base[5].x = b = ( base[3].x + d ) / 2;
c = ( c + d ) / 2;
base[2].x = a = ( a + c ) / 2;
base[4].x = b = ( b + c ) / 2;
base[3].x = ( a + b ) / 2;
base[6].y = base[3].y;
c = base[1].y;
d = base[2].y;
base[1].y = a = ( base[0].y + c ) / 2;
base[5].y = b = ( base[3].y + d ) / 2;
c = ( c + d ) / 2;
base[2].y = a = ( a + c ) / 2;
base[4].y = b = ( b + c ) / 2;
base[3].y = ( a + b ) / 2;
}
static void
gray_render_cubic( RAS_ARG_ const FT_Vector* control1,
const FT_Vector* control2,
const FT_Vector* to )
{
FT_Vector bez_stack[16 * 3 + 1]; /* enough to accommodate bisections */
FT_Vector* arc = bez_stack;
TPos dx, dy, dx_, dy_;
TPos dx1, dy1, dx2, dy2;
TPos L, s, s_limit;
arc[0].x = UPSCALE( to->x );
arc[0].y = UPSCALE( to->y );
arc[1].x = UPSCALE( control2->x );
arc[1].y = UPSCALE( control2->y );
arc[2].x = UPSCALE( control1->x );
arc[2].y = UPSCALE( control1->y );
arc[3].x = ras.x;
arc[3].y = ras.y;
/* short-cut the arc that crosses the current band */
if ( ( TRUNC( arc[0].y ) >= ras.max_ey &&
TRUNC( arc[1].y ) >= ras.max_ey &&
TRUNC( arc[2].y ) >= ras.max_ey &&
TRUNC( arc[3].y ) >= ras.max_ey ) ||
( TRUNC( arc[0].y ) < ras.min_ey &&
TRUNC( arc[1].y ) < ras.min_ey &&
TRUNC( arc[2].y ) < ras.min_ey &&
TRUNC( arc[3].y ) < ras.min_ey ) )
{
ras.x = arc[0].x;
ras.y = arc[0].y;
return;
}
for (;;)
{
/* Decide whether to split or draw. See `Rapid Termination */
/* Evaluation for Recursive Subdivision of Bezier Curves' by Thomas */
/* F. Hain, at */
/* http://www.cis.southalabama.edu/~hain/general/Publications/Bezier/Camera-ready%20CISST02%202.pdf */
/* dx and dy are x and y components of the P0-P3 chord vector. */
dx = dx_ = arc[3].x - arc[0].x;
dy = dy_ = arc[3].y - arc[0].y;
L = FT_HYPOT( dx_, dy_ );
/* Avoid possible arithmetic overflow below by splitting. */
if ( L > 32767 )
goto Split;
/* Max deviation may be as much as (s/L) * 3/4 (if Hain's v = 1). */
s_limit = L * (TPos)( ONE_PIXEL / 6 );
/* s is L * the perpendicular distance from P1 to the line P0-P3. */
dx1 = arc[1].x - arc[0].x;
dy1 = arc[1].y - arc[0].y;
s = FT_ABS( dy * dx1 - dx * dy1 );
if ( s > s_limit )
goto Split;
/* s is L * the perpendicular distance from P2 to the line P0-P3. */
dx2 = arc[2].x - arc[0].x;
dy2 = arc[2].y - arc[0].y;
s = FT_ABS( dy * dx2 - dx * dy2 );
if ( s > s_limit )
goto Split;
/* Split super curvy segments where the off points are so far
from the chord that the angles P0-P1-P3 or P0-P2-P3 become
acute as detected by appropriate dot products. */
if ( dx1 * ( dx1 - dx ) + dy1 * ( dy1 - dy ) > 0 ||
dx2 * ( dx2 - dx ) + dy2 * ( dy2 - dy ) > 0 )
goto Split;
gray_render_line( RAS_VAR_ arc[0].x, arc[0].y );
if ( arc == bez_stack )
return;
arc -= 3;
continue;
Split:
gray_split_cubic( arc );
arc += 3;
}
}
static int
gray_move_to( const FT_Vector* to,
gray_PWorker worker )
{
TPos x, y;
/* record current cell, if any */
if ( !ras.invalid )
gray_record_cell( RAS_VAR );
/* start to a new position */
x = UPSCALE( to->x );
y = UPSCALE( to->y );
gray_start_cell( RAS_VAR_ TRUNC( x ), TRUNC( y ) );
ras.x = x;
ras.y = y;
return 0;
}
static int
gray_line_to( const FT_Vector* to,
gray_PWorker worker )
{
gray_render_line( RAS_VAR_ UPSCALE( to->x ), UPSCALE( to->y ) );
return 0;
}
static int
gray_conic_to( const FT_Vector* control,
const FT_Vector* to,
gray_PWorker worker )
{
gray_render_conic( RAS_VAR_ control, to );
return 0;
}
static int
gray_cubic_to( const FT_Vector* control1,
const FT_Vector* control2,
const FT_Vector* to,
gray_PWorker worker )
{
gray_render_cubic( RAS_VAR_ control1, control2, to );
return 0;
}
static void
gray_render_span( int y,
int count,
const FT_Span* spans,
gray_PWorker worker )
{
unsigned char* p;
FT_Bitmap* map = &worker->target;
/* first of all, compute the scanline offset */
p = (unsigned char*)map->buffer - y * map->pitch;
if ( map->pitch >= 0 )
p += ( map->rows - 1 ) * (unsigned int)map->pitch;
for ( ; count > 0; count--, spans++ )
{
unsigned char coverage = spans->coverage;
if ( coverage )
{
unsigned char* q = p + spans->x;
/* For small-spans it is faster to do it by ourselves than
* calling `memset'. This is mainly due to the cost of the
* function call.
*/
switch ( spans->len )
{
case 7: *q++ = coverage;
case 6: *q++ = coverage;
case 5: *q++ = coverage;
case 4: *q++ = coverage;
case 3: *q++ = coverage;
case 2: *q++ = coverage;
case 1: *q = coverage;
case 0: break;
default:
FT_MEM_SET( q, coverage, spans->len );
}
}
}
}
static void
gray_hline( RAS_ARG_ TCoord x,
TCoord y,
TArea area,
TCoord acount )
{
int coverage;
/* compute the coverage line's coverage, depending on the */
/* outline fill rule */
/* */
/* the coverage percentage is area/(PIXEL_BITS*PIXEL_BITS*2) */
/* */
coverage = (int)( area >> ( PIXEL_BITS * 2 + 1 - 8 ) );
/* use range 0..256 */
if ( coverage < 0 )
coverage = -coverage;
if ( ras.outline.flags & FT_OUTLINE_EVEN_ODD_FILL )
{
coverage &= 511;
if ( coverage > 256 )
coverage = 512 - coverage;
else if ( coverage == 256 )
coverage = 255;
}
else
{
/* normal non-zero winding rule */
if ( coverage >= 256 )
coverage = 255;
}
y += ras.min_ey;
x += ras.min_ex;
if ( coverage )
{
FT_Span* span;
int count;
/* see whether we can add this span to the current list */
count = ras.num_gray_spans;
span = ras.gray_spans + count - 1;
if ( span->coverage == coverage &&
span->x + span->len == x &&
ras.span_y == y &&
count > 0 )
{
span->len = (unsigned short)( span->len + acount );
return;
}
if ( ras.span_y != y || count >= FT_MAX_GRAY_SPANS )
{
if ( ras.render_span && count > 0 )
ras.render_span( ras.span_y, count, ras.gray_spans,
ras.render_span_data );
#ifdef FT_DEBUG_LEVEL_TRACE
if ( count > 0 )
{
int n;
FT_TRACE7(( "y = %3d ", ras.span_y ));
span = ras.gray_spans;
for ( n = 0; n < count; n++, span++ )
FT_TRACE7(( "[%d..%d]:%02x ",
span->x, span->x + span->len - 1, span->coverage ));
FT_TRACE7(( "\n" ));
}
#endif /* FT_DEBUG_LEVEL_TRACE */
ras.num_gray_spans = 0;
ras.span_y = (int)y;
span = ras.gray_spans;
}
else
span++;
/* add a gray span to the current list */
span->x = (short)x;
span->len = (unsigned short)acount;
span->coverage = (unsigned char)coverage;
ras.num_gray_spans++;
}
}
static void
gray_sweep( RAS_ARG )
{
int yindex;
if ( ras.num_cells == 0 )
return;
ras.num_gray_spans = 0;
ras.span_y = 0;
FT_TRACE7(( "gray_sweep: start\n" ));
for ( yindex = 0; yindex < ras.count_ey; yindex++ )
{
PCell cell = ras.ycells[yindex];
TCoord cover = 0;
TCoord x = 0;
for ( ; cell != NULL; cell = cell->next )
{
TArea area;
if ( cell->x > x && cover != 0 )
gray_hline( RAS_VAR_ x, yindex, (TArea)cover * ( ONE_PIXEL * 2 ),
cell->x - x );
cover += cell->cover;
area = (TArea)cover * ( ONE_PIXEL * 2 ) - cell->area;
if ( area != 0 && cell->x >= 0 )
gray_hline( RAS_VAR_ cell->x, yindex, area, 1 );
x = cell->x + 1;
}
if ( cover != 0 )
gray_hline( RAS_VAR_ x, yindex, (TArea)cover * ( ONE_PIXEL * 2 ),
ras.count_ex - x );
}
if ( ras.render_span && ras.num_gray_spans > 0 )
ras.render_span( ras.span_y, ras.num_gray_spans,
ras.gray_spans, ras.render_span_data );
#ifdef FT_DEBUG_LEVEL_TRACE
if ( ras.num_gray_spans > 0 )
{
FT_Span* span;
int n;
FT_TRACE7(( "y = %3d ", ras.span_y ));
span = ras.gray_spans;
for ( n = 0; n < ras.num_gray_spans; n++, span++ )
FT_TRACE7(( "[%d..%d]:%02x ",
span->x, span->x + span->len - 1, span->coverage ));
FT_TRACE7(( "\n" ));
}
FT_TRACE7(( "gray_sweep: end\n" ));
#endif /* FT_DEBUG_LEVEL_TRACE */
}
#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_Decompose */
/* */
/* <Description> */
/* Walk over an outline's structure to decompose it into individual */
/* segments and Bézier arcs. This function is also able to emit */
/* `move to' and `close to' operations to indicate the start and end */
/* of new contours in the outline. */
/* */
/* <Input> */
/* outline :: A pointer to the source target. */
/* */
/* func_interface :: A table of `emitters', i.e., function pointers */
/* called during decomposition to indicate path */
/* operations. */
/* */
/* <InOut> */
/* user :: A typeless pointer which is passed to each */
/* emitter during the decomposition. It can be */
/* used to store the state during the */
/* decomposition. */
/* */
/* <Return> */
/* Error code. 0 means success. */
/* */
static int
FT_Outline_Decompose( const FT_Outline* outline,
const FT_Outline_Funcs* func_interface,
void* user )
{
#undef SCALED
#define SCALED( x ) ( ( (x) << shift ) - delta )
FT_Vector v_last;
FT_Vector v_control;
FT_Vector v_start;
FT_Vector* point;
FT_Vector* limit;
char* tags;
int error;
int n; /* index of contour in outline */
int first; /* index of first point in contour */
char tag; /* current point's state */
int shift;
TPos delta;
if ( !outline )
return FT_THROW( Invalid_Outline );
if ( !func_interface )
return FT_THROW( Invalid_Argument );
shift = func_interface->shift;
delta = func_interface->delta;
first = 0;
for ( n = 0; n < outline->n_contours; n++ )
{
int last; /* index of last point in contour */
FT_TRACE5(( "FT_Outline_Decompose: Outline %d\n", n ));
last = outline->contours[n];
if ( last < 0 )
goto Invalid_Outline;
limit = outline->points + last;
v_start = outline->points[first];
v_start.x = SCALED( v_start.x );
v_start.y = SCALED( v_start.y );
v_last = outline->points[last];
v_last.x = SCALED( v_last.x );
v_last.y = SCALED( v_last.y );
v_control = v_start;
point = outline->points + first;
tags = 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( 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--;
}
FT_TRACE5(( " move to (%.2f, %.2f)\n",
v_start.x / 64.0, v_start.y / 64.0 ));
error = func_interface->move_to( &v_start, user );
if ( error )
goto Exit;
while ( point < limit )
{
point++;
tags++;
tag = FT_CURVE_TAG( tags[0] );
switch ( tag )
{
case FT_CURVE_TAG_ON: /* emit a single line_to */
{
FT_Vector vec;
vec.x = SCALED( point->x );
vec.y = SCALED( point->y );
FT_TRACE5(( " line to (%.2f, %.2f)\n",
vec.x / 64.0, vec.y / 64.0 ));
error = func_interface->line_to( &vec, user );
if ( error )
goto Exit;
continue;
}
case FT_CURVE_TAG_CONIC: /* consume conic arcs */
v_control.x = SCALED( point->x );
v_control.y = SCALED( point->y );
Do_Conic:
if ( point < limit )
{
FT_Vector vec;
FT_Vector v_middle;
point++;
tags++;
tag = FT_CURVE_TAG( tags[0] );
vec.x = SCALED( point->x );
vec.y = SCALED( point->y );
if ( tag == FT_CURVE_TAG_ON )
{
FT_TRACE5(( " conic to (%.2f, %.2f)"
" with control (%.2f, %.2f)\n",
vec.x / 64.0, vec.y / 64.0,
v_control.x / 64.0, v_control.y / 64.0 ));
error = func_interface->conic_to( &v_control, &vec, user );
if ( error )
goto Exit;
continue;
}
if ( tag != FT_CURVE_TAG_CONIC )
goto Invalid_Outline;
v_middle.x = ( v_control.x + vec.x ) / 2;
v_middle.y = ( v_control.y + vec.y ) / 2;
FT_TRACE5(( " conic to (%.2f, %.2f)"
" with control (%.2f, %.2f)\n",
v_middle.x / 64.0, v_middle.y / 64.0,
v_control.x / 64.0, v_control.y / 64.0 ));
error = func_interface->conic_to( &v_control, &v_middle, user );
if ( error )
goto Exit;
v_control = vec;
goto Do_Conic;
}
FT_TRACE5(( " conic to (%.2f, %.2f)"
" with control (%.2f, %.2f)\n",
v_start.x / 64.0, v_start.y / 64.0,
v_control.x / 64.0, v_control.y / 64.0 ));
error = func_interface->conic_to( &v_control, &v_start, user );
goto Close;
default: /* FT_CURVE_TAG_CUBIC */
{
FT_Vector vec1, vec2;
if ( point + 1 > limit ||
FT_CURVE_TAG( tags[1] ) != FT_CURVE_TAG_CUBIC )
goto Invalid_Outline;
point += 2;
tags += 2;
vec1.x = SCALED( point[-2].x );
vec1.y = SCALED( point[-2].y );
vec2.x = SCALED( point[-1].x );
vec2.y = SCALED( point[-1].y );
if ( point <= limit )
{
FT_Vector vec;
vec.x = SCALED( point->x );
vec.y = SCALED( point->y );
FT_TRACE5(( " cubic to (%.2f, %.2f)"
" with controls (%.2f, %.2f) and (%.2f, %.2f)\n",
vec.x / 64.0, vec.y / 64.0,
vec1.x / 64.0, vec1.y / 64.0,
vec2.x / 64.0, vec2.y / 64.0 ));
error = func_interface->cubic_to( &vec1, &vec2, &vec, user );
if ( error )
goto Exit;
continue;
}
FT_TRACE5(( " cubic to (%.2f, %.2f)"
" with controls (%.2f, %.2f) and (%.2f, %.2f)\n",
v_start.x / 64.0, v_start.y / 64.0,
vec1.x / 64.0, vec1.y / 64.0,
vec2.x / 64.0, vec2.y / 64.0 ));
error = func_interface->cubic_to( &vec1, &vec2, &v_start, user );
goto Close;
}
}
}
/* close the contour with a line segment */
FT_TRACE5(( " line to (%.2f, %.2f)\n",
v_start.x / 64.0, v_start.y / 64.0 ));
error = func_interface->line_to( &v_start, user );
Close:
if ( error )
goto Exit;
first = last + 1;
}
FT_TRACE5(( "FT_Outline_Decompose: Done\n", n ));
return 0;
Exit:
FT_TRACE5(( "FT_Outline_Decompose: Error %d\n", error ));
return error;
Invalid_Outline:
return FT_THROW( Invalid_Outline );
}
/*************************************************************************/
/* */
/* <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 )
{
TPos xMin, yMin, xMax, yMax;
if ( outline && acbox )
{
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++ )
{
TPos 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_ */
typedef struct gray_TBand_
{
TCoord min, max;
} gray_TBand;
FT_DEFINE_OUTLINE_FUNCS(
func_interface,
(FT_Outline_MoveTo_Func) gray_move_to,
(FT_Outline_LineTo_Func) gray_line_to,
(FT_Outline_ConicTo_Func)gray_conic_to,
(FT_Outline_CubicTo_Func)gray_cubic_to,
0,
0 )
static int
gray_convert_glyph_inner( RAS_ARG )
{
volatile int error = 0;
#ifdef FT_CONFIG_OPTION_PIC
FT_Outline_Funcs func_interface;
Init_Class_func_interface(&func_interface);
#endif
if ( ft_setjmp( ras.jump_buffer ) == 0 )
{
error = FT_Outline_Decompose( &ras.outline, &func_interface, &ras );
if ( !ras.invalid )
gray_record_cell( RAS_VAR );
FT_TRACE7(( "band [%d..%d]: %d cells\n",
ras.min_ey, ras.max_ey, ras.num_cells ));
}
else
{
error = FT_THROW( Memory_Overflow );
FT_TRACE7(( "band [%d..%d]: to be bisected\n",
ras.min_ey, ras.max_ey ));
}
return error;
}
static int
gray_convert_glyph( RAS_ARG )
{
TCell buffer[FT_MAX_GRAY_POOL];
TCoord band_size = FT_MAX_GRAY_POOL / 8;
int num_bands;
TCoord min, max, max_y;
gray_TBand bands[32]; /* enough to accommodate bisections */
gray_TBand* band;
/* set up vertical bands */
if ( ras.count_ey > band_size )
{
/* two divisions rounded up */
num_bands = (int)( ( ras.count_ey + band_size - 1) / band_size );
band_size = ( ras.count_ey + num_bands - 1 ) / num_bands;
}
min = ras.min_ey;
max_y = ras.max_ey;
for (; min < max_y; min = max )
{
max = min + band_size;
if ( max > max_y )
max = max_y;
bands[0].min = min;
bands[0].max = max;
band = bands;
do
{
TCoord bottom, top, middle;
int error;
/* memory management */
{
size_t ycount = (size_t)( band->max - band->min );
size_t cell_start;
cell_start = ( ycount * sizeof ( PCell ) + sizeof ( TCell ) - 1 ) /
sizeof ( TCell );
if ( FT_MAX_GRAY_POOL - cell_start < 2 )
goto ReduceBands;
ras.cells = buffer + cell_start;
ras.max_cells = (FT_PtrDist)( FT_MAX_GRAY_POOL - cell_start );
ras.ycells = (PCell*)buffer;
while ( ycount )
ras.ycells[--ycount] = NULL;
}
ras.num_cells = 0;
ras.invalid = 1;
ras.min_ey = band->min;
ras.max_ey = band->max;
ras.count_ey = band->max - band->min;
error = gray_convert_glyph_inner( RAS_VAR );
if ( !error )
{
gray_sweep( RAS_VAR );
band--;
continue;
}
else if ( error != ErrRaster_Memory_Overflow )
return 1;
ReduceBands:
/* render pool overflow; we will reduce the render band by half */
bottom = band->min;
top = band->max;
middle = bottom + ( ( top - bottom ) >> 1 );
/* This is too complex for a single scanline; there must */
/* be some problems. */
if ( middle == bottom )
{
FT_TRACE7(( "gray_convert_glyph: rotten glyph\n" ));
return 1;
}
band[1].min = bottom;
band[1].max = middle;
band[0].min = middle;
band[0].max = top;
band++;
} while ( band >= bands );
}
return 0;
}
static int
gray_raster_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;
FT_BBox cbox, clip;
gray_TWorker worker[1];
if ( !raster )
return FT_THROW( Invalid_Argument );
if ( !outline )
return FT_THROW( Invalid_Outline );
/* return immediately if the outline is empty */
if ( outline->n_points == 0 || outline->n_contours <= 0 )
return 0;
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 );
/* if direct mode is not set, we must have a target bitmap */
if ( !( params->flags & FT_RASTER_FLAG_DIRECT ) )
{
if ( !target_map )
return FT_THROW( Invalid_Argument );
/* nothing to do */
if ( !target_map->width || !target_map->rows )
return 0;
if ( !target_map->buffer )
return FT_THROW( Invalid_Argument );
}
/* this version does not support monochrome rendering */
if ( !( params->flags & FT_RASTER_FLAG_AA ) )
return FT_THROW( Invalid_Mode );
FT_Outline_Get_CBox( outline, &cbox );
/* reject too large outline coordinates */
if ( cbox.xMin < -0x1000000L || cbox.xMax > 0x1000000L ||
cbox.yMin < -0x1000000L || cbox.yMax > 0x1000000L )
return FT_THROW( Invalid_Outline );
/* truncate the bounding box to integer pixels */
cbox.xMin = cbox.xMin >> 6;
cbox.yMin = cbox.yMin >> 6;
cbox.xMax = ( cbox.xMax + 63 ) >> 6;
cbox.yMax = ( cbox.yMax + 63 ) >> 6;
/* compute clipping box */
if ( !( params->flags & FT_RASTER_FLAG_DIRECT ) )
{
/* compute clip box from target pixmap */
clip.xMin = 0;
clip.yMin = 0;
clip.xMax = (FT_Pos)target_map->width;
clip.yMax = (FT_Pos)target_map->rows;
}
else if ( params->flags & FT_RASTER_FLAG_CLIP )
clip = params->clip_box;
else
{
clip.xMin = -32768L;
clip.yMin = -32768L;
clip.xMax = 32767L;
clip.yMax = 32767L;
}
/* clip to target bitmap, exit if nothing to do */
ras.min_ex = FT_MAX( cbox.xMin, clip.xMin );
ras.min_ey = FT_MAX( cbox.yMin, clip.yMin );
ras.max_ex = FT_MIN( cbox.xMax, clip.xMax );
ras.max_ey = FT_MIN( cbox.yMax, clip.yMax );
if ( ras.max_ex <= ras.min_ex || ras.max_ey <= ras.min_ey )
return 0;
ras.count_ex = ras.max_ex - ras.min_ex;
ras.count_ey = ras.max_ey - ras.min_ey;
ras.outline = *outline;
if ( params->flags & FT_RASTER_FLAG_DIRECT )
{
ras.render_span = (FT_Raster_Span_Func)params->gray_spans;
ras.render_span_data = params->user;
}
else
{
ras.target = *target_map;
ras.render_span = (FT_Raster_Span_Func)gray_render_span;
ras.render_span_data = &ras;
}
return gray_convert_glyph( RAS_VAR );
}
/**** RASTER OBJECT CREATION: In stand-alone mode, we simply use *****/
/**** a static object. *****/
#ifdef STANDALONE_
static int
gray_raster_new( void* memory,
FT_Raster* araster )
{
static gray_TRaster the_raster;
FT_UNUSED( memory );
*araster = (FT_Raster)&the_raster;
FT_MEM_ZERO( &the_raster, sizeof ( the_raster ) );
return 0;
}
static void
gray_raster_done( FT_Raster raster )
{
/* nothing */
FT_UNUSED( raster );
}
#else /* !STANDALONE_ */
static int
gray_raster_new( FT_Memory memory,
FT_Raster* araster )
{
FT_Error error;
gray_PRaster raster = NULL;
*araster = 0;
if ( !FT_ALLOC( raster, sizeof ( gray_TRaster ) ) )
{
raster->memory = memory;
*araster = (FT_Raster)raster;
}
return error;
}
static void
gray_raster_done( FT_Raster raster )
{
FT_Memory memory = (FT_Memory)((gray_PRaster)raster)->memory;
FT_FREE( raster );
}
#endif /* !STANDALONE_ */
static void
gray_raster_reset( FT_Raster raster,
unsigned char* pool_base,
unsigned long pool_size )
{
FT_UNUSED( raster );
FT_UNUSED( pool_base );
FT_UNUSED( pool_size );
}
static int
gray_raster_set_mode( FT_Raster raster,
unsigned long mode,
void* args )
{
FT_UNUSED( raster );
FT_UNUSED( mode );
FT_UNUSED( args );
return 0; /* nothing to do */
}
FT_DEFINE_RASTER_FUNCS(
ft_grays_raster,
FT_GLYPH_FORMAT_OUTLINE,
(FT_Raster_New_Func) gray_raster_new,
(FT_Raster_Reset_Func) gray_raster_reset,
(FT_Raster_Set_Mode_Func)gray_raster_set_mode,
(FT_Raster_Render_Func) gray_raster_render,
(FT_Raster_Done_Func) gray_raster_done )
/* END */
/* Local Variables: */
/* coding: utf-8 */
/* End: */