2021-12-31 14:07:24 +00:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KICAD, a free EDA CAD application.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2012 Torsten Hueter, torstenhtr <at> gmx.de
|
|
|
|
* Copyright (C) 2013 CERN
|
|
|
|
* @author Maciej Suminski <maciej.suminski@cern.ch>
|
2022-01-04 17:18:34 +00:00
|
|
|
* Copyright (C) 2016-2022 Kicad Developers, see AUTHORS.txt for contributors.
|
2021-12-31 14:07:24 +00:00
|
|
|
*
|
|
|
|
* Stroke font class
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, you may find one here:
|
|
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <gal/graphics_abstraction_layer.h>
|
|
|
|
#include <wx/string.h>
|
|
|
|
#include <wx/textfile.h>
|
|
|
|
#include <newstroke_font.h>
|
|
|
|
#include <font/glyph.h>
|
|
|
|
#include <font/stroke_font.h>
|
|
|
|
#include <geometry/shape_line_chain.h>
|
|
|
|
#include <trigo.h>
|
|
|
|
|
2022-01-13 13:31:00 +00:00
|
|
|
// The "official" name of the building Kicad stroke font (always existing)
|
|
|
|
#include <font/kicad_font_name.h>
|
|
|
|
|
2021-12-31 14:07:24 +00:00
|
|
|
using namespace KIFONT;
|
|
|
|
|
|
|
|
|
|
|
|
///< Factor that determines relative vertical position of the overbar.
|
|
|
|
static constexpr double OVERBAR_POSITION_FACTOR = 1.33;
|
|
|
|
|
|
|
|
///< Scale factor for a glyph
|
|
|
|
static constexpr double STROKE_FONT_SCALE = 1.0 / 21.0;
|
|
|
|
|
2022-01-04 00:44:59 +00:00
|
|
|
///< Tilt factor for italic style (this is the scaling factor on dY relative coordinates to
|
|
|
|
///< give a tilted shape)
|
2021-12-31 14:07:24 +00:00
|
|
|
static constexpr double ITALIC_TILT = 1.0 / 8;
|
|
|
|
|
|
|
|
static constexpr int FONT_OFFSET = -10;
|
|
|
|
|
|
|
|
|
2022-01-04 00:44:59 +00:00
|
|
|
bool g_defaultFontInitialized = false;
|
|
|
|
std::vector<std::shared_ptr<GLYPH>> g_defaultFontGlyphs;
|
|
|
|
std::vector<BOX2D>* g_defaultFontGlyphBoundingBoxes;
|
2021-12-31 14:07:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
STROKE_FONT::STROKE_FONT() :
|
|
|
|
m_glyphs( nullptr ),
|
2022-01-25 11:52:13 +00:00
|
|
|
m_glyphBoundingBoxes( nullptr ),
|
|
|
|
m_maxGlyphWidth( 0.0 )
|
2021-12-31 14:07:24 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
STROKE_FONT* STROKE_FONT::LoadFont( const wxString& aFontName )
|
|
|
|
{
|
|
|
|
if( aFontName.empty() )
|
|
|
|
{
|
|
|
|
STROKE_FONT* font = new STROKE_FONT();
|
|
|
|
font->loadNewStrokeFont( newstroke_font, newstroke_font_bufsize );
|
|
|
|
return font;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// FONT TODO: support for other stroke fonts?
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 00:44:59 +00:00
|
|
|
|
|
|
|
void buildGlyphBoundingBox( std::shared_ptr<STROKE_GLYPH>& aGlyph, double aGlyphWidth )
|
|
|
|
{
|
|
|
|
VECTOR2D min( 0, 0 );
|
|
|
|
VECTOR2D max( aGlyphWidth, 0 );
|
|
|
|
|
|
|
|
for( const std::vector<VECTOR2D>& pointList : *aGlyph )
|
|
|
|
{
|
|
|
|
for( const VECTOR2D& point : pointList )
|
|
|
|
{
|
|
|
|
min.y = std::min( min.y, point.y );
|
|
|
|
max.y = std::max( max.y, point.y );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
aGlyph->SetBoundingBox( BOX2D( min, max - min ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-12-31 14:07:24 +00:00
|
|
|
void STROKE_FONT::loadNewStrokeFont( const char* const aNewStrokeFont[], int aNewStrokeFontSize )
|
|
|
|
{
|
|
|
|
if( !g_defaultFontInitialized )
|
|
|
|
{
|
|
|
|
g_defaultFontGlyphs.reserve( aNewStrokeFontSize );
|
|
|
|
|
|
|
|
g_defaultFontGlyphBoundingBoxes = new std::vector<BOX2D>;
|
|
|
|
g_defaultFontGlyphBoundingBoxes->reserve( aNewStrokeFontSize );
|
|
|
|
|
|
|
|
for( int j = 0; j < aNewStrokeFontSize; j++ )
|
|
|
|
{
|
2022-01-09 14:28:07 +00:00
|
|
|
std::shared_ptr<STROKE_GLYPH> glyph = std::make_shared<STROKE_GLYPH>();
|
|
|
|
|
2021-12-31 14:07:24 +00:00
|
|
|
double glyphStartX = 0.0;
|
2022-01-04 00:44:59 +00:00
|
|
|
double glyphEndX = 0.0;
|
|
|
|
double glyphWidth = 0.0;
|
2022-01-09 14:28:07 +00:00
|
|
|
int strokes = 0;
|
|
|
|
int i = 0;
|
2021-12-31 14:07:24 +00:00
|
|
|
|
2022-01-09 14:28:07 +00:00
|
|
|
while( aNewStrokeFont[j][i] )
|
|
|
|
{
|
2021-12-31 14:07:24 +00:00
|
|
|
|
2022-01-09 14:28:07 +00:00
|
|
|
if( aNewStrokeFont[j][i] == ' ' && aNewStrokeFont[j][i+1] == 'R' )
|
|
|
|
strokes++;
|
|
|
|
|
|
|
|
i += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
glyph->reserve( strokes + 1 );
|
|
|
|
|
|
|
|
i = 0;
|
2021-12-31 14:07:24 +00:00
|
|
|
|
|
|
|
while( aNewStrokeFont[j][i] )
|
|
|
|
{
|
|
|
|
VECTOR2D point( 0.0, 0.0 );
|
2022-01-04 00:44:59 +00:00
|
|
|
char coordinate[2] = { 0, };
|
2021-12-31 14:07:24 +00:00
|
|
|
|
|
|
|
for( int k : { 0, 1 } )
|
|
|
|
coordinate[k] = aNewStrokeFont[j][i + k];
|
|
|
|
|
|
|
|
if( i < 2 )
|
|
|
|
{
|
|
|
|
// The first two values contain the width of the char
|
|
|
|
glyphStartX = ( coordinate[0] - 'R' ) * STROKE_FONT_SCALE;
|
2022-01-04 00:44:59 +00:00
|
|
|
glyphEndX = ( coordinate[1] - 'R' ) * STROKE_FONT_SCALE;
|
|
|
|
glyphWidth = glyphEndX - glyphStartX;
|
2021-12-31 14:07:24 +00:00
|
|
|
}
|
|
|
|
else if( ( coordinate[0] == ' ' ) && ( coordinate[1] == 'R' ) )
|
|
|
|
{
|
|
|
|
glyph->RaisePen();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// In stroke font, coordinates values are coded as <value> + 'R', where
|
|
|
|
// <value> is an ASCII char.
|
|
|
|
// therefore every coordinate description of the Hershey format has an offset,
|
|
|
|
// it has to be subtracted
|
|
|
|
// Note:
|
|
|
|
// * the stroke coordinates are stored in reduced form (-1.0 to +1.0),
|
|
|
|
// and the actual size is stroke coordinate * glyph size
|
|
|
|
// * a few shapes have a height slightly bigger than 1.0 ( like '{' '[' )
|
|
|
|
point.x = (double) ( coordinate[0] - 'R' ) * STROKE_FONT_SCALE - glyphStartX;
|
|
|
|
|
|
|
|
// FONT_OFFSET is here for historical reasons, due to the way the stroke font
|
|
|
|
// was built. It allows shapes coordinates like W M ... to be >= 0
|
|
|
|
// Only shapes like j y have coordinates < 0
|
|
|
|
point.y = (double) ( coordinate[1] - 'R' + FONT_OFFSET ) * STROKE_FONT_SCALE;
|
|
|
|
|
|
|
|
glyph->AddPoint( point );
|
|
|
|
}
|
|
|
|
|
|
|
|
i += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
glyph->Finalize();
|
|
|
|
|
|
|
|
// Compute the bounding box of the glyph
|
2022-01-04 00:44:59 +00:00
|
|
|
buildGlyphBoundingBox( glyph, glyphWidth );
|
2021-12-31 14:07:24 +00:00
|
|
|
g_defaultFontGlyphBoundingBoxes->emplace_back( glyph->BoundingBox() );
|
|
|
|
g_defaultFontGlyphs.push_back( glyph );
|
2022-01-04 00:44:59 +00:00
|
|
|
m_maxGlyphWidth = std::max( m_maxGlyphWidth, glyphWidth );
|
2021-12-31 14:07:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
g_defaultFontInitialized = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_glyphs = &g_defaultFontGlyphs;
|
|
|
|
m_glyphBoundingBoxes = g_defaultFontGlyphBoundingBoxes;
|
2022-01-13 13:31:00 +00:00
|
|
|
m_fontName = KICAD_FONT_NAME;
|
2022-01-07 14:49:17 +00:00
|
|
|
m_fontFileName = wxEmptyString;
|
2021-12-31 14:07:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
double STROKE_FONT::GetInterline( double aGlyphHeight, double aLineSpacing ) const
|
|
|
|
{
|
|
|
|
// Do not add the glyph thickness to the interline. This makes bold text line-spacing
|
|
|
|
// different from normal text, which is poor typography.
|
|
|
|
return ( aGlyphHeight * aLineSpacing * INTERLINE_PITCH_RATIO );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
double STROKE_FONT::ComputeOverbarVerticalPosition( double aGlyphHeight ) const
|
|
|
|
{
|
|
|
|
return aGlyphHeight * OVERBAR_POSITION_FACTOR;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-01-07 17:42:43 +00:00
|
|
|
VECTOR2I STROKE_FONT::GetTextAsGlyphs( BOX2I* aBBox, std::vector<std::unique_ptr<GLYPH>>* aGlyphs,
|
2022-01-25 22:33:37 +00:00
|
|
|
const wxString& aText, const VECTOR2I& aSize,
|
2022-01-04 23:00:00 +00:00
|
|
|
const VECTOR2I& aPosition, const EDA_ANGLE& aAngle,
|
|
|
|
bool aMirror, const VECTOR2I& aOrigin,
|
2022-01-04 00:44:59 +00:00
|
|
|
TEXT_STYLE_FLAGS aTextStyle ) const
|
2021-12-31 14:07:24 +00:00
|
|
|
{
|
|
|
|
wxPoint cursor( aPosition );
|
2022-01-04 23:00:00 +00:00
|
|
|
VECTOR2D glyphSize( aSize );
|
2021-12-31 14:07:24 +00:00
|
|
|
double tilt = ( aTextStyle & TEXT_STYLE::ITALIC ) ? ITALIC_TILT : 0.0;
|
|
|
|
|
|
|
|
if( aTextStyle & TEXT_STYLE::SUBSCRIPT || aTextStyle & TEXT_STYLE::SUPERSCRIPT )
|
|
|
|
{
|
|
|
|
constexpr double subscriptSuperscriptMultiplier = 0.7;
|
|
|
|
glyphSize.x *= subscriptSuperscriptMultiplier;
|
|
|
|
glyphSize.y *= subscriptSuperscriptMultiplier;
|
|
|
|
|
|
|
|
if( aTextStyle & TEXT_STYLE::SUBSCRIPT )
|
|
|
|
{
|
|
|
|
constexpr double subscriptVerticalMultiplier = 0.3;
|
|
|
|
cursor.y += glyphSize.y * subscriptVerticalMultiplier;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
constexpr double superscriptVerticalMultiplier = 0.5;
|
|
|
|
cursor.y -= glyphSize.y * superscriptVerticalMultiplier;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-25 22:33:37 +00:00
|
|
|
for( wxUniChar c : aText )
|
2021-12-31 14:07:24 +00:00
|
|
|
{
|
2022-01-25 22:33:37 +00:00
|
|
|
if( c >= (int) m_glyphBoundingBoxes->size() || c < 0 )
|
2021-12-31 14:07:24 +00:00
|
|
|
{
|
2022-01-25 22:33:37 +00:00
|
|
|
if( c == '\t' )
|
|
|
|
c = ' ';
|
|
|
|
else
|
|
|
|
c = '?';
|
2021-12-31 14:07:24 +00:00
|
|
|
}
|
|
|
|
|
2022-01-25 22:33:37 +00:00
|
|
|
// Index into bounding boxes table
|
|
|
|
int dd = (signed) c - ' ';
|
|
|
|
|
2021-12-31 14:07:24 +00:00
|
|
|
if( dd == 0 )
|
|
|
|
{
|
|
|
|
// 'space' character - draw nothing, advance cursor position
|
|
|
|
constexpr double spaceAdvance = 0.6;
|
|
|
|
cursor.x += glyphSize.x * spaceAdvance;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-01-04 00:44:59 +00:00
|
|
|
STROKE_GLYPH* source = static_cast<STROKE_GLYPH*>( m_glyphs->at( dd ).get() );
|
2021-12-31 14:07:24 +00:00
|
|
|
|
2022-01-07 17:42:43 +00:00
|
|
|
if( aGlyphs )
|
|
|
|
{
|
|
|
|
aGlyphs->push_back( source->Transform( glyphSize, cursor, tilt, aAngle, aMirror,
|
|
|
|
aOrigin ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
VECTOR2D glyphExtents = source->BoundingBox().GetEnd();
|
|
|
|
|
|
|
|
glyphExtents *= glyphSize;
|
|
|
|
|
2022-01-25 22:33:37 +00:00
|
|
|
if( tilt > 0.0 )
|
2022-01-07 17:42:43 +00:00
|
|
|
glyphExtents.x -= glyphExtents.y * tilt;
|
2021-12-31 14:07:24 +00:00
|
|
|
|
2022-01-07 17:42:43 +00:00
|
|
|
cursor.x += glyphExtents.x;
|
2021-12-31 14:07:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VECTOR2D barOffset( 0.0, 0.0 );
|
|
|
|
|
|
|
|
if( aTextStyle & TEXT_STYLE::OVERBAR )
|
|
|
|
{
|
|
|
|
barOffset.y = ComputeOverbarVerticalPosition( glyphSize.y );
|
|
|
|
|
|
|
|
if( aTextStyle & TEXT_STYLE::ITALIC )
|
|
|
|
barOffset.x = barOffset.y * ITALIC_TILT;
|
|
|
|
|
2022-01-04 23:00:00 +00:00
|
|
|
VECTOR2D barStart( aPosition.x + barOffset.x, cursor.y - barOffset.y );
|
|
|
|
VECTOR2D barEnd( cursor.x + barOffset.x, cursor.y - barOffset.y );
|
|
|
|
|
|
|
|
if( !aAngle.IsZero() )
|
|
|
|
{
|
|
|
|
RotatePoint( barStart, aOrigin, aAngle );
|
|
|
|
RotatePoint( barEnd, aOrigin, aAngle );
|
|
|
|
}
|
|
|
|
|
2022-01-07 17:42:43 +00:00
|
|
|
if( aGlyphs )
|
|
|
|
{
|
|
|
|
std::unique_ptr<STROKE_GLYPH> overbarGlyph = std::make_unique<STROKE_GLYPH>();
|
|
|
|
|
|
|
|
overbarGlyph->AddPoint( barStart );
|
|
|
|
overbarGlyph->AddPoint( barEnd );
|
|
|
|
overbarGlyph->Finalize();
|
2021-12-31 14:07:24 +00:00
|
|
|
|
2022-01-07 17:42:43 +00:00
|
|
|
aGlyphs->push_back( std::move( overbarGlyph ) );
|
|
|
|
}
|
2021-12-31 14:07:24 +00:00
|
|
|
}
|
|
|
|
|
2022-01-04 23:00:00 +00:00
|
|
|
if( aBBox )
|
2021-12-31 14:07:24 +00:00
|
|
|
{
|
2022-01-04 23:00:00 +00:00
|
|
|
aBBox->SetOrigin( aPosition );
|
|
|
|
aBBox->SetEnd( cursor.x + barOffset.x, cursor.y + std::max( glyphSize.y, barOffset.y ) );
|
|
|
|
aBBox->Normalize();
|
2021-12-31 14:07:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return VECTOR2I( cursor.x, aPosition.y );
|
|
|
|
}
|