Files
2025-09-19 21:08:01 +01:00

510 lines
15 KiB
C++

/*
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright The 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 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 <plotters/pdf_stroke_font.h>
#include <algorithm>
#include <cstdint>
#include <limits>
#include <fmt/format.h>
#include <font/glyph.h>
#include <advanced_config.h>
namespace
{
static constexpr int MAX_SIMPLE_FONT_CODES = 256;
// Build the stroked path for a glyph.
// KiCad's internal stroke font glyph coordinates use an inverted Y axis relative to the
// PDF coordinate system we are targeting here. We therefore optionally flip Y so text
// renders upright. A slightly thicker default stroke width (4% of EM) is used to improve
// legibility at typical plot zoom levels.
static std::string buildGlyphStream( const KIFONT::STROKE_GLYPH* aGlyph, double aUnitsPerEm,
bool aInvertY, bool aBold )
{
if( !aGlyph )
return std::string();
fmt::memory_buffer buffer;
double factor = ADVANCED_CFG::GetCfg().m_PDFStrokeFontWidthFactor;
if( aBold )
{
double boldMul = ADVANCED_CFG::GetCfg().m_PDFStrokeFontBoldMultiplier;
if( boldMul < 1.0 ) boldMul = 1.0;
factor *= boldMul;
}
if( factor <= 0.0 )
factor = 0.04; // fallback safety
double lw = aUnitsPerEm * factor;
fmt::format_to( std::back_inserter( buffer ), "{:.3f} w 1 J 1 j ", lw );
auto& cfg = ADVANCED_CFG::GetCfg();
for( const std::vector<VECTOR2D>& stroke : *aGlyph )
{
bool firstPoint = true;
for( const VECTOR2D& point : stroke )
{
double x = ( point.x + cfg.m_PDFStrokeFontXOffset ) * aUnitsPerEm;
double y = point.y * aUnitsPerEm;
if( aInvertY )
{
y = -y; // Mirror vertically about baseline (y=0)
y += cfg.m_PDFStrokeFontYOffset * aUnitsPerEm;
}
if( firstPoint )
{
fmt::format_to( std::back_inserter( buffer ), "{:.3f} {:.3f} m ", x, y );
firstPoint = false;
}
else
{
fmt::format_to( std::back_inserter( buffer ), "{:.3f} {:.3f} l ", x, y );
}
}
if( !stroke.empty() )
fmt::format_to( std::back_inserter( buffer ), "S " );
}
return std::string( buffer.data(), buffer.size() );
}
static std::string formatUnicodeHex( uint32_t aCodepoint )
{
if( aCodepoint <= 0xFFFF )
return fmt::format( "{:04X}", aCodepoint );
if( aCodepoint <= 0x10FFFF )
{
uint32_t value = aCodepoint - 0x10000;
uint16_t high = 0xD800 + ( value >> 10 );
uint16_t low = 0xDC00 + ( value & 0x3FF );
return fmt::format( "{:04X}{:04X}", high, low );
}
return std::string( "003F" );
}
} // anonymous namespace
PDF_STROKE_FONT_SUBSET::PDF_STROKE_FONT_SUBSET( const KIFONT::STROKE_FONT* aFont, double aUnitsPerEm,
unsigned aSubsetIndex, bool aBold, bool aItalic ) :
m_font( aFont ),
m_unitsPerEm( aUnitsPerEm ),
m_resourceName( fmt::format( "/KiCadStroke{}", aSubsetIndex ) ),
m_cmapName( fmt::format( "KiCadStrokeCMap{}", aSubsetIndex ) ),
m_widths( MAX_SIMPLE_FONT_CODES, 0.0 ),
m_nextCode( 1 ),
m_lastCode( 0 ),
m_bboxMinX( std::numeric_limits<double>::max() ),
m_bboxMinY( std::numeric_limits<double>::max() ),
m_bboxMaxX( std::numeric_limits<double>::lowest() ),
m_bboxMaxY( std::numeric_limits<double>::lowest() ),
m_charProcsHandle( -1 ),
m_fontHandle( -1 ),
m_toUnicodeHandle( -1 ),
m_isBold( aBold ),
m_isItalic( aItalic )
{
GLYPH notdef;
notdef.m_unicode = 0;
notdef.m_code = 0;
notdef.m_glyphIndex = -1;
notdef.m_name = ".notdef";
notdef.m_stream.clear();
notdef.m_width = 0.0;
notdef.m_minX = 0.0;
notdef.m_minY = 0.0;
notdef.m_maxX = 0.0;
notdef.m_maxY = 0.0;
notdef.m_charProcHandle = -1;
m_glyphs.push_back( notdef );
m_bboxMinX = 0.0;
m_bboxMinY = 0.0;
m_bboxMaxX = 0.0;
m_bboxMaxY = 0.0;
}
bool PDF_STROKE_FONT_SUBSET::Contains( wxUniChar aCode ) const
{
return m_unicodeToCode.find( aCode ) != m_unicodeToCode.end();
}
int PDF_STROKE_FONT_SUBSET::EnsureGlyph( wxUniChar aCode )
{
auto it = m_unicodeToCode.find( aCode );
if( it != m_unicodeToCode.end() )
return it->second;
if( IsFull() )
return -1;
int glyphIndex = glyphIndexForUnicode( aCode );
int code = m_nextCode++;
m_lastCode = std::max( m_lastCode, code );
GLYPH data;
data.m_unicode = aCode;
data.m_code = code;
data.m_glyphIndex = glyphIndex;
data.m_name = makeGlyphName( code );
data.m_charProcHandle = -1;
const KIFONT::STROKE_GLYPH* glyph = nullptr;
BOX2D bbox;
if( m_font )
{
glyph = m_font->GetGlyph( glyphIndex );
bbox = m_font->GetGlyphBoundingBox( glyphIndex );
}
VECTOR2D origin = bbox.GetOrigin();
VECTOR2D size = bbox.GetSize();
data.m_width = size.x * m_unitsPerEm;
data.m_minX = origin.x * m_unitsPerEm;
data.m_minY = origin.y * m_unitsPerEm;
data.m_maxX = ( origin.x + size.x ) * m_unitsPerEm;
data.m_maxY = ( origin.y + size.y ) * m_unitsPerEm;
// Invert Y so glyphs render upright in PDF coordinate space.
bool invertY = true;
if( invertY )
{
// Mirror bounding box vertically about baseline.
double newMinY = -data.m_maxY;
double newMaxY = -data.m_minY;
data.m_minY = newMinY;
data.m_maxY = newMaxY;
// Apply Y offset to bounding box to match the offset applied to stroke coordinates
double yOffset = ADVANCED_CFG::GetCfg().m_PDFStrokeFontYOffset * m_unitsPerEm;
data.m_minY += yOffset;
data.m_maxY += yOffset;
}
// Build charproc stream: first specify width and bbox (d1 operator) then stroke path.
double kerningFactor = ADVANCED_CFG::GetCfg().m_PDFStrokeFontKerningFactor;
if( kerningFactor <= 0.0 )
kerningFactor = 1.0;
std::string strokes = buildGlyphStream( glyph, m_unitsPerEm, invertY, m_isBold );
data.m_width = size.x * m_unitsPerEm * kerningFactor;
data.m_stream = fmt::format( "{:.3f} 0 {:.3f} {:.3f} {:.3f} {:.3f} d1 {}",
data.m_width,
data.m_minX, data.m_minY, data.m_maxX, data.m_maxY,
strokes );
m_widths[code] = data.m_width;
m_bboxMinX = std::min( m_bboxMinX, data.m_minX );
m_bboxMinY = std::min( m_bboxMinY, data.m_minY );
m_bboxMaxX = std::max( m_bboxMaxX, data.m_maxX );
m_bboxMaxY = std::max( m_bboxMaxY, data.m_maxY );
m_glyphs.push_back( std::move( data ) );
m_unicodeToCode.emplace( aCode, code );
return code;
}
int PDF_STROKE_FONT_SUBSET::CodeForGlyph( wxUniChar aCode ) const
{
auto it = m_unicodeToCode.find( aCode );
if( it != m_unicodeToCode.end() )
return it->second;
return -1;
}
bool PDF_STROKE_FONT_SUBSET::IsFull() const
{
return m_nextCode >= MAX_SIMPLE_FONT_CODES;
}
int PDF_STROKE_FONT_SUBSET::GlyphCount() const
{
return static_cast<int>( m_glyphs.size() );
}
int PDF_STROKE_FONT_SUBSET::FirstChar() const
{
return 0;
}
int PDF_STROKE_FONT_SUBSET::LastChar() const
{
return std::max( 0, m_lastCode );
}
std::string PDF_STROKE_FONT_SUBSET::BuildDifferencesArray() const
{
int first = FirstChar();
int last = LastChar();
fmt::memory_buffer buffer;
fmt::format_to( std::back_inserter( buffer ), "[ {} ", first );
for( int code = first; code <= last; ++code )
{
const GLYPH* glyph = glyphForCode( code );
if( glyph )
fmt::format_to( std::back_inserter( buffer ), "/{} ", glyph->m_name );
else
fmt::format_to( std::back_inserter( buffer ), "/.notdef " );
}
fmt::format_to( std::back_inserter( buffer ), "]" );
return std::string( buffer.data(), buffer.size() );
}
std::string PDF_STROKE_FONT_SUBSET::BuildWidthsArray() const
{
int first = FirstChar();
int last = LastChar();
fmt::memory_buffer buffer;
fmt::format_to( std::back_inserter( buffer ), "[" );
for( int code = first; code <= last; ++code )
fmt::format_to( std::back_inserter( buffer ), " {:g}", m_widths[code] );
fmt::format_to( std::back_inserter( buffer ), " ]" );
return std::string( buffer.data(), buffer.size() );
}
std::string PDF_STROKE_FONT_SUBSET::BuildToUnicodeCMap() const
{
size_t mappingCount = 0;
for( const GLYPH& glyph : m_glyphs )
{
if( glyph.m_code == 0 )
continue;
++mappingCount;
}
fmt::memory_buffer buffer;
fmt::format_to( std::back_inserter( buffer ), "/CIDInit /ProcSet findresource begin\n" );
fmt::format_to( std::back_inserter( buffer ), "12 dict begin\n" );
fmt::format_to( std::back_inserter( buffer ), "begincmap\n" );
fmt::format_to( std::back_inserter( buffer ), "/CIDSystemInfo << /Registry (KiCad) /Ordering (StrokeFont) /Supplement 0 >> def\n" );
fmt::format_to( std::back_inserter( buffer ), "/CMapName /{} def\n", m_cmapName );
fmt::format_to( std::back_inserter( buffer ), "/CMapType 2 def\n" );
fmt::format_to( std::back_inserter( buffer ), "1 begincodespacerange\n" );
fmt::format_to( std::back_inserter( buffer ), "<00> <FF>\n" );
fmt::format_to( std::back_inserter( buffer ), "endcodespacerange\n" );
fmt::format_to( std::back_inserter( buffer ), "{} beginbfchar\n", mappingCount );
for( const GLYPH& glyph : m_glyphs )
{
if( glyph.m_code == 0 )
continue;
fmt::format_to( std::back_inserter( buffer ), "<{:02X}> <{}>\n", glyph.m_code,
formatUnicodeHex( static_cast<uint32_t>( glyph.m_unicode ) ) );
}
fmt::format_to( std::back_inserter( buffer ), "endbfchar\n" );
fmt::format_to( std::back_inserter( buffer ), "endcmap\n" );
fmt::format_to( std::back_inserter( buffer ), "CMapName currentdict /CMap defineresource pop\n" );
fmt::format_to( std::back_inserter( buffer ), "end\n" );
fmt::format_to( std::back_inserter( buffer ), "end\n" );
return std::string( buffer.data(), buffer.size() );
}
int PDF_STROKE_FONT_SUBSET::glyphIndexForUnicode( wxUniChar aCode ) const
{
int value = static_cast<int>( aCode );
if( value < ' ' )
return static_cast<int>( '?' ) - ' ';
int index = value - ' ';
int count = static_cast<int>( m_font ? m_font->GetGlyphCount() : 0 );
if( index < 0 || index >= count )
return static_cast<int>( '?' ) - ' ';
return index;
}
std::string PDF_STROKE_FONT_SUBSET::makeGlyphName( int aCode ) const
{
return fmt::format( "g{:02X}", aCode );
}
const PDF_STROKE_FONT_SUBSET::GLYPH* PDF_STROKE_FONT_SUBSET::glyphForCode( int aCode ) const
{
for( const GLYPH& glyph : m_glyphs )
{
if( glyph.m_code == aCode )
return &glyph;
}
return nullptr;
}
PDF_STROKE_FONT_MANAGER::PDF_STROKE_FONT_MANAGER() :
m_font( KIFONT::STROKE_FONT::LoadFont( wxEmptyString ) ),
m_unitsPerEm( 1000.0 ),
m_nextSubsetIndex( 0 )
{
Reset();
}
void PDF_STROKE_FONT_MANAGER::Reset()
{
m_styleGroups.clear();
m_nextSubsetIndex = 0;
if( !m_font )
m_font.reset( KIFONT::STROKE_FONT::LoadFont( wxEmptyString ) );
}
PDF_STROKE_FONT_MANAGER::STYLE_GROUP& PDF_STROKE_FONT_MANAGER::groupFor( bool aBold, bool aItalic )
{
unsigned key = styleKey( aBold, aItalic );
return m_styleGroups[key];
}
void PDF_STROKE_FONT_MANAGER::EncodeString( const wxString& aText,
std::vector<PDF_STROKE_FONT_RUN>* aRuns,
bool aBold, bool aItalic )
{
if( !aRuns )
return;
aRuns->clear();
if( aText.empty() )
return;
PDF_STROKE_FONT_SUBSET* currentSubset = nullptr;
std::string currentBytes;
for( wxUniChar ch : aText )
{
PDF_STROKE_FONT_SUBSET* subset = ensureSubsetForGlyph( ch, aBold, aItalic );
if( !subset )
continue;
int code = subset->EnsureGlyph( ch );
if( code < 0 )
continue;
if( subset != currentSubset )
{
if( !currentBytes.empty() && currentSubset )
aRuns->push_back( { currentSubset, currentBytes, aBold, aItalic } );
currentSubset = subset;
currentBytes.clear();
}
currentBytes.push_back( static_cast<char>( code ) );
}
if( !currentBytes.empty() && currentSubset )
aRuns->push_back( { currentSubset, std::move( currentBytes ), aBold, aItalic } );
}
PDF_STROKE_FONT_SUBSET* PDF_STROKE_FONT_MANAGER::ensureSubsetForGlyph( wxUniChar aCode, bool aBold, bool aItalic )
{
STYLE_GROUP& group = groupFor( aBold, aItalic );
for( const std::unique_ptr<PDF_STROKE_FONT_SUBSET>& subset : group.subsets )
{
if( subset->Contains( aCode ) )
return subset.get();
}
for( const std::unique_ptr<PDF_STROKE_FONT_SUBSET>& subset : group.subsets )
{
if( subset->IsFull() )
continue;
if( subset->EnsureGlyph( aCode ) >= 0 )
return subset.get();
}
unsigned subsetIndex = m_nextSubsetIndex++;
auto newSubset = std::make_unique<PDF_STROKE_FONT_SUBSET>( m_font.get(), m_unitsPerEm, subsetIndex, aBold, aItalic );
PDF_STROKE_FONT_SUBSET* subsetPtr = newSubset.get();
subsetPtr->EnsureGlyph( aCode );
group.subsets.emplace_back( std::move( newSubset ) );
return subsetPtr;
}
std::vector<PDF_STROKE_FONT_SUBSET*> PDF_STROKE_FONT_MANAGER::AllSubsets() const
{
std::vector<PDF_STROKE_FONT_SUBSET*> out;
for( const auto& [key, group] : m_styleGroups )
{
(void) key;
for( const auto& up : group.subsets )
out.push_back( up.get() );
}
return out;
}