/* get scaling and hint flag from GlyphSlot */ static void cf2_getScaleAndHintFlag( CFF_Decoder* decoder, CF2_Fixed* x_scale, CF2_Fixed* y_scale, FT_Bool* hinted, FT_Bool* scaled ) { FT_ASSERT( decoder && decoder->builder.glyph ); /* note: FreeType scale includes a factor of 64 */ *hinted = decoder->builder.glyph->hint; *scaled = decoder->builder.glyph->scaled; if ( *hinted ) { *x_scale = FT_DivFix( decoder->builder.glyph->x_scale, cf2_intToFixed( 64 ) ); *y_scale = FT_DivFix( decoder->builder.glyph->y_scale, cf2_intToFixed( 64 ) ); } else { /* for unhinted outlines, `cff_slot_load' does the scaling, */ /* thus render at `unity' scale */ *x_scale = 0x0400; /* 1/64 as 16.16 */ *y_scale = 0x0400; } }
cf2_getBlueMetrics( CFF_Decoder* decoder, CF2_Fixed* blueScale, CF2_Fixed* blueShift, CF2_Fixed* blueFuzz ) { FT_ASSERT( decoder && decoder->current_subfont ); *blueScale = FT_DivFix( decoder->current_subfont->private_dict.blue_scale, cf2_intToFixed( 1000 ) ); *blueShift = cf2_intToFixed( decoder->current_subfont->private_dict.blue_shift ); *blueFuzz = cf2_intToFixed( decoder->current_subfont->private_dict.blue_fuzz ); }
cf2_getNominalWidthX( CFF_Decoder* decoder ) { FT_ASSERT( decoder && decoder->current_subfont ); return cf2_intToFixed( decoder->current_subfont->private_dict.nominal_width ); }
cf2_getDefaultWidthX( CFF_Decoder* decoder ) { FT_ASSERT( decoder && decoder->current_subfont ); return cf2_intToFixed( decoder->current_subfont->private_dict.default_width ); }
/* * This check should avoid most internal overflow cases. Clients should * generally respond to `Glyph_Too_Big' by getting a glyph outline * at EM size, scaling it and filling it as a graphics operation. * */ static FT_Error cf2_checkTransform( const CF2_Matrix* transform, CF2_Int unitsPerEm ) { CF2_Fixed maxScale; FT_ASSERT( unitsPerEm > 0 ); if ( transform->a <= 0 || transform->d <= 0 ) return FT_THROW( Invalid_Size_Handle ); FT_ASSERT( transform->b == 0 && transform->c == 0 ); FT_ASSERT( transform->tx == 0 && transform->ty == 0 ); if ( unitsPerEm > 0x7FFF ) return FT_THROW( Glyph_Too_Big ); maxScale = FT_DivFix( CF2_MAX_SIZE, cf2_intToFixed( unitsPerEm ) ); if ( transform->a > maxScale || transform->d > maxScale ) return FT_THROW( Glyph_Too_Big ); return FT_Err_Ok; }
cf2_getStdHW( CFF_Decoder* decoder ) { FT_ASSERT( decoder && decoder->current_subfont ); return cf2_intToFixed( decoder->current_subfont->private_dict.standard_width ); }
cf2_getPpemY( CFF_Decoder* decoder ) { FT_ASSERT( decoder && decoder->builder.face && decoder->builder.face->root.size ); FT_ASSERT( decoder->builder.face->root.size->metrics.y_ppem ); return cf2_intToFixed( decoder->builder.face->root.size->metrics.y_ppem ); }
cf2_stack_popFixed( CF2_Stack stack ) { if ( stack->top == &stack->buffer[0] ) { CF2_SET_ERROR( stack->error, Stack_Underflow ); return cf2_intToFixed( 0 ); /* underflow */ } --stack->top; switch ( stack->top->type ) { case CF2_NumberInt: return cf2_intToFixed( stack->top->u.i ); case CF2_NumberFrac: return cf2_fracToFixed( stack->top->u.f ); default: return stack->top->u.r; } }
cf2_stack_getReal( CF2_Stack stack, CF2_UInt idx ) { FT_ASSERT( cf2_stack_count( stack ) <= CF2_OPERAND_STACK_SIZE ); if ( idx >= cf2_stack_count( stack ) ) { CF2_SET_ERROR( stack->error, Stack_Overflow ); return cf2_intToFixed( 0 ); /* bounds error */ } switch ( stack->buffer[idx].type ) { case CF2_NumberInt: return cf2_intToFixed( stack->buffer[idx].u.i ); case CF2_NumberFrac: return cf2_fracToFixed( stack->buffer[idx].u.f ); default: return stack->buffer[idx].u.r; } }
cf2_getPpemY( CFF_Decoder* decoder ) { FT_ASSERT( decoder && decoder->builder.face && decoder->builder.face->root.size ); /* * Note that `y_ppem' can be zero if there wasn't a call to * `FT_Set_Char_Size' or something similar. However, this isn't a * problem since we come to this place in the code only if * FT_LOAD_NO_SCALE is set (the other case gets caught by * `cf2_checkTransform'). The ppem value is needed to compute the stem * darkening, which is disabled for getting the unscaled outline. * */ return cf2_intToFixed( decoder->builder.face->root.size->metrics.y_ppem ); }
cf2_blues_init( CF2_Blues blues, CF2_Font font ) { /* pointer to parsed font object */ CFF_Decoder* decoder = font->decoder; CF2_Fixed zoneHeight; CF2_Fixed maxZoneHeight = 0; CF2_Fixed csUnitsPerPixel; size_t numBlueValues; size_t numOtherBlues; size_t numFamilyBlues; size_t numFamilyOtherBlues; FT_Pos* blueValues; FT_Pos* otherBlues; FT_Pos* familyBlues; FT_Pos* familyOtherBlues; size_t i; CF2_Fixed emBoxBottom, emBoxTop; CF2_Int unitsPerEm = font->unitsPerEm; if ( unitsPerEm == 0 ) unitsPerEm = 1000; FT_ZERO( blues ); blues->scale = font->innerTransform.d; cf2_getBlueMetrics( decoder, &blues->blueScale, &blues->blueShift, &blues->blueFuzz ); cf2_getBlueValues( decoder, &numBlueValues, &blueValues ); cf2_getOtherBlues( decoder, &numOtherBlues, &otherBlues ); cf2_getFamilyBlues( decoder, &numFamilyBlues, &familyBlues ); cf2_getFamilyOtherBlues( decoder, &numFamilyOtherBlues, &familyOtherBlues ); /* * synthetic em box hint heuristic * * Apply this when ideographic dictionary (LanguageGroup 1) has no * real alignment zones. Adobe tools generate dummy zones at -250 and * 1100 for a 1000 unit em. Fonts with ICF-based alignment zones * should not enable the heuristic. When the heuristic is enabled, * the font's blue zones are ignored. * */ /* get em box from OS/2 typoAscender/Descender */ /* TODO: FreeType does not parse these metrics. Skip them for now. */ #if 0 FCM_getHorizontalLineMetrics( &e, font->font, &ascender, &descender, &linegap ); if ( ascender - descender == unitsPerEm ) { emBoxBottom = cf2_intToFixed( descender ); emBoxTop = cf2_intToFixed( ascender ); } else #endif { emBoxBottom = CF2_ICF_Bottom; emBoxTop = CF2_ICF_Top; } if ( cf2_getLanguageGroup( decoder ) == 1 && ( numBlueValues == 0 || ( numBlueValues == 4 && cf2_blueToFixed( blueValues[0] ) < emBoxBottom && cf2_blueToFixed( blueValues[1] ) < emBoxBottom && cf2_blueToFixed( blueValues[2] ) > emBoxTop && cf2_blueToFixed( blueValues[3] ) > emBoxTop ) ) ) { /* * Construct hint edges suitable for synthetic ghost hints at top * and bottom of em box. +-CF2_MIN_COUNTER allows for unhinted * features above or below the last hinted edge. This also gives a * net 1 pixel boost to the height of ideographic glyphs. * * Note: Adjust synthetic hints outward by epsilon (0x.0001) to * avoid interference. E.g., some fonts have real hints at * 880 and -120. */ blues->emBoxBottomEdge.csCoord = emBoxBottom - CF2_FIXED_EPSILON; blues->emBoxBottomEdge.dsCoord = cf2_fixedRound( FT_MulFix( blues->emBoxBottomEdge.csCoord, blues->scale ) ) - CF2_MIN_COUNTER; blues->emBoxBottomEdge.scale = blues->scale; blues->emBoxBottomEdge.flags = CF2_GhostBottom | CF2_Locked | CF2_Synthetic; blues->emBoxTopEdge.csCoord = emBoxTop + CF2_FIXED_EPSILON + 2 * font->darkenY; blues->emBoxTopEdge.dsCoord = cf2_fixedRound( FT_MulFix( blues->emBoxTopEdge.csCoord, blues->scale ) ) + CF2_MIN_COUNTER; blues->emBoxTopEdge.scale = blues->scale; blues->emBoxTopEdge.flags = CF2_GhostTop | CF2_Locked | CF2_Synthetic; blues->doEmBoxHints = TRUE; /* enable the heuristic */ return; } /* copy `BlueValues' and `OtherBlues' to a combined array of top and */ /* bottom zones */ for ( i = 0; i < numBlueValues; i += 2 ) { blues->zone[blues->count].csBottomEdge = cf2_blueToFixed( blueValues[i] ); blues->zone[blues->count].csTopEdge = cf2_blueToFixed( blueValues[i + 1] ); zoneHeight = blues->zone[blues->count].csTopEdge - blues->zone[blues->count].csBottomEdge; if ( zoneHeight < 0 ) { FT_TRACE4(( "cf2_blues_init: ignoring negative zone height\n" )); continue; /* reject this zone */ } if ( zoneHeight > maxZoneHeight ) { /* take maximum before darkening adjustment */ /* so overshoot suppression point doesn't change */ maxZoneHeight = zoneHeight; } /* adjust both edges of top zone upward by twice darkening amount */ if ( i != 0 ) { blues->zone[blues->count].csTopEdge += 2 * font->darkenY; blues->zone[blues->count].csBottomEdge += 2 * font->darkenY; } /* first `BlueValue' is bottom zone; others are top */ if ( i == 0 ) { blues->zone[blues->count].bottomZone = TRUE; blues->zone[blues->count].csFlatEdge = blues->zone[blues->count].csTopEdge; } else { blues->zone[blues->count].bottomZone = FALSE; blues->zone[blues->count].csFlatEdge = blues->zone[blues->count].csBottomEdge; } blues->count += 1; } for ( i = 0; i < numOtherBlues; i += 2 ) { blues->zone[blues->count].csBottomEdge = cf2_blueToFixed( otherBlues[i] ); blues->zone[blues->count].csTopEdge = cf2_blueToFixed( otherBlues[i + 1] ); zoneHeight = blues->zone[blues->count].csTopEdge - blues->zone[blues->count].csBottomEdge; if ( zoneHeight < 0 ) { FT_TRACE4(( "cf2_blues_init: ignoring negative zone height\n" )); continue; /* reject this zone */ } if ( zoneHeight > maxZoneHeight ) { /* take maximum before darkening adjustment */ /* so overshoot suppression point doesn't change */ maxZoneHeight = zoneHeight; } /* Note: bottom zones are not adjusted for darkening amount */ /* all OtherBlues are bottom zone */ blues->zone[blues->count].bottomZone = TRUE; blues->zone[blues->count].csFlatEdge = blues->zone[blues->count].csTopEdge; blues->count += 1; } /* Adjust for FamilyBlues */ /* Search for the nearest flat edge in `FamilyBlues' or */ /* `FamilyOtherBlues'. According to the Black Book, any matching edge */ /* must be within one device pixel */ csUnitsPerPixel = FT_DivFix( cf2_intToFixed( 1 ), blues->scale ); /* loop on all zones in this font */ for ( i = 0; i < blues->count; i++ ) { size_t j; CF2_Fixed minDiff; CF2_Fixed flatFamilyEdge, diff; /* value for this font */ CF2_Fixed flatEdge = blues->zone[i].csFlatEdge; if ( blues->zone[i].bottomZone ) { /* In a bottom zone, the top edge is the flat edge. */ /* Search `FamilyOtherBlues' for bottom zones; look for closest */ /* Family edge that is within the one pixel threshold. */ minDiff = CF2_FIXED_MAX; for ( j = 0; j < numFamilyOtherBlues; j += 2 ) { /* top edge */ flatFamilyEdge = cf2_blueToFixed( familyOtherBlues[j + 1] ); diff = cf2_fixedAbs( flatEdge - flatFamilyEdge ); if ( diff < minDiff && diff < csUnitsPerPixel ) { blues->zone[i].csFlatEdge = flatFamilyEdge; minDiff = diff; if ( diff == 0 ) break; } } /* check the first member of FamilyBlues, which is a bottom zone */ if ( numFamilyBlues >= 2 ) { /* top edge */ flatFamilyEdge = cf2_blueToFixed( familyBlues[1] ); diff = cf2_fixedAbs( flatEdge - flatFamilyEdge ); if ( diff < minDiff && diff < csUnitsPerPixel ) blues->zone[i].csFlatEdge = flatFamilyEdge; } } else { /* In a top zone, the bottom edge is the flat edge. */ /* Search `FamilyBlues' for top zones; skip first zone, which is a */ /* bottom zone; look for closest Family edge that is within the */ /* one pixel threshold */ minDiff = CF2_FIXED_MAX; for ( j = 2; j < numFamilyBlues; j += 2 ) { /* bottom edge */ flatFamilyEdge = cf2_blueToFixed( familyBlues[j] ); /* adjust edges of top zone upward by twice darkening amount */ flatFamilyEdge += 2 * font->darkenY; /* bottom edge */ diff = cf2_fixedAbs( flatEdge - flatFamilyEdge ); if ( diff < minDiff && diff < csUnitsPerPixel ) { blues->zone[i].csFlatEdge = flatFamilyEdge; minDiff = diff; if ( diff == 0 ) break; } } } } /* TODO: enforce separation of zones, including BlueFuzz */ /* Adjust BlueScale; similar to AdjustBlueScale() in coretype */ /* `bcsetup.c'. */ if ( maxZoneHeight > 0 ) { if ( blues->blueScale > FT_DivFix( cf2_intToFixed( 1 ), maxZoneHeight ) ) { /* clamp at maximum scale */ blues->blueScale = FT_DivFix( cf2_intToFixed( 1 ), maxZoneHeight ); } /* * TODO: Revisit the bug fix for 613448. The minimum scale * requirement catches a number of library fonts. For * example, with default BlueScale (.039625) and 0.4 minimum, * the test below catches any font with maxZoneHeight < 10.1. * There are library fonts ranging from 2 to 10 that get * caught, including e.g., Eurostile LT Std Medium with * maxZoneHeight of 6. * */ #if 0 if ( blueScale < .4 / maxZoneHeight ) { tetraphilia_assert( 0 ); /* clamp at minimum scale, per bug 0613448 fix */ blueScale = .4 / maxZoneHeight; } #endif } /* * Suppress overshoot and boost blue zones at small sizes. Boost * amount varies linearly from 0.5 pixel near 0 to 0 pixel at * blueScale cutoff. * Note: This boost amount is different from the coretype heuristic. * */ if ( blues->scale < blues->blueScale ) { blues->suppressOvershoot = TRUE; /* Change rounding threshold for `dsFlatEdge'. */ /* Note: constant changed from 0.5 to 0.6 to avoid a problem with */ /* 10ppem Arial */ blues->boost = FT_MulFix( cf2_floatToFixed( .6 ), ( cf2_intToFixed( 1 ) - FT_DivFix( blues->scale, blues->blueScale ) ) ); if ( blues->boost > 0x7FFF ) { /* boost must remain less than 0.5, or baseline could go negative */ blues->boost = 0x7FFF; } } /* boost and darkening have similar effects; don't do both */ if ( font->stemDarkened ) blues->boost = 0; /* set device space alignment for each zone; */ /* apply boost amount before rounding flat edge */ for ( i = 0; i < blues->count; i++ ) { if ( blues->zone[i].bottomZone ) blues->zone[i].dsFlatEdge = cf2_fixedRound( FT_MulFix( blues->zone[i].csFlatEdge, blues->scale ) - blues->boost ); else blues->zone[i].dsFlatEdge = cf2_fixedRound( FT_MulFix( blues->zone[i].csFlatEdge, blues->scale ) + blues->boost ); } }
cf2_blues_capture( const CF2_Blues blues, CF2_Hint bottomHintEdge, CF2_Hint topHintEdge ) { /* TODO: validate? */ CF2_Fixed csFuzz = blues->blueFuzz; /* new position of captured edge */ CF2_Fixed dsNew; /* amount that hint is moved when positioned */ CF2_Fixed dsMove = 0; FT_Bool captured = FALSE; CF2_UInt i; /* assert edge flags are consistent */ FT_ASSERT( !cf2_hint_isTop( bottomHintEdge ) && !cf2_hint_isBottom( topHintEdge ) ); /* TODO: search once without blue fuzz for compatibility with coretype? */ for ( i = 0; i < blues->count; i++ ) { if ( blues->zone[i].bottomZone && cf2_hint_isBottom( bottomHintEdge ) ) { if ( ( blues->zone[i].csBottomEdge - csFuzz ) <= bottomHintEdge->csCoord && bottomHintEdge->csCoord <= ( blues->zone[i].csTopEdge + csFuzz ) ) { /* bottom edge captured by bottom zone */ if ( blues->suppressOvershoot ) dsNew = blues->zone[i].dsFlatEdge; else if ( ( blues->zone[i].csTopEdge - bottomHintEdge->csCoord ) >= blues->blueShift ) { /* guarantee minimum of 1 pixel overshoot */ dsNew = FT_MIN( cf2_fixedRound( bottomHintEdge->dsCoord ), blues->zone[i].dsFlatEdge - cf2_intToFixed( 1 ) ); } else { /* simply round captured edge */ dsNew = cf2_fixedRound( bottomHintEdge->dsCoord ); } dsMove = dsNew - bottomHintEdge->dsCoord; captured = TRUE; break; } } if ( !blues->zone[i].bottomZone && cf2_hint_isTop( topHintEdge ) ) { if ( ( blues->zone[i].csBottomEdge - csFuzz ) <= topHintEdge->csCoord && topHintEdge->csCoord <= ( blues->zone[i].csTopEdge + csFuzz ) ) { /* top edge captured by top zone */ if ( blues->suppressOvershoot ) dsNew = blues->zone[i].dsFlatEdge; else if ( ( topHintEdge->csCoord - blues->zone[i].csBottomEdge ) >= blues->blueShift ) { /* guarantee minimum of 1 pixel overshoot */ dsNew = FT_MAX( cf2_fixedRound( topHintEdge->dsCoord ), blues->zone[i].dsFlatEdge + cf2_intToFixed( 1 ) ); } else { /* simply round captured edge */ dsNew = cf2_fixedRound( topHintEdge->dsCoord ); } dsMove = dsNew - topHintEdge->dsCoord; captured = TRUE; break; } } } if ( captured ) { /* move both edges and flag them `locked' */ if ( cf2_hint_isValid( bottomHintEdge ) ) { bottomHintEdge->dsCoord += dsMove; cf2_hint_lock( bottomHintEdge ); } if ( cf2_hint_isValid( topHintEdge ) ) { topHintEdge->dsCoord += dsMove; cf2_hint_lock( topHintEdge ); } } return captured; }
/* Compute a stem darkening amount in character space. */ static void cf2_computeDarkening( CF2_Fixed emRatio, CF2_Fixed ppem, CF2_Fixed stemWidth, CF2_Fixed* darkenAmount, CF2_Fixed boldenAmount, FT_Bool stemDarkened ) { /* Internal calculations are done in units per thousand for */ /* convenience. */ CF2_Fixed stemWidthPer1000, scaledStem; *darkenAmount = 0; if ( boldenAmount == 0 && !stemDarkened ) return; /* protect against range problems and divide by zero */ if ( emRatio < cf2_floatToFixed( .01 ) ) return; if ( stemDarkened ) { /* convert from true character space to 1000 unit character space; */ /* add synthetic emboldening effect */ /* we have to assure that the computation of `scaledStem' */ /* and `stemWidthPer1000' don't overflow */ stemWidthPer1000 = FT_MulFix( stemWidth + boldenAmount, emRatio ); if ( emRatio > CF2_FIXED_ONE && stemWidthPer1000 <= ( stemWidth + boldenAmount ) ) { stemWidthPer1000 = 0; /* to pacify compiler */ scaledStem = cf2_intToFixed( 2333 ); } else { scaledStem = FT_MulFix( stemWidthPer1000, ppem ); if ( ppem > CF2_FIXED_ONE && scaledStem <= stemWidthPer1000 ) scaledStem = cf2_intToFixed( 2333 ); } /* * Total darkening amount is computed in 1000 unit character space * using the modified 5 part curve as Avalon rasterizer. * The darkening amount is smaller for thicker stems. * It becomes zero when the stem is thicker than 2.333 pixels. * * In Avalon rasterizer, * * darkenAmount = 0.5 pixels if scaledStem <= 0.5 pixels, * darkenAmount = 0.333 pixels if 1 <= scaledStem <= 1.667 pixels, * darkenAmount = 0 pixel if scaledStem >= 2.333 pixels, * * and piecewise linear in-between. * */ if ( scaledStem < cf2_intToFixed( 500 ) ) *darkenAmount = FT_DivFix( cf2_intToFixed( 400 ), ppem ); else if ( scaledStem < cf2_intToFixed( 1000 ) ) *darkenAmount = FT_DivFix( cf2_intToFixed( 525 ), ppem ) - FT_MulFix( stemWidthPer1000, cf2_floatToFixed( .25 ) ); else if ( scaledStem < cf2_intToFixed( 1667 ) ) *darkenAmount = FT_DivFix( cf2_intToFixed( 275 ), ppem ); else if ( scaledStem < cf2_intToFixed( 2333 ) ) *darkenAmount = FT_DivFix( cf2_intToFixed( 963 ), ppem ) - FT_MulFix( stemWidthPer1000, cf2_floatToFixed( .413 ) ); /* use half the amount on each side and convert back to true */ /* character space */ *darkenAmount = FT_DivFix( *darkenAmount, 2 * emRatio ); } /* add synthetic emboldening effect in character space */ *darkenAmount += boldenAmount / 2; }
/* caller's transform is adjusted for subpixel positioning */ static void cf2_font_setup( CF2_Font font, const CF2_Matrix* transform ) { /* pointer to parsed font object */ CFF_Decoder* decoder = font->decoder; FT_Bool needExtraSetup = FALSE; /* character space units */ CF2_Fixed boldenX = font->syntheticEmboldeningAmountX; CF2_Fixed boldenY = font->syntheticEmboldeningAmountY; CF2_Fixed ppem; /* clear previous error */ font->error = FT_Err_Ok; /* if a CID fontDict has changed, we need to recompute some cached */ /* data */ needExtraSetup = font->lastSubfont != cf2_getSubfont( decoder ); /* if ppem has changed, we need to recompute some cached data */ /* note: because of CID font matrix concatenation, ppem and transform */ /* do not necessarily track. */ ppem = cf2_getPpemY( decoder ); if ( font->ppem != ppem ) { font->ppem = ppem; needExtraSetup = TRUE; } /* copy hinted flag on each call */ font->hinted = font->renderingFlags & CF2_FlagsHinted; /* determine if transform has changed; */ /* include Fontmatrix but ignore translation */ if ( ft_memcmp( transform, &font->currentTransform, 4 * sizeof ( CF2_Fixed ) ) != 0 ) { /* save `key' information for `cache of one' matrix data; */ /* save client transform, without the translation */ font->currentTransform = *transform; font->currentTransform.tx = font->currentTransform.ty = cf2_intToFixed( 0 ); /* TODO: FreeType transform is simple scalar; for now, use identity */ /* for outer */ font->innerTransform = *transform; font->outerTransform.a = font->outerTransform.d = cf2_intToFixed( 1 ); font->outerTransform.b = font->outerTransform.c = cf2_intToFixed( 0 ); needExtraSetup = TRUE; } /* * font->darkened is set to true if there is a stem darkening request or * the font is synthetic emboldened. * font->darkened controls whether to adjust blue zones, winding order, * and hinting. * */ if ( font->stemDarkened != ( font->renderingFlags & CF2_FlagsDarkened ) ) { font->stemDarkened = font->renderingFlags & CF2_FlagsDarkened; /* blue zones depend on darkened flag */ needExtraSetup = TRUE; } /* recompute variables that are dependent on transform or FontDict or */ /* darken flag */ if ( needExtraSetup ) { /* StdVW is found in the private dictionary; */ /* recompute darkening amounts whenever private dictionary or */ /* transform change */ /* Note: a rendering flag turns darkening on or off, so we want to */ /* store the `on' amounts; */ /* darkening amount is computed in character space */ /* TODO: testing size-dependent darkening here; */ /* what to do for rotations? */ CF2_Fixed emRatio; CF2_Fixed stdHW; CF2_Int unitsPerEm = font->unitsPerEm; if ( unitsPerEm == 0 ) unitsPerEm = 1000; ppem = FT_MAX( cf2_intToFixed( 4 ), font->ppem ); /* use minimum ppem of 4 */ #if 0 /* since vstem is measured in the x-direction, we use the `a' member */ /* of the fontMatrix */ emRatio = cf2_fixedFracMul( cf2_intToFixed( 1000 ), fontMatrix->a ); #endif /* Freetype does not preserve the fontMatrix when parsing; use */ /* unitsPerEm instead. */ /* TODO: check precision of this */ emRatio = cf2_intToFixed( 1000 ) / unitsPerEm; font->stdVW = cf2_getStdVW( decoder ); if ( font->stdVW <= 0 ) font->stdVW = FT_DivFix( cf2_intToFixed( 75 ), emRatio ); if ( boldenX > 0 ) { /* Ensure that boldenX is at least 1 pixel for synthetic bold font */ /* (similar to what Avalon does) */ boldenX = FT_MAX( boldenX, FT_DivFix( cf2_intToFixed( unitsPerEm ), ppem ) ); /* Synthetic emboldening adds at least 1 pixel to darkenX, while */ /* stem darkening adds at most half pixel. Since the purpose of */ /* stem darkening (readability at small sizes) is met with */ /* synthetic emboldening, no need to add stem darkening for a */ /* synthetic bold font. */ cf2_computeDarkening( emRatio, ppem, font->stdVW, &font->darkenX, boldenX, FALSE ); } else cf2_computeDarkening( emRatio, ppem, font->stdVW, &font->darkenX, 0, font->stemDarkened ); #if 0 /* since hstem is measured in the y-direction, we use the `d' member */ /* of the fontMatrix */ /* TODO: use the same units per em as above; check this */ emRatio = cf2_fixedFracMul( cf2_intToFixed( 1000 ), fontMatrix->d ); #endif /* set the default stem width, because it must be the same for all */ /* family members; */ /* choose a constant for StdHW that depends on font contrast */ stdHW = cf2_getStdHW( decoder ); if ( stdHW > 0 && font->stdVW > 2 * stdHW ) font->stdHW = FT_DivFix( cf2_intToFixed( 75 ), emRatio ); else { /* low contrast font gets less hstem darkening */ font->stdHW = FT_DivFix( cf2_intToFixed( 110 ), emRatio ); } cf2_computeDarkening( emRatio, ppem, font->stdHW, &font->darkenY, boldenY, font->stemDarkened ); if ( font->darkenX != 0 || font->darkenY != 0 ) font->darkened = TRUE; else font->darkened = FALSE; font->reverseWinding = FALSE; /* initial expectation is CCW */ /* compute blue zones for this instance */ cf2_blues_init( &font->blues, font ); } }
/* Compute a stem darkening amount in character space. */ static void cf2_computeDarkening( CF2_Fixed emRatio, CF2_Fixed ppem, CF2_Fixed stemWidth, CF2_Fixed* darkenAmount, CF2_Fixed boldenAmount, FT_Bool stemDarkened, FT_Int* darkenParams ) { /* * Total darkening amount is computed in 1000 unit character space * using the modified 5 part curve as Adobe's Avalon rasterizer. * The darkening amount is smaller for thicker stems. * It becomes zero when the stem is thicker than 2.333 pixels. * * By default, we use * * darkenAmount = 0.4 pixels if scaledStem <= 0.5 pixels, * darkenAmount = 0.275 pixels if 1 <= scaledStem <= 1.667 pixels, * darkenAmount = 0 pixel if scaledStem >= 2.333 pixels, * * and piecewise linear in-between: * * * darkening * ^ * | * | (x1,y1) * |--------+ * | \ * | \ * | \ (x3,y3) * | +----------+ * | (x2,y2) \ * | \ * | \ * | +----------------- * | (x4,y4) * +---------------------------------------------> stem * thickness * * * This corresponds to the following values for the * `darkening-parameters' property: * * (x1, y1) = (500, 400) * (x2, y2) = (1000, 275) * (x3, y3) = (1667, 275) * (x4, y4) = (2333, 0) * */ /* Internal calculations are done in units per thousand for */ /* convenience. The x axis is scaled stem width in */ /* thousandths of a pixel. That is, 1000 is 1 pixel. */ /* The y axis is darkening amount in thousandths of a pixel.*/ /* In the code, below, dividing by ppem and */ /* adjusting for emRatio converts darkenAmount to character */ /* space (font units). */ CF2_Fixed stemWidthPer1000, scaledStem; *darkenAmount = 0; if ( boldenAmount == 0 && !stemDarkened ) return; /* protect against range problems and divide by zero */ if ( emRatio < cf2_floatToFixed( .01 ) ) return; if ( stemDarkened ) { FT_Int x1 = darkenParams[0]; FT_Int y1 = darkenParams[1]; FT_Int x2 = darkenParams[2]; FT_Int y2 = darkenParams[3]; FT_Int x3 = darkenParams[4]; FT_Int y3 = darkenParams[5]; FT_Int x4 = darkenParams[6]; FT_Int y4 = darkenParams[7]; /* convert from true character space to 1000 unit character space; */ /* add synthetic emboldening effect */ /* we have to assure that the computation of `scaledStem' */ /* and `stemWidthPer1000' don't overflow */ stemWidthPer1000 = FT_MulFix( stemWidth + boldenAmount, emRatio ); if ( emRatio > CF2_FIXED_ONE && stemWidthPer1000 <= ( stemWidth + boldenAmount ) ) { stemWidthPer1000 = 0; /* to pacify compiler */ scaledStem = cf2_intToFixed( x4 ); } else { scaledStem = FT_MulFix( stemWidthPer1000, ppem ); if ( ppem > CF2_FIXED_ONE && scaledStem <= stemWidthPer1000 ) scaledStem = cf2_intToFixed( x4 ); } /* now apply the darkening parameters */ if ( scaledStem < cf2_intToFixed( x1 ) ) *darkenAmount = FT_DivFix( cf2_intToFixed( y1 ), ppem ); else if ( scaledStem < cf2_intToFixed( x2 ) ) { FT_Int xdelta = x2 - x1; FT_Int ydelta = y2 - y1; FT_Int x = stemWidthPer1000 - FT_DivFix( cf2_intToFixed( x1 ), ppem ); if ( !xdelta ) goto Try_x3; *darkenAmount = FT_MulFix( x, FT_DivFix( ydelta, xdelta ) ) + FT_DivFix( cf2_intToFixed( y1 ), ppem ); } else if ( scaledStem < cf2_intToFixed( x3 ) ) { Try_x3: { FT_Int xdelta = x3 - x2; FT_Int ydelta = y3 - y2; FT_Int x = stemWidthPer1000 - FT_DivFix( cf2_intToFixed( x2 ), ppem ); if ( !xdelta ) goto Try_x4; *darkenAmount = FT_MulFix( x, FT_DivFix( ydelta, xdelta ) ) + FT_DivFix( cf2_intToFixed( y2 ), ppem ); } } else if ( scaledStem < cf2_intToFixed( x4 ) ) { Try_x4: { FT_Int xdelta = x4 - x3; FT_Int ydelta = y4 - y3; FT_Int x = stemWidthPer1000 - FT_DivFix( cf2_intToFixed( x3 ), ppem ); if ( !xdelta ) goto Use_y4; *darkenAmount = FT_MulFix( x, FT_DivFix( ydelta, xdelta ) ) + FT_DivFix( cf2_intToFixed( y3 ), ppem ); } } else { Use_y4: *darkenAmount = FT_DivFix( cf2_intToFixed( y4 ), ppem ); } /* use half the amount on each side and convert back to true */ /* character space */ *darkenAmount = FT_DivFix( *darkenAmount, 2 * emRatio ); } /* add synthetic emboldening effect in character space */ *darkenAmount += boldenAmount / 2; }
/* Compute a stem darkening amount in character space. */ static void cf2_computeDarkening( CF2_Fixed emRatio, CF2_Fixed ppem, CF2_Fixed stemWidth, CF2_Fixed* darkenAmount, CF2_Fixed boldenAmount, FT_Bool stemDarkened, FT_Int* darkenParams ) { /* * Total darkening amount is computed in 1000 unit character space * using the modified 5 part curve as Adobe's Avalon rasterizer. * The darkening amount is smaller for thicker stems. * It becomes zero when the stem is thicker than 2.333 pixels. * * By default, we use * * darkenAmount = 0.4 pixels if scaledStem <= 0.5 pixels, * darkenAmount = 0.275 pixels if 1 <= scaledStem <= 1.667 pixels, * darkenAmount = 0 pixel if scaledStem >= 2.333 pixels, * * and piecewise linear in-between: * * * darkening * ^ * | * | (x1,y1) * |--------+ * | \ * | \ * | \ (x3,y3) * | +----------+ * | (x2,y2) \ * | \ * | \ * | +----------------- * | (x4,y4) * +---------------------------------------------> stem * thickness * * * This corresponds to the following values for the * `darkening-parameters' property: * * (x1, y1) = (500, 400) * (x2, y2) = (1000, 275) * (x3, y3) = (1667, 275) * (x4, y4) = (2333, 0) * */ /* Internal calculations are done in units per thousand for */ /* convenience. The x axis is scaled stem width in */ /* thousandths of a pixel. That is, 1000 is 1 pixel. */ /* The y axis is darkening amount in thousandths of a pixel.*/ /* In the code, below, dividing by ppem and */ /* adjusting for emRatio converts darkenAmount to character */ /* space (font units). */ CF2_Fixed stemWidthPer1000, scaledStem; FT_Int logBase2; *darkenAmount = 0; if ( boldenAmount == 0 && !stemDarkened ) return; /* protect against range problems and divide by zero */ if ( emRatio < cf2_floatToFixed( .01 ) ) return; if ( stemDarkened ) { FT_Int x1 = darkenParams[0]; FT_Int y1 = darkenParams[1]; FT_Int x2 = darkenParams[2]; FT_Int y2 = darkenParams[3]; FT_Int x3 = darkenParams[4]; FT_Int y3 = darkenParams[5]; FT_Int x4 = darkenParams[6]; FT_Int y4 = darkenParams[7]; /* convert from true character space to 1000 unit character space; */ /* add synthetic emboldening effect */ /* `stemWidthPer1000' will not overflow for a legitimate font */ stemWidthPer1000 = FT_MulFix( stemWidth + boldenAmount, emRatio ); /* `scaledStem' can easily overflow, so we must clamp its maximum */ /* value; the test doesn't need to be precise, but must be */ /* conservative. The clamp value (default 2333) where */ /* `darkenAmount' is zero is well below the overflow value of */ /* 32767. */ /* */ /* FT_MSB computes the integer part of the base 2 logarithm. The */ /* number of bits for the product is 1 or 2 more than the sum of */ /* logarithms; remembering that the 16 lowest bits of the fraction */ /* are dropped this is correct to within a factor of almost 4. */ /* For example, 0x80.0000 * 0x80.0000 = 0x4000.0000 is 23+23 and */ /* is flagged as possible overflow because 0xFF.FFFF * 0xFF.FFFF = */ /* 0xFFFF.FE00 is also 23+23. */ logBase2 = FT_MSB( (FT_UInt32)stemWidthPer1000 ) + FT_MSB( (FT_UInt32)ppem ); if ( logBase2 >= 46 ) /* possible overflow */ scaledStem = cf2_intToFixed( x4 ); else scaledStem = FT_MulFix( stemWidthPer1000, ppem ); /* now apply the darkening parameters */ if ( scaledStem < cf2_intToFixed( x1 ) ) *darkenAmount = FT_DivFix( cf2_intToFixed( y1 ), ppem ); else if ( scaledStem < cf2_intToFixed( x2 ) ) { FT_Int xdelta = x2 - x1; FT_Int ydelta = y2 - y1; FT_Int x = stemWidthPer1000 - FT_DivFix( cf2_intToFixed( x1 ), ppem ); if ( !xdelta ) goto Try_x3; *darkenAmount = FT_MulDiv( x, ydelta, xdelta ) + FT_DivFix( cf2_intToFixed( y1 ), ppem ); } else if ( scaledStem < cf2_intToFixed( x3 ) ) { Try_x3: { FT_Int xdelta = x3 - x2; FT_Int ydelta = y3 - y2; FT_Int x = stemWidthPer1000 - FT_DivFix( cf2_intToFixed( x2 ), ppem ); if ( !xdelta ) goto Try_x4; *darkenAmount = FT_MulDiv( x, ydelta, xdelta ) + FT_DivFix( cf2_intToFixed( y2 ), ppem ); } } else if ( scaledStem < cf2_intToFixed( x4 ) ) { Try_x4: { FT_Int xdelta = x4 - x3; FT_Int ydelta = y4 - y3; FT_Int x = stemWidthPer1000 - FT_DivFix( cf2_intToFixed( x3 ), ppem ); if ( !xdelta ) goto Use_y4; *darkenAmount = FT_MulDiv( x, ydelta, xdelta ) + FT_DivFix( cf2_intToFixed( y3 ), ppem ); } } else { Use_y4: *darkenAmount = FT_DivFix( cf2_intToFixed( y4 ), ppem ); } /* use half the amount on each side and convert back to true */ /* character space */ *darkenAmount = FT_DivFix( *darkenAmount, 2 * emRatio ); } /* add synthetic emboldening effect in character space */ *darkenAmount += boldenAmount / 2; }