diff --git a/LICENSES b/LICENSES index 64b56064..f2ad2d6f 100644 --- a/LICENSES +++ b/LICENSES @@ -36,3 +36,9 @@ c) Segoe-UI Fonts Code: https://github.com/mrbvrz/segoe-ui-linux License: https://github.com/mrbvrz/segoe-ui-linux/blob/master/license.txt + +d) Font Awesome + + Code: https://fontawesome.com/ + SPDX-License-Identifier: OFL-1.1-RFN + Copyright (c) 2024 Fonticons, Inc. diff --git a/playground/CMakeLists.txt b/playground/CMakeLists.txt index 30ddc7d1..592c4938 100644 --- a/playground/CMakeLists.txt +++ b/playground/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory(dials) add_subdirectory(dialogbuttons) add_subdirectory(fonts) add_subdirectory(gradients) +add_subdirectory(iconbrowser) add_subdirectory(invoker) add_subdirectory(shadows) add_subdirectory(shapes) diff --git a/playground/iconbrowser/CMakeLists.txt b/playground/iconbrowser/CMakeLists.txt new file mode 100644 index 00000000..945f9ef4 --- /dev/null +++ b/playground/iconbrowser/CMakeLists.txt @@ -0,0 +1,15 @@ +############################################################################ +# QSkinny - Copyright (C) The authors +# SPDX-License-Identifier: BSD-3-Clause +############################################################################ + +set(SOURCES + GlyphListView.h GlyphListView.cpp + main.cpp +) + +qt_add_resources(SOURCES + fonts.qrc +) + +qsk_add_example(iconbrowser ${SOURCES}) diff --git a/playground/iconbrowser/GlyphListView.cpp b/playground/iconbrowser/GlyphListView.cpp new file mode 100644 index 00000000..70fc9f90 --- /dev/null +++ b/playground/iconbrowser/GlyphListView.cpp @@ -0,0 +1,120 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "GlyphListView.h" + +#include +#include +#include + +#include +#include + +constexpr int glyphSize = 100; + +GlyphListView::GlyphListView( QQuickItem* parentItem ) + : Inherited( parentItem ) +{ +} + +GlyphListView::GlyphListView( const QString& fontName, QQuickItem* parentItem ) + : GlyphListView( parentItem ) +{ + setFont( QRawFont( fontName, 16 ) ); + setFontRoleHint( Text, QskFontRole::Title ); +} + +void GlyphListView::setFontPath( const QString& fontPath ) +{ + setFont( QRawFont( fontPath, 16 ) ); +} + +void GlyphListView::setFont( const QRawFont& font ) +{ + m_glyphTable.setIconFont( font ); + + const auto names = m_glyphTable.nameTable(); + + m_nameTable.clear(); + m_nameTable.reserve( names.size() ); + + m_maxNameWidth = 0; + + { + const QFontMetricsF fm( effectiveFont( Text ) ); + + for ( auto it = names.constBegin(); it != names.constEnd(); ++it ) + { + m_nameTable.insert( it.value(), it.key() ); + + const qreal w = qskHorizontalAdvance( fm, it.key() ); + if ( w > m_maxNameWidth ) + m_maxNameWidth = w; + } + } + + updateScrollableSize(); + update(); +} + +QRawFont GlyphListView::font() const +{ + return m_glyphTable.iconFont(); +} + +int GlyphListView::rowCount() const +{ + return m_glyphTable.glyphCount() - 1; +} + +int GlyphListView::columnCount() const +{ + return 3; +} + +qreal GlyphListView::columnWidth( int col ) const +{ + switch( col ) + { + case 0: + { + const QFontMetricsF fm( effectiveFont( Text ) ); + return qskHorizontalAdvance( fm, "999999" ); + } + + case 1: + return glyphSize; + + case 2: + return m_maxNameWidth; + } + + return 0; +} + +qreal GlyphListView::rowHeight() const +{ + return glyphSize + 20; +} + +QVariant GlyphListView::valueAt( int row, int col ) const +{ + const auto glyphIndex = row + 1; + switch( col ) + { + case 0: + return QVariant::fromValue( QString::number( glyphIndex ) ); + + case 1: + return QVariant::fromValue( m_glyphTable.glyphGraphic( glyphIndex ) ); + + case 2: + return QVariant::fromValue( m_nameTable.value( glyphIndex ) ); + } + + return QVariant(); +} + +#include "moc_GlyphListView.cpp" diff --git a/playground/iconbrowser/GlyphListView.h b/playground/iconbrowser/GlyphListView.h new file mode 100644 index 00000000..8e9239b6 --- /dev/null +++ b/playground/iconbrowser/GlyphListView.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#pragma once + +#include +#include +#include + +class GlyphListView : public QskListView +{ + Q_OBJECT + using Inherited = QskListView; + + public: + GlyphListView( QQuickItem* = nullptr); + GlyphListView( const QString&, QQuickItem* = nullptr); + + void setFontPath( const QString& ); + void setFont( const QRawFont& ); + QRawFont font() const; + + int rowCount() const override; + int columnCount() const override; + + virtual qreal columnWidth( int col ) const override; + virtual qreal rowHeight() const override; + + QVariant valueAt( int row, int col ) const override; + + private: + QskGlyphTable m_glyphTable; + + QHash< uint, QString > m_nameTable; + int m_maxNameWidth = 0; +}; diff --git a/playground/iconbrowser/fonts.qrc b/playground/iconbrowser/fonts.qrc new file mode 100644 index 00000000..2a0a4935 --- /dev/null +++ b/playground/iconbrowser/fonts.qrc @@ -0,0 +1,11 @@ + + + + fonts/FluentSystemIcons-Resizable.ttf + fonts/Font Awesome 6 Free-Regular-400.otf + fonts/IcoMoon-Free.ttf + fonts/MaterialSymbolsOutlined.ttf + fonts/Octicons.ttf + + + diff --git a/playground/iconbrowser/fonts/FluentSystemIcons-Resizable.ttf b/playground/iconbrowser/fonts/FluentSystemIcons-Resizable.ttf new file mode 100644 index 00000000..b8f87598 Binary files /dev/null and b/playground/iconbrowser/fonts/FluentSystemIcons-Resizable.ttf differ diff --git a/playground/iconbrowser/fonts/Font Awesome 6 Free-Regular-400.otf b/playground/iconbrowser/fonts/Font Awesome 6 Free-Regular-400.otf new file mode 100644 index 00000000..17a83506 Binary files /dev/null and b/playground/iconbrowser/fonts/Font Awesome 6 Free-Regular-400.otf differ diff --git a/playground/iconbrowser/fonts/IcoMoon-Free.ttf b/playground/iconbrowser/fonts/IcoMoon-Free.ttf new file mode 100644 index 00000000..56919449 Binary files /dev/null and b/playground/iconbrowser/fonts/IcoMoon-Free.ttf differ diff --git a/playground/iconbrowser/fonts/LICENSES b/playground/iconbrowser/fonts/LICENSES new file mode 100644 index 00000000..b17cc65e --- /dev/null +++ b/playground/iconbrowser/fonts/LICENSES @@ -0,0 +1,26 @@ +a) Material3 + + Code: https://github.com/google/material-design-icons + SPDX-License-Identifier: Apache License 2.0 + +b) Fluent2 + + Code: https://github.com/microsoft/fluentui-system-icons + SPDX-License-Identifier: MIT License + +c) Font Awesome + + Code: https://fontawesome.com/ + SPDX-License-Identifier: OFL-1.1-RFN + Copyright (c) 2024 Fonticons, Inc. + +d) IcoMoon + + Code: https://github.com/Keyamoon/IcoMoon-Free + SPDX-License-Identifier: CC-BY-4.0 + +e) Octicons + + Code: https://github.com/primer/octicons + SPDX-License-Identifier: MIT License + diff --git a/playground/iconbrowser/fonts/MaterialSymbolsOutlined.ttf b/playground/iconbrowser/fonts/MaterialSymbolsOutlined.ttf new file mode 100644 index 00000000..d446767b Binary files /dev/null and b/playground/iconbrowser/fonts/MaterialSymbolsOutlined.ttf differ diff --git a/playground/iconbrowser/fonts/Octicons.ttf b/playground/iconbrowser/fonts/Octicons.ttf new file mode 100755 index 00000000..9e091053 Binary files /dev/null and b/playground/iconbrowser/fonts/Octicons.ttf differ diff --git a/playground/iconbrowser/main.cpp b/playground/iconbrowser/main.cpp new file mode 100644 index 00000000..2a9ad9ad --- /dev/null +++ b/playground/iconbrowser/main.cpp @@ -0,0 +1,100 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "GlyphListView.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace +{ + class Header : public QskLinearBox + { + Q_OBJECT + + public: + Header( QQuickItem* parent = nullptr ) + : QskLinearBox( Qt::Horizontal, parent ) + { + setPaddingHint( QskBox::Panel, 5 ); + + initSizePolicy( QskSizePolicy::Ignored, QskSizePolicy::Fixed ); + setPanel( true ); + + m_comboBox = new QskComboBox( this ); + + const auto entries = QDir( ":iconfonts" ).entryInfoList(); + for ( const auto& entry : entries ) + m_comboBox->addOption( QUrl(), entry.absoluteFilePath() ); + + m_comboBox->setCurrentIndex( 0 ); + + connect( m_comboBox, &QskComboBox::currentIndexChanged, + this, &Header::comboBoxChanged ); + } + + QString fontPath() const { return m_comboBox->currentText(); } + + Q_SIGNALS: + void fontChanged( const QString& ); + + private: + void comboBoxChanged() { Q_EMIT fontChanged( fontPath() ); } + + QskComboBox* m_comboBox; + }; + + class MainView : public QskMainView + { + public: + MainView( QQuickItem* parent = nullptr ) + : QskMainView( parent ) + { + auto listView = new GlyphListView( this ); + auto header = new Header( this ); + + setHeader( header ); + setBody( listView ); + + listView->setFontPath( header->fontPath() ); + + connect( header, &Header::fontChanged, + listView, &GlyphListView::setFontPath ); + } + }; +} + +int main( int argc, char* argv[] ) +{ +#ifdef ITEM_STATISTICS + QskObjectCounter counter( true ); +#endif + + QGuiApplication app( argc, argv ); + + SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts ); + + auto mainView = new QskMainView(); + mainView->setBody( new GlyphListView( ":/fonts/Octicons.ttf" ) ); + + QskWindow window; + window.addItem( new MainView() ); + window.addItem( new QskFocusIndicator() ); + window.resize( 600, 400 ); + window.show(); + + return app.exec(); +} + +#include "main.moc" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7802c70c..5550ca53 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -77,6 +77,8 @@ list(APPEND PRIVATE_HEADERS list(APPEND HEADERS graphic/QskColorFilter.h + graphic/QskGlyphGraphicProvider.h + graphic/QskGlyphTable.h graphic/QskGraphic.h graphic/QskGraphicImageProvider.h graphic/QskGraphicIO.h @@ -91,6 +93,8 @@ list(APPEND HEADERS list(APPEND SOURCES graphic/QskColorFilter.cpp + graphic/QskGlyphGraphicProvider.cpp + graphic/QskGlyphTable.cpp graphic/QskGraphic.cpp graphic/QskGraphicImageProvider.cpp graphic/QskGraphicIO.cpp diff --git a/src/graphic/QskGlyphGraphicProvider.cpp b/src/graphic/QskGlyphGraphicProvider.cpp new file mode 100644 index 00000000..9986476c --- /dev/null +++ b/src/graphic/QskGlyphGraphicProvider.cpp @@ -0,0 +1,84 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskGlyphGraphicProvider.h" +#include "QskGraphic.h" +#include "QskGlyphTable.h" + +#include +#include +#include + +class QskGlyphGraphicProvider::PrivateData +{ + public: + QskGlyphTable glyphTable; +}; + +QskGlyphGraphicProvider::QskGlyphGraphicProvider( QObject* parent ) + : QskGraphicProvider( parent ) + , m_data( new PrivateData ) +{ +} + +QskGlyphGraphicProvider::~QskGlyphGraphicProvider() +{ +} + +void QskGlyphGraphicProvider::setIconFont( const QRawFont& font ) +{ + m_data->glyphTable.setIconFont( font ); +} + +QRawFont QskGlyphGraphicProvider::iconFont() const +{ + return m_data->glyphTable.iconFont(); +} + +QskGraphic QskGlyphGraphicProvider::glyphGraphic( uint index ) const +{ + return m_data->glyphTable.glyphGraphic( index ); +} + +const QskGraphic* QskGlyphGraphicProvider::loadGraphic( const QString& key ) const +{ + if ( const auto index = glyphIndex( key ) ) + { + const auto graphic = glyphGraphic( index ); + if ( !graphic.isNull() ) + return new QskGraphic( graphic ); + } + + return nullptr; +} + +uint QskGlyphGraphicProvider::glyphIndex( const QString& key ) const +{ + const auto& table = m_data->glyphTable; + + if ( ( table.glyphCount() > 0 ) && !key.isEmpty() ) + { + if ( key.startsWith( '#' ) ) + { + bool ok; + const auto glyphIndex = key.toUInt( &ok ); + if ( ok ) + return glyphIndex; + } + else if ( key.startsWith( "U+" ) ) + { + bool ok; + const char32_t code = key.mid( 2 ).toUInt( &ok, 16 ); + if ( ok ) + return table.codeToIndex( code ); + } + else + { + return table.nameToIndex( key ); + } + } + + return 0; +} diff --git a/src/graphic/QskGlyphGraphicProvider.h b/src/graphic/QskGlyphGraphicProvider.h new file mode 100644 index 00000000..15523093 --- /dev/null +++ b/src/graphic/QskGlyphGraphicProvider.h @@ -0,0 +1,35 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_GLYPH_GRAPHIC_PROVIDER_H +#define QSK_GLYPH_GRAPHIC_PROVIDER_H + +#include "QskGraphicProvider.h" + +class QRawFont; + +class QSK_EXPORT QskGlyphGraphicProvider : public QskGraphicProvider +{ + using Inherited = QskGraphicProvider; + + public: + QskGlyphGraphicProvider( QObject* parent = nullptr ); + ~QskGlyphGraphicProvider() override; + + void setIconFont( const QRawFont& ); + QRawFont iconFont() const; + + QskGraphic glyphGraphic( uint glyphIndex ) const; + + protected: + const QskGraphic* loadGraphic( const QString& ) const override final; + virtual uint glyphIndex( const QString& ) const; + + private: + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif diff --git a/src/graphic/QskGlyphTable.cpp b/src/graphic/QskGlyphTable.cpp new file mode 100644 index 00000000..ca6cccca --- /dev/null +++ b/src/graphic/QskGlyphTable.cpp @@ -0,0 +1,489 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskGlyphTable.h" +#include "QskGraphic.h" +#include "QskInternalMacros.h" + +#include +#include +#include +#include + +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END + +typedef QHash< QString, uint > GlyphNameTable; + +/* + The "parsers" below do no validation checks as the font has + already been validated by QRawFont + + Hope QRawFont will extend its API some day ( the underlying + font libraries do support glyph names ) and we can remove the nasty + code below. see https://bugreports.qt.io/browse/QTBUG-132629 + */ +namespace PostTableParser +{ + // https://learn.microsoft.com/en-us/typography/opentype/spec/post + + static inline QString toString( const uint8_t* s ) + { + // Pascal string. Valid characters are: [A–Z] [a–z] [0–9] '.' '_' + return QString::fromUtf8( + reinterpret_cast< const char* > ( s + 1 ), *s ); + } + + static GlyphNameTable glyphNames( const QByteArray& blob ) + { + GlyphNameTable names; + + const auto* glyphData = reinterpret_cast< const uint16_t* >( blob.constData() + 32 ); + if ( const auto nglyphs = qFromBigEndian( *glyphData++ ) ) + { + QVarLengthArray< const uint8_t* > strings; + strings.reserve( nglyphs ); + + const auto from = reinterpret_cast< const uint8_t* >( glyphData + nglyphs ); + const auto to = reinterpret_cast< const uint8_t* >( blob.data() + blob.size() ); + + for ( auto s = from; s < to; s += *s + 1 ) + strings += s; + + for ( int i = 0; i < nglyphs; i++ ) + { + const int idx = qFromBigEndian( glyphData[i] ) - 258; + + if ( idx >= 0 ) + names.insert( toString( strings[idx] ), i ); + } + } + + return names; + } +} + +namespace CFFTableParser +{ + // https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf + + static uint32_t offsetAt( const uint8_t* d, int size ) + { + switch (size) + { + case 0: + return 0; + case 1: + return d[0]; + case 2: + return ( d[0] << 8 ) | d[1]; + case 3: + return ( d[0] << 16 ) | ( d[1] << 8 ) | d[2]; + default: + return ( d[0] << 24 ) | ( d[1] << 16 ) | ( d[2] << 8 ) | d[3]; + } + } + + static int indexDataSize( const uint8_t* d ) + { + const int nitems = ( d[0] << 8 ) | d[1]; + if ( nitems == 0 ) + return 2; + + const int size = d[2]; + + const uint8_t* lx = d + 3 + nitems * size; + return lx + size - 1 + offsetAt( lx, size ) - d; + } + + static const uint8_t* indexData( const uint8_t* d ) + { + const int nitems = ( d[0] << 8 ) | d[1]; + d += 2; + + if ( nitems == 0 ) + return d; + + const int size = d[0]; + + const uint8_t* _contents = d + ( nitems + 1 ) * size; + return _contents + offsetAt(d + 1, size); + } + + static const uint8_t* skipHeader( const uint8_t* d ) + { + return d + d[2]; + } + + static const uint8_t* skipIndex( const uint8_t* d ) + { + return d + indexDataSize( d ); + } + + static QVector< int > stringIds( const uint8_t* data, int nglyphs ) + { + const int format = data[0]; + + QVector< int > _sids; + _sids += 0; + + const uint8_t* p = data + 1; + switch( format ) + { + case 0: + { + for (; _sids.size() < nglyphs; p += 2) + { + int sid = ( p[0] << 8 ) | p[1]; + _sids += sid; + } + break; + } + case 1: + { + for (; _sids.size() < nglyphs; p += 3) + { + const int sid = ( p[0] << 8 ) | p[1]; + const int n = p[2]; + + for ( int i = 0; i <= n; i++ ) + _sids += sid + i; + } + break; + } + case 2: + { + for (; _sids.size() < nglyphs; p += 4) + { + const int sid = ( p[0] << 8 ) | p[1]; + const int n = ( p[2] << 8 ) | p[3]; + + for ( int i = 0; i <= n; i++ ) + _sids += sid + i; + } + break; + } + } + + return _sids; + } + + static int dictValue( const uint8_t* d, int op ) + { + /* + see https://adobe-type-tools.github.io/font-tech-notes/pdfs/5176.CFF.pdf + We are intersted in the offset ( operator 15 ). + */ + + const uint8_t* data = indexData( d ); + + int value = 0; + + Q_FOREVER + { + // operand + const auto valueType = data[0]; + + if ( valueType == 28 ) + { + value = ( data[1] << 8 ) | data[2]; + data += 3; + } + else if ( valueType == 29 ) + { + value = ( data[1] << 24 ) | ( data[2] << 16 ) + | ( data[3] << 8 ) | data[4]; + + data += 5; + } + else if ( valueType == 30 ) + { + /* + Assuming, that an offset is never a double + we skip the operand without reading it + */ + while( ++data ) + { + const int b = *data; + if ( ( b & 0x0f ) == 0x0f || ( b & 0xf0 ) == 0xf0 ) + break; // 0xf nibble indicating the end + } + + data++; + } + else if ( valueType >= 31 && valueType <= 246 ) + { + value = data[0] - 139; + data++; + } + else if ( valueType >= 247 && valueType <= 250 ) + { + value = ( ( data[0] - 247 ) << 8 ) + data[1] + 108; + data += 2; + } + else if ( valueType >= 251 && valueType <= 254 ) + { + value = -( ( data[0] - 251 ) << 8 ) - data[1] - 108; + data += 2; + } + + // operator + + if ( op == data[0] ) + return value; + + data += ( data[0] == 12 ) ? 2 : 1; + } + + return 0; // default value + } + + class StringTable + { + public: + StringTable( const uint8_t* data ) + { + const int nitems = ( data[0] << 8 ) | data[1]; + if ( nitems == 0 ) + { + _contents = data + 2; + _offset = nullptr; + _offsize = 0; + } + else + { + _offsize = data[2]; + _offset = data + 3; + + _contents = _offset + nitems * _offsize + _offsize - 1; + } + } + + inline QString operator[]( int which ) const + { + const auto x = _offset + which * _offsize; + + const auto d1 = _contents + offsetAt(x, _offsize); + const auto d2 = _contents + offsetAt(x + _offsize, _offsize); + + return QString::fromUtf8( + reinterpret_cast< const char* >( d1 ), d2 - d1 ); + } + + private: + const uint8_t* _contents = nullptr; + const uint8_t* _offset = nullptr; + int _offsize = -1; + }; + + static GlyphNameTable glyphNames( const QByteArray& blob, int glyphCount ) + { + auto data = reinterpret_cast< const uint8_t* >( blob.constData() ); + + auto nameIndex = skipHeader( data ); + auto dictIndex = skipIndex( nameIndex ); + auto stringIndex = skipIndex( dictIndex ); + + auto charset = data + dictValue( dictIndex, 15 ); // 15: charset offset + + const QVector< int > sids = stringIds( charset, glyphCount ); + + const StringTable table( stringIndex ); + + GlyphNameTable names; + + for ( int i = 0; i < glyphCount; i++ ) + { + /* + The first 391 SIDs are reserved for standard strings + ( Appendix A ) that are not from the charset table. + */ + + const auto idx = sids[i] - 391; + + if ( idx >= 0 ) + names.insert( table[idx], i ); + } + + return names; + } +} + +static uint qskGlyphCount( const QRawFont& font ) +{ + /* + we could also read the count from the "maxp" table: + https://learn.microsoft.com/en-us/typography/opentype/spec/maxp + */ + + const auto fontEngine = QRawFontPrivate::get( font )->fontEngine; + return fontEngine ? fontEngine->glyphCount() : 0; +} + +static GlyphNameTable qskGlyphNameTable( const QRawFont& font ) +{ + const auto count = qskGlyphCount( font ); + if ( count > 0 ) + { + /* + The Compact Font Format ( CFF ) table has been introduced with + the OpenType specification. For not complying fonts the names + might be found in the Post table + */ + auto blob = font.fontTable( "CFF "); + if ( !blob.isEmpty() ) + return CFFTableParser::glyphNames( blob, count ); + + blob = font.fontTable( "post" ); + if ( !blob.isEmpty() ) + return PostTableParser::glyphNames( blob ); + } + + return GlyphNameTable(); +} + +static inline quint32 qskGlyphIndex( const QRawFont& font, char32_t ucs4 ) +{ + if ( qskGlyphCount( font ) == 0 ) + return 0; + + const auto idxs = font.glyphIndexesForString( + QString::fromUcs4( &ucs4, 1 ) ); + + // fingers crossed: icon fonts will map code points to single glyphs only + Q_ASSERT( idxs.size() == 1 ); + + return idxs.size() == 1 ? idxs[0] : 0; +} + +class QskGlyphTable::PrivateData +{ + public: + inline const GlyphNameTable& glyphNameTable() const + { + if ( !validNames ) + { + auto that = const_cast< PrivateData* >( this ); + that->nameTable = qskGlyphNameTable( font ); + that->validNames = true; + } + + return nameTable; + } + + QRawFont font; + GlyphNameTable nameTable; + bool validNames = false; +}; + +QskGlyphTable::QskGlyphTable() + : m_data( new PrivateData ) +{ +} + +QskGlyphTable::QskGlyphTable( const QRawFont& font ) + : QskGlyphTable() +{ + m_data->font = font; +} + +QskGlyphTable::QskGlyphTable( const QskGlyphTable& other ) + : QskGlyphTable() +{ + *m_data = *other.m_data; +} + +QskGlyphTable::~QskGlyphTable() +{ +} + +QskGlyphTable& QskGlyphTable::operator=( const QskGlyphTable& other ) +{ + if ( m_data != other.m_data ) + *m_data = *other.m_data; + + return *this; +} + +void QskGlyphTable::setIconFont( const QRawFont& font ) +{ + if ( font != m_data->font ) + { + m_data->font = font; + m_data->nameTable.clear(); + m_data->validNames = false; + } +} + +QRawFont QskGlyphTable::iconFont() const +{ + return m_data->font; +} + +QPainterPath QskGlyphTable::glyphPath( uint glyphIndex ) const +{ + QPainterPath path; + + /* + Unfortunately QRawFont::pathForGlyph runs into failing checks + when being called from a different thread - f.e the scene graph thread. + So we need to bypass QRawFont and retrieve from its fontEngine. + */ + if ( auto fontEngine = QRawFontPrivate::get( m_data->font )->fontEngine ) + { + QFixedPoint position; + quint32 idx = glyphIndex; + + fontEngine->addGlyphsToPath( &idx, &position, 1, &path, {} ); + } + + return path; +} + +QskGraphic QskGlyphTable::glyphGraphic( uint glyphIndex ) const +{ + QskGraphic graphic; + + if ( glyphIndex > 0 && qskGlyphCount( m_data->font ) > 0 ) + { + const auto path = glyphPath( glyphIndex ); + + if ( !path.isEmpty() ) + { + QPainter painter( &graphic ); + painter.setRenderHint( QPainter::Antialiasing, true ); + painter.fillPath( path, Qt::black ); + } + } + + return graphic; +} + +uint QskGlyphTable::glyphCount() const +{ + return qskGlyphCount( m_data->font ); +} + +uint QskGlyphTable::codeToIndex( char32_t ucs4 ) const +{ + return qskGlyphIndex( m_data->font, ucs4 ); +} + +uint QskGlyphTable::nameToIndex( const QString& name ) const +{ + /* + Names/Codes that do not correspond to any glyph in should + be mapped to glyph index 0. + + see https://learn.microsoft.com/en-us/typography/opentype/spec/cmap + */ + return m_data->glyphNameTable().value( name.toLatin1(), 0 ); +} + +QHash< QString, uint > QskGlyphTable::nameTable() const +{ + return m_data->glyphNameTable(); +} diff --git a/src/graphic/QskGlyphTable.h b/src/graphic/QskGlyphTable.h new file mode 100644 index 00000000..c218eab0 --- /dev/null +++ b/src/graphic/QskGlyphTable.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_GLYPH_TABLE_H +#define QSK_GLYPH_TABLE_H + +#include "QskGlobal.h" +#include + +class QskGraphic; +class QRawFont; +class QString; +class QPainterPath; +class QByteArray; +class QChar; + +template< typename Key, typename T > class QHash; + +class QSK_EXPORT QskGlyphTable +{ + public: + QskGlyphTable(); + QskGlyphTable( const QRawFont& ); + QskGlyphTable( const QskGlyphTable& ); + + ~QskGlyphTable(); + + QskGlyphTable& operator=( const QskGlyphTable& ); + + bool isValid() const; + + void setIconFont( const QRawFont& ); + QRawFont iconFont() const; + + uint glyphCount() const; + + QPainterPath glyphPath( uint glyphIndex ) const; + QskGraphic glyphGraphic( uint glyphIndex ) const; + + /* + Most icon fonts use code points from the Unicode Private Use Areas (PUA) + ( see https://en.wikipedia.org/wiki/Private_Use_Areas ): + + - [0x00e000, 0x00f8ff] + - [0x0f0000, 0x0ffffd] + - [0x100000, 0x10fffd] + + Note that QChar is 16-bit entity only that does not cover the higher PUA blocks. + */ + uint codeToIndex( char32_t ) const; + + /* + True/OpenType fonts often include a table with glyph names, that can be used + instead of the glyph index + */ + uint nameToIndex( const QString& ) const; + + QHash< QString, uint > nameTable() const; + + private: + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +inline bool QskGlyphTable::isValid() const +{ + return glyphCount() > 0; +} + +#endif