Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[api] Provide a mechanism to specify a list of fallback font families…
… for

QgsTextFormat

Just like in CSS, these families will be used as an ordered list
of fonts to fallback on if the actual text format font isn't available
on a particular QGIS install.

This is API only, and isn't designed to be shown anywhere in QGIS.
Instead the intended use is for creators of QGIS styles to either use
the raw api to specify the list of fallback fonts OR hand edit the
style xml to add the fallback fonts, e.g by adding a block like:

    <families>
      <family name="Arial"/>
      <family name="Helvetica"/>
      <family name="Sans"/>
    </families>

To the "text-style" parent element.
  • Loading branch information
nyalldawson committed Apr 22, 2021
1 parent ceff950 commit e5c709c
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 24 deletions.
32 changes: 32 additions & 0 deletions python/core/auto_generated/textrenderer/qgstextformat.sip.in
Expand Up @@ -216,6 +216,38 @@ Sets the named style for the font used for rendering text.
.. seealso:: :py:func:`namedStyle`

.. seealso:: :py:func:`setFont`
%End

QStringList families() const;
%Docstring
Returns the list of font families to use when restoring the text format, in order of precedence.

.. warning::

The list of families returned by this method is ONLY used when restoring the text format
from serialized versions, and will not affect the current :py:func:`~QgsTextFormat.font` familily used by the format.

.. seealso:: :py:func:`setFamilies`

.. versionadded:: 3.20
%End

void setFamilies( const QStringList &families );
%Docstring
Sets a list of font ``families`` to use for the text format, in order of precedence.

When restoring serialized versions of the text format then the first matching font family
from this list will be used for the text format. This provides a way to specify a list of possible
font families which are used as fallbacks if a family isn't available on a particular QGIS install (CSS style).

.. warning::

The list of families set by calling this method is ONLY used when restoring the text format
from serialized versions, and will not affect the current :py:func:`~QgsTextFormat.font` familily used by the format.

.. seealso:: :py:func:`families`

.. versionadded:: 3.20
%End

double size() const;
Expand Down
56 changes: 49 additions & 7 deletions src/core/textrenderer/qgstextformat.cpp
Expand Up @@ -84,6 +84,7 @@ bool QgsTextFormat::operator==( const QgsTextFormat &other ) const
|| mBackgroundSettings != other.mBackgroundSettings
|| mShadowSettings != other.mShadowSettings
|| mMaskSettings != other.mMaskSettings
|| d->families != other.families()
|| d->mDataDefinedProperties != other.dataDefinedProperties() )
return false;

Expand Down Expand Up @@ -201,6 +202,17 @@ void QgsTextFormat::setNamedStyle( const QString &style )
d->textNamedStyle = style;
}

QStringList QgsTextFormat::families() const
{
return d->families;
}

void QgsTextFormat::setFamilies( const QStringList &families )
{
d->isValid = true;
d->families = families;
}

QgsUnitTypes::RenderUnit QgsTextFormat::sizeUnit() const
{
return d->fontSizeUnits;
Expand Down Expand Up @@ -419,16 +431,36 @@ void QgsTextFormat::readXml( const QDomElement &elem, const QgsReadWriteContext
QFont appFont = QApplication::font();
mTextFontFamily = textStyleElem.attribute( QStringLiteral( "fontFamily" ), appFont.family() );
QString fontFamily = mTextFontFamily;
if ( mTextFontFamily != appFont.family() && !QgsFontUtils::fontFamilyMatchOnSystem( mTextFontFamily ) )

const QDomElement familiesElem = textStyleElem.firstChildElement( QStringLiteral( "families" ) );
const QDomNodeList familyNodes = familiesElem.childNodes();
QStringList families;
families.reserve( familyNodes.size() );
for ( int i = 0; i < familyNodes.count(); ++i )
{
// trigger to notify user about font family substitution (signal emitted in QgsVectorLayer::prepareLabelingAndDiagrams)
mTextFontFound = false;
const QDomElement familyElem = familyNodes.at( i ).toElement();
families << familyElem.attribute( QStringLiteral( "name" ) );
}
d->families = families;

// TODO: update when pref for how to resolve missing family (use matching algorithm or just default font) is implemented
// currently only defaults to matching algorithm for resolving [foundry], if a font of similar family is found (default for QFont)
mTextFontFound = false;
if ( mTextFontFamily != appFont.family() && !QgsFontUtils::fontFamilyMatchOnSystem( mTextFontFamily ) )
{
for ( const QString &family : std::as_const( families ) )
{
if ( QgsFontUtils::fontFamilyMatchOnSystem( family ) )
{
mTextFontFound = true;
fontFamily = family;
break;
}
}

// for now, do not use matching algorithm for substitution if family not found, substitute default instead
fontFamily = appFont.family();
if ( !mTextFontFound )
{
// couldn't even find a matching font in the backup list -- substitute default instead
fontFamily = appFont.family();
}
}
else
{
Expand Down Expand Up @@ -562,6 +594,16 @@ QDomElement QgsTextFormat::writeXml( QDomDocument &doc, const QgsReadWriteContex
// text style
QDomElement textStyleElem = doc.createElement( QStringLiteral( "text-style" ) );
textStyleElem.setAttribute( QStringLiteral( "fontFamily" ), d->textFont.family() );

QDomElement familiesElem = doc.createElement( QStringLiteral( "families" ) );
for ( const QString &family : std::as_const( d->families ) )
{
QDomElement familyElem = doc.createElement( QStringLiteral( "family" ) );
familyElem.setAttribute( QStringLiteral( "name" ), family );
familiesElem.appendChild( familyElem );
}
textStyleElem.appendChild( familiesElem );

textStyleElem.setAttribute( QStringLiteral( "namedStyle" ), QgsFontUtils::untranslateNamedStyle( d->textNamedStyle ) );
textStyleElem.setAttribute( QStringLiteral( "fontSize" ), d->fontSize );
textStyleElem.setAttribute( QStringLiteral( "fontSizeUnit" ), QgsUnitTypes::encodeUnit( d->fontSizeUnits ) );
Expand Down
26 changes: 26 additions & 0 deletions src/core/textrenderer/qgstextformat.h
Expand Up @@ -221,6 +221,32 @@ class CORE_EXPORT QgsTextFormat
*/
void setNamedStyle( const QString &style );

/**
* Returns the list of font families to use when restoring the text format, in order of precedence.
*
* \warning The list of families returned by this method is ONLY used when restoring the text format
* from serialized versions, and will not affect the current font() familily used by the format.
*
* \see setFamilies()
* \since QGIS 3.20
*/
QStringList families() const;

/**
* Sets a list of font \a families to use for the text format, in order of precedence.
*
* When restoring serialized versions of the text format then the first matching font family
* from this list will be used for the text format. This provides a way to specify a list of possible
* font families which are used as fallbacks if a family isn't available on a particular QGIS install (CSS style).
*
* \warning The list of families set by calling this method is ONLY used when restoring the text format
* from serialized versions, and will not affect the current font() familily used by the format.
*
* \see families()
* \since QGIS 3.20
*/
void setFamilies( const QStringList &families );

/**
* Returns the size for rendered text. Units are retrieved using sizeUnit().
* \see setSize()
Expand Down
2 changes: 2 additions & 0 deletions src/core/textrenderer/qgstextrenderer_p.h
Expand Up @@ -262,6 +262,7 @@ class QgsTextSettingsPrivate : public QSharedData
: QSharedData( other )
, isValid( other.isValid )
, textFont( other.textFont )
, families( other.families )
, textNamedStyle( other.textNamedStyle )
, fontSizeUnits( other.fontSizeUnits )
, fontSizeMapUnitScale( other.fontSizeMapUnitScale )
Expand All @@ -280,6 +281,7 @@ class QgsTextSettingsPrivate : public QSharedData

bool isValid = false;
QFont textFont;
QStringList families;
QString textNamedStyle;
QgsUnitTypes::RenderUnit fontSizeUnits = QgsUnitTypes::RenderPoints;
QgsMapUnitScale fontSizeMapUnitScale;
Expand Down

0 comments on commit e5c709c

Please sign in to comment.