freetype2/src/psaux/pshints.c

1937 lines
64 KiB
C
Raw Normal View History

/****************************************************************************
*
* pshints.c
*
* Adobe's code for handling CFF hints (body).
*
* Copyright 2007-2014 Adobe Systems Incorporated.
*
* This software, and all works of authorship, whether in source or
* object code form as indicated by the copyright notice(s) included
* herein (collectively, the "Work") is made available, and may only be
* used, modified, and distributed under the FreeType Project License,
* LICENSE.TXT. Additionally, subject to the terms and conditions of the
* FreeType Project License, each contributor to the Work hereby grants
* to any individual or legal entity exercising permissions granted by
* the FreeType Project License and this section (hereafter, "You" or
* "Your") a perpetual, worldwide, non-exclusive, no-charge,
* royalty-free, irrevocable (except as stated in this section) patent
* license to make, have made, use, offer to sell, sell, import, and
* otherwise transfer the Work, where such license applies only to those
* patent claims licensable by such contributor that are necessarily
* infringed by their contribution(s) alone or by combination of their
* contribution(s) with the Work to which such contribution(s) was
* submitted. If You institute patent litigation against any entity
* (including a cross-claim or counterclaim in a lawsuit) alleging that
* the Work or a contribution incorporated within the Work constitutes
* direct or contributory patent infringement, then any patent licenses
* granted to You under this License for that Work shall terminate as of
* the date such litigation is filed.
*
* By using, modifying, or distributing the Work you indicate that you
* have read and understood the terms and conditions of the
* FreeType Project License as well as those provided in this section,
* and you accept them fully.
*
*/
#include "psft.h"
#include <freetype/internal/ftdebug.h>
#include "psglue.h"
#include "psfont.h"
#include "pshints.h"
#include "psintrp.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 cf2hints
typedef struct CF2_HintMoveRec_
{
size_t j; /* index of upper hint map edge */
CF2_Fixed moveUp; /* adjustment to optimum position */
} CF2_HintMoveRec, *CF2_HintMove;
/* Compute angular momentum for winding order detection. It is called */
/* for all lines and curves, but not necessarily in element order. */
static CF2_Int
cf2_getWindingMomentum( CF2_Fixed x1,
CF2_Fixed y1,
CF2_Fixed x2,
CF2_Fixed y2 )
{
/* cross product of pt1 position from origin with pt2 position from */
/* pt1; we reduce the precision so that the result fits into 32 bits */
return ( x1 >> 16 ) * ( SUB_INT32( y2, y1 ) >> 16 ) -
( y1 >> 16 ) * ( SUB_INT32( x2, x1 ) >> 16 );
}
/*
* Construct from a StemHint; this is used as a parameter to
* `cf2_blues_capture'.
* `hintOrigin' is the character space displacement of a seac accent.
* Adjust stem hint for darkening here.
*
*/
static void
cf2_hint_init( CF2_Hint hint,
const CF2_ArrStack stemHintArray,
size_t indexStemHint,
const CF2_Font font,
CF2_Fixed hintOrigin,
CF2_Fixed scale,
FT_Bool bottom )
{
CF2_Fixed width;
const CF2_StemHintRec* stemHint;
FT_ZERO( hint );
stemHint = (const CF2_StemHintRec*)cf2_arrstack_getPointer(
stemHintArray,
indexStemHint );
width = SUB_INT32( stemHint->max, stemHint->min );
if ( width == cf2_intToFixed( -21 ) )
{
/* ghost bottom */
if ( bottom )
{
hint->csCoord = stemHint->max;
hint->flags = CF2_GhostBottom;
}
else
hint->flags = 0;
}
else if ( width == cf2_intToFixed( -20 ) )
{
/* ghost top */
if ( bottom )
hint->flags = 0;
else
{
hint->csCoord = stemHint->min;
hint->flags = CF2_GhostTop;
}
}
else if ( width < 0 )
{
/* inverted pair */
/*
* Hints with negative widths were produced by an early version of a
* non-Adobe font tool. The Type 2 spec allows edge (ghost) hints
* with negative widths, but says
*
* All other negative widths have undefined meaning.
*
* CoolType has a silent workaround that negates the hint width; for
* permissive mode, we do the same here.
*
* Note: Such fonts cannot use ghost hints, but should otherwise work.
* Note: Some poor hints in our faux fonts can produce negative
* widths at some blends. For example, see a light weight of
* `u' in ASerifMM.
*
*/
if ( bottom )
{
hint->csCoord = stemHint->max;
hint->flags = CF2_PairBottom;
}
else
{
hint->csCoord = stemHint->min;
hint->flags = CF2_PairTop;
}
}
else
{
/* normal pair */
if ( bottom )
{
hint->csCoord = stemHint->min;
hint->flags = CF2_PairBottom;
}
else
{
hint->csCoord = stemHint->max;
hint->flags = CF2_PairTop;
}
}
/* Now that ghost hints have been detected, adjust this edge for */
/* darkening. Bottoms are not changed; tops are incremented by twice */
/* `darkenY'. */
if ( cf2_hint_isTop( hint ) )
hint->csCoord = ADD_INT32( hint->csCoord, 2 * font->darkenY );
hint->csCoord = ADD_INT32( hint->csCoord, hintOrigin );
hint->scale = scale;
hint->index = indexStemHint; /* index in original stem hint array */
/* if original stem hint has been used, use the same position */
if ( hint->flags != 0 && stemHint->used )
{
if ( cf2_hint_isTop( hint ) )
hint->dsCoord = stemHint->maxDS;
else
hint->dsCoord = stemHint->minDS;
cf2_hint_lock( hint );
}
else
hint->dsCoord = FT_MulFix( hint->csCoord, scale );
}
/* initialize an invalid hint map element */
static void
cf2_hint_initZero( CF2_Hint hint )
{
FT_ZERO( hint );
}
FT_LOCAL_DEF( FT_Bool )
cf2_hint_isValid( const CF2_Hint hint )
{
return FT_BOOL( hint->flags );
}
static FT_Bool
cf2_hint_isPair( const CF2_Hint hint )
{
return FT_BOOL( hint->flags & ( CF2_PairBottom | CF2_PairTop ) );
}
static FT_Bool
cf2_hint_isPairTop( const CF2_Hint hint )
{
return FT_BOOL( hint->flags & CF2_PairTop );
}
FT_LOCAL_DEF( FT_Bool )
cf2_hint_isTop( const CF2_Hint hint )
{
return FT_BOOL( hint->flags & ( CF2_PairTop | CF2_GhostTop ) );
}
FT_LOCAL_DEF( FT_Bool )
cf2_hint_isBottom( const CF2_Hint hint )
{
return FT_BOOL( hint->flags & ( CF2_PairBottom | CF2_GhostBottom ) );
}
static FT_Bool
cf2_hint_isLocked( const CF2_Hint hint )
{
return FT_BOOL( hint->flags & CF2_Locked );
}
static FT_Bool
cf2_hint_isSynthetic( const CF2_Hint hint )
{
return FT_BOOL( hint->flags & CF2_Synthetic );
}
FT_LOCAL_DEF( void )
cf2_hint_lock( CF2_Hint hint )
{
hint->flags |= CF2_Locked;
}
FT_LOCAL_DEF( void )
cf2_hintmap_init( CF2_HintMap hintmap,
CF2_Font font,
CF2_HintMap initialMap,
CF2_ArrStack hintMoves,
CF2_Fixed scale )
{
FT_ZERO( hintmap );
/* copy parameters from font instance */
hintmap->hinted = font->hinted;
hintmap->scale = scale;
hintmap->font = font;
hintmap->initialHintMap = initialMap;
/* will clear in `cf2_hintmap_adjustHints' */
hintmap->hintMoves = hintMoves;
}
static FT_Bool
cf2_hintmap_isValid( const CF2_HintMap hintmap )
{
return hintmap->isValid;
}
static void
cf2_hintmap_dump( CF2_HintMap hintmap )
{
#ifdef FT_DEBUG_LEVEL_TRACE
CF2_UInt i;
FT_TRACE6(( " index csCoord dsCoord scale flags\n" ));
for ( i = 0; i < hintmap->count; i++ )
{
CF2_Hint hint = &hintmap->edge[i];
FT_TRACE6(( " %3ld %7.2f %7.2f %5d %s%s%s%s\n",
hint->index,
hint->csCoord / 65536.0,
hint->dsCoord / ( hint->scale * 1.0 ),
hint->scale,
( cf2_hint_isPair( hint ) ? "p" : "g" ),
( cf2_hint_isTop( hint ) ? "t" : "b" ),
( cf2_hint_isLocked( hint ) ? "L" : ""),
( cf2_hint_isSynthetic( hint ) ? "S" : "" ) ));
}
#else
FT_UNUSED( hintmap );
#endif
}
/* transform character space coordinate to device space using hint map */
static CF2_Fixed
cf2_hintmap_map( CF2_HintMap hintmap,
CF2_Fixed csCoord )
{
2018-10-07 08:59:56 +02:00
if ( hintmap->count == 0 || !hintmap->hinted )
{
/* there are no hints; use uniform scale and zero offset */
return FT_MulFix( csCoord, hintmap->scale );
}
else
{
/* start linear search from last hit */
CF2_UInt i = hintmap->lastIndex;
FT_ASSERT( hintmap->lastIndex < CF2_MAX_HINT_EDGES );
/* search up */
while ( i < hintmap->count - 1 &&
csCoord >= hintmap->edge[i + 1].csCoord )
i += 1;
/* search down */
while ( i > 0 && csCoord < hintmap->edge[i].csCoord )
i -= 1;
hintmap->lastIndex = i;
if ( i == 0 && csCoord < hintmap->edge[0].csCoord )
{
/* special case for points below first edge: use uniform scale */
return ADD_INT32( FT_MulFix( SUB_INT32( csCoord,
hintmap->edge[0].csCoord ),
hintmap->scale ),
hintmap->edge[0].dsCoord );
}
else
{
/*
* Note: entries with duplicate csCoord are allowed.
* Use edge[i], the highest entry where csCoord >= entry[i].csCoord
*/
return ADD_INT32( FT_MulFix( SUB_INT32( csCoord,
hintmap->edge[i].csCoord ),
hintmap->edge[i].scale ),
hintmap->edge[i].dsCoord );
}
}
}
/*
* This hinting policy moves a hint pair in device space so that one of
* its two edges is on a device pixel boundary (its fractional part is
* zero). `cf2_hintmap_insertHint' guarantees no overlap in CS
* space. Ensure here that there is no overlap in DS.
*
* In the first pass, edges are adjusted relative to adjacent hints.
* Those that are below have already been adjusted. Those that are
* above have not yet been adjusted. If a hint above blocks an
* adjustment to an optimal position, we will try again in a second
* pass. The second pass is top-down.
*
*/
static void
cf2_hintmap_adjustHints( CF2_HintMap hintmap )
{
size_t i, j;
cf2_arrstack_clear( hintmap->hintMoves ); /* working storage */
/*
* First pass is bottom-up (font hint order) without look-ahead.
* Locked edges are already adjusted.
* Unlocked edges begin with dsCoord from `initialHintMap'.
* Save edges that are not optimally adjusted in `hintMoves' array,
* and process them in second pass.
*/
for ( i = 0; i < hintmap->count; i++ )
{
FT_Bool isPair = cf2_hint_isPair( &hintmap->edge[i] );
/* index of upper edge (same value for ghost hint) */
j = isPair ? i + 1 : i;
FT_ASSERT( j < hintmap->count );
FT_ASSERT( cf2_hint_isValid( &hintmap->edge[i] ) );
FT_ASSERT( cf2_hint_isValid( &hintmap->edge[j] ) );
FT_ASSERT( cf2_hint_isLocked( &hintmap->edge[i] ) ==
cf2_hint_isLocked( &hintmap->edge[j] ) );
if ( !cf2_hint_isLocked( &hintmap->edge[i] ) )
{
/* hint edge is not locked, we can adjust it */
CF2_Fixed fracDown = cf2_fixedFraction( hintmap->edge[i].dsCoord );
CF2_Fixed fracUp = cf2_fixedFraction( hintmap->edge[j].dsCoord );
/* calculate all four possibilities; moves down are negative */
CF2_Fixed downMoveDown = 0 - fracDown;
CF2_Fixed upMoveDown = 0 - fracUp;
2016-12-25 22:55:25 +01:00
CF2_Fixed downMoveUp = ( fracDown == 0 )
? 0
: cf2_intToFixed( 1 ) - fracDown;
2016-12-25 22:55:25 +01:00
CF2_Fixed upMoveUp = ( fracUp == 0 )
? 0
: cf2_intToFixed( 1 ) - fracUp;
/* smallest move up */
CF2_Fixed moveUp = FT_MIN( downMoveUp, upMoveUp );
/* smallest move down */
CF2_Fixed moveDown = FT_MAX( downMoveDown, upMoveDown );
/* final amount to move edge or edge pair */
CF2_Fixed move;
CF2_Fixed downMinCounter = CF2_MIN_COUNTER;
CF2_Fixed upMinCounter = CF2_MIN_COUNTER;
FT_Bool saveEdge = FALSE;
/* minimum counter constraint doesn't apply when adjacent edges */
/* are synthetic */
/* TODO: doesn't seem a big effect; for now, reduce the code */
#if 0
if ( i == 0 ||
cf2_hint_isSynthetic( &hintmap->edge[i - 1] ) )
downMinCounter = 0;
if ( j >= hintmap->count - 1 ||
cf2_hint_isSynthetic( &hintmap->edge[j + 1] ) )
upMinCounter = 0;
#endif
/* is there room to move up? */
/* there is if we are at top of array or the next edge is at or */
/* beyond proposed move up? */
if ( j >= hintmap->count - 1 ||
hintmap->edge[j + 1].dsCoord >=
ADD_INT32( hintmap->edge[j].dsCoord,
moveUp + upMinCounter ) )
{
/* there is room to move up; is there also room to move down? */
if ( i == 0 ||
hintmap->edge[i - 1].dsCoord <=
ADD_INT32( hintmap->edge[i].dsCoord,
moveDown - downMinCounter ) )
{
/* move smaller absolute amount */
move = ( -moveDown < moveUp ) ? moveDown : moveUp; /* optimum */
}
else
move = moveUp;
}
else
{
/* is there room to move down? */
if ( i == 0 ||
hintmap->edge[i - 1].dsCoord <=
ADD_INT32( hintmap->edge[i].dsCoord,
moveDown - downMinCounter ) )
{
move = moveDown;
/* true if non-optimum move */
saveEdge = FT_BOOL( moveUp < -moveDown );
}
else
{
/* no room to move either way without overlapping or reducing */
/* the counter too much */
move = 0;
saveEdge = TRUE;
}
}
/* Identify non-moves and moves down that aren't optimal, and save */
/* them for second pass. */
/* Do this only if there is an unlocked edge above (which could */
/* possibly move). */
if ( saveEdge &&
j < hintmap->count - 1 &&
!cf2_hint_isLocked( &hintmap->edge[j + 1] ) )
{
CF2_HintMoveRec savedMove;
savedMove.j = j;
/* desired adjustment in second pass */
savedMove.moveUp = moveUp - move;
cf2_arrstack_push( hintmap->hintMoves, &savedMove );
}
/* move the edge(s) */
hintmap->edge[i].dsCoord = ADD_INT32( hintmap->edge[i].dsCoord,
move );
if ( isPair )
hintmap->edge[j].dsCoord = ADD_INT32( hintmap->edge[j].dsCoord,
move );
}
/* assert there are no overlaps in device space */
FT_ASSERT( i == 0 ||
hintmap->edge[i - 1].dsCoord <= hintmap->edge[i].dsCoord );
FT_ASSERT( i < j ||
hintmap->edge[i].dsCoord <= hintmap->edge[j].dsCoord );
/* adjust the scales, avoiding divide by zero */
if ( i > 0 )
{
if ( hintmap->edge[i].csCoord != hintmap->edge[i - 1].csCoord )
hintmap->edge[i - 1].scale =
FT_DivFix( SUB_INT32( hintmap->edge[i].dsCoord,
hintmap->edge[i - 1].dsCoord ),
SUB_INT32( hintmap->edge[i].csCoord,
hintmap->edge[i - 1].csCoord ) );
}
if ( isPair )
{
if ( hintmap->edge[j].csCoord != hintmap->edge[j - 1].csCoord )
hintmap->edge[j - 1].scale =
FT_DivFix( SUB_INT32( hintmap->edge[j].dsCoord,
hintmap->edge[j - 1].dsCoord ),
SUB_INT32( hintmap->edge[j].csCoord,
hintmap->edge[j - 1].csCoord ) );
i += 1; /* skip upper edge on next loop */
}
}
/* second pass tries to move non-optimal hints up, in case there is */
/* room now */
for ( i = cf2_arrstack_size( hintmap->hintMoves ); i > 0; i-- )
{
CF2_HintMove hintMove = (CF2_HintMove)
cf2_arrstack_getPointer( hintmap->hintMoves, i - 1 );
j = hintMove->j;
/* this was tested before the push, above */
FT_ASSERT( j < hintmap->count - 1 );
/* is there room to move up? */
if ( hintmap->edge[j + 1].dsCoord >=
ADD_INT32( hintmap->edge[j].dsCoord,
hintMove->moveUp + CF2_MIN_COUNTER ) )
{
/* there is more room now, move edge up */
hintmap->edge[j].dsCoord = ADD_INT32( hintmap->edge[j].dsCoord,
hintMove->moveUp );
if ( cf2_hint_isPair( &hintmap->edge[j] ) )
{
FT_ASSERT( j > 0 );
hintmap->edge[j - 1].dsCoord =
ADD_INT32( hintmap->edge[j - 1].dsCoord, hintMove->moveUp );
}
}
}
}
/* insert hint edges into map, sorted by csCoord */
static void
cf2_hintmap_insertHint( CF2_HintMap hintmap,
CF2_Hint bottomHintEdge,
CF2_Hint topHintEdge )
{
CF2_UInt indexInsert;
/* set default values, then check for edge hints */
FT_Bool isPair = TRUE;
CF2_Hint firstHintEdge = bottomHintEdge;
CF2_Hint secondHintEdge = topHintEdge;
/* one or none of the input params may be invalid when dealing with */
/* edge hints; at least one edge must be valid */
FT_ASSERT( cf2_hint_isValid( bottomHintEdge ) ||
cf2_hint_isValid( topHintEdge ) );
/* determine how many and which edges to insert */
if ( !cf2_hint_isValid( bottomHintEdge ) )
{
/* insert only the top edge */
firstHintEdge = topHintEdge;
isPair = FALSE;
}
else if ( !cf2_hint_isValid( topHintEdge ) )
{
/* insert only the bottom edge */
isPair = FALSE;
}
/* paired edges must be in proper order */
if ( isPair &&
topHintEdge->csCoord < bottomHintEdge->csCoord )
return;
/* linear search to find index value of insertion point */
indexInsert = 0;
for ( ; indexInsert < hintmap->count; indexInsert++ )
{
if ( hintmap->edge[indexInsert].csCoord >= firstHintEdge->csCoord )
break;
}
FT_TRACE7(( " Got hint at %.2f (%.2f)\n",
firstHintEdge->csCoord / 65536.0,
firstHintEdge->dsCoord / 65536.0 ));
if ( isPair )
FT_TRACE7(( " Got hint at %.2f (%.2f)\n",
secondHintEdge->csCoord / 65536.0,
secondHintEdge->dsCoord / 65536.0 ));
/*
* Discard any hints that overlap in character space. Most often, this
* is while building the initial map, where captured hints from all
* zones are combined. Define overlap to include hints that `touch'
* (overlap zero). Hiragino Sans/Gothic fonts have numerous hints that
* touch. Some fonts have non-ideographic glyphs that overlap our
* synthetic hints.
*
* Overlap also occurs when darkening stem hints that are close.
*
*/
if ( indexInsert < hintmap->count )
{
/* we are inserting before an existing edge: */
/* verify that an existing edge is not the same */
if ( hintmap->edge[indexInsert].csCoord == firstHintEdge->csCoord )
return; /* ignore overlapping stem hint */
/* verify that a new pair does not straddle the next edge */
if ( isPair &&
hintmap->edge[indexInsert].csCoord <= secondHintEdge->csCoord )
return; /* ignore overlapping stem hint */
/* verify that we are not inserting between paired edges */
if ( cf2_hint_isPairTop( &hintmap->edge[indexInsert] ) )
return; /* ignore overlapping stem hint */
}
/* recompute device space locations using initial hint map */
if ( cf2_hintmap_isValid( hintmap->initialHintMap ) &&
!cf2_hint_isLocked( firstHintEdge ) )
{
if ( isPair )
{
/* Use hint map to position the center of stem, and nominal scale */
/* to position the two edges. This preserves the stem width. */
CF2_Fixed midpoint =
cf2_hintmap_map(
hintmap->initialHintMap,
ADD_INT32( secondHintEdge->csCoord,
firstHintEdge->csCoord ) / 2 );
CF2_Fixed halfWidth =
FT_MulFix( SUB_INT32( secondHintEdge->csCoord,
firstHintEdge->csCoord ) / 2,
hintmap->scale );
firstHintEdge->dsCoord = SUB_INT32( midpoint, halfWidth );
secondHintEdge->dsCoord = ADD_INT32( midpoint, halfWidth );
}
else
firstHintEdge->dsCoord = cf2_hintmap_map( hintmap->initialHintMap,
firstHintEdge->csCoord );
}
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
/*
* Discard any hints that overlap in device space; this can occur
* because locked hints have been moved to align with blue zones.
*
* TODO: Although we might correct this later during adjustment, we
* don't currently have a way to delete a conflicting hint once it has
* been inserted. See v2.030 MinionPro-Regular, 12 ppem darkened,
* initial hint map for second path, glyph 945 (the perispomeni (tilde)
* in U+1F6E, Greek omega with psili and perispomeni). Darkening is
* 25. Pair 667,747 initially conflicts in design space with top edge
* 660. This is because 667 maps to 7.87, and the top edge was
* captured by a zone at 8.0. The pair is later successfully inserted
* in a zone without the top edge. In this zone it is adjusted to 8.0,
* and no longer conflicts with the top edge in design space. This
* means it can be included in yet a later zone which does have the top
* edge hint. This produces a small mismatch between the first and
* last points of this path, even though the hint masks are the same.
* The density map difference is tiny (1/256).
*
*/
if ( indexInsert > 0 )
{
/* we are inserting after an existing edge */
if ( firstHintEdge->dsCoord < hintmap->edge[indexInsert - 1].dsCoord )
return;
}
if ( indexInsert < hintmap->count )
{
/* we are inserting before an existing edge */
if ( isPair )
{
if ( secondHintEdge->dsCoord > hintmap->edge[indexInsert].dsCoord )
return;
}
else
{
if ( firstHintEdge->dsCoord > hintmap->edge[indexInsert].dsCoord )
return;
}
}
/* make room to insert */
{
CF2_UInt iSrc = hintmap->count - 1;
CF2_UInt iDst = isPair ? hintmap->count + 1 : hintmap->count;
CF2_UInt count = hintmap->count - indexInsert;
if ( iDst >= CF2_MAX_HINT_EDGES )
{
FT_TRACE4(( "cf2_hintmap_insertHint: too many hintmaps\n" ));
return;
}
while ( count-- )
hintmap->edge[iDst--] = hintmap->edge[iSrc--];
/* insert first edge */
hintmap->edge[indexInsert] = *firstHintEdge; /* copy struct */
hintmap->count += 1;
FT_TRACE7(( " Inserting hint %.2f (%.2f)\n",
firstHintEdge->csCoord / 65536.0,
firstHintEdge->dsCoord / 65536.0 ));
if ( isPair )
{
/* insert second edge */
hintmap->edge[indexInsert + 1] = *secondHintEdge; /* copy struct */
hintmap->count += 1;
FT_TRACE7(( " Inserting hint %.2f (%.2f)\n",
secondHintEdge->csCoord / 65536.0,
secondHintEdge->dsCoord / 65536.0 ));
}
}
return;
}
/*
* Build a map from hints and mask.
*
* This function may recur one level if `hintmap->initialHintMap' is not yet
* valid.
* If `initialMap' is true, simply build initial map.
*
* Synthetic hints are used in two ways. A hint at zero is inserted, if
* needed, in the initial hint map, to prevent translations from
* propagating across the origin. If synthetic em box hints are enabled
* for ideographic dictionaries, then they are inserted in all hint
* maps, including the initial one.
*
*/
FT_LOCAL_DEF( void )
cf2_hintmap_build( CF2_HintMap hintmap,
CF2_ArrStack hStemHintArray,
CF2_ArrStack vStemHintArray,
CF2_HintMask hintMask,
CF2_Fixed hintOrigin,
FT_Bool initialMap )
{
FT_Byte* maskPtr;
CF2_Font font = hintmap->font;
CF2_HintMaskRec tempHintMask;
size_t bitCount, i;
FT_Byte maskByte;
/* check whether initial map is constructed */
if ( !initialMap && !cf2_hintmap_isValid( hintmap->initialHintMap ) )
{
/* make recursive call with initialHintMap and temporary mask; */
/* temporary mask will get all bits set, below */
cf2_hintmask_init( &tempHintMask, hintMask->error );
cf2_hintmap_build( hintmap->initialHintMap,
hStemHintArray,
vStemHintArray,
&tempHintMask,
hintOrigin,
TRUE );
}
if ( !cf2_hintmask_isValid( hintMask ) )
{
/* without a hint mask, assume all hints are active */
cf2_hintmask_setAll( hintMask,
cf2_arrstack_size( hStemHintArray ) +
cf2_arrstack_size( vStemHintArray ) );
if ( !cf2_hintmask_isValid( hintMask ) )
{
if ( font->isT1 )
{
/* no error, just continue unhinted */
*hintMask->error = FT_Err_Ok;
hintmap->hinted = FALSE;
}
return; /* too many stem hints */
}
}
/* begin by clearing the map */
hintmap->count = 0;
hintmap->lastIndex = 0;
/* make a copy of the hint mask so we can modify it */
tempHintMask = *hintMask;
maskPtr = cf2_hintmask_getMaskPtr( &tempHintMask );
/* use the hStem hints only, which are first in the mask */
bitCount = cf2_arrstack_size( hStemHintArray );
/* Defense-in-depth. Should never return here. */
if ( bitCount > hintMask->bitCount )
return;
/* synthetic embox hints get highest priority */
if ( font->blues.doEmBoxHints )
{
CF2_HintRec dummy;
cf2_hint_initZero( &dummy ); /* invalid hint map element */
/* ghost bottom */
cf2_hintmap_insertHint( hintmap,
&font->blues.emBoxBottomEdge,
&dummy );
/* ghost top */
cf2_hintmap_insertHint( hintmap,
&dummy,
&font->blues.emBoxTopEdge );
}
/* insert hints captured by a blue zone or already locked (higher */
/* priority) */
for ( i = 0, maskByte = 0x80; i < bitCount; i++ )
{
if ( maskByte & *maskPtr )
{
/* expand StemHint into two `CF2_Hint' elements */
CF2_HintRec bottomHintEdge, topHintEdge;
cf2_hint_init( &bottomHintEdge,
hStemHintArray,
i,
font,
hintOrigin,
hintmap->scale,
TRUE /* bottom */ );
cf2_hint_init( &topHintEdge,
hStemHintArray,
i,
font,
hintOrigin,
hintmap->scale,
FALSE /* top */ );
if ( cf2_hint_isLocked( &bottomHintEdge ) ||
cf2_hint_isLocked( &topHintEdge ) ||
cf2_blues_capture( &font->blues,
&bottomHintEdge,
&topHintEdge ) )
{
/* insert captured hint into map */
cf2_hintmap_insertHint( hintmap, &bottomHintEdge, &topHintEdge );
*maskPtr &= ~maskByte; /* turn off the bit for this hint */
}
}
if ( ( i & 7 ) == 7 )
{
/* move to next mask byte */
maskPtr++;
maskByte = 0x80;
}
else
maskByte >>= 1;
}
/* initial hint map includes only captured hints plus maybe one at 0 */
/*
* TODO: There is a problem here because we are trying to build a
* single hint map containing all captured hints. It is
* possible for there to be conflicts between captured hints,
* either because of darkening or because the hints are in
* separate hint zones (we are ignoring hint zones for the
* initial map). An example of the latter is MinionPro-Regular
* v2.030 glyph 883 (Greek Capital Alpha with Psili) at 15ppem.
* A stem hint for the psili conflicts with the top edge hint
* for the base character. The stem hint gets priority because
* of its sort order. In glyph 884 (Greek Capital Alpha with
* Psili and Oxia), the top of the base character gets a stem
* hint, and the psili does not. This creates different initial
* maps for the two glyphs resulting in different renderings of
* the base character. Will probably defer this either as not
* worth the cost or as a font bug. I don't think there is any
* good reason for an accent to be captured by an alignment
* zone. -darnold 2/12/10
*/
if ( initialMap )
{
/* Apply a heuristic that inserts a point for (0,0), unless it's */
/* already covered by a mapping. This locks the baseline for glyphs */
/* that have no baseline hints. */
if ( hintmap->count == 0 ||
hintmap->edge[0].csCoord > 0 ||
hintmap->edge[hintmap->count - 1].csCoord < 0 )
{
/* all edges are above 0 or all edges are below 0; */
/* construct a locked edge hint at 0 */
CF2_HintRec edge, invalid;
cf2_hint_initZero( &edge );
edge.flags = CF2_GhostBottom |
CF2_Locked |
CF2_Synthetic;
edge.scale = hintmap->scale;
cf2_hint_initZero( &invalid );
cf2_hintmap_insertHint( hintmap, &edge, &invalid );
}
}
else
{
/* insert remaining hints */
maskPtr = cf2_hintmask_getMaskPtr( &tempHintMask );
for ( i = 0, maskByte = 0x80; i < bitCount; i++ )
{
if ( maskByte & *maskPtr )
{
CF2_HintRec bottomHintEdge, topHintEdge;
cf2_hint_init( &bottomHintEdge,
hStemHintArray,
i,
font,
hintOrigin,
hintmap->scale,
TRUE /* bottom */ );
cf2_hint_init( &topHintEdge,
hStemHintArray,
i,
font,
hintOrigin,
hintmap->scale,
FALSE /* top */ );
cf2_hintmap_insertHint( hintmap, &bottomHintEdge, &topHintEdge );
}
if ( ( i & 7 ) == 7 )
{
/* move to next mask byte */
maskPtr++;
maskByte = 0x80;
}
else
maskByte >>= 1;
}
}
FT_TRACE6(( "%s\n", initialMap ? "flags: [p]air [g]host [t]op"
" [b]ottom [L]ocked [S]ynthetic\n"
"Initial hintmap"
: "Hints:" ));
cf2_hintmap_dump( hintmap );
/*
* Note: The following line is a convenient place to break when
* debugging hinting. Examine `hintmap->edge' for the list of
* enabled hints, then step over the call to see the effect of
* adjustment. We stop here first on the recursive call that
* creates the initial map, and then on each counter group and
* hint zone.
*/
/* adjust positions of hint edges that are not locked to blue zones */
cf2_hintmap_adjustHints( hintmap );
FT_TRACE6(( "(adjusted)\n" ));
cf2_hintmap_dump( hintmap );
/* save the position of all hints that were used in this hint map; */
/* if we use them again, we'll locate them in the same position */
if ( !initialMap )
{
for ( i = 0; i < hintmap->count; i++ )
{
if ( !cf2_hint_isSynthetic( &hintmap->edge[i] ) )
{
/* Note: include both valid and invalid edges */
/* Note: top and bottom edges are copied back separately */
CF2_StemHint stemhint = (CF2_StemHint)
cf2_arrstack_getPointer( hStemHintArray,
hintmap->edge[i].index );
if ( cf2_hint_isTop( &hintmap->edge[i] ) )
stemhint->maxDS = hintmap->edge[i].dsCoord;
else
stemhint->minDS = hintmap->edge[i].dsCoord;
stemhint->used = TRUE;
}
}
}
/* hint map is ready to use */
hintmap->isValid = TRUE;
/* remember this mask has been used */
cf2_hintmask_setNew( hintMask, FALSE );
}
FT_LOCAL_DEF( void )
cf2_glyphpath_init( CF2_GlyphPath glyphpath,
CF2_Font font,
CF2_OutlineCallbacks callbacks,
CF2_Fixed scaleY,
/* CF2_Fixed hShift, */
CF2_ArrStack hStemHintArray,
CF2_ArrStack vStemHintArray,
CF2_HintMask hintMask,
CF2_Fixed hintOriginY,
const CF2_Blues blues,
const FT_Vector* fractionalTranslation )
{
FT_ZERO( glyphpath );
glyphpath->font = font;
glyphpath->callbacks = callbacks;
cf2_arrstack_init( &glyphpath->hintMoves,
font->memory,
&font->error,
sizeof ( CF2_HintMoveRec ) );
cf2_hintmap_init( &glyphpath->initialHintMap,
font,
&glyphpath->initialHintMap,
&glyphpath->hintMoves,
scaleY );
cf2_hintmap_init( &glyphpath->firstHintMap,
font,
&glyphpath->initialHintMap,
&glyphpath->hintMoves,
scaleY );
cf2_hintmap_init( &glyphpath->hintMap,
font,
&glyphpath->initialHintMap,
&glyphpath->hintMoves,
scaleY );
glyphpath->scaleX = font->innerTransform.a;
glyphpath->scaleC = font->innerTransform.c;
glyphpath->scaleY = font->innerTransform.d;
glyphpath->fractionalTranslation = *fractionalTranslation;
#if 0
glyphpath->hShift = hShift; /* for fauxing */
#endif
glyphpath->hStemHintArray = hStemHintArray;
glyphpath->vStemHintArray = vStemHintArray;
glyphpath->hintMask = hintMask; /* ptr to current mask */
glyphpath->hintOriginY = hintOriginY;
glyphpath->blues = blues;
glyphpath->darken = font->darkened; /* TODO: should we make copies? */
glyphpath->xOffset = font->darkenX;
glyphpath->yOffset = font->darkenY;
glyphpath->miterLimit = 2 * FT_MAX(
cf2_fixedAbs( glyphpath->xOffset ),
cf2_fixedAbs( glyphpath->yOffset ) );
/* .1 character space unit */
glyphpath->snapThreshold = cf2_doubleToFixed( 0.1 );
glyphpath->moveIsPending = TRUE;
glyphpath->pathIsOpen = FALSE;
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
glyphpath->pathIsClosing = FALSE;
glyphpath->elemIsQueued = FALSE;
}
FT_LOCAL_DEF( void )
cf2_glyphpath_finalize( CF2_GlyphPath glyphpath )
{
cf2_arrstack_finalize( &glyphpath->hintMoves );
}
/*
* Hint point in y-direction and apply outerTransform.
* Input `current' hint map (which is actually delayed by one element).
* Input x,y point in Character Space.
* Output x,y point in Device Space, including translation.
*/
static void
cf2_glyphpath_hintPoint( CF2_GlyphPath glyphpath,
CF2_HintMap hintmap,
FT_Vector* ppt,
CF2_Fixed x,
CF2_Fixed y )
{
FT_Vector pt; /* hinted point in upright DS */
pt.x = ADD_INT32( FT_MulFix( glyphpath->scaleX, x ),
FT_MulFix( glyphpath->scaleC, y ) );
pt.y = cf2_hintmap_map( hintmap, y );
ppt->x = ADD_INT32(
FT_MulFix( glyphpath->font->outerTransform.a, pt.x ),
ADD_INT32(
FT_MulFix( glyphpath->font->outerTransform.c, pt.y ),
glyphpath->fractionalTranslation.x ) );
ppt->y = ADD_INT32(
FT_MulFix( glyphpath->font->outerTransform.b, pt.x ),
ADD_INT32(
FT_MulFix( glyphpath->font->outerTransform.d, pt.y ),
glyphpath->fractionalTranslation.y ) );
}
/*
* From two line segments, (u1,u2) and (v1,v2), compute a point of
* intersection on the corresponding lines.
* Return false if no intersection is found, or if the intersection is
* too far away from the ends of the line segments, u2 and v1.
*
*/
static FT_Bool
cf2_glyphpath_computeIntersection( CF2_GlyphPath glyphpath,
const FT_Vector* u1,
const FT_Vector* u2,
const FT_Vector* v1,
const FT_Vector* v2,
FT_Vector* intersection )
{
/*
* Let `u' be a zero-based vector from the first segment, `v' from the
* second segment.
* Let `w 'be the zero-based vector from `u1' to `v1'.
* `perp' is the `perpendicular dot product'; see
* https://mathworld.wolfram.com/PerpDotProduct.html.
* `s' is the parameter for the parametric line for the first segment
* (`u').
*
* See notation in
2018-12-25 22:44:05 +01:00
* http://geomalgorithms.com/a05-_intersect-1.html.
* Calculations are done in 16.16, but must handle the squaring of
* line lengths in character space. We scale all vectors by 1/32 to
* avoid overflow. This allows values up to 4095 to be squared. The
* scale factor cancels in the divide.
*
* TODO: the scale factor could be computed from UnitsPerEm.
*
*/
#define cf2_perp( a, b ) \
( FT_MulFix( a.x, b.y ) - FT_MulFix( a.y, b.x ) )
/* round and divide by 32 */
#define CF2_CS_SCALE( x ) \
( ( (x) + 0x10 ) >> 5 )
FT_Vector u, v, w; /* scaled vectors */
CF2_Fixed denominator, s;
u.x = CF2_CS_SCALE( SUB_INT32( u2->x, u1->x ) );
u.y = CF2_CS_SCALE( SUB_INT32( u2->y, u1->y ) );
v.x = CF2_CS_SCALE( SUB_INT32( v2->x, v1->x ) );
v.y = CF2_CS_SCALE( SUB_INT32( v2->y, v1->y ) );
w.x = CF2_CS_SCALE( SUB_INT32( v1->x, u1->x ) );
w.y = CF2_CS_SCALE( SUB_INT32( v1->y, u1->y ) );
denominator = cf2_perp( u, v );
if ( denominator == 0 )
return FALSE; /* parallel or coincident lines */
s = FT_DivFix( cf2_perp( w, v ), denominator );
intersection->x = ADD_INT32( u1->x,
FT_MulFix( s, SUB_INT32( u2->x, u1->x ) ) );
intersection->y = ADD_INT32( u1->y,
FT_MulFix( s, SUB_INT32( u2->y, u1->y ) ) );
/*
* Special case snapping for horizontal and vertical lines.
* This cleans up intersections and reduces problems with winding
* order detection.
* Sample case is sbc cd KozGoPr6N-Medium.otf 20 16685.
* Note: these calculations are in character space.
*
*/
if ( u1->x == u2->x &&
cf2_fixedAbs( SUB_INT32( intersection->x,
u1->x ) ) < glyphpath->snapThreshold )
intersection->x = u1->x;
if ( u1->y == u2->y &&
cf2_fixedAbs( SUB_INT32( intersection->y,
u1->y ) ) < glyphpath->snapThreshold )
intersection->y = u1->y;
if ( v1->x == v2->x &&
cf2_fixedAbs( SUB_INT32( intersection->x,
v1->x ) ) < glyphpath->snapThreshold )
intersection->x = v1->x;
if ( v1->y == v2->y &&
cf2_fixedAbs( SUB_INT32( intersection->y,
v1->y ) ) < glyphpath->snapThreshold )
intersection->y = v1->y;
/* limit the intersection distance from midpoint of u2 and v1 */
if ( cf2_fixedAbs( intersection->x - ADD_INT32( u2->x, v1->x ) / 2 ) >
glyphpath->miterLimit ||
cf2_fixedAbs( intersection->y - ADD_INT32( u2->y, v1->y ) / 2 ) >
glyphpath->miterLimit )
return FALSE;
return TRUE;
}
/*
* Push the cached element (glyphpath->prevElem*) to the outline
* consumer. When a darkening offset is used, the end point of the
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
* cached element may be adjusted to an intersection point or we may
* synthesize a connecting line to the current element. If we are
* closing a subpath, we may also generate a connecting line to the start
* point.
*
* This is where Character Space (CS) is converted to Device Space (DS)
* using a hint map. This calculation must use a HintMap that was valid
* at the time the element was saved. For the first point in a subpath,
* that is a saved HintMap. For most elements, it just means the caller
* has delayed building a HintMap from the current HintMask.
*
* Transform each point with outerTransform and call the outline
* callbacks. This is a general 3x3 transform:
*
* x' = a*x + c*y + tx, y' = b*x + d*y + ty
*
* but it uses 4 elements from CF2_Font and the translation part
* from CF2_GlyphPath.
*
*/
static void
cf2_glyphpath_pushPrevElem( CF2_GlyphPath glyphpath,
CF2_HintMap hintmap,
FT_Vector* nextP0,
FT_Vector nextP1,
FT_Bool close )
{
CF2_CallbackParamsRec params;
FT_Vector* prevP0;
FT_Vector* prevP1;
FT_Vector intersection = { 0, 0 };
FT_Bool useIntersection = FALSE;
FT_ASSERT( glyphpath->prevElemOp == CF2_PathOpLineTo ||
glyphpath->prevElemOp == CF2_PathOpCubeTo );
if ( glyphpath->prevElemOp == CF2_PathOpLineTo )
{
prevP0 = &glyphpath->prevElemP0;
prevP1 = &glyphpath->prevElemP1;
}
else
{
prevP0 = &glyphpath->prevElemP2;
prevP1 = &glyphpath->prevElemP3;
}
/* optimization: if previous and next elements are offset by the same */
/* amount, then there will be no gap, and no need to compute an */
/* intersection. */
if ( prevP1->x != nextP0->x || prevP1->y != nextP0->y )
{
/* previous element does not join next element: */
/* adjust end point of previous element to the intersection */
useIntersection = cf2_glyphpath_computeIntersection( glyphpath,
prevP0,
prevP1,
nextP0,
&nextP1,
&intersection );
if ( useIntersection )
{
/* modify the last point of the cached element (either line or */
/* curve) */
*prevP1 = intersection;
}
}
params.pt0 = glyphpath->currentDS;
switch( glyphpath->prevElemOp )
{
case CF2_PathOpLineTo:
params.op = CF2_PathOpLineTo;
/* note: pt2 and pt3 are unused */
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
if ( close )
{
/* use first hint map if closing */
cf2_glyphpath_hintPoint( glyphpath,
&glyphpath->firstHintMap,
&params.pt1,
glyphpath->prevElemP1.x,
glyphpath->prevElemP1.y );
}
else
{
cf2_glyphpath_hintPoint( glyphpath,
hintmap,
&params.pt1,
glyphpath->prevElemP1.x,
glyphpath->prevElemP1.y );
}
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
/* output only non-zero length lines */
if ( params.pt0.x != params.pt1.x || params.pt0.y != params.pt1.y )
{
glyphpath->callbacks->lineTo( glyphpath->callbacks, &params );
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
glyphpath->currentDS = params.pt1;
}
break;
case CF2_PathOpCubeTo:
params.op = CF2_PathOpCubeTo;
/* TODO: should we intersect the interior joins (p1-p2 and p2-p3)? */
cf2_glyphpath_hintPoint( glyphpath,
hintmap,
&params.pt1,
glyphpath->prevElemP1.x,
glyphpath->prevElemP1.y );
cf2_glyphpath_hintPoint( glyphpath,
hintmap,
&params.pt2,
glyphpath->prevElemP2.x,
glyphpath->prevElemP2.y );
cf2_glyphpath_hintPoint( glyphpath,
hintmap,
&params.pt3,
glyphpath->prevElemP3.x,
glyphpath->prevElemP3.y );
glyphpath->callbacks->cubeTo( glyphpath->callbacks, &params );
glyphpath->currentDS = params.pt3;
break;
}
if ( !useIntersection || close )
{
/* insert connecting line between end of previous element and start */
/* of current one */
/* note: at the end of a subpath, we might do both, so use `nextP0' */
/* before we change it, below */
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
if ( close )
{
/* if we are closing the subpath, then nextP0 is in the first */
/* hint zone */
cf2_glyphpath_hintPoint( glyphpath,
&glyphpath->firstHintMap,
&params.pt1,
nextP0->x,
nextP0->y );
}
else
{
cf2_glyphpath_hintPoint( glyphpath,
hintmap,
&params.pt1,
nextP0->x,
nextP0->y );
}
if ( params.pt1.x != glyphpath->currentDS.x ||
params.pt1.y != glyphpath->currentDS.y )
{
/* length is nonzero */
params.op = CF2_PathOpLineTo;
params.pt0 = glyphpath->currentDS;
/* note: pt2 and pt3 are unused */
glyphpath->callbacks->lineTo( glyphpath->callbacks, &params );
glyphpath->currentDS = params.pt1;
}
}
if ( useIntersection )
{
/* return intersection point to caller */
*nextP0 = intersection;
}
}
/* push a MoveTo element based on current point and offset of current */
/* element */
static void
cf2_glyphpath_pushMove( CF2_GlyphPath glyphpath,
FT_Vector start )
{
CF2_CallbackParamsRec params;
params.op = CF2_PathOpMoveTo;
params.pt0 = glyphpath->currentDS;
/* Test if move has really happened yet; it would have called */
/* `cf2_hintmap_build' to set `isValid'. */
if ( !cf2_hintmap_isValid( &glyphpath->hintMap ) )
{
/* we are here iff first subpath is missing a moveto operator: */
/* synthesize first moveTo to finish initialization of hintMap */
cf2_glyphpath_moveTo( glyphpath,
glyphpath->start.x,
glyphpath->start.y );
}
cf2_glyphpath_hintPoint( glyphpath,
&glyphpath->hintMap,
&params.pt1,
start.x,
start.y );
/* note: pt2 and pt3 are unused */
glyphpath->callbacks->moveTo( glyphpath->callbacks, &params );
glyphpath->currentDS = params.pt1;
glyphpath->offsetStart0 = start;
}
/*
* All coordinates are in character space.
* On input, (x1, y1) and (x2, y2) give line segment.
* On output, (x, y) give offset vector.
* We use a piecewise approximation to trig functions.
*
* TODO: Offset true perpendicular and proper length
* supply the y-translation for hinting here, too,
* that adds yOffset unconditionally to *y.
*/
static void
cf2_glyphpath_computeOffset( CF2_GlyphPath glyphpath,
CF2_Fixed x1,
CF2_Fixed y1,
CF2_Fixed x2,
CF2_Fixed y2,
CF2_Fixed* x,
CF2_Fixed* y )
{
CF2_Fixed dx = SUB_INT32( x2, x1 );
CF2_Fixed dy = SUB_INT32( y2, y1 );
/* note: negative offsets don't work here; negate deltas to change */
/* quadrants, below */
if ( glyphpath->font->reverseWinding )
{
dx = NEG_INT32( dx );
dy = NEG_INT32( dy );
}
*x = *y = 0;
if ( !glyphpath->darken )
return;
/* add momentum for this path element */
glyphpath->callbacks->windingMomentum =
ADD_INT32( glyphpath->callbacks->windingMomentum,
cf2_getWindingMomentum( x1, y1, x2, y2 ) );
/* note: allow mixed integer and fixed multiplication here */
if ( dx >= 0 )
{
if ( dy >= 0 )
{
/* first quadrant, +x +y */
if ( dx > MUL_INT32( 2, dy ) )
{
/* +x */
*x = 0;
*y = 0;
}
else if ( dy > MUL_INT32( 2, dx ) )
{
/* +y */
*x = glyphpath->xOffset;
*y = glyphpath->yOffset;
}
else
{
/* +x +y */
*x = FT_MulFix( cf2_doubleToFixed( 0.7 ),
glyphpath->xOffset );
*y = FT_MulFix( cf2_doubleToFixed( 1.0 - 0.7 ),
glyphpath->yOffset );
}
}
else
{
/* fourth quadrant, +x -y */
if ( dx > MUL_INT32( -2, dy ) )
{
/* +x */
*x = 0;
*y = 0;
}
else if ( NEG_INT32( dy ) > MUL_INT32( 2, dx ) )
{
/* -y */
*x = NEG_INT32( glyphpath->xOffset );
*y = glyphpath->yOffset;
}
else
{
/* +x -y */
*x = FT_MulFix( cf2_doubleToFixed( -0.7 ),
glyphpath->xOffset );
*y = FT_MulFix( cf2_doubleToFixed( 1.0 - 0.7 ),
glyphpath->yOffset );
}
}
}
else
{
if ( dy >= 0 )
{
/* second quadrant, -x +y */
if ( NEG_INT32( dx ) > MUL_INT32( 2, dy ) )
{
/* -x */
*x = 0;
*y = MUL_INT32( 2, glyphpath->yOffset );
}
else if ( dy > MUL_INT32( -2, dx ) )
{
/* +y */
*x = glyphpath->xOffset;
*y = glyphpath->yOffset;
}
else
{
/* -x +y */
*x = FT_MulFix( cf2_doubleToFixed( 0.7 ),
glyphpath->xOffset );
*y = FT_MulFix( cf2_doubleToFixed( 1.0 + 0.7 ),
glyphpath->yOffset );
}
}
else
{
/* third quadrant, -x -y */
if ( NEG_INT32( dx ) > MUL_INT32( -2, dy ) )
{
/* -x */
*x = 0;
*y = MUL_INT32( 2, glyphpath->yOffset );
}
else if ( NEG_INT32( dy ) > MUL_INT32( -2, dx ) )
{
/* -y */
*x = NEG_INT32( glyphpath->xOffset );
*y = glyphpath->yOffset;
}
else
{
/* -x -y */
*x = FT_MulFix( cf2_doubleToFixed( -0.7 ),
glyphpath->xOffset );
*y = FT_MulFix( cf2_doubleToFixed( 1.0 + 0.7 ),
glyphpath->yOffset );
}
}
}
}
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
/*
* The functions cf2_glyphpath_{moveTo,lineTo,curveTo,closeOpenPath} are
* called by the interpreter with Character Space (CS) coordinates. Each
* path element is placed into a queue of length one to await the
* calculation of the following element. At that time, the darkening
* offset of the following element is known and joins can be computed,
* including possible modification of this element, before mapping to
* Device Space (DS) and passing it on to the outline consumer.
*
*/
FT_LOCAL_DEF( void )
cf2_glyphpath_moveTo( CF2_GlyphPath glyphpath,
CF2_Fixed x,
CF2_Fixed y )
{
cf2_glyphpath_closeOpenPath( glyphpath );
/* save the parameters of the move for later, when we'll know how to */
/* offset it; */
/* also save last move point */
glyphpath->currentCS.x = glyphpath->start.x = x;
glyphpath->currentCS.y = glyphpath->start.y = y;
glyphpath->moveIsPending = TRUE;
/* ensure we have a valid map with current mask */
if ( !cf2_hintmap_isValid( &glyphpath->hintMap ) ||
cf2_hintmask_isNew( glyphpath->hintMask ) )
cf2_hintmap_build( &glyphpath->hintMap,
glyphpath->hStemHintArray,
glyphpath->vStemHintArray,
glyphpath->hintMask,
glyphpath->hintOriginY,
FALSE );
/* save a copy of current HintMap to use when drawing initial point */
glyphpath->firstHintMap = glyphpath->hintMap; /* structure copy */
}
FT_LOCAL_DEF( void )
cf2_glyphpath_lineTo( CF2_GlyphPath glyphpath,
CF2_Fixed x,
CF2_Fixed y )
{
CF2_Fixed xOffset, yOffset;
FT_Vector P0, P1;
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
FT_Bool newHintMap;
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
/*
* New hints will be applied after cf2_glyphpath_pushPrevElem has run.
* In case this is a synthesized closing line, any new hints should be
* delayed until this path is closed (`cf2_hintmask_isNew' will be
* called again before the next line or curve).
*/
/* true if new hint map not on close */
newHintMap = cf2_hintmask_isNew( glyphpath->hintMask ) &&
!glyphpath->pathIsClosing;
/*
* Zero-length lines may occur in the charstring. Because we cannot
* compute darkening offsets or intersections from zero-length lines,
* it is best to remove them and avoid artifacts. However, zero-length
* lines in CS at the start of a new hint map can generate non-zero
* lines in DS due to hint substitution. We detect a change in hint
* map here and pass those zero-length lines along.
*/
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
/*
* Note: Find explicitly closed paths here with a conditional
* breakpoint using
*
* !gp->pathIsClosing && gp->start.x == x && gp->start.y == y
*
*/
if ( glyphpath->currentCS.x == x &&
glyphpath->currentCS.y == y &&
!newHintMap )
/*
* Ignore zero-length lines in CS where the hint map is the same
* because the line in DS will also be zero length.
*
* Ignore zero-length lines when we synthesize a closing line because
* the close will be handled in cf2_glyphPath_pushPrevElem.
*/
return;
cf2_glyphpath_computeOffset( glyphpath,
glyphpath->currentCS.x,
glyphpath->currentCS.y,
x,
y,
&xOffset,
&yOffset );
/* construct offset points */
P0.x = ADD_INT32( glyphpath->currentCS.x, xOffset );
P0.y = ADD_INT32( glyphpath->currentCS.y, yOffset );
P1.x = ADD_INT32( x, xOffset );
P1.y = ADD_INT32( y, yOffset );
if ( glyphpath->moveIsPending )
{
/* emit offset 1st point as MoveTo */
cf2_glyphpath_pushMove( glyphpath, P0 );
glyphpath->moveIsPending = FALSE; /* adjust state machine */
glyphpath->pathIsOpen = TRUE;
glyphpath->offsetStart1 = P1; /* record second point */
}
if ( glyphpath->elemIsQueued )
{
FT_ASSERT( cf2_hintmap_isValid( &glyphpath->hintMap ) ||
glyphpath->hintMap.count == 0 );
cf2_glyphpath_pushPrevElem( glyphpath,
&glyphpath->hintMap,
&P0,
P1,
FALSE );
}
/* queue the current element with offset points */
glyphpath->elemIsQueued = TRUE;
glyphpath->prevElemOp = CF2_PathOpLineTo;
glyphpath->prevElemP0 = P0;
glyphpath->prevElemP1 = P1;
/* update current map */
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
if ( newHintMap )
cf2_hintmap_build( &glyphpath->hintMap,
glyphpath->hStemHintArray,
glyphpath->vStemHintArray,
glyphpath->hintMask,
glyphpath->hintOriginY,
FALSE );
glyphpath->currentCS.x = x; /* pre-offset current point */
glyphpath->currentCS.y = y;
}
FT_LOCAL_DEF( void )
cf2_glyphpath_curveTo( CF2_GlyphPath glyphpath,
CF2_Fixed x1,
CF2_Fixed y1,
CF2_Fixed x2,
CF2_Fixed y2,
CF2_Fixed x3,
CF2_Fixed y3 )
{
CF2_Fixed xOffset1, yOffset1, xOffset3, yOffset3;
FT_Vector P0, P1, P2, P3;
/* TODO: ignore zero length portions of curve?? */
cf2_glyphpath_computeOffset( glyphpath,
glyphpath->currentCS.x,
glyphpath->currentCS.y,
x1,
y1,
&xOffset1,
&yOffset1 );
cf2_glyphpath_computeOffset( glyphpath,
x2,
y2,
x3,
y3,
&xOffset3,
&yOffset3 );
/* add momentum from the middle segment */
glyphpath->callbacks->windingMomentum =
ADD_INT32( glyphpath->callbacks->windingMomentum,
cf2_getWindingMomentum( x1, y1, x2, y2 ) );
/* construct offset points */
P0.x = ADD_INT32( glyphpath->currentCS.x, xOffset1 );
P0.y = ADD_INT32( glyphpath->currentCS.y, yOffset1 );
P1.x = ADD_INT32( x1, xOffset1 );
P1.y = ADD_INT32( y1, yOffset1 );
/* note: preserve angle of final segment by using offset3 at both ends */
P2.x = ADD_INT32( x2, xOffset3 );
P2.y = ADD_INT32( y2, yOffset3 );
P3.x = ADD_INT32( x3, xOffset3 );
P3.y = ADD_INT32( y3, yOffset3 );
if ( glyphpath->moveIsPending )
{
/* emit offset 1st point as MoveTo */
cf2_glyphpath_pushMove( glyphpath, P0 );
glyphpath->moveIsPending = FALSE;
glyphpath->pathIsOpen = TRUE;
glyphpath->offsetStart1 = P1; /* record second point */
}
if ( glyphpath->elemIsQueued )
{
FT_ASSERT( cf2_hintmap_isValid( &glyphpath->hintMap ) ||
glyphpath->hintMap.count == 0 );
cf2_glyphpath_pushPrevElem( glyphpath,
&glyphpath->hintMap,
&P0,
P1,
FALSE );
}
/* queue the current element with offset points */
glyphpath->elemIsQueued = TRUE;
glyphpath->prevElemOp = CF2_PathOpCubeTo;
glyphpath->prevElemP0 = P0;
glyphpath->prevElemP1 = P1;
glyphpath->prevElemP2 = P2;
glyphpath->prevElemP3 = P3;
/* update current map */
if ( cf2_hintmask_isNew( glyphpath->hintMask ) )
cf2_hintmap_build( &glyphpath->hintMap,
glyphpath->hStemHintArray,
glyphpath->vStemHintArray,
glyphpath->hintMask,
glyphpath->hintOriginY,
FALSE );
glyphpath->currentCS.x = x3; /* pre-offset current point */
glyphpath->currentCS.y = y3;
}
FT_LOCAL_DEF( void )
cf2_glyphpath_closeOpenPath( CF2_GlyphPath glyphpath )
{
if ( glyphpath->pathIsOpen )
{
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
/*
* A closing line in Character Space line is always generated below
* with `cf2_glyphPath_lineTo'. It may be ignored later if it turns
* out to be zero length in Device Space.
*/
glyphpath->pathIsClosing = TRUE;
cf2_glyphpath_lineTo( glyphpath,
glyphpath->start.x,
glyphpath->start.y );
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
/* empty the final element from the queue and close the path */
if ( glyphpath->elemIsQueued )
cf2_glyphpath_pushPrevElem( glyphpath,
&glyphpath->hintMap,
&glyphpath->offsetStart0,
glyphpath->offsetStart1,
TRUE );
/* reset state machine */
glyphpath->moveIsPending = TRUE;
glyphpath->pathIsOpen = FALSE;
Fix Savannah bug #39295. The bug was caused by switching to the initial hintmap (the one in effect when `moveto' executes) just before drawing the final element in the charstring. This ensured that the path was closed (in both Character Space and Device Space). But if the final element was a curve and if the final hintmap was different enough from the initial one, then the curve was visibly distorted. The first part of the fix is to draw the final curve using the final hintmap as specified by the charstring. This corrects the distortion but does not ensure closing in Device Space. It may require the rasterizer to automatically generate an extra closing line. Depending on the hintmap differences, this line could be from zero to a couple pixels in length. The second part of the fix covers the case where the charstring subpath is closed with an explicit line. We now modify that line's end point to avoid the distortion. Some glyphs in the bug report font (TexGyreHeros-Regular) that show the change are: 25ppem S (98) 24ppem eight (52) 25.5ppem p (85) Curves at the *end* of a subpath are no longer distorted. However, some of these glyphs have bad hint substitutions in the middle of a subpath, and these are not affected. The patch has been tested with a set of 106 fonts that shipped with Adobe Creative Suite 4, together with 756 Open Source CFF fonts from Google Fonts. There are 1.5 million glyphs, of which some 20k are changed with the fix. A sampling of a few hundred of these changes have been examined more closely, and the changes look good (or at least acceptable). * src/cff/cf2hints.h (CF2_GlyphPathRec): New element `pathIsClosing' to indicate that we synthesize a closepath line. * src/cff/cf2hints.c (cf2_glyphpath_init): Updated. (cf2_glyphpath_pushPrevElem): If closing, use first hint map (for `lineto' operator) and adjust hint zone. For synthesized closing lines, use end point in first hint zone. (cf2_glyphpath_lineTo): Take care of synthesized closing lines. In particular, shift the detection of zero-length lines from character space to device space. (cf2_glyphpath_closeOpenPath): Remove assertion. Updated.
2013-09-29 16:17:02 +02:00
glyphpath->pathIsClosing = FALSE;
glyphpath->elemIsQueued = FALSE;
}
}
/* END */