[truetype] In GX, make private point numbers work correctly.

This is completely missing in Apple's documentation: If a `gvar'
tuple uses private point numbers (this is, deltas are specified for
some points only), the uncovered points must be interpolated for
this tuple similar to the IUP bytecode instruction.  Examples that
need this functionality are glyphs `Oslash' and `Q' in Skia.ttf.

* src/truetype/ttgxvar.c (tt_delta_shift, tt_delta_interpolate,
tt_handle_deltas): New functions.
(TT_Vary_Get_Glyph_Deltas): Renamed to...
(TT_Vary_Apply_Glyph_Deltas): ... this; it directly processes the
points and does no longer return an array of deltas.
Add tracing information.
Call `tt_handle_deltas' to interpolate missing deltas.
Also fix a minor memory leak in case of error.

* src/truetype/ttgxvar.h: Updated.

* src/truetype/ttgload.c (TT_Process_Simple_Glyph,
load_truetype_glyph): Updated.
This commit is contained in:
Werner Lemberg 2015-05-31 12:21:34 +02:00
parent 9845c1d4e9
commit e9df4e47e3
4 changed files with 484 additions and 97 deletions

View File

@ -1,3 +1,27 @@
2015-05-31 Werner Lemberg <wl@gnu.org>
[truetype] In GX, make private point numbers work correctly.
This is completely missing in Apple's documentation: If a `gvar'
tuple uses private point numbers (this is, deltas are specified for
some points only), the uncovered points must be interpolated for
this tuple similar to the IUP bytecode instruction. Examples that
need this functionality are glyphs `Oslash' and `Q' in Skia.ttf.
* src/truetype/ttgxvar.c (tt_delta_shift, tt_delta_interpolate,
tt_handle_deltas): New functions.
(TT_Vary_Get_Glyph_Deltas): Renamed to...
(TT_Vary_Apply_Glyph_Deltas): ... this; it directly processes the
points and does no longer return an array of deltas.
Add tracing information.
Call `tt_handle_deltas' to interpolate missing deltas.
Also fix a minor memory leak in case of error.
* src/truetype/ttgxvar.h: Updated.
* src/truetype/ttgload.c (TT_Process_Simple_Glyph,
load_truetype_glyph): Updated.
2015-05-31 Werner Lemberg <wl@gnu.org>
[truetype] In GX, make intermediate tuplets work at extrema.

View File

@ -900,25 +900,12 @@
if ( ((TT_Face)loader->face)->doblend )
{
/* Deltas apply to the unscaled data. */
FT_Vector* deltas;
FT_Memory memory = loader->face->memory;
FT_Int i;
error = TT_Vary_Get_Glyph_Deltas( (TT_Face)(loader->face),
loader->glyph_index,
&deltas,
(FT_UInt)n_points );
error = TT_Vary_Apply_Glyph_Deltas( (TT_Face)(loader->face),
loader->glyph_index,
outline,
(FT_UInt)n_points );
if ( error )
return error;
for ( i = 0; i < n_points; ++i )
{
outline->points[i].x += deltas[i].x;
outline->points[i].y += deltas[i].y;
}
FT_FREE( deltas );
}
#endif /* TT_CONFIG_OPTION_GX_VAR_SUPPORT */
@ -1429,10 +1416,6 @@
FT_GlyphLoader gloader = loader->gloader;
FT_Bool opened_frame = 0;
#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
FT_Vector* deltas = NULL;
#endif
#ifdef FT_CONFIG_OPTION_INCREMENTAL
FT_StreamRec inc_stream;
FT_Data glyph_data;
@ -1571,26 +1554,47 @@
if ( ((TT_Face)(loader->face))->doblend )
{
/* a small outline structure with four elements for */
/* communication with `TT_Vary_Apply_Glyph_Deltas' */
FT_Vector points[4];
char tags[4] = { 1, 1, 1, 1 };
short contours[4] = { 0, 1, 2, 3 };
FT_Outline outline;
points[0].x = loader->pp1.x;
points[0].y = loader->pp1.y;
points[1].x = loader->pp2.x;
points[1].y = loader->pp2.y;
points[2].x = loader->pp3.x;
points[2].y = loader->pp3.y;
points[3].x = loader->pp4.x;
points[3].y = loader->pp4.y;
outline.n_points = 4;
outline.n_contours = 4;
outline.points = points;
outline.tags = tags;
outline.contours = contours;
/* this must be done before scaling */
FT_Memory memory = loader->face->memory;
error = TT_Vary_Get_Glyph_Deltas( (TT_Face)(loader->face),
glyph_index, &deltas, 4 );
error = TT_Vary_Apply_Glyph_Deltas( (TT_Face)(loader->face),
glyph_index,
&outline,
outline.n_points );
if ( error )
goto Exit;
loader->pp1.x += deltas[0].x;
loader->pp1.y += deltas[0].y;
loader->pp2.x += deltas[1].x;
loader->pp2.y += deltas[1].y;
loader->pp1.x = points[0].x;
loader->pp1.y = points[0].y;
loader->pp2.x = points[1].x;
loader->pp2.y = points[1].y;
loader->pp3.x += deltas[2].x;
loader->pp3.y += deltas[2].y;
loader->pp4.x += deltas[3].x;
loader->pp4.y += deltas[3].y;
FT_FREE( deltas );
loader->pp3.x = points[2].x;
loader->pp3.y = points[2].y;
loader->pp4.x = points[3].x;
loader->pp4.y = points[3].y;
}
#endif /* TT_CONFIG_OPTION_GX_VAR_SUPPORT */
@ -1677,45 +1681,104 @@
{
FT_UInt i, limit;
FT_SubGlyph subglyph;
FT_Memory memory = face->root.memory;
FT_Outline outline;
FT_Vector* points = NULL;
char* tags = NULL;
short* contours = NULL;
FT_Memory memory = face->root.memory;
/* this provides additional offsets */
/* for each component's translation */
limit = gloader->current.num_subglyphs;
if ( ( error = TT_Vary_Get_Glyph_Deltas(
face,
glyph_index,
&deltas,
gloader->current.num_subglyphs + 4 ) ) != 0 )
goto Exit;
/* construct an outline structure for */
/* communication with `TT_Vary_Apply_Glyph_Deltas' */
outline.n_points = gloader->current.num_subglyphs + 4;
outline.n_contours = outline.n_points;
if ( FT_NEW_ARRAY( points, outline.n_points ) ||
FT_NEW_ARRAY( tags, outline.n_points ) ||
FT_NEW_ARRAY( contours, outline.n_points ) )
goto Exit1;
subglyph = gloader->current.subglyphs + gloader->base.num_subglyphs;
limit = gloader->current.num_subglyphs;
for ( i = 0; i < limit; ++i, ++subglyph )
for ( i = 0; i < limit; i++, subglyph++ )
{
if ( subglyph->flags & ARGS_ARE_XY_VALUES )
{
/* XXX: overflow check for subglyph->{arg1,arg2}. */
/* deltas[i].{x,y} must be within signed 16-bit, */
/* but the restriction of summed delta is not clear */
subglyph->arg1 += (FT_Int16)deltas[i].x;
subglyph->arg2 += (FT_Int16)deltas[i].y;
}
/* applying deltas for anchor points doesn't make sense, */
/* but we don't have to specially check this since */
/* unused delta values are zero anyways */
points[i].x = subglyph->arg1;
points[i].y = subglyph->arg2;
tags[i] = 1;
contours[i] = i;
}
loader->pp1.x += deltas[i + 0].x;
loader->pp1.y += deltas[i + 0].y;
loader->pp2.x += deltas[i + 1].x;
loader->pp2.y += deltas[i + 1].y;
points[i].x = loader->pp1.x;
points[i].y = loader->pp1.y;
tags[i] = 1;
contours[i] = i;
loader->pp3.x += deltas[i + 2].x;
loader->pp3.y += deltas[i + 2].y;
loader->pp4.x += deltas[i + 3].x;
loader->pp4.y += deltas[i + 3].y;
i++;
points[i].x = loader->pp2.x;
points[i].y = loader->pp2.y;
tags[i] = 1;
contours[i] = i;
FT_FREE( deltas );
i++;
points[i].x = loader->pp3.x;
points[i].y = loader->pp3.y;
tags[i] = 1;
contours[i] = i;
i++;
points[i].x = loader->pp4.x;
points[i].y = loader->pp4.y;
tags[i] = 1;
contours[i] = i;
outline.points = points;
outline.tags = tags;
outline.contours = contours;
/* this call provides additional offsets */
/* for each component's translation */
if ( ( error = TT_Vary_Apply_Glyph_Deltas(
face,
glyph_index,
&outline,
outline.n_points ) ) != 0 )
goto Exit1;
subglyph = gloader->current.subglyphs + gloader->base.num_subglyphs;
for ( i = 0; i < limit; i++, subglyph++ )
{
/* XXX: overflow check for subglyph->{arg1,arg2}. */
/* Deltas must be within signed 16-bit, */
/* but the restriction of summed deltas is not clear */
subglyph->arg1 = (FT_Int16)points[i].x;
subglyph->arg2 = (FT_Int16)points[i].y;
}
loader->pp1.x = points[i + 0].x;
loader->pp1.y = points[i + 0].y;
loader->pp2.x = points[i + 1].x;
loader->pp2.y = points[i + 1].y;
loader->pp3.x = points[i + 2].x;
loader->pp3.y = points[i + 2].y;
loader->pp4.x = points[i + 3].x;
loader->pp4.y = points[i + 3].y;
Exit1:
FT_FREE( outline.points );
FT_FREE( outline.tags );
FT_FREE( outline.contours );
if ( error )
goto Exit;
}
#endif /* TT_CONFIG_OPTION_GX_VAR_SUPPORT */

View File

@ -1518,13 +1518,230 @@
}
/* Shift the original coordinates of all points between indices `p1' */
/* and `p2', using the same difference as given by index `ref'. */
/* modeled after `af_iup_shift' */
static void
tt_delta_shift( int p1,
int p2,
int ref,
FT_Vector* in_points,
FT_Vector* out_points )
{
int p;
FT_Vector delta;
delta.x = out_points[ref].x - in_points[ref].x;
delta.y = out_points[ref].y - in_points[ref].y;
if ( delta.x == 0 && delta.y == 0 )
return;
for ( p = p1; p < ref; p++ )
{
out_points[p].x += delta.x;
out_points[p].y += delta.y;
}
for ( p = ref + 1; p <= p2; p++ )
{
out_points[p].x += delta.x;
out_points[p].y += delta.y;
}
}
/* Interpolate the original coordinates of all points with indices */
/* between `p1' and `p2', using `ref1' and `ref2' as the reference */
/* point indices. */
/* modeled after `af_iup_interp', `_iup_worker_interpolate', and */
/* `Ins_IUP' */
static void
tt_delta_interpolate( int p1,
int p2,
int ref1,
int ref2,
FT_Vector* in_points,
FT_Vector* out_points )
{
int p, i;
FT_Pos out, in1, in2, out1, out2, d1, d2;
if ( p1 > p2 )
return;
/* handle both horizontal and vertical coordinates */
for ( i = 0; i <= 1; i++ )
{
/* shift array pointers so that we can access `foo.y' as `foo.x' */
in_points = (FT_Vector*)( (FT_Pos*)in_points + i );
out_points = (FT_Vector*)( (FT_Pos*)out_points + i );
if ( in_points[ref1].x > in_points[ref2].x )
{
p = ref1;
ref1 = ref2;
ref2 = p;
}
in1 = in_points[ref1].x;
in2 = in_points[ref2].x;
out1 = out_points[ref1].x;
out2 = out_points[ref2].x;
d1 = out1 - in1;
d2 = out2 - in2;
if ( out1 == out2 || in1 == in2 )
{
for ( p = p1; p <= p2; p++ )
{
out = in_points[p].x;
if ( out <= in1 )
out += d1;
else if ( out >= in2 )
out += d2;
else
out = out1;
out_points[p].x = out;
}
}
else
{
FT_Fixed scale = FT_DivFix( out2 - out1, in2 - in1 );
for ( p = p1; p <= p2; p++ )
{
out = in_points[p].x;
if ( out <= in1 )
out += d1;
else if ( out >= in2 )
out += d2;
else
out = out1 + FT_MulFix( out - in1, scale );
out_points[p].x = out;
}
}
}
}
/* Interpolate points without delta values, similar to */
/* the `IUP' hinting instruction. */
/* modeled after `Ins_IUP */
static void
tt_handle_deltas( FT_Outline* outline,
FT_Vector* in_points,
FT_Bool* has_delta )
{
FT_Vector* out_points;
FT_UInt first_point;
FT_UInt end_point;
FT_UInt first_delta;
FT_UInt cur_delta;
FT_UInt point;
FT_Short contour;
/* ignore empty outlines */
if ( !outline->n_contours )
return;
out_points = outline->points;
contour = 0;
point = 0;
do
{
end_point = outline->contours[contour];
first_point = point;
/* search first point that has a delta */
while ( point <= end_point && !has_delta[point] )
point++;
if ( point <= end_point )
{
first_delta = point;
cur_delta = point;
point++;
while ( point <= end_point )
{
/* search next point that has a delta */
/* and interpolate intermediate points */
if ( has_delta[point] )
{
tt_delta_interpolate( cur_delta + 1,
point - 1,
cur_delta,
point,
in_points,
out_points );
cur_delta = point;
}
point++;
}
/* shift contour if we only have a single delta */
if ( cur_delta == first_delta )
tt_delta_shift( first_point,
end_point,
cur_delta,
in_points,
out_points );
else
{
/* otherwise handle remaining points */
/* at the end and beginning of the contour */
tt_delta_interpolate( cur_delta + 1,
end_point,
cur_delta,
first_delta,
in_points,
out_points );
if ( first_delta > 0 )
tt_delta_interpolate( first_point,
first_delta - 1,
cur_delta,
first_delta,
in_points,
out_points );
}
}
contour++;
} while ( contour < outline->n_contours );
}
/*************************************************************************/
/* */
/* <Function> */
/* TT_Vary_Get_Glyph_Deltas */
/* TT_Vary_Apply_Glyph_Deltas */
/* */
/* <Description> */
/* Load the appropriate deltas for the current glyph. */
/* Apply the appropriate deltas to the current glyph. */
/* */
/* <Input> */
/* face :: A handle to the target face object. */
@ -1534,22 +1751,24 @@
/* n_points :: The number of the points in the glyph, including */
/* phantom points. */
/* */
/* <Output> */
/* deltas :: The array of points to change. */
/* <InOut> */
/* outline :: The outline to change. */
/* */
/* <Return> */
/* FreeType error code. 0 means success. */
/* */
FT_LOCAL_DEF( FT_Error )
TT_Vary_Get_Glyph_Deltas( TT_Face face,
FT_UInt glyph_index,
FT_Vector* *deltas,
FT_UInt n_points )
TT_Vary_Apply_Glyph_Deltas( TT_Face face,
FT_UInt glyph_index,
FT_Outline* outline,
FT_UInt n_points )
{
FT_Stream stream = face->root.stream;
FT_Memory memory = stream->memory;
GX_Blend blend = face->blend;
FT_Vector* delta_xy = NULL;
FT_Vector* points_org = NULL;
FT_Bool* has_delta = NULL;
FT_Error error;
FT_ULong glyph_start;
@ -1570,15 +1789,18 @@
if ( !face->doblend || blend == NULL )
return FT_THROW( Invalid_Argument );
/* to be freed by the caller */
if ( FT_NEW_ARRAY( delta_xy, n_points ) )
goto Exit;
*deltas = delta_xy;
if ( glyph_index >= blend->gv_glyphcnt ||
blend->glyphoffsets[glyph_index] ==
blend->glyphoffsets[glyph_index + 1] )
return FT_Err_Ok; /* no variation data for this glyph */
{
FT_TRACE2(( "TT_Vary_Apply_Glyph_Deltas:"
" no variation data for this glyph\n" ));
return FT_Err_Ok;
}
if ( FT_NEW_ARRAY( points_org, n_points ) ||
FT_NEW_ARRAY( has_delta, n_points ) )
goto Fail1;
if ( FT_STREAM_SEEK( blend->glyphoffsets[glyph_index] ) ||
FT_FRAME_ENTER( blend->glyphoffsets[glyph_index + 1] -
@ -1610,6 +1832,8 @@
FT_Stream_SeekSet( stream, here );
}
FT_TRACE5(( "gvar: there are %d tuples:\n", tupleCount ));
for ( i = 0; i < ( tupleCount & GX_TC_TUPLE_COUNT_MASK ); i++ )
{
FT_UInt tupleDataSize;
@ -1617,6 +1841,8 @@
FT_Fixed apply;
FT_TRACE6(( " tuple %d:\n", i ));
tupleDataSize = FT_GET_USHORT();
tupleIndex = FT_GET_USHORT();
@ -1629,7 +1855,7 @@
else if ( ( tupleIndex & GX_TI_TUPLE_INDEX_MASK ) >= blend->tuplecount )
{
error = FT_THROW( Invalid_Table );
goto Fail3;
goto Fail2;
}
else
FT_MEM_COPY(
@ -1684,24 +1910,101 @@
else if ( points == ALL_POINTS )
{
#ifdef FT_DEBUG_LEVEL_TRACE
int count = 0;
#endif
FT_TRACE7(( " point deltas:\n" ));
/* this means that there are deltas for every point in the glyph */
for ( j = 0; j < n_points; j++ )
{
delta_xy[j].x += FT_MulFix( deltas_x[j], apply );
delta_xy[j].y += FT_MulFix( deltas_y[j], apply );
#ifdef FT_DEBUG_LEVEL_TRACE
FT_Vector point_org = outline->points[j];
#endif
outline->points[j].x += FT_MulFix( deltas_x[j], apply );
outline->points[j].y += FT_MulFix( deltas_y[j], apply );
#ifdef FT_DEBUG_LEVEL_TRACE
if ( ( point_org.x != outline->points[j].x ) ||
( point_org.y != outline->points[j].y ) )
{
FT_TRACE7(( " %d: (%d, %d) -> (%d, %d)\n",
j,
point_org.x,
point_org.y,
outline->points[j].x,
outline->points[j].y ));
count++;
}
#endif
}
#ifdef FT_DEBUG_LEVEL_TRACE
if ( !count )
FT_TRACE7(( " none\n" ));
#endif
}
else
{
#ifdef FT_DEBUG_LEVEL_TRACE
int count = 0;
#endif
/* we have to interpolate the missing deltas similar to the */
/* IUP bytecode instruction */
for ( j = 0; j < n_points; j++ )
{
points_org[j] = outline->points[j];
has_delta[j] = FALSE;
}
for ( j = 0; j < point_count; j++ )
{
if ( localpoints[j] >= n_points )
FT_UShort idx = localpoints[j];
if ( idx >= n_points )
continue;
delta_xy[localpoints[j]].x += FT_MulFix( deltas_x[j], apply );
delta_xy[localpoints[j]].y += FT_MulFix( deltas_y[j], apply );
has_delta[idx] = TRUE;
outline->points[idx].x += FT_MulFix( deltas_x[j], apply );
outline->points[idx].y += FT_MulFix( deltas_y[j], apply );
}
/* no need to handle phantom points here, */
/* since solitary points can't be interpolated */
tt_handle_deltas( outline,
points_org,
has_delta );
#ifdef FT_DEBUG_LEVEL_TRACE
FT_TRACE7(( " point deltas:\n" ));
for ( j = 0; j < n_points; j++)
{
if ( ( points_org[j].x != outline->points[j].x ) ||
( points_org[j].y != outline->points[j].y ) )
{
FT_TRACE7(( " %d: (%d, %d) -> (%d, %d)\n",
j,
points_org[j].x,
points_org[j].y,
outline->points[j].x,
outline->points[j].y ));
count++;
}
}
if ( !count )
FT_TRACE7(( " none\n" ));
#endif
}
if ( localpoints != ALL_POINTS )
@ -1714,22 +2017,19 @@
FT_Stream_SeekSet( stream, here );
}
Fail3:
FT_TRACE5(( "\n" ));
Fail2:
FT_FREE( tuple_coords );
FT_FREE( im_start_coords );
FT_FREE( im_end_coords );
Fail2:
FT_FRAME_EXIT();
Fail1:
if ( error )
{
FT_FREE( delta_xy );
*deltas = NULL;
}
FT_FREE( points_org );
FT_FREE( has_delta );
Exit:
return error;
}

View File

@ -162,10 +162,10 @@ FT_BEGIN_HEADER
FT_LOCAL( FT_Error )
TT_Vary_Get_Glyph_Deltas( TT_Face face,
FT_UInt glyph_index,
FT_Vector* *deltas,
FT_UInt n_points );
TT_Vary_Apply_Glyph_Deltas( TT_Face face,
FT_UInt glyph_index,
FT_Outline* outline,
FT_UInt n_points );
FT_LOCAL( void )