Skip to content
Permalink
Browse files
[feature] Support Small Caps style in labels/text renderer
Adds two new capitalization styles for labels and text symbols:

- Small Caps: Renders lowercase characters as small caps
- All Small Caps: Renders all characters as small caps (regardless
of their original case)

Requires Qt 6.3+, or Qt 5.15 using KDE's fork and the cmake
HAS_KDE_QT5_SMALL_CAPS_FIX switch defined during build.
  • Loading branch information
nyalldawson committed Nov 8, 2021
1 parent a168bca commit dae69acc76f48bfde05c666af78330c9433312ac
Showing with 176 additions and 99 deletions.
  1. +2 −1 CMakeLists.txt
  2. +2 −1 cmake_templates/qgsconfig.h.in
  3. +29 −0 python/core/auto_additions/qgis.py
  4. +14 −0 python/core/auto_generated/qgis.sip.in
  5. +1 −11 python/core/auto_generated/qgsstringutils.sip.in
  6. +1 −1 python/core/auto_generated/textrenderer/qgstextblock.sip.in
  7. +1 −1 python/core/auto_generated/textrenderer/qgstextdocument.sip.in
  8. +2 −2 python/core/auto_generated/textrenderer/qgstextformat.sip.in
  9. +1 −1 python/core/auto_generated/textrenderer/qgstextfragment.sip.in
  10. +1 −1 src/app/qgisapp.cpp
  11. +18 −8 src/core/labeling/qgspallabeling.cpp
  12. +6 −6 src/core/labeling/qgsvectorlayerlabeling.cpp
  13. +1 −1 src/core/layout/qgslayoutexporter.cpp
  14. +1 −1 src/core/processing/models/qgsprocessingmodelalgorithm.cpp
  15. +23 −0 src/core/qgis.h
  16. +1 −1 src/core/qgsmaplayer.cpp
  17. +2 −2 src/core/qgspaintenginehack.cpp
  18. +11 −9 src/core/qgsstringutils.cpp
  19. +1 −13 src/core/qgsstringutils.h
  20. +1 −1 src/core/textrenderer/qgstextblock.cpp
  21. +1 −1 src/core/textrenderer/qgstextblock.h
  22. +1 −1 src/core/textrenderer/qgstextdocument.cpp
  23. +1 −1 src/core/textrenderer/qgstextdocument.h
  24. +20 −6 src/core/textrenderer/qgstextformat.cpp
  25. +2 −2 src/core/textrenderer/qgstextformat.h
  26. +2 −1 src/core/textrenderer/qgstextfragment.cpp
  27. +2 −2 src/core/textrenderer/qgstextfragment.h
  28. +1 −1 src/core/textrenderer/qgstextrenderer_p.h
  29. +1 −1 src/gui/processing/qgsprocessingalgorithmdialogbase.cpp
  30. +13 −9 src/gui/qgstextformatwidget.cpp
  31. +1 −1 src/gui/qgsvectorlayertemporalpropertieswidget.cpp
  32. +6 −6 tests/src/core/testqgslabelingengine.cpp
  33. +6 −6 tests/src/core/testqgsstringutils.cpp
@@ -425,7 +425,8 @@ if(WITH_CORE)
else()
set(QT_MIN_VERSION 5.12.0)
set(QT_VERSION_BASE "Qt5")
set(WITH_QT5_KDE_FORK FALSE CACHE BOOL "Using KDE's Qt 5.15 fork")
set(HAS_KDE_QT5_PDF_TRANSFORM_FIX FALSE CACHE BOOL "Using KDE's Qt 5.15 fork with the PDF brush transform fix")
set(HAS_KDE_QT5_SMALL_CAPS_FIX FALSE CACHE BOOL "Using KDE's Qt 5.15 fork with the QFont::SmallCaps fix")
endif()

# Use Qt5SerialPort optionally for GPS
@@ -104,7 +104,8 @@

#cmakedefine ENABLE_TESTS

#cmakedefine WITH_QT5_KDE_FORK
#cmakedefine HAS_KDE_QT5_PDF_TRANSFORM_FIX
#cmakedefine HAS_KDE_QT5_SMALL_CAPS_FIX

#endif

@@ -1132,3 +1132,32 @@
Qgis.DashPatternSizeAdjustment.__doc__ = 'Dash pattern size adjustment options.\n\n.. versionadded:: 3.24\n\n' + '* ``ScaleBothDashAndGap``: ' + Qgis.DashPatternSizeAdjustment.ScaleBothDashAndGap.__doc__ + '\n' + '* ``ScaleDashOnly``: ' + Qgis.DashPatternSizeAdjustment.ScaleDashOnly.__doc__ + '\n' + '* ``ScaleGapOnly``: ' + Qgis.DashPatternSizeAdjustment.ScaleGapOnly.__doc__
# --
Qgis.DashPatternSizeAdjustment.baseClass = Qgis
QgsStringUtils.Capitalization = Qgis.Capitalization
# monkey patching scoped based enum
QgsStringUtils.MixedCase = Qgis.Capitalization.MixedCase
QgsStringUtils.MixedCase.is_monkey_patched = True
QgsStringUtils.MixedCase.__doc__ = "Mixed case, ie no change"
QgsStringUtils.AllUppercase = Qgis.Capitalization.AllUppercase
QgsStringUtils.AllUppercase.is_monkey_patched = True
QgsStringUtils.AllUppercase.__doc__ = "Convert all characters to uppercase"
QgsStringUtils.AllLowercase = Qgis.Capitalization.AllLowercase
QgsStringUtils.AllLowercase.is_monkey_patched = True
QgsStringUtils.AllLowercase.__doc__ = "Convert all characters to lowercase"
QgsStringUtils.ForceFirstLetterToCapital = Qgis.Capitalization.ForceFirstLetterToCapital
QgsStringUtils.ForceFirstLetterToCapital.is_monkey_patched = True
QgsStringUtils.ForceFirstLetterToCapital.__doc__ = "Convert just the first letter of each word to uppercase, leave the rest untouched"
QgsStringUtils.SmallCaps = Qgis.Capitalization.SmallCaps
QgsStringUtils.SmallCaps.is_monkey_patched = True
QgsStringUtils.SmallCaps.__doc__ = "Mixed case small caps (since QGIS 3.24)"
QgsStringUtils.TitleCase = Qgis.Capitalization.TitleCase
QgsStringUtils.TitleCase.is_monkey_patched = True
QgsStringUtils.TitleCase.__doc__ = "Simple title case conversion - does not fully grammatically parse the text and uses simple rules only. Note that this method does not convert any characters to lowercase, it only uppercases required letters. Callers must ensure that input strings are already lowercased."
QgsStringUtils.UpperCamelCase = Qgis.Capitalization.UpperCamelCase
QgsStringUtils.UpperCamelCase.is_monkey_patched = True
QgsStringUtils.UpperCamelCase.__doc__ = "Convert the string to upper camel case. Note that this method does not unaccent characters."
QgsStringUtils.AllSmallCaps = Qgis.Capitalization.AllSmallCaps
QgsStringUtils.AllSmallCaps.is_monkey_patched = True
QgsStringUtils.AllSmallCaps.__doc__ = "Force all characters to small caps (since QGIS 3.24)"
Qgis.Capitalization.__doc__ = 'String capitalization options.\n\n.. note::\n\n Prior to QGIS 3.24 this was available as :py:class:`QgsStringUtils`.Capitalization\n\n.. versionadded:: 3.24\n\n' + '* ``MixedCase``: ' + Qgis.Capitalization.MixedCase.__doc__ + '\n' + '* ``AllUppercase``: ' + Qgis.Capitalization.AllUppercase.__doc__ + '\n' + '* ``AllLowercase``: ' + Qgis.Capitalization.AllLowercase.__doc__ + '\n' + '* ``ForceFirstLetterToCapital``: ' + Qgis.Capitalization.ForceFirstLetterToCapital.__doc__ + '\n' + '* ``SmallCaps``: ' + Qgis.Capitalization.SmallCaps.__doc__ + '\n' + '* ``TitleCase``: ' + Qgis.Capitalization.TitleCase.__doc__ + '\n' + '* ``UpperCamelCase``: ' + Qgis.Capitalization.UpperCamelCase.__doc__ + '\n' + '* ``AllSmallCaps``: ' + Qgis.Capitalization.AllSmallCaps.__doc__
# --
Qgis.Capitalization.baseClass = Qgis
@@ -732,6 +732,20 @@ The development version
ScaleGapOnly,
};



enum class Capitalization
{
MixedCase,
AllUppercase,
AllLowercase,
ForceFirstLetterToCapital,
SmallCaps,
TitleCase,
UpperCamelCase,
AllSmallCaps,
};

static const double DEFAULT_SEARCH_RADIUS_MM;

static const float DEFAULT_MAPTOPIXEL_THRESHOLD;
@@ -169,17 +169,7 @@ Utility functions for working with strings.
%End
public:

enum Capitalization
{
MixedCase,
AllUppercase,
AllLowercase,
ForceFirstLetterToCapital,
TitleCase,
UpperCamelCase,
};

static QString capitalize( const QString &string, Capitalization capitalization );
static QString capitalize( const QString &string, Qgis::Capitalization capitalization );
%Docstring
Converts a string by applying capitalization rules to the string.

@@ -64,7 +64,7 @@ Returns ``True`` if the block is empty.
Returns the number of fragments in the block.
%End

void applyCapitalization( QgsStringUtils::Capitalization capitalization );
void applyCapitalization( Qgis::Capitalization capitalization );
%Docstring
Applies a ``capitalization`` style to the block's text.

@@ -116,7 +116,7 @@ argument controls whether the lines should be wrapped to an ideal maximum of ``a
if ``False`` then the lines are wrapped to an ideal minimum length of ``autoWrapLength`` characters.
%End

void applyCapitalization( QgsStringUtils::Capitalization capitalization );
void applyCapitalization( Qgis::Capitalization capitalization );
%Docstring
Applies a ``capitalization`` style to the document's text.

@@ -402,7 +402,7 @@ Sets the ``orientation`` for the text.
.. versionadded:: 3.10
%End

QgsStringUtils::Capitalization capitalization() const;
Qgis::Capitalization capitalization() const;
%Docstring
Returns the text capitalization style.

@@ -411,7 +411,7 @@ Returns the text capitalization style.
.. versionadded:: 3.16
%End

void setCapitalization( QgsStringUtils::Capitalization capitalization );
void setCapitalization( Qgis::Capitalization capitalization );
%Docstring
Sets the text ``capitalization`` style.

@@ -78,7 +78,7 @@ based on the resultant font metrics. Failure to do so will result in poor qualit
at small font sizes.
%End

void applyCapitalization( QgsStringUtils::Capitalization capitalization );
void applyCapitalization( Qgis::Capitalization capitalization );
%Docstring
Applies a ``capitalization`` style to the fragment's text.

@@ -9660,7 +9660,7 @@ bool QgisApp::uniqueLayoutTitle( QWidget *parent, QString &title, bool acceptEmp
layoutNames << l->name();
}

const QString windowTitle = tr( "Create %1" ).arg( QgsGui::higFlags() & QgsGui::HigDialogTitleIsTitleCase ? QgsStringUtils::capitalize( typeString, QgsStringUtils::TitleCase )
const QString windowTitle = tr( "Create %1" ).arg( QgsGui::higFlags() & QgsGui::HigDialogTitleIsTitleCase ? QgsStringUtils::capitalize( typeString, Qgis::Capitalization::TitleCase )
: typeString );

while ( !titleValid )
@@ -1897,11 +1897,11 @@ std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerFeatureWithDetails
}

// apply capitalization
QgsStringUtils::Capitalization capitalization = mFormat.capitalization();
Qgis::Capitalization capitalization = mFormat.capitalization();
// maintain API - capitalization may have been set in textFont
if ( capitalization == QgsStringUtils::MixedCase && mFormat.font().capitalization() != QFont::MixedCase )
if ( capitalization == Qgis::Capitalization::MixedCase && mFormat.font().capitalization() != QFont::MixedCase )
{
capitalization = static_cast< QgsStringUtils::Capitalization >( mFormat.font().capitalization() );
capitalization = static_cast< Qgis::Capitalization >( mFormat.font().capitalization() );
}
// data defined font capitalization?
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontCase ) )
@@ -1916,24 +1916,34 @@ std::unique_ptr<QgsLabelFeature> QgsPalLayerSettings::registerFeatureWithDetails
{
if ( fcase.compare( QLatin1String( "NoChange" ), Qt::CaseInsensitive ) == 0 )
{
capitalization = QgsStringUtils::MixedCase;
capitalization = Qgis::Capitalization::MixedCase;
}
else if ( fcase.compare( QLatin1String( "Upper" ), Qt::CaseInsensitive ) == 0 )
{
capitalization = QgsStringUtils::AllUppercase;
capitalization = Qgis::Capitalization::AllUppercase;
}
else if ( fcase.compare( QLatin1String( "Lower" ), Qt::CaseInsensitive ) == 0 )
{
capitalization = QgsStringUtils::AllLowercase;
capitalization = Qgis::Capitalization::AllLowercase;
}
else if ( fcase.compare( QLatin1String( "Capitalize" ), Qt::CaseInsensitive ) == 0 )
{
capitalization = QgsStringUtils::ForceFirstLetterToCapital;
capitalization = Qgis::Capitalization::ForceFirstLetterToCapital;
}
else if ( fcase.compare( QLatin1String( "Title" ), Qt::CaseInsensitive ) == 0 )
{
capitalization = QgsStringUtils::TitleCase;
capitalization = Qgis::Capitalization::TitleCase;
}
#if defined(HAS_KDE_QT5_SMALL_CAPS_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
else if ( fcase.compare( QLatin1String( "SmallCaps" ), Qt::CaseInsensitive ) == 0 )
{
capitalization = Qgis::Capitalization::SmallCaps;
}
else if ( fcase.compare( QLatin1String( "AllSmallCaps" ), Qt::CaseInsensitive ) == 0 )
{
capitalization = Qgis::Capitalization::AllSmallCaps;
}
#endif
}
}
}
@@ -294,18 +294,18 @@ void QgsAbstractVectorLayerLabeling::writeTextSymbolizer( QDomNode &parent, QgsP
}
else
{
QgsStringUtils::Capitalization capitalization = format.capitalization();
if ( capitalization == QgsStringUtils::MixedCase && font.capitalization() != QFont::MixedCase )
capitalization = static_cast< QgsStringUtils::Capitalization >( font.capitalization() );
if ( capitalization == QgsStringUtils::AllUppercase )
Qgis::Capitalization capitalization = format.capitalization();
if ( capitalization == Qgis::Capitalization::MixedCase && font.capitalization() != QFont::MixedCase )
capitalization = static_cast< Qgis::Capitalization >( font.capitalization() );
if ( capitalization == Qgis::Capitalization::AllUppercase )
{
appendSimpleFunction( doc, labelElement, QStringLiteral( "strToUpperCase" ), settings.fieldName );
}
else if ( capitalization == QgsStringUtils::AllLowercase )
else if ( capitalization == Qgis::Capitalization::AllLowercase )
{
appendSimpleFunction( doc, labelElement, QStringLiteral( "strToLowerCase" ), settings.fieldName );
}
else if ( capitalization == QgsStringUtils::ForceFirstLetterToCapital )
else if ( capitalization == Qgis::Capitalization::ForceFirstLetterToCapital )
{
appendSimpleFunction( doc, labelElement, QStringLiteral( "strCapitalize" ), settings.fieldName );
}
@@ -1219,7 +1219,7 @@ void QgsLayoutExporter::preparePrintAsPdf( QgsLayout *layout, QPrinter &printer,
// May not work on Windows or non-X11 Linux. Works fine on Mac using QPrinter::NativeFormat
//printer.setFontEmbeddingEnabled( true );

#if defined(WITH_QT5_KDE_FORK) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
#if defined(HAS_KDE_QT5_PDF_TRANSFORM_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
// paint engine hack not required, fixed upstream
#else
QgsPaintEngineHack::fixEngineFlags( printer.paintEngine() );
@@ -488,7 +488,7 @@ QStringList QgsProcessingModelAlgorithm::asPythonCode( const QgsProcessing::Pyth
n.replace( rx2, QString() );
if ( !capitalize )
n = n.replace( ' ', '_' );
return capitalize ? QgsStringUtils::capitalize( n, QgsStringUtils::UpperCamelCase ) : n;
return capitalize ? QgsStringUtils::capitalize( n, Qgis::Capitalization::UpperCamelCase ) : n;
};

auto uniqueSafeName = [ &safeName ]( const QString & name, bool capitalize, const QMap< QString, QString > &friendlyNames )->QString
@@ -1196,6 +1196,29 @@ class CORE_EXPORT Qgis
};
Q_ENUM( DashPatternSizeAdjustment )


// NOTE -- the hardcoded numbers here must match QFont::Capitalization!

/**
* String capitalization options.
*
* \note Prior to QGIS 3.24 this was available as QgsStringUtils::Capitalization
*
* \since QGIS 3.24
*/
enum class Capitalization SIP_MONKEYPATCH_SCOPEENUM_UNNEST( QgsStringUtils, Capitalization ) : int
{
MixedCase = 0, //!< Mixed case, ie no change
AllUppercase = 1, //!< Convert all characters to uppercase
AllLowercase = 2, //!< Convert all characters to lowercase
ForceFirstLetterToCapital = 4, //!< Convert just the first letter of each word to uppercase, leave the rest untouched
SmallCaps = 5, //!< Mixed case small caps (since QGIS 3.24)
TitleCase = 1004, //!< Simple title case conversion - does not fully grammatically parse the text and uses simple rules only. Note that this method does not convert any characters to lowercase, it only uppercases required letters. Callers must ensure that input strings are already lowercased.
UpperCamelCase = 1005, //!< Convert the string to upper camel case. Note that this method does not unaccent characters.
AllSmallCaps = 1006, //!< Force all characters to small caps (since QGIS 3.24)
};
Q_ENUM( Capitalization )

/**
* Identify search radius in mm
* \since QGIS 2.3
@@ -953,7 +953,7 @@ QString QgsMapLayer::formatLayerName( const QString &name )
{
QString layerName( name );
layerName.replace( '_', ' ' );
layerName = QgsStringUtils::capitalize( layerName, QgsStringUtils::ForceFirstLetterToCapital );
layerName = QgsStringUtils::capitalize( layerName, Qgis::Capitalization::ForceFirstLetterToCapital );
return layerName;
}

@@ -21,7 +21,7 @@
// Hack to workaround Qt #5114 by disabling PatternTransform
void QgsPaintEngineHack::fixFlags()
{
#if defined(WITH_QT5_KDE_FORK) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
#if defined(HAS_KDE_QT5_PDF_TRANSFORM_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
// not required, fixed upstream
#else
gccaps = PaintEngineFeatures();
@@ -50,7 +50,7 @@ void QgsPaintEngineHack::fixFlags()

void QgsPaintEngineHack::fixEngineFlags( QPaintEngine *engine )
{
#if defined(WITH_QT5_KDE_FORK) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
#if defined(HAS_KDE_QT5_PDF_TRANSFORM_FIX) || QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
// not required, fixed upstream
( void )engine;
#else
@@ -21,23 +21,25 @@
#include <QRegularExpression>
#include <cstdlib> // for std::abs

QString QgsStringUtils::capitalize( const QString &string, QgsStringUtils::Capitalization capitalization )
QString QgsStringUtils::capitalize( const QString &string, Qgis::Capitalization capitalization )
{
if ( string.isEmpty() )
return QString();

switch ( capitalization )
{
case MixedCase:
case Qgis::Capitalization::MixedCase:
case Qgis::Capitalization::SmallCaps:
return string;

case AllUppercase:
case Qgis::Capitalization::AllUppercase:
return string.toUpper();

case AllLowercase:
case Qgis::Capitalization::AllLowercase:
case Qgis::Capitalization::AllSmallCaps:
return string.toLower();

case ForceFirstLetterToCapital:
case Qgis::Capitalization::ForceFirstLetterToCapital:
{
QString temp = string;

@@ -58,7 +60,7 @@ QString QgsStringUtils::capitalize( const QString &string, QgsStringUtils::Capit
return temp;
}

case TitleCase:
case Qgis::Capitalization::TitleCase:
{
// yes, this is MASSIVELY simplifying the problem!!

@@ -103,8 +105,8 @@ QString QgsStringUtils::capitalize( const QString &string, QgsStringUtils::Capit
return result;
}

case UpperCamelCase:
QString result = QgsStringUtils::capitalize( string.toLower(), QgsStringUtils::ForceFirstLetterToCapital ).simplified();
case Qgis::Capitalization::UpperCamelCase:
QString result = QgsStringUtils::capitalize( string.toLower(), Qgis::Capitalization::ForceFirstLetterToCapital ).simplified();
result.remove( ' ' );
return result;
}
@@ -669,7 +671,7 @@ QString QgsStringUtils::wordWrap( const QString &string, const int length, const
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 2)
newstr.append( line.midRef( strCurrent ) );
#else
newstr.append( QStringView {line}.mid( strCurrent ) );
newstr.append( QStringView {line} .mid( strCurrent ) );
#endif
strCurrent = strLength;
}
@@ -20,7 +20,6 @@
#include <QRegularExpression>
#include <QList>
#include <QDomDocument>
#include <QFont> // for enum values

#ifndef QGSSTRINGUTILS_H
#define QGSSTRINGUTILS_H
@@ -185,25 +184,14 @@ class CORE_EXPORT QgsStringUtils
{
public:

//! Capitalization options
enum Capitalization
{
MixedCase = QFont::MixedCase, //!< Mixed case, ie no change
AllUppercase = QFont::AllUppercase, //!< Convert all characters to uppercase
AllLowercase = QFont::AllLowercase, //!< Convert all characters to lowercase
ForceFirstLetterToCapital = QFont::Capitalize, //!< Convert just the first letter of each word to uppercase, leave the rest untouched
TitleCase = QFont::Capitalize + 1000, //!< Simple title case conversion - does not fully grammatically parse the text and uses simple rules only. Note that this method does not convert any characters to lowercase, it only uppercases required letters. Callers must ensure that input strings are already lowercased.
UpperCamelCase = QFont::Capitalize + 1001, //!< Convert the string to upper camel case. Note that this method does not unaccent characters.
};

/**
* Converts a string by applying capitalization rules to the string.
* \param string input string
* \param capitalization capitalization type to apply
* \returns capitalized string
* \since QGIS 3.0
*/
static QString capitalize( const QString &string, Capitalization capitalization );
static QString capitalize( const QString &string, Qgis::Capitalization capitalization );

/**
* Makes a raw string safe for inclusion as a HTML/XML string literal.
Loading

0 comments on commit dae69ac

Please sign in to comment.