Exemple #1
0
int QFontProto::capitalization() const
{
  QFont *item = qscriptvalue_cast<QFont*>(thisObject());
  if (item)
    return item->capitalization();
  return 0;
}
void QgsAbstractVectorLayerLabeling::writeTextSymbolizer( QDomNode &parent, QgsPalLayerSettings &settings, const QgsStringMap &props ) const
{
  QDomDocument doc = parent.ownerDocument();

  // text symbolizer
  QDomElement textSymbolizerElement = doc.createElement( QStringLiteral( "se:TextSymbolizer" ) );
  parent.appendChild( textSymbolizerElement );

  // label
  QgsTextFormat format = settings.format();
  QFont font = format.font();
  QDomElement labelElement = doc.createElement( QStringLiteral( "se:Label" ) );
  textSymbolizerElement.appendChild( labelElement );
  if ( settings.isExpression )
  {
    labelElement.appendChild( doc.createComment( QStringLiteral( "SE Export for %1 not implemented yet" ).arg( settings.getLabelExpression()->dump() ) ) );
    labelElement.appendChild( doc.createTextNode( "Placeholder" ) );
  }
  else
  {
    if ( font.capitalization() == QFont::AllUppercase )
    {
      appendSimpleFunction( doc, labelElement, QStringLiteral( "strToUpperCase" ), settings.fieldName );
    }
    else if ( font.capitalization() == QFont::AllLowercase )
    {
      appendSimpleFunction( doc, labelElement, QStringLiteral( "strToLowerCase" ), settings.fieldName );
    }
    else if ( font.capitalization() == QFont::Capitalize )
    {
      appendSimpleFunction( doc, labelElement, QStringLiteral( "strCapitalize" ), settings.fieldName );
    }
    else
    {
      QDomElement propertyNameElement = doc.createElement( QStringLiteral( "ogc:PropertyName" ) );
      propertyNameElement.appendChild( doc.createTextNode( settings.fieldName ) );
      labelElement.appendChild( propertyNameElement );
    }
  }

  // font
  QDomElement fontElement = doc.createElement( QStringLiteral( "se:Font" ) );
  textSymbolizerElement.appendChild( fontElement );
  fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-family" ), font.family() ) );
  double fontSize = QgsSymbolLayerUtils::rescaleUom( format.size(), format.sizeUnit(), props );
  fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-size" ), QString::number( fontSize ) ) );
  if ( format.font().italic() )
  {
    fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-style" ), QStringLiteral( "italic" ) ) );
  }
  if ( format.font().bold() )
  {
    fontElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "font-weight" ), QStringLiteral( "bold" ) ) );
  }

  // label placement
  QDomElement labelPlacement = doc.createElement( QStringLiteral( "se:LabelPlacement" ) );
  textSymbolizerElement.appendChild( labelPlacement );
  double maxDisplacement = 0;
  double repeatDistance = 0;
  switch ( settings.placement )
  {
    case QgsPalLayerSettings::OverPoint:
    {
      QDomElement pointPlacement = doc.createElement( "se:PointPlacement" );
      labelPlacement.appendChild( pointPlacement );
      // anchor point
      QPointF anchor = quadOffsetToSldAnchor( settings.quadOffset );
      QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, anchor );
      // displacement
      if ( settings.xOffset > 0 || settings.yOffset > 0 )
      {
        QgsUnitTypes::RenderUnit offsetUnit =  settings.offsetUnits;
        double dx = QgsSymbolLayerUtils::rescaleUom( settings.xOffset, offsetUnit, props );
        double dy = QgsSymbolLayerUtils::rescaleUom( settings.yOffset, offsetUnit, props );
        QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( dx, dy ) );
      }
      // rotation
      if ( settings.angleOffset != 0 )
      {
        QDomElement rotation = doc.createElement( "se:Rotation" );
        pointPlacement.appendChild( rotation );
        rotation.appendChild( doc.createTextNode( QString::number( settings.angleOffset ) ) );
      }
    }
    break;
    case QgsPalLayerSettings::AroundPoint:
    case QgsPalLayerSettings::OrderedPositionsAroundPoint:
    {
      QDomElement pointPlacement = doc.createElement( "se:PointPlacement" );
      labelPlacement.appendChild( pointPlacement );

      // SLD cannot do either, but let's do a best effort setting the distance using
      // anchor point and displacement
      QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, QPointF( 0, 0.5 ) );
      QgsUnitTypes::RenderUnit distUnit = settings.distUnits;
      double radius = QgsSymbolLayerUtils::rescaleUom( settings.dist, distUnit, props );
      double offset = std::sqrt( radius * radius / 2 ); // make it start top/right
      maxDisplacement = radius + 1; // lock the distance
      QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( offset, offset ) );
    }
    break;
    case QgsPalLayerSettings::Horizontal:
    case QgsPalLayerSettings::Free:
    {
      // still a point placement (for "free" it's a fallback, there is no SLD equivalent)
      QDomElement pointPlacement = doc.createElement( "se:PointPlacement" );
      labelPlacement.appendChild( pointPlacement );
      QgsSymbolLayerUtils::createAnchorPointElement( doc, pointPlacement, QPointF( 0.5, 0.5 ) );
      QgsUnitTypes::RenderUnit distUnit = settings.distUnits;
      double dist = QgsSymbolLayerUtils::rescaleUom( settings.dist, distUnit, props );
      QgsSymbolLayerUtils::createDisplacementElement( doc, pointPlacement, QPointF( 0, dist ) );
      break;
    }
    case QgsPalLayerSettings::Line:
    case QgsPalLayerSettings::Curved:
    case QgsPalLayerSettings::PerimeterCurved:
    {
      QDomElement linePlacement = doc.createElement( "se:LinePlacement" );
      labelPlacement.appendChild( linePlacement );

      // perpendicular distance if required
      if ( settings.dist > 0 )
      {
        QgsUnitTypes::RenderUnit distUnit = settings.distUnits;
        double dist = QgsSymbolLayerUtils::rescaleUom( settings.dist, distUnit, props );
        QDomElement perpendicular = doc.createElement( "se:PerpendicularOffset" );
        linePlacement.appendChild( perpendicular );
        perpendicular.appendChild( doc.createTextNode( qgsDoubleToString( dist, 2 ) ) );
      }

      // repeat distance if required
      if ( settings.repeatDistance > 0 )
      {
        QDomElement repeat = doc.createElement( "se:Repeat" );
        linePlacement.appendChild( repeat );
        repeat.appendChild( doc.createTextNode( QStringLiteral( "true" ) ) );
        QDomElement gap = doc.createElement( "se:Gap" );
        linePlacement.appendChild( gap );
        repeatDistance = QgsSymbolLayerUtils::rescaleUom( settings.repeatDistance, settings.repeatDistanceUnit, props );
        gap.appendChild( doc.createTextNode( qgsDoubleToString( repeatDistance, 2 ) ) );
      }

      // always generalized
      QDomElement generalize = doc.createElement( "se:GeneralizeLine" );
      linePlacement.appendChild( generalize );
      generalize.appendChild( doc.createTextNode( QStringLiteral( "true" ) ) );
    }
    break;
  }

  // halo
  QgsTextBufferSettings buffer = format.buffer();
  if ( buffer.enabled() )
  {
    QDomElement haloElement = doc.createElement( QStringLiteral( "se:Halo" ) );
    textSymbolizerElement.appendChild( haloElement );

    QDomElement radiusElement = doc.createElement( QStringLiteral( "se:Radius" ) );
    haloElement.appendChild( radiusElement );
    // the SLD uses a radius, which is actually half of the link thickness the buffer size specifies
    double radius = QgsSymbolLayerUtils::rescaleUom( buffer.size(), buffer.sizeUnit(), props ) / 2;
    radiusElement.appendChild( doc.createTextNode( qgsDoubleToString( radius ) ) );

    QDomElement fillElement = doc.createElement( QStringLiteral( "se:Fill" ) );
    haloElement.appendChild( fillElement );
    fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill" ), buffer.color().name() ) );
    if ( buffer.opacity() != 1 )
    {
      fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill-opacity" ), QString::number( buffer.opacity() ) ) );
    }
  }

  // fill
  QDomElement fillElement = doc.createElement( QStringLiteral( "se:Fill" ) );
  textSymbolizerElement.appendChild( fillElement );
  fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill" ), format.color().name() ) );
  if ( format.opacity() != 1 )
  {
    fillElement.appendChild( QgsSymbolLayerUtils::createSvgParameterElement( doc, QStringLiteral( "fill-opacity" ), QString::number( format.opacity() ) ) );
  }

  // background graphic (not supported by SE 1.1, but supported by the GeoTools ecosystem as an extension)
  QgsTextBackgroundSettings background = format.background();
  if ( background.enabled() )
  {
    std::unique_ptr<QgsMarkerSymbolLayer> layer = backgroundToMarkerLayer( background );
    layer->writeSldMarker( doc, textSymbolizerElement, props );
  }

  // priority and zIndex, the default values are 0 and 5 in qgis (and between 0 and 10),
  // in the GeoTools ecosystem there is a single priority value set at 1000 by default
  if ( settings.priority != 5 || settings.zIndex > 0 )
  {
    QDomElement priorityElement = doc.createElement( QStringLiteral( "se:Priority" ) );
    textSymbolizerElement.appendChild( priorityElement );
    int priority = 500 + 1000 * settings.zIndex + ( settings.priority - 5 ) * 100;
    if ( settings.priority == 0 && settings.zIndex > 0 )
    {
      // small adjustment to make sure labels in z index n+1 are all above level n despite the priority value
      priority += 1;
    }
    priorityElement.appendChild( doc.createTextNode( QString::number( priority ) ) );
  }

  // vendor options for text appearance
  if ( font.underline() )
  {
    QDomElement vo = QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "underlineText" ), QStringLiteral( "true" ) );
    textSymbolizerElement.appendChild( vo );
  }
  if ( font.strikeOut() )
  {
    QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "strikethroughText" ), QStringLiteral( "true" ) );
    textSymbolizerElement.appendChild( vo );
  }
  // vendor options for text positioning
  if ( maxDisplacement > 0 )
  {
    QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "maxDisplacement" ), qgsDoubleToString( maxDisplacement, 2 ) );
    textSymbolizerElement.appendChild( vo );
  }
  if ( settings.placement == QgsPalLayerSettings::Curved || settings.placement == QgsPalLayerSettings::PerimeterCurved )
  {
    QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "followLine" ), QStringLiteral( "true" ) );
    textSymbolizerElement.appendChild( vo );
    if ( settings.maxCurvedCharAngleIn > 0 || settings.maxCurvedCharAngleOut > 0 )
    {
      // SLD has no notion for this, the GeoTools ecosystem can only do a single angle
      double angle = std::min( std::fabs( settings.maxCurvedCharAngleIn ), std::fabs( settings.maxCurvedCharAngleOut ) );
      QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "maxAngleDelta" ), qgsDoubleToString( angle ) );
      textSymbolizerElement.appendChild( vo );
    }
  }
  if ( repeatDistance > 0 )
  {
    QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "repeat" ), qgsDoubleToString( repeatDistance, 2 ) );
    textSymbolizerElement.appendChild( vo );
  }
  // miscellaneous options
  if ( settings.displayAll )
  {
    QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "conflictResolution" ), QStringLiteral( "false" ) );
    textSymbolizerElement.appendChild( vo );
  }
  if ( settings.upsidedownLabels == QgsPalLayerSettings::ShowAll )
  {
    QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "forceLeftToRight" ), QStringLiteral( "false" ) );
    textSymbolizerElement.appendChild( vo );
  }
  if ( settings.mergeLines )
  {
    QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "group" ), QStringLiteral( "yes" ) );
    textSymbolizerElement.appendChild( vo );
    if ( settings.labelPerPart )
    {
      QDomElement vo =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "labelAllGroup" ), QStringLiteral( "true" ) );
      textSymbolizerElement.appendChild( vo );
    }
  }
  // background symbol resize handling
  if ( background.enabled() )
  {
    // enable resizing if needed
    switch ( background.sizeType() )
    {
      case QgsTextBackgroundSettings::SizeBuffer:
      {
        QString resizeType;
        if ( background.type() == QgsTextBackgroundSettings::ShapeRectangle || background.type() == QgsTextBackgroundSettings::ShapeEllipse )
        {
          resizeType = QStringLiteral( "stretch" );
        }
        else
        {
          resizeType = QStringLiteral( "proportional" );
        }
        QDomElement voResize =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-resize" ), resizeType );
        textSymbolizerElement.appendChild( voResize );

        // now hadle margin
        QSizeF size = background.size();
        if ( size.width() > 0 || size.height() > 0 )
        {
          double x = QgsSymbolLayerUtils::rescaleUom( size.width(), background.sizeUnit(), props );
          double y = QgsSymbolLayerUtils::rescaleUom( size.height(), background.sizeUnit(), props );
          // in case of ellipse qgis pads the size generously to make sure the text is inside the ellipse
          // the following seems to do the trick and keep visual output similar
          if ( background.type() == QgsTextBackgroundSettings::ShapeEllipse )
          {
            x += fontSize / 2;
            y += fontSize;
          }
          QString resizeSpec = QString( "%1 %2" ).arg( qgsDoubleToString( x, 2 ), qgsDoubleToString( y, 2 ) );
          QDomElement voMargin =  QgsSymbolLayerUtils::createVendorOptionElement( doc, QStringLiteral( "graphic-margin" ), resizeSpec );
          textSymbolizerElement.appendChild( voMargin );
        }
        break;
      }
      case QgsTextBackgroundSettings::SizeFixed:
      case QgsTextBackgroundSettings::SizePercent:
        // nothing to do here
        break;
    }
  }
}