Merge pull request #32629 from m-kuhn/dxf_hali_vali

[dxf] HAlign/VAlign support for TEXT
This commit is contained in:
Matthias Kuhn 2019-11-07 16:37:06 +01:00 committed by GitHub
commit e5df863302
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 361 additions and 35 deletions

View File

@ -6,3 +6,21 @@ QgsDxfExport.ExportResult.DeviceNotWritableError.__doc__ = "Device not writable
QgsDxfExport.ExportResult.EmptyExtentError.__doc__ = "Empty extent, no extent given and no extent could be derived from layers"
QgsDxfExport.ExportResult.__doc__ = 'The result of an export as dxf operation\n\n.. versionadded:: 3.10.1\n\n' + '* ``Success``: ' + QgsDxfExport.ExportResult.Success.__doc__ + '\n' + '* ``InvalidDeviceError``: ' + QgsDxfExport.ExportResult.InvalidDeviceError.__doc__ + '\n' + '* ``DeviceNotWritableError``: ' + QgsDxfExport.ExportResult.DeviceNotWritableError.__doc__ + '\n' + '* ``EmptyExtentError``: ' + QgsDxfExport.ExportResult.EmptyExtentError.__doc__
# --
# monkey patching scoped based enum
QgsDxfExport.VAlign.VBaseLine.__doc__ = "Top (0)"
QgsDxfExport.VAlign.VBottom.__doc__ = "Bottom (1)"
QgsDxfExport.VAlign.VMiddle.__doc__ = "Middle (2)"
QgsDxfExport.VAlign.VTop.__doc__ = "Top (3)"
QgsDxfExport.VAlign.Undefined.__doc__ = "Undefined"
QgsDxfExport.VAlign.__doc__ = 'Vertical alignments.\n\n' + '* ``VBaseLine``: ' + QgsDxfExport.VAlign.VBaseLine.__doc__ + '\n' + '* ``VBottom``: ' + QgsDxfExport.VAlign.VBottom.__doc__ + '\n' + '* ``VMiddle``: ' + QgsDxfExport.VAlign.VMiddle.__doc__ + '\n' + '* ``VTop``: ' + QgsDxfExport.VAlign.VTop.__doc__ + '\n' + '* ``Undefined``: ' + QgsDxfExport.VAlign.Undefined.__doc__
# --
# monkey patching scoped based enum
QgsDxfExport.HAlign.HLeft.__doc__ = "Left (0)"
QgsDxfExport.HAlign.HCenter.__doc__ = "Centered (1)"
QgsDxfExport.HAlign.HRight.__doc__ = "Right (2)"
QgsDxfExport.HAlign.HAligned.__doc__ = "Aligned = (3) (if VAlign==0)"
QgsDxfExport.HAlign.HMiddle.__doc__ = "Middle = (4) (if VAlign==0)"
QgsDxfExport.HAlign.HFit.__doc__ = "Fit into point = (5) (if VAlign==0)"
QgsDxfExport.HAlign.Undefined.__doc__ = "Undefined"
QgsDxfExport.HAlign.__doc__ = 'Horizontal alignments.\n\n' + '* ``HLeft``: ' + QgsDxfExport.HAlign.HLeft.__doc__ + '\n' + '* ``HCenter``: ' + QgsDxfExport.HAlign.HCenter.__doc__ + '\n' + '* ``HRight``: ' + QgsDxfExport.HAlign.HRight.__doc__ + '\n' + '* ``HAligned``: ' + QgsDxfExport.HAlign.HAligned.__doc__ + '\n' + '* ``HMiddle``: ' + QgsDxfExport.HAlign.HMiddle.__doc__ + '\n' + '* ``HFit``: ' + QgsDxfExport.HAlign.HFit.__doc__ + '\n' + '* ``Undefined``: ' + QgsDxfExport.HAlign.Undefined.__doc__
# --

View File

@ -60,6 +60,26 @@ The attribute value is used for layer names.
EmptyExtentError
};
enum class VAlign
{
VBaseLine,
VBottom,
VMiddle,
VTop,
Undefined
};
enum class HAlign
{
HLeft,
HCenter,
HRight,
HAligned,
HMiddle,
HFit,
Undefined
};
QgsDxfExport();
%Docstring
Constructor for QgsDxfExport.
@ -385,7 +405,7 @@ Write circle (as polyline)
.. versionadded:: 2.15
%End
void writeText( const QString &layer, const QString &text, const QgsPoint &pt, double size, double angle, const QColor &color ) /PyName=writeTextV2/;
void writeText( const QString &layer, const QString &text, const QgsPoint &pt, double size, double angle, const QColor &color, QgsDxfExport::HAlign hali = QgsDxfExport::HAlign::Undefined, QgsDxfExport::VAlign vali = QgsDxfExport::VAlign::Undefined ) /PyName=writeTextV2/;
%Docstring
Write text (TEXT)

View File

@ -1321,6 +1321,64 @@ void QgsDxfExport::writeLine( const QgsPoint &pt1, const QgsPoint &pt2, const QS
writePolyline( QgsPointSequence() << pt1 << pt2, layer, lineStyleName, color, width );
}
void QgsDxfExport::writeText( const QString &layer, const QString &text, pal::LabelPosition *label, const QgsPalLayerSettings &layerSettings, const QgsExpressionContext &expressionContext )
{
double lblX = label->getX();
double lblY = label->getY();
HAlign hali = HAlign::Undefined;
VAlign vali = VAlign::Undefined;
const QgsPropertyCollection &props = layerSettings.dataDefinedProperties();
if ( props.isActive( QgsPalLayerSettings::Hali ) )
{
hali = HAlign::HLeft;
QVariant exprVal = props.value( QgsPalLayerSettings::Hali, expressionContext );
if ( exprVal.isValid() )
{
const QString haliString = exprVal.toString();
if ( haliString.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 )
{
hali = HAlign::HCenter;
}
else if ( haliString.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 )
{
hali = HAlign::HRight;
}
}
}
std::unique_ptr<QFontMetricsF> labelFontMetrics( new QFontMetricsF( layerSettings.format().font() ) );
//vertical alignment
if ( props.isActive( QgsPalLayerSettings::Vali ) )
{
vali = VAlign::VBottom;
QVariant exprVal = props.value( QgsPalLayerSettings::Vali, expressionContext );
if ( exprVal.isValid() )
{
const QString valiString = exprVal.toString();
if ( valiString.compare( QLatin1String( "Bottom" ), Qt::CaseInsensitive ) != 0 )
{
if ( valiString.compare( QLatin1String( "Base" ), Qt::CaseInsensitive ) == 0 )
{
vali = VAlign::VBaseLine;
}
else if ( valiString.compare( QLatin1String( "Half" ), Qt::CaseInsensitive ) == 0 )
{
vali = VAlign::VMiddle;
}
else //'Cap' or 'Top'
{
vali = VAlign::VTop;
}
}
}
}
writeText( layer, text, QgsPoint( lblX, lblY ), label->getHeight(), label->getAlpha() * 180.0 / M_PI, layerSettings.format().color(), hali, vali );
}
void QgsDxfExport::writePoint( const QString &layer, const QColor &color, const QgsPoint &pt )
{
writeGroup( 0, QStringLiteral( "POINT" ) );
@ -1391,20 +1449,30 @@ void QgsDxfExport::writeCircle( const QString &layer, const QColor &color, const
writeGroup( 42, 1.0 );
}
void QgsDxfExport::writeText( const QString &layer, const QString &text, const QgsPoint &pt, double size, double angle, const QColor &color )
void QgsDxfExport::writeText( const QString &layer, const QString &text, const QgsPoint &pt, double size, double angle, const QColor &color, HAlign hali, VAlign vali )
{
writeGroup( 0, QStringLiteral( "TEXT" ) );
writeHandle();
writeGroup( 100, QStringLiteral( "AcDbEntity" ) );
// writeGroup( 6, "Continuous" ); // Line style
// writeGroup( 370, 18 ); // Line weight
writeGroup( 100, QStringLiteral( "AcDbText" ) );
writeGroup( 8, layer );
writeGroup( color );
writeGroup( 0, pt );
if ( hali != HAlign::Undefined || vali != VAlign::Undefined )
writeGroup( 1, pt ); // Second alignment point
writeGroup( 40, size );
writeGroup( 1, text );
writeGroup( 50, angle );
if ( hali != HAlign::Undefined )
writeGroup( 72, static_cast<int>( hali ) );
writeGroup( 7, QStringLiteral( "STANDARD" ) ); // so far only support for standard font
writeGroup( 100, QStringLiteral( "AcDbText" ) );
if ( vali != VAlign::Undefined )
{
writeGroup( 73, static_cast<int>( vali ) );
}
}
void QgsDxfExport::writeMText( const QString &layer, const QString &text, const QgsPoint &pt, double width, double angle, const QColor &color )
@ -2260,7 +2328,7 @@ void QgsDxfExport::drawLabel( const QString &layerId, QgsRenderContext &context,
if ( mFlags & FlagNoMText )
{
writeText( dxfLayer, txt, QgsPoint( label->getX(), label->getY() ), label->getHeight(), label->getAlpha() * 180.0 / M_PI, tmpLyr.format().color() );
writeText( dxfLayer, txt, label, tmpLyr, context.expressionContext() );
}
else
{

View File

@ -108,6 +108,30 @@ class CORE_EXPORT QgsDxfExport
EmptyExtentError //!< Empty extent, no extent given and no extent could be derived from layers
};
/**
* Vertical alignments.
*/
enum class VAlign : int
{
VBaseLine = 0, //!< Top (0)
VBottom = 1, //!< Bottom (1)
VMiddle = 2, //!< Middle (2)
VTop = 3, //!< Top (3)
Undefined = 9999 //!< Undefined
};
//! Horizontal alignments.
enum class HAlign : int
{
HLeft = 0, //!< Left (0)
HCenter = 1, //!< Centered (1)
HRight = 2, //!< Right (2)
HAligned = 3, //!< Aligned = (3) (if VAlign==0)
HMiddle = 4, //!< Middle = (4) (if VAlign==0)
HFit = 5, //!< Fit into point = (5) (if VAlign==0)
Undefined = 9999 //!< Undefined
};
/**
* Constructor for QgsDxfExport.
*/
@ -415,7 +439,7 @@ class CORE_EXPORT QgsDxfExport
* \note available in Python bindings as writeTextV2
* \since QGIS 2.15
*/
void writeText( const QString &layer, const QString &text, const QgsPoint &pt, double size, double angle, const QColor &color ) SIP_PYNAME( writeTextV2 );
void writeText( const QString &layer, const QString &text, const QgsPoint &pt, double size, double angle, const QColor &color, QgsDxfExport::HAlign hali = QgsDxfExport::HAlign::Undefined, QgsDxfExport::VAlign vali = QgsDxfExport::VAlign::Undefined ) SIP_PYNAME( writeTextV2 );
/**
* Write mtext (MTEXT)
@ -502,6 +526,11 @@ class CORE_EXPORT QgsDxfExport
void writeSymbolLayerLinetype( const QgsSymbolLayer *symbolLayer );
void writeLinetype( const QString &styleName, const QVector<qreal> &pattern, QgsUnitTypes::RenderUnit u );
/**
* Helper method to calculate text properties from (PAL) label
*/
void writeText( const QString &layer, const QString &text, pal::LabelPosition *label, const QgsPalLayerSettings &layerSettings, const QgsExpressionContext &expressionContext );
/**
* Writes geometry generator symbol layer
* \param ctx the symbol render context

View File

@ -30,6 +30,9 @@
#include "qgsvectorlayerlabeling.h"
#include <QTemporaryFile>
Q_DECLARE_METATYPE( QgsDxfExport::HAlign )
Q_DECLARE_METATYPE( QgsDxfExport::VAlign )
class TestQgsDxfExport : public QObject
{
Q_OBJECT
@ -49,6 +52,8 @@ class TestQgsDxfExport : public QObject
void testMTextNoSymbology(); //tests if label export works if layer has vector renderer type 'no symbols'
void testMTextEscapeSpaces();
void testText();
void testTextAlign();
void testTextAlign_data();
void testGeometryGeneratorExport();
void testCurveExport();
void testCurveExport_data();
@ -65,7 +70,7 @@ class TestQgsDxfExport : public QObject
void setDefaultLabelParams( QgsPalLayerSettings &settings );
QString getTempFileName( const QString &file ) const;
bool fileContainsText( const QString &path, const QString &text ) const;
bool fileContainsText( const QString &path, const QString &text, QString *debugInfo = nullptr ) const;
bool testMtext( QgsVectorLayer *vlayer, const QString &tempFileName ) const;
};
@ -336,32 +341,182 @@ void TestQgsDxfExport::testText()
QCOMPARE( d.writeToFile( &dxfFile, QStringLiteral( "CP1252" ) ), QgsDxfExport::ExportResult::Success );
dxfFile.close();
QString debugInfo;
QVERIFY2( fileContainsText( file, "TEXT\n"
" 5\n"
"**no check**\n"
"100\n"
"AcDbEntity\n"
"100\n"
"AcDbText\n"
" 8\n"
"points\n"
"420\n"
"**no check**\n"
" 10\n"
"**no check**\n"
" 20\n"
"**no check**\n"
" 40\n"
"**no check**\n"
" 1\n"
"Biplane\n"
" 50\n"
"0.0\n"
" 7\n"
"STANDARD\n"
"100\n"
"AcDbText", &debugInfo ), debugInfo.toUtf8().constData() );
}
QVERIFY( fileContainsText( file, "TEXT\n"
" 5\n"
"dd\n"
"100\n"
"AcDbEntity\n"
"100\n"
"AcDbText\n"
" 8\n"
"points\n"
"420\n"
"**no check**\n"
" 10\n"
"**no check**\n"
" 20\n"
"**no check**\n"
" 40\n"
"**no check**\n"
" 1\n"
"Biplane\n"
" 50\n"
"0.0\n"
" 7\n"
"STANDARD\n"
"100\n"
"AcDbText" ) );
void TestQgsDxfExport::testTextAlign()
{
QFETCH( QgsDxfExport::HAlign, dxfHali );
QFETCH( QgsDxfExport::VAlign, dxfVali );
QFETCH( QString, hali );
QFETCH( QString, vali );
QgsPalLayerSettings settings;
settings.fieldName = QStringLiteral( "text" );
QgsPropertyCollection props = settings.dataDefinedProperties();
QgsProperty halignProp = QgsProperty();
halignProp.setStaticValue( hali );
props.setProperty( QgsPalLayerSettings::Hali, halignProp );
QgsProperty posXProp = QgsProperty();
posXProp.setExpressionString( QStringLiteral( "x($geometry)" ) );
props.setProperty( QgsPalLayerSettings::PositionX, posXProp );
QgsProperty valignProp = QgsProperty();
valignProp.setStaticValue( vali );
props.setProperty( QgsPalLayerSettings::Vali, valignProp );
QgsProperty posYProp = QgsProperty();
posXProp.setExpressionString( QStringLiteral( "y($geometry)" ) );
props.setProperty( QgsPalLayerSettings::PositionY, posYProp );
settings.setDataDefinedProperties( props );
QgsTextFormat format;
format.setFont( QgsFontUtils::getStandardTestFont( QStringLiteral( "Bold" ) ).family() );
format.setSize( 12 );
format.setNamedStyle( QStringLiteral( "Bold" ) );
format.setColor( QColor( 200, 0, 200 ) );
settings.setFormat( format );
std::unique_ptr< QgsVectorLayer > vl = qgis::make_unique< QgsVectorLayer >( QStringLiteral( "Point?crs=epsg:2056&field=text:string" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
QgsGeometry g = QgsGeometry::fromWkt( "Point(2684679.392 1292182.527)" );
QgsFeature f( vl->fields() );
f.setGeometry( g );
f.setAttribute( 0, QStringLiteral( "--- MY TEXT ---" ) );
vl->dataProvider()->addFeatures( QgsFeatureList() << f );
vl->setLabeling( new QgsVectorLayerSimpleLabeling( settings ) );
vl->setLabelsEnabled( true );
QgsMapSettings mapSettings;
QSize size( 640, 480 );
mapSettings.setOutputSize( size );
mapSettings.setExtent( QgsRectangle( 2684658.97702550329267979, 1292165.99626861698925495, 2684711.73293229937553406, 1292188.10791716771200299 ) );
mapSettings.setLayers( QList<QgsMapLayer *>() << vl.get() );
mapSettings.setOutputDpi( 96 );
mapSettings.setDestinationCrs( vl->crs() );
QgsDxfExport d;
d.addLayers( QList< QgsDxfExport::DxfLayer >() << QgsDxfExport::DxfLayer( vl.get() ) );
d.setMapSettings( mapSettings );
d.setSymbologyScale( 1000 );
d.setSymbologyExport( QgsDxfExport::FeatureSymbology );
d.setFlags( QgsDxfExport::FlagNoMText );
d.setExtent( mapSettings.extent() );
static int testNumber = 0;
++testNumber;
QString file = getTempFileName( QStringLiteral( "text_dxf_%1_%2" ).arg( hali, vali ) );
QFile dxfFile( file );
QCOMPARE( d.writeToFile( &dxfFile, QStringLiteral( "CP1252" ) ), QgsDxfExport::ExportResult::Success );
dxfFile.close();
QString debugInfo;
QVERIFY2( fileContainsText( file, QStringLiteral( "TEXT\n"
" 5\n"
"89\n"
"100\n"
"AcDbEntity\n"
"100\n"
"AcDbText\n"
" 8\n"
"vl\n"
"420\n"
"**no check**\n"
" 10\n"
"REGEX ^2684679\\.39\\d*\n"
" 20\n"
"REGEX ^1292182\\.52\\d*\n"
" 11\n"
"REGEX ^2684679\\.39\\d*\n"
" 21\n"
"REGEX ^1292182\\.52\\d*\n"
" 40\n"
"**no check**\n"
" 1\n"
"--- MY TEXT ---\n"
" 50\n"
"0.0\n"
" 72\n"
" %1\n"
" 7\n"
"STANDARD\n"
"100\n"
"AcDbText\n"
" 73\n"
" %2" ).arg( QString::number( static_cast<int>( dxfHali ) ), QString::number( static_cast<int>( dxfVali ) ) ), &debugInfo ), debugInfo.toUtf8().constData() );
}
void TestQgsDxfExport::testTextAlign_data()
{
QTest::addColumn<QgsDxfExport::HAlign>( "dxfHali" );
QTest::addColumn<QgsDxfExport::VAlign>( "dxfVali" );
QTest::addColumn<QString>( "hali" );
QTest::addColumn<QString>( "vali" );
QTest::newRow( "Align left bottom" )
<< QgsDxfExport::HAlign::HLeft
<< QgsDxfExport::VAlign::VBottom
<< QStringLiteral( "Left" )
<< QStringLiteral( "Bottom" );
QTest::newRow( "Align center bottom" )
<< QgsDxfExport::HAlign::HCenter
<< QgsDxfExport::VAlign::VBottom
<< QStringLiteral( "Center" )
<< QStringLiteral( "Bottom" );
QTest::newRow( "Align right bottom" )
<< QgsDxfExport::HAlign::HRight
<< QgsDxfExport::VAlign::VBottom
<< QStringLiteral( "Right" )
<< QStringLiteral( "Bottom" );
QTest::newRow( "Align left top" )
<< QgsDxfExport::HAlign::HLeft
<< QgsDxfExport::VAlign::VTop
<< QStringLiteral( "Left" )
<< QStringLiteral( "Top" );
QTest::newRow( "Align right cap" )
<< QgsDxfExport::HAlign::HRight
<< QgsDxfExport::VAlign::VTop
<< QStringLiteral( "Right" )
<< QStringLiteral( "Cap" );
QTest::newRow( "Align left base" )
<< QgsDxfExport::HAlign::HLeft
<< QgsDxfExport::VAlign::VBaseLine
<< QStringLiteral( "Left" )
<< QStringLiteral( "Base" );
QTest::newRow( "Align center half" )
<< QgsDxfExport::HAlign::HCenter
<< QgsDxfExport::VAlign::VMiddle
<< QStringLiteral( "Center" )
<< QStringLiteral( "Half" );
}
bool TestQgsDxfExport::testMtext( QgsVectorLayer *vlayer, const QString &tempFileName ) const
@ -592,32 +747,68 @@ void TestQgsDxfExport::testCurveExport_data()
}
bool TestQgsDxfExport::fileContainsText( const QString &path, const QString &text ) const
bool TestQgsDxfExport::fileContainsText( const QString &path, const QString &text, QString *debugInfo ) const
{
QStringList debugLines;
const QStringList searchLines = text.split( '\n' );
QFile file( path );
if ( !file.open( QIODevice::ReadOnly ) )
return false;
QTextStream in( &file );
QString line;
QString failedLine;
QString failedCandidateLine;
int maxLine = 0;
do
{
bool found = true;
int i = 0;
for ( const QString &searchLine : searchLines )
{
line = in.readLine();
if ( searchLine != QLatin1String( "**no check**" ) && line != searchLine )
if ( searchLine != QLatin1String( "**no check**" ) )
{
found = false;
break;
if ( line != searchLine )
{
bool ok = false;
if ( searchLine.startsWith( QLatin1String( "REGEX " ) ) )
{
QRegularExpression re( searchLine.right( 7 ) );
if ( re.match( line ).hasMatch() )
ok = true;
}
if ( !ok )
{
if ( i == maxLine )
{
failedLine = searchLine;
failedCandidateLine = line;
}
found = false;
break;
}
}
}
int i = 1;
i++;
if ( i > maxLine )
{
maxLine = i;
debugLines.append( QStringLiteral( "\n Found line: %1" ).arg( searchLine ) );
}
}
if ( found )
return true;
}
while ( !line.isNull() );
if ( debugInfo )
{
while ( debugLines.size() > 10 )
debugLines.removeFirst();
debugInfo->append( debugLines.join( QLatin1String( "" ) ) );
debugInfo->append( QStringLiteral( "\n Failed on line %1" ).arg( failedLine ) );
debugInfo->append( QStringLiteral( "\n Candidate line %1" ).arg( failedCandidateLine ) );
}
return false;
}