Skip to content
Permalink
Browse files
Raise a user-visible warning message if fonts referenced in a QGIS
project are not available when loading that project on a system
without the required fonts installed

Currently shown for any QgsTextForamt or font marker symbol layer
restored when opening the project
  • Loading branch information
nyalldawson committed May 24, 2021
1 parent b086e6e commit 5f9d7b4809977e381543b4cc0057bb19e3659d82
@@ -974,6 +974,14 @@ Creates a new QgsFontMarkerSymbolLayer from a property map (see :py:func:`~QgsFo
Creates a new QgsFontMarkerSymbolLayer from an SLD XML ``element``.
%End

static void resolveFonts( const QVariantMap &properties, const QgsReadWriteContext &context );
%Docstring
Resolves fonts from a ``properties`` map, raising warnings in the specified ``context`` if the
required fonts are not available on the system.

.. versionadded:: 3.20
%End


virtual QString layerType() const;

@@ -63,6 +63,16 @@ This ensures that paths in project files can be relative, but in symbol layer
instances the paths are always absolute

.. versionadded:: 3.0
%End

virtual void resolveFonts( const QVariantMap &properties, const QgsReadWriteContext &context );
%Docstring
Resolve fonts from the symbol layer's ``properties``.

This tests whether the required fonts from the encoded ``properties`` are available on the system, and records
warnings in the ``context`` if not.

.. versionadded:: 3.20
%End

protected:
@@ -87,8 +97,11 @@ Convenience metadata class that uses static functions to create symbol layer and
virtual QgsSymbolLayer *createSymbolLayerFromSld( QDomElement &elem ) /Factory/;
virtual void resolvePaths( QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving );

virtual void resolveFonts( const QVariantMap &properties, const QgsReadWriteContext &context );

protected:


private:
QgsSymbolLayerMetadata();
};
@@ -139,6 +152,16 @@ This normally means converting relative paths to absolute paths when loading
and converting absolute paths to relative paths when saving.

.. versionadded:: 3.0
%End

void resolveFonts( const QString &name, QVariantMap &properties, const QgsReadWriteContext &context ) const;
%Docstring
Resolve fonts from the ``properties`` of a particular symbol layer.

This tests whether the required fonts from the encoded ``properties`` are available on the system, and records
warnings in the ``context`` if not.

.. versionadded:: 3.20
%End

QStringList symbolLayersForType( Qgis::SymbolType type );
@@ -3666,6 +3666,14 @@ QgsSymbolLayer *QgsFontMarkerSymbolLayer::createFromSld( QDomElement &element )
return m;
}

void QgsFontMarkerSymbolLayer::resolveFonts( const QVariantMap &properties, const QgsReadWriteContext &context )
{
const QString fontFamily = properties.value( QStringLiteral( "font" ), DEFAULT_FONTMARKER_FONT ).toString();
if ( !QgsFontUtils::fontFamilyMatchOnSystem( fontFamily ) )
{
context.pushMessage( QObject::tr( "Font “%1” not available on system" ).arg( fontFamily ) );
}
}

void QgsSvgMarkerSymbolLayer::prepareExpressions( const QgsSymbolRenderContext &context )
{
@@ -895,6 +895,14 @@ class CORE_EXPORT QgsFontMarkerSymbolLayer : public QgsMarkerSymbolLayer
*/
static QgsSymbolLayer *createFromSld( QDomElement &element ) SIP_FACTORY;

/**
* Resolves fonts from a \a properties map, raising warnings in the specified \a context if the
* required fonts are not available on the system.
*
* \since QGIS 3.20
*/
static void resolveFonts( const QVariantMap &properties, const QgsReadWriteContext &context );

// implemented from base classes

QString layerType() const override;
@@ -48,7 +48,7 @@ QgsSymbolLayerRegistry::QgsSymbolLayerRegistry()
addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "RasterMarker" ), QObject::tr( "Raster Image Marker" ), Qgis::SymbolType::Marker,
QgsRasterMarkerSymbolLayer::create, nullptr, QgsRasterFillSymbolLayer::resolvePaths ) );
addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "FontMarker" ), QObject::tr( "Font Marker" ), Qgis::SymbolType::Marker,
QgsFontMarkerSymbolLayer::create, QgsFontMarkerSymbolLayer::createFromSld ) );
QgsFontMarkerSymbolLayer::create, QgsFontMarkerSymbolLayer::createFromSld, nullptr, nullptr, QgsFontMarkerSymbolLayer::resolveFonts ) );
addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "EllipseMarker" ), QObject::tr( "Ellipse Marker" ), Qgis::SymbolType::Marker,
QgsEllipseSymbolLayer::create, QgsEllipseSymbolLayer::createFromSld ) );
addSymbolLayerType( new QgsSymbolLayerMetadata( QStringLiteral( "VectorField" ), QObject::tr( "Vector Field Marker" ), Qgis::SymbolType::Marker,
@@ -144,6 +144,14 @@ void QgsSymbolLayerRegistry::resolvePaths( const QString &name, QVariantMap &pro
mMetadata[name]->resolvePaths( properties, pathResolver, saving );
}

void QgsSymbolLayerRegistry::resolveFonts( const QString &name, QVariantMap &properties, const QgsReadWriteContext &context ) const
{
if ( !mMetadata.contains( name ) )
return;

mMetadata[name]->resolveFonts( properties, context );
}

QStringList QgsSymbolLayerRegistry::symbolLayersForType( Qgis::SymbolType type )
{
QStringList lst;
@@ -24,6 +24,7 @@ class QgsVectorLayer;
class QgsSymbolLayerWidget SIP_EXTERNAL;
class QgsSymbolLayer;
class QDomElement;
class QgsReadWriteContext;

/**
* \ingroup core
@@ -76,6 +77,20 @@ class CORE_EXPORT QgsSymbolLayerAbstractMetadata
Q_UNUSED( saving )
}

/**
* Resolve fonts from the symbol layer's \a properties.
*
* This tests whether the required fonts from the encoded \a properties are available on the system, and records
* warnings in the \a context if not.
*
* \since QGIS 3.20
*/
virtual void resolveFonts( const QVariantMap &properties, const QgsReadWriteContext &context )
{
Q_UNUSED( properties )
Q_UNUSED( context )
}

protected:
QString mName;
QString mVisibleName;
@@ -86,6 +101,7 @@ typedef QgsSymbolLayer *( *QgsSymbolLayerCreateFunc )( const QVariantMap & ) SIP
typedef QgsSymbolLayerWidget *( *QgsSymbolLayerWidgetFunc )( QgsVectorLayer * ) SIP_SKIP;
typedef QgsSymbolLayer *( *QgsSymbolLayerCreateFromSldFunc )( QDomElement & ) SIP_SKIP;
typedef void ( *QgsSymbolLayerPathResolverFunc )( QVariantMap &, const QgsPathResolver &, bool ) SIP_SKIP;
typedef void ( *QgsSymbolLayerFontResolverFunc )( const QVariantMap &, const QgsReadWriteContext & ) SIP_SKIP;

/**
* \ingroup core
@@ -100,12 +116,14 @@ class CORE_EXPORT QgsSymbolLayerMetadata : public QgsSymbolLayerAbstractMetadata
QgsSymbolLayerCreateFunc pfCreate,
QgsSymbolLayerCreateFromSldFunc pfCreateFromSld = nullptr,
QgsSymbolLayerPathResolverFunc pfPathResolver = nullptr,
QgsSymbolLayerWidgetFunc pfWidget = nullptr ) SIP_SKIP
QgsSymbolLayerWidgetFunc pfWidget = nullptr,
QgsSymbolLayerFontResolverFunc pfFontResolver = nullptr ) SIP_SKIP
: QgsSymbolLayerAbstractMetadata( name, visibleName, type )
, mCreateFunc( pfCreate )
, mWidgetFunc( pfWidget )
, mCreateFromSldFunc( pfCreateFromSld )
, mPathResolverFunc( pfPathResolver )
, mFontResolverFunc( pfFontResolver )
{}

//! \note not available in Python bindings
@@ -129,12 +147,25 @@ class CORE_EXPORT QgsSymbolLayerMetadata : public QgsSymbolLayerAbstractMetadata
mPathResolverFunc( properties, pathResolver, saving );
}

void resolveFonts( const QVariantMap &properties, const QgsReadWriteContext &context ) override
{
if ( mFontResolverFunc )
mFontResolverFunc( properties, context );
}

protected:
QgsSymbolLayerCreateFunc mCreateFunc;
QgsSymbolLayerWidgetFunc mWidgetFunc;
QgsSymbolLayerCreateFromSldFunc mCreateFromSldFunc;
QgsSymbolLayerPathResolverFunc mPathResolverFunc;

/**
* Font resolver function pointer.
*
* \since QGIS 3.20
*/
QgsSymbolLayerFontResolverFunc mFontResolverFunc;

private:
#ifdef SIP_RUN
QgsSymbolLayerMetadata();
@@ -181,6 +212,16 @@ class CORE_EXPORT QgsSymbolLayerRegistry
*/
void resolvePaths( const QString &name, QVariantMap &properties, const QgsPathResolver &pathResolver, bool saving ) const;

/**
* Resolve fonts from the \a properties of a particular symbol layer.
*
* This tests whether the required fonts from the encoded \a properties are available on the system, and records
* warnings in the \a context if not.
*
* \since QGIS 3.20
*/
void resolveFonts( const QString &name, QVariantMap &properties, const QgsReadWriteContext &context ) const;

//! Returns a list of available symbol layers for a specified symbol type
QStringList symbolLayersForType( Qgis::SymbolType type );

@@ -1171,6 +1171,8 @@ QgsSymbolLayer *QgsSymbolLayerUtils::loadSymbolLayer( QDomElement &element, cons
// if there are any paths stored in properties, convert them from relative to absolute
QgsApplication::symbolLayerRegistry()->resolvePaths( layerClass, props, context.pathResolver(), false );

QgsApplication::symbolLayerRegistry()->resolveFonts( layerClass, props, context );

QgsSymbolLayer *layer = nullptr;
layer = QgsApplication::symbolLayerRegistry()->createSymbolLayer( layerClass, props );
if ( layer )
@@ -467,6 +467,11 @@ void QgsTextFormat::readXml( const QDomElement &elem, const QgsReadWriteContext
mTextFontFound = true;
}

if ( !mTextFontFound )
{
context.pushMessage( QObject::tr( "Font “%1” not available on system" ).arg( mTextFontFamily ) );
}

if ( textStyleElem.hasAttribute( QStringLiteral( "fontSize" ) ) )
{
d->fontSize = textStyleElem.attribute( QStringLiteral( "fontSize" ) ).toDouble();

0 comments on commit 5f9d7b4

Please sign in to comment.