diff --git a/.vscode/settings.json b/.vscode/settings.json index f273162bf..652233883 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "files.associations": { - "ftoutln.h": "c" + "ftoutln.h": "c", + "svprop.h": "c" } } \ No newline at end of file diff --git a/src/dense/ftdense.c b/src/dense/ftdense.c index fd09b747c..4f83c934e 100644 --- a/src/dense/ftdense.c +++ b/src/dense/ftdense.c @@ -1,3 +1,622 @@ /** The rasterizer for the 'dense' renderer */ +#undef FT_COMPONENT +#define FT_COMPONENT dense + +#include +#include +#include +#include +#include "ftdense.h" + +#include "ftdenseerrs.h" + +typedef struct dense_TRaster_ +{ + void* memory; + +} dense_TRaster, *dense_PRaster; + +static RasterFP_Point +Lerp( float aT, RasterFP_Point aP0, RasterFP_Point aP1 ) +{ + RasterFP_Point p; + p.m_x = aP0.m_x + aT * ( aP1.m_x - aP0.m_x ); + p.m_y = aP0.m_y + aT * ( aP1.m_y - aP0.m_y ); + return p; +} + +static int +dense_move_to( const FT_Vector* to, RasterFP* aRasterFP ) +{ + + RasterFP_Point lp = {to->x, to->y}; + + aRasterFP->last_point = lp; + return 0; +} + +static int +dense_line_to( const FT_Vector* to, RasterFP* aRasterFP ) +{ + RasterFP_Point tp = {to->x, to->y}; + RasterFP_DrawLine(aRasterFP, aRasterFP->last_point, tp); + return 0; +} + +void +RasterFP_DrawLine( RasterFP* aRasterFP, RasterFP_Point aP0, RasterFP_Point aP1 ) +{ + assert( aRasterFP ); + if ( aP0.m_y == aP1.m_y ) + return; + + aP0.m_x -= aRasterFP->m_origin_x; + aP0.m_y -= aRasterFP->m_origin_y; + aP1.m_x -= aRasterFP->m_origin_x; + aP1.m_y -= aRasterFP->m_origin_y; + + float dir; + if ( aP0.m_y < aP1.m_y ) + dir = 1; + else + { + dir = -1; + RasterFP_Point temp = aP0; + aP0 = aP1; + aP1 = temp; + } + + // Clip to the height. + if ( aP0.m_y >= aRasterFP->m_h || aP1.m_y <= 0 ) + return; + + float dxdy = ( aP1.m_x - aP0.m_x ) / ( aP1.m_y - aP0.m_y ); + if ( aP0.m_y < 0 ) + { + aP0.m_x -= aP0.m_y * dxdy; + aP0.m_y = 0; + } + if ( aP1.m_y > aRasterFP->m_h ) + { + aP1.m_x -= ( aP1.m_y - aRasterFP->m_h ) * dxdy; + aP1.m_y = (float)aRasterFP->m_h; + } + + /** + Handle parts of the line outside the clip rectangle by + snapping them to the left or right edge, or, if they intersect the clip area, + by recursive calls. + */ + RasterFP_Point intersect = { 0, 0 }; + int recursive = 0; + if ( aP0.m_x >= aRasterFP->m_w && aP1.m_x >= aRasterFP->m_w ) + { + aP0.m_x = aP1.m_x = (float)aRasterFP->m_w; + dxdy = 0; + } + else if ( aP0.m_x <= 0 && aP1.m_x <= 0 ) + { + aP0.m_x = aP1.m_x = 0; + dxdy = 0; + } + else if ( ( aP0.m_x < 0 ) != ( aP1.m_x < 0 ) ) + { + intersect.m_x = 0; + intersect.m_y = aP1.m_y - aP1.m_x / dxdy; + recursive = 1; + } + else if ( ( aP0.m_x > aRasterFP->m_w ) != ( aP1.m_x > aRasterFP->m_w ) ) + { + intersect.m_x = (float)aRasterFP->m_w; + intersect.m_y = aP0.m_y + ( aRasterFP->m_w - aP0.m_x ) / dxdy; + recursive = 1; + } + if ( recursive ) + { + aP0.m_x += aRasterFP->m_origin_x; + aP0.m_y += aRasterFP->m_origin_y; + aP1.m_x += aRasterFP->m_origin_x; + aP1.m_y += aRasterFP->m_origin_y; + intersect.m_x += aRasterFP->m_origin_x; + intersect.m_y += aRasterFP->m_origin_y; + if ( dir == 1 ) + { + RasterFP_DrawLine( aRasterFP, aP0, intersect ); + RasterFP_DrawLine( aRasterFP, intersect, aP1 ); + } + else + { + RasterFP_DrawLine( aRasterFP, aP1, intersect ); + RasterFP_DrawLine( aRasterFP, intersect, aP0 ); + } + return; + } + + float x = aP0.m_x; + int y0 = (int)aP0.m_y; + int y_limit = (int)ceil( aP1.m_y ); + float* m_a = aRasterFP->m_a; + for ( int y = y0; y < y_limit; y++ ) + { + int linestart = y * aRasterFP->m_w; + float dy = fmin( y + 1.0f, aP1.m_y ) - fmax( (float)y, aP0.m_y ); + float xnext = x + dxdy * dy; + float d = dy * dir; + + float x0, x1; + if ( x < xnext ) + { + x0 = x; + x1 = xnext; + } + else + { + x0 = xnext; + x1 = x; + } + + /* + It's possible for x0 to be negative on the last scanline because of + floating-point inaccuracy That would cause an out-of-bounds array access at + index -1. + */ + float x0floor = x0 <= 0.0f ? 0.0f : (float)floor( x0 ); + + int x0i = (int)x0floor; + float x1ceil = (float)ceil( x1 ); + int x1i = (int)x1ceil; + if ( x1i <= x0i + 1 ) + { + float xmf = 0.5f * ( x + xnext ) - x0floor; + m_a[linestart + x0i] += d - d * xmf; + m_a[linestart + ( x0i + 1 )] += d * xmf; + } + else + { + float s = 1.0f / ( x1 - x0 ); + float x0f = x0 - x0floor; + float a0 = 0.5f * s * ( 1.0f - x0f ) * ( 1.0f - x0f ); + float x1f = x1 - x1ceil + 1.0f; + float am = 0.5f * s * x1f * x1f; + m_a[linestart + x0i] += d * a0; + if ( x1i == x0i + 2 ) + m_a[linestart + ( x0i + 1 )] += d * ( 1.0f - a0 - am ); + else + { + float a1 = s * ( 1.5f - x0f ); + m_a[linestart + ( x0i + 1 )] += d * ( a1 - a0 ); + for ( int xi = x0i + 2; xi < x1i - 1; xi++ ) + m_a[linestart + xi] += d * s; + float a2 = a1 + ( x1i - x0i - 3 ) * s; + m_a[linestart + ( x1i - 1 )] += d * ( 1.0f - a2 - am ); + } + m_a[linestart + x1i] += d * am; + } + x = xnext; + } +} + + + + + +static int +dense_conic_to( const FT_Vector* control, + const FT_Vector* to, + RasterFP* aRasterFP) +{ + RasterFP_Point controlP = {control->x, control->y}; + RasterFP_Point toP = {to->x, to->y}; + RasterFP_DrawQuadratic( aRasterFP, aRasterFP->last_point, controlP, toP ); + return 0; +} + +void +RasterFP_DrawQuadratic( RasterFP* aRasterFP, + RasterFP_Point aP0, + RasterFP_Point aP1, + RasterFP_Point aP2 ) +{ + assert( aRasterFP ); + + /* + Calculate devsq as the square of four times the + distance from the control point to the midpoint of the curve. + This is the place at which the curve is furthest from the + line joining the control points. + + 4 x point on curve = p0 + 2p1 + p2 + 4 x midpoint = 4p1 + + The division by four is omitted to save time. + */ + float devx = aP0.m_x - aP1.m_x - aP1.m_x + aP2.m_x; + float devy = aP0.m_y - aP1.m_y - aP1.m_y + aP2.m_y; + float devsq = devx * devx + devy * devy; + + if ( devsq < 0.333f ) + { + RasterFP_DrawLine( aRasterFP, aP0, aP2 ); + return; + } + + /* + According to Raph Levien, the reason for the subdivision by n (instead of + recursive division by the Casteljau system) is that "I expect the flatness + computation to be semi-expensive (it's done once rather than on each potential + subdivision) and also because you'll often get fewer subdivisions. Taking a + circular arc as a simplifying assumption, where I get n, a recursive approach + would get 2^ceil(lg n), which, if I haven't made any horrible mistakes, is + expected to be 33% more in the limit". + */ + + const float tol = 3.0f; + int n = (int)floor( sqrt( sqrt( tol * devsq ) ) ); + RasterFP_Point p = aP0; + float nrecip = 1.0f / ( n + 1.0f ); + float t = 0.0f; + for ( int i = 0; i < n; i++ ) + { + t += nrecip; + RasterFP_Point next = Lerp( t, Lerp( t, aP0, aP1 ), Lerp( t, aP1, aP2 ) ); + RasterFP_DrawLine( aRasterFP, p, next ); + p = next; + } + + RasterFP_DrawLine( aRasterFP, p, aP2 ); +} + + + + + +static int +dense_cubic_to( const FT_Vector* control1, + const FT_Vector* control2, + const FT_Vector* to, + RasterFP* aRasterFP ) +{ + + RasterFP_Point ap1 = {control1->x, control1->y}; + RasterFP_Point ap2 = {control2->x, control2->y}; + RasterFP_Point ap3 = {to->x, to->y}; + + + RasterFP_DrawCubic( aRasterFP, aRasterFP->last_point, ap1, ap2, ap3 ); + return 0; +} + +void +RasterFP_DrawCubic( RasterFP* aRasterFP, + RasterFP_Point aP0, + RasterFP_Point aP1, + RasterFP_Point aP2, + RasterFP_Point aP3 ) +{ + assert( aRasterFP ); + + float devx = aP0.m_x - aP1.m_x - aP1.m_x + aP2.m_x; + float devy = aP0.m_y - aP1.m_y - aP1.m_y + aP2.m_y; + float devsq0 = devx * devx + devy * devy; + devx = aP1.m_x - aP2.m_x - aP2.m_x + aP3.m_x; + devy = aP1.m_y - aP2.m_y - aP2.m_y + aP3.m_y; + float devsq1 = devx * devx + devy * devy; + float devsq = fmax( devsq0, devsq1 ); + + if ( devsq < 0.333f ) + { + RasterFP_DrawLine( aRasterFP, aP0, aP3 ); + return; + } + + const float tol = 3.0f; + int n = (int)floor( sqrt( sqrt( tol * devsq ) ) ); + RasterFP_Point p = aP0; + float nrecip = 1.0f / ( n + 1.0f ); + float t = 0.0f; + for ( int i = 0; i < n; i++ ) + { + t += nrecip; + RasterFP_Point a = Lerp( t, Lerp( t, aP0, aP1 ), Lerp( t, aP1, aP2 ) ); + RasterFP_Point b = Lerp( t, Lerp( t, aP1, aP2 ), Lerp( t, aP2, aP3 ) ); + RasterFP_Point next = Lerp( t, a, b ); + RasterFP_DrawLine( aRasterFP, p, next ); + p = next; + } + + RasterFP_DrawLine( aRasterFP, p, aP3 ); +} + + + + +static int +dense_raster_new( FT_Memory memory, dense_PRaster* araster ) +{ + FT_Error error; + dense_PRaster raster; + + if ( !FT_NEW( raster ) ) + raster->memory = memory; + + *araster = raster; + + return error; +} + +static void +dense_raster_done( FT_Raster raster ) +{ + FT_Memory memory = (FT_Memory)( (dense_PRaster)raster )->memory; + + FT_FREE( raster ); +} + + +static void +dense_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 +dense_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_OUTLINE_FUNCS( + func_interface, + + (FT_Outline_MoveTo_Func)dense_move_to, /* move_to */ + (FT_Outline_LineTo_Func)dense_line_to, /* line_to */ + (FT_Outline_ConicTo_Func)dense_conic_to, /* conic_to */ + (FT_Outline_CubicTo_Func)dense_cubic_to, /* cubic_to */ + + 0, /* shift */ + 0 /* delta */ +) + + + + + + + + +static int +gray_convert_glyph_inner( RAS_ARG, int continued ) +{ + int error; + + if ( ft_setjmp( ras.jump_buffer ) == 0 ) + { + if ( continued ) + FT_Trace_Disable(); + error = FT_Outline_Decompose( &ras.outline, &func_interface, &ras ); + if ( continued ) + FT_Trace_Enable(); + + FT_TRACE7( ( "band [%d..%d]: %ld cell%s remaining/\n", ras.min_ey, + ras.max_ey, ras.cell_null - ras.cell_free, + ras.cell_null - ras.cell_free == 1 ? "" : "s" ) ); + } + else + { + error = FT_THROW( Raster_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 ) +{ + const TCoord yMin = ras.min_ey; + const TCoord yMax = ras.max_ey; + + TCell buffer[FT_MAX_GRAY_POOL]; + size_t height = (size_t)( yMax - yMin ); + size_t n = FT_MAX_GRAY_POOL / 8; + TCoord y; + TCoord bands[32]; /* enough to accommodate bisections */ + TCoord* band; + + int continued = 0; + + /* Initialize the null cell at the end of the poll. */ + ras.cell_null = buffer + FT_MAX_GRAY_POOL - 1; + ras.cell_null->x = CELL_MAX_X_VALUE; + ras.cell_null->area = 0; + ras.cell_null->cover = 0; + ras.cell_null->next = NULL; + + /* set up vertical bands */ + ras.ycells = (PCell*)buffer; + + if ( height > n ) + { + /* two divisions rounded up */ + n = ( height + n - 1 ) / n; + height = ( height + n - 1 ) / n; + } + + for ( y = yMin; y < yMax; ) + { + ras.min_ey = y; + y += height; + ras.max_ey = FT_MIN( y, yMax ); + + band = bands; + band[1] = ras.min_ey; + band[0] = ras.max_ey; + + do + { + TCoord width = band[0] - band[1]; + TCoord w; + int error; + + for ( w = 0; w < width; ++w ) + ras.ycells[w] = ras.cell_null; + + /* memory management: skip ycells */ + n = ( width * sizeof( PCell ) + sizeof( TCell ) - 1 ) / sizeof( TCell ); + + ras.cell_free = buffer + n; + ras.cell = ras.cell_null; + ras.min_ey = band[1]; + ras.max_ey = band[0]; + ras.count_ey = width; + + error = gray_convert_glyph_inner( RAS_VAR, continued ); + continued = 1; + + if ( !error ) + { + if ( ras.render_span ) /* for FT_RASTER_FLAG_DIRECT only */ + gray_sweep_direct( RAS_VAR ); + else + gray_sweep( RAS_VAR ); + band--; + continue; + } + else if ( error != Smooth_Err_Raster_Overflow ) + return error; + + /* render pool overflow; we will reduce the render band by half */ + width >>= 1; + + /* this should never happen even with tiny rendering pool */ + if ( width == 0 ) + { + FT_TRACE7( ( "gray_convert_glyph: rotten glyph\n" ) ); + return FT_THROW( Raster_Overflow ); + } + + band++; + band[1] = band[0]; + band[0] += width; + } while ( band >= bands ); + } + + return Smooth_Err_Ok; +} + +static int +dense_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; + +#ifndef FT_STATIC_RASTER + gray_TWorker worker[1]; +#endif + + if ( !raster ) + return FT_THROW( Invalid_Argument ); + + /* this version does not support monochrome rendering */ + if ( !( params->flags & FT_RASTER_FLAG_AA ) ) + return FT_THROW( Cannot_Render_Glyph ); + + if ( !outline ) + return FT_THROW( Invalid_Outline ); + + /* return immediately if the outline is empty */ + if ( outline->n_points == 0 || outline->n_contours <= 0 ) + return Smooth_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 ); + + ras.outline = *outline; + + if ( params->flags & FT_RASTER_FLAG_DIRECT ) + { + if ( !params->gray_spans ) + return Smooth_Err_Ok; + + ras.render_span = (FT_Raster_Span_Func)params->gray_spans; + ras.render_span_data = params->user; + + ras.min_ex = params->clip_box.xMin; + ras.min_ey = params->clip_box.yMin; + ras.max_ex = params->clip_box.xMax; + ras.max_ey = params->clip_box.yMax; + } + else + { + /* if direct mode is not set, we must have a target bitmap */ + if ( !target_map ) + return FT_THROW( Invalid_Argument ); + + /* nothing to do */ + if ( !target_map->width || !target_map->rows ) + return Smooth_Err_Ok; + + if ( !target_map->buffer ) + return FT_THROW( Invalid_Argument ); + + if ( target_map->pitch < 0 ) + ras.target.origin = target_map->buffer; + else + ras.target.origin = + target_map->buffer + + ( target_map->rows - 1 ) * (unsigned int)target_map->pitch; + + ras.target.pitch = target_map->pitch; + + ras.render_span = (FT_Raster_Span_Func)NULL; + ras.render_span_data = NULL; + + ras.min_ex = 0; + ras.min_ey = 0; + ras.max_ex = (FT_Pos)target_map->width; + ras.max_ey = (FT_Pos)target_map->rows; + } + + /* exit if nothing to do */ + if ( ras.max_ex <= ras.min_ex || ras.max_ey <= ras.min_ey ) + return Smooth_Err_Ok; + + return gray_convert_glyph( RAS_VAR ); +} + + + + + + + + + + + + +FT_DEFINE_RASTER_FUNCS( + ft_dense_raster, + + FT_GLYPH_FORMAT_OUTLINE, + + (FT_Raster_New_Func)dense_raster_new, /* raster_new */ + (FT_Raster_Reset_Func)dense_raster_reset, /* raster_reset */ + (FT_Raster_Set_Mode_Func)dense_raster_set_mode, /* raster_set_mode */ + (FT_Raster_Render_Func)dense_raster_render, /* raster_render */ + (FT_Raster_Done_Func)dense_raster_done /* raster_done */ +) + /* END */ \ No newline at end of file diff --git a/src/dense/ftdense.h b/src/dense/ftdense.h index 9321aa3db..bf208d146 100644 --- a/src/dense/ftdense.h +++ b/src/dense/ftdense.h @@ -2,6 +2,148 @@ #ifndef FTDENSE_H_ #define FTDENSE_H_ + +#include +#include FT_CONFIG_CONFIG_H +#include + + +FT_BEGIN_HEADER + +/************************************************************************** + * + * @struct: + * SDF_Raster_Params + * + * @description: + * This struct must be passed to the raster render function + * @FT_Raster_RenderFunc instead of @FT_Raster_Params because the + * rasterizer requires some additional information to render properly. + * + * @fields: + * root :: + * The native raster parameters structure. + * + * spread :: + * This is an essential parameter/property required by the renderer. + * `spread` defines the maximum unsigned value that is present in the + * final SDF output. For the default value check file + * `ftsdfcommon.h`. + * + * flip_sign :: + * By default positive values indicate positions inside of contours, + * i.e., filled by a contour. If this property is true then that + * output will be the opposite of the default, i.e., negative values + * indicate positions inside of contours. + * + * flip_y :: + * Setting this parameter to true maked the output image flipped + * along the y-axis. + * + * overlaps :: + * Set this to true to generate SDF for glyphs having overlapping + * contours. The overlapping support is limited to glyphs that do not + * have self-intersecting contours. Also, removing overlaps require a + * considerable amount of extra memory; additionally, it will not work + * if generating SDF from bitmap. + * + * @note: + * All properties are valid for both the 'sdf' and 'bsdf' renderers; the + * exception is `overlaps`, which gets ignored by the 'bsdf' renderer. + * + */ +typedef struct DENSE_Raster_Params_ +{ + FT_Raster_Params root; + FT_UInt spread; + FT_Bool flip_sign; + FT_Bool flip_y; + FT_Bool overlaps; + +} DENSE_Raster_Params; + +/* rasterizer to convert outline to SDF */ +FT_EXPORT_VAR( const FT_Raster_Funcs ) ft_dense_raster; + +/** +RASTER_FP + +A floating-point anti-aliasing renderer. +Graham Asher, August 2016. + +Most of this code is derived from Raph Levien's font-rs code in the Rust +language, which is licensed under the Apache License, version 2.0. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef struct + { + float m_x; + float m_y; + } RasterFP_Point; + + typedef struct + { + /** The array used to store signed area differences. */ + float* m_a; + /** The number of elements in m_a. */ + int m_a_size; + /** The width of the current raster in pixels. */ + int m_w; + /** The height of the current raster in pixels. */ + int m_h; + /** The x origin of the raster. */ + int m_origin_x; + /** The y origin of the raster. */ + int m_origin_y; + + RasterFP_Point last_point; + } RasterFP; + + void RasterFP_Create( RasterFP* aRasterFP ); + void RasterFP_StartRasterizing( RasterFP* aRasterFP, + int aOriginX, + int aOriginY, + int aWidth, + int aHeight ); + void RasterFP_Destroy( RasterFP* aRasterFP ); + void RasterFP_DrawLine( RasterFP* aRasterFP, + RasterFP_Point aP0, + RasterFP_Point aP1 ); + void RasterFP_DrawQuadratic( RasterFP* aRasterFP, + RasterFP_Point aP0, + RasterFP_Point aP1, + RasterFP_Point aP2 ); + void RasterFP_DrawCubic( RasterFP* aRasterFP, + RasterFP_Point aP0, + RasterFP_Point aP1, + RasterFP_Point aP2, + RasterFP_Point aP3 ); + void RasterFP_GetBitmap( RasterFP* aRasterFP, unsigned char* aBitmap ); + +#ifdef __cplusplus +} // extern "C" +#endif + + +FT_END_HEADER + #endif /* FTDENSE_H_ */ /* END */ diff --git a/src/dense/ftdenserend.c b/src/dense/ftdenserend.c index a7ca8ca59..75c5771df 100644 --- a/src/dense/ftdenserend.c +++ b/src/dense/ftdenserend.c @@ -1,3 +1,360 @@ /** The 'dense' renderer */ +#include "ftdenserend.h" +#include +#include +#include +#include +#include +#include "ftdense.h" + +#include "ftdenseerrs.h" + +/************************************************************************** + * + * 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 dense + +/************************************************************************** + * + * macros and default property values + * + */ +#define DENSE_RENDERER( rend ) ( (DENSE_Renderer)rend ) + +/************************************************************************** + * + * for setting properties + * + */ + +/* property setter function */ +static FT_Error +dense_property_set( FT_Module module, + const char* property_name, + const void* value, + FT_Bool value_is_string ) +{ + FT_Error error = FT_Err_Ok; + DENSE_Renderer render = DENSE_RENDERER( FT_RENDERER( module ) ); + + FT_UNUSED( value_is_string ); + + if ( ft_strcmp( property_name, "spread" ) == 0 ) + { + // FT_Int val = *(const FT_Int*)value; + // if ( val > MAX_SPREAD || val < MIN_SPREAD ) + // { + // FT_TRACE0( + // ( "[sdf] dense_property_set:" + // " the `spread' property can have a value\n" ) ); + // FT_TRACE0( + // ( " " + // " within range [%d, %d] (value provided: %d)\n", + // MIN_SPREAD, MAX_SPREAD, val ) ); + // + // error = FT_THROW( Invalid_Argument ); + // goto Exit; + // } + // + // render->spread = (FT_UInt)val; + // FT_TRACE7( + // ( "[sdf] dense_property_set:" + // " updated property `spread' to %d\n", + // val ) ); + } + + else if ( ft_strcmp( property_name, "flip_sign" ) == 0 ) + { + ; + } + + else + { + FT_TRACE0( + ( "[dense] dense_property_set:" + " missing property `%s'\n", + property_name ) ); + error = FT_THROW( Missing_Property ); + } + +Exit: + return error; +} + +/* property getter function */ +static FT_Error +dense_property_get( FT_Module module, const char* property_name, void* value ) +{ + FT_Error error = FT_Err_Ok; + DENSE_Renderer render = DENSE_RENDERER( FT_RENDERER( module ) ); + + if ( ft_strcmp( property_name, "spread" ) == 0 ) + { + // FT_Int* val = (FT_Int*)value; + + // *val = render->spread; + } + + else if ( ft_strcmp( property_name, "flip_sign" ) == 0 ) + { + ; + } + + else + { + FT_TRACE0( + ( "[dense] dense_property_get:" + " missing property `%s'\n", + property_name ) ); + error = FT_THROW( Missing_Property ); + } + + return error; +} + +FT_DEFINE_SERVICE_PROPERTIESREC( + dense_service_properties, + + (FT_Properties_SetFunc)dense_property_set, /* set_property */ + (FT_Properties_GetFunc)dense_property_get ) /* get_property */ + +FT_DEFINE_SERVICEDESCREC1( dense_services, + + FT_SERVICE_ID_PROPERTIES, + &dense_service_properties ) + +static FT_Module_Interface +ft_dense_requester( FT_Renderer render, const char* module_interface ) +{ + FT_UNUSED( render ); + + return ft_service_list_lookup( dense_services, module_interface ); +} + +/************************************************************************** + * + * interface functions + * + */ + +static FT_Error +ft_dense_init( FT_Renderer render ) +{ + DENSE_Renderer dense_render = DENSE_RENDERER( render ); + + // dense_render->spread = 0; + // dense_render->flip_sign = 0; + // dense_render->flip_y = 0; + // dense_render->overlaps = 0; + + return FT_Err_Ok; +} + +static void +ft_dense_done( FT_Renderer render ) +{ + FT_UNUSED( render ); +} + +/* generate signed distance field from a glyph's slot image */ +static FT_Error +ft_dense_render( FT_Renderer module, + FT_GlyphSlot slot, + FT_Render_Mode mode, + const FT_Vector* origin ) +{ + FT_Error error = FT_Err_Ok; + FT_Outline* outline = &slot->outline; + FT_Bitmap* bitmap = &slot->bitmap; + FT_Memory memory = NULL; + FT_Renderer render = NULL; + + FT_Pos x_shift = 0; + FT_Pos y_shift = 0; + + FT_Pos x_pad = 0; + FT_Pos y_pad = 0; + + DENSE_Raster_Params params; + DENSE_Renderer dense_module = DENSE_RENDERER( module ); + + render = &dense_module->root; + memory = render->root.memory; + + /* check whether slot format is correct before rendering */ + if ( slot->format != render->glyph_format ) + { + error = FT_THROW( Invalid_Glyph_Format ); + goto Exit; + } + + /* check whether render mode is correct */ + if ( mode != FT_RENDER_MODE_NORMAL ) + { + FT_ERROR( + ( "[dense] ft_dense_render:" + " sdf module only render when" + " using `FT_RENDER_MODE_NORMAL'\n" ) ); + error = FT_THROW( Cannot_Render_Glyph ); + goto Exit; + } + + /* deallocate the previously allocated bitmap */ + if ( slot->internal->flags & FT_GLYPH_OWN_BITMAP ) + { + FT_FREE( bitmap->buffer ); + slot->internal->flags &= ~FT_GLYPH_OWN_BITMAP; + } + + /* preset the bitmap using the glyph's outline; */ + /* the sdf bitmap is similar to an antialiased bitmap */ + /* with a slightly bigger size and different pixel mode */ + if ( ft_glyphslot_preset_bitmap( slot, FT_RENDER_MODE_NORMAL, origin ) ) + { + error = FT_THROW( Raster_Overflow ); + goto Exit; + } + + if ( !bitmap->rows || !bitmap->pitch ) + goto Exit; + + /* the padding will simply be equal to the `spread' */ + x_pad = dense_module->spread; + y_pad = dense_module->spread; + + /* apply the padding; will be in all the directions */ + bitmap->rows += y_pad * 2; + bitmap->width += x_pad * 2; + + /* ignore the pitch, pixel mode and set custom */ + bitmap->pixel_mode = FT_PIXEL_MODE_GRAY; + bitmap->pitch = bitmap->width; + bitmap->num_grays = 255; + + /* allocate new buffer */ + if ( FT_ALLOC_MULT( bitmap->buffer, bitmap->rows, bitmap->pitch ) ) + goto Exit; + + slot->internal->flags |= FT_GLYPH_OWN_BITMAP; + + x_shift = 64 * -( slot->bitmap_left - x_pad ); + y_shift = 64 * -( slot->bitmap_top + y_pad ); + y_shift += 64 * (FT_Int)bitmap->rows; + + if ( origin ) + { + x_shift += origin->x; + y_shift += origin->y; + } + + /* translate outline to render it into the bitmap */ + if ( x_shift || y_shift ) + FT_Outline_Translate( outline, x_shift, y_shift ); + + /* set up parameters */ + params.root.target = bitmap; + params.root.source = outline; + params.root.flags = FT_RASTER_FLAG_DEFAULT; + params.spread = dense_module->spread; + params.flip_sign = dense_module->flip_sign; + params.flip_y = dense_module->flip_y; + params.overlaps = dense_module->overlaps; + + /* render the outline */ + error = + render->raster_render( render->raster, (const FT_Raster_Params*)¶ms ); + +Exit: + if ( !error ) + { + /* the glyph is successfully rendered to a bitmap */ + slot->format = FT_GLYPH_FORMAT_BITMAP; + } + else if ( slot->internal->flags & FT_GLYPH_OWN_BITMAP ) + { + FT_FREE( bitmap->buffer ); + slot->internal->flags &= ~FT_GLYPH_OWN_BITMAP; + } + + if ( x_shift || y_shift ) + FT_Outline_Translate( outline, -x_shift, -y_shift ); + + return error; +} + +/* transform the glyph using matrix and/or delta */ +static FT_Error +ft_dense_transform( FT_Renderer render, + FT_GlyphSlot slot, + const FT_Matrix* matrix, + const FT_Vector* delta ) +{ + FT_Error error = FT_Err_Ok; + + if ( slot->format != render->glyph_format ) + { + error = FT_THROW( Invalid_Argument ); + goto Exit; + } + + if ( matrix ) + FT_Outline_Transform( &slot->outline, matrix ); + + if ( delta ) + FT_Outline_Translate( &slot->outline, delta->x, delta->y ); + +Exit: + return error; +} + +/* return the control box of a glyph's outline */ +static void +ft_dense_get_cbox( FT_Renderer render, FT_GlyphSlot slot, FT_BBox* cbox ) +{ + FT_ZERO( cbox ); + + if ( slot->format == render->glyph_format ) + FT_Outline_Get_CBox( &slot->outline, cbox ); +} + +/* set render specific modes or attributes */ +static FT_Error +ft_dense_set_mode( FT_Renderer render, FT_ULong mode_tag, FT_Pointer data ) +{ + /* pass it to the rasterizer */ + return render->clazz->raster_class->raster_set_mode( render->raster, mode_tag, + data ); +} + +FT_DEFINE_RENDERER( + ft_dense_renderer_class, + + FT_MODULE_RENDERER, + sizeof( DENSE_Renderer_Module ), + + "dense", + 0x10000L, + 0x20000L, + + NULL, + + (FT_Module_Constructor)ft_dense_init, + (FT_Module_Destructor)ft_dense_done, + (FT_Module_Requester)ft_dense_requester, + + FT_GLYPH_FORMAT_OUTLINE, + + (FT_Renderer_RenderFunc)ft_dense_render, /* render_glyph */ + (FT_Renderer_TransformFunc)ft_dense_transform, /* transform_glyph */ + (FT_Renderer_GetCBoxFunc)ft_dense_get_cbox, /* get_glyph_cbox */ + (FT_Renderer_SetModeFunc)ft_dense_set_mode, /* set_mode */ + + (FT_Raster_Funcs*)&ft_dense_raster /* raster_class */ +) + /* END */ \ No newline at end of file diff --git a/src/dense/ftdenserend.h b/src/dense/ftdenserend.h index 409f7fe8c..7f28eae6d 100644 --- a/src/dense/ftdenserend.h +++ b/src/dense/ftdenserend.h @@ -2,6 +2,79 @@ #ifndef FTDENSEREND_H_ #define FTDENSEREND_H_ + +#include +#include +#include + +FT_BEGIN_HEADER + +/************************************************************************** + * + * @struct: + * DENSE_Renderer_Module + * + * @description: + * This struct extends the native renderer struct `FT_RendererRec`. It + * is basically used to store various parameters required by the + * renderer and some additional parameters that can be used to tweak the + * output of the renderer. + * + * @fields: + * root :: + * The native rendere struct. + * + * spread :: + * This is an essential parameter/property required by the renderer. + * `spread` defines the maximum unsigned value that is present in the + * final SDF output. For the default value check file + * `ftsdfcommon.h`. + * + * flip_sign :: + * By default positive values indicate positions inside of contours, + * i.e., filled by a contour. If this property is true then that + * output will be the opposite of the default, i.e., negative values + * indicate positions inside of contours. + * + * flip_y :: + * Setting this parameter to true makes the output image flipped + * along the y-axis. + * + * overlaps :: + * Set this to true to generate SDF for glyphs having overlapping + * contours. The overlapping support is limited to glyphs that do not + * have self-intersecting contours. Also, removing overlaps require a + * considerable amount of extra memory; additionally, it will not work + * if generating SDF from bitmap. + * + * @note: + * All properties except `overlaps` are valid for both the 'sdf' and + * 'bsdf' renderers. + * + */ +typedef struct DENSE_Renderer_Module_ +{ + FT_RendererRec root; + FT_UInt spread; + FT_Bool flip_sign; + FT_Bool flip_y; + FT_Bool overlaps; + +} DENSE_Renderer_Module, *DENSE_Renderer; + +/************************************************************************** + * + * @renderer: + * ft_sdf_renderer_class + * + * @description: + * Renderer to convert @FT_Outline to signed distance fields. + * + */ +FT_DECLARE_RENDERER( ft_dense_renderer_class ) + +FT_END_HEADER + #endif /* FTDENSEREND_H_ */ /* END */