Font outline decomposition fixes
This commit is contained in:
parent
f3fa7febeb
commit
da65fe0469
|
@ -38,7 +38,8 @@ OUTLINE_DECOMPOSER::OUTLINE_DECOMPOSER( FT_Outline& aOutline ) :
|
|||
|
||||
static VECTOR2D toVector2D( const FT_Vector* aFreeTypeVector )
|
||||
{
|
||||
return VECTOR2D( aFreeTypeVector->x, aFreeTypeVector->y );
|
||||
return VECTOR2D( aFreeTypeVector->x * GLYPH_SIZE_SCALER,
|
||||
aFreeTypeVector->y * GLYPH_SIZE_SCALER );
|
||||
}
|
||||
|
||||
|
||||
|
@ -62,8 +63,7 @@ int OUTLINE_DECOMPOSER::moveTo( const FT_Vector* aEndPoint, void* aCallbackData
|
|||
{
|
||||
OUTLINE_DECOMPOSER* decomposer = static_cast<OUTLINE_DECOMPOSER*>( aCallbackData );
|
||||
|
||||
decomposer->m_lastEndPoint.x = aEndPoint->x;
|
||||
decomposer->m_lastEndPoint.y = aEndPoint->y;
|
||||
decomposer->m_lastEndPoint = toVector2D( aEndPoint );
|
||||
|
||||
decomposer->newContour();
|
||||
decomposer->addContourPoint( decomposer->m_lastEndPoint );
|
||||
|
@ -76,8 +76,7 @@ int OUTLINE_DECOMPOSER::lineTo( const FT_Vector* aEndPoint, void* aCallbackData
|
|||
{
|
||||
OUTLINE_DECOMPOSER* decomposer = static_cast<OUTLINE_DECOMPOSER*>( aCallbackData );
|
||||
|
||||
decomposer->m_lastEndPoint.x = aEndPoint->x;
|
||||
decomposer->m_lastEndPoint.y = aEndPoint->y;
|
||||
decomposer->m_lastEndPoint = toVector2D( aEndPoint );
|
||||
|
||||
decomposer->addContourPoint( decomposer->m_lastEndPoint );
|
||||
|
||||
|
@ -112,12 +111,10 @@ int OUTLINE_DECOMPOSER::cubicTo( const FT_Vector* aFirstControlPoint,
|
|||
|
||||
GLYPH_POINTS result;
|
||||
decomposer->approximateBezierCurve( result, bezier );
|
||||
|
||||
for( const VECTOR2D& p : result )
|
||||
decomposer->addContourPoint( p );
|
||||
|
||||
decomposer->m_lastEndPoint.x = aEndPoint->x;
|
||||
decomposer->m_lastEndPoint.y = aEndPoint->y;
|
||||
decomposer->m_lastEndPoint = toVector2D( aEndPoint );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -177,14 +174,12 @@ bool OUTLINE_DECOMPOSER::approximateCubicBezierCurve( GLYPH_POINTS& aResul
|
|||
{
|
||||
wxASSERT( aCubicBezier.size() == 4 );
|
||||
|
||||
// minimumSegmentLength defines the "smoothness" of the
|
||||
// curve-to-straight-segments conversion: the larger, the coarser
|
||||
// TODO: find out what the minimum segment length should really be!
|
||||
static const int minimumSegmentLength = 50;
|
||||
GLYPH_POINTS tmp;
|
||||
constexpr int minimumSegmentLength = 10;
|
||||
BEZIER_POLY converter( aCubicBezier );
|
||||
converter.GetPoly( tmp, minimumSegmentLength );
|
||||
|
||||
for( unsigned int i = 0; i < tmp.size(); i++ )
|
||||
aResult.push_back( tmp[i] );
|
||||
converter.GetPoly( aResult, minimumSegmentLength );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -242,19 +237,8 @@ int OUTLINE_DECOMPOSER::winding( const GLYPH_POINTS& aContour ) const
|
|||
}
|
||||
}
|
||||
|
||||
unsigned int i_prev_vertex;
|
||||
unsigned int i_next_vertex;
|
||||
|
||||
// TODO: this should be done with modulo arithmetic for clarity
|
||||
if( i_lowest_vertex == 0 )
|
||||
i_prev_vertex = aContour.size() - 1;
|
||||
else
|
||||
i_prev_vertex = i_lowest_vertex - 1;
|
||||
|
||||
if( i_lowest_vertex == aContour.size() - 1 )
|
||||
i_next_vertex = 0;
|
||||
else
|
||||
i_next_vertex = i_lowest_vertex + 1;
|
||||
unsigned int i_prev_vertex = ( i_lowest_vertex + aContour.size() - 1 ) % aContour.size();
|
||||
unsigned int i_next_vertex = ( i_lowest_vertex + 1 ) % aContour.size();
|
||||
|
||||
const VECTOR2D& lowest = aContour[i_lowest_vertex];
|
||||
VECTOR2D prev( aContour[i_prev_vertex] );
|
||||
|
@ -268,7 +252,7 @@ int OUTLINE_DECOMPOSER::winding( const GLYPH_POINTS& aContour ) const
|
|||
|
||||
if( i_prev_vertex == i_lowest_vertex )
|
||||
{
|
||||
// ERROR: degenerate contour (all points are equal)
|
||||
// ERROR: degenerate contour (all points are colinear with equal Y coordinate)
|
||||
// TODO: signal error
|
||||
// for now let's just return something at random
|
||||
return cw;
|
||||
|
|
|
@ -41,21 +41,6 @@
|
|||
using namespace KIFONT;
|
||||
|
||||
|
||||
// The height of the KiCad stroke font is the distance between stroke endpoints for a vertical
|
||||
// line of cap-height. So the cap-height of the font is actually stroke-width taller than its
|
||||
// height.
|
||||
// Outline fonts are normally scaled on full-height (including ascenders and descenders), so we
|
||||
// need to compensate to keep them from being much smaller than their stroked counterparts.
|
||||
constexpr double OUTLINE_FONT_SIZE_COMPENSATION = 1.4;
|
||||
|
||||
// The KiCad stroke font uses a subscript/superscript size ratio of 0.7. This ratio is also
|
||||
// commonly used in LaTeX, but fonts with designed-in subscript and superscript glyphs are more
|
||||
// likely to use 0.58.
|
||||
// For auto-generated subscript and superscript glyphs in outline fonts we split the difference
|
||||
// with 0.64.
|
||||
static constexpr double SUBSCRIPT_SUPERSCRIPT_SIZE = 0.64;
|
||||
|
||||
|
||||
FT_Library OUTLINE_FONT::m_freeType = nullptr;
|
||||
|
||||
OUTLINE_FONT::OUTLINE_FONT() :
|
||||
|
@ -91,9 +76,6 @@ OUTLINE_FONT* OUTLINE_FONT::LoadFont( const wxString& aFontName, bool aBold, boo
|
|||
|
||||
FT_Error OUTLINE_FONT::loadFace( const wxString& aFontFileName )
|
||||
{
|
||||
m_faceScaler = m_faceSize * 64;
|
||||
int subscriptFaceScaler = KiROUND( m_faceSize * 64 * SUBSCRIPT_SUPERSCRIPT_SIZE );
|
||||
|
||||
// TODO: check that going from wxString to char* with UTF-8
|
||||
// conversion for filename makes sense on any/all platforms
|
||||
FT_Error e = FT_New_Face( m_freeType, aFontFileName.mb_str( wxConvUTF8 ), 0, &m_face );
|
||||
|
@ -101,15 +83,13 @@ FT_Error OUTLINE_FONT::loadFace( const wxString& aFontFileName )
|
|||
if( !e )
|
||||
{
|
||||
FT_Select_Charmap( m_face, FT_Encoding::FT_ENCODING_UNICODE );
|
||||
FT_Set_Char_Size( m_face, 0, m_faceScaler, 0, 0 );
|
||||
|
||||
e = FT_New_Face( m_freeType, aFontFileName.mb_str( wxConvUTF8 ), 0, &m_subscriptFace );
|
||||
|
||||
if( !e )
|
||||
{
|
||||
FT_Select_Charmap( m_subscriptFace, FT_Encoding::FT_ENCODING_UNICODE );
|
||||
FT_Set_Char_Size( m_subscriptFace, 0, subscriptFaceScaler, 0, 0 );
|
||||
}
|
||||
// params:
|
||||
// m_face = handle to face object
|
||||
// 0 = char width in 1/64th of points ( 0 = same as char height )
|
||||
// faceSize() = char height in 1/64th of points
|
||||
// GLYPH_RESOLUTION = horizontal device resolution (288dpi, 4x default)
|
||||
// 0 = vertical device resolution ( 0 = same as horizontal )
|
||||
FT_Set_Char_Size( m_face, 0, faceSize(), GLYPH_RESOLUTION, 0 );
|
||||
}
|
||||
|
||||
return e;
|
||||
|
@ -125,7 +105,7 @@ double OUTLINE_FONT::ComputeOverbarVerticalPosition( double aGlyphHeight ) const
|
|||
// The overbar on actual text is positioned above the bounding box of the glyphs. However,
|
||||
// that's expensive to calculate so we use an estimation here (as this is only used for
|
||||
// calculating bounding boxes).
|
||||
return aGlyphHeight * OUTLINE_FONT_SIZE_COMPENSATION;
|
||||
return aGlyphHeight * m_outlineFontSizeCompensation;
|
||||
}
|
||||
|
||||
|
||||
|
@ -140,7 +120,13 @@ double OUTLINE_FONT::GetInterline( double aGlyphHeight, double aLineSpacing ) co
|
|||
if( GetFace()->units_per_EM )
|
||||
pitch = GetFace()->height / GetFace()->units_per_EM;
|
||||
|
||||
return ( aLineSpacing * aGlyphHeight * pitch * OUTLINE_FONT_SIZE_COMPENSATION );
|
||||
double interline = aLineSpacing * aGlyphHeight * pitch * m_outlineFontSizeCompensation;
|
||||
|
||||
// FONT TODO this hack is an attempt to fix interline spacing by eyeballing it
|
||||
static constexpr double interlineHackMultiplier = 1.2;
|
||||
interline *= interlineHackMultiplier;
|
||||
|
||||
return interline;
|
||||
}
|
||||
|
||||
|
||||
|
@ -235,29 +221,32 @@ VECTOR2I OUTLINE_FONT::GetTextAsGlyphs( BOX2I* aBBox, std::vector<std::unique_pt
|
|||
bool aMirror, const VECTOR2I& aOrigin,
|
||||
TEXT_STYLE_FLAGS aTextStyle ) const
|
||||
{
|
||||
VECTOR2D glyphSize = aSize;
|
||||
FT_Face face = m_face;
|
||||
double scaler = faceSize();
|
||||
|
||||
if( IsSubscript( aTextStyle ) || IsSuperscript( aTextStyle ) )
|
||||
{
|
||||
scaler = subscriptSize();
|
||||
}
|
||||
|
||||
// set glyph resolution so that FT_Load_Glyph() results are good enough for decomposing
|
||||
FT_Set_Char_Size( face, 0, scaler, GLYPH_RESOLUTION, 0 );
|
||||
|
||||
hb_buffer_t* buf = hb_buffer_create();
|
||||
hb_buffer_add_utf8( buf, aText.c_str(), -1, 0, -1 );
|
||||
|
||||
// guess direction, script, and language based on contents
|
||||
hb_buffer_guess_segment_properties( buf );
|
||||
hb_buffer_guess_segment_properties( buf ); // guess direction, script, and language based on contents
|
||||
|
||||
unsigned int glyphCount;
|
||||
hb_glyph_info_t* glyphInfo = hb_buffer_get_glyph_infos( buf, &glyphCount );
|
||||
hb_glyph_position_t* glyphPos = hb_buffer_get_glyph_positions( buf, &glyphCount );
|
||||
hb_font_t* referencedFont;
|
||||
|
||||
VECTOR2D glyphSize = aSize;
|
||||
FT_Face face = m_face;
|
||||
double scaler = m_faceScaler / OUTLINE_FONT_SIZE_COMPENSATION;
|
||||
|
||||
if( IsSubscript( aTextStyle ) || IsSuperscript( aTextStyle ) )
|
||||
face = m_subscriptFace;
|
||||
|
||||
referencedFont = hb_ft_font_create_referenced( face );
|
||||
hb_font_t* referencedFont = hb_ft_font_create_referenced( face );
|
||||
hb_ft_font_set_funcs( referencedFont );
|
||||
hb_shape( referencedFont, buf, nullptr, 0 );
|
||||
|
||||
const VECTOR2D scaleFactor( glyphSize.x / scaler, -glyphSize.y / scaler );
|
||||
VECTOR2D scaleFactor( glyphSize.x / faceSize(), -glyphSize.y / faceSize() );
|
||||
scaleFactor = scaleFactor * m_outlineFontSizeCompensation;
|
||||
|
||||
VECTOR2I cursor( 0, 0 );
|
||||
VECTOR2D topLeft( INT_MAX * 1.0, -INT_MAX * 1.0 );
|
||||
|
@ -265,21 +254,16 @@ VECTOR2I OUTLINE_FONT::GetTextAsGlyphs( BOX2I* aBBox, std::vector<std::unique_pt
|
|||
|
||||
for( unsigned int i = 0; i < glyphCount; i++ )
|
||||
{
|
||||
hb_glyph_position_t& pos = glyphPos[i];
|
||||
int codepoint = glyphInfo[i].codepoint;
|
||||
|
||||
if( aGlyphs )
|
||||
{
|
||||
FT_Load_Glyph( face, codepoint, FT_LOAD_NO_BITMAP );
|
||||
|
||||
FT_GlyphSlot faceGlyph = face->glyph;
|
||||
FT_Load_Glyph( face, glyphInfo[i].codepoint, FT_LOAD_NO_BITMAP );
|
||||
|
||||
// contours is a collection of all outlines in the glyph;
|
||||
// example: glyph for 'o' generally contains 2 contours,
|
||||
// one for the glyph outline and one for the hole
|
||||
CONTOURS contours;
|
||||
|
||||
OUTLINE_DECOMPOSER decomposer( faceGlyph->outline );
|
||||
OUTLINE_DECOMPOSER decomposer( face->glyph->outline );
|
||||
decomposer.OutlineToSegments( &contours );
|
||||
|
||||
std::unique_ptr<OUTLINE_GLYPH> glyph = std::make_unique<OUTLINE_GLYPH>();
|
||||
|
@ -300,9 +284,9 @@ VECTOR2I OUTLINE_FONT::GetTextAsGlyphs( BOX2I* aBBox, std::vector<std::unique_pt
|
|||
topRight.y = std::max( topRight.y, pt.y );
|
||||
|
||||
if( IsSubscript( aTextStyle ) )
|
||||
pt.y -= 0.25 * scaler;
|
||||
pt.y += m_subscriptVerticalOffset * scaler;
|
||||
else if( IsSuperscript( aTextStyle ) )
|
||||
pt.y += 0.45 * scaler;
|
||||
pt.y += m_superscriptVerticalOffset * scaler;
|
||||
|
||||
pt *= scaleFactor;
|
||||
pt += aPosition;
|
||||
|
@ -339,14 +323,19 @@ VECTOR2I OUTLINE_FONT::GetTextAsGlyphs( BOX2I* aBBox, std::vector<std::unique_pt
|
|||
}
|
||||
}
|
||||
|
||||
// FONT TODO we might not want to do Fracture() here;
|
||||
// knockout text (eg. silkscreen labels with a background) will
|
||||
// need to do that after the contours have been turned into holes
|
||||
// and vice versa
|
||||
if( glyph->HasHoles() )
|
||||
glyph->Fracture( SHAPE_POLY_SET::PM_FAST ); // FONT TODO verify aFastMode
|
||||
|
||||
aGlyphs->push_back( std::move( glyph ) );
|
||||
}
|
||||
|
||||
cursor.x += pos.x_advance;
|
||||
cursor.y += pos.y_advance;
|
||||
hb_glyph_position_t& pos = glyphPos[i];
|
||||
cursor.x += ( pos.x_advance * GLYPH_SIZE_SCALER );
|
||||
cursor.y += ( pos.y_advance * GLYPH_SIZE_SCALER );
|
||||
}
|
||||
|
||||
if( IsOverbar( aTextStyle ) && aGlyphs )
|
||||
|
@ -354,8 +343,8 @@ VECTOR2I OUTLINE_FONT::GetTextAsGlyphs( BOX2I* aBBox, std::vector<std::unique_pt
|
|||
topLeft *= scaleFactor;
|
||||
topRight *= scaleFactor;
|
||||
|
||||
topLeft.y -= aSize.y * 0.16;
|
||||
topRight.y -= aSize.y * 0.16;
|
||||
topLeft.y -= aSize.y * m_overbarOffset;
|
||||
topRight.y -= aSize.y * m_overbarOffset;
|
||||
|
||||
topLeft += aPosition;
|
||||
topRight += aPosition;
|
||||
|
@ -366,7 +355,7 @@ VECTOR2I OUTLINE_FONT::GetTextAsGlyphs( BOX2I* aBBox, std::vector<std::unique_pt
|
|||
RotatePoint( topRight, aOrigin, aAngle );
|
||||
}
|
||||
|
||||
double overbarHeight = aSize.y * 0.07;
|
||||
double overbarHeight = aSize.y * m_overbarHeightMultiplier;
|
||||
SHAPE_POLY_SET overbar;
|
||||
|
||||
TransformOvalToPolygon( overbar, topLeft, topRight, overbarHeight, overbarHeight / 8,
|
||||
|
@ -401,9 +390,7 @@ void OUTLINE_FONT::RenderToOpenGLCanvas( KIGFX::OPENGL_GAL& aGal, const UTF8& aS
|
|||
{
|
||||
hb_buffer_t* buf = hb_buffer_create();
|
||||
hb_buffer_add_utf8( buf, aString.c_str(), -1, 0, -1 );
|
||||
|
||||
// guess direction, script, and language based on contents
|
||||
hb_buffer_guess_segment_properties( buf );
|
||||
hb_buffer_guess_segment_properties( buf ); // guess direction, script, and language based on contents
|
||||
|
||||
unsigned int glyphCount;
|
||||
hb_glyph_info_t* glyphInfo = hb_buffer_get_glyph_infos( buf, &glyphCount );
|
||||
|
|
|
@ -34,6 +34,14 @@
|
|||
|
||||
namespace KIFONT
|
||||
{
|
||||
|
||||
constexpr int GLYPH_DEFAULT_DPI = 72; ///< FreeType default
|
||||
// The FreeType default of 72 DPI is not enough for outline decomposition;
|
||||
// so we'll use something larger than that.
|
||||
constexpr int GLYPH_RESOLUTION = 288;
|
||||
constexpr double GLYPH_SIZE_SCALER = GLYPH_DEFAULT_DPI / (double) GLYPH_RESOLUTION;
|
||||
|
||||
|
||||
class GLYPH
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -134,13 +134,46 @@ private:
|
|||
static FT_Library m_freeType;
|
||||
FT_Face m_face;
|
||||
const int m_faceSize;
|
||||
FT_Face m_subscriptFace;
|
||||
|
||||
int m_faceScaler;
|
||||
|
||||
// cache for glyphs converted to straight segments
|
||||
// key is glyph index (FT_GlyphSlot field glyph_index)
|
||||
std::map<unsigned int, GLYPH_POINTS_LIST> m_contourCache;
|
||||
|
||||
// The height of the KiCad stroke font is the distance between stroke endpoints for a vertical
|
||||
// line of cap-height. So the cap-height of the font is actually stroke-width taller than its
|
||||
// height.
|
||||
// Outline fonts are normally scaled on full-height (including ascenders and descenders), so we
|
||||
// need to compensate to keep them from being much smaller than their stroked counterparts.
|
||||
static constexpr double m_outlineFontSizeCompensation = 1.4;
|
||||
|
||||
// FT_Set_Char_Size() gets character width and height specified in
|
||||
// 1/64ths of a point
|
||||
static constexpr int m_charSizeScaler = 64;
|
||||
|
||||
// The KiCad stroke font uses a subscript/superscript size ratio of 0.7. This ratio is also
|
||||
// commonly used in LaTeX, but fonts with designed-in subscript and superscript glyphs are more
|
||||
// likely to use 0.58.
|
||||
// For auto-generated subscript and superscript glyphs in outline fonts we split the difference
|
||||
// with 0.64.
|
||||
static constexpr double m_subscriptSuperscriptSize = 0.64;
|
||||
|
||||
int faceSize( int aSize ) const
|
||||
{
|
||||
return aSize * m_charSizeScaler * m_outlineFontSizeCompensation;
|
||||
};
|
||||
int faceSize() const { return faceSize( m_faceSize ); }
|
||||
|
||||
// also for superscripts
|
||||
int subscriptSize( int aSize ) const
|
||||
{
|
||||
return KiROUND( faceSize( aSize ) * m_subscriptSuperscriptSize );
|
||||
}
|
||||
int subscriptSize() const { return subscriptSize( m_faceSize ); }
|
||||
|
||||
static constexpr double m_subscriptVerticalOffset = -0.25;
|
||||
static constexpr double m_superscriptVerticalOffset = 0.45;
|
||||
static constexpr double m_overbarOffset = 0.16;
|
||||
static constexpr double m_overbarHeightMultiplier = 0.07;
|
||||
};
|
||||
|
||||
} //namespace KIFONT
|
||||
|
|
Loading…
Reference in New Issue