Outline font support.

This commit is contained in:
Ola Rinta-Koski 2022-01-01 01:21:03 +00:00 committed by Jeff Young
parent 9c29cc945c
commit 9b406c1da4
19 changed files with 1951 additions and 20 deletions

View File

@ -708,6 +708,15 @@ include_directories( SYSTEM ${PIXMAN_INCLUDE_DIR} )
find_package( Boost 1.59.0 REQUIRED )
include_directories( SYSTEM ${Boost_INCLUDE_DIR} )
#
# Libraries required for outline font support.
find_package( Freetype REQUIRED )
include_directories( SYSTEM ${FREETYPE_INCLUDE_DIRS} )
find_package( HarfBuzz REQUIRED )
include_directories( SYSTEM ${HarfBuzz_INCLUDE_DIRS} )
find_package( Fontconfig REQUIRED )
# Include MinGW resource compiler.
include( MinGWResourceCompiler )

View File

@ -0,0 +1,103 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
FindFontconfig
--------------
.. versionadded:: 3.14
Find Fontconfig headers and library.
Imported Targets
^^^^^^^^^^^^^^^^
``Fontconfig::Fontconfig``
The Fontconfig library, if found.
Result Variables
^^^^^^^^^^^^^^^^
This will define the following variables in your project:
``Fontconfig_FOUND``
true if (the requested version of) Fontconfig is available.
``Fontconfig_VERSION``
the version of Fontconfig.
``Fontconfig_LIBRARIES``
the libraries to link against to use Fontconfig.
``Fontconfig_INCLUDE_DIRS``
where to find the Fontconfig headers.
``Fontconfig_COMPILE_OPTIONS``
this should be passed to target_compile_options(), if the
target is not used for linking
#]=======================================================================]
# use pkg-config to get the directories and then use these values
# in the FIND_PATH() and FIND_LIBRARY() calls
find_package(PkgConfig QUIET)
pkg_check_modules(PKG_FONTCONFIG QUIET fontconfig)
set(Fontconfig_COMPILE_OPTIONS ${PKG_FONTCONFIG_CFLAGS_OTHER})
set(Fontconfig_VERSION ${PKG_FONTCONFIG_VERSION})
find_path( Fontconfig_INCLUDE_DIR
NAMES
fontconfig/fontconfig.h
HINTS
${PKG_FONTCONFIG_INCLUDE_DIRS}
/usr/X11/include
)
find_library( Fontconfig_LIBRARY
NAMES
fontconfig
PATHS
${PKG_FONTCONFIG_LIBRARY_DIRS}
)
if (Fontconfig_INCLUDE_DIR AND NOT Fontconfig_VERSION)
file(STRINGS ${Fontconfig_INCLUDE_DIR}/fontconfig/fontconfig.h _contents REGEX "^#define[ \t]+FC_[A-Z]+[ \t]+[0-9]+$")
unset(Fontconfig_VERSION)
foreach(VPART MAJOR MINOR REVISION)
foreach(VLINE ${_contents})
if(VLINE MATCHES "^#define[\t ]+FC_${VPART}[\t ]+([0-9]+)$")
set(Fontconfig_VERSION_PART "${CMAKE_MATCH_1}")
if(Fontconfig_VERSION)
string(APPEND Fontconfig_VERSION ".${Fontconfig_VERSION_PART}")
else()
set(Fontconfig_VERSION "${Fontconfig_VERSION_PART}")
endif()
endif()
endforeach()
endforeach()
endif ()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Fontconfig
FOUND_VAR
Fontconfig_FOUND
REQUIRED_VARS
Fontconfig_LIBRARY
Fontconfig_INCLUDE_DIR
VERSION_VAR
Fontconfig_VERSION
)
if(Fontconfig_FOUND AND NOT TARGET Fontconfig::Fontconfig)
add_library(Fontconfig::Fontconfig UNKNOWN IMPORTED)
set_target_properties(Fontconfig::Fontconfig PROPERTIES
IMPORTED_LOCATION "${Fontconfig_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${Fontconfig_COMPILE_OPTIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${Fontconfig_INCLUDE_DIR}"
)
endif()
mark_as_advanced(Fontconfig_LIBRARY Fontconfig_INCLUDE_DIR)
if(Fontconfig_FOUND)
set(Fontconfig_LIBRARIES ${Fontconfig_LIBRARY})
set(Fontconfig_INCLUDE_DIRS ${Fontconfig_INCLUDE_DIR})
endif()

View File

@ -0,0 +1,187 @@
# Copyright (c) 2012, Intel Corporation
# Copyright (c) 2019 Sony Interactive Entertainment Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Intel Corporation nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Try to find Harfbuzz include and library directories.
#
# After successful discovery, this will set for inclusion where needed:
# HarfBuzz_INCLUDE_DIRS - containg the HarfBuzz headers
# HarfBuzz_LIBRARIES - containg the HarfBuzz library
#[=======================================================================[.rst:
FindHarfBuzz
--------------
Find HarfBuzz headers and libraries.
Imported Targets
^^^^^^^^^^^^^^^^
``HarfBuzz::HarfBuzz``
The HarfBuzz library, if found.
``HarfBuzz::ICU``
The HarfBuzz ICU library, if found.
Result Variables
^^^^^^^^^^^^^^^^
This will define the following variables in your project:
``HarfBuzz_FOUND``
true if (the requested version of) HarfBuzz is available.
``HarfBuzz_VERSION``
the version of HarfBuzz.
``HarfBuzz_LIBRARIES``
the libraries to link against to use HarfBuzz.
``HarfBuzz_INCLUDE_DIRS``
where to find the HarfBuzz headers.
``HarfBuzz_COMPILE_OPTIONS``
this should be passed to target_compile_options(), if the
target is not used for linking
#]=======================================================================]
find_package(PkgConfig QUIET)
pkg_check_modules(PC_HARFBUZZ QUIET harfbuzz)
set(HarfBuzz_COMPILE_OPTIONS ${PC_HARFBUZZ_CFLAGS_OTHER})
set(HarfBuzz_VERSION ${PC_HARFBUZZ_CFLAGS_VERSION})
find_path(HarfBuzz_INCLUDE_DIR
NAMES hb.h
HINTS ${PC_HARFBUZZ_INCLUDEDIR} ${PC_HARFBUZZ_INCLUDE_DIRS}
PATH_SUFFIXES harfbuzz
)
find_library(HarfBuzz_LIBRARY
NAMES ${HarfBuzz_NAMES} harfbuzz
HINTS ${PC_HARFBUZZ_LIBDIR} ${PC_HARFBUZZ_LIBRARY_DIRS}
)
if (HarfBuzz_INCLUDE_DIR AND NOT HarfBuzz_VERSION)
if (EXISTS "${HarfBuzz_INCLUDE_DIR}/hb-version.h")
file(READ "${HarfBuzz_INCLUDE_DIR}/hb-version.h" _harfbuzz_version_content)
string(REGEX MATCH "#define +HB_VERSION_STRING +\"([0-9]+\.[0-9]+\.[0-9]+)\"" _dummy "${_harfbuzz_version_content}")
set(HarfBuzz_VERSION "${CMAKE_MATCH_1}")
endif ()
endif ()
if ("${HarfBuzz_FIND_VERSION}" VERSION_GREATER "${HarfBuzz_VERSION}")
message(FATAL_ERROR "Required version (" ${HarfBuzz_FIND_VERSION} ") is higher than found version (" ${HarfBuzz_VERSION} ")")
endif ()
# Find components
if (HarfBuzz_INCLUDE_DIR AND HarfBuzz_LIBRARY)
set(_HarfBuzz_REQUIRED_LIBS_FOUND ON)
set(HarfBuzz_LIBS_FOUND "HarfBuzz (required): ${HarfBuzz_LIBRARY}")
else ()
set(_HarfBuzz_REQUIRED_LIBS_FOUND OFF)
set(HarfBuzz_LIBS_NOT_FOUND "HarfBuzz (required)")
endif ()
if ("ICU" IN_LIST HarfBuzz_FIND_COMPONENTS)
pkg_check_modules(PC_HARFBUZZ_ICU QUIET harfbuzz-icu)
set(HarfBuzz_ICU_COMPILE_OPTIONS ${PC_HARFBUZZ_ICU_CFLAGS_OTHER})
find_path(HarfBuzz_ICU_INCLUDE_DIR
NAMES hb-icu.h
HINTS ${PC_HARFBUZZ_ICU_INCLUDEDIR} ${PC_HARFBUZZ_ICU_INCLUDE_DIRS}
PATH_SUFFIXES harfbuzz
)
find_library(HarfBuzz_ICU_LIBRARY
NAMES ${HarfBuzz_ICU_NAMES} harfbuzz-icu
HINTS ${PC_HARFBUZZ_ICU_LIBDIR} ${PC_HARFBUZZ_ICU_LIBRARY_DIRS}
)
if (HarfBuzz_ICU_LIBRARY)
if (HarfBuzz_FIND_REQUIRED_ICU)
list(APPEND HarfBuzz_LIBS_FOUND "ICU (required): ${HarfBuzz_ICU_LIBRARY}")
else ()
list(APPEND HarfBuzz_LIBS_FOUND "ICU (optional): ${HarfBuzz_ICU_LIBRARY}")
endif ()
else ()
if (HarfBuzz_FIND_REQUIRED_ICU)
set(_HarfBuzz_REQUIRED_LIBS_FOUND OFF)
list(APPEND HarfBuzz_LIBS_NOT_FOUND "ICU (required)")
else ()
list(APPEND HarfBuzz_LIBS_NOT_FOUND "ICU (optional)")
endif ()
endif ()
endif ()
if (NOT HarfBuzz_FIND_QUIETLY)
if (HarfBuzz_LIBS_FOUND)
message(STATUS "Found the following HarfBuzz libraries:")
foreach (found ${HarfBuzz_LIBS_FOUND})
message(STATUS " ${found}")
endforeach ()
endif ()
if (HarfBuzz_LIBS_NOT_FOUND)
message(STATUS "The following HarfBuzz libraries were not found:")
foreach (found ${HarfBuzz_LIBS_NOT_FOUND})
message(STATUS " ${found}")
endforeach ()
endif ()
endif ()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(HarfBuzz
FOUND_VAR HarfBuzz_FOUND
REQUIRED_VARS HarfBuzz_INCLUDE_DIR HarfBuzz_LIBRARY _HarfBuzz_REQUIRED_LIBS_FOUND
VERSION_VAR HarfBuzz_VERSION
)
if (HarfBuzz_LIBRARY AND NOT TARGET HarfBuzz::HarfBuzz)
add_library(HarfBuzz::HarfBuzz UNKNOWN IMPORTED GLOBAL)
set_target_properties(HarfBuzz::HarfBuzz PROPERTIES
IMPORTED_LOCATION "${HarfBuzz_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${HarfBuzz_COMPILE_OPTIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${HarfBuzz_INCLUDE_DIR}"
)
endif ()
if (HarfBuzz_ICU_LIBRARY AND NOT TARGET HarfBuzz::ICU)
add_library(HarfBuzz::ICU UNKNOWN IMPORTED GLOBAL)
set_target_properties(HarfBuzz::ICU PROPERTIES
IMPORTED_LOCATION "${HarfBuzz_ICU_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${HarfBuzz_ICU_COMPILE_OPTIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${HarfBuzz_ICU_INCLUDE_DIR}"
)
endif ()
mark_as_advanced(
HarfBuzz_INCLUDE_DIR
HarfBuzz_ICU_INCLUDE_DIR
HarfBuzz_LIBRARY
HarfBuzz_ICU_LIBRARY
)
if (HarfBuzz_FOUND)
set(HarfBuzz_LIBRARIES ${HarfBuzz_LIBRARY} ${HarfBuzz_ICU_LIBRARY})
set(HarfBuzz_INCLUDE_DIRS ${HarfBuzz_INCLUDE_DIR} ${HarfBuzz_ICU_INCLUDE_DIR})
endif ()

View File

@ -72,6 +72,10 @@ target_link_libraries( gal
${PIXMAN_LIBRARIES}
${OPENGL_LIBRARIES}
${GDI_PLUS_LIBRARIES}
# outline font support
${FREETYPE_LIBRARIES}
${HarfBuzz_LIBRARIES}
${Fontconfig_LIBRARIES}
)
@ -296,6 +300,10 @@ set( FONT_SRCS
font/font.cpp
font/glyph.cpp
font/stroke_font.cpp
font/outline_font.cpp
font/outline_decomposer.cpp
font/triangulate.cpp
font/fontconfig.cpp
)
set( COMMON_SRCS
@ -493,6 +501,10 @@ target_link_libraries( common
${CURL_LIBRARIES}
${wxWidgets_LIBRARIES}
${EXTRA_LIBS}
# outline font support
${FREETYPE_LIBRARIES}
${HarfBuzz_LIBRARIES}
${Fontconfig_LIBRARIES}
)
target_include_directories( common

View File

@ -28,6 +28,7 @@
#include <string_utils.h>
#include <gal/graphics_abstraction_layer.h>
#include <font/stroke_font.h>
#include <font/outline_font.h>
#include <trigo.h>
#include <markup_parser.h>
@ -68,22 +69,18 @@ FONT* FONT::getDefaultFont()
FONT* FONT::GetFont( const wxString& aFontName, bool aBold, bool aItalic )
{
if( aFontName.empty() )
if( aFontName.empty() || aFontName == _( "KiCad" ) )
return getDefaultFont();
std::tuple<wxString, bool, bool> key = { aFontName, aBold, aItalic };
FONT* font = s_fontMap[key];
#if 0
// FONT TODO: load a real font
if( !font )
font = OUTLINE_FONT::LoadFont( aFontName, aBold, aItalic );
#else
if( !font )
font = getDefaultFont();
#endif
s_fontMap[key] = font;
@ -102,7 +99,7 @@ bool FONT::IsStroke( const wxString& aFontName )
return font && font->IsStroke();
#else
return aFontName == _( "Default Font" );
return aFontName == _( "Default Font" ) || aFontName == wxT( "KiCad" );
#endif
}

133
common/font/fontconfig.cpp Normal file
View File

@ -0,0 +1,133 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2021 Ola Rinta-Koski
* Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <sstream>
#include <font/fontconfig.h>
#include <pgm_base.h>
#include <settings/settings_manager.h>
using namespace fontconfig;
static FONTCONFIG* g_config = nullptr;
inline static FcChar8* wxStringToFcChar8( const wxString& str )
{
wxScopedCharBuffer const fcBuffer = str.ToUTF8();
return (FcChar8*) fcBuffer.data();
}
FONTCONFIG::FONTCONFIG()
{
m_config = FcInitLoadConfigAndFonts();
wxString configDirPath( Pgm().GetSettingsManager().GetUserSettingsPath() + wxT( "/fonts" ) );
FcConfigAppFontAddDir( nullptr, wxStringToFcChar8( configDirPath ) );
};
FONTCONFIG& Fontconfig()
{
if( !g_config )
{
FcInit();
g_config = new FONTCONFIG();
}
return *g_config;
}
bool FONTCONFIG::FindFont( const wxString& aFontName, wxString& aFontFile )
{
FcPattern* pat = FcNameParse( wxStringToFcChar8( aFontName ) );
FcConfigSubstitute( nullptr, pat, FcMatchPattern );
FcDefaultSubstitute( pat );
FcResult r = FcResultNoMatch;
FcPattern* font = FcFontMatch( nullptr, pat, &r );
bool ok = false;
if( font )
{
FcChar8* file = nullptr;
if( FcPatternGetString( font, FC_FILE, 0, &file ) == FcResultMatch )
{
aFontFile = wxString::FromUTF8( (char*) file );
ok = true;
}
FcPatternDestroy( font );
}
FcPatternDestroy( pat );
return ok;
}
void FONTCONFIG::ListFonts( std::vector<std::string>& aFonts )
{
if( m_fonts.empty() )
{
FcPattern* pat = FcPatternCreate();
FcObjectSet* os = FcObjectSetBuild( FC_FAMILY, FC_STYLE, FC_LANG, FC_FILE, nullptr );
FcFontSet* fs = FcFontList( nullptr, pat, os );
for( int i = 0; fs && i < fs->nfont; ++i )
{
FcPattern* font = fs->fonts[i];
FcChar8* file;
FcChar8* style;
FcChar8* family;
if( FcPatternGetString( font, FC_FILE, 0, &file ) == FcResultMatch
&& FcPatternGetString( font, FC_FAMILY, 0, &family ) == FcResultMatch
&& FcPatternGetString( font, FC_STYLE, 0, &style ) == FcResultMatch )
{
std::ostringstream s;
s << family;
std::string theFile( (char*) file );
std::string theFamily( (char*) family );
std::string theStyle( (char*) style );
FONTINFO fontInfo( theFile, theStyle, theFamily );
if( theFamily.length() > 0 && theFamily.front() == '.' )
continue;
auto it = m_fonts.find( theFamily );
if( it == m_fonts.end() )
m_fonts.insert( std::pair<std::string, FONTINFO>( theFamily, fontInfo ) );
else
it->second.Children().push_back( fontInfo );
}
}
if( fs )
FcFontSetDestroy( fs );
}
for( const std::pair<const std::string, FONTINFO>& entry : m_fonts )
aFonts.push_back( entry.second.Family() );
}

View File

@ -116,3 +116,16 @@ void STROKE_GLYPH::Mirror( const VECTOR2D& aMirrorOrigin )
point.x = originX - ( point.x - originX );
}
}
BOX2D OUTLINE_GLYPH::BoundingBox()
{
BOX2I bbox = BBox();
return BOX2D( bbox.GetOrigin(), bbox.GetSize() );
}
void OUTLINE_GLYPH::Mirror( const VECTOR2D& aMirrorOrigin )
{
SHAPE_POLY_SET::Mirror( true, false, aMirrorOrigin );
}

View File

@ -0,0 +1,325 @@
/*
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright (C) 2021 Ola Rinta-Koski <gitlab@rinta-koski.net>
* Copyright (C) 2021 Kicad Developers, see AUTHORS.txt for contributors.
*
* Outline 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 <font/outline_decomposer.h>
#include <bezier_curves.h>
using namespace KIFONT;
OUTLINE_DECOMPOSER::OUTLINE_DECOMPOSER( FT_Outline& aOutline ) :
m_outline( aOutline )
{
}
static VECTOR2D toVector2D( const FT_Vector* aFreeTypeVector )
{
return VECTOR2D( aFreeTypeVector->x, aFreeTypeVector->y );
}
void OUTLINE_DECOMPOSER::newContour()
{
CONTOUR contour;
contour.orientation = FT_Outline_Get_Orientation( &m_outline );
m_contours->push_back( contour );
}
void OUTLINE_DECOMPOSER::addContourPoint( const VECTOR2D& p )
{
// don't add repeated points
if( m_contours->back().points.empty() || m_contours->back().points.back() != p )
m_contours->back().points.push_back( p );
}
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->newContour();
decomposer->addContourPoint( decomposer->m_lastEndPoint );
return 0;
}
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->addContourPoint( decomposer->m_lastEndPoint );
return 0;
}
int OUTLINE_DECOMPOSER::quadraticTo( const FT_Vector* aControlPoint, const FT_Vector* aEndPoint,
void* aCallbackData )
{
return cubicTo( aControlPoint, nullptr, aEndPoint, aCallbackData );
}
int OUTLINE_DECOMPOSER::cubicTo( const FT_Vector* aFirstControlPoint,
const FT_Vector* aSecondControlPoint, const FT_Vector* aEndPoint,
void* aCallbackData )
{
OUTLINE_DECOMPOSER* decomposer = static_cast<OUTLINE_DECOMPOSER*>( aCallbackData );
GLYPH_POINTS bezier;
bezier.push_back( decomposer->m_lastEndPoint );
bezier.push_back( toVector2D( aFirstControlPoint ) );
if( aSecondControlPoint )
{
// aSecondControlPoint == nullptr for quadratic Beziers
bezier.push_back( toVector2D( aSecondControlPoint ) );
}
bezier.push_back( toVector2D( aEndPoint ) );
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;
return 0;
}
void OUTLINE_DECOMPOSER::OutlineToSegments( CONTOURS* aContours )
{
m_contours = aContours;
FT_Outline_Funcs callbacks;
callbacks.move_to = moveTo;
callbacks.line_to = lineTo;
callbacks.conic_to = quadraticTo;
callbacks.cubic_to = cubicTo;
callbacks.shift = 0;
callbacks.delta = 0;
FT_Error e = FT_Outline_Decompose( &m_outline, &callbacks, this );
if( e )
{
// TODO: handle error != 0
}
for( CONTOUR& c : *m_contours )
c.winding = winding( c.points );
}
// use converter in kimath
bool OUTLINE_DECOMPOSER::approximateQuadraticBezierCurve( GLYPH_POINTS& aResult,
const GLYPH_POINTS& aBezier ) const
{
// TODO: assert aBezier.size == 3
// BEZIER_POLY only handles cubic Bezier curves, even though the comments say otherwise...
//
// Quadratic to cubic Bezier conversion:
// cpn = Cubic Bezier control points (n = 0..3, 4 in total)
// qpn = Quadratic Bezier control points (n = 0..2, 3 in total)
// cp0 = qp0, cp1 = qp0 + 2/3 * (qp1 - qp0), cp2 = qp2 + 2/3 * (qp1 - qp2), cp3 = qp2
static const double twoThirds = 2 / 3.0;
GLYPH_POINTS cubic;
cubic.push_back( aBezier.at( 0 ) ); // cp0
cubic.push_back( aBezier.at( 0 ) + twoThirds * ( aBezier.at( 1 ) - aBezier.at( 0 ) ) ); // cp1
cubic.push_back( aBezier.at( 2 ) + twoThirds * ( aBezier.at( 1 ) - aBezier.at( 2 ) ) ); // cp2
cubic.push_back( aBezier.at( 2 ) ); // cp3
return approximateCubicBezierCurve( aResult, cubic );
}
bool OUTLINE_DECOMPOSER::approximateCubicBezierCurve( GLYPH_POINTS& aResult,
const GLYPH_POINTS& aCubicBezier ) const
{
// TODO: assert aCubicBezier.size == 4
// TODO: find out what the minimum segment length should really be!
static const int minimumSegmentLength = 50;
GLYPH_POINTS tmp;
BEZIER_POLY converter( aCubicBezier );
converter.GetPoly( tmp, minimumSegmentLength );
for( unsigned int i = 0; i < tmp.size(); i++ )
aResult.push_back( tmp.at( i ) );
return true;
}
bool OUTLINE_DECOMPOSER::approximateBezierCurve( GLYPH_POINTS& aResult,
const GLYPH_POINTS& aBezier ) const
{
bool bezierIsCubic = ( aBezier.size() == 4 );
if( bezierIsCubic )
return approximateCubicBezierCurve( aResult, aBezier );
else
return approximateQuadraticBezierCurve( aResult, aBezier );
}
int OUTLINE_DECOMPOSER::winding( const GLYPH_POINTS& aContour ) const
{
// -1 == counterclockwise, 1 == clockwise
const int cw = 1;
const int ccw = -1;
if( aContour.size() < 2 )
{
// zero or one points, so not a clockwise contour - in fact not a contour at all
//
// It could also be argued that a contour needs 3 extremum points at a minimum to be
// considered a proper contour (ie. a glyph (subpart) outline, or a hole)
return 0;
}
unsigned int i_lowest_vertex;
double lowest_y = std::numeric_limits<double>::max();
for( unsigned int i = 0; i < aContour.size(); i++ )
{
VECTOR2D p = aContour[i];
if( p.y < lowest_y )
{
i_lowest_vertex = i;
lowest_y = p.y;
// note: we should also check for p.y == lowest_y and then choose the point with
// leftmost.x, but as p.x is a double, equality is a dubious concept; however
// this should suffice in the general case
}
}
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;
const VECTOR2D& lowest = aContour[i_lowest_vertex];
VECTOR2D prev( aContour[i_prev_vertex] );
while( prev == lowest )
{
if( i_prev_vertex == 0 )
i_prev_vertex = aContour.size() - 1;
else
i_prev_vertex--;
if( i_prev_vertex == i_lowest_vertex )
{
// ERROR: degenerate contour (all points are equal)
// TODO: signal error
// for now let's just return something at random
return cw;
}
prev = aContour[i_prev_vertex];
}
VECTOR2D next( aContour[i_next_vertex] );
while( next == lowest )
{
if( i_next_vertex == aContour.size() - 1 )
i_next_vertex = 0;
else
i_next_vertex++;
if( i_next_vertex == i_lowest_vertex )
{
// ERROR: degenerate contour (all points are equal)
// TODO: signal error
// for now let's just return something at random
return cw;
}
next = aContour[i_next_vertex];
}
// winding is figured out based on the angle between the lowest
// vertex and its neighbours
//
// prev.x < lowest.x && next.x > lowest.x -> ccw
//
// prev.x > lowest.x && next.x < lowest.x -> cw
//
// prev.x < lowest.x && next.x < lowest.x:
// ?
//
// prev.x > lowest.x && next.x > lowest.x:
// ?
//
if( prev.x < lowest.x && next.x > lowest.x )
return ccw;
if( prev.x > lowest.x && next.x < lowest.x )
return cw;
double prev_deltaX = prev.x - lowest.x;
double prev_deltaY = prev.y - lowest.y;
double next_deltaX = next.x - lowest.x;
double next_deltaY = next.y - lowest.y;
double prev_atan = atan2( prev_deltaY, prev_deltaX );
double next_atan = atan2( next_deltaY, next_deltaX );
if( prev_atan > next_atan )
return ccw;
else
return cw;
}

View File

@ -0,0 +1,623 @@
/*
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright (C) 2021 Ola Rinta-Koski <gitlab@rinta-koski.net>
* Copyright (C) 2021-2022 Kicad Developers, see AUTHORS.txt for contributors.
*
* Outline 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 <limits>
#include <pgm_base.h>
#include <settings/settings_manager.h>
#include <harfbuzz/hb-ft.h>
#include <bezier_curves.h>
#include <geometry/shape_poly_set.h>
#include <eda_text.h>
#include <font/outline_font.h>
#include FT_GLYPH_H
#include FT_BBOX_H
#include <trigo.h>
#include <font/fontconfig.h>
using namespace KIFONT;
FT_Library OUTLINE_FONT::m_freeType = nullptr;
OUTLINE_FONT::OUTLINE_FONT() :
m_faceSize( 16 ),
m_subscriptSize( 13 )
{
if( !m_freeType )
{
//FT_Error ft_error = FT_Init_FreeType( &m_freeType );
// TODO: handle ft_error
FT_Init_FreeType( &m_freeType );
}
}
OUTLINE_FONT* OUTLINE_FONT::LoadFont( const wxString& aFontName, bool aBold, bool aItalic )
{
OUTLINE_FONT* font = new OUTLINE_FONT();
wxString fontFile;
wxString qualifiedFontName = aFontName;
if( aBold )
qualifiedFontName << ":bold";
if( aItalic )
qualifiedFontName << ":italic";
if( Fontconfig().FindFont( qualifiedFontName, fontFile ) )
(void) font->loadFace( fontFile );
else
(void) font->loadFontSimple( aFontName );
return font;
}
bool OUTLINE_FONT::loadFontSimple( const wxString& aFontFileName )
{
wxFileName fontFile( aFontFileName );
wxString fileName = fontFile.GetFullPath();
// TODO: handle ft_error properly (now we just return false if load does not succeed)
FT_Error ft_error = loadFace( fileName );
if( ft_error )
{
// Try user dir
fontFile.SetExt( "otf" );
fontFile.SetPath( Pgm().GetSettingsManager().GetUserSettingsPath() + wxT( "/fonts" ) );
fileName = fontFile.GetFullPath();
if( wxFile::Exists( fileName ) )
{
ft_error = loadFace( fileName );
}
else
{
fontFile.SetExt( "ttf" );
fileName = fontFile.GetFullPath();
if( wxFile::Exists( fileName ) )
ft_error = loadFace( fileName );
}
}
if( ft_error == FT_Err_Unknown_File_Format )
{
std::cerr << "The font file " << fileName << " could be opened and read, "
<< "but it appears that its font format is unsupported." << std::endl;
}
else if( ft_error )
{
std::cerr << "ft_error " << ft_error << std::endl;
return false;
}
else
{
m_fontName = aFontFileName;
m_fontFileName = fileName;
}
return true;
}
FT_Error OUTLINE_FONT::loadFace( const wxString& aFontFileName )
{
m_faceScaler = m_faceSize * 64;
m_subscriptFaceScaler = m_subscriptSize * 64;
// 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 );
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, m_subscriptFaceScaler, 0, 0 );
m_fontName = wxString( m_face->family_name );
m_fontFileName = aFontFileName;
}
}
return e;
}
/**
* Compute the boundary limits of aText (the bounding box of all shapes).
*
* @return a VECTOR2D giving the width and height of text.
*/
VECTOR2D OUTLINE_FONT::StringBoundaryLimits( const KIGFX::GAL* aGal, const UTF8& aText,
const VECTOR2D& aGlyphSize,
double aGlyphThickness ) const
{
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 );
unsigned int glyphCount;
hb_glyph_info_t* glyphInfo = hb_buffer_get_glyph_infos( buf, &glyphCount );
hb_font_t* referencedFont = hb_ft_font_create_referenced( m_face );
//hb_glyph_position_t* glyphPos = hb_buffer_get_glyph_positions( buf, &glyphCount );
hb_ft_font_set_funcs( referencedFont );
hb_shape( referencedFont, buf, nullptr, 0 );
int width = 0;
int height = m_face->size->metrics.height;
FT_UInt previous;
for( int i = 0; i < (int) glyphCount; i++ )
{
//hb_glyph_position_t& pos = glyphPos[i];
int codepoint = glyphInfo[i].codepoint;
if( i > 0 )
{
FT_Vector delta;
FT_Get_Kerning( m_face, previous, codepoint, FT_KERNING_DEFAULT, &delta );
width += delta.x >> 6;
}
FT_Load_Glyph( m_face, codepoint, FT_LOAD_NO_BITMAP );
FT_GlyphSlot glyph = m_face->glyph;
width += glyph->advance.x >> 6;
previous = codepoint;
}
return VECTOR2D( width * m_faceScaler, height * m_faceScaler );
}
/**
* Compute the vertical position of an overbar. This is the distance between the text
* baseline and the overbar.
*/
double OUTLINE_FONT::ComputeOverbarVerticalPosition( double aGlyphHeight ) const
{
// TODO: dummy to make this compile! not used
return aGlyphHeight;
}
/**
* Compute the distance (interline) between 2 lines of text (for multiline texts). This is
* the distance between baselines, not the space between line bounding boxes.
*/
double OUTLINE_FONT::GetInterline( double aGlyphHeight, double aLineSpacing ) const
{
if( GetFace()->units_per_EM )
return ( aLineSpacing * aGlyphHeight * ( GetFace()->height / GetFace()->units_per_EM ) );
else
return ( aLineSpacing * aGlyphHeight * INTERLINE_PITCH_RATIO );
}
/**
* Compute the X and Y size of a given text. The text is expected to be a single line.
*/
VECTOR2D OUTLINE_FONT::ComputeTextLineSize( const KIGFX::GAL* aGal, const UTF8& aText ) const
{
return StringBoundaryLimits( aGal, aText, aGal->GetGlyphSize(), 0.0 );
}
static bool contourIsFilled( const CONTOUR& c )
{
switch( c.orientation )
{
case FT_ORIENTATION_TRUETYPE: return c.winding == 1;
case FT_ORIENTATION_POSTSCRIPT: return c.winding == -1;
default: return false;
}
}
static bool contourIsHole( const CONTOUR& c )
{
return !contourIsFilled( c );
}
BOX2I OUTLINE_FONT::getBoundingBox( const std::vector<std::unique_ptr<GLYPH>>& aGlyphs ) const
{
int minX = INT_MAX;
int minY = INT_MAX;
int maxX = INT_MIN;
int maxY = INT_MIN;
for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
{
BOX2D bbox = glyph->BoundingBox();
bbox.Normalize();
if( minX > bbox.GetX() )
minX = bbox.GetX();
if( minY > bbox.GetY() )
minY = bbox.GetY();
if( maxX < bbox.GetRight() )
maxX = bbox.GetRight();
if( maxY < bbox.GetBottom() )
maxY = bbox.GetBottom();
}
BOX2I ret;
ret.SetOrigin( minX, minY );
ret.SetEnd( maxX, maxY );
return ret;
}
VECTOR2I OUTLINE_FONT::GetLinesAsGlyphs( std::vector<std::unique_ptr<GLYPH>>& aGlyphs,
const EDA_TEXT* aText ) const
{
wxArrayString strings;
std::vector<wxPoint> positions;
int n;
VECTOR2I ret;
std::vector<VECTOR2D> boundingBoxes;
TEXT_STYLE_FLAGS textStyle = 0;
if( aText->IsItalic() )
textStyle |= TEXT_STYLE::ITALIC;
getLinePositions( aText->GetShownText(), aText->GetTextPos(), strings, positions, n,
boundingBoxes, aText->GetAttributes() );
for( int i = 0; i < n; i++ )
{
ret = drawMarkup( nullptr, aGlyphs, UTF8( strings.Item( i ) ), positions[i],
aText->GetTextSize(), aText->GetTextAngle(), textStyle );
}
return ret;
}
VECTOR2I OUTLINE_FONT::GetTextAsGlyphs( BOX2I* aBoundingBox,
std::vector<std::unique_ptr<GLYPH>>& aGlyphs,
const UTF8& aText, const VECTOR2D& aGlyphSize,
const wxPoint& aPosition, const EDA_ANGLE& aOrientation,
TEXT_STYLE_FLAGS aTextStyle ) const
{
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 );
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;
//const double subscriptAndSuperscriptScaler = 0.5;
VECTOR2D glyphSize = aGlyphSize;
FT_Face face = m_face;
int scaler = m_faceScaler;
if( IsSubscript( aTextStyle ) || IsSuperscript( aTextStyle ) )
{
face = m_subscriptFace;
//scaler = m_subscriptFaceScaler;
}
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 );
VECTOR2I cursor( 0, 0 );
VECTOR2I extentBottomLeft( INT_MAX, INT_MAX );
VECTOR2I extentTopRight( INT_MIN, INT_MIN );
VECTOR2I vBottomLeft( INT_MAX, INT_MAX );
VECTOR2I vTopRight( INT_MIN, INT_MIN );
for( unsigned int i = 0; i < glyphCount; i++ )
{
hb_glyph_position_t& pos = glyphPos[i];
int codepoint = glyphInfo[i].codepoint;
FT_Load_Glyph( face, codepoint, FT_LOAD_NO_BITMAP );
FT_GlyphSlot faceGlyph = face->glyph;
// 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 );
decomposer.OutlineToSegments( &contours );
std::unique_ptr<OUTLINE_GLYPH> glyph = std::make_unique<OUTLINE_GLYPH>();
std::vector<SHAPE_LINE_CHAIN> holes;
std::vector<SHAPE_LINE_CHAIN> outlines;
for( CONTOUR& c : contours )
{
GLYPH_POINTS points = c.points;
SHAPE_LINE_CHAIN shape;
VECTOR2D offset( aPosition );
if( IsSubscript( aTextStyle ) )
offset.y += glyphSize.y * 0.1;
else if( IsSuperscript( aTextStyle ) )
offset.y -= glyphSize.y * 0.2;
for( const VECTOR2D& v : points )
{
// Save text extents
if( vBottomLeft.x > v.x )
vBottomLeft.x = v.x;
if( vBottomLeft.y > v.y )
vBottomLeft.y = v.y;
if( vTopRight.x < v.x )
vTopRight.x = v.x;
if( vTopRight.y < v.y )
vTopRight.y = v.y;
VECTOR2D pt( v.x, v.y );
VECTOR2D ptC( pt.x + cursor.x, pt.y + cursor.y );
wxPoint scaledPtOrig( -ptC.x * scaleFactor.x, -ptC.y * scaleFactor.y );
wxPoint scaledPt( scaledPtOrig );
RotatePoint( &scaledPt, aOrientation.AsRadians() );
scaledPt.x += offset.x;
scaledPt.y += offset.y;
if( extentBottomLeft.x > scaledPt.x )
extentBottomLeft.x = scaledPt.x;
if( extentBottomLeft.y > scaledPt.y )
extentBottomLeft.y = scaledPt.y;
if( extentTopRight.x < scaledPt.x )
extentTopRight.x = scaledPt.x;
if( extentTopRight.y < scaledPt.y )
extentTopRight.y = scaledPt.y;
shape.Append( scaledPt.x, scaledPt.y );
//ptListScaled.push_back( scaledPt );
}
if( contourIsHole( c ) )
holes.push_back( std::move( shape ) );
else
outlines.push_back( std::move( shape ) );
}
for( SHAPE_LINE_CHAIN& outline : outlines )
{
if( outline.PointCount() )
{
outline.SetClosed( true );
glyph->AddOutline( outline );
}
}
int nthHole = 0;
for( SHAPE_LINE_CHAIN& hole : holes )
{
if( hole.PointCount() )
{
hole.SetClosed( true );
VECTOR2I firstPoint = hole.GetPoint( 0 );
//SHAPE_SIMPLE *outlineForHole = nullptr;
int nthOutline = -1;
int n = 0;
for( SHAPE_LINE_CHAIN& outline : outlines )
{
if( outline.PointInside( firstPoint ) )
{
//outlineForHole = outline;
nthOutline = n;
break;
}
n++;
}
if( nthOutline > -1 )
glyph->AddHole( hole, n );
}
nthHole++;
}
aGlyphs.push_back( std::move( glyph ) );
cursor.x += pos.x_advance;
cursor.y += pos.y_advance;
}
VECTOR2I cursorEnd( cursor );
if( IsOverbar( aTextStyle ) )
{
std::unique_ptr<OUTLINE_GLYPH> overbarGlyph = std::make_unique<OUTLINE_GLYPH>();
SHAPE_LINE_CHAIN overbar;
int left = extentBottomLeft.x;
int right = extentTopRight.x;
int top = extentBottomLeft.y - 800;
int barHeight = -3200;
overbar.Append( VECTOR2D( left, top ) );
overbar.Append( VECTOR2D( right, top ) );
overbar.Append( VECTOR2D( right, top + barHeight ) );
overbar.Append( VECTOR2D( left, top + barHeight ) );
overbar.SetClosed( true );
overbarGlyph->AddOutline( overbar );
aGlyphs.push_back( std::move( overbarGlyph ) );
}
hb_buffer_destroy( buf );
VECTOR2I cursorDisplacement( -cursorEnd.x * scaleFactor.x, cursorEnd.y * scaleFactor.y );
if( aBoundingBox )
{
aBoundingBox->SetOrigin( aPosition.x, aPosition.y );
aBoundingBox->SetEnd( cursorDisplacement );
}
return VECTOR2I( aPosition.x + cursorDisplacement.x, aPosition.y + cursorDisplacement.y );
}
VECTOR2D OUTLINE_FONT::getBoundingBox( const UTF8& aString, const VECTOR2D& aGlyphSize,
TEXT_STYLE_FLAGS aTextStyle ) const
{
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 );
FT_Face face = m_face;
int scaler = m_faceScaler;
if( IsSubscript( aTextStyle ) || IsSuperscript( aTextStyle ) )
face = m_subscriptFace;
hb_font_t* referencedFont = hb_ft_font_create_referenced( face );
hb_ft_font_set_funcs( referencedFont );
hb_shape( referencedFont, buf, nullptr, 0 );
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 );
VECTOR2D boundingBox( 0, 0 );
int xScaler = aGlyphSize.x / scaler;
int yScaler = aGlyphSize.y / scaler;
double maxHeight = 0.0;
for( unsigned int i = 0; i < glyphCount; i++ )
{
//hb_glyph_position_t& pos = glyphPos[i];
int codepoint = glyphInfo[i].codepoint;
FT_Load_Glyph( face, codepoint, FT_LOAD_NO_BITMAP );
FT_GlyphSlot glyphSlot = face->glyph;
FT_Glyph glyph;
FT_BBox controlBox;
FT_Get_Glyph( glyphSlot, &glyph );
FT_Glyph_Get_CBox( glyph, FT_Glyph_BBox_Mode::FT_GLYPH_BBOX_UNSCALED, &controlBox );
double width = controlBox.xMax * xScaler;
boundingBox.x += width;
double height = controlBox.yMax * yScaler;
if( height > maxHeight )
maxHeight = height;
FT_Done_Glyph( glyph );
}
boundingBox.y = aGlyphSize.y; //maxHeight;
hb_buffer_destroy( buf );
return boundingBox;
}
#undef OUTLINEFONT_RENDER_AS_PIXELS
#ifdef OUTLINEFONT_RENDER_AS_PIXELS
/*
* WIP: eeschema (and PDF output?) should use pixel rendering instead of linear segmentation
*/
void OUTLINE_FONT::RenderToOpenGLCanvas( KIGFX::OPENGL_GAL& aGal, const UTF8& aString,
const VECTOR2D& aGlyphSize, const wxPoint& aPosition,
const EDA_ANGLE& aOrientation, bool aIsMirrored ) const
{
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 );
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 = hb_ft_font_create_referenced( m_face );
hb_ft_font_set_funcs( referencedFont );
hb_shape( referencedFont, buf, nullptr, 0 );
const double mirror_factor = ( aIsMirrored ? 1 : -1 );
const double x_scaleFactor = mirror_factor * aGlyphSize.x / mScaler;
const double y_scaleFactor = aGlyphSize.y / mScaler;
hb_position_t cursor_x = 0;
hb_position_t cursor_y = 0;
for( unsigned int i = 0; i < glyphCount; i++ )
{
hb_glyph_position_t& pos = glyphPos[i];
int codepoint = glyphInfo[i].codepoint;
FT_Error e = FT_Load_Glyph( m_face, codepoint, FT_LOAD_DEFAULT );
// TODO handle FT_Load_Glyph error
FT_Glyph glyph;
e = FT_Get_Glyph( m_face->glyph, &glyph );
// TODO handle FT_Get_Glyph error
wxPoint pt( aPosition );
pt.x += ( cursor_x >> 6 ) * x_scaleFactor;
pt.y += ( cursor_y >> 6 ) * y_scaleFactor;
cursor_x += pos.x_advance;
cursor_y += pos.y_advance;
}
hb_buffer_destroy( buf );
}
#endif //OUTLINEFONT_RENDER_AS_PIXELS

View File

@ -0,0 +1,47 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2021 Ola Rinta-Koski
* Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <limits>
#include <font/triangulate.h>
void Triangulate( const SHAPE_POLY_SET& aPolylist, TRIANGULATE_CALLBACK aCallback,
void* aCallbackData )
{
SHAPE_POLY_SET polys( aPolylist );
polys.Fracture( SHAPE_POLY_SET::PM_FAST ); // TODO verify aFastMode
polys.CacheTriangulation();
for( unsigned int i = 0; i < polys.TriangulatedPolyCount(); i++ )
{
const SHAPE_POLY_SET::TRIANGULATED_POLYGON* polygon = polys.TriangulatedPolygon( i );
for ( size_t j = 0; j < polygon->GetTriangleCount(); j++ )
{
VECTOR2I a;
VECTOR2I b;
VECTOR2I c;
polygon->GetTriangle( j, a, b, c );
aCallback( i, a, b, c, aCallbackData );
}
}
}

View File

@ -44,6 +44,7 @@
#include <bezier_curves.h>
#include <math/util.h> // for KiROUND
#include <trace_helpers.h>
#include <font/triangulate.h>
#include <wx/frame.h>
@ -2420,12 +2421,28 @@ void OPENGL_GAL::DrawGlyph( const KIFONT::GLYPH& aGlyph, int aNth, int aTotal )
for( const std::vector<VECTOR2D>& pointList : strokeGlyph )
DrawPolyline( pointList );
}
#if 0 // FONT TODO
else if( aGlyph.IsOutline() )
{
fillPolygonAsTriangles( aGlyph.GetPolylist() );
const auto& outlineGlyph = static_cast<const KIFONT::OUTLINE_GLYPH&>( aGlyph );
fillPolygonAsTriangles( outlineGlyph );
}
#endif
}
void OPENGL_GAL::fillPolygonAsTriangles( const SHAPE_POLY_SET& aPolyList )
{
m_currentManager->Shader( SHADER_NONE );
m_currentManager->Color( m_fillColor );
auto triangleCallback = [&]( int aPolygonIndex, const VECTOR2D& aVertex1,
const VECTOR2D& aVertex2, const VECTOR2D& aVertex3,
void* aCallbackData )
{
m_currentManager->Vertex( aVertex1.x, aVertex1.y, m_layerDepth );
m_currentManager->Vertex( aVertex2.x, aVertex2.y, m_layerDepth );
m_currentManager->Vertex( aVertex3.x, aVertex3.y, m_layerDepth );
};
Triangulate( aPolyList, triangleCallback );
}

51
include/font/fontconfig.h Normal file
View File

@ -0,0 +1,51 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2021 Ola Rinta-Koski
* Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef KICAD_FONTCONFIG_H
#define KICAD_FONTCONFIG_H
#include <fontconfig/fontconfig.h>
#include <wx/string.h>
#include <vector>
#include <map>
#include <font/fontinfo.h>
namespace fontconfig
{
class FONTCONFIG
{
public:
FONTCONFIG();
bool FindFont( const wxString& aFontName, wxString& aFontFile );
void ListFonts( std::vector<std::string>& aFonts );
private:
FcConfig* m_config;
std::map<std::string, FONTINFO> m_fonts;
};
} // namespace fontconfig
fontconfig::FONTCONFIG& Fontconfig();
#endif //KICAD_FONTCONFIG_H

56
include/font/fontinfo.h Normal file
View File

@ -0,0 +1,56 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2021 Ola Rinta-Koski
* Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef FONT_FONTINFO_H
#define FONT_FONTINFO_H
#include <string>
#include <vector>
namespace fontconfig
{
class FONTINFO
{
public:
FONTINFO( std::string aFile, std::string aStyle, std::string aFamily ) :
m_file( aFile ),
m_style( aStyle ),
m_family( aFamily )
{
}
const std::string& File() const { return m_file; }
const std::string& Style() const { return m_style; }
const std::string& Family() const { return m_family; }
std::vector<FONTINFO>& Children() { return m_children; }
private:
std::string m_file;
std::string m_style;
std::string m_family;
std::vector<FONTINFO> m_children;
};
} // namespace fontconfig
#endif //FONT_FONTINFO_H

View File

@ -47,6 +47,21 @@ public:
};
class OUTLINE_GLYPH : public GLYPH, public SHAPE_POLY_SET
{
public:
OUTLINE_GLYPH() :
SHAPE_POLY_SET()
{}
bool IsOutline() const override { return true; }
BOX2D BoundingBox() override;
void Mirror( const VECTOR2D& aMirrorOrigin = VECTOR2D( 0, 0 ) ) override;
};
class STROKE_GLYPH : public GLYPH, public std::vector<std::vector<VECTOR2D>>
{
public:

View File

@ -0,0 +1,128 @@
/*
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright (C) 2021 Ola Rinta-Koski
* Copyright (C) 2021 Kicad Developers, see AUTHORS.txt for contributors.
*
* Outline 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
*/
#ifndef OUTLINE_DECOMPOSER_H
#define OUTLINE_DECOMPOSER_H
#include <vector>
#include <freetype2/ft2build.h>
#include FT_FREETYPE_H
#include FT_OUTLINE_H
#include <math/box2.h>
#include <math/vector2d.h>
#include <font/glyph.h>
namespace KIFONT
{
typedef std::vector<VECTOR2D> GLYPH_POINTS;
typedef std::vector<GLYPH_POINTS> GLYPH_POINTS_LIST;
typedef std::vector<BOX2D> GLYPH_BOUNDING_BOX_LIST;
typedef struct
{
GLYPH_POINTS points;
int winding;
FT_Orientation orientation;
} CONTOUR;
typedef std::vector<CONTOUR> CONTOURS;
class OUTLINE_DECOMPOSER
{
public:
OUTLINE_DECOMPOSER( FT_Outline& aOutline );
void OutlineToSegments( CONTOURS* aContours );
private:
void contourToSegmentsAndArcs( CONTOUR& aResult, unsigned int aContourIndex ) const;
void newContour();
void addContourPoint( const VECTOR2D& p );
int approximateContour( const GLYPH_POINTS& aPoints, const std::vector<bool>& aPointOnCurve,
GLYPH_POINTS& aResult ) const;
bool approximateBezierCurve( GLYPH_POINTS& result, const GLYPH_POINTS& bezier ) const;
bool approximateQuadraticBezierCurve( GLYPH_POINTS& result, const GLYPH_POINTS& bezier ) const;
bool approximateCubicBezierCurve( GLYPH_POINTS& result, const GLYPH_POINTS& bezier ) const;
/**
* @return 1 if aContour is in clockwise order, -1 if it is in
* counterclockwise order, or 0 if the winding can't be
* determined.
*/
int winding( const GLYPH_POINTS& aContour ) const;
inline static const unsigned int onCurve( char aTags )
{
return aTags & 0x1;
}
inline static const unsigned int thirdOrderBezierPoint( char aTags )
{
return onCurve( aTags ) ? 0 : aTags & 0x2;
}
inline static const unsigned int secondOrderBezierPoint( char aTags )
{
return onCurve( aTags ) ? 0 : !thirdOrderBezierPoint( aTags );
}
inline static const unsigned int hasDropout( char aTags )
{
return aTags & 0x4;
}
inline static const unsigned int dropoutMode( char aTags )
{
return hasDropout( aTags ) ? ( aTags & 0x38 ) : 0;
}
// FT_Outline_Decompose callbacks
static int moveTo( const FT_Vector* aEndPoint, void* aCallbackData );
static int lineTo( const FT_Vector* aEndPoint, void* aCallbackData );
static int quadraticTo( const FT_Vector* aControlPoint, const FT_Vector* aEndPoint,
void* aCallbackData );
static int cubicTo( const FT_Vector* aFirstControlPoint, const FT_Vector* aSecondControlPoint,
const FT_Vector* aEndPoint, void* aCallbackData );
private:
FT_Outline& m_outline;
CONTOURS* m_contours;
VECTOR2D m_lastEndPoint;
};
} //namespace KIFONT
#endif // OUTLINE_DECOMPOSER_H

166
include/font/outline_font.h Normal file
View File

@ -0,0 +1,166 @@
/*
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright (C) 2021 Ola Rinta-Koski
* Copyright (C) 2021-2022 Kicad Developers, see AUTHORS.txt for contributors.
*
* Outline 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
*/
#ifndef OUTLINE_FONT_H_
#define OUTLINE_FONT_H_
#include <gal/graphics_abstraction_layer.h>
#include <geometry/shape_poly_set.h>
#include <freetype2/ft2build.h>
#include FT_FREETYPE_H
#include FT_OUTLINE_H
//#include <gal/opengl/opengl_freetype.h>
#include <harfbuzz/hb.h>
#include <font/font.h>
#include <font/glyph.h>
#include <font/outline_decomposer.h>
namespace KIFONT
{
/**
* Class OUTLINE_FONT implements outline font drawing.
*/
class OUTLINE_FONT : public FONT
{
public:
OUTLINE_FONT();
bool IsOutline() const override { return true; }
bool IsBold() const override
{
return m_face && ( m_face->style_flags & FT_STYLE_FLAG_BOLD );
}
bool IsItalic() const override
{
return m_face && ( m_face->style_flags & FT_STYLE_FLAG_ITALIC );
}
/**
* Load an outline font. TrueType (.ttf) and OpenType (.otf) are supported.
* @param aFontFileName is the (platform-specific) fully qualified name of the font file
*/
static OUTLINE_FONT* LoadFont( const wxString& aFontFileName, bool aBold, bool aItalic );
#if 0
/**
* Draw a string.
*
* @param aGal
* @param aText is the text to be drawn.
* @param aPosition is the text position in world coordinates.
* @param aOrigin is the item origin
* @param aAttributes contains text attributes (angle, line spacing, ...)
* @return bounding box width/height
*/
VECTOR2D Draw( KIGFX::GAL* aGal, const UTF8& aText, const VECTOR2D& aPosition,
const VECTOR2D& aOrigin, const TEXT_ATTRIBUTES& aAttributes ) const override;
#endif
/**
* Compute the boundary limits of aText (the bounding box of all shapes).
*
* The overbar and alignment are not taken in account, '~' characters are skipped.
*
* @return a VECTOR2D giving the width and height of text.
*/
VECTOR2D StringBoundaryLimits( const KIGFX::GAL* aGal, const UTF8& aText,
const VECTOR2D& aGlyphSize,
double aGlyphThickness ) const override;
/**
* Compute the vertical position of an overbar. This is the distance between the text
* baseline and the overbar.
*/
double ComputeOverbarVerticalPosition( double aGlyphHeight ) const override;
/**
* Compute the distance (interline) between 2 lines of text (for multiline texts). This is
* the distance between baselines, not the space between line bounding boxes.
*/
double GetInterline( double aGlyphHeight = 0.0, double aLineSpacing = 1.0 ) const override;
/**
* Compute the X and Y size of a given text. The text is expected to be a single line.
*/
VECTOR2D ComputeTextLineSize( const KIGFX::GAL* aGal, const UTF8& aText ) const override;
VECTOR2I GetTextAsGlyphs( BOX2I* aBoundingBox, std::vector<std::unique_ptr<GLYPH>>& aGlyphs,
const UTF8& aText, const VECTOR2D& aGlyphSize,
const wxPoint& aPosition, const EDA_ANGLE& aAngle,
TEXT_STYLE_FLAGS aTextStyle ) const override;
/**
* Like GetTextAsGlyphs, but handles multiple lines.
* TODO: Combine with GetTextAsGlyphs, maybe with a boolean parameter,
* but it's possible a non-line-breaking version isn't even needed
*
* @param aGlyphs returns text glyphs
* @param aText the text item
*/
VECTOR2I GetLinesAsGlyphs( std::vector<std::unique_ptr<GLYPH>>& aGlyphs,
const EDA_TEXT* aText ) const;
const FT_Face& GetFace() const { return m_face; }
#if 0
void RenderToOpenGLCanvas( KIGFX::OPENGL_FREETYPE& aTarget, const UTF8& aString,
const VECTOR2D& aGlyphSize, const wxPoint& aPosition,
double aOrientation, bool aIsMirrored ) const;
#endif
protected:
VECTOR2D getBoundingBox( const UTF8& aString, const VECTOR2D& aGlyphSize,
TEXT_STYLE_FLAGS aTextStyle ) const override;
FT_Error loadFace( const wxString& aFontFileName );
bool loadFontSimple( const wxString& aFontFileName );
BOX2I getBoundingBox( const std::vector<std::unique_ptr<GLYPH>>& aGlyphs ) const;
private:
// FreeType variables
static FT_Library m_freeType;
FT_Face m_face;
const int m_faceSize;
FT_Face m_subscriptFace;
const int m_subscriptSize;
int m_faceScaler;
int m_subscriptFaceScaler;
// 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;
};
} //namespace KIFONT
#endif // OUTLINE_FONT_H_

View File

@ -0,0 +1,37 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2021 Ola Rinta-Koski
* Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.txt for contributors.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef TRIANGULATE_H
#define TRIANGULATE_H
#include <math/vector2d.h>
#include <font/glyph.h>
#include <geometry/shape_poly_set.h>
#include <functional>
typedef std::function<void( int, const VECTOR2I& aPoint1, const VECTOR2I& aPoint2,
const VECTOR2I& aPoint3, void* aCallbackData )>
TRIANGULATE_CALLBACK;
void Triangulate( const SHAPE_POLY_SET& aPolylist, TRIANGULATE_CALLBACK aCallback,
void* aCallbackData = nullptr );
#endif // TRIANGULATE_H

View File

@ -524,6 +524,8 @@ private:
VECTOR2D getScreenPixelSize() const;
void fillPolygonAsTriangles( const SHAPE_POLY_SET& aPolyList );
/**
* Basic OpenGL initialization and feature checks.
*

View File

@ -52,24 +52,27 @@
"drc_exclusions": [],
"meta": {
"filename": "board_design_settings.json",
"version": 1
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint_type_mismatch": "error",
"hole_clearance": "error",
"hole_near_hole": "error",
"invalid_outline": "error",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "error",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"missing_courtyard": "ignore",
@ -79,9 +82,14 @@
"padstack": "error",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_over_copper": "error",
"silk_overlap": "error",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_dangling": "warning",
"track_width": "error",
@ -97,27 +105,29 @@
"allow_microvias": false,
"max_error": 0.005,
"min_clearance": 0.0,
"min_copper_edge_clearance": 0.01,
"min_hole_clearance": 0.0,
"min_copper_edge_clearance": 0.1016,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.508,
"min_microvia_drill": 0.2032,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.7999999999999999,
"min_text_thickness": 0.12,
"min_through_hole_diameter": 0.508,
"min_track_width": 0.2032,
"min_via_annular_width": 0.049999999999999996,
"min_via_diameter": 0.889,
"solder_mask_clearance": 0.254,
"solder_mask_min_width": 0.0,
"solder_paste_clearance": 0.0,
"solder_paste_margin_ratio": 0.0
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"track_widths": [],
"via_dimensions": [],
"zones_allow_external_fillets": false,
"zones_use_no_outline": true
},
"layer_presets": []
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
@ -176,7 +186,7 @@
}
],
"meta": {
"version": 0
"version": 2
},
"net_colors": null
},