[sdf] Add utility functions for contours.
* src/sdf/ftsdf.c (get_control_box, get_contour_orientation): New functions. (split_conic, split_cubic, split_sdf_conic, split_sdf_cubic, split_sdf_shape): New functions.
This commit is contained in:
parent
2de1b5630d
commit
cd4138458a
|
@ -1,3 +1,12 @@
|
||||||
|
2020-08-18 Anuj Verma <anujv@iitbhilai.ac.in>
|
||||||
|
|
||||||
|
[sdf] Add utility functions for contours.
|
||||||
|
|
||||||
|
* src/sdf/ftsdf.c (get_control_box, get_contour_orientation): New
|
||||||
|
functions.
|
||||||
|
(split_conic, split_cubic, split_sdf_conic, split_sdf_cubic,
|
||||||
|
split_sdf_shape): New functions.
|
||||||
|
|
||||||
2020-08-17 Anuj Verma <anujv@iitbhilai.ac.in>
|
2020-08-17 Anuj Verma <anujv@iitbhilai.ac.in>
|
||||||
|
|
||||||
[sdf] Add functions to decompose `FT_Outline`.
|
[sdf] Add functions to decompose `FT_Outline`.
|
||||||
|
|
470
src/sdf/ftsdf.c
470
src/sdf/ftsdf.c
|
@ -842,4 +842,474 @@
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**************************************************************************
|
||||||
|
*
|
||||||
|
* utility functions
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Return the control box of a edge. The control box is a rectangle */
|
||||||
|
/* in which all the control points can fit tightly. */
|
||||||
|
static FT_CBox
|
||||||
|
get_control_box( SDF_Edge edge )
|
||||||
|
{
|
||||||
|
FT_CBox cbox;
|
||||||
|
FT_Bool is_set = 0;
|
||||||
|
|
||||||
|
|
||||||
|
switch ( edge.edge_type )
|
||||||
|
{
|
||||||
|
case SDF_EDGE_CUBIC:
|
||||||
|
cbox.xMin = edge.control_b.x;
|
||||||
|
cbox.xMax = edge.control_b.x;
|
||||||
|
cbox.yMin = edge.control_b.y;
|
||||||
|
cbox.yMax = edge.control_b.y;
|
||||||
|
|
||||||
|
is_set = 1;
|
||||||
|
/* fall through */
|
||||||
|
|
||||||
|
case SDF_EDGE_CONIC:
|
||||||
|
if ( is_set )
|
||||||
|
{
|
||||||
|
cbox.xMin = edge.control_a.x < cbox.xMin
|
||||||
|
? edge.control_a.x
|
||||||
|
: cbox.xMin;
|
||||||
|
cbox.xMax = edge.control_a.x > cbox.xMax
|
||||||
|
? edge.control_a.x
|
||||||
|
: cbox.xMax;
|
||||||
|
|
||||||
|
cbox.yMin = edge.control_a.y < cbox.yMin
|
||||||
|
? edge.control_a.y
|
||||||
|
: cbox.yMin;
|
||||||
|
cbox.yMax = edge.control_a.y > cbox.yMax
|
||||||
|
? edge.control_a.y
|
||||||
|
: cbox.yMax;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cbox.xMin = edge.control_a.x;
|
||||||
|
cbox.xMax = edge.control_a.x;
|
||||||
|
cbox.yMin = edge.control_a.y;
|
||||||
|
cbox.yMax = edge.control_a.y;
|
||||||
|
|
||||||
|
is_set = 1;
|
||||||
|
}
|
||||||
|
/* fall through */
|
||||||
|
|
||||||
|
case SDF_EDGE_LINE:
|
||||||
|
if ( is_set )
|
||||||
|
{
|
||||||
|
cbox.xMin = edge.start_pos.x < cbox.xMin
|
||||||
|
? edge.start_pos.x
|
||||||
|
: cbox.xMin;
|
||||||
|
cbox.xMax = edge.start_pos.x > cbox.xMax
|
||||||
|
? edge.start_pos.x
|
||||||
|
: cbox.xMax;
|
||||||
|
|
||||||
|
cbox.yMin = edge.start_pos.y < cbox.yMin
|
||||||
|
? edge.start_pos.y
|
||||||
|
: cbox.yMin;
|
||||||
|
cbox.yMax = edge.start_pos.y > cbox.yMax
|
||||||
|
? edge.start_pos.y
|
||||||
|
: cbox.yMax;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cbox.xMin = edge.start_pos.x;
|
||||||
|
cbox.xMax = edge.start_pos.x;
|
||||||
|
cbox.yMin = edge.start_pos.y;
|
||||||
|
cbox.yMax = edge.start_pos.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
cbox.xMin = edge.end_pos.x < cbox.xMin
|
||||||
|
? edge.end_pos.x
|
||||||
|
: cbox.xMin;
|
||||||
|
cbox.xMax = edge.end_pos.x > cbox.xMax
|
||||||
|
? edge.end_pos.x
|
||||||
|
: cbox.xMax;
|
||||||
|
|
||||||
|
cbox.yMin = edge.end_pos.y < cbox.yMin
|
||||||
|
? edge.end_pos.y
|
||||||
|
: cbox.yMin;
|
||||||
|
cbox.yMax = edge.end_pos.y > cbox.yMax
|
||||||
|
? edge.end_pos.y
|
||||||
|
: cbox.yMax;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Return orientation of a single contour. */
|
||||||
|
/* Note that the orientation is independent of the fill rule! */
|
||||||
|
/* So, for TTF a clockwise-oriented contour has to be filled */
|
||||||
|
/* and the opposite for OTF fonts. */
|
||||||
|
static SDF_Contour_Orientation
|
||||||
|
get_contour_orientation ( SDF_Contour* contour )
|
||||||
|
{
|
||||||
|
SDF_Edge* head = NULL;
|
||||||
|
FT_26D6 area = 0;
|
||||||
|
|
||||||
|
|
||||||
|
/* return none if invalid parameters */
|
||||||
|
if ( !contour || !contour->edges )
|
||||||
|
return SDF_ORIENTATION_NONE;
|
||||||
|
|
||||||
|
head = contour->edges;
|
||||||
|
|
||||||
|
/* Calculate the area of the control box for all edges. */
|
||||||
|
while ( head )
|
||||||
|
{
|
||||||
|
switch ( head->edge_type )
|
||||||
|
{
|
||||||
|
case SDF_EDGE_LINE:
|
||||||
|
area += MUL_26D6( ( head->end_pos.x - head->start_pos.x ),
|
||||||
|
( head->end_pos.y + head->start_pos.y ) );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDF_EDGE_CONIC:
|
||||||
|
area += MUL_26D6( head->control_a.x - head->start_pos.x,
|
||||||
|
head->control_a.y + head->start_pos.y );
|
||||||
|
area += MUL_26D6( head->end_pos.x - head->control_a.x,
|
||||||
|
head->end_pos.y + head->control_a.y );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDF_EDGE_CUBIC:
|
||||||
|
area += MUL_26D6( head->control_a.x - head->start_pos.x,
|
||||||
|
head->control_a.y + head->start_pos.y );
|
||||||
|
area += MUL_26D6( head->control_b.x - head->control_a.x,
|
||||||
|
head->control_b.y + head->control_a.y );
|
||||||
|
area += MUL_26D6( head->end_pos.x - head->control_b.x,
|
||||||
|
head->end_pos.y + head->control_b.y );
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return SDF_ORIENTATION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
head = head->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clockwise contours cover a positive area, and anti-clockwise */
|
||||||
|
/* contours cover a negative area. */
|
||||||
|
if ( area > 0 )
|
||||||
|
return SDF_ORIENTATION_CW;
|
||||||
|
else
|
||||||
|
return SDF_ORIENTATION_ACW;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* This function is exactly the same as the one */
|
||||||
|
/* in the smooth renderer. It splits a conic */
|
||||||
|
/* into two conics exactly half way at t = 0.5. */
|
||||||
|
static void
|
||||||
|
split_conic( FT_26D6_Vec* base )
|
||||||
|
{
|
||||||
|
FT_26D6 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 / 2;
|
||||||
|
base[2].x = ( a + b ) / 4;
|
||||||
|
base[1].x = a / 2;
|
||||||
|
|
||||||
|
base[4].y = base[2].y;
|
||||||
|
a = base[0].y + base[1].y;
|
||||||
|
b = base[1].y + base[2].y;
|
||||||
|
base[3].y = b / 2;
|
||||||
|
base[2].y = ( a + b ) / 4;
|
||||||
|
base[1].y = a / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* This function is exactly the same as the one */
|
||||||
|
/* in the smooth renderer. It splits a cubic */
|
||||||
|
/* into two cubics exactly half way at t = 0.5. */
|
||||||
|
static void
|
||||||
|
split_cubic( FT_26D6_Vec* base )
|
||||||
|
{
|
||||||
|
FT_26D6 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 / 2;
|
||||||
|
c += b;
|
||||||
|
base[4].x = c / 4;
|
||||||
|
base[1].x = a / 2;
|
||||||
|
a += b;
|
||||||
|
base[2].x = a / 4;
|
||||||
|
base[3].x = ( a + c ) / 8;
|
||||||
|
|
||||||
|
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 / 2;
|
||||||
|
c += b;
|
||||||
|
base[4].y = c / 4;
|
||||||
|
base[1].y = a / 2;
|
||||||
|
a += b;
|
||||||
|
base[2].y = a / 4;
|
||||||
|
base[3].y = ( a + c ) / 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Split a conic Bezier curve into a number of lines */
|
||||||
|
/* and add them to `out'. */
|
||||||
|
/* */
|
||||||
|
/* This function uses recursion; we thus need */
|
||||||
|
/* parameter `max_splits' for stopping. */
|
||||||
|
static FT_Error
|
||||||
|
split_sdf_conic( FT_Memory memory,
|
||||||
|
FT_26D6_Vec* control_points,
|
||||||
|
FT_Int max_splits,
|
||||||
|
SDF_Edge** out )
|
||||||
|
{
|
||||||
|
FT_Error error = FT_Err_Ok;
|
||||||
|
FT_26D6_Vec cpos[5];
|
||||||
|
SDF_Edge* left,* right;
|
||||||
|
|
||||||
|
|
||||||
|
if ( !memory || !out )
|
||||||
|
{
|
||||||
|
error = FT_THROW( Invalid_Argument );
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* split conic outline */
|
||||||
|
cpos[0] = control_points[0];
|
||||||
|
cpos[1] = control_points[1];
|
||||||
|
cpos[2] = control_points[2];
|
||||||
|
|
||||||
|
split_conic( cpos );
|
||||||
|
|
||||||
|
/* If max number of splits is done */
|
||||||
|
/* then stop and add the lines to */
|
||||||
|
/* the list. */
|
||||||
|
if ( max_splits <= 2 )
|
||||||
|
goto Append;
|
||||||
|
|
||||||
|
/* Otherwise keep splitting. */
|
||||||
|
FT_CALL( split_sdf_conic( memory, &cpos[0], max_splits / 2, out ) );
|
||||||
|
FT_CALL( split_sdf_conic( memory, &cpos[2], max_splits / 2, out ) );
|
||||||
|
|
||||||
|
/* [NOTE]: This is not an efficient way of */
|
||||||
|
/* splitting the curve. Check the deviation */
|
||||||
|
/* instead and stop if the deviation is less */
|
||||||
|
/* than a pixel. */
|
||||||
|
|
||||||
|
goto Exit;
|
||||||
|
|
||||||
|
Append:
|
||||||
|
/* Do allocation and add the lines to the list. */
|
||||||
|
|
||||||
|
FT_CALL( sdf_edge_new( memory, &left ) );
|
||||||
|
FT_CALL( sdf_edge_new( memory, &right ) );
|
||||||
|
|
||||||
|
left->start_pos = cpos[0];
|
||||||
|
left->end_pos = cpos[2];
|
||||||
|
left->edge_type = SDF_EDGE_LINE;
|
||||||
|
|
||||||
|
right->start_pos = cpos[2];
|
||||||
|
right->end_pos = cpos[4];
|
||||||
|
right->edge_type = SDF_EDGE_LINE;
|
||||||
|
|
||||||
|
left->next = right;
|
||||||
|
right->next = (*out);
|
||||||
|
*out = left;
|
||||||
|
|
||||||
|
Exit:
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Split a cubic Bezier curve into a number of lines */
|
||||||
|
/* and add them to `out`. */
|
||||||
|
/* */
|
||||||
|
/* This function uses recursion; we thus need */
|
||||||
|
/* parameter `max_splits' for stopping. */
|
||||||
|
static FT_Error
|
||||||
|
split_sdf_cubic( FT_Memory memory,
|
||||||
|
FT_26D6_Vec* control_points,
|
||||||
|
FT_Int max_splits,
|
||||||
|
SDF_Edge** out )
|
||||||
|
{
|
||||||
|
FT_Error error = FT_Err_Ok;
|
||||||
|
FT_26D6_Vec cpos[7];
|
||||||
|
SDF_Edge* left,* right;
|
||||||
|
|
||||||
|
|
||||||
|
if ( !memory || !out )
|
||||||
|
{
|
||||||
|
error = FT_THROW( Invalid_Argument );
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* split the conic */
|
||||||
|
cpos[0] = control_points[0];
|
||||||
|
cpos[1] = control_points[1];
|
||||||
|
cpos[2] = control_points[2];
|
||||||
|
cpos[3] = control_points[3];
|
||||||
|
|
||||||
|
split_cubic( cpos );
|
||||||
|
|
||||||
|
/* If max number of splits is done */
|
||||||
|
/* then stop and add the lines to */
|
||||||
|
/* the list. */
|
||||||
|
if ( max_splits <= 2 )
|
||||||
|
goto Append;
|
||||||
|
|
||||||
|
/* Otherwise keep splitting. */
|
||||||
|
FT_CALL( split_sdf_cubic( memory, &cpos[0], max_splits / 2, out ) );
|
||||||
|
FT_CALL( split_sdf_cubic( memory, &cpos[3], max_splits / 2, out ) );
|
||||||
|
|
||||||
|
/* [NOTE]: This is not an efficient way of */
|
||||||
|
/* splitting the curve. Check the deviation */
|
||||||
|
/* instead and stop if the deviation is less */
|
||||||
|
/* than a pixel. */
|
||||||
|
|
||||||
|
goto Exit;
|
||||||
|
|
||||||
|
Append:
|
||||||
|
/* Do allocation and add the lines to the list. */
|
||||||
|
|
||||||
|
FT_CALL( sdf_edge_new( memory, &left) );
|
||||||
|
FT_CALL( sdf_edge_new( memory, &right) );
|
||||||
|
|
||||||
|
left->start_pos = cpos[0];
|
||||||
|
left->end_pos = cpos[3];
|
||||||
|
left->edge_type = SDF_EDGE_LINE;
|
||||||
|
|
||||||
|
right->start_pos = cpos[3];
|
||||||
|
right->end_pos = cpos[6];
|
||||||
|
right->edge_type = SDF_EDGE_LINE;
|
||||||
|
|
||||||
|
left->next = right;
|
||||||
|
right->next = (*out);
|
||||||
|
*out = left;
|
||||||
|
|
||||||
|
Exit:
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Subdivide an entire shape into line segments */
|
||||||
|
/* such that it doesn't look visually different */
|
||||||
|
/* from the original curve. */
|
||||||
|
static FT_Error
|
||||||
|
split_sdf_shape( SDF_Shape* shape )
|
||||||
|
{
|
||||||
|
FT_Error error = FT_Err_Ok;
|
||||||
|
FT_Memory memory;
|
||||||
|
|
||||||
|
SDF_Contour* contours;
|
||||||
|
SDF_Contour* new_contours = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
if ( !shape || !shape->memory )
|
||||||
|
{
|
||||||
|
error = FT_THROW( Invalid_Argument );
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
contours = shape->contours;
|
||||||
|
memory = shape->memory;
|
||||||
|
|
||||||
|
/* for each contour */
|
||||||
|
while ( contours )
|
||||||
|
{
|
||||||
|
SDF_Edge* edges = contours->edges;
|
||||||
|
SDF_Edge* new_edges = NULL;
|
||||||
|
|
||||||
|
SDF_Contour* tempc;
|
||||||
|
|
||||||
|
|
||||||
|
/* for each edge */
|
||||||
|
while ( edges )
|
||||||
|
{
|
||||||
|
SDF_Edge* edge = edges;
|
||||||
|
SDF_Edge* temp;
|
||||||
|
|
||||||
|
switch ( edge->edge_type )
|
||||||
|
{
|
||||||
|
case SDF_EDGE_LINE:
|
||||||
|
/* Just create a duplicate edge in case */
|
||||||
|
/* it is a line. We can use the same edge. */
|
||||||
|
FT_CALL( sdf_edge_new( memory, &temp ) );
|
||||||
|
|
||||||
|
ft_memcpy( temp, edge, sizeof ( *edge ) );
|
||||||
|
|
||||||
|
temp->next = new_edges;
|
||||||
|
new_edges = temp;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDF_EDGE_CONIC:
|
||||||
|
/* Subdivide the curve and add it to the list. */
|
||||||
|
{
|
||||||
|
FT_26D6_Vec ctrls[3];
|
||||||
|
|
||||||
|
|
||||||
|
ctrls[0] = edge->start_pos;
|
||||||
|
ctrls[1] = edge->control_a;
|
||||||
|
ctrls[2] = edge->end_pos;
|
||||||
|
|
||||||
|
error = split_sdf_conic( memory, ctrls, 32, &new_edges );
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDF_EDGE_CUBIC:
|
||||||
|
/* Subdivide the curve and add it to the list. */
|
||||||
|
{
|
||||||
|
FT_26D6_Vec ctrls[4];
|
||||||
|
|
||||||
|
|
||||||
|
ctrls[0] = edge->start_pos;
|
||||||
|
ctrls[1] = edge->control_a;
|
||||||
|
ctrls[2] = edge->control_b;
|
||||||
|
ctrls[3] = edge->end_pos;
|
||||||
|
|
||||||
|
error = split_sdf_cubic( memory, ctrls, 32, &new_edges );
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
error = FT_THROW( Invalid_Argument );
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
edges = edges->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add to the contours list */
|
||||||
|
FT_CALL( sdf_contour_new( memory, &tempc ) );
|
||||||
|
|
||||||
|
tempc->next = new_contours;
|
||||||
|
tempc->edges = new_edges;
|
||||||
|
new_contours = tempc;
|
||||||
|
new_edges = NULL;
|
||||||
|
|
||||||
|
/* deallocate the contour */
|
||||||
|
tempc = contours;
|
||||||
|
contours = contours->next;
|
||||||
|
|
||||||
|
sdf_contour_done( memory, &tempc );
|
||||||
|
}
|
||||||
|
|
||||||
|
shape->contours = new_contours;
|
||||||
|
|
||||||
|
Exit:
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
/* END */
|
/* END */
|
||||||
|
|
Loading…
Reference in New Issue